1.SPI概念
spi是摩托罗拉公司设计的用于板间的串行通信方式,应用场景类似于IIC,速度快于IIC。
spi属于高速,全双工,同步的通信总线;和设备连接占用4根线,比较占用引脚,速度最快达到40Mbps。
2.硬件连接
CS(chip select):片选引脚
主设备控制,用于选择当前通信的从设备 , 一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access),所以, Master 设备必须首先通过 CS对 Slave 设备进行片选, 把想要访问的 Slave 设备选上.
SCLK:时钟线
产生时钟信号,由主设备控制
MISO(Master Input Slave output)
主设备输入,从设备输出
MOSI(Master Output Slave Input)
主设备输出,从设备输入(MISO和MOSI实现全双工)
3.spi协议
spi每一位数据的发送和接收是在时钟信号的边沿完成,根据选择的上升沿/下降沿和时钟信号的高低电平顺序,一共有4种情况。
CPOL表示高低电平顺序,叫做极性;若CPOL = 0,串行同步时钟的空闲状态为低电平;
若CPOL = 1,串行同步时钟的空闲状态为高电平;
CPHA表示第一个/第二个边沿,叫做相位。
SPI输出串行同步时钟极性和相位可以根据外设工作要求进行配置。 SPI 设备间的数据传输又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 "发送者(Transmitter)" 或者 "接收者(Receiver)". 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了.
分别是以下四种情况:
MODE0:CPOL= 0,CPHA=0。串行时钟SCLK空闲状态时为低电平,数据在SCLK时钟的上升沿采样,下降沿输出;
MODE1:CPOL= 0,CPHA=1。串行时钟SCLK空闲状态时为低电平,数据在SCLK时钟的下降沿采样,上升沿输出;
MODE2:CPOL= 1,CPHA=0。串行时钟SCLK空闲状态时为高电平,数据在SCLK时钟的下降沿采样,上升沿输出;
MODE3:CPOL= 1,CPHA=1。串行时钟SCLK空闲状态时为高电平,数据在SCLK时钟的上升沿采样,下降沿输出;
起始信号
NSS信号线由高变低,是SPI通讯的起始信号
结束信号
NSS信号由低变高,是SPI通讯的停止信号
数据传输
SPI使用MOSI以及MISO信号来传输数据,使用SCK信号线进行数据同步。
MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出时同时进行的。
SPI每次传输数据可以8位或16位为单位,每次传输的单位数不受限制
4.spi flash
原理图:
spi接口的CS连接到了PB14,SCLK,MISO,MOSI分别连接到了PB3 PB4 PB5,这三个IO口具有SPI的复用功能。
5.spi通信的实现(两种方法)
(1)使用GPIO接口模拟SPI的时序,只要是IO口即可以使用。
(2)使用SPI控制器,只需要配置好SPI的通信参数后,直接进行传输,接口必须有SPI复用功能。
6.spi控制器的库函数编程
添加spi的库函数源码:
(1)开启时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
(2)将GPIO配置为SPI复用功能(CS配置为输出)
GPIO_Init(...); GPIO_AFPinConfig(...);
(3)初始化SPI
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
参数:
SPIx - 哪个SPI
SPI_InitStruct - SPI初始化结构
typedef struct
{
uint16_t SPI_Direction; /*!< 传输方向 单向/双向 @ref SPI_data_direction */
uint16_t SPI_Mode; /*!< 主/从设备选择 @ref SPI_mode */
uint16_t SPI_DataSize; /*!< 数据位长度 @ref SPI_data_size */
uint16_t SPI_CPOL; /*!< 极性 @ref SPI_Clock_Polarity */
uint16_t SPI_CPHA; /*!< 相位 @ref SPI_Clock_Phase */
uint16_t SPI_NSS; /*!< 片选信号选择 软件/硬件 @ref SPI_Slave_Select_management */
uint16_t SPI_BaudRatePrescaler; /*!< 参考时钟的预分频系数 10M左右*/
uint16_t SPI_FirstBit; /*!< 传输位顺序 高位/低位 @ref SPI_MSB_LSB_transmission */
uint16_t SPI_CRCPolynomial; /*!< CRC校验,无需配置 */
}SPI_InitTypeDef;
(4)使能SPI
SPI_Cmd(...);
(5)使用SPI传输数据
//发送和接收同时进行 void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data); uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
(6)查询SPI传输状态
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
参数:
* @arg SPI_I2S_FLAG_TXE: Transmit buffer empty flag.
* @arg SPI_I2S_FLAG_RXNE: Receive buffer not empty flag.
* @arg SPI_I2S_FLAG_BSY: Busy flag.
* @arg SPI_I2S_FLAG_OVR: Overrun flag.
* @arg SPI_FLAG_MODF: Mode Fault flag.
* @arg SPI_FLAG_CRCERR: CRC Error flag.
* @arg SPI_I2S_FLAG_TIFRFE: Format Error.
* @arg I2S_FLAG_UDR: Underrun Error flag.
* @arg I2S_FLAG_CHSIDE: Channel Side flag.
7.W25Q128芯片
(1)接口和状态寄存器
CS WP HOLD低电平有效,WP和HOLD接VCC,功能关闭,片选是低电平选中。
SPI接口支持CPOL和CPHA为0,0和1,1的模式
(2)操作时序
在进行任何操作前开启片选,操作完成后关闭片选。
1)读设备ID
发送90H ===> 发送24位0地址 ===> 收到厂家ID(0xef)和设备ID(0x17)
*************************************************************************************************************
//代码实现W25Q128芯片的SPI通信
//功能函数
#include <stm32f4xx.h>
#include <spi_flash.h>
#include <delay.h>
#include <stdio.h>
void spi1_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
SPI_InitTypeDef SPI_InitStruct;
//1.开始时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
//2.配置GPIO为SPI功能
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;//输出模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//高速
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;//PB14
GPIO_Init(GPIOB,&GPIO_InitStruct);
//片选关闭
W25Q128_CS = 1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;//复用模式
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3 PB4 PB5
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);
//3.SPI初始化
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双向全双工
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;//主设备
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//数据长度8位
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;//极性 mode 0
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;//相位 mode 0
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;//软件片选
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;//APB2总线时钟 84M/16分频 = 5.25M
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//高位先出
SPI_Init(SPI1,&SPI_InitStruct);
//4.使能SPI1
SPI_Cmd(SPI1,ENABLE);
}
//发送接收数据 ----- 同时进行
//data是要发送的数据,返回接收的数据
u8 spi1_send_recv_byte(u8 data)
{
//等待发送缓冲区为空
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)!=SET);
//发送数据
SPI_I2S_SendData(SPI1,data);
//等待接收缓冲区非空
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)!=SET);
//接收数据
return SPI_I2S_ReceiveData(SPI1);
}
//读取W25Q128的ID
u16 w25q128_read_id(void)
{
u16 id = 0;
//片选选中
W25Q128_CS = 0;
//发送90H
spi1_send_recv_byte(0x90);
//发送24位0地址
spi1_send_recv_byte(0x00);
spi1_send_recv_byte(0x00);
spi1_send_recv_byte(0x00);
//接收厂家ID放在高8位
id |= spi1_send_recv_byte(0xff)<<8;
//接收设备ID放在低8位
id |= spi1_send_recv_byte(0xff);
//片选关闭
W25Q128_CS = 1;
return id;
}
//读取W25Q128的数据
void w25q128_read_data(u32 addr,u8 *data,u8 len)
{
//片选选中
W25Q128_CS = 0;
//发送03H
spi1_send_recv_byte(0x03);
//发送24位读的地址,高位先出
spi1_send_recv_byte((addr>>16)&0xff);//16~23位
spi1_send_recv_byte((addr>>8)&0xff);//8~15位
spi1_send_recv_byte((addr>>0)&0xff);//0~7位
//读取数据
while(len--){
*data++ = spi1_send_recv_byte(0xff);
}
//片选关闭
W25Q128_CS = 1;
}
//开启/关闭写使能 enable = 0 开启写使能 enable = 1 关闭写使能
void w25q128_write_enable_disable(u8 enable)
{
//片选选中
W25Q128_CS = 0;
if(enable){
spi1_send_recv_byte(0x04);
}
else{
spi1_send_recv_byte(0x06);
}
//片选关闭
W25Q128_CS = 1;
}
//读状态寄存器1
u8 w25q128_read_status(void)
{
u8 status = 0;
//片选选中
W25Q128_CS = 0;
//发送05H
spi1_send_recv_byte(0x05);
status = spi1_send_recv_byte(0xff);
//片选关闭
W25Q128_CS = 1;
return status;
}
//扇区擦除
void w25q128_sector_erase(u32 addr)
{
//开启写使能
w25q128_write_enable_disable(0);
//延时,让片选信号保持一段时间,使W25Q128感受到
delay_us(50);
//片选选中
W25Q128_CS = 0;
//发送20H
spi1_send_recv_byte(0x20);
//发送24位擦除地址,高位先出
spi1_send_recv_byte((addr>>16)&0xff);//16~23位
spi1_send_recv_byte((addr>>8)&0xff);//8~15位
spi1_send_recv_byte((addr>>0)&0xff);//0~7位
//片选关闭
W25Q128_CS = 1;
delay_us(50);
//等待擦除完成
while(w25q128_read_status()&0x1){
//1ms查询一次
delay_ms(1);
}
delay_us(50);
//关闭写使能
w25q128_write_enable_disable(1);
}
//写数据(按页)
void w25q128_write_page(u32 addr,u8 *data,u8 len)
{
//开启写使能
w25q128_write_enable_disable(0);
//延时,让片选信号保持一段时间,使W25Q128感受到
delay_us(50);
//片选选中
W25Q128_CS = 0;
//发送02H
spi1_send_recv_byte(0x02);
//发送24位写地址,高位先出
spi1_send_recv_byte((addr>>16)&0xff);//16~23位
spi1_send_recv_byte((addr>>8)&0xff);//8~15位
spi1_send_recv_byte((addr>>0)&0xff);//0~7位
//发送写的数据
while(len--){
spi1_send_recv_byte(*data++);
}
//片选关闭
W25Q128_CS = 1;
delay_us(50);
//等待写完成
while(w25q128_read_status()&0x1){
//1ms查询一次
delay_ms(1);
}
delay_us(50);
//关闭写使能
w25q128_write_enable_disable(1);
}
void flash_write_temp(u8 temp)
{
u8 buf[16] = {0},i,data = 0;
u32 addr = 0;
//找到未被写过的块
while(1){
w25q128_read_data(addr,buf,16);
if(buf[0]!=0x55)
break;
addr += 16;
}
data = 0x55;
//写入0x55
w25q128_write_page(addr,&data,1);
//写入temp
w25q128_write_page(addr+1,&temp,1);
data = 0;
//求校验和
w25q128_read_data(addr,buf,16);
for(i=0;i<15;i++){
data = (data + buf[i])&0xff;
}
//写入校验和
w25q128_write_page(addr+15,&data,1);
}
void flash_read_temp(void)
{
u8 buf[16] = {0},i,data;
u32 addr = 0;
//找出所有写入温度的块
while(1){
data = 0;
w25q128_read_data(addr,buf,16);
if(buf[0]!=0x55)
break;
//求校验和
for(i=0;i<15;i++){
data = (data + buf[i])&0xff;
}
if(data!=buf[15]){
printf("checksum error!data = %#x,buf[15] = %#x\r\n",data,buf[15]);
break;
}
printf("0x%x temp = %d\r\n",addr,buf[1]);
addr += 16;
}
}
//头文件声明
#ifndef _SPI_FLASH_H_
#define _SPI_FLASH_H_
#include <sys.h>
#define W25Q128_CS PBout(14)
void spi1_init(void);
u16 w25q128_read_id(void);
void w25q128_read_data(u32 addr,u8 *data,u8 len);
void w25q128_sector_erase(u32 addr);
void w25q128_write_page(u32 addr,u8 *data,u8 len);
void flash_write_temp(u8 temp);
void flash_read_temp(void);
#endif