UART编程框架详解

news2024/9/28 13:18:51

1. UART介绍

UART:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),简称串口。

  • 调试:移植u-boot、内核时,主要使用串口查看打印信息

  • 外接各种模块

 1.1 硬件知识_UART硬件介绍

UART的全称是Universal Asynchronous Receiver and Transmitter,即异步发送和接收。 串口在嵌入式中用途非常的广泛,主要的用途有:

  • 打印调试信息;

  • 外接各种模块:GPS、蓝牙;

串口因为结构简单、稳定可靠,广受欢迎。

通过三根线即可,发送、接收、地线。

TxD线把PC机要发送的信息发送给ARM开发板。 最下面的地线统一参考地。

1.2 串口的参数

  • 波特率:一般选波特率都会有9600,19200,115200等选项。其实意思就是每秒传输这么多个比特位数(bit)。

  • 起始位:先发出一个逻辑”0”的信号,表示传输数据的开始。

  • 数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。小端传输。

  • 校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。

  • 停止位:它是一个字符数据的结束标志。

怎么发送一字节数据,比如‘A‘? ‘A’的ASCII值是0x41,二进制就是01000001,怎样把这8位数据发送给PC机呢?

  • 双方约定好波特率(每一位占据的时间);

  • 规定传输协议

    • 原来是高电平,ARM拉低电平,保持1bit时间;

    • PC在低电平开始处计时;

    • ARM根据数据依次驱动TxD的电平,同时PC依次读取RxD引脚电平,获得数据;

 前面图中提及到了逻辑电平,也就是说代表信号1的引脚电平是人为规定的。 如图是TTL/CMOS逻辑电平下,传输‘A’时的波形:

在xV至5V之间,就认为是逻辑1,在0V至yV之间就为逻辑0。

如图是RS-232逻辑电平下,传输‘A’时的波形:

在-12V至-3V之间,就认为是逻辑1,在+3V至+12V之间就为逻辑0。

RS-232的电平比TTL/CMOS高,能传输更远的距离,在工业上用得比较多。

市面上大多数ARM芯片都不止一个串口,一般使用串口0来调试,其它串口来外接模块。

1.3 串口电平

ARM芯片上得串口都是TTL电平的,通过板子上或者外接的电平转换芯片,转成RS232接口,连接到电脑的RS232串口上,实现两者的数据传输。

现在的电脑越来越少有RS232串口的接口,当USB是几乎都有的。因此使用USB串口芯片将ARM芯片上的TTL电平转换成USB串口协议,即可通过USB与电脑数据传输。

1.4 串口内部结构

ARM芯片是如何发送/接收数据? 如图所示串口结构图:

要发送数据时,CPU控制内存要发送的数据通过FIFO传给UART单位,UART里面的移位器,依次将数据发送出去,在发送完成后产生中断提醒CPU传输完成。 接收数据时,获取接收引脚的电平,逐位放进接收移位器,再放入FIFO,写入内存。在接收完成后产生中断提醒CPU传输完成。

