STM32基础开发知识

环境搭建

  • 编译器:Keil5,AC5
  • 编辑器:VS Code,Embedded IDE,Cortex-Debug
  • 调试器:Openocd,Arm GNU Toolchain
  • 烧录器:ST-link V2

必要的配置:

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
33
34
// settings.json
{
"cortex-debug.armToolchainPath": "C:\\Program Files (x86)\\Arm GNU Toolchain arm-none-eabi\\12.3 Rel1\\bin",
"cortex-debug.openocdPath": "E:\\OpenOCD-20211118-0.11.0\\bin\\openocd.exe",
"cortex-debug.gdbPath": "C:\\Program Files (x86)\\Arm GNU Toolchain arm-none-eabi\\12.3 Rel1\\bin\\arm-none-eabi-gdb" ,
"showDevDebugOutput": "raw",
"cortex-debug.gdbPath.windows": "C:\\Program Files (x86)\\Arm GNU Toolchain arm-none-eabi\\12.3 rel1\\bin\\arm-none-eabi-gdb.exe",
"cortex-debug.stlinkPath": "C:\\ST\\STM32CubeIDE_1.8.0\\STM32CubeIDE\\plugins\\com.st.stm32cube.ide.mcu.externaltools.stlink-gdb-server.win32_2.0.100.202109301221\\tools\\bin\\ST-LINK_gdbserver.exe"
}

// launch.json
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Cortex Debug(STLink)",
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd",
"cwd": "${workspaceFolder}",
"executable": "${workspaceFolder}/build/${workspaceFolderBasename}/${workspaceFolderBasename}.elf",
"runToEntryPoint": "main",
"configFiles": [
"E:\\OpenOCD-20211118-0.11.0\\share\\openocd\\scripts\\interface\\stlink-v2.cfg",
"E:\\OpenOCD-20211118-0.11.0\\share\\openocd\\scripts\\target\\stm32f4x.cfg",
],
// "gdbPath":"C:\\Program Files (x86)\\Arm GNU Toolchain arm-none-eabi\\12.3 Rel1\\bin\\arm-none-eabi-gdb.exe",
"objdumpPath": "C:\\Program Files (x86)\\Arm GNU Toolchain arm-none-eabi\\12.3 rel1\\bin\\arm-none-eabi-objdump.exe",
}
]
}

使用STM32CUBEMX生成代码框架,在 VS Code 中进行编辑,编译,烧录,调试。

使用 printf 进行串口打印

  1. 在要使用printf的文件包含头文件stdio.h

  2. usart.c文件中加入重定向,这里以串口 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
    /*****************************************************************************
    * 【功 能】 printf函数重定向支持代码
    * 加入以下代码, 使用printf函数时, 不再需要选择use MicroLIB
    * 参 数:
    * 返回值:
    *****************************************************************************/

    #pragma import(__use_no_semihosting)
    struct __FILE
    {
    int handle;
    }; // 标准库需要的支持函数
    FILE __stdout; // FILE 在stdio.h文件
    void _sys_exit(int x) { x = x; } // 定义_sys_exit()以避免使用半主机模式

    int fputc(int ch, FILE *f) // 重定向fputc函数,使printf的输出,由fputc输出到UART, 这里使用串口1(USART1)
    {
    // if(xFlag.PrintfOK == 0) return 0; // 判断USART是否已配置,防止在配置前调用printf被卡死

    while ((USART1->SR & 0X40) == 0)
    ; // 等待上一次串口数据发送完成
    USART1->DR = (uint8_t)ch; // 写DR,串口1将发送数据
    return ch;
    }
  3. 进一步封装一个日志库,增加时间戳,文件名,行数,打印等级等功能。如果是在FreeRTOS下,还要考虑多个任务同时对串口 1 的使用互斥问题,这里有一个我封装好的日志库,sinlatansen/DBG: 一款线程安全的 FreeRTOS(cmsis_os2)的日志调试库,基于 STM32F4_HAL

避开 HAL_Delay

HAL 库这个延时函数,有 bug,建议自己实现延时函数,改用软件定时器实现。

这里我使用的 STM32F407VET6 主频 168MHz,其中我使用的 TIM6 所挂载的 AHB1 频率为 84MHz ,因此设置 PSC 为 83。

代码实现:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* delay.c */
#include "delay.h"

void Delay_ns(uint32_t ns)
{
uint32_t ticks =
(ns / 11.9) + 1; // 计算需要的时钟周期数,+1确保延时不会少于请求的ns

TIM6->CNT = 0; // 重置计数器
HAL_TIM_Base_Start(&htim6); // 启动定时器

while (TIM6->CNT < ticks)
; // 等待计数器达到目标值

HAL_TIM_Base_Stop(&htim6); // 停止定时器
}

void Delay_us(uint32_t us)
{
uint32_t ticks =
(84 * us) - 1; // 84时钟周期代表1微秒,-1确保延时不会少于请求的us

TIM6->CNT = 0; // 重置计数器
HAL_TIM_Base_Start(&htim6); // 启动定时器

while (TIM6->CNT < ticks)
; // 等待计数器达到目标值

HAL_TIM_Base_Stop(&htim6); // 停止定时器
}

// ms延时使用osDelay函数



/****************************************************************/

/* delay.h */
#ifndef DELAY_H_INCLUDED
#define DELAY_H_INCLUDED

#include "stm32f4xx_hal.h"
#include "tim.h"

void Delay_ns(uint32_t ns);
void Delay_us(uint32_t us);

#endif /* DELAY_H_INCLUDED */

STM32CUBEMX 沙箱定义

对于 CUBEMX 生成的代码,会有一套规范,通过注释提示了用户代码写在哪里,否则可能在下一次生成代码误删用户代码。

一般我倾向于把用户代码写在自己添加的 .c 文件,在main.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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
/* Private includes 私有文件包含沙箱----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
这里存放定义的类型
/* USER CODE END Includes */
/* Private typedef 私有类型定义沙箱-----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
这里存放你的定义的结构体,枚举体,共用体等
/* USER CODE END PTD */
/* Private define 私有宏沙箱------------------------------------------------------------*/
/* USER CODE BEGIN PD */
这里存放你定义的宏
/* USER CODE END PD */
/* Private macro 私有宏沙箱-------------------------------------------------------------*/
/* USER CODE BEGIN PM */
这里存放你定义的宏
/* USER CODE END PM */
/* Private variables 私有变量沙箱---------------------------------------------------------*/
/* USER CODE BEGIN PV */
这里存放你定义的变量
/* USER CODE END PV */
/* Private function prototypes 私有函数原型沙箱-----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
这里存放你的函数原型
/* USER CODE END PFP */
/* Private user code私有程序沙箱 ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();//整个HAL库初始化
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();//系统时钟初始化
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();//GPIO初始化
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}

CUBEMX 生成的工程在 keil5 中编译报错

【转】Keil ARM 开发 error L6236E 错误解决 - 壹点灵异 - 博客园 (cnblogs.com)

让 CUBEMX 生成 UTF-8 文件,避免中文乱码

添加系统环境变量:

  • 变量名:JAVA_TOOL_OPTIONS
  • 值 :-Dfile.encoding=UTF-8

ASCIi 艺术字体生成

[Text to ASCII Art Generator (TAAG) — 文本到 ASCII 艺术生成器 (TAAG) (patorjk.com)](http://patorjk.com/software/taag/#p=display&f=ANSI Shadow&t=Hello BUAA!)


STM32基础开发知识
http://sinlatansen.github.io/2024/04/15/STM32基础开发知识/
作者
fugu
发布于
2024年4月15日
许可协议