RK3568驱动指南|第十六篇 SPI-第188章 mcp2515驱动编写:复位函数

news2024/10/6 2:25:13

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十六篇 SPI_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


  1. 第188章 mcp2515驱动编写:复位函数

在上一章中填充了mcp2515字符设备注册相关的内容,有了mcp2515对应的设备节点,而实际上仍旧属于编写通用SPI外设驱动的范畴,如果要编写其他外设的SPI驱动程序,同样要完成上一章编写的驱动内容,而从本章节开始才真正进入到mcp2515特性相关驱动的编写,在本章节将填充mcp2515的复位函数。

188.1 理论分析

MCP2515具有五种模式,分别为配置模式、正常模式、休眠模式、仅监听模式和环回模式,只有在配置模式下,才能对关键寄存器进行初始化和配置,当MCP2515上电或者复位时,器件会自动进入配置模式,而MCP2515提供了一系列的SPI指令,SPI指令表如下图所示:

通过向MCP2515发送上述SPI指令就能实现复位、读、写等操作,复位操作对应的指令格式为11000000,在Linux驱动中可以使用spi_write函数来实现向SPI从设备发送数据,spi_write函数定义在include/linux/spi/spi.h文件中,具体内容如下所示:

static inline int
spi_write(struct spi_device *spi, const void *buf, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= buf,
			.len		= len,
		};

	return spi_sync_transfer(spi, &t, 1);
}

该函数首先会对要传输的数据以及传输的数据大小进行封装,然后调用spi_sync_transfer函数进行输入传输,spi_write函数传入的第一个参数为spi_device类型的结构体变量,struct spi_device是Linux内核中用于描述SPI从设备的结构体。它包含了与 SPI 设备相关的各种信息和配置选项,该结构体的具体内容如下所示:

struct spi_device {
    struct device dev; // 通用设备模型的设备结构体
    struct spi_controller *controller; // 指向控制器的指针
    struct spi_controller *master; // 兼容层,指向控制器的指针(与controller相同)
    u32 max_speed_hz; // 设备支持的最大速度(以赫兹为单位)
    u8 chip_select; // 片选编号
    u8 bits_per_word; // 每个字的位数
    u16 mode; // SPI 模式配置(包括时钟相位和极性等)
    int irq; // 中断号
    void *controller_state; // 控制器状态的私有数据
    void *controller_data; // 控制器数据的私有数据
    char modalias[SPI_NAME_SIZE]; // 设备别名
    const char *driver_override; // 驱动程序覆盖
    int cs_gpio; // 片选 GPIO 引脚
    struct spi_statistics statistics; // 统计数据

    /* mode flags */
    #define SPI_CPHA 0x01 // 时钟相位
    #define SPI_CPOL 0x02 // 时钟极性
    #define SPI_MODE_0 (0|0) // 模式0
    #define SPI_MODE_1 (0|SPI_CPHA) // 模式1
    #define SPI_MODE_2 (SPI_CPOL|0) // 模式2
    #define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) // 模式3
    #define SPI_CS_HIGH 0x04 // 片选高电平有效
    #define SPI_LSB_FIRST 0x08 // 最低有效位先传输
    #define SPI_3WIRE 0x10 // 三线模式
    #define SPI_LOOP 0x20 // 环回模式
    #define SPI_NO_CS 0x40 // 无片选信号
    #define SPI_READY 0x80 // 从设备拉低以暂停
    #define SPI_TX_DUAL 0x100 // 双线发送
    #define SPI_TX_QUAD 0x200 // 四线发送
    #define SPI_RX_DUAL 0x400 // 双线接收
    #define SPI_RX_QUAD 0x800 // 四线接收
    #define SPI_CS_WORD 0x1000 // 每个字后切换片选
};

所以在调用spi_write函数之前需要先定义一个struct spi_device类型的用于描述SPI设备的结构体,然后来编写MCP2515的复位函数,编写完成如下所示:

struct spi_device *spi_dev; // SPI设备指针

// MCP2515芯片复位函数
void mcp2515_reset(void){
    int ret;
    char write_buf[] = {0xc0}; // 复位指令0x11000000即0xc0
    ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // 发送复位命令
    if(ret < 0){
        printk("spi_write is error\n"); // 打印错误信息
    }
}

由于这里只编写了MCP2515的复位函数,无法进行验证,所以本章节并不能进行相应的实验,在下个小节中将会对SPI通信流程进行讲解。

188.2 SPI通信流程

在上个小节中讲解的spi_write函数可以向SPI从设备发送数据,而spi_read函数可以接收从设备发送的数据,spi_read函数具体内容如下所示:

static inline int
spi_read(struct spi_device *spi, void *buf, size_t len)
{
	struct spi_transfer	t = {
			.rx_buf		= buf,
			.len		= len,
		};

	return spi_sync_transfer(spi, &t, 1);
}

跟spi_write函数相同,spi_read函数也会对数据进行封包的操作,将数据buf以及数据大小len封包成spi_transfer类型的结构体,struct spi_transfer是一个描述SPI数据传输的结构体,用于配置一次SPI数据传输的各种参数,该结构体的具体内容如下所示:

struct spi_transfer {
    const void *tx_buf;    // 发送缓冲区
    void *rx_buf;          // 接收缓冲区
    unsigned len;          // 传输数据的长度

    dma_addr_t tx_dma;     // 发送缓冲区的 DMA 地址
    dma_addr_t rx_dma;     // 接收缓冲区的 DMA 地址
    struct sg_table tx_sg; // 发送缓冲区的散列-聚集表
    struct sg_table rx_sg; // 接收缓冲区的散列-聚集表

    unsigned cs_change:1;  // 是否在传输后改变片选状态
    unsigned tx_nbits:3;   // 发送的位数(单线、双线或四线传输)
    unsigned rx_nbits:3;   // 接收的位数(单线、双线或四线传输)

    #define SPI_NBITS_SINGLE 0x01 // 1 位传输
    #define SPI_NBITS_DUAL   0x02 // 2 位传输
    #define SPI_NBITS_QUAD   0x04 // 4 位传输

    u8 bits_per_word;     // 每个字的位数
    u16 delay_usecs;      // 传输之间的延迟(微秒)
    u32 speed_hz;         // 传输速度(赫兹)
    u16 word_delay;       // 每个字之间的延迟

    struct list_head transfer_list; // 传输链表,用于将多个传输串联起来
};

而spi_write函数和spi_read函数只差在struct spi_transfer结构体参数的不同,而封装为struct spi_transfer之后还需要再一次进行封装,spi_write函数和spi_read函数最后都会调用 spi_sync_transfer函数,该函数的具体内容如下所示:

static inline int
spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers,
	unsigned int num_xfers)
{
	struct spi_message msg;

	// 使用给定的传输初始化 SPI 消息
	spi_message_init_with_transfers(&msg, xfers, num_xfers);

	// 同步方式发送 SPI 消息
	return spi_sync(spi, &msg);
}

这个函数主要用于封装 SPI 同步传输操作,简化了调用过程。在第8行调用了spi_message_init_with_transfers函数进行SPI传输数据的初始化,最后在第11行调用spi_sync函数采用同步的方式发送SPI数据,spi_sync函数的具体内容如下所示:

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数据传输操作在一个互斥锁的保护下进行,以避免并发传输导致的冲突和数据错误。通过调用内部的 __spi_sync 函数来执行实际的数据传输。__spi_sync函数如下所示:

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_STATISTICS_INCREMENT_FIELD(&ctlr->statistics, spi_sync);
	SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);

	/* 如果我们不使用旧的传输方法,
	 * 那么我们将在调用上下文中尝试传输以进行特殊处理。
	 * 如果我们能够删除对驱动程序实现的消息队列的支持,这段代码会更简单。
	 */
	if (ctlr->transfer == spi_queued_transfer) {
		// 锁定总线锁旋转锁并保存中断标志
		spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);

		// 记录 SPI 消息提交的跟踪信息
		trace_spi_message_submit(message);

		// 执行队列传输
		status = __spi_queued_transfer(spi, message, false);

		// 解锁总线锁旋转锁并恢复中断标志
		spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);
	} else {
		// 异步锁定传输
		status = spi_async_locked(spi, message);
	}

	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_pump_messages(ctlr, false);
		}

		// 等待完成
		wait_for_completion(&done);
		// 获取消息的状态
		status = message->status;
	}
	// 清除消息的上下文
	message->context = NULL;
	return status;
}

