ADDA 三例程复现记录
这份笔记是干什么的
这份笔记记录本次把参考工程中的 ADDA 三个例程迁移到个人工程 D:\GD32F4_demo\adda_test 的完整复现链路。
这不是单纯的知识整理笔记,而是复现执行笔记,重点保留:
-
CubeMX 配置顺序
-
关键代码改动
-
编译报错和解决方式
-
实测串口现象
-
后续复盘点
原始备份笔记保留在:
D:\Embedded_Softwave_trellis\西门子嵌入式\05-练习层\note\7 ADDA三例程复现记录.md
整理版当前路径:
D:\Embedded_Softwave_trellis\西门子嵌入式\05-练习层\note_codex\7 ADDA三例程复现记录.md
对应课件与工程
-
课件:
嵌入式开发11:ADDA/index.html -
课件:
嵌入式开发12:STM32CUBEMX_ADDA配置.html -
参考工程:
D:\Embedded_Softwave_trellis\西门子嵌入式\scr\GD32\GD32_Xifeng_ADDA_波特率(460800)\GD32_Xifeng_ADDA -
本次个人工程:
D:\GD32F4_demo\adda_test -
本次方式:不是直接在参考工程里切
ADC_MODE,而是把参考工程的外设配置和应用层代码分三阶段迁移到个人工程。
参考工程特点:
-
APP/adc_app.c内通过#define ADC_MODE (1/2/3)切换三种例程。 -
参考工程串口波特率名义上是
460800。
本次个人工程特点:
-
串口实际使用
115200。 -
ADC 基础输入使用
PC0 / ADC_CHANNEL_10。 -
APP 层习惯使用
mydefine.h汇总头文件,adc_app.c中只保留#include "adc_app.h"。
本次复现前提
-
串口链路正常,
my_printf可以输出。 -
CubeMX ADC 基础资源已使能,
ADC1为 12 位、右对齐。 -
工程可以正常编译烧录。
-
Mode 1、Mode 2、Mode 3 都已经编译通过。
-
Mode 1、Mode 2、Mode 3 串口现象均正确。
本次复现总目标
A. Mode 1 轮询法
-
建立 ADC 轮询读取代码。
-
串口能看到
ADC Value: xxx, Voltage: x.xxV输出。 -
ADC 输入接近满量程时,数值在 4060 左右,电压约
3.27V ~ 3.28V。 -
能说明为什么最高值不一定到理想
3.30V。
B. Mode 2 DMA + 定时处理
-
CubeMX 配置 ADC DMA Circular 模式。
-
串口能看到
Average ADC: xxx, Voltage: x.xxV输出。 -
数值比 Mode 1 更平滑。
-
明确
HAL_ADC_Start_DMA第三个参数应使用元素个数ADC_DMA_BUFFER_SIZE,不能使用sizeof(...)。
C. Mode 3 TIM + DMA + 中断 + DAC 回采
-
CubeMX 配置 TIM3 触发 ADC、TIM6 触发 DAC。
-
ADC DMA 使用 Normal 模式,一批采满后进入完成回调。
-
DAC DMA 使用 Circular 模式,循环输出正弦表。
-
PA4 -> PA5短接后,串口能看到{dac}xxxx格式数据。 -
数据呈正弦波形状。
-
能说明数据不连续的原因是串口打印耗时。
Mode 1:ADC 轮询法
CubeMX 配置

