2020年5月28日木曜日

PSoC6向けI2C_CharLCDライブラリを作ってみた。

今回はPSoC6向けにI2CでつながるキャラクタLCD用ライブラリを作ってみました。
PSoC5LPとかでちゃちゃっと表示器が欲しい時にはキャラクタLCDのコンポーネントを使って表示させることが出来ます。PSoC5LPやPSoC4だとキャラクタLCDのコンポーネントはパラレル接続とI2C接続の2つから選ぶことが出来ます。PSoC6でもちゃちゃっと表示器が欲しい所なのですが、リソースがたくさんあるにもかかわらずキャラクタLCDのコンポーネントはありません。パラレルTFTのコンポーネントがあるにはあるのですが毎回持ち出すのはちょっと大げさですし、一部のPSoC6には対応できません。なので今回はPSoC6向けにI2Cで接続するキャラクタLCD用のライブラリを作ることにしました。

 まず、このライブラリについての説明をする前にPSoC6で使われるライブラリを知る必要があります。PSoC6のライブラリはPDLとHAL(以降はCYHALとします)の2つがあります。PDLはペリフェラルドライバーライブラリーの略で文字通りPSoC6のペリフェラルを使うためライブラリーです。CYHALはPDLを元に作られています。おそらく、PDLのバージョンを切り替えても動作することを目的にしているのだと思われます。PSoC6の開発環境はPSoC Creator と modus ToolBoxの2つがあるのですが、PDLはどちらの環境でも使われていますがCYHALライブラリはmodus ToolBoxにだけ使われています。CYHALベースでI2C_CharLCDライブラリを作ってしまうと、PSoC Creatorで使うことが出来ません。なのでI2C_CharLCDライブラリはPDLをベースに作ることにしました。また、PSoC6ではRTOSを多用することも多いのでRTOSを使っても問題が起きにくいようにしました。PDLの厄介な所は構造体やポインタを多用するのでちょっとメモリー周りに意識が向いていないと使いこなせない所ですね。きちんとC言語が分かっている、C言語向けのオブジェクト指向を知っている必要があります。

さて、I2C_CharLCDライブラリの説明に入ろうと思います。AQM0802Aで動作確認をしていますが、コントローラICがST7032であればI2C_CharLCDライブラリが適応できると考えられます。I2C_CharLCDライブラリはmodus ToolBox、PSoC Creatorの両方の環境で動作できます。I2C_CharLCDライブラリの関数は5つと構造体が1つあります。それぞれの関数と構造体は次の通りです。

void I2C_CharLCD_Init(I2C_CHAR_LCD_HANDLE *handle, CySCB_Type *base,cy_stc_scb_i2c_context_t *context)
I2C_CharLCD_InitはI2C_CharLCDで使うI2C_CHAR_LCD_HANDLE構造体の初期化、キャラクタLCDそのものを初期化をします。

void I2C_CharLCD_Clear(I2C_CHAR_LCD_HANDLE *handle)
I2C_CharLCD_Clear()はキャラクタLCDの表示を消します。

bool I2C_CharLCD_Set_Cursor(I2C_CHAR_LCD_HANDLE *handle, uint8_t col, uint8_t row)
I2C_CharLCD_Set_Cursor()は表示のどこから表示をするかを設定します。

bool I2C_CharLCD_PutString(I2C_CHAR_LCD_HANDLE *handle, uint8_t *string, uint16_t length)
I2C_CharLCD_PutString()は文字列を表示します。

void I2C_CharLCD_PutChar(I2C_CHAR_LCD_HANDLE *handle, uint8_t putstr)
I2C_CharLCD_PutChar()は文字を1字だけ表示します。

I2C_CHAR_LCD_HANDLE
I2C_CHAR_LCD_HANDLE構造体はI2C_CharLCDライブラリのすべてで使います。

RTOSを使わない場合はI2CCharLCDConfig.hの#define Free_RTOS_ONをコメントアウトします。RTOSを使う場合はI2CCharLCDConfig.hの#define Free_RTOS_ONと書きます。

サンプルプログラムは次の通りです。
サンプルプログラムmodusToolboxで書きました。

#include "cy_pdl.h"
#include "cyhal.h"
#include "cybsp.h"
#include "FreeRTOS.h"
#include "task.h"
#include "I2CCharLCD.h"
#include "stdio.h"

void ledTask(void* prameter);
TaskHandle_t ledtask_h;

void lcdTask(void* prameter);
TaskHandle_t lcdtask_h;

int uxTopUsedPriority;
cy_stc_i2s_context_t i2cContext;
void I2C_ISR(void);

const cy_stc_sysint_t i2cIntrConfig =
{
    .intrSrc      = I2C_0_IRQ,
    .intrPriority = 6
};

