STM32归档(下)
个人学习笔记,因为STM32归档越来越长(怕目录太长出问题),所以有了下。
I2C通信
I2C软件模拟通信
I2C简介
• I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线
• 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
• 同步,半双工
• 带数据应答
• 支持总线挂载多设备(一主多从、多主多从)
硬件电路
• 所有I2C设备的SCL连在一起,SDA连在一起
• 设备的SCL和SDA均要配置成开漏输出模式
• SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

开漏输出模式有个线与的现象,只要有一个设备输出低电平,总线就处于低电平。

SCLKN1OUT输出低电平时,mos管导通。输出高电平时,MOS管断开,处于浮空输入。
I2C时序基本单元
起始条件:
SCL高电平期间,SDA从高电平切换到低电平

终止条件:
SCL高电平期间,SDA从低电平切换到高电平

只能由主机产生起始或终止条件,从机不允许,且SCL和SDA置高电平都由上拉电阻实现。
发送一个字节:
SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

一般情况下,当SCL产生上升沿时,从机就从SDA读取数据完毕,所以主机需要提前放置数据置SDA。
接收一个字节:
SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

发送应答:
主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

接收应答:
主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

应答功能可用于判断数据是否传输成功,也可以用于判断是否需要继续传输。
数据帧示例
指定地址写:
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)。

当前地址读:
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

指定地址读:
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)。

MPU6050
MPU6050简介:
• MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角(欧拉角),常应用于平衡车、飞行器等需要检测自身姿态的场景
• 3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
• 3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度

芯片上会有个小图标标示x与y轴。

加速度计可以理解为一个弹簧测力计,当物体有加速度时,将使弹簧受力,改变电位器。
另外,该加速度计只具有静态稳定性,不具备动态稳定性。

而陀螺仪传感器只具有动态稳定性,不具备静态稳定性。
MPU6050参数:
• 16位ADC采集传感器的模拟信号,量化范围:-32768~32767
• 加速度计满量程选择:±2、±4、±8、±16(g)
• 陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
• 可配置的数字低通滤波器
• 可配置的时钟源
• 可配置的采样分频
• I2C从机地址:1101000(AD0=0) 1101001(AD0=1)
值得注意的是,I2C的数据格式为8位一个包,地址位为7位,此后有一位为读写位。所以写入MPU6050的地址为0x68再或上读写位,也可以直接写入0xD0,读写位和地址位合为一个8位数据。
硬件电路:

整体硬件电路由左上角的稳压器,左下引出排针和右侧芯片组成,有排针引脚可知该硬件支持拓展。

另外,在配置硬件的自由落体检测,运动检测等,可触发INT输出中段信号。
MPU6050框图:


MPU6050的I2C通信格式:

寄存器地址:

Addr(Hex):16进制地址
Addr(Dec):10进制地址
Register Name:寄存器名称
SerialI/F:读写权限
在文档“RM-MPU-6000A”中有该硬件的寄存器表,通过此表,可得知相应的寄存器地址。如果需要读取特定的寄存器数据(比如加速度计的数据)可由此表查询地址。当然,也可以写入命名到寄存器,用于设置硬件模式等等。
所有寄存器上电默认为0x00,除了107号寄存器上电默认为0x40(睡眠),117号寄存器为0x68。
程序示例
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
uint8_t ID; //设备ID号
int16_t AX,AY,AZ,GX,GY,GZ; //xyz加速度和角速度
int main(void)
{
//初始化
OLED_Init();
MPU6050_Init();
ID = MPU6050_GetID(); //获取设备ID号
OLED_ShowString(1,1,"ID:");
OLED_ShowHexNum(1,4,ID,2);
while (1)
{
MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ); //获取加速度和角速度的参数
OLED_ShowSignedNum(2,1,AX,5);
OLED_ShowSignedNum(3,1,AY,5);
OLED_ShowSignedNum(4,1,AZ,5);
OLED_ShowSignedNum(2,8,GX,5);
OLED_ShowSignedNum(3,8,GY,5);
OLED_ShowSignedNum(4,8,GZ,5);
}
}
MyI2c.c
#include "stm32f10x.h"
#include "Delay.h"
void MyI2C_W_SCL(uint8_t BitValue) //封装写SCL(时钟线)函数,并加上小延时确保相应。
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue) //封装写SDA(数据线)函数,
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void) //读SDA
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
void MyI2C_Init(void) //初始化
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11); //初始化I2C双线为高电平
}
void MyI2C_Start(void) //启动
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_STOP(void) //停止
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
void MyI2C_SendByte(uint8_t Byte) //发送一位字节
{
uint8_t i;
for(i=0;i<8;i++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReceiveByte(void) //接收一位字节
{
uint8_t i,Byte = 0x00;
for(i=0;i<8;i++)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
if(MyI2C_R_SDA() == 1) Byte |= (0x80>>i);
MyI2C_W_SCL(0);
}
return Byte;
}
void MyI2C_SendAck(uint8_t AckBit) //发送应答
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
uint8_t MyI2C_ReceiveAck(void) //接收应答
{
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
MPU6050.c
#include "stm32f10x.h"
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0 //宏定义MPU6050地址,应该为0x68,0xD0为7位地址或上读写位的整合。
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data) //指定地址写数据
{
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data); //如果需要连续写多个字节,可用for循环执行注释的这两句。
MyI2C_ReceiveAck(); //当写入多字节时,每写入一个字节,地址自动+1。
MyI2C_STOP();
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress) //指定地址读数据
{
uint8_t Data;
//第一个起始条件发送地址
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
//第二个起始条件读地址,读写地址位重新指定,通信协议读写位只能在起始条件后的一个字节设定。
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS | 0x01); //指定地址或上“读数据”位
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
MyI2C_SendAck(1);
MyI2C_STOP();
return Data;
}
void MPU6050_Init(void)
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //解除睡眠,时钟源为陀螺仪时钟
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); //6轴一直启用,均不待机
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采样分频为10
MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //滤波参数为最大
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪为最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度为最大量程
}
uint8_t MPU6050_GetID(void) //获取设备ID号
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ) //获取搜集的参数
{
uint8_t DataH, DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8)| DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8)| DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8)| DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8)| DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8)| DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8)| DataL;
}
MPU6050_Reg.h
#ifndef __MPU6050_Reg__H
#define __MPU6050_Reg__H
//控制寄存器
#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C
//收集的数据存放的寄存器
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
//电源管理寄存器和设备ID寄存器
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75
#endif
先写好I2C通讯时序,再通过时序组成数据帧。然后在MPU6050中对数据帧进行封装,此后可在主函数直接调用对硬件的读写函数。因为直接写0x3c一类写入寄存器不易于理解和后续维护,所以引入“MPU6050—Reg”,将寄存器地址进行宏定义。
STM32硬件I2C通信
I2C外设简介
• STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
• 支持多主机模型
• 支持7位/10位地址模式
• 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
• 支持DMA
• 兼容SMBus协议(系统管理总线)
• STM32F103C8T6 硬件I2C资源:I2C1、I2C2
I2C框图

I2C基本结构

移位寄存器左移,高位先行。
主机发送

7位和10位主要差别为寻址位是7位或是10位。当一个时序完成后,STM32会置EVx位(可理解为大标志位)。通过对此位的查询,可得知事件状态。
主机接收

EV5事件表示起始条件已发送
EV6事件表示寻址已完成
软件/硬件波形对比
软件波形:

硬件波形:

通过对比可知:
硬件I2C的波形会更加规整,每个时钟的周期和占空比都非常一致,而软件I2C由于操作引脚后加入了延时,导致每个时钟的周期和占空比不规整。由于I2C是同步时序,所以影响不大。
SCL低电平写,高电平读。保证尽早的原则,直接在SCL下降沿写,上升沿读。
程序示例
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
uint8_t ID;
int16_t AX,AY,AZ,GX,GY,GZ;
int main(void)
{
OLED_Init();
MPU6050_Init();
ID = MPU6050_GetID();
OLED_ShowString(1,1,"ID:");
OLED_ShowHexNum(1,4,ID,2);
while (1)
{
MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ);
OLED_ShowSignedNum(2,1,AX,5);
OLED_ShowSignedNum(3,1,AY,5);
OLED_ShowSignedNum(4,1,AZ,5);
OLED_ShowSignedNum(2,8,GX,5);
OLED_ShowSignedNum(3,8,GY,5);
OLED_ShowSignedNum(4,8,GZ,5);
}
}
MPU6050.c
#include "stm32f10x.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0 //宏定义MPU6050地址
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT) //等待EV事件,防止用while卡死,改用定时退出
{
uint32_t TimeOut;
TimeOut = 10000;
while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)
{
TimeOut--;
if(TimeOut == 0) break;
}
}
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data) //写字节
{
I2C_GenerateSTART(I2C2,ENABLE);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
I2C_SendData(I2C2,RegAddress);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
I2C_SendData(I2C2,Data);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);
I2C_GenerateSTOP(I2C2,ENABLE);
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress) //读字节
{
uint8_t Data;
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2,RegAddress);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
I2C_AcknowledgeConfig(I2C2,DISABLE);
I2C_GenerateSTOP(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);
Data = I2C_ReceiveData(I2C2);
I2C_AcknowledgeConfig(I2C2,ENABLE);
return Data;
}
void MPU6050_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_Init(I2C2,&I2C_InitStructure);
I2C_Cmd(I2C2,ENABLE);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //解除睡眠,时钟源为陀螺仪时钟
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); //6轴一直启用,均不待机
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采样分频为10
MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //滤波参数为最大
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪为最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度为最大量程
}
uint8_t MPU6050_GetID(void) //获取设备ID号
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ) //获取数据
{
uint8_t DataH, DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8)| DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8)| DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8)| DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8)| DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8)| DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8)| DataL;
}
MPU6050_Reg.h
#ifndef __MPU6050_Reg__H
#define __MPU6050_Reg__H
#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75
#endif
I2C缺点

I2C的电路结构是开漏外加上拉电阻,使通信线高电平的驱动能力比较弱,导致通信线由低电平到高电平时上升沿耗时长,这限制了I2C的最大通信速度。而SPI设计简单粗暴,传输速度也比I2C快很多。
SPI通信
SPI软件模拟通信
SPI简介
• SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
• 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
• 同步,全双工
• 支持总线挂载多设备(一主多从,不支持多主多从)
SCK提供时钟信号,数据位的输出输入都是在SCK的上升沿或下降沿进行的,此时数据位的收发时刻就可以明确确定。
硬件电路
• 所有SPI设备的SCK、MOSI、MISO分别连在一起
• 主机另外引出多条SS控制线,分别接到各从机的SS引脚
• 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入(SS为低电平片选时,从机才允许变为推挽输出)

移位示意图



该示例为高位先行。
由波特率发生器产生的时钟驱动主机的移位寄存器进行移位。时钟的上升沿,所有移位寄存器向左移一位,移位的数据放在MOSI和MISO上,发生器的下降沿,将采样MOSI和MISO到移位寄存器最低位存储。
SPI时序基本单元
起始条件:

SS从高电平切换到低电平
终止条件:

SS从低电平切换到高电平
交换一个字节(模式0)

CPOL(时钟极性) = 0:空闲状态时,SCK为低电平
CPHA(时钟相位) = 0:SCK第一个边沿移入数据,第二个边沿移出数据
因为SCK第一个边沿要移入数据至寄存器,所以需提前写入数据到数据线上,相较于模式1提前了数据线的相位。
交换一个字节(模式1)

CPOL = 0:空闲状态时,SCK为低电平
CPHA = 1:SCK第一个边沿移出数据,第二个边沿移入数据
即:SCK第一个上升沿(第一个边沿),双方移位寄存器同时移出寄存器数据置MOSI和MISO上,SCK下降沿(第二个边沿)的时候,双方移位寄存器同时移入MOSI和MISO的数据置寄存器,达成数据交换。
交换一个字节(模式2)

CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
交换一个字节(模式3)

CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
模式2和模式3,相较与模式0和模式1,仅是SCK时钟极性变化而已。
SPI时序
示例时序均为模式0
发送指令

向SS指定的设备,发送指令(0x06)写使能。
指定地址写

向SS指定的设备,发送写指令(0x02),
随后在指定地址(Address[23:0])下,写入指定数据(Data)。
指定地址读

向SS指定的设备,发送读指令(0x03),
随后在指定地址(Address[23:0])下,读取从机数据(Data)
W25Q64
W25Q64简介:
• W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景
• 存储介质:Nor Flash(闪存)
• 时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
• 存储容量(24位地址): (最大寻址能达到16M)
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte

硬件电路:



CS在图中引脚标识为“/CS”,表示低电平有效其余同理。有需要时,可启用IO2和IO3,相当于多线并行传输,提高传输效率。
W25Q64框图

W25Q64有相应的写保护和地址锁存。需要注意的是地址分配方式。以8M芯片为例(最后一位地址到7F),64KB划为一页,每页又可以以4KB为单位分成扇区。
Flash操作注意事项:
写入操作时:
• 写入操作前,必须先进行写使能
• 每个数据位只能由1改写为0,不能由0改写为1
• 写入数据前必须先擦除,擦除后,所有数据位变为1
• 擦除必须按最小擦除单元进行(例如示例芯片的最小单位为4KB扇区)
• 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
• 写入操作结束后,芯片进入忙状态,不响应新的读写操作
读取操作时:
• 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取
程序示例
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
//
uint8_t MID;
uint16_t DID;
uint8_t ArrayWrite[] = {0x55, 0x66, 0x77, 0x88};
uint8_t ArrayRead[4];
int main(void)
{
//初始化
OLED_Init();
W25Q64_Init();
OLED_ShowString(1, 1, "MID: DID:");
OLED_ShowString(2, 1, "W:");
OLED_ShowString(3, 1, "R:");
W25Q64_ReadID(&MID,&DID); //读取设备ID号
OLED_ShowHexNum(1,5,MID,2); //显示设备ID号
OLED_ShowHexNum(1,12,DID,4);
//
W25Q64_SectorErase(0X000000); //擦除扇区,为写入数据做准备
W25Q64_PageProgram(0X000000,ArrayWrite,4); //向指定地址写入数据,写入四个数据
W25Q64_ReadData(0X000000,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)
{
}
}
MySPI.C
#include "stm32f10x.h"
// 对引脚操控进行基础的封装
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); // 设置片选信号 (SS/CS) 的状态
}
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); // 设置时钟信号 (SCK) 的状态
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); // 设置主机输出从机输入 (MOSI) 的状态
}
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); // 读取从机输出主机输入 (MISO) 的状态
}
// 初始化SPI相关的GPIO引脚
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);
// 初始化SPI外设,设置初始状态
MySPI_W_SS(1); // 片选信号高电平
MySPI_W_SCK(0); // 时钟信号低电平
}
//起始时序
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
//终止时序
void MySPI_STOP(void)
{
MySPI_W_SS(1);
}
//交换数据
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for(i = 0; i < 8; i++)
{
MySPI_W_MOSI(ByteSend & (0x80 >> i)); // 设置主机输出数据
MySPI_W_SCK(1); // 拉高时钟信号
if(MySPI_R_MISO() == 1)
ByteReceive |= (0x80 >> i); // 读取从机输出的数据
MySPI_W_SCK(0); // 拉低时钟信号
}
return ByteReceive;
}
W25Q64.c
#include "stm32f10x.h"
#include "MySPI.h"
#include "W25Q64_Ins.h"
// 初始化SPI接口
void W25Q64_Init(void)
{
MySPI_Init();
}
//读设备ID
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start(); // 起始条件时序
MySPI_SwapByte(W25Q64_JEDEC_ID); // 发送指令
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 读取制造商ID
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 读取设备ID的低8位
*DID <<= 8;
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 读取设备ID的高8位
MySPI_STOP(); // 终止条件时序
}
//写使能
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE); // 发送写使能指令
MySPI_STOP();
}
//等待设备忙,并规定时间限制
void W25Q64_WaitBusy(void)
{
uint32_t Timeout = 100000;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); // 发送读状态寄存器指令
while ((MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1) & 0x01) == 0x01)
{
Timeout--;
if (Timeout == 0)
{
break;
}
}
MySPI_STOP();
}
//指定地址写入数据,
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
W25Q64_WriteEnable(); // 写使能
uint16_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM); // 发送页编程指令
MySPI_SwapByte(Address >> 16); // 发送地址的高8位
MySPI_SwapByte(Address >> 8); // 发送地址的中间8位
MySPI_SwapByte(Address); // 发送地址的低8位
for (i = 0; i < Count; i++)
{
MySPI_SwapByte(DataArray[i]); // 发送要写入的数据
}
MySPI_STOP();
W25Q64_WaitBusy(); // 等待操作完成
}
//擦除指定地址的扇区
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable(); // 启用写操作
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); // 发送扇区擦除指令
MySPI_SwapByte(Address >> 16); // 发送地址的高8位
MySPI_SwapByte(Address >> 8); // 发送地址的中间8位
MySPI_SwapByte(Address); // 发送地址的低8位
MySPI_STOP();
W25Q64_WaitBusy(); // 等待操作完成
}
//指定地址读
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA); // 发送读数据指令
MySPI_SwapByte(Address >> 16); // 发送地址的高8位
MySPI_SwapByte(Address >> 8); // 发送地址的中间8位
MySPI_SwapByte(Address); // 发送地址的低8位
for (i = 0; i < Count; i++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 读取数据
}
MySPI_STOP();
}
W25Q64_Ins.h
#ifndef __W25Q64_Ins__H
#define __W25Q64_Ins__H
// W25Q64指令
#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
程序设计思路和I2C差不多,都是先用软件模拟SPI时序基本单元,再将基本单元组成传输时序。然后再根据硬件对其再封装。写指令的常量将用宏定义代替,便于理解。
STM32硬件SPI通信
SPI外设简介
• STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
• 可配置8位/16位数据帧、高位先行/低位先行
• 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
• 支持多主机模型、主或从操作
• 可精简为半双工/单工通信
• 支持DMA
• 兼容I2S协议
• STM32F103C8T6 硬件SPI资源:SPI1、SPI2
SPI1挂载在APB2最大允许频率为72MHz,SPI2挂载在APB1最大允许频率为36MHz。使用不同的SPI时,应注意挂载的总线时钟。
主模式全双工连续传输

