四、GD32 MCU 常见外设介绍

系统架构

1.RCU 时钟介绍

众所周知,时钟是MCU能正常运行的基本条件,就好比心跳或脉搏,为所有的工作单元提供时间 基数。时钟控制单元提供了一系列频率的时钟功能,包括多个内部RC振荡器时钟(IRC)、一个外部 高速晶体振荡器时钟(HXTAL)、一个外部低速晶体振荡器时钟(LXTAL)、一个或多个锁相环(PLL) 一个HXTAL时钟和LXTAL时钟监视器、时钟预分频器、时钟多路复用器和时钟门控电路等。 本章,我们将通过一个“输出HXTAL时钟信号” 的实验来熟悉RCU的工作流程。

1.1RCU 配置

GD32系列MCU在启动后首先会执行Reset Handler,紧接着就会执行SystemInit()函数,而时钟的初始化,就是在这个函数中进行,其主要的功能是配置系统时钟CK_SYS(即主频),AHB、APB1以及APB2时钟。SystemInit()函数由GD32官方库提供,不同系列的MCU有一些差别,但实现方式基本相同:首先将RCU关于CK_SYS,AHB、APB1以及APB2时钟配置的一些寄存器恢复到默认值,然后再执行system_clock_config()函数,用于具体的时钟配置。

实际上用户可以不用过于关心上述的实现方式,因为GD32库已经为您提供了多种时钟源及时钟选择,您只需按照以下步骤即可将时钟设置为您期望的值(以GD32F30x为例,其他系列类似):

(1) 在system_gd32f30x.c中,用户可通过选择宏来进行预设的时钟配置,如下图代码清单时钟配置选择宏定义,选择了HXTAL作为PLL时钟源,且配置CK_SYS为120MHz。

/* system frequency define */
#define __IRC8M (IRC8M_VALUE) /* internal 8 MHz RC oscillator frequency */
#define __HXTAL (HXTAL_VALUE) /* high speed crystal oscillator frequency */
#define __SYS_OSC_CLK (__IRC8M) /* main oscillator frequency */
/* select a system clock by uncommenting the following line */
/* use IRC8M */
//#define __SYSTEM_CLOCK_IRC8M (uint32_t)(__IRC8M) 
//#define __SYSTEM_CLOCK_48M_PLL_IRC8M (uint32_t)(48000000)
//#define __SYSTEM_CLOCK_72M_PLL_IRC8M (uint32_t)(72000000)
//#define __SYSTEM_CLOCK_108M_PLL_IRC8M (uint32_t)(108000000)
//#define __SYSTEM_CLOCK_120M_PLL_IRC8M (uint32_t)(120000000)
/* use HXTAL(XD series CK_HXTAL = 8M, CL series CK_HXTAL = 25M) */
//#define __SYSTEM_CLOCK_HXTAL (uint32_t)(__HXTAL)
//#define __SYSTEM_CLOCK_48M_PLL_HXTAL (uint32_t)(48000000)
//#define __SYSTEM_CLOCK_72M_PLL_HXTAL (uint32_t)(72000000)
//#define __SYSTEM_CLOCK_108M_PLL_HXTAL (uint32_t)(108000000)
#define __SYSTEM_CLOCK_120M_PLL_HXTAL (uint32_t)(120000000)

但这种情况下您使用的外部晶振需要是默认值,此值由HXTAL_VALUE定义,如为8000000,那么您应该选择8MHz的外部晶振。

当然,您可以使用其他规格的外部晶振,这种情况下就需要去修改RCU配置函数里面的一些参数,主要是分频和倍频系数,以达到期望的配置,具体如何修改,可以结合GD32的User manual中定义的RCU寄存器来对配置函数进行分析。

(2) 设置HXTAL_VALUE的值。

此数值和RCU的初始化其实并没有太大关系,但如果您使用的外部晶振不是默认值,那么除了按照步骤(1)修改配置参数外,您还必须将此HXTAL_VALUE的值修改为实际的外部晶振频率,这是因为在一些通信外设配置时,库函数会调用HXTAL_VALUE值来设置波特率,如此值设置错误,会导致通信异常。

1.2.非默认外部晶振配置时钟实例

GD32各系列固件库都已提供配置系统时钟的函数。需要注意的是,在使用外部晶振时,固件库中HXTAL_VALUE值规定了 外部晶振的默认值,以 GD32F30x系列为例,如下图代码清单HXTAL_VALUE选择宏定义所示,当芯片为非互联型(GD32F303)时,默认使用的外部晶振频率为8MHz,当芯片为互联型(GD32F305/307)时,默认使用的外部晶振频率为25MHz。

#ifdef GD32F30X_CL 
#define HXTAL_VALUE ((uint32_t)25000000) 
#else 
#define HXTAL_VALUE ((uint32_t)8000000)

那么,当我们使用非默认值的外部晶振时,该如何修改时钟配置函数呢?以GD32F303为例,首先我们先看下GD32F303的时钟树,如图所示。

预分频器可以配置AHB、APB2和APB1域的时钟频率。 AHB、APB2、APB1域的最高时钟频率分别为120MHz、120MHz、60MHz。RCU通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。通过对SysTick控制和状态寄存器的设置,可选择上述时钟或AHB(HCLK)时钟作为SysTick时钟。

ADC时钟由APB2时钟经2、4、6、8、12、16分频或由AHB时钟经5、6、10、20分频获得,它们是通过设置RCU_CFG0和RCU_CFG1寄存器的ADCPSC位来选择。

SDIO, EXMC的时钟由CK_AHB提供。

TIMER时钟由CK_APB1和CK_APB2时钟分频获得,如果APBx(x=0,1)的分频系数不为1,则TIMER时钟为CK_APBx(x=0,1)的两倍。

USBD的时钟由CK48M时钟提供。通过配置 RCU_ADDCTL寄存器的CK48MSEL及PLL48MSEL位可以选择CK_PLL时钟或IRC48M时钟做为CK48M的时钟源。

CTC时钟由IRC48M时钟提供,通过CTC单元,可以实现IRC48M时钟精度的自动调整。

I2S的时钟由CK_SYS提供。

通过配置RCU_BDCTL寄存器的RTCSRC位, RTC时钟可以选择由LXTAL时钟、IRC40K时钟或HXTAL时钟的128分频提供。RTC时钟选择HXTAL时钟的128分频做为时钟源后,当1.2V内核电压域掉电时,时钟将停止。 RTC时钟选择IRC40K时钟做为时钟源后,当VDD掉电时,时钟将停止。

RTC时钟选择LXTAL时钟做为时钟源后,当VDD和VBAT都掉电时,时钟将停止。

当FWDGT启动时, FWDGT时钟被强制选择由IRC40K时钟做为时钟源。

现在,我们结合图GD32F303系统时钟树对时钟树进行分析:

(1) 标注A为CK_SYS,即系统主时钟,它一条线连接至CK_I2S,给I2S外设提供时钟,另一条线经过AHB分频器,输出到CK_AHB,即标注B。

(2) CK_AHB为AHB总线时钟,AHB总线时钟或直连,或经过APB1/APB2分频,给标注C位置的外设提供时钟。

(3) 那么,CK_SYS从何而来呢,我们看标注A的左边,CK_SYS通过SCS位域选择CK_IRC8M、CK_PLL、CK_HXTAL作为时钟来源,其中CK_IRC8M来源于标注D,即IRC8M(MCU内部8M RC时钟);CK_HXTAL来源于标注F,即HXTAL(外部时钟);CK_PLL的来源较复杂,我们单独拿出来说。

(4) CK_PLL来源于锁相环倍频器输出,倍频系数通过PLLMF位域选择,而PLLMF来源于两个地方,一个为 IRC8M 的 2 分 频 , 另 外 一 个 为 预 分 频 器 PREDV0 , 而 PREDV0 来 源 于 标 注 E, 即CK_IRC48M(内部48M RC时钟)和标注F,即HXTAL(外部高速时钟)。

(5) 通过以上分析可以得出结论,CK_PLL的时钟源为D:IRC8M、E:IRC48M、F:HXTAL,用户通过相关寄存器设置选择时钟线。

(6) 和前面分析相同,RTC的时钟来自于F:HXTAL的128分频、G:LXTAL(外部32.768K低速时钟)、F:IRC40K(内部40K RC时钟);FWDGT的时钟来源于F:IRC40K。

(7) 标注I位置为时钟输出线,它的作用是将MCU内部的一些时钟信号线输出到特定IO口上(大部分系列MCU的PA8口都可被设置为时钟输出口0,有些系列MCU含有两组输出IO,具体IO配置请参考各系列MCU Datasheet)用来给其他器件提供基准时钟。由图中可看出通过设置位域CK_OUT0,输出的时钟包括CK_PLL、CK_IRC8M、CK_HXTAL、CK_PLL的2分频。

结合以上分析,我们来看下GD32F30x固件库时钟配置函数(因篇幅有限,只贴出各分频和倍频配置部分),还是以GD32F303芯片为例,如下图代码清单时钟配置部分代码所示:

/* select HXTAL/2 as clock source */
RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PREDV0);
RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | RCU_CFG0_PREDV0);
/* CK_PLL = (CK_HXTAL/2) * 30 = 120 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PLLMF_5);
RCU_CFG0 |= RCU_PLL_MUL30;

 可以看出,8MHz的HXTAL经过预分频器PREDV0分频成4MHz,再通过锁相环PLL倍频30倍到了120MHz。

那么,当您选择其他规格的外部晶振,比如12MHz,则可以先通过预分频器PREDV0分频成6MHz,再通过锁相环PLL倍频20倍即可,如代码清单 0-4. 使用12MHz外部晶振配置120M系统时钟所示。

/* select HXTAL/2 as clock source */
RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PREDV0);
RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | RCU_CFG0_PREDV0);
/* CK_PLL = (CK_HXTAL/2) * 20 = 120 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PLLMF_5);
RCU_CFG0 |= RCU_PLL_MUL20;

当然,在修改完配置函数后,别忘了将HXTAL_VALUE值改为12000000。

需要注意的是,在进行时钟配置时,要严格按照Datasheet中规定的时钟范围进行配置,如GD32F303的 HXTAL的选 择范 围是4~32MHz, PLL的输 入范 围是 1~25MHz,输出范围是16~120Mhz,所以当使用32MHz的外部晶振时,不进行预分频,而直接倍频是不被允许的。

1.3.硬件连接说明

本章通过“输出HXTAL时钟信号”实验来熟悉RCU的工作流程。

通过前面内容讲解可知,本章实验为“输出HXTAL时钟信号”,即通过PA8口将HXTAL输出,我们使用示波器,将探头连接到PA8口,从示波器上读取PA8口波形即可。

1.4.软件配置说明

本小节讲解RCU_Example例程中RCU的配置说明,主要包括外设时钟配置、GPIO引脚配置、主函数介绍以及运行结果。

软件设计的流程如下:

(1)使能GPIOA时钟

(2)初始化PA8,将此端口设置为备用功能模式(AFIO)

(3)通过调用库函数选择HXTAL作为PA8时钟信号源

外设时钟配置

void rcu_config(void)
{
/* enable the GPIOA clock */
rcu_periph_clock_enable(RCU_GPIOA);
}

GPIO 引脚配置 

代码清单 0-6. RCU 例程引脚配置

void gpio_config(void)
{
/* configure PA8 port */ 
#if defined GD32F10X_HD || GD32F30X_HD || GD32F20X_CL || GD32E10X 
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
#elif GD32F1X0 || GD32F4XX || GD32F3X0 || GD32E23X
gpio_mode_set(GPIOA,GPIO_MODE_AF,GPIO_PUPD_NONE,GPIO_PIN_8);
gpio_af_set(GPIOA,GPIO_AF_0,GPIO_PIN_8);
#endif
}

GPIO的配置说明,请参考GPIO章节。

主函数说明

代码清单 0-7 . RCU 例程主函数

int main(void)
{
rcu_config();
gpio_config();
#if defined GD32F10X_HD || GD32F30X_HD || GD32E10X
rcu_ckout0_config(RCU_CKOUT0SRC_HXTAL);
#elif defined GD32F20X_CL || GD32F4XX
rcu_ckout0_config(RCU_CKOUT0SRC_HXTAL,RCU_CKOUT0_DIV1);
#elif GD32F1X0 || GD32F3X0 || GD32E23X
rcu_ckout_config(RCU_CKOUTSRC_HXTAL,RCU_CKOUT_DIV1);
#endifwhile(1){}
}

如代码清单RCU例程主函数,该主函数主要分成四部分,RCU时钟配置、GPIO配置、RCU输出相关库函数调用和while(1)主循环,其中RCU输出相关库函数请读者结合各系列MCU Datasheet、User Manual进行RCU例程的分析。

注意:因为是输出HXTAL,所以必须要使能HXTAL,否则PA8将无波形输出。一个简单的办法是将HXTAL作为CK_SYS时钟源,请参考本章第一节内容。

1.5.运行结果

如图所示 RCU 例程运行结果为 RCU 例程运行结果,可看出,PA8 口正确输出了 HXTAL 波形。

2.GPIO 模块介绍

GPIO的全称为通用输入输出口,是很多外设能够正常工作的必要条件。除了一些特定功能的引脚(如电源脚)外,MCU上其他的引脚都可以当做GPIO来使用。本章,我们将对GPIO进行简单介绍,并通过一个“流水灯”的实验来熟悉GPIO的工作模式。

2.1.GPIO 基础知识

GD32系列MCU的GPIO口是以“组”的形式工作,命名方式为PX(X=A,B,C,D,E···),每组配置有16个pin脚。每个pin脚都可由软件配置为输出(推挽或开漏)、输入、外设备用功能或者模拟模式。每个 GPIO 引脚都可以配置为上拉、下拉或浮空。除模拟模式外,所有的 GPIO 引脚都具备大电流驱动能力。

GPIO的主要特性

◼ 输入/输出方向控制;

◼ 施密特触发器输入功能使能控制;

◼ 每个引脚都具有弱上拉/下拉功能;

◼ 推挽/开漏输出使能控制;

◼ 置位/复位输出使能;

◼ 可编程触发沿的外部中断—使用EXTI配置寄存器;

◼ 模拟输入/输出配置;

◼ 备用功能输入/输出配置;

◼ 端口锁定配置;

◼ 单周期输出翻转功能;

GPIO结构框图 

如图所示为标准I/O端口位的基本结构图。

GPIO引脚配置

在复位期间或复位之后,备用功能并未激活,所有GPIO端口都被配置成输入浮空模式,这种输入模式禁用上拉(PU)/下拉(PD)电阻。用户可通过软件配置GPIO为输入或输出模式。当 GPIO引脚配置为输入引脚时,所有的GPIO引脚内部都有一个可选择的弱上拉和弱下拉电阻。当GPIO引脚配置为输出引脚,用户可以配置端口的输出速度和选择输出驱动模式:推挽或开漏模式。

输入配置

当GPIO引脚配置为输入时:

◼ 施密特触发输入使能;

◼ 可选择的弱上拉和下拉电阻;

◼ 当前I/O引脚上的数据在每个APB2时钟周期都会被采样并存入端口输入状态寄存器;

◼ 输出缓冲器禁用。 如图为输入配置是I/O引脚的输入配置。

输出配置

当GPIO引脚配置为输出时:

◼ 施密特触发输入使能;

◼ 弱上拉和下拉电阻禁用;

◼ 输出缓冲器使能;

◼ 开漏模式:输出控制寄存器设置为“0”时,相应引脚输出低电平;输出控制寄存器设置为“1”,相应管脚处于高阻状态;

◼ 推挽模式:输出控制寄存器设置为“0”时,相应引脚输出低电平;输出控制寄存器设置为“1”,相应引脚输出高电平;

◼ 对端口输出控制寄存器进行读操作,将返回上次写入的值;

◼ 对端口输入状态寄存器进行读操作,将获得当前I/O口的状态。

如图为I/O端口的输出配置

模拟配置

当GPIO引脚配置为模拟模式时:

◼ 弱上拉和下拉电阻禁用;

◼ 输出缓冲器禁用;

◼ 施密特触发输入禁用;

◼ 端口输入状态寄存器相应位为“0”。

如图为I/O端口的输出配置

2.2.备用功能(AF)

除了用作通用 IO 口以外,所有的 GPIO 都有备用功能。

对于 GD32F10x/20x/30x/403/E10x/E50x/A501/VF103 系列 MCU,用户可通过软件将某一个 GPIO配置为复用模式,然后再使能相应外设即可使用 IO 对应的复用功能。

对于 GD32F1x0/3x0/4xx/E23x/A501 系列 MCU,用户可通过软件将某一个 GPIO 配置为复用模式,同时还需要设置 GPIO 备用功能选择寄存器选择 16 个备用功能中的一个。以 GD32F130 为例,说下具体的配置流程。

(1) 设置 GPIO 模式 gpio_mode_setgpio_mode_set函数原型为:

void gpio_mode_set(uint32_t gpio_periph, uint32_t mode, uint32_t pull_up_down, uint32_t pin)
{
xxx
xxx
}

其中形参gpio_periph为需要设置的GPIO口组,其对应的实参为GPIOx(x = A,B,C,D,E,F,G),形参mode为需要设置的GPIO模式,对应的实参为:

GPIO_MODE_INPUT(输入模式)
GPIO_MODE_OUTPUT(输出模式)
GPIO_MODE_AF(复用模式)
GPIO_MODE_ANALOG(模拟模式)

形参pull_up_down为GPIO的上下拉设置,对应的实参为:

GPIO_PUPD_NONE(无上下拉)
GPIO_PUPD_PULLUP(上拉)
GPIO_PUPD_PULLDOWN(下拉)

形参pin为pin脚号,实参为GPIO_PIN_x(x=0..15)。

