转载 STM32简单数据传输方法与通信协议(适合串口和一般总线)

版权声明:谢谢你那么厉害还看了我的文章,欢迎转载交流学习~    https://blog.csdn.net/kilotwo/article/details/79307090
引言
在一般的项目开发过程中,往往需要两块或以上单片机进行通信完成数据传输,例如四旋翼无人机在飞行过程中无线传输数据回到地面站,治疗仪器需要实时将患者和机器运转情况传回上位机平台,粮仓温控装置需将各种传感器通过RS485总线或者CAN总线的方式达到数据传输的目的等等,这些数据传输往往需要合适稳定的总线和灵活的通信协议,我发现无论什么数据传输,原理大同小异,这里简单以stm32的几种数据传输总结下平时项目中用的一些传输方法。

通信协议
简单情况(如一对一)
首先在数据传输前一定要想好通信协议,如果传输的数据和过程非常简单,那么就可以采用简单的传输协议,例如: 
 
直接上代码:

int temp;   
u8 RS485_receive_str[128];   //接收缓冲,最大128个字节.
u8 uart_byte_count=0;        //接收到的数据长度
        ...
/****************************************************************************
* void RS485_Receive_Data(u8 *buf,u8 *len)
* RS485查询接收到的数据
* 入口参数:buf:接收缓存首地址
            len:读到的数据长度   
****************************************************************************/
void RS485_Receive_Data(u8 *buf,u8 *len)
{
    u8 rxlen=uart_byte_count;
    u8 i=0;
    *len=0;                //默认为0
    delay_ms(10);        //等待10ms,连续超过10ms没有接收到一个数据,则认为接收结束

    if(rxlen==uart_byte_count&&rxlen) //接收到了数据,且接收完成了
    {
        for(i=0;i<rxlen;i++)
        {
            buf[i]=RS485_receive_str[i];    
        }       
        *len=uart_byte_count;   //记录本次数据长度
        uart_byte_count=0;        //清零
    }
}
//接收中断服务函数
int state=0;
void USART2_IRQHandler(void)
{
    u8 rec_data;        
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)//接收到数据
    {       
        rec_data =(u8)USART_ReceiveData(USART2);                 //(USART2->DR) 读取接收到的数据
        if(rec_data=='S'&&state==0)                              //如果是S,表示是命令信息的起始位
        {
            state=1;
            uart_byte_count=0x00; 
        }else if(rec_data=='E'&&state==2)                         //如果E,表示是命令信息传送的结束位并开始处理数据
        {
            state=0;
            if(RS485_receive_str[0]==0x00)                      //判断地址 地址正确
            {
                if(RS485_receive_str[1]==0x02)                  //接受温度数据
                {
                    temp=RS485_receive_str[5]<<24|RS485_receive_str[2]|RS485_receive_str[3]<<8|RS485_receive_str[4]<<16;

                }   else if(RS485_receive_str[1]==0x03)         //led控制回馈
                {
                    led=RS485_receive_str[2];

                }
            }
        }else if(state==1)                                  //一位位接收数据并装入缓存
        {
            RS485_receive_str[uart_byte_count++]=rec_data;
            if(uart_byte_count==6)
                state=2;
        }
    }                                            

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
这样的传输协议往往在两个一对一的传输中比较好用,主要在接受缓存部分使用了状态机机制,并且定义了简单的帧头和结束帧,显然这样的通信协议并不可靠,遇到复杂的情况就不好办了。

复杂情况
复杂情况的协议可以先制定协议表,再做细分,帧头+功能字+长度+数据+校验位,这样的协议既能满足多功能的场合也能避免数据过多出现错误,比较通用。 
例如 GPS定位下位机协议: 
 


遥控上位机协议:


SUM所有字节的和:等于从该数据帧第一字节开始,也就是帧头开始,至该帧数据的最后一字节所有字节的和,只保留低八位,高位舍去。
LEN有效数据长度:表示该数据帧内包含数据的字节长度,(所有数据 除了:帧头、功能字、长度字节和最后的校验位),只是数据的字节长度和。 
比如该帧数据内容为3个int16型数据,那么会以6个char形式发送,那么LEN等于6
返回校验是YES的,飞控在收到该帧数据后,需要立即返回CHECK数据帧,也就是AAAAEF数据帧。
设置传输速度
一般选用尽可能低的传输速度下满足通信,对于无线数传来说,传输速度越低意味着越远的传输距离。 
例如通信的波特率为38400等等。

