ZYBOでPmodCLP(Parallel Interface LCD)を動かす
FPGAハマりの勢いで、Alteraに加えて、Xilinxにも手を出してしまいました。
今回買ったのは、XilinxのSoC (ARMコア)付きFPGA Zynqを搭載したZYBOを秋月さんで購入しました。SoC付きのFPGAを試すのなら、Altera系のDE1-SoCが自然な流れですが、HDLでなくC/C++を使った高位合成(HLS)が無償で使えるXilixの開発環境(Vivado)も試したくなって、Xilinxに手を出してしまいました。下の写真の上段がDE0-CVで、下段が今回買ったZYBOです。
高位合成のお試しは、下の写真にある「FPGAマガジンNo.14」を参考してやろうと思っているのですが、その前にZYBOにLCDを繋いでみました。
ZYBOは写真で見て分かる通り、DE0-CVに比べて7SEG-LEDがないなど、表示系デバイスが少ないです。その代わりに、ZYBOにはPmodと呼ばれる拡張コネクターが6個付いており、ZYBOの発売元であるDigilent社から各種のPmod拡張モジュールが販売されています。回路のデバッグを行う際など表示系があると便利だと思い、LEDが8個搭載されたPmod8LD(写真右側)とParallel接続のLCD PmodCLP(写真左側)を購入しました。(FedExを使った送料込みで、$50超となり、ちょっと高かったですが)。
Pmodの種別
ZYBOのPmodコネクタにはPS(ARMコア)につながっているPmod MIOと、PL(FPGA部)につながっているStandard Pmod, Hi-Speed Pmod, XADC Pmodがあります。PmodCLPはPmodコネクタ2つを占有するのですが、基板下側に4つ並んでいるPmodは左側1つがStandard Pmodで残りの3つはHi-Speed Pmodです。マニュアルによると、Hi-Speed Pmodは隣接する2つの信号ピンをペアで差動出力として使用することによって高速伝送ができるコネクターで、信号ペアを別々に使うとクロストークが発生すると記載があります。
しかしながら、ZYBOの構成ではどうしても1つはHi-Speed Pmodを使う必要があるので、買ってからこの制約に気がついて、果たしてLCDが動くのか気になっていたのでまずはこちらを試してみました。結果はちゃんと動いています。
今回は、制御信号4pinはStandard-Pmodコネクター(JE)を使用して、データーバス8pinをHi-Speed Pmodコネクター(JD)につなぎました。PmodCLPの回路図を見ると、データーバスには200Ωの抵抗が直列で入っていること(これは短絡対策の保護も兼ねていると思いますが)、差動出力で使う信号ペア(例えば、J1のpin-1とpin-2)をダイオードで2.5V電源にクランプしており、この辺りがHi-speed Pmodコネクターに繋いだ時のクロストーク対策になっているのかしらと思ったりしています(データーバスはFPGAの3.3V出力に接続するのでダイオードを使った過電圧保護は不要だと思うのですが、何のために入っているのか)。
ZynqのIP作成
Zynq(SoC付きFPGA)では まずARMコアありきで、ARMコアに回路ブロックを接続します。Vivadoではライブラリ化された回路ブロック(IP)をZinqに接続していきます。このあたりは、AlteraのQsysと同様の考え方だと思うのですが、QsysよりVivadoのIP Integratorの方が配線やアドレス設定を自動化してくれる度合いが高く使い易い感じがしました。またQsysではTopレベルのHDLは手で書く必要がありますが、Vivadoでは自動生成してくれます。
IPを作成する手順ですが、Digilentが提供している、Vivado用のBorad Fileをインストールしておくと、オンボードのLED/SWはGPIOのメニューに表示されるようになります。ZYBOボードを指定することでZYBOが使用しているZynq FPGAのタイプも自動的に選択してくれるためこのBorad Fileはインストールしておくと便利です。また、Digilentが提供しているPmod-Libraryがあるのですが、PmodCLPとPmod8LDにはライブラリがありません。
そのため、Zynq FPGAにARMコアとGPIOをVivadoのIP Integratorを使って配置して、GPIOにPmodCLPのデータバスと制御信号を接続します。
- IP IntegratorのCreate Block Designを選択、新規Designを作成。Design名はsystemとしました(IP Integratorの使い方はGetting Started with Zynqに詳細な解説があります)
- Add IPを使って、Zynq-7 Processing SystemとAXI GPIOを配置して接続します
- GPIOは「Enable Dual Channel」をチェックして、GPIO2も有効にし、GPIOをデーター用の8-bit入出力(All input/ All output双方をチェックしない)、GPIO2を制御用の4-bitの出力ポートに設定します。
- GPIOポートの名称を「LCD-DB」、GPIO2のポート名を「LCD-CTL」と設定します。
制約ファイルの作成
次に、FPGAのピンとPmodのピン(GPIOの端子)を関連付ける制約ファイルを作成します。この手順は、このブログを参考にさせていただきました。概要は以下の通りです。
- IP作成ができたら、HDL Wrappperを作成。SourcesウインドウのIP SourcesタブからBlock Design名(今回の例ではsystem)を右クリック、「Create HDL Wrapper...」を選択します。
- 次に制約ファイルを作成。SourcesウィンドウのHierarchyタブにおいて、Constraints > constrs_1上にて右クリック「Add Source」を選択、Add or create constraints選択して、PmodLCD.xdcの名称で制約ファイルを作成します。
- Run Implementationを実行した後、Open Implementation Designを選択してOKをクリック、画面下のI/O Portsタブを選択して、GPIOポートのPackage Pin列にFPGAのpin番号を割り付けます。今回は、LCD-DBポート(lcd_db_tri_io[0]〜[7])にPmod JDコネクターに対応したFPGAの端子を割り当て、LDC-CTLポート(lcd_ctl_tri_o[0]〜[3])にはPmod-JEコネクターの下段pin(JE7〜JE10に対応するFPGAのpin)を以下のように割り当てました。
- 加えて、I/O StdをLVCMOS33に変更します。
- 割り当てができたら、CTRL-Sでファイルを保存します。
一度割り当てを作ってしまえば、次からは制約ファイル(PmodLCD.xdc)インポートすることでFPGAのPin割り当てを自動的に行うことができます。
Bitstream(FPGA configデータ)の生成とSDKへのExport
- Generate Btstramを実行後、File > Export > Export Hardware…から、Include bitstreamチェックしてOKクリック。
- File > Launch SDKを選択して、EclipseベースのSDKでARMコア用のLCD制御ソフトを作成します。
LCD制御ソフトの作成
DigilentのPmodCLP用「Library and MPLAB Example」にサンプルコードがあるのですが、中身を見るとArduino用のLCDライブラリが入っています。このままでは使えないので、空のC++プロジェクトを作って、コードを以下のようにZynqのベアメタルARM用に書き換えました。
/************************************************************************/ /* */ /* LCDP.h -- Declaration for LCDP library */ /* */ /************************************************************************/ /* Author: Cristian Fatu */ /* Copyright 2011, Digilent Inc. */ /************************************************************************/ /* File Description: */ /* This file declares LCDP library functions and constants involved*/ /* */ /************************************************************************/ /* Revision History: */ /* */ /* 12/10/2011(CristianF): created */ /* 21/08/2016(Todotani): adapted fro ZYBO PmodCLP */ /* */ /************************************************************************/ #if !defined(LCDP_H) #define LCDP_H /* ------------------------------------------------------------ */ /* Include File Definitions */ /* ------------------------------------------------------------ */ #include <inttypes.h> #include "xparameters.h" #include "xgpio.h" #include "sleep.h" /* ------------------------------------------------------------ */ /* Definitions */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* Errors Definitions */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* Command codes Definitions */ /* ------------------------------------------------------------ */ #define LCDP_CMD_LcdFcnInit 0x38 // function set command, (8-bit interface, 2 lines, and 5x8 dots) #define LCDP_CMD_LcdCtlInit 0x08 // display control set command #define LCDP_CMD_LcdClear 0x01 // clear display command #define LCDP_CMD_LcdRetHome 0x02 // return home command #define LCDP_CMD_LcdDisplayShift 0x18 // shift display command #define LCDP_CMD_LcdCursorShift 0x10 // shift cursor command #define LCDP_CMD_LcdSetDdramPos 0x80 // set DDRAM position command #define LCDP_CMD_LcdSetCgramPos 0x40 // set CGRAM position command #define LCDP_MSK_BStatus 0x80 // bit busy #define LCDP_MSK_ShiftRL 0x04 // shift direction mask #define LCDP_OPT_DisplayOn 0x4 // Set Display On option #define LCDP_OPT_CursorOn 0x2 // Set Cursor On option #define LCDP_OPT_BlinkOn 0x1 // Set Blink On option /* ------------------------------------------------------------ */ /* Parameters Definitions */ /* ------------------------------------------------------------ */ #define LCDP_DISP_SetOptionDisplayOn 0x4 // Set Display On option #define LCDP_DISP_SetOptionCursorOn 0x2 // Set Cursor On option #define LCDP_DISP_SetBlinkOn 0x1 // Set Blink On option /* ------------------------------------------------------------ */ /* Class Declarations */ /* ------------------------------------------------------------ */ #define mskLCDPDat07 0x000000FF #define LCDP_ERR_SUCCESS 0 // The action completed successfully #define LCDP_ERR_UCHAR_POSITION_INVALID 0x20 // The user character position is not correct #define LCDP_ERR_ARG_ROW_RANGE 0x80 // The row index is not valid #define LCDP_ERR_ARG_COL_RANGE 0x40 // The column index is not valid #define LCDP_NO_ROWS 2 #define LCDP_NO_COLS 40 #define LCDP_NO_UCHARS 8 /* ------------------------------------------------------------ */ /* Control Singnals */ /* ------------------------------------------------------------ */ #define RS 0b0001 // Register Select: High for Data Transfer, Low for Instruction Transfer #define RW 0b0010 // Read/Write signal: High for Read mode, Low for Write mode #define EN 0b0100 // Read/Write Enable: High for Read, falling edge writes data #define BL 0b1000 class LCDP { private: XGpio PmodLCD; uint8_t control; uint8_t m_bDisplayMode; uint8_t ReadByte(); void WriteByte(uint8_t bData); void WaitUntilNotBusy(); void WriteCommand(uint8_t bCmd); void WriteDataByte(uint8_t bData); void SetWriteCgramPosition(uint8_t bAdr); void SetWriteDdramPosition(uint8_t bAdr); uint8_t ReadStatus(); public: LCDP(); void begin(); void DisplayClear(); void ReturnHome(); void SetDisplay(bool fDisplayOn); void SetCursor(bool fCursorOn); void SetBlink(bool fBlinkOn); void SetBacklight(bool fBacklightOn); uint8_t SetPos(uint8_t idxLine, uint8_t idxCol); uint8_t WriteStringAtPos(char *szLn, uint8_t idxLine, uint8_t idxCol); void DisplayShift(bool fRight); void CursorShift(bool fRight); uint8_t DefineUserChar(uint8_t *pBytes, uint8_t bCharNo); uint8_t WriteUserCharsAtPos(uint8_t* rgCharPos, uint8_t bNoChars, uint8_t idxLine, uint8_t idxCol) ; }; #endif /************************************************************************/ /* */ /* LCDP.cpp -- Definition for LCDP library */ /* */ /************************************************************************/ /* Author: Cristian Fatu */ /* Copyright 2011, Digilent Inc. */ /************************************************************************/ /* File Description: */ /* This file defines functions for LCDP */ /* */ /************************************************************************/ /* Revision History: */ /* */ /* 12/10/2011(CristianF): created */ /* 21/08/2016(Todotani): adapted fro ZYBO PmodCLP */ /* */ /************************************************************************/ /* ------------------------------------------------------------ */ /* Include File Definitions */ /* ------------------------------------------------------------ */ #include "LCDP.h" /* ------------------------------------------------------------ */ /* Procedure Definitions */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /** Description: ** Class constructor. Performs variables initialization tasks ** ** */ LCDP::LCDP() { m_bDisplayMode = 0; } /* ------------------------------------------------------------ */ /* LCDP::begin ** ** Synopsis: ** ** Description: ** This function saves the pins corresponding to the CLP and performs the required LCDP initialization tasks. ** */ void LCDP::begin() { // Initialize Zynq GPIO XGpio_Initialize(&PmodLCD, XPAR_AXI_GPIO_0_DEVICE_ID); XGpio_SetDataDirection(&PmodLCD, 2, 0x0); // Set control port output // set control flag 0 control = 0; // perform initialization sequence, according to datasheet // wait 20 ms usleep(20*000); // Set function WriteCommand(LCDP_CMD_LcdFcnInit); // Wait 37 us usleep(37); // display on, no cursor, no blinking SetDisplay(true); SetBacklight(true); // Wait 37 us usleep(37); // Display Clear DisplayClear(); // Wait 1.52 ms usleep(1520); } /* ------------------------------------------------------------ */ /* ReadByte ** ** Return Values: ** uint8_t - the byte that was read ** ** Description: ** The function implements a CLP read sequence. ** The function is used to read data (RS set before calling this function) or to read status (RS cleared before calling this function). ** The function implements two approaches of handling data pins. ** */ uint8_t LCDP::ReadByte() { uint8_t bData = 0; // Set data port input XGpio_SetDataDirection(&PmodLCD, 1, 0xFF); // Set RW control |= RW; XGpio_DiscreteWrite(&PmodLCD, 2, control); // Set EN control |= EN; XGpio_DiscreteWrite(&PmodLCD, 2, control); bData = XGpio_DiscreteRead(&PmodLCD, 1); // Clear EN control &= ~EN; XGpio_DiscreteWrite(&PmodLCD, 2, control); // Clear RW control &= ~RW; XGpio_DiscreteWrite(&PmodLCD, 2, control); return bData; } /* ------------------------------------------------------------ */ /* WriteByte ** ** Parameters: ** uint8_t bData - data to be written to display ** ** Return Values: ** void ** ** Description: ** The function implements a CLP write sequence. ** The function is used to write data (RS set before calling this function) or to write commands (RS cleared before calling this function). ** When writing data it writes in DDRAM or CGRAM according to the last set write position. */ void LCDP::WriteByte(uint8_t bData) { // Set data port output XGpio_SetDataDirection(&PmodLCD, 1, 0x00); // Clear RW control &= ~RW; XGpio_DiscreteWrite(&PmodLCD, 2, control); // Set Enalbe control |= EN; XGpio_DiscreteWrite(&PmodLCD, 2, control); XGpio_DiscreteWrite(&PmodLCD, 1, bData); // Clear Enable control &= ~EN; XGpio_DiscreteWrite(&PmodLCD, 2, control); // Set Read control |= RW; XGpio_DiscreteWrite(&PmodLCD, 2, control); } /* ------------------------------------------------------------ */ /* ReadStatus ** ** Return Values: ** uint8_t - the byte that was read. ** ** Description: ** Reads the status of the CLP. It clears the RS and calls ReadByte() function. ** */ uint8_t LCDP::ReadStatus() { uint8_t bStatus; // clear RS control &= ~RS; XGpio_DiscreteWrite(&PmodLCD, 2, control); // read status byte bStatus = ReadByte(); return bStatus; } /* ------------------------------------------------------------ */ /* WaitUntilNotBusy ** ** Synopsis: ** WaitUntilNotBusy() ** ** Return Values: ** void ** ** Description: ** Waits until the status of the CLP is not busy. This function relies on ReadStatus(). ** */ void LCDP::WaitUntilNotBusy() { uint8_t bStatus; bStatus = ReadStatus(); while (bStatus & LCDP_MSK_BStatus) { usleep(10); bStatus = ReadStatus(); } } /* ------------------------------------------------------------ */ /* WriteCommand ** ** Synopsis: ** WriteCommand(cmdLcdClear); ** Parameters: ** uint8_t bCmd - the command code byte ** ** Description: ** Writes the specified byte as command. When the device is ready it clears the RS and writes byte. ** */ void LCDP::WriteCommand(uint8_t bCmd) { // wait until LCD is not busy WaitUntilNotBusy(); // Clear RS control &= ~RS; XGpio_DiscreteWrite(&PmodLCD, 2, control); // Write command byte WriteByte(bCmd); } /* ------------------------------------------------------------ */ /* WriteDataByte ** ** Synopsis: ** WriteDataByte(pBytes[idx]); ** Parameters: ** uint8_t bData - the data byte ** ** Description: ** Writes the specified byte as data. When the device is ready it sets the RS and writes byte. ** */ void LCDP::WriteDataByte(uint8_t bData) { // wait until LCD is not busy WaitUntilNotBusy(); // Set RS control |= RS; XGpio_DiscreteWrite(&PmodLCD, 2, control); // Write command byte WriteByte(bData); } /* ------------------------------------------------------------ */ /* SetWriteCgramPosition ** ** Synopsis: ** SetWriteCgramPosition(bAdr); ** Parameters: ** uint8_t bAdr - the write location. The position in CGRAM where the next data writes will put bytes. ** ** Description: ** Sets the CGRAM write position. This is the location where the next data write will be performed. ** Be aware that writing to a location auto-increments the write location. ** */ void LCDP::SetWriteCgramPosition(uint8_t bAdr) { uint8_t bCmd = LCDP_CMD_LcdSetCgramPos | bAdr; WriteCommand(bCmd); } /* ------------------------------------------------------------ */ /* SetWriteDdramPosition ** ** Synopsis: ** SetWriteDdramPosition(bAddrOffset); ** Parameters: ** uint8_t bAdr - the write location. The position in DDRAM where the next data writes will put bytes. ** 0x00-0x27 refer to the first row ** 0x40-0x67 refer to the second row ** ** Description: ** Sets the DDRAM write position. This is the location where the next data write will be performed. ** Be aware that writing to a location auto-increments the write location. ** */ void LCDP::SetWriteDdramPosition(uint8_t bAdr) { uint8_t bCmd = LCDP_CMD_LcdSetDdramPos | bAdr; WriteCommand(bCmd); } /* ------------------------------------------------------------ */ /* DisplayClear ** ** Synopsis: ** DisplayClear(); ** ** Description: ** Clears the display and returns the cursor home (upper left corner). ** */ void LCDP::DisplayClear() { WriteCommand(LCDP_CMD_LcdClear); } /* ------------------------------------------------------------ */ /* ReturnHome ** ** Description: ** Returns the cursor home (upper left corner). ** */ void LCDP::ReturnHome() { WriteCommand(LCDP_CMD_LcdRetHome); } /* ------------------------------------------------------------ */ /* SetDisplay ** ** Synopsis: ** SetDisplay(true); ** Parameters: ** bool fDisplayOn - Display option ** - true in order to set the display ON ** - false in order to set the display OFF ** ** Description: ** Sets the display option. If true, display is on, if false, the display is off. ** */ void LCDP::SetDisplay(bool fDisplayOn) { if(fDisplayOn) { m_bDisplayMode |= LCDP_OPT_DisplayOn; } else { m_bDisplayMode &= ~LCDP_OPT_DisplayOn; } WriteCommand(LCDP_CMD_LcdCtlInit | m_bDisplayMode); } /* ------------------------------------------------------------ */ /* SetCursor ** ** Synopsis: ** SetCursor(true); ** Parameters: ** bool fCursorOn - Cursor option ** - true in order to set the Cursor ON ** - false in order to set the Cursor OFF ** ** Description: ** Sets the cursor option. If true, Cursor is on, if false, the Cursor is off. ** */ void LCDP::SetCursor(bool fCursorOn) { if(fCursorOn) { m_bDisplayMode |= LCDP_OPT_CursorOn; } else { m_bDisplayMode &= ~LCDP_OPT_CursorOn; } WriteCommand(LCDP_CMD_LcdCtlInit | m_bDisplayMode); } /* ------------------------------------------------------------ */ /* SetBlink ** ** Synopsis: ** SetBlink(true); ** Parameters: ** bool fBlinkOn - Blink option ** - true in order to set the Blink ON ** - false in order to set the Blink OFF ** ** Description: ** Sets the Blink option. If true, Blink is on, if false, the Blink is off. ** */ void LCDP::SetBlink(bool fBlinkOn) { if(fBlinkOn) { m_bDisplayMode |= LCDP_OPT_BlinkOn; } else { m_bDisplayMode &= ~LCDP_OPT_BlinkOn; } WriteCommand(LCDP_CMD_LcdCtlInit | m_bDisplayMode); } /* ------------------------------------------------------------ */ /* SetBacklight ** ** Synopsis: ** SetBacklight(fBackLight); ** Parameters: ** bool fBl - Backlight option ** - true in order to set the backlight ON ** - false in order to set the backlight OFF ** ** Description: ** This function turns the backlight on or off, according to the user's selection. ** Note that there are CLP Pmods that do not have backlight functionality. Using this function for this type of modules will have no effect. ** */ void LCDP::SetBacklight(bool fBacklightOn) { if (fBacklightOn) control |= BL; else control &= ~BL; XGpio_DiscreteWrite(&PmodLCD, 2, control); } /* ------------------------------------------------------------ */ /* SetPos ** ** Synopsis: ** SetPos(0, 3); ** Parameters: ** uint8_t idxLine - the line where the position will be set ** uint8_t idxCol - the column where the position will be set ** ** ** Return Value: ** uint8_t ** - LCDP_ERR_SUCCESS (0) - The action completed successfully ** - a combination (ORed) of the following errors: ** - LCDP_ERR_ARG_ROW_RANGE (0x80) - The row index is not valid ** - LCDP_ERR_ARG_COL_RANGE (0x40) - The column index is not valid ** Description: ** This function sets the corresponding LCD position. This is used for write position and cursor position. ** If position set is invalid (outside the display), errors are returned. ** ** */ uint8_t LCDP::SetPos(uint8_t idxLine, uint8_t idxCol) { uint8_t bResult = LCDP_ERR_SUCCESS; if (idxLine < 0 || idxLine >= LCDP_NO_ROWS) { bResult |= LCDP_ERR_ARG_ROW_RANGE; } if (idxCol < 0 || idxCol >= LCDP_NO_COLS) { bResult |= LCDP_ERR_ARG_COL_RANGE; } if (bResult == LCDP_ERR_SUCCESS) { // Set write position uint8_t bAddrOffset = (idxLine == 0 ? 0: 0x40) + idxCol; SetWriteDdramPosition(bAddrOffset); } return bResult; } /* ------------------------------------------------------------ */ /* WriteStringAtPos ** ** Synopsis: ** WriteStringAtPos(szInfo1, 0, 0); ** Parameters: ** char *szLn - string to be written to LCD ** uint8_t idxLine - the line where the string will be displayed ** uint8_t idxCol - the column line where the string will be displayed ** ** ** Return Value: ** uint8_t ** - LCDP_ERR_SUCCESS (0) - The action completed successfully ** - a combination (ORed) of the following errors: ** - LCDP_ERR_ARG_ROW_RANGE (0x80) - The row index is not valid ** - LCDP_ERR_ARG_COL_RANGE (0x40) - The column index is not valid ** ** ** Errors: ** See Return Value ** ** Description: ** The function writes the specified string at the specified position (line and column). ** It sets the corresponding write position and then writes data bytes when the device is ready. ** Strings that span over the end of line are trimmed so they fit in the line. ** If position is invalid (outside the display), errors are returned. ** */ uint8_t LCDP::WriteStringAtPos(char *szLn, uint8_t idxLine, uint8_t idxCol) { uint8_t bResult = SetPos(idxLine, idxCol); if (bResult == LCDP_ERR_SUCCESS) { // Strings that span over the end of line are trimmed so they fit in the line. int len = strlen(szLn); if(len + idxCol > LCDP_NO_COLS) { len = LCDP_NO_COLS - idxCol; szLn[len] = 0; // crop the string at this position } uint8_t bIdx = 0; while(bIdx < len) { WriteDataByte(szLn[bIdx]); bIdx++; } } return bResult; } /* ------------------------------------------------------------ */ /* DisplayShift ** ** Synopsis: ** DisplayShift(fBtn1Process); ** Parameters: ** bool fRight - parameter indicating the direction of the display shift ** - true in order to shift the display right ** - false in order to shift the display left ** ** Description: ** This function shifts the display one position right or left, depending on the fRight parameter. ** ** */ void LCDP::DisplayShift(bool fRight) { uint8_t bCmd = LCDP_CMD_LcdDisplayShift | (fRight != false ? LCDP_MSK_ShiftRL: 0); WriteCommand(bCmd); } /* ------------------------------------------------------------ */ /* CursorShift ** ** Synopsis: ** CursorShift(fBtn1Process); ** Parameters: ** bool fRight - parameter indicating the direction of the cursor shift ** - true in order to shift the cursor right ** - false in order to shift the cursor left ** ** Description: ** This function shifts the cursor one position right or left, depending on the fRight parameter. ** */ void LCDP::CursorShift(bool fRight) { uint8_t bCmd = LCDP_CMD_LcdCursorShift | (fRight != false ? LCDP_MSK_ShiftRL: 0); WriteCommand(bCmd); } /* ------------------------------------------------------------ */ /* DefineUserChar ** ** Synopsis: ** MyLCDP.DefineUserChar(defChar2, 2); ** Parameters: ** uint8_t *pBytes - pointer to the string that contains the 8 bytes definition of the character. ** uint8_t bCharNo - the position of the user character to be saved in the memory ** ** ** Return Values: ** uint8_t ** - LCDP_ERR_SUCCESS (0) - The action completed successfully ** - LCDP_ERR_UCHAR_POSITION_INVALID (0x20) - The user character position is not within 0 - 7 ** ** Description: ** This function writes the specified number of bytes to CGRAM starting at the specified position. ** It sets the corresponding write position and then writes data bytes when the device is ready. ** If the user character position is not within 0 - 7 range, error is returned. ** */ uint8_t LCDP::DefineUserChar(uint8_t *pBytes, uint8_t bCharNo) { uint8_t bResult = LCDP_ERR_SUCCESS; if (bCharNo >=0 || bCharNo < LCDP_NO_UCHARS) { uint8_t bAdr = bCharNo << 3; // multiply by 8 // Set write position to CGRAM SetWriteCgramPosition(bAdr); uint8_t len = 8; // Write the string of bytes that define the character to CGRAM uint8_t bIdx = 0; while(bIdx < len) { WriteDataByte(pBytes[bIdx]); bIdx++; } bResult = LCDP_ERR_SUCCESS; } else { bResult = LCDP_ERR_UCHAR_POSITION_INVALID; } return bResult; } /* ------------------------------------------------------------ */ /* WriteUserCharsAtPos ** ** Synopsis: ** WriteUserCharsAtPos(szInfo1, 0, 0); ** Parameters: ** rgCharPos - an array containing the index (position) of the user characters to be displayed ** bNoChars - an array containing the index (position) of the user characters to be displayed ** uint8_t idxLine - line where the string will be displayed ** uint8_t idxCol - the starting position of the string within the line ** ** Return Value: ** uint8_t ** - LCDP_ERR_SUCCESS (0) - The action completed successfully ** - a combination (ORed) of the following errors: ** - LCDP_ERR_ARG_ROW_RANGE (0x80) - The row index is not valid ** - LCDP_ERR_ARG_COL_RANGE (0x40) - The column index is not valid ** - LCDP_ERR_UCHAR_POSITION_INVALID (0x20) - The user character position is not within the accepted range (0 ? 7) ** ** Description: ** This function displays one or more user defined characters at the specified positions on the LCD. ** If the position set or the user character position is not correct, errors are returned. ** ** ** -----------------------------------------------------------------------*/ uint8_t LCDP::WriteUserCharsAtPos(uint8_t* rgCharPos, uint8_t bNoChars, uint8_t idxLine, uint8_t idxCol) { uint8_t bResult = SetPos(idxLine, idxCol); if (bResult == LCDP_ERR_SUCCESS) { // validate the user character positions to be between 0 and 7 uint8_t bIdx = 0; while(bIdx < bNoChars) { if (rgCharPos[bIdx] < 0 || rgCharPos[bIdx] >= LCDP_NO_UCHARS) { bResult = LCDP_ERR_UCHAR_POSITION_INVALID; bIdx = bNoChars; // force out, no need to continue } bIdx++; } if (bResult == LCDP_ERR_SUCCESS) { //set the write position of the cursor to the wanted line/column for displaying custom chars uint8_t bAddrOffset = (idxLine == 0 ? 0: 0x40) + idxCol; SetWriteDdramPosition(bAddrOffset); //send the position of the user character to be displayed uint8_t bIdx = 0; while(bIdx < bNoChars) { WriteDataByte(rgCharPos[bIdx]); bIdx++; } } } return bResult; }
LCDに表示を行う簡単なmain.cppを以下のように作成
#include "LCDP.h" #include "sleep.h" #include <stdio.h> int main() { LCDP lcd; char buff[16]; char message[16] = "Hello ZYBO"; lcd.begin(); int i = 0; while(1) { lcd.WriteStringAtPos(message, 0, 0); sprintf(buff, "%d", i); lcd.WriteStringAtPos(buff, 1, 0); i++; usleep(50*1000); } return 0; }
プログラムの実行
ビルドを行い以下の手順でFPGAのconfigとプログラムの実行を行います。
- ZYBOのProgramming Mode JumperをJTAGにセット
- Xilix Tools → Program FPGAを実行。LD10のグリーンLED(DONE)が点灯すればFPGAのconfigが完了です
- Run → Run As → Launch on Hardware (GDB)
以下のとおりプログラムが起動します。
JTAGインターフェース経由で起動した場合、電源を切ったりリセットを行うと、FPGAのconfig・PSのプログラムとも消えてしまいます。configやプログラムを永続化したい場合は、QSPIメモリーにbootイメージを書き込む必要があります。この辺りは別途書きます。
参考情報
- Getting Started with Zynq
- PmodCLP Resource
- ZYBO / Pmod > GPIOをPmodコネクタに出力してみた
- Zynq Design From Scratch Started February 2014
最近のコメント