文章目录
一、前言
学习记录,已建好工程。STM32F405单通道ADC+DMA,采样率2MHz,每次采集固定长度后停止。
二、步骤
一)配置Cube MX
双击 “Cube Mx Settings”(或“cubemx”->“cubemx.ioc”)打开Cube Mx,配置ADC。
1、配置时钟
在12位分辨率下,要实现2MHz采集频率,必须尽量选择较低的采样时间,采样时间选择3cycles。采集并转换一次的时间为15个时钟周期,那么ADC时钟即为2MHz × 15 = 30MHz。ADC时钟由PCLK2分频得来,最小2分频,PCLK2时钟为60MHz。
2、配置ADC
ADC使能连续转换模式,软件触发采集后可连续采集。使能DMA连续请求模式。建立规则组,选择对应的通道,转换时间选择3cycles。
3、配置DMA
为ADC通道选择一个DMA,模式为circular,外设地址不自增,内存地址自增,数据长度均为16位。这样ADC单通道采集的数据会被DMA按采集顺序依次搬到设置的数组中。开启DMA中断。
点击生成代码,并关闭CubeMX。
二)配置RT-thread Settings
双击 “RT-Thread Settings”打开配置页面,打开UART设备驱动程序(用于打印调试信息与发送数据),打开ADC驱动程序。
三)配置工程
1、排除构建
打开工程中“cubemx”文件夹,除“cubemx.ioc”外,其余全部排除构建。
2、复制生成的ADC相关函数
在软件左上角的“导航器”中选择项目,并将“cubemx”文件夹中“adc.c”文件的“MX_ADC1_Init”、“HAL_ADC_MspInit”,“dma.c”文件的“MX_DMA_Init”函数复制到“drivers”文件夹中“board.c”文件末尾。RT-Thread Studio只要求复制“HAL_ADC_MspInit”函数,但是RT-Thread目前并不支持ADC+DMA,所以3个函数均需要复制。ADC与DMA初始化需要的结构体也一并复制。
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
/* ADC1 init function */
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspInit 0 */
/* USER CODE END ADC1_MspInit 0 */
/* ADC1 clock enable */
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**ADC1 GPIO Configuration
PA3 ------> ADC1_IN3
*/
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* ADC1 DMA Init */
/* ADC1 Init */
hdma_adc1.Instance = DMA2_Stream0;
hdma_adc1.Init.Channel = DMA_CHANNEL_0;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);
/* USER CODE BEGIN ADC1_MspInit 1 */
/* USER CODE END ADC1_MspInit 1 */
}
}
void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA2_CLK_ENABLE();
/* DMA interrupt init */
/* DMA2_Stream0_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
3、配置board.h文件
将ADC1外设在“board.h"文件中打开宏定义。
按需求配置UART。
4、配置stm32f1xx_hal_conf.h
打开“#define HAL_ADC_MODULE_ENABLED”、“#define HAL_UART_MODULE_ENABLED”、“#define HAL_DMA_MODULE_ENABLED”宏定义。
5、测试功能
新建一个.c文件,在文件中建立一个线程,初始化ADC与DMA外设,并创建一个信号量。线程函数中先清除信号量,随后设置DMA传输的起始地址与长度,开始ADC采集与DMA传输。当DMA传输固定长度完成时,会触发DMA传输完成中断,此时在中断中禁用ADC DMA并禁用ADC外围设备,释放信号量。处理函数接收信号量后,即可处理数据,程序中将存储ADC数据的数组通过串口发送给了上位机软件“VOFA+”。
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-03-13 73554 the first version
*/
#include "board.h"
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "ADC"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "user_config.h"
/*********************************************************************************/
/*********************************************************************************/
#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */
#define ADC_DEV_CHANNEL 3 /* ADC 通道 */
#define DMA_BUF_COUNT 2000
struct rt_semaphore adc_done_sem; //ADC采集完成信号量
rt_adc_device_t pAdcDev;
rt_uint16_t l_AdcDmaBuf_u16[2500];
int_float g_int_float;
extern ADC_HandleTypeDef hadc1;
extern DMA_HandleTypeDef hdma_adc1;
/*********************************************************************************/
/*********************************************************************************/
#define UART485 "uart1"
//struct rt_semaphore uart4_rx_sem; //接收信号量
static rt_device_t uart1_serial; //串口设备句柄
static struct serial_configure Uart485Config = RT_SERIAL_CONFIG_DEFAULT;
/*********************************************************************************/
/*********************************************************************************/
/* 初始化ADC外设
*/
int L_ADCInit_i32(void)
{
/* 查找设备 */
pAdcDev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
if (pAdcDev == RT_NULL)
{
LOG_E("adc1 not find !");
return RT_ERROR;
}
MX_ADC1_Init(); //初始化ADC
MX_DMA_Init(); //初始化DMA
HAL_ADC_MspInit(&hadc1); //初始化其余参数,并将DMA与ADC联系起来
return RT_EOK;
}
/*********************************************************************************/
/*********************************************************************************/
/* 设置DMA搬运的内存初始地址与长度,开始ADC采集
*/
static void L_StartADCAcquisition(void)
{
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)l_AdcDmaBuf_u16, DMA_BUF_COUNT);
}
/*********************************************************************************/
/*********************************************************************************/
/* DMA2 Stream0 传输完成中断处理函数
*/
void DMA2_Stream0_IRQHandler(void)
{
/* enter interrupt */
//LOG_I("DMA2_Stream0_IRQHandler."); //LOG函数会占用大量的CPU时间,调试完成后此处LOG一定要去除
rt_interrupt_enter();
HAL_DMA_IRQHandler(&hdma_adc1);
/* leave interrupt */
rt_interrupt_leave();
}
/*********************************************************************************/
/*********************************************************************************/
/* ADC转换完成回调函数
*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
HAL_ADC_Stop_DMA(&hadc1); //自动停止ADC和DMA
rt_sem_release(&adc_done_sem);
//LOG_I("HAL_ADC_ConvCpltCallback."); //LOG函数会占用大量的CPU时间,调试完成后此处LOG一定要去除
}
/*********************************************************************************/
/*********************************************************************************/
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc)
{
LOG_I("HAL_ADC_ErrorCallback.");
}
/*********************************************************************************/
/*********************************************************************************/
/* ADC线程入口函数
*/
void G_ADC_thread_entry(void *parameter)
{
rt_uint8_t UartTx1Buf[100] = {};
while(1)
{
rt_sem_control(&adc_done_sem, RT_IPC_CMD_RESET, (void*) 0); //清一下信号量
//todo 用户函数,同步触发一些信号
L_StartADCAcquisition();
rt_err_t res = rt_sem_take(&adc_done_sem, rt_tick_from_millisecond(10));
if(res != RT_EOK) //未收到
{
// LOG_E("no rx !");
}
else
{
LOG_I("adc1 get data succ. data1:%d, data2:%d.", l_AdcDmaBuf_u16[0], l_AdcDmaBuf_u16[1]);
for(int a = 0; a < 2000; a++) //将采集到的adc数据数组通过串口发送
{
g_int_float.datau64 = 0;
g_int_float.dataf = (float)l_AdcDmaBuf_u16[a];
UartTx1Buf[0] = g_int_float.datau8[0];
UartTx1Buf[1] = g_int_float.datau8[1];
UartTx1Buf[2] = g_int_float.datau8[2];
UartTx1Buf[3] = g_int_float.datau8[3];
UartTx1Buf[4] = 0x00; //vofa 浮点数协议帧尾
UartTx1Buf[5] = 0x00;
UartTx1Buf[6] = 0x80;
UartTx1Buf[7] = 0x7f;
rt_device_write(uart1_serial, 0, UartTx1Buf, 8);
rt_thread_delay(1); //让出cpu
}
}
for(int a = 0; a < 200; a++) //发送200个-100,作为区分两次采集的标志
{
g_int_float.datau64 = 0;
g_int_float.dataf = -100.0f;
UartTx1Buf[0] = g_int_float.datau8[0];
UartTx1Buf[1] = g_int_float.datau8[1];
UartTx1Buf[2] = g_int_float.datau8[2];
UartTx1Buf[3] = g_int_float.datau8[3];
UartTx1Buf[4] = 0x00;
UartTx1Buf[5] = 0x00;
UartTx1Buf[6] = 0x80;
UartTx1Buf[7] = 0x7f;
rt_device_write(uart1_serial, 0, UartTx1Buf, 8);
rt_thread_delay(1);
}
// rt_thread_delay(200);
}
}
/*********************************************************************************/
/*********************************************************************************/
/* 初始化ADC线程
* 在main函数中调用此函数
*/
int G_InitADCThread_i32()
{
if(L_ADCInit_i32() != RT_EOK)
{
LOG_E("init ADC1 err !");
return RT_ERROR;
}
/* 初始化信号量 */
rt_sem_init(&adc_done_sem, "adc1_sem", 0, RT_IPC_FLAG_FIFO);
rt_thread_t thread = rt_thread_create("ADC1_serial", G_ADC_thread_entry, RT_NULL, 2048, 1, 10);
if(thread != RT_NULL)
{
rt_thread_startup(thread);
LOG_I("run adc1.");
}
else
{
LOG_E("create ADC1 thread failed !");
return RT_ERROR;
}
return RT_EOK;
}
/*********************************************************************************/
/*********************************************************************************/
/*********************************************************************************/
/* 初始化发送ADC数据的串口
*/
int G_ADCUartInit_i32(void)
{
int ret = 0;
/* 查找系统中的串口设备 */
uart1_serial = rt_device_find(UART485);
if(uart1_serial == RT_NULL)
{
LOG_E("find %s failed !", UART485);
return RT_ERROR;
}
/* 修改串口配置参数 */
Uart485Config.baud_rate = BAUD_RATE_115200; //波特率
Uart485Config.data_bits = DATA_BITS_8; //数据位 8
Uart485Config.stop_bits = STOP_BITS_1; //停止位 1
Uart485Config.bufsz = 512 * 2; //修改缓冲区 buff size
Uart485Config.parity = PARITY_NONE; //无奇偶校验位
rt_device_control(uart1_serial, RT_DEVICE_CTRL_CONFIG, &Uart485Config);
/* 以 中断接收、轮询发送 模式打开串口设备 */
rt_device_open(uart1_serial, RT_DEVICE_FLAG_INT_RX);
return ret;
}
INIT_APP_EXPORT(G_ADCUartInit_i32); //在main函数前初始化此线程
/*********************************************************************************/
#define ctrl_485_pin_num GET_PIN(A, 8)
int G_gpio_Ctrl(void) //485控制脚
{
rt_pin_write(ctrl_485_pin_num, 1);
rt_pin_mode(ctrl_485_pin_num, PIN_MODE_OUTPUT);
rt_pin_write(ctrl_485_pin_num, 1);
return RT_EOK;
}
INIT_APP_EXPORT(G_gpio_Ctrl); //此函数会在main函数前执行
/*********************************************************************************/
//rt_err_t g_GetADCData()
//{
// rt_adc_device_t pAdcDev;
// rt_uint32_t data1, data2, data3;
//
// /* 查找设备 */
// pAdcDev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
// if (pAdcDev == RT_NULL)
// {
// LOG_E("adc1 not find !");
// return RT_ERROR;
// }
//
// rt_adc_enable(pAdcDev, ADC_DEV_CHANNEL);
// rt_adc_enable(pAdcDev, ADC_FuShe_CHANNEL);
// rt_adc_enable(pAdcDev, ADC_REF_CHANNEL);
//
// data1 = rt_adc_read(pAdcDev, ADC_DEV_CHANNEL);
// data2 = rt_adc_read(pAdcDev, ADC_FuShe_CHANNEL);
// data3 = rt_adc_read(pAdcDev, ADC_REF_CHANNEL);
//
//
// LOG_D("dev:%f, fushe:%f, ref:%f",
// (float)data1 / 4095.0f * 3.3f,
// (float)data2 / 4095.0f * 3.3f,
// (float)data3 / 4095.0f * 3.3f);
//
// rt_adc_disable(pAdcDev, ADC_DEV_CHANNEL);
// rt_adc_disable(pAdcDev, ADC_FuShe_CHANNEL);
// rt_adc_disable(pAdcDev, ADC_REF_CHANNEL);
//
// return RT_EOK;
//}
typedef union
{
uint8_t datau8[8];
int16_t datai16;
uint16_t datau16;
int32_t datai32;
uint32_t datau32;
float dataf;
double datad;
uint64_t datau64;
}int_float;
测试中使用示波器校准探头用的方波发生器作为被测信号,Vpp = 3V,f = 1kHz,信号直接接入单片机ADC引脚,电路上未做任何处理。信号周期为1ms,采样率2MHz,1个信号周期ADC需要采集2000次。
控制台打印的提示信息。
在确认ADC DMA功能正常后,一定要去除关键时间点(DMA中断、ADC中断、用户启动ADC程序)的LOG函数,否则可能会因为大量延时(ms级)造成ADC采集不到需要的信号或数据异常。