以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
参考博客:s5pv210——串口通信 - biaohc - 博客园
一、代码与测试
1、代码文件
完整的案例文件见链接。
(1)start.S文件
/* * 演示串口通信 */#define WTCON 0xE2700000 #define SVC_STACK 0xd0037d80.global _start _start:// 第1步:关看门狗ldr r0, =WTCONldr r1, =0x0str r1, [r0]// 第2步:初始化时钟bl clock_init //这个函数见时钟系统部分,这里不列出// 第3步:设置SVC栈ldr sp, =SVC_STACK// 第4步:开/关icachemrc p15,0,r0,c1,c0,0; // 读出cp15的c1到r0中//bic r0, r0, #(1<<12) // bit12 置0 关icacheorr r0, r0, #(1<<12) // bit12 置1 开icachemcr p15,0,r0,c1,c0,0;bl main// 汇编代码最后的这个死循环不能丢b .
(2)main函数所在的main.c文件
void main(void) {uart_init(); while(1){uart_putc('a');delay();} }
(3)uart_init()函数所在的uart.c文件
#define GPA0CON 0xE0200000 #define UCON0 0xE2900004 #define ULCON0 0xE2900000 #define UMCON0 0xE290000C #define UFCON0 0xE2900008 #define UBRDIV0 0xE2900028 #define UDIVSLOT0 0xE290002C #define UTRSTAT0 0xE2900010 #define UTXH0 0xE2900020 #define URXH0 0xE2900024 #define rGPA0CON (*(volatile unsigned int *)GPA0CON) #define rUCON0 (*(volatile unsigned int *)UCON0) #define rULCON0 (*(volatile unsigned int *)ULCON0) #define rUMCON0 (*(volatile unsigned int *)UMCON0) #define rUFCON0 (*(volatile unsigned int *)UFCON0) #define rUBRDIV0 (*(volatile unsigned int *)UBRDIV0) #define rUDIVSLOT0 (*(volatile unsigned int *)UDIVSLOT0) #define rUTRSTAT0 (*(volatile unsigned int *)UTRSTAT0) #define rUTXH0 (*(volatile unsigned int *)UTXH0) #define rURXH0 (*(volatile unsigned int *)URXH0)// 串口初始化程序 void uart_init(void) {// 1、初始化Tx Rx对应的GPIO引脚rGPA0CON &= ~(0xff<<0); // 把寄存器的bit0~7全部清零rGPA0CON |= 0x00000022; // 0b0010, Rx Tx// 2、几个关键寄存器的设置rULCON0 = 0x3;rUCON0 = 0x5;rUMCON0 = 0;rUFCON0 = 0;// 3、设置波特率// 波特率设置公式:DIV_VAL = PCLK / (bps x 16) - 1 // 其中PCLK_PSYS = 66.7,bps表示想设置的波特率// DIV_VAL = 66700000/(115200*16) - 1 = 35.18// 整数部分是35,小数部分是0.18//整数部分是35rUBRDIV0 = 35; // (rUDIVSLOT中的1的个数)/16=上一步计算的余数=0.18// (rUDIVSLOT中的1的个数 = 16*0.18= 2.88 = 3// 3个1,查官方推荐表得到这个数字0x0888rUDIVSLOT0 = 0x0888; }// 串口发送程序,发送一个字节 void uart_putc(char c) { // 串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去// 因为串口控制器发送1个字节的速度远远低于CPU的速度,所以CPU发送1个字节前必须// 确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)// 如果缓冲区非空则位为0,此时应该循环,直到位为1while (!(rUTRSTAT0 & (1<<1))); //rUTRSTAT0的bit[1]表示是否发完,为1则发完rUTXH0 = c; }// 串口接收程序,轮询方式,接收一个字节 char uart_getc(void) { //rUTRSTAT0的bit[0]表示是否接收完成,为1则接收完while (!(rUTRSTAT0 & (1<<0)));return (rURXH0 & 0x0f);//这里应该是 return (rURXH0 & 0xff)? }
2、测试验证
(1)在linux中完成代码的编译
编译得到的210.bin是SD卡启动的镜像文件,uart.bin是usb启动的镜像文件。
root@ubuntu:/home/xjh/iot/embedded_basic/linux_basic/uart_c# ls clock.c led.c main.c Makefile mkv210_image.c start.S uart.c write2sd root@ubuntu:/home/xjh/iot/embedded_basic/linux_basic/uart_c# make //省略编译时的输出 root@ubuntu:/home/xjh/iot/embedded_basic/linux_basic/uart_c# ls 210.bin led.c main.o mkx210 uart.bin uart_elf.dis clock.c led.o Makefile start.o uart.c uart.o clock.o main.c mkv210_image.c start.S uart.elf write2sd root@ubuntu:/home/xjh/iot/embedded_basic/linux_basic/uart_c#
(2)将镜像文件下载到开发板
为了简单起见,这里选择usb启动方式,过程参考在X210开发板上进行裸机开发的流程。
注意检查dnw是否已经正常工作,每次开机前好像都要设置禁止数字签名,驱动才正常。
(3)在SCRT中观察现象
注意,这里程序使用的是uart0,也就是靠近网口的那个串口,因此务必将串口线插到这个串口( 其实也可以修改代码,使用另外一个串口),在SCRT设置正确前提下,可以看到SCRT界面不断输出“aaaaaaa……”
二、串口初始化代码分析
1、初始化串口的Tx和Rx引脚所对应的GPIO
由X210开发板的原理图可知,Rx和Tx分别对应GPA0_0和GPA0_1。
GPA0是一个IO端口,包含8个IO口,每个IO口分别记作GPA0_0,GPA0_1,…,GPA0_7。对于每个IO端口,都有对应的一个端口控制寄存器,这个端口控制寄存器是32bit的,每4bit控制着一个IO口的模式。与GPAO端口对应的端口控制寄存器叫做GPA0CON寄存器,该寄存器的设置说明如下。
从中可以看出,只要将GPA0CON寄存器设置为bit[3:0] = 0b0010,bit[7:4] = 0b0010,即可把引脚设置为串口的作用状态。
// 初始化Tx Rx对应的GPIO引脚 rGPA0CON &= ~(0xff<<0); // 把寄存器的bit0~7全部清零 rGPA0CON |= 0x00000022; // 0b0010 = 2
2、初始化关键寄存器
关键的寄存器有ULCON0、UCON0、UMCON0、UFCON0、UBRDIV0、UDIVSLOT0。
这里红色的数字是0,是因为我们使用的是uart0,如果换成其他uart则要修改。
其中UBRDIV0和UDIVSLOT0和波特率有关,要根据公式去计算。
而前面四个寄存器的设置及其含义如下:
// 几个关键寄存器的设置 rULCON0 = 0x3; // 0b0011,其含义是0校验位、8数据位、1停止位 rUCON0 = 0x5; // 0b0101,其含义是发送和接收都是polling mode rUMCON0 = 0; // 0b0000,其含义是禁止modem、afc rUFCON0 = 0; // 0b0000,其含义是禁止FIFO模式
(1)ULCONn寄存器(设置为0x3=0b0011)
- 设置红外模式:0普通模式,1红外模式
- 设置奇偶校验模式
- 设置终止位:0则1位,1则2位
- 设置字长:5至8bit
(2)UCONn寄存器(设置为0x5=0b0101)
- 设置时钟选择:0表示PCLK_PSYS,1表示SCLK_UART,我们设置为0。
- 设置发送模式:轮询/中断
- 设置接收模式:轮询/中断
(3)UMCONn寄存器(设置为0x0)
- 设置禁止AFC、modem
(4)UFCONn寄存器(设置为0x0)
- 主要用来设置禁止FIFO。
(5)UTRSTATn寄存器
- 它是状态寄存器,通过读取这个寄存器可以得知发生与接收情况。
- Transmitter empty: 移位器和发送缓冲寄存器是否有可用数据发送,0则非空,1则空。
- Transmitter buffer empty: 发送缓冲寄存器,0则非空,1则空。
- Recieve buffer empty: 接收缓冲寄存器,0则非空,1则空。
(6)UBRDIV0、UDIVSLOT0寄存器
- 这两个寄存器和波特率设置有关。
- 波特率设置方法:DIV_VAL + 1 = (PCLK / (bps x 16)) ,这里的bps表示想要设置的波特率大小,PCLK是66.7MHz。DIV_VAL的整数部分就是寄存器UBRDIVn中的值;而小数部分乘以16得到的值向上取整得到的整数,则是UDIVSLOTn中1的个数,然后根据下面的表格来设置UDIVSLOTn寄存器的值。
- 如66.7MHz/(115200*16) = 36.187,则UBRDIVn中的值就是36-1=35,而0.187*16=2.992 ,则UDIVSLOTn中1的个数为3个,则根据下面表格UDIVSLOTn寄存器的值应该写入0x0888。
(7)UTXHn寄存器、URXHn寄存器
这两个寄存器就是发送缓冲区、接收缓冲区。