GD32+FATFS+SDIO OLED播放视频 移植 踩坑 Bad Apple

开源链接:https://github.com/LuckyE993/BadApple

目录

效果展示:

准备工作:

一、SDIO

二、移植FATFS

三、视频取模与代码实现


效果展示:

【移植】GD32 FATFS SDIO 播放二进制Bad Apple

准备工作:

        GD32F4xx官方固件库

        调试好可使用的OLED屏幕(本文基于0.96'单色OLED)

        <=16GB的TF卡 并格式化

       Image2LCD 取模软件 

        一个Linux系统用于合并中间文件


一、SDIO

        这一部分主要是将官方固件库中给出的SDIO例程整合到自已的工程里,要点如下:

  1. 首先找到SDIO的示例文件夹位置GD32F4xx_Firmware_Library_V3.0.2\GD32F4xx_Firmware_Library\Examples\SDIO\Read_write ,在代码编辑器中打开这个文件夹。在这里我们需要复制的文件是以下sdcard.c 和sdcard.h文件,我们将其复制到自己的工程下,这里我放在Hardware下的SDIO文件中。在Keil中将文件添加进工程中。
  2. 打开SDIO\Read_write\main.c文件,首先是定义了一些变量和函数声明,我们可以直接将其复制进工程中。接着,将三个函数体也复制到工程中。
    sd_card_info_struct sd_cardinfo;                            /* information of SD card */
    uint32_t buf_write[512];                                    /* store the data written to the card */
    uint32_t buf_read[512];                                     /* store the data read from the card */
    
    void nvic_config(void);
    sd_error_enum sd_io_init(void);
    void card_info_get(void);

  3. 回到我们的工程中,在main.c文件的main函数中添加以下代码
    sd_error_enum res; //用于接收SD卡相关信息
    	res = sd_io_init();
    	
    	if(SD_OK != res)
    	{
    		printf("初始化失败,请检查代码或SD卡...");
    		while(1);
    	}
    	else
    	{
    		card_info_get();
    	}

    这段代码创建一个存储SD卡相关操作错误信息的变量,然后对SD卡进行初始化,如果初始化成功,会在串口打印SD卡的相关信息,也就说明SD卡初始化成功了。编译后将程序下载到单片机,打开串口工具,按下复位键可以看到SD卡初始化的相关信息。偶现初始化失败,但再复位就OK,常见于单片机初次上电。初始化成功串口打印信息如下 :

    Card information:
    ## Card version 4.xx ##
    ## SDHC card ##
    ## Device size is 15558144KB ##
    ## Block size is 512B ##
    ## Block count is 31116288 ##
    ## CardCommandClasses is: 5b5 ##
    ## Block operation supported ##
    ## Erase supported ##
    ## Lock unlock supported ##
    ## Application specific supported ##
    ## Switch function supported ##


 注意:移植SDIO的部分与移植FATFS的部分相互独立,也就是说在移植FATFS中会对SDIO部分做相应改动。


二、移植FATFS

        现在移植FATFS文件管理系统,GD32的固件库中也提供了相关的文件,在GD32F4xx_Firmware_Library_V3.0.2\GD32F4xx_Firmware_Library\Utilities\Third_Party\fat_fs下有两个文件夹inc和src,将这两个文件夹复制到自己的工程中,其他的三个txt文档不用复制。

        回到我们的工程中添加src和inc中的文件,编译运行。

        注意:编译有很多error,请先确认你用的是激活过的keil,因为FATFS系统有点大会超过社区版上限,还会报类似于L6050U这类错误代码。现在来看错误代码:

7个错误中有6个要求调整至C99模式,1个error是找不到usb_conf.h。

我们先解决第一个,勾选C99选项。

 第二个我们直接在报错文件integer.h中注释掉该语句即可。

        编译无错误后就可以进行下一步了,FATFS已经将底层代码写好了,只需要修改提供的几个接口函数即可。

         在这里只需要修改diskio.c即可,但我们首先打开diskio.h文件看有什么函数需要修改。

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);

         显而易见,他们分别是用于实现初始化、获取状态、读写和控制功能的函数,我们逐个进行修改。

        在此之前,我们先进行一下宏定义,其中ATA代表我们的SD卡,盘符为0,块的大小为512,busmode为4bit。

#define ATA 0
#define SPI_FLASH 1

#define BLOCKSIZE 512
#define BUSMODE_4BIT

        宏定义后,我们看disk_status函数,回到diskio.c。

//修改之前
DSTATUS disk_status (
    BYTE pdrv       /* Physical drive number to identify the drive */
)
{
    DSTATUS stat;

    stat = disk.drv[pdrv]->disk_status(disk.lun[pdrv]);

    return stat;
}

         传入一个prdv参数用于区分磁盘,接着读取磁盘的状态并返回。因为我们在宏定义中定义了两块磁盘,我们选择switch函数来选择,在返回状态前,一定要使用FATFS里的DSTATUS类型,因为GD的库返回的参数会有些许的不同,包括后边的一些函数也是,这是最容易出错的一点。修改后的代码如下

//修改之后
DSTATUS disk_status (
    BYTE pdrv       /* Physical drive number to identify the drive */
)
{
    DSTATUS stat = STA_NOINIT;

    switch(pdrv)
	{
		case ATA:
			stat = RES_OK;
			break;
		case SPI_FLASH:
			stat = STA_NOINIT;
	}

    return stat;
}

再来看 disk_initialize函数,原始代码非常简单,我们要做的是在这一步将我们的SD卡给初始化。

//修改之前
DSTATUS disk_initialize (
    BYTE pdrv       /* Physical drive nmuber to identify the drive */
)
{
    DSTATUS stat = RES_OK;

    if (disk.is_initialized[pdrv] == 0) {
        disk.is_initialized[pdrv] = 1;
        stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);
    }

    return stat;
}

 既然这一步主要是SD卡初始化,我们就去看样例SDIO是怎样初始化的,因为我们在第二步中已经将函数复制到main.c中了,我们直接去找相关函数,可以发现初始化函数为sd_io_init()函数。分析一下这个函数的流程

SD卡初始化 -> 获取SD卡信息 -> 选择指定SD卡 -> SD卡状态获取 -> SD卡总线模式选择 -> SD卡传输模式选择

 我们可以在switch选中我们的SD卡后 复制绝大多数代码过去,但我们要按照FATFS的格式返回标志信息,所以我们需要手动return一下,修改后的代码如下(为了debug方便我加了一写printf,不需要可以删除):

extern sd_card_info_struct sd_cardinfo;//使用main.c中存储SD卡信息的变量
DSTATUS disk_initialize (
    BYTE pdrv       /* Physical drive nmuber to identify the drive */
)
{
    DSTATUS stat = STA_NOINIT;
	uint32_t cardstate = 0;
	
    switch(pdrv)
	{
		case ATA:
			printf("SD_Init...\r\n");
			stat = sd_init();
			if(SD_OK == stat)
			{
				stat = sd_card_information_get(&sd_cardinfo);
				printf("sd_card_information_get\r\n");
			}else
			{
				return STA_NOINIT;
			}
			if(SD_OK == stat)
			{
				stat = sd_card_select_deselect(sd_cardinfo.card_rca);
				printf("sd_card_select_deselect\r\n");
				
			}else
			{
				return STA_NOINIT;
			}
			stat = sd_cardstatus_get(&cardstate);
			if(cardstate & 0x02000000)
			{
				while(1)
				{}
			}
			
			if(SD_OK == stat)
			{
				#ifdef BUSMODE_4BIT
					stat = sd_bus_mode_config(SDIO_BUSMODE_4BIT);
				#else 
					stat = sd_bus_mode_config(SDIO_BUSMODE_1BIT);
				#endif
				printf("sd_bus_mode_config\r\n");
			}else
			{
				return STA_NOINIT;
			}
			
			if(SD_OK == stat)
			{
				#ifdef DMA_MODE
					stat = sd_bus_mode_config(SD_DMA_MODE);
					nvic_irq_enable(SDIO_IRQn,0,0);
				#else 
					stat = sd_transfer_mode_config(SD_POLLING_MODE);
                    printf("SD_POLLING_MODE\r\n");
				#endif
				
			}else
			{
				return STA_NOINIT;
			}
			
			if(SD_OK == stat)
			{
				return 0;
				printf("FATFS Init Success...\r\n");
			}else
			{
				return STA_NOINIT;
			}
		default:
			stat = STA_NOINIT;
	}

    return stat;
}

回到main.c文件中,将sd_io_init()整个函数体删掉,因为我们在FATFS中进行了初始化,同时main函数里也要进行修改。

接着来看disk_read函数

DRESULT disk_read (
    BYTE pdrv,      /* Physical drive nmuber to identify the drive */
    BYTE *buff,     /* Data buffer to store read data */
    DWORD sector,   /* Sector address in LBA */
    UINT count      /* Number of sectors to read */
)
{
    DRESULT res;

    res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);

    return res;
}

 传入的参数有磁盘编号,保存的buff地址,磁盘中对应扇区的位置,要读取的扇区数。我们首先在这里对传入的参数进行一次验证,包括buff地址和要读取的扇区数,防止读取出现问题。选择好磁盘后,对于要读取的扇区数分两种情况,单个扇区和多个扇区两个读取函数。这样我们就可以进行代码的编写了。注意,在GD的读取扇区的库函数中针对传入数据格式有要求,因此需要进行强制类型转换。

DRESULT disk_read (
    BYTE pdrv,      /* Physical drive nmuber to identify the drive */
    BYTE *buff,     /* Data buffer to store read data */
    DWORD sector,   /* Sector address in LBA */
    UINT count      /* Number of sectors to read */
)
{
	
    sd_error_enum status = SD_ERROR;
	if(NULL == buff)
	{
		return RES_PARERR;
	}
	if(!count)
	{
		return RES_PARERR;
	}
	switch(pdrv)
    {
        case ATA:
            if(count == 1)
            {
                status = sd_block_read((uint32_t*)(&buff[0]), (uint32_t)(sector<<9),BLOCKSIZE);
            }
            else
            {
                status = sd_multiblocks_read((uint32_t*)(&buff[0]), (uint32_t)(sector<<9),BLOCKSIZE,(uint32_t)count);
            }
            if(SD_OK == status)
            {
                return RES_OK;
            }
            return RES_ERROR;

        default:
            return RES_PARERR;
    }

}

 写入函数disk_write也是如此,这里直接给出代码

DRESULT disk_write (
    BYTE pdrv,          /* Physical drive nmuber to identify the drive */
    const BYTE *buff,   /* Data to be written */
    DWORD sector,       /* Sector address in LBA */
    UINT count          /* Number of sectors to write */
)
{
    sd_error_enum status = SD_ERROR;
    if(!count){
        return RES_PARERR;
    }

	switch(pdrv)
    {
        case ATA:
            if(count == 1)
            {
                status = sd_block_write((uint32_t*)buff, sector<<9,BLOCKSIZE);
            }
            else
            {
                status = sd_multiblocks_read((uint32_t*)buff,sector<<9,BLOCKSIZE,(uint32_t)count);
            }
            if(SD_OK == status)
            {
                return RES_OK;
            }
            return RES_ERROR;

        default:
            return RES_PARERR;
    }

}

接着是diskio.c最后一个函数disk_ioctl

DRESULT disk_ioctl (
    BYTE pdrv,      /* Physical drive nmuber (0..) */
    BYTE cmd,       /* Control code */
    void *buff      /* Buffer to send/receive control data */
)
{
    DRESULT res;

    res = disk.drv[pdrv]->disk_ioctl(disk.lun[pdrv], cmd, buff);

    return res;
}

 这个函数最重要的就是cmd参数了,我们可以在diskio.h中找到相关定义

