donatello1996 发表于 2020-8-2 00:45

【STM32F769Discovery开发板试用】便捷的SD卡读取BMP文件&QSPI读写&SDRAM读写

<p>&nbsp; &nbsp; &nbsp; &nbsp; STM32F769Discovery板子外接了多种存储器,有SD卡,QSPI FLASH,SDRAM和EEPROM,属于掉电保存的大容量FLASH有SD卡和QSPI FLASH两种,EEPROM也可掉电保存但容量小,SDRAM不可掉电保存,本帖主要讲SD卡读取BMP文件和使用QSPI FLASH存储读写不带文件头的BMP数据,SDRAM和EEPROM会简单介绍,一笔带过。<br />
&nbsp; &nbsp; &nbsp; &nbsp;先来说说SD卡读取BMP文件,这个基本上没有难度,F769的库就写好了直接从BMP文件获取文件头信息和数据并显示的函数,只要像之前第三帖那样搭建好SD卡的文件系统,成功挂载SD卡就能正常读取BMP文件了,不需要像JPEG文件那样要解码,甚至不需要考虑BMP文件的长宽尺寸,文件占用空间,文件头各项数据(前提是BMP文件的文件头格式遵循Windows标准BMP协议),方便快捷,只不过还是老问题,只能读取24位色或16位色的BMP文件,无法读取32位色的BMP文件。这种方式适合的实际项目是不考虑SD卡的成本和外壳占用位置的项目,更新BMP文件数据只需要将SD卡取出来,在电脑上操作即可,SD卡能放多少张BMP文件就能存取多少,不会被嵌入式主控的RAM和ROM空间所限制。在使用这个方式之前,先普及一下BMP文件头数据格式:</p>

<p></p>

<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs">typedef struct tagBITMAPFILEHEADER
{
UINT16 bfType;   
DWORD bfSize;
UINT16 bfReserved1;
UINT16 bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER; </code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url(&quot;https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png&quot;) rgba(220, 220, 220, 0.5); left: 0px; top: -15px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>

<p>BMP文件头格式的信息非常精炼,包含的信息有文件占用空间,图片的长和宽,数据部分偏移量,调色板,位色(16/24/32),之后就是数据部分了,简单的50多个字节,包含了文件读取的所有必要信息。CubeF7库里面已经写好了读取BMP文件并显示的函数:</p>

<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs">uint32_t Storage_OpenReadFile(uint8_t *Address, const char* BmpName ,int *rsize)
{
uint32_t index = 0, i1 = 0;
uint32_t BmpAddress;
FIL F1;
        int size;

if (f_open(&amp;F1, (TCHAR const*)BmpName, FA_READ) != FR_OK)
{
                return 1;
}
if (f_read (&amp;F1, sector, 30, (UINT *)&amp;BytesRead) != FR_OK)
{
                return 2;
}

BmpAddress = (uint32_t)sector;

/* Read bitmap size */
size = *(uint16_t *) (BmpAddress + 2);
size |= (*(uint16_t *) (BmpAddress + 4)) &lt;&lt; 16;
        *rsize = size;

/* Get bitmap data address offset */
index = *(uint16_t *) (BmpAddress + 10);
index |= (*(uint16_t *) (BmpAddress + 12)) &lt;&lt; 16;

f_close (&amp;F1);

f_open (&amp;F1, (TCHAR const*)BmpName, FA_READ);

do
{
    if (size &lt; 256*2)
    {
      i1 = size;
    }
    else
    {
      i1 = 256*2;
    }
    size -= i1;
    f_read (&amp;F1, sector, i1, (UINT *)&amp;BytesRead);

    for (index = 0; index &lt; i1; index++)
    {
      *(volatile uint8_t*) (Address) = *(volatile uint8_t *)BmpAddress;
      
      BmpAddress++;
      Address++;
    }
   
    BmpAddress = (uint32_t)sector;
}
while (size &gt; 0);

f_close (&amp;F1);

return 0;
}
</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url(&quot;https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png&quot;) rgba(220, 220, 220, 0.5); left: 0px; top: -15px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>

<p>读取数据之后,当然就是显示了:</p>

<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs">void BSP_LCD_DrawBitmap(uint32_t Xpos, uint32_t Ypos, uint8_t *pbmp)
{
uint32_t index = 0, width = 0, height = 0, bit_pixel = 0;
uint32_t Address;
uint32_t InputColorMode = 0;

/* Get bitmap data address offset */
index = pbmp + (pbmp &lt;&lt; 8) + (pbmp &lt;&lt; 16)+ (pbmp &lt;&lt; 24);

/* Read bitmap width */
width = pbmp + (pbmp &lt;&lt; 8) + (pbmp &lt;&lt; 16)+ (pbmp &lt;&lt; 24);

/* Read bitmap height */
height = pbmp + (pbmp &lt;&lt; 8) + (pbmp &lt;&lt; 16)+ (pbmp &lt;&lt; 24);

/* Read bit/pixel */
bit_pixel = pbmp + (pbmp &lt;&lt; 8);

/* Set the address */
Address = hltdc_discovery.LayerCfg.FBStartAdress + (((BSP_LCD_GetXSize()*Ypos) + Xpos)*(4));

/* Get the layer pixel format */
if ((bit_pixel/8) == 4)
{
    InputColorMode = DMA2D_INPUT_ARGB8888;
}
else if ((bit_pixel/8) == 2)
{
    InputColorMode = DMA2D_INPUT_RGB565;
}
else
{
    InputColorMode = DMA2D_INPUT_RGB888;
}

/* Bypass the bitmap header */
pbmp += (index + (width * (height - 1) * (bit_pixel/8)));

/* Convert picture to ARGB8888 pixel format */
for(index=0; index &lt; height; index++)
{
    /* Pixel format conversion */
    LL_ConvertLineToARGB8888((uint32_t *)pbmp, (uint32_t *)Address, width, InputColorMode);

    /* Increment the source and destination buffers */
    Address+=(BSP_LCD_GetXSize()*4);
    pbmp -= width*(bit_pixel/8);
}
}</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url(&quot;https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png&quot;) rgba(220, 220, 220, 0.5); left: 0px; top: -15px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>

<p>真的是一条龙服务,简单的两个函数就完成了,官方的源代码有不合理的位置我已经修改过来了,比如f_open() f_read()这些操作,即使返回失败也不应用while(1)死循环来处理,然后就是修改了返回值,返回0成功,返回大于0的值为不同原因的失败,尽管这两个函数还有很大的改进空间,算法还是有许多不完善的地方,但简单读取SD卡BMP文件够用了。读取BMP文件的主循环代码:</p>

<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs">while(1)
{
        Storage_OpenReadFile(uwInternalBuffer,&quot;/Media/2.bmp&quot;,&amp;rsize);
        BSP_LCD_DrawBitmap(0,0,uwInternalBuffer);
        Storage_OpenReadFile(uwInternalBuffer,&quot;/Media/3.bmp&quot;,&amp;rsize);
        BSP_LCD_DrawBitmap(0,0,uwInternalBuffer);
        Storage_OpenReadFile(uwInternalBuffer,&quot;/Media/4.bmp&quot;,&amp;rsize);
        BSP_LCD_DrawBitmap(0,0,uwInternalBuffer);
        Storage_OpenReadFile(uwInternalBuffer,&quot;/Media/5.bmp&quot;,&amp;rsize);
        BSP_LCD_DrawBitmap(0,0,uwInternalBuffer);
}</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url(&quot;https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png&quot;) rgba(220, 220, 220, 0.5); left: 0px; top: -15px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>

<p>不过这里有一个地方是需要注意的,那就是uwInternalBuffer的地址,官方的代码是直接使用SDRAM中LTDC的两层图层的地址,任选一层,分别为layer0和layer1的两层数据空间,对这部分内存空间的写入,会直接作用到液晶屏显示上面:</p>

<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs">uint8_t* uwInternalBuffer = (uint8_t *)(0xC0000000+800*480*4);</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url(&quot;https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png&quot;) rgba(220, 220, 220, 0.5); left: 0px; top: -15px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>

<p>或<br />
&nbsp;</p>

<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs">uint8_t* uwInternalBuffer = (uint8_t *)(0xC0000000+800*480*8);</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url(&quot;https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png&quot;) rgba(220, 220, 220, 0.5); left: 0px; top: -15px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>

<p>这是有原因的,由于STM32F769的片内RAM空间中,可供用户调用的部分不足800*480*4=1536000(约等于1500KB)即缓存一帧BMP图所需要的空间(甚至800*480*3=1152000,约等于1100KB都不够),即无法直接定义一个大小为1152000的非常量数组,会直接报错:</p>

<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs">unsigned char temp;</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url(&quot;https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png&quot;) rgba(220, 220, 220, 0.5); left: 0px; top: -15px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>

<p>因此官方代码是直接使用(0xC0000000+800*480*4)~(0xC0000000+800*480*8)之间的3MB左右的空间,定义一个指针指向这块空间,将从SD卡中读取出来的缓存数据存放到此处。这里可能有坛友要问了,那么SDRAM其余几十MB的空间哪里去了?为什么不用?别问,问就是ST官方代码有漏洞,后面我尝试过,也不成功,之后再探讨。</p>

<p>读取BMP文件刷屏效果如下,非常简单便捷,再次声明只能读取16位或者24位色的BMP文件:</p>

<p></p>

<p>&nbsp;</p>

<p>SD卡读取BMP文件显示的测评完成了,之后就是读写QSPI FLASH了,同样也不难,官方都把大部分的代码都封装好了,只需要用到官方的三个函数,BSP_QSPI_Erase_Block(),BSP_QSPI_Write(),BSP_QSPI_Read(),QSPI FLASH的块/扇区在写入之前需要擦除:</p>

<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs">uint8_t BSP_QSPI_Erase_Block(uint32_t BlockAddress)
{
QSPI_CommandTypeDef s_command;

/* Initialize the erase command */
s_command.InstructionMode   = QSPI_INSTRUCTION_4_LINES;
s_command.Instruction       = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD;
s_command.AddressMode       = QSPI_ADDRESS_4_LINES;
s_command.AddressSize       = QSPI_ADDRESS_32_BITS;
s_command.Address         = BlockAddress;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode          = QSPI_DATA_NONE;
s_command.DummyCycles       = 0;
s_command.DdrMode         = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle= QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

/* Enable write operations */
if (QSPI_WriteEnable(&amp;QSPIHandle) != QSPI_OK)
{
    return QSPI_ERROR;
}

/* Send the command */
if (HAL_QSPI_Command(&amp;QSPIHandle, &amp;s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
    return QSPI_ERROR;
}

/* Configure automatic polling mode to wait for end of erase */
if (QSPI_AutoPollingMemReady(&amp;QSPIHandle, MX25L512_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK)
{
    return QSPI_ERROR;
}

return QSPI_OK;
}</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url(&quot;https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png&quot;) rgba(220, 220, 220, 0.5); left: 0px; top: -15px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>

<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs">uint8_t BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
{
QSPI_CommandTypeDef s_command;
uint32_t end_addr, current_size, current_addr;

/* Calculation of the size between the write address and the end of the page */
current_size = MX25L512_PAGE_SIZE - (WriteAddr % MX25L512_PAGE_SIZE);

/* Check if the size of the data is less than the remaining place in the page */
if (current_size &gt; Size)
{
    current_size = Size;
}

/* Initialize the address variables */
current_addr = WriteAddr;
end_addr = WriteAddr + Size;

/* Initialize the program command */
s_command.InstructionMode   = QSPI_INSTRUCTION_4_LINES;
s_command.Instruction       = QPI_PAGE_PROG_4_BYTE_ADDR_CMD;
s_command.AddressMode       = QSPI_ADDRESS_4_LINES;
s_command.AddressSize       = QSPI_ADDRESS_32_BITS;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode          = QSPI_DATA_4_LINES;
s_command.DummyCycles       = 0;
s_command.DdrMode         = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle= QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

/* Perform the write page by page */
do
{
    s_command.Address = current_addr;
    s_command.NbData= current_size;

    /* Enable write operations */
    if (QSPI_WriteEnable(&amp;QSPIHandle) != QSPI_OK)
    {
      return QSPI_ERROR;
    }
   
    /* Configure the command */
    if (HAL_QSPI_Command(&amp;QSPIHandle, &amp;s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
      return QSPI_ERROR;
    }
   
    /* Transmission of the data */
    if (HAL_QSPI_Transmit(&amp;QSPIHandle, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
      return QSPI_ERROR;
    }
   
    /* Configure automatic polling mode to wait for end of program */
    if (QSPI_AutoPollingMemReady(&amp;QSPIHandle, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK)
    {
      return QSPI_ERROR;
    }
   
    /* Update the address and size variables for next page programming */
    current_addr += current_size;
    pData += current_size;
    current_size = ((current_addr + MX25L512_PAGE_SIZE) &gt; end_addr) ? (end_addr - current_addr) : MX25L512_PAGE_SIZE;
} while (current_addr &lt; end_addr);

return QSPI_OK;
}</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url(&quot;https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png&quot;) rgba(220, 220, 220, 0.5); left: 0px; top: -15px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>

<div aria-label="代码段 小部件" contenteditable="false" role="region" tabindex="-1">
<pre data-widget="codesnippet">
<code class="hljs">uint8_t BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)
{
QSPI_CommandTypeDef s_command;

/* Initialize the read command */
s_command.InstructionMode   = QSPI_INSTRUCTION_4_LINES;
s_command.Instruction       = QPI_READ_4_BYTE_ADDR_CMD;
s_command.AddressMode       = QSPI_ADDRESS_4_LINES;
s_command.AddressSize       = QSPI_ADDRESS_32_BITS;
s_command.Address         = ReadAddr;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode          = QSPI_DATA_4_LINES;
s_command.DummyCycles       = MX25L512_DUMMY_CYCLES_READ_QUAD_IO;
s_command.NbData            = Size;
s_command.DdrMode         = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle= QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

/* Configure the command */
if (HAL_QSPI_Command(&amp;QSPIHandle, &amp;s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
    return QSPI_ERROR;
}

/* Set S# timing for Read command */
MODIFY_REG(QSPIHandle.Instance-&gt;DCR, QUADSPI_DCR_CSHT, QSPI_CS_HIGH_TIME_1_CYCLE);

/* Reception of the data */
if (HAL_QSPI_Receive(&amp;QSPIHandle, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
    return QSPI_ERROR;
}

/* Restore S# timing for nonRead commands */
MODIFY_REG(QSPIHandle.Instance-&gt;DCR, QUADSPI_DCR_CSHT, QSPI_CS_HIGH_TIME_4_CYCLE);

return QSPI_OK;
}</code></pre>
<img src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" /><span style="background: url(&quot;https://bbs.eeworld.com.cn/static/editor/plugins/widget/images/handle.png&quot;) rgba(220, 220, 220, 0.5); left: 0px; top: -15px; display: block;"><img height="15" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" /></span></div>

<p>擦除的单位是块或者扇区,一个块长度为512字节,如果要擦除多个连续块,则起始地址每次加512,比如要擦除0~1023这个空间的所对应的两个块:</p>

<pre>
<code>BSP_QSPI_Erase_Block(0);
BSP_QSPI_Erase_Block(512);</code></pre>

<p>依据此原理,要存储BMP图片的数据到QSPI FLASH里面就非常简单了,我自己写了一个便捷的函数,可以根据写入BMP图片数据的长/宽/位色参数进行调整:</p>

<pre>
<code>void BSP_QSPI_Store_BMP_Buffer(int w,int h,int bit_pixel,unsigned char buff[],int start_addr)
{
        int i;
        for(i=0;i&lt;w*h*bit_pixel/8/512;i++)
        {
                BSP_QSPI_Erase_Block(i*512+start_addr);
        }
        BSP_QSPI_Write((uint8_t*)buff,start_addr,w*h*bit_pixel/8);
}</code></pre>

<p>写入一张24位800*480图片的BMP数据(大小为1152000字节)到起始地址1200000的操作如下:</p>

<pre>
<code>const unsigned char image1={...};

BSP_QSPI_Store_BMP_Buffer(800,480,24,image1,1200000);</code></pre>

<p>这里我对三张图片的BMP数据进行写入,分别存放于起始地址0,起始地址1200000,起始地址2400000,写入一张800*480*3的图片BMP数组到QSPI FLASH空间的大致用时是一分多钟,同一个地址写入一次之后,后面就可以一直读了,能读多少次看QSPI FLASH的寿命:</p>

<p></p>

<p></p>

<p></p>

<p>写入的评测非常简单顺利,但是要从QSPI FLASH中读取的话就有点波折了,还是老问题,STM32F76不支持整张800*480*3图片这么大的缓存空间,如果想要缓存这么大的图片,要么把图片缓存到上述的外扩SDRAM空间地址(uint8_t *)(0xC0000000+800*480*4)中,要么切片缓存显示,一般而言为了不在缓存过程中影响液晶屏显示,都是采用切片缓存显示的方法,也就是将液晶屏的480行分成多个行数相同的组,每组的形状都是矩形,依次对每个组采用DMA2D方式进行局部矩形刷图,那既然要切片缓存显示,那就要开辟一个全局数组lcd_buf[]用于每一组数据的缓存,在全局数组lcd_buf[]缓存空间,每一组显示的数据量,QSPI读取次数之间找到一个平衡点,每一组的行数越大,QSPI读取次数就越少,全局lcd_buf[]缓存空间=每一组行数*800*3,比如说我这边定义每一组是16行,即800*3*16=38400,即开辟一个长度为38400的全局缓存数组unsigned char lcd_buf,这个缓存数组用于每次从QSPI FLASH读取长度为38400的数据并使用DMA2D搬运到SDRAM地址空间中,这个搬运函数是从下至上搬运的,如果每组是16行,那么一共就要搬运30次,这里我可以画一个简单的示意图:</p>

<p></p>

<p>读取函数如下:</p>

<pre>
<code>void BSP_QSPI_Load_Show_BMP_Buffer(int start_addr)
{
        int i,j;
        for(j=0;j&lt;30;j++)
        {
                BSP_QSPI_Read(lcd_buf,j*38400+start_addr,38400);
                BSP_LCD_DrawBuffer(0,464-j*16,800,16,24,lcd_buf);
        }
}</code></pre>

<pre>
<code>while(1)
{
        BSP_QSPI_Load_Show_BMP_Buffer(0);
        BSP_QSPI_Load_Show_BMP_Buffer(1200000);
        BSP_QSPI_Load_Show_BMP_Buffer(2400000);
}</code></pre>

<p>看看效果:</p>

<p></p>

<p>可以看出来从QSPI FLASH读取BMP数据和从SD卡读取的速度差不多,由于QSPI FLASH是切片读取缓存,所以会有从下至上的刷屏效果。</p>

<p>然后是SDRAM读写函数,不知道是什么原因,一次能连续操作的SDRAM空间极其有限,如果超过了空间限制的话,读写就会有问题,官方的代码是读写32位长度单位的空间,实际上可以以8位长度为单位进行,只需要改成HAL_SDRAM_Write_8b():</p>

<pre>
<code>uint8_t BSP_SDRAM_WriteBytes(uint32_t uwStartAddress, uint8_t *pData, uint32_t uwDataSize)
{
if(HAL_SDRAM_Write_8b(&amp;sdramHandle, (uint32_t *)uwStartAddress, pData, uwDataSize) != HAL_OK)
{
    return SDRAM_ERROR;
}
else
{
    return SDRAM_OK;
}
}</code></pre>

<pre>
<code>uint8_t BSP_SDRAM_ReadBytes(uint32_t uwStartAddress, uint8_t *pData, uint32_t uwDataSize)
{
if(HAL_SDRAM_Read_8b(&amp;sdramHandle, (uint32_t *)uwStartAddress, pData, uwDataSize) != HAL_OK)
{
    return SDRAM_ERROR;
}
else
{
    return SDRAM_OK;
}
}</code></pre>

<p>&nbsp;</p>

donatello1996 发表于 2020-8-2 00:48

<p>二楼放出工程文件</p>

<p></p>

littleshrimp 发表于 2020-9-8 09:25

<p>这个板子的屏幕不错,H7好像也是这块屏,看你的代码简单改一下,弄成TF卡加载JPG文件就可以实现一个电子相册了。</p>

donatello1996 发表于 2020-9-8 09:53

littleshrimp 发表于 2020-9-8 09:25
这个板子的屏幕不错,H7好像也是这块屏,看你的代码简单改一下,弄成TF卡加载JPG文件就可以实现一个电子相 ...

<p>读取JPG文件之前有帖子写了,并不顺利</p>

freebsder 发表于 2020-9-8 16:03

<p>图片不错。搞了这么多事情,楼主算是把F769摸透了</p>

donatello1996 发表于 2020-9-8 16:58

freebsder 发表于 2020-9-8 16:03
图片不错。搞了这么多事情,楼主算是把F769摸透了

<p>一个月之前的帖子...</p>

freebsder 发表于 2020-9-9 13:34

<div class='shownolgin' data-isdigest='no'>donatello1996 发表于 2020-9-8 16:58
一个月之前的帖子...

<p>时时要鞭策自己向先进学习,别说一个月,就是一年以前的都要不断温习!</p>
</div><script>showreplylogin();</script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>
页: [1]
查看完整版本: 【STM32F769Discovery开发板试用】便捷的SD卡读取BMP文件&QSPI读写&SDRAM读写