春雨日記 about me tags

STM32のグラフィックアクセラレータを使ってみるメモ

はじめに

最近STM32で液晶に画像を表示させたりする機会があり、フォント描画をうまくやれないかという事でSTM32の持つChrom-ARTについて調べていました。

とりあえず画像を表示したりブレンドしたりできるようになったのですが、ネット上であまり記事が見つからなかったので書いてみようと思います。

最初に言っておくと、STのサンプルプロジェクトに殆ど答えがあります

記事が少ないのはそういう事なんでしょう…

なので、サンプルが充実している普通のコピーや変換はサンプルを見てもらうとして、A8やL8を用いてブレンドする使用例を載せてみます。

前提知識

DMA2Dには、以下のような機能があります。

  • サイズの異なるバッファに座標を変換しながら画像をコピー
  • 色空間が異なるバッファに変換しながら転送
  • 色テーブルを用いて小さなデータとして保存し、バッファにテーブルを参照しながら復元して転送
  • 2つのバッファ(サイズが同じ必要は無い)を透明度を用いて合成しながら転送

至れり尽くせりですね(笑) しかもCPUと独立して動くため、その間にデータの用意をしたり別の計算をする事ができます。

で、これらの機能を使用するにあたって、どのように座標が取り扱われるか知っておく必要があります。

イラストにしてみたものがコチラ

正しく画像を転送するため、

  • 転送元画像のポインタ
  • 転送先開始地点のポインタ
  • 転送元画像の横幅
  • 転送元画像の縦幅
  • オフセット の情報が必要です。

内部的な動作としては、1行分転送し終わったら次の横要素までオフセットを飛ばし、再度転送という挙動みたいです。

そのため

オフセット = 転送先横幅 - 転送元横幅

となります。データ幅は内部的に変換されているようなのでピクセル値で指定します。

使用例1: A8でブレンド

A8やA4はアルファチャンネルのみを持つフォント描画等に向けたモードらしいです。1

ここではフォント描画として、転送先フレームバッファを背景として使用し、文字を合成してみます。

転送先と転送元が同じになる使い方はサンプルでもされているので、問題ないと考えています。

  • フレームバッファ: 800*480
  • フォントの形式: A8
  • フレームバッファの形式: RGB565

注意して欲しいのが、A8やA4を使用する際はhdma2d.LayerCfg[n].InputAlphaがAlphaではなく色情報となる点です。ここに描画色を設定する事で、A8の透明度と合成された色が書き込まれます。

 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void DMA2D_DrawA8(uint8_t *src, uint8_t *dst, uint16_t x, uint16_t y, uint16_t xlen, uint16_t ylen, uint32_t color){
  uint32_t destination = (uint32_t)dst + (y * 800 + x) * 2;
  uint32_t source      = (uint32_t)src;
  hdma2d.Init.Mode          = DMA2D_M2M_BLEND;
  hdma2d.Init.ColorMode     = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.OutputOffset  = 800 - xlen; //FB width - image width => skip count per line
  hdma2d.Init.AlphaInverted = DMA2D_REGULAR_ALPHA;
  hdma2d.Init.RedBlueSwap   = DMA2D_RB_REGULAR;
  hdma2d.XferCpltCallback  = NULL;
  //FG(font)
  hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
  hdma2d.LayerCfg[1].InputAlpha = color & 0x00ffffff; // <- color configuration on A8 & A4
  hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_A8;
  hdma2d.LayerCfg[1].InputOffset = 0;
  hdma2d.LayerCfg[1].RedBlueSwap = DMA2D_RB_REGULAR;
  hdma2d.LayerCfg[1].AlphaInverted = DMA2D_REGULAR_ALPHA;
  //BG
  hdma2d.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA;
  hdma2d.LayerCfg[0].InputAlpha = 0xff;
  hdma2d.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
  hdma2d.LayerCfg[0].InputOffset = 800 - xlen;
  hdma2d.LayerCfg[0].RedBlueSwap = DMA2D_RB_REGULAR;
  hdma2d.LayerCfg[0].AlphaInverted = DMA2D_REGULAR_ALPHA;

  hdma2d.Instance          = DMA2D;

  HAL_DMA2D_Init(&hdma2d);
  HAL_DMA2D_ConfigLayer(&hdma2d, 1);
  HAL_DMA2D_ConfigLayer(&hdma2d, 0);
  HAL_DMA2D_BlendingStart(&hdma2d, source, destination, destination, xlen, ylen);
  HAL_DMA2D_PollForTransfer(&hdma2d, 100);
}

int main(void){
  // 省略
  uint8_t canvas[800*32] = {0};
  memset(canvas,0x00,800*32);
  memset((void*)FBStart+(800*32*2),0xaa,800*32*2);
  strHandler.cursor_x = 0;
  strHandler.cursor_y = 0;
  FONTX2_str_puts(&strHandler, "アルファ255",255);
  FONTX2_str_puts(&strHandler, "アルファ127",127);
  FONTX2_str_puts(&strHandler, "アルファ63",63);
  FONTX2_str_puts(&strHandler, "アルファ31",31);

  DMA2D_DrawA8(canvas, (uint8_t*)FBStart, 0, 32, 800, 32  , 0);
}