(2) 设置 AF 类型 gpio_af_set

当需要将GPIO设置为复用模式时,除了调用gpio_mode_se设置GPIO模式外,还需要调用函数gpio_af_set进行复用类型的设置。gpio_af_set函数原型为:

void gpio_mode_set(uint32_t gpio_periph, uint32_t alt_func_num, uint32_t pin)
{
xxx
xxx
}

其中形参gpio_periph为需要设置的GPIO口组,其对应的实参为GPIOx(x = A,B,C,D,E,F,G),形参alt_func_num为需要设置复用类型,对应的实参为:

GPIO_AF_0(复用类型0)
GPIO_AF_1(复用类型1)
GPIO_AF_2(复用类型2)
GPIO_AF_3(复用类型3)
GPIO_AF_4(复用类型4)
GPIO_AF_5(复用类型5)
GPIO_AF_6(复用类型6)
GPIO_AF_7(复用类型7)

F1x0、F3x0、F4xx(F405\F407\F450)、E23x系列MCU的GPIO的复用类型可以从对应MCU的 Datasheet中查得,如GD32F130系列的复用类型表如下图(部分截图):

形参pin为pin脚号,实参为GPIO_PIN_x(x=0..15)。

根据以上介绍,如设置PA0为复用模式,且设置功能为USART0_CTS功能,函数调用如下:

gpio_mode_set(GPIOA,GPIO_MODE_AF,GPIO_PUPD_NONE,GPIO_PIN_0);
gpio_af_set (GPIOA,GPIO_AF_1,GPIO_PIN_0);

2.3.I/O 重映射功能

本节内容只适用于GD32F10x/30x/403/E103/E50x/A501/VF103系列MCU。

介绍

为了扩展 GPIO 的 灵 活 性 或 外 设 功 能 使 用 , 通 过 配 置 AFIO 端 口 配 置 寄 存 器(AFIO_PCF0/AFIO_PCF1),每个I/O引脚都可以配置多达4种不同的功能。通过使用外设 IO 的重映射功能可以选择合适的引脚另外,通过配置相应的EXTI源选择寄存器(AFIO_EXTISSx)选择触发中断或事件,GPIO引脚可以用作EXTI中断线。

主要特性

◼ EXTI 源选择

◼ 每个引脚具有多达4种备用功能的配置外设重映射配置以GD32F30x的USART0 AF重映射来说明重映射配置。

由上表可看出,当 USART0_REMAP = 0 时 ,USART0_TX 和USART0_RX 功 能 映 射 在 PA9 、 PA10 上 ,而当 USART0_REMAP = 1 时 ,USART0_TX和USART0_RX功能映射在PB6、PB7上。

其他外设端口重映射或其他系列MCU重映射表,请参考各系列MCU的User Manual。

JTAG/SWD 备用功能重映射

调试接口信号映射在 GPIO 端口的情况下表所示。

为了减少用于调试的 GPIO 端口,用户可以配置 AFIO_PCF0 寄存器中的 SWJ_CFG [2:0]位为不同的值。具体情况参照下表调试端口映射。

注意:当JTAG或SWD定义的IO口被用户设置为其他功能时,需要根据此表将JTAG或SWD功能关闭,否则可能会出现MCU无法运行的情况。

2.4.硬件连接说明

本章以一个“流水灯”的实验来熟悉GPIO的工作模式。

“流水灯”实验硬件连接图

如实验硬件连接图所示,LED2、LED3通过470Ω电阻和MCU的PF0、PF1连接,另一端接GND,可通过配置PF0、PF1的输出电平,来达到控制LED2、LED3亮灭的目的。

读者可以根据典型硬件连接图和相应系列的Datasheet设计出自己的硬件连接方式。

2.5.软件配置说明

本小节讲解GPIO_Example例程中GPIO的配置说明,主要包括外设时钟配置、GPIO引脚配置、主函数介绍以及运行结果。

软件设计的流程如下:

(1) 使能GPIOF时钟

(2) 初始化PF0和PF1,将这两个引脚配置为推挽输出

(3) 通过调用库函数配置PF0和PF1的电平,再通过一些延时处理,使LED2和LED3交替点亮,以实现流水灯

外设时钟配置

外设时钟配置如代码清单GPIO例程时钟配置所示,在GD32全系列MCU中需打开GPIOF时钟。

void rcu_config(void)
{
/* enable the led clock */
rcu_periph_clock_enable(RCU_GPIOF);
}

 GPIO 引脚配置

代码清单 0-9.GPIO 例程引脚配置

