本项目是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如果链接失效而您恰巧需要,可评论或私信