0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

GD32开发实战指南(基础篇) 第4章 GD32启动流程详解(Keil版)

嵌入式大杂烩 来源:嵌入式大杂烩 作者:嵌入式大杂烩 2023-05-10 09:00 次阅读

开发环境:

MDK:Keil 5.30

开发板:GD32F207I-EVAL

MCU:GD32F207IK

对于我们常用的桌面操作系统而言,我们在开发应用时,并不关心系统的初始化,绝大多数应用程序是在操作系统运行后才开始运行的,操作系统已经提供了一个合适的运行环境,然而对于嵌入式设备而言,在设备上电后,所有的一切都需要由开发者来设置,这里处理器是没有堆栈,没有中断,更没有外围设备,这些工作是需要软件来指定的,而且不同的CPU类型、不同大小的内存和不同种类的外设,其初始化工作都是不同的。本文将以GD32F207IK (基于Cortex-M3)为例进行讲解。

在开始正式讲解之前,你需要了解ARM寄存器汇编以及反编译相关的知识,这些可以参考笔者博文。

深入理解ARM寄存器:https://bruceou.blog.csdn.net/article/details/117866186

ARM汇编入门:https://bruceou.blog.csdn.net/article/details/117897496

Keil反编译入门(一):https://bruceou.blog.csdn.net/article/details/118314875

Keil反编译入门(二):https://bruceou.blog.csdn.net/article/details/118400368

下面我们就来具体看一下用户从Flash启动GD32的过程,主要讲解从上电复位到main函数的过程。主要有以下步骤:

1.初始化堆栈指针 SP=_initial_sp,初始化 PC 指针=Reset_Handler

2.初始化中断向量表

3.配置系统时钟

4.调用 C 库函数_main 初始化用户堆栈,然后进入 main 函数。

在开始讲解之前,我们需要了解GD32的启动模式。

1 GD32的启动模式

首先要讲一下GD32的启动模式,因为启动模式决定了向量表的位置,GD32有三种启动模式:

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 启动时,需要在应用程序初始化代码中重新设置向量表的位置。

用户可以通过设置BOOT0和BOOT1的引脚电平状态,来选择复位后的启动模式。如下图所示:

1683641881815mlchzr9x8b

启动模式只决定程序烧录的位置 ,加载完程序之后会有一个重映射(映射到0x00000000地址位置);真正产生复位信号的时候,CPU还是从开始位置执行。

值得注意的是GD32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中。

Bootloader 存放在系统(System)存储内,可以在 MCU 启动后对 Flash 进行再编程。在GD32F20x 系列产品中,Bootloader 通过 USART0 与外界交互。

GD32F20x芯片支持嵌入式引导程序通过多种接口方式来更新Flash。可以有1或2个USART端口和标准USB端口用于GD32F205xx和GD32F207xx互联型产品。如下表所示。

产品线 产品 支持串行外设
互联型 GD32F205xx USART0(PA9 PA10)USART1(PD5 PD6)USB(PA9 PA10 PA11 PA12)
GD32F207xx USART0(PA9 PA10)USART1(PD5 PD6)USB(PA9 PA10 PA11 PA12)

2 GD32的启动文件分析

因为启动过程主要是由汇编完成的,因此GD32的启动的大部分内容都是在启动文件里。笔者的启动文件是startup_gd32f20x_cl.s。

2.1堆栈定义

1. Stack栈

栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM 的大小。当程序较大时,需要修改栈的大小,不然可能会出现的HardFault的错误。

1683641882409orde6lxrqu

第43行:表示开辟栈的大小为 0X00000400(1KB),EQU是伪指令,相当于C 中的 define。

第45行:开辟一段可读可写数据空间,ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。ARER 后面的关键字表示这个段的属性。段名为STACK,可以任意命名;NOINIT 表示不初始化;READWRITE 表示可读可写,ALIGN=3,表示按照 8 字节对齐。

第46行:SPACE 用于分配大小等于 Stack_Size连续内存空间,单位为字节。

第47行: __initial_sp表示栈顶地址。栈是由高向低生长的。

2.Heap堆

堆主要用来动态内存的分配,像 malloc()函数申请的内存就在堆中。

1683641882719oqfmaee7b6

开辟堆的大小为 0X00000200(512 字节),名字为 HEAP,NOINIT 即不初始化,可读可写,8字节对齐。__heap_base 表示对的起始地址,__heap_limit 表示堆的结束地址。

