一、硬件接线
1、普通SPI设备接线
如NRF24L01,可以直接连接IO
2、FLASH设备接线
如GD25Q80BSIG,需要加上拉电阻
二、程序编写
1、和SPI相关的寄存器
① SPCTL寄存器
② SPSTAT寄存器
③ SPDAT寄存器
④ AUXR1/P_SW1寄存器
2、寄存器,数据类型重定义
sfr P_SW1 = 0xA2; //外设功能切换寄存器1
sfr SPSTAT = 0xCD; //SPI状态寄存器
sfr SPCTL = 0xCE; //SPI控制寄存器
sfr SPDAT = 0xCF; //SPI数据寄存器
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
3、寄存器相关位宏定义, CS引脚定义
#define SPI_S0 0x04
#define SPI_S1 0x08
#define SPIF 0x80 //SPSTAT.7
#define WCOL 0x40 //SPSTAT.6
#define SSIG 0x80 //SPCTL.7
#define SPEN 0x40 //SPCTL.6
#define DORD 0x20 //SPCTL.5
#define MSTR 0x10 //SPCTL.4
#define CPOL 0x08 //SPCTL.3
#define CPHA 0x04 //SPCTL.2
#define SPDHH 0x00 //CPU_CLK/4
#define SPDH 0x01 //CPU_CLK/16
#define SPDL 0x02 //CPU_CLK/64
#define SPDLL 0x03 //CPU_CLK/128
sbit SS_1 = P1^2; //SPI_1的CS脚
sbit SS_2 = P2^4; //SPI_2的CS脚
4、SPI初始化代码
void InitSPI_1(void)
{
uchar temp;
temp = P_SW1; //切换到第一组SPI
temp &= ~(SPI_S0 | SPI_S1); //SPI_S0=0 SPI_S1=0
P_SW1 = temp; //(P1.2/SS, P1.3/MOSI, P1.4/MISO, P1.5/SCLK)
// temp = P_SW1; //切换到第二组SPI
// temp &= ~(SPI_S0 | SPI_S1); //SPI_S0=1 SPI_S1=0
// temp |= SPI_S0; //(P2.4/SS_2, P2.3/MOSI_2, P2.2/MISO_2, P2.1/SCLK_2)
// P_SW1 = temp;
// temp = P_SW1; //切换到第三组SPI
// temp &= ~(SPI_S0 | SPI_S1); //SPI_S0=0 SPI_S1=1
// temp |= SPI_S1; //(P5.4/SS_3, P4.0/MOSI_3, P4.1/MISO_3, P4.3/SCLK_3)
// P_SW1 = temp;
SPDAT = 0; //初始化SPI数据
SPSTAT = SPIF | WCOL; //清除SPI状态位
SPCTL = SPEN | MSTR | SSIG | SPDLL; //主机模式
}
void InitSPI_2(void)
{
uchar temp;
// temp = P_SW1; //切换到第一组SPI
// temp &= ~(SPI_S0 | SPI_S1); //SPI_S0=0 SPI_S1=0
// P_SW1 = temp; //(P1.2/SS, P1.3/MOSI, P1.4/MISO, P1.5/SCLK)
temp = P_SW1; //切换到第二组SPI
temp &= ~(SPI_S0 | SPI_S1); //SPI_S0=1 SPI_S1=0
temp |= SPI_S0; //(P2.4/SS_2, P2.3/MOSI_2, P2.2/MISO_2, P2.1/SCLK_2)
P_SW1 = temp;
// temp = P_SW1; //切换到第三组SPI
// temp &= ~(SPI_S0 | SPI_S1); //SPI_S0=0 SPI_S1=1
// temp |= SPI_S1; //(P5.4/SS_3, P4.0/MOSI_3, P4.1/MISO_3, P4.3/SCLK_3)
// P_SW1 = temp;
SPDAT = 0; //初始化SPI数据
SPSTAT = SPIF | WCOL; //清除SPI状态位
SPCTL = SPEN | MSTR | SSIG | SPDLL; //主机模式
}
5、SPI数据交换代码
uchar SPISwap(uchar dat)
{
SPDAT = dat; //触发SPI发送数据
while (!(SPSTAT & SPIF)); //等待发送完成
SPSTAT = SPIF | WCOL; //清除SPI状态位
return SPDAT; //返回SPI数据
}
6、NRF24L01读写例程
//NRF24L01相关宏定义
#define NOP 0xFF //空操作
#define READ_REG 0x00
#define WRITE_REG 0x20
#define TX_ADDR 0x10
sbit CE = P2^5;
sbit IRQ = P3^2; //INT0
//SPI写寄存器
//reg:指定寄存器地址
//value:写入的值
uchar SPI_RW_Reg(uchar reg, uchar value)
{
uchar status;
SS_2 = 0; // 使能SPI传输
status = SPISwap(reg); //返回从MISO读出的数据,status应为上次向该寄存器内写的value
SPISwap(value); //写入寄存器的值
SS_2 = 1; // 禁止SPI传输
return status; // 返回状态值
}
//读取SPI寄存器值
//reg:要读的寄存器
uchar SPI_Read(uchar reg)
{
uchar reg_val;
SS_2 = 0; // 使能SPI传输
SPISwap(reg); // 发送寄存器号
reg_val = SPISwap(NOP); // 读取寄存器内容
SS_2 = 1; // 禁止SPI传输
return reg_val; // 返回状态值
}
//在指定位置写指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//bytes:数据长度
//返回值,此次读到的状态寄存器值
uchar SPI_Write_Buf(uchar reg, uchar *pBuf, uchar bytes)
{
uchar status, byte_ctr;
SS_2 = 0; // 使能SPI传输
status = SPISwap(reg);// 发送寄存器值(位置),并读取状态值
for(byte_ctr = 0; byte_ctr < bytes; byte_ctr++){ // 写入数据
SPISwap(*pBuf++);
}
SS_2 = 1;//关闭SPI传输
return status; // 返回读到的状态值
}
//在指定位置读出指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//bytes:数据长度
//返回值,此次读到的状态寄存器值
uchar SPI_Read_Buf(uchar reg, uchar *pBuf, uchar bytes)
{
uchar status, byte_ctr;
SS_2 = 0; // 使能SPI传输
status = SPISwap(reg); // 发送寄存器值(位置),并读取状态值
for(byte_ctr = 0; byte_ctr < bytes; byte_ctr++){
pBuf[byte_ctr] = SPISwap(NOP); // 读出数据
}
SS_2 = 1; // 关闭SPI传输
return status; // 返回读到的状态值
}
//检测24L01是否存在
//返回值:0,成功;1,失败
uchar NRF24L01_Check(void)
{
uchar buf[5] = {0xA5, 0xA5, 0xA5, 0xA5, 0xA5};
uchar buf1[5];
uchar i;
CE = 0;
SPI_Write_Buf(WRITE_REG + TX_ADDR, buf, 5);
SPI_Read_Buf(TX_ADDR, buf1, 5); //读出写入的地址
CE = 1;
for(i = 0; i < 5; i++)
if(buf1[i] != 0xA5)
break;
if(i != 5)
return 1;//检测24L01错误
return 0; //检测到24L01
}
//主函数
void main(void)
{
Init_Uart();
EA = 1; //开总中断
InitSPI_2();
NRF24L01_Check(); //切换SPI后需要读多几次,等待SPI稳定
NRF24L01_Check();
if(!NRF24L01_Check()){
SendString("NRF24L01 Checked OK!rn");
}
else{
SendString("NRF24L01 Checked Fail!rn");
}
while(1);
}
7、GD25Q80BSIG读写例程
//GD25Q80BSIG相关宏定义
#define NOP 0xFF //空操作
#define Write_Enable 0x06 //写使能
#define Write_Disable 0x04 //写禁能
#define Read_Status_Register 0x05 //读前八位状态寄存(S7-S0)
#define Read_Status_Register_1 0x35 //读后八位状态寄存(S15-S8)
#define Read_Data 0x03 //读数据
#define Page_Program 0x02 //页面编程,256字节
#define Chip_Erase_1 0xC7 //芯片擦除命令1
#define Chip_Erase_2 0x60 //芯片擦除命令2
#define Read_Identification 0x9F //读取标识命令允许读取8位制造商标识,然后是两个字节的设备标识。
sbit WP = P1^6; //写保护,低电平有效
//写使能
void Write_Enable_Cmd(void)
{
SS_1 = 0;
SPISwap(Write_Enable);
SS_1 = 1;
}
//写禁能
void Write_Disable_Cmd(void)
{
SS_1 = 0;
SPISwap(Write_Disable);
SS_1 = 1;
}
//读状态寄存器前八位
uchar Read_Status_Register_Sta(void)
{
uchar sta;
SS_1 = 0;
SPISwap(Read_Status_Register);
sta = SPISwap(NOP);
SS_1 = 1;
return sta;
}
//读数据
void Read_Data_Cmd(uchar ad1, uchar ad2, uchar ad3, uchar *dat, uint len)
{
uchar i, cmd[4];
cmd[0] = Read_Data;
cmd[1] = ad1;
cmd[2] = ad2;
cmd[3] = ad3;
SS_1 = 0;
for(i = 0; i < 4; i++){
SPISwap(cmd[i]);
}
for(i = 0; i < len; i++){
*dat++ = SPISwap(NOP);
}
SS_1 = 1;
}
//页编程,输入24位起始地址
void Page_Program_Cmd(uchar ad1, uchar ad2, uchar ad3, uchar *dat, uint len)
{
uchar i, cmd[4];
uint count = 0, temp = 0;
cmd[0] = Page_Program;
cmd[1] = ad1;
cmd[2] = ad2;
cmd[3] = ad3;
temp = 256 - ad3; //一次最多写256字节,超过的写进下一页
Write_Enable_Cmd(); //写使能
SS_1 = 0;
for(i = 0; i < 4; i++){
SPISwap(cmd[i]);
}
for(i = 0; i < temp; i++){
SPISwap(*dat++);
}
SS_1 = 1;
while(Read_Status_Register_Sta() & 0x01); //等待写入完毕
if(len > temp){ //需要写入的数据长度超过当前页,超过的写进下一页
cmd[0] = Page_Program;
cmd[1] = ad1;
cmd[2] = ad2 + 1; //超过的写进下一页
cmd[3] = 0;
temp = len - temp;
Write_Enable_Cmd();
SS_1 = 0;
for(i = 0; i < 4; i++){
SPISwap(cmd[i]);
}
for(i = 0; i < temp; i++){
SPISwap(*dat++);
}
SS_1 = 1;
while(Read_Status_Register_Sta() & 0x01);
}
}
//芯片擦除
void Chip_Erase_1_Cmd(void)
{
Write_Enable_Cmd();
SS_1 = 0;
SPISwap(Chip_Erase_2);
SS_1 = 1;
while(Read_Status_Register_Sta() & 0x01);
}
//读ID
void Read_Identification_Sta(uchar *rdid)
{
uchar i;
SS_1 = 0;
SPISwap(Read_Identification);
for(i = 0; i < 3; i++){
*rdid++ = SPISwap(NOP);
}
SS_1 = 1;
}
//16进制转字符串输出
void HexToAscii(uchar *pHex, uchar *pAscii, uchar nLen)
{
uchar Nibble[2];
uint i,j;
for (i = 0; i < nLen; i++){
Nibble[0] = (pHex[i] & 0xF0) >> 4;
Nibble[1] = pHex[i] & 0x0F;
for (j = 0; j < 2; j++){
if (Nibble[j] < 10){
Nibble[j] += 0x30;
}
else{
if (Nibble[j] < 16)
Nibble[j] = Nibble[j] - 10 + 'A';
}
*pAscii++ = Nibble[j];
} // for (int j = ...)
} // for (int i = ...)
*pAscii++ = '