该函数的主要作用是在锁定的上下文中同步执行SPI消息传输。它负责初始化传输消息,验证消息和设备的有效性,处理传输,并在完成后返回传输的状态。该函数的重点在__spi_pump_messages推送消息函数,该函数的具体内容如下所示:

static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
{
	unsigned long flags;
	bool was_busy = false;
	int ret;

	/* 锁定消息队列 */
	spin_lock_irqsave(&ctlr->queue_lock, flags);

	/* 确保没有其他消息正在处理 */
	if (ctlr->cur_msg) {
		spin_unlock_irqrestore(&ctlr->queue_lock, flags);
		return;
	}

	/* 如果另一个上下文正在空闲设备,则推迟处理 */
	if (ctlr->idling) {
		kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);
		spin_unlock_irqrestore(&ctlr->queue_lock, flags);
		return;
	}

	/* 检查队列是否空闲 */
	if (list_empty(&ctlr->queue) || !ctlr->running) {
		if (!ctlr->busy) {
			spin_unlock_irqrestore(&ctlr->queue_lock, flags);
			return;
		}

		/* 只有在线程中执行拆除操作 */
		if (!in_kthread) {
			kthread_queue_work(&ctlr->kworker,
					   &ctlr->pump_messages);
			spin_unlock_irqrestore(&ctlr->queue_lock, flags);
			return;
		}

		ctlr->busy = false;
		ctlr->idling = true;
		spin_unlock_irqrestore(&ctlr->queue_lock, flags);

		kfree(ctlr->dummy_rx);
		ctlr->dummy_rx = NULL;
		kfree(ctlr->dummy_tx);
		ctlr->dummy_tx = NULL;
		if (ctlr->unprepare_transfer_hardware &&
		    ctlr->unprepare_transfer_hardware(ctlr))
			dev_err(&ctlr->dev,
				"failed to unprepare transfer hardware\n");
		if (ctlr->auto_runtime_pm) {
			pm_runtime_mark_last_busy(ctlr->dev.parent);
			pm_runtime_put_autosuspend(ctlr->dev.parent);
		}
		trace_spi_controller_idle(ctlr);

		spin_lock_irqsave(&ctlr->queue_lock, flags);
		ctlr->idling = false;
		spin_unlock_irqrestore(&ctlr->queue_lock, flags);
		return;
	}

	/* 从队列中获取第一个消息 */
	ctlr->cur_msg =
		list_first_entry(&ctlr->queue, struct spi_message, queue);

	list_del_init(&ctlr->cur_msg->queue);
	if (ctlr->busy)
		was_busy = true;
	else
		ctlr->busy = true;
	spin_unlock_irqrestore(&ctlr->queue_lock, flags);

	mutex_lock(&ctlr->io_mutex);

	if (!was_busy && ctlr->auto_runtime_pm) {
		ret = pm_runtime_get_sync(ctlr->dev.parent);
		if (ret < 0) {
			pm_runtime_put_noidle(ctlr->dev.parent);
			dev_err(&ctlr->dev, "Failed to power device: %d\n",
				ret);
			mutex_unlock(&ctlr->io_mutex);
			return;
		}
	}

	if (!was_busy)
		trace_spi_controller_busy(ctlr);

	if (!was_busy && ctlr->prepare_transfer_hardware) {
		ret = ctlr->prepare_transfer_hardware(ctlr);
		if (ret) {
			dev_err(&ctlr->dev,
				"failed to prepare transfer hardware\n");

			if (ctlr->auto_runtime_pm)
				pm_runtime_put(ctlr->dev.parent);
			mutex_unlock(&ctlr->io_mutex);
			return;
		}
	}

	trace_spi_message_start(ctlr->cur_msg);

	if (ctlr->prepare_message) {
		ret = ctlr->prepare_message(ctlr, ctlr->cur_msg);
		if (ret) {
			dev_err(&ctlr->dev, "failed to prepare message: %d\n",
				ret);
			ctlr->cur_msg->status = ret;
			spi_finalize_current_message(ctlr);
			goto out;
		}
		ctlr->cur_msg_prepared = true;
	}

	ret = spi_map_msg(ctlr, ctlr->cur_msg);
	if (ret) {
		ctlr->cur_msg->status = ret;
		spi_finalize_current_message(ctlr);
		goto out;
	}

	ret = ctlr->transfer_one_message(ctlr, ctlr->cur_msg);
	if (ret) {
		dev_err(&ctlr->dev,
			"failed to transfer one message from queue\n");
		goto out;
	}

