当前位置: 首页 > news >正文

树莓派3B+驱动开发(2)- LED驱动(传统模式)

github主页:https://github.com/snqx-lqh
本项目github地址:https://github.com/snqx-lqh/RaspberryPiDriver
本项目硬件地址:https://oshwhub.com/from_zero/shu-mei-pai-kuo-zhan-ban
欢迎交流

笔记说明

如我在驱动开发总览中说的那样,一般的驱动开发模式就是有3种。

传统设备驱动模式:将所有的资源分配放进一个文件中。

PlatformDevice/PlatformDriver模式:资源放进PlatformDevice,实现驱动放进PlatformDriver文件。

设备树/PlatformDriver模式:资源放进设备树,实现驱动放进PlatformDriver文件。设备树一种是提前编译内核的时候就编译好,还有一种方式是添加设备树插件。

这一节主要说明传统的模式,就是将资源全部放进一个文件中处理。

主要参考的文章:

正点原子《I.MX6U 嵌入式 Linux 驱动开发指南 V1.81》

韦东山《01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板》

本节源码路径02_Firmware/02_led_drv_cdev

开发流程

1、初始化一个设备操作函数结构体 struct file_operations,并注册内部函数。该结构体就是这个模块文件读写的相关操作

2、编写入口函数,在这个里面注册设备,并将前面定义的file_operations结构体传入注册函数。自动分配节点需要创建类后创建设备节点。

3、编写出口函数,将设备注销。删除类和设备节点。

4、在入口函数完善IO引脚初始化,出口函数完善引脚相关处理。

5、在设备操作函数中完善各个操作函数的实现逻辑。

需要的头文件

先说明这部分代码需要包含的头文件。

#include <linux/init.h>     //用于定义模块初始化和清理相关的宏以及函数原型等内容
#include <linux/module.h>   //包含了众多用于构建内核模块的基本定义、宏和函数原型
#include <asm/io.h>         //用于处理输入 / 输出操作,尤其是和硬件底层的内存映射 I/O 相关的操作
#include <linux/string.h>   //提供了一系列字符串处理相关的函数
#include <linux/fs.h>       //定义了文件系统操作的各种结构体、函数原型等内容
#include <linux/uaccess.h>  //用于处理用户空间(User Space)和内核空间(Kernel Space)之间的数据拷贝操作
#include <linux/cdev.h>     //定义了字符设备相关的结构体

设备操作函数结构体

我们需要定义一个struct file_operations结构体,以供我们后面操作这个模块,并且需要注册这个结构体中的操作函数。

// 通过文件读取,得到当前LED的状态
ssize_t led_drv_read(struct file* filp, char __user* buf, size_t len, loff_t* off)
{return  0;
}// 通过向文件写入LED状态,控制LED灯
ssize_t led_drv_write(struct file* filp, const char __user* buf, size_t len, loff_t* off)
{return 0;
}const struct file_operations led_fops = {.owner   = THIS_MODULE,.read    =  led_drv_read,.write   =  led_drv_write,
};

编写入口函数

入口函数就是我们加载这个模块的时候会调用的函数,这里我们将注册一个字符型设备,并且动态的为其分配设备号,其实还有静态方法,指定设备号分配,可以看正点原子教程,这里不做说明,因为感觉没有动态好用。

static dev_t  led_dev_num = 0;          // 设备编号
static struct cdev   led_cdev;          // 字符设备结构体
static struct class  *class  = NULL;    //类
static struct device *device = NULL;    //设备static int __init led_drv_cdev_init(void)
{// 将该模块注册为一个字符设备,并动态分配设备号if (alloc_chrdev_region(&led_dev_num, 0, 1, "led_drv")) {printk(KERN_ERR"failed to register kernel module!\n");return -1;}printk(KERN_INFO"led_drv device major & minor is [%d:%d]\n", MAJOR(led_dev_num), MINOR(led_dev_num));//初始化并添加一个cdevcdev_init(&led_cdev, &led_fops);cdev_add(&led_cdev, led_dev_num, 1);//创建类class = class_create("led_drv");if (IS_ERR(class)) {return PTR_ERR(class);}//创建设备device = device_create(class, NULL, led_dev_num, NULL, "led_drv");if (IS_ERR(device)) {return PTR_ERR(device);}return 0;
}module_init(led_drv_cdev_init);

为什么要使用创建类和设备,因为不使用的话就需要自己加载模块后创建,使用以下指令创建设备节点文件:

mknod /dev/led_drv c 200 0

“c”表示这是个字符设备,“200”是设备的主设备号,“0”是设备的次设备号。主设备号不一定是200,我是动态生成的,需要在内核加载信息中去查看,因为我在代码中动态申请设备号后写了个打印,使用如下指令:

demsg | tail

但是这样没有创建类和设备方便,所以就使用创建类和设备了。

编写出口函数

出口函数就是,我们在注销设备的时候会执行的函数,一般我们需要注销我们前面定义的字符设备。

static void __exit led_drv_cdev_exit(void)
{// 释放字符设备cdev_del(&led_cdev);unregister_chrdev_region(led_dev_num, 1);//删除类和设备device_destroy(class, led_dev_num);class_destroy(class);
}module_exit(led_drv_cdev_exit);

出口&入口函数完善引脚初始化

我们把框架搭好后,就可以开始初始化引脚相关的寄存器了,其实操作寄存器的方式和单片机感觉很像,但是内核不能直接操作寄存器物理地址,我们需要先把寄存器相关地址,映射到虚拟地址,然后进行操作。

首先就是虚拟地址的映射,具体操作相关如下,映射后我们才能使用相关的寄存器。

// 寄存器地址
#define BCM2837_GPIO_FSEL0_BASE     0x3F200000   // GPIO功能选择寄存器0  
#define BCM2837_GPIO_FSEL1_BASE     0x3F200004   // GPIO功能选择寄存器1  
#define BCM2837_GPIO_FSEL2_BASE     0x3F200008   // GPIO功能选择寄存器2
#define BCM2837_GPIO_SET0_BASE      0x3F20001C   // GPIO置位寄存器0      
#define BCM2837_GPIO_CLR0_BASE      0x3F200028   // GPIO清零寄存器0      
#define BCM2837_GPIO_LEV0_BASE      0x3F200034   // GPIO清零寄存器0  // 寄存器对应的虚拟ioremap后的地址,会在后面初始化
static void __iomem *BCM2837_GPIO_FSEL0 = NULL;
static void __iomem *BCM2837_GPIO_FSEL1 = NULL;
static void __iomem *BCM2837_GPIO_FSEL2 = NULL;
static void __iomem *BCM2837_GPIO_SET0  = NULL;
static void __iomem *BCM2837_GPIO_CLR0  = NULL;
static void __iomem *BCM2837_GPIO_LEV0  = NULL;static int __init led_drv_cdev_init(void)
{// 将树莓派引脚控制相关的寄存器进行虚拟地址映射,使得可以控制对应寄存器BCM2837_GPIO_FSEL0 = ioremap(BCM2837_GPIO_FSEL0_BASE, 0x04);BCM2837_GPIO_FSEL1 = ioremap(BCM2837_GPIO_FSEL1_BASE, 0x04);BCM2837_GPIO_FSEL2 = ioremap(BCM2837_GPIO_FSEL2_BASE, 0x04);BCM2837_GPIO_SET0  = ioremap(BCM2837_GPIO_SET0_BASE , 0x04);BCM2837_GPIO_CLR0  = ioremap(BCM2837_GPIO_CLR0_BASE , 0x04);BCM2837_GPIO_LEV0  = ioremap(BCM2837_GPIO_LEV0_BASE , 0x04);// 注册字符设备、创建类和设备// ************** //return 0;
}

我们可以使用映射后的寄存器写一些对寄存器的读写操作来控制引脚电平的读写。

// 想要控制的LED灯,BCM引脚定义,这是设计的扩展板上的定义
#define LED2 17
#define LED3 27
#define LED4 22
#define LED5 23#define LED_OUTPUT 1
#define LED_INPUT  0/*** @brief 设置对应引脚的高低电平* @param pin    需要设置的引脚* @param level  1是高电平 0是低电平*/
void gpio_set_level(int pin, int level)
{// 通过想要的电平判断现在想要控制的寄存器void* reg = (level ? BCM2837_GPIO_SET0 : BCM2837_GPIO_CLR0);iowrite32(1 << pin, reg);
}/*** @brief 获得引脚的电平* @param pin 需要获得的引脚* @return 1是高电平 0是低电平*/
static int gpio_get_level(int pin)
{int ret = 0;int pin_level = 0;// 读取引脚电平寄存器pin_level = ioread32( BCM2837_GPIO_LEV0 );// 将想要读取的引脚的状态,提取出来ret = (1 << pin );ret = ret & pin_level;if(ret){return 1;}else{return 0;}
}/*** @brief 设置GPIO的寄存器,配置GPIO是输入还是输出* @param pin   需要设置的引脚* @param mode  1是输出模式 0是输入模式*/
static void gpio_set_mode(int pin, int mode)
{void *reg = NULL;int   val = 0;int   pin_ctl = 0;// 通过pin号来确定要控制的寄存器if(pin < 10){reg = BCM2837_GPIO_FSEL0;}else if(pin < 20){reg = BCM2837_GPIO_FSEL1;}else if(pin < 30){reg = BCM2837_GPIO_FSEL2;}// 比如我要控制11号脚,就是要控制BCM2837_GPIO_FSEL1_BASE的1号位置的3个位。pin_ctl = pin % 10;// 将对应的gpio的功能选择位全部写0val =  ~(7   << (pin_ctl * 3));val &= ioread32(reg);// 控制设置对应的gpio的功能选择位写 000 还是 001val |= (mode << (pin_ctl * 3));iowrite32(val, reg);
}

然后在初始化的时候我们就可以初始化我们的引脚

static int __init led_drv_cdev_init(void)
{// 寄存器进行虚拟地址映射// ************** //// 控制引脚输入输出状态gpio_set_mode(LED2, LED_OUTPUT);gpio_set_mode(LED3, LED_OUTPUT);gpio_set_mode(LED4, LED_OUTPUT);gpio_set_mode(LED5, LED_OUTPUT);// 设置引脚电平gpio_set_level(LED2, 0);gpio_set_level(LED3, 0);gpio_set_level(LED4, 0);gpio_set_level(LED5, 0);// 注册字符设备、创建类和设备// ************** //return 0;
}

在出口函数完善相关引脚处理,主要是释放内存映射。

static void __exit led_drv_cdev_exit(void)
{// 设置电平为高gpio_set_level(LED2, 1);gpio_set_level(LED3, 1);gpio_set_level(LED4, 1);gpio_set_level(LED5, 1);// 取消gpio物理内存映射iounmap(BCM2837_GPIO_FSEL0);iounmap(BCM2837_GPIO_FSEL1);iounmap(BCM2837_GPIO_FSEL2);iounmap(BCM2837_GPIO_SET0);iounmap(BCM2837_GPIO_CLR0);iounmap(BCM2837_GPIO_LEV0);// 释放字符设备、删除类和设备// ************** //
}

设备操作函数完善逻辑

我们之前不是写了write和read的操作函数吗,现在就需要完善这些操作函数,使得用户文件在调用这个文件的时候可以操作gpio进行控制。

先解释读函数,这里比较重要的操作就是copy_to_user,他会把第二个参数里面的值复制到第一个参数中去,这里的buf就是我们用户层想读的值,len是用户写的长度。

#define MIN(a, b) (a < b ? a : b)// 通过文件读取,得到当前LED的状态
ssize_t led_drv_read(struct file* filp, char __user* buf, size_t len, loff_t* off)
{int ret = 0;int char_len = 0;char led_state[4];printk("%s %s line %d\r\n", __FILE__, __FUNCTION__, __LINE__);led_state[0] = gpio_get_level(LED2);led_state[1] = gpio_get_level(LED3);led_state[2] = gpio_get_level(LED4);led_state[3] = gpio_get_level(LED5);char_len = sizeof(led_state);int real_len = MIN(len,char_len);ret = copy_to_user(buf, led_state, real_len);return ret < 0 ? ret : real_len;
}

