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

基于STM32实现OTABootLoader 第五章——OTA功能开发【下】

三、开发客户端ESP82661、自定义AT指令集1实际上Wi-Fi模块通常配有官方的AT指令集只要刷写官方提供的固件即可但可能功能不全不过AT指令本身就是基于串口通信实现的一种指令其原理并不复杂如果由自己实现那么就可以自定义指令集。2以下是本例程中使用的AT指令集读者可自行设计也可使用官方提供的固件需要注意的是调试时串口监视器发送消息必须将结束符设置为“换行 和 回车 两者都是”传输指令时无需单独输入换行和回车。①Wi-Fi连接指令AT指令功能ESP8266执行动作ATCWJAP\ssid\,\password\\r\n例ATCWJAPZevalin_Computer,00114514\r\n连接Wi-Fi网络ssid欲连接Wi-Fi的名password欲连接Wi-Fi的密码调用ESP8266WiFi库的begin函数连接目标Wi-Fi网络②发送HTTP请求指令AT指令功能ESP8266执行动作ATHTTPCONNECT\host,port\\r\n与服务器建立连接host被连接网络服务器的网址/IP地址port被连接网络服务器的端口编号HTTP协议的熟知端口号为80调用WiFiClient库的connect函数与服务器建立连接ATHTTPREQ\message\\r\n向服务器发送请求报文message报文内容需要程序员按照通信协议的格式自行构建调用WiFiClient库的print函数向服务器发送请求报文ATHTTPSTOPNULL\r\n与服务器的连接断开调用WiFiClient库的stop函数与服务器断开连接③订阅MQTT主题AT指令功能ESP8266执行动作ATMQTTCONNECT\mqttsever,port\\r\n连接MQTT服务器mqttsever欲连接的MQTT服务器地址port欲连接的MQTT服务器端口号调用PubSubClient库的setServer函数设置目标MQTT服务器及端口号然后调用connect函数连接MQTT服务端ATMQTTSUB\topic\\r\n订阅主题topic欲订阅的主题调用PubSubClient库的subscribe函数向MQTT服务端订阅主题ATMQTTDISCONNECTNULL\r\n断开与MQTT服务器的连接mqttsever欲断开的MQTT服务器地址port欲断开的MQTT服务器端口号调用PubSubClient库的disconnect函数断开与MQTT服务器的连接3以下是本例程中使用的回复数据集读者可自行设计也可使用官方提供的固件。①Wi-Fi连接回复回复数据功能IPD,16:Wi-Fi Connected!\r\n回复Wi-Fi网络连接成功16数据长度IPD,24:Wi-Fi Connection Failed!\r\n回复Wi-Fi网络连接失败24数据长度②接收HTTP响应回复回复数据功能IPD,16:Sever Connected!\r\n回复服务器连接成功16数据长度IPD,24:Sever Connection Failed!\r\n回复服务器连接失败24数据长度IPD,数据长度:响应报文\r\n返回HTTP响应报文数据长度数据长度响应报文HTTP响应报文③接收MQTT报文回复数据功能IPD,15:MQTT Connected!\r\n回复MQTT服务器连接成功15数据长度IPD,23:MQTT Connection Failed!\r\n回复MQTT服务器连接失败23数据长度IPD,17:Subscribe Success!\r\n回复主题订阅成功17数据长度IPD,16:Subscribe Failed!\r\n回复主题订阅失败16数据长度IPD,数据长度:\主题\,\消息主体\\r\n返回MQTT报文数据长度数据长度主题MQTT主题消息主体MQTT消息2、自定义AT指令集实现1需要说明的是以下代码不一定考虑到所有可能发生的场景实际开发中可能会面临各种各样的异常情况由于篇幅有限部分异常情况仅做判断不做后处理方案可参考注释的发送内容STM32根据“ERROR”捕捉异常情况码做相应的后处理。2将以下代码文件编译下载到ESP8266中。#include ESP8266WiFi.h #include ESP8266WiFiMulti.h #include PubSubClient.h ESP8266WiFiMulti wifiMulti; WiFiClient httpClient, espClient; // 同一个节点与两台服务器交互因而有两个抽象客户端 PubSubClient mqttClient(espClient); bool isConnected false; //HTTP连接状态 bool mqttConnected false; //MQTT连接状态 String inputString ; int state 0; // 0: 正常; 1: 收到\r; 2: 收到\r\n 完成 void setup(){ Serial.begin(9600); Serial.println(); } void loop(){ while (Serial.available()) { char c (char)Serial.read(); inputString c; // 状态机检测 \r\n根据AT指令结束标志获取AT指令 if (c \r state 0) { state 1; //Serial.println(state - 1 (got \\r)); //调试使用 } else if (c \n state 1) { state 2; //Serial.println(state - 2 (got \\n)); //调试使用 } else { // 如果出现不匹配的字符重置状态但保留已读数据 state 0; //Serial.print(Resetting state due to char: ); Serial.println(c); //调试使用 } // 当完成序列时处理AT指令 if (state 2) { // 去掉末尾的 \r\n if (inputString.length() 2) { inputString.remove(inputString.length() - 2); } AT_work(inputString); // 根据AT指令执行动作 // 清空准备下一条指令接收 inputString ; state 0; } } if(mqttConnected true) { mqttClient.loop(); //如果连接了MQTT服务器就保持心跳 } } // 连接MQTT服务端的具体函数 bool connectToMqtt(String host, int port) { // 1. 设置MQTT服务器地址和端口 mqttClient.setServer(host.c_str(), port); // 2. 生成一个唯一的客户端ID String clientId ESP8266Client- String(random(0xffff), HEX); // 3. 尝试连接使用最基本的连接方式无用户名密码 if (mqttClient.connect(clientId.c_str())) { mqttConnected true; // 连接成功后可以在这里设置回调函数用于处理收到的消息 mqttClient.setCallback(callback); return true; } else { mqttConnected false; return false; } } void callback(char* topic, byte* payload, unsigned int length) { // 将消息主体转换为 String假设 payload 是文本 String message ; for (unsigned int i 0; i length; i) { message (char)payload[i]; } // 构造要输出的主体部分topic,message // 如果 topic 或 message 中包含双引号需要转义为 \ String dataPart \ String(topic) \,\ message \; int dataLen dataPart.length(); // 数据部分的字节长度 // 构造完整的 AT 指令输出行 String output IPD, String(dataLen) : dataPart \r\n; // 通过串口发送 Serial.print(output); } void AT_CWJAP(String inputString) { // 提取 ATCWJAP 之后的部分包括双引号 int pos inputString.indexOf(ATCWJAP); String params inputString.substring(pos 9); // 查找第一个双引号的位置 int firstQuote params.indexOf(); if (firstQuote -1) { //Serial.print(ERROR: missing SSID quote\r\n); return; } int secondQuote params.indexOf(, firstQuote 1); if (secondQuote -1) { //Serial.print(ERROR: missing closing SSID quote\r\n); return; } String ssid params.substring(firstQuote 1, secondQuote); // 查找第二个双引号后的逗号 int comma params.indexOf(,, secondQuote); if (comma -1) { //Serial.print(ERROR: missing comma\r\n); return; } // 查找密码部分的起始双引号 int pwdFirstQuote params.indexOf(, comma 1); if (pwdFirstQuote -1) { //Serial.print(ERROR: missing password quote\r\n); return; } int pwdSecondQuote params.indexOf(, pwdFirstQuote 1); if (pwdSecondQuote -1) { //Serial.print(ERROR: missing closing password quote\r\n); return; } String password params.substring(pwdFirstQuote 1, pwdSecondQuote); /* 打印调试信息 */ //Serial.print(SSID: ); Serial.println(ssid); //Serial.print(PWD: ); Serial.println(password); // 传递String对象如果addAP支持 const String若不支持可转换为字符数组并复制 wifiMulti.addAP(ssid.c_str(), password.c_str()); int count 0; while (wifiMulti.run() ! WL_CONNECTED count 15) { delay(1000); count; } if (wifiMulti.run() WL_CONNECTED) { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(16); Serial.print(:); Serial.print(Wi-Fi Connected!\r\n); // 回复Wi-Fi连接成功 Serial.print(\r\n); } else { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(24); Serial.print(:); Serial.print(Wi-Fi Connection Failed!\r\n); // 回复Wi-Fi连接失败 Serial.print(\r\n); } } void AT_HTTPCONNECT(String inputString) { // 提取 ATHTTPCONNECT 之后的部分 int pos inputString.indexOf(ATHTTPCONNECT); if (pos -1) return; String params inputString.substring(pos 16); // ATHTTPCONNECT 长度为16 // 查找第一个双引号提取 host int firstQuote params.indexOf(); if (firstQuote -1) { //Serial.print(ERROR: missing host quote\r\n); return; } int secondQuote params.indexOf(, firstQuote 1); if (secondQuote -1) { //Serial.print(ERROR: missing closing host quote\r\n); return; } String host params.substring(firstQuote 1, secondQuote); // 查找逗号位置然后提取 port可能在双引号外 int comma params.indexOf(,, secondQuote); if (comma -1) { //Serial.print(ERROR: missing comma\r\n); return; } // 提取逗号后面的部分去除可能的空白和引号 String portStr params.substring(comma 1); portStr.trim(); // 如果端口号也被引号包围某些格式去除引号 if (portStr.startsWith(\) portStr.endsWith(\)) { portStr portStr.substring(1, portStr.length() - 1); } int port portStr.toInt(); if (port 0) { //Serial.print(ERROR: invalid port number\r\n); return; } /* 打印调试信息 */ //Serial.print(Connecting to ); //Serial.print(host); //Serial.print(:); //Serial.println(port); // 尝试建立 TCP 连接 if (httpClient.connect(host.c_str(), port)) { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(16); Serial.print(:); Serial.print(Sever Connected!\r\n); // 回复服务器连接成功 Serial.print(\r\n); isConnected true; // 置位连接状态 } else { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(24); Serial.print(:); Serial.print(Sever Connection Failed!\r\n); // 回复服务器连接失败 Serial.print(\r\n); httpClient.stop(); isConnected false; // 置位连接状态 } } void AT_HTTPREQ(String inputString) { // 检查是否已建立 TCP 连接 if (!isConnected || !httpClient.connected()) { //Serial.print(ERROR: no TCP connection\r\n); return; } // 提取 ATHTTPREQ 之后的部分 int pos inputString.indexOf(ATHTTPREQ); if (pos -1) return; String params inputString.substring(pos 10); // ATHTTPREQ 长度 10 // 查找双引号包裹的 message int firstQuote params.indexOf(); if (firstQuote -1) { //Serial.print(ERROR: missing request message\r\n); return; } int lastQuote params.lastIndexOf(); if (lastQuote firstQuote) { //Serial.print(ERROR: invalid message format\r\n); return; } String request params.substring(firstQuote 1, lastQuote); // 直接发送请求 //Serial.print(Sending HTTP request...\r\n); size_t sent httpClient.write((const uint8_t*)request.c_str(), request.length()); if (sent ! request.length()) { //Serial.print(ERROR: send incomplete\r\n); return; } // 等待并读取响应设置超时时间5秒 unsigned long timeout millis() 5000; String response ; while (millis() timeout) { if (httpClient.available()) { // 读取一行或全部数据这里简单读取所有可用数据 while (httpClient.available()) { response (char)httpClient.read(); } break; } delay(10); } if (response.length() 0) { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(response.length()); Serial.print(:); Serial.print(response); Serial.print(\r\n); } else { //Serial.print(ERROR: no response from server\r\n); } // 是否保持连接由请求头决定Connection: close 则服务器会关闭 // 若服务器关闭了连接需要更新 isConnected 状态 if (!httpClient.connected()) { httpClient.stop(); isConnected false; //Serial.print(Server closed connection\r\n); } } void AT_HTTPSTOP(String inputString) { // 1. 解析指令 if (!inputString.startsWith(ATHTTPSTOP)) { //Serial.print(ERROR: invalid command\r\n); return; } // 2. 检查连接状态 if (!isConnected !httpClient.connected()) { //Serial.print(ERROR: HTTP not connected\r\n); return; } // 3. 关闭连接 httpClient.stop(); // 调用 stop() 关闭连接 isConnected false; // 更新连接状态标志 // 4. 返回响应 //Serial.print(OK\r\n); } void AT_MQTTCONNECT(String inputString) { // 1. 检查指令前缀 if (!inputString.startsWith(ATMQTTCONNECT)) { //Serial.print(ERROR: invalid command\r\n); return; } // 2. 提取参数部分 (ATMQTTCONNECT 的长度为 15) String params inputString.substring(15); // 3. 查找并提取 host (服务器地址) int firstQuote params.indexOf(); if (firstQuote -1) { //Serial.print(ERROR: missing host\r\n); return; } int secondQuote params.indexOf(, firstQuote 1); if (secondQuote -1) { //Serial.print(ERROR: missing closing host quote\r\n); return; } String host params.substring(firstQuote 1, secondQuote); // 4. 提取 port (端口号) int comma params.indexOf(,, secondQuote); if (comma -1) { //Serial.print(ERROR: missing comma\r\n); return; } String portStr params.substring(comma 1); portStr.trim(); int port portStr.toInt(); if (port 0) { //Serial.print(ERROR: invalid port number\r\n); return; } /* 打印解析结果方便调试 */ //Serial.print(Connecting to MQTT server: ); //Serial.print(host); /Serial.print(:); Serial.println(port); // 进行实际的 MQTT 连接 if (connectToMqtt(host, port)) { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(15); Serial.print(:); Serial.print(MQTT Connected!); Serial.print(\r\n); } else { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(23); Serial.print(:); Serial.print(MQTT Connection Failed!); Serial.print(\r\n); } } void AT_MQTTSUB(String inputString) { // 1. 检查指令前缀 if (!inputString.startsWith(ATMQTTSUB)) { //Serial.print(ERROR: invalid command\r\n); return; } // 2. 检查 MQTT 是否已连接 if (!mqttConnected || !mqttClient.connected()) { //Serial.print(ERROR: MQTT not connected\r\n); return; } // 3. 提取参数部分 (ATMQTTSUB 的长度为 10) String params inputString.substring(10); // 4. 查找双引号提取主题 int firstQuote params.indexOf(); if (firstQuote -1) { //Serial.print(ERROR: missing topic quote\r\n); return; } int secondQuote params.indexOf(, firstQuote 1); if (secondQuote -1) { //Serial.print(ERROR: missing closing topic quote\r\n); return; } String topic params.substring(firstQuote 1, secondQuote); // 5. 检查主题是否为空 if (topic.length() 0) { //Serial.print(ERROR: empty topic\r\n); return; } // 6. 执行订阅 if (mqttClient.subscribe(topic.c_str(), 1)) { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(17); Serial.print(:); Serial.print(Subscribe Success!); Serial.print(\r\n); } else { // 按照 AT 指令格式输出IPD,len:data Serial.print(IPD,); Serial.print(16); Serial.print(:); Serial.print(Subscribe Failed!); Serial.print(\r\n); } } void AT_MQTTDISCONNECT(String inputString) { // 检查指令前缀忽略后面的任何内容 if (!inputString.startsWith(ATMQTTDISCONNECT)) { //Serial.print(ERROR: invalid command\r\n); return; } // 检查当前 MQTT 是否已连接 if (!mqttConnected !mqttClient.connected()) { //Serial.print(ERROR: MQTT not connected\r\n); return; } // 断开MQTT连接 mqttClient.disconnect(); // 强制关闭底层TCP连接disconnect通常会自动关闭但为保险可调用 espClient.stop(); // 更新连接状态标志 mqttConnected false; // 返回成功响应 //Serial.print(OK\r\n); } void AT_work(String inputString){ // 连接Wi-Fi网络的AT指令 int pos inputString.indexOf(ATCWJAP); if(pos ! -1) // 判断是否为该AT指令 { AT_CWJAP(inputString); return; } // 与服务器建立连接的AT指令 pos inputString.indexOf(ATHTTPCONNECT); if(pos ! -1) // 判断是否为该AT指令 { AT_HTTPCONNECT(inputString); return; } // 向服务器发送请求报文的AT指令 pos inputString.indexOf(ATHTTPREQ); if(pos ! -1) // 判断是否为该AT指令 { AT_HTTPREQ(inputString); return; } // 与服务器的连接断开的AT指令 pos inputString.indexOf(ATHTTPSTOP); if(pos ! -1) // 判断是否为该AT指令 { AT_HTTPSTOP(inputString); return; } // 连接MQTT服务器的AT指令 pos inputString.indexOf(ATMQTTCONNECT); if(pos ! -1) // 判断是否为该AT指令 { AT_MQTTCONNECT(inputString); return; } // 订阅主题的AT指令 pos inputString.indexOf(ATMQTTSUB); if(pos ! -1) // 判断是否为该AT指令 { AT_MQTTSUB(inputString); return; } // 断开与MQTT服务器的连接 pos inputString.indexOf(ATMQTTDISCONNECT); if(pos ! -1) // 判断是否为该AT指令 { AT_MQTTDISCONNECT(inputString); return; } }四、开发用户端STM321、准备工作1拷贝一份STM32教程中按键控制LED的工程文件夹并更名为“A区支持OTA实验工程”。