out:
	mutex_unlock(&ctlr->io_mutex);

	/* 如果成功传输,则唤醒调度器 */
	if (!ret)
		cond_resched();
}

124行代码ctlr->transfer_one_message是一个函数指针,它指向了SPI控制器中负责执行SPI消息传输的函数。通过调用这个函数,将当前的SPI消息 ctlr->cur_msg 传递给该函数进行处理。这个函数通常会负责将消息的数据发送到SPI设备或从设备接收数据,并与硬件设备进行通信。

因此,ctlr->transfer_one_message(ctlr, ctlr->cur_msg)这一行代码的作用是将当前的SPI消息传递给SPI控制器中的传输函数进行处理,以完成消息的传输操作。

至此,SPI通信过程已经分析完毕,而除了spi_write和spi_read函数,还有第三个常用函数spi_write_then_read,该函数的作用为先写后读,在后面的章节中会用到该函数,该函数的具体内容如下所示:

int spi_write_then_read(struct spi_device *spi,
		const void *txbuf, unsigned n_tx,
		void *rxbuf, unsigned n_rx)
{
	// 定义一个静态互斥锁
	static DEFINE_MUTEX(lock);

	int			status;
	struct spi_message	message;
	struct spi_transfer	x[2];
	u8			*local_buf;

	/* 如果可以的话,使用预分配的 DMA 安全缓冲区。我们不能避免
	 * 在这里进行拷贝操作(作为一种纯粹的方便操作),但是我们可以
	 * 在缓冲区未被其他进程使用或传输不太大的情况下,
	 * 避免在热路径上分配堆内存。
	 */
	if ((n_tx + n_rx) > SPI_BUFSIZ || !mutex_trylock(&lock)) {
		local_buf = kmalloc(max((unsigned)SPI_BUFSIZ, n_tx + n_rx),
				    GFP_KERNEL | GFP_DMA);
		if (!local_buf)
			return -ENOMEM; // 如果内存分配失败,返回错误码
	} else {
		local_buf = buf;
	}

	// 初始化 SPI 消息
	spi_message_init(&message);
	memset(x, 0, sizeof(x));
	if (n_tx) {
		x[0].len = n_tx;
		spi_message_add_tail(&x[0], &message);
	}
	if (n_rx) {
		x[1].len = n_rx;
		spi_message_add_tail(&x[1], &message);
	}

	// 将发送数据复制到本地缓冲区
	memcpy(local_buf, txbuf, n_tx);
	x[0].tx_buf = local_buf;
	x[1].rx_buf = local_buf + n_tx;

	/* 执行 I/O 操作 */
	status = spi_sync(spi, &message);
	if (status == 0)
		// 如果传输成功,将接收数据复制到接收缓冲区
		memcpy(rxbuf, x[1].rx_buf, n_rx);

	// 释放锁或释放内存
	if (x[0].tx_buf == buf)
		mutex_unlock(&lock);
	else
		kfree(local_buf);

	return status; // 返回传输状态
}

