这里写目录标题

  • 1. 需驱动的寄存器及所在地址
    • 1-1. RCC的所在地址
    • 1-2. APB2时钟使能寄存器
    • 1-3. GPIOA的所在地址
    • 1-4. GPIO端口相应的寄存器
  • 2. 直接寄存器配置
    • 2.1驱动流程
  • 3. 固件库开发
    • 3.1 前言
    • 3.2 封装(映像)
    • 3.3 封装(结构体)
  • 4 小结

前言
本文章主要讲解如何使用STM32F103系列单片机点亮LED,从直接驱动相应内部寄存器到一步一步封装成库函数。

1. 需驱动的寄存器及所在地址

1-1. RCC的所在地址

1-2. APB2时钟使能寄存器


1-3. GPIOA的所在地址

1-4. GPIO端口相应的寄存器





该图是从STM32F103参考手册剪辑过来的,目前只显示用到的寄存器及它们的地址。具体内容以STM32F103参考手册为主。

2. 直接寄存器配置

硬件只需一个LED灯,由GPIOA_Pin_0引脚控制。GPIOA的驱动流程请见下面文章。

2.1驱动流程

(1)驱动APB2时钟使能寄存器开启GPIOA的时钟。
GPIOA;

/* GPIOA的时钟 */
*(unsigned int*)(40021000) |= (1<<2);

(2)配置GPIOA寄存器组中的CRL(GPIO低8位控制寄存器),配置参数为:输出模式为推挽式输出,输出速度为50MH。
CRL低8位寄存器四位控制一位引脚。

/*   清除对应GPIOA端口第0引脚 */
*(unsigned int*)(40010800) &= ~(0x0F<<(4*0));

设置工作模式为:推挽式输出,输出速度为:50MHz。

/*   CRL_MODEx =3; CNFx =0;    */
*(unsigned int*)(40010800) |= (3<<(4*0));

(3)控制相应的GPIOA端口第0引脚输出高低电平,需控制的GPIO寄存器有BSRR,BRR,ODR,可选其中一个。

/*   用BSRR寄存器控制PA0引脚输出高电平 */
*(unsigned int*)(40010800+0x10) |= (1<<(0+0));
/*  用BSRR寄存器控制PA0引脚输出低电平 */
*(unsigned int*)(40010800+0x10) |= (1<<(16+0));/*  用BRR寄存器控制PA0引脚输出低电平 */
*(unsigned int*)(40010800+0x14) |= (1<<(0+0));/*   用ODR寄存器控制PA0引脚输出高电平 */
*(unsigned int*)(40010800+0x0C) |= (1<<(0+0));
/*  用ODR寄存器控制PA0引脚输出低电平 */
*(unsigned int*)(40010800+0x0C) |=(0<<(0+0));

先找到外设的基础地址如RCC的0x40021000,GPIOA的40010800,(在STM32F103系列参考手册存储器映像中),之后在加上相关的寄存器偏移地址,具体请查阅STM32F103系列参考手册中的寄存器介绍。确定寄存器所在地址后强制转换为 unsigned int *(无符号整型变量指针)再用间接运算符,取该地址的值以便之后参与运算。
函数的实现部分:

void main()
{/* GPIOA的时钟 */(unsigned int*)(40021000) |= (1<<2);/*    清除对应GPIOA端口第0引脚 */*(unsigned int*)(40010800) &= ~(0x0F<<(4*0));/*    CRL_MODEx =3; CNFx =0;    */*(unsigned int*)(40010800) |= (3<<(4*0));/*    用BSRR寄存器控制PA0引脚输出高电平 */
//  *(unsigned int*)(40010800+0x10) |= (1<<(0+0));/*   用BSRR寄存器控制PA0引脚输出低电平 */
//  *(unsigned int*)(40010800+0x10) |= (1<<(16+0));/*  用BRR寄存器控制PA0引脚输出低电平 */
//  *(unsigned int*)(40010800+0x14) |= (1<<(0+0));/*   用ODR寄存器控制PA0引脚输出高电平 */*(unsigned int*)(40010800+0x0C) |= (1<<(0+0));/* 用ODR寄存器控制PA0引脚输出低电平 */*(unsigned int*)(40010800+0x0C) |=(0<<(0+0));
}
void SystemInit(void)
{/* 时钟配置函数,在这里不编写任何东西,主要目的是使编译器不报错也可以把启动文件 straup_stm32f10x_.s中的SystemInit 注释掉 ;相当于C语言中的//在STM32启动时会进入启动文件中执行以下参数:1.初始化栈堆指针2.初始化程序指针3.初始化中断向量表4.配置系统时钟5.调用C库函数_main()初始化用户程序指针6.进入C语言main()主函数中    */
}

