发新帖本帖赏金 100.00元(功能说明)我要提问
返回列表
打印
[MM32生态]

在 VS-Code 中还能怎么玩单片机?用 PlatformIO 试试

[复制链接]
3811|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 yang377156216 于 2022-6-29 09:10 编辑
#申请原创#  @21小跑堂


整体概览
物联网 IOT 的概念已经炒了很多年了,抛开产品及其本身用到的技术而言,在单片机开发领域它会涉及到开发环境、硬件平台、编程框架以及操作系统等等几大方面。国内各大厂家争先也出了自己的物联网操作系统,比如:华为鸿蒙(liteos)、阿里 AliOS Things、腾讯 TinyOS 、RT-Thread OS 等等,至于框架以及操作系统相结合的代表作有 ZLG 的 AMetal + AWorks ,还有 ARM 官方的 mbed os 框架平台现在也是越来越流行了。提到开源硬件平台,基本都能提到 arduino、NodeMCU 、ESP32 和树莓派这几个,基于这些平台有太多极客、黑客们各种好玩的东西了。说到这里,想必大家就会自然发问:那玩转这些东西是不是得要有一个统一的集成开发环境呢?好在 PlatformIO 这个工具软件在合适的时候诞生了,它几乎包含了以上提到的所有内容,就是这么牛的存在。那今天就一起来揭开 PlatformIO 的面纱,在这个集成环境中使用 MM32F3273G9P MB-039 开发板来做开发调试。以下几部分为本次分享的要点:
  • PlatformIO 简介
  • 将 MM32F3273G9P 集成到 VS-Code PlatformIO 中
  • 后续待完成的集成要点
  • 参考链接
  • 附件内容


一、PlatformIO 简介
PlatformIO https://docs.platformio.org/en/latest/what-is-platformio.html)是一个用于物联网开发的开源生态系统。它提供跨平台的开发环境和统一的调试器,是一个跨平台的代码构建工具和库管理工具,支持远程单元测试和固件更新等等功能,还支持像 Arduino 和 MBED 这样的开源框架及硬件平台,可以抛开 Arduino IDE 和 ARM mbed ide 了它是独立于电脑平台运行的,只依赖于 python,所以在 macOS、linux 和 windows 上都能完美适配,也就是说 PlatformIO 的工程从一个电脑很容易迁移到另一个电脑,只需要拷贝再使用 PlatformIO 就能完美打开,不管团队中的成员使用什么操作系统 PlatformIO 可以让工程共享变得异常简单。 此外, PlatformIO 不仅可以在笔记本和台式机上运行,同样可以运行在没有显示桌面的服务器。使用它作开发时工作流程可以简化,首先用户在 “platformio.ini” (工程配置文件) 中指定开发平台(开发板),接着根据配置文件中的开发板(一个工程可以配置多个开发板),PlatformIO 会自动下载并安装对应的交叉编译链工具以及调试工具.,最后用户编写代码,PlatformIO 来保证所有指定开发板的编译、调试和上传工作即可完成所有开发工作。
熟悉 VS-Code 的伙伴们更加习惯以 PlatformIO 插件的形式做集成,VS-Code + PlatformIO 的环境有以下一些特性:
  • PIO 统一的调试器,可以零配置的对支持硬件调试的的嵌入式开发板进行调试工作,调试器支持很多的架构和开发平台
  • 跨平台的代码构建系统对系统软件没有额外的依赖: 600+ 嵌入式开发板, 30+ 开发平台, 15+ 框架
  • C/C++ 智能代码补全,语法检查,快速重构以及代码跳转满足快速专业的开发需求
  • VSCode 提供多工程和文件管理的支持和统一而流畅的使用体验,并且支持多种色彩主题,总有您喜欢和适合您的
  • 内建的终端支持 PlatformIO Core 命令行工具,并且支持强大的串口调试器
  • 除了支持大约 600+ 个开发板和市面上流行的支持跨平台的 30 个硬件平台,同时,PlatformIO 还提供大量的开发库,目前超过了 6000 个,为了方便新手入门开发,他们也同样提供代码例程
  • 可以通过命令行形态存在,也可以嵌入到其它主流的编辑环境中
  • 获得过 2015/16 IOT 年度最佳开发软件和工具奖  提名




那是不是就可以愉快地在灵动微 MCU 上耍起来了呢?可能没那么简单。 下面讲讲适配过程。


二、将 MM32F3273G9P 集成到 VS-Code PlatformIO 中
在开发板和平台页未找到关于灵动微的任何信息,顺便再看了一下,国产 MCU 厂家中也只有 GD32 的是已经适配好了的,不愧为国产 32位 MCU 中的 NO.1 ,另外也看到很多 STM32 的板子。在熟悉一个新鲜东西的时候,往往是需要从易到难的这么一个适应过程,不然就容易从入门到放弃了,所以我选择先搭建好 STM32F103ZET6 这颗芯片的闪灯工程,熟悉好了整个过程后再去适配灵动微的。下面为详细步骤:
1. 在 VS-Code 插件中搜索 PlatformIO ,下载安装,有时候需要添加到可信任工作区中并且重启 VS-Code 。完成安装后,可以先熟悉一遍软件中每个按键/Tab页的含义,另外需要注意,前提是需要确保安装好了 Python 环境。
2. 在 PIO 的主页点击 New Project 新建一个工程,输入自己的工程名称并且选择好对应的开发板以及开发框架,这里选择 genericSTM32F103ZE 开发板和 CMSIS 框架,然后 Finish ,第一次加载需要下载配套的工具包(编译器、tool、CMSIS 框架包等等),等待全部下载好即可出现空白工程目录,我的依赖包下载路径自动选择为 “C:\Users\yang3\.platformio\packages”,新建的工程路径默认选择为了“C:\Users\yang3\Documents\PlatformIO\Projects

