[其他] 华大MCU硬件SMBus的应用

[复制链接]
2363|30
 楼主| sj8zw8 发表于 2023-1-28 11:47 | 显示全部楼层 |阅读模式
本帖最后由 sj8zw8 于 2023-1-28 16:37 编辑

SMBus是一种类似于I2C的通讯协议,简单来说,你可以把它看成I2C,只是它的通讯速率比较慢,一般来说,I2C的通讯速率是100KHz~400KHz,但是SMBus的通讯速率只有10KHz~100KHz。但是SMBus有它的优点,比如:
  • 使用 SMBus,设备还可以提供它的生产信息,告诉系统它的型号,部件号等,针对挂起事件保存它的状态,报告不同类别的错误,接收控制参数,并返回它的状态等;
  • 因为SMBus的通讯速率较慢,所以允许单一主机与 CPU 和多个主从硬盘通讯并收发数据;
  • SMBus为系统和电源管理相关的任务提供一条控制总线。一个系统利用SMBus可以和多个设备互传信息,而不需使用独立的控制线路;
  • SMBus提醒模式,这个功能一般是跟广播呼叫地址一起应用的,此功能需要一条带中断的可选信号(SMBALERT),那些希望与主设备进行通讯的从设备可以通过这根线发信号给主设备,主机处理该中断并通过提醒响应地址ARA(Alert Response Address,地址值为0001100x)访问所有SMBALERT设备,只有那些把SMBALERT拉低的从设备才能应答ARA。主机执行一个修改过的接收字节操作。由从发送设备提供的7位设备地址被放在字节的7个最高位上,第八个位可以是0或1。如果多个设备把SMBALERT拉低,最高优先级设备(最小的地址)将在地址传输期间通过标准仲裁赢得通信权。
  • 超时错误,SMBus定义一个时钟低超时,35ms的超时。SMBus规定TLOW:SEXT为从设备的累积时钟低扩展时间。SMBus规定TLOW:MEXT为主设备的累积时钟低扩展时间。
原文链接:https://www.cnblogs.com/young-dalong/p/15055539.html
 楼主| sj8zw8 发表于 2023-1-28 11:50 | 显示全部楼层
I2C和SMBus的区别:
8709463d49b5f75189.png
以上内容参考百度百科:https://baike.baidu.com/item/smbus%E5%8D%8F%E8%AE%AE/5636057?fr=aladdin
 楼主| sj8zw8 发表于 2023-1-28 11:51 | 显示全部楼层
SMBus通讯协议在电源管理系统应用很广泛,现在手头上的一个案子也是通过SMBus去跟电池(这颗电池上有一个GAUGE IC,用于采集电池信息以及管理电池的作用)通讯,MCU用的是华大HC32F460系列的单片机。

华大的MCU和STM32 的MCU都自带有硬件SMBus的功能,最开始的时候我想通过硬件SMBus来通讯,但是因为官方例程以及网上都没能找到相关的资料,且咨询过FAE也没有可参考的信息,由于进度的关系没有太长时间给我研究,所以我还是采用了模拟SMBus。关于模拟SMBus的代码网上都可以找到,但是可能作者也没有经过仔细地测试验证,其可靠性还是有所欠缺,比如在通讯的过程中有时会出现通讯不上的情况,过一段时间后又可以通讯上。这个问题我猜测是时序上的问题,但是如何优化时序,以达到最佳的通讯效果,需要比较长的时间进行调试,而且我也试过优化时序,虽然段时间内好像问题得到很大的改善,但是实际上还是不能从根本上解决。

虽然模拟SMBus通讯不太稳定,但是我加了纠错机制后,基本上能满足客户的需求,所以事情暂时告一段落。但是问题存在总是令人不安,所以我开始研究起HC32F460的硬件SMBus。
 楼主| sj8zw8 发表于 2023-1-28 11:51 | 显示全部楼层
在我的案子上,用到了两组SMBus,第一路SMBus的SCL是PB6,SDA是PB7,第一路SMBus的SCL是PB13,SDA是PB14,。查过手册后发现有一个比较麻烦的问题就是这两组引脚,虽然都可以复用为I2C,但是都只能复用为I2C3,如果两组SMBus都采用硬件SMBus的话,就会冲突。
 楼主| sj8zw8 发表于 2023-1-28 11:52 | 显示全部楼层
 楼主| sj8zw8 发表于 2023-1-28 12:08 | 显示全部楼层
 楼主| sj8zw8 发表于 2023-1-28 16:01 | 显示全部楼层
 楼主| sj8zw8 发表于 2023-1-28 16:02 | 显示全部楼层