int main(void)
{
    cy_rslt_t result;

    /* Initialize the device and board peripherals */
    result = cybsp_init() ;
    if (result != CY_RSLT_SUCCESS)
    {
        CY_ASSERT(0);
    }
    uxTopUsedPriority = configMAX_PRIORITIES - 1;
    xTaskCreate(ledTask, "LEDTASK", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, &ledtask_h);
    xTaskCreate(lcdTask, "LCDTASK", 256, NULL, tskIDLE_PRIORITY + 2, &lcdtask_h);
    vTaskStartScheduler();

    __enable_irq();

    for (;;)
    {
    }
}

void ledTask(void* prameter)
{

for(;;)
{
Cy_GPIO_Set(LED0_PORT, LED0_PIN);
vTaskDelay(250);
Cy_GPIO_Clr(LED0_PORT, LED0_PIN);
vTaskDelay(250);
}
}


void lcdTask(void* prameter)
{
Cy_SCB_I2C_Init(I2C_0_HW,  &I2C_0_config, &i2cContext);
Cy_SCB_I2C_SetDataRate(I2C_0_HW, 100000, Cy_SysClk_PeriphGetFrequency(CY_SYSCLK_DIV_16_BIT, 0U));
/* Hook interrupt service routine and enable interrupt */
Cy_SysInt_Init(&i2cIntrConfig, &I2C_ISR);
NVIC_EnableIRQ(I2C_0_IRQ);
Cy_SCB_I2C_Enable(I2C_0_HW);

    I2C_CHAR_LCD_HANDLE aqm;

    I2C_CharLCD_Init(&aqm, I2C_0_HW, &i2cContext);
    I2C_CharLCD_PutString(&aqm, (uint8_t*) "count", 5);
    I2C_CharLCD_Set_Cursor(&aqm, 0, 1);

    uint8_t count = 0;
    uint8_t strCount[5] = {};
for(;;)
{
snprintf((char*) strCount, 5, "%d  ", count);
I2C_CharLCD_PutString(&aqm, strCount, 5);
count++;
if(count > 255)
{
count = 0;
}
vTaskDelay(250);
//I2C_CharLCD_Clear(&aqm);
}
}

void I2C_ISR(void)
{
    Cy_SCB_I2C_Interrupt(I2C_0_HW, &i2cContext);
}
/* [] END OF FILE */

サンプルプログラムが正しく動くと次のようになります。



一応ですが、I2C_CharLCDライブラリのソースファイルを置いておきます。

2020年5月8日金曜日

PSoC5LPでDMA転送をする

しばらくぶりです。
世間も世間で大変なことになっておりますが、私も色々と大変なことになってなってきてしまいました。
おし!!PSoCに逃げ込むぞ!!

今回はPSoC5LPでDMA転送をしようと思います。
DMA転送なんじゃいなって言うところに触れる前にペリフェラルへのデータのアクセスについてちょっとおさらい。
 通常のペリフェラルのデータレジスタにアクセスする場合、CPUがペリフェラルのレジスタ番号を使ってデータを取ってきていました。端的に言ってしまえばCPUがレジスタ番号の0x0000番地にあるデータを取得するとかですね。また「ペリフェラル側でデータの用意出来たら、CPUにお知らせしてね。そうしたらCPUがペリフェラルのデータレジスタにアクセスするよ。」っていう割り込み処理(IRQ)を使ったデータレジスタへのアクセスがあります。どちらともペリフェラルからデータを取得する際に「CPUがデータを取得する」という事が発生します。
 「CPUがデータを取得する」というのは時として難しいケースもあります。例えばADCからの連続データを取得したいけど、表示や計算にもCPUパワーを使いたい時。ほかにもありますねSPI通信で大容量のデータをセンサーやモジュールに流し込みたい時等々。こういうケースは結構あります。そこで「CPUを使わないでデータの移し替えが出来ないかしら…。」って登場するのがDMA転送です。

DMA転送についてを触れようと思うと、話が膨らみすぎるのでポイントを絞って進めようと思います。
① DMAコントローラーの行えることについて
② DMAコントローラーが何を手がかりデータを転送しているかについて
③ DMAコントローラーはどのタイミングでデータの転送をするかについて
の3つで考えておくと良いと思います。あえてCPUとペリフェラル間の通信バスについては触れないです。優先順位や通信バスの規格の話になってややこしいので。

① DMAコントローラーの行えることについて
DMAコントローラーの行えることは4つあります。どれもデータを送ったり、受け取ったりすることです。
・メモリー(flashも含め)からペリフェラルにデータを送る。
・ペリフェラルにあるデータをメモリーへ送る。
・メモリーからメモリーへデータを送る(移す)。
・ペリフェラルからペリフェラルへデータを送る。

