news 2026/4/16 10:36:56

使用GD32F103C8T6开发板的标准库实现硬件I2C协议通信(附源码下载地址)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用GD32F103C8T6开发板的标准库实现硬件I2C协议通信(附源码下载地址)

代码说明:

  1. 该I2C驱动实现了完整的硬件I2C配置,包括GPIO引脚设置、时钟配置和模式配置
  2. 包含全面的异常处理机制,能够检测和处理超时、NACK、总线忙、仲裁丢失等异常情况
  3. 提供了多种I2C操作函数,包括单字节读写和多字节读写操作
  4. 实现了超时检测机制,防止程序在异常情况下无限等待
  5. 主函数中包含了完整的测试流程,通过LED指示不同的通信状态
  6. 代码模块化设计,将I2C驱动分为头文件和实现文件,便于维护和复用
//i2c_deriver.h #ifndef __I2C_DRIVER_H #define __I2C_DRIVER_H #include "gd32f10x.h" // I2C配置参数 #define I2C_PERIPH I2C0 #define I2C_SPEED 100000 #define I2C_SCL_PIN GPIO_PIN_6 #define I2C_SDA_PIN GPIO_PIN_7 #define I2C_GPIO_PORT GPIOB #define I2C_TIMEOUT 0xFFFFF // I2C状态码定义 typedef enum { I2C_OK = 0, I2C_ERROR_TIMEOUT, I2C_ERROR_NACK, I2C_ERROR_BUS_BUSY, I2C_ERROR_ARBITRATION_LOST, I2C_ERROR_UNKNOWN } i2c_status_t; // 函数声明 i2c_status_t i2c_master_init(void); i2c_status_t i2c_master_write(uint8_t device_addr, uint8_t reg_addr, uint8_t *data, uint16_t length); i2c_status_t i2c_master_read(uint8_t device_addr, uint8_t reg_addr, uint8_t *data, uint16_t length); i2c_status_t i2c_master_write_byte(uint8_t device_addr, uint8_t data); i2c_status_t i2c_master_read_byte(uint8_t device_addr, uint8_t *data); void i2c_deinit(void); #endif /* __I2C_DRIVER_H */
//i2c_driver.c #include "i2c_driver.h" /** * @brief I2C超时检查函数 * @param flag: 要检查的标志位 * @param timeout: 超时计数 * @return i2c_status_t: 操作状态 */ static i2c_status_t i2c_wait_flag_timeout(uint32_t flag, uint32_t timeout) { while(!i2c_flag_get(I2C_PERIPH, flag)) { if((timeout--) == 0) { return I2C_ERROR_TIMEOUT; } } return I2C_OK; } /** * @brief I2C主模式初始化 * @return i2c_status_t: 初始化状态 */ i2c_status_t i2c_master_init(void) { uint32_t timeout = I2C_TIMEOUT; // 使能GPIO和I2C时钟 rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_I2C0); // 配置I2C引脚 gpio_init(I2C_GPIO_PORT, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, I2C_SCL_PIN); gpio_init(I2C_GPIO_PORT, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, I2C_SDA_PIN); // 复位I2C i2c_deinit(I2C_PERIPH); // 配置I2C参数 i2c_clock_config(I2C_PERIPH, I2C_SPEED, I2C_DTCY_2); i2c_mode_addr_config(I2C_PERIPH, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x00); i2c_ack_config(I2C_PERIPH, I2C_ACK_ENABLE); // 使能I2C i2c_enable(I2C_PERIPH); // 等待I2C就绪 while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_I2CBSY)) { if((timeout--) == 0) { return I2C_ERROR_TIMEOUT; } } return I2C_OK; } /** * @brief I2C主发送数据 * @param device_addr: 设备地址 * @param reg_addr: 寄存器地址 * @param data: 数据指针 * @param length: 数据长度 * @return i2c_status_t: 发送状态 */ i2c_status_t i2c_master_write(uint8_t device_addr, uint8_t reg_addr, uint8_t *data, uint16_t length) { i2c_status_t status; uint32_t timeout = I2C_TIMEOUT; // 检查总线是否空闲 if(i2c_flag_get(I2C_PERIPH, I2C_FLAG_I2CBSY)) { return I2C_ERROR_BUS_BUSY; } // 发送起始信号 i2c_start_on_bus(I2C_PERIPH); status = i2c_wait_flag_timeout(I2C_FLAG_SBSEND, timeout); if(status != I2C_OK) return status; // 发送设备地址(写) i2c_master_addressing(I2C_PERIPH, device_addr, I2C_TRANSMITTER); status = i2c_wait_flag_timeout(I2C_FLAG_ADDSEND, timeout); if(status != I2C_OK) { // 检查是否收到NACK if(i2c_flag_get(I2C_PERIPH, I2C_FLAG_AERR)) { i2c_flag_clear(I2C_PERIPH, I2C_FLAG_AERR); return I2C_ERROR_NACK; } return status; } i2c_flag_clear(I2C_PERIPH, I2C_FLAG_ADDSEND); // 发送寄存器地址 status = i2c_wait_flag_timeout(I2C_FLAG_TBE, timeout); if(status != I2C_OK) return status; i2c_data_transmit(I2C_PERIPH, reg_addr); status = i2c_wait_flag_timeout(I2C_FLAG_BTC, timeout); if(status != I2C_OK) return status; // 发送数据 for(uint16_t i = 0; i < length; i++) { status = i2c_wait_flag_timeout(I2C_FLAG_TBE, timeout); if(status != I2C_OK) return status; i2c_data_transmit(I2C_PERIPH, data[i]); status = i2c_wait_flag_timeout(I2C_FLAG_BTC, timeout); if(status != I2C_OK) return status; } // 发送停止信号 i2c_stop_on_bus(I2C_PERIPH); // 等待停止信号完成 timeout = I2C_TIMEOUT; while(I2C_CTL0(I2C_PERIPH) & 0x0200) { if((timeout--) == 0) { return I2C_ERROR_TIMEOUT; } } return I2C_OK; } /** * @brief I2C主接收数据 * @param device_addr: 设备地址 * @param reg_addr: 寄存器地址 * @param data: 数据指针 * @param length: 数据长度 * @return i2c_status_t: 接收状态 */ i2c_status_t i2c_master_read(uint8_t device_addr, uint8_t reg_addr, uint8_t *data, uint16_t length) { i2c_status_t status; uint32_t timeout = I2C_TIMEOUT; // 检查总线是否空闲 if(i2c_flag_get(I2C_PERIPH, I2C_FLAG_I2CBSY)) { return I2C_ERROR_BUS_BUSY; } // 第一阶段:发送寄存器地址 // 发送起始信号 i2c_start_on_bus(I2C_PERIPH); status = i2c_wait_flag_timeout(I2C_FLAG_SBSEND, timeout); if(status != I2C_OK) return status; // 发送设备地址(写) i2c_master_addressing(I2C_PERIPH, device_addr, I2C_TRANSMITTER); status = i2c_wait_flag_timeout(I2C_FLAG_ADDSEND, timeout); if(status != I2C_OK) { if(i2c_flag_get(I2C_PERIPH, I2C_FLAG_AERR)) { i2c_flag_clear(I2C_PERIPH, I2C_FLAG_AERR); return I2C_ERROR_NACK; } return status; } i2c_flag_clear(I2C_PERIPH, I2C_FLAG_ADDSEND); // 发送寄存器地址 status = i2c_wait_flag_timeout(I2C_FLAG_TBE, timeout); if(status != I2C_OK) return status; i2c_data_transmit(I2C_PERIPH, reg_addr); status = i2c_wait_flag_timeout(I2C_FLAG_BTC, timeout); if(status != I2C_OK) return status; // 第二阶段:读取数据 // 发送重复起始信号 i2c_start_on_bus(I2C_PERIPH); status = i2c_wait_flag_timeout(I2C_FLAG_SBSEND, timeout); if(status != I2C_OK) return status; // 发送设备地址(读) i2c_master_addressing(I2C_PERIPH, device_addr, I2C_RECEIVER); status = i2c_wait_flag_timeout(I2C_FLAG_ADDSEND, timeout); if(status != I2C_OK) { if(i2c_flag_get(I2C_PERIPH, I2C_FLAG_AERR)) { i2c_flag_clear(I2C_PERIPH, I2C_FLAG_AERR); return I2C_ERROR_NACK; } return status; } i2c_flag_clear(I2C_PERIPH, I2C_FLAG_ADDSEND); // 接收数据 for(uint16_t i = 0; i < length; i++) { // 最后一个字节前关闭ACK if(i == (length - 1)) { i2c_ack_config(I2C_PERIPH, I2C_ACK_DISABLE); } status = i2c_wait_flag_timeout(I2C_FLAG_RBNE, timeout); if(status != I2C_OK) return status; data[i] = i2c_data_receive(I2C_PERIPH); } // 重新使能ACK i2c_ack_config(I2C_PERIPH, I2C_ACK_ENABLE); // 发送停止信号 i2c_stop_on_bus(I2C_PERIPH); // 等待停止信号完成 timeout = I2C_TIMEOUT; while(I2C_CTL0(I2C_PERIPH) & 0x0200) { if((timeout--) == 0) { return I2C_ERROR_TIMEOUT; } } return I2C_OK; } /** * @brief I2C主发送单字节数据 * @param device_addr: 设备地址 * @param data: 数据 * @return i2c_status_t: 发送状态 */ i2c_status_t i2c_master_write_byte(uint8_t device_addr, uint8_t data) { return i2c_master_write(device_addr, 0x00, &data, 1); } /** * @brief I2C主接收单字节数据 * @param device_addr: 设备地址 * @param data: 数据指针 * @return i2c_status_t: 接收状态 */ i2c_status_t i2c_master_read_byte(uint8_t device_addr, uint8_t *data) { return i2c_master_read(device_addr, 0x00, data, 1); } /** * @brief I2C去初始化 */ void i2c_deinit(void) { i2c_disable(I2C_PERIPH); rcu_periph_clock_disable(RCU_I2C0); }
//main.c #include "i2c_driver.h" /** * @brief I2C超时检查函数 * @param flag: 要检查的标志位 * @param timeout: 超时计数 * @return i2c_status_t: 操作状态 */ static i2c_status_t i2c_wait_flag_timeout(uint32_t flag, uint32_t timeout) { while(!i2c_flag_get(I2C_PERIPH, flag)) { if((timeout--) == 0) { return I2C_ERROR_TIMEOUT; } } return I2C_OK; } /** * @brief I2C主模式初始化 * @return i2c_status_t: 初始化状态 */ i2c_status_t i2c_master_init(void) { uint32_t timeout = I2C_TIMEOUT; // 使能GPIO和I2C时钟 rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_I2C0); // 配置I2C引脚 gpio_init(I2C_GPIO_PORT, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, I2C_SCL_PIN); gpio_init(I2C_GPIO_PORT, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, I2C_SDA_PIN); // 复位I2C i2c_deinit(I2C_PERIPH); // 配置I2C参数 i2c_clock_config(I2C_PERIPH, I2C_SPEED, I2C_DTCY_2); i2c_mode_addr_config(I2C_PERIPH, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x00); i2c_ack_config(I2C_PERIPH, I2C_ACK_ENABLE); // 使能I2C i2c_enable(I2C_PERIPH); // 等待I2C就绪 while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_I2CBSY)) { if((timeout--) == 0) { return I2C_ERROR_TIMEOUT; } } return I2C_OK; } /** * @brief I2C主发送数据 * @param device_addr: 设备地址 * @param reg_addr: 寄存器地址 * @param data: 数据指针 * @param length: 数据长度 * @return i2c_status_t: 发送状态 */ i2c_status_t i2c_master_write(uint8_t device_addr, uint8_t reg_addr, uint8_t *data, uint16_t length) { i2c_status_t status; uint32_t timeout = I2C_TIMEOUT; // 检查总线是否空闲 if(i2c_flag_get(I2C_PERIPH, I2C_FLAG_I2CBSY)) { return I2C_ERROR_BUS_BUSY; } // 发送起始信号 i2c_start_on_bus(I2C_PERIPH); status = i2c_wait_flag_timeout(I2C_FLAG_SBSEND, timeout); if(status != I2C_OK) return status; // 发送设备地址(写) i2c_master_addressing(I2C_PERIPH, device_addr, I2C_TRANSMITTER); status = i2c_wait_flag_timeout(I2C_FLAG_ADDSEND, timeout); if(status != I2C_OK) { // 检查是否收到NACK if(i2c_flag_get(I2C_PERIPH, I2C_FLAG_AERR)) { i2c_flag_clear(I2C_PERIPH, I2C_FLAG_AERR); return I2C_ERROR_NACK; } return status; } i2c_flag_clear(I2C_PERIPH, I2C_FLAG_ADDSEND); // 发送寄存器地址 status = i2c_wait_flag_timeout(I2C_FLAG_TBE, timeout); if(status != I2C_OK) return status; i2c_data_transmit(I2C_PERIPH, reg_addr); status = i2c_wait_flag_timeout(I2C_FLAG_BTC, timeout); if(status != I2C_OK) return status; // 发送数据 for(uint16_t i = 0; i < length; i++) { status = i2c_wait_flag_timeout(I2C_FLAG_TBE, timeout); if(status != I2C_OK) return status; i2c_data_transmit(I2C_PERIPH, data[i]); status = i2c_wait_flag_timeout(I2C_FLAG_BTC, timeout); if(status != I2C_OK) return status; } // 发送停止信号 i2c_stop_on_bus(I2C_PERIPH); // 等待停止信号完成 timeout = I2C_TIMEOUT; while(I2C_CTL0(I2C_PERIPH) & 0x0200) { if((timeout--) == 0) { return I2C_ERROR_TIMEOUT; } } return I2C_OK; } /** * @brief I2C主接收数据 * @param device_addr: 设备地址 * @param reg_addr: 寄存器地址 * @param data: 数据指针 * @param length: 数据长度 * @return i2c_status_t: 接收状态 */ i2c_status_t i2c_master_read(uint8_t device_addr, uint8_t reg_addr, uint8_t *data, uint16_t length) { i2c_status_t status; uint32_t timeout = I2C_TIMEOUT; // 检查总线是否空闲 if(i2c_flag_get(I2C_PERIPH, I2C_FLAG_I2CBSY)) { return I2C_ERROR_BUS_BUSY; } // 第一阶段:发送寄存器地址 // 发送起始信号 i2c_start_on_bus(I2C_PERIPH); status = i2c_wait_flag_timeout(I2C_FLAG_SBSEND, timeout); if(status != I2C_OK) return status; // 发送设备地址(写) i2c_master_addressing(I2C_PERIPH, device_addr, I2C_TRANSMITTER); status = i2c_wait_flag_timeout(I2C_FLAG_ADDSEND, timeout); if(status != I2C_OK) { if(i2c_flag_get(I2C_PERIPH, I2C_FLAG_AERR)) { i2c_flag_clear(I2C_PERIPH, I2C_FLAG_AERR); return I2C_ERROR_NACK; } return status; } i2c_flag_clear(I2C_PERIPH, I2C_FLAG_ADDSEND); // 发送寄存器地址 status = i2c_wait_flag_timeout(I2C_FLAG_TBE, timeout); if(status != I2C_OK) return status; i2c_data_transmit(I2C_PERIPH, reg_addr); status = i2c_wait_flag_timeout(I2C_FLAG_BTC, timeout); if(status != I2C_OK) return status; // 第二阶段:读取数据 // 发送重复起始信号 i2c_start_on_bus(I2C_PERIPH); status = i2c_wait_flag_timeout(I2C_FLAG_SBSEND, timeout); if(status != I2C_OK) return status; // 发送设备地址(读) i2c_master_addressing(I2C_PERIPH, device_addr, I2C_RECEIVER); status = i2c_wait_flag_timeout(I2C_FLAG_ADDSEND, timeout); if(status != I2C_OK) { if(i2c_flag_get(I2C_PERIPH, I2C_FLAG_AERR)) { i2c_flag_clear(I2C_PERIPH, I2C_FLAG_AERR); return I2C_ERROR_NACK; } return status; } i2c_flag_clear(I2C_PERIPH, I2C_FLAG_ADDSEND); // 接收数据 for(uint16_t i = 0; i < length; i++) { // 最后一个字节前关闭ACK if(i == (length - 1)) { i2c_ack_config(I2C_PERIPH, I2C_ACK_DISABLE); } status = i2c_wait_flag_timeout(I2C_FLAG_RBNE, timeout); if(status != I2C_OK) return status; data[i] = i2c_data_receive(I2C_PERIPH); } // 重新使能ACK i2c_ack_config(I2C_PERIPH, I2C_ACK_ENABLE); // 发送停止信号 i2c_stop_on_bus(I2C_PERIPH); // 等待停止信号完成 timeout = I2C_TIMEOUT; while(I2C_CTL0(I2C_PERIPH) & 0x0200) { if((timeout--) == 0) { return I2C_ERROR_TIMEOUT; } } return I2C_OK; } /** * @brief I2C主发送单字节数据 * @param device_addr: 设备地址 * @param data: 数据 * @return i2c_status_t: 发送状态 */ i2c_status_t i2c_master_write_byte(uint8_t device_addr, uint8_t data) { return i2c_master_write(device_addr, 0x00, &data, 1); } /** * @brief I2C主接收单字节数据 * @param device_addr: 设备地址 * @param data: 数据指针 * @return i2c_status_t: 接收状态 */ i2c_status_t i2c_master_read_byte(uint8_t device_addr, uint8_t *data) { return i2c_master_read(device_addr, 0x00, data, 1); } /** * @brief I2C去初始化 */ void i2c_deinit(void) { i2c_disable(I2C_PERIPH); rcu_periph_clock_disable(RCU_I2C0); }

