C语言软件spi虚拟总线中间层设计详解

 更新时间:2023年01月30日 11:27:10   作者:MacRsh  
这篇文章主要为大家介绍了C语言软件spi虚拟总线中间层设计详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

简介

mr-soft-spi 模块为 mr-library 项目下的可裁剪模块,以C语言编写,可快速移植到各种平台(主要以嵌入式mcu为主)。 mr-soft-spi 模块通过 io 模拟实现 spi 协议。

SPI-协议

SPI 一般为一主多从设计。由4根线组成:CLK(时钟)、MISO(主机输入-从机输出)、MOSI(主机输出-从机输入)、CS/NSS(片选)。

接线方式

主机从机
CLKCLK
MISOMISO
MOSIMOSI
CS/NSSCS/NSS

主机从机一 一对应相接。

总线

SPI之所以被称为总线,是其可以在一条总线上挂载多个设备,不同于IIC的地址码设计,设备通过CS/NSS切换,更加高效。

理论上SPI可以挂载无限多的设备,只要有足够的CS/NSS。但在实际应用中IO资源是极为稀缺的,所以利用SPI的特性,有了菊花链设计。

主机将数据发送给从机1,从机1将数据发送给从机2,从机2将数据发送给从机3。菊花链充分利用了SPI的工作本质,减少了IO的占用。

工作本质

我们可以看到在MOSIMISO之间有一个移位寄存器,需要发送的数据从主机内部被写入到Tx buffer,然后移位寄存器移动一位,那么数据就被“挤”出到MOSI上,因为整体结构为环形,与此同时MISO上也被“挤”进了一位数据,存入Rx buffer,以此往复,就完成了全双工通信。

4种工作模式

时钟极性

CPOL空闲时电平
0空闲时为低电平
1空闲时为高电平

时钟相位

CPHA采集数据在第几个边缘
0第一个跳变沿采样
1第二个跳变沿采样

工作模式

MODECPOLCPHA
000
101
210
311

时序图:

h2>虚拟总线(中间层)设计

首先 SPI 总线的CLKMISOMOSI这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虚拟总线中间层的资料请关注脚本之家其它相关文章!

相关文章

  • C++实现两个有序数组的合并

    C++实现两个有序数组的合并

    这篇文章主要为大家详细介绍了C++实现两个有序数组的合并,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-02-02
  • C++中继承与多态的基础虚函数类详解

    C++中继承与多态的基础虚函数类详解

    这篇文章主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2017-09-09
  • C++中的模板类继承和成员访问问题

    C++中的模板类继承和成员访问问题

    这篇文章主要介绍了C++中的模板类继承和成员访问问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • C语言求2的n次方多种方法总结

    C语言求2的n次方多种方法总结

    这篇文章主要给大家介绍了关于C语言求2的n次方多种方法的相关资料,求2的N次幂是一个常用的功能,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • C++中std::construct()与std::destroy()的使用

    C++中std::construct()与std::destroy()的使用

    std::construct()和std::destroy()是C++ STL中的函数模板,用于在已分配的存储区域中构造或销毁对象,本文主要介绍了C++中std::construct()与std::destroy()的使用,感兴趣的可以了解一下
    2024-02-02
  • vscode+platformIO开发stm32f4的实现

    vscode+platformIO开发stm32f4的实现

    这篇文章主要介绍了vscode+platformIO开发stm32f4的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • C语言冷知识之预处理字符串操作符详解

    C语言冷知识之预处理字符串操作符详解

    当年学习C语言的第一门课就提到过标记(Token)的概念,不过,相信在多年之后你再次听到这个术语时会一脸懵逼,比如我。因此特地翻了翻资料,整理下来这些笔记,希望对大家有所帮助
    2022-11-11
  • 浅析c语言中的内存

    浅析c语言中的内存

    在c++中,内存分为5个区,分别是栈区,堆区,自由存储区,全局/静态存储区和常量存储区.
    2017-09-09
  • C++将CBitmap类中的图像保存到文件的方法

    C++将CBitmap类中的图像保存到文件的方法

    这篇文章主要介绍了C++将CBitmap类中的图像保存到文件的方法,涉及C++导出资源文件的实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • 详解C语言中telldir()函数和seekdir()函数的用法

    详解C语言中telldir()函数和seekdir()函数的用法

    这篇文章主要介绍了详解C语言中telldir()函数和seekdir()函数的用法,是C语言入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09

最新评论