CH32V307教程 [第二集] [GPIO]
目录
任务目标
基础知识
实现过程
实验代码
现象
任务目标
使用按键 Wake_Up 控制 LED1 亮灭。
基础知识
外部中断
优势
使用外部中断来处理按键动作不仅响应速度是最快最及时的,还不需要一直跑循环占用计算资源,因此我们选择使用外部中断来处理按键 Wake_Up 的动作。
查阅数据手册和应用手册
查询应用手册得出
所有的 GPIO 口都可以被配置外部中断输入通道,但一个外部中断输入通道最多只能映射到一个 GPIO 引脚上,且外部中断通道的序号必须和 GPIO 端口的位号一致,比如 PA1(或 PB1、PC1、PD1、PE1 等)只能映射到 EXTI1 上,且 EXTI1 只能接受 PA1、PB1、PC1、PD1 或 PE1 等其中之一的映射,两方都是一对一的关系。
![](https://verimake.com/assets/files/2022-03-10/1646901687-292494-1-exti.png)
- 值得注意的是,CH32V307 虽有 16 个 GPIO 外部中断输入通道,但对应的中断服务函数只有 7 个:
- 通道 0、1、2、3、4 各一个
- 通道 59 共用一个
- 通道 1015 共用一个
![](https://verimake.com/assets/files/2022-03-10/1646901687-323086-2-exti.png)
实现过程
根据第一篇教程的方法,新建工程。
通过 资料 -> openCH 赤菟开发板开发资源v1.0.0
中的引脚定义可以得出,按下 Wake_Up 按键后,引脚 PA0 处会得到高电平,另外,PE11 引脚处连接了 LED1。
![](https://verimake.com/assets/files/2022-03-10/1646901687-362490-3-yinjiao.png)
借鉴官方例程 CH32V307EVT 的 EXAM -> GPIO -> GPIO_Toggle
中初始化 GPIO 的代码,完成初始化 PE11 的代码。
官方例程 CH32V307EVT 的 EXAM -> GPIO -> GPIO_Toggle
中初始化 GPIO 的代码:
/*********************************************************************
* @fn GPIO_Toggle_INIT
*
* @brief Initializes GPIOA.0
*
* @return none
*/
void GPIO_Toggle_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
修改后的初始化 PE11 的代码。
void GPIO_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
借鉴官方例程 CH32V307EVT 的 EXAM -> EXTI -> EXTI0
中初始化外部中断的代码 完成初始化 PA0 的代码。
官方例程 CH32V307EVT 的 EXAM -> EXTI -> EXTI0
中初始化外部中断的代码:
/*********************************************************************
* @fn EXTI0_INT_INIT
*
* @brief Initializes EXTI0 collection.
*
* @return none
*/
void EXTI0_INT_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
EXTI_InitTypeDef EXTI_InitStructure={0};
NVIC_InitTypeDef NVIC_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* GPIOA ----> EXTI_Line0 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line=EXTI_Line0;
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 = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
可以看出初始化外部中断的代码分为三个部分:
初始化 GPIO,配置为输入模式
初始化 EXTI
初始化 NVIC
发现例程正好是使用的引脚 A0 的外部中断,因此,只需将外部中断的触发条件修改为上升沿触发模式,即可完成初始化外部中断。
修改后的代码:
void EXTI_INT_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
EXTI_InitTypeDef EXTI_InitStructure={0};
NVIC_InitTypeDef NVIC_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* GPIOA ----> EXTI_Line0 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_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);
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
按照以下所需功能完成 main()
函数。
1. 使用 `EXTI_INT_INIT()` 函数初始化外部中断。
2. 使用 `GPIO_INIT` 初始化GPIO。
3. 进入死循环等待中断。
因此,最终的 `main()` 函数为:
```c
/*******************************************************************************
* Function Name : main
* Description : Main program.
* Input : None
* Return : None
*******************************************************************************/
int main(void)
{
EXTI_INT_INIT(); // 初始化外部中断
GPIO_INIT(); // 初始化 GPIO
while(1); // 死循环
}
```
按照任务目标 使用按键 Wake_Up 控制 LED1 亮灭
完成外部中断的中断服务函数。
void EXTI0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
volatile uint16_t LED_Status = 0; // 中断里使用的变量加 volatile 可当成全局变量
void EXTI0_IRQHandler(void)
{
EXTI_ClearFlag(EXTI_Line0); // 置中断标志位为零
LED_Status = !LED_Status ; // 将 LED 状态值取反
GPIO_WriteBit(GPIOE, GPIO_Pin_11, LED_Status); // 配置 PE11 (即 LED1) 状态
}
中断服务函数里的内容为三步:
置中断标志位为零
将 LED 状态值取反
配置 PE11 引脚状态
注:中断服务程序的函数名可在工程中 Startup/startup_ch32v30x.S 中找到。
![](https://verimake.com/assets/files/2022-03-10/1646901687-437420-4-fuwuhanshuming.png)
实验代码
/**
******************************************************************
* @file main.c
* @author xy,Benue
* @version V1.0
* @date 2022-1-13
* @brief 使用按键 Wake_Up 控制 LED1 亮灭。
******************************************************************
* @attention
* VeriMake 用于CH32V307例程
******************************************************************
*/
#include "debug.h"// 包含 CH32V307 的头文件,C 标准单元库和delay()函数
/********************************************************************
* 函 数 名 : GPIO_INIT
* 函数功能 : 初始化 GPIO
* 输 入 : 无
* 输 出 : 无
********************************************************************/
void GPIO_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
/********************************************************************
* 函 数 名 : EXTI_INT_INIT
* 函数功能 : 初始化外部中断
* 输 入 : 无
* 输 出 : 无
********************************************************************/
void EXTI_INT_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
EXTI_InitTypeDef EXTI_InitStructure={0};
NVIC_InitTypeDef NVIC_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* GPIOA ----> EXTI_Line0 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_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);
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/********************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*********************************************************************/
int main(void)
{
EXTI_INT_INIT(); // 初始化外部中断
GPIO_INIT(); // 初始化 GPIO
while(1); // 死循环
}
/********************************************************************
* 函 数 名 : EXTI0_IRQHandler
* 函数功能 : 中断服务程序的函数
* 输 入 : 无
* 输 出 : 无
*********************************************************************/
void EXTI0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
volatile uint16_t LED_Status = 0; // 中断里使用的变量加 volatile 可当成全局变量
void EXTI0_IRQHandler(void)
{
EXTI_ClearFlag(EXTI_Line0); // 置中断标志位为零
LED_Status = !LED_Status ; // 将 LED 状态值取反
GPIO_WriteBit(GPIOE, GPIO_Pin_11, LED_Status); // 配置 PE11 (即 LED1) 状态
}
实验现象
按下按键 Wake_Up,可以切换 LED1 的亮 / 灭。