SPI驱动学习六(SPI_Master驱动程序)

目录

  • 前言
  • 一、SPI_Master驱动程序框架
    • 1. SPI传输概述
      • 1.1 数据组织方式
      • 1.2 SPI控制器数据结构
    • 2. SPI传输函数的两种方法
      • 2.1 老方法
      • 2.2 新方法
  • 二、如何编写SPI_Master驱动程序
    • 1. 编写设备树
    • 2. 编写驱动程序
  • 三、SPI_Master驱动程序简单示例demo
    • 1. 使用老方法编写的SPI Master驱动程序
    • 2. 使用新方法编写的SPI Master驱动程序

前言

  SPI 是“串行外设接口”的缩写,它在嵌入式系统中广泛使用,因为它是一个简单且高效的接口:基本上是一个多路复用的移位寄存器。它的三个信号线分别为时钟线(SCK,通常在 1-20 MHz 范围内)一个“主机输出从机输入”(MOSI)数据线一个“主机输入从机输出”(MISO)数据线。SPI 是一种全双工协议;每在MOSI线上移出一位(每时钟一位),MISO线上就会移入一位。这些位在去往和从系统内存传送的过程中会被组装成各种大小的字。一个额外的芯片选择线通常是低电平有效的(nCS);通常每个外设使用四个信号线,有时还会有一个中断线。

  SPI 总线设施提供了一个通用接口,用于声明 SPI 总线和设备,按照标准 Linux 驱动模型进行管理,并执行输入/输出操作。目前,仅支持“主设备”侧接口,即Linux与SPI外围设备通信,自己不实现这样的外围设备。(支持实现 SPI 从设备的接口必然会有所不同。)

  编程接口围绕两种类型的驱动程序和两种类型的设备构建。一个“控制器驱动程序”抽象了控制器硬件,可能是简单的 GPIO 引脚集,也可能是连接到 SPI 移位寄存器另一侧的双 DMA 引擎的一对 FIFO(以最大化吞吐量)。这种驱动程序在它们所在的总线(通常是平台总线)与 SPI 之间架起桥梁,并将其设备的 SPI 侧暴露为 struct spi_controller。SPI 设备是该主设备的子设备,以 struct spi_device 表示,并通过 struct spi_board_info 描述符构建,这些描述符通常由特定于板的初始化代码提供。一个 struct spi_driver 被称为“协议驱动程序”,并通过正常的驱动模型调用与 spi_device 绑定。

  I/O 模型是一个排队的消息集合。协议驱动程序提交一个或多个 struct spi_message 对象,这些对象会异步处理和完成(不过也有同步的封装)。消息由一个或多个 struct spi_transfer 对象构成,每个对象封装了一个全双工的 SPI 传输。由于不同芯片对 SPI 传输的比特有不同的使用策略,因此需要多种协议调节选项。

.. kernel-doc:: include/linux/spi/spi.h:internal:.. kernel-doc:: drivers/spi/spi.c:functions: spi_register_board_info.. kernel-doc:: drivers/spi/spi.c:export:

                        ----------------来源于kernel/Documentation/driver-api/spi.rst

一、SPI_Master驱动程序框架

  • 参考内核源码: drivers\spi\spi.c

1. SPI传输概述

1.1 数据组织方式

  使用SPI传输时,最小的传输单位是"spi_transfer",对于一个设备,可以发起多个spi_transfer,这些spi_transfer,会放入一个spi_message里。
在这里插入图片描述

  • spi_transfer:指定tx_buf、rx_buf、len
  • 同一个SPI设备的spi_transfer,使用spi_message来管理:
  • 同一个SPI Master下的spi_message,放在一个队列queue里:
  • 所以,反过来,SPI传输的流程是这样的:
    • 从spi_master的队列里取出每一个spi_message
      • 从spi_message的队列里取出一个spi_transfer
        • 处理spi_transfer

一个queue里可以有多个spi_message,一个spi_message可以有多个spi_transfer;

1.2 SPI控制器数据结构

  参考内核文件:include\linux\spi\spi.h,Linux中使用spi_master结构体描述SPI控制器,有两套传输方法:
在这里插入图片描述

2. SPI传输函数的两种方法

