1 UART通信協議
1.1 UART通信的物理連接
圖1 UART的物理連接
1.2 邏輯電平
用電平表示邏輯1和邏輯0,邏輯1和邏輯0用來組織計算機層面的數據。
1.3 電平標准
根據通訊使用的電平標准不同,串口通訊可分為 TTL標准及 RS-232 標准。
1.4 協議解析
通訊雙方需要約定波特率,並約定一致的數據包格式才能保證正常收發數據。
1.4.1 波特率(bps)
單位時間內,發送數據的位數。
1.4.2 數據格式
串口通信一般以起始位作為一幀數據傳輸的開始,以結束位表示一幀數據傳輸的結束。每一幀數據一般由起始位、數據位、停止位、校驗位組成。
起始位:由1個邏輯0的數據位表示;
停止位:由 0.5、1、1.5或 2個邏輯 1的數據位表示;
校驗位(奇校驗/偶校驗):當為奇校驗時,數據位和校驗位中,邏輯1的數據位的個數為奇數個;當為偶校驗時,數據位和校驗位中,邏輯1的數據位的個數為偶數個。
圖2 115200,8n1; send 0b01000001
如圖2所示,115200bps,則1/115200spb,即每傳輸一位需要1/115200秒,數據在(1/115200)/2處采樣。
2 printf的實現
2.1 標准庫中的printf
函數原型:
int printf(const char *format, ...)
返回值:
成功返回實際輸出字符數,失敗返回-1;
傳入參數說明:
format: 固定參數
...: 可變參數;參數的個數不確定,類型不確定;
2.2 可變參數的實現原理
調用子函數,函數的參數最終會以被壓入棧中,被函數使用;通過格式控制符,實現對棧中傳入參數的讀取和使用。
在標准庫中的實現:
typedef char* va_list;
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1) & ~(sizeof(int)-1)) //保證4字節對齊
#define va_start(ap, v) (ap = (va_list)&v + _INTSIZEOF(v)) //獲取第一個變參在棧中的地址
#define va_arg(ap, t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))) //獲取ap所指向的數據,並把ap偏移至下一個變參的地址
#define va_end(ap) (ap = (va_list)0) //使ap指向空,避免野指針
printf(format, arg1, arg2, arg3);參數在棧中的存放
2.3.1 對_INTSIZEOF(n)分析
棧指針總是4字節對齊的,因此使用_INTSIZEOF(n),使變量的大小是4的倍數(實際變量在棧中占據的空間)。
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1) & ~(sizeof(int)-1)),對這個宏定義有一個形象的比喻:
比方說有一個箱子可以裝4個瓶子,
如果我有8個瓶子 ,那么我需要2個箱子;
如果我有10個瓶子呢,我不能說我需要10除4,需要2.5個箱子吧,實際上我需要3個箱子;
那怎么求我實際需要的箱子數呢?
用一個容易理解的公式來求上述問題:
設我的瓶子數為B,我需要的箱子數為C,一個箱子最多可以裝A個瓶子。
公式:C =(B+A-1)/ A (舍去余數)
因此,((sizeof(n)+sizeof(int)-1) & ~(sizeof(int)-1))相當於C * A。
2.3.2 對va_arg(ap, t)分析
#define va_arg(ap, t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
這個宏定義實現了兩個功能:
a、ap += _INTSIZEOF(t) —— 求下一個參數的指針
b、(*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))) —— 取當前參數值,這個宏定義將返回的就是當前參數值
附錄:源代碼
uart.c —— 波特率:115200
debug_printf.c