• STM32F103外挂SPI Flash,内嵌FatFS文件系统,并通过USB接PC可访问FatFS内容


    原文链接 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端是不能被立马查看到的,需要重新插拔。
    其中有些问题可能还不是很理解或者有描述不准确的地方,希望大家一起探讨。

  • 相关阅读:
    为什么重写equals还要重写hashcode?
    谈谈关于Synchronized和lock
    springBoot为啥没有没有web.xml了
    springBoot整合mybatis开发
    springBoot的介绍与搭建
    Java i++原理及i=i++的问题说明
    Django学习笔记〇三——APP以及的文件结构
    Django学习笔记〇二——第一个Django项目
    Django学习笔记〇一——从web的概念引入
    MySQL学习笔记——〇六SQLAlchemy框架
  • 原文地址:https://www.cnblogs.com/birdBull/p/15908552.html
Copyright © 2020-2023  润新知