[以太网/USB/其他总线] 展示一份模拟I2C驱动代码

[复制链接]
489|0
dffzh 发表于 2025-10-24 14:07 | 显示全部楼层 |阅读模式
, , pi, IO
本帖最后由 dffzh 于 2025-10-24 14:08 编辑

MCU和EEPROM芯片之间会经常使用模拟I2C通信来实现数据读写,下面就分享一份模拟I2C驱动代码,供大家参考:
在MCU的I2C外设资源不够的情况和对I2C通信速率要求不高的场景下,可以考虑使用模拟I2C实现你需要的功能:
  1. /* 定义I2C总线连接的GPIO端口, 用户只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */
  2. #define GPIO_PORT_I2C_SCL          GPIOA                        /* GPIO端口 */
  3. #define GPIO_PORT_I2C_SDA          GPIOA                /* GPIO端口 */
  4. //#define RCC_I2C_PORT               RCC_AHB1Periph_GPIOB        /* GPIO端口时钟 */
  5. #define I2C_SCL_PIN                      GPIO_PIN_13                        /* 连接到SCL时钟线的GPIO */
  6. #define I2C_SDA_PIN                      GPIO_PIN_14                        /* 连接到SDA数据线的GPIO */

  7. /* 定义读写SCL和SDA的宏 */
  8. #define I2C_SCL_1()  HAL_GPIO_WritePin(GPIOA,I2C_SCL_PIN,GPIO_PIN_SET)            /* SCL = 1 */
  9. #define I2C_SCL_0()  HAL_GPIO_WritePin(GPIOA,I2C_SCL_PIN,GPIO_PIN_RESET)        /* SCL = 0 */

  10. #define I2C_SDA_1()  HAL_GPIO_WritePin(GPIOA,I2C_SDA_PIN,GPIO_PIN_SET)                /* SDA = 1 */
  11. #define I2C_SDA_0()  HAL_GPIO_WritePin(GPIOA,I2C_SDA_PIN,GPIO_PIN_RESET)        /* SDA = 0 */

  12. #define I2C_SDA_READ()  HAL_GPIO_ReadPin(GPIOA,I2C_SDA_PIN) //((GPIO_PORT_I2C_SDA->IDR & I2C_SDA_PIN) != 0)        /* 读SDA口线状态 */
  13. #define I2C_SCL_READ()  HAL_GPIO_ReadPin(GPIOA,I2C_SCL_PIN)  //((GPIO_PORT_I2C_SCL->IDR & I2C_SCL_PIN) != 0)        /* 读SCL口线状态 */

  14. static void bsp_I2CBusEnter(void);
  15. static void bsp_I2CBusExit(void);

  16. static uint8_t g_i2c_busy = 0;

  17. /*
  18. *********************************************************************************************************
  19. *        函 数 名: bsp_InitI2C
  20. *        功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现
  21. *        形    参:  无
  22. *        返 回 值: 无
  23. *********************************************************************************************************
  24. */
  25. void bsp_InitI2C(void)
  26. {
  27.         GPIO_InitTypeDef GPIO_InitStructure;

  28.         __HAL_RCC_GPIOA_CLK_ENABLE(); /* 打开GPIO时钟 */

  29.     GPIO_InitStructure.Pin = I2C_SCL_PIN | I2C_SDA_PIN;
  30.         GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;                /* 设为输出口 */        
  31.         GPIO_InitStructure.Pull = GPIO_NOPULL;        /* 上下拉电阻不使能 */
  32.         GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;  /* IO口最大速度 */
  33.         HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
  34.         /* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
  35.         i2c_Stop();
  36. }
  37. void bsp_DeInitI2C(void)
  38. {
  39.         GPIO_InitTypeDef GPIO_InitStructure;

  40.         __HAL_RCC_GPIOA_CLK_ENABLE(); /* 打开GPIO时钟 */

  41.     GPIO_InitStructure.Pin = I2C_SCL_PIN | I2C_SDA_PIN;
  42.         GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;                /* 设为输出口 */        
  43.         GPIO_InitStructure.Pull = GPIO_NOPULL;        /* 上下拉电阻不使能 */
  44.         GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;  /* IO口最大速度 */
  45.         HAL_GPIO_DeInit(GPIOA, I2C_SCL_PIN | I2C_SDA_PIN);
  46. }
  47. /*
  48. *********************************************************************************************************
  49. *        函 数 名: i2c_Delay
  50. *        功能说明: I2C总线位延迟,最快400KHz
  51. *        形    参:  无
  52. *        返 回 值: 无
  53. *********************************************************************************************************
  54. */
  55. static void i2c_Delay(void)
  56. {
  57.         uint8_t i;

  58.         /* 
  59.                 CPU主频168MHz时,在内部Flash运行, MDK工程不优化。用台式示波器观测波形。
  60.                 循环次数为5时,SCL频率 = 1.78MHz (读耗时: 92ms, 读写正常,但是用示波器探头碰上就读写失败。时序接近临界)
  61.                 循环次数为10时,SCL频率 = 1.1MHz (读耗时: 138ms, 读速度: 118724B/s)
  62.                 循环次数为30时,SCL频率 = 440KHz, SCL高电平时间1.0us,SCL低电平时间1.2us

  63.                 上拉电阻选择2.2K欧时,SCL上升沿时间约0.5us,如果选4.7K欧,则上升沿约1us

  64.                 实际应用选择400KHz左右的速率即可
  65.         */
  66.         for (i = 0; i < 25; i++);   // 25:378khz
  67. }

  68. /*
  69. *********************************************************************************************************
  70. *        函 数 名: i2c_Start
  71. *        功能说明: CPU发起I2C总线启动信号
  72. *        形    参:  无
  73. *        返 回 值: 无
  74. *********************************************************************************************************
  75. */
  76. void i2c_Start(void)
  77. {
  78.         bsp_I2CBusEnter();
  79.         
  80.         /* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
  81.         I2C_SDA_1();
  82.         I2C_SCL_1();
  83.         i2c_Delay();
  84.         I2C_SDA_0();
  85.         i2c_Delay();
  86.         
  87.         I2C_SCL_0();
  88.         i2c_Delay();
  89. }

  90. /*
  91. *********************************************************************************************************
  92. *        函 数 名: i2c_Stop
  93. *        功能说明: CPU发起I2C总线停止信号
  94. *        形    参:  无
  95. *        返 回 值: 无
  96. *********************************************************************************************************
  97. */
  98. void i2c_Stop(void)
  99. {
  100.         /* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
  101.         I2C_SDA_0();
  102.         I2C_SCL_1();
  103.         i2c_Delay();
  104.         I2C_SDA_1();
  105.         i2c_Delay();
  106.         
  107.         bsp_I2CBusExit();
  108. }

  109. /*
  110. *********************************************************************************************************
  111. *        函 数 名: i2c_SendByte
  112. *        功能说明: CPU向I2C总线设备发送8bit数据
  113. *        形    参:  _ucByte : 等待发送的字节
  114. *        返 回 值: 无
  115. *********************************************************************************************************
  116. */
  117. void i2c_SendByte(uint8_t _ucByte)
  118. {
  119.         uint8_t i;

  120.         /* 先发送字节的高位bit7 */
  121.         for (i = 0; i < 8; i++)
  122.         {
  123.                 if (_ucByte & 0x80)
  124.                 {
  125.                         I2C_SDA_1();
  126.                 }
  127.                 else
  128.                 {
  129.                         I2C_SDA_0();
  130.                 }
  131.                 i2c_Delay();
  132.                 I2C_SCL_1();
  133.                 i2c_Delay();
  134.                 I2C_SCL_0();
  135.                 if (i == 7)
  136.                 {
  137.                          I2C_SDA_1(); // 释放总线
  138.                 }
  139.                 _ucByte <<= 1;        /* 左移一个bit */
  140.                 i2c_Delay();
  141.         }
  142. }

  143. /*
  144. *********************************************************************************************************
  145. *        函 数 名: i2c_ReadByte
  146. *        功能说明: CPU从I2C总线设备读取8bit数据
  147. *        形    参:  无
  148. *        返 回 值: 读到的数据
  149. *********************************************************************************************************
  150. */
  151. uint8_t i2c_ReadByte(void)
  152. {
  153.         uint8_t i;
  154.         uint8_t value;

  155.         /* 读到第1个bit为数据的bit7 */
  156.         value = 0;
  157.         for (i = 0; i < 8; i++)
  158.         {
  159.                 value <<= 1;
  160.                 I2C_SCL_1();
  161.                 i2c_Delay();
  162.                 if (I2C_SDA_READ())
  163.                 {
  164.                         value++;
  165.                 }
  166.                 I2C_SCL_0();
  167.                 i2c_Delay();
  168.         }
  169.         return value;
  170. }

  171. /*
  172. *********************************************************************************************************
  173. *        函 数 名: i2c_WaitAck
  174. *        功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
  175. *        形    参:  无
  176. *        返 回 值: 返回0表示正确应答,1表示无器件响应
  177. *********************************************************************************************************
  178. */
  179. uint8_t i2c_WaitAck(void)
  180. {
  181.         uint8_t re;

  182.         I2C_SDA_1();        /* CPU释放SDA总线 */
  183.         i2c_Delay();
  184.         I2C_SCL_1();        /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
  185.         i2c_Delay();
  186.         if (I2C_SDA_READ())        /* CPU读取SDA口线状态 */
  187.         {
  188.                 re = 1;
  189.         }
  190.         else
  191.         {
  192.                 re = 0;
  193.         }
  194.         I2C_SCL_0();
  195.         i2c_Delay();
  196.         return re;
  197. }

  198. /*
  199. *********************************************************************************************************
  200. *        函 数 名: i2c_Ack
  201. *        功能说明: CPU产生一个ACK信号
  202. *        形    参:  无
  203. *        返 回 值: 无
  204. *********************************************************************************************************
  205. */
  206. void i2c_Ack(void)
  207. {
  208.         I2C_SDA_0();        /* CPU驱动SDA = 0 */
  209.         i2c_Delay();
  210.         I2C_SCL_1();        /* CPU产生1个时钟 */
  211.         i2c_Delay();
  212.         I2C_SCL_0();
  213.         i2c_Delay();
  214.         I2C_SDA_1();        /* CPU释放SDA总线 */
  215. }

  216. /*
  217. *********************************************************************************************************
  218. *        函 数 名: i2c_NAck
  219. *        功能说明: CPU产生1个NACK信号
  220. *        形    参:  无
  221. *        返 回 值: 无
  222. *********************************************************************************************************
  223. */
  224. void i2c_NAck(void)
  225. {
  226.         I2C_SDA_1();        /* CPU驱动SDA = 1 */
  227.         i2c_Delay();
  228.         I2C_SCL_1();        /* CPU产生1个时钟 */
  229.         i2c_Delay();
  230.         I2C_SCL_0();
  231.         i2c_Delay();
  232. }

  233. /*
  234. *********************************************************************************************************
  235. *        函 数 名: i2c_CheckDevice
  236. *        功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
  237. *        形    参:  _Address:设备的I2C总线地址
  238. *        返 回 值: 返回值 0 表示正确, 返回1表示未探测到
  239. *********************************************************************************************************
  240. */
  241. uint8_t i2c_CheckDevice(uint8_t _Address)
  242. {
  243.         uint8_t ucAck;

  244.         if (I2C_SDA_READ() && I2C_SCL_READ())
  245.         {
  246.                 i2c_Start();                /* 发送启动信号 */

  247.                 /* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
  248.                 i2c_SendByte(_Address | I2C_WR);
  249.                 ucAck = i2c_WaitAck();        /* 检测设备的ACK应答 */

  250.                 i2c_Stop();                        /* 发送停止信号 */

  251.                 return ucAck;
  252.         }
  253.         return 1;        /* I2C总线异常 */
  254. }

  255. /*
  256. *********************************************************************************************************
  257. *        函 数 名: bsp_I2CBusEnter
  258. *        功能说明: 占用I2C总线
  259. *        形    参: 无
  260. *        返 回 值: 0 表示不忙  1表示忙
  261. *********************************************************************************************************
  262. */
  263. static void bsp_I2CBusEnter(void)
  264. {
  265.         g_i2c_busy = 1;
  266. }

  267. /*
  268. *********************************************************************************************************
  269. *        函 数 名: bsp_I2CBusExit
  270. *        功能说明: 释放占用的I2C总线
  271. *        形    参: 无
  272. *        返 回 值: 0 表示不忙  1表示忙
  273. *********************************************************************************************************
  274. */
  275. static void bsp_I2CBusExit(void)
  276. {
  277.         g_i2c_busy = 0;
  278. }

  279. /*
  280. *********************************************************************************************************
  281. *        函 数 名: bsp_I2CBusBusy
  282. *        功能说明: 判断I2C总线忙。方法是检测其他SPI芯片的片选信号是否为1
  283. *        形    参: 无
  284. *        返 回 值: 0 表示不忙  1表示忙
  285. *********************************************************************************************************
  286. */
  287. uint8_t bsp_I2CBusBusy(void)
  288. {
  289.         return g_i2c_busy;
  290. }

  291. /*用SCL脚来测试主循环的运行时间*/
  292. void bsp_InitTestGpio(void)
  293. {
  294.         GPIO_InitTypeDef GPIO_InitStructure;
  295.     __HAL_RCC_GPIOA_CLK_ENABLE(); /* 打开GPIO时钟 */

  296.     GPIO_InitStructure.Pin = I2C_SCL_PIN;
  297.         GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;        /* 设为输出口 */        
  298.         GPIO_InitStructure.Pull = GPIO_NOPULL;        /* 上下拉电阻不使能 */
  299.         GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;  /* IO口最大速度 */
  300.         HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);

  301.         HAL_GPIO_WritePin(GPIOA, GPIO_PIN_13, GPIO_PIN_RESET);
  302. }

您需要登录后才可以回帖 登录 | 注册

本版积分规则

176

主题

1587

帖子

23

粉丝
快速回复 在线客服 返回列表 返回顶部