第十章IIC通信
协议规定, 起始之后主机必须先发送一个字节: 从机地址+读写位, 进行寻址
然后接收一下应答位,
然后再发送一个字节, 写入从机寄存器地址
之后就可以进行数据的收发了
注意: 在 主机的接收应答的时候, 立刻释放SDA 然后这时候从机会立刻做出反应, 即拉低SDA, 也就是置0, 说明给了应答, 如果后面SCL高电平期间, 主机读应答位的时候, 发现是高电平, 说明从机没给应答

软件模拟代码:
cpp
#include "Delay.h"
//用宏函数来方便改变电平, 但是这样有一点缺点, 可以套一个函数
//改变引脚电平后延时10us
//开漏输出+ 若上拉, 主机输出1 不是输出1 , 而不是释放
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11, (BitAction)BitValue);
Delay_us(10);
}
//读取的时候CLK是高电平
uint8_t MyI2C_R_SDA(void)
{
uint8_t Bit;
Bit = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);//这里主机读取, 所以是外部输入
Delay_us(10);
return Bit;
}
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);
}
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 Data = 0x00;
uint8_t i;
MyI2C_W_SDA(1);//先释放一次就可以了
for(i = 0;i<8;i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA()==1)
{
Data |= (0x80 >> i);
}
MyI2C_W_SCL(0);
}
return Data;
}
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;
}
这里的发送和接收都是高位先行, 按照规定SCL低电平的时候进行写数据, 高电平的时候读数据
MPU6050简介
I2C可以去进行软件模拟, 因为I2C是同步时序
MPU6050读取姿态信息代码展示:
这里用一个MPU6050_Reg.h头文件来去管理寄存器的宏定义
然后就按照指定地址读写进行完成读写函数
0xD0是MPU6050的地址, 同时最后一位给1或0 是我们要去读还是写的设定 , 给1 是读, 给0 是写
cpp
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0
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);
MyI2C_ReceiveAck();//接收应答
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);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
void MPU6050_GetDate(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
//接下来就读取数据, 放到指针里面, 这样就把数据传出去了, 数据寄存器是16位的, 那就分别读取高位和低位的值拼接起来
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;
}
还有最后通过变量指针的方式将数据带出函数
代码解释:
硬件I2C

GPIO要配置复用开漏输出模式

代码展示:

因为硬件I2C是非阻塞的, 我们要去等待标志位, 如图, 每一个操作后面都有事件, 我们要去捕捉那些事件, 等待那些事件到来之后再去进行下一步操作
同时这个等待也要进行超时检测!!!,封装一下就可以了
硬件I2C就是那些基本的发送, 接收, 起始, 结束, 等待都不需要我们自己去写
cpp
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0
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)
{
break;
}
}
}
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);
// MyI2C_ReceiveAck();//接收应答
// MyI2C_Stop();
//软件I2C都是阻塞形式的, 有延时, 而硬件是非阻塞式的, 那么就要等待标志位
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_TRANSMITTING);
I2C_SendData(I2C2, Data);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C2, ENABLE);
}
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;
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_TRANSMITTING);
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);//这是接收
//接收一个字节有特殊设定, 提前配置ACK位, 和给停止
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)
{
// MyI2C_Init();
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;//这是stm32做从机才有用, 这里随便填一个
I2C_Init(I2C2, &I2C_InitStructure);
I2C_Cmd(I2C2, ENABLE);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
void MPU6050_GetDate(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
//接下来就读取数据, 放到指针里面, 这样就把数据传出去了, 数据寄存器是16位的, 那就分别读取高位和低位的值拼接起来
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;
}
这几个操作时发送一个字节特殊的操作
第十一章SPI通信
ss线, 主机要和哪个从机通信, 就将哪个ss线电平置0
因为SPI是全双工, 所以可以使用推挽输出, 那么电平的变化就可以很快, 那么通信速率就可以达到很快
这个模式是对应上面的移位示意图
即SCK上升沿主机读数据, 下降沿主机发送数据

这些都是SCK上升沿或者是下降沿采样的区别
W25Q60芯片介绍

像这个芯片可以储存一些数据, 而且掉电不丢失, 有些项目中需要存储数据时可以选择
忙状态就是芯片在Flash里面操作数据需要一点时间, 这时候芯片处于忙状态, 不能去读写操作
重要的指令集
Write Enable 06h
Write Disable 04h
Read Status Register 05h
Pag Program 02h
Sendor Erase 20h
JEDEC ID 9Fh
Read Data 03h
写入不能跨页, 但是读取可以跨页, 即写入到达页尾的时候, 就会回到页头进行写入
如果要写多页, 我们就要计算有多少页, 然后封装一个函数, 分批次写入
软件SPI
代码展示:
MySPI.c
cpp
#include "stm32f10x.h" // Device header
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4 , (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5 , (BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7 , (BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
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_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
MySPI_W_SS(1);
MySPI_W_SCK(0);
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
//模式0 --其他的模式都是对这个模式0稍微修改一下就可以了
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t ByteReceive = 0x00;
uint8_t i;
//因为是软件模拟, SCK不可能和数据移入移出同时进行
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;
}
//改进之后, 更对应移位模型
//uint8_t MySPI_SwapByte(uint8_t ByteSend)
//{
// uint8_t i;
// for(i = 0;i<8;i++)
// {
// MySPI_W_MOSI(ByteSend);
// ByteSend<<=1;
// MySPI_W_SCK(1);
// if(MySPI_R_MISO()==1)
// {
// ByteSend |= 0x01;
// }
// MySPI_W_SCK(0);
// }
//
// return ByteSend;
//}
W25Q64.c : 这个上层模块就是按照SPI通信的要实现写使能, 等待繁忙, 页写入, 页清除, 读数据
cpp
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
void W25Q64_Init(void)
{
MySPI_Init();
}
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_JEDEC_ID);
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID<<=8;
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
MySPI_Stop();
}
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}
void W25Q64_WaitBusy(void)
{
uint16_t Timeout;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
Timeout = 10000;
while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) ==1)
{
Timeout--;
if(Timeout==0)
{
break;
}
}
MySPI_Stop();
}
void W25Q64_PageProgram(uint32_t Address, int8_t *DataArray, uint16_t Count)
{
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
uint16_t i;
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);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
MySPI_Stop();
W25Q64_WaitBusy();
}
//读数据的时候就不用再去管是否繁忙了
void W25Q64_ReadData(uint32_t Address, int8_t *DataArray, uint32_t Count)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
uint32_t i;
for(i = 0;i<Count;i++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
}
MySPI_Ins.h : 这个就是指令 集的封装
cpp
#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
硬件SPI

