Linux第71步_将linux中的多个文件编译成一个驱动模块

学习目的:采用旧字符设备测试linux系统点灯,进一步熟悉其设计原理。采用多文件参与编译,深度学习编写Makefile,有利于实现驱动模块化设计。

1、创建MyOldLED目录

输入“cd /home/zgq/linux/Linux_Drivers/回车

切换到“/home/zgq/linux/Linux_Drivers/”目录

输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/”目录的文件和文件夹

输入“mkdir MyOldLED回车”,创建MyOldLED目录

切换到“/home/zgq/linux/Linux_Drivers/MyOldLED/”目录

输入“ls回车”查看“/home/zgq/linux/Linux_Drivers”目录的文件和文件夹

2、新建LEDInterface.c

打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“Linux_Drivers”,点击“MyOldLED”。点击“文件”,点击“新建文件”,点击“文件”,点击“另存为”。然后在“名称”右边的文本框中输入“LEDInterface.c”,点击“保存

LEDInterface.c文件内容如下:

#include "LED.h"

#include <linux/types.h>

/*

数据类型重命名

使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t

使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t

*/

#include <linux/kernel.h>

#include <linux/delay.h>

#include <linux/ide.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/string.h>

#include <linux/errno.h>

#include <linux/gpio.h>

#define led_MAJOR   200

/*

定义主设备号

静态分配设备号:在串口输入“cat/proc/devices”查询当前已用的主设备号

然后使用一个“没有被使用的设备号”作为该设备的的主设备号

*/

#define led_NAME   "ledName"  //定义设备的名字

/* 打开设备 */

static int led_open(struct inode *inode, struct file *filp)

{

  int ret = 0;

  //printk("led_open!\r\n");

  return ret;

}

/* 向设备写数据,将数据块首地址为buf的数据,长度为cnt个字节,发送给用户 */

//file结构指针变量flip表示要打开的设备文件

//buf表示用户数据块的首地址

//cnt表示用户数据的长度,单位为字节

//loff_t结构指针变量offt表示“相对于文件首地址的偏移”

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)

{

  int ret = 0;

unsigned char databuf[1];

unsigned char ledstat;

ret = copy_from_user(databuf, buf, cnt);

if(ret <0){

printk("kernel write failed!\r\n");

ret = -EFAULT;

}

ledstat = databuf[0];/*获取到应用传递进来的开关灯状态*/

led_switch(ledstat);/*执行开灯或执行关灯*/

  return ret;

}

/* 关闭/释放设备 */

static int led_release(struct inode *inode, struct file *filp)

{

  int ret=0;

printk("led_release!\r\n");

  return ret;

}

/*声明file_operations结构变量MyCharDevice_fops*/

/*它是指向设备的操作函数集合变量*/

const struct file_operations led_fops = {

  .owner = THIS_MODULE,

  .open = led_open,

  .write = led_write,

  .release = led_release,

};

/*驱动模块的入口函数 */

static int  __init led_init(void)

{

  int ret=0;

/* 1、寄存器地址映射 */

  led_ioremap();//寄存器地址映射

  led_Pin_Init();//LED灯引脚初始化

  printk("led_init!\r\n");

  ret = register_chrdev(led_MAJOR, led_NAME, &led_fops);

  //注册字符设备

  //led_MAJOR为主设备号,采用宏led_NAME定义设备名字

  //led_fops是设备的操作函数集合,它是file_operations结构变量

  if (ret < 0)

  {

printk("CharDeviceDriver register failed!!!\r\n");

goto faile_register;

  }

  return 0;

faile_register:

  return -EIO;

}

/*驱动模块的出口函数 */

static void __exit led_exit(void)

{

/* 1、取消寄存器地址映射 */

  led_iounmap();

  printk("led_exit!\r\n");

  unregister_chrdev(led_MAJOR, led_NAME);

  //注销字符设备

  //led_MAJOR为主设备号,采用宏led_NAME定义设备名字

}

module_init(led_init);

//加载“驱动模块”:指定led_init()为驱动入口函数

module_exit(led_exit);

//卸载“驱动模块”:指定led_exit()为驱动出口函数

MODULE_AUTHOR("Zhanggong");//添加作者名字

MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”

MODULE_INFO(intree,"Y");

//去除显示“loading out-of-tree module taints kernel.”

3、新建LED.c

LED.c文件内容如下:

#include "LED.h"

#include <linux/module.h>

/* 映射后的寄存器虚拟地址指针 */

static void __iomem *MPU_AHB4_PERIPH_RCC_PI;

/*RCC_MP_AHB4ENSETR寄存器*/

static void __iomem *GPIOI_MODER_PI; /*GPIOx_MODER寄存器,x=A to K, Z*/

static void __iomem *GPIOI_OTYPER_PI;/*GPIOx_OTYPER,x=A to K,Z*/

static void __iomem *GPIOI_OSPEEDR_PI;/*GPIOx_OSPEEDR,x=A to K, Z*/

static void __iomem *GPIOI_PUPDR_PI; /*GPIOx_PUPDR,x=A to K, Z*/

static void __iomem *GPIOI_BSRR_PI;/*GPIOx_BSRR,x=A to K, Z*/

void led_ioremap(void);

void led_iounmap(void);

void led_Pin_Init(void);

void led_switch(u8 sta);

/* 寄存器地址映射 */

void led_ioremap(void)

{

   MPU_AHB4_PERIPH_RCC_PI = ioremap(RCC_MP_AHB4ENSETR, 4);

GPIOI_MODER_PI = ioremap(GPIOI_MODER, 4);

   GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER, 4);

GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR, 4);

GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR, 4);

GPIOI_BSRR_PI = ioremap(GPIOI_BSRR, 4);

}

/*取消“寄存器地址映射”*/

void led_iounmap(void)

{

  iounmap(MPU_AHB4_PERIPH_RCC_PI);

  iounmap(GPIOI_MODER_PI);

  iounmap(GPIOI_OTYPER_PI);

  iounmap(GPIOI_OSPEEDR_PI);

  iounmap(GPIOI_PUPDR_PI);

  iounmap(GPIOI_BSRR_PI);

}

void led_Pin_Init(void)

{

u32 val = 0;

/* 2、使能RCC时钟 */

val = readl(MPU_AHB4_PERIPH_RCC_PI);/* 读RCC_MP_AHB4ENSETR寄存器 */

val &= ~(0X1 << 8);/* 清除以前的bit8设置 */

val |= (0X1 << 8); /* 设置新的bit8值 */

writel(val, MPU_AHB4_PERIPH_RCC_PI);

/* 将val的值写入RCC_MP_AHB4ENSETR寄存器 */

/* 3、将PI0输出引脚。*/

val = readl(GPIOI_MODER_PI);/*读GPIOI_MODER寄存器*/

val &= ~(0X3 << 0); /* bit0:1清零 */

val |= (0X1 << 0); /* bit0:1设置01,配置为输出模式 */

writel(val, GPIOI_MODER_PI);

/* 将val的值写入GPIOI_MODER寄存器 */

/* 4、设置PI0为推挽模式 */

val = readl(GPIOI_OTYPER_PI);/*读GPIOI_OTYPER寄存器*/

val &= ~(0X1 << 0); /* bit0清零,设置为上拉*/

writel(val, GPIOI_OTYPER_PI);

/* 将val的值写入GPIOI_OTYPER寄存器 */

/* 5、设置PI0为高速 */

val = readl(GPIOI_OSPEEDR_PI);/*读GPIOI_OSPEEDR寄存器*/

val &= ~(0X3 << 0); /* bit0:1 清零 */

val |= (0x3 << 0); /* bit0:1 设置为11,极高速*/

writel(val, GPIOI_OSPEEDR_PI);

/* 将val的值写入GPIOI_OSPEEDR寄存器 */

/* 6、设置PI0为上拉。*/

val = readl(GPIOI_PUPDR_PI);/*读GPIOI_PUPDR寄存器*/

val &= ~(0X3 << 0); /* bit0:1 清零*/

val |= (0x1 << 0); /*bit0:1 设置为01,配置为上拉*/

writel(val,GPIOI_PUPDR_PI);

/* 将val的值写入GPIOI_PUPDR寄存器 */

/* 6、默认打开LED,PI0=0 */

val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/

val &= ~(0X1 << 16); /* bit16 清零*/

val |= (0x1 << 16); /*bit16 设置为1,令PI0输出低电平*/

writel(val, GPIOI_BSRR_PI);

/* 将val的值写入GPIOI_BSRR寄存器 */

/* 6、默认关闭LED,PI0=1 */

val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/

val &= ~(0X1 << 0); /* bit0 清零*/

val |= (0x1 << 0);/*bit0 设置为1,令PI0输出高电平*/

writel(val, GPIOI_BSRR_PI);

/* 将val的值写入GPIOI_BSRR寄存器 */

}

