我在做一个小东西的时候用到了Arduino和树莓派,因为需要将数据采集端放的比较远所以没有直接将传感器接在树莓派上,而是选择了使用Arduino采集传感器数据,然后使用串口与树莓派通信。就在我很快写完两端的代码之后遇到了一个奇怪的问题,那就是明明数据接收解析过程没有问题,但是解析的数据怎么也不对。经过分析发现是两个平台数据长度和默认的对齐方式不同导致的,特此记录一下。
struct sensor_data{
uint16_t hand;
double ambient_temp;
double object_temp;
uint16_t tail;
}sensor_data;
我在两个平台定义了相同的结构体用来传输数据,在Arduino上将传感器数据获取到之后写入结构体,然后使用按字节从串口发送,因为两个平台都是小端模式,所以理论上在树莓派上我只需要按字节把接收的数据拷贝到相同结构体就可以获取到数据,但是,实际发现Arduino只发送了14字节,也就是说在Arduino平台下double只占用了4字节,与float是一致的。而且是按2字节对齐。
A5 5A D0 A3 E2 41 30 5C E1 41 0F F0
实际打印的数据也印证了我的猜想,后来发现Arduino的文档中关于double的描述明确指出了这个问题。
树莓派自然是不存在double与float长度一致的问题,而且树莓派是4字节对齐,所以就会出现我遇到的数据错误的问题。知道问题所在,修改代码就愉快的解决问题了。
关于C语言中的字节对齐一般有两种方式:
一、强制按字节对齐:
#pragma pack (n) //C编译器将按照n个字节对齐。
#pragma pack () //取消自定义字节对齐方式。
二、对齐到n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐:
__attribute((aligned (n))) //C编译器将按照n个字节对齐。
__attribute ((packed)) //取消自定义字节对齐方式。
最后附上此次测试的代码:
树莓派:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#pragma pack (2)
struct sensor_data{
uint16_t hand;
uint16_t serial_number;
float ambient_temp;
float object_temp;
uint16_t tail;
}sensor_data;
#pragma pack ()
struct sensor_data rx_data;
int main(void)
{
int hs1;
char uart_rx_buffer[64];
uint16_t uart_flag = 0;
int16_t index = 0;
int16_t data_flag = 0;
wiringPiSetup(); // 使用wiring编码去初始化GPIO序号
// hs1 = serialOpen("/dev/ttyS0", 115200); // 打开 /dev/ttyS0 串口设备,波特率115200
hs1 = serialOpen("/dev/ttyUSB0", 115200); // 打开 /dev/ttyUSB0 串口设备,波特率115200
if(hs1 < 0){
printf("UART open error %d!\r\n",hs1);
return -1;
}
else{
printf("UART open success %d!\r\n",hs1);
}
char keyboard_input[10] = {};
while(TRUE){
int buffer_size = serialDataAvail(hs1);
while(buffer_size){
char c = serialGetchar(hs1); // 从接收缓存区读取一个字节
if((uart_flag == 0)&&(c == 0xA5)){
uart_flag = 1;
}
else if((uart_flag == 1)&&(c == 0x5A)){
uart_flag = 2;
}
else if((uart_flag == 1)&&(c != 0x5A)){
uart_flag = 0;
index = 0;
memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
}
if(uart_flag > 0){
uart_rx_buffer[index++] = c;
if(index > sizeof(rx_data)+1){
uart_flag = 0;
index = 0;
memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
}
}
if((uart_flag == 2)&&(c == 0x0F)){
uart_flag = 3;
}
else if((uart_flag == 3)&&(c == 0xF0)){
data_flag = 1;
memcpy((&rx_data),uart_rx_buffer,sizeof(rx_data));
uart_flag = 0;
index = 0;
memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
}
else if((uart_flag == 3)&&(c != 0xF0)){
uart_flag = 0;
index = 0;
memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
}
buffer_size --;
}
if(data_flag){
printf("%5d Ambient = %5.2f Object = %5.2f\r\n",\
rx_data.serial_number,\
rx_data.ambient_temp,\
rx_data.object_temp);
data_flag = 0;
}
}
serialClose(hs1); // 关闭串口
return 0;
}
因为使用到了wiringPi的库,所以编译的时候记得加-lwiringPi链接,执行的时候也需要使用管理员权限才可执行。
Arduino:
#include <Wire.h>
#include <Adafruit_MLX90614.h>
Adafruit_MLX90614 mlx = Adafruit_MLX90614();
struct sensor_data{
uint16_t hand;
uint16_t serial_number;
float ambient_temp;
float object_temp;
uint16_t tail;
}sensor_data;
struct sensor_data tx_data;
uint16_t num =0;
void setup() {
tx_data.hand = 0x5aa5;
tx_data.tail = 0xf00f;
Serial.begin(115200);
mlx.begin();
}
void loop() {
tx_data.serial_number = num;
tx_data.ambient_temp = mlx.readAmbientTempC();
tx_data.object_temp = mlx.readObjectTempC();
Serial.write((char*)(&tx_data),sizeof(tx_data));
num++;
delay(500);
}