蓝桥杯单片机组备赛指南请查看这篇文章:戳此跳转蓝桥杯备赛指南文章
本文章针对蓝桥杯-单片机组比赛开发板所写,代码可直接在比赛开发板上使用。
型号:国信天长4T开发板(绿板),芯片:IAP15F2K61S2
(使用国信天长蓝板也可以完美兼容,与绿板几乎无差别)
1. 代码目的
代码一:通过矩阵键盘输入数字,并在数码管上显示,当按下S16时确认发送数字到上位机
,上位机发送数据,单片机接收之后进行平方运算并返回。
代码二:开机初始化,自动关闭蜂鸣器和继电器,并发送初始化信息到上位机,上位机发送亮灯指令,单片机接收运行指令并进行反馈上位机。指令格式:a模式前四位,b模式后四位,1开灯,0关灯,c模式提示系统正在运行
2. 通信基础知识
大分类如下图:
相关名词解释如下:
并行通信:数据的每一位同时发送,且每个位都占有一根传输线;
串行通信:数据可以一位接一位地按照顺序,只经过一根传输线传输;(又分为SPI、IIC、UART等)
单工通信:数据只能单向传输,且只由固定的主机传向从机,不可交换;
半双工通信:数据在某时刻可以从一方发送,另外一方接收,但不能同时发送和接收;(RS485)
全双工通信:在同一时刻,两个设备之间可以同时进行发送和接收数据。
同步通信:双方有一根数据线、一根时钟线,采用相同的时钟信号进行数据交换的驱动;
异步通信:双方只有一根数据线,采用特定的帧格式约定通信的规范;
比赛采用异步串行通信的方式。
3. 串口通信基本原理
3.1 什么是上下位机的通信
一般来说,我们认为比赛用开发板就是下位机,我们的电脑就是上位机。由于采用异步串行通信方式,因此我们既可以从上位机向下位机发送数据,又可以在上位机接收下位机传来的数据,从未实现通信的效果。
近年来的省体考串口通信不多,但往年偶尔考过。考题倾向于某种控制系统,即我们用电脑向单片机发送某种数据,单片机就产生对应的响应,并且单片机向电脑传回相关的数据,以此对串口编程进行考察。在了解串口通信的简单原理之后,可以将小蜜蜂老师的代码段背下来,比赛时直接使用。
3.2 上下位机的通信设置
对于上位机,我们需要打开烧录软件STC-ISP,并点击串口助手,如下图:
红色框标注的部分为,需要我们留意并设置正确的部位。
3.3 通信的相关寄存器
在进行串行通信时,为了保证传输的实时性,我们采用中断的方式进行。设置定时计数器1,当产生一次溢出中断时,单片机就接收或发送一次数据。因此我们依旧会用到定时/计数器中断相关的寄存器。
下面简要介绍IE、IP、TMOD、TCON、THx寄存器:详细教程请戳此查看, 重点介绍SCON、SBUF、AUXR寄存器
两级中断允许控制:IE(interrupt enable)
|----|---|-----|----|-----|-----|-----|-----|
| EA | - | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
IE中各位的功能如下:
EA---中断总开关控制位,EA=1,所有的中断请求开放。
ES---串行口中断允许位。
ET1---定时器/计数器T1溢出中断允许位。
EX1---外部中断1中断允许位。
ET0---定时器/计数器T0的溢出中断允许位。
EX0---外部中断0中断允许位。
两级优先级控制:IP(interrupt priority)
|---|---|-----|----|-----|-----|-----|-----|
| - | - | PT2 | PS | PT1 | PX1 | PT0 | PX0 |
中断优先级寄存器IP各位含义:
PS---串行口中断优先级控制位,1---高级;0---低级。
PT1---T1中断优先级控制位,1---高级;0---低级。
PX1---外部中断1中断优先级控制位,1---高级;0---低级。
PT0---T0中断优先级控制位,1---高级;0---低级。
PX0---外部中断0中断优先级控制位,1---高级;0---低级。
该寄存器允许位寻址,本题中采用默认优先级,因此不用管这个寄存器。
特殊功能寄存器:TCON(timer controller)
|-----|-----|-----|-----|-----|-----|-----|-----|
| TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
TCON寄存器中与中断系统有关各标志位功能如下:
TF1---定时器/计数器T1的溢出中断请求标志位。
TF0---定时器/计数器T0溢出中断请求标志位似。
IE1---外部中断请求1中断请求标志位。
IE0---外部中断请求0中断请求标志位,与IE1类似。
IT1---选择外中断1请求方式。0--电平触发方式,1--跳沿触发方式。
IT0---选择外中断0请求方式。0--电平触发方式,1--跳沿触发方式。
特殊方式寄存器:TMOD(timer mode)
|------|-----|----|----|------|-----|----|----|
| GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
| <---- 定时器1(T1) ----> |||| <---- 定时器0(T0) ----> ||||
GATE: 用于控制定时器启动是否受外部中断源的影响
当GATE=0时,TR0或TR1等于1,可以启动T0或T1定时/计数器工作。
当GATE=1时,TR0或TR1等于1,外部中断引脚INT0/1为高电平,才启动工作。
注意:GATE=0表示控制定时器启动不受外部中断源的影响
C/T=0为定时模式,C/T=1为计数模式。
M1 M0工作方式选择位,两位一起看:
|-------|------|------------------------|
| M1 M0 | 工作方式 | 工作说明 |
| 0 0 | 方式0 | 13位定时/计数器,最大值8192 |
| 0 1 | 方式1 | 16位定时/计数器,最大值65535 |
| 1 0 | 方式2 | 8位自动重装定时/计数器,最大值255 |
| 1 1 | 方式3 | T1分成两个独立的定时/计数器,T1停止计数 |
初值寄存器:THx TLx
注意:当计算式确定之后,要把计算结果替换进赋值表达式,减少资源占用。
串口通信寄存器:SCON (Serial Control Register)
|-----|-----|-----|-----|-----|-----|----|----|
| SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |
SM0、SM1用于选择工作方式:
|---------|------|---------|-----------------|
| SM0,SM1 | 工作方式 | 功能描述 | 波特率 |
| 0 0 | 方式0 | 8位移位寄存器 | Fosc/12 |
| 0 1 | 方式1 | 10位UART | 可变 |
| 1 0 | 方式2 | 11位UART | Fosc/64或Fosc/32 |
| 1 1 | 方式3 | 11位UART | 可变 |一般我们只用方式1,因此牢记01设置就好。
SM2:多机控制位。方式0时SM2=0;方式1且SM2=1时只有接收到有效位才使RI置1;方式2或3且SM2=1且接收到第九位数据RB8=0时才使RI置1
REN:串行接收允许位。REN=1时允许接收,REN=0时禁止接收
TB8:存放发送数据的第9位。在方式2或3中存放要发送数据的第9位
RB8:存放接收数据的第9位。在方式2或3中存放要接收数据的第9位
TI:发送中断标志位。数据发送结束时,标志位会自动置1,需要通过程序将其清0
RI:接收中断标志位,数据接收结束时,标志位会自动置1,需要通过程序将其清0
该寄存器允许位选址,在实际使用中,主要是利用定时计数器1产生波特率为9600(0xfd)的中断信号,然后搭配串口进行发送或接收数据。
缓冲寄存器:SBUF (serial buffer)
在物理上,单片机内部有两个SBUF,一个叫接收SBUF,用于接收上位机发送的数据;另一个叫发送SBUF,用于将传入的数据发送到上位机。名字都叫SBUF,在编程时根据语句不同产生不同的效果。
接收数据时,形式为:变量名=SBUF;当RI=1时,单片机会产生一次中断,此时接收SBUF中存储了上位机发送的数据,通过赋值语句将内容提取到我们的变量中。
发送数据时,形式为:SBUF=变量名;让TI=1时,表示单片机发送完毕,我们将变量中的值传入SBUF,SBUF成功发送到了上位机。
辅助寄存器:AUXR
这个寄存器不需要我们进行单独设置,
如果你使用的头文件是:#include <IAP.......h>,则可以忽略掉这个步骤
如果你使用的头文件是:#include <reg52.h>,则需要添加。在代码的最开始定义:sfr AUXR = 0x8e;然后在定时计数器初始化代码中加一句:AUXR = 0x00;
4. 串口通信相关函数
4.1 串口初始化函数
编程时,主要是先设置定时计数器产生对应的波特率,然后设置SCON寄存器确定串口工作方式,然后将辅助寄存器赋值为0x00,最后正常打开IE、TCON等寄存器的相关位。
4.2 数据接收函数
使用中断的方式进行接收数据:即每收到一次数据,就产生一次优先级为4的串口中断
当产生中断时,单片机进入此函数。首先看RI是否等于1,然后手动将RI清零,最后通过赋值语句将SBUF中的数据读入我们定义的变量中。
4.3 数据发送函数
采用普通函数进行数据发送:因为当我们需要发送数据时,是由我们决定的,因此可以不写成中断函数。
当我们需要发送数据时,直接将需要发送的数据通过赋值语句写入SBUF中,单片机就会开始发送,但因为我们没有使用中断方式因此发送时间会有些长,在数据未发完之前我们需要让程序等待,等发送完数据再继续运行。因此写了一个while的空语句,直到TI不等于0时发送结束,空语句停止,此时我们再手动将TI清零。
4.4 字符串发送函数
C语言中,字符串的本质是一个字符数组,因此我们使用指针会更方便一些。
5. 小蜜蜂课程代码参考
代码一:通过矩阵键盘输入数字,并在数码管上显示,当按下S16时确认发送数字到上位机
,上位机发送数据,单片机接收之后进行平方运算并返回。
cpp
//使用UART通信进行单片机与上位机的通信
//通过矩阵键盘输入数字,并在数码管上显示,当按下S16时确认发送数字到上位机
//上位机发送数据,单片机接收之后进行平方运算并返回
#include < REG52.h >
#include < intrins.h >
unsigned char code SMG_duanma [18]=
{ 0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 ,
0x80 , 0x90 , 0x88 , 0x80 , 0xc6 , 0xc0 , 0x86 , 0x8e ,
0xbf , 0x7f };
sfr P4 = 0xc0;
sfr AUXR = 0x8e;
sbit L1 = P3^0;
sbit L2 = P3^1;
sbit L3 = P3^2;
sbit L4 = P3^3;
sbit C1 = P4^4;
sbit C2 = P4^2;
sbit C3 = P3^5;
sbit C4 = P3^4;
void select_HC573 ( unsigned char channal )
{
switch ( channal )
{
case 4:
P2 = ( P2 & 0x1f ) | 0x80;
break;
case 5:
P2 = ( P2 & 0x1f ) | 0xa0;
break;
case 6:
P2 = ( P2 & 0x1f ) | 0xc0;
break;
case 7:
P2 = ( P2 & 0x1f ) | 0xe0;
break;
}
}
void Input_SMG ( unsigned char pos_SMG , unsigned char value_SMG )
{
select_HC573 ( 6 );
P0 = 0x01 << pos_SMG;
select_HC573 ( 7 );
P0 = value_SMG;
}
void Delay ( unsigned char value_delay )
{
while ( value_delay -- )
{
unsigned char i = 22;
unsigned char j = 128;
_nop_ ();
_nop_ ();
do
{
while ( --j );
}while ( --i );
}
}
unsigned char state_key = 0;
void scan_keys ( )
{
//L1
L1 = 0;
C1 = C2 = C3 = C4 = 1;
L2 = L3 = L4 =1;
if ( C1 == 0 )
{
Delay ( 3 );
if ( C1 == 0 )
{
while ( C1 == 0 );
state_key = 7;
}
else;
}
else if ( C2 == 0 )
{
Delay ( 3 );
if ( C2 == 0 )
{
while ( C2 == 0 );
state_key = 11;
}
else;
}
else if ( C3 == 0 )
{
Delay ( 3 );
if ( C3 == 0 )
{
while ( C3 == 0 );
state_key = 15;
}
else;
}
else if ( C4 == 0 )
{
Delay ( 3 );
if ( C4 == 0 )
{
while ( C4 == 0 );
state_key = 19;
}
else;
}
//L2
L2 = 0;
C1 = C2 = C3 = C4 = 1;
L1 = L3 = L4 =1;
if ( C1 == 0 )
{
Delay ( 3 );
if ( C1 == 0 )
{
while ( C1 == 0 );
state_key = 6;
}
else;
}
else if ( C2 == 0 )
{
Delay ( 3 );
if ( C2 == 0 )
{
while ( C2 == 0 );
state_key = 10;
}
else;
}
else if ( C3 == 0 )
{
Delay ( 3 );
if ( C3 == 0 )
{
while ( C3 == 0 );
state_key = 14;
}
else;
}
else if ( C4 == 0 )
{
Delay ( 3 );
if ( C4 == 0 )
{
while ( C4 == 0 );
state_key = 18;
}
else;
}
//L3
L3 = 0;
C1 = C2 = C3 = C4 = 1;
L2 = L1 = L4 =1;
if ( C1 == 0 )
{
Delay ( 3 );
if ( C1 == 0 )
{
while ( C1 == 0 );
state_key = 5;
}
else;
}
else if ( C2 == 0 )
{
Delay ( 3 );
if ( C2 == 0 )
{
while ( C2 == 0 );
state_key = 9;
}
else;
}
else if ( C3 == 0 )
{
Delay ( 3 );
if ( C3 == 0 )
{
while ( C3 == 0 );
state_key = 13;
}
else;
}
else if ( C4 == 0 )
{
Delay ( 3 );
if ( C4 == 0 )
{
while ( C4 == 0 );
state_key = 17;
}
else;
}
//L4
L4 = 0;
C1 = C2 = C3 = C4 = 1;
L2 = L3 = L1 =1;
if ( C1 == 0 )
{
Delay ( 3 );
if ( C1 == 0 )
{
while ( C1 == 0 );
state_key = 4;
}
else;
}
else if ( C2 == 0 )
{
Delay ( 3 );
if ( C2 == 0 )
{
while ( C2 == 0 );
state_key = 8;
}
else;
}
else if ( C3 == 0 )
{
Delay ( 3 );
if ( C3 == 0 )
{
while ( C3 == 0 );
state_key = 12;
}
else;
}
else if ( C4 == 0 )
{
Delay ( 3 );
if ( C4 == 0 )
{
while ( C4 == 0 );
state_key = 16;
}
else;
}
}
//===================================================
void Init_Uart ()
{
TMOD = 0x20;
TH1 = 0xfd;
TL1 = 0xfd;
SCON = 0x50;
AUXR = 0x00;
ES = 1;
EA = 1;
TR1 = 1;
}
//接收信息采用中断,发送消息采用查询
unsigned char ri_uart_value;
void ri_UART () interrupt 4
{
if ( RI == 1 )
{
RI = 0;
ri_uart_value = SBUF;
}
}
void ti_UART ( unsigned char ti_uart_value )
{
SBUF = ti_uart_value;
while ( TI == 0 );
TI = 0;
}
//======================================================
unsigned char tmp_state_key = 0;
unsigned char tmp_ri_value = 0;
void Uartrunning ()
{
if ( tmp_state_key != state_key )
{
ti_UART ( state_key );
tmp_state_key = state_key;
}
else if ( ri_uart_value != tmp_ri_value )
{
tmp_ri_value = ri_uart_value;
state_key = tmp_ri_value;
}
}
void main ()
{
Init_Uart ();
while ( 1 )
{
Uartrunning ();
scan_keys ();
Input_SMG ( 0 , SMG_duanma [state_key/10] );
Delay ( 3 );
Input_SMG ( 1 , SMG_duanma [state_key%10] );
Delay ( 3 );
}
}
代码二:开机初始化,自动关闭蜂鸣器和继电器,并发送初始化信息到上位机,上位机发送亮灯指令,单片机接收运行指令并进行反馈上位机。指令格式:a模式前四位,b模式后四位,1开灯,0关灯,c模式提示系统正在运行
cpp
//建立通信,使得上位机和单片机进行数据交换,并实现上位机控制单片机灯光亮灭
//开机初始化,自动关闭蜂鸣器和继电器,并发送初始化信息到上位机
//上位机发送亮灯指令,单片机接收运行指令并进行反馈上位机。指令格式:a模式前四位,b模式后四位,1开灯,0关灯,c模式提示系统正在运行
#include < reg52.h >
sfr AUXR = 0x8e;
void select_HC573 ( unsigned char channal )
{
switch ( channal )
{
case 4:
P2 = ( P2 & 0x1f ) | 0x80;
break;
case 5:
P2 = ( P2 & 0x1f ) | 0xa0;
break;
case 6:
P2 = ( P2 & 0x1f ) | 0xc0;
break;
case 7:
P2 = ( P2 & 0x1f ) | 0xe0;
break;
}
}
void Init_Buzz ()
{
select_HC573 ( 5 );
P0 = 0x00;
}
//==================================================
void Init_UART ( )
{
TMOD = 0x20;
TH1 = 0xfd;
TL1 = 0xfd;
SCON = 0x50;
AUXR = 0x00;
ES = 1;
EA = 1;
TR1 = 1;
}
unsigned char uart_value;
void Service_UART () interrupt 4
{
if ( RI == 1 )
{
RI = 0;
uart_value = SBUF;
}
}
void Send_UART ( unsigned char value_uart )
{
SBUF = value_uart;
while ( TI == 0 );
TI = 0;
}
void Send_String ( unsigned char *string )
{
while ( *string != '\0' )
{
Send_UART ( *string ++ );
}
}
//========================================================
void light_led ( unsigned char value_led )
{
select_HC573 ( 4 );
P0 = value_led;
}
void UART_Commandrunning ( )
{
unsigned char command,value_led;
command = uart_value;
if ( command != 0x00 )
{
switch ( command & 0xf0 )
{
case 0xa0:
value_led = ~(( command & 0x0f ) << 4 ) ;
light_led ( value_led );
break;
case 0xb0:
value_led = ~( command & 0x0f ) ;
light_led ( value_led );
break;
case 0xc0:
Send_String ( "The system is runing ! \r\n" );
break;
}
}
}
void main ()
{
Init_UART ();
Init_Buzz ();
Send_String ( "The system is wake up \r\n" );
while ( 1 )
{
UART_Commandrunning ();
}
}
6. 编程思路重述
实际上对于串口通信实现上位机与单片机通信的设置有很多,但是在比赛中,我们只使用定时计数器1产生波特率,使用串口工作方式1进行通信,便已经足够。
1.设置TMOD将定时计数器1处于8位自动重装工作方式,初值寄存器值为0xfd产生9600bps的波特率,辅助寄存器赋值为0x00;
2.设置串口中断寄存器SCON,使其工作在方式1
3.正确编写接收数据函数、发送数据函数、发送字符串数据函数