(5)STM32 USB设备开发-USB键盘
讲解视频:2、USB键盘-下_哔哩哔哩_bilibili
例程:STM32USBdevice: 基于STM32的USB设备例子程序 - Gitee.com
本篇为使用使用STM32模拟USB键盘的例程,没有知识,全是实操,按照步骤就能获得一个STM32的USB键盘。本例子是在野火F103MINI开发板上验证的,如果代码中出现一些外设的配置,可以参考野火F103MINI开发板原理图对照。
设置外部晶振,必须要使用外部晶振,因为USB控制器需要48M的系统时钟,内部晶振无法倍频出48M。
配置外部时钟
配置调试口和系统基准源
开启USB设备
中间件中设备USB设备类型
标蓝色的部分需要根据你之前有没有使用过这两VID和PID,如果使用过最好换一下,避免使用之前的驱动引起一些奇奇怪怪的问题。
我使用了freertos v2
配置外部系统时钟
配置独立C和H文件
下面就讲一下设备描述符,设备描述符就像一个身份证一样,它包含了这个USB设备的全部信息,说明了USB设备的通用信息,包含应用到全部设备和所有设备配置的信息。USB设备只有一个设备描述符。设备描述符是在设备连接时主机读取的第一个描述符。设备描述符所含的信息,被主机用来取得设备的额外内容。设备描述符提供了关于设备、设备的配置以及任何设备所归属的类的信息。
如果你对USB协议足够了解,可以手写,但是还可以使用官方提供了一个专门的工具来生成,下载地址:HID Descriptor Tool | USB-IF
界面如下:
我们通过菜单的 FILE/Open,弹出需要打开的HID报告描述符。
这里我们选择键盘的报告描述符:keybrd.hid
如需要修改某些值,可以双击选中需要修改的行,如我们双击 INPUT(Data,Var,Abs) 81 02,弹出其修改页:
如果需要添加item可以在界面左侧 HID Items 栏中是一系列的 Item,通过双击需要的 Item 添加到右侧 Report Descriptor 中。添加过程中该工具会根据不同的 Item 让你选择或者填入值。
点击 Parser Descriptor 就会显示解析的结果
如果描述符有错,会给予提示,例如把例子中的 END_COLLECTION 去掉,再进行校验就会有如下提示:
修改完成以后,点击“File -> Save As”,保存为.h格式。保存完成后打开,效果如下:
char ReportDescriptor[63] = {0x05, 0x01, // USAGE_PAGE (Generic Desktop)0x09, 0x06, // USAGE (Keyboard)0xa1, 0x01, // COLLECTION (Application)0x05, 0x07, // USAGE_PAGE (Keyboard)0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)0x15, 0x00, // LOGICAL_MINIMUM (0)0x25, 0x01, // LOGICAL_MAXIMUM (1)0x75, 0x01, // REPORT_SIZE (1)0x95, 0x08, // REPORT_COUNT (8)0x81, 0x02, // INPUT (Data,Var,Abs)0x95, 0x01, // REPORT_COUNT (1)0x75, 0x08, // REPORT_SIZE (8)0x81, 0x03, // INPUT (Cnst,Var,Abs)0x95, 0x05, // REPORT_COUNT (5)0x75, 0x01, // REPORT_SIZE (1)0x05, 0x08, // USAGE_PAGE (LEDs)0x19, 0x01, // USAGE_MINIMUM (Num Lock)0x29, 0x05, // USAGE_MAXIMUM (Kana)0x91, 0x02, // OUTPUT (Data,Var,Abs)0x95, 0x01, // REPORT_COUNT (1)0x75, 0x03, // REPORT_SIZE (3)0x91, 0x03, // OUTPUT (Cnst,Var,Abs)0x95, 0x06, // REPORT_COUNT (6)0x75, 0x08, // REPORT_SIZE (8)0x15, 0x00, // LOGICAL_MINIMUM (0)0x25, 0x65, // LOGICAL_MAXIMUM (101)0x05, 0x07, // USAGE_PAGE (Keyboard)0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)0x81, 0x00, // INPUT (Data,Ary,Abs)0xc0 // END_COLLECTION
};
在上面的描述符中可以看到INPUT给电脑的是一个大小为REPORT_SIZE (8)的Ary数组的Data变量,
所以我们需要创建一个8字节的数组。
键盘发送给PC的数据每次8个字节
BYTE1 BYTE2 BYTE3 BYTE4 BYTE5 BYTE6 BYTE7 BYTE8
定义分别是:
BYTE0 --(0 = OFF,1 = ON,CONSTANT为保留位)
|--bit0: Left Control是否按下,按下为1
|--bit1: Left Shift 是否按下,按下为1
|--bit2: Left Alt 是否按下,按下为1
|--bit3: Left GUI 是否按下,按下为1
|--bit4: Right Control是否按下,按下为1
|--bit5: Right Shift 是否按下,按下为1
|--bit6: Right Alt 是否按下,按下为1
|--bit7: Right GUI 是否按下,按下为1
BYTE1 -- 为常量值,保留字节
BYTE2--BYTE7 -- 这六个为普通按键
代码需要修改如下:
在Middlewares\ST\STM32_USB_Device_Library\Class\HID\Src\usbd_hid.c文件中添加
__ALIGN_BEGIN static uint8_t HID_KEYBOARD_ReportDesc[HID_KEYBOARD_REPORT_DESC_SIZE] __ALIGN_END = {0x05, 0x01, // USAGE_PAGE (Generic Desktop)0x09, 0x06, // USAGE (Keyboard)0xa1, 0x01, // COLLECTION (Application)0x05, 0x07, // USAGE_PAGE (Keyboard)0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)0x15, 0x00, // LOGICAL_MINIMUM (0)0x25, 0x01, // LOGICAL_MAXIMUM (1)0x75, 0x01, // REPORT_SIZE (1)0x95, 0x08, // REPORT_COUNT (8)0x81, 0x02, // INPUT (Data,Var,Abs)0x95, 0x01, // REPORT_COUNT (1)0x75, 0x08, // REPORT_SIZE (8)0x81, 0x03, // INPUT (Cnst,Var,Abs)0x95, 0x05, // REPORT_COUNT (5)0x75, 0x01, // REPORT_SIZE (1)0x05, 0x08, // USAGE_PAGE (LEDs)0x19, 0x01, // USAGE_MINIMUM (Num Lock)0x29, 0x05, // USAGE_MAXIMUM (Kana)0x91, 0x02, // OUTPUT (Data,Var,Abs)0x95, 0x01, // REPORT_COUNT (1)0x75, 0x03, // REPORT_SIZE (3)0x91, 0x03, // OUTPUT (Cnst,Var,Abs)0x95, 0x06, // REPORT_COUNT (6)0x75, 0x08, // REPORT_SIZE (8)0x15, 0x00, // LOGICAL_MINIMUM (0)0x25, 0x65, // LOGICAL_MAXIMUM (101)0x05, 0x07, // USAGE_PAGE (Keyboard)0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)0x81, 0x00, // INPUT (Data,Ary,Abs)0xc0 // END_COLLECTION
};
在文件Middlewares\ST\STM32_USB_Device_Library\Class\HID\Inc\usbd_hid.h中添加
#define HID_KEYBOARD_REPORT_DESC_SIZE 63U
将程序中所有HID_MOUSE_ReportDesc替换为HID_KEYBOARD_ReportDesc
将程序中所有HID_MOUSE_REPORT_DESC_SIZE替换为HID_KEYBOARD_REPORT_DESC_SIZE.
配置描述符需要修改,在文件Middlewares\ST\STM32_USB_Device_Library\Class\HID\Src\usbd_hid.c中
修改端点大小,在文件Middlewares\ST\STM32_USB_Device_Library\Class\HID\Inc\usbd_hid.h中,因为我们上边定义的input是8个字节,所以这里改为8
我是在Core\Src\freertos.c文件中添加了按键检测的代码
/* init code for USB_DEVICE */MX_USB_DEVICE_Init();/* USER CODE BEGIN StartDefaultTask */uint8_t keyBoard[8] = {0};uint8_t key1Status = 0;uint8_t key2Status = 0;TickType_t xLastFlashTime = osKernelGetTickCount();/* Infinite loop */for (;;) {// 获取当前时间戳TickType_t xCurrentTime = osKernelGetTickCount();// 检查是否已经过了 1 秒(pdMS_TO_TICKS 函数将毫秒转换为系统时钟节拍)if ((xCurrentTime - xLastFlashTime) >= pdMS_TO_TICKS(1000)) {// 切换 LED1 的状态HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);// HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);// 更新上一次的时间戳xLastFlashTime = xCurrentTime;}if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_SET) {if (key1Status == 0) {keyBoard[2] = 4; // 设置a按键状态USBD_HID_SendReport(&hUsbDeviceFS, keyBoard, 8);key1Status = 1;}} else {if (key1Status == 1) {key1Status = 0;keyBoard[2] = 0; // 清除按键状态USBD_HID_SendReport(&hUsbDeviceFS, keyBoard, 8);}}if (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_SET) {if (key2Status == 0) {keyBoard[2] = 5; // 设置b按键状态USBD_HID_SendReport(&hUsbDeviceFS, keyBoard, 8);key2Status = 1;}} else {if (key2Status == 1) {key2Status = 0;keyBoard[2] = 0; // 清除按键状态USBD_HID_SendReport(&hUsbDeviceFS, keyBoard, 8);}}osDelay(1);}/* USER CODE END StartDefaultTask */
当检测到KEY1按下的时候我们在数组2中写入4,对应下图按键Usage ID“a”字母,当检测到KEY2按下的时候我们在数组2中写入5,对应下图按键Usage ID“b”字母。
这里主要是一个发送的函数需要我们来实现,函数名称为:USBD_HID_SendReport,我们可以跳转到这个函数的定义,函数说明
/*** @brief USBD_HID_SendReport* Send HID Report* @param pdev: device instance* @param buff: pointer to report* @retval status*/
uint8_t USBD_HID_SendReport(USBD_HandleTypeDef *pdev,uint8_t *report,uint16_t len)
第一个参数为USB设备的枚举,第二个设备为要发送的报文信息,第三个为报文的长度,这里我们先定义一个报文的数组,之后在不断发送数据即可。
第一个参数需要从#include "usbd_def.h"头文件中引出
/* USER CODE BEGIN Variables */
extern USBD_HandleTypeDef hUsbDeviceFS;
/* USER CODE END Variables */
引用头文件
/* USER CODE BEGIN Includes */
#include "usbd_def.h"
#include "usbd_hid.h"
/* USER CODE END Includes */
编译下载后能看到键盘设备
按下按键会发现文本中有字符输出。
键盘灯支持
我们在报告描述符中加入了一个output端点信息,就是灯信息。
我们要在代码中接收到灯信息的消息,因为我们是从鼠标工程的基础上进行修改的,而鼠标只有输入,没有输出,所以我们要增加一些输出端点的代码,内容有点多,下面一个个来。
首先我们在USB_DEVICE\Target\usbd_conf.c文件中需要修改PMA的端点映射,让PMA为输出端点分配内存
在USBD_LL_Init函数中,添加修改如下内容
/* USER CODE BEGIN EndPoint_Configuration */HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x20);HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x60);/* USER CODE END EndPoint_Configuration *//* USER CODE BEGIN EndPoint_Configuration_HID */HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x81 , PCD_SNG_BUF, 0xA0);HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x01 , PCD_SNG_BUF, 0xE0);/* USER CODE END EndPoint_Configuration_HID */
这里我们看到一共用到了4个端点,分别是输入端点0X80和0X81,输出端点0X00和0X01,其中0X00端点和0X80端点是供USB使用必须有的,0X81和0X01端点则是MSC设备输入输出端点。
那么一共使用了4个端点,按理来说PMA头部的端点描述大小应该是4X8=32(十六进制的0X20)字节,0X20之后的才是各个端点缓冲区。STM32的PMA一共512字节,也就是缓冲区大小一共可以到0x1FF。
输出端点0缓冲区对应0X20,输入端点的缓冲区是0X60,是因为USB全速设备的最大包是64字节(十进制的0X40),所以这里PMA的划分就是:
头部0X20字节为各个端点的描述
0X20地址开始的64字节为输出端点0的缓冲区
0X60地址开始的64字节为输入端点0的缓冲区
0XA0地址开始的64字节为输入端点1的缓冲区
0XE0地址开始的64字节为输出端点1的缓冲区
因为我们要添加一个输出端点的描述,所以我们还需要为这个输出端点准备一些宏,在Middlewares\ST\STM32_USB_Device_Library\Class\HID\Inc\usbd_hid.h文件中添加如下内容。
#define HID_EPIN_ADDR 0x81U
#define HID_EPOUT_ADDR 0x01U
#define HID_EPIN_SIZE 0x08U
#define HID_EPOUT_SIZE 0x02U
这里对应了在USBD_LL_Init函数中的端点地址0x01,还有端点大小2字节,其实我们报告描述符中只定义了一个字节,但是STM32是两字节对齐的,所以我们这里也就需要定义为2字节
我们还需要在Middlewares\ST\STM32_USB_Device_Library\Class\HID\Src\usbd_hid.c文件中修改配置描述符USBD_HID_CfgFSDesc,USBD_HID_CfgHSDesc,USBD_HID_OtherSpeedCfgDesc,因为我们需要告诉PC我们还有一个端点。
在每个变量的末尾添加如下代码,看注释应该就能知道是什么意思了。
0x07, /* bLength: Endpoint Descriptor size */USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: */HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/0x03, /* bmAttributes: Interrupt endpoint */HID_EPOUT_SIZE, /* wMaxPacketSize: 2 Bytes max */0x00,HID_HS_BINTERVAL, /* bInterval: Polling Interval *//* 41 */
对比如下:
我们会发现添加完成后我们的变量大小改变了,由原来的34个字节增加到了41个字节,所以我们还需要修改变量大小的宏定义。
在Middlewares\ST\STM32_USB_Device_Library\Class\HID\Inc\usbd_hid.h文件中
下面我们开始添加接收函数,让我们能收到灯变化的消息
我们需要在Middlewares\ST\STM32_USB_Device_Library\Class\HID\Src\usbd_hid.c文件中定义USBD_HID_DataOut函数,并且在USBD_HID变量中添加。
在配置描述符中修改端点数量为2,这一步很重要,也是很容易遗漏的一步。
在函数USBD_HID_Init中添加端点初始化代码,并且执行一次USBD_LL_PrepareReceive函数,为接收端点准备接收变量。这里定义了一个uint8_t pbuf[5];全局变量,因为USBD_LL_PrepareReceive函数需要先调用,后从pbuf中拿到结果,拿到后还需要再次调用USBD_LL_PrepareReceive函数,后面会看到。
uint8_t pbuf[5]; // 定义接收数据全局变量
/*** @brief USBD_HID_Init* Initialize the HID interface* @param pdev: device instance* @param cfgidx: Configuration index* @retval status*/
static uint8_t USBD_HID_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{/* Open EP IN */USBD_LL_OpenEP(pdev, HID_EPIN_ADDR, USBD_EP_TYPE_INTR, HID_EPIN_SIZE);pdev->ep_in[HID_EPIN_ADDR & 0xFU].is_used = 1U;/* Open EP OUT */USBD_LL_OpenEP(pdev, HID_EPOUT_ADDR, USBD_EP_TYPE_INTR, HID_EPOUT_SIZE);pdev->ep_out[HID_EPOUT_ADDR & 0xFU].is_used = 1U;pdev->pClassData = USBD_malloc(sizeof(USBD_HID_HandleTypeDef));if (pdev->pClassData == NULL){return USBD_FAIL;}else{USBD_LL_PrepareReceive(pdev, HID_EPOUT_ADDR , pbuf, HID_EPOUT_SIZE);}((USBD_HID_HandleTypeDef *)pdev->pClassData)->state = HID_IDLE;return USBD_OK;
}
在USBD_HID_DeInit函数中添加端点关闭函数。
static uint8_t USBD_HID_DeInit(USBD_HandleTypeDef *pdev,uint8_t cfgidx)
{/* Close HID EPs */USBD_LL_CloseEP(pdev, HID_EPIN_ADDR);pdev->ep_in[HID_EPIN_ADDR & 0xFU].is_used = 0U;USBD_LL_CloseEP(pdev, HID_EPOUT_ADDR);pdev->ep_out[HID_EPOUT_ADDR & 0xFU].is_used = 0U;/* FRee allocated memory */if (pdev->pClassData != NULL){USBD_free(pdev->pClassData);pdev->pClassData = NULL;}return USBD_OK;
}
USBD_HID_DataOut函数实现,这里我做了个LED2的闪烁,并打印接收到的消息,从pbuf中拿到结果,拿到后还需要再次调用USBD_LL_PrepareReceive函数。
static uint8_t USBD_HID_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum)
{HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);printf("data out: %x %x\n", pbuf[0], pbuf[1]);USBD_LL_PrepareReceive(pdev, HID_EPOUT_ADDR , pbuf, HID_EPOUT_SIZE);return USBD_OK;
}
我们街道电脑上后,当按另一个键盘上的能够让灯亮的按键:Caps Lock,Num Lock, Scroll Lock时板子会收到灯消息。
相关文章:

(5)STM32 USB设备开发-USB键盘
讲解视频:2、USB键盘-下_哔哩哔哩_bilibili 例程:STM32USBdevice: 基于STM32的USB设备例子程序 - Gitee.com 本篇为使用使用STM32模拟USB键盘的例程,没有知识,全是实操,按照步骤就能获得一个STM32的USB键盘。本例子是…...

Linux 系统服务开机自启动指导手册
一、引言 在 Linux 系统中,设置服务开机自启动是常见的系统配置任务。本文档详细介绍了多种实现服务开机自启动的方法,包括 systemctl 方式、通用脚本方式、crontab 方案等,并提供了生产环境下的方案建议和开机启动脚本示例。 二、systemct…...

分布式多卡训练(DDP)踩坑
多卡训练最近在跑yolov10版本的RT-DETR,用来进行目标检测。 单卡训练语句(正常运行): python main.py多卡训练语句: 需要通过torch.distributed.launch来启动,一般是单节点,其中CUDA_VISIBLE…...

Codeforces Round 1000 (Div. 2)-C题(树上两个节点不同边数最大值)
https://codeforces.com/contest/2063/problem/C 牢记一棵树上两个节点如果相邻,它们有一条边会重叠,两个节点延伸出去的所有不同边是两个节点入度之和-1而不是入度之和,那么如果这棵树上有三个节点它们的入度都相同,那么优先选择非相邻的两个节点才能使所有不同边的数量最大!!…...