3. 将原子的模板闪灯例程中的一些源文件拷贝到新建的空白工程下的 src 文件夹中,主要有 CORE、HARDWARE、STM32F10x_FWLib、SYSTEM 、main.c、
stm32f10x.h 、stm32f10x_conf.h 、stm32f10x_it.c 、stm32f10x_it.h 、system_stm32f10x.c 、system_stm32f10x.h 这些文件和文件夹,完成复制后整个工程目录结构如下:

4. 改写 PIO 的工程配置文件 platformio.ini ,该文件位于根目录下,改写的代码如下:
; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:genericSTM32F103ZE]
platform = ststm32
board = genericSTM32F103ZE  ;实际为 MM32F3273_MB039
framework = cmsis

upload_protocol = jlink     ; 代码上传工具,
debug_tool  = jlink         ; debug工具
build_flags =               ; Build options
    -Isrc
    -Isrc/CORE
    -Isrc/HARDWARE/LED
    -Isrc/MM32F327x_FWLib/inc
    -Isrc/SYSTEM/DELAY
    -Isrc/SYSTEM/SYS
    -Isrc/SYSTEM/UART

    -D STM32F10X_HD         ; 定义全局宏
    -D USE_STDPERIPH_DRIVER
类似于 keil ,上面代码配置定义好了平台、板子、框架软件包、调试下载工具类型、用户头文件路径以及其它一些全局宏定义等等。
5. 需要知道,PIO 的集成编译器是依赖于 toolchain-gccarmnoneeabi 的,所以源码中的一些不适合 gcc 编译器的地方都得改正过来,最后编译即可,点击编译可以是通过下方的“√”,也可通过 PIO PROJECT TASKS 中的 Build Task 按键。

到这里已经很轻松地完成了 STM32F103 的示例工程导入,通过这一系列的操作,也已经熟悉了 PIO 的开发流程,那接下来就是要做替换了,由于使用的是 CMSIS 框架,与平台相关的外设驱动包改动不涉及框架支持包的重写,所以会稍微容易实现些。下面为替换步骤:
1. 通过查看 ststm32 平台包,熟悉整个 PIO 平台支持包的内容含义,明确要改写哪些文件

2. 由于开发板选择的是 genericSTM32F103ZE ,对应的文件为 genericSTM32F103ZE.json ,其中主要改写调试相关的选择为:
"debug": {
    "jlink_device": "MM32F3273G9P",
    "openocd_target": "stm32f1x",
    "svd_path": "MM32F327x.svd"
  },
特别注意,core 、 extra_flags 、mcu 、variant 等几项选择不能改写,否则会影响其它支持包的适配,关于 name 和 vendor 这2项是可以修改的,这个会直接体现在 PIO 主页新建工程时候的 board 选项显示中。
3. 在 misc - svd 中需要添加 MM32F327x.svd ,该文件可以通过灵动微官方的 keil pack 中获取到,该文件描述了芯片的寄存器,在后面调试时可以对照参阅,如果不替换那么就会显示错误的寄存器数据。
4. 此外,还需要对依赖的包做内容修改替换,这里只涉及到“packages\framework-cmsis-stm32f1”这个包,需要将 Include 文件夹下与芯片相关的头文件全部替换掉,以及 Source - Templates 下的 system_stm32f1xx.c 需要更改内容为 system_mm32f3270.c 中的,名称不改,Source - Templates - gcc 中的 startup_stm32f103xe.s 需要更改为 startup_mm32f3270_gcc.s 中的内容,名称不改Source - Templates - gcc - linker 中的 STM32F103XE_FLASH.ld 同样按照 mm32f3273g9p 去做修改。

////////////////////////////////////////////////////////////////////////////////
/// [url=home.php?mod=space&uid=288409]@file[/url]     SYSTEM_MM32.C
/// [url=home.php?mod=space&uid=187600]@author[/url]   AE TEAM
/// [url=home.php?mod=space&uid=247401]@brief[/url]    THIS FILE PROVIDES ALL THE SYSTEM FUNCTIONS.
////////////////////////////////////////////////////////////////////////////////
/// @attention
///
/// THE EXISTING FIRMWARE IS ONLY FOR REFERENCE, WHICH IS DESIGNED TO PROVIDE
/// CUSTOMERS WITH CODING INFORMATION ABOUT THEIR PRODUCTS SO THEY CAN SAVE
/// TIME. THEREFORE, MINDMOTION SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT OR
/// CONSEQUENTIAL DAMAGES ABOUT ANY CLAIMS ARISING OUT OF THE CONTENT OF SUCH
/// HARDWARE AND/OR THE USE OF THE CODING INFORMATION CONTAINED HEREIN IN
/// CONNECTION WITH PRODUCTS MADE BY CUSTOMERS.
///
/// <H2><CENTER>© COPYRIGHT MINDMOTION </CENTER></H2>
////////////////////////////////////////////////////////////////////////////////

// Define to prevent recursive inclusion
#define _SYSTEM_MM32_C_

// Files includes

/// @addtogroup CMSIS
/// @{

#include "mm32_device.h"


/// @}



/// @}


/// Uncomment the line corresponding to the desired System clock (SYSCLK)
/// frequency (after reset the HSI is used as SYSCLK source)
///
/// IMPORTANT NOTE:
/// ==============
/// 1. After each device reset the HSI is used as System clock source.
///
/// 2. Please make sure that the selected System clock doesn't exceed y**ice's
/// maximum frequency.
///
/// 3. If none of the define below is enabled, the HSI is used as System clock
/// source.
///
/// 4. The System clock configuration functions provided based external crystal or HSE
/// An external 8MHz crystal is used to drive the System clock.
/// If you are using different crystal you have to modify HSE_VALUE in reg_common.h.


