stm32标准库开发笔记--野火标准库入门篇

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引脚配置

BOOT0BOOT1启动方式
00内部FLASH
1X系统存储器
01SRAM

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。

ISP一键下载电路原理

2.5 ISP模拟开关
  • 只要RTS为低电平时,DTR电平也是低电平,因此一般Q2不会被导通,但由于存在竞争冒险,会出现RTS的下降沿刚好遇到DTR的上升沿,这时候Q2导通,导致系统复位,而BOOT0也有可能为高电平,就会进入ISP模式。

  • 加入模拟开关U18,通过控制U18的使能端开达到隔离干扰电平的目的:上电后C65电容需要通过R18充电1S才能达到使能端的有效电平,因此不会干扰到系统复位。

3 STM32简介

3.1 STM32命名规范

STM32命名规范.bmp

3.2 原理图引脚分配

如何分配引脚.bmp

3.3 手册介绍

手册介绍

3.4 芯片手册

数据手册中对引脚功能的说明
在这里插入图片描述

3.5 芯片选型

  1. 根据功耗选择芯片内核。

  2. 根据引脚确定芯片。

  3. 确定是否需要Flash

4 寄存器介绍

4.1 区分芯片引脚

判断引脚

4.2 STM32F10x系列框图

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地址

总线地址
APB10x4000 0000
APB20x4001 0000
AHB0x4001 8000
外设名地址
GPIOA0x4001 0800
GPIOB0x4001 0c00
GPIOC0x4001 1000
GPIOD0x4001 1400
GPIOE0x4001 1800
GPIOF0x4001 1c00
GPIOG0x4001 2000

4.5 GPIO寄存器

寄存器名寄存器标识符偏移量功能
端口配置低寄存器GPIOX_CRL0x00调节低8位的输入输出模式
端口配置高寄存器GPIOX_CRH0x04调节高八位的输入输出模式
端口输出数据寄存器GPIOX_ODR0x08低16位为IO口的输出,高16位保留
端口输人数据寄存器GPIOX_IDR0x0c低16位为IO口的输入,高16位保留
端口位设置/清楚寄存器GPIOX_BSRR0x10低16位置1时置1,高16位置1清零
端口位清楚寄存器GPIOX_BRR0x14低16位置1,清零对应IO口寄存器,高16位保留
端口配置锁定寄存器GPIOX_LCKR0x18位16锁键,0~15只能在16位为0时才能写入

4.6 APB2外设时钟使能寄存器

  • 打开对引端口的使能时钟,才能进行该外设端口的其他操作,否则心脏停了,无法工作。

GPIOB使能时钟寄存器

在这里插入图片描述

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框图

GPIO框图.bmp

  1. 保护二极管
    • 电压过高时,直接导通保护二极管,降压保护芯片。
    • 接电机时需要加驱动元件,防止芯片与电机直连,烧毁芯片。
    • 高于3v3,流向VDD,低于0V,从VSS流向输入。
    • 容忍5V的引脚FT,需要对上面的保护二极管进行处理,才能容忍5V。
  2. 推挽输出模式
    • 接一个非门再接一个COMS缓冲级,实现电平的正常输出。
    • 当数据寄存器为高电平时,经过非门变为了低电平,CMOS管导通,输出电平为VDD,否则位VSS。
    • 推挽输出使得高低电平都有一定的驱动能力。
    • 推挽输出模式的输出速率一般大于10MHZ,不考虑功耗直接50。
    • 如果需要保持数据同步需要用到GPIO->BSRR寄存器的所有位,即SetBits和WriteBits函数。
  3. 输出数据寄存器
    • ODR寄存器,低16位控制IO口输出引脚电平,高16位保留。
  4. 复用功能输出
    • 片上外设对GPIO引脚进行控制,与GPIO本身数据寄存器一起接在COMS管上。
    • 配置USART串口时,该输出为TXD端。
  5. 输入数据寄存器
    • IDR寄存器,低16位控制IO口输入引脚电平,高16位保留。
    • 经过施密特触发器发器,电压整形,如果高于或低于上限或下线才会转变高低电平,使电压稳定无波动。
  6. 复位功能输入
    • 配置USART串口时,该输出计时RXD端。
  7. 模拟输入
    • 当输入模拟型号后,不经过施密特触发器,直接用原始电流电压数据传输。
  8. 开漏输出模式
    • 上方的MOS管不工作,外接一个电源,如果下方MOS管导通,输出低电平,如果下方MOS管截止,输出高电平。开漏输出低电平导通能力强,高电平导通能力弱。一般用在I2C总线的SCL和SDA线上,避免信号干扰,或者SMBUS等需要线与或者电平不匹配的场合。
    • 高电平无驱动能力,低电平有驱动能力

5.2 GPIO工作模式

GPIO模式.bmp

  1. 输入模式(模拟、浮空、上拉、下拉)

    • 上拉和下拉输入:默认的电平由上拉或下拉决定,两个开关打开上面就位上拉

    • 浮空输入:电平不确定,完全由外部输入决定,关闭了两个开关

    • 模拟输入:用在ADC采集方面。

  2. 输出模式(推挽、开漏)

    • 推挽输出:由两个MOS管配合输出。
    • 开漏输出:下方的MOS管工作,配合外部电源来控制输出。
    • 输出速度:2MHZ、10MZH、50MHZ,速度越大功耗越大。
  3. 复用功能(推挽、开漏)

    • 外设提供信号源,读取可以使用输入数据寄存器读,但是输出数据寄存器无效,实际上用外设寄存器来获取该数据信号。

