当前位置: 首页 > news >正文

品牌网站建设解决方案萨隆wordpress

品牌网站建设解决方案,萨隆wordpress,惠州外发加工网,宁波住房和城乡建设局网站首页6. 什么是寄存器 本章参考资料#xff1a;《STM32F10xx 参考手册》、《STM32F10xx数据手册》、 学习本章时#xff0c;配合《STM32F10xx 参考手册》“存储器和总线架构”及“通用I/O(GPIO)”章节一起阅读#xff0c;效果会更佳#xff0c;特别是涉及到寄存器说明的部分。…6. 什么是寄存器 本章参考资料《STM32F10xx 参考手册》、《STM32F10xx数据手册》、 学习本章时配合《STM32F10xx 参考手册》“存储器和总线架构”及“通用I/O(GPIO)”章节一起阅读效果会更佳特别是涉及到寄存器说明的部分。 6.1. 什么是寄存器 我们经常说寄存器那么什么是寄存器这是我们本章需要讲解的内容在学习的过程中大家带着这个疑问好好思考下到最后看看大家能否用一句话给寄存器下一个定义。 6.2. STM32长啥样 我们开发板中使用的芯片是64pin的STM32F103RCT6具体见图 STM32F103RCT6实物图。 这个就是我们接下来要学习的STM32它将带领我们进入嵌入式的殿堂。 芯片正面是丝印ARM应该是表示该芯片使用的是ARM的内核STM32F103RCT6是芯片型号后面的字应该是跟生产批次相关最上面的是ST的LOGO。 芯片四周是引脚左下角的小圆点表示1脚然后从1脚起按照逆时针的顺序排列所有芯片的引脚顺序都是逆时针排列的。 开发板中把芯片的引脚引出来连接到各种传感器上然后在STM32上编程实际就是通过程序控制这些引脚输出高电平或者低电平来控制各种传感器工作 通过做实验的方式来学习STM32芯片的各个资源。开发板是一种评估板板载资源非常丰富引脚复用比较多 力求在一个板子上验证芯片的全部功能。STM32F103RCT6正面引脚图 6.3. 芯片里面有什么 我们看到的STM32芯片是已经封装好的成品主要由内核和片上外设组成。若与电脑类比内核与外设就如同电脑上的CPU与主板、内存、显卡、硬盘的关系。 STM32F103采用的是Cortex-M3内核内核即CPU由ARM公司设计。ARM公司并不生产芯片而是出售其芯片技术授权。 芯片生产厂商(SOC)如ST、TI、Freescale负责在内核之外设计部件并生产整个芯片这些内核之外的部件被称为核外外设或片上外设。 如GPIO、USART串口、I2C、SPI等都叫做片上外设。具体见图 STM32芯片架构简图。 芯片这里指内核或者叫CPU和外设之间通过各种总线连接其中驱动单元有4个被动单元也有4个 具体见图 STM32F10xx系统框图。为了方便理解我们都可以把驱动单元理解成是CPU部分被动单元都理解成外设。 下面我们简单介绍下驱动单元和被动单元的各个部件。 6.3.1. ICode总线 ICode中的I表示Instruction即指令。我们写好的程序编译之后都是一条条指令存放在FLASH中 内核要读取这些指令来执行程序就必须通过ICode总线它几乎每时每刻都需要被使用它是专门用来取指的。 6.3.2. 驱动单元 6.3.2.1. DCode总线 DCode中的D表示Data即数据那说明这条总线是用来取数的。我们在写程序的时候数据有常量和变量两种 常量就是固定不变的用C语言中的const关键字修饰是放到内部的FLASH当中的变量是可变的不管是全局变量还是局部变量都放在内部的SRAM。 因为数据可以被Dcode总线和DMA总线访问所以为了避免访问冲突在取数的时候需要经过一个总线矩阵来仲裁决定哪个总线在取数。 6.3.2.2. 系统总线 系统总线主要是访问外设的寄存器我们通常说的寄存器编程即读写寄存器都是通过这根系统总线来完成的。 6.3.2.3. DMA总线 DMA总线也主要是用来传输数据这个数据可以是在某个外设的数据寄存器可以在SRAM可以在内部的FLASH。 因为数据可以被Dcode总线和DMA总线访问所以为了避免访问冲突在取数的时候需要经过一个总线矩阵来仲裁决定哪个总线在取数。 6.3.3. 被动单元 6.3.3.1. 内部的闪存存储器 内部的闪存存储器即FLASH我们编写好的程序就放在这个地方。内核通过ICode总线来取里面的指令。 6.3.3.2. 内部的SRAM 内部的SRAM即我们通常说的RAM程序的变量堆栈等的开销都是基于内部的SRAM。内核通过DCode总线来访问它。 6.3.3.3. FSMC FSMC的英文全称是Flexible static memory controller叫灵活的静态的存储器控制器 是STM32F10xx中一个很有特色的外设 通过FSMC我们可以扩展内存如外部的SRAMNANDFLASH和NORFLASH。但有一点我们要注意的是FSMC只能扩展静态的内存 即名称里面的Sstatic不能是动态的内存比如SDRAM就不能扩展。 6.3.3.4. AHB到APB的桥 从AHB总线延伸出来的两条APB2和APB1总线上面挂载着STM32各种各样的特色外设。我们经常说的GPIO、串口、I2C、SPI这些外设就挂载在这两条总线上 这个是我们学习STM32的重点就是要学会编程这些外设去驱动外部的各种设备。 6.4. 存储器映射 在图 STM32F10xx系统框图 中被控单元的FLASH RAMFSMC和AHB到APB的桥即片上外设这些功能部件共同排列在一个4GB的地址空间内。 我们在编程的时候可以通过他们的地址找到他们然后来操作他们通过C语言对它们进行数据的读和写。 6.4.1. 存储器映射 存储器本身不具有地址信息它的地址是由芯片厂商或用户分配给存储器分配地址的过程就称为存储器映射 具体见图 存储器映射。如果给存储器再分配一个地址就叫存储器重映射。 6.4.1.1. 存储器区域功能划分 在这4GB的地址空间中ARM已经粗线条的平均分成了8个块每块512MB每个块也都规定了用途具体分类见表格 存储器功能分类。 每个块的大小都有512MB显然这是非常大的芯片厂商在每个块的范围内设计各具特色的外设时并不一定都用得完都是只用了其中的一部分而已。 在这8个Block里面有3个块非常重要也是我们最关心的三个块。Block0用来设计成内部FLASHBlock1用来设计成内部RAM Block2用来设计成片上的外设下面我们简单的介绍下这三个Block里面的具体区域的功能划分。 6.4.1.1.1. 存储器Block0内部区域功能划分 Block0主要用于设计片内的FLASH我们使用的STM32F103ZET6霸道和STM32F103VET6指南者的FLASH都是512KB 属于大容量。要在芯片内部集成更大的FLASH或者SRAM都意味着芯片成本的增加往往片内集成的FLASH都不会太大 ST能在追求性价比的同时做到512KB实乃良心之举。Block内部区域的功能划分具体见表格 存储器Block0内部区域功能划分。 6.4.1.1.2. 储存器Block1内部区域功能划分 Block1用于设计片内的SRAM。我们使用的STM32F103ZET6霸道和STM32F103VET6指南者的SRAM都是64KB Block内部区域的功能划分具体见表格 存储器Block1内部区域功能划分。 6.4.1.1.3. 储存器Block2内部区域功能划分 Block2用于设计片内的外设根据外设的总线速度不同Block被分成了APB和AHB两部分其中APB又被分为APB1和APB2 具体见表格 存储器Block2内部区域功能划分。 6.5. 寄存器映射 我们知道存储器本身没有地址给存储器分配地址的过程叫存储器映射那什么叫寄存器映射寄存器到底是什么 在存储器Block2这块区域设计的是片上外设它们以四个字节为一个单元共32bit每一个单元对应不同的功能 当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址然后通过C语言指针的操作方式来访问这些单元 如果每次都是通过这种地址的方式来访问不仅不好记忆还容易出错这时我们可以根据每个单元功能的不同以功能为名给这个内存单元取一个别名 这个别名就是我们经常说的寄存器这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。 比如我们找到GPIOB端口的输出数据寄存器ODR的地址是0x40010C0C至于这个地址如何找到可以先跳过后面我们会有详细的讲解 ODR寄存器是32bit低16bit有效对应着16个外部IO写0/1对应的的IO则输出低/高电平。现在我们通过C语言指针的操作方式 让GPIOB的16个IO都输出高电平具体见代码清单:寄存器-1。 代码清单:寄存器-1 通过绝对地址访问内存单元 1 2 // GPIOB 端口全部输出 高电平 *(unsigned int*)(0x4001 0C0C) 0xFFFF;0x4001 0C0C在我们看来是GPIOB端口ODR的地址但是在编译器看来这只是一个普通的变量是一个立即数 要想让编译器也认为是指针我们得进行强制类型转换把它转换成指针 即(unsigned int *)0x4001 0C0C然后再对这个指针进行 * 操作。 刚刚我们说了通过绝对地址访问内存单元不好记忆且容易出错我们可以通过寄存器的方式来操作具体见代码清单:寄存器-2。 代码清单:寄存器-2 通过寄存器别名方式访问内存单元 1 2 3 // GPIOB 端口全部输出 高电平 #define GPIOB_ODR (unsigned int*)(GPIOB_BASE0x0C) * GPIOB_ODR 0xFF;为了方便操作我们干脆把指针操作“*”也定义到寄存器别名里面具体见代码清单:寄存器-3。 代码清单:寄存器-3 通过寄存器别名访问内存单元 1 2 3 // GPIOB 端口全部输出 高电平 #define GPIOB_ODR *(unsigned int*)(GPIOB_BASE0x0C) GPIOB_ODR 0xFF;6.5.1. STM32的外设地址映射 片上外设区分为三条总线根据外设速度的不同不同总线挂载着不同的外设APB1挂载低速外设APB2和AHB挂载高速外设。 相应总线的最低地址我们称为该总线的基地址总线基地址也是挂载在该总线上的首个外设的地址。其中APB1总线的地址最低片上外设从这里开始也叫外设基地址。 6.5.1.1. 总线基地址 表格 总线基地址 的“相对外设基地址偏移”即该总线地址与“片上外设”基地址0x4000 0000的差值。关于地址的偏移我们后面还会讲到。 6.5.1.2. 外设基地址 总线上挂载着各种外设这些外设也有自己的地址范围特定外设的首个地址称为“XX外设基地址”也叫XX外设的边界地址。 具体有关STM32F10xx外设的边界地址请参考《STM32F10xx参考手册》的2.3小节的存储器映射的表1STM32F10xx 寄存器边界地址。 这里面我们以GPIO这个外设来讲解外设的基地址GPIO属于高速的外设 挂载到APB2总线上具体见表格 外设GPIO基地址。 6.5.1.3. 外设寄存器 在XX外设的地址范围内分布着的就是该外设的寄存器。以GPIO外设为例GPIO是通用输入输出端口的简称 简单来说就是STM32可控制的引脚基本功能是控制引脚输出高电平或者低电平。最简单的应用就是把GPIO的引脚连接到LED灯的阴极 LED灯的阳极接电源然后通过STM32控制该引脚的电平从而实现控制LED灯的亮灭。 GPIO有很多个寄存器每一个都有特定的功能。每个寄存器为32bit占四个字节在该外设的基地址上按照顺序排列 寄存器的位置都以相对该外设基地址的偏移地址来描述。这里我们以GPIOB端口为例来说明GPIO都有哪些寄存器 具体见表格 GPIOB端口的寄存器地址列表。 有关外设的寄存器说明可参考《STM32F10xx参考手册》中具体章节的寄存器描述部分在编程的时候我们需要反复的查阅外设的寄存器说明。 这里我们以“GPIO端口置位/复位寄存器”为例教大家如何理解寄存器的说明 具体见图 GPIO端口置位_复位寄存器说明。 ①名称 寄存器说明中首先列出了该寄存器中的名称“(GPIOx_BSRR)(xA…E)”这段的意思是该寄存器名为“GPIOx_BSRR”其中的“x”可以为A-E 也就是说这个寄存器说明适用于GPIOA、GPIOB至GPIOE这些GPIO端口都有这样的一个寄存器。 ②偏移地址 偏移地址是指本寄存器相对于这个外设的基地址的偏移。本寄存器的偏移地址是0x10 从参考手册中我们可以查到GPIOA外设的基地址为0x4001 0800 我们就可以算出GPIOA的这个GPIOA_BSRR寄存器的地址为0x4001 08000x10同理 由于GPIOB的外设基地址为0x4001 0C00 可算出GPIOB_BSRR寄存器的地址为0x4001 0C000x10。其他GPIO端口以此类推即可。 ③寄存器位表 紧接着的是本寄存器的位表表中列出它的0-31位的名称及权限。表上方的数字为位编号中间为位名称最下方为读写权限其中w表示只写 r表示只读rw表示可读写。本寄存器中的位权限都是w所以只能写如果读本寄存器是无法保证读取到它真正内容的。而有的寄存器位只读 一般是用于表示STM32外设的某种工作状态的由STM32硬件自动更改程序通过读取那些寄存器位来判断外设的工作状态。 ④位功能说明 位功能是寄存器说明中最重要的部分它详细介绍了寄存器每一个位的功能。例如本寄存器中有两种寄存器位分别为BRy及BSy 其中的y数值可以是0-15这里的0-15表示端口的引脚号如BR0、BS0用于控制GPIOx的第0个引脚若x表示GPIOA那就是控制GPIOA的第0引脚 而BR1、BS1就是控制GPIOA第1个引脚。 其中BRy引脚的说明是“0不会对相应的ODRx位执行任何操作1对相应ODRx位进行复位”。这里的“复位”是将该位设置为0的意思 而“置位”表示将该位设置为1说明中的ODRx是另一个寄存器的寄存器位我们只需要知道ODRx位为1的时候对应的引脚x输出高电平 为0的时候对应的引脚输出低电平即可(感兴趣的读者可以查询该寄存器GPIOx_ODR的说明了解)。所以如果对BR0写入“1”的话 那么GPIOx的第0个引脚就会输出“低电平”但是对BR0写入“0”的话却不会影响ODR0位所以引脚电平不会改变。要想该引脚输出“高电平” 就需要对“BS0”位写入“1”寄存器位BSy与BRy是相反的操作。 6.5.2. C语言对寄存器的封装 以上所有的关于存储器映射的内容最终都是为大家更好地理解如何用C语言控制读写外设寄存器做准备此处是本章的重点内容。 6.5.2.1. 封装总线和外设基地址 在编程上为了方便理解和记忆我们把总线基地址和外设基地址都以相应的宏定义起来总线或者外设都以他们的名字作为宏名 具体见 代码清单:寄存器-4。 代码清单:寄存器-4 总线和外设基址宏定义 123456789 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 /* 外设基地址 */ #define PERIPH_BASE ((unsigned int)0x40000000)/* 总线基地址 */ #define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE 0x00010000) #define AHBPERIPH_BASE (PERIPH_BASE 0x00020000)/* GPIO外设基地址 */ #define GPIOA_BASE (APB2PERIPH_BASE 0x0800) #define GPIOB_BASE (APB2PERIPH_BASE 0x0C00) #define GPIOC_BASE (APB2PERIPH_BASE 0x1000) #define GPIOD_BASE (APB2PERIPH_BASE 0x1400) #define GPIOE_BASE (APB2PERIPH_BASE 0x1800) #define GPIOF_BASE (APB2PERIPH_BASE 0x1C00) #define GPIOG_BASE (APB2PERIPH_BASE 0x2000)/* 寄存器基地址以GPIOB为例 */ #define GPIOB_CRL (GPIOB_BASE0x00) #define GPIOB_CRH (GPIOB_BASE0x04) #define GPIOB_IDR (GPIOB_BASE0x08) #define GPIOB_ODR (GPIOB_BASE0x0C) #define GPIOB_BSRR (GPIOB_BASE0x10) #define GPIOB_BRR (GPIOB_BASE0x14) #define GPIOB_LCKR (GPIOB_BASE0x18)代码清单:寄存器-4 首先定义了 “片上外设”基地址PERIPH_BASE接着在PERIPH_BASE上加入各个总线的地址偏移 得到APB1、APB2总线的地址APB1PERIPH_BASE、APB2PERIPH_BASE在其之上加入外设地址的偏移得到GPIOA-G的外设地址 最后在外设地址上加入各寄存器的地址偏移得到特定寄存器的地址。一旦有了具体地址就可以用指针读写 具体见 代码清单:寄存器-5。 代码清单:寄存器-5 使用指针控制BSRR寄存器 1 2 3 4 5 6 7 8 9 /* 控制GPIOB 引脚0输出低电平(BSRR寄存器的BR0置1) */ *(unsigned int *)GPIOB_BSRR (0x01(160));/* 控制GPIOB 引脚0输出高电平(BSRR寄存器的BS0置1) */ *(unsigned int *)GPIOB_BSRR 0x010;unsigned int temp; /* 读取GPIOB 端口所有引脚的电平(读IDR寄存器) */ temp *(unsigned int *)GPIOB_IDR;该代码使用 (unsigned int *) 把GPIOB_BSRR宏的数值强制转换成了地址然后再用“*”号做取指针操作对该地址的赋值 从而实现了写寄存器的功能。同样读寄存器也是用取指针操作把寄存器中的数据取到变量里从而获取STM32外设的状态。 6.5.2.2. 封装寄存器列表 用上面的方法去定义地址还是稍显繁琐例如GPIOA-GPIOE都各有一组功能相同的寄存器如GPIOA_ODR/GPIOB_ODR/GPIOC_ODR等等 它们只是地址不一样但却要为每个寄存器都定义它的地址。为了更方便地访问寄存器我们引入C语言中的结构体语法对寄存器进行封装 具体见 代码清单:寄存器-6。 代码清单:寄存器-6 使用结构体对GPIO寄存器组的封装 123456789 10 11 12 13 typedef unsigned int uint32_t; /*无符号32位变量*/ typedef unsigned short int uint16_t; /*无符号16位变量*//* GPIO寄存器列表 */ typedef struct {uint32_t CRL; /*GPIO端口配置低寄存器 地址偏移: 0x00 */uint32_t CRH; /*GPIO端口配置高寄存器 地址偏移: 0x04 */uint32_t IDR; /*GPIO数据输入寄存器 地址偏移: 0x08 */uint32_t ODR; /*GPIO数据输出寄存器 地址偏移: 0x0C */uint32_t BSRR; /*GPIO位设置/清除寄存器 地址偏移: 0x10 */uint32_t BRR; /*GPIO端口位清除寄存器 地址偏移: 0x14 */uint16_t LCKR; /*GPIO端口配置锁定寄存器 地址偏移: 0x18 */ } GPIO_TypeDef;这段代码用typedef 关键字声明了名为GPIO_TypeDef的结构体类型结构体内有7个 成员变量变量名正好对应寄存器的名字。 C语言的语法规定结构体内变量的存储空间是连续的其中32位的变量占用4个字节16位的变量占用2个字节 具体见图 GPIO_TypeDef结构体成员的地址偏移。 也就是说我们定义的这个GPIO_TypeDef 假如这个结构体的首地址为0x4001 0C00这也是第一个成员变量CRL的地址 那么结构体中第二个成员变量CRH的地址即为0x4001 0C00 0x04 加上的这个0x04正是代表CRL所占用的4个字节地址的偏移量 其它成员变量相对于结构体首地址的偏移在上述代码右侧注释已给。 这样的地址偏移与STM32 GPIO外设定义的寄存器地址偏移一一对应只要给结构体设置好首地址就能把结构体内成员的地址确定下来 然后就能以结构体的形式访问寄存器具体见 代码清单:寄存器-7。 代码清单:寄存器-7 通过结构体指针访问寄存器 1 2 3 4 5 6 7 8 GPIO_TypeDef * GPIOx; //定义一个GPIO_TypeDef型结构体指针GPIOx GPIOx GPIOB_BASE; //把指针地址设置为宏GPIOB_BASE地址 GPIOx-IDR 0xFFFF; GPIOx-ODR 0xFFFF;uint32_t temp; temp GPIOx-IDR; //读取GPIOB_IDR寄存器的值到变量temp中这段代码先用GPIO_TypeDef类型定义一个结构体指针GPIOx并让指针指向地址GPIOB_BASE(0x4001 0C00)使用地址确定下来 然后根据C语言访问结构体的语法用GPIOx-ODR及GPIOx-IDR等方式读写寄存器。 最后我们更进一步直接使用宏定义好GPIO_TypeDef类型的指针而且指针指向各个GPIO端口的首地址 使用时我们直接用该宏访问寄存器即可具体 代码清单:寄存器-8。 代码清单:寄存器-8 定义好GPIO端口首地址址针 123456789 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 /*使用GPIO_TypeDef把地址强制转换成指针*/ #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) #define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)/*使用定义好的宏直接访问*/ /*访问GPIOB端口的寄存器*/ GPIOB-BSRR 0xFFFF; //通过指针访问并修改GPIOB_BSRR寄存器 GPIOB-CRL 0xFFFF; //修改GPIOB_CRL寄存器 GPIOB-ODR 0xFFFF; //修改GPIOB_ODR寄存器uint32_t temp; temp GPIOB-IDR; //读取GPIOB_IDR寄存器的值到变量temp中/*访问GPIOA端口的寄存器*/ GPIOA-BSRR 0xFFFF; GPIOA-CRL 0xFFFF; GPIOA-ODR 0xFFFF;uint32_t temp; temp GPIOA-IDR; //读取GPIOA_IDR寄存器的值到变量temp中这里我们仅是以GPIO这个外设为例给大家讲解了C语言对寄存器的封装。以此类推其他外设也同样可以用这种方法来封装。好消息是 这部分工作都由固件库帮我们完成了这里我们只是分析了下这个封装的过程让大家知其然也只其所以然。 6.5.3. 修改寄存器的位操作方法 使用C语言对寄存器赋值时我们常常要求只修改该寄存器的某几位的值且其它的寄存器位不变这个时候我们就需要用到C语言的位操作方法了。 6.5.3.1. 把变量的某位清零 此处我们以变量a代表寄存器并假设寄存器中本来已有数值此时我们需要把变量a的某一位清零且其它位不变 方法见 代码清单:寄存器-9。 代码清单:寄存器-9 对某位清零 123456789 10 11 12 13 //定义一个变量a 1001 1111 b (二进制数) unsigned char a 0x9f;//对bit2 清零a ~(12);//括号中的1左移两位(12)得二进制数0000 0100 b //按位取反~(12)得1111 1011 b //假如a中原来的值为二进制数 a 1001 1111 b //所得的数与a作”位与”运算a (1001 1111 b)(1111 1011 b), //经过运算后a的值 a1001 1011 b // a的bit2 位被清零而其它位不变。6.5.3.2. 把变量的某几个连续位清零 由于寄存器中有时会有连续几个寄存器位用于控制某个功能现假设我们需要把寄存器的某几个连续位清零 且其它位不变方法见 代码清单:寄存器-10。 代码清单:寄存器-10 对某几个连续位清零 123456789 10 11 12 13 14 15 16 17 18 19 20 //若把a中的二进制位分成2个一组 //即bit0、bit1为第0组bit2、bit3为第1组 // bit4、bit5为第2组bit6、bit7为第3组 //要对第1组的bit2、bit3清零a ~(32*1);//括号中的3左移两位(32*1)得二进制数0000 1100 b //按位取反~(32*1)得1111 0011 b //假如a中原来的值为二进制数 a 1001 1111 b //所得的数与a作”位与”运算a (1001 1111 b)(1111 0011 b), //经过运算后a的值 a1001 0011 b // a的第1组的bit2、bit3被清零而其它位不变。//上述(~(32*1))中的(1)即为组编号;如清零第3组bit6、bit7此处应为3 //括号中的(2)为每组的位数每组有2个二进制位;若分成4个一组此处即为4 //括号中的(3)是组内所有位都为1时的值;若分成4个一组此处即为二进制数“1111 b”//例如对第2组bit4、bit5清零 a ~(32*2);6.5.3.3. 对变量的某几位进行赋值。 寄存器位经过上面的清零操作后接下来就可以方便地对某几位写入所需要的数值了且其它位不变 方法见 代码清单:寄存器-11 这时候写入的数值一般就是需要设置寄存器的位参数。 代码清单:寄存器-11 对某几位进行赋值 1 2 3 4 5 //a 1000 0011 b //此时对清零后的第2组bit4、bit5设置成二进制数“01 b ”a | (12*2); //a 1001 0011 b成功设置了第2组的值其它组不变6.5.3.4. 对变量的某位取反 某些情况下我们需要对寄存器的某个位进行取反操作即 1变0 0变1这可以直接用如下操作其它位不变 见 代码清单:寄存器-12。 代码清单:寄存器-12 对某位进行取反操作 1 2 3 4 5 //a 1001 0011 b //把bit6取反其它位不变a ^(16); //a 1101 0011 b关于修改寄存器位的这些操作在下一章中有应用实例代码可配合阅读。 8.1. GPIO简介 GPIO是通用输入输出端口的简称简单来说就是STM32可控制的引脚STM32芯片的GPIO引脚与外部设备连接起来从而实现与外部通讯、 控制以及数据采集的功能。STM32芯片的GPIO被分成很多组每组有16个引脚如型号为STM32F103VET6型号的芯片有GPIOA、GPIOB、 GPIOC至GPIOE共5组GPIO芯片一共100个引脚其中GPIO就占了一大部分所有的GPIO引脚都有基本的输入输出功能。 最基本的输出功能是由STM32控制引脚输出高、低电平实现开关控制如把GPIO引脚接入到LED灯那就可以控制LED灯的亮灭 引脚接入到继电器或三极管那就可以通过继电器或三极管控制外部大功率电路的通断。 最基本的输入功能是检测外部输入电平如把GPIO引脚连接到按键通过电平高低区分按键是否被按下。 8.2. GPIO框图剖析 通过GPIO硬件结构框图就可以从整体上深入了解GPIO外设及它的各种应用模式。该图从最右端看起最右端就是代表STM32芯片引出的GPIO引脚其余部件都位于芯片内部。 8.2.1. 基本结构分析 下面我们按图中的编号对GPIO端口的结构部件进行说明。 8.2.1.1. 保护二极管及上、下拉电阻 引脚的两个保护二级管可以防止引脚外部过高或过低的电压输入当引脚电压高于VDD时 上方的二极管导通当引脚电压低于VSS时下方的二极管导通防止不正常电压引入芯片导致芯片烧毁。 尽管有这样的保护并不意味着STM32的引脚能直接外接大功率驱动器件如直接驱动电机强制驱动要么电机不转要么导致芯片烧坏必须要加大功率及隔离电路驱动。 8.2.1.2. P-MOS管和N-MOS管 GPIO引脚线路经过两个保护二极管后向上流向“输入模式”结构向下流向“输出模式”结构。先看输出模式部分线路经过一个由P-MOS和N-MOS管组成的单元电路。 这个结构使GPIO具有了“推挽输出”和“开漏输出”两种模式。 所谓的推挽输出模式是根据这两个MOS管的工作方式来命名的。在该结构中输入高电平时经过反向后上方的P-MOS导通下方的N-MOS关闭 对外输出高电平而在该结构中输入低电平时经过反向后N-MOS管导通P-MOS关闭对外输出低电平。当引脚高低电平切换时两个管子轮流导通 P管负责灌电流N管负责拉电流使其负载能力和开关速度都比普通的方式有很大的提高。推挽输出的低电平为0伏高电平为3.3伏 具体参考图 推挽等效电路 它是推挽输出模式时的等效电路。 而在开漏输出模式时上方的P-MOS管完全不工作。如果我们控制输出为0低电平则P-MOS管关闭N-MOS管导通使输出接地 若控制输出为1 (它无法直接输出高电平)时则P-MOS管和N-MOS管都关闭所以引脚既不输出高电平也不输出低电平为高阻态。 为正常使用时必须外部接上拉电阻参考图 开漏电路 中等效电路。它具有“线与”特性也就是说若有很多个开漏模式引脚连接到一起时 只有当所有引脚都输出高阻态才由上拉电阻提供高电平此高电平的电压为外部上拉电阻所接的电源的电压。若其中一个引脚为低电平 那线路就相当于短路接地使得整条线路都为低电平0伏。 推挽输出模式一般应用在输出电平为0和3.3伏而且需要高速切换开关状态的场合。在STM32的应用中除了必须用开漏模式的场合我们都习惯使用推挽输出模式。 开漏输出一般应用在I2C、SMBUS通讯等需要“线与”功能的总线电路中。除此之外还用在电平不匹配的场合如需要输出5伏的高电平 就可以在外部接一个上拉电阻上拉电源为5伏并且把GPIO设置为开漏模式当输出高阻态时由上拉电阻和电源向外输出5伏的电平 具体见图 STM32_IO对外输出5V电平。 8.2.1.3. 输出数据寄存器 前面提到的双MOS管结构电路的输入信号是由GPIO“输出数据寄存器GPIOx_ODR”提供的因此我们通过修改输出数据寄存器的值就可以修改GPIO引脚的输出电平。 而“置位/复位寄存器GPIOx_BSRR”可以通过修改输出数据寄存器的值从而影响电路的输出。 8.2.1.4. 复用功能输出 “复用功能输出”中的“复用”是指STM32的其它片上外设对GPIO引脚进行控制此时GPIO引脚用作该外设功能的一部分算是第二用途。 从其它外设引出来的“复用功能输出信号”与GPIO本身的数据据寄存器都连接到双MOS管结构的输入中通过图中的梯形结构作为开关切换选择。 例如我们使用USART串口通讯时需要用到某个GPIO引脚作为通讯发送引脚这个时候就可以把该GPIO引脚配置成USART串口复用功能由串口外设控制该引脚发送数据。 // GPIOB 16个IO全部输出 0XFFFF GPIOB-ODR 0XFFFF;8.2.1.5. 输入数据寄存器 看GPIO结构框图的上半部分GPIO引脚经过内部的上、下拉电阻可以配置成上/下拉输入然后再连接到施密特触发器信号经过触发器后 模拟信号转化为0、1的数字信号然后存储在“输入数据寄存器GPIOx_IDR”中通过读取该寄存器就可以了解GPIO引脚的电平状态。 // 读取GPIOB端口的16位数据值 uint16_t temp; temp GPIOB-IDR;8.2.1.6. 复用功能输入 与“复用功能输出”模式类似在“复用功能输入模式”时GPIO引脚的信号传输到STM32其它片上外设由该外设读取引脚状态。 同样如我们使用USART串口通讯时需要用到某个GPIO引脚作为通讯接收引脚这个时候就可以把该GPIO引脚配置成USART串口复用功能使USART可以通过该通讯引脚的接收远端数据。 8.2.1.7. 模拟输入输出 当GPIO引脚用于ADC采集电压的输入通道时用作“模拟输入”功能此时信号是不经过施密特触发器的因为经过施密特触发器后信号只有0、1两种状态 所以ADC外设要采集到原始的模拟信号信号源输入必须在施密特触发器之前。类似地当GPIO引脚用于DAC作为模拟电压输出通道时此时作为“模拟输出”功能 DAC的模拟信号输出就不经过双MOS管结构模拟信号直接输出到引脚。 8.2.2. GPIO工作模式 总结一下由GPIO的结构决定了GPIO可以配置成以下模式 代码清单:点亮LED-1 GPIO 8种工作模式 123456789 10 11 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总共有8种细分的工作模式稍加整理可以大致归类为以下三类 8.2.2.1. 输入模式(模拟/浮空/上拉/下拉) 在输入模式时施密特触发器打开输出被禁止可通过输入数据寄存器GPIOx_IDR读取I/O状态。其中输入模式可设置为上拉、 下拉、浮空和模拟输入四种。上拉和下拉输入很好理解默认的电平由上拉或者下拉决定。浮空输入的电平是不确定的完全由外部的输入决定 一般接按键的时候用的是这个模式。模拟输入则用于ADC采集。 8.2.2.2. 输出模式(推挽/开漏) 在输出模式中推挽模式时双MOS管以轮流方式工作输出数据寄存器GPIOx_ODR可控制I/O输出高低电平。开漏模式时只有N-MOS管工作 输出数据寄存器可控制I/O输出高阻态或低电平。输出速度可配置有2MHz10MHz50MHz的选项。此处的输出速度即I/O支持的高低电平状态最高切换频率 支持的频率越高功耗越大如果功耗要求不严格把速度设置成最大即可。 在输出模式时施密特触发器是打开的即输入可用通过输入数据寄存器GPIOx_IDR可读取I/O的实际状态。 8.2.2.3. 复用功能(推挽/开漏) 复用功能模式中输出使能输出速度可配置可工作在开漏及推挽模式但是输出信号源于其它外设输出数据寄存器GPIOx_ODR无效 输入可用通过输入数据寄存器可获取I/O实际状态但一般直接用外设的寄存器来获取该数据信号。 通过对GPIO寄存器写入不同的参数就可以改变GPIO的工作模式再强调一下 要了解具体寄存器时一定要查阅《STM32F10X-中文参考手册》中对应外设的寄存器说明。在GPIO外设中 控制端口高低控制寄存器CRH和CRL可以配置每个GPIO的工作模式和工作的速度每4个位控制一个IO CRH控制端口的高八位CRL控制端口的低8位具体的看CRH和CRL的寄存器描述。 8.3. 实验使用寄存器点亮LED灯 本小节中我们以实例讲解如何通过控制寄存器来点亮LED灯。此处侧重于讲解原理请直接用KEIL5软件打开我们提供的实验例程配合阅读 先了解原理学习完本小节后再尝试自己建立一个同样的工程。本节配套例程名称为“GPIO输出—使用寄存器点亮LED灯” 在工程目录下找到后缀为“.uvprojx”的文件用KEIL5打开即可。 自己尝试新建工程时请对照查阅《新建工程— 寄存器版》章节。若没有安装KEIL5软件请参考《如何安装KEIL5》章节。 打开该工程见图 工程文件结构 可看到一共有三个文件分别startup_stm32f10x_hd.s 、 stm32f10x.h 以及main.c下面我们对这三个文件进行讲解。 8.3.1. 硬件连接 在本教程中STM32芯片与LED灯的连接见图 LED灯电路连接图  两个LED灯的阳极引出连接到3.3V电源阴极各经过1个限流电阻引入至STM32的2个GPIO引脚中 所以我们只要控制这两个引脚输出高低电平 即可控制其所连接LED灯的亮灭。如果您的实验板STM32连接到LED灯的引脚或极性不一样 只需要修改程序到对应的GPIO引脚即可工作原理都是一样的。 我们的目标是把GPIO的引脚设置成推挽输出模式并且默认下拉输出低电平这样就能让LED灯亮起来了。 8.3.2. 启动文件 启动文件在这里只是简要的介绍下关于这个文件的详解请参考后面的《启动文件详解》章节。 名为“startup_stm32f10x_hd.s”的文件它里边使用汇编语言写好了基本程序当STM32芯片上电启动的时候首先会执行这里的汇编程序 从而建立起C语言的运行环境所以我们把这个文件称为启动文件。该文件使用的汇编指令是Cortex-M3内核支持的指令 可参考《Cortex-M3权威指南》中指令集章节。 startup_stm32f10x_hd.s文件由官方提供一般有需要也是在官方的基础上修改不会自己完全重写。该文件从 ST 固件库里面找到 找到该文件后把启动文件添加到工程里面即可。不同型号的芯片以及不同编译环境下使用的汇编文件是不一样的但功能相同。 对于启动文件这部分我们主要总结它的功能不详解讲解里面的代码其功能如下 初始化堆栈指针SP; 初始化程序计数器指针PC; 设置堆、栈的大小; 初始化中断向量表; 配置外部SRAM作为数据存储器这个由用户配置一般的开发板可没有外部SRAM; 调用SystemIni() 函数配置STM32的系统时钟。 设置C库的分支入口“__main”最终用来调用main函数; 先去除繁枝细节挑重点的讲主要理解最后两点在启动文件中有一段复位后立即执行的程序代码见 代码清单:点亮LED-2。 在实际工程中阅读时可使用编辑器的搜索(CtrlF)功能查找这段代码在文件中的位置搜索Reset_Handler即可找到。 代码清单:点亮LED-2复位后执行的程序 123456789 10 11 ;Reset handler Reset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT SystemInitIMPORT __mainLDR R0, SystemInitBLX R0LDR R0, __mainBX R0ENDP开头的是程序注释在汇编里面注释用的是“;”相当于 C 语言的“//”注释符 第二行是定义了一个子程序Reset_Handler。PROC 是子程序定义伪指令。这里就相当于C语言里定义了一个函数函数名为Reset_Handler。 第三行 EXPORT 表示 Reset_Handler 这个子程序可供其他模块调用。相当于C语言的函数声明。关键字[WEAK] 表示弱定义 如果编译器发现在别处定义了同名的函数则在链接时用别处的地址进行链接如果其它地方没有定义编译器也不报错以此处地址进行链接。 第四行和第五行 IMPORT 说明 SystemInit 和__main 这两个标号在其他文件在链接的时候需要到其他文件去寻找。 相当于C语言中从其它文件引入函数声明。以便下面对外部函数进行调用。 SystemInit 需要由我们自己实现即我们要编写一个具有该名称的函数用来初始化 STM32 芯片的时钟一般包括初始化AHB、APB等各总线的时钟 需要经过一系列的配置STM32才能达到稳定运行的状态。其实这个函数在固件库里面有提供官方已经为我们写好。 __main 其实不是我们定义的(不要与C语言中的main函数混淆)这是一个C库函数当编译器编译时只要遇到这个标号就会定义这个函数 该函数的主要功能是负责初始化栈、堆配置系统环境并在函数的最后调用用户编写的 main 函数从此来到 C 的世界。 第六行把 SystemInit 的地址加载到寄存器 R0。 第七行程序跳转到 R0 中的地址执行程序即执行SystemInit函数的内容。 第八行把__main 的地址加载到寄存器 R0。 第九行程序跳转到 R0 中的地址执行程序即执行__main函数执行完毕之后就去到我们熟知的 C 世界进入main函数。 第十行表示子程序的结束。 总之看完这段代码后了解到如下内容即可我们需要在外部定义一个SystemInit函数设置STM32的时钟STM32上电后会执行SystemInit函数最后执行我们C语言中的main函数。 8.3.3. stm32f10x.h文件 看完启动文件那我们立即写SystemInit和main函数吧别着急定义好了SystemInit函数和main我们又能写什么内容连接LED灯的GPIO引脚 是要通过读写寄存器来控制的就这样空着手如何控制寄存器。我们知道寄存器映射就是给一个已经分配好地址的特殊的内存空间取的一个别名 这个特殊的内存空间就是寄存器它可以通过指针来操作。在编程之前我们要先实现寄存器映射有关寄存器映射的代码都统一写在stm32f10x.h文件中 见 代码清单:点亮LED-2。 代码清单:点亮LED-3 外设地址定义 123456789 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /*片上外设基地址 */ #define PERIPH_BASE ((unsigned int)0x40000000)/*总线基地址GPIO都挂载到APB2上 */ #define APB2PERIPH_BASE (PERIPH_BASE 0x10000) /* AHB总线基地址 */ #define AHBPERIPH_BASE (PERIPH_BASE 0x20000)/*GPIOB外设基地址*/ #define GPIOB_BASE (APB2PERIPH_BASE 0x0C00)/* GPIOB寄存器地址,强制转换成指针 */ #define GPIOB_CRL *(unsigned int*)(GPIOB_BASE0x00) #define GPIOB_CRH *(unsigned int*)(GPIOB_BASE0x04) #define GPIOB_IDR *(unsigned int*)(GPIOB_BASE0x08) #define GPIOB_ODR *(unsigned int*)(GPIOB_BASE0x0C) #define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE0x10) #define GPIOB_BRR *(unsigned int*)(GPIOB_BASE0x14) #define GPIOB_LCKR *(unsigned int*)(GPIOB_BASE0x18)/*RCC外设基地址*/ #define RCC_BASE (AHBPERIPH_BASE 0x1000) /*RCC的AHB1时钟使能寄存器地址,强制转换成指针*/ #define RCC_APB2ENR *(unsigned int*)(RCC_BASE0x18)GPIO外设的地址跟前面章节讲解的相同不过此处把寄存器的地址值都直接强制转换成了指针方便使用。 代码的最后两段是RCC外设寄存器的地址定义RCC外设是用来设置时钟的以后我们会详细分析本实验中只要了解到使用GPIO外设必须开启它的时钟即可。 8.3.4. main文件 现在就可以开始编写程序在main文件中先编写一个 main 函数里面什么都没有暂时为空。 int main (void) { }此时直接编译的话会出现如下错误 Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f10x.o) 错误提示SystemInit 没有定义。从分析启动文件时我们知道Reset_Handler 调用了该函数用来初始化SMT32系统时钟 为了简单起见我们在 main 文件里面定义一个 SystemInit空函数什么也不做为的是骗过编译器把这个错误去掉。 关于配置系统时钟我们在后面再写。当我们不配置系统时钟时STM32会把HSI当作系统时钟HSI8M 由芯片内部的振荡器提供。我们在main中添加如下函数 // 函数为空目的是为了骗过编译器不报错 void SystemInit(void) { }这时再编译就没有错了完美解决。还有一个方法就是在启动文件中把有关SystemInit 的代码注释掉也可以见 代码清单:点亮LED-4。 代码清单:点亮LED-4 注释掉启动文件中调用SystemInit的代码 123456789 10 11 ; Reset handler Reset_Handler PROCEXPORT Reset_Handler [WEAK];IMPORT SystemInitIMPORT __main;LDR R0, SystemInit;BLX R0LDR R0, __mainBX R0ENDP接下来在main函数中添加代码实现我们的点灯之旅。 8.3.4.1. GPIO模式 首先我们把连接到LED灯的GPIO引脚配置成输出模式即配置GPIO的端口配置低寄存器CRL见图 GPIO端口控制低寄存器CRL。 CRL中包含0-7号引脚每个引脚占用4个寄存器位。MODE位用来配置输出的速度CNF位用来配置各种输入输出模式。在这里我们把GPIO引脚配置为通用推挽输出 输出的速度为10M具体见 代码清单:点亮LED-5。 代码清单:点亮LED-5 配置输出模式 1 2 3 4 // 清空控制PC2的端口位 GPIOC_CRL ~( 0x0F (4*2)); // 配置PC2为通用推挽输出速度为10M GPIOC_CRL | (14*2);在代码中我们先把控制PC2的端口位清0然后再向它赋值“0001 b”从而使GPIOC2引脚设置成输出模式速度为10M。 代码中使用了 ~、| 这种操作方法是为了避免影响到寄存器中的其它位因为寄存器不能按位读写假如我们直接给CRL寄存器赋值 GPIOC_CRL 0x0000 0100;这时CRL的的低8~11位被设置成“0001”输出模式但其它GPIO引脚就有意见了 因为其它引脚的MODER位都已被设置成输入模式(即全部被清0)。所以为了在配置寄存器某个位的时候不影响其它位 推荐使用“~”和“|”这两种操作方式其中“~”用于清0“|”用于置1。 8.3.4.2. 控制引脚输出电平 在输出模式时对端口位设置/清除寄存器BSRR寄存器、端口位清除寄存器BRR和ODR寄存器写入参数即可控制引脚的电平状态 其中操作BSRR和BRR最终影响的都是ODR寄存器然后再通过ODR寄存器的输出来控制GPIO。为了一步到位我们在这里直接操作ODR寄存器来控制GPIO的电平。 具体见 代码清单:点亮LED-6。 代码清单:点亮LED-6 控制引脚输出电平 1 2 // PC2输出低电平 GPIOC_ODR ~(12);8.3.4.3. 开启外设时钟 设置完GPIO的引脚控制电平输出以为现在总算可以点亮 LED 了吧其实还差最后一步。由于STM32的 外设很多为了降低功耗 每个外设都对应着一个时钟在芯片刚上电的时候这些时钟都是被关闭的如果想要外设工作必须把相应的时钟打开。 STM32 的所有外设的时钟由一个专门的外设来管理叫 RCCreset and clockcontrol RCC 在《 STM32 中文参考手册》的第六章。 关于RCC外设中的时钟部分我们在后面的章节《RCC—使用HSE/HIS配置》中有详细的讲解这里我们暂时先了解下。 所有的 GPIO都挂载到 APB2 总线上具体的时钟由APB2外设时钟使能寄存器(RCC_ APB2ENR)来控制 具体见 代码清单:点亮LED-7。 代码清单:点亮LED-7 开启端口时钟 1 2 // 开启 GPIOC 端口 时钟 RCC_APB2ENR | (14);8.3.4.4. 水到渠成 开启时钟配置引脚模式控制电平经过这三步我们总算可以控制一个 LED了。现在我们完整组织下用 STM32 控制一个 LED 的代码 见 代码清单:点亮LED-8。 代码清单:点亮LED-8 main文件中控制LED灯的代码 123456789 10 11 12 13 14 15 16 int main(void) {// 开启GPIOC 端口时钟RCC_APB2ENR | (14);//清空控制PC2的端口位GPIOC_CRL ~( 0x0F (4*2));// 配置PC2为通用推挽输出速度为10MGPIOC_CRL | (14*2);// PC2 输出 低电平GPIOC_ODR ~(12);while (1);}在本章节中要求完全理解stm32f10x.h文件及main文件的内容(RCC相关的除外)。 8.3.5. 下载验证 把编译好的程序下载到开发板并复位可看到板子上的LED灯被点亮。为了验证是否掌握了本节课的知识点 可以尝试把另外一个LED也点亮让两个灯一起实现流水灯的效果。
http://www.zqtcl.cn/news/983206/

