I2C软件模拟与Delay寄存器延迟函数
环境
芯片:STM32F103ZET6
库:来自HAL的STM32F1XX.H
原理图

有图可知SCL和SDA两条线接到了PB10和PB11
-
Driver_I2C.h
-
#ifndef __DRIVER_I2C #define __DRIVER_I2C#include "stm32f1xx.h" #include "Com_Delay.h" // 定义拉高SCL引脚的宏操作 #define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10) // 定义拉低SCL引脚的宏操作 #define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR10)// 定义拉高SDA引脚的宏操作 #define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11) // 定义拉低SDA引脚的宏操作 #define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)// 定义读取SDA引脚状态的宏操作 #define READ_SDA (GPIOB->IDR & GPIO_IDR_IDR11)// 定义I2C通信中的短暂延时宏 #define I2C_DELAY Delay_us(5)// 定义ACK和NACK信号的常量 #define ACK 0 #define NACK 1// 初始化I2C驱动的函数声明 void Driver_I2C_Init(void);// I2C的启动信号函数声明,发送启动信号 void Driver_I2C_Start(void); // I2C的停止信号函数声明,发送停止信号 void Driver_I2C_Stop(void);// 发送ACK信号的函数声明 void Driver_I2C_ACK(void); // 发送NACK信号的函数声明 void Driver_I2C_NACK(void);// 等待并返回ACK信号的函数声明,返回值为接收到的ACK/NACK信号 uint8_t Driver_I2C_WaitACK(void);// 发送一个字节数据的函数声明 void Driver_I2C_SendChar(uint8_t ch);// 读取一个字节数据的函数声明,返回值为读取到的数据字节 uint8_t Driver_I2C_ReceiveChar(void); #endif
-
-
Driver_I2C.c
-
#include "Driver_I2C.h"/*** I2C驱动初始化函数* * 使用软件模拟的I2C,这意味着我们不需要利用STM32的硬件I2C外设* 而是通过GPIO的基本功能来实现I2C通信,具体为:* - 配置PB10作为SCL* - 配置PB11作为SDA* * 主要工作步骤:* 1. 使能相关时钟* 2. 配置GPIO引脚模式*/ void Driver_I2C_Init(void) {// 使能GPIOB时钟,为PB10和PB11的配置做准备RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;// 配置PB10和PB11为开漏输出模式,以支持I2C通信的需要// 这里通过掩码操作清除了有关配置位,然后设置为开漏输出模式GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0 | GPIO_CRH_MODE10 | GPIO_CRH_MODE11); }/*** @brief I2C通信开始函数* * 本函数用于初始化I2C通信,并发送起始信号。在I2C通信的开始,* 需要确保SDA和SCL引脚都处于高电平状态,然后通过将SDA引脚拉低* 而SCL引脚保持高电平来产生起始条件。*/ void Driver_I2C_Start(void) {// 保持初始状态SDA_HIGH;SCL_HIGH;I2C_DELAY;// 发送起始信号SDA_LOW;I2C_DELAY; }/*** Driver_I2C_Stop函数用于在I2C通信中发送停止信号。* * 该函数通过拉低SCL和SDA信号线到低电平,然后在SCL为高电平的时候将SDA线拉高,从而发送一个标准的I2C停止信号。* 这个操作确保了在I2C总线上其他设备能够识别到当前传输已经结束。*/ void Driver_I2C_Stop(void) {// 保持初始状态,确保SCL和SDA均为低电平SCL_LOW;SDA_LOW;I2C_DELAY;// 发送停止信号,当SCL为高电平时,将SDA拉高SCL_HIGH;I2C_DELAY;SDA_HIGH;I2C_DELAY; }/*** 函数名: Driver_I2C_ACK* 功能: 在I2C通信中发送一个ACK信号* 描述:* 该函数通过控制I2C总线上的SCL(时钟)和SDA(数据)信号来发送一个ACK(Acknowledge)信号。* ACK信号用于响应接收到的字节,表示接收器已成功接收该字节。* 函数首先设置数据线SDA为高电平,时钟线SCL为低电平,进入空闲状态。* 然后将数据线SDA拉低,开始发送ACK(0)。* 随后时钟线SCL被拉高,完成数据的发送。* 最后恢复时钟线和数据线到空闲状态。* 注意: 该函数使用了特定的宏定义SCL_LOW, SDA_HIGH, SDA_LOW, SCL_HIGH和I2C_DELAY来控制I2C总线信号。*/ void Driver_I2C_ACK(void) {// 设置传输数据时的空闲状态SCL_LOW;SDA_HIGH;I2C_DELAY;// 发送ACK(0)SDA_LOW;I2C_DELAY;// 发送数据,伴随SCL上升沿SCL_HIGH;I2C_DELAY;// 恢复时钟线和数据线SCL_LOW;I2C_DELAY;SDA_HIGH;I2C_DELAY; }// 函数名称:Driver_I2C_NACK // 功能:处理I2C通信中的非应答(NACK)情况 // 该函数通过设置I2C线路的状态来通知其他设备当前设备不响应 void Driver_I2C_NACK(void) {// 保持初始状态SCL_LOW;SDA_HIGH;I2C_DELAY;// 准备数据// 发送数据SCL_HIGH;I2C_DELAY;// 恢复时钟线和数据线 }/*** @brief 等待并检测I2C通信中的ACK信号* * 此函数用于在I2C通信中发送数据后等待并检测从设备返回的ACK(应答信号)或NACK(非应答信号)。* 它通过操纵SCL(时钟线)和SDA(数据线)来同步和检测ACK信号。如果检测到ACK,则返回ACK;* 否则,返回NACK,表示从设备未正确接收到数据或未响应。* * @return uint8_t 返回ACK表示成功接收到ACK信号,返回NACK表示未接收到ACK信号。*/ uint8_t Driver_I2C_WaitACK(void) {// 将SCL线设置为低电平,准备开始发送或接收数据SCL_LOW;// 将SDA线设置为高电平,为发送ACK或NACK做准备SDA_HIGH;// 延时以确保信号稳定I2C_DELAY; // 将SCL线设置为高电平,使能数据传输SCL_HIGH;// 延时以确保数据线稳定I2C_DELAY;// 初始化返回值为ACK,表示准备确认接收到的数据uint8_t r = ACK;// 检查SDA线的状态,决定是否发送ACK或NACKif (READ_SDA){// 如果SDA为高电平,则发送NACK,表示未收到预期的ACKr=NACK;}// 将SCL线再次设置为低电平,完成此次通信SCL_LOW;// 延时以确保信号稳定I2C_DELAY;// 返回确认状态,ACK表示成功,NACK表示失败return r; }/*** 通过I2C协议发送一个字符* @param ch 要发送的字符数据* * 本函数实现了在I2C总线上发送一个字符的数据过程* 它通过操纵SCL和SDA线来发送数据位*/ void Driver_I2C_SendChar(uint8_t ch) {// 设置初始状态SCL_LOW;SDA_HIGH;I2C_DELAY;// 从高位到低位依次发送每一位数据for (uint8_t i = 0; i < 8; i++){// 当前位为1时,拉高SDA线if (ch & 0x80){SDA_HIGH;}else{// 当前位为0时,拉低SDA线SDA_LOW;}I2C_DELAY;// 左移数据,准备发送下一位ch <<= 1;// 发送数据,伴随SCL上升沿SCL_HIGH;I2C_DELAY;// 恢复状态,拉低SCL线SCL_LOW;I2C_DELAY;// 准备发送下一个数据位,先拉高SDA线SDA_HIGH;I2C_DELAY;} } /*** @brief I2C总线接收一个字符* * @return uint8_t 接收到的字符数据*/ uint8_t Driver_I2C_ReceiveChar(void) {uint8_t r = 0;// 设置初始状态SCL_LOW;SDA_HIGH;I2C_DELAY;// 从高位到低位依次接收每一位数据for (uint8_t i = 0; i < 8; i++){// 拉高时钟线,准备接收数据SCL_HIGH;I2C_DELAY;// 接收数据if (READ_SDA){r |= 0x1;}I2C_DELAY;// 除最后一位外,接收的数据左移一位if (i < 7){r <<= 1;}// 恢复时钟线SCL_LOW;I2C_DELAY;}return r; }
-
延迟函数
-
Com_Delay.h
-
// // Created by seven on 2024/8/20. //#ifndef __DELAY_H #define __DELAY_H#include "stm32f1xx.h"void Delay_us(uint32_t nus); void Delay_ms(uint32_t nms);#endif
-
-
Com_Delay.c
-
#include "Com_Delay.h"/// @brief nus延时 /// @param nus 延时的nus数 void Delay_us(uint32_t nus) {uint32_t temp;SysTick->LOAD=nus*168-1; // 计数值加载SysTick->VAL=0x00; // 清空计数器SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; // 开始计数do{temp=SysTick->CTRL; // 读取控制寄存器状态}while((temp&0x01)&&!(temp&(1<<16))); // temp&0x01:定时器使能,!(temp&(1<<16)):定时器计数值不为0SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; // 关闭计数SysTick->VAL=0x00;// 清空计数器 }/// @brief nms延时 /// @param nus 延时的ms数 void Delay_ms(uint32_t nms) {uint32_t repeat=nms/50;uint32_t remain=nms%50;while(repeat){Delay_us(50*1000); // 延时 50 msrepeat--;}if(remain){Delay_us(remain*1000); // 延时remain ms} }
-
