STM32单片机_3

第十章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;
}

代码解释:

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

相关推荐
Wangshanjie_981 小时前
【STM32】-SPI通讯
stm32
qq_411262422 小时前
整体无需占用任何硬件 UART,即可新增一条全双工软串口
单片机·嵌入式硬件
萝卜青今天也要开心4 小时前
2025年上半年软件设计师考后分享
笔记·学习
XINVRY-FPGA4 小时前
XCZU47DR-2FFVG1517I Xilinx FPGA AMD ZynqUltraScale+ RFSoC
人工智能·嵌入式硬件·fpga开发·信息与通信·信号处理·射频工程·fpga
amazinging4 小时前
北京-4年功能测试2年空窗-报培训班学测开-第四十七天
python·学习·selenium
Cyrus_柯4 小时前
单片机基础(STM32-DAY2(GPIO))
单片机·嵌入式硬件
吃货界的硬件攻城狮4 小时前
【STM32 学习笔记】SPI通信协议
笔记·stm32·学习
一个天蝎座 白勺 程序猿4 小时前
Python练习(1)Python基础类型操作语法实战:20道实战题解与案例分析(上)
开发语言·python·学习