Fun_Cheong's - 嵌入式
type: channel
link: https://funcheong.cn/index.php/category/%E5%B5%8C%E5%85%A5%E5%BC%8F/
items: RT-Thread BSP小记
items: c2000 基本通信外设使用笔记 （Driverlib)
items: C2000 epwm使用笔记 (Driverlib)
items: 再探FOC——读一读St电机库源码
items: SimpleFOC库移植简述
items: STM32——FMC/FSMC学习笔记
items: INAV飞控MSP通信程序分析
items: 如何配置使用CLion开发TM4C123的环境

RT-Thread BSP小记
type: item
date: 2024-07-02T20:40:00+08:00
description: 前言最近做的一个小项目需要使用到RT-Thread，借此机会了解了一下RT-Thread和它提供的BSP框架，并简单分析移植方法。关于RT-Thread和它的BSP，官方已经提供了比较详尽的文档，并且目前已经有部分厂家（比如最常见的STM32）的BSP模版可供使用：RT-Thread标准版本文档STM32系列BSP制作教程BSP工程目录总览本文以笔者手上的weact的stm32h750开发板BSP为例进行分析Applications:应用层代码Compiler:ARM的C语言库DeviceDrivers:RT-Thread的Device框架驱动程序Drivers:包括Device框架中各种设备在STM32外设上的实现以及开发板上外设的驱动程序FAL,Filesystem,Finsh:都是一些RT-Thread的可选组件Kernel:RT-Thread内核文件libcpu:RT-Thread内核移植需要进行实现的API和一些与单片机内核相关的函数Libraries:HAL库如何让内核在你的芯片上跑起来内核的移植首先是拷贝操作系统内核源码，然后对中断使能，上下文切换等依赖于具体单片机内核的功能进行实现操作系统源码这部分代码放在rt-thread仓库的src目录下，对应的头文件在include目录下。注意到这堆文件都加了个编译预处理选项：找了一下发现这个宏定义出现在了include/rtsched.h里：/** * NOTE: user should NEVER use these APIs directly. See rt_thread_.* or IPC * methods instead. */ #if defined(__RT_KERNEL_SOURCE__) || defined(__RT_IPC_SOURCE__) /* thread initialization and startup routine */ void rt_sched_thread_init_ctx(struct rt_thread *thread, rt_uint32_t tick, rt_uint8_t priority); void rt_sched_thread_init_priv(struct rt_thread *thread, rt_uint32_t tick, rt_uint8_t priority); void rt_sched_thread_startup(struct rt_thread *thread); /* scheduler related routine */ void rt_sched_post_ctx_switch(struct rt_thread *thread); rt_err_t rt_sched_tick_increase(void); /* thread status operation */ rt_uint8_t rt_sched_thread_get_stat(struct rt_thread *thread); rt_uint8_t rt_sched_thread_get_curr_prio(struct rt_thread *thread); rt_uint8_t rt_sched_thread_get_init_prio(struct rt_thread *thread); rt_err_t rt_sched_thread_yield(struct rt_thread *thread); rt_err_t rt_sched_thread_close(struct rt_thread *thread); rt_err_t rt_sched_thread_ready(struct rt_thread *thread); rt_err_t rt_sched_thread_suspend(struct rt_thread *thread, rt_sched_lock_level_t level); rt_err_t rt_sched_thread_change_priority(struct rt_thread *thread, rt_uint8_t priority); rt_err_t rt_sched_thread_bind_cpu(struct rt_thread *thread, int cpu); rt_uint8_t rt_sched_thread_is_suspended(struct rt_thread *thread); rt_err_t rt_sched_thread_timer_stop(struct rt_thread *thread); rt_err_t rt_sched_th…

