esp32课设记录(四)摩斯密码的实现 并用mqtt上传
摩斯密码(Morse Code)是一种通过点(.)和划(-)组合来表示字符的编码系统。下面我将在esp32上实现摩斯密码的输入,并能够发送到mqtt的broker。
先捋一下逻辑,首先esp32的按键已经编写了短按与长按功能,这将是输出摩斯密码点和划的基础。然后当2s没有新输入,我们就认为输入完了一个字符,自动识别即可。但是怎么发送到mqtt呢?我打算选用一种摩斯密码里没有的,并且又容易记忆的字符——6个点。
于是逻辑图如下:有了逻辑,编写代码就很容易了。
先编写一个密码表:
// 摩尔斯码表 - 常见字符
static const struct
{char character;const char *morse;
} morse_table[] = {{'A', ".-"},{'B', "-..."},{'C', "-.-."},{'D', "-.."},{'E', "."},{'F', "..-."},{'G', "--."},{'H', "...."},{'I', ".."},{'J', ".---"},{'K', "-.-"},{'L', ".-.."},{'M', "--"},{'N', "-."},{'O', "---"},{'P', ".--."},{'Q', "--.-"},{'R', ".-."},{'S', "..."},{'T', "-"},{'U', "..-"},{'V', "...-"},{'W', ".--"},{'X', "-..-"},{'Y', "-.--"},{'Z', "--.."},{'0', "-----"},{'1', ".----"},{'2', "..---"},{'3', "...--"},{'4', "....-"},{'5', "....."},{'6', "-...."},{'7', "--..."},{'8', "---.."},{'9', "----."},{' ', "/"}, // 空格用"/"表示{'\0', NULL} // 结束标记
};
定义摩斯密码的输入状态与输入类型。
// 摩尔斯码输入状态
typedef enum
{MORSE_IDLE, // 空闲状态MORSE_INPUT, // 输入中MORSE_CHAR_COMPLETE // 字符输入完成
} morse_state_t;// 摩尔斯码输入类型
typedef enum
{MORSE_DOT, // 点 (.)MORSE_DASH, // 划 (-)MORSE_GAP // 间隔 (表示字符结束)
} morse_input_t;
编写宏定义与变量
#define MORSE_CHAR_TIMEOUT 2000 // 字符超时时间(毫秒)
#define MORSE_MAX_BUFFER 32 // 最大缓冲区大小。限制当前正在输入的摩尔斯码序列(点和划)的最大长度
#define MORSE_OUTPUT_MAX 64 // 输出缓冲区最大大小。限制已解码文本的最大长度// 当前摩尔斯码输入
static morse_state_t morse_state = MORSE_IDLE;//跟踪摩尔斯码输入的当前状态
static char current_morse[MORSE_MAX_BUFFER] = {0};//存储当前正在输入的摩尔斯码序列
static char decoded_text[MORSE_OUTPUT_MAX] = {0};//存储已解码的完整文本
static int64_t last_input_time = 0;//记录最后一次用户输入的时间戳(毫秒)。用于检测输入超时,实现自动字符完成功能。通过与当前时间比较,判断是否超过了MORSE_CHAR_TIMEOUT
相应的编写各种功能函数:
// 获取当前时间戳(毫秒)
static int64_t get_current_time_ms(void)
{return esp_timer_get_time() / 1000;
}// 初始化摩尔斯码模块
void morse_init(void)
{morse_reset();
}// 添加一个摩尔斯码符号
void morse_add_symbol(morse_input_t symbol)
{size_t len = strlen(current_morse);// 防止缓冲区溢出if (len >= MORSE_MAX_BUFFER - 2){return;}// 添加符号switch (symbol){case MORSE_DOT:current_morse[len] = '.';current_morse[len + 1] = '\0';break;case MORSE_DASH:current_morse[len] = '-';current_morse[len + 1] = '\0';break;case MORSE_GAP:// 解码当前字符char decoded = morse_decode_current();if (decoded != '\0'){size_t decoded_len = strlen(decoded_text);if (decoded_len < MORSE_OUTPUT_MAX - 1){decoded_text[decoded_len] = decoded;decoded_text[decoded_len + 1] = '\0';}}// 重置当前输入current_morse[0] = '\0';break;}// 更新状态和时间戳morse_state = (symbol == MORSE_GAP) ? MORSE_CHAR_COMPLETE : MORSE_INPUT;last_input_time = get_current_time_ms();
}// 检查是否需要完成当前字符
bool morse_check_timeout(int64_t current_time)
{// 如果有输入且超时,则完成当前字符if (morse_state == MORSE_INPUT &&strlen(current_morse) > 0 &&(current_time - last_input_time) > MORSE_CHAR_TIMEOUT){morse_add_symbol(MORSE_GAP);return true;}return false;
}// 解码当前的摩尔斯码
char morse_decode_current(void)
{if (strlen(current_morse) == 0){return '\0';}// 尝试在摩尔斯码表中查找for (int i = 0; morse_table[i].morse != NULL; i++){if (strcmp(current_morse, morse_table[i].morse) == 0){return morse_table[i].character;}}// 如果找不到匹配项return '?';
}// 重置摩尔斯码输入
void morse_reset(void)
{morse_state = MORSE_IDLE;current_morse[0] = '\0';decoded_text[0] = '\0';last_input_time = get_current_time_ms();
}// 获取当前摩尔斯码字符串
const char *morse_get_current(void)
{return current_morse;
}// 获取已解码的字符串
const char *morse_get_decoded(void)
{return decoded_text;
}// 为显示准备摩尔斯码和解码结果
void morse_prepare_display(char *buffer, int size)
{// 拼接解码结果和当前输入的摩尔斯码snprintf(buffer, size, "解码: %s\n当前: %s",strlen(decoded_text) > 0 ? decoded_text : "[空]",strlen(current_morse) > 0 ? current_morse : "[等待输入]");
}// 检查当前输入是否为6个连续的点
bool morse_is_six_dots(void)
{// 检查当前输入是否为"......"(6个点)if (strlen(current_morse) == 6){// 逐个检查是否都是点for (int i = 0; i < 6; i++){if (current_morse[i] != '.'){return false;}}return true;}return false;
}
在主函数调用先调用初始化。
// 初始化摩尔斯码模块morse_init();
在while循环里面,首先加入超时处理机制,2s没有再输入便直接解码,并在屏幕上显示。
// 获取当前时间int64_t current_time = esp_timer_get_time() / 1000;// 检查摩尔斯码输入超时if (current_mode == MODE_MORSE){if (morse_check_timeout(current_time)){// 超时处理,更新显示char morse_display[128];morse_prepare_display(morse_display, sizeof(morse_display));clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);draw_string(lcd_buffer, 0, 0, "摩尔斯电码模式", 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT + 2, morse_display, 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 4 + 8, "短按: 输入点(.)", 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 5 + 10, "长按: 输入划(-)", 0xFFFF, LCD_H_RES);esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);}}
在按键短按的触发函数里面,按照前面的逻辑框图写带代码:先加入一个点,在判断是不是特殊消息(6个点),是的话直接用mqtt发送目前解析好的字符串,并重置,在屏上显示消息。如果不是,则正常更新显示,等待下一个输入。
// 短按输入"点"(.)morse_add_symbol(MORSE_DOT);// 检查是否为连续6个点if (morse_is_six_dots() && mqtt_connected){// 发送解码后的摩尔斯码消息到MQTTchar message[50];const char *decoded = morse_get_decoded();// 如果有解码结果,发送它,否则发送当前的摩尔斯码符号if (strlen(decoded) > 0){sprintf(message, "Morse: %s", decoded);}else{sprintf(message, "Morse: ......");}esp_mqtt_client_publish(mqtt_client, MQTT_PUBLISH_TOPIC, message, 0, 1, 0);// 重置摩尔斯码输入并显示发送成功信息morse_reset();clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);draw_string(lcd_buffer, 0, 0, "摩尔斯电码模式", 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT + 2, "信号已发送", 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 4 + 8, "短按: 输入点(.)", 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 5 + 10, "长按: 输入划(-)", 0xFFFF, LCD_H_RES);esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);// 延迟1秒让用户看到发送成功消息vTaskDelay(1000 / portTICK_PERIOD_MS);}else{// 正常更新显示char morse_display[128];morse_prepare_display(morse_display, sizeof(morse_display));clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);draw_string(lcd_buffer, 0, 0, "摩尔斯电码模式", 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT + 2, morse_display, 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 4 + 8, "短按: 输入点(.)", 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 5 + 10, "长按: 输入划(-)", 0xFFFF, LCD_H_RES);esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);}
长按直接添加划,并更新显示就好了。
// 长按输入"划"(-)morse_add_symbol(MORSE_DASH);// 更新显示char morse_display[128];morse_prepare_display(morse_display, sizeof(morse_display));clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);draw_string(lcd_buffer, 0, 0, "摩尔斯电码模式", 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT + 2, morse_display, 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 4 + 8, "短按: 输入点(.)", 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 5 + 10, "长按: 输入划(-)", 0xFFFF, LCD_H_RES);esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);break;
ok,代码写完了,让我们试试效果。
收到了。
相关文章:

esp32课设记录(四)摩斯密码的实现 并用mqtt上传
摩斯密码(Morse Code)是一种通过点(.)和划(-)组合来表示字符的编码系统。下面我将在esp32上实现摩斯密码的输入,并能够发送到mqtt的broker。 先捋一下逻辑,首先esp32的按键已经编写了短按与长按功能,这将是输出摩斯密码点和划的基础。然后当2…...

「HHT(希尔伯特黄变换)——ECG信号处理-第十三课」2025年5月19日
一、引言 心电信号(ECG)是反映心脏电活动的重要生理信号,其特征提取对于心脏疾病的诊断和监测具有关键意义。Hilbert - Huang Transform(HHT)作为一种强大的信号处理工具,在心电信号特征提取领域得到了广泛…...

前端(vue)学习笔记(CLASS 6):路由进阶
1、路由的封装抽离 将之前写在main.js文件中的路由配置与规则抽离出来,放置在router/index.js文件中,再将其导入回main.js文件中,即可实现路由的封装抽离 例如 //index.js import { createMemoryHistory, createRouter } from vue-routerim…...

GPT-4.1特点?如何使用GPT-4.1模型,GPT-4.1编码和图像理解能力实例展示
几天前,OpenAI在 API 中推出了三个新模型:GPT-4.1、GPT-4.1 mini 和 GPT-4.1 nano。这些模型的性能全面超越 GPT-4o 和 GPT-4o mini(感觉这个GPT-4.1就是GPT-4o的升级迭代版本),主要在编码和指令跟踪方面均有显著提升。还拥有更大的上下文窗口…...
使用Python和FastAPI构建网站爬虫:Oncolo医疗文章抓取实战
使用Python和FastAPI构建网站爬虫:Oncolo医疗文章抓取实战 前言项目概述技术栈代码分析1. 导入必要的库2. 初始化FastAPI应用3. 定义请求模型4. 核心爬虫功能4.1 URL验证和准备4.2 设置HTTP请求4.3 发送请求和解析HTML4.4 提取文章内容4.5 保存结果和返回数据 5. AP…...
写一段图片平移的脚本
问题描述: 写一段图片平移的脚本。 平移就是将对象换一个位置。如果你要沿方向移动,移动的距离是,你可以以下面的方式构建移动矩阵:。 你可以使用Numpy 数组构建这个矩阵(数据类型是np.float32)…...