以上是HC32F460系列用户手册上的引脚功能表上的部分截图,图中可以看到引脚后面具备的功能就可以通过软件配置去复用成对应的功能引脚,从图中可以看到这几个引脚的Func0~Func31都没有I2C的功能,那么只能寄希望于Func32~Func63,而这几个引脚的Func31~Func63对应的都是Func_Grp2,那么看一下Func32~63表中Func_Grp2中有没有具备I2C的功能就可以知道这几个引脚能不能复用为I2C的功能,从下图中可以看到,Func_Grp2中确实有I2C的功能,但是只有I2C3这一种。

 楼主| sj8zw8 发表于 2023-1-28 16:05 | 显示全部楼层
2009863d4d6bb1ed1e.png
所以,这两组SMBus只能复用为I2C3的功能,为了避免冲突,只能是分时复用,也就是说当我启用第一组SMBus的时候,我需要把第二组SMBus的SCL和SDA引脚配置为普通的GPIO,把第一组SMBus的SCL和SDA引脚配置为I2C3_SCL和I2C3_SDA,启用第二组的时候则要反过来。
 楼主| sj8zw8 发表于 2023-1-28 16:06 | 显示全部楼层
配置引脚功能的时候涉及到一个重要的寄存器:功能选择寄存器(PFSRxy,x=A~H,y=0~15),如图:
5532163d4d77ab1d51.png
 楼主| sj8zw8 发表于 2023-1-28 16:07 | 显示全部楼层
 楼主| sj8zw8 发表于 2023-1-28 16:08 | 显示全部楼层
结合上面的Func32~63的介绍,可以知道I2C3_SCL是Func49,I2C3_SDA是Func48,所以这个寄存器FSEL[5:0]应该配置为0x31或者0x30。可以通过调用官方提供的库函数进行配置:
  1. extern en_result_t PORT_SetFunc(en_port_t enPort, uint16_t u16Pin,              \
  2.                         en_port_func_t enFuncSel, en_functional_state_t enSubFunc);
 楼主| sj8zw8 发表于 2023-1-28 16:09 | 显示全部楼层
最后一个形参默认填Disable,这个参数是配置这个寄存器的b8位(副功能许可)的。