c2000 基本通信外设使用笔记 （Driverlib)
type: item
date: 2023-07-21T14:47:00+08:00
description: 简介在电力电子应用中单片机经常需要和各种外围设备进行通信，本文主要介绍几种常用的通信接口的使用方法低速时钟(LSPCLK)c2000的一些通信接口（McBSP、SCI、SPI）是将片上的低速时钟信号作为Bitclock的，低速时钟由系统时钟分频得到，默认是四分频，如果需要修改分频系数可以使用下面这个函数static inline void SysCtl_setLowSpeedClock(SysCtl_LSPCLKPrescaler prescaler)串行通信接口(SCI)SCI的发送和接收都有一个16字节的FIFO，还有一些常见的中断信号之类的，和其他串口区别不大捏妈，API手册SCI这块直接消失了，谢谢你，TI初始化设置GPIO//发送引脚 GPIO_setPadConfig(8,GPIO_PIN_TYPE_STD); GPIO_setPinConfig(GPIO_8_SCITXDA); GPIO_setQualificationMode(8,GPIO_QUAL_ASYNC); //接收引脚 GPIO_setPadConfig(9,GPIO_PIN_TYPE_STD); GPIO_setPinConfig(GPIO_9_SCIRXDA); GPIO_setQualificationMode(9,GPIO_QUAL_ASYNC);TI论坛上面有老哥说这个qualification不开也能用，笔者试了一下发现确实能用，按照TI的说法，这个qualification是用来防止在复杂环境下工作时受到各种噪声影响的，手册上面也让把这个打开，所以还是开了（重置SCI SCI_performSoftwareReset(SCIA_BASE);设置通信速率和帧格式波特率通过设置分频寄存器进行配置，手册给出了波特率的计算公式：BRR = (SCIHBAUD << 8) + (SCILBAUD)The SCI baud rate is calculated using the following equation:SCI Asynchronous Baud = LSPCLK / ((BRR + 1) *8)最大的可设置波特率是低速时钟频率除以十六使用Driverlib函数可以自动计算分频系数帧格式需要设置数据位数、停止位数和校验方式 SCI_setConfig(SCIA_BASE,50000000,115200,//低速时钟50MHz,波特率115200 (SCI_CONFIG_WLEN_8| //8位数据位 SCI_CONFIG_STOP_ONE| //1位停止位 SCI_CONFIG_PAR_NONE)); //无校验 设置FIFOSCI_enableFIFO(SCIA_BASE);//使能FIFO SCI_resetTxFIFO(SCIA_BASE);//重置FIFO SCI_resetRxFIFO(SCIA_BASE);设置中断下面这个是SCI的中断信号框图初始化中断向量表（如果没有初始化的话）Interrupt_enablePIE(); Interrupt_initModule(); Interrupt_initVectorTable();关中断DINT;注册中断服务函数，函数使用的中断号可以在hw_ints.h里面找到Interrupt_register(INT_SCIA_RX,Recv_Callback);//注册SCIA的串口接收中断服务函数使能中断Interrupt_enable(INT_SCIA_RX);//在CPU处使能SCIA串口接收中断 SCI_enableInterrupt(SCIA_BASE,SCI_INT_RXFF);//使能SCIA模块的接收中断设置FIFO中断条件，接收和发送可以分别设置SCI_setFIFOInterruptLevel(SCIA_BASE,SCI_FIFO_TX1,SCI_FIFO_RX1); //发送和接收FIFO都在接收到一个字节之后产生中断开中断EINT;其他通信外设的中断玩法和这个差不多，后面就不提了使能SCISCI_enableModule(SCIA_BASE);使用接收中断实现一个简单的echo功能__interrupt void Recv_Callback(void) { uint16_t recv_buffer[1]={0}; SCI_readCharArray(SCIA_BASE,recv_buffer,1); SCI_writeCharArray(SCIA_BASE,recv_buffer,1); SCI_clearOverflowStatus(SCIA_BASE); SCI_clearInterruptStatus(SCIA_BASE, SCI_INT_RXFF); I…

