7 ADDA 三例程复现记录

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 配置

Mode 1 ADC基础配置

图中对应的是 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.hadc.h

  • adc_app.c 保持只包含 adc_app.h

  • 编译通过后,串口能看到 ADC ValueVoltage

遗留问题

  • 当前存在 adc_app.h -> mydefine.h -> adc_app.h 的循环包含,依靠 include guard 可以编译。短期保留以贴合个人习惯,后续工程复杂后建议整理头文件依赖。

Mode 2:ADC DMA + 定时处理

CubeMX 配置

Mode 2 ADC连续转换配置

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

Mode 2 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 配置截图与说明

Mode 3 DAC输出通道配置

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

Mode 3 DAC触发源配置

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

Mode 3 DAC DMA Circular配置

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

Mode 3 ADC双通道扫描配置

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

Mode 3 ADC触发与DMA配置

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

Mode 3 TIM3触发ADC配置

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

Mode 3 TIM6触发DAC配置

这张图对应 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,Memory Word

  • 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,Memory Half 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.hdac.htim.h,也没有声明 hdma_adc1

解决:

 #include "dac.h"
 #include "tim.h"
 #include "math.h"
 ​
 extern DMA_HandleTypeDef hdma_adc1;

补完后编译通过,现象正常。

复现记录

日期:2026-05-14

卡住的步骤

  • Mode 3 代码写入后,编译阶段出现 sinfhtim6hdachtim3hdma_adc1 未定义。

查了什么

  • 检查 tim.c/tim.h,确认句柄名是 htim3htim6

  • 检查 dac.c/dac.h,确认句柄名是 hdac

  • 检查 adc.c,确认 hdma_adc1 存在,但需要在当前模块可见。

怎么跑通的

  • mydefine.h 中补充 dac.htim.hmath.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 间接拿到 hadc1huart1hdachtim3htim6 等声明。

这能编译,但存在一个结构问题:

 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 显示。

这些扩展不属于本次三例程复现的必要条件,但它们更接近后续竞赛题的工程形态。