打印
[应用相关]

在keil中使用printf

[复制链接]
2221|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
mintspring|  楼主 | 2015-12-1 14:21 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

摘要:

    当我们在调试代码时,通常需要将程序中的某个变量打印至PC机上,来判断我们的程序是否按预期的运行,printf函数很好的做到了这一 点,它能直接以字符的方式输出变量名和变量的值,这样使输出的信息很直观;但printf函数在使用时,不仅仅要初始化串口,还需要其它的一些设置或者要 调用其它的一些函数,否则printf函数将不能按我们想要的方式执行。

    由于不同的编译器studio函数不一样,所以使用的方法也不一样,这需要大家去看编译器的help,这里我以STM32、LPC24和AVR整理了几个串口打印程序,供需要的朋友参考。

简介:

   1、在KEIL下使用printf函数,以STM32为例

    在uart.c中添加如下代码

[url=][/url]
                                        /*******************************************************************************    函数名:fputc    输  入:    输  出:    功能说明:    重定义putc函数,这样可以使用printf函数从串口1打印输出 */ int fputc(int ch, FILE *f){ /* Place your implementation of fputc here */ /* e.g. write a character to the USART */ USART_SendData(USART1, (uint8_t) ch); /* Loop until the end of transmission */ while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)    {} return ch;} /*******************************************************************************    函数名:fputc    输  入:    输  出:    功能说明:    重定义getc函数,这样可以使用scanff函数从串口1输入数据 */ int fgetc(FILE *f){ /* 等待串口1输入数据 */ while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET)    {} return (int)USART_ReceiveData(USART1);}               
[url=][/url]


    这样,只要在需要用printf的文件里#include 就可以了,printf会自已的调用fputc函数来实现串口数据的输出。

2、添加Retarget.c,实现在KEIL下使用printf函数,以LPC2478为例

    首先在Keil安装目录下面ARM/Startup/Retarget.c找到Retarget.c文件将其复制到你的工程文件夹下面;并将其加入到工程中

    在uart.c中添加如下代码

[url=][/url]
                                        // Implementation of sendchar (also used by printf function to output data) int sendchar (int ch) { // Write character to Serial Port while (!(U0LSR & 0x20)); return (U0THR = ch); } int getkey (void)  { // Read character from Serial Port while (!(U0LSR & 0x01)); return (U0RBR); }               
[url=][/url]


    这样,只要在需要用printf的文件里#include 就可以了,printf会通过Retarget中的fputc函数调用sendchar来实现串口数据的输出。

   事实上,和第一种的方式是一样的。

3、自定义printf函数,以AVR为例

前面介绍的是在KEIL编译器上使用printf函数,但不是所有的编译器平台都能适用,因此有时候我们需要自定义printf函数,下面以AVR在GCC下为例

   在usart.c中添加如下代码

[url=][/url]
                                        #include <stdio.h> #include <stdarg.h> /*********************************************************/ //向串口usart0发送一个字节函数 void Uart0_putchar( unsigned char sdbyte)   {     UDR0=sdbyte; while(!(UCSR0A&0x40));     UCSR0A|=0x40; } //////////////////////////////////////////////////////// void Uart0_printf(char *str,...){ char buf[128]; unsigned char i = 0; va_list ptr; va_start(ptr,str); vsprintf(buf,str,ptr); while(buf) {     Uart0_putchar(buf);     i++; }}               
[url=][/url]


结语:

有了printf格式化输出函数,调试起来就方便多了。


沙发
mintspring|  楼主 | 2015-12-1 14:37 | 只看该作者
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;
}

复制代码

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

296

主题

4893

帖子

24

粉丝