STM32F4 HAL库使用DMA进行ADC采样实时发送波形到串口显示(包含傅里叶变换)

1.总体逻辑

按下 STM32F4 的 KEY0 按键,通过外部中断的方式对按键进行检测,然后进行一次固定点数的 DMA ADC 采集,采集完成后在 DMA 的中断发送采集到的数据,然后清空数据区准备下一次的按键中断。电脑接受到串口数据后对数据进行简单处理和傅里叶变化,然后实时显示在电脑上。
开发板:正点原子探索者STM32F407ZG

2. STM32

源工程文件
可以拿着正点原子的官方例程的单通道ADC采集(DMA读取)实验进行修改
这里只展示
部分重要代码

2.1 外部中断处理函数

打开 exti.c 文件,修改为以下的代码。删掉了冗余的代码,在 KEY0 按下后的逻辑中加入了 adc_dma_enable(ADC_DMA_BUF_SIZE) 来启动一次ADC采集

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/EXTI/exti.h"
#include "./BSP/ADC/adc.h"

/**
 * @brief       KEY0 外部中断服务程序
 * @param       无
 * @retval      无
 */
void KEY0_INT_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN);         /* 调用中断处理公用函数 清除KEY0所在中断线 的中断标志位 */
    __HAL_GPIO_EXTI_CLEAR_IT(KEY0_INT_GPIO_PIN);         /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
}

/**
 * @brief       中断服务程序中需要做的事情
 *              在HAL库中所有的外部中断服务函数都会调用此函数
 * @param       GPIO_Pin:中断引脚号
 * @retval      无
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    delay_ms(20);      /* 消抖 */
    switch(GPIO_Pin)
    {
        case KEY0_INT_GPIO_PIN:
            if (KEY0 == 0)
            {
                LED0_TOGGLE();
                adc_dma_enable(ADC_DMA_BUF_SIZE);                   /* 启动一次ADC DMA采集 */
            }
            break;
        default : break;
    }
}

/**
 * @brief       外部中断初始化程序
 * @param       无
 * @retval      无
 */
void extix_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    
    key_init();
    gpio_init_struct.Pin = KEY0_INT_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;            /* 下降沿触发 */
    gpio_init_struct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(KEY0_INT_GPIO_PORT, &gpio_init_struct);    /* KEY0配置为下降沿触发中断 */

    HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2);               /* 抢占0,子优先级2 */
    HAL_NVIC_EnableIRQ(KEY0_INT_IRQn);                       /* 使能中断线4 */

}

2.2 DMA传输完成中断函数

adc.c 中,找到 ADC DMA 采集中断服务函数,也就是 ADC_ADCX_DMASx_IRQHandler ,然后修改为如下的代码。在 DMA 采集完成后触发的中断中,通过串口将采集到的数据发送出来

/**
 * @brief       ADC DMA采集中断服务函数
 * @param       无
 * @retval      无
 */
void ADC_ADCX_DMASx_IRQHandler(void)
{
    if (ADC_ADCX_DMASx_IS_TC())     /* 判断DMA数据传输完成 */
    {
        g_adc_dma_sta = 1;          /* 标记DMA传输完成 */
        for (int i = 0; i < ADC_DMA_BUF_SIZE; i++)  /* 累加 */
        {
            printf("%d\r\n",g_adc_dma_buf[i]);
        }
        ADC_ADCX_DMASx_CLR_TC();    /* 清除DMA2 数据流4 传输完成中断 */
    }
}

2.3 DMA缓存区大小设置

adc.h 中,修改 ADC_DMA_BUF_SIZE 为自己想要的大小,如下图所示
在这里插入图片描述

2.4主函数

main.c 修改为如下代码

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc.h"
#include "./BSP/EXTI/exti.h"
#include "string.h"

uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];   /* ADC DMA BUF */
extern uint8_t g_adc_dma_sta;               /* DMA传输状态标志, 0,未完成; 1, 已完成 */

int main(void)
{
    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */
    delay_init(168);                        /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */
    lcd_init();                             /* 初始化LCD */
    extix_init();                           /* 初始化外部中断输入 */
    adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
    
    lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30,  70, 200, 16, 16, "ADC DMA TEST", RED);
    lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH5_VAL:", BLUE);
    lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH5_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
    
    while (1)
    {

    }
}

2.5采样率的计算

目前采用的是 21M 时钟,一个时钟周期是 0.047us,采集 3 个周期,转化要 12 个周期(STM32F4 参考手册有写),采样一次就是 15 个周期
采样时间:0.047x15=0.705us
采样率为:1/0.705=1.418M(这里由于计算的时候采用了近似值,若不使用近似值计算下来就是1.4M)
理论最高采集 0.700M 的信号,即 700K 的信号

3. Python实时显示

这里的傅里叶变化只会显示最后的 POINT 个点的傅里叶变化情况

