软件模拟SPI接口程序代码(4种模式)
SPI协议简介
SPI的通讯原理很简单,通常主从方式工做,这种模式一般有一个主设备和一个或多个从设备,一般采用的是4根线,它们是MISO(数据输入,针对主机来讲)、MOSI(数据输出,针对主机来讲)、SCLK(时钟,主机产生)、CS/SS(片选,通常由主机发送或者直接使能,一般为低电平有效)测试
●SPI接口介绍ui
SCK:时钟信号,由主设备产生,因此主设备SCK信号为输出模式,从设备的SCK信号为输入模式。spa
CS:使能信号,由主设备控制从设备,,因此主设备CS信号为输出模式,从设备的CS信号为输入模式。3d
MOSI:主设备数据输出,从设备数据输入,因此主设备MOSI信号为输出模式,从设备的MOSI信号为输入模式。code
MISO:主设备数据输入,从设备数据输出,因此主设备MISO信号为输入模式,从设备的MISO信号为输出模式。blog
●SPI接口链接图接口
注意:MOSI和MISO不能交叉链接(能够把主从机理解为一个总体系统,MOSI为系统主机发送从机接收的数据线,MISO为主机接收从机发送的数据线)ip
●SPI数据传输方向it
SPI做为全双工的的串行通讯协议,数据传输时高位在前,低位在后。主机和从机公用由主机产生的SCK信号,因此在每一个时钟周期内主机和从机有1bit的数据交换(由于MOSI和MISO数据线上的数据都是在时钟的边沿处被采样)。以下图:io
SPI协议规定数据采样是在SCK的上升沿或降低沿时刻(由SPI模式决定,下面会说到),观察上图,在SCK的边沿处(上升沿或降低沿),主机会在MISO数据线上采样(接收来从机的数据),从机会在MOSI数据线上采样(接收来自主机的数据),因此每一个时钟周期中会有一bit的数据交换。
SPI传输模式
SPI总线传输一共有4种模式,这4种模式分别由时钟极性(CPOL)和时钟相位(CPHA)来定义。
CPOL:规定了SCK时钟信号空闲状态的电平
CPHA:规定了数据是在SCK时钟的上升沿仍是降低沿被采样
----------- ------------------------------------
模式0:CPOL=0,CPHA =0 SCK空闲为低电平,数据在SCK的上升沿被采样(提取数据)
模式1:CPOL=0,CPHA =1 SCK空闲为低电平,数据在SCK的降低沿被采样(提取数据)
模式2:CPOL=1,CPHA =0 SCK空闲为高电平,数据在SCK的降低沿被采样(提取数据)
模式3:CPOL=1,CPHA =1 SCK空闲为高电平,数据在SCK的上升沿被采样(提取数据)
以模式0为例:SCK空闲为低电平,数据在SCK的上升沿被采样(提取数据),在SCK的降低沿切换数据线的数据。
◐在时钟的第1个上升沿(游标1处)(采样点)
MOSI上数据为1,则在此边沿从机采样(提取)数据为1,采样点在MOSI数据线的中间。
MISO上数据为0,则在此边沿主机采样(提取)数据为0,采样点在MISO数据线的中间。
◐在时钟的第1个降低沿(游标2处)(切换点)
MOSI上数据由1切换为0,,数据在时钟降低沿时切换数据。
MISO上数据由0切换为1,,数据在时钟降低沿时切换数据。
◐在时钟的第2~8个上升沿(采样点),主机在MISO上采样数据,从机在MOSI上采样数据。
◐在时钟的第2~8个降低沿(切换点),主机在MISO上切换数据,从机在MOSI上切换数据
经过模拟SPI程序来加深理解
使用STM32L4R5ZI MCU进行的测试
初始化代码:
/**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); }
模拟SPI4种工做模式:
/* 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; delay_us(1); SCK_H; read_dat <<= 1; if( MISO ) read_dat++; delay_us(1); 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; }