GPIO
什么是GPIO
General Purpose Input Output (通用输入/输出)简称为GPIO。
GPIO的英文全称General-Purpose Input /Output Ports,中文意思是通用I/O端口,在嵌入式系统中,经常需要控制许多结构简单的外部设备或者电路,这些设备有的需要通过CPU控制,有的需要为CPU提供输入信号。并且,许多设备或电路只要求有开/关两种状态就够了,比如LED的亮与灭。对这些设备的控制,使用传统的串口或者并口就显得比较复杂,所以,在嵌入式微处理器上通常提供了一种“通用可编程I/O端口”,也就是GPIO。一个GPIO端口需要多个寄存器,一个做控制用的“通用IO端口控制寄存器”,还有一个是存放数据的“通用I/O端口数据寄存器”。数据寄存器的每一位是和GPIO的硬件引脚对应的,而数据的传递方向是通过控制寄存器设置的,通过控制寄存器可以设置每一位引脚的数据流向。
IO资源
STM32F10x系列最多有7个16位并行I/O口:
PA、PB、PC、PD、PE、PF、PG
都是复用引脚,最少有2种功能,最多有6种功能
本人使用的芯片型号为STM32F103RCT6,如右图所示此芯片包含PA、PB、PC、PD共4个并行GPIO。
IO口的工作模式
STM32的输入输出管脚有以下8种可能的配置:(4输入+2输出+2复用输出)
- 浮空输入_IN_FLOATING
- 带上拉输入_IPU
- 带下拉输入_IPD
- 模拟输入_AIN
- 开漏输出_OUT_OD
- 推挽输出_OUT_PP
- 复用功能的推挽输出_AF_PP
- 复用功能的开漏输出_AF_OD
详细介绍:
-
浮空输入_IN_FLOATING
浮空输入一般多用于外部按键输入,在浮空输入的状态下,IO口的电平状态是不确定的,完全由外部输入决定;如果在该引脚悬空的,读取端口的电平状态是不确定的。
-
带上拉输入_IPU 内部有上电阻
-
带下拉输入_IPD 内部有下拉电阻
-
模拟输入_AIN 应用ADC模拟输入,或者低功耗下省电
-
开漏输出_OUT_OD
输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)。
开漏形式的电路有以下几个特点:
i、利用外部电路的驱动能力,减少IC内部的驱动。当IC内部MOSFET导通时,驱动电流是从外部的VCC流经上拉电阻、MOSFET到GND。IC内部仅需很小的栅极驱动电流。
ii、一般来说,开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。比如加上上拉电阻就可以提供TTL/CMOS电平输出等。(上拉电阻的阻值决定了逻辑电平转换的速度。阻值越大,速度越低功耗越小,所以负载电阻的选择要兼顾功耗和速度。)
iii、开漏输出提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。
vi、可以将多个开漏输出连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系,即“线与”。可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑0,相当于接地,与之并联的回路“相当于被一根导线短路”,所以外电路逻辑电平便为0,只有都为高电平时,与的结果才为逻辑1。
-
推挽输出_OUT_PP
可以输出高、低电平,连接数字器件;推挽结构一般是指两个三极管分别受两个互补信号的控制,总是在一个三极管导通的时候另一个截止。高低电平由IC的电源决定。
-
复用功能的推挽输出_AF_PP
可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用)
片内外设功能(I2C的SCL,SDA)
-
复用功能的开漏输出_AF_OD 可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用)
片内外设功能(TX1,MOSI,MISO.SCK.SS)
函数使用
GPIO初始化函数MX_GPIO_Init()
:
定义GPIO结构体:
GPIO_InitTypeDef GPIO_InitStruct = {0};
时钟使能:
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
设置输出引脚:
GPIO_InitStruct.Pin =
//需要初始化的引脚号GPIO_PIN_0;
GPIO_InitStruct.Mode =
/*输出模式:
推挽输出:GPIO_MODE_OUTPUT_PP;
开漏输出:GPIO_MODE_OUTPUT_OD;
复用推挽:GPIO_MODE_AF_PP;
复用开漏:GPIO_MODE_AF_OD;
*/
GPIO_InitStruct.Pull =
/*上下拉电阻配置:
无电阻:GPIO_NOPULL;
配置上拉电阻:GPIO_PULLUP;
配置下拉电阻:GPIO_PULLDOWN;
*/
GPIO_InitStruct.Speed =
/*最大输出速度设置
低速:GPIO_SPEED_FREQ_LOW;
中速:GPIO_SPEED_FREQ_MEDIUM;
高速:GPIO_SPEED_FREQ_HIGH;
*/
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);//将结构体写入端口组:GPIO(A-D)
设置输入引脚:
GPIO_InitStruct.Pin =
//需要初始化的引脚号GPIO_PIN_0;
GPIO_InitStruct.Mode =
//将引脚设置为输入模式:GPIO_MODE_INPUT;
GPIO_InitStruct.Pull =
/*上下拉电阻配置:
无电阻:GPIO_NOPULL;
配置上拉电阻:GPIO_PULLUP;
配置下拉电阻:GPIO_PULLDOWN;
*/
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);//将结构体写入端口组:GPIO(A-D)
设置输出电平函数:
HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin, GPIO_PinStatePinState);
电平翻转函数:
HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
电平读取函数:
HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
流水灯:
struct GPIO_smg//定义引脚结构体
{
GPIO_TypeDef * port;//端口组
uint16_t pin;//引脚编号
};
struct GPIO_smg LED[8]=
{
{GPIOA,GPIO_PIN_4},
{GPIOA,GPIO_PIN_3},
{GPIOA,GPIO_PIN_2},
{GPIOA,GPIO_PIN_1},
{GPIOA,GPIO_PIN_0},
{GPIOC,GPIO_PIN_3},
{GPIOC,GPIO_PIN_2},
{GPIOC,GPIO_PIN_1},
};
void water(void)//流水灯
{
int i;
for(i=0;i<8;i++)
{
HAL_GPIO_WritePin(LED[i].port,LED[i].pin,GPIO_PIN_RESET);//依次点亮流水灯
if(i!=0)
{
HAL_GPIO_WritePin(LED[i-1].port,LED[i-1].pin,GPIO_PIN_SET);//熄灭上一个流水灯,使小灯单个点亮流动
}
}
for(i=7;i>=0;i--)//反向流动流水灯
{
HAL_GPIO_WritePin(LED[i].port,LED[i].pin,GPIO_PIN_RESET);
if(i!=7)
{
HAL_GPIO_WritePin(LED[i+1].port,LED[i+1].pin,GPIO_PIN_SET);
}
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
water();
}
}
数码管动态显示:
struct GPIO_smg//定义引脚结构体
{
GPIO_TypeDef * port;
uint16_t pin;
};
struct GPIO_smg LED_TUBE_W[8]=//定义段码引脚
{
{GPIOA,GPIO_PIN_15},
{GPIOA,GPIO_PIN_11},
{GPIOC,GPIO_PIN_9},
{GPIOC,GPIO_PIN_7},
{GPIOC,GPIO_PIN_8},
{GPIOA,GPIO_PIN_12},
{GPIOA,GPIO_PIN_8},
{GPIOC,GPIO_PIN_6},
};
struct GPIO_smg LED_TUBE_X[4]=//定义位选引脚
{
{GPIOD,GPIO_PIN_2},
{GPIOB,GPIO_PIN_4},
{GPIOB,GPIO_PIN_6},
{GPIOB,GPIO_PIN_7},
};
uint8_t xs[]=//数字的段码状态
{
0xc0,//0
0xf9,//1
0xa4,//2
0xb0,//3
0x99,//4
0x92,//5
0x82,//6
0xf8,//7
0x80,//8
0x90,//9
};
int y=0;//计数变量
void display(uint8_t num,uint8_t ww)//单个数字显示,num:需要显示的数字,ww:显示该数字的数码管
{
int a;
for(a=0;a<8;a++)//遍历段码引脚,并控制段码引脚使能
{
HAL_GPIO_WritePin(LED_TUBE_W[a].port,LED_TUBE_W[a].pin,(GPIO_PinState)(xs[num]&(0x01<<a)));
}
for(a=0;a<4;a++)//遍历位选,并控制位选引脚使能
{
HAL_GPIO_WritePin(LED_TUBE_X[a].port,LED_TUBE_X[a].pin,(GPIO_PinState)(ww!=a));
}
}
void displayall(uint16_t num)//整体数字显示
{
display(num%10,0);
HAL_Delay(4);
display(num/10%10,1);
HAL_Delay(4);
display(num/100%10,2);
HAL_Delay(4);
display(num/1000%10,3);
HAL_Delay(4);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
displayall(y);//将变量y的值进行显示
}
}
按键读取:
struct GPIO_smg//定义引脚结构体
{
GPIO_TypeDef * port;
uint16_t pin;
};
struct GPIO_smg cols[4]={//按键列引脚定义
{GPIOB, GPIO_PIN_0},
{GPIOB, GPIO_PIN_1},
{GPIOB, GPIO_PIN_13},
{GPIOB, GPIO_PIN_12},
};
struct GPIO_smg rows[4]={//按键行引脚定义
{GPIOA, GPIO_PIN_6},
{GPIOA, GPIO_PIN_7},
{GPIOC, GPIO_PIN_4},
{GPIOC, GPIO_PIN_5},
};
uint8_t ScanKey()//单个按键读取(已将第一列置低,第一行第二行为上拉读取)
{
if(HAL_GPIO_ReadPin(rows[0].port,rows[0].pin)==GPIO_PIN_RESET)//读取第一行状态,抬起时进行操作
{
HAL_Delay(10);//按键防抖
if(HAL_GPIO_ReadPin(rows[0].port,rows[0].pin)==GPIO_PIN_RESET)
{
while(HAL_GPIO_ReadPin(rows[0].port,rows[0].pin) == GPIO_PIN_RESET);
HAL_GPIO_TogglePin(LED[0].port,LED[0].pin); //转换小灯状态
}
}
if(HAL_GPIO_ReadPin(rows[1].port,rows[1].pin)==GPIO_PIN_RESET)//读取第二行状态,按下时进行操作
{
HAL_Delay(10);
if(HAL_GPIO_ReadPin(rows[1].port,rows[1].pin)==GPIO_PIN_RESET)
{
HAL_GPIO_TogglePin(LED[1].port,LED[1].pin)
while(HAL_GPIO_ReadPin(rows[1].port,rows[1].pin) == GPIO_PIN_RESET);
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
displayall(ScanKey());//将按键代表的数值进行显示,显示函数见上一个程序
}
}
按键扫描:
struct GPIO_smg//定义引脚结构体
{
GPIO_TypeDef * port;
uint16_t pin;
};
struct GPIO_smg cols[4]={//按键列引脚定义
{GPIOB, GPIO_PIN_0},
{GPIOB, GPIO_PIN_1},
{GPIOB, GPIO_PIN_13},
{GPIOB, GPIO_PIN_12},
};
struct GPIO_smg rows[4]={//按键行引脚定义
{GPIOA, GPIO_PIN_6},
{GPIOA, GPIO_PIN_7},
{GPIOC, GPIO_PIN_4},
{GPIOC, GPIO_PIN_5},
};
uint8_t ScanKey()
{
uint8_t i,col=0,row=0;
GPIO_InitTypeDef GPIO_InitStruct;
for(i=0;i<4;i++)//行线设置为输出并输出低电平
{
HAL_GPIO_WritePin(rows[i].port,rows[i].pin,GPIO_PIN_RESET);
GPIO_InitStruct.Pin = rows[i].pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(rows[i].port, &GPIO_InitStruct);
}
for(i=0;i<4;i++)//列线设置为输入并读取状态
{
GPIO_InitStruct.Pin = cols[i].pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(cols[i].port, &GPIO_InitStruct);
if(HAL_GPIO_ReadPin(cols[i].port,cols[i].pin)==GPIO_PIN_RESET)
{
col += (i+1);
}
}
for(i=0;i<4;i++)//列线设置为输出并输出低电平
{
HAL_GPIO_WritePin(cols[i].port,cols[i].pin,GPIO_PIN_RESET);
GPIO_InitStruct.Pin = cols[i].pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(cols[i].port, &GPIO_InitStruct);
}
for(i=0;i<4;i++)//行线设置为输入并读取状态
{
GPIO_InitStruct.Pin = rows[i].pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(rows[i].port, &GPIO_InitStruct);
if(HAL_GPIO_ReadPin(rows[i].port,rows[i].pin)==GPIO_PIN_RESET)
{
row += (i+1);
}
}
if((row==0)&&(col==0))return 0;
return ((col-1)*4)+row;
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
displayall(ScanKey());//将按键代表的数值进行显示,显示函数见上一个程序
}
}