11.3编写Linux串口驱动

news2024/9/28 17:29:00

编写串口驱动主要步骤

  1. 构建并初始化 struct console 对象,若串口无需支持 console 可省略此步骤
//UART驱动的console
static struct uart_driver virt_uart_drv;
static struct console virt_uart_console = {
	//console 的名称,配合index字段使用,如果name为“ttyVIRT”,且index字段为小于0,则可以和“console=ttyVIRT“(n=0,1,2…)来确定index字段的值
	.name = "ttyVIRT",
	//操作函数集合
	.device = virt_uart_console_device,
	.write = virt_uart_console_write,
	.setup = virt_uart_console_setup,
	//CON_PRINTBUFFER表示从buffer中的第一行log开始打印
	//CON_CONSDEV表示从earlycon没有打印的log开始打印
	.flags = CON_PRINTBUFFER,
	//index小于0时通过bootargs参数确定其值
	.index = -1,
	//console私有数据,这里用于记录拥有此console的串口驱动
	.data = &virt_uart_drv,
};
  1. 构建并初始化 struct uart_driver 对象
//UART驱动
static struct uart_driver virt_uart_drv = {
	.owner = THIS_MODULE,
	//驱动名称,在dev文件系统中以此为前缀生成设备文件名
	.driver_name = "VIRT_UART",
	//设备名称
	.dev_name = "ttyVIRT",
	//主设备号和次设备号起始值
	.major = 0,
	.minor = 0,
	//只有一个端口
	.nr = 1,
	//UART的console
	.cons = &virt_uart_console,
};
  1. 使用 uart_register_driver 函数注册串口驱动
	//注册串口驱动
	result = uart_register_driver(&virt_uart_drv);
	if(result < 0)
	{
		printk("register uart driver failed\r\n");
		return result;
	}
  1. 构建并初始化 struct uart_port 对象
//UART端口
static struct uart_port virt_port = {};

	//设置端口
	virt_port.line = 0;
	//端口所属设备
	virt_port.dev = &pdev->dev;
	//串口寄存器物理基地址,iobase、mapbase、membase不能全部为0,否则在初始化时不会执行端口配置操作
	virt_port.mapbase = 1;
	//端口类型,不能为PORT_UNKNOWN
	virt_port.type = PORT_8250;
	//io访问方式
	virt_port.iotype = UPIO_MEM;
	//串口的中断号
	virt_port.irq = 0;
	//串口端口发送FIFO大小
	virt_port.fifosize = 32;
	//操作函数集合
	virt_port.ops = &virt_uart_ops;
	//RS485配置函数
	virt_port.rs485_config = NULL;
	//执行自动配置,但不探测UART类型
	virt_port.flags = UPF_BOOT_AUTOCONF | UPF_FIXED_TYPE;
  1. 使用 uart_add_one_port 函数在 uart_driver 添加串口端口
	//在串口驱动下添加端口
	result = uart_add_one_port(&virt_uart_drv, &virt_port);
	if(result < 0)
	{
		printk("add uart port failed\n");
		return result;
	}

编写串口驱动

这里以一个虚拟串口为例来介绍串口驱动的编写,它在 proc 文件系统中创建了一个文件,通过向这个文件写入数据来模拟串口硬件的接收(写入数据时先将数据写入到虚拟串口接收FIFO中,然后调度工作队列,用以模拟串口中断处理函数来处理写入到虚拟串口接收FIFO中的数据),通过串口发送的数据会写入虚拟串口发送FIFO中,然后可以通过这个文件来读取虚拟串口发送FIFO中的数据,用于模拟串口硬件发送。

编写设备树

在顶层设备树根节点中加入如下节点:

	virtual_uart: virtual_uart_controller {
		compatible = "atk,virtual_uart";
	};

用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树(arm-none-linux-gnueabihf-是编译器前缀),然后用新的.dtb文件启动系统

驱动代码编写

驱动代码的要点前面已经介绍过了,这里给出驱动程序的完整代码:

#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/sysrq.h>
#include <linux/platform_device.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#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>
#include <linux/dma-mapping.h>
#include <linux/proc_fs.h>
#include <linux/kfifo.h>
#include <linux/of_irq.h>
#include <linux/sched.h>

//定义一个FIFO,用于模拟串口的接收FIFO
static DEFINE_KFIFO(rx_fifo, char, 128);
//接收标志,非0表示开启接收
uint8_t rx_flag = 0;
//模拟串口接收完成中断的工作队列
static void rx_isr_work(struct work_struct *w);
DECLARE_WORK(rx_work, rx_isr_work);

