Chinaunix首页 | 论坛 | 博客
  • 博客访问: 605596
  • 博文数量: 165
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1554
  • 用 户 组: 普通用户
  • 注册时间: 2013-10-23 22:57
个人简介

我本仁慈,奈何苍天不许

文章分类

全部博文(165)

文章存档

2018年(1)

2016年(33)

2015年(5)

2014年(34)

2013年(92)

分类: 嵌入式

2016-07-21 14:13:17

原文地址:nrf51822裸机教程-IIC 作者:ifndef

关于IIC总线的核心有以下几点:

:时钟线高电平期间必须保持数据线不变。

:时钟线低电平期间可以改变数据。

:时钟线和数据线上都要接上拉电阻,以使总线不工作时,两根线的电平都处于高电平状态。

:每个传输的字节后面需要由对方回送一个应答信号。

 

由上面可知,在时钟线为高电平的时候如果数据线改变,那么就是”不合法” 的。于是就刚好利用这种”不合法的”的跳变来作为数据 起始信号和停止信号。

于是规定:

:时钟线为高电平时,数据线由高到低跳变为起始信号

:时钟线为高电平时,数据线有低到高跳变为 停止信号。

 

关于IIC的原理和时序,网上很多文章,这里主要介绍 51822 硬件Iic的使用。

 

首先看先相关寄存器的说明:

STARTRX: 启动接收,即iic的读。

STARTTX:启动发送,即写。

ADDRESS: 设备地址寄存器,IIC 总线的通信,总是以地址+读写标识 开始,因为总线上可能挂了不止一个IIC设备,所以需要通过发送地址来说明要和哪个设备通信。

 

这里需要注意的是,51822的ADRESS寄存器只有7位有效,不包含低8位的 读写指示,读写指示是 硬件通过 你是启动读(通过设置STARTRX寄存器)还是启动写(通过设置STARTTX寄存器)来自动 在ADDRESS的7位发送完后在发送的。

所以在使用时,你不需要自己根据是读还是写,而设置地址寄存器ADDRESS =( ADDRESS<<8) |0x01 或ADDRESS =( ADDRESS<<8)&0xfe。 而是 直接ADDRESS = 7位设备地址 就可以了。读写位 会有硬件自动发送。

 

 

STOP:停止IIC

SUSPEND:挂起IIC(暂停),通常在 IIC读中使用,是为了在收到一个字节后,暂停IIC的传输,以保证 接收数据寄存器不被后续的数据覆盖。

RESUME:恢复被暂停的IIC,继续传输

 

 

关于事件寄存器,主要是如下两个事件需要关注:

RXDREADY: 指示数据接收完成。

TXDREADY:指示数据发送完成

BB:该事件在每一个字节发送或者接收之前产生,改事件通常使用在 读操作中,即接收操作。

 

SHORTS:该寄存器重要用来 将某个eventtask短接。上面 说过,通过 设置SUSPEND可以暂停IIC总线,这样可以避免后续的接收数据覆盖了接收寄存器中的数据,而BB事件在每次数据接收之前会产生。 于是在接收过程中,可以通过判断接收的数据量如果还大于1那么久应该  通过SHORTS寄存器将BB事件和SUSPEND任务短接,那么每次从接收寄存器RXD中提取数据时,IIC总线就会自动被暂停,也就避免了后续数据覆盖了RXD内容,而如果接收的数据只剩最后一个了,那么久可以将BB事件和STOP 任务短接,那么在接收最后一个数据后就会自动发送停止信号了。这块看后面的代码注释更好理解。

 

INTEN:

INTENSET:

INTENCLR:

以上三个寄存器都是用来设置 当产生各种事件是是否产生中断。本教程中并未使用中断。

 

ERRORSRC:用来记录产生的错误原因

 

PSELSCL:用来选择哪个引脚作为 时钟线

PSELSDA:用来选择哪个引脚作为数据线

 

RXD:从该寄存器中提取接收到的数据

TXD:将要发送的数据填入该寄存器

 

FREQUENCY:设置 发送速率

ADDRESS:设置要通信的设备的地址

 

51822IIC写操作如下图所以所示:

所以对于写需要如下几个步骤:

1:首先设置地址寄存器。

2:设置 STARTTX启动写操作。

3:将要发送的数据放入TXD寄存器中。

4:等待TXDSENT信号

5:如果有数据,继续将后续数据放入TXD中,并会到步骤4.否则到步骤6

6:设置STOP寄存器,并等待IIC停止了。

 

 

 

对于读操作,一般IIC设备都需要先提供要读的寄存器或地址。所以读操作一般需要先有一个写操作,来设置要读的地址或寄存器,然后再跟随读操作。51822提供的操作图如下所示:




所以对于需要先写地址再执行读的操作有如下几个步骤:

1:设置设备地址

2:设置STARTTX启动写操作

3:将要发送的数据(寄存器地址或数据地址)写入TXD寄存器中

4:等待TXDSENT 事件,以确定数据发送完毕。

5:判断是否只有一个要读的数据,如果不是设置 SHORTSBB eventSUSPEND task短接(BB evnet 产生时自动触发SUSPEND task),否设置则BB eventSTOP task短接。

6:设置STARTRX寄存器启动读操作。

7:等待RXDRDY事件,提取数据。如果后续只有一个要读的数据了,则设置BB eventSTOP task短接,并跳到8。否则继续执行7

8:等待STOPED信号。