2.2 向量表

向量表是一个WORD( 32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0)处必须包含一张向量表,用于初始时的异常分配。

值得注意的是这里有个另类:0号类型并不是什么入口地址,而是给出了复位后 MSP 的初值,后面会具体讲解。

1683641883090tmtw4q1vno

……

1683641883526mqpa7bs3u8

第66行:定义一块代码段,段名字是RESET,READONLY 表示只读。

第67-69行:使用EXPORT将3个标识符申明为可被外部引用,声明 __Vectors、__Vectors_End 和__Vectors_Size 具有全局属性。这几个变量在Keil分散加载时会用到。

第71行:__Vectors 表示向量表起始地址,DCD 表示分配 1 个 4 字节的空间。每行 DCD 都会生成一个 4 字节的二进制代码,中断向量表 存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU 的中断系统会将相应的入口地址赋值给 PC 程序计数器,之后就开始执行中断服务程序。在60行之后,依次定义了中断服务程序的入口地址。

第179行:__Vectors_End 为向量表结束地址。

第181行:__Vectors_Size则是向量表的大小,向量表的大小是通过__Vectors 和__Vectors_End 相减得到的。

上述向量表可以在《GD32F20x_User_Manual_EN_Rev2.4》中找到的,笔者这里只截取了部分。

1683641883886t45biqjfm6

笔者只截取了部分。

2.3 复位程序

复位程序是系统上电后执行的第一个程序,复位程序也是中断程序,只是这个程序比较特殊,因此单独提出来讲解。

1683641884378vy8wfxquf8

第186行:定义了一个服务程序,PROC表示程序的开始。

第187行:使用EXPORT将Reset_Handler申明为可被外部引用,后面WEAK表示弱定义,如果外部文件定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位程序可以由用户在其他文件重新实现。

第188-189行:表示该标号来自外部文件,SystemInit()是一个库函数,在system_gd32f10x.c中定义的,__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,这个是由编译器完成的,该函数最终会调用我们自己写的main函数,从而进入C世界中。

第190行:这是一条汇编指令,表示从存储器中加载SystemInit到一个寄存器R0的地址中。R0~R3 寄存器通常用于函数入参出参或子程序调用。

第191行:汇编指令,表示跳转到寄存器R0的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR。

第192行:和190行是一个意思,表示从存储器中加载__main到一个寄存器R0的地址中。

第193行:和191稍微不同,这里跳转到至指定寄存器的地址后,不会返回。

第194行:和PROC是对应的,表示程序的结束。

值得注意的是,这里的__main和C语言中的main()不是一样东西,__main是C lib中的函数,也就是在Keil中自带的;而main()函数是C的入口,main()会被__main调用。

2.4 中断服务程序

我们平时要使用哪个中断,就需要编写相应的中断服务程序,只是启动文件把这些函数留出来了,但是内容都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置罢了。

1683641884727o0ylahugyn

这部分没啥好说的,和服务程序类似的,只需要注意‘B .’语句,B表示跳转,这里跳转到一个‘.’,即表示无线循环。

2.5 堆栈初始化

堆栈初始化是由一个IF条件来实现的,MICROLIB的定义与否决定了堆栈的初始化方式。

这个定义是在Options->Target中设置的。

1683641885080sccy5y5lo8

如果没有定义__MICROLIB , 则会使用双段存储器模式,且声明了__user_initial_stackheap 具有全局属性,这需要开发者自己来初始化堆栈。

16836418854676v6ytnwdkw

这部分也没啥讲的,需要注意的是,ALIGN表示对指令或者数据存放的地址进行对齐,缺省表示4字节对齐。

2.6 其他

16836418858143n0lz7qxcr

第62行:PRESERVE8 用于指定当前文件的堆栈按照 8 字节对齐。

第63行:THUMB 表示后面指令兼容 THUMB 指令。现在 Cortex-M 系列的都使用 THUMB-2 指令集,THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超集。

3 GD32的启动流程实例分析

有了前面的分析,接下来就来具体看看GD32启动流程的具体内容。

3.1初始化SP、PC、向量表

当系统复位后,处理器首先读取向量表中的前两个字(8 个字节),第一个字存入 MSP,第二个字为复位向量,也就是程序执行的起始地址。

C:\\Users\\BruceOu\\Desktop\\151651311436264.jpg

这里通过J-Flash打开hex文件。

16836418864610367kqtder