相关文章:

  • 网站 空间 租用wordpress搬家需要修改
  • 做网站推广怎么找客户网站换空间 seo
  • ipad网站开发seo哪家强
  • 昆明网站建设猫咪科技公司资料模板
  • 网站系统开发做网站需要填什么
  • 网站的数据库丢失建筑素材网
  • 个人网站做短视频pathon能做网站开发吗
  • 客户网站制作管理系统网站程序 wap pc 同步
  • 天津手动网站建设调试百度医院网站建设
  • ppt网站源码今天哈尔滨最新通告
  • asp网站乱码广州制作网页设计
  • 调用别人网站的数据库如何开网店卖自己的东西
  • 个人网站做影视网站开发学什么专业
  • 企业名称注册查询官网入口免费seo网站推广
  • 浙江门户网站建设公司个体工商户查询
  • 做网站的注意点赛事竞猜网站开发
  • 现在流行用什么语言做网站ppt设计教程网
  • 高端网站哪种好培训机构不退钱最怕什么举报
  • 青岛个人建站模板wordpress没有链接
  • 网上学习网站有哪些厦门城乡建设局网站
  • 怎样创建网站快捷方式个人制作一个网站的费用
  • 恒信在线做彩票的是什么样的网站软件开发流程管理
  • 网站服务器地址在哪里看艺术学校网站模板
  • 郑州中心站网站建设价格标准新闻
  • 电子商务网站管理互联网营销师主要做什么
  • 门户网站指的是什么凯里网络公司建设网站
  • 网站接入服务商查询0建设营销型网站步骤
  • 长沙如何做百度的网站小型网站建设实训教程
  • 昆明网络公司网站网站建设经费请示
  • 手机端网站欣赏wordpress 文章rss