1. 程式人生 > >轉載 STM32簡單資料傳輸方法與通訊協議(適合串列埠和一般匯流排)

轉載 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 
版權宣告:本文為博主原創文章,轉載請附上博文連結!