C2000 epwm使用笔记 (Driverlib)
type: item
date: 2023-07-18T17:32:00+08:00
description: 介绍The enhanced pulse width modulator (ePWM) peripheral is a key element in controlling many of the powerelectronic systems found in both commercial and industrial equipment. These systems include digitalmotor control, switch mode power supply control, uninterruptible power supplies (UPS), and other forms ofpower conversion.增强型脉宽调制器(ePWM)外设是控制许多商业和工业设备中电力电子系统的关键元素，这些系统一般包括数字电机控制，开关电源控制，不间断电源控制和其他形式的功率转换系统由上图可见，c2000的epwm被分为了几个子模块，包括时基模块，比较模块，波形发生器模块等GPIO配置首先要配置epwm模块工作时需要使用到的引脚，把输出引脚配成推挽就行void GPIO_setPadConfig(uint32_t pin, uint32_t pinType)pinType的参数：GPIO_PIN_TYPE_STD specifies a push-pull output or a floating inputGPIO_PIN_TYPE_PULLUP specifies the pull-up is enabled for an inputGPIO_PIN_TYPE_OD specifies an open-drain output pinGPIO_PIN_TYPE_INVERT specifies inverted polarity on an input把引脚分配给EPWMvoid GPIO_setPinConfig(uint32_t pinConfig)pinConfig的宏定义可以在pin_map.h里找到,不同的芯片的driverlib可能会有一点区别，比如28379是GPIO_0_EPWM1A,280049就变成了GPIO_0_EPWM1_A我也不知道为什么非要整个不一样的EPWM点灯流程首先禁用计数器的时钟信号SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_TBCLKSYNC);配置时基(Time-Base)设置分频系数（笔者这里设置了不分频，可以根据需求改）EPWM_setClockPrescaler(EPWM1_BASE,EPWM_CLOCK_DIVIDER_1, EPWM_HSCLOCK_DIVIDER_1);This function sets the pre scaler(divider)value for the time base clock counter and the high speed time base clock counter. Valid values for pre-scaler and highSpeedPrescaler are EPWM_CLOCK_DIVIDER_X, where X is 1,2,4,8,16, 32,64 or 128. The actual numerical values for these macros represent values 0,1…7. The equation for the output clock is: TBCLK = EPWMCLK/(highSpeedPrescaler * pre-scaler)设置计数方向EPWM_setTimeBaseCounterMode(EPWM1_BASE,EPWM_COUNTER_MODE_UP);设置重装载值装载方式EPWM_setPeriodLoadMode(EPWM1_BASE,EPWM_PERIOD_SHADOW_LOAD);EPWM_PERIOD_SHADOW_LOAD 使用影子寄存器EPWM_PERIOD_DIRECT_LOAD 直接访问设置计数器重装载事件EPWM_selectPeriodLoadEvent(EPWM1_BASE, EPWM_SHADOW_LOAD_MODE_COUNTER_ZERO);设置重装载值EPWM_setTimeBasePeriod(EPWM1_BASE,10000);配置比较器(Counter Compare)设置比较值的装载事件EPWM_setCounterCompareShadowLoadMode(EPWM1_BASE, EPWM_COUNTER_COMPARE_A, EPWM_COMP_L…

再探FOC——读一读St电机库源码
type: item
date: 2023-03-07T20:13:00+08:00
description: 前言最近在尝试把FOC部署到STM32G030上，为了使FOC的运行更加丝滑，有必要在代码上进行一些优化以加快运算速度，因此，笔者参考了St电机库的源码，从中学习到了一些优化的技巧。本文主要介绍FOC算法在St电机库中的实现方式，并与SimpleFOC进行简要对比。数据定点化众所周知，浮点数的运算相比整数运算要更耗费时间，在实际的电机控制应用中，一般会将算法中的数据变成定点数来处理（虽然SimpleFOC里面几乎全用的浮点数）。在谈论这个问题之前，我们先回顾一下浮点数和定点数在计算机中的存储方式。浮点数浮点数在内存中由三个部分组成：一个符号位、若干个指数位（表示小数点的位置），若干个尾数位。定点数定点数由于小数点的位置是固定的，因此不再需要指数位，一个定点数的小数点具体在哪个位置是由编写程序的人约定的TI的IQMath库中对定点数类型的定义SimpleFOC与St电机库数据处理的对比——以电流采样为例float _readADCVoltageInline(const int pinA, const void* cs_params){ uint32_t raw_adc = adcRead(pinA); return raw_adc * ((ESP32MCPWMCurrentSenseParams*)cs_params)->adc_voltage_conv; }可以看到SimpleFOC在电流计算的第一步就直接把原始值换算成了用浮点数表示的实际值。 bSector = pHandle->_Super.Sector; hReg1 = *pHandle->pParams_str->ADCDataReg1[bSector] * 2; hReg2 = *pHandle->pParams_str->ADCDataReg2[bSector] * 2; switch ( bSector ) { case SECTOR_4: case SECTOR_5: /* Current on Phase C is not accessible */ /* Ia = PhaseAOffset - ADC converted value) */ wAux = ( int32_t )( pHandle->PhaseAOffset ) - ( int32_t )( hReg1 ); /* Saturation of Ia */ if ( wAux < -INT16_MAX ) { pStator_Currents->a = -INT16_MAX; } else if ( wAux > INT16_MAX ) { pStator_Currents->a = INT16_MAX; } else { pStator_Currents->a = ( int16_t )wAux; }以上是三电阻电流采样函数的一部分，由于采用三电阻，因此要依电压扇区决定舍弃哪一相的电流值。主要做的事情是把原始值拿出来乘了个二然后限制了一下范围，乍一看有点摸不着头脑。根据初始化代码指定的ADC转换模式找到了对应的数据对齐方式图，可以发现乘二的操作其实就是把12位的原始值映射到了16位上（因为St电机库用的是q15的定点数）。定点数的运算定点数运算-维基百科定点数的加减法比较简单，直接按一般方法加减即可。在乘除运算时会遇到一些问题。比如两个q15的定点数相乘，要想保留全部的精度，结果应该是一个q30的数，如果想要得到一个与原来长度相同的定点数，就要舍弃一些精度来对结果进行裁剪，比如在本例中，要想让结果变成q15，需要把q30的结果右移15位。除法的规则类似。以下是St电机库中Clarke变换中定点数运算的例子： int32_t a_divSQRT3_tmp, b_divSQRT3_tmp ; int32_t wbeta_tmp; int16_t hbeta_tmp; Output.alpha = Input.a; a_divSQRT3_tmp = divSQRT_3 * ( int32_t )Input.a; b_divSQRT3_tmp = divSQRT_3 * ( int32_t )Input.b; wbeta_tmp = ( -( a_divSQRT3_tmp ) - ( b_divSQRT3_tmp ) - ( b_divSQRT3_tmp ) ) >> 15;扇区判断在没有加入电流环时，我们无法对U轴和D轴的电流进行精确的控制，输出电压的D分量一般设为0，此时电压矢量的扇区可以直接由转子机械角度换算得到，但在加入电流环之后，D分量不一定为零，因此需要针对输出电压矢量重新判断扇区。SimpleFOC的方案SimpleFOC新版本加入了电流环的功能，因此它里面也有这部分的代码。它对电压矢…

SimpleFOC库移植简述
type: item
date: 2023-01-20T20:34:00+08:00
description: 前言学校电赛冬令营发了一个带传感器的无刷云台电机和一块simpleFOC驱动板，借此机会学习一下有感FOC的基本原理和使用。前置知识稚晖君这篇文章里讲得非常的简单易懂，推荐阅读：https://zhuanlan.zhihu.com/p/147659820笔者在此对文章中的内容作简要的总结。FOC简介FOC全称Field-Oriented Control——磁场定向控制,FOC可以适用在交流感应电机及直流无刷电机，早期开发的目的为了高性能的电机应用，可以在整个频率范围内运转、电机零速时可以输出额定转矩、且可以快速的加减速。正因为上述的各种优点，如今FOC被广泛地用于各种机器人系统的电机驱动中。Clark变换和Park变换Clark变换是将三相电机的电流、电压向量进行了正交化；Park令两个正交的向量跟随转子进行旋转（对于这一步，个人理解是当向量随着转子旋转的时候，转子受到的力矩全部来源于两个向量中的q分量，因此只需控制q分量的大小就可以控制当前电机的转矩，并且可以通过减小d分量来减少其它的能量损耗）。对于这两步变换的作用，笔者认为稚晖君做的总结非常到位：所谓的“矢量控制”其实就是在做解耦，把相互耦合的三相磁链解耦为容易控制的交轴和直轴。整个过程就好比我们在做信号处理的时候，通过FFT把信号变换到频域进行处理之后再IFFT反变换回时域是一个道理SVPWM技术掌握了无刷电机输入相量的简化方法后，我们需要考虑如何进行输出，即如何将解耦后的交轴和直轴分量重新转化为电机的三个相量。通过改变半桥开关管的通断，三相电机的三个绕组共可以产生六种不同方向的磁场。而要产生任意方向任意强度的磁场，我们需要将该方向所处扇区两边的两个矢量进行合成，这一步合成的操作就需要使用SVPWM（Space Vector Pulse Width Modulation——空间矢量脉宽调制)技术。与我们熟悉的PWM技术不同的是，SVPWM从原本的一维标量变成了二维的矢量，它利用了电机绕组的电感特性对三个磁链（第三个是0）进行合成，实现了任意方向任意强度磁场的产生。具体实现方法见稚晖君文章。机械角度与电角度要实现以上操作，有一点我们不能忽略，就是我们需要知道转子当前的位置。在有感FOC中，转子的位置是通过角度传感器获取的，SimpleFOC支持四种不同类型的角度传感器。在极对数为一的情况下，电机的机械角度和计算电压矢量时使用的角度是可以完全对应的，但是在实际的无刷电机中，极对数一般会更多：这时候就出现了一个问题——电机的机械角度和电压矢量的角度是不相等的。从上图可以很直观地看出，在极对数为二的电机中，在电压矢量旋转了一圈后，电机实际上只转了半圈。所以要得到电角度，需要将传感器测得的机械角度进行一些换算，公式非常简单：电角度=机械角度*极对数程序分析SVPWMSimpleFOC代码最核心的部分，实际上是SVPWM的实现，因此要将SimpleFOC移植至不同平台，基本上就是移植这一部分的代码。 float Uout; if(Ud){ // 设置了不为零的d分量的情况，如果是带电流环的时候d分量可能会有输出 Uout = _sqrt(Ud*Ud + Uq*Uq) / driver->voltage_limit;//计算矢量的大小 angle_el = _normalizeAngle(angle_el + atan2(Uq, Ud));//计算矢量的方向 }else{//d分量为零的情况 Uout = Uq / driver->voltage_limit; angle_el = _normalizeAngle(angle_el + _PI_2);//将转子角度归一化至0到2pi之间 } sector = floor(angle_el / _PI_3) + 1;//求当前所处扇区 //根据SVPWM公式计算时间 float T1 = _SQRT3*_sin(sector*_PI_3 - angle_el) * Uout; float T2 = _SQRT3*_sin(angle_el - (sector-1.0f)*_PI_3) * Uout; float T0 = 0; //开发者说在供电电压比较低的时候可以不使用中心对齐，把零矢量的时间设为零 if (modulation_centered) { T0 = 1 - T1 - T2; //当使用PWM中心对齐模式输出时计算零矢量的持续时间 } // 根据转子所在扇区计算三个相位的时间（七段式SVPWM） float Ta,Tb,Tc; switch(sector){ case 1: Ta = T1 + T2 + T0/2; Tb = T2 + T0/2; Tc = T0/2; break; case 2: Ta = T1 + T0…

STM32——FMC/FSMC学习笔记
type: item
date: 2023-01-11T11:18:00+08:00
description: 简介FSMC(Flexible Static Memory Controller，可变静态存储控制器)是STM32系列采用的一种新型的存储器扩展技术。在外部存储器扩展方面具有独特的优势，可根据系统的应用需要，方便地进行不同类型大容量静态存储器的扩展（还有一种用途是用来驱动并口屏）系统框图*本文使用的图片均出自STM32H750VB的参考手册，FMC的具体情况在不同型号的芯片内可能会有所不同。在STM32单片机中，FMC的作用相当于沟通了芯片内部的设备总线和外部存储器件，使得CPU能够通过总线访问到外部器件的内存。内存映射FMC内存框图FMC的内存被分为若干个内存块，其中内存块1拥有一个独立的2位片选信号，将该内存块分为了四个扇区，也就是支持最多连接四个内存设备。NOR/PSRAM设备的内存映射前文提到内存块1的2位片选信号由地址线的第27、28位控制。地址线的前26位用于访问外部内存设备中的指定位置。考虑到设备寄存器宽度不同，st官方给出了以上说明。在对外部通信过程中，若设备的寄存器宽度为16位，那么地址线的第26位将不被使用，32位寄存器同理，不使用第26、25位。同时，对于CPU而言，内存的单位仍然是一个字节，因此在使用外部设备的寄存器地址时应当将其地址左移相应位数。应用举例:ILI9341LCD驱动芯片ILI9341使用8080通信协议，STM32的FSMC使用该协议时要求用户指定地址线上的一位作为8080的数据、命令信号。实际上是将LCD当作了一种只有两个寄存器的SRAM芯片，从而将SRAM总线的通信转换成了8080.以下是正点原子写的驱动代码：//LCD地址结构体 typedef struct { u16 LCD_REG; u16 LCD_RAM; } LCD_TypeDef; //使用NOR/SRAM的 Bank1.sector1,地址位HADDR[27,26]=00 A18作为数据命令区分线 //注意设置时STM32内部会右移一位对齐! #define LCD_BASE ((u32)(0x60000000 | 0x00007FFFE)) #define LCD ((LCD_TypeDef *) LCD_BASE)结合上文不难得知LCD内存的“基地址”如何求取。

INAV飞控MSP通信程序分析
type: item
date: 2023-01-09T14:06:00+08:00
description: 前言前段时间购入了一块MATEK的传感器模块，上面包含了一个光流和一个激光传感器，该模块使用MSP通信协议与飞控进行串口通信，因此笔者尝试从INAV的开源飞控代码入手研究它的具体通信过程协议介绍官方文档地址：https://github.com/iNavFlight/inav/wiki/MSP-V2协议简介MSP is a request-response protocol. MSP defines the side sending the request as "MSP Master" role and the side generating a response as "MSP Slave" role.A specific device may function as both "MSP Master" and "MSP Slave" or implement only one role, depending on requirements. Generally, a Groundstation software is an "MSP Master" and FC is an "MSP Slave", however in some use-cases a FC or any other device may be required to act as an "MSP Master" and "MSP Slave" concurrently.简单浏览得知，飞控和外设之间通信是经典的主从关系报文格式根据官方给出的表格：和一般的通信报文一样，MSP的报文中也包含了固定的帧头以及末尾的校验和。第三到第六字节包含了一些与本次通信相关的信息，其中type根据官方文档一共有三种，分别是请求、应答和异常：function包含了本次通信的功能信息，以传感器为例，传感器发出的MSP报文的function分为以下几类：MSP2_SENSOR_RANGEFINDER 0x1F01 //距离传感器 MSP2_SENSOR_OPTIC_FLOW 0x1F02 //光流传感器 MSP2_SENSOR_GPS 0x1F03 //GPS MSP2_SENSOR_COMPASS 0x1F04 //指南针 MSP2_SENSOR_BAROMETER 0x1F05 //气压计 MSP2_SENSOR_AIRSPEED 0x1F06 //空速计程序框架INAV飞控MSP处理程序的结构如下所示：mspSerialProcessmspSerialrocessOneportserialReadmspSerialProcessReceivedDatamspSerialProcessReceivedCommand在fc_tasks.c中，串口的处理作为一个任务交给系统调度，其中进行MSP通信处理的是下面的函数，该函数的两个参数分别是非MSP报文的处理方式以及报文处理函数的函数指针（记住，后面要考）：// Allow MSP processing even if in CLI mode mspSerialProcess(ARMING_FLAG(ARMED) ? MSP_SKIP_NON_MSP_DATA : MSP_EVALUATE_NON_MSP_DATA, mspFcProcessCommand);mspSerialProcessvoid mspSerialProcess(mspEvaluateNonMspData_e evaluateNonMspData, mspProcessCommandFnPtr mspProcessCommandFn) { for (uint8_t portIndex = 0; portIndex < MAX_MSP_PORT_COUNT; portIndex++) { mspPort_t * const mspPort = &mspPorts[portIndex]; if (mspPort->port) { mspSerialProcessOnePort(mspPort, evaluateNonMspData, mspProcessCommandFn); } } }该函数按次序将所有可用的msp端口进行处理，可以观察到该函数将上一层的参数都传了进去，在此基础上增加了所用端口的信息。mspSerialrocessOneportvoid mspSerialProcessOnePort(mspPort_t * const mspPort, mspEvaluateNonMspData_e evaluateNonMspData, mspProcessCommandFnPtr mspProcessCommandFn) { mspPostProcessFn…

如何配置使用CLion开发TM4C123的环境
type: item
date: 2022-09-11T19:38:00+08:00
description: 安装CLion1.进入Clion官网下载CLion(如果不知道如何获取证书的可以看底下的附录)2.安装Clion安装openocdopocd下载链接：https://gnutoolchains.com/arm-eabi/openocd/1.解压压缩包2.添加环境变量​ (1)打开设置-系统-系统信息-高级系统设置​ (2)点击环境变量​ ​ (3)向用户变量-path中添加第一步解压出的bin文件夹安装gcc1.打开gcc安装器2.安装​ (1)设置语言​ (2)阅读协议​ (3)设置安装路径​ (4)等待安装完成3.安装完成后勾选添加环境变量选项​ 安装调试器驱动1.下载Stellaris ICDI驱动下载链接：https://www.ti.com.cn/tool/cn/STELLARIS_ICDI_DRIVERS2.打开设备管理器，找到调试器3.安装调试器驱动​ 4.安装完成​ 配置CLion1.打开设置2.选择“嵌入式开发”，设置OpenOCD和Stm32CubeMX的路径3.选择编辑、调试选项4.选择对应的配置文件完成上述操作后，你就可以愉快地使用CLion进行TM4的开发了附：JetBrains全家桶申请方法1.进入JetBrains官网的申请界面https://www.jetbrains.com/shop/eform/students2.填写信息（注意邮箱使用教育邮箱)3.收取验证邮件4.开始白嫖