[TOC]

1 搭建STM32的开发环境

1.1 MDK是什么?

我们开发STM32使用HAL或者是3.5标准库函数都是使用keil MDK。后面可以使用VSCODE开发。MDK 即RealView MDK 或MDK-ARM(Microcontroller Development kit),是 ARM 公司收购Keil公司以后,基于uVision界面推出的针对ARM7、ARM9、Cortex-M0、Cortex-M1、Cortex-M2、Cortex-M3、Cortex-R4等ARM处理器的嵌入式软件开发工具。MDK-ARM 集成了业内最领先的技术,包括 uVision4 集成开发环境与 RealView 编译器RVCT。支持 ARM7、ARM9 和最新的Cortex-M3/M1/M0 核处理器,自动配置启动代码,集成 Flash 烧写模块,强大的 Simulation 设备模拟,性能分析等功能,与 ARM 之前的工具包 ADS 等相比,RealView 编译器的最新版本可将性能改善超过 20%。

1.2 MDK安装—Keil

1.2.1 安装前先了解注意

  • 1 安装路径为英文路径(不要是中文路径)。
  • 2 系统用户名不能为中文 。
  • 3 多个版本MDK(Keil)不要安装在同一目录。
  • 4 MDK5需要加载芯片对应的支持包。

1.2.2 找到keiI软件包安装

鼠标右键选择以管理员权限运行。点击next,直到安装结束。

安装完成后在桌面会出现keil5软件图标:

然后再安装相应的芯片支持包:我们用的是stm32f103所以安装1xx系列的支持包。 聖 Keil.STM32F1 xx_DFP.2.0.0.pack

安装注意事项

安装完MDK之后,MDK会自动从网站下载相关包描述,如果电脑没有连网,或者网速较慢,就会报错。这是没有问题的,直接关闭即可,手动来加载包即可。

关于怎么破J使用,请各位自己百度或者查看提供的文件教程

1.3 安装芯片加载的包

安装注意事项

基于不同类型的芯片要加载的包可能不一样。

这些包可以直接点击.pack格式的包(如果有)也可以再mdk里面加载,让MDK从keil官网 http://www.keil.com/dd2/pack下载。

2 keil工程搭建

2.1 目录结构管理

2.2 工程结构管理

2.2.1 打开keil5软件,点击project,


创建新的keil5工程

  • 创建新的Project

2.2.2 2 选择芯片型号(stm32f103c8)

2.2.3 管理keil工程内部的目录结构

2.2.4 文件添加

2.3 配置

2.4 最终项目

3 系统架构(中等容量芯片stm32f103c8)

3.1 主系统架构

  • 四 个 驱 动 单 元

    • Cortex-M3内核
    • Dcode总线

    • Icode总线

    • DMA1&DMA2
  • 四 个 被 动 单 元

    • Internel SRAM
    • Internel Flash
    • FSMC
    • AHB到APB桥

3.2 总线框图

STM32 的系统架构图:

3.3 总线介绍

ICode总线(Instruction Code)

该总线将Cortex™-M3内核的指令总线与闪存指令接口相连接。指令预取在此总线上完成

DCode总线 (Data Code)

该总线将Cortex™-M3内核的DCode总线与闪存存储器的数据接口相连接(常量加载和调试访问)

系统总线

此总线连接Cortex™-M3内核的系统总线(外设总线)到总线矩阵,总线矩阵协调着内核和DMA间的访问

DMA总线

此总线将DMA的AHB主控接口与总线矩阵相联,总线矩阵协调着CPU的DCode和DMA到SRAM、闪存和外设的访问

总线矩阵

