> 文档中心 > W801单片机学习笔记——SDK中一些难以理解的地方及修改意见

W801单片机学习笔记——SDK中一些难以理解的地方及修改意见

目录

1.前言

2.FreeRTOS嵌入式实时操作系统相关功能吐槽及修改意见

2.1首先,先给W801的SDK撑个腰

2.2新建进程函数修改

2.3内存申请与释放算法

2.4信号量获取函数的奇怪设计

3.FATFS文件系统相关功能吐槽及修改意见

3.1文件读写尚可,大批量读写有时出问题

4.挖坑


1.前言

W801单片机配套的SDK功能非常丰富,内置了FreeRTOS嵌入式实时操作系统,LWIP网络通讯协议栈以及HTTP、MQTT等等应用层的通讯协议,从宏观上来看,开发起来感觉得心应手。但是在使用中,确实有不少地方想吐槽一下,也想违抗纪律修改一下SDK中的部分文件,嗯,然后真这么做了,哈哈哈。

SDK下载连接如下(CSDN下载):

W801单片机官方SDK,更新时间2021年11月-单片机文档类资源-CSDN下载

SDK下载连接如下(百度云):

链接:https://pan.baidu.com/s/1nN4M4OZhPb8Upxpmlyneqg 
提取码:SYHT

FreeRTOS和Fatfs修改均修改中间层的文件,具体修改参考MDK的CMSIS软件包和STM32的HAL固件包。

该篇文章会随着后期不断学习的深入而增加内容,此次只是用W801制作了播放器功能,仅涉及到FreeRTOS嵌入式实时操作系统和FATFS文件系统两个板块。

2.FreeRTOS嵌入式实时操作系统相关功能吐槽及修改意见

2.1首先,先给W801的SDK撑个腰

这部分在我使用的时候已经发现有不少帖子在吐槽了,其中有不少同志表示到W801的SDK将FreeRTOS封装了一层,搞得莫名其妙,举例如下(文件wm_osal_rtos.c):

 void tls_os_time_delay(u32 ticks){vTaskDelay(ticks);}

该函数封装了FreeRTOS的延时功能,那么为什么要加中间这一层呢?这样做是为了更好得适配各式各样的操作系统,通过这个中间层,将不同操作系统功能函数的差异消除,为上层用户程序提供统一的接口。

这种做法在MDK中也出现了,在MDK中的cmsis_os.c或cmsis_os2.c文件中,同样举一例延时函数的例子,如下(cmsis_os2.c):

osStatus_t osDelay (uint32_t ticks) {  osStatus_t stat;  if (IS_IRQ()) {    stat = osErrorISR;  }  else {    stat = osOK;    if (ticks != 0U) {      vTaskDelay(ticks);    }  }  return (stat);}

MDK这样做已经是有先列的,MDK通过这种方式可以在应用层不修改的前提条件下适配FreeRTOS、RTX、ThreadX等嵌入式实时操作系统。

2.2新建进程函数修改

从W801的SDK的默认例程代码中可以发现,W801的启动过程中总共新建了两个进程,分别是task_start(在wm_main.c)和demo_console_task(在wm_demo_console_task.c)。W801启动过程详细见:W801单片机学习笔记——SDK的启动流程,例程使用_三月花科技的博客-CSDN博客

task_start进程的创建代码如下:

 tls_os_task_create(&tststarthdl, NULL,      task_start,      (void *)0,      (void *)TaskStartStk,   /* 任务栈的起始地址 */      TASK_START_STK_SIZE * sizeof(u32), /* 任务栈的大小     */      1,      0);

demo_console_task进程的创建代码如下:

    tls_os_task_create(NULL, NULL,  demo_console_task,  NULL,  (void *)DemoTaskStk,   /* task's stack start address */  DEMO_TASK_SIZE * sizeof(u32), /* task's stack size, unit:byte */  DEMO_TASK_PRIO,  0);

仔细看这会发现这两个创建新进程的函数调用时比以往我们大多数使用嵌入式操作系统创建新进程时多了第6个参数即“任务栈的起始地址”。这种写法意味着在新建进程前任务的堆栈就要申请好,在进程删除时进程堆栈也需要手动释放,这与使用习惯大相径庭,大多数情况进程堆栈应该由操作系统自动得申请和释放。

下面将根据MDK的CMSIS软件包中的cmsis_os2.c文件中的osTheadNew函数的实现思路来修改tls_os_task_create函数。

cmsis_os2.c文件中的osTheadNew函数的实现篇幅较长,这里直接说思路吧,该函数通过判断在调用osTheadNew时的参数中判断进程控制块起始地址和进程堆栈起始地址是否为NULL,若都不为NULL则调用xTaskCreateStatic(FreeRTOS静态创建进程API),若都为NULL则调用xTaskCreate(FreeRTOS创建进程API)。

下面将以相同的思路修改tls_os_task_create,修改后的代码如下(在wm_osal_rtos.c):

tls_os_status_t tls_os_task_create(tls_os_task_t *task,      const char* name,      void (*entry)(void* param),      void* param,      u8 *stk_start,      u32 stk_size,      u32 prio,      u32 flag){    u8 error;    tls_os_status_t os_status;if(stk_start == NULL){error = xTaskCreate(entry,(const signed char *)name,stk_size/sizeof(u32),param,configMAX_PRIORITIES - prio,/*优先级颠倒一下,与ucos优先级顺序相反*/task);} else{error = xTaskCreateExt(entry,(const signed char *)name,(portSTACK_TYPE *)stk_start,stk_size/sizeof(u32),param,configMAX_PRIORITIES - prio,/*优先级颠倒一下,与ucos优先级顺序相反*/task);}    if (error == pdTRUE) os_status = TLS_OS_SUCCESS;    else os_status = TLS_OS_ERROR;    return os_status;}

通过判断stk_start是否为NULL,如果为NULL则调用xTaskCreate若不为NULL则调用xTaskCreateExt函数。

此外,还要吐槽一点,想必此SDK的开发者是个UCOS的铁粉,由于FreeRTOS的优先级次序与UCOS相反,他在制作中间层的时候强行反转了优先级使得此FreeRTOS的优先级与UCOS相同,若原应用层代码编写时是按照FreeRTOS的次序,请将代码中注释为“优先级颠倒一下,与ucos优先级顺序相反”的那行代码直接改成prio。

2.3内存申请与释放算法

W801的SDK中默认添加的是heap.2算法,该算法没有碎片整理功能,不适合需要经常申请和释放大小不同的内存的应用,例如笔者制作的播放器,会因格式不同选择不同的解码算法而申请不同大小的内存,在实际使用时会因内存碎片严重利用率下降最终无法运行。

针对这个问题,改成heap.4即可,具体heap.4文件可以直接从STM32或其它平台的使用FreeRTOS的例程中获得即可,因为内存申请算法与硬件平台没有直接关系。

由于W801的SDK自带的FreeRTOS版本太过久远(V7.2.3,2011年版本)难以找到与之版本匹配的heap_4.c,故直接将手头的V10.3.1,2020年版本进行强行移植。直接将该文件复制到与heap_2.c相同的文件夹下,并且将heap_2.c替换为heap_4.c文件。替换后做如下改动:

  1. 在开头包含头文件处,添加#include "wm_config.h"
  2. 屏蔽hanshuvoid vPortGetHeapStats( HeapStats_t *pxHeapStats )原因是V7.2.3版本的FreeRTOS不支持这个功能,没有HeapStats_t结构体的定义,屏蔽此函数对后续功能没有影响
  3. 在FreeRTOS.h文件中添加一些宏定义以适应新版本的heap_4.h,添加内容如下:
  4. #ifndef mtCOVERAGE_TEST_MARKER#define mtCOVERAGE_TEST_MARKER()#endif#ifndef traceMALLOC    #define traceMALLOC( pvAddress, uiSize )#endif#ifndef traceFREE    #define traceFREE( pvAddress, uiSize )#endif

     

  5. 在heap_4.c末尾添加如下函数
  6. int xPortMemIsKernel(void *mem){    return ((mem >= (void *)ucHeap) && (mem <= (void *)(ucHeap + configTOTAL_HEAP_SIZE))) ? 1 : 0;}

