RT-Thread 学习记录(四)STM32F405单通道2MHz ADC+DMA固定长度采集


一、前言

学习记录,已建好工程。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采集不到需要的信号或数据异常。

三、工程代码

https://gitee.com/www73554/stm32-f405_-ta1.0.git

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值