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;
}