本项目是B站UP主老刘爱鼓捣制作的波形信号发生器:如何用单片机自制波形发生器生成方波和正弦波
项目GitHbu地址:https://github.com/CreativeLau/Function_Generator_STC
项目预览:
项目分析:
- 无负电源,输出无负值波形
- 无带负载能力
突然习惯了 STM32 ,再看寄存器操作的C还有些不太习惯呢
原理图 输出端口部分
SIN、PWM 分别为正弦,方波输出端口。
滤波呗?
控制部分现在发现 EC11 真是个好东西。
程序分析
包含函数:
| 名称 | 功能 |
|---|---|
| main | 主程序 |
| adc_stc15.c | 检测电压的程序 |
| lcd1602 | 基本上就是一些LCD1602的点亮配置函数库 |
| settings | LCD的显示、波形频率的改变控制 |
| delay | 延时函数、EC11长按/双击检测函数都在此文件 |
| wave | 有关输出波形的设定都在此文件 |
有关 LCD1602 显示库的部分就不做过多介绍了,这个东西是直接copy就能用的。
本项目最关心的为波形发生,故只分析波形发生之相关的函数!关于波形的改变以及LCD显示属于简单问题,在本文不予讨论。
主程序#include#include #include "lcd1602.h" #include "wave.h" #include "settings.h" #include "delay.h" #include "config_stc.h" #ifndef uint8 #define uint8 unsigned char #endif #ifndef int8 #define int8 char #endif #ifndef uint16 #define uint16 unsigned int #endif #ifndef uint32 #define uint32 unsigned long int #endif #define TIMER_0 1 //定时器0中断序号 #define INT_1 2 //编码器旋转 触发外部中断 #define INT_0 0 //编码器按下 触发外部中断 uint8 Timer0_Count; //时间计数器,定时器0用 bit Update_Flag = 1; //更新标志位 sbit SEL=P0^4; //继电器控制位 void main(void) { //LCD Pin P1M1 &= 0x00; //设置P1口为准双向 P1M0 &= 0x00; //设置P1口为准双向 P0M1 &= 0x00; //设置P0口为准双向 P0M0 &= 0x00; //设置P0口为准双向 //信号输出Pin PWM3 = 0; //设置PWM3 P4.5低电平 PWM4 = 0; //设置PWM4 P4.4低电平 P4M1 |= 0x30; //设置P4.4(PWM4_2),4.5(PWM3_2)为高阻 P4M0 &= ~0x30; //设置P4.4(PWM4_2),4.5(PWM3_2)为高阻 IT1 = 0; //外部中断1触发方式,上升沿和下降沿 PX1 = 1; //外部中断1高优先级 EX1 = 1; //开启外部中断1 IT0 = 1; //外部中断0触发方式,下降沿 PX0 = 1; //外部中断0高优先级 EX0 = 1; //开启外部中断0 TMOD &= 0xF0; //设置定时器0模式 16位自动重载,在Keil中debug的话,请注意,这种设置是8051的旧13位模式 AUXR &= ~0x80; //定时器0时钟12T模式 TL0 = 0xC0; //设置定时初值 24MHz 20ms TH0 = 0x63; //设置定时初值 24MHz 20ms ET0 = 1; //允许T0溢出中断 TMOD &= 0x0F; //工作模式,0: 16位自动重装 AUXR &= ~0x40; //12T ET1 = 1; //允许中断 EA = 1; //开总中断 PWM_Hz_Pre = PWM_Hz; //PWM_Hz_Pre记录上一次PWM频率 Wave_Shape_Pre = Wave_Shape; //Wave_Shape_Pre记录上次波形标志 Get_PWM_Duty_Limit(); //根据PWM_Hz设定占空比上下限 if (PWM_Duty > PWM_Max_Duty) PWM_Duty = PWM_Max_Duty; else if (PWM_Duty < PWM_Min_Duty) PWM_Duty = PWM_Min_Duty; Lcd_Init(); while (1) { if (Update_Flag) //更新标志位 { Update_Flag = 0; //清除标志位 Wave_OFF(); //关闭波形输出 if (Wave_Shape_Pre != Wave_Shape) //判断是否改变输出波形,1、方波;2、正弦波 { Wave_Shape_Pre = Wave_Shape; if (Wave_Shape == 0) //显示电源电压 { EX1 = 0; //关闭外部中断1(编码器旋转) TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 (定时器0为VCC更新计时) } else if (Wave_Shape == 1) //方波输出 { PWM_Hz = PWM_Hz_Pre; EX1 = 1; //开启外部中断1(编码器旋转) TR0 = 0; //关闭定时器0 (定时器0为VCC更新计时) TF0 = 0; //清除TF0标志 } else if (Wave_Shape == 2) //正弦波输出 { PWM_Hz_Pre = PWM_Hz; } } if (Wave_Shape == 1) //方波输出模式 { SEL = 1; //继电器控制 Set_PWMCKS_PS(); Set_PWM_Cycle(); Set_PWM_Width(); } else if (Wave_Shape == 2) //正弦波输出模式 { SEL = 0;//继电器控制 Set_Sin_Table_Times(); Set_PWMCKS_PS(); Set_PWM_Cycle(); } Update_LCD(); //更新LCD显示 Set_Wave_Shape(); //生成正弦波SPWM中断 } } } void Scan_EC11(void) { if ((EC11_A != EC11_B)) { Change_Val(1); } else if ((EC11_A == EC11_B)) { Change_Val(0); } } void INT1_interrupt(void) interrupt INT_1 { Delay1ms(); Scan_EC11(); Update_Flag = 1; //更新标志 //Delay50ms(); IE1 = 0; //清除中断标志位 } void INT0_interrupt(void) interrupt INT_0 { Delay5ms(); if (!EC11_KEY) //还在按下状态(EC11_KEY=0),去抖动 { if (Delay500ms_long_click()) //长按检测函数,长按返回1 { Wave_Shape++; //改变输出波形 if (Wave_Shape > WAVE_NUM) Wave_Shape = 0; if (Wave_Shape == 2) Options = 1; WAVE_ON = 0; //输出波形控制标志位 Clear_LCD_Flag = 1; //清屏LCD标志位 } else if (Delay200ms_double_click()) //双击检测函数,双击返回1 { if (Wave_Shape > 0) { WAVE_ON = ~WAVE_ON; //改变输出波形控制标志位 } } else { if (Wave_Shape == 1) //方波输出状态下 Options = ~Options; //频率、占空比切换控制标志位 } Update_Flag = 1; } Delay5ms(); IE0 = 0; } void TIMER0_interrupt() interrupt TIMER_0 { if (++Timer0_Count > 200) //200x20=4000ms { Timer0_Count = 0; Update_Flag = 1; } }
EC11 中断口判断为 A 端口。由 A 端口的上升沿/下降沿后的电平和 B 端口的电平做对比,判断是正转还是反转。(每次用 EC11 都会把正转反转搞混,我像个废物)
关于 EC11 我有篇文章写过,请参考(只能作为参考):多功能小键盘(基于HK32F030M)
波形控制函数 wave.h列出这些只是为了看 wave.c 文件时方便。
#ifndef WAVE_H #define WAVE_H #includewave.c#include #include "config_stc.h" #include "delay.h" #ifndef uint8 #define uint8 unsigned char #endif #ifndef int8 #define int8 char #endif #ifndef uint16 #define uint16 unsigned int #endif #ifndef uint32 #define uint32 unsigned long int #endif #define FOSC 24000000UL //主时钟 #define SPWM_VECTOR 22 //PWM中断序号 #define TIMER_1 3 //定时器1中断序号 #define CBIF 0x40 //PWM计数器归零中断标志 #define SIN_TABLE_PWM_HZ 150000 //T_SinTable计算使用的PWM频率,用FOSC/SIN_TABLE_PWM_HZ求出PWM最大宽度 #define SIN_POINTS 1000 //T_SinTable点数 #define SIN_OFFSET 1 //SIN值偏移量,用于修正第一次翻转和第二次翻转都为0的情况,本应该翻转两次,这样只翻转了一次,造成电平反向 #define WAVE_NUM 2 //波形选项的数量,暂时只做了方波和正弦波,设置为2,当Wave_Shape=0时,跳转到显示VCC电压 #define PWM_MAX_DUTY 99 //PWM最大占空比 #define PWM_MIN_DUTY 1 //PWM最小占空比 #define PWM_MIN_WIDTH 50 //PWM最小宽度持续时间 ns #define PWM_MAX_HZ 4000000 //PWM最大频率4MHz #define PWM_MIN_HZ 1 //PWM最小频率1Hz #define SIN_MAX_HZ 10000 //SIN最大频率10kHz #define SIN_MIN_HZ 1 //SIN最小频率1Hz #define CKS_50HZ 12 //小于50Hz的时钟分频 #define PWMC (*(uint16 volatile xdata *)0xfff0) //PWM计数器 #define PWMCKS (*(uint8 volatile xdata *)0xfff2) //PWM时钟选择位 #define PWM3T1 (*(uint16 volatile xdata *)0xff10) //PWM3T1计数器 #define PWM3T2 (*(uint16 volatile xdata *)0xff12) //PWM3T2计数器 #define PWM3CR (*(uint8 volatile xdata *)0xff14) //PWM3控制位 #define PWM4T1 (*(uint16 volatile xdata *)0xFF20) //PWM4T1计数器 #define PWM4T2 (*(uint16 volatile xdata *)0xFF22) //PWM4T2计数器 #define PWM4CR (*(uint8 volatile xdata *)0xFF24) //PWM4控制位 extern uint8 PWMCKS_PS; //系统时钟分频系数 extern uint32 PWM_Hz; //PWM频率 extern uint32 PWM_Hz_Pre; //记录上一次PWM频率 extern int8 PWM_Duty; //PWM占空比 extern uint32 SIN_Hz; //SIN频率 extern uint8 Wave_Shape; //波形标志 1:方波 2:正弦波 extern uint8 Wave_Shape_Pre; //上次波形标志 extern uint8 Sin_Table_Times; //SIN倍率 extern bit WAVE_ON; //输出波形标志位 1:ON 2:OFF void Set_Wave_Shape(); void Wave_OFF(); void Set_PWMCKS_PS(); void Set_PWM_Cycle(); void Set_PWM_Width(); void Set_Sin_Table_Times(); #endif
其实作者已经弄的很好了,但我还是想着再自己分析分析!
总的来说难度不算很高!正弦波的数据存储在 T_SineTable.h 文件中。
#include "wave.h"
#include "T_SineTable.h"
bit WAVE_ON = 0; //输出波形标志位 1:ON 0:OFF
uint8 PWMCKS_PS = 0; //系统时钟分频系数
uint32 PWM_Hz = 100; //PWM频率
uint32 PWM_Hz_Pre; //记录上一次PWM频率
uint32 PWM_Cycle; //PWM周期(最大值为32767)
int8 PWM_Duty = 50; //PWM占空比
uint32 PWM_Width; //PWM高电平宽度
uint32 SIN_Hz = 100; //SIN频率
uint8 Wave_Shape = 1; //波形标志 1:方波 2:正弦波
uint8 Wave_Shape_Pre; //上次波形标志
uint16 PWM_Index = 0; //SPWM查表索引
uint16 T_SinTable_Current[SIN_POINTS]; //根据原始SIN值计算新的SIN表
uint8 Sin_Table_Times = 1; //SIN倍率
uint32 PWM1_high, PWM1_low;
uint16 n, n_high, n_low;
void Set_PWMCKS_PS(void)
{
if (PWM_Hz <= (FOSC / 0x7fff))
{
PWMCKS_PS = 0x0F;
}
else
{
PWMCKS_PS = 0x00;
}
}
void Set_PWM_Cycle(void)
{
if (PWM_Hz < 50)
{
PWM_Cycle = FOSC / CKS_50HZ / PWM_Hz; //使用定时器1循环生成低频方波
// 主时钟 / 12分频系数 / PWM_Hz
}
else
{
PWM_Cycle = (FOSC * 10 / (PWMCKS_PS + 1) / PWM_Hz + 5) / 10 - 1; //使用STC15W4K的高精度PWM生成高频方波
}
}
void Set_PWM_Width(void)
{
PWM_Width = (PWM_Cycle * PWM_Duty * 10 + 5) / 10 / 100;
}
void Set_Sin_Table_Times()
{
Sin_Table_Times = 1;
if (SIN_Hz > 6000)
Sin_Table_Times = 100;
else if (SIN_Hz > 5000)
Sin_Table_Times = 50;
else if (SIN_Hz > 3000)
Sin_Table_Times = 40;
else if (SIN_Hz > 2500)
Sin_Table_Times = 25;
else if (SIN_Hz > 1000)
Sin_Table_Times = 20;
else if (SIN_Hz > 500)
Sin_Table_Times = 8;
else if (SIN_Hz > 250)
Sin_Table_Times = 4;
else if (SIN_Hz > 100)
Sin_Table_Times = 2;
PWM_Hz = SIN_Hz * SIN_POINTS / Sin_Table_Times;
}
void Wave_OFF(void)
{
P_SW2 |= 0x80; //访问xSFR
PWMCR &= ~0x80; //关闭PWM模块 (大于50Hz的方波和正弦波)
PWMCR &= ~0x40; //禁止PWM计数器归零中断 (正弦波)
PWMIF &= ~CBIF; //清除中断标志
P_SW2 &= ~0x80; //恢复访问XRAM
TR1 = 0; //关闭定时器1(小于50Hz的方波)
TF1 = 0; //清除定时器1中断标志
//PWM IO状态
PWM3 = 0; //设置PWM3 P4.5低电平 方波
PWM4 = 0; //设置PWM4 P4.4低电平 正弦波
P4M1 |= 0x30; //设置P4.4(PWM4_2),4.5(PWM3_2)为高阻
P4M0 &= ~0x30; //设置P4.4(PWM4_2),4.5(PWM3_2)为高阻
}
void PWM_Config(void)
{
if (WAVE_ON)
{
//PWM IO状态
PWM3 = 0; //设置PWM3 P4.5低电平
P4M1 &= ~0x20; //设置P4.5为推挽输出
P4M0 |= 0x20; //设置P4.5为推挽输出
if (PWM_Width == 0)
{
TR1 = 0; //关闭定时器1(小于50Hz的方波)
TF1 = 0; //清除定时器1中断标志
PWMCR &= ~0x02; //PWM通道3的端口为GPIO
PWM3 = 0; //PWM通道3始终输出低电平
}
else if (PWM_Width == PWM_Cycle)
{
TR1 = 0; //关闭定时器1(小于50Hz的方波)
TF1 = 0; //清除定时器1中断标志
PWMCR &= ~0x02; //PWM通道3的端口为GPIO
PWM3 = 1; //PWM通道3始终输出高电平
}
else if (PWM_Hz >= 50)
{
P_SW2 |= 0x80; //访问xSFR
PWMCKS = 0x00; //PWM时钟选择
PWMCKS |= PWMCKS_PS; //系统时钟分频作为PWM时钟
PWMC = PWM_Cycle; //设置PWM周期
PWMCFG &= ~0x02; //配置PWM的输出初始电平
PWM3T1 = 0; //第一次翻转计数器
PWM3T2 = PWM_Width; //第二次翻转计数器
PWM3CR = 0x08; //PWM3输出到P4.5
PWMCR = 0x02; //使能PWM3输出
PWMCR &= ~0x40; //禁止PWM计数器归零中断
PWMCR |= 0x80; //使能PWM模块
P_SW2 &= ~0x80; //恢复访问XRAM
}
else
{
PWMCR &= ~0x02; //PWM通道3的端口为GPIO
PWM3 = 0; //PWM通道3输出低电平
PWM1_high = PWM_Width; //高电平持续总时间
PWM1_low = PWM_Cycle - PWM_Width; //低电平持续总时间
n_high = PWM1_high / 65536; //高电平超过定时器溢出的次数
n_low = PWM1_low / 65536; //低电平超过定时器溢出的次数
PWM1_high = 65535 - PWM1_high % 65536 + FOSC * 2 / 10000 / CKS_50HZ; //定时器初值,并修正为判断PWM电平变化延时的200us
PWM1_low = 65535 - PWM1_low % 65536 + FOSC * 2 / 10000 / CKS_50HZ; //定时器初值,并修正为判断PWM电平变化延时的200us
if (PWM1_high > 65535) //修正后的定时器初值大于65535则再次修正
{
n_high--;
PWM1_high -= 65535;
}
if (PWM1_low > 65535) //修正后的定时器初值大于65535则再次修正
{
n_low--;
PWM1_low -= 65535;
}
n = n_low;
TH1 = (uint8)(PWM1_low >> 8); //如果是输出低电平,则装载低电平时间。
TL1 = (uint8)PWM1_low;
TR1 = 1; //定时器1开始运行
}
}
else
{
Wave_OFF();
}
}
void Sin_Wave_Config(void)
{
int i;
float Sin_Cycle_times;
Sin_Cycle_times = SIN_TABLE_PWM_HZ * 1.0 / PWM_Hz; //当前PWM_Hz相对SIN_TABLE_PWM_HZ的倍率
for (i = 0; i < SIN_POINTS; i += Sin_Table_Times) //重新计算正弦表,并增加偏移量SIN_OFFSET
{
T_SinTable_Current[i] = T_SinTable[i] * Sin_Cycle_times + SIN_OFFSET;
}
if (WAVE_ON)
{
//PWM IO状态
PWM4 = 0; //设置PWM4 P4.4低电平
P4M1 &= ~0x10; //设置P4.4为推挽输出
P4M0 |= 0x10; //设置P4.4为推挽输出
P_SW2 |= 0x80; //访问xSFR
PWMCR &= ~0x80; //关闭PWM模块
PWMCR &= ~0x40; //禁止PWM计数器归零中断
PWMIF &= ~CBIF; //清除中断标志
PWMCKS = 0x00; //PWM时钟选择
PWMCKS |= PWMCKS_PS; //系统时钟分频作为PWM时钟
PWMC = PWM_Cycle; //设置PWM周期
PWM_Index = 0;
PWM4T1 = 0; //第一次翻转计数器
PWM4T2 = T_SinTable_Current[PWM_Index]; //第二次翻转计数器
PWM_Index += Sin_Table_Times;
PWMCFG &= ~0x04; //配置PWM4的输出初始电平
PWM4CR = 0x08; //PWM4输出到P4.4,无中断
PWMCR |= 0x04; //使能PWM4输出
PWMCR |= 0x40; //允许PWM计数器归零中断
PWMCR |= 0x80; //使能PWM模块
P_SW2 &= ~0x80; //恢复访问XRAM
}
else
{
Wave_OFF();
}
}
void Set_Wave_Shape(void)
{
if(Wave_Shape==1) //方波
PWM_Config();
else if (Wave_Shape == 2) //正弦波
Sin_Wave_Config();
}
void SPWM_interrupt(void) interrupt SPWM_VECTOR
{
PWMIF &= ~CBIF; //清除中断标志
_push_(P_SW2); //保存SW2设置
P_SW2 |= 0x80; //访问XFR
PWM4T2 = T_SinTable_Current[PWM_Index];
if ((PWM_Index += Sin_Table_Times) >= SIN_POINTS)
PWM_Index = 0;
_pop_(P_SW2); //恢复SW2设置
}
void TIMER1_interrupt(void) interrupt TIMER_1
{
TR1 = 0;
if (n-- == 0)
{
PWM3 = !PWM3;
Delay200us(); //延时等待PWM3电平变化,200us 24MHz对应4800周期,在计时器初值扣除
if (PWM3)
{
n = n_high;
TH1 = (uint8)(PWM1_high >> 8); //如果是输出高电平,则装载高电平时间。
TL1 = (uint8)PWM1_high;
}
else
{
n = n_low;
TH1 = (uint8)(PWM1_low >> 8); //如果是输出低电平,则装载低电平时间。
TL1 = (uint8)PWM1_low;
}
}
else
{
TH1 = 0x00;
TL1 = 0x00;
}
TF1 = 0;
TR1 = 1;
}
总结
较为简单的项目,但让我自己写写不出来,哈哈哈哈哈哈!
还是要多看别人的项目,多学习!
感谢开源 ~
项目资料:
链接:https://pan.baidu.com/s/1hBGWUrWhzagesNPe3dUWkA
提取码:m493如果链接失效而您恰巧需要,可评论或私信



