SPI (Serial Peripheral interface)是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。0 l8 }6 a; Z/ q# T$ b' o: x
t# g2 P. P' B# C8 w/ ^" r$ Y$ w
! p9 d; M& T( i1 j: `$ u9 X3 Z' s( N5 n9 ^/ Y/ C; j
今天分享下,基于STM32F103C8T6工控板上的两个SPI接口进行互相通讯,其中SPI1作为主机,SPI2作为从机。$ Q8 W9 ]+ k) `4 K
3 ?- F; _* i/ Z: z, x! Y9 P
硬件部分6 z8 X$ T0 Z( T, W: W
1)某宝网上购买的STM32F103C8T6工控板,价格50¥左右;" Q" I# i3 |$ u
2)某宝网上购买的232转USB数据线(如下图所示),价格15¥左右;
% M& p5 w# M3 A" `3)杜邦线若干,价格几乎为0¥。8 j3 ^; i0 K( f- v2 L: X2 Z" m# ~3 [
7 b9 Y! j' E3 t+ [: Z2 y, o* e( f3 u
硬件连接1 o2 D2 |7 @6 v
由原理图(如下所示)可知,将PA5与PB13连接,PA6与PB14连接,PA7与PB15连接即可。注意:SPI与串口的连接不同,串口的RX引脚接另一个串口TX引脚,而两个SPI连接是同名字的相连!
+ U8 y8 v7 Q: h3 D
8 p4 x \% ^+ w8 l; V( d ]- B, R
/ ?% U- d$ B( R/ w; L7 K! V( P3 x8 d* s6 M4 T; M$ T5 y7 u
! K" j" m* b" ]& ?% e$ A
% r( l, M0 E( L2 p! V
- ]% v; l* p7 V9 O7 o6 f# Z部分代码
5 z2 n' }4 O8 |1.spi.h头文件
. [- w: P2 k% i8 o8 ?- /*** k% [9 U% i3 U: }' K8 {
- ******************************** STM32F10x *********************************( T5 s2 _- w7 O
- * @文件名称: spi.h
. M/ x; D+ N* C/ o( s - * @作者名称: 闲人Ne
3 a1 ?. }% E5 I i. s; Y& b - * @摘要简述: SPI头文件! {4 A/ _# h" g9 P% ?! w
- ******************************************************************************/
" {# `: K6 f: L7 }" C% J - #ifndef __SPI_H# B" B' `& k5 Z3 o, a
- #define __SPI_H" G2 } x: m7 ]% x1 x7 [$ q
- /* 包含的头文件 ---------------------------------------------------------------*/
/ b; `# C1 E. {. d7 [3 q - #include "stm32f10x.h"
0 L" k+ e& D' C' [+ H3 B - /* 函数申明 -------------------------------------------------------------------*/
) W Z9 t1 W# ~* o' g3 E, n2 E3 [, y( a - void SPI1_Init(void);
( d4 D/ f" s0 L9 T C* e% j - void SPI2_Init(void);
7 L2 r/ m3 m! H7 w0 L' @2 b - u8 SPI1_ReadWriteByte(u8 TxData);
% x$ W& @; n2 d/ i: f - u8 SPI2_ReadWriteByte(u8 TxData);
3 J0 l: n/ N; v' p6 R - #endif /* __SPI_H */) A* M8 E" @1 s+ M* t
- /****** Copyright (C)2021 闲人Ne. All Rights Reserved ****** END OF FILE *******/
复制代码 " \; e4 u0 [( o* C7 a% d
2.spi.c源文件
9 V! Q# E7 T, ^1 x F2 {- /**
5 M9 \3 v* h7 `! K, I - ******************************** STM32F10x *********************************% q+ d' \5 z9 @" u: U& w# T _
- * @文件名称: spi.c
9 S, ~1 S3 g/ `) w% b8 T3 \) k - * @作者名称: 闲人Ne
1 d+ b/ Y9 W% j2 N* ?: A/ a* w9 L - * @摘要简述: SPI头文件9 A! j$ h9 D& |0 b
- ******************************************************************************/
9 \/ L! E) ^6 b. b6 R+ I2 ?; j - /* 包含的头文件 ---------------------------------------------------------------*/
! `* d4 a, P# g. m - #include "spi.h"
9 q( W9 A) A0 G3 T9 v e - /**************************************************1 ~" c3 g4 V% z
- 函数名称:SPI1_Init()3 a0 `$ Q, q: Z/ C& Q- D
- 函数功能:SPI1初始化函数:配置成主机模式
/ [- m, w6 Y/ \0 K - 入口参数:无* n+ ]7 S; S7 A9 @& F$ V# e) S
- 返回参数:无) N/ t" r* D; w2 H6 Z' Z" r0 H2 Z" b
- 开发作者:闲人Ne; q# P) I; O5 Y6 q! x- [
- ***************************************************/* f2 r% t \4 `- Y [- Y6 s- G7 P% e
- void SPI1_Init(void)( |" g9 B' S% p" ?9 l
- {
' v2 ~" F+ [- W1 f% v - GPIO_InitTypeDef GPIO_InitStruct;
3 i6 m, p- o; A" M' \2 E" ? - SPI_InitTypeDef SPI_InitStruct;
0 E8 [* ^4 u' F. s/ j - RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); // GPIOA时钟使能,选择SPI1,对应PA4,PA5,PA6,PA7
0 `# E R6 o0 g0 L - RCC_APB2PeriphClockCmd( RCC_APB2Periph_SPI1, ENABLE ); // SPI1时钟使能
; p2 ~4 b1 s0 C" x - // 初始化GPIOA,PA5/6/7都设置复用推挽输出AF_PP
! c1 q8 \- n* h7 p% K - GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;; C E. H( y5 I# F! t
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出AF_PP3 L; X5 n+ _1 E" I( N
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
$ K$ t7 `* `4 H- M( B# v1 K5 N - GPIO_Init(GPIOA, &GPIO_InitStruct);0 \ b$ F2 {( z% y
- GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7); // PA5/6/7置高电平) E7 W9 c- A- P; ~" D' j5 D
- // 初始化SPI函数9 k- [ f6 Q; O+ M; n
- SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 设置SPI单向或双向的数据模式:SPI设置为双线双向全双工
6 ^ R8 ?9 f3 D - SPI_InitStruct.SPI_Mode = SPI_Mode_Master; // SPI1设为主机
3 l9 ~4 h# b% w8 Z9 A0 U3 K - SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // 针对SPI_CR1寄存器的DFF位,设置数据帧大小为8位
" c: E, q+ {7 P8 W& i8 P+ T - SPI_InitStruct.SPI_CPOL = SPI_CPOL_High; // 针对SPI_CR1寄存器的CPOL位,串行同步时钟的空闲状态为高电平. y; A0 u1 i* u' ^
- SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; // 针对SPI_CR1寄存器的CPHA位,串行同步时钟的第二个跳变沿(即上升沿)数据被采样
% l: i3 T, {1 D- c. p0 B F - SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; // 针对SPI_CR1寄存器的SSM位,NSS信号由软件(使用SSI位)管理9 q! \ M- V* R( l2 R
- SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // 针对SPI_CR1寄存器的BR位,波特率预分频值为256,最低速率 8 W$ ~" C/ v! K( k
- SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; // 针对SPI_CR1寄存器的LSBFIRST位,数据传输从MSB位开始/ t4 ?8 j5 J/ [' B/ W7 R1 G5 I* M
- SPI_InitStruct.SPI_CRCPolynomial = 7; // 针对SPI_CRCPR寄存器的CRCPOLY位,设为0x0007,为复位值4 K6 X P. U/ T% k: M( O
- SPI_Init(SPI1, &SPI_InitStruct); - N8 ~" |& D0 d$ B; R
- SPI_Cmd(SPI1, ENABLE); // 使能SPI外设
. {+ [1 _2 b% }) b/ P2 _ - }
7 ?; w' X* b+ U% z5 H Z3 N - /**************************************************
8 e0 k/ W O) k) G8 \5 t - 函数名称:u8 SPI1_ReadWriteByte(u8 TxData)
2 x. @6 H2 |4 G4 @+ ~ - 函数功能:SPI1读写一个字节函数7 a; ~3 W/ |5 \" a3 w
- 入口参数:TxData:要写入的字节
7 ^7 y6 ]5 V% T9 u6 M- Z - 返回参数:读取到的字节$ i7 M( _3 G, ]6 } F* i
- 开发作者:闲人Ne
6 _' d4 B, ^6 ]* \/ b3 T% } - ***************************************************/
) M" d2 q" }5 o" C - u8 SPI1_ReadWriteByte(u8 TxData)& J/ L' |# V( Q; J. X: v- U
- {
5 o" H0 W1 f4 X - u8 retry=0; 8 e2 G1 M' ]. C. o! Y
- // 检查SPI_SR寄存器的TXE位(发送缓冲为空),其值0时为非空,1时为空
8 ?! ?4 S$ H, E$ S% `& ]" k - while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) 0 j/ U ?# g2 Z
- {
; q8 c+ y; f6 D; K9 U( T8 j( J( }- e - retry++; // 发送缓冲为空时,retry++
8 D# v' S! l# W8 f0 Z, } - if(retry>200)return 0;# ^$ V: k/ D9 K( r
- } , V4 p m* ^, P0 N- b5 [- {% O
- SPI_I2S_SendData(SPI1, TxData); // 通过外设SPI2发送一个数据
3 x) [/ c7 e, F8 I) n6 ~/ x - retry=0;/ N5 x- n7 h+ `
- // 检查SPI_SR寄存器的RXNE位(接收缓冲为空),其值0时为空,1时为非空. n0 a' V: v4 ~7 O& s! a* [1 @3 t6 Q
- while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) / J, t3 j, b t Y
- {
9 c9 R$ d& m, [2 i6 t/ P - retry++; // 当接收缓冲为非空时,retry++
% k! M, Z! e/ t/ ~6 S3 L& T - if(retry>200)return 0;4 ]( a* ~" b( ]( F" r
- }
9 H, _' E! i* l: J, N - return SPI_I2S_ReceiveData(SPI1); // 返回通过SPIx最近接收的数据 % n# O" C5 v% ~" n2 Y
- }. X [6 g8 o) r% L6 X, {1 Y
- /**************************************************
8 Y8 Q4 q+ o! p - 函数名称:SPI2_Init()) Y! O, v& T4 x/ s! [. g/ m! e: p* n
- 函数功能:SPI2初始化函数:配置成从机模式
9 L) n, M$ M$ ?7 h - 入口参数:无
& T# f" \5 y: K' D$ ` - 返回参数:无/ t5 ^7 i- `: a1 J" E
- 开发作者:闲人Ne/ R5 F9 H4 d+ s" I7 A
- ***************************************************/- c$ O. H# M6 g
- void SPI2_Init(void)
0 Y+ B7 C" C. o9 D - {
1 b5 ^4 Q& u' I9 r7 e: A& T - GPIO_InitTypeDef GPIO_InitStruct;
; x4 _2 F9 K H9 T$ P j - SPI_InitTypeDef SPI_InitStruct; ; a! }4 F9 W7 e0 \4 o
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); // GPIOB时钟使能,选择SPI2,对应PB12,PB13,PB14,PB15
0 e, G7 Z$ _1 T1 O - RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE ); // SPI2时钟使能 + L% R+ j8 i- r2 k1 N4 t
- // 初始化GPIOB,PB13/14/15都设置复用推挽输出AF_PP,PB14对应MISO,最好设为带上拉输入" _7 b" _ y2 @+ ]( c4 I; U
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;4 A$ C8 _9 @$ V+ B0 K- [5 X1 ~0 e
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出AF_PP
3 a$ J' _7 |' `) t! H( i - GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;- k: h5 \6 p5 y. P
- GPIO_Init(GPIOB, &GPIO_InitStruct);% \. T( F1 g) U+ z2 G8 Z7 i; M+ q
- GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); // PB13/14/15置高电平1 {, J5 a5 U- y2 b! y
- // 初始化SPI函数6 `; L6 L0 R! r. G
- SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 设置SPI单向或双向的数据模式:SPI设置为双线双向全双工4 q4 r7 v# y5 V
- SPI_InitStruct.SPI_Mode = SPI_Mode_Slave; // SPI2设为从机
: I- M1 t, s" | - SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // 针对SPI_CR1寄存器的DFF位,设置数据帧大小为8位
9 M* @" ?8 m8 `9 n6 L - SPI_InitStruct.SPI_CPOL = SPI_CPOL_High; // 针对SPI_CR1寄存器的CPOL位,串行同步时钟的空闲状态为高电平
$ g. i9 c9 |, o: S' A - SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; // 针对SPI_CR1寄存器的CPHA位,串行同步时钟的第二个跳变沿(即上升沿)数据被采样/ ?# w8 V# m# D7 B2 S$ c! a
- SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; // 针对SPI_CR1寄存器的SSM位,NSS信号由软件(使用SSI位)管理
7 i2 E& @8 O# V/ G" l3 x! i - SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // 针对SPI_CR1寄存器的BR位,波特率预分频值为256,最低速率
6 A& T9 H, f% ]6 U9 G2 I% U# W - SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; // 针对SPI_CR1寄存器的LSBFIRST位,数据传输从MSB位开始
6 o) e+ q* @: h' y: Q3 f - SPI_InitStruct.SPI_CRCPolynomial = 7; // 针对SPI_CRCPR寄存器的CRCPOLY位,设为0x0007,为复位值
: z- R2 u9 M/ }0 s, f2 h - SPI_Init(SPI2, &SPI_InitStruct);
?7 Y: _( Q5 F5 B4 s9 X - SPI_Cmd(SPI2, ENABLE); // 使能SPI外设 0 L7 {* I. a' W/ e
- } * D! n& c0 d, Q, e3 o& F% S; r
- /**************************************************
# L7 y6 c# i6 P8 z' s) K - 函数名称:u8 SPI1_ReadWriteByte(u8 TxData): ]- g7 m& V7 ?$ n ~; e* Q
- 函数功能:SPI1读写一个字节函数
2 Q: y" I% S( y - 入口参数:TxData:要写入的字节
$ h, |& Y4 m; ^6 Z( Q: u O - 返回参数:读取到的字节
- Y8 _: A, K4 m4 S2 D0 \9 f - 开发作者:闲人Ne
; `$ Y* o4 x: I6 L- N \2 C, V - ***************************************************/. n2 n' G+ u0 w) ^1 N5 F7 D5 @( b- Y5 S
- u8 SPI2_ReadWriteByte(u8 TxData) w1 |) Q3 r' A2 \" v' J& I
- {
% z1 ]# m/ y2 e1 r! n1 Z D - u8 retry=0; # C" ]: W/ \6 @
- // 检查SPI_SR寄存器的TXE位(发送缓冲为空),其值0时为非空,1时为空
+ A: @4 o1 R: \6 I3 K - while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
" e6 Q# x% K! ^) ?8 p3 J9 x7 \ - {7 [6 Q- u. {3 Z2 s. |1 c
- retry++; // 发送缓冲为空时,retry++
' V/ r3 _+ r# I7 ?. K- H6 w - if(retry>200)return 0;) D7 ~3 t; _1 Z4 x5 M/ R" o
- }
0 v0 L9 L4 {3 z( W - SPI_I2S_SendData(SPI2, TxData); // 通过外设SPI2发送一个数据, @6 k+ D& M* z; z5 c
- retry=0;0 I7 i8 P( e: o# M' l+ C% U
- // 检查SPI_SR寄存器的RXNE位(接收缓冲为空),其值0时为空,1时为非空
, F: B& c5 A1 `: a - while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)
9 V& N" Z3 f; H# ^- {& z. d2 i - {. n/ d2 V8 A) g" K2 N
- retry++; // 当接收缓冲为非空时,retry++
, i: C1 u4 e1 P. |% o9 U - if(retry>200)return 0;
2 C- M X/ U: h9 j" Z% Y0 F6 M% | - }
0 F2 K) l& d$ b: o7 R) o/ j - return SPI_I2S_ReceiveData(SPI2); // 返回通过SPIx最近接收的数据
; s' ]2 S8 q& ?2 f( A& | - }% b+ A* z" ?' z6 f/ A$ a# ?6 [
- /****** Copyright (C)2021 闲人Ne. All Rights Reserved ****** END OF FILE *******/
复制代码 & T2 a! x% Z& U1 n5 `3 w& g' y; _; `
3.main.c主函数
7 h+ N% ?! d! T8 \- /**
+ W8 k" W, N7 G# M6 o& m( V - ******************************** STM32F10x *********************************" l3 K" Z p( _0 F1 {6 O
- * @文件名称: main.c% `. L3 b0 D7 `) t& K
- * @作者名称: 闲人Ne8 l% O' ]% T4 y5 y
- * @库版本号: V3.5.0
% I3 i/ @+ t' p) s - * @工程版本: V1.0.0
% z V' j' L8 r. n - * @开发日期: 2021年02月17日
6 F5 L" x1 ^% S0 q! z; U - * @摘要简述: 主函数
: C; P. G# j3 U5 q4 b - ******************************************************************************/4 U3 J) A# m3 Q# H) }7 ` \
- /* 包含的头文件 ---------------------------------------------------------------*/& H* M4 O# V4 L2 g
- #include "led.h"
2 ]0 D1 p/ X/ ]& ]- S, V - #include "spi.h"9 @0 ?$ T' a5 w# x4 e; e
- #include "serial_communication.h"6 Z5 r% S8 T. x0 x
- #include "sys.h"
" g, T, A8 s* X/ A2 ?' M& h$ ` - #include "delay.h"
9 Z0 B+ T* c8 D - #include "nvic_configuration.h"
& \, F4 I( G" x6 Y! ^1 v2 b9 z - /************************************************
0 Y- D' Z1 v5 k( u" A4 s' u - 函数名称:int main()) f/ T% _# [9 i2 Y W+ q+ B
- 函数功能:主函数入口( t4 [4 \2 l: d: q2 y+ x
- 入口参数:无
/ u1 b8 x" Y% @4 Q/ l4 B2 q& \# A - 返回参数:int+ x) J9 b+ X2 \* @' E) ]
- 开发作者:闲人Ne7 n2 R- _7 j0 k; L
- *************************************************/ L# g( n4 M& T2 R6 P# I/ k6 X( J
- u8 spi1_sand_data=0;
2 T: C ]( ?8 v" [7 Y4 _: w - u8 spi2_sand_data=100;
1 @; m3 S" x4 M% G5 j+ I( C - u8 spi2_receive_data;3 [% S) G4 A! Y- N+ c
- u8 spi1_receive_data;
" ^6 p9 m% D, q - int main(void)5 o5 {% R3 ]! {' D8 O2 x; Z: a
- {' j+ S) w; l" i0 l; z3 E5 c5 E4 M
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); ( b& G9 Q ?) b$ j& Z
- delay_init();" Z1 e% H. g. v
- SPI1_Init();% O- \2 c: @) s2 ^. v; E$ A C
- SPI2_Init();
2 f5 Q: N4 b' D/ M# i) h - My_USART1_Init();
# V( ?5 z4 K) s0 ~ - D1=1;
; K; I$ b0 v X, J* f - while(1), L4 B/ X9 l/ G. O6 w1 N# S; m
- {
1 o. H( M8 Z, Z D - spi1_receive_data=SPI1_ReadWriteByte(spi1_sand_data);
) n. V3 b' x1 K8 f - printf("\nSP1接收的数据为:%d\r\n",spi1_receive_data);
, R* ]- Z: p; C. {3 `; W - spi2_receive_data=SPI2_ReadWriteByte(spi2_sand_data); + p1 u0 t1 O4 e$ l8 F
- printf("\nSP2接收的数据为:%d\r\n",spi2_receive_data); 4 |1 }; @! p- k* d2 K; s
- spi1_sand_data++; spi2_sand_data++;4 ~7 Q' g; S4 ~5 _5 @3 X
- delay_ms(500);
8 o! t* g; |0 q3 U. d+ H6 Y - }
! \( o* S# }& j4 T/ Q/ t - }
% ~8 L5 v3 J9 n l2 i+ K$ q - /****** Copyright (C)2021 闲人Ne. All Rights Reserved ****** END OF FILE *******/
复制代码 # r. M( Q8 i5 p1 l' f
实验结果
* t7 E0 j/ V4 c& P/ s- `: ?4 ?4 U- G- k5 ~) E9 P" a% _
4 b- l- k" s) d' M$ z9 Y8 H实验结果完全符合逻辑,通讯成功!9 S* C4 \" V- Z) I' h
& K7 b5 V% M c' h: s7 n经验分享
& ?* J i( Z: O, t M' H* ~ a1 _2 B0 k6 g0 B
该程序可作为双芯片之间SPI通讯的参考代码。
% y. B* X. F- [: m————————————————
4 @# ^% X1 j/ _& n; g# B1 h版权声明:天亮继续睡
' W0 i4 d; U' x- E" x% C0 n' H' S) i; Z
6 h# w) }8 C. s" \5 f- I |