环境搭建

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

必要的配置:

// 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中进行编辑,编译,烧录,调试。

ST-Link烧录器管脚定义

使用 printf 进行串口打印

  1. 在要使用printf的文件包含头文件stdio.h
  2. usart.c文件中加入重定向,这里以串口1为例。

    /*****************************************************************************
     * 【功  能】 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。

代码实现:

/* 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中进行调用,毕竟那些注释看着还是比较乱。

但是避免不了要在生成的代码中写内容时,还是遵循官方的风格与规范比较好。

* 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)