# 1 stm32 简介

  • STM32是ST公司基于ARM Cortex-M内核开发的32位微控制器
  • STM32常应用在嵌入式领域,如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等
  • STM32功能强大、性能优异、片上资源丰富、功耗低,是一款经典的嵌入式微控制器

# 1.1 ARM 介绍

  • ARM既指ARM公司,也指ARM处理器内核
  • ARM公司是全球领先的半导体知识产权(IP)提供商,全世界超过95%的智能手机和平板电脑都采用ARM架构
  • ARM公司设计ARM内核,半导体厂商完善内核周边电路并生产芯片

stm 32 有高性能、主流、低功耗、无线四个系列。

A 系列适用高端应用型的领域,如手机芯片、电脑芯片。

R、M 系列适用于嵌入式领域,R 系列注重 real-time,应用场景如:硬盘控制器等,M 系列主要用于单片机领域。

# 1.2 芯片介绍

stm32f103rct6,是通用增强型,64 位引脚,48kb RAM,256k ROM, LQFP 封装,工作在 -40 摄氏度 至 85 摄氏度的芯片。

stm32f103c8t6,主流系列,M3 内核,主频 72Mhz,48 引脚,20k RAM(SRAM),64k ROM(Flash),LQFP 封装。

# 1.3 片上资源/外设

NVICSysTick 是 M3 的资源。其余是内核外的资源。

SysTick 可以给 rtos、ucos 等操作系统提供定时来进行任务切换功能。

上图是 Stm32F1 的所有外设,芯片具体有哪些外设,需要查询数据手册。

操作外设,必须先使能时钟。

DMA 帮助 CPU 搬运大量数据。

USART 同步/异步 串口。

当单片机因为电磁干扰死机或者程序设计不合理而死循环时,看门狗可以及时复位芯片,保持系统稳定。

stm32f103c8t6外设如下:

stm32f103rct6 外设如下:

# 1.4 命名规则

查看参考手册:STM32 系列产品命名规则

# 1.5 系统结构

详细模块框架图见数据手册 第 2 节。

简略框架图见参考手册 2.1 节。

1 是主动单元,2 是被动单元。

Icode 指令总线,连接flash闪存,加载程序指令

DCode 数据总线,加载数据

System 系统总线,连接 sram,FSMC 等。

AHB 先进高性能主线,挂在最基本的以及性能较高的外设,如 SDIO、复位和时钟控制。

APB 先进外设主线,用于连接一般外设,APB2 性能比 APB1 高,次要外设连接到 APB1 上,需要桥接转换和转存数据。

APB2 72MHz,APB1 36MHz

DMA 通过 DMA 总线与总线矩阵相连,用于数据搬运。外设可直接向 DMA 发起 DMA 请求。

# 1.6 引脚定义

查看 参考手册 第 3 节

stm32f103c8 引脚分布如下:

stm32f103rc 引脚分布如下:

引脚定义中,主功能代表上电后默认功能,默认复用功能是 I/O 口上同时连接的外设功能引脚。 配置 I/O 口的时候可以选择是通用 I/O 口还是复用功能。

重定义功能即如果有两个功能同时复用在一个 I/O 口上,而需要同时用到两个功能,可以把其中一个复用功能 重映射到其他端口上。

以 stm32f103c8 为例:

1 号引脚,VBAT 是电池供电引脚,可以接 3V 电池,系统电源断电时,备用电池给内部 RTC 时钟和备份寄存器提供电源。

2 号引脚 PC13/TAMPER-RTC是 I/O 口或侵入检测或者 RTC。I/O 口可以根据程序输出或读取高低电平。侵入检测可以做安全保障功能。 RTC 引脚可以用来输出 RTC 校准时钟,RTC 闹钟脉冲或者秒脉冲。

3、4 号引脚 PC14/OSC32_IN、PC15/OSC_OUT 是 I/O 口或者接 32.768Khz 的 RTC 晶振。

5、6 号引脚 OSC_IN、OSC_OUT 接系统主晶振,一般 8MHz,可以倍频到 72 MHz,作为系统的主时钟。

7 号引脚 NRST 代表复位引脚,N 代表低电平复位。

8、9 号引脚 VSS、VDD 是内部模拟部分的电源,如 ADC、RC 震荡器等。

10 号引脚 I/O 口或者 PA0-WKUP 可以唤醒待机模式的 stm32 。

20 号引脚是 I/O 口或 BOOT1 引脚,用来配置启动模式。

23、24 号引脚 VSS_1、VDD_1 是系统的主电源口

VSS_2 /VSS_3 都是系统主电源口,stm32 采用了分区供电的方式。

vss 都接 3.3v,vdd 都接地即可。

34、37、38、39、40 号,是 I/O 口或者调试端口,默认主功能是调试端口。用于调试和下载程序 stm32 支持 JTAG 和 SWD 两种调试方式,SWD 需要两根线,JTAG 需要 5 根线。 swd 占用 SWDIO、SWCLK 两个口。使用 swd 调试方式时,PA15、PB3、PB4 可配置为普通 I/O 口使用。

44 号引脚是 BOOT0,用于启动配置。

# 1.7 启动配置

stm32F10xxx 中可以通过 BOOT[1:0] 选择三种不同启动模式:

查询参考手册 2.4 小结,可得知 stm32f10xxx 的启动配置:

根据选定的启动模式,主闪存存储器、系统存储器或SRAM可以按照以下方式访问:

  • 从主闪存存储器启动:主闪存存储器被映射到启动空间(0x0000 0000),但仍然能够在它原有的地址(0x0800 0000)访问它,即闪存存储器的内容可以在两个地址区域访问,0x00000000或0x0800 0000。用于存储用户程序。
  • 从系统存储器启动:系统存储器被映射到启动空间(0x0000 0000),但仍然能够在它原有的地址(0x1FFF F000)访问它。存储有预烧录的启动程序 Bootloader。
  • 从内置SRAM启动:只能在0x2000 0000开始的地址区访问SRAM,掉电消失,不能永久存储。

ISP 下载即选择系统存储器启动,Bootloader接收串口数据,刷新到主闪存中。

内嵌的自举程序用于通过USART1串行接口对闪存存储器进行重新编程。这个程序位于系统存 储器中,由ST在生产线上写入。

BOOT 引脚的值是在上电复位后一瞬间有效,之后就不再有效。如 20 号引脚上电瞬间是 BOOT1 功能,第四个时钟过后就是 PB2 的功能。

# 1.8 最小系统电路

保证 MCU 正常工作的最小电路组成单元。

  • 主控芯片
  • 供电电路
  • 晶振电路
  • 下载调试电路
  • BOOT 启动电路
  • 复位电路

时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令。

如果需要 RTC 实时时钟功能的话可以接上 32.768Khz 的晶振于 3、4 号引脚。 osc32 就是 32.768 Khz。

RTC 时钟主要作用是记录设备关机时的时间以及设备开机时提供时间基准。

如果使用内部RC振荡器而不使用外部晶振,请按照下面方法处理:

  1. 对于100脚或144脚的产品,OSC_IN 应接地,OSC_OUT 应悬空。
  2. 对于少于100脚的产品,有2种接法:
    1. OSC_IN和OSC_OUT分别通过10K电阻接地。此方法可提高EMC性能。
    2. 分别重映射OSC_IN和OSC_OUT至PD0和PD1,再配置PD0和PD1为推挽输出并输出'0'。此方法可以减小功耗并(相对上面2.1)节省2个外部电阻。

# 2 开发环境安装

手册:原理图、数据手册、参考手册、内核编程手册、固件库使用手册、外设手册

# 2.1 keil 安装

官网下载。

# 2.2 安装器件支持包

官网下载或者软件中在线安装。

arm keil (opens new window)

KEIL 安装目录就有:

ARM/STLink/USBDriver 下dpinst_amd64.exe。

# 2.4 安装 USB 转串口驱动

CH340

# 3 工程建立

开发方式:

  1. 基于寄存器方式。
  2. 基于标准库方式。
  3. 基于 HAL 库方式。

# 3.1 下载固件库

stm32 (opens new window)

官网下载固件库 STM32F10x_StdPeriph_Lib_V3.5.0

下载支持包:想要在国内如何快速下载 keil 的 pack 文件包,可以先在 keil 的 pack 官网查找自己想要的 pack, 然后添加到 http://keilpack.azureedge.net/pack/ 末尾,如: http://keilpack.azureedge.net/pack/ARM.V2M_MPS3_SSE_300_BSP.1.1.0.pack,使用迅雷下载。

https://sadevicepacksprodus.blob.core.windows.net/ 网址后面加上对应的pack包就可以正常的进行数据的传输。

# 3.2 创建工程文件夹

一、复制 STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm 目录下的启动文件。

stm32f407 选择 Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm|startup_stm32f40_41xxx.s,并添加宏 STM32F40_41xxx

stm32 启动最先执行的就是该文件。

到项目目录下的 startup 目录下。

二、接着回到固件库的 Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x 文件夹,

stm32f407在:\Libraries\CMSIS\Device\ST\STM32F4xx\IncludeLibraries\CMSIS\Device\ST\STM32F4xx\Source\Templates

stm32f4xx.h,注:第 137 行定义了外部高速晶振的值 是 25MHz,如果不对需要修改。

stm32f4xx.h 中:11580 行重复定义,直接屏蔽,不然会有很多警告。

/********************  Bit definition for DBGMCU_APB2_FZ register  ************/
#define  DBGMCU_APB2_FZ_DBG_TIM1_STOP        ((uint32_t)0x00000001)
#define  DBGMCU_APB2_FZ_DBG_TIM8_STOP        ((uint32_t)0x00000002)
#define  DBGMCU_APB2_FZ_DBG_TIM9_STOP        ((uint32_t)0x00010000)
#define  DBGMCU_APB2_FZ_DBG_TIM10_STOP       ((uint32_t)0x00020000)
#define  DBGMCU_APB2_FZ_DBG_TIM11_STOP       ((uint32_t)0x00040000)

/* Legacy aliases */
#if 0
#define  DBGMCU_APB2_FZ_DBG_TIM1_STOP        DBGMCU_APB1_FZ_DBG_TIM1_STOP
#define  DBGMCU_APB2_FZ_DBG_TIM8_STOP        DBGMCU_APB1_FZ_DBG_TIM8_STOP
#define  DBGMCU_APB2_FZ_DBG_TIM9_STOP        DBGMCU_APB1_FZ_DBG_TIM9_STOP
#define  DBGMCU_APB2_FZ_DBG_TIM10_STOP       DBGMCU_APB1_FZ_DBG_TIM10_STOP
#define  DBGMCU_APB2_FZ_DBG_TIM11_STOP       DBGMCU_APB1_FZ_DBG_TIM11_STOP
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

目录下有:

  • stm32f10x.h,stm32 的外设寄存器描述文件,描述 stm32 的寄存器和对应地址。
  • system_stm32f10x.c,用来配置时钟的,stm32 主频 72 Mhz 就是该文件配置的。
  • system_stm32f10x.h

stm32f407 还要复制 main.h

复制该三个文件,粘贴到 startup 文件夹下。

三、因为 stm32 是内核和内核外围的设备组成的,这个内核的寄存器描述和外围设备的描述文件不是在一起的。

打开固件库的 Libraries\CMSIS\CM3\CoreSupport 目录下。

目录下的 core_cm3.ccore_cm3.h 就是内核的寄存器描述(.h) 和一些内核的配置函数(.c)。

stm32f407 复制 Libraries\CMSIS\Include\core_cm4.h、core_cmInstr.h、core_cmFunc.h·core_cmSimd.h

复制该两个文件,粘贴到 Startup 文件夹下。

工程中把 Startup 目录下所有文件添加到分组 Startup 中。

Target 里 ARM Complier 选择 V5.06 update7(build96) 需自行下载,因为 keil 最新版已不再 安装 armcc,改为 armlang。 而标准库不支持 armlang,如果要使用 armlang ,使用 HAL 库。

Output 中勾选 Create Hex, C/C++ 里勾选 C99 Mode,并且选择头文件路径。编译优化选择 -O0。

debug settings,勾上 reset and run

项目文件夹下新建 User 文件夹,工程里新建 User 组,新建 User/main.c 文件并添加到 User 组里。

有些烧录工具需要 bin 格式的文件,可以使用以下命令产生。

fromelf.exe --bin -o .\Objects\demo.bin .\Objects\demo.axf

ST-Link 烧录需要 .axf 文件,ISP 烧录需要 .hex 文件。

新建 system 文件夹,用于存放 系统资源

新建 drivers 文件夹,用于存放驱动文件。

# 3.3 创建工程

代码以 stm32f103rc 为例

寄存器方式

参考参考手册、原理图编写

#include "stm32f10x.h"                  // Device header

int main(void)
{
    while(1) {
    }
}

注意最后一行要是空行,不然会报警告。

hex 文件通过串口调试工具下载到 mcu。

新建 beep、delay

观察原理图,BEEP 低电平,三极管导通,蜂鸣器有电流流过,蜂鸣器鸣叫。

BEEP 高电平,三极管截止,蜂鸣器停止鸣叫。

beep.h

```c
#ifndef _BEEP_H
#define _BEEP_H

#include "stm32f10x.h"

void beep_init(void);

// 打开蜂鸣器:ODR[9] 清 0
#define BEEP_ON()  do { GPIOC->BSRR |= 1 << 25; } while (0)

// 关闭蜂鸣器
#define BEEP_OFF() do { GPIOC->ODR |= 1 << 9; } while (0)

#define GPIOC_CRH *((volatile unsigned int *)(0x40011000 + 0x04))

#endif

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

beep.c

#include "beep.h"

// BEEP - PC9,低电平蜂鸣器鸣叫
// PC9 是 GPIOC 外设的第 9 个 IO 口(从 0 数起)

void beep_init(void)
{
	// 使能 GPIOC 的时钟
	// void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
	//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    // APB2外设时钟使能寄存器
    RCC->APB2ENR |= 0x00000010;

	// 配置 PC9 为通用推挽输出,输出速度为 50MHz
	// CRL 管理 IO0~IO7,CRH 管理 IO8~IO15
	// ~(0xF << 4)
	//GPIOC->CRH &= ~(0xF << 4);   // 对 CRH[7:4] 位清 0
	// CRH[7:4] = 0b0011
	//GPIOC->CRH |= 0x3 << 4;

	// 直接使用地址值来操作
	// GPIOC->CRH 的地址是 0x40011000 + 0x04
	// *((volatile unsigned int *)(0x40011000 + 0x04)) &= ~(0xF << 4);
	// #define GPIOC_CRH *((volatile unsigned int *)(0x40011000 + 0x04))
    GPIOC_CRH &= ~(0xF << 4);
	GPIOC_CRH |= 0x3 << 4;

	// 关闭蜂鸣器
	BEEP_OFF();
}
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

delay.h

#ifndef _DELAY_H
#define _DELAY_H

#include <inttypes.h>

void delay(uint32_t count);

#endif

1
2
3
4
5
6
7
8
9

delay.c

#include "delay.h"

// 延时函数
void delay(uint32_t count)
{
	int i, j;

	for (i = 0; i < count; i++)
		for (j = 0; j < 1000; j++);
}
1
2
3
4
5
6
7
8
9
10

main.c

#include "stm32f10x.h"                  // Device header
#include "beep.h"
#include "delay.h"

int main(void)
{
    beep_init();
    while(1) {
        BEEP_ON();
        delay(5000);
        BEEP_OFF();
        delay(5000);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

代码简洁但是麻烦。

标准库方式

1 将STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver 下 inc 和 src 目录下文件拷贝到 项目目录下的 libs 目录下。

其中 misc.c 是内核的库函数,其他是片上外设的库函数。

注:stm32f407 没有 stm32f4xx_fmc.c 文件里面的功能,不能添加这个外设,不然会报错。

2 keil 新建组,并添加所有的库函数文件,配置 keil 头文件路径。

3 将 STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template 下的

stm32f10x_conf.h,配置库函数头文件包含关系,并且有一个用来参数检查的定义,是所有库函数都需要的。 它包含了所有的库函数头文件。stm32f10x.h 的最后包含了 stm32f10x_conf.h

/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Uncomment the line below to expanse the "assert_param" macro in the
   Standard Peripheral Library drivers code */
/* #define USE_FULL_ASSERT    1 */

/* Exported macro ------------------------------------------------------------*/
#ifdef  USE_FULL_ASSERT

/**
  * @brief  The assert_param macro is used for function's parameters check.
  * @param  expr: If expr is false, it calls assert_failed function which reports
  *         the name of the source file and the source line number of the call
  *         that failed. If expr is true, it returns no value.
  * @retval None
  */
  #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
  void assert_failed(uint8_t* file, uint32_t line);
#else
  #define assert_param(expr) ((void)0)
#endif /* USE_FULL_ASSERT */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

stm32f10x_it.c

stm32f10x_it.h

上面两个文件存放 interrupt 中断函数的。

4 三个文件复制到项目目录下的 user 目录下,在 keil 组中 ,添加三个文件。

5 观察外设寄存器描述文件stm32f10x.h 第 8296 行

#ifdef USE_STDPERIPH_DRIVER
  #include "stm32f10x_conf.h"
#endif
1
2
3

所以为了 stm32f10x_conf.h 能正常生效,才能包含标准外设库,使用标准外设驱动,需要定义全局的宏。

keil c/c++ 中定义宏 USE_STDPERIPH_DRIVER

有些工程会同时声明宏:STM32F10X_HDSTM32F10X_MD 根据芯片型号选择。 但 keil5 会自动声明。

# 3.4 启动文件选择

汇编文件选择,stm32f103rc 选择 hd.s,stm32103c8t6 选择 md.s

stm32f40x 和 stm32f41x 都选择 startup_stm32f40_41.s

# 3.5 工程架构

startup_stm32f10x_hd.s

该文件主要实现目的:

  1. 设置初始 sp
  2. 设置初始 PC=Reset_Handler
  3. 设置向量表入口地址,并初始化向量表
  4. 调用 Systeminit,把系统时钟配置成 72M,Systeminit 在 system_stm32f10x.c 中定义
  5. 跳转到标号 __main,最终来到 main 函数。

文件中 146 行:

; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP

; Dummy Exception Handlers (infinite loops which can be modified)
1
2
3
4
5
6
7
8
9
10
11
12

启动文件还定义了 stm32 的所有其他中断,达到触发条件就会自动执行。中断函数定义就在 stm32f103x_it.c

st 建议将中断也写到 stm32f103x_it.c 中。

/******************************************************************************/
/*                 STM32F10x Peripherals Interrupt Handlers                   */
/*  Add here the Interrupt Handler for the used peripheral(s) (PPP), for the  */
/*  available peripheral interrupt handler's name please refer to the startup */
/*  file (startup_stm32f10x_xx.s).                                            */
/******************************************************************************/

/**
  * @brief  This function handles PPP interrupt request.
  * @param  None
  * @retval None
  */
/*void PPP_IRQHandler(void)
{
}*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 3 GPIO 输出

# 3.1 简介

  • GPIO(General Purpose Input Output)通用输入输出口
  • 可配置为8种输入输出模式
  • 引脚电平:0V~3.3V,部分引脚可容忍5V
  • 输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等
  • 输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等

# 3.2 GPIO 基本结构

所有的 GPIO 都是挂载到 APB2 外设总线上。

每个 GPIO 外设有 16 个引脚。

每个 GPIO 模块内包含寄存器和驱动器。寄存器是一段特殊的存储器,内核可以通过 APB2 总线对寄存器进行读写。

寄存器每一位对应一个引脚,但是 stm32 内部寄存器都是有 32 位,所以 GPIO 模块内的寄存器只有低 16 位有效。

输出寄存器写 1,对应引脚就会输出高电平,输入寄存器读 0,则代表对应端口是低电平。

寄存器只负责存储数据,驱动器负责增大驱动能力。

# 3.3 GPIO 位结构

下图取自参考手册 8.1 GPIO功能描述 一节

左边是寄存器,中间是驱动器,右边是 I/O 引脚。

上面是输入部分,下面是输出部分。

I/O 引脚

保护二极管,对输入电路进行保护,避免过高电压对内部电路产生伤害。

输入驱动器

为了避免引脚悬空导致的输入数据不确定,加上了弱上拉电阻/弱拉电阻。

引脚悬空(引脚什么都不接),通过弱上拉/弱下拉电阻保证引脚的高/低电平。

上拉/下拉就是默认为高电平/低电平的输入模式。

弱上拉/弱下拉可尽量避免正常的输入操作。

施密特触发器

如果输入电压小于某一阈值,输出值就会瞬间降为低电平,输入电压大于某一阈值,输出值就会瞬间升为高电平。

对输入电压进行整形,有效避免信号波动造成的输出抖动现象。

整形后的波形就可以直接写入输入数据寄存器了。

输入数据寄存器

读取输入数据寄存器某一位的数据就可以知道端口的输入电平了。

输入至片上外设

模拟输入连接到 ADC 模数转换上,进入施密特触发器前就接入。

复用功能输入连接到其他需要读取端口的外设上,比如串口的输入引脚。

输出部分

输出控制可以由输出数据寄存器或片上外设控制,两种控制方式通过数据选择器接到了输出控制部分。 如果选择通过输出数据寄存器控制,就是普通的 I/O 口输出,写寄存器的某一位就可以操作对应的某个端口 了。

位设置/清除寄存器可用来单独操作输出数据寄存器的某一位,而不影响其他位。数据输出寄存器同时控制 16 个端口,只能整体读写,所以想要单独控制某一个端口而不影响其他端口的话,需要一些特殊的操作方式:

  1. 先读出寄存器,通过位运算操作更改某一位。再将更改后的数据写回去,麻烦,效率低。
  2. 设置位设置/清除寄存器。如果需要对某一位置1,在位设置寄存器的对应位写1,其余不需要操作的位写 0 即可。 想要对某一位清0,就在位清除寄存器对应位写 1,不需要操作的位置 0。
  3. 读写位带区域,stm32 中专门分配了一段地址区域,位带别名区,这段地址映射了 RAM 和外设寄存器所有的位,读写 这段地址中的数据,就相当于读写所映射位置的某一位。对位带别名区的一个字(32位)的操作,可以实现操 作对应的的位带区域的一个位。

输出驱动器

两个 MOS 管就是电子开关,信号控制开关的导通和关闭开关负责将 I/O 口接入到 VDD 或 VSS。

有推挽、开漏、关闭三种输出方式。

推挽输出模式下,也叫强推输出模式,两个 mos 管均有效,数据寄存器为 1 ,上管导通,数据寄存器为 0时,下管导通,输出输出电平。高低电平均有较强的驱动能力。这种模式下,stm32 对 I/O 口有绝对的控 制权。

开漏输出下,P-MOS 无效,这种模式下,只有低电平有驱动能力,高电平没驱动能力,数据寄存器为 1 ,输 出相当于端口,也就是高阻模式。可以作为通信协议的驱动方式,如 IIC 的引脚,可以避免多机通信下各个 设备的相互干扰。还可以用用于输出 5V 的电平信号,在 I/O 口接一个上拉电阻到 5V 的电源,用于兼容 一些 5V 电平的设备。一般用于线与和电流型驱动。

关闭模式,两个 MOS 管都无效,输出关闭,端口的电平由外部信号来控制。

# 3.4 GPIO 的模式

设置为浮空输入的时候,端口一定要接入一个连续的驱动源,不能出现悬空的状态。配置为输入时,输出驱动 是断开的,只能输入不能输出。

模拟输入是 ADC 模数转换器的专属配置,此时输入的施密特触发器也是关闭的无效状态。

输出模式下,输入也是有效的,一个端口只能有一个输出,但是可以有多个输入。

复用输出,引脚电平由片上外设控制,不连接输出数据寄存器,引脚控制权转移到片上外设,由片上外设控制。 此时片上外设也可以读取引脚的电平,因为输入是有效的。

只有模拟输入会关闭数字输入功能,其他模式下输入都是有效的。

GPIO 的输出速度设计是为了低功耗和稳定性,可以限制输出引脚的最大翻转速度,一般为 50MHz。

GPIO 的寄存器使用可查阅参考手册 8.2 节

共有 7 个寄存器:

  1. 端口配置低寄存器
  2. 端口配置高寄存器
  3. 端口输入数据寄存器
  4. 端口输出数据寄存器
  5. 端口位设置/清除寄存器
  6. 端口位清除寄存器
  7. 端口配置锁定寄存器

查看参考手册 2.1 系统构架,可得知外设挂载在哪个总线。

操作 GPIO 3个步骤:

  1. 使用 RCC 开启外设时钟。
  2. 使用 GPIO_init 初始化 GPIO。
  3. 使用输出或输入的函数控制 GPIO口。

# 3.5 硬件电路

# 3.6 LED 和 蜂鸣器

  • LED:发光二极管,正向通电点亮,反向通电不亮
  • 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定
  • 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音

单片机一般倾向使用低电平驱动,很多单片机或芯片都使用了高电平弱驱动,低电平强驱动的规则。 上面是低电平驱动,下面是高电平驱动。

三极管连接引脚的是基极,带箭头的是发射极,剩下的是集电极。三极管的通断需要在发射极和基极之间产生 一定的开启电压。

# 3.7 面包板

# 4 GPIO 输入

# 4.1 按键

按键:常见的输入设备,按下导通,松手断开

按键抖动:由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动

# 4.2 传感器

传感器模块:传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出

江协科技:

华清远见:

电路中一端接在电路中,一端接地的可能是滤波电容,用来保证电路稳定,并不是电路主要框架。

定值电阻和传感器的分压电路。

分析传感器电阻的阻值变化对输出电压的影响。

运算放大器当作比较器的情况:

同向输入端的电压大于反相输入端的电压时,输出就会瞬间升高为最大值也就是输出接 VCC,反之,输出接地。 这样就可以对模拟电压进行二值化。

# 4.3 硬件电路

按键

江协科技:

1 需要设置 IO口为上拉输入。

2 引脚不会出现悬空状态,可以设置为 上拉输入或者浮空输入。

3 需要设置为下拉输入。

4 引脚不会出现悬空状态,可以设置为 下拉输入或者浮空输入。

华清:

K1 按下,PA0 得到低电平,K1 松开,PA0 得到高电平。

传感器

江协科技:

DO 数字输出,接 PA 0

AO 模拟输出

华清:

# 4.4 按键控制和光敏传感器控制

stm32f103rc 光敏传感器需要使用 ADC。

TODO:

# 5 OLED 调试工具

# 5.1 调试方式

  • 串口调试,通过串口通信,将调试信息发送到电脑端,电脑使用串口助手显示调试信息。
  • 显示屏调式,直接将显示屏连接到单片机,将调试信息打印在显示屏上。
  • keil 调试模式,借助Keil软件的调试模式,可使用单步运行、设置断点、查看寄存器及变量等功能。

# 5.2 OLED 显示屏

  • OLED(Organic Light Emitting Diode):有机发光二极管
  • OLED显示屏:性能优异的新型显示屏,具有功耗低、相应速度快、宽视角、轻薄柔韧等特点
  • 0.96寸OLED模块:小巧玲珑、占用接口少、简单易用,是电子设计中非常常见的显示屏模块
  • 供电:3~5.5V,通信协议:I2C/SPI,分辨率:128*64

分为四针脚和七针脚的版本,四针脚一般是 iic 协议,七针脚一般是 spi 协议。

# 5.3 硬件电路

华清

`

