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

我本仁慈,奈何苍天不许

文章分类

全部博文(165)

文章存档

2018年(1)

2016年(33)

2015年(5)

2014年(34)

2013年(92)

分类: 嵌入式

2016-07-21 14:13:14

原文地址:nrf51822裸机教程-SPI(主) 作者:ifndef

关于SPI总线的介绍这里就不细说了,网上有很多介绍SPI总线时序的。

 

SPI总线的本质就是一个环形总线结构,在时钟驱动下两个双向移位寄存器进行数据交换。

所以SPI总线的特色就是:传输一字节数据的同时也会接收到一字节数据。支持SPI操作的芯片通常都会有一个CS引脚作为片选信号,所以总线上可以挂多个支持SPI操作的芯片,每次想对哪个操作就直接使能那个芯片就可以了。

 

对比于IIC总线,IIC总是在一个时钟周期内传输一个bit IIC总线总是在每个时钟周期的高电平部分采样数据。

SPI的特点是在时钟周期的跳变沿时采样数据或传输数据。一个时钟周期有两个跳变沿,一个上升沿一个下降沿。于是SPI总线的传输的特性就是在一个时钟周期的上升沿发送自己的一个bit数据,同时在该时钟周期的下降沿接收对端设备发送过来的一bit数据。或者是上升沿接收数据,下降沿发送数据。这都是可以设置的。

 

所以使用SPI总线时,当你要传输一个数据时总是同时会收到对端发来的一个数据,但是你没想要这个数据啊,很简单直接忽略就行了。

同理当你想读取对端的一个数据时,也随便发一个数据过去就行了。

 

51822发送数据是通过将数据写入TXD寄存器,因为每发送一个数据的同时会收到一个数据,该数据会放在RXD寄存器中以供读取使用。每当发送和接收到一个字节后,就会产生READY事件,以方便告知我们传输完成。所以SPI基本的一个字节的传输和接收操作通过如下函数实现

 

//SPI传输的基本函数,该函数传输一个字节给对方同时取得对方发送过来的字//

uint8_t spi_transfer(uint8_t data){

    uint8_t ret;

    NRF_SPI0->TXD = data;

    while ( NRF_SPI0->EVENTS_READY == 0 ); //等待传输结束

    NRF_SPI0->EVENTS_READY = 0;

    ret = NRF_SPI0->RXD;

    return ret;

}

 

 

下面介绍对flash GD25Q128B进行数据的写入和读取操作。

上面提到过,SPI总线可以设置在一个 上升沿接收数据,下降沿发送数据,或者相反。  那么我们就需要看一下这个板子上的这个flash芯片对这个有没有要求,如果没要求我们就可以随便设置。查看GD25Q128B手册知道该芯片要求每个字节的最高bit先传输,同时要求在时钟的上升沿会获取总线上的数据。这里是针对这个flash芯片来说的,那么对于51822,就需要在上升沿把数据发送出去。


查看51822手册的SPI部分,找到CONFIG寄存器,根据上图的要求,则需要设置第一bit0, 关于时钟极性(总线空闲时是保持低电平还是高电平)flash手册中没要求所以可以随便设置,所以我们只要满足flash芯片可以在时钟周期的上升沿能收到数据就行了。也就是51822在上升沿将数据发送出去。

 

所以 如果设置 CPOL0,即空闲时保持高电平,那么当开始工作时,每个时钟的第一个沿就是下降沿,第二个沿是上升沿。根据下图说明这时候我们就要设置CPHA0,即让51822在时钟周期第二个沿 将数据发送出去。

同理,如果CPOL1,那么开始工作时第一个沿就是上升沿,第二个沿就是下降沿,于是就要设置51822在第一个沿将数据发送出去也就是设置CPHA1.

   

//flash芯片要求在MSB先传输,并且在第一个上升沿采样。所以设置下。

//CPOL和CPHA都设置成1也是可以的,这里都设置成0了。

    NRF_SPI0->CONFIG = (0<<0)   //MSB first

                     | (0<< 1)

                     | (0 << 2)

 

基本传输要求满足了后,现在需要操作flash的读写,那么就需要看flash手册。了解关于读写命令的要求和命令码。查看手册说明,我们需要用到的命令有如下几个读/写命令。Flash操作都是以页为单位写(该芯片一页256字节),并且写之前需要先擦除,所以还需要用到sector erase(以4K为单位擦除)命令来擦数, block erase擦除的单位更大,这里用sector erase就行了。

读操作直接就可以完成,但是写操作发送后flash内被还需要时间去执行,以将数据写入flash中。所以我们还需要获取flash的执行状态,即当前是否在忙。所以还需要用到 读状态命令

另外,GD25Q128B这个flash芯片每次发送 页写或者 擦除命令之前需要先发送 Write Enable命令。


