一、概要
由于以往的单片机实现printf函数成本太高,基本上不会其上实现printf功能,随着ARM芯片的发展,printf调试受到了极大的欢迎。对于嵌入式软件的开发人员而言,“printf调试(printf-debugging)”这个术语描述了将调试字符串从嵌入式目标空闲的串口压出,并在运行于宿主工作站的终端模拟器上显示结果的常见方法。出于这个目的,许多程序员更喜欢使用有名的printf() C语言库函数,因为它在将文本输出和数据组合成单个函数调用上具有灵活性。
在嵌入式系统中,串口常用来辅助调试输出一些调试信息。所以有必要实现串口的格式化输出功能,这可以由3种方法实现.
1)构建串口的输入输出putc getc .
2)使用数组构建字符串输出.函数Uart_SendString(char *pt)
3)写printf()函数.
二、方法一putc(), getc()
void putc(char data)
{
while (!(UTRSTAT0 & 0x4)) ;//Wait until THR is empty.
UTXH0 = data;
}
unsigned char getc(void)
{
while (!(UTRSTAT0 & (1))) ;//Wait until THx is empty.
return URXH0;
三、方法二Uart_SendString(char *pt)
void Uart_SendString(char *pt)
{
while (*pt)
putc(*pt++);
四、方法三printf()函数
为了减少难度,提高效率,故继承前人的知识,在第三种方法来在自己的ARM9芯片上基于C语言实现串口Printf函数。
1、printf函数体
static unsigned char g_pcOutBuf[1024];
static unsigned char g_pcInBuf[1024];
int printf(const char *fmt, ...)
{
int i;
int len;
va_list args;
va_sart(args, fmt);
len =vsprintf(g_pcOutBuf,fmt,args);
va_end(args);
for (i = 0; i
{
putc(g_pcOutBuf[i]);
}
return len;
}
va_list就一个指针类型的封闭typedef char *va_list;
va_arg, va_end ,va_start就是三个宏
va_start(ap,A):很明显它先得到第一个参数内存地址,然后又加上这个参数的内存大小,就是下个参数的内存地址。
_bnd(X,bnd):对于任何编译器,每个栈单元的大小都是sizeof(int), 而函数的每个参数都至少要占一个栈单元大小。其中#define _AUPBND (sizeof (int) - 1) .
va_arg:第一次调用时,它得到是参数列表中的第二个参数的值。
va_end:把指针归0。
ap:va_linst的一个变量arg;T:是要取得的参数类型fmt的格式;A:是一个参数。
vsprintf:返回写入buf的字符个数。
int vsprintf(char *buf, const char *fmt, va_list args)
{
returnvsnprintf(buf, (~0U)>>1, fmt, args);
}
vsnprintf(...):格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出,此函数比较复杂,以后分析。
五、一些特殊规定字符
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
字符作用
——————————————————————————
\n 换行
\f 清屏并换页
\r 回车
\t Tab符
——————————————————————————
六、源程序
由于比较多,只列出几个比较重要点。在我的另一文章的《》
的基础上做出的修改,及增加。
由于生成的二进制文件比较大,故要用到Nand Flash的驱动来拷贝程序到内存中可参考下文,
《3、NAND Flash控制器--s3c2440》 。在start.S中修改了启动时的拷贝代码部分,增加nand.h nand.c文件。
start.S
/*
*************************************************************************
*
* lcl bootloader
*
*************************************************************************
*/
.globl _start
_start: b start_code
HandleUndef:
b HandleUndef @ 0x04: 指未定义指令终止模式的向量地址
HandleSWI:
b HandleSWI @ 0x08 指管理模式,通过SWI指令进入
HandlePerfetchAbort:
b HandlePerfetchAbort @ 0x0c: 指令预取终止导致的异常的向量地址
HandleDataAbort:
b HandleDataAbort @ 0x10: 数据访问终止导致的异常的向量地址
HandleNotUsed:
b HandleNotUsed @ 0x14: 保留
b HandleIRQ @ 0x18: 中断模式的向量地址
HandleFIQ:
b HandleFIQ @ 0x1c: 快中断模式的向量地址
start_code:
bl clock_init
bl cpu_init_crit
msr cpsr_c,#0xd2 @进入中断模式
ldr sp,=0x33fffc00 @设置中断模式堆栈指针
msr cpsr_c,#0xdf @进入系统模式
msr cpsr_c,#0x5f @开IRQ中断
/*
*call c function .
*/
mov fp, #0 // no previous frame, so fp=0
ldr sp, =4096
bl NandInit //修改成用从Nand Flash中拷贝程序
bl ReadImageFromNand
/*
*run in sdram
*/
ldr sp, =0x34000000
ldr lr, =halt_loop @返回地址
ldr pc, = Main
halt_loop:
b halt_loop
HandleIRQ:
sub lr,lr,#4
stmdb sp!,{r0-r12,lr} @保存使用到的寄存器至中断堆栈区
ldr lr,=int_return @设置ISR的返回地址
ldr pc,=EINT_Handle @调用中断服务函数
int_return:
ldmia sp!,{r0-r12,pc}^ @中断返回,恢复寄存器值,^表示将spsr的值复制到cpsr
.globl ReadPage512
ReadPage512: //读512字节
stmfd {r2-r7}
mov r2, #0x200
1:
ldr r4, [r1] //取出32bit,按8bit字访问
ldr r5, [r1] //取出32bit
ldr r6, [r1] //取出32bit
ldr r7, [r1] //取出32bit
stmia {r4-r7} //存 64bit 到buffer中
ldr r4, [r1]
ldr r5, [r1]
ldr r6, [r1]
ldr r7, [r1]
stmia {r4-r7} //存 64bit 到buffer中
ldr r4, [r1]
ldr r5, [r1]
ldr r6, [r1]
ldr r7, [r1]
stmia {r4-r7} //存 64bit 到buffer中
ldr r4, [r1]
ldr r5, [r1]
ldr r6, [r1]
ldr r7, [r1]
stmia {r4-r7} //存 64bit 到buffer中
subs r2, r2, #64 //减去已读出的64byte
bne 1b; //循环读出直到512byte
ldmfd {r2-r7}
mov pc,lr //返回
clock_init:
#define pWTCON 0x53000000
#define CLKDIVN 0x4C000014 /* clock divisor register */
#define CLK_CTL_BASE 0x4C000000 /* clock base address */
#define MDIV_405 0x7f << 12 /* MDIV 0x7f*/
#define PSDIV_405 0x21 /* PDIV SDIV 0x2 0x1 */
/* turn off the watchdog */
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/* FCLK:HCLK:PCLK = 1:4:8 */
ldr r0, =CLKDIVN
mov r1, #5 //1:4:8
str r1, [r0]
mrc p15, 0, r1, c1, c0, 0 //切换到实时总线HCLK
orr r1, r1, #0xc0000000
mcr p15, 0, r1, c1, c0, 0
mov r1, #CLK_CTL_BASE
mov r2, #MDIV_405
add r2, r2, #PSDIV_405
str r2, [r1, #0x04] /* MPLLCON address Mpll=405MHZ */
mov pc, lr
/*
*MMU and SDRAM initialize
*/
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr
bl memsetup /* memory control configuration */
mov lr, ip
mov pc, lr
memsetup:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
#define MEM_CTL_BASE 0x48000000
mov r1,#MEM_CTL_BASE
adr r2,mem_cfg_val
add r3,r1,#52
1:
ldr r4, [r2], #4
str r4, [r1], #4
cmp r1, r3
bne 1b
mov pc,lr
.align 4
/* the literal pools origin */
mem_cfg_val:
.long 0x22011110
.long 0x00000700
.long 0x00000700
.long 0x00000700
.long 0x00000700
.long 0x00000700
.long 0x00000700 @BANK5
.long 0x00018005
.long 0x00018005
.long 0x00ac03f4 @REFRESH // 12MHZ 0x00ac07a3
.long 0x000000B1
.long 0x00000030
.long 0x00000030
修改uart0_init.h,当生成的静态库中需用到其中的函数。
uart0_init.h
#ifndef __UART0_INIT_H__
#define __UART0_INIT_H__
#define SERIAL_ECHO
void uart0_init();
void putc(unsigned char c);
unsigned char getc(void);
void Uart_SendString(char *pt);
#endif
printf.c
#include "vsprintf.h"
#include "string.h"
#include "printf.h"
extern void putc(unsigned char c);
extern unsigned char getc(void);
#define OUTBUFSIZE 1024
#define INBUFSIZE 1024
static unsigned char g_pcOutBuf[OUTBUFSIZE];//输出缓冲
static unsigned char g_pcInBuf[INBUFSIZE]; //输入缓冲
int printf(const char *fmt, ...)
{
int i;
int len;
va_list args;
va_start(args, fmt);
len = vsprintf(g_pcOutBuf,fmt,args);
va_end(args);
for (i = 0; i < strlen(g_pcOutBuf); i++)
{
putc(g_pcOutBuf[i]);
}
return len;
}
int scanf(const char * fmt, ...)
{
int i = 0;
unsigned char c;
va_list args;
while(1)
{
c = getc();
if((c == 0x0d) || (c == 0x0a))
{
g_pcInBuf[i] = '\0';
break;
}
else
{
g_pcInBuf[i++] = c;
}
}
va_start(args,fmt);
i = vsscanf(g_pcInBuf,fmt,args);
va_end(args);
return i;
}
修改main.c函数,来验证我的们printf()函数。
main.c
/*
GPBCON 0x56000010 Port B control
GPBDAT 0x56000014 Port B data
GPBUP 0x56000018 Pull-up control B
LED1 GPB5 LED2 GPB6 LED3 GPB7 LED4 GPB8
*/
#include "uart0_init.h"
#include "s3c2440_addr.h"
#include "interrupt.h"
void leds_init()
{
/*GPBCON &= (GPX_mask(5) & GPX_mask(6) & GPX_mask(7) & GPX_mask(8));
GPBCON |= (GPB5_out | GPB6_out | GPB7_out | GPB8_out);
GPBUP = GPX_up;
GPBDAT |=( 0xf << 5 );*/
GPBCON = (GPB5_out | GPB6_out | GPB7_out | GPB8_out);
GPBUP = GPX_up;
}
void buttons_eint_init()
{
GPGCON &= (GPX_mask(0) & GPX_mask(3) & GPX_mask(5) & GPX_mask(6));
GPGCON |= (GPG0_eint8 | GPG3_eint11 | GPG5_eint13 | GPG6_eint14);
}
int Main()
{
unsigned long dwDat;
int i=0;
char str[100];
leds_init();
buttons_eint_init();
uart0_init();
irq_init();
GPBDAT |=( 0xf << 5 );
Uart_SendString("\r wait interrupt. \r\n");
while (1)
{
GPBDAT |=( 0xf << 5 );
printf("\r\nEnter value: \n\r");
scanf("%s",str);
printf("Input char is %s\n\r",str); //\n换行,\r移到行首
sscanf(str,"%d",&i);
printf("print interger : %d, 0x%x\n\r", i, i);
}
return 0;
}
修改Makefile文件,可参考本博另一文章对此Makefile的分析《个人Makefile通用模板》
CC = arm-linux-gcc
LD = arm-linux-ld
AR = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
INCLUDEDIR := $(shell pwd)/include
CFLAGS := -Wall -O2
CPPFLAGS := -nostdinc -I$(INCLUDEDIR)
export CC LD OBJCOPY OBJDUMP INCLUDEDIR CFLAGS CPPFLAGS
objs := start.o main.o uart0_init.o interrupt.o nand.o lib/libc.a
stdio.bin: $(objs)
${LD} -Tmem.lds -o stdio_elf $^
${OBJCOPY} -O binary -S stdio_elf $@
${OBJDUMP} -D -m arm stdio_elf > memory.dis
${OBJDUMP} -dx stdio_elf > memory.map
rm -f stdio_elf *.o
.PHONY : lib/libc.a
lib/libc.a:
cd lib; make; cd ..
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
make clean -C lib
rm -f stdio.bin memory.dis memory.map
修改mem.lds,使其能在内存中正常运行。分析 可参考本博另一文《GCC-LD 连接脚本分析--uboot.lds》
mem.lds
/*
*lcl bootloader mem.lds.. AT((LOADADDR(.init)+SIZEOF(.init)+3)&~(0x03))
*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS {
. = 0x30000000;
. = ALIGN(4);
.init : AT(0) {start.o nand.o}
. = ALIGN(4);
. = 0x30001000;
.text : AT(4096) { *(.text) }
.rodata ALIGN(4) : AT((LOADADDR(.text)+SIZEOF(.text)+3)&~(0x03)) { *(.rodata*) }
.data ALIGN(4) : AT((LOADADDR(.rodata)+SIZEOF(.rodata)+3)&~(0x03)) { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss :{ *(.bss) *(COMMON)}
__bss_end = .;
}
全部源代码打包如下:密码为主页地址