【C++】哈希的概念与实现
1.哈希概念 通过某种函数使元素的存储位置与它的关键码之间能够建立一一映射的关系,可以不经过任何比较,一次直接从表中得到要搜索的元素。 当向该结构中: 插入元素: 根据待插入元素的关键码,以此函数计算出该元素的…...

Yocto和Buildroot功能和区别
一.介绍 Yocto 和 Buildroot 都是用于嵌入式 Linux 系统开发的工具集,它们的目的是帮助开发者轻松构建定制的 Linux 系统镜像,以便在嵌入式设备上运行。 二.对比 1.Yocto Yocto 是一个开源的嵌入式 Linux 构建系统,它允许开发者创建自定义…...
物联网数据湖架构
物联网海量数据湖分析架构(推荐实践) ┌──────────────┐ │ IoT设备端 │ └──────┬───────┘│(MQTT/HTTP)▼ ┌──────────────┐ │ EMQX等 │ 可选(也可…...

详解RabbitMQ工作模式之发布订阅模式
目录 发布订阅模式 概念 概念介绍 特点和优势 应用场景 注意事项 代码案例 引入依赖 常量类 编写生产者代码 编写消费者1代码 运行代码 发布订阅模式 概念 RabbitMQ的发布订阅模式(Publish/Subscribe)是一种消息传递模式,它允许消…...
什么是子网委派?
Azure 子网委派的概念 子网委托使您能够为所选的 Azure PaaS 服务指定一个特定的子网,并将其注入到您的虚拟网络中。子网委托为客户提供了完全的控制权,可以管理 Azure 服务与其虚拟网络的集成。 当您将子网委托给 Azure 服务时,您允许该服务为该子网建立一些基本的网络配…...

微信学习之导航功能
先看这个功能的效果: 然后开始学习吧。 一、我们这里用的是vant的Grid控件,首先我们导入: { "usingComponents": {"van-search": "vant/weapp/search/index","my-swiper":"../../components…...

城市内涝监测预警系统守护城市安全
一、系统背景 城市内涝是指由于强降水或连续性降水超过城市排水能力,导致城市内产生积水灾害的现象。随着气候变化和城市化进程的加快,城市内涝现象愈发频繁和严重。传统的城市排水系统已难以满足当前的城市排水需求,特别是在暴雨等极端天气条…...

用 CodeBuddy 搭建「MiniGoal 小目标打卡器」:一次流畅的 UniApp 开发体验
我正在参加CodeBuddy「首席试玩官」内容创作大赛,本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 在日常生活中,我们总是希望能够坚持一些小习惯,比如每天锻炼十分钟、读一页书、早睡十分…...

Web技术与Nginx网站环境部署
目录 一.web基础 1.域名和DNS (1).域名的概念 (2).hosts文件 (3).DNS (4).域名注册 2.网页与HTML (1).网页简介 (2).HTML &a…...

AI移动监测:仓储环境安全的“全天候守护者”
AI移动监测在仓储方面的应用:技术赋能与场景突破 一、背景:仓储环境的“隐形威胁”与AI破局 仓储行业长期面临设备损坏、货物损失、卫生隐患等风险。传统监控依赖人工巡检或固定摄像头,难以实时捕捉动态风险。例如: 动物入侵&a…...
【数据库】数据库故障排查指南
数据库故障排查指南 数据库连接问题 检查数据库服务是否正常运行,确认网络连接是否畅通,验证数据库配置文件的正确性,确保防火墙或安全组规则允许数据库端口的访问。 性能问题 分析慢查询日志,优化SQL语句,检查索引…...
mariadb 升级 (通过yum)
* 注意下 服务名, 有的服务器上是mysql,有的叫mariadb,mysqld的 #停止 systemctl stop mysql #修改源 vi /etc/yum.repos.d/MariaDB.repo baseurl http://yum.mariadb.org/11.4/centos7-amd64 #卸载 yum remove mysql #安装 yum install MariaDB-server galera-4 MariaDB-…...

2025年5月华为H12-821新增题库带解析
IS-IS核心知识 四台路由器运行IS-IS且已经建立邻接关系,区域号和路由器的等级如图中标记,下列说法中正确的有? R2和R3都会产生ATT置位的Level-1的LSPR1没有R4产生的LSP,因此R1只通过缺省路由和R4通信R2和R3都会产生ATT置位的Leve1-2的LSPR2和…...

用 python 编写的一个图片自动分类小程序(三)
图片自动分类识别小程序记录 2025/5/18 0:38修改程序界面,增加一些功能 用 python 编写的一个图片自动识别分类小程序。 操作系统平台:Microsoft Windows 11 编程语言和 IDE:python 3.10 Visual studio code 一:图片自动分…...
用户行为日志分析的常用架构
## 1. 经典Lambda架构 Lambda架构是一种流行的大数据处理架构,特别适合用户行为日志分析场景。 ### 1.1 架构组成 Lambda架构包含三层: - **批处理层(Batch Layer)**: 存储全量数据并进行离线批处理 - **实时处理层(Speed Layer)**: 处理最新数据&…...

初识 java
目录 前言 一、jdk,JRE和JVM之间的关系 二、JVM的内存划分 前言 初步了解 jdk,JRE,JVM 之间的关系,JVM 的内存划分。 一、jdk,JRE和JVM之间的关系 jdk 是 java 开发工具集,包含JRE; JRE 是…...
3D 数据交换格式(.3DXML)简介
3DXML 是一种基于 XML 的 3D 数据交换格式,由达索系统(Dassault Systmes)开发,主要用于其 CATIA、SOLIDWORKS 和 3DEXPERIENCE 等产品中。 基本概述 全称:3D XML开发者:达索系统主要用途:3D…...

frida 配置
1.环境 1.1 下载 frida-server firda-server github下载地址 这边推荐使用最新版的上一个版本 根据虚拟机自行选择版本 我使用这个版本 frida-server-16.7.17-android-x86_64 1.2 启动 frida-server-16.7.17-android-x86_64 将文件解压至虚拟机目录 使用adb命令执行 chmo…...

16-看门狗和RTC
一、独立看门狗 1、独立看门狗概述 在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞(不按照正常程序进行运行,如程序重启,但是如果我们填加看门狗的技术࿰…...

【AI论文】用于评估和改进大型语言模型中指令跟踪的多维约束框架
摘要:接下来的指令评估了大型语言模型(LLMs)生成符合用户定义约束的输出的能力。 然而,现有的基准测试通常依赖于模板化的约束提示,缺乏现实使用的多样性,并限制了细粒度的性能评估。 为了填补这一空白&…...
AUTOSAR图解==>AUTOSAR_SRS_TimeService
AUTOSAR TimeService模块详解 AUTOSAR经典平台时间服务分析与图解 目录 1. 概述2. TimeService架构分析 2.1 模块位置与组件关系2.2 模块功能职责3. TimeService组件结构 3.1 预定义定时器类型3.2 时间函数功能3.3 与GPT驱动关系4. TimeService定时器实例 4.1 实例数据结构4.2 …...
设计模式的原理及深入解析
创建型模式 创建型模式主要关注对象的创建过程,旨在通过不同的方式创建对象,以满足不同的需求。 工厂方法模式 定义:定义一个创建对象的接口,让子类决定实例化哪一个类。 解释:工厂方法模式通过定义一个创建对象的…...

Chromium 浏览器核心生命周期剖析:从 BrowserProcess 全局管理到 Browser 窗口实例
在 Chromium 浏览器架构中,BrowserProcess 和 Browser 是两个核心类,分别管理 浏览器进程的全局状态 和 单个浏览器窗口的实例。它们的生命周期设计直接影响浏览器的稳定性和资源管理。以下是它们的详细生命周期分析: 1. BrowserProcess 的生…...

易境通海外仓系统:一件代发全场景数字化解决方案
随着全球经济一体化和消费升级,一件代发业务的跨境电商市场规模持续增长。然而,一件代发的跨境运营也面临挑战,传统海外仓管理模式更因效率低下、协同困难成为业务扩张的瓶颈。 一、一件代发跨境运营痛点 1、多平台协同:卖家往往…...