void gpio_config(void)
{
/* configure led GPIO port */ 
#if defined GD32F10X_HD || GD32F30X_HD || GD32F20X_CL || GD32E10X 
gpio_init(GPIOF, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
gpio_init(GPIOF, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
#elif GD32F1X0 || GD32F4XX || GD32F3X0 || GD32E23X
gpio_mode_set(GPIOF,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_0);
gpio_mode_set(GPIOF,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_1);
gpio_output_options_set(GPIOF,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_0);
gpio_output_options_set(GPIOF,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_1);
#endif
}

如代码清单GPIO例程引脚配置 ,GD32F10X、GD32F30X、GD32F20X、GD32E10X系列GPIO配置为推挽输出相同,调用gpio_init函数(由标准库提供)即可,此函数原型为:

void gpio_init(uint32_t gpio_periph, uint32_t mode, uint32_t speed, uint32_t pin)
{
xxx
xxx
}

其中形参gpio_periph为需要设置的GPIO口组,其对应的实参为GPIOx(x = A,B,C,D,E,F,G),形参mode为需要设置的GPIO模式,对应的实参为:

GPIO_MODE_AIN(模拟输入)
GPIO_MODE_IN_FLOATING(浮空输入)
GPIO_MODE_IPD(下拉输入)
GPIO_MODE_IPU(上拉输入)
GPIO_MODE_OUT_OD(开漏输出)
GPIO_MODE_OUT_PP(推挽输出)
GPIO_MODE_AF_OD(复用开漏输出)
GPIO_MODE_AF_PP(复用推挽输出)

形参speed为GPIO的速度设置,对应的实参为:

GPIO_OSPEED_2MHZ
GPIO_OSPEED_10MHZ
GPIO_OSPEED_50MHZ
GPIO_OSPEED_MAX(只有部分系列 MCU 的 GPIO 支持大于 50MHz)

形参pin为pin脚号,实参为GPIO_PIN_x(x=0..15)。

实际上GPIO的速度设置只在设置为输出模式下才有效,输入模式下是无效的。

如代码清单GPIO例程引脚配置 ,F1x0、F4xx、F3x0、E23x系列GPIO配置为推挽输出相同,需调用gpio_mode_set和gpio_output_options_set函数。

(1) gpio_mode_set函数

gpio_mode_set函数原型为:

void gpio_mode_set(uint32_t gpio_periph, uint32_t mode, uint32_t pull_up_down, uint32_t pin)
{
xxx
xxx
}

 其中形参gpio_periph为需要设置的GPIO口组,其对应的实参为GPIOx(x = A,B,C,D,E,F,G),形参mode为需要设置的GPIO模式,对应的实参为:

GPIO_MODE_INPUT(输入模式)
GPIO_MODE_OUTPUT(输出模式)
GPIO_MODE_AF(复用模式)
GPIO_MODE_ANALOG(模拟模式)

形参pull_up_down为GPIO的上下拉设置,对应的实参为:

GPIO_PUPD_NONE(无上下拉)
GPIO_PUPD_PULLUP(上拉)
GPIO_PUPD_PULLDOWN(下拉)

形参pin为pin脚号,实参为GPIO_PIN_x(x=0..15)。

(2) gpio_output_options_set函数

当需要将GPIO设置为输出模式时,除了调用gpio_mode_set设置GPIO模式外,还需要调用函数gpio_output_options_set进行GPIO的速度及输出类型的设置。gpio_output_options_set函数原型为:

void gpio_output_options_set(uint32_t gpio_periph, uint8_t otype, uint32_t speed, uint32_t pin)
{
xxx
xxx
}

 其中形参gpio_periph为需要设置的GPIO口组,其对应的实参为GPIOx(x = A,B,C,D,E,F,G),形参otype为需要设置的GPIO输出类型,对应的实参为:

GPIO_OTYPE_PP(推挽模式)
GPIO_OTYPE_OD(开漏模式)

 形参speed为引脚速度,实参为:

GPIO_OSPEED_2MHZ
GPIO_OSPEED_10MHZ
GPIO_OSPEED_50MHZ
GPIO_OSPEED_MAX(只有部分系列 MCU 的 GPIO 支持大于 50MHz)

 形参pin为pin脚号,实参为GPIO_PIN_x(x=0..15)。

主函数说明

代码清单 GPIO 例程主函数

int main(void)
{
systick_config();
rcu_config();
gpio_config();
GPIO_BC(GPIOF) = GPIO_PIN_0;
GPIO_BC(GPIOF) = GPIO_PIN_1;
while(1){/* turn on led2, turn off led3 */
GPIO_BC(GPIOF) = GPIO_PIN_0;
GPIO_BOP(GPIOF) = GPIO_PIN_1;
delay_1ms(1000);
/* turn on led3, turn off led2 */
GPIO_BC(GPIOF) = GPIO_PIN_1;
GPIO_BOP(GPIOF) = GPIO_PIN_0;
delay_1ms(1000);}
}

如代码清单 GPIO 例程主函数,该主函数主要分成四部分,systick 配置、RCU 时钟配置、GPIO 配置和 while(1)主循环,其中 systick 配置用于产生延时,while(1)主循环通过控制相应 GPIO 的 BC 和 BOP 寄存器来控制 GPIO 的输出电平。

运行结果

2.6.GPIO 使用注意事项

  1. 当出现在调试状态下程序可运行,但run模式下MCU无法启动,需要查看是否使用了JTAG口定义的IO,用户需要在程序中将JTAG口功能屏蔽,具体请参考2.3节。

  2. 当JTAG和SWD功能都被禁用后,MCU将无法通过仿真器连接。

3.NVIC 介绍

NVIC(Nested vectored interrupt controller,嵌套向量中断控制器)是Cortex-M处理器的一部分,它是可编程的,且寄存器位于存储器映射的系统控制空间(SCS)。NVIC与内核相辅相成,共同完成对中断的响应。本章将介绍中断的优先级设置、如何定义中断函数名称、中断向量如何偏移。有关NVIC的更多知识,请见《ARM Coretex-M3权威指南》。

3.1.优先级的设置

在Cortex-M中,优先级对于异常来说很关键的,它会影响一个异常是否能被响应,以及何时可以响应。优先级的数值越小,则优先级越高。Cortex-M支持中断嵌套,使得高优先级异常会抢占低优先级异常。有3个系统异常:复位,NMI以及硬fault,它们有固定的优先级,并且它们的优先级号是负数,从而高于所有其它异常。所有其它异常的优先级则都是可编程的,但不能编程为负数。

原则上,Cortex-M支持3 个固定的高优先级和多达256 级的可编程优先级,并且支持128级抢占。但是,绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数会更少,如8级,16级,32级 等。它们在设计时会裁掉表达优先级的几个低端有效位,以达到减少优先级数的目的。

举例来说,如果只使用了4位来表达优先级,则优先级配置寄存器的结构如图所示。

使用 4bit 表达优先级

GD32Fxxx系列、GD32E50x系列、GD32H7xx系 列、GD32A5x系列、GD32W51x系列和GD32VF103、GD32E10X系列使用了4位来表达优先级。GD32E23x和GD32L23x使用的是M23内核,只使用了2位表达优先级。

说明:GD32Fxxx系列是指:GD32F10x、GD32F1x0、GD32F20x、GD32F30x、GD32F3x0、GD32F40x、GD32F4xx。

用于表达优先级的这4bit,又被分组成抢占优先级和子优先级。如果有多个中断同时响应,抢占优先级高的就会抢占抢占优先级低的优先得到执行。如果抢占优先级相同,就比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。

GD32Fxxx系列、GD32E10x系列、GD32E50x系列、GD32H7xx系列、GD32A5x系列、GD32W51X系列和GD32VF103和GD32E10x系列可以设置抢占优先级和子优先级的等级,GD32E23x和GD32L23x系列没有抢占优先级和子优先级的说法,只可以设置优先级。

下面以GD32F10x举例说明如何设置优先级位数以及抢占优先级和子优先级的等级。在GD32f10x_misc.c文件中,nvic_priority_group_set函数用于设置多少位用于抢占优先级,多少位用于子优先级;nvic_irq_enable函数用于设置相应中断的抢占优先级和子优先级的等级。比如现在要设置SPI0的中断,其抢占优先级和子优先级的位数均为2,抢占优先级的等级为0,子优先级 的等级为1,那么代码如代码清单SPI0中断优先级设置所示。

nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
nvic_irq_enable(SPI0_IRQn,0,1);

有关这两个函数的原型以及函数参数的说明,请见代码清单nvic_priority_group_set函数原型、参数nvic_prigroup说明表、代码清单nvic_irq_enable函数原型、nvic_irq_enable()函数的参数说明表。

代码清单nvic_priority_group_set 函数原型

void nvic_priority_group_set(uint32_t nvic_prigroup)
{/* set the priority group value */SCB->AIRCR = NVIC_AIRCR_VECTKEY_MASK | nvic_prigroup;
}

参数 nvic_prigroup 说明表

代码清单nvic_irq_enable 函数原型

void nvic_irq_enable(uint8_t nvic_irq, uint8_t nvic_irq_pre_priority, uint8_t nvic_irq_sub_priority)
{uint32_t temp_priority = 0x00U, temp_pre = 0x00U, temp_sub = 0x00U;/* use the priority group value to get the temp_pre and the temp_sub */switch ((SCB->AIRCR) & (uint32_t)0x700U) {case NVIC_PRIGROUP_PRE0_SUB4:temp_pre = 0U;temp_sub = 0x4U;break;case NVIC_PRIGROUP_PRE1_SUB3:temp_pre = 1U;temp_sub = 0x3U;break;case NVIC_PRIGROUP_PRE2_SUB2:temp_pre = 2U;temp_sub = 0x2U;break;case NVIC_PRIGROUP_PRE3_SUB1:temp_pre = 3U;temp_sub = 0x1U;break;case NVIC_PRIGROUP_PRE4_SUB0:temp_pre = 4U;temp_sub = 0x0U;break;default:nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);temp_pre = 2U;temp_sub = 0x2U;break;}/* get the temp_priority to fill the NVIC->IP register */temp_priority = (uint32_t)nvic_irq_pre_priority << (0x4U - temp_pre);temp_priority |= nvic_irq_sub_priority &(0x0FU >> (0x4U - temp_sub));temp_priority = temp_priority << 0x04U;
NVIC->IP[nvic_irq] = (uint8_t)temp_priority;/* enable the selected IRQ */NVIC->ISER[nvic_irq >> 0x05U] = (uint32_t)0x01U << (nvic_irq & (uint8_t)0x1FU);
}

nvic_irq_enable()函数的参数说明表

参数nvic_irq是一个枚举变量,它定义了每一个中断的编号,具体定义在gd32f10x.h文件中,如代码清单中断号定义所示。

typedef enum IRQn
{/* Cortex-M3 processor exceptions numbers */NonMaskableInt_IRQn = -14, /*!< 2 non maskable interrupt */MemoryManagement_IRQn = -12, /*!< 4 Cortex-M3 memory management interrupt */BusFault_IRQn = -11, /*!< 5 Cortex-M3 bus fault interrupt */UsageFault_IRQn = -10, /*!< 6 Cortex-M3 usage fault interrupt */SVCall_IRQn = -5, /*!< 11 Cortex-M3 SV call interrupt */DebugMonitor_IRQn = -4, /*!< 12 Cortex-M3 debug monitor interrupt */PendSV_IRQn = -2, /*!< 14 Cortex-M3 pend SV interrupt */SysTick_IRQn = -1, /*!< 15 Cortex-M3 system tick interrupt *//* interruput numbers */WWDGT_IRQn = 0, /*!< window watchDog timer interrupt */LVD_IRQn = 1, /*!< LVD through EXTI line detect interrupt */TAMPER_IRQn = 2, /*!< tamper through EXTI line detect */RTC_IRQn = 3, /*!< RTC through EXTI line interrupt */FMC_IRQn = 4, /*!< FMC interrupt */RCU_CTC_IRQn = 5, /*!< RCU and CTC interrupt */EXTI0_IRQn = 6, /*!< EXTI line 0 interrupts */EXTI1_IRQn = 7, /*!< EXTI line 1 interrupts */EXTI2_IRQn = 8, /*!< EXTI line 2 interrupts */EXTI3_IRQn = 9, /*!< EXTI line 3 interrupts */EXTI4_IRQn = 10, /*!< EXTI line 4 interrupts */DMA0_Channel0_IRQn = 11, /*!< DMA0 channel0 interrupt */DMA0_Channel1_IRQn = 12, /*!< DMA0 channel1 interrupt */DMA0_Channel2_IRQn = 13, /*!< DMA0 channel2 interrupt */DMA0_Channel3_IRQn = 14, /*!< DMA0 channel3 interrupt */DMA0_Channel4_IRQn = 15, /*!< DMA0 channel4 interrupt */DMA0_Channel5_IRQn = 16, /*!< DMA0 channel5 interrupt */DMA0_Channel6_IRQn = 17, /*!< DMA0 channel6 interrupt */ADC0_1_IRQn = 18, /*!< ADC0 and ADC1 interrupt */
#ifdef GD32F10X_MDUSBD_HP_CAN0_TX_IRQn = 19, /*!< CAN0 TX interrupts */USBD_LP_CAN0_RX0_IRQn = 20, /*!< CAN0 RX0 interrupts */CAN0_RX1_IRQn = 21, /*!< CAN0 RX1 interrupts */CAN0_EWMC_IRQn = 22, /*!< CAN0 EWMC interrupts */EXTI5_9_IRQn = 23, /*!< EXTI[9:5] interrupts */
TIMER0_BRK_IRQn = 24, /*!< TIMER0 break interrupts */TIMER0_UP_IRQn = 25, /*!< TIMER0 update interrupts */TIMER0_TRG_CMT_IRQn = 26, /*!< TIMER0 trigger and commutation interrupts */TIMER0_Channel_IRQn = 27, /*!< TIMER0 channel capture compare interrupts */TIMER1_IRQn = 28, /*!< TIMER1 interrupt */TIMER2_IRQn = 29, /*!< TIMER2 interrupt */TIMER3_IRQn = 30, /*!< TIMER3 interrupts */I2C0_EV_IRQn = 31, /*!< I2C0 event interrupt */I2C0_ER_IRQn = 32, /*!< I2C0 error interrupt */I2C1_EV_IRQn = 33, /*!< I2C1 event interrupt */I2C1_ER_IRQn = 34, /*!< I2C1 error interrupt */SPI0_IRQn = 35, /*!< SPI0 interrupt */SPI1_IRQn = 36, /*!< SPI1 interrupt */USART0_IRQn = 37, /*!< USART0 interrupt */USART1_IRQn = 38, /*!< USART1 interrupt */USART2_IRQn = 39, /*!< USART2 interrupt */EXTI10_15_IRQn = 40, /*!< EXTI[15:10] interrupts */RTC_Alarm_IRQn = 41, /*!< RTC alarm interrupt */USBD_WKUP_IRQn = 42, /*!< USBD Wakeup interrupt */EXMC_IRQn = 48, /*!< EXMC global interrupt */
#endif /* GD32F10X_MD */
} IRQn_Type;

3.2.中断服务函数的命名

上一小节介绍了如何设置中断的优先级,那么中断服务函数如何命名和使用呢? 本小结将介绍这方面的内容。

下面以GD32F103C8T6产品为例,介绍如何命名中断服务函数名。GD32F103C8T6的flash容量为64KB,属于中密度产品,其对应的启动文件为startup_gd32f10x_md.s。在该启动文件中我们预先为每个中断都命名了一个中断服务函数,为的是初始化中断向量表。实际的中断服务函数里面的内容需要我们重新编写,中断服务函数我们统一写在gd32f10x_it.c文件里。

需要注意的是,中断服务函数的函数名必须和启动文件里面的一样,如果写错了,系统在中断向量表中就会找不到中断服务函数的入口,从而导致进不了中断。为了避免该错误,简单的处理方法是:打开startup_gd32f10x_md.s,找到需要的中断服务函数名,复制该函数名到gd32f10x_it.c文 件 中 即 可 。 以 SPI0 中 断 为 例 , 打 开 startup_gd32f10x_md.s , 找 到 SPI0_IRQHandler (SPI0_IRQHandler就是SPI0中断服务函数的名称),复制SPI0_IRQHandler到gd32f10x_it.c,修改其如代码清单SPI0中断服务函数所示即可。在该函数中就可以添加用户所需的中断服务 代码了。

void SPI0_IRQHandler(void)
{
}

3.3.中断向量偏移

当发生了异常并且要响应它时,Cortex-M 需要定位其处理例程的入口地址。这些入口地址存储在所谓的“异常向量表”中。默认情况下,Cortex-M认为该表位于零地址处,且各向量占用4 节,因此每个表项占用4 字节,如上电后的向量表所示。

因为地址0处应该存储引导代码,所以它通常是Flash或者是ROM器件,并且它们的值不得在运行时改变。然而,为了动态重分发中断,Cortex-M允许向量表重定位,从其它地址处开始定位各异常向量。这些地址对应的区域可以是代码区,但更多在RAM区。在RAM区就可以修改向量的入口地址了。为了实现这个功能,NVIC中有一个寄存器,称为“向量表偏移量寄存器”(在地址0xE000_ED08处),通过修改它的值就能定位向量表。但必须注意的是:向量表的起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上增大到是2 的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有32个中断,则共有32+16(系统异常)=48个向量,向上增大到2 的整次幂后值为64,因此地址地址必须能被64*4=256 整除,从而合法的起始地址可以是:0x0, 0x100, 0x200等。向量表偏移量寄存器的定义如向量表偏移寄存器(VTOR)表所示。

在gd32f10x_misc.c文件中,nvic_vector_table_set函数就是用来定义中断向量偏移的,该函数的原型如代码清单 0-16 nvic_vector_table_set函数原型所示,函数参数说明如参数说明表所示。

代码清单nvic_vector_table_set 函数原型

void nvic_vector_table_set(uint32_t nvic_vict_tab, uint32_t offset)
{SCB->VTOR = nvic_vict_tab | (offset & NVIC_VECTTAB_OFFSET_MASK);
}

参数说明表

下面举例说明如何使用该函数。

在实际使用中,用户会把FALSH分成BOOT区和APP区。BOOT区只用于代码升级,实际应用的程序在APP区里运行。假设客户把FLASH的第0页(大小为1KB)作为BOOT区,该页的地址范围为0x08000000~0x080003FF,第2页、第3页作为APP区,地址范围为0x08000800~0x08000FFF。执行完BOOT区的代码后,程序会跳转到0x08000800的地址开始执行APP程序。0x08000800相对于基地址0x08000000的偏移地址为0x800,此时调用nvic_vector_table_set函数的格式如代码清单调用nvic_vector_table_set函数所示。

nvic_vector_table_set(NVIC_VECTTAB_FLASH, 0x800);

3.4.NVIC 使用注意事项

E23x 系列使用的是 M23 内核,该内核的 NVIC 使用 2bit 定义优先级,并且不分抢占优先级和子优先级。在 gd32e23x_misc.c 文件中,nvic_irq_enable(uint8_t nvic_irq, uint8_t nvic_irq_priority)函数用于设置优先级,该函数的参数说明如图所示。

4.EXTI 中断介绍

EXTI(中断/事件控制器)包含多个相互独立的边沿检测电路并且能够向处理器内核产生中断请求或唤醒事件。 EXTI 有三种触发类型:上升沿触发、下降沿触发和任意沿触发。 EXTI中的每一个边沿检测电路都可以独立配置和屏蔽。

4.1.GD32 EXTI 外设原理简介

GD32 EXTI 主要特性(以 GD32F30x 为例)

◼ 高效的中断处理;

◼ 支持异常抢占和咬尾中断;

◼ 将系统从省电模式唤醒;

◼ 3 种触发类型:上升沿触发,下降沿触发和任意沿触发;

◼ 软件中断或事件触发;

◼ 可配置的触发源;

◼ Cortex-M4系统异常;

◼ 多达68种可屏蔽的外设中断;

◼ 4位中断优先级配置位,可配置16个中断优先级;

◼ EXTI中有多达20个相互独立的边沿检测电路;

EXTI 框图

如图所示,EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件。EXTI的输入线可以通过寄存器设置为任意GPIO,也可以是一些外设的事件,输入线是存在电平变化的信号。

EXTI包含一个边沿检测电路,它会根据上升沿触发选择寄存器和下降沿触发选择寄存器对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号给边沿检测电路,否则输出无效信号,而通过配置寄存器,可设置边沿检测电路响应跳变过程,如设置为上升沿触发、下降沿触发和双边沿触发。

EXTI还包含一个或门电路,它一个输入来自边沿检测电路,另外一个输入来自软件中断事件寄存器。软件中断事件寄存器允许我们通过程序控制就可以启动中断/事件线,这在某些地方非常有用。

中断/事件线

不同系列的MCU对应有不同数量、不同种类的中断/事件线,以GD32F10x系列为例,如EXTI中断线图所示它有20个中断/事件线,每个 GPIO都可以被设置为输入线,占用 EXTI0至EXTI15,另外 4根特定外设中断/事件线由外设触发,比如EXTI16代表低压检测LVD中断、EXTI17代表RTC闹钟中断、EXTI18代表USB唤醒中断、EXTI19代表以太网唤醒中断。

EXTI 中断线

EXTI0至 EXTI15用于 GPIO,通过编程控制可以实现任意一个 GPIO作为 EXTI的输入源。由表可知,EXTI0 可以通过 EXTI源选择寄存器0寄存器(AFIO_EXTISS0)的EXTI0_SS[3:0]位选择配置为 PA0、PB0、PC0、PD0、PE0、PF0、PG0,其他 EXTI线(EXTI中断/事件线)使用配置都是类似的。

注意:多组中同一标号PIN仅可配置一个IO口为外部中断,例: PA0、 PB0、 PC0仅支持三个中的其中一个IO口产生外部中断,不支持三个同为外部中断模式。

各系列 EXTI 功能差异

GD32系列MCU有关SPI外设各系列功能差异如 GD32 MCU 各系列 EXTI 功能差异表所示。

4.2.硬件连接说明

外部中断输入检测可以通过配置上升沿、下降沿或者任意沿触发,读者可根据输入信号的初始状态进行配置。如按键设计原理图所示,该图为GD32 开发板按键设计原理图,在按键未按下时KEY引脚状态为高电平,按下后,引脚电平状态为低电平,因而可以配置为下降沿(按键按下时)触发EXTI、上升沿(按键松开后)触发EXTI或任意沿(按键按下和松开后)触发EXTI。

4.3.软件配置说明

本小节讲解EXTI_Example历程中EXTI模块的配置说明,主要包括外设时钟配置、GPIO引脚配置、EXTI外设配置、主函数介绍以及运行结果。本例程主要介绍GD32 MCU各系列EXTI外部中断的使用。

外设时钟配置

外设时钟配置如代码清单EXTI例程时钟配置代码所示,在该历程中使用PA0作为EXTI输入检测引脚,因而,在GD32全系列MCU中均需打开GPIOA的时钟,另外,在GD32F1X0、GD32F3X0和GD32E23X中需要打开CFGCMP时钟,以及在GD32F4XX中需要打开SYSCFG时钟,主要由于 EXTI源选择控制位在系统配置寄存器中。

void rcu_config(void)
{
#if defined GD32F10X_HD || GD32F30X_HD || GD32F20X_CL || GD32E10Xrcu_periph_clock_enable(RCU_GPIOA);
#elif defined GD32F1X0 || GD32F4XX || GD32F3X0 || GD32E23X
rcu_periph_clock_enable(RCU_GPIOA);#if defined GD32F1X0 || GD32F3X0 || GD32E23Xrcu_periph_clock_enable(RCU_CFGCMP);#elif defined GD32F4XXrcu_periph_clock_enable(RCU_SYSCFG);#endif
#endif
}

GPIO 引脚配置

GPIO引脚配置如代码清单EXTI例程GPIO引脚配置代码所示,PA0引脚需要配置为浮空输入状态。

void gpio_config(void)
{
#if defined GD32F10X_HD || GD32F30X_HD || GD32F20X_CL || GD32E10Xgpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
#elif defined GD32F1X0 || GD32F4XX || GD32F3X0 || GD32E23Xgpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
#endif 
}

EXTI 外设配置

EXTI外设配置代码如代码清单EXTI例程EXTI外设配置代码所示,在该代码中,首先使能EXTI中断,之后配置EXTI源以及所需要的边沿,最后清除EXTI中断标志。

void exti_config(void)
{
#if defined GD32F10X_HD|| GD32F30X_HD || GD32F1X0 || GD32F20X_CL || GD32F4XX || GD32F3X0 || GD32E10X || 
GD32E23X
#if defined GD32F1X0 || GD32F3X0 || GD32E23X || GD32F4XX
#if defined GD32E23X
/* enable and set key EXTI interrupt to the lowest priority */
nvic_irq_enable(EXTI0_1_IRQn, 2U);#elif defined GD32F4XXnvic_irq_enable(EXTI0_IRQn, 2U,0U);
#elif defined GD32F1X0 || GD32F3X0 
nvic_irq_enable(EXTI0_1_IRQn, 2U,0U);
#endif
/* connect key EXTI line to key GPIO pin */
syscfg_exti_line_config(EXTI_SOURCE_GPIOA, EXTI_SOURCE_PIN0);
/* configure key EXTI line */
exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_flag_clear(EXTI_0);
#elsenvic_irq_enable(EXTI0_IRQn, 2U,0U);gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_0);
/* configure key EXTI line */
exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_flag_clear(EXTI_0);
#endif
#endif 
}

主函数说明及中断处理函数说明

主函数配置十分简单,如代码清单EXTI例程主函数所示,主要包括外设时钟初始化调用、GPIO初始化调用以及EXTI配置函数。

int main(void)
{/* peripheral clock enable */rcu_config();/* GPIO config */gpio_config();/* SPI config */exti_config();while(1)
{
}
}

中断处理函数如代码清单EXTI中断处理函数所示。在中断处理函数中,首先判断产生EXTI的中断是否为EXTI_0的中断标志,如果是,则进入EXTI_0的中断处理,处理完成后,清除EXTI_0的中断标志。

void EXTI0_IRQHandler(void)
{
if(exti_interrupt_flag_get(EXTI_0) == SET)
{exti_interrupt_flag_clear(EXTI_0);
}
}

运行结果

将EXTI_Example例程按照对应的芯片工程编译完成后,下载到对应芯片中,在中断处理函数中加断点,可以发现,当PA0有下降沿发生时,会产生EXTI中断,进入EXTI中断处理函数。

5.TIMER 模块介绍

5.1.TIMER 基础知识

TIMER分高级定时器,通用定时器L0,L1,L2和基本定时器。

5.2.硬件连接说明

TIMER 属于片内外设,对于外部硬件设计,只需要单独IO口外接信号线即可。

5.3.GD32 TIMER 外设原理简介(以 GD32F30X 的高级定时器为例)

GD32 TIMER 主要特性

◼ 总通道数: 4;

◼ 计数器宽度: 16位;

◼ 定时器时钟源可选:内部时钟,内部触发,外部输入,外部触发;

◼ 多种计数模式:向上计数,向下计数和中央计数;

◼ 正交编码器接口:用来追踪运动和分辨旋转方向和位置;

◼ 霍尔传感器接口:用来做三相电机控制;

◼ 可编程的预分频器: 16位。运行时可以被改变;

◼ 每个通道可配置:输入捕获模式,输出比较模式,可编程的PWM模式,单脉冲模式;

◼ 可编程的死区时间;

◼ 自动重装载功能;

◼ 可编程的计数器重复功能;

◼ 中止输入功能;

◼ 中断输出和DMA请求:更新事件,触发事件,比较/捕获事件和中止事件;

◼ 多个定时器的菊链使得一个定时器可以同时启动多个定时器;

◼ 定时器的同步允许被选择的定时器在同一个时钟周期开始计数;

◼ 定时器主/从模式控制器。

TIMER 结构框图介绍

5.4.软件配置说明

定时中断 TIMER4

通用定时器L0(TIMER1/2/3/4) 是4通道定时器,支持输入捕获,输出比较,产生PWM信号控制电机和电源管理。通用定时器L0计数器是16位无符号计数器。通用定时器L0是可编程的,可以被用来计数,其外部事件可以驱动其他定时器。

这一章,将使用定时器产生中断,然后在中断服务函数里面翻转 LED上的电平,来指示定时器中断的产生。接下来我们以通用定时器 TIMER4 为实例,来说明要经过哪些步骤,才能达到这个要 求,并产生中断。定时器配置步骤如下:

1)TIMER4 时钟使能

