该工具类为北邮-国院-电信工程及管理专业-通信方向-大三课程-微处理器设计(Microprocessor System Design)- Lab 2中英方给的Driver类 gpio.c 。
由于上课课件中并未对该Driver进行解读,因此在写Lab2的时候就会造成很多的困扰。因此在写完Lab 2之后写了这篇博客希望对还没有做Lab 2的同学及学弟学妹带来帮助。
在本博客中的解读皆基于对该工具类中的方法基于原英文注释的翻译及博主使用体验编写。博客末尾已附上该gpio.c 的源码。若有解读错误,欢迎指正。
直接看例子会更快哦!
如果设置了callback但在点击对应PIN还没有反应的,下拉到 设置回调函数 | set_callback 看解决方法。
设置PIN为Input或Output | gpio_set_mode
按照课程PPT的内容,我们在设置PIN
- 为输入时仅考虑设置其为Pull Up模式或者 Pull Down模式。不考虑其他情况
GPIO输入Pull Up与Pull Down模式解读
该部分为直接引用博主Yngz_Miao的博客【STM32】GPIO工作原理(八种工作方式超详细分析,附电路图)。
具体功能果园学子仍以PPT中为准。
Pull Up
上拉输入模式下,I/O端口的电平信号直接进入输入数据寄存器。
- I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在高电平
- 在I/O端口输入为低电平的时候,输入端的电平也还是低电平。
Pull Down
下拉输入模式下,I/O端口的电平信号直接进入输入数据寄存器。
- 在I/O端口悬空(在无信号输入)的情况下,输入端的电平为低电平;
- 在I/O端口输入为高电平的时候,输入端的电平为高电平。
设置PIN为input端口
调用方法:
void gpio_set_mode(Pin pin, PinMode mode){}
传入参数解释:
-
Pin pin :被设置的PIN -
PinMode :所要设置的模式。该项可传入选择有
例子:
-
设置PB_0 为上拉(Pull Up)输入模式 gpio_set_mode(PB_0, PullUp);
-
设置PA_0 为下拉(Pull Down)输入模式 gpio_set_mode(PA_0, PullDown);
设置PIN为output端口
调用方法与设置input一致。直接看例子
例子: 设置PA_1 为输出端口
gpio_set_mode(PA_1, Output);
读取PIN
读取单个PIN | gpio_get
调用方法:
int gpio_get(Pin pin) {}
传入参数解释:
返回值:为整数类型,而不是二进制数。
例子: 读取PB_0 ,假设此时PB_0 为高电平。
int status = gpio_get_range(PB_0);
读取一个范围内的PIN | gpio_get_range
调用方法:
unsigned int gpio_get_range(Pin pin_base, int count) {{}
传入参数解释:
Pin pin_base :从哪一位PIN开始读int count :一共要读取多少位。读一位则设为1
返回值:为整数类型,而不是二进制数。
例子:读取PB_1 、PB_2 、 PB_3 ,假设此时PB_1 、PB_2 、 PB_3 分为为1 、1 、0
LINE = gpio_get_range(PB_1, 3);
读取时的坑点
两个读取函数使用时,并不是说你将所要读取的PIN设了,它就能马上读到。而是还要再按另一个其他的PIN才能将原来的读进去。
例如:现在我用a = gpio_get_range(PB_1, 3) 读取3个PINs。进入Debug模式,假设运行后三个PINs为000 ,那么在我设置这三个PINs为001 时(将PB_1 设为HIGH),a 仍然为(十进制的)0 。在我(随意按一个没有set_mode的键)按PB_10 后,a 才变为(十进制的)1 。
设置PIN的值
每一个PIN只有高电平和低电平两个值。
设置单个PIN | gpio_set
调用方法:
void gpio_set(Pin pin, int value) {}
传入参数解释:
Pin pin :要设置PINint value :对应要设置的值。注意这个值的类型为int 类型的,这里只能输入0 或1 ,0 对应为LOW。(其他的值我也没测试)
例子:设置PA_1 为高电平
gpio_set(PA_1, 1);
设置一个范围内的PIN | gpio_set_range
调用方法:
void gpio_set_range(Pin pin_base, int count, int value) {}
传入参数解释:
Pin pin :要从哪一个PIN开始设置int count :要设置多少个PINint value :对应要设置的值。注意这个值的类型为int 类型的。也就是说,如果我们要设置3 PINs,当传入参数value 的值为4 时,这三个PINs会设置为100 (左边为MSB)
- 不允许设置的值超过所设置PIN的数量能表示的值,例如我要设置3个PINs,参数
value 传入十进制的8,这是会报错的。
例子: 将十进制的7用三个PB_1 、PB_2 、 PB_3 表示
gpio_set_range(PB_1, 3, 7);
例子: 利用PA_0 ~ PA_7 这8个PIN来表示0~8 的one-hot code。即,当输入十进制(int )的7 时,PA_7 为HIGH,其他都为LOW
#include <math.h>
gpio_set_range(PA_0, 8, pow(2, 7));
设置PIN为原来的相反值 | gpio_toggle
调用方法:
void gpio_toggle(Pin pin) {}
传入参数解释:
例子: 将PB_0 的值倒置。假设原PB_0 = LOW
gpio_toggle(PB_0);
设置上升沿(儿)/下降沿(儿)触发 | gpio_set_trigger
调用方法:
void gpio_set_trigger(Pin pin, TriggerMode trig) {}
传入参数解释:
Pin pin :所要设置的PinTriggerMode trig :对应触发的模式。可选Rising 、Falling 。
例子: 设置PB_0 为上升沿(儿)触发
gpio_set_trigger(PB_0, Rising);
设置回调函数 | gpio_set_callback
设置某个PIN触发后会调用什么函数来响应。(按下按钮触发函数就是设置这个!)
调用方法:
void gpio_set_callback(Pin pin, void (*callback)(int status)){}
传入参数解释:
Pin pin :所要设置的Pinvoid (*callback)(int status) :所要触发函数的指针(其实就是函数名)。注意这个函数的传入参数一定要是int status ,参数一定有且只有这个,函数内可不使用这个函数。
例子: 设定PB_0 为Push Button,按下后调用void PB_ISR(int status){} 函数
gpio_set_callback(PB_0, PB_ISR);
设置上了面代码,在Debug模式中点击PB_0仍没有触发PB_ISR 解决方法: 确保将external interrupts 的 source的值是B 。具体步骤为:
-
进入Debug模式 -
顶部工具栏 Peripherals -> External Interrupts。(点开Peripherals 没有内容就是没有进入debug模式!) -
Source设置为B
例子:设定PB_0 为上升沿儿触发的Push Button,按下后调用void PB_ISR(int status){} 函数
gpio_set_trigger(PB_0, Rising);
gpio_set_callback(PB_0, PB_ISR);
工具类源码 gpio.c
#include "platform.h"
#include "gpio.h"
uint32_t IRQ_status;
uint32_t IRQ_port_num;
uint32_t IRQ_pin_index;
uint32_t EXTI_port_set;
uint32_t prioritygroup;
uint32_t priority;
static void (*GPIO_callback)(int status);
void gpio_toggle(Pin pin) {
// Toggles a GPIO pin.
// In the absence of a pin toggle register, can be easily
// implemented with:
// gpio_set(pin, !gpio_get(pin));
gpio_set(pin, !gpio_get(pin));
}
void gpio_set(Pin pin, int value) {
// Sets the selected pin to the specified value.
GPIO_TypeDef* p = GET_PORT(pin);
uint32_t pin_index = GET_PIN_INDEX(pin);
MODIFY_REG(p->ODR,1UL<<pin_index,value<<pin_index);
}
int gpio_get(Pin pin) {
// Gets the current value of the specified pin.
GPIO_TypeDef* p = GET_PORT(pin);
uint32_t pin_index = GET_PIN_INDEX(pin);
return (READ_BIT(p->IDR,(1<<pin_index)))>>pin_index;
}
void gpio_set_range(Pin pin_base, int count, int value) {
// Sets a range of pins to the specified value.
// This can be used to write to an entire port, or just
// a subset of the (consecutive) pins on a port.
// The output pin_base should be set to the LSB of
// value. Pin (pin_base + 1) should be LSB + 1, etc.
// The mask for the value parameter should be:
// ((1 << count) - 1).
GPIO_TypeDef* p = GET_PORT(pin_base);
uint32_t pin_index = GET_PIN_INDEX(pin_base);
MODIFY_REG(p->ODR,((1UL<<count)-1)<<pin_index,value<<pin_index);
}
unsigned int gpio_get_range(Pin pin_base, int count) {
// Gets a range of pins.
// This can be used to read an entire port, or just
// a subset of the (consecutive) pins on a port.
// The LSB of the return value should be the state of
// pin_base. (LSB + 1) of the return value should be
// the state of (pin_base + 1) etc.
// e.g. if the state of pin_base is P1_0 (port 1, pin 0)
// and count is 4 this, function should return:
// (P1_3 << 3) | (P1_2 << 2) | (P1_1 << 1) | P1_0
// The mask for the value parameter should be:
// ((1 << count) - 1).
GPIO_TypeDef* p = GET_PORT(pin_base);
uint32_t pin_index = GET_PIN_INDEX(pin_base);
return READ_BIT(p->IDR,(((1 << count) - 1)<<pin_index))>>pin_index;
}
void gpio_set_mode(Pin pin, PinMode mode) {
// Sets the output mode of a pin.
// If the clock for the pin's port needs to be enabled, it
// should be done here.
// The modes configure the GPIO pin as follows:
// - Reset: resets to a default state.
// - Input: sets as a high impedance input.
// - Output: sets as a push-pull output.
// - PullUp: enables the internal pull-up resistor
// with the pin configured as an input, and
// sets the output to logic high (through
// the pull-up resistor).
// - PullDown: enables the internal pull-down resistor
// with the pin configured as an input, and
// sets the output to logic low (through the
// pull-down).
GPIO_TypeDef* p = GET_PORT(pin);
uint32_t pin_index = GET_PIN_INDEX(pin);
/*
RCC->AHB1ENR|=1UL<<GET_PORT_INDEX(pin);//enable clock output
// Enable clock for interrupts
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
// Enable debug in low-power mode
DBGMCU->CR |= DBGMCU_CR_DBG_SLEEP | DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY;
*/
switch(mode) {
case Reset: // input floating mode by default
if (pin_index < 8)
MODIFY_REG(p->CRL, 0xFUL<<((pin_index)*4), 4UL<<((pin_index)*4)); //0100
else
MODIFY_REG(p->CRH, 0xFUL<<((pin_index-8)*4), 4UL<<((pin_index-8)*4));
break;
case Input: // same as reset
if (pin_index < 8)
MODIFY_REG(p->CRL, 0xFUL<<((pin_index)*4), 4UL<<((pin_index)*4)); //0100
else
MODIFY_REG(p->CRH, 0xFUL<<((pin_index-8)*4), 4UL<<((pin_index-8)*4));
break;
case Output: // output push-pull max 2 MHz
if (pin_index < 8)
MODIFY_REG(p->CRL, 0xFUL<<((pin_index)*4), 2UL<<((pin_index)*4)); //0010
else
MODIFY_REG(p->CRH, 0xFUL<<((pin_index-8)*4), 2UL<<((pin_index-8)*4));
break;
case PullUp: // input pull-up
MODIFY_REG(p->ODR, 1UL<<((pin_index)*4), 1UL<<((pin_index)*4));
if (pin_index < 8)
MODIFY_REG(p->CRL, 0xFUL<<((pin_index)*4), 8UL<<((pin_index)*4)); //1000
else
MODIFY_REG(p->CRH, 0xFUL<<((pin_index-8)*4), 8UL<<((pin_index-8)*4));
break;
case PullDown: // input pull-down
MODIFY_REG(p->ODR, 1UL<<((pin_index)*4), 0UL<<((pin_index)*4));
if (pin_index < 8)
MODIFY_REG(p->CRL, 0xFUL<<((pin_index)*4), 8UL<<((pin_index)*4)); //1000
else
MODIFY_REG(p->CRH, 0xFUL<<((pin_index-8)*4), 8UL<<((pin_index-8)*4));
break;
}
}
/*void gpio_set_trigger(Pin pin, TriggerMode trig) {
__nop();
}*/
void gpio_set_trigger(Pin pin, TriggerMode trig) {
// Sets the interrupt trigger for the specified pin.
// The modes are as follows:
// - None: Disable the trigger.
// - Rising: Trigger on transition from logic low to
// high.
// - Falling: Trigger on transition from logic high to
// low.
uint32_t pin_index = GET_PIN_INDEX(pin);
switch(trig){
case None:
EXTI->IMR &= ~(1<<pin_index);
break;
case Rising:
EXTI->IMR |= (1<<pin_index);
EXTI->RTSR|= (1<<pin_index);
break;
case Falling:
EXTI->IMR |= (1<<pin_index);
EXTI->FTSR|= (1<<pin_index);
EXTI->PR &= (0<<pin_index);
break;
}
}
/*
void gpio_set_callback(Pin pin, void (*callback)(int status)) {
__nop();
}
*/
void gpio_set_callback(Pin pin, void (*callback)(int status)) {
// Set up and enable the interrupt on the passed pin's
// port.
// The callback function should be stored in an internal
// static function pointer.
// When the port ISR is fired, the callback function
// should be executed. The status parameter should equal
// a mask of which pin triggered the interrupt, for
// example if a callback is set on pin P1_2 (port 1, pin
// 2) and the interrupt is triggered on port 1, the
// function callback should be called with argument
// status equalling 0b00000100.
// This allows the user to determine the interrupt source
// with (status & GET_PIN_INDEX(P1_2)).
__enable_irq();
IRQ_status = 0;
IRQ_port_num = GET_PORT_INDEX(pin);
IRQ_pin_index = GET_PIN_INDEX(pin);
EXTI_port_set = IRQ_port_num<<(IRQ_pin_index % 4) * 4;
GPIO_callback = callback;
//Connect the pin to external interrupt line
switch(IRQ_pin_index){
case 0:
//SYSCFG->EXTICR[0]|= EXTI_port_set;
prioritygroup = NVIC_GetPriorityGrouping(); // will return 5
priority = NVIC_EncodePriority(prioritygroup, 1, 1 ); // Pri=1 , SubPri=1
NVIC_SetPriority(EXTI0_IRQn, priority);
NVIC_EnableIRQ(EXTI0_IRQn);
break;
case 1:
//SYSCFG->EXTICR[0]|= EXTI_port_set;
prioritygroup = NVIC_GetPriorityGrouping(); // will return 5
priority = NVIC_EncodePriority(prioritygroup, 1, 1 ); // Pri=1 , SubPri=1
NVIC_SetPriority(EXTI1_IRQn, priority);
NVIC_EnableIRQ(EXTI1_IRQn);
break;
case 2:
//SYSCFG->EXTICR[0]|= EXTI_port_set;
prioritygroup = NVIC_GetPriorityGrouping(); // will return 5
priority = NVIC_EncodePriority(prioritygroup, 1, 1 ); // Pri=1 , SubPri=1
NVIC_SetPriority(EXTI2_IRQn, priority);
NVIC_EnableIRQ(EXTI2_IRQn);
break;
case 3:
//SYSCFG->EXTICR[0]|= EXTI_port_set;
prioritygroup = NVIC_GetPriorityGrouping(); // will return 5
priority = NVIC_EncodePriority(prioritygroup, 1, 1 ); // Pri=1 , SubPri=1
NVIC_SetPriority(EXTI3_IRQn, priority);
NVIC_EnableIRQ(EXTI3_IRQn);
break;
case 4:
//SYSCFG->EXTICR[1]|= EXTI_port_set;
prioritygroup = NVIC_GetPriorityGrouping(); // will return 5
priority = NVIC_EncodePriority(prioritygroup, 1, 1 ); // Pri=1 , SubPri=1
NVIC_SetPriority(EXTI4_IRQn, priority);
NVIC_EnableIRQ(EXTI4_IRQn);
break;
case 5:
case 6:
case 7:
// SYSCFG->EXTICR[1]|= EXTI_port_set;
prioritygroup = NVIC_GetPriorityGrouping(); // will return 5
priority = NVIC_EncodePriority(prioritygroup, 1, 1 ); // Pri=1 , SubPri=1
NVIC_SetPriority(EXTI9_5_IRQn, priority);
NVIC_EnableIRQ(EXTI9_5_IRQn);
break;
case 8:
case 9:
// SYSCFG->EXTICR[2]|= EXTI_port_set;
prioritygroup = NVIC_GetPriorityGrouping(); // will return 5
priority = NVIC_EncodePriority(prioritygroup, 1, 1 ); // Pri=1 , SubPri=1
NVIC_SetPriority(EXTI9_5_IRQn,3);
NVIC_EnableIRQ(EXTI9_5_IRQn);
break;
case 10:
case 11:
// SYSCFG->EXTICR[2]|= EXTI_port_set;
prioritygroup = NVIC_GetPriorityGrouping(); // will return 5
priority = NVIC_EncodePriority(prioritygroup, 1, 1 ); // Pri=1 , SubPri=1
NVIC_SetPriority(EXTI15_10_IRQn,3);
NVIC_EnableIRQ(EXTI15_10_IRQn);
break;
case 12:
case 13:
case 14:
case 15:
//SYSCFG->EXTICR[3]|= EXTI_port_set;
prioritygroup = NVIC_GetPriorityGrouping(); // will return 5
priority = NVIC_EncodePriority(prioritygroup, 1, 1 ); // Pri=1 , SubPri=1
NVIC_SetPriority(EXTI15_10_IRQn,3);
NVIC_EnableIRQ(EXTI15_10_IRQn);
break;
}
}
//Note: only four interrupt lines are implemented i.e. only use pin 0-4
void EXTI0_IRQHandler(void){
GPIO_TypeDef* p = ((GPIO_TypeDef*)(GPIOA_BASE + 0x0400 * IRQ_port_num));
IRQ_status = EXTI->PR>>IRQ_pin_index & 1;
NVIC_ClearPendingIRQ(EXTI0_IRQn);
EXTI->PR|=(1<<IRQ_pin_index);
if(p->IDR&(1<<IRQ_pin_index)){
GPIO_callback(IRQ_pin_index);
}
}
void EXTI1_IRQHandler(void){
GPIO_TypeDef* p = ((GPIO_TypeDef*)(GPIOA_BASE + 0x0400 * IRQ_port_num));
IRQ_status = EXTI->PR>>IRQ_pin_index & 1;
NVIC_ClearPendingIRQ(EXTI1_IRQn);
EXTI->PR|=(1<<IRQ_pin_index);
if(p->IDR&(1<<IRQ_pin_index)){
GPIO_callback(IRQ_pin_index);
}
}
void EXTI2_IRQHandler(void){
GPIO_TypeDef* p = ((GPIO_TypeDef*)(GPIOA_BASE + 0x0400 * IRQ_port_num));
IRQ_status = EXTI->PR>>IRQ_pin_index & 1;
NVIC_ClearPendingIRQ(EXTI2_IRQn);
EXTI->PR|=(1<<IRQ_pin_index);
if(p->IDR&(1<<IRQ_pin_index)){
GPIO_callback(IRQ_pin_index);
}
}
void EXTI3_IRQHandler(void){
GPIO_TypeDef* p = ((GPIO_TypeDef*)(GPIOA_BASE + 0x0400 * IRQ_port_num));
IRQ_status = EXTI->PR>>IRQ_pin_index & 1;
NVIC_ClearPendingIRQ(EXTI3_IRQn);
EXTI->PR|=(1<<IRQ_pin_index);
if(p->IDR&(1<<IRQ_pin_index)){
GPIO_callback(IRQ_pin_index);
}
}
void EXTI4_IRQHandler(void){
GPIO_TypeDef* p = ((GPIO_TypeDef*)(GPIOA_BASE + 0x0400 * IRQ_port_num));
IRQ_status = EXTI->PR>>IRQ_pin_index & 1;
NVIC_ClearPendingIRQ(EXTI4_IRQn);
EXTI->PR|=(1<<IRQ_pin_index);
if(p->IDR&(1<<IRQ_pin_index)){
GPIO_callback(IRQ_pin_index);
}
}
void EXTI9_5_IRQHandler(void){
GPIO_TypeDef* p = ((GPIO_TypeDef*)(GPIOA_BASE + 0x0400 * IRQ_port_num));
IRQ_status = (EXTI->PR>>IRQ_pin_index) & 1;
NVIC_ClearPendingIRQ(EXTI9_5_IRQn);
EXTI->PR|=(1<<IRQ_pin_index);
if(p->IDR&(1<<IRQ_pin_index)){
GPIO_callback(IRQ_pin_index);
}
}
void EXTI15_10_IRQHandler(void){
GPIO_TypeDef* p = ((GPIO_TypeDef*)(GPIOA_BASE + 0x0400 * IRQ_port_num));
IRQ_status = (EXTI->PR>>IRQ_pin_index) & 1;
NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
EXTI->PR|=(1<<IRQ_pin_index);
if(p->IDR&(1<<IRQ_pin_index)){
GPIO_callback(IRQ_pin_index);
}
}
// *******************************ARM University Program Copyright ? ARM Ltd 2016*************************************
参考文章
Yngz_Miao,【STM32】GPIO工作原理(八种工作方式超详细分析,附电路图),https://blog.csdn.net/qq_38410730/article/details/79858906
|