其他分享
首页 > 其他分享> > STM32-GPIO学习-跑马灯实验和按键实验-寄存器版本和HAL库版本

STM32-GPIO学习-跑马灯实验和按键实验-寄存器版本和HAL库版本

作者:互联网

一、stm32跑马灯实验

a.GPIO

b.GPIO的8种工作模式

c.GPIO寄存器

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_AFRL&GPIOx_AFRH 复用功能

1.寄存器定义

F767:stm32f767xx.h

文件中查找GPIO得到:

typedef struct
{
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  __IO uint32_t BSRR;     /*!< GPIO port bit set/reset register,      Address offset: 0x18      */
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

再查找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)
#define GPIOI               ((GPIO_TypeDef *) GPIOI_BASE)
#define GPIOJ               ((GPIO_TypeDef *) GPIOJ_BASE)
#define GPIOK               ((GPIO_TypeDef *) GPIOK_BASE)

可以看出GPIOA是一个结构体指针,它指向基地址。

GPIOA->ODR即可访问GPIOA端口的ODR输出寄存器。

2.跑马灯硬件连接

在这里插入图片描述

可以看到,两个LED灯,一端连到LED0、LED1,另一端通过上拉电阻连到VCC。如果PB0输出0,那么LED1导通。

GPIO输出方式:采用可以输出高低电平的 推挽输出(上拉)

3.配置寄存器操作IO口步骤

  1. 初始化HAL库:HAL_Init();

  2. 初始化系统时钟:

    stm32F767:Stm32_Clock_Init(431,25,2,9);

  3. 使能IO口时钟。配置IO口时钟使能寄存器:RCC->AHB1ENR

    RCC AHB1 外设时钟寄存器 (RCC_AHB1ENR)

    在这里插入图片描述

    在这里插入图片描述

  4. 初始化IO口模式。配置四个配置寄存器

    GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR、GPIOx_PUPDR

  5. 操作IO口,输出高低电平

    配置寄存器GPIOX_ODR或者GPIOx_BSRR

4.手写跑马灯

在HALLIB里面的stm32f7xx_hal.c文件中可以找到HAL_Init函数,复制过来粘到main()文件中,完成了初始化HAL库。

在SYSTEM里面的sys.c文件中找到Stm32_Clock_Init函数,复制过来粘到main()文件中,完成了初始化系统时钟。

在HALLIB里面的stm32f7xx_hal.c文件中搜索RCC_TypeDef,可以看到:

typedef struct
{
  __IO uint32_t CR;            /*!< RCC clock control register,                                  Address offset: 0x00 */
  __IO uint32_t PLLCFGR;       /*!< RCC PLL configuration register,                              Address offset: 0x04 */
  __IO uint32_t CFGR;          /*!< RCC clock configuration register,                            Address offset: 0x08 */
  __IO uint32_t CIR;           /*!< RCC clock interrupt register,                                Address offset: 0x0C */
  __IO uint32_t AHB1RSTR;      /*!< RCC AHB1 peripheral reset register,                          Address offset: 0x10 */
  __IO uint32_t AHB2RSTR;      /*!< RCC AHB2 peripheral reset register,                          Address offset: 0x14 */
  __IO uint32_t AHB3RSTR;      /*!< RCC AHB3 peripheral reset register,                          Address offset: 0x18 */
  uint32_t      RESERVED0;     /*!< Reserved, 0x1C                                                                    */
  __IO uint32_t APB1RSTR;      /*!< RCC APB1 peripheral reset register,                          Address offset: 0x20 */
  __IO uint32_t APB2RSTR;      /*!< RCC APB2 peripheral reset register,                          Address offset: 0x24 */
  uint32_t      RESERVED1[2];  /*!< Reserved, 0x28-0x2C                                                               */
  __IO uint32_t AHB1ENR;       /*!< RCC AHB1 peripheral clock register,                          Address offset: 0x30 */
  __IO uint32_t AHB2ENR;       /*!< RCC AHB2 peripheral clock register,                          Address offset: 0x34 */
  __IO uint32_t AHB3ENR;       /*!< RCC AHB3 peripheral clock register,                          Address offset: 0x38 */
  uint32_t      RESERVED2;     /*!< Reserved, 0x3C                                                                    */
  __IO uint32_t APB1ENR;       /*!< RCC APB1 peripheral clock enable register,                   Address offset: 0x40 */
  __IO uint32_t APB2ENR;       /*!< RCC APB2 peripheral clock enable register,                   Address offset: 0x44 */
  uint32_t      RESERVED3[2];  /*!< Reserved, 0x48-0x4C                                                               */
  __IO uint32_t AHB1LPENR;     /*!< RCC AHB1 peripheral clock enable in low power mode register, Address offset: 0x50 */
  __IO uint32_t AHB2LPENR;     /*!< RCC AHB2 peripheral clock enable in low power mode register, Address offset: 0x54 */
  __IO uint32_t AHB3LPENR;     /*!< RCC AHB3 peripheral clock enable in low power mode register, Address offset: 0x58 */
  uint32_t      RESERVED4;     /*!< Reserved, 0x5C                                                                    */
  __IO uint32_t APB1LPENR;     /*!< RCC APB1 peripheral clock enable in low power mode register, Address offset: 0x60 */
  __IO uint32_t APB2LPENR;     /*!< RCC APB2 peripheral clock enable in low power mode register, Address offset: 0x64 */
  uint32_t      RESERVED5[2];  /*!< Reserved, 0x68-0x6C                                                               */
  __IO uint32_t BDCR;          /*!< RCC Backup domain control register,                          Address offset: 0x70 */
  __IO uint32_t CSR;           /*!< RCC clock control & status register,                         Address offset: 0x74 */
  uint32_t      RESERVED6[2];  /*!< Reserved, 0x78-0x7C                                                               */
  __IO uint32_t SSCGR;         /*!< RCC spread spectrum clock generation register,               Address offset: 0x80 */
  __IO uint32_t PLLI2SCFGR;    /*!< RCC PLLI2S configuration register,                           Address offset: 0x84 */
  __IO uint32_t PLLSAICFGR;    /*!< RCC PLLSAI configuration register,                           Address offset: 0x88 */
  __IO uint32_t DCKCFGR1;      /*!< RCC Dedicated Clocks configuration register1,                 Address offset: 0x8C */
  __IO uint32_t DCKCFGR2;      /*!< RCC Dedicated Clocks configuration register 2,               Address offset: 0x90 */

} RCC_TypeDef;

可以找到AHB1ENR。由寄存器可知只需要改变位1。在main中写RCC->AHB1ENR |=1<<1(1左移一位,然后或上RCC->AHB1ENR可以把位1置为1),或者写成RCC->AHB1ENR=0x02。

在这里插入图片描述

在这里插入图片描述

因为需要配置的端口位是0和1,所以需要配置MODER1和MODER0.因为是推挽输出,所以设置最后四个位0101,那么也就是5,最终MODER寄存器设置成0x05。在main函数中写上GPIOB->MODER=0x05。

在这里插入图片描述

由于是推挽输出,所以端口0和1都是0。在main函数中写上GPIOB->OTYPER=0x00;

在这里插入图片描述

设置为高速的话后面两个端口的两个位也都是11。这样的话设置成GPIOB->OSPEEDR=0x0f;虽然只写了8位,但是他其实一共有32位,前面都是零,可以省略不写,但是要规范的话是需要把0全写上补满32位。

在这里插入图片描述

这里要配置为上拉,所以后面两个端口都是0101,也就是要设置成GPIOB->PUPDR=0x05;

然后操作IO口,输出高低电平,配置寄存器GPIOX_ODR或者GPIOx_BSRR。

在这里插入图片描述

要配置端口1和0输出高电平,也就是需要BS1和BS0为1,其它位保持不变为0,也就是说要配置成0x03 。如果配置端口1和0输出低电平,也就是需要BR1和BR0为1进行复位,其它位保持不变为0,也就是说要配置成0x00030000 。写成代码也就是GPIOB->BSRR=0x00000003;//1,GPIOB->BSRR=0x00030000;//0。

然后由于开和关间隔时间过短,所以还需要一个delay函数。在SYSTEM-delay.c文件中可以找到delay.h,然后进入delay.h头文件,可看到void delay_init(u8 SYSCLK);void delay_ms(u16 nms);void delay_us(u32 nus);三个函数。使用delay首先进行初始化delay_init(216);其中216是stm32f767的系统时钟。然后就可以调用delay_ms(500);

最终的代码为:

#include "sys.h"
#include "delay.h"
#include "usart.h"

