软件模拟SPI协议

加多一个硬件SPI的程序

//时钟
/*SPI1*/
	RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_SPI2, ENABLE);
//GPIO初始化
	//SPI2_SCK->PB13
	//SPI2_MOSI->PB15
	GPIO_InitStructure.GPIO_Pins = GPIO_Pins_13 | GPIO_Pins_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	//SPI2_MISO->PB14
	GPIO_InitStructure.GPIO_Pins = GPIO_Pins_14;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	//SPI2_NSS->PB12
	GPIO_InitStructure.GPIO_Pins = GPIO_Pins_12;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT_PP;
	GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

void SPI2_Configuration()
{
	SPI_InitType SPI1_InitStructure;
	SPI1_InitStructure.SPI_TransMode = SPI_TRANSMODE_FULLDUPLEX;
	SPI1_InitStructure.SPI_CPHA = SPI_CPHA_1EDGE;
	SPI1_InitStructure.SPI_CPOL = SPI_CPOL_LOW;
	SPI1_InitStructure.SPI_CPOLY = 7;
	SPI1_InitStructure.SPI_FirstBit = SPI_FIRSTBIT_MSB;
	SPI1_InitStructure.SPI_FrameSize = SPI_FRAMESIZE_8BIT;
	SPI1_InitStructure.SPI_MCLKP = SPI_MCLKP_8;//75/32 = 2.34375 MBits/s
	SPI1_InitStructure.SPI_NSSSEL = SPI_NSSSEL_SOFT;
	SPI1_InitStructure.SPI_Mode = SPI_MODE_MASTER;

	SPI_Init(SPI2, &SPI1_InitStructure);
	SPI_Enable(SPI2, ENABLE);
}
//发送一个字节
void SPI1_sendByte(uint8_t b)
{
	SPI_I2S_TxData(SPI1, b);
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TE) == RESET);
	SPI_I2S_ClearFlag(SPI1, SPI_I2S_FLAG_TE);
}
//发送多个字节
void SPI1_sendNbyte(uint8_t* index, uint8_t len)
{
	while (len--)
	{
		SPI1_sendByte(*index++);
	}
}
//读一个字节
uint8_t SPI1_readByte()
{
	uint16_t tryCnt = 0;
	SPI_I2S_TxData(SPI1, 0x00);
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RNE) == RESET)
	{
		if (tryCnt++ > 1000) break;
	}
	if (tryCnt < 1000)
		return SPI1->DT;
	else
		return 0;
}
//发送并接收一个字节
uint8_t spi_transmit(uint8_t b)
{
	uint16_t tryCnt = 0;
	/* Wait for Tx buffer empty */
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TE) == RESET)
	{
		if (tryCnt++ > 1000) break;
	}
	/* Send data */
	SPI2->DT = b;
	/* Wait for data reception */
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RNE) == RESET)
	{
		if (tryCnt++ > 1000) break;
	}
	/* Read received data */
	return SPI2->DT;
}

————————————————————分割线——————————————


最近在学习51单片机的内容,为了防止自己学过就忘,在这里写一些平时的学习笔记,如果有错误希望大家可以给我指正一下。这里是最近学习的SPI串行总线通讯协议的内容,由于所用单片机没有硬件SPI,所以需要用IO口来模拟SPI。

什么是SPI

SPI的定义可以去网上查,能查到很多,这里主要记一下比较有用的部分。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。这四根线分别是MISO、MOSI、SCLK、CS,具体的描述见下表:
MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
SCLK – Serial Clock,时钟信号,由主设备产生;
CS – Chip Select,从设备使能信号,由主设备控制。

可能各个地方的名字不同,比如MISO线有的也叫做MDI(master data input),MOSI也叫作MDO(master data output),SCLK也叫SCK(时钟)等等。

需要注意SPI是串行通讯协议,所以他的数据是一位一位进行传输的。

SPI的通讯模式

在介绍SPI通讯模式之前,有两个值需要了解,CPOL(时钟极性)和CPHA(时钟相位)。CPOL是用来配置SCK时钟信号什么时候是空闲状态,什么时候是有效状态;CPHA用来配置数据采样发生在第几个边沿。
CPOL=0表示当SCK=0时为空闲状态,SCK=1时为有效状态。
CPOL=1表示当SCK=1时为空闲状态,SCK=0时为有效状态。
CPHA=0表示数据采样是在第1个边沿,数据发送在第2个边沿。
CPHA=1表示数据采样是在第2个边沿,数据发送在第1个边沿。这里需要结合SPI的时序图来看
图中的CPOL就是SCK时钟信号