/*** struct spi_message - 一个包含多段SPI传输的事务* @transfers: 本次事务中传输段的列表* @spi: SPI设备,事务将被排队到该设备* @is_dma_mapped: 如果为真,调用者为每个传输缓冲区提供了DMA和CPU虚拟地址* @complete: 被调用以报告事务完成情况* @context: 当complete()被调用时传递给它的参数* @frame_length: 消息中的总字节数* @actual_length: 在所有成功段中传输的总字节数* @status: 零表示成功,否则为负的errno值* @queue: 由当前拥有消息的驱动程序使用* @state: 由当前拥有消息的驱动程序使用* @resources: 在处理SPI消息时用于资源管理** spi_message用于执行一个原子序列的数据传输,每个传输段由一个spi_transfer结构表示。* 这个序列是"原子"的,意味着直到序列完成之前,任何其他spi_message都不能使用该SPI总线。* 在某些系统上,许多这样的序列可以作为单个编程的DMA传输执行。在所有系统上,这些消息都被排队,* 并且可能在其他设备的事务之后完成。发送到特定spi_device的消息总是以FIFO顺序执行。** 提交spi_message(及其spi_transfers)到较低层的代码负责管理其内存。* 零初始化每个你没有显式设置的字段,以防止未来的API更新影响。在你提交消息及其传输后,* 直到其完成回调之前,忽略它们。*/
struct spi_message {struct list_head transfers; // 传输段列表struct spi_device *spi; // SPI设备指针unsigned is_dma_mapped:1; // 标志位,用于指示是否进行了DMA映射// 完成报告通过回调进行void (*complete)(void *context); // 完成回调函数指针void *context; // 回调上下文unsigned frame_length; // 消息帧长度unsigned actual_length; // 实际传输长度int status; // 状态,成功为0,失败为负值// 由当前拥有spi_message的驱动程序可选使用struct list_head queue; // 队列void *state; // 状态信息// 处理SPI消息时的资源列表struct list_head resources;ANDROID_KABI_RESERVE(1); // 保留字段,用于Android KABI
};
APP -> Driver -> spi_sync 函数
1. 从spi device找到spi_master
2. 把message放到spi_master的queue
3. scheduler work:a.从queue取出messageb.启动传输c.等待传输完成d.传输完成触发中断,去唤醒等待传输完成的程序
4. 等待message传输完成;
/*** spi_sync - 阻塞/同步SPI数据传输* @spi: 与之交换数据的设备* @message: 描述数据传输* Context: 可以睡眠** 此调用仅可用于可以从允许睡眠的上下文中使用。睡眠是不可中断的,没有超时。* 低开销的控制器驱动程序可以直接DMA到消息缓冲区和从中DMA出来。** 注意,SPI设备的片选信号在消息期间是激活的,然后通常在消息之间禁用。* 一些常用设备的驱动程序可能希望减少选择芯片的成本,通过在芯片被选中后保持选中状态,* 以期待下一条消息将发送到相同的芯片。(这可能会增加功耗使用。)** 此外,调用者保证在该调用返回之前,不会释放与消息关联的内存。** 返回: 成功时返回零,否则返回一个负的错误码。*/
int spi_sync(struct spi_device *spi, struct spi_message *message)
{int ret;// 锁定SPI总线,以确保数据传输的同步性mutex_lock(&spi->controller->bus_lock_mutex);// 执行实际的SPI同步传输操作ret = __spi_sync(spi, message);// 解锁SPI总线mutex_unlock(&spi->controller->bus_lock_mutex);return ret;
}
// 将spi_sync符号导出,允许其他模块使用
EXPORT_SYMBOL_GPL(spi_sync);
/** 函数__spi_sync用于在SPI设备上同步传输数据。* 它负责将SPI消息排队并等待传输完成。* * 参数:* spi: 指向SPI设备结构的指针。* message: 指向SPI消息结构的指针,包含要传输的数据和配置。* * 返回:* 传输操作的状态,0表示成功,非0表示错误代码。*/
static int __spi_sync(struct spi_device *spi, struct spi_message *message)
{// 在栈上声明一个完成量,用于同步传输完成。DECLARE_COMPLETION_ONSTACK(done);int status;struct spi_controller *ctlr = spi->controller;unsigned long flags;// 验证SPI设备和消息的有效性。status = __spi_validate(spi, message);if (status != 0)return status;// 设置消息的完成回调和上下文。message->complete = spi_complete;message->context = &done;message->spi = spi;// 增加控制器和设备的spi_sync操作统计。SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics, spi_sync);SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);// 新方法,使用内核提供的transfer 函数// 如果不使用老的传输方法,将尝试在调用上下文中传输,需要特殊处理。if (ctlr->transfer == spi_queued_transfer) {// 锁定控制器的总线锁,并保存中断状态。spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);// 记录SPI消息提交的跟踪信息。trace_spi_message_submit(message);// 执行排队传输,不启用DMA。status = __spi_queued_transfer(spi, message, false);// 解锁控制器的总线锁,并恢复中断状态。spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);} else {// 使用异步锁定方式传输。status = spi_async_locked(spi, message);}// 如果传输状态为0,表示成功启动传输,则继续处理。if (status == 0) {// 如果使用的是排队传输方式,尝试立即推送消息。if (ctlr->transfer == spi_queued_transfer) {// 增加立即同步传输的统计。SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics, spi_sync_immediate);SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync_immediate);// 推送消息到SPI控制器。__spi_pump_messages(ctlr, false);}// 等待传输完成。wait_for_completion(&done);// 获取传输后的状态。status = message->status;}// 清空消息的上下文。message->context = NULL;// 返回传输操作的状态。return status;
}

