草庐IT

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

闪耀大叔 2025-05-20 原文

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 = 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出现了,随便输入也能回显,完美。

有关嵌入式Linux入门-彻底理解UART串口,手把手教你写程序的更多相关文章

  1. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  2. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  3. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  4. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  5. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  6. 区块链入门教程(6)--WeBASE-Front节点前置服务安装 - 2

    文章目录1.任务背景2.任务目标3.相关知识点4.任务实操4.1安装配置JDK4.2启动FISCOBCOS4.3下载解压WeBASE-Front4.4拷贝sdk证书文件4.5启动节点4.6访问节点4.7检查运行状态5.任务总结1.任务背景FISCOBCOS其实是有控制台管理工具,用来对区块链系统进行各种管理操作。但是对于初学者来说,还是可视化界面更友好,本节就来介绍WeBASE管理平台,这是一款微众银行开源的自研区块链中间件平台,可以降低区块链使用的门槛,大幅提高区块链应用的开发效率。微众银行是腾讯牵头设立的民营银行,在国内民营银行里还是比较出名的。微众银行参与FISCOBCOS生态建设,一定

  7. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  8. Simulink方法总结和避坑指南(一)——Simulink入门与基本调试方法 - 2

    文章目录一、项目场景二、基本模块原理与调试方法分析——信源部分:三、信号处理部分和显示部分:四、基本的通信链路搭建:四、特殊模块:interpretedMATLABfunction:五、总结和坑点提醒一、项目场景  最近一个任务是使用simulink搭建一个MIMO串扰消除的链路,并用实际收到的数据进行测试,在搭建的过程中也遇到了不少的问题(当然这比vivado里面的debug好不知道多少倍)。准备趁着这个机会,先以一个很基本的通信链路对simulink基础和相关的debug方法进行总结。  在本篇中,主要记录simulink的基本原理和基本的SISO通信传输链路(QPSK方式),计划在下篇记

  9. 【Linux操作系统】——网络配置与SSH远程 - 2

    Linux操作系统——网络配置与SSH远程安装完VMware与系统后,需要进行网络配置。第一个目标为进行SSH连接,可以从本机到VMware进行文件传送,首先需要进行网络配置。1.下载远程软件首先需要先下载安装一款远程软件:FinalShell或者xhell7FinalShellxhell7FinalShell下载:Windows下载http://www.hostbuf.com/downloads/finalshell_install.exemacOS下载http://www.hostbuf.com/downloads/finalshell_install.pkg2.配置CentOS网络安装好

  10. Linux磁盘分区中物理卷(PV)、卷组(VG)、逻辑卷(LV)创建和(LVM)管理 - 2

    文章目录一基础定义二创建逻辑卷2-1准备物理设备2-2创建物理卷2-3创建卷组2-4创建逻辑卷2-5创建文件系统并挂载文件三扩展卷组和缩减卷组3-1准备物理设备3-2创建物理卷3-3扩展卷组3-4查看卷组的详细信息以验证3-5缩减卷组四扩展逻辑卷4-1检查卷组是否有可用的空间4-2扩展逻辑卷4-3扩展文件系统五删除逻辑卷5-1备份数据5-2卸载文件系统5-3删除逻辑卷5-4删除卷组5-5删除物理卷六LVM逻辑卷缩容6-1缩容注意事项6-2标准缩容步骤一基础定义LVM,LogicalVolumeManger,逻辑卷管理,Linux磁盘分区管理的一种机制,建立在硬盘和分区上的一个逻辑层,提高磁盘分

随机推荐