[STM32F4] STM32CubeMX系列05——ADC(轮询、中断、DMA)

[复制链接]
2462|52
 楼主| SHOPQQ 发表于 2022-11-23 13:54 | 显示全部楼层
单通道轮询 在转换时会阻塞直到转换完成。
 楼主| SHOPQQ 发表于 2022-11-23 13:55 | 显示全部楼层
单通道中断

第一步:配置上:在“单通道轮询”实现配置基础上再打开ADC全局中断。
15406637db5a370fa5.png
 楼主| SHOPQQ 发表于 2022-11-23 13:55 | 显示全部楼层
第二步:点击生成代码
 楼主| SHOPQQ 发表于 2022-11-23 13:56 | 显示全部楼层
第三步:串口重定向,在usart.c中添加如下代码。
  1. // 需要调用stdio.h文件
  2. #include <stdio.h>
  3. //取消ARM的半主机工作模式
  4. #pragma import(__use_no_semihosting)//标准库需要的支持函数                 
  5. struct __FILE
  6. {
  7.         int handle;
  8. };
  9. FILE __stdout;      
  10. void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
  11. {
  12.         x = x;
  13. }

  14. int fputc(int ch, FILE *f)
  15. {  
  16.         HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  17.         return ch;
  18. }
 楼主| SHOPQQ 发表于 2022-11-23 14:00 | 显示全部楼层
第四步:编写 main.c 代码

生成后查看代码,在stm32f1xx_it.c文件中有 ADC1通道2的中断函数ADC1_2_IRQHandler,这个中断函数又调用了HAL_ADC_IRQHandler(&hadc1);

  1. /**
  2.   * [url=home.php?mod=space&uid=247401]@brief[/url] This function handles ADC1 and ADC2 global interrupts.
  3.   */
  4. void ADC1_2_IRQHandler(void)
  5. {
  6.   /* USER CODE BEGIN ADC1_2_IRQn 0 */

  7.   /* USER CODE END ADC1_2_IRQn 0 */
  8.   HAL_ADC_IRQHandler(&hadc1);
  9.   /* USER CODE BEGIN ADC1_2_IRQn 1 */

  10.   /* USER CODE END ADC1_2_IRQn 1 */
  11. }
 楼主| SHOPQQ 发表于 2022-11-23 14:01 | 显示全部楼层
在HAL_ADC_IRQHandler(&hadc1);函数在stm32f1xx_hal_adc.c中,这个函数考虑了很多情况,其中调用了HAL_ADC_ConvCpltCallback(hadc);,还是在同一个文件中,这是一个弱函数。根据翻译,很好理解,我们直接重新定义这个方法即可。
 楼主| SHOPQQ 发表于 2022-11-23 14:02 | 显示全部楼层
  1. /**
  2.   * @brief  Conversion complete callback in non blocking mode
  3.   * @param  hadc: ADC handle
  4.   * @retval None
  5.   */
  6. __weak void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
  7. {
  8.   /* Prevent unused argument(s) compilation warning */
  9.   UNUSED(hadc);
  10.   /* NOTE : This function should not be modified. When the callback is needed,
  11.             function HAL_ADC_ConvCpltCallback must be implemented in the user file.
  12.    */
  13. }
 楼主| SHOPQQ 发表于 2022-11-23 14:07 | 显示全部楼层
main.c
/* USER CODE BEGIN PFP */
// 重定义ADC转换完成回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
        if(hadc == &hadc1)
        {
                uint16_t adc_value = HAL_ADC_GetValue(hadc);
                printf("refresh adc value:%f \r\n", adc_value/4096.0*3.3);
                // 重新开启ADC中断
                HAL_ADC_Start_IT(&hadc1);
        }
}
/* USER CODE END PFP */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  
  /* USER CODE BEGIN WHILE */
        // 开启ADC中断
        HAL_ADC_Start_IT(&hadc1);
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
 楼主| SHOPQQ 发表于 2022-11-23 14:07 | 显示全部楼层
效果和轮询的一样,不过这个会一直执行,而且很快

实际上,这里设置的是单次转换,所以中断只会触发一次,需要再次使用HAL_ADC_Start_IT开启中断。如果需要实时的转换,可以将转换设为连续模式,这样的话ADC转换器便会实时的持续的进行转换,那将是非常消耗CPU的,以至于main将不能正常执行(采样时间太短的话)。
 楼主| SHOPQQ 发表于 2022-11-23 14:08 | 显示全部楼层
开启中断后,一般需要实现HAL_ADC_ConvCpltCallback函数,在callback中GetValue,也可以在程序其他地方像轮询那样先判断ADC状态,再GetValue。
 楼主| SHOPQQ 发表于 2022-11-23 14:09 | 显示全部楼层
3.3. 多通道轮询

第一步:ADC配置
多通道时扫描模式会自动打开。要开启“Discontinuous Conversion Mode”。
6344637db8ee5dd37.png
 楼主| SHOPQQ 发表于 2022-11-23 14:09 | 显示全部楼层
第二步:点击生成代码
 楼主| SHOPQQ 发表于 2022-11-23 14:10 | 显示全部楼层
