单片机软件常用设计分享(四)驱动设计之串口驱动设计

  • 前言
  • 《驱动设计–串口驱动》
  • 一、为什么要设计串口驱动
    • 1.CPU利用率低
    • 2.应用与串口底层耦合性高
    • 3.缺少串口统一管理的设计
  • 二、设计串口驱动需要解决的问题
    • 1.MCU内所有串口的统一管理
    • 2.MCU内所有串口的统一操作接口
    • 3.对串口数据发送的要求
    • 4.对串口数据接收的要求
    • 5.串口收发数据的压力测试要求
  • 三、如何设计串口驱动
    • 1.串口驱动任务的设计
      • 1)串口发送启动
      • 2)串口发送完成
      • 3)串口接收处理
      • 4)串口读取完成
    • 2.串口驱动操作接口函数的设计
      • 1)串口打开函数
      • 2)串口关闭函数
      • 3)串口控制函数
      • 4)串口读取函数
      • 5)串口打印函数
    • 3.串口驱动结构设计
  • 四、串口驱动源码
    • 1.Serial.h文件代码(驱动.h文件)
    • 2.Serial.c文件代码(驱动.c文件)
    • 3.驱动使用示列
    • 4.驱动中部分说明
    • 4.1 部分关联的数据结构

前言

   本人自从使用嵌入式实时操作系统后,就开始了串口的驱动设计,而这个驱动设计是具有严格意义的,因为它统一管理了MCU的所有串口,实现了非阻塞的自动处理发送与接收,同时设计并提供了的一些基本操作接口。
  在此之前,本人一直想努力设计一个与具体MCU耦合度较低的串口驱动,但发现比较难以完成。主要是每个厂家的MCU在串口设计上差别较大,同时软件驱动与硬件底层以及中断设计关联较深,如果非要把它分开,则需要传递的入口参数太多,这样就会变得比较复杂,并失去了原有的意义。因此,后面对于具体的串口驱动设计,则将依赖于一款具体的MCU。当然,我认为只要你能看懂并理解,将其移植到你使用的MCU还是很方便的。
  也很高兴有朋友能够对我之前写的部分文章表示赞许,甚至还期待着后续的写作。对此,本人深感荣幸,虽然此前对串口驱动的设计因为前述原因延期,但确实也有些拖拖拉拉的,变懒散了。今天,在2022春节即将到来前的最后一星期天,终于攒足了劲,完成了这篇文章,虽然可能有些差强人意,但还是先拿出来,后续如有需要再改进。无论如何,我给出的代码还是有积极意义的,希望对你有用。

《驱动设计–串口驱动》

  经过一段时间的思考,个人觉得串口驱动应该只适合操作系统,对于裸机,以下的说明可能不适用。
  以下将从3个角度去说明串口驱动设计,即为什么要设计串口驱动、设计串口驱动需要解决的问题、如何设计串口驱动。

一、为什么要设计串口驱动

1.CPU利用率低

  串口实际上是一个慢速硬件,其波特率与MCU的时钟相比,是不具备可比性的。因此,在应用层某个地方选择直接发送数据时,其它应用如果需要同时发送数据,则必然需要等待。此时,实际上有两个地方处于等待状态,一个是正在使用串口发送数据的应用,另一个是等待发送数据的应用,而实际上两个或更多的应用均被阻塞在那里。
  接收也存在相似的问题,如果当前串口接收到数据,应用层已经在解析数据,与此同时串口又收到数据,那么只能是等待应用层解析完数据后再处理;

2.应用与串口底层耦合性高

  目前大多数的人设计串口,一般是直接调用库函数的,其耦合性非常强。比如发送,基本是直接调用系统提供的库函数,使用poll/中断/DMA之一的模式。每个模块中涉及到串口输出的,就直接调用(DEBUG串口居多),这样既存在上面描述的问题,还有就是有些人不做互斥,也就是可能多个应用中同时对串口进行发送操作,想想这样会出现什么后果呢。
  另外,对于接收,很多就是接收到数据后,通知或调用解析函数(直接在中断中解析数据更加的不好)。当然,对于串口的操作不只是收发,还存在设置修改等,这些可能也存在于一些零零散散的代码里;

3.缺少串口统一管理的设计

  官方一般提供了一个库函数,但其中的串口操作是一些零散的最底层操作,其主要是实现对串口寄存器的组织的操作,并不完全适合于直接应用在客户的应用层软件中。如果你想将串口底层与应用层分开,则还需要设计一些接口,以隔离硬件与应用。当然,这些接口最好是标准的,比如提供打开串口、关闭串口,设置串口,串口写入,串口读取等等操作;

二、设计串口驱动需要解决的问题

1.MCU内所有串口的统一管理

  这个首先需要设计一个数据结构,其可以管理所有串口的初始化、去初始化、设置及参数等。这些结构可以适应每一个串口可以使用不同的操作方式(POLL/INTERRUPT/DMA)。另外,需要创建一个串口驱动任务(进程),其主要处理所有串口发送与接收操作;

2.MCU内所有串口的统一操作接口

  这个很重要,在创建串口驱动任务后,就应该获得一个在应用层使用的串口操作接口指针,其中至少应该包含:串口打开、串口关闭、串口控制、串口读取、串口写入等操作接口;这些操作接口仅凭一个入参(串口号)来区分不同的串口操作对象;

3.对串口数据发送的要求

  通过操作接口完成串口数据的发送,实现一个先入先出的功能,即以队列来暂存发送数据。在队列没有满的情况下,对应用层不进行阻塞,即调用完则离开,串口驱动任务会处理数据的发送(其依次取出队列的数据发送);

4.对串口数据接收的要求

  首先,串口接收不能丢失数据,也就是串口缓冲区需要一直接收数据,不能因为执行解析操作而暂停接收,接收缓冲区需要设计为环形结构。
  其次,每接收一帧数据,都能进行缓存并通知应用层进行解析,这个缓存也将采用队列的数据结构;

5.串口收发数据的压力测试要求

  串口收发数据的压力测试要求,其表现为连续发送或接收多帧数据的处理能力。这实际上是根据串口具体连接的硬件对象而定,其主要是修改或设置收发数据缓冲区尺寸及队列深度;

三、如何设计串口驱动

  本人在工作中,主要使用STM32及GD32系列MCU,并运行在FreeRTOS系统下。以下的介绍将以此为基础进行说明。

1.串口驱动任务的设计

  首先,串口驱动任务将作为一个基本任务或进程,在系统启动时首先执行,其在执行后将有利于调试串口输出调试信息。
  其次,串口驱动任务主要执行各种事务的处理,包括串口发送启动、串口发送结束、串口接收处理、串口读取完成。其中每一种事务的处理均是对所有串口进行遍历操作的,但执行时以非阻塞的方式完成(如果被阻塞了,会影响本串口或其它串口的其它事务处理)。为了便于以下描述,这里首先将给出串口驱动任务的主要代码。

//串口驱动任务
STATIC void Serial_Task(void *pvParameters)
{EventBits_t eventGroupValue;for (;;){//阻塞等待事件组eventGroupValue=xEventGroupWaitBits(pSerialDrv->eventGroupHandle,COM_EVENTGROUP,pdTRUE,pdFAIL,portMAX_DELAY);osMutexWait(pSerialDrv->optMutex,portMAX_DELAY);//检查并处理串口接收事件if  (eventGroupValue&COM_EVENTGROUP_RX)Serial_RxProcess(eventGroupValue);//检查并处理串口读取完成事件if  (eventGroupValue&COM_EVENTGROUP_RD)Serial_RdFinishProcess(eventGroupValue);//检查并处理串口发送完成事件if (eventGroupValue&COM_EVENTGROUP_TX_FINISH)Serial_TxFinishProcess(eventGroupValue);//检查并处理串口发送启动事件if (eventGroupValue&COM_EVENTGROUP_TX_START)Serial_TxStartProcess(eventGroupValue);osMutexRelease(pSerialDrv->optMutex);}
}

1)串口发送启动

  这个事件是由串口设计的操作接口触发的,一旦进入这个事件,则意味着至少有一个串口发送队列中有数据需要发送。因此,这个事务的处理实际上是遍历每一个串口是否有数据需要发送,并在检查到时启动发送。
  另外,串口发送设计了一个队列,用以传递发送数据缓冲区指针,其被定义如下:
  osMessageQId msg;

//串口发送启动实现代码
STATIC void Serial_TxStartProcess(EventBits_t   eventGroupValue)
{tComPort   comX;//循环遍历每一个串口是否有串口发送启动事件for  (comX=COM0;comX<COM_MAX;comX++){//串口打开,且有相应的发送启动事件时,进入发送启动处理if  (pSerialDrv->pObj[comX]!=NULL&&(eventGroupValue&COMX_EVENT_TX_START(comX))){//当前串口没有执行发送时,启动发送if    (pSerialDrv->pObj[comX]->tx.busy==FALSE)Serial_GetTxDataFromMessageToStart(comX);// 不进行展开说明,将在后面的完整代码中呈现}}
}

2)串口发送完成

  这个事件是由串口任务启动串口发送后由中断执行触发的(可能是串口发送中断,也可能是DMA TX完成中断),进入这个事件将处理相应串口的内存释放,检查发送队列中是否还有数据需要发送,如有则立即启动发送,否则禁止发送中断。

STATIC void Serial_TxFinishProcess(EventBits_t   eventGroupValue)
{tComPort   comX;//循环遍历每一个串口是否有串口发送完成事件for (comX=COM0;comX<COM_MAX;comX++){//串口打开,且有相应的发送完成事件时,进入发送完成处理if   (pSerialDrv->pObj[comX]&&(eventGroupValue&COMX_EVENT_TX_FINISH(comX))){//释放发送内存vPortFree(pSerialDrv->pObj[comX]->tx.pSend); pSerialDrv->pObj[comX]->tx.busy=FALSE;//检查等待发送队列(有则立即启动)if    (uxQueueMessagesWaiting(pSerialDrv->pObj[comX]->tx.msg)>0)Serial_GetTxDataFromMessageToStart(comX);if  (pSerialDrv->pObj[comX]->tx.busy==FALSE){//清空发送缓冲区并禁止发送中断pSerialDrv->pObj[comX]->tx.pSend=NULL;if    (pSerialDrv->pObj[comX]->optMode.sendUseDma==TRUE){usart_dma_transmit_config(usartHardConfig[comX].usart_periph,USART_DENT_DISABLE);dma_interrupt_disable(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_CHXCTL_FTFIE);nvic_irq_disable(usartDmaConfig[comX][TX].dma_nvic_irq);}elseusart_interrupt_disable(usartHardConfig[comX].usart_periph, USART_INT_TC);}}}
}

