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