代码实现
由于前面定义了适合的通信协议,所以在代码部分也必须严格按照用通信协议进行编写

宏定义
在数据传输.c文件中,可以预先宏定义一些固定格式的转换或者标志位,例如下面这样:

/* 数据拆分宏定义,在发送大于8位的数据类型时,比如int16、int32等,需要把数据拆分成8位逐个发送 */
#define BYTE0(dwTemp)       ( *( (char *)(&dwTemp) + 0) )
#define BYTE1(dwTemp)       ( *( (char *)(&dwTemp) + 1) )
#define BYTE2(dwTemp)       ( *( (char *)(&dwTemp) + 2) )
#define BYTE3(dwTemp)       ( *( (char *)(&dwTemp) + 3) )
/* 发送帧头 接收帧头*/
#define title1_send 0xAA
#define title2_send 0xAA
#define title1_received 0xAA
#define title2_received 0xAF
/* 等待发送数据的标志 */
u8 wait_for_translate;
/* 等待发送数据的标志 */
dt_flag_t f;
/* 发送数据缓存数组 */
u8 data_to_send[50];
/* 是否写入并保存数据 */
u16 flash_save_en_cnt = 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
数据发送
/*----------------------------------------------------------
 + 实现功能:数传数据发送
 + 调用参数:要发送的数据组 数据长度
----------------------------------------------------------*/
void DT_Send_Data(u8 *dataToSend , u8 length)
{
    /* 串口2发送 要发送的数据组 数据长度 */
    if(wait_for_translate)
        Usart2_Send(data_to_send, length);
}

/*----------------------------------------------------------
 + 实现功能:校验累加和回传
 + 调用参数:字帧 校验累加和
----------------------------------------------------------*/
static void DT_Send_Check(u8 head, u8 check_sum)
{
    /* 数据内容 */
    data_to_send[0]=title1_send;
    data_to_send[1]=title2_send;
    data_to_send[2]=0xEF;
    data_to_send[3]=2;
    data_to_send[4]=head;
    data_to_send[5]=check_sum;

    /* 校验累加和计算 */
    u8 sum = 0;
    for(u8 i=0; i<6; i++)
        sum += data_to_send[i];
    data_to_send[6]=sum;
    /* 发送 要发送的数据组 数据长度 */
    DT_Send_Data(data_to_send, 7);
}

/*----------------------------------------------------------
 + 实现功能:发送速度信息
 + 调用参数:向北速度 向西速度 向上速度 单位毫米每秒
----------------------------------------------------------*/
void DT_Send_Speed(float x_s,float y_s,float z_s)
{
    u8 _cnt=0;
    vs16 _temp;

    data_to_send[_cnt++]=title1_send;
    data_to_send[_cnt++]=title2_send;
    data_to_send[_cnt++]=0x0B;
    data_to_send[_cnt++]=0;

    _temp = (int)(x_s*100.0f);
    data_to_send[_cnt++]=BYTE1(_temp);
    data_to_send[_cnt++]=BYTE0(_temp);
    _temp = (int)(y_s*100.0f);
    data_to_send[_cnt++]=BYTE1(_temp);
    data_to_send[_cnt++]=BYTE0(_temp);
    _temp = (int)(z_s*100.0f);
    data_to_send[_cnt++]=BYTE1(_temp);
    data_to_send[_cnt++]=BYTE0(_temp);


    data_to_send[3] = _cnt-4;

    u8 sum = 0;
    for(u8 i=0; i<_cnt; i++)
        sum += data_to_send[i];
    data_to_send[_cnt++]=sum;

    DT_Send_Data(data_to_send, _cnt);
}

