LV04-04-串口通信-03-串口格式化函数移植

本文主要是串口通信——串口格式化函数printf的移植的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows版本 windows11
Ubuntu版本 Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
Win32DiskImager Win32DiskImager v1.0
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
https://elixir.bootlin.com/linux/latest/source 在线阅读linux kernel源码
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)

一、概述

上一节中,我们实现了 UART1 基本的数据收发功能,虽然可以用来调试程序,但是功能太单一了,只能输出字符。如果需要输出数字的时候就需要我们自己先将数字转换为字符,非常的不方便。学习 STM32 串口的时候我们都会将 printf 函数映射到串口上,这样就可以使用printf 函数来完成格式化输出了,使用非常方便。那 printf 这样的格式化函数能移植到 I.MX6U-ALPHA 开发板上吗?当然也可以啦。

格式化函数说的是 printf、 sprintf 和 scanf 这样的函数,分为格式化输入和格式化输出两类函数。学习 C 语言的时候常常通过 printf 函数在屏幕上显示字符串,通过 scanf 函数从键盘获取输入。这样就有了输入和输出了,实现了最基本的人机交互。在 I.MX6U-ALPHA 开发板上也可以将 printf 和 scanf 映射到串口上,这样就可以使用 MobaXterm 作为开发板的终端,完成与开发板的交互。也可以使用 printf 和 sprintf 来实现各种各样的格式化字符串,方便我们后续的开发。

二、printf移植

1. 从uboot移植

这里直接用了正点原子的资料,他为我们提供了一个stdio文件夹

image-20230729153948757

stdio 里面的文件其实是从 uboot 里面移植过来的。其实都是可以从 uboot 源码还有交叉编译工具链安装目录里面找出相应的文件,完成格式化函数的移植。这里要注意一点, stdio 中并没有实现完全版的格式化函数,比如 printf 函数并不支持浮点数,但是基本够我们使用了。 移植完成后,直接编译可能会报下边的警告:

1
2
3
4
5
6
7
8
In file included from project/main.c:5:0:
bsp/uart/bsp_uart.h:12:6: 警告: conflicting types for built-in function ‘putc’
void putc(unsigned char c);
^
bsp/uart/bsp_uart.h:13:6: 警告: conflicting types for built-in function ‘puts’
void puts(char *str);
^

这个表示“putc”、“puts”这两个函数与内建函数冲突,在编译的时候加入选项“-fno-builtin”表示不使用内建函数就可以了。另外还需要添加在编译 C 文件的时候添加选项“-Wa,-mimplicit-it=thumb”,否则的话会有如下类似的错误提示:

1
thumb conditional instruction should be in IT block -- `addcs r5,r5,#65536    

移植完成后,可以看gitee仓库:02_UART/03_printf · sumumm/imx6ull-bare-demo - 码云 - 开源中国 (gitee.com)

2. 自己实现一个?

自己实现的这个是参考的韦东山教程的逻辑开发教程,所以这里写一下基于的工程情况吧。可以看这里:02_UART/04_my_printf · sumumm/imx6ull-bare-demo - 码云 - 开源中国 (gitee.com)。需要注意的是这里的printf后面的换行只能是\n\r,换成\r\n就会报以下问题:

image-20240119004011983

具体原因还不清楚,上面直接移植uboot中相关文件的版本倒是没问题,后面知道了再补充。

2.1 基础工程结构

2.1.1 目录结构

image-20240116231150136

2.1.2 imx6ull.lds

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SECTIONS {
. = 0x80100000;

. = ALIGN(4);
.text :
{
*(.text)
}

. = ALIGN(4);
.rodata : { *(.rodata) }

. = ALIGN(4);
.data : { *(.data) }

. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
__bss_end = .;
}

2.1.3 main.c

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "uart.h"

int main()
{
unsigned char cTestData ; /*用于测试发送的数据*/
Uart_Init() ;

PutStr("Hello, world!\n\r"); /*发送字符串*/

while(1)
{
cTestData = GetChar() ; /*等待从串口获取数据*/

if (cTestData == '\r') /*添加回到行首\r*/
{
PutChar('\n');
}

if (cTestData == '\n') /*换行\n*/
{
PutChar('\r');
}

PutChar(cTestData) ; /*从串口发送数据*/
}

return 0;
}

2.1.4 start.S

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.text
.global _start
_start:

/* 设置栈 */
ldr sp,=0x80200000
/* 清除bss段 */
bl clean_bss
/* 跳转到主函数 */
bl main

halt:
b halt

clean_bss:
ldr r1, =__bss_start // 将链接脚本变量__bss_start变量保存于r1
ldr r2, =__bss_end // 将链接脚本变量__bss_end变量保存于r2
mov r3, #0
clean:
strb r3, [r1] // 将当前地址下的数据清零
add r1, r1, #1 // 将r1内存储的地址+1
cmp r1, r2 // 相等:清零操作结束;否则继续执行clean函数清零bss段
bne clean

mov pc, lr

2.1.5 uart.c

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include "uart.h"


static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA ;
static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA ;
static volatile unsigned int *IOMUXC_UART1_RX_DATA_SELECT_INPUT ;

void Uart_Init(void)
{

IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA = (volatile unsigned int *)(0x20E0084);
IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA = (volatile unsigned int *)(0x20E0088);
IOMUXC_UART1_RX_DATA_SELECT_INPUT = (volatile unsigned int *)(0x20E0624);

*IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA = 0;
*IOMUXC_UART1_RX_DATA_SELECT_INPUT = 3;
*IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA = 0;

UART1->UCR1 |= (1 << 0) ; /*关闭当前串口*/

/*
* 设置UART传输格式:
* UART1中的UCR2寄存器关键bit如下
* [14]: 1:忽略RTS引脚
* [8] : 0: 关闭奇偶校验 默认为0,无需设置
* [6] : 0: 停止位1位 默认为0,无需设置
* [5] : 1: 数据长度8位
* [2] : 1: 发送数据使能
* [1] : 1: 接收数据使能
*/

UART1->UCR2 |= (1<<14) |(1<<5) |(1<<2)|(1<<1);

/*
* UART1中的UCR3寄存器关键bit如下
* [2]: 1:根据官方文档表示,IM6ULL的UART用了这个MUXED模型,提示要设置
*/

UART1->UCR3 |= (1<<2);

/*
* 设置波特率
* 根据芯片手册得知波特率计算公式:
* Baud Rate = Ref Freq / (16 * (UBMR + 1)/(UBIR+1))
* 当我们需要设置 115200的波特率
* UART1_UFCR [9:7]=101,表示不分频,得到当前UART参考频率Ref Freq :80M ,
* 带入公式:115200 = 80000000 /(16*(UBMR + 1)/(UBIR+1))
*
* 选取一组满足上式的参数:UBMR、UBIR即可
*
* UART1_UBIR = 71
* UART1_UBMR = 3124
*/

UART1->UFCR = 5 << 7; /* Uart的时钟clk:80MHz */
UART1->UBIR = 71;
UART1->UBMR = 3124;

UART1->UCR1 |= (1 << 0); /*使能当前串口*/
}

void PutChar(int c)
{
while (!((UART1->USR2) & (1<<3))); /*等待上个字节发送完毕*/
UART1->UTXD = (unsigned char)c;
}

unsigned char GetChar(void)
{
while (!(UART1->USR2 & (1<<0))); /*等待接收数据*/
return (unsigned char)UART1->URXD;
}

void PutStr(const char *s)
{
while (*s)
{
PutChar(*s);
s++;
}
}

2.1.6 uart.h

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef _UART_H_
#define _UART_H_


/*UART1的寄存器的基地址*/
#define UART1_BASE (0x2020000u)

#define UART1 ((UART_Type *)UART1_BASE)

/*根据IMX6ULL芯片手册<<55.15 UART Memory Map/Register Definition>>的3608页,定义UART的结构体,*/
typedef struct {
volatile unsigned int URXD; /**< UART Receiver Register, offset: 0x0 串口接收寄存器,偏移地址0x0 */
unsigned char RESERVED_0[60];
volatile unsigned int UTXD; /**< UART Transmitter Register, offset: 0x40 串口发送寄存器,偏移地址0x40*/
unsigned char RESERVED_1[60];
volatile unsigned int UCR1; /**< UART Control Register 1, offset: 0x80 串口控制寄存器1,偏移地址0x80*/
volatile unsigned int UCR2; /**< UART Control Register 2, offset: 0x84 串口控制寄存器2,偏移地址0x84*/
volatile unsigned int UCR3; /**< UART Control Register 3, offset: 0x88 串口控制寄存器3,偏移地址0x88*/
volatile unsigned int UCR4; /**< UART Control Register 4, offset: 0x8C 串口控制寄存器4,偏移地址0x8C*/
volatile unsigned int UFCR; /**< UART FIFO Control Register, offset: 0x90 串口FIFO控制寄存器,偏移地址0x90*/
volatile unsigned int USR1; /**< UART Status Register 1, offset: 0x94 串口状态寄存器1,偏移地址0x94*/
volatile unsigned int USR2; /**< UART Status Register 2, offset: 0x98 串口状态寄存器2,偏移地址0x98*/
volatile unsigned int UESC; /**< UART Escape Character Register, offset: 0x9C 串口转义字符寄存器,偏移地址0x9C*/
volatile unsigned int UTIM; /**< UART Escape Timer Register, offset: 0xA0 串口转义定时器寄存器 偏移地址0xA0*/
volatile unsigned int UBIR; /**< UART BRM Incremental Register, offset: 0xA4 串口二进制倍率增加寄存器 偏移地址0xA4*/
volatile unsigned int UBMR; /**< UART BRM Modulator Register, offset: 0xA8 串口二进制倍率调节寄存器 偏移地址0xA8*/
volatile unsigned int UBRC; /**< UART Baud Rate Count Register, offset: 0xAC 串口波特率计数寄存器 偏移地址0xAC*/
volatile unsigned int ONEMS; /**< UART One Millisecond Register, offset: 0xB0 串口一毫秒寄存器 偏移地址0xB0*/
volatile unsigned int UTS; /**< UART Test Register, offset: 0xB4 串口测试寄存器 偏移地址0xB4*/
volatile unsigned int UMCR; /**< UART RS-485 Mode Control Register, offset: 0xB8 串口485模式控制寄存器 偏移地址0xB8*/
} UART_Type;

void Uart_Init(void);
void PutChar(int c);

#endif

2.1.7 Makefile

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PREFIX=arm-linux-gnueabihf-
CC=$(PREFIX)gcc
LD=$(PREFIX)ld
AR=$(PREFIX)ar
OBJCOPY=$(PREFIX)objcopy
OBJDUMP=$(PREFIX)objdump

uart.img : start.S uart.c main.c
$(CC) -nostdlib -g -c -o start.o start.S
$(CC) -nostdlib -g -c -o uart.o uart.c
$(CC) -nostdlib -g -c -o main.o main.c

$(LD) -T imx6ull.lds -g start.o uart.o main.o -o uart.elf

$(OBJCOPY) -O binary -S uart.elf uart.bin
$(OBJDUMP) -D -m arm uart.elf > uart.dis
./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d uart.bin uart.imx
dd if=/dev/zero of=1k.bin bs=1024 count=1
cat 1k.bin uart.imx > uart.img

clean:
rm -f uart.dis uart.bin uart.elf uart.imx uart.img *.o

2.2 移植printf

2.2.1 raise函数

在 uart.c 中加入 raise 函数,用于防止编译失败。

1
2
3
4
int raise(int signum)/* raise函数,防止编译报错 */
{
return 0;
}

2.2.2 修改 Makefile

my_printf.c 中用到除法的求模运算,需要提供除法库。一般的交叉工具链里都提示有基本的数学运算,它们位于 libgcc.a 中。我们需要把 libgcc.a也链接进程序里,需要修改 Makefile。

注意:链接指令中,每个“ -L”表示库在哪里,即它的目录;“ -l”表示哪个库,即库的名称, -lgcc 表示会链接“libgcc.a”库。

对 Makefile 作如下修改:

(1)增加$(CC) -nostdlib -g -c -o my_printf.o my_printf.c

(2)在$(LD) -T imx6ull.lds -g start.o uart.o main.o my_printf.o -o my_printf.elf
添加-lgcc – L<libgcc.a 的路径>

例如:

1
$(LD) -T imx6ull.lds -g start.o uart.o main.o my_printf.o -o my_printf.elf -lgcc -L/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_armlinux-gnueabihf/lib/gcc/arm-linux-gnueabihf/6.2.1

2.2.3 变参数函数移植

函数参数列表包括了字符串( format)和变参(…)组合而成, 在 vc6.0 的头文件 stdarg.h 中找到 typedef char * va_list:

1
2
3
4
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

① _INTSIZEOF(n) 用于获取其中一个变参类型占用的空间长度。

② va_start(ap,v) 令 ap 指向第一个变参地址。

③ va_arg(ap,t) 取出一个变参,同时指针指向下一个变参。

④ va_end(ap) 将指针指向 NULL,防止野指针 。

我们移植以上的代码,编写一个属于自己的 printf。参考 int printf(const char *format, …)库函数,实现 my_printf。我们创建my_printf.c :

1
2
3
4
5
6
7
8
9
10
//reference :  int printf(const char *format, ...); 
int printf(const char *fmt, ...)
{
va_list ap;

va_start(ap, fmt);
my_vprintf(fmt, ap);
va_end(ap);
return 0;
}

2.2.4 编写 my_vprintf(fmt, ap)

参考 int vprintf(const char *format, va_list ap)实现 my_vprintf ,在my_printf.c中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*reference :   int vprintf(const char *format, va_list ap); */
static int my_vprintf(const char *fmt, va_list ap)
{
char lead=' ';
int maxwidth=0;

for(; *fmt != '\0'; fmt++)
{
if (*fmt != '%') {
outc(*fmt);
continue;
}

lead=' ';
maxwidth=0;

//format : %08d, %8d,%d,%u,%x,%f,%c,%s
fmt++;
if(*fmt == '0'){
lead = '0';
fmt++;
}


while(*fmt >= '0' && *fmt <= '9'){
maxwidth *=10;
maxwidth += (*fmt - '0');
fmt++;
}

switch (*fmt) {
case 'd': out_num(va_arg(ap, int), 10,lead,maxwidth); break;
case 'o': out_num(va_arg(ap, unsigned int), 8,lead,maxwidth); break;
case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break;
case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break;
case 'c': outc(va_arg(ap, int )); break;
case 's': outs(va_arg(ap, char *)); break;

default:
outc(*fmt);
break;
}
}
return 0;
}

2.2.5 编写 out_c, outs 与 out_num 函数

  • (1)利用之前我们实现的单字节打印函数 void PutChar(int c)实现 out_c,outs 与 out_num 函数 outc 用于格式化输出中的%c 的输出。
1
2
3
4
5
static int outc(int c) 
{
PutChar(c);
return 0;
}
  • outs 用于格式化输出中的%s 的输出。
1
2
3
4
5
6
static int outs (const char *s)
{
while (*s != '\0')
PutChar(*s++);
return 0;
}
  • out_num 用于格式化输出中的%d, %o, %u, %x 的输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static int out_num(long n, int base,char lead,int maxwidth) 
{
unsigned long m=0;
char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf);
int count=0,i=0;


*--s = '\0';

if (n < 0){
m = -n;
}
else{
m = n;
}

do{
*--s = hex_tab[m%base];
count++;
}while ((m /= base) != 0);

if( maxwidth && count < maxwidth){
for (i=maxwidth - count; i; i--)
*--s = lead;
}

if (n < 0)
*--s = '-';

return outs(s);
}

2.2.6 测试函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int my_printf_test(void)
{
printf("This is www.100ask.org my_printf test\n\r") ;
printf("test char =%c,%c\n\r", 'A','a') ;
printf("test decimal number =%d\n\r", 123456) ;
printf("test decimal number =%d\n\r", -123456) ;
printf("test hex number =0x%x\n\r", 0x55aa55aa) ;
printf("test string =%s\n\r", "www.100ask.org") ;
printf("num=%08d\n\r", 12345);
printf("num=%8d\n\r", 12345);
printf("num=0x%08x\n\r", 0x12345);
printf("num=0x%8x\n\r", 0x12345);
printf("num=0x%02x\n\r", 0x1);
printf("num=0x%2x\n\r", 0x1);

printf("num=%05d\n\r", 0x1);
printf("num=%5d\n\r", 0x1);

return 0;
}

2.3 移植后的工程结构

2.3.1 目录结构

image-20240116233107762

2.3.2 imx6ull.lds

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SECTIONS {
. = 0x80100000;

. = ALIGN(4);
.text :
{
*(.text)
}

. = ALIGN(4);
.rodata : { *(.rodata) }

. = ALIGN(4);
.data : { *(.data) }

. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
__bss_end = .;
}

2.3.3 main.c

点击查看详情
1
2
3
4
5
6
7
#include "my_printf.h"
#include "uart.h"
int main()
{ Uart_Init();
my_printf_test();
return 0;
}

2.3.4 start.S

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.text
.global _start
_start:

/* 设置栈 */
ldr sp,=0x80200000
/* 清除bss段 */
bl clean_bss
/* 跳转到主函数 */
bl main

halt:
b halt

clean_bss:
ldr r1, =__bss_start // 将链接脚本变量__bss_start变量保存于r1
ldr r2, =__bss_end // 将链接脚本变量__bss_end变量保存于r2
mov r3, #0
clean:
strb r3, [r1] // 将当前地址下的数据清零
add r1, r1, #1 // 将r1内存储的地址+1
cmp r1, r2 // 相等:清零操作结束;否则继续执行clean函数清零bss段
bne clean

mov pc, lr

2.3.5 uart.c

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include "uart.h"


static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA ;
static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA ;
static volatile unsigned int *IOMUXC_UART1_RX_DATA_SELECT_INPUT ;

void Uart_Init(void)
{

IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA = (volatile unsigned int *)(0x20E0084);
IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA = (volatile unsigned int *)(0x20E0088);
IOMUXC_UART1_RX_DATA_SELECT_INPUT = (volatile unsigned int *)(0x20E0624);

*IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA = 0;
*IOMUXC_UART1_RX_DATA_SELECT_INPUT = 3;
*IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA = 0;

UART1->UCR1 |= (1 << 0) ; /*关闭当前串口*/

/*
* 设置UART传输格式:
* UART1中的UCR2寄存器关键bit如下
* [14]: 1:忽略RTS引脚
* [8] : 0: 关闭奇偶校验 默认为0,无需设置
* [6] : 0: 停止位1位 默认为0,无需设置
* [5] : 1: 数据长度8位
* [2] : 1: 发送数据使能
* [1] : 1: 接收数据使能
*/

UART1->UCR2 |= (1<<14) |(1<<5) |(1<<2)|(1<<1);

/*
* UART1中的UCR3寄存器关键bit如下
* [2]: 1:根据官方文档表示,IM6ULL的UART用了这个MUXED模型,提示要设置
*/

UART1->UCR3 |= (1<<2);

/*
* 设置波特率
* 根据芯片手册得知波特率计算公式:
* Baud Rate = Ref Freq / (16 * (UBMR + 1)/(UBIR+1))
* 当我们需要设置 115200的波特率
* UART1_UFCR [9:7]=101,表示不分频,得到当前UART参考频率Ref Freq :80M ,
* 带入公式:115200 = 80000000 /(16*(UBMR + 1)/(UBIR+1))
*
* 选取一组满足上式的参数:UBMR、UBIR即可
*
* UART1_UBIR = 71
* UART1_UBMR = 3124
*/

UART1->UFCR = 5 << 7; /* Uart的时钟clk:80MHz */
UART1->UBIR = 71;
UART1->UBMR = 3124;

UART1->UCR1 |= (1 << 0); /*使能当前串口*/
}

void PutChar(int c)
{
while (!((UART1->USR2) & (1<<3))); /*等待上个字节发送完毕*/
UART1->UTXD = (unsigned char)c;
}

unsigned char GetChar(void)
{
while (!(UART1->USR2 & (1<<0))); /*等待接收数据*/
return (unsigned char)UART1->URXD;
}

void PutStr(const char *s)
{
while (*s)
{
PutChar(*s);
s++;
}
}
int raise(int signum)/* raise函数,防止编译报错 */
{
return 0;
}

2.3.6 uart.h

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef _UART_H_
#define _UART_H_


/*UART1的寄存器的基地址*/
#define UART1_BASE (0x2020000u)

#define UART1 ((UART_Type *)UART1_BASE)

/*根据IMX6ULL芯片手册<<55.15 UART Memory Map/Register Definition>>的3608页,定义UART的结构体,*/
typedef struct {
volatile unsigned int URXD; /**< UART Receiver Register, offset: 0x0 串口接收寄存器,偏移地址0x0 */
unsigned char RESERVED_0[60];
volatile unsigned int UTXD; /**< UART Transmitter Register, offset: 0x40 串口发送寄存器,偏移地址0x40*/
unsigned char RESERVED_1[60];
volatile unsigned int UCR1; /**< UART Control Register 1, offset: 0x80 串口控制寄存器1,偏移地址0x80*/
volatile unsigned int UCR2; /**< UART Control Register 2, offset: 0x84 串口控制寄存器2,偏移地址0x84*/
volatile unsigned int UCR3; /**< UART Control Register 3, offset: 0x88 串口控制寄存器3,偏移地址0x88*/
volatile unsigned int UCR4; /**< UART Control Register 4, offset: 0x8C 串口控制寄存器4,偏移地址0x8C*/
volatile unsigned int UFCR; /**< UART FIFO Control Register, offset: 0x90 串口FIFO控制寄存器,偏移地址0x90*/
volatile unsigned int USR1; /**< UART Status Register 1, offset: 0x94 串口状态寄存器1,偏移地址0x94*/
volatile unsigned int USR2; /**< UART Status Register 2, offset: 0x98 串口状态寄存器2,偏移地址0x98*/
volatile unsigned int UESC; /**< UART Escape Character Register, offset: 0x9C 串口转义字符寄存器,偏移地址0x9C*/
volatile unsigned int UTIM; /**< UART Escape Timer Register, offset: 0xA0 串口转义定时器寄存器 偏移地址0xA0*/
volatile unsigned int UBIR; /**< UART BRM Incremental Register, offset: 0xA4 串口二进制倍率增加寄存器 偏移地址0xA4*/
volatile unsigned int UBMR; /**< UART BRM Modulator Register, offset: 0xA8 串口二进制倍率调节寄存器 偏移地址0xA8*/
volatile unsigned int UBRC; /**< UART Baud Rate Count Register, offset: 0xAC 串口波特率计数寄存器 偏移地址0xAC*/
volatile unsigned int ONEMS; /**< UART One Millisecond Register, offset: 0xB0 串口一毫秒寄存器 偏移地址0xB0*/
volatile unsigned int UTS; /**< UART Test Register, offset: 0xB4 串口测试寄存器 偏移地址0xB4*/
volatile unsigned int UMCR; /**< UART RS-485 Mode Control Register, offset: 0xB8 串口485模式控制寄存器 偏移地址0xB8*/
} UART_Type;

void Uart_Init(void);
void PutChar(int c);

#endif

2.3.7 Makefile

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PREFIX=arm-linux-gnueabihf-
CC=$(PREFIX)gcc
LD=$(PREFIX)ld
AR=$(PREFIX)ar
OBJCOPY=$(PREFIX)objcopy
OBJDUMP=$(PREFIX)objdump

my_printf.img : start.S uart.c main.c my_printf.c
$(CC) -nostdlib -g -c -o start.o start.S
$(CC) -nostdlib -g -c -o uart.o uart.c
$(CC) -nostdlib -g -c -o main.o main.c
$(CC) -nostdlib -g -c -o my_printf.o my_printf.c

$(LD) -T imx6ull.lds -g start.o uart.o main.o my_printf.o -o my_printf.elf -lgcc -L/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/6.2.1

$(OBJCOPY) -O binary -S my_printf.elf my_printf.bin
$(OBJDUMP) -D -m arm my_printf.elf > my_printf.dis
./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d my_printf.bin my_printf.imx
dd if=/dev/zero of=1k.bin bs=1024 count=1
cat 1k.bin my_printf.imx > my_printf.img

clean:
rm -f my_printf.dis my_printf.bin my_printf.elf my_printf.imx my_printf.img *.o

2.3.8 mp_printf.c

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include  "my_printf.h"
#include "uart.h"


//==================================================================================================
typedef char * va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
//#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_arg(ap,t) ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

//==================================================================================================

unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',\
'8','9','a','b','c','d','e','f'};


static int outc(int c)
{
PutChar(c);
return 0;
}

static int outs (const char *s)
{
while (*s != '\0')
PutChar(*s++);
return 0;
}

static int out_num(long n, int base,char lead,int maxwidth)
{
unsigned long m=0;
char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf);
int count=0,i=0;


*--s = '\0';

if (n < 0){
m = -n;
}
else{
m = n;
}

do{
*--s = hex_tab[m%base];
count++;
}while ((m /= base) != 0);

if( maxwidth && count < maxwidth){
for (i=maxwidth - count; i; i--)
*--s = lead;
}

if (n < 0)
*--s = '-';

return outs(s);
}


/*reference : int vprintf(const char *format, va_list ap); */
static int my_vprintf(const char *fmt, va_list ap)
{
char lead=' ';
int maxwidth=0;

for(; *fmt != '\0'; fmt++)
{
if (*fmt != '%') {
outc(*fmt);
continue;
}

lead=' ';
maxwidth=0;

//format : %08d, %8d,%d,%u,%x,%f,%c,%s
fmt++;
if(*fmt == '0'){
lead = '0';
fmt++;
}


while(*fmt >= '0' && *fmt <= '9'){
maxwidth *=10;
maxwidth += (*fmt - '0');
fmt++;
}

switch (*fmt) {
case 'd': out_num(va_arg(ap, int), 10,lead,maxwidth); break;
case 'o': out_num(va_arg(ap, unsigned int), 8,lead,maxwidth); break;
case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break;
case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break;
case 'c': outc(va_arg(ap, int )); break;
case 's': outs(va_arg(ap, char *)); break;

default:
outc(*fmt);
break;
}
}
return 0;
}

//reference : int printf(const char *format, ...);
int printf(const char *fmt, ...)
{
va_list ap;

va_start(ap, fmt);
my_vprintf(fmt, ap);
va_end(ap);
return 0;
}

int my_printf_test(void)
{
printf("This is www.100ask.org my_printf test\n\r") ;
printf("test char =%c,%c\n\r", 'A','a') ;
printf("test decimal number =%d\n\r", 123456) ;
printf("test decimal number =%d\n\r", -123456) ;
printf("test hex number =0x%x\n\r", 0x55aa55aa) ;
printf("test string =%s\n\r", "www.100ask.org") ;
printf("num=%08d\n\r", 12345);
printf("num=%8d\n\r", 12345);
printf("num=0x%08x\n\r", 0x12345);
printf("num=0x%8x\n\r", 0x12345);
printf("num=0x%02x\n\r", 0x1);
printf("num=0x%2x\n\r", 0x1);

printf("num=%05d\n\r", 0x1);
printf("num=%5d\n\r", 0x1);

return 0;
}

2.3.9 my_printf.h

点击查看详情
1
2
3
4
5
6
7
8
9
10

#ifndef _MY_PRINTF_H
#define _MY_PRINTF_H

#define MAX_NUMBER_BYTES 64
extern int my_printf_test(void);
int printf(const char *fmt, ...);

#endif /* _MY_PRINTF_H */

2.4 测试效果

不出意外,一切顺利的话我们应该会得到以下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
This is www.100ask.org my_printf test
test char =A,a
test decimal number =123456
test decimal number =-123456
test hex number =0x55aa55aa
test string =www.100ask.org
num=00012345
num= 12345
num=0x00012345
num=0x 12345
num=0x01
num=0x 1
num=00001
num= 1

串口终端如果打印以上信息,证明实验成功!

2.5 地址打印

我们来看一下上面移植好后,对地址的打印情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "my_printf.h"
#include "uart.h"

char g_charA = 'A'; // 存储在 .data 段
const char g_charB = 'B'; // 存储在 .rodata 段
const char g_charC; // 存储在 .bss 段
int g_intA = 0; // 存储在 .bss 段
int g_intB; // 存储在 .bss 段

int main(int argc, const char * argv[])
{
int c = 9;
Uart_Init();
my_printf_test();
printf("\n\r");//反过来会报错,具体原因未知,还没有深究
printf("g_charA=%d &g_charA=0x%x\n\r", g_charA, &g_charA);
printf("g_charB=%d &g_charB=0x%x\n\r", g_charB, &g_charB);
printf("g_charC=%d &g_intA=0x%x\n\r", g_charC, &g_charC);
printf("g_intA=%d &g_intA=0x%x\n\r", g_intA, &g_intA);
printf("g_intB=%d &g_intB=0x%x\n\r", g_intB, &g_intB);
printf("c=%d &c=0x%x\n\r", c, &c);
return 0;
}

不出意外的话,我们会得到以下打印输出:

image-20240119003227288

会发现全是错的,具体原因可能还是哪里移植的不太对,后面进行了修改,修改后的工程可以直接看这里:imx6ull-bare-demo: imx6ull的裸机开发demo - Gitee.com。修改完后打印信息如下:

image-20240119003653270