基于GD库的GD32开发

所有的Cortex-M处理器都有相同的SysTick定时器,因为CMSIS-Core头文件中定义了一个名为SysTick的结构体。

这个定时器可以用作延时函数,不管是STM32的芯片还是GD32,AT32的芯片,delay函数都可以这么写,只要它是cortex-M3/M4的芯片。

以下代码基于GD32F303RCT6,主频120M。

延迟函数

以下代码,需要根据芯片主频对SysTick->LOAD进行修改。

示例代码1

/**
  * @brief  微秒级延时
  * @param  xus 延时时长,范围:0~233015
  * @retval 无
  */
void Delay_us(uint32_t xus)
{
    //这里假设主频为120M,如果主频不是120M,自行修改即可
	SysTick->LOAD = 120 * xus;				//设置定时器重装值
	SysTick->VAL = 0x00;					//清空当前计数值
	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
	SysTick->CTRL = 0x00000004;				//关闭定时器
}
 
/**
  * @brief  毫秒级延时
  * @param  xms 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_ms(uint32_t xms)
{
	while(xms--)
	{
		Delay_us(1000);
	}
}
 
/**
  * @brief  秒级延时
  * @param  xs 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_s(uint32_t xs)
{
	while(xs--)
	{
		Delay_ms(1000);
	}
} 

示例代码2

可以自行根据芯片主频进行初始化。

#include "sys.h"
#include "delay.h"
//	 
//使用SysTick的普通计数模式对延迟进行管理
//包括delay_us,delay_ms
//

static uint16_t  g_fac_us = 0;      /* us延时倍乘数 */


/**
 * @brief       初始化延迟函数
 * @param       sysclk: 系统时钟频率, 即CPU频率(HCLK)
 * @retval      无
 */
void delay_init(uint16_t sysclk)
{
    SysTick->CTRL = 0;                                          /* 清Systick状态,以便下一步重设,如果这里开了中断会关闭其中断 */
    SysTick->CTRL&=~(1<<2);                                     /* SYSTICK使用内核时钟源8分频,因systick的计数器最大值只有2^24 */
    g_fac_us = sysclk / 8;                                      /* 不论是否使用OS,g_fac_us都需要使用,作为1us的基础时基 */
}


/**
 * @brief       延时nus
 * @param       nus: 要延时的us数.
 * @note        注意: nus的值,不要大于1118481us(最大值即2^24 / g_fac_us @g_fac_us = 15)
 * @retval      无
 */
void delay_us(uint32_t nus)
{
    uint32_t temp;
    SysTick->LOAD = nus * g_fac_us; /* 时间加载 */
    SysTick->VAL = 0x00;            /* 清空计数器 */
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ;       /* 开始倒数 */

    do
    {
        temp = SysTick->CTRL;
    } while ((temp & 0x01) && !(temp & (1 << 16))); /* CTRL.ENABLE位必须为1, 并等待时间到达 */

    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk ;    /* 关闭SYSTICK */
    SysTick->VAL = 0X00;            /* 清空计数器 */
}

/**
 * @brief       延时nms
 * @param       nms: 要延时的ms数 (0< nms <= 65535)
 * @retval      无
 */
void delay_ms(uint16_t nms)
{
    uint32_t repeat = nms / 1000;   /*  这里用1000,是系统频率为120Mhz的时候,
                                     *  delay_us最大只能延时1118481us左右了
                                     */
    uint32_t remain = nms % 1000;

    while (repeat)
    {
        delay_us(1000 * 1000);      /* 利用delay_us 实现 1000ms 延时 */
        repeat--;
    }

    if (remain)
    {
        delay_us(remain * 1000);    /* 利用delay_us, 把尾数延时(remain ms)给做了 */
    }
}


GPIO

参数配置

和STM32的GPIO类似。

库函数调用

时钟使能:rcu_periph_clock_enable()

GPIO参数初始化:gpio_init(uint32_t gpio_periph, uint32_t mode, uint32_t speed, uint32_t pin)

以初始化PA8,PD2为例

rcu_periph_clock_enable(RCU_GPIOA);  //GPIOA时钟使能
rcu_periph_clock_enable(RCU_GPIOD);  //GPIOD时钟使能
	
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8); //设置PA8推挽输出
gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2); //设置PD2推挽输出

写引脚电平

gpio_bit_write(GPIOA, GPIO_PIN_8, SET):

读引脚电平

gpio_input_bit_get(GPIOA,GPIO_PIN_13) 

翻转引脚电平

//翻转IO口状态
void gpio_togglepin(uint32_t gpio_periph, uint32_t pin)
{
  uint32_t octl;

  octl = GPIO_OCTL(gpio_periph);

  GPIO_BOP(gpio_periph) = ((octl & pin) << 16u) | (~octl & pin);
}


ADC

参数配置

ADC初始化

//初始化ADC																   
void  Adc_Init(void)
{    
  rcu_periph_clock_enable(RCU_GPIOC);                             //使能GPIOC时钟
  gpio_init(GPIOC, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_4); //AD采集引脚(PC4)模式设置,模拟输入 
	 
	rcu_periph_clock_enable(RCU_ADC0);   //使能ADC0时钟
	 
	adc_deinit(ADC0);   //复位ADC0
	
	//ADC时钟来自APB2,频率为120Mhz
	//使用6分频,得到APB2/6 = 20Mhz的ADC时钟
  rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV6);   //配置ADC时钟
	
  adc_mode_config(ADC_MODE_FREE);   //ADC独立工作模式              
	
	adc_special_function_config(ADC0, ADC_SCAN_MODE, DISABLE);        //非扫描模式	
  adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, DISABLE);  //禁止连续模式 
	
	adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);             //数据右对齐
	
	adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);                               //常规序列使能外部触发
  adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE); //常规序列使用软件触发
	
	adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1);   //规则序列长度,1个转换在规则序列中,也就是只转换规则序列1 

  adc_enable(ADC0);   //使能ADC0

  adc_calibration_enable(ADC0);   //使能ADC0校准复位
	
}

库函数调用

获取ADC值

//获得ADC转换后的结果
//ch:通道值 0~17
//返回值:转换结果
uint16_t Get_Adc(uint8_t ch)   
{
	  uint16_t adc_value = 0; 
	
	  adc_regular_channel_config(ADC0, 0, ch, ADC_SAMPLETIME_239POINT5);//配置ADC规则通道组,选择采样时间为239.5周期,提高采样时间可以提高精确度
	
    adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);   //软件触发使能,常规序列转换开始
	
	  while(SET != adc_flag_get(ADC0,ADC_FLAG_EOC));   //等待转换结束	
	   
	  adc_value = adc_regular_data_read(ADC0);         //读ADC规则组数据寄存器
	
    return  adc_value;   //返回最近一次ADC0的转换结果
}

获取多组ADC平均值

//获取通道ch的转换值,取times次,然后平均 
//ch:通道编号
//times:获取次数
//返回值:通道ch的times次转换结果平均值
uint16_t Get_Adc_Average(uint8_t ch,uint8_t times)
{
	uint32_t temp_val=0;
	uint8_t t;
	for(t=0;t<times;t++)
	{
		temp_val+=Get_Adc(ch);  //获取times次数据 
		delay_ms(5);
	}
	return temp_val/times;    //返回平均值
} 

自行转换ADC值为电压

adc_val = Get_Adc_Average(ADC_CH14,20);
adc_volt = (float)adc_val/4096.0f * 3.3f;

DAC

参数配置

.c文件

#include "dac.h"


//DAC初始化
void Dac_Init(void)
{
	rcu_periph_clock_enable(RCU_GPIOA);    //使能GPIOA时钟
	rcu_periph_clock_enable(RCU_DAC);      //使能DAC时钟 	
	   	 
	gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_4); // DAC引脚(PA4)模式设置,模拟输入

	dac_deinit();     //复位DAC 
	
	dac_output_buffer_disable(DAC0);                  //关闭DAC0输出缓冲区
	dac_trigger_disable(DAC0);                        //DAC0不使用触发功能
  dac_wave_mode_config(DAC0, DAC_WAVE_DISABLE);     //不使用波形模式
  dac_dma_disable(DAC0);                            //DAC0 DMA 模式失能,没有用到DMA功能
	dac_enable(DAC0);                                 //使能DAC0
	dac_data_set(DAC0, DAC_ALIGN_12B_R, 0);           //使用12位右对齐数据格式,设置DAC0的输出值为0
}

//设置DAC0输出电压
//vol:0~3300,代表0~3.3V
void Dac_Set_Vol(uint16_t vol)
{
	double temp=vol;
	temp/=1000;
	temp=temp*4096/3.3;
	
	if (temp >= 4096)temp = 4095;   //如果值大于等于4096, 则取4095 

	dac_data_set(DAC0, DAC_ALIGN_12B_R, temp);   //设置DAC0输出值
}

.h文件

#ifndef __DAC_H
#define __DAC_H	 
#include "sys.h"	    


void Dac_Init(void);		//DAC0初始化	 	 
void Dac_Set_Vol(uint16_t vol);	//设置DAC0输出电压
#endif

库函数调用

设置DAC输出数据

void dac_data_set(uint32_t dac_periph, uint32_t dac_align, uint16_t data)

dac_data_set(DAC0, DAC_ALIGN_12B_R, dacval);

获取DAC输出数据

uint16_t dac_output_value_get(uint32_t dac_periph)

adcx=dac_output_value_get(DAC0);          //获取DAC0输出数据  

示例代码

while(1) 
	  {		 
				t++;
				key=KEY_Scan(0);		      //进行按键扫描
				if(key==WKUP_PRES)        //按下WK_UP按键
				{		 
					if(dacval<4000)dacval+=200;
					dac_data_set(DAC0, DAC_ALIGN_12B_R, dacval);  //DAC输出增大200
				}else if(key==KEY0_PRES)	//按下KEY0按键
				{
					if(dacval>200)dacval-=200;
					else dacval=0;
					dac_data_set(DAC0, DAC_ALIGN_12B_R, dacval); 	//DAC输出减少200
				}	 
				if(t==10||key==KEY0_PRES||key==WKUP_PRES) 	//WKUP/KEY0按下了,或者定时时间到了
				{	  
					adcx=dac_output_value_get(DAC0);          //获取DAC0输出数据  
					LCD_ShowxNum(94,150,adcx,4,16,0);     	  //显示DAC输出值
					
					temp=(float)adcx*(3.3/4096);			        //得到DAC电压值
					adcx=temp;
					LCD_ShowxNum(94,170,temp,1,16,0);     	  //显示电压值整数部分
					
					temp-=adcx;
					temp*=1000;
					LCD_ShowxNum(110,170,temp,3,16,0X80); 	  //显示电压值的小数部分
					
					adcx=Get_Adc_Average(ADC_CH14,10);		    //ADC1的通道14采集DAC的输出电压,得到ADC转换值	  
					temp=(float)adcx*(3.3/4096);			        //得到ADC电压值
					adcx=temp;
					LCD_ShowxNum(94,190,temp,1,16,0);     	  //显示电压值整数部分
					
					temp-=adcx;
					temp*=1000;
					LCD_ShowxNum(110,190,temp,3,16,0X80); 	  //显示电压值的小数部分
					
				  LED0_TOGGLE();                            //LED0闪烁 
					t=0;
				}	    
				delay_ms(10);				
	 } 