//定义一个FIFO,用于模拟串口的发送FIFO
static DEFINE_KFIFO(tx_fifo, char, 128);
//发送标志,非0表示开启发送
uint8_t tx_flag = 0;
//模拟串口发送完成中断的工作队列
static void tx_isr_work(struct work_struct *w);
DECLARE_WORK(tx_work, tx_isr_work);

//UART端口
static struct uart_port virt_port = {};

//proc文件,用于模拟串口硬件收发数据
static struct proc_dir_entry *proc_file;

//从虚拟串口的发送FIFO中读取数据,用于模拟串口硬件发送数据
static ssize_t proc_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	unsigned int copied;

	//从虚拟串口的发送FIFO中读取数据,模拟串口硬件发送数据
	kfifo_to_user(&tx_fifo, buf, size, &copied);

	//调度工作队列,模拟产生发送中断
	schedule_work(&tx_work);

	return copied;
}

//向虚拟串口的接收FIFO写入数据,用于模拟串口硬件接收到数据
static ssize_t proc_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
	unsigned int copied;

	//将数据写入虚拟串口的接收FIFO,模拟串口硬件接收到数据
	kfifo_from_user(&rx_fifo, buf, size, &copied);

	//调度工作队列,模拟产生接收中断
	schedule_work(&rx_work);

	return copied;
}

//proc文件操作函数集合
static const struct file_operations proc_fops = {
	.read = proc_read,
	.write = proc_write,
};

//虚拟串口接收完成中断
static void rx_isr_work(struct work_struct *w)
{
	int rx_count;
	int result;
	unsigned long flags;
	uint8_t buffer[16];
	struct tty_port *ttyport = &virt_port.state->port;

	if(rx_flag)
	{
		while(1)
		{
			//从虚拟串口接收FIFO中读取数据
			rx_count = kfifo_out(&rx_fifo, buffer, sizeof(buffer));
			if(rx_count <= 0)
				break;

			//获取自旋锁
			spin_lock_irqsave(&virt_port.lock, flags);

			//将接收的数据写入行规程
			result = tty_insert_flip_string(ttyport, buffer, rx_count);
			//更新统计数据
			virt_port.icount.rx += result;

			//释放自旋锁
			spin_unlock_irqrestore(&virt_port.lock, flags);

			//通知行规程进行处理
			tty_flip_buffer_push(ttyport);
		}
	}
}

//虚拟串口发送完成中断
static void tx_isr_work(struct work_struct *w)
{
	int one;
	int two;
	int count;
	int tx_count;
	unsigned long flags;
	struct circ_buf *xmit = &virt_port.state->xmit;

	//获取自旋锁
	spin_lock_irqsave(&virt_port.lock, flags);

	tx_count = 0;
	//获取环形缓冲区的长度
	count = uart_circ_chars_pending(xmit);
	if(count > 0)
	{
		//将端口环形缓冲区的数据写入虚拟串口的发送FIFO
		if (xmit->tail < xmit->head) 
		{
			//一次完成拷贝
			tx_count = kfifo_in(&tx_fifo, &xmit->buf[xmit->tail], count);
		}
		else
		{
			//分两次拷贝
			one = UART_XMIT_SIZE - xmit->tail;
			if (one > count)
				one = count;
			two = count - one;

			tx_count = kfifo_in(&tx_fifo, &xmit->buf[xmit->tail], one);
			if((two > 0) && (tx_count >= one))
				tx_count += kfifo_in(&tx_fifo, &xmit->buf[0], two);
		}

		//更新环形缓冲区
		xmit->tail = (xmit->tail + tx_count) & (UART_XMIT_SIZE - 1);

		//更新统计数据
		virt_port.icount.tx += tx_count;
	}
	else
		tx_flag = 0;

	//释放自旋锁
	spin_unlock_irqrestore(&virt_port.lock, flags);
}

//发送是否空闲
static unsigned int virt_uart_tx_empty(struct uart_port *port)
{
	/* 因为要发送的数据瞬间存入buffer */
	return (!tx_flag) ? 1 : 0;
}

//配置流控
static void virt_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
}

//获取流控配置
static unsigned int virt_uart_get_mctrl(struct uart_port *port)
{
	return 0;
}

