AVR单片机之Bootloader技术详解

发布者:朱颜素韵最新更新时间:2016-10-24 来源: eefocus关键字:AVR单片机  Bootloader 手机看文章 扫描二维码
随时随地手机看文章
ATmega128具备引导加载支持的用户程序自编程功能(In-System Programming by On-chipBoot Program),它提供了一个真正的由MCU本身自动下载和更新(采用读/写同时"Read-While-Write"进行的方式)程序代码的系统程序 自编程更新的机制。利用AVR的这个功能,可以实现在应用编程(IAP)以及实现系统程序的远程自动更新的应用。 
IAP的本质就是,MCU可以灵活地运行一个常驻Flash的引导加载程序(Boot Loader Program),实现对用户应用程序的在线自编程更新。引导加载程序的设计可以使用任何的可用的数据接口和相关的协议读取代码,或者从程序存储器中读取 代码,然后将代码写入(编程)到Flash存储器中。 

引导加载程序有能力读写整个Flash存储器,包括引导加载程序所在的引导加载区本身。引导加载程序还可以对自身进行更新修改,甚至可以将自身删除,使系 统的自编程能力消失。引导加载程序区的大小可以由芯片的熔丝位设置,该段程序区还提供两组锁定位,以便用户选择对该段程序区的不同级别的保护。 

本节将给出一个实际的的Boot Loader程序,它可以配合Windows中的超级终端程序,采用Xmodem传输协议,通过RS232接口下载更新用户的应用程序。 

5.2.1 基本设计思想 
1. Boot Loader程序的设计要点 
Boot Loader程序的设计是实现IAP的关键,它必须能过通过一个通信接口,采用某种协议正确的接收数据,再将完整的数据写入到用户程序区中。本例Boot Loader程序的设计要点有: 
(1)采用ATmega128的USART口实现与PC之间的简易RS232三线通信; 
(2) 采用Xmodem通信协议完成与PC机之间的数据交换; 
(3)用户程序更新完成后自动转入用户程序执行; 
(4) Boot Loader程序采用C语言内嵌AVR汇编方式编写,阅读理解方便,可移植性强,代码小于1K字。 
2. Xmodem通信协议 
Xmodem协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运输协议。这种协议以128字节块的形式传输数据,并且每个块都使用一个 校验和过程来进行错误检测。如果接收方关于一个块的校验和与它在发送方的校验和相同时,接收方就向发送方发送一个认可字节。为了便于读者阅读程序,下面简 要说明该协议的主要特点,有关Xmoden的完整的协议请参考其它相关的资料。 
(1) Xmodem的控制字符: 01H、 04H、 06H、 15H、 18H、 1AH。 
(2) Xmodem传输数据块格式:" 
   个字节的数据块...> "。其中为起始字节;
为数据块编号字节,每次加一;是前一字节的反码;接下来是长度为128字节的数据块;最后的是128字节数据的CRC校验码,长度为2个字节。 
(3)接收端收到一个数据块并校验正确时,回送;接收错误回送;而回送表示要发送端停止发送。 
(4) 发送端收到后,可继续发送下一个数据块(packNO+1);而收到则可再次重发上一个数据块。 
(5)发送端发送表示全部数据发送完成。如果最后需要发送的数据不足128个字节,用填满一个数据块。 
(6) 控制字符"C"有特殊的作用,当发送端收到"C"控制字符时,它回重新开始以CRC校验方式发送数据块(packNO = 1)。 
(7) 每发送一个新的数据块
加1,加到OxFF后下一个数据块的
为零。 
(8) 校验方式采用16位CRC校验(X^16 + X^12 + X^5 + 1)。 
5.2.2 源程序代码 
下面给出的源程序是在ICCAVR中实现的。 
/***************************************************** 
采用串行接口实现Boot_load应用的实例 
华东师大电子系 马 潮 2004.07 
Compiler: ICC-AVR 6.31 
Target: Mega128 
Crystal: 16Mhz 
Used:        T/C0,USART0 
*****************************************************/ 
#include  
#define SPM_PAGESIZE 256              //M128的一个Flash页为256字节(128字) 
#define BAUD 38400             //波特率采用38400bps 
#define CRYSTAL 16000000          //系统时钟16MHz 
//计算和定义M128的波特率设置参数 
#define BAUD_SETTING (unsigned char)((unsigned long)CRYSTAL/(16*(unsigned long)BAUD)-1) 
#define BAUD_H (unsigned char)(BAUD_SETTING>>8) 
#define BAUD_L (unsigned char)BAUD_SETTING 
#define DATA_BUFFER_SIZE SPM_PAGESIZE        //定义接收缓冲区长度 
//定义Xmoden控制字符 
#define XMODEM_NUL 0x00 
#define XMODEM_SOH 0x01 
#define XMODEM_STX 0x02 
#define XMODEM_EOT 0x04 
#define XMODEM_ACK 0x06 
#define XMODEM_NAK 0x15 
#define XMODEM_CAN 0x18 
#define XMODEM_EOF 0x1A 
#define XMODEM_RECIEVING_WAIT_CHAR 'C' 
//定义全局变量 
const char startupString[]="Type 'd' download, Others run app.\n\r\0"; 
char data[DATA_BUFFER_SIZE]; 
long address = 0; 
//擦除(code=0x03)和写入(code=0x05)一个Flash页 
void boot_page_ew(long p_address,char code) 