图片中描述的是串行通信中的一个典型的串行接口(Peripheral Bus)结构,特别是关于数据发送和接收的组件。以下是对图片内容的解释:

  1. 发送器(发送器):

    • 负责将数据从串行接口发送出去。
  2. 发送FIFO寄存器:

    • 在FIFO(先进先出)模式下,发送FIFO寄存器用于暂存待发送的数据。
  3. 发送缓冲寄存器:

    • 这是一个64字节的缓冲区,用于存储即将通过发送器发送的数据。
  4. 发送保持寄存器:

    • 在非FIFO模式下,发送保持寄存器用于存储下一个要发送的字节。
  5. 发送移位器:

    • 负责将数据按位(bit)顺序移出,以串行方式发送。
  6. TXDn:

    • 表示发送数据线,数据通过这条线发送到外部设备。
  7. 控制:

    • 涉及串行通信的控制机制,如波特率、时钟源等。
  8. 波特率:

    • 串行通信中数据传输的速率,以比特每秒(bps)计量。
  9. 时钟源:

    • 为串行通信提供时钟信号的源,可以是PCLK(外设时钟)、FCLK(功能时钟)或UEXTCLK(外部时钟)。
  10. 单元产生器:

    • 可能是指控制单元,用于生成控制信号以管理数据传输。
  11. 接收器:

    • 负责接收来自外部设备的数据。
  12. 接收移位器:

    • 负责将接收到的串行数据按位顺序移入。
  13. RXDn:

    • 表示接收数据线,数据通过这条线接收到设备中。
  14. 接收保持寄存器:

    • 在非FIFO模式下,接收保持寄存器用于存储刚刚接收到的字节。
  15. 接收缓冲寄存器:

    • 这是一个64字节的缓冲区,用于存储接收到的数据。
  16. 接收FIFO寄存器:

    • 在FIFO模式下,接收FIFO寄存器用于暂存接收到的数据。
  17. FIFO模式与非FIFO模式:

    • FIFO模式允许使用整个64字节的缓冲寄存器作为FIFO,以暂存大量数据。
    • 非FIFO模式下,只使用缓冲寄存器中的1字节作为保持寄存器,用于存储单个数据字节。

在串行通信中,数据通常通过发送器和接收器在设备之间传输。FIFO模式和非FIFO模式决定了数据如何被存储和检索。FIFO模式适用于数据传输速率较高且需要缓冲大量数据的情况,而非FIFO模式则适用于数据传输速率较低或不需要大量缓冲的情况。

2.  Linux串口应用编程

在Linux系统中,操作设备的统一接口就是:open/ioctl/read/write。

对于UART,又在ioctl之上封装了很多函数,主要是用来设置行规程。

所以对于UART,编程的套路就是:

  • open

  • 设置行规程,比如波特率、数据位、停止位、检验位、RAW模式、一有数据就返回

  • read/write

编写驱动程序的套路:

  • 确定主设备号,也可以让内核分配

  • 定义自己的file_operations结构体

  • 实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体

  • 把file_operations结构体告诉内核:register_chrdev

  • 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数

  • 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev

  • 其他完善:提供设备信息,自动创建设备节点:class_create, device_create

 3. UART驱动框架分析

3.1 UART驱动情景分析_注册

 

3.2 UART驱动情景分析_open

它要做的事情:

  • 找到tty_driver

    • 分配/设置tty_struct

    • 行规程相关的初始化

3.3 UART驱动情景分析_read

串行通信中的"行规程":

在串行通信中,"行规程"特别指的是与串行数据传输相关的一系列设置,这些设置定义了数据如何被发送和接收。这包括:

  • 波特率:数据传输的速率,以比特每秒(bps)计量。
  • 数据位:每个字符的数据位长度,通常为7、8或9位。
  • 停止位:数据字符之间的可选额外位,用于标识字符边界,可以是1或2位。
  • 校验位:用于错误检测的位,可以是无校验、奇校验或偶校验。
  • 流控制:如XON/XOFF或RTS/CTS,用于控制数据流。

在Linux和其他操作系统中,行规程可以通过设置串行端口的属性来配置,这些属性通常通过termios结构体进行控制。

read过程分析:

流程为:

  • APP读

    • 使用行规程来读

    • 无数据则休眠

  • UART接收到数据,产生中断

    • 中断程序从硬件上读入数据

  • 发给行规程

    • 行规程处理后存入buffer

    • 行规程唤醒APP

  • APP被唤醒后,从行规程buffer中读入数据,返回

3.4 UART驱动情景分析_write

流程为:

  • APP写

    • 使用行规程来写

    • 数据最终存入uart_state->xmit的buffer里

  • 硬件发送:怎么发送数据?

    • 使用硬件驱动中uart_ops->start_tx开始发送

    • 具体的发送方法有2种:通过DMA,或通过中断

  • 中断方式

    • 方法1:直接使能 tx empty中断,一开始tx buffer为空,在中断里填入数据

    • 方法2:写部分数据到tx fifo,使能中断,剩下的数据再中断里继续发送