//停止发送
static void virt_uart_stop_tx(struct uart_port *port)
{
	tx_flag = 0;
}

//启动发送
static void virt_uart_start_tx(struct uart_port *port)
{
	int one;
	int two;
	int count;
	int tx_count;
	struct circ_buf *xmit = &virt_port.state->xmit;

	tx_count = 0;
	//设置发送忙标志
	tx_flag = 1;
	//获取环形缓冲区的长度
	count = uart_circ_chars_pending(xmit);
	if(count > 0)
	{
		//将端口环形缓冲区的数据写入虚拟串口的发送FIFO
		if(xmit->tail < xmit->head) 
		{
			//一次完成拷贝
			tx_count = kfifo_in(&tx_fifo, &xmit->buf[xmit->tail], count);
		}
		else
		{
			//分两次拷贝
			one = UART_XMIT_SIZE - xmit->tail;
			if (one > count)
				one = count;
			two = count - one;

			tx_count = kfifo_in(&tx_fifo, &xmit->buf[xmit->tail], one);
			if((two > 0) && (tx_count >= one))
				tx_count += kfifo_in(&tx_fifo, &xmit->buf[0], two);
		}

		//更新环形缓冲区
		xmit->tail = (xmit->tail + tx_count) & (UART_XMIT_SIZE - 1);

		//更新统计数据
		virt_port.icount.tx += tx_count;
	}
	else
		tx_flag = 0;
}

//停止接收
static void virt_uart_stop_rx(struct uart_port *port)
{
	rx_flag = 0;
}

//传输控制中断信号
static void virt_uart_break_ctl(struct uart_port *port, int break_state)
{
}

//启动串口
static int virt_uart_startup(struct uart_port *port)
{
	//复位接收FIFO
	kfifo_reset(&rx_fifo);
	//启动接收
	rx_flag = 1;

	return 0;
}

//关闭串口
static void virt_uart_shutdown(struct uart_port *port)
{
	//终止接收发送
	rx_flag = 0;
	tx_flag = 0;
}

//刷新输出缓冲区
static void virt_uart_flush_buffer(struct uart_port *port)
{
}

//配置端口时序
static void virt_uart_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old)
{
}

//获取端口类型
static const char *virt_uart_type(struct uart_port *port)
{
	return (port->type == PORT_8250) ? "VIRTUAL_UART" : NULL;
}

//释放端口
static void virt_uart_release_port(struct uart_port *port)
{
}

//请求端口
static int virt_uart_request_port(struct uart_port *port)
{
	return 0;
}

//配置端口
static void virt_uart_config_port(struct uart_port *port, int flags)
{
	if (flags & UART_CONFIG_TYPE)
		port->type = PORT_8250;
}

//验证端口
static int virt_uart_verify_port(struct uart_port *port, struct serial_struct *ser)
{
	return -EINVAL;
}

//console发送函数
static void virt_uart_console_write(struct console *co, const char *s, unsigned int count)
{
	//将数据写入发送FIFO
	kfifo_in(&tx_fifo, s, count);
}

//获取console所属的tty_driver
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;
}

//配置console
static int virt_uart_console_setup(struct console *co, char *options)
{
	return 0;
}

//UART端口操作函数集合
static const struct uart_ops virt_uart_ops = {
	.tx_empty = virt_uart_tx_empty,
	.set_mctrl = virt_uart_set_mctrl,
	.get_mctrl = virt_uart_get_mctrl,
	.stop_tx = virt_uart_stop_tx,
	.start_tx = virt_uart_start_tx,
	.stop_rx = virt_uart_stop_rx,
	.break_ctl = virt_uart_break_ctl,
	.startup = virt_uart_startup,
	.shutdown = virt_uart_shutdown,
	.flush_buffer = virt_uart_flush_buffer,
	.set_termios = virt_uart_set_termios,
	.type = virt_uart_type,
	.release_port = virt_uart_release_port,
	.request_port = virt_uart_request_port,
	.config_port = virt_uart_config_port,
	.verify_port = virt_uart_verify_port,
};

//UART驱动的console
static struct uart_driver virt_uart_drv;
static struct console virt_uart_console = {
	//console 的名称,配合index字段使用,如果name为“ttyVIRT”,且index字段为小于0,则可以和“console=ttyVIRT“(n=0,1,2…)来确定index字段的值
	.name = "ttyVIRT",
	//操作函数集合
	.device = virt_uart_console_device,
	.write = virt_uart_console_write,
	.setup = virt_uart_console_setup,
	//CON_PRINTBUFFER表示从buffer中的第一行log开始打印
	//CON_CONSDEV表示从earlycon没有打印的log开始打印
	.flags = CON_PRINTBUFFER,
	//index小于0时通过bootargs参数确定其值
	.index = -1,
	//console私有数据,这里用于记录拥有此console的串口驱动
	.data = &virt_uart_drv,
};

