0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

RT-Thread記錄(十五、I/O 設(shè)備模型之SPI設(shè)備)

矜辰所致 ? 來源:矜辰所致 ? 作者:矜辰所致 ? 2022-07-04 15:46 ? 次閱讀
本文學(xué)習(xí)一下I/O 設(shè)備模型之SPI設(shè)備使用,I/O 設(shè)備模型篇的最后一篇文章。

目錄

前言
一、SPI 通訊基礎(chǔ)
二、SPI 設(shè)備操作函數(shù)

2.1 掛載 SPI 設(shè)備
2.2 配置 SPI 設(shè)備
2.3 訪問 SPI設(shè)備
2.3.1 查找 SPI 設(shè)備
2.3.2 自定義數(shù)據(jù)傳輸
2.3.3 數(shù)據(jù)收發(fā)函數(shù)
2.3.4 特殊場景

三、SPI 設(shè)備測試

3.1 SPI 設(shè)備使用步驟
3.2 測試

結(jié)語

前言

本文應(yīng)該是 RT-Thread I/O 設(shè)備模型最后一篇,SPI 設(shè)備的學(xué)習(xí)測試。

我以前就說過,我的記錄是以應(yīng)用為目的,實際上我們在使用 RT-Thread 的時候,有很多常用的設(shè)備,官方或者很多開發(fā)者都已經(jīng)給我們寫好了驅(qū)動和軟件包,我們并不需要自己重新寫一篇,很多時候直接導(dǎo)入軟件包,直接調(diào)用現(xiàn)成的 API 函數(shù)就可以。

RT-Thread 文章接下來的系列,應(yīng)該會更新幾篇 軟件包和組件的使用,本文把 SPI 設(shè)備做一個學(xué)習(xí)測試。

??
本 RT-Thread 專欄記錄的開發(fā)環(huán)境:
RT-Thread記錄(一、RT-Thread 版本、RT-Thread Studio開發(fā)環(huán)境 及 配合CubeMX開發(fā)快速上手)
RT-Thread記錄(二、RT-Thread內(nèi)核啟動流程 — 啟動文件和源碼分析)
??
RT-Thread 內(nèi)核篇系列博文鏈接:
RT-Thread記錄(三、RT-Thread 線程操作函數(shù)及線程管理與FreeRTOS的比較)
RT-Thread記錄(四、RT-Thread 時鐘節(jié)拍和軟件定時器
RT-Thread記錄(五、RT-Thread 臨界區(qū)保護(hù))
RT-Thread記錄(六、IPC機(jī)制之信號量、互斥量和事件集)
RT-Thread記錄(七、IPC機(jī)制之郵箱、消息隊列)
RT-Thread記錄(八、理解 RT-Thread 內(nèi)存管理)
RT-Thread記錄(九、RT-Thread 中斷處理與階段小結(jié))
??
STM32L051C8 上使用 RT-Thread 應(yīng)用篇系列博文連接:
RT-Thread 應(yīng)用篇 — 在STM32L051上使用 RT-Thread (一、無線溫濕度傳感器 之 新建項目)
RT-Thread 應(yīng)用篇 — 在STM32L051上使用 RT-Thread (二、無線溫濕度傳感器 之 CubeMX配置)
RT-Thread 應(yīng)用篇 — 在STM32L051上使用 RT-Thread (三、無線溫濕度傳感器 之 I2C通訊)
RT-Thread 應(yīng)用篇 — 在STM32L051上使用 RT-Thread (四、無線溫濕度傳感器 之 串口通訊)
??
RT-Thread 設(shè)備篇系列博文鏈接:
RT-Thread記錄(十、全面認(rèn)識 RT-Thread I/O 設(shè)備模型)
RT-Thread記錄(十一、I/O 設(shè)備模型之UART設(shè)備 — 源碼解析)
RT-Thread記錄(十二、I/O 設(shè)備模型之UART設(shè)備 — 使用測試)
RT-Thread記錄(十三、I/O 設(shè)備模型之PIN設(shè)備)
RT-Thread記錄(十四、I/O 設(shè)備模型之ADC設(shè)備)

一、SPI 通訊基礎(chǔ)

SPI 通訊基本知識不過多介紹,原理與基礎(chǔ)可自行網(wǎng)上查詢,本文這里只做應(yīng)用所需的簡單概述:

SPI是串行外設(shè)接口(Serial Peripheral Interface)的縮寫,是一種高速的,全雙工,同步的通信總線,SPI 的通訊速度可以達(dá)到幾十M,并且在芯片的管腳上只占用四根線:

(1)MISO– Master Input Slave Output,主設(shè)備數(shù)據(jù)輸入,從設(shè)備數(shù)據(jù)輸出;
(2)MOSI– Master Output Slave Input,主設(shè)備數(shù)據(jù)輸出,從設(shè)備數(shù)據(jù)輸入;
(3)SCLK – Serial Clock,時鐘信號,由主設(shè)備產(chǎn)生;
(4)CS – Chip Select,從設(shè)備使能信號,由主設(shè)備控制。

pYYBAGLCmuWAfYoIAAA25IYPAK4385.png

SPI 以主從方式工作,通常有一個主設(shè)備和一個或多個從設(shè)備。

SPI 通訊有4中模式,由 CPOL (時鐘的極性)和 CPHA (時鐘的相位)決定:

CPOL=0,表示當(dāng)SCLK=0時處于空閑態(tài),空閑低電平,所以有效狀態(tài)就是SCLK處于高電平時
CPOL=1,表示當(dāng)SCLK=1時處于空閑態(tài),空閑高電平,所以有效狀態(tài)就是SCLK處于低電平時
CPHA=0,表示數(shù)據(jù)采樣是在第1個邊沿
CPHA=1,表示數(shù)據(jù)采樣是在第2個邊沿

如下表格:

pYYBAGLCjryAbKy8AAA8Tlo2bN8553.png

對于我們的從機(jī)設(shè)備,比如傳感器,支持的模式會使用手冊中說明:比如我們今天要測試的 SPI Flash:

poYBAGLCmuWANaQPAARKR5WikzQ812.png

二、SPI 設(shè)備操作函數(shù)

來了解一下 RT-Thread 提供的 SPI 設(shè)備操作函數(shù):

poYBAGLCjuGAI_AjAAB4Oi-n-08144.png

與前面的設(shè)備不同的地方在于,SPI 因為可以一主多從,所以 SPI 設(shè)備多了一個掛載操作,就是 RT-Thread 系統(tǒng)驅(qū)動會注冊好 SPI 總線,然后我們需要把自己所用的 SPI 設(shè)備掛載到總線上,使得可以對該設(shè)備進(jìn)行操作 。

☆ 自定義傳輸數(shù)據(jù)函數(shù) rt_spi_transfer_message 為核心,其實在其之后的那些都可以使用這個函數(shù)來表達(dá),這個下文會說明?!?/strong>

2.1 掛載 SPI 設(shè)備

SPI 驅(qū)動注冊完 SPI 總線,需要用 SPI 掛載函數(shù)將要使用的 SPI 設(shè)備需要掛載到已經(jīng)注冊好的 SPI 總線上:

/*
參數(shù) 	描述
device 		SPI 設(shè)備句柄
name 		SPI 設(shè)備名稱
bus_name 	SPI 總線名稱
user_data 	用戶數(shù)據(jù)指針
返回 	——
RT_EOK 		成功
其他錯誤碼 	失敗
*/
rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,
                                  const char           *name,
                                  const char           *bus_name,
                                  void                 *user_data)

