1 DAP仿真器
1.1全速版引脚说明
引脚 | 作用 | 是否必须连接 |
---|---|---|
TMS | 数据引脚 | 必须 |
GND | 接地 | 必须 |
TCK | 时钟引脚 | 必须 |
3V3 | 对外供电 | 非必须 |
RST | 复位引脚 | 非必须 |
备注:
-
① RST未连接时,需要勾选Under Reset。
-
② 仿真器接电脑,红灯亮1s后亮绿灯正常;下载时亮红灯,下载完亮绿灯。
1.2 配置仿真器
-
Debug选项配置,选择仿真器型号:CMSIS-DAP Debugger。
-
Utilities选项配置,选择默认配置的仿真器,Use Debug Driver。
-
Debug Settings选项配置,Debug里,选SW-5MHZ,勾选Under Reset,Auto。
-
Debug Settings选项配置,Flash Download里,勾选Erase Sectors,Reset and Run。
-
添加芯片包,选中STM32F10X High Densty Flash 512k。
1.3 配置失败原因
现象 | 原因 |
---|---|
SW/JTAG Communication failure | 开发板未上电 |
No ST_LINK Detected | 选择了其他仿真器如ST LINK |
No ULINK Device found | 没有勾选use Debug Driver |
Error:Flash Download failed - “cortex -m3” | 没添加对应芯片 |
2 ISP串口下载
2.1 ISP介绍
ISP(In-System Programming)在系统可编程:
- 利用内部自举程序(bootloader)将串行程序烧到系统存储器。
- 依靠CH430驱动,将5V的电源电压转化为3.3V的板载电压,利用USB线下载程序。
2.2 Boot引脚配置
BOOT0 | BOOT1 | 启动方式 |
---|---|---|
0 | 0 | 内部FLASH |
1 | X | 系统存储器 |
0 | 1 | SRAM |
2.3 普通ISP过程
- USB线连接对应的USART口与计算机串口。
- 以跳线方式将Boot0置位、Boot1清零。
- 按下复位键进入BootLoader,开始下载程序。
- 下载结束后恢复条线,复位运行程序。
2.4 一键ISP原理
- RTS置0,导通PNP型三级管,BOOT0置1,进入BootLoader。
- DTR置1,导通NPN型三级管,NRST置0,完成复位。
- 进入ISP模式之后,可以将RST置1,DTR置0,还原为原始状态。
- 利用MCUISP软件,选择hex文件,选择波特率115200,勾选校验、编程后执行。
- 由于RS232电平与单片机相反,DTR低电平复位,RTS高电平进入BootLoader。
2.5 ISP模拟开关
-
只要RTS为低电平时,DTR电平也是低电平,因此一般Q2不会被导通,但由于存在竞争冒险,会出现RTS的下降沿刚好遇到DTR的上升沿,这时候Q2导通,导致系统复位,而BOOT0也有可能为高电平,就会进入ISP模式。
-
加入模拟开关U18,通过控制U18的使能端开达到隔离干扰电平的目的:上电后C65电容需要通过R18充电1S才能达到使能端的有效电平,因此不会干扰到系统复位。
3 STM32简介
3.1 STM32命名规范
3.2 原理图引脚分配
3.3 手册介绍
3.4 芯片手册
3.5 芯片选型
-
根据功耗选择芯片内核。
-
根据引脚确定芯片。
-
确定是否需要Flash。
4 寄存器介绍
4.1 区分芯片引脚
4.2 STM32F10x系列框图
① Flash与SRAM:
Nor Flash,浮栅场效应管,读写速度和寿命有限,但胜在掉电不丢失,可以用来保存常量与代码这类读写为主的数据。
SRAM,锁存器,读写速度快,寿命无限,成本较高且掉电丢失数据,一般用来存经常修改的局部变量。
② 三总线:
指令总线:Icode,主要用来从flash中取值操作。
数据总线:Dcode,主要用来从flash中读取常量,从sram中读取变量。
系统总线:System,主要用来对操作寄存器。
③ 直接访问存储器:
DMA,直接访问存储器,DMA1有7个管道,DMA2有5个管道,主要在不占用CPU资源下搬运数据,需要总线矩阵裁定。
④ FSMC:用来扩展静态SRAM。
⑤ AHB相关总线:
AHB总线:72MHZ高速总线,挂载了RCC、SDIO两个外设以及APB1、APB2两个总线。
APB1总线:36MHZ低速总线,主要挂载I2C、Time、低速的USART等。
APB2总线:72MHz高速总线,主要挂载高速USART、GPIO、ADC和高速SPI等。
4.3 寄存器映射
4.4 GPIO地址
总线 | 地址 |
---|---|
APB1 | 0x4000 0000 |
APB2 | 0x4001 0000 |
AHB | 0x4001 8000 |
外设名 | 地址 |
---|---|
GPIOA | 0x4001 0800 |
GPIOB | 0x4001 0c00 |
GPIOC | 0x4001 1000 |
GPIOD | 0x4001 1400 |
GPIOE | 0x4001 1800 |
GPIOF | 0x4001 1c00 |
GPIOG | 0x4001 2000 |
4.5 GPIO寄存器
寄存器名 | 寄存器标识符 | 偏移量 | 功能 |
---|---|---|---|
端口配置低寄存器 | GPIOX_CRL | 0x00 | 调节低8位的输入输出模式 |
端口配置高寄存器 | GPIOX_CRH | 0x04 | 调节高八位的输入输出模式 |
端口输出数据寄存器 | GPIOX_ODR | 0x08 | 低16位为IO口的输出,高16位保留 |
端口输人数据寄存器 | GPIOX_IDR | 0x0c | 低16位为IO口的输入,高16位保留 |
端口位设置/清楚寄存器 | GPIOX_BSRR | 0x10 | 低16位置1时置1,高16位置1清零 |
端口位清楚寄存器 | GPIOX_BRR | 0x14 | 低16位置1,清零对应IO口寄存器,高16位保留 |
端口配置锁定寄存器 | GPIOX_LCKR | 0x18 | 位16锁键,0~15只能在16位为0时才能写入 |
4.6 APB2外设时钟使能寄存器
- 打开对引端口的使能时钟,才能进行该外设端口的其他操作,否则心脏停了,无法工作。
4.7 常用位操作
// 将第i位置1
*(unsigned int *)(0x40010c08) |= (1 << i);
// 将第i为置0
*(unsigned int *)(0x40010c08) &= ~(1 << i);
// 将第2,3位清零
*(unsigned int *)(0x40010c08) &= ~(0x11 << (2 * 1));
// 将第2,3位置为10
*(unsigned int *)(0x40010c08) |= (0x10 << (2 * 1));
// 将对应位取反
*(unsigned int *)(0x40010c08) ^= (1 << i);
5 GPIO寄存器
5.1 GPIO框图
- 保护二极管
- 电压过高时,直接导通保护二极管,降压保护芯片。
- 接电机时需要加驱动元件,防止芯片与电机直连,烧毁芯片。
- 高于3v3,流向VDD,低于0V,从VSS流向输入。
- 容忍5V的引脚FT,需要对上面的保护二极管进行处理,才能容忍5V。
- 推挽输出模式
- 接一个非门再接一个COMS缓冲级,实现电平的正常输出。
- 当数据寄存器为高电平时,经过非门变为了低电平,CMOS管导通,输出电平为VDD,否则位VSS。
- 推挽输出使得高低电平都有一定的驱动能力。
- 推挽输出模式的输出速率一般大于10MHZ,不考虑功耗直接50。
- 如果需要保持数据同步需要用到GPIO->BSRR寄存器的所有位,即SetBits和WriteBits函数。
- 输出数据寄存器
- ODR寄存器,低16位控制IO口输出引脚电平,高16位保留。
- 复用功能输出
- 片上外设对GPIO引脚进行控制,与GPIO本身数据寄存器一起接在COMS管上。
- 配置USART串口时,该输出为TXD端。
- 输入数据寄存器
- IDR寄存器,低16位控制IO口输入引脚电平,高16位保留。
- 经过施密特触发器发器,电压整形,如果高于或低于上限或下线才会转变高低电平,使电压稳定无波动。
- 复位功能输入
- 配置USART串口时,该输出计时RXD端。
- 模拟输入
- 当输入模拟型号后,不经过施密特触发器,直接用原始电流电压数据传输。
- 开漏输出模式
- 上方的MOS管不工作,外接一个电源,如果下方MOS管导通,输出低电平,如果下方MOS管截止,输出高电平。开漏输出低电平导通能力强,高电平导通能力弱。一般用在I2C总线的SCL和SDA线上,避免信号干扰,或者SMBUS等需要线与或者电平不匹配的场合。
- 高电平无驱动能力,低电平有驱动能力。
5.2 GPIO工作模式
-
输入模式(模拟、浮空、上拉、下拉)
-
上拉和下拉输入:默认的电平由上拉或下拉决定,两个开关打开上面就位上拉。
-
浮空输入:电平不确定,完全由外部输入决定,关闭了两个开关。
-
模拟输入:用在ADC采集方面。
-
-
输出模式(推挽、开漏)
- 推挽输出:由两个MOS管配合输出。
- 开漏输出:下方的MOS管工作,配合外部电源来控制输出。
- 输出速度:2MHZ、10MZH、50MHZ,速度越大功耗越大。
-
复用功能(推挽、开漏)
- 外设提供信号源,读取可以使用输入数据寄存器读,但是输出数据寄存器无效,实际上用外设寄存器来获取该数据信号。
5.3 GPIO模式设置
- 一共8位,只有前7位不一样。
- 第0、1位区分速度
- 00(输入)、01(10MHZ输出)、10(2MHZ输出)、11(50MHZ输出)。
- 第4位区分输入和输出
- 0(输入)、1(输出)。
- 第2、3配合4区分具体工作模式
- 000(模拟)、001(浮空)、010(上拉或下拉)、100(推挽)、101(开漏)、110(复用推挽)、111(复用开漏)。
- 第5、6位区分上拉和下拉
- 01(下拉)、10(上拉)。
6 自建库函数点亮LED
6.1 新建工程
- 打开Keil软件,新建Project,选择STM32F103VE芯片。
- 添加驱动文件:startup_stm32f10x_hd.s。
- 新建寄存器映射文件:stm32f10x.h。
- 新建main.c文件,引入头文件,并在main.c内部加入空SystemInit(),骗过编译器执行。
- 注意Keil的文件尾部都需要加空格。
6.2 LED原理图
6.3 volatile关键字
① 作用
volatile提醒编译器这个变量随时可能被改变,因此编译时不要对该变量做优化,保证能够稳定访问该变量,以免变量不一致。
② 应用
- 并行设备的硬件寄存器(如:状态寄存器)
例如:假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000。
int *output = (unsigned int *)0xff800000;//定义一个IO端口;
int init(void)
{
int i;
for(i=0;i< 10;i++){
*output = i;
}
}
经过编译器优化后,编译器认为前面循环半天都是废话,对最后的结果毫无影响,因为最终只是将output这个指针赋值为 9,所以编译器最后给你编译编译的代码结果相当于:
int init(void)
{
*output =9;
}
- 中断服务程序中修改的供其它程序检测的变量,需要加volatile;
当变量在触发某中断程序中修改,而编译器判断主函数里面没有修改该变量,因此可能只执行一次从内存到某寄存器的读操作,而后每次只会从该寄存器中读取变量副本,使得中断程序的操作被短路。
- 多任务环境下各任务间共享的标志,应该加volatile;
在本次线程内, 当读取一个变量时,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当内存变量或寄存器变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致 。
- 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000。for(i=0;i< 10;i++) *output = i;前面循环半天都是废话,对最后的结果毫无影响,因为最终只是将output这个指针赋值为9,省略了对该硬件IO端口反复读的操作。
这是区分C程序员和嵌入式系统程序员的最基本的问题:嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所有这些都要求使用volatile变量。不懂得volatile内容将会带来灾难。
③ volatile 问题和总结
volatile 常见的几个面试题:
- 一个参数既可以是const还可以是volatile吗?
可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
- 一个指针可以是volatile 吗?
可以,当一个中服务子程序修改一个指向buffer的指针时。
- 下面的函数有什么错误?
int square(volatile int*ptr)
{
return*ptr * *ptr;
}
该程序的目的是用来返指针*
ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int*ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int*ptr)
{
int a;
a = *ptr;
return a * a;
}
注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。
6.4 具体函数实现
① stm32f10x.h
/*外设寄存器映射列表*/
#ifndef STM32F10X_H__
#define STM32F10X_H__ // 条件编译,防止头文件重复包含
/*通用数据类型定义*/
#define _IO volatile // 不让编译器优化
#define uint32_t unsigned int // 32位无符号
#define uint16_t unsigned short // 16位无符号
#define uint8_t unsigned char // 8位无符号
/*寄存器映射*/
/*基地址映射*/
#define PERIPH_BASE 0x40000000 // 整个板子基地址
#define APB1_BASE (PERIPH_BASE) // APB1基地址
#define APB2_BASE (PERIPH_BASE + 0x10000) // APB2基地址
#define AHB_BASE (PERIPH_BASE + 0x20000) // AHB基地址,绕开SDIO
/*GPIO地址映射*/
#define GPIOA_BASE (APB2_BASE + 0x0800)
#define GPIOB_BASE (APB2_BASE + 0x0c00)
#define GPIOC_BASE (APB2_BASE + 0x1000)
#define GPIOD_BASE (APB2_BASE + 0x1400)
#define GPIOE_BASE (APB2_BASE + 0x1800)
#define GPIOF_BASE (APB2_BASE + 0x1c00)
#define GPIOG_BASE (APB2_BASE + 0x2000)
/*GPIO结构体*/
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;
/*GPIO具体定义*/
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
/*RCC结构体*/
typedef struct {
_IO uint32_t CR; // 时钟控制寄存器
_IO uint32_t CFGR; // 时钟配置寄存器
_IO uint32_t CIR; // 时钟中断寄存器
_IO uint32_t APB2RSTR; // APB2外设复位寄存器
_IO uint32_t APB1RSTR; // APB1外设复位寄存器
_IO uint32_t AHBENR; // AHB外设时钟使能寄存器
_IO uint32_t APB2ENR; // APB2外设时钟使能寄存器
_IO uint32_t APB1ENR; // APB1外设时钟使能寄存器
_IO uint32_t BDCR; // 备份域控制寄存器
_IO uint32_t CSR; // 控制/状态寄存器
_IO uint32_t AHBRSTR; // AHB外设时钟复位寄存器
_IO uint32_t CFGR2; // 时钟配置寄存器2
}RCC_TypeDef;
/*RCC基地址*/
#define RCC_BASE (AHB_BASE + 0x1000)
/*RCC具体定义*/
#define RCC ((RCC_TypeDef *) RCC_BASE)
#endif
// 尾部加空格,Keil的特色
② stm32f10x_gpio.h
#ifndef STM32F10X_GPIO_H__
#define STM32F10X_GPIO_H__
#include "stm32f10x.h"
/*定义引脚变量*/
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< 选择Pin0 */ //(00000000 00000001)b
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< 选择Pin1 */ //(00000000 00000010)b
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< 选择Pin2 */ //(00000000 00000100)b
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< 选择Pin3 */ //(00000000 00001000)b
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< 选择Pin4 */ //(00000000 00010000)b
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< 选择Pin5 */ //(00000000 00100000)b
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< 选择Pin6 */ //(00000000 01000000)b
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< 选择Pin7 */ //(00000000 10000000)b
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< 选择Pin8 */ //(00000001 00000000)b
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< 选择Pin9 */ //(00000010 00000000)b
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< 选择Pin10 */ //(00000100 00000000)b
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< 选择Pin11 */ //(00001000 00000000)b
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< 选择Pin12 */ //(00010000 00000000)b
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< 选择Pin13 */ //(00100000 00000000)b
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< 选择Pin14 */ //(01000000 00000000)b
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< 选择Pin15 */ //(10000000 00000000)b
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< 选择全部引脚*/ //(11111111 11111111)b
/*输出速度*/
typedef enum{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIO_Speed_TypeDef;
/*输入输出模式*/
typedef enum{
/*
规则
1. 只考虑前7位。
2. 第4位决定输入还是输出,1输出,0输入。
3. 第5位和第6位共同决定上拉还是下拉,01 下拉,10 上拉。
4. 第2、3位一起决定是模拟、浮空、复用、上拉、下拉等模式。
*/
GPIO_Mode_AIN = 0x00, // 模拟输入 0000 0000
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入 0000 0100
GPIO_Mode_IPD = 0x28, // 下拉输入 0010 1000
GPIO_Mode_IPU = 0x48, // 上拉输入 0100 1000
GPIO_Mode_Out_PP = 0x10, // 推挽输出 0001 0000
GPIO_Mode_Out_OD = 0x14, // 开漏输出 0001 0100
GPIO_Mode_AF_PP = 0x18, // 复用推挽输出 0001 1000
GPIO_Mode_AF_OD = 0x1c // 复用开漏输出 0011 1000
}GPIO_Mode_TypeDef;
/*GPIO初始化结构体*/
typedef struct
{
_IO uint16_t GPIO_Pin;
_IO uint16_t GPIO_Speed;
_IO uint16_t GPIO_Mode;
}GPIO_Init_TypeDef;
/*置位函数*/
void GPIO_SetBits(GPIO_TypeDef *GPIO_Port, uint16_t GPIO_Pin);
/*清零函数*/
void GPIO_ResetBits(GPIO_TypeDef * GPIO_Port, uint16_t GPIO_Pin);
/*初始化结构体,设置工作模式*/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_Init_TypeDef* GPIO_InitStruct);
#endif
③ stm32f10x_gpio.c
#include "stm32f10x_gpio.h"
/*置位函数*/
void GPIO_SetBits(GPIO_TypeDef *GPIO_Port, uint16_t GPIO_Pin){
GPIO_Port->BSRR = GPIO_Pin;
}
/*清零函数*/
void GPIO_ResetBits(GPIO_TypeDef * GPIO_Port, uint16_t GPIO_Pin){
GPIO_Port->BRR = GPIO_Pin;
}
/*初始化结构体,设置工作模式*/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_Init_TypeDef* GPIO_InitStruct){
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/*---------------------- GPIO 模式配置 --------------------------*/
// 把输入参数GPIO_Mode的低四位暂存在currentmode
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
// bit4是1表示输出,bit4是0则是输入
// 判断bit4是1还是0,即首选判断是输入还是输出模式
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
// 输出模式则要设置输出速度
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*-------------GPIO CRL 寄存器配置 CRL寄存器控制着低8位IO- -------*/
// 配置端口低8位,即Pin0~Pin7
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
// 先备份CRL寄存器的值
tmpreg = GPIOx->CRL;
// 循环,从Pin0开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
// pos的值为1左移pinpos位
pos = ((uint32_t)0x01) << pinpos;
// 令pos与输入参数GPIO_PIN作位与运算,为下面的判断作准备
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
// pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
// 把前面处理后的暂存值写入到CRL寄存器之中
GPIOx->CRL = tmpreg;
}
/*-------------GPIO CRH 寄存器配置 CRH寄存器控制着高8位IO- -----------*/
// 配置端口高8位,即Pin8~Pin15
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
// // 先备份CRH寄存器的值
tmpreg = GPIOx->CRH;
// 循环,从Pin8开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
// pos与输入参数GPIO_PIN作位与运算
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
//pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
// 把前面处理后的暂存值写入到CRH寄存器之中
GPIOx->CRH = tmpreg;
}
}
④ main.c
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
void SystemInit(void){
}
void Delay(uint32_t count){
for(;count != 0;count --);
}
int main(void){
/*端口函数数组*/
unsigned char map[3] = {GPIO_Pin_0,GPIO_Pin_1,GPIO_Pin_5}; // LED的引脚
unsigned char i = 0;
/*建立初始化结构体*/
GPIO_Init_TypeDef GPIO_Init_Structure;
/*打开GPIOB的使能时钟*/
RCC->APB2ENR |= (1<<3);
while(1){
/*设置工作模式*/
GPIO_Init_Structure.GPIO_Pin = map[i];
GPIO_Init_Structure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init_Structure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOB, &GPIO_Init_Structure);
/*点亮*/
GPIO_ResetBits(GPIOB,map[i]);
Delay(0xfffff);
/*熄灭*/
GPIO_SetBits(GPIOB,map[i]);
Delay(0xfffff);
/*遍历数组*/
i = (i + 1) % 3;
}
}
7 STM32标准库
7.1 CMSIS架构
7.2 固件库介绍
7.3 库文件分析
- core_cm3.c与core_cm3.h:内核相关寄存器映射文件。
- stm32f10x.h:寄存器外设映射文件。
- system_stm32f10x.c和system_stm32f10x.h:时钟配置文件。
- startup_stm32f10x_hd.s:启动文件。
- stm32f10x_it.h和stm32f10x_it.c:中断配置文件。
- inc:驱动的头文件。
- src:驱动的源代码。
- stm32f10x_conf.h:头文件管理,里面定义所有的头文件调用。
7.4 启动文件
① 启动文件主要工作
- 初始化堆栈:SP=_initial_sp
- 初始化PC指针:Reset_Handler
- 初始化中断向量表
- 配置系统时钟:SystemInit()
- 调用C库函数_main初始化用户堆栈
② 汇编指令汇总
8 标准库工程
8.1 创建工程
① D盘创建文件夹
文件夹 | 内容 |
---|---|
Doc | 存放文档readme.txt |
Libraries | CMSIS:startup_stm32f10x_hd.s、core_cm3.c、core_cm3.h、stm32f10x.h、system_stm32f10x.h、system_stm32f10x.c |
STM32F10x_StdPeriph_Driver:inc、src | |
Project | 存放工程 |
User | main.c、stm32f10x_conf.h、stm32f10x_it.c、stm32f10x_it.h |
② 工程中创建文件夹
文件夹 | 内容 |
---|---|
DOC | 存放readme.txt |
CMSIS | 导入corecm2.c、system_stm32f10x.c |
FWLB | 导入src所有c文件 |
STARTUP | 导入startup_stm32f10x_hd.s |
USER | 导入stm32f10x_it.c、main.c |
④ 添加路径
- Keil软件调用的位置在其安装文件,要调用工程里的库文件,需要添加路径。
- …\Libraries\CMSIS
- …\Libraries\STM32F10x_StdPeriph_Driver\inc
- …\USER
⑤ 添加宏命令
(1)
# 在Opyions for target 的 define 中添加:USE_STDPERIPH_DRIVER,STM32F10X_HD
(2)
#if !defined (STM32F10X_LD) && !defined (STM32F10X_LD_VL) && !defined (STM32F10X_MD) && !defined (STM32F10X_MD_VL) && !defined (STM32F10X_HD) && !defined (STM32F10X_HD_VL) && !defined (STM32F10X_XL) && !defined (STM32F10X_CL)
/* #define STM32F10X_LD */ /*!< STM32F10X_LD: STM32 Low density devices */
/* #define STM32F10X_LD_VL */ /*!< STM32F10X_LD_VL: STM32 Low density Value Line devices */
/* #define STM32F10X_MD */ /*!< STM32F10X_MD: STM32 Medium density devices */
/* #define STM32F10X_MD_VL */ /*!< STM32F10X_MD_VL: STM32 Medium density Value Line devices */
/* #define STM32F10X_HD */ /*!< STM32F10X_HD: STM32 High density devices */
/* #define STM32F10X_HD_VL */ /*!< STM32F10X_HD_VL: STM32 High density value line devices */
/* #define STM32F10X_XL */ /*!< STM32F10X_XL: STM32 XL-density devices */
/* #define STM32F10X_CL */ /*!< STM32F10X_CL: STM32 Connectivity line devices */
#endif
(3)
#ifdef USE_STDPERIPH_DRIVER
#include "stm32f10x_conf.h"
#endif
8.2 点亮LED
① main.c
/*流水灯显示红绿蓝*/
#include "stm32f10x.h"
#include "bsp_led.h"
int main(void){
/*初始化所有IO口时钟和工作模式*/
LED_G_GPIO_Config();
LED_R_GPIO_Config();
LED_B_GPIO_Config();
while(1){
/*打开新的灯必须将旧的关闭*/
LED_R(ON);
LED_Delay(0xfffff);
LED_R(OFF);
LED_G(ON);
LED_Delay(0xfffff);
LED_G(OFF);
LED_B(ON);
LED_Delay(0xfffff);
LED_B(OFF);
}
}
② bsp_led.h
#ifndef BSP_LED_H__
#define BSP_LED_H__
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
/*引脚配置*/
/*绿*/
#define LED_G_GPIO_PORT GPIOB // 绿色对应引脚
#define LED_G_GPIO_PIN GPIO_Pin_0 // 绿色引脚电平
#define LED_G_GPIO_CLK RCC_APB2Periph_GPIOB // 对应时钟
#define LED_G_GPIO_Speed GPIO_Speed_10MHz // 10MHZ的速度
#define LED_G_GPIO_Mode GPIO_Mode_Out_PP // 推挽输出
/*红*/
#define LED_R_GPIO_PORT GPIOB // 红色对应引脚
#define LED_R_GPIO_PIN GPIO_Pin_5 // 红色引脚电平
#define LED_R_GPIO_CLK RCC_APB2Periph_GPIOB // 对应时钟
#define LED_R_GPIO_Speed GPIO_Speed_10MHz // 10MHZ的速度
#define LED_R_GPIO_Mode GPIO_Mode_Out_PP // 推挽输出
/*蓝*/
#define LED_B_GPIO_PORT GPIOB // 蓝色对应引脚
#define LED_B_GPIO_PIN GPIO_Pin_1 // 蓝色引脚电平
#define LED_B_GPIO_CLK RCC_APB2Periph_GPIOB // 对应时钟
#define LED_B_GPIO_Speed GPIO_Speed_10MHz // 10MHZ的速度
#define LED_B_GPIO_Mode GPIO_Mode_Out_PP // 推挽输出
/*开关宏*/
#define ON 1 // 打开
#define OFF 0 // 关闭
/*绿:续行符,后面只允许一个回车*/
#define LED_G(A) if(A)\
GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);\
else \
GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
/*红*/
#define LED_R(A) if(A)\
GPIO_ResetBits(LED_R_GPIO_PORT,LED_R_GPIO_PIN);\
else \
GPIO_SetBits(LED_R_GPIO_PORT,LED_R_GPIO_PIN);
/*蓝*/
#define LED_B(A) if(A)\
GPIO_ResetBits(LED_B_GPIO_PORT,LED_B_GPIO_PIN);\
else \
GPIO_SetBits(LED_B_GPIO_PORT,LED_B_GPIO_PIN);
/*延迟函数*/
void LED_Delay(uint32_t Count);
/*初始化时钟与工作模式*/
/*绿*/
void LED_G_GPIO_Config(void);
/*红*/
void LED_R_GPIO_Config(void);
/*蓝*/
void LED_B_GPIO_Config(void);
#endif
③ bsp_led.c
#include "bsp_led.h" // 板级支持包,只能配套板子使用
/*初始化时钟与工作模式*/
/*绿*/
void LED_G_GPIO_Config(void){
/*创建结构体*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = LED_G_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = LED_G_GPIO_Mode;
GPIO_InitStruct.GPIO_Speed = LED_G_GPIO_Speed;
/*配置使能时钟*/
RCC_APB2PeriphClockCmd(LED_G_GPIO_CLK,ENABLE);
/*初始化工作模式*/
GPIO_Init(LED_G_GPIO_PORT,&GPIO_InitStruct);
/*关掉默认的输出,否则会被点亮*/
GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
}
/*红*/
void LED_R_GPIO_Config(void){
/*创建结构体*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = LED_R_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = LED_R_GPIO_Mode;
GPIO_InitStruct.GPIO_Speed = LED_R_GPIO_Speed;
/*配置使能时钟*/
RCC_APB2PeriphClockCmd(LED_R_GPIO_CLK,ENABLE);
/*初始化工作模式*/
GPIO_Init(LED_R_GPIO_PORT,&GPIO_InitStruct);
/*关掉默认的输出,否则会被点亮*/
GPIO_SetBits(LED_R_GPIO_PORT,LED_R_GPIO_PIN);
}
/*蓝*/
void LED_B_GPIO_Config(void){
/*创建结构体*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = LED_B_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = LED_B_GPIO_Mode;
GPIO_InitStruct.GPIO_Speed = LED_B_GPIO_Speed;
/*配置使能时钟*/
RCC_APB2PeriphClockCmd(LED_B_GPIO_CLK,ENABLE);
/*初始化工作模式*/
GPIO_Init(LED_B_GPIO_PORT,&GPIO_InitStruct);
/*关掉默认的输出,否则会被点亮*/
GPIO_SetBits(LED_B_GPIO_PORT,LED_B_GPIO_PIN);
}
/*延迟函数*/
void LED_Delay(uint32_t Count){
for(;Count != 0;Count--);
}
8.3 按键检测
① main.c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_key.h"
int main(void){
uint8_t Num = 0;
KEY_GPIO_Config(); // 设置按键
LED_G_GPIO_Config(); // 设置led
while(1){
Num = Key_GetNum();
if(Num == 1){ // 点亮
LED_G(ON);
}else if(Num == 2){ // 熄灭
LED_G(OFF);
}
}
}
② bsp_key.h
#ifndef BSP_KEY__
#define BSP_KEY__
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
/*引脚配置*/
/*Key1配置*/
#define KEY1_GPIO_PORT GPIOA // IO口A
#define KEY1_GPIO_PIN GPIO_Pin_0 // 第一位
#define KEY1_GPIO_CLK RCC_APB2Periph_GPIOA // GPIO的时钟
#define KEY1_GPIO_MODE GPIO_Mode_IN_FLOATING // 浮空输入
/*Key2配置*/
#define KEY2_GPIO_PORT GPIOC // IO口A
#define KEY2_GPIO_PIN GPIO_Pin_13 // 第一位
#define KEY2_GPIO_CLK RCC_APB2Periph_GPIOC // GPIO的时钟
#define KEY2_GPIO_MODE GPIO_Mode_IN_FLOATING // 浮空输入
/*打开时钟,配置工作模式*/
void KEY1_GPIO_Config(void); // 配置KEY1
void KEY2_GPIO_Config(void); // 配置KEY2
void KEY_GPIO_Config(void); // 配置KEY1和KEY2
/*扫描按键*/
#define KEY_ON 1
#define KEY_OFF 0
/*判断Key是否为按下*/
uint8_t Key_Scan(GPIO_TypeDef *GPIOX,uint16_t GPIO_Pin);
/*获取具体哪个按键按下*/
uint8_t Key_GetNum(void);
#endif
③ bsp_key.c
#include "bsp_key.h"
/*打开时钟,配置工作模式*/
/*配置KEY1*/
void KEY1_GPIO_Config(void){
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = KEY1_GPIO_MODE;
GPIO_InitStruct.GPIO_Pin = KEY1_GPIO_PIN;
RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK,ENABLE);
GPIO_Init(KEY1_GPIO_PORT,&GPIO_InitStruct);
}
/*配置KEY2*/
void KEY2_GPIO_Config(void){
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = KEY2_GPIO_MODE;
GPIO_InitStruct.GPIO_Pin = KEY2_GPIO_PIN;
RCC_APB2PeriphClockCmd(KEY2_GPIO_CLK,ENABLE);
GPIO_Init(KEY2_GPIO_PORT,&GPIO_InitStruct);
}
/*配置KEY1和KEY2*/
void KEY_GPIO_Config(void){
KEY1_GPIO_Config();
KEY2_GPIO_Config();
}
/*扫描按键*/
/*判断Key是否为按下*/
uint8_t Key_Scan(GPIO_TypeDef *GPIOX,uint16_t GPIO_Pin){
if(GPIO_ReadInputDataBit(GPIOX,GPIO_Pin) == KEY_ON){
// 松开获取,加了电容硬件消抖,不需要软件消抖
while(GPIO_ReadInputDataBit(GPIOX,GPIO_Pin) == KEY_ON);
return KEY_ON;
}else{
return KEY_OFF;
}
}
/*获取具体哪个按键按下*/
uint8_t Key_GetNum(void){
if(Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON){
return 1;
}else if(Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON){
return 2;
}else{
return 0;
}
}
8.4 位带操作
① 位带简介
位带操作即对寄存器的单个位进行操作,类比于51单片机中的sbit。
片上外设和片上SRAM上面都存在1M的字节的位带区,并且存在32M字节的位带别名区。
位带区的寄存器既可以直接总线访问,又可以通过位带别名区按位访问。
位带区的每一个位都在位带别名区被膨胀成32位,与机器字长相同速度更快。
对位带别名只能操作最低位。
推荐在IO密集型的底层代码中使用,个人理解是:相比于库函数操作,位带操作访问速度会快很多(原子类型的寄存器操作),如果在时序要求较严格的情况下,可以使用位带操作,如使用IO口模拟某种通信协议。另外,由于位带操作异常不可打断(原子操作),在带操作系统的开发中出于代码健壮性考虑可以使用位带操作。
② 转换公式
③ 通用公式
AliasAddr = ((A & 0xF0000000)+0x02000000+((A &0x00FFFFFF)<<5)+(n<<2))
④ bsp_bit.h
#ifndef BSP_BIT_H__
#define BSP_BIT_H__
#include "stm32f10x.h"
// 地址映射
#define GPIO_ODR_Addr (GPIOB_BASE + 0x0C)
// 将地址转为指针内容,volatile避免操作被编译器优化掉
#define BIT_ADDR(ADDR,N) (*(volatile unsigned long *)(((ADDR & 0xF0000000) + 0x02000000) + ((ADDR & 0x00FFFFFF) << 5) + (N << 2)))
// ODR的位带操作
#define PBout(n) BIT_ADDR(GPIO_ODR_Addr,n)
#endif
⑤ main.c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_bit.h"
int main(void){
LED_G_GPIO_Config();
while(1){
PBout(0) = 1;
LED_Delay(0xfffff);
PBout(0) = 0;
LED_Delay(0xfffff);
}
}