Keil MDK 中利用串口及c標准庫函數printf為cortex-m3做調試輸出(lpc1788)


摘要:

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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM