GD32F303调试小记(零)之工程创建与编译

前言

干这行的朋友都知道,真正拿单片机做项目时,作为软件编写人员,你所掌握的肯定不止一款单片机,又或者说你必须有能独立上手新单片机的能力。这里的新指的是对你个人来说是从未接触过的或者不熟悉的,而不一定是说这个单片机有多新。而调试一款新的单片机,往往得从工程的创建开始,这里分享一下GD32F303以MDK为编译软件从零开始的工程创建与编译。

环境搭建

  • 1、首先你的电脑安装了MDK,且已破解。版本没多大要求,支持C99即可,我的MDK版本是5.26。
    GD32F303调试小记(零)之工程创建与编译
    GD32F303调试小记(零)之工程创建与编译
  • 2、接着下载安装keil下对GD32F303的支持包。安装在你的KEIL5安装路径下就可以了。
    1)、进入Keil官网,选择Supported Microcontrollers.
    GD32F303调试小记(零)之工程创建与编译

2)、滚动页面找到我们使用的芯片对应的支持包,依次点击GigaDevice --> GD32F30X Series --> GD32F303 --> GD32F303RC
GD32F303调试小记(零)之工程创建与编译3)、进去后会有对该芯片的一个描述,点击页面里的Download
GD32F303调试小记(零)之工程创建与编译

4)、找到KEIL5的安装路径后,一路NEXT,最后FINISH即可。这里我已经装过,页面会略有区别。
GD32F303调试小记(零)之工程创建与编译

工程创建

  1. 打开KEIL5,选择Project --> new uVision Project。会跳出让你选择工程存放的地方,自己新建个文件夹,并给工程起名如:“test”。GD32F303调试小记(零)之工程创建与编译

  2. 之后会让你选择芯片型号,这里我们选GD32F303RC。(如果芯片里没有GigaDevice或没有GD32F303RC的查看下自己GD32F303的支持包安装路径是否在KEIL5的安装路径下)
    GD32F303调试小记(零)之工程创建与编译

  3. 弹出的Manage Run-Time Environment我们不用管直接右下角Cancel取消掉。

  4. 此时界面如下,一个空的GD32F303 keil5工程已经建好:
    GD32F303调试小记(零)之工程创建与编译

文件添加

  1. 由于32位单片机本身外设资源很丰富,不再如51一样使用寄存器开发,一般引入官方提供的标准库甚至HAL库进行开发,文件较为多杂,在把文件添加进来前,我们先建多个文件夹,具体如下:
    GD32F303调试小记(零)之工程创建与编译
    1)、Application/MDK-ARM 用于放置工程启动文件。如sratup_gd32f303_hd.s
    Application/User/FrameWork用于放置程序框架文件。比如是使用纯裸机还是状态机还是时间片还是RTOS。
    Application/User/Core用于放置main.c和gd32f30x_it.c,各模块间的业务逻辑,主要在这两个文件里体现。
    Application/User/Core_init用于放置配置单片机外设资源的文件。如AD、USART等外设的初始化函数放在此处。
    Application/User/Board_drv用于放置实现某些具体功能的函数文件。如AD滤波函数,显示函数等。
    2)、 Drivers/Library用于放置官方提供的标准库文件。
    Drivers/CMSIS用于放置官方提供的最底层头文件,里头有相应单片机的寄存器定义、编译环境配置等。
    3)、其他文件是我自己位实际项目所预留的,这部分不必跟我一样。

  2. 电脑里的工程文件夹如下:
    GD32F303调试小记(零)之工程创建与编译1)、Drivers里放置官方给我们的源文件。
    GUI放置要使用GUI库,这里可以先忽略。
    map放置工程编译输出的.map文件,便于查看每个代码段和整个代码的大小,分析问题,这里也不多说。
    MDK-ARM放置.uvprojx文件和启动文件。方便打开和调整启动文件。
    GD32F303调试小记(零)之工程创建与编译2)、User对应放置工程里以Application开头的文件。其文件下面会分成上图这几类。
    其中TMT是我这次使用的程序框架文件,本质是时间片,使用它也是方便后期的维护。(这里不多做拓展,源码在Gitee上,有兴趣的可以了解下,有点RTOS的味道)