图中对应的是 ADC 单通道基础配置。本次个人工程实际使用:
-
ADC:
ADC1 -
Channel:
ADC_CHANNEL_10 -
Pin:
PC0 -
Resolution:
12 bits -
Data Alignment:
Right alignment -
Continuous Conversion Mode:
Disabled -
External Trigger:
Software Start -
DMA Continuous Requests:
Disabled
注意:原始模板里写过 “如 IN0 / PA0”,本次个人工程不是 PA0,而是 PC0 / ADC_CHANNEL_10。
代码改动
APP/adc_app.c:
#include "adc_app.h"
void adc_task(void)
{
uint32_t adc_val;
float voltage;
HAL_ADC_Start(&hadc1);
if (HAL_ADC_PollForConversion(&hadc1, 1000) == HAL_OK)
{
adc_val = HAL_ADC_GetValue(&hadc1);
voltage = (float)adc_val * 3.3f / 4096.0f;
my_printf(&huart1, "ADC Value: %lu, Voltage: %.2fV\r\n",
adc_val, voltage);
}
HAL_ADC_Stop(&hadc1);
}
APP/adc_app.h:
#ifndef ADC_APP_H
#define ADC_APP_H
#include "mydefine.h"
void adc_task(void);
#endif
APP/mydefine.h 中需要让 adc_app.c 间接拿到 ADC 和串口声明:
#include "adc_app.h"
#include "adc.h"
#include "usart.h"
#include "usart_app.h"
APP/scheduler.c 中挂入 adc_task:
static task_t scheduler_task[] = {
{led_task, 1, 0},
{btn_task, 5, 0},
{uart_task, 5, 0},
{adc_task, 200, 0},
};
这里先使用 200ms 周期,避免串口刷屏太快。
实测输出
串口输出示例:
ADC Value: 4062, Voltage: 3.27V
ADC Value: 4066, Voltage: 3.28V
ADC Value: 4063, Voltage: 3.27V
ADC Value: 4068, Voltage: 3.28V
为什么到不了 3.30V
ADC Value = 4066 时:
4066 / 4096 * 3.3 = 3.275V
所以显示 3.28V 是合理的。
到不了理想 3.30V 的原因:
-
ADC 参考电压是实际 VDDA,不是理论
3.300V。 -
开发板 3.3V 轨可能实际只有
3.27V ~ 3.29V。 -
电位器、杜邦线、面包板和输入脚都有压降或接触误差。
-
12 位 ADC 满量程附近也会有量化误差和偏置误差。
若要贴近真实电压,应使用万用表实测 VDDA 替代代码里的 3.3f。
复现记录
日期:2026-05-14
卡住的步骤:
scheduler.c中加入adc_task后,曾出现identifier "adc_task" is undefined。
查了什么:
- 对照参考工程发现:参考工程通过
scheduler.c -> scheduler.h -> mydefine.h -> adc_app.h间接看到adc_task声明。
怎么跑通的:
-
按个人工程习惯,在
mydefine.h中汇总adc_app.h和adc.h。 -
adc_app.c保持只包含adc_app.h。 -
编译通过后,串口能看到
ADC Value和Voltage。
遗留问题:
- 当前存在
adc_app.h -> mydefine.h -> adc_app.h的循环包含,依靠 include guard 可以编译。短期保留以贴合个人习惯,后续工程复杂后建议整理头文件依赖。
Mode 2:ADC DMA + 定时处理
CubeMX 配置

这张图对应 ADC 从单次轮询切换到连续转换,用于配合 DMA Circular。