3. 固件库开发

3.1 前言

在前面讲解了直接面向寄存器开发,我想读者已经注意到了一个非常重要的问题。在那一串一串的16进制数中如果不查阅相关的手册,里面有什么,具体叫什么,实现什么功能我们根本不知道。所以这样的程序可读性,维护性上都不高效。但是这样的程序在编译运行上比用固件库编写出的程序还要快。不过随着STM32F103时钟频率越来越高、寄存器越来越多,底层编程已经显的很吃力。我们宁愿放弃这一点速度,换取可读性,可维护性上。在使用固件库开发的路上,用户无需了解函数是如何实现你想要的功能的,像C语言标准库中的格式输出函数 printf(),用户只需如何使用该函数即可。在官方库未完善时很多用户觉得使用固件库开发简直是在用浮沙筑高楼,完全脱离底层。不过随着官方固件库渐渐完善,用户也慢慢开始接受。在使用固件库开发时你可以把库层层揭开,你将看到固件库编写者在里面留下的智慧结晶。配合相关手册阅读你将被它的所魅力吸引。分析库可使你更了解底层以及寄存器的驱动方式。下面我将带读者从寄存器向上一步一步构建库,带读者了解库是如何形成的。

3.2 封装(映像)

把各个外设中的地址用C语言中的宏定义(预处理命令中的一种)给各地址起别名,此过成称为地址映像或寄存器映像。以后使用这些宏名就可达到操作地址的目的。下面就是用宏封装的地址。

/*   外设总线的地址*/
#define  PERPH_BASE          0x40000000
/*  相对总线的外设基地址  */
#define  APB1_PERPH_BASE     PERPH_BASE
/*  高速72MHzz总线地址    */
#define  APB2_PERPH_BASE     (PERPH_BASE + 0x10000)
/*  系统总线地址  */
#define  AHB_PERPH_BASE      (PERPH_BASE + 0x20000)/*  GPIOA寄存器的所在地址   */
#define  GPIOA_BASE          (APB2_PERPH_BASE + 0x0800)
/*  时钟RCC地址 */
#define  RCC_BASE            *(unsigned int*)(AHB_PERPH_BASE + 0x1000)
/*  低8位控制寄存器的地址 */
#define  GPIOA_CRL           *(unsigned int*)(GPIOA_BASE + 0x00)
/*  设置/清除寄存器的地址 */
#define  GPIOA_BSRR          *(unsigned int*)(GPIOA_BASE + 0x10)
/*  清除寄存器的地址    */
#define  GPIOA_BRR           *(unsigned int*)(GPIOA_BASE + 0x14)
/*  数据输出寄存器的地址  */
#define  GPIOA_ODR           *(unsigned int*)(GPIOA_BASE + 0x0C)

之后把main()函数中的部分内容修改为如下所示

void main()
{/* GPIOA的时钟 */RCC_BASE |= (1<<2);/* 清除对应GPIOA端口第0引脚 */GPIOA_CRL &= ~(0x0F<<(4*0));/* CRL_MODEx =3; CNFx =0;    */GPIOA_CRL |= (3<<(4*0));/* 用BSRR寄存器控制PA0引脚输出高电平 */
//  GPIOA_BSRR |= (1<<(0+0));/* 用BSRR寄存器控制PA0引脚输出低电平 */
//  GPIOA_BSRR |= (1<<(16+0));/*    用BRR寄存器控制PA0引脚输出低电平 */
//  GPIOA_BRR |= (1<<0);/*   用ODR寄存器控制PA0引脚输出高电平 */GPIOA_ODR |= (1<<0);/* 用ODR寄存器控制PA0引脚输出低电平 */GPIOA_ODR |=(0<<0);
}
void SystemInit(void)
{/* 时钟配置函数,在这里不编写任何东西,主要目的是使编译器不报错也可以把启动文件 straup_stm32f10x_.s中的SystemInit 注释掉 ;相当于C语言中的//在STM32启动时会进入启动文件中执行以下参数:1.初始化栈堆指针2.初始化程序指针3.初始化中断向量表4.配置系统时钟5.调用C库函数_main()初始化用户程序指针6.进入C语言main()主函数中    */
}