2.1 老方法

在这里插入图片描述
老方法需要自己实现对queue的管理!
在这里插入图片描述

2.2 新方法

在这里插入图片描述

int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)__spi_queued_transfer/*** 函数:__spi_queued_transfer* 功能:将SPI消息添加到传输队列中* 描述:此函数将给定的SPI消息(msg)添加到其控制器(ctlr)的传输队列中,*       并根据控制器的状态和是否需要启动传输来决定是否启动传输工作。* 参数:*   - spi: 指向spi_device结构体的指针,表示SPI设备信息*   - msg: 指向spi_message结构体的指针,表示待传输的SPI消息*   - need_pump: 布尔值,表示是否在队列为空时启动传输* 返回值:*   - 0: 表示成功将消息加入队列*   - 负值: 表示错误,如控制器正在关闭则返回-ESHUTDOWN*/
static int __spi_queued_transfer(struct spi_device *spi,struct spi_message *msg,bool need_pump)
{// 获取SPI控制器的信息struct spi_controller *ctlr = spi->controller;// 用于在中断上下文中保存和恢复中断状态的变量unsigned long flags;// 加锁以保护队列,防止同时修改spin_lock_irqsave(&ctlr->queue_lock, flags);// 检查控制器是否正在关闭if (!ctlr->running) {// 如果是,解锁并返回错误spin_unlock_irqrestore(&ctlr->queue_lock, flags);return -ESHUTDOWN;}// 初始化消息的实际长度和状态msg->actual_length = 0;msg->status = -EINPROGRESS;// 将消息添加到队列尾部list_add_tail(&msg->queue, &ctlr->queue);// 如果控制器空闲且需要启动传输,则启动传输工作if (!ctlr->busy && need_pump)kthread_queue_work(ctlr->kworker, &ctlr->pump_messages);// 解锁并恢复中断状态spin_unlock_irqrestore(&ctlr->queue_lock, flags);return 0;
}
/*** struct spi_bitbang_cs - 用于SPI通信的位爆炸(bit-bang)芯片选择结构体** @nsecs: 用于表示时钟周期时间的一半,作为时间基准,用于在spi_transfer过程中计算延迟。* @txrx_word: 是一个函数指针,用于发送(接收)一个单词大小的数据* @txrx_bufs: 是一个函数指针,用于处理缓冲区的发送(接收)操作** 该结构体主要用于在SPI通信中通过软件方式控制芯片选择(Chip Select, CS)信号* 提供的操作函数指针允许在不同SPI设备之间发送和接收数据*/
struct spi_bitbang_cs {unsigned	nsecs;	/* (clock cycle time)/2 */u32		(*txrx_word)(struct spi_device *spi, unsigned nsecs,u32 word, u8 bits, unsigned flags);unsigned	(*txrx_bufs)(struct spi_device *,u32 (*txrx_word)(struct spi_device *spi,unsigned nsecs,u32 word, u8 bits,unsigned flags),unsigned, struct spi_transfer *,unsigned);
};/** 发送接收单个字的函数指针。* 用于在spi_device上执行一次SPI传输操作。* * 参数:*   spi - SPI设备结构体指针。*   nsecs - SPI控制器的时钟周期时间的一半。*   word - 要发送的数据字。*   bits - 数据字的位数。*   flags - 传输操作的标志。* * 返回值:*   从SPI设备接收到的数据字。*/
u32 (*txrx_word)(struct spi_device *spi, unsigned nsecs,u32 word, u8 bits, unsigned flags);/** 发送接收缓冲区的函数指针。* 用于在spi_device上执行一系列SPI传输操作。* * 参数:*   spi - SPI设备结构体指针。*   txrx_word - 发送接收单个字的函数指针。*   nsecs - SPI控制器的时钟周期时间的一半。*   words - 数据字数组。*   bits - 数据字的位数。*   flags - 传输操作的标志。* * 返回值:*   传输操作的数量。*/
unsigned (*txrx_bufs)(struct spi_device *,u32 (*txrx_word)(struct spi_device *spi,unsigned nsecs,u32 word, u8 bits,unsigned flags),unsigned, struct spi_transfer *,unsigned);

