原文鏈接 https://blog.csdn.net/xushan239/article/details/79617165
早兩天往Stm32上移植Fatfs文件系統,花了一些時間;
后面又花了些時間移植Stm32的USB功能;
在這個過程中,自己摸索了很多東西,也向群里的高人請教過,所以希望把這部分東西記錄下,方便自己以后和想尋找這方面知識的人查看。
下面按照上面的介紹分幾步來介紹移植驅動所做的工作。
fatfs文件系統的移植
首先從fatfs官網下載驅動http://elm-chan.org/fsw/ff/arc/ff13a.zip
在下載的源碼里面可以看到source文件下面有上面這些文件。這就是我們要移植的文件系統。
其實為了方便移植前輩們在這個上面已經完善了很多很多,我們只需要做比較少的改動就可以應用起來;我們需要修改的文件是diskio.c這個文件里面的幾個標准接口:
DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
DWORD get_fattime (void);
在diskio.c中定義了幾個磁盤設備:
#define DEV_RAM 0 /* Example: Map Ramdisk to physical drive 0 */
#define DEV_MMC 1 /* Example: Map MMC/SD card to physical drive 1 */
#define DEV_USB 2 /* Example: Map USB MSD to physical drive 2 */
在對應的幾個操作函數里面也有這幾個設備對應的操作,但是我們只用一個spi-flash所以就只定義一個:
#define SPI_FLASH 0
定義扇區大小,塊大小,扇區個數
#define FLASH_SECTOR_SIZE (4*1024)
#define FLASH_BLOCK_SIZE 64
u16 FLASH_SECTOR_COUNT = 4*1024*1024/(4*1024);
獲取磁盤狀態直接返回成功:
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
int result;
switch (pdrv) {
case SPI_FLASH :return RES_OK;
}
return STA_NOINIT;
}
初始化磁盤的函數,主要是把spi-flash初始化:
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
int result;
char t = 0;
switch (pdrv) {
case SPI_FLASH :
//init spi-bus
SPI_Flash_Init();
if(SPI_FLASH_TYPE != FLASH_ADDRESS)
stat = RES_NOTRDY;
else
stat = RES_OK;
return stat;
}
return STA_NOINIT;
}
讀取文件系統驅動接口:讀取單位以sector(扇區)為單位
DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
int result;
switch (pdrv) {
case SPI_FLASH :
for(;count>0;count--)
{
SPI_Flash_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
sector++;
buff+=FLASH_SECTOR_SIZE;
}
return RES_OK;
}
return RES_PARERR;
}
寫文件系統驅動接口:寫入也是以sector為單位
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
int result;
switch (pdrv) {
case SPI_FLASH :
for(;count>0;count--)
{
SPI_Flash_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
sector++;
buff+=FLASH_SECTOR_SIZE;
}
return RES_OK;
}
return RES_PARERR;
}
文件系統一些控制命令:主要是獲取一些磁盤的參數
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT res = RES_OK;
int result;
switch (pdrv) {
case SPI_FLASH :
switch(cmd)
{
case CTRL_SYNC:
res = RES_OK;
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = FLASH_SECTOR_SIZE;
res = RES_OK;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = FLASH_BLOCK_SIZE;
res = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = FLASH_SECTOR_COUNT;
res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
}
return res;
}
文件系統時間的接口:
DWORD get_fattime (void)
{
return ((rtc.w_year - 1980)<<25)|\
(rtc.w_month<<21)|\
(rtc.w_date<<16)|\
(rtc.hour<<11)|\
(rtc.min<<5)|\
rtc.sec;
}
到這里驅動基本上就移植完了,下面要做的就是初始化文件系統:在ffconf.h文件中可以配置文件系統的參數和功能,因為一開始我們的flash是沒有文件系統的,需要自己格式化一下,所以需要有創建文件系統的功能:
#define FF_USE_MKFS 1//默認是0
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
創建文件系統:
首先嘗試掛載文件系統
掛載成功則返回成功,掛載失敗則創建文件系統
創建失敗則返回失敗,創建成功再一次嘗試掛載
static FATFS sysFs;
static BYTE gFsWork[FF_MAX_SS]; /* Work area (larger is better for processing time) */
static BYTE gFsInited = 0;
static int fileSystemInit()
{
FRESULT res = 0;
res = f_mount (&sysFs,"0:",1);
if(res != 0)
{
res = f_mkfs("0:", FM_FAT,0, gFsWork, sizeof gFsWork);
if(res == 0)
{
res = f_mount (&sysFs,"0:",1);
if(res == 0)
{
gFsInited = 1;
return 0;
}
else
return -1;
}
else
return -1;
}
else
gFsInited = 1;
return 0;
}
文件系統測試:
static int fileSystemTest()
{
FIL fil;
UINT bw;
FRESULT res = 0;
if(gFsInited)
{
static char i = 0;
char b[5] = {0};
res = f_open(&fil, "0:/hello.txt", FA_OPEN_APPEND | FA_WRITE);
if (res == 0)
{
/* Write a message */
sprintf(b,"%d",i++%10);
res = f_write(&fil, b, 1, &bw);
f_close(&fil);
}
res = f_open(&fil, "0:/hello.txt", FA_READ);
if (res == 0)
{
/* read a message */
char buffer[256];
memset(buffer,0,sizeof(256));
res = f_read(&fil,buffer,255,&bw);
printf("read:%d|%d =>>%s\r\n",res,bw,buffer);
f_close(&fil);
}
}
return 0;
}
測試輸出:
read:0|1 =>>0
read:0|2 =>>01
read:0|3 =>>012
read:0|4 =>>0123
read:0|5 =>>01234
read:0|6 =>>012345
read:0|7 =>>0123456
read:0|8 =>>01234567
read:0|9 =>>012345678
usb功能的移植
這部分驅動用原子哥的實驗”實驗50 USB讀卡器實驗“中的文件:
拷貝例程中的驅動:USB文件下到項目中,然后把剛才項目中初始化和測試文件系統的代碼注釋掉。
第一步:案例中定義了兩個存儲介質(SD卡和SPI-Flash),但是我們只需要SPI-Flash,所以將
#define MAX_LUN 0//原來為1
然后把磁盤操作改為只是對磁盤0有效:
u16 MAL_Write(u8 lun, u32 Memory_Offset, u32 *Writebuff, u16 Transfer_Length)
{
u8 STA;
switch (lun)
{
case 0:
STA=0;
SPI_Flash_Write((u8*)Writebuff, Memory_Offset, Transfer_Length);
break;
default:
return MAL_FAIL;
}
if(STA!=0)return MAL_FAIL;
return MAL_OK;
}
u16 MAL_Read(u8 lun, u32 Memory_Offset, u32 *Readbuff, u16 Transfer_Length)
{
u8 STA;
switch (lun)
{
case 0:
STA=0;
SPI_Flash_Read((u8*)Readbuff, Memory_Offset, Transfer_Length);
break;
default:
return MAL_FAIL;
}
if(STA!=0)return MAL_FAIL;
return MAL_OK;
}
u16 MAL_GetStatus (u8 lun)
{
switch(lun)
{
case 0:
return MAL_OK;
case 1:
return MAL_FAIL;
case 2:
return MAL_FAIL;
default:
return MAL_FAIL;
}
}
初始化的地方添加SPI-Flash初始化的操作和U盤信息初始化:
u16 MAL_Init(u8 lun)
{
u16 status = MAL_OK;
switch (lun)
{
case 0:
Mass_Memory_Size[0]= 1024*1024*4;//4M字節
Mass_Block_Size[0] = 512;
Mass_Block_Count[0]= Mass_Memory_Size[0]/Mass_Block_Size[0];
SPI_Flash_Init();
if(SPI_FLASH_TYPE != FLASH_ADDRESS)
status = RES_NOTRDY;
else
status = RES_OK;
break;
default:
return MAL_FAIL;
}
return status;
}
第二步:先將案例中的和本項目中相沖突的代碼去掉,例如LED部分;
實驗案例中的這兩個文件先不要加入到項目中stm32f10x_it.h、stm32f10x_it.c,因為我自己起的案例中已經有這兩個文件了,只要將stm32f10x_it.c中的中斷函數和頭文件包含內容復制到項目中的stm32f10x_it.c;
還有幾個頭文件包含問題就能編譯過。
第三步:開始初始化USB
static void usb_port_set(u8 enable)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
if(enable)
_SetCNTR(_GetCNTR()&(~(1<<1)));//退出斷電模式
else
{
_SetCNTR(_GetCNTR()|(1<<1)); // 斷電模式
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_12);
}
}
//USB口初始化
static void UsbPortInit()
{
//USB先斷開再連接
usb_port_set(0);
delay_ms(100);
usb_port_set(1);
//USB中斷配置
USB_Interrupts_Config();
//USB時鍾配置
Set_USBClock();
//USB初始化
USB_Init();
}
當USB-device插入的時候,從設備需要將USB-D+通過1.5K電阻拉高,讓主機識別到這是一個高速USB設備,所以在Mass_init()的函數中有
void MASS_init()
{
MAL_Config();//在mass初始化的時候添加磁盤初始化動作
......
/* Connect the device */
PowerOn();
......
}
RESULT PowerOn(void)
{
......
/*** cable plugged-in ? ***/
/*while(!CablePluggedIn());*/
USB_Cable_Config(ENABLE);
......
}
當USB上電時,將電阻上拉到3.3V,讓主機識別到
void USB_Cable_Config (FunctionalState NewState)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_Init(GPIOC, &GPIO_InitStructure);
if (NewState != DISABLE)
GPIO_SetBits(GPIOC,GPIO_Pin_11);
else
GPIO_ResetBits(GPIOC,GPIO_Pin_11);
}
第四步:添加USB狀態檢測
static void Fatfs_task(void *pdata)
{
u8 offline_cnt=0;
u8 tct=0;
u8 USB_STA;
u8 Divece_STA;
//log_debug("erase chip\r\n");SPI_Flash_Erase_Chip();
RTC_Init();//this will delay starting
printf("Now:%04d-%02d-%02d %02d:%02d:%02d\r\n",
LocalSetting.rtc.w_year,LocalSetting.rtc.w_month,LocalSetting.rtc.w_date,
LocalSetting.rtc.hour,LocalSetting.rtc.min,LocalSetting.rtc.sec);
#if 0
if(fileSystemInit())
printf("fileSystemInit fail\r\n");
else
printf("fileSystemInit ok\r\n");
#endif
UsbPortInit();
while(1)
{
delay_ms(1);
if(USB_STA!=USB_STATUS_REG)//狀態改變了
{
if(USB_STATUS_REG&0x01)//正在寫
{
//printf("USB Writing...\r\n");//提示USB正在寫入數據
}
if(USB_STATUS_REG&0x02)//正在讀
{
printf("USB Reading...\r\n");//提示USB正在讀出數據
}
if(USB_STATUS_REG&0x04)
printf("USB Write Err\r\n");//提示寫入錯誤
if(USB_STATUS_REG&0x08)
printf("USB Read Err\r\n");//提示讀出錯誤
USB_STA=USB_STATUS_REG;//記錄最后的狀態
}
if(Divece_STA!=bDeviceState)
{
if(bDeviceState==CONFIGURED)
printf("USB Connected\r\n");//提示USB連接已經建立
else
printf("USB DisConnected\r\n");//提示USB被拔出了
Divece_STA=bDeviceState;
}
tct++;
if(tct==200)
{
tct=0;
if(USB_STATUS_REG&0x10)
{
offline_cnt=0;//USB連接了,則清除offline計數器
bDeviceState=CONFIGURED;
}
else//沒有得到輪詢
{
offline_cnt++;
if(offline_cnt>10)
bDeviceState=UNCONNECTED;//2s內沒收到在線標記,代表USB被拔出了
}
USB_STATUS_REG=0;
}
}
}
編譯下載后就能看到pc端的U盤了,格式化以后就能使用。
但是發現格式化的時候只有480KB的大小。
經過很久的查找資料發現將文件系統的操作單位改為和USB的Mass_Block_Size大小一樣為512時就能檢測到4MB的U盤。
#define FLASH_SECTOR_SIZE 512//(4*1024)
有幾點需要注意的是:
Mass_Block_Size的大小只能設置為512,不然PC識別USB大容量存儲設備
文件系統的操作單位大小FLASH_SECTOR_SIZE要和Mass_Block_Size一致
最好在文件系統准備好以后再去掛載USB設備
如果單片機在文件系統中創建或者修改文件以后,在PC端是不能被立馬查看到的,需要重新插拔。
其中有些問題可能還不是很理解或者有描述不准確的地方,希望大家一起探討。