串口

参数配置

uart.c。

代码分为三部分。

第一部分为os以及printf函数的相关定义。

第二部分为串口的初始化

第三部分为串口的中断处理函数

#include "usart.h"


/* 如果使用os,则包括下面的头文件即可. */
#if SYS_SUPPORT_OS
#include "includes.h" /* os 使用 */
#endif

/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */

#if 1

#if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");    /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}


/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
	while(RESET == usart_flag_get(USART0, USART_FLAG_TC));       /* 等待上一个字符发送完成 */
	
	usart_data_transmit(USART0, (uint8_t)ch);                    /* 将要发送的字符 ch 写入到DR寄存器 */  
  return ch;
}
#endif
/******************************************************************************************/

#if USART_EN_RX /*如果使能了接收*/

/* 接收缓冲, 最大USART_REC_LEN个字节. */
uint8_t USART_RX_BUF[USART_REC_LEN];

/*  接收状态
 *  bit15,      接收完成标志
 *  bit14,      接收到0x0d
 *  bit13~0,    接收到的有效字节数目
*/
uint16_t USART_RX_STA = 0;



//串口0初始化函数
//bound: 波特率, 根据自己需要设置波特率值
void usart_init(uint32_t bound)
{
	//使能GPIO时钟和复用时钟
  rcu_periph_clock_enable(RCU_GPIOA);     //使能GPIOA时钟
  rcu_periph_clock_enable(RCU_AF);        //使能复用时钟
  rcu_periph_clock_enable(RCU_USART0);    //使能串口时钟

  //配置TX的GPIO
  gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);

  //配置RX的GPIO
  gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);

  //配置USART的参数
  usart_deinit(USART0);                                 //RCU配置恢复默认值
  usart_baudrate_set(USART0, bound);                    //设置波特率
  usart_stop_bit_set(USART0, USART_STB_1BIT);           //一个停止位
  usart_word_length_set(USART0, USART_WL_8BIT);         //字长为8位数据格式
  usart_parity_config(USART0, USART_PM_NONE);           //无奇偶校验位
  usart_receive_config(USART0, USART_RECEIVE_ENABLE);   //使能接收
  usart_transmit_config(USART0, USART_TRANSMIT_ENABLE); //使能发送
  usart_interrupt_enable(USART0, USART_INT_RBNE);       //使能接收缓冲区非空中断
  //配置NVIC,并设置中断优先级
  nvic_irq_enable(USART0_IRQn, 3, 3);                   //抢占优先级3,子优先级3

  //使能串口
  usart_enable(USART0);	
}

void USART0_IRQHandler(void)
{
	uint8_t Res;
#if SYSTEM_SUPPORT_OS 		                              //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
		if(usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾(回车/换行) )
		{
		Res =usart_data_receive(USART0);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
#if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
}
#endif

uart.h

#ifndef __USART_H
#define __USART_H

#include "stdio.h"
#include "sys.h"
//	 
//串口0初始化
// 
//如果想串口中断接收,请不要注释以下宏定义
#define USART_REC_LEN               200         /* 定义最大接收字节数 200 */
#define USART_EN_RX                 1           /* 使能(1)/禁止(0)串口0接收 */

extern uint8_t  USART_RX_BUF[USART_REC_LEN];    /*接收缓冲,最大USART_REC_LEN个字节.末字节为换行符*/ 
extern uint16_t USART_RX_STA;         		      /*接收状态标记*/	

void usart_init(uint32_t bound);                /* 串口初始化函数 */

#endif


库函数调用

串口使用的代码参考

if(USART_RX_STA&0x8000)    //接收完了一次数据
            {					    
                len=USART_RX_STA&0x3fff; //得到此次接收到的数据长度
                printf("\r\n您发送的消息为:\r\n");
                for(t=0;t<len;t++)
                {
                    usart_data_transmit(USART0, USART_RX_BUF[t]);         //发送接收到的数据

                    while(RESET == usart_flag_get(USART0, USART_FLAG_TC));//等待发送结束
                }
                printf("\r\n\r\n");      //插入换行
                USART_RX_STA=0;
            }
            else
            {
                times++;
                if(times%5000==0)
                {
                    printf("\r\nWKS MiniGD32开发板 串口实验\r\n\r\n");
                }
                if(times%200==0)printf("请输入数据,以回车键结束\r\n");  
                if(times%30==0) LED0_TOGGLE();//闪烁LED,提示系统正在运行.
                delay_ms(10);   
            } 


串口闲时中断+DMA

参数配置

代码编写


PWM输出

PWM频率计算公式

公式应用:

如果我们要输出1000hz,占空比为50%的方波信号。

假如系统频率为120MHz = 120*10^6Hz。可以这样设置

PSC = 1200 - 1,PSC + 1 = 1200。ARR = 100 - 1。ARR + 1 = 100。

f = fsystem / [ (PSC+1)*(ARR+1) ] = 1000Hz

参数配置

定时器初始化,这里设置上电后默认输出1000Hz,占空比为50%。

/**
 * @brief 初始化TIMER0为PWM模式
 *
 * 该函数用于初始化TIMER0定时器为PWM模式,设置相关的时钟、GPIO和定时器参数,
 * 以生成PWM信号。PWM信号的频率和占空比可以通过参数arr和psc进行调整。
 *
 * @param arr 自动重装载值,决定了PWM信号的周期
 * @param psc 预分频值,决定了定时器的时钟频率
 */
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
void TIM0_PWM_Init(uint16_t arr,uint16_t psc)
{
	//定义初始化结构体变量
  timer_oc_parameter_struct timer_ocinitpara;
  timer_parameter_struct timer_initpara;

  rcu_periph_clock_enable(RCU_GPIOA);    //使能GPIOA时钟
  rcu_periph_clock_enable(RCU_TIMER0);   //使能TIMER0时钟
  rcu_periph_clock_enable(RCU_AF);       //使能AF时钟
	
  gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);  //设置PA8复用推挽输出

  timer_deinit(TIMER0);                                  //复位TIMER0

  timer_initpara.prescaler         = psc;                //设置预分频值
  timer_initpara.alignedmode       = TIMER_COUNTER_EDGE; //设置对齐模式
  timer_initpara.counterdirection  = TIMER_COUNTER_UP;   //设置向上计数模式
  timer_initpara.period            = arr;                //设置自动重装载值。/* ARR-1即为周期 */
  timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;   //设置时钟分频因子
  timer_initpara.repetitioncounter = 0;                  //设置重复计数值
  timer_init(TIMER0, &timer_initpara);                   //根据参数初始化定时器

  timer_ocinitpara.outputstate  = TIMER_CCX_ENABLE;                    //使能通道输出
  timer_ocinitpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;               //设置通道输出极性为高
  timer_channel_output_config(TIMER0, TIMER_CH_0, &timer_ocinitpara);  //定时器通道输出配置 

  timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, arr/2);               //设置占空比,这里默认设置比较值为自动重装载值的一半,即占空比为50%
  timer_channel_output_mode_config(TIMER0, TIMER_CH_0, TIMER_OC_MODE_PWM0);         //设置通道比较模式为PWM模式0 
  
  timer_primary_output_config (TIMER0, ENABLE);                                     //TIMER0所有的通道输出使能
  timer_enable(TIMER0);                                                             //使能定时器TIMER0
}

//设置TIMER0通道0的占空比
//compare:比较值 CCR
void TIM_SetTIM0Compare1(uint32_t compare)
{
    /* compare的取值范围:0-ARR。占空比 = CCR/(ARR+1) */
  timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, compare);
}

库函数调用

初始化,并填入ARR和PSC参数。

TIM0_PWM_Init(100-1,1200-1);               //默认设置1000HZ,占空比为50%


PWM捕获与测量占空比

参数配置

/*********************************以下是通用定时器输入捕获实验程序*************************************/

//TIMER1_CH0 输入捕获配置
//arr:自动重装值。
//psc:时钟预分频数
void TIM1_CH0_Cap_Init(uint16_t arr,uint16_t psc)
{
	//定义定时器初始化结构体变量
  timer_ic_parameter_struct timer_icinitpara;
  timer_parameter_struct timer_initpara;

  rcu_periph_clock_enable(RCU_GPIOA);    //使能GPIOA时钟
  rcu_periph_clock_enable(RCU_TIMER1);   //使能TIMER1时钟
	
  gpio_init(GPIOA, GPIO_MODE_IPD, GPIO_OSPEED_MAX, GPIO_PIN_0);  //设置PA0为下拉输入

  timer_deinit(TIMER1);                                  //复位TIMER1

  timer_initpara.prescaler         = psc;                //设置预分频值
	timer_initpara.alignedmode       = TIMER_COUNTER_EDGE; //设置对齐模式
  timer_initpara.counterdirection  = TIMER_COUNTER_UP;   //设置向上计数模式
  timer_initpara.period            = arr;                //设置自动重装载值
  timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;   //设置时钟分频因子
	timer_initpara.repetitioncounter = 0;                  //设置重复计数值
  timer_init(TIMER1, &timer_initpara);                   //根据参数初始化定时器
	
	/* TIMER1  configuration */
    /* TIMER1 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(TIMER1,TIMER_CH_0,&timer_icinitpara);

    /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER1);//自动重载使能
    /* clear channel 0 interrupt bit */
    timer_interrupt_flag_clear(TIMER1,TIMER_INT_FLAG_CH0);//CH0 通道中断清除
    /* channel 0 interrupt enable */
    timer_interrupt_enable(TIMER1,TIMER_INT_CH0);//CH0 通道中断使能
    nvic_irq_enable(TIMER1_IRQn, 2, 0);                     //使能TIMER1中断,并设置抢占优先级为2,响应优先级为0
    /* TIMER1 counter enable */
    timer_enable(TIMER1);

}