代码下载地址: https://gitee.com/gd_15/gd32f103_i2c_driver.git

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 12:28:44

短视频内容打标:笑声密集段自动标记为‘搞笑’

短视频内容打标&#xff1a;笑声密集段自动标记为‘搞笑’ 在短视频运营中&#xff0c;一个常被忽视却极其关键的环节是内容语义打标——不是简单贴上“美食”“旅行”这类粗粒度标签&#xff0c;而是精准识别音频中隐藏的情绪信号与声音事件&#xff0c;比如某段视频里突然爆…

作者头像 李华
网站建设 2026/4/16 14:00:44

Banana Vision Studio入门指南:快速掌握4种工业美学方案

Banana Vision Studio入门指南&#xff1a;快速掌握4种工业美学方案 Banana Vision Studio不是又一个花哨的AI图片生成器&#xff0c;而是一把专为产品设计师、工业设计师和创意工作者打造的结构可视化手术刀。它不追求天马行空的幻想&#xff0c;而是聚焦于真实世界中那些被忽…

作者头像 李华
网站建设 2026/4/15 21:06:01

react笔记之useCallback如何增加代码复杂度和内存开销

虽然 useCallback 本身是为了优化性能&#xff0c;但如果过度或不当使用&#xff0c;确实会带来代码复杂度上升和额外的内存开销。下面我们从两个方面详细解释&#xff1a;一、为什么过度使用 useCallback 会增加 代码复杂度&#xff1f;1. 不必要的依赖管理每次使用 useCallba…