=下面介绍main.c代码细节。我的板子上有一个MPU6050是通过IIC来操作的,所以这里就使用该设备来验证IIC驱动的正确性。 根据板子的接线原理图,6050的设备地址为0x69,根据6050的手册知道该传感器  0x75地址的寄存器中存放的数据始终未0x68,所以下面就读这个寄存器中的值,来验证IIC是否正确通信,分别用两个led来指示读取的数据是否正确

 

PS:下面的驱动只是为了说明驱动原理,错误情况处理以及等待超时都没有做,如果自己的项目中需要使用IIC,请使用sdk中提供的,或者将下面的驱动参考SDK中的驱动加上错误处理和超时处理的相关代码。



点击(此处)折叠或打开

  1. #include "nrf51.h"
  2. #include "nrf_gpio.h"
  3. #include <stdbool.h>
  4. #include <stdio.h>
  5. #include <stdint.h>
  6. #include "nrf_delay.h"

  7. #define SCL_PIN        (1)
  8. #define SDA_PIN        (5)

  9. void iic_init(void){
  10.     NRF_TWI0->PSELSCL = SCL_PIN;
  11.     NRF_TWI0->PSELSDA = SDA_PIN;
  12.     
  13.     NRF_TWI0->FREQUENCY = 0x06680000;//400Khz
  14.     NRF_TWI0->ENABLE = 5;
  15.     
  16.     //清零各种事件
  17.     NRF_TWI0->EVENTS_STOPPED = 0;
  18.     NRF_TWI0->EVENTS_RXDREADY = 0;
  19.     NRF_TWI0->EVENTS_TXDSENT = 0;
  20.     NRF_TWI0->EVENTS_BB = 0;
  21.     NRF_TWI0->EVENTS_ERROR = 0;
  22. }


  23. void write_datas(uint8_t dev_addr, uint8_t arg_addr, uint32_t len, uint8_t *p_data, bool is_stop){

  24.     NRF_TWI0->ADDRESS = dev_addr;

  25.     NRF_TWI0->TXD = arg_addr;
  26.     NRF_TWI0->EVENTS_TXDSENT = 0;            //先清零一下事件

  27.     NRF_TWI0->TASKS_STARTTX = 0X01;            //开始启动发送

  28.     while(1){
  29.         while( NRF_TWI0->EVENTS_TXDSENT == 0 ){ }    //等待发送完成
  30.         
  31.         NRF_TWI0->EVENTS_TXDSENT = 0;        //清零
  32.         if( (len--) == 0 ){
  33.             break;
  34.             
  35.         }
  36.         NRF_TWI0->TXD = *p_data++;
  37.     }    
  38. //判断是否需要停止IIC,对于单独的写操作,应该需要停止IIC,
  39. //对于读寄存器中值的操作,因为需要先写地址,后再发起读,所以前面的写操//作之后就不需要 停止IIC。

  40.     if ( is_stop ){
  41.         NRF_TWI0->EVENTS_STOPPED = 0;
  42.         NRF_TWI0->TASKS_STOP = 1;
  43.         while( NRF_TWI0->EVENTS_STOPPED == 0){};    //等待iic正确结束
  44.     }
  45. }


  46. void read_data(uint8_t dev_addr, uint8_t arg_addr, uint32_t len, uint8_t *p_data){
  47.     
  48.     write_datas(dev_addr, arg_addr, 0, NULL, false);    //先写地址
  49.     
  50. //    NRF_TWI0->ADDRESS = dev_addr;    //设置地址
  51.     
  52.     if ( len == 1 ){
  53.         NRF_TWI0->SHORTS = 2;
  54.     }else{
  55.         NRF_TWI0->SHORTS = 1;    //将BB事件和SUSPEND短接,则每次接收到数据后,总线被挂起,目的是为了在提取数据的时候,防止对方又发送数据过来导致接收的数据被覆盖
  56.     }
  57.     NRF_TWI0->EVENTS_RXDREADY = 0;            //先清零一下事件
  58.     NRF_TWI0->EVENTS_STOPPED = 0;
  59.     NRF_TWI0->TASKS_STARTRX = 0X01;            //开始启动接收
  60.     
  61.     while( (len--) > 0 ){
  62.         while( NRF_TWI0->EVENTS_RXDREADY == 0 ){}    //等待接收到数据
  63.         NRF_TWI0->EVENTS_RXDREADY = 0;    //清零事件
  64.         *p_data++ = NRF_TWI0->RXD;
  65.         
  66.         if( len == 0){
  67.             break;
  68.         }
  69.         if ( len == 1){
  70.             NRF_TWI0->SHORTS = 2;
  71.         }
  72.         NRF_TWI0->TASKS_RESUME = 1;
  73.     }
  74.     while ( NRF_TWI0->EVENTS_STOPPED == 0){}
  75. }


  76. #define LED1 (18)
  77. #define LED2 (19)

  78. int main(void){
  79.     uint8_t who_am_i;

  80.     //我的板子 是高电平点亮LED,这里是置低关led
  81.     nrf_gpio_cfg_output(LED1);
  82.     nrf_gpio_pin_clear(LED1);
  83.     nrf_gpio_cfg_output(LED2);
  84.     nrf_gpio_pin_clear(LED2);
  85.     
  86.     nrf_delay_ms(50);
  87.     iic_init();
  88.     

  89.     read_data(0x69, 0x75, &who_am_i, 1);
  90.     if(who_am_i == 0x68){
  91.             nrf_gpio_pin_set(LED1);
  92.     }else{
  93.             nrf_gpio_pin_set(LED2);
  94.     }
  95.     while(1);
  96.     return 0;
  97. }


阅读(1670) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~