CH32V003 上手合集:初始配置、GPIO 库与 MultiButton 按键

8418 字约 21 分钟更新于 2026-10-010 次阅读#ch32v003#嵌入式#gpio#multibutton#环境配置

CH32V003 相关的前三篇备忘合在一起:开工前的工程初始配置、GPIO 库接口清单、以及用 MultiButton 做按键。按顺序看下来就能把这块 MounRiver 工程从零跑到能用。

一、开发前的初始配置

对新建工程需要先做的几处设置。

1. 未使用外部晶振时,先把晶振关掉

参考:关闭 CH32 的外部晶振

PA1、PA2 默认是外接晶振使用的,必须关闭外部晶振,才可以配置为普通 IO 使用。

修改系统定义文件 User\system_ch32v00x.c,把开头的时钟定义改成如下:

/* 
* Uncomment the line corresponding to the desired System clock (SYSCLK) frequency (after 
* reset the HSI is used as SYSCLK source).
* If none of the define below is enabled, the HSI is used as System clock source. 
*/
 
//#define SYSCLK_FREQ_8MHz_HSI    8000000
//#define SYSCLK_FREQ_24MHZ_HSI   HSI_VALUE
#define SYSCLK_FREQ_48MHZ_HSI   48000000
//#define SYSCLK_FREQ_8MHz_HSE    8000000
//#define SYSCLK_FREQ_24MHz_HSE   HSE_VALUE
//#define SYSCLK_FREQ_48MHz_HSE   48000000

只保留 #define SYSCLK_FREQ_48MHZ_HSI 48000000,其他全部注释。这样 PA1 和 PA2 就可以当作普通引脚使用。

2. 配置串口,开启中断接收

串口的默认引脚:USART1 TX-->D.5,RX-->D.6。

初始化代码(注意按需修改波特率 USART_InitStructure.USART_BaudRate):

 
/*********************************************************************
 * @fn      USARTx_CFG
 *
 * @brief   初始化USART1外设配置,使用中断模式接收
 *
 * @return  none
 */
void USARTx_CFG(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};   // GPIO初始化结构体
    USART_InitTypeDef USART_InitStructure = {0}; // USART初始化结构体
    NVIC_InitTypeDef NVIC_InitStructure = {0};   // NVIC初始化结构体
 
    // 使能GPIOD和USART1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1, ENABLE);
 
    /* USART1 TX-->D.5   RX-->D.6 */
    // 配置USART1的TX引脚(PD5)为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_30MHz; // GPIO速度设置为30MHz
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   // 复用推挽输出模式
    GPIO_Init(GPIOD, &GPIO_InitStructure);
 
    // 配置USART1的RX引脚(PD6)为浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入模式
    GPIO_Init(GPIOD, &GPIO_InitStructure);
 
    // 配置USART1通信参数
    USART_InitStructure.USART_BaudRate = 230400;                                    // 波特率设置为115200
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;                     // 8位数据位
    USART_InitStructure.USART_StopBits = USART_StopBits_1;                          // 1位停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;                             // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;                 // 使能发送和接收模式
 
    // 初始化USART1
    USART_Init(USART1, &USART_InitStructure);
 
    // 配置USART1接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
 
    // 配置NVIC中断优先级
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
 
    // 使能USART1
    USART_Cmd(USART1, ENABLE);
}

3. 中断服务函数

/*********************************************************************
 * @fn      USART1_IRQHandler
 *
 * @brief   USART1中断服务函数
 *
 * @return  none
 */
void USART1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void USART1_IRQHandler(void)
{
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        // 读取接收到的数据
        u8 data = USART_ReceiveData(USART1);
 
        // 解析协议数据
        ParseProtocolData(data);
    }
}

其中的数据处理部分(ParseProtocolData(data))需要替换成自己业务需要的代码。

4. 使用阻塞的方式发送数据

 
/*********************************************************************
 * @fn      USART_SendData_Block
 *
 * @brief   阻塞方式发送数据
 *
 * @param   data - 要发送的数据指针
 * @param   len - 要发送的数据长度
 *
 * @return  none
 */
void USART_SendData_Block(u8 *data, u16 len)
{
    u16 i;
    for (i = 0; i < len; i++)
    {
        // 等待发送缓冲区为空
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
        {
        }
        // 发送数据
        USART_SendData(USART1, data[i]);
    }
    // 等待发送完成
    while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
    {
    }
}

二、GPIO 库接口函数清单

void     GPIO_DeInit(GPIO_TypeDef *GPIOx);
void     GPIO_AFIODeInit(void);
void     GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_InitStruct);
void     GPIO_StructInit(GPIO_InitTypeDef *GPIO_InitStruct);
uint8_t  GPIO_ReadInputDataBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef *GPIOx);
uint8_t  GPIO_ReadOutputDataBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef *GPIOx);
void     GPIO_SetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
void     GPIO_ResetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
void     GPIO_WriteBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void     GPIO_Write(GPIO_TypeDef *GPIOx, uint16_t PortVal);
void     GPIO_PinLockConfig(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
void     GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void     GPIO_EventOutputCmd(FunctionalState NewState);
void     GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
void     GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

分类解释:

1. GPIO 初始化和配置函数

函数 说明
GPIO_DeInit() 复位GPIO外设,恢复默认值
GPIO_AFIODeInit() 复位AFIO(复用功能IO)
GPIO_Init() 最重要的 - 初始化GPIO引脚模式、速度等
GPIO_StructInit() 初始化GPIO配置结构体为默认值
GPIO_PinLockConfig() 锁定GPIO配置,防止意外修改
GPIO_PinRemapConfig() 配置引脚复用/重映射功能

2. GPIO 读写操作函数

输入操作:

函数 说明
GPIO_ReadInputDataBit() 读取单个输入引脚的电平(按键检测用这个)
GPIO_ReadInputData() 读取整个端口的输入数据(16位)

输出操作:

函数 说明
GPIO_ReadOutputDataBit() 读取单个输出引脚的设置值(不是实际电平)
GPIO_ReadOutputData() 读取整个端口的输出设置值
GPIO_SetBits() 置位引脚(设为高电平/1)
GPIO_ResetBits() 复位引脚(设为低电平/0)
GPIO_WriteBit() 写入单个引脚的值(Bit_RESET/Bit_SET)
GPIO_Write() 写入整个端口的值

3. 特殊功能函数

函数 说明
GPIO_EventOutputConfig() 配置事件输出功能(唤醒MCU等)
GPIO_EventOutputCmd() 启用/禁用事件输出
GPIO_EXTILineConfig() 配置外部中断引脚(重要)

4. 常用函数使用示例

4.1 GPIO 初始化

GPIO_InitTypeDef GPIO_InitStructure;
 
// LED引脚配置为输出
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);
 