void led_switch(u8 sta)

{

u32 val = 0;

if(sta == LEDON) {

val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/

    val &= ~(0X1 << 16); /* bit16 清零*/

val |= (0x1 << 16); /*bit16 设置为1,令PI0输出低电平*/

writel(val, GPIOI_BSRR_PI);

/* 将val的值写入GPIOI_BSRR寄存器 */

}

else if(sta == LEDOFF) {

val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/

val &= ~(0X1 << 0); /* bit0 清零*/

val |= (0x1 << 0);/*bit0 设置为1,令PI0输出高电平*/

writel(val, GPIOI_BSRR_PI);

/* 将val的值写入GPIOI_BSRR寄存器 */

}

}

4、新建LED.h

LED.h文件如下:

#ifndef __LED_H

#define __LED_H

#include <linux/types.h>

/*

数据类型重命名

使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t

使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t

*/

#define LEDOFF 0 /* 关灯 */

#define LEDON 1 /* 开灯 */

/* 寄存器物理地址 */

#define PERIPH_BASE      (0x40000000)

#define MPU_AHB4_PERIPH_BASE    (PERIPH_BASE + 0x10000000)

#define RCC_BASE         (MPU_AHB4_PERIPH_BASE + 0x0000)

#define RCC_MP_AHB4ENSETR (RCC_BASE + 0XA28)

#define GPIOI_BASE (MPU_AHB4_PERIPH_BASE + 0xA000)

#define GPIOI_MODER       (GPIOI_BASE + 0x0000)

#define GPIOI_OTYPER       (GPIOI_BASE + 0x0004)

#define GPIOI_OSPEEDR       (GPIOI_BASE + 0x0008)

#define GPIOI_PUPDR       (GPIOI_BASE + 0x000C)

#define GPIOI_BSRR       (GPIOI_BASE + 0x0018)

extern void led_ioremap(void);

extern void led_iounmap(void);

extern void led_Pin_Init(void);

extern void led_switch(u8 sta);

#endif

5、新建LED_APP.c

LED_APP.c文件内容如下:

#include "stdio.h"

#include "unistd.h"

#include "sys/types.h"

#include "sys/stat.h"

#include "fcntl.h"

#include "stdlib.h"

#include "string.h"

//APP运行命令:./LED_APP MyLED 1表示打开LED

//APP运行命令:./LED_APP MyLED 0表示关闭LED

#define LEDOFF 0 /* 关灯 */

#define LEDON 1 /* 开灯 */

/*

参数argc: argv[]数组元素个数

参数argv[]:是一个指针数组

返回值: 0 成功;其他 失败

*/

int main(int argc, char *argv[])

