队伍名称:摆烂三人组
下文有对相应软件和硬件的实现进行介绍
定时的基础单位是1ms!!!
模式二中自定义输出波,不仅仅是控制每个波在周期的输出占比(时间),还要能够定义三个波的输出顺序(不一定是先输出正弦,后方波,最后三角波)。
不是通过单片机输出波形。
应当软件项目开发前应当画好结构流程图(状态机)再根据状态机线路流程依次封装相应的功能,使逻辑显得更加清晰、缜密,同时开发效率也会大为提高
【反思】在这次的软件开发中,因为在初期没有规划好完整的流程图,主函数中的一些代码存在冗余的情况,并且前期开发逻辑混乱,一直在修改代码逻辑,导致浪费了很多时间】
【本次电赛未提前规划流程图造成的不便】在电赛验收的前一天晚上才发现一个非常容易忽略的说明:“定时时间的基本单位为1ms”。虽然在定时器0的模块中只需要把对应的参数由1000ms改为1ms即可实现单位重置,但是前面许多LCD显示的内容都需要要重新编写,因为这些显示内容并没有做整合封装,很不便于修改!!当时为了程序的稳定运行没有对此处做更改,让整个软件在电赛中没有起到得拓展分的作用
声明在前:
软件中的LCD显示还未改动,还缺少在模式二下输出顺序可选的状态机模块,该代码仅作为思路指引,没有做到完全正确!!!
main.c代码仍然存在多出纰漏亟待修改,请多包含(题主正忙于学习stm32,还没有时间来修正代码,如果这篇文章阅读量大于500,请在评论区踢题主一脚)
注意事项:
八位数据线依次接入外部引脚P1^0-P1^7
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7
#include
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P1
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
#ifndef __LCD1602_H__
#define __LCD1602_H__
//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
#endif
#include
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0_Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init(void);
#endif
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
//矩阵键盘-键值判断
uchar MatrixKey();
//输出电平模块
void Main_Otp();//控制输出引脚
void Out_Ctl_P0(uchar opt);//控制输出
//模式判断-状态机
uint OverFLoat(uint num);//防止数据溢出:将修改时间的范围控制在0-9s
void Mod_Single();//模式一单波的输出与停止
void Mod_Multi();//模式二三波混合输出
//状态机-主控系统
void Main_Ctl(); //统帅整个状态机的状态判断(由按键决定状态的走向)
void Init_System();//初始化整个系统
Main_Ctl()判断处于那个模式下,将工作分配给 Mod_Single()与Mod_Multi()
Mod_Single()与Mod_Multi()都会在调节时间的时候去调用防溢出函数OverFLoat
主函数main会调用一个【(按键)状态机判断模块】Main_Ctl()与
【引脚高低电平输出的模块】Main_Otp()
将模式一、二的相关时间,键值,状态等参数均封装在sta这个结构体供全局是使用
外部引脚P2^0-P2^2:分别对应继电器的三个开关,在通过这三个开关分别连接到三个波的电路上
flag_singleOK是模式一中完成
flag_allOK是模式二中完成三个波的各自输出时间
/**
* @file main.c
* @author jUicE_g2R(qq:3406291309)
*
* @brief 通过按键状态机实现两个模式下三个外接引脚的高低电平输出
* @用途:控制C组中三种不同波形的输出
*
* @version 0.1
* 0.1:通过按键控制状态机输出电平
* 0.2:PC端通过UART串口通信控制状态机输出电平
*
* @date 2023-3-6(the Latest Correct Time)
*
* @code(state) 已完成两种模式的调试
*
* @copyright Copyright (c) 2023
*/
#include
#include "Timer0.h"
#include "Delay.h"
#include "LCD1602.h"
#define uint unsigned int
#define uchar unsigned char
sbit opt1=P2^0; //小灯测试演示波输出
sbit opt2=P2^1;
sbit opt3=P2^2;
#define Keys P3 //矩阵键盘总控键
//记录波形输入
struct state
{
//值Val
uchar wave_val; //波值
uchar key_val; //矩阵键盘键值
//控制状态
uchar flag_singleOK;
uchar flag_otp; //是否输出波
uchar flag_free; //是否进入自由模式
uchar flag_allOK; //确定
//模式一----输出时间与停止时间
uint wave_otp_t;
uint wave_stop_t;
//模式二----各波输出的时间0-9
uint wave_t1;
uint wave_t2;
uint wave_t3;
//输出判断flag声明
uchar flag1_process;
uchar flag2_process;
} sta={1,0,0,1,0,0,1,1,1,1,1,0,0};
/*-----------子函数声明------------*/
//矩阵键盘-键值判断
uchar MatrixKey();
//输出电平模块
void Main_Otp();
void Out_Ctl_P0(uchar opt);
//模式判断-状态机
uint OverFLoat(uint num);
void Mod_Single();
void Mod_Multi();
//状态机-主控系统
void Main_Ctl();
void Init_System();
//主函数
int main(void)
{
Init_System();
while(1)
{
Main_Otp();
Main_Ctl();
}
}
uchar hex[5]={0,0xE,0xD,0xB,0x7};
/*-------------矩阵键盘 检测算法------------*/
uchar MatrixKey()
{
uchar row, colu, value, value_temp, i;
uchar rank; //矩阵键盘键号
Keys=0x0f;//矩阵键盘初始化
if(Keys!=0x0f)
{
Delay(10);//软件消抖
if(Keys!=0x0f)
{
row=Keys;
Keys=0xf0;
colu=Keys;
value=row+colu;
rank=0;
value_temp=value%16;
for( i=1;i<=4;i++)
{
if(value_temp==hex[i])
rank=(i-1)*4;
}
value_temp=value/16;
for( i=1;i<=4;i++)
{
if(value_temp==hex[i])
rank+=i;
}
while(Keys==0xFF);
}
while(Keys!=0xf0);
}
else
{
rank=0;
}
return rank;
}
/*------------电平输出控制-----------*/
void Out_Ctl_P0(uchar opt)
{
switch (opt)
{
case 0:
opt1=0;
opt2=0;
opt3=0;
case 1:
opt1=1;
opt2=0;
opt3=0;
break;
case 2:
opt1=0;
opt2=1;
opt3=0;
break;
case 3:
opt1=0;
opt2=0;
opt3=1;
default:
break;
}
}
void Main_Otp() //引脚输出
{
if(sta.flag_singleOK==1)
{
switch (sta.flag1_process)
{
case 1: //输出
Out_Ctl_P0(sta.wave_val);
break;
case 2: //停止
opt1=0;
opt2=0;
opt3=0;
break;
default:
break;
}
}
if(sta.flag_allOK==1)
{
switch (sta.flag2_process)
{
case 1: //输出一号波
opt1=1;
opt2=0;
opt3=0;
break;
case 2: //输出二号波
opt1=0;
opt2=1;
opt3=0;
break;
case 3: //输出三号波
opt1=0;
opt2=0;
opt3=1;
break;
default:
break;
}
}
if(sta.flag_otp==0)
{
LCD_ShowString(1,4,"STOP");
}
}
uint OverFLoat(uint num)
{
if(num>9)
{
num=0;
}
else if(num<0)
{
num=9;
}
return num;
}
/*-----------------键值处理-----------------*/
void Mod_Single() //单项调试
{
static uchar wave_temp=1;
static uchar flag_OKTemp=0;
static uchar i=1;
uchar wave_t;
if(sta.key_val==4&i==3) //选定波,输出以及停止时间后输出
{
i=1; //重置
sta.flag_singleOK=1;
LCD_ShowString(1,12,"allOK");
LCD_ShowString(1,4,"OPT_");
}
else
{
switch (sta.key_val)
{
case 1:
wave_temp++;
if(wave_temp>3)
{
wave_temp=1;
}
LCD_ShowNum(1,2,wave_temp,1);//显示波号
sta.wave_val=wave_temp; //记录wave号:便于停止波后重启时波保持停止前的输出状态
break;
case 2: //是否输出波
sta.flag_otp++;
if((sta.flag_otp)>1)
{
sta.flag_otp=0;
}
if(sta.flag_otp==0) //停止输出波
{
sta.flag_singleOK=0;
}
break;
case 4:
i++;
flag_OKTemp=1;
break;
case 6: //输出时间
flag_OKTemp=0;
switch (i)
{
case 2:
wave_t=sta.wave_otp_t+1;
sta.wave_otp_t=OverFLoat(wave_t);
LCD_ShowNum(1,9,sta.wave_otp_t,1);
break;
case 3:
wave_t=sta.wave_stop_t+1;
sta.wave_stop_t=OverFLoat(wave_t);
LCD_ShowNum(1,11,sta.wave_stop_t,1);
break;
default:
break;
}
break;
case 7: //停止时间
flag_OKTemp=0;
switch (i)
{
case 2:
wave_t=sta.wave_otp_t-1;
sta.wave_otp_t=OverFLoat(wave_t);
LCD_ShowNum(1,9,sta.wave_otp_t,1);
break;
case 3:
wave_t=sta.wave_stop_t-1;
sta.wave_stop_t=OverFLoat(wave_t);
LCD_ShowNum(1,11,sta.wave_stop_t,1);
break;
default:
break;
}
break;
default:
break;
}
if(flag_OKTemp==0)
{
LCD_ShowString(1,15,"no");
}
else
{
LCD_ShowString(1,15,"OK");
}
}
}
void Mod_Multi() //自由模式
{
static uchar flag_OKTemp=0;
uint wave_t;
static uchar i=0; //调好三个波的时间就可以输出了
if(i==3&&sta.key_val==4) //切换到第三个波且确定时,开始输出波
{
i=1; //重置
sta.flag_allOK=1; //可以输出波了
sta.flag_otp=1; //引脚高电平输出
LCD_ShowString(1,12,"allOK");
LCD_ShowString(1,4,"OPT_");
}
else //选择波以及波输出时间
{
switch (sta.key_val)
{
case 4: //每个波形时间确定键
flag_OKTemp=1;
i++;
break;
case 5: //切波:在确定的条件下才能切波
if(flag_OKTemp==1)
{
sta.wave_val++;
if(sta.wave_val>3)
{
sta.wave_val=1;
}
flag_OKTemp=0; //重置OK值
LCD_ShowNum(1,2,sta.wave_val,1); //切换的波的波号显示
}
break;
case 6: //增加波的时间
flag_OKTemp=0;
switch (sta.wave_val)
{
case 1:
wave_t=sta.wave_t1+1;
sta.wave_t1=OverFLoat(wave_t);
LCD_ShowNum(2,4,sta.wave_t1,1);
break;
case 2:
wave_t=sta.wave_t2+1;
sta.wave_t2=OverFLoat(wave_t);
LCD_ShowNum(2,9,sta.wave_t2,1);
break;
case 3:
wave_t=sta.wave_t3+1;
sta.wave_t3=OverFLoat(wave_t);
LCD_ShowNum(2,14,sta.wave_t3,1);
break;
default:
break;
}
break;
case 7: //减少波的时间
flag_OKTemp=0;
switch (sta.wave_val)
{
case 1:
wave_t=sta.wave_t1-1;
sta.wave_t1=OverFLoat(wave_t);
LCD_ShowNum(2,4,sta.wave_t1,1);
break;
case 2:
wave_t=sta.wave_t2-1;
sta.wave_t2=OverFLoat(wave_t);
LCD_ShowNum(2,9,sta.wave_t2,1);
break;
case 3:
wave_t=sta.wave_t3-1;
sta.wave_t3=OverFLoat(wave_t);
LCD_ShowNum(2,14,sta.wave_t3,1);
break;
default:
break;
}
break;
default:
break;
}
if(flag_OKTemp==0)
{
LCD_ShowString(1,15,"no");
}
else
{
LCD_ShowString(1,15,"OK");
}
}
}
void Main_Ctl() //主控模块
{
uchar wave_temp=1;
sta.key_val=MatrixKey(); //获取矩阵键值
if(sta.key_val!=0) //没有键按下就不进入判断
{
if(sta.flag_free==1)
{
if(sta.key_val==2) //在自由模式下任何情况都可以退出
{
sta.flag_otp=0; //停止输出波
sta.flag_free=0; //退出自由调节模式
LCD_ShowChar(2,16,'S'); //还原到单项操作
LCD_ShowString(1,15,"no");
}
else
{
Mod_Multi();
}
}
else if(sta.flag_free==0) //单项输出波
{
LCD_ShowChar(2,16,'S'); //更改模式
if(sta.flag_otp==0) //进入停止输出才能调至自由调整模式
{
if(sta.key_val==3) //进入自由调整模式
{
sta.flag_free=1; //此时波形已经停止输出
LCD_ShowChar(2,16,'M'); //更改模式
LCD_ShowString(1,4,"STOP");
LCD_ShowString(1,8," ");
Timer0_Init(); //重置定时器0
//输出初始化
opt1=0;
opt2=0;
opt3=0;
}
}
else if(sta.flag_otp==1) //输出波
{
Mod_Single();
}
}
}
}
void Init_System() //初始化系统
{
Timer0_Init(); //定时器初始化
/*屏幕初始化*/
LCD_Init();
//第一行演示: |W1 S|
LCD_ShowChar(1,1,'W'); //表示所处的波形
LCD_ShowNum(1,2,1,1); //波号
LCD_ShowString(1,4,"STOP");
LCD_ShowChar(2,16,'S'); //模式
//第二行演示: |W1 1 W2 2 W3 3 no|
LCD_ShowChar(2,1,'W');
LCD_ShowNum(2,2,1,1);
LCD_ShowChar(2,3,':');
LCD_ShowChar(2,6,'W');
LCD_ShowNum(2,7,2,1);
LCD_ShowChar(2,8,':');
LCD_ShowChar(2,11,'W');
LCD_ShowNum(2,12,3,1);
LCD_ShowChar(2,13,':');
LCD_ShowString(1,15,"no");
LCD_ShowChar(1,10,'|');
/*引脚输出输出初始化*/
// Out_Ctl_P0(0); //初始化输出端口
opt1=0;
opt2=0;
opt3=0;
}
/*定时器中断函数*/
void Timer0_Routine() interrupt 1
{
static int T0cot1=0;
static int T0cot2=0;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
if(sta.flag_otp==1) //控制停止输出波
{
//控制输出波flag
if(sta.flag_singleOK==1)
{
if(T0cot1==0)
{
sta.flag1_process=1;
}
else if(T0cot1==1*sta.wave_otp_t)
{
sta.flag1_process=2;
}
T0cot1++;
//重置循化
if(T0cot1>1*(sta.wave_otp_t+sta.wave_stop_t))
{
T0cot1=0;
}
}
if(sta.flag_allOK==1)
{
//控制输出波flag
if (T0cot2==0)
{
sta.flag2_process=1;
}
else if(T0cot2==1*sta.wave_t1)
{
sta.flag2_process=2;
}
else if(T0cot2==1*(sta.wave_t1+sta.wave_t2))
{
sta.flag2_process=3;
}
T0cot2++;
if(T0cot2>1*(sta.wave_t1+sta.wave_t2+sta.wave_t3)) //重置,循环
{
T0cot2=0;
}
}
}
}
采用文式桥振荡电路为基础产生正弦波,组成为:放大电路、反馈网络、选频网络、稳幅环节
文氏电桥振荡器采用的是同相放大器,当信号较小时,二极管不导通,放大倍数为A=1+(RP2+R1)/R8
当信号较大时,二极管导通,放大倍数为A=1+(RP2+r)/R8,r为R1所对应的匹配电阻。
反馈是将输出信号的全部或一部分返回至输入,使输入信号改变,而负反馈是引起输入信号减小的一种反馈方式。同向放大器引入负反馈,使得输出信号与输入信号比值稳定。
RC带通滤波器,该电路由高通和低通滤波器组合而成,其中RP1=RP4,C1=C2,频率为f=1/(2πRC)。
f=1/(2*Π*RC)(RP1 = RP4,C1 = C2),f=1/(2*Π*RP1*C2)。
文氏电桥的起振条件是:|AF|>1;但文氏电桥的稳幅条件确是:|AF|=1。利用二极管的非线性自动调节负反馈的强弱来控制输出电压的恒定。振荡过程中D1、D2将交替导通和截止,总有一个处于正向导通状态的二极管与R3并联,由于二极管正向电阻rd随ud增大而下降,因此负反馈随振幅上升而增强,也就是说A随振幅增大而下降,直到满足振幅平衡条件为止。
图中RP1和RP4可调节频率,RP2可调节放大倍数。
利用下行滞回比较器输出方波,滞回比较器的输入电压逐渐增大或者减小时,有两个不相等的阈值,其传输特性具有滞回曲线的形状,因此具有很强的抗干扰能力。
uo=土Uz,
RP3调节放大倍数。
利用集成运放构成积分器,然后对方波信号进行运算产生三角波。
R7是防止积分电路饱和的反馈电阻,它的大小需要比R1大两个数量级,这样就不会影响积分波形,R7越大三角波越往上。RP3和R5与E1的值决定三角波斜率,因此调节RP3可以改变三角波的幅值。
用继电器模块作为开关,对电路三种输出波形进行控制。由于继电器模块触发电压为3.3V~5V,进而可用控制单片机引脚输出的高低电平的方法来控制继电器的开关,从而实现对输出波形的智能化控制