C++17 新特性解析:Lambda 捕获 this
C17 引入了许多改进和新特性,其中之一是对 lambda 表达式的增强。在这篇文章中,我们将深入探讨 lambda 表达式中的一个特别有用的新特性:通过 *this 捕获当前对象的副本。这个特性不仅提高了代码的安全性,还极大地简化了某些场景下…...

Spring Boot 使用 Micrometer 集成 Prometheus 监控 Java 应用性能
在Spring Boot中使用Micrometer集成Prometheus来监控Java应用性能是一种常见的做法。 一、Micrometer简介 Micrometer是一个开源的Java项目,主要用于为JVM应用程序提供监控和度量功能。以下是对Micrometer的详细介绍: 定义与功能 Micrometer是一个针…...

Spring Boot 事件驱动:构建灵活可扩展的应用
在 Spring Boot 应用中,事件发布和监听机制是一种强大的工具,它允许不同的组件之间以松耦合的方式进行通信。这种机制不仅可以提高代码的可维护性和可扩展性,还能帮助我们构建更加灵活、响应式的应用。本文将深入探讨 Spring Boot 的事件发布…...

IM系统设计
读多写少,一般采用写扩散成timeline来做 写扩散模式 利用last message id作为这个作为最后一个消息体 timeline和批量未读和ack 利用ZSET来维护连接的定时心跳,来续约运营商的连接不断开...