{

  int fd, retvalue;

  char *filename;

  unsigned char databuf[1];

  if(argc != 3)

  {

    printf("Error Usage!\r\n");

    return -1;

  }

  //argv[]是指向输入参数“./LED_App” “/dev/MyLED” “1”

  filename = argv[1];

  //argv[1]指向字符串“/dev/MyLED

  fd = open(filename, O_RDWR);

  //如果打开“/dev/LED”文件成功,则fd为“文件描述符”

  //fd=0表示标准输入流; fd=1表示标准输出流;fd=2表示错误输出流;

  if(fd < 0)

  {

    printf("Can't open file %s\r\n", filename);

    return -1;

  }

  databuf[0]= atoi(argv[2]); /* 写入的数据,是数字的,表示打开或关闭 */

  retvalue = write(fd, databuf, 1);

  //将databuf[]中前1个字节发送给用户

  //返回值大于0表示写入的字节数;

  //返回值等于0表示没有写入任何数据;

  //返回值小于0表示写入失败

  if(retvalue < 0)

  {

    printf("write file %s failed!\r\n", filename);

    close(fd);

    //fd表示要关闭的“文件描述符”

    //返回值等于0表示关闭成功

    //返回值小于0表示关闭失败

    return -1;

  }

  /* 关闭设备 */

  retvalue = close(fd);

  //fd表示要关闭的“文件描述符”

  //返回值等于0表示关闭成功

  //返回值小于0表示关闭失败

  if(retvalue < 0)

  {

    printf("Can't close file %s\r\n", filename);

    return -1;

  }

  return 0;

}

6、新建Makefile

Makefile文件如下:

KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31

#使用“:=”将其后面的字符串赋值给KERNELDIR

CURRENT_PATH := $(shell pwd)

#采用“shell pwd”获取当前打开的路径

#使用“$(变量名)”引用“变量的值”

MyAPP := LED_APP

MyOldLED_Module-objs = LEDInterface.o LED.o

obj-m := MyOldLED_Module.o

CC := arm-none-linux-gnueabihf-gcc

drv:

$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

app:

$(CC)  $(MyAPP).c  -o $(MyAPP)

clean:

$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

rm $(MyAPP)

install:

sudo cp *.ko $(MyAPP) /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f

7、添加“c_cpp_properties.json

按下“Ctrl+Shift+P”,打开VSCode控制台,然后输入“C/C++:Edit Configurations(JSON)”,打开以后会自动在“.vscode ”目录下生成一个名为“c_cpp_properties.json” 的文件。

修改c_cpp_properties.json内容如下所示:

{

    "configurations": [

        {

            "name": "Linux",

            "includePath": [

                "${workspaceFolder}/**",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31",  "/home/zgq/linux/Linux_Drivers/MyOldLED", "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include", "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include", "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"
            ],

            "defines": [],

            "compilerPath": "/usr/bin/gcc",

            "cStandard": "gnu11",

            "cppStandard": "gnu++14",

            "intelliSenseMode": "gcc-x64"

        }

    ],

    "version": 4

}

8、编译

输入“make clean回车

输入“make drv回车

输入“make app回车

输入“make install回车

输入“ls /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -l回车”产看是存在“LED_APP和MyOldLED_Module.ko

9、测试

启动开发板,从网络下载程序

输入“root

输入“cd /lib/modules/5.4.31/回车

切换到“/lib/modules/5.4.31/”目录

注意:“lib/modules/5.4.31/在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下,但在开发板中,却是位于根目录中

输入“ls -l”查看“LED_APP和MyOldLED_Module.ko”是否存在

输入“depmod”,驱动在第一次执行时,需要运行“depmod”

输入“modprobe MyOldLED_Module.ko”,加载“MyOldLED_Module.ko”模块

输入“lsmod”查看有哪些驱动在工作

输入“mknod /dev/MyOldLED_Module c 200 0回车

//“mknod”是创建节点命令

//“/dev/MyOldLED_Module”表示节点文件

//“c”表示LED是个字符设备

//“200”表示设备的主设备号

//“0”表示设备的次设备号

输入“ls /dev/MyOldLED_Module  -l回车”,

发现节点文件“/dev/MyOldLED_Module

输入“./LED_APP /dev/MyOldLED_Module 1回车”执行写1开灯

输入“./LED_APP /dev/MyOldLED_Module 0回车”执行写0关灯

操作完成,则执行卸载模块:

输入“rmmod MyOldLED_Module.ko”,卸载“MyOldLED_Module.ko”模块

注意:输入“rmmod MyOldLED_Module”也可以卸载“MyOldLED_Module.ko”模块

输入“lsmod”查看有哪些驱动在工作。

 搞了一天才搞好,网上这方面的资料很少。如果Makeile写错了,就搞不好了。

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

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

相关文章

14、电源管理入门之Watchdog看门狗

目录 1. 软硬件watchdog的区别 2. 软件看门狗 2.1 kernel watchdog 2.1.1 soft lockup 2.1.1 hard lockup 2.2 用户态watchdog 2.2.1 softdog 2.2.1 hardware watchdog 3. 硬件看门狗 3.1 硬件寄存器介绍 3.2 喂狗操作 3.3 watchdog硬件驱动编写 参考: 看门狗,又…

量化人这样用Jupyter(2) - JupySQL, D-tale

当我们使用 Jupyter 时,很显然我们的主要目的是探索数据。这篇文章将介绍如何利用 JupySQL 来进行数据查询–甚至代替你正在使用的 Navicat, dbeaver 或者 pgAdmin。此外,我们还将介绍如何更敏捷地探索数据,相信这些工具,可以帮你省下 90%的 coding 时间。 原文发表在这里…

excel统计分析——正交设计

参考资料&#xff1a;生物统计学 单因素试验通常采用完全随机设计活动随机区组设计&#xff1b;两因素试验通常采用析因设计&#xff1b;多因素试验不考虑因素间的互作时&#xff0c;可以采用拉丁方设计或正交拉丁方设计&#xff1b;需要考虑因素间的互作时&#xff0c;析因设计…

【Docker】掌握 Docker 镜像操作:从基础到进阶

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 结语 我的其他博客 前言 在现代软件开发和部署中&#xff0c;容器化技术已经成为不可或缺的一部分。而 Docker 作为最流行的容器化…

JVM工作原理与实战(四十二):JVM常见面试题目

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、JVM常见面试题目 1.请阐述JVM的概念及其核心功能&#xff0c;并简要介绍其组成部分和常用的实现。 2.请阐述Java字节码文件的组成部分。 3.请描述JVM的运行时数据区及其组成部分…

RedTiger‘s Hackit

本文相关的ctf平台链接&#xff1a;RedTigers Hackit (overthewire.org) level 1 这里有个1可以点&#xff0c;先尝试点一下 因为这题是sql注入&#xff0c;cat应该就是注入点了 先判断注入类型 ?cat1 and 11 ?cat1 and 12 数字型注入 再判断列数 1 order by 4 -- 接下来…

Vue深度教程

一、Vue简介 1.简介 2.快速上手 二、基础 1.创建一个Vue应用 2.模板语法 3.响应式基础 4.计算属性 5.Class与 Style绑定 6.条件渲染 7.列表渲染 8.事件处理 9.表单输入绑定 10.生命周期钩子 11.侦听器 12.模板引用 13.组件基础 三、深入组件 1.组件注册 2.Props 3.组件事件 …

css补充(上)