5.3 GPIO模式设置

  1. 一共8位,只有前7位不一样
  2. 第0、1位区分速度
    • 00(输入)、01(10MHZ输出)、10(2MHZ输出)、11(50MHZ输出)。
  3. 第4位区分输入和输出
    • 0(输入)、1(输出)。
  4. 第2、3配合4区分具体工作模式
    • 000(模拟)、001(浮空)、010(上拉或下拉)、100(推挽)、101(开漏)、110(复用推挽)、111(复用开漏)。
  5. 第5、6位区分上拉和下拉
    • 01(下拉)、10(上拉)。

6 自建库函数点亮LED

6.1 新建工程

  1. 打开Keil软件,新建Project,选择STM32F103VE芯片。
  2. 添加驱动文件:startup_stm32f10x_hd.s。
  3. 新建寄存器映射文件:stm32f10x.h。
  4. 新建main.c文件,引入头文件,并在main.c内部加入空SystemInit(),骗过编译器执行。
  5. 注意Keil的文件尾部都需要加空格。

6.2 LED原理图

LED灯原理图.bmp

6.3 volatile关键字

① 作用

volatile提醒编译器这个变量随时可能被改变,因此编译时不要对该变量做优化,保证能够稳定访问该变量,以免变量不一致。

② 应用

  1. 并行设备的硬件寄存器(如:状态寄存器)

例如:假设要对一个设备进行初始化,此设备的某一个寄存器为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;
}
  1. 中断服务程序中修改的供其它程序检测的变量,需要加volatile;

当变量在触发某中断程序中修改,而编译器判断主函数里面没有修改该变量,因此可能只执行一次从内存到某寄存器的读操作,而后每次只会从该寄存器中读取变量副本,使得中断程序的操作被短路。

  1. 多任务环境下各任务间共享的标志,应该加volatile;

在本次线程内, 当读取一个变量时,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当内存变量或寄存器变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致 。

  1. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000。for(i=0;i< 10;i++) *output = i;前面循环半天都是废话,对最后的结果毫无影响,因为最终只是将output这个指针赋值为9,省略了对该硬件IO端口反复读的操作。

这是区分C程序员和嵌入式系统程序员的最基本的问题:嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所有这些都要求使用volatile变量。不懂得volatile内容将会带来灾难。

③ volatile 问题和总结

volatile 常见的几个面试题:

  1. 一个参数既可以是const还可以是volatile吗?

可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

  1. 一个指针可以是volatile 吗?

可以,当一个中服务子程序修改一个指向buffer的指针时。

  1. 下面的函数有什么错误?
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架构

STM32f10x库文件结构.bmp

7.2 固件库介绍

固件库文件夹.bmp

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 启动文件

① 启动文件主要工作

  1. 初始化堆栈:SP=_initial_sp
  2. 初始化PC指针:Reset_Handler
  3. 初始化中断向量表
  4. 配置系统时钟:SystemInit()
  5. 调用C库函数_main初始化用户堆栈

② 汇编指令汇总

启动代码汇编语言汇总.bmp

8 标准库工程

8.1 创建工程

① D盘创建文件夹

文件夹内容
Doc存放文档readme.txt
LibrariesCMSIS:startup_stm32f10x_hd.s、core_cm3.c、core_cm3.h、stm32f10x.h、system_stm32f10x.h、system_stm32f10x.c
STM32F10x_StdPeriph_Driver:inc、src
Project存放工程
Usermain.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 */
#endif3#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口模拟某种通信协议。另外,由于位带操作异常不可打断(原子操作),在带操作系统的开发中出于代码健壮性考虑可以使用位带操作。

位带示意图.bmp

② 转换公式

位带换算.bmp

③ 通用公式

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);
	}
}

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32标准库开发教程是一套教学材料,旨在帮助初学者快速入门STM32嵌入式系统的开发。教程中包括了安装开发工具、安装仿真器驱动、创建工程、配置工程、添加文件、编写代码、编译程序和下载程序等多个步骤。教程详细介绍了如何使用STM32标准库进行开发,并提供了丰富的实例代码和实验,以便初学者能够更好地理解和掌握相关知识。 在学习STM32标准库开发教程之前,初学者需要具备一定的电子和嵌入式系统的基础知识。教程会从基本的硬件知识和编程语言开始讲解,然后深入介绍STM32的应用和开发。通过逐步实践和探索,读者可以不断提高自己的技能水平。 在编写STM32标准库开发教程中,需要将STM32标准库中的源文件和头文件添加到工程中,例如stm32f0x.h、stm32f0x_gpio.h、stm32f0x_rcc.h等。这些文件提供了对STM32芯片的访问接口,方便开发者进行开发和调试。 总之,STM32标准库开发教程是一个详细而全面的教学材料,旨在帮助初学者快速入门STM32嵌入式系统的开发。通过学习教程,读者可以掌握STM32的基础知识和开发技巧,并不断提高自己的技能水平。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [手把手教你STM32入门教程(标准库)](https://blog.csdn.net/yunsheng233/article/details/131403745)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值