4.  编写虚拟UART驱动程序_框架

4.1  编写UART驱动要做的事

  • 注册一个uart_driver:它里面有名字、主次设备号等

  • 对于每一个port,调用uart_add_one_port,里面的核心是uart_ops,提供了硬件操作函数

    • uart_add_one_port由platform_driver的probe函数调用

    • 所以:

      • 编写设备树节点

      • 注册platform_driver

4.2 源码分析

#include <linux/module.h>       // 模块化编程支持
#include <linux/ioport.h>       // I/O端口支持
#include <linux/init.h>        // 模块初始化和清理宏
#include <linux/console.h>     // 控制台支持
#include <linux/sysrq.h>       // 系统请求键支持
#include <linux/platform_device.h> // 平台设备支持
#include <linux/tty.h>         // TTY支持
#include <linux/tty_flip.h>    // TTY翻转缓冲区支持
#include <linux/serial_core.h> // 串行核心支持
#include <linux/serial.h>      // 串行硬件支持
#include <linux/clk.h>         // 时钟支持
#include <linux/delay.h>       // 延时支持
#include <linux/rational.h>    // 有理数支持
#include <linux/reset.h>       // 重置支持
#include <linux/slab.h>        // 内存分配
#include <linux/of.h>         // 设备树支持
#include <linux/of_device.h>   // 设备树设备支持
#include <linux/io.h>          // IO操作
#include <linux/dma-mapping.h> // DMA内存映射
#include <linux/proc_fs.h>     // 进程文件系统

#include <asm/irq.h>           // 特定体系结构的中断支持

#define BUF_LEN  1024          // 定义缓冲区长度为1024字节
#define NEXT_PLACE(i) ((i+1)&0x3FF) // 循环缓冲区的索引计算宏

// 定义全局变量
static struct uart_port	*virt_port; // 虚拟UART端口
static unsigned char txbuf[BUF_LEN]; // 发送缓冲区
static int tx_buf_r = 0;            // 发送缓冲区读索引
static int tx_buf_w = 0;            // 发送缓冲区写索引
static unsigned char rxbuf[BUF_LEN]; // 接收缓冲区
static int rx_buf_w = 0;            // 接收缓冲区写索引
static struct proc_dir_entry *uart_proc_file; // 串行控制台的proc文件

// 虚拟UART驱动结构体
static struct uart_driver virt_uart_drv = {
	.owner          = THIS_MODULE, // 驱动的模块所有者
	.driver_name    = "VIRT_UART", // 驱动名称
	.dev_name       = "ttyVIRT",   // 设备名称
	.major          = 0,          // 主设备号
	.minor          = 0,          // 次设备号
	.nr             = 1,          // 设备数量
};

// 循环缓冲区操作函数
static int is_txbuf_empty(void) // 检查发送缓冲区是否为空
{
	return tx_buf_r == tx_buf_w;
}

static int is_txbuf_full(void) // 检查发送缓冲区是否已满
{
	return NEXT_PLACE(tx_buf_w) == tx_buf_r;
}

static int txbuf_put(unsigned char val) // 向发送缓冲区添加数据
{
	if (is_txbuf_full())
		return -1;
	txbuf[tx_buf_w] = val;
	tx_buf_w = NEXT_PLACE(tx_buf_w);
	return 0;
}

static int txbuf_get(unsigned char *pval) // 从发送缓冲区读取数据
{
	if (is_txbuf_empty())
		return -1;
	*pval = txbuf[tx_buf_r];
	tx_buf_r = NEXT_PLACE(tx_buf_r);
	return 0;
}

static int txbuf_count(void) // 计算发送缓冲区中的数据量
{
	if (tx_buf_w >= tx_buf_r)
		return tx_buf_w - tx_buf_r;
	else
		return BUF_LEN + tx_buf_w - tx_buf_r;
}

