关于串口模块化的思考

关于串口模块化的思考

单片机开发(并非嵌入式开发)中,总大部分时候我们都会需要一个串口,功能主要是通信,以及协助调试,而使用它的前提是,要初始化好,以及要调试到可以正常收发。当然,在排除硬件问题的情况下,这并不难(在嵌入式中更不难,因为往往操作系统已经帮你把那部分写好了,就算没写好,很大部分东西它都会提前写好,你只需要写一些部分)。

但是在一遍的重复写那些初始化代码以及操作代码后,你肯定厌倦了,烦了,不想再写这些幼稚的东西了。当然,如果是同个芯片,那代码无所谓,直接搬过来就好了。但是这样问题又来了。以前的那个代码是针对某个协议的,但是现在要用的是另一个协议,这就意味着如果你前期写的代码不好,你就得重新花费大量的功夫去写新的代码。

这时候,我们就该想到一些问题:

1.怎么样才能避免出现这个情况呢?->独立化!

2是不是该把这部分代码独立开来,不要与硬件勾搭,也不要与协议有染呢?->模块化!

3该如何实现它的独善其身的性格?->封装化!

实际上,不管是谁,只要你是做程序的,做久了就会遇到这个情况。我一个同事说,只会编代码的程序员是不懂程序的,只有懂得程序结构设计的程序员才算是真正的进入程序设计的门槛。

这句话对错我不评论,因为如果你去看下dsp的汇编代码,再去看看大多数嵌入式老工程师的代码,你会发现他们走的很多都是精简路线,怎么实现最好最快,他们大部分会挑那种。并不会说去理会什么程序模块化,程序独立化,更别谈封装化。越是别人看不懂的代码他们越是喜欢,因为能突出他们的价值。

但是有这么一句话,我很认同:程序的架构是程序的灵魂!

这句话我原先也不懂,但是直到前几天,老总突然说要把串口模块化,实现想初始化哪个串口就初始化哪个串口。这个看起来很简单,毕竟是针对芯片的。于是,我就开始想,如何才初始化任意串口呢,我想到的是,设置一个结构体,这个结构体能够包含串口他的状态,比如接收到多少个字节,是否溢出,是否超时,发送剩余字数,是否使能发送接收,是否使能中断,串口的缓冲区指针等等。

这样的话就能实现很多应用,因为对于一个串口来说,它的通信方式就两种,直接等待,或者中断传输。同时考虑自己要针对不同的应用,加设了两个回调指针,实现回调,另外编写两个自己常用的程序,实现没有回调的时候也能中断收发的功能。收发方式采用的是直接中断收发,不用队列,而常用的功能也编程独立函数。

再考虑共性,定义串口硬件属性为波特率、数据位、停止位、奇偶校验。

于是就有了下面的结构体,以及函数。

/*****************************结构体定义************************************************/
 
//串口状态结构体,包含相关可用标志及参数
typedef struct
{
    unsigned char Uart_Port;
    FunctionState Transmit;
    FunctionState TransmitInterrupt;
    FunctionState Receive;
    FunctionState ReceiveInterrupt;
    FunctionState ReceiveTimeOut;
    UART_ReceiveOVERFLOW OverFlow;
    UART_ReceiveTimeOut TimeOut;
    unsigned char *ReceiveBuffer;
    unsigned char *TransmitPointer;
    unsigned short ReceiveCount;
    unsigned short ReceiveBufferSize;
    unsigned short TransmitCount;
    unsigned short TimeOutCounter;
    unsigned short TimeOutDelay;
    ResigterTypedef ReveiveDR;
    ResigterTypedef TransmitDR;
    void (*ReceiveCallBackFunc)(unsigned char );
    void (*TransmitCallBackFunc)(unsigned char);
    //unsigned char (*UartTransmitFunc)(unsigned char,unsigned char *,unsigned short);
    //unsigned char (*UartReceiveFunc)(unsigned char,unsigned char *,unsigned short);
    //unsigned short (*UartGetReceiveCountFunc)(unsigned char );
    //unsigned char* (*UartGetReceiveBufferFunc)(unsigned char );
    //unsigned short (*UartGetTransmitCountFunc)(unsigned char );
}Uart_Status_Typedef;
 