/*----------------------------------------------------------
 + 实现功能:发送高度信息
 + 调用参数:发送气压计高度 超声波高度 发送单位厘米
----------------------------------------------------------*/
void DT_Send_Senser2(s32 bar_alt,u16 csb_alt)
{
    u8 _cnt=0;

    data_to_send[_cnt++]=title1_send;
    data_to_send[_cnt++]=title2_send;
    data_to_send[_cnt++]=0x07;
    data_to_send[_cnt++]=0;

    data_to_send[_cnt++]=BYTE3(bar_alt);
    data_to_send[_cnt++]=BYTE2(bar_alt);
    data_to_send[_cnt++]=BYTE1(bar_alt);
    data_to_send[_cnt++]=BYTE0(bar_alt);

    data_to_send[_cnt++]=BYTE1(csb_alt);
    data_to_send[_cnt++]=BYTE0(csb_alt);

    data_to_send[3] = _cnt-4;

    u8 sum = 0;
    for(u8 i=0; i<_cnt; i++)
        sum += data_to_send[i];
    data_to_send[_cnt++] = sum;

    DT_Send_Data(data_to_send, _cnt);
}
/*----------------------------------------------------------
 + 实现功能:自定义发送
----------------------------------------------------------*/
void DT_Send_User()
{
    u8 _cnt=0;
    vs16 _temp;

    data_to_send[_cnt++]=title1_send;
    data_to_send[_cnt++]=title2_send;
    data_to_send[_cnt++]=0xf1; //用户定义功能字
    data_to_send[_cnt++]=0;

    _temp = 0;           //1
    data_to_send[_cnt++]=BYTE1(_temp);
    data_to_send[_cnt++]=BYTE0(_temp);

    _temp = 0;           //1
    data_to_send[_cnt++]=BYTE1(_temp);
    data_to_send[_cnt++]=BYTE0(_temp);

    _temp = 0;
    data_to_send[_cnt++]=BYTE1(_temp);
    data_to_send[_cnt++]=BYTE0(_temp);

    _temp = 0;
    data_to_send[_cnt++]=BYTE1(_temp);
    data_to_send[_cnt++]=BYTE0(_temp);

    data_to_send[3] = _cnt-4;

    u8 sum = 0;
    for(u8 i=0; i<_cnt; i++)
        sum += data_to_send[i];

    data_to_send[_cnt++]=sum;

    DT_Send_Data(data_to_send, _cnt);
}