SPI共有四种通讯模式,不同的从设备可能在出厂是就是配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的主设备的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式,具体如下:

Mode00:CPOL=0,CPHA=0
Mode01:CPOL=0,CPHA=1
Mode10:CPOL=1,CPHA=0
Mode11:CPOL=1,CPHA=1

通讯协议

从SPI的通讯模式可以知道,四种不同的模式对应的通讯协议不同,所以我把四种模式下的通讯协议都写出来了,需要用到那种模式就用哪个协议就好了,可能会有些问题,希望大家可以指正出来。

详细程序

SPI通讯默认是高位字节(MSB)优先传送,除非某些特定的设备明确说明是低位字节(LSB)优先传送。

一般常用的是Mode00和Mode01两种模式。

首先第一种,MODE00

/* CPHA=0   CPOL=0  写入数据  MSB优先*/
void soft_SPI_Write_MODE00(u8 write_data)
{
    
    u8 i=0;
	CS=0;
	SCK=0;                //SCK空闲时为低电平
    
    for (i = 0; i < 8; i++) //写入数据,从高位开始
    {
        if (write_data & 0x80)
        {
            MOSI=1;
        }
        else
        {
            MOSI=0;
        }
        SCK=1; //时钟拉高 上升沿传输一位数据
        write_data<<=1;
        SCK=0; //时钟拉低等待下一位数据的传输
    }
    CS=1;		//片选拉高结束通讯
}
/*读取数据*/
u8 soft_SPI_Read_Mode00(u8 write_data)
{
    u8 read_data = 0;
    u8 i=0;
    CS=0;
    SCK=0;
    for (i = 0; i < 8; i++) //读取数据,从高位开始
    {
        read_data<<=1;
        if (MISO)
            read_data |= 0x01;
        SCK=1; //时钟拉高 上升沿传输一位数据

        SCK=0; //时钟拉低等待下一位数据的传输
    }

	CS=1;
    return read_data;
}

第二种Mode01

/* CPHA=0   CPOL=1  写入数据 MSB优先*/
void soft_SPI_RW_MODE01(u8 write_data)
{

    CS=0;  //片选拉低有效
    SCK=1; //SCK空闲时为高电平

    for (i = 0; i < 8; i++) //写入数据,从高位开始
    {
        if (write_data & (0x80 >> i))
            MOSI=1;
        else
            MOSI=0;
        SCK=0; //时钟拉低 下降沿传输一位数据
        SCK=1; //时钟拉高 等待下一位数据的传输
    }
}
/*读取数据*/
u8 soft_SPI_Read_Mode01(u8 write_data)
{
	u8 read_data = 0;
    u8 i;
    for (i = 0; i < 8; i++) //读取数据,从高位开始
    {
		read_data <<= 1;
        if (MISO)
            read_data |= 0x01;

        SCK=0; //时钟拉低 下降沿传输一位数据
        SCK=1; //时钟拉高 等待下一位数据的传输
    }
    CS=1; //一个字节传输结束 片选拉高 最后一位数据传输结束后时钟线已被拉高为空闲状态 不需要再次拉高
    return read_data;
}

另外两个模式一般不太用到,这里就不贴代码了。

简写程序

上面几段程序是根据SPI时序图来写的比较详细的通讯协议,程序会显得比较长,冗杂。但其实SPI通讯是全双工的,主机和从机的发送数据是同时完成的,两者的接收数据也是同时完成的。也就是说,当上升沿主机发送数据的时候,从机也发送了数据,再根据资料中写到的SPI是串行通信的方式,每次只发送一位数据,我们就可以在主机发送一位数据的同时也接收一位数据。以Mode00为例贴一段简写后的程序

u8 soft_SPI_RW_MODE00(u8 write_data)
{
    uint8_t i;
    uint8_t read_data=0;
    CS=0;
    SCK=0;        //空闲状态为低电平
    for(i=0;i<8;i++)
    {
        if(write_data&0X80)               //读一位    
            MOSI=1;
        else
            MOSI=0;
        read_data<<=1;
        if(MISO)
            read_data|=0x01;             //写一位
        SCK=1;
        write_data<<=1;
        
        SCK=0;
    }
	CS=1;
    return read_data;
}

结语

以上就是本次软件SPI通讯协议的全部内容了,有什么错误希望大家可以多多指正,共同进步。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值