UART编程框架详解

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;elsereturn 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.rhkb.cn/news/385785.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序教程001:小程序简介

文章目录 学习目标小程序简介1、小程序和普通网页开发的区别2、注册小程序账号3、获取小程序的AppID4、安装开发者工具4.1 了解开发者工具4.2 下载开发工具 5、设置开发者工具外观 学习目标 如何创建小程序项目小程序项目的基本组成结构小程序页面由几部分组成小程序常见的组件…

小模型狂飙!6家巨头争相发布小模型,Andrej Karpathy:大语言模型的尺寸竞争正在倒退...

过去一周&#xff0c;可谓是小模型战场最疯狂的一周&#xff0c;商业巨头改变赛道&#xff0c;向大模型say byebye~。 OpenAI、Apple、Mistral等“百花齐放”&#xff0c;纷纷带着自家性能优越的轻量化小模型入场。 小模型(SLM)&#xff0c;是相对于大语言模型&#xff08;LLM…

sql注入详解【从数据库架构分析】

文章目录 简介数据库的架构sql注入概念正常语句正常回显页面在页面中使用sql语句 跨库查询sql文件读写影响条件复现读写的路径的问题 sql注入请求分类sql注入请求类型sql注入请求方式&#xff1a;sql注入数据请求格式 数据库的增删改查数据库查询数据库添加数据库删除数据库修改…

拓扑排序+dp(消除主观臆断)

这题一开始写错的原因就是搞错了&#xff0c;处于西边的节点的编号不一定小&#xff0c;不能直接dp&#xff0c;要先进行拓扑排序 写到一般我才发现&#xff0c;其实可以一边dp&#xff0c;一边进行dp #define _CRT_SECURE_NO_WARNINGS #include<bits/stdc.h> using name…

GPT-4o mini 震撼登场:开发者的新机遇与挑战

GPT-4o mini 震撼登场&#xff1a;开发者的新机遇与挑战 一、引言二、GPT-4o mini 模型的卓越性能三、极具竞争力的价格优势四、开发者的探索与实践五、提升开发效率和创新能力的策略六、面临的挑战与应对措施七、未来展望八、总结 在科技的浪潮中&#xff0c;OpenAI 最新推出的…

论文快过(图像配准|Coarse_LoFTR_TRT)|适用于移动端的LoFTR算法的改进分析 1060显卡上45fps

项目地址&#xff1a;https://github.com/Kolkir/Coarse_LoFTR_TRT 创建时间&#xff1a;2022年 相关训练数据&#xff1a;BlendedMVS LoFTR [19]是一种有效的深度学习方法&#xff0c;可以在图像对上寻找合适的局部特征匹配。本文报道了该方法在低计算性能和有限内存条件下的…

改进智能优化算法中的一个常见错误

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ ​昨天看到网上有一个流传很广的改进鲸鱼优化算法M…

vue3 使用Mock

官网: http://mockjs.com/ 安装 npm install mockjs -Dsteps1: main.js 文件引入 import /api/mock.jssteps2: src/api/mock.js import Mock from mockjs import homeApi from ./mockData/home /*** 1.拦截的路径:mock拦截了正常NetWork/网络请求,数据正常响应* 2.方法* …

货架管理a

路由->vue的el标签->Api->call方法里calljs的api接口->数据声明const xxxData-> 编辑按钮:点击跳出页面并把这一行的数据给到表单formDataba2 保存按钮:formDataba2改过的数据->xxApi发送->查询Api 跳转仓库:把tableData.value数据清空->callXxxAp…

传输层协议——TCP

TCP协议 TCP全称为“传输控制协议”&#xff0c;要对数据的传输进行一个详细的控制。 特点 面向连接的可靠性字节流 TCP的协议段格式 源/目的端口&#xff1a;表示数据从哪个进程来&#xff0c;到哪个进程4位首部长度&#xff1a;表示该TCP头部有多少字节&#xff08;注意它…

前后端分离项目部署,vue--nagix发布部署,.net--API发布部署。

目录 Nginx免安装部署文件包准备一、vue前端部署1、修改http.js2、npm run build 编译项目3、解压Nginx免安装,修改nginx.conf二、.net后端发布部署1、编辑appsetting.json,配置跨域请求2、配置WebApi,点击发布3、配置文件发布到那个文件夹4、配置发布相关选项5、点击保存,…

搭建自己的金融数据源和量化分析平台(三):读取深交所股票列表

深交所的股票信息读取比较简单&#xff1a; 看上图&#xff0c;爬虫读取到下载按钮的链接之后发起请求&#xff0c;得到XLS文件后直接解析就可以了。 这里放出深交所爬虫模块的代码&#xff1a; # -*- coding: utf-8 -*- # 深圳交易所爬虫 import osimport pandas as pd imp…

Python代码格式化工具库之black使用详解

概要 在软件开发过程中,代码风格和一致性对于提高代码可读性和可维护性至关重要。Python 作为一种高度可读的语言,有多种代码风格指南,但手动保持代码风格的一致性可能会非常耗时且容易出错。black 是一个 Python 代码格式化工具,旨在通过自动格式化代码,使其符合 PEP 8 …

深入浅出mediasoup—WebRtcTransport

mediasoup 提供了多种 transport&#xff0c;包括 WebRtcTransport、PipeTransport、DirectTransport、PlainTransport 等&#xff0c;用来实现不同目的和场景的媒体通信。WebRtcTransport 是 mediasoup 实现与 WebRTC 客户端进行媒体通信的对象&#xff0c;是 mediasoup 最重要…

Clickhouse 生产集群部署(Centos 环境)

文章目录 机器环境配置安装 JDK 8安装 zookeeperClickhouse 集群安装rpm 包离线安装修改全局配置zookeeper配置Shard和Replica设置image.png添加macros配置启动 clickhouse启动 10.82.46.135 clickhouse server启动 10.82.46.163 clickhouse server启动 10.82.46.218 clickhous…

[网络通信原理]——TCP/IP模型—网络层

网络层 网络层概述 网络层位于OSI模型的第三层&#xff0c;它定义网络设备的逻辑地址&#xff0c;也就是我们说的IP地址&#xff0c;能够在不同的网段之间选择最佳数据转发路径。在网络层中有许多协议&#xff0c;其中主要的协议是IP协议。 IP数据包格式 IP数据报是可变长度…

汽车长翅膀:GPU 是如何加速深度学习模型的训练和推理过程的?

编者按&#xff1a;深度学习的飞速发展离不开硬件技术的突破&#xff0c;而 GPU 的崛起无疑是其中最大的推力之一。但你是否曾好奇过&#xff0c;为何一行简单的“.to(‘cuda’)”代码就能让模型的训练速度突飞猛进&#xff1f;本文正是为解答这个疑问而作。 作者以独特的视角&…

如何使用代理IP进行电子邮件保护?

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 前言 随着企业信息化的深入发展&#xff0c;电子邮件在私人生活和商业运营中起到越来越重要的作用&#xff0c;随之而来电子邮件…

【编程工具使用技巧】VS如何显示行号

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《编程工具与技巧探索》 期待您的关注 目录 引言 一、VS编译器行号显示的基本步骤 1.打开VS与项目 2.进入选项设置 3.找到并…

LeetCode 637, 67, 399

文章目录 637. 二叉树的层平均值题目链接标签思路代码 67. 二进制求和题目链接标签思路代码 399. 除法求值题目链接标签思路导入value 属性find() 方法union() 方法query() 方法 代码 637. 二叉树的层平均值 题目链接 637. 二叉树的层平均值 标签 树 深度优先搜索 广度优先…