實現電腦和單片機通信(單片機如何與電腦相連)
第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
這個函數會阻塞等待 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 小燈會變成“呼吸燈”,并且在變暗階段向電腦端輸出“呼氣”,在變亮階段向電腦端輸出“吸氣”。
限于篇幅,下一節再介紹了。