GD32串口DMA模式实现3M波特率--串口发送空中断和DMA发送完成中断--SylixOS系统下实现

1.DMA发送配置

1.DMA发送配置

  if (uiDmaX == DMA1) {
        rcu_periph_clock_enable(RCU_DMA1);
    } else if (uiDmaX == DMA0) {
        rcu_periph_clock_enable(RCU_DMA0);
    }
    dma_single_data_parameter_struct  DmaInitStruct;

    dma_deinit(uiDmaX, iDmaTxChan);
    DmaInitStruct.direction    = DMA_MEMORY_TO_PERIPH;                  /*  数据传输方向为内存到外设*/
    DmaInitStruct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;            /*  内存自增                    */
    DmaInitStruct.memory0_addr = (UINT32)pUartDmaChan->SIOCHAN_pcTxbuffer1;
    DmaInitStruct.periph_memory_width = DMA_MEMORY_WIDTH_8BIT;          /*  存储和外设的传输宽度        */
    DmaInitStruct.number       = 0;                                     /*  传输的过程会重新配置        */
    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, iDmaTxChan, &DmaInitStruct);
    dma_channel_subperipheral_select(uiDmaX, iDmaTxChan, uiDmaTxPeri);  /*  外设选择                    */

    dma_circulation_disable(uiDmaX, iDmaTxChan);                        /*  非循环模式 ,必须设置                     */

    if (pUartDmaChan->SIOCHAN_pcTxbuffer2 != 0) {                       /*  双BUFF时,设置第二个BUFF的地址*/
        uiDmaM1Addr = (UINT32)pUartDmaChan->SIOCHAN_pcTxbuffer2;
        DMA_CHM1ADDR(uiDmaX, iDmaTxChan) = uiDmaM1Addr;                 /*  设置存储器1基地址           */
    } else {
        UART_DEBUG("malloc tx buff2 faild!\r\n");
    }
    dma_channel_disable(uiDmaX, iDmaTxChan);                            /* disable DMA channel          */
    dma_interrupt_enable(uiDmaX, iDmaTxChan, DMA_CHXCTL_FTFIE);         /* full transfer finish interrupt enable*/

DMA发送传输的方向为:内存(DMA buff)------>外设(串口);
初始化设置失能dma发送:因为发送开启的是发送空中断,如果一开始就开启中断, 串口发送BUFF中的数据为空,串口会不断的进入中断;
初始化使能DMA完成中断:只有在DMA发送完成之后才会触发中断;
memory_inc(内存自增)设置:设置为自增使能,内存指的是DMA TxBuff,要发送的数据搬运到TxBuff时,TxBuff的内存地址自动增加,不需要用户手动一个一个地址的增加。
periph_inc(外设地址自增)设置:设置为不自增,因为外设地址是串口的DR;
循环模式:dma内存中数据满了之后,会从头开始存储数据到DMA buff。由于是DMA 发送,如果开启循环模式,会导致DMA TxBuff中的数据还没来得及传到外设,就会被新的数据覆盖(DMA会不断的搬运数据),导致数据丢失;

2.串口写成字符设备–write函数的实现

我将串口实现为一个字符设备,因为我们操作系统中的tty设备架构使数据传输速度达不到3M的波特率;
自实现一个write函数;从APP发送数据buff, 到DMA搬运到内存,再发送到外设流程如下:
WR在这里插入图片描述细节实现:为实现对WrRingBuff的操作只能是读和写其中一种, 需要自旋锁或者二进制信号量
write中使用自旋锁:自旋锁可以屏蔽中断,直接屏蔽uart中断;
二进制信号量:往WrRingBuff放数据前先阻塞,第一次放数据的时候不阻塞。有两种情况会释放信号量:①从WrRingBuff取完数据②上一次往WrRingBuff放数据时,WrRingBuff不满,这一次不阻塞直接放;
发送中断服务函数里面不能使用互斥锁,因为互斥锁在获取不到锁的情况下会睡眠,而中断是不能睡眠的;

下面是write函数的实现

static ssize_t  __sioWrite (PLW_FD_ENTRY      pFdEntry,
                            PCHAR             pcData,
                            size_t            stLen)
{
    __PUART_SIODMACHAN  pUartDmaChan = (__PUART_SIODMACHAN)pFdEntry->FDENTRY_pdevhdrHdr;
    UINT32  uiBase                   = pUartDmaChan->SIOCHAN_pBase;
    INT  iWriteLen = 0;
    REGISTER ssize_t  sstNbStart = 0;

    while (stLen > 0) {
        API_SemaphoreBPend(pUartDmaChan->SIOCHAN_hWrSyncSemB, LW_OPTION_WAIT_INFINITE);
        LW_SPIN_LOCK_IRQ(&_G_sioSpinlock, &iSioreg);
        iWriteLen = _rngBufPut(pUartDmaChan->SIOCHAN_vxRingIdWrBuf,
                               pcData,
                               (INT)stLen);
        if (!_rngIsFull(pUartDmaChan->SIOCHAN_vxRingIdWrBuf)) {         /*  Ring buff 没有满            */
            LW_SPIN_UNLOCK_IRQ(&_G_sioSpinlock, iSioreg);
            API_SemaphoreBPost(pUartDmaChan->SIOCHAN_hWrSyncSemB);      /*  释放发送信号量              */
        } else {
            LW_SPIN_UNLOCK_IRQ(&_G_sioSpinlock, iSioreg);
        }
        usart_interrupt_enable(uiBase, USART_INTEN_TBEIE);              /*  开启串口发送空中断          */

        sstNbStart += iWriteLen;
        stLen  -= (size_t)iWriteLen;
        pcData += iWriteLen;

    }

    return  (sstNbStart);
}
  

