UART串口这个东西,是嵌入式学习上避不开的,不仅在调试中经常用到,还有很多模块通过串口与SOC相连。这篇文章让你彻彻底底,搞明白串口程序的编写。

没有基础的先看:

嵌入式Linux学习系列全部文章:嵌入式Linux学习—从裸机到应用教程大全 

目录

1. UART串口

1.1 UART硬件连接

1.2 UART软件通信协议

2. 读手册,编程序

2.1 找对应引脚

2.2 设置GPIO为UART功能

2.3 设置UART(初始化)

2.4 编写发送接收函数

3. 完整代码和验证

1. UART串口

全称:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,简称UART)是一种串行异步收发协议。

用的最多的地方就是开发板的串口连接电脑发送信息了,我们先看看电脑端什么样的:

1.1 UART硬件连接

UART硬件连接比较简单,仅需要3条线,如下图所示:

TX:发送数据端,要接对面设备的RX RX:接收数据端,要接对面设备的TX GND:保证两设备共地,有统一的参考平面

和电脑连接用这个东西:

 这个东西可以把RS232电平转换为TTL电平,为什么要转换电平,这里不赘述,随便搜一下就知道了。如果你的开发板集成了电平转换就不需要这个东西,直接用USB线连接到电脑。

1.2 UART软件通信协议

首先我们要知道UART协议中数据是一位一位(0或1)发送的,并且连续的一串数据被分成了一帧一帧发送的,下图便是一帧数据(不包含空闲位)。

uart传输数据的顺序就是:刚开始传输一个起始位,接着传输数据位,接着传输校验位(可不需要此位),最后传输停止位。这样一帧的数据就传输完了。接下来接着像上述过程一直传送。

协议如下:空闲位: UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平起始位: 开始进行数据传输时,发送方先发出一个低电平’0’,表示传输字符的开始。数据位: 起始位之后就是要传输的数据,数据可以是5,6,7,8,9位,构成一个字符,一般都是8位。

传输方向:即数据是从高位(MSB)开始传输还是从低位(LSB)开始传输。比如传输“A”如果是MSB那么就是01000001,如果是LSB那么就是10000010奇偶校验位: 数据位传送完成后,要进行奇偶校验,校验位其实是调整个数,串口校验分几种方式: 1.无校验(no parity) 2.奇校验(odd parity):如果数据位中’1’的数目是偶数,则校验位为’1’,如果’1’的数目是奇数,校验位为’0’。 3.偶校验(even parity):如果数据为中’1’的数目是偶数,则校验位为’0’,如果为奇数,校验位为’1’。 4.mark parity:校验位始终为1 5.space parity:校验位始终为0

以传输“A”(01000001)为例: 1、当为奇数校验:”A”字符的8个bit位中有两个1,那么奇偶校验位为1才能满足1的个数为奇数(奇校验)。 2、当为偶数校验:”A”字符的8个bit位中有两个1,那么奇偶校验位为0才能满足1的个数为偶数(偶校验)。 通过配置相应寄存器,此位可以去除,即不需要奇偶校验位。通常是不需要的。停止位: 数据结束标志,可以是1位,1.5位,2位的高电平。波特率: 数据传输速率使用波特率来表示,单位bps(bits per second),常见的波特率9600bps,115200bps等等,其他标准的波特率是1200,2400,4800,19200,38400,57600。

例如:串口波特率设置为9600bps,那么传输一个比特需要的时间是1/9600≈104.2us。

再例如:数据传送速率为120字符/秒,而每一个字符为10位(1个起始位,7个数据位,1个校验位,1个结束位),则其传送的波特率为10×120=1200位/秒=1200波特。

2. 读手册,编程序

2.1 找对应引脚

手册告诉我们S3C2440有三个UART,那么哪个能用呢?我们找开发板上那个转了USB的方便与电脑连接。你手上的可能不一样,随便用一个就行。

翻一翻开发板的原理图

找到了,我的开发板有串口转USB功能

接着看,RxD0和TxD0连到了S3C2440的哪个引脚,