然后是写函数,其实就和读差不多了,主要是copy_from_user,能够把用户传进的值进行复制处理。

// 通过向文件写入LED状态,控制LED灯
ssize_t led_drv_write(struct file* filp, const char __user* buf, size_t len, loff_t* off)
{int ret = 0;char led_state[4];int char_len = 0;printk("%s %s line %d\r\n", __FILE__, __FUNCTION__, __LINE__);char_len = sizeof(led_state);int real_len = MIN(len,char_len);ret = copy_from_user(led_state, buf, real_len);switch (led_state[0]){case 0:gpio_set_level(LED2, led_state[1]);break;  case 1:gpio_set_level(LED3, led_state[1]);break;case 2:gpio_set_level(LED4, led_state[1]);break;case 3:gpio_set_level(LED5, led_state[1]);break;default:break;}return 0;
}

到这里,这个驱动文件就编写完成了,后面就是编译加载了。

编写应用层函数

驱动编写完成后,我们需要有一个应用层函数来调用这个驱动,写出以下示例。主要功能就是打开驱动,写和读驱动的内容。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>/** sudo ./led_drv_cdev_app -w 0 0* sudo ./led_drv_cdev_app -r*/int main(int argc, char **argv)
{int fd;char writeBuff[3];char readBuff[5];int len;if (argc < 2){printf("Usage: %s -w <string>\n", argv[0]);printf(" %s -r\n", argv[0]);return -1;}fd = open("/dev/led_drv", O_RDWR);if (fd == -1){printf("can not open file /dev/led_drv\n");return -1;}if ((0 == strcmp(argv[1], "-w")) && (argc == 4)){writeBuff[0] = (char)atoi(argv[2]);writeBuff[1] = (char)atoi(argv[3]);writeBuff[2] = '\0';printf("write : %d, %d\n", writeBuff[0], writeBuff[1]);write(fd, writeBuff, 3);}else if((0 == strcmp(argv[1], "-r")) && (argc == 2)){len = read(fd, readBuff, 5);readBuff[4] = '\0';printf("pin read : 0x%x, 0x%x, 0x%x, 0x%x\n", readBuff[0], readBuff[1], readBuff[2], readBuff[3]);}else{printf("APP Failed\n");}close(fd);return 0;
}

编译加载

makefile主要参考正点原子和韦东山的写法。这个Makefile会把驱动模块和应用程序一起编译。

# 模块驱动,必须以obj-m=xxx形式编写
obj-m = led_drv_cdev.oKDIR = /home/linux-rpi-6.6.y/
CROSS = ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
CROSS_COMPILE = arm-linux-gnueabihf-all:$(MAKE) -C $(KDIR) M=$(PWD) $(CROSS)  modules$(CROSS_COMPILE)gcc -o led_drv_cdev_app led_drv_cdev_app.c.PHONY: clean
clean:$(MAKE) -C $(KDIR) M=`pwd` $(CROSS) clean

makefile编译完成后,直接make就行

make

将make生成的文件,如我上文写了脚本,通过scp传到树莓派上。具体的内容参数,自己修改。

#!/bin/shsendfile="led_drv_cdev.ko led_drv_cdev_app"
pi_user=pi
pi_ip=192.168.2.149
pi_dir=/home/pi/RpiDriver/02_led_drv_cdevscp ${sendfile} ${pi_user}@${pi_ip}:${pi_dir}

在树莓派上,我们需要加载并使用这个模块。

sudo insmod led_drv_cdev.kosudo ./led_drv_cdev_app -w 0 1
sudo ./led_drv_cdev_app -rsudo rmmod led_drv_cdev

可以使用dmesg查看运行过程中的变化

dmesg | tail

相关文章:

树莓派3B+驱动开发(2)- LED驱动(传统模式)

