嵌入式学习交流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})},
       ];
    
     }
    

站内工具合集

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