硬件这时自动从0x0800 0000位置处读取数据赋给栈指针SP,然后自动从0x0800 0004位置处读取数据赋给PC,完成了复位操作,SP= 0x0200 2008,PC = 0x0800 01BD。

初始化SP、PC紧接着就初始化向量表,如果感觉看HEX文件抽象,我们看看反汇编文件吧。

16836418868297f6cfzsiqn

是不是更容易些,是不是和《GD32F20x_User_Manual_EN_Rev2.4》中的向量表对应起来了。其实看反汇编文件更好理解GD32的启动流程,只是有些抽象。

生成反汇编的方法如下。

在KEIL的User选项中,如下图添加这两项:

fromelf --bin --output=GD32F207I_EVAL.bin ../Output/GD32F207I_EVAL.axf

fromelf --text -a -c --output=GD32F207I_EVAL.dis ../Output/GD32F207I_EVAL.axf

然后重新编译,即可得到二进制文件GD32F207I_EVAL.bin(以后会分析)、反汇编文件GD32F207I_EVAL.dis。

如下图所示:

1683641887165zms87otk87

3.2 设置系统时钟

细心的朋友可能发现,PC=0x080001BD,这里表明MCU运行的是THUMB模式,最低位为1表示为THUMB状态。然后在反汇编文件中却是这样的:

1683641887552cchwcd1pxd

当然也可通过硬件调试来确认上面的分析:

16836418879176yj9y2lm3i

接下来就会进入SystemInit函数中。

1683641888886y6tbag6gzh

SystemInit函数内容如下:

/*!
    \\\\\\\\brief      setup the micro-controller system, initialize the system
    \\\\\\\\param[in]  none
    \\\\\\\\param[out] none
    \\\\\\\\retval     none
*/
void SystemInit(void)
{
    /* reset the RCC clock configuration to the default reset state */
    /* enable IRC8M */
    RCU_CTL |= RCU_CTL_IRC8MEN;

    RCU_CFG0 &= ~RCU_CFG0_SCS;
    /* reset HXTALEN, CKMEN, PLLEN bits */
    RCU_CTL &= ~(RCU_CTL_HXTALEN | RCU_CTL_CKMEN | RCU_CTL_PLLEN);

    /* reset SCS, AHBPSC, APB1PSC, APB2PSC, ADCPSC, CKOUT0SEL bits */
    RCU_CFG0 &= ~(RCU_CFG0_SCS | RCU_CFG0_AHBPSC | RCU_CFG0_APB1PSC | RCU_CFG0_APB2PSC |
                  RCU_CFG0_ADCPSC | RCU_CFG0_ADCPSC_2 | RCU_CFG0_CKOUT0SEL);

    /* reset HXTALEN, CKMEN, PLLEN bits */
    RCU_CTL &= ~(RCU_CTL_HXTALEN | RCU_CTL_CKMEN | RCU_CTL_PLLEN);

    /* Reset HXTALBPS bit */
    RCU_CTL &= ~(RCU_CTL_HXTALBPS);

    /* reset PLLSEL, PREDV0_LSB, PLLMF, USBFSPSC bits */
    RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PREDV0_LSB | RCU_CFG0_PLLMF |
                  RCU_CFG0_USBFSPSC | RCU_CFG0_PLLMF_4);

    /* reset PLL1EN and PLL2EN bits */
    RCU_CTL &= ~(RCU_CTL_PLL1EN | RCU_CTL_PLL2EN);

    /* reset CFG1 register */
    RCU_CFG1 = 0x00000000U;

    /* reset INT register */
    RCU_INT = 0x00FF0000U;

    /* reset CFG2 register */
    RCU_CFG2 = 0x00000000U;

    /* reset PLLTCTL register */
    RCU_PLLTCTL &= (~RCU_PLLTCTL_PLLTEN);

    /* reset PLLTINT register */
    RCU_PLLTINT = 0x00400000U;

    /* reset PLLTCFG register */
    RCU_PLLTCFG = 0x20003010U;

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

前面部分是配置时钟的,具体参考手册吧。

