当前位置: 首页 > article >正文

stm32week6

stm32学习

三.通信

5.硬件读取I2C

硬件读取I2C的代码(main.c与软件读取相同):

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"

#define MPU6050_ADDRESS  0xD0  //MPU6050的I2C从机地址

/**
  * 函    数:MPU6050等待事件
  * 参    数:同I2C_CheckEvent
  * 返 回 值:无
  */
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
 uint32_t Timeout;
 Timeout = 10000;         //给定超时计数时间
 while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) //循环等待指定事件
 {
  Timeout --;          //等待时,计数值自减
  if (Timeout == 0)        //自减到0后,等待超时
  {
   /*超时的错误处理代码,可以添加到此处*/
   break;          //跳出等待,不等了
  }
 }
}

/**
  * 函    数:MPU6050写寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
 I2C_GenerateSTART(I2C2, ENABLE);          //硬件I2C生成起始条件
 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);     //等待EV5
 
 I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送
 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6
 
 I2C_SendData(I2C2, RegAddress);           //硬件I2C发送寄存器地址
 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);   //等待EV8
 
 I2C_SendData(I2C2, Data);            //硬件I2C发送数据
 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);    //等待EV8_2
 
 I2C_GenerateSTOP(I2C2, ENABLE);           //硬件I2C生成终止条件
}

/**
  * 函    数:MPU6050读寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 返 回 值:读取寄存器的数据,范围:0x00~0xFF
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
 uint8_t Data;
 
 I2C_GenerateSTART(I2C2, ENABLE);          //硬件I2C生成起始条件
 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);     //等待EV5
 
 I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送
 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6
 
 I2C_SendData(I2C2, RegAddress);           //硬件I2C发送寄存器地址
 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);    //等待EV8_2
 
 I2C_GenerateSTART(I2C2, ENABLE);          //硬件I2C生成重复起始条件
 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);     //等待EV5
 
 I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);  //硬件I2C发送从机地址,方向为接收
 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);  //等待EV6
 
 I2C_AcknowledgeConfig(I2C2, DISABLE);         //在接收最后一个字节之前提前将应答失能
 I2C_GenerateSTOP(I2C2, ENABLE);           //在接收最后一个字节之前提前申请停止条件
 
 MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);    //等待EV7
 Data = I2C_ReceiveData(I2C2);           //接收数据寄存器
 
 I2C_AcknowledgeConfig(I2C2, ENABLE);         //将应答恢复为使能,为了不影响后续可能产生的读取多字节操作
 
 return Data;
}

/**
  * 函    数:MPU6050初始化
  * 参    数:无
  * 返 回 值:无
  */
void MPU6050_Init(void)
{
 /*开启时钟*/
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);  //开启I2C2的时钟
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  //开启GPIOB的时钟
 
 /*GPIO初始化*/
 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);     //将PB10和PB11引脚初始化为复用开漏输出
 
 /*I2C初始化*/
 I2C_InitTypeDef I2C_InitStructure;      //定义结构体变量
 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;    //模式,选择为I2C模式
 I2C_InitStructure.I2C_ClockSpeed = 50000;    //时钟速度,选择为50KHz
 I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;  //时钟占空比,选择Tlow/Thigh = 2
 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;    //应答,选择使能
 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //应答地址,选择7位,从机模式下才有效
 I2C_InitStructure.I2C_OwnAddress1 = 0x00;    //自身地址,从机模式下才有效
 I2C_Init(I2C2, &I2C_InitStructure);      //将结构体变量交给I2C_Init,配置I2C2
 
 /*I2C使能*/
 I2C_Cmd(I2C2, ENABLE);         //使能I2C2,开始运行
 
 /*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/
 MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);    //电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
 MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);    //电源管理寄存器2,保持默认值0,所有轴均不待机
 MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);    //采样率分频寄存器,配置采样率
 MPU6050_WriteReg(MPU6050_CONFIG, 0x06);     //配置寄存器,配置DLPF
 MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);   //陀螺仪配置寄存器,选择满量程为±2000°/s
 MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);   //加速度计配置寄存器,选择满量程为±16g
}


/**
  * 函    数:MPU6050获取ID号
  * 参    数:无
  * 返 回 值:MPU6050的ID号
  */
uint8_t MPU6050_GetID(void)
{
 return MPU6050_ReadReg(MPU6050_WHO_AM_I);  //返回WHO_AM_I寄存器的值
}

