« 2016年4月 | トップページ | 2016年9月 »

2016年8月の記事

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です。

IMG_1084.jpg

高位合成のお試しは、下の写真にある「FPGAマガジンNo.14」を参考してやろうと思っているのですが、その前にZYBOにLCDを繋いでみました。

IMG_1147.jpg

ZYBOは写真で見て分かる通り、DE0-CVに比べて7SEG-LEDがないなど、表示系デバイスが少ないです。その代わりに、ZYBOにはPmodと呼ばれる拡張コネクターが6個付いており、ZYBOの発売元であるDigilent社から各種のPmod拡張モジュールが販売されています。回路のデバッグを行う際など表示系があると便利だと思い、LEDが8個搭載されたPmod8LD(写真右側)とParallel接続のLCD PmodCLP(写真左側)を購入しました。(FedExを使った送料込みで、$50超となり、ちょっと高かったですが)。

IMG_1148.jpg

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出力に接続するのでダイオードを使った過電圧保護は不要だと思うのですが、何のために入っているのか)。

PmodCLP.png

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」と設定します。

Vivado_IP.PNG


Vovado-GPIO.PNG

制約ファイルの作成

次に、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でファイルを保存します。

Pmod-xdc.PNG

一度割り当てを作ってしまえば、次からは制約ファイル(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イメージを書き込む必要があります。この辺りは別途書きます。
参考情報

 

FPGA始めました

最近FPGAにハマっています。きっかけは、写真の「ディジタル回路設計とコンピュータアーキテクチャー ARM版」を買って、この本の解説に従って、FPGAボードのDE0-CVを使って最小限のARM命令が動くCPUコアをFPGAで作るようになって、FPGAのお手軽さと面白さに目覚めました。自分で部品をハンダ付け・配線して実際のハードを作るのは時間もなく(ましてやカスタム基盤を設計する根性もなく)、FPGAで回路を合成してシミュレーションすると、なんとなく回路設計したような気持ちになれ、ソフトを書くのとはまた違った面白さがあります。

IMG_0977.jpg

今回買ったFPGAボードはAlteraのCyclone Vを搭載したDE0-CVをマルツオンラインで購入しました。DE0-CVにした理由は、AlteraとXilixのどちらにしようかと思ったのですが、「ディジタル回路設計とコンピュータアーキテクチャー」でAlteraの開発環境やDE2-115ボードが紹介されていたこと、DE2-115は高価で手が出ないので、安価ですが7SEG-LEDなど表示系が比較的充実したDE0-CVを購入しました。

最初は「ディジタル回路設計とコンピュータアーキテクチャー」に掲載されているSystem Verilogのコードをそのまま打ち込んだだけですが、入力ミスを見つけるために、初めて使うAlteraの開発環境Quartus Primeと格闘しながら論理ミュレーションを動かすなどして、実際にARM命令が動くCPUが作れた時は結構感動しました。この本は、FPGAで実際に動くCPUを作ってみるためには絶好の名著だと思いますので、興味のある方は是非買ってみて下さい。Amazonのレビューに、私の書評も掲載しています。

その後コードを拡張して、演習問題のパイプライン化は自力で動かすことができました。また、パイプライン化に加えて、MOV/CMP/EOR(XOR)/ROR/LSR/ASR/LSL命令を追加しています。

現在はDE0-CVに搭載されているSD-RAMにアクセスできるようにしようとしているのですが、FPGA内臓メモリーはノーウェイトでアクセスできることに対してSD-RAMはノーウェイトではアクセスできないため、パイプラインの制御に改造が必要でまだうまくいっていません(LDR命令でメモリーから読みだすまでのパイプラーンストールに加えて、読みだした値をレジスタファイルにライトバックするあたりのパイプラインストールの追加考慮が必要なようです)。SD-RAMアクセスの単体試験はQsysで合成した出来合いのIPを使うことでできているのですが。

DE0-CVを使ったミニARMのコードなどは、オリジナル書籍発売元やARM社の版権もあると思うので、私が作ったHDLコードを掲載するのはやめておきます。

« 2016年4月 | トップページ | 2016年9月 »

2023年6月
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30  
無料ブログはココログ