/*!
    \\\\\\\\brief      configure the system clock to 120M by PLL which selects HXTAL(8M) as its clock source
    \\\\\\\\param[in]  none
    \\\\\\\\param[out] none
    \\\\\\\\retval     none
*/
static void system_clock_120m_hxtal(void)
{
    uint32_t timeout = 0U;
    uint32_t stab_flag = 0U;

    /* enable HXTAL */
    RCU_CTL |= RCU_CTL_HXTALEN;

    /* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
    do {
        timeout++;
        stab_flag = (RCU_CTL & RCU_CTL_HXTALSTB);
    } while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));

    /* if fail */
    if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)) {
        while(1) {
        }
    }

    /* HXTAL is stable */
    /* AHB = SYSCLK */
    RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
    /* APB2 = AHB/1 */
    RCU_CFG0 |= RCU_APB2_CKAHB_DIV1;
    /* APB1 = AHB/2 */
    RCU_CFG0 |= RCU_APB1_CKAHB_DIV2;

    /* CK_PLL = (CK_PREDIV0) * 10 = 120 MHz */
    RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PREDV0_LSB | RCU_CFG0_PLLSEL);
    RCU_CFG0 |= (RCU_PLLSRC_HXTAL | RCU_PLL_MUL10);

    /* CK_PREDIV0 = (CK_HXTAL) / 5 * 12 /5 = 12 MHz */
    RCU_CFG1 &= ~(RCU_CFG1_PREDV0SEL | RCU_CFG1_PLL1MF | RCU_CFG1_PREDV1 | RCU_CFG1_PREDV0);
    RCU_CFG1 |= (RCU_PREDV0SRC_CKPLL1 | RCU_PLL1_MUL12 | RCU_PREDV1_DIV5 | RCU_PREDV0_DIV5);

    /* enable PLL1 */
    RCU_CTL |= RCU_CTL_PLL1EN;
    /* wait till PLL1 is ready */
    while((RCU_CTL & RCU_CTL_PLL1STB) == 0U) {
    }

    /* enable PLL */
    RCU_CTL |= RCU_CTL_PLLEN;

    /* wait until PLL is stable */
    while(0U == (RCU_CTL & RCU_CTL_PLLSTB)) {
    }

    /* select PLL as system clock */
    RCU_CFG0 &= ~RCU_CFG0_SCS;
    RCU_CFG0 |= RCU_CKSYSSRC_PLL;

    /* wait until PLL is selected as system clock */
    while(0U == (RCU_CFG0 & RCU_SCSS_PLL)) {
    }
}

3.3 初始化堆栈并进入main

执行指令LDR R0, =__main,然后就跳转到__main程序段运行,当然这里指标准库的__main函数。

1683641889235szq3xsjq27

这中间初始化了栈区。

1683641889700udrb5u9tx7

这段代码是个循环(BCC 0x080001e6),实际运行时候循环了两次。第一次运行的时候,读取“加载数据段的函数”的地址并跳转到该函数处运行(注意加载已初始化数据段和未初始化数据段用的是同一个函数);第二次运行的时候,读取“初始化栈的函数”的地址并跳转到该函数处运行。

最后就进入C文件的main函数中,至此,启动过程到此结束。

最后,总结下GD32 从flash的启动流程。

MCU上电后从0x0800 0000处读取栈顶地址并保存,然后从0x0800 0004读取中断向量表的起始地址,这就是复位程序的入口地址,接着跳转到复位程序入口处,初始向量表,然后设置时钟,设置堆栈,最后跳转到C空间的main函数,即进入用户程序。

C:\\Users\\BruceOu\\Desktop\\8888.png

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • mcu
    mcu
    +关注

    关注

    146

    文章

    17123

    浏览量

    350972
  • 开发板
    +关注

    关注

    25

    文章

    5032

    浏览量

    97371
  • keil
    +关注

    关注

    68

    文章

    1212

    浏览量

    166838
  • Cortex-M
    +关注

    关注

    2

    文章

    229

    浏览量

    29752
  • GD32
    +关注

    关注

    7

    文章

    403

    浏览量

    24326
