STM32笔记 GPIO介绍及IO口操作 STM32F0 利用C语言位域实现仿位带操作

文章目录

GPIO简介

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

端口位基本结构:

在这里插入图片描述

TTL肖特基触发器作用:将相对缓慢变化的模拟信号变成矩形信号,便于后面读取。
(这里有一个阈值电压的概念,比如从低到高达到多少才会导通,从高到底多少才会关闭)

工作方式

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口使用),给内部外设使用的推挽,开漏输出,此时端口必须配置为复用功能输出模式。

而每个 GPIO 端口是通过 CNF[1:0]和 MODE[1:0]配置为 8 种模式中的一种,
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 函数实现的:

扫描二维码关注公众号,回复: 12332104 查看本文章
uint8_tGPIO_ReadInputDataBit(GPIO_TypeDef*GPIOx,uint16_tGPIO_Pin)
  • 1

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

GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);

  • 1
  • 2

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

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

void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
  • 1

该函数一般用来对一个 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

  • 1
  • 2

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

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

其中,两个参数分别为:配置引脚组(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);//设置该位为低电平
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

八种模式在 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; 


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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

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


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

uint_8t GPIO_ReadIputDataBit(GPIO_TypeDef*GPIOx,uint16_t GPIO_Pin)
  • 1

控制ODR:

void GPIO_Write(GPIO_TypeDef*GPIOx,uint16_t PortVal)
  • 1

控制BSRR与BRR:

void GPIO_SetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_Pin)

void GPIO_ResetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_Pin)
  • 1
  • 2
  • 3

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输出高 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

一般用寄存器控制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 

  • 1
  • 2

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

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

  • 1
  • 2

“*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口为高电平
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

附录:C语言相关

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

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

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

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

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

extern u16 USART_RX_STA; 
  • 1

此时,在其它文件会有:

u16 USART_RX_STA;
  • 1

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

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

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

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

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

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

偏移地址与绝对地址

偏移地址计算绝对地址非常简单, 比如要计算 GPIOC 端口 CRH 寄存器的绝对地址。 可以先查看 GPIOC的起始地址, 从图 8- 6 可以确定 GPIOC 的起始地址为 0x40011000, CRH 寄存器的偏移地址为0x04, 因此, GPIOC 的 CRH 寄存器的绝对地址通过计算, 即 0x40011000+0x04, 就可以得出为 0x40011004。

 

最近开发的时候,用到了STM32F030F4P6型号的单片机,它只有20个引脚,价格非常便宜,但是功能齐全;定时器、外部中断、串口、IIC、SPI、DMA和WWDG等等,应用尽有,非常适合用来做小设备。可是有个问题是,它是Cortex-M0内核的,不像M3,M4内核一样,可以支持位带操作(就是一位一位地操作,像80C51单片机一样),这就给程序移植或者开发带来了一点点小麻烦,因此我就利用C语言结构的位段操作,实现了个访位带操作,只是在效率可能会稍逊于真正的位带操作,但是代码上可以兼容,基本上可以应用于任何一款处理器。希望能够帮到大家。

2、位带操作基本知识#

 关于真正的位带操作,网上有不少的资料,写得也很详细,在这里我只是简单说一下我的理解。另,不理解真正的位带操作,也不影响对本文的理解,因本文跟位带操作没有任何关系,只是仿仿罢了,不能当真。如果不想了解货真价实的位带操作,此节可直接忽略。

如果不使用位带操作,我们操作一个次数据时,就要动32位(STM32是32位的),做一个不恰当的比喻,这就相当于我们坐在一辆有32节车厢的火车上,但是辆火车只有一个门,如果我们要查看这火车中乘客的信息,或者是乘客想下车,必须从那一个门进出,如下图1。

图1 只有1个车门的32节火车

 而如果我们有了位带操作,就相当于,给这辆32节车厢的火车装上了32个车门,这样一来,想查看哪个乘客的信息,或都那个乘客要下车,都可以迅速地从指定的车门下车。如下图2所示。

图2 有32个车门的32节火车

有了32个门后,速度就快多了,但是硬件成本肯定要起来了,这就是为什么STM32F030系列没有位带操作的原因,就是它的成本低。

3、C言语结构体位段操作#

此节主要讲述C语言结构体的基础知识,如果有C言高手,请无视此节。在C语言中,对结构体的声明,有一个位域,它可以控制,此结构体中的成员占几个位,关于它的使用,有如下代码:

复制代码

 1 typedef struct _16_Bits_Struct
 2 {
 3     u16 bit0 : 1;//占一个字节
 4     u16 bit1 : 1;
 5     u16 bit2 : 1;
 6     u16 bit3 : 1;
 7     u16 bit4 : 1;
 8     u16 bit5 : 1;
 9     u16 bit6 : 1;
10     u16 bit7 : 1;
11     u16 bit8 : 1;
12     u16 bit9 : 1;
13     u16 bit10 : 1;
14     u16 bit11 : 2;//占两个字节
15     u16 bit12 : 3;//占三个字节
16 } _16_Bits_Struct;