作者头像 李华
网站建设 2026/4/16 0:16:00

阿里软件测试工程师推荐|自动化测试——HTTP网络协议简介

HTTP网络协议HTTP是超文本传输协议&#xff08;Hyper Text Transfer Protocol&#xff0c;HTTP&#xff09;的缩写&#xff0c;是一个简单的请求-响应协议&#xff0c;它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的…

作者头像 李华
网站建设 2026/4/16 10:03:32

VBA 64位API声明语句第017讲

跟我学VBA&#xff0c;我这里专注VBA, 授人以渔。我98年开始&#xff0c;从源码接触VBA已经20余年了&#xff0c;随着年龄的增长&#xff0c;越来越觉得有必要把这项技能传递给需要这项技术的职场人员。希望职场和数据打交道的朋友&#xff0c;都来学习VBA,利用VBA,起码可以提高…

作者头像 李华
网站建设 2026/4/16 10:48:27

ue metahuman 绑定头发

目录 &#x1f9e9; 先确认你买的是什么类型 ✅ 正确绑定方法&#xff08;UE 官方方式&#xff09; 第一步&#xff1a;打开 MetaHuman 角色蓝图 第二步&#xff1a;找到头部 Skeletal Mesh 第三步&#xff1a;添加 Groom 组件 第四步&#xff1a;指定头发资产 第五步&a…

作者头像 李华