找回密码
 立即注册
查看: 77|回复: 2

第3篇_两块8H1K17用I2C通讯,成功

[复制链接]
  • 打卡等级:偶尔看看III
  • 打卡总天数:43
  • 最近打卡:2025-05-01 07:14:14
已绑定手机

12

主题

29

回帖

316

积分

中级会员

积分
316
发表于 2025-3-26 19:29:42 | 显示全部楼层 |阅读模式
昨天的帖子:8H1K17的I2C无法对主机做出应答 - QSPI/3组SPI/I2S/I2C,一线制温湿度传感器 国芯技术交流网站 - AI32位8051交流社区

继昨天I2C从机无应答,我使用了两块8H1K17,一块做主机,一块做从机,使用ISP中的例程,后用逻辑分析仪抓包,发现主从机通讯正常,从机可以应答。

于是我保留从机的板子,换成另外一个MCU作为主机,,情况依旧是从机无应答,下面是我的代码:
  1. /**
  2.   ******************************************************************************
  3.   * @file    I2C_Soft_M.c
  4.   * @author  zhouq
  5.   * @Date    2024/11/11
  6.   * @brief   I2C通信驱动文件,实现软件模拟I2C主机功能
  7.   * @Description
  8.   ******************************************************************************
  9.   * @attention
  10.   ******************************************************************************
  11.   */
  12. /* Includes ------------------------------------------------------------------*/
  13. #include "HeaderInc.h"
  14. /* Private define-------------------------------------------------------------*/
  15. //引脚定义
  16. #define GPIO_I2C_SCL    GPIO_Pin_13  //PA13
  17. #define GPIO_I2C_SDA    GPIO_Pin_14  //PA14
  18. //置位与清零SCL管脚
  19. #define SCL_SET    GPIOA_SetBits( GPIO_I2C_SCL )
  20. #define SCL_CLR    GPIOA_ResetBits( GPIO_I2C_SCL )
  21. //置位与清零SDA管脚
  22. #define SDA_SET    GPIOA_SetBits( GPIO_I2C_SDA )
  23. #define SDA_CLR    GPIOA_ResetBits( GPIO_I2C_SDA )
  24. //读SDA管脚状态
  25. #define READ_SDA   GPIOA_ReadInPortPin( GPIO_I2C_SDA )
  26. /* Private variables----------------------------------------------------------*/
  27. static void               I2C_GPIO_Init(void);          //I2C GPIO模式初始化
  28. static void               I2C_Init(void);               //I2C初始化
  29. static void               I2C_Start(void);              //I2C起始信号
  30. static void               I2C_Stop(void);               //I2C停止信号
  31. static ACK_Value_t        I2C_Write_Byte(uint8_t);      //I2C写字节
  32. static uint8_t            I2C_Read_Byte (ACK_Value_t);  //I2C读字节
  33. static DeviceStatus_t I2C_Write_Frame(uint8_t dev_addr, uint8_t reg_addr, uint8_t *send_data, uint16_t length);
  34. static DeviceStatus_t I2C_Read_Frame(uint8_t dev_addr, uint8_t reg_addr, uint8_t *recv_data, uint16_t length);
  35. /* Public variables-----------------------------------------------------------*/
  36. I2C_Soft_M_t I2C_Soft_M =
  37. {
  38.         .Init = I2C_Init,
  39.         .Write_Frame = I2C_Write_Frame,
  40.         .Read_Frame = I2C_Read_Frame
  41. };
  42. /* Private function prototypes------------------------------------------------*/
  43. /*
  44.         * @name   I2C_GPIO_Init
  45.         * @brief  I2C GPIO初始化
  46.         * @param  None
  47.         * @retval None      
  48. */
  49. void I2C_GPIO_Init(void)
  50. {
  51.         //将GPIO配置为开漏输出模式
  52.         GPIOA_ModeCfg( GPIO_I2C_SCL,GPIO_ModeOut_OP_8mA );
  53.         GPIOA_ModeCfg( GPIO_I2C_SDA,GPIO_ModeOut_OP_8mA );
  54.         
  55. }
  56. /*
  57.         * @name   I2C_Init
  58.         * @brief  I2C初始化
  59.         * @param  None
  60.         * @retval None      
  61. */
  62. static void I2C_Init(void)
  63. {
  64.     //SPI和I2C共用引脚
  65.     R8_SPI0_CTRL_MOD = 0x02;//复位SPI0模式配置寄存器,先失能SPI功能
  66.     I2C_GPIO_Init();
  67.         SCL_SET;
  68.         SDA_SET;
  69. }
  70. /*
  71.         * @name   I2C_Start
  72.         * @brief  I2C起始信号
  73.         * @param  None
  74.         * @retval None      
  75. */
  76. static void I2C_Start(void)
  77. {
  78.         //SCL为高电平,SDA的下降沿为I2C起始信号
  79.         SDA_SET;
  80.         SCL_SET;
  81.         Public.Delay_us(10);
  82.         
  83.         SDA_CLR;
  84.         Public.Delay_us(20);
  85.         
  86.         //将SCL拉低,以免下一位是SDA出现上升沿,导致总线出现停止信号
  87.         //SCL为高电平,SDA出现上升沿视为停止信号
  88.         SCL_CLR;
  89.         Public.Delay_us(10);
  90. }
  91. /*
  92.         * @name   I2C_Stop
  93.         * @brief  I2C停止信号
  94.         * @param  None
  95.         * @retval None      
  96. */
  97. static void I2C_Stop(void)
  98. {
  99.         //SCL为高电平,SDA的上升沿为I2C停止信号
  100.         SDA_CLR;
  101.         SCL_SET;
  102.         
  103.         Public.Delay_us(20);
  104.         SDA_SET;
  105. }
  106. /*
  107.         * @name   I2C_Write_Byte
  108.         * @brief  I2C写字节
  109.         * @param  WR_Byte -> 待写入数据
  110.         * @retval ACK_Value_t -> 从机应答值
  111. */
  112. ACK_Value_t I2C_Write_Byte(uint8_t WR_Byte)
  113. {
  114.         uint8_t i;
  115.         ACK_Value_t  ack_respond;
  116.         
  117.         //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
  118.         //数据按8位传输,高位在前,利用for循环逐个接收
  119.         for(i=0;i<8;i++)
  120.         {
  121.                 //SCL清零,主机SDA准备数据
  122.                 SCL_CLR;
  123.                 Public.Delay_us(10);
  124.                
  125.                 //MSB,先发送高位
  126.                 //待发送的数据循环左移,和BIT7比较
  127.                 if((WR_Byte&BIT7) == BIT7)
  128.                 {
  129.                         SDA_SET;
  130.                 }
  131.                 else
  132.                 {
  133.                         SDA_CLR;
  134.                 }
  135.                 Public.Delay_us(10);
  136.                 //SCL置高,传输数据
  137.                 SCL_SET;
  138.                 Public.Delay_us(20);
  139.                
  140.                 //准备发送下一比特位
  141.                 WR_Byte <<= 1;
  142.         }
  143.         
  144.         //主机在SCL的高电平读取应答位,所以要先拉低SCL
  145.         SCL_CLR;
  146.         
  147.         Public.Delay_us(2);
  148.         //释放SDA,等待从机拉高/拉低SDA
  149.         //如果主机不释放SDA,如果上一位数据是0,那么主机将占据SDA,从机没办法发送SDA=1的情况
  150.         SDA_SET;
  151.         //等待SDA被从机操作
  152.         Public.Delay_us(20);
  153.         //主机在SCL的高电平读取应答位,拉低SCL后再将其拉高
  154.         SCL_SET;
  155.     // 读取从机应答时的超时处理
  156.     Timer1.Timeout = 0;
  157.     while (READ_SDA != 0) // 等待从机应答拉低
  158.     {
  159.         if (Timer1.Timeout >= TIMER1_500mS)// 设置超时时间为500mS
  160.         {
  161.             SCL_CLR; //从机应答超时,主机继续占用总线
  162.             return ERROR_TIMEOUT; // 返回超时错误
  163.         }
  164.     }
  165.         //从机应答后,主机继续占用总线
  166.         SCL_CLR;
  167.         Public.Delay_us(10);
  168.     // 返回从机的应答信号
  169.     return (READ_SDA == 0) ? I2C_ACK : I2C_NACK; // 返回ACK或NACK
  170. }
  171. /*
  172.         * @name   I2C_Read_Byte
  173.         * @brief  I2C读字节
  174.         * @param  ACK_Value -> 主机发送的ACK/NACK
  175.         * @retval 从机返回主机要读的数据
  176. */
  177. static uint8_t I2C_Read_Byte(ACK_Value_t ACK_Value)
  178. {
  179.         uint8_t read_byte = 0,i;
  180.                
  181.         //接收前,主机先确保释放SDA,避免干扰从机的数据发送
  182.         SDA_SET;
  183.         
  184.         //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
  185.         //数据按8位传输,高位在前,利用for循环逐个接收
  186.         for(i=0;i<8;i++)
  187.         {
  188.                 //准备接收下一比特位
  189.                 read_byte <<= 1;//read_byte初始化时为0,第一次循环时,左移一位还是0
  190.                
  191.                 //SCL清零,从机SDA准备数据
  192.                 SCL_CLR;
  193.                 Public.Delay_us(20);
  194.                
  195.                 //SCL置高,获取数据
  196.                 SCL_SET;
  197.                 Public.Delay_us(20);
  198.         // 读取从机发送的数据,接收的数据放在最低位
  199.         if (READ_SDA) {
  200.             read_byte |= 1; // 如果READ_SDA为高电平,则将最低位设置为1
  201.         }//如果是低电平,则继续保持为0,不用操作
  202.         }
  203.         
  204.         //SCL清零,主机准备应答信号
  205.         SCL_CLR;
  206.         Public.Delay_us(10);
  207.         
  208.         //主机发送应答信号
  209.         if(ACK_Value == I2C_ACK)//应答,让从机继续发数据
  210.         {
  211.                 SDA_CLR;
  212.         }
  213.         else//非应答,停止数据传输
  214.         {
  215.                 SDA_SET;
  216.     }
  217.         Public.Delay_us(10);
  218.         
  219.         //置起SCL,将SDA发送出去
  220.         SCL_SET;
  221.         Public.Delay_us(20);
  222.         
  223.         //Note:
  224.     //释放SDA数据线
  225.         //SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACK信号
  226.         SCL_CLR;
  227.     SDA_SET;
  228.         Public.Delay_us(10);
  229.         //返回数据
  230.         return read_byte;
  231. }
  232. /**
  233.   * @brief  I2C主机写一帧数据
  234.   * @param  dev_addr: 从机地址
  235.   * @param  reg_addr: 寄存器地址
  236.   * @param  send_data: 数据缓冲区指针
  237.   * @param  length: 数据长度
  238.   * @retval 0: 成功, 1: 失败
  239.   */
  240. static DeviceStatus_t I2C_Write_Frame(uint8_t dev_addr, uint8_t reg_addr, uint8_t *send_data, uint16_t length)
  241. {
  242.     uint16_t i;
  243.     ACK_Value_t ack;
  244.     I2C_Start(); // 发送起始信号
  245.     // 发送从机地址和写位
  246.     ack = I2C_Write_Byte(dev_addr & 0xFE);
  247.     if (ack != I2C_ACK) {
  248.         I2C_Stop();
  249.         return ERROR_Data; // 失败
  250.     }
  251.         // 发送寄存器地址
  252.     ack = I2C_Write_Byte(reg_addr);
  253.     if (ack != I2C_ACK) {
  254.         I2C_Stop();
  255.         return ERROR_Data; // 失败
  256.     }
  257.     // 发送数据
  258.     for (i = 0; i < length; i++) {
  259.         ack = I2C_Write_Byte(send_data[i]);
  260.         if (ack != I2C_ACK) {
  261.             I2C_Stop();
  262.             return ERROR_Data; // 失败
  263.         }
  264.     }
  265.     I2C_Stop(); // 发送停止信号
  266.     return HAL_OK; // 成功
  267. }
  268. /**
  269.   * @brief  I2C主机读一帧数据
  270.   * @param  dev_addr: 从机地址
  271.   * @param  reg_addr: 寄存器地址
  272.   * @param  recv_data: 数据缓冲区指针,用于存储读取的数据
  273.   * @param  length: 要读取的数据长度
  274.   * @retval 0: 成功, 1: 失败
  275.   */
  276. static DeviceStatus_t I2C_Read_Frame(uint8_t dev_addr, uint8_t reg_addr, uint8_t *recv_data, uint16_t length)
  277. {
  278.     uint16_t i;
  279.     ACK_Value_t ack;
  280.     I2C_Start(); // 发送起始信号
  281.     // 发送从机地址和读位
  282.     ack = I2C_Write_Byte(dev_addr | 0x01);
  283.     if (ack != I2C_ACK) {
  284.         I2C_Stop();
  285.         return ERROR_Data; // 失败
  286.     }
  287.         // 发送寄存器地址
  288.     ack = I2C_Write_Byte(reg_addr);
  289.     if (ack != I2C_ACK) {
  290.         I2C_Stop();
  291.         return ERROR_Data; // 失败
  292.     }
  293.         I2C_Start(); // 重新启动I2C总线
  294.     // 读取数据
  295.     for (i = 0; i < length; i++) {
  296.         if (i == length - 1) {
  297.             // 最后一个字节发送NACK
  298.             recv_data[i] = I2C_Read_Byte(I2C_NACK);
  299.         } else {
  300.             // 其他字节发送ACK
  301.             recv_data[i] = I2C_Read_Byte(I2C_ACK);
  302.         }
  303.     }
  304.     I2C_Stop(); // 发送停止信号
  305.     return HAL_OK; // 成功
  306. }
  307. /********************************************************
  308.   End Of File
  309. ********************************************************/
