沁恒CH32V307使用记录:GPIO与EXTI

news/2024/11/23 4:08:05/

文章目录

  • 目的
  • GPIO(通用输入输出接口)
    • 基础说明
    • 初始化
    • 输出
    • 输入与电平读取
    • 锁定机制
  • EXTI(外部中断)
    • 基础说明
    • 使用演示
  • 总结

目的

GPIO是单片机最基础的功能,EXTI最常用的场景就是GPIO用于输入时使用。这篇文章将对CH32V307中相关内容进行说明。

本文使用沁恒官方的开发板 (CH32V307-EVT-R1沁恒RISC-V模块MCU赤兔评估板) 进行演示。

本文演示中需要用到开发板上的KEY和LED,默认只是引入接口到排针,并没有和芯片GPIO口相连,下文使用中需要手动用杜邦线连接。
在这里插入图片描述

GPIO(通用输入输出接口)

基础说明

在这里插入图片描述
CH32V307的GPIO和大部分单片机一样支持多种工作模式: 浮空输入 上拉输入 下拉输入 模拟输入 开漏输出 推挽输出 复用功能的输入和输出

复位后 ,GPIO口运行在初始状态,这时大多数IO口都是运行在浮空输入状态 ,但也有HSE等外设相关的引脚是运行在外设复用的功能上。

沁恒官方提供了库函数用于操作GPIO口,主要是 ch32v30x_gpio.hch32v30x_gpio.c 两个文件,前者中声明了提供给用户调用的函数以及相关的枚举和宏定义类型等。

下面只介绍些GPIO的基础使用,剩余的功能大多数是结合外设复用或是中断等进行的,会在包含在那些内中中进行介绍。需要详细了解也可以直接查看上面的库函数文件。

初始化

初始化GPIO只要配置需要使用的GPIO口和工作模式及其附加参数等,比如下面方式:

// 下面函数将初始化 PA0 为推挽输出模式
void GPIO_Toggle_INIT(void)
{GPIO_InitTypeDef GPIO_InitStructure = {0}; // GPIO后初始化结构体RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //初始化GPIOA时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 使用 Pin0GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出切换频率 // 这个通常在满足需求的情况下越低越好,比如使用 GPIO_Speed_2MHz GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化GPIO口
}

上面代码中出现的 GPIOA GPIO_Pin_0 GPIO_Mode_Out_PP 等枚举和宏定义类型都可以在 ch32v30x_gpio.h 文件中找到,其中同一组的 GPIO_Pin 可以多个一起使用,比如下面这样:

// 同时使用 PIN 0、2、4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2 | GPIO_Pin_4;

输出

这里以推挽输出模式进行介绍,这里直接以点亮控制控制开发板上的LED进行测试,用杜邦线将 LED1LED2 分别与 PC0PC1 相连接,然后使用 MounRiverStudio 新建 CH32V307VC 项目,将 main.c 中代码替换成如下即可:

#include "debug.h"int main(void)
{Delay_Init();// 以下初始化 GPIOC 的 PIN0 和 PIN1RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);GPIO_InitTypeDef GPIO_InitStructure = {0};GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOC, &GPIO_InitStructure);while(1){GPIO_SetBits(GPIOC, GPIO_Pin_0 | GPIO_Pin_1); // PC0 和 PC1 输出高电平,根据电路这将会熄灭LEDDelay_Ms(1000);GPIO_ResetBits(GPIOC, GPIO_Pin_0 | GPIO_Pin_1); // PC0 和 PC1 输出低电平,根据电路这将会点亮LEDDelay_Ms(500);}
}

编译程序下载到开发板就可以看到LED闪烁了。

输出相关操作库函数有下面一些:

// 指定IO口输出高电平
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
// 指定IO口输出低电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
// 写指定IO口的电平值,BitAction可选Bit_RESET(0)和Bit_SET(1)
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal); 
// 写一组IO口的输出值
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