// ### HSE_VALUE is defined in reg_common.h ###
#ifndef HSE_VALUE
#error "not define HSE_VALUE"
#endif

//#define SYSCLK_FREQ_XXMHz                96000000                               //select [120000000,96000000,72000000,48000000,24000000]
//#define SYSCLK_FREQ_HSE                 HSE_VALUE                               //use HSE_VALUE as system frequence
#define SYSCLK_HSI_XXMHz                 120000000                               //select [120000000,96000000,72000000,48000000,24000000]

//below message need to confirm Frequence  at first, then comment these warning
//#define DISABLE_FREQ_MACRO_CHECK
#ifndef DISABLE_FREQ_MACRO_CHECK
#if ((defined(SYSCLK_FREQ_HSE) || defined(SYSCLK_FREQ_XXMHz))  && (defined(SYSCLK_HSI_XXMHz) || defined(SYSCLK_FREQ_XXMHz)) && (defined(SYSCLK_FREQ_HSE) || defined(SYSCLK_HSI_XXMHz)))

#error "define more than one Freq config"

#elif (defined(SYSCLK_FREQ_HSE) || defined(SYSCLK_FREQ_XXMHz))

#if (defined(HSE_VALUE) && (HSE_VALUE == 8000000))
#warning "current HSE = 8000000, if on board HSE freq is not equal to 8000000Hz, Please redefine HSE_VALUE value in reg_common.h #define HSE_VALUE"
#undef HSE_VALUE
#define HSE_VALUE   8000000
#else
#warning "on board HSE is not equal to 8000000Hz, Please double check"
#endif

#elif defined(SYSCLK_HSI_XXMHz)
#warning "use PLL mode and PLL Source is HSI, Please double check"
#else
#warning "current HSI = 8000000Hz as SYSCLK"

#endif
#endif




/// Uncomment the following line if you need to relocate your vector Table in
/// Internal SRAM.
///#define VECT_TAB_SRAM
#define VECT_TAB_OFFSET  0x0
/// Vector Table base offset field.
/// This value must be a multiple of 0x200.


/// @}




///////////////////////////////////////////////////////////////
///Clock Definitions
///////////////////////////////////////////////////////////////
#if defined SYSCLK_FREQ_HSE
u32 SystemCoreClock         = SYSCLK_FREQ_HSE;
#elif defined SYSCLK_FREQ_XXMHz
u32 SystemCoreClock         = SYSCLK_FREQ_XXMHz;
#elif defined SYSCLK_HSI_XXMHz
u32 SystemCoreClock         = SYSCLK_HSI_XXMHz;
#else //HSI Selected as System Clock source
u32 SystemCoreClock         = HSI_VALUE;
#endif



/// @}


static void SetSysClock(void);
static void SetSysClockToAnyXX(void);



/// @}



/// [url=home.php?mod=space&uid=247401]@brief[/url]  Setup the microcontroller system
///         Initialize the Embedded Flash Interface, the PLL and update the
///         SystemCoreClock variable.
/// [url=home.php?mod=space&uid=536309]@NOTE[/url]   This function should be used only after reset.
/// @param  None
/// @retval None

void SystemInit (void)
{
    //Reset the RCC clock configuration to the default reset state(for debug purpose)
    //Set HSION bit
    RCC->CR |= (u32)0x00000001;

    //Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits
    RCC->CFGR &= (u32)0xF8FFC00C;

    //Reset HSEON, CSSON and PLLON bits
    RCC->CR &= (u32)0xFEF6FFFF;

    //Reset HSEBYP bit
    RCC->CR &= (u32)0xFFFBFFFF;

    //Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits
    RCC->CFGR &= (u32)0xFF3CFFFF;
    RCC->CR &= (u32)0x008FFFFF;

    //Disable all interrupts and clear pending bits
    RCC->CIR = 0x009F0000;
    //Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers
    //Configure the Flash Latency cycles and enable prefetch buffer
    SetSysClock();
}


/// @brief  use to return the pllm&plln.
/// @param  pllclkSourceFrq : PLL source clock frquency;
///         pllclkFrq : Target PLL clock frquency;
///         plln : PLL factor PLLN
///         pllm : PLL factor PLLM
/// @retval amount of error
u32 AutoCalPllFactor(u32 pllclkSourceFrq, u32 pllclkFrq, u8* plln, u8* pllm)
{
    u32 n, m;
    u32 tempFrq;
    u32 minDiff = pllclkFrq;
    u8  flag = 0;
    for(m = 0; m < 4 ; m++) {
        for(n = 0; n < 64 ; n++) {
            tempFrq =  pllclkSourceFrq * (n + 1) / (m + 1);
            tempFrq = (tempFrq >  pllclkFrq) ? (tempFrq - pllclkFrq) : (pllclkFrq - tempFrq) ;

            if(minDiff > tempFrq) {
                minDiff =  tempFrq;
                *plln = n;
                *pllm = m;
            }
            if(minDiff == 0) {
                flag = 1;
                break;
            }
        }
        if(flag != 0) {
            break;
        }
    }
    return  minDiff;
}

static void DELAY_xUs(u32 count)
{
    u32 temp;
    SysTick->CTRL = 0x0;                                                        //disable systick function
    SysTick->LOAD = count * 8;                                                  //time count for 1us with HSI as SYSCLK
    SysTick->VAL = 0x00;                                                        //clear counter
    SysTick->CTRL = 0x5;                                                        //start discrease with Polling
    do {
        temp = SysTick->CTRL;
    } while((temp & 0x01) && !(temp & (1 << 16)));                              //wait time count done
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;                                  //Close Counter
    SysTick->VAL = 0X00;                                                        //clear counter
}