// 虚拟UART的文件操作函数
ssize_t virt_uart_buf_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	// 实现从虚拟UART读取数据到用户空间的函数
	// ...
}

static ssize_t virt_uart_buf_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
	// 实现从用户空间写入数据到虚拟UART的函数
	// ...
}

static const struct file_operations virt_uart_buf_fops = {
	.read = virt_uart_buf_read, // 读取操作
	.write = virt_uart_buf_write, // 写入操作
};

// 虚拟UART的中断处理函数和相关操作
static unsigned int virt_tx_empty(struct uart_port *port)
{
	// 检查发送缓冲区是否为空
	// ...
}

static void virt_start_tx(struct uart_port *port)
{
	// 开始发送数据
	// ...
}

// 其他虚拟UART操作函数
static void virt_set_termios(struct uart_port *port, struct ktermios *termios,
		struct ktermios *old)
{
	// 设置终端参数
	// ...
}

static int virt_startup(struct uart_port *port)
{
	// 启动虚拟UART
	// ...
}

// 虚拟UART驱动的probe和remove函数
static int virtual_uart_probe(struct platform_device *pdev)
{
	// 虚拟UART设备探测函数
	// ...
}

static int virtual_uart_remove(struct platform_device *pdev)
{
	// 虚拟UART设备移除函数
	// ...
}

// 虚拟UART驱动的入口和出口函数
static int __init virtual_uart_init(void)
{
	// 虚拟UART驱动的初始化函数
	// ...
}

static void __exit virtual_uart_exit(void)
{
	// 虚拟UART驱动的退出函数
	// ...
}

module_init(virtual_uart_init); // 注册初始化函数
module_exit(virtual_uart_exit); // 注册退出函数

MODULE_LICENSE("GPL"); // 模块许可证

代码解释:

  • 头文件包含:代码包括了处理模块化编程、I/O端口、初始化、控制台、系统请求键、平台设备、TTY、串行通信、时钟、延时、有理数、重置、内存分配、设备树、IO操作、DMA内存映射和进程文件系统等所需的头文件。
  • 宏定义:定义了缓冲区长度和循环缓冲区索引计算的宏。
  • 全局变量:定义了虚拟UART端口、发送和接收缓冲区、proc文件系统条目等全局变量。
  • 虚拟UART驱动结构体:定义了一个uart_driver结构体,包含了驱动的名称、设备名称、设备号和设备数量。
  • 循环缓冲区操作函数:实现了一组函数,用于管理发送缓冲区的状态,包括检查缓冲区是否为空或满,以及添加和读取数据。
  • 文件操作函数:实现了虚拟UART的读取和写入操作,用于与用户空间交换数据。
  • 虚拟中断处理和UART操作:实现了虚拟UART的中断处理函数和一系列UART操作函数,包括发送数据、接收数据、设置终端参数等。
  • 平台设备探测和移除函数:实现了平台设备的探测和移除函数,用于初始化和清理虚拟UART设备。
  • 模块初始化和退出函数:实现了模块的初始化和退出函数,用于注册和注销虚拟UART驱动。

这个模块实现了一个虚拟的UART驱动程序,它可以模拟UART硬件的行为,对于嵌入式系统开发中的串行通信测试和调试非常有用。通过这种方式,开发者可以在没有实际硬件的情况下开发和测试UART相关的软件。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1949028.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OrangePi Zero2 全志H616 开发初探

目录&#xff1a; 一、刷机和系统启动1、TF 卡格式化&#xff1a;2、镜像烧录&#xff1a;3、登录系统&#xff1a; 二、基于官方外设开发1、wiringPi 外设 SDK 安装&#xff1a;2、C 文件编译&#xff1a;3、基于官方外设的应用开发&#xff1a;① GPIO 输入输出&#xff1a;②…

ROS2入门到精通—— 2-11 ROS2实战:实现基于voronoi_planner的全局规划(一)!!!保姆级教程