//串口初始化结构体
typedef struct
{
//  UART_PORT Uart_Port,
    unsigned long BaudRate;
    UART_DATA_BIT DataBit;
    UART_STOP_BIT StopBit;
    ParityState Parity;
    FunctionState Transmit;
    FunctionState TransmitInterrupt;
    FunctionState Receive;
    FunctionState ReceiveInterrupt;
    FunctionState ReceiveTimeOut;
    unsigned char * ReceiveBuffer;
    unsigned short ReceiveBufferSize;
    unsigned short TimeOutDelay;
    void (*ReceiveCallBackFunc)(unsigned char);
    void (*TransmitCallBackFunc)(unsigned char);
    //unsigned char (*UartTransmitFunc)(unsigned char,unsigned char *,unsigned short);
    //unsigned char (*UartReceiveFunc)(unsigned char,unsigned char *,unsigned short);
    //unsigned short (*UartGetReceiveCountFunc)(unsigned char );
    //unsigned char* (*UartGetReceiveBufferFunc)(unsigned char );
    //unsigned short (*UartGetTransmitCountFunc)(unsigned char );
}Uart_InitTypedef;
 
 
 
 /*******************************全局变量***********************************************/
 
//串口状态,包含初始化后串口参数与一些状态
extern Uart_Status_Typedef Uart_Status[];
//串口号索引表
extern unsigned char UartPortTable[];
 
/********************************函数定义***********************************************/
void UartInit(unsigned char Uart_Port,Uart_InitTypedef * Uart_InitStructure);
unsigned char UartTransmitByte(unsigned char Uart_Port, unsigned char data);
unsigned char UartTransmit(unsigned char Uart_Port, unsigned char * ptr,unsigned short size);
unsigned short UartGetTransmitCount(unsigned char Uart_Port);
unsigned char* UartGetTransmitBuffer(unsigned char Uart_Port);
void UartTimeOut(unsigned char Uart_Port);
void UartClearReceiveBuffer(unsigned char Uart_Port);
unsigned short UartGetReceiveCount(unsigned char Uart_Port);
unsigned char* UartGetReceiveBuffer(unsigned char Uart_Port);
unsigned char UartReceiveByte(unsigned char Uart_Port);
unsigned char UartReceive(unsigned char Uart_Port, unsigned char * buf,unsigned short size);
void UartStatusClear(unsigned char Uart_Port);
void UartStatusInit(unsigned char Uart_Port,Uart_InitTypedef * Uart_InitStructure);
unsigned char UartGetSendStatus(unsigned char Uart_Port);
unsigned char UartGetReceiveStatus(unsigned char UartPort);
void UartReceiveIsr(unsigned char Uart_Port);
void UartTransmitIsr(unsigned char Uart_Port);

文件结构上采用了5个文件。两个是硬件相关的文件,.c.h包含了硬件初始化,硬件状态返回;两个是主文件,与串口无关所有串口操作函数都放这了;最后一个串口中断函数。

为了能够在多个串口同时用,但是有时候串口是不连续的,比如1346组合的串口,如果每个都定义一个结构体,会造成浪费,于是又设置了一个串口状态对应表,便于查询。

这样有些好处:下次用我需要编写硬件相关的,然后按方法初始化后,就可以按函数直接操作发送接收。

但是这样也带来了一些问题:

1.要通过查询串口表得知串口在哪,需要修改对应表适应串口;.状态结构体保存的是指针,需要外部应用再重新设置缓冲,并且要足够大,再传递给他。

(过多的修改就是一种错,违反了对立的初衷)

2即使采用回调函数,还是要直接操作串口的状态结构体,当然只是我的问题。

(模块化的不好,外面经常偷窥,甚至还动手动脚....)

3发送机制不好,比如我用的是瑞萨的621,他的发送中断有两个,这样的话采用485发送就会出现很大的问题。

(什么时候接收,什么时候发送好呢,其实这个是我自己的问题,与这个无关)

4独立性并不好,与硬件有勾搭这是没办法的事情,谁叫你是串口呢?但是与协议也悠然这就是你的不对了, 不能脚踏两只船啊!

(我在这个基础上编写modbus协议,发现要用到串口的发送函数,还得经常查询串口状态,来看串口‘脸色’。当然,这个也跟我modbus协议编的不好有关系。)