华为EC6110T-海思Hi3798MV310_安卓9.0_通刷-强刷固件包
华为EC6110T-海思Hi3798MV310_安卓9.0_通刷-强刷固件包 刷机教程说明: 适用机型:华为EC6110-T、华为EC6110-U、华为EC6110-M 破解总分为两个部分:拆机短接破解(保留IPTV)和OTT卡刷(不保留IPTV)…...

ASP.NET Blazor托管模型有哪些?
今天我们来说说Blazor的三种部署方式,如果大家还不了解Blazor,那么我先简单介绍下Blazor Blazor 是一种 .NET 前端 Web 框架,在单个编程模型中同时支持服务器端呈现和客户端交互性: ● 使用 C# 创建丰富的交互式 UI。 ● 共享使用…...

PyTorch广告点击率预测(CTR)利用深度学习提升广告效果
目录 广告点击率预测问题数据集结构广告点击率预测模型的构建1. 数据集准备2. 构建数据加载器3. 构建深度学习模型4. 训练与评估 总结 广告点击率预测(CTR,Click-Through Rate Prediction)是在线广告领域中的重要任务,它帮助广告平…...

PAT甲级-1017 Queueing at Bank
题目 题目大意 银行有k个窗口,每个窗口只能服务1个人。如果3个窗口已满,就需要等待。给出n个人到达银行的时间和服务时间,要求计算每个人的平均等待时间。如果某个人的到达时间超过17:00:00,则不被服务,等待时间也不计…...

