donatello1996 发表于 2020-7-25 16:37

【STM32F769Discovery开发板试用】SD卡文件系统应用&让人自闭的硬解JPEG解码代码

本帖最后由 donatello1996 于 2020-7-25 16:41 编辑

<p>&nbsp; &nbsp; &nbsp; &nbsp; 在移植官方硬解JPEG代码的时候出现了严重问题,首先官方JPEG解码能够正常显示的文件跟BMP数组同样只支持16位和24位颜色,也就是只能成功显示图片格式为16位和24位颜色的JPEG文件,然后就是官方的JPEG解码代码工程只是用于Demo展示,没有进一步完善,官方代码中仅仅是显示一次JPEG图片,什么为之显示一次呢,如果把GPU显存看成容器,把JPEG硬件解码看成从扎着结的米袋中往容器倒米,那么目前移植的代码就是只能将米袋的结解开倒一次米,不能将米从容器中倒出来再装一次,也就是从头到尾容器只能使用一次,即使是同一张图片的数据也只能刷一次,归结到底,一是官方的代码没有做容器的复位,导致想要显示多次图片的时候会出现BUG,我目前是修复了这个BUG,不过有更多的BUG,官方的代码问题实在是多,而且显示也不稳定,不想搞了,明天有空移植一份原子的硬解JPEG代码算了,免得造轮子,一个试用活动搞得自己这么累,不值得,直接移植能用的代码不香吗?</p>

<p>&nbsp; &nbsp; &nbsp; &nbsp;要想实现JPEG解码,第一步当然是搭建文件系统,这步非常简单,即使是从ST官方那晦涩难懂的代码中直接移植也不会有什么大问题,步骤如下:<br />
第一,将所有FATFS文件系统相关驱动库函数全部移植,并修复其中的#include包含报错和stdint语法报错,能用volatile就不用__IO关键字,并尽量将core_cm7.h中一些关键定义用第三方头文件替代,减少工程对KEIL安装目录ARMCC交叉工具链的的依赖:</p>

<p></p>

<p>-其中ff.c是定义了一些文件系统常用函数,如f_open(),f_read(),f_close()之类的,用法跟stdio/stdlib的同名函数差不多,这个代码是抽象层代码,是前辈写好供大家直接调用的,不需要关心实现;ff_gen_dev.c是定义上述操作的几个相关接口;diskio.c是对于可访问磁盘设备的结构体函数抽象;所有支持文件系统的单片机都用同样的这三份文件;</p>

<p>-真正要根据每个不同单片机平台要修改的文件就是sd_diskio.c,当然名字可以变,比如用U盘的就叫usbms_diskio.c,用SPI FLASH的就叫spiflash_diskio.c,用NAND的就叫nand_diskio.c,里面就是将具体操作存储设备的底层函数挂载到diskio.c和ff_gen_dev.c的抽象层中。</p>

<p>然后是头文件,ffconf.h等。</p>

<p>成功挂载SD卡文件系统之后就可以进行一些简单的文件读写操作了,读写文件之前需要打开文件,f_open()函数的目标操作结构体是FIL:</p>

<pre>
<code>typedef struct {
        _FDID        obj;                        /* Object identifier (must be the 1st member to detect invalid object pointer) */
        BYTE        flag;                        /* File status flags */
        BYTE        err;                        /* Abort flag (error code) */
        FSIZE_t        fptr;                        /* File read/write pointer (Zeroed on file open) */
        DWORD        clust;                        /* Current cluster of fpter (invalid when fptr is 0) */
        DWORD        sect;                        /* Sector number appearing in buf[] (0:invalid) */
#if !_FS_READONLY
        DWORD        dir_sect;                /* Sector number containing the directory entry */
        BYTE*        dir_ptr;                /* Pointer to the directory entry in the win[] */
#endif
#if _USE_FASTSEEK
        DWORD*        cltbl;                        /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !_FS_TINY
        BYTE        buf;        /* File private data read/write window */
#endif
} FIL;</code></pre>

<p>然后使用f_read就可以读取文件内容了。举一个非常简单的栗子,读取TXT文件内容:</p>

<pre>
<code class="language-cpp">if(f_open(&amp;TXT_File, "1txt.txt", FA_READ) == FR_OK)
{
f_read(&amp;TXT_File,TXT_File.buf,10,&amp;b);
printf("TXT_File:%d %d %s\n",b,TXT_File.buf);
f_close(&amp;TXT_File);
}</code></pre>

<p>JPEG硬件解码,官方代码采用的方式是双通道DMA,一个DMA用于从JPEG文件中读取数据流到内存中,另一个DMA用于从内存中解码完毕的BMP数组搬运到GPU显存中显示,所以需要两个DMA中断服务函数:</p>

<pre>
<code class="language-cpp">void DMA2_Stream3_IRQHandler(void)
{
HAL_DMA_IRQHandler(JPEG_Handle.hdmain);
}

void DMA2_Stream4_IRQHandler(void)
{
HAL_DMA_IRQHandler(JPEG_Handle.hdmaout);
}</code></pre>

<p>然后JPEG硬解码也需要一个中断服务函数:</p>

<pre>
<code class="language-cpp">void JPEG_IRQHandler(void)
{
HAL_JPEG_IRQHandler(&amp;JPEG_Handle);
}
</code></pre>

<p>至此,JPEG文件就可以正常解码并输出到GPU显存中了,STM32F769的JPEG硬件解码库是怎么工作的,我们不需要关心,我们需要操作的是JPEG原始数据和JPEG成功解码数据的输送,特别注意的是我在官方的代码中加了一行,以便支持:</p>

<pre>
<code>int JPEG_OutputHandler(JPEG_HandleTypeDef *hjpeg)
{
int ConvertedDataCount;

if(Jpeg_OUT_BufferTab.State == JPEG_BUFFER_FULL)
{
    MCU_BlockIndex += pConvert_Function(Jpeg_OUT_BufferTab.DataBuffer, (unsigned char *)FrameBufferAddress, MCU_BlockIndex, Jpeg_OUT_BufferTab.DataBufferSize, &amp;ConvertedDataCount);   
   
    Jpeg_OUT_BufferTab.State = JPEG_BUFFER_EMPTY;
    Jpeg_OUT_BufferTab.DataBufferSize = 0;
   
    JPEG_OUT_Read_BufferIndex++;
    if(JPEG_OUT_Read_BufferIndex &gt;= NB_OUTPUT_DATA_BUFFERS)
    {
      JPEG_OUT_Read_BufferIndex = 0;
    }
   
    if(MCU_BlockIndex == MCU_TotalNb)
    {
      MCU_BlockIndex=0;
      //添加的一行
      return 1;
    }
}
else if((Output_Is_Paused == 1) &amp;&amp; \
          (Jpeg_OUT_BufferTab.State == JPEG_BUFFER_EMPTY) &amp;&amp;\
          (Jpeg_OUT_BufferTab.State == JPEG_BUFFER_EMPTY))
{
               
    Output_Is_Paused = 0;
    HAL_JPEG_Resume(hjpeg, JPEG_PAUSE_RESUME_OUTPUT);            
}
return 0;
}

void JPEG_InputHandler(JPEG_HandleTypeDef *hjpeg)
{
if(Jpeg_IN_BufferTab.State == JPEG_BUFFER_EMPTY)
{
    if(f_read (pFile, Jpeg_IN_BufferTab.DataBuffer , CHUNK_SIZE_IN, (UINT*)(&amp;Jpeg_IN_BufferTab.DataBufferSize)) == FR_OK)
    {
      Jpeg_IN_BufferTab.State = JPEG_BUFFER_FULL;
    }
    else
    {
      
    }
   
    if((Input_Is_Paused == 1) &amp;&amp; (JPEG_IN_Write_BufferIndex == JPEG_IN_Read_BufferIndex))
    {
      Input_Is_Paused = 0;
      HAL_JPEG_ConfigInputBuffer(hjpeg,Jpeg_IN_BufferTab.DataBuffer, Jpeg_IN_BufferTab.DataBufferSize);   

      HAL_JPEG_Resume(hjpeg, JPEG_PAUSE_RESUME_INPUT);
    }
   
    JPEG_IN_Write_BufferIndex++;
    if(JPEG_IN_Write_BufferIndex &gt;= NB_INPUT_DATA_BUFFERS)
    {
      JPEG_IN_Write_BufferIndex = 0;
    }            
}
}</code></pre>

<p>我自己封装了一个JPEG解码的函数:</p>

<pre>
<code>int Show_JPEG_File(JPEG_HandleTypeDef *hjpeg,unsigned char *path,JPEG_ConfTypeDef* hjinfo)
{
        unsigned char jpeg_done = 0;
        int xpos = 0, ypos = 0;
        uint32_t width_offset = 0;
        FIL fil;
        hjpeg-&gt;Instance = JPEG;
HAL_JPEG_Init(hjpeg);
        if(f_open(&amp;fil,(const char*)path, FA_READ) == FR_OK)
        {
                JPEG_Decode_DMA(hjpeg, &amp;fil, 0xC0200000);
                do
                {
                        JPEG_InputHandler(hjpeg);
                        jpeg_done = JPEG_OutputHandler(hjpeg);
                }while(jpeg_done == 0);
               
                HAL_JPEG_GetInfo(hjpeg,hjinfo);      
               
                xpos = (800 - hjinfo-&gt;ImageWidth)/2;
                ypos = (480 - hjinfo-&gt;ImageHeight)/2;      

                if(hjinfo-&gt;ChromaSubsampling == JPEG_420_SUBSAMPLING)
                {
                        if((hjinfo-&gt;ImageWidth % 16) != 0)
                        width_offset = 16 - (hjinfo-&gt;ImageWidth % 16);
                }

                if(hjinfo-&gt;ChromaSubsampling == JPEG_422_SUBSAMPLING)
                {
                        if((hjinfo-&gt;ImageWidth % 16) != 0)
                        width_offset = 16 - (hjinfo-&gt;ImageWidth % 16);
                }

                if(hjinfo-&gt;ChromaSubsampling == JPEG_444_SUBSAMPLING)
                {
                        if((hjinfo-&gt;ImageWidth % 8) != 0)
                        width_offset = (hjinfo-&gt;ImageWidth % 8);
                }

                DMA2D_CopyBuffer((uint32_t *)0xC0200000, (uint32_t *)0xC0000000,
                xpos , ypos, hjinfo-&gt;ImageWidth, hjinfo-&gt;ImageHeight, width_offset);
               
                f_close(&amp;fil);
        }
}
</code></pre>

<p>注意还要msp初始化,千万别漏了,这个是weak弱函数,由HAL主控代码调用:</p>

<pre>
<code>void HAL_JPEG_MspInit(JPEG_HandleTypeDef *hjpeg)
{
static DMA_HandleTypeDef   hdmaIn;
static DMA_HandleTypeDef   hdmaOut;

/* Enable JPEG clock */
__HAL_RCC_JPEG_CLK_ENABLE();

    /* Enable DMA clock */
__HAL_RCC_DMA2_CLK_ENABLE();

HAL_NVIC_SetPriority(JPEG_IRQn, 0x06, 0x0F);
HAL_NVIC_EnableIRQ(JPEG_IRQn);

/* Input DMA */   
/* Set the parameters to be configured */
hdmaIn.Init.Channel = DMA_CHANNEL_9;
hdmaIn.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdmaIn.Init.PeriphInc = DMA_PINC_DISABLE;
hdmaIn.Init.MemInc = DMA_MINC_ENABLE;
hdmaIn.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdmaIn.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdmaIn.Init.Mode = DMA_NORMAL;
hdmaIn.Init.Priority = DMA_PRIORITY_HIGH;
hdmaIn.Init.FIFOMode = DMA_FIFOMODE_ENABLE;         
hdmaIn.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdmaIn.Init.MemBurst = DMA_MBURST_INC4;
hdmaIn.Init.PeriphBurst = DMA_PBURST_INC4;      

hdmaIn.Instance = DMA2_Stream3;

/* Associate the DMA handle */
__HAL_LINKDMA(hjpeg, hdmain, hdmaIn);

HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0x07, 0x0F);
HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);   

/* DeInitialize the DMA Stream */
HAL_DMA_DeInit(&amp;hdmaIn);
/* Initialize the DMA stream */
HAL_DMA_Init(&amp;hdmaIn);


/* Output DMA */
/* Set the parameters to be configured */
hdmaOut.Init.Channel = DMA_CHANNEL_9;
hdmaOut.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdmaOut.Init.PeriphInc = DMA_PINC_DISABLE;
hdmaOut.Init.MemInc = DMA_MINC_ENABLE;
hdmaOut.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdmaOut.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdmaOut.Init.Mode = DMA_NORMAL;
hdmaOut.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdmaOut.Init.FIFOMode = DMA_FIFOMODE_ENABLE;         
hdmaOut.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdmaOut.Init.MemBurst = DMA_MBURST_INC4;
hdmaOut.Init.PeriphBurst = DMA_PBURST_INC4;


hdmaOut.Instance = DMA2_Stream4;
/* DeInitialize the DMA Stream */
HAL_DMA_DeInit(&amp;hdmaOut);
/* Initialize the DMA stream */
HAL_DMA_Init(&amp;hdmaOut);

/* Associate the DMA handle */
__HAL_LINKDMA(hjpeg, hdmaout, hdmaOut);

HAL_NVIC_SetPriority(DMA2_Stream4_IRQn, 0x07, 0x0F);
HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);   
}</code></pre>

<p>本来前两天还能正常显示图片的,现在又花屏了,都不知道什么原因,心累了,草(一种植物),放出代码工程让大佬们批评一下:</p>

<p></p>

huo_hu 发表于 2020-7-25 19:28

好的,正打算尝试这部分
页: [1]
查看完整版本: 【STM32F769Discovery开发板试用】SD卡文件系统应用&让人自闭的硬解JPEG解码代码