|
本文介绍如何使用STM32标准外设库的GPIO端口模拟SPI,本例程使用PA5、PA6和PA7模拟一路SPI。SPI有4种工作模式,模拟SPI使用模式0,即空闲时SCK为低电平,在奇数边沿采样。 本文适合对单片机及C语言有一定基础的开发人员阅读,MCU使用STM32F103VE系列。 2 B* S0 U* j" S' q% u5 ]% Q
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。0 W2 @" U6 e( ^
| SPI模式 | 时钟极性(空闲时SCK时钟) | 时钟相位(采样时刻) | 0 | 低电平 | 奇数边沿 | 1 | 低电平 | 偶数边沿 | 2 | 高电平 | 奇数边沿 | 3 | 高电平 | 偶数边沿 |
' R9 v3 N1 [* e* ^3 _. C+ h
* I( A- x) u# l3 H" j3. 时序说明以模式0举例说明: - 空闲状态:片选信号SS为高,SCK输出低电平。
- 开始信号:片选信号SS变低,SCK输出低电平。
- 结束信号:片选信号SS变高,SCK输出低电平。
- 读取:SCK由低变高之后,读取MISO引脚信号。
- 写入:SCK输出低电平,MOSI引脚输出相应的电平,然后SCK输出高电平。
- 一个时钟周期同时读取和写入:SCK输出低电平,主设备控制MOSI输出相应电平,从设备控制MISO输出相应电平,然后SCK输出高电平,从设备读取MOSI引脚电平,主设备读取MISO引脚电平。即无论主设备还是从设备,均在SCK为低电平时输出信号,在SCK为高电平时读取信号。5 y; [2 {3 Z; |' D
4. 初始化初始化跟普通GPIO类似,SCK和MOSI设置为推挽输出,而MISO设置为浮空输入。 GPIO初始化完成之后,SCK置为低电平,进入空闲状态。 5. 模拟信号由于SPI支持一个周期内同时读取和写入,因此读取和写入操作可以用一个函数实现,而单独的读取函数和写入函数可以通过调用该读写函数实现。 8 M9 N: |: H" R2 j$ @
完整代码(仅自己编写的部分)9 K8 @8 s8 W& }
- #define SPI_SCK_1 GPIO_SetBits(GPIOA, GPIO_Pin_5) /* SCK = 1 */
; w Y X# l" o! q ~ - #define SPI_SCK_0 GPIO_ResetBits(GPIOA, GPIO_Pin_5) /* SCK = 0 */$ O P. ^$ L: S4 l9 k% J% |2 m) O
% i# _7 Y$ {0 P8 g/ i: T! ]- |- #define SPI_MOSI_1 GPIO_SetBits(GPIOA, GPIO_Pin_7) /* MOSI = 1 */
1 U/ N; K7 I8 I! E( K - #define SPI_MOSI_0 GPIO_ResetBits(GPIOA, GPIO_Pin_7) /* MOSI = 0 */
* T) e) V; J v- `$ ~ - : _! h# S5 O. k5 g+ N2 l
- #define SPI_READ_MISO GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) /* 读MISO口线状态 */
9 J# b8 S4 b& m: j
+ b8 T0 Z4 Z+ g6 {2 `- #define Dummy_Byte 0xFF //读取时MISO发送的数据,可以为任意数据. n1 ~7 B3 }3 x7 U. M2 Q
- # Y; `- ]7 R; G5 I! s* |# m- }8 i/ a
- & I$ o; \) `# x6 | a
- //初始化SPI
1 o2 S. W9 x+ | J& m2 ] - void SPI_IoInit(void)
" ?$ n2 l- ~2 x, l7 D - {
2 V7 v* @) w* y- j% x - GPIO_InitTypeDef GPIO_InitStructure;$ K: @3 P' ]/ M5 H) }( ^
: O- h% a2 }, `- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
+ b% n* O% n) Z( Q2 O - * _! {1 Q8 l6 x: O4 f! q1 f& G
- //CS引脚初始化
, F) Z5 v* T0 S# Z4 } - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;, e; I& C3 \ {% s% @
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出9 u) s0 E: o8 q. V
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
4 V3 t$ F3 f. z" Z! P4 S1 X5 x - GPIO_Init(GPIOC, &GPIO_InitStructure);
$ P3 |2 S5 T& Q - 6 t0 G& b% K, [7 ~! h/ P
- //SCK和MOSI引脚初始化6 e5 n9 x8 N1 c5 U! f. H% [# v+ @3 p
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;1 u8 `" g% \' h" G9 ^
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
) ?7 J# Y( W1 K - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;7 O% o+ U. Q) ~+ N
- GPIO_Init(GPIOA, &GPIO_InitStructure);- ]+ M) Y; R( J0 i
B5 E4 t1 z) @* v- //MISO引脚初始化$ \% }2 b1 L& k0 I* U
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;- o3 j- _+ q3 v4 x: ^, R }
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ; //浮空输入
% r6 d) g+ I9 a% X, E, z, c6 P - GPIO_Init(GPIOA, &GPIO_InitStructure);
5 ?; a) G9 P$ {# K - ) c* E, F( Z5 W- X
- SPI_CS_1;3 [( |5 t; X& \0 z' _! S
- SPI_SCK_1;0 A, N1 o2 q6 |5 ?. C$ h
- }$ K" }% ~- I) b7 Y& X' w
- " t7 A5 K5 t6 m0 x
- //SPI可以同时读取和写入数据,因此一个函数即可满足要求: V3 C% U2 q; l1 J" U# i
- uint8_t SPI_ReadWriteByte(uint8_t txData)
* L6 _# q' \- D( E$ f - {
p! J6 Y& w' B - uint8_t i;) q3 @% O6 Z; a/ C+ k
- uint8_t rxData = 0;, w* b' T# e3 L! v8 B1 U
' \' D) X% s1 J3 s- for(i = 0; i < 8; i++)
% s5 ?/ |8 E8 v0 h$ V. r - {7 J* |8 e4 c1 E8 m' {
- SPI_SCK_0;
3 A4 G( C9 r8 D6 \" a& | - delay_us(1);% |* B1 |! R7 ?! m4 n/ z/ f
- //数据发送& D' T; f9 r3 v0 ]: T$ B! v
- if(txData & 0x80){/ \" f$ y/ Q( U0 a2 I M; x: n k
- SPI_MOSI_1;. I0 W0 Q: c/ F. \ l" X4 H d2 l
- }else{/ r6 E1 W: _( S6 v! Q
- SPI_MOSI_0;% A* k: z& C) @, B o, M! @
- }2 B! x2 x% e. G6 W! H
- txData <<= 1;$ c- c, Q& O' p- e
- delay_us(1);: b+ e( `3 |) \2 A$ C# l, r
' m' k8 ~8 u3 H& G9 G- SPI_SCK_1;
& n4 c& r8 f9 s' e. f - delay_us(1);
~0 u6 {+ S+ U" l! q3 e - //数据接收
8 h8 q' ~: r1 I ^1 n6 | - rxData <<= 1;
l- A- [9 [3 O0 X* V0 ? - if(SPI_READ_MISO){0 B& x6 C) d/ n, M- ~3 ~
- rxData |= 0x01;
2 b" r( L2 W' r3 H. \7 w( \$ S - }
6 z6 ], V2 i; G, ]; Q. S' f - delay_us(1);' p X8 R. u) \6 e* P* {, T4 k
- }3 Y9 p& b* y! ^. o& b8 T& r
- SPI_SCK_0;7 e' f- B" [3 S
- 1 ]' z! c* \8 a8 s
- return rxData;
9 v6 Z% r Z+ H% @, h* _ - }
- O7 d$ j% H1 f, S" T
" X2 g# r8 Y5 T7 W* O- uint8_t SPI_ReadByte(void)
$ C w8 J; D( c; k; Y5 a - {
% q; G- m0 R' @ Y$ v6 l - return SPI_ReadWriteByte(Dummy_Byte);
$ S/ C7 Z: z9 g1 H# k# e - }7 @# F& ^) r- ?7 G
/ m& f1 F: N( Z/ X4 E5 A- void SPI_WriteByte(uint8_t txData)3 Q( b' M! v6 B; U% S! [
- {
[8 M- Y5 w( v5 N - (void)SPI_ReadWriteByte(txData);7 s' ?5 L! ]; H, u6 Z/ p, C3 U) ]* q
- }
复制代码 ! \. }) n; I' x% }" g+ [. ]
5 A' ?; F$ q9 e/ R7 m! V% R |