环境
芯片: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或NACK if (READ_SDA) { // 如果SDA为高电平,则发送NACK,表示未收到预期的ACK r=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)):定时器计数值不为0 SysTick->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 ms repeat--; } if(remain) { Delay_us(remain*1000); // 延时remain ms } }
-