3.至此,不管是keil工程里的文件分类还是电脑下对各个源文件的分类管理已经处理好。

选项配置

  1. 在工程里点击魔法棒,修改默认arm编译器,个人喜欢V6编译器,这里默认不修改也行。
    GD32F303调试小记(零)之工程创建与编译

  2. OUTPUT选项里勾选Browse Information并修改名字,如test。这里是生成一系列文件,我们需要其中的.hex文件。为编译下载做好准备。
    GD32F303调试小记(零)之工程创建与编译

  3. C/C++(AC6)里选择Include Path一行右边的···按键,将电脑文件夹里的每个文件路径添加进去,检查是否是C99,确认无误后点击OK
    GD32F303调试小记(零)之工程创建与编译

  4. Debug一栏里选择使用的下载仿真器,我使用的是j-link。选完后点击右边的setting
    GD32F303调试小记(零)之工程创建与编译

  5. 在弹出的Debug一栏中,电脑连接上并检测到J-Link后,①和②处都会显示对应的J-Link数据、固件版本等。写这篇文章时身边并没有J-Link,所以什么都没有。③是选择J-Link下载模式,我选择SW模式,这样下载占用的IO口最少。至于下载速度,我一般选择2MHz,这跟PCB下载线上的阻抗有关,太快容易检测不到J-Link以及下载过程中容易失败。
    GD32F303调试小记(零)之工程创建与编译

  6. 在弹出的Flash Download一栏中,选择Erase Sectors 而不是 Erase Full Chip,因为我们是调试程序,把存放代码所涉及的FLASH片区擦除即可。如果擦除整个芯片,那么你之前存了一些掉电不丢失的数据也会被擦除。③中可以勾选Reset and Run,这样用下载器下完后可以立马运行你的代码。④是用来检查你的芯片实际型号容量大小是否与之对应。没问题后,确定即可。
    GD32F303调试小记(零)之工程创建与编译

时钟配置文件

  1. 启动文件里的Systeminit()函数
;/* reset Handler */
Reset_Handler   PROC
              EXPORT  Reset_Handler                     [WEAK]
              IMPORT  SystemInit
              IMPORT  __main
              LDR     R0, =SystemInit
              BLX     R0
              LDR     R0, =__main
              BX      R0
              ENDP

这里不多作解释,我们只管看R0,R0先被赋予了SystemInit,再然后才__main,真正进入main函数。意味着每次上电程序先跑完systeminit()这个函数才会进入到main函数里去。

  1. Systeminit()函数
void SystemInit (void)
{
  /* FPU settings */
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
#endif
    /* reset the RCU clock configuration to the default reset state */
    /* Set IRC8MEN bit */
    RCU_CTL |= RCU_CTL_IRC8MEN;

    RCU_MODIFY
 
    /* Reset CFG0 and CFG1 registers */
    RCU_CFG0 = 0x00000000U;
    RCU_CFG1 = 0x00000000U;

#if (defined(GD32F30X_HD) || defined(GD32F30X_XD))
    /* reset HXTALEN, CKMEN and PLLEN bits */
    RCU_CTL &= ~(RCU_CTL_PLLEN | RCU_CTL_CKMEN | RCU_CTL_HXTALEN);
    /* disable all interrupts */
    RCU_INT = 0x009f0000U;
#elif defined(GD32F30X_CL)
    /* Reset HXTALEN, CKMEN, PLLEN, PLL1EN and PLL2EN bits */
    RCU_CTL &= ~(RCU_CTL_PLLEN |RCU_CTL_PLL1EN | RCU_CTL_PLL2EN | RCU_CTL_CKMEN | RCU_CTL_HXTALEN);
    /* disable all interrupts */
    RCU_INT = 0x00ff0000U;
#endif
    /* reset HXTALBPS bit */
    RCU_CTL &= ~(RCU_CTL_HXTALBPS);

    /* configure the system clock source, PLL Multiplier, AHB/APBx prescalers and Flash settings */
    system_clock_config();
}

