【波形/信号发生器】基于 STC1524K32S4 for C on Keil

本项目是B站UP主老刘爱鼓捣制作的波形信号发生器:如何用单片机自制波形发生器生成方波和正弦波

项目GitHbu地址:https://github.com/CreativeLau/Function_Generator_STC

项目预览:

来自老刘爱鼓捣的GitHub仓库

 项目分析:

  • 无负电源,输出无负值波形
  • 无带负载能力

突然习惯了 STM32 ,再看寄存器操作的C还有些不太习惯呢


原理图

【波形/信号发生器】基于 STC1524K32S4 for C on Keil_第1张图片

输出端口部分

【波形/信号发生器】基于 STC1524K32S4 for C on Keil_第2张图片

SIN、PWM 分别为正弦,方波输出端口。

滤波呗?

控制部分

现在发现 EC11 真是个好东西。

【波形/信号发生器】基于 STC1524K32S4 for C on Keil_第3张图片


程序分析

【波形/信号发生器】基于 STC1524K32S4 for C on Keil_第4张图片

包含函数:

名称 功能
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.h

列出这些只是为了看 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

wave.c

其实作者已经弄的很好了,但我还是想着再自己分析分析!

总的来说难度不算很高!正弦波的数据存储在 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

如果链接失效而您恰巧需要,可评论或私信

【波形/信号发生器】基于 STC1524K32S4 for C on Keil_第5张图片

 

你可能感兴趣的:(开源小项目学习合集,c语言,单片机,开发语言)