本文學習一下I/O 設備模型之SPI設備使用,I/O 設備模型篇的最后一篇文章。
目錄
前言
一、SPI 通訊基礎
二、SPI 設備操作函數
2.1 掛載 SPI 設備
2.2 配置 SPI 設備
2.3 訪問 SPI設備
2.3.1 查找 SPI 設備
2.3.2 自定義數據傳輸
2.3.3 數據收發函數
2.3.4 特殊場景
三、SPI 設備測試
3.1 SPI 設備使用步驟
3.2 測試
結語
前言
本文應該是 RT-Thread I/O 設備模型最后一篇,SPI 設備的學習測試。
我以前就說過,我的記錄是以應用為目的,實際上我們在使用 RT-Thread 的時候,有很多常用的設備,官方或者很多開發者都已經給我們寫好了驅動和軟件包,我們并不需要自己重新寫一篇,很多時候直接導入軟件包,直接調用現成的 API 函數就可以。
RT-Thread 文章接下來的系列,應該會更新幾篇 軟件包和組件的使用,本文把 SPI 設備做一個學習測試。
??
本 RT-Thread 專欄記錄的開發環境:
RT-Thread記錄(一、RT-Thread 版本、RT-Thread Studio開發環境 及 配合CubeMX開發快速上手)
RT-Thread記錄(二、RT-Thread內核啟動流程 — 啟動文件和源碼分析)
??
RT-Thread 內核篇系列博文鏈接:
RT-Thread記錄(三、RT-Thread 線程操作函數及線程管理與FreeRTOS的比較)
RT-Thread記錄(四、RT-Thread 時鐘節拍和軟件定時器)
RT-Thread記錄(五、RT-Thread 臨界區保護)
RT-Thread記錄(六、IPC機制之信號量、互斥量和事件集)
RT-Thread記錄(七、IPC機制之郵箱、消息隊列)
RT-Thread記錄(八、理解 RT-Thread 內存管理)
RT-Thread記錄(九、RT-Thread 中斷處理與階段小結)
??
在STM32L051C8 上使用 RT-Thread 應用篇系列博文連接:
RT-Thread 應用篇 — 在STM32L051上使用 RT-Thread (一、無線溫濕度傳感器 之 新建項目)
RT-Thread 應用篇 — 在STM32L051上使用 RT-Thread (二、無線溫濕度傳感器 之 CubeMX配置)
RT-Thread 應用篇 — 在STM32L051上使用 RT-Thread (三、無線溫濕度傳感器 之 I2C通訊)
RT-Thread 應用篇 — 在STM32L051上使用 RT-Thread (四、無線溫濕度傳感器 之 串口通訊)
??
RT-Thread 設備篇系列博文鏈接:
RT-Thread記錄(十、全面認識 RT-Thread I/O 設備模型)
RT-Thread記錄(十一、I/O 設備模型之UART設備 — 源碼解析)
RT-Thread記錄(十二、I/O 設備模型之UART設備 — 使用測試)
RT-Thread記錄(十三、I/O 設備模型之PIN設備)
RT-Thread記錄(十四、I/O 設備模型之ADC設備)
一、SPI 通訊基礎
SPI 通訊基本知識不過多介紹,原理與基礎可自行網上查詢,本文這里只做應用所需的簡單概述:
SPI是串行外設接口(Serial Peripheral Interface)的縮寫,是一種高速的,全雙工,同步的通信總線,SPI 的通訊速度可以達到幾十M,并且在芯片的管腳上只占用四根線:
(1)MISO– Master Input Slave Output,主設備數據輸入,從設備數據輸出;
(2)MOSI– Master Output Slave Input,主設備數據輸出,從設備數據輸入;
(3)SCLK – Serial Clock,時鐘信號,由主設備產生;
(4)CS – Chip Select,從設備使能信號,由主設備控制。
SPI 以主從方式工作,通常有一個主設備和一個或多個從設備。
SPI 通訊有4中模式,由 CPOL (時鐘的極性)和 CPHA (時鐘的相位)決定:
CPOL=0,表示當SCLK=0時處于空閑態,空閑低電平,所以有效狀態就是SCLK處于高電平時
CPOL=1,表示當SCLK=1時處于空閑態,空閑高電平,所以有效狀態就是SCLK處于低電平時
CPHA=0,表示數據采樣是在第1個邊沿
CPHA=1,表示數據采樣是在第2個邊沿
如下表格:
對于我們的從機設備,比如傳感器,支持的模式會使用手冊中說明:比如我們今天要測試的 SPI Flash:
二、SPI 設備操作函數
來了解一下 RT-Thread 提供的 SPI 設備操作函數:
與前面的設備不同的地方在于,SPI 因為可以一主多從,所以 SPI 設備多了一個掛載操作,就是 RT-Thread 系統驅動會注冊好 SPI 總線,然后我們需要把自己所用的 SPI 設備掛載到總線上,使得可以對該設備進行操作 。
☆ 自定義傳輸數據函數 rt_spi_transfer_message
為核心,其實在其之后的那些都可以使用這個函數來表達,這個下文會說明。☆
2.1 掛載 SPI 設備
SPI 驅動注冊完 SPI 總線,需要用 SPI 掛載函數將要使用的 SPI 設備需要掛載到已經注冊好的 SPI 總線上:
/*
參數 描述
device SPI 設備句柄
name SPI 設備名稱
bus_name SPI 總線名稱
user_data 用戶數據指針
返回 ——
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)
此函數用于掛載一個 SPI 設備到指定的 SPI 總線,并向內核注冊 SPI 設備,并將 user_data 保存到 SPI 設備的控制塊里。
一般 SPI 總線命名原則為 spix, SPI 設備命名原則為 spixy ,如 spi10 表示掛載在 spi1 總線上的 0 號設備。
user_data 一般為 SPI 設備的 CS 引腳指針,進行數據傳輸時 SPI 控制器會操作此引腳進行片選。
對于我們測試使用的 STM32 而言,有專門的掛載函數 rt_hw_spi_device_attach
:
/*
參數 描述
bus_name SPI 總線名稱
device_name SPI 設備名稱
后面2個參數是設置片選引腳:
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 設備
上面介紹 SPI 通訊基礎的時候講到過 SPI 的工作模式等細節,RT-Thread 里使用 SPI 配置函數進行配置:
/*
參數 描述
device SPI 設備句柄
cfg SPI 配置參數指針
返回 ——
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; /* 數據寬度,可取8位、16位、32位 */
rt_uint16_t reserved; /* 保留 */
rt_uint32_t max_hz; /* 最大頻率 */
};
/**
* 上面結構體第一個參數: mode
* SPI configuration structure
* 其中與 SPI mode 相關的宏定義有
*/
#define RT_SPI_CPHA (1<<0) /* bit[0]:CPHA, clock phase */
#define RT_SPI_CPOL (1<<1) /* bit[1]:CPOL, clock polarity */
/* 設置數據傳輸順序是MSB位在前還是LSB位在前 */
#define RT_SPI_LSB (0<<2) /* bit[2]: 0-LSB */
#define RT_SPI_MSB (1<<2) /* bit[2]: 1-MSB */
/* 設置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)
/* 設置時鐘極性和時鐘相位 */
#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)
/**
* 上面結構體第二個和第四個參數: data_width 和 max_hz
*/
//根據 SPI 主設備及 SPI 從設備可發送及接收的數據寬度格式 和頻率 設置。
/*
* 示例程序
*/
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設備
前面的兩個函數類似于 SPI 的初始化工作,接下來就是我們熟悉的設備操作函數:
2.3.1 查找 SPI 設備
I/O 設備模型通用的查找函數:
/*
參數 描述
name SPI 設備名稱
返回 ——
設備句柄 查找到對應設備將返回相應的設備句柄
RT_NULL 沒有找到設備
*/
rt_device_t rt_device_find(const char* name);
注意事項和 ADC 設備一樣,用來接收的設備句柄不是使用rt_device_t
,但是與 ADC 也有不一樣的地方,具體如下圖:
因為 SPI 設備的接口體并沒有 typedef 重定義,所以使用起來還得直接使用結構體指針表示。
2.3.2 自定義數據傳輸
自定義傳輸函數rt_spi_transfer_message
,是訪問 SPI 設備的關鍵函數!
獲取到 SPI 設備句柄就可以使用 SPI 設備管理接口訪問 SPI 設備器件,進行數據收發:
/*
參數 描述
device SPI 設備句柄
message 消息指針
返回 ——
RT_NULL 成功發送
非空指針 發送失敗,返回指向剩余未發送的 message 的指針
*/
struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device,
struct rt_spi_message *message)
其中第二個參數,消息的結構體,這也是發送消息的關鍵:
/**
* SPI message structure
*/
struct rt_spi_message
{
const void *send_buf; /* 發送緩沖區指針,其值為 RT_NULL 時,
表示本次傳輸為只接收狀態,不需要發送數據。*/
void *recv_buf; /* 接收緩沖區指針,其值為 RT_NULL 時,
表示本次傳輸為只發送狀態,不需要保存接收到的數據 */
rt_size_t length; /* 發送 / 接收 數據字節數,單位為 word ,
長度為 8 位時,每個 length 占用 1 個字節;
當數據長度為 16 位時,每個 length 占用 2 個字節*/
struct rt_spi_message *next; /* 指向繼續發送的下一條消息的指針 ,
若只發送一條消息,則此指針值為 RT_NULL。
多個待傳輸的消息通過 next 指針以單向鏈表的形式連接在一起。*/
unsigned cs_take : 1; /* 片選選中
cs_take 值為 1 時,表示在傳輸數據前,設置對應的 CS 為有效狀態。*/
unsigned cs_release : 1; /* 釋放片選
cs_release 值為 1 時,表示在數據傳輸結束后,釋放對應的 CS。*/
};
關于最后兩個參數:
傳輸的第一條消息 cs_take 需置為 1,設置片選為有效,
傳輸的最后一條消息的 cs_release 需置 1,釋放片選。
示例 1 ,只發一條(主要關注最后兩個參數的設置):
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 ,先發后收(主要關注最后兩個參數的設置):
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; // 本次結束之后并不釋放總線,因為還要發送,所以為0
msg1.next = &msg2;
msg2.send_buf = RT_NULL;
msg2.recv_buf = id;
msg2.length = 5; //接收5個字節
msg2.cs_take = 0; //前面已經拉低了,沒有釋放,所以這里是不需要拉低的
msg2.cs_release = 1; //但是這個完成以后,需要釋放總線,這是結尾
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; // 本次結束之后并不釋放總線,因為還要發送,所以為0
msg1.next = &msg2;
msg2.send_buf = RT_NULL;
msg2.recv_buf = receive_buff;
msg2.length = length2;
msg2.cs_take = 0; //前面已經拉低了,沒有釋放,所以這里是不需要拉低的
msg2.cs_release = 0; //這里也不需要釋放,前面會拉,后面會放
msg2.next = &msg3;
msg3.send_buf = RT_NULL;
msg3.recv_buf = receive_buff;
msg3.length = len3; //
msg3.cs_take = 0; //前面已經拉低了,沒有釋放,所以這里是不需要拉低的
msg3.cs_release = 1; //但是這個完成以后,需要釋放總線,這是結尾
msg3.next = RT_NULL;
rt_spi_transfer_message(struct rt_spi_device *device, &msg1)
2.3.3 數據收發函數
除了上面通用的自定義數據傳輸函數, RT-Thread 還提供了一系列簡單的數據收發函數,其實都是通過上面的函數演變而來,我們也簡單的過一遍:
傳輸一次數據:
/*
參數 描述
device SPI 設備句柄
send_buf 發送數據緩沖區指針
recv_buf 接收數據緩沖區指針
length 發送/接收 數據字節數
返回 ——
0 傳輸失敗
非 0 值 成功傳輸的字節數
*/
rt_size_t rt_spi_transfer(struct rt_spi_device *device,
const void *send_buf,
void *recv_buf,
rt_size_t length)
使用此函數等同于:
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);
發送一次數據:
/*
參數 描述
device SPI 設備句柄
send_buf 發送數據緩沖區指針
length 發送數據字節數
返回 ——
0 發送失敗
非 0 值 成功發送的字節數
*/
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);
}
此函數直接是上面函數忽略接收數據的效果,可以直接看上面的函數內容。
接收一次數據:
/*
參數 描述
device SPI 設備句柄
recv_buf 接收數據緩沖區指針
length 接收數據字節數
返回 ——
0 接收失敗
非 0 值 成功接收的字節數
*/
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);
}
與上面發送一次數據相反,傳輸一次數據函數忽略接收的數據。
連續兩次發送數據:
/*
參數 描述
device SPI 設備句柄
send_buf1 發送數據緩沖區 1 指針
send_length1 發送數據緩沖區 1 數據字節數
send_buf2 發送數據緩沖區 2 指針
send_length2 發送數據緩沖區 2 數據字節數
返回 ——
RT_EOK 發送成功
-RT_EIO 發送失敗
*/
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)
本函數適合向 SPI 設備中寫入一塊數據,第一次先發送命令和地址等數據,第二次再發送指定長度的數據。
之所以分兩次發送而不是合并成一個數據塊發送,或調用兩次 rt_spi_send(),是因為在大部分的數據寫操作中,都需要先發命令和地址,長度一般只有幾個字節。如果與后面的數據合并在一起發送,將需要進行內存空間申請和大量的數據搬運。
而如果調用兩次 rt_spi_send(),那么在發送完命令和地址后,片選會被釋放,大部分 SPI 設備都依靠設置片選一次有效為命令的起始,所以片選在發送完命令或地址數據后被釋放,則此次操作被丟棄。
使用此函數等同于:
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);
先發送后接收數據:
/*
參數 描述
device SPI 從設備句柄
send_buf 發送數據緩沖區指針
send_length 發送數據緩沖區數據字節數
recv_buf 接收數據緩沖區指針
recv_length 接收數據字節數
返回 ——
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)
本函數適合從 SPI 從設備中讀取一塊數據,第一次會先發送一些命令和地址數據,然后再接收指定長度的數據。
使用此函數等同于:
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 特殊場景
特殊場景部分暫時并不能體會其中的意義,所以這里直接套用官方的說明,等以后再使用過程中如果確實遇到問題,再來更新自己的心得體會。
在一些特殊的使用場景,某個設備希望獨占總線一段時間,且期間要保持片選一直有效,期間數據傳輸可能是間斷的,則可以按照如所示步驟使用相關接口。傳輸數據函數必須使用 rt_spi_transfer_message(),并且此函數每個待傳輸消息的片選控制域 cs_take 和 cs_release 都要設置為 0 值,因為片選已經使用了其他接口控制,不需要在數據傳輸的時候控制。
獲取總線:
在多線程的情況下,同一個 SPI 總線可能會在不同的線程中使用,為了防止 SPI 總線正在傳輸的數據丟失,從設備在開始傳輸數據前需要先獲取 SPI 總線的使用權,獲取成功才能夠使用總線傳輸數據:
/*
參數 描述
device SPI 設備句柄
返回 ——
RT_EOK 成功
錯誤碼 失敗
*/
rt_err_t rt_spi_take_bus(struct rt_spi_device *device)
選中片選:
從設備獲取總線的使用權后,需要設置自己對應的片選信號為有效:
/*
參數 描述
device SPI 設備句柄
返回 ——
0 成功
錯誤碼 失敗
*/
rt_err_t rt_spi_take(struct rt_spi_device *device);
增加一條消息:
使用 rt_spi_transfer_message() 傳輸消息時,所有待傳輸的消息都是以單向鏈表的形式連接起來的:
/*
參數 描述
list 待傳輸的消息鏈表節點
message 新增消息指針
*/
rt_inline void rt_spi_message_append(struct rt_spi_message *list,
struct rt_spi_message *message)
釋放片選:
傳輸完成釋放片選:
/*
device SPI 設備句柄
返回 ——
0 成功
錯誤碼 失敗
*/
rt_err_t rt_spi_release(struct rt_spi_device *device)
釋放總線:
從設備不在使用 SPI 總線傳輸數據,必須盡快釋放總線,這樣其他從設備才能使用 SPI 總線傳輸數據:
/*
參數 描述
device SPI 設備句柄
返回 ——
RT_EOK 成功
*/
rt_err_t rt_spi_release_bus(struct rt_spi_device *device);
三、SPI 設備測試
與上一篇文章說的 ADC 設備類似,我們可以通過,但是也需要注意他的使用步驟:
3.1 SPI 設備使用步驟
在 board.h
文件中,我們可以查看其中關于 SPI的 使用步驟的注釋:
1、首先,在 RT-Thread Studio 工程中,打開 RT-Thread Settings,使能 SPI 驅動,如下圖所示:
2、 宏定義 #define BSP_USING_SPI1
(根據自己使用的設備硬件連接定義):
比如我使用的開發板原理圖(忽略當時的引腳標號,這里應該是 SPI1,當時寫標號居然寫的是 SPI2 ):
查看對應的手冊資料:
所以我們需要使能的是 SPI1:
3、通過STM32CubeMX 配置 SPI :
和上一篇文章的 ADC 設備一樣進行操作,如下圖:
到這一步,我們已經能夠找到我們需要的 HAL_SPI_MspInit
文件了,通過 spi.h
頭文件找到 spi.c
文件中的這個函數:
4、 把HAL_SPI_MspInit
函數復制到 board.c
文件最后面,如下圖:
5. 查看 stm32xxxx_hal_config.h
文件SPI 模塊是否使能:
在上一篇文章 ADC 步驟中已經講解過,使用 STM32CubeMX 設置以后,文件會自動使能:
到這里 SPI 的配置就算全部完成了,我們可以直接在應用程序中,使用 SPI 設備操作函數實現 SPI 的讀取。
3.2 測試
我們板載的是SPI設備是 W25Q128 ,我們測試一下 RT-Thread 的 SPI 設備模型是否能夠正常通行,這里只做簡單的讀取 ID 的測試,官方的示例也是針對 W25Qxx 系列的,但是我還是按照自己的理解來進行。
第一步:檢查 spi 總線
我們根據上面的使用步驟,配置好 SPI ,我們應用程序什么都不操作,看看初始化以后是否有 spi1 總線設備,如下圖:
第二步:掛載 spi 設備至 spi 總線
確認了上電初始化以后 spi1 總線就已經存在,我們就可以使用 SPI 的操作函數進行,我們先把 spi 設備掛載上 spi 總線,然后進行必要的配置,操作代碼如圖:
到這一步,看可以看設備是否正常注冊:
第三部,通訊
好了,接下來就可以經常正常的操作了,官方的示例是讀取 W25Qxx 的 ID,至于讀取 ID 操作流程,是需要查看 芯片手冊的,但是我還想想到曾經在裸機使用過這個 SPI Flash ,那么我可以直接參考以前的驅動代碼,這樣就省去了再一次的手冊查看資料 = = !
上一下裸機的有關操作代碼:
//讀取芯片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
上電的時候讀取一次設備的 ID,如果 讀取的 ID 正常,說明設備正常,可以進行接下來的通訊。
通過上面的操作我們可以看到這個操作流程,先發送一個字節消息(讀取指令), 然后再讀取 5個字節的消息,第 4個字節和第5個字節就是 SPI Flash 的設備ID (數據寬度 8 位),通過手冊我們可以可以看到說明:
搞清楚了流程,下面的讀取代碼,其實和官方基本一樣:
測試結果:
測試出來居然是反了,這個倒是無所謂,因為簡單,反的原因這里不深究了。
當然上面是用的自定義數據傳輸函數rt_spi_transfer_message
實現,我們也可以通過上面講到的先發送后接收數據函數rt_spi_send_then_recv
實現:
可以看到使用這種專有函數,程序會更加簡單,但是我更加建議使用自定義,因為可以滿足不同需求。
結語
本文我們學習了 RT-Thread 中 SPI 設備的使用方法,最終也通過簡單的測試成功操作了 SPI 設備。
但是我們并沒有進行正真的數據讀寫,在實際應用中,我們需要用到不同的 SPI 設備,就算是 SPI Flash 這一種設備,都有不同廠家不同型號的,難免有不同之處。
RT-Thread 有一個很大的特點在于他的生態比一般的 RTOS 完善,我們在實際應用中,有許許多多現成的官方或者很多開發者提供的組件或者軟件包,我們可以直接導入工程進行使用。
比如就本文我們學習的 SPI 設備,我們就可以使用官方標準的組件 — SFUD組件。
對于RT-Thread 設備模型篇的內容,我也就更新到這篇文章,接下來就要開始學習使用 RT-Thread 的組件和軟件包。
希望大家多多支持!本文就到這里,謝謝!
審核編輯:符乾江
-
操作系統
+關注
關注
37文章
6747瀏覽量
123201 -
RT-Thread
+關注
關注
31文章
1274瀏覽量
39940
發布評論請先 登錄
相關推薦
評論