rcu_periph_clock_enable(RCU_TIMER4);

2) 初始化定时器参数,设置自动重装值,分频系数,计数方式等

在库函数中,定时器的初始化参数是通过初始化函数timer_parameter_struct 实现的:

void timer_init(uint32_t timer_periph, timer_parameter_struct* initpara);

第一个参数是确定是哪个定时器,这个比较容易理解。第二个参数是定时器初始化参数结构体指针,结构体类型为timer_parameter_struct ,下面我们看看这个结构体的定义:

/* TIMER init parameter struct definitions */
typedef struct
{ uint16_t prescaler; /*!< prescaler value */uint16_t alignedmode; /*!< aligned mode */uint16_t counterdirection; /*!< counter direction */uint32_t period; /*!< period value */uint16_t clockdivision; /*!< clock division value */uint8_t repetitioncounter; /*!< the counter repetition value */
}timer_parameter_struct;

针对 TIMR4 初始化范例代码格式

 timer_initpara.prescaler = 5999; //30M/6000 =500Hztimer_initpara.alignedmode = TIMER_COUNTER_EDGE;timer_initpara.counterdirection = TIMER_COUNTER_UP;timer_initpara.period = 4000-1; //800mstimer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_init(TIMER4, &timer_initpara);

对于定时器定时周期的计算,设 TIMER4 的经过总线分频后得到的时钟为 30MHz,通过预分频 5999,得到 TIMER4 每个计 数的时钟为 1/(30MHz / (5999+1)) =0.2ms,4000 得到的周期为 0.2ms *4000 =800ms

