目录
CPOL = 0, CPHA = 0, first 模式下读写多字节数据
总述
SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps
信号线
SPI接口一般使用四条信号线通信:SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)
MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
SCLK:串行时钟信号,由主设备产生。
CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
通信模式和时序
时钟极性(CPOL)定义了时钟空闲状态电平:
CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时
CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时
时钟相位(CPHA)定义数据的采集时间。
CPHA=0,在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样,在第2个边沿发送数据
CPHA=1,在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样,在第1个边沿发送数据
通信模式
Mode0:CPOL=0,CPHA=0
此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿(准备数据),(发送数据)数据发送是在下降沿。
Mode1:CPOL=0,CPHA=1
此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是SCK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
Mode2:CPOL=1,CPHA=0
此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
Mode3:CPOL=1,CPHA=1
此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
通信时序
软件SPI
初始化代码
/**SPI1 GPIO Configuration
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PA7 ------> SPI1_MOSI
*/
#define SPI_SCK_PIN GPIO_PIN_5
#define SPI_SCK_GPIO_PORT GPIOA
#define SPI_MOSI_PIN GPIO_PIN_7
#define SPI_MOSI_GPIO_PORT GPIOA
#define SPI_MISO_PIN GPIO_PIN_6
#define SPI_MISO_GPIO_PORT GPIOA
#define SPI_NSS_PIN GPIO_PIN_14
#define SPI_NSS_GPIO_PORT GPIOD
#define SPI_SCK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_MISO_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_MOSI_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_NSS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()
#define MOSI_H HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_SET)
#define MOSI_L HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_RESET)
#define SCK_H HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_SET)
#define SCK_L HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_RESET)
#define MISO HAL_GPIO_ReadPin(SPI_MISO_GPIO_PORT, SPI_MISO_PIN)
#define NSS_H HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET)
#define NSS_L HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET)
·
void SPI_Init(void)
{
/*##-1- Enable peripherals and GPIO Clocks #########################*/
/* Enable GPIO TX/RX clock */
SPI_SCK_GPIO_CLK_ENABLE();
SPI_MISO_GPIO_CLK_ENABLE();
SPI_MOSI_GPIO_CLK_ENABLE();
SPI_NSS_GPIO_CLK_ENABLE();
/*##-2- Configure peripheral GPIO #######################*/
/* SPI SCK GPIO pin configuration */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = SPI_SCK_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
//GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_SET);
/* SPI MISO GPIO pin configuration */
GPIO_InitStruct.Pin = SPI_MISO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
/* SPI MOSI GPIO pin configuration */
GPIO_InitStruct.Pin = SPI_MOSI_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_SET);
GPIO_InitStruct.Pin = SPI_NSS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);
}
四种模拟SPI
/* CPOL = 0, CPHA = 0, MSB first */
uint8_t SOFT_SPI_RW_MODE0(uint8_t write_dat)
{
uint8_t i, read_dat = 0; // 初始化读取数据的变量
// 遍历处理每一个比特位(8位对于 uint8_t 类型)
for (i = 0; i < 8; i++)
{
// 将最高位(MSB)数据输出到 MOSI 引脚
if (write_dat & 0x80)
MOSI_H; // 如果最高位是 1,则设置 MOSI 为高电平
else
MOSI_L; // 如果最高位是 0,则设置 MOSI 为低电平
// 将数据字节向左移动一位,为下一个数据位做准备
write_dat <<= 1;
// 延时,可能是为了满足时序要求
delay_us(1);
// 在 SCK(时钟)上升沿之前,从设备准备采样 MOSI 的数据
// 这里省略了从设备采样的具体代码,通常在硬件 SPI 中,从设备会在 SCK 上升沿时采样 MOSI 的数据
// 将 SCK(时钟)置高,主设备在此时刻采样 MISO 上的数据
SCK_H;
// 从 MISO 输入的数据位,被主设备读取到 read_dat 中
read_dat <<= 1; // 主设备左移一位,为下一个数据位做准备
if (MISO)
read_dat++; // 如果 MISO 为高电平,则在 read_dat 中设置相应的位
// 延时,可能是为了满足时序要求
delay_us(1);
// 将 SCK(时钟)置低,结束本次数据传输周期
SCK_L;
// 空操作,可能是为了处理时序
__nop();
}
// 返回读取到的数据
return read_dat;
}
/* CPOL=0,CPHA=1, MSB first */
uint8_t SOFT_SPI_RW_MODE1(uint8_t byte)
{
uint8_t i,Temp=0;
for(i=0;i<8;i++) // 循环8次
{
SCK_H; //拉高时钟
if(byte&0x80)
{
MOSI_H; //若最到位为高,则输出高
}
else
{
MOSI_L; //若最到位为低,则输出低
}
byte <<= 1; // 低一位移位到最高位
delay_us(1);
SCK_L; //拉低时钟
Temp <<= 1; //数据左移
if(MISO)
Temp++; //若从从机接收到高电平,数据自加一
delay_us(1);
}
return (Temp); //返回数据
}
/* CPOL=1,CPHA=0, MSB first */
uint8_t SOFT_SPI_RW_MODE2(uint8_t byte)
{
uint8_t i,Temp=0;
for(i=0;i<8;i++) // 循环8次
{
if(byte&0x80)
{
MOSI_H; //若最到位为高,则输出高
}
else
{
MOSI_L; //若最到位为低,则输出低
}
byte <<= 1; // 低一位移位到最高位
delay_us(1);
SCK_L; //拉低时钟
Temp <<= 1; //数据左移
if(MISO)
Temp++; //若从从机接收到高电平,数据自加一
delay_us(1);
SCK_H; //拉高时钟
}
return (Temp); //返回数据
}
/* 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;
delay_us(1);
SCK_H;
read_dat <<= 1;
if( MISO )
read_dat++;
delay_us(1);
__nop();
}
return read_dat;
}
CPOL = 0, CPHA = 0, first 模式下读写多字节数据
/*
u16Len: 读写的数据长度
pu8Dat 写入的数据
pu8RxBuf 读取的数据
*/
int soft_spi_trx_bytes ( uint16_t u16Len, uint8_t* pu8Dat, uint8_t* pu8RxBuf)
{
uint16_t i = 0;
if (( NULL == pu8Dat ) || ( 0 == u16Len ))
{
return 1;
}
for ( i = 0; i < u16Len; i++ )
{
pu8RxBuf[i]=SOFT_SPI_RW_MODE0 ( pu8Dat[i]);
}
return 0;
}