[原创] 裸板程序之串口调试

影子的影子   2015-4-16 13:14 楼主
在论坛里潜水了很久,今天分享一个刚完成的串口调试的例子。水平不高,但是我会详细的讲解的。
    解压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


可以看到GPH0GPH1的引脚功能里有RXD[0]TXD[0],这里代表的是串口0TXRX线和芯片的GPIO中的GPH0GPH1复用,将它们配置为。。。。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 启动,IROM2416内部一个只读寄存器。cpu上电后,IROMsd卡中后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    影子的影子

    uart.zip (2015-4-16 13:11 上传)

    6.25 KB, 下载次数: 3

    解压后make

    SdBoot.rar (2015-4-16 13:11 上传)

    27.88 KB, 下载次数: 1

    制作sd卡启动bin文件

回复评论 (4)

补充说明一下,这里的串口使用方式是查询法。真正在数据传输中应用时,为了保证cpu的工作效率。都是设置为中断方式。
点赞  2015-4-16 19:51
亲自实践底层操作都值得赞!
My dreams will go on... http://www.jyxtec.com
点赞  2015-4-16 21:50
接口封装成 open read write close,这样子结构会比较好.为以后切换到linux打好基础.
点赞  2015-4-20 19:15
引用: zouguolvyi 发表于 2015-4-20 19:15
接口封装成 open read write close,这样子结构会比较好.为以后切换到linux打好基础.

..这个可以有,不过我写裸板基本都用一样的文件名和函数名、这样省得改makefile和链接脚本。省事
点赞  2015-4-20 20:52
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复