第2节介绍了如何用单片机控制 LED 小灯闪烁起来,在此基础上,又在上一节讨论了如何制作“呼吸灯”。
缺乏交互的单片机
不过,这两节制作的小灯时,我们把使用 C语言编写的控制程序烧写到单片机后,就无法再控制 LED 小灯了,也就是说,“只能看不能动”,交互性比较差。接下来两节,将介绍一种交互方法,目的是烧写 C语言控制程序到单片机后,仍然能够从外界控制 LED 小灯。
既然想实现交互,单片机就得能捕捉外界的变化,最简单的方法就是通过按键。不过这里不打算使用按键,而是通过“输入命令”的方式控制 LED 小灯。
将单片机内部的信息,printf 传递给电脑
一般的软件开发中,如果想查看某个变量的值,或者想输出一句提示信息,直接使用 printf 将信息输出到屏幕即可。遇到分支流程需要外界选择时,我们也只需按一下键盘就可以。但是对于 51 单片机来说,怎么与之交互呢?我的这款 51 单片机可既没有配屏幕,也没有配键盘:
其实 printf 只是将信息输出到终端,终端不一定必须是屏幕,也可以是其他字符设备,比如一般单片机都会有的串口外设。所以,没有屏幕的 51 单片机也能够使用 printf 函数,只需要将其输出口重定向到串口即可。
重定向的工作 keil4 已经做好了,剩下需要我们做的工作仅仅只是配置一下单片机的串口寄存器而已,这项工作非常简单,C语言代码可以如下写,请看:
void init_uart(unsigned int baud)
{
SCON = 0x5a;
TMOD = 0x20;
TH1 = TL1 = -(FOSC/12/32/baud);
TR1 = 1;
ES = 1;
EA = 1;
}
其中 FOSC 是单片机的晶振频率,我使用的单片机频率是 11.0592MHz。现在包含一下“stdio.h”头文件就可以使用 printf 函数了:
#include "reg51.h"
#include "stdio.h"
void main()
{
init_uart(9600);
printf("hello world, num: %d\n", 98);
while(1);
}
编译程序并烧写到单片机,打开电脑中的串口调试软件,发现字符串都正确,但是C语言程序明明想传递的是 98,电脑端的串口工具显示的数字却是 25088!
这个问题估计是因为我使用的单片机是 8 位的原因。解决问题的方法,其实 keil 帮助文件里也提了:
使用格式符 “%bd”代替“%d”即可,或者将要输出的数字强转为 int 类型也可以:
...
printf("hello world, num: %bd\n", 98);
printf("hello world, num: %d\n", (int)98);
...
再编译烧写,发现输出正常了。
将信息传递给单片机
以后可以使用 printf 函数将单片机内部的信息传递给电脑了,但是既然是“交互”,就应该还能把电脑端的信息传递给单片机才行。那么,怎样把电脑端的信息传给单片机呢?其实还可以借助串口。请看 init_uart() 函数的代码,应该能发现C语言程序已经把串口中断打开了,所以可以如下写中断处理程序,请看:
#define MAX_CMD_LEN 32
static bit is_cmd_ready = 0;
static char cmd[MAX_CMD_LEN] = {0};
static unsigned char cmd_len = 0;
void interrupt_uart() interrupt 4
{
static unsigned char i = 0;
if(RI){
RI = 0;
cmd[i%MAX_CMD_LEN] = SBUF;
if(cmd[i%MAX_CMD_LEN]=='\n'){
cmd_len = i;
i = 0;
is_cmd_ready = 1;
}else
i++;
}
}
当电脑通过串口发送数据给单片机时,串口数据会被逐字节放入 SBUF,而 C语言程序则会立刻进入 interrupt_uart() 函数,这时可以将串口数据用全局变量 cmd 接收。我们与电脑端约定,每次发送的命令都以换行符 '\n' 结束,所以当接收到 '\n' 时,就可以把 is_cmd_ready 标志位置 1 了。
我们常说的“通信协议”其实就是一系列约定,这么看来,这里说的“每次发送的命令都以换行符 '\n' 结束”其实也属于一种“通信协议”。
也就是说,电脑端通过串口发送的命令都会被自动放入 cmd 里,因此我们可以如下定义接收命令的 C语言函数:
unsigned char get_uart_cmd(char* oCmd)
{
unsigned char i = 0;
while(!is_cmd_ready);
for(i=0;i<cmd_len;i++)
oCmd[i] = cmd[i];
is_cmd_ready = 0;
return cmd_len;
}
这个函数会阻塞等待 is_cmd_ready 标志位,收到电脑端发送来的命令后,将命令通过 oCmd 传出,返回接收到的命令长度。现在可以如下写测试程序了:
void main()
{
char mycmd[32] = {0};
init_uart(9600);
printf("enter a num...\n");
get_uart_cmd(mycmd);
printf("cmd: %d\n", (int)(mycmd[0]));
printf("enter a string...\n");
get_uart_cmd(mycmd);
printf("cmd: %s\n", mycmd);
while(1);
}
编译C语言程序并烧写,会在电脑端的串口调试工具中发现如下提示信息:
因为单片机先需要一个数字,且命令需要换行符结尾,所以勾选了下面的选项,点击发送,发现终端输出:
104正好等于十六进制的 68,接着再输入一段字符串:
一切与预期一致。
将通信模块封装成库
这一步是简单的,只需要删去 my_uart.c 文件里的 main 函数,然后再新建一个头文件,如下图:
以后在其他功能开发中,如果需要与电脑端交互,直接把这两个文件加入工程就可以了。接下来,将使用本节介绍的交互模块,目的是能够通过电脑控制单片机,决定单片机的动作。例如:
* 在电脑端输入 led twinkle 命令,LED 小灯会闪烁。
* 在电脑端输入 led breath 命令,LED 小灯会变成“呼吸灯”,并且在变暗阶段向电脑端输出“呼气”,在变亮阶段向电脑端输出“吸气”。
限于篇幅,下一节再介绍了。