github主页&#xff1a;https://github.com/snqx-lqh 本项目github地址&#xff1a;https://github.com/snqx-lqh/RaspberryPiDriver 本项目硬件地址&#xff1a;https://oshwhub.com/from_zero/shu-mei-pai-kuo-zhan-ban 欢迎交流 笔记说明 如我在驱动开发总览中说的那样&…...

超详细搭建PhpStorm+PhpStudy开发环境

刚开始接触PHP开发&#xff0c;搭建开发环境是第一步&#xff0c;网上下载PhpStorm和PhpStudy软件&#xff0c;怎样安装和激活就不详细说了&#xff0c;我们重点来看一看怎样搭配这两个开发环境。 前提&#xff1a;现在假设你已经安装完PhpStorm和PhpStudy软件。 我的PhpStor…...

分析比对vuex和store模式

在 Vue 中&#xff0c;Vuex 和 store 模式 是两个不同的概念&#xff0c;它们紧密相关&#xff0c;主要用于管理应用的状态。下面我会详细介绍这两个概念&#xff0c;并通过例子帮助你更好地理解。 1. Vuex 是什么&#xff1f; Vuex 是 Vue.js 的一个状态管理库&#xff0c;用…...

C# 网络编程--基础核心内容

在现今软件开发中&#xff0c;网络编程是非常重要的一部分&#xff0c;本文简要介绍下网络编程的概念和实践。 C#网络编程的主要内容包括以下几个方面‌&#xff1a; : 上图引用大佬的图&#xff0c;大家也关注一下&#xff0c;有技术有品质&#xff0c;有国有家&#xff0c;情…...

【C++游戏程序】easyX图形库还原游戏《贪吃蛇大作战》(三)

承接上一篇文章&#xff1a;【C游戏程序】easyX图形库还原游戏《贪吃蛇大作战》&#xff08;二&#xff09;&#xff0c;我们这次来补充一些游戏细节&#xff0c;以及增加吃食物加长角色长度等设定玩法&#xff0c;也是本游戏的最后一篇文章。 一.玩家边界检测 首先是用来检测…...

uni-app H5端使用注意事项 【跨端开发系列】

&#x1f517; uniapp 跨端开发系列文章&#xff1a;&#x1f380;&#x1f380;&#x1f380; uni-app 组成和跨端原理 【跨端开发系列】 uni-app 各端差异注意事项 【跨端开发系列】uni-app 离线本地存储方案 【跨端开发系列】uni-app UI库、框架、组件选型指南 【跨端开…...

SpringBoot中的@Configuration注解

在Spring Boot中&#xff0c;Configuration注解扮演着非常重要的角色&#xff0c;它是Spring框架中用于定义配置类的一个核心注解。以下是Configuration注解的主要作用&#xff1a; 定义配置类&#xff1a; 使用Configuration注解的类表示这是一个配置类&#xff0c;Spring容器…...

十二、路由、生命周期函数

router路由 页面路由指的是在应用程序中实现不同页面之间的跳转,以及数据传递。通过 Router 模块就可以实现这个功能 2.1创建页面 之前是创建的文件,使用路由的时候需要创建页面,步骤略有不同 方法 1:直接右键新建Page(常用)方法 2:单独添加页面并配置2.1.1直接右键新建…...

【蓝桥杯每日一题】X 进制减法

X 进制减法 2024-12-6 蓝桥杯每日一题 X 进制减法 贪心 进制转换 题目大意 进制规定了数字在数位上逢几进一。 XX 进制是一种很神奇的进制, 因为其每一数位的进制并不固定&#xff01;例如说某 种 XX 进制数, 最低数位为二进制, 第二数位为十进制, 第三数位为八进制, 则 XX 进制…...

《蓝桥杯比赛规划》

大家好啊&#xff01;我是NiJiMingCheng 我的博客&#xff1a;NiJiMingCheng 这节课我们来分享蓝桥杯比赛规划&#xff0c;好的规划会给我们的学习带来良好的收益&#xff0c;废话少说接下来就让我们进入学习规划吧&#xff0c;加油哦&#xff01;&#xff01;&#xff01; 一、…...