此函數(shù)用于掛載一個 SPI 設(shè)備到指定的 SPI 總線,并向內(nèi)核注冊 SPI 設(shè)備,并將 user_data 保存到 SPI 設(shè)備的控制塊里。

一般 SPI 總線命名原則為 spix, SPI 設(shè)備命名原則為 spixy ,如 spi10 表示掛載在 spi1 總線上的 0 號設(shè)備。
user_data 一般為 SPI 設(shè)備的 CS 引腳指針,進(jìn)行數(shù)據(jù)傳輸時 SPI 控制器會操作此引腳進(jìn)行片選。

對于我們測試使用的 STM32 而言,有專門的掛載函數(shù) rt_hw_spi_device_attach

/*
參數(shù) 	描述
bus_name 			SPI 總線名稱
device_name 		SPI 設(shè)備名稱

后面2個參數(shù)是設(shè)置片選引腳:

cs_gpiox			 GPIOA、GPIOB之類...
cs_gpio_pin  		 GPIO口名稱
返回 	——
RT_EOK 		成功
其他錯誤碼 	失敗
*/
rt_err_t rt_hw_spi_device_attach(const char *bus_name, 
								 const char *device_name, 
								 GPIO_TypeDef *cs_gpiox, 
								 uint16_t cs_gpio_pin)

2.2 配置 SPI 設(shè)備

上面介紹 SPI 通訊基礎(chǔ)的時候講到過 SPI 的工作模式等細(xì)節(jié),RT-Thread 里使用 SPI 配置函數(shù)進(jìn)行配置:

/*
參數(shù) 		描述
device 	SPI 設(shè)備句柄
cfg 	SPI 配置參數(shù)指針
返回 	——
RT_EOK 	成功
*/
rt_err_t rt_spi_configure(struct rt_spi_device        *device,
                          struct rt_spi_configuration *cfg)

...

/**
 * SPI configuration structure
 */
struct rt_spi_configuration
{
    rt_uint8_t mode;        /* 模式 */
    rt_uint8_t data_width;  /* 數(shù)據(jù)寬度,可取8位、16位、32位 */
    rt_uint16_t reserved;   /* 保留 */
    
    rt_uint32_t max_hz;     /* 最大頻率 */
};


/**
 * 上面結(jié)構(gòu)體第一個參數(shù): mode
 * SPI configuration structure
 * 其中與 SPI mode 相關(guān)的宏定義有
 */
#define RT_SPI_CPHA     (1<<0)                             /* bit[0]:CPHA, clock phase */
#define RT_SPI_CPOL     (1<<1)                             /* bit[1]:CPOL, clock polarity */
/* 設(shè)置數(shù)據(jù)傳輸順序是MSB位在前還是LSB位在前 */
#define RT_SPI_LSB      (0<<2)                             /* bit[2]: 0-LSB */
#define RT_SPI_MSB      (1<<2)                             /* bit[2]: 1-MSB */
/* 設(shè)置SPI的主從模式 */
#define RT_SPI_MASTER   (0<<3)                             /* SPI master device */
#define RT_SPI_SLAVE    (1<<3)                             /* SPI slave device */

