(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时板子会收到灯消息。

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

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

相关文章

[SUCTF 2018]MultiSQL1

进去题目页面如下 发现可能注入点只有登录和注册,那么我们先注册一个用户,发现跳转到了/user/user.php, 查看用户信息,发现有传参/user/user.php?id1 用?id1 and 11,和?id1 and 12,判断为数字型注入 原本以为是简单的数字型注入,看到大…

阿里云-银行核心系统转型之业务建模与技术建模

业务领域建模包括业务建模和技术建模,整体建模流程图如下: 业务建模包括业务流程建模和业务对象建模 业务流程建模:通过对业务流程现状分析,结合目标核心系统建设能力要求,参考行业建 模成果,形成结构化的…

接口(完)

大家好,今天我们着重来总结一下接口的知识,并且将接口和抽象类的区别罗列一下,帮助我们更好的认识抽象类和接口。 2.7 抽象类和接口的区别. 抽类和接口都是Java中多态的常见使用方式,都需要重点掌握,同时又要认清两者的区别(重要!!&#xf…

windows11关闭系统更新详细操作步骤

文章目录 1.打开注册表2.修改注册表内容2.1 新建文件2.2 修改值 3.修改设置 1.打开注册表 winR输入regedit(如下图所示) 2.修改注册表内容 进HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 2.1 新建文件 右侧界面右键即可 2.2 修改值 重命名为如下…

改进候鸟优化算法之二:基于混沌映射的候鸟优化算法(MBO-CM)

基于混沌映射的候鸟优化算法(Migrating Birds Optimization based on Chaotic Mapping,MBO-CM)是一种结合了混沌映射与候鸟优化算法(Migrating Birds Optimization,MBO)的优化方法。 一、候鸟优化算法(MBO)简介 候鸟优化算法是一种自然启发的元启发式算法,由Duman等人…

Linux学习笔记——网络管理命令

一、网络基础知识 TCP/IP四层模型 以太网地址(MAC地址): 段16进制数据 IP地址: 子网掩码: 二、接口管命令 ip命令:字符终端,立即生效,重启配置会丢失 nmcli命令:字符…

力扣hot100-->滑动窗口、贪心

你好呀,欢迎来到 Dong雨 的技术小栈 🌱 在这里,我们一同探索代码的奥秘,感受技术的魅力 ✨。 👉 我的小世界:Dong雨 📌 分享我的学习旅程 🛠️ 提供贴心的实用工具 💡 记…

Linux_线程控制

线程控制的相关接口 进程创建相关 之前我们已经认识到了pthread_create函数用来创建线程&#xff0c;这里不再赘述。 pthread_self函数 void* routine(void* args) {std::cout << "我是新线程..." << pthread_self() << std::endl;return null…

[java] 面向对象进阶篇1--黑马程序员

目录 static 静态变量及其访问 实例变量及其访问 静态方法及其访问 实例方法及其访问 总结 继承 作用 定义格式 示例 总结 子类不能继承的内容 继承后的特点 成员变量 成员变量不重名 成员变量重名 super访问父类成员变量 成员方法 成员方法不重名 成员方法…

TCP 三次握手四次挥手

目录 TCP 三次握手 1. SYN (Synchronize&#xff1a;同步) 2. SYN-ACK (Synchronize Acknowledge&#xff1a;同步确认) 3. ACK (Acknowledge&#xff1a;确认) 为什么是三次而不是两次或四次&#xff1f; 三次握手的作用 TCP 四次挥手 第一次挥手&#xff1a;客户端发送 FIN …

Vue2下篇

插槽&#xff1a; 基本插槽&#xff1a; 普通插槽&#xff1a;父组件向子组件传递静态内容。基本插槽只能有一个slot标签&#xff0c;因为这个是默认的位置&#xff0c;所以只能有一个 <!-- ParentComponent.vue --> <template> <ChildComponent> <p>…

第38周:猫狗识别 (Tensorflow实战第八周)

目录 前言 一、前期工作 1.1 设置GPU 1.2 导入数据 输出 二、数据预处理 2.1 加载数据 2.2 再次检查数据 2.3 配置数据集 2.4 可视化数据 三、构建VGG-16网络 3.1 VGG-16网络介绍 3.2 搭建VGG-16模型 四、编译 五、训练模型 六、模型评估 七、预测 总结 前言…

具身智能与大模型融合创新技术实训研讨会成功举办

2025年1月16日-19日武汉&#xff0c;TsingtaoAI联合北京博创鑫鑫教育科技&#xff0c;举行“具身智能与大模型融合创新技术”实训研讨会&#xff0c;本次会议面向高校AI教师和企业AI工程师群体&#xff0c;通过3天的技术研修和实操教学&#xff0c;通过将 AI 大模型与具备3D视觉…

OpenAI的工具革命: 当Operator撕开中国AI「内卷式创新」的遮羞布

OpenAI最新发布的智能体Operator&#xff0c;并非简单的任务执行工具&#xff0c;而是一场针对「工具的工具」的底层革命。它用通用性智能体架构重构人机协作范式&#xff0c;而中国AI产业仍在「卷场景」「卷补贴」的泥潭中打转。这场降维打击背后&#xff0c;暴露的是中美AI竞…

MySQL(1)

数据库 基础篇 MYSQL概述 SQL 函数 约束 多表查询 事务 进阶篇 存储索引 索引 SQL优化 试图/存储过程/触发器 锁 InnoDB核心 MySQL管理 运维篇 日志 主从复制 分库本表 读写分离 基础篇 MySQL 数据库概念&#xff1a;存储数据的仓库&#xff0c;数据是有…

SpringBoot+Vue使用Echarts

前言 在vue项目中使用echarts&#xff0c;本次演示是使用vue2 1 前端准备 echarts官网&#xff1a; https://echarts.apache.org/zh/index.html 官网提供了基本的使用说明和大量的图表 1.1 下载echarts 执行命令 npm install echarts 直接这样执行很可能会失败&#xff0c;…

PyQt6医疗多模态大语言模型(MLLM)实用系统框架构建初探(下.代码部分)

医疗 MLLM 框架编程实现 本医疗 MLLM 框架结合 Python 与 PyQt6 构建,旨在实现多模态医疗数据融合分析并提供可视化界面。下面从数据预处理、模型构建与训练、可视化界面开发、模型 - 界面通信与部署这几个关键部分详细介绍编程实现。 6.1 数据预处理 在医疗 MLLM 框架中,多…

Linux-day10

第21章 Linux高级篇-日志管理 日志介绍和实例 基本介绍 系统常用的日志 日志服务 日志服务原理图 在这个配置文件里面记录了日志服务程序 日志管理服务rsyslogd -v是反向匹配 invert 日志服务配置文件 时间、主机、是由哪个程序或者服务发生的、事件信息 自定义日志服务 日…

Linux第一讲--基本的命令操作

从今天开始&#xff0c;我将在csdn这个平台上和大家分享Linux的相关知识&#xff0c;欢迎大家一起讨论&#xff01; 零、基本操作 1.进入全屏&#xff1a; ALTENTER,退出也是这个 2.复制&#xff1a;ctrlinsert 3.粘贴&#xff1a;shiftinsert Linux中&#xff0c;cv是不好…

WinRAR.exe命令行的使用

工具 命令行打包命令 rem 默认压缩根目录&#xff0c;递归处理子文件夹使用 -r WinRAR.exe a -r test.rar C:/web/Views/