11.3编写Linux串口驱动

编写串口驱动主要步骤

  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 程序也会打印它收到的字节数。
    在这里插入图片描述
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uniapp 解决安卓App使用uni.requestPayment实现沙箱环境支付宝支付报错

背景&#xff1a;uniapp与Java实现的安卓端app支付宝支付&#xff0c;本想先在沙箱测试环境测支付&#xff0c;但一直提示“商家订单参数异常&#xff0c;请重新发起付款。”&#xff0c;接着报错信息就是&#xff1a;{ "errMsg": "requestPayment:fail [pa…

Archlinux下自启动rclone mount

路径&#xff1a; /etc/systemd/system/rclonemount.service [Unit] Descriptionrclonemount Requiresnetwork-online.target.wants Afteralist.service[Service] Typesimple ExecStartPre/bin/mkdir -p /media ExecStart/usr/bin/rclone mount \aliyun: /media \--config /ro…

Video-GroundingDino论文解读

文章目录 前言一、摘要二、引言三、贡献四、模型结构1、模型定义与问题数据少问题模型解决问题模型模块 2、模型结构模型结构图Cross-Modality Spatio-Temporal EncoderLanguage-Guided Query SelectionCross-Modality Spatio-Temporal DecoderPrediction Heads 总结 前言 之前…

冬天夺去的清爽,可爱,春天都会还给你

这款外套上身可太时尚好看了 春天日常穿着或者出行游玩 应对早晚温差&#xff0c;兼具时尚和温度两不误 干净率性闲适的洒脱范整件衣服干净不失细节 下摆有橡筋收紧更加保暖了工艺方面也毫不逊色&#xff0c;防水拉链 四合扣、猪鼻扣一应俱全简直就是一件实用与时尚并存的…

Grind75第2天 | 238.除自身以外数组的乘积、75.颜色分类、11.盛最多水的容器

238.除自身以外数组的乘积 题目链接&#xff1a;https://leetcode.com/problems/product-of-array-except-self 解法&#xff1a; 这个题有follow up&#xff0c; 要求优化到空间复杂度为O(1)&#xff0c;所以给出baseline和follow up的解法。 Baseline&#xff1a;利用索引…

网络报文分析程序的设计与实现(2024)

1.题目描述 在上一题的基础上&#xff0c;参照教材中各层报文的头部结构&#xff0c;结合使用 wireshark 软件&#xff08;下载地址 https://www.wireshark.org/download.html#releases&#xff09;观察网络各层报文捕获&#xff0c;解析和分析的过程&#xff08;如下 图所示&a…

SpringBoot+Redis实现接口防刷功能

场景描述&#xff1a; 在实际开发中&#xff0c;当前端请求后台时&#xff0c;如果后端处理比较慢&#xff0c;但是用户是不知情的&#xff0c;此时后端仍在处理&#xff0c;但是前端用户以为没点到&#xff0c;那么再次点击又发起请求&#xff0c;就会导致在短时间内有很多请求…

FCN-8s源码理解

FCN网络用于对图像进行分割&#xff0c;由于是全卷积网络&#xff0c;所以对输入图像的分辨率没有要求。本文重点对fcn8s.py中图像降采样和上采样后图像分辨率的变换进行理解。 相关知识 为准确理解图像分辨率的变换&#xff0c;对网络结构中影响图像分辨率变换的几个函数进行…

leetcode:3. 无重复字符的最长子串

一、题目 二、函数原型 int lengthOfLongestSubstring(char* s) 三、思路 本题就是找最长的无重复字符子串。 两层循环&#xff0c;外层循环控制字串的起始位置&#xff0c;内层循环控制字串的长度。 设置一个长度为256且初始为0的hash表&#xff08;因为一共有256个字符…

windows----Vmware虚拟机安装ubuntu

