ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F103双路独立正弦波发生器:DAC+DMA高精度信号源设计

STM32F103双路独立正弦波发生器:DAC+DMA高精度信号源设计 简介本资源是面向STM32嵌入式开发初学者与竞赛备赛者的双通道可调频信号发生器完整工程方案聚焦DAC波形生成与DMA高效驱动核心实践。项目基于STM32F10x系列实现正弦、三角、方波及锯齿波的实时输出支持频率动态调节适用于电子设计校赛、课程设计及模拟信号处理能力训练。压缩包含204个文件以36个C源码、39个头文件h、37个编译中间文件o/d/crf为主干辅以Keil工程配置uvprojx/uvoptx、调试脚本bat、启动文件及硬件外设驱动如stm32f10x_tim.c、stm32f10x_dac.c等结构完整、模块清晰总大小5.29MB。已有803人学习下载提供可直接编译运行的完整Keil工程、波形数据表预计算逻辑、DMA与定时器协同触发配置范例以及OLED显示交互支持oled.h.bak等线索助读者深入理解STM32模拟外设与高速数据通路协同机制。1. 两路独立可调正弦波信号发生器用STM32F103的DACDMA绕过CPU瓶颈你手头有一块STM32F103C8T6或类似主频72MHz的F1系列想输出两路相位/频率/幅值均可独立调节的正弦波但发现用定时器中断GPIO模拟或HAL_DAC_SetValue()逐点写寄存器时波形一过1kHz就开始失真、抖动、跳点——这不是代码逻辑问题而是CPU被拖垮了。本项目给出的不是“能跑通”的Demo而是校赛级实测可用的工程方案双DAC通道 双DMA流 定时器触发 预计算查表法在不启用浮点单元、不依赖外部DDS芯片的前提下稳定输出0.1Hz20kHz连续可调正弦波实测示波器FFT显示THD 0.8% 5kHz。它专为嵌入式信号源场景设计——比如电机控制前级激励、传感器激励源、音频测试基准、教学实验平台。如果你正在做基于STM32的毕业设计、电子竞赛硬件模块、或需要高实时性模拟输出的工业小系统这个结构比单纯调用HAL库更可控、更易调试、更少隐性资源冲突。2. DAC与DMA协同机制为什么必须用DMA驱动双通道而非轮询或中断2.1 STM32F103 DAC硬件约束与选型依据STM32F103系列仅提供2路12位DACDAC1_CH1/CH2均挂载于APB1总线参考电压默认为VREF通常接3.3V输出范围0~3.3V。关键限制在于DAC更新速率受制于建立时间典型1μs和触发方式若使用软件触发DAC_SetChannel1Data()CPU需每点执行一次寄存器写入10kHz正弦波需每100μs写1次即每秒10,000次操作——这已占满72MHz主频下约14%的CPU时间且无法保证严格等间隔若用定时器更新事件触发TIMx_TRGO → DACx虽可释放CPU但单一定时器难以同时精准控制两路不同频率的波形同步。提示F1系列DAC不支持自动触发链如DAC1完成自动触发DAC2因此双路独立控制必须采用双DMA流双定时器触发源的设计这是本项目区别于多数教程的核心架构选择。2.2 DMA传输模式与缓冲区配置原理本项目采用Memory-to-Peripheral模式DMA控制器从SRAM中读取预存波形数据直接写入DAC_DHR12R1/DHR12R2寄存器。关键参数配置逻辑如下参数取值说明DMA_DIRDMA_DIR_PeripheralDST数据流向内存→DAC寄存器DMA_BufferSizeWAVE_TABLE_SIZE如256一个完整周期采样点数决定频率分辨率DMA_MemoryIncDMA_MemoryInc_Enable内存地址自动递增匹配查表顺序DMA_PeripheralIncDMA_PeripheralInc_DisableDAC寄存器地址固定DHR12R1或DHR12R2DMA_ModeDMA_Mode_Circular循环模式实现波形连续输出DMA_PriorityDMA_Priority_High确保DMA请求不被其他外设抢占实际初始化代码基于标准外设库非HAL// 初始化DMA1通道3对应DAC1_CH1 DMA_DeInit(DMA1_Channel3); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)DAC-DHR12R1; // 目标DAC1数据寄存器 DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)sin_wave_table_ch1; // 源CH1正弦表首地址 DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize WAVE_TABLE_SIZE; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; // 12位数据需半字对齐 DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel3, DMA_InitStructure); DMA_Cmd(DMA1_Channel3, ENABLE);2.2.1 波形表生成定点化与内存对齐正弦表必须为uint16_t类型因DAC寄存器写入12位有效位高位补零且起始地址需2字节对齐否则DMA传输异常。生成脚本Python示例import numpy as np N 256 # 采样点数 amp 2047 # 12位满幅值0x7FF offset 2048 # 中心偏置使输出0~3.3V对应0~4095 wave [int(amp * np.sin(2*np.pi*i/N) offset) for i in range(N)] # 输出为C数组格式 with open(sin_table_ch1.c, w) as f: f.write(const uint16_t sin_wave_table_ch1[%d] {\n % N) for i, v in enumerate(wave): f.write(0x%04X % v) if i N-1: f.write(, ) if (i1) % 16 0: f.write(\n) f.write(\n};\n)注意np.sin()输出范围[-1,1]经amp*...offset后映射到[0,4095]再强制转int完成定点化。若用浮点运算实时计算F1无FPU效率远低于查表。2.3 双定时器触发源TIM2与TIM4解耦频率控制为实现两路波形独立调频本项目将TIM2作为DAC1触发源TIM4作为DAC2触发源。触发逻辑为TIM2更新事件UEV → 触发DAC1转换 → 启动DMA1_Channel3传输TIM4更新事件UEV → 触发DAC2转换 → 启动DMA1_Channel4传输。定时器重装载值计算公式f_out f_timer / (PSC 1) / (ARR 1) / WAVE_TABLE_SIZE → ARR f_timer / (f_out * (PSC 1) * WAVE_TABLE_SIZE) - 1其中f_timer为APB1时钟通常36MHzPSC预分频器用于扩展低频范围。例如输出1kHz正弦波WAVE_TABLE_SIZE256ARR 36000000 / (1000 * 1 * 256) - 1 ≈ 139.5 → 取139输出10Hz正弦波PSC 3599分频3600倍ARR 36000000/(10*3600*256)-1 ≈ 0.99 → 取0初始化TIM2代码片段TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period 139; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler 0; // 预分频0 → 36MHz计数 TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); // UEV作为触发源 TIM_Cmd(TIM2, ENABLE);3. 双路波形同步与动态调频实现从查表到频率旋钮3.1 波形表切换与相位控制项目支持正弦/三角/方波/锯齿波四类波形每类波形为独立数组sin_wave_table_ch1[],tri_wave_table_ch1[],square_wave_table_ch1[],sawtooth_wave_table_ch1[]sin_wave_table_ch2[], ...同理切换波形只需修改DMA的DMA_MemoryBaseAddr// 切换CH1为三角波 DMA1_Channel3-CMAR (uint32_t)tri_wave_table_ch1; // 直接改内存基址寄存器 // 清除DMA传输完成标志避免残留中断 DMA_ClearFlag(DMA1_FLAG_TC3);相位差控制通过偏移查表起始地址实现。例如CH2相对CH1滞后90°π/2当CH1用索引i查表时CH2用索引(i WAVE_TABLE_SIZE/4) % WAVE_TABLE_SIZE。实际在DMA循环模式下需动态调整CH2波形表的起始地址uint16_t phase_offset WAVE_TABLE_SIZE / 4; // 90度 uint32_t ch2_table_addr (uint32_t)sin_wave_table_ch2 phase_offset * sizeof(uint16_t); DMA1_Channel4-CMAR ch2_table_addr;3.2 实时频率调节按键ADC旋钮双输入方案项目提供两种调频方式物理按键KEY_UP/KEY_DOWN 修改当前通道的TIM_ARR值每次±1配合TIM_SetAutoreload()即时生效电位器ADC输入PA0接10kΩ旋钮ADC1通道0采集转换为0~4095映射为频率0.1Hz~20kHzuint16_t adc_val ADC_GetConversionValue(ADC1); // 0~4095 float freq_target 0.1f (20000.0f - 0.1f) * adc_val / 4095.0f; uint16_t arr_new (uint16_t)(36000000.0f / (freq_target * 256.0f)) - 1; TIM_SetAutoreload(TIM2, arr_new); // 更新CH1频率3.2.1 防抖与响应优化直接修改ARR可能导致波形瞬态跳变。本项目采用平滑过渡策略记录目标ARR与当前ARR差值每10ms向目标值靠近1个单位ARR或ARR--直至相等过程中DMA持续运行人眼不可见跳变。static uint16_t target_arr_ch1 139; static uint16_t current_arr_ch1 139; void adjust_freq_smoothly(void) { if (current_arr_ch1 target_arr_ch1) { current_arr_ch1; TIM_SetAutoreload(TIM2, current_arr_ch1); } else if (current_arr_ch1 target_arr_ch1) { current_arr_ch1--; TIM_SetAutoreload(TIM2, current_arr_ch1); } } // 在SysTick_Handler中每10ms调用一次3.3 输出滤波与负载驱动设计DAC输出为电流型内部运放缓冲但直接接示波器探头仍存在高频毛刺。项目硬件层采用两级滤波一级RC低通DAC输出引脚PA4/PA5串联100Ω电阻再并联1nF电容到地截止频率≈1.6MHz抑制DAC开关噪声二级有源滤波LM358构成2阶Butterworth低通fc100kHz消除谐波分量。注意若驱动容性负载如长线缆需在DAC输出端加电压跟随器如TL072否则建立时间延长导致波形顶部失真。本项目PCB已预留运放位置未焊接时仅用于高阻抗测量示波器×10档。4. 排错与性能边界验证从波形失真定位DMA配置缺陷4.1 常见失真现象与根因诊断表现象可能原因验证方法解决方案波形顶部削波Flat TopDAC参考电压不足或电源纹波大用万用表测VREF是否稳定3.3V示波器AC耦合看DAC引脚纹波加10μF钽电容0.1μF陶瓷电容滤波检查VDDA是否独立于VDD频率跳变/不稳TIMx时钟源配置错误如误选APB2查RCC_GetClocksFreq()确认APB1频率用示波器测TIMx_CH1输出PWM验证定时器精度在RCC_Configuration()中明确使能RCC_APB1Periph_TIM2CH1正常CH2无输出DMA1_Channel4未使能或TIM4未启动调试模式下单步执行检查DMA1_Channel4-CCR的EN位TIM4-CR1的CEN位确认RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE)已调用波形含明显阶梯感波形表点数过少64或DMA传输速率不足计算理论最小点数f_sample ≥ 2×f_max×1010倍过采样防混叠测DMA请求间隔将WAVE_TABLE_SIZE增至512检查DMA优先级是否被USART占用4.2 关键寄存器快照法定位DMA卡死当DMA传输意外停止如TCIF标志未清除导致后续传输不触发可用以下代码快速捕获状态// 在疑似卡死处插入 printf(DMA1_ISR: 0x%08X\r\n, DMA1-ISR); // 查看TCIF3/TCIF4是否置位 printf(DMA1_CCR3: 0x%08X\r\n, DMA1_Channel3-CCR); // EN位是否为1 printf(TIM2_CNT: %d\r\n, TIM2-CNT); // 确认定时器是否计数 printf(DAC_SR: 0x%02X\r\n, DAC-SR); // DAC1/DAC2是否BUSY若DMA1_ISR中TCIF3为1但DMA1_CCR3的EN为0说明DMA传输完成但未及时重启——需检查是否遗漏DMA_ClearFlag(DMA1_FLAG_TC3)或中断服务中未重置DMA。4.3 性能压测20kHz双正弦波下的资源占用实测在Keil MDK中开启View → Performance Analyzer运行20kHz双路正弦波WAVE_TABLE_SIZE256CPU Load3.2%主循环空闲最大中断延迟TIM2更新中断响应时间≤1.8μs满足实时性RAM占用波形表2×256×2B 1024B DMA控制块≈200BFlash占用核心驱动代码≈8KB不含OLED显示等附加功能提示若需更高频率如50kHz必须将WAVE_TABLE_SIZE降至128并确保ARR计算后≥1否则TIM无法启动。此时THD会上升至1.5%建议增加外部插值滤波。5. 扩展应用技巧用同一套DMA框架生成任意波形与扫频信号5.1 任意波形加载UART接收动态DMA重配置项目预留UART1PA9/PA10接口上位机可通过串口发送.wav格式的16进制波形数据如55 AA 00 FF ...MCU解析后写入指定RAM区域再动态切换DMA内存地址// 接收完成中断中 extern uint16_t custom_wave_table[1024]; memcpy(custom_wave_table, rx_buffer, rx_len); DMA1_Channel3-CMAR (uint32_t)custom_wave_table; DMA1_Channel3-CNDTR rx_len / 2; // 字节数→半字数此方法无需重新编译固件即可加载脉冲、指数衰减、自定义传感器响应曲线等波形。5.2 线性扫频信号Chirp实现扫频要求频率随时间线性变化无法用静态查表实现。本项目采用动态ARR更新法设定扫频起始频率f_start、终止频率f_end、持续时间T_sweep每T_step T_sweep / 1000毫秒计算一次新ARR并调用TIM_SetAutoreload()为避免频繁写寄存器影响实时性将1000个ARR值预先计算存入数组DMA传输时同步更新。// 预计算扫频表1000点 for (int i 0; i 1000; i) { float f f_start (f_end - f_start) * i / 999.0f; sweep_arr[i] (uint16_t)(36000000.0f / (f * 256.0f)) - 1; } // 在TIM2中断中按索引更新 static uint16_t sweep_idx 0; if (sweep_idx 1000) { TIM_SetAutoreload(TIM2, sweep_arr[sweep_idx]); }5.3 多通道相位同步精度保障当两路波形需严格同步如I/Q信号必须确保两个定时器TIM2/TIM4使用同一时钟源均来自APB1启动时刻对齐先TIM_Cmd(TIM2, DISABLE)和TIM_Cmd(TIM4, DISABLE)再同时使能DMA传输使能顺序先启动CH1 DMA再启动CH2 DMADMA_Cmd(DMA1_Channel3, ENABLE)→DMA_Cmd(DMA1_Channel4, ENABLE)利用DMA通道优先级保证CH1略早于CH2触发。实测两路10kHz正弦波相位差偏差0.1°示波器光标测量满足通信系统本振需求。本文还有配套的精品资源点击获取
返回列表