在论坛里潜水了很久,今天分享一个刚完成的串口调试的例子。水平不高,但是我会详细的讲解的。
解压uart.zip到随意一个目录下 然后
cd uart/
将会看到start.S Makefile uart.c uart.h link.lds五个文件
vi start.S
进入到start.S代码界面。
.global _start
# define PWTCON 0x5300000
# define CLKDIVN 0x4C000014
# define INTMSK 0x4A000008
.text
_start:
LDR R0,=PWTCON
MOV R1,#0X0
STR R1,[R0]
LDR R0,=INTMSK
MOV R1,#0xFFFFFFFF
STR R1,[R0]
LDR R0, =CLKDIVN
MOV R1, #3
STR R1, [R0]
BL uart
.end
可以看到,在start.S中完成了4个动作:1、关闭看门狗
2、关闭所有中断
3、设置分频系数为FCLK:HCLK:PCLK = 1:2:4 ,即 PCLk为30mhz
4、跳转到c函数uart() 中去
代码中主要是通过对2416的3个寄存器进行操作,关于这3个寄存器,请参照2416的数据手册。
LDR 指令: 内存访问指令 LDR R0,[R1] 是将R1寄存器地址出的内容放入R0寄存器
在本文中使用的是LDR伪指令 LDR R0,=num 将一个数num放入R0寄存器中
MOV指令: 寄存器访问指令 MOV R0,#num 将一个数num放入R0寄存器中
容易发现,mov指令和ldr伪指令的作用是近乎一样的。但是这里要强调一点,mov指令操作的数num必须是一个立即数。即一个能用4个字节来存储的数。一个比较的数,超出了4个字节的存储数最大值,是会被分为两部分,存储在8个字节中。关于立即数和伪指令,可以自行百度,得到更详细的解释。
根据start.S的流程,现在cpu转去执行c函数uart(),打开uart.c和uart.h继续分析
vi uart.c
它包含了一个头文件uart.h,uart.h中主要是定义了关于串口的寄存器和3个全局函数
# define uchar unsigned char
# define uint unsigned int
# define ULCON0 *(volatile unsigned char *)0x50000000
# define UCON0 *(volatile unsigned char *)0x50000004
# define UTRSTAT0 *(volatile unsigned char *)0x50000010
# define UTXH0 *(volatile unsigned char *)0x50000020
# define URXH0 *(volatile unsigned char *)0x50000024
# define GPH0 *(volatile unsigned char *)0x56000070
# define UBRDIV0 *(volatile unsigned char *)0x50000028
# define PCLK 30000000
extern void uart_init(uint baudrate);
extern unsigned char getc(void);
extern void putc(uchar c);
extern void uart(void);
这里说一下寄存器地址的定义方法 *在c语言中代表取地址符 ,volatile是c语言关键字,告诉编译器不要对这里定义的变量优化。因为编译器优化代码是可能会改变变量,这里加上volatile是确保寄存器的地址是正确的。
例如# define GPH0 *(volatile unsigned char *)0x56000070 就是告诉编译器,GPH0的地址为0x56000070 ,(volatile unsigned char *)定义为一个指针,*取地址符再取出这个指针指向地址的值。
分析完了uart.h,我们回到uart.c中来
void uart()
{
uart_init(115200);
uchar a;
while(1)
{
a=getc();
putc(a);
}
}
这里uart()函数你可以将其理解为main函数,从汇编指令跳转到这里,继续执行。首先是 uart_init(115200); 初始化串口,并设置波特率为115200
void uart_init(uint baudrate)
{
GPH0=0xA;
ULCON0 |=0x03;
UCON0=0x05;
UBRDIV0=15;
URXH0=0;
}
将GPH0设置为oxA,打开2416手册,第239页
file:///C:\Users\cx\AppData\Local\Temp\ksohtml\wps5208.tmp.jpg
可以看到GPH0和GPH1的引脚功能里有RXD[0]和TXD[0],这里代表的是串口0的TX和RX线和芯片的GPIO中的GPH0和GPH1复用,将它们配置为。。。。1010,启用发送接收功能。第二步,配置ULCON0,将手册翻到312页。
ULCON0寄存器主要是设置数据位和停止位。数据位不用多说,是发送数据的位数,设为8位。即一字节 停止位就是让串口知道这一次发送已经结束的数。就像c语言字符串的结尾是‘\0’转义字符一样。
第三步,设置UCON0寄存器 ,手册313页。设置发送和接收模式都使用查询模式。串口发送(接收)动作结束后,都会将UTRSTAT0(手册317页)的第2位(第0位)置1.在程序中可以以此判断是否发送(接收)结束。
第四部设置波特率UBRDIV0=15;这里其实我是将寄存器操作简化了,原本的公式应当为
UBRDIV0=(int)(PCLK/baudrate/16)-1.我给大家解读一下这句,PCLk是2416的io设备使用的时钟。关于2416的几种时钟请查询手册,这里我就不叙述了。我在start.S中将它设置为30mhz 即30000000,baudrate是波特率,设置为115200,通过uart()函数的参数传入。本来我直接将这一句UBRDIV0=(int)(PCLK/baudrate/16)-1.放在代码中,编译发现报错,我百度了原因是编译器不支持浮点运算,解决办法是在链接脚本中指定使用c库里的一个浮点运算文件,太复杂,不予考虑,干脆直接把UBRDIV0计算出来,写入。
第五步,将URXH0清0.URXH0是接收缓冲寄存器,串口接收到的数据会先放在这个寄存器中。
putc和getc函数比较简单,就是分别查询UTRSTAT0的第2位和第0位,然后写(读)缓冲寄存器UTXH0(RTXH0)就可以了。
Uart()函数的流程是1、初始化 2、读取输入的字符 3、将输入的字符输出到pc。
接下来,分析一下链接脚本link.lds
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x40000000;
.start :
{
. = ALIGN(4);
start.o (.text)
uart.o
}
.text :
{
. = ALIGN(4);
*(.text*)
*(.rodata*)
}
.data :
{
. = ALIGN(4);
*(.data*)
}
}
首先,第一句OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm"),将代码的格式设置为小端存储。Arm的存储格式分为大端和小端两种,默认为小端格式。。。。关于这个,又是一大段的知识。想深入理解的自行百度吧=_=
OUTPUT_ARCH(arm) 设置代码在arm处理器上运行
ENTRY(_start) 告诉编译器 ,汇编指令的起始地址是_start 回头去翻看start.S,开头有一句.global _start ,声明_start是一个全局标号。就是为了能在别的文件中调用它。
SECTIONS是一个关键字,定义一些段,例如代码段,bss段,只读数据段等
. = 0x40000000;指定链接地址为0x40000000.说到这个,不得不提到2416的启动方式,我这里选择的IROM SD 启动,IROM是2416内部一个只读寄存器。cpu上电后,IROM将sd卡中后18个扇区中的前16个扇区的代码(代码大小是4k还是8k我忘了==)复制到片内的stepping stone(就是0x40000000开始的8k大小的一片内存)中运行。并且,IROM中固化的代码会自动帮我们设置好堆栈,方便了我们对c函数的调用。(使用c语言,必须要有堆栈,堆栈是c语言中临时变量的存储地址)。
然后link.lds我们还需要注意的是.start :
{
. = ALIGN(4);
start.o (.text)
uart.o
}
这里把start.o放在代码段的首地址,接下来才是放uart.o(剩下的代码是bss段、只读数据段,我也不熟悉,看看就好)
最后,vi Makefile
看一下编译脚本Makefile。首先是关于编译脚本的名称,Makefile 、makefile都是可以的,gcc编译器会自己在目录中寻找可用的编译脚本。
Makefile有特定的语法,目标和依赖。这个并不能一一讲来,论坛里有人发过相关的帖子。去查阅那个吧。
Makefile里要重点解读的一句是arm-linux-ld -Tlink.lds -o tmp.elf $^ 指定链接脚本为link.lds。如果你自己写裸机工程,使用别人写好的makefile和链接脚本时,一定要注意makefile里链接脚本的指定和链接脚本里汇编代码的入口的指定。
到这里,关于uart调试的所有代码分析完毕。
因为使用了sd卡启动,生成bin文件后,要调整一下才能直接使用sd卡启动直接运行起来。使用附件里的sdboot即可。关于为什么要这么做,有坛友已经做过分析了,大家自行到yuanlai2010的帖子裸机第二弹这个帖子查阅。
串口调试的效果是你打开超级终端,设置好参数后连接com口。这时候,你按下键盘上的随意一个键,终端就会打印出对应的字符。(这样做可能效果不明显,可以修改uart.c中的uart函数,将a改为字符‘a’,这时候,你按下键盘上的任意按键,终端都会打印字符a)
值得注意的是设置超级终端或者securecrt时,要设置成8位数据位,一位停止位。无数据流控制。波特率115200,否则可能调试失败。另外使用securecrt软件调试时,可能会无法输入字符,别担心,那是因为你没有设置可输入字符功能,具体设置自行百度吧。
最后,在本文的结尾声明一下,这次的串口调试使用的是串口的比较浅显的非FIFO模式。
为了传输的效率,真正使用串口时,都会配置成FIFO模式。关于这个,想深入了解就去看手册的UART章节吧。
感谢网友分享的sdboot工具,感谢boss的指点。
参考书籍《ARM处理器裸机开发实战-机制而非策略》
2015/4/16
论坛id 影子的影子