历史上的今天
返回首页

历史上的今天

今天是:2025年04月22日(星期二)

正在发生

2018年04月22日 | STM32简单数据传输方法与通信协议(适合串口和一般总线)

2018-04-22 来源:eefocus

引言

在一般的项目开发过程中,往往需要两块或以上单片机进行通信完成数据传输,例如四旋翼无人机在飞行过程中无线传输数据回到地面站,治疗仪器需要实时将患者和机器运转情况传回上位机平台,粮仓温控装置需将各种传感器通过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

        {

            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;

        }

    }                                            

这样的传输协议往往在两个一对一的传输中比较好用,主要在接受缓存部分使用了状态机机制,并且定义了简单的帧头和结束帧,显然这样的通信协议并不可靠,遇到复杂的情况就不好办了。

复杂情况

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

数据发送

/*----------------------------------------------------------

 + 实现功能:数传数据发送

 + 调用参数:要发送的数据组 数据长度

----------------------------------------------------------*/

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);

    }

...

}

数据接收

那么如何对接收到的数据解析?每次接收到的数据长度是多少?

一般写个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);

    }

接受数据过程中怎样处理接收数据的状态?如何对接收到的数据判断、校验?

通过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);

    }

}

小结

对项目中使用的数据传输方法进行了简单总结,并且针对复杂和简单情况的通信协议进行了分析汇总,看似复杂的总线通信技术在仔细的推敲下想上手并不难,当然在工业和高要求行业的应用肯定不是这么简单,这里只是为了方便以后的学习和再利用,与大家共勉


推荐阅读

史海拾趣

意华(CZT)公司的发展小趣事

自1995年成立以来,意华(CZT)始终秉持“诚信”的合作态度和“创新”的发展思路。在公司初创阶段,面对激烈的市场竞争,意华坚持高质量的产品和服务,赢得了客户的信赖。同时,公司不断投入研发,推出了一系列具有创新性的电子产品和连接器,迅速在市场上树立了良好的口碑。

Beck IPC GmbH公司的发展小趣事

Beck IPC始终坚持以研发和创新为核心驱动力。公司每年投入大量资金用于研发新技术和产品,并建立了完善的研发团队和体系。通过不断的技术创新和产品升级,Beck IPC始终保持着在嵌入式通讯和工业物联网领域的领先地位。同时,公司还积极参与国际标准制定和行业交流活动,为推动行业发展贡献自己的力量。

静芯微电子(ElecSuper)公司的发展小趣事

静芯微电子深知产品质量对于企业的重要性,因此建立了完善的质量管理体系。公司从原材料采购、生产制造到产品检测等各个环节都实行严格的质量控制和管理,确保每一款产品都符合高标准的质量要求。同时,静芯微电子还引进了先进的检测设备和技术手段,对产品质量进行全面检测和评估。这些措施保证了静芯微电子产品的稳定性和可靠性,赢得了客户的信任和好评。

Aerotronics Marketing Inc公司的发展小趣事

Aerotronics Marketing Inc公司注重与高校和研究机构的合作,通过产学研合作的方式推动技术创新。公司与多所知名高校建立了紧密的合作关系,共同开展无人机技术的研发和应用研究。这种合作模式不仅为公司提供了源源不断的技术支持和人才储备,还推动了整个电子行业的技术进步。

Alpha Industries公司的发展小趣事

在环保意识日益增强的今天,Alpha Industries积极响应绿色生产的号召。公司利用电子技术优化生产流程,减少能源消耗和废弃物排放。此外,Alpha Industries还推出了采用环保材料制成的军事服装系列,以实际行动践行绿色环保理念。这些举措不仅有助于提升公司的社会形象,也为公司的可持续发展奠定了坚实基础。

这些故事展示了Alpha Industries如何在电子技术的推动下,不断创新并适应市场变化,进而在相关领域取得显著发展。尽管这些故事并未直接涉及电子行业的核心业务,但它们却充分体现了Alpha Industries在技术创新和市场拓展方面的积极努力和取得的成果。

芯茂微电子公司的发展小趣事

芯茂微电子始终坚持以创新驱动发展,不断投入研发资源,推动技术创新和产品升级。同时,公司积极寻求与国内外知名企业和研究机构的合作,共同开展技术研发和市场拓展。通过与合作伙伴的紧密合作,芯茂微电子在集成电路设计、制造工艺、封装测试等方面取得了重要进展,进一步提升了公司的核心竞争力和市场地位。

问答坊 | AI 解惑

Spartan-6 FPGA User Guide

Packaging and Pinouts - Advance Specification This advance specification includes the tables for device/package combinations and maximum I/Os, pin definitions, pinout tables, pinout diagrams, mechanical drawings, and thermal spec ...…

查看全部问答>

到了新单位,接手一个新项目,让我认识到了开发板的重要性

从刚毕业时候买的单片机到FPGA再到ARM,可以说我每样都买了一块学习板,但一直以来都是拿它做为学习之用,根本没有用到实际工作中去,这次到了新单位,接手一个项目是对他们原来的一个产品进行性能和可靠性进行提高,原来的产品是用51做的,这次我 ...…

查看全部问答>

WINCE字体的问题!

请问:修改WINCE的字体变使它变小后,窗口上文字又显示不清晰!有什么好办法解决呀!谢谢了!…

查看全部问答>

求单片机控制程序源代码

各位大侠帮忙啊~ 求单片机控制程序源代码,单片机控制状态的转移  就是实现标签和阅读器的控制 希望做过有经验的 帮帮忙啊  感谢感谢…

查看全部问答>

wince下如何进行音频压缩?

wince下如何进行音频压缩?压成MP3或者WMA都行~~ 用directshow能在wince下进行音频压缩吗?? 谁能提供一个WINCE下音频压缩的库啊?? 不胜感激~~ …

查看全部问答>

个人觉得写得很好的关于TC35的文章

 自己在进行TC35调试过程中觉得这两篇文章对调试还是有很大的帮助的,分享给大家…

查看全部问答>

STM32的RTC问题 难搞啊

被STM32的RTC搞晕的过来看看本来打算放弃内置的RTC而采用DS1302了,无奈1302的通信太慢,屡次不成功,就把扔在一边的老板子拿来又试了试,这次没用自己原来的程序,在网上又新找了一个,结果程序都不用动的居然就行了··· 芯片是VCT6,晶振是那 ...…

查看全部问答>

用芯币或E金币能收到POS机套件吗?

POS机套件确实是个好东西,但对于非营利性的DIY者,想利用它确实麻烦,看看投资吧 1、PCB板费。象AM3715这个CUS封装的,最起码需要4层板,一般六层。打样一次,不要埋孔也得一千多。 2、贴片。网上有提供贴片的,按每点算,1~2分,一个CPU 425个 ...…

查看全部问答>

在别处拷贝的程序,出现了点问题,求帮助

**************************************************第一部分**************************************************** /********************************************************* 程序功能:MCU的片内ADC对P6.0端口的电压进行转换   &nb ...…

查看全部问答>