STM32必须掌握的有时钟树和系统架构,感觉那个是比较简单的,多看几遍就能看会了。在后面的笔记中也会记录到这些,如果自己看不懂的话可以跟着不同外设的笔记来了解一下。
这篇笔记是讲解的GPIO,学会了GPIO的话就可以完成32的第一个功能
GPIO描述
GPIO,英文全称为General-Purpose IO ports,中文意思是通用I/O端口。
在嵌入式系统中,经常需要控制许多结构简单的外部设备或者电路,这些设备有的需要通过CPU控制,有的需要CPU提供输入信号。并且,许多设备或电路只要求有开/关两种状体就够了,比如LED的亮与灭。对这些设备的控制,使用传统的串口或者并口就显得比较复杂,所以,在嵌入式微处理器上通常提供了一种“通用可编程I/O端口”,也就是GPIO。
![在这里插入图片描述](https://img-blog.csdnimg.cn/d0706a30024a45eca85d4cb85152d6f9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oOz5a2m5bWM5YWl5byP55qE5bCP6I-c6bif,size_18,color_FFFFFF,t_70,g_se,x_16)
GPIO外设位于APB2总线上,在使用时我们需要开APB2总线的时钟。
![在这里插入图片描述](https://img-blog.csdnimg.cn/d730ef8d7e6e4dc6be17a368d5c1b9c8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oOz5a2m5bWM5YWl5byP55qE5bCP6I-c6bif,size_19,color_FFFFFF,t_70,g_se,x_16)
GPIO外设的最大时钟频率为72MHz。
GPIO模式
GPIO端口的每个位可以由软件分别配置
成多种模式。
─ 输入浮空
─ 输入上拉
─ 输入下拉
─ 模拟输入
─ 开漏输出
─ 推挽式输出
─ 推挽式复用功能
─ 开漏复用功能
输入配置
当I/O端口配置为输入时:
● 输出缓冲器被禁止(没有输出功能)
● 施密特触发输入被激活
● 根据输入配置(上拉,下拉或浮动)的不同,弱上拉和下拉电阻被连接
● 出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器(模拟输入除外)
● 对输入数据寄存器的读访问可得到I/O状态(状态:0/1)
![在这里插入图片描述](https://img-blog.csdnimg.cn/060a1b9199054d8eb6480a7f9a43317d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oOz5a2m5bWM5YWl5byP55qE5bCP6I-c6bif,size_17,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/feeb7600f8a3410db0494a9f8ba10794.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oOz5a2m5bWM5YWl5byP55qE5bCP6I-c6bif,size_12,color_FFFFFF,t_70,g_se,x_16)
注意:
用寄存器编程配置上下拉输入时要注意,因为上下拉输入时,CNF0,CNF1都为0,1,所以配置上下拉输入时要配置ODR寄存器对应的位,对应位1为上拉,0为下拉,如上图所示。
输出配置
当I/O端口被配置为输出时:
● 输出缓冲器被激活
─ 开漏模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将端口置于高阻状态(PMOS从不被激活—不能被控制)。
─ 推挽模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将激活P-MOS。(两个MOS管都可以被控制,此时工作一定是一个导通一个截至)
● 施密特触发输入被激活
● 弱上拉和下拉电阻被禁止(M3的单片机输出没有上下拉,M4的单片机输出有上下拉)
● 出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器
● 在开漏模式时,对输入数据寄存器的读访问可得到I/O状态(开漏访问输入数据寄存器可以得到I/O口的状态)
● 在推挽式模式时,对输出数据寄存器的读访问得到最后一次写的值。(输出状态也可以获取I/O口的状态----访问输出数据寄存器)
![在这里插入图片描述](https://img-blog.csdnimg.cn/ba10c3c743c34a1280edc122976773ff.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oOz5a2m5bWM5YWl5byP55qE5bCP6I-c6bif,size_18,color_FFFFFF,t_70,g_se,x_16)
读取IO口状态的方法:
1、在开漏模式时,访问输入数据寄存器可以得到I/O口的状态
2、输出状态也可以获取I/O口的状态----访问输出数据寄存器
GPIO寄存器介绍
每个GPI/O端口有:
两个32位配置寄存器(GPIOx_CRL, GPIOx_CRH)
两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR)
一个32位置位/复位寄存器(GPIOx_BSRR)
一个16位复位寄存器(GPIOx_BRR)
一个32位锁定寄存器(GPIOx_LCKR)。
![在这里插入图片描述](https://img-blog.csdnimg.cn/f51951c2456c4a72a011081606a7656b.png)
配置寄存器:GPIOx_CRL, GPIOx_CRH
数据寄存器:GPIOx_IDR和GPIOx_ODR
CRL and CRH 端口配置高寄存器
![在这里插入图片描述](https://img-blog.csdnimg.cn/01a2e56bc225488b8c77c4fc8d8ebdb4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oOz5a2m5bWM5YWl5byP55qE5bCP6I-c6bif,size_19,color_FFFFFF,t_70,g_se,x_16)
IDR 端口输入数据寄存器
![在这里插入图片描述](https://img-blog.csdnimg.cn/79ac0ee0793f439fa19b92302657dde6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oOz5a2m5bWM5YWl5byP55qE5bCP6I-c6bif,size_20,color_FFFFFF,t_70,g_se,x_16)
ODR 端口输出数据寄存器
![在这里插入图片描述](https://img-blog.csdnimg.cn/b0d5774abf91497591f2b6ec0d17e4c2.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oOz5a2m5bWM5YWl5byP55qE5bCP6I-c6bif,size_20,color_FFFFFF,t_70,g_se,x_16)
注意:对GPIOx_BSRR(x = A…E),可以分别地对各个ODR位进行独立的设置/清除
BSRR 端口位设置/清除寄存器
![在这里插入图片描述](https://img-blog.csdnimg.cn/ddc521a047384aca8be14751f0984c68.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5oOz5a2m5bWM5YWl5byP55qE5bCP6I-c6bif,size_20,color_FFFFFF,t_70,g_se,x_16)
在标准库中让某一个端口输出1,就是对BSRR寄存器进行操作的。
为啥不用BSRR寄存器让端口输出0呢?因为操作BSRR寄存器输出0有点麻烦,还有一个BRR寄存器,让端口输出0比较简单点。如果想不明白看程序和寄存器想想就明白了。
如下所示:
/*****************************************
Sets the selected data port bits.
设置选定的数据端口位。(端口输出1)
*******************************************/
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BSRR = GPIO_Pin;
}
BRR 端口位清除寄存器
![在这里插入图片描述](https://img-blog.csdnimg.cn/9be91ccdffc348f991341289448a20a5.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/ab3a5bf0c7394a25834464890073ae62.png)
在标准库中让某一个端口输出0,就是对BRR寄存器进行操作的,如下所示:
/****************************************
Clears the selected data port bits.
清除选定的数据端口位。(端口设置为0)
***************************************/
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BRR = GPIO_Pin;
}
程序
了解了这些,写一个点灯的程序还是很简单的
/*******************************
一些实用的重定义
**********************************/
#define led0 GPIOB
#define led1 GPIOE
#define Led_On(port, pin) (port->ODR &= ~(0x1<<pin))
#define Led_Off(port, pin) (port->ODR |= (0x1<<pin))
#define Led_Toggle(port, pin) (port->ODR ^= (0x1<<pin))
/**************************
寄存器版本的LED初始化程序
初始化的GPIOB.5/GPIOE.5
*************************/
void LED_init(void)
{
//开启时钟GPIOB/E
RCC->APB2ENR |= 1<<3;
RCC->APB2ENR |= 1<<6;
//设置GPOPB5 and GPIOE5
GPIOB->CRL&=0XFF0FFFFF;
GPIOB->CRL|=0X00300000;//PB.5 推挽输出
GPIOE->CRL&=0XFF0FFFFF;
GPIOE->CRL|=0X00300000;//PE.5 推挽输出
//GPIO输出初始化
GPIOB->ODR|=1<<5; //PB.5输出高
GPIOE->ODR|=1<<5; //PE.5输出高
}
/**********************************
标准库初始化
******************************/
void LED_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//开启时钟GPIOB/E
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
//设置GPOPB5 and GPIOE5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE,&GPIO_InitStructure);
//GPIO输出初始化
GPIO_SetBits(GPIOE, GPIO_Pin_3); //PB.5输出高
GPIO_SetBits(GPIOE, GPIO_Pin_2); //PE.5输出高
}
编写程序两种库的组合有:
标准库+寄存器
hal库+寄存器
GPIO就简单介绍到这里,GPIO的其他功能将会在后期用到的时候逐渐介绍。