瀚文机械键盘固件开发详解:HWKeyboard.cpp文件解析与应用
🔥 机械键盘固件开发从入门到精通:HWKeyboard模块全解析
作为一名嵌入式开发老司机,今天带大家拆解一个完整的机械键盘固件代码。即使你是单片机小白,看完这篇教程也能轻松理解机械键盘的工作原理,甚至自己动手复刻一个!
🚀 项目整体概览
这个hw_keyboard.cpp
模块实现了一个完整的机械键盘固件,基于STM32单片机开发,主要功能包括:
- 按键矩阵扫描与状态读取
- 机械按键消抖处理
- 键位映射与HID协议数据生成
- RGB灯效控制(支持WS2812B灯带)
- 特殊功能键(Fn键、触控条)处理
整个固件就像一个智能中转站,把物理按键的按下抬起动作,转换成电脑能理解的键盘信号,同时控制炫酷的RGB灯光效果。
🧩 核心模块拆解
一、延时函数:精确掌控时间
inline void DelayUs(uint32_t _us)
{for (int i = 0; i < _us; i++) // 外层循环,每次循环代表1微秒for (int j = 0; j < 8; j++) // 内层循环,调整延时时长(不同芯片需调整)__NOP(); // 空操作,单纯耗时
}
白话解析:
- 这是一个微秒级的延时函数,精确控制程序暂停的时间
- 双重循环结构,外层控制延迟多少微秒,内层循环次数需要根据芯片主频调整
__NOP()
是"No Operation"的缩写,就是让CPU空转一个周期- 为什么需要它?键盘需要精确的时间控制,比如消抖、LED控制都需要
二、按键扫描模块:监听键盘上的每一次敲击
uint8_t* HWKeyboard::ScanKeyStates()
{memset(spiBuffer, 0xFF, IO_NUMBER / 8 + 1); // 将spiBuffer缓冲区全部置为0xFF,准备接收数据PL_GPIO_Port->BSRR = PL_Pin; // 设置锁存引脚为高电平,锁存当前按键状态spiHandle->pRxBuffPtr = (uint8_t*) spiBuffer; // 设置SPI接收缓冲区指针spiHandle->RxXferCount = IO_NUMBER / 8 + 1; // 设置SPI接收字节数__HAL_SPI_ENABLE(spiHandle); // 使能SPI外设while (spiHandle->RxXferCount > 0U) // 循环直到所有数据接收完毕{if (__HAL_SPI_GET_FLAG(spiHandle, SPI_FLAG_RXNE)) // 检查SPI接收缓冲区非空标志{(*(uint8_t*) spiHandle->pRxBuffPtr) = *(__IO uint8_t*) &spiHandle->Instance->DR; // 从SPI数据寄存器读取数据到缓冲区spiHandle->pRxBuffPtr += sizeof(uint8_t); // 指针后移spiHandle->RxXferCount--; // 剩余接收字节数减一}}__HAL_SPI_DISABLE(spiHandle); // 禁用SPI外设PL_GPIO_Port->BRR = PL_Pin; // 设置锁存引脚为低电平,完成采样return scanBuffer; // 返回扫描缓冲区指针
}
白话解析:
- 这个函数就像键盘的"耳朵",不断监听按键是否被按下
PL_Pin
是一个特殊引脚,拉高时会把所有按键的状态"拍照"保存- SPI通信就像快递系统:
spiBuffer
是装数据的袋子pRxBuffPtr
是指向袋子的手指RxXferCount
是需要接收的数据数量
- 数据接收完成后,返回的
scanBuffer
里存着所有按键的当前状态(1表示未按下,0表示按下)
三、按键消抖模块:解决机械按键的"手抖"问题
void HWKeyboard::ApplyDebounceFilter(uint32_t _filterTimeUs)
{memcpy(debounceBuffer, spiBuffer, IO_NUMBER / 8 + 1); // 备份当前SPI缓冲区到消抖缓冲区DelayUs(_filterTimeUs); // 延时一段时间,等待抖动消除ScanKeyStates(); // 再次扫描按键状态uint8_t mask;for (int i = 0; i < IO_NUMBER / 8 + 1; i++) // 遍历所有字节{mask = debounceBuffer[i] ^ spiBuffer[i]; // 计算两次扫描的不同位spiBuffer[i] |= mask; // 将有变化的位强制置为1(消除抖动影响)}
}
白话解析:
- 机械按键按下时会像"手抖"一样产生短暂的多次通断,这就是抖动
- 消抖处理就像拍照时的防抖功能:
- 先保存第一次拍的照片(按键状态)
- 等一小会儿(
_filterTimeUs
微秒) - 再拍一张照片(再次扫描按键)
- 比较两张照片,如果有不同,就认为是"抖动",修正这些差异
- 这个过程很重要,否则键盘会误判你按了多次键
四、键位映射模块:把物理按键变成电脑认识的键
uint8_t* HWKeyboard::Remap(uint8_t _layer)
{int16_t index, bitIndex; // 定义索引变量memset(remapBuffer, 0, IO_NUMBER / 8); // 清空重映射缓冲区for (int16_t i = 0; i < IO_NUMBER / 8; i++) // 遍历每个字节{for (int16_t j = 0; j < 8; j++) // 遍历每个bit{index = (int16_t) (keyMap[0][i * 8 + j] / 8); // 计算当前物理按键在scanBuffer中的字节索引bitIndex = (int16_t) (keyMap[0][i * 8 + j] % 8); // 计算当前物理按键在该字节中的位索引if (scanBuffer[index] & (0x80 >> bitIndex)) // 检查该物理按键是否被按下(高电平为未按下,低电平为按下)remapBuffer[i] |= 0x80 >> j; // 如果按下,则在remapBuffer中对应位置标记为1}remapBuffer[i] = ~remapBuffer[i]; // 取反,转换为"按下为1,未按下为0"}memset(hidBuffer, 0, KEY_REPORT_SIZE); // 清空HID报告缓冲区int i = 0, j = 0;while (8 * i + j < IO_NUMBER - 6) // 遍历所有可用按键(排除最后6个保留位){for (j = 0; j < 8; j++){index = (int16_t) (keyMap[_layer][i * 8 + j] / 8 + 1); // 计算映射后按键在hidBuffer中的字节索引(+1跳过修饰键)bitIndex = (int16_t) (keyMap[_layer][i * 8 + j] % 8); // 计算映射后按键在该字节中的位索引if (bitIndex < 0){index -= 1; // 位索引为负时,向前借一字节bitIndex += 8;} else if (index > 100)continue; // 越界保护if (remapBuffer[i] & (0x80 >> j)) // 如果该按键被按下hidBuffer[index + 1] |= 1 << (bitIndex); // 在hidBuffer中对应位置标记为1(+1跳过Report-ID)}i++;j = 0;}return hidBuffer; // 返回HID报告缓冲区
}
白话解析:
- 这个模块就像一个"翻译官",把物理按键的位置翻译成电脑认识的键码
- 过程分两大步:
- 第一步:把原始扫描结果(
scanBuffer
)转换成中间格式(remapBuffer
)- 这一步是把"物理按键位置"变成"逻辑按键位置"
- 使用
keyMap[0]
查表,找到每个物理按键对应的逻辑位置
- 第二步:把中间格式(
remapBuffer
)转换成USB-HID标准格式(hidBuffer
)- 这一步是把"逻辑按键位置"变成"标准键码"
- 使用
keyMap[_layer]
查表,支持多层键位映射(比如Fn组合键)
- 第一步:把原始扫描结果(
- 最终生成的
hidBuffer
就是可以直接发送给电脑的USB-HID报告
五、RGB灯效控制模块:让键盘"发光发热"
void HWKeyboard::SetRgbBufferByID(uint8_t _keyId, HWKeyboard::Color_t _color, float _brightness)
{// 防止全0导致ws2812b协议错误if (_color.b < 1)_color.b = 1; // 蓝色分量最小为1,避免全0for (int i = 0; i < 8; i++) // 遍历8位{rgbBuffer[_keyId][0][i] =((uint8_t) ((float) _color.g * _brightness) >> brightnessPreDiv) & (0x80 >> i) ? WS_HIGH : WS_LOW; // 绿色分量rgbBuffer[_keyId][1][i] =((uint8_t) ((float) _color.r * _brightness) >> brightnessPreDiv) & (0x80 >> i) ? WS_HIGH : WS_LOW; // 红色分量rgbBuffer[_keyId][2][i] =((uint8_t) ((float) _color.b * _brightness) >> brightnessPreDiv) & (0x80 >> i) ? WS_HIGH : WS_LOW; // 蓝色分量}
}void HWKeyboard::SyncLights()
{while (isRgbTxBusy); // 等待上一次DMA传输完成isRgbTxBusy = true; // 标记DMA忙HAL_SPI_Transmit_DMA(&hspi2, (uint8_t*) rgbBuffer, LED_NUMBER * 3 * 8); // 通过DMA发送RGB数据while (isRgbTxBusy); // 等待DMA完成isRgbTxBusy = true; // 再次标记DMA忙HAL_SPI_Transmit_DMA(&hspi2, wsCommit, 64); // 发送ws2812b协议结尾信号
}
白话解析:
- 这个模块负责控制键盘上的RGB灯,让键盘变得炫酷
SetRgbBufferByID
函数像是一支神奇的画笔:_keyId
:选择要涂色的灯珠_color
:选择RGB颜色(红、绿、蓝三原色)_brightness
:控制颜色的亮度(0.0-1.0)
- WS2812B是一种智能LED灯珠,需要特殊的信号格式:
- 每个灯珠需要24位数据(8位绿+8位红+8位蓝)
WS_HIGH
和WS_LOW
是两种不同的电平时序,用来表示1和0- 所有灯珠串联在一起,数据像多米诺骨牌一样传递
SyncLights
函数使用DMA(直接内存访问)技术快速发送数据:- DMA可以在不占用CPU的情况下传输数据
- 发送完所有LED数据后,还要发送一个结束信号(
wsCommit
)
六、特殊功能键处理模块:Fn键和触控条
bool HWKeyboard::FnPressed()
{return remapBuffer[9] & 0x02; // 检查remapBuffer第9字节的第2位(Fn键状态)
}uint8_t HWKeyboard::GetTouchBarState(uint8_t _id)
{uint8_t tmp = (remapBuffer[10] & 0b00000001) << 5 | // 取remapBuffer第10字节的各个位,重新排列组合(remapBuffer[10] & 0b00000010) << 3 |(remapBuffer[10] & 0b00000100) << 1 |(remapBuffer[10] & 0b00001000) >> 1 |(remapBuffer[10] & 0b00010000) >> 3 |(remapBuffer[10] & 0b00100000) >> 5;return _id == 0 ? tmp : (tmp & (1 << (_id - 1))); // 返回全部状态或指定触控条状态
}
白话解析:
- 这部分处理键盘上的特殊功能键:Fn键和触控条
FnPressed
函数检查Fn键是否按下:- 简单查看
remapBuffer
中的特定位,1表示按下,0表示未按下 - Fn键在这个键盘中位于第9字节的第2位(从0开始计数)
- 简单查看
GetTouchBarState
函数读取触控条状态:- 触控条有多个触摸点,每个点对应
remapBuffer[10]
的一位 - 函数进行位重排,使触摸点按从左到右的顺序排列
- 参数
_id
为0时返回所有触摸点状态,否则返回特定触摸点状态
- 触控条有多个触摸点,每个点对应
七、HID报告处理模块:电脑与键盘的"对话"
uint8_t* HWKeyboard::GetHidReportBuffer(uint8_t _reportId)
{switch (_reportId){case 1:hidBuffer[0] = 1; // 设置报告ID为1return hidBuffer; // 返回主报告缓冲区case 2:hidBuffer[KEY_REPORT_SIZE] = 2; // 设置报告ID为2return hidBuffer + KEY_REPORT_SIZE; // 返回备用报告缓冲区default:return hidBuffer; // 默认返回主报告缓冲区}
}bool HWKeyboard::KeyPressed(KeyCode_t _key)
{int index, bitIndex;if (_key < RESERVED) // 判断是否为保留键{index = _key / 8; // 计算字节索引bitIndex = (_key + 8) % 8; // 计算位索引} else{index = _key / 8 + 1; // 计算字节索引(跳过修饰键)bitIndex = _key % 8; // 计算位索引}return hidBuffer[index + 1] & (1 << bitIndex); // 检查对应位是否为1(按下)
}void HWKeyboard::Press(HWKeyboard::KeyCode_t _key)
{int index, bitIndex;if (_key < RESERVED){index = _key / 8;bitIndex = (_key + 8) % 8;} else{index = _key / 8 + 1;bitIndex = _key % 8;}hidBuffer[index + 1] |= (1 << bitIndex); // 设置对应位为1(按下)
}void HWKeyboard::Release(HWKeyboard::KeyCode_t _key)
{int index, bitIndex;if (_key < RESERVED){index = _key / 8;bitIndex = (_key + 8) % 8;} else{index = _key / 8 + 1;bitIndex = _key % 8;}hidBuffer[index + 1] &= ~(1 << bitIndex); // 清除对应位(释放)
}
白话解析:
- 这部分处理键盘的HID报告,这是键盘与电脑通信的"官方语言"
GetHidReportBuffer
函数准备不同类型的HID报告:- 报告ID 1:标准键盘报告
- 报告ID 2:扩展功能报告(如多媒体键、自定义功能键)
KeyPressed
函数检查某个键是否被按下:- 通过计算键码在HID报告中的位置(字节索引和位索引)
- 特殊处理小于
RESERVED
的键(可能是修饰键如Ctrl、Shift等)
Press
和Release
函数模拟按键按下和释放:- 直接修改HID报告缓冲区中对应键的状态位
- 这允许程序在不实际按键的情况下发送按键信号
📊 完整工作流程
一个按键从按下到被电脑识别的全过程:
-
硬件初始化:
- 设置SPI通信参数
- 配置GPIO引脚
- 初始化RGB灯为熄灭状态
-
按键扫描循环:
while(1) {ScanKeyStates(); // 扫描按键矩阵,读取原始状态ApplyDebounceFilter(5000); // 应用5ms消抖滤波uint8_t layer = FnPressed() ? 1 : 0; // 根据Fn键状态选择映射层Remap(layer); // 重映射键位,生成HID报告// 发送HID报告给电脑uint8_t* report = GetHidReportBuffer(1);USB_SendData(report, KEY_REPORT_SIZE);// 更新RGB灯效UpdateRgbEffects();SyncLights();HAL_Delay(10); // 10ms扫描周期 }
-
关键环节解析:
- 按键扫描:使用SPI读取74HC165移位寄存器中的按键状态
- 消抖处理:比较两次扫描结果,忽略抖动引起的差异
- 重映射处理:物理按键位置→逻辑按键位置→标准HID键码
- RGB控制:设置每个LED的RGB值,通过DMA高速传输数据
- USB通信:定期发送HID报告给电脑,告知当前按键状态
💡 小白开发指南
开发环境搭建
-
硬件准备:
- STM32F1/F4系列单片机(如STM32F103C8T6)
- 74HC165移位寄存器(扩展输入IO)
- WS2812B RGB灯珠
- 机械键盘轴体和轴座
- PCB电路板
-
软件工具:
- STM32CubeIDE或Keil MDK(代码编写和编译)
- STM32CubeMX(单片机外设配置)
- PCB设计软件(如立创EDA、Altium Designer)
从零开始的实现步骤
-
项目结构设计:
- main.c // 主程序入口 - hw_keyboard.h // HWKeyboard类声明 - hw_keyboard.cpp // HWKeyboard类实现 - usb_device.c // USB设备配置 - key_map.h // 键位映射表
-
关键硬件连接:
STM32 SPI1_MISO <- 74HC165 QH (串行数据输出) STM32 SPI1_CLK -> 74HC165 CLK (时钟信号) STM32 GPIO_PL -> 74HC165 PL (锁存信号)STM32 SPI2 -> WS2812B数据线(通过电平转换)
-
代码实现步骤:
- 实现
ScanKeyStates
函数,通过SPI读取按键状态 - 添加
ApplyDebounceFilter
消抖处理 - 实现
Remap
函数,完成键位映射 - 添加RGB灯效控制函数
- 最后实现USB通信部分
- 实现
-
测试调试方法:
- 分阶段测试:先测试按键扫描,再测试灯效控制
- 使用串口打印中间变量进行调试
- 使用示波器观察SPI和WS2812B信号波形
📚 进阶知识点
1. 如何定制键位映射
键位映射是通过keyMap
二维数组实现的:
// 示例键位映射表(简化版)
const uint16_t keyMap[2][64] = {// Layer 0: 标准层{KEY_ESC, KEY_1, KEY_2, KEY_3, /* 更多键... */},// Layer 1: Fn层{KEY_GRAVE, KEY_F1, KEY_F2, KEY_F3, /* 更多键... */}
};
定制步骤:
- 测量物理按键矩阵位置
- 确定每个位置对应的标准键码(参考USB HID标准)
- 填写到
keyMap
数组中
2. RGB灯效编程技巧
// 彩虹灯效示例
void RainbowEffect() {static uint8_t hue = 0;for(int i = 0; i < LED_NUMBER; i++) {// 创建彩虹色相滚动效果Color_t color = HsvToRgb(hue + i * 255 / LED_NUMBER, 255, 255);keyboard.SetRgbBufferByID(i, color, 0.5f); // 亮度50%}keyboard.SyncLights();hue++; // 颜色循环移动
}// HSV转RGB颜色转换
Color_t HsvToRgb(uint8_t h, uint8_t s, uint8_t v) {Color_t rgb = {0, 0, 0};// 转换算法实现// ...return rgb;
}
3. 性能优化技巧
-
扫描频率优化:
- 降低扫描频率可节省CPU资源
- 但过低会导致输入延迟
- 推荐扫描频率:100Hz(10ms周期)
-
DMA使用:
- 使用DMA传输RGB数据,释放CPU资源
- 使用中断而非轮询等待DMA完成
-
内存优化:
- 使用位操作减少内存使用
- 共用缓冲区减少RAM占用
🎯 实战项目:DIY全彩RGB机械键盘
完成这个教程后,你可以尝试以下项目:
-
简易版:61键迷你键盘
- 标准QWERTY布局
- 单色背光
- 两层键位映射
-
进阶版:64键配置RGB
- 增加方向键
- 全RGB背光
- 多种灯效模式
-
大师版:分体式人体工学键盘
- 左右分离设计
- 每键RGB可寻址
- 支持无线蓝牙连接
通过本教程的学习,你已经掌握了机械键盘固件开发的核心技术。从简单的按键扫描到复杂的RGB控制,从底层硬件操作到高层次的用户体验,一步步揭开了机械键盘的神秘面纱。希望这份教程能帮助你开启DIY键盘的奇妙旅程!
相关文章:
瀚文机械键盘固件开发详解:HWKeyboard.cpp文件解析与应用
🔥 机械键盘固件开发从入门到精通:HWKeyboard模块全解析 作为一名嵌入式开发老司机,今天带大家拆解一个完整的机械键盘固件代码。即使你是单片机小白,看完这篇教程也能轻松理解机械键盘的工作原理,甚至自己动手复刻一…...
Nginx+Tomcat负载均衡与动静分离架构
目录 简介 一、Tomcat基础部署与配置 1.1 Tomcat应用场景与特性 1.2 环境准备与安装 1.3 Tomcat主配置文件详解 1.4 部署Java Web站点 二、NginxTomcat负载均衡群集搭建 2.1 架构设计与原理 2.2 环境准备 2.3 Tomcat2配置(与Tomcat1对称) 2.4…...

AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年6月8日第102弹
从今天开始,咱们还是暂时基于旧的模型进行预测,好了,废话不多说,按照老办法,重点8-9码定位,配合三胆下1或下2,杀1-2个和尾,再杀4-5个和值,可以做到100-300注左右。 (1)定…...
LeetCode--25.k个一组翻转链表
解题思路: 1.获取信息: (1)给定一个链表,每k个结点一组进行翻转 (2)余下不足k个结点,则不进行交换 2.分析题目: 其实就是24题的变题,24题是两两一组进行交换&…...
css | class中 ‘.‘ 和 ‘:‘ 的使用 | 如,何时用 .is-selected{ ... } 何时用 :hover{...}?
省流总结:交互时的短暂视觉反馈 → 用 :hover,状态需要记录或切换 → 用类名如 .is-selected。 🧠 本质区别: 写法触发方式用途&.is-selected依赖 class 切换需要 JavaScript 控制状态,如选中、激活&:hover鼠…...

【第九篇】 SpringBoot测试补充篇
简介 本文介绍了SpringBoot测试中的五项关键技术:测试类专用属性加载、 测试类专用Bean配置、 表现层测试方法、测试类事务回滚控制、配置文件随机数据设置)。这些技术可以有效隔离测试环境,确保测试数据不影响生产环境,同时提供了…...

springcloud SpringAmqp消息队列 简单使用
这期只是针对springBoot/Cloud 在使用SpringAmqp消息队列的时候遇到的坑。 前提 如果没有安装RabbitMQ是无法连接成功的!所以前提是你要安装好RabbitMQ。 docker 安装命令 # 拉取docker镜像 docker pull rabbitmq:management# 创建容器 docker run -id --namera…...

Framework开发之IMS逻辑浅析1--关键线程及作用
关键线程:EventHub,InputReader,InputDispatcher EventHub: 由于Android继承Linux,Linux的思想是一切皆文件,而输入的类型不止一种(触碰,写字笔,键盘等),每种类型都对应一种驱动设备,而每个硬件驱动设备又对应Linux的一个目录文件…...
The Quantization Model of Neural Scaling
文章目录 摘要1引言2 理论3 概念验证:一个玩具数据集3.1 “多任务稀疏奇偶校验”数据集3.2 幂律规模和新兴能力 4 拆解大型语言模型的规模定律4.1 单token损失的分布4.2 单基因(monogenic)与多基因(polygenic)的规模曲…...
数据源指的是哪里的数据,磁盘中还是内存中
在 MyDB 项目中,特别是这段缓存框架代码: T obj getForCache(key);以及它的上下文: AbstractCache 是一个抽象类,内部有两个抽象方法,留给实现类去实现具体的操作: protected abstract T getForCache(lon…...

系统思考:跳出症状看全局
明天将为华为全球采购认证管理部的伙伴们带来一场关于系统思考的深度课程!通过经典的啤酒游戏经营决策沙盘,一起沉浸式体验如何从全局视角看待问题,发现单点最优并不等于全局最优。 这不仅是一次简单的课程,更是一次洞察系统背后…...

DeepSeek R1 V2 深度探索:开源AI编码新利器,效能与创意并进
最近,AI界迎来了一位神秘的“突袭者”——DeepSeek团队悄无声息地发布了其推理模型DeepSeek R1的重磅升级版V2(具体型号R1-0528)。这款基于MIT许可的开源模型,在原版R1的基础上进行了多项令人瞩目的改进,正以其强大的潜…...

surfer15安装
安装文件 安装包和破解文件 安装 破解及汉化 打开软件...
MySQL从入门到DBA深度学习指南
目录 引言 MySQL基础入门 数据库基础概念 MySQL安装与配置 SQL语言进阶 数据库设计与规范化 数据库设计原则 表结构设计 MySQL核心管理 用户权限管理 备份与恢复 性能优化基础 高级管理与高可用 高可用与集群 故障诊断与监控 安全与审计 DBA实战与运维 性能调…...

Python训练营---DAY48
DAY 48 随机函数与广播机制 知识点回顾: 随机张量的生成:torch.randn函数卷积和池化的计算公式(可以不掌握,会自动计算的)pytorch的广播机制:加法和乘法的广播机制 ps:numpy运算也有类似的广播机…...

debian12拒绝海外ip连接
确保 nftables 已安装: Debian 12 默认使用 nftables 作为防火墙框架。检查是否安装: sudo apt update sudo apt install nftables启用并启动 nftables 服务 sudo systemctl enable nftables sudo systemctl start nftables下载maxmind数据库 将文件解…...

70年使用权的IntelliJ IDEA Ultimate安装教程
安装Java环境 下载Java Development Kit (JDK) 从Oracle官网或OpenJDK。推荐选择JDK 11或更高版本。 运行下载的安装程序,按照提示完成安装。注意记录JDK的安装路径(如C:\Program Files\Java\jdk-11.0.15)。 配置环境变量: 右键…...

MySQL的日志
就相当于人的日记本,记录每天发生的事,可以对数据进行追踪 一、错误日志 也就是存放错误信息的 二、二进制日志-binlog 在低版本的MySQL中,二进制日志是不会默认开启的 存放除了查询语句的其他语句 三、查询日志 查询日志会记录客户端的所…...

低功耗高安全:蓝牙模块在安防系统中的应用方案
随着物联网(IoT)和智能家居的快速发展,安防行业正迎来前所未有的技术革新。蓝牙模块作为一种低功耗、高稳定性的无线通信技术,凭借其低成本、易部署和智能化管理等优势,在安防领域发挥着越来越重要的作用。本文将探讨蓝牙模块在安防系统中的应…...
数据库(sqlite)基本操作
数据库(sqlite) 一:简介: 为什么需要单独的数据库来进行管理数据? 数据的各种查询功能数据的备份和恢复花大量时间在文件数据的结构设计和维护上要考虑多线程对数据的操作会涉及到同步问题,会增加很多额…...
【HarmonyOS 5】游戏开发教程
一、开发环境搭建 工具配置 安装DevEco Studio 5.1,启用CodeGenie AI助手(Settings → Tools → AI Assistant)配置游戏模板:选择"Game"类型项目,勾选手机/平板/折叠屏多设备支持 二、游戏引擎核心架构…...
神经元激活函数在神经网络里起着关键作用
神经元激活函数在神经网络里起着关键作用,它能为网络赋予非线性能力,让网络可以学习复杂的函数映射关系。下面从多个方面详细剖析激活函数的作用和意义: 1. 核心作用:引入非线性因素 线性模型的局限性: 假设一个简单…...
[蓝桥杯 2024 国 B] 蚂蚁开会
问题描述 二维平面上有 n 只蚂蚁,每只蚂蚁有一条线段作为活动范围,第 i 只蚂蚁的活动范围的两个端点为 (uix,uiy),(vix,viy)。现在蚂蚁们考虑在这些线段的交点处设置会议中心。为了尽可能节省经费,它们决定只在所有交点为整点的地方设置会议…...
GIT(AI回答)
在Git中,git push 命令主要用于将本地分支的提交推送到远程仓库(如GitHub、GitLab等)。如果你希望将本地分支的改动同步到另一个本地分支,这不是 git push 的设计目的。以下是正确的替代方法: 方法1࿱…...
JAVA学习-练习试用Java实现“TF-IDF算法 :用于文本特征提取。”
问题: java语言编辑,实现TF-IDF算法 :用于文本特征提取。 解答思路: TF-IDF(Term Frequency-Inverse Document Frequency)是一种常用的文本特征提取方法,用于评估一个词语对于一个文件集或一个语料库中的其中一份文件的…...

C++定长内存块的实现
内存池 内存池是指程序预先从操作系统 申请一块足够大内存 ,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是 直接从内存池中获取 ; 同理,当 **程序释放内存 **的时候,并不真正将…...
【判断自整除数】2022-4-6
缘由是判断自整除数的,这个我的结果是正确的,但是提交就有运行错误是怎么回事啊-编程语言-CSDN问答 void 自整除数字() {//所谓的自整除数字就是该数字可以整除其每一个位上的数字。 //对一个整数n,如果其各个位数的数字相加得到的数m能整除n,则称n为自…...
使用 Ansible 在 Windows 服务器上安装 SSL 证书系列之二
今天带大家实战一下如何通过ansible在windows 服务器上给iis web site安装证书。 前提条件: 准备一张pfx证书,可以通过openssl工具来生成,具体的步骤请参考帮助文档。一台安装了iis 的windows 服务器 准备inventory文件 [windows] solarwinds ansible_host=20.47.126.72 a…...

Unity使用代码分析Roslyn Analyzers
一、创建项目(注意这里不要选netstandard2.1会有报错) 二、NuGet上安装Microsoft.CodeAnalysis.CSharp 三、实现[Partial]特性标注的类,结构体,record必须要partial关键字修饰 需要继承DiagnosticAnalyzer 注意一定要加特性Diagn…...

大数据CSV导入MySQL
CSV Import MySQL 源码主要特性技术栈快速开始1. 环境要求2. 构建项目3. 使用方式交互式模式命令行模式编程方式使用 核心组件1. CsvService2. DatabaseService3. CsvImportService 数据类型映射性能优化1. 连接池优化2. 批量操作优化3. MySQL配置优化 配置说明application.yml…...