SPI通信图文概述:
代码:
软件SPI基层代码:
#include "stm32f10x.h" // Device header
/*对应的引脚号*/
#define CS GPIO_Pin_4
#define DO GPIO_Pin_6
#define SLK GPIO_Pin_5
#define DI GPIO_Pin_7
/*模块需要使用到的端口:GPIOA或GPIOB*/
#define BUS GPIOA
/*写SS片选的引脚位*/
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(BUS, CS,(BitAction)BitValue);
}
/*写SCK时钟的引脚位*/
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(BUS, SLK,(BitAction)BitValue);
}
/*写MOSI(主设备输出从设备输入)的引脚位*/
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(BUS, DI,(BitAction)BitValue);
}
/*读MISO(主设备输入从设备输出)的引脚位*/
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(BUS, DO);
}
void MySPI_Init(void)
{
//1.开启时钟(SPI输出模式为推挽输出,输入模式为上拉或浮空)
/* 对输出引脚的配置 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = CS | DI | SLK;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(BUS, &GPIO_InitStructure);
/* 对输入引脚的配置 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = DO;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(BUS, &GPIO_InitStructure);
//2.配置引脚的默认电平
MySPI_W_SS(1);//默认不选中从机设备
MySPI_W_SCK(0);//默认SCK低电平(为计划使用SPI的模式0)
}
/* 时序单元 */
//1.开始时序
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
//2.停止时序
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
//3.主机从机交换字节数据(SPI模式0)
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i;
for(i = 0; i < 8; i++)
{
MySPI_W_MOSI(ByteSend & 0x80); //主机输出一位
ByteSend <<= 1; //发送的数据左移一位(低位补0)
MySPI_W_SCK(1); //SCK上升沿(从机输入一位)
if(MySPI_R_MISO() == 1){ByteSend |= 0x01;} //从机输入的一位赋值ByteSend变量的最低位
MySPI_W_SCK(0); //SCK下降沿(主机输出一位)
}
return ByteSend;
}
对应SPI时序基本单元的关键代码:
1. 起始条件与终止条件:
/* 时序单元 */
//1.开始时序
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
//2.停止时序
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
2. 交换一个字节(模式0)
//3.主机从机交换字节数据(SPI模式0)
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i;
for(i = 0; i < 8; i++)
{
MySPI_W_MOSI(ByteSend & 0x80); //主机输出一位
ByteSend <<= 1; //发送的数据左移一位(低位补0)
MySPI_W_SCK(1); //SCK上升沿(从机输入一位)
if(MySPI_R_MISO() == 1){ByteSend |= 0x01;} //从机输入的一位赋值ByteSend变量的最低位
MySPI_W_SCK(0); //SCK下降沿(主机输出一位)
}
return ByteSend;
}
W25Q64芯片图文概述:
W25Q64基于软件SPI通信实现功能的代码:
W25Q64_Ins.h文件(存放W25Q64的关键指令集):
#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H
#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3
#define W25Q64_DUMMY_BYTE 0xFF
#endif
W25Q64.c文件:
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
void W25Q64_Init(void)
{
MySPI_Init();
}
/**
* @brief W25Q64_ReadID---获取W25Q64芯片的厂商ID和设备ID
* @param *MID---指针变量返回厂商ID
* @param *DID---指针变量返回设备ID
* @retval 无
*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start(); //开始时序
MySPI_SwapByte(W25Q64_JEDEC_ID); //发送获取ID的指令
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //获取从机返回的厂商ID
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //获取从机返回高8位的设备ID
*DID <<= 8; //把第一次获取的高8位设备ID移位到指针变量*DID的高8位
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //获取从机返回低8位的设备ID
MySPI_Stop(); //停止时序
}
/**
* @brief W25Q64_WriteEnable---开启W25Q64的写使能
* @param 无
* @retval 无
*/
void W25Q64_WriteEnable(void)
{
MySPI_Start(); //开始时序
MySPI_SwapByte(W25Q64_WRITE_ENABLE); //发送让W25Q64写使能的指令
MySPI_Stop(); //停止时序
}
/**
* @brief W25Q64_WaitBusy---等待W25Q64空闲状态
* @param 无
* @retval 无
*/
void W25Q64_WaitBusy(void)
{
uint32_t Timeout;
MySPI_Start(); //开始时序
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //发送读状态寄存器1的指令(用于查最后一位Busy)
Timeout = 100000;
/*
判断从机发送过来的状态寄存器1的字节数据最后一位(Busy位)是否为0(1为忙,0为不忙)
并做超时退出处理
*/
while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
{
Timeout--;
if(Timeout == 0)
{
break;
}
}
MySPI_Stop(); //停止时序
}
/**
* @brief W25Q64_PageProgram---写W25Q64一页区域(256Byte,页内地址为xxxx00-xxxxFF,不支持跨页写入)的字节数据
* @param Address---要写入的页地址
* @param *DataArray---要写入的数据数组指针变量
* @param Count---要写入的字节数据个数
* @retval 无
*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
W25Q64_WriteEnable();//写使能W25Q64
uint16_t i;
MySPI_Start(); //开始时序
MySPI_SwapByte(W25Q64_PAGE_PROGRAM); //发送写1页程序(即256Byte)的指令
/*发送三个字节(即24位)的地址*/
MySPI_SwapByte(Address >> 16); //发出高8位地址
MySPI_SwapByte(Address >> 8); //发出中间8位地址(左移8位,高位舍弃)
MySPI_SwapByte(Address); //发出低8位地址
/*循环发送指定个数的字节数据*/
for(i = 0; i < Count; i++)
{
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop(); //停止时序
W25Q64_WaitBusy(); //等待不忙后再退出程序(事后等待)
}
/**
* @brief W25Q64_SectorErase---擦除W25Q64一个扇区域(4KB)的字节数据
* @param Address---要擦除的扇地址(xxx000-xxxFFF内表示一扇区数据)
* @retval 无
*/
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable(); //写使能W25Q64
MySPI_Start(); //开始时序
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //选择擦除一个扇区域(4KB)的数据
/*发送三个字节(即24位)的地址*/
MySPI_SwapByte(Address >> 16); //发出高8位地址
MySPI_SwapByte(Address >> 8); //发出中间8位地址(左移8位,高位舍弃)
MySPI_SwapByte(Address); //发出低8位地址
MySPI_Stop(); //停止时序
W25Q64_WaitBusy(); //等待不忙后再退出程序(事后等待)
}
/**
* @brief W25Q64_ReadData---读W25Q64指定地址的字节数据(支持跨页读取,无限制读多少字节)
* @param Address---要读的起始地址
* @param *DataArray---要存入读取数据的数组指针变量
* @param Count---要读的字节数据个数
* @retval 无
*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start(); //开始时序
MySPI_SwapByte(W25Q64_READ_DATA); //发送写1页程序(即256Byte)的指令
/*发送三个字节(即24位)的地址*/
MySPI_SwapByte(Address >> 16); //发出高8位地址
MySPI_SwapByte(Address >> 8); //发出中间8位地址(左移8位,高位舍弃)
MySPI_SwapByte(Address); //发出低8位地址
/*循环读取指定个数的字节数据(W25Q64芯片会在每次读取后指针自增往后)*/
for(i = 0; i < Count; i++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
}
MySPI_Stop(); //停止时序
}
W25Q64.h文件:
#ifndef __W25Q64_H
#define __W25Q64_H
void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);
#endif
对应W25Q64基于软件SPI的关键代码:
写W25Q64一页区域(256Byte,页内地址为xxxx00-xxxxFF,不支持跨页写入)的字节数据:
每次写入先都需要写使能W25Q64,且执行步骤为: 发送指令(具体指令随要求改变)---> 发送24位写入的地址(即3个字节) --->交换数据(最多256Byte数据,且跨页会重新从页头重新写入).
/**
* @brief W25Q64_PageProgram---写W25Q64一页区域(256Byte,页内地址为xxxx00-xxxxFF,不支持跨页写入)的字节数据
* @param Address---要写入的页地址
* @param *DataArray---要写入的数据数组指针变量
* @param Count---要写入的字节数据个数
* @retval 无
*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
W25Q64_WriteEnable();//写使能W25Q64
uint16_t i;
MySPI_Start(); //开始时序
MySPI_SwapByte(W25Q64_PAGE_PROGRAM); //发送写1页程序(即256Byte)的指令
/*发送三个字节(即24位)的地址*/
MySPI_SwapByte(Address >> 16); //发出高8位地址
MySPI_SwapByte(Address >> 8); //发出中间8位地址(左移8位,高位舍弃)
MySPI_SwapByte(Address); //发出低8位地址
/*循环发送指定个数的字节数据*/
for(i = 0; i < Count; i++)
{
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop(); //停止时序
W25Q64_WaitBusy(); //等待不忙后再退出程序(事后等待)
}