串口DMA实现接收--不定长度接收

1.DMA接收配置

1.direction :数据传输的方向是外设(串口)----->内存(DMA Buff);
2.memory_inc:内存自增,内存指DMA Buff,往Buff中搬移数据的时候,会依次往buff中搬运;
3.memory0_addr:DMA接收Buff地址;
4.periph_addr:外设地址为串口数据寄存器;
5.DMA_MEMORY_INCREASE_DISABLE:外设地址不增加
6.配置为循环模式:当DMA RxBuff存满数据时,从首地址开始存数据,不用在缓存区满的情况下手动切换缓存区,增加了程序运行效率。
7.dma_channel_enable:DMA接收使能,一开始就使能保证串口接收数据时开始的数据不会丢;

 dma_single_data_parameter_struct  DmaInitStruct;

    dma_deinit(uiDmaX, iDmaRxChan);
    DmaInitStruct.direction    = DMA_PERIPH_TO_MEMORY;                  /*  *****                       */
    DmaInitStruct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;            /*  内存自增                    */
    DmaInitStruct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;          /*  存储和外设的传输宽度        */
    DmaInitStruct.number       =  usRxBuffSize;
    DmaInitStruct.memory0_addr = (UINT32)pUartDmaChan->SIOCHAN_pcRxbuffer;
    DmaInitStruct.periph_addr  = (UINT32)&USART_DATA(iBase);
    DmaInitStruct.periph_inc   = DMA_MEMORY_INCREASE_DISABLE;           /*  外设地址固定                */
    DmaInitStruct.priority     = DMA_PRIORITY_ULTRA_HIGH;
    dma_single_data_mode_init(uiDmaX, iDmaRxChan, &DmaInitStruct);

    dma_circulation_enable(uiDmaX, iDmaRxChan);                         /*  配置为循环模式              */
    dma_channel_subperipheral_select(uiDmaX, iDmaRxChan, uiDmaRxPeri);  /*  外设选择                    */

    dma_channel_enable(uiDmaX, iDmaRxChan);                             /* enable DMA channel           */

2.DMA BUFF接收数据的过程

串口往DMA搬运数据是自动完成的:当检测到外设中有数据时,DMA就会先搬运数据到DMA RX Buff, 这个过程不消耗CPU,并且速度快;
另开启了一个线程,在线程中1ms检测一次DMA RxBuff里面的数据量,和上次的比较,就能得出此次需要传输的数据量,然后将数据全部复制到RxRingBuff,;因为DMA buff在配置时配置的是内存自增模式,所以搬运到DMA BUFF中的数据地址一直是增加的,且DMA BUFF是循环的;
为什么另外开启一个线程:因为等待信号的过程是一个死等的过程,如果不开启一个线程,那么其他的函数将不能执行;

2.1 1ms检测一次DMA BUFF中的数据量是怎么实现的:

方法一: 使用POSIX里面的高速定时器和信号通知机制
信号通知,定时通知一个线程,线程中会调用一个函数,函数中会接收信号。

    timer_t    timerId;
    struct sigevent sigev;
    struct timespec interval;
    struct itimerspec tspec;
    int ret;
    if (uiChannl != 0 && uiChannl <= 7) {
        sprintf(pcUartName, "t_uart%dcntGet", uiChannl);
        if (!(bIsTimerCreate)) {
            API_ThreadAttrBuild(&threadattr,
                              4 * LW_CFG_KB_SIZE,
                              140,
                              LW_OPTION_THREAD_STK_CHK,
                              (VOID *)pUartDmaChan);
            pUartDmaChan->SIOCHAN_ulSioThreadId = API_ThreadCreate("t_dmaGetCnt",           //创建一个线程,线程中会执行一个函数;
                                                                     (PTHREAD_START_ROUTINE)__dmaGetCnt,
                                                                     &threadattr, LW_NULL);
            sigev.sigev_notify           = SIGEV_THREAD_ID;
            sigev.sigev_signo            = SIGUSR2;
            sigev.sigev_notify_thread_id = pUartDmaChan->SIOCHAN_ulSioThreadId;

            ret = timer_create(CLOCK_MONOTONIC, &sigev, &timerId);      /*   创建定时器,但是不开启                 */
            if (ret) {
                fprintf(stderr, "timer create failed.\n");
                return -1;
            }
            bIsTimerCreate = 1;
            /*
            * 设置定时时间
            */
            interval.tv_sec   = 0;
            interval.tv_nsec  = 1000000;
            tspec.it_interval = interval;                                        /*  定时间隔时间                      */
            tspec.it_value    = interval;
            ret = timer_settime(timerId, 0, &tspec, LW_NULL);        /*  开启定时器                       */

等待信号

 struct timespec ts1, ts2;
    sigset_t sigset;
    INT iSig;   
    if (!(pUartDmaChan->SIOCHAN_bIsSigCreate)) {       //有问题;创建一个timer,创建一个信号,
         sigemptyset(&sigset);
         sigaddset(&sigset, SIGUSR2);
         sigprocmask(SIG_BLOCK, &sigset, NULL);
         pUartDmaChan->SIOCHAN_bIsSigCreate = 1;
    }
      while(1) {
        sigwait(&sigset, &iSig);            /*  等待信号                */

        if (iSig == SIGUSR2) {                

/**********定时调用的代码  ******************************/
}

**方法二:**高速定时器 hstimerfd(); hstimerfd_settime()
创建定时器后,使用read()函数等待;
定时器创建后会生成一个描述符,read函数中就是等待这个描述符;

_G_itimerFd = hstimerfd_create(0);                          /*  创建定时器                  */
            if (_G_itimerFd == -1) {
                fprintf(stderr, "error: %s.\n", strerror(errno));
                return -1;
            }
            bIsTimerCreate = 1;
            /*
             * 我们以纳秒为单位
             */
            ts.tv_sec          = 0;
            ts.tv_nsec         = 1000000;                               /*  1ms定时调用接收线程         */
            itspec.it_interval = ts;
            itspec.it_value    = ts;
            iRet = hstimerfd_settime(_G_itimerFd, &itspec, LW_NULL);

            if (iRet) {
                fprintf(stderr, "error: %s.\n", strerror(errno));
                return  (PX_ERROR);
           }
            API_ThreadAttrBuild(&threadattr,
                                  4 * LW_CFG_KB_SIZE,
                                  249,
                                  LW_OPTION_THREAD_STK_CHK,
                                  NULL);
            _G_ulSioThreadId = API_ThreadCreate("t_dmaGetCnt",
                                               (PTHREAD_START_ROUTINE)__dmaGetCnt,
                                                &threadattr, LW_NULL);

等待

UINT64           u64Overruns = 0;
    INT              i;

    while(1) {
        read(_G_itimerFd, &u64Overruns, sizeof(u64Overruns));           /*  等待定时器计时到时          */
/*************************要执行的代码*************************************/
}

方法三:使用tick中断,发送二进制信号量;
tick是系统的滴答时钟,1ms的时候系统tick中断函数中释放二进制信号量,所以tick的响应时间可以达到1ms;线程调用的函数中等待二进制信号量;

3.串口DMA Read函数的实现

Read函数的实现
读数据其实是从RxRingBuff中获取数据到APP中的buff,只需调用ringBuffGet()函数就可以。RxRingBuff中的数据上面已经提示怎么获取了;
实现RxRingBuff中有数据才从循环BUFF中读数据怎么实现?
答:可以使用二进制信号量;
一:进入read函数的时候先阻塞,当DMA BUFF中的数据全部搬运到RxRingBuff时,并且检测到上一次RxRingBuff的数据为空,释放二进制信号量;二、检测到RxRingBuff不为空,释放二进制信号量,APP调用read时会从非空的RxRingBuff中再次获取数据;
下面是read函数的实现

** 函数名称: __sioRead
** 功能描述: 串口读数据函数
** 输 入  : pFdEntry                  打开的设备描述符
**           pcData                    接收缓存区地址
**           usLen                     接收数据长度
** 输 出  : NONE
** 返回值  : 实际写入的字节数
*********************************************************************************************************/
static ssize_t  __sioRead (PLW_FD_ENTRY  pFdEntry,
                           PCHAR         pcData,
                           size_t        stLen)
{
    __PUART_SIODMACHAN  pUartDmaChan = (__PUART_SIODMACHAN)pFdEntry->FDENTRY_pdevhdrHdr;
    INT                 iNBytes;

    if (pcData == LW_NULL) {
        _ErrorHandle(EINVAL);
        return  (PX_ERROR);
    }
    if (stLen == 0) {
        return  (0);
    }

    API_SemaphoreBPend(pUartDmaChan->SIOCHAN_hRdSyncSemB, LW_OPTION_WAIT_INFINITE);

    RDBUF_LOCK(pUartDmaChan);                                           /*  读互斥信号量                */
    iNBytes = _rngBufGet(pUartDmaChan->SIOCHAN_vxRingIdRdBuf,
                         pcData,
                         (INT)stLen);
    if (!_rngIsEmpty(pUartDmaChan->SIOCHAN_vxRingIdRdBuf)) {            /* ring buff数据未取完,下次读不等待直接取*/
        RDBUF_UNLOCK(pUartDmaChan);
        API_SemaphoreBPost(pUartDmaChan->SIOCHAN_hRdSyncSemB);
    } else {
        RDBUF_UNLOCK(pUartDmaChan);
    }

    return  ((LONG)iNBytes);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值