以LPC1778和ADE7878为例讲SPI的初始化及使用

 最近使用的AD采样芯片与主机采用的是SPI的通信方式,之前没接触过,在这里整理下驱动方法。

SPI简介

  SPI,Serial Peripheral Interface的缩写,串行外设接口,是一种高速的,全双工,同步的通信总线。
  采用主从工作模式,支持一个主机与多个从机通信,特殊的也可以多主机。由四根线组成,名称可能在不同的芯片上不一样但是功能是一样的。

  • SSEL 从机片选信号,用于指定与哪一个从机进行通信
  • SCLK 时钟信号,由主机提供
  • MOSI 主机输出,从机输入,用于主机向从机写指令数据
  • MISO 从机输出,主机输入,用于从机向主机发送数据

  下图为AD7878的SPI读操作时序图,关于时序图怎么看,在上一篇博客中已经介绍过。

ADE7878 SPI读操作时序图

主机SPI初始化

  在LPC1778的库函数中提供了SPI的结构体,结构体中各成员用于规定SPI通信的细节(事实上,调用库函数在前期对编程不太熟悉的时候比较方便,但是库函数的定义往往要进行很多次跳转,让人看起来很晕,直接使用寄存器倒是显得简洁高效许多)。

1
2
3
4
5
6
7
8
typedef struct {
uint32_t Databit; //数据传输位数
uint32_t CPHA; //读取数据是在时钟电平的第一个跳变还是第二个跳变
uint32_t CPOL; //在没有时钟信号时,处于高电平还是低电平
uint32_t Mode; //工作模式是主机还是从机
uint32_t FrameFormat; //帧格式,Motorola、TI、National Microwire
uint32_t ClockRate; //时钟频率
} SSP_CFG_Type;

  首先对管脚进行初始化,LPC1778的管脚存在复用,需要选择管脚功能为SPI所需的管脚,由于需要根据时序图人为改变SSEL的电平,所以其对应管脚功能选择为GPIO而不是SSEL。之后按照需要的配置将上述结构体中的成员赋值,使能主机的SPI即完成主机的SPI初始化。

1
2
3
4
5
6
7
8
// CPOL = SSP_CPOL_LO; CPHA = SSP_CPHA_SECOND; ClockRate = 1843200; Databit=SSP_DATABIT_8; FrameFormat = SSP_FRAME_SPI
SSP_ConfigStructInit(&SSP_ConfigStruct);
SSP_ConfigStruct.ClockRate = 1843200;
SSP_ConfigStruct.Databit = SSP_DATABIT_8;
SSP_ConfigStruct.CPOL = SSP_CPOL_LO;
SSP_ConfigStruct.CPHA = SSP_CPHA_SECOND;
SSP_ConfigStruct.FrameFormat=SSP_FRAME_SPI;
SSP_Init(LPC_SSP0, &SSP_ConfigStruct);

从机SPI初始化

  如果从机也是类似于LPC1778这样的单片机,按照上述流程修改一些地方就行。不过此处使用的是ADE7878采样芯片,其SPI初始化比较特殊,需要根据数据手册上的说明来进行。

  通过查阅手册,为了激活ADE7878的SPI端口,需要将其SS/HAS管脚由高电平切到低电平3次。通过查阅官网提供的示例程序,可以修改成适用于自己项目的程序。以下为官网提供的示例代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void ADE7878SPICfg(void)
{
SET_PIN00();
SPIDelay();
CLR_PIN00(); //toggle once
SPIDelay();
SET_PIN00();
SPIDelay();
CLR_PIN00(); //toggle twice
SPIDelay();
SET_PIN00();
SPIDelay();
CLR_PIN00(); //toggle triple
SPIDelay();
SET_PIN00();
SPIDelay();
SPIWrite1Byte(0xEC01, 0x02); //通过向CONFIG2寄存器写入指令来启动ADE7878
}

编写读写函数

  读函数需要从指定地址读取数据,传入参数为要读取的地址。根据时序图,需要先写入0x01命令字节,再写入两个字节的地址,随后每次写入一个字节的0x00,读取1个字节数据,重复4次。在实际编写中发现,MOSI上发送一个字节的数据就会有一个字节的数据在MISO上发出,因此需要将写入命令和地址这3个字节得到的数据清除。根据官网的示例代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
uint32_t Read_ADE7878(uint16_t add)
{
uint8_t Rx_buf[4]; // 接收4个字节数据
uint8_t Tx_buf[3];
uint8_t i=0;
volatile uint8_t tempbuffer; // 用于清除前三个字节,否则数据有误
GPIO_ClearValue(ADE_SEL_PORT, ADE_SEL_MASK); // 片选使能
Tx_buf[0]=0x01;
Tx_buf[1]=add>>8;
Tx_buf[2]=add;
for(i=0;i<3;i++)
{
SSP_SendData(LPC_SSP0,Tx_buf[i]); //写入0x01和地址
SPI_Delay(150);
tempbuffer=(uint8_t)SSP_ReceiveData(LPC_SSP0); //接收清除MISO的反馈数据
}
for(i=0;i<4;i++)
{
SSP_SendData(LPC_SSP0,0x00); //写入0x00
SPI_Delay(150);
Rx_buf[i]=(uint8_t)SSP_ReceiveData(LPC_SSP0); //接收有用的MISO数据
}
GPIO_SetValue(ADE_SEL_PORT, ADE_SEL_MASK); // 片选失能
return((uint32_t)Rx_buf[0] << 24) | ((uint32_t)Rx_buf[1] << 16) | ((uint32_t)Rx_buf[2] << 8) | ((uint32_t)Rx_buf[3] << 0);
}

  写函数向ADE7878的指定地址发送数据即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void Write_ADE7878(uint16_t address, uint32_t value)
{
uint8_t Tx_buf[7]; // 用于发送7个字节数据
uint8_t i=0;
GPIO_ClearValue(ADE_SEL_PORT, ADE_SEL_MASK); // 片选使能
Tx_buf[0] = 0x00;
Tx_buf[1] = address>>8; // 先写入地址,再写入数据
Tx_buf[2] = address;
Tx_buf[3] = (uint8_t) ((value>>24)&0xFF);
Tx_buf[4] = (uint8_t) ((value>>16)&0xFF);
Tx_buf[5] = (uint8_t) ((value>>8)&0xFF);
Tx_buf[6] = (uint8_t) ((value>>0)&0xFF);
for(i=0;i<7;i++)
{
SSP_SendData(LPC_SSP0,Tx_buf[i]);
SPI_Delay(150);
}
GPIO_SetValue(ADE_SEL_PORT, ADE_SEL_MASK); // 片选失能
}

结果

  通过示波器同时测量SCLK和MOSI,以及同时测量SCLK和MISO,可以发现数据按照SPI初始化中的要求进行传输,SPI通信正常工作。

|