当前位置: 首页 > news >正文

Linux学习笔记--UART子系统

set_opt() - 串口参数设置

int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{struct termios newtio,oldtio;if ( tcgetattr( fd,&oldtio) != 0) { perror("SetupSerial 1");return -1;}bzero( &newtio, sizeof( newtio ) );newtio.c_cflag |= CLOCAL | CREAD; newtio.c_cflag &= ~CSIZE; newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/newtio.c_oflag  &= ~OPOST;   /*Output*/switch( nBits ){case 7:newtio.c_cflag |= CS7;break;case 8:newtio.c_cflag |= CS8;break;}switch( nEvent ){case 'O':newtio.c_cflag |= PARENB;newtio.c_cflag |= PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;case 'E': newtio.c_iflag |= (INPCK | ISTRIP);newtio.c_cflag |= PARENB;newtio.c_cflag &= ~PARODD;break;case 'N': newtio.c_cflag &= ~PARENB;break;}switch( nSpeed ){case 2400:cfsetispeed(&newtio, B2400);cfsetospeed(&newtio, B2400);break;case 4800:cfsetispeed(&newtio, B4800);cfsetospeed(&newtio, B4800);break;case 9600:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;case 115200:cfsetispeed(&newtio, B115200);cfsetospeed(&newtio, B115200);break;default:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;}if( nStop == 1 )newtio.c_cflag &= ~CSTOPB;else if ( nStop == 2 )newtio.c_cflag |= CSTOPB;newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: * 比如VMIN设为10表示至少读到10个数据才返回,* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)* 假设VTIME=1,表示: *    10秒内一个数据都没有的话就返回*    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回*/tcflush(fd,TCIFLUSH);if((tcsetattr(fd,TCSANOW,&newtio))!=0){perror("com set error");return -1;}//printf("set done!\n");return 0;
}

参数说明:

  • fd: 串口文件描述符

  • nSpeed: 波特率(2400,4800,9600,115200)

  • nBits: 数据位(7或8)

  • nEvent: 校验位('N'无校验,'O'奇校验,'E'偶校验)

  • nStop: 停止位(1或2)

功能特点:

  • 设置原始模式(非规范模式)

  • 禁用回显和信号处理

  • 配置数据位、停止位、校验位

  • 设置最小读取字节数和超时

open_port() - 打开串口

int open_port(char *com)
{int fd;//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);fd = open(com, O_RDWR|O_NOCTTY);if (-1 == fd){return(-1);}if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/{printf("fcntl failed!\n");return -1;}return fd;
}
  • 以读写方式打开串口设备

  • 设置为阻塞模式

int main(int argc, char **argv)
{int fd;int iRet;char c;/* 1. open *//* 2. setup * 115200,8N1* RAW mode* return data immediately*//* 3. write and read */if (argc != 2){printf("Usage: \n");printf("%s </dev/ttySAC1 or other>\n", argv[0]);return -1;}fd = open_port(argv[1]);if (fd < 0){printf("open %s err!\n", argv[1]);return -1;}iRet = set_opt(fd, 9600, 8, 'N', 1);if (iRet){printf("set port err!\n");return -1;}printf("Enter a char: ");while (1){scanf("%c", &c);iRet = write(fd, &c, 1);iRet = read(fd, &c, 1);if (iRet == 1)printf("get: %02x %c\n", c, c);elseprintf("can not get data\n");}return 0;
}
fd = open_port(argv[1]);  // 打开指定的串口设备

配置串口参数

iRet = set_opt(fd, 115200, 8, 'N', 1);  // 115200,8N1配置

数据收发循环

while (1) {scanf("%c", &c);           // 从用户获取字符iRet = write(fd, &c, 1);   // 发送到串口iRet = read(fd, &c, 1);    // 从串口读取if (iRet == 1)printf("get: %02x %c\n", c, c);  // 显示接收到的数据elseprintf("can not get data\n");
}

VMIN和VTIME组合

VMINVTIME行为
00立即返回,读取可用数据
10阻塞直到收到1个字节
11最多等待0.1秒,收到1个字节立即返回
01最多等待0.1秒读取数据

读取GPS数据

read_gps_raw_data() - 读取GPS原始数据

