|
本文介绍如何使用STM32标准外设库的GPIO端口模拟SPI,本例程使用PA5、PA6和PA7模拟一路SPI。SPI有4种工作模式,模拟SPI使用模式0,即空闲时SCK为低电平,在奇数边沿采样。 本文适合对单片机及C语言有一定基础的开发人员阅读,MCU使用STM32F103VE系列。 ' T7 k7 t* @8 B, y" U% U
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。# F! W+ _8 b s" Z3 j
| SPI模式 | 时钟极性(空闲时SCK时钟) | 时钟相位(采样时刻) | 0 | 低电平 | 奇数边沿 | 1 | 低电平 | 偶数边沿 | 2 | 高电平 | 奇数边沿 | 3 | 高电平 | 偶数边沿 | 8 F. s* z" e* m% j$ i8 U
[' v J$ X- S7 H: |, ~3. 时序说明以模式0举例说明: - 空闲状态:片选信号SS为高,SCK输出低电平。
- 开始信号:片选信号SS变低,SCK输出低电平。
- 结束信号:片选信号SS变高,SCK输出低电平。
- 读取:SCK由低变高之后,读取MISO引脚信号。
- 写入:SCK输出低电平,MOSI引脚输出相应的电平,然后SCK输出高电平。
- 一个时钟周期同时读取和写入:SCK输出低电平,主设备控制MOSI输出相应电平,从设备控制MISO输出相应电平,然后SCK输出高电平,从设备读取MOSI引脚电平,主设备读取MISO引脚电平。即无论主设备还是从设备,均在SCK为低电平时输出信号,在SCK为高电平时读取信号。" O$ ^% V; z* I8 K. G% O
4. 初始化初始化跟普通GPIO类似,SCK和MOSI设置为推挽输出,而MISO设置为浮空输入。 GPIO初始化完成之后,SCK置为低电平,进入空闲状态。 5. 模拟信号由于SPI支持一个周期内同时读取和写入,因此读取和写入操作可以用一个函数实现,而单独的读取函数和写入函数可以通过调用该读写函数实现。 # J+ A: c* \4 y. {4 L7 j" y
完整代码(仅自己编写的部分) v$ q6 e: q. `, u
- #define SPI_SCK_1 GPIO_SetBits(GPIOA, GPIO_Pin_5) /* SCK = 1 */
G- u% `; W6 r' }7 L% a/ l - #define SPI_SCK_0 GPIO_ResetBits(GPIOA, GPIO_Pin_5) /* SCK = 0 */
# B& J) M7 F, Z( R6 w* J; W
9 k* v H: C9 I% n8 Y# F+ i- #define SPI_MOSI_1 GPIO_SetBits(GPIOA, GPIO_Pin_7) /* MOSI = 1 */
0 z7 ?0 N* s. | - #define SPI_MOSI_0 GPIO_ResetBits(GPIOA, GPIO_Pin_7) /* MOSI = 0 */
: f7 b+ z- t4 L
2 S$ @; j4 ]+ x. Y- #define SPI_READ_MISO GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) /* 读MISO口线状态 */
0 p" e9 g: \' t' S) T - # v. n8 q, _) t* a& V- D4 D2 K
- #define Dummy_Byte 0xFF //读取时MISO发送的数据,可以为任意数据& U" [3 y- ^' B2 X8 \
- % X5 K2 \8 Z: C& f# P! w8 R' r
- 6 O3 \7 O7 {& x3 `/ f
- //初始化SPI
" M' h2 M, F g: C& A' o - void SPI_IoInit(void)& i( u. {. h* `9 |
- {3 a4 B# Q+ {7 X( }3 F! `
- GPIO_InitTypeDef GPIO_InitStructure;
( s5 Q8 t3 S Y3 o* Q" D! l# q( [+ d - % ~# [% Z& N" k/ ~6 J1 |0 |7 P
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);$ v+ D& l0 D r- M
- 7 w8 L2 y/ G9 c- K9 h! a, l
- //CS引脚初始化& Y* L( N0 M2 m) ^# ?; S! h9 L
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
$ k+ T- S* g$ L' A* e g - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出 K$ j4 e+ `. p$ e
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
0 A) V2 M7 W5 A+ P - GPIO_Init(GPIOC, &GPIO_InitStructure);* N4 d5 C6 `: ]; f
! a5 B8 O# N/ d9 A4 h* f `- //SCK和MOSI引脚初始化
( |/ f5 n& O1 W" ~5 C - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;
5 _) n- J& c% m$ l3 V. ? - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
$ h1 n P3 Y9 u3 C - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
6 [- I2 Q6 x5 ^3 E6 J7 | - GPIO_Init(GPIOA, &GPIO_InitStructure);
& i. {' l/ `3 s- q6 \; R
x$ A" B" O( h' w2 r/ T- //MISO引脚初始化: F3 K- ~; d* i: d1 M
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;0 X+ e4 [- T Q- X4 L1 F
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ; //浮空输入' k" b) R6 \* f$ A1 L
- GPIO_Init(GPIOA, &GPIO_InitStructure);/ K- S/ T, ~5 t! f7 k
- 7 y* P1 ^2 H8 j+ H
- SPI_CS_1;- o: L9 Z9 H' } Q) X
- SPI_SCK_1;7 N) P3 Z0 j) U
- }2 m1 P2 ~* r) ~' T) g8 l8 L
- ( m6 V+ r' [, f3 F
- //SPI可以同时读取和写入数据,因此一个函数即可满足要求$ I7 D6 V( X0 Q( f( X0 b) q
- uint8_t SPI_ReadWriteByte(uint8_t txData). y) |- z# [ E9 m7 n: r
- {
: U9 A2 m [* I9 h - uint8_t i;6 x6 j1 n) j* E4 f4 G
- uint8_t rxData = 0;
$ F5 i* Z$ ?; w# c! f' P/ [
1 E' I7 J0 t) V+ y- for(i = 0; i < 8; i++)
1 @2 n% M8 C+ L2 f4 m9 F7 {# ~ - {
' j, E$ \: x8 s - SPI_SCK_0;6 o9 X" `% Y3 w/ p( J; \! }
- delay_us(1);
* s6 R* L. q$ W - //数据发送
' ^; f" X4 ^8 r/ @ - if(txData & 0x80){4 o4 C! b+ i# o# }: X9 g8 Q
- SPI_MOSI_1;
# v! o# o$ H* V8 Q* x - }else{* Q {! P* H$ Z! u
- SPI_MOSI_0;7 n; ^: ?) `& A8 C
- }
$ h+ }! G- l; h) r: u; u - txData <<= 1;
6 M. L( W: @& a - delay_us(1);/ \" L2 u/ G0 a" t" O9 E
- 0 d$ L* S6 ^4 U
- SPI_SCK_1;
% u0 t/ D7 }4 H, |% g - delay_us(1);
% N3 h6 c% i7 B6 b+ ^4 ` - //数据接收/ |, [' f0 C) Z; `
- rxData <<= 1;
& e. S* w3 _. Z4 ~3 X: _6 L - if(SPI_READ_MISO){
! P: R' H0 M6 O - rxData |= 0x01;
' W3 r& s: {: O2 Y4 q2 a+ A - }2 G3 ~6 F: q; [3 A
- delay_us(1);
6 t! p% k6 I& W# i# O - }# V/ S3 O p2 F$ c0 e6 |. ?% t
- SPI_SCK_0;# ?" x# b/ |8 q" S# t2 S
- + W- i4 j w( ~0 h
- return rxData;
% z% i) v, R* Y" n' c& b% H! ?6 D& f5 F - }
# @7 {; m: r+ \" M$ D. e
K3 O- k x3 \1 |% n. H- uint8_t SPI_ReadByte(void)
; \1 M0 d7 ]! F# h. } - {
' C. o3 Z1 D6 S: w7 D& f - return SPI_ReadWriteByte(Dummy_Byte);; B+ z" C3 K* y* P! K
- }0 P0 M9 y" d7 v% q. Y
- 5 b' ~2 a2 { k
- void SPI_WriteByte(uint8_t txData)) v4 K3 g0 k& V! E+ i# s
- {# J8 ~: s. y4 r3 G
- (void)SPI_ReadWriteByte(txData);: s5 @% m. ~. N0 A
- }
复制代码 % ~4 j9 w1 h' k* }# }9 `8 h
" T' s2 V9 y2 ]; Q( c
|