一.项目需求:
-
通过芯片的PWM + 板上LPF电路生成常用到的正弦波、三角波、方波
-
频率为DC - 100KHz,并可以通过旋转编码器、按键进行调节
-
信号的幅度从0-3V,并可以通过旋转编码器、按键进行调节,调节精度1%
-
在OLED显示屏设计信号发生器的界面,包含基本的波形区域、参数设置区域
二.成品展示:
下图为设计完成后的项目实拍图:
波形参数设置界面:
通过示波器观察产生的波形:
三.思路及框架构建:
按照题目要求,需要用到的板卡模块主要为PWM+板上低通电路,其余外设为旋转编码器和按键以及OLED屏幕,利用波形生成模块产生信号后,通过旋转编码器和按键的操作进行参数设置和改变并显示到OLED屏幕上即可。其电路结构如下(PWM+板上低通):
思路确定以后,按照模块先分别完成各部分的功能,然后综合起来即可完成项目的要求。
主要有以下几个方面:
(1).PWM+LPF 波形的生成;
(2).按键和旋转编码器的设置;
(3).OLED屏幕的显示;
2.1根据平台的原理图,进行管脚的选择和设置;
本次项目需要用到的管脚不多,主要为两个按键输入,旋转编码器的两路输入,以及OLED屏幕的四路输出控制,一路PWM输出。通过STM32CUBEIDE的配置图如下所示:
其中PA4管脚控制幅度、频率、波形的上调,PA5管脚控制幅度、频率、波形的下调。旋转编码器两相A相由PA15控制,B相由PB4控制,负责参数的调整。OLED屏幕使用的是SPI协议与stm32进行通信,四根管脚分别为PB5,PB6,PB7,PB8。PWM输出信号管脚为PB0。
确定管脚后就要进行管脚的设置,这里把两个按键输入设置为外部中断,按下按键时进行相应的响应对参数进行调整。然后是旋转编码器的两相设置,根据相关知识,在这里把A相作为参考并设置为外部中断,当检测到下降沿时,判断B相的电平,高和低分别对应两个方向的选择。对于OLED屏幕,设置SPI通信方式以后,就可以进行相应的显示了。最后是PWM输出,需要设置为PWM输出模式,同时使能DMA通道,将存储好的波形数据通过DMA对PWM的占空比进行相应的调整,通过低通网络后从而实现波形的生成。
四.代码实现:
本次平台的使用采用的是STM32CUBEIDE和相应的程序下载软件,代码通过图形化设置后自行生成底层代码,我们只需要编写核心部分的代码即可。
3.1 PWM+LPF波形输出
通过使能DMA通道,并载入不同的占空比的值,从而改变输出电压的幅值,以下代码为波形的参数设置部分:
static void setparameters(void){
uint16_t i;
tim = 64000000 / LEN / frequency;
TIM3->CNT = 0;
__HAL_TIM_SET_AUTORELOAD(&htim3, (tim-1));
switch(WAVESTATE){
case 0:
for (i = 0; i < LEN; i++){
send_Buf[i]=(uint16_t)((50-50*amplitude/165) * (tim-1) / 50);
}
break;
case 1:
for (i = 0; i < LEN; i++){
send_Buf[i]=(uint16_t)((sine_wave_value[i]-50) * (tim -1) / 100 * (amplitude) / 165 + tim /2);
}
break;
case 2:
for (i = 0; i < LEN; i++){
send_Buf[i]=(uint16_t)((50 + 50*(amplitude)/165 * (i<LEN/2?1:-1)) * (tim-1) / 100);
}
break;
case 3:
for (i = 0; i < LEN; i++){
send_Buf[i]=(uint16_t)((triangle_wave_value[i]-50) * tim / 100 * (amplitude) / 165 + tim /2);
}
break;
default:
break;
}
}
主函数里通过判断输出的状态来选择是否使能波形输出:
while (1)
{
/* USER CODE END WHILE */
if(PUTSTATE==1){
startoutput();
}
else{
stopoutput();
}
/* USER CODE BEGIN 3 */
}
void startoutput(void){
HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_3,(uint32_t*)send_Buf,LEN);
}
void stopoutput(void){
HAL_TIM_PWM_Stop_DMA(&htim3, TIM_CHANNEL_3);
}
3.2 按键及旋转编码器的中断选择
根据不同的按键按下,以及旋转编码器的状态,要进行对应的幅值、频率、波形的改变,以下为中断函数代码:
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == Astate_Pin)
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)==0){
NUM=NUM+1;
}
else{
NUM=NUM-1;
}
switch(NUM%4){
case 1:
OLED_ShowString(0, 100, "Amplitude:", 8, 1);
OLED_ShowString(0, 120, "Signal Output:", 8, 1);
OLED_ShowString(0, 90, "Signal Wave:", 8, 0);
OLED_Refresh();
BUTTONSTATE=1;
break;
case 2:
OLED_ShowString(0, 90, "Signal Wave:", 8, 1);
OLED_ShowString(0, 110, "Frequency:", 8, 1);
OLED_ShowString(0, 100, "Amplitude:", 8, 0);
OLED_Refresh();
BUTTONSTATE=2;
break;
case 3:
OLED_ShowString(0, 100, "Amplitude:", 8, 1);
OLED_ShowString(0, 120, "Signal Output:", 8, 1);
OLED_ShowString(0, 110, "Frequency:", 8, 0);
OLED_Refresh();
BUTTONSTATE=3;
break;
case 0:
OLED_ShowString(0, 110, "Frequency:", 8, 1);
OLED_ShowString(0, 90, "Signal Wave:", 8, 1);
OLED_ShowString(0, 120, "Signal Output:", 8, 0);
OLED_Refresh();
BUTTONSTATE=0;
break;
}
}
if(GPIO_Pin == KeyUp_Pin){
switch(BUTTONSTATE){
case 1:
if(WAVESTATE>=3){
WAVESTATE=0;
}
else{
WAVESTATE++;
}
switch(WAVESTATE){
case 0:
OLED_ShowString(78, 90, "DC ", 8, 1);
OLED_ShowPicture(0, 20, 128, 64, dc, 1);
OLED_Refresh();
setparameters();
break;
case 1:
OLED_ShowString(78, 90, "SINE", 8, 1);
OLED_ShowPicture(0, 20, 128, 64, sin, 1);
OLED_Refresh();
setparameters();
break;
case 2:
OLED_ShowString(78, 90, "SQUA", 8, 1);
OLED_ShowPicture(0, 20, 128, 64, sqr, 1);
OLED_Refresh();
setparameters();
break;
case 3:
OLED_ShowString(78, 90, "TRIA", 8, 1);
OLED_ShowPicture(0, 20, 128, 64, tri, 1);
OLED_Refresh();
setparameters();
break;
}
break;
case 2:
increaseamplitude();
break;
case 3:
increasefreq();
break;
case 0:
switch(PUTSTATE){
case 0:
OLED_ShowString(90, 120, "ON ", 8, 1);
OLED_Refresh();
break;
case 1:
OLED_ShowString(90, 120, "OFF", 8, 1);
OLED_Refresh();
break;
}
if(PUTSTATE>=1){
PUTSTATE=0;
}
else{
PUTSTATE++;
}
break;
}
}
if(GPIO_Pin == KeyDown_Pin){
switch(BUTTONSTATE){
case 1:
if(WAVESTATE <= 0){
WAVESTATE=3;
}
else{
WAVESTATE--;
}
switch(WAVESTATE){
case 0:
OLED_ShowString(78, 90, "DC ", 8, 1);
OLED_ShowPicture(0, 20, 128, 64, dc, 1);
OLED_Refresh();
setparameters();
break;
case 1:
OLED_ShowString(78, 90, "SINE", 8, 1);
OLED_ShowPicture(0, 20, 128, 64, sin, 1);
OLED_Refresh();
setparameters();
break;
case 2:
OLED_ShowString(78, 90, "SQUA", 8, 1);
OLED_ShowPicture(0, 20, 128, 64, sqr, 1);
OLED_Refresh();
setparameters();
break;
case 3:
OLED_ShowString(78, 90, "TRIA", 8, 1);
OLED_ShowPicture(0, 20, 128, 64, tri, 1);
OLED_Refresh();
setparameters();
break;
}
break;
case 2:
decreaseamplitude();
break;
case 3:
decreasefreq();
break;
case 0:
switch(PUTSTATE){
case 0:
OLED_ShowString(90, 120, "ON ", 8, 1);
OLED_Refresh();
break;
case 1:
OLED_ShowString(90, 120, "OFF", 8, 1);
OLED_Refresh();
break;
}
if(PUTSTATE<=0){
PUTSTATE=1;
}
else{
PUTSTATE--;
}
break;
}
}
}
按键按下进行相应的波形参数设置:
void increaseamplitude(void){
if (amplitude < 165)
{
amplitude = amplitude + 15;
if(WAVESTATE == 0){
AMP =(double) ( 3*((double)amplitude / (double)165 ));
}
else{
AMP = (double)((0.5*tim * amplitude / 165 + tim/2 )/tim*3-1.5);
}
setparameters();
ShowAMP(AMP);
}
}
void decreaseamplitude(void){
if (amplitude > 0)
{
amplitude = amplitude - 15;
if(WAVESTATE == 0){
AMP = (double) ( 3*((double)amplitude / (double)165 ));
}
else{
AMP = (double)((0.5*tim * amplitude / 165 + tim/2 )/tim*3-1.5);
}
setparameters();
ShowAMP(AMP);
}
}
void increasefreq(void){
if (frequency < 100000)
{
frequency += 1000;
FRE=frequency/1000;
setparameters();
ShowFRE(FRE);
}
}
void decreasefreq(void){
if (frequency > 100)
{
frequency -= 1000;
FRE=frequency/1000;
setparameters();
ShowFRE(FRE);
}
}
3.3 OLED显示参数信息
连接好OLED后,直接调用相应的库函数进行显示即可,需要显示波形的参数信息以及简单的界面,并随着按键状态的改变而改变,主要代码如下:
void showbegin(void){
OLED_ShowPicture(0, 20, 128, 64, dc, 1);
OLED_ShowString(0, 0, "Signal Generator", 16, 1);
OLED_ShowString(0, 90, "Signal Wave:", 8, 1);
OLED_ShowString(78, 90, "DC ", 8, 1);
OLED_ShowString(0, 100, "Amplitude:", 8, 1);
OLED_ShowString(66, 100, "3.00V", 8, 1);
OLED_ShowString(0, 110, "Frequency:", 8, 1);
OLED_ShowString(66, 110, "2KHz", 8, 1);
OLED_ShowString(0, 120, "Signal Output:", 8, 1);
OLED_ShowString(90, 120, "---", 8, 1);
OLED_Refresh();
}
void ShowAMP(double AMP)
{
char str[5];
sprintf(str, "%.2fV", AMP);
OLED_ShowString(66, 100, str, 8, 1);
OLED_Refresh();
}
void ShowFRE(uint16_t FRE)
{
char str[6];
sprintf(str, "%dKHz", FRE);
OLED_ShowString(66, 110, str, 8, 1);
OLED_Refresh();
}
五.项目总结:
完成本次项目比较简单,目的较为明确,其主要原因是stm的库函数功能强大,通过简单的学习即可调用并应用到实践中,我们只需要进行适量的修改就可以了。通过本次项目的学习,我大致了解了STM32系列单片机的使用,以及一般项目设计完成的流程与开发,完成本次项目,还提高了我的信息搜集能力,能够在网上找到很多自己需要的东西,这是很有用的。本次由于时间有限未进行示波器的功能实现,只实现了波形发生功能,以后可以再试试示波器的设计。
通过本次项目,我主要了解和掌握了:
- SPI通信协议及其工作模式;
- stm32开发软件的使用;
- OLED的显示以及旋转编码器的使用;
- PWM+LPF生成波形;
设计制作中遇到的困难:
- 对stm32cubeide的不熟悉;
- 不清楚SPI的通信协议;
- PWM设置有问题;