int read_gps_raw_data(int fd, char *buf)
{int i = 0;int iRet;char c;int start = 0;while (1){iRet = read(fd, &c, 1);  // 每次读取1个字符if (iRet == 1){if (c == '$')        // 检测到NMEA语句起始符start = 1;if (start)           // 开始记录数据{buf[i++] = c;}if (c == '\n' || c == '\r')  // 检测到行结束符return 0;}else{return -1;           // 读取失败}}
}

工作流程:

  1. 逐个字符读取串口数据

  2. 遇到$符号开始记录(NMEA语句起始符)

  3. 持续记录直到遇到换行符\n或回车符\r

  4. 返回完整的NMEA语句

parse_gps_raw_data() - 解析GPS数据

int parse_gps_raw_data(char *buf, char *time, char *lat, char *ns, char *lng, char *ew)
{char tmp[10];if (buf[0] != '$')              // 检查起始符return -1;else if (strncmp(buf+3, "GGA", 3) != 0)  // 检查是否为GPGGA语句return -1;else if (strstr(buf, ",,,,,"))  // 检查数据是否有效{printf("Place the GPS to open area\n");return -1;}else {// 解析逗号分隔的字段sscanf(buf, "%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]", tmp, time, lat, ns, lng, ew);return 0;}
}

解析逻辑:

  • 验证数据格式正确性

  • 只处理GPGGA语句(全球定位系统固定数据)

  • 检查数据有效性(避免解析无效定位数据)

  • 使用sscanf%[^,]格式提取逗号分隔的字段

NMEA GPGGA数据格式

示例数据:
$GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76

各字段含义:

  • $GPGGA: 语句头

  • 082559.00: UTC时间(08:25:59.00)

  • 4005.22599: 纬度(40度05.22599分)

  • N: 北纬

  • 11632.58234: 经度(116度32.58234分)

  • E: 东经

  • 1: 定位质量指示器

  • 04: 使用的卫星数量

  • 3.08: 水平精度因子

  • 14.6: 海拔高度

  • M: 单位(米)

  • -5.6: 大地水准面高度

  • M: 单位(米)

  • *76: 校验和

经纬度格式转换

原始格式 → 十进制度格式

纬度转换:

/* 纬度格式: ddmm.mmmm */
sscanf(Lat+2, "%f", &fLat);      // 提取分钟部分(跳过前2位度数)
fLat = fLat / 60;                // 分钟转换为度
fLat += (Lat[0] - '0')*10 + (Lat[1] - '0');  // 加上度数部分

示例计算:

/* 经度格式: dddmm.mmmm */  
sscanf(Lng+3, "%f", &fLng);      // 提取分钟部分(跳过前3位度数)
fLng = fLng / 60;                // 分钟转换为度
fLng += (Lng[0] - '0')*100 + (Lng[1] - '0')*10 + (Lng[2] - '0'); // 加上度数
  • 原始数据: 4005.22599

  • 度数: 40

  • 分钟: 05.22599

  • 转换: 40 + 5.22599/60 = 40.0870998°

UART驱动

static struct uart_port *virt_port;    // 虚拟串口端口
static unsigned char txbuf[1024];      // 发送缓冲区
static int tx_buf_r = 0;               // 缓冲区读指针
static int tx_buf_w = 0;               // 缓冲区写指针

 UART驱动结构体

static struct uart_driver virt_uart_drv = {.owner          = THIS_MODULE,.driver_name    = "VIRT_UART",     // 驱动名称.dev_name       = "ttyVIRT",       // 设备节点名称(/dev/ttyVIRT).major          = 0,               // 动态分配主设备号.minor          = 0,               // 动态分配次设备号.nr             = 1,               // 支持1个端口
};

UART操作函数集

static const struct uart_ops virt_pops = {.tx_empty   = virt_tx_empty,       // 检查发送是否完成.start_tx   = virt_start_tx,       // 启动发送.set_termios = virt_set_termios,   // 设置终端参数
};

核心函数

1. virt_tx_empty() - 发送空检查

static unsigned int virt_tx_empty(struct uart_port *port)
{return 1;  // 总是返回"空",因为数据瞬间存入buffer
}
  • 这个函数告诉上层数据已经发送完成

  • 对于虚拟设备,数据立即进入缓冲区,所以总是返回"空"

2. virt_start_tx() - 启动发送

static void virt_start_tx(struct uart_port *port)
{struct circ_buf *xmit = &port->state->xmit;while (!uart_circ_empty(xmit) && !uart_tx_stopped(port)) {// 从环形缓冲区读取数据到发送缓冲区txbuf[tx_buf_w++] = xmit->buf[xmit->tail];xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);port->icount.tx++;  // 更新发送计数}if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)uart_write_wakeup(port);  // 唤醒等待的写进程
}

关键概念说明:

环形缓冲区 (circ_buf):

  • xmit->buf: 缓冲区数组

  • xmit->head: 写指针

  • xmit->tail: 读指针

  • UART_XMIT_SIZE: 通常为4096,缓冲区大小

发送流程:

  1. 检查环形缓冲区是否有数据

  2. 将数据从环形缓冲区复制到虚拟发送缓冲区

  3. 更新读指针(环形缓冲区)

  4. 更新写指针(发送缓冲区)

  5. 统计发送字节数

3. virt_set_termios() - 设置终端参数

static void virt_set_termios(struct uart_port *port, struct ktermios *termios,struct ktermios *old)
{return;  // 虚拟设备,忽略参数设置
}

平台设备驱动实现

1. 探测函数

static int virtual_uart_probe(struct platform_device *pdev)
{   int rxirq;// 从设备树获取中断号(虚拟设备可能不需要)rxirq = platform_get_irq(pdev, 0);// 分配uart_port结构体virt_port = devm_kzalloc(&pdev->dev, sizeof(*virt_port), GFP_KERNEL);// 初始化uart_portvirt_port->dev = &pdev->dev;virt_port->iotype = UPIO_MEM;      // 内存映射IOvirt_port->irq = rxirq;            // 中断号virt_port->fifosize = 32;          // FIFO大小virt_port->ops = &virt_pops;       // 操作函数集virt_port->flags = UPF_BOOT_AUTOCONF;  // 自动配置标志// 注册UART端口return uart_add_one_port(&virt_uart_drv, virt_port);
}

2. 设备树匹配

static const struct of_device_id virtual_uart_of_match[] = {{ .compatible = "100ask,virtual_uart", },  // 设备树兼容性字符串{ },
};

模块初始化和退出

1. 初始化函数

static int __init virtual_uart_init(void)
{   int ret;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);// 1. 注册UART驱动ret = uart_register_driver(&virt_uart_drv);if (ret)return ret;// 2. 注册平台驱动return platform_driver_register(&virtual_uart_driver);
}

驱动工作流程

用户空间 → 内核空间的数据流

  1. 用户写入数据

echo "hello" > /dev/ttyVIRT
  1. TTY子系统处理

    • 数据通过tty_write()进入TTY核心

    • 调用UART驱动的写函数

  2. UART驱动处理

    • 数据存入环形缓冲区xmit

    • 调用virt_start_tx()启动发送

    • 数据从环形缓冲区复制到txbuf

用户空间|v
tty_struct (TTY核心)|v
uart_state (UART状态)|-- xmit (环形缓冲区)|v  
uart_port (UART端口)|-- ops->start_tx()  // 调用virt_start_tx()|v
txbuf[1024]  // 虚拟发送缓冲区
/{virtual_uart: virtual_uart_100ask {compatible = "100ask,virtual_uart";interrupt-parent = <&intc>;interrupts = <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>;};};

控制台支持

1. 控制台支持 (Console Support)

控制台结构体定义
static struct console virt_uart_console = {.name       = "ttyVIRT",           // 控制台名称.write      = virt_uart_console_write,  // 控制台写入函数.device     = virt_uart_console_device, // 控制台设备函数.flags      = CON_PRINTBUFFER,     // 控制台标志.index      = -1,                  // 自动分配索引.data       = &virt_uart_drv,      // 关联的UART驱动
};
控制台写入函数
static void virt_uart_console_write(struct console *co, const char *s, unsigned int count)
{int i;for (i = 0; i < count; i++)if (txbuf_put(s[i]) != 0)  // 将控制台输出存入发送缓冲区return;
}
控制台设备函数
struct tty_driver *virt_uart_console_device(struct console *co, int *index)
{struct uart_driver *p = co->data;*index = co->index;return p->tty_driver;  // 返回对应的tty驱动
}
在UART驱动中注册控制台
static struct uart_driver virt_uart_drv = {// ... 其他字段.cons           = &virt_uart_console,  // 注册控制台
};

2. 完整的UART操作函数集

UART操作函数
static int virt_startup(struct uart_port *port)
{return 0;  // 启动UART端口
}static void virt_set_mctrl(struct uart_port *port, unsigned int mctrl)
{// 设置调制解调器控制线(空实现)
}static unsigned int virt_get_mctrl(struct uart_port *port)
{return 0;  // 获取调制解调器控制线状态
}static void virt_stop_tx(struct uart_port *port)
{// 停止发送(空实现)
}static void virt_stop_rx(struct uart_port *port)
{// 停止接收(空实现)
}static void virt_shutdown(struct uart_port *port)
{// 关闭UART端口(空实现)
}static const char *virt_type(struct uart_port *port)
{return "100ASK_VIRT_UART";  // 返回驱动类型名称
}
static const struct uart_ops virt_pops = {.tx_empty   = virt_tx_empty,.set_mctrl  = virt_set_mctrl,.get_mctrl  = virt_get_mctrl,.stop_tx    = virt_stop_tx,.start_tx   = virt_start_tx,.stop_rx    = virt_stop_rx,.startup    = virt_startup,.shutdown   = virt_shutdown,.set_termios = virt_set_termios,.type       = virt_type,
};

3. 改进的中断处理函数

使用tty_port结构
static irqreturn_t virt_uart_rxint(int irq, void *dev_id)
{struct uart_port *port = dev_id;struct tty_port *tport = &port->state->port;  // 获取tty_portunsigned long flags;int i;spin_lock_irqsave(&port->lock, flags);for (i = 0; i < rx_buf_w; i++) {port->icount.rx++;/* 使用tty_port而不是uart_port */tty_insert_flip_char(tport, rxbuf[i], TTY_NORMAL);}rx_buf_w = 0;spin_unlock_irqrestore(&port->lock, flags);tty_flip_buffer_push(tport);  // 推送数据到tty_portreturn IRQ_HANDLED;
}

4. 改进的端口配置

端口类型和基地址设置
virt_port->flags = 0;           // 清除自动配置标志
virt_port->type = PORT_8250;    // 设置为8250兼容类型
virt_port->iobase = 1;          /* 为了让uart_configure_port能执行 */

完整的数据流分析

1. 内核打印输出流

printk() 或内核日志↓
console_write() [控制台核心]↓
virt_uart_console_write() [虚拟控制台]↓
数据存入txbuf环形缓冲区↓
用户读取/proc/virt_uart_buf获取内核输出

2. 应用程序数据流

应用程序写入/dev/ttyVIRT↓
tty_write() [TTY核心]↓  
uart_write() [串口核心]↓
数据存入circular buffer (xmit)↓
virt_start_tx()被调用↓
数据从xmit复制到txbuf环形缓冲区

3. 接收数据流

用户写入/proc/virt_uart_buf↓
数据存入rxbuf接收缓冲区↓
模拟产生RX中断↓
virt_uart_rxint()中断处理函数↓
数据从rxbuf推送到TTY flip buffer↓
tty_flip_buffer_push()通知TTY核心↓
应用程序从/dev/ttyVIRT读取数据

http://www.dtcms.com/a/555327.html

相关文章:

  • 新闻发布网站如果做wordpress漏洞检测
  • 网站头部设计优化青岛做外贸网站建设
  • 东城专业网站建设公司创意二维码制作网站
  • 网站的用户登录一般怎么做的计算机前端开发要学哪些软件
  • 上海网站建设 虹口做个小程序需要多少钱
  • 自适应响应式网站源码城阳网站开发公司
  • 网站源码建站教程wordpress 最新 调用
  • 静态网站可以做哪些网站开发成本计算
  • 蚌埠网站建设费用郑州网站建设网站
  • 国外直播做游戏视频网站有哪些php做的网站首页是什么文件
  • HttpPrinter是一款专为解决Web打印痛点设计的跨平台打印组件
  • dw制作班级网站wordpress升级提示文件流的目标
  • 建设网站是不是必须要服务器电脑培训学校网站
  • 游戏网站织梦模板平台网站建设需求
  • 怎样做自己的视频网站不需要企业提供
  • 到做任务的网站上面推广粉象生wordpress电商主题
  • ae免费模板网站dedecms农业种植网站模板
  • 农村建设有限公司网站苏州工业园区地图
  • C++中使用cpp-httplib和nlohmann_json库实现http请求获取天气数据
  • 网站首页被k 内页还有广告推广合同范本
  • 案例设计:使用stress工具模拟压力的自动伸缩
  • 嘉兴自助建网站3d在线设计网站
  • 免费做图表的网站网站建设中服务器搭建方式
  • 滨州论坛网站建设开封建设局网站
  • 网站布局方案dedecms中英文网站开发
  • 山东高端网站建设方案微信导航网站 dedecms
  • 股票交易网站建设网站制作公司服务
  • 全球首个AI Agent操作系统发布,FlowithOS凭什么跑分超越OpenAI Atlas?
  • 钉钉网站建设服务协议北京app制作哪家好
  • 沈阳免费自助建站模板网站设计流程大致分为几个阶段