江协科技

SCL 和 SDA 是 IIC 的通信引脚。

# 5.4 江协科技 OLED 驱动函数

# 6 EXTI 外部中断

# 6.1 中断系统

1. 中断

在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。

中断优先级

当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。

中断嵌套

当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。

# 6.2 中断执行流程

# 6.3 stm32 中断

68个可屏蔽中断通道,是 F1 系列最多的中断源,包含EXTI外部中断、TIM定时器、ADC模数转换器、USART串口、SPI通信、I2C通信、RTC 实时时钟等多个外设

使用内核中的 NVIC 统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级

概念:中断向量表(汇编)。

中断列表可以查看参考手册 9.1 中断和异常向量,表55 其它STM32F10xxx产品(小容量、中容量和大容量)的向量表。

灰色部分是内核中断。

看门狗中断:程序卡死了,没有及时喂狗,看门狗就会申请中断。

PVD 中断:电源电压检测中断,如果供电不足,PVD 电路就会申请中断

中断地址:由于程序中的函数的地址是由编译器分配的,不固定,但是我们的中断跳转,由于硬件的限制,只能跳到固定的地址执行程序,所以为了 能让硬件跳转到不固定的地址执行,就需要在内存中定义一个地址的列表,列表中地址是固定的,中断发生后,就跳转到这个固定位置,然后再由 编译器加上一条跳转到中断函数的代码,这样中断函数就可以跳转到任意位置了。这个中断地址的列表就叫中断向量表。

NVIC 嵌套中断向量控制器,是同一分配和管理中断的,NVIC 是一个内核外设,一个外设可能有多个中断,但 NVIC 只有一个输出口, NVIC 根据每个中断的优先级分配中断的先后顺序,之后通过输出口告诉 CPU 处理哪个中断。

NVIC 基本结构如下:

NVIC 优先级分组

  • NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
  • 抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队

优先级,值越小优先级越高。

# 6.4 EXTI

  • Extern Interrupt 外部中断
  • EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
  • 支持的触发方式:上边沿/下边沿/双边沿/软件触发
  • 支持的 GPIO 口: 所有 GPIO 口,但相同的 Pin 不能同时触发中断。
  • 通道数:16 个 GPIO_Pin,外加 PVD 输出、RTC 闹钟、USB 唤醒、以太网唤醒。
  • 触发方式:中断响应/事件响应

外部中断有个功能:就是从低功耗模式的停止模式下唤醒 stm32,对于 PVD 电源电压检测, 当电源从电压过低恢复时,就需要 PVD 借助一下外部中断退出停止模式。对 RTC 来说,有时为了省电, RTC 定了一个闹钟后,stm32 会进入停止模式,等到闹钟响的时候再唤醒,这也需要借助外部中断。

事件响应:外部中断的信号不会通向 CPU,而是通向其他外设,用来触发其他外设的操作,比如触发 ADC 转换, 触发 DMA 等。

中断响应是正常的流程:引脚电平变化触发中断,事件响应不会触发中断,而是触发别的外设操作,属于外设之间的联合操作。

NVIC 的相关说明可以查看 编程手册。

# 6.5 EXTI 基本结构

要注意的是 EXTI 5 - 9 触发同一个中断函数,EXTI 10 - 15 触发同一个中断函数。