修改后的heap_4.c文件如下,修改时请做对比:

/* * FreeRTOS Kernel V10.3.1 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * http://www.FreeRTOS.org * http://aws.amazon.com/freertos * * 1 tab == 4 spaces! *//* * A sample implementation of pvPortMalloc() and vPortFree() that combines * (coalescences) adjacent memory blocks as they are freed, and in so doing * limits memory fragmentation. * * See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the * memory management pages of http://www.FreeRTOS.org for more information. */#include /* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefiningall the API functions to use the MPU wrappers.  That should only be done whentask.h is included from an application file. */#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE#include "FreeRTOS.h"#include "task.h"#include "wm_config.h"#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE//#if( configSUPPORT_DYNAMIC_ALLOCATION == 0 )//#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0//#endif/* Block sizes must not get too small. */#define heapMINIMUM_BLOCK_SIZE( ( size_t ) ( xHeapStructSize << 1 ) )/* Assumes 8bit bytes! */#define heapBITS_PER_BYTE( ( size_t ) 8 )/* Allocate the memory for the heap. */#if( configAPPLICATION_ALLOCATED_HEAP == 1 )/* The application writer has already defined the array used for the RTOSheap - probably so it can be placed in a special segment or address. */extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];#elsestatic uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];#endif /* configAPPLICATION_ALLOCATED_HEAP *//* Define the linked list structure.  This is used to link free blocks in orderof their memory address. */typedef struct A_BLOCK_LINK{struct A_BLOCK_LINK *pxNextFreeBlock;/*<< The next free block in the list. */size_t xBlockSize;/*< 0 ){xWantedSize += xHeapStructSize;/* Ensure that blocks are always aligned to the required numberof bytes. */if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 ){/* Byte alignment required. */xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}if( ( xWantedSize > 0 ) && ( xWantedSize xBlockSize pxNextFreeBlock != NULL ) ){pxPreviousBlock = pxBlock;pxBlock = pxBlock->pxNextFreeBlock;}/* If the end marker was reached then a block of adequate sizewasnot found. */if( pxBlock != pxEnd ){/* Return the memory space pointed to - jumping over theBlockLink_t structure at its start. */pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );/* This block is being returned for use so must be taken outof the list of free blocks. */pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;/* If the block is larger than required it can be split intotwo. */if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ){/* This block is to be split into two.  Create a newblock following the number of bytes requested. The voidcast is used to prevent byte alignment warnings from thecompiler. */pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );/* Calculate the sizes of two blocks split from thesingle block. */pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;pxBlock->xBlockSize = xWantedSize;/* Insert the new block into the list of free blocks. */prvInsertBlockIntoFreeList( pxNewBlockLink );}else{mtCOVERAGE_TEST_MARKER();}xFreeBytesRemaining -= pxBlock->xBlockSize;if( xFreeBytesRemaining xBlockSize |= xBlockAllocatedBit;pxBlock->pxNextFreeBlock = NULL;xNumberOfSuccessfulAllocations++;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}traceMALLOC( pvReturn, xWantedSize );}( void ) xTaskResumeAll();#if( configUSE_MALLOC_FAILED_HOOK == 1 ){if( pvReturn == NULL ){extern void vApplicationMallocFailedHook( void );vApplicationMallocFailedHook();}else{mtCOVERAGE_TEST_MARKER();}}#endifconfigASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );return pvReturn;}/*-----------------------------------------------------------*/void vPortFree( void *pv ){uint8_t *puc = ( uint8_t * ) pv;BlockLink_t *pxLink;if( pv != NULL ){/* The memory being freed will have an BlockLink_t structure immediatelybefore it. */puc -= xHeapStructSize;/* This casting is to keep the compiler from issuing warnings. */pxLink = ( void * ) puc;/* Check the block is actually allocated. */configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );configASSERT( pxLink->pxNextFreeBlock == NULL );if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 ){if( pxLink->pxNextFreeBlock == NULL ){/* The block is being returned to the heap - it is no longerallocated. */pxLink->xBlockSize &= ~xBlockAllocatedBit;vTaskSuspendAll();{/* Add this block to the list of free blocks. */xFreeBytesRemaining += pxLink->xBlockSize;traceFREE( pv, pxLink->xBlockSize );prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );xNumberOfSuccessfulFrees++;}( void ) xTaskResumeAll();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}}/*-----------------------------------------------------------*/size_t xPortGetFreeHeapSize( void ){return xFreeBytesRemaining;}/*-----------------------------------------------------------*/size_t xPortGetMinimumEverFreeHeapSize( void ){return xMinimumEverFreeBytesRemaining;}/*-----------------------------------------------------------*/void vPortInitialiseBlocks( void ){/* This just exists to keep the linker quiet. */}/*-----------------------------------------------------------*/static void prvHeapInit( void ){BlockLink_t *pxFirstFreeBlock;uint8_t *pucAlignedHeap;size_t uxAddress;size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;/* Ensure the heap starts on a correctly aligned boundary. */uxAddress = ( size_t ) ucHeap;if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 ){uxAddress += ( portBYTE_ALIGNMENT - 1 );uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;}pucAlignedHeap = ( uint8_t * ) uxAddress;/* xStart is used to hold a pointer to the first item in the list of freeblocks.  The void cast is used to prevent compiler warnings. */xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;xStart.xBlockSize = ( size_t ) 0;/* pxEnd is used to mark the end of the list of free blocks and is insertedat the end of the heap space. */uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;uxAddress -= xHeapStructSize;uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );pxEnd = ( void * ) uxAddress;pxEnd->xBlockSize = 0;pxEnd->pxNextFreeBlock = NULL;/* To start with there is a single free block that is sized to take up theentire heap space, minus the space taken by pxEnd. */pxFirstFreeBlock = ( void * ) pucAlignedHeap;pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;pxFirstFreeBlock->pxNextFreeBlock = pxEnd;/* Only one block exists - and it covers the entire usable heap space. */xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;/* Work out the position of the top bit in a size_t variable. */xBlockAllocatedBit = ( ( size_t ) 1 ) <pxNextFreeBlock pxNextFreeBlock ){/* Nothing to do here, just iterate to the right position. */}/* Do the block being inserted, and the block it is being inserted aftermake a contiguous block of memory? */puc = ( uint8_t * ) pxIterator;if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert ){pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;pxBlockToInsert = pxIterator;}else{mtCOVERAGE_TEST_MARKER();}/* Do the block being inserted, and the block it is being inserted beforemake a contiguous block of memory? */puc = ( uint8_t * ) pxBlockToInsert;if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock ){if( pxIterator->pxNextFreeBlock != pxEnd ){/* Form one big block from the two blocks. */pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;}else{pxBlockToInsert->pxNextFreeBlock = pxEnd;}}else{pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;}/* If the block being inserted plugged a gab, so was merged with the blockbefore and the block after, then it's pxNextFreeBlock pointer will havealready been set, and should not be set here as that would make it pointto itself. */if( pxIterator != pxBlockToInsert ){pxIterator->pxNextFreeBlock = pxBlockToInsert;}else{mtCOVERAGE_TEST_MARKER();}}/*-----------------------------------------------------------*///void vPortGetHeapStats( HeapStats_t *pxHeapStats )//{//BlockLink_t *pxBlock;//size_t xBlocks = 0, xMaxSize = 0, xMinSize = portMAX_DELAY; /* portMAX_DELAY used as a portable way of getting the maximum value. */////vTaskSuspendAll();//{//pxBlock = xStart.pxNextFreeBlock;/////* pxBlock will be NULL if the heap has not been initialised.  The heap//is initialised automatically when the first allocation is made. *///if( pxBlock != NULL )//{//do//{///* Increment the number of blocks and record the largest block seen//so far. *///xBlocks++;////if( pxBlock->xBlockSize > xMaxSize )//{//xMaxSize = pxBlock->xBlockSize;//}////if( pxBlock->xBlockSize xBlockSize;//}/////* Move to the next block in the chain until the last block is//reached. *///pxBlock = pxBlock->pxNextFreeBlock;//} while( pxBlock != pxEnd );//}//}//xTaskResumeAll();////pxHeapStats->xSizeOfLargestFreeBlockInBytes = xMaxSize;//pxHeapStats->xSizeOfSmallestFreeBlockInBytes = xMinSize;//pxHeapStats->xNumberOfFreeBlocks = xBlocks;////taskENTER_CRITICAL();//{//pxHeapStats->xAvailableHeapSpaceInBytes = xFreeBytesRemaining;//pxHeapStats->xNumberOfSuccessfulAllocations = xNumberOfSuccessfulAllocations;//pxHeapStats->xNumberOfSuccessfulFrees = xNumberOfSuccessfulFrees;//pxHeapStats->xMinimumEverFreeBytesRemaining = xMinimumEverFreeBytesRemaining;//}//taskEXIT_CRITICAL();//}/*-----------------------------------------------------------*/int xPortMemIsKernel(void *mem){    return ((mem >= (void *)ucHeap) && (mem <= (void *)(ucHeap + configTOTAL_HEAP_SIZE))) ? 1 : 0;}

也可以直接下载添加进工程(添加前记得修改freertos.h文件)

适配W801的SDK的heap_4.c-单片机文档类资源-CSDN下载

经过实际测试发现:原播放器来回切换曲目内存不足的问题解决,现在可以自由得切换不同格式的曲目(播放器将会在后续介绍)。

2.4信号量获取函数的奇怪设计

该函数的位置在wm_osal_rtos.c中,具体内容如下:

 tls_os_status_t tls_os_sem_acquire(tls_os_sem_t *sem, u32 wait_time){    u8 error;    tls_os_status_t os_status;unsigned int time;if(0 == wait_time)time = portMAX_DELAY;elsetime = wait_time;portBASE_TYPE pxHigherPriorityTaskWoken = pdFALSE;u8 isrcount = 0;isrcount = tls_get_isr_count();if(isrcount > 0){error = xSemaphoreTakeFromISR((xQUEUE *)sem, &pxHigherPriorityTaskWoken );if((pdTRUE == pxHigherPriorityTaskWoken) && (1 == isrcount)){portYIELD_FROM_ISR(TRUE);}}else{error = xSemaphoreTake((xQUEUE *)sem, time );}    if (error == pdPASS) os_status = TLS_OS_SUCCESS;    else os_status = TLS_OS_ERROR;    return os_status;}

该函数将获取信号量,若信号量小于等于一,则将阻塞当前进程,其阻塞的最大时间长度由第二个参数决定,即超过第二个参数设置的最大时间后,将越过信号量等待继续向下执行。

该函数有时会用于给二值信号量清零,例如在功能重新开始运行时,由于信号量的状态未知,可以用此功能并给第二个参数给0x00,意味着不等待直接给信号量清零。然而此种使用方式将会在此SDK环境下出问题,原因正如上面的代码开头所示,当等待时间为0时,将会强行给实际等待时间赋值最大值,这个思路非常清奇,我无法理解,就像是夸我做的好然后扇我一巴掌。于是将开头的if判断直接删除。

3.FATFS文件系统相关功能吐槽及修改意见

3.1文件读写尚可,大批量读写有时出问题

问题出在底层的读写函数中,该函数的位置在如下图所示文件中。

 对应的函数分别是MMC_disk_read和MMC_disk_write。接下来以读为例进行讨论,MMC_disk_read原代码如下:

static int MMC_disk_read(BYTE *buff, LBA_t sector, UINT count){int ret, i;int buflen = BLOCK_SIZE*count;    BYTE *rdbuff = buff;if (((u32)buff)&0x3) /*non aligned 4*/{    rdbuff = tls_mem_alloc(buflen);if (rdbuff == NULL){return -1;}}    for( i=0; i 1) {    ret = wm_sd_card_blocks_read(fs_rca, sector, (char *)rdbuff, buflen); }if( ret == 0 ) break;}    if(rdbuff != buff)    { if(ret == 0) {     memcpy(buff, rdbuff, buflen); } tls_mem_free(rdbuff);    }return ret;}

