STM32笔记(2)GPIO介绍及IO口操作

GPIO简介

GPIO:每个连接到I/O总线上的设备都有自己的I/O地址集,即所谓的I/O端口。类似51单片机的P0~P3,但与51单片机不同的是,对stm32的GPIO来说,使用前需要设置其工作方式。。STM32 的每个 IO 端口都有 7 个寄存器来控制其工作方式,而每一个寄存器都需要用32bit来控制。在STM32中,一组GPIO有16个IO口。

端口位基本结构:

在这里插入图片描述

工作方式

stm32的IO口一共有8种工作方式:
1、 输入浮空:读取相应外部的电平,但当引脚不输入时,相当于悬空,输入电平未知,范围为0~VCC。
2、 输入上拉:保证在无信号输入时输入端的电平为高电平。而在信号输入为低电平时输入端的电平也为低电平。
3、 输入下拉:保证在无信号输入时输入端的电平为低电平。而在信号输入为高电平时输入端的电平也为高电平。
4、 模拟输入:传统方式的输入,将0,1的二进制数字信号,通过数模转换,变成模拟信号,应用ADC模拟输入,或者低功耗下省电。简单来说,就是将原本的高低电平转换为范围为0~VCC信号。
5、 开漏输出:IO口输出0接地,输出1由外接电阻控制为0/1.
6、 推挽输出:低电平输出为0,高电平输出为VCC
链接:用三极管很好地说明上面两者输出
7、 复用推挽输出:用作串口的输出。
8、 复用开漏输出 :用在IIC。
7和8两种输出共同点:可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用),给内部外设使用的推挽,开漏输出,此时端口必须配置为复用功能输出模式。

STM32 的 IO 口位配置表 :
在这里插入图片描述
STM32 输出模式配置 :

在这里插入图片描述

相关寄存器介绍

配置模式的 2 个 32 位的端口配置寄存器 CRL 和 CRH; 2 个 32 位的数据寄存器 IDR 和 ODR; 1 个 32 位的置位/复位寄存器BSRR;一个 16 位的复位寄存器 BRR; 1 个 32 位的锁存寄存器 LCKR。

1.CRL 和 CRH(控制着每个 IO 口的模式及输出速率)
在这里插入图片描述
CRH 的作用和 CRL 完全一样,只是 CRL 控制的是低 8 位输出口,而 CRH 控制的是高 8位输出口。

2.IDR 和 ODR
IDR:IDR 是一个端口输入数据寄存器,只用了低 16 位。该寄存器为只读寄存器,并且只能以16 位的形式读出。 在这里插入图片描述
在固件库中操作 IDR 寄存器读取 IO 端口数据是通过GPIO_ReadInputDataBit 函数实现的:

uint8_tGPIO_ReadInputDataBit(GPIO_TypeDef*GPIOx,uint16_tGPIO_Pin)

比如读取 GPIOA.5 的电平状态,那么方法是:

GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);

返回值是 1(Bit_SET)或者 0(Bit_RESET);

ODR:ODR 是一个端口输出数据寄存器,也只用了低 16 位。该寄存器为可读写,从该寄存器读出来的数据可以用于判断当前 IO 口的输出状态。而向该寄存器写数据,则可以控制某个 IO 口的输出电平。
在这里插入图片描述
在固件库中设置 ODR 寄存器的值来控制 IO 口的输出状态是通过函数GPIO_Write 来实现的:

void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

该函数一般用来对一个 GPIO 的多个端口设值。

3.BSRR:BSRR 寄存器是端口位设置/清除寄存器。 该寄存器和 ODR 寄存器具有类似的作用,都可以用来设置 GPIO 端口的输出位是 1 还是 0。
在这里插入图片描述
4. BRR: BRR 寄存器是端口位清除寄存器。该寄存器的作用跟 BSRR 的高 16 位相同。

5.LCKR(不常用)

在这里插入图片描述

IO 操作步骤

1) 使能 IO 口时钟。调用函数为 RCC_APB2PeriphClockCmd()。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOX,ENABLE);//GPIOX  使能时钟,X=A~E

2) 初始化 IO 参数。调用函数 GPIO_Init();(确定操作的IO口)

void GPIO_Init(GPIO_TypeDef*GPIOx,GPIO_InitTypeDef*GPIO_InitStruct);

