FreeRTOS下STM32双缓冲ADC数据采集与处理
目录
1. CubeMX工程配置ADC
ADC配置:
DMA配置:
2. 代码编写
业务逻辑:
具体实现逻辑:
逻辑图
流程图
详细说明
1. 系统初始化流程
2. ADC转换完成中断处理
3. 线程A (默认任务)处理流程
4. 线程B (数据处理任务)处理流程
5. 关键设计特点
6. 数据流向
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. 数据流向
ADC -> DMA -> buffer1/buffer2 -> 线程B处理
数据通过DMA直接搬运到内存缓冲区,减少了CPU干预,提高了系统效率。
7. 错误处理
代码中包含了对队列操作、内存分配和互斥锁操作的错误检查,确保系统稳定性。
这种设计实现了ADC数据采集和处理的流水线操作,充分利用了DMA和双缓冲技术的优势,提高了系统的实时性和效率。
代码
/* 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 1uint32_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 handleQueueHandle_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: buffer2osMutexId_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:buffer2DMA_pointer = 0; // creat ADC_Conv_Cplt_Notice_xQueueADC_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_xQueueADC_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 modeADC_Mutex = xSemaphoreCreateMutex();if (ADC_Mutex == NULL) {printf("ADC_Mutex creat failed! at [%d] tick\r\n",HAL_GetTick());}// creat data receive bufferbuffer1 = (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 valuememset(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 peripheralif (HAL_OK != HAL_ADC_Start_DMA(&hadc1, buffer1, BUFFER_SIZE)){// print error infoprintf("HAL_ADC_Start_DMA call [%d] tick\r\n", HAL_GetTick());}#if 1 // UnitTest Queue send and receive// receive queue dataBaseType_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_Taskwhile (pdTRUE == xQueuePeek(ADC_data_need_cal_Notice_xQueue, &tmp, 0)){// task switchtaskYIELD();}// take ADC_Mutexif (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_pointerif (0 == DMA_pointer){// print receive dataprintf("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 buffer2DMA_pointer = 1; // DMA_pointer point buffer2ADC_data_need_cal_Pattern = BUFFER1_READY; // buffer1 data ready// Send queue to notice adc_output_Task to cal ADC dataif (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 dataprintf("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 buffer1DMA_pointer = 0; // DMA_pointer point buffer1ADC_data_need_cal_Pattern = BUFFER2_READY; // buffer2 data ready// Send queue to notice adc_output_Task to cal ADC dataif (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_Mutexif (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 noticeif (NULL != ADC_data_need_cal_Notice_xQueue){// queue_peek check if there's new data in queueif (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_Mutexif (pdTRUE != xSemaphoreTake(ADC_Mutex, portMAX_DELAY)) {printf("ADC_Mutex take error! at [%d] tick\r\n", HAL_GetTick());}// queue receivequeue_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 dataif (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_Mutexif (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 Queueuint32_t dma_pattern_cplt = DMA_ADC_CPLT_INT;BaseType_t queue_ret = pdTRUE; // return value of xQueueSendFromISRBaseType_t xHigherPriorityTaskWoken = pdFALSE; queue_ret = xQueueSendFromISR(ADC_Conv_Cplt_Notice_xQueue, &dma_pattern_cplt, &xHigherPriorityTaskWoken);// check the return valueif (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 CallbacktaskYIELD();}
}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 */