该函数的功能时按扇区读SD卡到缓冲区,可以一次读取一个或多个缓冲区。然而由于连接SD卡的外设SDIO-HOST具有DMA的功能,于是需要4字节对齐才能正常工作,而传入此函数的缓冲区不一定4字节对齐,于是就有了函数中的判断if (((u32)buff)&0x3),申请内存,并读取,读取后复制到原缓冲区。

这个思路从理论上来看是没有问题的,然而在实际使用当中,若是一次需要读很多个扇区,且缓冲区没有4字节对齐的话就需要在读取时先申请一个多个缓冲区大小的空间,读取并复制。所以当内存资源紧缺时,将无法再申请一个足够大小的空间从而导致返回错误。其次,若从参数传入的缓冲区频繁得没有4字节对齐的话将会导致频繁得申请和释放内存,尤其是该SDK原配的heap_2.c无法应对大小不同的内存申请而导致的内存碎片,故在MMC_disk_read函数多测折腾下彻底摆烂,不断返回错误。

由于没有4字节对齐,所以需要找一个已经4字节对齐的空间读取,然后复制。好像没有什么更好的方法能够替代这个思路了,但是同样的思路也可以有不同的实现方法。STM32的官方SDK中同样有对于SD卡4字节对齐问题处理的读写代码,一个字“妙”,下面就参照STM32的官方SDK的SD卡扇区读写函数进行修改。STM32的扇区读函数如下:

DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count){  uint8_t ret;  DRESULT res = RES_ERROR;  uint32_t timer;#if (osCMSIS < 0x20000U)  osEvent event;#else  uint16_t event;  osStatus_t status;#endif#if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)  uint32_t alignedAddr;#endif  /*  * ensure the SDCard is ready for a new operation  */  if (SD_CheckStatusWithTimeout(SD_TIMEOUT) < 0)  {    return res;  }#if defined(ENABLE_SCRATCH_BUFFER)  if (!((uint32_t)buff & 0x3))  {#endif    /* Fast path cause destination buffer is correctly aligned */    ret = BSP_SD_ReadBlocks_DMA((uint32_t*)buff, (uint32_t)(sector), count);    if (ret == MSD_OK) {#if (osCMSIS < 0x20000U)    /* wait for a message from the queue or a timeout */    event = osMessageGet(SDQueueID, SD_TIMEOUT);    if (event.status == osEventMessage)    {      if (event.value.v == READ_CPLT_MSG)      { timer = osKernelSysTick(); /* block until SDIO IP is ready or a timeout occur */ while(osKernelSysTick() - timer <SD_TIMEOUT)#else   status = osMessageQueueGet(SDQueueID, (void *)&event, NULL, SD_TIMEOUT);   if ((status == osOK) && (event == READ_CPLT_MSG))   {     timer = osKernelGetTickCount();     /* block until SDIO IP is ready or a timeout occur */     while(osKernelGetTickCount() - timer <SD_TIMEOUT)#endif     {if (BSP_SD_GetCardState() == SD_TRANSFER_OK){  res = RES_OK;#if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)  /*  the SCB_InvalidateDCache_by_Addr() requires a 32-Byte aligned address,  adjust the address and the D-Cache size to invalidate accordingly.  */  alignedAddr = (uint32_t)buff & ~0x1F;  SCB_InvalidateDCache_by_Addr((uint32_t*)alignedAddr, count*BLOCKSIZE + ((uint32_t)buff - alignedAddr));#endif  break;}     }#if (osCMSIS < 0x20000U)   } }#else      }#endif    }#if defined(ENABLE_SCRATCH_BUFFER)    }    else    {      /* Slow path, fetch each sector a part and memcpy to destination buffer */      int i;      for (i = 0; i < count; i++)      { ret = BSP_SD_ReadBlocks_DMA((uint32_t*)scratch, (uint32_t)sector++, 1); if (ret == MSD_OK ) {   /* wait until the read is successful or a timeout occurs */#if (osCMSIS < 0x20000U)   /* wait for a message from the queue or a timeout */   event = osMessageGet(SDQueueID, SD_TIMEOUT);   if (event.status == osEventMessage)   {     if (event.value.v == READ_CPLT_MSG)     {timer = osKernelSysTick();/* block until SDIO IP is ready or a timeout occur */while(osKernelSysTick() - timer <SD_TIMEOUT)#else  status = osMessageQueueGet(SDQueueID, (void *)&event, NULL, SD_TIMEOUT);if ((status == osOK) && (event == READ_CPLT_MSG)){  timer = osKernelGetTickCount();  /* block until SDIO IP is ready or a timeout occur */  ret = MSD_ERROR;  while(osKernelGetTickCount() - timer < SD_TIMEOUT)#endif  {    ret = BSP_SD_GetCardState();    if (ret == MSD_OK)    {      break;    }  }  if (ret != MSD_OK)  {    break;  }#if (osCMSIS < 0x20000U)}     }#else   }#endif#if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)   /*   *   * invalidate the scratch buffer before the next read to get the actual data instead of the cached one   */   SCB_InvalidateDCache_by_Addr((uint32_t*)scratch, BLOCKSIZE);#endif   memcpy(buff, scratch, BLOCKSIZE);   buff += BLOCKSIZE; } else {   break; }      }      if ((i == count) && (ret == MSD_OK )) res = RES_OK;    }#endif  return res;}

篇幅非常长,不过我们紧紧盯住  if (!((uint32_t)buff & 0x3))即可,因为我们要借鉴的正是该条件不成立时的处理方法。可以看到当 if (!((uint32_t)buff & 0x3))不成立时,该函数每次只读取一个扇区,而读取的缓冲区名字叫scratch。scratch并未在该函数中申请空间,而是在文件开头定义了一个4字节对齐的全局变量,代码如下。

__ALIGN_BEGIN static uint8_t scratch[BLOCKSIZE] __ALIGN_END;

其解决具体方法是:当条件 if (!((uint32_t)buff & 0x3))不成立时,依次将扇区读进scratch,然后再拷贝到读缓冲区。这样做避免了反复申请内存,且由于该函数还利用了队列传递SD卡的读状态,故可以在等待DMA从SD卡传输数据时先阻塞读SD卡的进程,运行其它进程,极大地提高了整体运行效率。

下面我们模仿STM32的官方SDK的解决方法修改W801的SDK的MMC_disk_read函数,代码如下(队列阻塞的功能还未添加):

static uint8_t scratch[BLOCK_SIZE] __attribute__ ((aligned (4)));static int MMC_disk_read(BYTE *buff, LBA_t sector, UINT count){int ret=-1, i;int buflen = BLOCK_SIZE*count;if (!((u32)buff&0x3)) /*non aligned 4*/{ret = wm_sd_card_blocks_read(fs_rca, sector, (char *)buff, buflen);} else{for(i=0; i<count; i++){ret = wm_sd_card_block_read(fs_rca, sector++, (char *)scratch);memcpy(buff, scratch, BLOCK_SIZE);buff += BLOCK_SIZE;}}return ret;}

修改后读写大量扇区时再未出现错误情况,且由于无需频繁申请释放内存,效率很大程度的提高。

4.挖坑

下一期分享 W801的调试器设置。