/**
  * 函    数:MPU6050获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
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;        //定义数据高8位和低8位的变量
 
 DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);  //读取加速度计X轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);  //读取加速度计X轴的低8位数据
 *AccX = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
 
 DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);  //读取加速度计Y轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);  //读取加速度计Y轴的低8位数据
 *AccY = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
 
 DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);  //读取加速度计Z轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);  //读取加速度计Z轴的低8位数据
 *AccZ = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
 
 DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);  //读取陀螺仪X轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);  //读取陀螺仪X轴的低8位数据
 *GyroX = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
 
 DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);  //读取陀螺仪Y轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);  //读取陀螺仪Y轴的低8位数据
 *GyroY = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
 
 DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);  //读取陀螺仪Z轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);  //读取陀螺仪Z轴的低8位数据
 *GyroZ = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
}

6.SPI通信

SPI(Serial Peripheral Interface):四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
同步、全双工、没有规定的速率
支持总线挂载多设备
MOSI也可能叫DI,MISO也可能叫DO,SCK也可能叫SCLK、CLK、CK
所有SPI设备的SCK、MOSI、MISO分别连在一起
主机另外引出多条SS控制线,分别接到各从机的SS引脚
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
当SS为1时,从机保持沉默,MISO为高阻态(防止一条线有多个输出,而导致的电平冲突),当SS为0时,表示主机要与该从机通信
SPI通信的逻辑是主机的数据与从机的数据进行交换,如果仅传输则无视从机输出的数据,如果仅输入则随便发送数据(一般是0x00或者0xFF)

图片消失了

其中,当SS为1时,MISO处于中间位置,表示高阻态
若CPOL=1,则空闲时,SCK为1
若CPHA=1,则SCK第一个边沿移入数据,第二个边沿移出数据(第一个数据在SS变0后移出)

7.W25Q64

W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,长应用于数据存储、字库存储(用stm32存储汉字字库比较浪费)、固件程序存储等场景
存储介质:Nor Flash(闪存)
时钟频率:80MHz/160MHz(Dual SPI)/320MHz(Quad SPI)
存储容量(24位地址):64Mbit/8MByte(有一半地址没用到)

图片消失了

最左边是普通的SPI电路
中间的多了写保护和数据保持的引脚,其中CS、HOLD、WP上面的横线表示低电平触发
右边的HOLD、WP可以当作io引脚使用,这样最多有4个引脚进行输入输出,这时的时钟频率是320MHz(Quad SPI)
W25Q64框图:

图片消失了

其中,存储空间每64kb划分一个块,块内每4kb划分一个页
存储地址最大7FFFFFh是因为一半的地址没用到
中间有个高电压发生器,这与Flash的存储原理有关
flash的存储是掉电不丢失的,存储需要较长的时间,所以右下角有个256byte的页缓存,当缓存满时,会向左侧的状态寄存器发送忙信号,使SPI暂停

Flash操作注意事项:
写入操作时:

  1. 每次写入操作前,必须先进行写使能
  2. 每个数据位只能由1改写成0,不能由0改写成1
  3. 写入数据前必须先擦除,擦除后,所有数据位变为1,所以没数据的空间存储的是FF
  4. 擦除必须按最小擦除单元进行
  5. 连续写入多字节时,最多写入一页的数据,超过页尾的数据,会回到页首覆盖写入
  6. 写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

  1. 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

代码:
main.c的测试代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;       //定义用于存放MID号的变量
uint16_t DID;       //定义用于存放DID号的变量

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04}; //定义要写入数据的测试数组
uint8_t ArrayRead[4];        //定义要读取数据的测试数组

int main(void)
{
 /*模块初始化*/
 OLED_Init();      //OLED初始化
 W25Q64_Init();      //W25Q64初始化
 
 /*显示静态字符串*/
 OLED_ShowString(1, 1, "MID:   DID:");
 OLED_ShowString(2, 1, "W:");
 OLED_ShowString(3, 1, "R:");
 
 /*显示ID号*/
 W25Q64_ReadID(&MID, &DID);   //获取W25Q64的ID号
 OLED_ShowHexNum(1, 5, MID, 2);  //显示MID
 OLED_ShowHexNum(1, 12, DID, 4);  //显示DID
 
 /*W25Q64功能函数测试*/
 W25Q64_SectorErase(0x000000);     //扇区擦除
 W25Q64_PageProgram(0x000000, ArrayWrite, 4); //将写入数据的测试数组写入到W25Q64中
 
 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)
 {
  
 }
}

实现SPI通信的代码:

#include "stm32f10x.h"                  // Device header

/*引脚配置层*/

/**
  * 函    数:SPI写SS引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
  */
void MySPI_W_SS(uint8_t BitValue)
{
 GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);  //根据BitValue,设置SS引脚的电平
}

/**
  * 函    数:SPI写SCK引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCK的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCK为低电平,当BitValue为1时,需要置SCK为高电平
  */
void MySPI_W_SCK(uint8_t BitValue)
{
 GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);  //根据BitValue,设置SCK引脚的电平
}

/**
  * 函    数:SPI写MOSI引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入MOSI的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue为1时,需要置MOSI为高电平
  */