② DMAコントローラーは何を手がかりにしてデータの転送をしているか
これは3つあるのですがちょっと複雑です。
・ペリフェラルやメモリーに割り当てられているアドレス
・送るデータの総数
・1個当たりのデータ長さ
これらを頼りにDMAコントローラーはデータ転送をします。
これだけだとピンと来ないと思うので簡単に例を挙げようと思います。


ペリフェラル(送り元)からメモリー(送り先)にDMAが転送するとします。
送るデータの総数は32byte、1個当たりのデータは1byteとします。
送り先のメモリーはuint8_tで32byte分の配列になっていてます。(送り先のメモリーは配列じゃないと送れないです。)
DMAコントローラーはペリフェラルのアドレスは分かっているので、ペリフェラルから1個目のデータを取得します。DMAコントローラーは取得したデータをメモリーへ1個目格納します。格納が終わったらまたDMAコントローラーはペリフェラルからデータを取得してメモリーへ格納するわけですがちょっと問題ですね。メモリーのアドレスが分かっている1個目の配列にはもうデータが入っています。そこでDMAコントローラーは転送したデータの個数を数えることで次に格納するメモリーのアドレスを特定することが出来ます。
メモリー側の配列の1番目のアドレスが0x0001であれば、カウンタの値と合わせて2番目は0x0002、3番目のアドレス0x0003というようになります。

③ DMAコントローラーはどのタイミングでデータの取得を行うか
DMAコントローラーにデータの取得を伝えることを「DMA request」といいます。
このDMA requestはデータの送り元がDMAコントローラーに伝えることでDMA転送が開始されます。ペリフェラルでは内部割込みのピンがDMA requestを操作したり、メモリ間での転送はCPUがDMA requestを操作することもあります。
DMAコントローラーへDMA requestを伝えるタイミングはケースバイケースなのです。例を挙げておくと、通信系のペリフェラルで受信時にペリフェラル側のデータが溜まった時にDMA requestを行います。送信時ではペリフェラル側の送るデータが無い時にDMA requestを行います。

ここからは実際にPSoC5LPでDMA転送を行います。
今回はUARTの受信をDMA転送でSRAMに移すことをします。
コンポーネントの接続は次の通りです。


DMA requestは次の通りです。
UARTの場合Level edge設定じゃないとDMA requestがかかりません。


プログラムはFreeRTOSが入った状態で書きました。
現状のコードだとデバッガを当てないと通信テストが出来ないです。

void vGPStask()
{
    uint8_t gpsbuff[32];
   
    /* Defines for DMA_rx */
    #define DMA_rx_BYTES_PER_BURST 1 //1回のデータの長さ(byte単位)
    #define DMA_rx_REQUEST_PER_BURST 1 //request毎に転送
    #define DMA_rx_SRC_BASE (CYDEV_PERIPH_BASE) //転送元
    #define DMA_rx_DST_BASE (CYDEV_SRAM_BASE) //転送先
   
    /* Variable declarations for DMA_rx */
    /* Move these variable declarations to the top of the function */
    uint8 DMA_rx_Chan;
    uint8 DMA_rx_TD[1];
   
    /* DMA Configuration for DMA_rx */
    DMA_rx_Chan = DMA_rx_DmaInitialize(DMA_rx_BYTES_PER_BURST, DMA_rx_REQUEST_PER_BURST,
        HI16(DMA_rx_SRC_BASE), HI16(DMA_rx_DST_BASE));
    DMA_rx_TD[0] = CyDmaTdAllocate();
    CyDmaTdSetConfiguration(DMA_rx_TD[0], 32, DMA_rx_TD[0], CY_DMA_TD_INC_DST_ADR);//DMAのデータのカウントと送る設定
    CyDmaTdSetAddress(DMA_rx_TD[0], LO16((uint32)UART_1_RXDATA_PTR), LO16((uint32)gpsbuff));// 転送元と転送先のアドレス
    CyDmaChSetInitialTd(DMA_rx_Chan, DMA_rx_TD[0]);   
    CyDmaChEnable(DMA_rx_Chan, 1);//DMAの有効化
    UART_1_Start();//UARTの有効化

    for(;;)
    {
        vTaskDelay(250);
    }
}

転送テストを行った結果
tera tarmから文字で「123」と入力しました。
バッファの先頭から1(0x31)、2(0x32)、3(0x33)が入っています。

注意事項
PSoC5LPのDMAは最大で8chまでです。複数のDMAを使う場合はDMA同士の優先順位を決める必要があります。またDMAのカウンタは4095なのでそれを超えるデータの量を取り扱う場合はDMAの設定を組み替える必要があります。DMAと割り込みを組み合わせるケースもあります。DMAのコードジェネレータ(DMA Wizard)の使い方や詳細はアプリケーションマニュアルAN52705 - PSoC® 3 and PSoC 5LP - Getting Started with DMAを読んでください。