C語言軟件spi虛擬總線中間層設計詳解
簡介
mr-soft-spi 模塊為 mr-library 項目下的可裁剪模塊,以C語言編寫,可快速移植到各種平臺(主要以嵌入式mcu為主)。 mr-soft-spi 模塊通過 io 模擬實現(xiàn) spi 協(xié)議。
SPI-協(xié)議
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的特性,有了菊花鏈設計。

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

我們可以看到在MOSI和MISO之間有一個移位寄存器,需要發(fā)送的數(shù)據(jù)從主機內(nèi)部被寫入到Tx buffer,然后移位寄存器移動一位,那么數(shù)據(jù)就被“擠”出到MOSI上,因為整體結構為環(huán)形,與此同時MISO上也被“擠”進了一位數(shù)據(jù),存入Rx buffer,以此往復,就完成了全雙工通信。
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>虛擬總線(中間層)設計
首先 SPI 總線的CLK、MISO、 MOSI這3條線是不會變動的,所以我們可以把這部分單獨設計為spi-bus,SPI總線需要知道當前有哪個設備擁有SPI總線的使用權,為了防止出現(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; // 當前該總線的所有者
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 的函數(shù)指針
};
當創(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;
}
那么由于是虛擬總線設計,當我們要開始傳輸前需要先去獲取總線。
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;
}
到此其實虛擬總線已經(jīng)設計完畢,設備需要使用僅需通過掛載 、獲取、釋放 三步操作即可,其余操作交由中間層處理。 為調(diào)用接口的統(tǒng)一,設計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 設備 */
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)
{
/* 需要發(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 設備到 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
請仔細閱讀README.md ?。。。。?/p>
以上就是C語言軟件spi虛擬總線中間層設計詳解的詳細內(nèi)容,更多關于C語言軟件spi虛擬總線中間層的資料請關注腳本之家其它相關文章!
相關文章
圖解AVL樹數(shù)據(jù)結構輸入與輸出及實現(xiàn)示例
這篇文章主要為大家介紹了C++圖解AVL樹數(shù)據(jù)結構輸入與輸出操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-05-05
C++深入刨析優(yōu)先級隊列priority_queue的使用
最近我學習了C++中的STL庫中的優(yōu)先級隊列(priority_queue)容器適配器,對于優(yōu)先級隊列,我們不僅要會使用常用的函數(shù)接口,我們還有明白這些接口在其底層是如何實現(xiàn)的2022-08-08