写的很多,但关键就两个。system_clock_config();#if (defined(GD32F30X_HD) || defined(GD32F30X_XD)),前者肯定是对时钟的配置,后者是对芯片容量的定义。

  1. GD32F30X_HD、GD32F30X_XD与GD32F30X_CL
    GD32F303调试小记(零)之工程创建与编译
/* define GD32F30x */
#if !defined (GD32F30X_HD) && !defined (GD32F30X_XD) && !defined (GD32F30X_CL)
  /* #define GD32F30X_HD */
  /* #define GD32F30X_XD */
  /* #define GD32F30X_CL */
#endif /* define GD32F30x */

上述代码在gd32f30x.h中,默认是都没有定义,我们根据实际用的容量大小去判断是否定义其中的一个。

  1. system_clock_config()
/* select a system clock by uncommenting the following line */
/* use IRC8M */
//#define __SYSTEM_CLOCK_IRC8M                    (uint32_t)(__IRC8M) 
//#define __SYSTEM_CLOCK_48M_PLL_IRC8M            (uint32_t)(48000000)
//#define __SYSTEM_CLOCK_72M_PLL_IRC8M            (uint32_t)(72000000)
//#define __SYSTEM_CLOCK_108M_PLL_IRC8M           (uint32_t)(108000000)
//#define __SYSTEM_CLOCK_120M_PLL_IRC8M           (uint32_t)(120000000)

/* use HXTAL(XD series CK_HXTAL = 8M, CL series CK_HXTAL = 25M) */
//#define __SYSTEM_CLOCK_HXTAL                    (uint32_t)(__HXTAL)
//#define __SYSTEM_CLOCK_48M_PLL_HXTAL            (uint32_t)(48000000)
//#define __SYSTEM_CLOCK_72M_PLL_HXTAL            (uint32_t)(72000000)
#define __SYSTEM_CLOCK_108M_PLL_HXTAL           (uint32_t)(108000000)
//#define __SYSTEM_CLOCK_120M_PLL_HXTAL           (uint32_t)(120000000)

static void system_clock_config(void)
{
#ifdef __SYSTEM_CLOCK_IRC8M
    system_clock_8m_irc8m();
#elif defined (__SYSTEM_CLOCK_48M_PLL_IRC8M)
    system_clock_48m_irc8m();
#elif defined (__SYSTEM_CLOCK_72M_PLL_IRC8M)
    system_clock_72m_irc8m();
#elif defined (__SYSTEM_CLOCK_108M_PLL_IRC8M)
    system_clock_108m_irc8m();
#elif defined (__SYSTEM_CLOCK_120M_PLL_IRC8M)
    system_clock_120m_irc8m();

#elif defined (__SYSTEM_CLOCK_HXTAL)
    system_clock_hxtal();
#elif defined (__SYSTEM_CLOCK_48M_PLL_HXTAL)
    system_clock_48m_hxtal();
#elif defined (__SYSTEM_CLOCK_72M_PLL_HXTAL)
    system_clock_72m_hxtal();
#elif defined (__SYSTEM_CLOCK_108M_PLL_HXTAL)
    system_clock_108m_hxtal();
#elif defined (__SYSTEM_CLOCK_120M_PLL_HXTAL)
    system_clock_120m_hxtal();
#endif /* __SYSTEM_CLOCK_IRC8M */
}

system_clock_config()函数与systeminit()函数在同一个.c文件里,这里主要是配置系统时钟源,我选用外部晶振输入+PLL倍频后的108M作为系统时钟。

工程编译

  1. 创建并修改main.h
#ifndef MAIN_H
#define MAIN_H

#define _nop_() __asm("nop");
#define NOP _nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();\
			_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();