# 6.6 AFIO 复用 I/O 口

从 PA0/PB0 ... /PG0 中选择一个。

  • AFIO主要用于引脚复用功能的选择和重定义
  • 在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择

# 6.6 EXTI 框图

触发中断首先会置一个挂起寄存器,这相当于是一个中断标志位了,可以读取该寄存器判断是哪个通道触发的中断。 然后在通过中断控制寄存器的控制,中断控制寄存器输出为 1 才执行中断,否则,相当于屏蔽中断。

事件响应也会有一个事件屏蔽寄存器进行控制。最后通过一个脉冲发生器,到其他外设。脉冲发生器的作用就是 给一个电平脉冲,用来触发其他外设的动作。

# 6.7 什么时候需要使用外部中断?

使用外部中断的特性

想要获取的信号是外部驱动的很快的突发信号。信号是突发的,是外部驱动的,stm32 只能被动读取,且信号非常快。

如旋转编码器、红外摄像头。

但是按键除外,因为外部中断不好处理按键抖动和松手检测的问题,而且按键的输出波形也不是转瞬即逝的。 可以在主循环循环读取或者定时器中断读取方式。

# 6.8 旋转编码器

旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向

类型:机械触点式/霍尔传感器式/光栅式

单相输出不能测方向。

带正交波形信号输出的编码器,是可以用来测方向的。

触点接触形式的编码器一般用于调节音量等,不适合电机这种高速旋转的地方。

非接触的霍尔传感器式类可以用于电机测速。

硬件电路

旋转轴旋转时,两个触点以相位相差 90 度的方式交替导通,配合外围电路输出高低电平。

A、B接了外拉电阻,默认没旋转的时候,是高电平。旋转时,被拉低到 GND,通过 R3 输出,就是低电平。

R3 是输出限流电阻,为了防止模块引脚电流过大的。

C1 是输出滤波电容,防止一些输出信号抖动。

# 6.9 编程手册和参考手册

内核相关的如 NVIC 可在编程手册内查看,介绍了 NVIC 的一些寄存器如中断使能寄存器、中断清除使能寄存器等。 中断分组的配置在 SCB 章节里,SCB_AIRCR 中可以配置中断分组。

中断向量表、EXTI、中断线路映像、外部中断的寄存器描述等可在参考手册里找到。

# 7 外设中断编程

# 7.1 配置 RCC

  1. 配置 GPIO 时钟使能。
  2. 配置 AFIO 时钟使能。
    // GPIO 14
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    // AFIO 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
1
2
3
4

EXTI 和 NVIC 的时钟一直都是打开的,EXTI 直接由 NVIC 模块控制,并不需要单独的外设时钟。

NVIC 是内核的外设,内核外设是不需要开启时钟的。RCC 管的都是内核外的外设。

# 7.2 配置 GPIO

查看参考手册第 8 节 图 31,可知道 EXTI,推荐配置为:上拉输入,浮空输入,下拉输入。这里我配置为上拉输入。

    // 配置 GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    // GPIO14
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
    // 浮空输入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    // 初始化
    GPIO_Init(GPIOB, &GPIO_InitStructure);
1
2
3
4
5
6
7
8
9

# 7.3 配置 AFIO,选择 GPIO,连接到 EXTI。

    // 配置 AFIO 外部中断引脚选择配置
    // 现在 PB14 引脚的电平信号可以顺利通过 AFIO 进入到 EXTI 电路了。(EXTI14)
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
1
2
3

# 7.4 配置 EXTI,选择边沿触发方式和触发响应方式。

    EXTI_InitTypeDef EXTI_InitStructure;
    // 外部中断引脚是 Pin14
    EXTI_InitStructure.EXTI_Line = EXTI_Line14;
    // 下降沿触发
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    // 使能中断
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    // 中断类型:中断
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_Init(&EXTI_InitStructure);
1
2
3
4
5
6
7
8
9
10

# 7.5 配置 NVIC,给中断选择一个合适的优先级。

    // NVIC 配置
    // 两位抢占,两位响应
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitTypeDef NVIC_InitStructure;
    // 指定通道为 15_10
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    // 使能通道
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    // 设置抢占优先级 1
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    // 设置响应优先级 1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
1
2
3
4
5
6
7
8
9
10
11
12
13

写中断函数,中断函数名从启动文件复制。

中断函数不需要声明,因为是不需要调用的,自动执行。

如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动。

中断函般都是无参数无返回值的。

多个中断可以设置优先级

格式:

void EXTI15_10_IRQHandler(void)
{
    // 执行中断程序
    if (EXTI_GetITStatus(EXTI_Line14) == SET) {
        ++count;
        // 清除中断标志位。
        EXTI_ClearITPendingBit(EXTI_Line14);
    }
}
1
2
3
4
5
6
7
8
9

中断函数注意点:

  1. 中断函数要简短快速,中断是处理突发的事件。
  2. 最好不要再中断函数和主函数调用同一个函数或者操作同一个硬件,多用变量和标志位,减少代码的耦合性,让 各部分代码相互独立。
  3. 记得最后清除中断标志位。

# 8 TIM 定时中断

# 8.1 简介

定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断。

stm32的定时器有 16 位计数器、预分频器、自动重装寄存器的时基单元。

不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式 等多种功能。

根据复杂度和应用场景分为高级定时器、通用定时器、基本定时器。

# 8.2 定时器类型

# 8.3 基本定时器

时基单元:预分频器、计数器、自动重装寄存器。

仅支持向上计数模式。

基本定时器只能选择内部时钟。

预分频器写 0,就是 1 分频,写 2 就是三分频,最高 65536 分频。

当计数值等于自动重装值时,也就是计时时间到了,就会产生一个中断,并且清零计数器。

计数器自动开始下一次的计数。

计数值等于自动重装值产生的中断就是更新中断,更新中断之后就会通往 NVIC,配置好 NVIC 的定时器 通道,定时器的中断就可以得到 CPU 的响应了。

定时器也可以产生更新事件,触发其他电路的工作。

主模式:可以把定时器的更新事件映射到 TRGO(触发输出) 的位置,TRGO 接到要控制的引脚上,就可以 不在需要中断触发事件,不需要软件的参与,实现硬件的自动化。

# 8.4 通用定时器

通用定时器支持向上计数、向下计数、中央对齐模式。

通用定时器可以选择外部时钟,ETR 可以提供时钟,即“外部时钟模式2”,TRGI 可以当作外部时钟,就叫做 “外部时钟模式1”,可以选择 ETR 作为时钟、ITR 作为时钟,时钟源来自其他定时器。

TRGO 也可以通向其他定时器,ITR0 到 ITR3 分别来自其他定时器的 TRGO 输出。

定时器和 ITR 的连接关系:

定时器的级联举例:

初始化 TIM3,使用主模式把它的更新事件映射到 TRGO,然后再初始化 TIM2,选择 ITR2,也就是 TIM3 的 TRGO,再选择时钟为外部时钟模式1,这样 TIM3 的更新事件就可以驱动 TIM2 的时基单元,也就 实现了定时器的级连。

时钟还可以通过 T1F_ED 获得,它连接的是输入捕获单元的 CH1 引脚,从 CH1 引脚获得时钟,ED 后缀就是 edge(边沿)的意思。 上升沿、下降沿都有效。

时钟还能通过 TI1FP1 和 TI1FP2 获得,TI1FP1 是 CH1 引脚的时钟,TI2FP2 是ch2 引脚的时钟。

定时器的编码器接口可以读取正交编码器的输出波形。

定时器的主模式输出可以把内部的一些事件映射到这个 TRGO 引脚上,比如将更新事件映射到 TRGO,触发 DAC。

输出比较电路有 4 个通道,可以用于输出 PWM 波形、驱动电机。

