RS485利用地址主动仲裁驱动

        最近在工作中使用了RS485协议,之前虽然知道怎么用,但是实际应用到工程上还是第一次,在使用过程中就涉及到RS485总线的一些架构问题,我们都知道,RS485是半双工通信,一般应用在主从式且是一主多从的场景中,很少有人将他应用在多主多从的场景中,其根本原因我想应该是RS485不能像CAN总线一样走硬件仲裁吧,如果不做处理的强行应用在多主多从的场景里很容易就会造成通信冲突,那么真的没有办法解决这个问题吗?办法肯定是有的,我一开始也查了很多的资料,网上的办法有很多,其中“令牌”的方式是我觉得做得比较好的方式之一,但是实现起来较为麻烦与困难。经过我自己的思考,参考CAN总线的实现方式,我自己写了一套RS485主动竞争的驱动,不知道是不是已经有现成驱动和我的一样,或者差不多的,我没有去核实,我现在姑且称之为“RS485利用地址主动仲裁驱动”吧。

  • 核心原理

        该套485主动竞争驱动核心原理十分简单,主要是利用RS485设备的地址来进行竞争,类比CAN总线的仲裁原理。

        这里简述一下CAN总线的仲裁原理,CAN总线上的每一个设备在发送数据之前必须先向总线申请仲裁,仲裁通过了才可以忘总线上发送数据,直到该设备数据发送完毕才释放权限。CAN总线其实就是利用设备地址来总裁的,我们知道对于CAN总线来说逻辑“0”在总线上呈现是“显性电平”(不理解显性电平的朋友请先百度一下),所以CAN总线在仲裁的时候,利用其地址位(标准CAN 11位,扩展CAN 29位),将地址位一位位的往总线上传,并监测总线上的电平状态,如果发现与自己的不一样就结束仲裁,仲裁失败。

        那么我们这里485的仲裁也可以这样子做,因为RS485总线上,如果需要仲裁那一定是挂了相对多的设备,所以设备地址是个必要条件,且每个设备的地址在该总线上是唯一的,那么我们在发送数据之前先发送其地址位,并监测接收到的数据,与发送的地址位相比较,采用一定的仲裁策略来分析当前设备是否仲裁成功(仲裁策略稍后描述)。由于CAN总线的仲裁是硬件实现,所以可以对每一位进行仲裁,而本套485仲裁策略只能以字节的形式仲裁。

  • 驱动接口API

        有了以上基本核心原理,那么我们就可以以这个为中心来设计驱动了,本驱动分为两层,第一层也就是最底层是硬件层,主要用来对不同芯片,不同板子的UART进行驱动设计;第二层才是真正的RS485协议驱动层,接下来我们一层层来讲。

第一层:USART驱动设计

这一层对外的主要是以上几个API接口,用来调动USART底层驱动的,接下来我们一一描述。

void usart_Set_Receiver (USART_ID_enum id , unsigned char status);

该函数用来设置串口接收器是否工作,USART_ID_enum id 为串口编号,unsigned char status为设置参数,其参数范围为[USART_ENABLE,USART_DISABLE],当配置为USART_ENABLE的时候,对应串口的接收器使能并开始工作,当配置为USART_DISABLE的时候,对应串口的接收器失能并停止工作。该函数的实现方法主要为使能和失能对应串口接收功能寄存器已经接收中断。

void usart_Set_Transmitter (USART_ID_enum id , unsigned char status); 

该函数用来设置串口发射机是否工作,USART_ID_enum id 为串口编号,unsigned char status为设置参数,其参数范围为[USART_ENABLE,USART_DISABLE],当配置为USART_ENABLE的时候,对应串口的发射机使能并开始工作,当配置为USART_DISABLE的时候,对应串口的发射机失能并停止工作。该函数的实现方法主要为使能和失能对应串口发送功能寄存器已经接收中断。

void usart_send_one_byte (USART_ID_enum id , unsigned char dat); 

unsigned char usart_receive_one_byte (USART_ID_enum id); 

这两个函数分别是对应串口发送一个字节的数据以及对应串口接收一个字节的数据。

void uasrt_Device_Registration (USART_ID_enum id , Usart_Object object); 

该函数结束注册串口函数,也是初始化串口函数,通过该函数可以将RS485设备注册到对应串口上,接下来描述一下Usart_Object这个结构体类型。

