(2023年的工程) 蓝桥杯单片机stc15f2k60s2 个人当年的工程模板

自上次参加蓝桥杯单片机组也是过去一年了,真是恍惚不再获啊,想起一年前的我此刻估计还在学习备赛蓝桥杯单片机的国赛,也正在编写这个自己的这个模板,当时真是觉得这就是我一路学来的结晶啊,现在看来也不过是个加了定时器中断的前后台裸机系统罢了......

真不是什么拿得出手的货,这里就分享一下吧......

这个模板可以开启几乎所有模块,并解算它们传感回的数据:

文章提供测试代码讲解、完整工程下载、测试效果图

目录

前后台裸机系统:

关键部分代码逻辑:

时序安排函数:

定时器中断服务函数:

串口与上位机通信部分:

按键的处理:

数码管的处理:

用数组思想操作LED:

 DS1302修改时间:

完整测试工程下载:


前后台裸机系统:

裸机前后台系统是一个嵌入式系统的概念,它通常指的是没有操作系统(如Linux、Windows等)参与的嵌入式环境。在这样的环境中,开发者需要直接管理硬件资源,如内存、中断、定时器等。

在裸机前后台系统中,定时器(Timer)是一个非常重要的组件,用于实现定时任务、延时操作、周期性检查等功能。前后台系统通常包含一个后台程序(Background Program)和一个或多个前台程序(Foreground Program)。

  • 后台程序:后台程序通常是一个无限循环的程序,它负责整个系统的资源管理、任务调度和事件处理。在后台程序中,可以使用定时器来周期性地检查任务状态、更新系统时间、处理超时事件等。
  • 前台程序:前台程序负责处理具体的业务逻辑和中断服务程序(ISR)。当中断发生时,中断服务程序会立即响应并处理中断事件。如果中断事件需要耗时较长的时间来处理,中断服务程序可以标记事件的发生,并返回。然后,后台程序会在适当的时机调度前台程序来处理这些事件。

在裸机前后台系统中,定时器的实现方式取决于具体的硬件平台和编程语言。一般来说,可以使用硬件定时器(如CPU内置的定时器)或软件定时器(通过编程实现的定时器)来实现。

硬件定时器通常具有更高的精度和可靠性,因为它们是由硬件直接支持的。但是,硬件定时器的数量和使用方式可能受到硬件平台的限制。软件定时器则是通过编程实现的,它们可以在没有硬件定时器支持的情况下使用。软件定时器通常使用一个计数器或定时器中断来实现定时功能。

 

关键部分代码逻辑:

时序安排函数:

这是个由若干个if(++n == x )组成的安排时序的函数,它就像一个任务列表,不断刷新各个模块任务的标志位,然后主函数接收到多余标志位就调用对应的函数处理具体的任务

而刷新这些任务的条件就是"时间",有的任务被安排在定时器中断 计数到 6ms时执行,有的被安排到 25ms时,并且执行完任务后相应的 计数标志还会被清零,以便后续循环做这个任务

//时序决定函数 ————决定各个模块运转时序 的函数
//此函数十分重要!乃是程序运行之心
//(在定时器0中断服务函数被调用 )
void Task_Clock()
{
	if(++smg_cnt==6) 																					   //6ms 一次打印数码管	
	  {smg_cnt=0;give_nr();smg_display(nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8);}
	if(URX_Num > 0) 																					   //33ms 一次串口接收		
	  { if(++URX_tt == 33) { URX_Over = 1;} }	
	if(++key_cnt==25)  							                             //25ms 一次按键扫描
	  {key_cnt=0;key_flag=1;}
	if(wei!=0)  {if(++lm_cnt==600) {lm_cnt=0;lm_flag=!lm_flag;}} //600ms 改变一次亮灭标志(前提是有位被选中)
	if(wei==0)  {lm_flag=0;}                                     //没位被选中,就不灭
	if(++DS1302_cnt==145)                                        //145ms 一次读取ds1302   
		{DS1302_cnt=0;DS1302_flag=1;}
	if(++temperature_cnt==601)                                   //601ms 一次读取温度
	  {temperature_cnt=0;temperarure_flag = 1;}
	if(++LCM_cnt==655)                                           //655ms读取一次超声波
	  {LCM_cnt=0;LCM_flag=1;}
	if(++adc_cnt==122)                                           //122ms读取一次ADC
	  {adc_cnt=0;adc_flag=1;}
	if(++dac_cnt==172)                                           //172msDAC电压输出一次
	  {dac_cnt=0;dac_flag=1;}
	if(++uart_send_cnt==1251)                                    //1251ms集中进行一次串口发送数据		
	  {uart_send_cnt=0;uart_send_flag=1;}
}

