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