Usart_Object结构体类型比较简单,针对串口本身的主要是前三个参数(baud,interrupt_group,interrupt_priority)配置串口的波特率和中断优先级;

剩下的参数都是为了做适应串口驱动的外设准备的,例如我们现在做的RS485驱动

void *parameter:传入的是对应设备的句柄,主要用来识别不同的设备,例如同一套程序中可能有多个485设备,那么就依靠这个参数判断是具体在操作哪个设备。

void(*usart_tx_Handle) (void *parameter);  //串口发送中断

void(*usart_rx_Handle) (void *parameter);  //串口接收中断

void(*usart_rtf_Handle)(void *parameter);  //接收超时中断

以上三个函数分别是对应串口的发送、接收以及接收超时中断服务函数,传入的参数就是该结构体中的第三个参数void *parameter,通过该函数可以跳转到不同类型外设驱动所需的中断函数,注意这里讲的是不同类型的外设,而不是同一类型的不同设备。有的芯片没有串口接收超时的硬件中断的话就需要自己想办法去模拟,例如加个定时器来做也可以。

有了以上关于对硬件USART驱动的接口函数,我们就可以设计上层驱动——“RS485驱动”了。

第二层:RS485协议驱动层

这一层对外的API接口为以上5个函数接口,接下来一一描述其作用

signed char MAX485_Device_Registration (MAX485_Registration_object object);

该函数用来注册485设备,如果注册成功,将会返回一个对应的485设备ID,如果注册失败,将会返回-1,每一个成功注册的设备都会拥有一个属于自己的唯一的ID编号,接下来的操作都要依靠这个返回的ID号才能完成。

void MAX485_Device_Send_Byte (unsigned char max485_id,unsigned char dat);  

void MAX485_Device_Send_Buf (unsigned char max485_id,unsigned char *buf,unsigned char len); 

void MAX485_Device_Send_String (unsigned char max485_id,unsigned char *str); 

这三个函数都是给485设备向外发送数据的接口函数,利用这三个API可以向外发送一个字节,一个数组,一个字符串的数据。

unsigned char MAX485_Device_Receive_Byte (unsigned char max485_id,unsigned char *dat);

该函数用来向485设备的接收缓冲区获取数据,若缓冲区内无数据将返回0,若有数据将返回1,同时利用unsigned char *dat向外带出获取到的数据。

接下来描述一下485设备注册函数中的参数MAX485_Registration_object结构体类型

unsigned int address;       //max485的地址

这个参数就比较简单,就是字面意思,就是设置该485设备的地址

unsigned char arbitration_bit_num; //需要仲裁位数

用该参数就是配置485设备在竞争的时候需要仲裁的位数,如果该位为0的话则不需要参与总线竞争,如果为其他的数字n的话则表示竞争的时候从address的低位依次开始竞争,一直竞争n个地址位。

unsigned char *txbuf; //发送数据缓冲区

unsigned int txbuf_size; //发送数据缓冲区大小

unsigned char *rxbuf;       //接收数据缓冲区

unsigned int rxbuf_size;    //接收数据缓冲区大小

以上4个参数分别为发送数据缓冲区,发送数据缓冲区大小,接收数据缓冲区以及接收数据缓冲区大小。

MAX485_Group_enum group;    //max485在本设备中的分组

这个参数一般的场景来说可以不适用,这是用来区别同一个主板中有多个485设备并且挂在同一个总线上的情况,将他们配置到同一组,那在竞争的时候,内部就不需要利用地址竞争,以此来提高程序运行效率,不过这种情况一般不会发生。

USART_ID_enum usart_id;     //max485注册对应的串口编号

该参数用来将485设备注册到对应的硬件串口上。

unsigned int bound;         //波特率

void(*MAX485_Dir_Control_gpio_Init)(void); //485控制方向引脚初始化

该参数用来配置485芯片的方向引脚的初始化函数,当然也可以不要这个参数,在应用程序中直接初始化,但是为了模块化做得更好点,我还是把这个参数加进来了。

void(*MAX485_Configure_Trans_Dir)(MAX485_Trans_Dir_enum Dir); //MAX485修改传输方向函数

MAX485_Trans_Dir_enum 为一个枚举如上所示,用来配置485芯片的发送端和接收端是否使能的。

有了这些接口函数,我们就可以在应用程序中对485进行配置和控制了。这里目前我只给出简单的API接口和使用方法,具体到驱动的实现我们下一节再介绍,我先给举一个简单的使用的例子,有一个简单的整体的使用逻辑的印象。