/* include all headfiles you created */
#include "TMT.h"
#include "task.h"
#include "peripheral.h"
#include "gpio.h"

#endif /* MAIN_H */
  1. 创建并修改peripheral.c和peripheral.h
#include "gd32f30x.h"
#include "peripheral.h"

void SystemTick_Init(void)
{
   /* setup systick timer for 1000Hz interrupts */
   if (SysTick_Config(SystemCoreClock / 1000U)){
       /* capture error */
       while (1){
       }
   }
		
	/* Set Interrupt Group Priority */
	nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
}

void SystemClock_Reconfig(void)
{
	/* Enable all peripherals clocks you need*/
	rcu_periph_clock_enable(RCU_GPIOA);
	rcu_periph_clock_enable(RCU_GPIOB);
	rcu_periph_clock_enable(RCU_GPIOC);
	rcu_periph_clock_enable(RCU_GPIOD);
}

1ms的滴答时钟以及所以中断的优先级组配置。

#ifndef peripheral_H
#define peripheral_H

#include <stdint.h>

/* SystemTick Init */
void SystemTick_Init(void);
/* Initializes the CPU, AHB and APB busses clocks.Enable all peripherals clocks you need. */
void SystemClock_Reconfig(void);

#endif /* peripheral_H */
  1. 创建并修改gpio.c和gpio.h
#include "gd32f30x.h"
#include "gpio.h"

void GPIO_Init(void)
{
	/* 使用SW下载,不使用JTAG下载,管脚用作其它功能 */
	gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);
	
	/* demo board LED I/O */
 	gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_11);
	gpio_bit_reset(GPIOC,GPIO_PIN_11);
}
#ifndef gpio_H
#define gpio_H

#include "main.h"
#include <stdint.h>

void GPIO_Init(void);

#endif
  1. 创建并修改task.c和task.h
#include "gd32f30x.h"
#include "task.h"

void TASK_IO_REVERSE(void)
{
	static uint32_t countx=0;
	if(countx<65535)	countx++;
	
	if(countx%2==0)
	{
		gpio_bit_reset(GPIOC,GPIO_PIN_11);
	}
	else
	{
		gpio_bit_set(GPIOC,GPIO_PIN_11);
	}
}
#ifndef task_H
#define task_H

#include "main.h"
#include <stdint.h>

void TASK_IO_REVERSE(void);

#endif
  1. 创建并修改gd32f30x_it.c
#include "gd32f30x_it.h"
#include "main.h"

void SysTick_Handler(void)
{
	TMT.Tick();	
}
  1. 创建并修改main.c
#include "gd32f30x.h"
#include "gd32f30x_libopt.h"
#include "main.h"

int main(void)
{	
	SystemTick_Init();
	SystemClock_Reconfig();
	GPIO_Init();
	TMT_Init();
	TMT.Create(TASK_IO_REVERSE,500);
	while(1)
	{
		TMT.Run();
	}
}
  1. 这里大概解释一下,
    SystemTick_Init() 、SystemClock_Reconfig()和GPIO_Init()是对单片机外设的初始化配置,放在工程里的Application/User/Core_init里头。
    由于用到了时间片框架,每一个任务单独写一个具体实现的函数在Task.c里头,放在工程里的Application/User/Board_drv里头。
    TASK_IO_REVERSE()这个函数每500ms执行一次。这里的意图也很简单,就是让PC11这个IO每0.5秒翻转一次。

总结

至此我们,从零开始创建的一个GD32F303的keil工程就已经完成了。后续就可以以此为模板,尽情的编写我们的代码。最终编译和调试的结果就不放图出来了,因为也没啥好看的,这里只是把需要修改的几个关键点放出来,避免工程创建的不成功。最后中秋佳节还祝各位阖家幸福,少掉点头发,哈哈哈。

!!!本文为欢喜6666在CSDN原创发布,复制或转载请注明出处 :)!!!

上一篇:linux starttimer Timerevent函数用法


下一篇:Xilinx约束学习笔记(二)—— 定义时钟