FreeRTOS下STM32双缓冲ADC数据采集与处理

目录

[1. CubeMX工程配置ADC](#1. CubeMX工程配置ADC)

ADC配置:

DMA配置:

[2. 代码编写](#2. 代码编写)

业务逻辑:

具体实现逻辑:

逻辑图

流程图

详细说明

[1. 系统初始化流程](#1. 系统初始化流程)

[2. ADC转换完成中断处理](#2. ADC转换完成中断处理)

[3. 线程A (默认任务)处理流程](#3. 线程A (默认任务)处理流程)

[4. 线程B (数据处理任务)处理流程](#4. 线程B (数据处理任务)处理流程)

[5. 关键设计特点](#5. 关键设计特点)

[6. 数据流向](#6. 数据流向)

[7. 错误处理](#7. 错误处理)

代码


1. CubeMX工程配置ADC

ADC配置:

  • Clock Prescaler

  • Resolution:几位有效数据(ADC寄存器有16位,这里12表示只有12位有效)

  • Data Alignment:数据对齐方向(Right alignment表示向右对齐,即16位的寄存器,有效数据向右对齐,只有右边的低12位有效,高4位是无效数据),虽然CPU读取时还是按照2字节读取,但是只取出其中12位的有效数据。

  • Scan Conversion Mode 是控制:ADC 是否按照配置的通道序列,对多个通道进行依次转换。(我们本次就一个通道,所以未开启)

    • 如果是 Enable,ADC 将按照在规则通道(Regular Channels)中配置的通道序列,依次对多个通道进行转换。 每次触发(无论是软件触发还是硬件触发),ADC 都会按照顺序对所有配置的通道进行一次完整的转换序列。适用于需要同时采集多个模拟信号的情况,例如多传感器数据采集、数据监测等。

      • 如果同时启用了 Continuous Conversion Mode(连续转换模式), ADC 会在完成一次完整的通道序列转换后,立即开始下一次序列的转换,形成一个连续的循环。如果没有启用 Continuous Conversion Mode(连续转换模式),ADC 在完成一次通道序列转换后停止,等待下一个触发事件。

      • 如果同时启用了 Discontinuous Conversion Mode(非连续转换模式),会将通道序列分成若干组,每次触发事件只转换一组通道。

    • 如果是 Disable,ADC 仅对配置的一个通道进行转换,没有通道序列的概念。每次触发只转换一个通道,简单高效。适用于只需要采集一个模拟信号的简单应用,例如单一传感器的读取。

  • Continuous Conversion Mode(连续转换模式)是控制:是否持续的对某一个通道不停地转换,你会在 DR 里面一直看到数据更新,EOC 标志位一直会产生。于是你可以通过轮询或者中断的方式一直来取 ADC 的数据。

  • Discontinuous Conversion Mode(非连续转换模式)是控制:是否 ADC 将进入 非连续转换模式(Discontinuous Conversion Mode)。

    • 在非连续转换模式下(Enable),ADC 会将配置的通道序列分成若干个小组,每个小组的大小由 Discontinuous Number(非连续数目)参数确定,范围是 1 到 8。(应确保 Scan Conversion Mode 也是 Enable 的)

      • ADC 会在每个触发事件(比如软件或硬件触发)下,仅转换一个小组的通道,然后停止,等待下一个触发事件。每次非连续转换都需要新的触发事件。这种模式适用于需要在多个触发事件下分批次采样的情况。

        • 例如,在实时控制系统中,可能希望在每个控制周期内只采样部分通道(而不是 全部通道),以减少 CPU 负担。比如说,如果你配置了 6 个通道的序列,且将 Discontinuous Number 设置为 2,那么 ADC 会将这 6 个通道分成 3 组,每组 2 个通道。每次触发事件会启动一组(2 个通道)的转换,需要 3 次触发事件才能完成所有通道的转换。
  • DMA Continuous Requests:

    • enabled 每次ADC转换完了数据都会发出DMA请求,让DMA来搬运数据。

    • Disabled 状态下,即使开启了 Continuous Conversion Mode,不停地对一个通道转换,也只会发出一次DMA请求。

  • End Of Conversion Selection:

    • EOC flag at the end of single channel conversions:每一次转换完成后产生中断

    • EOC flag at the end of all conversions:所有转换完成后再产生中断

DMA配置:

2. 代码编写

业务逻辑:

  • ADC+DMA搬运ADC采集的数据到数据缓冲区buffer1或buffer2,数据搬运完成后,通过ADC转换完成回调函数发送queue1通知线程A控制更换另一个缓冲区作为下次接收的数据缓冲区,同时使用queue2通知线程B去刚搬运好数据的缓冲区中处理ADC转换数据。在线程A和线程B之间创建一个互斥锁,保证这两个线程互斥运行。

具体实现逻辑:

  • 在FreeRTOS下,一开始默认使用ADC+DMA搬运ADC采集的数据到数据缓冲区buffer1,配置ADC转换完成回调函数,在ADC转换完成回调函数中发送queue1给线程A。

  • 线程A阻塞等待receive queue1的数据,接收到数据后,使用queue_peek查看queue2中的数据是否被线程B接收走,如果队列为空,则阻塞等待获取互斥锁,拿到互斥锁后,HAL_ADC_Start_DMA启动ADC转换和DMA搬运到另一个buffer,使用一个全局变量DMA_pointer来标志当前使用的buffer(为0则目前dma搬运的目标buffer是buffer1,为1则目前dma搬运的目标buffer是buffer2),根据DMA_pointer的值来判断发送queue2的值是BUFFER1_READY还是BUFFER2_READY。

  • 线程B使用queue_peek阻塞查看queue2是否有数据,如果queue_peek返回pdTRUE,则阻塞获取互斥锁,然后receive queue2的数据,处理数据,然后释放互斥锁。

逻辑图

流程图

详细说明

1. 系统初始化流程
  1. 分配两个数据缓冲区(buffer1和buffer2)

  2. 创建两个队列:

    1. queue1 (ADC_Conv_Cplt_Notice_xQueue): 用于ADC转换完成通知

    2. queue2 (ADC_data_need_cal_Notice_xQueue): 用于数据就绪通知

  3. 创建互斥锁(ADC_Mutex)确保线程间同步

  4. 初始化DMA_pointer为0,表示当前使用buffer1

  5. 启动ADC DMA传输,将数据直接搬运到buffer1

2. ADC转换完成中断处理
  • 当ADC转换完成时,HAL库会调用HAL_ADC_ConvCpltCallback函数

  • 在中断上下文中,向queue1发送通知(DMA_ADC_CPLT_INT)

  • 必要时触发任务切换,确保高优先级任务及时运行

3. 线程A (默认任务)处理流程
  1. 阻塞等待queue1的通知

  2. 收到通知后,检查queue2是否为空(确保前一次数据已被处理)

  3. 获取互斥锁(防止与线程B同时访问共享资源)

  4. 根据DMA_pointer值切换DMA目标缓冲区:

    1. 如果当前使用buffer1,则切换到buffer2

    2. 如果当前使用buffer2,则切换到buffer1

  5. 通过queue2发送通知,告知线程B哪个缓冲区数据就绪

  6. 释放互斥锁

4. 线程B (数据处理任务)处理流程
  1. 使用queue_peek检查queue2是否有数据(不移除消息)

  2. 发现数据后,获取互斥锁

  3. 从queue2接收数据,确定哪个缓冲区数据就绪

  4. 处理相应缓冲区中的数据

  5. 释放互斥锁

5. 关键设计特点
  1. 双缓冲机制: 使用两个缓冲区交替工作,实现数据采集和处理的并行执行

  2. 线程同步: 通过互斥锁确保对共享资源(缓冲区和DMA_pointer)的互斥访问

  3. 通知机制: 使用两个队列实现任务间通信,解耦数据采集和数据处理

  4. 阻塞等待: 任务在等待资源时主动让出CPU,提高系统效率

6. 数据流向
cpp 复制代码
ADC -> DMA -> buffer1/buffer2 -> 线程B处理

数据通过DMA直接搬运到内存缓冲区,减少了CPU干预,提高了系统效率。

7. 错误处理

代码中包含了对队列操作、内存分配和互斥锁操作的错误检查,确保系统稳定性。

这种设计实现了ADC数据采集和处理的流水线操作,充分利用了DMA和双缓冲技术的优势,提高了系统的实时性和效率。

代码

cpp 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : freertos.c
  * Description        : Code for freertos applications
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdlib.h>
#include <string.h>
#include "queue.h"
#include "semphr.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define BUFFER_SIZE 1

uint32_t* buffer1 = NULL;
uint32_t* buffer2 = NULL;
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define DMA_ADC_CPLT_INT    0xA1
#define BUFFER1_READY       0x01
#define BUFFER2_READY       0x02
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */

extern ADC_HandleTypeDef hadc1;         // ADC handle
extern DMA_HandleTypeDef hdma_adc1;     // ADC's DMA handle

QueueHandle_t ADC_Conv_Cplt_Notice_xQueue = NULL;       // ADC_Conv_Cplt_Notice_xQueue
QueueHandle_t ADC_data_need_cal_Notice_xQueue = NULL;   // ADC data need to calculate notice queue
uint32_t DMA_pointer = 0;                               // 0: buffer1   1: buffer2

osMutexId_t ADC_Mutex = NULL;              // define ADC_Mutex

/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
  .name = "defaultTask",
  .stack_size = 128 * 4,
  .priority = (osPriority_t) osPriorityNormal,
};

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
osThreadId_t adc_output_TaskHandle;
const osThreadAttr_t adc_output_Task_attributes = {
  .name = "adc_output_Task",
  .stack_size = 128 * 4,
  .priority = (osPriority_t)osPriorityNormal,
};

void adc_output_Task(void* argument);

/* USER CODE END FunctionPrototypes */

void StartDefaultTask(void *argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* creation of defaultTask */
  defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);

  /* USER CODE BEGIN RTOS_THREADS */
  adc_output_TaskHandle = osThreadNew(adc_output_Task, NULL, &adc_output_Task_attributes);
  /* add threads, ... */
  /* USER CODE END RTOS_THREADS */

  /* USER CODE BEGIN RTOS_EVENTS */
  /* add events, ... */
  /* USER CODE END RTOS_EVENTS */

}

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
    /* USER CODE BEGIN StartDefaultTask */
    // 0:buffer1    || 1:buffer2
    DMA_pointer = 0;  

    // creat ADC_Conv_Cplt_Notice_xQueue
    ADC_Conv_Cplt_Notice_xQueue = xQueueCreate(10, 4);
    if (NULL == ADC_Conv_Cplt_Notice_xQueue)
    {
        printf("ADC_Conv_Cplt_Notice_xQueue creat failed! at [%d] tick\r\n", HAL_GetTick());
        return;
    }
    // creat ADC_data_need_cal_Notice_xQueue
    ADC_data_need_cal_Notice_xQueue = xQueueCreate(10, 4);
    if (NULL == ADC_data_need_cal_Notice_xQueue)
    {
        printf("ADC_data_need_cal_Notice_xQueue creat failed! at [%d] tick\r\n", HAL_GetTick());
        return;
    }
    // creat ADC_Mutex in default mode
    ADC_Mutex = xSemaphoreCreateMutex();
    if (ADC_Mutex == NULL) {
        printf("ADC_Mutex creat failed! at [%d] tick\r\n",HAL_GetTick());
    }
        // creat data receive buffer
    buffer1 = (uint32_t*)malloc(sizeof(uint32_t) * BUFFER_SIZE);
    buffer2 = (uint32_t*)malloc(sizeof(uint32_t) * BUFFER_SIZE);
    if (NULL == buffer1)
    {
        printf("buffer1 malloc failed!\r\n");
        return;
    }
    if (NULL == buffer2)
    {
        printf("buffer2 malloc failed!\r\n");
        return;
    }
    printf("buffer1,buffer2 malloc successfully!\r\n");
    // set buffer default value
    memset(buffer1, 0xFF, (sizeof(uint32_t) * BUFFER_SIZE));
    memset(buffer2, 0xFF, (sizeof(uint32_t) * BUFFER_SIZE));

    // Enables ADC DMA request after last transfer (Single-ADC mode) and enables ADC peripheral
    if (HAL_OK != HAL_ADC_Start_DMA(&hadc1, buffer1, BUFFER_SIZE))
    {
        // print error info
        printf("HAL_ADC_Start_DMA call [%d] tick\r\n", HAL_GetTick());
    }

#if 1   // UnitTest Queue send and receive
    
    // receive queue data
    BaseType_t queue_ret = pdTRUE;
    uint32_t ADC_Conv_Cplt_Pattern = 0xff;
    uint32_t ADC_data_need_cal_Pattern = BUFFER1_READY;
    BaseType_t buffer_is_handled_Pattern = pdTRUE;
    uint32_t tmp = 0;

#endif  // End of UnitTest Queue send and receive

    /* Infinite loop */
  for(;;)
  {
      osDelay(1);
      printf("default task at [%d] tick\r\n", HAL_GetTick());

      queue_ret = xQueueReceive(ADC_Conv_Cplt_Notice_xQueue, &ADC_Conv_Cplt_Pattern, portMAX_DELAY);
      if (pdTRUE != queue_ret)
      {
          printf("xQueueReceive ADC_Conv_Cplt_Notice_xQueue failed! at [%d] tick\r\n", HAL_GetTick());
      }

      // check if the buffer data is calculated by adc_output_Task
      while (pdTRUE == xQueuePeek(ADC_data_need_cal_Notice_xQueue, &tmp, 0))
      {
          // task switch
          taskYIELD();
      }

      // take ADC_Mutex
      if (pdTRUE != xSemaphoreTake(ADC_Mutex, portMAX_DELAY))
      {
          printf("ADC_Mutex take error! at [%d] tick\r\n", HAL_GetTick());
      }

      // change the DMA_receive_buffer and DMA_pointer
      if (0 == DMA_pointer)
      {
          // print receive data
          printf("buffer1 data = [%ld] at [%d] tick\r\n", buffer1[0], HAL_GetTick());
          HAL_ADC_Start_DMA(&hadc1, buffer2, BUFFER_SIZE);    // start ADC, DMA move data to buffer2
          DMA_pointer = 1;                                    // DMA_pointer point buffer2
          ADC_data_need_cal_Pattern = BUFFER1_READY;          // buffer1 data ready
          // Send queue to notice adc_output_Task to cal ADC data
          if (pdTRUE != xQueueSend(ADC_data_need_cal_Notice_xQueue, &ADC_data_need_cal_Pattern, portMAX_DELAY))
          {
              printf("ADC_data_need_cal_Notice_xQueue QueueSend error! [%d] tick",HAL_GetTick());
          }
      }
      else
      {
          // print receive data
          printf("buffer2 data = [%ld] at [%d] tick\r\n", buffer2[0], HAL_GetTick());
          HAL_ADC_Start_DMA(&hadc1, buffer1, BUFFER_SIZE);    // start ADC, DMA move data to buffer1
          DMA_pointer = 0;                                    // DMA_pointer point buffer1
          ADC_data_need_cal_Pattern = BUFFER2_READY;          // buffer2 data ready
          // Send queue to notice adc_output_Task to cal ADC data
          if (pdTRUE != xQueueSend(ADC_data_need_cal_Notice_xQueue, &ADC_data_need_cal_Pattern, portMAX_DELAY))
          {
              printf("ADC_data_need_cal_Notice_xQueue QueueSend error! [%d] tick", HAL_GetTick());
          }
      }

      // release ADC_Mutex
      if (pdTRUE != xSemaphoreGive(ADC_Mutex))
      {
          printf("ADC_Mutex give error! at [%d] tick\r\n", HAL_GetTick());
      }
  }
  /* USER CODE END StartDefaultTask */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */

void adc_output_Task(void* argument)
{
    printf("adc_output_thread [%d] tick\r\n", HAL_GetTick());

    uint32_t ADC_data_need_cal_Pattern = BUFFER1_READY;
    BaseType_t queue_ret = pdTRUE;
    uint32_t tmp = 0;

    for (;;)
    {
        osDelay(1);
        // receive queue notice
        if (NULL != ADC_data_need_cal_Notice_xQueue)
        {
            // queue_peek check if there's new data in queue
            if (pdTRUE != xQueuePeek(ADC_data_need_cal_Notice_xQueue, &tmp, portMAX_DELAY))
            {
                printf("xQueuePeek ADC_data_need_cal_Notice_xQueue error! at [%d] tick\r\n", HAL_GetTick());
            }

            // take ADC_Mutex
            if (pdTRUE != xSemaphoreTake(ADC_Mutex, portMAX_DELAY)) {
                printf("ADC_Mutex take error! at [%d] tick\r\n", HAL_GetTick());
            }

            // queue receive
            queue_ret = xQueueReceive(ADC_data_need_cal_Notice_xQueue, &ADC_data_need_cal_Pattern, portMAX_DELAY);
            if (pdTRUE != queue_ret)
            {
                printf("xQueueReceive ADC_Conv_Cplt_Notice_xQueue failed! at [%d] tick\r\n", HAL_GetTick());
            }
            // handle the data
            if (BUFFER1_READY == ADC_data_need_cal_Pattern)
            {
                printf("output thread buffer1 data = [%d] at [%d] tick\r\n", buffer1[0], HAL_GetTick());
            }
            else if (BUFFER2_READY == ADC_data_need_cal_Pattern)
            {
                printf("output thread buffer1 data = [%d] at [%d] tick\r\n", buffer2[0], HAL_GetTick());
            }

            // release ADC_Mutex
            if (pdTRUE != xSemaphoreGive(ADC_Mutex)) {
                printf("ADC_Mutex release error! at [%d] tick\r\n", HAL_GetTick());
            }
        }

    }
}

/* ADC Conversion Compelet Callback */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    /* Prevent unused argument(s) compilation warning */
    UNUSED(hadc);
    /* NOTE : This function Should not be modified, when the callback is needed,
              the HAL_ADC_ConvCpltCallback could be implemented in the user file
     */

    printf("HAL_ADC_ConvCpltCallback at [%d] tick\r\n", HAL_GetTick());
    // send Queue
    uint32_t dma_pattern_cplt           = DMA_ADC_CPLT_INT;
    BaseType_t queue_ret                = pdTRUE;           // return value of xQueueSendFromISR
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;          
    queue_ret = xQueueSendFromISR(ADC_Conv_Cplt_Notice_xQueue, &dma_pattern_cplt, &xHigherPriorityTaskWoken);
    // check the return value
    if (pdPASS != queue_ret)
    {
        printf("xQueueSend ADC_Conv_Cplt_Notice_xQueue failed! at [%d] tick\r\n", HAL_GetTick());
    }

    /* xQueueSendFromISR() will set
    ** pxHigherPriorityTaskWoken to pdTRUE if sending to the queue caused a task
    * to unblock, and the unblocked task has a priority higher than the currently
    * running task. */
    if (pdTRUE == xHigherPriorityTaskWoken)
    {
        // Set a PendSV to request a context switch after this ISR Callback
        taskYIELD();
    }
}

void HAL_ADC_ErrorCallback(ADC_HandleTypeDef* hadc)
{
    /* Prevent unused argument(s) compilation warning */
    UNUSED(hadc);
    /* NOTE : This function Should not be modified, when the callback is needed,
              the HAL_ADC_ErrorCallback could be implemented in the user file
     */
    printf("ADC convert error at [%d] tick\r\n",HAL_GetTick());
}

/* USER CODE END Application */
相关推荐
乌拉_乌拉_乌拉3 小时前
asrpro2.0天问语音模块搭配STM32(STM32F103c8t6)-杨桃电子开发板
stm32·单片机·嵌入式硬件
点灯小铭6 小时前
基于51单片机步数检测计步器无线蓝牙APP上传设计
单片机·嵌入式硬件·毕业设计·51单片机·课程设计
沐欣工作室_lvyiyi9 小时前
基于单片机和LabVIEW的多路数据采集器系统设计(论文+源码)
单片机·嵌入式硬件·毕业设计·数据采集·labview
何须至远10 小时前
机器人市场:犹如一颗深水核弹
stm32·单片机·机器人
LadyKaka22614 小时前
【IMX6ULL驱动学习】PWM驱动
linux·stm32·单片机·学习
Molesidy16 小时前
【MCU】【STM32】基于STM32CubeMX+CLion的STM32开发环境
stm32·单片机·嵌入式硬件·stm32cubemx·clion
光子物联单片机20 小时前
STM32G474单片机开发入门(一)STM32G474RET6单片机详解
stm32·单片机·嵌入式硬件·mcu
zhuxinmingde20 小时前
电机控制-PMSM无感FOC控制(五)相电流检测及重构 — 单电阻采样
单片机·嵌入式硬件·重构
眰恦ゞLYF20 小时前
嵌入式硬件——基于IMX6ULL的UART(通用异步收发传输器)
单片机·嵌入式硬件·uart·imx6ull