#define RT_SPI_CS_HIGH  (1<<4)                             /* Chipselect active high */
#define RT_SPI_NO_CS    (1<<5)                             /* No chipselect */
#define RT_SPI_3WIRE    (1<<6)                             /* SI/SO pin shared */
#define RT_SPI_READY    (1<<7)                             /* Slave pulls low to pause */

#define RT_SPI_MODE_MASK    (RT_SPI_CPHA | RT_SPI_CPOL | RT_SPI_MSB | RT_SPI_SLAVE | RT_SPI_CS_HIGH | RT_SPI_NO_CS | RT_SPI_3WIRE | RT_SPI_READY)
/* 設(shè)置時鐘極性和時鐘相位 */
#define RT_SPI_MODE_0       (0 | 0)                        /* CPOL = 0, CPHA = 0 */
#define RT_SPI_MODE_1       (0 | RT_SPI_CPHA)              /* CPOL = 0, CPHA = 1 */
#define RT_SPI_MODE_2       (RT_SPI_CPOL | 0)              /* CPOL = 1, CPHA = 0 */
#define RT_SPI_MODE_3       (RT_SPI_CPOL | RT_SPI_CPHA)    /* CPOL = 1, CPHA = 1 */

#define RT_SPI_BUS_MODE_SPI         (1<<0)
#define RT_SPI_BUS_MODE_QSPI        (1<<1)

/**
 * 上面結(jié)構(gòu)體第二個和第四個參數(shù): data_width 和 max_hz
 */
//根據(jù) SPI 主設(shè)備及 SPI 從設(shè)備可發(fā)送及接收的數(shù)據(jù)寬度格式 和頻率 設(shè)置。


 /*
 * 示例程序
 */
 struct rt_spi_configuration cfg;
 cfg.data_width = 8;
 cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
 cfg.max_hz = 20 * 1000 *1000;                           /* 20M */

 rt_spi_configure(spi_dev, &cfg);

2.3 訪問 SPI設(shè)備

前面的兩個函數(shù)類似于 SPI 的初始化工作,接下來就是我們熟悉的設(shè)備操作函數(shù):

2.3.1 查找 SPI 設(shè)備

I/O 設(shè)備模型通用的查找函數(shù):

/*
參數(shù) 	描述
name 	SPI 設(shè)備名稱
返回 	——
設(shè)備句柄 	查找到對應(yīng)設(shè)備將返回相應(yīng)的設(shè)備句柄
RT_NULL 	沒有找到設(shè)備
*/
rt_device_t rt_device_find(const char* name);

注意事項和 ADC 設(shè)備一樣,用來接收的設(shè)備句柄不是使用rt_device_t ,但是與 ADC 也有不一樣的地方,具體如下圖:

pYYBAGLCmuWAHOXoAAC7LrtEE1E821.png

因為 SPI 設(shè)備的接口體并沒有 typedef 重定義,所以使用起來還得直接使用結(jié)構(gòu)體指針表示。

2.3.2 自定義數(shù)據(jù)傳輸

自定義傳輸函數(shù)rt_spi_transfer_message,是訪問 SPI 設(shè)備的關(guān)鍵函數(shù)!

獲取到 SPI 設(shè)備句柄就可以使用 SPI 設(shè)備管理接口訪問 SPI 設(shè)備器件,進(jìn)行數(shù)據(jù)收發(fā):

/*
參數(shù) 	描述
device 		SPI 設(shè)備句柄
message 	消息指針
返回 	——
RT_NULL 	成功發(fā)送
非空指針 	發(fā)送失敗,返回指向剩余未發(fā)送的 message 的指針
*/
struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device  *device,
                                               struct rt_spi_message *message)

其中第二個參數(shù),消息的結(jié)構(gòu)體,這也是發(fā)送消息的關(guān)鍵:

/**
 * SPI message structure
 */
struct rt_spi_message
{
    const void *send_buf;           /* 發(fā)送緩沖區(qū)指針,其值為 RT_NULL 時,
    								表示本次傳輸為只接收狀態(tài),不需要發(fā)送數(shù)據(jù)。*/
    void *recv_buf;                 /* 接收緩沖區(qū)指針,其值為 RT_NULL 時,
    								表示本次傳輸為只發(fā)送狀態(tài),不需要保存接收到的數(shù)據(jù) */
    rt_size_t length;               /* 發(fā)送 / 接收 數(shù)據(jù)字節(jié)數(shù),單位為 word ,
    								長度為 8 位時,每個 length 占用 1 個字節(jié);
    								當(dāng)數(shù)據(jù)長度為 16 位時,每個 length 占用 2 個字節(jié)*/
    struct rt_spi_message *next;    /* 指向繼續(xù)發(fā)送的下一條消息的指針 ,
    								若只發(fā)送一條消息,則此指針值為 RT_NULL。
    								多個待傳輸?shù)南⑼ㄟ^ next 指針以單向鏈表的形式連接在一起。*/
    
    unsigned cs_take    : 1;        /* 片選選中 
    								cs_take 值為 1 時,表示在傳輸數(shù)據(jù)前,設(shè)置對應(yīng)的 CS 為有效狀態(tài)。*/
    unsigned cs_release : 1;        /* 釋放片選 
   									 cs_release 值為 1 時,表示在數(shù)據(jù)傳輸結(jié)束后,釋放對應(yīng)的 CS。*/
};

