后记:
虽然一般来讲,I2C用最下面的方法已经可以很好的工作,特别是对于STM32作为主机来讲,没有出现锁死的问题。但是STM32作为从机,仅仅从错误回调中进行I2C reset是不足够,特别是在I2C的线上进行干扰,偶尔会导致I2C从机锁死。所以在FREERTOS线程里面读一下I2C的DATA PIN,如果DATA PIN一直都是被拉高的,说明I2C总线被STM32锁死了,所以在线程里面调用一下RESET就好了。
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET)
{
testI2C1Status+=1;
if(testI2C1Status > 200){
I2C_Reset(&hi2c1);
testI2C1Status = 0;
}
}else{
testI2C1Status = 0;
}
同时还参考了网上别人的方案,把I2C的PIN先设置为输出,然后拉高一下,然后再进行I2C的GPIO初始化,不知道起没起作用,也没有去试验,反正不管如何出错,I2C已经可以活过来了。
/* USER CODE BEGIN I2C1_MspInit 0 */
__HAL_RCC_I2C1_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //GPIO配置为输出
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(GPIOB,&GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); //拉高SCL
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); //拉高SDA
for(uint8_t i = 0; i < 10; i++){
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) == GPIO_PIN_SET)
{
break;
}
HAL_Delay(1);
}
/* USER CODE END I2C1_MspInit 0 */
原始记录:
第一次用STM32 的I2C功能,实验了好久才搞定。目前这套方案已经每隔5MS读写一次数据,工作10几个小时没问题了,中途插入I2C传输数据,均能正常工作,此套方案有点像UART的DMA解决方案,HAL库的逻辑基本都是这样。
1 被官方例程误导,以为读写都是自动化的,是一直循环的,只需要操作buffer就好了,实际并非如此,stm32库的封装还是不够完美,直接用该有多好,应该像esp32学习,简简单单多好。
2 网上大部分文章较久,跟新版的HAL不能兼容,没有拿来就用的代码,反倒搞复杂了。
稳定工作的逻辑是:
1 按cubeide直接配置好i2c,把i2c dma 中断优先级调最高。
2 free rtos,也配置好。
3 在main函数中初始化后要先调用一次DMA接收函数,后面就都靠中断了。
main.c中:
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
__HAL_RCC_I2C1_CLK_ENABLE();
__HAL_RCC_I2C2_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_I2C2_Init();
MX_ADC1_Init();
MX_SPI1_Init();
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
HAL_Delay(1000);
//最重要就是这个地方了
HAL_I2C_Slave_Receive_DMA(&hi2c1, aRxBuffer, 4);//open i2c DMA receive.
/* USER CODE END 2 */
/* Call init function for freertos objects (in freertos.c) */
MX_FREERTOS_Init();
/* Start scheduler */
osKernelStart();
4 hal库目前的dma接收完成回调callback函数是没有联接上,需要写一个callback函数,放到dma中断中。
在stm32f4xx_it.c中放入写好的main.c中写好的回调函数。
/**
* @brief This function handles DMA1 stream0 global interrupt.
*/
void DMA1_Stream0_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Stream0_IRQn 0 */
/* USER CODE END DMA1_Stream0_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_i2c1_rx);
/* USER CODE BEGIN DMA1_Stream0_IRQn 1 */
HAL_I2C_SlaveRxCpltCallback(&hi2c1);
/* USER CODE END DMA1_Stream0_IRQn 1 */
}
/**
* @brief This function handles DMA1 stream6 global interrupt.
*/
void DMA1_Stream6_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Stream6_IRQn 0 */
/* USER CODE END DMA1_Stream6_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_i2c1_tx);
/* USER CODE BEGIN DMA1_Stream6_IRQn 1 */
HAL_I2C_SlaveTxCpltCallback(&hi2c1);
/* USER CODE END DMA1_Stream6_IRQn 1 */
}
5 在中断中对接收的buffer 数据进行switch,每个case里面都要加一句dma接收或发送函数,默认case里面也要放,为了再次启动dma接收函数.特别是对于主机来stm32中读数据,主机会先发一个写的数据,根据这个数据在case中判断主机要数据了,把buffer数组的数据更新到主机需要的数据,把发送dma函数也放进去(注意这儿不放接收函数),接下来就自动发送数据到主机了。
6 当stm32 dma发送了数据,也会回调,这个函数也需要自己写,然后放到发送dma的中断里,在回调中一定要把dma接收函数放进去,等待主机的呼叫。
7 回调错误函数,rest一下i2c.记得把dma接收函数也要一起放进去。这样i2c挂了,也能起死回生。
main.c中的三个回调函数:
/* USER CODE BEGIN 4 */
uint8_t L_Level;
uint8_t R_Level;
//重点就是操作这个函数,数据的读取都在这儿处理。
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) {
if (hi2c == &hi2c1) {
i2c1GetDataCount++;
uint8_t reg;
reg = aRxBuffer[0];//check master send's command and then receive DMA again. if it need read, put the transmit DMA。
switch (reg) {
case 0xAA: //frequency reg
audio_frequecy = aRxBuffer[1];
HAL_I2C_Slave_Receive_DMA(&hi2c1, aRxBuffer, 4);
break;
case 0x03: {
L_Level = aRxBuffer[1];
R_Level = aRxBuffer[2];
HAL_I2C_Slave_Receive_DMA(&hi2c1, aRxBuffer, 4);
break;
}
case 0xEE: { //0xFE是自己定义的,告诉STM32 从机需要读数据,所以立即放上发送DMA函数。
aTxBuffer[0] = ADC_Value_AVG[0];
aTxBuffer[1] = audio_frequecy;
aTxBuffer[2] = 0x33;
HAL_I2C_Slave_Transmit_DMA(&hi2c1, aTxBuffer, 3);
break;
}
default:
//防止传送了没有定义的数据,导致I2C挂死。
HAL_I2C_Slave_Receive_DMA(&hi2c1, aRxBuffer, 4);
break;
}
for (uint8_t i = 0; i < 4; i++) {
// 清空缓存
aRxBuffer[i] = 0;
}
}
}
//这个回调函数也特别重要,传完数据一定要转回接收。
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c) {
if (hi2c == &hi2c1) {
HAL_I2C_Slave_Receive_DMA(&hi2c1, aRxBuffer, 4);
}
}
//以下都是解决i2c错误的。
void I2C_Reset(I2C_HandleTypeDef *I2cHandle) {
HAL_I2C_DeInit(I2cHandle);
I2cHandle->State = HAL_I2C_STATE_RESET;
if (I2cHandle == &hi2c1) {
MX_I2C1_Init();
HAL_I2C_Slave_Receive_DMA(&hi2c1, aRxBuffer, 4);
}
if (I2cHandle == &hi2c2) {
MX_I2C2_Init();
}
HAL_Delay(500);
}
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *I2cHandle) {
if (HAL_I2C_GetError(I2cHandle) != HAL_I2C_ERROR_AF) {
I2C_Reset(I2cHandle);
}
}
/* USER CODE END 4 */