也可以使用调试器来查看GPIOC的输出寄存器状态了解输出情况:
在这里插入图片描述
上面的 OUTDR 就是输出数据寄存器了,该寄存器数据也可以通过下面库函数进行读取:

uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);

输入与电平读取

这里以下拉输入模式进行介绍,用杜邦线将 KEYPB2 相连接, PB2 设置为浮空输入模式,根据电路连接,当按键松开时读取到的输入电平为高,按键被按下时读取到的输入电平为低。

修改 main.c 代码如下:

#include "debug.h"int main(void)
{Delay_Init();USART_Printf_Init(115200);// 以下初始化 PB2 为浮空输入模式RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure = {0};GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOB, &GPIO_InitStructure);while(1){Delay_Ms(2000);// 读取PB2输入电平值printf("PB2 Input Data:%d\r\n", GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2));}
}

输入数据读取相关操作库函数有下面一些:

// 读取指定IO口电平值
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
// 读取一组IO口电平值
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);

锁定机制

锁定机制可以锁定IO口的配置,在一些可靠性要求高的场合下比较有用。经过特定的一个写序列后,选定的 IO 引脚配置将被锁定,在下
一个复位前无法更改。使用库函数的话只需要使用下面函数即可:

void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

EXTI(外部中断)

基础说明

在这里插入图片描述
CH32V307有二十多条外部中断线,大部分都是给GPIO用的,这里也只对这个进行介绍,其它的中断线都是用于特定功能了,会在那些功能下进行介绍。

GPIO使用外部中断通常用于输入模式下,根据中断配置当外部电平上升或下降变化时会触发中断。

GPIO上的外部中断使用时需要处理的内容如下:

  • 初始化GPIO;
  • 初始化EXTI;
  • 中断都受到中断控制器控制(NVIC),所以相应用能也需要初始化;
  • 编写外部中断触发时的回调函数;

外部中断的库函数主要位于 ch32v30x_exti.hch32v30x_exti.c 两个文件,前者中声明了提供给用户调用的函数以及相关的枚举和宏定义类型等。

使用演示

这里使用PB2触发外部中断,电路上保持 KEYPB2 的连接。将 main.c 代码改为如下:

#include "debug.h"int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 将外部中断配置为分组二,这样中断父优先级和子优先级取值都为0~3Delay_Init();USART_Printf_Init(115200);// 以下初始化 PB2 为浮空输入模式RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB, ENABLE); // 此处需要把复用时钟也打开GPIO_InitTypeDef GPIO_InitStructure = {0};GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOB, &GPIO_InitStructure);//    EXTI_ClearITPendingBit(EXTI_Line2); // 推荐在使能中断前先清除一次中断,防止意外发生// 以下初始化外部中断EXTI_InitTypeDef EXTI_InitStructure = {0};GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource2); // 配置外部中断源为 PB2EXTI_InitStructure.EXTI_Line = EXTI_Line2; // 外部中断线2EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 配置为外部中断EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 配置上升沿下降沿都触发中断EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能外部中断EXTI_Init(&EXTI_InitStructure);// 以下初始化中断控制器NVIC_InitTypeDef NVIC_InitStructure = {0};NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; // 配置使用 EXTI2 通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 父优先级设置为1,数值越小优先级越高。父优先级高的中断会抢占优先级低的中断NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; // 子优先级设置为2,数值越小优先级越高,子优先级不会引起抢占NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断NVIC_Init(&NVIC_InitStructure);while(1){}
}// 下面是中断回调函数的声明,该函数名是在启动文件 startup_ch32v30x_D8C.S 中定义的
// 需要注意的是后面的 __attribute__((interrupt())); 是必须的这是 GCC For RISCV 对于中断的一种处理
// 另外也可以使用__attribute__((interrupt("WCH-Interrupt-fast"))); 这样可以使用沁恒RISCV的快速中断功能
void EXTI2_IRQHandler(void) __attribute__((interrupt()));// 下面是中断回调函数的定义
void EXTI2_IRQHandler(void)
{if(EXTI_GetITStatus(EXTI_Line2)!=RESET) // 检查是否是中断线2的中断{printf("PB2 Input Data:%d\r\n", GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2));EXTI_ClearITPendingBit(EXTI_Line2); // 清除中断标志,这样才能触发下次中断}
}