3)串口接收处理

  这个事件是由串口接收空闲中断执行时触发的,这里也将遍历每一个串口,检查到有串口接收事件时,将读取串口数据,并发送到串口读取队列中。同时,检查执行串口打开时是否提供回调函数,并执行调用回调函数功能。
  另外,串口接收设计了2个队列,其作用说明如下:
  osMessageQId msgIsr;这个队列主要用于将串口接收缓冲区中的数据偏移地址与数据长度发送到串口驱动任务中;
  osMessageQId msgApp;这个队列主要用于将串口串口驱动中申请的数据缓冲区地址与数据长度发送到应用层读取;
  串口接收处理函数为Serial_RxProcess(eventGroupValue),不进行展开说明,将在后面的完整代码中呈现。

4)串口读取完成

  首先说明,这个模块是在后来设计的,原有的设计中不包含这个模块。因为在压力测试中发现解析处理过程中多次执行回调函数会导致接收处理异常,或某些接收数据没有执行回调处理。因此,在读取完成后,再执行下一次的回调处理会比较合理,并且不再出错。
  其实,这里也就是保证每一个接收数据帧能都被有序的调用回调函数执行解析处理。

STATIC void Serial_RdFinishProcess(EventBits_t   eventGroupValue)
{tComPort       comX;//循环遍历每一个串口是否有串口读取完成事件for  (comX=COM0;comX<COM_MAX;comX++){//串口打开,且有相应的读取完成事件时,进入读取完成处理if  (pSerialDrv->pObj[comX]!=NULL&&(eventGroupValue&COMX_EVENT_RD_FINISH(comX))){if (pSerialDrv->pObj[comX]->rx.receiveFull==TRUE)pSerialDrv->pObj[comX]->rx.receiveFull=FALSE;//检查并执行下一次的回调函数处理(接收通知)if   (pSerialDrv->pObj[comX]->rx.RecNotice!=NULL&&uxQueueMessagesWaiting(pSerialDrv->pObj[comX]->rx.msgApp)>0)pSerialDrv->pObj[comX]->rx.RecNotice();}}
}

2.串口驱动操作接口函数的设计

  串口驱动任务仅仅执行了串口的收发操作的有序处理。但要使用串口驱动,则需要编写一些标准的操作接口,以便同串口驱动任务协同工作。这里的串口驱动操作接口函数,主要包括:串口打开、串口关闭、串口控制、串口读取、串口打印、串口格式化打印、串口ASCII打印。其中后面两个接口是增强设计,如没有必要可以不做设计(将在完整代码中呈现)。
  以下分别描述各个接口函数的设计。

1)串口打开函数

  这个接口函数主要执行串口驱动任务使用内存资源创建,串口硬件初始化,串口接收启动等操作。以下是具体的实现函数,只有执行过串口打开后,其它接口才可以使用。

STATIC BOOL Serial_Open(tComPort comX,tSerialUartParam* pParam,tSerialRecNoticeFunc RecNoticeFunc)
{if (pSerialDrv!=NULL  &&pSerialDrv->pObj[comX]==NULL &&inHandlerMode()==0){//创建串口内存资源pSerialDrv->pObj[comX]=(tSerialObj*)pvPortMalloc(sizeof(tSerialObj)+usartBuffMax[comX][RX]);if   (pSerialDrv->pObj[comX]==NULL)return   FALSE;//复制串口参数(包括波特率、数据位、停止位、奇偶校验等)pSerialDrv->pObj[comX]->param=*pParam;//串口使用的操作系统资源创建Serial_ParamInial(comX,RecNoticeFunc);//串口硬件初始化Serial_UsartXInit(comX);//启动串口接收Serial_ReceiveStart(comX);//以下函数暂时没有处理Serial_Enable(comX);return    TRUE;}return    FALSE;
}

2)串口关闭函数

  这个函数接口实际上就是执行一个串口的整体去初始化操作,释放操作系统资源,释放内存资源、释放硬件等。当然,串口关闭函数其实大多时候是不会使用的。以下是具体的实现函数:

STATIC BOOL Serial_Close(tComPort comX)
{//禁止串口操作(暂时没有处理)Serial_Disable(comX);//串口硬件去初始化Serial_UsartXDeInit(comX);//串口收发队列及链表释放Serial_TRxListFree(comX);//串口使用的操作系统资源释放Serial_ParamDeInial(comX);//串口内存资源释放vPortFree(pSerialDrv->pObj[comX]);pSerialDrv->pObj[comX]=NULL;return    TRUE;
}

3)串口控制函数

  这个函数接口主要实现串口参数修改,主要是波特率等参数修改。当然,也可以添加执行其它你希望的操作。这里实际上已经预留了其它操作的设计。以下是具体的实现函数:

STATIC BOOL Serial_Ctrl(tComPort comX,tSerialCtrl *pCtrl)
{switch (pCtrl->cmd){//串口参数修改case    SERIAL_CTRL_UARTPARAM:Serial_ParamSetup(comX);break;//串口放弃操作case    SERIAL_CTRL_ABORT:Serial_TRxListFree(comX);break;}return    TRUE;
}

4)串口读取函数

  串口读取操作,实际上是从队列中读取串口数据的缓冲区指针,调用者将通过这个返回的指针读取数据。另外,在读取完成后,将向串口驱动发送一个读取完成的回执,使得串口驱动可以执行下一次的回调函数功能。以下是具体的实现函数:

STATIC BOOL Serial_Read(tComPort comX,tSerialBuff **pSerialRx,tReadMode readMode,TickType_t xTicksToWait)
{//应用层读取到串口数据,在处理完成后需要释放pSerialRx指向的内存TickType_t delayTime;uint32_t  address;if  (pSerialDrv!=NULL&&pSerialDrv->pObj[comX]!=NULL){//执行读取时有三种操作模式,分别是仅读取一次/在指定时间内一直读取/不限时间的读取if   (readMode==READ_ONLYONCE)delayTime=1;else if (readMode==READ_SETTIME)delayTime=xTicksToWait;else if   (readMode==READ_WAITING)delayTime=portMAX_DELAY;elsereturn   FALSE;osMutexWait(pSerialDrv->pObj[comX]->rx.mutex, portMAX_DELAY);//这里将读取到缓冲区地址address=osQueueUint32DataTake(pSerialDrv->pObj[comX]->rx.msgApp,delayTime);if  (address!=NULL){*pSerialRx=(tSerialBuff *)address;//向串口驱动发送读取完成的事件组   osEventGroupSet(pSerialDrv->eventGroupHandle,COMX_EVENT_RD_FINISH(comX));osMutexRelease(pSerialDrv->pObj[comX]->rx.mutex);return   TRUE;}elsepSerialRx=NULL;osMutexRelease(pSerialDrv->pObj[comX]->rx.mutex);}return    FALSE;
}

5)串口打印函数

  串口打印函数,主要完成将发送数据拷贝出来,并添加到发送队列中,然后,向串口驱动任务发送启动事件组。以下是具体的实现函数:

STATIC BOOL Serial_Print(tComPort comX,uint8_t *pData,uint16_t length)
{tMessage   message;tSerialBuff *pSerialTx;//串口已经打开,并且数据有效时执行if  (pSerialDrv!=NULL&&pSerialDrv->pObj[comX]!=NULL&&pData!=NULL&&length>0){//检查发送队列(至少需要保留一个空队列)if  (uxQueueSpacesAvailable(pSerialDrv->pObj[comX]->tx.msg)<=1)return FALSE;//同一串口调用之间的互斥(其作用实际上是避免无限执行耗尽内存)osMutexWait(pSerialDrv->pObj[comX]->tx.mutex, portMAX_DELAY);if   (TRUE&&inHandlerMode()==0){//非中断中执行,可以申请内存进行拷贝pSerialTx=(tSerialBuff*)pvPortMalloc(sizeof(tSerialBuff)+length);if   (pSerialTx==NULL){osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return FALSE;}//拷贝数据并设置消息pSerialTx->pBuff=((uint8_t*)pSerialTx)+sizeof(tSerialBuff);memcpy(pSerialTx->pBuff,pData,length);pSerialTx->length=length;message.event=0;message.address=(uint32_t)pSerialTx;//执行消息发送if  (osMessagePut(pSerialDrv->pObj[comX]->tx.msg,message,100)==FALSE){vPortFree(pSerialTx);osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return    FALSE;}}else{//在中断内,不能调用pvPortMalloc申请内存,因此设计了参数传递,同时借用message.event传递数据长度message.event=length;message.address=(uint32_t)pData;//执行消息发送if   (osMessagePut(pSerialDrv->pObj[comX]->tx.msg,message,100)==FALSE){osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return FALSE;}}//向串口任务发送事件组        osEventGroupSet(pSerialDrv->eventGroupHandle,COMX_EVENT_TX_START(comX));osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return    TRUE;}return    FALSE;
}

3.串口驱动结构设计

  感觉在这里再仔细描述串口驱动所使用的每一个数据结构,好像似乎有些多余了。因为我是打算给出完整的驱动代码,因此我将不再像以前一样重复,尽量在完整代码中将注释做多一点,这里就不再多讲了。

四、串口驱动源码

  说明:此代码基于FreeRTOS V10.2.1,MCU为GD32F409。代码中的某些部分基于GD32的库函数。另外,以下代码文件并非独立的存在,其必定依赖于一些其它文件,这里我将不会一一列出。

1.Serial.h文件代码(驱动.h文件)