接下来,贴出我的初始化代码:

  1. uint8_t SMBus_Init(uint8_t SMBusNum)
  2. {
  3.         stc_i2c_init_t stcI2cInit;
  4.         stc_clk_freq_t stcClkFreq;
  5.         stc_i2c_smbus_init_t stcSmBusInit;
  6.          
  7.          
  8.      /* Initialize I2C port*/
  9.         //根据要启用的具体某一组SMBus,配置其引脚为I2C3_SCL和I2C3_SDA功能,并把另外一组引脚配置为普通的GPIO
  10.         if(SMBusNum == SMBus_Num1)
  11.         {
  12.             PORT_SetFunc(SMBUS_SCL_PORT, SMBUS_2_SCL_PIN, Func_Gpio, Disable);
  13.             PORT_SetFunc(SMBUS_SDA_PORT, SMBUS_2_SDA_PIN, Func_Gpio, Disable);
  14.             PORT_SetFunc(SMBUS_SCL_PORT, SMBUS_1_SCL_PIN, Func_I2c3_Scl, Disable);
  15.             PORT_SetFunc(SMBUS_SDA_PORT, SMBUS_1_SDA_PIN, Func_I2c3_Sda, Disable);
  16.         }
  17.         else
  18.         {
  19.             PORT_SetFunc(SMBUS_SCL_PORT, SMBUS_1_SCL_PIN, Func_Gpio, Disable);
  20.             PORT_SetFunc(SMBUS_SDA_PORT, SMBUS_1_SDA_PIN, Func_Gpio, Disable);
  21.             PORT_SetFunc(SMBUS_SCL_PORT, SMBUS_2_SCL_PIN, Func_I2c3_Scl, Disable);
  22.             PORT_SetFunc(SMBUS_SDA_PORT, SMBUS_2_SDA_PIN, Func_I2c3_Sda, Disable);
  23.         }
  24.          
  25.          

  26.         /* Enable I2C Peripheral*/
  27.         PWC_Fcg1PeriphClockCmd(PWC_FCG1_PERIPH_I2C3, Enable);

  28.         I2C_DeInit(I2C_CH);                //初始化之前先复位I2C

  29.         /* Get system clock frequency */
  30.         CLK_GetClockFreq(&stcClkFreq);

  31.         MEM_ZERO_STRUCT(stcI2cInit);
  32.         stcI2cInit.u32Baudrate = I2C_BAUDRATE;    //宏定义,80
  33.         stcI2cInit.u32Pclk3 = stcClkFreq.pclk3Freq;
  34.         stcI2cInit.enI2cMode = I2cMaster;       //主设备
  35.         stcI2cInit.u32SclTime =  0ul;
  36.         I2C_Init(I2C_CH, &stcI2cInit);         //I2C初始化
  37.          
  38.          
  39.         MEM_ZERO_STRUCT(stcSmBusInit);
  40.         stcSmBusInit.enHostAdrMatchFunc = Disable;
  41.         stcSmBusInit.enDefaultAdrMatchFunc = Disable;
  42.         stcSmBusInit.enAlarmAdrMatchFunc = Disable;
  43.         I2C_SmbusConfig(I2C_CH, &stcSmBusInit);
  44.          
  45.          
  46.         使能I2C功能
  47.         I2C_Cmd(I2C_CH, Enable);
  48.         //使能SMBus功能
  49.         I2C_SmBusCmd(I2C_CH,Enable);

  50.         return I2C_RET_OK;
  51. }      
 楼主| sj8zw8 发表于 2023-1-28 16:10 | 显示全部楼层
 I2C基本功能函数:
  1. /**
  2. ******************************************************************************
  3. ** \brief  Send start or restart condition
  4. **
  5. ** \param  none
  6. **
  7. ** \retval Process result
  8. **         - I2C_RET_ERROR  Send start or restart failed
  9. **         - I2C_RET_OK     Send start or restart success
  10. ******************************************************************************/
  11. uint8_t Master_StartOrRestart(uint8_t u8Start)
  12. {
  13.     uint32_t u32TimeOut = TIMEOUT;
  14.     en_flag_status_t enFlagBusy = Reset;
  15.     en_flag_status_t enFlagStartf = Reset;

  16.     /* generate start or restart signal */
  17.     //if(GENERATE_START == u8Start)
  18.     if(!u8Start)
  19.     {
  20.         /* Wait I2C bus idle */
  21.         while(Set == I2C_GetStatus(I2C_CH, I2C_SR_BUSY))
  22.         {
  23.             if(0ul == (u32TimeOut--))
  24.             {
  25.                 return I2C_RET_ERROR;
  26.             }
  27.         }

  28.         I2C_GenerateStart(I2C_CH , Enable);
  29.     }
  30.     else
  31.     {
  32.         /* Clear start status flag */
  33.         I2C_ClearStatus(I2C_CH, I2C_CLR_STARTFCLR);
  34.         /* Send restart condition */
  35.         I2C_GenerateReStart(I2C_CH , Enable);
  36.     }

  37.     /* Judge if start success*/
  38.     u32TimeOut = TIMEOUT;
  39.     while(1)
  40.     {
  41.         enFlagBusy = I2C_GetStatus(I2C_CH, I2C_SR_BUSY);
  42.         enFlagStartf = I2C_GetStatus(I2C_CH, I2C_SR_STARTF);
  43.         if(enFlagBusy && enFlagStartf)
  44.         {
  45.             break;
  46.         }
  47.         if(0ul == (u32TimeOut--))
  48.         {
  49.             return I2C_RET_ERROR;
  50.         }
  51.     }

  52.     return I2C_RET_OK;
  53. }

  54. /**
  55. ******************************************************************************
  56. ** \brief  Send slave address
  57. **
  58. ** \param  u16Adr  The slave address
  59. **
  60. ** \retval Process result
  61. **         - I2C_RET_ERROR  Send failed
  62. **         - I2C_RET_OK     Send success
  63. ******************************************************************************/
  64. uint8_t Master_SendAdr(uint8_t u8Adr)
  65. {
  66.     uint32_t u32TimeOut = TIMEOUT;

  67.     /* Wait tx buffer empty */
  68.     while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_TEMPTYF))
  69.     {
  70.         if(0ul == (u32TimeOut--))
  71.         {
  72.             return I2C_RET_ERROR;
  73.         }
  74.     }

  75.     /* Send I2C address */
  76.     I2C_SendData(I2C_CH, u8Adr);

  77.     //if(E2_ADDRESS_W == (u8Adr & 0x01u))
  78.     if(!(u8Adr & 0x01u))     /*  C-STAT MISRAC2004-13.7 */
  79.     {
  80.         /* If in master transfer process, Need wait transfer end*/
  81.         uint32_t u32TimeOut = TIMEOUT;
  82.         while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_TENDF))
  83.         {
  84.             if(0ul == (u32TimeOut--))
  85.             {
  86.                 return I2C_RET_ERROR;
  87.             }
  88.         }
  89.     }

  90.     /* Check ACK */
  91.     u32TimeOut = TIMEOUT;
  92.     while(Set == I2C_GetStatus(I2C_CH, I2C_SR_NACKDETECTF))
  93.     {
  94.         if(0ul == (u32TimeOut--))
  95.         {
  96.             return I2C_RET_ERROR;
  97.         }
  98.     }

  99.     return I2C_RET_OK;
  100. }

  101. /**
  102. ******************************************************************************
  103. ** \brief  Send data to slave
  104. **
  105. ** \param  pTxData  Pointer to the data buffer
  106. ** \param  u32Size  Data size
  107. **
  108. ** \retval Process result
  109. **         - I2C_RET_ERROR  Send failed
  110. **         - I2C_RET_OK     Send success
  111. ******************************************************************************/
  112. uint8_t Master_WriteData(uint8_t *pTxData, uint32_t u32Size)
  113. {
  114.     uint32_t u32TimeOut = TIMEOUT;

  115.     while(u32Size--)
  116.     {
  117.         /* Wait tx buffer empty */
  118.         u32TimeOut = TIMEOUT;
  119.         while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_TEMPTYF))
  120.         {
  121.             if(0ul == (u32TimeOut--))
  122.             {
  123.                 return I2C_RET_ERROR;
  124.             }
  125.         }

  126.         /* Send one byte data */
  127.         I2C_SendData(I2C_CH, *pTxData++);

  128.         /* Wait transfer end*/
  129.         u32TimeOut = TIMEOUT;
  130.         while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_TENDF))
  131.         {
  132.             if(0ul == (u32TimeOut--))
  133.             {
  134.                 return I2C_RET_ERROR;
  135.             }
  136.         }

  137.         /* Check ACK */
  138.         u32TimeOut = TIMEOUT;
  139.         while(Set == I2C_GetStatus(I2C_CH, I2C_SR_NACKDETECTF))
  140.         {
  141.             if(0ul == (u32TimeOut--))
  142.             {
  143.                 return I2C_RET_ERROR;
  144.             }
  145.         }
  146.     }

  147.     return I2C_RET_OK;
  148. }

  149. /**
  150. ******************************************************************************
  151. ** \brief  General stop condition to slave
  152. **
  153. ** \param  None
  154. **
  155. ** \retval Process result
  156. **         - I2C_RET_ERROR  Send failed
  157. **         - I2C_RET_OK     Send success
  158. ******************************************************************************/
  159. uint8_t Master_Stop(void)
  160. {
  161.     uint32_t u32TimeOut;

  162.     /* Wait I2C bus busy */
  163.     u32TimeOut = TIMEOUT;
  164.     while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_BUSY))
  165.     {
  166.         if(0ul == (u32TimeOut--))
  167.         {
  168.             return I2C_RET_ERROR;
  169.         }
  170.     }

  171.     I2C_GenerateStop(I2C_CH, Enable);

  172.     /* Wait STOPF */
  173.     u32TimeOut = TIMEOUT;
  174.     while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_STOPF))
  175.     {
  176.         if(0ul == (u32TimeOut--))
  177.         {
  178.             return I2C_RET_ERROR;
  179.         }
  180.     }
  181.     return I2C_RET_OK;
  182. }

  183. /**
  184. ******************************************************************************
  185. ** \brief  Receive data from slave
  186. **
  187. ** \param  pTxData  Pointer to the data buffer
  188. ** \param  u32Size  Data size
  189. **
  190. ** \retval Process result
  191. **         - I2C_RET_ERROR  Process failed
  192. **         - I2C_RET_OK     Process success
  193. ******************************************************************************/
  194. uint8_t Master_RevData(uint8_t *pRxData, uint32_t u32Size)
  195. {
  196.     uint32_t u32TimeOut = TIMEOUT;

  197.     for(uint32_t i=0ul; i<u32Size; i++)
  198.     {
  199.         /* if the last byte receive, need config NACK*/
  200.         if(i == (u32Size - 1ul))
  201.         {
  202.             I2C_NackConfig(I2C_CH, Enable);
  203.         }

  204.         /* Wait receive full flag*/
  205.         u32TimeOut = TIMEOUT;
  206.         while(Reset == I2C_GetStatus(I2C_CH, I2C_SR_RFULLF))
  207.         {
  208.             if(0ul == (u32TimeOut--))
  209.             {
  210.                 return I2C_RET_ERROR;
  211.             }
  212.         }

  213.         /* read data from register*/
  214.         *pRxData++ = I2C_ReadData(I2C_CH);
  215.     }
  216.     return I2C_RET_OK;
  217. }
 楼主| sj8zw8 发表于 2023-1-28 16:11 | 显示全部楼层