動作させると、トップ画像の下のようになります。

使用例2: L8でブレンド

L8は色テーブル(CULT)を用いて少ない情報量でRGB888やARGB8888を扱う方法です。

特にフォント描画になると、同じ値でドットを埋める事が多く、uint32_tの広大なバッファを0x00000000か0xffffffffで埋める事になったりします(笑)

この例では色テーブルの設定にHAL_DMA2D_ConfigCLUTを使用しているのですが、STが推奨するHAL_DMA2D_CLUTLoadはうまく動きませんでした。

SET_BIT(hdma2d->Instance->BGPFCCR, DMA2D_BGPFCCR_START); があるか否かの違いしかないのですが、よくわからないです…

CULTの0番目を0にしておくと、バッファを0埋めする事で背景が透明になって便利です。

  • フレームバッファ: 800*480
  • フォントの形式: L8
  • フレームバッファの形式: RGB565
  • CULTの形式: ARGB8888

ちなみに、実際に使った感じCULTがARGB888でも最終的には合成されてRGB565になるみたいです。

 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
uint32_t clut_font[] = {
            0x00000000, //透明: 背景色
            0xffff0000, //(255,0,0)
            0xff00ff00, //(0,255,0)
            0xff0000ff};//(0,0,255)

void DMA2D_DrawL8(uint8_t *src, uint8_t *dst, uint16_t x, uint16_t y, uint16_t xlen, uint16_t ylen){
  uint32_t destination = (uint32_t)dst + (y * 800 + x) * 2;
  uint32_t source      = (uint32_t)src;
  hdma2d.Init.Mode          = DMA2D_M2M_BLEND;
  hdma2d.Init.ColorMode     = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.OutputOffset  = 800 - xlen;
  hdma2d.Init.AlphaInverted = DMA2D_REGULAR_ALPHA;
  hdma2d.Init.RedBlueSwap   = DMA2D_RB_REGULAR;
  hdma2d.XferCpltCallback  = NULL;
  //FG(font)
  hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
  hdma2d.LayerCfg[1].InputAlpha = 0xff;
  hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_L8;
  hdma2d.LayerCfg[1].InputOffset = 0;
  hdma2d.LayerCfg[1].RedBlueSwap = DMA2D_RB_REGULAR;
  hdma2d.LayerCfg[1].AlphaInverted = DMA2D_REGULAR_ALPHA;

  //BG
  hdma2d.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA;
  hdma2d.LayerCfg[0].InputAlpha = 0xff;
  hdma2d.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
  hdma2d.LayerCfg[0].InputOffset = 800 - xlen;
  hdma2d.LayerCfg[0].RedBlueSwap = DMA2D_RB_REGULAR;
  hdma2d.LayerCfg[0].AlphaInverted = DMA2D_REGULAR_ALPHA;

  hdma2d.Instance          = DMA2D;

  DMA2D_CLUTCfgTypeDef cfg = {0};
  cfg.CLUTColorMode = DMA2D_CCM_ARGB8888;
  cfg.Size = 4;
  cfg.pCLUT = clut_font;

  HAL_DMA2D_Init(&hdma2d);
  HAL_DMA2D_ConfigLayer(&hdma2d, 1);
  HAL_DMA2D_ConfigLayer(&hdma2d, 0);
//  HAL_DMA2D_CLUTLoad(&hdma2d, cfg, 1); // <- suggested by ST but won't work...
  HAL_DMA2D_ConfigCLUT(&hdma2d, cfg, 1);
  HAL_DMA2D_BlendingStart(&hdma2d, source, destination, destination, xlen, ylen);
  HAL_DMA2D_PollForTransfer(&hdma2d, 100);
}

int main(void){
  // 省略
  uint8_t canvas[800*32] = {0};
  FONTX2_str_handler_t strHandler = {0};
  FONTX2_str_init(&strHandler, 1, &hHanF, &hZenF,800,32,canvas);
  FONTX2_str_puts(&strHandler, "赤色:0x01",0x01);
  FONTX2_str_puts(&strHandler, "緑色:0x02",0x02);
  FONTX2_str_puts(&strHandler, "青色:0x03",0x03);

  DMA2D_DrawL8(canvas, (uint8_t*)FBStart, 0, 0, 800, 32); //L8 CULT draw

}

動作させると、トップ画像の上のようになります。

おわりに

300円液晶でうまく動かなかったのは何だったんだと思うぐらい簡単に動いてくれました。 STのサンプルは要チェックですね…

ちなみに、使用例に出てきたフォント描画には 拙作H3FONTX2 を使っています。