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