软件模拟SPI接口程序代码

目录

SPI协议简介

SPI接口介绍

SPI接口连接图

SPI数据传输方向

 SPI传输模式

模拟SPI程序

SPI协议简介

SPI的通信原理很简单,一般主从方式工作,这种模式通常有一个主设备和一个或者多个从设备,通常采用的是4根线,它们是MISO(数据输入,针对主机来说)、MOSI(数据输出,针对主机来说)、SCLK(时钟,主机产生)、CS/SS(片选,一般由主机发送或者直接使能,通常为低电平有效)

  • SPI接口介绍

SCK:时钟信号,由主设备产生,所以主设备SCK信号为输出模式,从设备的SCK信号为输入模式

CS:使能信号,由主设备控制从设备,所以主设备CS信号为输出模式,从设备的CS信号为输入模式。

MOSI:主设备数据输出,从设备输入,所以主设备MOSI信号为输出模式,从设备的MOSI信号为输入模式

MISO:主设备数据输入,从设备数据输出,所以主设备MISO信号为输入模式,从设备的MISO信号为输出模式

  • SPI接口连接图

注意:MOSI和MISO不能交叉连接(可以把主从机理解为一个整体系统,MOSI为系统主机发送从机接收的数据线,MISO为主机接收从机发送的数据线)

  • SPI数据传输方向

SPI作为全双工的串行通信协议,数据传输时高位在前,低位在后。主机和从机公用由主机产生的SCK信号,所以在每个时钟周期内主机和从机有1bit的数据交换(因为MOSI和MISO数据线上的数据都是在时钟的边沿处被采样)。如下图

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处)(采样点)

MOSI上数据为1,则在此边沿从机采样,数据为1,采样点在MOSI数据线的中间

MISO上数据为0,则在此边沿从机采样,数据为0,采样点在MISO数据线的中间

  • 在时钟的第一个下降沿(游标2处)(触发点)

MOSI上数据由1切换为0,数据在时钟的下降沿时切换数据

MISO上数据由0切换为1,数据在时钟的下降沿时切换数据

  • 在时钟的第2~8个上升沿(采样点),主机在MISO上采样数据,从机在MOSI上采样数据
  • 在时钟的第2~8个下降沿(触发点),主机在MISO上切换数据,从机在MOSI上切换数据

模拟SPI程序

初始化代码

#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      GPIOOD


#define    SPI_SCK_GPIO_CLK_ENABLE()     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_data)
{
    uint8_t i, read_data;
    for(i=0;i<8;i++)
    {
        if(write_data & 0x80)
            MOSI_H;
        else
            MOSI_L;
        write_data <<= 1;
        delay_us(1);
        SCK_H;
        read_data <<= 1;
        if(MISO)
            read_data++;
        delay_us(1);
        SCK_L;
        __nop();    
    }
    return read_data;

}

/* 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;
}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
将SD库的SPI接口配置成软件模拟的方法如下: 1. 打开SD库的源代码,找到SPI接口的相关代码。 2. 将SPI接口的配置修改成软件模拟,例如将SD库中的SPI配置代码: ```c SPI.beginTransaction(sdSettings); ``` 改成: ```c digitalWrite(sd_cs, LOW); digitalWrite(sd_clk, LOW); digitalWrite(sd_di, LOW); digitalWrite(sd_cs, HIGH); ``` 其中,`sd_cs`、`sd_clk`、`sd_di`是软件模拟SPI所需要的引脚。 3. 在SD库的源代码中,将SPI通信的相关函数修改成软件模拟的方式。 例如,将SD库中的SPI发送数据函数: ```c SPI.transfer(data); ``` 改成: ```c for (int i = 7; i >= 0; i--) { digitalWrite(sd_clk, LOW); digitalWrite(sd_di, (data >> i) & 0x01); digitalWrite(sd_clk, HIGH); } ``` 其中,`sd_clk`、`sd_di`是软件模拟SPI所需要的引脚。 4. 在程序中引入SD库,并调用SD库中的相关函数进行SD卡的读写操作。 下面是一个使用软件模拟SPI的SD卡读取文件的示例代码: ```c #include <SD.h> const int sd_cs = 10; const int sd_clk = 13; const int sd_di = 11; void setup() { pinMode(sd_cs, OUTPUT); pinMode(sd_clk, OUTPUT); pinMode(sd_di, OUTPUT); digitalWrite(sd_cs, HIGH); digitalWrite(sd_clk, LOW); digitalWrite(sd_di, LOW); Serial.begin(9600); while (!Serial); SD.begin(sd_cs); if (!SD.exists("test.txt")) { Serial.println("File not found!"); return; } File file = SD.open("test.txt"); while (file.available()) { Serial.write(file.read()); } file.close(); } void loop() { } ``` 注意,在使用软件模拟SPI时,由于发送数据的速度较慢,可能会影响SD卡的读写速度,需要根据实际情况进行评估。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值