接下来简单介绍一下这个Demo,

第6-8行是对该485设备的发送接收缓冲区进行一个定义;

第11-16行是对485设备的发送接收使能的控制引脚进行初始化,因为我这里硬件上接收使能引脚(接地)处于常使能状态,所以我这里就只左右发送引脚的初始化程序,实际怎么初始化要视具体情况而定。

第18-25行是对485设备的发送使能配置函数的定义,利用这个函数可以让485芯片切换发送/接收状态,同样因为我只用到了发送使能引脚,所以就只做了发送使能控制的程序,要根据具体情况将程序补全。

定27-55行就是对485设备进行注册了,主要是配置MAX485_Registration_object结构体里的数据,利用USER_MAX485_ID这个数据将该485设备的ID保存起来。

  • 实现过程
  • 描述了这套驱动的API接口,其实对于真正使用来说,只需要关注第二层驱动的API接口就够了,因为第一层USART的硬件驱动的接口函数是提供给第二层485设备驱动使用的。那么接下来我们来聊一聊每层驱动的实现方法,同样我们也分为两层来讲。

第一层:USART的驱动实现方法

USART对于不同的芯片和主板来说有不同的配置方案,但是对于我这套驱动来说,主体的思路是不变的,就是分成两个部分,一个数串口初始化,一个是中断服务函数。

由于现在大多数芯片都有多个串口外设,所以为了方便管理和实现接口统一化,我们给每个串口都手动编上ID,也就是上面USART_ID_enum id这个枚举,并且可以宏定义我们每个串口是否使用使能,那有了这个ID后,后面我们就可以直接根据ID来操作对应的串口,使用起来简单方便明了,我这里定义的枚举结构图下图所示:

        对于串口初始化函数中,我们这里需要对每一个用到的串口进行初始化函数的编写,然后放在void uasrt_Device_Registration (USART_ID_enum id , Usart_Object object);这个接口中调用,然后根据Usart_Object object中的参数来配置串口,并将一些参数保存在对应串口Usart_Object结构体中,为此我们建立了一个结构体数组来保存每一个串口的配置参数

其串口注册初始化过程如下图所示:

         对于串口的初始化我们使用了Usart_Object中的三个参数,分别是baud,interrupt_group以及interrupt_priotity,其实对于一些芯片来说后面连个参数就可以选择性的使用了,那么对于每一个串口初始化的函数就需要根据具体的芯片去进行编写了,这个没有什么一致性的要求,只要能让串口成功的跑起来就行了。

接下来就是中断服务函数的编写,在这一层中,中断服务函数就更简单了,针对不同的中断进入注册好的不同的函数就行了,我这里做了三个中断,字节发送完成中断,字节接收完成中断,接收超时中断,注意前两个中断大部分带串口外设的芯片都有,但是接收超时中断需要的芯片都是没有的,那就需要用户自己去想办法设计一个,例如加一个定时器来计算最后一个字节接收的时间是否超时等,而每个函数的传入参数就是结构体Usart_Object中的parameter具体原因可参考第二节驱动接口API的内容,函数设计如下图所示:

对于USART驱动层,最主要的函数就是这两个函数,这两个函数有了,这个USART的驱动架构就搭起来了,接下来就是设计USART的接收和发送函数了。

        可以看到这两个函数比较简单,主要就是根据串口ID获取到串口操作基址,然后对应的发送和接收函数即可,注意这里的USART_DATA(USART_ADDRESS)实际上就是直接操作寄存器,相当于51内核的单片机SBUF = dat;和dat = SBUF;不用深究其原理,而获取串口基址这个函数也要根据具体的芯片情况去设计,做这个函数的意义在于封装好底层寄存器操作,对外提供统一的操作接口。

其实有了以上描述的四个函数就可以直接设计下一层485驱动了,而我为什么还要再在这一层设计两个关于配置串口发送和接收器使能的函数呢,这还是由于我的硬件侧没有将485的接收使能引脚的控制给到单片机,所以我就从串口接收使能这里想办法,其实我也建议在真正使用的时候也将这两个函数做好,这两个函数主要就是对串口的接收/发送使能/失能,同时使能/失能对应中断。这里不做深入描述。具体函数实现如下图所示:

         以上我们详细的描述了一下底层串口驱动的设计方案,其实我觉得这套方案可以不仅仅使用在485设备的驱动上,很多利用到串口的外围设备都可以这么来用,甚至可以不用对其做修改直接使用我认为都是没问题的,因为这仅仅是将串口的实现封装了一层对外提供操作接口,并没有具体的逻辑实现,所以适应性相对来说较强。

