一、概要
由于以往的单片机实现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;
}//可参考本博另文章《4、UART通用异步接收器-s3c2440》
三、方法二Uart_SendString(char *pt)
void Uart_SendString(char *pt)
{
while (*pt)
putc(*pt++);
}//可参考本博另文章《4、UART通用异步接收器-s3c2440》
四、方法三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 < strlen(g_pcOutBuf); 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)
{
return vsnprintf(buf, (~0U)>>1, fmt, args);
}
vsnprintf(...):格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出,此函数比较复杂,以后分析。
五、一些特殊规定字符
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
字符 作用
——————————————————————————
\n 换行
\f 清屏并换页
\r 回车
\t Tab符
——————————————————————————
六、源程序
由于比较多,只列出几个比较重要点。在我的另一文章的《》
的基础上做出的修改,及增加。
由于生成的二进制文件比较大,故要用到Nand Flash的驱动来拷贝程序到内存中可参考下文,
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;
- }
- 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
- /*
- *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 = .;
- }
全部源代码打包如下:密码为主页地址
阅读(3615) | 评论(0) | 转发(0) |