前言
本文只负责指导一些问题,学单片机MCU还是以下列视频为主:
- 正点原子HAL库视频:
- 正点原子电机专项视频:
- 正点原子FreeRTOS:
-
大疆开发板C板开发教程:大疆开发板C型嵌入式软件教程文档.pdf
-
【【中科大RM电控合集】手把手Keil+STM32CubeMX+VsCode环境配置-哔哩哔哩】 https://b23.tv/5mwveRt
-
【ARM与STM32啥关系?不来了解一下嘛?-哔哩哔哩】 https://b23.tv/VvcYgUD
stm32单片机
单片机介绍
①什么是单片机?
单片机又称单片微控制器,它不是完成某一个逻辑功能的芯片,而是把一个计算机系统集成到一个芯片上。相当于一个微型的计算机,和计算机相比,单片机只缺少了I/O设备。概括的讲:一块芯片就成了一台计算机。它的体积小、质量轻、价格便宜、为学习、应用和开发提供了便利条件。同时,学习使用单片机也是了解计算机原理与结构的最佳选择。

②单片机的应用?
-
物联网(※)
-
医用设备
-
工业控制
-
计算机网络通信(※)
-
… …
③stm32单片机组成部分
-
CPU(中央处理器)
- 芯片框图
-
处理器内核
-
介绍与作用 : CPU所有的计算、接收/存储命令、处理数据全部由内核执行。
-
指令集分类 : ARM架构、X86架构、LoongArch架构,RISC-V架构
-
ARM架构指令集
-
应用:广泛应用于移动行业(手机、平板、工控机等)等需要很强的能耗比的场景中
-
架构分类: ARM32、AArch64(ARM64)等
-
内核分类:ARM Cortex-X(手机) 、ARM Cortex-A(手机) 、 ARM Cortex-R(嵌入式) 、 ARM Cortex-M(嵌入式)
-
了解CPU Soc和CPU内核的区别
小米玄戒o1,华为麒麟9000,意法半导体STM32F407VET6这三个芯片都是基于ARM架构。他们的CPU的核心的前端设计都是由英国ARM公司设计好的,ARM的内核决定了这个CPU的性能,总线,浮点运算器等等。 而小米,华为,意法半导体只对CPU核心进行后端设计,对CPU性能等影响不会太大,**CPU绝大部分特征都是由ARM的前端设计决定的**。 **所以,我们在使用F407IG,F407VE的时候,因为他们的CPU内核都是Cortex-M4,所以CPU特征都一样,所以代码也几乎都一样的。**
-
- X86架构指令集
1. 应用:广泛应用于电脑、软路由、工控机等需要高性能计算的场景中 2. 分类:X86、AMD64(X86\_64)- LoongArch架构
1. 应用:政府单位采购、军工采购、个人电脑等 2. 分类:龙架构32位、龙架构64位- RISC-V架构
-
-
-
GPIO(通用输入/输出端口General-Purpose IO ports)
-
定义:CPU与外部进行信息交换的端口(Input输入、Output输出)
-
理解:CPU上的“金手指”(注意和电路板PCB上的引脚区分)
-
所在位置:通常把CPU焊在电路板PCB上,一般地,GPIO端口在电路板上经过某些电路最终被引出来成为电路板引脚
-
数量:STM32F407系列具有上百GPIO端口,由于数量过多,将其分为7个组(A,B,C… …),每组共16个IO口(0,1,2,3…15)
-
命名:P+GPIO组+IO口号(比如PA2,PB6等)
-
应用
-
普通输出IO口:输出高电平,或低电平
-
普通输入IO口:读取外部高低电平
-
复用IO口:可变为通信IO口与电脑、电机、蓝牙模块等通信(定时器PWM、串口UART、CAN通信、SWD调试通信、晶振IO口)
④原理图
-
组成部分:
-
元器件(包括元器件端口)
-
导线
-
网络标号
-
电源符号
-
等… …
-
-
-
⑤芯片手册
- 作用:查询各种芯片信息(比如CPU频率,IO定义,时钟树等等)
软件介绍
-
STM32库
-
各种开发方式:寄存器,标准库,HAL库,LL库
-
寄存器功能简单了解:寄存器就是一个离CPU内核更近的存储结构,所以与CPU内核交换数据比内存(RAM)更快,每个寄存器都有不同的功能,在寄存器里存不同的值,CPU读取后都会实现对应的不同功能。
-
库:库是源文件+头文件。stm32的库是由汇编语言、C语言混合编译而成(HAL库、LL库兼容C++)。现有标准库、HAL库、LL库(标准库已淘汰,咱们实验室使用HAL库和LL库)
-
各种开发方式的优缺点:
-
寄存器:这种开发方式硬件执行效率高,但由于STM32寄存器过于多,用寄存器写可读性差,且麻烦繁琐,故不建议全用寄存器写,在某些场合下可以偶尔使用。
-
标准库:太老了,现在已经淘汰,该库使用汇编+C语言进行开发,代码可读性很高,但是由于初期对标准库设计有些问题以及一些专利上的问题,导致会出一些问题,且时钟配置过于麻烦繁琐,所以咱们于2021级开始就不再使用标准库。
-
HAL库、LL库(力推):ARM公司与ST意法半导体力推的库,符合ARM CMSIS标准,该标准是当今嵌入式开发者都需要遵循的一个标准。该库由汇编语言+C语言进行开发,且兼容C++(头文件中有extern "C"条件编译),使用C++的OOP(面向对象)进行开发要方便一万倍。HAL库和LL库仍然被ST公司维护中,其解决掉了标准库的各种确定,比如硬件IIC无法正常使用,时钟配置及其容易。ARM CMSIS标准介绍:CMSIS – Arm®
-
-
-
-
开发软件介绍:
-
搭建环境教程:STM32 Windows开发环境软件安装教程
-
ARM Keil MDK
-
介绍:可进行开发各种基于ARM Cortex系列内核开发的CPU的单片机(stm32),也可以开发其他类型单片机
-
作用:进行单片机的代码编辑(edit)、编译(compile)、构建(build)以及下载(download)与调试(debug)。
-
版本选择:
-
MDK 5.3及以上:建议使用,但需要自己装ARMCC编译器。
-
MDK 6及以上:详见下方的VScode
-
折中方案(Keil MDK5 + VScode +Keil Assistant): 详见下方的VScode
-
-
-
STM32 CubeMX
-
介绍:用图形界面生成STM32 HAL库部分驱动层代码的软件,由ST公司开发,仅支持STM32系列单片机。
-
作用:后期开发使用,进行STM32单片机的驱动层的基本配置(比如时钟树、GPIO、各种外设通信、中断、嵌入式实时操作系统等的配置),前期新手禁止使用STM32 CubeMX这款软件**,不然就和没学一样。前期新手只可以用该软件生成时钟函数,其他的一切操作概不允许,可以了解一下,但不准作为主力开发工具。** (大概熟练掌握CAN通信,DMA等就可以使用该软件了)
-
-
STM32 CubeIDE(选用,没有需求就不要去使用)
- 介绍:跨平台的STM32单片机开发平台,仅支持STM32系列单片机,且只能用ARM-GCC编译器(该编译器远远比不上MDK5和MDK6上的ARM-Clang编译器,甚至部分性能也比不上ARM-CC编译器)
-
VScode
-
介绍:由微软开发的,开源的,世界第一的万能编辑器
-
作用:只是个编辑器(类似记事本),不自带编译器(比如GCC、MSVC,ARMCC(AC5),ARMCLANG(AC6),ARM-GCC),需要自己配置环境才能够正常开发C/C++,CMake,Python,ROS2,单片机等。
-
优点:①图形界面非常优美,②可跨平台,在Windows,Linux,MacOS上均能使用,③有非常多好用的插件。
-
缺点:①VSCode是使用Electron开发的,约等于塞了一个Google Chromium浏览器内核,非常占内存。②且环境难配置,但是这是必须要学的。
-
插件:
- Keil Studio Pack(Keil MDK 6,截止2024年1月2日,推荐熟练使用keil5后再使用) MDK6已经基本完善了,可以使用,但是不建议使用。MDK6学习成本比较高,对新手不友好,且MDK5还在更新维护,所以建议使用MDK5.3及以上。但MDK 6基于MS VScode编辑器开发,实现了跨平台,可在Windows,Linux,MacOS上进行开发,且界面非常优美,所以未来可期。ARM Keil MDK6使用教程
- Keil Assistant(后期开发建议使用,可以代替Keil MDK 5.3 完成代码编译(edit),但是编译,构建,下载,调试仍然建议在Keil MDK 5.3 上使用)在Windows上用MDK5软件配合Vscode的keil assistant插件进行开发。【VS Code开发stm32和51单片机的教程,vscode代替Keil-哔哩哔哩】 https://www.bilibili.com/video/BV18e4y1H7xX
-
-
时钟树
①使用CubeMX配置时钟的步骤
-
时钟配置介绍:这是每一个工程都需要做的事情,给予CPU正常的心跳。
-
作用:给予CPU正常的心跳,并且给予各个外设的心跳,让CPU和其各个外设正常工作。(比如延时函数的准确度,定时器PWM波形的准确度)
-
STM32F1系列CPU时钟框图:
-
配置需要注意的事项:
-
注意电路板上HSE的真实晶振频率,填高了会导致超频,会出现比较严重的问题
-
配置时建议用CubeMX配置时钟函数,然后复制到正点原子模板工程中(因为手撸时钟函数太难了)
-
CubeMX参考文档:大疆开发板C型嵌入式软件教程文档.pdf
-
-
配置步骤(这里只点出几个要注意的点,详细步骤请看大疆C板开发文档):
-
打开大疆C板开发文档
-
找到目录,点击0.4.2
-
按照0.4.2的步骤开始操作(每一步必须都得做,特别是Debug选Serial Wire,不选的话该工程代码会让板子假变砖)
- 需要注意板子型号,大疆板子是stm32f407igh6,咱们需要根据咱们实际的板子型号进行选择
- 配置时钟树时,需要注意HSE的时钟频率,按照实际原理图上的HSE频率来配置
- 代码路径必须全是英文,并且不能有连续两个空格,建议直接不要空格,单词之间用下划线(不可以放在桌面上)
- 解释
-
打开CubeMX生成的MDK 5工程
- 再复制一个并打开正点原子的模板工程
- 在CubeMX HAL库工程中的main.c中找到时钟函数void SystemClock_Config(void)的定义
- 复制整个void SystemClock_Config(void)函数的定义
- 然后打开正点原子的工程,在main函数中找到sys_stm32_clock_init(RCC_PLL_MUL9)函数,右键该函数,并go to definition of "sys_stm32_clock_init"找到这个函数的定义。
- 如果弹出下方的问题,请按照这个框框中的提示来解决,说的很明白。(如果看不懂英语,就去百度搜,锻炼下搜索能力)

-
go to definition of "sys_stm32_clock_init"完后找到这个函数的定义,删掉整个函数,并把刚才复制的CubeMX HAL库里的时钟函数复制到这里。并将Error_Handler();直接删掉,或者替换成while(1);
-
找到sys_stm32_clock_init函数定义
- 框选后删掉
- 把复制的CubeMX HAL库里的时钟函数复制到这里。
- 用while(1);替换掉Error_Handler();或直接删掉。
- 找到void SystemClock_Config(void)函数所在的源文件sys.c对应的头文件sys.h
- 找到sys_stm32_clock_init(uint32_t plln)函数,删掉,替换成void SystemClock_Config(void)的声明。
- 回到主函数,找到sys_stm32_clock_init(RCC_PLL_MUL9);函数,删掉,并调用咱们新的时钟函数
- 修改HSE_VALUE
- 随便找个地方输入HSE_VALUE并go to definition(go to definition完毕后,就可以删掉这个自己写的HSE_VALUE)
- 修改HSE_VALUE的值(如果是8MHz就写8000000U,如果是12MHz就写12000000U)
通过看原理图可知,该板子为8MHz。(具体填多少,看你板子HSE的原理图,对应OSCIN和OSCOUT这俩IO口)
-
删掉原来用来go to definition才写的HSE_VALUE
-
删掉多余的代码
-
第9行的delay_init的入口参数具体填什么值,先查看一下他的定义
-
查看delay_init的定义,得知其入口参数为sysclk(系统时钟)
- 查看CubeMX的时钟树框图,得知SYSCLK的值为72MHz
- 把delay_init的值改为时钟树中的SYSCLK的值
- 然后编译所有文件

- 零错误零警告即配置成功,有错误有警告请自行百度、谷歌
-
②查询某个外设时钟频率的方法(拿定时器来举例子)
- 打开tim.c
- 找到Msp初始化弱函数(看TIM的基句柄得知是哪个TIMx)
- 查找__HAL_RCC_XXX_CLK_ENABLE()的定义
- 根据函数定义,可以看出TIM1挂载在APB2上
- 查询时钟树,找APB2 Timer Clock可得TIM1的TCLK是168MHz
- 所以得知,TIM1的TCLK频率为168MHz
stm32程序组成
基本介绍(主函数等)
-
工程构成:stm32工程是由C语言和汇编语言的库组成的工程,所以有主函数,符合C/C++语言的结构。
-
程序运行顺序:除了预编译等,程序从主函数开始运行,而且非常符合C/C++运行顺序,从主函数开始会逐行运行代码,然后会进入死循环。
-
主函数内必须的组成部分:死循环[while(true)或者for(;;)],因为单片机要一直运行下去,所以有个死循环。
-
HAL库 与 用户自定义库
-
库:
-
.h文件声明函数
-
.c/.cpp文件定义函数
-
.c/.cpp文件调用函数
-
-
中断服务函数的介绍
-
特殊函数(中断服务函数):中断服务函数是由汇编定义的,与芯片硬件更紧密,是由芯片中断事件所触发,并不满足常规C/C++调用顺序。
-
中断服务函数的调用方式:由中断事件所触发。一旦满足某个中断事件,就立马从正在运行的地方切换到中断服务函数里开始运行,然后运行完中断服务函数后,再返回刚才运行的地方接着运行。
-
中断事件:比如说第X条线的外部中断事件、systick滴答定时器中断(普通延时函数的实现方式)、UART接收中断事件、UART发送中断事件、TIM定时器溢出更新中断、TIM定时器输入捕获中断、CAN通信发送中断事件、CAN通信接收中断事件、RTOS的PendSV中断等等。
-
中断服务函数处理过程:
-
CPU检测到有中断事件的发生
-
保护现场,将当前位置的PC地址压栈(程序计数器(Program Counter));
-
跳转到中断服务函数,执行中断服务程序;
-
恢复现场,将栈顶的值回送给PC;
-
跳转到被中断的位置开始执行下一个指令。
-
-
中断优先级与分组
-
优先级:抢占优先级和子优先级
-
分组0-5
- 更改分组(在HAL_Init中更改)
-
中断服务函数内容:
-
先查询中断标志位,确定被触发的中断事件
-
清除对应标志位,防止中断一直被触发,好让下次中断正常运行
-
接收数据等(可选)
-
逻辑业务代码实现(可选,比如数据处理等)
-
-
中断服务函数的特点:
1. 中断服务函数不能传入参数; 2. 中断服务函数不能有返回值; 3. 中断服务函数应该做到短小精悍; 4. 迫不得已的情况下,不准在中断服务函数中使用延时函数,如果要使用延时,请设置好延时和中断的优先级,否则程序出卡死(除了外部中断为了软件消抖而设立的延时) 5. 不要在中断服务函数中使用printf函数,会带来重入和性能问题。- 举例:
 2. USART1\_IRQHnadler函数 1. 中文名:串口1\_中断服务函数 2. 声明定义:由汇编声明,需要用户自己去定义(如果使能了,用户还不定义,程序将会卡在汇编代码中) 3. 调用条件:由CPU中断事件调用 4. 作用:被CPU调用,并调用紧急的中断程序(中断程序也就是中断服务函数里的内容) 3. HAL\_UART\_IRQHnadler(句柄) 1. 中文名:串口\_中断**公共**服务函数(公共的意思指串口1,2,3,4,5......等所有的串口都共用这一个函数实现功能,由后面的句柄决定究竟是哪个函数被触发) 2. 声明定义:ST公司编写的HAL库声明和定义 3. 调用条件:由中断服务函数调用 4. 作用: 1. 先查询中断标志位,确定被触发的中断事件 2. 清除对应标志位,防止中断一直被触发,好让下次中断正常运行 3. 接收数据等(可选) 4. 调用中断事件对应的中断回调函数 5. 其他操作(比如特殊的,在串口接收中断里会disable失能中断,也就是关掉中断) 4. HAL\_UART\_RxCpltCallback(句柄) 1. 中文名:串口\_中断回调函数(因为他被中断公共服务函数调用,所以句柄是由调用它的中断公共服务函数所决定) 2. 声明定义:ST公司编写的HAL库声明为弱函数,需要用户自己去定义 3. 调用条件:被中断公共服务函数调用 4. 作用:先确定是哪个句柄调用的,再进行相应的业务逻辑实现(可选,比如数据处理等)   -
-
RTOS与ROS/ROS2简单了解
-
进阶(非裸机开发,基于RTOS系统开发)
-
常见的RTOS(嵌入式实时操作系统):FreeRTOS、Nuttx、RT-Thread、μC/OS-II、Xiaomi VelaOS
-
FreeRTOS官网:FreeRTOS™ - FreeRTOS™

-
FreeRTOS简单理解:拥有多线程库特性并兼容POSIX标准的操作系统
-
多线程:系统拥有多个任务(线程),每个任务(线程)独立并同时运行(可以理解成每个任务都是一个主函数,这些任务都是同时在执行的。具体实现方式以后再学,原理就是PendSV中断等)
-
-
进阶(非裸机开发,基于RTOS和ROS2_MicroROS)
-
使用方式:ESP32使用arduino库+FreeRTOS+MicroROS并通过串口与STM32进行通信。
-
主要作用之一:可通过WIFI远程与上位机(电脑、工控机)的ROS2进行更加安全、稳定的通信,对比直接用串口通信(rosserial),要好很多(DDS分布式)。
-
MicroROS Vs ROSserial的详解链接: https://mp.weixin.qq.com/s/1lQXAA3sV-4GpXAzHiGChQ
-
寄存器
- 理解:是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。
- 实现的功能:
- 寄存器如何在基于C语言的HAL库中发挥作用的呢?
机器人队标准工程格式
英语
必须用 英语 ,工程文件名、函数名、变量名必须用英语!
正点原子HAL库工程标准:
机器人队STM32工程标准(Cube+C语言):

1. applications应用层

2. bsp驱动层

3. Middlewares中间层

4. Core(主函数所在地,条件编译配置HAL库的头文件所在地)


机器人队STM32C/C++工程标准

1. 应用层、驱动层等采用模块集成式,不再采用Src和Inc分离式


2. 公共兼容层:
3. C++子main兼容库

2. 作用:在.cpp文件中创建一个普通的函数,该函数调用C++的代码,然后被C语言main.c文件中的main函数所调用。


5. 弱函数\_回调函数库(该文件的源文件全局要有extern "C",因为弱函数是C语言的东西,C++无法正常识别)




(建议)机器人队STM32Cube C/C++工程标准(类Cube,试运行,建议):
首先打开CubeMX进行工程配置,比如我们这里用裸机开发使一个LED灯闪烁
然后选择OpenFolder打开文件夹
打开Github将一些必备文件进行克隆
仓库链接:
https://github.com/tungchiahui/CubeMX\_MDK5to6\_Template
或者直接打开terminal输入
git clone https://github.com/tungchiahui/CubeMX_MDK5to6_Template.git
打开克隆的模板与刚才CubeMX生成的工程
打开模板中的***工程文件移植(创建新模板请看这里)*** 文件夹,然后将里面的文件全部复制到CubeMX工程文件中。
移动后:
打开工程设置工程
- 打开MDK5工程

- 点击Options for Target
- 修改编译器为
ARMClang[ARM Compiler6 (AC6)]替换掉ARMCC[ARM Compiler5 (AC5)]
- 添加头文件的路径(Include Path)

添加applications中的Inc文件夹
添加bsp/boards中的Inc文件夹
点击OK即可

- 添加源文件.c/.cpp等
打开Manage Project Items
创建两个分组
分组的名字分别叫
applications
bsp/boards
将Core/Src目录下的startup_main.cpp加入到Application/User/Core组中。
将bsp/boards/Src目录下的bsp_delay.cpp加入到bsp/boards组中。
可以看到工程里的文件都就绪了。
编译并配置一些必要代码


