ソフトウェア的なビットボング方式の SPI スピードでは、特に動画的なフレーム更新において限界が見えることがあります。CPU 負荷を削減しつつ転送レートを向上させるため、本プロジェクトでは標準ペリフェラルライブラリを活用し、ハードウェア SPI モジュールと DMA(Direct Memory Access)機能を併用して 0.99 インチ円形 TFT ドライブを実行します。
DMA(直接メモリアクセス) の役割
DMA は、システム内部のメモリ間や、メモリと周辺機器間のデータ転送を CPU を介さずに制御する機能です。データの書き込み・読み出し処理において、CPU が待機する必要がなく、他のタスクを実行可能となります。これにより、大容量データの連続転送時であってもシステム全体の応答性が維持されます。
具体的には、ストリーミング転送や画像データのようなバッチ処理に適しています。例えば、メモリ上の画像データからディスプレイバッファへデータを流す際、DMA コントローラーがアドレスカウンタを管理し、データの転送が完了するまで CPU は介入しません。この機構により、ソフトウェアマルチスレッドに近い効率を単一のフローで実現できます。
SPI(シリアル周辺インターフェース) 概要
SPI は高速全二重同期通信プロトコルであり、マイクロコントローラーとセンサーやメモリなどの周辺装置を接続するために広く採用されています。主に 4 本の信号線(SCK, MOSI, MISO, NSS/CS)を使用します。
- SCK (Serial Clock): クロック信号。マスター側が生成し転送速度を決定。
- MOSI (Master Out Slave In): マスターからスレーブへのデータ出力。
- MISO (Master In Slave Out): スレーブからマスターへのデータ入力。
- NSS/CS (Slave Select / Chip Select): 対象デバイスの選択ライン。
通信モードは Clock Polarity (CPOL) と Clock Phase (CPHA) の組み合わせによって 4 パターンに分類されます。本例では GC9107 ドライバーとの互換性を考慮し、特定のタイミング設定を選択します。
ハードウェア接続仕様
STM32F103RCT6 にて 0.99 インチ TFT 画面を制御する場合、以下の GPIO ポート割り当てを行います。PWM やタイマー機能などとの競合を避けるため、ハードウェア SPI デフォルトピンを設定しました。
| マイコン端子 (STM32) | デバイス端子 (TFT Screen) | 機能 |
|---|---|---|
| PB5 | MOSI | データ出力 |
| PB3 | SCK | クロック出力 |
| PB9 | CS (CS1_L) | スレーブセレクト |
| PC13 | RES | リセット制御 |
| PC14 | DC | データ/コマンド選択 |
| PC15 | BLK | バックライト |
ドライバー実装の詳細
ここでは、主要な構成要素となる C ソースコードの一部を示します。変数名は可読性を高めるために再設計され、ロジック構造は最適化されています。
1. GPIO 初期化
TFT コントロール用の出力ポートを構成します。
/**
* @brief 画面制御ピン設定
* @param なし
* @retval なし
*/
void TFT_Port_Initialize(void)
{
GPIO_InitTypeDef gpio_config;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
// リセット,DC,バックライト (Port C)
gpio_config.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
gpio_config.GPIO_Mode = GPIO_Mode_Out_PP;
gpio_config.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &gpio_config);
GPIO_SetBits(GPIOC, GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
// チャネルセレクション (Port B)
gpio_config.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
gpio_config.GPIO_Mode = GPIO_Mode_Out_PP;
gpio_config.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_config);
GPIO_SetBits(GPIOB, GPIO_Pin_8 | GPIO_Pin_9);
}
2. ハードウェア SPI 設定
SPI3 モジュールをマスターモードとして構成します。クロック分周比およびデータサイズを調整します。
/**
* @brief SPI3 初期化関数
*/
void SPI3_Hardware_Init(void)
{
GPIO_InitTypeDef spi_gpio_cfg;
SPI_InitTypeDef spi_cfg;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);
// JTAG アセンブリ機能抑制 (PB3 解放のため)
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
// SCK, MOSI の复用設定
spi_gpio_cfg.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5;
spi_gpio_cfg.GPIO_Mode = GPIO_Mode_AF_PP;
spi_gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &spi_gpio_cfg);
spi_cfg.SPI_Direction = SPI_Direction_1Line_Tx;
spi_cfg.SPI_Mode = SPI_Mode_Master;
spi_cfg.SPI_DataSize = SPI_DataSize_8b;
spi_cfg.SPI_CPOL = SPI_CPOL_High;
spi_cfg.SPI_CPHA = SPI_CPHA_2Edge;
spi_cfg.SPI_NSS = SPI_NSS_Soft;
spi_cfg.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
spi_cfg.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI3, &spi_cfg);
SPI_Cmd(SPI3, ENABLE);
}
3. DMA 設定
転送方向、データ幅、メモリアドレス管理方式を動的に設定可能な汎用関数を作成します。色塗りつぶし(半字長転送)と画像転送(バイト転送)に対応させます。
/**
* @brief SPI トランスファー DMA カンフィギュレーション
* @param channel: DMA チャンネル
* peripheral: 周辺機器ベースアドレス (SPI_DR)
* memory: メモリポインタアドレス
* buffer_size: 転送バイト数
* @retval なし
*/
static void Config_DMA_SPI_Transfer(DMA_Channel_TypeDef* channel,
uint32_t peripheral,
uint32_t memory,
uint16_t buffer_size)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
DMA_DeInit(channel);
DMA_StructInit(&dma_init_struct); // デフォルトリセット
dma_init_struct.DMA_PeripheralBaseAddr = peripheral;
dma_init_struct.DMA_MemoryBaseAddr = memory;
dma_init_struct.DMA_DIR = DMA_DIR_PeripheralDST; // 記憶体→周辺
dma_init_struct.DMA_BufferSize = buffer_size;
dma_init_struct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
dma_init_struct.DMA_MemoryInc = DMA_MemoryInc_Enable;
dma_init_struct.DMA_Priority = DMA_Priority_Medium;
dma_init_struct.DMA_M2M = DMA_M2M_Disable;
// データサイズ設定は呼び出し元で変更される場合があるが、ここではデフォルト保持
DMA_Init(channel, &dma_init_struct);
}
// ダミーフラグチェック用
void WaitFor_DMA_Complete(DMA_Channel_TypeDef* channel)
{
while(DMA_GetFlagStatus(channel, DMA_FLAG_TC) == RESET);
DMA_ClearFlag(channel, DMA_FLAG_TC);
}
4. 領域塗りつぶし処理
指定矩形範囲を一括色で埋める機能です。16 ビットカラーデータを扱いため、DMA を HalfWord モードで使用します。
/**
* @brief 矩形エリアのカラフル塗りつぶし
*/
void LCD_FillRect(uint16_t x_start, uint16_t y_start,
uint16_t x_end, uint16_t y_end, uint16_t color_val)
{
uint16_t buffer[1];
uint32_t pixel_count;
buffer[0] = color_val;
pixel_count = (uint32_t)(x_end - x_start) * (y_end - y_start);
LCD_Address_Set(x_start, y_start, x_end - 1, y_end - 1);
CS_Port_Low();
DC_Port_Low(); // コマンドまたはデータ依存で切り替えが必要
// SPI データサイズを 16bit に設定
SPI3->CR1 |= (1<<11);
// DMA 設定(HalfWord モード想定)
Config_DMA_SPI_Transfer(DMA2_Channel2, (uint32_t)&(SPI3->DR), (uint32_t)buffer, (uint16_t)pixel_count);
SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE);
DMA_Cmd(DMA2_Channel2, ENABLE);
WaitFor_DMA_Complete(DMA2_Channel2);
CS_Port_High();
SPI3->CR1 &= ~(1<<11); // 8bit モードに戻る準備
}
5. ビットマップ画像表示
配列形式の画像データを読み取り、順次送信します。今回は 8 ビット単位での転送を想定しています。
/**
* @brief 画像データ表示処理
*/
void LCD_DrawBitmap(uint16_t pos_x, uint16_t pos_y,
uint16_t width, uint16_t height, const uint8_t *data_ptr)
{
uint32_t total_bytes;
total_bytes = (uint32_t)width * (uint32_t)height * 2; // RGB565 データの場合
LCD_Address_Set(pos_x, pos_y, pos_x + width - 1, pos_y + height - 1);
CS_Port_Low();
DC_Port_High(); // データモード
// DMA セットアップ(Byte モード)
Config_DMA_SPI_Transfer(DMA2_Channel2, (uint32_t)&(SPI3->DR), (uint32_t)data_ptr, (uint16_t)total_bytes);
SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE);
DMA_Cmd(DMA2_Channel2, ENABLE);
WaitFor_DMA_Complete(DMA2_Channel2);
CS_Port_High();
}
これらの機能を実装することで、ソフトウェア SPI に比べて転送帯域が大幅に向上し、フレームレート低下やちらつきが発生しない滑らかなアニメーション再生が可能になります。
メインルーティンでは上記関数を呼び出し、適宜デューティー比やウェイト時間を調整することで、視認性のある動きを実現します。DMA 完了待ちループを使用することで、正確なタイミング制御を保証しつつ、他のタスクスケジューリングに影響を与えないように設計されています。
以上のように、ハードウェア SPI と DMA の組み合わせを用いることで、組み込み環境における高負荷なグラフィック表示処理が現実的なコストで達成可能です。