精华内容
下载资源
问答
  • ZLSN3003是一款TTL电平串口转以太网的联网模块,ZLSN3003可以实现TTL电平串口数据和TCP/IP/UDP数据的透明传输,实现用户MCU通过串口连接网络。 ZLSN3003是方便ZLSN2003的应用设计的,由于ZLSN2003需要设计电路板才...
  • 一、必须要先知道的储备知识 树莓派4B的外设一共包含两个串口,一个称之为硬件串口(/dev/ttyAMA0),一个称...树莓派(3/4代)板载蓝牙模块,默认的硬件串口分配给蓝牙模块使用的,而性能较差的mini串口分配给GPI
  • 单片机多串口数据转发模型

    千次阅读 2021-10-11 10:10:13
    单片机多串口数据转发模型51黑电子论坛地址:[http://www.51hei.com/bbs/dpj-213065-1.html](http://www.51hei.com/bbs/dpj-213065-1.html)实际需求矛盾的产生软件设计思想建立数据结构 51黑电子论坛地址:...

    51黑电子论坛地址:http://www.51hei.com/bbs/dpj-213065-1.html

    实际需求

    最近在做一个西门子 Step 200 系列的PLC通讯口扩展项目时,遇到了这样的问题:

    • 224XP ,这个CPU的外部通讯端口只用两个,在物联网大火的当下,这样的扩展口数量,在加入联网模块后,显然无法满足更多的联网需求。当前实际需求如下:
    编号功能
    01PLC串口屏通讯
    02EBM风扇通讯
    034G/WIFI模块通讯
    04以太网通讯
    • 在考虑到成本与技术可行性前提下,尽可能保留产品研发核心技术手段,选用STC8系列单片机对PLC原有的两个通讯口利用串口进行扩展。设计思路如下:
      理论中多串口数据交互模型

      从图中可以看出,数据信息的主要请求目标主要是通过 PLC_PORT0 获得PLC内部存储区数据( PLC_PORT1 默认用于连接屏幕)。因此,进行软件拓展的目标物理链路就是 PLC_PORT0

    矛盾的产生

    • 从上面的模型可以看出,当前工作模式应该是一个多主单从结构。那么按照常理应该是由STC8的4个串口通过轮询的方式对共享设备PLC
      目标地址发出数据请求的命令,随后由PLC把响应数据返回给当前请求对象。如果严格遵循这样的工作模式,不会存在任何问题。但是,实际的架构设计需求如下:
      实际的多串口数据交互模型
    TX
    RX
    Tx
    Rx
    Tx
    Rx
    Tx
    Rx
    PLC_PORT0
    STC_UART4
    STC8_UART1
    STC8_UART2
    STC8_UART3

    其中每个通讯端口上端的标号都代表在实际的通讯过程中,STC8单片机作为扩展主机时轮询框架下的调度关系(数字越小,优先级越高;数字相等,代表处于同一优先级)。

    • 这里实际使用的时候是通过 PLC_PORT0STC8_UART4 进行物理上的连接,在通过STC8内部软件协议通过其他串口与拓展设备进行数据交互。很显然当前的架构无法满足这样的实际需求,矛盾就应运而生了。
    • 既然多主机,单从机的通讯模型无法在PLC作为主机时满足需求,那么就可以重新考虑另外一种工作模式。为了适应更多可能的情况,
      建立一种不分主从结构的工作模式,在多对象数据交互的基础上建立一种相对是一对一的通讯机制。
      改进后的多串口数据交互模型

    软件设计思想

    基础数据结构
    从图中可以看出,最上层采用的是循环队列,每个队列的元素由一条链表进行连接,每条链表的一个节点代表一帧数据。

    标识符意义
    Frame_Flag帧标志:由定时器帧中断机制置为true;轮询转发程序转发当前帧后置为false
    Timer_Flag帧中断定时器开启标志:当任意串口接收中断收到一个字节数据时设置为true;超时后设置false
    Rx_Buffer数据帧接收缓冲区
    Rx_Length当前数据帧长度
    OverTime帧判定时间:该变量在串口中断有字节数据接收时会不断刷新;在帧仲裁定时器中其值不断减小至0

    详细工作原理:
    以PLC通过485总线发送数据为例,假设PLC当前要像EBM请求某一个状态值,发出一帧数据 15 21 01 CA,此时EBM响应数据为 35 01 01 00 CA ,则:

    • 串口四接收中断收到PLC发出的第一个字节,打开帧中断定时器,判断当前写指针所对应的链表节点帧标志是否为false,条件成立后判断当前节点帧长度是否溢出,如果没有就刷新当前帧链表块中 OverTime , 最后把当前字节 15 存到当前帧缓冲区 Rx_Buffer 的位置上。
    • 后续字符 21 01 CA 的接收操作与第一个字符一致,其中每个字节间间隔由通讯的波特率决定,<<Timer(OverTime) ,当接收完这一帧数据后,OverTime值将不会在串口接收中断中被刷新,而是由帧中断定时器中不断减小为0,最终标志该节点上这帧数据接收完成,并把对应的 Frame_Flag置为true。
    • 在主程序轮询机制中,一旦检测到有 Frame_Flag 产生,则利用读指针访问当前节点帧缓冲区,对目标设备发出请求命令。
    • 响应数据返回给目标对象的工作过程与前三个步骤完全一致。值得注意的是,入果存在对个数据交换序列 PLC_PORT0-->UART4-->UART3UART2-->UART4-->PLC_PORT0,存在相反的公共序列 PLC_PORT0-->UART4UART4-->PLC_PORT0,此时如果公用的是同一个缓冲区,且不对不同类型的数据进行分流,将会造成不同请求对象数据响应错误,所以必须加以条件限制。

    建立数据结构

    @function:1.0.0 **基础数据结构**
    
    /*链队数据结构*/
    typedef struct 
    {
    	uint8_t Frame_Flag;	/*帧标志*/
       uint8_t Timer_Flag;	/*打开定时器标志*/
       uint8_t Rx_Buffer[MAX_SIZE];	/*数据接收缓冲区*/
       uint16_t Rx_Length;	/*数据接收长度*/
       uint16_t OverTime;	/*目标设备响应超时时间*/
    }Uart_Queu;
    
    typedef struct 
    {
    	Uart_Queu LNode[MAX_NODE];
    	/*存储R ,W指针,表示一个队列*/
      uint8_t Wptr;
      uint8_t Rptr;
    }Uart_List;
    
    /*声明链队*/
    extern Uart_List Uart_LinkList[MAX_LQUEUE];
    

    顶层数据结构采用环形队列,只不过队列中的单个元素并不是一个单一的值,而是一个带有记录信息的数据块 Uart_Queu 。这样做的目的在于,使用的单片机是C51,其本身的串口是不带有空闲中断或者DMA这些高级硬件的,那这就需要我们通过软件算法模拟这一些硬件功能 来完成功能设计。

    @function: 1.0.1 **改进后基础数据结构**
     /*链队数据结构*/
    typedef struct 
    {
    	uint8_t Frame_Flag; /*帧标志*/
     	uint8_t Timer_Flag; /*打开定时器标志*/
     	uint8_t Rx_Buffer[MAX_SIZE]; /*数据接收缓冲区*/
    	uint16_t Rx_Length; /*数据接收长度*/
    	uint16_t OverTime; /*目标设备响应超时时间*/
    	Uart_Queu *Next; /*指向下一个节点*/
    }Uart_Queu;
    

    主要改进了队列下数据块元素的内存分配方式,由原来的静态的分配,改为程序运行过程根据实际需求来分配。考虑 Malloc 函数在51编译器中安全性和适用性,实际使用过程建议非必要情况采用静态内存分配方式。当然,采用动态内存分配方式,使用循环链表将会带来更多的可操作性、灵活性和内存节约。

    @function: 1.0.2 **串口帧中断机制设计**
       /**
        * @brief	定时器0的中断服务函数
        * @details	
        * @param	None
        * @retval	None
        */
        void Timer0_ISR() interrupt 1
        {
    
    	    if(COM_UART1.LNode[COM_UART1.Wptr].Timer_Flag)
    		    /*以太网串口接收字符间隔超时处理*/
    		    SET_FRAME(COM_UART1);
    	    if(COM_UART2.LNode[COM_UART2.Wptr].Timer_Flag)
    		    /*4G/WiFi串口接收字符间隔超时处理*/
    		    SET_FRAME(COM_UART2);
    	    if(COM_UART3.LNode[COM_UART3.Wptr].Timer_Flag)
    		    /*RS485串口接收字符间隔超时处理*/
    		    SET_FRAME(COM_UART3);
    	    if(COM_UART4.LNode[COM_UART4.Wptr].Timer_Flag)
    		    /*PLC串口接收字符间隔超时处理*/
    		    SET_FRAME(COM_UART4);
        }
    
        /**
        * @brief	串口4中断函数
        * @details	使用的是定时器4作为波特率发生器,PLC口用
        * @param	None
        * @retval	None
        */
        void Uart4_Isr() interrupt 18 
        {   /*发送中断*/
            if (S4CON & S4TI)
            {
                S4CON &= ~S4TI;
                /*发送完成,清除占用*/
                Uart4.Uartx_busy = false;
            }
            /*接收中断*/
            if (S4CON & S4RI)
            {
                S4CON &= ~S4RI;
    
                /*当收到数据时打开帧中断定时器*/
                COM_UART4.LNode[COM_UART4.Wptr].Timer_Flag = true;
                /*当前节点还没有收到一帧数据*/
                if (!COM_UART4.LNode[COM_UART4.Wptr].Frame_Flag)
                {
                    /*刷新帧超时时间*/
                    COM_UART4.LNode[COM_UART4.Wptr].OverTime = MAX_SILENCE;
                    if (COM_UART4.LNode[COM_UART4.Wptr].Rx_Length < MAX_SIZE)
                    { /*把数据存到当前节点的缓冲区*/
                        COM_UART4.LNode[COM_UART4.Wptr].Rx_Buffer[COM_UART4.LNode[COM_UART4.Wptr].Rx_Length++] = S4BUF;
                    }
                }
            }
        }
    

    因为硬件定时器数量有限,所以几个串口的帧中断机定时器均采用了 Timer0 进行仲裁,可能会存在中断延时的问题,在硬件定时器资源充足情况下,尽可能选用硬件定时器较佳。

    @function: **1.0.3 帧中断宏**
    /*置位目标串口接收帧标志*/
        #define SET_FRAME(COM_UARTx) (COM_UARTx.LNode[COM_UARTx.Wptr].OverTime ? \
    	(COM_UARTx.LNode[COM_UARTx.Wptr].OverTime--): \
    	((COM_UARTx.LNode[COM_UARTx.Wptr].Frame_Flag = true), \
    	(COM_UARTx.Wptr = ((COM_UARTx.Wptr + 1U) % MAX_NODE)), \
    	(COM_UARTx.LNode[COM_UARTx.Wptr].Timer_Flag = false)))
    

    最后,有了这些软件机制,仅仅只需要编写对应的逻辑就可以了。

    @function: **1.0.4 多串口数据轮询处理机制**
     /*设置队列读指针*/
        #define SET_RPTR(x) ((COM_UART##x).Rptr = (((COM_UART##x).Rptr + 1U) % MAX_NODE))				   
        /*设置队列写指针*/
        #define SET_WPTR(x) ((COM_UART##x).Wptr = (((COM_UART##x).Wptr + 1U) % MAX_NODE))
    
        /*串口一对一数据转发数据结构*/
        typedef struct
        {
            SEL_CHANNEL Source_Channel; /*数据起源通道*/
            SEL_CHANNEL Target_Channel; /*数据交付通道*/
            void (*pHandle)(void);
        } ComData_Handle;
    
        /*定义当前串口交换序列*/
        const ComData_Handle ComData_Array[] =
    	{
    		{CHANNEL_PLC, CHANNEL_RS485, Plc_To_Rs485},
    		{CHANNEL_WIFI, CHANNEL_PLC, Wifi_To_Plc},
        };
    
        /*增加映射关系时,计算出当前关系数*/
        #define COMDATA_SIZE (sizeof(ComData_Array) / sizeof(ComData_Handle))
    
        /**
        * @brief	串口1对1数据转发
        * @details	
        * @param	None
        * @retval	None
        */
        void Uart_DataForward(SEL_CHANNEL Src, SEL_CHANNEL Dest)
        {
    	    uint8_t i = 0;
    
    	    for (i = 0; i < COMDATA_SIZE; i++)
    	    {
    		    if ((Src == ComData_Array[i].Source_Channel) && (Dest == ComData_Array[i].Target_Channel))
    		    {
    			ComData_Array[i].pHandle();
    		    }
    	    }
        }
    
        /**
        * @brief	串口事件处理
        * @details	
        * @param	None
        * @retval	None
        */
        void Uart_Handle(void)
        {
    	    /*数据交换序列1:PLC与RS485进行数据交换*/
    	    Uart_DataForward(CHANNEL_PLC, CHANNEL_RS485);
    	    /*数据交换序列2:WIFI与PLC进行数据交换*/
    	    Uart_DataForward(CHANNEL_WIFI, CHANNEL_PLC);
        }
    
        /**
        * @brief	PLC数据交付到RS485
        * @details	
        * @param	None
        * @retval	None
        */
        void Plc_To_Rs485(void)
        {
    	    /*STC串口4收到PLC发出的数据*/
    	    if ((COM_UART4.LNode[COM_UART4.Rptr].Frame_Flag)) //&& (COM_UART4.LNode[COM_UART4.Rptr].Rx_Length)
    	    {												  
    		    /*如果串口4接收到的数据帧不是EBM所需的,过滤掉*/
    		    if (COM_UART4.LNode[COM_UART4.Rptr].Rx_Buffer[0] != MODBUS_SLAVEADDR)
    		    {	/*标记该接收帧以进行处理*/
    			    COM_UART4.LNode[COM_UART4.Rptr].Frame_Flag = false;
    			    /*允许485发送*/
    			    USART3_EN = 1;
    			    /*数据转发给RS485时,数据长度+1,可以保证MAX3485芯片能够最后一位数据刚好不停止在串口的停止位上*/
    			    Uartx_SendStr(&Uart3, COM_UART4.LNode[COM_UART4.Rptr].Rx_Buffer, COM_UART4.LNode[COM_UART4.Rptr].Rx_Length + 1U);
    			    /*接收到数据长度置为0*/
    			    COM_UART4.LNode[COM_UART4.Rptr].Rx_Length = 0;
    			    /*发送中断结束后,清空对应接收缓冲区*/
    			    memset(&COM_UART4.LNode[COM_UART4.Rptr].Rx_Buffer[0], 0, MAX_SIZE);
    			    /*发送完一帧数据后拉低*/
    			    USART3_EN = 0;
    			    /*读指针指到下一个节点*/
    			    SET_RPTR(4);
    		    }
    
    		    /*目标设备发出应答*/
    		    if ((COM_UART3.LNode[COM_UART3.Rptr].Frame_Flag)) //&& (COM_UART3.LNode[COM_UART3.Rptr].Rx_Length)
    		    {
    			    /*标记该接收帧已经进行处理*/
    			    COM_UART3.LNode[COM_UART3.Rptr].Frame_Flag = false;
    			    /*数据返回给请求对象*/
    			    Uartx_SendStr(&Uart4, COM_UART3.LNode[COM_UART3.Rptr].Rx_Buffer, COM_UART3.LNode[COM_UART3.Rptr].Rx_Length);
    			    /*接收到数据长度置为0*/
    			    COM_UART3.LNode[COM_UART3.Rptr].Rx_Length = 0;
    			    /*发送中断结束后,清空对应接收缓冲区*/
    			    memset(&COM_UART3.LNode[COM_UART3.Rptr].Rx_Buffer[0], 0, MAX_SIZE);
    			    /*读指针指到下一个节点*/
    			    SET_RPTR(3);
    		    }
    	    }
        }
    

    如果需要PDF文档,请到51黑电子论坛主页下载。

    展开全文
  • 本文章主要讲下蓝牙串口协议SPP(Serial Port Profile)连接/接受数据/发送数据/断开连接的流程介绍,可能之前的写的底层文章你看的云里雾里,此小节就是开发从应用Profile层面来把整个地方串起来,让你们对协议栈有...

    零. 概述

    本文章主要讲下蓝牙串口协议SPP(Serial Port Profile)连接/接受数据/发送数据/断开连接的流程介绍,可能之前的写的底层文章你看的云里雾里,此小节就是开发从应用Profile层面来把整个地方串起来,让你们对协议栈有一个更深刻的认识。

    一. 声明

    本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下:

    第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。

    第二篇:Transport层介绍,主要介绍蓝牙协议栈跟蓝牙芯片之前的硬件传输协议,比如基于UART的H4,H5,BCSP,基于USB的H2等

    第三篇:传统蓝牙controller介绍,主要介绍传统蓝牙芯片的介绍,包括射频层(RF),基带层(baseband),链路管理层(LMP)等

    第四篇:传统蓝牙host介绍,主要介绍传统蓝牙的协议栈,比如HCI,L2CAP,SDP,RFCOMM,HFP,SPP,HID,AVDTP,AVCTP,A2DP,AVRCP,OBEX,PBAP,MAP等等一系列的协议吧。

    第五篇:低功耗蓝牙controller介绍,主要介绍低功耗蓝牙芯片,包括物理层(PHY),链路层(LL)

    第六篇:低功耗蓝牙host介绍,低功耗蓝牙协议栈的介绍,包括HCI,L2CAP,ATT,GATT,SM等

    第七篇:蓝牙芯片介绍,主要介绍一些蓝牙芯片的初始化流程,基于HCI vendor command的扩展

    第八篇:附录,主要介绍以上常用名词的介绍以及一些特殊流程的介绍等。

    另外,开发板如下所示,对于想学习蓝牙协议栈的最好人手一套。以便更好的学习蓝牙协议栈,相信我,学完这一套视频你将拥有修改任何协议栈的能力(比如Linux下的bluez,Android下的bluedroid)。

    -------------------------------------------------------------------------------------------------------------------------

    CSDN学院链接(进入选择你想要学习的课程):https://edu.csdn.net/lecturer/5352?spm=1002.2001.3001.4144

    蓝牙交流扣扣群:970324688

    Github代码:https://github.com/sj15712795029/bluetooth_stack

    入手开发板:https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22329603896.18.5aeb41f973iStr&id=622836061708

    蓝牙学习目录https://blog.csdn.net/XiaoXiaoPengBo/article/details/107727900

    --------------------------------------------------------------------------------------------------------------------------

    二. 蓝牙SPP协议连接/接受数据/发送数据/断开连接 流程介绍

    在上面章节我们铺垫了很多底层协议,可能你们看的可能会云里雾里,此章节就是开发从应用Profile层面来把整个地方串起来,让你们对协议栈有一个更深刻的认识。此小节我们每个协议只是会讲解到一次raw data分析,其他的封包你们要反复参照之前讲解的协议来分析,如果一旦搞清楚,会对蓝牙协议栈交互有一个深刻的认识

    本小节主要通过一个SPP的应用例程来测试开发板跟手机SPP交互的流程,其中流程包括:

    • 手机连接开发板
    • 手机往开发板发送SPP数据
    • 开发板往手机发送SPP数据
    • 手机主动断开连接

    分析工具:Ellisys(打开方式参照工具的使用)

    分析封包:spp连接_发送_接受_断开.log(在资料中STM32_UBUNTU_BLUETOOTH\2-蓝牙资料\蓝牙协议分析)

    设计到的工具:Android “蓝牙串口” apk(在资料中STM32_UBUNTU_BLUETOOTH\3-软件工具\bt_spp_apk)

    本分析为了简化流程,所以采用Base on pincode配对方式,如果想看SSP的配对方式,可以参照HCI章节的SSP配对

    整个流程如下(初始化部分不讲解,可以参照HCI章节的初始化流程,直接从手机连接开发板步骤说起):

    整个步骤比较复杂,我来一个个流程给你们拆解下,总结流程如下(备注:每个设备的交互流程稍有差别,但是总体框架一样,另外每个步骤含有几个子步骤,我们在后面介绍完整个流程再展开说明):

    步骤1)手机发送连接请求,开发板接受连接,然后收到连接完成事件

    步骤2)开发板主动问询手机支持的feature,手机回复开发板

    步骤3)开发板处理一些HCI command(Page scan reprtition Moide change/Max slot change/Link supervision timeout change)

    步骤4)手机发起L2CAP information request,开发板回复(Extened Feature/Fixed channel support)

    步骤5)手机主动连接SDP,然后L2CAP configure交互MTU参数

    步骤6)手机主动问询开发板SPP的SDP信息,开发板回复,问询完后手机断开SDP的连接

    步骤7)手机主动连接RFCOMM,然后L2CAP configure交互MTU参数。

    步骤8)手机主动连接RFCOMM signal通道,并且交互PN参数

    步骤9)手机发起配对请求,配对完成后芯片上报给协议栈Link Key

    步骤10)手机连接开发板SPP的RFCOMM server channel

    步骤11)开发板跟手机交互RFCOMM Modem status参数

    步骤12)开发板主动跟手机交互PnP SDP参数(此部分是DID协议部分,暂时略过)

    步骤13)手机主动发送SPP字符串(上层决定)

    步骤14)开发板主动发送SPP字符串(上层决定)

    步骤15)手机主动开发与开发板的连接

    下面我们就来一一分析下整个流程,

    备注:关于HCI command/event,l2cap,rfcomm,sdp封包格式我会做简短的介绍,详细的可以分别看下我之前的文章

    步骤1)手机发送连接请求,开发板接受连接,然后收到连接完成事件(HCI层面的范畴)

    整个步骤1分为以下几个小步骤组成

    用另外一种flow表示如下:

    ① 手机发起蓝牙连接,芯片收到后,发送给协议栈,相当于Remote chip -> Local Chip(Controller) -> Local BT stack(host)这个过程,在后面我们就直接说手机发起连接,协议栈收到来简化描述首先我们来看下这个event的格式。

    我们来分析下raw data

    04 0A 0A 7F 24 DF 0C 9C 0C 02 5A 01 (hex)

    04 -> HCI_Connection_Request event id

    0A -> para的长度,我们也可以看到参数部分蓝牙地址6个byte,cod 3个byte,link type 1byte

    0A 7F 24 DF 0C 9C -> 蓝牙地址(手机的)

    0C 02 5A -> cod (关于cod的分析参照我另外的一篇文章)

    01 -> link type,也就是acl

    我们来看下btsnoop工具帮我们分析的结果,可以看到跟我们一样

    ② 协议栈发送给芯片接受连接,Local芯片收到后,然后发送手机,相当于Local BT stack(host)->Local chip(Controller)->Remote chip这个过程,后续我们直接说协议栈发送给手机来简化描述。

    我们先看下这个HCI command的格式:

    我们来看下raw data

    09 04 07 0A 7F 24 DF 0C 9C 01

    09 04 -> HCI opcode ,也就是HCI_Accept_Connection_Request,我们之前也有写过怎么把OGF,OCF组装成opcode,我在这里再贴下那这个命令是OGF=1,OCF=9,

    Opcode[0] = OCF & 0xff = 0x09

    Opcode[1] = (OCF >> 8) | (OGF << 2) = 0x04,组装起来就是0x09,0x04

    07 -> para len,也就是蓝牙地址+link type的长度

    0A 7F 24 DF 0C 9C -> 蓝牙地址(手机的)

    01 -> 保持slave角色,如果是Master,那么Local stack要发送Role switch的command

    我们来看下btsnoop工具分析的结果:

    ③ 芯片发送给手机command status来表示收到的command,这个没什么好讲解的,直接就是我们发送的command的status,其他文章已经说过多次,我们不再重复

    ④ 芯片发送给手机connection complete

    我们来看下这个event的格式以及参数

    我们来分析下raw data:

    03 0B 00 4C 00 0A 7F 24 DF 0C 9C 01 00

    03 -> HCI event id

    0b -> 后续参数长度

    00 -> status success,在HCI core文档error code中有集中说明每个value代表什么意思

    4C 00 -> 连接句柄,注意,此部分重要,后续所以acl封包都会用到这个参数

    0A 7F 24 DF 0C 9C -> 蓝牙地址(手机的)

    01 -> link type ,1代表acl

    00 -> disable加密

    我们来看下btsnoop的视图分析:

    后续的HCI command/event封包我们不再一一对raw data做分析,可以举一反三,自己根据core文档分析下。

    步骤2)开发板主动问询手机支持的feature,手机回复开发板(HCI层面的范畴)

    整个步骤2分为以下几个小步骤组成

     

    ① 协议栈获取对方支持的Feature

    可以看到,此部分的HCI command的参数就是上面链接完成的连接句柄

    ② 协议栈收到芯片发送的command status

    ③ 手机发送给协议栈封包,告知协议栈手机支持的Feature

    步骤3)开发板处理一些HCI command(Page scan reprtition Moide change/Max slot change/Link supervision timeout change)(HCI协议层面范畴)

     

    Page scan等名词参照附录

    Slots可以先不用关注

    Link Supervison Timeout,大白话就是link lost的时间,在说这个timeout的时候,先来说下supervison time,比如两台设备连接,突然一台断电,可以看到另外一台并不是直接断开连接,而是过一段时间才会显示断开连接,而这个时间就是supervision time,这个supervison timeout后芯片就告知协议栈断线了·,其中检测原理我在附录中讲解

    步骤4)手机发起L2CAP information request,开发板回复(Extened Feature/Fixed channel support)(L2CAP协议层面范畴)

    整个步骤4分为以下几个小步骤组成

    ① 手机问询L2CAP information with Extened Feature Support

    我们来看下L2CAP information requese的格式(我们是用的basic mode)

    其中Information payload就是下面的

    那我们整个分析下raw data,包括L2CAP/HCI

    4C 20 0A 00 06 00 01 00 0A 02 02 00 02 00

    4C 20 -> HCl ACL header,格式如下

    Packet Boundary Flag First automatically-flushable packet(0b10)

    Broadcast Flag Point-to-point (0b00)

    然后Handle就是connection complete的0x004C,然后整个值就是4C 20

    0A 00 -> ACL数据的长度,也就是10byte

    从这里开始就是L2CAP的数据

    06 00 -> l2cap payload的长度,也就是6个byte

    01 00 -> channel id(CID)为1,也就是signal channel(L2CAP命令通道)

    0A -> L2CAP information requese code

    02 -> id,requese跟response一样

    02 00 -> 后续的长度

    后续是InfoType

    02 00 -> Extended feature supported

    下面我们来看下整个封包的btsnoop

    L2CAP的抛砖引玉到这里,后续的几个小步骤L2CAP封包自己举一反三下

    ② 给手机回复L2CAP information with Extened Feature Support的response

    ③ 手机问询L2CAP information with Fixed channel Support

    ④ 给手机回复L2CAP information with Fixed channel Support的response

    步骤5)手机主动连接SDP,然后L2CAP configure交互MTU参数(L2CAP协议层面范畴)

    ① 手机主动来连接SDP

    我们通过raw data来分析下整个连接PSM协议流程(HCI,L2CAP)

    4C 20 0C 00 08 00 01 00 02 04 04 00 01 00 52 00

    4C 20 ->

    Packet Boundary Flag First automatically-flushable packet(0b10)

    Broadcast Flag Point-to-point (0b00)

    然后Handle就是connection complete的0x004C,然后整个值就是4C 20

    0C 00 -> acl packet length

    08 00 -> l2cap basic packet length

    01 00 -> L2CAP signal通道

    下面的数据就是参照此部分格式

    02 -> l2cap connection request的code

    04 -> id,response必须跟这个一致

    04 00 -> 后续的length

    01 00 -> PSM,SDP是0x0001,后续还会有RFCOMM的PSM,所以我一起截图了

    52 00 -> source ID为0x0052,记住后面这个值,我们会用到

    我们来看下整个封包的btsnoop分析。

    ② 针对手机连接SDP回复response

    此分析流程参照其他L2CAP的分析,但是以上我们要记住一点,就是Destination cid是0x0040

    ③ 手机主动来配置MTU,我们回复

    ④ 我们主动去配置MTU,手机回复

    步骤6)手机主动问询开发板SPP的SDP信息,开发板回复,问询完后手机断开SDP的连接(SDP/L2CAP协议层面)

    分为以下几个小步骤:

    ① 手机发起SPP的相关SDP信息的问询

    我们先来看下raw data的分析(包含SDP/L2CAP/HCI)

    4C 20 18 00 14 00 40 00 06 00 00 00 0F 35 03 19 11 01 03 F0 35 05 0A 00 00 FF FF 00

    4C 20 ->

    Packet Boundary Flag First automatically-flushable packet(0b10)

    Broadcast Flag Point-to-point (0b00)

    然后Handle就是connection complete的0x004C,然后整个值就是4C 20

    18 00 -> acl packet length

    14 00 -> L2CAP basic mode的length

    40 00 -> Destination id,也就是上面L2CAP连线分配的

    后面就是SDP的数据了,SDP的数据格式是:

    06 -> SDP_SERVICE_SEARCH_ATTR_REQ

    00 00 -> TID

    00 0F -> Length

    35 03 19 11 01 -> ServiceSearchPattern,此部分是一个数据元,关于数据元的详解介绍参照我SDP相关章节的介绍

    此部分就是表示UUID 0x1101,也就是SPP

     03 F0 -> max count 1008

    35 05 0A 00 00 FF FF -> AttributeIDList:是个数据元,range是0x0000~0xffff

    00 -> ContinuationState,是0

    我们来看下btsnoop

    ② 手机回复

    此部分我们就不一一分析,可以按照前面的分析流程来做分析,btsnoop如下

    另外,为什么是这个样子呢,因为我们code中注册的是这个样子,code如图:

    注意啦:我们注册的RFCOMM server channel(scn)是8,这个值要记住,后面手机过来连接rfcomm channel的时候就是通过这个值

    ③ 手机断开SDP连接,我们回复response

    步骤7)手机主动连接RFCOMM,然后L2CAP configure交互MTU参数(L2CAP层面)

     

    此步骤L2CAP配置MTU我们在上面已经说明,所以不再重复说明,我们只来看下连接RFCOMM通道的步骤

    ① 手机过来连接RFCOMM

    ② 我们回复response

    步骤8)手机主动连接RFCOMM signal通道,并且交互PN参数(RFCOMM协议范畴)

    分为以下几个小步骤:

    ① 手机过来连接RFCOMM signal通道

    我们先来分析下raw data(RFCOMM/L2CAP/HCI):

    4C 20 08 00 04 00 40 00 03 3F 01 1C

    4C 20 ->

    Packet Boundary Flag First automatically-flushable packet(0b10)

    Broadcast Flag Point-to-point (0b00)

    然后Handle就是connection complete的0x004C,然后整个值就是4C 20

    08 00 -> acl packet length

    04 00 -> l2cap basic mode length 4 byte

    40 00 -> Destination id,就是上面l2cap rfcomm连线分配的

    下面就是RFCOMM数据了,格式如下:

    03 -> 这个是Address field,格式如下:

    0x03 = 00000011b,也就是EA=1,C/R=1,也就是command,server channel是0,也就是signal channel

    3F -> Control field,格式如下:

    0x3f = 00111111b,也就是 SABM帧,P/F=1

    01 -> length,格式如下:

    0x01 = 00000001b,也就是EA=1,7个byte表示后续的长度,也就是0,后续无长度

    1C -> FCS

    我们来看下btsnoop

    ② 我们回复

    ③ 手机过来配置PN参数,并且携带credit

    ④ 我们回复PN参数,并且携带credit

    步骤9)手机发起配对请求,配对完成后芯片上报给协议栈Link Key(HCI层面)

     

    分为以下几个小步骤:

    ① 芯片给协议栈发HCI pin code requese的event

    ② 协议栈回复pincode,芯片上报command complete event

    ③ 芯片上报给协议栈link key

    步骤10)手机连接开发板SPP的RFCOMM server channel(RFCOMM层面)

    ① 手机过来连接SPP server channel通道

    注意此部分手机过来连接的server channel就是我们SDP回复给手机的RFCOMM channel 8

    ② 我们回复

    步骤11)开发板跟手机交互RFCOMM Modem status参数(RFCOMM层面)

    此部分我们只列举一个Modem status response

    步骤12)开发板主动跟手机交互PnP SDP参数(此部分是DID协议部分,暂时略过)

    步骤13)手机主动发送SPP字符串(上层决定)(SPP层面)

    手机发送的字符串为:

    其中RFCOMM的payload就是SPP发送的数据

    我们来分析下raw data:

    4C 20 32 00 2E 00 40 00 43 FF 53 03 68 65 6C 6C 6F 2C 49 20 61 6D 20 77 69 72 65 6C 65 73 73 20 6C 69 6E 6B 20 69 6E 20 61 6E 64 72 6F 69 64 20 70 68 6F 6E 65 38

    4C 20 ->

    Packet Boundary Flag First automatically-flushable packet(0b10)

    Broadcast Flag Point-to-point (0b00)

    然后Handle就是connection complete的0x004C,然后整个值就是4C 20

    32 00 -> acl packet length,50byte

    2E 00 -> l2cap basic mode length = 46byte

    40 00 -> Destination id,也就是0x0040

    43 -> RFCOMM address

    FF -> RFCOMM control,此部分为UIH帧

    53 -> RFCOMM length

    03 -> give credit

    68 65 6C 6C 6F 2C 49 20 61 6D 20 77 69 72 65 6C 65 73 73 20 6C 69 6E 6B 20 69 6E 20 61 6E 64 72 6F 69 64 20 70 68 6F 6E 65

    -> 此部分就是我们发送的字符串

    38 -> FCS

    步骤14)开发板主动发送SPP字符串(上层决定)(SPP层面)

    我们发送的字符串为

    步骤15)手机主动开发与开发板的连接(RFCOMM/L2CAP/HCI层面)

    希望通过这篇文章能让你们熟悉蓝牙芯片跟蓝牙协议栈整个流程的交互方式!

    展开全文
  • 之前写了篇关于ESP8266使用AT指令进行互相通讯的实验,在写STM32串口接发数据的程序中,觉得有必要将之前学的有关于串口方面的使用经历加以总结。 串口发送数据: 1. 串口发送数据最直接的方式就是标准调用...

    之前写了篇关于ESP8266使用AT指令进行互相通讯的实验,在写STM32串口接发数据的程序中,觉得有必要将之前学的有关于串口方面的使用经历加以总结。

     

    串口发送数据:

           1. 串口发送数据最直接的方式就是标准调用库函数 。 void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
    第一个参数是发送的串口号,第二个参数是要发送的数据了。但是用过的朋友应该觉得不好用,一次只能发送单个字符,所以我们有必要根据这个函数加以扩展。

    void Send_data(u8 *s)
    {
        while(*s!='\0')
        { 
            while(USART_GetFlagStatus(USART1,USART_FLAG_TC )==RESET);    
            USART_SendData(USART1,*s);
            s++;
        }
    }
            以上程序的形参就是我们调用该函数时要发送的字符串,这里通过循环调用USART_SendData来一 一发送我们的字符串。

    while(USART_GetFlagStatus(USART1,USART_FLAG_TC )==RESET);
    <span style="color:#333333">        这句话有必要加,他是用于检查串口是否发送完成的标志,如果不加这句话会发生数据丢失的情况。
            这个函数只能用于串口1发送。有些时候根据需要,要用到多个串口发送那么就还需要改进这个程序。如下:</span>
    void Send_data(USART_TypeDef * USARTx,u8 *s)
    {
        while(*s!='\0')
        { 
            while(USART_GetFlagStatus(USARTx,USART_FLAG_TC )==RESET);    
            USART_SendData(USARTx,*s);
            s++;
        }
    }
            这样就可实现任意的串口发送。但有一点,我在使用实时操作系统的时候(如UCOS,Freertos等),需考虑函数重入的问题。当然也可以简单的实现把该函数复制一下,然后修改串口号也可以避免该问题。然而这个函数不能像printf那样传递多个参数,所以还可以在改进,最终程序如下

    void USART_printf ( USART_TypeDef * USARTx, char * Data, ... )
    {
        const char *s;
        int d;   
        char buf[16];
        
        va_list ap;
        va_start(ap, Data);
     
        while ( * Data != 0 )     // 判断是否到达字符串结束符
        {                                          
            if ( * Data == 0x5c )  //'\'
            {                                      
                switch ( *++Data )
                {
                    case 'r':                                      //回车符
                    USART_SendData(USARTx, 0x0d);
                    Data ++;
                    break;
     
                    case 'n':                                      //换行符
                    USART_SendData(USARTx, 0x0a);    
                    Data ++;
                    break;
     
                    default:
                    Data ++;
                    break;
                }             
            }
            
            else if ( * Data == '%')
            {                                      //
                switch ( *++Data )
                {                
                    case 's':                                          //字符串
                    s = va_arg(ap, const char *);
                    
                    for ( ; *s; s++) 
                    {
                        USART_SendData(USARTx,*s);
                        while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
                    }
                    
                    Data++;
                    
                    break;
     
                    case 'd':            
                        //十进制
                    d = va_arg(ap, int);
                    
                    itoa(d, buf, 10);
                    
                    for (s = buf; *s; s++) 
                    {
                        USART_SendData(USARTx,*s);
                        while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
                    }
                    
                    Data++;
                    
                    break;
                    
                    default:
                    Data++;
                    
                    break;
                    
                }         
            }
            
            else USART_SendData(USARTx, *Data++);
            
            while ( USART_GetFlagStatus ( USARTx, USART_FLAG_TXE ) == RESET );
            
        }
    }
            该函数就可以像printf使用可变参数,方便很多。通过观察函数但这个函数只支持了%d,%s的参数,想要支持更多,可以仿照printf的函数写法加以补充。
            2. 直接使用printf函数。        很多朋友都知道想要STM32要直接使用printf不行的。需要加上以下的说明

            最后记得还要修改一下选中Code Generation——选中Use MicroLI

     

    串口接收数据:       

            串口接收最后应有一定的协议,如发送一帧数据应该有头标志或尾标志,也可两个标志都有。这样在处理数据时既能能保证数据的正确接收,也有利于接收完后我们处理数据。串口的配置在这里就不在赘述,这里我以串口2接收中断服务程序函数且接收的数据包含头尾标识为例,先来看我经常使用的接收程序格式。

    #define Max_BUFF_Len 18
    unsigned char Uart2_Buffer[Max_BUFF_Len];
    unsigned int Uart2_Rx=0;
    void USART2_IRQHandler() 
    {
        if(USART_GetITStatus(USART2,USART_IT_RXNE) != RESET) //中断产生 
        {
            USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中断标志
                 
            Uart2_Buffer[Uart2_Rx] = USART_ReceiveData(USART2);     //接收串口1数据到buff缓冲区
            Uart2_Rx++; 
                  
            if(Uart2_Buffer[Uart2_Rx-1] == 0x0a || Uart2_Rx == Max_BUFF_Len)    //如果接收到尾标识是换行符(或者等于最大接受数就清空重新接收)
            {
                if(Uart2_Buffer[0] == '+')                      //检测到头标识是我们需要的 
                {
                    printf("%s\r\n",Uart2_Buffer);        //这里我做打印数据处理
                    Uart2_Rx=0;                                   
                } 
                else
                {
                    Uart2_Rx=0;                                   //不是我们需要的数据或者达到最大接收数则开始重新接收
                }
            }
        }
    }
     


            数据的头标识为“\n”既换行符,尾标识为“+”。该函数将串口接收的数据存放在USART_Buffer数组中,然后先判断当前字符是不是尾标识,如果是说明接收完毕,然后再来判断头标识是不是“+”号,如果还是那么就是我们想要的数据,接下来就可以进行相应数据的处理了。但如果不是那么就让Usart2_Rx=0重新接收数据。这样做的有以下好处:

            1.可以接受不定长度的数据,最大接收长度可以通过Max_BUFF_Len来更改

            2.可以接受指定的数据

            3.防止接收的数据使数组越界
            这里我的把接受正确数据直接打印出来,也可以通过设置标识位,然后在主函数里面轮询再操作。

            

            以上的接收形式,是中断一次就接收一个字符,这在UCOS等实时内核系统中频繁的中断,非常消耗CPU资源,在有些时候我们需要接收大量数据时且波特率很高的情况下,长时间中断会带来一些额外的问题。所以以DMA形式配合串口的IDLE(空闲中断)来接受数据将会大大的提高CPU的利用率,减少系统资源的消耗。首先还是先看代码。

    #define DMA_USART1_RECEIVE_LEN 18
    void USART1_IRQHandler(void)                                 
    {     
        u32 temp = 0;  
        uint16_t i = 0;  
          
        if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)  
        {  
            USART1->SR;  
            USART1->DR; //这里我们通过先读SR(状态寄存器)和DR(数据寄存器)来清USART_IT_IDLE标志             
            DMA_Cmd(DMA1_Channel5,DISABLE);  
            temp = DMA_USART1_RECEIVE_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); //接收的字符串长度=设置的接收长度-剩余DMA缓存大小 
            for (i = 0;i < temp;i++)  
            {  
                Uart2_Buffer[i] = USART1_RECEIVE_DMABuffer[i];  
                    
            }  
            //设置传输数据长度  
            DMA_SetCurrDataCounter(DMA1_Channel5,DMA_USART1_RECEIVE_LEN);  
            //打开DMA  
            DMA_Cmd(DMA1_Channel5,ENABLE);  
        }        

            之前的串口中断是一个一个字符的接收,现在改为串口空闲中断,就是一帧数据过来才中断进入一次。而且接收的数据时候是DMA来搬运到我们指定的缓冲区(程序中是USART1_RECEIVE_DMABuffer数组),是不占用CPU时间的。具体什么是IDLE中断和DMA需要朋友们先行了解。

        参考链接:

        https://blog.csdn.net/jdh99/article/details/8444474

        https://blog.csdn.net/phker/article/details/51925668   

       最后在讲下DMA的发送

    #define DMA_USART1_SEND_LEN 64
    void DMA_SEND_EN(void)
    {
        DMA_Cmd(DMA1_Channel4, DISABLE);      
        DMA_SetCurrDataCounter(DMA1_Channel4,DMA_USART1_SEND_LEN);   
        DMA_Cmd(DMA1_Channel4, ENABLE);
    }
            这里需要注意下DMA_Cmd(DMA1_Channel4,DISABLE)函数需要在设置传输大小之前调用一下,否则不会重新启动DMA发送。

        有了以上的接收方式,对一般的串口数据处理是没有问题的了。下面再讲一下,在ucosiii中我使用信号量+消息队列+储存管理的形式来处理我们的串口数据。先来说一下这种方式对比其他方式的一些优缺点。一般对串口的处理形式是"生产者"和"消费者"的模式,即本次接收的数据要马上处理,否则当数据大量涌进的时候,就来不及"消费"掉生产者(串口接收中断)的数据,那么就会丢失本次的数据处理。所以使用队列就能够很方便的解决这个问题。

        在下面的程序中,对数据的处理是先接受,在处理,如果在处理的过程中,有串口中断接受数据,那么就把它依次放在队列中,队列的特征是先进先出,在串口中就是先处理先接受的数据,所以根据生产和消费的速度,定义不同大小的消息队列缓冲区就可以了。缺点就是太占用系统资源,一般51单片机是没可能了。下面是从我做的项目中截取过来的程序

    OS_MSG_SIZE  Usart1_Rx_cnt;          //字节大小计数值
    unsigned char Usart1_data;           //每次中断接收的数据
    unsigned char* Usart1_Rx_Ptr;        //储存管理分配内存的首地址的指针
    unsigned char* Usart1_Rx_Ptr1;       //储存首地址的指针
    void USART1_IRQHandler() 
    {
        OS_ERR err;
        OSIntEnter();
        
      if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET) //中断产生 
      {      
        USART_ClearFlag(USART1, USART_FLAG_RXNE);     //清除中断标志
            
        Usart1_data = USART_ReceiveData(USART1);     //接收串口1数据到buff缓冲区
            
            if(Usart1_data =='+')                     //接收到数据头标识
            {
    //            OSSemPend((OS_SEM*        )&SEM_IAR_UART,  //这里请求信号量是为了保证分配的存储区,但一般来说不允许
    //            (OS_TICK        )0,                   //在终端服务函数中调用信号量请求但因为
    //            (OS_OPT            )OS_OPT_PEND_NON_BLOCKING,//我OPT参数设置为非阻塞,所以可以这么写
    //            (CPU_TS*        )0,
    //            (OS_ERR*        )&err); 
    //            if(err==OS_ERR_PEND_WOULD_BLOCK)        //检测到当前信号量不可用
    //            {
    //                 printf("error");
    //            }                
                Usart1_Rx_Ptr=(unsigned char*) OSMemGet((OS_MEM*)&UART1_MemPool,&err);//分配存储区
                Usart1_Rx_Ptr1=Usart1_Rx_Ptr;                //储存存储区的首地址
            }
            if(Usart1_data == 0x0a )                   //接收到尾标志
            {                    
                *Usart1_Rx_Ptr++=Usart1_data;
                Usart1_Rx_cnt++;                            //字节大小增加
                OSTaskQPost((OS_TCB    *  )&Task1_TaskTCB,
                                       (void      *  )Usart1_Rx_Ptr1,    //发送存储区首地址到消息队列
                                       (OS_MSG_SIZE  )Usart1_Rx_cnt,
                                       (OS_OPT       )OS_OPT_POST_FIFO,  //先进先出,也可设置为后进先出,再有地方很有用
                                       (OS_ERR    *  )&err);
                                        
                Usart1_Rx_Ptr=NULL;          //将指针指向为空,防止修改
                Usart1_Rx_cnt=0;         //字节大小计数清零
            }
            else
            {
                *Usart1_Rx_Ptr=Usart1_data; //储存接收到的数据
                Usart1_Rx_Ptr++;
                Usart1_Rx_cnt++;
            }    
        }             
        OSIntExit();
    }
           上面被注释掉的代码为我是为了防止当分区中没有空闲的存储块时加入信号量,打印出报警信息。当然我们也可以将存储块直接设置大一点,但是还是无法避免当没有可有存储块时会程序会崩溃现象。希望懂的朋友能告知下~。

            下面是串口数据处理任务,这里删去了其他代码,只把他打印出来了而已。

    void task1_task(void *p_arg)
    {
        OS_ERR err;
        OS_MSG_SIZE Usart1_Data_size;
        u8 *p;
        
        while(1)
        {
            p=(u8*)OSTaskQPend((OS_TICK        )0, //请求消息队列,获得储存区首地址
                (OS_OPT                )OS_OPT_PEND_BLOCKING,
                (OS_MSG_SIZE*    )&Usart1_Data_size,
                (CPU_TS*            )0,
                (OS_ERR*            )&err);
     
            printf("%s\r\n",p);        //打印数据
     
            delay_ms(100);
            OSMemPut((OS_MEM*    )&UART1_MemPool,    //释放储存区
            (void*            )p,
            (OS_ERR*        )&err);
                             
            OSSemPost((OS_SEM*    )&SEM_IAR_UART,    //释放信号量
            (OS_OPT     )OS_OPT_POST_NO_SCHED,
            (OS_ERR*    )&err);
                             
            OSTimeDlyHMSM(0,0,1,500,OS_OPT_TIME_PERIODIC,&err);                 
        }
    }
     
    --------------------- 
    作者:可以吃的鱼 
    来源:CSDN 
    原文:https://blog.csdn.net/qq_35281599/article/details/80299770 
    版权声明:本文为博主原创文章,转载请附上博文链接!

    展开全文
  • 最近在写AM335x平台的串口测试工具,最开始的时候写的第一版本,测试一直很ok,但是存在一些缺陷,于是就想改进一下,没想到后面在新的板子测试,竟然发现了以个很致命的问题,在旧系统旧内核测试一切正常,在新系统...

     

     

    最近在写AM335x平台的串口测试工具,最开始的时候写的第一版本,测试一直很ok,但是存在一些缺陷,于是就想改进一下,没想到后面在新的板子测试,竟然发现了以个很致命的问题,在旧系统旧内核测试一切正常,在新系统的情况下,系统16路串口测试,am335x自带的4路总是出现丢包的问题,其他扩展出来的16路没有任何问题,于是折腾了好久。

     

    总算搞定。

     

    我的板子系统自带4路,经过spi扩展出来12路,总的16路,首先是串口配置,一般有两种情况下的配置,一种是使用默认的参数直接配置即可,一种就是根据自己的需要重新配置。

     

    首先定义termios结构体

     static  struct termios termold[17],termnew[17];

     

    使用系统默认参数配置:

     

    fd[i]=open(port[i],O_RDWR) ;     //打开串口
    tcgetattr(fd[i],&termold[i]);          //获得默认串口配置参数
    tcgetattr(fd[i],&termnew[i]);
    cfmakeraw(&termnew[i]);          //使用cfmakeraw 配置
    cfsetspeed(&termnew[i],B115200); //{115200,460800,921600}           //设置波特率
    tcsetattr(fd[i],TCSANOW,&termnew[i]);                                                //立即生效

     

    这里直接使用cfmakeraw,将串口设置为原始模式

    这个在新系统是不可用的,不知道什么原因,这样接收就会丢包,可能被内核优化了吧。

     

     重新配置串口参数: 配置为阻塞模式

    if(tcgetattr(fd[i], &termold[i]) != 0)
    {
    perror("SetupSerial 1");
    return ;
    }
    bzero(&termnew[i], sizeof(termnew[i]));
    termnew[i].c_iflag &= ~(ICRNL|IGNCR) ;
    termnew[i].c_cflag |= CLOCAL | CREAD; //CLOCAL:忽略modem控制线 CREAD:打开接受者
    termnew[i].c_cflag &= ~CSIZE;
    termnew[i].c_cflag |= CS8;
    termnew[i].c_cflag &= ~PARENB;
    cfsetispeed(&termnew[i], B115200);
    cfsetospeed(&termnew[i], B115200);
    termnew[i].c_cflag &= ~CSTOPB;
    termnew[i].c_cc[VTIME] = 20; //VTIME:非cannoical模式读时的延时,以十分之一秒位单位
    termnew[i].c_cc[VMIN] = LENGHTH ; //VMIN:非canonical模式读到最小字符数
    tcflush(fd[i],TCIFLUSH); // 改变在所有写入 fd 引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃。
    if((tcsetattr(fd[i],TCSANOW,&termnew[i]))!=0) //TCSANOW:改变立即发生
    {
    perror("com set error");
    return ;
    }
    perror(" set done!");

     

     

    关于这两种的配置,网上很多,直接搜索 linux串口通信配置  就会有很多,这里不阐述。

    因为我这里要测试16路,所以用数组保存fd  termnew  ,配置好评后,如果要测试多路,基本都是创建线程实现。

     

     

    在这里我是使用双向收发,把 读取设置为阻塞,就可以收发,例如:

    串口1发送到串口2  串口2收到后会发给串口1  这样循环 

     

    我是使用线程实现,开始的时候也总是出问题,就是系统自带的串口1和2   11和12,总是丢包,或者接收顺序不对数据感觉呗截取

     

    但是单独开窗口,telnet登录各自单独才是,开8个窗口测试16路,都没有问题

    所以我想到不使用线程管理,而是每一路开自己的进程管理,这样也没有问题,就是使用多线程的时候就出现问题,经过一番折腾,总算解决多线程通信的问题

    一开始我在定义数据的时候使用static来修饰,到这这样就出现了问题,而且只是系统自带的串口出现问题,其他的都正常 

    加static修饰

     运行结果出错:

     

     去掉static直接ok,

    线程代码如下:

    void* read_thread(void* arg)
    {
    char write_txbuf[LENGHTH] = DATE_STRING;
    char read_rxbuf[LENGHTH]={0} ;
    char checkbuf[LENGHTH] = DATE_STRING;;
    //printf("线程开始进入 %d\n",paramer_in) ;
    while(1){
    //clear rxdate buf ===read===
    memset(read_rxbuf,0,sizeof(read_rxbuf));
    retlen[(int)arg]= read(fd[(int)arg],read_rxbuf,sizeof(read_rxbuf));
    if(strcmp(checkbuf, read_rxbuf)==0){
    rxtotal[(int)arg]+=retlen[(int)arg];
    }else{
    faillen[(int)arg]+=retlen[(int)arg] ;
    tcflush(fd[(int)arg],TCIFLUSH); //如果接收失败 刷新缓冲 继续接收
    }
    //===write===
    txlen[(int)arg]= write(fd[(int)arg],write_txbuf,sizeof(write_txbuf));
    txtotal[(int)arg]+=txlen[(int)arg] ;

    }
    }
    void* write_thread(void* arg)
    {
    char write_txbuf[LENGHTH] = DATE_STRING;
    char read_rxbuf[LENGHTH]={0} ;
    char checkbuf[LENGHTH] = DATE_STRING;;
    //printf("线程开始进入 %d\n",paramer_in) ;
    while(1){
    txlen[(int)arg] = write(fd[(int)arg],write_txbuf,sizeof(write_txbuf)) ;

    memset(read_rxbuf,0,sizeof(read_rxbuf)); //clear rxdate buf
    retlen[(int)arg] = read(fd[(int)arg],read_rxbuf,sizeof(read_rxbuf)) ; //send after receive
    if(strcmp(checkbuf, read_rxbuf)==0){
    rxtotal[(int)arg]+=retlen[(int)arg];
    //printf("\n===rev:%s send:%s len:%d===\n",read_rxbuf, write_txbuf, retlen[paramer_in]) ;
    }else{
    faillen[(int)arg]+=retlen[(int)arg] ;
    tcflush(fd[(int)arg],TCIFLUSH); //如果接收失败 刷新缓冲 继续接收
    }
    //因为read会有阻塞 所以等接收到后再加 避免发送回比接收多
    txtotal[(int)arg]+=txlen[(int)arg] ;

    }
    }

     运行结果:

     

     

     总结:linux串口通信多线程,新版的内核相对于旧版的,发送速度明显提升,而且芯片自带的串口比经过扩展出来的块很多倍,也可能是发送太快,我16线程频繁调用,导致分配 内存不过来,或者给发送结束后增加100ms以上的延时,所有串口速度差不多的时候,也是所有的都是正常的,所有折腾了很久。  最后去掉static直接ok  所有linux多线程,要谨慎使用static  

     

    转载于:https://www.cnblogs.com/ChenChangXiong/p/10977813.html

    展开全文
  • STM32串口接收不定长数据帧->链表数据帧说明二级目录三级目录 数据帧说明 STM32数据寄存器为USARTx->DR寄存器 二级目录 三级目录
  • 串口 2.LCD模块 3.ETH 4.PCF8574 四、软件设计 1.tcp_server_thread() 2.tcp_server_init() 3.main.c 五、下载验证 六、总结 实验概述   本实验所用硬件为正点原子STM32F4/F7系列APOLLO开发板,主芯片为STM32F767...
  • STM32CubeMX-串口开启DMA进行数据传输

    千次阅读 热门讨论 2021-05-19 22:37:19
    STM32CubeMX笔记-串口开启DMA进行数据传输
  • 文章目录1 串口的硬件初步认识2 中间桥梁芯片硬件电路3 电脑如何识别 中间桥梁芯片4 串口调试助手5 为什么需要CH340模块6 CH340模块可以换成别的吗7 单片机开发版设计理念18 单片机开发版设计理念2 1 串口的硬件初步...
  • EDA课设 FPGA开发板 VHDL实现串口通信

    千次阅读 多人点赞 2021-07-20 21:28:14
    VHDL UART串口通信设计 实现开发板与计算机串口助手之间的收发,并能够自行调节波特率。自行设计通信格式并完成调试。
  • 海思HI35XX串口调试

    千次阅读 热门讨论 2019-01-17 18:50:50
    我测试使用的是海思HI3520DV400设备,它总共有三个串口,官方提供的SDK只使能了UART0,也就是调试串口。如果要使用UART1或是UART2,用户需要自己手动设置。 (一)使能串口 最直接的方式就是将设备树中对应uart的...
  • 1、按键说明分配芯片按键原理图,以及简码图。 2、具体设想: 分为点动模式和设定运动模式,以及回0模式。 一、点动模式 点动模式,按下点动连续按键,进行切换两种模式。在屏幕上有显示。点动显示DD,连续显示...
  • 那么如何进行串口数据位的确定呢 ? 当时是利用上面提到的串口第二大特点 - 特征表匹配法。 为了说明该办法,bug菌首先做了一个简单示例,从机采用8bit-无校验-1停止位进行数据接收,而主机通过不同的5、6、7bit数据...
  • RS485串口通讯详解

    千次阅读 2020-07-13 23:40:20
    我们知道,最初数据是模拟信号输出简单过程量,后来仪表接口是RS232接口,这种接口可以实现点对点的通信方式,但这种方式不能实现联网功能,随后出现的RS485解决了这个问题。为此本文通过问答的形式详细介绍RS485...
  • 最近因为项目需要,需要做一个STM32和Windows的串口通讯协议来交换数据,本着最求极致的心态,来讨论一下简明的数据帧的设计方法。数据的传输方式对于很多单片机初学者而言,可能他们接触到串口 首先想到的就是通过...
  • RS-232是常用的传输接口,是硬件学习的入门级接口。...常见的9脚接口管脚分配如下图,参考链接:http://zh.wikipedia.org/wiki/RS-232 DE-9 Male (Pin Side) DE-9 Female (Pin Side) -------------
  • ADC采集的数据通过串口进行发送 (1)

    千次阅读 2019-07-13 15:59:31
    1 RIDE串口调通 1) 首先进行串口的测试,将《0403_ADC_UART_LED》文件夹里的程序注释掉所有的关于ADC的部分,仅对UART的部分进行分析与调试。 2) void SCU_Configuration(void)的配置 配置成关于UART0的...
  • 尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间...
  • 单片机 串口编程之串口通信仿真实验

    万次阅读 多人点赞 2018-11-17 18:28:24
    记--简单的使能串口串口收发数据的例子。(使用Proteus仿真+虚拟串口调试) 代码,仿真文件打包:链接: https://pan.baidu.com/s/1nyb46fTJrYcAy_VarFdO3A 提取码: j44s 蓝奏:...
  • DELPHI串口编程

    2015-12-10 21:39:48
    差异,接口通常由一些寄存器或RAM芯片组成,如果芯片足够大还可以实现批量数据的传输; 2)能够进行信息格式的转换,例如串行和并行的转换; 3)能够协调CPU和外设两者在信息的类型和电平的差异,如电平 转换驱动...
  • 程序实现:模块配置为AP模式,建立Sokcet Server ,终端上网设备可通过 TCP Client方式访问模块服务器,模块数据可通过串口与终端设备交互; 下面是实现的代码 #include <ESP8266WiFi.h> #define LED 2 //...
  • CPU 地址空间分配原理分析

    千次阅读 2021-05-15 21:31:51
    本文参考了 主存与CPU的连接(字扩展,位扩展,芯片的地址分配)文章中的部分文字和图片,这里向作者致敬。 前言 对于硬件开发工程师来说,尤其是初学者,地址空间分配原理是比较难以理解的,起码对于我来说,是比较...
  • 串口编程(python串口通信实例)

    千次阅读 2021-03-05 14:11:07
    串口编程用于工业控制,PC侧可利用MSCOMM控件或WIN API函数操作串口(RS232口)与外设进行通信,命令外设执行某种操作,获取外设返回的数据,进行数据处理.我想通过串口调试助手给单片机发1-30的数,然后在单片机里有...
  • 如何利用廉价的51单片机来控制网卡芯片进行数据传输,加载TCP/IP协议连接到互联网,实现网络通信成了众多设计者的目标。但由于指令及资源的限制,实施过程会有许多困难。我们在设计方案中舍弃了耗费资源的高级协议,...
  • Arduino串口通信

    千次阅读 多人点赞 2020-09-25 21:58:54
    通信是用来在不同电子设备之间交换数据用的技术,其实就是要实现不同电子设备之间的“通讯对话”。 Arduino串口通信 Arduino采用USART通信模式,可以有硬串口,软串口两种实现方式。 通常将Arduino UNO上自带的串口...
  • 十五、串口发送图片数据到SRAM在TFT屏上显示 之前分享过rom存储图片数据在TFT屏上显示,该方法只能显示小点的图片,如果想显示TFT屏幕大小的图片上述方法rom内存大小不够。小梅哥给了个方案,利用串口将图片数据传...
  • FPGA串口通信

    2021-07-20 15:38:40
    本文主要讲解如何编写 FPGA 串口通信的收发程序,在程序中使用了状态机,是学习状态机的重要实验。 2 实验环境 黑金 FPGA 开发板(AX301 开发板、AX4010 开发板、AX515 开发板、AX530 开发板、AX1025 开发板) 串口...
  • 串口,USB,USB转串口,串口驱动,仿真器下载程序 在初学stm32,接触到的第一步便是下载程序, 方法一:利用仿真器下载程序 方法二:利用串口下载程序 本文简单介绍两种下载方法,浅入窥探下载原理,因为初学,错误...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,372
精华内容 3,748
关键字:

串口数据分配芯片