3.串口发送中断服务函数的实现

中断服务函数的实现流程放到上图了;
串口DMA发送是从DMA TxBuff —>外设(串口的DR);这个搬运过程是使能DMA后,DMA主动实现的;
在DMA搬运数据到串口之前,DMA TxBuff中需要有数据,数据是从WrRingBuff中读取的,我这里使用
_rngBufGet(pUartDmaChan->SIOCHAN_vxRingIdWrBuf,
pUartDmaChan->SIOCHAN_pcTxbuffer1,
usTxBuffSize);
将数据放到DMA 的 Txbuff1;具体的实现可以自行定义,比如可以使用复制的方式;
第二步修改DMA的配置:因为使用的是双BUFF,所以需要指定DMA TxBuff用于传输,当然是哪个BUFF中有数,指定哪个BUFF了;配置DMA要传输的个数,即往DMA buff中放了多少个数,配置为多少;
第三步:开启DMA传输,即使能DMA
dma_channel_enable(uiDmaX, iDmaTxChan);
释放二进制信号量,可以往WrRingBuff中写数据;

if ((RESET != usart_interrupt_flag_get(uiBase, USART_INT_TBEIE))
&& (RESET != usart_flag_get(uiBase, USART_FLAG_TBE))) {

    bIsUsedBuff1  = pUartDmaChan->SIOCHAN_bTxUsedBuff1;
    if (bIsUsedBuff1) {                                             /*  双buff选择                  */
        uiWriteBytes = _rngBufGet(pUartDmaChan->SIOCHAN_vxRingIdWrBuf,
                                  pUartDmaChan->SIOCHAN_pcTxbuffer1,
                                  usTxBuffSize);
    } else {
        uiWriteBytes = _rngBufGet(pUartDmaChan->SIOCHAN_vxRingIdWrBuf,
                                  pUartDmaChan->SIOCHAN_pcTxbuffer2,
                                  usTxBuffSize);
    }
    pUartDmaChan->SIOCHAN_usTxNextSent = uiWriteBytes;
    if (pUartDmaChan->SIOCHAN_usTxNextSent != 0) {                  /*  DMA BUFF中的数据量大于0     */
        dma_channel_disable(uiDmaX, iDmaTxChan);
        DMA_CHCNT(uiDmaX, iDmaTxChan) = pUartDmaChan->SIOCHAN_usTxNextSent;
        pUartDmaChan->SIOCHAN_usTxNextSent = 0;                     /* 设置要传输长度为0            */

        if (bIsUsedBuff1) {
            dmaSwitchBuffer(uiDmaX, iDmaTxChan, DMA_MEMORY_0);      /*  指定要传输的DMA buff        */
         } else {
            dmaSwitchBuffer(uiDmaX, iDmaTxChan, DMA_MEMORY_1);
        }
        pUartDmaChan->SIOCHAN_bTxUsedBuff1 = !(bIsUsedBuff1);       /*  切换DMA tx buff             */
        dma_channel_enable(uiDmaX, iDmaTxChan);
        API_SemaphoreBPost(pUartDmaChan->SIOCHAN_hWrSyncSemB);      /*  ring buff中的数取完了       */
    }
}

DMA传输完后中断服务函数:
对DMA传输完成的说明:

发送的数据方向为存储器到外设,DMA默认为传输控制器,从上图看CNT的值减为0则传输完成;CNT的值是每次开启DMA传输之前设置,每通过DMA传输一个字节,CNT的值就会减1;传输完成,则状态寄存器的传输完成标志位就会置1;输入和输出完成标志位使用的是同一个;

 

  if((RESET != dma_interrupt_flag_get(uiDmaX, iDmaTxChan, DMA_INTF_FTFIF))) {
       dma_interrupt_flag_clear(uiDmaX, iDmaTxChan, DMA_INTC_FTFIFC);
       while(usart_flag_get(uiBase, USART_FLAG_TC) == RESET);           /*  防止数据没有传完丢失        */
       usart_flag_clear(uiBase, USART_FLAG_TC);
       usart_interrupt_disable(uiBase, USART_INTEN_TBEIE);              /* 关闭在startup开启的中断      */
    }

这里重点要注意的是等待串口完成标志,因为在测试的时候发现,DMA传输数据的过程中最后一个数据会丢。数据从DMA传到串口数据寄存器,然后再发送到移位寄存器
在这里插入图片描述
数据发送到移位寄存器后,就会将TBE置1,所以数据会被覆盖;
失能串口发送空中断,是为了防止一直重复进入中断服务函数,导致APP调用write后不起作用;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值