收藏 人收藏

    评论

    相关推荐

    GD32开发实战指南(基础) 14 内部温度传感器

    GD32 有一个内部的温度传感器,可以用来测量 CPU 及周围的温度(TA)。该温度传感器在内部和 ADCx_IN16 输入通道相连接,此通道把传感器输出的电压转换成数字值。温度传感器模拟输入
    的头像 发表于 05-17 08:58 5331次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>14<b class='flag-5'>章</b> 内部温度传感器

    GD32开发实战指南(基础) 16 RTC

    开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 RTC工作原理 1.1 RT
    的头像 发表于 05-18 22:14 7151次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>16<b class='flag-5'>章</b> RTC

    GD32和STM32有哪些不同的地方

    一、前言GD32是国内开发的一款单片机,据说开发的人员是来自ST公司的,GD32也是以STM32作为模板做出来的。所以GD32和STM32有
    发表于 08-09 07:03

    什么是GD32

    一、前言什么GD32GD32是国内开发的一款单片机,据说开发的人员是来自ST公司的,GD32也是以STM32作为模板做出来的。所以
    发表于 08-12 07:46

    GD32芯片包添加步骤有哪些

    **GD32芯片包添加步骤**GD32开发环境可以有多个选择,常见的开发编译环境有:Keil4keil
    发表于 11-22 08:30

    GD32 MCU原理及固件库开发指南》+读后感

    2介绍GD32 MCU快速入门与开发平台搭建的方法,包括对软硬件开发平台、调试工具、GD32
    发表于 06-06 21:52

    GD32单片机开发环境搭建(Keil5安装)

    GD32单片机开发环境搭建(Keil5安装)
    发表于 11-13 14:36 40次下载
    <b class='flag-5'>GD32</b>单片机<b class='flag-5'>开发</b>环境搭建(<b class='flag-5'>Keil</b>5安装)

    GD32F系列单片机开发总结(二):GD32芯片包添加步骤

    **GD32芯片包添加步骤**GD32开发环境可以有多个选择,常见的开发编译环境有:Keil4keil
    发表于 11-13 17:21 37次下载
    <b class='flag-5'>GD</b>32F系列单片机<b class='flag-5'>开发</b>总结(二):<b class='flag-5'>GD32</b>芯片包添加步骤

    GD32和STM32的区别

    一、前言GD32是国内开发的一款单片机,据说开发的人员是来自ST公司的,GD32也是以STM32作为模板做出来的。所以GD32和STM32有
    发表于 11-18 20:51 47次下载
    <b class='flag-5'>GD32</b>和STM32的区别

    GD32移植到STM32开发平台

    GD32移植到STM32开发平台
    发表于 12-02 14:51 28次下载
    <b class='flag-5'>GD32</b>移植到STM32<b class='flag-5'>开发</b>平台

    GD32开发实战指南(基础) 19 程序加密

    GD32通过读取芯片唯一ID号来实现程序的保护,防止被抄袭。96位的产品唯一身份标识所提供的参考号码对任意一个GD32微控制器
    的头像 发表于 05-20 09:10 4138次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>19<b class='flag-5'>章</b> 程序加密

    GD32和AT32哪个好?

    GD32和AT32哪个好? GD32和AT32是两种不同的微控制器型号,它们都是国内开发的芯片。GD32系列由国内芯片制造商GigaDevice Semiconductor
    的头像 发表于 08-16 11:32 2439次阅读

    GD32与STM32兼容吗?

    的STMicroelectronics公司设计和生产的。 虽然GD32与STM32都是基于ARM Cortex-M内核的微控制器,但是它们并不完全兼容。由于两个公司的设计和生产流程不同,GD32系列和STM32系列
    的头像 发表于 08-16 11:32 2911次阅读

    GD32 MCU 入门教程】一、GD32 MCU 开发环境搭建(1)使用Keil开发GD32

    GD32系列为通用型MCU,所以开发环境也可以使用通用型的IDE,目前使用较多的是KEIL、IAR、 GCC和Embedded Builder,客户可以根据个人喜好来选择相应的开发环境
    的头像 发表于 08-08 15:01 1159次阅读
    【<b class='flag-5'>GD32</b> MCU 入门教程】一、<b class='flag-5'>GD32</b> MCU <b class='flag-5'>开发</b>环境搭建(1)使用<b class='flag-5'>Keil</b><b class='flag-5'>开发</b><b class='flag-5'>GD32</b>

    GD32 MCU 入门教程】一、GD32 MCU 开发环境搭建(2)使用 IAR 开发 GD32

    GD32系列为通用型MCU,所以开发环境也可以使用通用型的IDE,目前使用较多的是KEIL、IAR、 GCC和Embedded Builder,客户可以根据个人喜好来选择相应的开发环境
    的头像 发表于 08-08 15:40 679次阅读
    【<b class='flag-5'>GD32</b> MCU 入门教程】一、<b class='flag-5'>GD32</b> MCU <b class='flag-5'>开发</b>环境搭建(2)使用 IAR <b class='flag-5'>开发</b> <b class='flag-5'>GD32</b>