前言

参考文章

GD32替换STM32,这些细节一定要知道

博主-Car12

博主-Leung

不问归期的博客

在线时间戳

资源

Pack包下载GDF10x

GD32F1选型手册

GD数据手册

GD32F10x标准固件库下载

GD32F10x系列MCU用户手册-中英文

GD32F103xx系列MCU数据手册-英文

固件库手册的话可以在下载的固件库压缩包里面找到

注意

以下所说的用户手册指 GD32F10x_User_Manual_Rev2.6_CN

我的工程代码

Github

搭建环境

  • 前往官网下载 STM32F10x Library,下载完解压即可

  • 前往Keil官网下载Pack包, 选择GD32F10x,下载完直接双击它安装即可

  • 打开固件库里的示例工程Project

  • 选择芯片型号,我用的是 GD32F103VET6

  • 修改宏定义,在选型表找到你的MCU型号,查看Flash大小,我的型号FLASH是512KB

中密度产品 (GD32F10x_MD):GD32F101xx和GD32F103xx的闪存存储器容量16K到128K字节之间的产品

高密度产品 (GD32F10x_HD):GD32F101xx和GD32F103xx的闪存存储器容量256K到512K字节之间的产品

超高密度产品 (GD32F10x_XD):GD32F101xx和GD32F103xx的闪存存储器容量大于512K字节的产品

互联型产品(GD32F10x_CL):GD32F105xx和GD32F107xx微控制器

  • Debug那选择你要下载程序的方式,然后把勾全打了即可

  • 启动文件需要保留对应宏定义的那个,详情见 报错集合

  • 编译即可,如果报错就检查一下之前步骤是否正确或者点击 Options of TargetARM Compiler 选项里面切换一下ARM 编译器版本

烧录程序

  • 使用 CMSIS-DAP烧录,Port 选为 SW,速度选择 5MHz,板子硬件连接,工程配置都正确,可以进行下一步下载
DAPLINK模块 GD32管脚
5V 5V
GND GND
SWD DIO
SCK CLK

新建工程模板

  • 直接 New 一个项目,选择对应芯片即可,点击OK,会弹出一个界面让你在线配置文件,直接点击 Cancel,选择手动添加
  • 在工程文件夹下创建3个文件夹,分别是 USERAPPLiability,然后进行添加:

APP文件夹的话是存放自己写的驱动程序的,到时候自行添加

  • 点击 魔法棒 进行设置和添加对应头文件路径

HD这个取决于芯片的Flash大小

USE_STDPERIPH_DRIVER,GD32F10X_HD

  • 点击 正方形 添加对应文件
  • 编译即可

vscode编写

这里不能像MX生成的代码那样直接就在vscode里编译和下载,但是能写代码,编译的话就需要使用终端命令执行bat脚本进行编译,下载的话只能回到Keil里了 目前可以了可看STM32破解那篇文章

.\xxx.bat

芯片介绍

GD32F103VET6信息:最大时钟频率108MHz,Flash512K,SRAM64K,管脚80个,通用定时器 4个,高级定时器2个,基本定时器2个,都是 16位;滴答定时器是 24位,看门狗 2个,RTC 1个,USART+UART 5个,ADC16位 3个,DAC12位 2个,I2C 2个,SPI 3个,CAN2.0B 1个

  • GD32和STM32区别
  1. GD32采用二代的M3内核,STM32主要采用一代M3内核
  2. 主频时钟
  • 使用HSE(高速外部时钟):GD32的主频最大108M,STM32的主频最大72M
  • 使用HSI(高速内部时钟):GD32的主频最大108M,STM32的主频最大64M

主频大意味着单片机代码运行的速度会更快,GD32的 _NOP() 时间比STM32更加短,所以不使用定时器做延时时要注意修改,项目中如果需要进行刷屏,开方运算,电机控制等操作,GD是一个不错的选择

  1. 启动时间

GD32启动时间相同,由于GD运行稍快,需要 延长上电时间配置2ms

  1. 时序要求

GD32对时序要求严格, 配置外设需要先打开时钟,否则可能导致外设无法配置成功;STM32的可以先配置再开时钟。

  1. 供电
GD32F STM32F
外部电压 2.6-3.6V 2.0-3.6V
内核电压 1.2V 1.8V

外部供电:GD32外部供电范围是 2.6-3.6V,STM32外部供电范围是 2.0-3.6V。GD32的供电范围比STM32相对要窄一点

内核电压:GD32内核电压是 1.2V,STM32内核电压是 1.8V。GD的内核电压比STM32的内核电压要低, 所以GD的芯片在运行的时候运行功耗更低

  1. Flash区别
  • Fash擦除时间:GD32的Flash擦除时间要比STM32更长
  • GD32的Flash最大有3M,STM32最大只有1M

  1. 功耗

功耗上GD32的静态功耗要相对高一点

  1. 串口

GD在连续发送数据的时候 每两个字节之间会有一个Bit的Idle,而STM32没有

GD32的串口在发送的时候停止位只有 1/2 两种停止位模式。STM32有 0.5/1/1.5/2 四种停止位模式

  1. ADC
  • GD32的输入阻抗和采样时间的设置和STM32有一定差异,相同配置GD采样的输入阻抗相对来说要小

  • ADC通道要配置成模拟输入,芯片默认是浮空输入,如果不配成模拟输入,ST的可以正常采集,GD不行

  • ADC时钟没有手动配置分频最大运行频率14MHz以内,ST可以正常采集,GD不行

// 采样周期配置如下
rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);
// ADC使能后需要加不少于20us延时
for(i=0;i<0x1000;i++);
  1. FSMC

STM32只有100Pin以上的大容量(256K及以上)才有FSMC,GD32所有的100Pin 或 100Pin以上的都有FSMC

  1. SWD接口

GD32的SWD接口驱动能力比STM32弱,可以有如下几种方式解决:

  • 线尽可能短一些

  • 降低SWD通讯速率

  • SWDIO接10k上拉,SWCLK接10k下拉

  1. BOOT0管脚

GD32的BOOT0必须接 10K下拉 或接 GND,STM32 可悬空

  1. RC复位电路

RC复位电路必须要有,否则MCU可能不能正常工作,STM32有时候可以不要

硬件替换需要注意的地方

  • GD32F103系列和STM32F103系列是兼容的,但也需要一些注意的地方

  • BOOT0必须接10K下拉或接GND,ST可悬空,这点很重要

  • RC复位电路必须要有,否则MCU可能不能正常工作,ST的有时候可以不要

  • 有时候发现用仿真器连接不上。因为GD的SWD接口驱动能力比ST弱,可以有如下几种方式解决:

  1. 线尽可能短一些;
  2. 降低SWD通讯速率;
  3. SWDIO接10k上拉,SWCLK接10k下拉。
  • 使用电池供电等,注意GD的工作电压,例如跌落到2.0V~2.6V区间,ST还能工作,GD可能无法启动或工作异常
  • 在GD32F103小容量产品中使用有源晶振,发现会在MCU的复位管脚一直把电平拉到 0.89V,电平不能保持在高电平。是由于部分有源晶振起振时间太快,复位信号还没有完成导致的。解决方法就是 在有源晶振的输入端与地之前并上一个30pF电容

使用ST标准库开发需要修改的地方

  • 修改外部晶振启动时间

不用外部晶振可跳过这步

由于GD与ST的启动时间存在差异,为了让GD MCU更准确复位,需要对下面参数进行修改:

将宏定义:
#define HSE_STARTUP_TIMEOUT ((uint16_t)0x0500)
修改为:
#define HSE_STARTUP_TIMEOUT ((uint16_t)0xFFFF)
  • 修改主频
  1. 以72MHz运行

只需要修改上面提到的 HSE_STARTUP_TIMEOUT 把这个从 ((uint16_t)0x0500) 改为 ((uint16_t)0xFFFF)

  1. 以108MHz运行

参考文章:https://leung-manwah.blog.csdn.net/article/details/124036410

固件库详解

外设缩写

外设缩写 说明
ADC 模数转换器
BKP 备份寄存器
CAN 局域网控制器模块
CRC 循环冗余校验计算单元
DAC 数模转换器
DBG 调试模块
DMA 直接存储器访问控制器
ENET 以太网控制器模块
EXMC 外部存储器控制器
EXTI 外部中断事件控制器
FMC 闪存控制器
FWDGT 独立看门狗
GPIO/AFIO 通用和备用输入/输出接口
I2C 内部集成电路总线接口
MISC 嵌套中断向量列表控制器
PMU 电源管理单元
RCU 复位和时钟单元
RTC 实时时钟
SDIO SDIO接口
SPI/I2S 串行外设接口/片上音频接口
TIMER 定时器
USART 通用同步异步收发器
WWDGT 窗口看门狗
USBD 通用串行总线全速设备接口
USBFS 通用串行总线全速接口

命名规则

固件库遵从以下命名规则:

  1. XXX表示任一外设缩写,例如: ADC
  2. 源文件和头文件命名都以 “gd32f10x_” 作为开头,例如 gd32f10x_adc.h
  3. 常量仅被应用于一个文件的,定义于该文件中;被应用于多个文件的,在对应头文件中定义。 所有常量都由英文字母大写书写
  4. 寄存器作为常量处理。他们的命名都由英文字母大写书写。在大多数情况下,寄存器缩写规范与本用户手册一致
  5. 变量名采用全部小写,有多个单词组成的,在单词之间以下划线分隔
  6. 外设函数的命名以该外设的缩写加下划线为开头,有多个单词组成的,在单词之间以下划线分隔, 所有外设函数都由英文字母小写书写

时钟树

用户手册5.2—时钟控制单元(CCTL)

AHB、APB和Cortex®-M3时钟 都源自 系统时钟(CK_SYS),系统时钟的时钟源可以选择IRC8M、HXTAL或PLL

GD32F10x系列系统时钟的最大运行时钟频率可以达到 108MHz

AHB、APB2、APB1域的最高时钟频率分别为 108MHz、108MHz、54MHz

RCU通过 AHB 时钟(HCLK)8分频 后作为Cortex系统定时器(SysTick)的外部时钟

ADC时钟由 APB2 时钟经 2、4、6、8、12、16分频 获得

TIMER时钟由 CK_APB1CK_APB2 时钟分频获得,如果APB1分频系数为1那定时器时钟频率就是 x1,如果不为1则 x2

在system_gd32f10x.c里

  • 上电后CPU默认是选择IRC8M(高速内部8MHz时钟)

  • 通过选择宏定义来选择不同的时钟源和主频,默认选择108MHz

  • 这部分会根据上面宏定义选择而自动选择

  • 这个函数就是最终配置时钟的函数

分频系数是在这里定好的

如果按照默认的话,TIM1,2,3,4,5,6,11,12,13的时钟频率就是 APB1/2*2;7,8,9,10的时钟频率就是 APB2/1*1;AHB的时钟频率等于系统时钟频率,系统时钟频率是 108MHz,相当于所有定时器的时钟频率都是 108MHz

可以程序调试看看时钟频率值

uint32_t SYS_CLK,AHB_CLK,APB1_CLK,APB2_CLK;	

SYS_CLK = rcu_clock_freq_get(CK_SYS);
AHB_CLK = rcu_clock_freq_get(CK_AHB);
APB1_CLK = rcu_clock_freq_get(CK_APB1);
APB2_CLK = rcu_clock_freq_get(CK_APB2);

GPIO的使用

操作流程:

  1. 开启时钟
  2. 配置gpio
  3. 设置IO电平

GPIO的位带操作

属于Contex M3 M4的内核有 1M 个区域的RAM和一个 1M 区域的外设地址可以实现位带操作

支持位带操作的两个内存区的范围是:

0x2000_0000-0x200F_FFFF (SRAM区中的最低1MB )

0x4000_0000-0x400F_FFFF (片上外设区中的最低1MB)

对SRAM 位带区的某个比特,记它所在字节地址为A,位序号在别名区的地址为:

AliasAddr = 0x22000000 + ((A - 0x20000000) * 8 + n) * 4 = 0x22000000 + (A - 0x20000000) * 32 + n * 4

对于片上外设位带区的某个比特,记它所在字节的地址为A,位序号为n(0<=n<=7),则该比特在别名区的地址为:

AliasAddr = 0x42000000 + ((A - 0x40000000) * 8 + n) * 4 = 0x42000000 + (A - 0x40000000) * 32 + n * 4

上式中, *4 表示一个字为4 个字节,*8 表示一个字节中有8 个比特,n 表示管脚编号

// IO口操作宏定义
// 作用:是将输入的addr和bitnum转换成位带操作所需的指针
// 过程
1.对输入的addr和0xF0000000进行按位与运算,得到addr的高4位
2.将上述结果加上0x2000000,得到指向位带寄存器区域(SRAM区)的基地址(即0x20000000)  
3.对输入的addr和0xFFFFF进行按位与运算,得到addr的低20位
4.将上述结果左移5位,得到位于位带寄存器区域中的偏移地址
5.对输入的bitnum进行左移2位,得到真实存储单元中的偏移地址  
6.将上面步骤结果加起来得到真实地址,即可访问需要操作的位
#define BITBAND(addr, bitnum) ((addr & 0xF0000000) + 0x2000000 + ((addr & 0xFFFFF)<<5)+ (bitnum<<2))
 
// 作用:将指针转换为一个无符号整型变量,由于涉及到硬件寄存器的读写,因此需要加上volatile关键字以确保读写的可靠性和正确性
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))    
 
// 作用:使用了BITBAND()和MEM_ADDR()两个宏,将输入的addr和bitnum转换成指向位带区域中某一位地址的指针,并将其转换为一个无符号整型变量。具体来说,该宏将输入的addr和bitnum作为参数传递给BITBAND()宏,得到指向正在操作的位的指针。然后,又将该指针传递给MEM_ADDR()宏,将该指针内存中的数据转换成无符号整型变量并返回,从而实现对该位的读写操作
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))    
  • 在GD32手册可以看到 GPIO寄存器基地址

  • 然后找 端口输出控制寄存器ODR 的偏移地址(GD32里叫做OCTL)

// IO输出口地址映射
#define GPIOA_ODR_Addr    (GPIOA+12) // 0X4001 080C = 0x4001 0800 + C 
#define GPIOB_ODR_Addr    (GPIOB+12) // 0X4001 0C0C = 0x4001 0C00 + C
#define GPIOC_ODR_Addr    (GPIOC+12) // 0x4001 100C = 0x4001 1000 + C 
#define GPIOD_ODR_Addr    (GPIOD+12) // 0x4001 140C = 0x4001 1400 + C
#define GPIOE_ODR_Addr    (GPIOE+12) // 0x4001 180C = 0x4001 1800 + C
#define GPIOF_ODR_Addr    (GPIOF+12) // 0x4001 1C0C = 0x4001 1C00 + C   
#define GPIOG_ODR_Addr    (GPIOG+12) // 0x4001 200C = 0x4001 2000 + C
  • 然后找 端口输入状态寄存器IDR 的偏移地址(GD32里叫做ISTAT)

// IO输入口地址映射
#define GPIOA_IDR_Addr    (GPIOA+8) // 0X4001 0808 = 0x4001 0800 + 8
#define GPIOB_IDR_Addr    (GPIOB+8) // 0X4001 0C08 = 0x4001 0C00 + 8 
#define GPIOC_IDR_Addr    (GPIOC+8) // 0x4001 1008 = 0x4001 1000 + 8
#define GPIOD_IDR_Addr    (GPIOD+8) // 0x4001 1408 = 0x4001 1400 + 8
#define GPIOE_IDR_Addr    (GPIOE+8) // 0x4001 1808 = 0x4001 1800 + 8
#define GPIOF_IDR_Addr    (GPIOF+8) // 0x4001 1C08 = 0x4001 1C00 + 8
#define GPIOG_IDR_Addr    (GPIOG+8) // 0x4001 2008 = 0x4001 2000 + 8
  • IO口操作,只对单一的IO口,确保n的值小于16
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  //输入

#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)  //输出 
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)  //输入

串口

需要注意的是GD32串口是从 0 开始的,跟STM32不一样,所以STM32的串口1相当于GD32的串口0

串口中断方式接收数据和输出重定向

可以看到串口0的引脚可以是 PA9,PA10,PB6,PB7

myUart1.h
/*
*@Description: 串口1
*@Author: Yang
*@Date: 2023-05-09 21:06:37
*/
#ifndef __MYUART1_H
#define __MYUART1_H
#include "AllHead.h"

// 重映射需要打开这个宏
// #define USART0_REMAP
// 串口0接收的最大长度
#define UART0_MAX_LEN   50


typedef struct
{
    // 串口0接收数据完成标志位
    bool bUart0_Rx_Over_Flag;
    // 串口0接收的数据长度
    uint8_t ucUart0_Rx_Len;
    // 串口0接收数组
    uint8_t ucUart0_Rx_Buff[UART0_MAX_LEN];
    void (*vUART0_Init)(void);
    void (*vUSART0_Data_Process)(void);
}MyUart0_TypeDef;


extern MyUart0_TypeDef MyUart0;

void vUART0_Init(void);
int fputc(int ch,FILE *f);
void vUSART0_Data_Process(void);
#endif
myUart1.c
#include "myUart1.h"

/*====================================变量区 BEGIN====================================*/
MyUart0_TypeDef MyUart0 = 
{
    .bUart0_Rx_Over_Flag = 0,
    .ucUart0_Rx_Len = 0,
    .ucUart0_Rx_Buff = {0},
    .vUART0_Init = &vUART0_Init,
    .vUSART0_Data_Process = &vUSART0_Data_Process
};

/*====================================变量区    END====================================*/

/*====================================静态内部函数声明区 BEGIN====================================*/
static void svUART0_Interrupt_Init(void);
/*====================================静态内部函数声明区    END====================================*/


/*
 * @description: 串口1初始化
 * @return {*}
 * @Date: 2023-05-09 21:21:23
 */
// 串口1初始化
void vUART0_Init(void)
{
    // 使能串口1时钟
    rcu_periph_clock_enable(RCU_USART0);

// 如果引脚需要重映射
#ifdef USART0_REMAP
    // 使能GPIO时钟
    rcu_periph_clock_enable(GPIOB);
    rcu_periph_clock_enable(RCU_AF);
    // 串口1重映射使能
    gpio_pin_remap_config(GPIO_USART0_REMAP, ENABLE);
    // TX--复用推挽
    gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
    // RX--浮空输入
    gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
#else
    // 使能GPIO时钟
    rcu_periph_clock_enable(RCU_GPIOA);
    // TX--复用推挽
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
    // RX--浮空输入
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
#endif
    // 串口参数初始化
    // 重置串口1
    usart_deinit(USART0);
    // 设置波特率
    usart_baudrate_set(USART0,115200U);
    // 设置数据长度
    usart_word_length_set(USART0,USART_WL_8BIT);
    // 设置停止位
    usart_stop_bit_set(USART0,USART_STB_1BIT);
    // 设置检验位
    usart_parity_config(USART0,USART_PM_NONE);
    // 硬件流控制--关闭
    usart_hardware_flow_rts_config(USART0,USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(USART0,USART_CTS_DISABLE);
    // 串口接收使能
    usart_receive_config(USART0,USART_RECEIVE_ENABLE);
    // 串口发送使能
    usart_transmit_config(USART0,USART_TRANSMIT_ENABLE);
    // 使能串口
    usart_enable(USART0);

    svUART0_Interrupt_Init();
}

/*
 * @description: 串口1重定向
 * @param {int} ch
 * @param {FILE} *f
 * @return {*}
 * @Date: 2023-05-10 14:53:40
 */
// 串口1重定向
int fputc(int ch,FILE *f)
{
    usart_data_transmit(USART0,(uint8_t)ch);
    int Cnt = 1000;
    while(RESET == usart_flag_get(USART0,USART_FLAG_TBE) && Cnt--);
    return ch;
}

/*
 * @description: 使能串口0中断
 * @return {*}
 * @Date: 2023-05-10 16:02:09
 */
// 使能串口0中断
static void svUART0_Interrupt_Init(void)
{
    // 中断管理器使能,并分配优先级
    nvic_irq_enable(USART0_IRQn,1,1);
    // 清除中断标志
    usart_interrupt_flag_clear(USART0,USART_INT_FLAG_RBNE);
    usart_interrupt_flag_clear(USART0,USART_INT_FLAG_IDLE);
    // 使能串口中断
    usart_interrupt_enable(USART0,USART_INT_RBNE);  // 读数据缓冲区非空中断和过载错误中断
    usart_interrupt_enable(USART0,USART_INT_IDLE);  // IDLE线检测中断
}

/*
 * @description: 串口0中断服务函数
 * @return {*}
 * @Date: 2023-05-10 18:43:08
 */
// 串口0中断服务函数
void USART0_IRQHandler(void)
{
    // 读取缓冲区不为空
    if(SET == usart_interrupt_flag_get(USART0,USART_INT_FLAG_RBNE))
    {
        // 读取数据存储起来
        MyUart0.ucUart0_Rx_Buff[MyUart0.ucUart0_Rx_Len++] = usart_data_receive(USART0);
    }
    else if(SET == usart_interrupt_flag_get(USART0,USART_INT_FLAG_IDLE))
    {
        // 清除空闲中断标志位
        usart_data_receive(USART0);
        // 另一种清除空闲中断标志位方法
        // usart_interrupt_flag_clear(USART0,USART_INT_FLAG_IDLE);
        // 接收完一帧数据标志位
        MyUart0.bUart0_Rx_Over_Flag = 1;
    }
}

/*
 * @description: 对接收的数据进行处理
 * @return {*}
 * @Date: 2023-05-10 18:45:09
 */
// 对接收的数据进行处理
void vUSART0_Data_Process(void)
{
    if(MyUart0.bUart0_Rx_Over_Flag)
    {
        MyUart0.bUart0_Rx_Over_Flag = 0;
        printf("Read Len: %d ",MyUart0.ucUart0_Rx_Len);
        // 数组清0,索引清0
        for(uint8_t i = 0; i < MyUart0.ucUart0_Rx_Len; i++)
        {
            printf("%02x  ",(uint32_t)MyUart0.ucUart0_Rx_Buff[i]);
        }
        printf("\r\n");
        memset(MyUart0.ucUart0_Rx_Buff,0,sizeof(MyUart0.ucUart0_Rx_Buff));
        MyUart0.ucUart0_Rx_Len = 0;
    }
}

实验现象

串口DMA+IDLE方式

DMA控制器有 12个通道( DMA0有 7个通道, DMA1有 5个通道)。每个通道都是专门用来处理一个或多个外设的,支持软件优先级(低、中、高、极高)和硬件优先级(通道号越低,优先级越高)

注意仅 HD、 XD和 CL型产品中有 DMA1控制器 (数据手册有说明)

然后通过手册里的这个表可以知道对应通道是用于哪个外设的

myUart1.c
/*
 * @description: 串口1初始化
 * @return {*}
 * @Date: 2023-05-09 21:21:23
 */
// 串口1初始化
void vUART0_Init(void)
{
    //串口中断初始化(屏蔽)
    // svUART0_Interrupt_Init();
    // 串口DMA初始化
    svUSART0_Dma_Init(UART0_MAX_LEN);
    // 使能空闲中断
    svUSART0_Enable_Idle_Interrupt();
}

/*
 * @description: 串口0中断服务函数
 * @return {*}
 * @Date: 2023-05-10 18:43:08
 */
// 串口0中断服务函数
void USART0_IRQHandler(void)
{
    if (SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE))
    {
        // 清除标志位
        usart_data_receive(USART0);
        dma_channel_disable(DMA0, DMA_CH4);
        if (UART0_MAX_LEN)
        {
            MyUart0.ucUart0_Rx_Len = UART0_MAX_LEN - dma_transfer_number_get(DMA0, DMA_CH4);
            if((MyUart0.ucUart0_Rx_Len != 0) && (MyUart0.ucUart0_Rx_Len < UART0_MAX_LEN))
            {
                MyUart0.bUart0_Rx_Over_Flag = 1;
            }
        }
    }
}

/*
 * @description: DMA中断函数
 * @return {*}
 * @Date: 2023-05-11 14:49:08
 */
// DMA中断函数
void DMA0_Channel4_IRQHandler(void)
{
    // DMA通道传输完成标志
    if(dma_interrupt_flag_get(DMA0, DMA_CH4, DMA_INT_FLAG_FTF))
    {
        // 清除DMA中断标志
        dma_interrupt_flag_clear(DMA0, DMA_CH4, DMA_INT_FLAG_G);
    }
}

/*
 * @description: 对接收的数据进行处理
 * @return {*}
 * @Date: 2023-05-10 18:45:09
 */
// 对接收的数据进行处理
void vUSART0_Data_Process(void)
{
    if(MyUart0.bUart0_Rx_Over_Flag)
    {
        MyUart0.bUart0_Rx_Over_Flag = 0;
        printf("Read Len: %d ", MyUart0.ucUart0_Rx_Len);
        // 数组清0,索引清0
        for(uint8_t i = 0; i < MyUart0.ucUart0_Rx_Len; i++)
        {
            printf("%02x  ", (uint32_t)MyUart0.ucUart0_Rx_Buff[i]);
        }
        printf("\r\n");
        memset(MyUart0.ucUart0_Rx_Buff, 0, sizeof(MyUart0.ucUart0_Rx_Buff));
        MyUart0.ucUart0_Rx_Len = 0;
        // 重新打开DMA接收
        svUSART0_Again_Dma_Tx();
    }
}

/*
 * @description: 串口0DMA初始化
 * @param {uint32_t} hope_len 希望接收的数据个数
 * @return {*}
 * @Date: 2023-05-11 13:20:59
 */
// 串口0DMA初始化
static void svUSART0_Dma_Init(uint32_t hope_len)
{
#define USART0_DATA_ADDRESS ((uint32_t)&USART_DATA(USART0))

    dma_parameter_struct myDMA1;
    // 使能DMA0时钟
    rcu_periph_clock_enable(RCU_DMA0);
    // 开启通道4中断
    nvic_irq_enable(DMA0_Channel4_IRQn, 0, 1);
    // 重置
    dma_deinit(DMA0, DMA_CH4);
    dma_struct_para_init(&myDMA1);
    // 方向--外设到内存
    myDMA1.direction = DMA_PERIPHERAL_TO_MEMORY;
    // 内存自动增长
    myDMA1.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    // 外设地址不自动增长
    myDMA1.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    // DMA优先级--高
    myDMA1.priority = DMA_PRIORITY_HIGH;
    // 缓冲区大小
    myDMA1.number = hope_len;
    // 数据长度--8位
    myDMA1.memory_width = DMA_MEMORY_WIDTH_8BIT;
    // 接收缓冲区开始地址
    myDMA1.memory_addr = (uint32_t)MyUart0.ucUart0_Rx_Buff;
    // 外输数据宽度
    myDMA1.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
    // 外设寄存器地址
    myDMA1.periph_addr = USART0_DATA_ADDRESS;
    // 初始化
    dma_init(DMA0, DMA_CH4, &myDMA1);
    // 循环传输模式关闭
    dma_circulation_disable(DMA0, DMA_CH4);
    // 数据传输方式不是内存--->内存
    dma_memory_to_memory_disable(DMA0, DMA_CH4);
    // 串口DMA数据接收使能
    usart_dma_receive_config(USART0, USART_RECEIVE_DMA_ENABLE);
    // 串口DMA接收完成中断使能(这个无所谓)
    dma_interrupt_enable(DMA0, DMA_CH4, DMA_INT_FTF);
    // 启动指定的DMA通道
    dma_channel_enable(DMA0, DMA_CH4);
}


/*
 * @description: 开启串口空闲中断
 * @return {*}
 * @Date: 2023-05-11 14:29:22
 */
// 开启串口空闲中断
static void svUSART0_Enable_Idle_Interrupt(void)
{
    nvic_irq_enable(USART0_IRQn, 0, 0);
    usart_interrupt_enable(USART0, USART_INT_IDLE);
}

/*
 * @description: 不使用连续模式下重新发起一次代码传输
 * @return {*}
 * @Date: 2023-05-11 14:32:21
 */
// 不使用连续模式下重新发起一次代码传输
static void svUSART0_Again_Dma_Tx(void)
{
    svUSART0_Enable_Idle_Interrupt();
    dma_channel_disable(DMA0, DMA_CH4);
    DMA_CHCNT(DMA0, DMA_CH4) = UART0_MAX_LEN;
    dma_channel_enable(DMA0, DMA_CH4);
}

实验现象

同上

定时器

常用寄存器(可直接操作):

TIMER_CHxCV:通道x捕获/比较值寄存器,即pulse

TIMER_CAR:重装载值ARR寄存器

TIMER_PSC:预分频PSC寄存器

TIMER_CNT:计数CNT寄存器

// 使用方法
TIMER_CHxCV(TIMER2);
TIMER_CAR(TIMER2);
TIMER_PSC(TIMER2);
TIMER_CNT(TIMER2);

普通定时

具体定时器不知道是哪几个的话可以通过查看 gd32f10x.h 找到芯片对应的那个md宏,我的是 GD32F10X_HD,包含下面:

高级定时器:0,7 — 4通道,支持输入捕获和输出比较,还包含一个死区时间插入,适用于电机控制

通用定时器L0:1,2,3,4 — 4通道,支持输入捕获和输出比较

基本定时器:5,6 – 用作计时和DAC提供时钟

注意:通用定时器L1和L2(8,11,9,10,12,13) 仅可用于超高密度(XD)产品中,详情看数据手册定时器那篇

对应的时钟是(看时钟树):

APB2:0,7,8,9,10

APB1:1,2,3,4,5,6,11,12,13

myTIMER.h
/*
*@Description: 定时器
*@Author: Yang
*@Date: 2023-05-11 19:42:48
*/
#ifndef __MYTIMER_H
#define __MYTIMER_H
#include "AllHead.h"

typedef struct 
{
    void (*vTIMER5_init)(uint16_t,uint16_t);
}MyTIMER5_TypeDef;


extern MyTIMER5_TypeDef MyTimer5;

void vTIMER5_init(uint16_t psc,uint16_t arr);
#endif
myTIMER.c
/*
*@Description: 定时器
*@Author: Yang
*@Date: 2023-05-11 19:42:10
*/
#include "myTIMER.h"

/*====================================变量区 BEGIN====================================*/
MyTIMER5_TypeDef MyTimer5 = 
{
    .vTIMER5_init = &vTIMER5_init
};
/*====================================变量区    END====================================*/


/*
 * @description: 定时器5初始化
 * @param {uint16_t} psc 预分频值
 * @param {uint16_t} arr 重装载值
 * @return {*}
 * @Date: 2023-05-11 21:27:05
 */
// 定时器5初始化
void vTIMER5_init(uint16_t psc,uint16_t arr)
{
    timer_parameter_struct myTIMER5;
    // 开启定时器时钟
    rcu_periph_clock_enable(RCU_TIMER5);
    // 结构体复位初始化
    timer_deinit(TIMER5);
    // 初始化定时器结构体
    timer_struct_para_init(&myTIMER5);
    // 预分频--psc值
    myTIMER5.prescaler = psc;
    // 对齐模式
    myTIMER5.alignedmode = TIMER_COUNTER_EDGE;
    // 计数方向--向上计数
    myTIMER5.counterdirection = TIMER_COUNTER_UP;
    // 重装载值--arr
    myTIMER5.period = arr;
    // 时钟分频因子--不分频
    myTIMER5.clockdivision = TIMER_CKDIV_DIV1;
    // 计数器重复计数次数-- 0~255(高级定时器才有)
    myTIMER5.repetitioncounter = 0;
    // 初始化
    timer_init(TIMER5,&myTIMER5);
    // 显式清除中断标志位
    timer_interrupt_flag_clear(TIMER5,TIMER_INT_FLAG_UP);
    // 开启中断
    timer_interrupt_enable(TIMER5, TIMER_INT_UP);
    // 配置中断函数和优先级
    nvic_irq_enable(TIMER5_IRQn,0,0);
    // 使能
    timer_enable(TIMER5);
}


/*
 * @description: 定时器5中断函数
 * @return {*}
 * @Date: 2023-05-12 08:40:32
 */
// 定时器5中断函数
void TIMER5_IRQHandler(void)
{
    if(SET == timer_interrupt_flag_get(TIMER5, TIMER_INT_UP))
    {
        // 清除标志位
        timer_interrupt_flag_clear(TIMER5, TIMER_INT_UP);

        static uint16_t Led_Cnt = 0;

        Led_Cnt++;
        if(100 == Led_Cnt)
        {
            Led_Cnt = 0;
            MyLed.bLed1_State = !MyLed.bLed1_State;
            MyLed.vLED_Control(MyLed.bLed1_State);
        }
    }
}
main.c
// 定时1ms ---> 108000000Hz/108/1000 = 1000Hz,1/1000Hz = 0.001s = 1ms
MyTimer.vTIMER5_init(107,999);

PWM输出

注意:基本定时器是没有PWM功能的

对于普通PWM输出的话只需要配置 outputstate(通道使能)ocpolarity(通道极性) 即可(对于除了高级定时器0/7来说),然后不要忘记把管脚设置为 复用推挽输出

定时器通道对应的管脚可以查询数据手册,比如下面使用的是TIMER1:

myTIMER.c
/*
 * @description: 定时器1PWM初始化
 * @param {uint16_t} psc 预分频
 * @param {uint16_t} arr 重装载值
 * @param {uint16_t} ch0_duty 通道0占空比
 * @param {uint16_t} ch1_duty 通道1占空比
 * @param {uint16_t} ch2_duty 通道2占空比
 * @param {uint16_t} ch3_duty 通道3占空比
 * @return {*}
 * @Date: 2023-05-12 15:52:50
 */
// 定时器1PWM初始化
void vTIMER1_Pwm_Init(uint16_t psc,uint16_t arr,uint16_t ch0_duty,uint16_t ch1_duty,uint16_t ch2_duty,uint16_t ch3_duty)
{
    timer_parameter_struct myTIMER1;
    timer_oc_parameter_struct myTIMER1_OC;

    // 开启定时器时钟和复用时钟
    rcu_periph_clock_enable(RCU_TIMER1);
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
    // 结构体复位初始化
    timer_deinit(TIMER1);

    // 初始化定时器结构体
    timer_struct_para_init(&myTIMER1);
    // 预分频--psc值
    myTIMER1.prescaler = psc;
    // 对齐模式
    myTIMER1.alignedmode = TIMER_COUNTER_EDGE;
    // 计数方向--向上计数
    myTIMER1.counterdirection = TIMER_COUNTER_UP;
    // 重装载值--arr
    myTIMER1.period = arr;
    // 时钟分频因子--不分频
    myTIMER1.clockdivision = TIMER_CKDIV_DIV1;
    // 初始化
    timer_init(TIMER1,&myTIMER1);

    // 通道使能
    myTIMER1_OC.outputstate = TIMER_CCX_ENABLE;
    // 通道极性--高电平有效
    myTIMER1_OC.ocpolarity = TIMER_OC_POLARITY_HIGH;
    // ------通道0配置-------
    // 外设TIMERx的通道输出配置
    timer_channel_output_config(TIMER1,TIMER_CH_0,&myTIMER1_OC);
    // 通道占空比设置
    timer_channel_output_pulse_value_config(TIMER1,TIMER_CH_0,ch0_duty);
    // 通道模式---PWM0模式
    timer_channel_output_mode_config(TIMER1,TIMER_CH_0,TIMER_OC_MODE_PWM0);
    // 不使用输出比较影子寄存器
    timer_channel_output_shadow_config(TIMER1,TIMER_CH_0,TIMER_OC_SHADOW_DISABLE);
    // ------通道1配置-------
    // 外设TIMERx的通道输出配置
    timer_channel_output_config(TIMER1,TIMER_CH_1,&myTIMER1_OC);
    // 通道占空比设置
    timer_channel_output_pulse_value_config(TIMER1,TIMER_CH_1,ch1_duty);
    // 通道模式---PWM0模式
    timer_channel_output_mode_config(TIMER1,TIMER_CH_1,TIMER_OC_MODE_PWM0);
    // 不使用输出比较影子寄存器
    timer_channel_output_shadow_config(TIMER1,TIMER_CH_1,TIMER_OC_SHADOW_DISABLE);
    // ------通道2配置-------
    // 外设TIMERx的通道输出配置
    timer_channel_output_config(TIMER1,TIMER_CH_2,&myTIMER1_OC);
    // 通道占空比设置
    timer_channel_output_pulse_value_config(TIMER1,TIMER_CH_2,ch2_duty);
    // 通道模式---PWM0模式
    timer_channel_output_mode_config(TIMER1,TIMER_CH_2,TIMER_OC_MODE_PWM0);
    // 不使用输出比较影子寄存器
    timer_channel_output_shadow_config(TIMER1,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);
    // ------通道3配置-------
    // 外设TIMERx的通道输出配置
    timer_channel_output_config(TIMER1,TIMER_CH_3,&myTIMER1_OC);
    // 通道占空比设置
    timer_channel_output_pulse_value_config(TIMER1,TIMER_CH_3,ch3_duty);
    // 通道模式---PWM0模式
    timer_channel_output_mode_config(TIMER1,TIMER_CH_3,TIMER_OC_MODE_PWM0);
    // 不使用输出比较影子寄存器
    timer_channel_output_shadow_config(TIMER1,TIMER_CH_3,TIMER_OC_SHADOW_DISABLE);
    // 使能自动重装载
    timer_auto_reload_shadow_enable(TIMER1);
    // 使能定时器1
    timer_enable(TIMER1);
}
myTIMER.h
typedef struct
{
    void (*vTIMER5_init)(uint16_t, uint16_t);
    void (*vTIMER1_Pwm_Init)(uint16_t, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t);
} MyTIMER_TypeDef;


void vTIMER1_Pwm_Init(uint16_t psc, uint16_t arr, uint16_t ch0_duty, uint16_t ch1_duty, uint16_t ch2_duty, uint16_t ch3_duty);

实验现象

通过示波器,可以发现4个管脚的占空比是跟程序一样的

PWM输入捕获

  • 我这里使用的是TIMER2的通道0进行捕获,具体对应哪个引脚可以看数据手册重映射那里

  • PWM输入捕获的话需要把引脚设置为 浮空输入上拉输入,记得开复用时钟
  • icpolarityicselection 的问题
  1. icselection 等于 TIMER_IC_SELECTION_DIRECTTI(直连模式)时,输入捕获触发信号和捕获到的信号极性是相同的,即输入捕获触发信号的边沿和捕获到的信号边沿的极性相同
  2. icselection 等于 TIMER_IC_SELECTION_INDIRECTTI(间接模式)时,输入捕获触发信号和捕获到的信号极性是相反的,即输入捕获触发信号的上升沿触发捕获,但是捕获到的信号却是下降沿
  3. 直连模式下icpolarity的设置与捕获的信号极性无关,如果您需要在间接连接模式下捕获下降沿信号,则需要将ICPolarity参数设置为TIM_ICPolarity_Falling。这样,在输入捕获触发信号的上升沿时,定时器将开始计数,并在捕获到信号下降沿时触发输入捕获中断

定时器0触发定时器1计数可参考:https://blog.csdn.net/u010261063/article/details/124068043

myTIMER.c
/*
 * @description: 定时器2初始化PWM捕获
 * @return {*}
 * @Date: 2023-05-12 21:43:32
 */
// 定时器2初始化PWM捕获
void vTIMER2_Init(void)
{
    timer_parameter_struct myTIMER2;
    timer_ic_parameter_struct myTIMER2_IC;

    // 使能时钟
    rcu_periph_clock_enable(RCU_GPIOA);
    // 使能复用时钟
    rcu_periph_clock_enable(RCU_AF);
    // 配置 PA6为CH0的复用模式
    gpio_init(GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_50MHZ,GPIO_PIN_6);
    // 使能定时器2
    rcu_periph_clock_enable(RCU_TIMER2);
    // -----------------定时器基本+中断配置-----------------
   // 结构体复位初始化
    timer_deinit(TIMER2);
    // 预分频--psc值
    myTIMER2.prescaler = 107;
    // 对齐模式
    myTIMER2.alignedmode = TIMER_COUNTER_EDGE;
    // 计数方向--向上计数
    myTIMER2.counterdirection = TIMER_COUNTER_UP;
    // 重装载值--arr(设置为最大值即可)
    myTIMER2.period = 65535;
    // 时钟分频因子--不分频
    myTIMER2.clockdivision = TIMER_CKDIV_DIV1;
    // 初始化
    timer_init(TIMER2,&myTIMER2);
    // 清除中断标志---通道0
    timer_interrupt_flag_clear(TIMER2,TIMER_INT_FLAG_CH0);
    // 开启中断
    timer_interrupt_enable(TIMER2, TIMER_INT_FLAG_CH0);
    // 配置中断函数和优先级
    nvic_irq_enable(TIMER2_IRQn,0,1);
    // -----------------定时器输入捕获配置-----------------
    // 通道输入极性---上升沿
    myTIMER2_IC.icpolarity = TIMER_IC_POLARITY_RISING;
    // 通道输入模式选择---直连
    myTIMER2_IC.icselection = TIMER_IC_SELECTION_DIRECTTI;
    // 通道输入捕获预分频---不分频
    myTIMER2_IC.icprescaler = TIMER_IC_PSC_DIV1;
    // 通道输入捕获滤波(0~15)
    myTIMER2_IC.icfilter = 0;
    // 配置输入捕获参数
    timer_input_capture_config(TIMER2,TIMER_CH_0,&myTIMER2_IC);
    // 自动重装载使能
    timer_auto_reload_shadow_enable(TIMER2);
    // 使能
    timer_enable(TIMER2);
}

/*
 * @description: 定时器2中断函数
 * @return {*}
 * @Date: 2023-05-12 21:44:27
 */
// 定时器2中断函数
void TIMER2_IRQHandler(void)
{
    static uint32_t Last_ic_Value = 0;
    uint32_t ic_Value = 0;

    if(SET == timer_interrupt_flag_get(TIMER2,TIMER_INT_FLAG_CH0))
    {
        // 清除中断标志位
        timer_interrupt_flag_clear(TIMER2,TIMER_INT_FLAG_CH0);
        // 读取通道捕获值
        ic_Value = timer_channel_capture_value_register_read(TIMER2,TIMER_CH_0);
        // 如果上一个值存在
        if(Last_ic_Value)
        {
            if(ic_Value > Last_ic_Value)
            {
                MyTimer.ulTimer2_IC_Fre = 1000000 / (ic_Value - Last_ic_Value);
            }
            else
            {
                // 说明已经溢出,需要加最大重装载值(都是16位所以是0xFFFF,32位则0xFFFFFFFF)
                MyTimer.ulTimer2_IC_Fre = 1000000 / (0xFFFF + ic_Value - Last_ic_Value);
            }
            MyTimer.bTimer2_IC_Over_Flag = 1;
        }
        Last_ic_Value = ic_Value;
    }
}
myTIMER.h
typedef struct 
{
    // 捕获完成标志位
    bool bTimer2_IC_Over_Flag;
    // 定时器2输入捕获的占空比
    uint8_t ucTimer2_IC_Duty;
    // 定时器2输入捕获的频率
    uint32_t ulTimer2_IC_Fre;
}MyTIMER_TypeDef;

实验现象

杜邦线连接起来即可

利用通道0,1测量PWM的频率和占空比

在上一个例程的基础上添加/修改即可

其实跟STM32的差不多

myTIMER.c
/*
 * @description: 定时器2初始化PWM捕获
 * @return {*}
 * @Date: 2023-05-12 21:43:32
 */
// 定时器2初始化PWM捕获
void vTIMER2_Init(void)
{
    timer_parameter_struct myTIMER2;
    timer_ic_parameter_struct myTIMER2_IC;

    // 使能时钟
    rcu_periph_clock_enable(RCU_GPIOA);
    // 使能复用时钟
    rcu_periph_clock_enable(RCU_AF);
    // 配置 PA6为CH0的复用模式
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
    // 使能定时器2
    rcu_periph_clock_enable(RCU_TIMER2);
    // -----------------定时器基本+中断配置-----------------
    // 结构体复位初始化
    timer_deinit(TIMER2);
    // 预分频--psc值
    myTIMER2.prescaler = 107;
    // 对齐模式
    myTIMER2.alignedmode = TIMER_COUNTER_EDGE;
    // 计数方向--向上计数
    myTIMER2.counterdirection = TIMER_COUNTER_UP;
    // 重装载值--arr(设置为最大值即可)
    myTIMER2.period = 65535;
    // 时钟分频因子--不分频
    myTIMER2.clockdivision = TIMER_CKDIV_DIV1;
    // 初始化
    timer_init(TIMER2, &myTIMER2);
    // 清除中断标志---通道0
    timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_CH0);
    // 开启中断
    timer_interrupt_enable(TIMER2, TIMER_INT_FLAG_CH0);
    // 配置中断函数和优先级
    nvic_irq_enable(TIMER2_IRQn, 0, 1);
    // -----------------定时器输入捕获配置-----------------
    // 通道输入极性---上升沿
    myTIMER2_IC.icpolarity = TIMER_IC_POLARITY_RISING;
    // 通道输入模式选择---直连
    myTIMER2_IC.icselection = TIMER_IC_SELECTION_DIRECTTI;
    // 通道输入捕获预分频---不分频
    myTIMER2_IC.icprescaler = TIMER_IC_PSC_DIV1;
    // 通道输入捕获滤波(0~15)
    myTIMER2_IC.icfilter = 0;
    // 捕获PWM输入参数
    timer_input_pwm_capture_config(TIMER2,TIMER_CH_0,&myTIMER2_IC);
    // 输入触发源选择---滤波后的通道0输入
    timer_input_trigger_source_select(TIMER2,TIMER_SMCFG_TRGSEL_CI0FE0);
    // 从模式配置---复位模式
    timer_slave_mode_select(TIMER2,TIMER_SLAVE_MODE_RESTART);
    // 主从模式配置---使能
    timer_master_slave_mode_config(TIMER2,TIMER_MASTER_SLAVE_MODE_ENABLE);
    // 自动重装载使能
    timer_auto_reload_shadow_enable(TIMER2);
    // 使能
    timer_enable(TIMER2);
}

/*
 * @description: 定时器2中断函数
 * @return {*}
 * @Date: 2023-05-12 21:44:27
 */
// 定时器2中断函数
void TIMER2_IRQHandler(void)
{
    uint32_t ic_Value1 = 0, ic_Value2 = 0;

    if(SET == timer_interrupt_flag_get(TIMER2, TIMER_INT_FLAG_CH0))
    {
        // 清除中断标志位
        timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_CH0);
         // 读取通道0捕获值---周期时间
        ic_Value1 = timer_channel_capture_value_register_read(TIMER2, TIMER_CH_0) + 1;
        // 也可以这样写
        //ic_Value1 = TIMER_CH0CV(TIMER2) + 1;
        if(ic_Value1 != 0)
        {
             // 读取通道1捕获值---高电平时间
            ic_Value2 = timer_channel_capture_value_register_read(TIMER2, TIMER_CH_1) + 1;
            // 也可以这样写
            //ic_Value1 = TIMER_CH1CV(TIMER2) + 1;
            MyTimer.ulTimer2_IC_Fre = (float)1000000 / ic_Value1;
            MyTimer.ucTimer2_IC_Duty = (ic_Value2 * 100) / ic_Value1;
            MyTimer.bTimer2_IC_Over_Flag = 1;
        }
        else
        {
            MyTimer.ulTimer2_IC_Fre = 0;
            MyTimer.ucTimer2_IC_Duty = 0;
        }
    }
}

实验现象

PWM3路互补输出

  • 这里使用TIMER0进行测试,也可以使用另一个高级定时器TIMER7

myTIMER.c
/*
 * @description: 定时器0PWM初始化
 * @return {*}
 * @Date: 2023-05-13 22:00:00
 */
// 定时器0PWM初始化
void vTIMER0_Pwn_Init(uint16_t psc,uint16_t arr,uint16_t ch0_duty,uint16_t ch1_duty,uint16_t ch2_duty)
{
    timer_parameter_struct myTIMER0;
    timer_oc_parameter_struct myTIMER0_OC;

    // 使能GPIOA、B时钟
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_GPIOB);
    // 使能复用时钟
    rcu_periph_clock_enable(RCU_AF);
    rcu_periph_clock_enable(RCU_TIMER0);
    // PWM输出引脚 初始化
    gpio_init(GPIOA,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_8);
    gpio_init(GPIOA,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_9);
    gpio_init(GPIOA,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_10);
    // PWM互补输出引脚 初始化
    gpio_init(GPIOB,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_13);
    gpio_init(GPIOB,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_14);
    gpio_init(GPIOB,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_15);
    //------------------定时器基本配置--------------------
    // 初始化定时器结构体
    timer_struct_para_init(&myTIMER0);
    timer_deinit(TIMER0);
    // 预分频
    myTIMER0.prescaler = psc;
    // 对齐模式
    myTIMER0.alignedmode = TIMER_COUNTER_EDGE;
    // 计数方向
    myTIMER0.counterdirection = TIMER_COUNTER_UP;
    // 重装载值
    myTIMER0.period = arr;
    // 分频因子
    myTIMER0.clockdivision = TIMER_CKDIV_DIV1;
    myTIMER0.repetitioncounter = 0;
    timer_init(TIMER0,&myTIMER0);
    // ---------------定时器PWM输出配置------------------
    // 输出使能
    myTIMER0_OC.outputstate = TIMER_CCX_ENABLE;
    // 互补输出使能
    myTIMER0_OC.outputnstate = TIMER_CCXN_ENABLE;
    // 输出极性---高
    myTIMER0_OC.ocpolarity = TIMER_OC_POLARITY_HIGH;
    // 互补输出极性---高
    myTIMER0_OC.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
    // 空闲输出电平---低
    myTIMER0_OC.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
    // 互补空闲输出电平---低
    myTIMER0_OC.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
    // 外设TIMERx的通道输出配置
    timer_channel_output_config(TIMER0,TIMER_CH_0,&myTIMER0_OC);
    timer_channel_output_config(TIMER0,TIMER_CH_1,&myTIMER0_OC);
    timer_channel_output_config(TIMER0,TIMER_CH_2,&myTIMER0_OC);
    // --------通道0----------
    // 通道占空比设置
    timer_channel_output_pulse_value_config(TIMER0,TIMER_CH_0,ch0_duty);
    // 通道模式---PWM0模式
    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);    
    // --------通道1----------
    // 通道占空比设置
    timer_channel_output_pulse_value_config(TIMER0,TIMER_CH_1,ch1_duty);
    // 通道模式---PWM0模式
    timer_channel_output_mode_config(TIMER0,TIMER_CH_1,TIMER_OC_MODE_PWM0);
    // 不使用输出比较影子寄存器
    timer_channel_output_shadow_config(TIMER0,TIMER_CH_1,TIMER_OC_SHADOW_DISABLE);    
    // --------通道2----------
    // 通道占空比设置
    timer_channel_output_pulse_value_config(TIMER0,TIMER_CH_2,ch2_duty);
    // 通道模式---PWM0模式
    timer_channel_output_mode_config(TIMER0,TIMER_CH_2,TIMER_OC_MODE_PWM0);
    // 不使用输出比较影子寄存器
    timer_channel_output_shadow_config(TIMER0,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);            
    // 高级定时器需要打开
    timer_primary_output_config(TIMER0,ENABLE);
    // 自动重装载使能
    timer_auto_reload_shadow_enable(TIMER0);
    // 使能定时器
    timer_enable(TIMER0);
}

实验现象

  • 1

配置:

// 输出极性---高
myTIMER0_OC.ocpolarity = TIMER_OC_POLARITY_HIGH;
// 互补输出极性---高
myTIMER0_OC.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
// 空闲输出电平---低
myTIMER0_OC.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
// 互补空闲输出电平---低
myTIMER0_OC.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;

频率2KHz,占空比分别是20%,45%,70%

管脚 通道 输出现象
PA8 CH0 频率2KHz,占空比20%
PB13 CH0_N 频率2KHz,占空比80%
PA9 CH1 频率2KHz,占空比45%
PB14 CH1_N 频率2KHz,占空比55%
PA10 CH2 频率2KHz,占空比70%
PB15 CH2_N 频率2KHz,占空比30%

注意PA9不能和串口0的TX同时使用,因为它们使用相同的复用引脚

  • 2

配置:

// 输出极性---高
myTIMER0_OC.ocpolarity = TIMER_OC_POLARITY_HIGH;
// 互补输出极性---低
myTIMER0_OC.ocnpolarity = TIMER_OCN_POLARITY_LOW;

CH1和CH1_N输出占空比都是 45%

  • 3

配置:

// 输出极性---高
myTIMER0_OC.ocpolarity = TIMER_OC_POLARITY_LOW;
// 互补输出极性---高
myTIMER0_OC.ocnpolarity = TIMER_OCN_POLARITY_HIGH;

输出图像跟2一样,但是占空比CH1和CH1_N都是 55%

  • 4
// 输出极性---高
myTIMER0_OC.ocpolarity = TIMER_OC_POLARITY_LOW;
// 互补输出极性---高
myTIMER0_OC.ocnpolarity = TIMER_OCN_POLARITY_LOW;

输出图像跟1一样,但是占空比CH1是 55%,CH1_N是 45%

PWM刹车死区保护

基于上面的程序上添加

myTIMER.c
/*
 * @description: 定时器0PWM初始化
 * @return {*}
 * @Date: 2023-05-13 22:00:00
 */
// 定时器0PWM初始化
void vTIMER0_Pwn_Init(uint16_t psc,uint16_t arr,uint16_t ch0_duty,uint16_t ch1_duty,uint16_t ch2_duty)
{
    timer_parameter_struct myTIMER0;
    timer_oc_parameter_struct myTIMER0_OC;

    // 使能GPIOA、B时钟
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_GPIOB);
    // 使能复用时钟
    rcu_periph_clock_enable(RCU_AF);
    rcu_periph_clock_enable(RCU_TIMER0);
    // PWM输出引脚 初始化
    gpio_init(GPIOA,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_8);
    gpio_init(GPIOA,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_9);
    gpio_init(GPIOA,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_10);
    // PWM互补输出引脚 初始化
    gpio_init(GPIOB,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_12); // 刹车引脚
    gpio_init(GPIOB,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_13);
    gpio_init(GPIOB,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_14);
    gpio_init(GPIOB,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_15);
    //------------------定时器基本配置--------------------
    // 初始化定时器结构体
    timer_struct_para_init(&myTIMER0);
    timer_deinit(TIMER0);
    // 预分频
    myTIMER0.prescaler = psc;
    // 对齐模式
    myTIMER0.alignedmode = TIMER_COUNTER_EDGE;
    // 计数方向
    myTIMER0.counterdirection = TIMER_COUNTER_UP;
    // 重装载值
    myTIMER0.period = arr;
    // 分频因子
    myTIMER0.clockdivision = TIMER_CKDIV_DIV1;
    myTIMER0.repetitioncounter = 0;
    timer_init(TIMER0,&myTIMER0);
    // ---------------定时器PWM输出配置------------------
    // 输出使能
    myTIMER0_OC.outputstate = TIMER_CCX_ENABLE;
    // 互补输出使能
    myTIMER0_OC.outputnstate = TIMER_CCXN_ENABLE;
    // 输出极性---高
    myTIMER0_OC.ocpolarity = TIMER_OC_POLARITY_HIGH;
    // 互补输出极性---高
    myTIMER0_OC.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
    // 空闲输出电平---低
    myTIMER0_OC.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
    // 互补空闲输出电平---低
    myTIMER0_OC.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
    // 外设TIMERx的通道输出配置
    timer_channel_output_config(TIMER0,TIMER_CH_0,&myTIMER0_OC);
    timer_channel_output_config(TIMER0,TIMER_CH_1,&myTIMER0_OC);
    timer_channel_output_config(TIMER0,TIMER_CH_2,&myTIMER0_OC);
    // --------通道0----------
    // 通道占空比设置
    timer_channel_output_pulse_value_config(TIMER0,TIMER_CH_0,ch0_duty);
    // 通道模式---PWM0模式
    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);    
    // --------通道1----------
    // 通道占空比设置
    timer_channel_output_pulse_value_config(TIMER0,TIMER_CH_1,ch1_duty);
    // 通道模式---PWM0模式
    timer_channel_output_mode_config(TIMER0,TIMER_CH_1,TIMER_OC_MODE_PWM0);
    // 不使用输出比较影子寄存器
    timer_channel_output_shadow_config(TIMER0,TIMER_CH_1,TIMER_OC_SHADOW_DISABLE);    
    // --------通道2----------
    // 通道占空比设置
    timer_channel_output_pulse_value_config(TIMER0,TIMER_CH_2,ch2_duty);
    // 通道模式---PWM0模式
    timer_channel_output_mode_config(TIMER0,TIMER_CH_2,TIMER_OC_MODE_PWM0);
    // 不使用输出比较影子寄存器
    timer_channel_output_shadow_config(TIMER0,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);            
    // 高级定时器需要打开
    timer_primary_output_config(TIMER0,ENABLE);
    // 自动重装载使能
    timer_auto_reload_shadow_enable(TIMER0);
    // -------------------刹车配置-------------------    
    timer_break_parameter_struct myTIMER0_Break;
    // 运行模式下“关闭状态”配置---使能
    myTIMER0_Break.runoffstate = TIMER_ROS_STATE_ENABLE;
    // 空闲模式下“关闭状态”配置---使能
    myTIMER0_Break.ideloffstate = TIMER_IOS_STATE_ENABLE;
    // 死区时间(0~255)
    myTIMER0_Break.deadtime = 255;
    // 中止信号极性---高电平(触发刹车电平)
    myTIMER0_Break.breakpolarity = TIMER_BREAK_POLARITY_HIGH;
    // 自动输出使能---使能
    myTIMER0_Break.outputautostate = TIMER_OUTAUTO_ENABLE;
    // 互补寄存器保护控制
    myTIMER0_Break.protectmode = TIMER_CCHP_PROT_0;
    // 中止使能---使能
    myTIMER0_Break.breakstate = TIMER_BREAK_ENABLE;
    // 刹车配置
    timer_break_config(TIMER0,&myTIMER0_Break);
    // 默认低电平
    gpio_bit_write(GPIOB,GPIO_PIN_12,RESET);
    // 使能定时器
    timer_enable(TIMER0);
}

实验现象

Systick定时器

就一个普通的计数,可当做基本定时器使用,而且函数的延时也是调用的它

void SysTick_Handler(void)
{
    static uint16_t Led_Cnt = 0;
    // 自带不需要管
    delay_decrement();
    Led_Cnt++;
    if (1000 == Led_Cnt)
    {
        Led_Cnt = 0;
        MyLed.bLed1_State = !MyLed.bLed1_State;
        MyLed.vLED_Control(MyLed.bLed1_State);
    }
}

外部中断

对应的中断函数可在 startup_gd32f10x_hd.s 里面找:

// 常用的就这几个
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
EXTI5_9_IRQHandler
EXTI10_15_IRQHandler
myKEY.c
/*
 * @description: 按键外部中断初始化
 * @return {*}
 * @Date: 2023-05-13 20:28:22
 */
// 按键外部中断初始化
void vKEY_Exti_Init(void)
{
    // 使能GPIO时钟
    rcu_periph_clock_enable(RCU_GPIOA);
    // 初始化引脚
    gpio_init(GPIOA,GPIO_MODE_IPU,GPIO_OSPEED_50MHZ,GPIO_PIN_15);
    // 使能复用时钟
    rcu_periph_clock_enable(RCU_AF);
    // 设置优先级---15
    nvic_irq_enable(EXTI10_15_IRQn,2,2);
    // 设置中断线---PA15
    gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA,GPIO_PIN_SOURCE_15);
    // 设置中断线和中断边沿---15下降沿触发
    exti_init(EXTI_15,EXTI_INTERRUPT,EXTI_TRIG_FALLING);
    // 清除中断标记
    exti_interrupt_flag_clear(EXTI_15);
}

/*
 * @description: 外部中断--Line15函数
 * @return {*}
 * @Date: 2023-05-13 20:51:42
 */
// 外部中断--Line15函数
void EXTI10_15_IRQHandler(void)
{
    if(SET == exti_interrupt_flag_get(EXTI_15))
    {
        // 清除标志位
        exti_interrupt_flag_clear(EXTI_15);
        MyKey.Key_Down_State[0] = 1;
    }
}

实验现象

设置为下降沿触发的话,按下一瞬间就触发,设置为上升沿触发的话就抬起一瞬间才触发,这个看个人需要设置

ADC

获取内部温度传感器温度:https://bruceou.blog.csdn.net/article/details/126150848

介绍

具体哪个引脚是ADC通道,可以去MX查找(输入STM32对应跟GD32一样后缀型号的板子即可),也可以去看 GD32F103xx系列MCU数据手册 然后看时钟树可以知道它最大频率不能超过14MHz,所以一般8分频即可(108MHz/8=13.5MHz)

  • GD32多达 18 个通道,然后又分为 注入组规则组,常用的是 规则组,注入的话相当于插队,跟中断有点像。数据转换后也会产生中断,ADC时钟的话可以看时钟树我的是最大只能是 14MHz

ADC的转换时间跟ADC的输入时钟和采样时间有关,公式:

Tconv = 采样时间+12.5个周期

例如:当ADCLK=14MHz,采样时间设置为1.5个周期(最快),那么总的转换时间:Tconv=1.5周期+12.5周期=14周期=1us,具体计算过程:

转换时钟周期=114MHz(ADCLK)71.4ns/周期\text{转换时钟周期} = \frac{1}{14MHz(ADCLK)} ≈ 71.4ns/\text{周期}

Tconv=采样时间+12.5×转换时钟时间Tconv = \text{采样时间}+12.5\times\text{转换时钟时间}

=1.5周期 + 12.5 x 71.4ns/周期1us= \text{1.5周期 + 12.5 x 71.4ns/周期} ≈ 1us

故当采样时间设置为1.5个周期时,总的转换时间约为1us

  • 转换模式

【单次转换模式】

  1. 规则通道单次转换结束后,转换数据将被存放于 ADC_RDATA 寄存器中, EOC 将会置1。如果 EOCIE 位被置1,将产生一个中断
  2. 注入通道单次转换结束后,转换数据将被存放于 ADC_IDATA0 寄存器中, EOCEOIC 位将会置1。如果 EOCIE 或EOICIE 位被置1,将产生一个中断
  3. 单次的话它采样一次就停止了

【连续运行模式】

  1. 连续模式下一旦触发ADC采样则一直采样,采样的轮数看用户,实时更新数值

【扫描模式】

  1. ADC就会一个接一个通道的采样和转换规则组或注入组通道。转换数据存储在 ADC_RDATAADC_IDATAx寄存器中

一个简单的ADC采集电路可参考:

:由于我的是最小系统板故没有ADC采集电路

  • DMA0相关通道

  • DMA1相关通道

ADC单通道轮询采集

myADC.c
#include "myADC.h"

/*====================================变量区 BEGIN====================================*/
MyADC_TypeDef MyADC =
{
    .fADC_Value = {0.0},
    .bADC_Start_Flag = 0,
    .vADC_Init = &vADC_Init,
    .usADC_Get_Value = &usADC_Get_Value
};
/*====================================变量区    END====================================*/


/*
 * @description: ADC初始化
 * @return {*}
 * @Date: 2023-05-14 17:03:43
 */
// ADC初始化
void vADC_Init(void)
{
    // 使能GPIOA时钟
    rcu_periph_clock_enable(RCU_GPIOA);
    // 使能ADC时钟
    rcu_periph_clock_enable(RCU_ADC0);
    // 配置ADC时钟,ADC最大14MHz
    rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);
    // 端口初始化--一定要设置成模拟输入
    gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4);
    // ----------------ADC配置----------------
    // ADC工作模式---独立模式(只使用了一个ADC)
    adc_mode_config(ADC_MODE_FREE);
    // 数据对齐方式---右对齐
    adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
    // 是否开启连续转换模式---单次转换模式
    adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);
    // 规则序列的长度(通道数)---常规组,1
    adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1);
    // ADC规则通道触发源选择---软件触发
    adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
    // 配置ADC外部触发---使能
    adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
    // 使能ADC
    adc_enable(ADC0);
    // 延时1ms
    delay_1ms(1);
    // ADC校准复位
    adc_calibration_enable(ADC0);
}

/*
 * @description: ADC采样
 * @return {*}
 * @Date: 2023-05-14 18:11:17
 */
// ADC采样
uint16_t usADC_Get_Value(uint8_t channel)
{
    // 配置规则通道采集---采样时间为7.5个时钟周期
    adc_regular_channel_config(ADC0, 0, channel, ADC_SAMPLETIME_7POINT5);
    // 软件触发使能
    adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
    // 等待采样完成
    while(!adc_flag_get(ADC0, ADC_FLAG_EOC));
    // 清除标志
    adc_flag_clear(ADC0, ADC_FLAG_EOC);

    return (adc_regular_data_read(ADC0));
}

myUSART.c
// 定时器200ms扫描一次然后进行ADC采集打印
if(MyADC.bADC_Start_Flag)
{
    MyADC.fADC_Value[0] = (float)MyADC.usADC_Get_Value(ADC_CHANNEL_1);
    MyADC.fADC_Value[0] = (float)MyADC.fADC_Value[0] / 4096 * 3.3f;
    MyADC.fADC_Value[1] = (float)MyADC.usADC_Get_Value(ADC_CHANNEL_2);
    MyADC.fADC_Value[1] = (float)MyADC.fADC_Value[1] / 4096 * 3.3f;
    MyADC.fADC_Value[2] = (float)MyADC.usADC_Get_Value(ADC_CHANNEL_3);
    MyADC.fADC_Value[2] = (float)MyADC.fADC_Value[2] / 4096 * 3.3f;
    MyADC.fADC_Value[3] = (float)MyADC.usADC_Get_Value(ADC_CHANNEL_4);
    MyADC.fADC_Value[3] = (float)MyADC.fADC_Value[3] / 4096 * 3.3f;
    printf("%.1f--%.1f--%.1f--%.1f\r\n", MyADC.fADC_Value[0], MyADC.fADC_Value[1], MyADC.fADC_Value[2], MyADC.fADC_Value[3]);
    MyADC.bADC_Start_Flag = 0;
}

实验现象

把PA1引脚引到5V或者3.3就看到数值变化,接到GND则全部为0

DMA+连续扫描模式

注意:定义存储的数组类型要跟配置的那里一样,用float的话会报错,定义是16位则配置那都用16位,32位则32位,不能乱

myADC.c
/*
 * @description: ADC初始化
 * @return {*}
 * @Date: 2023-05-14 17:03:43
 */
// ADC初始化
void vADC_Init(void)
{
    // 使能GPIOA时钟
    rcu_periph_clock_enable(RCU_GPIOA);
    // 使能ADC时钟
    rcu_periph_clock_enable(RCU_ADC0);
    // 配置ADC时钟,ADC最大14MHz
    rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);
    // 端口初始化--一定要设置成模拟输入
    gpio_init(GPIOA,GPIO_MODE_AIN,GPIO_OSPEED_50MHZ,GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4);
    // ----------------DMA配置----------------
    // 使能DMA时钟
    rcu_adc_clock_config(RCU_DMA0);
    dma_parameter_struct myDMA0;
    // 复位DMA-CH0
    dma_deinit(DMA0,DMA_CH0);
    // 外设基地址---ADC的规则数据寄存器
    myDMA0.periph_addr = (uint32_t)(&ADC_RDATA(ADC0));
    // 外设数据传输宽度---32位
    myDMA0.periph_width = DMA_PERIPHERAL_WIDTH_32BIT;
    // 存储器基地址 
    myDMA0.memory_addr = (uint32_t)(&MyADC.ulADC_Value);
    // 存储器数据传输宽度---32位
    myDMA0.memory_width = DMA_MEMORY_WIDTH_32BIT;
    // DMA通道数据传输数量---4个通道
    myDMA0.number = 4;
    // DMA通道传输软件优先级---高
    myDMA0.priority = DMA_PRIORITY_HIGH;
    // 外设地址生成算法模式---外设基地址不增加
    myDMA0.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    // 存储器地址生成算法模式---内存基地址自增
    myDMA0.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    // DMA通道数据传输方向---外设到内存
    myDMA0.direction = DMA_PERIPHERAL_TO_MEMORY;
    // DMA初始化
    dma_init(DMA0,DMA_CH0,&myDMA0);
    // 循环模式---开启
    dma_circulation_enable(DMA0,DMA_CH0);
    // DMA通道使能
    dma_channel_enable(DMA0,DMA_CH0);


    // ----------------ADC配置----------------
    // ADC工作模式---独立模式(只使用了一个ADC)
    adc_mode_config(ADC_MODE_FREE);
    // 数据对齐方式---右对齐
    adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT);
    // 是否开启连续转换模式---使能
    adc_special_function_config(ADC0,ADC_CONTINUOUS_MODE,ENABLE);
    // 扫描模式开启
    adc_special_function_config(ADC0,ADC_SCAN_MODE,ENABLE);
    // 规则序列的长度(通道数)---常规组,4
    adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,4);
    // 配置4个规则通道采集---采样时间为7.5个时钟周期
    adc_regular_channel_config(ADC0,0,ADC_CHANNEL_0,ADC_SAMPLETIME_7POINT5);
    adc_regular_channel_config(ADC0,1,ADC_CHANNEL_1,ADC_SAMPLETIME_7POINT5);
    adc_regular_channel_config(ADC0,2,ADC_CHANNEL_2,ADC_SAMPLETIME_7POINT5);
    adc_regular_channel_config(ADC0,3,ADC_CHANNEL_3,ADC_SAMPLETIME_7POINT5);
    // ADC规则通道触发源选择---软件触发
    adc_external_trigger_source_config(ADC0,ADC_REGULAR_CHANNEL,ADC0_1_2_EXTTRIG_REGULAR_NONE);
    // 配置ADC外部触发---使能
    adc_external_trigger_config(ADC0,ADC_REGULAR_CHANNEL,ENABLE);
    // 使能ADC
    adc_enable(ADC0);
    // 延时1ms
    delay_1ms(1);
    // ADC校准复位
    adc_calibration_enable(ADC0);
    // ADC DMA使能
    adc_dma_mode_enable(ADC0);
    // ADC 软件触发使能
    adc_software_trigger_enable(ADC0,ADC_REGULAR_CHANNEL);
}
myADC.h
typedef struct 
{
    // ADC存储值
    uint32_t ulADC_Value[4];    
    // ADC开始采集标志位
    bool bADC_Start_Flag;
    void (*vADC_Init)(void);
    uint16_t (*usADC_Get_Value)(uint8_t);
}MyADC_TypeDef;
myUSART.c
if(MyADC.bADC_Start_Flag)
{
    float adc_temp[4] = {0.0};

    adc_temp[0] = (float)MyADC.fADC_Value[0] / 4096 * 3.3f;
    adc_temp[1] = (float)MyADC.fADC_Value[1] / 4096 * 3.3f;
    adc_temp[2] = (float)MyADC.fADC_Value[2] / 4096 * 3.3f;
    adc_temp[3] = (float)MyADC.fADC_Value[3] / 4096 * 3.3f;
    printf("%.1f--%.1f--%.1f--%.1f\r\n", adc_temp[0], adc_temp[1], adc_temp[2], adc_temp[3]);
    MyADC.bADC_Start_Flag = 0;
}

实验现象

同上

DMA+连续扫描模式+DMA中断

在上面的工程基础上添加修改即可

myADC.c
/*
 * @description: ADC初始化
 * @return {*}
 * @Date: 2023-05-14 17:03:43
 */
// ADC初始化
void vADC_Init(void)
{
    // 使能GPIOA时钟
    rcu_periph_clock_enable(RCU_GPIOA);
    // 使能ADC时钟
    rcu_periph_clock_enable(RCU_ADC0);
    // 配置ADC时钟,ADC最大14MHz
    rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);
    // 端口初始化--一定要设置成模拟输入
    gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4);
    // ----------------DMA配置----------------
    // 使能DMA时钟
    rcu_adc_clock_config(RCU_DMA0);
    dma_parameter_struct myDMA0;
    // 复位DMA-CH0
    dma_deinit(DMA0, DMA_CH0);
    // 外设基地址---ADC的规则数据寄存器
    myDMA0.periph_addr = (uint32_t)(&ADC_RDATA(ADC0));
    // 外设数据传输宽度---16位
    myDMA0.periph_width = DMA_PERIPHERAL_WIDTH_32BIT;
    // 存储器基地址
    myDMA0.memory_addr = (uint32_t)(&MyADC.ulADC_Value);
    // 存储器数据传输宽度---16位
    myDMA0.memory_width = DMA_MEMORY_WIDTH_32BIT;
    // DMA通道数据传输数量---4个通道
    myDMA0.number = 4;
    // DMA通道传输软件优先级---高
    myDMA0.priority = DMA_PRIORITY_HIGH;
    // 外设地址生成算法模式---外设基地址不增加
    myDMA0.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    // 存储器地址生成算法模式---内存基地址自增
    myDMA0.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    // DMA通道数据传输方向---外设到内存
    myDMA0.direction = DMA_PERIPHERAL_TO_MEMORY;
    // DMA初始化
    dma_init(DMA0, DMA_CH0, &myDMA0);
    // 循环模式---开启
    dma_circulation_enable(DMA0, DMA_CH0);
    // 打开DMA通道中断
    nvic_irq_enable(DMA0_Channel0_IRQn, 0, 1);
    // 转换结束产生中断
    dma_interrupt_enable(DMA0, DMA_CH0, DMA_INT_FTF);
    // DMA通道使能
    dma_channel_enable(DMA0, DMA_CH0);


    // ----------------ADC配置----------------
    // ADC工作模式---独立模式(只使用了一个ADC)
    adc_mode_config(ADC_MODE_FREE);
    // 数据对齐方式---右对齐
    adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
    // 是否开启连续转换模式---使能
    adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);
    // 扫描模式开启
    adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
    // 规则序列的长度(通道数)---常规组,4
    adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 4);
    // 配置4个规则通道采集---采样时间为7.5个时钟周期
    adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_0, ADC_SAMPLETIME_7POINT5);
    adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_1, ADC_SAMPLETIME_7POINT5);
    adc_regular_channel_config(ADC0, 2, ADC_CHANNEL_2, ADC_SAMPLETIME_7POINT5);
    adc_regular_channel_config(ADC0, 3, ADC_CHANNEL_3, ADC_SAMPLETIME_7POINT5);
    // ADC规则通道触发源选择---软件触发
    adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
    // 配置ADC外部触发---使能
    adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
    // 使能ADC
    adc_enable(ADC0);
    // 延时1ms
    delay_1ms(1);
    // ADC校准复位
    adc_calibration_enable(ADC0);
    // ADC DMA使能
    adc_dma_mode_enable(ADC0);
    // ADC 软件触发使能
    adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
}

/*
 * @description: DMA中断函数
 * @return {*}
 * @Date: 2023-05-15 14:54:16
 */
void DMA0_Channel0_IRQHandler(void)
{
    if(SET == dma_interrupt_flag_get(DMA0, DMA_CH0, DMA_INT_FLAG_FTF))
    {
        // 清除标志位
        dma_interrupt_flag_clear(DMA0, DMA_CH0, DMA_INT_FLAG_FTF);
        MyADC.bADC_Dma_Flag = 1;
    }
}
myADC.h
typedef struct 
{
    // ADC DMA 中断标志位
    bool bADC_Dma_Flag;
    // ADC存储值
    uint32_t ulADC_Value[4];    
    // ADC开始采集标志位
    bool bADC_Start_Flag;
    void (*vADC_Init)(void);
    uint16_t (*usADC_Get_Value)(uint8_t);
}MyADC_TypeDef;
myUSART.c
if(MyADC.bADC_Start_Flag && MyADC.bADC_Dma_Flag)
{
    float adc_temp[4] = {0.0};

    adc_temp[0] = (float)MyADC.ulADC_Value[0] / 4096 * 3.3f;
    adc_temp[1] = (float)MyADC.ulADC_Value[1] / 4096 * 3.3f;
    adc_temp[2] = (float)MyADC.ulADC_Value[2] / 4096 * 3.3f;
    adc_temp[3] = (float)MyADC.ulADC_Value[3] / 4096 * 3.3f;
    printf("%.1f--%.1f--%.1f--%.1f\r\n", adc_temp[0], adc_temp[1], adc_temp[2], adc_temp[3]);
    MyADC.bADC_Start_Flag = 0;
    MyADC.bADC_Dma_Flag = 0;
}

实验现象

同上

RTC

掉电,是指主电源 VDD断开的情况,为了 RTC 外设掉电继续运行,必须给GD32芯片通过 VBAT引脚接上锂电池,RTC 一般使用低速外部时钟LXTAL,频率为实时时钟模块中常用的 32.768KHz

设置时间获取时间

  • 这里RTC跟STM32有点不太一样,设置时间也比较麻烦,需要传一个时间戳,获取的话也没有库函数,需要计算时间戳获得当前时间
myRTC.c
#include "myRTC.h"

/*====================================变量区 BEGIN====================================*/
MyRTC_TypeDef MyRTC = 
{
    .bRTC_Sec_Flag = 0,
    .ucYear = 0,
    .ucMon = 0,
    .ucDay = 0,
    .ucHour = 0,
    .ucMin = 0,
    .ucSecond = 0,
    .vRTC_Init = &vRTC_Init,
    .vRTC_Convert_Time = &vRTC_Convert_Time
};

static int DAYS = 24 * 3600;
static int FOURSYEARS = 365 * 3 + 366;
static int norMoth[] = {31,28,31,30,31,30,31,31,30,31,30,31};
static int leapMoth[] = {31,29,31,30,31,30,31,31,30,31,30,31};

/*====================================变量区    END====================================*/

/*====================================静态内部函数声明区 BEGIN====================================*/
static void svRTC_Config(void);
static void svRTC_Time_Set(uint32_t timestamp_value);
static void svRTC_Get_Hour_Min_Sec(uint32_t timestamp, uint16_t *hour, uint16_t *min, uint16_t *sec);
static void svRTC_Get_Mon_Day(bool year_state,int nDays,uint16_t *nMon,uint16_t *nDay);
/*====================================静态内部函数声明区    END====================================*/

/*
 * @description: RTC配置
 * @return {*}
 * @Date: 2023-05-15 15:26:05
 */
// RTC配置
static void svRTC_Config(void)
{
    // 备份区域的时钟使能
    rcu_periph_clock_enable(RCU_BKPI);
    // 电源管理时钟使能
    rcu_periph_clock_enable(RCU_PMU);
    // 备份区允许访问
    pmu_backup_write_enable();
    // 备份域复位
    bkp_deinit();
    // 使能外部低速时钟32.768K
    rcu_osci_on(RCU_LXTAL);
    // 等待低速振荡器稳定
    rcu_osci_stab_wait(RCU_LXTAL);
    // RTC时钟源选择
    rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);
    // 使能RTC时钟
    RCU_RTCSRC_LXTAL
    rcu_periph_clock_enable(RCU_RTC);
    // 进入RTC配置模式
    rtc_configuration_mode_enter();
     // 等待寄存器与APB1时钟同步
    rtc_register_sync_wait();
    // 等待最后一次操作完成
    rtc_lwoff_wait();
    // 使能秒中断
    rtc_interrupt_enable(RTC_INT_SECOND);
    // 等待配置完成
    rtc_lwoff_wait();
    // 设置预分频,1s
    rtc_prescaler_set(32767);
    // 等待配置完成
    rtc_lwoff_wait();
}

/*
 * @description: 设置时间
 * @param {uint32_t} timestamp_value 时间戳
 * @return {*}
 * @Date: 2023-05-15 15:28:46
 */
// 设置时间
static void svRTC_Time_Set(uint32_t timestamp_value)
{
    // 等待最后一次操作完成
    rtc_lwoff_wait();
    // 改变当前时间
    rtc_counter_set(timestamp_value);
    // 等待最后一次操作完成
    rtc_lwoff_wait();
}

/*
 * @description: RTC初始化
 * @return {*}
 * @Date: 2023-05-15 15:26:31
 */
// RTC初始化
void vRTC_Init(void)
{
    printf("RTC中断初始化\r\n");
    // 读取备份区寄存器0的数据,检查是否已经配置
    if(bkp_data_read(BKP_DATA0) != 0xA5A5)
    {
        // 说明没配置过
        printf("RTC 没有配置过\r\n");
        // 配置
        svRTC_Config();
        printf("RTC配置完成\r\n");
        // 首次调整时间,传入一个时间戳
        svRTC_Time_Set(1684142909);
        bkp_data_write(BKP_DATA_0,0xA5A5);
    }
    else
    {
        // 判断是不是电源复位标志
        if(SET == rcu_flag_get(RCU_FLAG_PORRST))
        {
            printf("电源复位\r\n");
        }
        // 判断是不是软件复位标志
        else if(SET == rcu_flag_get(RCU_FLAG_SWRST))
        {
            printf("软件复位\r\n");
        }
        // 判断是不是外部引脚复位标志
        else if(SET == rcu_flag_get(RCU_FLAG_EPRST))
        {
            printf("外部引脚复位\r\n");
        }
        // 等待寄存器与APB1时钟同步
        rtc_register_sync_wait();
        // 使能秒中断
        rtc_interrupt_enable(RTC_INT_SECOND);
        // 等待配置完成
        rtc_lwoff_wait();
    }
    // 使能中断
    nvic_irq_enable(RTC_IRQn,1,0);
    // 退出配置模式, 更新配置
    rtc_configuration_mode_exit();
    // 清除所有标志
    rcu_all_reset_flag_clear();
}

/*
 * @description: RTC中断函数
 * @return {*}
 * @Date: 2023-05-15 16:11:08
 */
// RTC中断函数
void RTC_IRQHandler(void)
{
    if(SET == rtc_interrupt_flag_get(RTC_INT_FLAG_SECOND))
    {
        // 清除标志位
        rtc_interrupt_flag_clear(RTC_INT_FLAG_SECOND);
        MyRTC.bRTC_Sec_Flag = 1;
    }
}

/*
 * @description: 获取时分秒
 * @param {uint32_t} timestamp_value
 * @return {*}
 * @Date: 2023-05-15 16:41:50
 */
// 获取时分秒
static void svRTC_Get_Hour_Min_Sec(uint32_t timestamp, uint16_t *hour, uint16_t *min, uint16_t *sec)
{
    // 计算当前时间的秒数
    uint32_t current_seconds = timestamp % 60;
    // 分别计算当前时间的总分钟数和分钟部分
    uint32_t total_minutes = timestamp / 60;
    uint32_t current_minutes = total_minutes % 60;
    // 分别计算当前时间的总小时数和小时部分
    uint32_t total_hours = total_minutes / 60;
    uint32_t current_hours = (total_hours + 8) % 24;  // 以东八区的北京时间为准
    // 赋值
    *hour = current_hours;
    *min = current_minutes;
    *sec = current_seconds;
}

/*
 * @description: 获取月份日期
 * @param {bool} year_state
 * @param {int} nDay
 * @param {uint16_t} *nMon
 * @param {uint16_t} *nDay
 * @return {*}
 * @Date: 2023-05-15 16:45:01
 */
// 获取月份日期
static void svRTC_Get_Mon_Day(bool year_state,int nDays,uint16_t *nMon,uint16_t *nDay)
{
    int nTemp = 0;
    // // 根据是否是闰年来选择月份天数数组
    int *pMon = year_state ? leapMoth : norMoth;

    // 遍历月份
    for(uint8_t i = 0;i < 12; i++)
    {
        // 计算天数
        nTemp = nDays - pMon[i];
        // 找到对应的月份
        if(nTemp <= 0)
        {
            // 存储月份
            *nMon = i + 1;
            // 如果正好等于本月最后一天
            if(0 == nTemp)
            {
                // 存储本月最后一天
                *nDay = pMon[i];
            }
            // 否则就存储剩余天数
            else
            {
                // 存储天数
                *nDay = nDays;
            }
            break;
        }
        // 计算剩余天数
        nDays = nTemp;
    }
    return;
}

void vRTC_Convert_Time(uint32_t timestamp_value)
{
    // 计算天数,加上余数不为0就再加一天
    int nDays = timestamp_value / DAYS + ((timestamp_value % DAYS) ? 1 : 0);
    // 计算整除四年的个数
    int nYear4 = nDays / FOURSYEARS;
    // 计算剩余天数
    int nRemain = nDays % FOURSYEARS;
    // 计算公历年份
    int nDecyear = 1970 + nYear4 * 4;
    int nDemon = 0;
    int nDeday = 0;
    // 是否闰年的标志
    bool year_Flag = 0;

    // 剩余天数小于365,说明是当年的日期
    if(nRemain < 365)
    {
        ;
    }
    // 剩余天数小于365*2,说明是第二年的日期
    else if(nRemain < (365 * 2))
    {
        nDecyear += 1;
        // 减去第一年的天数
        nRemain -= 365;
    }
    // 剩余天数小于365*3,说明是第三年的日期
    else if(nRemain < (365 * 3))
    {
        nDecyear += 2;
        // 减去前两年的天数
        nRemain -= 365 * 2;
    }
    // 否则就是第四年的日期
    else
    {
        nDecyear += 3;
        // 减去前三年的天数
        nRemain -= 365 * 3;
        // 设为闰年
        year_Flag = 1;
    }
    svRTC_Get_Mon_Day(year_Flag,nRemain,&MyRTC.ucMon,&MyRTC.ucDay);
    svRTC_Get_Hour_Min_Sec(timestamp_value,&MyRTC.ucHour,&MyRTC.ucMin,&MyRTC.ucSecond);
    MyRTC.ucYear = nDecyear;
}
myRTC.h
#ifndef __MYRTC_H
#define __MYRTC_H
#include "AllHead.h"

typedef struct
{
    // 秒中断标志位
    bool bRTC_Sec_Flag;
    uint16_t ucYear;
    uint16_t ucMon;
    uint16_t ucDay;
    uint16_t ucHour;
    uint16_t ucMin;
    uint16_t ucSecond;
    void (*vRTC_Init)(void);
    void (*vRTC_Convert_Time)(uint32_t);
}MyRTC_TypeDef;

extern MyRTC_TypeDef MyRTC;

void vRTC_Init(void);
void vRTC_Convert_Time(uint32_t timestamp_value);
#endif
myUSART.c
if(MyRTC.bRTC_Sec_Flag)
{
    int timestamp_temp;

    MyRTC.bRTC_Sec_Flag = 0;
    // 获取时间戳
    timestamp_temp = rtc_counter_get();
    MyRTC.vRTC_Convert_Time(timestamp_temp);
    printf("%d-%d-%d %d:%d:%d\r\n", MyRTC.ucYear, MyRTC.ucMon, MyRTC.ucDay, MyRTC.ucHour, MyRTC.ucMin, MyRTC.ucSecond);
}

实验现象

RTC闹钟间隔10s中断

在上面的基础上添加一个闹钟即可

myRTC.c
/*
 * @description: 创建一个闹钟
 * @return {*}
 * @Date: 2023-05-15 18:31:46
 */
// 创建一个闹钟
void vRTC_Alarm_Create(void)
{
    // 备份区域的时钟使能
    rcu_periph_clock_enable(RCU_BKPI);
    // 电源管理时钟使能
    rcu_periph_clock_enable(RCU_PMU);
    // 备份区域允许访问
    pmu_backup_write_enable();
    // 等待寄存器与APB1时钟同步
    rtc_register_sync_wait();
    // 等待最后一次完成
    rtc_lwoff_wait();
    uint32_t tick = rtc_counter_get() + 10;
    // 等待最后一次完成
    rtc_lwoff_wait();
    rtc_alarm_config(tick);
    // 使能闹钟中断
    rtc_interrupt_enable(RTC_INT_ALARM);
    rtc_alarm_config(tick);
    pmu_backup_write_disable();
}

/*
 * @description: RTC中断函数
 * @return {*}
 * @Date: 2023-05-15 16:11:08
 */
// RTC中断函数
void RTC_IRQHandler(void)
{
    if(SET == rtc_interrupt_flag_get(RTC_INT_FLAG_SECOND))
    {
        // 清除标志位
        rtc_interrupt_flag_clear(RTC_INT_FLAG_SECOND);
        MyRTC.bRTC_Sec_Flag = 1;
    }
    if(SET == rtc_interrupt_flag_get(RTC_FLAG_ALARM))
    {
        // 清除标志位
        rtc_interrupt_flag_clear(RTC_FLAG_ALARM);
        MyRTC.bRTC_Alarm_Flag = 1;
    }
}
myRTC.h
typedef struct
{
    // 闹钟触发标志位
    bool bRTC_Alarm_Flag;
    // 秒中断标志位
    bool bRTC_Sec_Flag;
    uint16_t ucYear;
    uint16_t ucMon;
    uint16_t ucDay;
    uint16_t ucHour;
    uint16_t ucMin;
    uint16_t ucSecond;
    void (*vRTC_Init)(void);
    void (*vRTC_Convert_Time)(uint32_t);
    void (*vRTC_Alarm_Create)(void);
}MyRTC_TypeDef;
main.c
// 在初始化那里先启动一次闹钟
// 闹钟初始化启动
MyRTC.vRTC_Alarm_Create();
myUSART.c
if(MyRTC.bRTC_Alarm_Flag)
{
    MyRTC.bRTC_Alarm_Flag = 0;
    MyRTC.vRTC_Alarm_Create();
    printf("闹钟触发\r\n");
}

实验现象

看门狗

FWDGT 最适合应用于那些需要看门狗作为一个在主程序之外,能够完全独立工作,并且对时间精度要求较低的场合。 WWDGT最适合那些要求看门狗在精确计时窗口起作用的应用程序。

独立看门狗

独立看门狗用通俗一点的话来解释就是一个 12 位的递减计数器,当计数器的值从某个值一直减到 0 的时候,系统就会产生一个复位信号,即 IWDG_RESET。如果在计数没减到 0 之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的喂狗。看门狗功能由 VDD 电压域供电,在停止模式和待机模式下仍能工作。

独立看门狗由内部专门的 40KHz 低速时钟驱动,即使主时钟发生故障,它也仍然有效。这里需要注意独立看门狗的时钟是一个内部 RC 时钟,所以并不是准确的 40Khz,而是在 30~60KHz 之间的一个可变化的时钟,只是我们在估算的时候,以 40Khz 的频率来计算,看门狗对时间的要求不是很精确,所以,时钟有些偏差,都是可以接受的。

这些时间是按照40kHz时钟给出

  • 控制寄存器FWDGT_CTL

在控制寄存器(FWDGT_CTL)中写入 0xCCCC,开始启用独立看门狗;此时计数器开始从其复位值 0xFFF 递减计数。当计数器计数到末尾 0x000 时,会产生一个复位信号(FWDGT_RESET)。无论何时,只要键寄存器FWDGT_CTL中被写入 0xAAAA,FWDGT_RLD中的值就会被重新加载到计数器中从而避免产生看门狗复位 。

  • 预分频寄存器 FWDGT_PSC 和 重装载寄存器 FWDGT_RLD

具有写保护功能。要修改这两个寄存器的值,必须先向FWDGT_CTL寄存器中写入 0x5555

注意PSC范围只能是 0~7,即预分频只能选择 0~128,RLD范围只能是 0~4095

  • 独立看门狗一般用来检测和解决由程序引起的故障

  • 注意FWDGT在一旦启用,就不能再被关闭!想要关闭,只能重启,并且重启之后不能打开FWDGT,否则问题依旧

  • 看门狗溢出计算公式:

out=((4×2prer)×rlr)/40MHzout=((4×2^{prer}) ×rlr) /40MHz

设置预分频 40000Hz / 64 = 625Hz,一个周期就是 1 /625 = 1.6ms

myDog.c
#include "myDOG.h"

/*====================================变量区 BEGIN====================================*/
MyDog_TypeDef MyDog = 
{
    .vDOG_Fwdgt_Init = &vDOG_Fwdgt_Init
};
/*====================================变量区    END====================================*/

void vDOG_Fwdgt_Init(void)
{
    // 使能写功能
    fwdgt_write_enable();
    // 设置预分频 40000/64=625Hz,1/625 = 1.6ms
    // 设置初值 800*1.6=1.28s
    fwdgt_config(800,FWDGT_PSC_DIV64);
    // 喂狗
    fwdgt_counter_reload();
    // 使能独立看门狗
    fwdgt_enable();
}
myDog.h
/*
*@Description: 看门狗
*@Author: Yang
*@Date: 2023-05-15 21:01:33
*/
#ifndef __MYDOG_H
#define __MYDOG_H
#include "AllHead.h"

typedef struct
{
    void (*vDOG_Fwdgt_Init)(void);
}MyDog_TypeDef;

extern MyDog_TypeDef MyDog;

void vDOG_Fwdgt_Init(void);
#endif
myTIMER.c
if(800 == Dog_Fwdgt_Cnt)
{
    Dog_Fwdgt_Cnt = 0;
    // 喂狗
    fwdgt_counter_reload();
}

实验现象

如果喂狗时间小于1.28s的话正常,太长时间的话会像下面那样重启

窗口看门狗

窗口看门狗通常被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障

跟独立看门狗类似的地方,不同的地方是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫窗口的上限,上限值由用户独立设置。因此 窗口看门狗计数器的值必须在上窗口和下窗口之间才可以喂狗

窗口看门狗定时器开启后, 7 位向下递减计数器值逐渐减小。计数值达到 0x3F 时会产生系统复位,窗口看门狗定时器在计数器计数值达到 0x40,会产生一个提前唤醒标志,如果使能中断将会产生提前唤醒中断

窗口看门狗定时器时钟是由 APB1 时钟预分频而来(看手册)

当计数器的值小于窗口值,且大于0x3F的时候,重装载向下计数器可以避免复位,否则在其他时候进行重加载就会引起复位

myDOG.c
/*
 * @description: 窗口看门狗初始化
 * @return {*}
 * @Date: 2023-05-16 08:06:26
 */
// 窗口看门狗初始化
void vDOG_Wwdgt_Init(void)
{
    // 使能窗口看门狗时钟
    rcu_periph_clock_enable(RCU_WWDGT);
    // 窗口上限值
    wwdgt_config(0x7F,0x5F,WWDGT_CFG_PSC_DIV8);
    // 使能窗口看门狗
    wwdgt_enable();
    // 清除中断标志
    wwdgt_flag_clear();
    // 开启中断
    wwdgt_interrupt_enable();
    nvic_irq_enable(WWDGT_IRQn,2,0);
}

/*
 * @description: 窗口看门狗中断函数
 * @return {*}
 * @Date: 2023-05-16 08:14:18
 */
// 窗口看门狗中断函数
void WWDGT_IRQHandler(void)
{
    wwdgt_counter_update(0x7F);
    wwdgt_flag_clear();
    printf("WWDG\r\n");
}
myDOG.h
#ifndef __MYDOG_H
#define __MYDOG_H
#include "AllHead.h"

typedef struct
{
    void (*vDOG_Fwdgt_Init)(void);
    void (*vDOG_Wwdgt_Init)(void);
}MyDog_TypeDef;

extern MyDog_TypeDef MyDog;

void vDOG_Fwdgt_Init(void);
void vDOG_Wwdgt_Init(void);
#endif

实验现象

程序加密

GD32通过读取芯片唯一ID号来实现程序的保护,防止被抄袭, 96位 的产品唯一身份标识所提供的参考号码对任意一个GD32微控制器,在任何情况下都是唯一的。用户在何种情况下,都不能修改这个身份标识。

可以以字节(8位)为单位读取,也可以以半字(16位)或者全字(32位)读取,但是需要注意大小端模式

读取的3个地址为:

main.c
/*
 * @description: 芯片ID检查
 * @return {*}
 * @Date: 2023-05-16 11:23:45
 */
// 芯片ID检查
static void svSYS_Id_Check(void)
{
    for(uint8_t i = 0; i < 12; i++)
    {
        // 先强制类型转换为无符号char类型以防溢出然后解引用取值
        Sys_Id[i] = *(uint8_t*)(0x1FFFF7E8 + i);
        // 我的是:25 7b 48 53 39 36 32 04 50 36 31 33
        // printf("%0.2x ",Sys_Id[i]);
    }
    // 判断
    if(0x25 == Sys_Id[0] && 0x7b == Sys_Id[1] && 0x48 == Sys_Id[2] && 0x53 == Sys_Id[3] &&
        0x39 == Sys_Id[4] && 0x36 == Sys_Id[5] && 0x32 == Sys_Id[6] && 0x04 == Sys_Id[7] &&
        0x50 == Sys_Id[8] && 0x36 == Sys_Id[9] && 0x31 == Sys_Id[10] && 0x33 == Sys_Id[11])
    {
        printf("\r\nPASS\r\n");
    }
    // 错误则死循环
    else
    {
        printf("\r\nFAIL\r\n");
        while(1);
    }
}

GD32存储结构

存储器映射是指把芯片中或芯片外的FLASH,RAM,外设,BOOTBLOCK等进行统一编址。即用地址来表示对象。这个地址绝大多数是由厂家规定好的,用户只能用而不能改。用户只能在挂外部RAM或FLASH的情况下可进行自定义。

GD32的存储器地址空间被划分为大小相等的 8 块区域,每块区域大小为 512MB

  • GD32的Flash

严格说,应该是Flash模块。该Flash模块包括: Flash主存储区(Main memory)Flash信息区(Information block),以及 Flash存储接口寄存器区(Flash memory interface)。三个组成部分分别在 0x0000 0000 - 0xFFFF FFFF 不同的区域

Peripherals:外设的存储器映射,对该区域操作,就是对相应的外设进行操作;
SRAM:运行时临时存放代码的地方;
Flash:存放代码的地方;
System Memory:GD32出厂时自带的你只能使用,不能写或擦除;
Option Bytes:可以按照用户的需要进行配置(如配置看门狗为硬件实现还是软件实现)

  • 启动方式

有3种:

  1. 主闪存存储器(Main Flash)启动:从GD32内置的Flash启动(0x0800 0000-0x0807 FFFF),一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。以0x08000000 对应的内存为例,则该块内存既可以通过0x00000000 操作也可以通过0x08000000 操作,且都是操作的同一块内存。

  2. 系统存储器(System Memory)启动:从系统存储器启动(0x1FFFF000 - 0x1FFF F7FF),这种模式启动的程序功能是由厂家设置的。一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的ISP程序中,提供了串口下载程序的固件,可以通过这个ISP程序将用户程序下载到系统的Flash中。以0x1FFFFFF0对应的内存为例,则该块内存既可以通过0x00000000 操作也可以通过0x1FFFFFF0操作,且都是操作的同一块内存。

  3. 片上SRAM启动:从内置SRAM启动(0x2000 0000-0x3FFFFFFF),既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。SRAM 只能通过0x20000000进行操作,与上述两者不同。从SRAM 启动时,需要在应用程序初始化代码中重新设置向量表的位置。

启动模式只决定程序烧录的位置,加载完程序之后会有一个重映射(映射到0x00000000地址位置);真正产生复位信号的时候,CPU还是从开始位置执行。GD32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中

Flash读写

参考:https://bruceou.blog.csdn.net/article/details/126669695

GD32F10x的片上Flash起始地址是从 0x800 0000 开始,可支持字节、半字(16 bits)和字(32 bits)访问。 Flash 编程以半字(16 bits)或字(32bits)为单位。擦除可以以页(page)为单位,也可以进行全片擦除(information blocks 除外)

  1. Flash解锁操作
  2. 清除Flash标志
  3. 页擦除
  4. 读写操作
  5. 锁定
main.c
/*
 * @description: Flash读写测试
 * @param {uint32_t} Data
 * @return {*}
 * @Date: 2023-05-16 12:13:40
 */
// Flash读写测试
static void svFLASH_Test(uint32_t InData)
{
    #define FLASH_ADD 0x0807F800

    uint32_t OutData = 0;
    // 解锁
    fmc_unlock();
    // 清除标志位
    fmc_flag_clear(FMC_FLAG_BANK0_PGERR | FMC_FLAG_BANK0_WPERR | FMC_FLAG_BANK0_END | FMC_FLAG_BANK1_PGERR | FMC_FLAG_BANK1_WPERR | FMC_FLAG_BANK1_END);
    // 要擦除页起始地址
    fmc_page_erase(FLASH_ADD);
    // 写数据
    fmc_word_program(FLASH_ADD,InData);
    // 锁定
    fmc_lock();
    OutData = (*(__IO uint32_t*)(FLASH_ADD));
    printf("InData:%d--OutData:%d\r\n",InData,OutData);
    if(InData == OutData)
    {
        printf("Flash PASS\r\n");
    }
    else
    {
        printf("Flash FAIL\r\n");
    }
}

// 调用
svFLASH_Test(12345678);

实验现象

I2C

I2C (内部集成电路总线)模块提供了符合工业标准的两线串行制接口,可用于 MCU和外部 I2C设备的通讯。 I2C总线使用两条串行线: 串行数据线 SDA串行时钟线 SCL

附1-常用函数

rcu_periph_clock_enable(rcu_periph_enum periph)

功能:开启指定外设时钟功能,包括APB1和APB2总线上的外设时钟

参数:外设值(枚举类型rcu_periph_enum)

gpio_bit_reset(uint32_t gpio_periph, uint32_t pin)

功能:引脚置0

参数1:GPIO端口组

参数2:引脚

gpio_bit_set(uint32_t gpio_periph, uint32_t pin)

功能:引脚置1

参数1:GPIO端口组

参数2:引脚

gpio_bit_write(uint32_t gpio_periph, uint32_t pin, bit_status bit_value)

功能:引脚置1或置0

参数1:GPIO端口组

参数2:引脚

参数3:SET/RESET

gpio_input_bit_get(uint32_t gpio_periph,uint32_t pin)

功能:读取一个引脚的电平

参数1:GPIO端口组

参数2:引脚

返回值:SET/RESET

gpio_pin_remap_config(uint32_t remap, ControlStatus newvalue)

功能:配置GPIO引脚重映射

参数1:需要映射的外设

参数2:ENABLE/DISABLE

usart_deinit(uint32_t usart_periph)

功能:复位外设USART

参数:串口外设(USART0/1/2;UART3/4)

usart_baudrate_set(uint32_t usart_periph, uint32_t baudval)

功能:配置USART波特率

参数1:串口外设(USART0/1/2;UART3/4)

参数2:波特率(一般115200U或9600)

usart_word_length_set(uint32_t usart_periph, uint32_t wlen)

功能:设置数据位长度

参数1:串口外设(USART0/1/2;UART3/4)

参数2:USART_WL_8BIT/USART_WL_9BIT

usart_stop_bit_set(uint32_t usart_periph, uint32_t stblen)

功能:设置停止位

参数1:串口外设(USART0/1/2;UART3/4)

参数2:常用就是USART_STB_1BIT

usart_parity_config(uint32_t usart_periph, uint32_t paritycfg)

功能:设置奇偶检验

参数1:串口外设(USART0/1/2;UART3/4)

参数2:常用就是USART_PM_NONE

usart_hardware_flow_rts_config(uint32_t usart_periph, uint32_t rtsconfig)

功能:设置USART RTS硬件控制流

参数1:串口外设(USART0/1/2;UART3/4)

参数2:USART_RTS_ENABLE/USART_RTS_DISABLE

usart_hardware_flow_cts_config(uint32_t usart_periph, uint32_t ctsconfig)

功能:设置USART CTS硬件控制流

参数1:串口外设(USART0/1/2;UART3/4)

参数2:USART_CTS_ENABLE/USART_CTS_DISABLE

usart_receive_config(uint32_t usart_periph, uint32_t rxconfig)

功能:USART接收设置

参数1:串口外设(USART0/1/2;UART3/4)

参数2:USART_CTS_ENABLE/USART_RECEIVE_DISABLE

usart_transmit_config(uint32_t usart_periph, uint32_t txconfig)

功能:USART发送设置

参数1:串口外设(USART0/1/2;UART3/4)

参数2:USART_TRANSMIT_ENABLE/USART_TRANSMIT_DISABLE

usart_enable(uint32_t usart_periph)

功能:使能USART

参数1:串口外设(USART0/1/2;UART3/4)

usart_data_transmit(uint32_t usart_periph, uint16_t data)

功能:USART发送一个字节数据

参数1:串口外设(USART0/1/2;UART3/4)

参数2:字节数据

usart_flag_get(uint32_t usart_periph, usart_flag_enum flag)

功能:获取USART状态寄存器标志位

参数1:串口外设(USART0/1/2;UART3/4)

参数2:枚举类型,各种标志位

返回值:SET/RESET

usart_interrupt_flag_clear(uint32_t usart_periph, uint32_t flag)

功能:清除USART中断标志位状态

参数1:串口外设(USART0/1/2;UART3/4)

参数2:USART中断标志

usart_interrupt_flag_get(uint32_t usart_periph, uint32_t int_flag)

功能:获取USART中断标志位状态

参数1:串口外设(USART0/1/2;UART3/4)

参数2:USART中断标志

返回值:SET/RESET

usart_data_receive(uint32_t usart_periph)

功能:USART/UART接收数据功能

参数:串口外设(USART0/1/2;UART3/4)

返回值:接收的数据(0-0x1FF)

usart_interrupt_enable(uint32_t usart_periph, uint32_t int_flag)

功能:使能USART中断

参数1:串口外设(USART0/1/2;UART3/4)

参数2:USART中断标志

nvic_irq_enable(uint8_t nvic_irq,uint8_t nvic_irq_pre_priority,uint8_t nvic_irq_sub_priority)

功能:使能中断,配置中断的优先级

参数1:NVIC中断,枚举类型

参数2:抢占优先级(0~4)

参数3:响应优先级(0~4)

dma_channel_disable(uint32_t dma_periph, dma_channel_enum channelx)

功能:禁能外设DMAx的通道y传输

参数1:DMA0/1

参数2:DMA通道

dma_transfer_number_get(uint32_t dma_periph, dma_channel_enum channelx)

功能:获取DMAx通道y还有多少数据要传输

参数1:DMA0/1

参数2:DMA通道

返回值:DMA数据传输剩余数量

dma_interrupt_flag_get(uint32_t dma_periph, dma_channel_enum channelx, uint32_t flag)

功能:获取DMAx通道y中断标志位状态

参数1:DMA0/1

参数2:DMA通道

参数3:DMA标志,常用DMA_INT_FLAG_FTF,有3个一共

返回值:SET/RESET

dma_interrupt_flag_clear(uint32_t dma_periph, dma_channel_enum channelx, uint32_t flag)

