SPI简介:
SPI是一种串行外围设备接口高速全双工同步的通信总线。与I2C总线存在不同之处,如下:
功能说明 | SPI总线 | IIC总线 |
通信方式 | 同步 串行 全双工 | 同步 串行 半双工 |
总线接口 | MOSI、MISO、SCL、CS | SDA、SCL |
拓扑结构 | 一主多从/一主一从 | 多主从 |
从机选择 | 片选引脚选择 | SDA上设备地址片选 |
通信速率 | 一般50MHz以下 | 100kHz、400kHz、3.4MHz |
数据格式 | 8位/16位 | 8位 |
传输顺序 | MSB/LSB | MSB |
广泛应用于MCU、FLASH、AD转换器和LCD之间。
SPI的引脚信息:
CS:片选信号,主设备产生;
SCLK:时钟信号,主设备产生;
MISO:主机输入,从机输出;
MOSI:主机输出,从机输入;
SPI的工作原理:
在主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器写入一个字节来发起一次传输。串行移位寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。外设的写操作和读操作是同步完成的。如果只是进行写操作,主机只需忽略接收到的字节。反之,若主机要读取从机的一个字节,就必须发送一个空字节引发从机传输。
SPI的工作模式:
SPI 通信协议就具备 4 种工作模式,在讲这 4 种工作模式前,首先先知道两个单词 CPOL 和 CPHA。
CPOL,详称 Clock Polarity,就是时钟极性,当主从机没有数据传输的时候即空闲状态,
SCL 线的电平状态,假如空闲状态是高电平, CPOL=1;若空闲状态时低电平,那么 CPOL = 0。
CPHA,详称 Clock Phase,就是时钟相位。在这里先科普一下数据传输的常识: 同步通信时,数据的变化和采样都是在时钟边沿上进行的,每一个时钟周期都会有上升沿和下降沿两个边沿,那么数据的变化和采样就分别安排在两个不同的边沿,由于数据在产生和到它稳定是需要一定的时间,那么假如我们在第 1 个边沿信号把数据输出了,从机只能从第 2 个边沿信号去采样这个数据。CPHA=1 的情况就是表示数据采样是从第 2 个边沿即偶数边沿。
四种工作模式表,常用的是模式0和模式3:
代码:
spi.h
#ifndef _SPI_H
#define _SPI_H
#include "./SYSTEM/sys/sys.h"
#define SPI2_SCK_GPIO_PORT GPIOB
#define SPI2_SCK_GPIO_Pin GPIO_PIN_13
#define SPI2_SCK_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOB_CLK_ENABLE();}while(0)
#define SPI2_MISO_GPIO_PORT GPIOB
#define SPI2_MISO_GPIO_Pin GPIO_PIN_14
#define SPI2_MISO_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOB_CLK_ENABLE();}while(0)
#define SPI2_MOSI_GPIO_PORT GPIOB
#define SPI2_MOSI_GPIO_Pin GPIO_PIN_15
#define SPI2_MOSI_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOB_CLK_ENABLE();}while(0)
#define SPI2_SPI SPI2
#define SPI2_SPI_CLK_ENABLE() do{__HAL_RCC_SPI2_CLK_ENABLE();}while(0)
void spi_init();
uint8_t spi2_swapbyte(uint8_t tbyte);
#endif
spi.c
#include "./BSP/SPI/spi.h"
SPI_HandleTypeDef g_spi2_handle;
void spi_init()
{
GPIO_InitTypeDef gpio_init_struct;
SPI2_SPI_CLK_ENABLE();
SPI2_SCK_GPIO_CLK_ENABLE();
SPI2_MISO_GPIO_CLK_ENABLE();
SPI2_MOSI_GPIO_CLK_ENABLE();
/*SCK引脚配置*/
gpio_init_struct.Mode=GPIO_MODE_AF_PP;
gpio_init_struct.Pin=SPI2_SCK_GPIO_Pin;
gpio_init_struct.Pull=GPIO_PULLUP;
gpio_init_struct.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SPI2_SCK_GPIO_PORT,&gpio_init_struct);
/*MISO引脚配置*/
gpio_init_struct.Mode=GPIO_MODE_AF_PP;
gpio_init_struct.Pin=SPI2_MISO_GPIO_Pin;
HAL_GPIO_Init(SPI2_MISO_GPIO_PORT,&gpio_init_struct);
/*MOSI引脚配置*/
gpio_init_struct.Mode=GPIO_MODE_AF_PP;
gpio_init_struct.Pin=SPI2_MOSI_GPIO_Pin;
HAL_GPIO_Init(SPI2_MOSI_GPIO_PORT,&gpio_init_struct);
/*配置SPI2*/
g_spi2_handle.Instance=SPI2_SPI; /*SPI2*/
g_spi2_handle.Init.Mode=SPI_MODE_MASTER; /*设置SPI2为主机模式*/
g_spi2_handle.Init.Direction=SPI_DIRECTION_2LINES; /*设置SPI2工作方式:全双工*/
g_spi2_handle.Init.DataSize=SPI_DATASIZE_8BIT; /*数据格式:8bit*/
g_spi2_handle.Init.CLKPolarity=SPI_POLARITY_LOW; /*时钟极性*/
g_spi2_handle.Init.CLKPhase=SPI_PHASE_1EDGE; /*时钟相位*/
g_spi2_handle.Init.NSS=SPI_NSS_SOFT; /*设置片选方式,软件片选:自定义GPIO*/
g_spi2_handle.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256; /*设置时钟波特率分频*/
g_spi2_handle.Init.FirstBit=SPI_FIRSTBIT_MSB; /*高位先发*/
g_spi2_handle.Init.TIMode=SPI_TIMODE_DISABLE; /*设置帧格式,关闭TI模式*/
g_spi2_handle.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE; /*关闭CRC校验*/
g_spi2_handle.Init.CRCPolynomial=7; /*设置校验多项式*/
HAL_SPI_Init(&g_spi2_handle);
__HAL_SPI_ENABLE(&g_spi2_handle);
}
uint8_t spi2_swapbyte(uint8_t tbyte)
{
uint8_t rbyte=0;
HAL_SPI_TransmitReceive(&g_spi2_handle,&tbyte,&rbyte,1,1000);
return rbyte;
}
flash.h
#ifndef _NoFrash_H
#define _NoFrash_H
#include "./SYSTEM/sys/sys.h"
#define NORflash_GPIO_PORT GPIOB
#define NORflash_GPIO_Pin GPIO_PIN_12
#define NORflash_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOB_CLK_ENABLE();}while(0)
#define NORflash_CS(x) do{x?\
HAL_GPIO_WritePin(NORflash_GPIO_PORT,NORflash_GPIO_Pin,GPIO_PIN_SET):\
HAL_GPIO_WritePin(NORflash_GPIO_PORT,NORflash_GPIO_Pin,GPIO_PIN_RESET);\
}while(0)
void norflash_init();
uint8_t norflash_rd_srl();
uint8_t read_data(uint32_t addr);
void norflash_erase_sector(uint32_t addr);
void write_page(uint8_t data, uint32_t addr);
#endif
flash.c
#include "./BSP/NoFlash/norfrash.h"
#include "./BSP/SPI/spi.h"
void norflash_init()
{
NORflash_GPIO_CLK_ENABLE();
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Pin=NORflash_GPIO_Pin;
gpio_init_struct.Pull=GPIO_PULLUP;
gpio_init_struct.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(NORflash_GPIO_PORT,&gpio_init_struct);
spi_init();
spi2_swapbyte(0XFF); /*清除数据寄存器DR*/
NORflash_CS(1);
}
uint8_t norflash_rd_srl()
{
uint8_t rec_data=0;
NORflash_CS(0);
spi2_swapbyte(0X05); /*读状态寄存器1*/
rec_data = spi2_swapbyte(0XFF);
NORflash_CS(1);
return rec_data;
}
uint8_t read_data(uint32_t addr)
{
uint8_t rec_data=0;
NORflash_CS(0);
/*发送读命令*/
spi2_swapbyte(0x03);
/*发送读地址*/
spi2_swapbyte(addr>>16);
spi2_swapbyte(addr>>8);
spi2_swapbyte(addr);
/*读取数据*/
rec_data=spi2_swapbyte(0xff);
NORflash_CS(1);
return rec_data;
}
void norflash_erase_sector(uint32_t addr)
{
/*写使能*/
NORflash_CS(0);
spi2_swapbyte(0x06);
NORflash_CS(1);
/*等待空闲*/
while(norflash_rd_srl() & 0x01);
/*发送扇区擦除指令*/
NORflash_CS(0);
spi2_swapbyte(0x20);
/*发送擦除地址*/
spi2_swapbyte(addr>>16);
spi2_swapbyte(addr>>8);
spi2_swapbyte(addr);
NORflash_CS(1);
/*等待空闲*/
while(norflash_rd_srl() & 0x01);
}
void write_page(uint8_t data,uint32_t addr)
{
/*擦除扇区*/
norflash_erase_sector(addr);
/*写使能*/
NORflash_CS(0);
spi2_swapbyte(0x06);
NORflash_CS(1);
/*发送页写指令*/
NORflash_CS(0);
spi2_swapbyte(0x02);
spi2_swapbyte(addr>>16);
spi2_swapbyte(addr>>8);
spi2_swapbyte(addr);
/*要写入的数据*/
spi2_swapbyte(data);
NORflash_CS(1);
/*等待空闲(写入完成)*/
while(norflash_rd_srl() & 0x01);
}