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