/// @brief  Configures the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers.
/// @param  None
/// @retval None

static void SetSysClock(void)
{
    CACHE->CCR &= ~(0x3 << 3);
    CACHE->CCR |= 1;
    while((CACHE->SR & 0x3) != 2);
    SetSysClockToAnyXX();
}

u32 GetCurrentSysClockFreq(void)
{
    u32 result;
    u32 clock, mul, div;
    switch (RCC->CFGR & RCC_CFGR_SWS) {
        case RCC_CFGR_SWS_LSI:
            result = LSI_VALUE;
            break;

        case RCC_CFGR_SWS_HSE:
            result = HSE_VALUE;
            break;

        case RCC_CFGR_SWS_PLL:
            clock = READ_BIT(RCC->PLLCFGR, RCC_PLLCFGR_PLLSRC) ? (READ_BIT(RCC->PLLCFGR, RCC_PLLCFGR_PLLXTPRE) ? (HSE_VALUE >> 1) : HSE_VALUE)
                    : HSI_VALUE_PLL_ON;
            mul = ((RCC->PLLCFGR & (u32)RCC_PLLCFGR_PLL_DN) >> RCC_PLLCFGR_PLL_DN_Pos) + 1;
            div = ((RCC->PLLCFGR & RCC_PLLCFGR_PLL_DP) >> RCC_PLLCFGR_PLL_DP_Pos) + 1;

            result = clock * mul / div;
            break;
        default:
            result =  HSI_VALUE;
            break;
    }
    return result;
}


