本篇博客重点在于标准库函数的理解与使用,搭建一个框架便于快速开发
目录
前言
本篇博客学习使用STM32的SPI硬件收发电路生成SPI时序。SPI协议用模式0,一主多从,非连续传输。
SPI简介
STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
可配置8位/16位数据帧、高位先行/低位先行
支持多主机模型、主或从操作
可精简为半双工/单工通信
支持DMA
STM32F103C8T6 硬件SPI资源:SPI1、SPI2
IO口初始化
STM32F103C8T6 | 模式 |
SCK | 复用推挽输出,由硬件SPI生成时钟 |
MOSI | 复用推挽输出,由硬件SPI生成数据帧 |
MISO | 上拉输入 |
SS | 推挽输出,不用硬件SPI的NSS,使用GPIO模拟通讯的起始与终止 |
GND | 与从机共地 |
GPIO的其它参数的理解可以阅读下方博客,这里不再赘述。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//推挽输出默认输出低电平
MySPI_W_SS(1);//不选择从机,空闲状态
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SPI配置
时钟使能
SPI1在APB2总线上,时钟频率最大72MHz
SPI2、SPI3在APB1总线上,时钟频率最大36MHz
由RCC时钟树,需要使能SPI外设对应的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI初始化
SPI_Mode
SPI_Mode | 描述 |
SPI_Mode_Master | 设置为主SPI |
SPI_Mode_Slave | 设置为从 SPI |
SPI_ Direction
SPI_ Direction | 描述 |
SPI_Direction_2Lines_FullDuplex | SPI 设置为双线双向全双工 |
SPI_Direction_2Lines_RxOnly | SPI 设置为双线单向接收 |
SPI_Direction_1Line_Rx | SPI 设置为单线双向接收 |
SPI_Direction_1Line_Tx | SPI 设置为单线双向发送 |
SPI_DataSize
SPI_DataSize | 描述 |
SPI_DataSize_16b | SPI 发送接收16 位帧结构 |
SPI_DataSize_8b | SPI 发送接收 8 位帧结构 |
SPI_FirstBit
SPI_FirstBit 指定了数据传输从 MSB 位还是 LSB 位开始
SPI_FirstBit | 描述 |
SPI_FisrtBit_MSB | 数据传输从 MSB 位开始 |
SPI_FisrtBit_LSB | 数据传输从 LSB 位开始 |
SPI_BaudRatePrescale
通讯时钟频率(SCK): fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
用来定义波特率预分频的值,这个值用以设置发送和接收的 SCK 时钟
注意:通讯时钟由主 SPI 的时钟分频而得,不需要设置从 SPI 的时钟
SPI_CPOL和SPI_CPHA
这两个参数有四种组合,对应四种模式。主机所选择SPI的模式,要与从机一致。
模式对应的参数见本博客前文提供的博客链接
SPI_NSS
本博客代码使用软件模拟来实现片选
SPI_NSS | 描述 |
SPI_NSS_Hard | NSS 由外部管脚管理 |
SPI_NSS_Soft | 内部 NSS 信号有 SSI 位控制 |
SPI初始化
/*SPI初始化*/
SPI_InitTypeDef SPI_InitStructure; //定义结构体变量
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //模式,选择为SPI主模式
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //方向,选择2线全双工
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据宽度,选择为8位
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //先行位,选择高位先行
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率分频,选择128分频
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //SPI极性,选择低极性
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS,选择由软件控制
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC多项式,暂时用不到,给默认值7
SPI_Init(SPI1, &SPI_InitStructure); //将结构体变量交给SPI_Init,配置SPI1
SPI使能
初始化的最后调用即可
SPI_Cmd(SPI1, ENABLE);
数据接收与发送
先判断发送缓存器中是否有数据,没有?数据就将放在发送缓存区。硬件将数据并行发送到移位寄存器,这时发送缓存器为空(非连续传输模式并没有在这时将下一个要发送的字节写入发送缓存器,而是等待接收到数据后再将数据放发送缓存区),移位寄存器在时钟同步下,将数据发送一位一位发送,对应SPI的模式0的SCL奇数上升沿,数据一位一位接收到移位寄存器,直到一个字节发送完成,也表示一个字节接收完成。移位寄存器的数据会移至接收缓存区,数据接收后及时取走,以免被覆盖。
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);// 在写入发送缓冲器之前,软件必须确认TXE标志为’1’,否则新的数据会覆盖已经在发送缓冲器中的数据
SPI_I2S_SendData(SPI1, ByteSend);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);//发送完成一个字节的同时也接收一个字节,等待接收完成
return SPI_I2S_ReceiveData(SPI1);
}
标志位由硬件清除
硬件SPI代码
MySPI.h
#ifndef __MYSPI_H
#define __MYSPI_H
void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);
#endif
MySPI.c
#include "stm32f10x.h" // Device header
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
MySPI_W_SS(1);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
SPI_I2S_SendData(SPI1, ByteSend);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
return SPI_I2S_ReceiveData(SPI1);
}