看到这样的程序是不是一下就知道具体是实现什么功能了。这里我们把整型指针也放到了里面去,在使用时只需调用该宏名即可。现在用户不会再看到一串一串16进制数而感到头皮发麻。
但是现在又出现了一个问题,像STM32F103的外设这么多,许多外设的寄存器都是一样的。如GPIOx(x=A~G)它们的寄存器都是一样的,如果这样编写下去宏名就已经泛滥成灾了。不过C语言提供了一个非常好的解决方法,结构体指针类型。

3.3 封装(结构体)

在STM32F103中许多外设的寄存器都是一样的,如果单单用宏来封装,重复编写的工作量会变得很大,可读性也会慢慢降低。在这里就介绍如何使用结构体来封装重复的寄存器所在地址。
学过结构体的用户都知道结构体成员在存储器中所占空间是连续的或有一些偏移地址(对齐)。而STM32中的各个寄存器所占4个字节(32位),这样就可以定义结构体成员为 unsigned int ,因为整型变量在这占4个字节。成员与成员之间的偏移地址也为4个字节,符合外设寄存器之间的偏移地址。定义结构体成员时,外设有多少个寄存器就定义多少个结构体。如下定义GPIO端口结构体所示

/*   GPIO端口寄存器成员 */
typedef struct  {unsigned int  CRL;unsigned int  CRH;unsigned int  ODR;unsigned int  IDR;unsigned int  BSRR;unsigned int  BRR;unsigned int  LCKR;
} GPIO_TypeDef;

之后再把该结构体与外设地址关联起来,如下所示

/*   GPIOA_BASE = 0x40010800    */
#define GPIOA       ((GPIO_TypeDef*)(GPIOA_BASE))

修改程序如下所示

 /* GPIOA的时钟 */RCC_BASE |= (1<<2);/*  清除对应GPIOA端口第0引脚 */GPIOA->CRL &= ~(0x0F<<(4*0));/* CRL_MODEx =3; CNFx =0;    */GPIOA->CRL |= (3<<(4*0));/* 用BSRR寄存器控制PA0引脚输出高电平 */
//  GPIOA->BSRR |= (1<<(0+0));/* 用BSRR寄存器控制PA0引脚输出低电平 */
//  GPIOA->BSRR |= (1<<(16+0));/*    用BRR寄存器控制PA0引脚输出低电平 */
//  GPIOA->BRR |= (1<<0);/*   用ODR寄存器控制PA0引脚输出高电平 */GPIOA->ODR |= (1<<0);/* 用ODR寄存器控制PA0引脚输出低电平 */GPIOA->ODR |=(0<<0);

不过我看不出有什么区别呀,不过就是把下斜杆 _ 换成指向运算符 -> 而已。但是如果要驱动的外设多了缺点就会越来越明显,大量意义相同的宏定义看着头皮发麻。
现在明显有点像库的感觉了,可是好像还差了点什么。认真看一下写入寄存器的值是用位运算符计算出值送入的,但是要知道计算出的值是否符合想要送入的值,这还是要查询关于寄存器的介绍手册才行。
我们再定义一个包含三个成员的结构体变量用来设置GPIOA的工作环境。成员具体实现的功能有:指定 1.GPIO端口引脚,1. 工作模式,3.输出速度,如下所示

