你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32CubeMX |42-使用DMA2D加速显存数据传输

[复制链接]
STMCU小助手 发布时间:2021-7-26 10:38
一、使用CPU搬运数据到显存( J6 m2 x( u5 w' N' W4 j/ ^) o
在上一篇文章中讲述了如何配置 LTDC 驱动 RGB 屏幕:
3 D9 x8 [+ j4 u0 ^* X. [2 n" m4 ~. m: \4 k9 I- N6 F( m9 {  v5 X! l1 C

+ c1 r2 K9 Z. ?9 m. `8 SSTM32CubeMX | 41-使用LTDC驱动TFT-LCD屏幕(RGB屏)。# ^, e" H; I- l7 p  K2 i
, V  G2 _! j6 K0 C8 o8 f( t$ S

: O0 E5 l  D$ g* o* ?9 W# S9 C( r本节中我们接着上一节的实验,讲述如何使用 DMA2D 实现打点、画线、填充等函数,只需要单层全屏即可,修改LTDC层配置如下:- r& t6 t7 F, @. c/ z% f
1.png + i7 w" ^3 \' L

7 u: C. X! R! I

3 l7 X: z( F, ]6 c1. 编写lcd驱动头文件, j  l1 ^! Y+ M1 `/ j2 B! c: a- G
创建lcd_rgb_ltdc_drv.h文件,存放关于操作LCD屏幕的一些宏定义配置和函数定义:
2 G9 w6 n3 d0 m
  1. <font face="微软雅黑" size="3">#ifndef _LCD_RGB_LTDC_DRV_H_+ d! u$ b) w2 f
  2. #define _LCD_RGB_LTDC_DRV_H_
    ! ~7 L9 `6 c" w* d

  3. 5 x3 g* B$ K5 C! d1 r' |* M
  4. #include "ltdc.h"
    4 u) w: b" d/ t! H( B2 E/ Y0 I
  5.   j9 u4 n. Q8 e/ J: X( p* e
  6. /**  ]# t* y6 Y5 |; I* n
  7. * @brief   Windows size on lcd.
    9 T* e) o9 x8 \: C2 D
  8. */
    ) _0 E; X# U6 l$ h7 T) k
  9. #define LCD_WIDTH       1024
    ! b* s$ y* `& p: x/ Z/ m! E
  10. #define LCD_HEIGHT      600
    ( E% @. I" h3 y! |. a4 `; E

  11. 0 N1 j3 ^( ^6 d- Z6 p4 g' w
  12. /**7 z1 m! Q$ v9 B
  13. * @brief   Backlight control pin of lcd.- M  F* h# N+ e4 \1 D! `6 N' ]
  14. */1 T% h6 b& k' O8 r
  15. #define LCD_BL_GPIO_PORT    GPIOB6 W5 p9 F3 i6 L. x3 y0 k' Y$ z
  16. #define LCD_BL_GPIO_PIN     GPIO_PIN_5
    9 g" [6 F+ Q! l

  17. 0 \! a+ Y8 A2 ?0 Q! }
  18. /**. z* n) t6 n# N4 J6 f9 X
  19. * @brief   start address of lcd framebuffer.
    # Z- k7 V# N: u' Z
  20. */# g7 }! ~0 k6 a* C
  21. #define LCD_FRAME_BUFFER    0xc0000000: `* m' c' R; y! L1 @. m8 U

  22. 2 c" n4 k: b9 k/ z+ @+ ^
  23. /**
    ' _% @; S9 h! H! [' M
  24. * @brief   color9 S, R: {/ ^# c! ?3 m- p0 o
  25. * @note    rgb565   
    # ]- w, G' o* U, M- L: K- x
  26. */
    3 W8 [3 s9 W5 D+ o
  27. #define BLACK   0x0000
    ; Z: M: [$ K$ ~) r; l: ^
  28. #define BLUE    0x001F
    # s8 }; `7 u% f' M0 [/ P6 o! J
  29. #define GREEN   0x07E0
    : L/ C) v4 L6 u6 g; U+ Y& _
  30. #define GBLUE   0X07FF9 |/ C7 c8 u! R' i. w
  31. #define GRAY    0X8430' r& z2 \; j1 Z/ V
  32. #define BROWN   0XBC405 y/ t; [* y# D6 C  O: O* B) m
  33. #define RED     0xF800$ R: b9 L5 G' E
  34. #define PINK    0XF81F' K( g" n0 n4 t; {# v" h% ~. s; N0 c' [
  35. #define BRRED   0XFC07- L" X5 d5 ~. U. T
  36. #define YELLOW  0xFFE0, G. |2 y5 U" G9 g0 N2 O& G, O
  37. #define WHITE   0xFFFF7 @; J# Y8 [, W/ k

  38. / c: w) P: j* D9 R) O
  39. /**4 r3 N3 C. ^; h& V
  40. * @brief       Control the lcd backlight.
    2 j/ K0 T3 r# O. h/ T) d, y
  41. * @param[in]   brightness  the value of lcd backlight.$ U1 ?- \- |$ r8 _& _/ m
  42. * @return      None
    # u- V1 _. J6 a+ m  A9 {
  43. */
    & U/ @+ U' @! r5 s7 S# b& G
  44. void lcd_backlight_control(uint8_t bightness);$ ], l' v. \. f) F* M( f: n
  45. 3 z. a  f4 E5 q  ~. ?+ U
  46. /**+ l: X5 ~0 {2 \7 S" `9 j
  47. * @brief       LCD initialization.5 r' O2 I  _- A( G- a) _/ B
  48. * @param       None
    5 v/ b' x$ ^: e; L
  49. * @return      None
    * D' V* q! R" d  I6 k& y( W% ?0 x
  50. */
    3 ^" s' {: b, w$ b# Y/ U
  51. void lcd_init(void);
    $ m7 P. h# p$ `. N- W3 D

  52. 5 K- G/ G  k0 v# {# |
  53. /**" }+ [; T. Z" {! {  M8 [$ c
  54. * @brief       Clear lcd.4 r8 f& K+ r0 w
  55. * @param[in]   color   rgb565., M! x9 M; ]6 Y" C# A9 d
  56. * @return      None
    , W& O- s5 N4 |/ d6 R
  57. */; r$ L$ ?/ z$ v& u) l( W9 Z; a
  58. void lcd_clear(uint16_t color);
    ( Q3 I0 v( |$ z+ M+ b$ ~3 g; }) E
  59. + S8 k1 |5 a6 B9 X8 {' r  ?
  60. #endif /* _LCD_RGB_LTDC_DRV_H_ */</font>
复制代码

2 r2 `: l" J; C/ i7 h2 |  {- L2 J

1 W/ k# F2 X) e; u3 r9 _2. lcd驱动实现- s+ M/ m. O; v; Z. i
创建lcd_rgb_ltdc_drv.c文件,存放关于操作LCD屏幕的函数实现。0 b9 B$ o9 q  Z  V% \

) g1 u1 a4 }/ x4 n0 r0 `' B% D. l
# x8 {1 `- R/ j6 v! G2 Q# l
首先是背光控制实现,应该使用pwm实现背光调节,本文中为了方便直接使用GPIO控制:2 o* X( l% C3 A) S0 y3 C
  1. <font face="微软雅黑" size="3">void lcd_backlight_control(uint8_t bightness)$ W5 L+ x9 Z  C4 U+ i
  2. {
    2 K3 d; q/ U4 D: q, t: u7 t
  3.     // todo: use pwm to control backlight
      h' T+ P6 f$ Q
  4. ( c( j4 q! W: \' q* M) O3 l
  5.     if (bightness) {   
    / A: c! W8 R+ X) z& }; _& \
  6.         // turn on the backlight
    - l9 z* f- a0 q' t8 r: A
  7.         HAL_GPIO_WritePin(LCD_BL_GPIO_PORT, LCD_BL_GPIO_PIN, GPIO_PIN_SET);
    / H3 v* k, R4 Z! J; E9 ]; C( }
  8.     } else {            
    6 F0 M: p! b% p- i" t( K- i( |* m
  9.         // turn off the backlight
    ; z* f( [- x6 Q. H! s9 t4 X3 @
  10.         HAL_GPIO_WritePin(LCD_BL_GPIO_PORT, LCD_BL_GPIO_PIN, GPIO_PIN_RESET);4 p9 X" s/ R' E- B1 j
  11.     }& \( e+ \- x1 m5 F
  12. }</font>
复制代码
接着实现lcd清屏函数,使用CPU(循环)搬运数据到显存中:6 g2 w2 `  U( Z: h8 h) C! |
  1. <font face="微软雅黑" size="3">void lcd_clear(uint16_t color)( f9 H/ U& ^0 l* C" [
  2. {
    7 E2 \$ k+ ]% z) K+ D: ?
  3.     uint16_t *ptr = (uint16_t*)LCD_FRAME_BUFFER;, e9 T3 n% M; o! a( u6 p
  4.     uint32_t i = 0;6 _+ p) e2 D9 C" u$ l6 g* m1 T( F
  5. 3 B8 }( C0 y* H3 v
  6.     while (i++ < LCD_WIDTH*LCD_HEIGHT) {
    ( j5 ~% M: T+ f9 ^
  7.         *(ptr+i) = color;7 M# G% w- N7 r: J# X6 r% T6 M
  8.     }
    - M, N9 ]! |9 P1 z) k: e: R; W; ~% @6 M
  9. }</font>
复制代码
最后实现lcd初始化函数,先刷显存,然后开背光,防止设备上电时屏幕闪烁:
9 S+ _0 N, K/ w
  1. <font face="微软雅黑" size="3">void lcd_init()6 `: U% j( h- K/ u1 @1 O
  2. {
    ! k- y+ B# i/ I- [
  3.     lcd_clear(BLACK);0 X/ V. W# C; Y1 _7 W0 F' ~
  4.     lcd_backlight_control(255);
    * ]' l8 m6 \1 n& |4 w4 b* X* j* G
  5. }</font>
复制代码
+ P4 G" H8 B+ ^6 ^
0 g: `8 P9 ]$ R( m
3. lcd单次清屏时间测试0 F3 a6 D3 Q6 x4 l. D2 N+ \; Y- p# s
在main.c中包含驱动头文件:
3 d; r' j9 \2 t9 s0 V1 S
  1. <font face="微软雅黑" size="3">#include "lcd_rgb_ltdc_drv.h"</font>
复制代码
在main函数中的sdram初始化函数之后,添加lcd初始化函数,并使用HAL库自带的systick时间戳测量一次清屏的时间:
/ ^* J; p) B" j! e
  1. <font face="微软雅黑" size="3">/* USER CODE BEGIN 2 */
    5 _7 e8 [- [. K- O& U8 j' j) ^+ |
  2. printf("sdram test by mculover666\r\n");
    7 D# N- L) Y9 S$ d
  3. SDRAM_Init();: }) J+ W: D! D6 o
  4. printf("sdram init success\r\n");
    " H8 H2 D6 X$ }3 `2 J1 f2 r

  5. 5 ]) c, e" K, ^
  6. lcd_init();& Z: w: Q( U# }( b1 R7 }

  7. ' K; x; }1 `& R3 x
  8. start_time = HAL_GetTick();/ I5 a$ `6 v- A9 G+ h
  9. lcd_clear(PINK);' W9 @+ `8 d! M5 g( n: h
  10. end_time = HAL_GetTick();
    $ l  g4 i) f6 f
  11. 5 c) I8 {  e  Z' L7 r# u
  12. printf("lcd clear spend time:%ld ms\r\n", end_time - start_time);
    ! j8 O! r+ \4 D
  13. /* USER CODE END 2 */</font>
复制代码
编译、运行,在串口助手可以看到使用CPU搬运数据到显存中,在-Og优化等级下单次清屏需要 155 ms左右,在-O0优化等级下单次清屏需要321ms左右:
1 J4 S# ^! d9 c6 v 2.png 0 a8 ?: s( W& p) a
' [9 `. `1 O7 w$ a+ h8 M% U

" R9 O7 y# r& b4 a- Y  O二、使用DMA2D加速显存数据搬运
; L! w) e2 u# C0 p1. DMA2D
, q4 y  H9 i; E% R7 I% T在STM32中,DMA2D外设专门用来给LCD显示加速,有LTDC外设的型号中,通常也会配套有DMA2D。
0 z8 B0 N9 D; n! c* L
% K' T' z4 b4 d4 |
/ r7 o# k/ R* ~, b" M! j  h: V
DMA2D外设主要提供了两个功能:
2 z2 o, Y+ m9 w6 h$ |& D/ t
; N8 t3 V$ T# n2 G1 L% o
% v* Z3 I4 N3 M
DMA数据搬运:常用从寄存器到存储器、存储器到存储器两种模式,快速高效,并且不占用cpu资源;: b, t) [4 j" @2 f3 X5 h3 V1 x
2D图形加速:支持快速格式转换和混合;) H5 S2 n5 \5 `& B, B" M: x
本文中主要使用到DMA2D外设的数据搬运功能,使用起来也是比较简单。$ q% m8 C' a, h$ I5 w1 i

/ p6 M$ Y$ p( Z$ ^7 f* }

+ l+ S6 g* |  Y2. 开启DMA2D
+ g. x+ N3 z5 k4 W$ D5 @$ g 3.png
+ Y" \1 V. d5 U! M7 V9 R% M6 s, |重新生成工程,cubemx会自动生成并调用dma2d初始化函数,完成dma2d外设时钟使能以及dma2d传输模式配置。1 q9 p+ O- F; k7 o" {: j! j3 ?

$ M4 [4 o0 F# D  c" [  o5 H
; h- n4 w* {3 a) G
3. 使用DMA2D实现lcd清屏函数8 M4 R- \1 e, Y" D
在lcd驱动头文件中添加一个宏定义,用于控制是否使能DMA2D:
' J' ?9 [: m$ D2 }
  1. <font face="微软雅黑" size="3">/**
    2 K1 Z; Y; o. R2 h6 u9 ]
  2. * @brief   whether use dma2d to transfer data to lcd framebuffer.6 {/ U5 D2 Q  b9 c
  3. */' X  h+ c. Y4 k+ O0 U
  4. #define USE_DMA2D_EN        1</font>
复制代码
接着对DMA2D传输操作进行封装,编写一个DMA2D传输函数:# B, b0 [- ]0 |4 _2 \* y+ z2 N0 r
  1. <font face="微软雅黑" size="3">static void dma2d_transfer_data_r2m(uint32_t *addr, uint32_t xSize, uint32_t ySize, uint32_t offsetLine, uint16_t color). d" m5 ~$ o2 o5 S/ }# y& d! f( f+ A
  2. {
    : S; H* t; ^  r5 F
  3.     DMA2D->CR = DMA2D_R2M;   // dma2d mode: register to memory.& ]8 R' p- m/ h& C# A% b
  4.     DMA2D->OPFCCR = DMA2D_OUTPUT_RGB565;0 t9 W; t! F! Z" a, D

  5. ! I- q2 F) ]1 {- e4 A
  6.     DMA2D->OCOLR = color;$ A1 Z$ x: Y, B( L. s
  7.     DMA2D->OMAR = (uint32_t)addr;
    5 I& E9 m# r$ u& a4 |! L
  8.     DMA2D->OOR = offsetLine;
    2 i0 X0 e! W# g7 }
  9.     DMA2D->NLR = (uint32_t)(xSize << 16) | (uint16_t)ySize;8 j2 F0 y* g1 ~( y( t  Z! T! J
  10.    
    ) p" V8 B: I9 f2 v# W9 \
  11.     DMA2D->CR |= DMA2D_CR_START;
    0 @8 q: i# l2 o* T  [& \
  12.     while (DMA2D->CR & DMA2D_CR_START);' T$ E, [8 i* J( S5 i* n. {! @
  13. }</font>
复制代码
利用此DMA2D传输函数,重新添加清屏函数的实现:
% Y& p+ z) i) H! T. B+ M( x
  1. <font face="微软雅黑" size="3">void lcd_clear(uint16_t color)
    $ L' X( B0 P8 y
  2. {) C1 m( D( k* {2 x
  3. #if USE_DMA2D_EN$ d) j6 i% O, H  V. s
  4.     dma2d_transfer_data_r2m((uint32_t *)LCD_FRAME_BUFFER, LCD_WIDTH, LCD_HEIGHT, 0, color);
    3 p! v  ^6 u2 K  ~
  5. #else; `" h' ~  |0 h$ L; \
  6.     uint16_t *ptr = (uint16_t*)LCD_FRAME_BUFFER;
    ' v1 B7 X8 x* T) L3 |
  7.     uint32_t i = 0;
    : ^$ H/ d7 I6 m. Z$ Y; T

  8. 1 Z3 ]( b% E4 l1 e& m; w
  9.     while (i++ < LCD_WIDTH*LCD_HEIGHT) {: D( }5 e# X! T2 k+ Q2 ?  P
  10.         *(ptr+i) = color;7 ]3 i7 i" ?& C. N
  11.     }
    3 g! w) i7 ~( |4 h; k7 L! a
  12. #endif /* USE_DMA2D_EN */# W# x2 g6 c2 h2 b0 }! m
  13. }</font>
