STM32 HAL库—基础篇

1. 单片机简介

(1)单片机特点:体积小、功耗低、集成度高、使用方便、扩展灵活

(2)CISC和RISC的区别:

对比项 复杂指令集CISC 精简指令集RISC
目的 为了便于编程和提高存储器访问效率 为了提高处理器的运行速度
特点 - 指令多、模式多、格式可变 - 指令的时钟周期差距很大 - 无流水线或流水线程度较低 - 指令由微代码翻译执行 - 指令少、模式少、格式固定 - 指令只需 1 个时钟周期 - 流水线结构 - 指令由硬件执行
优点 - 指令丰富、功能强大 - 寻址方式灵活 - 指令精简、易于设计、使用率均衡 - 程序执行效率高
缺点 - 指令使用率不均衡 - 不利于采用先进结构提高性能 - 结构复杂,不利于超大规模集成电路实现 - 指令少较少、功能不如CISC - 寻址方式不够灵活
举例 8051、X86 ARM、MIPS、RISC-V

(3)冯诺依曼结构与哈佛结构的区别

对比项 冯诺依曼结构 哈佛结构
特点 程序指令和数据被存储在同一个存储器中 程序指令和数据被存储在两个独立的存储器中
优点 总线资源占用少 执行效率较高
缺点 执行效率较低 总线资源占用多

(4)cortex内核分类

对比项 Cortex - A(应用) Cortex - R(实时) Cortex - M(微处理器)
特点 高时钟频率,长流水线,高性能 较高时钟频率,较长流水线,实时性强 低时钟频率,较短流水线,超低功耗
应用场景 移动计算、智能手机、平板电脑、数字电视 军工、汽车电子、无限基带、硬盘控制器 工控、传感器、消费电子、家用电器、医疗器械

2. 开发调试工具

(1)需要追踪程序执行时间时设置,根据系统时钟频率的实际值设置:

  • STM32F1 --- 72MHz

    • STM32F407 --- 168MHz

(2)仿真报错解决方法:

  • 仿真结束前将所有断点清除

  • 将工程路径改浅,并改成全英文路径

(3)工具栏常用窗口按钮结束

  • Call Stack窗口:查看函数调用关系和局部变量

  • Watch窗口:查看函数首地址和变量值;设置全局变量在被读或被写后自动停止运行

  • Memory窗口:内存查看窗口(M3 / M4 / M7 内核是小端模式)

  • Peripheral窗口:查看寄存器的值(确定配置寄存器是否有问题)

注意:

仿真时,使用 MDK 的 Level 0 等级优化

调试停止在断点处时,只是内核停止,外设会继续运行

断点的设置要有时间观念,考虑是否会打断正常通信

3. C语言基础

(1)stdint.h

从C99中引进的标准C库文件,推荐使用符合C99标准的 uint8_t、uint16_t、uint32_t等类型别名,需配置MDK支持C99(勾选 C/C++ -> C99 Mode)。

(2)位操作运算符

运算符 含义
& 按位与
| 按位或
^ 按位异或
~ 按位取反
<< 左移
>> 右移

给寄存器某个位赋值:如 uint32_t temp = 0;

给位6赋值为0:

  • temp &= 0xFFFFFFBF ; temp |= 0x00000040

  • temp &= ~ (1 << 6) ; temp |= 1 << 6

(3)宏定义

提高效率、可读性、易改性(核心是替换)。

格式:#define 标识符 字符串

标识符:宏定义的名字

字符串:常数、表达式、格式串等

带参数的宏定义:利用 do{...} while(0) 来构造,这样不会受到大括号、分号、运算优先级等影响

如:

 #define LED1(x)  do{ x ? HAL_GPIO_WritePin ( LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : HAL_GPIO_WritePin ( LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET}; } while(0)

(4)条件编译

指令 作用
#if 编译预处理条件指令,类似 if
#ifdef 判断某个宏是否已被定义( #ifdef + 宏名)
#ifndef 判断某个宏是否未被定义( #ifndef + 宏名)
#elif 若前面的条件不满足,则判定新的条件,类似 else if
#else 若前面的条件不满足,则执行后面的语句,类似 else
#endif #if / #ifdef / #ifndef的结束标志

头文件的条件编译:

 #ifndef __LED_H
 #define __LED_H
 ​
 #include 
 ​
 code 
     
 #endif

代码的条件编译:

 #if SYS_SUPPORT_OS
     code
 #endif
 #ifdef 标识符
 程序段1
 #else
 程序段2
 #endif

注意:

当标识符已经被定义过(一般使用 #define 定义),对程序段 1 进行编译;否则对程序段 2 进行编译或结束。

(5)extern 声明

放在变量/函数前,表示该变量/函数已在其他文件定义,以便本文件引用。

extern 申明变量可以多次,但定义只有一次。

(6)typedef类型别名

为现有数据类型创新一个新的名字 / 类型别名,简化变量定义(定义结构体的类型别名和枚举类型)。

格式:typedef 现有类型 新名字

 typedef unsigned char uint8_t;
 typedef unsigned short int uint16_t;
 typedef unsigned int uint32_t;

类型别名的应用:

 Struct GPIO_TypeDef
 {
     __IO uint32_t CRL;
     __IO uint32_t CRH;
     ......
 };
 Struct GPIO_TypeDef gpiox
 typedef struct
 {
     __IO uint32_t CRL;
     __IO uint32_t CRH;
     ......
 }GPIO_TypeDef;
 GPIO_TypeDef gpiox

(7)结构体

由若干基本数据类型集合组成的一种自定义数据类型,也叫聚合类型。

声明结构体类型:

struct 结构体名

{

成员列表;

}变量名列表(可选);

 struct student
 {
     char *name;
     int num;
     int age;
     int group;
     float score;
 }stu1,stu2;
 typedef struct
 {
     uint32_t Pin;
     uint32_t Mode;
     uint32_t Pull;
     uint32_t Speed;
 }GPIO_InitTypeDef;

注意:

由结构体变量名引用其成员:结构体变量名 . 成员名

结构体指针成员变量引用方法:指针变量名 -> 成员名

(8)指针(内存地址)

指针变量:保存了指针的变量。

格式:类型名 * 指针变量名

 char *p_str = "This is a test!";
  • *p_str:取 p_str 变量的值;

  • &p_str:取 p_str 变量的地址。

 uint8_t buf[5] = {1,3,5,7,9};
 uint8_t *p_buf = buf;
 *p_buf = ?              // 1
 p_buf[0] = ?            // 1
 p_buf[1] = ?            // 3
 p_buf++;
 *p_buf = ?              // 3
 p_buf[0] = ?            // 3

指针使用的两大问题:

  • 未分配(申请)内存(使用指针前必须初始化)

 char *p_buf;
 p_buf[0] = 100;
 p_buf[1] = 120;
 p_buf[2] = 150;
  • 越界使用

 uint8_t buf[5] = {1,3,5,7,9};
 uint8_t *p_buf = buf;
 p_buf[5] = 200;
 p_buf[6] = 250;

(9)C 语言中的 u/U

0U:无符号整型 0

1U:无符号整型 1

如果不写 u/U后缀,系统默认为 int 型,即为有符号型整数。

4. 存储器与寄存器映射

(1)STM32 寻址范围

1)32 位单片机有 32 根地址线(每根地址线有导通或不导通两种状态)

2)单片机内存地址访问的存储单元是按字节编址的(不是 bit )

地址线根数 地址编号数(内存大小)
1 2 (0, 1)
2 4 (00, 01, 10, 11)
3 8 (000, 001, 010, 011, 100, 101, 110, 111)
... ...
n 2^n

STM32寻址大小:232 = 4G(字节)

STM32寻址范围:0x0000 0000 ~ 0xFFFF FFFF

(2)存储器映射

1)存储器:存储数据的设备,存储器本身没有地址信息

2)存储器映射:对存储器分配地址的过程

3)存储器功能划分:(将 4GB 地址空间分为 8 个块,重点是前三个块)

存储块 功能 地址范围
Block 0 Code (FLASH) 0x0000 0000 ~ 0x1FFF FFFF (512MB)
Block 1 SRAM 0x2000 0000 ~ 0x3FFF FFFF (512MB)
Block 2 片上外设(APB1、APB2、AHB) 0x4000 0000 ~ 0x5FFF FFFF (512MB)

(3)寄存器映射

1)寄存器:实现对单片机各个功能的控制(寄存器是单片机内部的控制结构)

2)分类:内核寄存器、外设寄存器

3)寄存器映射:给寄存器地址命名

如:0x4001080C(寄存器地址)➡️ GPIOA_ODR(寄存器名字)

4)寄存器描述:

  • 寄存器名字

  • 偏移量及复位值

  • 寄存器位表

  • 位功能描述

5)寄存器地址计算:

  • 总线基地址 ( BUS_BASE_ADDR )

  • 外设基于总线基地址的偏移量 ( PERIPH_OFFSET )

  • 寄存器相对外设基地址的偏移量 ( REG_OFFSET )

寄存器地址 = BUS_BASE_ADDR + PERIPH_OFFSET + REG_OFFSET

外设基地址(PERIPH_BASE):APB1总线的基地址

例如:

(a) STM32F103 中 GPIOA_ODR 寄存器地址计算过程:

GPIOA 在 APB2 总线上,APB2 总线基地址:0x4001 0000

GPIOA 基于 APB2 总线基地址的偏移量:0x4001 0800 - 0x4001 0000 = 0x0800

ODR 寄存器基于 GPIOA 外设基地址的偏移量:0x0C

寄存器地址 = 0x4001 0000 + 0x0800 + 0x0C = 0x4001 080C

(b) STM32F4047 中 GPIOA_ODR 寄存器地址计算过程:

GPIOA 在 AHB1 总线上,AHB1 总线基地址:0x4002 0000

GPIOA 基于 AHB1 总线基地址的偏移量:0

ODR 寄存器基于 GPIOA 外设基地址的偏移量:0x14

寄存器地址 = 0x4002 0000 + 0 + 0x14 = 0x4001 0014

6)使用结构体,完成对寄存器的映射

 typedef struct
 {
     __IO uint32_t CRL;
     __IO uint32_t CRH;
     __IO uint32_t IDR
     __IO uint32_t ODR
     __IO uint32_t BSRR
     __IO uint32_t BRR
     __IO uint32_t LCKR
 }GPIO_TypeDef;
 #define GPIOA ((GPIOA_TypeDef *)GPIOA_BASE)
 ​
 GPIOA_BASE: 0x4001 0800
 &GPIOA->CRL: 0x4001 0800
 &GPIOA->CRH: 0x4001 0804
 &GPIOA->IDR: 0x4001 0808
 &GPIOA->ODR: 0x4001 080C
 &GPIOA->BSRR: 0x4001 0810
 &GPIOA->BRR: 0x4001 0814
 &GPIOA->LCKR: 0x4001 0818

实际应用:

 GPIOA->ODR = 0xFFFF;

5. MAP文件

(1)MDK编译过程文件(11种)

文件类型 说明
.o 可重定向对象文件,每个源文件(.c / .s 等)编译都会生成一个 .o 文件
.axf 可执行对象文件,由 .o 文件链接生成 .axf 文件,仿真时用到
.hex 用于下载到 MCU 中,由 .axf 文件转换而来
.map 连接器生成的列表文件,对分析程序存储占用情况非常有用
其他 .crf、.d、.dep、.lnp、.lst、.htm、.build_log.htm一般用不到

注意:

可重定向:该文件包含数据/代码,但没有指定地址,可由后续链接时再指定;

不可重定向:该文件的数据/代码已经指定地址了,不能再改变。

(2)MAP文件

1)概念:MDK编译代码后,产生集程序、数据和IO空间的一种映射列表文件,包含交叉链接信息(各种 .c 文件、函数、符号等地址、大小、引用关系等信息)

2)作用:分析各个 .c 文件占用 FLASH(ROM)和 RAM 的大小,方便优化代码

3)组成:

组成 说明
程序段交叉引用关系 描述各文件之间函数的调用关系
删除映像未使用的程序段 描述工程中未用到而被删除的冗余程序段(函数/数据)
映像符号表 描述各符号(程序段/数据)在存储器中的地址、类型、大小等
映像内存分布图 描述各个程序段(函数)在存储器中的地址及占用大小
映像组件大小 给出整个映像代码 .o 占用空间汇总信息
概念 说明 占用空间
RO (Read Only) 只读数据 RO data + 代码 RO code FLASH
RW (Read Write) 可读写数据 RW data(有初值,且不为0) FLASH(存储初值)+ RAM(读写操作)
ZI (Zero Initialized) 初始化为 0 的数据 ZI data RAM

6. STM32启动模式与启动过程(看不懂,暂时没学)

7. STM32CubeMX

(1)软件功能:图形配置软件,可通过配置自动生成初始化代码

(2)安装步骤:

  • STM32CubeMX 软件获取

  • 搭建 Java 运行环境

  • 安装 STM32CubeMX 软件

  • 下载并关联 STM32Cube 固件包 ( Help->Manage embedded software packages->install )

(3)新建 STM32CubeMX 工程

  • 工程初步建立:新建工程,选取芯片型号

  • 时钟模块配置:设置高速外部时钟 HSE(频率范围为 4MHz~16MHz )、低速外部时钟 LSE(接频率为 32.768KHz 的石英晶体)、MCO

  • 时钟系统配置:锁相环 PLL、系统时钟 SYSCLK、高速总线 AHB、低速总线 APB1、APB2等

  • GPIO 引脚配置

  • Cortex 内核配置:SYS ( DEBUG) 配置、NVIC(优先级分组)

  • 生成工程源码:设置工程、MDK等,最后生成代码工程

  • 编写用户程序:在main.c文件预留的位置编写代码 ( USER CODE BEGIN 3~USER CODE END 3 )

8. STM32时钟系统

(1)时钟:具有周期性的脉冲信号,最常用的是占空比为 50% 的方波

(2)认识时钟树:

F1系列:

时钟源名称 频率 材料 用途
高速外部振荡器 HSE 4~16MHz 晶体/陶瓷 SYSCLK / RTC
低速外部振荡器 LSE 32.768KHz 晶体/陶瓷 RTC
高速内部振荡器 HSI 8MHz RC SYSCLK
低速内部振荡器 LSI 40KHz RC RTC / IWDG

STM32 HAL库—基础篇_第1张图片

F407系列:

时钟源名称 频率 材料 用途
高速外部振荡器 HSE 4~26MHz 晶体/陶瓷 SYSCLK / RTC
低速外部振荡器 LSE 32.768KHz 晶体/陶瓷 RTC
高速内部振荡器 HSI 16MHz RC SYSCLK
低速内部振荡器 LSI 32KHz RC RTC / IWDG

STM32 HAL库—基础篇_第2张图片

备注:F7 和 H7 时钟树跳过

(3)系统时钟配置步骤

  • 配置 HSE_VALUE:stm32xxxx_hal_conf.h,告诉 HAL 库外部晶振频率

  • 调用SystemInit() 函数(可选):在启动文件中调用,在system_stm32xxxx.c定义

  • 选择时钟源,配置PLL:通过 HAL_RCC_OscConfig() 函数设置

  • 选择系统时钟源,配置总线分频器:通过 HAL_RCC_ClockConfig() 函数设置

  • 配置扩展外设时钟(可选):通过 HAL_RCCEx_PeriphCLKConfig() 函数设置

(4)外设时钟使能和失能

使能:

 __HAL_RCC_GPIOA_CLK_ENABLE();       //使能GPIOA时钟

失能:

 __HAL_RCC_GPIOA_CLK_DISABLE();      //禁止GPIOA时钟

9. printf 函数

(1)printf 函数输出流程

printf() 用户调用 ➡️ C 标准库( printf 部分,由编译器提供的 stdio.h 解析) ➡️ fputc() 最终实现输出

printf重定向:用户需要根据最终输出的硬件重新定义该函数

(2)printf 的使用

  • printf("字符串 \r\n");

  • printf("输出控制字符 \r\n",输出参数);

  • printf("输出控制字符1 输出控制字符 2 ... \r\n",输出参数 1,输出参数 2,... );

  • printf("非输出控制符 输出控制符 非输出控制符 \r\n",输出参数);

  • 如何输出 %、\ 、""

 printf("%%\r\n");
 printf(" \\ \r\n");
 printf(" \"\" \r\n");

(3)printf 的支持

  • 避免使用半主机模式

    • 微库法:魔术棒 -> Target 选项卡 -> 勾选 "Use MicroLIB"

    • 代码法

  • 实现 fputc 函数

 #include 
 ​
 UATR_HandleTypeDef huart1;
 ​
 int fputc(int ch,FILE *f)
 {
     HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
     return ch;
 }

文章参考:

第1讲 基础篇-单片机简介_哔哩哔哩_bilibili

你可能感兴趣的:(stm32,嵌入式硬件,单片机)