二、如何编写SPI_Master驱动程序

1. 编写设备树

在设备树中,对于SPI Master,必须的属性如下:

  • #address-cells:这个SPI Master下的SPI设备,需要多少个cell来表述它的片选引脚
  • #size-cells:必须设置为0
  • compatible:根据它找到SPI Master驱动

可选的属性如下:

  • cs-gpios:SPI Master可以使用多个GPIO当做片选,可以在这个属性列出那些GPIO
  • num-cs:片选引脚总数

其他属性都是驱动程序相关的,不同的SPI Master驱动程序要求的属性可能不一样。

在SPI Master对应的设备树节点下,每一个子节点都对应一个SPI设备,这个SPI设备连接在该SPI Master下面。

这些子节点中,必选的属性如下:

  • compatible:根据它找到SPI Device驱动
  • reg:用来表示它使用哪个片选引脚
  • spi-max-frequency:必选,该SPI设备支持的最大SPI时钟

可选的属性如下:

  • spi-cpol:这是一个空属性(没有值),表示CPOL为1,即平时SPI时钟为低电平
  • spi-cpha:这是一个空属性(没有值),表示CPHA为1,即在时钟的第2个边沿采样数据
  • spi-cs-high:这是一个空属性(没有值),表示片选引脚高电平有效
  • spi-3wire:这是一个空属性(没有值),表示使用SPI 三线模式
  • spi-lsb-first:这是一个空属性(没有值),表示使用SPI传输数据时先传输最低位(LSB)
  • spi-tx-bus-width:表示有几条MOSI引脚;没有这个属性时默认只有1条MOSI引脚
  • spi-rx-bus-width:表示有几条MISO引脚;没有这个属性时默认只有1条MISO引脚
  • spi-rx-delay-us:单位是毫秒,表示每次读传输后要延时多久
  • spi-tx-delay-us:单位是毫秒,表示每次写传输后要延时多久

2. 编写驱动程序

  • 核心为:分配/设置/注册spi_master结构体
  • 对于老方法,spi_master结构体的核心是transfer函数

数据传输流程:
在这里插入图片描述

三、SPI_Master驱动程序简单示例demo

1. 使用老方法编写的SPI Master驱动程序