/*   GPIO端口参数设置结构体   */
typedef struct  {unsigned short GPIO_Pin;   /*  GPIO口*/unsigned short GPIO_Speed;   /*  输出速度*/unsigned short GPIO_Mode;     /*  工作模式*/} GPIO_InitTypeDef;/* CRL/CRH 输出速度定义  */
typedef enum  {GPIO_Speed_10MHz = 1,GPIO_Speed_2MHz ,GPIO_Speed_50MHz
} GPIOSpeed_TypeDef;/*  CRL/CRH 工作模式选择  */
typedef enum  {/*   输入模式    */GPIO_Mode_AIN = 0X0,         /*  模拟*/GPIO_Mode_IN_FLOATING = 0X04,  /*  浮空*/GPIO_Mode_IPD = 0X28,          /*  下拉*/GPIO_Mode_IPU = 0X48,          /*  上拉*//*  输出模式    */GPIO_Mode_Out_OD = 0X14,     /*  开漏*/GPIO_Mode_Out_PP = 0X10,       /*  推挽*/GPIO_Mode_AF_OD = 0X1C,            /*  复用开漏*/GPIO_Mode_AF_PP = 0X18           /*  复用推挽*/
} GPIOMode_TypeDef;/*   GPIO端口引脚定义  */
#define  GPIO_Pin_0          ((uint16_t) 0x0001)    /*  1<<0  */
#define  GPIO_Pin_1          ((uint16_t) 0x0002)    /*  1<<1  */
#define  GPIO_Pin_2          ((uint16_t) 0x0004)    /*  1<<2  */
#define  GPIO_Pin_3          ((uint16_t) 0x0008)    /*  1<<3  */
#define  GPIO_Pin_4          ((uint16_t) 0x0010)

在初始化结构体的同时还定义了两个枚举类型,为GPIO端口配置工作模式及输出速度。和一些宏,具体对应GPIO端口的引脚。它们配置完参数时还需调用一个GPIO端口初始化函数 GPIO_Init(GPIO_TypeDef * ,GPIO_InitTypeDef *),由该函数把参数写入到对应的寄存器中。
该函数的写入过程:
(1)判断是否为输出模式(根据工作模式参数中的第4位来判断,0为输入,1为输出),如为1 则把输出速度加入进去。
(2)寻找需驱动的引脚,找到则把参数写入到控制该引脚的配置寄存器CRL/CRH中(4位控制一个引脚)。
(3)判断输入模式如果是下拉输入则驱动,清除位寄存器(BRR)。如果为上拉输入则驱动,设置/清除寄存器(BSRR)低16位。具体该函数的实现部分请查看官方库函数中的 GPIO_Init(GPIO_TypeDef * ,GPIO_InitTypeDef)。
函数如下所示

/*   先前编写的一些宏定义,结构体,枚举类型都在这个头文件中   */
#include “stm32f10x.h”
/*  延迟函数    */
void LED_Delay(unsigned int dat);void main()
{GPIO_InitTypeDef GPIO_InitStructure ;/*    开启GPIO_A的时钟 */RCC_APB2ENR |= (1<<2);/*   选择GPIO_0 端口     */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ;/* 工作模式为 推挽式输出 */GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;/*  输出速度为 10MHz */GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz ;/* 初始化GPIOA端口      */GPIO_Init(GPIOA , &GPIO_InitStructure);while(1){/*    高电平 */GPIOA->BSRR = GPIO_Pin_0;LED_Delay(unsigned int dat)/*    低电平 */GPIOA->BRR = GPIO_Pin_0;LED_Delay(unsigned int dat)}
}void LED_Delay(unsigned int dat)
{for(; dat>0; dat--);
}

4 小结

(1)带读者熟悉一些需驱动的寄存器如APB2时钟配置寄存器,CRL/CRH端口配置寄存
器,ODR输出寄存器,BRR清除寄存器,设置/清除寄存器。
(2)面向寄存器直接配置参数,带读者了解它的优缺点。
(3)用宏封装寄存器的所在地址。
(4)用结构体定义需配置的寄存器参数,最后调用GPIO端口初始化函数

void GPIO_Init(GPIO_Typedf *GPIOx , GPIO_InitTypedf *InitStruct);