复制代码

上面的_16_Bits_Struct结构体类型共占用2个字节,即16位,但它的13个成员变量所占用的位数不全都一样,通过“:”后面的数字可决定它占几位。代码如下,操作一个此结构体类型的位。

复制代码

 1     _16_Bits_Struct _16_bits;
 2     unsigned short _16bits_data;
 3     memset(&_16_bits, 0, sizeof(_16_Bits_Struct));//将其内存清0
 4 
 5     _16_bits.bit2 = 1;
 6     _16_bits.bit5 = 1;
 7     _16_bits.bit8 = 5;
 8 
 9     _16bits_data = *((unsigned short*)(&_16_bits));
10 
11     printf("_16bits_data = %0xH\n", _16bits_data);

复制代码

其输出结果为:

从结果中可以看出,在结构体,从bit0~bit12依次是从低位到高位。在上面代码的第7行,虽然给bit8写入了5,但是因为它只占一位,所以只取了5(D)=0101(B)的最低位,即为1。因此最终结果为124H,它的内存结构如下图3所示。

图3 结构体内存结构图 

4、STM32F030仿位带操作#

有了上面结构体位段操作的基础后,离实现仿STM32F030的位带操作就很近了。我打算做一个最简单的,实现对GPIO的某一个引脚操作,达到亮灭LED的功能。

从STM32F030的参考手册中,找到GPIO的输出寄存器ODR,看到它的基本信息如下图4所示,这个寄存器是可读可写的(RW),因此只要作我们给这个寄存器其中的一个位写入1,那么这个引脚就会输出1,写0就输出0(当然前提条件是你把它配置成输出模式,并且使能了它的时钟)。

图4 GPIO的ODR寄存器结构图

我是如何对这个寄存器一次只操作一位的呢,且看下面代码再来解释。

复制代码

 1 typedef struct _16_Bits_Struct
 2 {
 3     u16 bit0 : 1;
 4     u16 bit1 : 1;
 5     u16 bit2 : 1;
 6     u16 bit3 : 1;
 7     u16 bit4 : 1;
 8     u16 bit5 : 1;
 9     u16 bit6 : 1;
10     u16 bit7 : 1;
11     u16 bit8 : 1;
12     u16 bit9 : 1;
13     u16 bit10 : 1;
14     u16 bit11 : 1;
15     u16 bit12 : 1;
16     u16 bit13 : 1;
17     u16 bit14 : 1;
18     u16 bit15 : 1;
19 } Bits_16_TypeDef;
20 #define LED_GPIO_CLK   RCC_AHBPeriph_GPIOA 
21 #define LED_PORT       GPIOA
22 #define LED_PIN        GPIO_Pin_4
23 //使用结构体的位段操作, 兼容Cortex-M3的位带操作.
24 #define LED_PORT_OUT    ((Bits_16_TypeDef *)(&(LED_PORT->ODR)))
25 #define LED             (LED_PORT_OUT->bit4) 

复制代码

 我的硬件连接是:LED接GPIOA的4引脚上。1~19行在前面的结构体知识中已经做出了解释了,20~22只是为了代码更好移植做的一些宏定义,可不要。24行就比较关键了:先取出GPIOA->ODR的地址,然后再将它强制转化为Bits_16_TypeDef * 类型(注意,是指针类型)。转化为此类型后,ODR就有位域的特性了,因此就可以对它进行位操作。25行就是将接在PA.4的LED定义为GPIOA->ODR的第4位。

有了这样的操作后,想要我们的LED亮灭,就很容易了,代码如下。

1 LED = 0;//LED亮
2 LED = 1;//LED灭

 因硬件的连接不同,效果可能是反的。看到这里,是不是觉得操作起来很简单呢。

完整的代码如下:

led.c

led.h

mytype.h

一、前言

平常使用STM32F1和F4,程序不少参考的正点原子的教程,代码里都包含头文件他们的sys.h,这里面主要是实现了stm32的位带操作,位带是啥,博主也没深入研究,但是就是能直接读写GPIO口的某一位,例如:

PAout(1) = 1;//GPIOA Pin1 输出高电平

 if(PAin(2)==1);//判断GPIOA Pin2 是否为高电平

用起来清晰直观,但是最近使用STM32F0,才发现F0没有位带,自然正点原子sys.h里位带操作就没法移植到F0了,那是不是没办法了呢,百度一下发现可以利用位域来实现仿位带操作,下面就来写一个STMF0的sys.h,废话不多说,直接看下面代码。 

 

二、代码实现

代码分三步,可以新建名为sys.h的头文件,放在里面:

(1)因为GPIOX->ODR和GPIOX->IDR寄存器都是低16位有效,所以先定义一个结构体位域,大小为2字节(16位),每一位表示一个IO口。而且要用volatile 防止被编译器优化,如果不加volatile ,像PAout(0)=1;PAout(1)=1;原本先后置1,可能会被优化成GPIOA->ODR=0x03,一起置1了,这可是致命错误,在模拟I2C、SPI时序的时候会出错。

//定义GPIO结构体位域
typedef struct
{
   volatile unsigned short bit0 : 1;
   volatile unsigned short bit1 : 1;
   volatile unsigned short bit2 : 1;
   volatile unsigned short bit3 : 1;
   volatile unsigned short bit4 : 1;
   volatile unsigned short bit5 : 1;
   volatile unsigned short bit6 : 1;
   volatile unsigned short bit7 : 1;
   volatile unsigned short bit8 : 1;
   volatile unsigned short bit9 : 1;
   volatile unsigned short bit10 : 1;
   volatile unsigned short bit11 : 1;
   volatile unsigned short bit12 : 1;
   volatile unsigned short bit13 : 1;
   volatile unsigned short bit14 : 1;
   volatile unsigned short bit15 : 1;
 
} GPIO_Bit_TypeDef;

 

(2)将 寄存器地址&(GPIOX->ODR)和& (GPIOX->IDR)强制转换为GPIO_Bit_TypeDef* 指针,并利用宏定义替换为标识符PORTX_OUT或PORTX_IN

#define PORTA_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOA->ODR)))
#define PORTA_IN     ((GPIO_Bit_TypeDef *)(&(GPIOA->IDR)))
#define PORTB_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOB->ODR)))
#define PORTB_IN     ((GPIO_Bit_TypeDef *)(&(GPIOB->IDR)))
#define PORTC_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOC->ODR)))
#define PORTC_IN     ((GPIO_Bit_TypeDef *)(&(GPIOC->IDR)))
#define PORTD_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOD->ODR)))
#define PORTD_IN     ((GPIO_Bit_TypeDef *)(&(GPIOD->IDR)))
#define PORTE_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOE->ODR)))
#define PORTE_IN     ((GPIO_Bit_TypeDef *)(&(GPIOE->IDR)))
#define PORTF_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOF->ODR)))
#define PORTF_IN     ((GPIO_Bit_TypeDef *)(&(GPIOF->IDR)))
#define PORTG_OUT    ((GPIO_Bit_TypeDef *)(&(GPIOG->ODR)))
#define PORTG_IN     ((GPIO_Bit_TypeDef *)(&(GPIOG->IDR)))

 

(3)利用宏函数和##字符串拼接,实现IO口操作,函数名称和正点原子sys.h里的一致,方便其他程序移植。这里提一下“##”拼接,这是一种宏的特殊符号,##表示把左右字符串拼接在一起

例如:#define GPIO(io) GPIO##io  则 GPIO(A) 会被替换成 GPIOA

在下面这里如果输入n为4,就会变成 bit4,n为8,就会变成bit8

#define PAout(n) (PORTA_OUT->bit##n)
#define PAin(n)  (PORTA_IN->bit##n)
#define PBout(n) (PORTB_OUT->bit##n)
#define PBin(n)  (PORTB_IN->bit##n)
#define PCout(n) (PORTC_OUT->bit##n)
#define PCin(n)  (PORTC_IN->bit##n)
#define PDout(n) (PORTD_OUT->bit##n)
#define PDin(n)  (PORTD_IN->bit##n)
#define PEout(n) (PORTE_OUT->bit##n)
#define PEin(n)  (PORTE_IN->bit##n)
#define PFout(n) (PORTF_OUT->bit##n)
#define PFin(n)  (PORTF_IN->bit##n)
#define PGout(n) (PORTG_OUT->bit##n)
#define PGin(n)  (PORTG_IN->bit##n)

 

三、例程

实际使用起来就很简单了,和在F1、F4上的一样,以点灯为例,初始化GPIO后,直接赋值就行了

#define LED PAout(4)
void LED_Init(void);
 
//点灯测试
int main(void)
{
	LED_Init();
	delay_init();
  while (1)
  {
		LED=1;
		delay_ms(500);
		LED=0;
		delay_ms(500);
  }
}
 
void LED_Init(void)
{
 GPIO_InitTypeDef  GPIO_InitStructure;
 	
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);	 //使能PA端口时钟	
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;				 //LED0-->PB.5 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; 		 //输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
 GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽
 GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_NOPULL;//无上下拉
 GPIO_Init(GPIOA, &GPIO_InitStructure);	
	//
 GPIO_SetBits(GPIOA,GPIO_Pin_4);						 //PA.4 输出高
 
}

 

 

猜你喜欢

转载自blog.csdn.net/qq_36958104/article/details/111983036