复制代码
上层测试代码不变,确保控制dma2d的宏使能:
4 Y" m8 Q& f  J  W
  1. <font face="微软雅黑" size="3">6 Y. |5 d3 O" [, k1 O3 _4 a# _0 S
  2. #define USE_DMA2D_EN        1</font>
复制代码
编译、下载,在串口助手中查看清屏一次所需时间:7 a4 ]' L; B3 x* {; f
4.png 4 h5 L% q5 K  s8 H6 G- c; z0 u
可以看到,刷屏一次只需31ms即可,并且在使用dma2d传输数据的情况下,数据传输时间和编译优化等级无关。
8 l. ]2 ^6 s6 @1 C5 o# ~+ ]
7 |; r- \3 @6 d3 _; M5 F9 I2 L: [! ]
6 E4 a4 P( m4 b9 X8 h
三、LCD基本功能实现) d% \: \0 W3 t) A
LCD基本功能包括打点、读点、画线、绘图等函数。4 |, P+ x0 F. [
$ G% A- ~2 ~7 P' `
8 P' W* N' d$ u* A% T  [$ L
1. 打点函数
0 C- S3 R, E6 o- N  ^. H3 T打点函数的核心是计算当前用户给出的坐标位置在显存中的位置,两种实现如下:
! H% c4 q# [# q3 l+ d* E0 d4 g  ^. ?6 x. @
  1. <font face="微软雅黑" size="3">void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color)) ?7 c! q+ U  Z- X
  2. {
    8 L' O1 y' Z( I
  3.     uint32_t pos;
    3 F: F0 t( m$ j" |9 X
  4.     uint16_t *ptr;4 v* o$ ~5 ], R3 V  }9 w
  5. " h" E3 q& o8 h
  6.     // check position.4 \4 B9 C- y" h4 |
  7.     if (x > LCD_WIDTH || y > LCD_HEIGHT) {* u' E% |) J5 O0 N1 f
  8.         return;
    * x! X* h6 }' Z  n
  9.     }! d8 I: U2 h+ {! N; f
  10.   ~4 T2 h0 t4 D. S% ]$ m" J9 B
  11.     // calculate the position offset in framebuffer.  N* S4 R% L& I9 h' V# J2 A
  12.     pos = x + y*LCD_WIDTH;
    , A9 t- e7 [3 |, ~! y/ N4 y' M+ T
  13.     ptr = (uint16_t*)LCD_FRAME_BUFFER;
    : {  y2 D+ `! L. B( ~+ M9 d& s
  14. 3 A8 Y. m! x# E# \: Y* \7 c" }
  15.     // modify the framebuffer., \- x  z' A8 z1 z9 m
  16. #if USE_DMA2D_EN( }% f. a3 j: Y2 P
  17.     dma2d_transfer_data_r2m((uint32_t *)(ptr+pos), 1, 1, 0, color);( ]0 q# Y6 C# g
  18. #else, u4 f  I4 e4 s: ?1 d7 E% T
  19.     *(ptr+pos) = color;& p* y+ \% A( g: a; t4 a
  20. #endif /* USE_DMA2D_EN */9 Q' G  k8 ~: M; x$ b, i- F
  21. }</font>
复制代码
2. 读点函数实现
+ @: a* _8 c, }! ~3 A" t读点函数实现的核心也是计算出用户给出的坐标位置在显存中的位置:
! g4 O- P' D- A7 o2 C" s5 q1 z! _% n
% ~' {2 P( _$ T
  1. <font face="微软雅黑" size="3">uint16_t lcd_read_point(uint16_t x, uint16_t y)
    . d* R, l5 ]& g
  2. {( m& ?7 E' J& z' n/ K/ u# c
  3.     uint32_t pos;
    : o/ k) D6 v8 i6 o
  4.     uint16_t *ptr, data;
    0 z1 _; ?' H  ?3 T" b# C  {! ~8 L
  5. ( ^5 ^) A2 Y4 ~) A& D
  6.     // check position.
    9 y- h; s2 |6 b1 I9 |1 }: [
  7.     if (x > LCD_WIDTH || y > LCD_HEIGHT) {
    ! f' m  E' w$ O& ~: O9 z
  8.         return 0;* j$ r  _$ X. k% ]& p# N
  9.     }
    1 T+ P  Z: }1 e9 i& t7 |
  10. 8 _+ F4 O4 _# v
  11.     // calculate the position offset in framebuffer.: t8 d- f0 r/ d+ V- ]- C. r7 |6 j
  12.     pos = x + y*LCD_WIDTH;
    7 r" }& Q' K% F6 J6 B
  13.     ptr = (uint16_t*)LCD_FRAME_BUFFER;
    + N; d% P7 S, a3 M' {2 f

  14. ' l  n! s+ x1 w( L
  15.     // read the framebuffer.& q& k2 |. k9 F/ b$ L  o) t
  16.     data = *(ptr+pos);
    : x" h1 n3 d& G- a/ {( ^: [
  17. : o# U/ C4 o9 E( d
  18.     return data;% D' N! ], J& O
  19. }</font>
复制代码
3. 画线、画矩形、画圆1 q! y( s; |# n) H. V) @3 ~
这三个功能都是基于打点函数,使用 Bresenham 算法,代码篇幅过多,如有兴趣可直接查看本篇源码。* D! Z7 k* j5 P

  I0 F1 h) x+ G( G, K$ c

, P) h( h) G: K) g: A. ?% P/ h; H) i4. 测试
# r; _1 n0 d" r/ b7 D: b$ R! Q; g在main函数中添加测试代码:
' t4 g6 U( n, g9 t3 K5 i) R$ u+ s! H
  1. <font face="微软雅黑" size="3">lcd_draw_line(0, 0, 1024, 600, GREEN);2 X. o+ u; q' I, q
  2. lcd_draw_line(0, 300, 1024, 300, RED);
    - r& v0 h, M" Y
  3. lcd_draw_line(512, 0, 512, 600, BLUE);0 d+ V+ s9 c+ x& v, [
  4. lcd_draw_line(1024, 0, 0, 600, YELLOW);
      W) N# X0 E( T# n

  5. 8 G  P4 ]! h) J  p& V
  6. lcd_draw_rect(256, 150, 1024-256, 600-150, PINK);</font>
复制代码
编译,下载,结果如下图:) u- S: H8 z, ?9 i# p/ t5 s
5.png $ s3 O& B& p4 _) E4 K, X
) M$ J2 L7 n9 j: o% U

6 Y) N, K8 s5 [  ]  B4 q  {/ D* L
3.png
2.png
1.png
4.png
5.png
收藏 评论1 发布时间:2021-7-26 10:38

举报

1个回答
lugl 回答时间:2024-8-5 12:38:48

大佬,源码可以分享一下吗?

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版