C語言軟件spi虛擬總線中間層設(shè)計詳解
簡介
mr-soft-spi 模塊為 mr-library 項目下的可裁剪模塊,以C語言編寫,可快速移植到各種平臺(主要以嵌入式mcu為主)。 mr-soft-spi 模塊通過 io 模擬實(shí)現(xiàn) spi 協(xié)議。
SPI-協(xié)議
SPI 一般為一主多從設(shè)計。由4根線組成:CLK(時鐘)、MISO(主機(jī)輸入-從機(jī)輸出)、MOSI(主機(jī)輸出-從機(jī)輸入)、CS/NSS(片選)。
接線方式
| 主機(jī) | 從機(jī) |
|---|---|
| CLK | CLK |
| MISO | MISO |
| MOSI | MOSI |
| CS/NSS | CS/NSS |
主機(jī)從機(jī)一 一對應(yīng)相接。
總線
SPI之所以被稱為總線,是其可以在一條總線上掛載多個設(shè)備,不同于IIC的地址碼設(shè)計,設(shè)備通過CS/NSS切換,更加高效。

理論上SPI可以掛載無限多的設(shè)備,只要有足夠的CS/NSS。但在實(shí)際應(yīng)用中IO資源是極為稀缺的,所以利用SPI的特性,有了菊花鏈設(shè)計。

主機(jī)將數(shù)據(jù)發(fā)送給從機(jī)1,從機(jī)1將數(shù)據(jù)發(fā)送給從機(jī)2,從機(jī)2將數(shù)據(jù)發(fā)送給從機(jī)3。菊花鏈充分利用了SPI的工作本質(zhì),減少了IO的占用。
工作本質(zhì)

我們可以看到在MOSI和MISO之間有一個移位寄存器,需要發(fā)送的數(shù)據(jù)從主機(jī)內(nèi)部被寫入到Tx buffer,然后移位寄存器移動一位,那么數(shù)據(jù)就被“擠”出到MOSI上,因為整體結(jié)構(gòu)為環(huán)形,與此同時MISO上也被“擠”進(jìn)了一位數(shù)據(jù),存入Rx buffer,以此往復(fù),就完成了全雙工通信。
4種工作模式
時鐘極性
| CPOL | 空閑時電平 |
|---|---|
| 0 | 空閑時為低電平 |
| 1 | 空閑時為高電平 |
時鐘相位
| CPHA | 采集數(shù)據(jù)在第幾個邊緣 |
|---|---|
| 0 | 第一個跳變沿采樣 |
| 1 | 第二個跳變沿采樣 |
工作模式
| MODE | CPOL | CPHA |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
| 2 | 1 | 0 |
| 3 | 1 | 1 |
時序圖:

h2>虛擬總線(中間層)設(shè)計
首先 SPI 總線的CLK、MISO、 MOSI這3條線是不會變動的,所以我們可以把這部分單獨(dú)設(shè)計為spi-bus,SPI總線需要知道當(dāng)前有哪個設(shè)備擁有SPI總線的使用權(quán),為了防止出現(xiàn)搶占還需要配置一個互斥鎖。
struct mr_soft_spi_bus
{
void (*set_clk)(mr_uint8_t level); // 操作 CLK 的函數(shù)指針
void (*set_mosi)(mr_uint8_t level); // 操作 MOSI 的函數(shù)指針
mr_uint8_t (*get_miso)(void); // 讀取 MISO 的函數(shù)指針
struct mr_soft_spi *owner; // 當(dāng)前該總線的所有者
mr_uint8_t lock; // 互斥鎖
};
SPI設(shè)備唯一獨(dú)有的只有CS/NSS一條線,所以我們把這部分定義為spi-device。SPI設(shè)備還需要知道自己歸屬于哪條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; // 該設(shè)備歸屬的總線
void (*set_cs)(mr_uint8_t level); // 操作 CS/NSS 的函數(shù)指針
};
當(dāng)創(chuàng)建了一條spi-bus,一個spi-device后我們需要一個掛載函數(shù),即將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;
}
那么由于是虛擬總線設(shè)計,當(dāng)我們要開始傳輸前需要先去獲取總線。
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;
}
當(dāng)我們使用完畢后需要釋放總線
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;
}
到此其實(shí)虛擬總線已經(jīng)設(shè)計完畢,設(shè)備需要使用僅需通過掛載 、獲取、釋放 三步操作即可,其余操作交由中間層處理。 為調(diào)用接口的統(tǒng)一,設(shè)計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; // 發(fā)送數(shù)據(jù)地址
mr_size_t send_size; // 發(fā)送數(shù)據(jù)個數(shù)
mr_uint8_t *recv_buffer; // 接收數(shù)據(jù)地址
mr_size_t recv_size; //接收數(shù)據(jù)個數(shù)
};
然后通過transfer函數(shù)統(tǒng)一調(diào)用接口。
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;
}
使用示例
/* -------------------- 配置 -------------------- */
/* 創(chuàng)建一條 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;
/* 創(chuàng)建一個 spi 設(shè)備 */
struct mr_soft_spi spi_device;
/* 適配 spi 設(shè)備接口 */
void set_cs(mr_uint8_t level)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_3,level);
}
/* 配置 spi 設(shè)備 */
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)
{
/* 需要發(fā)送的數(shù)據(jù) */
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 設(shè)備到 spi 總線 */
mr_soft_spi_attach(&spi_device,&spi_bus);
/* 創(chuàng)建 spi 消息 */
struct mr_soft_spi_msg spi_msg;
spi_msg.send_buffer = buffer; //發(fā)送數(shù)據(jù)地址
spi_msg.send_size = 10; //發(fā)送數(shù)據(jù)數(shù)量
spi_msg.recv_buffer = MR_NULL; //讀取數(shù)據(jù)地址
spi_msg.recv_size = 0; //讀取數(shù)據(jù)數(shù)量
spi_msg.read_write = SPI_WR; //只讀模式
/* 發(fā)送消息 */
mr_soft_spi_transfer(&spi_device,spi_msg);
}
剩余底層代碼位于開源代碼中,請下載開源代碼。
開源代碼倉庫鏈接 gitee.com/chen-fanyi/…
路徑:master/mr-library/ device / mr_soft_spi
請仔細(xì)閱讀README.md ?。。。。?/p>
以上就是C語言軟件spi虛擬總線中間層設(shè)計詳解的詳細(xì)內(nèi)容,更多關(guān)于C語言軟件spi虛擬總線中間層的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
圖解AVL樹數(shù)據(jù)結(jié)構(gòu)輸入與輸出及實(shí)現(xiàn)示例
這篇文章主要為大家介紹了C++圖解AVL樹數(shù)據(jù)結(jié)構(gòu)輸入與輸出操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
解析C語言中結(jié)構(gòu)體struct的對齊問題
這篇文章主要介紹了C語言中結(jié)構(gòu)體struct的對齊問題,作者深入到內(nèi)存分配方面來進(jìn)行解析,需要的朋友可以參考下2016-04-04
Qt實(shí)現(xiàn)TCP同步與異步讀寫消息的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何在?Qt?中實(shí)現(xiàn)?TCP?客戶端和服務(wù)器的同步和異步讀寫消息,有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-04-04
C++深入刨析優(yōu)先級隊列priority_queue的使用
最近我學(xué)習(xí)了C++中的STL庫中的優(yōu)先級隊列(priority_queue)容器適配器,對于優(yōu)先級隊列,我們不僅要會使用常用的函數(shù)接口,我們還有明白這些接口在其底層是如何實(shí)現(xiàn)的2022-08-08