至此,关于常用的SPI通信函数以及SPI通信流程就讲解完成了,在下个章节中将会继续对MCP2515驱动程序进行完善。

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

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

相关文章

基于java+springboot+vue实现的家政服务平台(文末源码+Lw)299

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本家政服务平台就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&a…

RAID详解

一、RAID存储是什么&#xff1f; RAID 存储&#xff08;Redundant Arrays of Independent Disks&#xff0c;独立磁盘冗余阵列&#xff09;是一种通过将多个独 立的物理磁盘组合在一起&#xff0c;以实现更高的存储性能、数据可靠性和容错能力的技术。 其主要目的是解决单个…

Appium启动APP时报错Security exception: Permission Denial

报错内容Security exception: Permission Denial: starting Intent 直接通过am命令尝试也是同样的报错 查阅资料了解到&#xff1a;android:exported | App quality | Android Developers exported属性默认false&#xff0c;所以android:exported"false"修改为t…

MATLAB中findall用法

目录 语法 说明 示例 查找具有可见或隐藏句柄的图窗 查找句柄处于隐藏状态的对象 查找 Text 对象 提示 findall的功能是查找所有图形对象。 语法 h findall(objhandles) h findall(objhandles,prop1,value1,...,propN,valueN) 说明 h findall(objhandles) 返回 ob…

12. Revit API: Document、Element

12. Revit API: Document、Element 前言 还是先讲一下Document吧&#xff0c;不然Selection不好讲&#xff0c;那涉及到了挺多东西的&#xff0c;比元素&#xff08;Element&#xff09;和各类Filter&#xff0c;这些都与Document有关&#xff0c;所以先简单讲一下这个。 一、…

牛!手机、TV双端聚合,免费可同步!

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 有不少小伙伴闲时会选择观看游戏、户外、娱乐等各类的直播&#xff0c;而关注的主播可能驻留在不同直播平台&#xff0c;需要下载多个APP&#xff0c;且切换非常不方便。 所以今天给大…

程序化交易广告及其应用

什么是程序化交易广告&#xff1f; 程序化交易广告是以实时竞价技术即RTB&#xff08;real-time bidding&#xff09;为核心的广告交易方式。说到这里&#xff0c;你可能会有疑问&#xff1a;像百度搜索关键词广告还有百度网盟的广告&#xff0c;不也是CPC实时竞价的吗&#x…

永劫无间国服延迟高、报错、卡顿的处理措施一览

永劫无间国服延迟高、报错、卡顿怎么办&#xff1f;快速解决办法分享 第一个办法&#xff1a;改善延迟 如果是一直遇到永劫无间国服延迟高、报错、卡顿的问题&#xff0c;重启游戏也不管用的话&#xff0c;那应该就是网络问题&#xff0c;玩家可以启动雷神&#xff0c;让其快速…

【CV炼丹师勇闯力扣训练营 Day22:§7 回溯1】

CV炼丹师勇闯力扣训练营 代码随想录算法训练营第22天 回溯法其实就是暴力查找,回溯的本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出我们想要的答案&#xff0c;一般可以解决如下几种问题&#xff1a; 组合问题&#xff1a;N个数里面按一定规则找出k个数的集合切割…

番外篇 | 手把手教你如何去更换YOLOv5的检测头为ASFF_Detect

前言:Hello大家好,我是小哥谈。自适应空间特征融合(ASFF)的主要原理旨在解决单次检测器中不同尺度特征的不一致性问题。具体来说,ASFF通过动态调整来自不同尺度特征金字塔层的特征贡献,确保每个检测对象的特征表示是一致且最优的。本文所做出的改进是将YOLOv5的检测头更换…

JVM原理(十一):JVM虚拟机六种必需对类进行初始化的情况