有关字体 1.所有有关字体的样式都会被继承 div {font-size: 30px;}<span>777</span> <div>123<p>456</p> </div>span中777是默认大小16px div设置了30px p作为div的后代继承了字体样式也是30px 2.字体颜色 div{color: red;border: 1px …

【VTKExamples::PolyData】第四十九期 Silhouette

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享VTK样例Silhouette,并解析接口vtkPolyDataSilhouette,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. Silhouett…

RocketMQ架构详解

文章目录 概述RocketMQ架构Broker 高可用集群刷盘策略 概述 RocketMQ一个纯java、分布式、队列模型的开源消息中间件&#xff0c;前身是MetaQ&#xff0c;是阿里研发的一个队列模型的消息中间件&#xff0c;后开源给apache基金会成为了apache的顶级开源项目&#xff0c;具有高…

QT计算两个日期之间的月份数

数据库中单表数据存储量过大时&#xff0c;会造成数据库的查询统计速度变慢&#xff0c;因此需将单表数据拆分存储到按年月命名的多张数据表中。解决思路是获取单表中的最小时间和最大时间&#xff0c;然后计算两个时间中的月份数量&#xff0c;最后根据开始年月循环算出所有需…

软件测试的基本概念

目录 1.测试需求 2.测试用例的概念 3.bug 4.软件生命周期 4.1需求分析 4.2计划 4.3编码 4.4测试 4.5运行维护 5.测试模型 5.1敏捷开发模型 5.2scrume 5.3测试模型 5.4w模型(双v模型) 6.软件测试的生命周期 7.BUG的描述和定义 8.如何定义bug的级别 9.BUG的…

机器学习——线性回归

机器学习——线性回归 文章目录 机器学习——线性回归一、什么是线性回归二、一元线性回归方程三、损失函数四、代码实现五、运用说明 一、什么是线性回归 线性回归是一种用来建立自变量和因变量之间线性关系的统计分析方法&#xff0c;也是机器学习中最常见、最容易理解的一个…

Java面试篇【RabbitMQ】常见面试题(2024最新)

RabbitMQ 1.为什么使用MQ?优点是什么 因为MQ可以异步处理&#xff0c;提高系统吞吐量。 应用解耦&#xff0c;系统之间可以通过消息通信&#xff0c;不关心其他系统的处理。 流量削峰&#xff0c;可以通过消息队列的长度&#xff0c;控制请求量。可以缓解短时间内的高并发请…

无线电信号及其分类

有线与无线通信 通信&#xff1a;指信息的交流与传递。 有线通信&#xff1a;用导向性传输媒体&#xff0c;即信息流沿着固体媒体传播。 例如电报、座机都是沿导线传送信号。 无线通信&#xff1a;用非导向性传输媒体&#xff0c;即在自由空间中被传播。分为红外线、电磁波等…

【Mysql】InnoDB 中的聚簇索引、二级索引、联合索引

一、聚簇索引 其实之前内容中介绍的 B 树就是聚簇索引。 这种索引不需要我们显示地使用 INDEX 语句去创建&#xff0c;InnoDB 引擎会自动创建。另外&#xff0c;在 InnoDB 引擎中&#xff0c;聚簇索引就是数据的存储方式。 它有 2 个特点&#xff1a; 特点 1 使用记录主键…

如何恢复已删除的华为手机图片?5 种方式分享

不幸的现实是&#xff0c;华为的珍贵时刻有时会因为意外删除、软件故障或其他不可预见的情况而在眨眼之间消失。在这种情况下&#xff0c;寻求恢复已删除的图片成为个人迫切关心的问题。 本文旨在为用户提供如何从华为恢复已删除图片的实用解决方案。我们将探索五种可行的方法…

光线追踪5- Surface normals and multiple objects

首先&#xff0c;让我们获取一个表面法线&#xff0c;以便进行着色。这是一个垂直于交点处表面的向量。在我们的代码中&#xff0c;我们需要做一个重要的设计决定&#xff1a;法线向量是否为任意长度&#xff0c;还是将其归一化为单位长度。 诱人的是&#xff0c;如果不…

视频远程监控平台EasyCVR集成后播放只有一帧画面的原因排查与解决

智慧安防视频监控平台EasyCVR能在复杂的网络环境中&#xff08;专网、局域网、广域网、VPN、公网等&#xff09;将前端海量的设备进行统一集中接入与视频汇聚管理&#xff0c;平台可支持的接入协议包括&#xff1a;国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议…

【STM32下UART协议的一些认识与使用方法】

STM32下UART协议的一些认识与使用方法 串口定义通用串行异步收发器 协议中相关的概念空闲位起始位数据位奇偶校验位三种校验方式BT接收数据的流程 停止位波特率总结 UART的三种工作方式UART控制器发送数据流程接收数据流程 UART初始化UART相关结构体和库函数 串口定义 通用串行…