基于S5PC100裸机程序之SPI

基于S5PC100裸机程序之SPI(上)

作者:杨老师,华清远见嵌入式学院讲师。

SPI作为应用最为广泛的通信总线协议之一,开发人员应当掌握,本章将介绍SPI总线协议的基本理论,以及S5PC100的SPI总线控制器的操作方法。

1. SPI总线协议理论

1.1  协议简介

SPI是英文Serial Peripheral Interface的缩写,该协议是由美国摩托罗拉公司推出的一种同步串行传输规范,首先由摩托罗拉公司在其MC68HCXX系列处理器上定义,后主要应用在 EEPROM、FLASH、实时时钟、AD转换器,还有数字信号处理器和数字信号解码器之间。

SPI是一种高速的全双工、同步的通信总线,并且在芯片的引脚上只占用四根线,节约了芯片的引脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。

1.2  协议内容

SPI有4个引脚:CS(从器件选择线)、SDO(串行数据输出线)、SDI(串行数据输入线)和SPICLK(同步串行时钟线)。

SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,这些脚的定义如下:

(1)SDO(MOSI)——主设备数据输出,从设备数据输入。

(2)SDI(MISO)——主设备数据输入,从设备数据输出。

(3)SPICLK——时钟信号,由主设备产生。

(4)CS——从设备使能信号,由主设备控制。

其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效。这就使在同一总线上连接多个SPI设备成为可能。其中总线协议时序如图1所示。


图1 SPI总线协议时序

接下来就是负责通信的3根线了。通信是通过数据交换完成的,这里先要知道SPI是串行通信协议,也就是说数据是一位一位地传输的。这就是SPICLK时钟线存在的原因,由SPICLK提供时钟脉冲,SDO、SDI则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取,完成一位数据传输,输入也使用同样原理。这样,在至少8次时钟信号的改变 (上沿和下沿为一次)后,就可以完成8位数据的传输。

要注意的是,SPICLK信号线只由主设备控制,从设备不能控制信号线。同样在一个基于SPI的设备中,至少有一个主控设备。这样传输的特点:这样的传输方式有一个优点,即其与普通的串行通信不同,普通的串行通信一次连续传送至少8位数据,而SPI允许数据一位一位地传送,甚至允许暂停,因为SPICLK时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。也就是说,主设备通过对SPICLK时钟线的控制可以完成对通信的控制。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义。

在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。在多个从设备的系统中,每个从设备需要独立地使能信号,在硬件上要比I2C总线控制稍微复杂一些。

注意:
        SPI的一个缺点是没有指定的流控制,没有应答机制确认是否接收到数据。

SPI 控制器为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大影响。如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样,如图2所示;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样,如图3所示。SPI主控制器和与之通信的外设时钟相位和极性应该一致,另外,上面提到这些特性将在寄存器中具体实现。


图2 CPHA=0时的情况


图3 CPHA=1时的情况

2  SPI控制器详解

2.1  S5PC100的SPI控制器简介

S5PC100包含了两套8位、16位、32位移位寄存器用于收发。在SPI传输数据时,数据的发送及接收数据是同步的,该总线控制器支持摩托罗拉串行外设接口。

下面是该控制器的特性:

全双工通信方式。

8位、16位、32位移位寄存器。

3时钟源供应。

支持8位、16位、32位总线接口。

支持摩托罗拉SPI协议。

支持两个独立的传输及接收FIFO。

支持主机模式及从机模式。

无法传送条件下接收。

Tx/Rx频率最大支持50MHz。

2.2 时钟源控制

每个SPI都能获得不同的3个时钟源,用户可以根据自己的需要来进行配置,需要设置CLK_CFG这个寄存器,后面将会介绍。

时钟控制器如图4所示。


图4 时钟控制器

2.3  寄存器详解

如表-1所示为SPI配置寄存器。

表1  SPI配置寄存器

MODE_CFGn描述复位值
CH_WIDTH[30:29]00 = 字节           01 = 半字
10 = 字          11 = 保留
0
TRAILING_CNT[28:19]接收FIFO中最后写入字节的个数0
BUS_WIDTH[18:17]00 = 字节           01 = 半字
10 = 字          11 = 保留
0

如表2所示为时钟配置寄存器。

表2  时钟配置寄存器

MODE_CFGn描述复位值
CH_WIDTH[30:29]00 = 字节           01 = 半字
10 = 字          11 = 保留
0
TRAILING_CNT[28:19]接收FIFO中最后写入字节的个数0
BUS_WIDTH[18:17]00 = 字节           01 = 半字
10 = 字          11 = 保留
0

如表3所示为SPI模式配置寄存器。

表3 SPI模式配置寄存器

CLK_CFGn描述复位值
SPI_CLKSEL[10:9]时钟源选择
00 = PCLK           01 = SCLK_SPI_48
10 = SCLK_SPI       11 = 保留
0
ENCLK[8]时钟使能
0 = 禁止           1 = 使能
0
SPI_SCALER[7:0]SPI时钟分频值
SPI 时钟输出 =时钟源 /(2 x(预分值 +1))
0