好像说来说去都是我自己的问题呢。

但是事实上,这就是我的问题,一个考虑片面周全而忽略整体的问题。

首先我犯了一个错误,收发缓冲区这个应该我们自己定义,不应该在外部定义,外部需要定义的是,这个到底需要多大的缓冲区才能满足收发,而不是所谓的自己再去定义变量再给它,因为这样就以为着我们可以随意的篡改缓冲区的内容,在发送还没完成之前,或者数据接收完成后还没处理,这样是很不安全的,虽然我们一般不会这样,但是如果一个判断机制做得不好,发送没完成前就把 数据写进去,一个数据还没处理就给清零,这样就破坏了原来的数据了。

所以判断机制很重要,缓冲区的安全性也同样重要。这样的情况下,该怎么结局呢?

封装!

缓冲区我们自己定,根据需要定义一个宏定义开放,再自己定义缓冲区。

判断机制封装好,当然,需要我们自己合理的判断,这个就是流程的架构了。需要顾全大局而不是简单的适应局面,我现在就是吃了这方面的亏,为了适应modbus,适应485的方式而去判断是错误的。甚至说,一个发送包开始,直到发送完成再开始写入,这本身也是一个错误的方法。比较正确的(当然,是我认为比较正确的),应该是将数据移入缓冲,然后让它自己发,整个缓冲区发送完成。我们不需要管它什么时候发, 什么时候发完成, 而只需要着眼于数据来了, 处理,发送。、

那么我们怎么知道数据来了呢?

开中断!回调!

这才是回调的作用,适用于你的应用编写。

而发送,接收,由串口中断自己管理。

于是,这样你就发现,你的协议跟你的串口关系没以前那么亲密了了,串口只相当于一个输入输出工具,协议只需要知道什么时候调用它自己,以及怎么发出数据,其他的就不用管了。就跟快递员跟客户一样。当发送完了,还有个回调,于是你就知道自己该干嘛了。

于是思想上,我觉得我解决了一个协议跟串口的依赖,但是跟底层硬件的奸情就难解决了,藕断丝连啊……

首先是初始化硬件需要用的是串口号,以及一些配置参数。

而一些功能转而放到状态结构体中,我们自己管理。

这里注意到一个重要的特点,采用中断发送的时候,当发送字节到数后关掉中断或者不再发数据。再设置状态位,于是我们就知道数据是否发完了,接收的同理。而如果有心的话,会发现可以定义一个指针直接给硬件寄存器赋值或者取值,因为在芯片里面他也是这样处理的。

于是我们可以直接把这个包含在结构体内。

这样做的作用是能够同时管理多个串口。同时多个串口收发。这个很重要,因为我们希望的是多个串口收发,因为你并不知道会有多少个串口,要跑多少个协议。尽量考虑到通用是很必要的。。

这样初步上也是思想上解决了跟硬件的奸情,虽然他们有小交往,但是随他们去把。

这里其实涉及到程序的模块化。。

网上很多都提到这个概念,但是机会没有好好说过怎么实现的,这个真的很让人讨厌。典型的挂羊头卖狗肉。

不过好像我基本很少在网上找资料,以为大部分时间是找不到的, 索性自己解决。包括这个也是自己想出来的。

其实模块化设计,简单的说,就是把一个功能的应用封装起来。怎么封装?理清上层调用的关系,底层承上的关系。通过这个关系,编写对应函数功能,也就是实现独立性,其中的函数会调用自己的东西,但是如果外界用不到这些, 就不要变成全局函数,只把外界要的函数编成全局函数,这就是所谓的封装,所有相关的函数封装到一起后成为一个模块。这就是所谓的模块化了。这个模块可以直接拷贝。几乎不需要怎么修改就可以直接用。当做到这一点就是所谓的程序模块化设计了,当然,这个需要很多编程经验的,至少现在我是做不到的。只是想想,应该无罪把。

好把,这个内容只是随便吐槽。-  -不代表任何观点,不反驳任何知识,只是随笔。

-------------------------------------------------------------------By. ㄨ。淡漠ャ.   2011-11-19

猜你喜欢

转载自blog.csdn.net/xuzhenglim/article/details/6990355