相关文章:

基于STM32实现OTABootLoader 第五章——OTA功能开发【下】

三、开发客户端ESP82661、自定义AT指令集(1)实际上,Wi-Fi模块通常配有官方的AT指令集,只要刷写官方提供的固件即可(但可能功能不全),不过,AT指令本身就是基于串口通信实现的一种指令…...

从一次现场故障说起:如何通过分析三相变压器感应电动势的谐波来预判铁芯隐患?

三相变压器谐波诊断实战:从波形异常到铁芯隐患精准预判 去年夏天,某220kV变电站的主变在例行巡检中被发现输出电压波形出现明显畸变——这本是电力运维中常见的"小异常",但当我们深入分析谐波成分后,却揭露出一个潜在的…...

人声分离实战指南:从UVR、Demucs到Spleeter的模型选型与场景适配

1. 人声分离技术入门:为什么我们需要它? 第一次接触人声分离技术是在去年帮朋友做婚礼视频的时候。当时需要把现场嘈杂的背景音和人声分开,试了各种音频编辑软件都没法完美解决,直到发现了这些开源工具。简单来说,人声…...

SpringBoot-基础面试篇

什么是 Spring Boot?Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,使开发者能快速上手。为什么要用Spring…...

光学工程师进阶指南:从入门到精通的实战路径