//UART驱动
static struct uart_driver virt_uart_drv = {
	.owner = THIS_MODULE,
	//驱动名称,在dev文件系统中以此为前缀生成设备文件名
	.driver_name = "VIRT_UART",
	//设备名称
	.dev_name = "ttyVIRT",
	//主设备号和次设备号起始值
	.major = 0,
	.minor = 0,
	//只有一个端口
	.nr = 1,
	//UART的console
	.cons = &virt_uart_console,
};

//设备和驱动匹配成功执行
static int virtual_uart_probe(struct platform_device *pdev)
{
	int result;

	printk("%s\r\n", __FUNCTION__);

	//设置端口
	virt_port.line = 0;
	//端口所属设备
	virt_port.dev = &pdev->dev;
	//串口寄存器物理基地址,iobase、mapbase、membase不能全部为0,否则在初始化时不会执行端口配置操作
	virt_port.mapbase = 1;
	//端口类型,不能为PORT_UNKNOWN
	virt_port.type = PORT_8250;
	//io访问方式
	virt_port.iotype = UPIO_MEM;
	//串口的中断号
	virt_port.irq = 0;
	//串口端口发送FIFO大小
	virt_port.fifosize = 32;
	//操作函数集合
	virt_port.ops = &virt_uart_ops;
	//RS485配置函数
	virt_port.rs485_config = NULL;
	//执行自动配置,但不探测UART类型
	virt_port.flags = UPF_BOOT_AUTOCONF | UPF_FIXED_TYPE;

	//在串口驱动下添加端口
	result = uart_add_one_port(&virt_uart_drv, &virt_port);
	if(result < 0)
	{
		printk("add uart port failed\n");
		return result;
	}

	//创建proc文件,用于模拟串口硬件的发送和接收
	proc_file = proc_create("virt_uart", 0, NULL, &proc_fops);
	if (!proc_file)
	{
		uart_remove_one_port(&virt_uart_drv, &virt_port);
		printk("create proc file failed\n");
		return -ENOMEM;
	}

	return 0;
}

//设备或驱动卸载时执行
static int virtual_uart_remove(struct platform_device *pdev)
{
	printk("%s\r\n", __FUNCTION__);

	//删除proc文件
	proc_remove(proc_file);
	//移除端口
	return uart_remove_one_port(&virt_uart_drv, &virt_port);
}

//匹配列表,用于设备树和平台驱动匹配
static const struct of_device_id virtual_uart_of_match[] = {
	{.compatible = "atk,virtual_uart"},
	{ /* Sentinel */ }
};
//平台驱动
static struct platform_driver virtual_uart_drv = {
	.driver = {
		.name = "virtual_uart",
		.owner = THIS_MODULE,
		.pm = NULL,
		.of_match_table = virtual_uart_of_match,
	},
	.probe = virtual_uart_probe,
	.remove = virtual_uart_remove,
};

static int __init virtual_uart_init(void)
{
	int result;

	printk("%s\r\n", __FUNCTION__);

	//注册串口驱动
	result = uart_register_driver(&virt_uart_drv);
	if(result < 0)
	{
		printk("register uart driver failed\r\n");
		return result;
	}

	//注册平台驱动
	result = platform_driver_register(&virtual_uart_drv);
	if(result < 0)
	{
		uart_unregister_driver(&virt_uart_drv);
		printk("register platform driver failed\r\n");
		return result;
	}

	return 0;
}

static void __exit virtual_uart_exit(void)
{
	printk("%s\r\n", __FUNCTION__);

	//注销平台驱动
	platform_driver_unregister(&virtual_uart_drv);
	//注销串口驱动
	uart_unregister_driver(&virt_uart_drv);
}

