ARM 串列埠輸出函式uart_printf
ARM如果能使用C函式庫自帶的printf函式格式輸出,那多方便,但是預設的printf都是定位到stdout終端,而不是串列埠,本文章講述的是如何定位到ARM的串列埠。
1.1.1 函式主要程式碼
有在Mini2440開發板上驗證過
//*****************main.c*******************************
#include"serial.h"
int Main()
{
unsignedint plck_val = 50000000;
unsignedint buad_val = 115200;
unsignedint ch_val = 0;
char*string="Hello,http://blog.csdn.net/wfq0624";
uart_init();
uart_printf("\n\r%s\n",string);
uart_printf("\rUse uart0\n\rParameter:PCLK is %d,buad is %d,uart_port is %d \n",plck_val,buad_val,ch_val);
uart_printf("\r該函式不能列印浮點數!");
return 0;
}
//***********************serial.c**************************
#include"s3c2440.h"
#include"serial.h"
#include <stdarg.h>
#include <stdio.h> //需要包涵此stdio.h標頭檔案
#define TXD0READY (1<<2)
#defineRXD0READY (1)
#definePCLK 50000000 // init.c中的clock_init函式設定PCLK為50MHz
#defineUART_CLK PCLK // UART0的時鐘源設為PCLK
#defineUART_BAUD_RATE 115200 // 波特率
#defineUART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
voiduart_init()
{ //UART初始化:埠使能、功能設定、波特率、設定資料格式
GPHCON = (GPHCON & ~(0xfff<<4)) |(0xaaa<<4);//埠配置成uart0、uart1,uart3
GPHUP = 0x38; //埠GPH禁止上拉
UFCON0 = 0x0; //禁止FIFO
UMCON0 = 0x0; //禁止AutoFlow Control
//Normal:No parity:One stop:8-bits 中斷響應 UART clock: PCLK
ULCON0 = 0x03; // 8N1(8個數據位,無較驗,1個停止位)
UCON0 = 0x05; // 查詢方式,UART時鐘源為PCLK
UBRDIV0 = UART_BRD; // 波特率為115200
}
voiduart_send_byte(char data)
{
while (!(UTRSTAT0 & TXD0READY));
UTXH0 = data;
}
voiduart_send_string(char *string)
{
while(*string)
{
uart_send_byte(*string++);
}
}
void uart_printf(char *fmt,...) //這個才是本文重點
{
va_listap;
charstring[256];
va_start(ap,fmt);
vsprintf(string,fmt,ap);
uart_send_string(string);
va_end(ap);
}
//***************************************************
1.1.2 uart_printf分析
這裡涉及到一個重要概念,變參函式,比如C庫函式int printf(char *fmt, ...),就是一個典型的變參函式。
我們即將編寫的uart_printf毫無疑問,也是一個變參函式。
可變引數入棧順序
在程序中,堆疊地址是從高到低分配的.當執行一個函式的時候,將引數列表入棧,壓入堆疊的高地址部分,然後入棧函式的返回地址,接著入棧函式的執行程式碼,這個入棧過程,堆疊地址不斷遞減,一些黑客就是在堆疊中修改函式返回地址,執行自己的程式碼來達到執行自己插入的程式碼段的目的.
總之,函式在堆疊中的分佈情況是:地址從高到低,依次是:函式引數列表,函式返回地址,函式執行程式碼段.
堆疊中,各個函式的分佈情況是倒序的.即最後一個引數在列表中地址最高部分,第一個引數在列表地址的最低部分.引數在堆疊中的分佈情況如下:
最後一個引數
倒數第二個引數
...
第一個引數
函式返回地址
函式程式碼段
va_list/ va_start/ va_arg/va_end 介紹
C語言標準庫中標頭檔案stdarg.h索引的介面包含了一組能夠遍歷變引數列表的巨集。主要包含下面幾個:
1.va_list:在函式裡定義一個va_list型的變數,這個變數是指向引數的指標
2.va_start:用va_start巨集初始化剛定義的va_list變數,讓它指向可變引數表裡面的第一個引數,
3.va_arg:每次呼叫時都會返回當前指標指向的變數,並將指標挪至下一個位置,引數的型別需要在這個呼叫的第二個引數來指定,va_arg也是根據這個引數來判斷偏移的距離。
4.va_end:獲取所有的引數之後,我們有必要將這個指標關掉,以免發生危險,方法是呼叫 va_end,置為 NULL,應該養成獲取完引數表之後關閉指標的習慣。
void uart_printf(char *fmt,...) //這個才是本文重點
{
va_listap; //定義一個 va_list 指標來訪問引數表
charstring[256];
va_start(ap,fmt); //初始化 ap,讓它指向第一個變參[也就是fmt引數後面的引數]
vsprintf(string,fmt,ap); //將帶引數的字串按照引數列表格式化到string中
uart_send_string(string);
va_end(ap); //結束變數列表,和va_start成對使用
}
可變引數在編譯器中的處理
va_list ,va_start,va_arg,va_end是在stdarg.h中被定義成巨集的,以VC++中stdarg.h裡x86平臺的巨集定義摘錄如
=================================================================
typedef char * va_list;
#define_INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#defineva_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#defineva_arg(ap,t) ( *(t *)((ap +=_INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0
=================================================================
è#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
定義_INTSIZEOF(n)主要是為了某些需要記憶體的對齊的系統
我們知道對於X86,sizeof(int)一定是4的整數倍,所以~(sizeof(int) - 1) )的值一定是右面[sizeof(n)-1]/2位為0,整個這個巨集也就是保證了右面[sizeof(n)-1]/2位為0,其餘位置為1,所以_INTSIZEOF(n)的值只有可能是4,8,16,......等等,實際上是實現了記憶體對齊。
舉例:#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
目的在於把sizeof(n)的結果變成至少是sizeof(int)的整倍數,這個一般用來在結構中實現按int的倍數對齊。
如果sizeof(int)是4,那麼,當sizeof(n)的結果在1~4之間是,_INTSIZEOF(n)的結果會是4;當sizeof(n)的結果在5~8時,_INTSIZEOF(n)的結果會是8;當sizeof(n)的結果在9~12時,_INTSIZEOF(n)的結果會是12;……總之,會是sizeof(int)的倍數。
è#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
ap指向引數v之後的那個引數的地址,即 ap指向第一個可變引數在堆疊的地址。
è#define va_arg(ap,t) ( *(t *)((ap +=_INTSIZEOF(t)) - _INTSIZEOF(t)) )
取出當前ap指標所指的值,並使ap指向下一個引數。
用va_arg返回可變的引數,va_arg的第二個引數是你要返回的引數的型別(如果函式有多個可變引數的,依次呼叫va_arg獲取各個引數
è#define va_end(ap) (ap = (va_list)0
清空va_list ap.
使用VA_LIST應該注意的問題:
(1)因為va_start,va_arg, va_end等定義成巨集,並不能智慧地識別不同引數的個數和型別. 也就是說,你想實現智慧識別可變引數的話是要通過在自己的程式裡作判斷來實現的.這個方法存在漏洞:輸入引數的型別隨意性,使得引數很容易以一個不正確的型別獲取一個值(譬如輸入一個float,卻以int型去獲取他),這樣做會出現莫名其妙的執行結果
(2)另外有一個問題,因為編譯器對可變引數的函式的原型檢查不夠嚴格,對程式設計查錯不利.不利於我們寫出高質量的程式碼。
(3)由於引數的地址用於va_start巨集,所以引數不能宣告為暫存器變數,或作為函式或陣列型別。