其中,两个参数分别为:配置引脚组(GPIO_TypeDef* GPIOx),配置的参数( GPIO_InitTypeDef* GPIO_InitStruct)。

3) 操作 IO。(固件库操作,寄存器操作,位操作)

IO口三种操作细解

1.固件库操作

 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP; //模式选择为推挽输出
 GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;//确定选位为第五个IO口
 GPIO_InitStructure.GPIO_Speed=  GPIO_Speed_50MHz;//速率选择为50M
 GPIO_Init(GPIOX,&GPIO_InitStructure);//初始化GPIOX
 
 GPIO_SetBits(GPIOX,GPIO_Pin_5);//设置该位为高电平
  GPIO_ResetBits(GPIOX,GPIO_Pin_5);//设置该位为低电平

八种模式在 MDK 中是通过一个枚举类型定义的:

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; 

IO 口速度设置, 有三个可选值,在 MDK 中同样是通过枚举类型定义 :

typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef; 

在模式设置完成后,可通过其它库函数进行IO口设置:
控制IDR:

uint_8t GPIO_ReadIputDataBit(GPIO_TypeDef*GPIOx,uint16_t GPIO_Pin)

控制ODR:

void GPIO_Write(GPIO_TypeDef*GPIOx,uint16_t PortVal)

控制BSRR与BRR:

void GPIO_SetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_Pin)

void GPIO_ResetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_Pin)

2.寄存器操作
完整IO口初始化示例:

void LED_Init(void)
 {     
  RCC->APB2ENR |= 1<<2;    //使能PTA时钟     
  RCC->APB2ENR |= 1<<5;    //使能PTD时钟     
  GPIOA->CRH&=0XFFFF FFF0; //清空PA8设置     
  GPIOA->CRH|=0X0000 0003; //设置PA8推挽输出     
  GPIOA->ODR|=1<<8;  //PA8输出高     
  GPIOD->CRL&=0XFFFF F0FF;//清空PD2设置     
  G)PIOD->CRL|=0X0000 0300;//设置PD2推挽输出     
  GPIOD->ODR|=1<<2;//PD2输出高 
}

一般用寄存器控制IO口的形式如上,可以直接赋值,也可以通过与(&),或(|),非(~)进行控制 。

3.位操作:通过直接对IO口的地址进行操作来改变IO口的值,达到操作IO的目的。
原理:将每个位膨胀为一个32位的字(即其地址),称为这个位的“位带别名区 ”,如下图:
在这里插入图片描述

STM32中有两个位带区:

在这里插入图片描述
对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:

AliasAddr= 0x42000000+((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4 

对 SRAM 位带区的某个比特,记它所在字节地址为 A,位序号为 n(0<=n<=7),则该比特 在别名区的地址为:

AliasAddr= 0x42000000+((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4

“*4”表示一个字为 4 个字节,“*8”表示一个字节中有 8 个比特。

使用(以控制GPIOA的16个IO口中的第一个为例):

//IO口操作宏定义,获取相应地址
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
//IO口地址映射,即获取GPIOA_ODR的地址

#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入

PAin(1=1//使得GPIOA的第一个IO口为高电平

附录:C语言相关

1.C语言位操作
在这里插入图片描述

2.define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供方便。常见的格式:#define 标识符 字符串(无分号)“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。 (类似typedef )

3.单片机程序开发过程中,经常会遇到一种情况, 当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。 条件编译命令最常见的形式为:

#ifdef 标识符
程序段 1
#else
程序段 2
#endif 

当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,否则编译程序段 2。 其中#else 部分也可以没有,。

4.C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。 如:

extern u16 USART_RX_STA; 

此时,在其它文件会有:

u16 USART_RX_STA;

但是如果有多个文件同时要对应用的变量进行操作,而且可能会修改该变量,那就会影响其他模块的使用。

5.结构体
声明结构体类型:

Struct 结构体名{
成员列表;
}变量名列表; 

例:
struct U_TYPE {
int BaudRate
int WordLength;
}usart1,usart2; 

结构体成员变量的引用方法是:结构体变量名字.成员名

usart1.BaudRate//引用usart1中的成员BaudRate

当变量定义过多,或者某几个变量是用来描述某一个对象,结构体可以使函数更加易读。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章