一 - 项目介绍
2024-2025年寒假我参加了电子森林寒假天天练项目,本次项目有多项任务可以选择,我选择了基于STM32的简易示波器/频谱仪/信号发生器学习平台活动中的音频模拟电路放大特性分析项目 音频模拟放大电路特性分析 ,该项目要求
- 通过芯片的PWM + 板上LPF电路生成频率在DC到20KHz的扫频信号(图2)
- 将该信号通过Test端口连接到测试电路的输入端(图3中的JP1将1和2短接,2和3断开)
- STM32G031采集运算放大器U4(LMV358)第7脚输出的信号,对其进行量化处理
- 在OLED上绘制该电路的幅频、相频特性图
经过本次项目本人成功实现,并实现了手动调整频率测试幅值和相位的功能。
二 - 使用到的硬件与软件介绍
硬件介绍来源于https://www.eetree.cn/platform/662
本次项目使用的开发板为基于STM32G031的口袋仪器训练平台,采用128*128 OLED显示,2个通道的模拟输入 + 一个通道的Micphone语音输入,并有一路信号输出。
开发板具有以下硬件:
- 基于STM32G031微控制器, Arm Cortex M0+内核,主频为64MHz
- 2个按键 + 1个光电旋转编码器用于控制输入
- 1个SPI接口的OLED显示屏(128*128分辨率)
- 1路音频放大电路用于产生ADC的测试信号,并可作为测试电路使用
- 一个蜂鸣器用于音效输出
- 1路基于PWM的DDS信号输出,用于产生测试信号(任意波形)
- 2路增益可调的模拟信号输入,通过12bits ADC采集2mVpp - 30Vpp,带宽为100KHz的模拟信号
这是该训练平台的电路图:
想要了解更详细的信息,请访问上面提供的说明链接。
软件方面使用了stm32cubeide+programer进行编译调试和烧录。
三 - 方案框图和项目设计思路介绍
方案框图:
设计思路介绍:
生成扫频信号:
通过芯片的 PWM + 板上 LPF 电路生成频率在 DC 到 20KHz 的扫频信号。先生成频率表,再根据频率表计算定时器分频系数和重装值,实现不同频率输出。
采集输出:
STM32G031 采集运算放大器 U4(LMV358)第 7 脚输出的信号。将信号保存在数组中等待下一步量化处理。
量化处理:
对采集到的信号进行量化处理,若输出模式为幅值,则直接输出,若为相位,则经过计算之后再输出。
绘制图形:
根据按键的情况切换索引,实现幅频相频,自动手动的切换。
四 - 软件流程图和关键代码介绍
软件流程图:
关键代码介绍:
- 频率表生成:在
Core/Src/Feq.C
文件中,GenerateFreqTable
函数用于生成频率表,通过线性分布的方式为每个索引分配一个目标频率,范围从 0 到 20000Hz。
// 线性分布示例
void GenerateFreqTable(void)
{
const uint32_t step = 20000 / FREQ_TABLE_SIZE;
for(uint8_t i=0; i<FREQ_TABLE_SIZE; i++)
{
freq_table[i].target_freq = (i+1)*step;
}
}
- 定时器参数计算:
calculate_TIM_params
函数根据目标频率计算定时器的分频系数psc
和重装值arr
,以实现不同频率的 PWM 输出。
// 计算 PSC 和 ARR
void calculate_TIM_params(uint32_t freq, FreqConfig *params)
{
if (freq == 0)
{
params->psc = 0;
params->arr = 0;
return;
}
uint32_t total_div = CLOCK_FREQ / freq;
if (total_div <= 65535)
{
params->psc = 0;
params->arr = total_div - 1;
}
else
{
// 尝试分解总分频系数
for (uint16_t psc = 1; psc < 65535; psc++)
{
uint32_t arr = total_div / psc;
if (arr <= 65535 && arr * psc == total_div)
{
params->psc = psc - 1;
params->arr = arr - 1;
return;
}
}
// 如果无法分解,使用最大 PSC 和最小 ARR
params->psc = 65535 - 1;
params->arr = (total_div / 65535) - 1;
}
}
- PWM 输出配置:
TIM3_QuickConfig
函数根据频率索引快速配置定时器 TIM3 的参数,实现不同频率的 PWM 输出。
// 快速配置TIM3
void TIM3_QuickConfig(uint8_t freq_index)
{
if(freq_index >= FREQ_TABLE_SIZE) return;
TIM3->ARR=freq_table[freq_index].arr;
TIM3->PSC=freq_table[freq_index].psc;
TIM3->CCR3=freq_table[freq_index].arr/2;
}
- 量化处理:在
Core/Src/main.c
文件的主循环中,根据Disp_Id
的值对采集到的信号进行不同的量化处理。
if(Disp_Id==0)
{
Alt=(float)(Map(uhADC[0], 0, 4096, 0, 63));
Pos_y[0][Pos]=(uint8_t)Alt;
}
else if(Disp_Id==1)
{
Pos_y[0][Pos]=127-(uint8_t)(phase);
}
- 当
Disp_Id
为 0 时,使用Map
函数将 ADC 采集值从范围[0, 4096]
映射到范围[0, 63]
。 - 当
Disp_Id
为 1 时,使用phase
计算量化值。 phase
是通过定时器TIM2
的计数值计算得到的相位值,具体计算逻辑在Core/Src/stm32g0xx_it.c
文件的HAL_GPIO_EXTI_Falling_Callback
函数中:
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
// ...
if(GPIO_Pin==Pwn_Plus1_Pin)
{
if(tim==0)
{
CNT[0]=TIM2->CNT;
tim=1;
}
else
{
CNT[1]=TIM2->CNT;
tim=0;
if(CNT[1]>CNT[0])
{
Perd=64000000/(CNT[1]-CNT[0]);
float a1=CNT[2]-CNT[0];
float a2=CNT[1]-CNT[0];
phase=360*a1/a2;
}
}
}
// ...
}
五 - 实物演示
展示相频
随频率变大,相位差也变大
展示幅频
(后面的4791是之前相频演示的led残留)
展示手动
可以通过手动调节测量具体频率信号的特性
六 - 遗憾和不足
由于led板和内存限制,测量扫频时采取了离散的存储数组的方案,先将数值存在数组中,再显示到led上,导致测量幅频时离散状态严重。
七 - 对本次活动的心得体会
首先非常感谢硬禾学堂提供这样一次机会让我学习stm32开发板的有关知识,提供了包括硬件,软件,直播课堂,答疑社群方方面面的众多支持。其次在本次开发过程中,我深刻认识到了单片机的有趣好玩之处,明白了其强大功能,对单片机的特点包括编程学到了许多,最后希望我能在以后的学习中,首先将本次项目继续完善做好,其次是继续深入学习单片机的相关知识,在条件允许的情况下,希望能更多地参加硬禾组织的活动,提升自己!