OneData体系架构详解
阿里巴巴的 OneData 体系架构方法论,主要分为三个阶段:业务板块、规范定义 和 模型设计。每个阶段的核心目标是确保数据的高效管理、共享与分析能力。 一. 业务板块(Business Segment) 业务板块是OneData体系架构中的第一步&…...

Gin 框架入门实战系列教程
一,Gin介绍 Gin是一个 Go (Golang) 编写的轻量级 http web 框架,运行速度非常快,如果你是性能和高效的追求者,我们推荐你使用Gin框架。 Gin最擅长的就是Api接口的高并发,如果项目的规模不大,业务相对简单…...

鸿蒙harmony json转对象(2)
在ArkTS(Ark TypeScript)中,接口(interface)是用来定义一个对象的结构,它可以包含属性、方法签名,以及嵌套的类型(包括其他接口或对象类型)。因此,接口里面可…...

M-LAG与E-trunk
M-LAG和E-trunk都是用来实现跨设备链路聚合,解决单点故障的,其大部分特性相同,工作模式M-LAG更胜一筹,支持双活,而且其原理感觉像是vrrpmstp的升级版,是往增加网络可靠性去发展的;而E-trunk是基于LACP扩展实现…...

【面试常见问题】
如何自我介绍 自我介绍是面试关键部分,是面试官了解求职者的首要途径,清晰自信的介绍能提升面试官印象,对求职成功至关重要。 糟糕的自我介绍示例 求职者朱晓明虽表明自己善于交际、积极,23 年毕业且从事 java 开发,…...

