本文介绍如何使用STM32标准外设库的GPIO端口模拟SPI,本例程使用PA5、PA6和PA7模拟一路SPI。SPI有4种工作模式,模拟SPI使用模式0,即空闲时SCK为低电平,在奇数边沿采样。 本文适合对单片机及C语言有一定基础的开发人员阅读,MCU使用STM32F103VE系列。 5 H) \6 K/ x1 L9 K1 [) W) I) r
1. 简介SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在要求通讯速率较高的场合。SPI用于多设备之间通讯,分为主机Master和从机Slave,主机只有一个,从机可以有多个,通过片选信号对从机进行选择,一次只能选择一个从机。通讯只能由主机发起,支持的操作分为读取和写入,即主机读取从机的数据,以及向从机写入数据。 SPI一般有4根线,分别是片选线SS、时钟线SCK、主设备输出\从设备输入MOSI、主设备输入\从设备输出MISO,其中除MISO对于主机为输入引脚外,其他引脚对于主机均为输出引脚。因为有独立的输入和输出引脚,因此SPI支持全双工工作模式,即可以同时接收和发送。 2. 总线传输信号- 空闲状态:片选信号SS低电平有效,那么空闲状态片选信号SS为高。
- 开始信号及结束信号:开始信号需要将片选信号SS拉低,结束信号需要将片选信号SS拉高。
- 通讯模式:SPI有4种通讯模式,分别为0、1、2、3,根据时钟极性和时钟相位确定,时钟极性分别为空闲低电平和空闲高电平,时钟相位分别为SCK奇数边沿采样和偶数边沿采样。常用的模式为模式0和模式3。% B- J/ ^# D1 l8 ~" _( G2 T, l
SPI模式 | 时钟极性(空闲时SCK时钟) | 时钟相位(采样时刻) | 0 | 低电平 | 奇数边沿 | 1 | 低电平 | 偶数边沿 | 2 | 高电平 | 奇数边沿 | 3 | 高电平 | 偶数边沿 |
; h* p0 x$ L- a( D4 j
6 |3 n7 h9 h0 B( ~+ v3. 时序说明以模式0举例说明: - 空闲状态:片选信号SS为高,SCK输出低电平。
- 开始信号:片选信号SS变低,SCK输出低电平。
- 结束信号:片选信号SS变高,SCK输出低电平。
- 读取:SCK由低变高之后,读取MISO引脚信号。
- 写入:SCK输出低电平,MOSI引脚输出相应的电平,然后SCK输出高电平。
- 一个时钟周期同时读取和写入:SCK输出低电平,主设备控制MOSI输出相应电平,从设备控制MISO输出相应电平,然后SCK输出高电平,从设备读取MOSI引脚电平,主设备读取MISO引脚电平。即无论主设备还是从设备,均在SCK为低电平时输出信号,在SCK为高电平时读取信号。4 e4 E D8 D5 J' x5 Y/ j9 v5 u
4. 初始化初始化跟普通GPIO类似,SCK和MOSI设置为推挽输出,而MISO设置为浮空输入。 GPIO初始化完成之后,SCK置为低电平,进入空闲状态。 5. 模拟信号由于SPI支持一个周期内同时读取和写入,因此读取和写入操作可以用一个函数实现,而单独的读取函数和写入函数可以通过调用该读写函数实现。
4 k) B0 ]) z, q8 [完整代码(仅自己编写的部分)+ I2 w x! n# D7 h9 ^
- #define SPI_SCK_1 GPIO_SetBits(GPIOA, GPIO_Pin_5) /* SCK = 1 */; C W6 [8 ~4 _
- #define SPI_SCK_0 GPIO_ResetBits(GPIOA, GPIO_Pin_5) /* SCK = 0 */
6 p( i2 H5 m/ X3 p& ?0 L
, F# ]/ X6 y# r i7 j( J- #define SPI_MOSI_1 GPIO_SetBits(GPIOA, GPIO_Pin_7) /* MOSI = 1 */' v, h8 }& S' b8 T# z
- #define SPI_MOSI_0 GPIO_ResetBits(GPIOA, GPIO_Pin_7) /* MOSI = 0 */
k% T' e$ s( b, B
- @. I( L R% N# r% A: Z- w: y* e% N- #define SPI_READ_MISO GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) /* 读MISO口线状态 */5 Y3 d* G3 g& I# l
& ^ g' }2 r# t5 z7 e4 t- #define Dummy_Byte 0xFF //读取时MISO发送的数据,可以为任意数据2 M1 N0 V' N: Z* Z5 G3 o
- 8 M- K3 v* r: C/ x* q& W. p! r! l
: }0 X% x8 R, h8 E$ k! g- //初始化SPI
. g* G! D' ?+ e5 M& g. p - void SPI_IoInit(void)
& [8 X& w& L- q, ` ?2 l0 n - {( X3 ~/ P8 D3 {
- GPIO_InitTypeDef GPIO_InitStructure;
$ i. ]3 Q% N# l: }( u' O
# O) \# r. h- s; h$ @0 V8 b- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
4 _/ O3 r% F) T( D# F& Q1 K
: u' l0 |3 z0 o6 a8 ?- //CS引脚初始化
% f- [0 x/ J7 r) k0 j - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
6 O. I2 n* c$ j1 F4 O6 Y" M0 K - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
: l$ g5 i% R, B; ^/ A0 M - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- l& L. c1 j. ], H/ x - GPIO_Init(GPIOC, &GPIO_InitStructure);
# ] N2 @; i4 B; |9 C+ B - 8 s3 w9 n3 l, H
- //SCK和MOSI引脚初始化
$ y+ _& r8 U. Y( F- \# f" a- x - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;( k7 H7 r# C# e3 Z) G( r9 N
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出# Z! P& n8 {1 R& q+ Z) X9 ?& j/ C
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;$ e) J# ]3 v. D
- GPIO_Init(GPIOA, &GPIO_InitStructure);
2 r& R. G; e% S; N, D
" ?* ?4 n/ f: x6 f6 }# L2 {8 |- //MISO引脚初始化
" c# c+ P u& _# j; j2 r6 K: R - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
! S+ p2 S& f+ _9 j - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ; //浮空输入4 S! ?# m8 q/ q
- GPIO_Init(GPIOA, &GPIO_InitStructure);' N' f3 o0 c" S" R
- R1 _5 m. H4 m7 i$ V- SPI_CS_1;3 P! p, K( k- [/ g! z, T, v2 C
- SPI_SCK_1;9 o5 ~9 x/ b# Y2 Q8 J
- }+ Q% V( |, t0 X, w
- / c4 g8 C& z$ x; [, M6 y
- //SPI可以同时读取和写入数据,因此一个函数即可满足要求
" N& Y1 _% `$ ]: k0 V/ K- V% k( H0 [ - uint8_t SPI_ReadWriteByte(uint8_t txData)/ D: o7 A5 T+ f n
- {7 b" f! K3 }8 K3 c; z
- uint8_t i;
9 S) |; I5 X8 J - uint8_t rxData = 0;8 |1 X5 x: \# e, O/ C! ^
% y) `& I% }4 H: Z- for(i = 0; i < 8; i++)
. r, |4 h: V h7 U- c6 w( p - {
- ~% i, [3 i( ]6 B D - SPI_SCK_0;! W5 U& z4 H# @6 k3 ^+ n; g
- delay_us(1);
+ L( G9 ?) c( @; l3 ~* x* c - //数据发送
: J2 P4 w( X/ i8 c! | - if(txData & 0x80){# {# F& q+ k6 V
- SPI_MOSI_1;
9 O, t! z/ ]3 j) z' k0 o - }else{
9 f4 B% \5 B8 |( N - SPI_MOSI_0;: _# `* c/ y# Y
- }- Y) \, T7 \# h" `+ h3 v
- txData <<= 1;& P$ D+ Y; E* m# a# T- g
- delay_us(1);, L5 k( j) w. \$ l
- 3 ?6 W- {" e K; q- u; q7 [% ]
- SPI_SCK_1;3 }6 d* U9 H9 K1 e2 q
- delay_us(1);
* p) c& _9 o6 t' L+ |$ y/ ]! X - //数据接收
% y/ g2 _# O' V# h8 \8 ?: | - rxData <<= 1;3 o# I: C$ C9 B( m+ W' j
- if(SPI_READ_MISO){
' Z b6 [& ` \8 n3 C8 L; r - rxData |= 0x01;1 \, |( f- ]+ H# V( c* u$ z# n
- } o$ C2 Y: V5 X; [9 w/ C- \/ z5 M
- delay_us(1);! U( a7 B: c& z
- }
9 }3 ? h6 y+ s6 u+ i" R$ E - SPI_SCK_0;' k# ~: b# D2 t( G
0 N$ D; r. x! k$ x2 F- return rxData;0 s( i8 J8 |+ t4 P
- }* U5 S. A" ^2 N' |
2 I2 Q8 i$ h& P; g- uint8_t SPI_ReadByte(void)2 D) W, B1 w& S {/ v, n
- {
+ f# T) ]" W# a' l8 ]; N7 G - return SPI_ReadWriteByte(Dummy_Byte);# [, q) Y6 e+ q2 L* E
- }4 A! b1 H4 W+ \5 `5 D
- ) @; k4 ]# L4 V! p3 ~
- void SPI_WriteByte(uint8_t txData)
' a2 @) N5 O7 g3 o y- b - {. k+ w9 {2 Y7 w) A4 ?8 Z3 l _
- (void)SPI_ReadWriteByte(txData);" n' j5 V7 q, O" g, M
- }
复制代码
! j( l# }4 [ n* ^# b& B+ I" u1 n9 e+ D' Z( j
|