本项目是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)为高阻
-
- /* 编码器旋转中断
- Interrupt for Encoder Rotation */
- IT1 = 0; //外部中断1触发方式,上升沿和下降沿
- PX1 = 1; //外部中断1高优先级
- EX1 = 1; //开启外部中断1
-
- /* 编码器按键中断
- Interrupt for Encoder Click */
- IT0 = 1; //外部中断0触发方式,下降沿
- PX0 = 1; //外部中断0高优先级
- EX0 = 1; //开启外部中断0
-
- /* 定时器0,用于更新电压信息计时
- Timer 0 for updating the information of VCC*/
- TMOD &= 0xF0; //设置定时器0模式 16位自动重载,在Keil中debug的话,请注意,这种设置是8051的旧13位模式
- AUXR &= ~0x80; //定时器0时钟12T模式
- TL0 = 0xC0; //设置定时初值 24MHz 20ms
- TH0 = 0x63; //设置定时初值 24MHz 20ms
- ET0 = 1; //允许T0溢出中断
-
- /* 定时器1,用于生成小于50Hz的PWM
- Timer 1 for generate the PWM when frequency less than 50Hz*/
- 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中断
- }
- }
- }
-
- /* 编码器旋转响应函数
- Encoder Rotate */
- void Scan_EC11(void)
- {
- /* 正转
- Rotate clockwise */
- if ((EC11_A != EC11_B))
- {
-
- Change_Val(1);
- }
- /* 反转
- Rotate anticlockwise*/
- else if ((EC11_A == EC11_B))
- {
- Change_Val(0);
- }
- }
-
- /* 编码器旋转中断
- Interrupt for Encoder rotation */
- void INT1_interrupt(void) interrupt INT_1
- {
- Delay1ms();
- Scan_EC11();
- Update_Flag = 1; //更新标志
- //Delay50ms();
- IE1 = 0; //清除中断标志位
- }
-
- /* 编码器点击中断
- Interrupt for Encoder click */
- void INT0_interrupt(void) interrupt INT_0
- {
- Delay5ms();
- if (!EC11_KEY) //还在按下状态(EC11_KEY=0),去抖动
- {
- /* 长按
- Long Press */
- 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标志位
- }
- /* 双击
- Double click */
- else if (Delay200ms_double_click()) //双击检测函数,双击返回1
- {
- if (Wave_Shape > 0)
- {
- WAVE_ON = ~WAVE_ON; //改变输出波形控制标志位
- }
- }
- /* 单击
- Single click */
- else
- {
- if (Wave_Shape == 1) //方波输出状态下
- Options = ~Options; //频率、占空比切换控制标志位
- }
- Update_Flag = 1;
- }
- Delay5ms();
- IE0 = 0;
- }
-
- /* 更新电压信息计时中断
- Timer interrupt for update voltage information */
- 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.c 文件时方便。
- #ifndef WAVE_H
- #define WAVE_H
-
- #include
- #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)
- {
- /* 0X7fff=32767是15位PWM计数器的最大值,
- * 因此FOSC / 0X7fff是主时钟不分频的情况下PWM_Hz的最小值
- * 24000000/32767 = 732
- */
- if (PWM_Hz <= (FOSC / 0X7fff))
- {
- PWMCKS_PS = 0x0F;
- }
- else
- {
- PWMCKS_PS = 0x00;
- }
- }
- /*
- *
- *
- */
- /* PWM周期,大于50Hz时使用增强型PWM波形发生器直接生成,
- * 适用STC15W4K和STC8,小于50Hz使用定时器控制GPIO翻转
- */
- 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生成高频方波
- }
- }
- /* 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)为高阻
- }
- /* PWM 配置函数 */
- void PWM_Config(void)
- {
- if (WAVE_ON)
- {
- //PWM IO状态
- PWM3 = 0; //设置PWM3 P4.5低电平
- P4M1 &= ~0x20; //设置P4.5为推挽输出
- P4M0 |= 0x20; //设置P4.5为推挽输出
- /* 占空比为0时,始终输出低电平
- Output low when duty cycle is 0*/
- if (PWM_Width == 0)
- {
- TR1 = 0; //关闭定时器1(小于50Hz的方波)
- TF1 = 0; //清除定时器1中断标志
- PWMCR &= ~0x02; //PWM通道3的端口为GPIO
- PWM3 = 0; //PWM通道3始终输出低电平
- }
- /* 占空比为100%时,始终输出高电平
- Output high when duty cycle is 100%*/
- else if (PWM_Width == PWM_Cycle)
- {
- TR1 = 0; //关闭定时器1(小于50Hz的方波)
- TF1 = 0; //清除定时器1中断标志
- PWMCR &= ~0x02; //PWM通道3的端口为GPIO
- PWM3 = 1; //PWM通道3始终输出高电平
- }
- /* PWM频率大于等于50时,使用内置增强型PWM输出
- Use enhanced PWM waveform generator when PWM frequency higher than or equal to 50*/
- 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
- }
- /* PWM频率小于50时,使用定时器输出
- Use timer when PWM frequency lower than 50*/
- 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;
- /* SPWM_interrupt中断函数运行需要时间,
- * SPWM频率120K是STC15W4K32S4的上限,
- * 再高就来不及通过SPWM_interrupt调节占空比了
- */
- float Sin_Cycle_times;
-
- /* T_SinTable的数值是根据SIN_TABLE_PWM_HZ计算得到最大周期,
- * 周期为FOSC/SIN_TABLE_PWM_HZ,24MHz主时钟150kHz对应周期160
- * 根据当前的PWM_Hz重新计算正弦表,使得正弦波幅尽量大
- */
- 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();
- }
-
- /* 用来生成正弦波的SPWM中断
- SPWM Interrupt for generating the sine waveform*/
- 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设置
- }
-
- /* 50Hz以下PWM使用Timer1中断产生
- Generate PWM below 50Hz by Timer1 Interrupt*/
- 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如果链接失效而您恰巧需要,可评论或私信