virtual_spi_master {compatible = "100ask,virtual_spi_master";status = "okay";cs-gpios = <&gpio4 27 GPIO_ACTIVE_LOW>;num-chipselects = <1>;#address-cells = <1>;#size-cells = <0>;virtual_spi_dev: virtual_spi_dev@0 {compatible = "spidev";reg = <0>;spi-max-frequency = <100000>;};
};
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/spi/spi.h>static struct spi_master *g_virtual_master;
static struct work_struct g_virtual_ws;static const struct of_device_id spi_virtual_dt_ids[] = {{ .compatible = "100ask,virtual_spi_master", },{ /* sentinel */ }
};static void spi_virtual_work(struct work_struct *work)
{struct spi_message *mesg;while (!list_empty(&g_virtual_master->queue)) {mesg = list_entry(g_virtual_master->queue.next, struct spi_message, queue);list_del_init(&mesg->queue);/* 假装硬件传输已经完成 */mesg->status = 0;if (mesg->complete)mesg->complete(mesg->context);}	
}static int spi_virtual_transfer(struct spi_device *spi, struct spi_message *mesg)
{
#if 0	/* 方法1: 直接实现spi传输 *//* 假装传输完成, 直接唤醒 */mesg->status = 0;mesg->complete(mesg->context);return 0;#else/* 方法2: 使用工作队列启动SPI传输、等待完成 *//* 把消息放入队列 */mesg->actual_length = 0;mesg->status = -EINPROGRESS;list_add_tail(&mesg->queue, &spi->master->queue);/* 启动工作队列 */schedule_work(&g_virtual_ws);/* 直接返回 */return 0;
#endif	
}static int spi_virtual_probe(struct platform_device *pdev)
{struct spi_master *master;int ret;/* 分配/设置/注册spi_master */g_virtual_master = master = spi_alloc_master(&pdev->dev, 0);if (master == NULL) {dev_err(&pdev->dev, "spi_alloc_master error.\n");return -ENOMEM;}master->transfer = spi_virtual_transfer;INIT_WORK(&g_virtual_ws, spi_virtual_work);master->dev.of_node = pdev->dev.of_node;ret = spi_register_master(master);if (ret < 0) {printk(KERN_ERR "spi_register_master error.\n");spi_master_put(master);return ret;}return 0;}static int spi_virtual_remove(struct platform_device *pdev)
{/* 反注册spi_master */spi_unregister_master(g_virtual_master);return 0;
}static struct platform_driver spi_virtual_driver = {.probe = spi_virtual_probe,.remove = spi_virtual_remove,.driver = {.name = "virtual_spi",.of_match_table = spi_virtual_dt_ids,},
};static int virtual_master_init(void)
{return platform_driver_register(&spi_virtual_driver);
}static void virtual_master_exit(void)
{platform_driver_unregister(&spi_virtual_driver);
}module_init(virtual_master_init);
module_exit(virtual_master_exit);MODULE_DESCRIPTION("Virtual SPI bus driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.100ask.net");

2. 使用新方法编写的SPI Master驱动程序

vitural_spi_master {compatible = "100ask,virtual_spi_master";status = "okay";cs-gpios = <&gpio4 27 GPIO_ACTIVE_LOW>;num-chipselects = <1>;#address-cells = <1>;#size-cells = <0>;virtual_spi_dev: virtual_spi_dev@0 {compatible = "spidev";reg = <0>;spi-max-frequency = <100000>;};
};
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>static struct spi_master *g_virtual_master;
static struct spi_bitbang *g_virtual_bitbang;
static struct completion g_xfer_done;static const struct of_device_id spi_virtual_dt_ids[] = {{ .compatible = "100ask,virtual_spi_master", },{ /* sentinel */ }
};/* xxx_isr() { complete(&g_xfer_done)  } */static int spi_virtual_transfer(struct spi_device *spi,struct spi_transfer *transfer)
{int timeout;#if 1	/* 1. init complete */reinit_completion(&g_xfer_done);/* 2. 启动硬件传输 */complete(&g_xfer_done);/* 3. wait for complete */timeout = wait_for_completion_timeout(&g_xfer_done,100);if (!timeout) {dev_err(&spi->dev, "I/O Error in PIO\n");return -ETIMEDOUT;}
#endifreturn transfer->len;
}static void	spi_virtual_chipselect(struct spi_device *spi, int is_on)
{
}static int spi_virtual_probe(struct platform_device *pdev)
{struct spi_master *master;int ret;/* 分配/设置/注册spi_master */g_virtual_master = master = spi_alloc_master(&pdev->dev, sizeof(struct spi_bitbang));if (master == NULL) {dev_err(&pdev->dev, "spi_alloc_master error.\n");return -ENOMEM;}g_virtual_bitbang = spi_master_get_devdata(master);init_completion(&g_xfer_done);/* 怎么设置spi_master?* 1. spi_master使用默认的函数* 2. 分配/设置 spi_bitbang结构体: 主要是实现里面的txrx_bufs函数* 3. spi_master要能找到spi_bitbang*/g_virtual_bitbang->master = master;g_virtual_bitbang->txrx_bufs  = spi_virtual_transfer;g_virtual_bitbang->chipselect = spi_virtual_chipselect;master->dev.of_node = pdev->dev.of_node;ret = spi_bitbang_start(g_virtual_bitbang);if (ret) {printk("bitbang start failed with %d\n", ret);return ret;}return 0;
}static int spi_virtual_remove(struct platform_device *pdev)
{spi_bitbang_stop(g_virtual_bitbang);spi_master_put(g_virtual_master);return 0;
}static struct platform_driver spi_virtual_driver = {.probe = spi_virtual_probe,.remove = spi_virtual_remove,.driver = {.name = "virtual_spi",.of_match_table = spi_virtual_dt_ids,},
};static int virtual_master_init(void)
{return platform_driver_register(&spi_virtual_driver);
}static void virtual_master_exit(void)
{platform_driver_unregister(&spi_virtual_driver);
}module_init(virtual_master_init);
module_exit(virtual_master_exit);MODULE_DESCRIPTION("Virtual SPI bus driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.100ask.net");

  本文章参考了韦东山老师驱动大全部分笔记,其余内容为自己整理总结而来。水平有限,欢迎各位在评论区指导交流!!!😁😁😁

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

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