实现基于voronoi_planner的全局规划将分为两篇博文进行讲解 本文参考该大佬代码: https://github.com/nkuwenjian/voronoi_planner.githttps://github.com/nkuwenjian/voronoi_layer.git将上面的ROS1代码移植到ROS2,移植不易,中间遇到很多坑 0 前言 针对一些狭窄区域,可能…

PostgreSQL的pg-collector工具

PostgreSQL的pg-collector工具 pg-collector 是一个用于 PostgreSQL 数据库的监控和数据收集工具。它主要用于收集 PostgreSQL 实例的性能指标、查询统计和日志信息&#xff0c;以便进行数据库性能分析和故障排查。通过收集这些数据&#xff0c;管理员可以更好地了解数据库的运…

『 Linux 』用户态与内核态的转换机制及信号检测时机

文章目录 用户态与内核态进程地址空间操作系统的本质 信号的处理时机 用户态与内核态 进程在执行代码的过程中代码必定涉及用户代码,库函数代码及操作系统内核代码; 以简单的printf()函数为例,该函数必定为先执行用户的代码即知道需要调用printf()函数,再执行库(如libc)中的代码…

InsCode GPU服务器快速使用

文章目录 1. 背景介绍2. 环境配置 1. 背景介绍 InsCode服务器地址&#xff1a;https://inscode.csdn.net/workbench?tabcomputed。 2. 环境配置 新建环境后&#xff0c;按照如下步骤快速配置&#xff0c;以便后续执行深度学习模型训练。 数据 openlane 环境依赖 Copy Mini…

切换数据失败0x1671分析

1、问题背景 切换双卡数据开关&#xff0c;无法切换成功&#xff0c;且单机必现该问题 2、问题分析 搜索Log发现相关拨号无法建立成功&#xff0c;返回0x1671&#xff0c;无法建立PDN连接。 相关拨号上层未下发相关AT命令&#xff0c;属于上层报错&#xff0c;并非网络问题&…

《系统架构设计师教程(第2版)》第12章-信息系统架构设计理论与实践-05-信息系统架构案例分析

文章目录 1. 价值驱动的体系结构——连接产品策略与体系结构1.1 价值模型1&#xff09;概述2&#xff09;价值驱动因素3&#xff09;传统方法识别价值模型的缺陷&#xff08;了解即可&#xff09; 1.2 体系结构策略&#xff08;挑战&#xff09;1&#xff09; 优先级的确定2&am…

厚积薄发,详解 IoTeX 2.0 如何推动 DePIN 赛道迈向新台阶

背 景 DePIN 是加密货币行业的一个新兴垂直领域&#xff0c;也是本轮牛市最重要的叙事之一。DePIN 通常通过发行和分配代币来激励参与者&#xff0c;用户可以通过提供资源、维护网络、参与治理等方式获得代币奖励并产生直接的经济收益&#xff0c;从而重新洗牌财富分配方…

友思特应用 | 硅片上的光影贴合:UV-LED曝光系统在晶圆边缘曝光中的高效应用

导读 晶圆边缘曝光是帮助减少晶圆涂布过程中多余的光刻胶对电子器件影响的重要步骤。友思特 ALE/1 和 ALE/3 UV-LED 高性能点光源&#xff0c;作为唯一可用于宽带晶圆边缘曝光的 i、h 和 g 线的 LED 解决方案&#xff0c;可高效实现WEE系统设计和曝光需求。 晶圆边缘曝光及处…

GRE VPN和MGRE VPN综合练习

GRE VPN和MGRE VPN综合练习 实验拓扑 实验要求 1、R5为ISP&#xff0c;只能进行IP地址配置&#xff0c;其所有地址均配为公有IP地址; 2、R1和R5间使用PPP的PAP认证&#xff0c;R5为主认证方; R2与R5之间使用ppp的CHAP认证&#xff0c;R5为主认证方; R3与R5之间使用HDLC封装;…

【leetcode】两数相加【中等】(C++递归解法)