输入捕获电路有 4 个通道,可以用于测量输入方波的频率。

两个电路引脚共用,不能同时使用。

捕获/比较寄存器是输入捕获和输出比较电路共用的。

总结

外部时钟模式 1 的输入可以是 ETR 引脚、其他定时器、CH1 引脚的边沿、CH1 引脚和 CH2 引脚。

# 8.5 高级定时器

申请中断的地方,增加了重复次数计数器,可以实现每隔几个计数周期才发生一次更新事件和更新中断。 相当于对输出的更新信号又做了一次分频。

DTG 是死区生成电路,右边这里的输出引脚由原来的一个变为了两个互补的输出,可以输出一对互补的 PWM 波,这些电路是为了驱动三相无刷电机。

死区生成电路可以在开关切换的瞬间,产生一定时长的死区,让桥臂的上下管全都关断,防止直通现象。

刹车输入部分是为了给电机驱动提供安全保障的,如果外部引脚产生了刹车信号,或者内部时钟失效,产生 故障,控制电路会自动切断电机的输出,防止意外产生。

# 8.6 定时中断基本结构

运行控制:比如启动、停止、向上、向下计数等等,操作寄存器控制时基单元的运行。

高计计时器会在中断输出控制前会有个重复次数计数器。

定时器中会产生很多中断,因此需要一个中断输出控制,需要的中断就运行,不需要就禁止。

预分频寄存器为防止计数中途更改数值造成错误,设置了缓冲寄存器,缓冲寄存器才是真正起作用的寄存器。

引入影子寄存器的作用是为了同步,就是让值的变化和更新事件同步发生,防止在运行中更改造成错误。

# 8.7 时钟树

时钟是外设运行的基础,所以时钟要最先配置。(详见 system_stm32f10x.c 的 SystemInit 函数。)

时钟树,左边是时钟的产生电路,右边是时钟的分配电路。

SYSCLK 就是系统时钟(72MHz)

时钟产生电路,有 4 个振荡源:

  1. 内部 8MHz 的高速 RC 振荡器。(HSI RC)
  2. 外部 4-16MHz 高速石英晶体振荡器。(HSE OSC),一般 8 MHz。
  3. 外部 32.768KHz 低速晶振。(LSE OSC),一般给 RTC 提供时钟。
  4. 内部 40KZh 低速振荡器。(LSI RC),可以给看门狗提供时钟。

石英振荡器比内部的 RC 振荡器更稳定,一般使用外部的石英晶振。

st 公司的 SystemInit 函数中这样配置时钟:

  1. 选择内部 HSI 作为系统时钟,暂时以 8MHz 运行。
  2. 启动外部时钟,HSE,经过锁相环,进行 9 倍频,得到 72 MHz。
  3. 锁相环输出稳定后,选择锁相环作为系统时钟。

如果外部晶振出问题了,系统时钟就会变为 8 MHz。

CSS 监测外部时钟的运行状态,一旦外部时钟失效,就会自动把时钟切换为内部时钟。

保证系统时钟的运行,防止程序卡死造成事故。

AHB 总线有个预分频器,SystemInit 中配置的分配系数为 1。

进入 APB1 总线,配置的分配系数是 2,所以,APB1 总线的时钟是 36 MHz。

APB1 上挂载的定时器的时钟都是 72MHz。

APB2 时钟 72MHz。

与门上的外设时钟使能,能控制时钟的开启/关闭,控制时钟能否输出给外设。

# 8.8 内部时钟使用

以 TIM2 通用定时器的使用为例

1. 开启时钟

    // * 开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
1
2

2. 选择时基单元的时钟

可以不写,定时器默认调用的内部时钟。

    // * 选择时基单元时钟
    // 选择内部时钟
    TIM_InternalClockConfig(TIM2);
1
2
3

3. 配置时基单元

    // * 配置时基单元
    TIM_TimeBaseInitTypeDef Tim_TimeBaseInitStructure;
    // 滤波器采样频率,随便设置即可。
    Tim_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    // 向上计数
    Tim_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    // 重复计数器,非高级计数器,赋值 0 即可。
    Tim_TimeBaseInitStructure.TIM_RepetitionCounter = 0x00;
    // 要让定时器 2 每 1s 触发一次更新中断,
    // ARR 和 PSC 都是 16 位寄存器,最高可设置 65535。
    // 内部时钟频率是 72MHz,所以 预分频系数 1 的情况下,72000000 次才能计时 1s
    // 72000000 > 65535,ARR 无法设置,所以对系统时钟进行分频,10000 分频后,f 为 7200
    // 所以,10000 分频后,频率为 7200 次,计数 7200 次为 1s。

    // PSC 预分频系数。
    Tim_TimeBaseInitStructure.TIM_Prescaler = 10000 - 1;
    // ARR 自动重装器的值,周期,0 - 7199
    Tim_TimeBaseInitStructure.TIM_Period = 7200 - 1;
    TIM_TimeBaseInit(TIM2, &Tim_TimeBaseInitStructure);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

4. 使能更新中断

注意:初始化时基单元后为了让写入的值立刻起作用,会手动生成一个更新事件,让预分频器的值有效。 更新事件和更新中断是同时发生的,所以会导致一初始化完就会立刻进入更新中断。

所以需要在更新中断开启前,手动清除中断标志位。

开启更新中断到 NVIC 的通路。

    // * 使能更新中断
    // 先清除中断标志位
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
1
2
3
4

5. NVIC

记得NVIC 优先级分组。(整个程序只能定义一次。)

    // * 中断分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    // * NVIC 配置
    NVIC_InitTypeDef NVIC_InitStructure;
    // IRQ 通道
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    // 抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    // 响应优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
1
2
3
4
5
6
7
8
9
10
11
12

6. 启动定时器

    // * 启动定时器
    TIM_Cmd(TIM2, ENABLE);
1
2

7. 写中断函数

extern size_t num;
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
        num++;
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}
1
2
3
4
5
6
7
8

# 8.9 外部时钟使用

查询数据手册可知,PA0 默认复用功能是 TIM2_CH1_ETR 即外部时钟通道。

查看参考手册 8.1.11 外设的 GPIO 配置,可知, TIM2/3/4/5_ETR 外部触发时钟输入推荐是浮空输入。

示例:让 对射式红外传感器作为时钟源。

/******************************************************************************
 *     Copyright:  (C) 2023 shachi
 *                 All rights reserved.
 *
 *      Filename:  .\system\tim2.c
 *   Description:  This file
 *
 *       Version:  0.1.0
 *        Author:  shachi <shachi1758@outlook.com>
 *
 ******************************************************************************
 *                                ChangeLog
 *
 * +------------+----------------------------------------------------+--------+
 * |    Date    |                      Change                        | Author |
 * +------------+----------------------------------------------------+--------+
 * | 2023-07-14 |                   Create file.                     | shachi |
 * +------------+----------------------------------------------------+--------+
 *
 *****************************************************************************/
