このチュートリアルは、Wei Dongshan Baiwen.com によって作成されたDShanMCU-RA6M5 開発ボードに基づいて書かれています。必要な学生はここから入手できます: https://item.taabao.com/item.htm?id=728461040949
サポート情報の入手: https://renesas-docs.100ask.net
ルネサス MCU ゼロベース エントリ シリーズ チュートリアルの概要: https://blog.csdn.net/qq_35181236/article/details/132779862
第 8 章 SCI SPI
この章の目的
- RASC を使用して SCI の SPI モードを迅速に設定する
- SCI-SPI API を使用してデータを送受信する方法を学習します。
8.1 sci spiモジュールの使用
8.1.1 sci spiモジュールの構成
この章で設定する SPI は RA チップの SCI モジュール内のモードであるため、その設定方法は前の章の SCI の UART モード設定と非常に似ています。
この実験は他のペリフェラルに接続されていないため、任意の SCI チャネルを選択できます。「シンプル SPI」モードに設定し、デフォルトのピンを使用します。RASC でプロジェクトを作成した後、「ピン」の「周辺機器」の「接続:SCI」を展開し、SCI5 などの SCI チャネルの 1 つを選択し、「ピン構成」設定インターフェースの「動作モード」で動作を設定します。図に示すように、モードは「Simple SPI」として選択されています。
次に、ハードウェア設計に従ってクロックおよびデータ トランシーバー ピンを選択します。
次に、次の図に示すように、「スタック」に SCI SPI スタック モジュールを追加し、クリックして「スタック」構成インターフェイスに入り、「新規スタック」をクリックして、内部の「接続」を展開し、「SPI (r_sci_spi)」を選択します。 :
ここで注意が必要なのは、RA の SPI は「シンプル SPI」と「SPI」に分かれており、「シンプル SPI」は SCI のモードの 1 つであり、「SPI」は実際の SPI ハードウェア コントローラです。この章の実験は SCI の SPI に基づいているため、「SPI(r_sci_spi)」が選択されました。
これらの操作が完了すると、「スタック」設定インターフェースの「HAL/共通スタック」に「g_spi0 SPI (r_sci_spi)」モジュールが追加されますので、次にこのモジュールの属性を実際の状況に応じて設定する必要があります。たとえば、SCI の SPI5 が以前に選択されており、ここでの新しいモジュールのデフォルト名は「g_spi0」で、デフォルトのチャネルは 0 です。実際のチャネルと一致させるには、名前とそのチャネルを変更する必要があります。このモジュールのプロパティ。以下に示すように:
ここで「チャンネル」を選択すると、その下の「ピン」の内容が以前に操作したピンに自動更新されます。
属性の「Callback」の場合、コールバック関数を中断します。関数名には次の 2 つの方法があります。
- ペリフェラルが異なれば、使用するコールバック関数も異なります。
- 同じ種類のペリフェラルは同じコールバック関数を使用し、パラメータの内容に応じて異なる処理を実行します。
初心者には最初の方法を使用することをお勧めします。経験豊富なエンジニアには、プログラム コードのオーバーヘッドを軽減するために 2 番目の方法を使用することをお勧めします。この本では最初の方法を使用し、図に示すように、SCI SPI 割り込みコールバック関数の名前をそのチャネルと一致させ、「sci_spi5_callback」に変更します。
SCI SPI のその他のパラメータについては、実際の通信周辺機器に応じて設定する必要があります。RASC でこれらのパラメータに関する情報を確認してください。
-
動作モード: SPI の動作モード。SPI にはマスター モードとスレーブ モードの 2 つのモードがあり、ここでの「マスター」と「スレーブ」に対応します。
-
クロック位相: SPI データをクロックの立ち上がりエッジまたは立ち下がりエッジでサンプリングし、立ち上がりエッジまたは立ち下がりエッジで保持するかどうかを決定するクロック位相設定。これは SPI 通信のペリフェラル要件に従って決定されます。
-
クロック極性: クロック優先状態。つまり、SPI 通信がアイドルのとき、クロックは High または Low のままです。
-
ビット順序: データ送信方向には、ハイエンドが最初とローエンドが最初の 2 つのオプションがあり、これも SPI 通信の周辺要件に従って決定されます。
-
ビットレート: 通信速度。この速度は、ホストがサポートする SCI SPI 最大通信速度とスレーブがサポートする SPI 最大通信速度に基づいて決定され、どちらか小さい値を取ります。
モジュールのプロパティでパラメータを設定した後、「プロジェクト コンテンツの生成」をクリックしてコードを生成します。RASC の「概要」インターフェイスの「場所」でプロジェクトが配置されているディレクトリをすぐに開くと、次のようにプロジェクトを開くことができます。図に示すように:
8.1.2 構成情報の解釈
RASC を使用して SCI SPI を設定した後、ピン構成情報と SCI SPI 自体の構成情報が生成されます。この章の最初の実験である「0801_sci_spi_loopback」ループバック トランシーバーの実験を例に挙げます。
- ピン構成情報
この情報は、0801_sci_spi_loopback\ra_gen\pin_data.c ファイルに生成されます。RASC で構成されたピンごとに、ioport_pin_cfg_t 配列項目が pin_data.c に生成され、内部の内容は構成中に選択されたパラメーターと一致します。コードは以下のように表示されます。
const ioport_pin_cfg_t g_bsp_pin_cfg_data[] = {
{
.pin = BSP_IO_PORT_01_PIN_08,
.pin_cfg = ((uint32_t) IOPORT_CFG_PERIPHERAL_PIN +
| (uint32_t) IOPORT_PERIPHERAL_DEBUG)
},
{
.pin = BSP_IO_PORT_03_PIN_00,
.pin_cfg = ((uint32_t) IOPORT_CFG_PERIPHERAL_PIN
| (uint32_t) IOPORT_PERIPHERAL_DEBUG)
},
{
.pin = BSP_IO_PORT_05_PIN_01,
.pin_cfg = ((uint32_t) IOPORT_CFG_PERIPHERAL_PIN
| (uint32_t) IOPORT_PERIPHERAL_SCI1_3_5_7_9)
},
{
.pin = BSP_IO_PORT_05_PIN_02,
.pin_cfg = ((uint32_t) IOPORT_CFG_PERIPHERAL_PIN
| (uint32_t) IOPORT_PERIPHERAL_SCI1_3_5_7_9)
},
{
.pin = BSP_IO_PORT_05_PIN_03,
.pin_cfg = ((uint32_t) IOPORT_CFG_PERIPHERAL_PIN
| (uint32_t) IOPORT_PERIPHERAL_SCI1_3_5_7_9)
},
};
- 10 ~ 21 行目は、SCI の SPI5 ピン P501/P502/P503 を設定し、SCI チャネル 1、3、5、7、および 9 のペリフェラル ピンとして設定します。
- SCI SPI 構成情報
この情報は、0801_sci_spi_loopback\ra_gen\hal_data.c ファイルに生成されます。RASC では、SPI がどの SCI チャネルを使用するか、そのチャネル、クロック特性、データ送信方向、割り込みコールバック関数の登録などの情報が指定されます。これらの構成情報は、spi_cfg_t 構造体に入れられます。一部のコードの抜粋は次のとおりです。
const spi_cfg_t g_spi5_cfg =
{
.channel = 5,
.operating_mode = SPI_MODE_MASTER,
.clk_phase = SPI_CLK_PHASE_EDGE_ODD,
.clk_polarity = SPI_CLK_POLARITY_LOW,
.mode_fault = SPI_MODE_FAULT_ERROR_DISABLE,
.bit_order = SPI_BIT_ORDER_MSB_FIRST,
......(省略内容)
.p_callback = sci_spi5_callback,
......(省略内容)
};
- 行 3: 使用する SF チャネルを指定します。
- 4 ~ 8 行目: SPI の通信パラメータを設定します。
- 行 10: SPI 割り込みコールバック関数を登録します。
8.1.3 割り込みコールバック関数
割り込みコールバック関数のプロトタイプは hal_data.h で宣言されており、ユーザーはそれを独自のプログラムに実装する必要があります。
/** Called by the driver when a transfer has completed or an error has occurred (Must be implemented by the user). */
#ifndef sci_spi5_callback
void sci_spi5_callback(spi_callback_args_t *p_args);
#endif
パラメータの型は spi_callback_args_t 構造体ポインタであり、この構造体のプロトタイプは次のとおりです。
/** Common callback parameter definition */
typedef struct st_spi_callback_args
{
uint32_t channel; ///< Device channel number
spi_event_t event; ///< Event code
void const * p_context; ///< Context provided to user during callback
} spi_callback_args_t;
この構造は、どの SF チャネルが割り込みをトリガーしたか、またどのイベントが割り込みをトリガーしたかを示します。トリガーされた内容は SF では使用されません。
割り込みをトリガーするイベントは列挙型 spi_event_t で指定されます。この列挙の内容は次のとおりです。
/** SPI events */
typedef enum e_spi_event
{
SPI_EVENT_TRANSFER_COMPLETE = 1, ///< The data transfer was completed
SPI_EVENT_TRANSFER_ABORTED, ///< The data transfer was aborted
SPI_EVENT_ERR_MODE_FAULT, ///< Mode fault error
SPI_EVENT_ERR_READ_OVERFLOW, ///< Read overflow error
SPI_EVENT_ERR_PARITY, ///< Parity error
SPI_EVENT_ERR_OVERRUN, ///< Overrun error
SPI_EVENT_ERR_FRAMING, ///< Framing error
SPI_EVENT_ERR_MODE_UNDERRUN ///< Underrun error
} spi_event_t;
SPI 送信完了割り込みイベント、送信一時停止イベント、その他のエラー イベントを含みます。ユーザーは受信したイベントに応じて対応する処理を実行できます。例えば、spi5の割り込みコールバック関数では、以下のように「送信完了フラグ」を設定できます。
void sci_spi5_callback(spi_callback_args_t *arg)
{
if(SPI_EVENT_TRANSFER_COMPLETE == arg->event)
sci_spi5_tx_cplt = 1;
}
-
行 3: イベントが SPI 送信完了イベントであるかどうかを判断します。
-
4 行目: 送信完了によって割り込みがトリガされた場合は、送信完了フラグを 1 に設定します。
8.1.4 APIインターフェースとその使用法
spi モジュールのインターフェイスは 0801_sci_spi_loopback\ra\fsp\inc\api\r_spi_api.h で定義されており、次の内容の構造体タイプ spi_api_t が定義されています。
/** Shared Interface definition for SPI */
typedef struct st_spi_api
{
fsp_err_t (* open)(spi_ctrl_t * p_ctrl, spi_cfg_t const * const p_cfg);
fsp_err_t (* read)(spi_ctrl_t * const p_ctrl,
void * p_dest,
uint32_t const length,
spi_bit_width_t const bit_width);
fsp_err_t (* write)(spi_ctrl_t * const p_ctrl,
void const * p_src,
uint32_t const length,
spi_bit_width_t const bit_width);
fsp_err_t (* writeRead)(spi_ctrl_t * const p_ctrl,
void const * p_src,
void * p_dest,
uint32_t const length,
spi_bit_width_t const bit_width);
fsp_err_t (* callbackSet)(spi_ctrl_t * const p_api_ctrl,
void (* p_callback)(spi_callback_args_t *),
void const * const p_context,
spi_callback_args_t * const p_callback_memory);
fsp_err_t (* close)(spi_ctrl_t * const p_ctrl);
} spi_api_t;
特定の C ファイルでは、sci_api_t 構造体変数を実装する必要があります。たとえば、次の構造体は r_sci_spi.c に実装されます。
const spi_api_t g_spi_on_sci =
{
.open = R_SCI_SPI_Open,
.read = R_SCI_SPI_Read,
.write = R_SCI_SPI_Write,
.writeRead = R_SCI_SPI_WriteRead,
.close = R_SCI_SPI_Close,
.callbackSet = R_SCI_SPI_CallbackSet
};
SCI SPI を使用してデータを送受信する場合、構造体 g_spi_on_sci 内の各関数ポインタを呼び出すことも、r_sci_spi.c に実装されている各関数 (たとえば、
R_SCI_SPI_Open、R_SCI_SPI_Read)。
- オープン SCI SPI デバイス
この関数は SCI の SPI を設定するために使用され、フラグ遷移は「オープン」になります。関数プロトタイプ:
fsp_err_t (* open)(spi_ctrl_t * p_ctrl, spi_cfg_t const * const p_cfg);
この関数のパラメータを見てみましょう。
a) p_ctrl: このパラメータは spi_ctrl_t 構造体のポインタ型であり、構造体は本質的に void 型であり、プロトタイプは次のとおりです。
typedef void spi_ctrl_t;
r_sci_spi.h では、このパラメータの実際のタイプは sci_spi_instance_ctrl_t 構造体であり、次のように定義されています。
typedef struct st_sci_spi_instance_ctrl
{
uint32_t open;
spi_cfg_t const * p_cfg;
R_SCI0_Type * p_reg;
uint8_t * p_src;
uint8_t * p_dest;
uint32_t tx_count;
uint32_t rx_count;
uint32_t count;
/* Pointer to callback and optional working memory */
void (* p_callback)(spi_callback_args_t *);
spi_callback_args_t * p_callback_memory;
/* Pointer to context to be passed into callback function */
void const * p_context;
} sci_spi_instance_ctrl_t;
- 行 3: open は、SPI デバイスのステータス (閉じているか開いているか) を示します。
- 行 4: SPI 構成情報。割り込みの送信、割り込みの受信などの SPI 送信プロセス中の割り込み番号、割り込み優先順位、およびクロック特性を指定します。
- 5 行目: R_SCI0_Type 構造体は SCI の各レジスタに対応します。
- 6 行目と 7 行目: メモリに保存されている送信データの最初のアドレスと受信データの最初のアドレスを指します。
- 8 行目と 9 行目: 1 バイトのデータを送信または 1 バイトのデータを受信した後、割り込みで送信カウントが 1 減算され、送信カウントが 0 になるか受信カウントが指定された回数に達するまで、受信カウントが 1 ずつ増加します。長さの値を受け取ると、割り込みコールバック関数が呼び出されます。
- 行 13: 割り込みコールバック関数を指します。
- 行 14: 割り込みコールバック関数情報を保存します。
- 行 17: 割り込みコールバック関数に渡されるその他の情報。
b) p_cfg: このパラメータは spi_cfg_t 構造体タイプです。この構造体は上記の 4 行目の内容です。プロトタイプは次のとおりです。
typedef struct st_spi_cfg
{
uint8_t channel; ///< Channel number to be used
IRQn_Type rxi_irq; ///< Receive Buffer Full IRQ number
IRQn_Type txi_irq; ///< Transmit Buffer Empty IRQ number
IRQn_Type tei_irq; ///< Transfer Complete IRQ number
IRQn_Type eri_irq; ///< Error IRQ number
uint8_t rxi_ipl; ///< Receive Interrupt priority
uint8_t txi_ipl; ///< Transmit Interrupt priority
uint8_t tei_ipl; ///< Transfer Complete Interrupt priority
uint8_t eri_ipl; ///< Error Interrupt priority
spi_mode_t operating_mode; ///< Select master or slave operating mode
spi_clk_phase_t clk_phase; ///< Data sampling on odd or even clock edge
spi_clk_polarity_t clk_polarity; ///< Clock level when idle
spi_mode_fault_t mode_fault;///< Mode fault error (master/slave conflict) flag
spi_bit_order_t bit_order; ///< Select to transmit MSB/LSB first
transfer_instance_t const * p_transfer_tx; ///< To use SPI DTC/DMA write transfer, link a DTC/DMA instance here. Set to NULL if unused.
transfer_instance_t const * p_transfer_rx; ///< To use SPI DTC/DMA read transfer, link a DTC/DMA instance here. Set to NULL if unused.
void (* p_callback)(spi_callback_args_t * p_args); ///< Pointer to user callback function
void const * p_context; ///< User defined context passed to callback function
void const * p_extend; ///< Extended SPI hardware dependent configuration
} spi_cfg_t;
これらのパラメータが RASC を使用して設定されると、対応する設定情報コードが自動的に生成されます。
開発者は、独自のコードで open 関数を呼び出して sci spi デバイスを初期化できます。次に例を示します。
void drv_sci_spi_init(void)
{
fsp_err_t err = g_spi5.p_api->open(g_spi5.p_ctrl, g_spi5.p_cfg);
if(FSP_SUCCESS == err)
printf("Success to open device: spi5\r\n");
else
printf("Failed to open device: spi5\r\n");
}
- SCI SPI デバイスをシャットダウンします。
SCI SPI デバイスの close 関数は、SPI デバイスのステータス フラグ open を 0 に設定します。プロトタイプは次のとおりです。
fsp_err_t (* close)(spi_ctrl_t * const p_ctrl);
この関数のパラメータはspi_crl_t構造体型のパラメータです。開発者は、次のコードを参照して、指定された spi デバイスを閉じることができます。
void drv_sci_spi_close(void)
{
fsp_err_t err = g_spi5.p_api->close(g_spi5.p_ctrl);
if(FSP_SUCCESS == err)
printf("Success to close device: spi5\r\n");
else
printf("Failed to close device: spi5\r\n");
}
- SCI SPI を使用して指定された長さのデータを送信する
SCI SPI の送信関数書き込みは、半二重または単信通信シナリオに適しており、そのプロトタイプは次のとおりです。
fsp_err_t (* write)(spi_ctrl_t * const p_ctrl,
void const * p_src,
uint32_t const length,
spi_bit_width_t const bit_width);
そのパラメータを見てみましょう:
- p_ctrl: spi_crl_t 構造体タイプのパラメーター。RASC を使用して生成された spi デバイスのグローバル構造体変数を渡します。
- p_src: ソースデータ (送信するデータ) アドレス。
- 長さ: 送信されるデータの数。
- bit_width: データ幅。SCI では常に 8 ビットに設定されます。つまり、このビットは SPI_BIT_WIDTH_8_BITS に渡されます。
開発者は、次のコードを参照して、指定した長さのデータを送信できます。
void drv_sci_spi_write(uint8_t *pbuf, uint16_t size)
{
sci_spi5_tx_cplt = 0;
g_spi5.p_api->write(g_spi5.p_ctrl,
(uint8_t*)pbuf,
(uint32_t)size, SPI_BIT_WIDTH_8_BITS);
while(!sci_spi5_tx_cplt);
}
行 3 と行 5 は送信フラグです。送信前にクリアされ、送信が完了すると割り込みコールバック関数で 1 に設定されます。
- SCI SPI を使用して指定された長さのデータを受信します
SCI SPI の受信関数 read は、半二重または単信通信シナリオに適しており、そのプロトタイプは次のとおりです。
fsp_err_t (* read)(spi_ctrl_t * const p_ctrl,
void * p_dest,
uint32_t const length,
spi_bit_width_t const bit_width);
これは送信と似ていますが、p_dest がデータを受信するバッファの最初のアドレスであり、そのデータ幅が 8 ビットのままである点が異なります。
開発者は、次のコードを参照して、指定された長さのデータを受け取ることができます。
g_spi5.p_api->read(g_spi5.p_ctrl, (uint8_t*)pbuf, (uint32_t)size, SPI_BIT_WIDTH_8_BITS);
- SCI SPI を使用して指定した長さのデータを同時に送受信する
全二重モードでは、SCI SPI の同時送受信関数 writeRead の使用を推奨します。そのプロトタイプは次のとおりです。
fsp_err_t (* writeRead)(spi_ctrl_t * const p_ctrl,
void const * p_src,
void * p_dest,
uint32_t const length,
spi_bit_width_t const bit_width);
この機能は、同じ長さのデータを同時に送受信する場合に使用します。データ幅は 8 ビットです。開発者は、次のコードを参照して、同時送信と受信を実現できます。
void drv_sci_spi_writeRead(uint8_t *wbuf, uint8_t *rbuf, uint16_t size)
{
sci_spi5_tx_cplt = 0;
g_spi5.p_api->writeRead(g_spi5.p_ctrl,
(uint8_t*)wbuf,
(uint8_t*)rbuf,
(uint32_t)size,
SPI_BIT_WIDTH_8_BITS);
while(!sci_spi5_tx_cplt);
}
8.2 sci spiループバックトランシーバー実験
この節の実験ではUARTのprintf関数を使用しますので、printf関数の設定については前回の記事「7.3 stdioの実験」を参照してください。
8.2.1 ハードウェア接続
いわゆるループバック送受信とは自発的な自己受信のことで、MCUのMOSIからMISOへ直接データを送信するため、ハードウェア的にはMOSIとMISOを短絡するだけで済みます。たとえば、このセクションの実験 0801_sci_spi_loopback で使用した SCI5 には、送信ピンが P501 で、受信ピンが P502 があります。この実験では、SCK ピンと CS ピンをそのままにして、これら 2 つのピンを短絡するだけです。
8.2.2 アプリケーション
この実験的プロジェクトは spi ドライバ コードをモジュール化し、それを初期化関数、トランシーバ関数、割り込みコールバック関数およびテスト関数に分割します。spi ドライバ関数はすべて drv_sci_spi.c に実装され、drv_sci_spi.h で宣言されます。テスト関数は app_spi にあります。これは c で実装され、その後の移植のために app.h で宣言されます。
- デバイスの初期化
関数: void SPIDrvInit(void)、次のように実装されます。
void SPIDrvInit(void)
{
/* 打开设备 */
fsp_err_t err = g_spi5.p_api->open(g_spi5.p_ctrl, g_spi5.p_cfg);
/* 发送调试信息 */
if(FSP_SUCCESS == err)
printf("Success to open device: spi5\r\n");
else
printf("Failed to open device: spi5\r\n");
}
- 割り込みコールバック関数
割り込みコールバック関数についてはこれまでに何度も述べてきましたが、その実装を直接示します。
static volatile bool gSPITxCplt = false;
void sci_spi5_callback(spi_callback_args_t *arg)
{
/* 判断是否是发送完成触发的中断 */
/* 如果是的话就将发送完成标志位置1 */
if(SPI_EVENT_TRANSFER_COMPLETE == arg->event)
gSPITxCplt = true;
}
送信完了フラグは静的なグローバル変数です。
static volatile bool gSPITxCplt = false;
これは割り込みコールバック関数で変更され、キーワード volatile で変更されます。
- 送受信完了待ち機能
通信を送受信するたびに、送受信ステータスフラグに基づいて通信が完了したかどうかを判断する必要があるため、この判断アクションはタイムアウト待機関数 void SPIDrvWaitTxCplt(void) にカプセル化されます。コードは次のとおりです。 :
static void SPIDrvWaitTxCplt(void)
{
uint16_t wTimeout = 50;
while(!gSPITxCplt && wTimeout)
{
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);
wTimeout--;
}
gSPITxCplt = false;
}
- データドライバー送受信機能
実際、この関数の呼び出し元は、送信データを含むバッファ、データ受信用のバッファ、送受信するデータの数だけを気にする必要があり、データ送信の幅は気にしません。 、つまり、送信関数と受信関数がカプセル化されているのは、次のような関数です。
void SPIDrvWriteReadBuf(uint8_t *wbuf, uint8_t *rbuf, uint16_t wSize)
{
/* 调用writeRead函数收发数据 */
g_spi5.p_api->writeRead(g_spi5.p_ctrl,
(uint8_t*)wbuf,
(uint8_t*)rbuf,
(uint32_t)wSize,
SPI_BIT_WIDTH_8_BITS);
/* 等待size长度的数据发送和接收完成 */
SPIDrvWaitTxCplt();
}
- テストコード
テスト コードは比較的単純です。毎回同じ長さのデータを送受信し、慎重に比較します。エラーが発生した場合は、間違ったデータが出力されます。コードは次のように実装されます。
void SPIAppTest(void)
{
SPIDrvInit();
/* 测试计数,测试count次后退出测试 */
uint32_t dwCount = 5;
/* 收发数据保存的数组,长度为256字节 */
uint8_t wBuf[256] = {
0};
uint8_t rBuf[256] = {
0};
while(dwCount)
{
/* 每次发送数据前给发送数组赋值随机数,增加测试可靠性 */
for(uint16_t i=0; i<256; i++)
{
wBuf[i] = (uint8_t)rand();
}
/* 同时收发256字节数据 */
SPIDrvWriteReadBuf(wBuf, rBuf, 256);
uint16_t err = 0;
/* 逐一比较收发数组中的数据是否一致,如果出现不一致就将其打印出来观察并且计数错误个数 */
for(uint16_t i=0; i<256; i++)
{
if(wBuf[i] != rBuf[i])
{
err++;
printf("Error:\r\n\twBuf:0x%.2x\trBuf:0x%.2x\r\n", wBuf[i], rBuf[i]);
}
}
if(0 == err)
printf("Success to write and read data by sci spi:\t%d!\r\n", (int)dwCount);
dwCount--
/* 每隔1秒测试一次 */
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
}
}
8.2.3 コンピュータ実験
hal_entry.c で void drv_sci_spi_init(void) 初期化関数を呼び出し、コンパイルが成功した後に生成された実行可能バイナリ ファイルをチップにプログラムすると、次の現象が観察されます。
8.3 sci spi駆動の表示実験
この実験ではUARTのprintf関数を使用しますので、前回の記事「7.3 stdio実験」を参照してprintf関数の設定を行い、0801_sci_spi_loopback/driversにあるdrv_uart.cとdrv_uart.hを本節のプロジェクトに移植してください。実験。
8.3.1 ハードウェア接続
このセクションの実験では、RA の SCI SPI モードを使用して、SPI インターフェイスを備えたディスプレイを駆動します。このディスプレイのドライバー チップは ST7796 です。実験ボードとディスプレイの接続概略図は、図に示すとおりです。
このセクションの実験は SPI 通信のみを行うため、回路図のピン 13 ~ 19 に注目するだけで済みます。各ピンの意味は次のとおりです。
インターフェース番号を表示する | インターフェースの意味 | MCUピン番号 | MCUピンモード |
---|---|---|---|
13-味噌 | SPIスレーブ出力ピン、マスター入力ピン | P100 | SCI0 SPI RXD0 |
14-CS | SPI チップ選択ピン、アクティブ Low レベル | P103 | GPIO出力 |
15-RS | ST7796sのデータ/コマンド切り替え端子、ハイレベルはデータ受信、ローレベルはコマンド受信を示します | P104 | GPIO出力 |
16-SCK | SPIクロック出力端子 | P102 | SCI0 SPI SCK0 |
17-MOSI | SPIマスターの出力ピンとスレーブの入力ピン | P101 | SCI0 SPIのTXD0 |
18-リセット | ST7796のハードウェアリセットピン | P105 | GPIO出力 |
19-PWM | 表示画面のバックライト制御端子、ハイレベル点灯 | P608 | GPIO 出力/GPT5 用 GTIOC5A |
バックライト制御ピンについては、IO を直接使用してハイレベルのフルパワーを出力して画面を点灯することも、GPT 機能を使用して PWM 調整を出力することもできます。GPT ペリフェラルについてはまだ言及されていないため、このセクションでは設定します。 GPIO の出力モードとして P608。
8.3.2 SCI SPI とポートの構成
SCI の SPI と GPIO の構成については前の章で説明したため、このセクションでは詳しく説明しません。この章の最初の実験と異なり、このセクションで使用するピンは SCI0 の SPI ピンであるため、SCI チャネルを選択する場合は SCI0 を選択し、そのスタックを構成する場合は制御ブロック名とチャネルが同じである必要があります。チャネル 0 は一致し、割り込みコールバック関数の名前も sci_spi0_callback です。
8.3.3 ディスプレイドライバーの解析
ディスプレイを駆動するには、まずディスプレイ内のドライバー チップを理解し、チップがサポートするインターフェイス、通信に使用されるプロトコル、サポートされるカラー フォーマット、使用可能な構成コマンドなどを調べる必要があります。これらについては、ディスプレイ ドライバー チップのマニュアルを注意深く読む必要があります。
ディスプレイ画面の駆動は比較的複雑です。ディスプレイのパフォーマンスを最大限に引き出したい場合は、マニュアルを読むのに長い時間を費やす必要があります。これは本書の焦点ではありません。本書では、ST7796 で駆動される画面を点灯する方法を説明します。 . 画面を指定した色に設定します。
- 画面の解像度
ST7796 は、最大解像度 320 480 の画面を駆動でき、各ピクセルは 16 tbit を使用して色を表現します。全画面表示には、320 480*2 バイト、約 300K バイトのビデオ メモリが必要です。
- サポートされているインターフェースプロトコル
ST7796 は、次のような多くのインターフェイス プロトコルをサポートしています。
- 8ビット/9ビット/16ビット/18ビット8080パラレルインターフェイス;
- 16/18 RGB インターフェイス。
- 3線式/4線式シリアルインターフェース。
- MIPIインターフェース。
本書では、CS、SCK、MOSI、MISO を含む SPI インターフェイスである 4 線シリアル インターフェイスを使用します。
- コマンド/データ切り替え制御
シリアル インターフェイスによって駆動され、データ モードからコマンド モードへの通信データの相互切り替えは、ST7796 の DCX ピン (前の回路図では RS ピン) に依存します。マニュアルでのこのピンの説明は次のとおりです。
ST7796s は、この端子の入力レベルが「1」の場合は受信データを表示データとみなし、「0」の場合は受信データをコマンドとみなします。つまり、本SPIホスト、つまりMCU側はRS端子からハイレベルとローレベルを出力し、データモードを切り替えます。
- 寝て起きて
ST7796 では、ハードウェア リセットまたはソフトウェア リセットが発生すると、リセット完了後にチップはスリープ モードに入り、マニュアルに記載されているスリープ イン モードになります。このモードでは、チップ内部の水晶発振器は動作せず、 DC変換も停止しますので表示が崩れることはありません。
正常に表示するには復帰する必要があり、スリープと復帰はコマンドによって制御されます。
- 0x10:睡眠
- 0x11:外泊
これら 2 つのコマンドは、スリープ モードで SPI を介して ST7796 に送信できるため、低消費電力シナリオでは、これら 2 つのコマンドを使用して画面をスリープおよびウェイクアップできます。
- 表示モードの設定
ST7796 がサポートする表示モードには、部分表示と通常表示があります。部分表示とはその名の通り、320RGB*480の解像度で完全に表示するのではなく、特定の領域のみを表示するものです。部分表示モードでは、表示領域をコマンドで設定する必要があります。通常表示とは全画面表示のことです。
これら 2 つのモードの設定コマンドは次のとおりです。
- 0x12: 部分表示。
- 0x13: 通常の表示。
- ディスプレイの向きの設定
画面上の画像の表示方向は、画素の表示方向と色の表示方向の2つの要素によって制御されます。ピクセルの表示方向は上下左右の4方向で構成されており、次のように分類できます。
- 左→右、上→下。
- 左→右、下→上。
- 右→左、上→下。
- 右→左、下→上。
色の方向は RGB または BGR の 2 つだけです。
一般に、人々は左→右、上→下と読むことに慣れており、色の方向には RGB がよく使用されます。表示方向を制御するコマンドは0x36で、その説明は次のとおりです。
MY/X/V は、既存の行アドレスと列アドレスのピクセル データの読み取りおよび書き込みの方向であり、詳細は次のとおりです。
具体的な例はマニュアルに詳しく記載されていますが、ここでは図に示すように一部を抜粋します。
データに対応するピクセルを表示する方法は、特定のアプリケーション シナリオによって異なります。
- 色の書式設定
カラーフォーマットの設定に使用されるコマンドは 0x3A で、その説明を次の図に示します。
通常、RGB インターフェイスのカラー フォーマットとコントロール インターフェイスのカラー フォーマットは 16 ビットに設定されており、0x55 として書き込まれます。
- 表示のオンとオフ
ディスプレイのオンとオフを切り替えるために使用されるコマンドは、それぞれ 0x29 と 0x28 です。
- 0x28: ディスプレイオフ、ディスプレイをオフにします。
- 0x29: Display On、ディスプレイをオンにします。
- アドレス設定を表示
表示アドレスは行アドレスと列アドレスに分かれており、行アドレスと列アドレスを設定するには次のようなさまざまなコマンドがあります。
- 0x2A: Column Address Set、列アドレス設定。
- 0x2B: 行アドレスセット、行アドレス設定。
マニュアル内のこれら 2 つのコマンドの説明を参照してください。
これらはすべて、独自の開始アドレスと終了アドレスを設定する必要があり、最初に開始アドレスの上位 8 ビットを送信し、次に開始アドレスの下位 8 ビットを送信し、次に開始アドレスの上位 8 ビットを送信することがわかります。終了アドレス、最後に終了アドレスの下位 8 ビットを送信します。
このルールを理解すると、後から表示領域を設定する際のコードの書き方が分かります。
- ビデオメモリにデータを書き込む
ビデオ メモリにデータを書き込むために使用されるコマンドは 0x2C です。その説明を参照してください。
0x2C をチップに送信した後、N バイトのピクセル データをビデオ メモリに送信できることを理解するのは難しくありません。
8.3.4 ディスプレイドライバー
ST7796 を駆動する基本プロセスは、前の記事で明確にできます。
この実験では、ディスプレイ ドライバー コードは drv_sci_spi_disp.c に実装され、ディスプレイ デバイスの構造は drv_config.h で宣言されます。
- 割り込みコールバック関数と書き込み待ち関数
割り込みコールバック関数は送信完了フラグに1を書き込み、ライトウェイト関数は送信完了フラグが1になるのを待ってクリアするコードです。
static volatile bool gFlagWaitTX = false;
void sci_spi0_callback(spi_callback_args_t *arg)
{
/* 判断是否是发送完成触发的中断 */
/* 如果是的话就将发送完成标志位置1 */
if(SPI_EVENT_TRANSFER_COMPLETE == arg->event)
gFlagWaitTX = true;
}
static void LCDDrvWaitTX(void)
{
volatile uint16_t wTimeout = 500;
while(!gFlagWaitTX && wTimeout)
{
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);
wTimeout--;
}
gFlagWaitTX = false;
}
- ピンのステータスを定義する
ST7796 のピンの説明と回路図の接続関係に従って、各ピンのステータス列挙タイプが定義されており、コードは次のとおりです。
typedef enum{
notLight,
isLight
}Black; /* 背光引脚控制状态 */
typedef enum{
isReset,
notReset
}Reset; /* 复位引脚控制状态 */
typedef enum{
isSelect,
notSelect
}CS; /* 片选信号控制状态 */
typedef enum{
isCommand,
isData
}DC; /* 数据/命令切换控制状态 */
- 制御端子操作機能
ST7796 を駆動するには、デバイスの選択、コマンド データの切り替え、ハードウェア リセット、バックライトの制御などに使用される 4 つのピンを制御する必要があります。それらのコードは次のとおりです。
static void LCDDrvWriteCS(CS eState)
{
g_ioport.p_api->pinWrite(g_ioport.p_ctrl,
BSP_IO_PORT_01_PIN_03,
(bsp_io_level_t)eState);
}
static void LCDDrvWriteDCX(DCX eState)
{
g_ioport.p_api->pinWrite(g_ioport.p_ctrl,
BSP_IO_PORT_01_PIN_04,
(bsp_io_level_t)eState);
}
static void LCDDrvWriteReset(Reset eState)
{
g_ioport.p_api->pinWrite(g_ioport.p_ctrl,
BSP_IO_PORT_01_PIN_05,
(bsp_io_level_t)eState);
}
static void LCDDrvWriteBlack(Black eState)
{
g_ioport.p_api->pinWrite(g_ioport.p_ctrl,
BSP_IO_PORT_06_PIN_08,
(bsp_io_level_t)eState);
}
これらの制御関数は ST7796 の内部ドライバ関数でのみ使用されるため、すべて静的関数であり、以降の使用では次のように使用できます。
LCDDrvWriteReset(isReset);
LCDDrvWriteDCX(isCommand);
LCDDrvWriteDCX(isData);
LCDDrvWriteCS(isSelect);
- ハードウェアリセット
RESET ピン ハードウェアを介してドライバー チップをリセットし、ロー レベルにリセットします。リセットが完了した後、チップがリセット状態にならないように、ハイ レベルに戻す必要があります。コードは次のとおりです。
static void LCDDrvHWReset(void)
{
LCDDrvWriteReset(isReset);
R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
LCDDrvWriteReset(notReset);
R_BSP_SoftwareDelay(50, BSP_DELAY_UNITS_MILLISECONDS);
}
- ライトレジスタコマンド機能
コマンドを書き込むには、DCX ピンを Low にしてから 1 バイトのコマンド データを送信する必要があります。コードは次のとおりです。
static void LCDDrvWriteReg(uint8_t reg)
{
LCDDrvWriteDCX(isCommand);
g_spi0.p_api->write(&g_spi0_ctrl, (uint8_t*)®, 1, SPI_BIT_WIDTH_8_BITS);
LCDDrvWaitTX();
}
- データ書き込み関数
データを書き込むには、DCX を High にしてからデータの送信を開始する必要があります。ここでカプセル化されているのは、データをレジスタに送信する関数です。一度に 1 バイトだけが送信されます。コードは次のとおりです:
static void LCDDrvWriteDat(uint8_t dat)
{
LCDDrvWriteDCX(isData);
g_spi0.p_api->write(&g_spi0_ctrl, (uint8_t*)&dat, 1, SPI_BIT_WIDTH_8_BITS);
LCDDrvWaitTX();
}
- マルチバイトデータ書き込み関数
ディスプレイ ドライバーでは、通常、大量のデータがディスプレイに継続的に送信されますが、このプロジェクトでは、それを次の関数にカプセル化します。
static void LCDDrvWriteBuf(uint8_t* buf, uint32_t size)
{
LCDDrvWriteReg(0x3C);
LCDDrvWriteDCX(isData);
unsigned char *pbuf = (unsigned int*)buf;
while(size)
{
uint32_t length = 0;
if(size<65536)
length = (uint16_t)size;
else
{
length = 65535;
}
fsp_err_t err = g_spi0.p_api->write(g_spi0.p_ctrl, pbuf, length, SPI_BIT_WIDTH_8_BITS);
assert(FSP_SUCCESS==err);
LCDDrvWaitTX();
size = size - length;
pbuf = pbuf + length;
}
}
static void LCDDrvWriteBuf(uint8_t* buf, uint32_t size)
{
LCDDrvWriteReg(0x3C);
LCDDrvWriteDCX(isData);
g_spi0.p_api->write(&g_spi0_ctrl, (uint8_t*)buf, size, SPI_BIT_WIDTH_8_BITS);
LCDDrvWaitTX();
}
まず 0x3C コマンドを送信して、大量のデータが来ていることをディスプレイに伝えてから、データの送信を開始します。
0x3C 連続書き込みモードは、ST7796 の「MX=1」下でのみ使用できます。
- レジスタ表示制御機能
また、オブジェクト指向プログラミングの考え方を使用して、表示の操作は構造体にカプセル化され、特定の操作関数を指すために関数ポインター メンバーが使用されます。この構造体は drv_disp.h で次のように定義されます。
typedef struct DisplayDevice {
char *name;
void *FBBase; /* CPU能直接读写的显存 */
unsigned short wXres; /* X方向分辨率 */
unsigned short wYres; /* Y方向分辨率 */
unsigned short wBpp; /* 每个像素使用多少个像素 */
unsigned int dwSize;
void (*Init)(struct DisplayDevice *ptDev); /* 硬件初始化 */
void (*DisplayON)(struct DisplayDevice *ptDev); /* 开启显示 */
void (*DisplayOFF)(struct DisplayDevice *ptDev); /* 关闭显示 */
void (*SetDisplayWindow)(struct DisplayDevice* ptDev, \
unsigned short wXs, unsigned short wYs, \
unsigned short wXe, unsigned short wYe);
void (*Flush)(struct DisplayDevice *ptDev); /* 把FBBase的数据刷到LCD的显存里 */
/* 设置FBBase中的数据, 把(iX,iY)的像素设置为颜色dwColor
* dwColor的格式:0x00RRGGBB
*/
int (*SetPixel)(struct DisplayDevice *ptDev, \
unsigned short wX, unsigned short wY, \
unsigned short wColor);
struct DisplayDevice *pNext;
}DisplayDevice, *PDisplayDevice;
開発者は、この構造をドライバー プログラムに実装する必要があります。内部パラメーター (解像度など) と関数ポインターを設定します。このプロジェクトで実装された構造は次のとおりです。
static DisplayDevice gLcdDevice = {
.name = "LCD",
.FBBase = gLcdFbuf,
.wXres = 320,
.wYres = 480,
.wBpp = 16,
.dwSize = 320*480*16/8,
.Init = LCDDrvInit,
.DisplayON = LCDDrvSetDisplayOn,
.DisplayOFF = LCDDrvSetDisplayOff,
.SetDisplayWindow = LCDDrvSetDisplayWindow,
.Flush = LCDDrvFlush,
.SetPixel = LCDDrvSetPixel
};
2 番目のメンバー FBBase が指す配列は、ディスプレイの特性に応じて決定する必要があります。たとえば、今回の実験の ST7796 では 1 ピクセルが 16 ビット データで構成されており、全画面には 320 × 480 ピクセルあるため、320 ピクセルが必要です。480 *16/8 ワード以下に示すように、プロセッサ メモリ内のグラフィックスのマッピングを表すセクションの配列。
static unsigned short gLcdFbuf[320*480];
今回の実験で実装した操作機能については、後ほど紹介します。
- 表示デバイス関数の取得
適切なプログラミングの実践として、ディスプレイ デバイスの構造は静的なグローバル変数として定義されます。上位レベルのコードには、この構造を取得するためのインターフェイスが必要です。
struct DisplayDevice *LCDGetDevice(void)
{
return &gLcdDevice;
}
この関数は、上位層アプリケーションがディスプレイ デバイスを操作するために使用する DisplayDevice ポインターを返します。
- モニターのオン/オフ機能
これら 2 つの操作関数は比較的単純で、0x28/0x29 命令を ST7796 に送信するだけです。コードは次のとおりです。
void LCDDrvSetDisplayOn(struct DisplayDevice* ptDev)
{
if(NULL == ptDev->name) return;
LCDDrvWriteReg(0x29);
}
void LCDDrvSetDisplayOff(struct DisplayDevice* ptDev)
{
if(NULL == ptDev->name) return;
LCDDrvWriteReg(0x28);
}
- 表示領域設定機能
前回の記事でわかるように、表示領域の設定とは、表示行と列の開始アドレスと終了アドレスを設定することであり、アドレスの上位 8 ビットが最初に送信され、次にアドレスの下位 8 ビットが送信されます。次のコードの関数はカプセル化されています。
void LCDDrvSetDisplayWindow(struct DisplayDevice* ptDev, \
unsigned short hwXs, unsigned short hwYs, \
unsigned short hwXe, unsigned short hwYe)
{
if(NULL == ptDev->name) return;
/* 设置列地址 */
LCDDrvWriteReg(0x2A);
LCDDrvWriteDat((uint8_t)(hwXs>>8)); // 起始地址先高后低
LCDDrvWriteDat((uint8_t)(0x00FF&hwXs));
LCDDrvWriteDat((uint8_t)(hwXe>>8)); // 结束地址先高后低
LCDDrvWriteDat((uint8_t)(0x00FF&hwXe));
/* 设置行地址 */
LCDDrvWriteReg(0x2B);
LCDDrvWriteDat((uint8_t)(hwYs>>8));
LCDDrvWriteDat((uint8_t)(0x00FF&hwYs));
LCDDrvWriteDat((uint8_t)(hwYe>>8));
LCDDrvWriteDat((uint8_t)(0x00FF&hwYe));
}
- 全画面リフレッシュ機能
この関数は、デバイス構造内のすべての FBBase データを一度に画面に送信し、画面全体を更新するために使用されます。コードは次のとおりです。
void LCDDrvFlush(struct DisplayDevice *ptDev)
{
if(NULL == ptDev->name) return;
LCDDrvWriteBuf((uint8_t*)ptDev->FBBase, (uint32_t)ptDev->dwSize);
}
- ピクセルポイント関数の設定
一部のアプリケーション シナリオでは、指定された画像を表示するために 1 つまたは複数のピクセルを個別に操作することがあります。そのため、使用する個々のピクセルを設定するためのインターフェイスを実装する必要があります。コードは次のとおりです。
int LCDDrvSetPixel(struct DisplayDevice *ptDev, \
unsigned short wX, unsigned short wY, \
unsigned short wColor)
{
if(NULL == ptDev->name) return -1;
if (wX >= ptDev->wXres || wY >= ptDev->wYres)
return -1;
unsigned short *buf = (unsigned short*)ptDev->FBBase;
buf[wY * ptDev->wXres + wX] = (unsigned short)wColor;
return 0;
}
ピクセル位置のオフセットは画面の解像度と表示モードによって決まりますので、本章の計算式がすべての画面に適しているとは限りませんので、移植して使用する場合には注意が必要です。
- 画面初期化関数
この実験で画面設定を初期化するためのパラメータは比較的単純です: SPI を開く -> ハードウェア リセット -> デバイスを起動する -> カラー フォーマットを設定する -> ディスプレイをオンにする -> バックライトを点灯する。コードは以下のように表示されます。
void LCDDrvInit(struct DisplayDevice* ptDev)
{
if(NULL == ptDev->name) return;
/* 打开SPI设备完成初始化 */
fsp_err_t err = g_spi0.p_api->open(&g_spi0_ctrl, &g_spi0_cfg);
if(FSP_SUCCESS == err)
printf("Success to open device:\tspi0\r\n");
else
printf("Failed to open device:\tspi0\r\n");
/* 初始化屏幕设备 */
LCDDrvHWReset(); //LCD 复位
LCDDrvWriteCS(isSelect);
LCDDrvWriteBlack(isLight);//点亮背光
LCDDrvWriteReg(0x11);
LCDDrvWriteReg(0x20);
LCDDrvWriteReg(0x36);
LCDDrvWriteDat(0x40);
LCDDrvWriteReg(0x3A);
LCDDrvWriteDat(0x55);
LCDDrvWriteReg(0x13);
LCDDrvWriteReg(0x29);
}
8.3.5 表示テスト手順
この実験のテスト プログラムには、画面をクリアする機能と円を描画する機能があります。テスト プログラムは app_disp.c ファイルに実装され、app.h で宣言されます。テストプログラムのコードは以下のとおりです。
#define FLOYRGB565(r, g, b) ((unsigned short)((((unsigned short)(r>>3)<<11)|(((unsigned short)(g>>2))<<5)|((unsigned short)b>>3))))
void DispAppTest(void)
{
DisplayDevice *ptDispDev = LCDGetDevice();
if(NULL == ptDispDev)
{
printf("Failed to get LCD device!\r\n");
return;
}
/* 初始化显示设备 */
ptDispDev->Init(ptDispDev);
/* 设置屏幕显示区域 */
ptDispDev->SetDisplayWindow(ptDispDev, 0, 0, ptDispDev->wXres - 1, ptDispDev->wYres - 1);
/* 清除屏幕 */
memset((uint8_t*)ptDispDev->FBBase, 0x00, ptDispDev->dwSize);
ptDispDev->Flush(ptDispDev);
/* 画一个实心圆 */
uint16_t x = 0, y = 0, r = 100;
for(x = ((ptDispDev->wXres>>1) - r); x<((ptDispDev->wXres>>1) + r); x++)
{
for(y = ((ptDispDev->wYres>>1) - r); y<((ptDispDev->wYres>>1) + r); y++)
{
if(((x-(ptDispDev->wXres>>1)) * (x-(ptDispDev->wXres>>1))+(y-(ptDispDev->wYres>>1)) * (y-(ptDispDev->wYres>>1))) <= r*r)
{
ptDispDev->SetPixel(ptDispDev, x, y, FLOYRGB565(0, 255, 0));
}
}
}
ptDispDev->Flush(ptDispDev);
}
- 行 01: マクロ関数を使用して、RGB カラーを 16 ビットのカラー値に変換します。
- 15 ~ 16 行目: 画面をクリアします。
- 19 ~ 30 行目: 画面の中央に半径 100 ピクセルの実線の円を描きます。
次に、hal_entry.c の hal_entry() 関数が各デバイスの初期化関数と Circle 関数を呼び出し、画面上に黒丸を表示します。
#include "drv_uart.h"
#include "app.h"
#include "hal_data.h"
void hal_entry(void)
{
/* TODO: add your own code here */
UARTDrvInit();
DispAppTest();
}
1.3.6 コンピュータ実験
コンパイルされた実行可能バイナリ ファイルがチップに焼き付けられた後、シリアル ポートを介して次の印刷情報を取得できます。
Success to open device: uart7
Success to open device: spi0
画面を見ると黒丸が表示されます。