各位听众,早上好/下午好/晚上好!我是你们今天的串口通讯小喇叭,今天咱们来聊聊 Web Serial API 里那些让你头大的硬件级配置:Parity(奇偶校验)、Stop Bits(停止位)和 Data Bits(数据位)。别怕,这玩意儿听起来玄乎,其实理解起来就像你泡方便面一样简单。
开场白:串口通讯,老朋友新玩法
串口通讯,这玩意儿在嵌入式领域可是老古董了,但 Web Serial API 赋予了它新的生命。以前你得装驱动、写 C 代码、折腾半天才能和硬件设备说上话,现在好了,浏览器直接上,JavaScript 一把梭,就能搞定。但是,要想玩转 Web Serial API,就绕不开那几个硬件配置参数,它们就像方便面里的调料包,加对了味道才正。
第一部分:数据位(Data Bits):信息的宽度
想象一下,你要给朋友发短信,每个字代表一部分信息。数据位就相当于每个字的大小,也就是每个串口传输的数据包里有多少个 bit。常见的选择有 5、6、7 和 8 个 bit。
- 5 bits: 这玩意儿现在很少见了,除非你还在用一些超级古老的设备。
- 6 bits: 也比较少见,但某些特殊应用可能会用到。
- 7 bits: 可以用来传输 ASCII 码,但现在通常会被 8 bits 取代。
- 8 bits: 这是最常用的,可以传输完整的 ASCII 码和扩展 ASCII 码,还可以传输二进制数据。就像你发微信,啥表情包都能发。
代码示例:设置数据位
navigator.serial.requestPort().then(port => {
port.open({
baudRate: 9600,
dataBits: 8 // 设置数据位为 8 bits
}).then(() => {
console.log("串口已打开,数据位:8");
}).catch(err => {
console.error("打开串口失败:", err);
});
});
幽默解释: 数据位就像你家的带宽,带宽越大,一次能传的数据就越多。8 bits 就像百兆光纤,5 bits 就像拨号上网,速度差太多了!
第二部分:奇偶校验(Parity):数据传输的质检员
奇偶校验就像数据传输过程中的质检员,它能帮你检测数据在传输过程中有没有出错。想象一下,你在高速公路上开车,奇偶校验就像路上的摄像头,随时监控有没有车辆违规。
奇偶校验有几种模式:
- None (无校验): 最简单粗暴,啥也不管,数据直接发。就像你开车直接上路,没有交通规则约束。
- Even (偶校验): 统计数据位中 1 的个数,如果 1 的个数是奇数,就在数据后面加一个 1,让总数变成偶数。如果 1 的个数已经是偶数,就加一个 0。
- Odd (奇校验): 和偶校验相反,如果 1 的个数是偶数,就加一个 1,让总数变成奇数。如果 1 的个数已经是奇数,就加一个 0。
- Mark (标记校验): 在数据后面加一个 1。
- Space (空格校验): 在数据后面加一个 0。
表格总结:奇偶校验的工作原理
校验模式 | 原始数据 (1 的个数) | 添加的校验位 | 最终数据 (1 的个数) |
---|---|---|---|
None | 1010101 (4, 偶数) | 无 | 1010101 (4, 偶数) |
None | 1010100 (3, 奇数) | 无 | 1010100 (3, 奇数) |
Even | 1010101 (4, 偶数) | 0 | 10101010 (4, 偶数) |
Even | 1010100 (3, 奇数) | 1 | 10101001 (4, 偶数) |
Odd | 1010101 (4, 偶数) | 1 | 10101011 (5, 奇数) |
Odd | 1010100 (3, 奇数) | 0 | 10101000 (3, 奇数) |
Mark | 1010101 (4, 偶数) | 1 | 10101011 (5, 奇数) |
Space | 1010101 (4, 偶数) | 0 | 10101010 (4, 偶数) |
代码示例:设置奇偶校验
navigator.serial.requestPort().then(port => {
port.open({
baudRate: 9600,
dataBits: 8,
parity: "even" // 设置奇偶校验为偶校验
}).then(() => {
console.log("串口已打开,奇偶校验:偶校验");
}).catch(err => {
console.error("打开串口失败:", err);
});
});
幽默解释: 奇偶校验就像你给文件打包加个校验码,如果文件在传输过程中损坏了,校验码就能告诉你哪里出错了。None 就像你直接发裸奔的文件,万一丢了数据也不知道。
第三部分:停止位(Stop Bits):数据的分割线
停止位就像数据的分割线,它告诉接收方,一个数据包已经发送完毕,可以准备接收下一个数据包了。想象一下,你在读一本书,停止位就像句号,告诉你一句话已经结束,可以开始读下一句了。
常见的选择有 1 和 2 个 bit。
- 1 bit: 这是最常用的。
- 2 bits: 有些老设备可能需要 2 个停止位,但现在很少见了。
代码示例:设置停止位
navigator.serial.requestPort().then(port => {
port.open({
baudRate: 9600,
dataBits: 8,
parity: "none",
stopBits: 1 // 设置停止位为 1 bit
}).then(() => {
console.log("串口已打开,停止位:1");
}).catch(err => {
console.error("打开串口失败:", err);
});
});
幽默解释: 停止位就像你说话时的停顿,停顿太短别人听不清,停顿太长别人以为你卡壳了。1 bit 就像正常的停顿,2 bits 就像你突然宕机了。
第四部分:综合应用:一个完整的串口配置
现在,我们把数据位、奇偶校验和停止位组合起来,创建一个完整的串口配置。
navigator.serial.requestPort().then(port => {
port.open({
baudRate: 115200, // 波特率
dataBits: 8, // 数据位
parity: "none", // 奇偶校验
stopBits: 1, // 停止位
flowControl: "none" //流控
}).then(() => {
console.log("串口已打开,配置:115200-8-N-1");
// 115200: 波特率
// 8: 数据位
// N: 无校验 (None)
// 1: 停止位
}).catch(err => {
console.error("打开串口失败:", err);
});
});
重要提示:
- 通信双方的配置必须一致! 就像你和朋友打电话,双方都得用普通话才能交流。如果你的设备用的是 8-N-1,你的 Web Serial API 也必须配置成 8-N-1,否则就乱码了。
- 波特率也很重要! 波特率决定了数据传输的速度,如果波特率不一致,也会导致乱码。
第五部分:调试技巧:遇到乱码怎么办?
如果你发现串口通讯出现乱码,不要慌,按照下面的步骤排查:
- 检查波特率: 确认你的 Web Serial API 和硬件设备的波特率是否一致。
- 检查数据位、奇偶校验和停止位: 确认你的 Web Serial API 和硬件设备的配置是否一致。
- 检查编码方式: 确认你的数据是用什么编码方式发送的,例如 UTF-8 或 ASCII。
- 使用串口调试工具: 可以使用串口调试工具(例如 Serial Monitor)来查看原始数据,帮助你定位问题。
代码示例:使用 TextDecoder
解码数据
// 假设你已经成功打开了串口并获取了 reader
const decoder = new TextDecoder(); // 默认使用 UTF-8 解码
reader.read().then(({ value, done }) => {
if (done) {
console.log("读取结束");
return;
}
const text = decoder.decode(value);
console.log("接收到的数据:", text);
});
如果你需要使用其他编码方式,可以在 TextDecoder
构造函数中指定:
const decoder = new TextDecoder('gbk'); // 使用 GBK 解码
第六部分:高级话题:流控制(Flow Control)
除了数据位、奇偶校验和停止位,还有一个重要的参数是流控制。流控制用于防止数据溢出,就像你家的水管,如果水流太快,可能会爆管。
常见的流控制方式有:
- None (无流控制): 最简单粗暴,啥也不管,数据直接发。
- RTS/CTS (硬件流控制): 使用 RTS (Request To Send) 和 CTS (Clear To Send) 信号线来控制数据流。
- XON/XOFF (软件流控制): 使用 XON 和 XOFF 字符来控制数据流。
代码示例:设置流控制
navigator.serial.requestPort().then(port => {
port.open({
baudRate: 9600,
dataBits: 8,
parity: "none",
stopBits: 1,
flowControl: "none" // 设置流控制为无流控制
}).then(() => {
console.log("串口已打开,流控制:无");
}).catch(err => {
console.error("打开串口失败:", err);
});
});
第七部分:实际案例:使用 Web Serial API 控制 Arduino
假设我们要使用 Web Serial API 控制 Arduino 的 LED 灯。
Arduino 代码:
const int ledPin = 13;
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
}
void loop() {
if (Serial.available() > 0) {
char command = Serial.read();
if (command == '1') {
digitalWrite(ledPin, HIGH); // 点亮 LED
} else if (command == '0') {
digitalWrite(ledPin, LOW); // 关闭 LED
}
}
}
Web Serial API 代码:
let port;
let writer;
async function connectSerial() {
try {
port = await navigator.serial.requestPort();
await port.open({ baudRate: 9600, dataBits: 8, parity: "none", stopBits: 1 });
writer = port.writable.getWriter();
console.log("串口已连接");
} catch (error) {
console.error("连接串口失败:", error);
}
}
async function sendCommand(command) {
if (!writer) {
console.warn("串口未连接");
return;
}
const data = new Uint8Array([command.charCodeAt(0)]); // 将字符转换为字节数组
await writer.write(data);
console.log(`发送命令: ${command}`);
}
async function disconnectSerial() {
if (writer) {
await writer.close();
}
if (port && port.readable) {
await port.readable.cancel();
}
if (port) {
await port.close();
}
port = null;
writer = null;
console.log("串口已断开");
}
// 示例用法:
document.getElementById("connectButton").addEventListener("click", connectSerial);
document.getElementById("onButton").addEventListener("click", () => sendCommand('1'));
document.getElementById("offButton").addEventListener("click", () => sendCommand('0'));
document.getElementById("disconnectButton").addEventListener("click", disconnectSerial);
代码解释:
- Arduino 代码: 监听串口数据,如果收到 ‘1’,就点亮 LED,如果收到 ‘0’,就关闭 LED。
- Web Serial API 代码:
connectSerial()
函数:连接串口,并配置波特率、数据位、奇偶校验和停止位。sendCommand()
函数:发送命令到 Arduino。disconnectSerial()
函数:断开串口连接。
总结:
Web Serial API 提供了强大的串口通讯功能,但要玩转它,你需要理解数据位、奇偶校验和停止位这些硬件级配置。希望今天的讲座能帮助你更好地使用 Web Serial API,连接你的硬件设备,创造出更多有趣的应用!记住,调试是关键,遇到问题不要怕,一步一步排查,总能找到解决方案。
最后的温馨提示:
在实际开发中,请务必参考你所使用的硬件设备的文档,了解它的串口配置要求,并根据实际情况进行调整。不同的设备可能有不同的配置要求,照搬别人的配置可能会导致通讯失败。祝大家玩得开心!