如表4所示为SPI数据发送寄存器。

表4 SPI数据发送寄存器

SPI_TX_DATAn描述复位值
TX_DATA[31:0]该寄存器包含了所要发送的数据0

表5所示为SPI数据接收寄存器。

表5 SPI数据接收寄存器

SPI_RX_DATAn描述复位值
RX_DATA[31:0]该寄存器包含了所要接收的数据0

如表6所示为SPI状态寄存器。

表6 SPI状态寄存器

SPI_STATUSn描述复位值
TX_DONE[21]0 = 其他情况
1 = 发送移位寄存器准备
0
RX_FIFO_LVL[19:13]RX FIFO 0 ~ 64 字节0
TX_FIFO_LVL[12:6]TX FIFO 0 ~ 64 字节0
RX_OVERRUN[5]Rx Fifo 溢出错误
0 =无误     1 = 溢出错
0
RX_UNDERRUN[4]0 = 无误     1 = 数据缺失0
TX_OVERRUN[3]Tx Fifo 溢出错误
0 =无误     1 = 溢出错
0
TX_UNDERRUN[2]0 = 无误     1 = 数据缺失0

3 SPI接口应用示例

这里将介绍一种通过SPI通信的Flash,该芯片是M24PXX,如图5所示为该芯片的原理图,每根接线的意义已经清楚地标识出来了。


图5 M25PXX原理图

这一款芯片内部集成了12条指令,包括了通用的读、写、配置等命令,还有一个内置的状态寄存器,可以通过该寄存器获取芯片当前状态。


基于S5PC100裸机程序之SPI(下)

3 SPI接口应用示例

这里将介绍一种通过SPI通信的Flash,该芯片是M24PXX,如图5所示为该芯片的原理图,每根接线的意义已经清楚地标识出来了。


图5 M25PXX原理图

这一款芯片内部集成了12条指令,包括了通用的读、写、配置等命令,还有一个内置的状态寄存器,可以通过该寄存器获取芯片当前状态。

如表7所示为M5DPXX芯片指令集。

表7 M25PXX芯片指令集

 Instrucion Description One-byle Instruction Code Address Bytes Dummy Bytes Date Bytes
 WREN 写使能 0000 0110 06h 0 0 0
 WRDI 写禁止 0000 0100 04h 0 0 0
 RDID 读取ID 1001 1111 9Fh 0 0 1 to 3
 RDSR 读状态寄存器 0000 0101 05h 0 0 1 to ∞
 WRSR 写状态寄存器 0000 0001 01h 0 0 1
 READ 字节数据读取 0000 0011 03h 3 0 1 to ∞
 FAST_READ 高速的字节读取 0000 1011 0Bh 3 1 1 to ∞
 PP 页编程 0000 0010 02h 3 0 1 to 256
 SE 扇区擦除 1101 1000 D8h 3 0 0
 BE 块擦除 1100 0111 C7h 0 0 0
 DP 深度擦除 1011 1001 B9h 0 0 0
 RES 从睡眠模式唤醒以及读出电特性 1010 1011 ABh 0 3 1 to ∞
 唤醒 0 0 0

有了上文的知识做铺垫,现在我们先来看一下SPI控制器的基本编程模型:

(1)设置时钟源并配置分频值等参数。

(2)软复位后,并设置SPI配置寄存器(SPI CONFIGURATION REGISTER)。

(3)设置模式寄存器。

(4)设置从机选择寄存器。

(5)收发数据。

根据以上信息,这里分成若干模块来逐一实现。

1.相关寄存器结构体定义及宏定义。

/*SPI总线控制器寄存器定义*/
        typedef struct {
               unsigned int CHCFG;
               unsigned int CLKCFG;
               unsigned int MODECFG;
               unsigned int SLAVESEL;
               unsigned int INTEN;
               unsigned int STATUS;
               unsigned int TXDATA;
               unsigned int RXDATA; 
               unsigned int PACKETCNT;
               unsigned int PENDINGCLR;
               unsigned int SWAPCFG;
               unsigned int FBCLK;
        }spi;
        #define SPI0 ( * (volatile spi *)0XEC300000 )
        /* Flash opcodes. */
        #define    OPCODE_WREN        0x06 /*写使能*/
        #define    OPCODE_WRDA        0x04 /*写禁止*/
        #define    OPCODE_RDSR        0x05 /*读状态寄存器*/
        #define    OPCODE_WRSR        0x01 /*写状态寄存器*/
        #define    OPCODE_NORM_READ   0x03 /*低频率的数据读取*/
        #define    OPCODE_FAST_READ   0x0b /*高频率的数据读取*/
        #define    OPCODE_PP          0x02 /*页编程*/
        #define    OPCODE_BE_4K       0x20 /* Erase 4KiB block */
        #define    OPCODE_BE_32K      0x52 /* Erase 32KiB block */
        #define    OPCODE_CHIP_ERASE  0xc7 /*擦除整块芯片*/
        #define    OPCODE_SE          0xd8 /*扇区擦除*/
        #define    OPCODE_RDID        0x9f /*读ID */
        /*状态寄存器*/
        #define    SR_WIP             1        /*写状态中*/
        #define    SR_WEL               2            /*写保护锁*/