#include "stm32f10x.h"
void tim2_init(void)
{
    // * 开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // * 初始化 GPIOA
    GPIO_InitTypeDef GPIO_InitStructure;
    // 上拉输入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // * 选择时基单元时钟
    // 选择内部时钟
    //TIM_InternalClockConfig(TIM2);
    // 选择外部时钟
    // 不反向(上升沿有效)、不分频、无滤波器
    TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted, 0x00);

    // * 配置时基单元
    TIM_TimeBaseInitTypeDef Tim_TimeBaseInitStructure;
    // 滤波器采样频率,随便设置即可。
    Tim_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    // 向上计数
    Tim_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    // 重复计数器,非高级计数器,赋值 0 即可。
    Tim_TimeBaseInitStructure.TIM_RepetitionCounter = 0x00;
    // 要让定时器 2 每 1s 触发一次更新中断,
    // ARR 和 PSC 都是 16 位寄存器,最高可设置 65535。
    // 内部时钟频率是 72MHz,所以 预分频系数 1 的情况下,72000000 次才能计时 1s
    // 72000000 > 65535,ARR 无法设置,所以对系统时钟进行分频,10000 分频后,f 为 7200
    // 所以,10000 分频后,频率为 7200 次,计数 7200 次为 1s。

    // PSC 预分频系数。
    Tim_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
    // ARR 自动重装器的值,周期
    Tim_TimeBaseInitStructure.TIM_Period = 10 - 1;
    TIM_TimeBaseInit(TIM2, &Tim_TimeBaseInitStructure);

    // * 使能更新中断
    // 先清除中断标志位
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    // * NVIC 配置
    NVIC_InitTypeDef NVIC_InitStructure;
    // IRQ 通道
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    // 抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    // 响应优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);

    // * 启动定时器
    TIM_Cmd(TIM2, ENABLE);
}
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

# 9 TIM 输出比较

OC(output compare)输出比较

IC(input caputre)输入捕获

CC(Capture/Compare)输出比较/输入捕获

  • 输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
  • 每个高级定时器和通用定时器都拥有4个输出比较通道
  • 高级定时器的前3个通道额外拥有死区生成和互补输出的功能

CCR 捕获/比较寄存器,输入捕获和输出比较共用的寄存器。

# 9.1 PWN 脉冲宽度调制

在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域

频率 = 1 / TS

占空比 = TON / TS

分辨率 = 占空比变化步距,也就是占空比变化的精细程度,如 1%

# 9.2 输出比较模式

# 9.3 PWM 基本结构

PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1),就等于计数器的更新频率。

PWM占空比: Duty = CCR / (ARR + 1)

PWM分辨率: Reso = 1 / (ARR + 1)

# 9.4 舵机

舵机是一种根据输入PWM信号占空比来控制输出角度的装置

输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms

# 9.5 直流电机

  • 直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
  • 直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
  • TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

# 9.6 呼吸灯

产生一个频率为 1KHz,占空比为 50%,分频率为 1% 的 PWM 波形。

选择 TIM3,通道 2,查询数据手册可知,为 PA7 引脚。

1. 开启时钟

TIM、GPIO 时钟开启。

    // * 开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    // GPIOA
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
1
2
3
4

2. 配置时基单元

建议先给结构体赋初始值,再修改需要的成员。

    // * 配置时基单元
    // 配置时基单元时钟为内部时钟
    TIM_InternalClockConfig(TIM3);

    TIM_TimeBaseInitTypeDef Tim_TimeBaseInitStructure;
    TIM_TimeBaseStructInit(&Tim_TimeBaseInitStructure);
    // 频率 1KHz,分频率 1 %
    // 因为 分频率 1%,所以 周期为 = 100,ARR = 99
    // 频率为 1KHz,那么需要 100 个周期定时 1/1000 s,
    // 即周期为:1/1000/100 = 1/100000。
    // 频率为 100KHz
    // PSC 预分频系数。72MHz / 72 / 10 = 100KHz,计数 100 次就是 1ms
    Tim_TimeBaseInitStructure.TIM_Prescaler = 720;
    // ARR 自动重装器的值,周期,
    Tim_TimeBaseInitStructure.TIM_Period = 100 - 1;
    TIM_TimeBaseInit(TIM3, &Tim_TimeBaseInitStructure);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

3. 配置输出比较单元

    // * 配置输出比较单元
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);
    // 设置为 PWM 模式 1
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    // 有效电平是高电平
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    // 输出使能
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    // 设置占空比
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OC2Init(TIM3,&TIM_OCInitStructure);
1
2
3
4
5
6
7
8
9
10
11
12

4. 配置 GPIO

定时器的输出比较通道借用 GPIO,查看数据手册的引脚定义,默认复用功能。

pwm 对应的 GPIO 口若要作为输出比较通道,要初始化为复用推挽输出,将引脚的控制权转移给片上外设。(参考手册 8.1.11 外设的 GPIO 配置)

    // * 配置 GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    // 复用推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure);
1
2
3
4
5
6
7
8

5. 运行控制

    // * 使能定时器
    TIM_Cmd(TIM3, ENABLE);


    pwm_led_init();
    int32_t i;
    while (true) {
        for (i = 0; i <= 100; ++i) {
            TIM_SetCompare2(TIM3, i);
            Delay_ms(10);
        }
        for (i = 100; i >= 0; --i) {
            TIM_SetCompare2(TIM3, i);
            Delay_ms(10);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

引脚重映射

可以使用引脚重映射 GPIO_PinRemapConfig() 函数来进行引脚重映射。

具体可看数据手册和参考手册。

如:可把 TIM3_CH2 重映射到 pb5 或 pc7。

# 9.7 舵机使用实例

使用 pa1,TIM2_CH2。

舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度 控制脉冲部分。以180度角度伺服为例,那么对应的控制关系是这样的:

0.5ms--------------0度;

1.0ms------------45度;

1.5ms------------90度;

2.0ms-----------135度;

2.5ms-----------180度;

#include "stm32f10x.h"

enum sg90_angle {
    SG90_0 = 500,
    SG90_45 = 1000,
    SG90_90 = 1500,
    SG90_135 = 2000,
    SG90_180 = 2500
};

void pwm_sg90_init(void)
{
    // * 开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    // GPIOA
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    // * 配置时基单元
    // 配置时基单元时钟为内部时钟
    TIM_InternalClockConfig(TIM2);

    TIM_TimeBaseInitTypeDef Tim_TimeBaseInitStructure;
    TIM_TimeBaseStructInit(&Tim_TimeBaseInitStructure);
    // 时基脉冲周期 20ms
    // 进行 1000 分频,72MHz / 72 = 1MHz,即 计数 1000000次。
    // 1ms 需要计数 1000 次。
    // 需要定时 20ms ,所以计数 1000 * 20 = 20000,ARR 就为 20000 - 1
    Tim_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;
    // ARR 自动重装器的值,周期,
    Tim_TimeBaseInitStructure.TIM_Period = 20000 - 1;
    TIM_TimeBaseInit(TIM2, &Tim_TimeBaseInitStructure);

    // * 配置输出比较单元
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);
    // 设置为 PWM 模式 1
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    // 有效电平是高电平
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    // 输出使能
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    // 设置占空比
    TIM_OCInitStructure.TIM_Pulse = SG90_0;
    TIM_OC2Init(TIM2,&TIM_OCInitStructure);

    // * 配置 GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    // 复用推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // * 使能定时器
    TIM_Cmd(TIM2, ENABLE);
}
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

# 9.8 直流电机使用示例

使用 TB6612FNG 驱动 1 路电机。

选择 TIM3_CH1 作为 pwm 输出控制速度,查询数据手册,为 pa6 引脚。

stby 接 3.3v。

pa4、pa5 作为控制引脚。


#include "stm32f10x.h"                  // Device header
#include <stdint.h>

void pwm_dcmotor_init(void)
{
    // * 开启时钟
    // GPIOA 和 TIM3 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    // * 配置 GPIO
    // 配置 pa4、5、6
    // pa4,pa5 设置为 通用推挽输出
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_4;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // 查询参考手册,将 pa6 设置为复用推挽输出。
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_ResetBits(GPIOA, GPIO_Pin_4 | GPIO_Pin_5);

    // * 配置时基单元
    TIM_InternalClockConfig(TIM3);
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    // 结构体赋默认值
    // 默认向上计数
    TIM_TimeBaseStructInit(&TIM_TimeBaseInitStructure);
    // 设置 预分频系数和周期值
    // 预分频后频率为 100KHz,周期 100, 1ms,频率为 1KHz
    TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;
    TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);

    // * 配置输出比较单元
    TIM_OCInitTypeDef TIM_OCInitStructure;
    // 结构体默认赋值。
    TIM_OCStructInit(&TIM_OCInitStructure);
    // 输出比较模式
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    // 输出比较有效电平设置为高电平有效
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    // 输出使能
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    // 占空比。
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);

    //* 使能定时器
    TIM_Cmd(TIM3, ENABLE);
}