1. 光学工程师的职业发展路径 光学工程师的成长就像搭积木,需要从最基础的模块开始,一层层往上搭建。我刚入行时也走过不少弯路,后来才明白这个职业的发展是有明确路径的。一般来说,我们可以把成长过程分为三个阶段:初…...

云原生下的PostgreSQL高可用实战:在K8s里用StatefulSet和Patroni API告别VIP和HAProxy

云原生时代的PostgreSQL高可用架构:基于Kubernetes与Patroni的实践指南 当企业的数据库基础设施全面转向云原生环境时,传统基于虚拟机的高可用方案显得格格不入。在Kubernetes生态中,StatefulSet控制器和Patroni的Kubernetes原生集成让我们能…...

知网维普都要过,AI率85%用哪款工具最合适

越来越多高校开始同时要求知网和维普检测,这让选工具变得更复杂了——不是只要过一个平台,而是要同时达标。 AI率85%,知网和维普都要过20%以下,这种情况用哪款工具最合适? 知网和维普的算法差异 先说一个背景知识&a…...

知网检测AI率90%,我用这个方法两天降到12%

三月底,距离论文提交还有8天,知网AIGC检测报告出来了:AI率90%。 我当时的反应就是愣在那里。90%,这意味着几乎整篇论文都被标红了。后来用两天时间,把AI率降到了12%。今天把这个过程完整记录下来,因为我知…...