void MySPI_W_MOSI(uint8_t BitValue)
{
 GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);  //根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
}

/**
  * 函    数:I2C读MISO引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前MISO的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1
  */
uint8_t MySPI_R_MISO(void)
{
 return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);   //读取MISO电平并返回
}

/**
  * 函    数:SPI初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化
  */
void MySPI_Init(void)
{
 /*开启时钟*/
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
 
 /*GPIO初始化*/
 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);     //将PA4、PA5和PA7引脚初始化为推挽输出
 
 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);     //将PA6引脚初始化为上拉输入
 
 /*设置默认电平*/
 MySPI_W_SS(1);           //SS默认高电平
 MySPI_W_SCK(0);           //SCK默认低电平
}

/*协议层*/

/**
  * 函    数:SPI起始
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Start(void)
{
 MySPI_W_SS(0);    //拉低SS,开始时序
}

/**
  * 函    数:SPI终止
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Stop(void)
{
 MySPI_W_SS(1);    //拉高SS,终止时序
}

/**
  * 函    数:SPI交换传输一个字节,使用SPI模式0
  * 参    数:ByteSend 要发送的一个字节
  * 返 回 值:接收的一个字节
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
 uint8_t i, ByteReceive = 0x00;     //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
 
 for (i = 0; i < 8; i ++)      //循环8次,依次交换每一位数据
 {
  /*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/
  MySPI_W_MOSI(!!(ByteSend & (0x80 >> i))); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
  MySPI_W_SCK(1);        //拉高SCK,上升沿移出数据
  if (MySPI_R_MISO()){ByteReceive |= (0x80 >> i);} //读取MISO数据,并存储到Byte变量
               //当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
  MySPI_W_SCK(0);        //拉低SCK,下降沿移入数据
 }
 
 return ByteReceive;        //返回接收到的一个字节数据
}

根据SPI通信准则跟W25Q64实现通信功能的代码:

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

/**
  * 函    数:W25Q64初始化
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_Init(void)
{
 MySPI_Init();     //先初始化底层的SPI
}

/**
  * 函    数:MPU6050读取ID号
  * 参    数:MID 工厂ID,使用输出参数的形式返回
  * 参    数:DID 设备ID,使用输出参数的形式返回
  * 返 回 值:无
  */
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
 MySPI_Start();        //SPI起始
 MySPI_SwapByte(W25Q64_JEDEC_ID);   //交换发送读取ID的指令
 *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收MID,通过输出参数返回
 *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收DID高8位
 *DID <<= 8;         //高8位移到高位
 *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //或上交换接收DID的低8位,通过输出参数返回
 MySPI_Stop();        //SPI终止
}

/**
  * 函    数:W25Q64写使能
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WriteEnable(void)
{
 MySPI_Start();        //SPI起始
 MySPI_SwapByte(W25Q64_WRITE_ENABLE);  //交换发送写使能的指令
 MySPI_Stop();        //SPI终止
}

/**
  * 函    数:W25Q64等待忙
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WaitBusy(void)
{
 uint32_t Timeout;
 MySPI_Start();        //SPI起始
 MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);    //交换发送读状态寄存器1的指令
 Timeout = 100000;       //给定超时计数时间
 while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) //循环等待忙标志位
 {
  Timeout --;        //等待时,计数值自减
  if (Timeout == 0)      //自减到0后,等待超时
  {
   /*超时的错误处理代码,可以添加到此处*/
   break;        //跳出等待,不等了
  }
 }
 MySPI_Stop();        //SPI终止
}

/**
  * 函    数:W25Q64页编程
  * 参    数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray 用于写入数据的数组
  * 参    数:Count 要写入数据的数量,范围:0~256
  * 返 回 值:无
  * 注意事项:写入的地址范围不能跨页
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
 uint16_t i;
 
 W25Q64_WriteEnable();      //写使能
 
 MySPI_Start();        //SPI起始
 MySPI_SwapByte(W25Q64_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终止
 
 W25Q64_WaitBusy();       //等待忙
}

/**
  * 函    数:W25Q64扇区擦除(4KB)
  * 参    数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF
  * 返 回 值:无
  */
void W25Q64_SectorErase(uint32_t Address)
{
 W25Q64_WriteEnable();      //写使能
 
 MySPI_Start();        //SPI起始
 MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //交换发送扇区擦除的指令
 MySPI_SwapByte(Address >> 16);    //交换发送地址23~16位
 MySPI_SwapByte(Address >> 8);    //交换发送地址15~8位
 MySPI_SwapByte(Address);     //交换发送地址7~0位
 MySPI_Stop();        //SPI终止
 
 W25Q64_WaitBusy();       //等待忙
}