關(guān)于最后兩個參數(shù):

傳輸?shù)牡谝粭l消息 cs_take 需置為 1,設(shè)置片選為有效,

傳輸?shù)淖詈笠粭l消息的 cs_release 需置 1,釋放片選。

示例 1 ,只發(fā)一條(主要關(guān)注最后兩個參數(shù)的設(shè)置):

struct rt_spi_message msg1;


msg1.send_buf   = send_buf;
msg1.recv_buf   = receive_buf;
msg1.length     = send_length;
msg1.cs_take    = 1;     			// 傳輸之前要先把總線拉低
msg1.cs_release = 1;				// 傳輸之后要把總線釋放
msg1.next       = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg1);

示例 2 ,先發(fā)后收(主要關(guān)注最后兩個參數(shù)的設(shè)置):

struct rt_spi_message msg1,msg2;

uint8 id[5] = {0};

msg1.send_buf   = send_buf;
msg1.recv_buf   = RT_NULL;
msg1.length     = send_length;
msg1.cs_take    = 1;     		// 傳輸之前要先把總線拉低
msg1.cs_release = 0;				// 本次結(jié)束之后并不釋放總線,因為還要發(fā)送,所以為0
msg1.next       = &msg2;

msg2.send_buf   = RT_NULL;
msg2.recv_buf   = id;
msg2.length     = 5; 		//接收5個字節(jié)
msg2.cs_take    = 0; 		//前面已經(jīng)拉低了,沒有釋放,所以這里是不需要拉低的
msg2.cs_release = 1;		//但是這個完成以后,需要釋放總線,這是結(jié)尾
msg2.next       = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg1);

示例 3 ,假如有3個 message:

struct rt_spi_message msg1,msg2,msg3;


msg1.send_buf   = send_buf;
msg1.recv_buf   = RT_NULL;
msg1.length     = length1;
msg1.cs_take    = 1;     		// 傳輸之前要先把總線拉低
msg1.cs_release = 0;				// 本次結(jié)束之后并不釋放總線,因為還要發(fā)送,所以為0
msg1.next       = &msg2;

msg2.send_buf   = RT_NULL;
msg2.recv_buf   = receive_buff;
msg2.length     = length2; 		
msg2.cs_take    = 0; 		//前面已經(jīng)拉低了,沒有釋放,所以這里是不需要拉低的
msg2.cs_release = 0;		//這里也不需要釋放,前面會拉,后面會放
msg2.next       = &msg3;


msg3.send_buf   = RT_NULL;
msg3.recv_buf   = receive_buff;
msg3.length     = len3; 		//
msg3.cs_take    = 0; 		//前面已經(jīng)拉低了,沒有釋放,所以這里是不需要拉低的
msg3.cs_release = 1;		//但是這個完成以后,需要釋放總線,這是結(jié)尾
msg3.next       = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg1)

2.3.3 數(shù)據(jù)收發(fā)函數(shù)

除了上面通用的自定義數(shù)據(jù)傳輸函數(shù), RT-Thread 還提供了一系列簡單的數(shù)據(jù)收發(fā)函數(shù),其實都是通過上面的函數(shù)演變而來,我們也簡單的過一遍:

傳輸一次數(shù)據(jù):

/*
參數(shù) 	描述
device 		SPI 設(shè)備句柄
send_buf 	發(fā)送數(shù)據(jù)緩沖區(qū)指針
recv_buf 	接收數(shù)據(jù)緩沖區(qū)指針
length 		發(fā)送/接收 數(shù)據(jù)字節(jié)數(shù)
返回 	——
0 	傳輸失敗
非 0 值 	成功傳輸?shù)淖止?jié)數(shù)
*/
rt_size_t rt_spi_transfer(struct rt_spi_device *device,
                          const void           *send_buf,
                          void                 *recv_buf,
                          rt_size_t             length)

使用此函數(shù)等同于:

struct rt_spi_message msg;

msg.send_buf   = send_buf;
msg.recv_buf   = recv_buf;
msg.length     = length;
msg.cs_take    = 1;
msg.cs_release = 1;
msg.next        = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg);

發(fā)送一次數(shù)據(jù):

/*
參數(shù) 	描述
device 		SPI 設(shè)備句柄
send_buf 	發(fā)送數(shù)據(jù)緩沖區(qū)指針
length 		發(fā)送數(shù)據(jù)字節(jié)數(shù)
返回 	——
0 	發(fā)送失敗
非 0 值 	成功發(fā)送的字節(jié)數(shù)
*/
rt_inline rt_size_t rt_spi_send(struct rt_spi_device *device,
                                const void           *send_buf,
                                rt_size_t             length)
{
    return rt_spi_transfer(device, send_buf, RT_NULL, length);
}

此函數(shù)直接是上面函數(shù)忽略接收數(shù)據(jù)的效果,可以直接看上面的函數(shù)內(nèi)容。

接收一次數(shù)據(jù):

/*
參數(shù) 	描述
device 		SPI 設(shè)備句柄
recv_buf 	接收數(shù)據(jù)緩沖區(qū)指針
length 		接收數(shù)據(jù)字節(jié)數(shù)
返回 	——
0 		接收失敗
非 0 值 	成功接收的字節(jié)數(shù)
*/
rt_inline rt_size_t rt_spi_recv(struct rt_spi_device *device,
                                void                 *recv_buf,
                                rt_size_t             length)
{
    return rt_spi_transfer(device, RT_NULL, recv_buf, length);
}

