模拟spi抽象

版权声明:本文为博主原创文章,转载请注明出处! https://blog.csdn.net/qq_20553613/article/details/79060103

1.前言

      接上一篇“spi抽象/硬件spi”博客。上一篇文章主要描述spi总线抽象过程和使用方式,同时实现stm32f1硬件spi,及spi抽象接口函数的使用。对于一些特殊mcu没有硬件spi,或者硬件spi不够用的情况下,可以用io翻转方式模拟spi总线。模拟spi实现,只需将硬件spi部分替代即可,上层代码或者器件外设驱动程序无须更改,方便移植。模拟spi与之前一篇“i2c抽象/模拟i2c”中描述的模拟i2c抽象思路一致,对模拟spi通过函数指针的方式进行封装,将时序实现源码和应用程序分离,方便移植和更换模拟spi的io口。

2.模拟spi抽象

此部分实现源码为:spi_bitops.c  spi_bitos.h

1)总线入口参数(设备指针)

与硬件spi的函数参数一致,主要是“struct spi_bus_device”的实例化。再看“struct spi_bus_device”参数如下:

struct spi_bus_device
{	
        int             (*spi_bus_xfer)(struct spi_dev_device *spi_bus,struct spi_dev_message *msg);
        void            *spi_phy;
        unsigned char   data_width;
};

a)spi_bus_xfer:收发函数实现有差异,主要通过io翻转时序收发;

b)spi_phy:spi物理指针,对于硬件spi,以stm32f1为例,就是SPI1、SPI2;模拟spi则是io翻转模式的spi函数;

c)data_with:不变,数据位数,一般是8位或者16位。

2)spi_bus_device 模拟方式实例化

a)spi_bus_xfer

参考硬件spi_bus_xfer实现过程,模拟spi_bus_xfer具体代码如下:

int ops_spi_bus_xfer(struct spi_dev_device *spi_dev,struct spi_dev_message *msg)  
{  
    	int size;  
  
    	size = msg->length;  
    	if(msg->cs_take)  
    	{// take CS   
        	spi_dev->spi_cs(0);  
    	}  
    	if(spi_dev->spi_bus->data_width <=8)  
    	{//8bit  
       		const unsigned char *send_ptr = msg->send_buf;  
           	unsigned char *recv_ptr = msg->recv_buf;  
                  
            	while(size--)  
            	{   
                    	unsigned char data = 0xFF;  
                    	if(send_ptr != 0)  
                    	{  
                        	data = *send_ptr++;  
                    	}  
                        data = ops_spi_send_byte((struct ops_spi_bus_device*)spi_dev->spi_bus->spi_phy,data);
                    	if(recv_ptr != 0)  
                    	{  
                	        *recv_ptr++ = data;  
                    	}  
            	}     
    	}  
    	if(msg->cs_release)  
    	{// release CS   
        	spi_dev->spi_cs(1);  
    	}  
    	return msg->length;  
}  

—代码当中,可以发现,收发函数(ops_spi_send_byte)与硬件spi的收发函数(硬件spi是函数库的接口)存在差异。“ops_spi_send_byte”就是我们需要实现的模拟spi收发函数。此部分涉及到一个结构体,也就是模拟spi的封装结构体——struct ops_spi_bus_device,要实现“ops_spi_send_byte”函数,主要就是实现“struct ops_spi_bus_device”函数指针的实体了。

—函数调用中涉及“(struct ops_spi_bus_device*)spi_dev->spi_bus->spi_phy”,从而可以知道此时的物理spi指针“spi_phy”一定是指向我们模拟spi的某一地址。

b)首先实现ops_spi_send_byte函数

int8_t ops_spi_send_byte(struct ops_spi_bus_device *spi_bus,int8_t data)
{//8bit
	char i,recv=0;
	
	for(i = 0x80;i != 0;i >>= 1)
	{     
		spi_bus->clk(0);	
		spi_bus->sdo((data&i) ? 1 : 0);
		spi_bus->delayus(1);
		spi_bus->clk(1); 
		spi_bus->delayus(1);
		if(spi_bus->sdi())
		recv |= i;
	}
	spi_bus->clk(0);	
    return recv;
}

—ops_spi_bus_device函数指针需要具体实例化;

—ops_spi_bus_device通过函数指针调用实体函数进行io翻转、时序延时、数据接收发;

—ops_spi_bus_device实例化,属于与mcu“底层”相的范畴,更换spi io口或者移植到其他mcu平台,主要修改此实体函数;

—该时序图是spi 4种模式中的“L 1”(“H 2”)模式,余下的2种模式用得较少,后期再增加。

3.模拟spi实现(以stm32f1为例)

此部分实现源码为:spi_hw.c  spi_hw.h

