自制工具合集之《在线串口助手》
一个自定义协议的在线串口助手
·
嵌入式学习交流Q群 679912988
前言
- 工具地址: 串口助手
- 无需下载,支持不同的波特率,停止位等基本参数的设置。
- 自定义收发协议。
- 可扩展的批处理。
- 预定义的发送内容。
- 可下载的接收内容。
- 纯前端工具,不保留任何数据!!!
- 有好的想法或遇到问题,请到留言板留言。
简介
基于Web Serial API实现,仅支持Chrome、Edge浏览器在https下运行。
界面分为已下部分:
- 串口配置:配置串口参数,如波特率、数据位、校验位、停止位、流控、打开关闭、统计等
- 控制台:显示串口接收和发送的数据,支持时间戳、序号显示、发送回显、窗口置底、原始数据等功能
- 协议配置:可选的自定义协议,预设按键,可自定义发送数据。
发送示例:
hello
hi\r\n
[30 31 32]
协议
- 自定义协议采用注入js的方式,即上传js文件实现。
- 不涉及后端服务器,上传后的文件保存在本地浏览器中,即更换电脑后需要重新上传。
- 更多示例参考协议示例
js文件中函数模型说明如下:
发送序列化:
/**
* @param {string} str 编辑的字符串
* @return {Uint8Array} 序列化后的数组
*/
function serialize(str) {
// 检查是否为十六进制格式 [xx xx xx]
if (str.match(/^\[([\da-fA-F]{2}\s*)+\]$/)) {
// 移除[]并分割成数组
const hexValues = str.slice(1, -1).trim().split(/\s+/);
// 转换为Uint8Array
return new Uint8Array(hexValues.map(hex => parseInt(hex, 16)));
}
// 普通字符串处理
str = str.replace(/\\r/g, '\r')
.replace(/\\n/g, '\n')
.replace(/\\t/g, '\t')
.replace(/\\f/g, '\f')
.replace(/\\b/g, '\b')
.replace(/\\v/g, '\v');
// 使用TextEncoder编码字符串
const data = new TextEncoder('gbk').encode(str);
return data;
}
接收序列化 :
/**
* @param {Uint8Array} array 接收缓冲区数组
* @return {number} 缓冲区数组中,能正确解析完整一包的长度
*/
function deserialize(array) {
//检索协议\n的结束标识
let len = array.findIndex(item => item == 10);
len += len >= 0 ? 1 : 0;
return len > 0 ? len : array.length;
}
控制台输出:
/**
* @param {Uint8Array} array 通过接收序列化的长度提取的完整一包数据
* @return {string} 解析后的字符串
*/
function log(array) {
let log = '';
console.log(array);
// 创建一个新的Uint8Array来存储数据
const data = new Uint8Array(array.slice(0, array.length + 1));
try {
// 使用TextDecoder解码Uint8Array
log = new TextDecoder().decode(data);
} catch (error) {
console.error('解码错误:', error);
log = '';
}
return log;
}
预设按键:
async function custom1(transfer) {
transfer("hello1");
await new Promise(resolve => setTimeout(resolve, 1000));
transfer("hi1\r\n");
}
async function custom2(transfer) {
transfer("hello2");
await new Promise(resolve => setTimeout(resolve, 1000));
transfer("hi2\r\n");
}
async function custom3() {
alert('一个简短的介绍')
}
/**
* @return {Array} 预设按键数组
*/
function customJson() {
//json格式示例
// {
// "name": "按键名称",
// "title": "悬停时的提示信息",
// "str": "待序列化的发送字符串"
// "fun": "自定义函数"
// }
return [
{ name: "发送hello", str:"hello\\r\\n", title:"字符串示例" },
{ name: "发送hi", str:"hi\\r\\n", title:"换行符字符串示例" },
{ name: "发送hex", str:"[30 31 32]", title:"十六进制示例" },
{ name: "自定义发送1", fun:"custom1" },
{ name: "自定义发送2", fun:"custom2" },
{ name: "自定义介绍", fun: "custom3" },
];
}
协议示例
-
复制协议内容,保存到本地已js后缀的文件,如
default.js,然后在协议管理中选择上传js文件。 -
重名则覆盖原文件。
-
请注意不要上传不信任的js文件,由此引发的安全问题自行承担。
-
缺省协议
支持字符串、换行符字符串、十六进制格式发送
/** * @param {string} str 编辑的字符串 * @return {Uint8Array} 序列化后的数组 */ function serialize(str) { // 检查是否为十六进制格式 [xx xx xx] if (str.match(/^\[([\da-fA-F]{2}\s*)+\]$/)) { // 移除[]并分割成数组 const hexValues = str.slice(1, -1).trim().split(/\s+/); // 转换为Uint8Array return new Uint8Array(hexValues.map(hex => parseInt(hex, 16))); } // 普通字符串处理 str = str.replace(/\\r/g, '\r') .replace(/\\n/g, '\n') .replace(/\\t/g, '\t') .replace(/\\f/g, '\f') .replace(/\\b/g, '\b') .replace(/\\v/g, '\v'); // 使用TextEncoder编码字符串 const data = new TextEncoder('gbk').encode(str); return data; } /** * @param {Uint8Array} array 接收缓冲区数组 * @return {number} 缓冲区数组中,能正确解析完整一包的长度 */ function deserialize(array) { //检索协议\n的结束标识 let len = array.findIndex(item => item == 10); len += len >= 0 ? 1 : 0; return len > 0 ? len : array.length; } /** * @param {Uint8Array} array 通过接收序列化的长度提取的完整一包数据 * @return {string} 解析后的字符串 */ function log(array) { let log = ''; console.log(array); // 创建一个新的Uint8Array来存储数据 const data = new Uint8Array(array.slice(0, array.length + 1)); try { // 使用TextDecoder解码Uint8Array log = new TextDecoder().decode(data); } catch (error) { console.error('解码错误:', error); log = ''; } return log; } async function custom1(transfer) { transfer("hello1"); await new Promise(resolve => setTimeout(resolve, 1000)); transfer("hi1\r\n"); } async function custom2(transfer) { transfer("hello2"); await new Promise(resolve => setTimeout(resolve, 1000)); transfer("hi2\r\n"); } async function custom3() { alert('一个简短的介绍') } /** * @return {Array} 预设按键数组 */ function customJson() { //json格式示例 // { // "name": "按键名称", // "title": "悬停时的提示信息", // "str": "待序列化的发送字符串" // "fun": "自定义函数" // } return [ { name: "发送hello", str:"hello\\r\\n", title:"字符串示例" }, { name: "发送hi", str:"hi\\r\\n", title:"换行符字符串示例" }, { name: "发送hex", str:"[30 31 32]", title:"十六进制示例" }, { name: "自定义发送1", fun:"custom1" }, { name: "自定义发送2", fun:"custom2" }, { name: "自定义介绍", fun: "custom3" }, ]; } -
CH9141
- CH9141是一款WCH(南京沁恒)蓝牙串口透传芯片,芯片支持广播模式、主机模式和从机模式。
- 芯片的串口接收缓存为 512 个字节,串口接收数据的同时会实时进行蓝牙传输。
- 建议主机发送时做一些速度方面的限制以降低丢包和缓冲区溢出。
- 由于蓝牙通信速率与其环境有关,所以在串口波特率超过 9600bit/s,且蓝牙平均 RSSI 小于70dBm 时,建议使用 CTS/RTS 流控防止缓存区溢出。
/** * @param {string} str 编辑的字符串 * @return {Uint8Array} 序列化后的数组 */ function serialize(str) { // 检查是否为十六进制格式 [xx xx xx] if (str.match(/^\[([\da-fA-F]{2}\s*)+\]$/)) { // 移除[]并分割成数组 const hexValues = str.slice(1, -1).trim().split(/\s+/); // 转换为Uint8Array return new Uint8Array(hexValues.map(hex => parseInt(hex, 16))); } // 普通字符串处理 str = str.replace(/\\r/g, '\r') .replace(/\\n/g, '\n') .replace(/\\t/g, '\t') .replace(/\\f/g, '\f') .replace(/\\b/g, '\b') .replace(/\\v/g, '\v'); // 使用TextEncoder编码字符串 const data = new TextEncoder('gbk').encode(str); return data; } /** * @param {Uint8Array} array 接收缓冲区数组 * @return {number} 缓冲区数组中,能正确解析完整一包的长度 */ function deserialize(array) { //检索协议\n的结束标识 let len = array.findIndex(item => item == 10); len += len >= 0 ? 1 : 0; return len > 0 ? len : array.length; } /** * @param {Uint8Array} array 通过接收序列化的长度提取的完整一包数据 * @return {string} 解析后的字符串 */ function log(array) { let log = ''; console.log(array); // 创建一个新的Uint8Array来存储数据 const data = new Uint8Array(array.slice(0, array.length + 1)); try { // 使用TextDecoder解码Uint8Array log = new TextDecoder().decode(data); } catch (error) { console.error('解码错误:', error); log = ''; } return log; } async function help(transfer) { alert("自定义执行例子,发送一个'hello'") } function customJson() { return [ { name: "进入 AT 配置", str:"AT...\\r\\n" }, { name: "退出 AT 配置", str:"AT+EXIT\\r\\n" }, { name: "复位芯片", str:"AT+RESET\\r\\n" }, // { name: "查询开机语", str:"AT+HELLO?\\r\\n" }, // { name: "设置开机语", str:"AT+HELLO=\\r\\n", title: "设置开机语为空" }, { name: "查询串口参数", str:"AT+UART?\\r\\n" }, { name: "设置串口参数", str:"AT+UART=115200,8,1,0,50\\r\\n", title:"波特率:115200,数据位:8,校验位:n,停止位:1,超时时间:50ms" }, { name: "本地MAC地址", str:"AT+MAC?\\r\\n" }, { name: "查询发射功率", str:"AT+TPL?\\r\\n" }, { name: "设置发射功率", str:"AT+TPL=0\\r\\n", title: "设置蓝牙发射功率,x 支持的参数:\n0:0DB\n1:1DB\n2:2DB\n3:3DB\n4:-3DB\n5:-8DB\n6:-14DB\n7:-20DB"}, { name: "查询蓝牙工作模式", str:"AT+BLEMODE?\\r\\n", title: "查询蓝牙工作模式\n0:广播模式\n1:主机模式\n2:从机模式"}, { name: "设置广播工作模式", str:"AT+BLEMODE=0\\r\\n", title: "复位芯片后生效" }, { name: "设置主机工作模式", str:"AT+BLEMODE=1\\r\\n", title: "复位芯片后生效" }, { name: "设置从机工作模式", str:"AT+BLEMODE=2\\r\\n", title: "复位芯片后生效" }, { name: "查询蓝牙状态", str:"AT+BLESTA?\\r\\n", title: "[0]广播模式: 00 未初始化、 01 设备初始化完成、02 广播、07 错误\n[1]主机模式: 00 未初始化、01 扫描、02 连接中、03 连接成功、04 断开连接中\n[2]从机模式: 00 未初始化、01 设备初始化完成、02 广播、03 准备广播状态、04 连接超时、05 连接成功、07 错误"}, { name: "主机扫描", str:"AT+SCAN=ON\\r\\n" }, { name: "查询主机默认连接", str:"AT+CONADD?\\r\\n" }, { name: "新增主机默认连接", str:"AT+CONADD=00:01:02:03:04:05\\r\\n" }, { name: "清空主机默认连接", str:"AT+CLRCONADD\\r\\n" }, // { name: "查询从机密码", str:"AT+PASS?\\r\\n" }, // { name: "当前连接MAC地址", str:"AT+CCADD?\\r\\n" }, // { name: "重置所有参数", str:"AT+RELOAD\\r\\n" }, // { name: "显示芯片信息", str:"AT+SHOW\\r\\n" }, // { name: "保存当前参数", str:"AT+SAVE\\r\\n" }, // { name: "获取芯片版本号", str:"AT+VER\\r\\n" }, ]; } -
modbus
一个简单的modbus协议示例。
// MODBUS CRC16校验计算 function calculateCRC16(data) { let crc = 0xFFFF; for (let i = 0; i < data.length; i++) { crc ^= data[i]; for (let j = 0; j < 8; j++) { if (crc & 0x0001) { crc = (crc >> 1) ^ 0xA001; } else { crc = crc >> 1; } } } return crc; } function serialize(str) { // 尝试解析JSON格式 const modbusCmd = JSON.parse(str); switch(modbusCmd.functionCode) { case 5: case 3: case 6: // 创建MODBUS请求帧 if(1) { const frame = new Uint8Array(8); frame[0] = modbusCmd.slaveId; // 设备ID frame[1] = modbusCmd.functionCode; // 功能码 frame[2] = (modbusCmd.address >> 8) & 0xFF; // 寄存器地址高字节 frame[3] = modbusCmd.address & 0xFF; // 寄存器地址低字节 frame[4] = (modbusCmd.number >> 8) & 0xFF; // 寄存器数量高字节 frame[5] = modbusCmd.number & 0xFF; // 寄存器数量低字节 // 计算CRC const crc = calculateCRC16(frame.slice(0, 6)); frame[6] = crc & 0xFF; // CRC低字节 frame[7] = (crc >> 8) & 0xFF;// CRC高字节 return frame; } break; case 15: if(1) { const frame = new Uint8Array(9 + modbusCmd.bytes); frame[0] = modbusCmd.slaveId; // 设备ID frame[1] = modbusCmd.functionCode; // 功能码 frame[2] = (modbusCmd.address >> 8) & 0xFF; // 寄存器地址高字节 frame[3] = modbusCmd.address & 0xFF; // 寄存器地址低字节 frame[4] = (modbusCmd.number >> 8) & 0xFF; // 寄存器数量高字节 frame[5] = modbusCmd.number & 0xFF; // 寄存器数量低字节 frame[6] = modbusCmd.bytes; // 写入字节数 for (let i = 0; i < modbusCmd.bytes; i++) frame[7 + i] = modbusCmd.data[i]; // 计算CRC const crc = calculateCRC16(frame.slice(0, 7 + modbusCmd.bytes)); frame[7 + modbusCmd.bytes] = crc & 0xFF; // CRC低字节 frame[8 + modbusCmd.bytes] = (crc >> 8) & 0xFF;// CRC高字节 return frame; } break; default: break; } } /** * @param {Uint8Array} array 接收缓冲区数组 * @return {number} 缓冲区数组中,能正确解析完整一包的长度 */ function deserialize(array) { //检索协议\n的结束标识 let len = array.findIndex(item => item == 10); len += len >= 0 ? 1 : 0; return len > 0 ? len : array.length; } function log(array) { let log = ''; console.log(array); // 创建一个新的Uint8Array来存储数据 const data = new Uint8Array(array.slice(0, array.length + 1)); try { // 使用TextDecoder解码Uint8Array log = new TextDecoder().decode(data); } catch (error) { console.error('解码错误:', error); log = ''; } return log; } async function test(transfer) { for(let i = 0; i < 2; i++) { transfer(JSON.stringify({"slaveId":17, "functionCode":15, "address":0, "number":6, "bytes": 1, "data":[0x3f]})) await new Promise(resolve => setTimeout(resolve, 200)); transfer(JSON.stringify({"slaveId":17, "functionCode":15, "address":0, "number":6, "bytes": 1, "data":[0]})) await new Promise(resolve => setTimeout(resolve, 200)); } for(let i = 0; i < 24; i++) { transfer(`{"slaveId":17, "functionCode":15, "address":0, "number":6, "bytes": 1, "data":[ ${1 << (i%6)}]}`) await new Promise(resolve => setTimeout(resolve, 200)); } for(let i = 0; i < 3; i++) { transfer(JSON.stringify({"slaveId":17, "functionCode":15, "address":0, "number":6, "bytes": 1, "data":[0x3f]})) await new Promise(resolve => setTimeout(resolve, 200)); transfer(JSON.stringify({"slaveId":17, "functionCode":15, "address":0, "number":6, "bytes": 1, "data":[0]})) await new Promise(resolve => setTimeout(resolve, 200)); } } function customJson() { return [ { name: "打开1", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":0, "number":0xff00})}, { name: "打开2", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":1, "number":0xff00})}, { name: "打开3", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":2, "number":0xff00})}, { name: "打开4", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":3, "number":0xff00})}, { name: "打开5", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":4, "number":0xff00})}, { name: "打开6", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":5, "number":0xff00})}, { name: "关闭1", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":0, "number":0})}, { name: "关闭2", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":1, "number":0})}, { name: "关闭3", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":2, "number":0})}, { name: "关闭4", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":3, "number":0})}, { name: "关闭5", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":4, "number":0})}, { name: "关闭6", str: JSON.stringify({"slaveId":17, "functionCode":5, "address":5, "number":0})}, { name: "打开全部", str: JSON.stringify({"slaveId":17, "functionCode":15, "address":0, "number":6, "bytes": 1, "data":[0x3f]})}, { name: "关闭全部", str: JSON.stringify({"slaveId":17, "functionCode":15, "address":0, "number":6, "bytes": 1, "data":[0]})}, { name: "老化测试", fun: "test"}, { name: "查询设备ID", str: JSON.stringify({"slaveId":17, "functionCode":3, "address":0, "number":0x0001})}, { name: "设置设备ID=3", str: JSON.stringify({"slaveId":17, "functionCode":6, "address":0, "number":0x0003})}, { name: "设置设备ID=17", str: JSON.stringify({"slaveId":17, "functionCode":6, "address":0, "number":0x0011})}, ]; }
站内工具合集
更多推荐



所有评论(0)