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

STM32F4 SPI DMA

[复制链接]
STMCU小助手 发布时间:2021-10-12 13:31
STM32F4 SPI DMA
自己整理(存储器到外设模式)
5 V) h" f& J4 eSPI结构体! e# c! V- C$ y5 K3 z

6 D* Z+ \! }" _/ a& v* J; ]
! [* L+ T7 ~- X7 c- ~
  1. typedef struct! _0 G8 P3 [- e& T' X
  2. {
    ! l' S& B  W$ C. q
  3. uint16_t SPI_Direction; /*设置SPI 的单双向模式 */
    , V, B4 k2 A. Y+ `+ l
  4. uint16_t SPI_Mode; /*设置SPI 的主/从机端模式 */" |% V. Z& _! l7 j
  5. uint16_t SPI_DataSize; /*设置SPI 的数据帧长度,可选8/16 位 */: j2 M  D+ U* z
  6. uint16_t SPI_CPOL; /*设置时钟极性CPOL,可选高/低电平*/$ l1 ^  C9 l% J  j% i
  7. uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */
    % g6 L; K/ ^0 ?
  8. uint16_t SPI_NSS; /*设置NSS 引脚由SPI 硬件控制还是软件控制*/$ b6 A9 G: C% _# ~
  9. uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子,fpclk/分频数=fSCK */
    6 |$ f/ G( j. H- H0 m
  10. uint16_t SPI_FirstBit; /*设置MSB/LSB 先行 */
    & E/ P  _; t, P; S  [
  11. uint16_t SPI_CRCPolynomial; /*设置CRC 校验的表达式 */
    , p4 b3 s' `  u2 {( A9 o1 O" c/ t' j
  12. }SPI_InitTypeDef;; z7 M8 F- n' U+ o
复制代码
SPI引脚编号
. B9 {9 w( q! a' J
14.png
15.png
16.png
SPI配置* I+ ^$ M* a' Z9 O* b9 @9 m
  1. void SPI_Config(void)
      [& m: ^  v4 y9 A* }
  2. {( |$ V! P3 C- D! Z6 Z# l+ d
  3. GPIO_InitTypeDef GPIO_InitStructure;
    ( z3 T7 j) T/ }* V. w1 \( \
  4. SPI_InitTypeDef SPI_InitStructure;; J: A/ H: Y% y6 d
  5. 8 Q" a7 H# L0 U" z7 q6 ^' ?
  6. //1.初始化GPIO 7 x+ W% `4 j1 Q' F) c
  7. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB,ENABLE);
    ( b: V, V$ m, m1 p
  8. /* 连接 引脚源*/
    % b; Q) m- \, u5 k7 Q% c8 ^
  9. GPIO_PinAFConfig(GPIOA,GPIO_PinSource15,GPIO_AF_SPI1);5 Z# Z7 H3 p! ?
  10. GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
    3 `* g: ?& |1 n. d
  11. GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);3 k& ~8 e& k4 w! M, \2 z$ w: C

  12. ; t4 K2 |- L& h6 _$ N2 {3 ]
  13. /* 使能 SPI 时钟 */3 j$ C! W6 Q3 m( _7 @& |$ h& X* Y
  14. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); & W+ `% r+ b& `/ y' m) J1 _  Q2 _

  15. 0 b; d/ S; ~3 ~) p8 j
  16. /* GPIO初始化 */
    9 r1 Z% t4 ~3 f  O. W% G1 Y0 \7 R
  17. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;        & Q) F6 K) ^/ t$ N) P% G/ K
  18. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + `5 e" ]$ \" D1 d: a1 D; @: h
  19. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;* s0 H. Z0 ]' U" {6 W2 ?
  20. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    ; x. U9 C/ Q) R
  21. ! ^: B! ^* x" w3 q* k
  22. /* 配置SCK引脚为复用功能 */
    4 Y8 F7 V2 z; {4 [0 K. ]# N
  23. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 ;
    % G) M+ P- @' G' N% L) Y
  24. GPIO_Init(GPIOB, &GPIO_InitStructure);
    " H, t" \" N; k7 u
  25. ' M& a# N; K' s! X, h  O$ M
  26. /* 配置MISO引脚为复用功能 */" y6 z7 n* d3 n0 I/ l  I' [
  27. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;2 s6 }) Q  w8 \! t$ Q* P) ~
  28. GPIO_Init(GPIOB, &GPIO_InitStructure);( G/ z9 [$ P5 g5 t3 J% }% m
  29. ! O8 W, Y) _, i, i" Y9 V9 U
  30. /* 配置MOSI引脚为复用功能 */
    , g  G5 D, I. T1 j( T
  31. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;7 y: O, n: p  H2 i
  32. GPIO_Init(GPIOA, &GPIO_InitStructure);
    $ \2 l9 y  x; S' M' j
  33. / E( Z7 `) q4 v2 d( }5 N3 l
  34. /*CS引脚 */
    - D* ^/ f" j9 o; c1 l, O& C
  35. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;8 S7 U, e* ]( N
  36. 5 E7 C4 a5 V+ @) v5 S
  37. /* 配置CS(NSS,自动控制SPI的片选信号)引脚为复用功能 */, c# V. N% l" L2 G1 E/ o% _
  38. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 ; 3 y: d2 o$ K+ C% V; L; b
  39. GPIO_Init(GPIOA, &GPIO_InitStructure);$ Y3 l& c8 x! R6 v* x: c+ V

  40. ) W+ @& V) v' X0 O* F, Y, I
  41. /* 停止信号: CS 引脚高电平 */( J0 f5 K6 _2 p- d5 w2 s
  42. GPIO_SetBits(GPIOA, GPIO_Pin_15);6 q+ x* d1 @% p0 D. k4 M+ K0 J
  43. 8 H$ V' r4 ^# T# A0 f; W
  44. //2.配置SPI工作模式
    , V. ~8 X4 ]4 C" `
  45. // 分频
    ; w6 r* K) l9 c
  46. SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
    : c! a7 Y0 y( [3 J8 z
  47. // 数据捕获于第二个时钟沿
    2 @2 n. n" Y9 y8 P' E
  48. SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
    ' a  M# }& n' J4 m" b
  49. // 时钟空闲idle时是低电平5 x  J$ ?& i" r
  50. SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    % D3 X. _) |8 N5 C
  51. // 不需要使用CRC校验- t' H# G2 g+ U$ ~& }4 c4 K9 S  ~
  52. SPI_InitStructure.SPI_CRCPolynomial = 0;
    / q) w" I: K, a" l+ y
  53. // 数据帧长度为8位" X; I! c- \# h2 I( A
  54. SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    5 `& a! L( g" f( V4 @% U
  55. // 双线全双工模式
    " j) h; J7 O& ~5 J
  56. SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;( a- o" C* x2 g
  57. //数据传输从 MSB 位开始! p, C+ z" [  q) l8 H( l( Y
  58. SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    4 K( C  E8 b  o+ M0 q2 m2 S
  59. //NSS 信号由软件管理& d6 a  z5 ?9 B2 N/ R7 H/ [
  60. SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;# Q& m" N; o( N0 j5 _3 I
  61. //设置为主设备8 \7 _. c: y) ]6 I) N8 q
  62. SPI_InitStructure.SPI_Mode = SPI_Mode_Master;        / e6 ^+ @) S% s5 j2 m
  63. SPI_Init(SPI1,&SPI_InitStructure);
    ' @5 C# L4 l9 A

  64. . S. }& b2 B) S* c# s4 |2 \
  65. /* 使能 FLASH_SPI */       
    ; H( t" ~. T3 i$ Z1 p' h
  66. SPI_Cmd(SPI1, ENABLE);
    ' ^- x: r  p4 \0 }
  67. }7 _4 G. p% K5 W' d7 d
复制代码
DMA结构体
6 y: F8 |; x% A% B
  1. typedef struct {
    9 r5 k) L! X3 C  g' ]
  2.     uint32_t DMA_Channel;                                 //通道选择
    - `% T" Q, S# `8 r- o
  3.     uint32_t DMA_PeripheralBaseAddr;         //外设地址
    9 P+ h! ~( I1 Z9 N
  4.     uint32_t DMA_Memory0BaseAddr;                 //存储器0 地址
    8 p$ u% K4 w; Z, s# d
  5.     uint32_t DMA_DIR;                                         //传输方向
    & {" I: P1 F5 |3 c  f
  6.     uint32_t DMA_BufferSize;                         //数据数目4 C; _, I. B+ w/ a2 @! ~7 X
  7.     uint32_t DMA_PeripheralInc;                 //外设递增
    ( ]6 F# P, q3 R' R+ j
  8.     uint32_t DMA_MemoryInc;                         //存储器递增
    , v5 Y! c& p6 l
  9.     uint32_t DMA_PeripheralDataSize;         //外设数据宽度
    % f9 \, ?& F9 f, j
  10.     uint32_t DMA_MemoryDataSize;                 //存储器数据宽度5 x7 n+ \% h" g
  11.     uint32_t DMA_Mode;                                         //模式选择2 }: |! a  `+ S! J& |6 e* A& p: d
  12.     uint32_t DMA_Priority;                                 //优先级1 N, F7 z( V( W, ~5 H
  13.     uint32_t DMA_FIFOMode;                                 //FIFO 模式% B6 n. d; J& S' s; f7 R/ M5 w
  14.     uint32_t DMA_FIFOThreshold;                 //FIFO 阈值2 I  U: }) U8 Y' |, Y: V. i4 E
  15.     uint32_t DMA_MemoryBurst;                         //存储器突发传输7 ~" J3 ~4 r; O: ?# D8 |8 y
  16.     uint32_t DMA_PeripheralBurst;                 //外设突发传输
    ' g9 @) ?6 e8 p) l
  17. } DMA_InitTypeDef;- F9 i# {* j1 ^$ @0 [: F
复制代码
! h9 h/ Z* H7 y% }# }
DMA请求映射
4 r4 y; [3 ?( Y2 m
17.png
18.png
DMA传输模式
' o/ g( |" w# Y& m7 g! S1 z
19.png
SPI 发送DMA配置
) \4 u; `2 t. e" u3 \
  1. #define SENDBUFF_SIZE (1024*20)            // 一次发送的数据        0 f0 g$ M1 G' R9 D5 ?- p/ h" ?
  2. uint8_t TX_Buff[SENDBUFF_SIZE];                // 发送缓存3 w! {2 C' g' x' T4 z/ D
  3. void SPI2_TX_DMA_Config(void)6 m$ a" Z" U6 n, d1 }
  4. {
    * t  N' z- v" S3 N
  5.     // 中断结构体
    : A1 t) H& m1 B& Y) x. }. Z% m% ?
  6.     NVIC_InitTypeDef NVIC_InitStructure;                / M6 V+ H; g* I0 j% A. k
  7.     // DMA结构体( U( U& O" r; B: ~1 i4 o8 w
  8.     DMA_InitTypeDef DMA_InitStructure;                 
    4 _/ V, ~6 x1 c' L  v! W9 P7 H+ V1 B
  9.     /* 使能DMA时钟 */                  0 L2 y. J6 U  ^, S+ Q2 z2 c5 S5 G
  10.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);       
    # f  f3 g6 v9 W! A, [( R/ j
  11.     /* 复位初始化DMA数据流 */  
    ' R0 j. C* Z' n/ r6 K1 t& P
  12.     DMA_DeInit(DMA2_Stream5);                                                               
    * W, T; n# s* U6 u1 v
  13.     /* 确保DMA数据流复位完成 */  ) Y6 U* t; ]& x4 J
  14.     while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE);        - H7 z, H' ]+ S' h) O) @) P0 y
  15. 4 @* x4 W& I' h3 w3 G" G
  16.     /* 配置 DMA Stream */7 _* `$ A5 l3 h. }; P
  17.     /* 通道3,数据流5 */          
    : T" V0 X% G& N- i& M% a
  18.     DMA_InitStructure.DMA_Channel = DMA_Channel_3;2 M) ?3 ]" S% X3 q
  19.     /* 外设地址 */  3 I& t9 L9 ~6 H
  20.     DMA_InitStructure.DMA_PeripheralBaseAddr = (SPI1_BASE+0x0C);        3 b. q0 a- _' I. `
  21.     /* 内存地址(要传输的变量的指针) ,DMA存储器0地址*/         
    ' ^4 L& G. G% x+ m: e" H
  22.     DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)TX_Buff;        ' D7 Q, E6 y+ }% [" A! r2 L, P$ ]
  23.     /* 方向:存储器到外设 */                        0 v) y- t; s5 s) D5 w$ @
  24.     DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;2 a8 v! j$ ~! A1 X
  25.     /* 数据传输量 ,可设置为0, 实际发送时会重新设置*/            * d- e% P; D6 A5 Z% R
  26.     DMA_InitStructure.DMA_BufferSize = (uint32_t)SENDBUFF_SIZE;                3 X# Y" H  F% q7 T1 Y+ a
  27.     /* 外设非增量模式 */                9 u) `+ K, x' Q* W
  28.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;0 c3 T" z8 x6 D' w" g
  29.     /* 存储器增量模式 */          1 M% w3 |% r9 \. j4 k1 Y, S
  30.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    " M4 H: [) E% I5 E& C
  31.     /* 外设数据长度:8位 */         7 _" x4 m, O  Y& M$ S! q: z- }- _
  32.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    0 I4 T, _9 P0 `. h- w- X3 Q& Q" A
  33.     /* 内存数据长度:8位 */
    ( s% j: M9 U9 ]0 e) ^0 R
  34.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;; a1 M  M) y, k4 ?0 r) ~6 L/ s
  35.     /* DMA模式:正常模式 */                 
    0 ]( }5 V% @. q0 r
  36.     DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    . ~& e& w7 B: n
  37.     /* 优先级:高 */                        
    4 x/ M; X* q- D0 H
  38.     DMA_InitStructure.DMA_Priority = DMA_Priority_High;( {  s' A& }0 i
  39.     /* 禁用FIFO */
    6 a# y1 O) b+ R0 G1 E
  40.     DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;                        % q- X- r* c: U7 s9 Y' a3 N6 M$ ]8 d
  41.     DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;   
    ; ]( {8 Y: m1 }+ ^8 V- `  R8 h
  42.     /* 外设突发单次传输 */  
    # ^6 \7 \: @: ?1 ]) d, X
  43.     DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;                   
    7 ^0 z% c* G- U' t
  44.     /* 存储器突发单次传输 */  / q1 i3 G7 N2 c: T" p
  45.     DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; + _8 O' x, v- }+ p

  46. 3 g7 O: H' b  Z8 j6 \1 K1 X
  47.     /* 初始化DMA Stream */               
    - D) g9 R5 @8 Y/ N6 C& ]
  48.     DMA_Init(DMA2_Stream5, &DMA_InitStructure);' j  \% X' t$ V% ]5 I
  49.     /* 开启传输完成中断  */               
    9 U( o* d- u4 L; I3 j0 B
  50.     DMA_ITConfig(DMA2_Stream5,DMA_IT_TC,ENABLE);& u+ A+ I! Y/ ?% G) j1 h) ], F
  51. # _' [/ z0 y7 J3 o
  52.     // 中断初始化 3 p4 `. t8 V0 i$ j; C2 V% h5 g' d! I
  53.     /* DMA发送中断源 */  
    : C2 \  q% i4 N0 v6 U
  54.     NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream5_IRQn;       
    ( @( t% B8 x: ?6 A
  55.     /* 抢断优先级 */  ' M( b& d  Z" V3 I) Q6 _$ v! I
  56.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    4 }+ b8 z8 L/ }% J( a
  57.     /* 响应优先级 */  . l' {% |2 h; z6 N2 m4 J! m# P
  58.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;                                5 k( k* \' r& v. r
  59.     /* 使能外部中断通道 */
    . g8 D: c$ N  [$ [9 ]
  60.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                 $ ^3 `: c3 ?, J: H0 a, d7 k. {
  61.     /* 配置NVIC */               
    8 a& r9 [  C; C$ G
  62.     NVIC_Init(&NVIC_InitStructure);
    ) O! q* L& \% Q0 ?% u' ^
  63. }
    ) n8 A1 y0 c$ a/ ~
复制代码
DMA发送中断服务函数4 Y0 p! a1 |0 g4 L
  1. void DMA2_Stream5_IRQHandler(void)4 k5 A+ c1 v# p3 o  C* a
  2. {7 x$ G" o8 }/ o) t  V
  3.     // DMA 发送完成
    ( \4 A) \  }7 f) U# @
  4.     if(DMA_GetITStatus(DMA2_Stream5, DMA_IT_TCIF5))          t' I' m5 F4 s: r" w* V
  5.     {
    , W. v% K! I9 r
  6.         // 清除DMA发送完成标志
    3 \) t* J0 `  J
  7.         DMA_ClearITPendingBit(DMA2_Stream5, DMA_IT_TCIF5);        # U0 q& U& P% ^& K7 a. T0 C8 N+ W
  8.         // 片选拉高,数据发送完毕       
    ; b6 A1 N+ \$ u5 S0 E
  9.         GPIO_SetBits(GPIOA, GPIO_Pin_15);        : r1 Q1 p1 k# y" u$ x% F- U
  10.     }( r- Y! {. [' O" l
  11. }$ e, P( Z0 N: v2 J
复制代码
SPI 接收DMA 配置
  j: t& |1 Z. d! ~
  1. #define RECEIVE_SIZE                  800          // 接收大小
    . I9 q9 n: z# K( }$ L
  2. uint8_t RX_Buff[RECEIVE_SIZE];                // 接收到缓存* D, y! E! u: w4 z" O
  3. void SPI2_RX_DMA_Config(void)
    4 q& u  o' S: d) k) p/ H/ X* Q# H
  4. {
    % H+ Q9 K4 O: |* y4 U
  5.     // 中断结构体0 @3 o4 \. g, R8 J
  6.     NVIC_InitTypeDef NVIC_InitStructure;       
    / |5 [: L- N2 o  I& b" Z5 C
  7.     // DMA结构体  # f0 h& t+ ^9 E
  8.     DMA_InitTypeDef DMA_InitStructure;               
    9 {7 N. T/ X- `- g- U
  9.     /* 使能DMA时钟*/  
    " t6 J4 f1 i- U& {) J
  10.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);                                        /* 复位初始化DMA数据流 */ 2 U( [  z- j+ D6 J6 V
  11.     DMA_DeInit(DMA2_Stream2);                                                                                                /* 确保DMA数据流复位完成 */- O7 Y3 o+ s) v. R% i6 S- i
  12.     while(DMA_GetCmdStatus(DMA2_Stream2)!=DISABLE);! [( K. ?% U, D) t, G) i

  13. 3 |$ Y: d  j, z, F/ M
  14.     /* 配置 DMA Stream */
    - W7 O) d" U, F8 S
  15.     /* 通道3,数据流2*/          
    # h# b' r: }. c* m+ }
  16.     DMA_InitStructure.DMA_Channel = DMA_Channel_3;                         
    7 b3 ?! H' u% P6 q$ }
  17.     /* 设置DMA源:串口数据寄存器地址*/  1 G$ @; l; `6 Q/ A  {! I, _
  18.     DMA_InitStructure.DMA_PeripheralBaseAddr = (SPI1_BASE+0x0C)                ;
    + a7 u1 ~! L. X( g2 G
  19.     /* 内存地址(要传输的变量的指针)*/  
    2 v! m: r' [3 k" }
  20.     DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)RX_Buff;                       
    / i) M- o; i# Q+ u  u+ Y
  21.     /* 方向:存储器到外设模式 */                        ' p% k9 x7 r) n3 a
  22.     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;& z! a, X) x9 a) B& E; s! C. n
  23.     /* 数据传输量 ,需要最大可能接受的数据量[不能为0],实际发送时会重新设置*/          
    : h0 M! P+ ~9 [. H
  24.     DMA_InitStructure.DMA_BufferSize = (uint32_t)RECEIVE_SIZE;+ N; ?" A5 Z. Q( P: u' Q; J
  25.     /* 外设非增量模式 */          : |( `4 A  W" j
  26.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;           4 h1 B! ]' c! x" L  L; _3 S
  27.     /* 存储器增量模式 */   
    - W: T% }, U# J4 B- e
  28.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    " z2 ^, W4 _! l! W, \
  29.     /* 外设数据长度:8位 */          + u) ]9 c' ^: R. y1 H% T7 ?0 }
  30.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;' r  Y5 H& M2 o' }0 h/ t7 S
  31.     /* 内存数据长度:8位 */
    2 ?- S6 S* k  r! ], f- b, o6 J$ V
  32.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;                4 w7 ]9 F9 P  V- e% P6 v
  33.     /* DMA模式:正常模式 */  
      P6 l4 F; t+ [: b+ Z
  34.     DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;9 A" C* X' e, ~5 B9 F
  35.     /* 优先级:高 */           : b' u+ l" P( }* Q/ y
  36.     DMA_InitStructure.DMA_Priority = DMA_Priority_High;7 F) S& T% ~  l$ M
  37.     /*禁用FIFO*/    : Q5 u1 t3 S1 t8 N$ L
  38.     DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;, y) K, B' ]9 e" b3 N# Q& f
  39.     DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
    . D1 m. }/ ^  V$ [1 k% D6 e
  40.     /* 外设突发单次传输 */  0 G( g* F4 V6 E3 U( q* P& D- F8 Z, h
  41.     DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    6 t: f5 l- F; ^9 ]. e2 O7 t
  42.     /* 存储器突发单次传输 */  3 R2 O% M, X: U, ~
  43.     DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    / R  h; w+ ^" \" U. Y7 o0 _' ]
  44.     /* 初始化DMA Stream */                / Y8 n1 u! T/ z; R! |$ x
  45.     DMA_Init(DMA2_Stream2, &DMA_InitStructure);                                                          
    ' k* ?' g4 y4 T) E
  46.     /* 开启传输完成中断  */5 S* T( C; P, Q+ g6 k
  47.     DMA_ITConfig(DMA2_Stream2,DMA_IT_TC,ENABLE);                                          4 }  A' _0 @) Y

  48. ) y: ]9 Z: i6 ^9 n: \: L8 O
  49.     // 中断初始化
    # z- W# W7 Y! k* o0 u; E: b0 g" E
  50.     /* 配置 DMA接收为中断源 */
    - D. ^. m8 A7 a6 _
  51.     NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn;          - ^  m) z& D4 V
  52.     /* 抢断优先级 */  
    & [* L7 C% j' R6 \, G4 X
  53.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;               
    + r- e% l/ R! ?' i' h
  54.     /* 响应优先级 */  7 X$ ?2 U' s4 c4 N
  55.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;                               
    9 ?$ e1 [3 }* ^4 m* ?# ]9 q7 h
  56.     /* 使能外部中断通道 */  
    % C! a' @: {! y5 v! a
  57.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    - ~/ |. b1 l& B
  58.     /* 配置NVIC */       
    # c! v' d' s" m
  59.     NVIC_Init(&NVIC_InitStructure);( Z  j7 a; e" C$ b$ k5 d4 ~
  60. }# X0 G3 x# ^  e7 M
复制代码
DMA接收中断服务函数
+ E) t. J" u, }2 L
  1. void DMA2_Stream2_IRQHandler(void)
    % z+ c, X% }9 @0 t  q& B2 |
  2. {                               
    ' k* q. p4 x6 ^
  3.     // DMA接收完成
    $ u& @) F  S2 ]2 x1 W
  4.     if(DMA_GetITStatus(DMA2_Stream2, DMA_IT_TCIF2))       
    $ g1 f' C0 J' q( i0 u1 s4 d
  5.     {                # [9 e6 p% f7 n5 Y% ^7 _/ c
  6.         // 数据接收完成 拉高片选, Y* ~9 N" j: t
  7.         GPIO_SetBits(GPIOA, GPIO_Pin_15);       
    6 w3 Z8 S' v$ j8 Y
  8.         // 清除DMA接收完成标志位                7 m' [5 r" w, {& W
  9.         DMA_ClearITPendingBit(DMA2_Stream2, DMA_IT_TCIF2);       
    : e, R% P& B; u
  10.     }
    ! m& t- n* X+ ?
  11. }' b; m/ i. S- m( @4 Q  R- i
复制代码
DMA请求使能: o8 Y  r0 Y0 c# X
  1. //SPI2 TX DMA请求使能
    ' ?" }8 ]/ [  Y& u
  2. SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);       
    ' z" Y& B8 p' H7 t6 v
  3. //SPI2 RX DMA请求使能
    - V' _& R# {9 J
  4. SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);       
复制代码
SPI_DMA 读写一个buf5 e% P' N/ g$ B
  1. #define BufSize                800
    5 u5 u# F0 h5 O2 [
  2. void SPI_DMA_WRITE_READ_BUF(void) ; _) X. g; B7 u' E8 `. C- E& A
  3. {   
    9 t+ W- k3 W" @0 Q$ Z  t! d
  4.     // 关闭发送 DMA        % K# U2 m6 _% `3 [6 n% N0 w1 e
  5.     DMA_Cmd(DMA2_Stream5, DISABLE);        ( a0 _- }; v$ |$ B4 u' v8 R) z
  6.     // 关闭接收 DMA       
    * b2 |; Z6 S  d) v0 G; F& |
  7.     DMA_Cmd(DMA2_Stream2, DISABLE);
    ) e0 t! [! h6 f4 Y4 ]8 v
  8.     // 设置发送的数据量
    2 N- Q9 W5 f9 f9 f, m3 I
  9.     DMA_SetCurrDataCounter(DMA2_Stream5, BufSize);       
    8 v8 o0 z/ K- e- D  U
  10.     // 设置接收的数据量5 Y6 X0 D. L" j# L0 k
  11.     DMA_SetCurrDataCounter(DMA2_Stream2, BufSize);  Y* T/ a# N5 k9 ]3 e/ Y
  12.         // 清空数据
    6 i3 X# M1 E. y
  13.     SPI1->DR;                                  ) t7 L3 B* n* [7 ?) s% {6 _& k8 F
  14.     // 擦除DMA标志位" A+ ]) b. }- U7 z# ]! ~
  15.     DMA_ClearFlag(DMA2_Stream5, DMA_IT_TCIF5);       
    5 }2 H9 @# A$ I! V1 A% ?
  16.     DMA_ClearFlag(DMA2_Stream2, DMA_IT_TCIF5);* u3 U5 ~: |3 z' V+ u9 z

  17. . q) ^( m4 D- c3 o# c5 k, i
  18.     // 片选拉低,接收数据
    1 u1 D$ B7 M/ N1 O  F) h. ^+ I7 x
  19.     GPIO_ResetBits(GPIOA, GPIO_Pin_15);       
    ; Y# d% T" [. A2 p3 v3 @
  20.     // 开启接收 DMA& e+ [, r! I$ ~- q
  21.     DMA_Cmd(DMA2_Stream5, ENABLE);       
    0 {4 g8 m. s' L+ p4 b7 O
  22.     DMA_Cmd(DMA2_Stream2, ENABLE);) y5 u, |! ^" a( _% H  E
  23. }5 D8 a0 B3 ^- J+ e. h# q$ n
复制代码
SPI_DMA 发送一个buf
- m; ^! ~" J. Y$ f4 ~8 r; Y
  1. void DMA_Write_buf(uint32_t SizeLen)$ K1 t' g8 v2 H# k4 M
  2. {   
    0 y6 w: V" y4 @' Y) I/ \) I2 Z
  3.     // 关闭发送 DMA               
      t& @+ q! S; V9 Y2 P# _- ?
  4.     DMA_Cmd(DMA2_Stream5, DISABLE);       
    0 q$ V' u  Q: n4 V8 g
  5.     // 设置发送的数据量    , p% ^  Z4 {. X
  6.     DMA_SetCurrDataCounter(DMA2_Stream5,SizeLen);
      c; E5 Q) b5 ^" M" i+ n
  7.     // 清空数据
    ( K2 e+ K$ J9 r
  8.     SPI1->DR;                9 p+ M$ E* E/ `  I
  9.     // 擦除DMA标志位       
    * G! |0 \3 z5 S+ E
  10.     DMA_ClearFlag(DMA2_Stream5,DMA_IT_TCIF5);; w8 a- H- L. d4 C2 d$ g
  11.     // 片选拉低,接收数据
    : Y* E% }3 q  u
  12.     GPIO_ResetBits(GPIOA, GPIO_Pin_15);       
    & D3 q* n3 J  I. M
  13.     // 开启发送 DMA
    " G6 g$ U2 B4 q+ Z& _4 Q3 A& K# i
  14.     DMA_Cmd(DMA2_Stream5, ENABLE);        5 U' p$ K8 v0 n) t! X; Y
  15. }# X4 `+ u+ I- m  h( ?, W
复制代码
张贴的代码显示如何配置我的GPIO,定时器,SPI, DMA和NVIC模块,以及一些系统如何工作的解释。) ]* G: Y1 T$ x, {5 o$ T
注意,我使用的是STM32F4标准外设库。0 I9 T7 l" O/ J# H( `
  h1 t" E% j" u( Z4 E" o
1 h+ R  x  G3 H# l1 I( y* ~
网上内容  q7 x3 a4 Q. g1 M+ q
通过RCC(复位和时钟控制)模块使时钟信号到达所需模块:
+ Z1 C" N7 d7 f8 {& W5 v
7 p* X; C1 L- x1 u. }  z  E- W

  o5 O9 h4 o2 ~RCC, _8 h; A, s( o
  1. // 为所需模块配置时钟
    $ s# U5 Q" t1 j
  2.     // 启用GPIO外围时钟5 n4 }& c  c6 D- o. p# O
  3.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    6 J5 f" l  Y1 K% C
  4.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);: `" I. ~7 u% Y% Z) k0 ~( ~2 r
  5.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
    6 }  N! X# T6 l* T( N
  6.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
    - j" P) u0 C  @3 X6 P
  7. 9 R: F/ o1 F4 ~9 N) a) d  J) S
  8.     // 启用串行外围接口外围时钟
    7 [; y% i! n0 K* e
  9.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);, G  O5 {; t% `) r8 Z, g

  10. 2 `" k2 @' y! S5 i  `0 B* d$ X& G+ f
  11.     // 启用直接内存访问外围时钟
    % Y/ [1 K3 P9 ~7 N
  12.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
    3 z0 ?. E8 {( U- H& j0 I

  13. ' r8 w, Y$ K# [/ }
  14.    // 启用计时器外围时钟4 A' ^( L; O* F+ M# L6 ]. {/ d( c
  15.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);8 r; R& k2 a& n$ e4 @( y, m. z
  16.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    : X6 _$ m- z6 g, U
复制代码
GPIO  n" ^5 _9 j2 \8 |* J8 F
接下来,配置所需的GPIO引脚:* s$ f+ N* ]/ \( U! D
  1. #define GPIO_SCAN_PORT            GPIOB; W# w7 k/ w- m0 f) i) x
  2.   #define GPIO_SCAN_PIN                GPIO_Pin_7
    9 b, d( s7 B' q7 \8 c5 w
  3.   #define GPIO_XLAT_PORT              GPIOA# Z% j1 k! m9 L$ O* l+ `
  4.   #define GPIO_XLAT_PIN                 GPIO_Pin_57 h& G0 z4 T% Z% e9 X. Y4 a
  5.   #define GPIO_BLANK_PORT           GPIOB
    3 D% T- r  O7 ]( ?
  6.   #define GPIO_BLANK_PIN              GPIO_Pin_6
    . Z; x$ C, \: ]# Y( G& Y$ X

  7. 5 }6 Q/ l( X5 E/ H, }
  8. // 配置GPIO引脚% q8 N  Q/ n" p* F+ [! J, ]8 c) d
  9.     GPIO_InitTypeDef GPIO_InitStructure;
    3 |6 E1 B8 M; W  F7 o6 P* v
  10. 7 ]3 V& w* i/ y. I8 ]! j
  11.     // Timer3&4 输出 (TLC5940 GSCLK and BLANK)
    # m0 t+ u" b9 c: r( N& d
  12.     GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_4 | GPIO_Pin_6);
    9 {: x  H( I1 W- k5 u. m6 F& g6 V# P7 Y# x
  13.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;; e9 b/ n7 {$ Z* {7 v
  14.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;" m8 U3 n1 w4 N
  15.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;6 x2 V) A* n$ J4 f
  16.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    " {) L# m: z/ F' J5 H
  17.     GPIO_Init(GPIOB, &GPIO_InitStructure);$ ~3 u4 \1 i% ?6 H: }
  18.     // 连接计时器到GPIO引脚
    - i0 q& p$ G( N4 c& \" A+ ]& V
  19.     GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_TIM3);   // 将TIM3 OC1输出连接到PortB Pin4 (GSCLK)0 H' ^; X# R0 x+ \8 t5 N8 m
  20.     GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4);   // 将TIM4 OC1输出连接到PortB Pin6(BLANK)3 V' I& o* K0 `+ K' _; u

  21. ; Z- l# M9 T3 K
  22.     // TLC5940 XLAT 引脚* q7 o- e/ a' X: w% a; t( G0 D
  23.     GPIO_InitStructure.GPIO_Pin = GPIO_XLAT_PIN;
    0 U/ y! G( K) a1 l" @$ ^
  24.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;4 l1 W( |1 x- i
  25.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    * h2 f4 t& L4 _% [( H
  26.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    2 g& R3 o  W( f
  27.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;5 n4 c  w: k. M- l$ y0 O& J
  28.     GPIO_Init(GPIO_XLAT_PORT, &GPIO_InitStructure);
    6 a% f( O7 E  T$ C7 P
  29. 9 n4 m  _  h# j9 F8 Z: L
  30.     // 显示扫描 引脚( Q* G- J: M; ]; H
  31.     GPIO_InitStructure.GPIO_Pin = GPIO_SCAN_PIN;
    7 h: |0 r4 O2 j  z( l
  32.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    $ P* X' a! V4 h2 U0 G
  33.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;# H. e5 v& G5 i3 N5 ^5 P
  34.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;  h" ^, ]9 `0 c" z2 m
  35.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    . |2 ?, H/ R4 W# [9 `6 C+ R
  36.     GPIO_Init(GPIO_SCAN_PORT, &GPIO_InitStructure);& t+ |% B* u( b4 y+ d0 o

  37. 6 j, ]5 ]: B- F
  38.     // SPI2 引脚; P* |  Q! q6 ?9 C" z
  39.     //    SCLK =    PB10
    ( g. N% h$ G0 h% t" @3 g$ T! Q
  40.     //    NSS =     PB9. L# b8 Y4 q& d; l
  41.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;6 P9 B( z. o( s' Q6 y
  42.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    6 H0 s9 @4 t" D2 J" x
  43.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    ! b* p& x3 ]/ A7 I' ]' V% c4 v
  44.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    ; r9 }3 Y( ^9 G  }
  45.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    1 i7 u) X" E3 u( C# ^
  46.     GPIO_Init(GPIOB, &GPIO_InitStructure);" |, u; h2 B) s7 _
  47.     GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_SPI2);, Q3 r! q1 n/ i( }4 T" m
  48.     GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_SPI2);
    ( T+ x! h* y9 @! q- \! |( C  k6 {

  49. 1 o, v8 l( Q( `4 M6 w, f
  50.     //    MISO =    PC2
    ' m9 S3 N3 ?) e% x
  51.     //    MOSI =    PC3
    + _: ^# ~& [" ^! p0 \
  52.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;+ H  v+ N& N6 v2 S* s! J
  53.     GPIO_Init(GPIOC, &GPIO_InitStructure);5 m! z, _6 @7 |* K- t% W
  54.     GPIO_PinAFConfig(GPIOC, GPIO_PinSource2, GPIO_AF_SPI2);
    - r1 W! Q+ f" P: c+ F
  55.     GPIO_PinAFConfig(GPIOC, GPIO_PinSource3, GPIO_AF_SPI2);
    & c+ B4 Y! E5 o6 p; Y
  56. ————————————————- k0 F4 n  Z' R+ R. a9 b
复制代码
这里的要点是,我将把TIM3的OC1输出直接连接到GPIO引脚(用于GSCLK),把TIM4的OC1输出连接到BLANK信号。, Y, n0 B( D- Q4 s; |  t) O+ P

/ M; S" ?2 `$ n# U& E

! p% ]# u0 i/ q7 o  S0 USPI6 j) V! o# y8 L, S( J! L% P
现在SPI模块可以初始化:
3 }7 c" {$ V& a% u- W) {0 n0 V
  1. // 初始化SPI模块9 |$ W" f- e" @: ?9 u# Y
  2.     SPI_InitTypeDef SPI_InitStructure;/ v( h1 u- R8 q% A
  3.     SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;       // SPI总线设置使用两条线,一条用于Rx,另一条用于Tx
    / o" e8 j1 [/ l1 {% w- j
  4.     SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                                // STM32是主服务,tlc5940作为从服务& X, \0 R  M( r# ^
  5.     SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                             // 使用8位数据传输% G  _# b6 W( n! }* e% O6 a
  6.     SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                                    // TLC5940时钟空闲时低
    4 M! l1 y2 W6 @" H8 _5 I- X
  7.     SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                                // TLC5940使用第一个时钟过渡作为“捕获边缘”
    ( b' S  o+ x  y
  8.     SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                                        // 软件slave-select操作) l1 M0 w2 i6 U- g
  9.     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; // 设置预定标器' B6 n, O9 G- U$ z$ ^# Y+ s' |
  10.     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                             // TLC5940数据先从MSB传输
    0 d8 M, ~4 }4 p7 G
  11.     SPI_InitStructure.SPI_CRCPolynomial = 0;                                          // 没有使用CRC
    3 F7 F! N( f9 q  F; S$ ^4 I
  12. ( M5 b3 a; f; z( o
  13.     SPI_Init(SPI2, &SPI_InitStructure);                                                    // 初始化SPI2外围设备
    ( F- H. `. a/ w4 [  f( d  a
  14.     SPI_SSOutputCmd(SPI2, ENABLE);                                                 // 将SS Pin设置为输出(主模式)
    9 E8 z" \5 x9 `9 @7 j- J
  15.     SPI_Cmd(SPI2, ENABLE);
    2 f* k$ ]" Q6 s+ I
复制代码
我选择的SPI时钟分频器是相当随意的,但这里的关键点是,我已经配置了时钟相位和极性,根据TLC5940数据表,所有传输都将是8位(稍后详细介绍)。
, I  F! F, @2 l
/ O1 O& z9 ?  o+ @6 T

' v7 P0 o5 }" e7 X2 p# a' pDMA
$ {& _# R/ S' ]. d6 D/ P用于SPI传输的DMA模块如下:
0 t# y& f, s4 C
  1. // 初始化用于SPI2_TX DMA访问的DMA1流4通道0
    3 y* M  h# h  c) ~( g
  2. ) [7 V% y, v& f+ f
  3.     #define DISP_SCAN_DATA_CNT     (24 * 3 * 2)    // 初始化用于SPI2_TX DMA访问的DMA1流4通道0+ M, B: Y( t, G* F3 m; y
  4.     volatile uint8_t dispData0[DISP_SCAN_DATA_CNT];
    7 g# m& q4 _# H
  5.     volatile uint8_t dispData1[DISP_SCAN_DATA_CNT];
    * o- q" d  z: q0 {( C; }5 n- {

  6. & A( @7 H) k' a. n
  7.     DMA_InitTypeDef DMA_InitStructure;
    / f6 G, e, m+ y  n7 h7 o2 o

  8. # W7 ~2 k8 C, P
  9.     DMA_InitStructure.DMA_Channel = DMA_Channel_0;                   // SPI2 Tx DMA是DMA1/Stream4/Channel0$ X6 I3 I5 S, H* @; t+ |5 G
  10.     DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)&(SPI2->DR);                // 设置SPI2 Tx' k' W! C  l+ _
  11.     DMA_InitStructure.DMA_Memory0BaseAddr  = (uint32_t)&dispData0;                   // 设置内存位置
    / N2 v$ m. z0 u  W. l- L% L
  12.     DMA_InitStructure.DMA_DIR  = DMA_DIR_MemoryToPeripheral;                          // 从内存发送数据到外设的Tx寄存器
    2 f1 u3 A$ s, W3 @$ o
  13.     DMA_InitStructure.DMA_BufferSize  = DISP_SCAN_DATA_CNT;                          // 定义要发送的字节数
      G" u. b' Y9 y
  14.     DMA_InitStructure.DMA_PeripheralInc  = DMA_PeripheralInc_Disable;                  // 不要增加外围“内存”9 X" z. J: X2 z: H
  15.     DMA_InitStructure.DMA_MemoryInc  = DMA_MemoryInc_Enable;                        // 增加内存位置& e" O$ M: E+ `& y' X
  16.     DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_Byte;    // 字节大小内存传输
    & S3 b/ w' L, Y- {( J2 W( {
  17.     DMA_InitStructure.DMA_MemoryDataSize  = DMA_MemoryDataSize_Byte;         // 字节大小内存传输
    + m$ R+ N4 u9 l0 n' Q# |2 F
  18.     DMA_InitStructure.DMA_Mode  = DMA_Mode_Normal;                           // 正常模式(非循环)) q% ~" t: E  |" p7 x. R7 u+ l5 [5 t
  19.     DMA_InitStructure.DMA_Priority  = DMA_Priority_High;                // 优先级高,以避免使FIFO饱和,因为我们是在直接模式/ t, T8 F% m4 Q- s" S
  20.     DMA_InitStructure.DMA_FIFOMode  = DMA_FIFOMode_Disable;                 // 操作在“直接模式”没有FIFO
    3 O# V; f4 e1 ?: ~3 q5 G0 H
  21.     DMA_Init(DMA1_Stream4, &DMA_InitStructure);
    1 \8 ~! W- k* `3 K  E, G2 }

  22. 6 r4 h" `3 E' P5 C/ [' K, r4 B
  23.     // 操作在“直接模式”没有FIFO
    0 q: q4 W4 O; V$ T. G( {
  24.     DMA_ITConfig(DMA1_Stream4, DMA_IT_TC, ENABLE);
    7 N9 r& W  q' W2 P- H  C
复制代码
NVIC" M% v& n* |4 [* k) b  x! B3 a$ k
接下来,我已经为两个中断服务例程触发器配置了NVIC(嵌套矢量中断控制器):0 H8 u! S* i/ ~1 ~# K
  1. // 初始化嵌套矢量中断控制器
    ' }! a& R) b8 a! Z9 H, ^/ s
  2.     NVIC_InitTypeDef NVIC_InitStructure;
    " b# I* {9 D  A1 x* R; M, O+ @
  3. 6 J; C8 w( \: Z' y7 U
  4.     // 启用TIM4(BLANK)中断& z$ }: D: ~$ @3 o' l, b$ r
  5.     NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;% b0 }% G6 r% y* ^) l
  6.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;7 `: _( x3 y' q) Q
  7.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;# f3 X5 I% z8 y" s4 E, Q
  8.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;: l9 F( w" ?; ^1 c; |
  9.     NVIC_Init(&NVIC_InitStructure);3 I0 r* R, E3 @  @2 [4 ~
  10. 8 c. e) S& f7 r0 Y" ]# `) A
  11.     // 启用DMA1 Stream4 (SPI2_TX)中断
    * H. v: M# t) U0 p$ K3 b* u
  12.     NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream4_IRQn;! n- w# o2 W% |: Y5 j
  13.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    . }" @9 T( ?  P0 |1 @
  14.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    ! W! _1 I' w4 A! i1 j6 J: |) s
  15.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    ; @; h6 E% X0 w- l) I5 m/ `5 k
  16.     NVIC_Init(&NVIC_InitStructure);: U, S( M9 g1 `, ]% |( H6 E1 o
复制代码
BLANK中断用于产生空白脉冲,初始化DMA传输,并在每个扫描周期后锁定先前传输的数据。
( y) G! a6 T+ H4 I5 P. [9 a! k  S$ d4 `* M& U
4 k8 `/ [2 `7 {. o7 ~0 W* H0 \
Timers
, G! c4 K. N- z+ p7 r) M最后,定时器模块配置:, Y- o8 e# u0 N5 i5 n: w* P/ K
  1.    #define TLC5940_GSCLK_COUNTS    256                   // GSCLK在BLANK 脉冲之间进行计数
    6 c7 {4 A- x+ Y: I
  2.    #define TLC5940_GSCLK_FREQ        1000000            // GSCLK频率
    2 Z" L0 I2 ^1 W7 n0 P
  3.    #define TLC5940_BLANK_COUNT      50                     // 在切换到下一列之前,允许前一扫描列的正电源轨道关闭的填充
    ! ^: D( h; s' p4 S$ D
  4.    #define TIM_APB1_FREQ                  84000000          // 内部时钟频率(CK_INT)
    - g; e' a$ @7 C" m. E. L0 g% m9 U
  5. % u0 `5 A' q5 e% I9 S
  6. // 启动计时器模块
    + N  s0 u) E/ r1 `
  7.     TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;2 D3 ^  P; w" L! e1 I
  8.     TIM_OCInitTypeDef TIM_OCInitStructure;
    4 S5 w. [3 K: L5 e: i

  9. 7 V: F% z& V( j9 v' t5 T/ N
  10.     // 反初始化计时器模块和初始化结构
    $ L! @8 N1 R- @4 q+ C6 c+ n0 H! `2 l
  11.     TIM_DeInit(TIM3);& ?% V( l$ ~8 V" l/ L3 i6 F
  12.     TIM_DeInit(TIM4);9 F- D) F* H) O% ^
  13.     TIM_TimeBaseStructInit(&TIM_BaseInitStructure);, g7 B) \: @+ Z7 s6 @& P
  14.     TIM_OCStructInit(&TIM_OCInitStructure);5 X$ O1 l9 t  l7 l  @; p

  15. + a1 B4 U- b3 Z
  16.     // 设置TIM3来生成“主时钟”: E) B, ]0 c6 G% T! g2 k) I5 w
  17.     TIM_BaseInitStructure.TIM_Period = 1;. o- i% d& ^* N, l! R& F5 M7 P
  18.     TIM_BaseInitStructure.TIM_Prescaler = (uint16_t) (((TIM_APB1_FREQ / TLC5940_GSCLK_FREQ)/4) - 1);    // 请注意,4的除法因子是由OC1频率vs CK_INT频率引起的5 c; c0 i. u1 ]+ F; [0 _2 i$ m
  19.     TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;7 D+ C: x$ O8 J# `% }
  20.     TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    % L) ]8 _7 s4 |$ d# E8 Z+ t
  21.     TIM_TimeBaseInit(TIM3, &TIM_BaseInitStructure);( Z2 h, Z* J" y2 r/ O8 l* r6 ?  @
  22.     // 配置通道1输出比较作为触发器输出(用于生成‘GSCLK’信号)$ V. v0 x0 e  d0 g+ L4 q
  23.     TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
    4 l4 U2 j# B/ w9 [8 a! R7 D
  24.     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;5 J/ ~# T* G2 P4 t
  25.     TIM_OCInitStructure.TIM_Pulse = 1;
      x, t) S  k9 Y7 X( k: A. r5 }. Q
  26.     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    + t$ i7 Q- T8 L) t8 [3 n
  27.     TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    & ]  {. [$ c" ]2 g
  28.     TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
    ' N% P; Y: |6 o3 \" F" x- A1 m3 z
  29. * Z: k& ]  _0 N6 y) k& V( K
  30.    // 为对称计数器设置TIM4基准,最大计数指定为“GSCLK计数”(实际上是TLC5940的灰度分辨率)( c) I, G9 ?* Y2 m" o3 l5 k0 G
  31.     TIM_BaseInitStructure.TIM_Period = TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT;                   // GSCLK溢出计数(对“阻塞”的空白信号额外加1): i5 B) \7 _( S- B7 A
  32.     TIM_BaseInitStructure.TIM_Prescaler = 0;
    $ g2 f  P, O# b4 G' o& l2 K. X, w% m
  33.     TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    # ?2 K, M6 d1 p( c! ^6 i  o" V
  34.     TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;
    ' [  P& M/ S9 V4 T7 i0 |4 q
  35.     TIM_TimeBaseInit(TIM4, &TIM_BaseInitStructure);  D3 S/ @2 ~; U* l- E; |0 T* b* U
  36.     // 将Channel 1输出Compare配置为触发输出(TIM4用作时钟信号来生成'BLANK')
    % I" P! `$ d9 w3 m& O3 c0 o
  37.     TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;' \4 g' }9 H: ^3 N) ^7 l, J" ?& F. u
  38.     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;! n  ]1 o. c6 o! F$ V0 r
  39.     TIM_OCInitStructure.TIM_Pulse = TLC5940_BLANK_COUNT;, l7 A* Z3 K# o1 P
  40.     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;8 ~; E9 }) u6 [7 _& w8 s3 a, S  J
  41.     TIM_OC1Init(TIM4, &TIM_OCInitStructure);
    / k1 i$ Y; ?  p2 B& n7 r
  42.     TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
    3 B. A; H4 L. E
  43. / J) n0 P, ?. J/ d' r4 ?. e$ Q3 C
  44.     // 将TIM3配置为主计时器
    2 l$ M+ S& S  @# p, u
  45.     TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);                 // TRGO与TIM3的更新绑定
    + P, D: M7 t' K4 U! D1 e+ x( H
  46.     TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable);    // TIM3作为master启用  v5 `9 s: d) A/ K# N. y/ f7 `

  47. + k4 Q& o3 F' ?1 P# H
  48.    // 将TIM4配置为从属# E  S- C% B0 ?" b# M
  49.     TIM_SelectInputTrigger(TIM4, TIM_TS_ITR2);                                     // 设置TIM4(从)触发TIM3(主)
    $ _& {3 d0 ~, K( W! j* K
  50.     TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_External1);                   // 使用主信号输入作为一个“外部时钟”
    : M* n$ L% y' e( _: p

  51. + y# Y! e& ]8 n4 z! q
  52.     // 将TIM4模块配置为在捕获/比较1个事件时中断(向上计数和向下计数都匹配)3 {. q( J  Y8 T7 L+ V
  53.     TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE);
    7 u2 M6 N% |' a$ W- ]

  54. 1 C7 b* D8 H5 S" W! a9 k
  55.     // 启用计时器3和41 C8 Z/ {/ p6 s  K; b
  56.     TIM_Cmd(TIM4, ENABLE);
    % K3 s' K& l& o7 K+ M; D  \; [
  57.     TIM_Cmd(TIM3, ENABLE);
    $ n; _5 m0 V, h# j7 Z, p2 T
  58. ————————————————
    9 W) |0 o* \% C) ?- p- r4 n9 w
复制代码
在这里,定时器3被用作主时钟,在它的输出比较1线上产生GSCLK信号,和驱动定时器4被配置为一个中心对齐的PWM输出在OC1上。# A) D3 _9 ~% l. u3 Y$ `1 _% {
BLANK计数是有效的填充脉冲,允许:" k5 [! g- Q, D: Q( L( z2 c' H
* S7 h$ ?5 ]$ H( z& ^7 ?6 e
  g1 |+ u  K5 L+ e
最小空白脉冲时间
. H) w' Q  D6 H: y( U6 E: MXLAT和DMA传输触发+ c$ x6 d+ e2 \# L
MOSFET输出在之前的扫描列完全放电(我已经通过观察放电时间在我的示波器调谐)
: X+ D( V* y1 ^6 ~3 F设置了GSCLK频率,并且在下降和上升的空白信号边缘之间的GSCLK脉冲数设置为256,因为我使用的是8位的颜色,而不是TLC5940芯片(12位)的全部功能。这意味着在空白脉冲之间将有256个GSCLK周期。
3 E+ a; a/ r" C: e2 c外围设备现在已经完全配置好了,所以最后要做的是查看中断服务例程,并调查结果:+ p9 l: n9 D8 E$ S
+ s* w( L" i. o8 P. b& n5 F( e) D
8 D$ `& ^- l# O. o! E
ISRs
0 p# E. J3 f7 X* y/ j
  1. #define DISP_SCAN_FREQ                 200                                                                                                                                                                           // 扫描信号的频率3 S& X3 S# w  t+ h1 `, N% @
  2. #define DISP_BLANK_CYCLE_LIMIT    ((((TLC5940_GSCLK_FREQ / (TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT)) / DISP_SCAN_FREQ) / 2) - 1)   // 扫描前要计数的BLANK循环数
    % A+ d+ r3 ~5 q

  3. 4 @  `- U. T' k$ M  t1 g
  4.    void TIM4_IRQHandler(void)& W# H4 z) m2 D! W
  5.     {
    ! f5 `3 x  U5 |/ J" _
  6.         // TIM4 IRQ处理程序有几个任务:
    2 T% Q. E& k% Y) v, `% H6 e
  7.         //    - 切换扫描信号  P- O" A6 k7 s. \' {! N  A$ m) q
  8.        //    - 锁定新选择('扫描')列的先前传输的数据
    / c: Z7 g% y5 C+ S: B8 X
  9.         //    - 设置并启动SPI2 DMA流来传输下一列的数据7 F+ v* w, W# I" @" M
  10.         // 所有这些都应该在BLANK信号(TIM4 OC1)高的窗口内执行(而不是完整的SPI传输)。
    1 e* w! U  k; A4 A, a0 j+ d
  11. . K5 H+ E1 D2 Z0 g0 B1 F: p
  12.         // 检查所生成的中断是否为OC1更新- {" o/ p3 Q2 B" Q
  13.         if(TIM_GetFlagStatus(TIM4,TIM_IT_CC1))
    ) s) ]* V7 o- n9 b
  14.         {
    # {* v: [& f4 g# c& [
  15.             // 清除TIM4 CC1中断位
    # Y% Z& ?, ?0 b2 C) R7 a
  16.             TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);/ c" |* a9 ~) q" o& |. J
  17. ( x$ N7 T; b. H2 l, s
  18.             // 仅在向下计数时执行事件(这确保在空白脉冲内触发XLAT脉冲、扫描更新和SPI传输)( ^+ d( y6 _9 S; ?2 l0 n
  19.             if(TIM4->CR1 & TIM_CR1_DIR)( C& S; z. Z" H/ T3 o4 a5 ?
  20.             {7 d4 |6 n8 [" F8 c1 ^+ S
  21.                 // 检查是否需要'SCAN'更新(XLAT脉冲,扫描切换,下一次传输触发)3 \3 N( e) H) T! ?: t$ e6 M
  22.                 if(dispBlankCycleCnt++ >= DISP_BLANK_CYCLE_LIMIT)! k1 W2 @2 h8 P% L/ ^
  23.                 {
    ' ^2 i% N1 L8 E6 j% K
  24.                     GPIO_SetBits(GPIO_XLAT_PORT, GPIO_XLAT_PIN);                  // 设置XLAT引脚
    : m' S# J/ p3 ?% N
  25.                     dispBlankCycleCnt = 0;                                        // 重置计数器
    # S* V9 J# ~6 I+ `8 y% K/ ~
  26. 2 U1 v) `' J8 t1 L8 r# y6 v( u/ l
  27.                     // 确定当前列,并相应地移动
    3 `8 i6 Z8 a0 @" G! f
  28.                     if(dispCurrentCol)% k. b+ X5 D( u6 j2 i7 P: ]+ P
  29.                     {
    % K9 X. q' l8 T0 _! D1 M$ E( ^
  30.                         dispCurrentCol = 0;                              // 更改为“0”列
    # a( c1 r0 R: b
  31.                        GPIO_SetBits(GPIO_SCAN_PORT, GPIO_SCAN_PIN);      // 设置扫描引脚(注意列0是逻辑高位,列1是逻辑低位)
    % H' _4 g9 J2 r2 W- B" Y' C/ b! \6 V
  32.                         DMA1_Stream4->M0AR = (uint32_t)&dispData1;       // Send *next*列的数据(由于当前列现在是'0',因此将发送(针对下一个循环)8 j) g) J% g0 X' v" o
  33.                    }" i8 }5 G0 O; k  G6 a2 k' G! X
  34.                    else
    9 n- e' S2 x4 p
  35.                     {5 X5 ^' ~  a4 F. t( r/ @# Q
  36.                         dispCurrentCol = 1;                                    // 更改为“1”列# S4 w( E# Y% v3 Y$ ~  H! C
  37.                         GPIO_ResetBits(GPIO_SCAN_PORT, GPIO_SCAN_PIN);      // 重置扫描引脚(注意列0是逻辑高位,列1是逻辑低位)- Y: q+ ~: u  Q& r0 m- _6 \
  38.                         DMA1_Stream4->M0AR = (uint32_t)&dispData0;         // Send *next*列的数据(由于当前列现在是'1',因此发送了(对于下一个循环)8 q! [. ]& _, Q
  39.                     }
    3 A. h' v& @3 H* k" o; h

  40. % s5 E0 k; w9 D
  41.                     GPIO_ResetBits(GPIO_XLAT_PORT, GPIO_XLAT_PIN);             // 清除XLAT引脚( b3 b& L$ Z! |( V! d5 v

  42. & U0 |; _0 K  S% K* a  Y( Q
  43.                     //Trigger the next transfer- ?# s- q% P# n6 z0 |
  44.                     SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);        // 启用DMA传输请求
    + ~1 K& c- o& J. v2 C% u
  45.                     DMA_Cmd(DMA1_Stream4, ENABLE);                         // 启用分配给SPI2的DMA流$ d2 I/ @/ T. o* x* ]
  46.                 }4 V5 ]- ~: ~1 V6 v
  47.             }
    0 _+ ~' f8 j+ A7 s4 w$ {, i: A
  48.         }
    2 D. D  E+ ?6 y0 q  p* V
  49.     }- s9 R/ l9 M% u
  50.   s# m2 j% D7 n" y, j2 ]
  51.     void DMA1_Stream4_IRQHandler(void)# L: n! y1 a3 V* }/ z$ t0 F8 w+ x
  52.     {
    1 ~5 f1 p4 [$ c+ R
  53.         // 检查是否设置了传输完成中断标志& [& i+ Z. G4 N* [
  54.         if(DMA_GetITStatus(DMA1_Stream4, DMA_IT_TCIF4) == SET)
    6 X7 V0 W9 O* h1 [! g4 ]* W
  55.         {5 H1 j. K; H: K" p/ C
  56.             // 清除DMA1 Stream4传输完成标志. }0 ]9 L4 B+ P( a& F" V
  57.             DMA_ClearITPendingBit(DMA1_Stream4, DMA_IT_TCIF4);6 T, U" B6 y% V! L8 X8 d
  58.         }8 R8 k& c' v- o/ B. _
  59.     }( x0 E9 t3 x: u. J7 u! n8 A
复制代码
DMA ISR目前没有被使用(我确实打算用它做一些无关的事情),但TIM4 ISR基本上控制了整个显示。
, q* h1 p  c/ o- w6 d% E4 V7 W空白脉冲的上升边缘(有效地)触发中断。在确定正确的ISR触发了事件之后,使用TIM_CR1_DIR位检查计数器是否正在减少计数。这确保了我们只在BLANK脉冲的上升边缘执行以下任务。
0 e" g1 g, J. Y+ H- m* s每当ISR运行时,我们增加一个计数器,如果这个计数器超过扫描显示所需的数量,我们就使用XLAT信号锁存前面的数据,切换扫描信号,并传输下一个数据(在disdata0[]或disdata1[]数组中找到)。
$ W6 x) O& W4 s7 z, q2 ]在dis_blank_cycle_limit中计算扫描前等待的空循环数,其中考虑到:7 D# _; L$ B" I. g4 h! a( \

