蓝桥杯单片机模板

本文介绍了在蓝桥杯单片机竞赛中,如何通过模板快速编写底层代码,包括初始化LED、蜂鸣器和继电器,定义基本变量,处理按键和数码管显示,以及利用定时器0进行中断服务。详述了如何在MDK中创建工程、配置文件结构和编写关键函数,以提高编程效率。
摘要由CSDN通过智能技术生成

        在蓝桥杯单片机中,由于外设的引脚都固定,为了在最短的时间内编写准确且实用的底层,我们有必要熟练一种模板从而把更多时间精力放到题目功能的实现上。

目录

模板框架

初始化关闭LED、蜂鸣器、继电器

基本变量

LED

KEY

SEG

定时器0中断部分

主函数


新建工程

新建文件夹(考号命名)后在该目录下新建“Driver”和“User”子文件夹,在MDK中新建工程到“User”目录下,添加芯片包“IAP15F2K60S2”.

魔术棒“Output”勾选生产HEX文件,“C51”添加Driver路径。旁边的品字形进行文件名的修改(考试中“Target1”命名为考号)

在MDK新建两个页面均保存到“Driver”文件夹下,一个命名为init.c,一个为init.h,在品字形添加init.c,在init..c内右击选择“Inser'#include <STC15F2K60S2.H>'.将该头文件放到init..h,该.c文件引用init..h文件。

在模块对应的.c文件夹下引入自身的.h文件和“reg52.h”必要的话还有“intrins.h”

模块引脚定义在.c,下面就是函数定义。

.h文件一般仅放函数声明。 

在User下面右击添加“Add Nem Item to Group 'User'... ”选择C文件,命名为main.c,建立主函数。

  

模板框架

1.头文件声明。2.变量声明。3.键盘处理函数(Key)。4.信息显示函数(Seg)。5.其他显示函数(LED)。6.定时器初始化(Tim)。7.中断服务函数。8.主函数。

初始化关闭LED、蜂鸣器、继电器

Init.c文件仅含System_Init()函数,在程序一开始调用,起关闭LED、蜂鸣器、继电器作用。

void System_Init()
{
	P0 = 0xff;//关闭LED,LED共阳,置一熄灭
	P2 = P2 & 0x1f | 0x80;//打开锁存器:P2=100xxxxx,Y4=0,Y4C=1
	P2 &= 0x1f;//关闭锁存器:P2=000xxxxx,
	
	P0 = 0x00;//关闭继电器、蜂鸣器:低电平驱动,ULN2003反相,置零不工作
	P2 = P2 & 0x1f | 0xa0;//打开锁存器:P2=101xxxxx,Y5=0,Y5C=1
	P2 &= 0x1f;//关闭锁存器:P2=000xxxxx
}

主函数基本变量

unsigned char Key_Slow_Down;//按键减速变量
unsigned char Key_Val,Key_Old,Key_Up,Key_Down;//按键扫描读取变量
unsigned int Seg_Slow_Down;//数码管减速变量
unsigned char Seg_Pos;//数码管扫描变量
unsigned char Seg_Buf[8]={1,10,6,4,5,6,7,7};
//数码管段选数组,表示从左到右以此为:1 不显示 6 4 5 6 7 7 
unsigned char Seg_Point[8]={0,0,0,0,0,0,0,0};//数码管小数点数组,0表示不显示小数点,1表示显示

LED

Led.c包含点亮LED、蜂鸣器、继电器的函数。

/*addr表示点亮哪一个LED;enable为1使能,为0失能.例如Led_Disp(1,1)表示第二个LED点亮*/
void Led_Disp(unsigned char addr,enable)
{
	static unsigned char temp = 0x00;//局部静态变量,仅初始化一次。
	static unsigned char temp_old = 0xff;
	if(enable)
		temp |= 0x01 << addr;//第addr位置1
	else
		temp &= ~(0x01 << addr);//第addr位置0
	if(temp != temp_old)//只有temp值变化才进入下面的程序
	{
		P0 = ~temp;//addr位取反,对应位点亮或熄灭
		P2 = P2 & 0x1f | 0x80;//打开控制LED的锁存器
		P2 &= 0x1f;//关闭控制LED的锁存器
		temp_old = temp;//更新temp_old变量
	}
}

蜂鸣器、继电器函数:

void beep_disp(unsigned char flag)
{
	static unsigned char temp = 0xff;
	static unsigned char temp_old = 0x00;
	if(flag)
		temp |= 0x40;
	else
		temp &= ~0x40;
	if(temp != temp_old)
	{
		P0 = ~temp;
		P2 = P2 & 0x1f | 0xa0;
		P2 &= 0x1f;
		temp_old = temp;
	}
}

void relay_disp(unsigned char flag)
{
	static unsigned char temp = 0xff;
	static unsigned char temp_old = 0x00;
	if(flag)
		temp |= 0x10;
	else
		temp &= ~0x10;
	if(temp != temp_old)
	{
		P0 = ~temp;
		P2 = P2 & 0x1f | 0xa0;
		P2 &= 0x1f;
		temp_old = temp;
	}
}

若使用以上代码,继电器和蜂鸣器的使用会相互影响。修改如下(增加MOTOR引脚输出PWM):

static unsigned char temp_1 = 0x00;
static unsigned char temp_old_1 = 0xff;

void Beep(unsigned char flag)
{
	if(flag)
		temp_1 |= 0x40;
	else
		temp_1 &= ~0x40;
	if(temp_1 != temp_old_1)
	{
		P0 = temp_1;
		P2 = P2 & 0x1f | 0xa0;
		P2 &= 0x1f;
		temp_old_1 = temp_1;		
	}
}

void Relay(unsigned char flag)
{
	if(flag)
		temp_1 |= 0x10;
	else
		temp_1 &= ~0x10;
	if(temp_1 != temp_old_1)
	{
		P0 = temp_1;
		P2 = P2 & 0x1f | 0xa0;
		P2 &= 0x1f;
		temp_old_1 = temp_1;		
	}	
}
void pulse(unsigned char flag)
{
	if(flag)
		temp_0 |= 0x20;
	else
		temp_0 &= ~0x20;
	if(temp_0 != temp_old_0)
	{
		P0 = temp_0;
		P2 = P2 & 0x1f | 0xa0;
		P2 &= 0x1f;
		temp_old_0 = temp_0;
	}
}

在main.c中:继电器、蜂鸣器驱动函数在定时器0中每1ms扫描一次,可在其他代码块中修改标志量控制模块状态

bit Relay_Flag;//继电器使能标志位
bit Beep_Enable_Flag;//闹钟功能使能标志位

void Timer0Server() interrupt 1
{  
	if(++Key_Slow_Down == 10) Key_Slow_Down = 0;//键盘减速专用
	if(++Seg_Slow_Down == 500) Seg_Slow_Down = 0;//数码管减速专用
	if(++Seg_Pos == 8) Seg_Pos = 0;//数码管显示专用
	Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
	Led_Disp(Seg_Pos,ucLed[Seg_Pos]);
	Relay(Relay_Flag);
	Beep(Relay_Flag & Beep_Enable_Flag);	
}

KEY

Key.c包含按键扫描函数

unsigned char Key_Read()
{
	unsigned char temp = 0;
	if(P33 == 0) temp = 4;
	if(P32 == 0) temp = 5;
	if(P31 == 0) temp = 6;
	if(P30 == 0) temp = 7;
	return temp;
}

键盘读取处理函数在main.c中

void Key_Proc()
{
	if(Proc_Flag != 1) return;
	Proc_Flag = 0;//键盘减速程序

	Key_Val = Key_Read();//读取按键的值
	Key_Down = Key_Val & (Key_Old ^ Key_Val);//检测按键的下降沿
	Key_Up = ~Key_Val & (Key_Old ^ Key_Val);//检测按键的上升沿
	Key_Old = Key_Val;//更新按键旧值
}


switch(Key_Down)//如果下降沿有效,触发按键操作
	{
		case 4://显示界面切换
			
		break;
		case 5://输出模式切换
			
		break;
		case 6://LED 指示灯功能控制
			
		break;
		case 7://数码管显示功能控制
			
		break;
	}
}

//其他使用
if(Key_Down != 0) //按下任意按键
if(Key_Old == 4) //长按“4按键”

SEG

Seg.c包含数码管写入函数

/*共阳极段选,从0xc0到0x90依次表示0~9,0xff表示全部熄灭,0x88表示小数点*/
unsigned char seg_dula[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff,0x88};
/*段选*/
unsigned char seg_wela[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
/*数码管写入函数*/
void Seg_Disp(unsigned char wela,dula,point)
{
	P0 = 0xff;//消隐,段码全部熄灭
	P2 = P2 & 0x1f | 0xe0;//打开控制段选的锁存器:P2=111xxxxx,Y7=0
	P2 &= 0x1f;//关闭控制段选的锁存器

	P0 = seg_wela[wela];//单个数码管位码
	P2 = P2 & 0x1f | 0xc0;打开控制位选的锁存器:P2=110xxxxx,Y6=0
	P2 &= 0x1f;//关闭控制位选的锁存器
	
	P0 = seg_dula[dula];//单个数码管段码
	if(point)//判断当前位是否需要加小数点
		P0 &= 0x7f;//需要显示小数点,最高位dp置0并不影响其他位,01111111&xxxxxxxx
	P2 = P2 & 0x1f | 0xe0;///打开控制段选的锁存器:P2=111xxxxx,Y7=0
	P2 &= 0x1f;	//关闭控制段选的锁存器
}

数码管显示函数在main.c

在给Seg_Buf[]赋值时:char型变量、数组直接“=”赋值;如果是float型变量,整数部分进行char型强制转换,小数部分进行int型强制转换。

......

千位赋值=变量/1000%10

百位赋值=变量/100%10

十位赋值=变量/10%10

个位赋值=变量%10

两位小数十分位赋值=(变量*100)/10%10

两位小数百分位赋值=(变量*100)%10

void Seg_Proc()
{
	if(Seg_Slow_Down) return;
	Seg_Slow_Down = 1;//数码管减速程序
  //AD、DS18B20读取在该处进行。如:Voltage = Ad_Read(0x03) / 51.0;//实时获取电压值
switch(Seg_Disp_Mode)
	{
		case 0://温度显示界面
			Seg_Buf[0] = 11;//标识符C
			Seg_Buf[4] = (unsigned char)Temperature / 10 % 10;
			Seg_Buf[5] = (unsigned char)Temperature % 10;
			Seg_Buf[6] = (unsigned int)(Temperature * 100) / 10 % 10;
			Seg_Buf[7] = (unsigned int)(Temperature * 100) % 10;
			Seg_Point[5] = 1;//使能小数点
		break;
		case 1://参数设置界面
			Seg_Buf[0] = 12;//标识符P
			Seg_Buf[4] = Seg_Buf[5] = 10;//熄灭第五、第六个数码管
			Seg_Buf[6] = Temperature_Params / 10 % 10;
			Seg_Buf[7] = Temperature_Params % 10;
			Seg_Point[5] = 0;//关闭小数点
		break;

}

注:拿到题目首先观察数码管显示界面,如果某个位或小数点之前点亮,切换界面后不点亮,在后面的界面显示程序中要熄灭该位。

高位熄灭

如果某个待显示变量变化幅度大(如NE555的输出频率变化为几十到几千),高位需要熄灭使用while语句即可实现:

unsigned char i = 3;//高位熄灭专用变量
while(Seg_Buf[i] == 0) //数码管高位为0,进入下面的程序
	{
		Seg_Buf[i] = 10;//数码管高位熄灭
		if(++i == 7) break; //保证最低位不熄灭 避免程序卡死
	}

以上程序的表示监视数码管第三位到第六位(第七位若为0,表示该变量此时为0,可显示):i=3若万位为0,进入语句熄灭该位...当i=6若十位为0,进入语句该位熄灭之后i自增为7跳出语句执行后续程序,所以最多监视第六位

定时器0中断部分

/* 定时器0中断初始化函数 */
void Timer0Init(void)		//12.000MHz晶振,定时1ms
{
	AUXR &= 0x7F;		//设置定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0x18;		//设置定时器初值
	TH0 = 0xFC;		//设置定时器初值
	TF0 = 0;		//清除TF0标志位
	TR0 = 1;		//定时器0开始计时
	ET0 = 1;    //定时器中断0打开
	EA = 1;     //总中断打开
}

/* 定时器0中断服务函数*/
void Timer0Server() interrupt 1
{  
	if(++Key_Slow_Down == 10) Key_Slow_Down = 0;//10ms按键读取扫描一次
	if(++Seg_Slow_Down == 500) Seg_Slow_Down = 0;//500ms数码管显示扫描一次
	if(++Uart_Slow_Down == 200) Uart_Slow_Down = 0;//200ms串口扫描一次
	if(++Seg_Pos == 8) Seg_Pos = 0;//数码管0-7位循环扫描
	Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);//数码管显示
	Led_Disp(Seg_Pos,ucLed[Seg_Pos]);//其他显示函数
}

主函数

void main()
{
	Sys_Init();
	Timer0Init();//初始化
	while(1)
	{
		Key_Proc();//三大函数循环扫描
		Seg_Proc();
		Led_Proc();
	}
}

这段代码看起来是针对单片机中的矩阵按键扫描函数。以下是一些可以改进的建议: 1. 函数名:函数名可以更加规范,建议使用大写字母和下划线的命名方式,如:CHECK_KEY、KEY_SCAN、BUTTON_DOWN。 2. 参数:函数没有参数,可以添加参数以支持更多的功能。 3. 变量名:变量名应该具有描述性,能够表达变量的含义。 4. 按键扫描:建议添加按键消抖功能,以避免按键不稳定的现象。 5. 代码风格:建议对代码进行缩进,以提高可读性。 下面是改进后的代码: ```c typedef enum { KEY_NONE = 0, // 无按键按下 KEY_UP, KEY_7, KEY_8, KEY_9, KEY_DOWN, KEY_4, KEY_5, KEY_6, KEY_LEFT, KEY_1, KEY_2, KEY_3, KEY_RIGHT, KEY_D, KEY_0, KEY_E, KEY_F1, KEY_F2, KEY_F3, KEY_F4 } KEY_Status; /** * @brief 获取按键状态 * @param key_map 按键映射表 * @param row 行数 * @param col 列数 * @return KEY_Status 按键状态 */ KEY_Status get_key_status(unsigned char* key_map, unsigned char row, unsigned char col) { return (KEY_Status)key_map[row * 4 + col]; } /** * @brief 矩阵按键扫描函数 * @param key_map 按键映射表 * @param mode 0:支持单次按键功能,1:支持连续按键功能 * @return KEY_Status 按键状态 */ KEY_Status key_scan(unsigned char* key_map, u8 mode) { static u8 key_up = 1; // 按键按松开标志 if (mode) key_up = 1; // 支持连按 unsigned char row, col; unsigned int tmp1, tmp2, key_dout; tmp1 = 0x0800; for (row = 0; row < 4; row++) { key_dout = 0x0F00; key_dout -= tmp1; GPIOD->ODR = ((GPIOD->ODR & 0xF0FF) | key_dout); tmp1 >>= 1; if ((GPIO_ReadInputData(GPIOD) & 0xF000) < 0xF000) { tmp2 = 0x1000; for (col = 0; col < 4; col++) { if (0x00 == (GPIO_ReadInputData(GPIOD) & tmp2)) { KEY_Status key_status = get_key_status(key_map, row, col); if (key_up) { delay_ms(10); // 去抖动 key_up = 0; return key_status; } else { return KEY_NONE; } } tmp2 <<= 1; } } } if (KEY_UP == get_key_status(key_map, 0, 0)) { if (key_up) { delay_ms(10); // 去抖动 key_up = 0; return KEY_UP; } else { return KEY_NONE; } } else if ((KEY_7 == get_key_status(key_map, 0, 1)) && (KEY_8 == get_key_status(key_map, 0, 2)) && (KEY_9 == get_key_status(key_map, 0, 3))) { if (key_up) { delay_ms(10); // 去抖动 key_up = 0; return KEY_UP; } else { return KEY_NONE; } } else { key_up = 1; return KEY_NONE; } } /** * @brief 按键事件处理函数 */ void key_event(void) { // TODO: 处理按键事件 } /** * @brief 按键检测函数 * @param key_map 按键映射表 * @param mode 0:支持单次按键功能,1:支持连续按键功能 */ void check_key(unsigned char* key_map, u8 mode) { KEY_Status key_status = key_scan(key_map, mode); if (key_status != KEY_NONE) { key_event(); } } /** * @brief 按钮按下函数 * @param key_map 按键映射表 * @param mode 0:支持单次按键功能,1:支持连续按键功能 */ void button_down(unsigned char* key_map, u8 mode) { check_key(key_map, mode); } ``` 改进后的代码对函数名、参数、返回值等进行了规范化,同时添加了按键消抖功能,提高了按键扫描的稳定性。代码逻辑也更加清晰,易于阅读和维护。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值