3)设置 TIMER 允许更新中断

因为我们要使用 TIMER4 的更新中断,寄存器的相应位便可使能更新中断。在库函数里面定时器中断使能是通过timer_interrupt_enable函数来实现的:

void timer_interrupt_enable(uint32_t timer_periph, uint32_t interrupt);

第一个参数是选择定时器号,这个容易理解。

第二个参数非常关键,是用来指明我们使能的定时器中断的类型。

4) TIMER4 中断优先级设置

在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,设置中断优先级。通过nvic_irq_enable 函数实现中断优先级的设置。

针对 TIMR4 初始化范例代码格式

nvic_irq_enable(TIMER4_IRQn, 1, 1);

 5)允许 TIMER工作,也就是使能 TIMER

光配置好定时器还不行,没有开启定时器,照样不能用。我们在配置完后要开启定时器,在固件库里面使能定时器的函数是通过timer_enable函数来实现的

void timer_enable(uint32_t timer_periph)

这个函数非常简单,比如我们要使能TIMER4,方法为:

timer_enable(TIMER4);

6)编写中断服务函数

在最后,还是要编写定时器中断服务函数,通过该函数来处理定时器产生的相关中断。中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。然后执行相关的操作,我们这里使用的是更新(溢出)中断,在处理完中断之后应来清除该中断标志。