库函数调用

编写定时器中断函数。

uint8_t tim1_state = 0;//0-开始计时 1-获取T1 2-获取T2
uint32_t tim1_cnt1 = 0;//高电平时间
uint32_t tim1_cnt2 = 0;//总周期
uint32_t frequency;//频率
float duty;//占空比

void TIMER1_IRQHandler(void)
{
    timer_ic_parameter_struct timer_icinitpara;
    timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI;
    timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1;
    timer_icinitpara.icfilter    = 0x0;		
    if(SET == timer_interrupt_flag_get(TIMER1,TIMER_INT_FLAG_CH0)){
        /* clear channel 0 interrupt bit */
        timer_interrupt_flag_clear(TIMER1,TIMER_INT_FLAG_CH0);		
		
		if(tim1_state == 0)//第一个上升沿产生,开始计时
		{
			
			timer_icinitpara.icpolarity = TIMER_IC_POLARITY_FALLING;  //设置为下降沿
			timer_input_capture_config(TIMER1,TIMER_CH_0,&timer_icinitpara);	
			timer_counter_value_config(TIMER1 , 0); // 计数清零,重新计时
            tim1_state = 1;// 更新状态为等待下降沿
		
		}
		else if(tim1_state ==1)//第一个下降沿时,获取T1(高电平时间),并改为上升沿中断
		{
			
			tim1_cnt1 = timer_channel_capture_value_register_read(TIMER1,TIMER_CH_0)+1;// 读取捕获计数,这个时间即为上升沿持续的时间
			timer_icinitpara.icpolarity = TIMER_IC_POLARITY_RISING;  //设置为上升沿
			timer_input_capture_config(TIMER1,TIMER_CH_0,&timer_icinitpara);	
			tim1_state = 2;// 更新状态为等待第二个上升沿
		}		
		else if(tim1_state == 2)//第二个上升沿中断,获取T2(us)周期
		{		
			tim1_cnt2 = timer_channel_capture_value_register_read(TIMER1,TIMER_CH_0)+1;//读取捕获计数,这个时间即为上升沿持续的时间
            frequency = 1e6/tim1_cnt2;// 计算频率(单位为Hz),定时器时钟频率分频为1MHz。120M/[ arr+1 * psc+1]=1M 
            duty = tim1_cnt1*100.0f/tim1_cnt2;// 计算占空比(单位为%)
            tim1_state = 0;// 重置状态,准备下一次捕获
		}

    }

}

其他思路

代码修改自

GD32F303固件库开发(13)----定时器TIM捕获PWM测量频率与占空比_tft读取占空比-CSDN博客

#include "capture.h"
#include "led.h"

/*********************************以下是通用定时器输入捕获实验程序*************************************/

//TIMER1_CH0 输入捕获配置
//arr:自动重装值。
//psc:时钟预分频数
void TIM1_CH0_Cap_Init(uint16_t arr,uint16_t psc)
{
	//定义定时器初始化结构体变量
  timer_ic_parameter_struct timer_icinitpara;
  timer_parameter_struct timer_initpara;

  rcu_periph_clock_enable(RCU_GPIOA);    //使能GPIOA时钟
  rcu_periph_clock_enable(RCU_TIMER1);   //使能TIMER1时钟
	
	gpio_init(GPIOA, GPIO_MODE_IPD, GPIO_OSPEED_50MHZ, GPIO_PIN_0);  //设置PA0为下拉输入

  timer_deinit(TIMER1);                                  //复位TIMER1

  timer_initpara.prescaler         = psc;                //设置预分频值
	timer_initpara.alignedmode       = TIMER_COUNTER_EDGE; //设置对齐模式
  timer_initpara.counterdirection  = TIMER_COUNTER_UP;   //设置向上计数模式
  timer_initpara.period            = arr;                //设置自动重装载值
  timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;   //设置时钟分频因子
	timer_initpara.repetitioncounter = 0;                  //设置重复计数值
  timer_init(TIMER1, &timer_initpara);                   //根据参数初始化定时器
	
	/* TIMER1  configuration */
    /* TIMER1 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(TIMER1,TIMER_CH_0,&timer_icinitpara);

    /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER1);//自动重载使能
    /* clear channel 0 interrupt bit */
    timer_interrupt_flag_clear(TIMER1,TIMER_INT_FLAG_CH0);//CH0 通道中断清除
    /* channel 0 interrupt enable */
    timer_interrupt_enable(TIMER1,TIMER_INT_CH0);//CH0 通道中断使能
    nvic_irq_enable(TIMER1_IRQn, 2, 0);                     //使能TIMER1中断,并设置抢占优先级为2,响应优先级为0
    /* TIMER1 counter enable */
    timer_enable(TIMER1);

}

#define IR_IN1  gpio_input_bit_get (GPIOA, GPIO_PIN_0)
uint8_t time_up_flag=0;//上升沿标志位
uint8_t time_dowm_flag=0;//下降沿标志位

uint32_t time_up_num=0;//上升沿计数
uint32_t time_dowm_num=0;//下降沿计数
uint32_t time_frequency;//频率
float time_duty;//占空比


void TIMER1_IRQHandler(void)
{
    timer_ic_parameter_struct timer_icinitpara;
    timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI;
    timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1;
    timer_icinitpara.icfilter    = 0x0;		
    if(SET == timer_interrupt_flag_get(TIMER1,TIMER_INT_FLAG_CH0)){
        /* clear channel 0 interrupt bit */
        timer_interrupt_flag_clear(TIMER1,TIMER_INT_FLAG_CH0);		
		
		if(IR_IN1&&time_up_flag==0)//第一次上升
		{
			time_up_flag=1;
			timer_icinitpara.icpolarity = TIMER_IC_POLARITY_FALLING;  //设置为下降沿
			timer_input_capture_config(TIMER1,TIMER_CH_0,&timer_icinitpara);	
			timer_counter_value_config(TIMER1 , 0); // 计数清零,从头开始计
		
		}
		else if(IR_IN1==0&&time_dowm_flag==0)//下降
		{
			
			time_dowm_num = timer_channel_capture_value_register_read(TIMER1,TIMER_CH_0)+1; // 读取捕获计数,这个时间即为上升沿持续的时间
			timer_icinitpara.icpolarity = TIMER_IC_POLARITY_RISING;  //设置为上升沿
			timer_input_capture_config(TIMER1,TIMER_CH_0,&timer_icinitpara);	
			time_dowm_flag=1;
		}		
		else if(IR_IN1&&time_dowm_flag==1)//第二次之后上升
		{		
			time_up_num = timer_channel_capture_value_register_read(TIMER1,TIMER_CH_0)+1;; // 读取捕获计数,这个时间即为上升沿持续的时间
			timer_icinitpara.icpolarity = TIMER_IC_POLARITY_FALLING;  //设置为下降沿
			timer_input_capture_config(TIMER1,TIMER_CH_0,&timer_icinitpara);
			time_dowm_flag=0;
			timer_counter_value_config(TIMER1 , 0); // 计数清零,从头开始计
		
		}

    }

}

示例代码

extern uint32_t time_up_num;//上升沿计数
extern uint32_t time_dowm_num;//下降沿计数
extern uint32_t time_frequency;//频率
extern float time_duty;//占空比

int main(void)
{ 
	  delay_init(120);                          //初始化延时函数 
	  usart_init(115200);                       //初始化串口
	  LED_Init();							                  //初始化LED
	  TIM0_PWM_Init(500-1,120-1);               //设置1Mhz的计数频率,2Khz的PWM
	  TIM1_CH0_Cap_Init(0XFFFF,120-1);          //以1Mhz的频率计数 捕获
    while(1)
    {
        time_frequency=1000000/time_up_num;//频率
        time_duty = (float)time_dowm_num/(float)time_up_num;//占空比	
        printf("time_frequency=%d,time_duty=%.2f\r\n",time_frequency,time_duty*100)	;		
        delay_ms(1000);	
    }
}


定时器

参数配置

定时器初始化配置

//通用定时器2中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//APB1时钟为60MHz,TIMER2时钟选择为APB1的2倍,因此,TIMER2时钟为120MHz
void TIM2_Int_Init(uint16_t arr,uint16_t psc)
{
	timer_parameter_struct timer_initpara;               //timer_initpara用于存放定时器的参数

  //使能RCU相关时钟 
  rcu_periph_clock_enable(RCU_TIMER2);                 //使能TIMER2的时钟

  //复位TIMER2
  timer_deinit(TIMER2);                                //复位TIMER2
  timer_struct_para_init(&timer_initpara);             //初始化timer_initpara为默认值

  //配置TIMER2
  timer_initpara.prescaler         = psc;              //设置预分频值
  timer_initpara.counterdirection  = TIMER_COUNTER_UP; //设置向上计数模式
  timer_initpara.period            = arr;              //设置自动重装载值
  timer_initpara.clockdivision     = TIMER_CKDIV_DIV1; //设置时钟分频因子
  timer_init(TIMER2, &timer_initpara);                 //根据参数初始化定时器

  //使能定时器及其中断
  timer_interrupt_enable(TIMER2, TIMER_INT_UP);        //使能定时器的更新中断
  nvic_irq_enable(TIMER2_IRQn, 1, 3);                  //配置NVIC设置优先级,抢占优先级1,响应优先级3
  timer_enable(TIMER2);                                //使能定时器TIMER2
}

函数调用

//定时器2中断服务程序
void TIMER2_IRQHandler(void)
{
  if(timer_interrupt_flag_get(TIMER2, TIMER_INT_FLAG_UP) == SET)   //判断定时器更新中断是否发生
  {
    
    /*--功能代码--*/

    LED1_TOGGLE(); 
                                                //LED1翻转
    /*-----------*/
    timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_UP);         //清除定时器更新中断标志
  }
}

独立看门狗

参数配置

.c文件

#include "fwdgt.h"


//初始化独立看门狗
//prer:分频数:FWDGT_PSC_DIV4~FWDGT_PSC_DIV256
//rlr:自动重装载值,0~0XFFF.
//时间计算(大概):Tout=((4*2^prer)*rlr)/40 (ms).
void FWDGT_Init(uint8_t prer,uint16_t rlr)
{
	  //配置独立看门狗定时器
  fwdgt_write_enable();          //使能对寄存器的写操作
  
  fwdgt_config(rlr, prer);       //设置重装载值和预分频值
    
  fwdgt_counter_reload();        //将FWDGT_RLD寄存器的值重装载FWDGT计数器
  
  fwdgt_enable();                //使能独立看门狗定时器

}
   
