jicheng0622

【技术分享】【原创】简单实现KL26串口UART的DMA方式发送数据

0
阅读(7486)

    虽然好些时日没有更新博客了,但是手感还不错,上篇是一番激情感慨的文艺范,这篇还是搞点实料的好,不然该有人怀疑我开始走文艺小清新路线了,那可不好了,吃饭的家伙可不能丢啊,呵呵,下面直接进入正题。

    以DMA方式通过UART发送数据应该是工程应用中很常用的一种方式了,尤其是在需要频繁发送数据或者数据包长度较大的场合,如果使用传统的UART查询或者中断方式发送和接收数据,对CPU资源的占用将是极大的浪费,带操作系统的应用还好些,如果是纯粹的前后台程序有时不能容忍的,所以DMA方式是很恰当的选择。关于DMA原理和细节的一些东西我在很早之前曾经写过一篇《从零入手Kinetis系统开发(十)之eDMA模块》专门介绍过,不过是针对Kinetis K系列的eDMA的,而本篇的主角则是最近火热起来的Cortex-M0+内核Kinetis L系列,不同于K系列复杂强大的eDMA功能,L系列的DMA模块配置起来还是比较简单的,原理性的东西我就不多做介绍了,建议去看一下L系列RM(这里再提一下,毕竟绝大多数的半导体厂商是欧美的,所以想靠中文去研究透一款芯片是不太现实的,咳咳,我很爱国),这里就直接上代码了:

测试平台:IAR6.7 + KL26 FRDM

测试代码:FRDM-KL26Z_SC\FRDM-KL26Z_SC_Rev_1.0\klxx-sc-baremetal\build\iar\uart0_dma

    其实KL26的官方代码中是自带uart0_dma例程的,但是实现的功能只是将UART口接收到的每一个字节的数据通过DMA方式再发送出去(即环形缓冲),这样用来作为功能演示可以,但是往往我们需要的是将某缓冲区的数据以DMA方式发送出去或者将接收到的数据以DMA方式写到某缓冲区这样的功能,为此我们就需要在原有的例程上进行修改从而达到我们的应用目的,这里给出几点需要修改的地方,并做了相关注释:

1)定义待发送缓冲区:

/* array to be sended */ 
uint8 testdata[]={"\nFreescale Kinetis KL26\n"};

2)设置DMA源地址:

#define DMA0_DESTINATION  0x4006A007    /* the memory adress of UART0_D register */        
#define DMA0_SOURCE_ADDR  (uint32)testdata    /* define the source data array address */ 

3) 在DMA0_init()函数中修改发送数据包的长度:

DMA_SAR0 = DMA0_SOURCE_ADDR;    //Set source address to UART0_D REG
DMA_DSR_BCR0 = DMA_DSR_BCR_BCR(sizeof(testdata));    //Set BCR to know how many bytes to transfer
DMA_DCR0 &= ~(DMA_DCR_SSIZE_MASK | DMA_DCR_DSIZE_MASK);    //Clear source size and destination size fields

4)添加源地址自动加1功能,因为之前的环形缓冲方式只是单字节数据,所以不需要源地址递增,但是由于我们这次需要发送整个数据包,所以这里我们就需要将源地址递增功能打开,而具体递增1,2还是4则取决于发送数据的最小单位(8bit,16bit or 32bit):

/* Set DMA as follows: 
       Source size is 8-bit size 
       Destination size is 8-bit size 
       Cycle steal mode 
       External requests are enabled 
       source address increments 1 automatically 
*/
DMA_DCR0 |= (DMA_DCR_SSIZE(1) 
             | DMA_DCR_DSIZE(1) 
             | DMA_DCR_CS_MASK  
             | DMA_DCR_ERQ_MASK 
             | DMA_DCR_EINT_MASK 
             | DMA_DCR_SINC_MASK);

5)配置DMAMUX通道为UART0 TX即发送通道,因为我们需要的是UART0_TX触发DMA传送:

DMA_DAR0 = DMA0_DESTINATION;    //Set source address to UART0_D REG
DMAMUX0_CHCFG0 = DMAMUX_CHCFG_SOURCE(3);    //Select UART0 TX as channel source
DMAMUX0_CHCFG0 |= DMAMUX_CHCFG_ENBL_MASK;    //Enable the DMA MUX channel

6)在UART0_DMA_init()函数中修改UART0发送缓冲区为空时即触发DMA发送:

void UART0_DMA_init(void){ 
  
  UART0_C2 &= ~(UART0_C2_TE_MASK | UART0_C2_RE_MASK);   //Disable UART0 
  
  UART0_C5 |= UART0_C5_TDMAE_MASK;                      // Turn on DMA request(Transmit) for UART0 
  
  UART0_C2 |= (UART0_C2_TE_MASK | UART0_C2_RE_MASK);   //Enable UART0 
}

7)在DMA发送完成中断服务函数中禁掉DMA通道,实现单次发送,即每个数据包发送完成之后即停止发送,否则不禁掉的话会一直触发DMA发送,造成串口堵塞:

void DMA0_IRQHandler(void){ 
  
  /* Create pointer & variable for reading DMA_DSR register */ 
  volatile uint32_t* dma_dsr_bcr0_reg = &DMA_DSR_BCR0; 
  uint32_t dma_dsr_bcr0_val = *dma_dsr_bcr0_reg; 
  
  if (((dma_dsr_bcr0_val & DMA_DSR_BCR_DONE_MASK) == DMA_DSR_BCR_DONE_MASK) 
      | ((dma_dsr_bcr0_val & DMA_DSR_BCR_BES_MASK) == DMA_DSR_BCR_BES_MASK) 
      | ((dma_dsr_bcr0_val & DMA_DSR_BCR_BED_MASK) == DMA_DSR_BCR_BED_MASK) 
        | ((dma_dsr_bcr0_val & DMA_DSR_BCR_CE_MASK) == DMA_DSR_BCR_CE_MASK)){ 
     
          
    DMA_DSR_BCR0 |= DMA_DSR_BCR_DONE_MASK;                //Clear Done bit 
    DMA_DSR_BCR0 = DMA_DSR_BCR_BCR(sizeof(testdata));      //Reset BCR 
    dma0_done = 1; 
  }  
  /* once the array complete the transfer, then disable the DMA channel.*/ 
  DMAMUX0_CHCFG0 &= ~DMAMUX_CHCFG_ENBL_MASK; 
}

    将上述代码做完相应修改即可实现单次将内存缓冲区数据以DMA方式通过UART0发送出去,效果如下。此外,如果想周期性触发或者条件性触发,则只需再相应位置添加“DMAMUX0_CHCFG0 |= DMAMUX_CHCFG_ENBL_MASK;”这句代码即可打开通道,然后立即会触发UART0_TX发送数据,然后待数据包发送完之后再次停止等待下次使能。

image

    呼呼,一激动一紧张没想到熬到这么晚,呵呵,本篇内容实际上配置很简单,但是为了解释清楚还是一步步给大家分析了一遍,如果还有不清楚的欢迎下面留言。好了,赶紧洗洗睡了,未完待续~

附件为修改后的整个工程:kl26_uart0_dma.zip