在固件库函数里面,用来读取中断状态寄存器的值判断中断类型的函数是:

FlagStatus timer_interrupt_flag_get(uint32_t timer_periph, uint32_t interrupt)

该函数的作用是,判断定时器 TIMER 的中断类型,并判断是否发生中断。

针对 TIMR4 中断服务函数范例代码:

void TIMER4_IRQHandler(void)
{if(SET == timer_interrupt_flag_get(TIMER4, TIMER_INT_UP)){/* clear channel 0 interrupt bit */timer_interrupt_flag_clear(TIMER4, TIMER_INT_UP);gd_eval_led_toggle(LED2);}
}

PWM 输出 TIMER0

高级定时器(TIMER0和TIMER7)是四通道定时器,支持输入捕获和输出比较。可以产生PWM信号控制电机和电源管理。高级定时器含有一个16位无符号计数器。高级定时器是可编程的,可以用来计数,其外部事件可以驱动其他定时器。高级定时器包含了一个死区时间插入模块,非常适合电机控制。

本章,我们使用的是 TIMER0的通道0 输出 PWM(脉冲宽度调制)。

下面我们介绍通过库函数来配置该功能的步骤:

(1)开启 TIMER0 和 GPIO 时钟,配置 PA8复用功能输出。

rcu_periph_clock_enable(RCU_TIMER0);
rcu_periph_clock_enable(RCU_GPIOA);
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);

这里还需要说明一下, 对于定时器通道的引脚关系,引脚的IO口

这里补充说明下关于TIMER的相关GPIO口的命名

TIMERx_CHx : 定时器通道x

TIMERx_CHx_ON :定时器反向通道

TIMERx_BRKIN :刹车引脚

TIMERx_ETI:外部时钟输入

(2)初始化 TIMER0 ,设置 TIMER0 的预分频和周期等参数, ,在上一节定时器中断章节我们已经有讲解,这里就不详细讲解,调用的格式为

timer_initpara.prescaler = 5999;timer_initpara.alignedmode = TIMER_COUNTER_EDGE;timer_initpara.counterdirection = TIMER_COUNTER_UP;timer_initpara.period = 4000;timer_initpara.clockdivision = TIMER_CKDIV_DIV1;timer_initpara.repetitioncounter = 0;
timer_init(TIMER0, &timer_initpara);

3)设置 TIMER0_CH0 的 PWM 模式, 使能 TIMER0 的 CH0 输出。 在库函数中, PWM 通道设置是通过函数timer_channel_output_config来设置的

void timer_channel_output_config(uint32_t timer_periph, uint16_t channel, timer_oc_parameter_struct* ocpara)

我们直接来看看结构体timer_oc_parameter_struct的定义:

typedef struct
{ uint16_t outputstate; /*!< channel output state */uint16_t outputnstate; /*!< channel complementary output state */uint16_t ocpolarity; /*!< channel output polarity */uint16_t ocnpolarity; /*!< channel complementary output polarity */uint16_t ocidlestate; /*!< idle state of channel output */uint16_t ocnidlestate; /*!< idle state of channel complementary output */
}timer_oc_parameter_struct;

 针对 TIMR0 CH0 初始化范例代码格式

/* CH0, CH1 and CH2 configuration in PWM mode */timer_ocinitpara.outputstate = TIMER_CCX_ENABLE;timer_ocinitpara.outputnstate = TIMER_CCXN_DISABLE;timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH;timer_ocinitpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;timer_ocinitpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;timer_ocinitpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
timer_channel_output_config(TIMER0, TIMER_CH_0, &timer_ocinitpara);

4)设置PWM输出以及脉冲宽度占空比

timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, 2000);timer_channel_output_mode_config(TIMER0, TIMER_CH_0, TIMER_OC_MODE_PWM0);timer_channel_output_shadow_config(TIMER0, TIMER_CH_0, TIMER_OC_SHADOW_DISABLE);timer_primary_output_config(TIMER0,ENABLE);

5)使能 TIMER0

在完成以上设置了之后,我们需要使能 TIMER0。使能 TIMER0 的方法前面已经讲解过:

timer_enable(TIMER0);

通过以上 5 个步骤,我们就可以控制 TIMER0的 CH0 输出 PWM 波了。这里特别提醒一下大家,高级定时器虽然和通用定时器类似,但是高级定时器要想输出 PWM,必须多额外加一条函数

void timer_primary_output_config(uint32_t timer_periph, ControlStatus newvalue);

输入捕获 TIMER2

通用定时器L0(TIMER1/2/3/4) 是4通道定时器,支持输入捕获,输出比较,产生PWM信号控制电机和电源管理。通用定时器L0计数器是16位无符号计数器。通用定时器L0是可编程的,可以被用来计数,其外部事件可以驱动其他定时器。

本章要实现通过输入捕获,来获取TIMER2_CH0(PA6)上面的下降沿,下面我们介绍库函数配置上述功能输入捕获的步骤:

1)开启 TIMER2 时钟,配置 PA6为复用功能,并开启上拉电阻。

rcu_periph_clock_enable(RCU_TIMER2);
rcu_periph_clock_enable(RCU_GPIOA);
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_6); //INCUPTURE -TIMER2

跟上一讲 PWM 输出类似,这里我们使用的是定时器2的通道 0,所以我们从对应的数据手册可以查看到对应的 IO 口为 PA6: 

 2) 初始化定时器参数,设置自动重装值, 分频系数,计数方式等

/* TIMER2 configuration */timer_initpara.prescaler = 5999;timer_initpara.alignedmode = TIMER_COUNTER_EDGE;timer_initpara.counterdirection = TIMER_COUNTER_UP;timer_initpara.period = 4000;timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_init(TIMER2, &timer_initpara);

 3)设置 TIMER2 的输入捕获参数,开启输入捕获

库函数是通过 timer_input_capture_config 函数来初始化输入比较参数的: timer_input_capture_config(TIMER2,TIMER_CH_0,&timer_icinitpara);

同样,我们来看看参数设置结构体 TIM_ICInitTypeDef 的定义:

typedef struct
{ uint16_t icpolarity; /*!< channel input polarity */uint16_t icselection; /*!< channel input mode selection */uint16_t icprescaler; /*!< channel input capture prescaler */uint16_t icfilter; /*!< channel input capture filter control */
}timer_ic_parameter_struct;

我们的配置代码是:

/* initialize TIMER channel input parameter struct */timer_channel_input_struct_para_init(&timer_icinitpara);/* TIMER2 CH0 input capture configuration */timer_icinitpara.icpolarity = TIMER_IC_POLARITY_RISING;timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI;timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1;timer_icinitpara.icfilter = 0x0;
timer_input_capture_config(TIMER2,TIMER_CH_0,&timer_icinitpara);

4) 使能捕获中断和NVIC

timer_interrupt_enable(TIMER2,TIMER_INT_CH0);
nvic_irq_enable(TIMER2_IRQn, 1, 1);

5) 编写中断服务函数

void TIMER2_IRQHandler(void){……}

 6) 使能定时器

timer_enable(TIMER2);

通过以上 6 步设置,定时器 2 的通道 0 就可以开始输入捕获了

外部时钟输入 TIMER1

通用定时器L0(TIMER1/2/3/4) 是4通道定时器,支持输入捕获,输出比较,产生PWM信号控制电机和电源管理。通用定时器L0计数器是16位无符号计数器。通用定时器L0是可编程的,可以被用来计数,其外部事件可以驱动其他定时器。

本章要实现使用TIMER1 PA0 作为时钟输入引脚,配置流程:

(1)使能GPIO,TIMER 时钟和GPIO口复用配置

rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_AF);
rcu_periph_clock_enable(RCU_TIMER1);
gpio_init(GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_50MHZ,GPIO_PIN_0);

 

(2)通过查看数据手册,可以看到TIMER1_CH0_ETI,根据前面所讲,是可以支持外部时钟输入的。

TIMER的结构体,初始化定时器参数,设置自动重装值, 分频系数,计数方式等

//ETItimer_initpara.prescaler = 1; // 2 分频timer_initpara.alignedmode = TIMER_COUNTER_EDGE;timer_initpara.counterdirection = TIMER_COUNTER_UP;timer_initpara.period = 65535;timer_initpara.clockdivision = TIMER_CKDIV_DIV1;timer_initpara.repetitioncounter = 0;timer_init(TIMER1,&timer_initpara);
timer_enable(TIMER1);

(3)配置TIMER的时钟来源和时钟源处理的配置