这张图对应 ADC DMA 配置。Mode 2 的关键是 DMA 必须是 Circular,这样 ADC 数据会持续搬运到缓冲区。
配置要点:
-
ADC1 → Parameter Settings
-
Continuous Conversion Mode:
Enabled -
DMA Continuous Requests:
Enabled -
External Trigger:
Software Start -
DMA Mode:
Circular -
Peripheral Increment:
Disabled -
Memory Increment:
Enabled -
Peripheral Data Width:
Word -
Memory Data Width:
Word
生成代码后确认 Core/Src/adc.c:
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DMAContinuousRequests = ENABLE;
确认 ADC DMA 初始化:
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_WORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
代码改动
APP/adc_app.c:
#include "adc_app.h"
#define ADC_DMA_BUFFER_SIZE 32
uint32_t adc_dma_buffer[ADC_DMA_BUFFER_SIZE];
void adc_dma_init(void)
{
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buffer,
ADC_DMA_BUFFER_SIZE);
}
void adc_task(void)
{
uint32_t adc_sum = 0;
uint32_t adc_val;
float voltage;
for (uint16_t i = 0; i < ADC_DMA_BUFFER_SIZE; i++)
{
adc_sum += adc_dma_buffer[i];
}
adc_val = adc_sum / ADC_DMA_BUFFER_SIZE;
voltage = (float)adc_val * 3.3f / 4096.0f;
my_printf(&huart1, "Average ADC: %lu, Voltage: %.2fV\r\n",
adc_val, voltage);
}
APP/adc_app.h 增加:
void adc_dma_init(void);
Core/Src/main.c 中,在所有 MX_..._Init() 之后、scheduler_init() 之前调用:
adc_dma_init();
scheduler_init();
关键坑:第三参数不是 sizeof
正确写法:
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buffer, ADC_DMA_BUFFER_SIZE);
错误写法:
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buffer, sizeof(adc_dma_buffer));
原因:HAL_ADC_Start_DMA 的第三参数是“搬运元素个数”,不是字节数。sizeof(adc_dma_buffer) 得到的是字节数,会导致采样长度错误。
实测输出
串口输出形态:
Average ADC: xxxx, Voltage: x.xxV
Average ADC: xxxx, Voltage: x.xxV
相比 Mode 1,输出来自 32 个 DMA 样本的平均值,数值更平滑。
复现记录
日期:2026-05-14
卡住的步骤:
- 本阶段没有明显卡住,配置和代码迁移后编译一次通过。
查了什么:
-
对照参考工程确认 ADC DMA 使用
Circular。 -
确认
HAL_ADC_Start_DMA第三个参数应使用ADC_DMA_BUFFER_SIZE。
怎么跑通的:
-
CubeMX 中打开 ADC 连续转换和 DMA Continuous Requests。
-
ADC DMA 配置成 Circular。
-
main.c中调用adc_dma_init()后再进入调度器。
遗留问题:
- Mode 2 只做平均值验证,还没有加入半传输/全传输回调的双缓冲处理。
Mode 3:TIM + DMA + 中断 + DAC 正弦波回采
整体链路
Mode 3 的完整链路是:
TIM6 -> DAC DMA Circular -> PA4 输出正弦波
PA4 -> PA5 物理短接
TIM3 -> ADC 外部触发扫描 Rank1/Rank2
ADC DMA Normal -> 采满 BUFFER_SIZE 后触发 ConvCpltCallback
adc_task -> 提取 Rank2(PA5) 数据 -> 串口打印 {dac}xxxx
CubeMX 配置截图与说明

这张图对应 DAC 输出通道配置。Mode 3 使用 DAC_OUT1 / PA4 输出正弦波。

这张图对应 DAC Trigger 配置。DAC 由 TIM6 TRGO 触发,而不是软件手动输出。

这张图对应 DAC DMA 配置。DAC DMA 必须是 Circular,这样正弦查找表可以循环输出。

这张图对应 ADC 扫描配置。Mode 3 使用两个 Rank,Rank1 保留外部输入通道,Rank2 使用 PA5 回采 DAC 输出。

这张图对应 ADC 触发和 DMA 相关配置。ADC 由 TIM3 TRGO 触发,DMA 使用 Normal,采满一批后停下来触发完成回调。

这张图对应 TIM3 配置。TIM3 的 TRGO 使用 Update Event,作为 ADC 的外部触发源。

