单片机很好玩4,实现交互,使用电脑控制单片机的执行动作(1)

第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 小灯会变成“呼吸灯”,并且在变暗阶段向电脑端输出“呼气”,在变亮阶段向电脑端输出“吸气”。

限于篇幅,下一节再介绍了。

阅读更多:   单片机
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif