1.寄存器介绍
STM32 的每个 IO 端口都有 7 个寄存器来控制。他们分别是:配置模式的 2 个 32 位的端口配置寄存器CRL 和 CRH;2 个 32 位的数据寄存器IDR 和 ODR;1 个 32 位的置位/复位寄存器BSRR;一个 16 位的复位寄存器 BRR;1 个 32 位的锁存寄存器LCKR;这里我们仅介绍常用 的几个寄存器,我们常用的 IO 端口寄存器只有 4 个:CRL、CRH、IDR、ODR,即端口配置寄存器和数据寄存器。
1.1端口配置寄存器(CRL和CRH)
CRL:每个 IO 端口的位占用 CRL 的 4 个位,高两位为 CNF,用来配置端口的模式,低两位为 MODE,用来配置端口的最大输出速度(CRH 的作用和 CRL 完全一样,只是 CRL 控制的是低 8 位输出口,而 CRH 控制的是高 8位输出口)。
比如我们要配置端口n为"推挽输出、最大输出速度为10M",那就是CNFn[1:0]=00,MODEn[1:0]=01。
关于IO模式(转自不文东)
(1) 浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX1
(2)带上拉输入_IPU——IO内部上拉电阻输入
(3)带下拉输入_IPD—— IO内部下拉电阻输入
(4) 模拟输入_AIN ——应用ADC模拟输入,或者低功耗下省电
(5)开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能
(6)推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的
(7)复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL,SDA)
(8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)
关于输出速度(转自wuyuzun)
I/O口输出模式下有三种输出速度可选(2MHz,10MHz,50MHz),这个速度是指I/O口驱动电路的响应速度;I/O管脚内部有多个响应不同的驱动电路,用户可以根据自己的需要选择合适的驱动电路。
高低频比较
高频驱动电路:输出频率高,噪音大,功耗高,电磁干扰强;
低频驱动电路:输出频率低,噪音小,功耗低,电磁干扰弱;提高系统EMI(电磁干扰)性能;总结:通过选择速度来选择不同的输出驱动模块,达到最佳的噪音控制和降低功耗的目的如果需要选择较高频率信号,但是却选择了低频驱动模块,很有可能会失真的输出信号;所以GPIO的引脚速度应与应用匹配。
举几个栗子:
1. 对于串口来说,加入最大波特率为115200,这样只需要用2M的GPIO的引脚速度就可以了,省电噪音又小;
2. 对于I2C接口,假如使用400 000波特率,若想把余量留大一些,2M的GPIO引脚速度或许是不够,这时可以选用10M的GPIO引脚速度;
3. 对于SPI接口,假如使用18M或9M的波特率,用10M的GPIO口也不够用了,需要选择呢50M的GPIO引脚速度
4. GPIO口设置为输入时,输出驱动电路与端口是断开的,所以这时配置输出速度是无意义的;
5. 在复位期间和刚复位后,复位功能未开启,I/O端口被配置成浮空输入模式;
6. 所有端口都有外部中断能力,当使用外部中断功能时,端口必须设置成输入模式;
7. GPIO的配置具有上锁的功能,当配置好GPIO后,可以通过程序锁住配置组合,知道下次芯片复位才能解开;
1.2.数据寄存器(IDR和ODR)
IDR 是一个端口输入数据寄存器,只用了低 16 位。该寄存器为只读寄存器,并且只能以16 位的形式读出. 要想知道某个 IO 口的状态,只要读这个寄存器,再看某个位的状态就可以了。
ODR 是一个端口输出数据寄存器,也只用了低 16 位。该寄存器为可读写,从该寄存器读出来的数据可以用于判断当前 IO 口的输出状态。而向该寄存器写数据,则可以控制某个 IO 口的输出电平。
1.3.BSRR和BRR寄存器
BSRR寄存器是端口位设置/清除寄存器,它和ODR寄存器类似。
BRR寄存器是端口位清除寄存器,与BSRR的高16位相同。
2.GPIO的代码实现(库函数)
GPIO配置函数主要在stm32f10x_gpio.h/c文件中,此外在使用GPIO之前要使能端口的时钟,相关函数在stm32f10x_rcc.h/c文件中。一般的端口配置中需要用到以下两个函数。
RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);//stm32f10x_rcc.h/c中
GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);//stm32f10x_gpio.h/c中
这里需要了解一些STM32的系统时钟。我们大多数情况下用到的是APB1和APB2的时钟,其中APB1上连接的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3、等等。APB2上连接的是高速外设,包括UART1、SPI1、Timer1、ADC1、ADC2、GPIO、第二功能IO口等。当我们使用外设的时候,一定要知道这个外设使用的是哪个时钟,并且使能该时钟,才能使用该外设。
2.1 RCC_APB2PeriphClockCmd( )函数
RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);函数的可选参数
//RCC_APB2Periph可选参数(以APB2为时钟源的外设):
#define RCC_APB2Periph_AFIO ((uint32_t)0x00000001)
/*与AFIO(alternate function io)时钟相关的寄存器
1、 事件控制寄存器(AFIO_EVCR)
2、 复用重映射和du调试I/O 配置寄存器(AFIO_MAPR)
3、 外部中zhi断配置寄存器1(AFIO_EXTICR1)
4、 外部中断配置寄存器2(AFIO_EXTICR2)
5、 外部中断配置寄存器3(AFIO_EXTICR3)
6、 外部中断配置寄存器4(AFIO_EXTICR4)
对这些寄存器进行操作前要使能该时钟*/
#define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008)
#define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010)
#define RCC_APB2Periph_GPIOD ((uint32_t)0x00000020)
#define RCC_APB2Periph_GPIOE ((uint32_t)0x00000040)
#define RCC_APB2Periph_GPIOF ((uint32_t)0x00000080)
#define RCC_APB2Periph_GPIOG ((uint32_t)0x00000100)
#define RCC_APB2Periph_ADC1 ((uint32_t)0x00000200)
#define RCC_APB2Periph_ADC2 ((uint32_t)0x00000400)
#define RCC_APB2Periph_TIM1 ((uint32_t)0x00000800)
#define RCC_APB2Periph_SPI1 ((uint32_t)0x00001000)
#define RCC_APB2Periph_TIM8 ((uint32_t)0x00002000)
#define RCC_APB2Periph_USART1 ((uint32_t)0x00004000)
#define RCC_APB2Periph_ADC3 ((uint32_t)0x00008000)
#define RCC_APB2Periph_TIM15 ((uint32_t)0x00010000)
#define RCC_APB2Periph_TIM16 ((uint32_t)0x00020000)
#define RCC_APB2Periph_TIM17 ((uint32_t)0x00040000)
#define RCC_APB2Periph_TIM9 ((uint32_t)0x00080000)
#define RCC_APB2Periph_TIM10 ((uint32_t)0x00100000)
#define RCC_APB2Periph_TIM11 ((uint32_t)0x00200000)
//NewState参数
#define IS_FUNCTIONAL_STATE(STATE) (((STATE) == DISABLE) || ((STATE) == ENABLE))
2.2 GPIO_Init( )函数
GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
GPIO_TypeDef是一个GPIO结构体,里边定义了GPIO的寄存器(可以理解为GPIO的属性,有了这些属性,它就是了GPIO),库文件使用该结构体来定义GPIO外设,这里做一个了解。在使用的时候很简单,其参数为GPIOA~G。这个时候,我们就创建了一个GPIO的空壳子,有了属性,接下来还要对其属性进行配置。
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} 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)
选择了GPIO之后,接下来就要对其参数进行配置了。GPIO_InitTypeDef是一个配置GPIO(寄存器)的结构体(配置CRL、CRH寄存器参数),这些参数包括具体的端口GPIO_Pin、端口速度GPIO_Speed、端口模式GPIO_Mode(GPIO端口的模式和速度见上文)。
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);函数的可选参数:
//GPIO_Pin
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */
//GPIO_Speed
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
//GPIO_MODE
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || ((MODE) == GPIO_Mode_IN_FLOATING) || \
((MODE) == GPIO_Mode_IPD) || ((MODE) == GPIO_Mode_IPU) || \
((MODE) == GPIO_Mode_Out_OD) || ((MODE) == GPIO_Mode_Out_PP) || \
((MODE) == GPIO_Mode_AF_OD) || ((MODE) == GPIO_Mode_AF_PP))
2.3 GPIO配置实例
IO初始化实例:
void GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);//使能PC端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //PC.13 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化GPIOC.13
}
2.4 几个常用的操作GPIO的函数
另外还有几个常用的函数,主要用来操作几个关于端口数据的寄存器(BSRR、BRR、ODR、IDR)。从下列函数的名字就能看出来,前四个是写IO数据、后四个是读IO数据。需要说明的是,要清楚操作对象是"port "还是"pin",即清楚“写寄存器”和写“寄存器的某一位的区别”——下边几个函数凡是带bits的都是操作寄存器的某一位,对应的就是一个IO引脚(pin),否则就是操作寄存器(32的寄存器,但是只用了16位【一组GPIO有16个端口】,所以定义的参数是uint16_t 型),即对应的是一组GPIO(port )。
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//端口置1
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//端口清0
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);//向端口写BitVal
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);//向GPIOx写PortVal
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//读端口值(bit)
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);//读GPIO组值(16位)
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
/**
* @brief Sets the selected data port bits.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @param GPIO_Pin: specifies the port bits to be written.
* This parameter can be any combination of GPIO_Pin_x where x can be (0..15).
* @retval None
*/
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;
}
/**
* @brief Clears the selected data port bits.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @param GPIO_Pin: specifies the port bits to be written.
* This parameter can be any combination of GPIO_Pin_x where x can be (0..15).
* @retval None
*/
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;
}
/**
* @brief Sets or clears the selected data port bit.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @param GPIO_Pin: specifies the port bit to be written.
* This parameter can be one of GPIO_Pin_x where x can be (0..15).
* @param BitVal: specifies the value to be written to the selected bit.
* This parameter can be one of the BitAction enum values:
* @arg Bit_RESET: to clear the port pin
* @arg Bit_SET: to set the port pin
* @retval None
*/
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_BIT_ACTION(BitVal));
if (BitVal != Bit_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BRR = GPIO_Pin;
}
}
/**
* @brief Writes data to the specified GPIO data port.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @param PortVal: specifies the value to be written to the port output data register.
* @retval None
*/
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
GPIOx->ODR = PortVal;
}
/**
* @brief Reads the specified input port pin.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @param GPIO_Pin: specifies the port bit to read.
* This parameter can be GPIO_Pin_x where x can be (0..15).
* @retval The input port pin value.
*/
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t bitstatus = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET)
{
bitstatus = (uint8_t)Bit_SET;
}
else
{
bitstatus = (uint8_t)Bit_RESET;
}
return bitstatus;
}
/**
* @brief Reads the specified GPIO input data port.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @retval GPIO input data port value.
*/
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
return ((uint16_t)GPIOx->IDR);
}
/**
* @brief Reads the specified output data port bit.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @param GPIO_Pin: specifies the port bit to read.
* This parameter can be GPIO_Pin_x where x can be (0..15).
* @retval The output port pin value.
*/
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t bitstatus = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
if ((GPIOx->ODR & GPIO_Pin) != (uint32_t)Bit_RESET)
{
bitstatus = (uint8_t)Bit_SET;
}
else
{
bitstatus = (uint8_t)Bit_RESET;
}
return bitstatus;
}
/**
* @brief Reads the specified GPIO output data port.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @retval GPIO output data port value.
*/
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
return ((uint16_t)GPIOx->ODR);
}
3.小结
3.1 理论总结
1.GPIO在APB2时钟下,使用前要使能该时钟。
2.GPIO的基本使用过程中的几个常用的寄存器:
端口配置寄存器(CRL和CRH)用来配置IO引脚(pin)的模式、速度。
数据寄存器(IDR和ODR)和端口置位/清除位寄存器(BSRR和BRR)用来设置pin、port的值和读取pin、port的值。
3.2 使用总结
1.调用RCC_APB2PeriphClockCmd( )函数使能IO时钟。
2.定义一个 GPIO_InitTypeDef 型结构体变量,并配置要初始化的IO参数。
3.通过GPIO_Init( )函数使用配置好的GPIO_InitTypeDef 类型的变量初始化IO。
4.使用2.4节的函数操作IO。