总线矩阵协调内核系统总线和DMA主控总线之间的访问仲裁,仲裁利用轮换算法。在互联型产品中,总线矩阵包含5个驱动部件(CPU的DCode、系统总线、以太网DMA、DMA1总线和DMA2总线)和3个从部件(闪存存储器接口(FLITF)、SRAM和AHB2APB桥)。在其它产品中总线矩阵包含4个驱动部件(CPU的DCode、系统总线、DMA1总线和DMA2总线)和4个被动部件(闪存存储器接口(FLITF)、SRAM、FSMC和AHB2APB桥)。AHB外设通过总线矩阵与系统总线相连,允许DMA访问

AHB/APB桥(APB)

两个AHB/APB桥在AHB和2个APB总线间提供同步连接。APB1操作速度限于36MHz, APB2操作于全速(最高72MHz)。有关连接到每个桥的不同外设的地址映射请参考表1。在每一次复位以后,所有除SRAM 和FLITF以外的外设都被关闭,在使用一个外设之前,必须设置寄存器RCC_AHBENR来打开该外设的时钟

4 存储器结构

程序存储器数据存储器寄存器输入输出端口,被组织在同一个4G的线性地址空间中。可以通过地址的方法访问对应的存储器或寄存器。

5 启动模式

我们使用的stm32f103c8核心板

boot0 0
boot1 0

启动方式从内部的Flash中启动存储器映射

0x0000 0000 —— 0x0800 0000映射的内部Flash

6 启动文件

  • cl:互联型产品,stm32f105/107系列
  • vl:超值型产品,stm32f100系列
  • xl:超高密度产品,stm32f101/103系列

flash容量大小

  • ld:小容量产品, 小于64KB
  • md:中等容量度产品,64KB和128KB hd:大容量产品,大于128KB

stm32f103C8 :选择的启动文件->`startup_stm32f10x_md.s

7 启动文件分析

上电或按下复位按键,从Reset_Handler 开始执行。

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

执行的第一个函数:SystemInit

  • 初始化flash接口
  • 初始化设置PLL
  • 初始化设置系统时钟

执行的第二个函数:,main 属于C库函数作用:

  • 完成全局/静态变量的初始化初始化堆栈
  • 库函数的初始化
  • 程序的跳转,进入用户的main函数入口

    启动代码

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
                ;AREA 伪指令,用于定义一个代码段、数据段、栈...
                ;ALIGN=3 ---> 2^3=8 8字节对齐
                ;STACK 段名
                ;NOINIT:指定此数据段仅仅保留了内存单元
                ;READWRITE属性:指定本段为可读可写,数据段的默认属性为READWRITE
====================================================堆、栈=================================================================
Stack_Mem       SPACE   Stack_Size
                ;SPACE 用来分配一片连续的存储区域并初始化为0
                ;Stack_Mem  表示分配0x400个连续字节,并初始化为0

__initial_sp    ;表示栈顶地址,汇编代码地址标号

; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit     ;表示堆空间结束地址
                PRESERVE8
                THUMB
 =================================================中断向量表===============================================================               
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler

                ; External Interrupts
                DCD     WWDG_IRQHandler            ; Window Watchdog
                DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
                DCD     TAMPER_IRQHandler          ; Tamper
                DCD     RTC_IRQHandler             ; RTC
                DCD     FLASH_IRQHandler           ; Flash
                DCD     RCC_IRQHandler             ; RCC
                DCD     EXTI0_IRQHandler           ; EXTI Line 0
                DCD     EXTI1_IRQHandler           ; EXTI Line 1
                DCD     EXTI2_IRQHandler           ; EXTI Line 2
                DCD     EXTI3_IRQHandler           ; EXTI Line 3
                DCD     EXTI4_IRQHandler           ; EXTI Line 4
                DCD     DMA1_Channel1_IRQHandler   ; DMA1 Channel 1
                DCD     DMA1_Channel2_IRQHandler   ; DMA1 Channel 2
                DCD     DMA1_Channel3_IRQHandler   ; DMA1 Channel 3
                DCD     DMA1_Channel4_IRQHandler   ; DMA1 Channel 4
                DCD     DMA1_Channel5_IRQHandler   ; DMA1 Channel 5
                DCD     DMA1_Channel6_IRQHandler   ; DMA1 Channel 6
                DCD     DMA1_Channel7_IRQHandler   ; DMA1 Channel 7
                DCD     ADC1_2_IRQHandler          ; ADC1 & ADC2
                DCD     USB_HP_CAN1_TX_IRQHandler  ; USB High Priority or CAN1 TX
                DCD     USB_LP_CAN1_RX0_IRQHandler ; USB Low  Priority or CAN1 RX0
                DCD     CAN1_RX1_IRQHandler        ; CAN1 RX1
                DCD     CAN1_SCE_IRQHandler        ; CAN1 SCE
                DCD     EXTI9_5_IRQHandler         ; EXTI Line 9..5
                DCD     TIM1_BRK_IRQHandler        ; TIM1 Break
                DCD     TIM1_UP_IRQHandler         ; TIM1 Update
                DCD     TIM1_TRG_COM_IRQHandler    ; TIM1 Trigger and Commutation
                DCD     TIM1_CC_IRQHandler         ; TIM1 Capture Compare
                DCD     TIM2_IRQHandler            ; TIM2
                DCD     TIM3_IRQHandler            ; TIM3
                DCD     TIM4_IRQHandler            ; TIM4
                DCD     I2C1_EV_IRQHandler         ; I2C1 Event
                DCD     I2C1_ER_IRQHandler         ; I2C1 Error
                DCD     I2C2_EV_IRQHandler         ; I2C2 Event
                DCD     I2C2_ER_IRQHandler         ; I2C2 Error
                DCD     SPI1_IRQHandler            ; SPI1
                DCD     SPI2_IRQHandler            ; SPI2
                DCD     USART1_IRQHandler          ; USART1
                DCD     USART2_IRQHandler          ; USART2
                DCD     USART3_IRQHandler          ; USART3
                DCD     EXTI15_10_IRQHandler       ; EXTI Line 15..10
                DCD     RTCAlarm_IRQHandler        ; RTC Alarm through EXTI Line
                DCD     USBWakeUp_IRQHandler       ; USB Wakeup from suspend
                DCD     TIM8_BRK_IRQHandler        ; TIM8 Break
                DCD     TIM8_UP_IRQHandler         ; TIM8 Update
                DCD     TIM8_TRG_COM_IRQHandler    ; TIM8 Trigger and Commutation
                DCD     TIM8_CC_IRQHandler         ; TIM8 Capture Compare
                DCD     ADC3_IRQHandler            ; ADC3
                DCD     FSMC_IRQHandler            ; FSMC
                DCD     SDIO_IRQHandler            ; SDIO
                DCD     TIM5_IRQHandler            ; TIM5
                DCD     SPI3_IRQHandler            ; SPI3
                DCD     UART4_IRQHandler           ; UART4
                DCD     UART5_IRQHandler           ; UART5
                DCD     TIM6_IRQHandler            ; TIM6
                DCD     TIM7_IRQHandler            ; TIM7
                DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1
                DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2
                DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3
                DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End

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

8 STM32 开发基础知识

8.1 学习 操作

学过 C 语言的人都不陌生了,就是对基本类型变量可以在位级别进行操作。C 语言支持如下 6 中位操作:

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

下面我们想着重讲解位操作在单片机开发中的一些实用技巧。

8.1.1 不改变其他位的值的状况下,对某几个位进行设值。

位 操作在单片机开发中经常使用,方法就是先对需要设置的位用&操作符进行清零操作,然后用|操作符设值。比如我要改变 GPIOA 的状态,可以先对寄存器的值进行&清零操作。

GPIOA->CRL&=0XFFFFFF0F; //将第 4-7 位清 0

然后再与需要设置的值进行|或运算

GPIOA->CRL|=0X00000040; //设置相应位的值,不改变其他位的值

8.2.2 移位操作提高代码的可读性。

移位操作在单片机开发中也非常重要,下面让我们看看固件库的 GPIO 初始化的函数里面的一行代码

GPIOx->BSRR = (((uint32_t)0x01) << pinpos);

8.2.3 ~取反操作使用技巧

SR 寄存器的每一位都代表一个状态,某个时刻我们希望去设置某一位的值为 0,同时其他位都保留为 1,简单的作法是直接给寄存器设置一个值:

TIMx->SR=0xFFF7;

这样的作法设置第 3 位为 0,但是这样的作法同样不好看,并且可读性很差。看看库函数代码中怎样使用的:

TIMx->SR = (uint16_t)~TIM_FLAG;

而 TIM_FLAG 是通过宏定义定义的值:

#define TIM_FLAG_Update ((uint16_t)0x0001)
#define TIM_FLAG_CC1 ((uint16_t)0x0002)

看这个应该很容易明白,可以直接从宏定义中看出 TIM_FLAG_Update 就是设置的第 0 位了,可读性非常强。

8.3 define宏定义

define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供方便。常见的格式:

#define 标识符 字符串

“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。例如:

#define SYSCLK_FREQ_72MHz 72000000

8.4 ifdef 条件编译

单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:

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

它的作用是:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,否则编译程序段 2。 其中#else 部分也可以没有,即:

#ifdef 
程序段 1 
#endif

这个条件编译在 MDK 里面是用得很多的,在 stm32f10x.h 这个头文件中经常会看到这样的语句:

#ifdef STM32F10X_HD
大容量芯片需要的一些变量定义
#end

8.5 extern 变量申明

C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。这里面要注意,对于 extern 申明变量可以多次,但定义只有一次。在我们的代码中你会看到看到这样的语句:

extern u16 USART_RX_STA;

这个语句是申明 USART_RX_STA 变量在其他文件中已经定义了,在这里要使用到。所以,你肯定可以找到在某个地方有变量定义的语句:

u16 USART_RX_STA;

8.6 typedef 类型别名

typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。typedef 在 MDK 用得最多的就是定义结构体的类型别名和枚举类型了。

struct _GPIO
{
 __IO uint32_t CRL;
 __IO uint32_t CRH;
 …
}

定义了一个结构体 GPIO,这样我们定义变量的方式为:

struct _GPIO GPIOA;//定义结构体变量 GPIOA

但是这样很繁琐,MDK 中有很多这样的结构体变量需要定义。这里我们可以为结体定义一个别名 GPIO_TypeDef,这样我们就可以在其他地方通过别名 GPIO_TypeDef 来定义结构体变量了。方法如下:

typedef struct
{
 __IO uint32_t CRL;
 __IO uint32_t CRH;
…
} GPIO_TypeDef;

Typedef 为结构体定义一个别名 GPIO_TypeDef,这样我们可以通过 GPIO_TypeDef 来定义结构体变量:

GPIO_TypeDef _GPIOA,_GPIOB;

8.7 结构体

经常很多用户提到,他们对结构体使用不是很熟悉,但是 MDK 中太多地方使用结构体以及结构体指针,这让他们一下子摸不着头脑,学习 STM32 的积极性大大降低,其实结构体并不是那么复杂,这里我们稍微提一下结构体的一些知识,还有一些知识我们会在下一节的“寄存器地址名称映射分析”中讲到一些。声明结构体类型:

Struct 结构体名{

成员列表; 

}变量名列表;

在结构体申明的时候可以定义变量,也可以申明之后定义,方法是:

Struct 结构体名字 结构体变量列表 ;

通过使用结构体组合参数,可以提高代码的可读性。

9 代码下载

STM32F103C8T6烧录下载方法大体上有三种,分别为JTAG下载,SWD下载,以及串口下载。

JTAG下载,JTAG全名为Joint Test Action Group,即为联合测试行动小组,是一种国际标准测试协议。JTAG下载是需要依靠烧录器的,标准的JTAG接口是4线的,包括JTMS, JTCK, JTDI, JTDO,它们的功能分别是模式选择,时钟输入,数据输入和数据输出,烧录器上的引脚对应连接到STM32F103C8T6的I/O口分别为PA13,PA14,PA15,PB3,加上接VCC和GND,所以JTAG接口最少需要6个引脚。同时单片机的BOOT0,BOOT1引脚要接地,具体原因在本节后面详述。

SWD下载,SWD全名为Serial Wire Debug,即为串行调试接口。SWD下载是需要依靠烧录器的,SWD接口是2线的,包括SWDIO,SWCLK,它们的功能分别是数据输入输出和时钟输入,烧录器上的引脚对应对应连接到STM32F103C8T6的I/O口分别为PA13,PA14,加上接VCC和GND,所以SWD接口最少需要4个引脚。同时单片机的BOOT0,BOOT1引脚要接地,具体原因在本节后面详述。

串口下载,通过USART进行烧录下载。串口下载是不需要依靠烧录器的,它直接通过安卓线连接电脑和单片机,其中STM32F103C8T6与电脑相连接的串口为USART1,对应的I/O口为PA9(TX),PA10(RX),一般在开发板上会使用串口下载,简单方便。同时单片机的BOOT0引脚要接高电平,BOOT1引脚要接低电平,具体原因在本节后面详述。

9.1 CH340 串口下载

安装CH340驱动

  • 1.先在电脑安装好CH340的驱动

  • 2.打开STM ISP 下载器MCUISP,并选择好要下载的hex文件

  • 3.将USB 转 TTL下载器(CH340)的TXD、RXD、GND与开发板的RXD、TXD、GND连接好,下载器另一头插电脑

    1. 开发板的BOOT0要用跳冒跳到1端

开发板上有两个跳帽来调整BOOT0和BOOT1的状态。跳帽跳到上边是选择1也就是高电平,跳到下边是选择0也就是低电平BOOT0 和 BOOT1 是用于设置 STM32 的启动方式的

ST-Link v2是STM8、STM32系列单片机的在线仿真器和下载器。STM8采用SWIM接口模式。STM32采用的是SWD接口模式,因此ST-Link出生就带有两种接口模式。

ST-Link V2是支持STM32家族所有芯片型号的存在。因为它的独特功能,使得它比jlink ob要全一点,比如H7系列的STM32,ob款就无法支持。

前端(电脑端) USB接口

后端(mcu端)排针接口10根;有5V、3.3V供电

SWD接线 ( for STM32 ):3.3V、GND、SWDIO、SWCLK

  • 1.先安装好ST-Link的驱动,64位的电脑选择amd64.exe的安装程序,一直下一步

  • 2.驱动安装成功

  • 3.将ST-Link V2插入电脑,在设备管理里可以看到ST-Link的串行口,表示驱动没问题

  • 4.将ST-Link V2的SWDIO、GND、SWCLK、3.3V接到开发板的DIO、GND、CLK、3.3引脚上,有些板的丝印标法不同,但都能对应的看出来,然后将ST-Link插电脑上

  • 5.打开keil软件,并打开一个工程,然后打开魔术棒,选择Debug,选择ST-Link Debugger,再点击Settings

  • 6.Port默认是JTAG的,改为SW

  • 7.在Flash Download界面里,首先下载功能的三个勾要勾上,Reset and Run默认是没有勾的,这个是下载后重启单片机并运行程序,所以有时候下载成功了没看到现象,可能是这里没有勾上;点击Add,看手上的开发板是什么型号,手上的是STM32F103C8T6,就找到STM32F10x系列的,选择中容量Med-density,128k的,最后点击确定

  • 8.回到打开魔术棒的界面,点击Utilities,选择ST-Link Debugger,点击OK

  • 9.回到编程界面,先编译一下程序,确保编译通过,然后点击下载选项

*10.看到Verify OK,则表示程序下载成功,开发板LED灯闪烁