这张图对应 TIM6 配置。TIM6 的 TRGO 使用 Update Event,作为 DAC 更新触发源。
关键配置汇总
ADC1
-
Continuous Conversion Mode:
Disabled -
External Trigger:
Timer 3 Trigger Out event -
External Trigger Edge:
Rising edge -
Scan Conversion Mode:
Enabled -
Number of Conversions:
2 -
Rank 1:
ADC_CHANNEL_10 / PC0 -
Rank 2:
ADC_CHANNEL_5 / PA5 -
DMA Mode:
Normal -
DMA Data Width:Peripheral
Word,MemoryWord -
NVIC:启用 ADC Global Interrupt 和 ADC 对应 DMA Stream Interrupt
TIM3
-
Clock Source:
Internal Clock -
Prescaler:
180 - 1 -
Counter Period:
100 - 1 -
Trigger Output:
Update Event
TIM3 用来触发 ADC 采样。
TIM6
-
Clock Source:
Internal Clock -
Prescaler:
180 - 1 -
Counter Period:
100 - 1 -
Trigger Output:
Update Event
TIM6 用来触发 DAC 输出。
DAC
-
Channel:
DAC_OUT1 / PA4 -
Output Buffer:
Enable -
Trigger:
Timer 6 Trigger Out event -
DMA Direction:
Memory to Peripheral -
DMA Mode:
Circular -
DMA Data Width:Peripheral
Half Word,MemoryHalf Word
代码改动
APP/adc_app.c:
#include "adc_app.h"
#define SINE_SAMPLES 100
#define DAC_MAX_VALUE 4095
#define BUFFER_SIZE 1000
uint16_t SineWave[SINE_SAMPLES];
uint32_t dac_val_buffer[BUFFER_SIZE / 2];
__IO uint32_t adc_val_buffer[BUFFER_SIZE];
__IO uint8_t AdcConvEnd = 0;
void Generate_Sine_Wave(uint16_t *buffer,
uint32_t samples,
uint16_t amplitude,
float phase_shift)
{
float step = 2.0f * 3.14159f / samples;
for (uint32_t i = 0; i < samples; i++)
{
float sine_value = sinf(i * step + phase_shift);
buffer[i] = (uint16_t)((sine_value * amplitude) +
(DAC_MAX_VALUE / 2.0f));
if (buffer[i] > DAC_MAX_VALUE)
{
buffer[i] = DAC_MAX_VALUE;
}
}
}
void dac_sin_init(void)
{
Generate_Sine_Wave(SineWave, SINE_SAMPLES, DAC_MAX_VALUE / 2,
0.0f);
HAL_TIM_Base_Start(&htim6);
HAL_DAC_Start_DMA(&hdac,
DAC_CHANNEL_1,
(uint32_t *)SineWave,
SINE_SAMPLES,
DAC_ALIGN_12B_R);
}
void adc_tim_dma_init(void)
{
HAL_TIM_Base_Start(&htim3);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_val_buffer,
BUFFER_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_adc1, DMA_IT_HT);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc == &hadc1)
{
HAL_ADC_Stop_DMA(hadc);
AdcConvEnd = 1;
}
}
void adc_task(void)
{
if (AdcConvEnd)
{
for (uint16_t i = 0; i < BUFFER_SIZE / 2; i++)
{
dac_val_buffer[i] = adc_val_buffer[i * 2 + 1];
}
for (uint16_t i = 0; i < BUFFER_SIZE / 2; i++)
{
my_printf(&huart1, "{dac}%lu\r\n", dac_val_buffer[i]);
}
memset(dac_val_buffer, 0, sizeof(dac_val_buffer));
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_val_buffer,
BUFFER_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_adc1, DMA_IT_HT);
AdcConvEnd = 0;
}
}
APP/adc_app.h:
#ifndef ADC_APP_H
#define ADC_APP_H
#include "mydefine.h"
void adc_task(void);
void dac_sin_init(void);
void adc_tim_dma_init(void);
#endif
Core/Src/main.c:
dac_sin_init();
adc_tim_dma_init();
scheduler_init();
顺序必须是先 dac_sin_init(),再 adc_tim_dma_init()。先让 PA4 上有 DAC 波形,再启动 ADC 回采。
APP/mydefine.h 需要补充:
#include "dac.h"
#include "tim.h"
#include "math.h"
extern DMA_HandleTypeDef hdma_adc1;
硬件接线
PA4 -> PA5
GND 共地
如果有示波器,先看 PA4,应该能看到正弦波。
预期输出
串口收到:
{dac}2048
{dac}2178
{dac}2300
...
{dac}1800
{dac}1900
实际判断重点不是每个数字完全一致,而是数据整体呈周期性上升下降,即正弦波形状。
本次实际卡点
Mode 3 首次编译时报错:
-
function "sinf" declared implicitly -
identifier "htim6" is undefined -
identifier "hdac" is undefined -
identifier "htim3" is undefined -
identifier "hdma_adc1" is undefined
原因:
本工程习惯在 adc_app.c 里只包含 adc_app.h,再由 mydefine.h 汇总公共头文件。但 mydefine.h 当时还没有包含 Mode 3 需要的 math.h、dac.h、tim.h,也没有声明 hdma_adc1。
解决:
#include "dac.h"
#include "tim.h"
#include "math.h"
extern DMA_HandleTypeDef hdma_adc1;
补完后编译通过,现象正常。
复现记录
日期:2026-05-14
卡住的步骤:
- Mode 3 代码写入后,编译阶段出现
sinf、htim6、hdac、htim3、hdma_adc1未定义。
查了什么:
-
检查
tim.c/tim.h,确认句柄名是htim3和htim6。 -
检查
dac.c/dac.h,确认句柄名是hdac。 -
检查
adc.c,确认hdma_adc1存在,但需要在当前模块可见。
怎么跑通的:
-
在
mydefine.h中补充dac.h、tim.h、math.h。 -
在
mydefine.h中补充extern DMA_HandleTypeDef hdma_adc1;。 -
编译成功后,
PA4 -> PA5短接,串口输出{dac}xxxx数据,数据形状正确。
遗留问题:
- 当前 Mode 3 主要验证链路跑通,尚未展开频率精算、波形参数控制、串口命令切换波形。
常见坑
| 现象 | 可能原因 |
|---|---|
adc_task 未定义 |
scheduler.c 看不到 adc_app.h 中的函数声明 |
| ADC 值一直是 0 | GND 没共地、通道选错、输入脚没接到实际信号 |
| Mode 1 满量程只有 3.27V 左右 | 实际 VDDA 不等于理论 3.300V,属于正常偏差 |
| DMA 模式数据固定不变 | DMA Mode 没选 Circular,或 ADC 没设连续转换 |
HAL_ADC_Start_DMA 长度异常 |
第三个参数误写成 sizeof(buffer) |
Mode 3 AdcConvEnd 一直不置 1 |
DMA 中断没启用,或 ADC DMA 没设 Normal 模式 |
HAL_ADC_ConvCpltCallback 没进 |
ADC Global Interrupt 或 DMA Channel Interrupt 未开 |
| DAC 输出没有波形 | TIM6 没启动,DAC Trigger 没选 TIM6 TRGO,或 DAC DMA 没启动 |
Mode 3 串口没有 {dac} 数据 |
PA4 没短接 PA5、ADC Rank2 没选 PA5、DMA 完成回调没触发 |
| 串口数据乱码 | 串口助手波特率和工程不一致;参考工程是 460800,本次 adda_test 是 115200 |
| 打印浮点后异常 | 浮点 printf 耗栈或库配置不合适,可改成整数打印 |
跑通后的最小验证清单
-
Mode 1 串口能看到
ADC Value: xxx, Voltage: x.xxV。 -
Mode 1 ADC 输入接近满量程时,电压显示约
3.27V ~ 3.28V,与实际 VDDA 偏差一致。 -
Mode 2 串口能看到
Average ADC: xxx, Voltage: x.xxV。 -
Mode 2 DMA Circular 平均值输出正常。
-
Mode 3 串口能看到
{dac}xxxx数据。 -
Mode 3 数据呈正弦波形状。
-
PA4 -> PA5回采链路验证通过。 -
Mode 3 编译报错已定位为头文件/句柄声明可见性问题,并已解决。
后续复盘点
-
当前
adc_app.h -> mydefine.h -> adc_app.h存在循环包含,但依靠 include guard 可以编译通过。短期保留以贴合个人工程习惯,后续 APP 模块变多后建议重构头文件依赖。 -
Mode 1/2 电压计算使用
3.3f / 4096.0f,适合教学验证;若要贴近实际电压,应使用万用表实测 VDDA。 -
Mode 3 目前验证的是链路跑通,下一步可以继续做频率计算、波形参数控制、串口命令控制和 OLED/上位机显示。
-
后续如果要把三种模式长期保留在一个工程里,可以恢复参考工程的
#define ADC_MODE (1/2/3)切换结构,避免频繁手动替换adc_app.c。
后续优化方向
串口输出优化
参考工程使用 460800 波特率,本次个人工程使用 115200。两者都能跑通,但 Mode 3 一次会打印约 500 行 {dac}xxxx 数据,115200 会明显阻塞更久。
优化建议:
-
Mode 1/2 保持
115200即可,便于常规调试。 -
Mode 3 若需要连续观察波形数据,建议把工程和串口助手都改成
460800。 -
Mode 3 不一定每批打印 500 点,可以每隔 2 点、5 点打印一次,或只打印前 200 点。
-
如果后续要上位机画波形,可以考虑输出更短格式或二进制数据,而不是长文本
{dac}xxxx\r\n。
当前瓶颈不是 ADC/DAC 本身,而是 my_printf -> HAL_UART_Transmit 的阻塞式串口发送。
调度周期优化
参考工程 Mode 3 使用:
{adc_task, 10, 0}
这是合理的,因为 Mode 3 的 adc_task() 大多数时候只是检查 AdcConvEnd 标志位:
if (AdcConvEnd)
{
// 只在 DMA 采满一批后处理和打印
}
Mode 1/2 不适合一开始就用 10ms 的原因:
-
Mode 1 每次任务执行都会轮询 ADC 并打印。
-
Mode 2 每次任务执行都会计算平均值并打印。
-
如果 10ms 打印一次,就是每秒 100 行,调试时刷屏严重,且串口阻塞时间增加。
建议:
-
Mode 1:
100ms ~ 200ms -
Mode 2:
100ms ~ 200ms -
Mode 3:可以使用参考工程的
10ms,因为它主要是轮询完成标志
注意:Mode 3 一旦开始打印 500 行,仍然会被串口阻塞拖慢,尤其在 115200 波特率下更明显。
模式切换结构优化
当前个人工程是按阶段替换 adc_app.c 来复现 Mode 1/2/3。这适合学习过程,但不适合长期保留。
建议后续恢复参考工程风格:
// 1 轮询
// 2 DMA连续转换
// 3 TIM + DMA + 中断 + DAC回采
#define ADC_MODE (3)
然后用条件编译保留三套代码:
#if ADC_MODE == 1
// Mode 1 code
#elif ADC_MODE == 2
// Mode 2 code
#elif ADC_MODE == 3
// Mode 3 code
#endif
好处:
-
方便复现和对比三种模式。
-
避免频繁覆盖
adc_app.c。 -
后续写竞赛模板时,可以保留多个实现路径作为参考。
头文件依赖优化
当前个人工程为了贴合自己的习惯,让 adc_app.c 只包含:
#include "adc_app.h"
然后通过 adc_app.h -> mydefine.h 间接拿到 hadc1、huart1、hdac、htim3、htim6 等声明。
这能编译,但存在一个结构问题:
adc_app.h -> mydefine.h -> adc_app.h
这是循环包含,短期依靠 include guard 可以工作,长期不够干净。
后续更推荐的结构:
-
.h文件只声明本模块对外函数和必要类型。 -
.c文件直接包含自己实际用到的外设头文件。 -
mydefine.h只保留真正公共的 APP 头、extern 声明和通用库头。
例如更清晰的 adc_app.c 头部可以是:
#include "adc_app.h"
#include "adc.h"
#include "dac.h"
#include "tim.h"
#include "usart.h"
#include "usart_app.h"
#include "math.h"
#include "string.h"
当前阶段可以先不改,以免打断学习节奏;等 ADDA 复现稳定后再整理。
全局变量作用域优化
Mode 3 中很多变量只在 adc_app.c 内部使用:
uint16_t SineWave[SINE_SAMPLES];
uint32_t dac_val_buffer[BUFFER_SIZE / 2];
__IO uint32_t adc_val_buffer[BUFFER_SIZE];
__IO uint8_t AdcConvEnd = 0;
后续可以改成 static,减少跨文件命名污染:
static uint16_t SineWave[SINE_SAMPLES];
static uint32_t dac_val_buffer[BUFFER_SIZE / 2];
static __IO uint32_t adc_val_buffer[BUFFER_SIZE];
static __IO uint8_t AdcConvEnd = 0;
Generate_Sine_Wave 也只在 adc_app.c 内使用,可以改成:
static void Generate_Sine_Wave(...)
浮点打印优化
Mode 1/2 当前使用:
float voltage;
my_printf(&huart1, "Voltage: %.2fV\r\n", voltage);
浮点格式化对嵌入式环境开销较大,有些库配置下还可能带来栈或链接问题。
后续可以改成整数毫伏:
uint32_t mv = adc_val * 3300 / 4096;
my_printf(&huart1, "ADC Value: %lu, Voltage: %lu.%03luV\r\n",
adc_val, mv / 1000, mv % 1000);
这样能避免浮点 printf,同时输出仍然直观。
后续功能扩展
当三种基础模式稳定后,可以继续扩展:
-
串口命令控制采样开始/停止。
-
串口命令切换波形类型:正弦波、方波、三角波。
-
串口命令设置频率和幅值。
-
根据 ADC 回采数据计算最大值、最小值、峰峰值。
-
基于回采数据估算频率。
-
将
{dac}数据接入上位机或 OLED 显示。
这些扩展不属于本次三例程复现的必要条件,但它们更接近后续竞赛题的工程形态。