第十章IIC通信

协议规定, 起始之后主机必须先发送一个字节: 从机地址+读写位, 进行寻址

然后接收一下应答位, 

然后再发送一个字节, 写入从机寄存器地址 

之后就可以进行数据的收发了

注意: 在 主机的接收应答的时候, 立刻释放SDA 然后这时候从机会立刻做出反应, 即拉低SDA, 也就是置0, 说明给了应答, 如果后面SCL高电平期间, 主机读应答位的时候, 发现是高电平, 说明从机没给应答

 

软件模拟代码:

#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 是写

#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0void 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就是那些基本的发送, 接收, 起始, 结束, 等待都不需要我们自己去写


#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0void 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

#include "stm32f10x.h"                  // Device headervoid 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通信的要实现写使能, 等待繁忙, 页写入, 页清除, 读数据

#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 : 这个就是指令 集的封装

#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 : 硬件和软件的上层都一样, 只是底层硬件会封装好一些时序写入读出函数方便我们使用

#include "stm32f10x.h"                  // Device headervoid 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;//空闲默认时钟电平  --这两个就是模式0SPI_InitStructure.SPI_CRCPolynomial = 7;//随便填一个默认值7SPI_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为1SPI_I2S_SendData(SPI1, ByteSend);while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)!=SET);//等待RXNE为1return SPI_I2S_ReceiveData(SPI1);
}

这里发现一个问题, 用高级定时器输出PWM的时候要打开这个开关, 要不然不能输出PWM

第十二章Unix时间戳

BKT备份寄存器跟上一节的Flash闪存有一点类似, 只是Flash闪存是真正的掉电不丢失, 但是BKT备份寄存器是靠着备用电源供电的 

 分频器其实就是一个计数器, 计几个数溢出一次就是几分频, 重装值是几, 分频值就是重装值+1

下面是RTC配置的注意事项

读写备份寄存器

代码展示:

main.c

#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

#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, PKBRCC_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;
}

代码解释:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/89720.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/89720.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/89720.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringAI_Chat模型_DeepSeek模型--基础对话

一、前言 Spring AI 提供跨 AI 供应商&#xff08;如 OpenAI、Hugging Face 等&#xff09;的一致性 API, 通过分装的ChatModel或ChatClient即可轻松调动LLM进行流式或非流式对话。 本专栏主要围绕着通过OpenAI方式调用各种大语言模型展开学习&#xff08;因为95%以上模型都…

数据结构:字符串(Strings)

目录 第一性问题&#xff1a;计算机如何表示文字&#xff1f; ASCII&#xff1a;最早的字符编码标准&#xff08;美国人写的&#xff09; Unicode&#xff1a;解决全球语言的编码方案 字符&#xff08;Character&#xff09; ​编辑 为什么字符常量必须加上单引号 &#…

【vue-5】Vue 3 中的 v-model:双向数据绑定的全面指南

在 Vue 开发中&#xff0c;v-model 是实现表单输入和应用状态之间双向绑定的关键指令。Vue 3 对 v-model 进行了重大改进&#xff0c;使其更加灵活和强大。本文将深入探讨 Vue 3 中 v-model 的工作原理、新特性以及最佳实践。 1. v-model 基础 1.1 什么是 v-model v-model 是 V…

结合自身,制定一套明确的 Web3 学习路线和技术栈建议

目录 ✅ 一、结合自身&#xff0c;明确方向和目的 ✅ 二、技术路线和建议 &#x1f9ed; 技术路线图&#xff08;按阶段划分&#xff09; 第一阶段&#xff1a;巩固 Web3 基础&#xff08;1-2 周&#xff09; 第二阶段&#xff1a;NFT 平台开发实战&#xff08;4-6 周&…

SPARKLE:深度剖析强化学习如何提升语言模型推理能力

摘要&#xff1a;强化学习&#xff08;Reinforcement Learning&#xff0c;RL&#xff09;已经成为赋予语言模型高级推理能力的主导范式。尽管基于 RL 的训练方法&#xff08;例如 GRPO&#xff09;已经展示了显著的经验性收益&#xff0c;但对其优势的细致理解仍然不足。为了填…

【Linux服务器】-MySQL数据库参数调优

一、基础配置 [mysqld] # 声明以下配置属于MySQL服务器&#xff08;mysqld&#xff09;[mysqld]&#xff1a;配置文件的模块标识&#xff0c;表示这是 MySQL 服务器的配置段。 二、路径与基础设置 datadir/var/lib/mysql socket/var/lib/mysql/mysql.sock pid-file/var/run/mys…

sqli-labs靶场通关笔记:第32-33关 宽字节注入

第32关 宽字节注入查看一下本关的源代码&#xff1a;function check_addslashes($string) // 定义一个用于过滤特殊字符的函数&#xff0c;目的是转义可能用于注入的特殊符号 {$string preg_replace(/. preg_quote(\\) ./, "\\\\\\", $string); // 转义…

基于Eureka和restTemple的负载均衡