相关文章

Webrtc开发实战系列 - win10+vs2022下编译最新webrtc代码

1. 准备起步 操作系统&#xff1a;windows 10 安装 vs2019/vs2022 安装 win10 sdk 19041 一定勾选 Debugging Tools for Windows 科学上网准备代理工具 磁盘剩余空间至少 30G 推荐用一台干净的机器或者虚拟机来编译WebRTC&#xff0c;安装过python的会出现一些非常棘手…

昂首资本:欧美货币对的交易智慧

在外汇市场的海洋中&#xff0c;昂首资本的投资者们深知&#xff0c;把握欧美货币对的交易时段是获取收益的关键。欧美货币对&#xff0c;即欧元对美元&#xff0c;因其在欧洲和美国市场的活跃交易时段而备受瞩目。这两个时段不仅交易量巨大&#xff0c;而且价格波动剧烈&#…

【隐私计算篇】利用多方安全计算MPC实现VGG16人脸识别隐私推理

1. 背景介绍 本文主要介绍一种利用多方安全计算MPC技术&#xff0c;实现VGG16的人脸识别模型&#xff0c;侧重于模型推理阶段&#xff0c;目前已经公开专利&#xff0c;因此以下内容的分享都是基于公开材料。该分享涉及到最小化多方安全计算(MPC)以及明密文混合计算的思想&…

JAVA开源项目 甘肃非物质文化网站 计算机毕业设计

本文项目编号 T 043 &#xff0c;文末自助获取源码 \color{red}{T043&#xff0c;文末自助获取源码} T043&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

python画图|把X轴标签移动到图像顶端

在前述学习过程中&#xff0c;我们一直使用的是默认的轴坐标&#xff0c;X轴往往置于图像的下端。 有时候&#xff0c;也会有将X轴标签放置在图形顶端的需求&#xff0c;今天就一起学习一下。 【1】官网教程 首先打开官网&#xff0c;可以通过下述链接一步直达&#xff1a; …

软考高级:系统安全 -区块链特点:去中心化、开放性、自治性、安全性、匿名性

讲解 生活化例子 想象一下&#xff0c;你和朋友们玩一个共享账本的游戏。每个人都可以在账本上记账&#xff0c;没人可以单独改动账本&#xff0c;大家都可以随时查看账本内容&#xff0c;也不用再信任某个单独的人来管理账本。这就类似于区块链的工作原理。 概念讲解 去中…

基于c++实现的简易shell

代码逻辑 核心思想 解析命令行&#xff0c;拆解命令及其选项创建子进程&#xff0c;在子进程中执行命令如果是前台执行命令&#xff0c;则父进程就阻塞等待子进程中命令执行结束后回收子进程的资源如果是后台执行命令&#xff0c;则父进程不进行阻塞等待&#xff0c;可继续向下…

【机器学习】---神经架构搜索(NAS)

