STM32CubeMX硬件IIC从机查询收发实现
STM32CubeMX 硬件IIC从机查询收发实现
前⾔
这⼏天再做STM32的从机实验,由于是新⼿,以前都是⽤IO⼝模拟实现的,多次听说STM32硬件IIC有⼤坑,这次想看看,⽹上搜集了很多资料,主机的还有迹可循,可做从机的就没看见⼀份完整的所以这⾥给整理下。(太坑了,硬件iic过程没法看,标志位⼜⼀堆硬件控制,只能⼀点点查⼿册)
有⼀些关键的都⽤了寄存器解释,不是同库的也可参考下
尚存bug:偶尔会出现收发错误的情况,可能是被中断中断了通讯
IIC外设初始化
对应Cube来说就是配置下外设,这⾥就不粘了,直接上代码:
⾸先是管脚的初始化,⼤家可以参照⼀下:
void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(hi2c->Instance==I2C3)
{
/* USER CODE BEGIN I2C3_MspInit 0 */
/* USER CODE END I2C3_MspInit 0 */
__HAL_RCC_I2C3_CLK_ENABLE();
/**I2C3 GPIO Configuration
PA7    ------> I2C3_SCL
PB4 (NJTRST)    ------> I2C3_SDA
*/
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;//很重要,必须是开漏模式
GPIO_InitStruct.Pull = GPIO_NOPULL; //这⾥cube设置的是PULLUP,调的时候参照论坛⼤佬改了,但实际使⽤中,两种皆ok
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C3;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;//很重要,必须是开漏模式
GPIO_InitStruct.Pull = GPIO_NOPULL;//这⾥也⼀样,不影响
马自达mx5论坛GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C3;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* Peripheral clock enable */
/* I2C3 interrupt Init */
//这⾥中断没⽤到,后⾯等楼主调出来了再更新,⽬前是while⾥查询做的
HAL_NVIC_SetPriority(I2C3_EV_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(I2C3_EV_IRQn);
HAL_NVIC_SetPriority(I2C3_ER_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(I2C3_ER_IRQn);
/* USER CODE BEGIN I2C3_MspInit 1 */
/* USER CODE END I2C3_MspInit 1 */
}
}
IIC外设初始化配置
static void MX_I2C3_Init(void)
{
hi2c3.Instance = I2C3;
hi2c3.Init.Timing = 0x10909CEC;
hi2c3.Init.OwnAddress1 = 0x60;//这个是从机地址,很重要,如果没和主机对上,那么没有⼀点响应  hi2c3.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c3.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c3.Init.OwnAddress2 = 0;//这⾥是可以配置第⼆个地址的,我没有使⽤
hi2c3.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
hi2c3.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c3.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Configure Analogue filter
*/
if (HAL_I2CEx_ConfigAnalogFilter(&hi2c3, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/
**Configure Digital filter
*/
if (HAL_I2CEx_ConfigDigitalFilter(&hi2c3, 0) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
完成初始化后,对应寄存器的数据应该是这样的:
这⾥之后,初始化完成了。
从机的收发
由于从机接受代码的缺乏,以下是楼主根据其寄存器规则摸索的经验。当然仍然有⼀些问题,希望⼤家可以填坑,不过倒是实现了基本的收发。
同正常的IIC⼀样,从机的收发都需要主机发起,当从机接受到主机的起始信号后,硬件⾃动检测与⾃⼰是否匹配。
这⾥主要是硬件操作ISR寄存器
地址匹配:ISR->addr ⾃动置1,否则为零。 操作ICR清空
读写判断:ISR->DIR
1:进⼊transfer(主机 读,从机需要发数据)
0:进⼊recieve(主机写,从机只管收数据)
addr更新时⾃动更新
主机地址:ISR->ADDCODE (这⾥楼主⼀直很困惑,IIC应该没发主机地址呀,我这个从机是怎么知道主机是什么地址的?但事实是确实这⾥存储着主机地址)
ISR->busy 总线繁忙标志位,受到起始信号置1,结束信号置0(这个位确实很忙有时候受到了结束信号,但却仍没有被置0)
ISR->STOPF 这个位被置1(⼿册上解释是在发送时受到了stop信号),导致busy⼀直为1,我是⾃⼰通过操作ICR寄存器清空的。
下⾯是受到读命令后的寄存器
从机收发代码:
if((((I2C3->ISR)&0x200)>>9)==1)//判断仲裁是否失败,否则重置
{
I2C3->CR1=0x00;
I2C3->CR1=0x01;
}
if((((I2C3->ISR)&0x08)>>3)==1)//判断是否受到主机数据(addr)
{
if((((I2C3->ISR)&0x10000)>>16)==1)//判断DIR(读写)
{
//(主机读数据)
if(I2C3->RXDR==0x00)//读地址,这⾥根据⾃⼰协议
x=HAL_I2C_Slave_Transmit(&hi2c3,ppp1,8,10);//库⾃带的发送函数
if(I2C3->RXDR==0x12)//读取 0x12 的数据
{
x=HAL_I2C_Slave_Transmit(&hi2c3,ppp2,21,30);
ppp2[0]++;
}
}
else
{
//主机写
x=HAL_I2C_Slave_Receive(&hi2c3,ppp,2,10);//读或者写的字节数都是固定的,楼主查了很多资料,都没办法修改,不过读的字节超过主机发的字节会提⽰读取失败,但已读到的数据会保留
//I2C3->ICR=(I2C3->ICR)|0x04;
}
}
if((((I2C3->ISR)&0x20)>>5)==1)//⼿动清空stop标志位
{
//
I2C3->ICR|=0x20;
//    I2C3->CR1=0x01;
}