在微服务架构中&#xff0c;基于 Eureka&#xff08;服务注册中心&#xff09;和 RestTemplate&#xff08;HTTP 客户端&#xff09;实现负载均衡是常见的方案&#xff0c;核心是通过 Eureka 获取服务实例列表&#xff0c;再结合负载均衡策略选择具体服务实例进行调用。以下是详…

子线程不能直接 new Handler(),而主线程可以

在 Android 中&#xff0c;子线程不能直接 new Handler()&#xff0c;而主线程可以&#xff0c;原因在于 Looper 机制。下面详细解释&#xff1a;1. 为什么主线程可以直接 new Handler()&#xff1f; 主线程&#xff08;UI 线程&#xff09;在启动时&#xff0c;系统会自动调用…

Android无需授权直接访问Android/data目录漏洞

从android11开始&#xff0c;访问/sdcard/Android/data目录需要URI授权&#xff0c;而从更高的版本开始甚至URI权限也被收回&#xff0c;返回“无法使用此文件夹”的提示&#xff0c;这里提供一种方法&#xff0c;可以越权强制访问data目录&#xff0c;当然也包括obb、media等目…

本地部署 Kimi K2 全指南(llama.cpp、vLLM、Docker 三法)

Kimi K2 是 Moonshot AI 于2025年7月11日发布的高性能多专家语言模型&#xff08;MoE&#xff09;&#xff0c;支持最大 128K 上下文&#xff0c;激活参数规模为 32B&#xff0c;具备极强的推理、代码生成与多轮对话能力。自从其权重以多种格式开源以来&#xff0c;许多开发者希…

使用python的pillow模块将图片转化为灰度图和相关的操作

使用python的pillow模块可以将图片转化为灰度图&#xff0c; 可以获取灰度图的特定点值&#xff0c;区域值&#xff0c; 修改值并保存到图片 图片转换为灰度图 from PIL import Image# 打开图片 image Image.open("d://python//2//1.jpg")gray_image image.convert…

【网络安全】大型语言模型(LLMs)及其应用的红队演练指南

未经许可,不得转载。 文章目录 什么是红队演练? 为什么 RAI 红队演练是一项重要实践? 如何开展和规划 LLM 的红队演练 1.测试前的准备 规划:由谁负责测试 规划:测试内容 规划:测试方式 规划:数据记录方式 2.测试过程中 3.每轮测试后 报告数据 区分“识别”与“测量” 本…

ROS2安装ros-humble-usb-cam 404错误导致失败的解决方法

ROS2安装ros-humble-usb-cam遇到404错误导致安装失败&#xff0c;如图&#xff1a;解决方法&#xff1a; 备份 sources.list sudo cp /etc/apt/sources.list.d/ros2.list /etc/apt/sources.list.d/ros2.list.bak替换为清华源 sudo sed -i s|http://packages.ros.org/ros2/ubunt…

OllyDbg技巧学习

1 尝试在反汇编代码中找到一个函数的二进制代码 有的时候需要一个函数的二进制代码&#xff0c;注入到另外的一些地方&#xff1b;以此程序为示例&#xff0c; 八叉树的C实现与原理解析-CSDN博客 Ollydbg打开可执行文件&#xff0c;我想先找到此函数的二进制代码体&#xff0…

数据分析智能体:让AI成为你的数据科学家

数据分析智能体&#xff1a;让AI成为你的数据科学家 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用代码丈量世界&#xff0c…

K8s与Helm实战:从入门到精通

Kubernetes 简介 Kubernetes(简称 K8s)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用。最初由 Google 设计并捐赠给云原生计算基金会(CNCF),现已成为容器编排领域的事实标准。 核心功能 自动化容器部署:支持声明式配置和自动化部署,减少人工干预。…

根据ARM手册,分析ARM架构中,原子操作的软硬件实现的底层原理

目录 1.问题背景&#xff1a; 2.原子操作 2.1 硬件操作 2.1.1 LDREX/LDXR指令 2.1.2 STREX/STXR指令 2.2 软件操作 2.3 软件硬件操作的各性能对比 3.总结 1.问题背景&#xff1a; 我们知道&#xff0c;RTOS的任务调度算法是抢占式优先级调度算法。 既然是抢占了&…

iOS 抓包工具选择与配置指南 从零基础到高效调试的完整流程

iOS 抓包&#xff1a;复杂网络调试的必要技能 随着移动端应用越来越依赖网络交互&#xff0c;iOS 抓包作为核心调试工具之一&#xff0c;变得尤为重要。无论是调试 App 与后端的接口通信、排查 HTTPS 请求加密问题&#xff0c;还是定位网络连接超时、请求异常&#xff0c;抓包都…

Java使用FastExcel实现Excel文件导入

依赖配置 (Maven pom.xml)<dependencies><!-- FastExcel 核心库 --><dependency><groupId>cn.idev.excel</groupId><artifactId>fastexcel</artifactId><version>1.0.0</version></dependency><!-- Apache POI…