基础知识
大小端
我们都知道,对于一个超过一个字节的数据,其在计算机中的存储需要跨越字节。
某些机器选择在存储器中按照从最低为有效字节到最高有效字节的顺序存储对象,而另一些机器则按照从最高为有效字节到到最低为有效字节的顺序存储,
前一种存储方式被称为小端存储,后一种方式被称为大端存储。
举例:对于十六进制数0x01234567,其字节的存储顺序便依赖于机器,如下:也就是大端就是正常的顺序,高位在前。 stm32和PC都是大端 但需要注意的是他们之间都需要设置为1字节对齐(stm32默认用的是4字节对齐)
byte数组与结构体之间的转换
byte数组与结构体之间的转换 参考1 byte数组与结构体之间的转换 参考2 c#中采用内存方式转换字节数组与结构体需要使用一个单独的命名空间(海康提供的二次开发c#API里面也是这样操作的)
using System.Runtime.InteropServices;
1、结构体前面 [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 固定这样写就好了
- 这是C#引用非托管的C/C++的DLL的一种定义定义结构体的方式,主要是为了内存中排序
- LayoutKind:有两个属性Sequential和Explicit,Sequential表示顺序存储,结构体内数据在内存中都是顺序存放的
- CharSet=CharSet.Ansi:表示编码方式。这都是为了使用非托管的指针准备的,这两点大家记住就可以。
- 需要注意的是 Pack = 1 这个特性:它代表了结构体的字节对齐方式,在实际开发中,C++开发环境开始默认是2字节对齐方式 ,拿上面报文包头结构体为例,char类型在虽然在内存中至占用一个字节,但在结构体转为字节数组时,系统会自动补齐两个字节,所以如果C#这面定义为Pack=1,C++默认为2字节对齐的话,双方结构体会出现长度不一致的情况,相互转换时必然会发生错位,所以需要大家都默认1字节对齐的方式,C#定义Pack=1,C++ 添加 #pragma pack 1,保证结构体中字节对齐方式一致。使用为1就行了
2、数组
- 数组的定义,结构体中每个成员的长度都是需要明确的,因为内存需要根据这个分配空间,而C#结构体中数组是无法进行初始化的,这里我们需要在成员声明时进行定义;
- 一些基本的数据类型,C#与C++都是可以匹配的,也就是不需要变动。
UnmanagedType 枚举:指定如何将参数或字段封送到非托管代码。
MarshalAsAttribute.ArraySubType:如果是数组的话,指定数组元素的类型 下面这句话的意思就是 下面是长度为12的里面装的是结构体的数组。有些就是里面装的都是浮点型啊 整形啥的
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12, ArraySubType = UnmanagedType.Struct)]
收发的转换
我觉得 发送数字那么传输的就是他们的值转成的二进制。而发送字符,则是发送字符的ASCII值。发送中文,那就需要看是具体用的什么编解码方式。
实验讲解
上位机发送 25 01 下位机发送结构体 上位机将结构体显示出来 添加监视后看到(这样看的更全面,需要打断点才能使用该功能的。也就是先打断点,然后运行程序,找到需要的,选择添加监视)
上位机代码
Byte2Struct.cs
引用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
定义的结构体
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct CSModuleInfo_ACC
{
public float _acc_X;
public float _acc_Y;
public float _acc_Z;
}
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct CSmouduleInfo_LL
{
public float _latitude;
public float _longitude;
}
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct CSInfoStrcutre
{
[MarshalAs(UnmanagedType.U1, SizeConst = 1)]
public byte _temp_O_MCU;
public float _temp_env;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U1)]
public byte[] gp;
public CSModuleInfo_ACC acc;
public CSmouduleInfo_LL ll;
}
字节转结构体 结构体转字节函数
class Byte2Struct
{
public static byte[] StructToBytes(object structObj)
{
int size = Marshal.SizeOf(structObj);
byte[] bytes = new byte[size];
IntPtr structPtr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(structObj, structPtr, false);
Marshal.Copy(structPtr, bytes, 0, size);
Marshal.FreeHGlobal(structPtr);
return bytes;
}
public static object BytesToStuct(byte[] bytes, Type type)
{
int size = Marshal.SizeOf(type);
if (size > bytes.Length)
{
return null;
}
IntPtr structPtr = Marshal.AllocHGlobal(size);
Marshal.Copy(bytes, 0, structPtr, size);
object obj = Marshal.PtrToStructure(structPtr, type);
Marshal.FreeHGlobal(structPtr);
return obj;
}
}
Form1.cs
引用
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Sunny.UI;
using System.Threading;
定义一个串口
public static SerialPort serialPort1 = new SerialPort();
核心代码(接收回调函数)
int HEAD1 = 0x80;
int HEAD2 = 0x81;
int HEAD3 = 0x82;
byte[] infoArray = new byte[27];
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
Thread.Sleep(40);
int ptr=0;
int len = serialPort1.BytesToRead;
Byte[] readBuffer = new Byte[100];
serialPort1.Read(readBuffer, 0, len);
for (ptr=0;ptr<len;ptr++)
{
if((readBuffer[ptr] == HEAD1) && (readBuffer[ptr + 1] == HEAD2) && (readBuffer[ptr+2]==HEAD3))
{
ptr += 3;
for (int i=0;i<27;i++)
{
infoArray[i] = readBuffer[ptr+i];
}
CSInfoStrcutre fram = (CSInfoStrcutre)Byte2Struct.BytesToStuct(infoArray, typeof(CSInfoStrcutre));
this.Invoke((EventHandler)delegate
{
uiRichTextBox1.AppendText("\r\n" + "_temp_O_MCU:"+fram._temp_O_MCU+ "\r\n"+ "_temp_env:"+fram._temp_env+ "\r\n" + "gp[1]" + fram.gp[1] + "\r\n" + "ll._longitude"+fram.ll._longitude);
});
}
}
}
Form1_Load
try
{
Updata_Serialport_Name(ComComboBox1);
BaudComboBox2.Items.Add(115200);
BaudComboBox2.Items.Add(9600);
BaudComboBox2.Text = "115200";
StopComboBox3.Items.Add(System.IO.Ports.StopBits.One);
StopComboBox3.Items.Add(System.IO.Ports.StopBits.OnePointFive);
StopComboBox3.Items.Add(System.IO.Ports.StopBits.Two);
StopComboBox3.Text = System.IO.Ports.StopBits.One.ToString();
DataComboBox4.Items.Add(8);
DataComboBox4.Text = "8";
CheckComboBox5.Items.Add(System.IO.Ports.Parity.None);
CheckComboBox5.Items.Add(System.IO.Ports.Parity.Odd);
CheckComboBox5.Items.Add(System.IO.Ports.Parity.Even);
CheckComboBox5.Text= System.IO.Ports.Parity.None.ToString();
}
catch (Exception ex)
{
MessageBox.Show("串口打开异常" + serialPort1.PortName);
}
Updata_Serialport_Name
string[] ArryPort = System.IO.Ports.SerialPort.GetPortNames();
MycomboBox.Items.Clear();
for (int i = 0; i < ArryPort.Length; i++)
{
MycomboBox.Items.Add(ArryPort[i]);
}
ComComboBox1_Click
ComComboBox1.Items.Clear();
Updata_Serialport_Name(ComComboBox1);
打开串口
try
{
serialPort1.PortName = ComComboBox1.Text;
serialPort1.BaudRate = Convert.ToInt32(BaudComboBox2.Text, 10);
Parity NIParity = (Parity)Enum.Parse(typeof(Parity), CheckComboBox5.Text);
serialPort1.Parity = NIParity;
StopBits NIStopBits = (StopBits)Enum.Parse(typeof(StopBits), StopComboBox3.Text);
serialPort1.StopBits = NIStopBits;
serialPort1.DataBits = Convert.ToInt32(DataComboBox4.Text, 10);
serialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(serialPort1_DataReceived);
serialPort1.ReceivedBytesThreshold = 1;
serialPort1.Open();
OpenuiButton2.Enabled = false;
CloseuiButton3.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show( ex.ToString());
}
GetCurTime
System.DateTime currentTime = new System.DateTime();
currentTime = System.DateTime.Now;
string res;
res = (currentTime.Hour < 10 ? ("0" + currentTime.Hour) : currentTime.Hour + "") + ":" + (currentTime.Minute < 10 ? ("0" + currentTime.Minute) : currentTime.Minute + "") + ":" + (currentTime.Second < 10 ? ("0" + currentTime.Second) : currentTime.Second + "");
return res;
关闭串口
try
{
serialPort1.Close();
OpenuiButton2.Enabled = true;
CloseuiButton3.Enabled = false;
}
catch
{
}
发送
try
{
serialPort1.WriteLine(NameTextBox2.Text);
String time = GetCurTime() + ":";
RecevieTextBox1.AppendText("\r\n"+"发送" +time + " " + NameTextBox2.Text);
}
catch (Exception err)
{
MessageBox.Show("串口数据写入错误", "发送错误");
}
ClearuiButton1_Click
RecevieTextBox1.Clear();
STM32
main
uint8_t aRxBuffer;
char RxBuffer[255]={0};
HAL_UART_Receive_IT(&huart1,&aRxBuffer, 1);
中断回调函数
uint8_t Uart1_Rx_Cnt=0;
extern uint8_t aRxBuffer;
extern char RxBuffer[255];
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(Uart1_Rx_Cnt >= 255)
{
Uart1_Rx_Cnt = 0;
memset(RxBuffer,0x00,strlen(RxBuffer));
HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF);
}
else
{
RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer;
if((RxBuffer[Uart1_Rx_Cnt-1] == 49)&&(RxBuffer[Uart1_Rx_Cnt-2] ==48)&&(RxBuffer[Uart1_Rx_Cnt-3] ==53&&(RxBuffer[Uart1_Rx_Cnt-4] ==50)))
{
CSInfoS ni;
ni._temp_O_MCU=27;
ni._temp_env=27.5;
ni.gp[0]=100;
ni.gp[1]=50;
ni.acc._acc_X=21;
ni.ll._latitude=11.1;
ni.ll._longitude=12.2;
CSInfo_PackAndSend(&ni);
Uart1_Rx_Cnt = 0;
memset(RxBuffer,0,strlen(RxBuffer));
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
Uart1_Rx_Cnt = 0;
}
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
}
结构体处理函数.c
#include "StructSerialPort.h"
void CSInfo_PackAndSend(ptrCSInfo ptrInfoStructure)
{
uint8_t infoArray[27]={0};
uint8_t infoPackage[30]={0};
CSInfo_2Array_uint8(ptrInfoStructure,infoArray);
CSInfo_Pack(infoPackage,infoArray,sizeof(infoArray));
CSInfo_SendPack(infoPackage,sizeof(infoPackage));
}
void CSInfo_2Array_uint8(ptrCSInfo infoSeg,uint8_t* infoArray)
{
int ptr=0;uint8_t
*infoElem=(uint8_t*)infoSeg;
for(ptr=0;ptr<sizeof(CSInfoS);ptr++){
infoArray[ptr] = (*(infoElem+ptr));
}
}
void CSInfo_Pack(uint8_t* infopackage,uint8_t* infoArray,uint8_t infoSize)
{
uint8_t ptr=0;
infopackage[0] = HEAD1;
infopackage[1] = HEAD2;
infopackage[2] = HEAD3;
for(;ptr<infoSize;ptr++){
infopackage[ptr+3] = infoArray[ptr];
}
}
void CSInfo_SendPack(uint8_t* infoPackage,uint8_t packSize)
{
int ptr=0;
for(ptr=0;ptr<packSize;ptr++){
USART_SendByte(infoPackage[ptr]);
}
}
void USART_SendByte(uint8_t byte)
{
HAL_UART_Transmit(&huart1,&byte,1, 1000);
}
结构体处理函数.h
#ifndef StructSerialPort
#define StructSerialPort
#include "main.h"
#include "usart.h"
#define HEAD1 0x80
#define HEAD2 0x81
#define HEAD3 0x82
#define TAIL1 0xFF
#define TAIL2 0xFF
#define TAIL3 0xFF
#define BYTE_ALIGN __attribute__ ((packed))
typedef struct CSModuleInfo_ACC{
float _acc_X;
float _acc_Y;
float _acc_Z;
} BYTE_ALIGN CSInfo_Acc;
typedef struct CSmouduleInfo_LL{
float _latitude;
float _longitude;
}BYTE_ALIGN CSInfo_LL;
typedef struct CSInfoStrcutre CSInfoS;
typedef struct CSInfoStrcutre{
uint8_t _temp_O_MCU;
float _temp_env;
uint8_t gp[2];
CSInfo_Acc acc;
CSInfo_LL ll;
}BYTE_ALIGN * ptrCSInfo;
void CSInfo_PackAndSend(ptrCSInfo ptrInfoStructure);
void CSInfo_2Array_uint8(ptrCSInfo infoSeg,uint8_t* infoArray);
void CSInfo_Pack(uint8_t* infopackage,uint8_t* infoArray,uint8_t infoSize);
void CSInfo_SendPack(uint8_t* infoPackage,uint8_t packSize);
void USART_SendByte(uint8_t byte);
#endif
问题
字节对齐问题
问题:发送结构体的时候中间总是出现很多0; 解答:stm32字节对齐问题,默认是4字节对齐的。 stm32做串口或网络传输数据时,经常需要用结构体定义帧格式。如果按照keil默认的对齐方式(4字节对齐),经常会出现结构体中补零的问题,造成帧格式错误。所以,在定义结构体类型时,最好把结构体对齐方式改为1字节对齐,防止出错。
#define BYTE_ALIGN __attribute__ ((packed))
typedef struct
{
u8 node;
u8 cmd;
u8 bytelen;
u16 data;
u16 crc;
}BYTE_ALIGN mdb_send_t;
参考
byte数组与结构体之间的转换 参考1
byte数组与结构体之间的转换 参考2
UnmanagedType 枚举:指定如何将参数或字段封送到非托管代码。
MarshalAsAttribute.ArraySubType:如果是数组的话,指定数组元素的类型
|