除了 gre_gor 在评论中所说的(因此,对于读取和写入整数,您需要发送和接收两个字节),您需要一种更强大的方式来发送和接收字节(只是等待和祈祷它们同步不是一种选择)。
有很多方法可以制定合适的协议。在您的理想协议中,您需要一种方法来确定数据的开始和/或结束,以及一种确定正在传输的数据的方法。
如果您使用 ascii 表示(即 withprint
和println
s),您可以使用不可打印的字符或空格或换行来设置它。但是,由于您使用的write
是 .
在面向字节的传输中,我通常会找到两种处理方法。
第一个是使用一组保留字节和一个“转义”字节。每当您获得一个保留字节时,您都会进行转义,然后修改该字节以将其设置为非保留。例如,如果您将 7D 和 7E 定义为保留,并将 7E 定义为转义字节(例如 7D 是传输的结束,并且您通过将 7 更改为 8 来将保留变为非保留)如果您的字节数组是 01 34 7D 66 7E 然后您将 7D 转换为 7E 8D(转义 + 非保留 =,将 7E 转换为 7E 8E,并附加一个 7D。这样您发送 01 34 7E 8D 66 7E 8E 7D。解析器等待7D,并且知道每当它接收到 7E 时,它都会丢弃该字节并将后面的 8x 更改为 7x。
这是一种制作更健壮协议的简单方法,但有一个缺点,即输出长度可变。当我需要发送短于 8 位(或倍数)的数据时,我经常使用的替代方法是保留一个位来指示字节是数据还是命令,然后使用其他 7 位来存储实际数据. 我在您的情况下使用这种方法,假设您发送的两个变量已签名并且范围为 16383 - -16384。
所以,协议很简单。如果接收到的字节的第一位是 1,那么这是一个命令。有效的命令是:
0x80 Accelerometer data
0x81 LDR data
(you can add your own ones, but all of them should start with a 1 bit)
所有命令后面都应该有两个数据字节,每个数据字节都以 0 位开始。组成是
bit position 7 6 5 4 3 2 1 0
1st byte '0' b13 b12 b11 b10 b09 b08 b07
2nd byte '0' b06 b05 b04 b03 b02 b01 b00
其中“0”是 0 位,bxx 是正在传输的数据的第 xx 位。
现在,发送数据很简单。这个简单的代码可以做到:
#define SEND_ACCEL_DATA 0x80
#define SEND_LDR_DATA 0x81
void sendData(uint8_t type, int value)
{
mySerial.write(type);
mySerial.write((value >> 7) & 0x7F); // Upper 7 bytes
mySerial.write(value & 0x7F); // Lower 7 bytes
// Never needed to call flush, but if you need it put it here
}
usage:
sendData(SEND_LDR_DATA, ldr_value);
sendData(SEND_ACCEL_DATA, accelerom_value);
如您所见,您只是发送类型和值。
接收代码也很简单:
#define SEND_ACCEL_DATA 0x80
#define SEND_LDR_DATA 0x81
if (mySerial.available() >= 3)
{
switch (mySerial.read())
{
case SEND_ACCEL_DATA:
{
int acceleration = mySerial.read() & 0x7F;
acceleration = (acceleration << 7) | (mySerial.read() & 0x7F);
if (acceleration & 0x2000) acceleration |= 0xC000; // This is the sign extension
// now you can use acceleration (and store it in the database)
}
break;
case SEND_LDR_DATA:
{
int ldr = mySerial.read() & 0x7F;
ldr = (ldr << 7) | (mySerial.read() & 0x7F);
if (ldr & 0x2000) ldr |= 0xC000; // This is the sign extension
// now you can use ldr (and store it in the database)
}
break;
}
}
如果你愿意,你也可以将它嵌入到一个函数中:
#define SEND_NONE 0x00
#define SEND_ACCEL_DATA 0x80
#define SEND_LDR_DATA 0x81
int getDataFromSerial(uint8_t *p_type)
{
int result = 0;
*p_type = SEND_NONE;
if (mySerial.available() >= 3)
{
switch (*p_type = mySerial.read())
{
case SEND_ACCEL_DATA:
case SEND_LDR_DATA:
int result = mySerial.read() & 0x7F;
result = (result << 7) | (mySerial.read() & 0x7F);
if (result & 0x2000) result |= 0xC000; // This is the sign extension
break;
default:
*p_type = SEND_NONE;
break;
}
}
return result;
}
// Usage
uint8_t type;
int result = getDataFromSerial(&type);
switch (type)
{
case SEND_ACCEL_DATA:
// result is the acceleration
break;
case SEND_LDR_DATA:
// result is the ldr
break;
}
请注意,您不会同时收到两个数据,而是一个接一个。如果您需要数据同时到达,请询问,我将上传修改后的版本以同时发送和接收两个数据。
编辑:如所问,这是数据一起传输的版本。
现在,如果将这两个数据组合在一起,则数据包的类型只是一个。所以不需要一个字节来说明它是什么类型的字节。因此,您可以修改协议以仅报告字节是否为起始字节。该协议现在只有一种类型的数据包,长度为 4 个字节:
bit position 7 6 5 4 3 2 1 0
1st byte '1' l13 l12 l11 l10 l09 l08 l07
2nd byte '0' l06 l05 l04 l03 l02 l01 l00
3rd byte '0' a13 a12 a11 a10 a09 a08 a07
4th byte '0' a06 a05 a04 a03 a02 a01 a00
其中 l13-l00 是 LDR 值的 14 位,a13-a00 是用于加速的位。
发送被修改,但是一旦你知道要发送什么就很简单了:
void sendData(int ldr, int accel)
{
mySerial.write(((ldr >> 7) & 0x7F) | 0x80); // Upper 7 bytes and first bit
mySerial.write(ldr & 0x7F); // Lower 7 bytes
mySerial.write((accel >> 7) & 0x7F); // Upper 7 bytes
mySerial.write(accel & 0x7F); // Lower 7 bytes
}
usage:
sendData(ldr_value, accel_value);
唯一值得注意的是第一个字节设置了最高位,而其他字节没有。
接收代码只需要等待所有四个字节都到达,检查第一个字节是否真的以 1 开头,然后完全按照前面的方式恢复这两个值:
while (mySerial.available() >= 4)
{
if (!(mySerial.peek() & 0x80))
{ // If the first byte in the buffer is not a valid start, discard it
mySerial.read();
continue;
}
int ldr = mySerial.read() & 0x7F;
ldr = (ldr << 7) | (mySerial.read() & 0x7F);
if (ldr & 0x2000) ldr |= 0xC000; // This is the sign extension
int acceleration = mySerial.read() & 0x7F;
acceleration = (acceleration << 7) | (mySerial.read() & 0x7F);
if (acceleration & 0x2000) acceleration |= 0xC000; // This is the sign extension
// now you can use acceleration and ldr (and store them in the database)
}
}
如果你更喜欢一个函数,你可以这样实现它:
boolean getDataFromSerial(int *p_ldr, int *p_accel)
{
boolean foundData = false;
while ((!foundData) && (mySerial.available() >= 4))
{
if (!(mySerial.peek() & 0x80))
{ // If the first byte in the buffer is not a valid start, discard it
mySerial.read();
continue;
}
*p_ldr = mySerial.read() & 0x7F;
*p_ldr = ((*p_ldr) << 7) | (mySerial.read() & 0x7F);
if ((*p_ldr) & 0x2000) (*p_ldr) |= 0xC000; // This is the sign extension
*p_accel = mySerial.read() & 0x7F;
*p_accel = ((*p_accel) << 7) | (mySerial.read() & 0x7F);
if ((*p_accel) & 0x2000) (*p_accel) |= 0xC000; // This is the sign extension
foundData = true;
}
}
return foundData;
}
usage:
int accel, ldr;
if (getDataFromSerial(&ldr, &accel))
{
// use accel and ldr
}
在这种情况下,这两个变量总是一起出现,而在另一种情况下,您可以分别发送它们。哪个更好?这取决于你需要什么。这个更简单有效,另一个更灵活。
编辑2:完整的例子:
我设计了一个完整的例子,两个 arduino 使用最新协议(2 个数据一起)进行通信。左边的一个从两个电位器开始生成两个值,另一个打印它们。
在这里你可以找到它:https ://circuits.io/circuits/3690285-software-serial-binary-transmission
连接:
发件人代码:
#include <SoftwareSerial.h>
SoftwareSerial mySerial(10, 11);
void sendData(int ldr, int accel)
{
mySerial.write(((ldr >> 7) & 0x7F) | 0x80); // Upper 7 bytes and first bit
mySerial.write(ldr & 0x7F); // Lower 7 bytes
mySerial.write((accel >> 7) & 0x7F); // Upper 7 bytes
mySerial.write(accel & 0x7F); // Lower 7 bytes
}
// the setup routine runs once when you press reset:
void setup() {
Serial.begin(9600);
mySerial.begin(2400);
}
// the loop routine runs over and over again forever:
void loop() {
int ldr_value = analogRead(A0);
int accel_value = analogRead(A1);
sendData(ldr_value, accel_value);
// Print it on the serial interface for debug
Serial.print("TX LDR: ");
Serial.print(ldr_value);
Serial.print(" - ACC: ");
Serial.println(accel_value);
delay(1000);
}
收货人代码:
#include <SoftwareSerial.h>
SoftwareSerial mySerial(10, 11);
boolean getDataFromSerial(int *p_ldr, int *p_accel)
{
boolean foundData = false;
while ((!foundData) && (mySerial.available() >= 4))
{
if (!(mySerial.peek() & 0x80))
{ // If the first byte in the buffer is not a valid start, discard it
mySerial.read();
continue;
}
*p_ldr = mySerial.read() & 0x7F;
*p_ldr = ((*p_ldr) << 7) | (mySerial.read() & 0x7F);
if ((*p_ldr) & 0x2000) (*p_ldr) |= 0xC000; // This is the sign extension
*p_accel = mySerial.read() & 0x7F;
*p_accel = ((*p_accel) << 7) | (mySerial.read() & 0x7F);
if ((*p_accel) & 0x2000) (*p_accel) |= 0xC000; // This is the sign extension
foundData = true;
}
return foundData;
}
// the setup routine runs once when you press reset:
void setup() {
Serial.begin(9600);
mySerial.begin(2400);
}
// the loop routine runs over and over again forever:
void loop() {
int accel, ldr;
if (getDataFromSerial(&ldr, &accel))
{
// Print it on the serial interface for debug
Serial.print("RX LDR: ");
Serial.print(ldr);
Serial.print(" - ACC: ");
Serial.println(accel);
}
}