复制代码

我的代码我看不出问题,另外,不知道作为从机,对I2C通讯的速率是否有要求,如果有,得是多少?目前我的速率是小于400K的
花有重开日,人无再少年
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:423
  • 最近打卡:2025-05-01 07:10:25
已绑定手机

76

主题

4833

回帖

8343

积分

超级版主

DebugLab

积分
8343
发表于 2025-3-27 10:18:08 | 显示全部楼层
https://www.stcaimcu.com/forum.php?mod=redirect&goto=findpost&ptid=16514&pid=155128
根据另一个帖子的逻辑分析仪截图猜测是主机推挽输出,从机无法拉低
但您的主机是其他厂商的单片机,还是软件模拟的,还用了库函数
其他厂商的MCU建议到其他地方寻求技术支持,本论坛仅讨论STC AI MCU,可以使用相关的MCU做主机
不熟悉你用的这个库函数,开漏应该是OD(Open Drain),推挽是PP(Push Pull),这个OP无法理解
截图202503271024457331.jpg
如使用STC AI MCU,一主多从或单主单从,建议主机I2C打开内部4K上拉,从机开漏
不清楚你用的MCU是否支持内部4K上拉(建议1K~5K上拉),如无,需要外接上拉电阻
软件模拟I2C主机可以参考8051U实验箱例程:
AI8051U-DEMO-CODE-V1.2.zip\Ai8051U-32Bit\84-USB录放音声卡-TLV320AIC23B-内部36.864M-外接32768Hz晶振\src\TLV320AIC23.c
截图202503271036462508.jpg
截图202503271040358698.jpg
实验箱:
https://www.stcai.com/syx
AI8051U实验箱1.2:
https://www.stcaimcu.com/data/download/DemoCode/AI8051U-DEMO-CODE-V1.2.zip

DebugLab
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:43
  • 最近打卡:2025-05-01 07:14:14
已绑定手机

12

主题

29

回帖

316

积分

中级会员

积分
316
发表于 2025-3-27 14:19:07 | 显示全部楼层
Debu*** 发表于 2025-3-27 10:18
https://www.stcaimcu.com/forum.php?mod=redirect&goto=findpost&ptid=16514&pid=155128
根据另一个帖子的 ...

好的,谢谢解答
花有重开日,人无再少年
回复 支持 反对

使用道具 举报 送花

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

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-5-2 02:55 , Processed in 0.112657 second(s), 61 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表