双系统来回切有点麻烦&#xff0c;还是安装虚拟机先整个简单的。 1 安装Vmware17虚拟机 虚拟机下载网址&#xff0c;一直下一步就行&#xff0c;更新和加入计划关闭 秘钥&#xff1a;MC60H-DWHD5-H80U9-6V85M-8280D 2 下载ubantu镜像 浙大镜像&#xff0c;自己选择版本吧&a…

灰色关联分析

&#xff08;相关性分析&#xff09;反映关系有多么紧密 “在系统发展过程中&#xff0c;若两个因素变化的趋势具有一致性&#xff0c;即同步变化程度较高&#xff0c;即可谓二者关联程度较高&#xff1b;反之&#xff0c;则较低。因此&#xff0c;灰色关联分析方法&#xff0…

STM32 ADC采样调试笔记

最近在搞STM32L051系列一个小MCU&#xff0c;要用这个去采集两路ADC作为输入。期间也碰到过一些问题&#xff0c;顺便记录下。 ADC采集原理不说了&#xff0c;主要采集电压&#xff0c;用数字进行细分&#xff0c;这样就可以知道输入电压多少了&#xff0c;网上也有很多相关文…

Spark中的二分类与多分类问题的解决

机器学习中的分类问题是数据科学中的一个重要领域&#xff0c;而在大数据环境中使用Apache Spark来解决二分类和多分类问题可以更好地处理大规模数据。本文将深入探讨如何使用Spark来解决二分类和多分类问题&#xff0c;包括数据准备、模型选择和性能评估等方面。 二分类问题 …

dnSpy调试工具二次开发1-新增菜单

测试环境&#xff1a; window 10 visual studio 2019 版本号&#xff1a;16.11.15 .net framework 4.8 开发者工具包 下载 .NET Framework 4.8 | 免费官方下载 .net 5开发者工具包 下载 .NET 5.0 (Linux、macOS 和 Windows) 利用git拉取代码(源码地址&#xff1a;Gi…

入库和出库的成本对不上如果如何解决

入库是前期手工录入的车价是对的&#xff0c;出库是根据销售出库单生成的 入库成本和出库成本不一致的解决方法 解决方法&#xff1a; 整车管理——正车库存——库存核算——整车出库 成本核算

Marvelous Designer 各版本安装指南

Marvelous Designer下载链接 https://pan.baidu.com/s/1ZZCraq6w2Z4JPisND8q0jA?pwd0531 1.鼠标右击【Marvelous Designer 12(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;选择【解压到 Marvelous Designer 12(64bit)】。 2.打开解压后的…

深入了解 RDD

深入了解 RDD 案例 明确需求&#xff1a; 在访问日志中&#xff0c;统计独立IP数量 TOP10 查看数据结构&#xff1a; IP&#xff0c;时间戳&#xff0c;Http&#xff0c;Method&#xff0c;Url…… 明确编码步骤 取出IP&#xff0c;生成一个只有IP的数据集简单清洗统计IP出现…

【小沐学CAD】开源Assimp库导入三维模型(C++、Python)

文章目录 1、简介2、下载编译3、代码测试3.1 C3.2 pyassimp&#xff08;Python&#xff09; 结语 1、简介 https://github.com/assimp/assimp Open Asset Import Library 是一个库&#xff0c;用于将各种 3D 文件格式加载为共享的内存格式。它支持 40 多种用于导入的文件格式和…

openssl3.2 - 编译

文章目录 openssl3.2 - 编译概述OpenSSL源码下载编译目标如何编译前置环境 - perl前置环境 - VS前置环境 - NASM快速编译步骤编译 - Quick startInstall PerlInstall NASMUse Visual Studio Developer Command Prompt with administrative privilegesFrom the root of the Open…

I.MX6ULL开发笔记(二)——硬件外设操作

0x01 点亮第一个RGB灯 在文章http://t.csdnimg.cn/EGWt9中有介绍Linux下文件目录&#xff0c;那么在Linux系统下&#xff0c;RGB灯也是一个设备&#xff0c;所以我们需要到/sys目录下去操作这个设备。 之后&#xff0c;我们进入到class目录&#xff0c;这里挂载着开发板上的外…