int main(void)
{
	HAL_Init();
	Stm32_Clock_Init(431,25,2,9);
	delay_init(216);
	RCC->AHB1ENR |= 1<<1;
	GPIOB->MODER=0x05;
	GPIOB->OTYPER=0x00;
	GPIOB->OSPEEDR=0x0f;
	GPIOB->PUPDR=0x05;
	while(1)
	{
		GPIOB->BSRR=0x00000003;//1
		delay_ms(500);
		GPIOB->BSRR=0x00030000;//0
		delay_ms(500);
	}
}

5.使用HAL库

优点:方便在各个stm32平台移植。

HAL库很好的解决了程序移植的问题,不同型号的stm32芯片它的标准库是不一样的,例如在F4上开发的程序移植到F3上是不能通用的,而使用HAL库,只要使用的是相通的外设,程序基本可以完全复制粘贴,注意是相同外设。

在HALLIB文件夹下可以找到stm32f7xx_hal_gpio.c文件,然后找到stm32f7xx_hal_gpio.h文件,可以看到里面有几个函数:

/* Initialization and de-initialization functions *****************************/
void  HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init);//初始化函数
void  HAL_GPIO_DeInit(GPIO_TypeDef  *GPIOx, uint32_t GPIO_Pin);
/**
/* IO operation functions *****************************************************/
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//读取输入电平函数
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);//设置输出电平函数
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//电平翻转函数
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//引脚电平锁定函数
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin);//外部中断函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);

6.配置HAL库操作IO口步骤

int main()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	HAL_Init();
	delay_init(216);
	Stm32_Clock_Init(431,25,2,9);
	__HAL_RCC_GPIOB_CLK_ENABLE();//使能PB时钟
	GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_PP;//推挽输出
	GPIO_InitStructure.Pin=GPIO_PIN_0 | GPIO_PIN_1;
	GPIO_InitStructure.Pull=GPIO_PULLUP;//上拉
	GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_VERY_HIGH;//高速
	
	HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);
	while(1){
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);//PB0=0
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);//PB1=0
		delay_ms(500);
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);//PB0=1
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);//PB1=1
		delay_ms(500);
		
	}
	
}

二、stm32按键实验

1.电路连接图

在这里插入图片描述

由图可知,KEY0、KEY1、KEY2需要设置成上拉,这样的话,如果按键KEY0、KEY1、KEY2按下,那么将得到低电平信号,如果KEY0、KEY1、KEY2没有按下,将是高电平信号。WK_UP设置为下拉,KEY_UP按下的话,将检测到高电平信号,没有按下将是低电平信号。KEY0->PH3上拉输入、KEY1->PH2上拉输入、KEY2->PC13上拉输入、WK_UP->PA0下拉输入。

2.步骤

1.使能按键对应IO口时钟

__HAL_RCC_GPIOx_CLK_ENABLE();

由电路连接图,可以发现,涉及到的是A、C、H口:

	__HAL_RCC_GPIOA_CLK_ENABLE();           //开启GPIOA时钟
    __HAL_RCC_GPIOC_CLK_ENABLE();           //开启GPIOC时钟
    __HAL_RCC_GPIOH_CLK_ENABLE();           //开启GPIOH时钟

2.初始化IO模式:上拉/下拉输入

HAL_GPIO_Init

根据KEY0->PH3上拉输入、KEY1->PH2上拉输入、KEY2->PC13上拉输入、WK_UP->PA0下拉输入。

 	GPIO_InitTypeDef GPIO_Initure; 	
	GPIO_Initure.Pin=GPIO_PIN_0;            //PA0
    GPIO_Initure.Mode=GPIO_MODE_INPUT;      //输入
    GPIO_Initure.Pull=GPIO_PULLDOWN;        //下拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);
    
    GPIO_Initure.Pin=GPIO_PIN_13;           //PC13
    GPIO_Initure.Mode=GPIO_MODE_INPUT;      //输入
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOC,&GPIO_Initure);
    
    GPIO_Initure.Pin=GPIO_PIN_2|GPIO_PIN_3; //PH2,3
    HAL_GPIO_Init(GPIOH,&GPIO_Initure);

3.扫描IO口电平(库函数/寄存器)

HAL库函数:GPIO_PinState HAL_GPIO_ReadPin();

寄存器:GPIOx_IDR

4.编写按键扫描逻辑