总体来说&#xff0c;链表类问题往往是蛮适合用递归的方式求解的 要写出有效的递归&#xff0c;关键是要考虑清楚&#xff1a; 0. return的条件 1. 每步递归的操作&#xff0c;以及何时调用下一步递归 2. 鲁棒性&#xff08;头&#xff0c;尾结点等特殊情况是否依旧成立&am…

Hadoop学习笔记1

hadoop节点规划 服务器集群规划&#xff0c;6台服务器&#xff1a; 一个主节点 两个从节点 三个工作节点 集群服务器用的都是centos7.9.2009的镜像 一、基础环境 1.1配置阿里云yum源 1.下载repo文件 wget http://mirrors.aliyun.com/repo/Centos-7.repo 2.备份并替换系…

Tensorflow深度学习总结

1.正态分布和高斯分布 正态分布&#xff08;Normal Distribution&#xff09;和高斯分布&#xff08;Gaussian Distribution&#xff09;实际上指的是同一个概率分布&#xff0c;在统计学和概率论中是最常见和最重要的连续概率分布之一。这两个术语可以互换使用&#xff0c;但…

【Linux】网络通信基础:应用层协议、HTTP、序列化与会话管理

文章目录 前言1. 应用层自定义协议与序列化1.1 什么是应用层&#xff1f;1.2 再谈 "协议"1.3 序列化 和 反序列化 2. HTTP 协议3. 认识 URL(统一资源定位符)4. urlencode和urldecode5. HTTP 协议请求与响应格式5.1 HTTP 请求5.2 HTTP 响应 6. HTTP 的方法6.1 GET 方法…

【BUG】已解决:AttributeError: module ‘sys‘ has no attribute ‘setdefaultencoding‘

AttributeError: module ‘sys‘ has no attribute ‘setdefaultencoding‘ 目录 AttributeError: module ‘sys‘ has no attribute ‘setdefaultencoding‘ 【常见模块错误】 【解决方案】 原因分析&#xff1a; 解决方案&#xff1a; 示例代码&#xff1a; 总结&#…

「JavaEE」Spring MVC:基本操作1

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;JavaEE &#x1f387;欢迎点赞收藏加关注哦&#xff01; 简介 Spring Web MVC 是⼀个 Web 框架&#xff0c;简称为 Spring MVC MVC 是 Model View Controller 的缩写&#xff0c;它是软件工程…

CSS(七)——CSS 列表和CSS Table(表格)

目录 CSS 列表 列表 作为列表项标记的图像 列表 - 简写属性 移除默认设置 所有的CSS列表属性 CSS 表格 表格边框 折叠边框&#xff08;border-collapse&#xff09; 表格宽度和高度 表格文字对齐 表格填充 表格颜色 CSS 列表 CSS 列表属性作用如下&#xff1a; 设…

反射型与dom型的xss的区别【源码分析】

反射型 XSS 和 DOM 型 XSS 都属于跨站脚本攻击 (XSS) 的类型&#xff0c;它们的共同点是均能通过注入恶意脚本在用户浏览器中执行&#xff0c;不同点是dom型xss不经过服务器&#xff0c;而反射型是经过服务器的。但是&#xff0c;它们在攻击方式、执行过程和防御措施上有所不同…

基于TensorFlow.js和COCO-SsD模型的实时目标检测网络应用程序

基于TensorFlow.js和COCO-SsD模型的实时目标检测网络应用程序 实现流程 访问用户的桌面录屏并且显示视频源&#xff08;位置居中&#xff09;。对视频源进行实时目标检测。在检测到的目标周围绘制边界框&#xff0c;并用它们的类别和检测置信度进行标记。在视频源下方显示一个…

关于SpringBoot项目利用阿里EasyExcel快捷导入Excel文件入库初始化数据的简单实现

一、问题描述 无论新项目还是旧项目&#xff0c;都会出现数据维护、数据初始化等操作&#xff0c;手动录显然很low(领导会骂你)&#xff0c;所以一般采用批量导入导出。这里你还在用原始读取excel逐行逐列去读取吗&#xff1f;2024了ok&#xff1f;利用工具是我们cv大师的一贯…