CH32V003 上手合集:初始配置、GPIO 库与 MultiButton 按键
CH32V003 相关的前三篇备忘合在一起:开工前的工程初始配置、GPIO 库接口清单、以及用 MultiButton 做按键。按顺序看下来就能把这块 MounRiver 工程从零跑到能用。
一、开发前的初始配置
对新建工程需要先做的几处设置。
1. 未使用外部晶振时,先把晶振关掉
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
整个文件的结构是这样的:

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
微光录