STM32 I2C 从设备 DMA方式读写稳定工作,记录以下方法。

后记:
虽然一般来讲,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 */
  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值