一、SPI控制器简介
SPI 接口是 Motorola 首先提出的全双工三线同步串行外围接口, 采用主从模式(MasterSlave) 架构; 支持多 slave模式应用, 一般仅支持单 Master。 时钟由 Master 控制, 在时钟移位脉冲下, 数据按位传输, 高位在前, 低位在后(MSBfirst) ; SPI 接口有 2 根单向数据线, 为全双工通信。
1.SPI 接口
共有 4 根信号线, 分别是: 设备选择线、 时钟线、 串行输出数据线、 串行输入数据线。
(1) MOSI: 主器件数据输出, 从器件数据输入
(2) MISO: 主器件数据输入, 从器件数据输出
(3) SCLK: 时钟信号, 由主器件产生
(4) /SS: 从器件使能信号, 由主器件控制
2.时钟极性和时钟相位
时钟的极性(CPOL)。决定空闲时时钟时低电平还是高电平。当时钟极性为 0 时(CPOL=0) , SCK 信号线在空闲时为低电平; 当时钟极性为 1 时(CPOL=1) , SCK 信号线在空闲时为高电平;
时钟的相位(CPHA)。当时钟相位为 1 时(CPHA=1) , 在 SCK 信号线的第二个跳变沿进行采样。为0的时候是在第一个跳变沿采样
3. ZYNQ的SPI控制器
ZYNQ中有两个SPI控制器(SPI0和SPI1),每个控制器有三个片选信号,也可3-8译码获得八个片选信号。既可以做从机又可以做主机。他的引脚:SS/SCLK/MOSI和MISO,可以映射到MIO引脚上,也可以映射到EMIO引脚上(就可以根据需要调试)
二、在ZYNQ中使用SPI控制器的方法
1.在vivado中配置
在vivado中程配置IP核时,勾选SPI0。同时可以选择映射到MIO还是EMIO上。这里选择EMIO,就可以把SPI控制器的线引到特定的管脚,方便调试。
设置完IP核之后要连线,检查是否有错误,然后保存——生成OUTPUT文件——生成HDL文件。映射到EMIO的时候,要进行管脚约束,确定映射到哪一个EMIO上。设置管脚约束的方法可以在正点原子的嵌入式开发指南中的EMIO教程视频当中看到。
设置完以后要生成比特流文件。然后导出XSA文件。注意要记录下来SPI控制器的哪个脚,引射到了哪个EMIO上面。当使用了更多EMIO的时候,也要记录下来EMIO[0]对应了哪个引脚,EMIO[1]对应了哪个引脚等等……方便后面编程的时候能正确对应起来
2.在vitis中编程
将刚才生成的xsa文件导入,在vitis中编写程序
(1)添加spi控制器的相关代码
介绍轮询方式:
首先添加文件命名为spi_ps.c文件
#include "spi_ps.h"
XSpiPs SpiInstance;
/* 发送和接收的缓冲区 */
u8 *ReadBuf = (void*) 0x08000000 ;
u8 *SendBuf = (void*) 0x08100000 ;
/* -----------------spi初始化函数--------------------------- */
int SpiPs_Init(XSpiPs *SpiInstancePtr,u16 SpiDeviceId )
{
int Status;
XSpiPs_Config *SpiConfig;
//根据器件ID查找配置信息
SpiConfig = XSpiPs_LookupConfig(SpiDeviceId);
if (NULL == SpiConfig) {
return XST_FAILURE;
}
/* 初始化函数 */
Status = XSpiPs_CfgInitialize((SpiInstancePtr), SpiConfig,
SpiConfig->BaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/* 配置SPI的工作模式
* 第一个参数是XSpiPs 实例
* 第二个参数设置为主机模式, */
Status = XSpiPs_SetOptions(SpiInstancePtr, XSPIPS_MASTER_OPTION | XSPIPS_FORCE_SSELECT_OPTION );
/* 配置SPI时钟的分频参数 */
Status = XSpiPs_SetClkPrescaler(SpiInstancePtr, XSPIPS_CLK_PRESCALE_256);
/* 配置被选择的从设备 */
XSpiPs_SetSlaveSelect(SpiInstancePtr, 0x00);
return XST_SUCCESS;
}
/* -------------------SPI的发送函数------------- */
void SpiPs_Send(XSpiPs *SpiInstancePtr,u8 *SendBuffer, int ByteCount)
{
/* 原函数形式
* s32 XSpiPs_PolledTransfer(XSpiPs *InstancePtr, u8 *SendBufPtr,u8 *RecvBufPtr, u32 ByteCount)
* 其中 第二个参数 SendBufPtr 是发送的数据的指针,指向要发送数据的地址了
* 第三个参数 RecvBufPtr 是接收的数据,
* ByteCount 是之要发送的字节数
* 这个属于轮询模式 */
XSpiPs_PolledTransfer(SpiInstancePtr, SendBuffer, ReadBuf,ByteCount);
}
/* -------------------SPI的读取函数------------- */
void SpiPs_Read(XSpiPs *SpiInstancePtr,u8 *ReadBuffer,int ByteCount)
{
XSpiPs_PolledTransfer(SpiInstancePtr, SendBuf, ReadBuffer,ByteCount);
}
其中,这一句配置SPI的工作模式,包括主机模式、手动片选还是自动片选、手动开始传输还是自动开始传输、时钟相位和时钟极性。要配置哪一个,就把对应的写到第二个参数的位置上。
Status = XSpiPs_SetOptions(SpiInstancePtr, XSPIPS_MASTER_OPTION | XSPIPS_FORCE_SSELECT_OPTION );
可以跳转进这个函数,能看到这个配置表,需要加哪个就把哪个复制进去。
typedef struct {
u32 Option;
u32 Mask;
} OptionsMap;
static OptionsMap OptionsTable[] = {
{XSPIPS_MASTER_OPTION, XSPIPS_CR_MSTREN_MASK},
{XSPIPS_CLK_ACTIVE_LOW_OPTION, XSPIPS_CR_CPOL_MASK},
{XSPIPS_CLK_PHASE_1_OPTION, XSPIPS_CR_CPHA_MASK},
{XSPIPS_DECODE_SSELECT_OPTION, XSPIPS_CR_SSDECEN_MASK},
{XSPIPS_FORCE_SSELECT_OPTION, XSPIPS_CR_SSFORCE_MASK},
{XSPIPS_MANUAL_START_OPTION, XSPIPS_CR_MANSTRTEN_MASK}
};
比如,要想设置时钟极性为1,就把这个函数改写成:
Status = XSpiPs_SetOptions(SpiInstancePtr, XSPIPS_MASTER_OPTION | XSPIPS_FORCE_SSELECT_OPTION | XSPIPS_CLK_ACTIVE_LOW_OPTION);
第二个分频参数:SPI的时钟是166.66MHz,分频后的时钟就是SPI控制器做主机时的时钟频率。比如最大256分频,这时候时钟频率就是大概是651KHz左右。
然后设置片选的从设备。这个功能我没有使用,但是应该是用哪个片选就写哪个,从0到2编号
然后后面的是轮询模式下发送和接收的函数。可以直接调用。
然后再添加spi_ps.h的文件:
#ifndef SRC_SPIPS_H_
#define SRC_SPIPS_H_
#include "xparameters.h" /* EDK generated parameters */
#include "xspips.h" /* SPI device driver */
#include "xscugic.h" /* Interrupt controller device driver */
#include "xil_exception.h"
#include "xil_printf.h"
void SpiPs_Read(XSpiPs *SpiInstancePtr,u8 *ReadBuffer,int ByteCount);
void SpiPs_Send(XSpiPs *SpiInstancePtr,u8 *SendBuffer, int ByteCount);
int SpiPs_Init(XSpiPs *SpiInstancePtr,u16 SpiDeviceId );
#define SPI_DEVICE_ID XPAR_XSPIPS_0_DEVICE_ID
#define MAX_DATA 100
#endif /* SRC_SPIPS_H_ */
具体要用SPI发送数据的时候,第一步先把要发的数据写到sendbuf缓冲区里面,然后调用send函数即可。比如这样:
SendBuf[0] = RegAddr;
for(t=0;t<Len;t++)
{
SendBuf[t+1] = Data[t];
}
SpiPs_Send(&SpiInstance,SendBuf,Len+1);
(部分内容来自米联客开发指南)