单片机很好玩4,实现交互,使用电脑控制单片机的执行动作(1)
发表于: 2019-01-07 22:57:40 | 已被阅读: 121 | 分类于: 单片机
第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!


...
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++;
}
}

我们常说的“通信协议”其实就是一系列约定,这么看来,这里说的“每次发送的命令都以换行符 '\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;
}

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);
}




将通信模块封装成库
这一步是简单的,只需要删去 my_uart.c 文件里的 main 函数,然后再新建一个头文件,如下图:

- 在电脑端输入 led twinkle 命令,LED 小灯会闪烁。
- 在电脑端输入 led breath 命令,LED 小灯会变成“呼吸灯”,并且在变暗阶段向电脑端输出“呼气”,在变亮阶段向电脑端输出“吸气”。
限于篇幅,下一节再介绍了。