什么是GPIO?(详细介绍)
以STM32F4系列的单片机做例子
一.引入
单片机最小系统的组成:
芯片 + 供电电路 + 复位电路 + 时钟(晶振)电路
一个完成的系统的组成
最小系统 + 项目所需要的其他硬件(外设)
芯片:
整个系统的核心 相当于人类的大脑 会提供引脚与外部电路相连接
引脚(俗称 官方称呼“GPIO”)
二. GPIO
GPIO是什么?
General Purpose Input Output 通用功能输出输出
GPIO就是从芯片内部引出来一根功能复用的口线(电线)
功能复用是指:GPIO的引脚可以由CPU配置成不同的功能
比如:输入功能 输出功能 模拟功能 复用功能等等
分析GPIO内部结构图如picture/STM32F4XX_GPIO内部结构.PNG
通过图我们可以得知 每个GPIO可以独立地被配置成不同的功能。
GPIO配置功能如下:
(1)输入功能
CPU可以通过该GPIO的来获取外部电路输入的一个电平状态
输入功能又可以分为几种模式:
a.带上拉的输入(input pull-up)
默认接一个上拉电阻
此时就算IO引脚没有外部输入信号时 CPU也能读到一个高电平
只有在外部电路输入低电平的时候 CPU读取到的才是低电平
b.带下拉的输入
默认接一个下拉电阻
此时就算IO引脚没有外部输入信号时 CPU也能读到一个低电平
只有在外部电路输入高电平的时候 CPU读取到的才是高电平
c.输入悬空
既不接上拉电阻 也不接下拉电阻
这种情况下 IO引脚的电平状态完全由外部输入所绝对 此时CPU可以通过读取数据的
操作来获取外部电路的工作状态
d.模拟输入
该引脚被设置为模拟输入的时候 能够获取到模拟信号
通过ADC转换为数字量
(2)输出功能
CPU可以通过该GPIO口往外部输出一个电平状态(相当于可以控制外部电路工作)
输出功能也可以分为以下两种模式
a.输出推挽 (PP: push-pull)
CPU往外写高电平(1)时,此时引脚输出一个高电平
CPU往外写低电平(0)时,此时引脚输出一个低电平
b.输出开漏 (OD: open drain)
不输出电压
CPU往外写低电平(0)时 此时引脚接VSS(GND)相当于接地
CPU外外写高电平(1)时 此时引脚的电平状态由上下拉电阻决定
(3)复用功能
复用功能是指GPIO口用作其他的外设的功能口线
比如:
I2C USART SPI等等
每个GPIO口都可以被配置成多达16中复用功能
具体哪个引脚可以被复用成哪种功能 需要看原理图
STM32F4xx共有144个GPIO引脚
分为九组 记为GPIOA , GPIOB ... GPIOI
简写PA PB ... PI
每组有16根引脚 编号从0~15
也就是说:
比如GPIOA这一组就有
GPIOA0 PA0
GPIOA1 PA1
GPIOA2 PA2
...
GPIOA15 PA15
而这些GPIO的功能 都有独立的寄存器组(不同的GPIO硬件控制器)来配置他们
也就是说我们如果要使用比如GPIO口的输入功能的话 我们首先需要把对应寄存器组配置好。
那么如果我们要去配置(访问)寄存器的话 就必须知道寄存器的地址
每组GPIO的地址分布如下: 参考:第 192 页的第 7.4.11 节:GPIO 寄存器映射
边界地址 外设 总线:
0x4002 2000 - 0x4002 23FF GPIOI
0x4002 1C00 - 0x4002 1FFF GPIOH
0x4002 1800 - 0x4002 1BFF GPIOG
0x4002 1400 - 0x4002 17FF GPIOF
0x4002 1000 - 0x4002 13FF GPIOE AHB1
0x4002 0C00 - 0x4002 0FFF GPIOD
0x4002 0800 - 0x4002 0BFF GPIOC
0x4002 0400 - 0x4002 07FF GPIOB
0x4002 0000 - 0x4002 03FF GPIOA
边界地址:指对应的寄存器组的起始地址(基址)和结束地址
外设: 该寄存器组对应的硬件控制器
总线: 该硬件控制器所处的系统时钟总线
请注意:任何一个硬件控制器想要去正常工作 都必须开启(使能)时钟
而总线 就是给硬件控制器提供时钟的
那么有哪些寄存器呢?分别有什么用呢?
三.STM32F4XX GPIO寄存器
每个通用 I/O 端口包括
4 个 32 位配置寄存器(GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR 和 GPIOx_PUPDR)
2 个 32 位数据寄存器(GPIOx_IDR 和GPIOx_ODR)
1 个 32 位置位/复位寄存器 (GPIOx_BSRR)
1 个 32 位锁定寄存器(GPIOx_LCKR)
2 个 32 位复用功能选择寄存器(GPIOx_AFRH 和 GPIOx_AFRL)。
(1)GPIOx_MODER :功能模式选择寄存器
偏移地址:0x00 (寄存器地址 = 基址 + 偏移地址)
比如 :GPIOA_MODER的地址 0x40020000 + 0x00 = 0x40020000
该寄存器用来控制GPIOx(x=A,B,C...I)组的16个引脚的模式(4种:输入、输出,模拟,复用)
一个寄存器是32bits 一组GPIOx共有16个引脚
每个GPIO引脚占2bits
2bits正好可以表示4种状态
编号为y(y=0,1...15)的GPIO引脚在寄存器中的比特位为GPIOx_MODER[2y+1,2y]
具体配置如下:
GPIOx_MODER[2y,2y+1] 模式
00 输入模式
01 输出模式
10 复用模式
11 模拟模式
例子: 用c代码将PF9配置称为输出模式
分析:
GPIOF组寄存器的起始地址(基址):0x4002 1400
GPIOx_MODER的偏移地址是0x00
so:
GPIOF_MODER的寄存器地址:0x4002 1400 + 0x00 = 0x4002 1400
如果要将PF9设置为输出模式 就需要将
GPIOF_MODER[2*9+1:2*9]
GPIOF_MODER[19:18] ==> 01
把地址为0x40021400的寄存器中的bit19置为0 bit18置为1 怎么做到这两点呢?
通过地址我们就可以将寄存器中的bit置位
在STM32中 用unsigned long来表示地址的值
unsigned long * p = (unsigned long *)0x4002 1400
但是一般情况下我们会在地址的前面加上volatile 变成如下:
volatile unsigned long * p = (volatile unsigned long *)0x4002 1400
volatile的作用是作为指令关键字 禁止编译器优化 访问的就是实际地址
而不会被编译器优化成别的地址 一般用于多线程的全局变量 中断处理函数访问
的全局变量 状态寄存器。
那么我们就可以通过指针p将地址0x40021400的寄存器中的bit19置为0 bit18置为1
操作如下:
xxxxxxxxxxxx yy xxxxxxxxxxxxxxxxxx
<----12----> <------ 18 ------>
& 111111111111 01 111111111111111111 <=先把bit19置为0
===> xxxxxxxxxxxx 0y xxxxxxxxxxxxxxxxxx
1<<19 000000000000 10 000000000000000000
~(1<<19) 111111111111 01 111111111111111111
*p = *p & ~(1<<19)
再把bit18置为1
类似与上面操作 为:
*p = *p | (1<< 18)
所以我们分两步完成这个操作:
*p = *p & ~(1<<19)
*p = *p | (1<< 18)
但是实际上面的操作对寄存器进行了两次操作 效率太低
有点耗费硬件资源 我们对寄存器的修改必须一步到位
所以我们会先定义一个中间变量 用来记录寄存器的值
然后再通过中间变量 一步到位去修改寄存器的值 如下操作
unsigned long r = 0 ;
r = *p ; //先用r保存寄存器中的值 并按照需求修改r值
r &= ~(1<<19);
r |= (1<<18);
*p = r; //通过中间变量 一步到位修改寄存器的值
例子: 将PA0配置为输入模式
用C语言配置一次 用汇编配置一次
C语言:
volatile unsigned long * p = (volatile unsigned long * )0x40020000
unsigned long r = 0 ;
r = *p;
r &= ~3 ; //把bit1和bit0都设置为0 00表示输入模式
*p = r;
汇编:
LDR R0,=0X40020000
LDR ,R1,[R0] ;R1就相当于r 此指令相当于r = *p
BIC R1,R1,#0X03 ; r &= ~3
STR R1,[R0]
(2)GPIOx_OTYPER :Output Type Register 输出类型选择寄存器
偏移地址:0x04
该寄存器用来选择GPIOx(x=A,B...I)这组的16个GPIO引脚的输出类型
寄存器有32bits
低16个bit用于保存对于编号引脚的输出类型 高16bit保留
一个bit保存一个引脚
一个bit有两种状态 分别对应开漏输出和推挽输出
每个GPIO引脚占1bit 编号为y(y=0,1,2...15)的引脚在该寄存器中对于的bit为GPIOx_OTYPER[y]
具体配置如下:
GPIOx_OTYPER[y] 输出类型
1 输出开漏(OD)
0 输出推挽(PP)
(3)GPIOx_OSPEEDR:Output Speed Register 输出速率寄存器
偏移地址:0x08
用于控制GPIOx组的16个GPIO引脚的输出速率
每个引脚占2bit
编号为y的引脚在该寄存器中的bit位是GPIOx_OSPEEDR[2y+1:2y]
具体配置如下:
GPIOx_OSPEEDR 速率
00 2MHZ
01 25MHZ
10 50MHZ
11 30pf则为100MHZ
15pf则为80MHZ
(4)GPIOx_PUPDR:Pull Up Pull Down Register 端口上拉/下拉寄存器
偏移地址:0x0c
该寄存器用来控制GPIOx组的16个引脚的上拉/下拉选择
每个GPIO引脚占2bits
编号为y的GPIO引脚在该寄存器中所在的bit为GPIOx_PUPDR[2y+1:2y]
具体配置如下:
GPIOx_PUPDR 上下拉选择
00 无上拉、无下拉
01 上拉
10 下拉
11 保留
(5)GPIOx_IDR: Input Data Register 输入数据寄存器
偏移地址:0x10
该寄存器用来表示GPIOx这组的16个GPIO引脚的输入的电平状态值
每个GPIO引脚占1bits 该寄存器中高16bit保留没有使用
低16bit表示x组的16个引脚的电平状态
比如:GPIOx_IDR[0] ==>表示的就是该组的第0个引脚GPIOx0的输入电平状态
具体配置如下:
GPIOx_IDR[y] 编号为y的引脚的输入电平状态
1 高电平
0 低电平
比如:
CPU想要知道GPIOA0这个引脚输入的是高电平还是低电平?
思路:
if(GPIOA_IDR & 0X01 == 0X01)
{
PA0为高电平
}
else
{
PA0为低电平
}
===>
volatile unsigned long * p =(volatile unsigned long *)(0x40020000+0x10)
if(*p & 0x01)
{
PA0为高电平
}
(6)GPIOx_ODR:Output Data Register 端口输出数据寄存器
偏移地址: 0x14
该寄存器保存了该组16个GPIO引脚的输出电平状态
高16bit保留的 低16个bit就是对于编号的引脚的输出电平状态
具体配置如下:
GPIOx_ODR[y] 编号为y的引脚的输出电平状态
1 高电平
0 低电平
(7)GPIOx_BSRR:Bit Set Reset Register 端口置位/复位寄存器
偏移地址:0x18
置位:set 把bit位置为1
复位:reset 把bit位置为0
该寄存器用来表示GPIOx组的16个GPIO引脚的输出状态
其中:
高16bits为端口复位寄存器
低16bits为端口置位寄存器
这个寄存器有点特殊 写1有效 写0无效
将GPIOx_BSRR[31:16]置为1 表示将GPIOx15~GPIOx0设置为0
将GPIOx_BSRR[15:0]置为1 表示将GPIOx15~GPIOx0设置为1
实现效果跟GPIOx_ODR一样 用来设置GPIO引脚的输出状态
(8)GPIOx_LCKR :锁定寄存器
(9)GPIOx_AFRL:复用功能低位寄存器 偏移地址:0x20
(10)GPIOx_AFRH:复用功能高位寄存器 偏移地址:0x24
GPIOx_AFRL和GPIOx_AFRH这两个寄存器是放在一起使用的
AFR:Alternate Function Register 复用功能选择寄存器
因为一个GPIO引脚最多有16个复用功能 那么1个GPIO引脚需要4个bit
所以16个引脚就需要16*4 = 64bits 也就是2个寄存器的空间
GPIO引脚编号为0-7由GPIOx_AFRL进行配置
8-15由GPIOx_AFRH进行配置
具体的值表示哪个复用功能或者引脚有哪些复用功能
需要结合电路原理图和功能手册来看
四. STM32F4XX GPIO时钟使能
根据上述的寄存器 就可以去实现所有基于GPIO能够完成的功能配置了
比如:
点亮led灯
获取key按键的状态(按下/松开)
控制蜂鸣器等等
但是我们在之前就提到过 任何一个硬件控制器想要工作 都必须去实现时钟使能
(GPIO所有分组全部属于AHB1时钟总线)
那么时钟的相关配置 请参考RCC部分(数据手册的第六章)
RCC: Reset Clock Control 复位时钟控制 基址:0x40023800
那么现在我们的目的是使能GPIO分组时钟:
RCC AHB1外设时钟使能寄存器(RCC_AHB1ENR)
偏移地址:0x30
该寄存器的第0位到第8位分别控制GPIOA到GPIOI组时钟的使能:
1 使能对于GPIO分组的时钟
0 禁止对应GPIO分组的时钟
比如:使能GPIOF组时钟
==》RCC_AHB1ENR[5] -> 1
C语言实现:
volatile unsigned long * p = (volatile unsigned long *)(0x40023800 + 0x30);
unsigned long r = 0;
r = *p;
r |= 1<<5;
*p = r;
GPIOF组的时钟使能后 就可以去配置GPIOF组的GPIO引脚了
配置后这些GPIO引脚可以与连接硬件电路正常工作了
总结:
利用寄存器来实现GPIO功能配置的步骤
1)配置GPIO分组时钟(RCC_AHB1ENR)
2)配置GPIO功能模式(GPIOx_MODER)
3)配置输出类型(输出功能)
4)配置输出速率
5)配置上拉/下拉
6)如果是输入模式 则通过GPIOx_IDR可以获取外部电路工作状态
如果是输出模式 则通过GPIOx_ODR或这GPIOx_BSRR来向外部输出一个电平信息
如果是复用模式 则后面再讲
什么是GPIO?(详细介绍)相关推荐
- 树莓派 GPIO详细介绍与使用
树莓派 介绍 Raspberry Pi(中文名为"树莓派",简写为RPi,(或者RasPi / RPI) [1] 是为学习计算机编程教育而设计),只有信用卡大小的微型电脑,其系统基 ...
- 【STM32】GPIO详细介绍
目录 GPIO概述 GPIO功能及特性 GPIO的电路结构 GPIO的工作模式 GPIO概述 GPIO是通用输入输出接口(general purpose input/output)的简称主要用于数字量 ...
- STM32F103单片机详细介绍
一.简介 STM32F103C8T6是一款由意法半导体公司(ST)推出的基于Cortex-M3内核的32位微控制器,硬件采用LQFP48封装,属于ST公司微控制器中的STM32系列.除了被我们熟知的S ...
- 详细介绍Audition输出一个周期2khz的正弦波(循环)。将一段数字音频歌曲数据转换为模拟音频波形输出(循环)。
详细介绍Audition输出一个周期2khz的正弦波(循环).将一段数字音频歌曲数据转换为模拟音频波形输出(循环) 序 言 1)STM32 的 DAC简介 2)DAC基本原理 2)DAC数字信号格式 ...
- 超声波模块详细介绍(stm32循迹小车中超声波的介绍)
超声波模块详细介绍(stm32循迹小车中超声波的介绍) 超声波模块是非常重要的一个模块,今天给大家全面介绍一下超声波模块的原理以及用法,代码的编写. 1 超声波模块的认识 首先,市面上的常见超声波模块 ...
- 详细介绍如何读懂STM32开发板电路原理图以及芯片文档和开发手册,并编写一个测试程序:点亮一个LED灯
开发环境: 开发板:STM32PZ6806L芯片:ARM_STM32F103_ZE 软件开发环境:KEIL5 开发所需资料: STM32F1XX芯片电路原理图 STM32F1XX系列芯片手册 XX代表 ...
- HTML页面加载和解析流程详细介绍
浏览器加载和渲染html的顺序.如何加快HTML页面加载速度.HTML页面加载和解析流程等等,在本文将为大家详细介绍下,感兴趣的朋友不要错过 浏览器加载和渲染html的顺序 1. IE下载的顺序是从上 ...
- mysql为什么要压测_mysql集群压测的详细介绍
本篇文章给大家带来的内容是关于mysql集群压测的详细介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. mysql压测 mysql自带就有一个叫mysqlslap的压力测试工具,通 ...
- php比较运算符案列,PHP实例:PHP比较运算符的详细介绍
<PHP实例:PHP比较运算符的详细介绍>要点: 本文介绍了PHP实例:PHP比较运算符的详细介绍,希望对您有用.如果有疑问,可以联系我们. 比拟运算符种类 PHP实战如同它们名称所暗示的 ...
- Tempdb数据库详细介绍
Tempdb数据库详细介绍 一.Tempdb简介 tempdb是SQLServer的系统数据库一直都是SQLServer的重要组成部分,用来存储临时对象.可以简单理解tempdb是SQLServer的 ...
最新文章
- 彻底搞懂浏览器Event-loop
- LeetCode 06Z字形变换07整数反转
- jQuery源码分析系列 : 整体架构
- CentOS 6上配置安装MariaDB,二进制文件,非源码
- fatal error LNK1169: 找到一个或多个多重定义的符号 的解决方案
- mybatis入门(五)之Java API
- RestQL:现代化的 API 开发方式
- mysql基础之四:int(M)中M的含义
- 表单相关标签之textarea,select
- TensorFlow精进之路(十五):深度神经网络简介
- exit()与_exit()函数的区别(Linux系统中)
- 汽车底盘线控与动力学域控制技术
- python的scrapy爬虫模块间进行传参_python网络爬虫——scrapy核心组件介绍、请求传参、下载中间件...
- java经典算法(四)---zws
- 必须重启计算机才能关闭用户账户控制,Win10系统怎么彻底关闭用户帐户控制?...
- LeNet-5 手写字体识别模型
- windows常用系统命令
- ISC技术分享:从RASP开启云上应用安全防护
- 地下城与勇士(DNF)格兰之森副本(幽暗密林、幽暗密林深处、雷鸣废墟、猛毒雷鸣废墟、冰霜幽暗密林、格拉卡、烈焰格拉卡、暗黑雷鸣废墟)(童年的回忆)
- 测相伪距观测方程、线性组合
热门文章
- 优秀成绩标记—— 小王是班级干部,对于即将到来的三好学生评选,负责统计平均成绩超过85分的同学
- 归一化相关 matlab,matlab – 归一化互相关的基础知识
- Java引用和C++引用
- cscd期刊是c刊吗_武工商C刊和北大核心期刊论文发表数量位列全省同类高校前三甲...
- 转战物联网·基础篇03-从JSON数据到短指令谈思维的转变
- 基于WebSocket实现一个简易的群聊功能
- Python篇:用python画xy散点图
- java通过poi读取excel中的日期类型
- SQLiteOpenHelper 崩溃Couldnt read row 0, col -1 from CursorWindow.
- Quartz 定时任务相关介绍表