#ifndef _SERIAL_H
#define _SERIAL_H
#include "typedef.h"  //此头文件仅仅是一些类型定义,此处不再给出,但说明一下其中几个定义
/*
typedef.h中的部分定义
typedef SemaphoreHandle_t   osMutexId;
typedef SemaphoreHandle_t   osSemaphoreId;
typedef QueueHandle_t       osMessageQId;
*/
//串口参数-停止位定义
#define UART_STOPBIT_0P5                0
#define UART_STOPBIT_1                  1
#define UART_STOPBIT_2                  2
#define UART_STOPBIT_1P5                3
//串口参数-奇偶校验定义
#define UART_PARITY_NONE                0
#define UART_PARITY_ODD                 1
#define UART_PARITY_OVEN                2//串口参数类型定义
typedef struct
{uint32_t               baudRate;               //波特率uint8_t                    wordLength;             //字长(8/9)uint8_t                    stopBits;               //停止位(0/1/2/3<0.5/1/2/1.5>)uint8_t                    parity;                 //奇偶校验位(0/1/2:无/奇/偶)
}tSerialUartParam;//串口参数//com口定义,注这里是我一个项目中使用串口的具体定义
typedef enum
{COM0,COM1,COM2,COM5,COM_MAX,
}tComPort;//COM口//串口数据读取方式定义
typedef enum
{READ_ONLYONCE=0,                              //仅读一次READ_SETTIME=1,                                  //指定等待时间READ_WAITING=2                                 //一直等待
}tReadMode;//串口外部读取数据方式//串口底层收发使用传输操作方式
typedef struct
{BOOL                   sendUseDma;             //TURE使用DMA,FALSE使用中断BOOL                    receiveUseDma;          //TURE使用DMA,FALSE使用中断
}tUsartOptMode;//串口底层收发使用传输操作方式//串口收发缓存
typedef struct
{uint8_t                    *pBuff;                 //缓存区指针uint16_t             length;                 //缓存区长度
}tSerialBuff;//串口收发缓存//串口控制操作命令
typedef enum
{SERIAL_CTRL_UARTPARAM,                         //控制修改串口参数SERIAL_CTRL_ABORT,                                //控制串口放弃操作
}tSerialCtrlCmd;//串口控制操作命令//串口控制操作
typedef struct
{tSerialCtrlCmd                 cmd;                //设置命令tSerialUartParam              uartParam;          //串口参数
}tSerialCtrl;//串口控制操作//串口操作接口功能类型定义
typedef BOOL(*tSerialRecNoticeFunc)(void);  //串口接收通知回调函数
typedef BOOL(*tSerialOpenFunc)(tComPort,tSerialUartParam*,tSerialRecNoticeFunc);
typedef BOOL(*tSerialCloseFunc)(tComPort);
typedef BOOL(*tSerialCtrlFunc)(tComPort,tSerialCtrl*);
typedef BOOL(*tSerialReadFunc)(tComPort,tSerialBuff**,tReadMode,TickType_t);
typedef BOOL(*tSerialPrintFunc)(tComPort,uint8_t*,uint16_t,BOOL);
typedef BOOL(*tSerialPrintAsciiFunc)(tComPort,char*,uint8_t*,uint16_t);
typedef BOOL(*tSerialPrintStrFunc)(tComPort,char *fmt,...);//串口驱动操作接口类型定义
typedef struct
{/*******************************************************************************************************Open(tComPort comX,tSerialUartParam* pParam,tSerialRecNoticeFunc RecNoticeFunc)调用参数说明如下:comX:打开的COM口号pParam:串口参数RecNoticeFunc:串口接收到数据后的应用层回调函数实际使用举例如下:Open(DBG_COM,&uartParam,DebugRecNotice);*******************************************************************************************************/tSerialOpenFunc          Open;//Open(tComPort comX,tSerialUartParam* pParam,tSerialRecNoticeFunc RecNoticeFunc)/*******************************************************************************************************Close(tComPort comX)调用参数说明如下:(注意,目前暂时没有设计这个功能)comX:关闭的COM口号实际使用举例如下:Close(DBG_COM);*******************************************************************************************************/tSerialCloseFunc       Close;//Close(DBG_COM)/*******************************************************************************************************Ctrl(tComPort comX,tSerialCtrl *pCtrl)调用参数说明如下:(注意,目前串口参数功能没有设计)comX:控制操作的COM口号pCtrl:串口控制操作参数数据指针实际使用举例如下:Ctrl(DBG_COM,&ctrlParam);*******************************************************************************************************/tSerialCtrlFunc            Ctrl;//Ctrl(tComPort comX,tSerialCtrl *pCtrl)/*******************************************************************************************************Read(tComPort comX,tSerialBuff **pSerialRx,tReadMode readMode,TickType_t xTicksToWait)调用参数说明如下:comX:读取串口接收数据的COM口号pSerialRx:接收串口数据的缓存指针readMode:读取数据的模式,包括READ_ONLYONCE(仅读一次)/READ_SETTIME(指定等待时间)/READ_WAITING(一直等待)xTicksToWait:读取时最大等待时间实际使用举例如下:Read(DBG_COM,&pSerialBuff,READ_WAITING,0);*******************************************************************************************************/tSerialReadFunc            Read;//Read(tComPort comX,tSerialBuff **pSerialRx,tReadMode readMode,TickType_t xTicksToWait)/*******************************************************************************************************Print(tComPort comX,uint8_t *pData,uint16_t length)调用参数说明如下:comX:写入串口数据的COM口号pData:写入数据缓冲区指针length:写入数据长度实际使用举例如下:Print(DBG_COM,pBuff,length);注:此函数可以在中断里调用*******************************************************************************************************/tSerialPrintFunc      Print;//Print(tComPort comX,uint8_t *pData,uint16_t length,BOOL copy)/*******************************************************************************************************PrintAscii(tComPort comX,char* pTitle,uint8_t *pData,uint16_t length,BOOL retSymbol)调用参数说明如下:comX:写入串口ASCII数据的COM口号pTitle:附加的输出标题,可以设置为NULLpData:写入数据缓冲区指针length:写入数据长度retSymbol:是否在数据末尾增加\r\n实际使用举例如下:PrintAscii(DBG_COM,"接收数据:",pBuff,length,TRUE);注:此函数不可以在中断中调用,即便调用也拒绝执行*******************************************************************************************************/tSerialPrintAsciiFunc    PrintAscii;//PrintAscii(tComPort comX,char* pTitle,uint8_t *pData,uint16_t length,BOOL retSymbol)/*******************************************************************************************************PrintStr(tComPort comX,char *fmt,...)调用参数说明如下:comX:写入串口字符串数据的COM口号fmt:格式化字符串实际使用举例如下:PrintStr(DBG_COM,“接收数据:%s\r\n”,pBuff);注:此函数原则上不在中断中调用(属于不安全调用),即便调用也仅输出字符串部分,格式解析不会执行*******************************************************************************************************/tSerialPrintStrFunc     PrintStr;//PrintStr(tComPort comX,char *fmt,...)
}tSerialHandle;//串口驱动外部调用接口//串口发送结构
typedef struct
{osMessageQId           msg;                    //发送消息队列osMutexId               mutex;                  //发送互斥量tSerialBuff              *pSend;                 //缓存指针BOOL                  busy;                   //发送忙uint16_t               tranmitCounterISR;      //通过中断发送数据的计数值uint16_t              sendLengthMax;          //允许发送数据最大长度
}tSerialTx;//串口发送//串口接收缓冲区读写偏移量
typedef struct
{uint16_t               read;uint16_t               write;
}tSerialRxOffset;//串口接收缓冲区读写偏移量//串口接收结构
typedef struct
{BOOL                   receiveFull;            //接收队列已经满tSerialRecNoticeFunc   RecNotice;              //接收串口数据通知回调函数(只能传递非阻塞的消息通知函数,否则可能会阻塞串口任务)osMessageQId           msgIsr;                 //接收消息队列(用于中断发送到任务)osMessageQId             msgApp;                 //接收消息队列(用于任务发送到应用层)osMutexId               mutex;                  //接收互斥量tSerialBuff              get;                    //缓存tSerialRxOffset         offset;                 //接收缓冲区读写偏移量
}tSerialRx;//串口接收  //串口操作对象
typedef struct
{tSerialTx              tx;                     //发送对象tSerialRx             rx;                     //接收对象tSerialUartParam      param;                  //串口参数tUsartOptMode         optMode;                //串口操作模式(收发分别使用DMA或中断)
}tSerialObj;//串口操作对象//串口驱动结构
typedef struct
{EventGroupHandle_t     eventGroupHandle;       //串口任务事件组osMutexId              optMutex;               //串口操作互斥量tSerialObj             *pObj[COM_MAX];         //串口对象
}tSerialDriver;//串口驱动//串口接收传递参数
typedef union
{struct{uint16_t            len;                    //串口接收数据长度uint16_t          offset;                 //串口接收缓冲区数据起始偏移位置}item;//串口接收uint32_t               address;                //消息地址
}tSerialRecInfo;//串口接收传递参数
//========================================================================================
//以下是以下任务内的配置
//串口配置参数(可能需要修改)
//XXX1使用串口
#define UART0Tx_QueueDepth              10
#define UART0Rx_QueueDepth              10
#define UART0Tx_BuffMax                 4096
#define UART0Rx_BuffMax                 4096
#define UART0_SENDMODE                  TRUE        //TRUE使用DMA,FALSE使用中断INTERRUPT
#define UART0_RECVMODE                  TRUE        //TRUE使用DMA,FALSE使用中断INTERRUPT
//XXX2使用串口
#define UART1Tx_QueueDepth              20
#define UART1Rx_QueueDepth              20
#define UART1Tx_BuffMax                 4096
#define UART1Rx_BuffMax                 4096
#define UART1_SENDMODE                  TRUE        //TRUE使用DMA,FALSE使用中断INTERRUPT
#define UART1_RECVMODE                  TRUE        //TRUE使用DMA,FALSE使用中断INTERRUPT
//XXX3使用串口
#define UART2Tx_QueueDepth              10
#define UART2Rx_QueueDepth              10
#define UART2Tx_BuffMax                 256
#define UART2Rx_BuffMax                 1024
#define UART2_SENDMODE                  TRUE        //TRUE使用DMA,FALSE使用中断INTERRUPT
#define UART2_RECVMODE                  TRUE        //TRUE使用DMA,FALSE使用中断INTERRUPT
//DEBUG使用串口
#define UART5Tx_QueueDepth              50
#define UART5Rx_QueueDepth              10
#define UART5Tx_BuffMax                 4096
#define UART5Rx_BuffMax                 (4096+32)
#define UART5_SENDMODE                  TRUE        //TRUE使用DMA,FALSE使用中断INTERRUPT
#define UART5_RECVMODE                  TRUE        //TRUE使用DMA,FALSE使用中断INTERRUPT
//进一步封装串口号
#define XXX1_COM                        COM0
#define XXX2_COM                        COM1
#define XXX3_COM                        COM2
#define DBG_COM                         COM5
//========================================================================================
const tSerialHandle* SerialTask_Register(void);
void Serial_UartInterruptHandler(const tComPort comX);
void Serial_DmaTxInterruptHandler(const tComPort    comX);
//外部调用的串口缓冲区内存释放操作
#define Free_ComxRxBuff(pBuff)          vPortFree((uint8_t*)(pBuff)-sizeof(tSerialBuff))
#endif

2.Serial.c文件代码(驱动.c文件)

#include "Serial.h"
#include "system.h"
//串口硬件接口
typedef struct
{uint32_t               usart_periph;           //串口外设硬件寄存器地址IRQn_Type              usart_nvic_irq;         //串口外设中断号rcu_periph_enum            periph_RCU_Gpio;        //外设硬件GPIO端口时钟地址rcu_periph_enum         periph_RCU_uart;        //外设硬件串口时钟地址uint32_t                gpio_periph;            //外设GPIO端口寄存器地址uint32_t             tx_pin;                 //串口TX外设GPIO引脚号uint32_t             rx_pin;                 //串口RX外设GPIO引脚号uint32_t                 alt_func_num;           //GPIO多功能配置序号
}tUsartHard;//串口硬件接口//串口DMA操作硬件配置
typedef struct
{rcu_periph_enum            rcu_periph;             //DMA时钟地址IRQn_Type              dma_nvic_irq;           //DMA中断号uint32_t                dma_periph;             //DMA外设地址dma_channel_enum       dma_channel;            //DMA通道号dma_subperipheral_enum  subperipheral_enum;     //DMA子通道号uint32_t               periph_address;         //串口数据寄存器地址
}tUsartDma;//串口DMA操作硬件配置
//========================================================================================
//任务栈与任务优先级
#define Serial_TaskStackDepth           (configMINIMAL_STACK_SIZE*3)
#define Serial_TaskPriority             (configMAX_PRIORITIES-1)//串口任务事件组定义(最多可定义6个串口)
#define COM_EVENTGROUP_TX_START_LOC     0
#define COM_EVENTGROUP_TX_FINISH_LOC    6
#define COM_EVENTGROUP_RX_LOC           12
#define COM_EVENTGROUP_RD_LOC           18
//事件组操作宏定义
#define COMX_EVENT_TX_START(comX)       (1UL<<((comX)+COM_EVENTGROUP_TX_START_LOC))
#define COMX_EVENT_TX_FINISH(comX)      (1UL<<((comX)+COM_EVENTGROUP_TX_FINISH_LOC))
#define COMX_EVENT_RX(comX)             (1UL<<((comX)+COM_EVENTGROUP_RX_LOC))
#define COMX_EVENT_RD_FINISH(comX)      (1UL<<((comX)+COM_EVENTGROUP_RD_LOC))
//发送启动事件组
#define COM_EVENTGROUP_TX1_START        (1UL<<COM_EVENTGROUP_TX_START_LOC)
#define COM_EVENTGROUP_TX2_START        (1UL<<(COM_EVENTGROUP_TX_START_LOC+1))
#define COM_EVENTGROUP_TX3_START        (1UL<<(COM_EVENTGROUP_TX_START_LOC+2))
#define COM_EVENTGROUP_TX4_START        (1UL<<(COM_EVENTGROUP_TX_START_LOC+3))
#define COM_EVENTGROUP_TX5_START        (1UL<<(COM_EVENTGROUP_TX_START_LOC+4))
#define COM_EVENTGROUP_TX6_START        (1UL<<(COM_EVENTGROUP_TX_START_LOC+5))
//事件组集合
#define COM_EVENTGROUP_TX_START         (COM_EVENTGROUP_TX1_START|\COM_EVENTGROUP_TX2_START|\COM_EVENTGROUP_TX3_START|\COM_EVENTGROUP_TX4_START|\COM_EVENTGROUP_TX5_START|\COM_EVENTGROUP_TX6_START)
//发送完成事件组
#define COM_EVENTGROUP_TX1_FINISH       (1UL<<COM_EVENTGROUP_TX_FINISH_LOC)
#define COM_EVENTGROUP_TX2_FINISH       (1UL<<(COM_EVENTGROUP_TX_FINISH_LOC+1))
#define COM_EVENTGROUP_TX3_FINISH       (1UL<<(COM_EVENTGROUP_TX_FINISH_LOC+2))
#define COM_EVENTGROUP_TX4_FINISH       (1UL<<(COM_EVENTGROUP_TX_FINISH_LOC+3))
#define COM_EVENTGROUP_TX5_FINISH       (1UL<<(COM_EVENTGROUP_TX_FINISH_LOC+4))
#define COM_EVENTGROUP_TX6_FINISH       (1UL<<(COM_EVENTGROUP_TX_FINISH_LOC+5))
//事件组集合
#define COM_EVENTGROUP_TX_FINISH        (COM_EVENTGROUP_TX1_FINISH|\COM_EVENTGROUP_TX2_FINISH|\COM_EVENTGROUP_TX3_FINISH|\COM_EVENTGROUP_TX4_FINISH|\COM_EVENTGROUP_TX5_FINISH|\COM_EVENTGROUP_TX6_FINISH)
//所有发送事件组集合
#define COM_EVENTGROUP_TX               (COM_EVENTGROUP_TX_START|COM_EVENTGROUP_TX_FINISH)
//接收数据事件组
#define COM_EVENTGROUP_RX1              (1UL<<COM_EVENTGROUP_RX_LOC)
#define COM_EVENTGROUP_RX2              (1UL<<(COM_EVENTGROUP_RX_LOC+1))
#define COM_EVENTGROUP_RX3              (1UL<<(COM_EVENTGROUP_RX_LOC+2))
#define COM_EVENTGROUP_RX4              (1UL<<(COM_EVENTGROUP_RX_LOC+3))
#define COM_EVENTGROUP_RX5              (1UL<<(COM_EVENTGROUP_RX_LOC+4))
#define COM_EVENTGROUP_RX6              (1UL<<(COM_EVENTGROUP_RX_LOC+5))
//事件组集合
#define COM_EVENTGROUP_RX               (COM_EVENTGROUP_RX1|\COM_EVENTGROUP_RX2|\COM_EVENTGROUP_RX3|\COM_EVENTGROUP_RX4|\COM_EVENTGROUP_RX5|\COM_EVENTGROUP_RX6)
// 读取数据完成事件组
#define COM_EVENTGROUP_RD1              (1UL<<COM_EVENTGROUP_RD_LOC)
#define COM_EVENTGROUP_RD2              (1UL<<(COM_EVENTGROUP_RD_LOC+1))
#define COM_EVENTGROUP_RD3              (1UL<<(COM_EVENTGROUP_RD_LOC+2))
#define COM_EVENTGROUP_RD4              (1UL<<(COM_EVENTGROUP_RD_LOC+3))
#define COM_EVENTGROUP_RD5              (1UL<<(COM_EVENTGROUP_RD_LOC+4))
#define COM_EVENTGROUP_RD6              (1UL<<(COM_EVENTGROUP_RD_LOC+5))
//事件组集合
#define COM_EVENTGROUP_RD               (COM_EVENTGROUP_RD1|\COM_EVENTGROUP_RD2|\COM_EVENTGROUP_RD3|\COM_EVENTGROUP_RD4|\COM_EVENTGROUP_RD5|\COM_EVENTGROUP_RD6)//整个串口驱动事件组集合
#define COM_EVENTGROUP                  (COM_EVENTGROUP_TX|COM_EVENTGROUP_RX|COM_EVENTGROUP_RD)
//串口驱动指针
tSerialDriver   *pSerialDrv;
//========================================================================================
#define TX                              0
#define RX                              1
//串口数据寄存器地址
#define USART0_DATA_ADDRESS             0x40011004
#define USART1_DATA_ADDRESS             0x40004404
#define USART2_DATA_ADDRESS             0x40004804
#define USART5_DATA_ADDRESS             0x40011404
//串口收发缓冲区大小配置
const uint16_t  usartBuffMax[COM_MAX][2]=
{{UART0Tx_BuffMax, UART0Rx_BuffMax},//usart0{UART1Tx_BuffMax, UART1Rx_BuffMax},//usart1{UART2Tx_BuffMax, UART2Rx_BuffMax},//usart2{UART5Tx_BuffMax, UART5Rx_BuffMax},//usart5
};
//串口收发队列深度配置
const uint8_t   usartQueueDepth[COM_MAX][2]=
{{UART0Tx_QueueDepth, UART0Rx_QueueDepth},//usart0{UART1Tx_QueueDepth, UART1Rx_QueueDepth},//usart1{UART2Tx_QueueDepth, UART2Rx_QueueDepth},//usart2{UART5Tx_QueueDepth, UART5Rx_QueueDepth},//usart5
};
//以下参数用于分别控制串口发送和接收方式,仅包括:DMA和INTERRUPT
const tUsartOptMode usartOptMode[COM_MAX]=
{{UART0_SENDMODE, UART0_RECVMODE},//usart0  {UART1_SENDMODE, UART1_RECVMODE},//usart1   {UART2_SENDMODE, UART2_RECVMODE},//usart2   {UART5_SENDMODE, UART5_RECVMODE},//usart5
};
//串口硬件配置
const tUsartHard usartHardConfig[COM_MAX]=
{{USART0, USART0_IRQn, RCU_GPIOA, RCU_USART0, GPIOA, GPIO_PIN_9,  GPIO_PIN_10, GPIO_AF_7},//usart0{USART1, USART1_IRQn, RCU_GPIOA, RCU_USART1, GPIOA, GPIO_PIN_2,  GPIO_PIN_3,  GPIO_AF_7},//usart1{USART2, USART2_IRQn, RCU_GPIOB, RCU_USART2, GPIOB, GPIO_PIN_10, GPIO_PIN_11, GPIO_AF_7},//usart2{USART5, USART5_IRQn, RCU_GPIOC, RCU_USART5, GPIOC, GPIO_PIN_6,  GPIO_PIN_7,  GPIO_AF_8},//usart5
};
//串口使用DMA配置
const tUsartDma usartDmaConfig[COM_MAX][2]=
{   //{TX},{RX}{{RCU_DMA1, DMA1_Channel7_IRQn, DMA1, DMA_CH7, DMA_SUBPERI4, USART0_DATA_ADDRESS},{RCU_DMA1, DMA1_Channel5_IRQn, DMA1, DMA_CH5, DMA_SUBPERI4, USART0_DATA_ADDRESS}},//usart0{{RCU_DMA0, DMA0_Channel6_IRQn, DMA0,DMA_CH6,  DMA_SUBPERI4, USART1_DATA_ADDRESS},{RCU_DMA0, DMA0_Channel5_IRQn, DMA0,DMA_CH5,  DMA_SUBPERI4, USART1_DATA_ADDRESS}},//usart1{{RCU_DMA0, DMA0_Channel3_IRQn, DMA0,DMA_CH3,  DMA_SUBPERI4, USART2_DATA_ADDRESS},{RCU_DMA0, DMA0_Channel1_IRQn, DMA0,DMA_CH1,  DMA_SUBPERI4, USART2_DATA_ADDRESS}},//usart2{{RCU_DMA1, DMA1_Channel6_IRQn, DMA1,DMA_CH6,  DMA_SUBPERI5, USART5_DATA_ADDRESS},{RCU_DMA1, DMA1_Channel1_IRQn, DMA1,DMA_CH1,  DMA_SUBPERI5, USART5_DATA_ADDRESS}},//usart5
};
//========================================================================================
//串口驱动函设计
//十六进制数据的ASCII码
STATIC char GetAsciiCodeFromUint8(uint8_t inputData)
{if (inputData<=9)return    inputData+'0';if (inputData>=10&&inputData<=15)return    'A'+inputData-10;return  ' ';
}//Serial_UartInterruptHandler函数需要放置到相应的串口中断
void Serial_UartInterruptHandler(tComPort   comX)
{uint16_t   dmaRxRemainCount;if (pSerialDrv->pObj[comX]->optMode.receiveUseDma==TRUE)dmaRxRemainCount=dma_transfer_number_get(usartDmaConfig[comX][RX].dma_periph,usartDmaConfig[comX][RX].dma_channel);//发送中断if   (usart_interrupt_flag_get(usartHardConfig[comX].usart_periph,USART_INT_FLAG_TC)==SET){usart_interrupt_flag_clear(usartHardConfig[comX].usart_periph,USART_INT_FLAG_TC);pSerialDrv->pObj[comX]->tx.tranmitCounterISR++;if  (pSerialDrv->pObj[comX]->tx.tranmitCounterISR==pSerialDrv->pObj[comX]->tx.pSend->length)osEventGroupSet(pSerialDrv->eventGroupHandle,COMX_EVENT_TX_FINISH(comX));//发送完成elseusart_data_transmit(usartHardConfig[comX].usart_periph,  pSerialDrv->pObj[comX]->tx.pSend->pBuff[pSerialDrv->pObj[comX]->tx.tranmitCounterISR]);}//接收中断if (usart_interrupt_flag_get(usartHardConfig[comX].usart_periph,USART_INT_FLAG_RBNE)==SET){pSerialDrv->pObj[comX]->rx.get.pBuff[pSerialDrv->pObj[comX]->rx.offset.write++]=                               (uint8_t)usart_data_receive(usartHardConfig[comX].usart_periph);if  (pSerialDrv->pObj[comX]->rx.offset.write>=pSerialDrv->pObj[comX]->rx.get.length)pSerialDrv->pObj[comX]->rx.offset.write=0;}//空闲中断if (usart_interrupt_flag_get(usartHardConfig[comX].usart_periph, USART_INT_FLAG_IDLE)==SET){USART_STAT0(usartHardConfig[comX].usart_periph);usart_data_receive(usartHardConfig[comX].usart_periph);if (pSerialDrv->pObj[comX]->optMode.receiveUseDma==TRUE)pSerialDrv->pObj[comX]->rx.offset.write=pSerialDrv->pObj[comX]->rx.get.length-dmaRxRemainCount;if (pSerialDrv->pObj[comX]->rx.offset.write!=pSerialDrv->pObj[comX]->rx.offset.read){tSerialRecInfo   serialRecInfo;if    (pSerialDrv->pObj[comX]->rx.offset.write>pSerialDrv->pObj[comX]->rx.offset.read)serialRecInfo.item.len=pSerialDrv->pObj[comX]->rx.offset.write-pSerialDrv->pObj[comX]->rx.offset.read;elseserialRecInfo.item.len=pSerialDrv->pObj[comX]->rx.get.length+pSerialDrv->pObj[comX]->rx.offset.write-pSerialDrv->pObj[comX]->rx.offset.read;serialRecInfo.item.offset=pSerialDrv->pObj[comX]->rx.offset.read;pSerialDrv->pObj[comX]->rx.offset.read=pSerialDrv->pObj[comX]->rx.offset.write;//if  (uxQueueSpacesAvailable(pSerialDrv->pObj[comX]->rx.msgIsr)>0){osQueueUint32DataSend(pSerialDrv->pObj[comX]->rx.msgIsr,serialRecInfo.address,1);//如果传输队列满,则会直接丢弃数据osEventGroupSet(pSerialDrv->eventGroupHandle,COMX_EVENT_RX(comX));}}}//以下属于发生错误中断,暂时不做处理,后面有时间再考虑怎样处理//错误中断包括:溢出/噪声/帧/校验等错误if  (usart_interrupt_flag_get(usartHardConfig[comX].usart_periph, USART_INT_FLAG_ERR_ORERR)==SET||usart_interrupt_flag_get(usartHardConfig[comX].usart_periph, USART_INT_FLAG_ERR_NERR)==SET||usart_interrupt_flag_get(usartHardConfig[comX].usart_periph, USART_INT_FLAG_ERR_FERR)==SET||usart_interrupt_flag_get(usartHardConfig[comX].usart_periph, USART_INT_FLAG_PERR)==SET){USART_STAT0(usartHardConfig[comX].usart_periph);usart_data_receive(usartHardConfig[comX].usart_periph);}
}//Serial_DmaTxInterruptHandler函数需要放置到相应的DMA TX中断函数中
void Serial_DmaTxInterruptHandler(tComPort  comX)
{if (dma_interrupt_flag_get(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_FTFIF)==SET){dma_interrupt_flag_clear(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_FTFIF);osEventGroupSet(pSerialDrv->eventGroupHandle,COMX_EVENT_TX_FINISH(comX));}if  (dma_interrupt_flag_get(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_HTFIF)==SET)dma_interrupt_flag_clear(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_HTFIF);//以下属于发生错误中断,暂时不做处理,后面有时间再考虑怎样处理//错误中断包括:FIFO错误与FIFO异常/单数据传输模式异常/传输等错误if   (dma_interrupt_flag_get(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_FEEIF)==SET)dma_interrupt_flag_clear(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_FEEIF);if    (dma_interrupt_flag_get(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_SDEIF)==SET)dma_interrupt_flag_clear(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_SDEIF);if    (dma_interrupt_flag_get(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_TAEIF)==SET)dma_interrupt_flag_clear(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_TAEIF);
}//串口中断发送操作
STATIC void Serial_UsartInterrupteTransmit(tComPort comX)
{usart_interrupt_enable(usartHardConfig[comX].usart_periph, USART_INT_TC);pSerialDrv->pObj[comX]->tx.tranmitCounterISR=0;usart_data_transmit(usartHardConfig[comX].usart_periph,pSerialDrv->pObj[comX]->tx.pSend->pBuff[0]);
}//串口中断接收操作
STATIC void Serial_UsartInterrupteReceive(tComPort comX)
{usart_interrupt_enable(usartHardConfig[comX].usart_periph, USART_INT_RBNE);
}//串口DMA发送操作
STATIC void Serial_UsartDmaTransmit(tComPort comX)
{dma_single_data_parameter_struct dma_init_struct;/* enable DMAx */rcu_periph_clock_enable(usartDmaConfig[comX][TX].rcu_periph);/* deinitialize DMA channelX(USARTx TX) */dma_deinit(usartDmaConfig[comX][TX].dma_periph, usartDmaConfig[comX][TX].dma_channel);dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;dma_init_struct.memory0_addr = (uint32_t)pSerialDrv->pObj[comX]->tx.pSend->pBuff;dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;dma_init_struct.number = pSerialDrv->pObj[comX]->tx.pSend->length;dma_init_struct.periph_addr = usartDmaConfig[comX][TX].periph_address;dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;dma_single_data_mode_init(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,&dma_init_struct);dma_channel_subperipheral_select(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,usartDmaConfig[comX][TX].subperipheral_enum);/* configure DMA mode */dma_circulation_disable(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel);dma_channel_enable(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel);dma_interrupt_enable(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_CHXCTL_FTFIE);nvic_irq_enable(usartDmaConfig[comX][TX].dma_nvic_irq, 3, 0);usart_dma_transmit_config(usartHardConfig[comX].usart_periph,USART_DENT_ENABLE);
}//串口DMA接收操作
STATIC void Serial_UsartDmaReceive(tComPort comX)
{dma_single_data_parameter_struct dma_init_struct;/* enable DMAx */rcu_periph_clock_enable(usartDmaConfig[comX][RX].rcu_periph);/* deinitialize DMA channelX(USARTx RX) */dma_deinit(usartDmaConfig[comX][RX].dma_periph, usartDmaConfig[comX][RX].dma_channel);dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;dma_init_struct.memory0_addr =(uint32_t)pSerialDrv->pObj[comX]->rx.get.pBuff;dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;dma_init_struct.number = (uint32_t)pSerialDrv->pObj[comX]->rx.get.length;dma_init_struct.periph_addr = usartDmaConfig[comX][RX].periph_address;dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;dma_single_data_mode_init(usartDmaConfig[comX][RX].dma_periph,usartDmaConfig[comX][RX].dma_channel,&dma_init_struct);dma_channel_subperipheral_select(usartDmaConfig[comX][RX].dma_periph,usartDmaConfig[comX][RX].dma_channel,usartDmaConfig[comX][RX].subperipheral_enum);/* configure DMA mode */dma_circulation_enable(usartDmaConfig[comX][RX].dma_periph, usartDmaConfig[comX][RX].dma_channel);dma_channel_enable(usartDmaConfig[comX][RX].dma_periph, usartDmaConfig[comX][RX].dma_channel);usart_dma_receive_config(usartHardConfig[comX].usart_periph, USART_DENR_ENABLE);
}//启动串口接收
STATIC void Serial_ReceiveStart(tComPort comX)
{pSerialDrv->pObj[comX]->rx.offset.read=0;pSerialDrv->pObj[comX]->rx.offset.write=0;if    (pSerialDrv->pObj[comX]->optMode.receiveUseDma==TRUE)Serial_UsartDmaReceive(comX);elseSerial_UsartInterrupteReceive(comX);usart_interrupt_enable(usartHardConfig[comX].usart_periph, USART_INT_IDLE);
}//串口硬件初始化
STATIC void Serial_UsartXInit(tComPort comX)
{/* enable GPIO clock */rcu_periph_clock_enable(usartHardConfig[comX].periph_RCU_Gpio);/* enable USART clock */rcu_periph_clock_enable(usartHardConfig[comX].periph_RCU_uart);/* connect port to USARTx_Tx */gpio_af_set(usartHardConfig[comX].gpio_periph,usartHardConfig[comX].alt_func_num,usartHardConfig[comX].tx_pin);/* configure USART Tx as alternate function push-pull */gpio_mode_set(usartHardConfig[comX].gpio_periph,GPIO_MODE_AF,GPIO_PUPD_PULLUP,usartHardConfig[comX].tx_pin);gpio_output_options_set(usartHardConfig[comX].gpio_periph,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,usartHardConfig[comX].tx_pin);/* connect port to USARTx_Rx */gpio_af_set(usartHardConfig[comX].gpio_periph, usartHardConfig[comX].alt_func_num, usartHardConfig[comX].rx_pin);/* configure USART Rx as alternate function push-pull */gpio_mode_set(usartHardConfig[comX].gpio_periph, GPIO_MODE_AF, GPIO_PUPD_PULLUP,usartHardConfig[comX].rx_pin);gpio_output_options_set(usartHardConfig[comX].gpio_periph, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,usartHardConfig[comX].rx_pin);/* USART configure */usart_deinit(usartHardConfig[comX].usart_periph);usart_baudrate_set(usartHardConfig[comX].usart_periph,pSerialDrv->pObj[comX]->param.baudRate);if   (pSerialDrv->pObj[comX]->param.wordLength==8)usart_word_length_set(usartHardConfig[comX].usart_periph, USART_WL_8BIT);elseusart_word_length_set(usartHardConfig[comX].usart_periph, USART_WL_9BIT);if   (pSerialDrv->pObj[comX]->param.stopBits==UART_STOPBIT_0P5)usart_stop_bit_set(usartHardConfig[comX].usart_periph, USART_STB_0_5BIT);else if  (pSerialDrv->pObj[comX]->param.stopBits==UART_STOPBIT_1)usart_stop_bit_set(usartHardConfig[comX].usart_periph, USART_STB_1BIT);else if  (pSerialDrv->pObj[comX]->param.stopBits==UART_STOPBIT_2)usart_stop_bit_set(usartHardConfig[comX].usart_periph, USART_STB_2BIT);elseusart_stop_bit_set(usartHardConfig[comX].usart_periph, USART_STB_1_5BIT);if  (pSerialDrv->pObj[comX]->param.parity==UART_PARITY_NONE)usart_parity_config(usartHardConfig[comX].usart_periph, USART_PM_NONE);else if  (pSerialDrv->pObj[comX]->param.parity==UART_PARITY_ODD)usart_parity_config(usartHardConfig[comX].usart_periph, USART_PM_ODD);else//if (pSerialDrv->pObj[comX]->param.parity==UART_PARITY_OVEN)usart_parity_config(usartHardConfig[comX].usart_periph, USART_PM_EVEN);usart_hardware_flow_rts_config(usartHardConfig[comX].usart_periph, USART_RTS_DISABLE);usart_hardware_flow_cts_config(usartHardConfig[comX].usart_periph, USART_CTS_DISABLE);//串口收发允许/串口中断允许/串口允许usart_receive_config(usartHardConfig[comX].usart_periph,USART_RECEIVE_ENABLE);usart_transmit_config(usartHardConfig[comX].usart_periph,USART_TRANSMIT_ENABLE);nvic_irq_enable(usartHardConfig[comX].usart_nvic_irq, 3, 0);usart_enable(usartHardConfig[comX].usart_periph);//串口中断:发送完成/接收/空闲禁止usart_interrupt_disable(usartHardConfig[comX].usart_periph, USART_INT_TC);usart_interrupt_disable(usartHardConfig[comX].usart_periph, USART_INT_RBNE);usart_interrupt_disable(usartHardConfig[comX].usart_periph, USART_INT_IDLE);
}//串口驱动操作系统资源初始化
STATIC void Serial_ParamInial(tComPort  comX,tSerialRecNoticeFunc RecNoticeFunc)
{//pSerialDrv->pObj[comX]->tx.msg=xQueueCreate(usartQueueDepth[comX][TX],sizeof(tMessage));pSerialDrv->pObj[comX]->tx.mutex=xSemaphoreCreateMutex();pSerialDrv->pObj[comX]->tx.busy=FALSE;pSerialDrv->pObj[comX]->tx.sendLengthMax=usartBuffMax[comX][TX];//pSerialDrv->pObj[comX]->rx.receiveFull=FALSE;pSerialDrv->pObj[comX]->rx.RecNotice=RecNoticeFunc;pSerialDrv->pObj[comX]->rx.msgIsr=xQueueCreate(usartQueueDepth[comX][RX],4);pSerialDrv->pObj[comX]->rx.msgApp=xQueueCreate(usartQueueDepth[comX][RX],4);pSerialDrv->pObj[comX]->rx.mutex=xSemaphoreCreateMutex();pSerialDrv->pObj[comX]->rx.get.pBuff=((uint8_t*)pSerialDrv->pObj[comX])+sizeof(tSerialObj);pSerialDrv->pObj[comX]->rx.get.length=usartBuffMax[comX][RX];pSerialDrv->pObj[comX]->rx.offset.read=0;pSerialDrv->pObj[comX]->rx.offset.write=0;//pSerialDrv->pObj[comX]->optMode=usartOptMode[comX];
}//串口发送启动
STATIC void Serial_SendStart(tComPort   comX)
{if (pSerialDrv->pObj[comX]->optMode.sendUseDma==TRUE){//串口使用DMA发送dma_interrupt_flag_clear(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_INTF_FTFIF);Serial_UsartDmaTransmit(comX);}else{//串口使用中断发送usart_interrupt_flag_clear(usartHardConfig[comX].usart_periph,USART_INT_FLAG_TC);Serial_UsartInterrupteTransmit(comX);}pSerialDrv->pObj[comX]->tx.busy=TRUE;
}//串口接收处理
STATIC void Serial_RxProcess(EventBits_t    eventGroupValue)
{tComPort       comX;tSerialRecInfo serialRecInfo;for   (comX=COM0;comX<COM_MAX;comX++){if    (pSerialDrv->pObj[comX]!=NULL&&(eventGroupValue&COMX_EVENT_RX(comX))){//在应用层接收队列满时未保留msgIsr队列数据,因为从理论上来讲,其数据有被覆盖的可能(所以宁可丢弃也不能出错)while (uxQueueMessagesWaiting(pSerialDrv->pObj[comX]->rx.msgIsr)>0){//因在执行此模块时,串口空闲中断发送的队列有可能存在多个,因此需要都取出serialRecInfo.address=osQueueUint32DataTake(pSerialDrv->pObj[comX]->rx.msgIsr,pdMS_TO_TICKS(100));if (serialRecInfo.address==NULL||serialRecInfo.item.len>pSerialDrv->pObj[comX]->rx.get.length||serialRecInfo.item.offset>(pSerialDrv->pObj[comX]->rx.get.length-1))continue;if (pSerialDrv->pObj[comX]->rx.receiveFull==FALSE){tSerialBuff     *pSerialRx;//从消息中取出数据在缓冲区起始偏移量和数据长度,然后拷贝出数据if    (uxQueueSpacesAvailable(pSerialDrv->pObj[comX]->rx.msgApp)>0){pSerialRx=(tSerialBuff*)pvPortMalloc(serialRecInfo.item.len+sizeof(tSerialBuff)+1);//增加一个字节目的是可直接作为字符串使用if    (pSerialRx==NULL)return;  pSerialRx->pBuff=((uint8_t*)pSerialRx)+sizeof(tSerialBuff);if  ((serialRecInfo.item.offset+serialRecInfo.item.len)<pSerialDrv->pObj[comX]->rx.get.length)memcpy(pSerialRx->pBuff,&pSerialDrv->pObj[comX]->rx.get.pBuff[serialRecInfo.item.offset],serialRecInfo.item.len);else{uint16_t len;len=pSerialDrv->pObj[comX]->rx.get.length-serialRecInfo.item.offset;memcpy(pSerialRx->pBuff,&pSerialDrv->pObj[comX]->rx.get.pBuff[serialRecInfo.item.offset],len);memcpy(&pSerialRx->pBuff[len],pSerialDrv->pObj[comX]->rx.get.pBuff,serialRecInfo.item.len-len);}pSerialRx->pBuff[serialRecInfo.item.len]=0;pSerialRx->length=serialRecInfo.item.len;osQueueUint32DataSend(pSerialDrv->pObj[comX]->rx.msgApp,(uint32_t)pSerialRx,pdMS_TO_TICKS(100));if (pSerialDrv->pObj[comX]->rx.RecNotice!=NULL&&uxQueueMessagesWaiting(pSerialDrv->pObj[comX]->rx.msgApp)==1){if    (pSerialDrv->pObj[comX]->rx.RecNotice()==FALSE)vPortFree(pSerialRx);}}elsepSerialDrv->pObj[comX]->rx.receiveFull=TRUE;}}}}
}//串口读取完成处理
STATIC void Serial_RdFinishProcess(EventBits_t  eventGroupValue)
{tComPort       comX;for    (comX=COM0;comX<COM_MAX;comX++){if    (pSerialDrv->pObj[comX]!=NULL&&(eventGroupValue&COMX_EVENT_RD_FINISH(comX))){if (pSerialDrv->pObj[comX]->rx.receiveFull==TRUE)pSerialDrv->pObj[comX]->rx.receiveFull=FALSE;if    (pSerialDrv->pObj[comX]->rx.RecNotice!=NULL&&uxQueueMessagesWaiting(pSerialDrv->pObj[comX]->rx.msgApp)>0)pSerialDrv->pObj[comX]->rx.RecNotice();}}
}//串口接收链表内存释放
STATIC void Serial_RxListFree(tComPort comX)
{uint32_t   address;while   (uxQueueMessagesWaiting(pSerialDrv->pObj[comX]->rx.msgApp)>0){address=osQueueUint32DataTake(pSerialDrv->pObj[comX]->rx.msgApp,100);if (address==0)break;vPortFree((void*)address);}
}//串口发送链表内存释放
STATIC void Serial_TxListFree(tComPort comX)
{tMessage   message;while (uxQueueMessagesWaiting(pSerialDrv->pObj[comX]->tx.msg)>0){message=osMessageGet(pSerialDrv->pObj[comX]->tx.msg,pdMS_TO_TICKS(100));   if (message.address==0)break;vPortFree((void*)message.address);}
}//串口驱动收发链表内存释放
STATIC void Serial_TRxListFree(tComPort comX)
{osMutexWait(pSerialDrv->optMutex,portMAX_DELAY);Serial_TxListFree(comX);osMutexRelease(pSerialDrv->optMutex);osMutexWait(pSerialDrv->pObj[comX]->rx.mutex, portMAX_DELAY);Serial_RxListFree(comX);osMutexRelease(pSerialDrv->pObj[comX]->rx.mutex);
}//从消息中获取发送数据
STATIC void Serial_GetTxDataFromMessageToStart(tComPort comX)
{tMessage   message;tSerialBuff *pSerialBuff;message=osMessageGet(pSerialDrv->pObj[comX]->tx.msg,100);if (message.address!=0){if    (message.event==0)pSerialDrv->pObj[comX]->tx.pSend=(tSerialBuff*)message.address;else{pSerialBuff=(tSerialBuff*)pvPortMalloc(sizeof(tSerialBuff));pSerialBuff->pBuff=(uint8_t*)message.address;pSerialBuff->length=message.event;pSerialDrv->pObj[comX]->tx.pSend=pSerialBuff;}Serial_SendStart(comX);}
}//串口发送启动处理
STATIC void Serial_TxStartProcess(EventBits_t   eventGroupValue)
{tComPort   comX;for    (comX=COM0;comX<COM_MAX;comX++){if    (pSerialDrv->pObj[comX]!=NULL&&(eventGroupValue&COMX_EVENT_TX_START(comX))){if  (pSerialDrv->pObj[comX]->tx.busy==FALSE)Serial_GetTxDataFromMessageToStart(comX);}}
}//串口发送完成处理
STATIC void Serial_TxFinishProcess(EventBits_t  eventGroupValue)
{tComPort   comX;for (comX=COM0;comX<COM_MAX;comX++){if   (pSerialDrv->pObj[comX]&&(eventGroupValue&COMX_EVENT_TX_FINISH(comX))){vPortFree(pSerialDrv->pObj[comX]->tx.pSend);//释放发送内存pSerialDrv->pObj[comX]->tx.busy=FALSE;//检查等待发送队列if   (uxQueueMessagesWaiting(pSerialDrv->pObj[comX]->tx.msg)>0)Serial_GetTxDataFromMessageToStart(comX);if  (pSerialDrv->pObj[comX]->tx.busy==FALSE){pSerialDrv->pObj[comX]->tx.pSend=NULL;#if   USE_VIRTUAL_COM==1if  (comX!=VIR_COM)#endif{//禁止中断if (pSerialDrv->pObj[comX]->optMode.sendUseDma==TRUE){usart_dma_transmit_config(usartHardConfig[comX].usart_periph,USART_DENT_DISABLE);dma_interrupt_disable(usartDmaConfig[comX][TX].dma_periph,usartDmaConfig[comX][TX].dma_channel,DMA_CHXCTL_FTFIE);nvic_irq_disable(usartDmaConfig[comX][TX].dma_nvic_irq);}elseusart_interrupt_disable(usartHardConfig[comX].usart_periph, USART_INT_TC);}}}}
}//串口驱动任务
STATIC void Serial_Task(void *pvParameters)
{EventBits_t eventGroupValue;for (;;){eventGroupValue=xEventGroupWaitBits(pSerialDrv->eventGroupHandle,COM_EVENTGROUP,pdTRUE,pdFAIL,portMAX_DELAY);osMutexWait(pSerialDrv->optMutex,portMAX_DELAY);if    (eventGroupValue&COM_EVENTGROUP_RX)Serial_RxProcess(eventGroupValue);if (eventGroupValue&COM_EVENTGROUP_RD)Serial_RdFinishProcess(eventGroupValue);if (eventGroupValue&COM_EVENTGROUP_TX_FINISH)Serial_TxFinishProcess(eventGroupValue);if (eventGroupValue&COM_EVENTGROUP_TX_START)Serial_TxStartProcess(eventGroupValue);osMutexRelease(pSerialDrv->optMutex);}
}
//========================================================================================
//串口操作接口函数定义
STATIC BOOL Serial_Open(tComPort comX,tSerialUartParam* pParam,tSerialRecNoticeFunc RecNoticeFunc)
{if (pSerialDrv!=NULL&&pSerialDrv->pObj[comX]==NULL&&inHandlerMode()==0){pSerialDrv->pObj[comX]=(tSerialObj*)pvPortMalloc(sizeof(tSerialObj)+usartBuffMax[comX][RX]);if    (pSerialDrv->pObj[comX]==NULL)return   FALSE;pSerialDrv->pObj[comX]->param=*pParam;Serial_ParamInial(comX,RecNoticeFunc);Serial_UsartXInit(comX);Serial_ReceiveStart(comX);Serial_Ensable(comX);return  TRUE;}return    FALSE;
}STATIC BOOL Serial_Close(tComPort comX)
{//禁止串口操作(暂时没有处理)Serial_Disable(comX);//串口硬件去初始化Serial_UsartXDeInit(comX);//串口收发队列及链表释放Serial_TRxListFree(comX);//串口使用的操作系统资源释放Serial_ParamDeInial(comX);//串口内存资源释放vPortFree(pSerialDrv->pObj[comX]);pSerialDrv->pObj[comX]=NULL;return    TRUE;
}STATIC BOOL Serial_Ctrl(tComPort comX,tSerialCtrl *pCtrl)
{switch (pCtrl->cmd){//串口参数修改case    SERIAL_CTRL_UARTPARAM:Serial_ParamSetup(comX);break;//串口放弃操作case    SERIAL_CTRL_ABORT:Serial_TRxListFree(comX);break;}return    TRUE;
}STATIC BOOL Serial_Read(tComPort comX,tSerialBuff **pSerialRx,tReadMode readMode,TickType_t xTicksToWait)
{//应用层读取到串口数据,在处理完成后需要释放pSerialRx指向的内存TickType_t delayTime;uint32_t  address;if  (pSerialDrv!=NULL&&pSerialDrv->pObj[comX]!=NULL){if    (readMode==READ_ONLYONCE)delayTime=1;else if (readMode==READ_SETTIME)delayTime=xTicksToWait;else if   (readMode==READ_WAITING)delayTime=portMAX_DELAY;elsereturn   FALSE;osMutexWait(pSerialDrv->pObj[comX]->rx.mutex, portMAX_DELAY);address=osQueueUint32DataTake(pSerialDrv->pObj[comX]->rx.msgApp,delayTime);if   (address!=NULL){*pSerialRx=(tSerialBuff *)address;osEventGroupSet(pSerialDrv->eventGroupHandle,COMX_EVENT_RD_FINISH(comX));osMutexRelease(pSerialDrv->pObj[comX]->rx.mutex);return   TRUE;}elsepSerialRx=NULL;osMutexRelease(pSerialDrv->pObj[comX]->rx.mutex);}return    FALSE;
}STATIC BOOL Serial_Print(tComPort comX,uint8_t *pData,uint16_t length)
{//此功能在中断中调用时,不会拷贝数据tMessage message;tSerialBuff *pSerialTx;if   (pSerialDrv!=NULL&&pSerialDrv->pObj[comX]!=NULL&&pData!=NULL&&length>0){if (uxQueueSpacesAvailable(pSerialDrv->pObj[comX]->tx.msg)<=1)return FALSE;osMutexWait(pSerialDrv->pObj[comX]->tx.mutex, portMAX_DELAY);if (inHandlerMode()==0){pSerialTx=(tSerialBuff*)pvPortMalloc(sizeof(tSerialBuff)+length);if    (pSerialTx==NULL){osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return FALSE;}pSerialTx->pBuff=((uint8_t*)pSerialTx)+sizeof(tSerialBuff);memcpy(pSerialTx->pBuff,pData,length);pSerialTx->length=length;message.event=0;message.address=(uint32_t)pSerialTx;if (osMessagePut(pSerialDrv->pObj[comX]->tx.msg,message,100)==FALSE){vPortFree(pSerialTx);osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return    FALSE;}}else{//在中断内严格来讲,不能调用pvPortMalloc申请内存,因此设计了参数传递,同时借用message.event传递数据长度message.event=length;message.address=(uint32_t)pData;if   (osMessagePut(pSerialDrv->pObj[comX]->tx.msg,message,100)==FALSE){osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return FALSE;}}osEventGroupSet(pSerialDrv->eventGroupHandle,COMX_EVENT_TX_START(comX));osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return    TRUE;}return    FALSE;
}STATIC BOOL Serial_PrintAscii(tComPort comX,char* pTitle,uint8_t *pData,uint16_t length,BOOL retSymbol)
{//此功能禁止在中断中使用tMessage  message;tSerialBuff *pSerialTx;uint16_t titleLen,retLen,totalLength,asciiLength,loop;if (pSerialDrv!=NULL&&pSerialDrv->pObj[comX]!=NULL&&pData!=NULL&&length>0&&inHandlerMode()==0){//计算ASCII码输出数据长度、标题长度、回车换行符(是否输出)、总数据长度if    (uxQueueSpacesAvailable(pSerialDrv->pObj[comX]->tx.msg)<=1)return FALSE;osMutexWait((osMutexId)pSerialDrv->pObj[comX]->tx.mutex, portMAX_DELAY);asciiLength=length*3;titleLen=pTitle!=NULL ? strlen(pTitle) : 0;retLen=retSymbol==TRUE ? 2 : 0;totalLength=titleLen+retLen+asciiLength;//检查输出数据长度是否超出缓存if  ((totalLength+sizeof(tSerialBuff))>pSerialDrv->pObj[comX]->tx.sendLengthMax){totalLength=pSerialDrv->pObj[comX]->tx.sendLengthMax-sizeof(tSerialBuff);length=(totalLength-titleLen-retLen)/3;asciiLength=length*3;}//申请内存pSerialTx=(tSerialBuff*)pvPortMalloc(totalLength+sizeof(tSerialBuff));if  (pSerialTx==NULL){//osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return FALSE;}pSerialTx->pBuff=((uint8_t*)pSerialTx)+sizeof(tSerialBuff);pSerialTx->length=totalLength;//复制标题if   (titleLen>0)memcpy(pSerialTx->pBuff,pTitle,titleLen);//生成ASCII码字符for  (loop=0;loop<length;loop++){pSerialTx->pBuff[titleLen+loop*3]=GetAsciiCodeFromUint8(pData[loop]/0x10);pSerialTx->pBuff[titleLen+loop*3+1]=GetAsciiCodeFromUint8(pData[loop]%0x10);pSerialTx->pBuff[titleLen+loop*3+2]=' ';}//添加回车换行符if (retLen){pSerialTx->pBuff[titleLen+asciiLength]='\r';pSerialTx->pBuff[titleLen+asciiLength+1]='\n';}message.event=0;message.address=(uint32_t)pSerialTx;//输出到串口if  (osMessagePut(pSerialDrv->pObj[comX]->tx.msg,message,100)==FALSE){//osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return   FALSE;}osEventGroupSet(pSerialDrv->eventGroupHandle,COMX_EVENT_TX_START(comX));osMutexRelease(pSerialDrv->pObj[comX]->tx.mutex);return TRUE;}return    FALSE;
}STATIC BOOL Serial_PrintStr(tComPort comX,char *fmt,...)
{//此功能原则上不在中断中使用BOOL    result=FALSE;va_list ap;char   *pStr;int   length;if   (pSerialDrv!=NULL&&pSerialDrv->pObj[comX]!=NULL){if    (uxQueueSpacesAvailable(pSerialDrv->pObj[comX]->tx.msg)<=1)return FALSE;if    (inHandlerMode()==0){pStr=(char*)pvPortMalloc(pSerialDrv->pObj[comX]->tx.sendLengthMax);if (pStr==NULL)return    FALSE;va_start(ap,fmt);vsnprintf(pStr,pSerialDrv->pObj[comX]->tx.sendLengthMax,fmt,ap);va_end(ap);length=strlen(pStr);if (length>0)result=Serial_Print(comX,(uint8_t*)pStr,length,TRUE);vPortFree(pStr);}elseresult=Serial_Print(comX,(uint8_t*)fmt,strlen(fmt),FALSE);}return  result;
}
//========================================================================================
//串口驱动操作接口定义
const tSerialHandle SerialHandle=
{Serial_Open,Serial_Close,Serial_Ctrl,Serial_Get,Serial_Read,Serial_Print,Serial_PrintAscii,Serial_PrintStr,
};//串口驱动任务注册
const tSerialHandle* SerialTask_Register(void)
{if (pSerialDrv==NULL){//申请驱动任务内存资源pSerialDrv=(tSerialDriver*)pvPortMalloc(sizeof(tSerialDriver));if (pSerialDrv!=NULL){memset((uint8_t*)pSerialDrv,0,sizeof(tSerialDriver));//创建驱动任务使用的FreeRTOS资源pSerialDrv->optMutex=xSemaphoreCreateMutex();pSerialDrv->eventGroupHandle=xEventGroupCreate();#if USE_VIRTUAL_COM==1pSerialDrv->virtualCom.pTRx=NULL;#endif//创建驱动任务xTaskCreate(Serial_Task,"Serial",Serial_TaskStackDepth,NULL,Serial_TaskPriority,NULL);return   &SerialHandle;}}return  NULL;
}

3.驱动使用示列

  这里仅仅给一个驱动使用的示列片段,不再像之前的代码一样给出一个完整示列文件。

const tSerialHandle*     pCom=NULL;         //串口驱动功能接口句柄/*
其它代码
...
*/void System(void)
{//...pCom=SerialTask_Register();      //注册串口任务DebugTask_Register();           //注册调试任务/*{...//这里打开了调试串口,并传递了串口参数与接收通知回调函数pCom->Open(DBG_COM,(tSerialUartParam*)&DebugUartParamDefault,Debug_UartReceiveDataNotice);...*/pCom->PrintStr(DBG_COM,"\r\nprompt>初始化开始...\r\n");//...
}

4.驱动中部分说明

因上面的驱动及应用部分难免会涉及到一些没有列出的数据结构或其它说明,这部分章节仅根据大家的要求,逐一列出。

4.1 部分关联的数据结构

tMessage消息数据结构,其中event成员表示不同的事件ID,address成员

typedef struct
{uint16_t               event;              //事件 (表示不同的事件,或可以称为事件ID)uint32_t               address;            //消息地址(与事件相关的数据指针,即事件需要传递的数据地址)
}tMessage;//消息结构类型

如果你感兴趣,可查看其它文档.
单片机软件常用设计分享(一)驱动设计之按键设计
单片机软件常用设计分享(二)驱动设计之LED灯显示设计
单片机软件常用设计分析(三)驱动设计之数码显示设计
嵌入式开发<串口调试工具>
嵌入式开发<网络调试工具>
嵌入式开发<单片机软件调试>
嵌入式开发<单片机软件升级>

单片机软件常用设计分享(四)驱动设计之串口驱动设计相关推荐

  1. 单片机软件常用设计分享(二)驱动设计之LED灯显示设计

    单片机软件常用设计分享(二)驱动设计之LED灯显示设计 前言 <驱动设计–LED灯显示驱动> 一.LED灯工作方式 1.常亮 2.常灭 3.闪烁 4.间歇性闪烁 二.LED灯驱动数据结构 ...

  2. linux 串口驱动 理解,linux 串口驱动 理解

    linux 串口 驱动 理解 一.核心数据结构 串口驱动有3个核心数据结构,它们都定义在 1.uart_driver uart_driver包含了串口设备名.串口驱动名.主次设备号.串口控制台(可选) ...

  3. linux串口驱动机制,Linux串口驱动移植的一些心得总结

    串口驱动的源文件一般是使用drivers/serial/8250.c文件,或该文件的稍作修改.这是因为大多的串口接口的操作寄存器都是符合相关的定义,都是基本一样的.那么在移植串口驱动时,一般是为该驱动 ...

  4. 视频分析服务器系统架构,视频管理软件技术分析报告(四)--基于SOA的VMS软件架构设计...

    设备管理服务(包含设备IO服务):是ONVIF中定义的核心服务,对设备进行设备参数,设备状态等信息的管理和配置.通过设备管理服务能够获取其它服务的地址. 媒体服务:提供对媒体设备相关元数据(视频源.视 ...

  5. 响应式Web设计(四):响应式Web设计的优化

    这篇文章主要说说在进行响应式Web设计的过程中,涉及到页面的一些需要进行优化的地方: 1.  轻量级的Javascript库: 针对PC端网页当然会首选jQuery来作为前端javascript库,但 ...

  6. 嵌入式开发<单片机软件调试>

    嵌入式开发<单片机软件调试> 前言 一.交互式调试设计依赖工具 二.交互式调试设计的使用方法 1.普通方法 2.定制方法 三.交互式调试产品设计 1, 全局需要使用的宏定义 2,设计一个功 ...

  7. 嵌入式开发<单片机软件升级>

    嵌入式开发<单片机软件升级> 前言 一.单片机软件升级方式 二.IAP升级原理 1.FLASH区域划分 2. FLASH各个区域作用 三.IAP软件BOOT设计 1. 第一种设计方法 2. ...

  8. 优漫动游:有人说UI设计培训四个月骗局,是真的吗?

      在当前数字化时代,UI设计行业变得越来越受欢迎.越来越多的人开始对这个职业感兴趣,并参加相应的培训课程以提高自己的技能水平.然而,近期有关于UI设计培训四个月的负面评论,称其为骗局.那么,有人说U ...

  9. 微服务设计的四个原则

    本文记录下微服务设计的四个原则 微服务的设计原则 AKF原则   业界对于可扩展的系统架构设计有一个朴素的理念,就是:通过加机器就可以解决容量和可用性问题.(如果一台不行那就两台).(世界上没有什么事 ...

  10. 响应式Web设计(一):响应式Web设计的背景

    2019独角兽企业重金招聘Python工程师标准>>> 不是专业前端,可2011年底至今大部分时间在做着一个前端开发工程师的事情,所以多少也有点总结,多少也有点"经验&qu ...

最新文章

  1. SpringDataJpa框架单元测试实现增删改查
  2. 【学习笔记】Python 基础零碎知识
  3. 华为路由器配置默认路由为什么配偶的下一跳不可达也可以_静态路由特性
  4. DoTween NGUI bug
  5. (02)System Verilog 程序块结束仿真
  6. html 段落定位,使用HTML :: TreeBuilder在perl中使用段落定位div
  7. 经典算法排序——插入排序
  8. 让Kubernetes成为数据中心操作系统(DCOS)的一等公民
  9. 自学python-自学Python要学多久可以学会?老男孩Python培训
  10. 华泰证券高薪诚聘 技术大牛/运维平台架构师
  11. xapp1025仿真
  12. newifi路由器 php,newifi路由器有线桥接教程
  13. win10引导安卓x86_windows10开启VT和Hyper-V无法安装模拟器的另一种x86 Android解决办法...
  14. Coding and Paper Letter(八十七)
  15. java实现zigzag扫描
  16. Hdu1208 Pascal's Travels
  17. 搜狐邮箱怎样开启imap服务器,搜狐企业邮箱iPhone如何设置?
  18. oracle 指定关联,Oracle巧取指定记录以及巧用外关联查询
  19. windows下python利用f2py调用Fortran
  20. [Python] 实现文本进度条

热门文章

  1. TOEFL备考2.23
  2. 网络安全进校园 | 走进诸暨学勉中学
  3. PaperNotes(21)-Safety-compliant Generative Adversarial Networks for Human Trajectory Forecasting
  4. 基于ARM-Linux的点菜系统
  5. eclipse中jsp/html文件 自动排版、字体大小
  6. java火柴课设报告摘要,java火柴游戏课程设计报告
  7. 把多线程当成一个人,你瞬间就能明白它的原理
  8. 爬虫之旅(四):爬取豆瓣喜剧排行榜数据
  9. 题解 | #查找两个字符串a,b中的最长公共子串# 动态规划
  10. 09 Redis的切片集群