AVR單片機教程——串口發送

本文隸屬於<a href="https://www.cnblogs.com/jerry-fuyi/p/avr_tutorial.html" target="_blank">AVR單片機教程</a>系列。

 

到目前爲止,我們的開發板只能處理很小量的數據:讀取幾個引腳電平,輸出幾個LED,頂多用數碼管顯示一個兩位數字。至於輸入一個指令、輸出一條調試信息,甚至用scanfprintf來輸入輸出,在已經接觸過的這些器件上是難以想象的。而本講“串口發送”與下一講“串口接收”,將打開這一扇大門。

硬件

本講的主題是UART(Universal Asynchronous Receiver-Transmitter,通用異步收發器),俗稱串口。實際上串口是串行接口的統稱,在單片機領域通常指UART。“串行”的意思是每次傳輸一個bit,而一個字節的數據被拆成8個bit傳輸;相比之下並行總線可以一次傳輸一個或多個字節(這並不意味着並行總線一定優於串行總線)。

AVR單片機提供的硬件組件不是UART,而是USART(S代表Synchronous,同步的),相比UART額外支持同步通信。所謂“同步”是指收發雙方通過時鐘同步,“異步”是指沒有時鐘來同步,但實際上雙方還是由一些特殊信號同步的。

數據在UART總線上以“幀(frame)”爲單位發送,如下圖所示,帶有方括號的位是可選的。

一幀包含一個起始位、5~9個數據位(常用8位;很多設備不支持9位)、可選的一個校驗位(偶校驗或奇校驗,即所有數據位與0或1的異或結果)與1或2個終止位。起始位與終止位統稱爲同步位,用於在異步總線上起到同步的作用,這樣接收方纔能知道一幀何時開始。

波特率的定義是信息在通信信道上傳輸的速率。假如信號線上的波形允許1秒有9600個方框(方框表示高電平或低電平,實際電平是其中一個),那麼波特率就是9600。常用的波特率有9600與115200(打開Serial Port Utility或類似軟件,可選的波特率都是常用的)。

在開始通信之前,收發雙方必須約定好波特率與幀格式。uart_init函數的配置是波特率38400,8數據位,偶校驗,1停止位。相應地在電腦的串口調試軟件中也要這樣配置。

開發板的TX引腳發送數據,RX引腳接收數據。爲了使開發板與電腦能通過UART通信,電腦上需要插一個USB轉串口的工具。用杜邦線把開發板與工具的TXRX引腳交叉連接。本講只涉及串口發送,所以只連接開發板的TX與串口工具的RX就可以了。在串口調試軟件中打開端口,就可以通信了。

軟件

由於printf是變參函數,不是很安全(如果格式串和參數對應錯,程序可能直接跑飛),我傾向於使用類型安全的函數,即函數通過C的句法知道實參類型(寫錯就編譯錯誤,而不是通過編譯器無法檢測的格式串)。不過,avr-gcc的<stdio.h>中還是提供了printf等函數,你可以<a href="https://www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html" target="_blank">瞭解一下</a>。

庫中提供的發送函數都是同步阻塞的,即等待硬件組件把數據全部發送完,函數才返回。這裏的“同步”與剛纔的“異步總線”所指是不同的。關於“同步”與“異步”、“阻塞”與“非阻塞”的概念,可以參考:<a href="https://www.zhihu.com/question/19732473" target="_blank">怎樣理解阻塞非阻塞與同步異步的區別?</a>

不難計算,總線發送一個字節的時間是幾千個CPU週期,CPU會浪費大量時間在無用的等待上。這個問題直到我們講到中斷纔會解決(也許我會把它封裝起來放進庫)。

實例

我們來寫一個用串口發送按鍵與撥動開關信息的程序。如果你會相關的C#編程,就可以讓電腦響應按鍵事件。

#include <ee1/delay.h>
#include <ee1/button.h>
#include <ee1/switch.h>
#include <ee1/uart.h>

int main(void)
{
    button_init(PIN_6, PIN_7);
    switch_init(PIN_4, PIN_5);
    uart_init(UART_TX);
    uart_print_string("start\n");
    while (1)
    {
        for (uint8_t i = 0; i != BUTTON_COUNT; ++i)
            if (button_pressed(i))
            {
                uart_print_string("button ");
                uart_print_int(i);
                uart_print_string("\n");
            }
        for (uint8_t i = 0; i != SWITCH_COUNT; ++i)
            if (switch_changed(i))
            {
                uart_print_string("switch ");
                uart_print_int(i);
                uart_print_string(switch_status(i) ? " on\n" : " off\n");
            }
        delay(1);
    }
}

程序首先將UART初始化爲發送模式(UART_TX),然後打印"start"。在間隔一毫秒的循環中(實際上串口發送的時間遠長於一毫秒,因爲是阻塞的),程序檢測每一個按鍵與開關的動作,如果有則發送相應數據。

printf一行就能解決的操作這裏需要三行才能完成,這就是權衡吧。

作業

  1. 基於uart_print_char,實現my_print_int函數,在串口上打印一個int類型整數(在avr-gcc中,int類型默認是16位寬度;注意負號和0;你可以瞭解一下<a href="https://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#gaa571de9e773dde59b0550a5ca4bd2f00" target="_blank">itoa</a>,儘管它是非標準的)。

  2. 將旋轉編碼器的數據通過串口傳輸給電腦。將原始數據(pin_readrotary_status)與處理後的數據(rotary_rotated)一同打印,你可以更直觀地感受數據處理的過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章