/// @brief  Sets System clock frequency to XXMHz and configure HCLK, PCLK2
///         and PCLK1 prescalers.
/// [url=home.php?mod=space&uid=536309]@NOTE[/url]   This function should be used only after reset.
/// @param  None
/// @retval None
static void SetSysClockToAnyXX(void)
{
    __IO u32 temp;
#if defined(SYSCLK_FREQ_HSE) || defined(SYSCLK_FREQ_XXMHz)
    __IO u32 StartUpCounter = 0, HSEStatus = 0;
#endif
#if defined(SYSCLK_FREQ_XXMHz) || defined(SYSCLK_HSI_XXMHz)
    __IO u32 tn, tm;
    u8 plln, pllm;
#endif
   
#if defined SYSCLK_FREQ_HSE
    SystemCoreClock         = SYSCLK_FREQ_HSE;
#elif defined SYSCLK_FREQ_XXMHz
    SystemCoreClock         = SYSCLK_FREQ_XXMHz;
#elif defined SYSCLK_HSI_XXMHz
    SystemCoreClock         = SYSCLK_HSI_XXMHz;
#else //HSI Selected as System Clock source
    SystemCoreClock         = HSI_VALUE;
#endif
   
    RCC->CR |= RCC_CR_HSION;
    while(!(RCC->CR & RCC_CR_HSIRDY));
#if defined(SYSCLK_FREQ_HSE) || defined(SYSCLK_FREQ_XXMHz)
    //PLL SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------
    //Enable HSE
    RCC->CR |= ((u32)RCC_CR_HSEON);


#endif
    DELAY_xUs(5);
    if(SystemCoreClock > 96000000) {
        RCC->APB1ENR |= RCC_APB1ENR_PWR;
        PWR->CR &= ~(3 << 14);
        PWR->CR |= 2 << 14;
    }
    else if(SystemCoreClock > 48000000) {
        RCC->APB1ENR |= RCC_APB1ENR_PWR;
        PWR->CR &= ~(3 << 14);
        PWR->CR |= 1 << 14;
    }
    else {
        RCC->APB1ENR |= RCC_APB1ENR_PWR;
        PWR->CR &= ~(3 << 14);
        PWR->CR |= 0 << 14;
    }
#if defined(SYSCLK_FREQ_HSE) || defined(SYSCLK_FREQ_XXMHz)
    //Wait till HSE is ready and if Time out is reached exit
    while(1) {
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        if(HSEStatus != 0)
            break;
        StartUpCounter++;
        if(StartUpCounter >= (10 * HSE_STARTUP_TIMEOUT))
            return;
    }

    if ((RCC->CR & RCC_CR_HSERDY) == RESET) {
        //If HSE fails to start-up, the application will have wrong clock
        //configuration. User can add here some code to deal with this error
        HSEStatus = (u32)0x00;
        return;
    }

    HSEStatus = (u32)0x01;
    DELAY_xUs(5);
#endif
#if defined(SYSCLK_FREQ_XXMHz)
    SystemCoreClock         = SYSCLK_FREQ_XXMHz;
#elif defined(SYSCLK_FREQ_HSE)
    SystemCoreClock         = HSE_VALUE;
#elif defined(SYSCLK_HSI_XXMHz)
    SystemCoreClock         = SYSCLK_HSI_XXMHz;
#else
    SystemCoreClock         = HSI_VALUE;
#endif

    //Enable Prefetch Buffer
    FLASH->ACR |= FLASH_ACR_PRFTBE;
    //Flash 0 wait state ,bit0~2
    FLASH->ACR &= ~FLASH_ACR_LATENCY;
    temp = (SystemCoreClock - 1) / 24000000;
    FLASH->ACR |= (temp & FLASH_ACR_LATENCY);
   
    RCC->CFGR &= (~RCC_CFGR_HPRE) & ( ~RCC_CFGR_PPRE1) & (~RCC_CFGR_PPRE2);
    //HCLK = AHB = FCLK = SYSCLK divided by 4
    RCC->CFGR |= (u32)RCC_CFGR_HPRE_DIV4;
    //PCLK2 = APB2 = HCLK divided by 1, APB2 is high APB CLK
    RCC->CFGR |= (u32)RCC_CFGR_PPRE2_DIV1;
    if(SystemCoreClock > 72000000) {
        //PCLK1 = APB1 = HCLK divided by 4, APB1 is low APB CLK
        RCC->CFGR |= (u32)RCC_CFGR_PPRE1_DIV4;
    }
    else if(SystemCoreClock > 36000000) {
        //PCLK1 = APB1 = HCLK divided by 2, APB1 is low APB CLK
        RCC->CFGR |= (u32)RCC_CFGR_PPRE1_DIV2;
    }
   
#if defined(SYSCLK_FREQ_XXMHz) || defined(SYSCLK_HSI_XXMHz)
   
#if defined(SYSCLK_FREQ_XXMHz)
    AutoCalPllFactor(HSE_VALUE, SystemCoreClock, &plln, &pllm);
    RCC->PLLCFGR &= ~((u32) RCC_PLLCFGR_PLLSRC | RCC_PLLCFGR_PLLXTPRE);
    RCC->PLLCFGR |= (u32) RCC_PLLCFGR_PLLSRC;
#endif
#if defined(SYSCLK_HSI_XXMHz)
    AutoCalPllFactor(HSI_VALUE_PLL_ON, SystemCoreClock, &plln, &pllm);
    RCC->PLLCFGR &= ~((u32) RCC_PLLCFGR_PLLSRC | RCC_PLLCFGR_PLLXTPRE);
    RCC->PLLCFGR &= ~((u32) RCC_PLLCFGR_PLLSRC);
#endif
    tm = (((u32)pllm) & 0x07);
    tn = (((u32)plln) & 0x7F);

    RCC->APB1ENR |= RCC_APB1ENR_PWR;
    RCC->PLLCFGR &= (u32)((~RCC_PLLCFGR_PLL_DN) & (~RCC_PLLCFGR_PLL_DP));
    RCC->PLLCFGR |= ((tn << RCC_PLLCFGR_PLL_DN_Pos) | (tm << RCC_PLLCFGR_PLL_DP_Pos));
    //Enable PLL
    RCC->CR |= RCC_CR_PLLON;
    //Wait till PLL is ready
    while((RCC->CR & RCC_CR_PLLRDY) == 0) {
        __ASM ("nop") ;//__NOP();
    }
    //Select PLL as system clock source
    RCC->CFGR &= (u32)((u32)~(RCC_CFGR_SW));
    RCC->CFGR |= (u32)RCC_CFGR_SW_PLL;
    //Wait till PLL is used as system clock source
    while ((RCC->CFGR & (u32)RCC_CFGR_SWS) != (u32)RCC_CFGR_SWS_PLL) {
        __ASM ("nop") ;//__NOP();
    }   
#elif defined(SYSCLK_FREQ_HSE)
    RCC->CFGR &= (u32)((u32)~(RCC_CFGR_SW));
    RCC->CFGR |= (u32)RCC_CFGR_SW_HSE;
    while ((RCC->CFGR & (u32)RCC_CFGR_SWS) != (u32)RCC_CFGR_SWS_HSE) {
        __ASM ("nop") ;//__NOP();
    }
#else
    RCC->CFGR &= (u32)((u32)~(RCC_CFGR_SW));
    RCC->CFGR |= (u32)0;
    while ((RCC->CFGR & (u32)RCC_CFGR_SWS) != (u32)0) {
        __ASM ("nop") ;//__NOP();
    }
#endif


    DELAY_xUs(1);
    // set HCLK = AHB = FCLK = SYSCLK divided by 2
    RCC->CFGR &= (~(RCC_CFGR_PPRE_0));
    DELAY_xUs(1);

    // set HCLK = AHB = FCLK = SYSCLK divided by 1
    RCC->CFGR &= (~(RCC_CFGR_PPRE_3));

    DELAY_xUs(1);

    if(GetCurrentSysClockFreq()!=SystemCoreClock){
        //set SystemCoreClock fail
        RCC->CFGR &= (u32)((u32)~(RCC_CFGR_SW));
        RCC->CFGR |= (u32)0;
        while ((RCC->CFGR & (u32)RCC_CFGR_SWS) != (u32)0) {
            __ASM ("nop") ;//__NOP();
        }
        SystemCoreClock = HSI_VALUE;
        //while(1){};
    }
}



/// @}



/// @}



/// @}

/**
  *************** (C) COPYRIGHT 2017 STMicroelectronics ************************
  * [url=home.php?mod=space&uid=288409]@file[/url]      startup_mm32f3273g9p.s
  * [url=home.php?mod=space&uid=187600]@author[/url]    MCD Application Team
  * @brief     MM32F3273G9PDevices vector table for Atollic toolchain.
  *            This module performs:
  *                - Set the initial SP
  *                - Set the initial PC == Reset_Handler,
  *                - Set the vector table entries with the exceptions ISR address
  *                - Configure the clock system   
  *                - Configure external SRAM mounted on MM32F3273G9P EVAL board
  *                  to be used as data memory (optional, to be enabled by user)
  *                - Branches to main in the C library (which eventually
  *                  calls main()).
  *            After Reset the Cortex-M3 processor is in Thread mode,
  *            priority is Privileged, and the Stack is set to Main.
  ******************************************************************************
  * @attention
  *
  * <h2><center>© Copyright (c) 2017 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */

  .syntax unified
  .cpu cortex-m3
  .fpu softvfp
  .thumb

.global g_pfnVectors
.global Default_Handler

/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss

.equ  BootRAM,        0xF1E0F85F
/**
* @brief  This is the code that gets called when the processor first
*          starts execution following a reset event. Only the absolutely
*          necessary set is performed, after which the application
*          supplied main() routine is called.
* @param  None
* @retval : None
*/

  .section .text.Reset_Handler
  .weak Reset_Handler
  .type Reset_Handler, %function
Reset_Handler:

/* Copy the data segment initializers from flash to SRAM */
  movs r1, #0
  b LoopCopyDataInit

CopyDataInit:
  ldr r3, =_sidata
  ldr r3, [r3, r1]
  str r3, [r0, r1]
  adds r1, r1, #4

LoopCopyDataInit:
  ldr r0, =_sdata
  ldr r3, =_edata
  adds r2, r0, r1
  cmp r2, r3
  bcc CopyDataInit
  ldr r2, =_sbss
  b LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:
  movs r3, #0
  str r3, [r2], #4

LoopFillZerobss:
  ldr r3, = _ebss
  cmp r2, r3
  bcc FillZerobss

/* Call the clock system intitialization function.*/
    bl  SystemInit
/* Call static constructors */
    bl __libc_init_array
/* Call the application's entry point.*/
  bl main
  bx lr
.size Reset_Handler, .-Reset_Handler

/**
* @brief  This is the code that gets called when the processor receives an
*         unexpected interrupt.  This simply enters an infinite loop, preserving
*         the system state for examination by a debugger.
*
* @param  None
* @retval : None
*/
    .section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
  b Infinite_Loop
  .size Default_Handler, .-Default_Handler