2.延时函数,使能芯片及禁用芯片的实现如下。

void delay(int times)
        {
                volatile int i,j;
                for (j = 0; j < times; j++){
                      for (i = 0; i < 100000; i++);
                      i = i + 1;
                }
        }
        void disable_chip(void)
        { 
                /* disable chip*/
                SPI0.SLAVESEL |= 0x1;
                delay(1);
        }
        void enable_chip(void)
        { 
                /* enable chip*/
                SPI0.SLAVESEL &= ~0x1;
                delay(1);
        }

3.软件复位的代码如下

void soft_reset(void)
        {
                SPI0.CHCFG |= 0x1 << 5;    
                delay(1);
                SPI0.CHCFG &= ~(0x1 << 5);
        }

4.接收(字节读)的实现。

在实现读字节功能的时候,第一次发送读指令后,紧接着芯片就会回送数据,这时芯片会自动累加地址,因此需要人为控制所读数据的范围,读时序如图6所示。


图6 读时序

void receive(unsigned char *buf, int len)
        {
                int i;
                SPI0.CHCFG &= ~0x1; // disable Tx  
                SPI0.CHCFG |= 0x1 << 1; // enable Rx    
                delay(1);
                for (i = 0; i < len; i++){
                      buf[i] = SPI0.RXDATA;
                       //S5PC100 SPI支持在FiFo为空时,读缓存产生SPI时钟,否则需要发送数据产生时钟 
                      delay(1);
                }
                SPI0.CHCFG &= ~(0x1 << 1);
        }

5.发送(写页面)的实现。

在发出写指令后,要紧跟着发出第一个数据要存放的地址,这个时候再顺序写入数据,和读的时候一样,芯片会自动累加地址,芯片的页面为256Bytes,写时序如图7所示。


图7 写时序

关键代码如下。

void transfer(unsigned char *data, int len)
        {
                int i;
                SPI0.CHCFG &= ~(0x1 << 1);
                SPI0.CHCFG = SPI0.CHCFG | 0x1; // enable Tx and disable Rx
                delay(1);
                for (i = 0; i < len; i++){
                        SPI0.TXDATA = data[i];
                        while( !(SPI0.STATUS & (0x1 << 21)) );
                        delay(1);
                }
        SPI0.CHCFG &= ~0x1; 
        }

6.擦除芯片相关操作如下。

这块芯片提供了两种擦除方式,第一种是分扇区来进行擦除,重点在于选好指定的地址,然后进行擦除,结合如图8所示的时序图。


图8 擦除扇区时序图

第二种是整片芯片擦除,如图9所示为整片芯片的擦除时的时序情况。


图9 整片芯片擦除时序图

关键代码如下。

/*擦除扇区*/
        void erase_sector(int addr)
        {
                unsigned char buf[4];
                buf[0] = OPCODE_SE;
                buf[1] = addr >> 16;
                buf[2] = addr >> 8;
                buf[3] = addr;
                enable_chip();
                transfer(buf, 4);
                disable_chip();
        }
        /*擦除芯片*/
        void erase_chip()
        {
                unsigned char buf[4];
                buf[0] = OPCODE_CHIP_ERASE;
                enable_chip();
                transfer(buf, 1);
                disable_chip();
        }

7.解析状态。

判断芯片工作是否结束,如图10所示为状态寄存器读时序。


图10 状态寄存器读时序

关键代码如下。

void wait_till_write_finished()
        {
                unsigned char buf[1]; 
                enable_chip();
                buf[0] = OPCODE_RDSR;
                transfer(buf, 1); 
                while(1) {
                      receive(buf, 1);
                      if(buf[0] & SR_WIP) {
                              //   printf( "Write is still in progress\n" );
                      }
                      else {
                           printf( "Write is finished.\n" );
                           break;
                      }
                }
                disable_chip();
        }

8.读芯片ID。

读ID时序如图11所示。


图11 读ID时序

关键代码如下。

void read_ID(void)
        { 
                unsigned char buf[3];
                int i;
                buf[0] = OPCODE_RDID;
                soft_reset();
                enable_chip();
                transfer(buf, 1);
                receive(buf, 3);
                disable_chip();
                printf("MI = %x\tMT = %x\tMC = %x\t\n", buf[0], buf[1], buf[2]);
        }

9.主程序

int main()
        {
                unsigned char buf[10] = "home\n";
                unsigned char data[10] = "morning\n";
                uart0_init();
                printf("aaaaa \n");
                cfg_gpio();       //配置SPI IO功能 
                set_clk();        //使能SPI控制器的时钟 
                cfg_spi0();       //配置SPI0控制器 
                while(1)
                {
                      read_ID();               //读出SPI Flash的ID号 
                      write_spi(buf, 4, 0);  //向目标芯片0地址写入4个字节数据 
                      read_spi(data, 4, 0);  //从目标芯片0地址读出4个字节数据 
                      printf("read from spi :%s", data);
                }
                return 0;
        }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值