1.什么样的应用
首先让我们看一下提供哪些应用程序。我们进入站点https://all-hw.com/。
您需要登录那里(免费注册)。之后,将显示可用板的列表。 它们中的第一个没有屏幕,因此不方便用作本文的插图。第二个是平庸的,每个人都拥有,谈论它只是没有意思。我将选择第三个STM32F469I Discovery。 经过几个步骤后,我进入了以下页面: 实际上,在左侧,我们看到了照相机卸下的面板。而且该应用程序在那里可以正常工作。计数器滴答作响。并且如果您在右边的终端中输入一行,由于该终端已连接到UART调试端口,该行将显示在屏幕上。好吧,我在这里输入了Just Test。
看来控制器的应用程序要实现此功能并不复杂,但是……但是……我重复一遍,我们将ST板的类型数乘以至少三个。同样,一个非常重要的条件:此应用程序的开发时间不应太长。我们需要尽快开发它。而且它必须至少由三个编译器编译。
2.可能的方法
首先想到的是在STM32Cube MX中开发一个典型的应用程序。在那里,您可以为三个开发环境生成所有内容,并快速添加标准设备。就是这样,但是:
- 如果查看使用通过CubeMX创建的应用程序的示例,则可以看到在自动创建后,仍然需要使用文件进行大量优化。即,尽管节省了时间,但仍然不是最大。
- , .
- Cube MX STemWin, ( , , , ST STemWin TouchGFX, – ).
实际上,这足以推迟保留此类选项,并查看是否有更简单的解决方案……事实证明确实存在,并且它们也与Cube MX有关。使用此环境的任何人都知道它由核心代码和服务于特定系列控制器的软件包组成。包装是什么?这是一个ZIP文件。让我们下载并解压缩它。例如,拿一个全新STM32H747的包装。我有这个文件en.stm32cubeh7_v1-8-0.zip。
我们看到了如此丰富的内容:
这些是使用这种控制器的不同原型板的典型解决方案。好的。我们进入我们的目录STM32H747I-DISCO...有单独的现成应用程序和单独的-使用控制器块的示例。对于那些只想构建示例的人来说,这里没有什么有趣的,但是典型的演示应用程序的开发人员应该研究UART目录的内容。
当然,从应用程序来说,是STemWin。和最简单的。你好,世界。所有用户都将对它所在的目录感兴趣。
我们将基于此应用程序创建示例。为什么?我们进入STemWin_HelloWorld目录,然后看到:
ST程序员为我们做了一切。他们创建了可以从Keil,IAR和Eclipse(实际上是Cube-IDE)进行编译的源。因此,纠正这些来源就足够了,并且无需编辑依赖开发环境的文件就可以解决任务!好了,在Hello World项目中,它还在屏幕上显示了文本。为其添加UART支持就足够了,并且一切正常。这就是为什么我在上面指出UART示例对开发人员也有用的原因。
在这种情况下,我踩着自己的歌声。如果有人读过我以前的文章,他们知道我讨厌HAL。 HAL和优化是两个不兼容的东西。在实际项目中,我要么直接使用硬件,要么使用Konstantin Chizhov的驱动程序(mcucpp库)。但是在这种特殊情况下,情况并非如此。我们只是在制作一个显示文本并与COM端口配合使用的程序。在时钟频率为数百兆赫的控制器上。老实说,在Ona时代,一个普通的BK-shka对此进行了应对,其处理器的工作频率为3 MHz。而且,BK-shki没有RISC命令,甚至没有管道。但是有一个多路复用总线(即每个周期几个时钟)和一个没有缓存的异步DRAM。简而言之,性能仅为每秒30万次寄存器注册操作。这就足以解决文本输出问题,并可以通过UART(通过IRPS模块)使用UART。也就是说,在现代STM32上针对此任务的HAL代码的最优性也足以满足当前的任务。但是使用HAL时的开发速度将是最高的。
将会出现一个新板,该板肯定具有连接了统一呼叫的HAL。我们将根据随附的UART示例在典型示例中纠正初始化,并且工作将始终相同。开发速度为数十分钟。甚至没有手表。也就是说,要解决此问题,绝对最好使用HAL,尽管我不喜欢战斗情况。
所有。我告诉我,理论的最低要求是不可能的,否则无法继续实践。实验室工作后,我将告诉您更多详细的理论知识。因此,让我们继续进行实验。
3.最终用户应该做什么
3.1下载软件包
所以。您不会立即创建自己的东西,但是首先您想使用从All-Hardware网站获取的现成示例。您需要什么来构建和运行它?首先,您需要下载特定电路板的库。使用开源解决方案时,我已经遇到了一个事实,那就是下载一个项目还不够。我们仍然需要安装100,500个工具并下载100,500个第三方库。在这里,您只需要下载并解压缩一个文件即可。是的,他的身材巨大。但是内容很棒。所以。我们需要STM32 CubeMX的软件包。现在,直接链接到其存储库的外观如下所示:
www.st.com/en/development-tools/stm32cubemx.html#tools-software
应该下载哪个程序包如下表所示。
工资 | 包 |
---|---|
STM32F429I发现 | STM32CubeF4 |
STM32F469I发现 | STM32CubeF4 |
STM32G474RE DPOW1发现(无屏幕) | STM32CubeG4 |
STM32F746G发现 | STM32CubeF7 |
STM32H747I发现 | STM32CubeH7 |
3.2复制项目并开始工作
包的结构在内部是相同的,因此,我们进入具有以下层次结构的目录:
<包名称> \ Projects \ <板名> \ Applications \ STemWin。
复制带有示例的目录。我们得到这样的东西:
目录的位置很重要,因为库的路径是以相对格式编写的。如果由于缺少大量文件而无法构建项目,则您无法猜测其在目录层次结构中的位置。我们进入目录,为其中一种开发环境选择项目变体,打开项目,然后使用它...说明结束!
3.3 STM32G474RE DPOW1发现板的功能
该开发板没有屏幕,这意味着专有包装中没有STemWin目录。因此,应将项目放在下一个级别:
\ STM32Cube_FW_G4_V1.3.0 \项目\ B-G474E-DPOW1 \示例\ UART
4.如何创建项目
组装示例非常容易。现在让我们看一下这些示例是如何完成的。对于那些除了在屏幕上显示文本以外,还想做其他事情的人来说,此信息可能有用。好吧,几个月后有必要介绍下一块板,而我已经忘记了所有内容,我自己将打开本文以刷新我记忆中的说明。
4.1添加和初始化UART
可以预期,典型的STemWin Hello World示例缺少UART。必须添加它。看来我们采用了示例代码并将其添加。life,生活更加复杂。在工作功能中,我们采用这种方式。但是在初始化过程中有一些细微差别。首先,不同的原型开发板将不同的端口连接到内置在JTAG适配器中的USB-UART适配器。连接了哪一个,您需要查看电路板的描述。通常,当然是USART1,但最好再次检查。
此外,随着控制器的发展,各种有用的功能被添加到UART设备这一事实引入了多样性。还需要在新板的HAL中对其进行初始化。特别是使用FIFO。
因此,我建议以下典型步骤:
- 添加端口句柄。
- 添加UART硬件初始化。
- 添加UART引脚的初始化。
- 添加UART中断处理程序。
有了手柄,一切都变得清晰而通用。它看起来像这样:
UART_HandleTypeDef huart1;
在其目录\ <Package Name> \ Projects \ <Board Name> \ Examples \ UART的示例中查看其余内容。
例如,项目\ STM32Cube_FW_H7_V1.8.0 \项目\ STM32H747I-DISCO \示例\ UART \ UART_WakeUpFromStopUsingFIFO。
在main()函数中,有一个UART本身的初始化(您只需要确保速度为9600,否则就输入)。与
分支的连接在另一个文件中的void函数HAL_UART_MspInit(UART_HandleTypeDef * huart)中进行配置。
我更喜欢将其全部放在一个函数中。由于时间有限,因此我没有考虑从这些资料中编译这样的函数,而是将其放在main.c文件中,并且不要忘记从main()函数中调用它。
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
GPIO_InitTypeDef GPIO_InitStruct;
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
// __HAL_RCC_LPUART1_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_EnableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
/* Enable the UART RX FIFO threshold interrupt */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXFT);
/* Enable the UART wakeup from stop mode interrupt */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_WUF);
/* USER CODE BEGIN USART3_Init 2 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/*##-2- Configure peripheral GPIO ##########################################*/
/* UART TX GPIO pin configuration */
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* UART RX GPIO pin configuration */
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* NVIC for USART */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE END USART1_Init 2 */
}
, , JTAG. . — , JTAG , . .好了,中断很简单。我们在示例中正在寻找带有_it.c后缀的文件,并将UART行传输到带有我们项目的_it后缀的文件。
在这种情况下,从文件
\ STM32Cube_FW_H7_V1.8.0 \项目\ STM32H747I-DISCO \示例\ UART \ UART_WakeUpFromStopUsingFIFO \ CM7 \ Src \ stm32h7xx_it.c
到文件
\ STM32Cube_FW_H7_V1_V1.8.0。 \ STemWin \ Demo_H747 \ CM7 \ Core \ Src \ stm32h7xx_it.c
传输片段:
extern UART_HandleTypeDef huart1;
…
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
所有。
4.2编辑主要工作功能
有了主要的工作功能,从原理上讲,一切都变得更加容易。尽管不是原则上的,特别是不是全部,但我们将在下面讨论。现在,让我们看看在主函数末尾调用了哪个函数。在这种情况下,这些是:
MainTask();
这意味着主要工作已完成。我们只是将其主体替换为我们站点上的典型示例。
4.3接下来是什么?
此外-您可以拖动其他设备的示例,就像我们对UART设备所做的那样。的确,对此设备的操作进行控制超出了本文的范围。如您所见,WEB页上的典型控件仅用于图像和一个UART。其余的转发难度更高。不过,我不禁要告诉您,有一次晚上我被指示如何基于STM32G474RE DPOW1 Discovery板制作一个摩尔斯电码发生器。在陌生的板上从零开始发展的一个晚上不会使您阅读数百页的文档。如果该项目完成了几个世纪,我将开始向管理层证明这是不对的,应该仔细研究所有内容。但是该项目也有一个短期的生命周期。因此,我决定走出例子的道路。
因此,对于摩尔斯电码,您需要一个频率为1 KHz的正弦波……以通常的手部运动,解压文件en.stm32cubeg4_v1-3-0.zip并检查目录D:\ tmp \ STM32Cube_FW_G4_V1.3.0 \ Projects \ B-G474E-DPOW1 \示例...那里没有什么好用的找不到...
DAC目录中没有任何有用的东西。
结束了吗?并不是的。让我们看看其他具有相同晶体的板(尽管包装不同)的示例。这就是我们为Nucleo板所发现的美丽!
美丽之处在于main.c文件中有一个用于生成正弦的表:
/* Sine wave values for a complete symbol */
uint16_t sinewave[60] = {
0x07ff,0x08cb,0x0994,0x0a5a,0x0b18,0x0bce,0x0c79,0x0d18,0x0da8,0x0e29,0x0e98,0x0ef4,0x0f3e,0x0f72,0x0f92,0x0f9d,
0x0f92,0x0f72,0x0f3e,0x0ef4,0x0e98,0x0e29,0x0da8,0x0d18,0x0c79,0x0bce,0x0b18,0x0a5a,0x0994,0x08cb,0x07ff,0x0733,
0x066a,0x05a4,0x04e6,0x0430,0x0385,0x02e6,0x0256,0x01d5,0x0166,0x010a,0x00c0,0x008c,0x006c,0x0061,0x006c,0x008c,
0x00c0,0x010a,0x0166,0x01d5,0x0256,0x02e6,0x0385,0x0430,0x04e6,0x05a4,0x066a,0x0733};
我们检查并确保是。本示例在DAC输出处生成正弦或三角形。而且只有1 KHz的频率。太好了!由于时间有限,我什至不花任何时间去阅读任何理论。我只是通过快速查看代码来确保所有构成都在硬件级别进行。之后,在项目中,我将控制器替换为所需板中的控制器,组装,填充,启动控制器,然后沿支脚运行示波器探头后,发现上面有正弦的控制器。然后我将其连接到扬声器的输入端,将正弦或三角形的生成替换为正弦或静音的生成(是的,我仅从零开始制作了另一个表格)...嗯,用莫尔斯电码编写应用部分就像给梨加壳一样容易。我想起了我的青年时期,军事部门,巴甫洛夫上校...
通常,“查找示例,将其插入代码中”技术非常有效。而“构建典型示例-下载巨大的库”的方法对此有所帮助,因为所有这些带有商标的示例都是其中的一部分。
5.统一问题
我的一位同事喜欢引用以下哲学观点:
“理论上,理论与实践之间没有区别。实际上是这样。”
当使用一堆不同的STM32时,我经常记得它。我已经说过不可避免的不同UART,但是似乎统一的StemWin应该不会给您带来任何惊喜...已展示!
5.1 STM32H747
绘制了Hello World标题。但是,当我传输工作代码时,我会看到一个蓝屏。只是我们首先绘制红色屏幕一秒钟,然后绘制绿色屏幕一秒钟,然后绘制蓝色屏幕一秒钟,然后开始工作。如果在初始化之后甚至在任何图形之前立即放置一个断点,则在触发该断点时,屏幕上将显示最后一次开始的计时器读数。然后它们被同一蓝屏覆盖。什么?
我一秒钟删除了三种颜色的输出,然后立即添加了工作。它可以工作,但是很快就会永久冻结。渐渐地,我找出了37毫秒后会冻结的内容。这是什么样的魔术时间?一毫秒是清楚的。系统刻度。但是37。是的,至少有些东西是圆的,关闭的……
多长时间或短时间,但是我发现一切都显示在中断处理程序中HAL_DSI_IRQHandler(DSI_HandleTypeDef * hdsi)。它将被调用,显示所有内容,然后终止其调用。一切都在缓冲区中形成,但是没有出现在屏幕上。更准确地说,在程序生命周期中,所有内容都会两次出现在屏幕上。初始化时(与过去相同的工件)和37 ms之后。介于两者之间的所有内容,没人会看到。
在头脑上-您需要研究文档并弄清楚是什么。但是实际上-完成任务的时间不是很多,而是很少。很明显,必须引起中断,但是如何处理?老实说,我正在尝试调用GUI_Exec(),尽管无论如何都在这里调用了GUI_Delay()……无济于事。
这个例子已经死了。相反,这很有趣。Hello World的输出与前37ms一样。然后-一个死的例子。好的,我将以同一目录中的动画为例。他工作。渐渐地,我弄清楚了要使我们的示例正常工作需要删除的内容……这就是我们的典型代码如下所示:
GUI_SetBkColor(GUI_RED);
GUI_Clear();
GUI_Delay(1000);
GUI_SetBkColor(GUI_GREEN);
GUI_Clear();
GUI_Delay(1000);
GUI_SetBkColor(GUI_BLUE);
GUI_Clear();
GUI_Delay(1000);
好吧,这是合乎逻辑的!它可以工作!..在其他板上...但是经过这样的编辑,它在某种程度上在H747上移动了:
相同的文字。
GUI_MULTIBUF_Begin();
GUI_SetBkColor(GUI_RED);
GUI_Clear();
GUI_MULTIBUF_End();
GUI_Delay(1000);
GUI_MULTIBUF_Begin();
GUI_SetBkColor(GUI_GREEN);
GUI_Clear();
GUI_MULTIBUF_End();
GUI_Delay(1000);
GUI_MULTIBUF_Begin();
GUI_SetBkColor(GUI_BLUE);
GUI_Clear();
GUI_MULTIBUF_End();
GUI_Delay(1000);
但总的来说,它是可行的,特别是-红色和绿色的屏幕持续一秒钟,蓝色的屏幕闪烁并立即出现工作屏幕。经过少量的实验,结果发现一切都以这种形式开始完全起作用:
GUI_MULTIBUF_Begin();
GUI_SetBkColor(GUI_RED);
GUI_Clear();
GUI_MULTIBUF_End();
GUI_MULTIBUF_Begin();
GUI_MULTIBUF_End();
GUI_MULTIBUF_Begin();
GUI_MULTIBUF_End();
GUI_Delay(1000);
GUI_MULTIBUF_Begin();
GUI_SetBkColor(GUI_GREEN);
GUI_Clear();
GUI_MULTIBUF_End();
GUI_MULTIBUF_Begin();
GUI_MULTIBUF_End();
GUI_MULTIBUF_Begin();
GUI_MULTIBUF_End();
GUI_Delay(1000);
GUI_MULTIBUF_Begin();
GUI_SetBkColor(GUI_BLUE);
GUI_Clear();
GUI_MULTIBUF_End();
GUI_MULTIBUF_Begin();
GUI_MULTIBUF_End();
GUI_MULTIBUF_Begin();
GUI_MULTIBUF_End();
GUI_Delay(1000);
统一太重要了……有人怀疑这种情况应归咎于:
/* Define the number of buffers to use (minimum 1) */
#define NUM_BUFFERS 3
但是对它进行校正的实验并没有给出理想的结果,并且时间不允许研究细节。也许评论中的某人会告诉您如何正确执行操作,本节介绍了如何在有限的时间内进行开发。同时,代码仍然处于如此糟糕的状态。幸运的是,这不是教程,而只是工作代码的示例。
5.2 STM32F429
这块板太旧了,这可能有什么问题呢?萌芽!在旧的董事会上,已经存在但长期停止工作的产物出来了。运行Hello World示例,然后查看:
图像相对于典型图像旋转了90度。有什么会更容易?回到2016年,当我将MZ3D 3D打印机的固件从Arduinka拖到STM32F429时,我亲自通过简单的设置打开了图片。来吧。事情怎么样了?这是设置!
#define LCD_SWAP_XY 1
#define LCD_MIRROR_Y 1
尝试更改它们无济于事。检查它们的使用位置……但是无处可去!他们刚刚宣布。我怀疑在实施DMA2D时不再对它们进行处理,但是我不会保证。但是,具有相同的功能。这是来自数十个论坛的建议。我在2016年使用了此功能:
GUI_SetOrientation(GUI_SWAP_XY)
有些仍然添加一个镜像常量。该功能在2020年无法使用!不起作用,就是这样!而且该库以对象形式提供,为什么它不起作用-没有人知道。
好的,我仍然了解屏幕。我转到文件\ STM32F429-DISC1 \驱动程序\ BSP \组件\ ili9341 \ ili9341.c(是的,它具有写保护,但很容易删除)。图形芯片在那里配置。我们改变。
相同的文字。
ili9341_WriteReg(LCD_MAC);
ili9341_WriteData(0xC8);
上
相同的文字。
ili9341_WriteReg(LCD_MAC);
ili9341_WriteData(0x48|0x20);
图像当然会旋转...但是缓冲区的配置显然有些不正确:
这些是尺寸:
#define XSIZE_PHYS 240
#define YSIZE_PHYS 320
也不会以任何方式影响工作。同样适用于此站点:
#define ILI9341_LCD_PIXEL_WIDTH ((uint16_t)240)
#define ILI9341_LCD_PIXEL_HEIGHT ((uint16_t)320)
但是该站点更有趣:
if (LCD_GetSwapXYEx(0)) {
LCD_SetSizeEx (0, YSIZE_PHYS, XSIZE_PHYS);
LCD_SetVSizeEx(0, YSIZE_PHYS * NUM_VSCREENS, XSIZE_PHYS);
} else {
LCD_SetSizeEx (0, XSIZE_PHYS, YSIZE_PHYS);
LCD_SetVSizeEx(0, XSIZE_PHYS, YSIZE_PHYS * NUM_VSCREENS);
}
我添加了否定的乐趣:
相同的文字。
if (!LCD_GetSwapXYEx(0)) {
LCD_SetSizeEx (0, YSIZE_PHYS, XSIZE_PHYS);
LCD_SetVSizeEx(0, YSIZE_PHYS * NUM_VSCREENS, XSIZE_PHYS);
} else {
LCD_SetSizeEx (0, XSIZE_PHYS, YSIZE_PHYS);
LCD_SetVSizeEx(0, XSIZE_PHYS, YSIZE_PHYS * NUM_VSCREENS);
}
我得到了这种美丽。
在其他实验中,我得到了相似但不完全相同的美丽,我不再记得了……总之,我们都得出了同样令人失望的结论。我们必须坐下来整理一下……但是没有时间分配给审判。该怎么办?幸运的是,我设法从Google那里得到了这个问题的答案。结果,演示应用程序中的典型代码如下所示:
GUI_DispStringHCenterAt("www.all-hw.com", xSize / 2, 20);
对于F429,必须将其简化为以下形式:
GUI_RECT Rect = {20-10, 0, 20+10, xSize};
GUI_DispStringInRectEx("www.all-hw.com", &Rect,
GUI_TA_HCENTER | GUI_TA_VCENTER,
20, GUI_ROTATE_CCW);
使用输出旋转文本的功能。是的,为此,您必须添加“矩形”实体。好吧,你能做什么。并且计数器的显示不是通过打印数字的功能来进行,而是首先通过形成字符串,然后才以旋转形式显示来进行。但是除此之外,几乎所有东西都普遍存在。相反,甚至有可能以这种方式在各处显示文本,而不是在各处显示旋转标记。但是在我看来,这已经是一个变态。
六,结论
我们从开发人员的角度以及从用户的角度研究了为各种STM32板开发典型程序的方法,他们只是为他的板下载了一个演示应用程序,只是想在不考虑物理的情况下进行构建。我们还查看了一些示例,这些示例表明,不幸的是,代码统一的程度很高,但没有达到100%。可以通过全硬件服务与不同的板交替工作来检查所获得的知识,而无需花钱购买。
本文介绍了如何比较肤浅地解决问题。这使您可以快速掌握特定的板。但是,当然,与她打交道的时间越长,您就会获得越深的知识。在解决实际问题时,这非常重要,因为复杂的程序(导致作者不了解过程的物理原理)是导致算法错误的途径。我希望每个人都足够深入地掌握一切。是的,使用“所有硬件”服务中的硬件。