1 K$ t6 ?& l6 z7 O3 a! p
6 X  r. r; N4 k/ d
TLC5940_GSCLK_FREQ -灰度时钟频率0 v& K- U) t$ d8 m$ y
TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT——上升的空白边之间的GSCLK脉冲数9 C9 }) y9 `; W6 u7 d+ u
expec_scan_freq -我们希望扫描数组的频率(此处设置为200Hz)
! e, s3 W% w; d% @% \, A2 v现在,更新disdatax[]数组中的数据将改变led上显示的内容。( w- c& F2 I$ `' m( ~
GSCLK频率为1MHz,扫描频率为200Hz,我没有明显的LED闪烁,即使我听到人们谈论使用>5MHz来避免它与他们的设置。
/ ]2 p3 w4 n+ w& J% u  l1 W, f6 o  m: @" X0 P' l2 S! d4 ]- F
# Y8 M* |1 a: t& r' U
Logic Analysis
0 N" x- K4 h' K$ q我在STM32F407输出和TLC5940显示板输入之间附加了一个逻辑分析器,如下所示:% R& k  k9 [; s% ~
20.png
现在我们知道GSCLK周期是预期的,我们可以研究空白时间来确定正在以8位分辨率记录的灰度数据。
8 P/ U: y* m1 f& v/ ^下降和上升空白边之间的时间是305.6 - 49.6 = 256us,这是预期的。
1 b, b2 A. \5 M' k% r) \( P9 @我还调查了一个更接近的水平,以检查信号的相位是正确的2^8计数。2 a3 I( ]! @6 S/ n
21.png
最后,检查扫描宽度,我们可以看到一个列启用了2.445ms。
/ s1 e0 N( n5 b, k3 L$ f4 h即扫描速率为409Hz;考虑到2。5us =不能被256us整除,这很好。5 i) M7 N2 @& Y' g
上面的捕获还显示,当空白计数达到极限时,相关的ISR锁存前面的数据,8 T  D3 y+ q' n3 q! d$ M
切换扫描行,然后触发SPI传输。4 }: x: f. i+ m2 M/ }7 ~, t
然后在锁定该数据之前计算所需的空白周期数(XLAT信号在2445ms光标所在的蓝色箭头右侧几乎不可见)。9 Y+ y* L( }' H8 }) J% j! d

" y/ F# n  P6 L4 Q% C$ G4 F) e# E( R
3 ^2 ~& w1 I$ Z& W5 p1 K1 W! }; e
$ a0 j* R: d) ^! ?6 }' |
收藏 评论0 发布时间:2021-10-12 13:31

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版