donatello1996

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

    freebsder 发表于 2020-9-8 16:03 图片不错。搞了这么多事情,楼主算是把F769摸透了
    一个月之前的帖子...

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

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

  • 2020-08-11
  • 回复了主题帖: 【STM32F769Discovery开发板试用】串口空闲中断不定长接收&PWM输出方波

    freebsder 发表于 2020-8-11 20:54 挺好啊,写的很详细,幸好我转手了,要不和你比就相形见绌了。
    谢谢道长,道长过奖了

  • 回复了主题帖: 【STM32F769Discovery开发板试用】串口空闲中断不定长接收&PWM输出方波

    freebsder 发表于 2020-8-10 18:59 你这是想搞到100分啊?
    哈哈道长见笑了,反正这段时间都有在玩这板,写一下心得罢了

  • 2020-08-10
  • 回复了主题帖: 【STM32F769Discovery开发板试用】串口空闲中断不定长接收&PWM输出方波

    初步推测,能输出更高频率PWM方波跟定时器本身有关系,试下换TIM2 TIM3这些更常用的定时器,看看能能不能输出更高频率的方波,做这个PWM实验的一大难题是开发板引出的GPIO引脚极其稀少,大部分都被FMC和LTDC两个接口的外设占用掉了,Arduino接口上引出的那些引脚每一个都是非常宝贵的资源。

  • 回复了主题帖: 又有板子开箱!这回是兆易GD32307E-START开发板

    接口啥的不介意,反正搞嵌入式的是不允许出现缺数据线的情况的,比较介意的地方是外设太少了,光秃秃一块核心板,不说液晶屏SDRAM那些,至少传感器焊几粒啊,真的没兴趣

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

    freebsder 发表于 2020-8-6 22:16 啥问题?简单说。。。
    使用echo方式测试TCP,第一,接收TCP报文的次数有限,接收了几条之后就哑火了,没法接续接收了,第二,TCP服务器在接入一段时间之后自动断开了

  • 发表了主题帖: 【STM32F769Discovery开发板试用】串口空闲中断不定长接收&PWM输出方波

    本帖最后由 donatello1996 于 2020-8-10 12:01 编辑        串口空闲中断和PWM方波输出都是实际产品项目中非常常用的功能,串口空闲中断的好处在于可以使用不轮询方式,不占用CPU资源的前提下进行不定长串口字符串接收,因为触发方式是使用中断。使用空闲中断实现不定长接收的方式非常简单,只需要两点,一个是开启空闲中断,一个是开启接收DMA,这边我使用开发板的Arduino接口上面的串口6(USART6)来进行: 接收DMA初始化使用CubeMX自动一键生成: UART_HandleTypeDef huart6; DMA_HandleTypeDef hdma_usart6_rx; void UART6_Init(int baud) { __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_USART6_CLK_ENABLE(); __HAL_RCC_DMA2_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_6; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF8_USART6; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); huart6.Instance = USART6; huart6.Init.BaudRate = baud; huart6.Init.WordLength = UART_WORDLENGTH_8B; huart6.Init.StopBits = UART_STOPBITS_1; huart6.Init.Parity = UART_PARITY_NONE; huart6.Init.Mode = UART_MODE_TX_RX; huart6.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart6.Init.OverSampling = UART_OVERSAMPLING_16; huart6.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; huart6.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; HAL_UART_Init(&huart6); hdma_usart6_rx.Instance = DMA2_Stream1; hdma_usart6_rx.Init.Channel = DMA_CHANNEL_5; hdma_usart6_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart6_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart6_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart6_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart6_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart6_rx.Init.Mode = DMA_CIRCULAR; hdma_usart6_rx.Init.Priority = DMA_PRIORITY_LOW; hdma_usart6_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; HAL_DMA_Init(&hdma_usart6_rx); __HAL_LINKDMA(&huart6,hdmarx,hdma_usart6_rx); __HAL_UART_ENABLE_IT(&huart6,UART_IT_IDLE); HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn); HAL_NVIC_SetPriority(USART6_IRQn, 0, 0); HAL_NVIC_EnableIRQ(USART6_IRQn); } void USART6_IRQHandler() { int temp; if(__HAL_UART_GET_FLAG(&huart6, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart6); HAL_UART_DMAStop(&huart6); temp=__HAL_DMA_GET_COUNTER(&hdma_usart6_rx); rx6_len=BUFFERSIZE-temp; uart6_recv_end_flag=1; } } 主循环中使用轮询处理代码处理空闲中断中接收到的数据,这里是只有触发了空闲中断才会处理数据并重新开启DMA,所以是不会占用CPU轮询资源的: #define BUFFERSIZE 255 unsigned char rx6_buf[BUFFERSIZE],rx6_len=0,uart6_recv_end_flag; void UART6_DMA_Get() { if(uart6_recv_end_flag==1) { uart6_recv_end_flag=0; printf("%s %d\n",rx6_buf,rx6_len); HAL_UART_Receive_DMA(&huart6, (unsigned char*)rx6_buf, BUFFERSIZE); } } 将串口6接入一个定时打印数值的传感器,就可以采集数据了,不过值得一提的是,串口空闲中断本质上仍是中断,如果空闲中断触发次数过于频繁,会严重影响CPU正常轮询工作,所以选用的串口传感器不能是每一帧发送间隔太短,频繁触发空闲中断的传感器。 然后是PWM,这里我使用Arduino接口的PF6即TIM10的通道1,可以直接参考原子代码来修改进行初始化: TIM_HandleTypeDef TIM10_Handler; TIM_OC_InitTypeDef TIM10_CH1Handler; void TIM10_PWM_Init(int arr,int psc) { __HAL_RCC_TIM10_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); GPIO_InitTypeDef GPIO_Initure; GPIO_Initure.Pin=GPIO_PIN_6; GPIO_Initure.Mode=GPIO_MODE_AF_PP; GPIO_Initure.Pull=GPIO_PULLUP; GPIO_Initure.Speed=GPIO_SPEED_HIGH; GPIO_Initure.Alternate=GPIO_AF3_TIM10; HAL_GPIO_Init(GPIOF,&GPIO_Initure); TIM10_Handler.Instance=TIM10; TIM10_Handler.Init.Prescaler=psc; TIM10_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; TIM10_Handler.Init.Period=arr; TIM10_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&TIM10_Handler); TIM10_CH1Handler.OCMode=TIM_OCMODE_PWM1; //模式选择PWM1 TIM10_CH1Handler.Pulse=arr/2; //设置比较值,此值用来确定占空比, //默认比较值为自动重装载值的一半,即占空比为50% TIM10_CH1Handler.OCPolarity=TIM_OCPOLARITY_LOW; HAL_TIM_PWM_ConfigChannel(&TIM10_Handler,&TIM10_CH1Handler,TIM_CHANNEL_1); HAL_TIM_PWM_Start(&TIM10_Handler,TIM_CHANNEL_1); } void TIM_SetTIM10Compare1(int compare) { TIM10->CCR1=compare; } 输出效果: 这里可以提一下TIM10的两个初始化参数,一个是重装载值,一个是分频值,重装载值的作用是设置一个PWM比较阈值,比如256,在0~256范围内占空比是0%~100%变化,而分频值就是PWM输出方波的频率,分频值越小,PWM频率越高,输出一个方波周期的时间就越短,这里我设置TIM10分频值为1,PWM波的输出间隔达到了1us级别,即1MHz,不过这样仍然算是频率较低的,之后我再继续探讨看看怎么输出更高频率的方波。 其它参数中,向上计数模式即为增量模式,即TIM10的计数器越计越多,模式为PWM1即占空比数值越大,一个周期内的低电平时间越长。

  • 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; } }  

统计信息

已有191人来访过

  • 芯币:739
  • 好友:1
  • 主题:48
  • 回复:78
  • 课时:--
  • 资源:--

留言

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


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