完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 硬汉Eric2013 于 2015-1-6 15:49 编辑 第4章 Cortex-M处理器的OS特性 本期教程带领大家学习Cortex-M处理器的OS特性,主要是M3和M4,M4和M3反应在RTOS上,主要区别是M4多了一个浮点单元,用户可以根据需要选择是否使用浮点单元。本期教程主要学CM内核的操作模式,特权等级和双堆栈机制。这部分知识学习起来比较的枯燥,但是作为初学者一定要将本期教程认真的多看几遍,有兴趣继续学的,还需上ARM的官网多找些相关的资料进行学习,可以这么说,这部分知识直接关系着你对一款RTOS的认识程度。 4.1 内核的OS特性 4.2寄存器组 4.3操作模式和特权等级 4.4双堆栈机制 4.5内核相关的API函数 4.6总结 4.1 内核的OS特性 为了更好的支持RTOS的运行,Cortex-M系列处理器在这方面做出了很多的努力。当前至少有30多款RTOS支持Cortex-M系列处理器,这个数量还在增加,下面总结了几条ARM公司为了使Cortex-M系列处理器更好的支持RTOS运行所做的努力: l 双堆栈指针:MSP指针用于OS内核和中断,PSP指针用于任务。 l Systick定时器:可以很方便的用于OS的时钟节拍,嘀嗒定时器在第10章:SysTick实验有详细的讲解。 l SVC和PendSV异常:实现RTOS,这两个异常是必须的,特别是任务切换的实现。 l 非特权级执行模式:程序运行在非特权级模式可以限制应用任务的访问权限,特权级模式和非特权级模式配合MPU(Memory Protection Unit 内存保护单元)可以有效的提高系统的鲁棒性。 l 独占式访问:独占式的存储和加载指令对于RTOS中的信号量和互斥操作的实现非常有用。 l 另外,低的中断延迟(可以降低任务切换的开销)和指令的各种特性使得RTOS可以有效的工作。 上面的这些特性对于提高RTOS的性能十分重要,当然,也不是每个RTOS都会用全这些特性,后面会跟大家具体的讲述,本期教程先把部分特性详细的讲解下。 4.2 寄存器组 Cortex-M4/M3处理器拥有R0-R15的寄存器组。其中R13作为堆栈指针SP。SP有两个,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。 4.2.1 通用寄存器 寄存器从R0到R12是通用寄存器。R0-R7被称作lowregisters,由于指令集有限的可用空间,很多的16位指令只能访问这些寄存器。R8-R12被称作high registers,这些寄存器可用于32位指令和部分的16位指令,比如MOV指令。R0-R12的初始值是不定的。 4.2.2 堆栈指针寄存器 Cortex-M3/M4拥有两个堆栈指针,然而它们是banked,因此任一时刻只能使用其中的一个,指针的切换通过特殊寄存器CONTROL实现。当引用R13(或写作SP)时,引用到的是当前正在使用的那一个,另一个必须用特殊的指令来访问(MRS,MSR指令)。这两个堆栈指针分别是: l 主堆栈指针(MSP):这是缺省的堆栈指针,它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。 l 进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服用例程中时)。 说起堆栈指针就必须得说一下堆栈(在前面3.1.2小节有简单讲解和举例,后面会详细展开讨论),堆栈指针用于访问堆栈,并且PUSH指令和POP指令默认使用SP。在Cortex-M3/M4中,有专门的指令负责堆栈操作——PUSH和POP。它俩的汇编语言语法如下例所演示: PUSH {R0} ; *(--R13)=R0。R13是long*的指针 POP {R0} ; R0= *R13++ 请注意后面程序的注释,它诠释了所谓的“向下生长的满栈”(在本期教程后面讲到任务切换时还要展开论述),Cortex-M3/M4就是以这种方式使用堆栈的。因此,在PUSH新数据时,堆栈指针先减一个单元。通常在进入一个子程序后,第一件事就是把寄存器的值先PUSH入堆栈中,在子程序退出前再POP曾经PUSH的那些寄存器。另外,PUSH和POP还能一次操作多个寄存器,如下所示: subroutine_1 PUSH {R0-R7, R12,R14} ; 保存寄存器列表 … ; 执行处理 POP {R0-R7, R12, R14} ; 恢复寄存器列表 BX R14 ; 返回到主调函数 在程序中为了突出重点,可以一直把R13写作SP。在程序代码中,both MSP和PSP都被称为R13/SP。不过,我们可以通过MRS/MSR指令来指名道姓地访问具体的堆栈指针。MSP,亦 写作SP_main,这是复位后缺省使用堆栈指针,服务于操作系统内核和异常服务例程;而PSP,亦写作SP_process,典型地用于普通的用户线程中。 寄存器的PUSH 和POP 操作永远都是4 字节对齐的——也就是说他们的地址必须是0x4,0x8,0xc,……。事实上,R13的最低两位被硬线连接到0,并且总是读出0(Read AsZero)。 这里有两点大家要特别注意: Ø 大多数情况下的应用,只需使用指针MSP,而PSP多用于RTOS中。 Ø 堆栈指针的最低两位永远是0,这意味着堆栈总是4字节对齐的。 4.2.3 连接寄存器 R14也称作Link Register (LR),当调用子程序或者函数时,这个函数用于保存返回地址。函数或者子程序结束时,程序将LR中的地址赋给PC返回到调用函数中。当调用子程序或者函数时,LR中的数值是自动更新的,如果此函数或者子程序嵌套调用其它函数或者子程序,需要保存LR中的数值到堆栈中,要不LR中的数值会由于函数调用而丢失。 在中断服务程序中,LR中的数值自动更新给EXC_RETURN((Exception Return),然后在中断服务程序的末尾触发异常返回。 尽管Cortex-M处理器的返回地址是偶数(bit 0 = 0,因为指令需要半字对齐),LR的BIT 0是可读可写的,一些分支和调用操作需要LR的bit 0置一来表示Thumb状态(这是历史遗留的产物,CM3/CM4只需此位可读写)。 4.2.4 程序计数器 R15是程序计数器,在汇编代码中一般我们都都叫它的外号“PC”。 因为 CM3/CM4内部使用了指令流水线,读PC时返回的值是当前指令的地址+4。比如说: 0x1000: MOV R0, PC ; R0 = 0x1004 如果向PC中写数据,就会引起一次程序的分支(但是不更新LR寄存器)。CM3/CM4中的指令至少是半字对齐的,所以PC的LSB总是读回0。然而,在分支时,无论是直接写PC的值还是使用分支指令,都必须保证加载到PC的数值是奇数(即LSB=1),用以表明这是在Thumb状态下执行。倘若写了0,则视为企图转入ARM模式,CM3将产生一个fault异常。 4.2.5 特殊功能寄存器 Cortex-M3/M4中的特殊功能寄存器包括: l 程序状态寄存器组(PSRs或曰xPSR) l 中断屏蔽寄存器组(PRIMASK, FAULTMASK,以及BASEPRI) l 控制寄存器(CONTROL) 它们只能被专用的MSR/MRS指令访问,而且它们也没有与之相关联的访问地址。 MRS MSR 这三组特殊功能寄存器都比较重要,初学者要有一定的了解。 l 程序状态寄存器组(PSRs或曰xPSR) 程序状态寄存器在其内部又被分为三个子状态寄存器: Ø 应用程序PSR(APSR) Ø 中断号PSR(IPSR) Ø 执行PSR(EPSR) 通过MRS/MSR指令,这3个PSRs即可以单独访问,也可以组合访问(2个组合,3个组合都 可以)。当使用三合一的方式访问时,应使用名字“xPSR”或者“PSR”。下图是三个子状态分别使用的那几位。 l 中断屏蔽寄存器组(PRIMASK, FAULTMASK,以及BASEPRI) 这里特别注意GE(这个是CM4新加的,CM3没有)和中断号PSR(IPSR),后面将SVC异常的时候要用到。 这三个寄存器非常重要,初学者一定要了解,能记住最好。这里建立一个简单的表格,方便大家查看。 要访问PRIMASK, FAULTMASK以及BASEPRI,同样要使用MRS/MSR指令,如: 对于时间-关键任务而言,恰如其分地使用PRIMASK和BASEPRI来暂时关闭一些中断是非常重要的(FreeRTOS中这个两个寄存器都有用到,μCOS-II和μCOS-III中只用到了PRIMASK)。 而FAULTMASK(这个寄存器在当前比较流行的RTOS中还没有用到过)则可以被OS用于暂时关闭fault处理机能,这种处理在某个任务崩溃时可能需要。因为在任务崩溃时,常常伴随着一大堆faults。在系统料理“后事”时,通常不再需要响应这些fault,总之FAULTMASK就是专门留给OS用的。 MRS R0, BASEPRI ;读取BASEPRI到R0中 MRS R0, FAULTMASK ;同上 MRS R0, PRIMASK ;同上 MSR BASEPRI, R0 ;写入R0到BASEPRI中 MSR FAULTMASK, R0 ;同上 MSR PRIMASK, R0 ;同上 只有在特权级下,才允许访问这3个寄存器。 其实,为了快速地开关中断,CM3还专门设置了一条CPS指令,有4种用法(在RTOS中用的比较多) CPSID I ;PRIMASK=1, ;关中断 CPSIE I ;PRIMASK=0, ;开中断 CPSID F ;FAULTMASK=1, ;关异常 CPSIE F ;FAULTMASK=0 ;开异常 l 控制寄存器(CONTROL) 控制寄存器有两个用途,其一用于定义特权级别,其二用于选择当前使用哪个堆栈指针。由两个比特来行使这两个职能。这个寄存器也十分的重要,需认真学习。 CONTROL[1] 在Cortex-M3/M4的handler模式中,CONTROL[1]总是0。在线程模式中则可以为0或1。因此,仅当处于特权级的线程模式下,此位才可写,其它场合下禁止写此位。改变处理器的模式也有其它的方式:在异常返回时,通过修改LR的位2,也能实现模式切换。这是LR在异常返回时的特殊用法,颠覆了对LR的传统使用方式。 前面已经讲到,在进入异常服务程序后,将自动更新LR的值为特殊的EXC_RETURN。EXC_RETURN中高28位全为1的值,只有[3:0]的值有特殊含义,当异常服务例程把这个值送往PC时,就会启动处理器的中断返回序列。因为LR的值是由CM3/CM4自动设置的,所以只要没有特殊需求,就不要改动它。 下面是EXC_RETURN位段详解。 总结一下上面的表,可以得出,合法的EXC_RETURN值共3个 如果主程序在线程模式下运行,并且在使用MSP时被中断,则在服务例程中LR=0xFFFF_FFF9(主程序被打断前的LR已被自动入栈)。 如果主程序在线程模式下运行,并且在使用PSP时被中断,则在服务例程中LR=0xFFFF_FFFD(主程序被打断前的LR已被自动入栈)。 CONTROL[0] 仅当在特权级下操作时才允许写该位。一旦进入了用户级,唯一返回特权级的途径,就是触发一个(软)中断,再由服务例程改写该位。 CONTROL寄存器也是通过MRS和MSR指令来操作的: MRS R0, CONTROL MSR CONTROL, R0 4.2.6 浮点寄存器 Cortex-M4处理器提供了可选的浮点单元用于浮点数据处理,浮点单元还包含一个状态控制寄存器FPSCR,每个32位浮点寄存器S0-S31(S代表single precision单精度)可以通过浮点指令进行访问。也可以每两个为一对,也就是D0-D15(D代表double-precision双精度)进行访问。 4.3 操作模式和特权等级 Cortex-M3/M4支持两个模式(handler模式,也就是中断模式和线程模式)和两个特权等级(特权级和非特权级,这里将其称为用户级):
当处理器处在线程状态下时,既可以使用特权级,也可以使用用户级;另一方面,handler模式 总是特权级的。在复位后,处理器进入线程模式+特权级。 在特权级下的代码可以通过置位CONTROL[0]来进入用户级。而不管是任何原因产生了任何异常,处理器都将以特权级来运行其服务例程,异常返回后,系统将回到产生异常时所处的级别。用户级下的代码不能再试图修改CONTROL[0]来回到特权级。它必须通过一个异常handler,由那个异常handler来修改CONTROL[0],才能在返回到线程模式后拿到特权级。下图是特权级线程模式和用户级线程模式的切换图: 一些简单的应用程序是不需要用户级线程模式的,如下图所示: 把代码按特权级和用户级分开对待(当前流行的RTOS中,RTX支持此功能,用户可根据自己的需要进行配置),有利于使CM3/CM4的架构更加安全和健壮。例如,当某个用户程序代码出问题时,不会让它成为害群之马,因为用户级的代码是禁止写特殊功能寄存器和NVIC中断寄存器的。另外,如果还配有MPU,保护力度就更大,甚至可以阻止用户代码访问不属于它的内存区域。 为了避免系统堆栈因应用程序的错误使用而毁坏,我们可以给应用程序专门配一个堆栈,不让它共享操作系统内核的堆栈。在这个管理制度下,运行在线程模式的用户代码使用PSP,而异常服务例程则使用MSP。这两个堆栈指针的切换是智能全自动的,就在异常服务的始末由硬件处理。 如前所述,特权等级和堆栈指针的选择均由CONTROL负责。当CONTROL[0]=0时,在异常处理的始末,只发生了处理器模式的转换,如下图所示。 但若CONTROL[0]=1(线程模式+用户级),则在中断响应的始末,both 处理器模式和特权等极都要发生变化,如下图所示。 CONTROL[0]只有在特权级下才能访问。用户级的程序如想进入特权级,通常都是使用一条“系统服务呼叫指令(SVC)”来触发“SVC异常”,该异常的服务例程可以视具体情况而修改CONTROL[0]。 关于操作模式和特权级就跟大家说这么多,在讲解RTOS时,再跟大家详细讲解。有了上面的这些知识我们就可以讲解双堆栈机制了。 4.4 双堆栈机制 讲解双堆栈机制之前,大家要先对CM3/CM4对堆栈的处理方式有一个基本的了解。在CM3/CM4中,除了可以使用PUSH和POP指令来处理堆栈外,内核还会在异常处理的始末自动地执行PUSH与POP操作。 4.4.1 堆栈的基本操作 笼统地讲,堆栈是一种存储器的使用模型。它由一块连续的内存和一个栈顶指针组成,用于实现“后进先出”的缓冲区(堆栈是LIFO的模式,和咱们前面讲的按键FIFO和串口FIFO不一样,别搞混了)。其最典型的应用,就是在数据处理前先保存寄存器的值,再在处理任务完成后从中恢复先前保护的这些值。堆栈操作就是对内存的读写操作,但是访问地址由SP给出。寄存器的数据通过PUSH操作存入堆栈,以后用POP操作从堆栈中取回。在PUSH与POP的操作中,SP的值会按堆栈的使用法则自动调整,以保证后续的PUSH不会破坏先前PUSH进去的内容。下面的图片摘自Cortex-M4权威指南,看起来更形象。简单演示了PUSH操作和POP操作过程,堆栈地址由下往上表示从低地址到高地址,也就是所谓的“向下生长的满栈”模型。 Cortex-M3/M4使用的就是“向下生长的满栈”模型(这个要谨记,后面讲RTOS任务切换的时候要用到)。堆栈指针SP指向最后一个被压入堆栈的32位数值。在下一次压栈时,SP先自减4,再存入新的数值。 堆栈的功能就是把寄存器的数据临时备份在内存中,以便将来能恢复之——在一个任务或一段子程序执行完毕后恢复。正常情况下,PUSH与POP必须成对使用,而且参与的寄存器,不论是身份还是先后顺序都必须完全一致。当PUSH/POP指令执行时,SP指针的值也根着自减/自增。 主程序 子程序 ; R0=X, R1=Y,R2=Z BL Fx1 -------------------------------> Fx1 PUSH {R0 } ;把R0存入栈 & 调整SP PUSH {R1} ;把R1存入栈 & 调整SP PUSH {R2} ;把R2存入栈 & 调整SP … ;执行Fx1的功能,中途可以改变R0-R2的值 POP {R2} ;恢复R2早先的值 & 再次调整SP POP {R1} ;恢复R1早先的值 & 再次调整SP POP {R0} ;恢复R0早先的值 & 再次调整SP BX LR ;返回 <------------------------------------------- ;回到主程序 ;R0=X, R1=Y,R2=Z (调用Fx1的前后R0-R2的值完好无损,从寄存器上下文来看,就好像什么都没发生过一样)。 PUSH/POP指令支持一次操作多个寄存器。在前面1.2.2小节有说明。 4.4.2 堆栈空间的分配 Stack_Size EQU 0x00004000 Heap_Size EQU 0x00000400 在main函数中添加malloc函数的调用,要不无法看到heap的使用情况。
首先用MDK全编译工程,打开.map文件: 工程文件生成的map文件信息量很大,涉及到的知识也很多,这里就先说下我们关系的堆栈空间的分配,看之前简易初学者再看看第6章STM32F4XX启动过程详解。 Image Symbol Table Local Symbols Symbol Name Value OvType Size Object(Section) HEAP 0x200021c0 Section 1024 startup_stm32f4xx.o(HEAP) STACK 0x200025c0 Section 16384 startup_stm32f4xx.o(STACK) Global Symbols Symbol Name Value OvType Size Object(Section) __heap_base 0x200021c0 Data 0 startup_stm32f4xx.o(HEAP) __heap_limit 0x200025c0 Data 0 startup_stm32f4xx.o(HEAP) __initial_sp 0x200065c0 Data 0 startup_stm32f4xx.o(STACK) Memory Map of the image Execution Region RW_IRAM1 (Base: 0x20000000,Size: 0x000065c0, Max: 0x00020000, ABSOLUTE) Base Addr Size Type Attr Idx E Section Name Object 0x20000000 0x0000000d Data RW 322 .data bsp_timer.o 0x2000000d 0x00000001 Data RW 423 .data bsp_uart_fifo.o 0x2000000e 0x00000010 Data RW 694 .data stm32f4xx_rcc.o 0x2000001e 0x00000002 PAD 0x20000020 0x00000014 Data RW 1293 .data system_stm32f4xx.o 0x20000034 0x00000004 Data RW 1621 .data mc_w.l(stdout.o) 0x20000038 0x00000004 Data RW 1630 .data mc_w.l(mvars.o) 0x2000003c 0x00000004 Data RW 1631 .data mc_w.l(mvars.o) 0x20000040 0x000000ad Zero RW 174 .bss bsp_key.o 0x200000ed 0x00000003 PAD 0x200000f0 0x00000030 Zero RW 320 .bss bsp_timer.o 0x20000120 0x000020a0 Zero RW 422 .bss bsp_uart_fifo.o 0x200021c0 0x00000400 Zero RW 643 HEAP startup_stm32f4xx.o 0x200025c0 0x00004000 Zero RW 642 STACK startup_stm32f4xx.o 上面的这些信息还不够直接,这里借助Cortex-M3权威指南中的一个截图进行说明。 先解释下这个截图的含义:在离开复位状态后,CM3做的第一件事就是读取下列两个32位整数的值 l 从地址0x0000,0000处取出MSP的初始值(也就是上面的0x20008000)。 l 从地址0x0000,0004处取出PC的初始值——这个值是复位向量,LSB必须是1。然后从这个值所对应的地址处取指。 请注意,这与传统的ARM架构不同——其实也和绝大多数的其它单片机不同。传统的ARM架构总是从0地址开始执行第一条指令。它们的0地址处总是一条跳转指令。在CM3/CM4中,在0地址处提供MSP的初始值,然后紧跟着就是向量表(向量表在以后还可以被移至其它位置) 。向量表中的数值是32位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令。 因为CM3/CM4使用的是向下生长的满栈,所以MSP的初始值必须是堆栈内存的末地址加1。举例来说,如果你的堆栈区域在0x20007C00-0x20007FFF之间,那么MSP的初始值就必须是0x20008000。 向量表跟随在MSP的初始值之后——也就是第2个表目。要注意因为CM3/CM4是在Thumb态下执行,所以向量表中的每个数值都必须把LSB置1(也就是奇数)。正是因为这个原因,上图中使用0x101来表达地址0x100。当0x100处的指令得到执行后,就正式开始了程序的执行。在此之前初始化MSP是必需的,因为可能第1条指令还没来得及执行,就发生了NMI或是其它fault。MSP初始化好后就已经为它们的服务例程准备好了堆栈。 对于不同的开发工具,需要使用不同的格式来设置MSP初值和复位向量。有些则由开发工具自行计算并生成。现在接着上面的.map文件进行说明,这个文件里面有一项是: __initial_sp 0x200065c0 其实__initial_sp就是MSP的初始值,下面这个是Debug状态下的截图,MSP初始值和__initial_sp是一样的,也就是MDK帮我们计算好了MSP初始值。 这里就跟大家讲这么多,后面的教程中继续的完善。 4.4.3 双堆栈机制的实现 我们已经知道了CM3/CM4的堆栈是分为两个:主堆栈和进程堆栈,CONTROL[1]决定如何选择。当CONTROL[1]=0时,只使用MSP,此时用户程序和异常handler共享同一个堆栈。这也是复位后的缺省使用方式。下图是CONTROL[1]=0时,堆栈的使用情况(线程模式和handler都是使用的MSP): 当CONTROL[1]=1时,线程模式将不再使用MSP,而改用PSP(handler模式永远使用MSP)。 这样做的好处在哪里?在使用OS的环境下,只要OS内核仅在handler模式下执行,用户应用程序仅在用户模式下执行,这种双堆栈机制派上了用场——防止用户程序的堆栈错误破坏OS使用的堆栈。(特别注意:此时,进入异常时的自动压栈使用的是进程堆栈,进入异常handler后才自动改为MSP,退出异常时切换回PSP,并且从进程堆栈上弹出数据)。下图是线程模式使用的PSP,handler模式使用的MSP。 在特权级下,可以指定具体的堆栈指针,而不受当前使用堆栈的限制,示例代码如下: MRS R0, MSP ; 读取主堆栈指针到R0 MSR MSP, R0 ; 写R0的值到主堆栈中 MRS R0, PSP ; 读取进程堆栈指针到R0 MSR PSP, R0 ; 写R0的值到进程堆栈中 通过读取PSP的值,OS就能够获取用户应用程序使用的堆栈,进一步地就知道了在发生异常时,被压入寄存器的内容,而且还可以把其它寄存器进一步压栈(使用STMDB和LDMIA的书写形式)。OS还可以修改PSP,用于实现多任务中的任务上下文切换。 这里我们再从Cortex-M3权威指南上获取两个关于双堆栈,操作模式以及特权模式的图片, 第一个截图是线程模式使用主堆栈(注意观察中断嵌套后,堆栈和模式的变化): 第二个截图是线程模式使用线程堆栈(注意观察中断嵌套后,堆栈和模式的变化,在嵌套时,更深层ISR所看到的LR总是0xFFFF_FFF1): 4.5 内核相关的API函数 要访问上面提到的寄存器,可以通过ARM官方提供的CMSIS软件包,里面有个文件core_cmdFunc.h,相关API函数就在里面,提供了三个版本的函数支持:MDK,IAR,GCC。这里将MDK版本的函数摘录出来,方便大家查看。
4.6 总结 对于初学者一下子掌握这么多的东西比较的困难,这里总结一下主要的几点,一般应用记住这几点即可,需要的时候再查阅这个知识点相关的内容: l 系统复位后默认使用的是MSP,复位后的状态是特权级线程状态,在这个状态下是允许修改寄存器的。进入到用户特权以后就不能修改这些寄存器了。 l 用户特权的情况(也就是用户建立的非中断服务程序)下可以使用MSP或PSP,特权模式(中断服务程序)只能使用MSP。 l 还有很重要的一条就是。假如在用户模式下使用的是PSP,那么寄存器的数值被保存到任务堆栈的空间,进入中断程序后就开始使用MSP,如果还有一个高优先级的中断难么就继续的使用MSP,在程序推出最后一级中断的时候就用用户堆栈恢复寄存器。 l Cortex-M4的堆栈模型是向下生长的满栈。 下期教程会做两个相关的例子帮大家深入的理解CM4内核方面的知识。 参考资料: 1. Patterns fortime-triggered embedded systems英文版和中文版 2. Cortex-M3权威指南中文版 3. TheDefinitive Guide to Arm Cortex-M3 and Cortex-M4 Processors(M4权威指南)
|
||
相关推荐
|
||
本帖最后由 硬汉Eric2013 于 2015-1-7 11:06 编辑
这篇教程只贴了一个代码,这个代码是ARM官方CMSIS库里面的,英文注释都有的,换成中文注释只是画蛇添足。 这个就好比你用ST的固件库,固件库里面都是英文的,你会把他改成中文吗,英文注释已经很完善。 如果您读了我写的这篇文章,就不会有此疑问。 |
|
|
|
|
|
本帖最后由 硬汉Eric2013 于 2015-1-7 11:22 编辑 这个帖子我们已经尽可能的与实践联系起来的,可能是您没有看文章内容。专门配套实例的在其余章节,教程中都配有详细注释和说明 |
|
|
|
|
|
672 浏览 0 评论
2954 浏览 0 评论
如何使用python调起UDE STK5.2进行下载自动化下载呢?
2344 浏览 0 评论
开启全新AI时代 智能嵌入式系统快速发展——“第六届国产嵌入式操作系统威廉希尔官方网站 与产业发展论坛”圆满结束
2810 浏览 0 评论
获奖公布!2024 RT-Thread全球巡回线下培训火热来袭!报名提问有奖!
30369 浏览 11 评论
72510 浏览 21 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-15 10:31 , Processed in 0.703034 second(s), Total 70, Slave 52 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号