與上面發(fā)送一次數(shù)據(jù)相反,傳輸一次數(shù)據(jù)函數(shù)忽略接收的數(shù)據(jù)。

連續(xù)兩次發(fā)送數(shù)據(jù):

/*
參數(shù) 	描述
device 	SPI 設(shè)備句柄
send_buf1 	發(fā)送數(shù)據(jù)緩沖區(qū) 1 指針
send_length1 	發(fā)送數(shù)據(jù)緩沖區(qū) 1 數(shù)據(jù)字節(jié)數(shù)
send_buf2 	發(fā)送數(shù)據(jù)緩沖區(qū) 2 指針
send_length2 	發(fā)送數(shù)據(jù)緩沖區(qū) 2 數(shù)據(jù)字節(jié)數(shù)
返回 	——
RT_EOK 	發(fā)送成功
-RT_EIO 	發(fā)送失敗
*/
rt_err_t rt_spi_send_then_send(struct rt_spi_device *device,
                               const void           *send_buf1,
                               rt_size_t             send_length1,
                               const void           *send_buf2,
                               rt_size_t             send_length2)

本函數(shù)適合向 SPI 設(shè)備中寫入一塊數(shù)據(jù),第一次先發(fā)送命令和地址等數(shù)據(jù),第二次再發(fā)送指定長度的數(shù)據(jù)。

之所以分兩次發(fā)送而不是合并成一個數(shù)據(jù)塊發(fā)送,或調(diào)用兩次 rt_spi_send(),是因為在大部分的數(shù)據(jù)寫操作中,都需要先發(fā)命令和地址,長度一般只有幾個字節(jié)。如果與后面的數(shù)據(jù)合并在一起發(fā)送,將需要進(jìn)行內(nèi)存空間申請和大量的數(shù)據(jù)搬運(yùn)。

而如果調(diào)用兩次 rt_spi_send(),那么在發(fā)送完命令和地址后,片選會被釋放,大部分 SPI 設(shè)備都依靠設(shè)置片選一次有效為命令的起始,所以片選在發(fā)送完命令或地址數(shù)據(jù)后被釋放,則此次操作被丟棄。

使用此函數(shù)等同于:

struct rt_spi_message msg1,msg2;

msg1.send_buf   = send_buf1;
msg1.recv_buf   = RT_NULL;
msg1.length     = send_length1;
msg1.cs_take    = 1;
msg1.cs_release = 0;
msg1.next       = &msg2;

msg2.send_buf   = send_buf2;
msg2.recv_buf   = RT_NULL;
msg2.length     = send_length2;
msg2.cs_take    = 0;
msg2.cs_release = 1;
msg2.next       = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg1);

先發(fā)送后接收數(shù)據(jù):

/*
參數(shù) 	描述
device 			SPI 從設(shè)備句柄
send_buf 		發(fā)送數(shù)據(jù)緩沖區(qū)指針
send_length 	發(fā)送數(shù)據(jù)緩沖區(qū)數(shù)據(jù)字節(jié)數(shù)
recv_buf 		接收數(shù)據(jù)緩沖區(qū)指針
recv_length 	接收數(shù)據(jù)字節(jié)數(shù)
返回 	——
RT_EOK 		成功
-RT_EIO 	失敗
*/
rt_err_t rt_spi_send_then_recv(struct rt_spi_device *device,
                               const void           *send_buf,
                               rt_size_t             send_length,
                               void                 *recv_buf,
                               rt_size_t             recv_length)

本函數(shù)適合從 SPI 從設(shè)備中讀取一塊數(shù)據(jù),第一次會先發(fā)送一些命令和地址數(shù)據(jù),然后再接收指定長度的數(shù)據(jù)。

使用此函數(shù)等同于:

struct rt_spi_message msg1,msg2;

msg1.send_buf   = send_buf;
msg1.recv_buf   = RT_NULL;
msg1.length     = send_length;
msg1.cs_take    = 1;
msg1.cs_release = 0;
msg1.next       = &msg2;

msg2.send_buf   = RT_NULL;
msg2.recv_buf   = recv_buf;
msg2.length     = recv_length;
msg2.cs_take    = 0;
msg2.cs_release = 1;
msg2.next       = RT_NULL;


rt_spi_transfer_message(struct rt_spi_device *device, &msg1);

2.3.4 特殊場景

特殊場景部分暫時并不能體會其中的意義,所以這里直接套用官方的說明,等以后再使用過程中如果確實遇到問題,再來更新自己的心得體會。

在一些特殊的使用場景,某個設(shè)備希望獨(dú)占總線一段時間,且期間要保持片選一直有效,期間數(shù)據(jù)傳輸可能是間斷的,則可以按照如所示步驟使用相關(guān)接口。傳輸數(shù)據(jù)函數(shù)必須使用 rt_spi_transfer_message(),并且此函數(shù)每個待傳輸消息的片選控制域 cs_take 和 cs_release 都要設(shè)置為 0 值,因為片選已經(jīng)使用了其他接口控制,不需要在數(shù)據(jù)傳輸?shù)臅r候控制。

獲取總線:

在多線程的情況下,同一個 SPI 總線可能會在不同的線程中使用,為了防止 SPI 總線正在傳輸?shù)臄?shù)據(jù)丟失,從設(shè)備在開始傳輸數(shù)據(jù)前需要先獲取 SPI 總線的使用權(quán),獲取成功才能夠使用總線傳輸數(shù)據(jù):

/*
參數(shù) 	描述
device 	SPI 設(shè)備句柄
返回 	——
RT_EOK 	成功
錯誤碼 	失敗
*/
rt_err_t rt_spi_take_bus(struct rt_spi_device *device)

選中片選:

從設(shè)備獲取總線的使用權(quán)后,需要設(shè)置自己對應(yīng)的片選信號為有效:

/*
參數(shù) 	描述
device 	SPI 設(shè)備句柄
返回 	——
0 	成功
錯誤碼 	失敗
*/
rt_err_t rt_spi_take(struct rt_spi_device *device);

增加一條消息:

使用 rt_spi_transfer_message() 傳輸消息時,所有待傳輸?shù)南⒍际且詥蜗蜴湵淼男问竭B接起來的:

/*
參數(shù) 	描述
list 		待傳輸?shù)南㈡湵砉?jié)點
message 	新增消息指針
*/
rt_inline void rt_spi_message_append(struct rt_spi_message *list,
                                     struct rt_spi_message *message)

釋放片選:

傳輸完成釋放片選:

/*
device 	SPI 設(shè)備句柄
返回 	——
0 		成功
錯誤碼 	失敗
*/
rt_err_t rt_spi_release(struct rt_spi_device *device)

釋放總線:

從設(shè)備不在使用 SPI 總線傳輸數(shù)據(jù),必須盡快釋放總線,這樣其他從設(shè)備才能使用 SPI 總線傳輸數(shù)據(jù):

/*
參數(shù) 	描述
device 	SPI 設(shè)備句柄
返回 	——
RT_EOK 	成功
*/
rt_err_t rt_spi_release_bus(struct rt_spi_device *device);

三、SPI 設(shè)備測試

與上一篇文章說的 ADC 設(shè)備類似,我們可以通過,但是也需要注意他的使用步驟:

3.1 SPI 設(shè)備使用步驟

board.h 文件中,我們可以查看其中關(guān)于 SPI的 使用步驟的注釋:

poYBAGLCmuaAB_wYAACYsFC9_Bc498.png

1、首先,在 RT-Thread Studio 工程中,打開 RT-Thread Settings,使能 SPI 驅(qū)動,如下圖所示:

pYYBAGLCmuaANmPxAABEWC_r-C4633.png

2、 宏定義 #define BSP_USING_SPI1(根據(jù)自己使用的設(shè)備硬件連接定義):

比如我使用的開發(fā)板原理圖(忽略當(dāng)時的引腳標(biāo)號,這里應(yīng)該是 SPI1,當(dāng)時寫標(biāo)號居然寫的是 SPI2 ):

poYBAGLCmueAah4EAAB9cnNa4ws828.png

查看對應(yīng)的手冊資料

pYYBAGLCmueAW-CxAACHzAL-lVQ135.png

所以我們需要使能的是 SPI1:

poYBAGLCmueAM0i7AAAiiAlf7hs304.png

3、通過STM32CubeMX 配置 SPI :

和上一篇文章的 ADC 設(shè)備一樣進(jìn)行操作,如下圖:

pYYBAGLCmueAeyVBAADYIyGYG5Y639.png

到這一步,我們已經(jīng)能夠找到我們需要的 HAL_SPI_MspInit 文件了,通過 spi.h 頭文件找到 spi.c 文件中的這個函數(shù):

poYBAGLCmuiAQSnOAACatt529PI091.png

4、 把HAL_SPI_MspInit 函數(shù)復(fù)制到 board.c 文件最后面,如下圖:

pYYBAGLCmuiAEDhIAACi56zeZhc351.png

5. 查看 stm32xxxx_hal_config.h 文件SPI 模塊是否使能:

在上一篇文章 ADC 步驟中已經(jīng)講解過,使用 STM32CubeMX 設(shè)置以后,文件會自動使能:

poYBAGLCmuiAKyBXAABZrNA-IYk507.png

到這里 SPI 的配置就算全部完成了,我們可以直接在應(yīng)用程序中,使用 SPI 設(shè)備操作函數(shù)實現(xiàn) SPI 的讀取。

3.2 測試

我們板載的是SPI設(shè)備是 W25Q128 ,我們測試一下 RT-Thread 的 SPI 設(shè)備模型是否能夠正常通行,這里只做簡單的讀取 ID 的測試,官方的示例也是針對 W25Qxx 系列的,但是我還是按照自己的理解來進(jìn)行。

第一步:檢查 spi 總線

我們根據(jù)上面的使用步驟,配置好 SPI ,我們應(yīng)用程序什么都不操作,看看初始化以后是否有 spi1 總線設(shè)備,如下圖:

pYYBAGLCmuiAGbRJAABLCU3AwJI846.png

第二步:掛載 spi 設(shè)備至 spi 總線

確認(rèn)了上電初始化以后 spi1 總線就已經(jīng)存在,我們就可以使用 SPI 的操作函數(shù)進(jìn)行,我們先把 spi 設(shè)備掛載上 spi 總線,然后進(jìn)行必要的配置,操作代碼如圖:

poYBAGLCmumANjmaAACe1gqastk553.png

到這一步,看可以看設(shè)備是否正常注冊:

pYYBAGLCmumAAGyJAABly8tj97o828.png

第三部,通訊

好了,接下來就可以經(jīng)常正常的操作了,官方的示例是讀取 W25Qxx 的 ID,至于讀取 ID 操作流程,是需要查看 芯片手冊的,但是我還想想到曾經(jīng)在裸機(jī)使用過這個 SPI Flash ,那么我可以直接參考以前的驅(qū)動代碼,這樣就省去了再一次的手冊查看資料 = = !

上一下裸機(jī)的有關(guān)操作代碼:

//讀取芯片ID W25Q128的ID:0XEF17
u16 SPI_Flash_ReadID()
{
	u16 Temp = 0;	  
	W25Qxx_CS_ON;				    
	SPI1_ReadWriteByte(W25X_ManufactDeviceID);// 
	SPI1_ReadWriteByte(0x00); 	    
	SPI1_ReadWriteByte(0x00); 	    
	SPI1_ReadWriteByte(0x00); 	 // 		   
	Temp|=SPI1_ReadWriteByte(0xFF)<<8;  
	Temp|=SPI1_ReadWriteByte(0xFF);	 
	W25Qxx_CS_OFF;				    
	return Temp;	
}


//指令表
#define W25X_WriteEnable		0x06 
#define W25X_WriteDisable		0x04 
#define W25X_ReadStatusReg		0x05 
#define W25X_WriteStatusReg		0x01 
#define W25X_ReadData			0x03 
#define W25X_FastReadData		0x0B 
#define W25X_FastReadDual		0x3B 
#define W25X_PageProgram		0x02 
#define W25X_BlockErase			0xD8 
#define W25X_SectorErase		0x20 
#define W25X_ChipErase			0xC7 
#define W25X_PowerDown			0xB9 
#define W25X_ReleasePowerDown	0xAB 
#define W25X_DeviceID			0xAB 
#define W25X_ManufactDeviceID	0x90 
#define W25X_JedecDeviceID		0x9F 

上電的時候讀取一次設(shè)備的 ID,如果 讀取的 ID 正常,說明設(shè)備正常,可以進(jìn)行接下來的通訊。

通過上面的操作我們可以看到這個操作流程,先發(fā)送一個字節(jié)消息(讀取指令), 然后再讀取 5個字節(jié)的消息,第 4個字節(jié)和第5個字節(jié)就是 SPI Flash 的設(shè)備ID (數(shù)據(jù)寬度 8 位),通過手冊我們可以可以看到說明:

poYBAGLCmumAM6mbAAIMKDFtcWs687.png

搞清楚了流程,下面的讀取代碼,其實和官方基本一樣:

pYYBAGLCmuqAblL3AACkwvivNFY297.png

測試結(jié)果:

poYBAGLCmuqADHF0AABJHUm8VLk989.png

測試出來居然是反了,這個倒是無所謂,因為簡單,反的原因這里不深究了。

當(dāng)然上面是用的自定義數(shù)據(jù)傳輸函數(shù)rt_spi_transfer_message實現(xiàn),我們也可以通過上面講到的先發(fā)送后接收數(shù)據(jù)函數(shù)rt_spi_send_then_recv實現(xiàn):

pYYBAGLCmuqAUzdTAAA0lcJSsuM375.png

可以看到使用這種專有函數(shù),程序會更加簡單,但是我更加建議使用自定義,因為可以滿足不同需求。

結(jié)語

本文我們學(xué)習(xí)了 RT-Thread 中 SPI 設(shè)備的使用方法,最終也通過簡單的測試成功操作了 SPI 設(shè)備。

但是我們并沒有進(jìn)行正真的數(shù)據(jù)讀寫,在實際應(yīng)用中,我們需要用到不同的 SPI 設(shè)備,就算是 SPI Flash 這一種設(shè)備,都有不同廠家不同型號的,難免有不同之處。

RT-Thread 有一個很大的特點在于他的生態(tài)比一般的 RTOS 完善,我們在實際應(yīng)用中,有許許多多現(xiàn)成的官方或者很多開發(fā)者提供的組件或者軟件包,我們可以直接導(dǎo)入工程進(jìn)行使用。

比如就本文我們學(xué)習(xí)的 SPI 設(shè)備,我們就可以使用官方標(biāo)準(zhǔn)的組件 — SFUD組件。

對于RT-Thread 設(shè)備模型篇的內(nèi)容,我也就更新到這篇文章,接下來就要開始學(xué)習(xí)使用 RT-Thread 的組件和軟件包。

希望大家多多支持!本文就到這里,謝謝!

審核編輯:符乾江

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 操作系統(tǒng)
    +關(guān)注

    關(guān)注

    37

    文章

    6545

    瀏覽量

    122752
  • RT-Thread
    +關(guān)注

    關(guān)注

    31

    文章

    1239

    瀏覽量

    39442
