STM32 硬件spi通信篇:读写W25Q128
SPI外设简介
SPI框图
左上角移位寄存器,右边的数据低位一位一位从MOSI移出,左边的数据高位一位一位的从MISO移入(低位先行);
其左侧MOSI和MISO在作为主机时,方框中的交叉线无需使用,MOSI输出,MISO输入;做从机时,MOSI从机输入后经过交叉进入移位寄存器,MISO从机输出,数据从移位寄存器经过交叉后输出到MISO(箭头应该画反了,也许是往下指的);
移位寄存器上下的接收缓存区RDR和发送缓存区TDR编程上为同一个地址,统一为DR;在具体数据流转运的过程中(作为主机),要发送的数据先进入发送缓存区TDR,当移位寄存器没有数据移位时,TDR中的数据整体转入移位寄存器,并开始移位至MOSI,同时MISO移入移位寄存器,随时钟信号进行一字节的数据交换,在TDR数据转入移位寄存器时,状态寄存器TXE会立刻置1,随后TDR传入下一字节数据;当移位寄存器中一字节数据交换完毕后,移位寄存器中的接收数据会转入接收缓存区RDR中,并置状态寄存器RXNE为1,表示接受寄存器非空,此时需要在下一个数据到来之前读出RDR的数据;
需注意,TDR用于发送数据,RDR用于接收数据,它们在内核硬件内部是不同的物理寄存器,编程时映射到同一个地址,这是通过外设的硬件逻辑设计实现的。
/************************************************************************************/
与IIC、USART对比:
由于SPI为全双工,发送和接收同步进行,故数据寄存器缓冲区,发送和接收是分离的,移位寄存器则是共用的;
IIC为半双工,发送和接收不同时进行,故发送和接收缓冲区、移位寄存器都是共用的
串口为全双工,发送和接收异步进行,故发送和接收缓冲区、移位寄存器都是分离的
/************************************************************************************/
波特率发生器用于产生SCK时钟,内部为分频器;右边的SPI_CR1寄存器的三个位:BR0、BR1、BR2用于控制分频系数
SPI_CR1寄存器:LSB FIRST 决定高位/低位先行 SPE 为SPI使能位
BR 配置波特率 MSTR 配置主从模式 1主0从
CPOL、CPHA 选择SPI四种模式 NSS 从机选择,低电平有效,一般用于多主机选择
TEX 发送寄存器空 RXNE 接受寄存器非空
硬件SPI移位示意图、基本时序单元、模式选择与软件SPI一致:
STM32 软件spi通信篇
硬件SPI运行时序图
主模式全双工连续传输(下图为模式3):
发送运行逻辑:
1、TXE为1表示TDR为空,软件写入数据(0xF1)至TDR寄存器(发送数据缓存区),TXE为0,此时移位寄存器无数据,故TDR中的数据立刻转入移位寄存器并开始发送;
2、数据(0xF1)转入移位寄存器后,TXE置1表示TDR为空,软件写入下一数据(0xF2)至TDR寄存器(发送数据缓存区),等待移位寄存器中的数据发送/接收完毕;
3、移位寄存器中的数据发送/接收完毕后,TDR中的数据(0xF2)立刻转入移位寄存器并开始发送,同时TXE置1,软件写入下一数据(0xF3)至TDR寄存器(发送数据缓存区),等待移位寄存器中的数据发送/接收完毕,以此类推;
接收运行逻辑:
1、在第一个字节(0xF1)发送完毕时,移位寄存器中,第一个字节(0xA1)的接收也同步完成,此时移位寄存器的数据(0xA1)整体转入RDR,同时软件等待RXNE标志位置1,表示RDR中已存入数据,随后从RDR中读出数据(0xA1),同时软件清除RXNE标志位;
2、当下一个数据(0xF2)发送完毕时,移位寄存器中第二个字节的接收(0xA2)也同步完成,后面的步骤同上直至数据交换结束;需注意,RDR存入数据后需及时读出,避免后续数据覆盖导致数据丢失;
连续传输对软件的配合度要求较高,在标志位产生后需及时处理,通信时每个字节之间没有任何空袭,传输效率高
非连续传输
运行逻辑:
1、TXE为1表示TDR为空,软件写入数据(0xF1)至TDR寄存器(发送数据缓存区),TXE为0,此时移位寄存器无数据,故TDR中的数据立刻转入移位寄存器并开始发送(同连续传输);
2、与连续传输不同,数据(0xF1)转入移位寄存器后,TXE置1表示TDR为空后,不再要求立刻将下一数据写入TDR寄存器,而是等待第一个交换字节的时序结束,随后等待RXNE置1;
3、RXNE为1后,将第一个接收到的数据读出后,再在TDR中写入下一数据(0xF2),随后进入移位寄存器并交换字节;以此类推,重复上述时序直至通信完毕;
代码实现
初始化流程:
1、开启SPI、GPIO时钟;
2、初始化GPIO口:SCK、MOSI为输出口,配置为复用推挽输出;MISO为硬件外设的输入信号,可配置为上拉输入;SS为软件控制的输出信号,配置为通用推挽输出即可;
3、配置SPI外设;
4、开关控制,调用SPI_Cmd,给SPI使能
SPI相关库函数
//恢复缺省配置
void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
//初始化
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
//结构体变量初始化
void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);
//外设使能
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
//中断使能
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
//DMA使能
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
//写DR数据寄存器
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
//读DR数据寄存器
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
//获取和清除标志位
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
实现代码:
main:
#include "stm32f10x.h" // Device header
#include "delay.h"
#include "LED.h"
#include "KEY.h"
#include "OLED.h"
#include "W25Q128.h"
int main(void)
{
delay_init();
OLED_Init();
W25Q128_Init();
uint8_t MID;
uint16_t DID;
uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04}; //定义要写入数据的测试数组
uint8_t ArrayRead[4]; //定义要读取数据的测试数组
/*显示静态字符串*/
OLED_ShowString(1, 1, "MID: DID:");
OLED_ShowString(2, 1, "W:");
OLED_ShowString(3, 1, "R:");
/*显示ID号*/
W25Q128_ReadID(&MID, &DID); //获取W25Q128的ID号
OLED_ShowHexNum(1, 5, MID, 2);
OLED_ShowHexNum(1, 12, DID, 4);
/*W25Q64功能函数测试*/
W25Q128_SectorErase(0x000000); //指定扇区起始地址来扇区擦除
W25Q128_PageProgram(0x000000, ArrayWrite, 4); //将写入数据的测试数组写入到W25Q128中。起始地址:0x000000。写入数组:把ArrayWrite传进去。写入数量:4个字节。
//这里00是最后2个16进制数的页内地址,前面4位是页地址
W25Q128_ReadData(0x000000, ArrayRead, 4); //读取刚写入的测试数据到读取数据的测试数组中。读取数组:把ArrayRead传进去。读取数量:4个字节
/*显示数据*/
OLED_ShowHexNum(2, 3, ArrayWrite[0], 2); //显示写入数据的测试数组
OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
OLED_ShowHexNum(3, 3, ArrayRead[0], 2); //显示读取数据的测试数组
OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
while (1)
{
}
}
My_SPI.c:
#include "stm32f10x.h" // Device header
void MySPI_W_CS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_12, (BitAction)BitValue);
}
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15; //MOSI、SCK
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //MISO
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;//64分频
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //模式0
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC校验的多项式?
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位数据帧
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//全双工
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位先行
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //配置为主机
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //软件模拟
SPI_Init(SPI2, &SPI_InitStructure);
SPI_Cmd(SPI2, ENABLE);
MySPI_W_CS(1);
}
void MySPI_Start(void) //起始信号
{
MySPI_W_CS(0);
}
void MySPI_Stop(void) //中止信号
{
MySPI_W_CS(1);
}
uint8_t MySPI_SwapByte(uint8_t ByteSend)//交换一字节
{
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) != SET);//等待TXE为1,无需手动清除
SPI_I2S_SendData(SPI2, ByteSend);
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) != SET);//等待RXNE为1,无需手动清除
return SPI_I2S_ReceiveData(SPI2);
}
W25Q127.c:
#include "stm32f10x.h" // Device header
#include "My_SPI.h"
#include "W25Q128_Ins.h"
void W25Q128_Init(void)
{
MySPI_Init();
}
void W25Q128_ReadID(uint8_t *MID, uint16_t *DID)//读取W25Q128的厂商ID和设备ID
{
MySPI_Start();
MySPI_SwapByte(W25Q128_JEDEC_ID); //根据指令集,9H为读ID号的指令
*MID = MySPI_SwapByte(W25Q128_DUMMY_BYTE); //交换数据,用FF交换厂商ID
*DID = MySPI_SwapByte(W25Q128_DUMMY_BYTE); //交换数据,用FF交换设备ID高8位
*DID <<= 8;
*DID |= MySPI_SwapByte(W25Q128_DUMMY_BYTE); //交换数据,用FF交换设备ID低8位
MySPI_Stop();
}//由于寄存器只读,故FF不会产生影响,且地址指针跟随时钟信号递增
void W25Q128_WriteEnable(void)
{
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q128_WRITE_ENABLE); //交换发送写使能的指令
MySPI_Stop(); //SPI终止
}
void W25Q128_WaitBusy(void) //等待Busy为0
{
uint32_t Timeout;
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q128_READ_STATUS_REGISTER_1); //交换发送读状态寄存器1的指令
Timeout = 100000; //给定超时计数时间
while ((MySPI_SwapByte(W25Q128_DUMMY_BYTE) & 0x01) == 0x01) //循环等待忙标志位。 //& 0x01,用掩码取出最低位;==0x01就是BUSY为1。BUSY为1,进入While死循环,进行等待,在次读出一次状态寄存器。BUSY为0,跳出循环。
{
Timeout --; //等待时,计数值自减
if (Timeout == 0) //自减到0后,等待超时
{
/*超时的错误处理代码,可以添加到此处*/
break; //跳出等待,不等了
}
}
MySPI_Stop(); //SPI终止
}
void W25Q128_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
uint16_t i; //定义变量
W25Q128_WriteEnable(); //写使能,写使能仅对之后跟随一条时序有效
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q128_PAGE_PROGRAM); //交换发送页编程的指令
MySPI_SwapByte(Address >> 16); //交换发送地址23~16位
MySPI_SwapByte(Address >> 8); //交换发送地址15~8位
MySPI_SwapByte(Address); //交换发送地址7~0位
for (i = 0; i < Count; i ++) //循环Count次
{
MySPI_SwapByte(DataArray[i]); //依次在起始地址后写入数据
}
MySPI_Stop(); //SPI终止
W25Q128_WaitBusy(); //事后等待忙,就是写入后,立刻等待,不忙了再退出
}
void W25Q128_SectorErase(uint32_t Address)
{
W25Q128_WriteEnable(); //写使能,写使能仅对之后跟随一条时序有效
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q128_SECTOR_ERASE_4KB); //交换发送扇区擦除的指令
MySPI_SwapByte(Address >> 16); //交换发送地址23~16位
MySPI_SwapByte(Address >> 8); //交换发送地址15~8位
MySPI_SwapByte(Address); //交换发送地址7~0位
MySPI_Stop(); //SPI终止
W25Q128_WaitBusy(); //事后等待忙,就是写入后,立刻等待,不忙了再退出
}
void W25Q128_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q128_READ_DATA); //交换发送读取数据的指令
MySPI_SwapByte(Address >> 16); //交换发送地址23~16位
MySPI_SwapByte(Address >> 8); //交换发送地址15~8位
MySPI_SwapByte(Address); //交换发送地址7~0位
for (i = 0; i < Count; i ++) //循环Count次
{
DataArray[i] = MySPI_SwapByte(W25Q128_DUMMY_BYTE); //依次在起始地址后读取数据
}
MySPI_Stop(); //SPI终止
}
作者:陽临