学习笔记之STM32库(学习笔记)相关推荐

  1. Arduino开发-TFT_eSPI库学习

    TFT_eSPI库学习 文章目录 TFT_eSPI库学习 TFT_eSPI库安装以及配置 TFT_eSPI库文件目录 配置文件 1.User_Setup_.h 2. User_Setup_Select ...

  2. STM32 HAL库学习笔记1-HAL库简介

    STM32 HAL库学习笔记1-HAL库简介 HAL库 SPL 库 和 HAL 库两者相互独立,互不兼容.几种库的比较如下 目前几种库对不同芯片的支持情况如下 ST 中文官网上有一篇<关于ST库 ...

  3. STM32 HAL库学习笔记4-SPI

    STM32 HAL库学习笔记4-SPI 前言 一.SPI协议简介 SPI物理层 SPI协议层 1.基本通讯过程 2. 通讯的起始和停止信号 3. 数据有效性 4. CPOL/CPHA 及通讯模式 二. ...

  4. STM32 HAL库学习笔记2 HAL库介绍

    STM32 HAL库学习笔记2 HAL库介绍 CMSIS标准 一.再次认识HAL库 HAL库设计思想 HAL库实现方式 以GPIO模块为例 GPIO外设数据类型 GPIO外设接口函数 二.使用HAL库 ...

  5. STM32软件学习笔记(一)基于HAL库的STM32F429单片机串口打印程序

    |版权声明:本文为博主原创文章,转载请注明出处.https://blog.csdn.net/NeverImagine_/article/details/95517664   目前ST官方有提供两种库文 ...

  6. STM32 FSMC学习笔记+补充(LCD的FSMC配置)

    STM32 FSMC学习笔记+补充(LCD的FSMC配置) STM32 FSMC学习笔记 STM32 FSMC的用法--LCD 转载于:https://www.cnblogs.com/LittleTi ...

  7. 多线程编程学习笔记——任务并行库(二)

    接上文 多线程编程学习笔记--任务并行库(一) 三.   组合任务 本示例是学习如何设置相互依赖的任务.我们学习如何创建一个任务的子任务,这个子任务必须在父任务执行结束之后,再执行. 1,示例代码如下 ...

  8. 多线程编程学习笔记——任务并行库(三)

    接上文 多线程编程学习笔记--任务并行库(一) 接上文 多线程编程学习笔记--任务并行库(二) 六.   实现取消选项 本示例学习如何实现基于Task的异步操作进行取消流程,以及在任务真正运行前如何知 ...

  9. python xlwings 切片_Python xlwings库学习笔记(1)

    Python xlwings库学习笔记(1) Python是最近几年很火的编程语言,被办公自动化的宣传吸引入坑,办公自动化必然绕不开Excel的操作,能操作Excel的库有很多,例如: xlrd xl ...

最新文章

  1. tcpdump 命令的个常用选项:三
  2. flask项目中无法更改端口号
  3. [转]24岁到26岁 奔三的尴尬年纪,你要知道的50件事
  4. 第6周第4课:复习及扩展知识
  5. SAP SD:SAP信贷出口
  6. Why Opportunity list is empty
  7. linux与windows查看占用端口的进程ID并杀死进程
  8. 支持python开发的环境有哪些特点_Python虚拟环境详细教程,一篇带你入坑
  9. django 1.8 官方文档翻译: 2-5-1 管理器
  10. 【数理统计】一题了解假设检验
  11. 字符串的哈希值mysql_字符串经典的hash算法
  12. 【python学习笔记】python运算符以及简单语句
  13. Android 面试必备 - 线程
  14. 杰里之drc 限幅器、多带限幅器、压缩器、多带压缩器调节【篇】
  15. while循环因为内部使用ssh命令而导致不能循环文件的所有行
  16. fastspeech2复现github项目--数据准备
  17. REW声学测试(四):REW的测试原理
  18. 沪市和深市股票托管方式的区别
  19. 北大4位数学天才,如今齐聚美国搞科研,令人叹息
  20. 常用WebServices 天气,IP,邮编,Email,火车时刻表,股票 web接口服务

热门文章

  1. 解决 未将对象引用设置到对象的实例,遇到异常,这可能是由某个扩展导致的
  2. BUUCTF 每天10道Misc Day5
  3. python中fillna_在Python中在datetime对象上的pandas fillna
  4. win10电脑硬盘出现黄三角感叹号的解决方法
  5. GeForce RTX 2080显卡驱动安装(linux系统)
  6. 爬取用益信托网部分数据
  7. About ThatsMyJamRadio
  8. Icomoon插入图标方法②
  9. 关于vmware新建虚拟机时,最开始出现新建向导提示无法识别光盘问题的解决方案
  10. vuex当中mapGetters使用