前言

STM32系列单片机作为我使用时间最长,参与比赛最多的单片机,陪伴我通过科协考核,拿过电赛国奖,但一直以来我对于它的使用都比较功利性,着重于功能的实现,忽视了它很多的优秀素质,如我在H7系列中使用过USB虚拟串口,但最近我才知道连基本的STM32F1C8T6都有usb外设,可使用HID模拟键盘、鼠标。
本博客记录一些STM32中的优化方法,尝试最大化发挥STM32的效用。

STM32时钟系统

以STM32F4系列为例,关于时钟的配置在system_stm32f4xx.c中,上电后startup汇编调用SystemInit(void)进而使用SetSysClock(void)配置系统时钟。
(stm32f103也有相关的设置,具体位置可去.h文件中go to define)
在316行左右可配置锁相环,类似下图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/************************* PLL Parameters *************************************/
#if defined (STM32F40_41xxx) || defined (STM32F427_437xx) || defined (STM32F429_439xx) || defined (STM32F401xx)
/* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */
#define PLL_M 8
#else /* STM32F411xE */
#if defined (USE_HSE_BYPASS)
#define PLL_M 8
#else /* STM32F411xE */
#define PLL_M 16
#endif /* USE_HSE_BYPASS */
#endif /* STM32F40_41xxx || STM32F427_437xx || STM32F429_439xx || STM32F401xx */

/* USB OTG FS, SDIO and RNG Clock = PLL_VCO / PLLQ */
#define PLL_Q 7

#if defined (STM32F40_41xxx)
#define PLL_N 336
/* SYSCLK = PLL_VCO / PLL_P */
#define PLL_P 2
#endif /* STM32F40_41xxx */

HSE->PPL->SysClock->APHB->外设……
修改锁相环可实现超频。

使用片外FLASH

使用片外flash扩展作为程序空间,如STM32H750VBT6,片内flash仅有128k。

关于程序载入片外FLASH的方法

  1. 使用下载器+探针直接烧录到SPI储存芯片中
  2. 使用SWD调试器,将片内FLASH和片外FLASH一并下载
    本教程使用第二种方法。

    引导程序

    主要完成下列操作:
    1. 配置系统时钟
    2. 初始化QSPI和W25Q64
    3. 将W25Q64设置为内存映射模式
    4. 程序跳转到W25Q64

      制作下载算法

      下载算法的作用是使得程序放在正确的地址上,在本例中使用下载算法将片内FLASH和片外FLASH一并下载

“多余的”下载方式

我一直觉得使用调试器如Jlink,STlink下载程序虽然高效,但不够优雅,特别是有时需要带回寝室开发功能的时候,显得累赘,STlink往往还要使用杜邦线接线。下面是仅需一根Type-c线便可完成调试下载的方案。
但需注意的是:在f4系列中,使用非调试器下载程序疑似会触发其保护机制,导致下载后程序无法执行,需进行一次全片擦除.F1系列则没遇到过这种问题(可能f1的安全等级比较低吧)

  1. UART1下载
  2. DFU下载

HID模块

在CubeMX中配置

虚拟串口

以F4为例子,f1并没有虚拟串口

CubeMX

CubeMX是用来为不同的ide生成初始化代码的,如Keil,CubeIDE。

HAl库基本使用

串口

  1. 在魔术棒中勾选 USE MicroLIB
  2. stm32f4xx_hal.c中添加:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #include <stdio.h>
    extern UART_HandleTypeDef huart1; //声明串口
    /**
    * 函数功能: 重定向c库函数printf到DEBUG_USARTx
    * 输入参数: 无
    * 返 回 值: 无
    * 说 明:无
    */
    int fputc(int ch, FILE *f)
    {
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
    return ch;
    }

    /**
    * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
    * 输入参数: 无
    * 返 回 值: 无
    * 说 明:无
    */
    int fgetc(FILE *f)
    {
    uint8_t ch = 0;
    HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
    return ch;
    }

移植FreeRTOS

教程示例

USB外设

以STM32F407VET6为例。
注意:外设时钟必须为48MHz或其整数倍,这是USB外设的要求。

LVGL

  1. Github-LVGL
  2. 百问网-LVGL

    移植

    关于移植部分我推荐看正点原子的教程

运行Demo

  1. 添加\Middlewares\LVGL\GUI_APP\demos和其下你想跑的例程文件夹至工程设置的Include Paths中
  2. 新建工程分组app,添加你想跑的例程文件夹其下的所有.c文件(stress不用引入asset)
  3. 在lv_conf.h文件中使能相应的Demo
  4. 在main文件中包含对应例程的.h文件与启动函数。

优化显示帧率

移植很简单,但关于优化的资料比较少,笔者总结LVGL的优化分为3步

  1. 更改LVGL缓冲区,使用双缓冲,加大缓冲区
  2. 更改屏幕刷新周期#define LV_DISP_DEF_REFR_PERIOD 10,
  3. 优化底层刷屏,由软件改为硬件,而后加上DMA,注意加入DMA后需要使用16bit-swap
    使用双缓冲DMA+SPI的源码:笔者写的例程

实体按键控制

alt text

alt text

所谓的组就是用来连接输入与控件间的桥梁,如键盘与List,键盘的按键可以映射成上下、确认等。
笔者以按键作为例子:
它比较特殊,按照LVGL的处理逻辑,按键是被映射成屏幕上的一个点,按一下按键,相当于在屏幕对应位置点了一下,因此它不需要初始化组。
使用步骤:

  1. 配置板子的按键接口,返回是否按下,例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    //按键初始化函数
    void KEY_Init(void)
    {

    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA,GPIOE时钟

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    }

    u8 KEY_Scan(void)
    {
    if( SET == GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) )
    {
    /* 延迟消抖 */
    delay_ms(20);

    // while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == SET) // 等待按键松开
    // {

    // }

    /* 延迟消抖 */
    delay_ms(20);
    return 1;
    }
    return 0;
    }
  2. 在lv_port_indev_templ.c中配置中,更改define使能本组件,使能Button相关的驱动,失能无用的,如触摸编码器等。
  3. 在初始化驱动处,增加自己编写的按键接口初始化,修改按键所映射的点。
  4. 在main.c中包含在lv_port_indev_templ.h并初始化组件:
    1
    lv_port_indev_init();

    使用SquareLine Studio

    注意:除了配置长宽像素长度外,如果使用DMA传输数据,颜色深度需要使用16bit-swap以适配数据大小端,在lvgl工厂目录下新建一个UI文件夹,把squareline生成的丢进去,包含下头文件,主函数加个ui_init()就行,这个软件只是帮你做了些重复工作,具体的实现不能弃之不管,还是要go to define看一看的。