赞
踩
在使用I2C外设HAL库或者LL库例程作为从机时进行通信时,如果我们使用发送/接收库函数对从机进行初始化,就要按照官方例程这样,先写一个接收初始化函数,并设定接收字节长度,接收buffer,查询从机I2C状态后,再执行一次发送初始化函数。
如果我们非常清楚主机的发送时序,这样没有任何问题。但如果我们不知道主机何时开始发送,何时开始接收,也不知道主机会发送/接收多少个byte长度,这样的写法就会有问题。
- /* I2C初始化 */
- I2cHandle.Instance = I2C; /* I2C */
- I2cHandle.Init.ClockSpeed = I2C_SPEEDCLOCK; /* I2C通讯速度 */
- I2cHandle.Init.DutyCycle = I2C_DUTYCYCLE; /* I2C占空比 */
- I2cHandle.Init.OwnAddress1 = I2C_ADDRESS; /* I2C地址 */
- I2cHandle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; /* 禁止广播呼叫 */
- I2cHandle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; /* 允许时钟延长 */
- if (HAL_I2C_Init(&I2cHandle) != HAL_OK)
- {
- APP_ErrorHandler();
- }
-
- /*I2C从机中断方式接收*/
- while (HAL_I2C_Slave_Receive_IT(&I2cHandle, (uint8_t *)aRxBuffer, DARA_LENGTH) != HAL_OK)
- {
- APP_ErrorHandler();
- }
- /*判断当前I2C状态*/
- while (HAL_I2C_GetState(&I2cHandle) != HAL_I2C_STATE_READY);
- /*I2C从机中断方式发送*/
- while (HAL_I2C_Slave_Transmit_IT(&I2cHandle, (uint8_t *)aTxBuffer, DARA_LENGTH) != HAL_OK)
- {
- APP_ErrorHandler();
- }
- /*判断当前I2C状态*/
- while (HAL_I2C_GetState(&I2cHandle) != HAL_I2C_STATE_READY);
为了探究更灵活的处理方式,我们可以从硬件I2C的中断逻辑入手,根据官方手册:EV表示在I2C通信过程中,从机会产生的中断。
我们先定义从机I2C需要发送、接收的数据buffer,数据size等数据结构:
- /*---------IIC1---------------*/
- #define BUFFER_LENGTH 140
- uint8_t aTxBuffer[BUFFER_LENGTH];//发送缓存
- uint8_t aRxBuffer[BUFFER_LENGTH];//接收缓存
- uint8_t TxIndex = 0;//发送计数
- uint8_t RxIndex = 0;//接收计数
- uint8_t FlagRcvOk = 0;// 接收完成标志
然后根据上图的I2C中断需要处理的节点,编写处理函数:
- void User_I2C_EV_IRQHandler(void)
- {
- static uint32_t SR1Register =0;
- static uint32_t SR2Register =0;
-
- SR1Register = I2C1->SR1;
- SR2Register = I2C1->SR2;
-
- /* I2C1是从机(MSL = 0) */
- /* 主机已发送地址,地址为被置位·(ADDR = 1: EV1(包括发送和接收)) */
- if((SR1Register & 0x0002) == 0x0002)
- {
- RxIndex=0;
- TxIndex=0;
-
- /* 验证传输方向,Read 时的方向,Slave 进入接收器模式 */
- if((uint32_t)(READ_BIT(SR2Register, I2C_SR2_TRA)) == 0x00000000U)//从机读
- {
-
- }
- else //从机写
- {
-
- }
- }
-
- /* 接收数据(RXNE = 1: EV2) */
- if((SR1Register & 0x0040) == 0x0040)
- {
- aRxBuffer[RxIndex++] = I2C1->DR;
- }
-
- /* 检测到停止条件(STOPF =1: EV4) */
- if((SR1Register & 0x0010) == 0x0010)
- {
- I2C1->CR1 |= 0x0001;
- FlagRcvOk = 1;
- }
-
- //无应答执行
- if((I2C1->SR1 & I2C_SR1_AF)==I2C_SR1_AF)
- {
- CLEAR_BIT(I2C1->SR1,I2C_SR1_AF);
- FlagRcvOk = 1;
- }
-
- /* 发送数据(TxE = 1: EV3、EV3-1) */
- if((SR1Register & 0x0080) == 0x0080)
- {
- I2C1->DR = aTxBuffer[TxIndex++];
- }
- }
在中断函数里面调用处理函数,并做好发生错误中断时需要进行的处理:
- void I2C1_IRQHandler(void)
- {
- User_I2C_EV_IRQHandler();
-
- if((I2C1->SR1 & I2C_SR1_BERR)==I2C_SR1_BERR) //总线错误执行
- {
- CLEAR_BIT(I2C1->SR1,I2C_SR1_BERR);
- I2C_Init();
- }
-
- if((I2C1->SR1 & I2C_SR1_OVR)==I2C_SR1_OVR) //过载执行
- {
- CLEAR_BIT(I2C1->SR1,I2C_SR1_OVR);
- I2C_Init();
- }
-
- if((I2C1->SR1 & I2C_SR1_PECERR)==I2C_SR1_PECERR) //PEC执行
- {
- CLEAR_BIT(I2C1->SR1,I2C_SR1_PECERR);
- I2C_Init();
- }
- }
贴一下HAL库的I2C配置函数,配置完开启中断就好了:
- void I2C_Init(void)
- {
- I2cHandle.Instance = I2C; //I2C
- I2cHandle.Init.ClockSpeed = I2C_SPEEDCLOCK; //I2C通讯速度
- I2cHandle.Init.DutyCycle = I2C_DUTYCYCLE; //I2C占空比
- I2cHandle.Init.OwnAddress1 = I2C_ADDRESS; //I2C地址
- I2cHandle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; //禁止广播呼叫
- I2cHandle.Init.NoStretchMode = I2C_NOSTRETCH_ENABLE; //允许时钟延长
-
- if (HAL_I2C_DeInit(&I2cHandle) != HAL_OK) //I2C初始化
- {
- Error_Handler();
- }
- __HAL_RCC_I2C_CLK_DISABLE();
-
- if (HAL_I2C_Init(&I2cHandle) != HAL_OK) //I2C初始化
- {
- Error_Handler();
- }
-
- SET_BIT(I2C1->CR1, I2C_CR1_ACK);//使能应答
- SET_BIT(I2C1->CR2, I2C_IT_EVT);
- SET_BIT(I2C1->CR2, I2C_IT_BUF);
- SET_BIT(I2C1->CR2, I2C_IT_ERR);
- }
最后在main函数里面更新从机要发送的数据,直接判断完成标志后,对buffer进行数据赋值就好了:
- int main(void)
- {
- unsigned char i;
-
- HAL_Init();
-
- I2C_Init();//I2C初始化
-
- for(i = 0;i < DARA_LENGTH;i++)
- {
- aTxBuffer[i]=i;
- }
-
- while(1)
- {
- if(FlagRcvOk)
- {
- FlagRcvOk = 0;
- for(i = 0;i < DARA_LENGTH;i++)
- {
- aTxBuffer[i]++;
- }
- }
- }
- }
完成这些操作,这时候就可以让PY32作为从机进行I2C通信并自由应答了!
程序写完用逻辑分析器看一下波形,非常nice:
完结撒花✿✿ヽ(°▽°)ノ✿
>>>【点击进入,普冉PY32仿真烧录工具】https://zhuanlan.zhihu.com/p/673851365
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。