SPI (Serial Peripheral interface)是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
) ]6 Y$ N0 f1 c, |: f* N
3 w9 W7 ]6 ~+ b! X- H. y
& A% Y+ R+ A+ ~* Q$ b* O6 ^4 M+ N) f8 g y% M
今天分享下,基于STM32F103C8T6工控板上的两个SPI接口进行互相通讯,其中SPI1作为主机,SPI2作为从机。! e" I4 C, q& m. x- M2 O8 H* c
5 f+ ]' I# [2 Y0 x w
硬件部分
: r$ _* `3 W, ]1)某宝网上购买的STM32F103C8T6工控板,价格50¥左右;) u, z( g6 q. D/ D N
2)某宝网上购买的232转USB数据线(如下图所示),价格15¥左右;% J( ~; D- Q' p, R% r4 n
3)杜邦线若干,价格几乎为0¥。
5 |! J! H+ ^1 N. o. j5 @
- f; q8 _( ?$ e' o! j( v+ e
5 w' x7 H* ~0 v* W3 A
硬件连接- Q5 p: q* G- V( S* X+ x: L
由原理图(如下所示)可知,将PA5与PB13连接,PA6与PB14连接,PA7与PB15连接即可。注意:SPI与串口的连接不同,串口的RX引脚接另一个串口TX引脚,而两个SPI连接是同名字的相连!
! [* l' K5 D( y! _" n2 ]' T( l, d& ^4 p& J
2 ^9 n) o5 }& O" Q2 D* q ]% ]6 P4 a
) W( K0 Q* D# Z, s& V5 D5 `) L/ `. ]; n1 F% p* O8 L
, p U4 m1 u, k' O; U0 x部分代码
& {( }4 }' r$ }+ X# e1.spi.h头文件8 ^" p0 H5 b/ V; M, u7 u
- /**9 F3 H9 U9 d* l! u
- ******************************** STM32F10x *********************************
4 U2 ]! A& b0 ~ - * @文件名称: spi.h
- u5 @" S1 B) L1 A4 U2 H - * @作者名称: 闲人Ne5 M4 b) D8 g* k# o$ v, H8 Q1 }
- * @摘要简述: SPI头文件
% a$ Y1 _ K7 x0 X7 d8 w+ R% A2 r - ******************************************************************************/! e3 j6 w6 \/ u% @8 n
- #ifndef __SPI_H' ~$ q7 z" w: I$ r" N, B2 c+ D6 E
- #define __SPI_H0 d3 T1 K2 t, Y3 d. H! X
- /* 包含的头文件 ---------------------------------------------------------------*/$ N7 n+ n: Y+ N2 d3 s
- #include "stm32f10x.h"3 r& ], ?0 i6 B' B+ Y3 R
- /* 函数申明 -------------------------------------------------------------------*/2 O/ I1 `. v3 @. Q; i
- void SPI1_Init(void);
% G+ y/ A& ^/ h { - void SPI2_Init(void);
! P" }" n; |6 s4 q - u8 SPI1_ReadWriteByte(u8 TxData);2 v5 B6 j' Z7 d2 c/ w2 i; @
- u8 SPI2_ReadWriteByte(u8 TxData); ?2 I* _3 o! j E: z7 q" n J8 `
- #endif /* __SPI_H */
b' G7 _& S+ O: l- M! Q# G - /****** Copyright (C)2021 闲人Ne. All Rights Reserved ****** END OF FILE *******/
复制代码 7 z% H$ @& k3 J7 U4 `
2.spi.c源文件
& x4 e' y2 G9 Q/ c& H' _$ Q! \' N2 E- /**
9 S. w5 z! J1 l* U! Y% w) M - ******************************** STM32F10x *********************************
, z+ S9 s8 p! j) O( M( x - * @文件名称: spi.c9 D: r! j' D. J* g
- * @作者名称: 闲人Ne$ D% v: a1 V5 d5 l, B3 c1 s5 Z+ i1 }
- * @摘要简述: SPI头文件. ~6 V8 U; o6 r0 {5 I G4 d
- ******************************************************************************/
2 X- g) J: I0 U8 x; a - /* 包含的头文件 ---------------------------------------------------------------*/3 O5 L# l9 k1 `4 k4 n( @, _
- #include "spi.h"! Y( X3 h" y; y. N7 K
- /**************************************************# U5 [" H& @' g
- 函数名称:SPI1_Init()
1 _9 Z5 v c) o9 `. u$ ~ ~ - 函数功能:SPI1初始化函数:配置成主机模式
- e9 Z" ? c4 N7 {* f - 入口参数:无. R5 Z6 W b- g1 t
- 返回参数:无1 |2 |" ^' x' g- x6 C& v
- 开发作者:闲人Ne
# N! [$ t1 ^7 j/ Z4 J - ***************************************************/
. ~, ~) @7 ^/ f" g - void SPI1_Init(void)
3 O% K/ g# m- N f+ z% t+ Y - {5 P5 Q' T% u* \ ]0 M
- GPIO_InitTypeDef GPIO_InitStruct; - D3 s9 S4 R4 `
- SPI_InitTypeDef SPI_InitStruct;
9 E! I) j6 H8 E/ z& E - RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); // GPIOA时钟使能,选择SPI1,对应PA4,PA5,PA6,PA7
; _: s* b( t/ d+ {$ u- _ - RCC_APB2PeriphClockCmd( RCC_APB2Periph_SPI1, ENABLE ); // SPI1时钟使能 7 _& z, f9 L ^ \
- // 初始化GPIOA,PA5/6/7都设置复用推挽输出AF_PP
, \% z: A4 X) G# T' ? - GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
3 r( T! L. M, n4 I! U* p' J( G. H3 Q: w - GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出AF_PP& @( Y3 K, ]1 z* `
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;7 n k% ]" [) j% m( k: x! ^
- GPIO_Init(GPIOA, &GPIO_InitStruct);0 U3 k# c# Y, K
- GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7); // PA5/6/7置高电平; E8 D5 m G0 z# P
- // 初始化SPI函数
, K! @# A6 W& v+ D; T4 Z - SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 设置SPI单向或双向的数据模式:SPI设置为双线双向全双工
: O Z# Z1 ]6 L* j* C: o g) C - SPI_InitStruct.SPI_Mode = SPI_Mode_Master; // SPI1设为主机8 ]2 j2 Y& M# o8 b" U9 a
- SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // 针对SPI_CR1寄存器的DFF位,设置数据帧大小为8位( e S! k& c! b; R* [% `
- SPI_InitStruct.SPI_CPOL = SPI_CPOL_High; // 针对SPI_CR1寄存器的CPOL位,串行同步时钟的空闲状态为高电平
8 a6 ? r! O2 Z* K2 {3 s - SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; // 针对SPI_CR1寄存器的CPHA位,串行同步时钟的第二个跳变沿(即上升沿)数据被采样
( {% l' w7 y0 Z0 V8 I; y1 Q - SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; // 针对SPI_CR1寄存器的SSM位,NSS信号由软件(使用SSI位)管理
$ |) [( A6 N" Q - SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // 针对SPI_CR1寄存器的BR位,波特率预分频值为256,最低速率 ' h/ C2 @- r/ q
- SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; // 针对SPI_CR1寄存器的LSBFIRST位,数据传输从MSB位开始' V5 I1 `& O3 x* E
- SPI_InitStruct.SPI_CRCPolynomial = 7; // 针对SPI_CRCPR寄存器的CRCPOLY位,设为0x0007,为复位值' u; R: w! ]5 b( Y7 r) y
- SPI_Init(SPI1, &SPI_InitStruct); ' M' U# a( w7 @2 T t' C/ j# t$ |: g
- SPI_Cmd(SPI1, ENABLE); // 使能SPI外设 ( k5 Q( [" B# |, V( g
- } ) U* ~9 W; X+ G. l
- /**************************************************5 `+ I! G- }' |3 Q+ ^. N" Y1 G
- 函数名称:u8 SPI1_ReadWriteByte(u8 TxData)
0 O$ Q1 o& j' d8 Q - 函数功能:SPI1读写一个字节函数7 h+ }0 ^+ w& B
- 入口参数:TxData:要写入的字节# p& g0 D& t& ~; g4 T" f
- 返回参数:读取到的字节" l$ C; O! K" `* e, W
- 开发作者:闲人Ne
. _ ^; d/ a: r+ n' k( n8 h - ***************************************************/
& M( g) x# U6 E2 X, j - u8 SPI1_ReadWriteByte(u8 TxData)3 f7 L: ^+ o% E i
- { % P, p0 \3 r2 Y. L- u( k7 @
- u8 retry=0; , e4 x7 Y- O( n$ k# G
- // 检查SPI_SR寄存器的TXE位(发送缓冲为空),其值0时为非空,1时为空 + w4 M, r0 G; ?" \) l8 i. s
- while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) 4 R/ ?% y) M6 p9 j* z5 v
- {
; |! U! l, O7 O# r, r - retry++; // 发送缓冲为空时,retry++
: b# h& c; E+ E d! o+ Q$ ~ - if(retry>200)return 0;
/ s& G8 `7 L5 N5 F9 R - } : x1 L/ u- s! h8 e) c
- SPI_I2S_SendData(SPI1, TxData); // 通过外设SPI2发送一个数据
$ e: `- U$ a! Z2 h - retry=0;
* R: \. ^* E7 t - // 检查SPI_SR寄存器的RXNE位(接收缓冲为空),其值0时为空,1时为非空
2 Q7 o1 m. X+ }% F$ _: m) ` - while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)
& ~; L7 H5 O0 {- S8 z# | - {
4 i8 }! y9 P* W4 ^1 ]- R( o - retry++; // 当接收缓冲为非空时,retry++
" d4 I1 C$ g ?5 Y$ A2 ] - if(retry>200)return 0;
/ ]: i6 B+ k9 ` }; V# u' h% ?" R- z - } 7 V; ?" f5 Y* K. s/ v
- return SPI_I2S_ReceiveData(SPI1); // 返回通过SPIx最近接收的数据
; N2 h" p; z( l/ A {; j$ t - }
) L$ v+ C# C9 `" b$ h, { - /**************************************************
* {3 S. A J1 s( k% t5 z1 y+ p - 函数名称:SPI2_Init()0 w# U P) U" S& J
- 函数功能:SPI2初始化函数:配置成从机模式
0 @' y8 {/ h! K, R - 入口参数:无
) F7 t) |3 y3 `- @& g( p - 返回参数:无# z0 f! C% y' R+ b$ j
- 开发作者:闲人Ne+ y$ }* g$ c$ Y& v" E
- ***************************************************/# u5 ]( J. [& M* q6 I3 r( M
- void SPI2_Init(void)8 J; S, q! T3 ?. V, x0 u3 z+ p( ]
- {
" ?- x( D; J/ ~3 ]: L+ } - GPIO_InitTypeDef GPIO_InitStruct; $ H' O8 g7 ?9 F4 a5 S+ j
- SPI_InitTypeDef SPI_InitStruct; # a4 _9 n( { T) Z% `1 u1 r
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); // GPIOB时钟使能,选择SPI2,对应PB12,PB13,PB14,PB15 , n( q& l2 n% `, O" u$ K
- RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE ); // SPI2时钟使能 ' X) G0 C+ Y! Q# j6 G
- // 初始化GPIOB,PB13/14/15都设置复用推挽输出AF_PP,PB14对应MISO,最好设为带上拉输入
8 g4 b9 s4 n/ j: T - GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
. {% ^# a" x! f& f - GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出AF_PP
2 f. W( t6 E6 u. G# @3 X8 c/ e; \ - GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
8 b9 a" f/ C* W* F) t - GPIO_Init(GPIOB, &GPIO_InitStruct);
: {9 \4 }" B+ R - GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); // PB13/14/15置高电平* a% B: s" n. G
- // 初始化SPI函数. A# B6 y: r& v5 d' b
- SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 设置SPI单向或双向的数据模式:SPI设置为双线双向全双工
4 o' t2 e5 X5 ?/ Y# N; J - SPI_InitStruct.SPI_Mode = SPI_Mode_Slave; // SPI2设为从机& r1 E! R9 h) B7 H; i7 T
- SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // 针对SPI_CR1寄存器的DFF位,设置数据帧大小为8位9 }0 H6 {2 \- v5 X0 t) y) m
- SPI_InitStruct.SPI_CPOL = SPI_CPOL_High; // 针对SPI_CR1寄存器的CPOL位,串行同步时钟的空闲状态为高电平
8 y: s, j- j$ K; v# {( P* Z) z - SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; // 针对SPI_CR1寄存器的CPHA位,串行同步时钟的第二个跳变沿(即上升沿)数据被采样; ~6 A; u0 j; C5 R/ u' n: ^; Q; T- T* z
- SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; // 针对SPI_CR1寄存器的SSM位,NSS信号由软件(使用SSI位)管理
) B& `/ s9 o$ _- |" d. ^ - SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // 针对SPI_CR1寄存器的BR位,波特率预分频值为256,最低速率3 ~$ ~2 _7 W6 _9 B1 H& S5 \
- SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; // 针对SPI_CR1寄存器的LSBFIRST位,数据传输从MSB位开始
( O0 M( k/ H0 {- e: `9 Y4 _. u4 [ - SPI_InitStruct.SPI_CRCPolynomial = 7; // 针对SPI_CRCPR寄存器的CRCPOLY位,设为0x0007,为复位值
5 w- f7 D2 [ e. a; I - SPI_Init(SPI2, &SPI_InitStruct);
4 y8 [* k9 Y+ r! l( j# Y - SPI_Cmd(SPI2, ENABLE); // 使能SPI外设 # h6 c9 T3 @+ a3 r
- } 5 a* C% r& O) ] ^5 s7 P
- /**************************************************7 W, @5 e! |! @$ A+ Z# R
- 函数名称:u8 SPI1_ReadWriteByte(u8 TxData) Z( E" s% E0 E3 ]( P6 ^: @7 z
- 函数功能:SPI1读写一个字节函数
6 E# Z2 j# y# K5 C - 入口参数:TxData:要写入的字节, i3 u* w; ]0 D$ n0 r7 A
- 返回参数:读取到的字节' P0 Y$ g$ Q5 m8 T; T3 r
- 开发作者:闲人Ne
3 I8 V& [5 f" t( l0 ^2 I$ q8 R - ***************************************************/
* @3 O/ X1 f7 m7 u5 T3 K# ? - u8 SPI2_ReadWriteByte(u8 TxData)+ D- g, Z- W4 R3 g1 c M$ s
- {
$ \4 T8 X! s6 D& `8 r9 Z" e6 G - u8 retry=0; 4 k0 Z4 M5 X% P# v+ d8 ]
- // 检查SPI_SR寄存器的TXE位(发送缓冲为空),其值0时为非空,1时为空 4 p0 t, _8 R8 r7 r( C! M7 T
- while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
6 b& r4 s/ }1 R: |# n - {$ n- g5 u; @. V$ }: v4 n- l- z% h4 x
- retry++; // 发送缓冲为空时,retry++& g4 }+ D# F8 f
- if(retry>200)return 0;
; Z* c+ `5 ^5 m+ n6 ] - }
$ I3 B( q& `5 @: A - SPI_I2S_SendData(SPI2, TxData); // 通过外设SPI2发送一个数据
3 t& p4 F2 ?, J3 ]! |3 D5 D - retry=0;
5 E! R7 S# d, i( q7 R& {; J! G - // 检查SPI_SR寄存器的RXNE位(接收缓冲为空),其值0时为空,1时为非空
2 Z9 O% z1 e x/ E2 ~; Y# } - while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)
; C* ]# Q+ @* H - {6 c$ {0 Y; h& w; m
- retry++; // 当接收缓冲为非空时,retry++
3 e4 |, _' v; k, g, e - if(retry>200)return 0;& ]# }8 h n) K8 e6 R
- } z: ]; l( z( j- E$ e
- return SPI_I2S_ReceiveData(SPI2); // 返回通过SPIx最近接收的数据
' F6 a L2 W% S# t2 J - }
/ D% v) p* e' d* j0 A2 c - /****** Copyright (C)2021 闲人Ne. All Rights Reserved ****** END OF FILE *******/
复制代码 2 `6 k4 o3 l6 D! _5 V+ N
3.main.c主函数
1 D: T' k( F$ {% u0 ]" S1 [1 J3 I- /** R' z9 s, C' ?2 a7 w% h+ `1 F
- ******************************** STM32F10x *********************************
) p" M- V, l% B( @( Z0 T - * @文件名称: main.c
O6 l' e6 Z, x( \8 |5 D - * @作者名称: 闲人Ne
' Y+ N5 Q( g) o, f! H! a - * @库版本号: V3.5.08 Y B$ S" Z! F
- * @工程版本: V1.0.0+ b9 `" d9 T1 p' {
- * @开发日期: 2021年02月17日 `% W$ e( F+ Z% \$ g5 F
- * @摘要简述: 主函数6 ?7 Y8 v! E; T' z
- ******************************************************************************/8 Q3 U& s9 x# U
- /* 包含的头文件 ---------------------------------------------------------------*/: j9 Q% Y1 c# A
- #include "led.h"$ x# h' R/ e! z3 O
- #include "spi.h"
5 Q/ W( X8 K7 W: v( P! H - #include "serial_communication.h"
2 P6 K' x9 S4 e; W! o - #include "sys.h"
0 L B' `' \, x J' ~5 D5 a - #include "delay.h"6 i P2 k; l) p- ^% A
- #include "nvic_configuration.h"3 Y" ~- \1 C' D8 G# ]
- /************************************************
0 Y4 E3 w+ A: L9 r2 \& K - 函数名称:int main()
' A3 @# @3 l/ T4 l4 w2 G - 函数功能:主函数入口2 X# ?$ j& S; f& g
- 入口参数:无- S5 Q1 K$ {& W0 u( x7 u# M$ i' a
- 返回参数:int
8 j* g( a: S: m. k. V+ z - 开发作者:闲人Ne
( b+ m% O7 R6 R2 S% v" r - *************************************************/. e" Q& [ a+ V/ O, E% \) d
- u8 spi1_sand_data=0;
1 W% ~& _' L3 Q" W8 d- P. u) U0 x - u8 spi2_sand_data=100;( j3 c+ c4 p9 V9 P3 {
- u8 spi2_receive_data;
" B# H1 R+ G+ z% o, [+ x W! O - u8 spi1_receive_data;* M p4 Y7 V, r3 }5 x- t" Q
- int main(void)( ^" z! {% G( o( ?4 ?- w& V/ R' Q
- {+ B! P2 {& ]* @+ I+ y: l
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- C9 X, {. w/ d - delay_init();
& e8 O: |, J) c/ h( Y - SPI1_Init();
5 ^3 v0 Z& c' u7 ^! t& x% Z" j- ~( y/ R - SPI2_Init();
" n% q: I( j e ~( [1 f" n* }: U - My_USART1_Init();
) L0 m. s5 g' A$ \8 }; Z" E8 n - D1=1; 9 X9 b0 R# h+ N0 `
- while(1)" B7 S, q% j! r( C2 u0 _
- {1 v! @8 s) s w! ?9 K( u
- spi1_receive_data=SPI1_ReadWriteByte(spi1_sand_data);( Z6 ^: K# m6 B" E' B/ j
- printf("\nSP1接收的数据为:%d\r\n",spi1_receive_data);
1 m# m8 F. z9 P, S0 ~ - spi2_receive_data=SPI2_ReadWriteByte(spi2_sand_data); . ?+ A$ _% [& E; k. h
- printf("\nSP2接收的数据为:%d\r\n",spi2_receive_data); ; Q# |8 E* v9 ~' r' h" V
- spi1_sand_data++; spi2_sand_data++;0 ~$ s$ r0 d0 P
- delay_ms(500);( g5 F8 |" n; {
- }& u* P$ g( H) Z/ u! q7 K$ [5 y# R; V
- }8 z- w2 | G4 S8 ~. I
- /****** Copyright (C)2021 闲人Ne. All Rights Reserved ****** END OF FILE *******/
复制代码 * U j( f/ W$ B0 x
实验结果
! d1 A1 v. c4 D" |8 E, P! A9 d
* a7 S( i \3 W0 A4 H) r" }0 R
3 P7 w2 q4 r( I" \, E" g* `
实验结果完全符合逻辑,通讯成功!7 Q. ^8 o3 G& R) z/ B, Y2 J
5 n% ^3 f3 ~2 J5 U) l
经验分享
1 [" h g; |: u
! ]# m* j2 f! D8 F+ U该程序可作为双芯片之间SPI通讯的参考代码。
2 S$ I, P& J, z; G8 y9 ]————————————————
% F* L! }2 ^( t1 O( f版权声明:天亮继续睡
6 l5 _/ J, ~- b4 Y5 k/ x% i6 R/ Q3 g2 S
$ D6 L2 ]8 Y# e- G4 ^6 z+ Y3 M: s/ a& z5 K# v! D
|