预算有限AI率还有80%,性价比最高的降AI方案

AI率80%,但预算只有100-200元,怎么处理? 这是一个真实存在的困境。不同工具的定价差异很大,预算不够时怎么取舍,怎么用最少的钱解决问题? 这篇文章给出不同预算下的最优方案。 先了解各工具定价 工具定…...

比话降AI和嘎嘎降AI处理80%+AI率哪个更好

比话降AI和嘎嘎降AI是目前市面上处理极高AI率最有效的两款工具,但很多人不知道该选哪个。 这篇文章做一个直接的对比:两款工具在AI率80%场景下,各有什么优势和劣势,你的情况适合哪个。 基础信息对比 项目比话降AI嘎嘎降AI官网b…...

比话降AI实测:AI率87%的论文降到11%全程记录

这篇是比话降AI的真实使用记录,不是广告软文,是我帮朋友处理论文的完整过程。 朋友的情况:研究生论文,4.2万字,知网AIGC检测87%,距离提交截止7天。 为什么选比话降AI 比话降AI(www.bihuapass…...

抖音无水印视频批量下载器深度解析:从架构设计到实战应用

抖音无水印视频批量下载器深度解析:从架构设计到实战应用 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback su…...

Source Han Serif CN:开源宋体的技术特性与跨场景应用指南

Source Han Serif CN:开源宋体的技术特性与跨场景应用指南 【免费下载链接】source-han-serif-ttf Source Han Serif TTF 项目地址: https://gitcode.com/gh_mirrors/so/source-han-serif-ttf 一、技术特性深度剖析 1.1 字体技术架构解析 Source Han Serif…...

hsjdvfjfgdhdydh

一、OpenAI 1.OpenAI是什么简单来说,OpenAI 大模型 是由美国人工智能公司 OpenAI 开发的一系列大型语言模型(LLMs) 。你可以把它们想象成拥有巨大“知识储备”和“学习能力”的超级大脑,它们被训练用来理解和生成人类语言&#xf…...

akdbdudhdhfvf

一、OpenAI 1.OpenAI是什么简单来说,OpenAI 大模型 是由美国人工智能公司 OpenAI 开发的一系列大型语言模型(LLMs) 。你可以把它们想象成拥有巨大“知识储备”和“学习能力”的超级大脑,它们被训练用来理解和生成人类语言&#xf…...

ToClaw把AI自动化门槛降到零?先看清它到底解决了什么,没解决什么

先说结论ToClaw的核心价值在于封装了OpenClaw的复杂部署与Token计费,通过云端算力和签到积分机制,让非技术用户也能快速体验AI自动化。它更适合处理文件整理、定时任务、文档生成等中低复杂度场景,但在需要深度自定义或私有化部署的高阶需求上…...

COMSOL热应力仿真新手入门:从零开始设置热膨胀参数(附案例解析)

COMSOL热应力仿真新手入门:从零开始设置热膨胀参数(附案例解析) 热应力仿真是工程设计中不可或缺的一环,尤其在电子设备散热、航空航天材料分析等领域应用广泛。作为COMSOL Multiphysics的初学者,掌握热应力仿真的基础…...

Unity WebGL小游戏上抖音,从踩坑到上线:一份避坑指南与性能优化清单

Unity WebGL小游戏上抖音:性能优化与避坑实战手册 当你第一次将Unity WebGL小游戏发布到抖音平台时,可能会遇到各种意想不到的性能瓶颈和兼容性问题。iOS设备上的内存限制、WebGL与Native的性能差距、包体大小控制等挑战,都可能让原本流畅的游…...

Harbor集成Trivy实现镜像安全扫描:从安装到离线环境配置全指南

1. 为什么需要Harbor集成Trivy进行镜像安全扫描 在容器化部署成为主流的今天,一个NGINX镜像可能隐藏着数十个安全漏洞而不自知。去年某金融公司就曾因为使用了存在高危漏洞的Redis镜像,导致数据泄露事件。这种案例让我深刻意识到:镜像安全扫描…...

电赛赛题深度解析:从五大类别到实战备赛策略

1. 电赛赛题五大类别全解析 全国大学生电子设计竞赛(简称电赛)作为电子类专业最具影响力的赛事,其赛题设置直接反映了行业技术发展趋势。经过对近十年赛题的统计分析,所有题目可明确划分为五大类别,每类都有独特的考察…...

C#并行编程进阶:除了Task和Parallel,你还需要学会用PerformanceCounter做资源熔断

C#并行编程中的资源熔断机制:用PerformanceCounter构建自适应系统 当你在深夜部署一个高负载数据处理服务时,最可怕的不是代码报错——而是系统在默默崩溃。我曾经历过这样的时刻:一个看似完美的并行处理管道,在凌晨三点突然吞噬了…...

Vivado DDS IP核的‘光栅化’模式详解:告别相位噪声,提升信号纯度的秘密

Vivado DDS IP核的‘光栅化’模式深度解析:高纯度信号生成的工程实践 在FPGA数字信号处理领域,直接数字频率合成(DDS)技术因其频率分辨率高、切换速度快等优势,已成为雷达系统、通信设备和测试仪器中的核心模块。Xilin…...

当PLC网口IP丢了怎么办?用Wireshark抓LLDP包,免费找回施耐德M580的地址

工业现场急救指南:用Wireshark找回施耐德M580 PLC的失踪IP地址 那天下午三点,工厂生产线突然停机,监控系统显示PLC通讯中断。当我冲到控制柜前,发现前任工程师留下的文档里,M580的IP地址记录栏赫然写着"见设备标签…...

告别硬编码:用SqlSugar Expression动态构建多条件Left Join查询(附分页技巧)

告别硬编码:用SqlSugar Expression动态构建多条件Left Join查询(附分页技巧) 在后台管理系统开发中,数据列表查询是最常见的需求之一。面对复杂的多表关联、动态筛选条件和分页需求,很多开发者会陷入字符串拼接SQL的泥…...

Android 14 ShellTransitions 实战:手把手教你理解 Transition 如何“抓取”动画参与者(WindowContainer 篇)

Android 14 ShellTransitions 深度解析:WindowContainer 动画参与者捕获机制实战指南 在 Android 14 的动画框架革新中,ShellTransitions 引入了一套精密的"参与者捕获"系统,其运作机制堪比特种部队的精准行动。本文将带您深入这套…...

若依微服务版实战:5分钟搞定积木报表1.5.6集成与权限控制

若依微服务版深度整合积木报表1.5.6全流程指南 1. 环境准备与架构设计 在微服务架构中引入报表模块需要特别注意服务边界和资源隔离。积木报表1.5.6作为一款企业级Web报表工具,其与若依微服务版的整合涉及以下几个核心层面: 服务独立性:建议将…...

毕业党速看:这款 AI 论文神器太疯狂,输入标题直接生成万字长文

赶 due 党、论文特困生直接狂喜!谁懂啊家人们,以前写论文从选题到憋出万字初稿,至少得熬半个月,现在输入一个论文标题,短短 20 分钟就能自动生成结构完整、逻辑通顺、带真实参考文献的万字长文,从摘要、引言…...

智能应急灯V16:多场景照明解决方案

目录 一、方案概述 二、硬件方案设计 2.1 硬件整体架构 2.2 核心模块选型与设计 2.2.1 主控模块(核心单元) 2.2.2 电源管理模块(供电核心) 2.2.3 照明驱动模块 2.2.4 状态监测模块 2.2.5 通信模块(可选&#…...

GitHub中文插件终极指南:3分钟让GitHub界面全面中文化

GitHub中文插件终极指南:3分钟让GitHub界面全面中文化 【免费下载链接】github-chinese GitHub 汉化插件,GitHub 中文化界面。 (GitHub Translation To Chinese) 项目地址: https://gitcode.com/gh_mirrors/gi/github-chinese 你是否曾经因为GitH…...

ncmdumpGUI高效使用指南:NCM文件转换完全掌握

ncmdumpGUI高效使用指南:NCM文件转换完全掌握 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换,Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 一、建立NCM转换认知体系 1.1 理解NCM文件加密机制 …...