搜索一下,找到了,GPH2和GPH3,我们就用他了。 

2.2 设置GPIO为UART功能

翻开S3C2440的数据手册,找到IO那一章。

 找到GPIOH的控制寄存器地址:0x56000070.

 GPH3配置为TXD0M,就是把GPIOH第6、7为分别置为1和0,GPH2同理。

代码就出来了

/* 设置引脚用于串口 */

/* GPH2,3用于TxD0, RxD0 */

volatile unsigned int *GPHCON=0x56000070;

*GPHCON &= ~((3<<4) | (3<<6));

*GPHCON |= ((2<<4) | (2<<6));

不懂volatile和位运算的可以看这篇:嵌入式C语言重点(const、static、voliatile、位运算)

别忘了,前面说过:

空闲位: UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平。

因此还得把端口内部上拉电阻设置一下,让他在空闲时,输出高电平

找到寄存器GPHUP的地址:0x56000078.

 把寄存器GPHUP第2、3位设置为0就行。

volatile unsigned int *GPHUP=0x56000078;

*GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */

2.3 设置UART(初始化)

根据第一部分内容,我们知道,要设置帧格式:校验位、停止位、数据长度、波特率

目标:校验位:无,停止位1,数据长度:8,波特率:115200

首先找到控制帧格式的寄存器:

ULCON0地址为0x50000000。

校验位:

校验位设置如上图,我们不需要校验位,刚好默认就是没有,不用设置了。

停止位:

停止位设置如上图,我们设置为1位停止位,刚好默认值也是1位,又不用设置了。

数据位:

我们想设置为8位长度。

这次不能用默认了,得把1、0位设置为1、1.

volatile unsigned int *ULCON0=0x50000000;

/* 设置数据格式 */

*ULCON0 = 0x00000003; /*8个数据位 */

波特率

UART clock可以用PCLK、FCLK\n、UEXTCLK,我们就用PCLK

我们想让波特率buad rate=115200,

根据上面公式,计算一下

UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1 UART clock = 50MUBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26

上图又表明UBRDIV0地址为0x50000028,代码就出来了。

volatile unsigned int *UBRDIV0=0x50000028;

*UBRDIV0 = 26;

UART模式

还得设置一下控制器,选择传送模式,UART支持DMA,但是我们不用。

包括上面提到的

UART clock可以用PCLK、FCLK\n、UEXTCLK,我们用PCLK也得设置一下

这两个设置都在UART控制寄存器。

 找到UCON0地址0x50000004.

 默认UART clock就是用PCLK,不用管了。

 我们用这个中断或轮询模式。

volatile unsigned int *UCON0=0x50000004;

*UCON0 = 0x00000005; /* PCLK,中断/查询模式 */

综合上述,得到UART初始化代码

volatile unsigned int *GPHCON=0x56000070;

volatile unsigned int *GPHUP=0x56000078;

volatile unsigned int *ULCON0=0x50000000;

volatile unsigned int *UBRDIV0=0x50000028;

volatile unsigned int *UCON0=0x50000004;

/* 设置引脚用于串口 */

/* GPH2,3用于TxD0, RxD0 */

*GPHCON &= ~((3<<4) | (3<<6));

*GPHCON |= ((2<<4) | (2<<6));

*GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */

/* 设置数据格式 */

*ULCON0 = 0x00000003; /* 8n1: 8个数据位, 无较验位, 1个停止位 */

/* 设置波特率 */

/* UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1

* UART clock = 50M

* UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26

*/

*UBRDIV0 = 26;

/* PCLK,中断/查询模式 */

*UCON0 = 0x00000005;

2.4 编写发送接收函数

UART发送和接收分别有寄存器来保存数据,同时又有相应的状态寄存器。可以读取状态寄存器的值来判断发送或者接收完数据没有。

这里就直接给出简单的发送接收代码,大家可以自己去芯片手册找到寄存器,要多读手册,才能提高水平。

int putchar(int c)

{

/* UTRSTAT0 */

/* UTXH0 */

while (!(UTRSTAT0 & (1<<2)));

UTXH0 = (unsigned char)c;

}