/**
  * 函    数:W25Q64读取数据
  * 参    数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray 用于接收读取数据的数组,通过输出参数返回
  * 参    数:Count 要读取数据的数量,范围:0~0x800000
  * 返 回 值:无
  */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
 uint32_t i;
 MySPI_Start();        //SPI起始
 MySPI_SwapByte(W25Q64_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(W25Q64_DUMMY_BYTE); //依次在起始地址后读取数据
 }
 MySPI_Stop();        //SPI终止
}

8.硬件SPI通信

stm32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
可配置8位/16位数据帧,配置高位先行、低位先行
时钟频率: f P C L K f_{PCLK} fPCLK/(2, 4, 8, 16, 32, 64, 128, 256)
支持多主机模型、主或从操作
可精简为半双工/单工通信
支持DMA
兼容I2S协议(多用于传输音频)
STM32F103C8T6硬件SPI资源:SPI1(APB2)、SPI2(APB1)

图片消失了

重要的是左上角的缓冲区和寄存器组成的无延迟交换模块,右下角的寄存器可以看手册描述

图片消失了

连续传输的实现十分复杂,代码不易封装,所以一般用的是下面的非连续传输

图片消失了

硬件实现SPI通信代码(只有初始化函数和交换字节函数有修改):

void MySPI_Init(void)
{
 /*开启时钟*/
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //开启SPI1的时钟
 
 /*GPIO初始化*/
 GPIO_InitTypeDef GPIO_InitStructure;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);     //将PA4引脚初始化为推挽输出
 
 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);     //将PA5和PA7引脚初始化为复用推挽输出
 
 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);     //将PA6引脚初始化为上拉输入
 
 /*SPI初始化*/
 SPI_InitTypeDef SPI_InitStructure;      //定义结构体变量
 SPI_InitStructure.SPI_Mode = SPI_Mode_Master;   //模式,选择为SPI主模式
 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //方向,选择2线全双工
 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;  //数据宽度,选择为8位
 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  //先行位,选择高位先行
 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率分频,选择128分频
 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;    //SPI极性,选择低极性
 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;   //SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;    //NSS,选择由软件控制
 SPI_InitStructure.SPI_CRCPolynomial = 7;    //CRC多项式,暂时用不到,给默认值7
 SPI_Init(SPI1, &SPI_InitStructure);      //将结构体变量交给SPI_Init,配置SPI1
 
 /*SPI使能*/
 SPI_Cmd(SPI1, ENABLE);         //使能SPI1,开始运行
 
 /*设置默认电平*/
 MySPI_W_SS(1);           //SS默认高电平
}
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET); //等待发送数据寄存器空
 
 SPI_I2S_SendData(SPI1, ByteSend);        //写入数据到发送数据寄存器,开始产生时序
 
 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET); //等待接收数据寄存器非空
 
 return SPI_I2S_ReceiveData(SPI1);        //读取接收到的数据并返回
}

9.unix时间戳

unix时间戳定义为1970年1月1日0时0分0秒到现在经历的秒数,不考虑闰秒
时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量
世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间

GMT:格林尼治标准时间,以地球自转为基础的时间计量系统,将自转一周的时间分为24小时(但是地球自转一周的时间是在变化的)

UTC:协调世界时,以原子钟为基础的时间计量系统。当原子钟计时一天的时间与地球自转一周的时间差超过0.9s时,UTC会执行闰秒来保证其计时与地球自转的协调一致
c语言的库函数中提供了时间获取和时间戳转换的相关函数:

图片消失了

时间戳转换示意图(具体的用法可以百度):

图片消失了

http://www.kler.cn/a/581987.html

相关文章:

  • 华为OD机试-抢7游戏(Java 2024 D卷 100分)
  • ⭐算法OJ⭐经典题目分类索引(持续更新)
  • Tomcat+Servlet运行后出现404错误解决方案
  • 【华为OD机考真题】- 星际篮球争霸赛(Java)
  • 蓝桥杯 商品库存管理
  • 《Android应用性能优化全解析:常见问题与解决方案》
  • llamafactory 微调教程
  • 如何在Django中实现批量覆盖更新的示例
  • Oracle数据库深度优化实战指南:从SQL到架构的全维度调优
  • 蓝桥杯 k倍区间
  • 《深度解析DeepSeek-M8:量子经典融合,重塑计算能效格局》
  • 深度学习分词器char-level实战详解
  • BambuStudio学习笔记:MinAreaBoundigBox
  • AS_Path过滤器应用灵活配置示例
  • Nginx 缓存清理
  • Hugging Face的Transformers核心模块:Pipelines(参数说明,各种模型类型调用案例)
  • 第十一届蓝桥杯单片机国赛
  • 最后一个单词的长度(js实现,LeetCode58)
  • 简单工厂 、工厂方法模式和抽象工厂模式
  • Spring-事务