摘要:
c標准庫的printf是輸出給顯示器的,將printf函數進行修改,使其輸出重定向至串口,就能實現目的。printf函數調用fputc函數完成實質輸出單一字符的工作,因此將fputc函數修改使之完成串口單字符發送工作即可。
注:
本文方法性內容主要來自《Keil MDK環境下使用printf函數的解決方法》與《STM32串口使用Printf()函數問題》。除使用c標准庫外,還可以使用keil mdk提供的microLib,在STM32串口使用Printf()函數問題》一文有介紹,另外,該文同時也提到如果使用c標准庫函數,則要避免鏈接使用半主機模式的函數,retarge.c文件中的#pragma import(__use_no_semihosting_swi) 和_sys_exit函數實現就是來確保不鏈接半主機模式函數的。
實現步驟:
1. keil MDK已經為我們提供了這樣的接口文件:
文件位置:C:\Keil\ARM\Startup,(C:\Keil\為我的keil安裝根目錄)
文件名:Retarget.c
文件內容:
/******************************************************************************/ /* RETARGET.C: 'Retarget' layer for target-dependent low level functions */ /******************************************************************************/ /* This file is part of the uVision/ARM development tools. */ /* Copyright (c) 2005 Keil Software. All rights reserved. */ /* This software may only be used under the terms of a valid, current, */ /* end user licence from KEIL for a compatible version of KEIL software */ /* development tools. Nothing else gives you the right to use this software. */ /******************************************************************************/ #include <stdio.h> #include <time.h> #include <rt_misc.h> #pragma import(__use_no_semihosting_swi) extern int sendchar(int ch); /* in Serial.c */ extern int getkey(void); /* in Serial.c */ extern long timeval; /* in Time.c */ struct __FILE { int handle; /* Add whatever you need here */ }; FILE __stdout; FILE __stdin; int fputc(int ch, FILE *f) { return (sendchar(ch)); } int fgetc(FILE *f) { return (sendchar(getkey())); } int ferror(FILE *f) { /* Your implementation of ferror */ return EOF; } void _ttywrch(int ch) { sendchar (ch); } void _sys_exit(int return_code) { while (1); /* endless loop */ }
因此我們的工作就是:
(1)將Retarget.c文件加入自己的工程
(2)提供Serial.c文件,在該文件中實現sendchar和getkey()
sendchar即為串口發送單字符函數。
2. Serial.c文件實現(lpc1788芯片)
這里使用lpc1788的uart 0口實現rs232功能
#include "lpc177x_8x.h" // SET32BIT宏將32位變量x的l位至h位部分置為數val, // 例如: // x = 0x03; 二進制 00000000000000000000000000000011 // SET32BIT(x,1,3,6); // 則x變為0x0D ,二進制 00000000000000000000000000001101 #define SET32BIT(x,l,h,val) {(x) &= (~((~(0xfffffffful<<(h-l+1)))<<l)); (x) |= val##ul<<l;} void uart0_init() { // 配置P0.2, P0.3為UART接口(即使用UART0) SET32BIT(LPC_IOCON->P0_2,0,2,1); // TXD0 SET32BIT(LPC_IOCON->P0_3,0,2,1); // RXD0 // RXD0口設置為內部上拉。在232通信協議、電平為TTL電平時,除邏輯1外,空閑狀態也用高電平表示,因此接個上拉不影響通信, // 同時還會在空閑時鉗制電平,不至於引入干擾信號。當然這都是自己的理解而已。。。 SET32BIT(LPC_IOCON->P0_3,3,4,2); // 給UART0模塊供電(實際上UART0默認為單片機上電時供電,但像UART2、3是不供電的,需要像這里一樣手動置位供電) SET32BIT(LPC_SC->PCONP,0,2,1); // 配置NVIC 中斷使能寄存器,使能UART0模塊的中斷能力(這是對內核對中斷的第一層配置管理) NVIC->ISER[0] |= 0x01<<5; // 配置UART0 FCR FIFO控制寄存器 // 使能FIFO,如果不使能,那么...我認為必須使能啊,如果不使能,串口沒有緩存隊列,豈不是殘疾了。 LPC_UART0->FCR |= 0x01<<0; // 清空緩存,每次清緩存只需要對下列位置位 LPC_UART0->FCR |= 0x01<<1; // 清接收緩存 LPC_UART0->FCR |= 0x01<<2; // 清發送緩存 // 設定 接收中斷觸發事件 SET32BIT(LPC_UART0->FCR,6,7,1); // 設置觸發點1事件(默認為接收緩存中有不少於4個字節時觸發接收中斷) // 配置UART0 LCR線控制寄存器 SET32BIT(LPC_UART0->LCR,0,1,3); // 8bit 數據位 SET32BIT(LPC_UART0->LCR,2,2,0); // 1bit 停止位 SET32BIT(LPC_UART0->LCR,3,3,0); // 無校驗 // 配置UART0 中斷使能寄存器IER,使能UART0中斷響應(這是對中斷的第二層管理 // 在LPC1788上,一般各模塊或IO的中斷都需要這樣兩級控制,包括使能與禁能 // IER必須是在分頻除數寄存器LSB與MSB被鎖定、即訪問被禁止的情況下才能進行讀寫操作,上電后的默認值是鎖定的(對應LCR的DLAB位為0) // 鎖定通過LCR控制寄存器中的DLAB位被置位來實現,上電時的默認值為0,表示禁止訪問LSB與MSB。 SET32BIT(LPC_UART0->LCR,7,7,0); // DLAB賦0,禁止訪問LSB與MSB LPC_UART0->IER |= 0x1<<0; // 使能UART0接收事件中斷,前面FCR設置的事件發生時觸發中斷 // lpc1788在上電后默認使用片內12MHz晶振,通過SystemInit函數設置后的PCLK時鍾頻率為60MHz // 要根據想要的波特率和PCLK時鍾頻率得到UART0分頻除數寄存器的配置值,需要先開放LSB與MSB的訪問 LPC_UART0->LCR |= 0x1<<7; // DLAB賦1,允許訪問LSB與MSB // 對於9600波特率與60Mhz(60000000)的PCLK,按照lpc1788手冊提供的算法計算得到以下各寄存器的值 // 這些寄存器的值確定出的波特率為9615,與9600有0.15%的誤差, SET32BIT(LPC_UART0->FDR,0,3,1); // 小數分頻寄存器DIVADDVAL賦1 SET32BIT(LPC_UART0->FDR,4,7,2); // 小數分頻寄存器MULVAL賦2 SET32BIT(LPC_UART0->DLL,0,7,4); // LSB寄存器賦4 SET32BIT(LPC_UART0->DLM,0,7,1); // MSB寄存器賦1 SET32BIT(LPC_UART0->LCR,7,7,0); // LSB和MSB配置結束后,要禁止訪問LSB與MSB,否則UART的一些功能不能使用 } int sendchar(int ch) { LPC_UART0->THR = (uint8_t)ch; while((LPC_UART0->LSR&0x40)==0){}; return ch; } int getkey(void) { while((LPC_UART0->LSR&0x1)==0){}; return LPC_UART0->RBR; }
3. 將Serial.c加入工程,就可以使用printf了
#include "lpc177x_8x.h" #include <stdio.h> void uart0_init(void); int main() { uart0_init(void); printf("hello world\n"); return 0; }