C++算法练习day70——53.最大子序和

题目来源&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目思路分析 题目&#xff1a;寻找最大子数组和&#xff08;也称为最大子序和&#xff09;。 给定一个整数数组 nums&#xff0c;找到一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#x…...

import是如何“占领满屏“

import是如何“占领满屏“的&#xff1f; 《拒绝使用模块重导&#xff08;Re-export&#xff09;》 模块重导是一种通用的技术。在腾讯、字节、阿里等各大厂的组件库中都有大量使用。 如&#xff1a;字节的arco-design组件库中的组件&#xff1a;github.com/arco-design… …...

ceph /etc/ceph-csi-config/config.json: no such file or directory

环境 rook-ceph 部署的 ceph。 问题 kubectl describe pod dragonfly-redis-master-0Warning FailedMount 7m59s (x20 over 46m) kubelet MountVolume.MountDevice failed for volume "pvc-c63e159a-c940-4001-bf0d-e6141634cc55" : rpc error: cod…...

C语言——验证“哥德巴赫猜想”

问题描述&#xff1a; 验证"哥德巴赫猜想" 任何一个大于2的偶数都可以表示为两个质数之和。例如&#xff0c;4可以表示为22&#xff0c;6可以表示为33&#xff0c;8可以表示为35等 //验证"哥德巴赫猜想" //任何一个大于2的偶数都可以表示为两个质数之和…...

Flourish笔记:柱状图(Column chart (grouped))

文章目录 样式设定Chart Type&#xff1a;图表类型Controls & Filters&#xff1a;展示方式Colors&#xff1a;颜色bars&#xff1a;柱子的调整labels&#xff1a;柱子数字标注X axis&#xff1a;横坐标标签Y axis&#xff1a;纵坐标标签Plot BackgroundNumber FormatingLe…...

深度学习案例:DenseNet + SE-Net

本文为为&#x1f517;365天深度学习训练营内部文章 原作者&#xff1a;K同学啊 一 回顾DenseNet算法 DenseNet&#xff08;Densely Connected Convolutional Networks&#xff09;是一种深度卷积神经网络架构&#xff0c;提出的核心思想是通过在每一层与前面所有层进行直接连接…...

excel文件合并,每个excel名称插入excel列

import pandas as pd import os # 设置文件夹路径 folder_path rC:\test # 替换为您的下载文件夹路径 output_file os.path.join(folder_path, BOM材料.xlsx) # 创建一个空的 DataFrame 用于存储合并的数据 combined_data pd.DataFrame() # 遍历文件夹中的所有文件 for …...

Linux 如何设置特殊权限?

简介 通过使用 setuid、setgid 、sticky&#xff0c;它们是 Linux 中的特殊权限&#xff0c;可以对文件和目录的访问和执行方式提供额外的控制。 命令八进制数字功能setuid4当执行文件时&#xff0c;它以文件所有者的权限运行&#xff0c;而不是执行它的用户的权限运行。setg…...

零基础如何使用ChatGPT快速学习Python

引言 AI编程时代来临&#xff0c;没有编程基础可以快速上车享受时代的红利吗&#xff1f;答案是肯定的。本文旨在介绍零基础如何利用ChatGPT快速学习Python编程语言&#xff0c;开启AI编程之路。解决的问题包括&#xff1a;传统学习方式效率低、缺乏互动性以及学习资源质量参差…...

【开源】一款基于SpringBoot 的全开源充电桩平台

一、下载项目文件 下载源码项目文件口令&#xff1a;动作璆璜量子屏多好/~d1b8356ox2~:/复制口令后&#xff0c;进入夸克网盘app即可保存&#xff08;如果复制到夸克app没有跳转资源&#xff0c;可以复制粘贴口令到夸克app的搜索框也可以打开&#xff08;不用点搜索按钮&#…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

SpringCloudGateway 自定义局部过滤器

场景&#xff1a; 将所有请求转化为同一路径请求&#xff08;方便穿网配置&#xff09;在请求头内标识原来路径&#xff0c;然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...