SPI 协议简介
SPI 是串行外设接口(Serial Peripheral Interface)的缩写,是美国摩托罗拉公司(Motorola)最先推出的一种同步串行传输规范,是一种高速、全双工、同步通信总线,可以在同一时间发送和接收数据,SPI没有定义速度限制,通常能达到甚至超过10M/bps。
SPI 有主、从两种模式,通常由一个主模块和一个或多个从模块组成(SPI 不支持多主机),主模块选择一个从模块进行同步通信,从而完成数据的交换。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起,当存在多个从设备时,通过各自的片选信号进行管理。
SPI 接口介绍
- SCK:串行时钟信号,由主设备产生
- CS/SS:片选信号,由主设备产生,用来控制从设备
- MOSI:主设备数据输出,从设备数据输入
- MISO:主设备数据输入,从设备数据输出
SPI 四种模式
SPI 根据时钟极性和时钟相位的不同可以有 4 种工作模式。
- 时钟极性(CPOL)指通讯设备处于空闲状态(SPI 开始通讯前、CS 线无效)时 SCK 的状态。
- CPOL = 0:SCK在空闲时为低电平
- CPOL = 1:SCK在空闲时为高电平
- 时钟相位(CPHA)指数据的采样时刻位于 SCK 的偶数边沿采样还是奇数边沿采样。
- CPHA = 0:在SCK的奇数边沿采样
- CPHA = 1:在SCK的偶数边沿采样
通过时钟极性和时钟相位的不同组合 SPI 总共可以设置为4种工作模式
- MODE0 : CPOL = 0 CPHA = 0 SCK空闲为低,SCK的奇数次边沿(上升沿)采样
- MODE1 : CPOL = 0 CPHA = 1 SCK空闲为低,SCK的偶数次边沿(下降沿)采样
- MODE2 : CPOL = 1 CPHA = 0 SCK空闲为高,SCK的奇数次边沿(下降沿)采样
- MODE3 : CPOL = 1 CPHA = 1 SCK空闲为高,SCK的偶数次边沿(上升沿)采样
ZYNQ EMIO
EMIO 是扩展的 MIO,ZYNQ 支持通过配置将 PS 的控制器信号通过 EMIO 输出,即 EMIO 是在 PL 侧连接使用 PS 侧资源的扩展通道接口。可扩展到 PIN 上,也可以扩展到运用上。EMIO 与 MIO 一样归属于 GPIO,即经过扩展 PS 一共可以控制 54(MIO) + 64(EMIO) = 118 个引脚。
模拟驱动示例
- spi_ctrl.c
/** * Copyright (c) 2022-2023,HelloAlpha * * Change Logs: * Date Author Notes */ #include "spi_ctrl.h" /* CPOL = 0, CPHA = 0, MSB first */ uint8_t SOFT_SPI_RW_MODE0(uint8_t write_dat) { uint8_t i, read_dat; for( i = 0; i < 8; i++ ) { if( write_dat & 0x80 ) MOSI_H(); else MOSI_L(); write_dat <<= 1; SPI_DELAY(1); SCK_H(); read_dat <<= 1; if( MISO() ) read_dat++; SPI_DELAY(1); SCK_L(); SPI_DELAY(1); } return read_dat; } /* CPOL=0,CPHA=1, MSB first */ uint8_t SOFT_SPI_RW_MODE1(uint8_t write_dat) { uint8_t i, read_dat; for( i = 0; i < 8; i++ ) { SCK_H(); if( write_dat & 0x80 ) MOSI_H(); else MOSI_L(); write_dat <<= 1; SPI_DELAY(100); SCK_L(); read_dat <<= 1; if(MISO()) read_dat++; SPI_DELAY(100); } return read_dat; } /* CPOL=1,CPHA=0, MSB first */ uint8_t SOFT_SPI_RW_MODE2(uint8_t write_dat) { uint8_t i, read_dat; for( i = 0; i < 8; i++ ) { if( write_dat & 0x80 ) MOSI_H(); else MOSI_L(); write_dat <<= 1; SPI_DELAY(1); SCK_L(); read_dat <<= 1; if(MISO()) read_dat++; SPI_DELAY(1); SCK_H(); } return read_dat; } /* CPOL = 1, CPHA = 1, MSB first */ uint8_t SOFT_SPI_RW_MODE3( uint8_t write_dat ) { uint8_t i, read_dat; for( i = 0; i < 8; i++ ) { SCK_L(); if( write_dat & 0x80 ) MOSI_H(); else MOSI_L(); write_dat <<= 1; SPI_DELAY(1); SCK_H(); read_dat <<= 1; if( MISO() ) read_dat++; SPI_DELAY(1); } return read_dat; }
- spi_ctrl.h
/** * Copyright (c) 2022-2023,HelloAlpha * * Change Logs: * Date Author Notes */ #ifndef __SPI_CTRL_H__ #define __SPI_CTRL_H__ #include "spi_io.h" #include "sleep.h" #define SPI_DELAY(...) usleep(__VA_ARGS__) #define SPI_START_COMMUNICATION NSS_L() #define SPI_STOP_COMMUNICATION NSS_H() /** * CPOL 配置 SPI 总线的极性 * CPHA 配置 SPI 总线的相位 * * 模式0:CPOL=0,CPHA =0 MSB first * SCK空闲为低电平,数据在SCK的上升沿被采样(提取数据) * 模式1:CPOL=0,CPHA =1 MSB first * SCK空闲为低电平,数据在SCK的下降沿被采样(提取数据) * 模式2:CPOL=1,CPHA =0 MSB first * SCK空闲为高电平,数据在SCK的下降沿被采样(提取数据) * 模式3:CPOL=1,CPHA =1 MSB first * SCK空闲为高电平,数据在SCK的上升沿被采样(提取数据) */ /* CPOL = 0, CPHA = 0, MSB first*/ uint8_t SOFT_SPI_RW_MODE0(uint8_t write_dat); /* CPOL=0,CPHA=1, MSB first */ uint8_t SOFT_SPI_RW_MODE1(uint8_t write_dat); /* CPOL=1,CPHA=0, MSB first */ uint8_t SOFT_SPI_RW_MODE2(uint8_t write_dat); /* CPOL = 1, CPHA = 1, MSB first */ uint8_t SOFT_SPI_RW_MODE3( uint8_t write_dat ); #endif
- spi_io.c
/** * Copyright (c) 2022-2023,HelloAlpha * * Change Logs: * Date Author Notes */ #include "spi_io.h" extern XGpioPs GpioPs; static void SET_PIN_OUT(uint32_t PIN) { XGpioPs_SetDirectionPin(&GpioPs, PIN, GPIO_MODEL_OUTPUT); XGpioPs_SetOutputEnablePin(&GpioPs, PIN, GPIO_OUTPUT_ENABLE); } void MOSI_H(void) { SET_PIN_OUT(SPI_MOSI_PIN); XGpioPs_WritePin(&GpioPs, SPI_MOSI_PIN, GPIO_SET); } void MOSI_L(void) { SET_PIN_OUT(SPI_MOSI_PIN); XGpioPs_WritePin(&GpioPs, SPI_MOSI_PIN, GPIO_RESET); } void SCK_H(void) { SET_PIN_OUT(SPI_SCK_PIN); XGpioPs_WritePin(&GpioPs, SPI_SCK_PIN, GPIO_SET); } void SCK_L(void) { SET_PIN_OUT(SPI_SCK_PIN); XGpioPs_WritePin(&GpioPs, SPI_SCK_PIN, GPIO_RESET); } uint32_t MISO(void) { XGpioPs_SetDirectionPin(&GpioPs, SPI_MISO_PIN, GPIO_MODEL_INPUT); return XGpioPs_ReadPin(&GpioPs, SPI_MISO_PIN); } void NSS_H(void) { SET_PIN_OUT(SPI_NSS_PIN); XGpioPs_WritePin(&GpioPs, SPI_NSS_PIN, GPIO_SET); } void NSS_L(void) { SET_PIN_OUT(SPI_NSS_PIN); XGpioPs_WritePin(&GpioPs, SPI_NSS_PIN, GPIO_RESET); }
- spi_io.h
/** * Copyright (c) 2022-2023,HelloAlpha * * Change Logs: * Date Author Notes */ #ifndef __SPI_IO_H__ #define __SPI_IO_H__ #include "xgpiops.h" #define SPI_SCK_PIN 54 #define SPI_MOSI_PIN 55 #define SPI_MISO_PIN 56 #define SPI_NSS_PIN 57 #define GPIO_MODEL_INPUT 0 #define GPIO_MODEL_OUTPUT 1 #define GPIO_OUTPUT_DISABLE 0 #define GPIO_OUTPUT_ENABLE 1 #define GPIO_RESET 0 #define GPIO_SET 1 void MOSI_H(void); void MOSI_L(void); void SCK_H(void); void SCK_L(void); uint32_t MISO(void); void NSS_H(void); void NSS_L(void); #endif