asm("mov r30,r16\n" 
       "mov r31,r17\n" 
       "out 0x3b,r18\n");          //将页地址放入Z寄存器和RAMPZ的Bit0中 
SPMCSR = code;             //寄存器SPMCSR中为操作码 
asm("spm\n");                    //对指定Flash页进行操作 
}       
//填充Flash缓冲页中的一个字 
void boot_page_fill(unsigned int address,int data) 

asm("mov r30,r16\n" 
       "mov r31,r17\n"          //Z寄存器中为填冲页内地址 
       "mov r0,r18\n" 
       "mov r1,r19\n");          //R0R1中为一个指令字 
SPMCSR = 0x01; 
asm("spm\n"); 

//等待一个Flash页的写完成 
void wait_page_rw_ok(void) 

   while(SPMCSR & 0x40) 
    { 
      while(SPMCSR & 0x01); 
      SPMCSR = 0x11; 
      asm("spm\n"); 
    } 

//更新一个Flash页的完整处理 
void write_one_page(void) 

int i; 
boot_page_ew(address,0x03);                    //擦除一个Flash页 
wait_page_rw_ok();                         //等待擦除完成 
for(i=0;i//将数据填入Flash缓冲页中 

       boot_page_fill(i, data+(data[i+1]//将缓冲页数据写入一个Flash页 
wait_page_rw_ok();                         //等待写入完成 
}       
//从RS232发送一个字节 
void uart_putchar(char c) 

while(!(UCSR0A & 0x20)); 
UDR0 = c; 

//从RS232接收一个字节 
int uart_getchar(void) 

unsigned char status,res; 
if(!(UCSR0A & 0x80)) return -1;        //no data to be received 
status = UCSR0A; 
res = UDR0; 
if (status & 0x1c) return -1;        // If error, return -1 
return res; 

//等待从RS232接收一个有效的字节 
char uart_waitchar(void) 

int c; 
while((c=uart_getchar())==-1); 
return (char)c; 

//计算CRC 
int calcrc(char *ptr, int count) 

int crc = 0; 
char i; 
   
while (--count >= 0) 

       crc = crc ^ (int) *ptr++ //退出Bootloader程序,从0x0000处执行应用程序 
void quit(void) 

   uart_putchar('O');uart_putchar('K'); 
uart_putchar(0x0d);uart_putchar(0x0a); 
    while(!(UCSR0A & 0x20));          //等待结束提示信息回送完成 
    MCUCR = 0x01; 
    MCUCR = 0x00;                    //将中断向量表迁移到应用程序区头部 
    RAMPZ = 0x00;                    //RAMPZ清零初始化 
    asm("jmp 0x0000\n");        //跳转到Flash的0x0000处,执行用户的应用程序 

//主程序 
void main(void) 

int i = 0; 
unsigned char timercount = 0; 
unsigned char packNO = 1; 
int bufferPoint = 0; 
unsigned int crc; 
//初始化M128的USART0 
UBRR0H = BAUD_H;    
UBRR0L = BAUD_L;          //Set baud rate 
UCSR0B = 0x18;          //Enable Receiver and Transmitter 
UCSR0C = 0x0E;          //Set frame. format: 8data, 2stop bit 
//初始化M128的T/C0,15ms自动重载 
   OCR0 = 0xEA; 
   TCCR0 = 0x0F;    
//向PC机发送开始提示信息 
while(startupString!='\0') 

       uart_putchar(startupString); 
       i++; 

//3秒种等待PC下发"d",否则退出Bootloader程序,从0x0000处执行应用程序 
while(1) 

       if(uart_getchar()== 'd') break; 
       if (TIFR & 0x02)                   //timer0 over flow 
       { 
            if (++timercount > 200) quit();    //200*15ms = 3s 
         TIFR = TIFR|0x02; 
       } 

//每秒向PC机发送一个控制字符"C",等待控制字〈soh〉 
while(uart_getchar()!=XMODEM_SOH)        //receive the start of Xmodem 

      if(TIFR & 0x02)              //timer0 over flow 
       { 
         if(++timercount > 67)                //wait about 1 second 
         { 
            uart_putchar(XMODEM_RECIEVING_WAIT_CHAR); //send a "C" 
            timercount="0"; 
         } 
         TIFR="TIFR" | 0x02; 
       } 

//开始接收数据块 
do 

       if ((packNO == uart_waitchar()) && (packNO ==(~uart_waitchar()))) 
       { //核对数据块编号正确 
         for(i=0;i//接收128个字节数据 
         { 
            data[bufferPoint]= uart_waitchar(); 
            bufferPoint++;    
         } 
         crc = (uart_waitchar()//接收2个字节的CRC效验字 
         if(calcrc(&data[bufferPoint-128],128)==crc) //CRC校验验证 
         { //正确接收128个字节数据 
            while(bufferPoint >= SPM_PAGESIZE) 
            { //正确接受256个字节的数据 
                   write_one_page();       //收到256字节写入一页Flash中 
                   address += SPM_PAGESIZE; //Flash页加1 
                   bufferPoint = 0; 
            }    
            uart_putchar(XMODEM_ACK);    //正确收到一个数据块 
            packNO++;                   //数据块编号加1 
         } 
         else 
         { 
            uart_putchar(XMODEM_NAK);     //要求重发数据块 
         } 
       } 
       else 
       { 
         uart_putchar(XMODEM_NAK);           //要求重发数据块 
       } 
}while(uart_waitchar()!=XMODEM_EOT);       //循环接收,直到全部发完 
uart_putchar(XMODEM_ACK);                    //通知PC机全部收到 
   
if(bufferPoint) write_one_page();        //把剩余的数据写入Flash中 
quit();             //退出Bootloader程序,从0x0000处执行应用程序

程序的主体部分采用C高级编写,结构性好,程序的相应部分都给出了比较详细的注释说明,读者非常容易读懂和理解。下面再对程序做进一步的说明。 
(1) 函数"void   write_one_page(void)" 实现了对ATmega128一个Flash页的完整编程处理。当程序从串口正确接收到256个字节后,(ATmega128一个Flash页为128个 字),便调用该函数将其写入ATmega128一个Flash页中。函数先将一个指定的Flash页进行擦除;然后将数据填入Flash的缓冲页中,最后 将Flash 缓冲页的数据写入到该指定的Flash页中(详细技术细节见第二章相关内容的介绍)。 
(2) 一个Flash页的擦除、写入,以及填充Flash缓冲页的函数采用内嵌AVR汇编完成,在ICCAVR中,寄存器R16、R17、R18、R19用于传递一个C函数的第1、2个参数(int类型)或第1个乘数(long类型),具体参考ICCAVR应用说明。 
(3) 函数"void quit(void)"的用途是退出Bootloader程序,从Flash的0x0000处执行用户的应用程序。在执行强行跳转指令"jmp 0x0000"前,对寄存器MCUCR的操作是将中断向量地址迁移回应用程序区的头部,因为在ICCAVR环境中编译Bootloader程序时,其自动 把中断向量地址迁移到了Bootloader区的头部。为了保证能正确执行用户的程序,在跳转前需要把中断向量地址迁再移回应用程序区的头部。 
(4)在这段Bootloader程序中使用的硬件资源为T/C0和USART0,用户在编写其应用程序时,应首先对这两个硬件资源相关的寄存器重新做初始化。 
(5) Bootloader程序占具并住留在Flash的最高1K字空间内,因此实际的应用程序空间为63K字(126K字节),所以用户编写的应用程序不得超 出126K字节。同时应将ATmega128的熔丝位BLB12、BLB11的状态设置为"00",禁止SPM和LPM指令对Bootloader区的读 写操作,已确保Bootloader程序不被改写和擦除。 
5.2.3 IAP的实现与应用 
1.   Bootloader程序的编译与下载 
首先在ICCAVR中新建一个工程项目,并按照生成Bootloader程序代码的要求进行正确的设置。打开Project -> Options的Compiler Options设置选项窗口,见图5.1: 
(1) 在Device Configration栏中选定器件ATMega128; 
(2) 选定Use RAMPZ/ELPM项(ATMega128的Flash > 64K字节); 
(3) Program Type选定为Boot Loader; 
(4)Boot Size选择1K Words。 
正确设置好编译选项后输入C的源代码,然后编译生成.HEX的下载代码程序。 
在下载HEX文件前还要对ATmega128芯片的熔丝位进行正确的配置: 
(1) 配置M103C熔丝位,使芯片工作于ATmega128方式; 
(2) 配置BOOTSZ1和BOOTSZ0熔丝位,设定BOOTLOADER区的大小为1024个字,起始首地址为0xFC00;
(3)配置BOOTRST熔丝位,设定芯片上电起动从BOOTLOADER区的起始地址处开始,即每次RESET复位后从0xFC00处执行Bootloader程序; 
(4)下载Bootloader程序的HEX文件; 
(5) 配置LB2和LB1熔丝位,加密程序; 
(6)配置BLB12和BLB11熔丝位,对BOOTLOADER区进行安全锁定。 
特别注意的是,以上对芯片熔丝位的配置以及Bootloader程序的下载,需要由ISP、或JTAG、或并行方式实现,既要实现IAP,首先还需要使用一次非IAP的编程方式来建立IAP的应用环境。 
2. IAP应用 
当你按照上面的方法将Bootloader程序下载完成后,就可以使用它来下载你的应用程序了。具体操作如下。 
(1) 编写你的应用程序,编译生成HEX文件; 
(2)使用HEX2BIN.EXE转换程序,将HEX文件转换成BIN文件; 
(3)使用普通的RS232电缆将PC机的串口与ATmega128的串口连接; 
(4)打开WINDOWS中的超级终端软件,正确设置COM口的参数:38400,1,8,无,2,无(使用2位停止位提高通信可靠性); 
(5)ATmega128上电,在PC超级终端收到"Type 'd' download, Others run app."的Bootloader程序启动的提示详细; 
(6)3秒钟内在PC上按下"d"键,通知Bootloader程序转入接收数据并更新应用程序的处理。3秒钟内没有按"d"键,PC超级终端收 到"OK"提示,Bootloader程序退出,自动转入执行芯片内原有的用户应用程序(如果有的话,否则再次启动Bootloader程序); 
(7)当PC超级终端收到"C"(一秒钟一个),说明Bootloader程序转入接收数据和更新应用程序的处理流程,正在等待PC下发数据; 
(8)在PC超级终端上的工具栏中选择"传送->发送文件",在发送文件窗口选择协议"Xmodem",文件栏中选定要下载应用程序的BIN文件,单击发送按钮; 
(9)   此时出现文件发送窗口,显示文件发送的过程和进度,以及是否出错; 
(10)当文件全部正确发送完成后,PC超级终端收到"OK"提示,Bootloader程序退出,自动转入执行刚更新的用户应用程序。 
在ATmega128中烧入这样一个Bootloader程序,建立了IAP后,最基本的开发AVR的环境就简化成"PC+RS232电缆+目标板"。读 者在掌握了Bootloader程序编写的原理后,可以编写自己的Bootloader程序,实现系统程序的自动远程网络更新等应用。 
AVR的BOOTLOADER功能同其它一些芯片不同,它的BOOTLOADER程序没有固化(固定)在芯片内部(出厂为空),而是需要由用户设计实现 (实际上,你第一次下载BOOTLOADER程序还必须使用其它的方式编程,如ISP、JTAG等),因此对一般的用户掌握起来有一定的困难,不如一些其 它芯片的固化IAP使用方便。但对高手来讲,可以根据实际需要编写高级、高效、专用的BOOTLOADER程序,如从一个U盘读取数据,更新用户的应用程 序;编写一个时间炸弹,或对用户的密码进行验证,10次不对则将系统程序销毁等等。简单意味着使用方便,但灵活和适应性差,而灵活性需要你具备更高的能力 去驾驭它。可能会有一天,在单片机的系统上也出现了"病毒"程序,其原因就是使用了固化的BOOTLOADER程序。由于固化(固定)的程序采用规定公开 (开放)的接口,那么用一个带"病毒"的应用程序更新原来的应用程序也就轻而易举了。
关键字:AVR单片机  Bootloader 引用地址:AVR单片机之Bootloader技术详解

上一篇:AVR单片机ISP(在系统编程)及熔丝补救方法(DebugWIRE)
下一篇:单片机(AVR)串口接收和发送

推荐阅读最新更新时间:2024-03-16 15:17

基于S3C2410开发板的Bootloader运行原理与实现
在专用的嵌入式开发板上运行操作系统(如Linux)已经变得越来越流行,而Bootloader就是为引导操作系统内核运行的一段代码。通过它可以初始化硬件设备、建立或检测内存空间的映射,其功能有点类似于PC机的BIOS(基本输入输出系统)程序。它的主要作用是为运行操作系统提供基本的运行环境,并操作系统的内核装载到存储器(RAM)中的合适位置上去运行。本文将以Samsung公司的S3C2410开发板为开发平台,具体阐述了Bootloader的运行原理与实现分析。 Bootloader程序与CPU芯片的内核结构、具体芯片和使用的操作系统等因素有着密切关系,因此要为所有类型的嵌入式开发板建立一个通用的Bootloader几乎是不可能的。
[单片机]
基于S3C2410开发板的<font color='red'>Bootloader</font>运行原理与实现
ARM学习之S3C2440的bootloader代码分析(1)
;=========================================== ; NAME: OPTION.A ; DESC: Configuration options for .S files ; HISTORY: ; 02.28.2002: ver 0.0 ; 03.11.2003: ver 0.0 attached for 2440. ; jan E, 2004: ver0.03 modified for 2440A01. ;=========================================== ;这个Option.inc文件主要是为设置时钟服务的,选择好分频系数 ;Start address
[单片机]
nRF24L01p+AVR单片机ATmage88射频收发程序
单片机源程序如下: #define _nRF24L01_C_ #include nRF24L01.h INT8U CE_Status = 0; /* ================================================================================ Function : L01_GetCEStatus( ) Description : Get the status of the CE PIN Input : NONE Output: 1:CE=1, 0:CE=0 ===================================================
[单片机]
基于AVR单片机的高压防护系统的研究
目前,我国铁路上运行的电力机车所使用的电源,是接触网提供的25kV单相交流电。在电气化铁路区段运行和整备作业的机车,经常需要作业人员通过人孔盖登上车顶,对机车进行检查维修工作。通常在上车顶工作前,作业人员应当按照登顶作业程序,在地面对作业网断电,再打开车顶人孔盖登顶作业。 然而,因为作业人员疏忽大意,未认真执行接触网断电的操作程序,在接触网上仍带电的情况下,就盲目登车顶工作,多次造成被电击伤亡的事故。针对这一情况,我们研制了机车车顶人孔盖安全报警联运接地装置,可以为登顶作业的工作人员提供可靠、有效的人身安全保护,确保登顶作业人员的人身安全。 本文将介绍一套可靠检测接触网有无高电压的告警装置和一套受告警装置控制的联运接地装置。
[单片机]
AVR单片机中断实现 ATmega16 INT ISR(INT0_vect)
2.21实例功能 前面例子中分别介绍了按键控制发光二极管的亮灭,但是我们注意到,在程序中需要一直检测按键的状态,这样明显的浪费了单片机的资源,降低了单片机的工作效率,。那么有没有一种方法可以让单片机不用一直检测按键的状态,而只在有按键动作时才去响应呢? 当然有!单片机中除了具有基本输入输出功能的作用外,还有专门检测外界信号并作出响应的中断系统。在本例中,通过利用外部中断实现单片机对按键事件的响应和处理。 本例中三个功能模块描述如下: ● 单片机系统:对按键事件产生的中断时间作出响应,并在数码管上显示按键按下的次数。 ● 外围电路:通过将按键连接到单片机的外部中断检测端口,实现中断产生电路,数码管显示电路用于指示
[单片机]
基于AVR单片机在采暖炉控制系统中的应用
1. 引言 单片机在工业控制领域应用时不同于民用、商用领域中的应用,工业控制所处的环境相对比较恶劣,干扰源多,其常见干扰源来自现场工业电气在投入、运行、切断等工况下产生的静电感应、尖峰电压、浪涌电流等干扰。实践表明,在工作室中按用户要求设计的小型工业采暖控制系统,尽管各项逻辑功能及技术指标的测试都正常,但该系统拿到现场上却不能使用,检测失灵,操作失控,显示花屏等现象接踵而来。经分析,其干扰是从现场不同路径传入单片机控制系统的。切断干扰源,提高单片机抗干扰能力是解决控制系统正常工作的前提。 2 抗干扰措施 2.1 测温信号的抗干扰 测温电路采用的是单总线芯片DS18B20,该芯片具有测温精度高,连接线路简单等
[单片机]
基于<font color='red'>AVR单片机</font>在采暖炉控制系统中的应用
AVR单片机Atmega128外扩RAM
由于AVR系列单片机采用的是内部外部RAM统一编址,ATmega128工作在非ATmega 103模式时具有4k+256B的包括寄存器文件(通用工作寄存器)、I/O寄存器、扩展I/O寄存器和内部SRAM的连续内部存储空间。所以在扩展外部RAM时,和内部SRAM地址重叠的外部RAM地址是不能直接访问的。也就是说扩展的外部RAM每64k要浪费掉内部SRAM那么大的空间(AT90系列如此)。所幸的是mega系列解决了这一缺点,专门有一个寄存器XMCRB用来解决对与内部SRAM地址空间相同地址的外部RAM访问。其低三位XMM2 、XMM1 、XMM0三位的设置,决定高位地址线PC口的哪些口线被释放为普通I/O,而不是作为高位地址。这
[单片机]
<font color='red'>AVR单片机</font>Atmega128外扩RAM
汽车电子ECU bootloader工作原理及开发要点有哪些?
MCU内部集成的逻辑功能外设随着半导体技术的不断进步(按照摩尔定律),变得越来越多,存储器也越来越大。消费者对于汽车节能(经济和法规对排放的要求)型、舒适性、互联性、安全性(功能安全和信息安全)的要求越来越高,特别是近年来新能源电动车、车联网和自动驾驶技术的兴起,更大大加速了汽车电子技术的发展。汽车电子ECU(Electronic Control Unit--电控单元)集成的功能日益复杂,为了应对软件远程(在线)功能升级(增加新的功能)和bug修复的需求、对bootLoader(启动加载程序)的需求越来越多。本文详细介绍了汽车电子ECU bootloader的一般性工作原理和开发要点,其适用于所有的汽车电子ECU bootload
[嵌入式]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 学习ARM开发(16)
    ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采 ...
  • 学习ARM开发(17)
    因为嵌入式系统里全部要使用中断的,那么我的S3C44B0怎么样中断流程呢?那我就需要了解整个流程了。要深入了解,最好的方法,就是去写程序 ...
  • 学习ARM开发(18)
    上一次已经了解ARM的中断处理过程,并且可以设置中断函数,那么它这样就可以工作了吗?答案是否定的。因为S3C44B0还有好几个寄存器是控制中 ...
  • 嵌入式系统调试仿真工具
    嵌入式硬件系统设计出来后就要进行调试,不管是硬件调试还是软件调试或者程序固化,都需要用到调试仿真工具。 随着处理器新品种、新 ...
  • 最近困扰在心中的一个小疑问终于解惑了~~
    最近在驱动方面一直在概念上不能很好的理解 有时候结合别人写的一点usb的例子能有点感觉,但是因为arm体系里面没有像单片机那样直接讲解引脚 ...
  • 学习ARM开发(1)
  • 学习ARM开发(2)
  • 学习ARM开发(4)
  • 学习ARM开发(6)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
更多每日新闻
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved