donatello1996

  • 2020-08-06
  • 回复了主题帖: 【STM32F769Discovery开发板试用】USB-HS HID收发简单测评

    freebsder 发表于 2020-8-6 22:02 深挖的有点意思。
    道长见笑了

  • 回复了主题帖: 【STM32F769Discovery开发板试用】USB-HS HID收发简单测评

    littleshrimp 发表于 2020-8-6 19:54 USBD_CUSTOM_HID_SendReport函数有返回值 你试一下,判断返回值为USBD_OK时再操作GPIO 测量一下波 ...
    好的,这周我有空试试,我也想知道F769的USBHID性能如何

  • 回复了主题帖: 【STM32F769Discovery开发板试用】USB-HS HID收发简单测评

    littleshrimp 发表于 2020-8-6 17:37 单从USB2.0的参数看,最大480Mbps即使不考虑协议最大速率不会超过60MB/S 你再去看USB3320的数据手册最大 ...
    有图有真相,令人信服,点赞!既然官方手册都说了不超过60MB/s的话,那就肯定不会超过,但具体应该是多少数值我得写个上位机验证一下,这点是必须要实事求是的。我跟楼上那位争论的重点在于,不应该把USB时钟和每秒发送数据量混在一起讨论,我认为这是两个完全不同的概念,发送数据量是跟USB时钟有很小的关联,但绝对不是简单的1对1关系,F769主控的USB控制器从USB时钟源获取工作时钟,在规定的周期内从缓冲区把一帧数据发出,那么一秒发送的数据量具体有多少,跟一帧数据大小和占用时钟周期有关系,这是一个二元关系,不是简单的1对1关系

  • 加入了学习《TouchGFX 设计》,观看 sylar^z的TouchGFX作品

  • 2020-08-05
  • 回复了主题帖: 【STM32F769Discovery开发板试用】USB-HS HID收发简单测评

    cruelfox 发表于 2020-8-4 21:28 你那只是函数返回而已,数据并没有发送出去。 HID报文是Interrupt Transfer类型的,只有主机polling的 ...
    数据有没有成功发送出去,需要电脑上位机那边接收并做计数,不是STM32这边需要关心的问题,先不说polling到底间隔多长,反正成功发送多少数据量肯定跟60MHz的时钟没有半毛钱关系。

  • 2020-08-04
  • 回复了主题帖: 【STM32F769Discovery开发板试用】USB-HS HID收发简单测评

    cruelfox 发表于 2020-8-4 17:49 扯了,你这么用示波器抓只是判断 USBD_CUSTOM_HID_SendReport(&hUsbDeviceHS,buf,sizeof(buf)); 这 ...
    时钟60MHz跟可以发送的数据量速率128MB/s有什么关系呢?60MHz只不过是USB时钟电平的频率,一秒钟内时钟可以翻转60M次,只要在时钟翻转一次电平的时候CPU往USBHS缓冲区送入一次64字节的数据包就代表发送成功了,接收端能否正常,准确无误接收这些数据包跟发送端有啥关系呢?

  • 回复了主题帖: 27家运营商“弃用华为”!美国公布“5G干净网络”名单

    搞协议栈的我默默路过。。。看到几个熟悉的牌子,中华电信是香港应科院的,台湾之星没听说过,至于KT和SKT就比较熟悉了,都是辣鸡韩棒子的牌子,反正我对SKT没啥好印象,LOL季中赛一选亮老鼠,biss!

  • 回复了主题帖: 【STM32F769Discovery开发板试用】能用但并不好用的TCP服务器通信代码简单尝试

    damiaa 发表于 2020-8-4 09:02 楼主加油。把问题给解决掉。产品中。经常死机和掉线是不允许发生的。这样无法用到产品中去的。
    如果是用于产品的话,别说死机掉线了,就是速率有一丁点不满足要求或是传输中有一些错误码都是不允许的,必须要无任何可见问题。对于STM32来说,最好用的高速传输方式还是USB,TCP网络传输比较少用,我昨晚发了一帖USBHID的帖子,你可以去看看。

  • 回复了主题帖: 【STM32F769Discovery开发板试用】USB-HS HID收发简单测评

    上传工程文件

  • 发表了主题帖: 【STM32F769Discovery开发板试用】USB-HS HID收发简单测评

           STM32的大部分评估板和第三方开发板都带有USB接口,USB接口配置为Device模式的话就可以跟电脑进行通信了,一般Device模式又分为HID/CDC/DFU/MS/Audio等协议,其中HID为人体学输入设备,CDC为虚拟串口设备,DFU为固件升级设备,MS为大容量存储设备,Audio为音频通信设备,HID适用于简单/低速的透传通信或是虚拟为鼠标键盘等人体学输入设备,可以自定义每个透传字段的长度,特征值等,不需要对数据包中的逐个字节进行解析;CDC适用于高速/大流量的透传通信,可以以winusb形式与电脑进行通信,不可以自定义透传字段特征值,只能从一帧数据包中逐个字节进行解析;DFU只适用于主控代码升级;MS只适用于将主控虚拟为U盘等存储设备;Audio只适用于将主控虚拟为声卡设备。在数据透传应用上,HID协议与CDC协议极其相似。       官方给出的搭建HID代码例程的步骤非常简单,只需要使用CubeMX并配置发送字段即可完成,一步步开始吧,不过USB通信是需要固定48MHz时钟的,对于官方的评估板或者时钟设计参考官方评估板的第三方开发板,一般尽量使用CubeMX的板级初始化功能(即Access to Board Selector): 不使用芯片级初始化功能(Access to MCU Selector)意义在于,可以正确初始化USB时钟,避免不必要的麻烦。进入CubeMX引脚配置界面,将不必要的外设如ETH I2S SAI QSPI FMC LTDC等全部剔除,并设置USBHS(高速接口)为Device模式,外挂PHY,SOF不启用: 为什么是外挂PHY呢,很简单,看原理图和板子的芯片布局就知道了: 板子F769主控的USBHS接口是通过一块USB3320 USB-PHY芯片引出物理层microUSB接口的。 然后是对于USB Device第三方外设的配置,设置模式为Custom HID: 最后确认USB时钟频率为48MHz即可生成工程: 工程生成后,按照ST官方给出USBHID文档的指引,需要替换usbd_custom_hid_if.c文件下的CUSTOM_HID_ReportDesc_HS枚举数组: #define REPORT_INPUT_LEN 64 __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_HS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END = { 0x06,0xA0,0xFF,//用法页(FFA0h, vendor defined) 0x09, 0x01,//用法(vendor defined) 0xA1, 0x01,//集合(Application) 0x09, 0x02 ,//用法(vendor defined) 0xA1, 0x00,//集合(Physical) 0x06,0xA1,0xFF,//用法页(vendor defined) //输入报告 0x09, 0x03 ,//用法(vendor defined) 0x09, 0x04,//用法(vendor defined) 0x15, 0x80,//逻辑最小值(0x80 or -128) 0x25, 0x7F,//逻辑最大值(0x7F or 127) 0x35, 0x00,//物理最小值(0) 0x45, 0xFF,//物理最大值(255) 0x75, 0x08,//报告长度Report size (8位) 0x95, REPORT_INPUT_LEN, //报告数值 0x81, 0x02, //输入(data, variable, absolute) //输出报告 0x09, 0x05, //用法(vendor defined) 0x09, 0x06, //用法(vendor defined) 0x15, 0x80, //逻辑最小值(0x80 or -128) 0x25, 0x7F, //逻辑最大值(0x7F or 127) 0x35, 0x00, //物理最小值(0) 0x45, 0xFF, //物理最大值(255) 0x75, 0x08, //报告长度(8位) 0x95, 0x40, //报告数值(64 fields) 0x91, 0x02, //输出(data, variable, absolute) 0xC0, //集合结束(Physical) 0xC0 //集合结束(Application) }; 这个数组会被初始化函数集USBD_CustomHID_fops_HS调用: USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops_HS = { CUSTOM_HID_ReportDesc_HS, CUSTOM_HID_Init_HS, CUSTOM_HID_DeInit_HS, CUSTOM_HID_OutEvent_HS }; 然后修改usbd_conf.h的USBD_CUSTOM_HID_REPORT_DESC_SIZE,这里填52,即刚刚那个数组的长度: 添加回调函数,这步必不可少: void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { HAL_IncTick(); } 上述代码修改完毕之后就可以用USBD_CUSTOM_HID_SendReport(&hUsbDeviceHS,buf,sizeof(buf));函数进行USBHID发送,每次调用函数发送的单位是64字节,固定的: 当然为了便于系统和HID软件识别,需要修改VID PID 设备名等参数: #define USBD_VID 1155 #define USBD_PID_HS 22352 #define USBD_LANGID_STRING 1033 #define USBD_MANUFACTURER_STRING "STMicroelectronics" #define USBD_PRODUCT_STRING_HS "donatello1996 STM32F769 USBHID Device" #define USBD_CONFIGURATION_STRING_HS "Custom HID Config" #define USBD_INTERFACE_STRING_HS "Custom HID Interface" 最后使用示波器抓取发送函数耗时:   while(1) { GPIOJ->BSRR |= 0x00000002; USBD_CUSTOM_HID_SendReport(&hUsbDeviceHS,buf,sizeof(buf)); GPIOJ->BSRR |= 0x00020000; USBD_CUSTOM_HID_SendReport(&hUsbDeviceHS,buf,sizeof(buf)); } 可以看出,即使是HID这种低速模式,发送间隔居然也缩小到不可思议的500ns,也就是1us可以发送128个字节,那么1s就可以发送128MB,在多数项目中绝对够用了,如果是使用高速的CDC模式则更快,1s发送几百MB不在话下。

  • 2020-08-02
  • 回复了主题帖: 【STM32F769Discovery开发板试用】便捷的SD卡读取BMP文件&QSPI读写&SDRAM读写

    二楼放出工程文件

  • 发表了主题帖: 【STM32F769Discovery开发板试用】便捷的SD卡读取BMP文件&QSPI读写&SDRAM读写

            STM32F769Discovery板子外接了多种存储器,有SD卡,QSPI FLASH,SDRAM和EEPROM,属于掉电保存的大容量FLASH有SD卡和QSPI FLASH两种,EEPROM也可掉电保存但容量小,SDRAM不可掉电保存,本帖主要讲SD卡读取BMP文件和使用QSPI FLASH存储读写不带文件头的BMP数据,SDRAM和EEPROM会简单介绍,一笔带过。        先来说说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文件头数据格式: typedef struct tagBITMAPFILEHEADER { UINT16 bfType; DWORD bfSize; UINT16 bfReserved1; UINT16 bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER; BMP文件头格式的信息非常精炼,包含的信息有文件占用空间,图片的长和宽,数据部分偏移量,调色板,位色(16/24/32),之后就是数据部分了,简单的50多个字节,包含了文件读取的所有必要信息。CubeF7库里面已经写好了读取BMP文件并显示的函数: 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(&F1, (TCHAR const*)BmpName, FA_READ) != FR_OK) { return 1; } if (f_read (&F1, sector, 30, (UINT *)&BytesRead) != FR_OK) { return 2; } BmpAddress = (uint32_t)sector; /* Read bitmap size */ size = *(uint16_t *) (BmpAddress + 2); size |= (*(uint16_t *) (BmpAddress + 4)) << 16; *rsize = size; /* Get bitmap data address offset */ index = *(uint16_t *) (BmpAddress + 10); index |= (*(uint16_t *) (BmpAddress + 12)) << 16; f_close (&F1); f_open (&F1, (TCHAR const*)BmpName, FA_READ); do { if (size < 256*2) { i1 = size; } else { i1 = 256*2; } size -= i1; f_read (&F1, sector, i1, (UINT *)&BytesRead); for (index = 0; index < i1; index++) { *(volatile uint8_t*) (Address) = *(volatile uint8_t *)BmpAddress; BmpAddress++; Address++; } BmpAddress = (uint32_t)sector; } while (size > 0); f_close (&F1); return 0; } 读取数据之后,当然就是显示了: 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[10] + (pbmp[11] << 8) + (pbmp[12] << 16) + (pbmp[13] << 24); /* Read bitmap width */ width = pbmp[18] + (pbmp[19] << 8) + (pbmp[20] << 16) + (pbmp[21] << 24); /* Read bitmap height */ height = pbmp[22] + (pbmp[23] << 8) + (pbmp[24] << 16) + (pbmp[25] << 24); /* Read bit/pixel */ bit_pixel = pbmp[28] + (pbmp[29] << 8); /* Set the address */ Address = hltdc_discovery.LayerCfg[ActiveLayer].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 < 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); } } 真的是一条龙服务,简单的两个函数就完成了,官方的源代码有不合理的位置我已经修改过来了,比如f_open() f_read()这些操作,即使返回失败也不应用while(1)死循环来处理,然后就是修改了返回值,返回0成功,返回大于0的值为不同原因的失败,尽管这两个函数还有很大的改进空间,算法还是有许多不完善的地方,但简单读取SD卡BMP文件够用了。读取BMP文件的主循环代码: while(1) { Storage_OpenReadFile(uwInternalBuffer,"/Media/2.bmp",&rsize); BSP_LCD_DrawBitmap(0,0,uwInternalBuffer); Storage_OpenReadFile(uwInternalBuffer,"/Media/3.bmp",&rsize); BSP_LCD_DrawBitmap(0,0,uwInternalBuffer); Storage_OpenReadFile(uwInternalBuffer,"/Media/4.bmp",&rsize); BSP_LCD_DrawBitmap(0,0,uwInternalBuffer); Storage_OpenReadFile(uwInternalBuffer,"/Media/5.bmp",&rsize); BSP_LCD_DrawBitmap(0,0,uwInternalBuffer); } 不过这里有一个地方是需要注意的,那就是uwInternalBuffer的地址,官方的代码是直接使用SDRAM中LTDC的两层图层的地址,任选一层,分别为layer0和layer1的两层数据空间,对这部分内存空间的写入,会直接作用到液晶屏显示上面: uint8_t* uwInternalBuffer = (uint8_t *)(0xC0000000+800*480*4); 或   uint8_t* uwInternalBuffer = (uint8_t *)(0xC0000000+800*480*8); 这是有原因的,由于STM32F769的片内RAM空间中,可供用户调用的部分不足800*480*4=1536000(约等于1500KB)即缓存一帧BMP图所需要的空间(甚至800*480*3=1152000,约等于1100KB都不够),即无法直接定义一个大小为1152000的非常量数组,会直接报错: unsigned char temp[1152000]; 因此官方代码是直接使用(0xC0000000+800*480*4)~(0xC0000000+800*480*8)之间的3MB左右的空间,定义一个指针指向这块空间,将从SD卡中读取出来的缓存数据存放到此处。这里可能有坛友要问了,那么SDRAM其余几十MB的空间哪里去了?为什么不用?别问,问就是ST官方代码有漏洞,后面我尝试过,也不成功,之后再探讨。 读取BMP文件刷屏效果如下,非常简单便捷,再次声明只能读取16位或者24位色的BMP文件:   SD卡读取BMP文件显示的测评完成了,之后就是读写QSPI FLASH了,同样也不难,官方都把大部分的代码都封装好了,只需要用到官方的三个函数,BSP_QSPI_Erase_Block(),BSP_QSPI_Write(),BSP_QSPI_Read(),QSPI FLASH的块/扇区在写入之前需要擦除: 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(&QSPIHandle) != QSPI_OK) { return QSPI_ERROR; } /* Send the command */ if (HAL_QSPI_Command(&QSPIHandle, &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(&QSPIHandle, MX25L512_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK) { return QSPI_ERROR; } return QSPI_OK; } 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 > 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(&QSPIHandle) != QSPI_OK) { return QSPI_ERROR; } /* Configure the command */ if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; } /* Transmission of the data */ if (HAL_QSPI_Transmit(&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(&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) > end_addr) ? (end_addr - current_addr) : MX25L512_PAGE_SIZE; } while (current_addr < end_addr); return QSPI_OK; } 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(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; } /* Set S# timing for Read command */ MODIFY_REG(QSPIHandle.Instance->DCR, QUADSPI_DCR_CSHT, QSPI_CS_HIGH_TIME_1_CYCLE); /* Reception of the data */ if (HAL_QSPI_Receive(&QSPIHandle, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) { return QSPI_ERROR; } /* Restore S# timing for nonRead commands */ MODIFY_REG(QSPIHandle.Instance->DCR, QUADSPI_DCR_CSHT, QSPI_CS_HIGH_TIME_4_CYCLE); return QSPI_OK; } 擦除的单位是块或者扇区,一个块长度为512字节,如果要擦除多个连续块,则起始地址每次加512,比如要擦除0~1023这个空间的所对应的两个块: BSP_QSPI_Erase_Block(0); BSP_QSPI_Erase_Block(512); 依据此原理,要存储BMP图片的数据到QSPI FLASH里面就非常简单了,我自己写了一个便捷的函数,可以根据写入BMP图片数据的长/宽/位色参数进行调整: 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<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); } 写入一张24位800*480图片的BMP数据(大小为1152000字节)到起始地址1200000的操作如下: const unsigned char image1[1152000]={...}; BSP_QSPI_Store_BMP_Buffer(800,480,24,image1,1200000); 这里我对三张图片的BMP数据进行写入,分别存放于起始地址0,起始地址1200000,起始地址2400000,写入一张800*480*3的图片BMP数组到QSPI FLASH空间的大致用时是一分多钟,同一个地址写入一次之后,后面就可以一直读了,能读多少次看QSPI FLASH的寿命: 写入的评测非常简单顺利,但是要从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[38400],这个缓存数组用于每次从QSPI FLASH读取长度为38400的数据并使用DMA2D搬运到SDRAM地址空间中,这个搬运函数是从下至上搬运的,如果每组是16行,那么一共就要搬运30次,这里我可以画一个简单的示意图: 读取函数如下: void BSP_QSPI_Load_Show_BMP_Buffer(int start_addr) { int i,j; for(j=0;j<30;j++) { BSP_QSPI_Read(lcd_buf,j*38400+start_addr,38400); BSP_LCD_DrawBuffer(0,464-j*16,800,16,24,lcd_buf); } } while(1) { BSP_QSPI_Load_Show_BMP_Buffer(0); BSP_QSPI_Load_Show_BMP_Buffer(1200000); BSP_QSPI_Load_Show_BMP_Buffer(2400000); } 看看效果: 可以看出来从QSPI FLASH读取BMP数据和从SD卡读取的速度差不多,由于QSPI FLASH是切片读取缓存,所以会有从下至上的刷屏效果。 然后是SDRAM读写函数,不知道是什么原因,一次能连续操作的SDRAM空间极其有限,如果超过了空间限制的话,读写就会有问题,官方的代码是读写32位长度单位的空间,实际上可以以8位长度为单位进行,只需要改成HAL_SDRAM_Write_8b(): uint8_t BSP_SDRAM_WriteBytes(uint32_t uwStartAddress, uint8_t *pData, uint32_t uwDataSize) { if(HAL_SDRAM_Write_8b(&sdramHandle, (uint32_t *)uwStartAddress, pData, uwDataSize) != HAL_OK) { return SDRAM_ERROR; } else { return SDRAM_OK; } } uint8_t BSP_SDRAM_ReadBytes(uint32_t uwStartAddress, uint8_t *pData, uint32_t uwDataSize) { if(HAL_SDRAM_Read_8b(&sdramHandle, (uint32_t *)uwStartAddress, pData, uwDataSize) != HAL_OK) { return SDRAM_ERROR; } else { return SDRAM_OK; } }  

  • 2020-07-28
  • 回复了主题帖: 【STM32F769Discovery开发板试用】能用但并不好用的TCP服务器通信代码简单尝试

    littleshrimp 发表于 2020-7-28 05:32 这个问题有没有办法解决?官方例程有这个问题吗?
    目前没找到解决方案,这个用CubeMX生成的简化裸机例程已经比官方CubeF7例程要好用得多了,官方的例程调用的函数是一样的,并且封装更多,更难用,目前用单片机实现以太网通信,还是NXP,新塘这些比ST专业得多

  • 发表了主题帖: 【STM32F769Discovery开发板试用】能用但并不好用的TCP服务器通信代码简单尝试

           官方的网口TCP例程都是要带操作系统的,并且使用难度较高,因此不太想用官方的TCP例程,好在ST官方给出了使用CubeMX搭建裸机TCP例程的步骤,可以很方便地让开发者搭建TCP通信代码工程,不过经过实测,这种方式搭建出来的例程并不好用,这不是CubeMX的问题,而是单片机软硬件资源限制的问题,不知道以后ST会不会改进这点,我看是不会了,况且对于开发者来说,开发板与电脑的高速通信,使用USB-HID明显要比用TCP方便得多,也更好用。        官方的CubeMX搭建步骤其实很简单,基本上造轮子的部分都集成好了,只需要配置LWIP打开,ETH(以太网)配置为RMII模式,MAC(数据链路层)地址为02:00:00:00:00:00,PHY(物理层)为0,配置DHCP关闭: 我这边也懒得用CubeMX生成工程了,直接用网上已经有的例程来尝试:   LWIP裸机工程的核心步骤只有几个: -tcp_new()函数用于新建TCP进程控制块(PCB); -tcp_bind()函数用于绑定TCP服务器的IP地址和端口,这里我用的是192.168.1.10和6666; -tcp_listen()函数监听客户端接入,可以任选特定IP地址允许接入或全开放(任意地址)接入,默认全开放; -这个TCP的进程控制块(PCB)被封装成一个巨大的结构体(struct tcp_pcb *tcp_echoserver_pcb;),如果要读懂里面的参数含义,需要一定的网络工程知识,我们不需要关心实现原理,也不需要关心结构体怎么用,只需要知道,TCP通信就靠这个结构体就行了; -tcp_accept()函数是注册接收回调函数tcp_recv()的函数,通过两层回调将tcp_recv()和tcp_echoserver_pcb绑定在一起。 void TCP_Server_Init(void) { err_t err; tcp_echoserver_pcb = tcp_new(); if(tcp_echoserver_pcb !=NULL) { err = tcp_bind(tcp_echoserver_pcb,IP_ADDR_ANY,6666); if(err == ERR_OK) { tcp_echoserver_pcb = tcp_listen(tcp_echoserver_pcb); tcp_accept(tcp_echoserver_pcb,TCP_Server_Accept); } else { memp_free(MEMP_TCP_PCB, tcp_echoserver_pcb); } } }   TCP接收通过两层回调函数的形式实现,接收函数tcp_recv()封装于TCP_Server_Accept()函数中,而这个TCP_Server_Accept()函数由tcp_accept()函数调用: static err_t TCP_Server_Accept(void *arg, struct tcp_pcb *newpcb,err_t err) { /* initialize lwip tcp_recv callback function for newpcb */ tcp_recv(newpcb, TCP_Server_Recv); return ERR_OK; } 接收函数的具体动作则在TCP_Server_Recv()函数中实现,其中tcp_write()就是用于TCP发送的阻塞型函数: static err_t TCP_Server_Recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p,err_t err) { struct pbuf *ptr; u16_t plen; uint8_t *val; val=p->payload; tcp_write(tpcb,"66666",5,1); tcp_write(tpcb,p->payload,p->len,1); return ERR_OK; }   依照个人对于线程和网络工程的理解,可以看出来ST的代码还是有点东西的(虽然总体上是一坨屎),tcp_recv函数是通过回调函数的形式实现的,避免了阻塞,相比Linux系统下的TCP编程,recv()函数是阻塞函数,如果一份Linux代码要同时实现发送和接收,则需要开两条线程,STM32F769实现的TCP通信与之相比实时性要高得多,唯一不足的就是那少得可怜的存储池,基本存不下多少TCP数据,轻易就爆满了,这也是单片机不适合用于复杂应用级TCP通信的原因。这次测评TCP功能,仅仅是为了尊重STM32F769Discovery这块强大的探索板子,更是测评STM32F769自带的MAC性能。如果要使用单片机做简单网页通信的话,一般常规做法是外接QSPI/SPI以太网模块,比如W5500,ENC28J60等,这些模块集成了MAC和PHY,目标主控只需要引出串行或并行接口就可以实现基于这些接口的IP层(网络层)通信,非常的方便。 做好了上述操作之后,还需要设置开发板的TCP服务器IP地址,不使用DHCP,用静态IP 192.168.1.10: void MX_LWIP_Init(void) { /* IP addresses initialization */ IP_ADDRESS[0] = 192; IP_ADDRESS[1] = 168; IP_ADDRESS[2] = 1; IP_ADDRESS[3] = 10; NETMASK_ADDRESS[0] = 255; NETMASK_ADDRESS[1] = 255; NETMASK_ADDRESS[2] = 255; NETMASK_ADDRESS[3] = 0; GATEWAY_ADDRESS[0] = 192; GATEWAY_ADDRESS[1] = 168; GATEWAY_ADDRESS[2] = 1; GATEWAY_ADDRESS[3] = 1; lwip_init(); IP4_ADDR(&ipaddr, IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]); IP4_ADDR(&netmask, NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]); IP4_ADDR(&gw, GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]); /* add the network interface (IPv4/IPv6) without RTOS */ netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input); /* Registers the default network interface */ netif_set_default(&gnetif); if (netif_is_link_up(&gnetif)) { netif_set_up(&gnetif); } else { netif_set_down(&gnetif); } } 主循环其实就是个摆设,但摆设也不能忽略,不然通信失败: while (1) { MX_LWIP_Process(); } 电脑IP是192.168.1.2: 使用网线连接开发板和电脑: 使用电脑端的TCP通信软件如SSCOM等接入开发板的IP和端口6666: 为什么我说板子的TCP通信不好用呢,这里就体现出来了,一是断开重连超级费劲,二是TCP通信了几条之后就哑火了,没法继续接收了,而且过了十来秒钟之后服务器还会神奇地自动断开连接:

  • 2020-07-26
  • 回复了主题帖: 种草吗?最新的 STM32探索套件:简化物联网节点开发

    这个板以前见到过类似的,当时还是用L475/L496之流,现在看来貌似是把主控换成L4+系列,然后外接的模块换成别的物联网模块,其实搞了物联网这么多年的大佬都知道,物联网里面用得最多的只有WIFI和BLE,LORA一直都是未来可期,一直都是只有宣传没有大规模实装

  • 发表了主题帖: 【STM32F769Discovery开发板试用】单总线DHT11驱动&ADC光照/接近传感器&串行驱动RGB

           本帖使用庆科Arduino扩展板的各个外设,测评STM32F769的单总线/AD/模拟串行总线的通信性能,使用扩展板的外设模块有DHT11模块,光敏电阻,红外接收头,P9813RGB彩灯模块。        首先是DHT11单总线模块,这个直接参考网上代码即可: void DHT11_Rst(void) { DHT11_IO_OUT(); DHT11_DQ_OUT(0); Delay_ms(20); DHT11_DQ_OUT(1); Delay_us(30); } unsigned char DHT11_Check() { unsigned char retry=0; DHT11_IO_IN(); while (DHT11_DQ_IN&&retry<100) { retry++; Delay_us(1); }; if(retry>=100)return 1; else retry=0; while (!DHT11_DQ_IN&&retry<100) { retry++; Delay_us(1); }; if(retry>=100)return 1; return 0; } unsigned char DHT11_Read_Bit(void) { unsigned char retry=0; while(DHT11_DQ_IN&&retry<100) { retry++; Delay_us(1); } retry=0; while(!DHT11_DQ_IN&&retry<100) { retry++; Delay_us(1); } Delay_us(40); if(DHT11_DQ_IN)return 1; else return 0; } unsigned char DHT11_Read_Byte(void) { unsigned char i,dat; dat=0; for (i=0;i<8;i++) { dat<<=1; dat|=DHT11_Read_Bit(); } return dat; } unsigned char DHT11_Read_Data(unsigned char *temp,unsigned char *humi) { unsigned char buf[5]; unsigned char i; DHT11_Rst(); if(DHT11_Check()==0) { for(i=0;i<5;i++) { buf=DHT11_Read_Byte(); } if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]) { *humi=buf[0]; *temp=buf[2]; } }else return 1; return 0; } unsigned char BSP_DHT11_Init() { GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_GPIOJ_CLK_ENABLE(); GPIO_Initure.Pin=GPIO_PIN_4; GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; GPIO_Initure.Pull=GPIO_PULLUP; GPIO_Initure.Speed=GPIO_SPEED_HIGH; HAL_GPIO_Init(GPIOJ,&GPIO_Initure); DHT11_Rst(); return DHT11_Check(); } 对于DHT11模块,主要评测在200MHz主频下,STM32F769采集一次DHT11温湿度数据所需要的时间间隔: GPIOJ->BSRR |= 0x00000002; DHT11_Read_Data(&temp,&humi); GPIOJ->BSRR |= 0x00020000; DHT11_Read_Data(&temp,&humi); 可以看出,DHT11这种单总线且延迟较大的器件,采集一次的延时时间还是挺长的,达到了20ms,当项目要求us甚至us级实时的话,DHT11就绝对不是很好的选择。 然后是AD采集,这次我只做了AD常规转换的评测,没有走DMA通道,评测单次AD采集,不同采集节拍下的延时,分别是480节拍和28节拍,两组AD接口分别接了一个光敏电阻和接近传感器: ADC_HandleTypeDef hadc1,hadc3; void BSP_ADC1_Init() { hadc1.Instance=ADC1; hadc1.Init.ClockPrescaler=ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution=ADC_RESOLUTION_12B; hadc1.Init.DataAlign=ADC_DATAALIGN_RIGHT; //右对齐 hadc1.Init.ScanConvMode=DISABLE; //非扫描模式 hadc1.Init.EOCSelection=DISABLE; //关闭EOC中断 hadc1.Init.ContinuousConvMode=DISABLE; //关闭连续转换 hadc1.Init.NbrOfConversion=1; //1个转换在规则序列中 也就是只转换规则序列1 hadc1.Init.DiscontinuousConvMode=DISABLE; //禁止不连续采样模式 hadc1.Init.NbrOfDiscConversion=0; //不连续采样通道数为0 hadc1.Init.ExternalTrigConv=ADC_SOFTWARE_START; //软件触发 hadc1.Init.ExternalTrigConvEdge=ADC_EXTERNALTRIGCONVEDGE_NONE;//使用软件触发 hadc1.Init.DMAContinuousRequests=DISABLE; HAL_ADC_Init(&hadc1); } void BSP_ADC3_Init() { hadc3.Instance=ADC3; hadc3.Init.ClockPrescaler=ADC_CLOCK_SYNC_PCLK_DIV4; hadc3.Init.Resolution=ADC_RESOLUTION_12B; hadc3.Init.DataAlign=ADC_DATAALIGN_RIGHT; //右对齐 hadc3.Init.ScanConvMode=DISABLE; //非扫描模式 hadc3.Init.EOCSelection=DISABLE; //关闭EOC中断 hadc3.Init.ContinuousConvMode=DISABLE; //关闭连续转换 hadc3.Init.NbrOfConversion=1; //1个转换在规则序列中 也就是只转换规则序列1 hadc3.Init.DiscontinuousConvMode=DISABLE; //禁止不连续采样模式 hadc3.Init.NbrOfDiscConversion=0; //不连续采样通道数为0 hadc3.Init.ExternalTrigConv=ADC_SOFTWARE_START; //软件触发 hadc3.Init.ExternalTrigConvEdge=ADC_EXTERNALTRIGCONVEDGE_NONE;//使用软件触发 hadc3.Init.DMAContinuousRequests=DISABLE; HAL_ADC_Init(&hadc3); } void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) { GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_ADC3_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); GPIO_Initure.Pin=GPIO_PIN_2; GPIO_Initure.Mode=GPIO_MODE_ANALOG; GPIO_Initure.Pull=GPIO_NOPULL; HAL_GPIO_Init(GPIOC,&GPIO_Initure); GPIO_Initure.Pin=GPIO_PIN_10; GPIO_Initure.Mode=GPIO_MODE_ANALOG; GPIO_Initure.Pull=GPIO_NOPULL; HAL_GPIO_Init(GPIOF,&GPIO_Initure); } int BSP_ADC1_Get(int ch) { ADC_ChannelConfTypeDef ADC1_ChanConf; ADC1_ChanConf.Channel=ch; ADC1_ChanConf.Rank=1; ADC1_ChanConf.SamplingTime=ADC_SAMPLETIME_28CYCLES; ADC1_ChanConf.Offset=0; HAL_ADC_ConfigChannel(&hadc1,&ADC1_ChanConf); HAL_ADC_Start(&hadc1); // HAL_ADC_PollForConversion(&hadc1,10); return (int)HAL_ADC_GetValue(&hadc1); } int BSP_ADC3_Get(int ch) { ADC_ChannelConfTypeDef ADC3_ChanConf; ADC3_ChanConf.Channel=ch; ADC3_ChanConf.Rank=1; ADC3_ChanConf.SamplingTime=ADC_SAMPLETIME_28CYCLES; ADC3_ChanConf.Offset=0; HAL_ADC_ConfigChannel(&hadc3,&ADC3_ChanConf); HAL_ADC_Start(&hadc3); // HAL_ADC_PollForConversion(&hadc3,10); return (int)HAL_ADC_GetValue(&hadc3); } GPIOJ->BSRR |= 0x00000002; BSP_ADC1_Get(12); BSP_ADC3_Get(8); DHT11_Read_Data(&temp,&humi); 可以看出480节拍和28节拍的差异还是很大的,28节拍可以控制在5us左右(两次采集),480节拍则达到了20us: 最后就是RGB彩灯驱动了,这是使用了板上的PB8和PB9的一组I2C接口,走的是模拟I2C总线,给P9813传入32位灰度数据,就可以控制RGB灯三个颜色分量的变化: 利用触摸屏做了个演示Demo: while (1) { DHT11_Read_Data(&temp,&humi); adc_value1=BSP_ADC1_Get(12); adc_value2=BSP_ADC3_Get(8); LCD_Show_Char_1632(96,64,temp/10%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(112,64,temp%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(96,96,humi/10%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(112,96,humi%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(208,128,adc_value1/1000%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(224,128,adc_value1/100%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(240,128,adc_value1/10%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(256,128,adc_value1%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(256,160,adc_value2/1000%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(272,160,adc_value2/100%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(288,160,adc_value2/10%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); LCD_Show_Char_1632(304,160,adc_value2%10+'0',LCD_COLOR_BLACK,LCD_COLOR_CYAN); BSP_TS_GetState(&tst); tx1=tst.touchX[0]; ty1=tst.touchY[0]; if(tx1>=545) { if(tx1<565)tx1=545; if(tx1>780)tx1=799; if(0<=ty1&&ty1<120) { rtemp=tx1-545; BSP_LCD_FillRect(545,0,rtemp,120,LCD_COLOR_RED); BSP_LCD_FillRect(tx1,0,255-rtemp,120,LCD_COLOR_CYAN); } else if(120<=ty1&&ty1<240) { gtemp=tx1-545; BSP_LCD_FillRect(545,120,gtemp,120,LCD_COLOR_GREEN); BSP_LCD_FillRect(tx1,120,255-gtemp,120,LCD_COLOR_CYAN); } else if(240<=ty1&&ty1<360) { btemp=tx1-545; BSP_LCD_FillRect(545,240,btemp,120,LCD_COLOR_BLUE); BSP_LCD_FillRect(tx1,240,255-btemp,120,LCD_COLOR_CYAN); } BSP_LCD_FillRect(545,360,255,120, 0xff000000|(rtemp<<16)|(gtemp<<8)|btemp); RGB_Color_Control(rtemp,gtemp,btemp); } } 工程文件:    

  • 2020-07-25
  • 发表了主题帖: 【STM32F769Discovery开发板试用】SD卡文件系统应用&让人自闭的硬解JPEG解码代码

    本帖最后由 donatello1996 于 2020-7-25 16:41 编辑         在移植官方硬解JPEG代码的时候出现了严重问题,首先官方JPEG解码能够正常显示的文件跟BMP数组同样只支持16位和24位颜色,也就是只能成功显示图片格式为16位和24位颜色的JPEG文件,然后就是官方的JPEG解码代码工程只是用于Demo展示,没有进一步完善,官方代码中仅仅是显示一次JPEG图片,什么为之显示一次呢,如果把GPU显存看成容器,把JPEG硬件解码看成从扎着结的米袋中往容器倒米,那么目前移植的代码就是只能将米袋的结解开倒一次米,不能将米从容器中倒出来再装一次,也就是从头到尾容器只能使用一次,即使是同一张图片的数据也只能刷一次,归结到底,一是官方的代码没有做容器的复位,导致想要显示多次图片的时候会出现BUG,我目前是修复了这个BUG,不过有更多的BUG,官方的代码问题实在是多,而且显示也不稳定,不想搞了,明天有空移植一份原子的硬解JPEG代码算了,免得造轮子,一个试用活动搞得自己这么累,不值得,直接移植能用的代码不香吗?        要想实现JPEG解码,第一步当然是搭建文件系统,这步非常简单,即使是从ST官方那晦涩难懂的代码中直接移植也不会有什么大问题,步骤如下: 第一,将所有FATFS文件系统相关驱动库函数全部移植,并修复其中的#include包含报错和stdint语法报错,能用volatile就不用__IO关键字,并尽量将core_cm7.h中一些关键定义用第三方头文件替代,减少工程对KEIL安装目录ARMCC交叉工具链的的依赖: -其中ff.c是定义了一些文件系统常用函数,如f_open(),f_read(),f_close()之类的,用法跟stdio/stdlib的同名函数差不多,这个代码是抽象层代码,是前辈写好供大家直接调用的,不需要关心实现;ff_gen_dev.c是定义上述操作的几个相关接口;diskio.c是对于可访问磁盘设备的结构体函数抽象;所有支持文件系统的单片机都用同样的这三份文件; -真正要根据每个不同单片机平台要修改的文件就是sd_diskio.c,当然名字可以变,比如用U盘的就叫usbms_diskio.c,用SPI FLASH的就叫spiflash_diskio.c,用NAND的就叫nand_diskio.c,里面就是将具体操作存储设备的底层函数挂载到diskio.c和ff_gen_dev.c的抽象层中。 然后是头文件,ffconf.h等。 成功挂载SD卡文件系统之后就可以进行一些简单的文件读写操作了,读写文件之前需要打开文件,f_open()函数的目标操作结构体是FIL: 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[_MAX_SS]; /* File private data read/write window */ #endif } FIL; 然后使用f_read就可以读取文件内容了。举一个非常简单的栗子,读取TXT文件内容: if(f_open(&TXT_File, "1txt.txt", FA_READ) == FR_OK) { f_read(&TXT_File,TXT_File.buf,10,&b); printf("TXT_File:%d %d %s\n",b,TXT_File.buf); f_close(&TXT_File); } JPEG硬件解码,官方代码采用的方式是双通道DMA,一个DMA用于从JPEG文件中读取数据流到内存中,另一个DMA用于从内存中解码完毕的BMP数组搬运到GPU显存中显示,所以需要两个DMA中断服务函数: void DMA2_Stream3_IRQHandler(void) { HAL_DMA_IRQHandler(JPEG_Handle.hdmain); } void DMA2_Stream4_IRQHandler(void) { HAL_DMA_IRQHandler(JPEG_Handle.hdmaout); } 然后JPEG硬解码也需要一个中断服务函数: void JPEG_IRQHandler(void) { HAL_JPEG_IRQHandler(&JPEG_Handle); } 至此,JPEG文件就可以正常解码并输出到GPU显存中了,STM32F769的JPEG硬件解码库是怎么工作的,我们不需要关心,我们需要操作的是JPEG原始数据和JPEG成功解码数据的输送,特别注意的是我在官方的代码中加了一行,以便支持: int JPEG_OutputHandler(JPEG_HandleTypeDef *hjpeg) { int ConvertedDataCount; if(Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].State == JPEG_BUFFER_FULL) { MCU_BlockIndex += pConvert_Function(Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].DataBuffer, (unsigned char *)FrameBufferAddress, MCU_BlockIndex, Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].DataBufferSize, &ConvertedDataCount); Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].State = JPEG_BUFFER_EMPTY; Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].DataBufferSize = 0; JPEG_OUT_Read_BufferIndex++; if(JPEG_OUT_Read_BufferIndex >= 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) && \ (Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].State == JPEG_BUFFER_EMPTY) &&\ (Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].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[JPEG_IN_Write_BufferIndex].State == JPEG_BUFFER_EMPTY) { if(f_read (pFile, Jpeg_IN_BufferTab[JPEG_IN_Write_BufferIndex].DataBuffer , CHUNK_SIZE_IN, (UINT*)(&Jpeg_IN_BufferTab[JPEG_IN_Write_BufferIndex].DataBufferSize)) == FR_OK) { Jpeg_IN_BufferTab[JPEG_IN_Write_BufferIndex].State = JPEG_BUFFER_FULL; } else { } if((Input_Is_Paused == 1) && (JPEG_IN_Write_BufferIndex == JPEG_IN_Read_BufferIndex)) { Input_Is_Paused = 0; HAL_JPEG_ConfigInputBuffer(hjpeg,Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].DataBuffer, Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].DataBufferSize); HAL_JPEG_Resume(hjpeg, JPEG_PAUSE_RESUME_INPUT); } JPEG_IN_Write_BufferIndex++; if(JPEG_IN_Write_BufferIndex >= NB_INPUT_DATA_BUFFERS) { JPEG_IN_Write_BufferIndex = 0; } } } 我自己封装了一个JPEG解码的函数: 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->Instance = JPEG; HAL_JPEG_Init(hjpeg); if(f_open(&fil,(const char*)path, FA_READ) == FR_OK) { JPEG_Decode_DMA(hjpeg, &fil, 0xC0200000); do { JPEG_InputHandler(hjpeg); jpeg_done = JPEG_OutputHandler(hjpeg); }while(jpeg_done == 0); HAL_JPEG_GetInfo(hjpeg,hjinfo); xpos = (800 - hjinfo->ImageWidth)/2; ypos = (480 - hjinfo->ImageHeight)/2; if(hjinfo->ChromaSubsampling == JPEG_420_SUBSAMPLING) { if((hjinfo->ImageWidth % 16) != 0) width_offset = 16 - (hjinfo->ImageWidth % 16); } if(hjinfo->ChromaSubsampling == JPEG_422_SUBSAMPLING) { if((hjinfo->ImageWidth % 16) != 0) width_offset = 16 - (hjinfo->ImageWidth % 16); } if(hjinfo->ChromaSubsampling == JPEG_444_SUBSAMPLING) { if((hjinfo->ImageWidth % 8) != 0) width_offset = (hjinfo->ImageWidth % 8); } DMA2D_CopyBuffer((uint32_t *)0xC0200000, (uint32_t *)0xC0000000, xpos , ypos, hjinfo->ImageWidth, hjinfo->ImageHeight, width_offset); f_close(&fil); } } 注意还要msp初始化,千万别漏了,这个是weak弱函数,由HAL主控代码调用: 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(&hdmaIn); /* Initialize the DMA stream */ HAL_DMA_Init(&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(&hdmaOut); /* Initialize the DMA stream */ HAL_DMA_Init(&hdmaOut); /* Associate the DMA handle */ __HAL_LINKDMA(hjpeg, hdmaout, hdmaOut); HAL_NVIC_SetPriority(DMA2_Stream4_IRQn, 0x07, 0x0F); HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn); } 本来前两天还能正常显示图片的,现在又花屏了,都不知道什么原因,心累了,草(一种植物),放出代码工程让大佬们批评一下:

  • 2020-07-19
  • 回复了主题帖: 【STM32F769Discovery开发板试用】关于DMA2D刷屏的效率探讨和应用

    dcexpert 发表于 2020-7-19 16:16 很好的测试,就是代码好像没有正常显示出来。
    已经修改过来了

  • 发表了主题帖: 【STM32F769Discovery开发板试用】关于DMA2D刷屏的效率探讨和应用

    本帖最后由 donatello1996 于 2020-7-19 18:51 编辑          STM32F4和STM32F7系列的高端型号都能使用DMA2D图形加速器在LTDC显示外设上进行图形图像缓存的快速搬运,DMA2D外设在其中就相当于一个快速通道,可以将内存地址中的数据快速搬运到LTDC外设总线的地址,这种方式与用户直接操作GPU显存地址是两种不同原理,不同用途的方式,那问题来了,如果对于范围甚至是全屏刷新的话,这两种方式到底差多远的效率呢?官方的给出的数据是理想值,实际测试肯定需要借助仪器,这里我就简单使用GPIO引脚电平翻转+示波器读波形的方式进行探讨。示波器的1号输入探头接上板子并打开通道1: 使用示波器查看各种代码的延时时间,最简单的方式就是使用GPIO引脚电平翻转的方式,这里我使用了板上Arduino排针的PJ1引脚,初始化代码和寄存器翻转电平代码如下: void PJ1_Init() { __HAL_RCC_GPIOJ_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_NOPULL; HAL_GPIO_Init(GPIOJ, &GPIO_InitStruct); } GPIOJ->BSRR=0x00000002; GPIOJ->BSRR=0x00020000; 首先看看使用直接操作显存地址画点的方式的效率,代码就是直接使用画点函数: void fun(int color) { int i,j; for(i=0;i<800;i++) for(j=0;j<480;j++) BSP_LCD_DrawPixel(i,j,color); } while (1) { fun(0xffffff00); GPIOJ->BSRR=0x00000002; fun(0xffff00ff); GPIOJ->BSRR=0x00020000; }   然后查看示波器波形: 可以看出,这种逐个画点方式刷全屏的延迟大概是50~60ms一帧。 之后就是直接使用BSP_LCD_Clear进行刷屏,这种方式是使用DMA2D通道进行的:   while (1) { BSP_LCD_Clear(0xffffff00); GPIOJ->BSRR=0x00000002; BSP_LCD_Clear(0xffff00ff); GPIOJ->BSRR=0x00020000; } 查看示波器波形,有明显提升,大概是7~8ms一帧,效率差不多是画点方式的7倍: 最后就是今天的重头戏,使用DMA2D刷自定义图像数据,这边官方的代码存在一些问题,官方的代码支持ARGB888 RGB888 RGB565三种图像颜色格式,但是我都试了一遍,只有RGB888和RGB565是可以正常刷出的,原因不知,但是没有关系,因为BMP格式的数组最常用的也就是RGB888 24位色,ARGB8888即使不支持也没有关系,这边我改造了一下官方给的BMP文件数据刷屏函数,改成直接用BMP数组做参数,不引入文件头数据: void BSP_LCD_DrawBuffer(int Xpos, int Ypos,int width,int height,int bit_pixel,uint8_t *buf) { uint32_t index = 0; uint32_t Address; uint32_t InputColorMode = 0; Address = 0xC0000000 + (((800*Ypos) + Xpos)*(4)); switch(bit_pixel) { case 16: InputColorMode = DMA2D_INPUT_RGB565; break; case 24:InputColorMode = DMA2D_INPUT_RGB888; break; case 32: InputColorMode = DMA2D_INPUT_ARGB8888; break; } buf += (index + (width * (height - 1) * (bit_pixel/8))); for(index=0; index < height; index++) { LL_ConvertLineToARGB8888((uint32_t *)buf, (uint32_t *)Address, width, InputColorMode); Address+= (BSP_LCD_GetXSize()*4); buf -= width*(bit_pixel/8); } } -Xpos和Ypos是图像起始XY坐标 -width和height是图像宽度和高度 -bit_pixel是位长,可选参数16,24和32 -uint8_t *buf是图像数据,取自Image2Lcd软件 DMA2D默认方式是从下至上扫描,因此选项要选中 在Image2Lcd软件中设置图像的宽和高分别为600和360,这是因为假如全屏显示的话没办法同时存下两张图片的数据在Flash中,因此只能选宽高各3/4大小的图片,设置完成之后同样在主循环中刷新两张图片的图像数据: while (1) { BSP_LCD_DrawBuffer(100,60,600,360,24,(uint8_t *)image1); GPIOJ->BSRR=0x00000002; BSP_LCD_DrawBuffer(100,60,600,360,24,(uint8_t *)image2); GPIOJ->BSRR=0x00020000; } 示波器波形显示,可以看出不管是全屏800*480*24的缓存数据,还是600*360*24的小图片,DMA2D传输效率都不会有比较明显的差异,都是7~8ms一帧。 最后放上全屏显示图片的效果: 最后要重点说明一点,上述的所有测试均在主频200MHz下进行,且LTDC总线主频也直接按照官方DMA2D的默认参数,一般不推荐使用比官方更快的倍频,因为很有可能会导致画面撕裂。

  • 2020-07-10
  • 回复了主题帖: 【STM32F769Discovery开发板试用】搭建开发环境&驱动液晶屏显示点阵字符&触摸屏驱动

    freebsder 发表于 2020-7-9 20:53 头疼,转给4哥了。
    不会吧,ST管板把例程都给出来了,这都不玩嘛道长

统计信息

已有181人来访过

  • 芯币:726
  • 好友:1
  • 主题:47
  • 回复:71
  • 课时:--
  • 资源:--

留言

你需要登录后才可以留言 登录 | 注册


早晨五点 2018-7-13
在吗哥,我想请教您一个问题。我用HAL库的接收中断接收数据为什么接收到的数据总是变换次序?比如第2个数据变成第一个,第三个编程第二个。。。我应该怎么办呢?希望您能帮助我一下
查看全部