第二层:485设备驱动实现方法

这一层驱动才是我们要讲的重点内容,也是我这套驱动的核心内容,之所以放到这么后面来讲,一方面是对该驱动要有个整体的介绍,另一方面需要铺垫一些驱动里要用到的一些变量函数的名称功能等,这样有助于接下来的介绍。RS485设备驱动层的流程图如下图所示,其中三个标黄色块的流程表示对应串口的三个中断,而该程序驱动进入流程的入口只有三个,一个是主动发送数据也就是流程图中蓝色块,另外两个就是从接收数据中断和接收超时中断中进入。该流程图对我们驱动设计起着指引性的作用。

        有了以上流程图,我们的驱动设计思路一下子就清晰起来了,接下来我们拆开来细细的讲,慢慢的聊。

首先,我们看一下RS485设备注册函数是如何实现的,我们通过第二节已经知道了如何去调用该函数,那么这里我们来介绍一下这个函数是如何实现的,这个函数主要要实现两部分功能,第一部分就是将应用层的配置参数带进来,第二部分就是将参数配置进对应的串口,接下来我们;来看一下函数的原型。

         在第二节我们详细讲解了MAX485_Registration_object object这个形参的作用,那么接下来我们看一下485设备注册函数MAX485_Device_Registration 的原型,我们再注册一个设备的时候,首先要看看是不是有剩余的位置来注册,我们定义了一个最大的设备注册量为MAX485_DEVICE_MAX,一旦注册设备量达到了这个限制,再注册设备就会注册失败,这个函数就会返回-1告诉主设备,其中注册标志位为reg_st,这个在第184行有体现,然后就是将有关485驱动的部分的参数填进485的“对象”(结构体/控制块),将属于USART的参数利用串口注册函数uasrt_Device_Registration注册进串口,这样我们就成功注册了一个485设备,并将之与对应的串口关联起来。这个函数里涉及到一个非常重要的对象(结构体/控制块)——MAX485_object *max_object;这是用来控制每一个485设备的对象,也就是说每一个485设备都有一个对应的控制块操作,那么这些控制块在哪呢?我们可以看到第182行,实际上我们定义了一个大小为MAX485_DEVICE_MAXMAX485_object结构体数组来存储这些控制块,而设备在该数值中的位置就是该设备的ID,这也就是为什么我们的设备注册量最多只能到MAX485_DEVICE_MAX的原因,控制块的原型如下所示: 

当然,这个控制块是输入485设备所私有的,所以该控制块对外并不可见,所以我们对外想要操作485设备的时候只能通过设备ID来操作。在该控制块中我们还可以看到两个数据缓冲区,分别是txrx,那么他们的类型是如何定义的呢,这里不过分讲解,但是稍后会贴出代码以示说明;还有一个仲裁标志位arbitration,该标志位用来指示对应设备的仲裁状态,有三个取值,其取值结构如下图所示:

在485注册函数中,我们需要还需要对方向控制引脚进行初始化以及配置设备的初始状态为接收状态。而控制块中的txrx的原型定义如下图所示,Usart_Work_Struct这个结构体类型是在串口设备驱动中定义的,因为这两个缓冲区其实是属于所有要使用串口的设备的共有属性,为了方便使用,才为485设备驱动声明一个别名。到此为止,485驱动的初始化函数就算是讲解完成了。

 

从上面的注册函数中看,可能会有所迷惑,那所谓的max485_usart_rtf_Handlemax485_usart_rx_Handlemax485_usart_tx_Handle这三个中断服务函数是从哪里来的,又是如何实现的呢?接下来我们一一讲解。

max485_usart_rtf_Handle //接收超时中断

该函数的函数原型为void max485_usart_rtf_Handle (void *parameter),由此可见需要传入一个指针,而这个指针指向的实际上就是对于设备的控制块,也就是说实际上parameter会在函数内部转换成MAX485_object *类型。该函数注册好后将会在串口的接收超时中断中调用,并带回对应的设备控制块以供设备驱动识别具体是哪个设备,根据我们的流程图可以看到,实际上在接收超时中断中我们要处理的内容非常少,只需要判断当前所有有没有数据需要被发送,如果有就申请仲裁就行了,其函数定义如下图所示:

根据函数定义我们也简单分析一下函数内容,首先将传入的参数强制转换成485驱动控制块,然后将485忙碌标志位中的接收忙碌标志位清0,最后判断发送缓冲区有没有数据需要被发送,如果有就立即申请仲裁。至于判断发送缓冲区是否有数据和申请中断这两个函数我们后面再讲。

max485_usart_tx_Handle //发送中断

该函数的原型为void max485_usart_tx_Handle (void *parameter) ,传入形参参考接收完成中断,该函数在对应串口的发送完成中断中调用。这个函数相对来说也是比较简单的,主要就是判断发送缓冲区是否还有数据需要被发送,有的话就继续发送,没有就退出发送模式,进入接收模式,其函数定义如下图所示:

该函数在获取到控制块后首先就是判断当前设备有没有获取到发送权限,获取到了权限才能进行接下来的发送数据操作,进一步保障驱动的容错性和健壮性。

max485_usart_rx_Handle//接收中断

该中断服务函数是这三个中断中最复杂的函数了,根据上面的流程图我们可以知道,接收到的数据分为两大类,一类是自己发送的仲裁的数据,另一类就是其他设备发送来的数据。如果是自己发送的仲裁数据,那么就要根据接收到数据与自己发送的数据的关系来判断当前地址位仲裁是否有效,这里有个小小的问题需要阐明一下,由于我们不能像CAN总线一样每个Bit都做比较,那这里我们就只能曲线救国,将设备需要参与竞争的地址位扩展成n个Byte发送竞争,例如一个设备的地址为0x34(0b0011 0100),参与竞争的地址位为8位的话,那么我们将依次发送[0x00,0x00,0x01,0x00,0x01,0x01,0x00,0x00],前面我们说CAN总线的显性逻辑是“0”,而485的显性逻辑刚好与CAN相反为“1”(至于原因可以自己查阅资料或者实验),为了让我们的设备与通用的习惯一致,我们也力求地位越小的设备在竞争的时候的优先级越高,同时类比CAN总线,当总线上的逻辑与自身的逻辑不一样时认为仲裁失败,将原本需要发送的数据做了一点小小的处理,将其转换成如下[0x01,0x01,0x00,0x01,0x00,0x00,0x01,0x01],也就是说将每个字节的最后一位取反,接下来我们对比一下这样做的好处在哪:

原始数据与期望

未变换发送与实际

变换发送与实际

A

B

期望

A

B

总线

实际

A

B

总线

实际

0

0

null

0

0

0

null

1

1

1

null

0

1

A

0

1

1

B

1

0

1

A

1

1

null

1

1

1

null

0

0

0

null

1

0

B

1

0

1

A

0

1

1

B

由上表可以很明显的得到变换后得到的结果与我们期望的是一致的。

铺垫了那么多,那么接下来我们看一下具体的函数的实现方法: 

首先进入该函数第一件事就是获取对应设备的控制块,然后获取对应串口已经接收好的数据,接下来就是判断获取到的数据来源,根据arbitration来判断是否正在进行仲裁,实际上判断的都是最后一位,然后根据仲裁的结果来判断是否退出仲裁还是继续仲裁还是仲裁成功开始发送数据,如果不是处于仲裁阶段那么接收到的数据就是来自其他设备的数据,至于数据是否有效就要根据上层协议来保障。

讲到这里,基本上整个485驱动层的架构就算是讲完了,接下来就是对上面一些出现了还没有来得及讲的函数稍微进行一下讲解,例如启动仲裁,开始发送数据等这些函数了。

 以上这些函数其实都是485驱动的私有函数,对外都是不可见状态,也就说,其余程序不能够直接去使用这些函数,接下来我们一一去分析。

static void MAX485_Apply_Arbitration (MAX485_object *object)//485启动仲裁

进入该函数后首先利用static MAX485_BUSY_enum MAX485_Apply_Group_Arbitration (MAX485_object *object) 与内部同组(包含自己)的设备进行仲裁,如得到的结果是总线处于空闲阶段才可以进行与线外设备进行总裁,而在与其他设备进行仲裁前先判断一下该设备是否需要与其他设备仲裁,如果需要仲裁才进行仲裁,不需要直接发送函数即可。而具体到实现细节方面相信结合上面的流程图以及之前的介绍和函数内容是很清楚明白的。这里也就不做太细致的讲解。