Spring Boot Starter介绍
前言 大概10来年以前,当时springboot刚刚出现并没有流行,当时的Java开发者们开发Web应用主要是使用spring整合springmvc或者struts、iBatis、hibernate等开发框架来进行开发。项目里一般有许多xml文件配置,其中配置了很多项目中需要用到的Be…...

vue和reacts数据响应式的差异
Vue 的数据响应式: 原理: Vue 使用 Object.defineProperty 或 Proxy(在 Vue 3 中)来实现数据的响应式。当创建 Vue 实例时,会对 data 对象中的属性进行遍历,将其转换为响应式属性。对于 Object.definePro…...

OpenEuler学习笔记(九):安装 OpenEuler后配置和优化
安装OpenEuler后,可以从系统基础设置、网络配置、性能优化等方面进行配置和优化,以下是具体内容: 系统基础设置 更新系统:以root用户登录系统后,在终端中执行sudo yum update命令,对系统进行更新…...

npm命令与yarn命令的区别
npm与Yarn的区别详解 在软件开发中,npm和Yarn都是流行的包管理工具,它们各自拥有独特的特性和优势。以下是它们的主要区别: 1. 安装速度 npm:安装速度相对较慢,尤其是在依赖项较多的情况下。Yarn:采用并…...

python如何导出数据到excel文件
python导出数据到excel文件的方法: 1、调用Workbook()对象中的add_sheet()方法 wb xlwt.Workbook() ws wb.add_sheet(A Test Sheet) 2、通过add_sheet()方法中的write()函数将数据写入到excel中,然后使用save()函数保存excel文件 ws.write(0, 0, 1234…...

MYSQL学习笔记(五):单行函数(字符串、数学、日期时间、条件判断、信息、加密、进制转换函数)讲解
前言: 学习和使用数据库可以说是程序员必须具备能力,这里将更新关于MYSQL的使用讲解,大概应该会更新30篇,涵盖入门、进阶、高级(一些原理分析);这一篇是讲解单行函数,当然mysql函数很多哈,只有多用才能记得…...

Grafana系列之Dashboard:新增仪表板、新增变量、过滤变量、变量查询、导入仪表板、变量联动、Grafana Alert
概述 关于Prometheus和Grafana的安装,略过。 写在前面 Dashboard:仪表板,可包含多个PanelPanel:面板,Dashboard中的组件 如有写得不对的地方,烦请指出。 新增仪表板 点击右上角的 选择New dashboard…...

(java版本)基于Misty1算法的加密软件的实现-毕业设计
一、基于Misty1算法的加密软件(Java)的实现 随着计算机网络及通信技术的飞速发展,信息安全成了信息社会急需解决的最重要的问题之一,密码技术是保证信息安全的核心技术。本文用JAVA语言开发了一个基于Misty1算法的加密软件&#x…...

Spring注解篇:@RestController详解
全文目录: 开篇语前言摘要概述源码解析使用案例分享代码分析使用场景优缺点分析测试用例 应用场景案例优缺点分析核心类方法介绍测试用例测试用例分析使用场景优缺点分析测试用例 小结总结文末 开篇语 哈喽,各位小伙伴们,你们好呀,…...

C++:将字符数组rkpryyrag,每个字母转换为其前面第13个字母后输出,如果超过a则从z再继续接着数。例如:b前面第1个字母是a。a前面第3个字母是x。
代码如下: #include <iostream> #include <string> using namespace std;int main(){string str "rkpryyrag";for (int i 0; i < str.length(); i){if (str[i] > a && str[i] < z){if (str[i] - a < 13){cout <<…...

《探秘鸿蒙Next:人工智能助力元宇宙高效渲染新征程》
在元宇宙的宏大愿景中,高效的渲染技术是构建沉浸式虚拟世界的关键。鸿蒙Next凭借与人工智能的深度融合,为元宇宙的渲染带来了全新的解决方案和无限可能。 智能场景分析与优化 人工智能能够对元宇宙场景进行智能分析。鸿蒙Next可以利用AI技术对场景中的…...

微前端qiankun的部署
微前端qiankun的部署 本地开发主应用配置启动端口子应用配置启动端口测试环境部署:场景 1:主应用和微应用部署到同一个服务器(同一个 IP 和端口)微应用都放在在一个特殊名称(不会和微应用重名)的文件夹下主应用配置子应用配置配置nginx本地开发 主应用配置启动端口 打开…...

HTML表格-掌握表格标签与属性
HTML表格是网页设计中用于展示数据的强大工具,它通过一系列标签和属性来控制表格的布局和样式。 一、HTML表格的基本结构 HTML表格由<table>标签定义,内部包含多个行(<tr>)、单元格(<td>或<th&…...