查看: 2465|回复: 4

[求助] Kinetis K系列使用DMA实现I2C读取

[复制链接]

该用户从未签到

8

主题

62

帖子

0

注册会员

Rank: 2

积分
79
最后登录
2018-5-24
发表于 2015-8-28 16:01:22 | 显示全部楼层 |阅读模式
原作者:jicheng


   以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干了,小心脏扑通一下有木有,相当强大吧,哈哈,拭目以待。


    那说到这,我有必要提一下这种方式的具体应用场合(考验大家发散思维的时候到了,呵呵),在常用的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初始化配置如下:


  1. <font size="3">void eDMA_Init(void)
  2. {
  3.   SIM_SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
  4.    
  5.   DMAMUX_CHCFG0 =  DMAMUX_CHCFG_ENBL_MASK              /* 使能DMA通道 */     
  6.                    | DMAMUX_CHCFG_SOURCE(19);  /* 指定DMA触发源为I2C,这个可以在Reference Mannual第三章节中找到 */
  7.    
  8.   SIM_SCGC7 |= SIM_SCGC7_DMA_MASK;
  9.    
  10.   DMA_BASE_PTR->TCD[0].SADDR = (unsigned int)&I2C1_D;                  /* 分配DMA源地址 */
  11.   DMA_BASE_PTR->TCD[0].DADDR = (unsigned int)&result;                  /* 分配DMA目标地址 */
  12.   DMA_BASE_PTR->TCD[0].NBYTES_MLNO = 1;                 /* 每次minor loop传送1个字节 */
  13.   DMA_BASE_PTR->TCD[0].ATTR  =(0
  14.                                      |DMA_ATTR_SMOD(0)        /* Source modulo feature disabled */
  15.                                      | DMA_ATTR_SSIZE(0)      /* Source size, 8位传送 */
  16.                                      | DMA_ATTR_DMOD(0)       /* Destination modulo feature disabled */
  17.                                      | DMA_ATTR_DSIZE(0)      /* Destination size, 8位传送 */
  18.                                     );
  19.   DMA_BASE_PTR->TCD[0].SOFF  = 0x0000;                  /* 每次操作完源地址,源地址不增加 */
  20.   DMA_BASE_PTR->TCD[0].DOFF  = 0x0001;                  /* 每次操作完目标地址,目标地址增加1  */
  21.   DMA_BASE_PTR->TCD[0].SLAST = 0x00;                    /* DMA完成一次输出之后即major_loop衰减完之后不更改源地址 */
  22.   DMA_BASE_PTR->TCD[0].DLAST_SGA = 0x00;                /* DMA完成一次输出之后即major_loop衰减完之后不更改目标地址 */
  23.   DMA_BASE_PTR->TCD[0].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(6-1); /* 1个major loop, 即一次传输量=major_loop*minor_loop,最大为2^15=32767 */
  24.   DMA_BASE_PTR->TCD[0].BITER_ELINKNO = DMA_CITER_ELINKNO_CITER(6-1); /* BITER应该等于CITER */
  25.    
  26.   DMA_BASE_PTR->TCD[0].CSR = 0;                         /* 先清零CSR,之后再设置 */
  27.    
  28.   DMA_INT |= 1<<0;                                      /* 先清零DMA相应通道的传输完成中断,与TCD_CSR_INTMAJOR或者TCD_CSR_INTHALF搭配 */
  29.   DMA_BASE_PTR->TCD[0].CSR |= DMA_CSR_INTMAJOR_MASK;   /* 打开DMA major_loop完成中断 */
  30.   DMA_BASE_PTR->TCD[0].CSR &= ~DMA_CSR_DREQ_MASK;      /* major_loop递减为0时不关闭ERQ功能,准备下一次DMA Major loop传输 */
  31.    
  32.   /* DMA_ERQ寄存器很重要,置位相应的位即开启DMA工作 */
  33.   //DMA_ERQ |= 1 << 0;                                   /* 打开相应通道的DMA请求*/
  34.    
  35.   enable_irq(INT_DMA0-16);
  36. }</font>
复制代码




2. I2C配置部分,由于前面的发送地址信息等操作还是传统的手动方式,所以I2C初始化部分就不需要改了,我们只需要修改读取多字节数据的API函数即可,具体操作如下:


  1. <font size="3">void I2CReadMultiRegisters(unsigned char u8RegisterAddress, unsigned char bytes)
  2. {
  3.     unsigned char n = bytes;
  4.     int i;
  5.     /* Send Slave Address */
  6.     IIC_StartTransmission(SlaveID, MWSR);
  7.     i2c_Wait();
  8.     /* Write Register Address */
  9.     I2C1_D = u8RegisterAddress;
  10.     i2c_Wait();
  11.     /* Do a repeated start */
  12.     I2C1_C1 |= I2C_C1_RSTA_MASK;
  13.     /* Send Slave Address */
  14.     I2C1_D = (ACCEL_I2C_ADDRESS << 1) | 0x01;    //read address
  15.     i2c_Wait();
  16.     /* Put in Rx Mode */
  17.     I2C1_C1 &= (~I2C_C1_TX_MASK);
  18.     /* Ensure TXAK bit is 0 */
  19.     I2C1_C1 &= ~I2C_C1_TXAK_MASK;
  20.     /* Dummy read */
  21.     result[0] = I2C1_D;
  22.          
  23.         I2C1_C1 |= I2C_C1_IICIE_MASK | I2C_C1_DMAEN_MASK; /* 在发送完地址信息之后打开DMA功能,Added by JiCheng */
  24.         DMA_ERQ |= 1 << 0; //打开DMA
  25. }</font>
