[STM32 fried cold rice] GPIO and external interrupt (standard peripheral Library)
It has been a year since I started a project with STM32 series single chip microcomputer. I suddenly want to open a new pit and write something, which is equivalent to my own understanding. I don't say much about building a development environment and configuration engineering when I'm free. There are a lot of tutorials on the Internet. I really don't like the UI style of MDK, so I always use the development mode of VSCode+Keil Assist+MDK-ARM
Directly enter the topic. This series will write two versions of standard peripheral library and HAL library. The single chip microcomputer used is STM32F407ZGT6, and the SYSTEM folder provided by punctual atom is used. Because the library function does not provide delay function, in order to directly use the delay function provided by punctual atom, we will also talk about how to use the SYSTEM tick clock to build delay function later.
About F4 series system clock
When we use STM32CubeMX to build the project, we can intuitively see the PLL clock, frequency division and frequency doubling of the system in the clock tree. More simply, we can directly set the frequency you want. STM32CubeMX will help you configure it automatically. Then we don't have similar tools when using the standard peripheral library, and F4 series is different from F1, F1 has set the external high-speed crystal oscillator to 8MHz by default, and set the system clock to the highest 72MHz. In F4 series, my board HSE crystal oscillator is 8MHz, so we want to use the highest 168MHz that F407 can run, in system_stm32f4xx.c in this file, change the macro definition to #define PLL_M 8 is enough
1, What is GPIO
The whole process of GPIO is a general-purpose input / output port, which can output 1 or 0 simply; Let's look at the basic structure of STM32 GPIO:
It can be seen that the pin can be set to push-pull or open drain through the P-MOS and N-MOS below, and the pin can be directly output to strong high level through the internal pull-up or pull-down resistance. The 5V tolerance here means that the pin can withstand 5V voltage, but not all pins can withstand 5V voltage. The STMF4xx series data manual clearly indicates the pin that can tolerate 5V voltage. It is not recommended to directly connect 5V voltage to the pin here. The VDD voltage of the chip is nominal 3.3V, and it is not recommended to directly input 5V voltage to GPIO.
Next, we verify the input and output functions of the pin through the LED, buzzer and key:
2, Code writing
1. Output part
First, we configure the relevant pins of LED and buzzer:
The cathode of the LED is connected to the GPIO of STM32, and the anode is connected to 3.3V through a voltage dividing resistor, so when our GPIO output is low, the LED can be lit normally;
The relevant pins are defined first through macro definition. The purpose of using macro definition is to improve the portability of code, beauty and easy to understand and read. The punctual atom sys is also used here The interface function for directly operating GPIO registers provided in C file is convenient for us to understand GPIO with the idea of 51. The number of registers of 51 is a drop in the bucket in front of STM32. It is obviously unrealistic to carry registers, but we can't completely understand the operation of registers.
/* LED Macro */ #define LEDx_GPIO_PROT GPIOF #define LEDx_RCC_CLOCK RCC_AHB1Periph_GPIOF #define LED0_PIN_NUM GPIO_Pin_9 #define LED1_PIN_NUM GPIO_Pin_10 #define LED_NUM_0 PFout(9) #define LED_NUM_1 PFout(10) /* LED Status */ typedef enum { LED_ON = 0, LED_OFF } LedStatus_TypeDef_t;
Above, we have enumerated the two states of LED for easy understanding;
In the initialization of GPIO, set GPIO to push-pull output mode according to the actual hardware; It doesn't matter whether pull-up or pull-down is set here. Push-pull output can directly output strong high level and low level. Open drain output can only output low level. External pull-up resistance is required to output high level.
The following two functions are provided, but the GPIO related functions provided by ST are encapsulated for ease of use;
/** * @name Led_Init * @brief LedPin Init Function * @param InitState: Leds initial state * @retval None */ void Led_Init(LedStatus_TypeDef_t InitState) { GPIO_InitTypeDef GPIO_InitSturcture; RCC_AHB1PeriphClockCmd(LEDx_RCC_CLOCK, ENABLE); GPIO_InitSturcture.GPIO_Pin = LED0_PIN_NUM | LED1_PIN_NUM; GPIO_InitSturcture.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitSturcture.GPIO_OType = GPIO_OType_PP; GPIO_InitSturcture.GPIO_PuPd = GPIO_PuPd_DOWN; GPIO_InitSturcture.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(LEDx_GPIO_PROT, &GPIO_InitSturcture); LED_NUM_0 = InitState; LED_NUM_1 = InitState; } /** * @name Led_SetState * @brief LedPin set level function * @param LedPinx: Leds pin number * @param LedState: Leds State * @retval None */ void Led_SetState(uint16_t LedPinx, LedStatus_TypeDef_t LedState) { GPIO_WriteBit(LEDx_GPIO_PROT, LedPinx, (BitAction)LedState); } /** * @name Led_ToggleState * @brief LedPin set state inversion function * @param LedPinx: Leds pin number * @retval None */ void Led_ToggleState(uint16_t LedPinx) { GPIO_ToggleBits(LEDx_GPIO_PROT, LedPinx); }
The buzzer is the same:
A triode is used in the hardware circuit to drive the buzzer. Personally, I think a 0.1uF ceramic capacitor should be connected in parallel at both ends of the buzzer. A spike will be generated at the moment when the inductive element is turned on and off. A 0.1uF capacitor can be used to eliminate the spike. S8050 is an NPN type triode. The base input is turned on at high level.
#define BUZZER_GPIO_PORT GPIOF #define BUZZER_RCC_CLOCK RCC_AHB1Periph_GPIOF #define BUZZER_PIN_NUM GPIO_Pin_8 typedef enum { BUZZER_OFF = 0, BUZZER_ON } BuzzerStatus_TypeDef_t;
/** * @name Buzzer_Init * @brief BuzzerPin Init Function * @param None * @retval None */ void Buzzer_Init(void) { GPIO_InitTypeDef GPIO_InitSturcture; RCC_AHB1PeriphClockCmd(BUZZER_RCC_CLOCK, ENABLE); GPIO_InitSturcture.GPIO_Pin = BUZZER_PIN_NUM; GPIO_InitSturcture.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitSturcture.GPIO_OType = GPIO_OType_PP; GPIO_InitSturcture.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitSturcture.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(BUZZER_GPIO_PORT, &GPIO_InitSturcture); } /** * @name Buzzer_SetState * @brief Buzzer set level function * @param None * @retval None */ void Buzzer_SetState(BuzzerStatus_TypeDef_t BuzzerState) { GPIO_WriteBit(BUZZER_GPIO_PORT, BUZZER_PIN_NUM, (BitAction)BuzzerState); } /** * @name Buzzer_ToggleState * @brief Buzzer set state inversion function * @param None * @retval None */ void Buzzer_ToggleState(void) { GPIO_ToggleBits(BUZZER_GPIO_PORT, BUZZER_PIN_NUM); } /** * @name Buzzer_Beep * @brief Buzzer beep once function * @param None * @retval None */ void Buzzer_Beep(void) { Buzzer_ToggleState(); delay_ms(10); Buzzer_ToggleState(); }
2. Input part
It can be seen from the schematic diagram that when the key is pressed, the GPIO is directly connected with GND. We can judge whether the key is pressed by judging whether the input level of the pin is high or low:
/* Key Macro */ #define KEYx_GPIO_PORT GPIOE #define WAKE_UP_GPIO_PORT GPIOA #define KEYx_RCC_CLOCK RCC_AHB1Periph_GPIOE #define WAKE_UP_RCC_CLOCK RCC_AHB1Periph_GPIOA #define KEY0_PIN_NUM GPIO_Pin_4 #define KEY1_PIN_NUM GPIO_Pin_3 #define KEY2_PIN_NUM GPIO_Pin_2 #define WAKE_UP_PIN_NUM GPIO_Pin_0 #define KEY0 PEin(4) #define KEY1 PEin(3) #define KEY2 PEin(2) #define WAKE_UP PAin(0) typedef enum { KEY_DOWN = 0, KEY_UP } KeyStatus_TypeDef_t; #define KEY_MODE_NORMAL 0x00 #define KEY_MODE_INTERRUPT 0x01
#define KEY_TRIGGER_MODE KEY_MODE_NORMAL /** * @name Key_Init * @brief LedPin Init Function * @param None * @retval None */ void Key_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(KEYx_RCC_CLOCK, ENABLE); RCC_AHB1PeriphClockCmd(WAKE_UP_RCC_CLOCK, ENABLE); GPIO_InitStructure.GPIO_Pin = KEY0_PIN_NUM | KEY1_PIN_NUM | KEY2_PIN_NUM; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Init(KEYx_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = WAKE_UP_PIN_NUM; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; GPIO_Init(WAKE_UP_GPIO_PORT, &GPIO_InitStructure); #if KEY_TRIGGER_MODE NVIC_InitTypeDef NVIC_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource2); SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource3); SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4); SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0); EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); EXTI_InitStructure.EXTI_Line = EXTI_Line2 | EXTI_Line3 | EXTI_Line4; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x03; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); #endif } /** * @name Key_Init * @brief LedPin Init Function * @param None * @retval */ uint8_t Key_Scan(void) { static uint8_t KeyState = KEY_UP; if (KeyState && (KEY0 == KEY_DOWN || KEY1 == KEY_DOWN || KEY2 == KEY_DOWN || WAKE_UP == KEY_UP)) { delay_ms(10); KeyState = KEY_DOWN; if (KEY0 == KEY_DOWN) { Buzzer_Beep(); return 1; } else if (KEY1 == KEY_DOWN) { Buzzer_Beep(); return 2; } else if (KEY2 == KEY_DOWN) { Buzzer_Beep(); return 3; } else if (WAKE_UP == KEY_UP) { Buzzer_Beep(); return 4; } } else if (KEY0 == KEY_UP && KEY1 == KEY_UP && KEY2 == KEY_UP && WAKE_UP == KEY_DOWN) KeyState = KEY_UP; return 0; }
Carefully, I have found that external interrupt initialization is added to the key initialization code. Here, I initialize the key mode through precompiled instructions. By changing the macro definition of the key mode, I can choose whether to enable the key to trigger the function of external interrupt
Verification function
Add such a code to the main function and use the key to change the state of the LED;
switch (Key_Scan()) { case 1: /* code */ Led_ToggleState(LED0_PIN_NUM); break; case 2: /* code */ Led_ToggleState(LED1_PIN_NUM); break; case 3: /* code */ Led_ToggleState(LED0_PIN_NUM); Led_ToggleState(LED1_PIN_NUM); break; }
Burn verification:
The bibi rising tone of the buzzer when the key is pressed cannot be heard in GIF;