int getchar(void)

{

while (!(UTRSTAT0 & (1<<0)));

return URXH0;

}

int puts(const char *s)

{

while (*s)

{

putchar(*s);

s++;

}

}

3. 完整代码和验证

启动代码和makefile先给出,不知道怎么来的,先看一下我之前的两篇文章:

1.嵌入式Linux入门-从启动代码开始,真正从0开始点个灯

2.嵌入式Linux入门-读数据手册,设置时钟,让代码跑得更快

启动代码:

.text

.global _start

_start:

/* 关闭看门狗 */

ldr r0, =0x53000000

ldr r1, =0

str r1, [r0]

/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */

/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */

ldr r0, =0x4C000000

ldr r1, =0xFFFFFFFF

str r1, [r0]

/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */

ldr r0, =0x4C000014

ldr r1, =0x5

str r1, [r0]

/* 设置CPU工作于异步模式 */

mrc p15,0,r0,c1,c0,0

orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA

mcr p15,0,r0,c1,c0,0

/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)

* m = MDIV+8 = 92+8=100

* p = PDIV+2 = 1+2 = 3

* s = SDIV = 1

* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M

*/

ldr r0, =0x4C000004

ldr r1, =(92<<12)|(1<<4)|(1<<0)

str r1, [r0]

/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定

* 然后CPU工作于新的频率FCLK

*/

/* 设置内存: sp 栈 */

ldr sp, =4096 /* nand启动 */

bl main

halt:

b halt

Makefile:

all:

arm-linux-gcc -c -o uart.o uart.c

arm-linux-gcc -c -o start.o start.S

arm-linux-ld -Ttext 0 start.o uart.o -o uart.elf

arm-linux-objcopy -O binary -S uart.elf uart.bin

clean:

rm *.bin *.o *.elf

c代码:

在main函数中向电脑发个“Hello World”,并且回送电脑发过来的数据

#include

int putchar(int c)

{

/* UTRSTAT0 */

volatile unsigned int *UTRSTAT0=0x50000010;

volatile unsigned int *UTXH0=0x50000020;

/* UTXH0 */

while (!(*UTRSTAT0 & (1<<2)));

*UTXH0 = (unsigned char)c;

}

int getchar(void)

{

volatile unsigned int *UTRSTAT0=0x50000010;

volatile unsigned int *URXH0=0x50000024;

while (!(*UTRSTAT0 & (1<<0)));

return *URXH0;

}

int puts(const char *s)

{

while (*s)

{

putchar(*s);

s++;

}

}

int uart0_init(void)

{

volatile unsigned int *GPHCON=0x56000070;

volatile unsigned int *GPHUP=0x56000078;

volatile unsigned int *ULCON0=0x50000000;

volatile unsigned int *UBRDIV0=0x50000028;

volatile unsigned int *UCON0=0x50000004;

/* 设置引脚用于串口 */

/* GPH2,3用于TxD0, RxD0 */

*GPHCON &= ~((3<<4) | (3<<6));

*GPHCON |= ((2<<4) | (2<<6));

*GPHUP &= ~((1<<2) | (1<<3)); /* 使能内部上拉 */

/* 设置数据格式 */

*ULCON0 = 0x00000003; /* 8n1: 8个数据位, 无较验位, 1个停止位 */

/* 设置波特率 */

/* UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1

* UART clock = 50M

* UBRDIVn = (int)( 50000000 / ( 115200 x 16) ) –1 = 26

*/

*UBRDIV0 = 26;

/* PCLK,中断/查询模式 */

*UCON0 = 0x00000005;

}

int main(void)

{

unsigned char c;

uart0_init();

puts("Hello, world!\n\r");

while(1)

{

c = getchar();

if (c == '\r')

{

putchar('\n');

}

if (c == '\n')

{

putchar('\r');

}

putchar(c);

}

return 0;

}

make命令,得到二进制文件,烧写,结果:

 Hello,world出现了,随便输入也能回显,完美。

精彩链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。