C语言软件spi虚拟总线中间层设计详解
作者:MacRsh
简介
mr-soft-spi 模块为 mr-library
项目下的可裁剪模块,以C语言编写,可快速移植到各种平台(主要以嵌入式mcu为主)。 mr-soft-spi 模块通过 io 模拟实现 spi 协议。
SPI-协议
SPI 一般为一主多从设计。由4根线组成:CLK
(时钟)、MISO
(主机输入-从机输出)、MOSI
(主机输出-从机输入)、CS/NSS
(片选)。
接线方式
主机 | 从机 |
---|---|
CLK | CLK |
MISO | MISO |
MOSI | MOSI |
CS/NSS | CS/NSS |
主机从机一 一对应相接。
总线
SPI之所以被称为总线,是其可以在一条总线上挂载多个设备,不同于IIC的地址码设计,设备通过CS/NSS
切换,更加高效。
理论上SPI可以挂载无限多的设备,只要有足够的CS/NSS
。但在实际应用中IO资源是极为稀缺的,所以利用SPI的特性,有了菊花链设计。
主机将数据发送给从机1,从机1将数据发送给从机2,从机2将数据发送给从机3。菊花链充分利用了SPI的工作本质,减少了IO的占用。
工作本质
我们可以看到在MOSI
和MISO
之间有一个移位寄存器,需要发送的数据从主机内部被写入到Tx buffer
,然后移位寄存器移动一位,那么数据就被“挤”出到MOSI
上,因为整体结构为环形,与此同时MISO
上也被“挤”进了一位数据,存入Rx buffer
,以此往复,就完成了全双工通信。
4种工作模式
时钟极性
CPOL | 空闲时电平 |
---|---|
0 | 空闲时为低电平 |
1 | 空闲时为高电平 |
时钟相位
CPHA | 采集数据在第几个边缘 |
---|---|
0 | 第一个跳变沿采样 |
1 | 第二个跳变沿采样 |
工作模式
MODE | CPOL | CPHA |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
2 | 1 | 0 |
3 | 1 | 1 |
时序图:
h2>虚拟总线(中间层)设计
首先 SPI 总线的CLK
、MISO
、 MOSI
这3条线是不会变动的,所以我们可以把这部分单独设计为spi-bus
,SPI总线需要知道当前有哪个设备拥有SPI总线的使用权,为了防止出现抢占还需要配置一个互斥锁。
struct mr_soft_spi_bus { void (*set_clk)(mr_uint8_t level); // 操作 CLK 的函数指针 void (*set_mosi)(mr_uint8_t level); // 操作 MOSI 的函数指针 mr_uint8_t (*get_miso)(void); // 读取 MISO 的函数指针 struct mr_soft_spi *owner; // 当前该总线的所有者 mr_uint8_t lock; // 互斥锁 };
SPI设备唯一独有的只有CS/NSS
一条线,所以我们把这部分定义为spi-device
。SPI设备还需要知道自己归属于哪条SPI总线。
struct mr_soft_spi { mr_uint8_t mode :2; // SPI 工作模式 mr_uint8_t cs_active :1; // CS/NSS 的有效电平(一般为低) struct mr_soft_spi_bus *bus; // 该设备归属的总线 void (*set_cs)(mr_uint8_t level); // 操作 CS/NSS 的函数指针 };
当创建了一条spi-bus
,一个spi-device
后我们需要一个挂载函数,即将spi-device
挂载到spi-bus
上
void mr_soft_spi_attach(struct mr_soft_spi *spi, struct mr_soft_spi_bus *spi_bus) { spi->bus = spi_bus; }
那么由于是虚拟总线设计,当我们要开始传输前需要先去获取总线。
mr_err_t mr_soft_spi_bus_take(struct mr_soft_spi *spi) { mr_uint8_t spi_bus_lock; /* check spi-bus owner */ if(spi->bus->owner != spi) { /* check mutex lock */ do{ spi_bus_lock = spi->bus->lock; } while(spi_bus_lock != MR_UNLOCK); /* lock mutex lock */ spi->bus->lock = MR_LOCK; /* stop spi cs */ if(spi->bus->owner != MR_NULL) spi->bus->owner->set_cs(!spi->bus->owner->cs_active); /* exchange spi-bus owner */ spi->bus->owner = spi; /* start spi cs */ spi->set_cs(spi->cs_active); } else { /* lock mutex lock */ spi->bus->lock = MR_LOCK; /* start spi cs */ spi->set_cs(spi->cs_active); } return MR_EOK; }
当我们使用完毕后需要释放总线
mr_err_t mr_soft_spi_bus_release(struct mr_soft_spi *spi) { /* check spi-bus owner */ if(spi->bus->owner == spi) { /* stop spi cs */ spi->set_cs(!spi->cs_active); /* unlock mutex lock */ spi->bus->lock = MR_UNLOCK; return MR_EOK; } return -MR_ERROR; }
到此其实虚拟总线已经设计完毕,设备需要使用仅需通过挂载
、获取
、释放
三步操作即可,其余操作交由中间层处理。 为调用接口的统一,设计spi-msg
struct mr_soft_spi_msg { mr_uint8_t read_write; // 读写模式:SPI_WR/ SPI_RD/ SPI_RDWR/ SPI_WR_THEN_RD mr_uint8_t *send_buffer; // 发送数据地址 mr_size_t send_size; // 发送数据个数 mr_uint8_t *recv_buffer; // 接收数据地址 mr_size_t recv_size; //接收数据个数 };
然后通过transfer
函数统一调用接口。
mr_err_t mr_soft_spi_transfer(struct mr_soft_spi *spi, struct mr_soft_spi_msg msg) { mr_err_t ret; /* check function args */ MR_DEBUG_ARGS_NULL(spi,-MR_EINVAL); MR_DEBUG_ARGS_IF(msg.read_write > SPI_WR_THEN_RD,-MR_EINVAL); /* take spi-bus */ ret = mr_soft_spi_bus_take(spi); if(ret != MR_EOK) return ret; if(msg.read_write == SPI_WR || msg.recv_buffer == MR_NULL) msg.recv_size = 0; if(msg.read_write == SPI_RD || msg.send_buffer == MR_NULL) msg.send_size = 0; switch (msg.read_write) { case SPI_RD: /* receive */ while (msg.recv_size) { *msg.recv_buffer = mr_soft_spi_bus_transmit(spi,0u); ++msg.recv_buffer; --msg.recv_size; } break; case SPI_WR: /* send */ while (msg.send_size) { mr_soft_spi_bus_transmit(spi,*msg.send_buffer); ++msg.send_buffer; --msg.send_size; } break; case SPI_WR_THEN_RD: /* send */ while (msg.send_size) { mr_soft_spi_bus_transmit(spi,*msg.send_buffer); ++msg.send_buffer; --msg.send_size; } /* receive */ while (msg.recv_size) { *msg.recv_buffer = mr_soft_spi_bus_transmit(spi,0u); ++msg.recv_buffer; --msg.recv_size; } break; case SPI_RDWR: /* transmit */ while (msg.send_size) { *msg.recv_buffer = mr_soft_spi_bus_transmit(spi,*msg.send_buffer); ++msg.send_buffer; ++msg.recv_buffer; --msg.send_size; } break; } /* release spi-bus */ mr_soft_spi_bus_release(spi); return MR_EOK; }
使用示例
/* -------------------- 配置 -------------------- */ /* 创建一条 spi 总线 */ struct mr_soft_spi_bus spi_bus; /* 适配 spi 总线接口 */ void set_clk(mr_uint8_t level) { GPIO_WriteBit(GPIOA,GPIO_Pin_0,level); } void set_mosi(mr_uint8_t level) { GPIO_WriteBit(GPIOA,GPIO_Pin_1,level); } mr_uint8_t get_miso(void) { return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_2); } /* 配置 spi 总线 */ spi_bus.set_clk = set_clk; spi_bus.set_mosi = set_mosi; spi_bus.get_miso = get_miso; spi_bus.lock = MR_UNLOCK; spi_bus.owner = MR_NULL; /* 创建一个 spi 设备 */ struct mr_soft_spi spi_device; /* 适配 spi 设备接口 */ void set_cs(mr_uint8_t level) { GPIO_WriteBit(GPIOA,GPIO_Pin_3,level); } /* 配置 spi 设备 */ spi_device.mode = SPI_MODE_0; //SPI MODE 0 spi_device.cs_active = LEVEL_LOW; //CS 引脚低电平有效 spi_device.set_cs = set_cs; /* -------------------- 使用 -------------------- */ int main(void) { /* 需要发送的数据 */ mr_uint8_t buffer[10]={0,1,2,3,4,5,6,7,8,9}; /* 初始化 gpio */ GPIO_InitTypeDef GPIO_InitStructure = {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* 挂载 spi 设备到 spi 总线 */ mr_soft_spi_attach(&spi_device,&spi_bus); /* 创建 spi 消息 */ struct mr_soft_spi_msg spi_msg; spi_msg.send_buffer = buffer; //发送数据地址 spi_msg.send_size = 10; //发送数据数量 spi_msg.recv_buffer = MR_NULL; //读取数据地址 spi_msg.recv_size = 0; //读取数据数量 spi_msg.read_write = SPI_WR; //只读模式 /* 发送消息 */ mr_soft_spi_transfer(&spi_device,spi_msg); }
剩余底层代码位于开源代码中,请下载开源代码。
开源代码仓库链接 gitee.com/chen-fanyi/…
路径:master/mr-library/ device / mr_soft_spi
请仔细阅读README.md !!!!!
以上就是C语言软件spi虚拟总线中间层设计详解的详细内容,更多关于C语言软件spi虚拟总线中间层的资料请关注脚本之家其它相关文章!