/******************************************************************************
*
* The minimal vector table for a Cortex M3.  Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
  .section .isr_vector,"a",%progbits
  .type g_pfnVectors, %object
  .size g_pfnVectors, .-g_pfnVectors


g_pfnVectors:

  .word _estack
  .word Reset_Handler
  .word NMI_Handler
  .word HardFault_Handler
  .word MemManage_Handler
  .word BusFault_Handler
  .word UsageFault_Handler
  .word 0
  .word 0
  .word 0
  .word 0
  .word SVC_Handler
  .word DebugMon_Handler
  .word 0
  .word PendSV_Handler
  .word SysTick_Handler
  .word WWDG_IRQHandler
  .word PVD_IRQHandler
  .word TAMPER_IRQHandler
  .word RTC_IRQHandler
  .word FLASH_IRQHandler
  .word RCC_CRS_IRQHandler
  .word EXTI0_IRQHandler
  .word EXTI1_IRQHandler
  .word EXTI2_IRQHandler
  .word EXTI3_IRQHandler
  .word EXTI4_IRQHandler
  .word DMA1_Channel1_IRQHandler
  .word DMA1_Channel2_IRQHandler
  .word DMA1_Channel3_IRQHandler
  .word DMA1_Channel4_IRQHandler
  .word DMA1_Channel5_IRQHandler
  .word DMA1_Channel6_IRQHandler
  .word DMA1_Channel7_IRQHandler
  .word ADC1_2_IRQHandler
  .word FlashCache_IRQHandler
  .word 0
  .word CAN1_RX_IRQHandler
  .word 0
  .word EXTI9_5_IRQHandler
  .word TIM1_BRK_IRQHandler
  .word TIM1_UP_IRQHandler
  .word TIM1_TRG_COM_IRQHandler
  .word TIM1_CC_IRQHandler
  .word TIM2_IRQHandler
  .word TIM3_IRQHandler
  .word TIM4_IRQHandler
  .word I2C1_IRQHandler
  .word 0
  .word I2C2_IRQHandler
  .word 0
  .word SPI1_IRQHandler
  .word SPI2_IRQHandler
  .word UART1_IRQHandler
  .word UART2_IRQHandler
  .word UART3_IRQHandler
  .word EXTI15_10_IRQHandler
  .word RTCAlarm_IRQHandler
  .word OTG_FS_WKUP_IRQHandler
  .word TIM8_BRK_IRQHandler
  .word TIM8_UP_IRQHandler
  .word TIM8_TRG_COM_IRQHandler
  .word TIM8_CC_IRQHandler
  .word ADC3_IRQHandler
  .word 0
  .word SDIO_IRQHandler
  .word TIM5_IRQHandler
  .word SPI3_IRQHandler
  .word UART4_IRQHandler
  .word UART5_IRQHandler
  .word TIM6_IRQHandler
  .word TIM7_IRQHandler
  .word DMA2_Channel1_IRQHandler
  .word DMA2_Channel2_IRQHandler
  .word DMA2_Channel3_IRQHandler
  .word DMA2_Channel4_IRQHandler
  .word DMA2_Channel5_IRQHandler
  .word ETH_IRQHandler
  .word 0
  .word 0
  .word COMP1_2_IRQHandler
  .word 0
  .word 0
  .word OTG_FS_IRQHandler
  .word 0
  .word 0
  .word 0
  .word UART6_IRQHandler
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word UART7_IRQHandler
  .word UART8_IRQHandler
  .word BootRAM      

/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/

  .weak NMI_Handler
  .thumb_set NMI_Handler,Default_Handler

  .weak HardFault_Handler
  .thumb_set HardFault_Handler,Default_Handler

  .weak MemManage_Handler
  .thumb_set MemManage_Handler,Default_Handler

  .weak BusFault_Handler
  .thumb_set BusFault_Handler,Default_Handler

  .weak UsageFault_Handler
  .thumb_set UsageFault_Handler,Default_Handler

  .weak SVC_Handler
  .thumb_set SVC_Handler,Default_Handler

  .weak DebugMon_Handler
  .thumb_set DebugMon_Handler,Default_Handler

  .weak PendSV_Handler
  .thumb_set PendSV_Handler,Default_Handler

  .weak SysTick_Handler
  .thumb_set SysTick_Handler,Default_Handler

  .weak WWDG_IRQHandler
  .thumb_set WWDG_IRQHandler,Default_Handler

  .weak PVD_IRQHandler
  .thumb_set PVD_IRQHandler,Default_Handler

  .weak TAMPER_IRQHandler
  .thumb_set TAMPER_IRQHandler,Default_Handler

  .weak RTC_IRQHandler
  .thumb_set RTC_IRQHandler,Default_Handler

  .weak FLASH_IRQHandler
  .thumb_set FLASH_IRQHandler,Default_Handler

  .weak RCC_CRS_IRQHandler
  .thumb_set RCC_CRS_IRQHandler,Default_Handler

  .weak EXTI0_IRQHandler
  .thumb_set EXTI0_IRQHandler,Default_Handler

  .weak EXTI1_IRQHandler
  .thumb_set EXTI1_IRQHandler,Default_Handler

  .weak EXTI2_IRQHandler
  .thumb_set EXTI2_IRQHandler,Default_Handler

  .weak EXTI3_IRQHandler
  .thumb_set EXTI3_IRQHandler,Default_Handler

  .weak EXTI4_IRQHandler
  .thumb_set EXTI4_IRQHandler,Default_Handler

  .weak DMA1_Channel1_IRQHandler
  .thumb_set DMA1_Channel1_IRQHandler,Default_Handler

  .weak DMA1_Channel2_IRQHandler
  .thumb_set DMA1_Channel2_IRQHandler,Default_Handler

  .weak DMA1_Channel3_IRQHandler
  .thumb_set DMA1_Channel3_IRQHandler,Default_Handler

  .weak DMA1_Channel4_IRQHandler
  .thumb_set DMA1_Channel4_IRQHandler,Default_Handler

  .weak DMA1_Channel5_IRQHandler
  .thumb_set DMA1_Channel5_IRQHandler,Default_Handler

  .weak DMA1_Channel6_IRQHandler
  .thumb_set DMA1_Channel6_IRQHandler,Default_Handler

  .weak DMA1_Channel7_IRQHandler
  .thumb_set DMA1_Channel7_IRQHandler,Default_Handler

  .weak ADC1_2_IRQHandler
  .thumb_set ADC1_2_IRQHandler,Default_Handler

  .weak FlashCache_IRQHandler
  .thumb_set FlashCache_IRQHandler,Default_Handler

  .weak CAN1_RX_IRQHandler
  .thumb_set CAN1_RX_IRQHandler,Default_Handler

  .weak EXTI9_5_IRQHandler
  .thumb_set EXTI9_5_IRQHandler,Default_Handler

  .weak TIM1_BRK_IRQHandler
  .thumb_set TIM1_BRK_IRQHandler,Default_Handler

  .weak TIM1_UP_IRQHandler
  .thumb_set TIM1_UP_IRQHandler,Default_Handler

  .weak TIM1_TRG_COM_IRQHandler
  .thumb_set TIM1_TRG_COM_IRQHandler,Default_Handler

  .weak TIM1_CC_IRQHandler
  .thumb_set TIM1_CC_IRQHandler,Default_Handler

  .weak TIM2_IRQHandler
  .thumb_set TIM2_IRQHandler,Default_Handler

  .weak TIM3_IRQHandler
  .thumb_set TIM3_IRQHandler,Default_Handler

  .weak TIM4_IRQHandler
  .thumb_set TIM4_IRQHandler,Default_Handler

  .weak I2C1_IRQHandler
  .thumb_set I2C1_IRQHandler,Default_Handler

  .weak I2C2_IRQHandler
  .thumb_set I2C2_IRQHandler,Default_Handler

  .weak SPI1_IRQHandler
  .thumb_set SPI1_IRQHandler,Default_Handler

  .weak SPI2_IRQHandler
  .thumb_set SPI2_IRQHandler,Default_Handler

  .weak UART1_IRQHandler
  .thumb_set UART1_IRQHandler,Default_Handler

  .weak UART2_IRQHandler
  .thumb_set UART2_IRQHandler,Default_Handler

  .weak UART3_IRQHandler
  .thumb_set UART3_IRQHandler,Default_Handler

  .weak EXTI15_10_IRQHandler
  .thumb_set EXTI15_10_IRQHandler,Default_Handler

  .weak RTCAlarm_IRQHandler
  .thumb_set RTCAlarm_IRQHandler,Default_Handler

  .weak OTG_FS_WKUP_IRQHandler
  .thumb_set OTG_FS_WKUP_IRQHandler,Default_Handler

  .weak TIM8_BRK_IRQHandler
  .thumb_set TIM8_BRK_IRQHandler,Default_Handler

  .weak TIM8_UP_IRQHandler
  .thumb_set TIM8_UP_IRQHandler,Default_Handler

  .weak TIM8_TRG_COM_IRQHandler
  .thumb_set TIM8_TRG_COM_IRQHandler,Default_Handler

  .weak TIM8_CC_IRQHandler
  .thumb_set TIM8_CC_IRQHandler,Default_Handler

  .weak ADC3_IRQHandler
  .thumb_set ADC3_IRQHandler,Default_Handler

  .weak SDIO_IRQHandler
  .thumb_set SDIO_IRQHandler,Default_Handler

  .weak TIM5_IRQHandler
  .thumb_set TIM5_IRQHandler,Default_Handler

  .weak SPI3_IRQHandler
  .thumb_set SPI3_IRQHandler,Default_Handler

  .weak UART4_IRQHandler
  .thumb_set UART4_IRQHandler,Default_Handler

  .weak UART5_IRQHandler
  .thumb_set UART5_IRQHandler,Default_Handler

  .weak TIM6_IRQHandler
  .thumb_set TIM6_IRQHandler,Default_Handler

  .weak TIM7_IRQHandler
  .thumb_set TIM7_IRQHandler,Default_Handler

  .weak DMA2_Channel1_IRQHandler
  .thumb_set DMA2_Channel1_IRQHandler,Default_Handler

  .weak DMA2_Channel2_IRQHandler
  .thumb_set DMA2_Channel2_IRQHandler,Default_Handler

  .weak DMA2_Channel3_IRQHandler
  .thumb_set DMA2_Channel3_IRQHandler,Default_Handler

  .weak DMA2_Channel4_IRQHandler
  .thumb_set DMA2_Channel4_IRQHandler,Default_Handler

  .weak DMA2_Channel5_IRQHandler
  .thumb_set DMA2_Channel5_IRQHandler,Default_Handler

  .weak ETH_IRQHandler
  .thumb_set ETH_IRQHandler,Default_Handler

  .weak COMP1_2_IRQHandler
  .thumb_set COMP1_2_IRQHandler,Default_Handler

  .weak OTG_FS_IRQHandler
  .thumb_set OTG_FS_IRQHandler,Default_Handler

  .weak UART6_IRQHandler
  .thumb_set UART6_IRQHandler,Default_Handler

  .weak UART7_IRQHandler
  .thumb_set UART7_IRQHandler,Default_Handler

  .weak UART8_IRQHandler
  .thumb_set UART8_IRQHandler,Default_Handler

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/*
*****************************************************************************
**
**  File        : MM32F3273G9P_FLASH.ld
**
**  Abstract    : Linker script for MM32F3273G9P Device with
**                512KByte FLASH, 128Byte RAM
**
**                Set heap size, stack size and stack location according
**                to application requirements.
**
**                Set memory bank area and size if external memory is used.
**
**  Target      : MM32F3273G9P
**
**
**  Distribution: The file is distributed as is, without any warranty
**                of any kind.
**
**  (c)Copyright Ac6.
**  You may use this file as-is or modify it according to the needs of your
**  project. Distribution of this file (unmodified or modified) is not
**  permitted. Ac6 permit registered System Workbench for MCU users the
**  rights to distribute the assembled, compiled & linked contents of this
**  file as part of an application binary file, provided that it is built
**  using the System Workbench for MCU toolchain.
**
*****************************************************************************
*/

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = 0x2001FFFF;    /* end of RAM */