1)主要实现上述的“ops_spi_bus_device”实体函数

先来看“struct ops_spi_bus_device”结构体参数:

struct ops_spi_bus_device
{
       void (*sdo)(int8_t state);
       int8_t (*sdi)(void);
       void (*clk)(int8_t state);
       void (*delayus)(uint32_t us); 
};

a)sdo:数据线,输出1bit;

b)sdi:数据线,输入1bit;

c)clk:时钟线,控制输出一个高电平或者低电平;

d)delayus:延时函数,可以根据mcu时钟进行适当延时,或者可以不用延时。

2)stm32f1函数实体

此部分,除了“delayus”延时函数外,其他3个都是基本的io输出或者输入,比较易实现。以sdo为例:

static void spi0_gpio_sdo(int8_t state)
{
        if (state)
                SPI0_SDO_PORT->BSRR = SPI0_SDO_PIN;
        else
                SPI0_SDO_PORT->BRR = SPI0_SDO_PIN;
}
static void gpio_delayus(uint32_t us)
{
        volatile int32_t i;

        for (; us > 0; us--)
        {
                i = 10;  
                while(i--);
        }
        //Delayus(us);
}

a)为了提高速率,上述是用寄存器的操作方式,可以库函数设置输出io电平;

b)对于延时函数,可以用系统延时、循环计时,对于一些高速率的器件,可以不用延时,不用延时时该函数为空即可;

c)余下实现查看附件源码。

3)初始化一根模拟spi总线

void st_ops_spi0_init(struct ops_spi_bus_device *ops_spi_bus)
{
	GPIO_InitTypeDef GPIO_InitStructure;										
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);		

	GPIO_InitStructure.GPIO_Pin = SPI0_SDO_PIN | SPI0_CLK_PIN;
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	   			
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	   		
  	GPIO_Init(SPI0_SDO_PORT, &GPIO_InitStructure);								   				
	SPI0_SDO_PORT->BRR = SPI0_SDO_PIN;								
	SPI0_CLK_PORT->BRR = SPI0_CLK_PIN;
		
	GPIO_InitStructure.GPIO_Pin = SPI0_SDI_PIN;
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;	   	
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	 
	GPIO_Init(SPI0_SDI_PORT, &GPIO_InitStructure);	
	//device init
	ops_spi_bus->sdo = spi0_gpio_sdo;
	ops_spi_bus->sdi = spi0_gpio_sdi;
	ops_spi_bus->clk = spi0_gpio_clk;
	ops_spi_bus->delayus = gpio_delayus;
}

a)模拟spi 用到的io初始化;

b)“struct ops_spi_bus_device”参数指针初始化(指向实体函数)。

4)spi总线设备初始化(与硬件spi接口一致)

void stm32f1xx_spi_init(struct spi_bus_device *spi0,unsigned char byte_size0,
			struct spi_bus_device *spi1,unsigned char byte_size1)
{
	if(spi0)
	{//SPI1
		static struct ops_spi_bus_device ops_spi_bus0;
			
		st_ops_spi0_init(&ops_spi_bus0);
		spi0->data_width 	= byte_size0;
		spi0->spi_bus_xfer 	= ops_spi_bus_xfer;
		spi0->spi_phy 		= &ops_spi_bus0;
	}
	if(spi1)
	{//SPI2
		static struct ops_spi_bus_device ops_spi_bus1;

		st_ops_spi1_init(&ops_spi_bus1);
		spi1->data_width 	= byte_size1;
		spi1->spi_bus_xfer 	= ops_spi_bus_xfer;
		spi1->spi_phy 		= &ops_spi_bus1;
	}
}

a)主要将“spi_bus_device”指针实例化,指向上面实现的模拟spi和数据收发函数。

4.模拟spi的使用

使用模拟spi时,同样是通过“spi_core.h”中的4个API接口,因此上层代码、spi外设驱动代码完全不需改变。具体测试可以通过源码中模拟spi的宏选择“USE_SPI_BUS_BITOPS”选择使用硬件还是模拟spi,进行测试。

5.总结

1)结合“spi_core.h”的函数接口,模拟spi只需替换硬件spi部分即可实现模拟spi的使用;

2)模拟spi与之前的模拟i2c思想一致,通过函数指针方式封装,方便移植、修改、增加总线;

3)部分未完善或者未测试过的代码使用时需注意。

6.相关文章

[1]  http://blog.csdn.net/qq_20553613/article/details/78998617

[2]  http://blog.csdn.net/qq_20553613/article/details/78878211

[3]  http://blog.csdn.net/qq_20553613/article/details/79043934

7.源码

[1]  https://github.com/Prry/drivers-for-mcu

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/79060103
SPI
今日推荐