连续传输将传输效率最大化。先写入数据0xF1到发送缓冲器,再开始传输。每次数据传输,都是如图,第二个数据0xF2发送时,0xF1才被接收。并不是0xF1发送 → 0xF1接收,再0xF2发送 → 0xF2接收。而是0xF1先写入发送缓冲器,时序开始时发送 0xF1,并将 0xF2写入发送缓冲器,再0xF1接收 → (同时)0xF2发送。0xF2接收 → (同时)0xF3发送以此类推。。。发送总比接收早,数据传输是紧跟的。
非连续传输

非连续传输相较于连续传输简单,效率不如连续传输。因为连续传输的数据是紧挨着传输,而非连续传输,0xF1发送 → 0xF1接收。将等待接收完毕后再发送,而不是先将0xF1写入缓冲器,时序开始时0xF1挂载到数据线,在交换数据,接收0xF1的同时发送0xF2.
当然,SPI通信的核心是主从两机寄存器数据交换,描述只是便于表达,切记数据是双向同时传输
软件/硬件波形对比
软件模拟SPI

硬件SPI

区别无非是软件有延迟,并且波形没有硬件平滑等等,和I2C波形软硬件对比差不多。
程序示例
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
//
uint8_t MID;
uint16_t DID;
uint8_t ArrayWrite[] = {0x55, 0x66, 0x77, 0x88};
uint8_t ArrayRead[4];
int main(void)
{
//初始化
OLED_Init();
W25Q64_Init();
OLED_ShowString(1, 1, "MID: DID:");
OLED_ShowString(2, 1, "W:");
OLED_ShowString(3, 1, "R:");
W25Q64_ReadID(&MID,&DID); //读取设备ID号
OLED_ShowHexNum(1,5,MID,2); //显示设备ID号
OLED_ShowHexNum(1,12,DID,4);
//
W25Q64_SectorErase(0X000000); //擦除扇区,为写入数据做准备
W25Q64_PageProgram(0X000000,ArrayWrite,4); //向指定地址写入数据,写入四个数据
W25Q64_ReadData(0X000000,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)
{
}
}
MySPI.c
#include "stm32f10x.h"
//对引脚操控进行基础的封装
void MySPI_W_SS(uint8_t BitValue) //片选信号,SS也称CS引脚
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}
void MySPI_Init(void)
{
//GPIO引脚根据API硬件外设配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,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_AF_PP;
GPIO_InitStructure.GPIO_Pin = 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);
//配置硬件SPI
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //分频系数为128.
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //第一个边沿采样
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //空闲状态时,SCK为低电平
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; //配置为软件NSS
SPI_Init(SPI1,&SPI_InitStructure);
//SPI使能
SPI_Cmd(SPI1,ENABLE);
//初始化
MySPI_W_SS(1); //默认不选中从机
}
//起始时序
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
//终止时序
void MySPI_STOP(void)
{
MySPI_W_SS(1);
}
//交换数据
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1,ByteSend);
while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
W25Q64.c
#include "stm32f10x.h"
#include "MySPI.h"
#include "W25Q64_Ins.h"
// 初始化SPI接口
void W25Q64_Init(void)
{
MySPI_Init();
}
//读设备ID
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start(); // 起始条件时序
MySPI_SwapByte(W25Q64_JEDEC_ID); // 发送指令
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 读取制造商ID
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 读取设备ID的低8位
*DID <<= 8;
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 读取设备ID的高8位
MySPI_STOP(); // 终止条件时序
}
//写使能
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE); // 发送写使能指令
MySPI_STOP();
}
//等待设备忙,并规定时间限制
void W25Q64_WaitBusy(void)
{
uint32_t Timeout = 100000;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); // 发送读状态寄存器指令
while ((MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1) & 0x01) == 0x01)
{
Timeout--;
if (Timeout == 0)
{
break;
}
}
MySPI_STOP();
}
//指定地址写入数据,
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
W25Q64_WriteEnable(); // 写使能
uint16_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM); // 发送页编程指令
MySPI_SwapByte(Address >> 16); // 发送地址的高8位
MySPI_SwapByte(Address >> 8); // 发送地址的中间8位
MySPI_SwapByte(Address); // 发送地址的低8位
for (i = 0; i < Count; i++)
{
MySPI_SwapByte(DataArray[i]); // 发送要写入的数据
}
MySPI_STOP();
W25Q64_WaitBusy(); // 等待操作完成
}
//擦除指定地址的扇区
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable(); // 启用写操作
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); // 发送扇区擦除指令
MySPI_SwapByte(Address >> 16); // 发送地址的高8位
MySPI_SwapByte(Address >> 8); // 发送地址的中间8位
MySPI_SwapByte(Address); // 发送地址的低8位
MySPI_STOP();
W25Q64_WaitBusy(); // 等待操作完成
}
//指定地址读
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA); // 发送读数据指令
MySPI_SwapByte(Address >> 16); // 发送地址的高8位
MySPI_SwapByte(Address >> 8); // 发送地址的中间8位
MySPI_SwapByte(Address); // 发送地址的低8位
for (i = 0; i < Count; i++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 读取数据
}
MySPI_STOP();
}
W25Q64_Ins.h
#ifndef __W25Q64_Ins__H
#define __W25Q64_Ins__H
// W25Q64指令
#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
软硬件SPI的主函数没什么差别,只是将MySPI.c中的软件模拟部分替换成STM32标准库中的SPI函数。
1 | //NRF24L01寄存器操作命令 |