目录
[1. CubeMX工程配置ADC](#1. CubeMX工程配置ADC)
[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. 系统初始化流程
-
分配两个数据缓冲区(buffer1和buffer2)
-
创建两个队列:
-
queue1 (ADC_Conv_Cplt_Notice_xQueue): 用于ADC转换完成通知
-
queue2 (ADC_data_need_cal_Notice_xQueue): 用于数据就绪通知
-
-
创建互斥锁(ADC_Mutex)确保线程间同步
-
初始化DMA_pointer为0,表示当前使用buffer1
-
启动ADC DMA传输,将数据直接搬运到buffer1
2. ADC转换完成中断处理
-
当ADC转换完成时,HAL库会调用
HAL_ADC_ConvCpltCallback
函数 -
在中断上下文中,向queue1发送通知(DMA_ADC_CPLT_INT)
-
必要时触发任务切换,确保高优先级任务及时运行
3. 线程A (默认任务)处理流程
-
阻塞等待queue1的通知
-
收到通知后,检查queue2是否为空(确保前一次数据已被处理)
-
获取互斥锁(防止与线程B同时访问共享资源)
-
根据DMA_pointer值切换DMA目标缓冲区:
-
如果当前使用buffer1,则切换到buffer2
-
如果当前使用buffer2,则切换到buffer1
-
-
通过queue2发送通知,告知线程B哪个缓冲区数据就绪
-
释放互斥锁
4. 线程B (数据处理任务)处理流程
-
使用queue_peek检查queue2是否有数据(不移除消息)
-
发现数据后,获取互斥锁
-
从queue2接收数据,确定哪个缓冲区数据就绪
-
处理相应缓冲区中的数据
-
释放互斥锁
5. 关键设计特点
-
双缓冲机制: 使用两个缓冲区交替工作,实现数据采集和处理的并行执行
-
线程同步: 通过互斥锁确保对共享资源(缓冲区和DMA_pointer)的互斥访问
-
通知机制: 使用两个队列实现任务间通信,解耦数据采集和数据处理
-
阻塞等待: 任务在等待资源时主动让出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 */