赞
踩
提示:今天是STM32入门学习的第二天,重点学习了GPIO的工作原理,通过库函数、寄存器、位操作来实现跑马灯。
目录
每个GPIO端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。
根据数据手册中列出的每个I/O端口的特定硬件特征, GPIO端口的每个位可以由软件分别配置 成多种模式。
4种输入模式
每个I/O端口位可以自由编程,然而I/0端口寄存器必须按32位字被访问(不允许半字或字节访 问)。GPIOx_BSRR和GPIOx_BRR寄存器允许对任何GPIO寄存器的读/更改的独立访问;这 样,在读和更改访问之间产生IRQ时不会发生危险。 下图给出了一个I/O端口位的基本结构
3种最大翻转速度
GPIO的输入工作模式1—输入浮空模式
GPIO的输入工作模式2—输入上拉模式
GPIO的输入工作模式3—输入下拉模式
GPIO的输入工作模式4—模拟模式
GPIO的输出工作模式1—开漏输出模式
GPIO的输出工作模式2—开漏复用输出模式
GPIO的输出工作模式3—推挽输出模式
GPIO的输出工作模式4—推挽复用输出模式
推挽输出:可以输出强高低电平,连接数字器件
开漏输出:只可以输出强低电平,高电平得靠外部电阻拉高。输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行. 适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)
两个32位配置寄存器(GPIOx_CRL ,GPIOx_CRH) ,
两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),
一个32位置位/ 复位寄存器(GPIOx_BSRR),
一个16位复位寄存器(GPIOx_BRR),
一个32位锁定寄存器(GPIOx_LCKR)。
每个I/O端口位可以自由编程,然而I/O端口寄存器必须按32位字被访问(不允许半字或字节访问)
例如:STM32F103ZET6:一共有7组IO口,每组IO口有16个IO,一共16X7=112个IO。GPIOA,GPIOB---GPIOG。每组IO口含下面7个寄存器,也就是7个寄存器,一共可以控制一组GPIO的16个IO口。
32位寄存器配置每个IO口要四位,所以只能配置八个IO口,而有十六个IO口,故需要两个端口配置寄存器。CRL控制0—7的IO口,CRH控制8—15个IO口。
2.1 端口配置低寄存器(GPIOx_CRL)
观察使用时先确定MODY是在输出还是输入,若为00则为输入,此时在看CNYF是输入的那个状态,其中10有上拉和下拉是在ODR这个寄存器里面。
2.2 端口配置高寄存器(GPIOx_CRH)
2.3 端口输入数据寄存器(GPIOx_IDR)
2.4 端口输出数据寄存器(GPIOx_ODR)
输出为1则是高电平,输出为0则是低电平。和输出寄存器一样,主要是这个寄存器控制着上拉还是下拉电阻。其中0为下拉1为上拉。
2.5 端口位设置/清除寄存器(GPIOx_BSRR)
2.6 端口位清除寄存器(GPIOx_BRR)
设置那个IO口就是那个IO口。不过经常以这个寄存器清除低位,以2.5那个寄存器清除高位。
GPIO输出方式:推挽输出,可以输出强高低电平。
库函数的介绍:操作IO口必须引入的源文件和头文件。
头文件:stm32f10x_gpio.h
源文件:stm32f10x_gpio.c
1个初始化函数:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
2个读取输入电平函数:
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
2个读取输出电平函数:
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
4个设置输出电平函数:
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);输出高
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);输出低
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
1个初始化函数:此函数调用的时候要输入两个参数,对于第一个参数来说进入是一个结构体里面存放了IO口的七个寄存器,而范围是GPIOA~GPIOG 。第二个参数是来初始化的里面是如下方框里的一个结构体,后面跟着一个指针变量。
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
作用:初始化一个或者多个IO口(同一组)的工作方式和速度。
该函数主要是操作GPIO_CRL(CRH)寄存器,在上拉或者下拉的时候有设置BSRR或者BRR寄存器
GPIOx: GPIOA~GPIOG
- typedef struct
- {
- uint16_t GPIO_Pin; //指定那一组中要初始化的IO口
- GPIOSpeed_TypeDef GPIO_Speed; //设置IO口输出速度
- GPIOMode_TypeDef GPIO_Mode; //设置工作模式:8种中的一个
- }GPIO_InitTypeDef;
注意:外设(包括GPIO)在使用之前,几乎都要先使能对应的时钟。
GPIO_Init函数初始化样:
-
- GPIO_InitTypeDef GPIO_InitStructure;
-
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
- GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
2个读取输入电平函数:
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:读取某个GPIO的输入电平。实际操作的是GPIOx_IDR寄存器。
例如:GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输入电平
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
作用:读取某组GPIO的输入电平。实际操作的是GPIOx_IDR寄存器。
例如:GPIO_ReadInputData(GPIOA);//读取GPIOA组中所有io口输入电平
4个设置输出电平函数:
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:设置某个IO口输出为高电平(1)。实际操作BSRR寄存器
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:设置某个IO口输出为低电平(0)。实际操作的BRR寄存器。
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
这两个函数不常用,也是用来设置IO口输出电平。
使能IO口时钟。调用函数RCC_APB2PeriphColckCmd();
不同的IO组,调用的时钟使能函数不一样。
- #include"led.h"
- #include"stm32f10x.h"
-
- void LED_Init(void)
- {
- GPIO_InitTypeDef GPIO_InitStructture;
-
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);//ʹÄÜʱÖÓ
-
- GPIO_InitStructture.GPIO_Mode=GPIO_Mode_Out_PP;
- GPIO_InitStructture.GPIO_Pin=GPIO_Pin_5;
- GPIO_InitStructture.GPIO_Speed=GPIO_Speed_50MHz;
-
- GPIO_Init(GPIOB,&GPIO_InitStructture);
- GPIO_SetBits(GPIOB,GPIO_Pin_5);
-
- GPIO_InitStructture.GPIO_Mode=GPIO_Mode_Out_PP;
- GPIO_InitStructture.GPIO_Pin=GPIO_Pin_5;
- GPIO_InitStructture.GPIO_Speed=GPIO_Speed_50MHz;
-
- GPIO_Init(GPIOE,&GPIO_InitStructture);
- GPIO_SetBits(GPIOE,GPIO_Pin_5);
- }
- #include "stm32f10x.h"
- #include "delay.h"
- #include "led.h"
-
-
- int main(void)
- {
-
- delay_init();
- LED_Init();
-
- while(1){
-
- GPIO_SetBits(GPIOB,GPIO_Pin_5);
- GPIO_SetBits(GPIOE,GPIO_Pin_5);
- delay_ms(500);
-
- GPIO_ResetBits(GPIOB,GPIO_Pin_5);
- GPIO_ResetBits(GPIOB,GPIO_Pin_5);
- delay_ms(500);
- }
- }
在了解下GPIO相关的寄存器,在库函数里操作寄存器。
两个32位配置寄存器(GPIOx_CRL ,GPIOx_CRH) ,
两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),
一个32位置位/ 复位寄存器(GPIOx_BSRR),
一个16位复位寄存器(GPIOx_BRR),
一个32位锁定寄存器(GPIOx_LCKR)。
- #include"led.h"
- #include"stm32f10x.h"
-
- void LED_Init(void)
- {
- RCC->APB2ENR!=1<<3;//寄存器只要第三位,其他不改变
- RCC->APB2ENR!=1<<6;//寄存器第六位
-
- GPIOB->CRL&=0XFF0FFFFF;//GPIOB.5
- GPIOB->CRL|=0X00300000;//先清零在赋值
-
- GPIO->ODR=1<<5;
-
-
- GPIOB->CRL&=0XFF0FFFFF;//GPIOB.6
- GPIOB->CRL|=0X00300000;//先清零在赋值
-
- GPIO->ODR=1<<5;
-
- }
- #include"stm32f10x.h"
- #include"delay.h"
- #include"led.h"
-
- int main(void)
- {
- delay_init();
- LED_Init();
-
- while(1){
- GPIOB->|=1<<5;
- GPIOE->|=1<<5;
- delay_init(500);
-
- GPIOB->ODR=~(1<<5);
- delay_init(500);
- }
- }
位操作:支持了位带 中,有两个区中实现了位带。其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设 区的最低 1MB 范围。这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自 己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访 问这些字时,就可以达到访问原始比特的目的。
也可以理解成把每个比特膨胀为一个32位的字,当访问这些字的时候就达到了访问比特的目的,比如说BSRR寄存器有32个位,那么可以映射到32个地址上,我们去访问(读-改-写)这32个地址就达到访问32个比特的目的。
举例如下:
映射关系:
位带区:支持位带操作的地址区
位带别名:对别名地址的访问最终作用到位带区的访问上(注意:这中间有一个地址映射过程)
手把手写跑马灯实验-位带操作。
使能IO口时钟。调用函数RCC_APB2PeriphColckCmd();
初始化IO口模式。调用函数GPIO_Init();
- #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))
- //IO口地址映射
- #define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
- #define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
- #define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
- #define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
- #define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
- #define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
- #define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
- //IO口操作,只对单一的IO口!
- //确保n的值小于16!
- #define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
- #define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
-
- #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
- #define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
- …
- #define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
- #define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
-
- #define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
- #define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
- #include "stm32f10x.h"
- #include "led.h"
- #include "delay.h"
-
- int main(void)
- {
-
- delay_init();
-
- LED_Init();
-
- while(1){
-
- PBout(5)=1;
- PEout(5)=1;
-
- delay_ms(500);
-
- PBout(5)=0;
- PEout(5)=0;
-
- delay_ms(500);
-
- }
-
- }
1.蜂鸣器简介
蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。 我们使用的开发板板载的蜂鸣器是电磁式的有源蜂鸣器。这里的有源不是指电源的“源”,而是指有没有自带震荡电路,有源蜂鸣器自带了震荡电路, 一通电就会发声;无源蜂鸣器则没有自带震荡电路,必须外部提供 2~5Khz 左右的方波驱动, 才能发声。
STM32 的单个 IO 最大可以提供 25mA 电流(来自数据手册),而蜂鸣器的驱动电流是 30mA 左右,两 者十分相近,但是全盘考虑,STM32 整个芯片的电流,最大也就 150mA,如果用 IO 口直接驱 动蜂鸣器,其他地方用电就得省着点了…所以,我们不用 STM32 的 IO 直接驱动蜂鸣器,而是 通过三极管扩流后再驱动蜂鸣器,这样 STM32 的 IO 只需要提供不到 1mA 的电流就足够了。
2.硬件设计
1)指示灯 DS0
2)蜂鸣器
NPN 三极管(S8050)来驱动蜂鸣器,R60 主要用于防止蜂鸣器的误发 声。当 PB.8 输出高电平的时候,蜂鸣器将发声,当 PB.8 输出低电平的时候,蜂鸣器停止发声。
3.软件介绍
- #include "beep.h"
- //初始化 PB8 为输出口.并使能这个口的时钟
- //LED IO 初始化
- void BEEP_Init(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
- //使能 GPIOB 端口时钟
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //BEEP-->GPIOB.8 端口配置
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度为 50MHz
- GPIO_Init(GPIOB, &GPIO_InitStructure); //根据参数初始化 GPIOB.8
- GPIO_ResetBits(GPIOB,GPIO_Pin_8); //输出 0,关闭蜂鸣器输出
- }
void BEEP_Init(void),该函数的作用就是使能 PORTB 的时钟, 同时配置 PB8 为推挽输出。这里的初始化内容跟跑马灯实验几乎是一样的,这里就不做 深入的讲解。
- 在 main.c 里面编写如下代码:
- #include "sys.h"
- #include "delay.h"
- #include "led.h"
- #include "beep.h"
- //ALIENTEK 战舰 STM32 开发板实验 2
- //蜂鸣器实验
- int main(void)
- {
- delay_init(); //延时函数初始化
- LED_Init(); //初始化与 LED 连接的硬件接口
- BEEP_Init(); //初始化蜂鸣器端口
- while(1)
- { LED0=0;
- BEEP=0;
- delay_ms(300);
- LED0=1;
- BEEP=1;
- delay_ms(300);
- }
- }
1.STM32 IO 口简介
STM32F1 的 IO 口已经有了比较详细的介绍。STM32F1 的 IO 口做输入使用的时候,是通过调用函数 GPIO_ReadInputDataBit()来读取 IO 口的状态的。就可以开始我们的代码编写了。 这一章,我们将通过 ALIENTEK 战舰 STM32 开发板上载有的 4 个按钮(WK_UP、KEY0、 KEY1 和 KEY2),来控制板上的 2 个 LED(DS0 和 DS1)和蜂鸣器,其中 WK_UP 控制蜂鸣器, 按一次叫,再按一次停;KEY2 控制 DS0,按一次亮,再按一次灭;KEY1 控制 DS1,效果同 KEY2;KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。
2.硬件设计
本实验用到的硬件资源有:
1) 指示灯 DS0、DS1
2) 蜂鸣器
3) 4 个按键:KEY0、KEY1、KEY2、和 WK_UP。
KEY0、KEY1 和 KEY2 是低电平有效的,而 WK_UP 是高电平有效的, 并且外部都没有上下拉电阻,所以,需要在 STM32 内部设置上下拉。
3.软件设计
按键实验工程引入了 key.c 文件以及头文件 key.h。下面我们首先打开 key.c 文件,代码如下:
- #include "key.h"
- #include "sys.h"
- #include "delay.h"
- //按键初始化函数
- void KEY_Init(void) //IO 初始化
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|
- RCC_APB2Periph_GPIOE,ENABLE); //使能 PORTA,PORTE 时钟
-
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;//GPIOE.2~4
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
- GPIO_Init(GPIOE, &GPIO_InitStructure); //初始化 GPIOE2,3,4
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //初始化 WK_UP-->GPIOA.0
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 设置成输入,下拉
- GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.0
- }
- //按键处理函数
- //返回按键值
- //mode:0,不支持连续按;1,支持连续按;
- //0,没有任何按键按下;1,KEY0 按下;2,KEY1 按下;3,KEY2 按下 ;4,KEY3 按下 WK_UP
- //注意此函数有响应优先级,KEY0>KEY1>KEY2>KEY3!!
- u8 KEY_Scan(u8 mode)
- {
- static u8 key_up=1; //按键按松开标志
- if(mode)key_up=1; //支持连按
- if(key_up&&(KEY0==0||KEY1==0||KEY2==0||KEY3==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(KEY3==1)return WKUP_PRES;
- }else if(KEY0==1&&KEY1==1&&KEY2==1&&KEY3==0)key_up=1;
- return 0; // 无按键按下
- }
接下来我们看看头文件 key.h 里面的代码:
- #ifndef __KEY_H
- #define __KEY_H
- #include "sys.h"
- #define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)//读取按键 0
- #define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)//读取按键 1
- #define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)//读取按键 2
- #define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//读取按键 3(WK_UP)
- #define KEY0_PRES 1 //KEY0 按下
- #define KEY1_PRES 2 //KEY1 按下
- #define KEY2_PRES 3 //KEY2 按下
- #define WKUP_PRES 4 //WK_UP 按下(即 WK_UP/WK_UP)
- void KEY_Init(void); //IO 初始化
- u8 KEY_Scan(u8); //按键扫描函数
- #endif
位操作也可以:
- #define KEY0 PEin(4) //PE4
- #define KEY1 PEin(3) //PE3
- #define KEY2 PEin(2) //PE2
- #define WK_UP PAin(0) //PA0 WK_UP
主函数:
- #include "led.h"
- #include "delay.h"
- #include "key.h"
- #include "sys.h"
- #include "beep.h"
- //ALIENTEK 战舰 STM32 开发板实验 3
- //按键输入实验
- int main(void)
- {
-
- u8 key;
- delay_init(); //延时函数初始化
- LED_Init(); //LED 端口初始化
- KEY_Init(); //初始化与按键连接的硬件接口
- BEEP_Init(); //初始化蜂鸣器端口
- LED0=0; //先点亮红灯
- while(1)
- {
- key =KEY_Scan(0); //得到键值
- if(key)
- { switch(t)
- { case WKUP_PRES: //控制蜂鸣器
- BEEP=!BEEP;break;
- case KEY2_PRES: //控制 LED0 翻转
- LED0=!LED0;break;
- case KEY1_PRES: //控制 LED1 翻转
- LED1=!LED1;break;
- case KEY0_PRES: //同时控制 LED0,LED1 翻转
- LED0=!LED0;
- LED1=!LED1;break;
- }
- }else delay_ms(10);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。