// 按键引脚配置为输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  // 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);

4.2 读取按键状态

// 读取PA1引脚输入
uint8_t key_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1);

4.3 控制 LED

// 方法1:分别控制
GPIO_SetBits(GPIOA, GPIO_Pin_0);   // LED亮(高电平点亮)
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // LED灭
 
// 方法2:使用WriteBit
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);   // 亮
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET); // 灭
 
// 方法3:翻转(如果低电平点亮)
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0) == 0) {
    GPIO_SetBits(GPIOA, GPIO_Pin_0);   // 关->开
} else {
    GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 开->关
}

4.4 配置外部中断

// 配置PA1为外部中断源
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);

5. GPIO 模式说明

模式 说明
GPIO_Mode_AIN 模拟输入
GPIO_Mode_IN_FLOATING 浮空输入(默认)
GPIO_Mode_IPD 下拉输入
GPIO_Mode_IPU 上拉输入(常用,按键)
GPIO_Mode_Out_OD 开漏输出
GPIO_Mode_Out_PP 推挽输出(常用,LED)
GPIO_Mode_AF_OD 复用开漏
GPIO_Mode_AF_PP 复用推挽

6. 在项目中的对应关系

  • 按键:用 GPIO_ReadInputDataBit() 读取状态
  • LED:用 GPIO_SetBits()/GPIO_ResetBits() 控制
  • 初始化:用 GPIO_Init() 配置引脚模式

三、用 MultiButton 做按键

1. 开源库介绍

MultiButton 是一个高效、灵活的多按键状态机库,支持多种按键事件检测。

2. 将 MultiButton 添加进工程

2.1 使用 git 工具下载源码

在工程中创建 Bsp 文件夹,专门用于放单片机的底层驱动代码文件,做到程序的分层设计,也方便后续工程的直接迁移使用。在软件中进入这个 Bsp 文件夹,运行 Git Bash,下载源码:

git clone https://github.com/0x1abin/MultiButton.git

或者(GitHub 镜像):

git clone https://gitclone.com/github.com/0x1abin/MultiButton.git

整个文件的结构是这样的:

MultiButton 目录结构

2.2 排除不需要的文件

将除了 Bsp\MultiButton\multi_button.c、Bsp\MultiButton\multi_button.h、Bsp\MultiButton\README.md 这三个文件之外的文件/文件夹排除编译——右键单击文件/文件夹,选择排除编译即可。

排除编译

2.3 将创建的文件加入编译器

最重要的是把创建的文件添加进编译,和 Keil 类似,入口在工程 → 属性:

加入编译

这样就可以正常编译并在工程中应用了。

3. 编写应用代码

README.md 里有这个库的介绍和使用说明,直接看「快速开始」部分照做即可。

我创建了一个按键相关的文件,专门写按键的初始化和驱动函数:Bsp\bsp_multibutton\bsp_multibutton.c 和 Bsp\bsp_multibutton\bsp_multibutton.h,同样需要把这些加入编译器配置。

3.1 编写 bsp_multibutton.c

以 PA2 和 PA0 做演示,前提是将 PA2 配置为按键输入,PA0 配置为 LED 灯的输出。演示实现的效果是:按下按键灯点亮,再次按下灯熄灭,在亮灭之间循环。

#include "bsp_multibutton.h"
 
#define BUTTON1_GPIO_Port GPIOA
#define BUTTON1_Pin GPIO_Pin_2
 
// 前向声明
void btn1_single_click_handler(Button *btn);
// 定义按键
static Button btn1;
 
// 实现对GPIO 读取函数
uint8_t read_button_gpio(uint8_t button_id)
{
    switch (button_id)
    {
    case 1:
        return GPIO_ReadInputDataBit(BUTTON1_GPIO_Port, BUTTON1_Pin);
    default:
        return 0;
    }
}
 
// 按键回调函数
void btn1_single_click_handler(Button *btn)
{
    // 使用三元运算符
    uint8_t current_state = GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0);
    GPIO_WriteBit(GPIOA, GPIO_Pin_0, current_state ? Bit_RESET : Bit_SET);
}
 
// 初始化按键
void bsp_multibutton_init(void)
{
    // 初始化按键 (active_level: 0=低电平有效, 1=高电平有效)
    button_init(&btn1, read_button_gpio, 0, 1);
    button_attach(&btn1, BTN_SINGLE_CLICK, btn1_single_click_handler);
    button_start(&btn1); // 开始检测按键
}

3.2 编写 bsp_multibutton.h

#ifndef __BSP_MULTIBUTTON_H
#define __BSP_MULTIBUTTON_H
 
#include "debug.h"
#include "multi_button.h"
 
void bsp_multibutton_init(void);
 
#endif //__BSP_MULTIBUTTON_H

相关文章


评论