• 【STM32】标准库的引入


    一、为什么要会有标志外设库

    1、传统单片机软件开发方式

    (1)芯片厂商提供数据手册、示例代码、开发环境
    (2)单片机软件工程师面向产品功能,查阅数据手册,参考官方示例代码进行开发
    (3)硬件操作的方式是用C语言对寄存器进行读写以操作硬件
    (4)主要工作量分2块:一是调通各种外设,二是实现产品功能
    (5)在简单单片机(如51单片机)上这一套工作的很好,但是随着单片机变复杂就带来一些问题

    2、外设库有什么价值

    (1)外设库其实就是以前芯片公司提供的示例代码的标准化产物
    (2)外设库简化了我们开发产品的2大工作量的第一个
    (3)外设库以源码方式提供,这个源码本身写的很标准,可以用作学习素材

    3、学习和使用外设库的难点

    (1)要有规范化编程的意识和能力
    (2)C语言功底要过关
    (3)要有一定的框架和层次认识
    (4)要会没有外设库时直接C语言操作寄存器的方式(看原理图、查数据手册、位操作等)

    二、外设库的结构介绍和之后的学习方法

    1.外设库的结构介绍

    (1)最新版本库的下载和解压

    官方网站:STM32标准外设软件库: 相关产品

    (2)建立SourceInsight工程

    1)先在盘符中建立一个文件夹,然后再sourceInsight中打开这个文件夹

    (3)文件夹结构和主要文件的作用

    1.Project

    2.Libraries

    CMSIS(STM32内部ARM核心相关内容)
        CM3(Cortex-M3)
            CoreSupport
                内核相关的一些设置的寄存器集合及其封装


            DeviceSupport
                ST
                    STM32F10x
                        startup(起始文件)

    https://www.cnblogs.com/amanlikethis/p/3989989.html
                        stm32f10x.h【STM32常见数据结构的封装】
                        system_stm32f10x.c
                        system_stm32f10x.h


    STM32F10x_StdPeriph_Driver(外设驱动)
        inc(include,头文件,.h)
        src(source,源文件, .c)

    2.system_stm32f10x.c

    在这个文件中定义了2个函数和一个全局变量:时钟相关的

    3、后续的学习方法

    (1)先搞清楚库对STM32这个硬件的封装和表达方式【对外设进行封装】
    (2)再彻底理解库中使用的结构体式访问硬件寄存器的方式
    (3)初步建立起面向对象式编程的概念并且去体会
    (4)以模块为单位去研究这个模块的库函数,并且用库函数去编程,并且实验结果,并且分析代码,去体会去熟悉库函数使用的方法
    (5)最终达到什么程度?眼里有库心中无库。用人话说就是:思维能够穿透库函数直达内部对寄存器的操作。

    三、标准库对硬件信息的封装方法

    1.寄存器地址的封装

    1.手动操作寄存器地址

    2.库文件中的寄存器地址(stm32f10x.h)

    2.寄存器位定义的封装

    1.直接通过移位操作位寄存器

    2.使用宏定义进行位寄存器

    不用直接写数字,而使用宏定义

    3.外设操作的封装

    1.自定义函数

    2.使用库函数

    .c文件对其进行封装

    四、使用结构体方式访问寄存器的原理

    1.最原始的方法

    1)C语言访问寄存器的本质是c语言访问内存,本质思路是:

    定义一个指针指向这块内存,然后*p=xx这种方式去解引用指针从而向没有标内存中写入内容。

    2)缺陷:当寄存器多了之后每一个寄存器都要定义一套套路,很麻烦

    3)解决思路:就是打包,批发式定义,用结构体(为什么不用数组)的方式来进行打包。具体方法:把整个模块的所有寄存器(地址是连续的)打包在一个结构体中,每一个寄存器对应结构体中的一个元素,然后结构体基地址对应寄存器组的基地址,将来就可以通过结构体的各个元素来访问各个寄存器了。

    2.使用结构体

    结构体方式来访问寄存器和指针式访问寄存器,本质上其实是一样的,区别是c语言的封装不同。

    TIM的

    GPIO的

    五、使用结构体方式访问寄存器的实践

    1.原始方式

    gpio.h

    1. #define GPIOB_CRH 0x40010C04
    2. #define GPIOB_CRL 0x40010C00
    3. #define GPIOB_ODR 0x40010C0C
    4. #define GPIOB_BSRR 0x40010C10
    5. #define GPIOB_BRR 0x40010C14
    6. #define RCC_APB2ENR 0x40021018
    7. #define rGPIOB_CRH (*((unsigned int *)GPIOB_CRH))
    8. #define rGPIOB_ODR (*((unsigned int *)GPIOB_ODR))
    9. #define rGPIOB_BSRR (*((unsigned int *)GPIOB_BSRR))
    10. #define rGPIOB_BRR (*((unsigned int *)GPIOB_BRR))
    11. #define rRCC_APB2ENR (*((unsigned int *)RCC_APB2ENR))
    12. void led_flash(void);
    13. void led_init();
    14. void delay();

    gpio.c

    1. /**
    2. 点亮led灯
    3. */
    4. #include "gpio.h"
    5. void delay(){
    6. unsigned int i=0,j=0;
    7. for(i=0;i<1000;i++){
    8. for(j=0;j<4000;j++){
    9. }
    10. }
    11. }
    12. void led_init(){
    13. rRCC_APB2ENR = 0x00000008;
    14. rGPIOB_CRH = 0x33333333;
    15. rGPIOB_ODR = 0x0000aa00;//全灭
    16. }
    17. void led_flash(void){
    18. unsigned int i=0;
    19. for(i=0;i<3;i++){
    20. rGPIOB_ODR = 0x00000000;//全亮
    21. delay();
    22. rGPIOB_ODR = 0x0000ff00;//全灭
    23. delay();
    24. }
    25. }
    26. void main(void){
    27. led_init();
    28. led_flash();
    29. }

    2.使用结构体的方式对寄存器访问

    注意点:

    我们在定义相关寄存器的结构体的时候要注意顺序问题,一定要按照寄存器偏移量从低到高,要不然会出现问题。

    3.两者对比

    代码

    gpio.h

    1. #define GPIOB_BASE 0x40010C00
    2. #define GPIOC_BASE 0x40011000
    3. #define GPIOB_CRH (GPIOB_BASE + 0x04)
    4. #define GPIOB_ODR (GPIOB_BASE + 0x0C)
    5. #define GPIOB_BSRR (GPIOB_BASE + 0x10)
    6. #define GPIOB_BRR (GPIOB_BASE + 0x14)
    7. #define GPIOC_CRL (GPIOC_BASE + 0x00)
    8. #define GPIOC_ODR (GPIOC_BASE + 0x0C)
    9. #define GPIOC_BSRR (GPIOC_BASE + 0x10)
    10. #define GPIOC_BRR (GPIOC_BASE + 0x14)
    11. #define RCC_APB2ENR 0x40021018
    12. //-------------------------------------------------
    13. #define rGPIOB_CRH (*((unsigned int *)GPIOB_CRH))
    14. #define rGPIOB_ODR (*((unsigned int *)GPIOB_ODR))
    15. #define rGPIOB_BSRR (*((unsigned int *)GPIOB_BSRR))
    16. #define rGPIOB_BRR (*((unsigned int *)GPIOB_BRR))
    17. #define rGPIOC_CRL (*((unsigned int *)GPIOC_CRL))
    18. #define rGPIOC_ODR (*((unsigned int *)GPIOC_ODR))
    19. #define rGPIOC_BSRR (*((unsigned int *)GPIOC_BSRR))
    20. #define rGPIOC_BRR (*((unsigned int *)GPIOC_BRR))
    21. #define rRCC_APB2ENR (*((unsigned int *)RCC_APB2ENR))
    22. //用结构体的方式对寄存器访问
    23. typedef struct{
    24. unsigned int CRL;
    25. unsigned int CRH;
    26. unsigned int IDR;
    27. unsigned int ODR;
    28. unsigned int BSRR;
    29. unsigned int BRR;
    30. unsigned int LCKR;
    31. }GPIO_Typedef;
    32. void led_flash(void);
    33. void led_init();
    34. void delay();

    gpio.c

    1. /**
    2. 点亮led灯
    3. */
    4. #include "gpio.h"
    5. void delay(){
    6. unsigned int i=0,j=0;
    7. for(i=0;i<1000;i++){
    8. for(j=0;j<4000;j++){
    9. }
    10. }
    11. }
    12. /*
    13. 原始方法
    14. void led_init(){
    15. rRCC_APB2ENR = 0x00000008;
    16. rGPIOB_CRH = 0x33333333;
    17. rGPIOB_ODR = 0x0000aa00;//隔一个亮一个
    18. }
    19. void led_flash(void){
    20. unsigned int i=0;
    21. for(i=0;i<3;i++){
    22. rGPIOB_ODR = 0x00000000;//全亮
    23. delay();
    24. rGPIOB_ODR = 0x0000ff00;//全灭
    25. delay();
    26. }
    27. }
    28. */
    29. void led_init(){
    30. GPIO_Typedef *p=(GPIO_Typedef *)GPIOB_BASE;
    31. //因为RCC部分还没有定义结构体,所以还是按照原来的方式去操作
    32. rRCC_APB2ENR = 0x00000008;
    33. //结构体只能使用【->】
    34. p->CRH=0x33333333;
    35. p->ODR=0x0000aa00;
    36. }
    37. void led_flash(void){
    38. GPIO_Typedef *p=(GPIO_Typedef *)GPIOB_BASE;
    39. unsigned int i=0;
    40. for(i=0;i<3;i++){
    41. p->ODR = 0x00000000;//全亮
    42. delay();
    43. p->ODR = 0x0000ff00;//全灭
    44. delay();
    45. }
    46. }
    47. void main(void){
    48. led_init();
    49. led_flash();
    50. }

    4.小技巧

    1.将基地址指针作为全局变量

    因为在每一个函数开始之前,都要使用到基地址

    2.结构体元素填充

    我们前面提到说,在定义结构体的时候一定一定要按照寄存器的偏移量从小到大的顺序定义要不然出错,但是我们在一些地方可能空缺出来,没有寄存器,所以我们需要一个占位,将其补充,要不然后面的其他寄存器可能受到影响。

    3.寄存器可操作位数不同

    我们在学习RCC时钟的时候遇到一些寄存器可以操作32位,但是有一些只能操作16位。此时我们可以将其直接写入(不用管是否用足32位),也可以分成2个16位写入。

    1. //用结构体的方式对寄存器访问
    2. typedef struct{
    3. unsigned int CRL;
    4. unsigned int CRH;
    5. //unsigned int IDR;
    6. //将32位的IDR分为2个16位
    7. unsigned short IDR;
    8. unsigned short paddingIDR;//此16位用不到
    9. unsigned int ODR;
    10. unsigned int BSRR;
    11. unsigned int BRR;
    12. unsigned int LCKR;
    13. }GPIO_Typedef;

    六、使用标准库重写LED的程序

    1.分析标准库自带的文件模板

    1.完整目录

    2.User,Driver

    3.CMSIS.startup

    4.配置注意点:

    2.stm32f10x.h

    选择芯片类型

    在“stm32f10x.h”有可以查看,要修改可以在options--》c/c++中进行修改宏定义

    2.宏定义的设置

    3.外部晶振时钟设置

    3.stm32f10x_conf.h

    1.包含所有外设的头文件

    这个头文件是标准库的模板中的,将所有需要使用到的头文件包含进去

    所以在整个工程中,无论我们创建一个什么类型的.c文件,只要我们定义了

    【“stm32f10x.h”】

    则就等价于将所有的外设器件的.h文件包含进来了

    2.assert_param(断言机制)

    4.正式自己搭建文件目录

    1.文件结构

    2.文件导入

    1.startup

    2.CMSIS

    3.stdperiph_driver

    4.user

    3.配置

    自定义宏定义,在stm32f10x.h,stm32f10_conf.h文件中查找

    4.出现问题

    将配置中的”断言“去除

    5.代码修改

    1. #include "stm32f10x.h"
    2. /**
    3. 使用标准库重写LED的程序
    4. */
    5. /*
    6. //原始代码
    7. void led_init(){
    8. //GPIO_Typedef *p=(GPIO_Typedef *)GPIOB_BASE;
    9. //因为RCC部分还没有定义结构体,所以还是按照原来的方式去操作
    10. rRCC_APB2ENR = 0x00000008;
    11. //结构体只能使用【->】
    12. //p->CRH=0x33333333;
    13. pGPIOB->CRH=0x33333333;
    14. //p->ODR=0x0000aa00;
    15. pGPIOB->ODR=0x0000aa00;
    16. }
    17. */
    18. //第一步:先去”stm32f10x.h"文件中查找相关的寄存器
    19. // 比如RCC,GPIO
    20. //第二步:找到相关的寄存器的宏定义,将其直接复制过来
    21. //使用HAL库
    22. void led_init(){
    23. //GPIO_Typedef *p=(GPIO_Typedef *)GPIOB_BASE;
    24. //因为RCC部分还没有定义结构体,所以还是按照原来的方式去操作
    25. RCC->APB2ENR = 0x00000008;
    26. //结构体只能使用【->】
    27. //p->CRH=0x33333333;
    28. GPIOB->CRH=0x33333333;
    29. //p->ODR=0x0000aa00;
    30. GPIOB->ODR=0x0000aa00;
    31. }
    32. int main(){
    33. led_init();
    34. return 0;
    35. }

    七、RCC模块的标准库全解析

    【STM32】RCC时钟模块(使用HAL库)-CSDN博客

    八、CPIO模块的标准库全解析

    【STM32】GPIO控制LED(HAL库版)-CSDN博客

    九、标准库中的面向对象思想

    1、面向对象介绍

    (1)一种编程思想(面向过程、面向对象)
    (2)什么是对象
    (3)面向对象三大特征:封装、继承、多态
    (4)面向对象编程思想和面向对象语言是两码事

    2、标准库的面向对象特征

    (1)各种数据类型结构体就是一种封装
    (2)标准库是为了被复用
    (3)GPIO的编程模式是典型的面向对象式编程

    典型面向对象的编程模式:

    第1步:先构建对象(可以理解为定义一个结构体类型)
    第2步:用对象构造实例(可以理解为用结构体类型来定义结构体变量)malloc
    第3步:填充实例(其实就是给结构体的各个元素赋值)
    第4步:使用实例(其实就是把结构体变量作为参数传给某个函数使用)
    第5步:销毁实例(其实就是把前面第2步定义的机构体变量给销毁掉)free

  • 相关阅读:
    禅道数据库异机访问,远程连接,navicat连接
    在线客服系统源码 聊天记录实时保存 附带完整的搭建教程
    PHP Error 和 Logging 函数
    基于AVR单片机的移动目标视觉追踪系统设计与实现
    gcc/g++使用格式+各种选项,预处理/编译(分析树,编译优化,生成目标代码)/汇编/链接过程(函数库,动态链接)
    【排序算法】Leetcode刷题心得
    go的gin框架实现接受多个图片和单个视频并保存到本地服务器的接口
    2023上海国际电力电工展盛大举行 规模创新高 与行业「升级、转型、融合」
    场景之在线人数或者粉丝查询实现
    在大厂工作是这样的
  • 原文地址:https://blog.csdn.net/m0_63077733/article/details/133999302