void dcmotor_set_speed(int8_t Speed)
{
	if (Speed >= 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_4);
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);
		TIM_SetCompare1(TIM3, Speed);
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_4);
		GPIO_SetBits(GPIOA, GPIO_Pin_5);
		TIM_SetCompare1(TIM3, -Speed);
	}
}

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
63
64
65
66
67
68
69
70
71
72

# 10 TIM 输入捕获

# 10.1 简介

  • 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
  • 每个高级定时器和通用定时器都拥有4个输入捕获通道
  • 可配置为 PWMI 模式,同时测量频率和占空比
  • 可配合主从触发模式,实现硬件全自动测量

# 10.2 频率测量

测频法:在闸门时间T内,对上升沿计次,得到N,则频率 𝑓_𝑥=𝑁,适合高频信号。

测周法:两个上升沿内,以标准频率fc计次,得到N ,则频率 𝑓_𝑥=𝑓_𝑐 / 𝑁,适合低频信号。

中界频率:测频法与测周法误差相等的频率点,𝑓_𝑚=√(𝑓_𝑐 / 𝑇)

# 10.3 输入捕获电路

ch1、ch2、ch3 三个通道接入了一个三输入的异或门,是为三相无刷电机服务的。

通道 1 和通道 2 可以各自独自连接,也可以选择进行交叉,通道 3 和通道 4 同理,是为了把一个引脚的 输入,同时映射到两个捕获单元,两个通道对同一个引脚进行捕获,就可以同时测量频率和占空比。

TRC 信号也可以作为捕获信号的输入,是为了无刷电机的驱动。

每来一个触发信号,CNT 的值就会向 CCR 转运一次,同时发生一个捕获事件,在状态寄存器置标志位,可以 产生中断。

# 10.4 主从触发模式

主从触发模式是主模式、从模式、触发源选择三个功能的简称。

主模式可以将定时器内部的信号,映射到 TRGO 引脚,用于触发别的外设。

从模式接收其他外设或者自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制。

触发源选择就是选择从模式的触发信号源。

具体可查看 TIMx 控制寄存器、从模式控制寄存器。

14.3.5 定时器同步 章节介绍,主从触发模式可以

  1. 使用一个定时器作为另一个定时器的预分频器,也就是定时器级联。
  2. 一个定时器使能另一个定时器。
  3. 一个定时器启动另一个定时器。
  4. ……

# 10.5 输入捕获基本结构

举例:TI1FP1 出现上升沿,CNT 的值转运到 CCR1 中,同时触发源选择,选择 TI1FP1 为触发信号,从 模式选择复位操作,触发 CNT 清零。

注意:

  1. CNT 和 ARR 均为 16 为寄存器,最多只能存储 0 ~ 65535。
  2. 从模式的触发源选择,没有 TI3 和 TI4 的信号,通道 3 和 通道 4 只能开启捕获中断,在中断里 操作。

# 10.6 PWMI 基本结构

使用了两个通道同时捕获一个引脚,可以同时测量频率和占空比。

举例:TI1FP1 设为上升沿触发,TI2FP2 设为下降沿触发,并将 CNT 的值写入 CCR2,但是并不将 CNT 清零,这样就能计算高电平的计数值从而算出占空比。

# 10.7 输入捕获模式测频率

输出比较模式下,CCR 是只写的,要用 SetCompare() 写入,输入捕获模式下,CCR 是只读的,要用 GetCapture() 读出。

1. 开启时钟

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
1
2

2. 配置 GPIO

GPIO 模式根据参考手册设置为浮空输入。

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
1
2
3
4

3. 配置时基单元

    // 设置时钟为内部时钟
	TIM_InternalClockConfig(TIM3);
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;		//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;		//PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
1
2
3
4
5
6
7
8
9

4.初始化输入捕获单元

	TIM_ICInitTypeDef TIM_ICInitStructure;
    // 通道一
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    // 输入捕获的滤波器,增大滤波器参数,避免干扰
    // 一般远高于信号频率,滤除高频噪声,使信号更平滑
	TIM_ICInitStructure.TIM_ICFilter = 0xF;
    // 边沿检测极性选择,上升沿触发
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    // 触发信号分频器
    // 2 分频就是每隔一次有效一次
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    // 配置数据选择器,触发信号从哪个引脚输入,直连或交叉
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

5. 配置 TRGI 的触发源为 TI1FP1

	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
1

6. 配置从模式为 ReSet

	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
1

7. 启动定时器

	TIM_Cmd(TIM3, ENABLE);
1

# 10.8 PWMI 模式测量频率和占空比

示例:



1
2

# inbox

# 任意组合多个外设

初始化外设可以用 | 一次性初始化多个外设,也可以一次性初始化多个时钟。

例:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOB, ENABLE);
GPIO_ResetBits(GPIOC,GPIO_Pin_13|GPIO_Pin_12);
1
2

# 引脚不初始化

stm32 引脚上电后,如果不初始化,默认是浮空输入模式。引脚不会输出电平。

# 需要哪些手册?

  1. 数据手册
  2. 参考手册
  3. 编程手册,内核和内核外设的详细介绍。
  4. 原理图
  5. 外设手册

# keil5.38 debug配置STlink调试,软件闪退

下载旧版本的 ST-Link 覆盖 keil 目录下即可。

https://blog.csdn.net/sinat_29891353/article/details/82778695

问题:https://developer.arm.com/documentation/ka005381/1-0?lang=en&rev=

# SysTick

CM4内核的处理和CM3一样,内部都包含了一个SysTick定时器,SysTick 是一个24 位的倒计数定时器, 当计到0 时 ,将 从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的 使能位清除,就永不停息。我们就是利用STM32的内部SysTick来实现延时的,这样既不占用中断,也不占用 系统定时器。

SysTick 有 4 个寄存器:

typedefstruct
{
  __IO uint32_t CTRL;                    /*!< Offset: 0x000(R/W)  SysTick Control and StatusRegister */
  __IO uint32_t LOAD;                    /*!< Offset: 0x004(R/W)  SysTick Reload Value Register       */
  __IO uint32_t VAL;                     /*!< Offset: 0x008(R/W)  SysTick Current ValueRegister      */
  __I uint32_t CALIB;                  /*!< Offset: 0x00C (R/ ) SysTick Calibration Register       */
} SysTick_Type;
1
2
3
4
5
6
7

CTRL 控制寄存器

第0位:ENABLE,Systick 使能位 (0:关闭Systick功能;1:开启Systick功能)

第1位:TICKINT,Systick 中断使能位 (0:关闭Systick中断;1:开启Systick中断)

第2位:CLKSOURCE,Systick时钟源选择 (0:使用HCLK/8 作为Systick时钟;1:使用HCLK作为Systick时钟)

第16位:COUNTFLAG,Systick计数比较标志,如果在上次读取本寄存器后,SysTick 已经数到了0,则该位为1。如果读取该位,该位将自动清零。

LOAD 寄存器:

Systick是一个递减的定时器,当定时器递减至0时,重载寄存器中的值就会被重装载,继续开始递减。STK_LOAD 重载寄存器是个24位的寄存器最大计数0xFFFFFF。

VAL 寄存器

也是个24位的寄存器,读取时返回当前倒计数的值,写它则使之清零,同时还会清除在SysTick 控制及状态寄存器中的COUNTFLAG 标志。

CALIB 寄存器

一般不会用到。

最后更新: 2023/9/12 08:24:09