timer_input_trigger_source_select(TIMER1,TIMER_SMCFG_TRGSEL_ETIFP);
timer_external_clock_mode1_config(TIMER1, TIMER_EXT_TRI_PSC_OFF, TIMER_ETP_RISING, 0);

(4)使能TIMER

timer_enable(TIMER1);

5.5.TIMER 使用注意事项

TIMER 高级定时器 做定时用的时候(使用到UPDAT中断),在产生中断之后,高级定时器的其他所有的状态标志位会被置位,但是不会置位中断标志位.

本章内容每日持续更新,如有兴趣,请关注收藏

更多GD32 MCU相关咨询:https://www.gd32bbs.com/ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/380701.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《背包乱斗》为什么好玩 苹果电脑怎么玩《背包乱斗》游戏 mac怎么玩steam windows游戏

在当今竞争激烈的游戏市场中&#xff0c;《背包乱斗》以其独特的魅力在众多作品中脱颖而出&#xff0c;吸引了大量玩家的关注和喜爱。其创新的游戏机制和不断迭代的内容&#xff0c;加之出色的视觉效果和社区建设&#xff0c;使其成为了游戏界的一股清流。 一、《背包乱斗》为…

【计算机视觉】siamfc论文复现实现目标追踪

什么是目标跟踪 使用视频序列第一帧的图像(包括bounding box的位置)&#xff0c;来找出目标出现在后序帧位置的一种方法。 什么是孪生网络结构 孪生网络结构其思想是将一个训练样本(已知类别)和一个测试样本(未知类别)输入到两个CNN(这两个CNN往往是权值共享的)中&#xff0…

jmeter部署

一、windows环境下部署 1、安装jdk并配置jdk的环境变量 (1) 安装jdk jdk下载完成后双击安装包&#xff1a;无限点击"下一步"直到完成&#xff0c;默认路径即可。 (2) jdk安装完成后配置jdk的环境变量 找到环境变量中的系统变量&#xff1a;此电脑 --> 右键属性 …

Figma 中文版指南:获取和安装汉化插件

Figma是一种主流的在线团队合作设计工具&#xff0c;也是一种基于 Web 端的设计工具。在当今的设计时代&#xff0c;Figma 的使用满足了每个人的设计需求&#xff0c;不仅可以实现在线编辑&#xff0c;还可以方便日常管理&#xff0c;有效提高工作效率。然而&#xff0c;相信很…

RPM、YUM 安装 xtrabackup 8 (mysql 热备系列一)包含rpm安装 mysql 8 配置主从

RPM安装 percona-xtrabackup-80-8.0.35-30.1.el7.x86_64.rpm 官网&#xff1a; https://www.percona.com/ 下载地址&#xff1a; https://www.percona.com/downloads wget https://downloads.percona.com/downloads/percona-distribution-mysql-ps/percona-distribution-mysq…

【Vue3】响应式数据

【Vue3】响应式数据 背景简介开发环境基本数据类型对象数据类型使用 reactive 定义对象类型响应式数据使用 ref 定义对象类型响应式数据 ref 和 reactive 的对比使用原则建议 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0…

Linux系统之部署扫雷小游戏(三)

Linux系统之部署扫雷小游戏(三) 一、小游戏介绍1.1 小游戏简介1.2 项目预览二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍三、检查本地环境3.1 检查系统版本3.2 检查系统内核版本3.3 检查软件源四、安装Apache24.1 安装Apache2软件4.2 启动apache2服务4.3 查看apache2服…

项目管理进阶之RACI矩阵

前言 项目管理进阶系列续新篇。 RACI&#xff1f;这个是什么矩阵&#xff0c;有什么用途&#xff1f; 在项目管理过程中&#xff0c;如Team规模超5以上时&#xff0c;则有必要采用科学的管理方式&#xff0c;满足工作需要。否则可能事倍功半。 Q&#xff1a;什么是RACI矩阵 …

【HarmonyOS】HarmonyOS NEXT学习日记:五、交互与状态管理

【HarmonyOS】HarmonyOS NEXT学习日记&#xff1a;五、交互与状态管理 在之前我们已经学习了页面布局相关的知识&#xff0c;绘制静态页面已经问题不大。那么今天来学习一下如何让页面动起来、并且结合所学完成一个代码实例。 交互 如果是为移动端开发应用&#xff0c;那么交…

Unity Apple Vision Pro 开发(四):体积相机 Volume Camera

文章目录 &#x1f4d5;教程说明&#x1f4d5;教程内容概括&#x1f4d5;体积相机作用&#x1f4d5;创建体积相机&#x1f4d5;添加体积相机配置文件&#x1f4d5;体积相机配置文件参数&#x1f4d5;体积相机的边界盒大小&#x1f4d5;体积相机边界盒大小和应用边界盒大小的区别…

弹性网络回归(Elastic Net Regression)

弹性网络回归&#xff08;Elastic Net Regression&#xff09;的详细理论知识推导 理论背景 弹性网络回归结合了岭回归&#xff08;Ridge Regression&#xff09;和Lasso回归&#xff08;Lasso Regression&#xff09;的优点&#xff0c;通过引入两个正则化参数来实现特征选择…

kubernetes k8s Deployment 控制器配置管理 k8s 红蓝部署 金丝雀发布

目录 1、Deployment控制器&#xff1a;概念、原理解读 1.1 Deployment概述 1.2 Deployment工作原理&#xff1a;如何管理rs和Pod&#xff1f; 2、Deployment资源清单文件编写技巧 3、Deployment使用案例&#xff1a;创建一个web站点 4、Deployment管理pod&#xff1a;扩…

通义千问AI模型对接飞书机器人-模型配置(2-1)

一 背景 根据业务或者使用场景搭建自定义的智能ai模型机器人&#xff0c;可以较少我们人工回答的沟通成本&#xff0c;而且可以更加便捷的了解业务需求给出大家设定的业务范围的回答&#xff0c;目前基于阿里云的通义千问模型研究。 二 模型研究 参考阿里云帮助文档&#xf…

持续集成04--Jenkins结合Gitee创建项目

前言 在持续集成/持续部署&#xff08;CI/CD&#xff09;的旅途中&#xff0c;Jenkins与版本控制系统的紧密集成是不可或缺的一环。本篇“持续集成03--Jenkins结合Gitee创建项目”将引导如何将Jenkins与Gitee&#xff08;一个流行的Git代码托管平台&#xff09;相结合&#xff…

修改了mybatis的xml中的sql不重启服务器如何动态加载更新

目录 一、背景 二、注意 三、代码 四、使用示例 五、其他参考博客 一、背景 开发一个报表功能&#xff0c;好几百行sql&#xff0c;每次修改完想自测下都要重启服务器&#xff0c;启动一次服务器就要3分钟&#xff0c;重启10次就要半小时&#xff0c;耗不起时间呀。于是在…

【Android】Activity的生命周期

Activity的生命周期 1.返回栈 其实Android是使用任务&#xff08;task&#xff09;来管理Activity的&#xff0c;一个任务就是一组存放在栈里的Activity的集合&#xff0c;这个栈也被称作返回栈&#xff08;back stack&#xff09;。栈是一种后进先出的数据结构&#xff0c;在…

自动驾驶-预测概览

通过生成一条路径来预测一个物体的行为&#xff0c;在每一个时间段内&#xff0c;为每一辆汽车重新计算预测他们新生成的路径&#xff0c;这些预测路径为规划阶段做出决策提供了必要信息 预测路径有实时性的要求&#xff0c;预测模块能够学习新的行为。我们可以使用多源的数据…

【Unity实战100例】Unity声音可视化多种显示效果

目录 一、技术背景 二、界面搭建 三、 实现 UIAudioVisualizer 基类 四、实现 AudioSampler 类 五、实现 IAudioSample 接口 六、实现MusicAudioVisualizer 七、实现 MicrophoneAudioManager 类 八、实现 MicrophoneAudioVisualizer 类 九、源码下载 Unity声音可视化四…

系统架构设计师教程 第3章 信息系统基础知识-3.6 办公自动化系统(OAS)-解读

系统架构设计师教程 第3章 信息系统基础知识-3.6 办公自动化系统&#xff08;OAS&#xff09; 3.6.1 办公自动化系统的概念3.6.1.1 办公活动3.6.1.1 办公自动化的概念 3.6.2 办公自动化系统的功能3.6.2.1 事务处理3.6.2.1.1 单机系统3.6.2.1.2 多机系统 3.6.2.2 信息管理3.6.2.…

YOLOV8/V7/V5的PCB缺陷检测:可视化界面+GUI+目标计数+视频目标检测与跟踪

在本文中&#xff0c;我将介绍如何使用PyQt5创建一个YOLOv8V7/V5目标检测的可视化界面&#xff0c;可以根据需求选择YOLOv8V7/V5的权重。 该可视化界面的功能丰富&#xff0c;包含内容&#xff1a; 1.GUI目标计数视频目标检测与跟踪 2.完整的OLO数据格式制作流程以及代码 3…