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示例差不多,仅是将软件时序替换为STM32标准库函数,并且STM32对通信的状态有事件标志位和定义,通过读取标志位就明白时序是否完成。

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//NRF24L01寄存器操作命令
#define NRF_READ_REG 0x00 //读配置寄存器,低5位为寄存器地址
#define NRF_WRITE_REG 0x20 //写配置寄存器,低5位为寄存器地址
#define RD_RX_PLOAD 0x61 //读RX有效数据,1~32字节
#define WR_TX_PLOAD 0xA0 //写TX有效数据,1~32字节
#define FLUSH_TX 0xE1 //清除TX FIFO寄存器.发射模式下用
#define FLUSH_RX 0xE2 //清除RX FIFO寄存器.接收模式下用
#define REUSE_TX_PL 0xE3 //重新使用上一包数据,CE为高,数据包被不断发送.
#define NOP 0xFF //空操作,可以用来读状态寄存器

//SPI(NRF24L01)寄存器地址
#define CONFIG 0x00 //配置寄存器地址;bit0:1接收模式,0发射模式;bit1:电选
//择;bit2:CRC模式;bit3:CRC使能;
//bit4:中断MAX_RT(达到最大重发次数中断)使能;bit5:中断TX_DS使
//能;bit6:中断RX_DR使能
#define EN_AA 0x01 //使能自动应答功能 bit0~5,对应通道0~5
#define EN_RXADDR 0x02 //接收地址允许,bit0~5,对应通道0~5
#define SETUP_AW 0x03 //设置地址宽度(所有数据通道):bit1,0:00,3字节;01,4字节;02,5字
//节;
#define SETUP_RETR 0x04 //建立自动重发;bit3:0,自动重发计数器;bit7:4,自动重发延时
//250*x+86us
#define RF_CH 0x05 //RF通道,bit6:0,工作通道频率;
#define RF_SETUP 0x06 //RF寄存器;bit3:传输速率(0:1Mbps,1:2Mbps);bit2:1,发射功
//率;bit0:低噪声放大器增益
#define STATUS 0x07 //状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最
//大:6);bit4,达到最多次重发
//bit5:数据发送完成中断;bit6:接收数据中断;
#define MAX_TX 0x10 //达到最大发送次数中断
#define TX_OK 0x20 //TX发送完成中断
#define RX_OK 0x40 //接收到数据中断

#define OBSERVE_TX 0x08 //发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器
#define CD 0x09 //载波检测寄存器,bit0,载波检测;
#define RX_ADDR_P0 0x0A //数据通道0接收地址,最大长度5个字节,低字节在前
#define RX_ADDR_P1 0x0B //数据通道1接收地址,最大长度5个字节,低字节在前

#define RX_ADDR_P2 0x0C //数据通道2接收地址,最低字节可设置
//高字节,必须同 RX_ADDR_P1[39:8]相等;

#define RX_ADDR_P3 0x0D //数据通道3接收地址,最低字节可设置
//高字节,必须同 RX_ADDR_P1[39:8]相等;

#define RX_ADDR_P4 0x0E //数据通道4接收地址,最低字节可设置
//高字节,必须同RX_ADDR_P1[39:8]相等;

#define RX_ADDR_P5 0x0F //数据通道5接收地址,最低字节可设置,
//高字节,必须同RX_ADDR_P1[39:8]相等;

#define TX_ADDR 0x10 //发送地址(低字节在前),ShockBurstTM模式下,RX_ADDR_P0与此地址
相等
#define RX_PW_P0 0x11 //接收数据通道0有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P1 0x12 //接收数据通道1有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P2 0x13 //接收数据通道2有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P3 0x14 //接收数据通道3有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P4 0x15 //接收数据通道4有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P5 0x16 //接收数据通道5有效数据宽度(1~32字节),设置为0则非法
#define NRF_FIFO_STATUS 0x17 //FIFO状态寄存器;bit0,RX FIFO寄存器空标志;
//bit1,RX FIFO满标志;bit2,3,保留
//bit4,TX FIFO空标志;bit5,TX FIFO满标志;bit6,1,循环发送上一
//数据包.0,不循环;
/
//24L01操作线
#define NRF24L01_CE PAout(4) //24L01片选信号
#define NRF24L01_CSN PCout(4) //SPI片选信号
#define NRF24L01_IRQ PAin(1) //IRQ主机数据输入
//24L01发送接收数据宽度定义
#define TX_ADR_WIDTH 5 //5字节的地址宽度
#define RX_ADR_WIDTH 5 //5字节的地址宽度
#define TX_PLOAD_WIDTH 32 //32字节的用户数据宽度
#define RX_PLOAD_WIDTH 32 //32字节的用户数据宽度