可以右键头文件,然后点Open Document "xxx.h"来打开头文件,用来检查头文件是否导入成功。
找到main.c文件,准备在主函数main()中调用C++的类主函数startup_main();
在USER CODE BEGIN Includes和USER CODE END Includes这两行注释中间 引用startup_main.h (因为不放在BEGIN和END之间的代码在CubeMX重新配置后,代码都会消失)
在USER CODE BEGIN和USER CODE END这两行注释中间 调用startup_main();(因为不放在BEGIN和END之间的代码在CubeMX重新配置后,代码都会消失)

打开startup_main.h
更改isRTOS宏的值,如果是裸机开发则为0,如果使用了FreeRTOS则改为1。
至此,你可以在startup_main()函数中随意调用C/C++库中的代码啦。
C++库的头文件格式
拿bsp_delay.h举例
#ifndef __BSP_DELAY_H_
#define __BSP_DELAY_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include "startup_main.h"
class BSP_Delay
{
public:
class F1
{
public:
void Init(uint16_t sysclk);
void us(uint32_t nus);
void ms(uint16_t nms);
}f1;
class F4
{
public:
void Init(uint16_t sysclk);
void us(uint32_t nus);
void ms(uint16_t nms);
}f4;
class FreeRTOS
{
public:
void Init(void);
}freertos;
};
extern BSP_Delay bsp_delay;
#ifdef __cplusplus
}
#endif
#endif
条件编译肯定不能少,一个是防止头文件重复引用的条件编译,一个是把C++链接为C语言的条件编译。
然后引用startup_main.h头文件
再创建该模块的类,比如说class BSP_LED等,我这里为延时的类,所以是class BSP_Delay。
然后写类里的声明。
注意:不要在.h文件里写任何代码实现,也就是不能写任何函数的定义。
然后第35行的extern BSP_Delay bsp_delay;是在主函数中进行了创建对象bsp_delay,我们要在这里声明(extern)一下对象(变量)。方便其他的源文件调用。
C++库的源文件格式
拿bsp_delay.cpp举例
#include "bsp_delay.h"
#if isRTOS == 1
#include "cmsis_os.h"
#endif
static uint32_t g_fac_us = 0; /* us延时倍乘数 */
BSP_Delay bsp_delay;
/**
* @brief 初始化延迟函数
* @param sysclk: 系统时钟频率, 即CPU频率(HCLK)
* @retval 无
*/
void BSP_Delay::F1::Init(uint16_t sysclk)
{
SysTick->CTRL = 0; /* 清Systick状态,以便下一步重设,如果这里开了中断会关闭其中断 */
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8); /* SYSTICK使用内核时钟源8分频,因systick的计数器最大值只有2^24 */
g_fac_us = sysclk / 8; /* 不论是否使用OS,g_fac_us都需要使用,作为1us的基础时基 */
}
/**
* @brief 延时nus
* @param nus: 要延时的us数.
* @note 注意: nus的值,不要大于1864135us(最大值即2^24 / g_fac_us @g_fac_us = 9)
* @retval 无
*/
void BSP_Delay::F1::us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD = nus * g_fac_us; /* 时间加载 */
SysTick->VAL = 0x00; /* 清空计数器 */
SysTick->CTRL |= 1 << 0 ; /* 开始倒数 */
do
{
temp = SysTick->CTRL;
} while ((temp & 0x01) && !(temp & (1 << 16))); /* CTRL.ENABLE位必须为1, 并等待时间到达 */
SysTick->CTRL &= ~(1 << 0) ; /* 关闭SYSTICK */
SysTick->VAL = 0X00; /* 清空计数器 */
}
/**
* @brief 延时nms
* @param nms: 要延时的ms数 (0< nms <= 65535)
* @retval 无
*/
void BSP_Delay::F1::ms(uint16_t nms)
{
uint32_t repeat = nms / 1000; /* 这里用1000,是考虑到可能有超频应用,
* 比如128Mhz的时候, delay_us最大只能延时1048576us左右了
*/
uint32_t remain = nms % 1000;
while (repeat)
{
us(1000 * 1000); /* 利用delay_us 实现 1000ms 延时 */
repeat--;
}
if (remain)
{
us(remain * 1000); /* 利用delay_us, 把尾数延时(remain ms)给做了 */
}
}
/**
* @brief 初始化延迟函数
* @param sysclk: 系统时钟频率, 即CPU频率(rcc_c_ck), 168MHz
* @retval 无
*/
void BSP_Delay::F4::Init(uint16_t sysclk)
{
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);/* SYSTICK使用外部时钟源,频率为HCLK */
g_fac_us = sysclk; /* 不论是否使用OS,g_fac_us都需要使用 */
}
/**
* @brief 延时nus
* @param nus: 要延时的us数.
* @note nus取值范围 : 0~190887435(最大值即 2^32 / fac_us @fac_us = 21)
* @retval 无
*/
void BSP_Delay::F4::us(uint32_t nus)
{
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;
uint32_t reload = SysTick->LOAD; /* LOAD的值 */
ticks = nus * g_fac_us; /* 需要的节拍数 */
told = SysTick->VAL; /* 刚进入时的计数器值 */
while (1)
{
tnow = SysTick->VAL;
if (tnow != told)
{
if (tnow < told)
{
tcnt += told - tnow; /* 这里注意一下SYSTICK是一个递减的计数器就可以了 */
}
else
{
tcnt += reload - tnow + told;
}
told = tnow;
if (tcnt >= ticks)
{
break; /* 时间超过/等于要延迟的时间,则退出 */
}
}
}
}
/**
* @brief 延时nms
* @param nms: 要延时的ms数 (0< nms <= 65535)
* @retval 无
*/
void BSP_Delay::F4::ms(uint16_t nms)
{
uint32_t repeat = nms / 540; /* 这里用540,是考虑到可能有超频应用, 比如248M的时候,delay_us最大只能延时541ms左右了 */
uint32_t remain = nms % 540;
while (repeat)
{
us(540 * 1000); /* 利用delay_us 实现 540ms 延时 */
repeat--;
}
if (remain)
{
us(remain * 1000); /* 利用delay_us, 把尾数延时(remain ms)给做了 */
}
}
void BSP_Delay::FreeRTOS::Init(void)
{
//调用FreeRTOS自带的延时即可。
//osDelay
//vTaskDelay
//vTaskDelayUntil
}
/**
* @brief HAL库内部函数用到的延时
HAL库的延时默认用Systick,如果我们没有开Systick的中断会导致调用这个延时后无法退出
* @param Delay 要延时的毫秒数
* @retval None
*/
void HAL_Delay(uint32_t Delay)
{
#if isRTOS==0 //如果是裸机开发
#ifdef STM32F1 //如果是裸机开发且为F1
bsp_delay.f1.ms(Delay);
#endif
#ifdef STM32F4 //如果是裸机开发且为F4
bsp_delay.f4.ms(Delay);
#endif
#elif isRTOS==1 //如果是FreeRTOS开发
osDelay(Delay);
#endif
}
刚上来肯定要引用自己的头文件。
这个条件编译不用管,因为延时在裸机开发和RTOS开发时有区别,所以我加了一行条件编译。

上来要先创建一下类对象bsp_delay;

然后把类里的函数都进行定义。
函数注释格式:
这一块是该函数的注释,以后尽量都这样写注释。(在以后MDK6中进行调用函数时,会提示该注释,一目了然)
这样写注释的好处,在调用时,会显示入口参数需要填什么,会显示返回值是什么。
brief 函数摘要
param 入口参数
retval 返回值
note或attention 注意事项
注意这里,有几个param入口参数,就写几个param
比如
/**
* @brief CAN1通信发送函数
* @param motor1: 第1个电机的相对电流值
* @param motor2: 第2个电机的相对电流值
* @param motor3: 第3个电机的相对电流值
* @param motor4: 第4个电机的相对电流值
* @retval bool是否发送成功
* @note 无特殊注意事项
*/
bool CAN_BUS::CAN1::CMD1(int16_t motor1,int16_t motor2,int16_t motor3,int16_t motor4)
{
// ... ...
}
注意事项
-
在.cpp源文件中,弱函数的定义前面要加个extern “C” 因为__weak是C语言(汇编向量)特有的,所以必须把代码以C语言的形式链接。
-
代码要写在Begin和End之间,否则再次用CubeMX配置代码后,代码会消失。





















































































