功能:清除DMAx通道y中断标志位状态

参数1:DMA0/1

参数2:DMA通道

参数3:DMA标志,常用DMA_INT_FLAG_FTF,有3个一共

dma_deinit(uint32_t dma_periph, dma_channel_enum channelx)

功能:复位外设DMAx的通道y的所有寄存器

参数1:DMA0/1

参数2:DMA通道

dma_struct_para_init(dma_parameter_struct* init_struct)

功能:初始化DMA结构体为默认值

参数:DMA结构体

dma_init(uint32_t dma_periph, dma_channel_enum channelx, dma_parameter_struct *init_struct)

功能:初始化外设DMAx的通道y

参数1:DMA0/1

参数2:DMA通道

参数3:DMA结构体

dma_circulation_disable(uint32_t dma_periph, dma_channel_enum channelx)

功能:禁能DMA循环模式

参数1:DMA0/1

参数2:DMA通道

dma_memory_to_memory_disable(uint32_t dma_periph, dma_channel_enum channelx)

功能:禁能存储器到存储器DMA传输

参数1:DMA0/1

参数2:DMA通道

usart_dma_receive_config(uint32_t usart_periph, uint8_t dmaconfig)

功能:配置USART DMA接收功能

参数1:串口外设(USART0/1/2;UART3/4)

参数2:USART_RECEIVE_DMA_ENABLE/USART_RECEIVE_DMA_DISABLE

dma_interrupt_enable(uint32_t dma_periph, dma_channel_enum channelx, uint32_t source)

功能:使能DMAx通道y中断

参数1:DMA0/1

参数2:DMA通道

参数3:DMA中断源,一般选择DMA_INT_FTF,共有3个

dma_channel_enable(uint32_t dma_periph, dma_channel_enum channelx)

功能:使能外设DMAx的通道y传输

参数1:DMA0/1

参数2:DMA通道

#define DMA_CHCNT(dma, channel) REG32(((dma) + 0x0CU) + 0x14U * (uint32_t)(channel))

功能:用于获取DMA通道剩余下传输的数据个数

参数1:DMA0/1

参数2:DMA通道

timer_deinit(uint32_t timer_periph)

功能:复位外设TIMERx

参数1:TIMER外设(0~13)

timer_struct_para_init(timer_parameter_struct* initpara)

功能:初始化外设TIMER结构体参数

参数1:初始化结构体

timer_init(uint32_t timer_periph, timer_parameter_struct* initpara)

功能:初始化外设TIMERx

参数1:TIMER外设(0~13)

参数2:初始化结构体

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

功能:外设TIMERx的通道输出配置

参数1:TIMER外设(0~13)

参数2:待配置通道(不同定时器通道可能不一样具体看固件库手册)

参数3:输出通道结构体

timer_channel_output_pulse_value_config(uint32_t timer_periph, uint16_t channel, uint16_t pulse)

功能:配置外设TIMERx的通道输出比较值

参数1:TIMER外设(0~13)

参数2:待配置通道(不同定时器通道可能不一样具体看固件库手册)

参数3:通道输出比较值(0~65535)

timer_channel_output_mode_config(uint32_t timer_periph, uint16_t channel, uint16_t ocmode)

功能:配置外设TIMERx通道输出比较模式

参数1:TIMER外设(0~13)

参数2:待配置通道(不同定时器通道可能不一样具体看固件库手册)

参数3:通道输出比较模式(常用就是PWM0/1模式,匹配时翻转)

timer_channel_output_shadow_config(uint32_t timer_periph, uint16_t channel, uint16_t ocshadow)

功能:配置TIMERx通道输出比较影子寄存器功能

参数1:TIMER外设(0~13)

参数2:待配置通道(不同定时器通道可能不一样具体看固件库手册)

参数3:输出比较影子寄存器功能状态(TIMER_OC_SHADOW_DISABLE/TIMER_OC_SHADOW_ENABLE)

timer_auto_reload_shadow_enable(uint32_t timer_periph)

功能:TIMERx自动重载影子使能

参数1:TIMER外设(0~13)

timer_input_capture_config(uint32_t timer_periph, uint16_t channel, timer_ic_parameter_struct* icpara)

功能:配置TIMERx输入捕获参数

参数1:TIMER外设(0~13)

参数2:待配置通道(不同定时器通道可能不一样具体看固件库手册)

参数3:输入捕获结构体

timer_channel_capture_value_register_read(uint32_t timer_periph, uint16_t channel)

功能:读取通道捕获值

参数1:TIMER外设(0~13)

参数2:待配置通道(不同定时器通道可能不一样具体看固件库手册)

返回值:通道输入捕获值(0x0000~0xFFFF)

timer_input_pwm_capture_config(uint32_t timer_periph, uint16_t channel, timer_ic_parameter_struct* icpwm)

功能:配置TIMERx捕获PWM输入参数

参数1:TIMER外设(0~13)

参数2:待配置通道(TIMER_CH_0/TIMER_CH_1)

参数3:输入捕获结构体

timer_input_trigger_source_select(uint32_t timer_periph, uint32_t intrigger)

功能:TIMERx的输入触发源选择

参数1:TIMER外设(0~13)

参数2:待选择的触发源

timer_slave_mode_select(uint32_t timer_periph, uint32_t slavemode)

功能:TIMERx从模式配置

参数1:TIMER外设(0~13)

参数2:从模式选择

timer_master_slave_mode_config(uint32_t timer_periph, uint32_t masterslave)

功能:TIMERx主从模式配置

参数1:TIMER外设(0~13)

参数2:主从模式使能状态(TIMER_MASTER_SLAVE_MODE_ENABLE/TIMER_MASTER_SLAVE_MODE_DISABLE)

timer_primary_output_config(uint32_t timer_periph, ControlStatus newvalue)

功能:所有的通道输出使能(适用于高级定时器0,7)

参数1:TIMER外设(0,7)

参数2:控制状态(ENABLE/DISABLE)

timer_break_config(uint32_t timer_periph, timer_break_parameter_struct* breakpara)

功能:配置刹车功能

参数1:TIMER外设(0,7)

参数2:刹车功能配置结构体

rcu_adc_clock_config(uint32_t adc_psc)

功能:配置ADC的时钟分频系数

参数1:ADC分频因子(2/4/6/8/12)

adc_mode_config(uint32_t mode)

功能:配置ADC同步模式

参数1:ADC运行模式

adc_data_alignment_config(uint32_t adc_periph, uint32_t data_alignment)

功能:配置ADC数据对齐方式

参数1:ADC外设(0/1/2)

参数2:数据对齐方式选择(左对齐/右对齐)

adc_special_function_config(uint32_t adc_periph, uint32_t function, ControlStatus newvalue)

功能:使能或禁能ADC特殊功能

参数1:ADC外设(0/1/2)

参数2:功能配置

参数3:功能使能/禁能(ENABLE/DISABLE)

adc_channel_length_config(uint32_t adc_periph, uint8_t adc_channel_group, uint32_t length)

功能:配置规则通道组或注入通道组的长度

参数1:ADC外设(0/1/2)

参数2:通道组选择(规则组/注入组)

参数3:通道长度,规则通道组为(1-16),注入通道组为(1-4)

adc_external_trigger_source_config(uint32_t adc_periph, uint8_t adc_channel_group, uint32_t external_trigger_source)

功能:配置ADC外部触发源

参数1:ADC外设(0/1/2)

参数2:通道组选择(规则组/注入组)

参数3:规则通道组或注入通道组触发源(一般选择软件触发规则组)ADC0_1_2_EXTTRIG_REGULAR_NONE

adc_external_trigger_config(uint32_t adc_periph, uint8_t adc_channel_group, ControlStatus newvalue)

功能:配置ADC外部触发

参数1:ADC外设(0/1/2)

参数2:通道组选择(规则组/注入组)

参数3:通道使能/禁能(ENABLE/DISABLE)

adc_enable(uint32_t adc_periph)

功能:ADC使能

参数1:ADC外设(0/1/2)

adc_calibration_enable(uint32_t adc_periph)

功能:ADC校准复位

参数1:ADC外设(0/1/2)

adc_regular_channel_config(uint32_t adc_periph, uint8_t rank, uint8_t adc_channel, uint32_t sample_time)

功能:配置ADC规则通道组

参数1:ADC外设(0/1/2)

参数2:规则组通道序列,取值范围为0~15

参数3:ADC通道选择

参数4:采样时间(1.5/7.5/13.5/28.5/41.5/55.5/71.5/239.5)

dma_circulation_enable(uint32_t dma_periph, dma_channel_enum channelx)

功能:使能DMA循环模式

参数1:DMA外设(0/1)

参数2:DMA通道

adc_dma_mode_enable(uint32_t adc_periph)

功能:ADC DMA请求使能

参数1:ADC外设(0/1/2)

adc_flag_clear(uint32_t adc_periph, uint32_t adc_flag)

功能:清除ADC标志位

参数1:ADC外设(0/1/2)

参数2:ADC标志位

adc_flag_get(uint32_t adc_periph, uint32_t adc_flag)

功能:获取ADC标志位

参数1:ADC外设(0/1/2)

参数2:ADC标志位

返回值:SET/RESET

dma_interrupt_flag_get(uint32_t dma_periph, dma_channel_enum channelx, uint32_t flag)

功能:获取DMAx通道y中断标志位状态

参数1:DMA外设(0/1)

参数2:DMA通道

参数3:DMA标志

返回值:SET/RESET

dma_interrupt_flag_clear(uint32_t dma_periph, dma_channel_enum channelx, uint32_t flag)

功能:清除DMAx通道y中断标志位状态

参数1:DMA外设(0/1)

参数2:DMA通道

参数3:DMA标志

pmu_backup_write_enable(void)

功能:备份域写使能

bkp_deinit(void)

功能:备份域复位

rcu_osci_on(rcu_osci_type_enum osci)

功能:打开振荡器

参数:振荡器类型,一般是 RCU_LXTAL

rcu_osci_stab_wait(rcu_osci_type_enum osci)

功能:等待振荡器稳定标志位置位或振荡器起振超时

参数:振荡器类型,一般是 RCU_LXTAL

返回值:SUCCESS/ERROR

rcu_rtc_clock_config(uint32_t rtc_clock_source)

功能:配置RTC的时钟源选择

参数:RTC时钟源选择,一般是 RCU_RTCSRC_LXTAL

rtc_configuration_mode_enter(void)

功能:进入RTC配置模式

rtc_register_sync_wait(void)

功能:等待寄存器与APB1时钟同步

rtc_lwoff_wait(void)

功能:等待最后一次操作完成

rtc_interrupt_enable(uint32_t interrupt)

功能:使能RTC中断

注意:调用此函数之前,必须调用函数rtc_lwoff_wait

参数:待使能的RTC中断源(秒中断/闹钟中断/溢出中断)

rtc_prescaler_set(uint32_t psc)

功能:设置RTC预分频值

注意:调用此函数之前,必须调用函数rtc_lwoff_wait

参数:RTC预分频值(0~0x000FFFFF)

rtc_counter_set(uint32_t cnt)

功能:设置RTC计数器的值

注意:调用此函数之前,必须调用函数rtc_lwoff_wait

参数:RTC计数器的值(0~0xFFFF FFFF)

bkp_data_read(bkp_data_register_enum register_number)

功能:读备份数据寄存器

参数:参考枚举

返回值:0~0xffff

bkp_data_write(bkp_data_register_enum register_number, uint16_t data)

功能:写备份数据寄存器

参数1:参考枚举

参数2:待写入BKP数据寄存器的数据(0~0xFFFF)

rtc_configuration_mode_exit(void)

功能:退出配置模式, 更新配置

rcu_all_reset_flag_clear(void)

功能:清除所有标志

rtc_interrupt_flag_get(uint32_t flag)

功能:获取RTC中断标志位状态

参数:需要获取的RTC中断标志位(秒中断/闹钟中断/溢出中断)

返回值:SET/RESET

rtc_interrupt_flag_clear(uint32_t flag)

功能:清除RTC中断标志位

参数:需要清除的RTC中断标志位(秒中断/闹钟中断/溢出中断)

rtc_alarm_config(uint32_t alarm)

功能:设置RTC闹钟值

注意:调用此函数之前,必须调用函数rtc_lwoff_wait

参数:RTC闹钟值(0~0xFFFF FFFF)

pmu_backup_write_disable(void)

功能:备份域写除能

fwdgt_write_enable(void)

功能:使能对寄存器FWDGT_PSC和FWDGT_RLD的写操作

fwdgt_config(uint16_t reload_value, uint8_t prescaler_div)

功能:设置FWDGT重装载值、预分频值

参数1:重装载值(0x0000 - 0x0FFF)

参数2:FWDGT预分频值

返回值:ERROR/SUCCESS

fwdgt_counter_reload(void)

功能:按照FWDGT_RLD寄存器的值重装载FWDGT计数器

fwdgt_enable(void)

功能:使能独立看门狗

wwdgt_config(uint16_t counter, uint16_t window, uint32_t prescaler)

功能:设置WWDGT计数器值、窗口值和预分频值

参数1:0x00 - 0x7F

参数2:0x00 - 0x7F

参数3:WWDGT预分频值(1/2/4/8)

wwdgt_enable(void)

功能:使能窗口看门狗

wwdgt_flag_clear(void)

功能:清除窗口看门狗中断标志

wwdgt_interrupt_enable(void)

功能:使能窗口看门狗中断

wwdgt_counter_update(uint16_t counter_value)

功能:设置WWDGT计数器更新值

参数:0x00 - 0x7F

问题

  • 关于中断里判断SET或RESET问题

SET表示发送寄存器为空RESET则表示不为空

  • 涉及到中断枚举类型值的在 gd32f10x.h 里面有,看你Flash属于哪个类型,我的是 GD32F10X_HD

  • 中断函数的话可以在对应的startup.s里找,我的是 startup_gd32f10x_hd.s

  • 中断标志位清除的话可以使用 usart_data_receive(USART0); ,也可以使用 usart_interrupt_flag_clear,但是使用前者的话第一次会接收,使用后者的话第一次是接收不到数据的,第二次才正常

  • 定时器设为PWM时需要开启 复用,然后根据输出类型可以配置引脚的输出模式和类型使用 gpio_init 函数,不能只使用 rcu_periph_clock_enable(RCU_AF); 函数,这样是不行的,而且还要开复用引脚所在的GPIO时钟

报错集合

  • 编译GD32F10x例程时Keil提示错误信息 uses ARM-Compiler ‘V5.06 update 6 (build 750)‘ which is not available

解决方法:点击 Options of Target 在 ARM Compiler 选项里面选择 ARM 编译器版本,一般选择默认的就好或者都试一遍

  • Keil提示错误信息 error: L6235E: More than one section matches selector - cannot all be FIRST/LAST.

因为在工程中包含了不只一个启动文件导致的

解决方法:

方法1:应该针对不同的CPU选择不同的启动文件,从项目中删除不相关的启动文件,只留下一个

方法2:右击不相关的启动文件,点击 Options for File ‘startup_gd32f10x_md.s’,在弹出的对话框中,勾去灰化的 Include in Target BuildAlways Build 两项。

  • Keil提示错误信息 error: 'RTE_Components.h' file not found

解决方法:照着别人编译正确的模板进行添加对应文件即可,一开始我也是这样

  • 关于DG32f103VET6 不启动的问题-调试可以运行自启动不行

解决方法:打开魔法棒,勾选 Use MicroLIB