打印

Kinetis K系列使用DMA实现I2C读取

[复制链接]
200|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
香菇选手|  楼主 | 2018-8-23 10:22 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
以DMA为主题的**我之前写过两篇,一篇是DMA+UART,一篇是DMA+ADC,将这些外设模块加上DMA一下子功能上灵活和强大了很多(个人来讲,俺一直觉着DMA是一个高大上的东西,用好了非常方便),今儿个就继续为DMA加点料,把I2C通信这位大兄弟也拉入伙吧。前面那哥俩(UART和ADC)还好理解,可能提到I2C+DMA的话一些人不由得会脑子里有一个问号,I2C通信协议不同于UART等简单的通信,读写数据之前有比较繁琐的从机地址、寄存器地址发送,读写位控制,ACK/NACK等信号的握手,最后才是读写纯数据,这些复杂的操作如果都使用DMA去做,那么问题就来了(最近这句话貌似很火),一个是DMA能不能做,二是即使能做岂不是相当复杂和浪费很多DMA Channel资源吗。呵呵,如果我们有实际应用场景的话就会明白了,实际上使用DMA读取I2C数据只需要在读取纯数据阶段打开就行了,前面的发送从机地址、寄存器地址等操作还是使用传统的手动方式,待前面命令都发出去之后等待I2C从机发送数据流过来时再打开DMA,下次读取时重复上述步骤即可。这样虽然还是保留了前面的手动发送命令的方式,但是后面如果需要从I2C从机读取多字节数据时(单字节就不用了,呵呵)使用DMA传输的确可以释放不少CPU的loading的(CPU不需要死等数据的返回了,可以抽出空去干其他事,这个事就交给DMA去做了),最后稍稍提一句,据说我司的下一代产品的I2C模块可以把前面传统的手动方式都可以用DMA干了,小心脏扑通一下有木有,相当强大吧,哈哈,拭目以待。</p>
    那说到这,我有必要提一下这种方式的具体应用场合(考验大家发散思维的时候到了,呵呵),在常用的I2C接口IC中,MEMS的sensor是其中一员,比如我们常用的三轴加速度传感器、陀螺仪和磁感器,或者他们融合在一块的9轴Sensor,他们的X、Y、Z轴的数据一般都是存放在连续的寄存器地址上的(这为使用DMA提供了基本条件),而且如果分辨率在8位以上的话,每个轴都有至少2个字节数据,那如果使用传统的等待方式的话,这至少6个字节的数据读取需要花费相对比较长的时间(I2C本身的通信速度就有点慢),这种情况下可想而知如果使用DMA会节省多少时间资源。当然还有,现在是可穿戴设备大行其道的时代,我们以其中一个必不可少的计步sensor为例(实际上使用的是加速度传感器,但是需要FIFO深度比较深以节省唤醒功耗,具体为啥这里就不细说了),其内部的数据FIFO深度一般都是十几个甚至几十个字节,这样的话每当MCU被唤醒读取这几十个字节数据做计步算法的时候就可以想象DMA带来的巨大好处了。至于更多的应用场合,我就不多说了,靠大家自己去想象了!

    一不小心啰嗦了不少,对工程师来说,说的再好也不如直接上代码来的简单,呵呵,简单粗暴就是俺们这伙人的风格。俺在这方面也不是抠门的人(俺比较讨厌在这方面藏着掖着),下面源代码呈上:

1. 首先是Kinetis的eDMA初始化部分,我们需要先把I2C触发源分配到DMA相应的Channel上,然后配置好DMA的源地址和目的地址,以及相关传输属性等操作,具体每一步可以直接参考代码中的注释部分,具体eDMA初始化配置如下:

void eDMA_Init(void)

{

  SIM_SCGC6 |= SIM_SCGC6_DMAMUX_MASK;

   

  DMAMUX_CHCFG0 =  DMAMUX_CHCFG_ENBL_MASK              /* 使能DMA通道 */     

                   | DMAMUX_CHCFG_SOURCE(19);  /* 指定DMA触发源为I2C,这个可以在Reference Mannual第三章节中找到 */

   

  SIM_SCGC7 |= SIM_SCGC7_DMA_MASK;

   

  DMA_BASE_PTR->TCD[0].SADDR = (unsigned int)&I2C1_D;                  /* 分配DMA源地址 */

  DMA_BASE_PTR->TCD[0].DADDR = (unsigned int)&result;                  /* 分配DMA目标地址 */

  DMA_BASE_PTR->TCD[0].NBYTES_MLNO = 1;                 /* 每次minor loop传送1个字节 */

  DMA_BASE_PTR->TCD[0].ATTR  =(0

                                     |DMA_ATTR_SMOD(0)        /* Source modulo feature disabled */

                                     | DMA_ATTR_SSIZE(0)      /* Source size, 8位传送 */

                                     | DMA_ATTR_DMOD(0)       /* Destination modulo feature disabled */

                                     | DMA_ATTR_DSIZE(0)      /* Destination size, 8位传送 */

                                    );

  DMA_BASE_PTR->TCD[0].SOFF  = 0x0000;                  /* 每次操作完源地址,源地址不增加 */

  DMA_BASE_PTR->TCD[0].DOFF  = 0x0001;                  /* 每次操作完目标地址,目标地址增加1  */

  DMA_BASE_PTR->TCD[0].SLAST = 0x00;                    /* DMA完成一次输出之后即major_loop衰减完之后不更改源地址 */

  DMA_BASE_PTR->TCD[0].DLAST_SGA = 0x00;                /* DMA完成一次输出之后即major_loop衰减完之后不更改目标地址 */

  DMA_BASE_PTR->TCD[0].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(6-1); /* 1个major loop, 即一次传输量=major_loop*minor_loop,最大为2^15=32767 */

  DMA_BASE_PTR->TCD[0].BITER_ELINKNO = DMA_CITER_ELINKNO_CITER(6-1); /* BITER应该等于CITER */

   

  DMA_BASE_PTR->TCD[0].CSR = 0;                         /* 先清零CSR,之后再设置 */

   

  DMA_INT |= 1<<0;                                      /* 先清零DMA相应通道的传输完成中断,与TCD_CSR_INTMAJOR或者TCD_CSR_INTHALF搭配 */

  DMA_BASE_PTR->TCD[0].CSR |= DMA_CSR_INTMAJOR_MASK;   /* 打开DMA major_loop完成中断 */

  DMA_BASE_PTR->TCD[0].CSR &= ~DMA_CSR_DREQ_MASK;      /* major_loop递减为0时不关闭ERQ功能,准备下一次DMA Major loop传输 */

   

  /* DMA_ERQ寄存器很重要,置位相应的位即开启DMA工作 */

  //DMA_ERQ |= 1 << 0;                                   /* 打开相应通道的DMA请求*/

   

  enable_irq(INT_DMA0-16);

}

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

450

主题

462

帖子

0

粉丝