这里特别需要注意的是这颗RISC-V内核的单片机使用时中断处理函数需要特殊的标识声明后才能正常使用,比如上面代码中使用 ( void EXTI2_IRQHandler(void) __attribute__((interrupt())); ) 方式进行声明,不然进一次中断后程序就跑飞了。

在这里插入图片描述

演示中读取到的数据异常是由于按键抖动引起的,实际使用中需要添加软件和硬件上的按键消抖功能。

总结

GPIO与EXTI虽然是使用频率比较高的功能,但总体使用比较简单,没有太多需要详细说明的地方。


http://www.ppmy.cn/news/22946.html

相关文章

C# 使用MQTT 协议实现通讯

1.什么是MQTT 协议 MQTT 协议的全称是 Message Queuing Telemetry Transport,翻译为消息队列传输探测,它是 ISO 标准下的一种基于发布 - 订阅模式的消息协议,它是基于 TCP/IP 协议簇的,它是为了改善网络设备硬件的性能和网络的性能…

Mongodb搭建并使用

1、配置文件 1.1、添加pom文件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency> </dependencies> 1.2、配置连接&#xff0c;账号密码可以自己…

【Python入门第二天】Python入门

Python 安装 已经安装软件的小伙伴要检查是否已在 Windows PC 上安装了 python&#xff0c;请在开始栏中寻找 Python 或者直接在命令提示符窗口输入Python按回车&#xff0c;如果已经安装就会出现你的安装信息。 如果有小伙伴还没有安装环境或者编辑器&#xff0c;可以转到这里…

JavaScript 异步编程

JavaScript 异步编程 Javascript语言的执行环境是"单线程"&#xff08;single thread&#xff09;。 所谓"单线程"&#xff0c;就是指一次只能完成一件任务。如果有多个任务&#xff0c;就必须排队&#xff0c;前面一个任务完成&#xff0c;再执行后面一…

谷粒商城:认证服务准备+60s短信验证

gatewa服务路由配置 - id: gulimall_auth_routeuri: lb://gulimall-auth-serverpredicates:- Hostauth.gulimall.com nginx改变 将静态资源全部转移 gulimall-auth-server启动类 SpringBootApplication EnableFeignClients EnableDiscoveryClient public class GulimallAut…

冒泡排序——“C”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰考试了呀&#xff0c;考的是计算机基础&#xff0c;希望不大......不过这些都不是问题&#xff0c;这不能阻止我对C语言的热情&#xff0c;那现在&#xff0c;就让我们进入冒泡排序的世界吧&#xff0c;我在写数组这篇博客的时候就…

c# 面试题 2023-2-6

1.POP、OOP、AOP区别。AOP解决什么问题 2.AOP的实现 3.装箱和拆箱 4.抽象类和接口的区别和使用场景 5.锁:乐观锁,悲观锁 举例说明应用场景 6.什么是死锁&#xff1f;如何保证你实现的锁结果不发生死锁 7.数组和链表的区别 8.WebAPI 和 webservice的区别 9.什么是线…

小程序抽象节点(插槽获取组件数据)

在开发过程中&#xff0c;封装组件经常会遇到这一种场景&#xff0c;例如这里我要封装一个swiper组件&#xff0c;但是不同的使用位置&#xff0c;布局结构样式这些需要不同的展现方式&#xff0c;这里我们就会使用插槽&#xff0c;在vue中我们可以使用slot-scope来获取组件的数…