定时器中断服务函数:

这个函数十分重要,他的中断频率决定了各个标志位的刷新频率,它还包含调用了之前的时序安排函数 并能够为按键长按等特殊需要计时的操作提供计时


//1毫秒@12.000MHz 定时器0 初始化函数
//此函数可在软件生成,别忘了添加 EA=1; ET0=1;这俩句
//此函数初始化定时器0为 1ms 中断一次,满足普遍要求
//当要使用NE555时,要避免使用定时器0 转而使用定时器1
void Timer0Init(void)
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0x18;		  //设置定时初始值
	TH0 = 0xFC;		  //设置定时初始值
	TF0 = 0;		    //清除TF0标志
	TR0 = 1;		    //定时器0开始计时
	
	EA=1;           //打开总中断
	ET0=1;					//打开定时器0中断
}

//定时器0中断服务函数
//调用 时序决定函数 
//为按键长按计数计时,开启相应功能
void TIMER0_serv() interrupt 1 
{
	Task_Clock();    //调用 时序决定函数 	
	
	//为按键的长按计数计时:
	if(key4_flag==1){k4_cnt++;} else if(key4_flag==0) {k4_cnt=0;}
	if(key5_flag==1){k5_cnt++;} else if(key5_flag==0) {k5_cnt=0;}
	if(key8_flag==1){k8_cnt++;} else if(key8_flag==0) {k8_cnt=0;}
	if(key9_flag==1){k9_cnt++;} else if(key9_flag==0) {k9_cnt=0;}	
	
	//为按键长按到达做相应功能:
	//别忘了让 长按到达标志(key_long_state)置 1,以消除短按影响:
	//此处 nrx++ 仅做测试用,nrx++ 可改变 数码管对应nr打印内容,删去会使长按取消短按功能
	if(k4_cnt==1000) {AT24C02_flag=1;key_long_state=1;}
	if(k5_cnt==1000 && jm==2) {clear_time(); key_long_state=1;}	
	if(k8_cnt==1000) {key_long_state=1;}	
	if(k9_cnt==1000) {key_long_state=1;}		
}

串口与上位机通信部分:

当时还注意学习了如何与上位机进行简单的互动,但像这样简单的定义一个数组堆栈的接收方式,现在看来十分不严谨,还是要用状态机思维进行接收,数据以包的形式收发,有帧头帧尾才好......

//串口命令 接收处理按键函数
//会先向上位机 返回单片机接收到的字符串,然后执行一些反馈,反馈如下:
//接收到 START\r\n 就返回  START OVER\r\n
//接收到 HI\r\n    就返回  HELLO\r\n
//接收到 其他字符串命令一律返回 ERROR\r\n
//此函数 封装包含了 对应标志位的 监测作用
//注意需要头文件 #include "string.h" 
void handle_uart()
{
	if(URX_Over==1)
	{
		URX_Over=0;
		printf("%s\n",&URX);
		if(URX_Num==7)          //先判断命令字符串长度,在进行比较( str1 的长度为7 )
		{if(strncmp(URX,str1,7)==0) {Uart_Sendstring("START OVER\r\n");}
		 else    Uart_Sendstring("ERROR\r\n");}
		else if(URX_Num==4)      //先判断命令字符串长度,在进行比较( str2 的长度为4 )
		{if(strncmp(URX,str2,4)==0)  {Uart_Sendstring("HELLO\r\n");}
		 else    Uart_Sendstring("ERROR\r\n");}
		else    Uart_Sendstring("ERROR\r\n");
		URX_Num = 0;              //处理完命令别忘了将其清零,以便接收下个命令
		memset(URX,0,sizeof(URX));//处理完命令别忘了将其清零,以便接收下个命令
	}
}