Java虚拟机把描述类的数据从Class文件加载到内存&#xff0c;并对数据进行校验、转换解析和初始化&#xff0c;最终形成可以被虚拟机直接使用的Java类型&#xff0c;这个过程被称作虚拟机的类加载机制。Java天生可以动态扩展的语言特性就是依赖运行期间动态加载和动态链接这个特…

西藏文旅与薛之谦梦幻联动共赴一场灵魂的西藏之约

【西藏文旅与薛之谦的梦幻联动&#xff1a;共赴一场灵魂的西藏之约】在这个快节奏的时代&#xff0c;每一颗渴望自由与宁静的心在寻找一片净土&#xff0c;而西藏&#xff0c;便是那片无数人梦寐以求的圣地。当西藏文旅以一句“啊啊啊 可以dream一个西藏吗&#xff1f;&#xf…

小米10屏幕录制在哪里?看了这篇就会了!

无论你是想记录手机游戏的精彩瞬间&#xff0c;还是想制作教学视频&#xff0c;或者只是想保存某个应用的操作教程&#xff0c;屏幕录制都能轻松帮你实现。那么&#xff0c;对于小米10用户来说&#xff0c;屏幕录制功能究竟在哪里&#xff1f;又有哪些录屏软件可以选择呢&#…

windows电脑蓝屏解决方法(亲测有效)

如果不是硬件问题&#xff0c;打开终端尝试以下命令 sfc /scannow DISM /Online /Cleanup-Image /RestoreHealth

等保2.0 实施方案

一、引言 随着信息技术的广泛应用&#xff0c;网络安全问题日益突出&#xff0c;为确保信息系统安全、稳定、可靠运行&#xff0c;保障国家安全、公共利益和个人信息安全&#xff0c;根据《网络安全法》及《信息安全技术 网络安全等级保护基本要求》&#xff08;等保2.0&#x…

如何用Python实现三维可视化?

Python拥有很多优秀的三维图像可视化工具&#xff0c;主要基于图形处理库WebGL、OpenGL或者VTK。 这些工具主要用于大规模空间标量数据、向量场数据、张量场数据等等的可视化&#xff0c;实际运用场景主要在海洋大气建模、飞机模型设计、桥梁设计、电磁场分析等等。 本文简单…

OpenCV直方图计算函数calcHist的使用

操作系统&#xff1a;ubuntu22.04OpenCV版本&#xff1a;OpenCV4.9IDE:Visual Studio Code编程语言&#xff1a;C11 功能描述 图像的直方图是一种统计表示方法&#xff0c;用于展示图像中不同像素强度&#xff08;通常是灰度值或色彩强度&#xff09;出现的频率分布。具体来说…

居然这么简单就能实现扫雷游戏!

目录 一.思路 1.成果展示 2.思路 二.具体操作 1.创建"棋盘" 2.初始化雷 3.布置雷 4.打印 5.排除雷 三.代码实现 1.test.c文件 2.thunder.h文件 3.thunder.c文件 Hello&#xff0c;大家好&#xff0c;今天我们来实现扫雷游戏&#xff0c;希望这一篇博客能给带给大家一…

Hadoop集群部署【一】HDFS详细介绍以及HDFS集群环境部署【hadoop组件HDFS笔记】(图片均为学习时截取的)

HDFS详细介绍 HDFS是什么 HDFS是Hadoop三大组件(HDFS、MapReduce、YARN)之一 全称是&#xff1a;Hadoop Distributed File System&#xff08;Hadoop分布式文件系统&#xff09;&#xff1b;是Hadoop技术栈内提供的分布式数据存储解决方案 可以在多台服务器上构建存储集群&…

对秒杀的思考

一、秒杀的目的 特价商品&#xff0c;数量有限&#xff0c;先到先得&#xff0c;售完为止 二、优惠券的秒杀 和特价商品的秒杀是一样的&#xff0c;只不过秒杀的商品是优惠券 三、秒杀的需求 秒杀前&#xff1a;提前将秒杀商品&#xff0c;存放到Redis秒杀中&#xff1a;使…