获取电池信息:
  1. uint8_t Get_Battery_Info(uint8_t slaveAddr, uint8_t Comcode,uint8_t *data,uint8_t size)
  2. {
  3.         uint8_t u8Ret = I2C_RET_OK;
  4.         uint8_t *cmd;
  5.         *cmd = Comcode;
  6.          
  7.         Ddl_Delay1ms(5ul);
  8.     /* I2C master data read*/
  9.     u8Ret = Master_StartOrRestart(GENERATE_START);
  10.         if(u8Ret    != I2C_RET_OK)
  11.         {
  12.             printf("Error : 1");
  13.         }
  14.     JudgeResult(u8Ret);
  15.          
  16.     u8Ret = Master_SendAdr((uint8_t)(slaveAddr)|ADDRESS_W);
  17.         if(u8Ret    != I2C_RET_OK)
  18.         {
  19.             printf("Error : 2");
  20.         }
  21.     JudgeResult(u8Ret);

  22.         u8Ret = Master_WriteData(cmd,1);
  23.         if(u8Ret    != I2C_RET_OK)
  24.         {
  25.             printf("Error : 3");
  26.         }
  27.     JudgeResult(u8Ret);

  28.         u8Ret = Master_StartOrRestart(GENERATE_RESTART);
  29.         if(u8Ret    != I2C_RET_OK)
  30.         {
  31.             printf("Error : 1");
  32.         }
  33.         u8Ret = Master_SendAdr((uint8_t)(slaveAddr)|ADDRESS_R);
  34.         if(u8Ret    != I2C_RET_OK)
  35.         {
  36.             printf("Error : 4");
  37.         }
  38.     JudgeResult(u8Ret);

  39.     u8Ret = Master_RevData(data, size);
  40.         if(u8Ret    != I2C_RET_OK)
  41.         {
  42.             printf("Error : 5");
  43.         }
  44.     JudgeResult(u8Ret);

  45.     u8Ret = Master_Stop();
  46.         if(u8Ret    != I2C_RET_OK)
  47.         {
  48.             printf("Error : 6");
  49.         }
  50.     JudgeResult(u8Ret);

  51.         return I2C_RET_OK;
  52.          
  53. }
 楼主| sj8zw8 发表于 2023-1-28 16:15 | 显示全部楼层