这里写目录标题 引言1. 什么是神经架构搜索&#xff08;NAS&#xff09;1.1 为什么需要NAS&#xff1f; 2. NAS的三大组件2.1 搜索空间搜索空间设计的考虑因素&#xff1a; 2.2 搜索策略2.3 性能估计 3. NAS的主要方法3.1 基于强化学习的NAS3.2 基于进化算法的NAS3.3 基于梯度的…

【数据结构】图的遍历

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《C游记》《进击的C》《Linux迷航》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、深度优先遍历1.1 定义1.2 实现 二、广度优先遍历2.1 定义2.2 实现 三、DFS与BFS的对比 引言 前置知识&…

linux用户管理运行级别找回root密码

目录 1.用户的添加 1.1用户添加的基本指令 1.2不指定家目录的名称 1.3指定家目录的名称 2.密码的修改 3.删除目录 3.1删除的两个情况 3.2删除的流程 4.查询用户的信息 5.用户的切换 6.用户组 6.1用户组的概念 6.2创建用户到指定的组 6.3修改用户到其他的组 6.4用…

SpringCloud Alibaba之Sentinel实现熔断与限流

&#xff08;学习笔记&#xff09; QPS&#xff08;Query Per Second&#xff09;&#xff1a;即每秒查询率&#xff0c;是对⼀个特定的查询服务器在规定时间内所处理流量多少的衡量标准。QPS req/sec 请求数/秒&#xff0c;即每秒的响应请求数&#xff0c;也即是最⼤吞吐能⼒…

ATTCK实战系列-Vulnstack三层网络域渗透靶场(一)

ATT&CK实战系列-Vulnstack三层网络域渗透靶场&#xff08;一&#xff09; 一、环境搭建1.1 靶场拓扑图1.2 靶场下载链接1.3 虚拟机配置1.3.1 Windows 7 (web服务器)1.3.2 Windows 2008 (域控)1.3.3 Win2k3 (域内主机) 二、外网打点突破2.1 信息搜集2.2 phpmyadmin 后台 Get…

肾癌的多模态预测模型-临床-组织学-基因组

目录 摘要 技术路线 ① lncRNA的预测模型 ②病理 WSI 的分类器 ③临床病理分类器 模型结果 与别的模型比较 同行评审学习 1&#xff09;使用lncRNA的原因 2&#xff09;模型临床使用意义 3&#xff09;关于截止值的使用 摘要 A multi-classifier system integrated…

.NET常见的5种项目架构模式

前言 项目架构模式在软件开发中扮演着至关重要的角色&#xff0c;它们为开发者提供了一套组织和管理代码的指导原则&#xff0c;以提高软件的可维护性、可扩展性、可重用性和可测试性。 假如你有其他的项目架构模式推荐&#xff0c;欢迎在文末留言&#x1f91e;&#xff01;&a…

Java_Day04学习

类继承实例 package com.dx.test03; public class extendsTest {public static void main(String args[]) {// 实例化一个Cat对象&#xff0c;设置属性name和age&#xff0c;调用voice()和eat()方法&#xff0c;再打印出名字和年龄信息/********* begin *********/Cat cat ne…

实战OpenCV之直方图

基础入门 直方图是对数据分布情况的图形表示&#xff0c;特别适用于图像处理领域。在图像处理中&#xff0c;直方图通常用于表示图像中像素值的分布情况。直方图由一系列矩形条&#xff08;也被称为bin&#xff09;组成&#xff0c;每个矩形条的高度表示某个像素值&#xff08;…

鸿蒙设置,修改APP图标和名称

1、先看默认的图标和名称 2、打开项目开始设置自己需要的图标和名称 2.1找到 路径src\main\module.json5&#xff0c; 找到 abilities&#xff0c;下的&#xff0c;图标icon、名称label&#xff0c;label可以按住ctrl鼠标左键点击跳转 2.2先修改APP名称 1、ctrl鼠标左键点击…

华为OD机试 - 选修课(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

【C语言零基础入门篇 - 15】:单链表

文章目录 单链表链表的基本概念单链表功能的实现单链表的初始化单链表新结点的创建单链表头插法单链表的输出单链表的查找单链表修改单链表的删除单链表所有数据结点释放源代码 单链表 链表的基本概念 一、什么是链表&#xff1f; 链表是数据结构中线性表的一种&#xff0c;其…

华为OD机试 - 需要打开多少监控器(Java 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加…