/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x800;      /* required amount of heap  */
_Min_Stack_Size = 0x800; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 128K
}

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH

  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH

  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
  .ARM : {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } >FLASH

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH
  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >FLASH

  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >RAM AT> FLASH

  
  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM

  

  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}
5. 更改完后,需要对 src 文件夹下的文件做替换,从 MM32F3270 官方的闪灯例程中找到对应文件替换过来,为了保险起见,将以上几个文件也包含到目录中(也是后面调试时才发现一定要添加进来的,不然编译通过但是无法正常运行,未包含启动文件),并且将 uart.c 的retarget 修改好适配 gcc 编译器

6. 至此已经完成了替代,可以进行 build 编译、upload 下载以及使用 Cortex-Debug 调试工程了

下载调试使用到 j-link 驱动包,由于该包也是通过依赖而新下载而来的,所以需要将灵动微的设备添加到驱动包中,这样才可以正常下载和调试。可以看到,调试环境很方便,还能看到反编译的汇编代码以及寄存器实时更新窗口,赞!

三、后续待完成的集成要点
由于是从 ST 改写而来的集成,并未通过 PIO 官方 package  注册获得 MM32 本身的平台认证,所以后续是需要制作平台支持包以及板子支持包的,而且还得适配好 Arduino 、 SPL 、 mbed os 等几大主流框架,加上适配好 CMSIS DAP/openocd/ Serial 调试或者下载工具支持包。所以说,其实还有很多支持包需要官方做好,我们用户才能拿来就用,更加方便地进行 PIO 开发。
附件内容包含了我这次修改的支持包,以及调试运行的示例闪灯工程,大家可以拿去试着搭建起自己的环境,跑跑看了!


四、参考链接
PlatformIO 玩转单片机开发   https://www.zhihu.com/column/c_1094956718374633472
如何新增自己的 PIO 开发平台  https://docs.platformio.org/en/latest/platforms/creating_platform.html   
GD32 的开源 git 仓   https://github.com/CommunityGD32Cores/gd32-pio-projects


ststm32.zip (887.81 KB)
framework-cmsis-stm32f1.zip (1.23 MB)
mm32f3273_pio_demo.zip (287.11 KB)
MM32_JLINK_pack.zip (292.49 KB)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 100.00 元 2022-07-04
理由:恭喜通过原创文章审核!请多多加油哦!

评论
21小跑堂 2022-7-4 10:05 回复TA
以ST开发板为跳板,实现MM32开发板移植到VS-Code的PlatformIO 环境中,实现过程具有借鉴意义,但是由于官方适配问题,此方案还有挺长的路要走 
沙发
zwk34| | 2022-6-29 08:56 | 只看该作者
谢谢分享。

使用特权

评论回复
板凳
ACE丶打铁匠| | 2022-10-9 15:47 | 只看该作者
感谢分享

使用特权

评论回复
地板
芯路例程| | 2022-10-13 17:05 | 只看该作者
PlatformIO之前也听过,一直没实际用过,看样子好像还不赖。

使用特权

评论回复
5
星辰大海不退缩| | 2022-10-14 08:33 | 只看该作者
PlatformIO 讲解的很详细,过程配置也非常清楚,后期有时间自己试一下,赞

使用特权

评论回复
发新帖 本帖赏金 100.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

35

主题

204

帖子

10

粉丝