华大的硬件SMBus(I2C)库函数跟STM32的库函数(HAL库)还是有比较大的差别的,比如读从设备的数据的时候,HAL库函数可能调用一个函数就搞掂了,硬件会帮你实现整个数据交换的通讯过程,但是华大的不一样,由上面的代码你也可以看到,整个时序都需要程序员去操控,例如要采集电池的电压值的时候,你需要先调用:

Master_StartOrRestart(GENERATE_START);产生开始条件
Master_SendAdr((uint8_t)(slaveAddr)|ADDRESS_W);发送从设备地址加写位
Master_WriteData(cmd,1);把采集电压的命令发送到从设备
Master_StartOrRestart(GENERATE_RESTART);重新开始
Master_SendAdr((uint8_t)(slaveAddr)|ADDRESS_R);发送从设备地址加读位
Master_RevData(data, size);接受数据
Master_Stop();产生停止条件
 楼主| sj8zw8 发表于 2023-1-28 16:19 | 显示全部楼层
需要程序员对SMBus的通讯时序比较了解,目前测试了一个下午,还没有发现通讯问题,所以应该说这个硬件SMBus可靠性还是比较高的。
tpgf 发表于 2023-2-4 14:32 | 显示全部楼层
SMBus (System Management Bus,系统管理总线) 是1995年由Intel提出的,应用于移动PC和桌面PC系统中的低速率通讯。希望通过一条廉价并且功能强大的总线(由两条线组成),来控制主板上的设备并收集相应的信息。

heimaojingzhang 发表于 2023-2-4 14:38 | 显示全部楼层
SMBus 为系统和电源管理这样的任务提供了一条控制总线,使用 SMBus 的系统,设备之间发送和接收消息都是通过 SMBus,而不是使用单独的控制线,这样可以节省设备的管脚数。
keaibukelian 发表于 2023-2-4 14:45 | 显示全部楼层
SMBus器件存在现有7层OSI网络模型中的前3层,即物理层,数据链路层和网络层。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

52

主题

409

帖子

1

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