文章目錄
rt-thread SDIO驅(qū)動框架分析之SD卡驅(qū)動
1. 前言
2. SDIO通用驅(qū)動框架介紹
3. 文件架構(gòu)分析
4. SDIO設備驅(qū)動分析
5. SDIO設備驅(qū)動架構(gòu)分析
6. 調(diào)試記錄
7. 總結(jié)
1. 前言
RT-Thread是一款國產(chǎn)化的嵌入式操作系統(tǒng),目前在嵌入式領域得到廣泛應用,其強大的擴展功能以及通用的外設驅(qū)動框架備受大家追捧。
關于基本的外設驅(qū)動,其官網(wǎng)上基本也都有部分描述,但是關于SDIO設備驅(qū)動目前為止還沒有相關文檔說明,因此本文筆者將根據(jù)自己的調(diào)試使用經(jīng)驗,與大家分享下rtthread的通用SDIO設備驅(qū)動的實現(xiàn)。
本文基于代碼倉庫 rt-thread/bsp/stm32/stm32f103-fire-arbitrary 分析代碼
分支:main
commit:6808f48bdcf914f03ac757cc19b264a5d0db56de
說明:main分支會有不斷更新,但是SDIO驅(qū)動框架目前應該不會有大變更
硬件介紹:
控制器:STM32 基于手上為數(shù)不多的野火開發(fā)板吧
SD卡:本次采用的并非SD卡,而是創(chuàng)世CS家的一顆SD Nand, CSNP4GCR01-AMW,有幸申請到了一顆樣片
這里多說幾句,SD nand使用起來和SD卡完全一樣,而且SD Nand相比SD卡感覺好用太多,貼片LGA-8封裝,和SPI flash 差不多,完美的解決了SD卡松動導致系統(tǒng)不穩(wěn)定的問題,而且容量又大,個人感覺以后必定是嵌入式存儲應用上的主流 (除了價格貴點啥都好,哈哈)想要樣片試試水的可以去找深圳雷龍公司官網(wǎng)申請下
2. SDIO通用驅(qū)動框架介紹
首先來介紹下 SDIO 通用驅(qū)動框架。
RT-Thread 區(qū)別于其他操作系統(tǒng),如FreeRTOS,的一大重要特征是,RT-Thread 中引入了設備驅(qū)動框架,并且針對絕大多數(shù)外設基本上都已完成對應的設備驅(qū)動框架編寫,所謂的設備驅(qū)動框架,也就是我們所說的建立在應用層與底層驅(qū)動層之間的中間件
如下圖所示:
應用層:完成業(yè)務應用,調(diào)用通用接口操作設備驅(qū)動層
設備驅(qū)動框架層:完成外設通用驅(qū)動框架設計,脫離具體的芯片,將驅(qū)動中相同部分,如針對SPI,關于SPI的完整讀寫邏輯等抽離出來
設備驅(qū)動層:完成對應芯片的外設驅(qū)動程序編寫,實現(xiàn)設備驅(qū)動框架層的具體接口
對于SDIO外設亦是如此:
在設備驅(qū)動框架層中,實現(xiàn)SD卡、SDIO卡、MMC卡的通用外設驅(qū)動邏輯,如卡的識別、卡的模塊切換、卡的讀寫操作等,這些都是通用的,遵循SD標準協(xié)議;
在設備驅(qū)動層中,根據(jù)對應的硬件,完成具體芯片的SDIO外設配置,并實現(xiàn)設備驅(qū)動框架層所需要實現(xiàn)的具體接口,如發(fā)送CMD命令等。
在應用層實現(xiàn)具體的應用,應用層與驅(qū)動層解耦
通過這種方式,這樣便可以輕松的做到:
需要驅(qū)動具體的SD、SDIO、MMC時,根據(jù)具體的芯片實現(xiàn)對應的SDIO驅(qū)動接口即可
應用層可直接移植,如出現(xiàn)方案芯片替代時,只需完成設備驅(qū)動層適配即可
這也就是RT-Thread讓眾多開發(fā)者瘋狂追捧的重大原因了,接下來,我們將具體分析關于SD卡的具體框架層實現(xiàn),關于SDIO卡、MMC卡,由于使用不多,本文不做深入分析。
3. 文件架構(gòu)分析
首先我們先來看下SDIO驅(qū)動框架有關文件及架構(gòu)
SDIO驅(qū)動框架文件:
SDIO驅(qū)動框架文件架構(gòu):
4. SDIO設備驅(qū)動分析
設備驅(qū)動與驅(qū)動框架文件在不同的目錄,設備驅(qū)動一般在 bsp 目錄中
通常設備驅(qū)動完成以下幾個事情:
初始化具體外設有關數(shù)據(jù)結(jié)構(gòu);
完成具體外設初始化程序編寫;
實現(xiàn)設備框架層的具體接口,如:open,read,write,close,control 等;
將具體設備注冊到內(nèi)核中;
需要注意的是,SDIO設備驅(qū)動會有些許區(qū)別,在SDIO設備驅(qū)動程序中,主要完成以下幾件事:
初始化具體外設有關數(shù)據(jù)結(jié)構(gòu);
SDIO外設的初始化配置;
實現(xiàn)設備框架層的以下幾個接口:
struct rt_mmcsd_host_ops {
void (*request)(struct rt_mmcsd_host *host, struct rt_mmcsd_req *req);
void (*set_iocfg)(struct rt_mmcsd_host *host, struct rt_mmcsd_io_cfg *io_cfg);
rt_int32_t (*get_card_status)(struct rt_mmcsd_host *host);
void (*enable_sdio_irq)(struct rt_mmcsd_host *host, rt_int32_t en);
};
4.通知驅(qū)動框架層(此處demo程序默認上電前sd卡已接入);
以 rt-thread/bsp/stm32/libraries/HAL_Drivers/drv_sdio.c 程序為例,SDIO驅(qū)動層程序從 rt_hw_sdio_init 函數(shù)開始,由于使能了自動初始化,此函數(shù)由 INIT_DEVICE_EXPORT(rt_hw_sdio_init); 宏實現(xiàn)初始化調(diào)用
(關于自動初始化如何實現(xiàn)的細節(jié),可參考筆者另外一篇博文對自動初始化的詳細分析:代碼自動初始化(點擊跳轉(zhuǎn)))
在 rt_hw_sdio_init 函數(shù)中,驅(qū)動程序主要初始化以下幾個結(jié)構(gòu)體:
stm32外設HAL庫配置結(jié)構(gòu)體 SD_HandleTypeDef hsd
stm32 sdio 設備結(jié)構(gòu)體 struct stm32_sdio_des sdio_des
sdio硬件外設結(jié)構(gòu)體 struct rthw_sdio *sdio
mmc sd host結(jié)構(gòu)體struct rt_mmcsd_host
其關系如下圖所示:
結(jié)構(gòu)體數(shù)據(jù)初始化完成以后,調(diào)用mmcsd_change()函數(shù),觸發(fā)框架層邏輯
此外,在設備驅(qū)動層提供的操作函數(shù)主要有:
static const struct rt_mmcsd_host_ops ops =
{
rthw_sdio_request,
rthw_sdio_iocfg,
rthw_sd_detect,
rthw_sdio_irq_update,
};
rthw_sdio_request 實現(xiàn)一次SDIO數(shù)據(jù)發(fā)送
rthw_sdio_iocfg 實現(xiàn)SDIO外設配置,注意在SD識別過程中會反復調(diào)用,不斷更新SDIO外設配置
rthw_sd_detect 實現(xiàn)獲取卡的狀態(tài)獲取,demo里這里實際沒有實現(xiàn)
rthw_sdio_irq_update 實現(xiàn)SDIO外設中斷的開關配置
函數(shù)調(diào)用順序如下:
/* 函數(shù)調(diào)用順序 */
rt_hw_sdio_init()
-> sdio_host_create(&sdio_des)
-> mmcsd_change(host)
5. SDIO設備驅(qū)動架構(gòu)分析
設備驅(qū)動架構(gòu)層,也就是中間層,文件框架如下圖所示:
我們首先來看下 mmcsd_core.c 這個文件:
rt_mmcsd_core_init() 初始化函數(shù)通過 INIT_PREV_EXPORT(rt_mmcsd_core_init); 被初始化調(diào)用,同時初始化用于 mmc、sd、sdio檢測的郵箱mmcsd_detect_mb,用于熱插拔處理的 mmcsd_hotpluge_mb 以及 mmc、sd、sdio檢測線程 mmcsd_detect_thread;
在線程mmcsd_detect_thread 中,等待mmcsd_detect_mb郵箱喚醒;
當SDIO驅(qū)動層完成初始化話之后,通過調(diào)用 mmcsd_change(host) 函數(shù),將mmcsd_detect_thread線程喚醒,開始進行mmc、sd卡、sdio卡的識別過程
mmcsd_core_init()函數(shù)內(nèi)容如下:
int rt_mmcsd_core_init(void)
{
rt_err_t ret;
/* initialize detect SD cart thread */
/* initialize mailbox and create detect SD card thread */
ret = rt_mb_init(&mmcsd_detect_mb, "mmcsdmb",
&mmcsd_detect_mb_pool[0], sizeof(mmcsd_detect_mb_pool) / sizeof(mmcsd_detect_mb_pool[0]),
RT_IPC_FLAG_FIFO);
RT_ASSERT(ret == RT_EOK);
ret = rt_mb_init(&mmcsd_hotpluge_mb, "mmcsdhotplugmb",
&mmcsd_hotpluge_mb_pool[0], sizeof(mmcsd_hotpluge_mb_pool) / sizeof(mmcsd_hotpluge_mb_pool[0]),
RT_IPC_FLAG_FIFO);
RT_ASSERT(ret == RT_EOK);
ret = rt_thread_init(&mmcsd_detect_thread, "mmcsd_detect", mmcsd_detect, RT_NULL,
&mmcsd_stack[0], RT_MMCSD_STACK_SIZE, RT_MMCSD_THREAD_PREORITY, 20);
if (ret == RT_EOK)
{
rt_thread_startup(&mmcsd_detect_thread);
}
rt_sdio_init();
return 0;
}
INIT_PREV_EXPORT(rt_mmcsd_core_init);
mmcsd_detect()線程以及mmcsd_change()函數(shù)如下:
mmcsd_detect() 函數(shù)主要負責完成 SDIO卡、SD卡、MMC卡的初步識別,初步識別確認是哪種類型的卡接入之后,將會調(diào)用對應卡驅(qū)動文件(SD卡對應sd.c,SDIO卡對應sdio.c,MMC卡對應mmc.c)內(nèi)的初始化函數(shù),重新完成卡的完整識別流程
如果對于SD卡識別流程不了解,建議先熟悉SD卡識別流程,參考 SD Nand 與 SD卡 SDIO模式應用流程(點擊跳轉(zhuǎn))
具體流程見下述函數(shù)描述,對應步驟已補充注釋描述
void mmcsd_change(struct rt_mmcsd_host *host)
{
rt_mb_send(&mmcsd_detect_mb, (rt_uint32_t)host);
}
void mmcsd_detect(void *param)
{
struct rt_mmcsd_host *host;
rt_uint32_t ocr;
rt_int32_t err;
while (1)
{
/* 首先等待 mmcsd_detect_mb 信號量,此信號量由 mmcsd_change() 函數(shù)發(fā)送過來 */
if (rt_mb_recv(&mmcsd_detect_mb, (rt_ubase_t *)&host, RT_WAITING_FOREVER) == RT_EOK)
{
/* 通過判斷 host->card 確認此次操作是識別卡還是移除卡 */
if (host->card == RT_NULL) /* 識別卡 */
{
mmcsd_host_lock(host); /* 獲取鎖 */
mmcsd_power_up(host); /* 配置SDIO外設電源控制器,power up, 即卡的時鐘開啟,同時配置SDIO外設時鐘為低速模式 */
mmcsd_go_idle(host); /* 發(fā)送CMD0指令,使卡進入空閑狀態(tài) */
mmcsd_send_if_cond(host, host->valid_ocr); /* 發(fā)送CMD8命令,查詢SD卡接口條件 (獲取OCR寄存器) */
/*
* 檢測SDIO卡使用,SD卡不用管
*/
err = sdio_io_send_op_cond(host, 0, &ocr); /* 發(fā)送CMD5命令,此處是針對SDIO卡使用,SD卡不會響應 */
if (!err) /* SD卡不會響應此指令,因此此條件不會成立 */
{
if (init_sdio(host, ocr))
mmcsd_power_off(host);
mmcsd_host_unlock(host);
continue;
}
/*
* 檢測SD卡使用,使用SD卡重點關注此項!??!
*/
err = mmcsd_send_app_op_cond(host, 0, &ocr); /* 發(fā)送ACMD41指令(ACMD41:CMD55+CMD41) SD卡將應答此指令 */
if (!err)
{
if (init_sd(host, ocr)) /* 此函數(shù)內(nèi)完成SD卡完整的識別流程 */
mmcsd_power_off(host); /* 設置SDIO外設,電源關閉,卡的時鐘停止 */
mmcsd_host_unlock(host); /* 釋放鎖 */
rt_mb_send(&mmcsd_hotpluge_mb, (rt_uint32_t)host); /* 發(fā)送郵箱,通知熱插拔事件 */
continue;
}
/*
* 檢測MMC卡檢測使用,SD卡不用管
*/
err = mmc_send_op_cond(host, 0, &ocr);
if (!err)
{
if (init_mmc(host, ocr))
mmcsd_power_off(host);
mmcsd_host_unlock(host);
rt_mb_send(&mmcsd_hotpluge_mb, (rt_uint32_t)host);
continue;
}
mmcsd_host_unlock(host); /* 識別失敗,釋放鎖 */
}
else /* 移除卡 */
{
/* card removed */
mmcsd_host_lock(host); /* 獲取鎖 */
if (host->card->sdio_function_num != 0)
{
LOG_W("unsupport sdio card plug out!");
}
else
{
rt_mmcsd_blk_remove(host->card);
rt_free(host->card);
host->card = RT_NULL;
}
mmcsd_host_unlock(host); /* 釋放鎖 */
rt_mb_send(&mmcsd_hotpluge_mb, (rt_uint32_t)host);
}
}
}
}
在mmcsd_detect()函數(shù)內(nèi)完成SD卡的初步識別之后,之后將調(diào)用sd.c文件內(nèi)的init_sd()函數(shù)完成 sd 卡的完整識別過程
/*
* Starting point for SD card init.
*/
rt_int32_t init_sd(struct rt_mmcsd_host *host, rt_uint32_t ocr)
{
rt_int32_t err;
rt_uint32_t current_ocr;
/*
* We need to get OCR a different way for SPI.
*/
if (controller_is_spi(host)) /* 判斷是否采用SPI模式訪問SD卡 */
{
mmcsd_go_idle(host);
err = mmcsd_spi_read_ocr(host, 0, &ocr);
if (err)
goto err;
}
if (ocr & VDD_165_195)
{
LOG_I(" SD card claims to support the "
"incompletely defined 'low voltage range'. This "
"will be ignored.");
ocr &= ~VDD_165_195;
}
current_ocr = mmcsd_select_voltage(host, ocr); /* 配置SDIO外設設置為合適的電壓,對于stm32、gd32等相關控制器,實際是不支持不同等級電壓配置的,所以這里可以忽略,不過你需要注意你所使用的sd卡的電源在硬件上是匹配的 */
/*
* Can we support the voltage(s) of the card(s)?
*/
if (!current_ocr)
{
err = -RT_ERROR;
goto err;
}
/*
* Detect and init the card.
*/
err = mmcsd_sd_init_card(host, current_ocr); /* 完整的SD卡初始化流程在此函數(shù)內(nèi)實現(xiàn) */
if (err)
goto err;
mmcsd_host_unlock(host); /* 釋放鎖 */
err = rt_mmcsd_blk_probe(host->card); /* 注冊塊設備 */
if (err) /* 如果注冊塊設備失敗,將移除卡 */
goto remove_card;
mmcsd_host_lock(host); /* 獲取鎖 */
return 0;
remove_card:
mmcsd_host_lock(host); /* 獲取鎖 */
rt_mmcsd_blk_remove(host->card); /* 移除塊設備 */
rt_free(host->card); /* 釋放對應的內(nèi)存 */
host->card = RT_NULL;
err:
LOG_D("init SD card failed!");
return err;
}
調(diào)用mmcsd_sd_init_card()函數(shù)完成SD卡檢測以及初始化配置
static rt_int32_t mmcsd_sd_init_card(struct rt_mmcsd_host *host,
rt_uint32_t ocr)
{
struct rt_mmcsd_card *card;
rt_int32_t err;
rt_uint32_t resp[4];
rt_uint32_t max_data_rate;
mmcsd_go_idle(host); /* 發(fā)送CMD0,復位SD卡,使卡進入空閑模式 */
/*
* If SD_SEND_IF_COND indicates an SD 2.0
* compliant card and we should set bit 30
* of the ocr to indicate that we can handle
* block-addressed SDHC cards.
*/
err = mmcsd_send_if_cond(host, ocr); /* 發(fā)送CMD8指令,判斷是否為V2.0或V2.0以上的卡,并獲取OCR寄存器值 */
if (!err) /* 如果是V2.0及以上版本的卡,將置為OCR的bit30位,表明主機支持高容量SDHC卡(OCR將在ACMD41指令時作為參數(shù)發(fā)送給卡) */
ocr |= 1 << 30;
err = mmcsd_send_app_op_cond(host, ocr, RT_NULL); /* 發(fā)送ACMD41(ACMD41 = CMD55+CMD41)指令,發(fā)送主機容量支持信息,并詢問卡的操作條件 */
if (err)
goto err;
if (controller_is_spi(host)) /* 判斷是否使用SPI方式訪問SD卡 */
err = mmcsd_get_cid(host, resp); /* 采用SPI方式獲取CID寄存器值 */
else
err = mmcsd_all_get_cid(host, resp);/* 發(fā)送CMD2命令,獲取CID寄存器值 */
if (err)
goto err;
card = rt_malloc(sizeof(struct rt_mmcsd_card)); /* 創(chuàng)建rt_mmcsd_card結(jié)構(gòu)體,用于存儲對應SD卡的CID寄存器內(nèi)容 */
if (!card)
{
LOG_E("malloc card failed!");
err = -RT_ENOMEM;
goto err;
}
rt_memset(card, 0, sizeof(struct rt_mmcsd_card));
card->card_type = CARD_TYPE_SD;
card->host = host;
rt_memcpy(card->resp_cid, resp, sizeof(card->resp_cid));
/*
* For native busses: get card RCA and quit open drain mode.
*/
if (!controller_is_spi(host)) /* 如果不是采用SPI方式訪問SD卡 */
{
err = mmcsd_get_card_addr(host, &card->rca); /* 發(fā)送CMD3命令,獲取RCA地址 */
if (err)
goto err1;
mmcsd_set_bus_mode(host, MMCSD_BUSMODE_PUSHPULL);/* 設置CMD總線為推挽輸出模式,需要注意的是,MMC卡V3.31版本以前的卡,初始化階段,CMD總線需要為開路模式,對于SD/SD I/O卡和MMC V4.2在初始化時也使用推挽驅(qū)動 */
}
err = mmcsd_get_csd(card, card->resp_csd); /* 發(fā)送CMD9命令,獲取CSD寄存器值 */
if (err)
goto err1;
err = mmcsd_parse_csd(card); /* 解析CSD寄存器值,將解析完成的數(shù)據(jù)存放在剛剛申請的card結(jié)構(gòu)體內(nèi) */
if (err)
goto err1;
if (!controller_is_spi(host)) /* 如果不是采用SPI方式訪問SD卡 */
{
err = mmcsd_select_card(card); /* 發(fā)送CMD7命令,選擇卡 */
if (err)
goto err1;
}
err = mmcsd_get_scr(card, card->resp_scr); /* 發(fā)送CMD9命令,獲取SCR寄存器值,并保存在剛剛申請的card結(jié)構(gòu)體內(nèi) */
if (err)
goto err1;
mmcsd_parse_scr(card); /* 解析SCR寄存器的值,并將解析結(jié)果存放在在card結(jié)構(gòu)體內(nèi) */
if (controller_is_spi(host))
{
err = mmcsd_spi_use_crc(host, 1);
if (err)
goto err1;
}
/*
* change SD card to high-speed, only SD2.0 spec
*/
err = mmcsd_switch(card); /* 發(fā)送CMD6指令,切換卡訪問速率由默認的12.5MB/Sec為25MB/Sec高速接口 */
if (err)
goto err1;
/* set bus speed */
max_data_rate = (unsigned int)-1;
if (card->flags & CARD_FLAG_HIGHSPEED)
{
if (max_data_rate > card->hs_max_data_rate)
max_data_rate = card->hs_max_data_rate;
}
else if (max_data_rate > card->max_data_rate)
{
max_data_rate = card->max_data_rate;
}
mmcsd_set_clock(host, max_data_rate); /* 修改SDIO外設時鐘速度 */
/*switch bus width*/
if ((host->flags & MMCSD_BUSWIDTH_4) &&
(card->scr.sd_bus_widths & SD_SCR_BUS_WIDTH_4)) /* 根據(jù)SD卡的SCR寄存器反饋的值,判斷SD卡是否支持4線寬度訪問模式,如果支持則切換為4線寬度訪問模式 */
{
err = mmcsd_app_set_bus_width(card, MMCSD_BUS_WIDTH_4); /* 發(fā)送ACMD6(ACMD6=CMD55+CMD6)指令,通知SD卡切換為4線訪問模式 */
if (err)
goto err1;
mmcsd_set_bus_width(host, MMCSD_BUS_WIDTH_4); /* 修改SDIO外設配置為4線訪問模式 */
}
host->card = card; /* 將card結(jié)構(gòu)體數(shù)據(jù)與host結(jié)構(gòu)體建立綁定關系 */
return 0;
err1:
rt_free(card);
err:
return err;
}
6. 調(diào)試記錄
RT-Thread的SDIO驅(qū)動,默認上層使用到了 elm-fatfs 文件系統(tǒng),因此通常我們配置好對應的芯片的SDIO驅(qū)動之后,直接就可以快速使用文件系統(tǒng)來操作訪問SD Nand了,關于文件系統(tǒng)的有關內(nèi)容,不在此文中做過多描述,有興趣的同學可以關注本人博客,后續(xù)將及時更新。
此外,在實際使用中有一點需要注意,當我們首次使用芯片的時候,sd nand內(nèi)還未寫入任何數(shù)據(jù),此時通常是沒有文件系統(tǒng)的,所以當一次執(zhí)行之后你會見到如下錯誤:
這是由于SD nand內(nèi)沒有掛載文件系統(tǒng)導致,解決此問題有以下兩個方法:
方法一:在命令終端使用mkfs掛載文件系統(tǒng),具體命令步驟如下:
使用list_device查看sd nand對應的設備名
使用mkfs命令格式化sd nand:mkfs -t elm sd0(-t指定文件系統(tǒng)類型為elm-FAT文件系統(tǒng),對sd0設備操作)
由 drv_sdio.c 外設驅(qū)動或其他調(diào)用 mmcsd_change() 觸發(fā) mmcsd_detect() 檢測
在 mmcsd_detect () 任務中,實現(xiàn)對SD卡、SD I/O卡、MMC卡的初步識別(發(fā)送對應卡特有命令,并判斷是否正確響應),之后根據(jù)卡片類型調(diào)用不同類型卡片驅(qū)動文件內(nèi)的初始化程序
如針對SD卡,則調(diào)用sd.c文件內(nèi)的 init_sd() 函數(shù)完成
在init_sd()函數(shù)內(nèi)調(diào)用 mmcsd_sd_init_card() 完成SD卡的完整識別流程以及初始化流程,同時同步修改SDIO外設配置
SD卡初始化完成之后,調(diào)用 rt_mmcsd_blk_probe() 將sd卡注冊為塊設備
至此SD的識別與初始化流程順利完成
-
FlaSh
+關注
關注
10文章
1614瀏覽量
147659 -
SD卡
+關注
關注
2文章
558瀏覽量
63743 -
SD NAND
+關注
關注
0文章
80瀏覽量
1202
發(fā)布評論請先 登錄
相關推薦
評論