STM32单片机输入输出操作详解
参考:STM32F1xx中文参考手册_V10
注:以下所用电路图及寄存器等以stm32f103RCT6为例,具体硬件具体分析
向外输出:
例:点亮LED灯
(1)电路图分析
如图,分析可知,若想要LED1、LED2点亮,则需要再LED1/LED2接口处给一个低电平信号,即芯片要对外设LED输出一个低电平信号
在单片机电路图中寻找LED1、LED2接口对应的IO引脚,如图,可得引脚为PA6/PA7,即引脚GPIOA6/GPIOA7,在实现过程中,我们需要使GPIOA6/GPIOA7获得时钟,并进行输出电信号的准备过程,即初始化,再输出一个低电平信号
(2)寄存器分析
使GPIOA6/GPIOA7获得时钟:
观察时钟树
APB1上面连接的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、USART2、USART3、UART4、UART5、SPI2、SP3等;
而APB2上面连接的是高速外设,包括UART1、SPI1、Timer1、ADC1、ADC2、ADC3、所有的普通I/O口(PA-PE)、第二功能I/O(AFIO)口等。
可知,GPIO接口的时钟都在APB2总线上,由APB2总线给GPIO各端口使能。
时钟控制寄存器RCC(Reset and Clock Control)是专门用于控制各外设时钟使能的。官方参考手册中RCC所有寄存器如下:
前文分析过,点亮LED灯需要给 GPIOA6/7 引脚使能,GPIO引脚在APB2总线上,所以需要 APB2 外设时钟使能寄存器(RCC_APB2ENR)。该寄存器详情如下:
其2位~8位的 IOPX 代表 GPIOX,置0时为该端口时钟关闭(失能),置1时该端口时钟开启(使能)。
进行输出电信号的准备过程,即初始化:
上文已分析端口使能,接下来要对所需端口进行初始化,配置端口,即关于 GPIO 的输入输出模式选择,输出速率选择。GPIO所有寄存器如下:
初始化所需的端口配置寄存器在这里分为两部分,其中低寄存器指的是GPIO0~GPIO7,高寄存器指GPIO8~GPIO15。GPIOA6/7显然是低寄存器,它的配置应该在GPIOx_CRL中处理。GPIOx_CRL详情如下:
可以看出,配置一个引脚需要连续4位 CNFX:MODEX(X为0~7),两者含义及配置如图所示,不再赘述。
假设我们只需要点亮LED1,即初始化GPIOA6,第6引脚,则需要控制24~27位【CNF6:MODE6】。先对24、25位【MODE6】赋值,我们需要进行的是输出操作,速度选择随意,此处举例选择最大速度 50MHz,则24~25位设置为11。再对26、27位【CNF6】赋值,输出模式选择通用推挽输出模式(此模式可以输出高低电平,方便后续对LED进行点亮、熄灭控制;通用开漏模式只能输出低电平,只能控制LED点亮,无法熄灭;后面两个复用是用于特殊用途,此处无法使用,不能选择),即26/27位设置为00。以上,24~27所需设置为0011,实现GPIOA6引脚的初始化。
输出一个低电平信号:
输出信号存储在端口输出数据寄存器(GPIOx_ODR),输出一个信号的操作等同于设置该寄存器相应位数据的操作。该寄存器详情如下:
如图,该寄存器低16位给出了端口GPIOX所有的16个引脚,我们需要使GPIOA6输出一个低电平信号,设置GPIOA->ODR6为0即可。
(3)使用固件库函数进行操作
如上寄存器分析的三步,我们可以选取固件库中封装好的相应的寄存器函数来点亮LED灯。
函数分析:
使能:
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
peripheral(外设)
引脚初始化:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
输出信号设置:
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
该函数将指定位置0
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
该函数将指定位置1
(4)实例
void LED_Init(){
//使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//引脚初始化:
GPIO_InitTypeDef gpio_init={0};
gpio_init.GPIO_Pin= GPIO_Pin_7 | GPIO_Pin_6;
gpio_init.GPIO_Mode= GPIO_Mode_Out_PP;
gpio_init.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&gpio_init);
}
//输出信号设置:输出低电平信号--亮,否则--灭
GPIO_ResetBits(GPIOA,GPIO_Pin_7| GPIO_Pin_6);
向内输入:
例:按键控制LED灯亮灭
(1)题目分析
LED灯亮灭前文已经分析过,要使用按键来控制,就必须要知道按键什么时候按下,或者说是按键是否按下。在设置LED灯引脚输出高低电平来控制其点亮熄灭的代码前加上判断按键是否按下的条件语句就可以了。
(2)电路分析
可以看到,DOWN、LEFT、RIGHT按键左侧接地,在按键按下后这些引脚会显示为低电平,因此输入模式选择上拉模式会使按键按下前后产生较大的差距,较易观察;WAKEUP相反,选择下拉模式较好。因此,我们可以根据DOWN、LEFT、RIGHT及WKUP这些引脚的信号高低来判断按键是否按下。
以下均以DOWN为例。
查询单片机上DOWN引脚,可得DOWN对应的是PC4,即GPIOC4端口。
(3)寄存器分析
关于GPIO的使能及初始化在LED部分已经分析过,输入输出的使能及GPIO初始化几乎一样,只要在模式选择中选择输出模式即可。主要区别在于输入时单片机是被动接收方,外设是主动的,那输出时的输出设置在输入中就变成了接收操作。
接收外设输入的数据
GPIO寄存器中有一个用来存储输入数据的寄存器——端口输入数据寄存器(GPIOx_IDR)。该寄存器详情如下:
其中0~15位的数值表示GPIOX端口0~15个引脚所接收到的数据,例如假设按键DOWN按下,DOWN的引脚为GPIOC4,则GPIOC–>IDR的4位会被置0,我们读取IDR中的4位,如果为0,则代表按键已按下。
注:端口输入数据寄存器中在按键未按下时的值可能与引脚初始化时选择的输入模式有关,选择上拉模式,则该引脚初始被设置为高电平,在未按下按键时,GPIOx_IDR可能是读取了该引脚的电信号高低来设值的。总之,该值除了0就是1,但在重置GPIO后,该值为X,未知。
(4)使用固件库函数进行操作
使能和初始化函数没有什么变化,固件库中读取端口输入数据寄存器中某位的值的函数为
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
其返回值为你要读取的那位的值,即外设输入的值。
void Key_Down_Init(){
//使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
//引脚初始化
GPIO_InitTypeDef gpio_Init={0};
gpio_Init.GPIO_Pin=GPIO_Pin_4;
gpio_Init.GPIO_Mode=GPIO_Mode_IPU;//上拉
gpio_Init.GPIO_Speed=GPIO_Speed_50MHz;//该值理论上可以省略
GPIO_Init(GPIOC, &gpio_Init);
}
int main(){
//使能及初始化操作
LED_Init();
Key_Down_Init();
//判断按键是否按下
//当读取到GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_4)为0时,表示按键按下
if(!GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_4)){
//设置 GPIOA 引脚6/7为低电平,点亮led灯
GPIO_ResetBits(GPIOA,GPIO_Pin_7| GPIO_Pin_6);
}else{
//设置 GPIOA 引脚6/7为高电平,熄灭led灯
GPIO_SetBits(GPIOA,GPIO_Pin_7| GPIO_Pin_6);
}
}
作者:鹤唳馁樂多