creep 发表于 2017-2-19 17:17:36

【F769IDISCOVERY评测】”弹幕“来了

本帖最后由 creep 于 2017-2-19 21:36 编辑

       虽然STM32F769-Discovery的论坛评测活动早就结束了,但是截止到目前STM32F769-Discovery 依然是ST的开发板中可玩性和综合性能比较高的,其中的独有或者新增的外设都值得好好学习一下,下面就简单看下板载的LCD相关的内容。

    1、DSI和LTDC

       STM32F769-Discovery上面的LCD是一个分辨率为800*480的电容触摸屏,驱动接口为DSI-MIPI模式,这种模式是手机行业比较常用的驱动模式,最近几年才出现在ST的高端MCU上面,其特点是接口简单占用IO口少但是速度非常快,但是配置可能比较复杂一些。在此之前ST的比较方便使用的LCD接口是以STM32F429上的LTDC接口为代表,现在的出现的DSI-MIPI是在LTDC的基础上增添了MIPI相关的协议,所以如果比较熟悉LTDC相关的使用对学习STM32D769上的DIS-MIPI有很大的帮助。

       DSI-MIPI接口驱动LCD主要涉及到了外设有DSIHOST、LTDC、FMC(SDRAM)、DMA2D等。前2个外设DSIHOST、LTDC主要是数据传输控制接口;FMC(SDRAM)主要用于LCD的缓存使用;DMA2D是一个LCD专用的DMA,可实现方便快速的图像图层显示控制,在不占用CPU的情况话显示图片、文字等功能。虽然看起来可能设置比较复杂但是好在ST的开发包里面提供很多详细的配置例子,除此之外使用CubeMX也能通过图形的界面进行LCD驱动的配置,这都给我们入门学习这些新的外设提供了很多便捷。


    通过下面的DSIHOST和LTDC的框图可以总体上对这个接口有个大致的了解:

                                                   

LCD的初始化就说对DSI、LTDC、SDRAM、DMA2D进行初始化的配置,大致如下:
1)DSI
/*************************DSI Initialization***********************************/

/* Base address of DSI Host/Wrapper registers to be set before calling De-Init */
hdsi_discovery.Instance = DSI;

HAL_DSI_DeInit(&(hdsi_discovery));

dsiPllInit.PLLNDIV= 100;
dsiPllInit.PLLIDF   = DSI_PLL_IN_DIV5;
dsiPllInit.PLLODF= DSI_PLL_OUT_DIV1;
laneByteClk_kHz = 62500; /* 500 MHz / 8 = 62.5 MHz = 62500 kHz */

/* Set number of Lanes */
hdsi_discovery.Init.NumberOfLanes = DSI_TWO_DATA_LANES;

/* TXEscapeCkdiv = f(LaneByteClk)/15.62 = 4 */
hdsi_discovery.Init.TXEscapeCkdiv = laneByteClk_kHz/15620;

HAL_DSI_Init(&(hdsi_discovery), &(dsiPllInit));

/* Timing parameters for all Video modes
* Set Timing parameters of LTDC depending on its chosen orientation
*/
if(orientation == LCD_ORIENTATION_PORTRAIT)
{
    lcd_x_size = OTM8009A_480X800_WIDTH;/* 480 */
    lcd_y_size = OTM8009A_480X800_HEIGHT; /* 800 */                              
}
else
{
    /* lcd_orientation == LCD_ORIENTATION_LANDSCAPE */
    lcd_x_size = OTM8009A_800X480_WIDTH;/* 800 */
    lcd_y_size = OTM8009A_800X480_HEIGHT; /* 480 */                              
}

HACT = lcd_x_size;
VACT = lcd_y_size;

/* The following values are same for portrait and landscape orientations */
VSA= OTM8009A_480X800_VSYNC;      /* 12*/
VBP= OTM8009A_480X800_VBP;          /* 12*/
VFP= OTM8009A_480X800_VFP;          /* 12*/
HSA= OTM8009A_480X800_HSYNC;      /* 63*/
HBP= OTM8009A_480X800_HBP;          /* 120 */
HFP= OTM8009A_480X800_HFP;          /* 120 */   

hdsivideo_handle.VirtualChannelID = LCD_OTM8009A_ID;
hdsivideo_handle.ColorCoding = LCD_DSI_PIXEL_DATA_FMT_RBG888;
hdsivideo_handle.VSPolarity = DSI_VSYNC_ACTIVE_HIGH;
hdsivideo_handle.HSPolarity = DSI_HSYNC_ACTIVE_HIGH;
hdsivideo_handle.DEPolarity = DSI_DATA_ENABLE_ACTIVE_HIGH;
hdsivideo_handle.Mode = DSI_VID_MODE_BURST; /* Mode Video burst ie : one LgP per line */
hdsivideo_handle.NullPacketSize = 0xFFF;
hdsivideo_handle.NumberOfChunks = 0;
hdsivideo_handle.PacketSize                = HACT; /* Value depending on display orientation choice portrait/landscape */
hdsivideo_handle.HorizontalSyncActive      = (HSA * laneByteClk_kHz)/LcdClock;
hdsivideo_handle.HorizontalBackPorch       = (HBP * laneByteClk_kHz)/LcdClock;
hdsivideo_handle.HorizontalLine            = ((HACT + HSA + HBP + HFP) * laneByteClk_kHz)/LcdClock; /* Value depending on display orientation choice portrait/landscape */
hdsivideo_handle.VerticalSyncActive      = VSA;
hdsivideo_handle.VerticalBackPorch         = VBP;
hdsivideo_handle.VerticalFrontPorch      = VFP;
hdsivideo_handle.VerticalActive            = VACT; /* Value depending on display orientation choice portrait/landscape */

/* Enable or disable sending LP command while streaming is active in video mode */
hdsivideo_handle.LPCommandEnable = DSI_LP_COMMAND_ENABLE; /* Enable sending commands in mode LP (Low Power) */

/* Largest packet size possible to transmit in LP mode in VSA, VBP, VFP regions */
/* Only useful when sending LP packets is allowed while streaming is active in video mode */
hdsivideo_handle.LPLargestPacketSize = 16;

/* Largest packet size possible to transmit in LP mode in HFP region during VACT period */
/* Only useful when sending LP packets is allowed while streaming is active in video mode */
hdsivideo_handle.LPVACTLargestPacketSize = 0;

/* Specify for each region of the video frame, if the transmission of command in LP mode is allowed in this region */
/* while streaming is active in video mode                                                                         */
hdsivideo_handle.LPHorizontalFrontPorchEnable = DSI_LP_HFP_ENABLE;   /* Allow sending LP commands during HFP period */
hdsivideo_handle.LPHorizontalBackPorchEnable= DSI_LP_HBP_ENABLE;   /* Allow sending LP commands during HBP period */
hdsivideo_handle.LPVerticalActiveEnable = DSI_LP_VACT_ENABLE;/* Allow sending LP commands during VACT period */
hdsivideo_handle.LPVerticalFrontPorchEnable = DSI_LP_VFP_ENABLE;   /* Allow sending LP commands during VFP period */
hdsivideo_handle.LPVerticalBackPorchEnable = DSI_LP_VBP_ENABLE;   /* Allow sending LP commands during VBP period */
hdsivideo_handle.LPVerticalSyncActiveEnable = DSI_LP_VSYNC_ENABLE; /* Allow sending LP commands during VSync = VSA period */

/* Configure DSI Video mode timings with settings set above */
HAL_DSI_ConfigVideoMode(&(hdsi_discovery), &(hdsivideo_handle));

/*************************End DSI Initialization*******************************/ 2)LTDC、SDRAM、驱动IC(OTM8009A)的初始化
/************************LTDC Initialization***********************************/

/* Timing Configuration */   
hltdc_discovery.Init.HorizontalSync = (HSA - 1);
hltdc_discovery.Init.AccumulatedHBP = (HSA + HBP - 1);
hltdc_discovery.Init.AccumulatedActiveW = (lcd_x_size + HSA + HBP - 1);
hltdc_discovery.Init.TotalWidth = (lcd_x_size + HSA + HBP + HFP - 1);

/* Initialize the LCD pixel width and pixel height */
hltdc_discovery.LayerCfg->ImageWidth= lcd_x_size;
hltdc_discovery.LayerCfg->ImageHeight = lcd_y_size;   

/** LCD clock configuration
    * Note: The following values should not be changed as the PLLSAI is also used
    *      to clock the USB FS
    * PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz
    * PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 384 Mhz
    * PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 384 MHz / 7 = 54.85 MHz
    * LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_2 = 54.85 MHz / 2 = 27.429 MHz
    */
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
PeriphClkInitStruct.PLLSAI.PLLSAIN = 384;
PeriphClkInitStruct.PLLSAI.PLLSAIR = 7;
PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_2;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);

/* Background value */
hltdc_discovery.Init.Backcolor.Blue = 0;
hltdc_discovery.Init.Backcolor.Green = 0;
hltdc_discovery.Init.Backcolor.Red = 0;
hltdc_discovery.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
hltdc_discovery.Instance = LTDC;

/* Get LTDC Configuration from DSI Configuration */
HAL_LTDC_StructInitFromVideoConfig(&(hltdc_discovery), &(hdsivideo_handle));

/* Initialize the LTDC */
HAL_LTDC_Init(&hltdc_discovery);

/* Enable the DSI host and wrapper after the LTDC initialization
   To avoid any synchronization issue, the DSI shall be started after enabling the LTDC */
HAL_DSI_Start(&hdsi_discovery);

#if !defined(DATA_IN_ExtSDRAM)
/* Initialize the SDRAM */
BSP_SDRAM_Init();
#endif /* DATA_IN_ExtSDRAM */

/* Initialize the font */
BSP_LCD_SetFont(&LCD_DEFAULT_FONT);

/************************End LTDC Initialization*******************************/


/***********************OTM8009A Initialization********************************/

/* Initialize the OTM8009A LCD Display IC Driver (KoD LCD IC Driver)
*depending on configuration set in 'hdsivideo_handle'.
*/
OTM8009A_Init(OTM8009A_FORMAT_RGB888, orientation);

/***********************End OTM8009A Initialization****************************/ 一旦LCD初始化之后,对LCD的操作就说对其缓存SDRAM的操作,比如下面的下一个点到LCD上的函数,这样的操作非常的简单方便:
/**
* @briefDraws a pixel on LCD.
* @paramXpos: X position
* @paramYpos: Y position
* @paramRGB_Code: Pixel color in ARGB mode (8-8-8-8)
*/
void BSP_LCD_DrawPixel(uint16_t Xpos, uint16_t Ypos, uint32_t RGB_Code)
{
/* Write data value to all SDRAM memory */
*(__IO uint32_t*) (hltdc_discovery.LayerCfg.FBStartAdress + (4*(Ypos*BSP_LCD_GetXSize() + Xpos))) = RGB_Code;
}我们的测试是在LCD上滚动显示一些字符,这些字符可以通过串口发送进行修改,类似“弹幕”一样,下面是初始化后显示的滚动字符


我们通过串口发送2个“弹幕”试下:分别为“2017-02-19 16:17:33”   “Hi,this message is from sscom...”   
串口的接收函数使用的是前面帖子介绍的 超时中断接收不定长字符串。



2、QUADSPI 和中文字库

    板载上有个NorFlash使用QUADSPI 接口可用于汉字库和图片的存储,我们将汉字库存在Norflash总然后利用QUADSPI 接口的memory-map 功能可以直接读取汉字进行显示,在程序中初始化后NorFlash之后通过内部的FLASH将汉字库写入到NorFlash之中然后在进行验证字库是否正确。
BSP_QSPI_Init();
      CopyFont2NorFlash();
      BSP_QSPI_EnableMemoryMappedMode();
      CheckGBKFont4NorFlash();其中memory-map 如下:
uint8_t BSP_QSPI_EnableMemoryMappedMode(void)
{
    QSPI_CommandTypeDef      s_command;
    QSPI_MemoryMappedTypeDef s_mem_mapped_cfg;
    /* Configure the command for the read instruction */
    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.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    s_command.DataMode          = QSPI_DATA_4_LINES;
    s_command.DummyCycles       = MX25L512_DUMMY_CYCLES_READ_QUAD_IO;
    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 memory mapped mode */
    s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
    s_mem_mapped_cfg.TimeOutPeriod   = 0;

    if(HAL_QSPI_MemoryMapped(&QSPIHandle, &s_command, &s_mem_mapped_cfg) != HAL_OK)
    {
      return QSPI_ERROR;
    }

    return QSPI_OK;
}烧写和验证:



我们发送2个中文字符串试下:




3、手机发送“弹幕”

    如果只能用电脑发送不能用手机发送弹幕,那绝对不是一个好弹幕,之前的帖子里面我们介绍了板载有个wifi模块的接口,通过这个接口我们
可以发送数据到LCD显示,我是通过建立一个UDP连接进行数据通信的。关于esp8266的AT命令和使用方法可以参考前面的帖子 。同样wifi接口的串口也是使用超时中断完成的。具体代码如下;
void WIFI_ReceiverTimeOut_Callback(UART_HandleTypeDef *huart)
{
    uint16_t len;
    uint32_t tmp1 = 0;
    tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_RTOF);
    if((tmp1 != RESET))
    {
      __HAL_UART_CLEAR_IT(huart, UART_CLEAR_RTOF);
      /* set uart stateready*/
      huart->RxState = HAL_UART_STATE_READY;
      /* Disable the rxDMA peripheral */
      __HAL_DMA_DISABLE(huart->hdmarx);
      /*Clear the DMA Stream pending flags.*/
      __HAL_DMA_CLEAR_FLAG(huart->hdmarx, __HAL_DMA_GET_TC_FLAG_INDEX(huart->hdmarx));
      /* get rx data len */
      len = huart->hdmarx->Instance->NDTR;
      WIFI_Rxlen = RXBUFFLENGTH - len;
                        if(WIFI_Rxlen && WIFI_Init_Sta)
                              {
                                        if(strlen((char*)WIFI_RxBuff))
                                        {
                                                if(WIFI_Rxlen > RXASCIIMAX) WIFI_Rxlen = RXBUFFLENGTH;
                                                if(Display_pos_dn == 0)Display_pos_dn = 14;      
                                                Display_pos_dn--;
                                                memset(Display_Buff,0,101);
                                                memcpy(Display_Buff,WIFI_RxBuff,WIFI_Rxlen);      
                                        }                                                
                              }
      /* Process Unlocked */
      __HAL_UNLOCK(huart->hdmarx);
      
       huart->hdmarx->State = HAL_DMA_STATE_READY;

      HAL_UART_Receive_DMA(huart, WIFI_RxBuff, RXBUFFLENGTH);
                        WIFI_CMD_Response_Sta = ENABLE;
    }
}
手机发送的“弹幕“如下;



LCD 显示如下:



当然截止到目前这还不算是个好的弹幕,这只能一个人自娱自乐,好的弹幕应该是很多人都能发。其实这样也不是很难,如果有个公网IP然后在路由器中做个端口映射那就在外面直接连上wiif模块发送弹幕了。上面有些GIF图片可能较大会加载较慢。
      4、总结

    最后我们看下main 函数:

int main(void)
{
        uint16_t x0,t;
CPU_CACHE_Enable();
HAL_Init();
SystemClock_Config();
        USART1_Init();
        My_ESP8266_Init();
        HAL_Delay(200);
        ESP8266_Establish_UDP();
        ResetReciverBuff();
BSP_LCD_Init();   
BSP_LCD_LayerDefaultInit(0, LCD_FB_START_ADDRESS);
BSP_LCD_SetTextColor(LCD_COLOR_RED);
        BSP_LCD_Clear(LCD_COLOR_BLACK);
        BSP_LCD_SetBackColor(LCD_COLOR_BLACK);
        BSP_QSPI_Init();
        CopyFont2NorFlash();
        BSP_QSPI_EnableMemoryMappedMode();
        CheckGBKFont4NorFlash();
        HAL_Delay(500);
        BSP_LCD_Clear(LCD_COLOR_BLACK);
       
        for(t = 0;t < 14;t++)
        {
                memcpy(Display_Buff,TextBuff,strlen((const char*)TextBuff));
        }
while (1)
{
                for(x0 = 0;x0 < 800;x0++)
                {
                                for(t = 0;t < 14;t++)
                                {
                                        BSP_LCD_SlideShow(x0,10 + 30*t,t);
                                }
                }

}
}         在上面的main函数中我们先初始化了串口1用于在电脑上进行发送“弹幕”,然后又初始化了串口WIFI模块ESP8266模块,初始化了WIFI模块和手机建立一个UDP连接,此时首先你应该知道自己的手机的IP地址和WIFI的IP地址,在用wifi模块建立UDP连接的时候我省略了输入路由器的SSID和密码的步骤如果你的模块也配置连接过路由器,这个也能省略否则要自己添加上这个步骤。然后就说初始化LCD的模块部分,最后就是将汉字库通过内部FLASH烧写到NORFALSH中,因为烧写字库要下载很大的BIN文件比较浪费时间,所以烧写字库进行一次即可,代码中我通过宏定义来设置要不要进行字库的烧写,限于时间和篇幅限制还有很多细节帖子中没法详细描述,具体可以测试参考代码,代码写的比较简陋只用于演示验证作用。
   帖子中用到的2个串口超时中断以及WIFI模块的AT命令可以参考下面的推荐阅读中详细内容。


    推荐阅读:
         串口接收超时中断和字符匹配中断

         ESP8266简单上手

测试代码:











jackten 发表于 2017-2-19 22:30:42

厉害厉害                              

creep 发表于 2017-2-21 13:35:32

队长shiwo 发表于 2017-2-21 09:33
收藏着学习先,仰慕大神

队长好:)         

creep 发表于 2017-2-20 08:36:54

freeelectron 发表于 2017-2-19 22:19
厉害,不错哦,
gif动画怎么弄的?

ios客户端

Windows客户端

Stm32McuLover 发表于 2017-2-19 17:35:09

沙发,顶大神

netlhx 发表于 2017-2-19 20:14:00

牛X,下载下来慢慢学习:lol

zoomdy 发表于 2017-2-19 20:52:54

很棒的分享,谢谢

Paderboy 发表于 2017-2-19 21:48:27

Niubility。。。支持支持。。。

ALTIUM2 发表于 2017-2-19 21:53:41

:lol厉害,赞一个

anobodykey 发表于 2017-2-19 22:11:04

 这个挺不错的,给楼主一个赞

freeelectron 发表于 2017-2-19 22:19:29

厉害,不错哦,
gif动画怎么弄的?

人之颠 发表于 2017-2-20 08:31:18

不错哦,收藏了

霹雳之火 发表于 2017-2-20 08:47:47

谢谢分享

请叫我小张张 发表于 2017-2-20 08:53:45

CREEP哥哥 这个好高端 收藏慢慢研究感觉好好玩啊

风子 发表于 2017-2-20 08:56:03

膜拜大神

Xinfeng 发表于 2017-2-21 09:10:49

非常棒:):)
页: [1] 2
查看完整版本: 【F769IDISCOVERY评测】”弹幕“来了