//串口1  中断服务函数
//这里默认串口1中断服务是要接收 字符串命令 
//接收到字符就清零URX_tt,阻止其在定时器中的计数,直到没有字符进来为止
void Uart_1_serv() interrupt 4
{
 	if(RI)
	{
		RI=0;  URX_tt = 0;
		if(URX_Num < 10) { URX[URX_Num++] = SBUF;	}		
	}
}

按键的处理:

当时对于按键的处理还停留在行列式扫描的阶段,也许是那时觉得这样写最稳定把,我还是建议用状态机思维去写按键


//按键扫描初始化函数 
//通过参数来选择初始化哪一行哪一列
void key_scan_inint(u8 n)
{
	switch(n)
	{
		case 1:X1=0;X2=1;Y1=1;Y2=1;break;
		case 2:X1=1;X2=0;Y1=1;Y2=1;break;
	}
}

//按键返回键值函数
//给S4 S5 S8 S9每个都配备了长按功能
u8 key_return()
{
	u8 key_value;
	key_value=0;
	
	Delay12ms();        //延时12ms 消抖
	
	key_scan_inint(1);
	if(Y1==0){while(Y1==0){key4_flag=1;} key4_flag=0; key_value=4;}
	if(Y2==0){while(Y2==0){key8_flag=1;} key8_flag=0; key_value=8;}	
	
	key_scan_inint(2);
	if(Y1==0){while(Y1==0){key5_flag=1;} key5_flag=0; key_value=5;}
	if(Y2==0){while(Y2==0){key9_flag=1;} key9_flag=0; key_value=9;}	
	
	if(key_long_state==1) //如果上次进行了长按
	{
		key_long_state=0;		//清零长按标志
		key_value=0;				//清除长按松手误发的键值
	}
	return key_value;
}

//按键短按键值接收处理按键函数
//此函数 封装包含了 对应标志位的 监测作用
//此处 nrx++ 仅做测试用,nrx++可改变 数码管对应nr打印内容
void handle_keyreturn()
{
	u8 key_value;
	if(key_flag==1)
	{
		  key_flag=0;
			key_value=key_return();
			switch(key_value)
			{
				case 4:jm++; if(jm>=3) {jm=0;}   break;
				case 5:if(jm==2) { wei++; if(wei>=4) {wei=0;} } break;
				case 8:if(jm==2) write_ds1302_wei(1);    else{value++;if(value>=255){value=255;}}break;
				case 9:if(jm==2) write_ds1302_wei(0);    else{value--;if(value<=0)    {value=0;}}break;	
			}
	}
}

 

数码管的处理:

当时对数码管还是有点废了相当时间去研究的,这也是当时觉得比较显示稳定的写法了:

