一、目的
串口通信我们并不陌生,我们经常用串口来进行数据传输,可并不清楚它是如何工作
的。那这一节我们就来揭开 ARM S3c2410 UART(Universal Asynchronous Receiver and
Transmitter) 串口通信的神秘面纱。
二、代码
我们先来分析文件 crt0.s
@ 文件 crt0.s
@ 作用:设置堆栈指针
.text
.global _start
_start:
ldr sp, =1024*4
bl main
halt_loop:
b halt_loop
你可能会有疑问,这个汇编文件有什么用?呵呵,这是因为我们的串口通信代码要用 C
编写(用汇编可读性太差了)。可这又和这个 crt0.s 有什么关系呢?这得从 C 语言程序的
编译说起。C 语言程序执行的第一条指令并不在 main 函数里。当生成一个 C 语言程序时
编译器总是在我们的代码前加一段固定的代码--crt0.o,它是编译器自带的一个文件,用来
设置 C 程序的堆栈等,然后调用 main 函数。可惜在我们的裸板上它自带的 crt0.o 的代
码是不能运行的,我们得自己动手写,这就是为什么要有 crt0.s 这个文件。稍后你将看到,
这个 crt0.s 被编译成我们自己的 crt0.o 文件。
/* 头文件 serl.h
* 作用:定义相关寄存器、UART 初始化函数声明、串口读写函数的声明
*/
#ifndef __SERL_H__
#define __SERL_H__
#define GPHCON (*(volatile unsigned long *)0x56000070)
/* PORT PULL-UP REGISTER*/
#define GPHUP (*(volatile unsigned long *)0x56000078)
/* UART FIFO control register 0*/
#define UFCON0 (*(volatile unsigned long *)0x50000008)
/* UART line control register 0*/
#define ULCON0 (*(volatile unsigned long *)0x50000000)
/* UART control register 0*/
#define UCON0 (*(volatile unsigned long *)0x50000004)
/* UART modem control register 0*/
#define UMCON0 (*(volatile unsigned long *)0x5000000C)
/* UART baud rate divisor register 0*/
#define UBRDIV0 (*(volatile unsigned long *)0x50000028)
/* UART TX/RX status register 0*/
#define UTRSTAT0 (*(volatile unsigned long *)0x50000010)
/* UART transmit buffer register 0*/
#define UTXH0 (*(volatile unsigned char *)0x50000020)
/* UART receive buffer register 0*/
#define URXH0 (*(volatile unsigned char *)0x50000024)
#define TXD0_READY 0x2
#define RXD0_READY 0x1
void init_uart();
void putc(unsigned char ch);
unsigned char getc();
#endif
/* 文件 serl.c*/
#include "serl.h"
void init_uart() {
GPHCON |= 0xa0; /* GPH2, GPH3 used as RXD0, TXD0*/
GPHUP = 0x0c; /* GPH2, GPH3 poll-up */
ULCON0 = 0x03; /* normal mode, no parity, one stop bit, 8-bit*/
UCON0 = 0x05; /* Loopback mode*/
UFCON0 = 0x00; /* not use FIFO*/
UMCON0 = 0x00; /* disable flow control*/
UBRDIV0 = 12; /* baud rate 57600*/
}
void putc(unsigned char ch) {
while (!(UTRSTAT0 & TXD0_READY));
UTXH0 = ch;
}
unsigned char getc(){
while (! (UTRSTAT0 & RXD0_READY));
return URXH0;
}
我们选用最简单的方法,用 UART0 进行实验,用到的寄存器有8个多,初始化用去5
个,余下的3个用于接收、发送数据。初始化设置的代码说明如下:
1. GPHCON 的 GPH2、GPH3用控制接收数据寄存器 RXD0 和发送数据寄存器 TXD0
手册中GPH2、GPH3描述如下:
GPHCON | Bit | Description | |
GPH3 | [7:6] | 00 = Input | 01 = Output |
10 = RXD0 | 11 = reserved | ||
GPH2 | [5:4] | 00 = Input | 01 = Output |
10 = TXD0 | 11 = Reserved |
2. ULCON0 设置为 0x03, 含义是正常操作模式、无校验、停止位1、8个数据位
3. UCON0 设置为 0x05 表示发送、接收数据都使用查询方式
4. UFCON0 设置为 0x00 为不使用 FIFO (每个UART内部都有一个16字节的发送和接收
FIFO)
5. UMCON0 设置为 0x00 为不使用流控
6. UBRDIV0 设置为 12 含义为 波特率设为 57600, 由下面公式算得:
UBRDIVn = (int) (PCLK/bps*16) - 1
其中 PCLK = 12MHz
发送/接收数据的代码说明如下:
1. UTRSTA0 (UART TX/RX status register 0 )
bit[1]:无数据发送时自动设为1,我们要用串口发送数据时,先读此位以判断是否有
数据正在发送。
bit[0]:接收缓冲区是否有数据,如果有,此位自动设为1,我们需要读此位来判断是
否接收到了数据。
2. UTXH0: 把要发送的数据写入此寄存器
3. URXH0: 读此寄存器会得到串口接收到的数据
/*
* 测试代码 main.c
* 作用:将从串口接收的数据发回串口
*/
#include "serl.h"
int main(void) {
unsigned char ch;
init_uart();
while (1) {
ch = getc();
/* 如果接收到的是回车符就发送回车和换行符*/
if (ch == 0x0d) {
putc(0x0d);
putc(0x0a);
}
else {
putc(ch);
}
}
}
# 文件 Makefile
# 由代码文件生成目标文件,并连接目标文件
# 最后将连接生成的目标文件转换成二进制格式
main:crt0.s serl.c main.c
arm-linux-gcc -c -o crt0.o crt0.s
arm-linux-gcc -c -o serl.o serl.c
arm-linux-gcc -c -o main.o main.c
arm-linux-ld -Ttext 0x00000000 crt0.o serl.o main.o -o main_tmp.o
arm-linux-objcopy -O binary -S main_tmp.o main
clean:
rm -f *.o
rm -f main