//喂独立看门狗
void FWDGT_Feed(void)
{   
  fwdgt_counter_reload();        //喂狗
}

.h文件

#ifndef __FWDGT_H
#define __FWDGT_H
#include "sys.h"

void FWDGT_Init(uint8_t prer,uint16_t rlr);//初始化FWDGT,并使能FWDGT
void FWDGT_Feed(void);                     //喂独立看门狗 
#endif

示例代码

#include "sys.h"
#include "usart.h"		
#include "delay.h"	
#include "led.h"
#include "key.h"
#include "fwdgt.h"

int main(void)
{ 	
	  delay_init(120);                          //初始化延时函数 
	  usart_init(115200);                       //初始化串口
	  LED_Init();							                  //初始化LED
	  KEY_Init();							                  //初始化按键
	  delay_ms(100);                  	        //延时100ms再初始化看门狗,LED0的变化"可见"
	  FWDGT_Init(FWDGT_PSC_DIV64,625);  	      //分频数为64,重载值为625,溢出时间为1s	
	  LED0(0);                                  //点亮LED0
    while(1)
		{
        if(KEY_Scan(0)==WKUP_PRES)  	        //如果WK_UP按下,喂狗
        {
            FWDGT_Feed();    			            //喂狗
        }
        delay_ms(10); 
		}
}

实验现象:
    如果看门狗没有复位,开发板的LED0将常亮,如果WK_UP按键按下,就喂狗,
    只要WK_UP不停的按,看门狗就一直不会产生复位,保持LED0的常亮,一旦超过看门狗
    溢出时间(Tout=1s)还没按,那么将会导致程序重启,这将导致LED0熄灭一次,看起来就是LED0在闪烁。

窗口看门狗

参数配置

.c文件

#include "wwdgt.h"
#include "led.h"
#include "usart.h"	

//初始化窗口看门狗 	
//tr   :T[6:0],计数器值 
//wr   :W[6:0],窗口值 
//fprer:分频系数,范围:WWDGT_CFG_PSC_DIV1 ~WWDGT_CFG_PSC_DIV8
//Fwwdg=PCLK1/(4096*2^fprer). 一般PCLK1=60Mhz
void WWDGT_Init(uint8_t tr,uint8_t wr,uint32_t fprer)
{
  rcu_periph_clock_enable(RCU_WWDGT);         //使能WWDGT时钟
	
	wwdgt_config(tr, wr, fprer);                //设置WWDGT计数器值、窗口值和预分频值 ;
	
	wwdgt_enable();                             //使能窗口看门狗定时器
	
	wwdgt_flag_clear();                         //清除WWDGT提前唤醒中断标志位状态
	
	wwdgt_interrupt_enable();                   //使能窗口看门狗提前唤醒中断
	
	nvic_irq_enable(WWDGT_IRQn, 2, 3);          //抢占优先级2,响应优先级为3
}
   
//窗口看门狗中断服务函数
void WWDGT_IRQHandler(void)
{ 
  wwdgt_counter_update(127);                  //更新窗口看门狗值。相当于喂狗
	
	wwdgt_flag_clear();                         //清除WWDGT提前唤醒中断标志位状态
	
	LED1_TOGGLE();                              //LED1闪烁
}

.h文件

#ifndef __WWDGT_H
#define __WWDGT_H
#include "sys.h"

void WWDGT_Init(uint8_t tr,uint8_t wr,uint32_t fprer); //初始化窗口看门狗 
#endif

示例代码

#include "sys.h"
#include "usart.h"		
#include "delay.h"	
#include "led.h"
#include "wwdgt.h"

int main(void)
{ 	
	  delay_init(120);                            //初始化延时函数 
	  usart_init(115200);                         //初始化串口
	  LED_Init();							                    //初始化LED
	  LED0(0);                                    //点亮LED0
	  delay_ms(300);                  	          //延时300ms再初始化看门狗,LED0的变化"可见"
	  WWDGT_Init(0X7F, 0X5F, WWDGT_CFG_PSC_DIV8); //计数器值为7F,窗口值为5F,预分频值为8
    while(1)
		{
       LED0(1);                                 //关闭LED0   
		}
}

独立看门狗与窗口看门狗的区别

特性独立看门狗(FWDGT)窗口看门狗(WWDGT)
时钟源内部低速时钟(LSI)APB1时钟预分频而来
喂狗时间要求设定时间内喂狗即可,灵活喂狗时间有上下限,必须在特定窗口内
应用场景时间精度要求低的场合需要精确计时、严格监测程序运行状态的场合
其他特点启动后不能停止,LSI不能被禁止具有提前唤醒中断(EWI)功能


RTC

参数配置

底层代码

.c文件

#include "delay.h"
#include "usart.h"
#include "led.h"
#include "rtc.h" 		    


_calendar_obj calendar; //时钟结构体 

//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常""
//默认尝试使用CK_LXTAL,当CK_LXTAL启动失败后,切换为CK_IRC40K.
//通过BKP寄存器0的值,可以判断RTC使用的是CK_LXTAL/CK_IRC40K:
//当BKP0==0X5050时,使用的是CK_LXTAL
//当BKP0==0X5051时,使用的是CK_IRC40K
//注意:切换CK_LXTAL/CK_IRC40K将导致时间/日期丢失,切换后需重新设置.
//返回0:正常;1,进入初始化模式失败
uint8_t RTC_Init(void)
{
		//检查是不是第一次配置时钟 
		uint16_t bkpflag = 0;
		uint16_t retry = 200;
		uint32_t tempreg = 0;
		uint32_t clockfreq = 0;
	
		rcu_periph_clock_enable(RCU_PMU);        //使能PMU时钟
		rcu_periph_clock_enable(RCU_BKPI);       //使能BKPI时钟
		pmu_backup_write_enable();               //备份域写使能
	
		bkpflag = bkp_read_data(BKP_DATA_0);     //读取BKP0的值

		if (bkpflag != 0X6060)                   //之前使用的不是CK_LXTAL
		{
			bkp_deinit();                          //复位备份域
			rcu_osci_on(RCU_LXTAL);                //使能外部低速时钟
		
			if ((rcu_osci_stab_wait(RCU_LXTAL) == ERROR))              //开启CK_LXTAL失败?
			{ 
					rcu_osci_on(RCU_IRC40K);           //使能CK_IRC40K 
					if ((rcu_osci_stab_wait(RCU_IRC40K) == SUCCESS))       //等待CK_IRC40K准备好
					{
							rcu_rtc_clock_config(RCU_RTCSRC_IRC40K); //选择CK_IRC40K时钟作为RTC的时钟源
							bkp_write_data(BKP_DATA_0, 0x6061);      //标记已经初始化过了,使用CK_IRC40K
							clockfreq = 40000 - 1;  //CK_IRC40K频率约40Khz
					}
			}	
			else
			{ 
				 rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);       //选择RCU_LXTAL时钟作为RTC的时钟源
				 bkp_write_data(BKP_DATA_0, 0x6060);           //标记已经初始化过了,使用RCU_LXTAL
				 clockfreq = 32768 - 1;       //RCU_LXTAL频率为32.768Khz
			}
			rcu_periph_clock_enable(RCU_RTC);      //使能RTC时钟
			
			rtc_lwoff_wait();                      //等待最近一次对RTC寄存器的写操作完成		
			rtc_register_sync_wait();              //等待RTC寄存器与APB时钟同步		
			rtc_interrupt_enable(RTC_INT_SECOND);  //使能RTC秒中断
			rtc_lwoff_wait();                      //等待最近一次对RTC寄存器的写操作完成
			
			rtc_prescaler_set(clockfreq);          //频率为1Hz		
			rtc_lwoff_wait();                      //等待最近一次对RTC寄存器的写操作完成
		
			if (bkpflag != 0X6061)          //BKP0的内容既不是0X5050,也不是0X5051,说明是第一次配置,需要设置时间日期
			{
					RTC_Set_Time(2023, 8, 28, 23, 58, 55);   //设置时间 
			}		
		}
		else   //RTC继续计时 
		{
			retry = 30;     //避免卡死 
			while (((RESET == (RTC_CTL & RTC_CTL_RSYNF))&& retry))   //等待RTC寄存器与APB时钟同步
			{
					delay_ms(5);
					retry--;
			}
			retry = 100;    //检测RCU_LXTAL/CK_IRC40K是否正常工作 
			tempreg = rtc_divider_get();            //获取RTC分频值 
			while (retry)
			{
					delay_ms(5);
					retry--;			
					if (tempreg != rtc_divider_get())   //对比RTC分频值和tempreg,如果有差异,则退出 
					{
							break;                          //分频值!= tempreg, 即RTC在计数,说明晶振没问题 
					}
			}
			if (retry == 0)
			{
					bkp_write_data(BKP_DATA_0, 0xFFFF);    //标记错误的值 
					bkp_deinit();                          //复位备份域
					return 1;                              //初始化失败 
			}
			else
			{
					rtc_interrupt_enable(RTC_INT_SECOND);  //使能RTC秒中断
					rtc_lwoff_wait();                      //等待最近一次对RTC寄存器的写操作完成
			}	
		}
		nvic_irq_enable(RTC_IRQn, 0, 0);             //设置中断优先级
		RTC_Get_Time();      //更新时间 
		return 0;            //ok
}


//RTC时钟中断
//秒钟中断服务函数,顺带处理闹钟标志  	 
void RTC_IRQHandler(void)
{		 
		if(RESET != rtc_flag_get(RTC_FLAG_SECOND))  //秒中断
		{
			rtc_flag_clear(RTC_FLAG_SECOND); 		      //清除秒中断标志
			RTC_Get_Time();				     //更新时间   
			 printf("sec:%d\r\n", calendar.sec);
		}		

		if(RESET != rtc_flag_get(RTC_FLAG_ALARM))  //闹钟中断
		{
			rtc_flag_clear(RTC_FLAG_ALARM); 	       //清除闹钟中断标志   
			printf("ALARM A!\r\n");
		}	
		rtc_flag_clear(RTC_CTL_OVIF); 		//清除溢出  	  
		rtc_lwoff_wait();                 //等待最近一次对RTC寄存器的写操作完成	
}

//判断是否是闰年函数
//月份   1  2  3  4  5  6  7  8  9  10 11 12
//闰年   31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//year:年份
//返回值:该年份是不是闰年.1,是.0,不是
static uint8_t Is_Leap_Year(uint16_t year)
{			  
		if(year%4==0) //必须能被4整除
		{ 
			if(year%100==0) 
			{ 
				if(year%400==0)return 1;//如果以00结尾,还要能被400整除 	   
				else return 0;   
			}else return 1;   
		}else return 0;	
}	


//月份数据表											 
uint8_t const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表	  
//平年的月份日期表
const uint8_t mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};

//设置时间, 包括年月日时分秒
//以1970年1月1日为基准, 往后累加时间
//合法年份范围为: 1970 ~ 2105年
//syear,smon,sday,hour,min,sec:年月日时分秒
//返回值:设置结果。0,成功;1,失败。
uint8_t RTC_Set_Time(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec)
{
		uint32_t seccount=0;
		
		if(syear<1970||syear>2099)return 1;	   

		seccount = rtc_date2sec(syear, smon, sday, hour, min, sec); //将年月日时分秒转换成总秒钟数 

		//设置时钟
		rcu_periph_clock_enable(RCU_PMU);        //使能PMU时钟
		rcu_periph_clock_enable(RCU_BKPI);       //使能BKPI时钟
		pmu_backup_write_enable();               //备份域写使能
		//上面三步是必须的!
		
		rtc_counter_set(seccount);               //设置时间
		
		rtc_lwoff_wait();                        //等待最近一次对RTC寄存器的写操作完成	
		
		RTC_Get_Time();                          //设置完之后更新一下时间 	
		
		return 0;	    
}
//设置闹钟, 具体到年月日时分秒
//以1970年1月1日为基准, 往后累加时间
//合法年份范围为: 1970 ~ 2105年
//syear,smon,sday,hour,min,sec:闹钟的年月日时分秒   
//返回值:0,成功;其他:错误代码.
uint8_t RTC_Alarm_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec)
{
		uint32_t seccount=0;
		
		if(syear<1970||syear>2099)return 1;	   

		seccount = rtc_date2sec(syear, smon, sday, hour, min, sec); //将年月日时分秒转换成总秒钟数 

		//设置时钟
		rcu_periph_clock_enable(RCU_PMU);        //使能PMU时钟
		rcu_periph_clock_enable(RCU_BKPI);       //使能BKPI时钟
		pmu_backup_write_enable();               //备份域写使能
		//上面三步是必须的!

		rtc_alarm_config(seccount);              //设置闹钟时间
		
		rtc_lwoff_wait();                        //等待最近一次对RTC寄存器的写操作完成	
			
		return 0;	    
}
//得到当前的时间
//该函数不直接返回时间, 时间数据保存在calendar结构体里面
void RTC_Get_Time(void)
{
		static uint16_t daycnt=0;
		uint32_t seccount=0; 
		uint32_t temp=0;
		uint16_t temp1=0;	  
		
		seccount = rtc_counter_get();	 //获取RTC计数器的值(秒钟数)

		temp=seccount/86400;   //得到天数(秒钟数对应的)
		
		if(daycnt!=temp)//超过一天了
		{	  
			daycnt=temp;
			temp1=1970;	  //从1970年开始
			while(temp>=365)
			{				 
				if(Is_Leap_Year(temp1))  //是闰年
				{
					if(temp>=366)temp-=366;//闰年的秒钟数
					else break;  
				}
				else temp-=365;	    //平年 
				temp1++;  
			}   
			calendar.year=temp1;//得到年份
			temp1=0;
			while(temp>=28)//超过了一个月
			{
				if(Is_Leap_Year(calendar.year)&&temp1==1)//当年是不是闰年/2月份
				{
					if(temp>=29)temp-=29;//闰年的秒钟数
					else break; 
				}
				else 
				{
					if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
					else break;
				}
				temp1++;  
			}
			calendar.month=temp1+1;	  //得到月份
			calendar.date=temp+1;  	  //得到日期 
		}
		temp=seccount%86400;     		//得到秒钟数   	   
		calendar.hour=temp/3600;     	//小时
		calendar.min=(temp%3600)/60; 	//分钟	
		calendar.sec=(temp%3600)%60; 	//秒钟
		calendar.week=RTC_Get_Week(calendar.year,calendar.month,calendar.date);//获取星期   
}	 
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//year,month,day:公历年月日 
//返回值:星期号; 0, 星期天; 1 ~ 6: 星期一 ~ 星期六																						 
uint8_t RTC_Get_Week(uint16_t year,uint8_t month,uint8_t day)
{	
//	uint16_t temp2;
//	uint8_t yearH,yearL;
//	
//	yearH=year/100;	yearL=year%100; 
//	// 如果为21世纪,年份数加100  
//	if (yearH>19)yearL+=100;
//	// 所过闰年数只算1900年之后的  
//	temp2=yearL+yearL/4;
//	temp2=temp2%7; 
//	temp2=temp2+day+table_week[month-1];
//	if (yearL%4==0&&month<3)temp2--;
//	return(temp2%7);
	
		uint8_t week = 0;

		if (month < 3)
		{
				month += 12;
				--year;
		}

		week = (day + 1 + 2 * month + 3 * (month + 1) / 5 + year + (year >> 2) - year / 100 + year / 400) % 7;
		return week;
}

//将年月日时分秒转换成秒钟数
//以1970年1月1日为基准, 1970年1月1日, 0时0分0秒, 表示第0秒钟
//最大表示到2105年, 因为uint32_t最大表示136年的秒钟数(不包括闰年)!
//syear,smon,sday,hour,min,sec:闹钟的年月日时分秒   
//返回值:转换后的秒钟数.
static long rtc_date2sec(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
		uint16_t t;
		uint32_t seccount=0;
		for(t=1970;t<syear;t++)	//把所有年份的秒钟相加
		{
			if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
			else seccount+=31536000;			  //平年的秒钟数
		}
		smon-=1;
		for(t=0;t<smon;t++)	   //把前面月份的秒钟数相加
		{
			seccount+=(uint32_t)mon_table[t]*86400;//月份秒钟数相加
			if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数	   
		}
		seccount+=(uint32_t)(sday-1)*86400;//把前面日期的秒钟数相加 
		seccount+=(uint32_t)hour*3600;//小时秒钟数
		seccount+=(uint32_t)min*60;	 //分钟秒钟数
		seccount+=sec;//最后的秒钟加上去

		return seccount;
}


.h文件

#ifndef __RTC_H
#define __RTC_H	  
#include "sys.h"

												    
//时间结构体,包括年月日周时分秒等信息
typedef struct 
{
	uint8_t hour;       //时
	uint8_t min;        //分
	uint8_t sec;			  //秒
	//公历年月日周
	uint16_t year;      //年
	uint8_t  month;     //月
	uint8_t  date;      //日
	uint8_t  week;	    //周
}_calendar_obj;					 
extern _calendar_obj calendar;				//时间结构体

//静态函数 
static uint8_t Is_Leap_Year(uint16_t year);					//判断当前年份是不是闰年 
static long rtc_date2sec(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec); //将年月日时分秒转换成秒钟数

uint8_t RTC_Init(void);        					  //初始化RTC
uint8_t RTC_Set_Time(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec);	//设置时间	
uint8_t RTC_Alarm_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec);	//设置闹钟	时间
void RTC_Get_Time(void);         					//获取时间   
uint8_t RTC_Get_Week(uint16_t year,uint8_t month,uint8_t day); //根据年月日获取星期几 

#endif

示例代码

#include "sys.h"
#include "usart.h"		
#include "delay.h"	
#include "usmart.h"
#include "led.h"
#include "lcd.h"
#include "rtc.h" 	


int main(void)
{ 	
	  uint8_t t;
	  delay_init(120);                 //初始化延时函数 
	  usart_init(115200);              //初始化串口
	  usmart_init(120);	               //初始化USMART
	  LED_Init();							         //初始化LED
    LCD_Init();                      //初始化LCD    

	  POINT_COLOR=RED;    //设置字体为红色 
	  LCD_ShowString(60,50,200,16,16,"Mini GD32");	
	  LCD_ShowString(60,70,200,16,16,"RTC TEST");	
	  LCD_ShowString(60,90,200,16,16,"WKS SMART");	
		while(RTC_Init())		//RTC初始化,检测时钟是否工作正常
	  { 
			LCD_ShowString(60,130,200,16,16,"RTC ERROR!   ");	
			delay_ms(800);
			LCD_ShowString(60,130,200,16,16,"RTC Trying...");	
	  }		  
		//显示时间
		POINT_COLOR=BLUE;   //设置字体为蓝色					 
		LCD_ShowString(60,130,200,16,16,"    -  -     ");	   
		LCD_ShowString(60,162,200,16,16,"  :  :  ");	
  	while(1) 
	  {		
         // 检查当前秒数是否与上次记录的秒数不同
         // 如果不同,说明时间已经变化,需要更新显示内容 
		 if(t!=calendar.sec)
		 {
			t=calendar.sec;
			LCD_ShowNum(60,130,calendar.year,4,16);									  
			LCD_ShowNum(100,130,calendar.month,2,16);									  
			LCD_ShowNum(124,130,calendar.date,2,16);	 //在指定位置显示年月日
			switch(calendar.week)
			{
				case 0:
					LCD_ShowString(60,148,200,16,16,"Sunday   ");
					break;
				case 1:
					LCD_ShowString(60,148,200,16,16,"Monday   ");
					break;
				case 2:
					LCD_ShowString(60,148,200,16,16,"Tuesday  ");
					break;
				case 3:
					LCD_ShowString(60,148,200,16,16,"Wednesday");
					break;
				case 4:
					LCD_ShowString(60,148,200,16,16,"Thursday ");
					break;
				case 5:
					LCD_ShowString(60,148,200,16,16,"Friday   ");
					break;
				case 6:
					LCD_ShowString(60,148,200,16,16,"Saturday ");
					break;  
			}
			LCD_ShowNum(60,162,calendar.hour,2,16);									  
			LCD_ShowNum(84,162,calendar.min,2,16);									  
			LCD_ShowNum(108,162,calendar.sec,2,16);       //在指定位置显示时分秒
			LED0_TOGGLE();                                //翻转一次LED0 
		}	                                      
		delay_ms(10); 
	 } 
}


DMA

参数配置

.c文件

#include "dma.h"
#include "delay.h"


//DMA0的DMA_CH3通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//par:外设地址
//mar:存储器地址
void DMA_Config(uint32_t mar, uint32_t par)
{
	  dma_parameter_struct dma_init_struct;  
	  
	  rcu_periph_clock_enable(RCU_DMA0);     //使能DMA0时钟
	
	  dma_deinit(DMA0, DMA_CH3);             //复位DMA0的通道DMA_CH3
    dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;     //存储器到外设模式
    dma_init_struct.memory_addr = mar;                        //DMA 存储器地址
    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;  //存储器地址增量模式 
    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;     //存储器数据传输宽度: 8位
    dma_init_struct.number = 0;                               //DMA通道数据传输数量清零, 后续在MYDMA_Enable函数设置
    dma_init_struct.periph_addr = par;                        //DMA 外设地址
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //外设地址非增量模式
    dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; //外设数据传输宽度: 8位
    dma_init_struct.priority = DMA_PRIORITY_MEDIUM;           //中等优先级
    dma_init(DMA0, DMA_CH3, &dma_init_struct);                //初始化DMA0的通道DMA_CH3 
	
	  dma_circulation_disable(DMA0, DMA_CH3);                   //非DMA循环模式
    dma_memory_to_memory_disable(DMA0, DMA_CH3);              //非存储器到存储器传输模式
	
}




//开启一次DMA传输
//num: 数据传输量
void MYDMA_Enable(uint16_t num)
{
	  dma_channel_disable(DMA0, DMA_CH3);    //关闭DMA传输
	  
	  while(DMA_CHCTL(DMA0, DMA_CH3) & DMA_CHXCTL_CHEN);  //确保DMA可以被设置 	  
	
	  dma_transfer_number_config(DMA0, DMA_CH3, num);     //配置DMA数据传输数量
	
	  dma_channel_enable(DMA0, DMA_CH3);     //开启DMA传输
}

.h文件

#ifndef __DMA_H
#define	__DMA_H

#include "sys.h"


void DMA_Config(uint32_t mar, uint32_t par);      //配置DMA0的DMA_CH3
void MYDMA_Enable(uint16_t num);                  //使能一次DMA传输 

#endif

示例代码

#include "sys.h"
#include "usart.h"		
#include "delay.h"	
#include "usmart.h"
#include "led.h"
#include "lcd.h"
#include "key.h"
#include "dma.h" 


#define USART0_DATA_ADDRESS      ((uint32_t)((USART0) + 0x00000004U))

const uint8_t TEXT_TO_SEND[] = {"WKS Mini GD32 DMA 串口实验"}; //要循环发送的字符串 
#define SEND_BUF_SIZE       (sizeof(TEXT_TO_SEND) + 2) * 200   //发送数据长度, 等于sizeof(TEXT_TO_SEND) + 2的200倍. 
uint8_t SendBuff[SEND_BUF_SIZE];	                             //发送数据缓冲区

int main(void)
{ 	
	  uint8_t  key = 0;
    uint16_t i, k;
    uint16_t len;
    uint8_t  mask = 0;
    float pro = 0;                   //用于显示进度 
	
	  delay_init(120);                 //初始化延时函数 
	  usart_init(115200);              //初始化串口
	  LED_Init();							         //初始化LED
    LCD_Init();                      //初始化LCD   
    KEY_Init();                      //初始化按键	
	  DMA_Config((uint32_t)SendBuff, USART0_DATA_ADDRESS); //DMA0的DMA_CH3通道配置,外设为串口0数据寄存器,存储器为SendBuff

	  POINT_COLOR=RED;    //设置字体为红色 
	 	LCD_ShowString(30,50,200,16,16,"Mini GD32");	
	  LCD_ShowString(30,70,200,16,16,"DMA TEST");	
	  LCD_ShowString(30,90,200,16,16,"WKS SMART");	 
	  LCD_ShowString(30,110,200,16,16,"KEY0:Start"); 
	  POINT_COLOR=BLUE;   //设置字体为蓝色
	 
	  len = sizeof(TEXT_TO_SEND);
	  k = 0;
		
	  for (i = 0; i < SEND_BUF_SIZE; i++)  //填充ASCII字符集数据 
    {
        if (k >= len)   //加入换行符 
        {
            if (mask)
            {
                SendBuff[i] = 0x0a;
                k = 0;
            }
            else
            {
                SendBuff[i] = 0x0d;
                mask++;
            }
        }
        else     //复制TEXT_TO_SEND的内容到SendBuff  
        {
            mask = 0;
            SendBuff[i] = TEXT_TO_SEND[k];
            k++;
        }
    }
    i = 0;
		
  	while(1) 
	  {		 
		    key = KEY_Scan(0);

        if (key == KEY0_PRES)  //KEY0按下
        {   
					  printf("\r\nDMA DATA:\r\n");
					  LCD_ShowString(30,130,200,16,16,"Start Transimit....");	 //显示开始发送
	          LCD_ShowString(30,150,200,16,16,"   %");                 //显示百分号
            usart_dma_transmit_config(USART0, USART_TRANSMIT_DMA_ENABLE);  //使能串口0的DMA
            MYDMA_Enable(SEND_BUF_SIZE);  //开启一次DMA传输
					  //等待DMA传输完成,传输数据期间,可以执行另外的任务
					  while (1)
            {
                if (dma_flag_get(DMA0, DMA_CH3, DMA_FLAG_FTF))        //等待DMA0的DMA_CH3传输完成 
                {
                    dma_flag_clear(DMA0, DMA_CH3, DMA_INT_FLAG_FTF);  //清除DMA0的DMA_CH3传输完成标志 
									  usart_dma_transmit_config(USART0, USART_TRANSMIT_DMA_DISABLE); //传输完成以后关闭串口DMA 
                    break;
                }

                pro = dma_transfer_number_get(DMA0, DMA_CH3); //得到当前还剩余多少个数据 
                len = SEND_BUF_SIZE;        //总长度 
                pro = 1 - (pro / len);      //得到百分比 
                pro *= 100;                 //扩大100倍 
								LCD_ShowNum(30,150,pro,3,16);	  //显示传输进度  
            } 
							  LCD_ShowNum(30,150,100,3,16);   //显示100%	  
		            LCD_ShowString(30,130,200,16,16,"Transimit Finished!"); //提示传送完成
        }
        i++;
        delay_ms(10);
        if (i == 20)
        {
            LED0_TOGGLE();  //LED0闪烁,提示系统正在运行 
            i = 0;
        }
	  } 
}


IIC

底层驱动

.c文件

//初始化IIC
void IIC_Init(void)
{
    rcu_periph_clock_enable(RCU_GPIO_IIC);  //GPIOC时钟使能
    
	  //SCL引脚模式设置,推挽输出 
   	gpio_init(GPIO_SCL, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_SCL);
	  //SDA引脚模式设置,开漏输出,这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平
	  gpio_init(GPIO_SDA, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_SDA);
    
    IIC_Stop();     //停止总线上所有设备   
}


//IIC延时函数,用于控制IIC读写速度
static void IIC_delay(void)
{
    delay_us(2);    //2us的延时
}

//产生IIC起始信号
void IIC_Start(void)
{
    IIC_SDA(1);
    IIC_SCL(1);
    IIC_delay();
    IIC_SDA(0);     //START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 
    IIC_delay();
    IIC_SCL(0);     //钳住I2C总线,准备发送或接收数据 
    IIC_delay();
}	  

//产生IIC停止信号
void IIC_Stop(void)
{
    IIC_SDA(0);     //STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号
    IIC_delay();
    IIC_SCL(1);
    IIC_delay();
    IIC_SDA(1);     //发送I2C总线结束信号 
    IIC_delay();				   	
}

//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
uint8_t IIC_Wait_Ack(void)
{
		uint8_t waittime = 0;
		uint8_t rack = 0;

		IIC_SDA(1);       //主机释放SDA线(此时外部器件可以拉低SDA线)
		IIC_delay();	    
		IIC_SCL(1);       //SCL=1, 此时从机可以返回ACK    
		IIC_delay();	    
	
		while(IIC_READ_SDA)       //等待应答 
		{
			waittime++;
			if(waittime>250)
			{
				IIC_Stop();
				rack = 1;
				break;      //没有收到应答信号
			}
		}
	
		IIC_SCL(0);     //SCL=0, 结束ACK检查  
		IIC_delay();	
		return rack;  
} 

//产生ACK应答
void IIC_Ack(void)
{
    IIC_SDA(0);     //SCL 0 -> 1  时 SDA = 0,表示应答 
    IIC_delay();
    IIC_SCL(1);     //产生一个时钟 
    IIC_delay();
    IIC_SCL(0);
    IIC_delay();
    IIC_SDA(1);     //主机释放SDA线 
    IIC_delay();
}

//不产生ACK应答		    
void IIC_NAck(void)
{
    IIC_SDA(1);     //SCL 0 -> 1  时 SDA = 1,表示不应答 
    IIC_delay();
    IIC_SCL(1);     //产生一个时钟 
    IIC_delay();
    IIC_SCL(0);
    IIC_delay();
}		

//IIC发送一个字节
//data: 要发送的数据	  
void IIC_Send_Byte(uint8_t data)
{                        
    uint8_t t;   
	
    for(t=0;t<8;t++)
    {              
       IIC_SDA((data & 0x80) >> 7);    //高位先发送 
       IIC_delay();
       IIC_SCL(1);
       IIC_delay();
       IIC_SCL(0);
       data <<= 1;      //左移1位,用于下一位发送 
    }	 
		IIC_SDA(1);         //发送完成, 主机释放SDA线 
} 	    

//IIC读取一个字节
//ack:ack=1时,发送ACK,ack=0,发送nACK   
//返回值:接收到的数据
uint8_t IIC_Read_Byte(uint8_t ack)
{
	  uint8_t i,receive=0;
	
    for(i=0;i<8;i++ )     //接收1个字节数据 
	  {
        receive <<= 1;    //高位先输出,所以先收到的数据位要左移
        IIC_SCL(1);
        IIC_delay();

        if (IIC_READ_SDA)
        {
            receive++;
        }
        
        IIC_SCL(0);
        IIC_delay();
    }					 
    if (!ack)
        IIC_NAck();       //发送nACK
    else
        IIC_Ack();        //发送ACK   
		
    return receive;       //返回读到的数据
}


.h

#ifndef _MYIIC_H
#define _MYIIC_H
#include "sys.h"

/* 移植时只需修改引脚相关宏定义 */
#define RCU_GPIO_IIC             RCU_GPIOC

#define GPIO_SCL            GPIOC
#define GPIO_PIN_SCL        GPIO_PIN_12

#define GPIO_SDA            GPIOC
#define GPIO_PIN_SDA        GPIO_PIN_11
/*----------------------------*/
//IO操作
#define IIC_SCL(x)          gpio_bit_write(GPIO_SCL, GPIO_PIN_SCL,(bit_status)(x))  //SCL
#define IIC_SDA(x)          gpio_bit_write(GPIO_SDA, GPIO_PIN_SDA, (bit_status)(x))  //SDA
#define IIC_READ_SDA        gpio_input_bit_get(GPIO_SDA,GPIO_PIN_SDA)  //读取SDA

//IIC所有操作函数
void IIC_Init(void);                //初始化IIC的IO口				 
void IIC_Start(void);				        //发送IIC开始信号
void IIC_Stop(void);	  			      //发送IIC停止信号
void IIC_Ack(void);					        //IIC发送ACK信号
void IIC_NAck(void);				        //IIC不发送ACK信号
uint8_t IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Send_Byte(uint8_t data);		//IIC发送一个字节
uint8_t IIC_Read_Byte(uint8_t ack); //IIC读取一个字节
 
#endif


SPI

底层驱动

.c头文件

//SPI初始化代码,配置成主机模式
//这里是针对SPI0的初始化
void SPI0_Init(void)
{
	  spi_parameter_struct spi_init_struct;
	
    rcu_periph_clock_enable(RCU_GPIOA);    //GPIOA时钟使能
	  rcu_periph_clock_enable(RCU_SPI0);     //SPI0时钟使能
	  rcu_periph_clock_enable(RCU_AF);       //复用时钟使能
	   
	  //PA5,6,7复用推挽输出
	  gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7);  
	
	  spi_init_struct.trans_mode           = SPI_TRANSMODE_FULLDUPLEX;   //全双工模式
    spi_init_struct.device_mode          = SPI_MASTER;                 //主机模式
    spi_init_struct.frame_size           = SPI_FRAMESIZE_8BIT;         //8位数据帧格式 
    spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE;    //空闲状态下,CLK保持高电平,在第二个时钟跳变沿采集第一个数据
    spi_init_struct.nss                  = SPI_NSS_SOFT;               //NSS软件模式,NSS电平取决于SWNSS位
    spi_init_struct.prescale             = SPI_PSC_256;                //默认使用256分频, 速度最低
    spi_init_struct.endian               = SPI_ENDIAN_MSB;             //MSB先传输
    spi_init(SPI0, &spi_init_struct);
	  
    spi_enable(SPI0);	                                                 //使能SPI0	
}


//SPI0速度设置函数
//SPI0时钟选择来自APB2, 即PCLK2, 为120Mhz
//SPI速度 = PCLK2 / 2^(speed + 1)
//speed   : SPI0时钟分频系数
void SPI0_SetSpeed(uint8_t speed)
{
	  speed &= 0X07;                  //限制范围
	  spi_disable(SPI0);	            //SPI0失能
	  SPI_CTL0(SPI0) &= ~(7 << 3);    //先清零
	  SPI_CTL0(SPI0) |= speed << 3;   //设置分频系数
	  spi_enable(SPI0);	              //SPI0使能
}


//SPI0读写一个字节数据
//txdata  : 要发送的数据(1字节)
//返回值:接收到的数据(1字节)
uint8_t SPI0_ReadWriteByte(uint8_t txdata)
{   
	  while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE));    //等待发送缓冲区空
	
	  spi_i2s_data_transmit(SPI0, txdata);                     //发送一个字节
	
	  while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE));   //等待接收缓冲区非空
	
	  return spi_i2s_data_receive(SPI0);                       //返回收到的数据
}


.h头文件

#ifndef __SPI_H
#define __SPI_H

#include "sys.h"



//SPI总线速度设置 
#define SPI_SPEED_2         0
#define SPI_SPEED_4         1
#define SPI_SPEED_8         2
#define SPI_SPEED_16        3
#define SPI_SPEED_32        4
#define SPI_SPEED_64        5
#define SPI_SPEED_128       6
#define SPI_SPEED_256       7


void SPI0_Init(void);                           //SPI0初始化               
void SPI0_SetSpeed(uint8_t speed);              //设置SPI0速度
uint8_t SPI0_ReadWriteByte(uint8_t txdata);     //SPI0读写一个字节

#endif


Flash读写

底层代码

.c文件

#include "fmc.h"
#include "delay.h"
#include "usart.h"


//从指定地址读取一个半字 (16位数据) 
//faddr : 读取地址 (此地址必须为2的倍数!!) 
//返回值:读取到的数据 (16位)
uint16_t GDFLASH_ReadHalfWord(uint32_t faddr)
{
		return  REG16(faddr); 
}

//不检查的写入
//这个函数假设已经把原来的扇区擦除过再写入
//waddr  : 起始地址 (此地址必须为2的倍数!!)
//pbuf   : 数据指针
//length : 要写入的 半字(16位)数 
void GDFLASH_Write_NoCheck(uint32_t waddr, uint16_t *pbuf, uint16_t length)   
{ 			 		 
		uint16_t i;
		for(i=0;i<length;i++)
		{  
				fmc_halfword_program(waddr, pbuf[i]);
				waddr+=2; //地址增加2,指向下一个半字
		}  
} 

//在FLASH指定位置,写入指定长度的数据(自动擦除)
//该函数往GD32闪存控制器FMC指定位置写入指定长度的数据
//该函数会先检测要写入的页是否是空(全0XFFFF)的?, 如果
//不是, 则先擦除, 如果是, 则直接往页里面写入数据.
//数据长度不足一页时,自动写回擦除前的数据
//waddr  : 起始地址 (此地址必须为2的倍数!!)
//pbuf   : 数据指针
//length : 要写入的 半字(16位)数 
uint16_t FLASH_BUF[FLASH_PAGE_SIZE/2]; //最多是2K字节
void GDFLASH_Write(uint32_t waddr, uint16_t *pbuf, uint16_t length)	
{
    uint32_t secpos;    //扇区地址 
    uint16_t secoff;    //扇区内偏移地址(16位字计算) 
    uint16_t secremain; //扇区内剩余地址(16位字计算) 
    uint16_t i;
    uint32_t offaddr;   //去掉0X08000000后的地址 
	
		if(waddr<GD32_FLASH_BASE||(waddr>=(GD32_FLASH_BASE+GD32_FLASH_SIZE)))
		{
			  return;           //非法地址
		}
		
		fmc_unlock();					//解锁FLASH
		
		offaddr=waddr-GD32_FLASH_BASE;		    //实际偏移地址.
		secpos=offaddr/FLASH_PAGE_SIZE;			  //扇区地址  0~127 for GD32F303RBT6
		secoff=(offaddr%FLASH_PAGE_SIZE)/2;		//在扇区内的偏移(2个字节为基本单位.)
		secremain=FLASH_PAGE_SIZE/2-secoff;		//扇区剩余空间大小   
		if(length<=secremain) secremain=length;  //不大于该扇区范围
		while(1) 
		{	
			GDFLASH_Read(secpos*FLASH_PAGE_SIZE+GD32_FLASH_BASE,FLASH_BUF,FLASH_PAGE_SIZE/2); //读出整个扇区的内容
			for(i=0;i<secremain;i++)               //校验数据
			{
				if(FLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除  	  
			}
			if(i<secremain)//需要擦除
			{
				fmc_page_erase(secpos*FLASH_PAGE_SIZE+GD32_FLASH_BASE); //擦除这个扇区
				for(i=0;i<secremain;i++) //复制
				{
					FLASH_BUF[i+secoff]=pbuf[i];	  
				}
				GDFLASH_Write_NoCheck(secpos*FLASH_PAGE_SIZE+GD32_FLASH_BASE,FLASH_BUF,FLASH_PAGE_SIZE/2);//写入整个扇区  
			}else GDFLASH_Write_NoCheck(waddr,pbuf,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
			if(length==secremain) break;//写入结束了
			else //写入未结束
			{
				  secpos++;				    //页地址增1
				  secoff=0;				    //偏移位置为0 	 
					pbuf+=secremain;  	//指针偏移
				  waddr+=secremain*2;	//写地址偏移(16位数据地址,需要*2)	   
					length-=secremain;	//要写入字节(16位)数递减
				  if(length>(FLASH_PAGE_SIZE/2))secremain=FLASH_PAGE_SIZE/2;//下一个扇区还是写不完
				  else secremain=length;//下一个扇区可以写完了
			}	 
		}	
		fmc_lock();//FLASH上锁
}


//从指定地址开始读出指定长度的数据
//raddr : 起始地址
//pbuf  : 数据指针
//length: 要读取的半字(16位)数,即2个字节的整数倍
void GDFLASH_Read(uint32_t raddr, uint16_t *pbuf, uint16_t length)   	
{
		uint16_t i;
		for(i=0;i<length;i++)
		{
			pbuf[i]=GDFLASH_ReadHalfWord(raddr); //读取2个字节.
			raddr+=2; //偏移2个字节.	
		}
}

//测试用代码///
//测试写数据(写1个半字)
//WriteAddr:起始地址
//WriteData:要写入的数据
void Test_Write(uint32_t WriteAddr,uint16_t WriteData)   	
{
		GDFLASH_Write(WriteAddr,&WriteData,1); //写入一个半字
}

.h文件

#ifndef __FMC_H__
#define __FMC_H__
#include "sys.h"  


#define FLASH_PAGE_SIZE	        ((uint32_t)0x0800)   //页大小
#define GD32_FLASH_SIZE         0x40000              //GD32内部FLASH总大小 
#define GD32_FLASH_BASE         0x08000000 			     //GD32内部FLASH起始地址




uint16_t GDFLASH_ReadHalfWord(uint32_t faddr);		  	                       //FLASH读出半字  
void GDFLASH_Write_NoCheck(uint32_t waddr, uint16_t *pbuf, uint16_t length); //不检查的写入
void GDFLASH_Write(uint32_t waddr, uint16_t *pbuf, uint16_t length);		     //在FLASH指定位置,写入指定长度的数据(自动擦除)
void GDFLASH_Read(uint32_t raddr, uint16_t *pbuf, uint16_t length);   		   //从指定地址开始读出指定长度的数据

//测试写入
void Test_Write(uint32_t WriteAddr,uint16_t WriteData);		

#endif

示例代码

while(1) 
	  {		 
			key=KEY_Scan(0);   //按键扫描
			if(key==KEY1_PRES) //KEY1按下,写入GD32 FLASH
			{
					LCD_Fill(0,150,239,319,WHITE);   //清除半屏    
					LCD_ShowString(30,150,200,16,16,"Start Write FLASH....");
					GDFLASH_Write(FLASH_SAVE_ADDR, (uint16_t *)TEXT_Buffer, SIZE);              
					LCD_ShowString(30,150,200,16,16,"FLASH Write Finished!"); //提示传送完成
			}
			if(key==KEY0_PRES) //KEY0 按下,读取字符串并显示
			{
					LCD_ShowString(30,150,200,16,16,"Start Read FLASH.... ");
					GDFLASH_Read(FLASH_SAVE_ADDR, (uint16_t *)datatemp, SIZE);                        
					LCD_ShowString(30,150,200,16,16,"The Data Readed Is:  "); //提示传送完成
					LCD_ShowString(30,170,200,16,16,(char *)datatemp);        //显示读到的字符串
			}
			i++;
			delay_ms(10);
			if(i==20)
			{
					LED0_TOGGLE(); //LED0提示系统正在运行	
					i=0;
			}		   
	 } 

待机唤醒

参数配置

.c文件

#include "pwr.h"
#include "led.h"
#include "lcd.h"



//外部中断0服务程序
void EXTI0_IRQHandler(void)
{
    exti_interrupt_flag_clear(EXTI_0);  //清除EXTI Line0上的中断标志位  
}


//低功耗模式下的按键初始化(用于唤醒睡眠模式/停止模式)
void pwr_wkup_key_init(void)
{
	  rcu_periph_clock_enable(RCU_AF);        //使能AF时钟
    rcu_periph_clock_enable(RCU_GPIOA);     //GPIOA时钟使能
	
		gpio_init(GPIOA, GPIO_MODE_IPD, GPIO_OSPEED_50MHZ, GPIO_PIN_0);     //设置PA0为下拉输入
	
	  gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_0); //选择PA0作为EXTI源
	
	  exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_RISING);   //初始化EXTI_0
	
	  nvic_irq_enable(EXTI0_IRQn, 2, 2);      //使能外部中断线EXTI0中断请求并设置优先级,抢占优先级为2,响应优先级为2
	
	  exti_interrupt_flag_clear(EXTI_0);      //清除EXTI Line0上的中断标志位	
}




//进入待机模式
void pwr_enter_standby(void)
{
	  rcu_periph_clock_enable(RCU_PMU); //使能电源时钟
	  
	  pmu_wakeup_pin_enable();          //设置WKUP用于唤醒 
	
	  pmu_to_standbymode();             //进入待机模式	
}

.h文件

#ifndef __PWR_H
#define __PWR_H

#include "sys.h"


void pwr_wkup_key_init(void);       //唤醒按键初始化 
void pwr_enter_standby(void);       //进入待机模式 

#endif


示例代码

int main(void)
{ 	
	  uint8_t t = 0;
    uint8_t key = 0;
	
	  delay_init(120);                 //初始化延时函数 
	  usart_init(115200);              //初始化串口
	  LED_Init();							         //初始化LED
    LCD_Init();                      //初始化LCD   
    KEY_Init();                      //初始化按键	

	  POINT_COLOR=RED;    //设置字体为红色 
	  LCD_ShowString(30,50,200,16,16,"Mini GD32");	
	  LCD_ShowString(30,70,200,16,16,"STANDBY TEST");	
	  LCD_ShowString(30,90,200,16,16,"WKS SMART");	
	  LCD_ShowString(30,110,200,16,16,"KEY0:Enter STANDBY MODE");	
    LCD_ShowString(30,130,200,16,16,"KEY_UP:Exit STANDBY MODE");	

  	while(1) 
	  {		 
		    key = KEY_Scan(0);

        if (key == KEY0_PRES)
        {  
            pwr_enter_standby();    //进入待机模式
            //从待机模式唤醒相当于系统重启(复位), 因此不会执行到这里 
        }

        if ((t % 20) == 0)
        {
            LED0_TOGGLE();      //每200ms,翻转一次LED0 
        }

        delay_ms(10);
        t++;
	  } 
}

IAP下载

想详细了解IAP可参考这篇文章零基础IAP远程升级入门讲解-CSDN博客

bootloader代码

iap.c

#include "iap.h"
#include "usart.h"
#include "delay.h"
#include "fmc.h"


iapfun jump2app;
uint16_t iapbuf[1024];       //2K字节缓存 

//IAP写入APP BIN
//appxaddr : 应用程序的起始地址
//appbuf   : 应用程序CODE
//appsize  : 应用程序大小(字节)
void iap_write_appbin(uint32_t appxaddr, uint8_t *appbuf, uint32_t appsize)
{
    uint16_t t;
    uint16_t i = 0;
    uint16_t temp;
    uint32_t fwaddr = appxaddr; //当前写入的地址 
    uint8_t *dfu = appbuf;

    for (t = 0; t < appsize; t += 2)
    {
        temp = (uint16_t)dfu[1] << 8;
        temp |= (uint16_t)dfu[0];
        dfu += 2;               //偏移2个字节 
        iapbuf[i++] = temp;
        if (i == 1024)
        {
            i = 0;
            GDFLASH_Write(fwaddr, iapbuf, 1024);
            fwaddr += 2048;     //偏移2048  16 = 2 * 8  所以要乘以2 
        }
    }
    if(i)
    {
        GDFLASH_Write(fwaddr, iapbuf, i);  //将最后的一些内容字节写进去 
    }
}

//跳转到应用程序段(执行APP)
//appxaddr : 应用程序的起始地址
void iap_load_app(uint32_t appxaddr)
{
    if (((REG32(appxaddr)) & 0x2FFE0000) == 0x20000000)     //检查栈顶地址是否合法.
    {
        //用户代码区第二个字为程序开始地址(复位地址) 
        jump2app = (iapfun) REG32(appxaddr + 4);
        
        //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) 
        sys_msr_msp(REG32(appxaddr));
        
        //跳转到APP 
        jump2app();
    }
}

iap.h

#ifndef __IAP_H
#define __IAP_H

#include "sys.h"




typedef void (*iapfun)(void);                   //定义一个函数类型的参数 

#define FLASH_APP1_ADDR         0x08009000      //第一个应用程序起始地址(存放在内部FLASH)
                                                //保留 0X08000000~0X08008FFF(36KB) 的空间为 Bootloader 使用
                                                 


void iap_load_app(uint32_t appxaddr);   //跳转到APP程序执行 
void iap_write_appbin(uint32_t appxaddr,uint8_t *appbuf,uint32_t applen);   //在指定地址开始,写入bin 

#endif

Flash_app示例代码

#include "sys.h"
#include "usart.h"		
#include "delay.h"	
#include "usmart.h"
#include "led.h"
#include "lcd.h"
#include "rtc.h" 	


int main(void)
{ 	
	  uint8_t t;
	
	  //设置向量表地址为 NVIC_VECTTAB_FLASH+0x9000
	  nvic_vector_table_set (NVIC_VECTTAB_FLASH,0x9000); 
	
	  delay_init(120);                 //初始化延时函数 
	  usart_init(115200);              //初始化串口
	  usmart_init(120);	               //初始化USMART
	  LED_Init();							         //初始化LED
    LCD_Init();                      //初始化LCD    

	  POINT_COLOR=RED;    //设置字体为红色 
	  LCD_ShowString(60,50,200,16,16,"Mini GD32");	
	  LCD_ShowString(60,70,200,16,16,"RTC TEST");	
	  LCD_ShowString(60,90,200,16,16,"WKS SMART");	
		while(RTC_Init())		//RTC初始化,检测时钟是否工作正常
	  { 
			LCD_ShowString(60,130,200,16,16,"RTC ERROR!   ");	
			delay_ms(800);
			LCD_ShowString(60,130,200,16,16,"RTC Trying...");	
	  }		  
		//显示时间
		POINT_COLOR=BLUE;   //设置字体为蓝色					 
		LCD_ShowString(60,130,200,16,16,"    -  -     ");	   
		LCD_ShowString(60,162,200,16,16,"  :  :  ");	
  	while(1) 
	  {		 
		 if(t!=calendar.sec)
		 {
			t=calendar.sec;
			LCD_ShowNum(60,130,calendar.year,4,16);									  
			LCD_ShowNum(100,130,calendar.month,2,16);									  
			LCD_ShowNum(124,130,calendar.date,2,16);	 //在指定位置显示年月日
			switch(calendar.week)
			{
				case 0:
					LCD_ShowString(60,148,200,16,16,"Sunday   ");
					break;
				case 1:
					LCD_ShowString(60,148,200,16,16,"Monday   ");
					break;
				case 2:
					LCD_ShowString(60,148,200,16,16,"Tuesday  ");
					break;
				case 3:
					LCD_ShowString(60,148,200,16,16,"Wednesday");
					break;
				case 4:
					LCD_ShowString(60,148,200,16,16,"Thursday ");
					break;
				case 5:
					LCD_ShowString(60,148,200,16,16,"Friday   ");
					break;
				case 6:
					LCD_ShowString(60,148,200,16,16,"Saturday ");
					break;  
			}
			LCD_ShowNum(60,162,calendar.hour,2,16);									  
			LCD_ShowNum(84,162,calendar.min,2,16);									  
			LCD_ShowNum(108,162,calendar.sec,2,16);       //在指定位置显示时分秒
			LED0_TOGGLE();                                //翻转一次LED0 
		}	                                      
		delay_ms(10); 
	 } 
}

SRAM_app示例代码

在main函数中,添加这行代码。

//设置向量表地址为 NVIC_VECTTAB_RAM+0x1000
	nvic_vector_table_set (NVIC_VECTTAB_RAM,0x1000); 

int main(void)
{ 
    //设置向量表地址为 NVIC_VECTTAB_RAM+0x1000
	  nvic_vector_table_set (NVIC_VECTTAB_RAM,0x1000); 
	
	  delay_init(120);                 //初始化延时函数 
	  usart_init(115200);              //初始化串口
	  LED_Init();							         //初始化LED
    LCD_Init();                      //初始化LCD  
	  KEY_Init();                      //初始化按键
    tp_dev.init();				   	       //触摸屏初始化 
	  
	  POINT_COLOR=RED;    //设置字体为红色 
		LCD_ShowString(30,50,200,16,16,"Mini GD32");	
  	LCD_ShowString(30,70,200,16,16,"TOUCH TEST");	
	  LCD_ShowString(30,90,200,16,16,"WKS SMART");	

	  if(tp_dev.touchtype!=0X80)
	  {
	    	LCD_ShowString(30,130,200,16,16,"Press KEY0 to Adjust");//电阻屏才显示
	  }
		delay_ms(1500);
		Load_Drow_Dialog();	 	
		
		if(tp_dev.touchtype&0X80)ctp_test(); //电容屏测试
		else rtp_test(); 					           //电阻屏测试  	  
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值