|
STM32F4 SPI DMA 自己整理(存储器到外设模式), x8 T4 q3 Y8 x/ [+ d& o
SPI结构体
- p& b4 n2 A. l' X8 t$ R2 d9 D; I
9 Z. C4 p2 R6 ~- p8 H; _- ?. \* ^7 _ j m0 v
- typedef struct
% ?9 [1 w$ Z/ M* [2 t/ @+ s0 N6 w - {
' e6 M3 n( C9 l1 }+ _" [0 _/ b - uint16_t SPI_Direction; /*设置SPI 的单双向模式 */
; }. d! \) l- J F - uint16_t SPI_Mode; /*设置SPI 的主/从机端模式 */! n) {( E) b" P1 ^5 R$ P
- uint16_t SPI_DataSize; /*设置SPI 的数据帧长度,可选8/16 位 */) s( O* l9 T, y: x4 i" e* x: g
- uint16_t SPI_CPOL; /*设置时钟极性CPOL,可选高/低电平*/; I& S- v/ s! |' G# x) t, e# O; c9 J
- uint16_t SPI_CPHA; /*设置时钟相位,可选奇/偶数边沿采样 */; _/ r: n, p& |
- uint16_t SPI_NSS; /*设置NSS 引脚由SPI 硬件控制还是软件控制*/
4 G* k, z$ w' F2 K* S, H - uint16_t SPI_BaudRatePrescaler; /*设置时钟分频因子,fpclk/分频数=fSCK */
- ?$ e; @% e# Z4 @7 [ - uint16_t SPI_FirstBit; /*设置MSB/LSB 先行 */
8 L% x7 V* D: Z3 b - uint16_t SPI_CRCPolynomial; /*设置CRC 校验的表达式 */! O2 e1 O! k6 Z5 [3 ]
- }SPI_InitTypeDef;
5 b8 e3 k, H Y# s) a
复制代码 SPI引脚编号) h7 G% W7 d- [( A
SPI配置! F% R( N( n2 O. h C: r) }& L
- void SPI_Config(void)
: f, i3 O' L* g7 E' t# b4 \ - {
" Y( }: Z7 s/ V% e% C& v2 }5 C9 V% Y - GPIO_InitTypeDef GPIO_InitStructure;, c( x1 x; C. ]9 ~
- SPI_InitTypeDef SPI_InitStructure;* A& V8 y3 l9 x+ }
- " v* `0 i* r8 n" P
- //1.初始化GPIO
; F6 i1 x3 a9 p- ]$ `6 T1 S7 f. J$ A - RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB,ENABLE); % A V8 _3 a# R5 C4 z2 r/ N" U" Q" j
- /* 连接 引脚源*/; g- Z" b0 x- f2 @
- GPIO_PinAFConfig(GPIOA,GPIO_PinSource15,GPIO_AF_SPI1);
+ e! ~1 E5 G" \" r7 k - GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); 1 k/ W1 r- [# |) T4 V, Q- M1 E r* o
- GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);
7 u" I. A* a$ A
2 A7 ^7 k) Q. Z' T' y7 B- /* 使能 SPI 时钟 */
9 |. E4 {+ s; D0 U5 y - RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
. [4 Z' q, G; L7 l! o( t, h - ) K0 b% J. B/ N; A. l3 E
- /* GPIO初始化 */. v& M3 X. p- }9 J) E3 A
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
0 E( }: ~, ]. [. D" t4 u - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; ; F3 Y: j) D1 E. J
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;6 g& r4 J, ]; ^+ s
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;# L* c) f# [; B! L) ~& h8 ]% B) H
) g5 y% h( ~+ z, s- /* 配置SCK引脚为复用功能 */* K* ~" f% T3 o( H" Y% w( i' p% r
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 ; ' \) c/ t5 a5 B, v7 o
- GPIO_Init(GPIOB, &GPIO_InitStructure);
/ z7 \; n3 {8 W7 N4 o - 0 k+ M; |3 C) a/ d/ C
- /* 配置MISO引脚为复用功能 */+ }3 {+ ^5 C# {$ r( X4 _
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;# X& w. m. ]- s2 _3 [- V- O; Y
- GPIO_Init(GPIOB, &GPIO_InitStructure);; F! \$ l- ~! Q( ^
- , l8 u* R; V3 u+ `- C: Z( N
- /* 配置MOSI引脚为复用功能 */
w" @6 W7 |- \# u8 J6 D" S3 s - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;1 F# J6 c0 b: ], P
- GPIO_Init(GPIOA, &GPIO_InitStructure); - m7 T, ~' n( p& ~$ |) R' h3 w
1 u, s' m3 @: l4 A- /*CS引脚 */! e) @0 P x- h
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
8 L' `, o3 F& p/ u - 3 w! A( O( V) r% y9 @# F
- /* 配置CS(NSS,自动控制SPI的片选信号)引脚为复用功能 */- g9 p) Q, V5 I4 R. A
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 ;
7 n6 n8 z/ x( R, G/ m/ D6 m7 m - GPIO_Init(GPIOA, &GPIO_InitStructure);
1 b2 K) O* J% K+ B - 8 Y Q! O2 L" j4 ?4 ]+ X
- /* 停止信号: CS 引脚高电平 */, K8 Y) Y' g: P
- GPIO_SetBits(GPIOA, GPIO_Pin_15);- J% B9 w! d7 \8 Y
- ( \) D. S" y+ j; T! R& P
- //2.配置SPI工作模式
# q$ |/ R3 d* X/ D/ p1 q/ a2 B - // 分频
: J- l C+ S) A# ?, T - SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
* O1 f8 i$ b/ p- r# l( E/ j - // 数据捕获于第二个时钟沿) k9 D/ ~3 _/ ~5 U% O
- SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; 5 y/ O9 B; @( H9 b* V
- // 时钟空闲idle时是低电平
% q7 D8 }3 R+ W% I2 n9 H0 N! y0 [. z - SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; 8 N( b3 p v' a& b" d3 [' ~1 |% W
- // 不需要使用CRC校验8 ?; b& j6 N8 m; b/ E
- SPI_InitStructure.SPI_CRCPolynomial = 0; m0 ]7 I$ d- J W' Q1 @; {/ E- U3 Y
- // 数据帧长度为8位
G4 z. ]+ }! |/ ]0 J% Q2 Z - SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
' f* K2 C- h, Q - // 双线全双工模式
4 B9 X8 j% N9 @4 Z5 K - SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
- k6 H5 ~) J# I, M2 j3 \$ Q1 Q; z# I - //数据传输从 MSB 位开始, w7 s7 b( L$ b
- SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; 1 d& D; {& w* u0 X7 f
- //NSS 信号由软件管理
& Q8 R; ]) }; D% Z: m7 E - SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
. U' `- A: m4 U/ ?* f8 [' S3 o - //设置为主设备+ u- X3 M3 ]' n1 w1 k) A i+ C0 \
- SPI_InitStructure.SPI_Mode = SPI_Mode_Master; 6 [, _1 f3 e% p8 |5 G/ k, b
- SPI_Init(SPI1,&SPI_InitStructure); 6 g' X$ n5 n+ P2 t. f
0 F0 ]% v, B& i( Q/ `: \) Q- e4 j- /* 使能 FLASH_SPI */
9 e7 m# U% |( U9 a, I, F - SPI_Cmd(SPI1, ENABLE); . X5 y/ m! x9 Y# z
- }
) i Z& D$ \( v& ?4 L4 G
复制代码 DMA结构体6 E5 s" J' ?0 A
- typedef struct {
! @7 H, H, S5 g& k* V) B2 Q - uint32_t DMA_Channel; //通道选择
0 K" X2 F" q; D7 g% A - uint32_t DMA_PeripheralBaseAddr; //外设地址2 x: {- [2 A9 ?! D0 G% q& ^$ o
- uint32_t DMA_Memory0BaseAddr; //存储器0 地址) N/ a1 |3 V/ s) T/ |1 N5 Q. g
- uint32_t DMA_DIR; //传输方向
& [& H+ O8 t$ S( s) C$ F - uint32_t DMA_BufferSize; //数据数目* v N5 [% I6 m0 O. u6 R6 p; O
- uint32_t DMA_PeripheralInc; //外设递增- A- i! H% F ?9 ?
- uint32_t DMA_MemoryInc; //存储器递增" z2 ? r& L+ T! Q+ `3 F4 n
- uint32_t DMA_PeripheralDataSize; //外设数据宽度
* J0 n: K+ ?/ Z0 R; P( O4 P; S - uint32_t DMA_MemoryDataSize; //存储器数据宽度! e! d/ U; P) R6 B$ B
- uint32_t DMA_Mode; //模式选择
9 @: U" S) w: [% K" q3 J - uint32_t DMA_Priority; //优先级
9 z) F$ F. u* f& z0 `1 w0 a - uint32_t DMA_FIFOMode; //FIFO 模式
8 A) h% Z0 L+ ^: _7 n - uint32_t DMA_FIFOThreshold; //FIFO 阈值- ~, s- i+ K2 v' ]& W' U! S
- uint32_t DMA_MemoryBurst; //存储器突发传输: O- o% q6 S0 U: o* a% e: k$ M( U; V
- uint32_t DMA_PeripheralBurst; //外设突发传输
8 _5 ?9 R9 C2 a& k - } DMA_InitTypeDef;8 A9 T* @$ J8 A- I" R# S
复制代码
8 h9 A0 `' n$ ZDMA请求映射
# T8 u: @6 S5 I5 Y* }9 A7 q* ADMA传输模式6 A4 ~0 J& v( N. A
SPI 发送DMA配置- h$ q, n9 x# ~
- #define SENDBUFF_SIZE (1024*20) // 一次发送的数据
- h9 c" M) l/ J C V8 c( G - uint8_t TX_Buff[SENDBUFF_SIZE]; // 发送缓存
$ }0 B" I7 q% x' D& b - void SPI2_TX_DMA_Config(void); L* T+ f5 R6 h [% P1 o
- {2 A/ s5 u: c; l6 X% d
- // 中断结构体
4 W0 C0 N3 u: U - NVIC_InitTypeDef NVIC_InitStructure;
, _7 m$ h. n7 y% D+ Z' x3 b& q& o - // DMA结构体
# p' Q- @8 \; g( s9 x2 x+ ] - DMA_InitTypeDef DMA_InitStructure;
) l0 K* a$ ]# } - /* 使能DMA时钟 */ ) g2 ?. L: g1 I0 ]1 E6 O
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
5 e6 ~1 C# \ g- p3 Q) S1 V7 y' N - /* 复位初始化DMA数据流 */ , e% |' I+ g: F% ?
- DMA_DeInit(DMA2_Stream5);
# x( Z9 C3 j5 M4 y# S, r8 \& L - /* 确保DMA数据流复位完成 */
* p0 K ?0 v& t- g - while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE); / Q5 p9 t2 }+ j& ]' K
$ U9 R' ^. `4 G- /* 配置 DMA Stream */
m5 e) e# c2 T- a2 s - /* 通道3,数据流5 */
2 R* w& V8 Y% e - DMA_InitStructure.DMA_Channel = DMA_Channel_3;; i4 d7 N! H( c
- /* 外设地址 */
; j1 t; g" R3 L2 i) m$ ` - DMA_InitStructure.DMA_PeripheralBaseAddr = (SPI1_BASE+0x0C);
" t, f5 Y7 A0 O4 [3 f/ u1 l* S - /* 内存地址(要传输的变量的指针) ,DMA存储器0地址*/
) p% S4 x, c- ]! r+ M' y - DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)TX_Buff; + ^ a6 U7 [' [, v& o' s
- /* 方向:存储器到外设 */ $ v! R& @2 u+ w: ~ d5 `6 F3 ^) Z
- DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;" } s5 T6 j6 `! V$ K
- /* 数据传输量 ,可设置为0, 实际发送时会重新设置*/
" N9 v9 B' r. c" O0 S0 D+ e- \ - DMA_InitStructure.DMA_BufferSize = (uint32_t)SENDBUFF_SIZE;
, E/ E+ H! f$ Z6 R1 }+ i$ H/ L/ s - /* 外设非增量模式 */ : ` w& H% |/ g/ i5 }
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;& } `4 n0 b, d1 k: G! l+ k
- /* 存储器增量模式 */ : U' c* _" ^( q/ _
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;: V8 j" d5 c; _( i7 m( @* R
- /* 外设数据长度:8位 */
& b3 ] v- f# o0 ^; q - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
9 b P% u( [$ g) ] - /* 内存数据长度:8位 */
/ d) c3 y$ K5 U% G1 t7 Y- j" g+ M, Q - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
/ Y' q4 X: q4 J0 q; T - /* DMA模式:正常模式 */ , U( n% h2 i" p2 W. y
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
3 H( A1 ]; o2 T. E# Z1 D ?" k- ^ V - /* 优先级:高 */
) Z+ i8 R% u9 x- W3 a+ r5 d/ G - DMA_InitStructure.DMA_Priority = DMA_Priority_High;
' ?. B' `4 J: q4 b& V% \ p - /* 禁用FIFO */2 U* }) V% Q6 H% Z# ?
- DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
/ x8 e, w/ [% X; T1 k; M/ u - DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull; % H2 ?! B( e+ H7 [' m
- /* 外设突发单次传输 */ 2 j" f F, |& u8 Y& F" ]1 q2 w' b* @$ a
- DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; 7 u. k/ p# o( o' g# ~+ u
- /* 存储器突发单次传输 */ 7 t* C# H) W* {% `
- DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; - G Q2 T) g% A# W% o6 K- Z: Z i
- : ]9 \ `: W" `, j" c( {, S5 |- L
- /* 初始化DMA Stream */ 1 ~, H- z9 z1 } t' v$ H9 n. x
- DMA_Init(DMA2_Stream5, &DMA_InitStructure);- h; z+ @! @" j5 c8 D# U
- /* 开启传输完成中断 */
0 O: i+ }# l6 n* f- q5 i; ]4 V+ j0 g! ^ - DMA_ITConfig(DMA2_Stream5,DMA_IT_TC,ENABLE);
: h! x$ N1 B" `& {% I1 }
2 b3 H c$ l- i8 K' a1 X- // 中断初始化
" n4 v$ ^+ K2 a2 G+ G( g E! F7 C, }8 l - /* DMA发送中断源 */ ; w5 L4 z8 z3 ~' O% Q# c
- NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream5_IRQn;
$ {5 o3 K# M+ D: W0 E; A1 O - /* 抢断优先级 */ & i# f4 A- @* \& c" \
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;$ E& M8 G) Y# A$ p
- /* 响应优先级 */
* i$ L! p, Y$ \9 Q$ i - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
7 G7 x* \) v4 O1 j* r4 Q - /* 使能外部中断通道 */ q! L7 J% R8 Q
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
! M" H! n: \4 p5 s - /* 配置NVIC */ 4 }" r) `# {1 Y7 b
- NVIC_Init(&NVIC_InitStructure);
5 @8 @5 ?0 P4 k8 e. t - }
7 I6 I3 D4 J/ S: q
复制代码 DMA发送中断服务函数
. k1 c0 w4 f: i) m" n; w/ d- void DMA2_Stream5_IRQHandler(void)
' m! m0 W" D, O8 j5 Y f - {9 I- F- S9 W$ Z% l- L3 l/ O
- // DMA 发送完成& I" ^2 p+ A4 d9 a' e/ r! o
- if(DMA_GetITStatus(DMA2_Stream5, DMA_IT_TCIF5))
, V- I$ P3 c# S) l1 b1 ~) [! D - {/ Y! @% U ~" B# d- P- @4 [4 A- `
- // 清除DMA发送完成标志& a( H! K9 F: ]8 {' W3 T
- DMA_ClearITPendingBit(DMA2_Stream5, DMA_IT_TCIF5); # J6 S- i4 L/ U6 ]( A- @
- // 片选拉高,数据发送完毕
. Z0 ]0 s. H$ a7 {' ~0 T: D- x' R/ O - GPIO_SetBits(GPIOA, GPIO_Pin_15); & f$ H" r# E! \% [+ Z3 l
- }
/ Q; o4 K" K$ N) `3 a, |; K - }- B# C+ Q( h ~& P9 g
复制代码 SPI 接收DMA 配置* q0 ~" ]! R0 {0 {
- #define RECEIVE_SIZE 800 // 接收大小
( t6 m- C9 k; |( h; R x% c - uint8_t RX_Buff[RECEIVE_SIZE]; // 接收到缓存; O" u/ z! y8 o) B# w; Y
- void SPI2_RX_DMA_Config(void)
0 W6 ^) i5 X/ m/ k4 g - {
( q# \% w- O: J f; v+ ?8 s# X - // 中断结构体
$ h0 q" R& I- Q: } - NVIC_InitTypeDef NVIC_InitStructure; " q. X* t o8 b* n4 r. K. L
- // DMA结构体 6 W D; h% _3 `$ s5 c5 w$ ^
- DMA_InitTypeDef DMA_InitStructure; 0 i, m4 ^& ]! h3 O. ^7 i
- /* 使能DMA时钟*/
7 }& V: J/ n6 \2 _ - RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); /* 复位初始化DMA数据流 */
3 `8 G$ [! F% \( g2 k - DMA_DeInit(DMA2_Stream2); /* 确保DMA数据流复位完成 */8 @" Q0 S/ o- N- t# C
- while(DMA_GetCmdStatus(DMA2_Stream2)!=DISABLE);# W( E$ E. z0 k9 K' k6 J
- # h" n" X3 d! V, ~2 V
- /* 配置 DMA Stream */3 M2 I: g& L- i. f
- /* 通道3,数据流2*/ % I, X& X |& W/ [- | Q
- DMA_InitStructure.DMA_Channel = DMA_Channel_3;
5 Z3 K5 H6 h) @/ `# e - /* 设置DMA源:串口数据寄存器地址*/
* `. ~( j* X* }) }6 H. s - DMA_InitStructure.DMA_PeripheralBaseAddr = (SPI1_BASE+0x0C) ; {1 [2 m- T7 ]
- /* 内存地址(要传输的变量的指针)*/ 1 p' v$ `3 ]! L8 K, u* V: M
- DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)RX_Buff; 8 m: X, E% |( D C2 X
- /* 方向:存储器到外设模式 */
+ B! k) k: K, i Y9 x' | - DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
j* g( ?4 z* B0 O/ ] - /* 数据传输量 ,需要最大可能接受的数据量[不能为0],实际发送时会重新设置*/
% o. B% n- C5 w1 C% ^ q( _; S - DMA_InitStructure.DMA_BufferSize = (uint32_t)RECEIVE_SIZE;- o- u5 M& Z( v
- /* 外设非增量模式 */
3 @5 G0 c# B' `7 k- D' x8 A - DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
& ?' e! |& _$ k i% D! | - /* 存储器增量模式 */ * C g1 l$ I# o4 {/ p+ `8 c' \
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;8 j# y3 g( n; E% b/ ]
- /* 外设数据长度:8位 */
9 h0 j0 K/ v& a+ S6 u- B7 x - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;9 S; |+ J. y; M- ?1 _, l: x
- /* 内存数据长度:8位 */9 r% `5 p+ B% x% j
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
' D+ g9 P, ]0 s - /* DMA模式:正常模式 */ 1 i! i# e, Q3 u* a! K
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
+ b+ s. ~ x) K8 s- a% r1 { - /* 优先级:高 */
& L( D. P V3 n2 [ ] - DMA_InitStructure.DMA_Priority = DMA_Priority_High;
O6 P1 y3 n$ z$ ~ - /*禁用FIFO*/
3 z% }9 ?1 I, E( V - DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;# e# }% I; a8 d6 O
- DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
' v+ P6 R+ Z, o" D8 L2 e# P# Q - /* 外设突发单次传输 */ 3 ?6 N3 v# ^- a4 r4 ^- B( O, k
- DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
. F! h7 t! b& X& g! K6 a - /* 存储器突发单次传输 */ 1 C! B+ v( k6 A
- DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; $ I0 Q* V" p9 }; G4 o- h
- /* 初始化DMA Stream */ 6 M1 l) a5 p$ h+ h1 x4 v: G* ?
- DMA_Init(DMA2_Stream2, &DMA_InitStructure);
0 s1 ]+ W4 D+ ]7 C f2 x2 d$ l g - /* 开启传输完成中断 */+ k4 [; L+ o) r# W) g, r& L! w( K
- DMA_ITConfig(DMA2_Stream2,DMA_IT_TC,ENABLE);
& a* m* Q6 Q/ u3 I
% d% T }% L; H: z- h& H- // 中断初始化
! [% ?1 l& w0 {$ _0 G - /* 配置 DMA接收为中断源 */- ]% G4 ~. K8 o& z' e. l
- NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream2_IRQn; # W1 Z& a8 D, a9 }# `
- /* 抢断优先级 */
; m2 [' [; f" I, _2 a. C2 t e - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
% a8 o& I) y7 }. v1 ~4 [ - /* 响应优先级 */
; u( J/ v* J8 c/ B - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
1 f5 [, V; Y7 \* F - /* 使能外部中断通道 */
5 u8 O8 N7 X; g, r6 Q& n - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
1 q7 |' f% [% k0 c6 L! U - /* 配置NVIC */
R9 H, d8 i7 ~$ y# I - NVIC_Init(&NVIC_InitStructure);' e) j0 H q& i+ _4 n
- }8 P" k3 G* P5 k4 W# w
复制代码 DMA接收中断服务函数* Y5 s) v/ Z# A; \1 J+ v0 ]
- void DMA2_Stream2_IRQHandler(void), v9 ? X+ t; g/ i
- {
( \) y, F4 M! n - // DMA接收完成
. y/ V+ r2 }# `: B/ \ - if(DMA_GetITStatus(DMA2_Stream2, DMA_IT_TCIF2))
2 ]+ [" `6 R! ? h9 e - { ! f. e4 C1 Z/ t" E5 i' t+ H
- // 数据接收完成 拉高片选
. \# y; A- _* Z. @ - GPIO_SetBits(GPIOA, GPIO_Pin_15); * }9 M% f; v8 _9 u& }
- // 清除DMA接收完成标志位
& u: R) a- @5 Y. n, U - DMA_ClearITPendingBit(DMA2_Stream2, DMA_IT_TCIF2); ! g5 X% i+ d" L) w( `( ^
- }% s# t' b' P1 t& J0 w
- }$ j5 {; `# Q% x7 ~
复制代码 DMA请求使能: Q3 s9 l2 ^8 A ~
- //SPI2 TX DMA请求使能9 P- y8 z; s) H- x( y5 v* a9 W7 h
- SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
" \8 n' C2 h9 v5 Y7 [! P# K - //SPI2 RX DMA请求使能5 _7 R* N! z o! p) K
- SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
复制代码 SPI_DMA 读写一个buf/ X( F/ q; B. i' X9 x8 |# O' H
- #define BufSize 800
B' o/ K) L- G, @' g! i* \ - void SPI_DMA_WRITE_READ_BUF(void) $ z! n6 O7 Q* n" N# b1 `
- {
1 [. T, w( b r4 q: r& [6 W - // 关闭发送 DMA
S; v0 ~: T _0 n6 D* P+ \) a - DMA_Cmd(DMA2_Stream5, DISABLE); * k; w1 u* K( n
- // 关闭接收 DMA % A) I3 Q3 N! q. f
- DMA_Cmd(DMA2_Stream2, DISABLE);1 i3 @0 j `1 d# H$ Q, _# l
- // 设置发送的数据量' s4 A8 W1 q3 Q' X9 d' i' ^
- DMA_SetCurrDataCounter(DMA2_Stream5, BufSize); * p1 F! h$ b( [2 ]5 i
- // 设置接收的数据量- T( s$ n1 i) b
- DMA_SetCurrDataCounter(DMA2_Stream2, BufSize);
4 m% j( O- t- u; R, u& z - // 清空数据
1 A3 J) T) n; @. Z* @ - SPI1->DR; 4 \( S, {/ a X1 I" U7 f R4 C7 K
- // 擦除DMA标志位1 c; g* m5 u8 t2 J F* H
- DMA_ClearFlag(DMA2_Stream5, DMA_IT_TCIF5);
. p- s0 q% k$ @8 g6 J. A$ B/ X - DMA_ClearFlag(DMA2_Stream2, DMA_IT_TCIF5);* v# ~/ i4 u) E6 ?4 p
- 6 E& A5 n( a) j# q
- // 片选拉低,接收数据1 u& V8 s2 K- u7 C, c6 B- E1 D
- GPIO_ResetBits(GPIOA, GPIO_Pin_15); ) ], M; N! a" J, |3 h9 N4 d# w( s
- // 开启接收 DMA
1 B' |0 O9 O3 ?4 U: P$ r& O f& k - DMA_Cmd(DMA2_Stream5, ENABLE); * W# i9 s3 y- F7 w2 ~, d
- DMA_Cmd(DMA2_Stream2, ENABLE);6 ^: }! F1 G& {8 [4 n
- }
) \* y( R# `! v4 G
复制代码 SPI_DMA 发送一个buf
" r' r2 C8 E5 t& ~' {. I) a; G% Z- void DMA_Write_buf(uint32_t SizeLen)
; }) P- @# h9 Z - { a! ]; E1 G% n1 x& | B$ |
- // 关闭发送 DMA : ^2 ^( y) V+ E
- DMA_Cmd(DMA2_Stream5, DISABLE);
- E& z$ Q) {) g6 H5 `2 g - // 设置发送的数据量
# y0 X2 A& o# j9 A - DMA_SetCurrDataCounter(DMA2_Stream5,SizeLen);* p7 Q# U3 ]4 a
- // 清空数据5 a% \+ R; `, p; y8 E8 u, L. n
- SPI1->DR;
* I, C* T1 h3 k8 \% b# w7 ^, k - // 擦除DMA标志位 * t$ V1 t8 [: _$ C9 t6 g7 s8 W
- DMA_ClearFlag(DMA2_Stream5,DMA_IT_TCIF5);
6 @' o8 C* I% _4 O - // 片选拉低,接收数据
. D7 I5 U: F! M7 h5 o4 O' o/ F - GPIO_ResetBits(GPIOA, GPIO_Pin_15);
8 W) r" W' @) m6 g3 J4 h8 } - // 开启发送 DMA: T) S7 f& s$ d% T+ x
- DMA_Cmd(DMA2_Stream5, ENABLE); 4 ]7 |* Z1 \3 a4 J% h4 z- m4 F
- }" b% g( p# \; R& C, o
复制代码 张贴的代码显示如何配置我的GPIO,定时器,SPI, DMA和NVIC模块,以及一些系统如何工作的解释。
( h9 H6 G+ J: B7 }1 C0 o6 A注意,我使用的是STM32F4标准外设库。8 m( K3 d4 Z- a: H
! \* H& z/ E0 N" m9 y( i& i6 k; t; Y4 L( H
网上内容
& H+ x3 u% B. q, n |8 D通过RCC(复位和时钟控制)模块使时钟信号到达所需模块:
6 _4 U2 |7 s# S9 j5 N% B8 o5 h, c+ V6 w7 _4 ]7 c) o
% G3 ~9 f$ D1 H$ ]* @' C
RCC6 w+ Y b, a6 s/ r3 L: O& L
- // 为所需模块配置时钟+ ]$ y l8 I4 N4 ]4 K0 U
- // 启用GPIO外围时钟% W& {7 w2 a0 c) ~' G; X
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);. U8 T: d) U$ D8 m: k2 ]: v
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);4 R% V# I$ q% h0 U9 s$ ~* Z( Q
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
% f' g1 Y5 E# s" x: J7 I - RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);3 Q: _+ u& J* x" _: x! J, _
-
+ ~2 O* ?2 B c9 s2 [5 D - // 启用串行外围接口外围时钟! e( I z, ~# ]. j7 Q
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);8 E0 m" k2 U& c$ {0 U) U
- - `' v+ Y0 F2 C4 J6 e7 G2 M
- // 启用直接内存访问外围时钟
9 f9 Z0 R- ~ S! Z4 n4 [) b - RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);2 ?! k! ?6 F9 l2 B( Z
, M9 j! G5 j3 ^. b, o# T- // 启用计时器外围时钟4 K, I; P! m& r
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
2 }/ r8 c* }; x2 Y" ^! p& B - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
. H% {. V- K- M0 c; c( [
复制代码 GPIO
: x- J# y; G5 ?( [8 `$ \5 X接下来,配置所需的GPIO引脚:, |6 z' H9 B* G6 u3 w+ m- Y8 z
- #define GPIO_SCAN_PORT GPIOB6 T6 D) H( t1 ^- X% _4 H3 I
- #define GPIO_SCAN_PIN GPIO_Pin_7
: ?# h' |% p! a. M' p8 Y' _ - #define GPIO_XLAT_PORT GPIOA
[3 G7 ^0 |2 e' `9 F6 D; y - #define GPIO_XLAT_PIN GPIO_Pin_5
; T5 k! P3 j3 r0 d5 Y+ i; X4 M - #define GPIO_BLANK_PORT GPIOB A6 G7 J' n( l4 ~ @; W, y
- #define GPIO_BLANK_PIN GPIO_Pin_6
9 n+ @' r; R2 l7 V# M0 J
% l% V% P% t H# N1 f: j, p- // 配置GPIO引脚7 C$ Q0 h) Y1 U- Y/ j9 z, o, p7 `. Z# L
- GPIO_InitTypeDef GPIO_InitStructure;
/ |# ~! U: ]7 ~, m }
5 t' n8 u9 ^- o6 @ ~' m8 j9 [0 D- // Timer3&4 输出 (TLC5940 GSCLK and BLANK)
" N& E9 e7 B0 o. v' ]+ ~/ d$ Y - GPIO_InitStructure.GPIO_Pin = (GPIO_Pin_4 | GPIO_Pin_6);
- X7 L. S0 _1 p+ U/ i - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;( z" b, D# ]9 {: C0 D3 \1 F0 L% T; E# t
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
: Q) i( y* x8 C# O% i' Y0 F - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;- V" n1 l6 }& ]: n4 y8 T
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;5 }2 g! L1 P! J, t: E- k* j! B; Y
- GPIO_Init(GPIOB, &GPIO_InitStructure);
6 d" n: K- ~0 U& a0 ~/ Z - // 连接计时器到GPIO引脚
) I. v$ _6 F: }6 ~* c/ r - GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_TIM3); // 将TIM3 OC1输出连接到PortB Pin4 (GSCLK)
1 m d: J0 v* k3 i9 y - GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4); // 将TIM4 OC1输出连接到PortB Pin6(BLANK)2 o" h- B. h- i! ]
; \) w X$ |4 V# g* f- // TLC5940 XLAT 引脚1 {/ m2 q; a0 R5 v$ ~; y+ K/ |
- GPIO_InitStructure.GPIO_Pin = GPIO_XLAT_PIN;5 c; k; O! x* I! b3 Y5 J7 ]
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
6 H! s! g. t; P8 c. P; p - GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
% R1 k+ p0 G( ^% t - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;, |/ c# o+ f+ F, m: J! s
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;* B3 ?$ v2 V4 p0 Z: f; C4 S" c7 ^
- GPIO_Init(GPIO_XLAT_PORT, &GPIO_InitStructure);$ v A" o0 K x9 r4 o# }7 Y* v
) i: ? R5 e( H- // 显示扫描 引脚! Q" o4 I- q. n: D% o: y- q& K
- GPIO_InitStructure.GPIO_Pin = GPIO_SCAN_PIN;
, \ M4 h7 m3 \0 |: S. b% S - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
: }" V% L$ P( W% `( m - GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
4 N6 }! o, u0 Y0 w - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;3 j h% z W/ O3 \1 e
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
9 c8 T% K' g& L - GPIO_Init(GPIO_SCAN_PORT, &GPIO_InitStructure);
$ V' m+ b( t2 L' H: _: ^, B' [
3 @0 k2 E7 |0 S. _7 u5 z2 J- // SPI2 引脚
, t# Q( U) E2 i" u' _$ x T - // SCLK = PB10% L9 i9 I: Z6 D) D. x/ N- ?$ @% E; j
- // NSS = PB99 O# e( r% I& o7 p/ f+ z/ j! Z
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;3 A: g# a; Q' d/ t# z; n
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
$ o5 T3 n7 ^' R$ }: ~' ` - GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
) r. m7 P; ]# N - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;2 ] F6 u5 ~; |
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;& C! x4 |$ v& c
- GPIO_Init(GPIOB, &GPIO_InitStructure);
7 A# E; F9 a$ O5 U% N1 ~, { - GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_SPI2);
$ Q& u+ B. c/ M$ y2 u7 m2 m8 C0 p2 E - GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_SPI2);
( w$ ^+ F% A6 _& q8 _
1 Z1 r0 L: c/ U: l- // MISO = PC2
; c% C" @3 @9 S - // MOSI = PC3
7 t+ F4 U8 R8 } - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
! B- B7 x5 W- q+ Z/ P! H - GPIO_Init(GPIOC, &GPIO_InitStructure);
; \& T% g9 h/ F - GPIO_PinAFConfig(GPIOC, GPIO_PinSource2, GPIO_AF_SPI2);4 }# W: j# `6 G7 L( i2 F
- GPIO_PinAFConfig(GPIOC, GPIO_PinSource3, GPIO_AF_SPI2);
4 X: d" V( \3 Z, K( [# R - ————————————————; \ }5 @8 u1 q. ]3 o* m
复制代码 这里的要点是,我将把TIM3的OC1输出直接连接到GPIO引脚(用于GSCLK),把TIM4的OC1输出连接到BLANK信号。
4 C7 I* o" L5 u; K* l5 }
5 P1 T# l) y5 a
2 H! D7 h, Q b- D9 W) i' J2 xSPI6 P! O' u* V* ^! n/ T) }0 f
现在SPI模块可以初始化:3 A X/ J' g! ?7 a7 G6 O; l4 T( q
- // 初始化SPI模块
2 e' I3 j# Z4 r0 q' P# G# \0 v - SPI_InitTypeDef SPI_InitStructure;
+ H2 D+ D$ s: Y. G- {/ ~, r% R - SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // SPI总线设置使用两条线,一条用于Rx,另一条用于Tx' Y. j' j0 q* u3 m8 G
- SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // STM32是主服务,tlc5940作为从服务
; J5 n! A I$ C9 l - SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 使用8位数据传输
$ l* a: e# H9 B/ s" s - SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // TLC5940时钟空闲时低+ f) C1 c2 t3 L/ \3 U
- SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // TLC5940使用第一个时钟过渡作为“捕获边缘”- g# i$ w( U3 ]6 ~* X; n, v
- SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件slave-select操作. V4 O9 D* o3 u7 @0 v: S' L
- SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; // 设置预定标器3 u1 B8 L% ~; ~) z
- SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // TLC5940数据先从MSB传输
1 S& I4 [4 l2 D6 `" E8 M1 O4 ~ - SPI_InitStructure.SPI_CRCPolynomial = 0; // 没有使用CRC: ?+ `% d6 O# D _
- - v0 F; u1 P6 F9 }$ A
- SPI_Init(SPI2, &SPI_InitStructure); // 初始化SPI2外围设备( a; L. D& l& e1 R+ c0 f
- SPI_SSOutputCmd(SPI2, ENABLE); // 将SS Pin设置为输出(主模式)
4 I* X# ^( @( ~' P) v! K( \ - SPI_Cmd(SPI2, ENABLE); + L, |0 Y$ v6 ?5 o4 k u
复制代码 我选择的SPI时钟分频器是相当随意的,但这里的关键点是,我已经配置了时钟相位和极性,根据TLC5940数据表,所有传输都将是8位(稍后详细介绍)。0 t4 m% T; R- c
6 }( P$ Q, R/ d$ O7 J
5 [5 F9 V+ k) DDMA
! X* J Q! u7 R用于SPI传输的DMA模块如下:- R4 K# Q% ^$ P/ D @3 H G
- // 初始化用于SPI2_TX DMA访问的DMA1流4通道00 s5 H, N7 k5 ^7 O7 j' Y& g
-
! _% k# o' b( G0 R - #define DISP_SCAN_DATA_CNT (24 * 3 * 2) // 初始化用于SPI2_TX DMA访问的DMA1流4通道0; c N7 Y r9 q4 Z
- volatile uint8_t dispData0[DISP_SCAN_DATA_CNT];
7 }6 P4 G. j: _1 t* `: [, C" l. ] - volatile uint8_t dispData1[DISP_SCAN_DATA_CNT];
' j" j8 r' I! A) `! d - - G1 v, R! j8 W0 T& {- I. `$ M
- DMA_InitTypeDef DMA_InitStructure;' N: i/ _+ k8 P, j4 z* u" {
- # L- G1 S- u# i7 Y1 _8 e
- DMA_InitStructure.DMA_Channel = DMA_Channel_0; // SPI2 Tx DMA是DMA1/Stream4/Channel0& ~' l( y( n& V$ m! w0 `
- DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(SPI2->DR); // 设置SPI2 Tx
7 Y! o- s; M* Z& q! @# Y& g - DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&dispData0; // 设置内存位置
0 e. u- ?' ^+ L# |2 k3 ~$ }% ~ - DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; // 从内存发送数据到外设的Tx寄存器
3 M: |: w8 r$ @ W! o0 I - DMA_InitStructure.DMA_BufferSize = DISP_SCAN_DATA_CNT; // 定义要发送的字节数
) H3 B, r! i' m2 |9 I - DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 不要增加外围“内存”
$ d% i8 B1 w) K* E: _0 S - DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 增加内存位置
. T( U. j# V+ z* Q" y8 Q) U - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 字节大小内存传输3 _! Q# [( d$ U% u& x8 m1 c7 W
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 字节大小内存传输/ M [' H/ e7 |* N
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 正常模式(非循环)
1 @+ v' ~7 c! J' D6 F. ]+ A - DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 优先级高,以避免使FIFO饱和,因为我们是在直接模式4 _- c, k) n- c" ~' Q& h1 Y
- DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; // 操作在“直接模式”没有FIFO
" _/ V" Q0 |; Q - DMA_Init(DMA1_Stream4, &DMA_InitStructure);& [7 p7 M% X# Q2 x
- 7 L ~+ M# s r8 z6 J- Z
- // 操作在“直接模式”没有FIFO
$ Y+ t, F t. s5 I1 e - DMA_ITConfig(DMA1_Stream4, DMA_IT_TC, ENABLE); $ V& f) T0 O/ J/ j
复制代码 NVIC
- \: @; t; N) U* ^: x) B接下来,我已经为两个中断服务例程触发器配置了NVIC(嵌套矢量中断控制器):% r: W# J& g% t" ]
- // 初始化嵌套矢量中断控制器# }( u3 c2 q; f0 Z* A# [/ R" k
- NVIC_InitTypeDef NVIC_InitStructure;
O7 s/ l3 d3 P: E -
1 T' U# S+ K3 ]6 ? - // 启用TIM4(BLANK)中断
. I/ S0 ~: n9 D' i* E. |3 I3 H7 k4 C - NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
# h4 A0 M! u' C - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
8 _: Q R2 i/ k- a/ A - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
( E) u) u: F. g" U" W+ g m - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;6 M9 p$ t! ~! w7 R6 e$ a2 \1 y
- NVIC_Init(&NVIC_InitStructure);
/ L6 _2 w! Q4 P. D) M
( T6 ~8 \: ^7 ~; a) y- // 启用DMA1 Stream4 (SPI2_TX)中断 J. \) _9 {4 {$ ?( A
- NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream4_IRQn;5 D6 b+ p8 W1 ^4 J
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;" s! z* e5 e# j" w
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
7 E! r4 U, X, Q' L - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
& E! D$ z' K* ~$ Q" V' ]6 `6 u - NVIC_Init(&NVIC_InitStructure);1 s. U. X4 F$ H9 [3 q
复制代码 BLANK中断用于产生空白脉冲,初始化DMA传输,并在每个扫描周期后锁定先前传输的数据。2 K* X- K9 Q3 e& o" u7 D( Y5 ?
7 i( ^2 u& |6 E+ F2 F- r1 s, W: ]" ~" G" X' q" l3 K2 ?
Timers
# E: Z2 u/ ~% K0 G" F最后,定时器模块配置:
- [. p8 Q4 G) b) }- #define TLC5940_GSCLK_COUNTS 256 // GSCLK在BLANK 脉冲之间进行计数& O( j- e6 I6 i2 [3 l
- #define TLC5940_GSCLK_FREQ 1000000 // GSCLK频率
& Z& K$ N$ [1 A* Z. Q9 L - #define TLC5940_BLANK_COUNT 50 // 在切换到下一列之前,允许前一扫描列的正电源轨道关闭的填充) h" C4 k0 O! g. H3 z/ Z
- #define TIM_APB1_FREQ 84000000 // 内部时钟频率(CK_INT)
8 z2 ?+ O# Q1 }2 C+ c, z -
7 Z) f0 |6 [2 O- }! D( p - // 启动计时器模块
V% R. G% {. Y - TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;
! U2 o- |" c$ F) G - TIM_OCInitTypeDef TIM_OCInitStructure;- [; ?! W) F6 y9 E8 c' U: Z' o0 P
9 G* S' r R# S- // 反初始化计时器模块和初始化结构) @6 G: X( m6 u
- TIM_DeInit(TIM3);
, a& V) t0 r0 D7 y; g- P - TIM_DeInit(TIM4);' q6 k2 `; [# o$ T4 G
- TIM_TimeBaseStructInit(&TIM_BaseInitStructure);
0 o7 I# U) p9 x7 ^1 d! L5 [7 z - TIM_OCStructInit(&TIM_OCInitStructure);9 ?; L8 D2 M9 p$ n1 Q6 A& G
- ! w$ F3 }% H0 V
- // 设置TIM3来生成“主时钟”# F2 R* l# E& w& X5 ^7 a: v
- TIM_BaseInitStructure.TIM_Period = 1;" @, p- K4 Z9 g
- TIM_BaseInitStructure.TIM_Prescaler = (uint16_t) (((TIM_APB1_FREQ / TLC5940_GSCLK_FREQ)/4) - 1); // 请注意,4的除法因子是由OC1频率vs CK_INT频率引起的
1 b o/ c0 E `% t+ o - TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
3 ?0 B7 m ]& Y( J" O - TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
# W9 e; X# ?+ P: G - TIM_TimeBaseInit(TIM3, &TIM_BaseInitStructure);! B5 q( B, V4 c
- // 配置通道1输出比较作为触发器输出(用于生成‘GSCLK’信号)
+ p8 `! w1 ?; J - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;1 d* d# E. M( U; ` o
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
7 e+ h* e B" n$ j& P+ @ - TIM_OCInitStructure.TIM_Pulse = 1;
8 ^* W8 F; ^7 B$ v1 H1 | - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;' \+ [" a' E- T" J0 V
- TIM_OC1Init(TIM3, &TIM_OCInitStructure);
( O! `8 n0 }; @2 u. H- E: d3 s( F' d9 T - TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
* G8 G' M7 q! N
; s% Q, E& {, B" l6 i- // 为对称计数器设置TIM4基准,最大计数指定为“GSCLK计数”(实际上是TLC5940的灰度分辨率)# `& z+ g8 M0 q) z4 D, F
- TIM_BaseInitStructure.TIM_Period = TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT; // GSCLK溢出计数(对“阻塞”的空白信号额外加1)8 e- |' l) h: C* w- b
- TIM_BaseInitStructure.TIM_Prescaler = 0;* k% f. n. u0 Q) O2 q) R
- TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
* z8 J# j N0 ~$ b B+ L1 z - TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;
5 {$ c3 U* u2 r0 Y - TIM_TimeBaseInit(TIM4, &TIM_BaseInitStructure); d9 `0 G' d3 e4 D, m0 R% _! o
- // 将Channel 1输出Compare配置为触发输出(TIM4用作时钟信号来生成'BLANK')
, J5 t8 g: s% f" w, \% a - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; f( K5 r" k0 [. |
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
?" c+ I5 K/ x" v' X - TIM_OCInitStructure.TIM_Pulse = TLC5940_BLANK_COUNT;
( t( F- W. b( \% H2 y" T' K - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;* o: N( K" g- G; l! z4 B
- TIM_OC1Init(TIM4, &TIM_OCInitStructure);
. V( c6 w6 { C! X - TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);4 Y4 j& v; d& E$ S U6 l5 ?
- ; q+ \% {: m P; {% ~1 x# V( w
- // 将TIM3配置为主计时器" D( w8 {, ^) u& t- T1 [ K
- TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); // TRGO与TIM3的更新绑定! {# t! |4 E, v# Y2 p
- TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable); // TIM3作为master启用
% l( {# r$ o4 {. O2 l8 v( ~ - r, G; }% w# z) }) j1 I
- // 将TIM4配置为从属
8 p& F/ m8 n( C! |2 I - TIM_SelectInputTrigger(TIM4, TIM_TS_ITR2); // 设置TIM4(从)触发TIM3(主)' F& Z* z$ H! z/ C9 N- r1 m3 M
- TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_External1); // 使用主信号输入作为一个“外部时钟”7 ^, n, d9 \7 s1 h8 M, T4 E
- * z. j6 z8 x3 G
- // 将TIM4模块配置为在捕获/比较1个事件时中断(向上计数和向下计数都匹配)
4 \) p. k/ l0 v: B% |( Y2 c& V - TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE);
0 V& E" z$ W5 {1 y
, V3 b& S; y ?; ]0 u$ H: b- // 启用计时器3和47 ?6 y' B8 Z8 U9 d7 x% g
- TIM_Cmd(TIM4, ENABLE);& [# Q$ O7 N A$ l
- TIM_Cmd(TIM3, ENABLE);* e1 q6 K8 \ ]5 k9 t
- ————————————————& W/ h+ ~' T5 @$ |6 x6 {- a' L
复制代码 在这里,定时器3被用作主时钟,在它的输出比较1线上产生GSCLK信号,和驱动定时器4被配置为一个中心对齐的PWM输出在OC1上。
7 K5 A: \ b5 k; s4 d3 sBLANK计数是有效的填充脉冲,允许:
4 X# a- {7 ]& [" J; X% _! _/ G( e( i9 R
3 W# x+ S. g) K* C最小空白脉冲时间$ B1 U( U) e6 \% h% e" R% ~% e
XLAT和DMA传输触发5 t0 I6 j e, R5 x$ k2 [( ]4 `
MOSFET输出在之前的扫描列完全放电(我已经通过观察放电时间在我的示波器调谐) g6 k9 l# @0 T3 F3 D' D9 A: n
设置了GSCLK频率,并且在下降和上升的空白信号边缘之间的GSCLK脉冲数设置为256,因为我使用的是8位的颜色,而不是TLC5940芯片(12位)的全部功能。这意味着在空白脉冲之间将有256个GSCLK周期。
) H5 z: \2 L9 f3 z* u3 r8 s* Z; K) E外围设备现在已经完全配置好了,所以最后要做的是查看中断服务例程,并调查结果:0 \ G4 G) X, r# Q/ r0 v
2 e q) ]" f* ]
j" z/ l! N6 mISRs0 L. @0 m" X/ Y
- #define DISP_SCAN_FREQ 200 // 扫描信号的频率
6 B% { U" _, M7 Z w - #define DISP_BLANK_CYCLE_LIMIT ((((TLC5940_GSCLK_FREQ / (TLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT)) / DISP_SCAN_FREQ) / 2) - 1) // 扫描前要计数的BLANK循环数/ G# G2 |2 t4 [% X) J( I% B
- , k9 k- h% j2 r
- void TIM4_IRQHandler(void)
2 x5 b7 T. x5 b7 d% ]. s - {
- ^4 M- n X5 `2 s - // TIM4 IRQ处理程序有几个任务:
; {! K( W. p! h- k4 a# y - // - 切换扫描信号
/ s8 c5 V) Y- d5 f( n - // - 锁定新选择('扫描')列的先前传输的数据7 U8 q2 w2 I$ y8 j+ b* _# C8 t+ ^
- // - 设置并启动SPI2 DMA流来传输下一列的数据' i0 B1 ^4 y+ b( }( s
- // 所有这些都应该在BLANK信号(TIM4 OC1)高的窗口内执行(而不是完整的SPI传输)。
" d( ]$ V5 h v4 b7 c( [2 O- W
$ l5 ?: f$ e. C8 K- x" o5 e- // 检查所生成的中断是否为OC1更新. s) z9 O- i6 n. E' A: W
- if(TIM_GetFlagStatus(TIM4,TIM_IT_CC1))- u5 S) e& v' ]
- { Y* A% f8 J+ j3 s, x
- // 清除TIM4 CC1中断位6 R5 \, Z7 S+ q3 p( z6 p, q& M# X& z, I
- TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);
: w+ T/ I' ~ W) M - ( Y8 d- i2 l; H1 G
- // 仅在向下计数时执行事件(这确保在空白脉冲内触发XLAT脉冲、扫描更新和SPI传输)
6 S( u# k$ P" M- w8 x/ S - if(TIM4->CR1 & TIM_CR1_DIR)3 c7 X' ]1 j5 ^5 Q6 _, h; L
- {7 B: U5 W {. |6 }2 G5 F$ x2 u9 V
- // 检查是否需要'SCAN'更新(XLAT脉冲,扫描切换,下一次传输触发): J: a/ v" k( ?- `/ l* K
- if(dispBlankCycleCnt++ >= DISP_BLANK_CYCLE_LIMIT)$ _; g6 [% ^8 U1 Y" |! @" r; N# H9 t
- {
9 w' _& @! ^4 J- I - GPIO_SetBits(GPIO_XLAT_PORT, GPIO_XLAT_PIN); // 设置XLAT引脚
, \; A& ^, m) \ - dispBlankCycleCnt = 0; // 重置计数器+ q( o P( f& M& v7 L
- $ }$ d( e% ~7 D+ z) w7 @' B
- // 确定当前列,并相应地移动7 j# Q5 x# Z) Z4 A6 m; s) w
- if(dispCurrentCol)
' b# e p& A0 Z( f9 A - {
# z* X% N4 |0 x; P9 j( N - dispCurrentCol = 0; // 更改为“0”列
2 U( X& v. J7 h( b! U" @- v - GPIO_SetBits(GPIO_SCAN_PORT, GPIO_SCAN_PIN); // 设置扫描引脚(注意列0是逻辑高位,列1是逻辑低位)" J/ U/ K0 a/ Q
- DMA1_Stream4->M0AR = (uint32_t)&dispData1; // Send *next*列的数据(由于当前列现在是'0',因此将发送(针对下一个循环) X9 q- m9 E- F9 B& T! c+ _. {
- }
$ O g- I' [& k, S% Z e) Y - else+ P( |/ P" k: f; ?
- {
0 F+ y# ]& E% a - dispCurrentCol = 1; // 更改为“1”列
1 t; P8 F0 d2 `. x4 D4 [; E - GPIO_ResetBits(GPIO_SCAN_PORT, GPIO_SCAN_PIN); // 重置扫描引脚(注意列0是逻辑高位,列1是逻辑低位)1 u1 v$ ^5 b/ ^
- DMA1_Stream4->M0AR = (uint32_t)&dispData0; // Send *next*列的数据(由于当前列现在是'1',因此发送了(对于下一个循环)& T c! K% u) W2 z% k7 e
- }
* g) ^, h, W9 _5 C; f - . ^1 I+ D6 m( L' w+ l" _9 n
- GPIO_ResetBits(GPIO_XLAT_PORT, GPIO_XLAT_PIN); // 清除XLAT引脚) @! w/ {4 g2 P
- % l3 ] T) ]5 U& ^! ]
- //Trigger the next transfer
- X* D8 d2 c+ y+ }5 v - SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE); // 启用DMA传输请求3 S0 [$ @$ a/ J1 Z/ c' p/ f; }! q, Y
- DMA_Cmd(DMA1_Stream4, ENABLE); // 启用分配给SPI2的DMA流
7 C: f3 ?8 z/ i2 M) j& ~$ ` | - }
6 D2 N) q' K( f7 L" K - }/ w S8 H: J/ N/ Y2 J
- }
* Z1 T ?4 g z. |( ]9 l e4 s - }
0 H$ q7 Z6 I- G) D - % M6 W5 a) T: O9 t2 S
- void DMA1_Stream4_IRQHandler(void)7 C# k* Z7 d5 K- M% _, Q$ f2 `
- {
" W: b- K# H- o8 a0 c: Z' M - // 检查是否设置了传输完成中断标志
& G4 u, n' `/ |' z - if(DMA_GetITStatus(DMA1_Stream4, DMA_IT_TCIF4) == SET)
0 @' r* E2 p$ m, W# _ - {
4 Z+ P" z& l1 r4 Y3 {+ k& i - // 清除DMA1 Stream4传输完成标志% N1 k8 D3 z& s1 A+ G9 ?' Q
- DMA_ClearITPendingBit(DMA1_Stream4, DMA_IT_TCIF4);
$ b* V+ w/ o4 |# l& E - }
) v6 B4 U; ^0 p1 d$ e7 _ - }/ V# ^9 n; |" w6 B/ @: g* ~
复制代码 DMA ISR目前没有被使用(我确实打算用它做一些无关的事情),但TIM4 ISR基本上控制了整个显示。+ l2 e- ^1 b: n0 J7 X) i. y
空白脉冲的上升边缘(有效地)触发中断。在确定正确的ISR触发了事件之后,使用TIM_CR1_DIR位检查计数器是否正在减少计数。这确保了我们只在BLANK脉冲的上升边缘执行以下任务。9 x; e6 _/ h! W9 i3 h: m
每当ISR运行时,我们增加一个计数器,如果这个计数器超过扫描显示所需的数量,我们就使用XLAT信号锁存前面的数据,切换扫描信号,并传输下一个数据(在disdata0[]或disdata1[]数组中找到)。
$ ]( z% m' ?. P$ B. F在dis_blank_cycle_limit中计算扫描前等待的空循环数,其中考虑到:
F( P9 \: ?$ s1 I z% ?1 b2 T* l& H% m& @1 s
" h% M/ G2 _5 f8 N9 tTLC5940_GSCLK_FREQ -灰度时钟频率
$ R/ ] ]' i/ u. u) mTLC5940_GSCLK_COUNTS + TLC5940_BLANK_COUNT——上升的空白边之间的GSCLK脉冲数
$ Q, G6 b" L% Iexpec_scan_freq -我们希望扫描数组的频率(此处设置为200Hz)
5 Q3 M) z5 a9 I- i: ?/ e现在,更新disdatax[]数组中的数据将改变led上显示的内容。, P, i& H/ d: M: e$ W
GSCLK频率为1MHz,扫描频率为200Hz,我没有明显的LED闪烁,即使我听到人们谈论使用>5MHz来避免它与他们的设置。9 c# L: q7 m6 T* c
' d' \# F: M7 ^
4 m& |. k2 ~3 f( R! V7 oLogic Analysis6 _, m) f7 ^5 U' d6 ~4 p5 I: d
我在STM32F407输出和TLC5940显示板输入之间附加了一个逻辑分析器,如下所示:
# T9 X: u6 l. x现在我们知道GSCLK周期是预期的,我们可以研究空白时间来确定正在以8位分辨率记录的灰度数据。' _& E- M {/ a. R% N5 @* D6 f
下降和上升空白边之间的时间是305.6 - 49.6 = 256us,这是预期的。9 a6 f* x l( S) w% v
我还调查了一个更接近的水平,以检查信号的相位是正确的2^8计数。
) |8 J6 c1 R" M6 y" y6 {' d3 i1 X最后,检查扫描宽度,我们可以看到一个列启用了2.445ms。1 ]" B2 o2 `" s5 N1 X
即扫描速率为409Hz;考虑到2。5us =不能被256us整除,这很好。$ y' J6 S! p! M( b/ `2 u5 ~
上面的捕获还显示,当空白计数达到极限时,相关的ISR锁存前面的数据,
8 f; z1 C3 i v+ F切换扫描行,然后触发SPI传输。
6 H* B# Y9 x5 w B9 M. m1 U. Q然后在锁定该数据之前计算所需的空白周期数(XLAT信号在2445ms光标所在的蓝色箭头右侧几乎不可见)。5 k+ P; x' Z, L8 X
$ }; I7 I( Y6 }5 m
3 V$ E- D' w. O8 z; m0 ^. {! b/ ]) |1 _! h" i3 |/ J) {
|