下面针对几个用到的命令看下 flash的手册说明

 

先看下读状态寄存器 命令,这里我们只需要读取状态寄存器的前7位就行了。然后判断  0位的WIP是否为1,为1则表示flash芯片正在忙(正在写数据到flash中或者正在擦除),否则为不忙,则可以执行 写或擦除命令。


所以读状态寄存器命令我们用0x05就行了,因为我们只需要看第0位指示的flash芯片是否在忙,所以我们只要用0x05命令读取s0-s7位就行了。



通过读取状态寄存器,然后判断第0位,我们就可以知道设备是否在忙。

因为 基本的SPI传输和接收一个字节的函数我们已经实现了。所以等待设备不忙函数实现如下:

uint8_t flash_status_read(void){

    uint8_t ret;

   

    enable_cs_pin();

    spi_transfer(READ_STATUS);//发命令

    ret = spi_transfer(0xff);//读取状态寄存器的s0-s7, 写数据随便填的。

    disable_cs_pin();

   

    return ret;

}

 

#define WAIT_COUNT   (200000UL)

 

uint8_t wait_flash_ready(void){

    uint8_t ret;

    uint32_t i ;

    for(i= 0; i

       ret = flash_status_read();

       if( (ret&0x01) == 0) break;

    }

    if ( i == WAIT_COUNT ) return FAIL;

    return SUCCESS;

}

 

 

再看下write enable命令,由下图可知该命令只需要 使能片选信号cs(拉低)后,发送一个 0x06命令码就可以了。之后才可以执行 页写和擦除命令

有了前面的基本spi的 写入和读取 一个字节的基本函数后,我们就可以实现如下的write enable函数

uint8_t flash_write_enable(void){

    //需要等待设备不忙

    if(wait_flash_ready() != SUCCESS ) return FAIL;

 

    enable_cs_pin();  //命令发送前需要先 使能片选信号

    spi_transfer(WRITE_ENABLE_CMD);

    disable_cs_pin();

    // 等待设备不忙,即该命令再flash芯片内部执行好了,然后才能去执行

    // 写/擦除命令

    if(wait_flash_ready() != SUCCESS ) return FAIL;

 

    return SUCCESS;

}



再看下读命令:

由下图可知 通过写命令码0x03 + 3字节的地址,然后就可以获取指定地址的数据了。获得得的数据量由自己定义。该flash芯片每次发送一字节数据后,内部会对读取地址自动加一,所以我们只要一直随便发一个数据给该flash芯片,flash芯片就会一直回复数据,并自动对读取地址加1.

同样由基本 传输/接收函数实现 读取函数如下:

 

uint8_t flash_read(uint32_t addr, uint8_t *buff, uint32_t len){

    uint8_t ret;

    uint8_t i;

   

    if( NULL == buff ) return FAIL;

   

    wait_flash_ready();  //等待设备当前是不忙的

    enable_cs_pin();

    spi_transfer(READ_CMD);     //先传命令

    spi_transfer( (addr>>16)&0xff );//依次传输3字节地址,高字节先发送

    spi_transfer( (addr>>8)&0xff );

    spi_transfer( addr&0xff);

    //开始获取读到的数据

    for( i = 0; i < len; i++){

ret = spi_transfer(0xff);   //这里主要是读数据,所以随便传输一//个数据给对方

       buff[i] = ret;

    }

    disable_cs_pin();

   

    return SUCCESS;

}



页写命令:该命令执行前需要先执行了 write enable命令。

由下面可知 通过写 命令码0x02+3字节地址+要写的数据,数据量应该小于等于256字节。不然只有最后的256字节才会被写入。

函数实现如下:

//板子上的flash芯片一页是256字节,如果写超过256字节,只有最后的256字节能被写到flash

uint8_t flash_page_write(uint32_t addr, uint8_t *data, uint32_t len){

    uint8_t ret;

    uint8_t i;

    ret = flash_write_enable();     //先执行write enable

    if(ret!= SUCCESS) return ret;

   

    enable_cs_pin();

    spi_transfer(PAGE_PROGRAM);     //先传命令

    spi_transfer( (addr>>16)&0xff );   //依次传输3字节地址,高字节先发送

    spi_transfer( (addr>>8)&0xff );

    spi_transfer( addr&0xff);

    //开始传数据给flsh芯片

    for( i = 0; i < len; i++){

       spi_transfer(data[i]);   //这里主要是写数据,所以忽略返回值。

    }

    disable_cs_pin();

   

    return SUCCESS;

   

}


最后看一下 擦除命令:该命令执行前需要先执行 write enable命令。

 

由下图可知通过写命令码0x20+3字节地址就可以了。

所以实现 擦除函数如下:

uint8_t flash_sector_erase(uint32_t addr){

    uint8_t ret;

    ret = flash_write_enable();     //先写 write enable命令

    if ( ret != SUCCESS ) return ret;

   

    enable_cs_pin();

    spi_transfer(SECTOR_ERASE);     //先传命令

    spi_transfer( (addr>>16)&0xff );//依次传输3字节地址,高字节先发送

    spi_transfer( (addr>>8)&0xff );

    spi_transfer( addr&0xff);

    disable_cs_pin();

   

    return SUCCESS;

   

}


各个功能函数都有了就可以操作flash了。

 

下面将各个功能函数和 测试的main函数都贴出来。

为了可以通过打印来验证flash操作的正确性,可以直接用sdk下面的 uartble demo。虽然该demo是跑ble的。但是我们直接将main函数换掉就行了。用该例子的原因就是为了方便初学者可以使用Printf函数来打印信息验证flash操作的正确性。

直接打开sdk中的ble_app_uart例子,然后将下列代码复制到 main函数的上面,然后将Main函数替换成 最下面提供的那个main函数就行了。


点击(此处)折叠或打开

  1. #define PIN_CS                28
  2. #define PIN_MISO            29
  3. #define PIN_MOSI            24
  4. #define PIN_CLK                25


  5. #define WRITE_ENABLE_CMD    0x06
  6. #define READ_CMD            0x03
  7. #define PAGE_PROGRAM        0x02
  8. #define SECTOR_ERASE        0x20
  9. #define READ_STATUS            0x05 //我们读状态寄存器只是为了判断flash当前是不是正在忙,所以只需判断s0位,所以用0x05命令读出s0-s7就可以了

  10. #define SUCCESS                0x01
  11. #define FAIL                0x00
  12. void init_spi_master(void){
  13.     
  14.     //按手册要求设置
  15.     nrf_gpio_cfg_input(PIN_MISO, NRF_GPIO_PIN_NOPULL);
  16.     nrf_gpio_cfg_output(PIN_MOSI);
  17.     nrf_gpio_cfg_output(PIN_CLK);    
  18.     nrf_gpio_cfg_output(PIN_CS);
  19.     nrf_gpio_pin_set(PIN_CS);
  20.     
  21.     
  22.     //flash芯片要求在MSB先传输,并且在第一个上升沿采样。所以设置下。 CPOL和CPHA都设置成1也是可以的,这里都设置成0了。
  23.     NRF_SPI0->CONFIG = (0<<0)    //MSB first
  24.                         | (0<< 1)
  25.                         | (0 << 2);
  26.     NRF_SPI0->FREQUENCY = 0x10000000;
  27.     NRF_SPI0->PSELSCK = PIN_CLK;
  28.     NRF_SPI0->PSELMOSI = PIN_MOSI;
  29.     NRF_SPI0->PSELMISO = PIN_MISO;
  30.     
  31.     NRF_SPI0->ENABLE = 1;
  32.     
  33. }

  34. //SPI传输的基本函数,该函数传输一个字节给对方同时取得对方发送过来的字节
  35. uint8_t spi_transfer(uint8_t data){
  36.     uint8_t ret;
  37.     NRF_SPI0->TXD = data;
  38.     while ( NRF_SPI0->EVENTS_READY == 0 );    //等待传输结束
  39.     NRF_SPI0->EVENTS_READY = 0;

  40.     ret = NRF_SPI0->RXD;
  41.     
  42.     return ret;
  43. }
  44. void enable_cs_pin(void){
  45.     nrf_gpio_pin_clear(PIN_CS);
  46. }

  47. void disable_cs_pin(void){
  48.     nrf_gpio_pin_set(PIN_CS);
  49. }

  50. uint8_t flash_status_read(void){
  51.     uint8_t ret;
  52.     
  53.     enable_cs_pin();
  54.     spi_transfer(READ_STATUS);//发命令
  55.     ret = spi_transfer(0xff);//读取状态寄存器的s0-s7, 写数据随便填的。
  56.     disable_cs_pin();
  57.     
  58.     return ret;
  59. }

  60. #define WAIT_COUNT    (200000UL)

  61. uint8_t wait_flash_ready(void){
  62.     uint8_t ret;
  63.     uint32_t i ;
  64.     for(i= 0; i<WAIT_COUNT; i++ ){
  65.         ret = flash_status_read();
  66.         if( (ret&0x01) == 0) break;
  67.     }
  68.     if ( i == WAIT_COUNT ) return FAIL;
  69.     
  70.     return SUCCESS;
  71.     
  72. }

  73. uint8_t flash_write_enable(void){
  74.     
  75.     if(wait_flash_ready() != SUCCESS ) return FAIL;

  76.     enable_cs_pin();
  77.     spi_transfer(WRITE_ENABLE_CMD);
  78.     disable_cs_pin();
  79.     if(wait_flash_ready() != SUCCESS ) return FAIL;

  80.     return SUCCESS;
  81. }

  82. uint8_t flash_read(uint32_t addr, uint8_t *buff, uint32_t len){
  83.     uint8_t ret;
  84.     uint8_t i;
  85.     
  86.     if( NULL == buff ) return FAIL;
  87.     
  88.     wait_flash_ready();
  89.     enable_cs_pin();
  90.     spi_transfer(READ_CMD);        //先传命令
  91.     spi_transfer( (addr>>16)&0xff );    //依次传输3字节地址,高字节先发送
  92.     spi_transfer( (addr>>8)&0xff );
  93.     spi_transfer( addr&0xff);
  94.     //开始获取读到的数据
  95.     for( i = 0; i < len; i++){
  96.         ret = spi_transfer(0xff);    //这里主要是读数据,所以随便传输一个数据给对方
  97.         buff[i] = ret;
  98.     }
  99.     disable_cs_pin();    
  100.     
  101.     return SUCCESS;
  102. }

  103. //板子上的flash芯片一页是256字节,如果写超过256字节,只有最后的256字节能被写到flash中
  104. uint8_t flash_page_write(uint32_t addr, uint8_t *data, uint32_t len){
  105.     uint8_t ret;
  106.     uint8_t i;
  107.     ret = flash_write_enable();
  108.     if( SUCCESS) return ret;
  109.     
  110.     enable_cs_pin();
  111.     spi_transfer(PAGE_PROGRAM);        //先传命令
  112.     spi_transfer( (addr>>16)&0xff );    //依次传输3字节地址,高字节先发送
  113.     spi_transfer( (addr>>8)&0xff );
  114.     spi_transfer( addr&0xff);
  115.     //开始传数据给flsh芯片
  116.     for( i = 0; i < len; i++){
  117.         spi_transfer(data[i]);    //这里主要是写数据,所以忽略返回值。
  118.     }
  119.     disable_cs_pin();    
  120.     
  121.     return SUCCESS;
  122.     
  123. }
  124. uint8_t flash_sector_erase(uint32_t addr){
  125.     uint8_t ret;
  126.     ret = flash_write_enable();
  127.     if ( ret != SUCCESS ) return ret;
  128.     
  129.     enable_cs_pin();
  130.     spi_transfer(SECTOR_ERASE);        //先传命令
  131.     spi_transfer( (addr>>16)&0xff );    //依次传输3字节地址,高字节先发送
  132.     spi_transfer( (addr>>8)&0xff );
  133.     spi_transfer( addr&0xff);
  134.     disable_cs_pin();
  135.     
  136.     return SUCCESS;
  137.     
  138. }




  139. /**@brief Application main function.
  140.  */
  141. uint8_t *data;
  142. int main(void)
  143. {

  144.     uint32_t err_code;
  145.     bool erase_bonds;
  146.     uint8_t start_string[] = START_STRING;
  147.     uint8_t buff[200];
  148.     uint8_t ret;
  149.     
  150.     uart_init();
  151.     printf("start\r\n");
  152.     
  153.     init_spi_master();
  154.     //先读出200字节
  155.     ret = flash_read(0, buff, 200);
  156.     if(ret!=SUCCESS)printf("read error\r\n");
  157.     printf("原始数据\r\n");
  158.     for(uint8_t i = 0; i < 200; i++){
  159.         printf("%d ",buff[i]);
  160.     }
  161.     printf("\r\n");
  162.     
  163.     //擦除第0个sector,并再读取下,
  164.     ret = flash_sector_erase(0x0);
  165.     if ( ret!=SUCCESS ) printf("erase error\r\n");
  166.     ret = flash_read(0, buff, 200);
  167.     printf("擦除后数据\r\n");
  168.     if(ret!=SUCCESS)printf("read error\r\n");
  169.     for(uint8_t i = 0; i < 200; i++){
  170.         printf("%d ",buff[i]);
  171.     }
  172.     printf("\r\n");    
  173.     
  174.     for(uint8_t i = 0; i < 200; i++){
  175.         buff[i] = i;
  176.     }
  177.     //现在已经擦除过了,写数据进去
  178.     ret = flash_page_write(0, buff, 200);
  179.     if ( ret!=SUCCESS ) printf("write error\r\n");
  180.     
  181.     //再读数据看对不对
  182.     ret = flash_read(0, buff, 200);
  183.     printf("写后数据:\r\n");
  184.     if(ret!=SUCCESS)printf("read error\r\n");
  185.     for(uint8_t i = 0; i < 200; i++){
  186.         printf("%d ",buff[i]);
  187.     }
  188.     printf("\r\n");    
  189.     
  190.     while(1);    
  191.  
  192. }



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