嵌入式Linux入门-彻底理解UART串口,手把手教你写程序

发布于:2023-01-04 ⋅ 阅读:(775) ⋅ 点赞:(0)

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:保证两设备共地,有统一的参考平面

和电脑连接用这个东西:

usb转换板

 这个东西可以把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 = 50M
UBRDIVn = (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 <stdio.h>
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出现了,随便输入也能回显,完美。

本文含有隐藏内容,请 开通VIP 后查看