让串口正常打印数据
一般在移植过程中,我们经常会碰到许多问题,这就需要让开发板给我们一些调试信息,最简单的调试信息就是通过点亮LED实现,但是LED就那么几个,能表示的调试信息少而且不直观,另一种更好的方法则是通过串口打印数据。所以我们在移植U-BOOT过程中一般都先实现串口能正常打印数据。
用过UART的人都知道,要想串口正常打印数据,最重要的两点就是时钟和波特率,所以在本章移植中,我们重点就是关注这两块的代码。
通过第一章的分析,我们知道按照程序的执行顺序,要想使用U-BOOT自带框架下的串口,要修改这几个部分:
- .globl reset(\arch\arm\cpu\arm920t\start.S)里设置了系统分频系数。
- board_early_init_f,(\common\board_f.c)初始化系统时钟
- init_baud_rate, serial_init,(\common\board_f.c)初始化串行通讯相关。
先看第一部分设置系统分频系数有没有问题。为了不在去计算时钟频率等,而直接使用韦东山老师后续的程序,我们修改一下分频系数,让FCLK:HCLK:PCLK = 1:4:8。然后根据datasheet说明,当HDIV设置为非0的时候,cpu总线模式要进行改变,默认情况下FCLK=HCLK,cpu工作在fast bus mode快速总线模式下,HDIV设置为非0后,FCLK和HCLK不再相等,要将cpu改为异步总线模式。然后为了让程序跑的更快,我们可以启用ICACHE,所以 代码修改如下:
\arch\arm\cpu\arm920t\start.S,第83行起
///* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
//mov r1, #3
mov r1, #5 /* FCLK:HCLK:PCLK = 1:4:8 */
str r1, [r0]
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */
orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */
mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */
/* 启动ICACHE */
mrc p15, 0, r0, c1, c0, 0 @ read control reg
orr r0, r0, #(1<<12)
mcr p15, 0, r0, c1, c0, 0 @ write it back
然后看第二部分,也就是系统时钟。关于时钟以及分频系数这块,现在不做过多讲解,需要的可以看这个这个链接: [https://blog.csdn.net/lee244868149/article/details/49962203].里面讲的非常详细,这边直接把修改的地方列出来,也就是简单的改了一下宏的值,让系统跑到400MHz:
\board\samsung\smdk2440\smdk2440.c,第19行起
#define FCLK_SPEED 1
#if (FCLK_SPEED == 0) /* Fout = 203MHz, Fin = 12MHz for Audio */
#define M_MDIV 0xC3
#define M_PDIV 0x4
#define M_SDIV 0x1
#elif (FCLK_SPEED == 1) /* Fout = 202.8MHz */
#define M_MDIV 0x5C
#define M_PDIV 0x1
#define M_SDIV 0x1
#endif
最后看修改串口配置这块,要修改的地方就一个,把头文件上的宏定义改一下就好了,修改后如下:
\include\configs\smdk2440.h,第16行起
/*
* High Level Configuration Options
* (easy to change)
*/
#define CONFIG_S3C24X0 /* This is a SAMSUNG S3C24x0-type SoC */
#define CONFIG_S3C2440 /* specifically a SAMSUNG S3C2440 SoC */
#define CONFIG_SMDK2410 /* on a SAMSUNG SMDK2410 Board */
#define CONFIG_SYS_TEXT_BASE 0x0
#define CONFIG_SYS_ARM_CACHE_WRITETHROUGH
虽然修改的地方少,但是这块的程序第一眼看还是有点复杂的,如果不想看分析的,可以跳过,直接配置编译,串口就能正常打印数据了。
\common\board_f.c,第909行起
- init_baud_rate,
- gd->baudrate = getenv_ulong(“baudrate”, 10, CONFIG_BAUDRATE);1
- serial_init,2
- get_current();(\drivers\serial\serial.c)
- default_serial_console();
- return &s3c24xx_serial0_device;(drivers\serial\serial_s3c24x0.c)3
- #define INIT_S3C_SERIAL_STRUCTURE(port, __name) {
.name = __name,
.start = s3serial##port##_init,
.stop = NULL,
.setbrg = s3serial##port##_setbrg,
.getc = s3serial##port##_getc,
.tstc = s3serial##port##_tstc,
.putc = s3serial##port##_putc,
.puts = s3serial##port##_puts,
}
- #define INIT_S3C_SERIAL_STRUCTURE(port, __name) {
- return &s3c24xx_serial0_device;(drivers\serial\serial_s3c24x0.c)3
- default_serial_console();
- start()4;(\drivers\serial\serial.c)
- int s3serial##port##_init(void) \(drivers\serial\serial_s3c24x0.c)
{
return serial_init_dev(port);
} \- _serial_setbrg(dev_index);
- get_PCLK()(\arch\arm\cpu\arm920t\s3c24x0\speed.c)
- get_HCLK()
- #ifdef CONFIG_S3C24405
switch (readl(&clk_power->clkdivn) & 0x6) {
default:
case 0:
return get_FCLK();
case 2:
return get_FCLK() / 2;
case 4:
return (readl(&clk_power->camdivn) & (1 << 9)) ?
get_FCLK() / 8 : get_FCLK() / 4;
case 6:
return (readl(&clk_power->camdivn) & (1 << 8)) ?
get_FCLK() / 6 : get_FCLK() / 3;
}
#else
return (readl(&clk_power->clkdivn) & 2) ? get_FCLK() / 2 : get_FCLK();
#endif
- #ifdef CONFIG_S3C24405
- get_HCLK()
- get_PCLK()(\arch\arm\cpu\arm920t\s3c24x0\speed.c)
- _serial_setbrg(dev_index);
- int s3serial##port##_init(void) \(drivers\serial\serial_s3c24x0.c)
- get_current();(\drivers\serial\serial.c)
可以看到虽然只要简单的需要一个宏,但是中间的代码分析还是蛮复杂的,推荐大家可以多看看这一块的程序,写的还是非常巧妙。
最后我们重新编译一下,烧到开发板上就可以看到如下的打印信息:
最后写我发现的一个小问题,在修改\include\configs\smdk2440.h文件的时候,我写成这样
/*
* High Level Configuration Options
* (easy to change)
*/
#define CONFIG_S3C24X0 /* This is a SAMSUNG S3C24x0-type SoC */
//#define CONFIG_S3C2410 /* specifically a SAMSUNG S3C2410 SoC */
#define CONFIG_S3C2440 /* specifically a SAMSUNG S3C2440 SoC */
#define CONFIG_SMDK2410 /* on a SAMSUNG SMDK2410 Board */
#define CONFIG_SYS_TEXT_BASE 0x0
#define CONFIG_SYS_ARM_CACHE_WRITETHROUGH
结果编译不通过打印这个错误信息:
LDS u-boot.lds
LD u-boot
arm-linux-ld:u-boot.lds:1: ignoring invalid character `#' in expression
arm-linux-ld:u-boot.lds:1: syntax error
Makefile:1018: recipe for target 'u-boot' failed
make: *** [u-boot] Error 1
查了一下才知道不能用//注释宏,得用#if 0…#endif,或者直接删除宏定义。特此记录一下。
下面附上补丁文件链接:
链接:https://pan.baidu.com/s/1v85_5VApVgmMG9IXrwgCiQ
提取码:bb6y
注意:这边的补丁文件只是相对于上一节的u-boot,直接下载下来的u-boot打这个补丁是没有用的。
环境变量里面读取波特率,如果没有设置,则返回默认值115200。 ↩︎
该函数关键在这句话get_current()->start(),总的来说,前半句用于确定使用哪个串口,后半句使用该串口的start函数。 ↩︎
因为我们定义了defined CONFIG_SERIAL1,所以返回struct serial_device s3c24xx_serial0_device这个结构体,而这个结构体又等于INIT_S3C_SERIAL_STRUCTURE(port, __name)这个宏定义,这个宏根据传进来的名字和端口,来确定结构体start,stop,setbrg等等对应的函数。 ↩︎
我们已经在get_current()里面确定了用哪个串口的结构体,那么这个函数就是调用这个结构体的.start。 ↩︎
可以看到如果定义了宏CONFIG_S3C2440,才能获取到正确的时钟频率。 ↩︎