#%%
import serial
import matplotlib.pyplot as plt
import numpy as np
import time
#%%
LINE = 1 # 是否用线的方式连接
OFFSET = 0 # 是否减去偏置值
POINT = 140 # 这里设置的大小和STM32中DMA缓存区的大小要一致
SAMPLE = 1.418e6
count = 0
# 设置画布大小
fig, (ax1, ax2) = plt.subplots(1, 2)
line1, = ax1.plot([], [])
line2, = ax2.plot([], [])
ax1.set_xlim(0, 100)
ax2.set_xlim(0,SAMPLE)
ax1.set_ylim(0, 5)
ax2.set_ylim(0, 100)
ax1.set_title('Time Domain')
ax2.set_title('Frequency Domain')

# 初始化数据
x = []
y = []
yfft = []
xfft = np.linspace(0,POINT-1,POINT)
xfft = xfft*(SAMPLE/POINT)

# 创建曲线对象
if LINE:
    line1, = ax1.plot([], [])
    line2, = ax2.plot([], [])
else:
    line1, = ax1.plot([], [],'.')
    line2, = ax2.plot([], [],'.')   

# 开始绘图
start_time = time.time()
ser = serial.Serial(port ='COM3', baudrate =115200,bytesize = serial.EIGHTBITS,parity = serial.PARITY_NONE,
                    stopbits = serial.STOPBITS_ONE,xonxoff = False,rtscts =False,dsrdtr =False) 
#%%
# 循环读取串口数据并绘图
while True:
    count+=1
    # print(ser.inWaiting())
    # 读取串口数据
    if(ser.inWaiting()):
        line = ser.readline()
        ser.flush()
        # print(line)
        if len(line) : 
            real_vol = int(line) * (3.3 / 4096)
            print(real_vol)
    else:
        real_vol = 0

    # 实时更新x轴
    t = time.time() - start_time

    # 更新数据
    x.append(t)
    y.append(real_vol)
    if count>POINT:
        #FFT
        temp = []
        # xfft = np.linspace(0,POINT,1)
        # xfft = xfft*(SAMPLE/POINT)
        if OFFSET:
            yfft = np.fft.fft(y[-POINT:]-np.mean(y[-POINT:]))
        else:
            yfft = np.fft.fft(y[-POINT:])
        line2.set_data(xfft, abs(yfft))
    
    # 更新曲线数据
    line1.set_data(x, y)
    ax1.set_xlim(max(0, t - 5), t)

    # 重新绘制图形
    fig.canvas.draw()
    fig.canvas.flush_events()
    plt.pause(0.01)  # 控制循环速率 

4. 结果展示

输入信号:700KHz 正弦波,幅度1V,偏置1V
显示的结果如下图所示,观察频域图发现与输入信号的频率一致

在这里插入图片描述

  • 9
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
STM32F4HAL库是针对STM32F4系列微控制器的官方库,提供了丰富的功能和接口,方便开发者进行快速开发。ADC(Analog-to-Digital Converter)是模数转换器,用于将模拟信号转换为数字信号。在STM32F4HAL库中,ADC单通道采样可以通过以下步骤实现: 1. 初始化ADC模块:使用`HAL_ADC_Init()`函数初始化ADC模块,设置采样分辨率、采样时间等参数。 2. 配置ADC通道:使用`HAL_ADC_ConfigChannel()`函数配置ADC通道,选择要采样的通道和采样顺序。 3. 启动ADC转换:使用`HAL_ADC_Start()`函数启动ADC转换。 4. 等待转换完成:使用`HAL_ADC_PollForConversion()`函数等待转换完成。 5. 读取转换结果:使用`HAL_ADC_GetValue()`函数读取转换结果。 下面是一个示例代码,演示了如何使用STM32F4HAL库进行ADC单通道采样: ```c #include "stm32f4xx_hal.h" ADC_HandleTypeDef hadc; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_ADC1_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); while (1) { // 启动ADC转换 HAL_ADC_Start(&hadc); // 等待转换完成 HAL_ADC_PollForConversion(&hadc, HAL_MAX_DELAY); // 读取转换结果 uint32_t adcValue = HAL_ADC_GetValue(&hadc); // 处理采样结果... // 延时一段时间 HAL_Delay(1000); } } void SystemClock_Config(void) { // 系统时钟配置... } static void MX_GPIO_Init(void) { // GPIO初始化... } static void MX_ADC1_Init(void) { // ADC初始化... hadc.Instance = ADC1; hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2; hadc.Init.Resolution = ADC_RESOLUTION_12B; hadc.Init.ScanConvMode = DISABLE; hadc.Init.ContinuousConvMode = DISABLE; hadc.Init.DiscontinuousConvMode = DISABLE; hadc.Init.NbrOfDiscConversion = 0; hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc.Init.NbrOfConversion = 1; hadc.Init.DMAContinuousRequests = DISABLE; hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV; if (HAL_ADC_Init(&hadc) != HAL_OK) { Error_Handler(); } ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES; if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK) { Error_Handler(); } } ```
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天地神仙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值