按键支持连按,举个遥控器的例子,一直按,一直换台。如果不支持连按,就说明这个遥控器按下一次换台,如果后面也一直按,它不会换台。如果不支持连按的话,需要一个变量记录上一次的状态,如果是上一次是未按下,那么这一次检测到按下,说明这个按键算是按下了。如果检测上一次按下了,那么这次按不算,也就是说,按键按下了,没有松开,只能算一次。

按键扫描支持连按的思路:

u8 KEY_Scan(void){
	if(KEY按下)
	{
		delay_ms(10);//延时防抖
		if(KEY确实按下){
			return KEY_Value;
		}
		return 无效值;
	}
}

每次调用getValue函数之后,返回值是多少

int getValue()
{
	static int flag = 0;
	flag++;
	return flag;
}

每一次调用,第一次返回1,第二次返回2…因为static变量存在,static修饰的变量只被初始化一次,并且保持最近的值,哪怕创建它的函数已经结束,这个变量也不会被释放,下次调用是同一个地址,所以里面的值是上次的。static定义的变量有记忆的作用。所以不支持连按的思路:注意key_up一定记录的是上一次的状态。

u8 KEY_SCAN(void)
{
    static u8 key_up = 1;//没有按下
    if(key_up && KEY按下)//上一次松开,这次按下
    {
        delay_ms(10);
        key_up=0;
        if(KEY确实按下){
            return KEY_VALUE;
        }
    }
    else if(KEY没有按下)
        key_up=1;
}

3.代码

//按键初始化函数
void KEY_Init(void)
{
    GPIO_InitTypeDef GPIO_Initure;
    
    __HAL_RCC_GPIOA_CLK_ENABLE();           //开启GPIOA时钟
    __HAL_RCC_GPIOC_CLK_ENABLE();           //开启GPIOC时钟
    __HAL_RCC_GPIOH_CLK_ENABLE();           //开启GPIOH时钟
    
    GPIO_Initure.Pin=GPIO_PIN_0;            //PA0
    GPIO_Initure.Mode=GPIO_MODE_INPUT;      //输入
    GPIO_Initure.Pull=GPIO_PULLDOWN;        //下拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);
    
    GPIO_Initure.Pin=GPIO_PIN_13;           //PC13
    GPIO_Initure.Mode=GPIO_MODE_INPUT;      //输入
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOC,&GPIO_Initure);
    
    GPIO_Initure.Pin=GPIO_PIN_2|GPIO_PIN_3; //PH2,3
    HAL_GPIO_Init(GPIOH,&GPIO_Initure);
}

//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,WKUP按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY2>WK_UP!!
u8 KEY_Scan(u8 mode)
{
    static u8 key_up=1;     //按键松开标志
    if(mode==1)key_up=1;    //支持连按
    if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1))
    {
        delay_ms(10);
        key_up=0;
        if(KEY0==0)       return KEY0_PRES;
        else if(KEY1==0)  return KEY1_PRES;
        else if(KEY2==0)  return KEY2_PRES;
        else if(WK_UP==1) return WKUP_PRES;          
    }else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1;
    return 0;   //无按键按下
}

int main(void)
{
   	u8 key;
	u8 led0sta=1,led1sta=1;		    //LED0,LED1的当前状态
    Cache_Enable();                 //打开L1-Cache
    HAL_Init();				        //初始化HAL库
    Stm32_Clock_Init(432,25,2,9);   //设置时钟,216Mhz 
    delay_init(216);                //延时初始化
	uart_init(115200);		        //串口初始化
    LED_Init();                     //初始化LED
    KEY_Init();                     //按键初始化
    while(1)
    {
		key=KEY_Scan(0); 		    //得到键值
	   	if(key)
		{						   
			switch(key)
			{				 
				case WKUP_PRES:	//控制LED0,LED1互斥点亮
					led1sta=!led1sta;
					led0sta=!led1sta;
					break;
				case KEY2_PRES:	//控制LED0翻转
					led0sta=!led0sta;
					break;
				case KEY1_PRES:	//控制LED1翻转	 
					led1sta=!led1sta;
					break;
				case KEY0_PRES:	//同时控制LED0,LED1翻转 
					led0sta=!led0sta;
					led1sta=!led1sta;
					break;
			}
			LED0(led0sta);		//控制LED0状态
			LED1(led1sta);		//控制LED1状态
		}else delay_ms(10);
	}
}

标签:__,HAL,IO,PIN,跑马灯,实验,版本,GPIO,RCC
来源: https://www.cnblogs.com/jiangyiming/p/15790851.html