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
显示的结果如下图所示,观察频域图发现与输入信号的频率一致文章来源:https://uudwc.com/A/pJa0w
文章来源地址https://uudwc.com/A/pJa0w