static MAX485_BUSY_enum MAX485_Apply_Group_Arbitration (MAX485_object *object) //MAX485申请本组仲裁

该函数主要是用来做同组的部内判断总线是否处于忙碌状态,总言之,同组内只有有一个设备包含自己的忙碌busy标志位处于发送忙碌状态MAX485_TX_BUSY即视为总线不可使用,即内部竞争失败,内部竞争失败后也就不用再参+与其他设备的竞争了。

static void MAX485_Start_Send_Data (MAX485_object *object) //MAX485设备正式启动发送数据 用在仲裁成功后

该函数主要用来启动485对应的串口发送数据,在开始发送数据前需要先将设备的忙碌busy标志位设置成MAX485_TX_BUSY状态,然后才能开始发送数据。

static unsigned char check_tx_buf_len (MAX485_object *object) //获取发送缓冲区未发送的剩余字节数

这个函数不用过多解释,就是用来获取485设备对应的发送缓冲区还剩多少字节未发送。

有了以上这些函数后,那么我们就可以开始编写收发数据函数了,主要是发送函数的编写,例如发送一个字节,发送一个字符串,发送一个数组这些常用的函数。实现过程也是非常简单,这里不做详细的解读,稍后贴出源码,但有一点需要注意一下,我们这里用到的发送函数都是异步,也就是说只要关心发送的数据就行了,将数据填充到发送缓冲区后就不用关心其他的了,至于驱动是如何去发送的,怎么去仲裁的,在实际应用中不需要去关心。但是如果想要将驱动做得完善一点,有些地方还是要注意的,这些问题将会在下一章中讲解。那么到此为止,我们整个驱动的设计就算是结束了,虽然篇幅较长,但是只要把流程图理解了,其他的也就没那么难了。

  • 不足之处

金无赤足,人无完人!这个驱动中当然存在一些问题,有一些问题是致命的,有一些问题是无伤大雅的,有些问题是偶发性特别低的问题。这里我列举一下目前我发现的这个驱动中存在的问题和优化方法,但是为什么我不直接完善好再写这篇文档呢?原因很简单,因为我懒!

问题1:当接收端出现问题,这里的接收端不管是485驱动芯片还是单片机,只要单片机不能正常接收到数据就算,这个时候可能导致485设备不仅仅是不能接收数据,也有可能导致设备不能正常发送数据,原因是我们的仲裁方案依赖于自收发,如果不能接收数据,那么设备就会一直处于正在仲裁中的状态,这就会出现我们上面说的那些问题。

解决方案:在仲裁的时候引入一个定时器,若长时间没有接收到数据,可直接仲裁失败,甚至可以用这个向上层应用报警设备故障。

问题2:发送缓冲区溢出,这里的主要问题是由于我们缓冲区定义太小,而填充数据速度太快,或者一次填入的数据量太大,导致还有数据没有发送完就被覆盖掉。

解决方案:我们可以在填充数据前先判断数据缓冲区的容量够不够,如果不够就不填充,并反馈上层应用错误标志,上层应用接收到该错误标志后可做其他的逻辑处理。

问题3:接收缓冲区溢出,这同发送缓冲区溢出的问题一样,当接收的数据太多而上层应用来不及处理时,就会导致还有数据未被处理完就被新的数据覆盖。

解决方案:加快的接收数据的处理速度;增加缓冲区容量;添加应急函数,每次接收数据时先判断是否会导致缓冲区溢出,如果会溢出立即进入应急处理函数,释放缓冲区。当然以上三种解决方法各有利弊,需要均衡考虑。

问题4:没有做串口注册失败逻辑,无法判断当前串口是否已经被注册成其他设备。

解决方案:在串口注册时判断当前串口是否已被注册,如果被注册需要返回已经注册的设备的控制块或其他标志。

问题5:没有做设备注销程序,无法进行多设备分时复用逻辑,或即插即用逻辑。

解决方案:引入设备注销程序,底层串口驱动和中层设备驱动都需要引入设备注销程序。

问题6:仲裁时需要发送的数据量太大

解决方案:目前这个我没有想到有合适的解决方案,要是有可能我也不用这种方案了,这个需要大家集思广益。

  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值