/* Generic command (Used by FatFs) */
#define CTRL_SYNC        0    /* Complete pending write process (needed at _FS_READONLY == 0) */
#define GET_SECTOR_COUNT    1    /* Get media size (needed at _USE_MKFS == 1) */
#define GET_SECTOR_SIZE        2    /* Get sector size (needed at _MAX_SS != _MIN_SS) */
#define GET_BLOCK_SIZE        3    /* Get erase block size (needed at _USE_MKFS == 1) */
#define CTRL_TRIM        4    /* Inform device that the data on the block of sectors is no longer used (needed at _USE_TRIM == 1) */

 CTRL_SYNC在本项目中并不涉及到,因此直接退出即可;GET_SECTOR_COUNT可以通过库函数经过相关变换求得;GET_SECTOR_SIZE即SD卡的块大小,在前边初始化已经获取过,直接引用即可;GET_BLOCK_SIZE是我们宏定义的块大小,在这里是512。实现的代码如下

DRESULT disk_ioctl (
    BYTE pdrv,      /* Physical drive nmuber (0..) */
    BYTE cmd,       /* Control code */
    void *buff      /* Buffer to send/receive control data */
)
{
   uint32_t capacity;
   DRESULT status = RES_PARERR;
   switch (pdrv)
   {
   case ATA:
        switch (cmd)
        {
            case GET_SECTOR_SIZE:
                *(WORD * )buff = BLOCKSIZE;
            break;
            case GET_BLOCK_SIZE:
                *(DWORD * )buff = sd_cardinfo.card_blocksize;
            break;    
            case GET_SECTOR_COUNT:
                capacity = sd_card_capacity_get();
                *(DWORD * )buff = capacity*1024/sd_cardinfo.card_blocksize;
            break;
            case CTRL_SYNC:
            break;    
        }
        return RES_OK;
        break;
   
   default:
        status  = RES_PARERR;
    break;
   }

   return status;
}

 因为在FATFS中使用的栈大小超出了预先的设计,因此我们需要在启动文件startup_gd32f450_470.s中队栈的默认大小进行修改,修改后代码如下

; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00001000

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp

 至此,FATFS移植已经完成,下面将进行相关的FATFS文件系统使用。

回到main.c函数中,编写SD卡和FATFS初始化函数sd_fatfs_init,因为我们已经在diskic中编写过初始化代码了,因此在初始化时只需要进行挂在磁盘即可,记得配合相关的异常处理函数。

void sd_fatfs_init (void)
{
    uint16_t result = 0;
    result = f_mount(&fs,"0:",1);
    if(result == FR_NO_FILESYSTEM)
    {
        printf("No file system in SD...\r\n");
        while (1);
        
    }
    else if(result != FR_OK)
    {
        printf("File mount error for SD! Result:(%d)\r\n",result);
        printf("Initilization error maybe!\r\n");
        while (1);
        
    }
    else
    {
        printf("File system existed in SD!\r\n");
    }
}

 在主函数里调用初始化函数,FATFS文件管理系统就已经完成初始化并可以使用了.

三、视频取模与代码实现

关于视频取模与代码的实现可以参考这位大佬

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:0.96寸oled显示坏苹果(badapple)_点灯大师~的博客-CSDN博客

 在这位大佬第三步批量生成bin的步骤中,我需要补充一点内容.

推荐使用Linux进行文件合并,Windows下会出现很奇怪的问题,比如莫名其妙文件大小对不起来,最后播放的时候导致错位,频闪等。

将生成的batch文件夹压缩成压缩包后传到Linux中,使用

cat $(ls -v pic*.bin) > badapple.bin

命令合并成badapple.bin 文件 。这一步命令是将生成的文件按照升序合并。合并完后就可以拷贝到SD卡中了。

播放二进制文件可以参考大佬的函数,注意一下延时函数,显示函数等是否和自己的匹配,进行一定的修改。也可以下载我的代码编译运行。


下面给一下我的踩坑经历:

  • FATFS下SD卡始终挂载失败,检查启动文件栈大小是否修改。
  • 播放时跳帧,检查合并bin文件的时候是否按顺序合并,Windows和Linux均会出现问题,因此要使用上边的代码按顺序合成。
  • 播放时错位,重新批量生成bin文件并合并,重新来一次。
  • 上电后播放,按下复位键后检查串口输出数据,根据串口输出的数据进行debug。
  • 部分开发板偶现无法下载程序的问题,重新下载,移走手机等电磁干扰严重的电器。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值