拓冰建站拓冰建站
首页 / 资讯中心 / 正文

嵌入式点阵屏36进制显示:算法实现与工程实践

最近在做一个复古风格的蒸汽火车模拟项目时遇到了一个有趣的显示需求如何在有限的点阵屏上高效地展示车厢编号、速度、压力等内部信息。传统的十进制数字位数多占用空间大而36进制0-9, A-Z能以更短的字符串表示更大的数值范围非常适合这种资源受限的嵌入式场景。本文将详细拆解如何为“蒸汽火车内部信息点阵显示”实现36进制的转换与显示从核心算法、代码实现到点阵渲染提供一个完整的、可复用的解决方案。1. 背景与核心概念为什么选择36进制在嵌入式或复古风格的显示设备上点阵屏的像素资源非常宝贵。例如一个8x8的点阵模块显示数字“1024”需要4个字符位而如果使用36进制“1024”的十进制值对应的36进制是“SG”仅需2个字符位。这直接节省了50%的显示空间使得在同一个屏幕上可以同时展示更多信息如“车厢: SG | 速度: 1A | 压力: 2K”。36进制Base-36是一种以36为基数的计数系统。它使用数字0-9和英文字母A-Z不区分大小写通常用大写来表示数值其中A代表10B代表11……Z代表35。一个36进制的字符串可以转换为一个十进制整数反之亦然。应用场景对比十进制 (Base-10):“车厢号: 1024” - 显示需要4个字符位。三十六进制 (Base-36):“车厢号: SG” - 显示仅需2个字符位。优势:在点阵屏、LED屏或任何宽度受限的显示器上36进制能显著压缩信息长度提升信息密度同时保留人类可读性比纯二进制友好得多。2. 环境准备与版本说明本项目不依赖特定框架核心是算法因此具有很高的语言通用性。下面以Python 3.8和Arduino C常用于嵌入式点阵屏控制两种典型环境为例进行说明。你可以根据你的实际硬件如ESP32、STM32驱动MAX7219点阵模块或软件模拟环境进行选择。Python 环境:用于算法验证、模拟和上位机测试。确保安装Python 3.8或更高版本。Arduino/C 环境:用于最终部署到嵌入式硬件。需要Arduino IDE或PlatformIO以及对应的点阵屏驱动库如MD_MAX72xx或LedControl。核心工具:代码编辑器VS Code, PyCharm, Arduino IDE、串口调试工具。示例项目结构 (Python视角):steam_train_display/ ├── base36_converter.py # 36进制转换核心模块 ├── font_8x8.py # 8x8点阵字体定义包含0-9, A-Z ├── matrix_simulator.py # 控制台模拟点阵显示 └── main.py # 主程序整合转换与显示3. 核心算法十进制与36进制互转这是整个功能的核心。我们将编写一个通用的工具类/函数。3.1 算法原理十进制转36进制 (encode):不断将十进制数除以36取余数作为最低位然后用商继续除以36直到商为0。将余数0-35映射到字符0-9和A-Z。36进制转十进制 (decode):从字符串最高位最左端开始将每个字符映射回数字0-35然后执行decimal decimal * 36 digit的累加。3.2 Python 实现创建一个base36_converter.py文件。# base36_converter.py class Base36Converter: # 定义36进制字符集 _BASE36_CHARS 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ staticmethod def encode(num: int) - str: 将十进制正整数转换为36进制字符串。 :param num: 十进制正整数 :return: 36进制字符串 if num 0: return 0 if num 0: # 根据需求可以处理负数例如加上前缀。这里简单返回空或抛异常。 # 为了简单本项目假设车厢号、速度均为非负。 raise ValueError(编码函数暂时只支持非负整数) result [] while num 0: num, remainder divmod(num, 36) result.append(Base36Converter._BASE36_CHARS[remainder]) # 因为计算是从低位到高位所以需要反转 return .join(reversed(result)) staticmethod def decode(base36_str: str) - int: 将36进制字符串转换为十进制整数。 :param base36_str: 有效的36进制字符串仅包含0-9, A-Z :return: 十进制整数 base36_str base36_str.upper().strip() if not base36_str: return 0 decimal 0 for char in base36_str: if char not in Base36Converter._BASE36_CHARS: raise ValueError(f无效的36进制字符: {char}) decimal decimal * 36 Base36Converter._BASE36_CHARS.index(char) return decimal # 快速使用示例 if __name__ __main__: converter Base36Converter() test_num 1024 encoded converter.encode(test_num) decoded converter.decode(encoded) print(f十进制 {test_num} - 36进制 {encoded}) print(f36进制 {encoded} - 十进制 {decoded}) # 输出: 十进制 1024 - 36进制 SG # 输出: 36进制 SG - 十进制 10243.3 Arduino C 实现对于嵌入式设备我们需要一个轻量级的实现。创建一个Base36.h头文件。// Base36.h #ifndef BASE36_H #define BASE36_H #include Arduino.h class Base36 { public: // 十进制转36进制 static String encode(unsigned long num) { if (num 0) { return 0; } String result ; while (num 0) { int remainder num % 36; result charToBase36(remainder) result; // 注意拼接顺序 num / 36; } return result; } // 36进制转十进制 static unsigned long decode(const String base36Str) { String str base36Str; str.toUpperCase(); unsigned long decimal 0; for (int i 0; i str.length(); i) { char c str.charAt(i); int digit base36CharToInt(c); if (digit -1) { // 非法字符可根据需求处理这里返回0 return 0; } decimal decimal * 36 digit; } return decimal; } private: static char charToBase36(int digit) { if (digit 0 digit 9) { return 0 digit; } else if (digit 10 digit 35) { return A (digit - 10); } return 0; // 不应该发生 } static int base36CharToInt(char c) { if (c 0 c 9) { return c - 0; } else if (c A c Z) { return 10 (c - A); } return -1; // 非法字符 } }; #endif4. 点阵字体设计与显示有了36进制字符串下一步就是如何在点阵屏上显示它。我们需要一个包含数字0-9和字母A-Z的字体库。4.1 设计8x8点阵字体一个8x8的点阵可以用一个8字节的数组表示每个字节代表一行每个bit代表该行的某一列是否点亮。我们为46个字符0-9, A-Z定义字体。以下是一个简化的字体定义示例Python字典形式实际项目中可能需要更精细的设计。# font_8x8.py # 这是一个非常基础的示例字体实际使用时需要精心设计每个字符的图案 # 每个字符是一个包含8个整数的列表每个整数范围0-255表示8行像素MSB通常在最左或最右取决于硬件 FONT_8X8 { 0: [0x3E, 0x7F, 0x63, 0x73, 0x6B, 0x67, 0x7F, 0x3E], # 数字0 1: [0x0C, 0x1C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x3F], 2: [0x3E, 0x7F, 0x03, 0x3E, 0x60, 0x63, 0x7F, 0x3E], // ... 此处省略数字3-9和字母A-Z的完整定义实际项目需要补全 A: [0x1C, 0x3E, 0x63, 0x63, 0x7F, 0x63, 0x63, 0x63], B: [0x7E, 0x7F, 0x63, 0x7E, 0x63, 0x63, 0x7F, 0x7E], // ... 继续定义直到Z Z: [0x7F, 0x7F, 0x06, 0x0C, 0x18, 0x30, 0x7F, 0x7F], } # 注意这是一个示意结构。完整的46个字符字体数据较长通常从标准字体库提取或使用工具生成。重要提示在实际嵌入式开发中字体数据通常以PROGMEMArduino AVR或const数组ESP32等形式存储在Flash中以节省RAM。4.2 在点阵屏上渲染字符串假设我们有一个横向串联的多个8x8点阵模块来显示字符串。渲染流程如下将需要显示的十进制数据如车厢号1024通过Base36.encode()转换为短字符串如”SG”。遍历字符串中的每个字符。根据字符从FONT_8X8字典中取出对应的8字节数据。将这8字节数据发送到点阵屏驱动芯片的对应位置一个字符占用一个8x8模块。Arduino 伪代码逻辑// 假设使用 MD_MAX72xx 库驱动点阵屏 #include MD_MAX72xx.h #include Base36.h #include font_8x8.h // 假设字体数据放在这里 #define HARDWARE_TYPE MD_MAX72XX::FC16_HW #define MAX_DEVICES 4 // 假设有4个8x8模块串联可显示4个字符 #define CLK_PIN 13 #define DATA_PIN 11 #define CS_PIN 10 MD_MAX72XX mx MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); void displayBase36String(const String str, int startModule) { // 确保不超出显示范围 int len str.length(); if (startModule len MAX_DEVICES) { len MAX_DEVICES - startModule; } for (int i 0; i len; i) { char c str.charAt(i); int module startModule i; // 清除该模块原有显示 mx.control(module, MD_MAX72XX::UPDATE, MD_MAX72XX::OFF); // 绘制字符c到第module个模块 drawChar(c, module); mx.control(module, MD_MAX72XX::UPDATE, MD_MAX72XX::ON); } } void drawChar(char c, int module) { // 从字体数组中获取字符c的数据 const uint8_t* charData getFontData(c); // getFontData需要你根据字体数组实现 if (charData nullptr) return; // 将8行数据设置到点阵模块 for (int row 0; row 8; row) { mx.setRow(module, row, charData[row]); } } void setup() { mx.begin(); mx.control(MD_MAX72XX::INTENSITY, 5); // 设置亮度 mx.clear(); // 示例显示车厢号102436进制为SG unsigned long carriageNumber 1024; String base36Str Base36::encode(carriageNumber); displayBase36String(base36Str, 0); // 从第一个模块开始显示 // 可以继续显示其他信息如速度 unsigned long speed 45; // 十进制速度 String speedStr S: Base36::encode(speed); // 组合成S:1D displayBase36String(speedStr, 2); // 从第三个模块开始显示 } void loop() { // 动态更新逻辑可以放在这里 }5. 完整实战案例蒸汽火车信息显示系统让我们整合以上模块创建一个简单的Python模拟程序演示从数据到点阵模拟显示的全流程。项目结构steam_train_simulator/ ├── base36.py # 包含Base36Converter类 ├── fonts.py # 包含FONT_8X8字典简化版 ├── matrix_simulator.py # 点阵模拟显示类 └── main_simulator.py # 主程序matrix_simulator.py- 控制台模拟点阵屏# matrix_simulator.py import os class ConsoleMatrixSimulator: 在控制台模拟一个横向的8x8点阵屏 def __init__(self, num_modules4): :param num_modules: 模拟的点阵模块数量 self.width num_modules * 8 self.height 8 self.buffer [[ for _ in range(self.width)] for _ in range(self.height)] def clear(self): 清空显示缓冲区 self.buffer [[ for _ in range(self.width)] for _ in range(self.height)] def set_pixel(self, x, y, onTrue): 设置缓冲区中特定像素点 if 0 x self.width and 0 y self.height: self.buffer[y][x] # if on else def draw_char(self, char, font_dict, start_x): 在缓冲区的指定起始x坐标绘制一个字符 char_data font_dict.get(char.upper()) if not char_data: return # 字符不在字体库中跳过 # char_data 是8个整数每个整数代表一行 for row in range(8): row_byte char_data[row] for col in range(7, -1, -1): # 从最高位(最左)到最低位(最右) pixel_on (row_byte col) 0x01 self.set_pixel(start_x (7 - col), row, pixel_on) def display(self): 将缓冲区内容打印到控制台模拟点阵屏显示 os.system(cls if os.name nt else clear) # 清屏 print( * (self.width 2)) for row in self.buffer: print(| .join(row) |) print( * (self.width 2))main_simulator.py- 主程序# main_simulator.py from base36 import Base36Converter from fonts import FONT_8X8 from matrix_simulator import ConsoleMatrixSimulator import time def main(): # 1. 初始化模拟器假设有4个8x8模块可显示4个字符 matrix ConsoleMatrixSimulator(num_modules4) # 2. 模拟火车数据 train_data { carriage: 1024, # 车厢号 speed: 45, # 速度 pressure: 1200, # 压力 temp: 85, # 温度 } # 3. 转换为36进制字符串 base36_strings {} for key, value in train_data.items(): base36_strings[key] Base36Converter.encode(value) print(原始数据十进制:, train_data) print(转换后36进制:, base36_strings) # 输出示例 # 原始数据十进制: {carriage: 1024, speed: 45, pressure: 1200, temp: 85} # 转换后36进制: {carriage: SG, speed: 19, pressure: YC, temp: 2D} # 4. 设计显示格式例如 “C:SG S:19” display_text fC:{base36_strings[carriage]} S:{base36_strings[speed]} print(f\n将在点阵屏上显示: {display_text}) # 5. 在模拟点阵屏上绘制 matrix.clear() current_x 0 for char in display_text: matrix.draw_char(char, FONT_8X8, current_x) current_x 8 # 每个字符占8列像素 if current_x matrix.width: # 防止超出屏幕 break # 6. 显示 matrix.display() # 7. 模拟动态更新 print(\n模拟动态更新速度...) for s in range(46, 52): train_data[speed] s speed_str Base36Converter.encode(s) display_text fC:{base36_strings[carriage]} S:{speed_str} matrix.clear() current_x 0 for char in display_text: matrix.draw_char(char, FONT_8X8, current_x) current_x 8 matrix.display() time.sleep(0.5) if __name__ __main__: main()运行main_simulator.py你将在控制台看到一个由#和空格组成的模拟点阵屏动态显示车厢号和速度的36进制信息。6. 常见问题与排查思路在实现和集成过程中你可能会遇到以下问题问题现象可能原因排查思路与解决方案转换结果不正确如1024转36进制不是SG1. 字符集顺序错误如A代表1。2. 计算余数或商时逻辑错误。3. 字符串拼接顺序错误应先得到的余数是低位。1. 核对_BASE36_CHARS字符串确保是0-9A-Z。2. 使用divmod函数确保整除和取余正确。3. 检查encode函数中结果列表result的拼接顺序应是reversed(result)。点阵屏显示乱码或字符错位1. 字体数据与硬件扫描方向不匹配行/列MSB/LSB。2.drawChar函数中行数据setRow或setColumn用错。3. 字符间间距计算错误。1.这是最常见问题。确认硬件驱动库如MD_MAX72xx的坐标系。可能需要转置字体数据或调整setRow/setColumn。2. 编写一个测试函数单独显示字符 ‘0’ 和 ‘A’检查图案是否正确。3. 确保start_x或模块索引每次增加一个字符的宽度通常是8。显示内容超出屏幕边界36进制字符串长度估算错误超过了预分配的点阵模块数量。1. 在显示前计算字符串长度len_str len(base36_str)。2. 与最大显示字符数MAX_CHARS比较必要时进行截断或滚动显示。3. 对于数值可以预估其36进制最大长度ceil(log36(num))。Arduino 程序内存(RAM)不足字体数组过大直接放在全局变量区消耗了过多RAM。1. 将常量字体数据用PROGMEMAVR或const修饰并存储在Flash中。2. 使用时通过pgm_read_byte()等函数读取。3. 考虑使用更紧凑的字体如5x7。性能问题刷新慢在loop()中频繁进行进制转换和全屏刷新。1. 仅当数据变化时才更新显示。2. 将进制转换结果缓存起来避免重复计算。3. 使用局部刷新只更新变化的字符区域而非全局刷新。7. 最佳实践与工程建议将36进制显示集成到真实的嵌入式项目中需要考虑更多工程细节。字体存储优化使用PROGMEM/Flash在Arduino AVR平台务必使用PROGMEM将字体数据存储在程序存储器中。// Arduino AVR 示例 #include avr/pgmspace.h const uint8_t font_8x8[46][8] PROGMEM { {0x3E, 0x7F, 0x63, 0x73, 0x6B, 0x67, 0x7F, 0x3E}, // 0 // ... 其他字符 }; // 读取时使用 pgm_read_byte(font_8x8[index][row])压缩字体对于极度受限的环境可以考虑使用高度压缩的位图字体或仅包含项目所需的字符子集。显示策略信息轮播如果信息太多一屏显示不下可以采用滚动或分页轮播的方式如先显示“车厢: SG”2秒后显示“速度: 1A”。状态指示可以保留点阵屏的某些像素作为状态指示灯如连接状态、报警状态与字符显示区域分开管理。双缓冲在内存中维护一个显示缓冲区所有绘制操作先在缓冲区完成然后一次性刷新到硬件可以避免闪烁。代码结构模块化将36进制转换、字体管理、显示驱动封装成独立的类或模块提高代码可复用性和可测试性。配置化将点阵屏的硬件类型、模块数量、引脚定义、亮度等参数定义为宏或放在配置文件中便于移植。错误处理在decode函数中增加对非法输入字符的健壮处理。显示函数应对超出边界的绘制请求做安全截断。扩展思考更高进制如果需要更极致的压缩可以考虑62进制0-9, a-z, A-Z但要注意区分大小写可能增加字体复杂度。数据协议如果显示数据来自串口、网络等可以定义简单的协议如“C:1024,S:45,P:1200\n”设备端解析后转换为36进制再显示。用户体验虽然36进制节省空间但对不熟悉的人来说不易读。可以考虑在调试模式或通过上位机软件同时显示十进制原始值。通过以上步骤我们完成了从需求分析、算法实现、字体设计、模拟显示到嵌入式部署的完整闭环。36进制在资源受限的显示场景下是一个巧妙而实用的解决方案。它不仅适用于蒸汽火车这样的复古项目任何需要在小型点阵屏、OLED屏或数码管上高效显示数字ID、状态码的场景都可以借鉴此思路。核心在于权衡信息密度与可读性并设计可靠的转换与渲染流程。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门