第三步:串口重定向,在usart.c中添加如下代码。
// 需要调用stdio.h文件
#include <stdio.h>
//取消ARM的半主机工作模式
#pragma import(__use_no_semihosting)//标准库需要的支持函数                 
struct __FILE
{
        int handle;
};
FILE __stdout;      
void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
{
        x = x;
}

int fputc(int ch, FILE *f)
{  
        HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
        return ch;
}

 楼主| SHOPQQ 发表于 2022-11-23 14:15 | 显示全部楼层
第四步:编写 main.c 代码
 楼主| SHOPQQ 发表于 2022-11-23 14:18 | 显示全部楼层
HAL_ADCEx_Calibration_Start(&hadc1);
const uint8_t kNbrOfPin = 3;
  while (1)
  {
        for(int i = 0; i < kNbrOfPin; i++)
        {
                HAL_ADC_Start(&hadc1);
                HAL_ADC_PollForConversion(&hadc1, 100);
                float value = 0;
                uint32_t state = HAL_ADC_GetState(&hadc1);
                if (( state & HAL_ADC_STATE_REG_EOC) == HAL_ADC_STATE_REG_EOC)
                {
                        value = HAL_ADC_GetValue(&hadc1);
                        printf("adc value [%d]:%f\r\n", i,value/4096.0*3.3);
                }
                else
                {
                        printf("adc state[%d]:%d\r\n", i, state);
                }
        }
        HAL_ADC_Stop(&hadc1);
        HAL_Delay(200);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
 楼主| SHOPQQ 发表于 2022-11-23 14:19 | 显示全部楼层
效果验证
917637dbb46ddeb4.png
 楼主| SHOPQQ 发表于 2022-11-23 14:22 | 显示全部楼层
DMA模式
第一步:ADC配置
31262637dbc14d945a.png
 楼主| SHOPQQ 发表于 2022-11-23 14:22 | 显示全部楼层
 楼主| SHOPQQ 发表于 2022-11-23 14:23 | 显示全部楼层
第二步:点击生成代码
第三步:串口重定向,在usart.c中添加如下代码、
  1. // 需要调用stdio.h文件
  2. #include <stdio.h>
  3. //取消ARM的半主机工作模式
  4. #pragma import(__use_no_semihosting)//标准库需要的支持函数                 
  5. struct __FILE
  6. {
  7.         int handle;
  8. };
  9. FILE __stdout;      
  10. void _sys_exit(int x) //定义_sys_exit()以避免使用半主机模式
  11. {
  12.         x = x;
  13. }

  14. int fputc(int ch, FILE *f)
  15. {  
  16.         HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  17.         return ch;
  18. }
 楼主| SHOPQQ 发表于 2022-11-23 14:24 | 显示全部楼层
第四步:编写 main.c 代码
  1. /* USER CODE BEGIN PFP */
  2. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
  3. {
  4.         if(hadc == &hadc1)
  5.         {
  6.                 // 使用DMA其实也会运行到这里,也可以将结果在这里输出。
  7.                 // 当然此函数也可以不写。
  8.         }
  9. }
  10. /* USER CODE END PFP */

  11. /**
  12.   * @brief  The application entry point.
  13.   * @retval int
  14.   */
  15. int main(void)
  16. {
  17.   /* USER CODE BEGIN 1 */
  18.         uint16_t adc_value[3] = {0};
  19.   /* USER CODE END 1 */

  20.   /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  21.   HAL_Init();
  22.   
  23.   /* Configure the system clock */
  24.   SystemClock_Config();
  25.   
  26.   /* Initialize all configured peripherals */
  27.   MX_GPIO_Init();
  28.   MX_DMA_Init();
  29.   MX_ADC1_Init();
  30.   MX_USART1_UART_Init();
  31.   
  32.   /* USER CODE BEGIN WHILE */
  33.         HAL_ADCEx_Calibration_Start(&hadc1);
  34.         // enable DMA通道
  35.         // 参数:ADC1、目标缓冲区地址、从ADC外围设备传输到内存的数据长度
  36.         /*
  37.          * 此处有个大坑,经过测试,DMA中断非常容易进(具体的不知道)
  38.          *
  39.          * 如果ADC采样周期短的话,一直在执行中断,
  40.          * 导致无法执行主程序,因此会卡死在这个函数里面出不去。
  41.          *
  42.          * 因此,ADC的采用周期需要长一点。
  43.          */
  44.         HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_value, 3);
  45.        
  46.   while (1)
  47.   {
  48.         printf("-------------------- \r\n");
  49.         printf("adc value[0]:%f \r\n", adc_value[0]/4096.0*3.3);
  50.         printf("adc value[1]:%f \r\n", adc_value[1]/4096.0*3.3);
  51.         printf("adc value[2]:%f \r\n", adc_value[2]/4096.0*3.3);
  52.         HAL_Delay(1000);
  53.     /* USER CODE END WHILE */
  54.     /* USER CODE BEGIN 3 */
  55.   }
  56.   /* USER CODE END 3 */
  57. }
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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