复制代码


3. 然后就是DMA接收完成中断了,这里面有一个地方需要注意一下,这里我以我司三轴加速度传感器为例,其分辨率为14位即每轴占用2个字节数据,这样的话应该是6个字节的数据,但是我这里在重新配置DMA接收长度时(即接收几个字节后产生DMA中断)写的是6-1即5(回到第一步中也是),这个在我们手中的I2C章节里特别标注了如下,即I2C在使用DMA读取数据时,其最后一个字节数据需要回到手动接收已提供NACK和STOP指令如下图,还有就是我们在DMA中断的最后需要把I2C的DMA先关掉,因为下次读取还要手动去发送地址信息呢,具体代码如下:


  1. <font size="3">void DMA_ISR(void)
  2. {
  3.     DMA_INT |= 1<<0;
  4.      
  5.     DMA_BASE_PTR->TCD[0].DADDR = (unsigned int)&result;                  /* 分配DMA目标地址 */
  6.     DMA_BASE_PTR->TCD[0].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(6-1); /* 1个major loop, 即一次传输量=major_loop*minor_loop,最大为2^15=32767 */
  7.     DMA_BASE_PTR->TCD[0].BITER_ELINKNO = DMA_CITER_ELINKNO_CITER(6-1); /* BITER应该等于CITER */
  8.     DMA_ERQ &= ~(1 << 0);
  9.     I2C1_C1 &=~ (I2C_C1_IICIE_MASK | I2C_C1_DMAEN_MASK); /* Added by JiCheng */
  10.     /* Turn off ACK since this is  last read */
  11.     I2C1_C1 |= I2C_C1_TXAK_MASK;
  12.          
  13.     i2c_Wait();
  14.          
  15.     /* Send stop */
  16.     i2c_Stop();
  17.                  
  18.        result[5] = I2C1_D;
  19.      
  20.     printf("%d  %d  %d  %d  %d  %d\n\r", result[0], result[1], result[2], result[3], result[4], result[5]);
  21.      
  22. }
  23. clip_image002</font>
复制代码

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

4. 最后我们简单调用一下I2C读取多字节API函数,即可以实现高大上的I2C+DMA读取功能了。


  1. <font size="3">void main(void)
  2. {
  3.     printf("Kinetis %s I2C Demo\n", TWR_STRING);
  4.     /* Initialize I2C */
  5.     init_I2C();
  6.         eDMA_Init();
  7.          
  8.     /* Configure accelemoter sensor */
  9.     I2CWriteRegister(0x2A, 0x01);
  10.     printf("   X     Y     Z\n");
  11.     while (1) {
  12.         /* Look at status of accelerometer */
  13.         I2CReadMultiRegisters(0x01, 6);
  14.         /* Delay for 250ms */
  15.         time_delay_ms(100);
  16.         printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
  17.     }
  18. }</font>
复制代码



我知道答案 目前已有4人回答

评分

参与人数 1NXP金币 +20 收起 理由
小七 + 20

查看全部评分

回复

使用道具 举报

该用户从未签到

145

主题

4926

帖子

0

金牌会员

Rank: 6Rank: 6

积分
9267
最后登录
1970-1-1
发表于 2015-8-28 17:05:27 | 显示全部楼层
感谢楼主的转帖。
回复 支持 反对

使用道具 举报

  • TA的每日心情
    开心
    2017-1-24 09:50
  • 签到天数: 2 天

    [LV.1]初来乍到

    654

    主题

    3262

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    11054
    最后登录
    2019-1-27
    发表于 2015-8-28 17:29:11 | 显示全部楼层
    谢谢楼主分享哦
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2018-7-23 21:04
  • 签到天数: 103 天

    [LV.6]常住居民II

    228

    主题

    5379

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    15249
    最后登录
    1970-1-1
    发表于 2015-8-28 21:47:45 | 显示全部楼层
    不错,学习一下
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2018-1-30 19:52
  • 签到天数: 19 天

    [LV.4]偶尔看看III

    19

    主题

    628

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    1384
    最后登录
    2018-5-28
    发表于 2015-8-29 21:25:43 | 显示全部楼层
    转来看看
    该会员没有填写今日想说内容.
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-4-25 22:38 , Processed in 0.146330 second(s), 28 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表