/*----------------------------------------------------------
 + 实现功能:任务调度调用周期1ms
----------------------------------------------------------*/
void Call_Data_transfer(void)
{
    /* 定义局部静态变量控制发送周期 */
    static int cnt = 0;
    /* cnt是从1到10000的数据 */
    if(++cnt>10000) cnt = 1;
    /* 1发送姿态数据,周期49ms */
    if((cnt % 49) == 0)
      //  f.send_status = 1;
          f.send_senser2 = 1;
    /* 2发送速度数据,周期199ms */
    if((cnt % 199) == 0)
        f.send_speed = 1;
        ...
    /* 6发送高度数据,周期399ms */
    if((cnt % 399) == 0)
     //   f.send_senser2 = 1;
          f.send_status = 1;

    /* 1发送姿态数据,周期49ms */
    if(f.send_status)
    {
        f.send_status = 0;
        /* 横滚、俯仰、航向、气压cm高度、控制高度模式、解锁状态 */
        DT_Send_Status(IMU_Roll,IMU_Pitch,IMU_Yaw,(0.1f *baro_height),height_ctrl_mode,unlocked_to_fly);
    }
    /* 2发送速度数据,周期199ms */
    else if(f.send_speed)
    {
        f.send_speed = 0;
        /* 向北速度 向西速度 向上速度 单位毫米每秒 */
        DT_Send_Speed(0.1f *north_speed,0.1f *west_speed,0.1f *wz_speed);
    }
...

    /* 6发送高度数据 */
    else if(f.send_senser2)
    {
        f.send_senser2 = 0;
        /* 发送气压计高度 超声波高度 发送单位厘米 */
        DT_Send_Senser2(baro_height*0.1f,ultra_distance/10);
    }
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
数据接收
那么如何对接收到的数据解析?每次接收到的数据长度是多少?
一般写个USART2_IRQHandler类似函数为接收中断,系统会自动调用每次只能接收到单字节数据,通过中断的方式调用函数DT_Data_Receive_Prepare将接收到的数据完整的组合在一起

/*----------------------------------------------------------
 + 实现功能:串口发送数据
 + 中断调用
----------------------------------------------------------*/
void USART2_IRQHandler(void)
{
    /* 接收数据临时变量 */
    u8 com_data;

    /* 判断过载错误中断 */
    if(USART2->SR & USART_SR_ORE)
        com_data = USART2->DR;

    /* 判断是否接收中断 */
    if( USART_GetITStatus(USART2,USART_IT_RXNE) )
    {
        /* 清除中断标志 */
        USART_ClearITPendingBit(USART2,USART_IT_RXNE);

        /* 接收数据及后续的任务 */
        com_data = USART2->DR;

        /* 数传数据处理解析 */
        DT_Data_Receive_Prepare(com_data);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
接受数据过程中怎样处理接收数据的状态?如何对接收到的数据判断、校验?
通过Mooer状态机的方式: 
Mooer状态机的输出只与当前的状态有关,也就是数当前的状态决定输出,输入只决定状态机的状态改变。 
如何数据校验:当判断输入数据无效时重新等待判断下一帧数据


/*----------------------------------------------------------
 + 实现功能:数据接收并保存
 + 调用参数:接收到的单字节数据
----------------------------------------------------------*/
void DT_Data_Receive_Prepare(u8 data)
{
    /* 局部静态变量:接收缓存 */
    static u8 RxBuffer[50];
    /* 数据长度 *//* 数据数组下标 */
    static u8 _data_len = 0,_data_cnt = 0;
    /* 接收状态 */
    static u8 state = 0;

    /* 帧头1  一个数据帧中第一个数据并且判断是否与宏定义帧头1相等*/        
    if(state==0&&data==title1_received)
    {
        state=1;
        RxBuffer[0]=data;
    }
    /* 帧头2 一个数据帧中第二个数据并且判断是否与宏定义帧头2相等*/
    else if(state==1&&data==title2_received)
    {
        state=2;
        RxBuffer[1]=data;
    }
    /* 功能字 */
    else if(state==2&&data<0XF1)
    {
        state=3;
        RxBuffer[2]=data;
    }
    /* 长度 */
    else if(state==3&&data<50)
    {
        state = 4;
        RxBuffer[3]=data;
        _data_len = data;
        _data_cnt = 0;
    }
    /* 接收数据组*/
    else if(state==4&&_data_len>0)
    {
        _data_len--;
        RxBuffer[4+_data_cnt++]=data;
        if(_data_len==0)
            state = 5;
    }
    /* 校验累加和 */
    else if(state==5)
    {
        state = 0;
        RxBuffer[4+_data_cnt]=data;
        DT_Data_Receive_Anl(RxBuffer,_data_cnt+5);  //调用数据分析函数,总长比索引+1
    }
    /* 若有错误重新等待接收帧头 */
    else
        state = 0;
}
/*----------------------------------------------------------
 + 实现功能:数据分析
 + 调用参数:传入接受到的一个数据帧和长度
----------------------------------------------------------*/
void DT_Data_Receive_Anl(u8 *data_buf,u8 num)
{
    u8 sum = 0;
    /* 首先计算校验累加和 */
    for(u8 i=0; i<(num-1); i++)
        sum += *(data_buf+i);
    /* 判断校验累加和 若不同则舍弃*/
    if(!(sum==*(data_buf+num-1)))       return;
    /* 判断帧头 */
    if(!(*(data_buf)==title1_received && *(data_buf+1)==title2_received))       return;
    /* 判断功能字:主要命令集 */
    if(*(data_buf+2)==0X01)
    {
        /* 加速度计校准 */
        if(*(data_buf+4)==0X01)
        {
            mpu6050.Acc_CALIBRATE = 1;
            start_height=0;
        }
        /* 陀螺仪校准 */
        else if(*(data_buf+4)==0X02)
        {
            mpu6050.Gyro_CALIBRATE = 1;
            start_height=0;
        }
...
    }
    /* 判断功能字:次要命令集 */
    if(*(data_buf+2)==0X02)
    {
     ...
    }
    /* 判断功能字 接收数据 */
    if(*(data_buf+2)==0X03)
    {
   ...
    }

    /* 回传校验累加和 */
    if(*(data_buf+2)==0X14)
    {
        DT_Send_Check(*(data_buf+2),sum);
    }
    /* 回传校验累加和 */
    if(*(data_buf+2)==0X15)
    {
        DT_Send_Check(*(data_buf+2),sum);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
小结
对项目中使用的数据传输方法进行了简单总结,并且针对复杂和简单情况的通信协议进行了分析汇总,看似复杂的总线通信技术在仔细的推敲下想上手并不难,当然在工业和高要求行业的应用肯定不是这么简单,这里只是为了方便以后的学习和再利用,与大家共勉! o(∩_∩)o
--------------------- 
作者:Kilotwo_zero 
来源:CSDN 
原文:https://blog.csdn.net/kilotwo/article/details/79307090 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/figa_figa/article/details/85092035