C語言軟件spi虛擬總線中間層設(shè)計(jì)詳解
簡介
mr-soft-spi 模塊為 mr-library
項(xiàng)目下的可裁剪模塊,以C語言編寫,可快速移植到各種平臺(主要以嵌入式mcu為主)。 mr-soft-spi 模塊通過 io 模擬實(shí)現(xiàn) spi 協(xié)議。
SPI-協(xié)議
SPI 一般為一主多從設(shè)計(jì)。由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è)計(jì),設(shè)備通過CS/NSS
切換,更加高效。
理論上SPI可以掛載無限多的設(shè)備,只要有足夠的CS/NSS
。但在實(shí)際應(yīng)用中IO資源是極為稀缺的,所以利用SPI的特性,有了菊花鏈設(shè)計(jì)。
主機(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
上,因?yàn)檎w結(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è)計(jì)
首先 SPI 總線的CLK
、MISO
、 MOSI
這3條線是不會變動的,所以我們可以把這部分單獨(dú)設(shè)計(jì)為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è)計(jì),當(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è)計(jì)完畢,設(shè)備需要使用僅需通過掛載
、獲取
、釋放
三步操作即可,其余操作交由中間層處理。 為調(diào)用接口的統(tǒng)一,設(shè)計(jì)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è)計(jì)詳解的詳細(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-04Qt實(shí)現(xiàn)TCP同步與異步讀寫消息的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何在?Qt?中實(shí)現(xiàn)?TCP?客戶端和服務(wù)器的同步和異步讀寫消息,有需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-04-04C++深入刨析優(yōu)先級隊(duì)列priority_queue的使用
最近我學(xué)習(xí)了C++中的STL庫中的優(yōu)先級隊(duì)列(priority_queue)容器適配器,對于優(yōu)先級隊(duì)列,我們不僅要會使用常用的函數(shù)接口,我們還有明白這些接口在其底層是如何實(shí)現(xiàn)的2022-08-08