//数码管打印函数
//传入八个参数u8 nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8 进行打印
//一次性打印八位数码管
void smg_display(u8 nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8)
{
	u8 i;
	i=0;
	for(i=0;i<=8;i++)
	{
			inint_port(6);
			switch(i)
			{
				case 1:P0=0X01;inint_port(7);P0=smgZK[nr1];Delay250us();P0=0Xff;break;			
				case 2:P0=0X02;inint_port(7);P0=smgZK[nr2];Delay250us();P0=0Xff;break;
				case 3:P0=0X04;inint_port(7);P0=smgZK[nr3];Delay250us();P0=0Xff;break;
				case 4:P0=0X08;inint_port(7);P0=smgZK[nr4];Delay250us();P0=0Xff;break;
				case 5:P0=0X10;inint_port(7);P0=smgZK[nr5];Delay250us();P0=0Xff;break;
				case 6:P0=0X20;inint_port(7);P0=smgZK[nr6];Delay250us();P0=0Xff;break;				
				case 7:P0=0X40;inint_port(7);P0=smgZK[nr7];Delay250us();P0=0Xff;break;			
				case 8:P0=0X80;inint_port(7);P0=smgZK[nr8];Delay250us();P0=0Xff;break;
			}
	}
}

用数组思想操作LED:

当时为了操作LED方便,写了个数组来操作LED,想让那个灯亮灭进行与/或运算即可,而且还不会影响到其余LED的亮灭情况,算是小有聪明的设计了吧,哈哈......

//LED操作库数组:

//与之进行相与运算,以打开对应led
//1_L1 2_L2 3_L3 4_L4 5_L5 6_L6 7_L7 8_L8
u8 code LED_codeON[10]={0xff,0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
 
//与之进行相或运算,以关闭对应led
//1_L1 2_L2 3_L3 4_L4 5_L5 6_L6 7_L7 8_L8
u8 code LED_codeOFF[10]={0xff,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

 

 DS1302修改时间:

这个当时觉得挺麻烦的,主要是需要进行进制转换,谁又知道一个即将参加国赛的大二大学生,其实连进制转换都弄不明白呢......

自学之路总是要浪费相当的时间与精神的,毕竟之前都没学过单片机就参加这个比赛了.....


//ds1302时间选位修改函数
//修改所选择的位的时间值,不是写入 具体值,是将当前值 加1 或 减1
//传入参数介绍:
//add表示加减,1是加1  0是减1
//每次写要先关写保护,写完后要开写保护。
//该函数可能开机无法正常 使用,因为开机瞬间无读取值
void write_ds1302_wei(u8 add)
{
	char time_temp;          
	u8 time_temp_1;
	u8 time_temp_2;
	//从 read_t[] 对应时间位 读取到 10进制时间值
	time_temp=read_time[wei-1];
	//根据add数值决定当前位 加1 还是 减1
	if(add==1) {	
								time_temp++;								
								if(wei==1 && time_temp>=59) time_temp=59; 
								if(wei==2 && time_temp>=59)	time_temp=59;
								if(wei==3 && time_temp>=24)	time_temp=24;	
						}
  if(add==0) {	time_temp--;if(time_temp<=0)  time_temp=0;}
	//以下三行将time_temp重新转化为16进制值方便写操作的进行
	time_temp_1=time_temp/10;
	time_temp_2=time_temp%10;
	time_temp=time_temp_1*16+time_temp_2;
	//写操作
	Write_Ds1302_Byte(0x8e,0x00);                        //允许写入数据
  Write_Ds1302_Byte(Write_DS1302_adrr[wei-1],time_temp); //写入数据
  Write_Ds1302_Byte(0x8e,0x80);                        //禁止写入数据
}


//读取当前时钟数据
//读取的数据存储在 数组 Timer[] 
//一次读取三个位的值,秒、分、时
//并且用 read_t[] 数组进行转化为 十进制的值
void read_DS1302_Timer()
{
	u8 i;
	for(i=0;i<3;i++)
	{
		Timer[i]=Read_Ds1302_Byte(Read_DS1302_adrr[i]);
		read_time[i]=Timer[i]/16*10+Timer[i]%16;
	}
} 


//时间标志位 处理函数
void handle_Timer()
{
	if(DS1302_flag==1)
	{
		DS1302_flag=0;
		read_DS1302_Timer();
	}
}

 

 

完整测试工程下载:

https://download.csdn.net/download/qq_64257614/89342529?spm=1001.2014.3001.5503

 

  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NULL指向我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值