收藏 人收藏

    評論

    相關(guān)推薦

    RT-Thread記錄(十、全面認(rèn)識 I/O 設(shè)備模型

    學(xué)完 RT-Thread 內(nèi)核,從本文開始熟悉了解 RT-Thread I/O 設(shè)備管理相關(guān)知識。
    的頭像 發(fā)表于 06-30 10:38 ?3942次閱讀
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>記錄</b>(十、全面認(rèn)識 <b class='flag-5'>I</b>/<b class='flag-5'>O</b> <b class='flag-5'>設(shè)備</b><b class='flag-5'>模型</b>)

    RT-Thread記錄(十一、UART設(shè)備—源碼解析)

    一文帶你深入理解 RT-Thread I/O 設(shè)備模型 — UART 設(shè)備源碼分析。
    的頭像 發(fā)表于 07-01 11:24 ?5279次閱讀
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>記錄</b>(十一、UART<b class='flag-5'>設(shè)備</b>—源碼解析)

    RT-Thread記錄(十四、I/O 設(shè)備模型ADC設(shè)備

    我曾經(jīng)考慮過把 RT-Thread 常用的設(shè)備都寫完,其實通過前面的《全面認(rèn)識 RT-Thread I/O
    的頭像 發(fā)表于 07-04 12:28 ?4175次閱讀
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>記錄</b>(十四、<b class='flag-5'>I</b>/<b class='flag-5'>O</b> <b class='flag-5'>設(shè)備</b><b class='flag-5'>模型</b><b class='flag-5'>之</b>ADC<b class='flag-5'>設(shè)備</b>)

    RT-Thread記錄(十二、UART設(shè)備—使用測試)

    從 UART 設(shè)備開始學(xué)會使用 RT-Thread I/O 設(shè)備模型
    的頭像 發(fā)表于 07-02 12:42 ?4817次閱讀
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>記錄</b>(十二、UART<b class='flag-5'>設(shè)備</b>—使用測試)

    RT-Thread記錄(十三、I/O 設(shè)備模型PIN設(shè)備

    講完UART設(shè)備之后,我們已經(jīng)熟悉RT-Thread I/O 設(shè)備模型了,回頭看看基本的 PIN
    的頭像 發(fā)表于 07-03 11:28 ?4339次閱讀
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>記錄</b>(十三、<b class='flag-5'>I</b>/<b class='flag-5'>O</b> <b class='flag-5'>設(shè)備</b><b class='flag-5'>模型</b><b class='flag-5'>之</b>PIN<b class='flag-5'>設(shè)備</b>)

    基于RT-ThreadSPI通訊

    驅(qū)動層的驅(qū)動。(rt-thread設(shè)備 I/O 模型設(shè)備管理層、
    的頭像 發(fā)表于 08-22 09:28 ?1534次閱讀

    RT-Thread 的 IO 設(shè)備模型框架是由哪些部分組成的呢

    RT-ThreadI/O 設(shè)備模型框架是由哪些部分組成的呢?接下來由小編給大家詳細(xì)介紹一下。1、R
    發(fā)表于 03-11 18:17

    【原創(chuàng)精選】RT-Thread征文精選技術(shù)文章合集

    RT-Thread記錄(十二、UART設(shè)備—使用測試)RT-Thread記錄(十三、I/
    發(fā)表于 07-26 14:56

    RT-Thread設(shè)備模型框架及創(chuàng)建注冊設(shè)備的實現(xiàn)

    RT-Thread設(shè)備模型框架及創(chuàng)建注冊設(shè)備的實現(xiàn)方式介紹如下:
    的頭像 發(fā)表于 05-28 10:38 ?2044次閱讀
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>設(shè)備</b><b class='flag-5'>模型</b>框架及創(chuàng)建注冊<b class='flag-5'>設(shè)備</b>的實現(xiàn)

    RT-Thread文檔_I/O 設(shè)備模型

    RT-Thread文檔_I/O 設(shè)備模型
    發(fā)表于 02-22 18:31 ?0次下載
    <b class='flag-5'>RT-Thread</b>文檔_<b class='flag-5'>I</b>/<b class='flag-5'>O</b> <b class='flag-5'>設(shè)備</b><b class='flag-5'>模型</b>

    RT-Thread文檔_I2C 總線設(shè)備

    RT-Thread文檔_I2C 總線設(shè)備
    發(fā)表于 02-22 18:35 ?0次下載
    <b class='flag-5'>RT-Thread</b>文檔_<b class='flag-5'>I</b>2C 總線<b class='flag-5'>設(shè)備</b>

    RT-Thread文檔_PWM 設(shè)備

    RT-Thread文檔_PWM 設(shè)備
    發(fā)表于 02-22 18:35 ?1次下載
    <b class='flag-5'>RT-Thread</b>文檔_PWM <b class='flag-5'>設(shè)備</b>

    RT-Thread文檔_SPI 設(shè)備

    RT-Thread文檔_SPI 設(shè)備
    發(fā)表于 02-22 18:36 ?2次下載
    <b class='flag-5'>RT-Thread</b>文檔_<b class='flag-5'>SPI</b> <b class='flag-5'>設(shè)備</b>

    RT-Thread文檔_Pulse Encoder 設(shè)備

    RT-Thread文檔_Pulse Encoder 設(shè)備
    發(fā)表于 02-22 18:39 ?1次下載
    <b class='flag-5'>RT-Thread</b>文檔_Pulse Encoder <b class='flag-5'>設(shè)備</b>

    RT-ThreadI/O設(shè)備模型與分類

    RT- ThreadI/O設(shè)備模型(簡稱“設(shè)備
    的頭像 發(fā)表于 10-11 17:12 ?555次閱讀
    <b class='flag-5'>RT-Thread</b>的<b class='flag-5'>I</b>/<b class='flag-5'>O</b><b class='flag-5'>設(shè)備</b><b class='flag-5'>模型</b>與分類