module_init(virtual_uart_init);
module_exit(virtual_uart_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("virtual_uart_dev");

串口测试程序

测试程序可以使用11.1Linux串口应用程序开发中编写的串口回环用于程序。

上机实验

  1. 修改设备树,在顶层设备树根节点中加入描述虚拟串口的设备节点,然后编译设备树,用新的设备树启动目标板
  2. 从这里下载测试程序,并进行编译,然后拷贝到目标板根文件系统的root目录
  3. 从这里下载驱动程序并进行编译,然后拷贝到目标板根文件系统的root目录
  4. 执行命令 insmod virtual_uart.ko 加载驱动程序
    在这里插入图片描述
  5. 执行命令 ./uart_teat.out /dev/ttyVIRT0 运行测试程序
  6. 另开一个终端,在终端中执行命令 echo 123456 > /proc/virt_uart 模拟串口硬件接收数据,执行命令 cat /proc/virt_uart 模拟串口硬件发送数据,在执行命令过程中 ./uart_teat.out 程序也会打印它收到的字节数。
    在这里插入图片描述
    在这里插入图片描述

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

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

相关文章

胡圆圆的暑期实习经验分享

背景 实验室一般是在研究生二年级的时候会放实习&#xff0c;在以后的日子就是自己完成毕业工作要求&#xff0c;基本上不再涉及实验室的活了&#xff0c;目前是一月份也是开始准备暑期实习的好时间。实验室每年这个时候都会有学长学姐组织暑期实习经验分享&#xff0c;本着不…

SpringBoot基于哨兵模式的Redis(7.2)集群实现读写分离

文章目录 一、前提条件二、SpringBoot访问Redis集群1. 引入依赖2. yaml配置3. 设置读写分离4. 简单的controller 三、运行四、测试1. 写2. 读3. 额外测试 环境 docker desktop for windows 4.23.0redis 7.2Idea 一、前提条件 先根据以下文章搭建一个Redis集群 Docker-Compo…

如何利用ssh将手机连接电脑

首先我们需要下载ssh&#xff0c;因为我们没有安装 sshd 命令意思是开启ssh 下载完以后要设置密码&#xff0c;我设置得是 123456 开启服务&#xff0c;查看ip 电脑连接 ssh 刚刚得ip -p 8022 后面就连接上了 我可以在这里启动我手机上的vnc

2024年汉字小达人区级选拔备考——选择题:选字填空

前面的几篇文章&#xff0c;六分成长介绍了汉字小达人区级选拔样题的前面三道题&#xff1a;看拼音写汉字、补充成语、诗词连线&#xff0c;这三道大题都是填空题&#xff0c;适合线下笔试&#xff0c;不太适合线上比赛。事实上&#xff0c;在区级自由比赛和市级比赛的时候&…

JVM是如何基于虚拟机栈运行的

众所周知&#xff1a;JVM执行Java代码是靠执行引擎实现的。执行引擎有两套解释器&#xff1a;字节码解释器、模板解释器。字节码解释器比较简单&#xff0c;不多说&#xff0c;看图。本篇文章咱们讨论模板解释器执行Java代码的底层原理。 早些年研究模板解释器看到R大用汇编写的…

学习笔记——C++运算符之比较运算符

作用&#xff1a;用于表达式的比较&#xff0c;并返回一个真值或假值 比较运算符有以下符号&#xff1a; #include<bits/stdc.h> using namespace std; int main(){//int a10;int b20;cout<<(ab)<<endl;//0//!cout<<(a!b)<<endl;//1//>cout&…

Python教程(20)——python面向对象编程基本概念

面向对象 类和对象初始化方法属性和方法self关键字继承多态 面向对象&#xff08;Object-oriented&#xff09;是一种常用的程序设计思想&#xff0c;它以对象作为程序的基本单元&#xff0c;将数据和操作封装在一起&#xff0c;通过对象之间的交互来实现程序的功能。 在面向对…

Wpf 使用 Prism 实战开发Day09

设置模块设计 1.效果图 一.系统设置模块&#xff0c;主要有个性化(用于更改主题颜色)&#xff0c;系统设置&#xff0c;关于更多&#xff0c;3个功能点。 个性化的颜色内容样式&#xff0c;主要是从 Material Design Themes UI简称md、提供的demo里复制代码过来使用的。 1.设置…

CHS_02.1.1.2+操作系统的特征

CHS_02.1.1.2操作系统的特征 操作系统的四个特征并发这个特征为什么并发性对于操作系统来说是一个很重要的基本特性资源共享虚拟异步性 各位同学 大家好 在这个小节当中 我们会学习 操作系统的四个特征 操作系统有并发 共享 虚拟和异部这四个基本的特征 其中 并发和共享是两个…

1871_什么是PCB

Grey 全部学习内容汇总&#xff1a; https://github.com/GreyZhang/g_hardware_basic 1871_什么是PCB 简单的PCB的缩写自然很容易理解&#xff0c;不过PCB涉及到的一些概念性的知识会比一个简单的缩写多得多。这里根据AD官方上的一个页面来整理一下这方面的基础知识点。 主…

【⭐AI工具⭐】AI工具导航推荐

目录 零 工具导航&#x1f449;【[AI工具集导航](https://ai-bot.cn/)】&#x1f448;&#x1f449;【[iForAI](https://iforai.com/)】&#x1f448;&#x1f449;【[AInav](https://www.ainav.cn/)】&#x1f448;&#x1f449;【[Navi AI 导航](https://www.naviai.cn/)】&a…

YTM32的低功耗PowerDown模式及唤醒管理器WKU模块

文章目录 Introduction专门的唤醒源管理器WKU外部的唤醒引脚内部的触发信号 进入PowerDown模式的操作流进入低功耗模式配合使用的其他模块 性能指标低功耗电流唤醒时间 Conclusion Introduction YTM32的低功耗系统中有设计了多种工作模式&#xff0c;功耗从高到低&#xff0c;…

大数据时代必备技能!Shell脚本学习网站助你一臂之力!

介绍&#xff1a;Shell脚本是一种用于自动化任务的脚本语言&#xff0c;它使用Shell命令来执行一系列操作。Shell脚本通常以.sh为扩展名&#xff0c;并使用#!/bin/bash作为第一行来指定使用的Shell解释器。 在Shell脚本中&#xff0c;我们可以使用各种命令和控制结构来实现自动…

【大数据】Flink CDC 的概览和使用

Flink CDC 的概览和使用 1.什么是 CDC2.什么是 Flink CDC3.Flink CDC 前生今世3.1 Flink CDC 1.x3.2 Flink CDC 2.x3.3 Flink CDC 3.x 4.Flink CDC 使用5.Debezium 标准 CDC Event 格式详解 1.什么是 CDC CDC&#xff08;Change Data Capture&#xff0c;数据变更抓取&#xf…

汽车电子行业的 C 语言编程标准

前言 之前分享了一些编程规范相关的文章&#xff0c;有位读者提到了汽车电子行业的MISRA C标准&#xff0c;说这个很不错。 本次给大家找来了一篇汽车电子行业的MISRA C标准的文章一同学习下。 什么是MISRA&#xff1f; MISRA (The Motor Industry Software Reliability Ass…

Linux-文件系统管理实验2

1、将bin目录下的所有文件列表放到bin.txt文档中&#xff0c;并将一共有多少个命令的结果信息保存到该文件的最后一行。统计出文件中以b开头的所有命令有多少个&#xff0c;并将这些命令保存到b.txt文档中。将文档中以p结尾的所有命令保存到p.txt文件中&#xff0c;并统计有多少…

【linux】Ubuntu 22.04.3 LTS截屏

一、快捷键 交互式录屏 ShiftCtrltAltR 交互式截图 Print 对窗口进行截图 AltPrint 截图 ShiftPrint 快捷键可能取决于使用的桌面环境和个人的键盘快捷键设置。如果上述快捷键不起作用&#xff0c;可能需要检查系统设置中的键盘快捷键部分&#xff0c;以了解系统中截图的…

法线变换矩阵的推导

背景 在冯氏光照模型中&#xff0c;其中的漫反射项需要我们对法向量和光线做点乘计算。 从顶点着色器中读入的法向量数据处于模型空间&#xff0c;我们需要将法向量转换到世界空间&#xff0c;然后在世界空间中让法向量和光线做运算。这里便有一个问题&#xff0c;如何将法线…

问答领域的基本了解

问答领域是人工智能领域中的一个重要研究方向&#xff0c;旨在让计算机能够理解人类提出的问题&#xff0c;并以自然语言形式回答这些问题。问答系统可以应用于各种场景&#xff0c;包括搜索引擎、虚拟助手、智能客服等。 一.目标 目标&#xff1a; 问答系统的主要目标是使计…

逆向一个Go程序

前奏 事先声明&#xff0c;自导自演&#xff0c;纯属为了演示基本的逆向思维 用Go写一段模拟登录的代码&#xff1a; package mainimport ("fmt" )func main() {pass : ""fmt.Print("input password:")fmt.Scan(&pass)if pass "hel…