一、前言
+ Q6 Q$ _: n: I$ f最近因为需要读取传感器数据,需要单片机发送命令,传感器返回24位数据,因为使用SPI传输数据,虽然命令只有8位,但是必须发送24位数据才能获得传感器的24位数据。自己在这里困了很久,所以写这篇文章记录一下,也给后面需要的朋友一点帮助。9 d9 x/ R& o; p' l
我的目的就是消除或者减小每帧数据之间的发送间隔。# I+ c7 G- f* h& Y4 c: G" r
二、GPIO配置
7 c9 ?$ P N7 d, ^6 ^7 F' @- GPIO_InitTypeDef GPIO_InitStructure;! ^( k# q3 e* |7 ?; [+ D3 N
- /* 使能AHB时钟 */
- U; x$ ^! G; ~! |4 I ~ - RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
/ z' P) a; N& |* K( Z- N! o - RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
4 q( e& d2 M$ @1 ~; a0 s - /*定义 SPI复用引脚 */
" C% t5 D: V# e, P% |, Z4 h# e - GPIO_InitStructure.GPIO_Pin = PIN_SPI_SCK | PIN_SPI_MISO | . M# r2 J9 T* D2 ?% h6 o# l7 o+ E
- PIN_SPI_MOSI;4 R8 d3 t* r( V5 s+ ?
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用模式0 t1 C, K' L8 \& F1 U* ]1 G+ j! U. W
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //高速输出
& h- Z% g# q z- j - GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推完输出
. t; H6 P6 s8 H% v - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
) N" s t3 e `! B - GPIO_Init(PORT_SPI_SCK, &GPIO_InitStructure);: u" S5 R# U9 B( M
- & X3 V: f* d' h0 f. M, Y0 S
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_0);
2 _, y% c6 Q6 q, I0 |5 m! \ - GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_0);
8 F. }! I7 C* }: H& Q6 F+ [! V - GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_0);* F! ^/ a2 e9 A8 A- h6 _: `6 J5 v
- /* 片选CS */- y, \+ @# q* P# j' O5 W* ^
- GPIO_InitStructure.GPIO_Pin = PIN_SPI_CS;
3 c3 L; Z/ s5 z5 Z0 z - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出模式% D- w% v- y) r3 h3 a/ |
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推完输出
6 c) |& A( h* n# v+ t5 c - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //高速输出
4 q) N# ?% p" ]! h6 d: o! }, H2 f - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉4 V; r( b/ y8 |) j! o
- GPIO_Init(PORT_SPI_CS, &GPIO_InitStructure);+ |, @" ~* R7 [- y8 R
复制代码 三、SPI配置2 w, ^5 T" c& ^; n6 X& s f
- /* SPI 初始化定义 */$ I3 C, o2 [0 v8 H
- SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI设置为双线双向全双工
2 u7 y; x* b- ^/ C& p - SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主 SPI
1 M/ q0 a8 `0 @ - SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //SPI发送接收 8 位帧结构: _; H; t+ H2 _4 f* F1 M
- SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟悬空低* b/ w) a. l& T% p/ n0 X& O- ~
- SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿! \; l+ {- Q; x D
- SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //软件控制 NSS 信号
) Q# h3 P; E: A6 o" S& q1 ? - SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //波特率预分频值为8
/ H: ^& O8 `+ u9 y! W, |6 E - SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始- u+ _" Q* W/ V1 P
- SPI_InitStructure.SPI_CRCPolynomial = 7; //定义了用于 CRC值计算的多项式
( r' u: `) a1 @ - SPI_Init(SPI1, &SPI_InitStructure);
% o+ I/ x- h r/ I0 \) B - 6 z2 ]: {, G, w) P* a
- SPI_RxFIFOThresholdConfig(SPI1, SPI_RxFIFOThreshold_QF);
% s" x: M+ k6 g9 k1 M* R& ~# B1 X - SPI_Cmd(SPI1, ENABLE);
复制代码 此处有一点需要注意哦,STM32F0区别于STM32F1系列,SPI初始化后需要初始化RxFIFO:SPI_RxFIFOThresholdConfig(SPI1, SPI_RxFIFOThreshold_QF);- r: Y& y" o1 e; s7 K- T- A9 W
至于结构体参数的初始化参数,根据自己项目改。我里使用的是内部晶振超频56M,8分频获取7M的SPI时钟。(为了获取最大传输速度,别问为什么,就是需要这么干,之前在群里讨论我的时钟速度为什么上不去,结果很多人问我为什么要那么高的时钟,追求速度?什么东西要那么高的速度?。。。。。。。。我。。。。对了,顺便说一下遇到的坑,我有示波器和逻辑分析仪,我一直用逻辑分析仪,时钟怎么都上不去,一直是2M,真的是找遍了原因,最后是逻辑分析仪的速度设低了,让你手贱!让你手贱!!)3 Y0 V$ z* A( _9 ~
四、SPI发送接收(非DMA)
" V4 j) w/ @6 Q4 b& _/ G9 {- uint32_t SPI_WriteRead(void)5 [+ U- D k; y9 g, O9 ~# H0 ^
- {5 p3 f0 u$ d. R4 G5 M5 w
- uint16_t num1,num2,num3;
9 a& |7 [4 h: g - uint32_t AngelData;
7 C$ \2 I. }. q8 i/ w/ T& m - GPIO_ResetBits(GPIOA, GPIO_Pin_15);//拉低片选, z- d% p& D5 @
- *((uint8_t*)&(SPI1->DR) + 1 ) = 0x3F;//发送指令
/ g6 w2 _: Q( z. i: Z% s% g8 ^ - num1 = SPI1->DR; //读SPI, Z$ f c# d& T- b/ ?
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET); 3 x' l: C; V. g& J& E) i
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_BSY) == RESET);
% l$ B+ Q0 F, q7 {; @- d' h* C4 |; g/ k
/ g/ F: g F* _8 m o! T Q- *((uint8_t*)&(SPI1->DR) + 1 ) = 0xFF;//发送无关数据,为了获取返回数据, S: |8 D2 P4 O: a3 P; u
- num2 = SPI1->DR;//读SPI/ X' C6 \1 T* ~" h
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
% O) l- n5 a$ q1 L% P - while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_BSY) == RESET);( v& e; P5 |" I0 L0 N& F: a
- ! ~5 X M; Q+ B
- *((uint8_t*)&(SPI1->DR) + 1 ) = 0xFF;//发送无关数据,为了获取返回数据
1 _# k/ y5 e$ t @ - num3 = SPI1->DR;//读SPI, a7 h' ?1 v, W \
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);+ D9 S/ g2 @+ k- q4 g8 K, H: a/ O
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_BSY) == RESET);
0 U4 i! n/ S7 Z& l% Y+ E& X - GPIO_SetBits(GPIOA, GPIO_Pin_15);//拉高片选
$ K6 P$ r& ^: b. m - AngelData = ((num2&0xFF)<<16 |(num3&0xFF)<<8 | (num1&0xFF));( |4 E6 _2 u5 j; @+ r( c
- return AngelData ;
7 y4 Q! _5 D' T; l' R! X; M5 o1 X - }
复制代码 说一下注意的点,STM32F0慎用while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);判断数据接收完整,非常容易卡死在这里面,可以使用忙标志判断,很好用,不然会提前拉高片选信号,导致数据不完整。
. |' O, h8 R, F; r: N如图,我发送24位数据,时钟却输出很多。因为DR寄存器是16位的,如果你直接SPI1->DR = 0xFF ; 这样的操作是不正确的,你的数据会变成0x00FF之后赋值给DR寄存器,也就是操作了16位,所以STM32会输出16个时钟脉冲" @4 K J* @( c: D
解决方法:0 d: k. q- ?. m- P4 }
我们先找到DR寄存器的地址,再用一个八位的指针指向这个地址,现在指向的是DR寄存器的开头,那么指针+1,指针指向了DR寄存器的低八位这时候给指针指向的地址赋值0xFF,那么这个字节就会放入DR低八位的空间内,而不是操作整个16位DR寄存器," N- x+ u3 _. |' U
((uint8_t)&(SPI1->DR) + 1 ) = 0xFF;
) m8 d3 z( i' Y$ ^; a' z% k经过上面的代码就已经可以获得24位数据,时钟也会连续,不会出现上面两张图片的问题,后面贴上DMA的代码。" g- ]: Q8 I2 F. _9 W( P8 B
五、SPI DMA配置
- E7 v8 o8 h+ u7 I# s- void MYDMA_TX_Config(DMA_Channel_TypeDef* DMA_CHx,uint32_t cpar,uint32_t cmar,uint16_t cndtr)4 m6 S# S/ Q' q# _5 I
- {
: e" y% S' ~: d- u# Y - DMA_InitTypeDef DMA_InitStructure;- R/ q, c/ d/ S7 [& x5 B* w; k- b
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
7 }7 b0 Y7 o# ?' g& ~' r - DMA_DeInit(DMA_CHx); //将DMA的通道3寄存器重设为缺省值8 L+ W! J. ?7 d) ~; T
- DMA1_MEM_LEN=cndtr;; Q S9 V; p4 T1 O# @- O
- DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设基地址
5 a! Y8 W j" G4 G+ Y/ f$ ]1 _8 P/ H - DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址) w, d: k F% t3 z- L
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
% _4 K% J- b& a' i, i! f% v - DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA缓存的大小: ~2 e0 U) S/ B' n6 d; i5 f$ q ?' Y* x
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变5 b1 l& q$ h7 |' b% A0 ]
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增$ \% Z# H/ {. e; \, {+ O
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
- G( i( i8 g; K& b2 Q5 k @3 r) q8 Z( C - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位1 e, X a2 J7 I
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式 P/ y+ h) [; ~+ R; j, u- T- w
- DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 3 h$ v0 h" H/ ^% E6 O
- DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
# _1 w5 V: ]/ D. I6 \2 j - DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道SPI_Tx_DMA_Channel所标识的寄存器
' ^% v3 W$ J$ t1 d7 u - }
4 V1 `3 `4 M6 _( N - //开启一次DMA传输
0 c2 l2 P6 o; |0 f% b; W - void MYDMA_TX_Enable(DMA_Channel_TypeDef*DMA_CHx)! ~' C" F3 s" h% x( c6 q% F
- {
. y3 v/ Z+ I- J4 d) B$ h) e - DMA_Cmd(DMA_CHx, DISABLE ); //关闭SPI TX DMA1 所指示的通道
9 H1 [6 A4 E% X2 ^/ O6 t7 ^% h% u - DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小1 J& s+ z' H0 Q* B z$ R# w( l5 ?
- DMA_Cmd(DMA_CHx, ENABLE); //使能SPI TX DMA1 所指示的通道
+ E8 J5 _6 G+ h8 \ r2 l - }
* a. J8 c0 C' B+ m+ z0 t - - z5 P2 d& s6 J! s, I- u
9 y4 [" M4 N+ c( C! _8 d- void MYDMA_RX_Confog(DMA_Channel_TypeDef* DMA_CHx,uint32_t cpar,uint32_t cmar,uint16_t cndtr)/ \0 G* ^% S/ s& V! u
- {
4 @2 i, p9 g7 m& e1 G( P - DMA_InitTypeDef DMA_InitStructure;
' O' A4 }( G$ D - RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输( `- c: n$ x" _, U, \
- 6 {0 d3 P9 q2 {: i. X
- DMA_DeInit(DMA_CHx); //将DMA的通道2寄存器重设为缺省值
, e! D% d- W4 _1 y, S& s7 |6 T8 Z0 O
6 i6 \# A4 i$ n1 ?! I$ e; ]& [) x- DMA1_MEM_LEN=cndtr;
' Y, R6 h% I/ \0 x" ]& g. p$ D - DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设基地址
' e! [( f& t" E5 I. V4 {8 }* _ - DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址
& B4 b% D M. @0 L/ @7 B - DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设到内存
9 {' Q' n- k* C2 P - DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA缓存的大小
- H1 r8 C+ o( ]9 T' a4 c- s& ~5 P* W - DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变9 e" p6 G+ V% S/ J# ~( z
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
/ n; P$ V- Q" `9 v8 O" i& ~ - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
: T( L# ?0 v% F3 N0 g+ b; @/ N - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位# z: e1 A: L" d: R
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
r3 N' C" _. R0 y$ m( Y - DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 * O a: J. x; ]1 G* W% U
- DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
2 p+ t" x: e! P9 } - DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
# w/ J/ C/ _; x* b1 C9 r) o" F - DMA_Cmd(DMA1_Channel2, ENABLE); //使能USART1 TX DMA1 所指示的通道
* y0 u) q* M8 @: o - }6 \" R6 S/ r r' I1 ~
- & i+ Q5 a o# H* X3 q6 l# {& B8 U/ w# b
- //开启一次DMA传输
- c, _, c( K; T+ g - void MYDMA_RX_Enable(DMA_Channel_TypeDef*DMA_CHx)6 U4 L: u$ k; q: w! s
- { . P! S3 \8 I0 N0 s
- DMA_Cmd(DMA_CHx, DISABLE ); //关闭SPI RX DMA1 所指示的通道
/ b2 k2 E: X: ^+ h' Z! y2 _ - DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小
+ x; R0 Q5 i) d2 b K - DMA_Cmd(DMA_CHx, ENABLE); //使能SPI RX DMA1 所指示的通道 6 F& w6 p3 z! s5 h: x* R$ p
- }
复制代码 六、SPI发送接收(DMA模式)& M ]& x$ g0 e6 O, g
- void SPI_DMA_WriteReadByte(void)
4 G! U# R+ ~4 `2 c; Y - {
5 F8 E- ]0 y9 y& @' Q5 f" E2 c6 U - GPIO_ResetBits(GPIOA, GPIO_Pin_15);//拉低片选 (放在此处为了节省0.5us的时间)& j5 x. S+ ]8 V4 z# C" h# E
- SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Tx, ENABLE);//SPI 发送DMA使能
! {" k; n2 Q* k - SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Rx, ENABLE);//SPI 接收DMA使能( v& j0 D1 b: h, Y( [1 h1 e B
- MYDMA_TX_Enable(DMA1_Channel3); //发送, x- D) O; A" I1 W5 t" H
- MYDMA_RX_Enable(DMA1_Channel2);//接收
4 s. M( o( d# J2 g. ` - if(DMA_GetFlagStatus(DMA1_FLAG_TC3) == RESET)
2 K$ t. z2 Q4 H1 }. V - {
]- ~, u- k/ @0 D$ R - DMA_ClearFlag(DMA1_FLAG_TC3);
& X4 C# Y7 F- `+ t+ f# e1 { - } , K! B; M1 i/ W U6 a6 d
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET); 2 ~" `5 p0 C+ Q* D2 s, A
- while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_BSY) == 1); //保证发送接收数据完整
6 s& \; }9 C8 i _ - GPIO_SetBits(GPIOA, GPIO_Pin_15);//拉低片选' T- Q2 b. m$ [8 Z# Z
- }
复制代码 2 X/ ]2 Y- p$ S! D7 \
/ @% C1 K* i* r# R: J, E9 q
& Z# p. I& Q, o3 _; ?; c6 r! E
. b! d3 X# C$ G: v7 Y4 V
9 V4 a6 I8 F$ P/ A9 O4 o- P9 v$ ] |