目錄
Preface:
(一)原理相關(guān)
(二)CUBEMX配置
(三)輪詢方式讀寫
(四)DMA方式讀寫
Preface:
STM32F4有一個FSMC(Flexible Static Memory Controller,可變靜態(tài)存儲控制器),可以用來驅(qū)動8080接口的TFT LCD,我之前就寫過一篇blog,是用FSMC來驅(qū)動4.3寸液晶屏;此外,還可以用FSMC來連接外部的各種存儲器,比如說SRAM、NOR FLASH、PSRAM等等;但是每個區(qū)(Bank)的功能是不一樣的;Bank1可以連接多達4個NOR FLASH或PSRAM/SRAM存儲器件(通過片選);Bank2和Bank3只能用于訪問NAND FLASH,且每個Bank只能連一個設(shè)備;Bank4只能用于連接PC Card設(shè)備。
(一)原理相關(guān)
STM32F4的FSMC控制器的存儲區(qū)分為4個區(qū),分別為Bank 1~Bank 4,每個Bank大小為2e28個字節(jié),即256MB,因此總共管理的內(nèi)存可達到1GB;而每個Bank又分成4個子區(qū),每個子區(qū)64MB;Bank1的地址范圍為0x6000 0000h~6FFF FFFFh,Bank2的地址范圍為0x7000 0000h~7FFF FFFFh,Bank3的地址范圍為0x8000 0000h~8FFF FFFFh,Bank4的地址范圍為0x9000 0000h~9FFF FFFFh;如下圖:
對于STM32F407ZGT6來說,其內(nèi)部SRAM為192kB,一般的應用程序是足夠用了,但是在使用GUI(特別是要做得很炫酷那種)等需要大量內(nèi)存的功能時,192kB是不太夠的,可能就需要擴展SRAM了。FSMC連接PSRAM/SRAM設(shè)備時,接口線的功能如下表所示:?
根據(jù)開發(fā)板的原理圖(如下圖)可知,F(xiàn)SMC的NE3線連接到了外部SRAM的片選,而由于只有Bank1才能連接SRAM,所以可知板子用的是FSMC的Bank1的子區(qū)3來連接外部SRAM;
該SRAM芯片為IS62WV51216,這是一個16位寬512K容量(512K×16位,即1024KB)的靜態(tài)內(nèi)存芯片。它與MCU的連接電路如下圖所示。芯片幾個主要管腳的功能,以及與MCU的連接原理如下:
- A0至A18是19根地址線,連接FSMC的19根地址線,即FSMC_A0至FSMC_A18;
- I/O0至I/O15是16位數(shù)據(jù)線,連接FSMC的FSMC_D0至FSMC_D15數(shù)據(jù)線;
- CE是芯片的片選信號,連接MCU的FSMC_NE3(PG10引腳),也就是Bank1子區(qū)3的片選信號;
- OE是輸出使能信號,連接MCU的FSMC_NOE(PD4引腳),是讀數(shù)據(jù)時的使能信號;
- WE是寫使能信號,連接MCU的FSMC_NWE (PD5引腳),是寫數(shù)據(jù)使能信號;
- UB是高字節(jié)使能信號,連接MCU的FSMC_NBL[1](PE1引腳);LB是低字節(jié)使能信號,連接MCU的FSMC_NBL[0](PE0引腳);通過UB和LB的控制可以只讀取一個地址的高字節(jié)(I/O8~I/O15)或低字節(jié)(I/O0~I/O7),或讀取16位數(shù)據(jù);
IS62WV51216有19根地址線,能表示的地址范圍是512K,而數(shù)據(jù)寬度是16位(2B),因此實際存儲容量是1024KB,偏移地址范圍是0x00000~0x7FFFF。又因為Bank1子區(qū)3的起始地址是0x68000000,所以IS62WV51216的全部1024KB的地址范圍是 0x68000000~0x680FFFFF。FSMC_NBL[1]和FSMC_NBL[0]分別控制高位字節(jié)和低位字節(jié)訪問,實現(xiàn)全部1024KB存儲空間的按字節(jié)訪問。
(二)CUBEMX配置
cubemx中的FSMC模式配置如下(選擇子區(qū)3,片選為NE3;Mem類型SRAM;地址19位;數(shù)據(jù)16位;Wait是PSRAM芯片發(fā)送給FSMC的等待輸入信號,IS62WV51216沒有該線,所以disable掉;最后勾上Byte enable,允許字節(jié)訪問):
開啟之后再對照原理圖看一遍發(fā)現(xiàn)引腳剛好與開發(fā)板上的一致,因此無需更改引腳重映射:
接下來進行參數(shù)配置;首先是控制參數(shù):Memory type只能選SRAM;Bank只能選Bank1子區(qū)3,這兩項與模式設(shè)置部分是一一對應的;Write operation設(shè)置為Enabled,表示使能寫操作;Extended mode設(shè)置為Disabled,F(xiàn)SMC自動使用模式A對SRAM進行操作,SRAM的讀寫操作速度基本相同,所以讀寫操作可以使用相同的時序參數(shù),無需使用擴展模式單獨設(shè)置讀時序和寫時序;接下來是時序參數(shù):地址建立時間ADDSET,設(shè)置范圍為0~15,設(shè)置為0即可;數(shù)據(jù)建立時間DATASET,設(shè)置范圍為1~255,設(shè)置為8;總線翻轉(zhuǎn)時間,設(shè)置范圍為0~15,設(shè)置為0即可;
另外,因為FSMC參數(shù)設(shè)置部分沒有DMA設(shè)置頁面,如果要用DMA的話需要去System Core的DMA里面手動創(chuàng)建,并且在代碼里要手動LINK DMA;
如下圖所示:
然后,因為代碼里會使用隨機數(shù)生成器,所以打開Security分組下的RNG模塊,啟用RNG;RNG需要用到48MHz時鐘,時鐘樹上可能會提示錯誤;單擊時鐘樹界面上的Resolve Clock Issues,讓cubemx自動解決即可:
?
配置好后直接生成代碼即可
(三)輪詢方式讀寫
首先加入3個宏,分別表示Bank1子區(qū)3的SRAM起始地址、中間地址、結(jié)束地址,如下所示:
#define SRAM_ADDR_BEGIN 0x68000000UL //Bank1子區(qū)3的SRAM起始地址
#define SRAM_ADDR_HALF 0x68080000UL //SRAM中間地址,一共512KB
#define SRAM_ADDR_END 0x680FFFFFUL //SRAM結(jié)束地址,一共1024KB
然后封裝一下讀取、寫入數(shù)據(jù);如下:
#include "fsmc_func.h"
#include "fsmc.h"
#include "rng.h"
/*
* 用HAL函數(shù)寫入數(shù)據(jù)
* */
HAL_StatusTypeDef SRAM_WriteByFunc() {
HAL_StatusTypeDef status = HAL_OK;
uint8_t str[] = "Input Data"; //待寫入字符
uint16_t length = sizeof (str); //數(shù)據(jù)長度(注意是字節(jié)數(shù)),包括'\0'
auto *paddr = (uint32_t*)(SRAM_ADDR_BEGIN); //目標地址
//寫入字符串
if (HAL_OK == HAL_SRAM_Write_8b(&hsram3, paddr, str, length)) {
HAL_Delay(1);
} else {
status = HAL_ERROR;
}
//寫入數(shù)字
uint32_t num = 0;
paddr = (uint32_t*)(SRAM_ADDR_HALF); //修改目標地址
HAL_RNG_GenerateRandomNumber(&hrng, &num); //生成隨機數(shù)
if (HAL_OK == HAL_SRAM_Write_32b(&hsram3, paddr, &num, 1)) {
HAL_Delay(1);
} else {
status = HAL_ERROR;
}
return status;
}
/*
* 用HAL函數(shù)讀取數(shù)據(jù)
* */
HAL_StatusTypeDef SRAM_ReadByFunc() {
HAL_StatusTypeDef status = HAL_OK;
auto *paddr = (uint32_t*)(SRAM_ADDR_BEGIN);
uint8_t str[30];
uint16_t length = 30; //讀取字節(jié)數(shù)
//讀取字符
if (HAL_OK == HAL_SRAM_Read_8b(&hsram3, paddr, str, length)) {
HAL_Delay(1);
} else {
status = HAL_ERROR;
}
//讀取數(shù)字
uint32_t num = 0;
paddr = (uint32_t*)(SRAM_ADDR_HALF);
if (HAL_OK == HAL_SRAM_Read_32b(&hsram3, paddr, &num, 1)) {
HAL_Delay(1);
} else {
status = HAL_ERROR;
}
return status;
}
?然后在主函數(shù)中輸入測試代碼,測試是否正確寫入、讀出:
進入調(diào)試,跑了上半部分,status為HAL_OK,說明成功寫入字符串:
?跑完下半部分,status為HAL_OK,說明成功寫入隨機數(shù)num:
接下來是讀出調(diào)試;重新讀出SRAM開始出的字符,發(fā)現(xiàn)前面部分與剛剛寫入的字符串一模一樣,且status仍為HAL_OK,表示成功寫入,讀出字符串:
接著重新讀出SRAM中間部分的一個32位數(shù)字,發(fā)現(xiàn)status為HAL_OK,說明讀取成功,并且能看到num中的數(shù)字與剛剛寫入的隨機數(shù)一模一樣,表示成功寫入、讀出數(shù)字:
除此之外,因為這個擴展RAM本質(zhì)上還是存儲器,所以還可以不使用HAL庫函數(shù),直接使用指針讀取指定地址的內(nèi)容;STM32是32位機器,最大能夠管理的地址空間為2e32 = 4GB,只要在0x0000 0000h~0xFFFF FFFFh中實際存在的地址,STM32都能訪問;下面代碼是通過指針直接訪問對應地址中的內(nèi)容:
/*
* 用指針寫入數(shù)據(jù)
* */
void SRAM_WriteByPointer() {
uint16_t num = 100;
uint16_t *paddr_16b = (uint16_t*)(SRAM_ADDR_BEGIN); //uint16_t類型的指針
for (int i = 0; i < 5; ++i) {
num += 10;
*paddr_16b = num; //指定地址寫入數(shù)據(jù)
paddr_16b++; //每次自增2B
}
}
/*
* 用指針讀出數(shù)據(jù)
* */
void SRAM_ReadByPointer() {
uint16_t num[5] = {0};
uint16_t *paddr_16b = (uint16_t*)(SRAM_ADDR_BEGIN); //uint16_t類型的指針
for (int i = 0; i < 5; ++i) {
num[i] = *paddr_16b;
paddr_16b++;
}
}
通過調(diào)試可以發(fā)現(xiàn),用指針來讀寫數(shù)據(jù)也無誤:
PS.注意,在使用HAL函數(shù)讀寫外部SRAM數(shù)據(jù)時,傳遞的目的地址必須是uint32_t類型的指針;而在使用指針直接訪問SRAM時,指針的類型需要與實際訪問的數(shù)據(jù)類型一致,比如說要讀一個16位的數(shù)據(jù),就要指定讀取地址為一個uint16_t的指針(因為指針只是一個數(shù),指針的類型就是表示該指針所指向的地址中的數(shù)據(jù)的類型)
(四)DMA方式讀寫
前面說了,要用FSMC的DMA需要去System Core的DMA里面手動創(chuàng)建,并且在代碼里要手動LINK DMA;為了使用DMA,重新打開項目中的cubemx,如下:
然后進入DMA設(shè)置頁面,在MenToMem欄新建一個DMA流,發(fā)現(xiàn)DMA2中出現(xiàn)了一個同樣的DMA流,這是因為只有DMA2控制器支持mem到mem的傳輸,DMA1不支持;設(shè)置屬性如下:
- DMA的工作模式只能設(shè)置為Normal模式,沒有Circular模式;
- DMA流自動使用FIFO(DMA流隊列),且不能關(guān)閉,Burst Size保持默認Single即可;
- 源存儲器和目標存儲器的數(shù)據(jù)寬度設(shè)置為Word,這是因為HAL_SRAM_Write_DMA()和HAL_SRAM_Read_DMA()函數(shù)只支持uint32_t類型的數(shù)據(jù)buffer;
- 源存儲器和目標存儲器都應開啟地址自增;
配置如下圖所示:?
此外,還要在NVIC中開啟該DMA流的中斷,否則系統(tǒng)不會調(diào)用中斷回調(diào)函數(shù);然后生成代碼即可;
首先添加幾個定義,主要是定義需要用得的宏、變量;如下:
#define COUNT 5 //緩沖區(qū)數(shù)據(jù)個數(shù)
uint32_t txbuf[COUNT]; //DMA發(fā)送緩沖區(qū)
uint32_t rxbuf[COUNT]; //DMA接收緩沖區(qū)
bool direction = true; //DMA傳輸方向:ture表示MCU向外部SRAM傳,false則相反
bool is_busy = false; //DMA狀態(tài):true表示正忙,false表示idle
還有,在主函數(shù)初始化FSMC后,需要加上LINK,將DMA流對象連接到SRAM對象:
接下來,封裝一下DMA數(shù)據(jù)讀、寫函數(shù);如下:
/*
* DMA發(fā)送函數(shù)
* */
HAL_StatusTypeDef SRAM_WriteDMA() {
HAL_StatusTypeDef status = HAL_OK;
uint32_t val = 1000;
//準備數(shù)據(jù)
for (int i = 0; i < COUNT; ++i) {
txbuf[i] = val;
val += 100;
}
direction = true;
dma_is_busy = true; //指示傳輸方向以及狀態(tài)
uint32_t *paddr = (uint32_t*)(SRAM_ADDR_BEGIN);
if (HAL_OK == HAL_SRAM_Write_DMA(&hsram3, paddr, txbuf, COUNT)) {
HAL_Delay(1);
} else {
status = HAL_ERROR;
}
return status;
}
/*
* DMA讀取函數(shù)
* */
HAL_StatusTypeDef SRAM_ReadDMA() {
HAL_StatusTypeDef status = HAL_OK;
uint32_t *paddr = (uint32_t*)(SRAM_ADDR_BEGIN);
direction = false;
dma_is_busy = true; //指示傳輸方向以及狀態(tài)
if (HAL_OK == HAL_SRAM_Read_DMA(&hsram3, paddr, rxbuf, COUNT)) {
HAL_Delay(1);
} else {
status = HAL_ERROR;
}
return status;
}
/*
* DMA傳輸結(jié)束中斷回調(diào)函數(shù)
* */
volatile uint8_t test = 0;
/*
* 測試變量 test
* 當MCU向外部SRAM寫入成功時,該變量賦值為1
* 當MCU從外部SRAM讀取成功時,該變量賦值為2
* */
void HAL_SRAM_DMA_XferCpltCallback(DMA_HandleTypeDef *hdma)
{
if (direction) { //方向為
test = 1;
} else {
test = 2;
}
dma_is_busy = false; //表示dma傳輸結(jié)束
}
在主函數(shù)中調(diào)用這兩個函數(shù),打個斷點,然后進入快樂的debug環(huán)節(jié);一開始發(fā)現(xiàn)幾個全局變量不在watch窗口中,首先加入窗口:
接著檢查一下txbuf和rxbuf中的值,看是否正確:
然后在中斷回調(diào)函數(shù)中打一個斷點,看發(fā)送完成是否會進入回調(diào);并注意發(fā)送數(shù)據(jù)前兩個標志以及test變量的值:
接著走一步,發(fā)現(xiàn)發(fā)送成功了,進入了HAL_Delay()函數(shù),然后再走一步,果然進入了回調(diào)函數(shù);如下:
說明理論與實際情況一致,DMA發(fā)送成功。
接下來進入接收環(huán)節(jié);一樣的調(diào)試方法,最后發(fā)現(xiàn)依然進入回調(diào)函數(shù),test被賦為2,此時查看rxbuf的值,可以看到與剛剛發(fā)送的5個數(shù)據(jù)一模一樣,說明DMA接收也成功;如下:
大功告成!
工程鏈接:https://pan.baidu.com/s/18AJoG1epClGWzjHQkf6SRQ?
提取碼:0xFF
完~
以上均為個人學習心得,如有錯誤,請不吝賜教~文章來源:http://www.zghlxwxcb.cn/news/detail-772044.html
THE END文章來源地址http://www.zghlxwxcb.cn/news/detail-772044.html
到了這里,關(guān)于STM32復習筆記(五):FSMC連接外部SRAM的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!