KL26通过UART的DMA方式发送数据包(Send data package through UART with DMA mode on KL26)

Document created by Calvin Ji Employee on Jan 22, 2014Last modified by ebiz_ws_prod on Dec 13, 2017
Version 3Show Document
  • View in full screen mode

    以DMA方式通过UART发送数据应该是工程应用中很常用的一种方式了,尤其是在需要频繁发送数据或者数据包长度较大的场合,如果使用传统的UART查询或者中断方式发送和接收数据,对CPU资源的占用将是极大的浪费,带操作系统的应用还好些,如果是纯粹的前后台程序有时不能容忍的,所以DMA方式是很恰当的选择。而本篇以Kinetis L系列为例介绍一下以DMA方式通过UART端口发送长数据包,当然不同于K系列复杂强大的eDMA功能,L系列的DMA模块配置起来还是比较简单的。


测试平台:IAR6.7 + KL26 FRDM

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


       其实KL26的官方sample code中是自带uart0_dma例程的,但是实现的功能只是将UART口接收到的每一个字节的数据通过DMA方式再发送出去(即环形缓冲),这样用来作为一个功能演示的demo是可以,但是往往我们需要的是将某缓冲区的数据以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即发送通道(通道号为3),因为我们需要的是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

 

另外,关于DMA的传输速度的话,因为其独立占用一条自己的总线,其搬运时钟为系统时钟(即coreclock/Systemclock),相比于总线上的传输速度,本例程中整个数据包的发送时间主要是取决于UART串口的波特率*数据包长度。

 

 

附件为修改好后的完整工程:

Original Attachment has been moved to: kl26_uart0_dma.zip

1 person found this helpful

Attachments

    Outcomes