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