软件实现SPI:通过普通GPIO电平实现通信
代码整体框架:
1、建立SPI通信层的.c和.h模块,编写SPI底层的GPIO初始化和3个时序基本单元(起始、终止、交换一个字节
)
2、建立W25Q64的.c和.h模块,基于SPI通信模块实现发送指令、指定地址读、指定地址写
3、在main.c里调用W25Q64模块,初始化、拿到数据、显示数据
【基本驱动代码】
1、完成SPI驱动文件导入操作和编写驱动程序基本代码(参考之前文章)
//将【MySPI】驱动文件放在【Hardware】文件夹中
//【MySPI】名称是为了防止与库函数里面的SPI重复
2、在
MySPI.c中初始化函数
MySPI_Init
//定义函数,对操作端口的库函数进行封装//SPI初始化void MySPI_Init(void){//主机输出引脚配置为【推挽输出】、主机输入引脚配置为【浮空/上拉输入】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_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);//置引脚默认电平MySPI_W_SS(1);//不选中从机MySPI_W_SCK(0);//使用SPI模式0,默认时钟为低电平}
时序单元函数
//写从机选择void MySPI_W_SS(uint8_t BitValue){//参考接线,从机选择对应PA4引脚GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);}//写时钟void MySPI_W_SCK(uint8_t BitValue){//参考接线,时钟对应PA5引脚GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);}//写主机输出从机输入void MySPI_W_MOSI(uint8_t BitValue){//参考接线,主机输出从机输入对应PA7引脚GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);}//读主机输入从机输出uint8_t MySPI_R_MISO(void){//参考接线,主机输入从机输出对应PA6引脚return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);}/**********************SPI时序逻辑**********************///起始信号void MySPI_Start(void){//将SS置低电平MySPI_W_SS(0);}//终止信号void MySPI_Stop(void){//将SS置高电平MySPI_W_SS(1);}//交换一个字节(函数可读、可写)//SPI共有四种模式【0、1、2、3】//W25Q64支持模式【0、3】uint8_t MySPI_SwapByte(uint8_t ByteSend){uint8_t ByteReceive = 0x00;uint8_t i;for(i = 0;i < 8;i ++){//SS下降沿后——主机和从机同时移出数据,但从机移出不需要管理MySPI_W_MOSI(ByteSend & ( 0x80 >> i ) );//发送最高位MySPI_W_SCK(1);//SS产生上升沿——从机自动把MOSI的数据读走,主机则需要读MISOif ( MySPI_R_MISO() == 1 ){ByteReceive |= ( 0x80 >> i );//最高位存在ByteReceive}MySPI_W_SCK(0);//SS产生下降沿}return ByteReceive;}
3、在MySPI.h中声明初始化函数
MySPI_Init及其
时序单元函数
void MySPI_Init(void);void MySPI_Start(void);void MySPI_Stop(void);uint8_t MySPI_SwapByte(uint8_t ByteSend);
【W25Q64部分】
1、完成W25Q64驱动文件导入操作和编写驱动程序基本代码(参考之前文章)
//将【W25Q64】驱动文件放在【Hardware】文件夹中
2、在W25Q64.c中初始化函数
W25Q64
_Init
和
读取ID函数
#include "MySPI.h"void W25Q64_Init(void){//调用底层SPIMySPI_Init();}读取ID函数//读取ID函数——函数有两个返回值,用【指针】实现多返回值/*读取ID号的时序:起始、交换【发送】指令9F、连续交换【接收】3个字节、停止连续交换接收3个字节——字节1:厂商ID、字节2&3:设备ID*///输出参数1:8位厂商ID//输出参数2:16位设备IDvoid W25Q64_ReadID(uint8_t *MID,uint16_t *DID){//起始信号——SS低电平MySPI_Start();//目的:交换【发送】指令9F(读取ID)MySPI_SwapByte(0x9F);//返回值:交换【接收】在此处没有意义——接收什么不重要//目的:交换【接收】从机返回的ID(字节1:厂商ID)*MID = MySPI_SwapByte(0xFF);//交换【发送】在此处没有意义——发送什么不重要//目的:交换【接收】从机返回的ID(字节2:设备ID高8位)*DID = MySPI_SwapByte(0xFF);//交换【发送】在此处没有意义——发送什么不重要//将第一次【DID】读到的数据移到高8位*DID <<= 8;//目的:交换【接收】从机返回的ID(字节3:设备ID低8位)*DID |= MySPI_SwapByte(0xFF);//交换【发送】在此处没有意义——发送什么不重要//结束信号——SS高电平MySPI_Stop();}
3、在W25Q64.h中声明初始化函数
W25Q64
_Init
和
读取ID函数
void W25Q64_Init(void);void W25Q64_ReadID(uint8_t *MID,uint16_t *DID);
4、在主程序main.c中
#include
"W25Q64
.h
"
#include "W25Q64.h"
5、在主循环中编写程序主体
//存ID号的变量uint8_t MID;uint16_t DID;int main(void){OLED_Init();W25Q64_Init();//获取ID号以验证SPI驱动代码是否正确W25Q64_ReadID(&MID,&DID);OLED_ShowHexNum(1,1,MID,2);OLED_ShowHexNum(1,8,DID,4);while(1){}}
实现功能:初始化OLED和W25Q64(包括底层SPI),上电后SPI发送读取ID指令并接收数据,通过OLED显示厂商ID【EF】和设备号【4017】