串口是低位先行, I2C和SPI是高位先行
I2S是数字音频传输协议


这里有连续传输和非连续传输
对于下面这些引脚, 需要解除引脚复用的设置才可以正常当作GPIO , 或者是其他从定义的功能
如何去解除可以看6-4的视频
对于TXE 和 RXNE 这两个DR寄存器为空标志位都不需要我们去清除, 硬件自己会清除(这是应为写入的时候TXE硬件清除, 读取的时候RXNE硬件清除)
代码展示:
MySPI.c : 硬件和软件的上层都一样, 只是底层硬件会封装好一些时序写入读出函数方便我们使用
cpp
#include "stm32f10x.h" // Device header
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4 , (BitAction)BitValue);
}
void MySPI_Init(void)
{
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_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//分频
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//哪个边沿开始采样
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//空闲默认时钟电平 --这两个就是模式0
SPI_InitStructure.SPI_CRCPolynomial = 7;//随便填一个默认值7
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
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;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
MySPI_W_SS(1);
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
//模式0 --其他的模式都是对这个模式0稍微修改一下就可以了
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)!=SET);//等待TXE为1
SPI_I2S_SendData(SPI1, ByteSend);
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)!=SET);//等待RXNE为1
return SPI_I2S_ReceiveData(SPI1);
}
这里发现一个问题, 用高级定时器输出PWM的时候要打开这个开关, 要不然不能输出PWM

第十二章Unix时间戳
BKT备份寄存器跟上一节的Flash闪存有一点类似, 只是Flash闪存是真正的掉电不丢失, 但是BKT备份寄存器是靠着备用电源供电的
分频器其实就是一个计数器, 计几个数溢出一次就是几分频, 重装值是几, 分频值就是重装值+1
下面是RTC配置的注意事项
读写备份寄存器
代码展示:
main.c
cpp
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
uint16_t ArrayWrite[] = {0x1234, 0x4444};
uint16_t ArrayRead[2];
uint8_t KeyNum;
int main(void)
{
OLED_Init();
Key_Init();
OLED_ShowString(1, 1, "W:");
OLED_ShowString(2, 1, "R:");
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_BackupAccessCmd(ENABLE);
while (1)
{
KeyNum = Key_GetNum();
if(KeyNum==1)
{
ArrayWrite[0]++;
ArrayWrite[1]++;
OLED_ShowHexNum(1, 4, ArrayWrite[0], 4);
OLED_ShowHexNum(1, 8, ArrayWrite[1], 4);
BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);
BKP_WriteBackupRegister(BKP_DR2, ArrayWrite[1]);
}
ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);
ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);
OLED_ShowHexNum(2, 4, ArrayRead[0], 4);
OLED_ShowHexNum(2, 8, ArrayRead[1], 4);
}
}
代码解释:

实时时钟
代码展示:
MyRTC.c
cpp
#include "stm32f10x.h" // Device header
#include <time.h>
uint16_t MyRTC_Time[] = {2024, 1, 1, 15, 55, 59};
void MyRTC_SetTime(void);
void MyRTC_Init(void)
{
//1使能PWR, PKB
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_BackupAccessCmd(ENABLE);
//避免重复初始化
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
{
//2开启LSE, 等待启动完成
RCC_LSEConfig(RCC_LSE_ON);
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
//3选择时钟源
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
//等待函数
RTC_WaitForSynchro();//等待同步
RTC_WaitForLastTask();//等待写入完成
//配置预分频
RTC_SetPrescaler(32768-1);//自己带了进入和退出配置模式
RTC_WaitForLastTask();
//设置时间
MyRTC_SetTime();
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
}
else
{
RTC_WaitForSynchro();
RTC_WaitForLastTask();
}
}
void MyRTC_SetTime(void)
{
time_t time_cnt;
struct tm time_date;
time_date.tm_year = MyRTC_Time[0]-1900;
time_date.tm_mon = MyRTC_Time[1]-1;
time_date.tm_mday = MyRTC_Time[2];
time_date.tm_hour = MyRTC_Time[3];
time_date.tm_min = MyRTC_Time[4];
time_date.tm_sec = MyRTC_Time[5];
time_cnt = mktime(&time_date);
RTC_SetCounter(time_cnt);
RTC_WaitForLastTask();
}
void MyRTC_ReadTime(void)
{
time_t time_cnt;
struct tm time_date;
time_cnt = RTC_GetCounter() + 8*60*60;
time_date = *localtime(&time_cnt);
MyRTC_Time[0] = time_date.tm_year+1900;
MyRTC_Time[1] = time_date.tm_mon+1;
MyRTC_Time[2] = time_date.tm_mday;
MyRTC_Time[3] = time_date.tm_hour;
MyRTC_Time[4] = time_date.tm_min;
MyRTC_Time[5] = time_date.tm_sec;
}
代码解释:

设定时间和读取时间都是秒计数器和日期时间的转化
