嵌入式

基本功能

在本设计中,数据的处理可以使用PC机的MATLAB等功能强大的软件,但是这类现有的数据处理软件并不能对特有的数据采集系统的下位机采集模块进行直接控制,因此需要针对特定的数据采集系统编写对应的上位机软件,上位机软件是针对上述目的而设计与编写的,是整个采集系统的控制前端和数据存储及处理中心。控制功能主要包括控制下位机采集的开始与终止,采集的频率等,数据处理功能主要包括绘制波形图,将数据显示于列表,将数据存储于文件,其中将数据存储于文件将便于使用现有的数据处理软件对数据进行一些数值算法处理,以达到科学研究,结论验证等目的。

开发环境

C++程序设计语言可以很好地实现面向对象的编程思想,采用C++编写上位机程序,可以将每一个功能模块封装成一个类,修改某个类的实现,增加类的功能不会影响整个程序的框架,这样就很容易维护和扩展功能;加之我们要实现的软件功能中需要调用大量的windows API函数库,所以采用VC++6.0作为上位机的开发环境。

程序功能模块划分

总的功能模块主要包括三个模块,即HID设备读写模块,数据采集模块,数据处理模块。

基于PIC的数据采集系统---上位机设计

HID设备的查找与读写

(1)枚举

USB主机在检测到USB设备插入后,就要对设备进行枚举了。枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序。

(2)HID

人机接口设备(HID)是指直接和人进行互动的设备,如鼠标、键盘等. 在Windows 中,具有相似属性和提供相似服务的设备被归为一种设备类型,一种类型的设备可以使用一个通用的设备驱动程序. 在运行Windows 98 或更高版本的PC 机上,应用程序可以使用操作系统内置的HID 类驱动程序与HID 通信. 这样使得符合HID 类的USB 设备很容易开发与运行.

(3)HID设备的查找

在Windows操作系统中内置很多与HID有关的API函数,调用这些函数,就可以开始对指定的HID设备进行查找,查找HID设备的最终目的是获得该设备的路径名,设备的存取容量等信息,为以后对该设备进行读写做好准备。

基于PIC的数据采集系统---上位机设计

(4)HID设备的读写

在取得了HID设备的路径全面后,即可开始对HID设备进行读写,对设备的读写也是通过调用相应的函数来实现的。

控制下位机进行数据采集

上位机向下位机发送命令,控制下位机进行数据采集,并从下位机获取数据,在这个过程中,要处理好两个线程的同步的问题,即数据采集线程和数据处理线程能够协调工作,保正系统能正确稳定的工作。具体的解决方法是实现对某些数据访问的原子操作,即一个线程在对公共数据进行访问时,另一个线程不能打扰,直到操作线程操作完成,放弃对数据的使用权,另一个线程才能够访问数据。

下位机获取了关于采集的有关参数后,即可开始采集,每隔一定时间采集一个数据,当采集数据数目达到限制值个数后,本次采集完成,此时下位机才开始将采集数据发送给上位机。

基于PIC的数据采集系统---上位机设计

上位机对采集的数据的处理

上位机在将数据采集命令发送给下位机后,所要做的就是等待下位机采集完成并接收数据,因此上位机将循环查询下位机工作状态,一旦检测到下位机采集结束的标志,上位机就开始对数据进行处理。

数据处理分为三种:

(1)绘制波形图

绘制波形图的要求有两点:第一是不能频繁闪烁,影响观察;二是波形图是动态的,因为绘制区域有限,而所采集的数据是源源不断增加的,因此要求波形图能够动态的更新。

(2)添加到列表显示

可直观地查看目前所采集的所有数据。

(3)保存到文件

运用功能强大的数据处理软件对数据进行更深的处理。

界面显示

采集单极性正弦波工作界面

基于PIC的数据采集系统---上位机设计

代码

转自:markmin214

围观 354

单片机的特点:

(1) 受集成度限制,片内存储器容量较小,一般内ROM:8KB以下;
(2) 内RAM:256KB以内。
(3) 可靠性高
(4) 易扩展
(5) 控制功能强
(6) 易于开发

ARM的特点:

(1)自带廉价的程序存储器(FLASH)和非易失的数据存储器(EEPROM)。这些存储器可多次电擦写,使程序开发实验更加方便,工作更可靠。

(2)高速度,低功耗。在和M51单片机外接相同晶振条件下,AVR单片机的工作速度是M51单片机的30-40倍;并且增加了休眠功能及CMOS技术,使其功耗远低于M51单片机。

(3) 工业级产品。具有大电流输出可直接驱动SSR和继电器,有看门狗定时器,防止程序走飞,从而提高了产品的抗干扰能力。

(4) 超功能精简指令,具有32个通用工作寄存器,相当于M51单片机中32个累加器!从而克服了单一累加器工作的瓶颈效应。

(5)程序下载方便。AVR单片机即可并行下载也可串行下载,无需昂贵的编程器。此外,还可以在线下载!也就是说可以直接在电路板上进行程序修改和烧录。

(6) 具有模拟比较器、脉宽调制器、模数转换功能。使得工业控制中的模拟信号处理更为简单方便。

(7) 并行口、定时计数器、中断系统等单片机内部重要资源的功能进行了大幅度提升,使之更适合工业生产过程的实时控制。

(8) 其时钟频率既可外接也可使用单片机内部自带的振荡器,其频率可在1MHz-8MHz内设置,使得硬件开发制作更为简洁。

(9)强大的通讯功能,内置了同步串行接口SPI、通用串行接口UAST、两线串行总线接口TWI(I2C ),使网络控制、数据传送更为方便。

(10)超级保密功能,应用程序可采用多重保护锁功能。可低价快速完成厂家产品商品化等等。 除上述特点外“零外设”也是AVR嵌入式单片机的重要特征。由于该芯片已内置了程序存储器、晶振并增加了在线汇编功能。

所以AVR单片机芯片接上直流电源,下载个程序就可以独立工作。无需附加外部设备,无需使用昂贵的编程器和仿真装置。这给我们学习和开发带来了便利条件。

FPGA的特点:

(1)采用FPGA设计ASIC电路(专用集成电路),用户不需要投片生产,就能得到合用的芯片。 
 
(2)FPGA可做其它全定制或半定制ASIC电路的中试样片。  

(3)FPGA内部有丰富的触发器和I/O引脚。  

(4)FPGA是ASIC电路中设计周期最短、开发费用最低、风险最小的器件之一。 

(5)FPGA采用高速CMOS工艺,功耗低,可以与CMOS、TTL电平兼容。  

可以说,FPGA芯片是小批量系统提高系统集成度、可靠性的最佳选择之一。 
 
FPGA是由存放在片内RAM中的程序来设置其工作状态的,因此,工作时需要对片内的RAM进行编程。用户可以根据不同的配置模式,采用不同的编程方式。  

加电时,FPGA芯片将EPROM中数据读入片内编程RAM中,配置完成后,FPGA进入工作状态。掉电后,FPGA恢复成白片,内部逻辑关系消失,因此,FPGA能够反复使用。FPGA的编程无须专用的FPGA编程器,只须用通用的EPROM、PROM编程器即可。当需要修改FPGA功能时,只需换一片EPROM即可。这样,同一片FPGA,不同的编程数据,可以产生不同的电路功能。因此,FPGA的使用非常灵活。

嵌入式系统的特点:

(1)系统内核小
由于嵌入式系统一般是应用于小型电子装置的,系统资源相对有限,所以内核较之传统的操作系统要小得多。比如Enea公司的OSE分布式系统,内核只有5K,而Windows的内核?简直没有可比性。  

(2)专用性强
嵌入式系统的个性化很强,其中的软件系统和硬件的结合非常 紧密,一般要针对硬件进行系统的移植,即使在同一品牌、同一系列的产品中也 需要根据系统硬件的变化和增减不断进行修改。同时针对不同的任务,往往需要 对系统进行较大更改,程序的编译下载要和系统相结合,这种修改和通用软件的 “升级”是完全两个概念。  

(3)系统精简
嵌入式系统一般没有系统软件和应用软件的明显区分,不要求 其功能设计及实现上过于复杂,这样一方面利于控制系统成本,同时也利于实现系统安全。 

(4)高实时性的系统软件(OS)是嵌入式软件的基本要求。而且软件要求固态存储,以提高速度;软件代码要求高质量和高可靠性。  

(5)嵌入式软件开发要想走向标准化,就必须使用多任务的操作系统
嵌入式系统的应用程序可以没有操作系统直接在芯片上运行;但是为了合理地调度多任 务、利用系统资源、系统函数以及和专家库函数接口,用户必须自行选配RTOS (Real-Time Operating System)开发平台,这样才能保证程序执行的实时性、 可靠性,并减少开发时间,保障软件质量。  

(6)嵌入式系统开发需要开发工具和环境。
由于其本身不具备自举开发能力, 即使设计完成以后用户通常也是不能对其中的程序功能进行修改的,必须有一套 开发工具和环境才能进行开发,这些工具和环境一般是基于通用计算机上的软硬 件设备以及各种逻辑分析仪、混合信号示波器等。开发时往往有主机和目标机的 概念,主机用于程序的开发,目标机作为最后的执行机,开发时需要交替结合进行。

转自:小丑l

围观 219

在uCOS-II实时内核下,对外设的访问接口没有统一完善,有很多工作需要用户自己去完成。串口通信是单片机测控系统的重要组成部分,异步串行口是一个比较简单又很具代表性的中断驱动外设。本文以单片机中的串口为例,介绍uCOS—II下编写中断服务程序以及外设驱动程序的一般思路。

1、uCOS-II的中断处理及51系列单片机中断系统分析

uCOS-II中断服务程序(ISR)一般用汇编语言编写。以下是中断服务程序的步骤。

保存全部CPU寄存器;调用OSIntEnter()或OSIntNesting(全局变量)直接加1; 执行用户代码做中断服务; 调用OSIntExit(); 恢复所有CPU寄存器; 执行中断返回指令。

uCOS-II提供两个ISR与内核接口函数;OSIntEnter()和OSIntExit()。OSIntEnter()通知uCOS-II核,中断服务程序开始了。事实上,此函数做的工作是把一个全局变量OSIntNesting加1,此中断嵌套计数器可以确保所有中断处理完成后再做任务调度。另一个接口函数OSIntExit()则通知内核,中断服务已结束。根据相应情况,退回被中断点(可能是一个任务或者是被嵌套的中断服务程序)或由内核作任务调度。

用户编写的ISR必须被安装到某一位置,以便中断发生后,CPU根据相应的中断号运行准确的服务程序。许多实时操作系统都提供了安装和卸载中断服务程序的 API接口函数,但uCOS-II内核没有提供类似的接口函数,需要用户在对CPU的移植中自己实现。这些接口函数与具体的硬件环境有关,接下来以51单片机下的中断处理对此详细说明。

51单片机的中断基本过程如下:CPU在每个机器周期的S5P2时刻采样中断标志,而在下一指令周期将对采样的中断进行查询。如果有中断请求,则按照优先级高低的原则进行处理。响应中断时,先置相应的优先级激活触发器于相应位,封锁同级或低级中断,然后根据中断源类别,在硬件控制下,将中断地址压入堆栈,并转向相应的中断向量入口单元。通常在入口单元处放一跳转指令,转向执行中断服务程序.当执行中断返回指令RETI时,把响应中断时所置位的优先级激活触发器清零后,从堆栈中弹出被保护的断点地址,装入程序计数器PC,CPU返回原来被中断处继续执行程序。

在移植的过程中,采用Keil C51作为编译环境。Keil C5l集成C编译和汇编器。中断子程序用汇编语言编写,放到移植uCOS-II后的OS_CPU_A.ASM汇编文件中。下面是以串行口中断为例的移植中断服务子程序代码。

CSEGAT0023H ;串口中断响应入口地址

LJMPSerialISR;转移到串口中断子程序入口地址

RSEG?PR?SeriallSR?OS_CPU_A

SerialISR:

USINGO

CLR EA ;先关中断,以防中断嵌套

PUSHALL ;已定义的压栈宏,用于将CPU寄存器的值压入堆栈

LCALL_?OSIntEnter ;监视中断嵌套

LCALL_?Serial ;串口中断服务程序

LCALL_?OSintExlt

SETBEA

POPALL;已定义的出栈宏,将CPU寄存器的值出栈

RETI

2、串口驱动程序

笔者已在5l单片机上成功移植了uCOS-II内核,移植过程在此不再讨论。这里重点分析uC0S—II内核下串口驱动程序编写。

由于串行设备存在外设处理速度和CPU速度不匹配的问题,所以需要一个缓冲区.向串口发送数据时,只要把数据写到缓冲区中,然后由串口逐个取出往外发。从串口接收数据时,往往等收到若干个字节后才需要CPU进行处理,所以这些预收的数据可以先存于缓冲区中。实际上,单片机的异步串口中只有两个相互独立、地址相同的接收、发送缓冲寄存器SBUF。在实际应用中,需要从内存中开辟两个缓冲区,分别为接收缓冲区和发送缓冲区。这里把缓冲区定义为环形队列的数据结构。

uCOS-II内核提供了信号量作为通信和同步的机制,引入数据接收信号量、数据发送信号量分别对缓冲区两端的操作进行同步。串口的操作模式如下:用户任务想写,但缓冲区满时,在信号量上睡眠,让CPU运行别的任务,待ISR从缓冲区读走数据后唤醒此睡眠的任务;同样,用户任务想读,但缓冲区空时,也可以在信号量上睡眠,待外部设备有数据来了再唤醒。由于uCOS-II的信号量提供了超时等待机制,串口当然也具有超时读写能力。

图1是带缓冲区和信号量的串口接收示意图。数据接收信号量初始化为0,表示在环形缓冲区中无数据。

uCOS-II的嵌入式串口通信模块设计

接收中断到来后,ISR从UART的接收缓冲器SBUF中读入接收的字节(②),放入接收缓冲区(③),然后通过接收信号量唤醒用户任务端的读操作(④、 ①)。在整个过程中,可以查询记录缓冲区中当前字节数的变量值,此变量表明接收缓冲区是否已满。UART收到数据并触发了接收中断,但如果此时缓冲区是满的,那么放弃收到的字符。缓冲区的大小应合理设置,降低数据丢失的可能性,又要避免存储空间的浪费。
uCOS-II的嵌入式串口通信模块设计

图2为带环形缓冲区和超时信号量的串口发送示意图。发送信号量初始值设为发送缓冲区的大小,表示缓冲区已空,并且关闭发送中断。发送数据时,用户任务在信号量上等待(①)。如果发送缓冲区未满,用户任务向发送缓冲区中写入数据(②)。如果写入的是发送缓冲区中的第一个字节,则允许发送中断(②)。然后,发送ISR从发送缓冲区中取出最早写入的字节输出至UART(④),这个操作又触发了下一次的发送中断,如此循环直到发送缓冲区中最后一个字节被取走,重新关闭发送中断。在ISR向UART输出的同时,给信号量发信号(⑤),发送任务据此信号量计数值来了解发送缓冲区中是否有空间。

3、串口通信模块的设计

每个串行端口有两个环状队列缓冲区,同时有两个信号量:一个用来指示接收字节,另一个用来指示发送字节。每个环状缓冲区有以下四个要素:

存储数据(INT8U数组); 包含环状缓冲区字节数的计数器; 环状缓冲区中指向将被放置的下一字节的指针; 环状缓冲区中指向被取出的下一字节的指针。

uCOS-II的嵌入式串口通信模块设计

图3是接收数据软件模块的流程图。SerialGetehar()用来获取接收到的数据,如果缓冲区已空时将任务挂起,接收到字节时,任务将被唤醒,同时从串行口接收字节。SerialPutRxChar()用来将接收的字节放到缓冲区中,如果接收缓冲区已满,则该字节被丢弃。当字节插入到缓冲区中,SerialPutRxChar()通知数据接收信号量,使之将数据己到的消息传达给所有等待的任务。为防止挂起应用任务,可以通过调用 SceiallsEmPty()去发现环状队列中是否有字节。
uCOS-II的嵌入式串口通信模块设计

图4是发送数据模块的流程图。当需要发送数据给串行端口时,SerialPurChar()等待信号量在初始化发送信号量时应该初始为缓冲区的大小。因此,当缓冲区中没有更多空间时,SerialPutChar()就挂起任务,只要UART再次发送字节,挂起任务就将恢复。 SerialGctChar()被中断服务程序调用,如果发送缓冲区至少还有一个字节,Seri-a1GetChar()就返回一个从缓冲区发送的字节。如果缓冲区己空,则SerialGetChar()返回Null,这将使调用停止进一步的发送中断,一直到有数据发送为止。

4、异步串行通信的接口函数

应用任务可以通过如下的几个函数来控制和访问UART:SerialCfgPort()、SerialGetChar()、SerialInit()、SerialIsEmpty()、SerialIsFull()和SerialPutChar()。

SerialCfgPort()用于建立串行端口的特征,在为指定端口调用其他服务前,必须先调用该函数,包括确定波特率、比特数、奇偶校验和停止位等。

SerialGetChar()使应用程序从接收数据的环状缓冲区中取出数据。

SerialInit()用于初始化整个串口软件模块,且必须在该模块提供的其他任何服务前调用。SeriallInit()将环状缓冲区计数器的字节数清零,并初始化每个环状缓冲区的IN和OUT指针,指向数据存储区的开始处。数据接收信号量初始化为0,表示在环状缓冲区无数据。用传送缓冲区大小初始化数据传送信号量,表示缓冲区已空。

SerialIsEmpty()允许应用程序确定是否有字节从串口接收进来。本函数允许在无数据时避免将任务挂起。

SerialIsFull()允许应用程序确定传送环状缓冲区的状态,本函数可以在缓冲区已满时避免将任务挂起。

SerialPutChar()允许应用程序向一个串行端口发送数据。

5、结语

该串口通信模块充分利用了实时内核的任务调度功能和信号量机制,系统软件模块化,可读性增强,便于修改和移植,其设计思路和方法可以很好的应用在多种情况下的测控系统中,系统的扩展方便,具有一定的借鉴作用。该串口通信模块已作为某铁路供水远程控制终端的一部分,运行稳定,提高了整个系统的运行效率和实时性。

转自:畅学电子网

围观 554

嵌入式Linux系统移植主要由四大部分组成:
  •   搭建交叉开发环境;
  •   bootloader的选择和移植;
  •   kernel的配置、编译、和移植;
  •   根文件系统的制作;

第一部分:搭建交叉开发环境

先介绍第一分部的内容:搭建交叉开发环境,首先必须得思考两个问题,什么是交叉环境? 为什么需要搭建交叉环境?

先回答第一个问题,在嵌入式开发中,交叉开发是很重要的一个概念,开发的第一个环节就是搭建环境,第一步不能完成,后面的步骤从无谈起,这里所说的交叉开发环境主要指的是:在开发主机上(通常是我的pc机)开发出能够在目标机(通常是我们的开发板)上运行的程序。嵌入式比较特殊的是不能在目标机上开发程序(狭义上来说),因为对于一个原始的开发板,在没有任何程序的情况下它根本都跑不起来,为了让它能够跑起来,我们还必须要借助pc机进行烧录程序等相关工作,开发板才能跑起来,这里的pc机就是我们说的开发主机,想想如果没有开发主机,我们的目标机基本上就是无法开发,这也就是电子行业的一句名言:搞电子,说白了,就是玩电脑!

然后回答第二个问题,为什么需要交叉开发环境?主要原因有以下几点:

原因1:嵌入式系统的硬件资源有很多限制,比如cpu主频相对较低,内存容量较小等,想想让几百MHZ主频的MCU去编译一个Linux kernel会让我们等的不耐烦,相对来说,pc机的速度更快,硬件资源更加丰富,因此利用pc机进行开发会提高开发效率。

原因2:嵌入式系统MCU体系结构和指令集不同,因此需要安装交叉编译工具进行编译,这样编译的目标程序才能够在相应的平台上比如:ARM、MIPS、POWEPC上正常运行。

交叉开发环境的硬件组成主要由以下几大部分:

1.开发主机
2.目标机(开发板)
3.二者的链接介质,常用的主要有3中方式:
    (1)串口线
    (2)USB线
    (3)网线

对应的硬件介质,还必须要有相应的软件“介质”支持:

1.对于串口,通常用的有串口调试助手,putty工具等,工具很多,功能都差不多,会用一两款就可以;

2.对于USB线,当然必须要有USB的驱动才可以,一般芯片公司会提供,比如对于三星的芯片,USB下载主要由DNW软件来完成;

3.对于网线,则必须要有网络协议支持才可以,常用的服务主要两个

  •   tftp服务:主要用于实现文件的下载,比如开发调试的过程中,主要用tftp把要测试的bootloader、kernel和文件系统直接下载到内存中运行,而不需要预先烧录到Flash芯片中,一方面,在测试的过程中,往往需要频繁的下载,如果每次把这些要测试的文件都烧录到Flash中然后再运行也可以,但是缺点是:过程比较麻烦,而且Flash的擦写次数是由限的;另外一方面:测试的目的就是把这些目标文件加载到内存中直接运行就可以了,而tftp就刚好能够实现这样的功能,因此,更没有必要把这些文件都烧录到Flash中去

  •   nfs服务:主要用于实现网络文件的挂载,实际上是实现网络文件的共享,在开发的过程中,通常在系统移植的最后一步会制作文件系统,那么这是可以把制作好的文件系统放置在我们开发主机PC的相应位置,开发板通过nfs服务进行挂载,从而测试我们制作的文件系统是否正确,在整个过程中并不需要把文件系统烧录到Flash中去,而且挂载是自动进行挂载的,bootload启动后,kernel运行起来后会根据我们设置的启动参数进行自动挂载,因此,对于开发测试来讲,这种方式非常的方便,能够提高开发效率。

另外,还有一个名字叫samba的服务也比较重要,主要用于文件的共享,这里说的共享和nfs的文件共享不是同一个概念,nfs的共享是实现网络文件的共享,而samba实现的是开发主机上Windows主机和Linux虚拟机之间的文件共享,是一种跨平台的文件共享,方便的实现文件的传输。

以上这几种开发的工具在嵌入式开发中是必备的工具,对于嵌入式开发的效率提高做出了伟大的贡献,因此,要对这几个工具熟练使用,这样你的开发效率会提高很多。等测试完成以后,就会把相应的目标文件烧录到Flash中去,也就是等发布产品的时候才做的事情,因此对于开发人员来说,所有的工作永远是测试。

通过前面的工作,我们已经准备好了交叉开发环境的硬件部分和一部分软件,最后还缺少交叉编译器,读者可能会有疑问,为什么要用交叉编译器?前面已经讲过,交叉开发环境必然会用到交叉编译工具,通俗地讲就是在一种平台上编译出能运行在体系结构不同的另一种平台上的程序,开发主机PC平台(X86 CPU)上编译出能运行在以ARM为内核的CPU平台上的程序,编译得到的程序在X86 CPU平台上是不能运行的,必须放到ARM CPU平台上才能运行,虽然两个平台用的都是Linux系统。相对于交叉编译,平常做的编译叫本地编译,也就是在当前平台编译,编译得到的程序也是在本地执行。用来编译这种跨平台程序的编译器就叫交叉编译器,相对来说,用来做本地编译的工具就叫本地编译器。所以要生成在目标机上运行的程序,必须要用交叉编译工具链来完成。

这里又有一个问题,不就是一个交叉编译工具吗?为什么又叫交叉工具链呢?原因很简单,程序不能光编译一下就可以运行,还得进行汇编和链接等过程,同时还需要进行调试,对于一个很大工程,还需要进行工程管理等等,所以,这里 说的交叉编译工具是一个由编译器、连接器和解释器组成的综合开发环境,交叉编译工具链主要由binutils(主要包括汇编程序as和链接程序ld)、gcc(为GNU系统提供C编译器)和glibc(一些基本的C函数和其他函数的定义) 3个部分组成。有时为了减小libc库的大小,也可以用别的 c 库来代替 glibc,例如 uClibc、dietlibc 和 newlib。

那么,如何得到一个交叉工具链呢?是从网上下载一个“程序”然后安装就可以使用了吗?回答这个问题之前先思考这样一个问题,我们的交叉工具链顾名思义就是在PC机上编译出能够在我们目标开发平台比如ARM上运行的程序,这里就又有一个问题了,我们的ARM处理器型号非常多,难道有专门针对我们某一款的交叉工具链吗?若果有的话,可以想一想,这么多处理器平台,每个平台专门定制一个交叉工具链放在网络上,然后供大家去下载,想想可能需要找很久才能找到适合你的编译器,显然这种做法不太合理,且浪费资源!因此,要得到一个交叉工具链,就像我们移植一个Linux内核一样,我们只关心我们需要的东西,编译我们需要的东西在我们的平台上运行,不需要的东西我们不选择不编译,所以,交叉工具链的制作方法和系统移植有着很多相似的地方,也就是说,交叉开发工具是一个支持很多平台的工具集的集合(类似于Linux源码),然后我们只需从这些工具集中找出跟我们平台相关的工具就行了,那么如何才能找到跟我们的平台相关的工具,这就是涉及到一个如何制作交叉工具链的问题了。

通常构建交叉工具链有如下三种方法:

方法一 :分步编译和安装交叉编译工具链所需要的库和源代码,最终生成交叉编译工具链。该方法相对比较困难,适合想深入学习构建交叉工具链的读者。如果只是想使用交叉工具链,建议使用下列的方法二构建交叉工具链。

方法二: 通过Crosstool-ng脚本工具来实现一次编译,生成交叉编译工具链,该方法相对于方法一要简单许多,并且出错的机会也非常少,建议大多数情况下使用该方法构建交叉编译工具链。

方法三 :直接通过网上下载已经制作好的交叉编译工具链。该方法的优点不用多说,当然是简单省事,但与此同时该方法有一定的弊端就是局限性太大,因为毕竟是别人构建好的,也就是固定的,没有灵活性,所以构建所用的库以及编译器的版本也许并不适合你要编译的程序,同时也许会在使用时出现许多莫名其妙的错误,建议读者慎用此方法。

crosstool-ng是一个脚本工具,可以制作出适合不同平台的交叉编译工具链,在进行制作之前要安装一下软件:

$ sudo apt-get install g++ libncurses5-dev bison flex texinfo automake libtool patch gcj cvs cvsd gawk

crosstool脚本工具可以在http://ymorin.is-a-geek.org/projects/crosstool下载到本地,然后解压,接下来就是进行安装配置了,这个配置优点类似内核的配置。

主要的过程有以下几点:

  1. 设定源码包路径和交叉编译器的安装路径
  2. 修改交叉编译器针对的构架
  3. 增加编译时的并行进程数,以增加运行效率,加快编译,因为这个编译会比较慢。
  4. 关闭JAVA编译器 ,减少编译时间
  5. 编译
  6. 添加环境变量
  7. 刷新环境变量。
  8. 测试交叉工具链

到此,嵌入式Linux系统移植四大部分的第一部分工作全部完成,接下来可以进行后续的开发了。

第二部分:bootloader的选择和移植

一、Boot Loader 概念

就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境,他就是所谓的引导加载程序(Boot Loader)。

嵌入式Linux系统移植的四大步骤
【图1】Flash存储中存放文件的分布图

二、为什么系统移植之前要先移植BootLoader?

BootLoader的任务是引导操作系统,所谓引导操作系统,就是启动内核,让内核运行就是把内核加载到内存RAM中去运行,那先问两个问题:第一个问题,是谁把内核搬到内存中去运行?第二个问题:我们说的内存是SDRAM,大家都知道,这种内存和SRAM不同,最大的不同就是SRAM只要系统上电就可以运行,而SDRAM需要软件进行初始化才能运行,那么在把内核搬运到内存运行之前必须要先初始化内存吧,那么内存是由谁来初始化的呢?其实这两件事情都是由bootloader来干的,目的是为内核的运行准备好软硬件环境,没有bootloadr我们的系统当然不能跑起来。

三、bootloader的分类。

首先更正一个错误的说法,很多人说bootloader就是U-boot,这种说法是错误的,确切来说是u-boot是bootloader的一种。也就是说bootloader具有很多种类,大概的分类如下图所示:

嵌入式Linux系统移植的四大步骤
【图2】bootloader分类图

由上图可以看出,不同的bootloader具有不同的使用范围,其中最令人瞩目的就是有一个叫U-Boot的bootloader,是一个通用的引导程序,而且同时支持X86、ARM和PowerPC等多种处理器架构。U-Boot,全称 Universal Boot Loader,是遵循GPL条款的开放源码项目,是由德国DENX小组开发的用于多种嵌入式CPU的bootloader程序,对于Linux的开发,德国的u-boot做出了巨大的贡献,而且是开源的。

u-boot具有以下特点:

① 开放源码;
② 支持多种嵌入式操作系统内核,如Linux、NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS;
③ 支持多个处理器系列,如PowerPC、ARM、x86、MIPS、XScale;
④ 较高的可靠性和稳定性;
⑤ 高度灵活的功能设置,适合U-Boot调试、操作系统不同引导要求、产品发布等;
⑥ 丰富的设备驱动源码,如串口、以太网、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、键盘等;
⑦ 较为丰富的开发调试文档与强大的网络技术支持;

其实,把u-boot可以理解为是一个小型的操作系统。

四、u-boot的目录结构

* board 目标板相关文件,主要包含SDRAM、FLASH驱动;
* common 独立于处理器体系结构的通用代码,如内存大小探测与故障检测;
* cpu 与处理器相关的文件。如mpc8xx子目录下含串口、网口、LCD驱动及中断初始化等文件;
* driver 通用设备驱动,如CFI FLASH驱动(目前对INTEL FLASH支持较好)
* doc U-Boot的说明文档;
* examples可在U-Boot下运行的示例程序;如hello_world.c,timer.c;
* include U-Boot头文件;尤其configs子目录下与目标板相关的配置头文件是移植过程中经常要修改的文件;
* lib_xxx 处理器体系相关的文件,如lib_ppc, lib_arm目录分别包含与PowerPC、ARM体系结构相关的文件;
* net 与网络功能相关的文件目录,如bootp,nfs,tftp;
* post 上电自检文件目录。尚有待于进一步完善;
* rtc RTC驱动程序;
* tools 用于创建U-Boot S-RECORD和BIN镜像文件的工具;

五、u-boot的工作模式

U-Boot的工作模式有启动加载模式和下载模式。启动加载模式是Bootloader的正常工作模式,嵌入式产品发布时,Bootloader必须工作在这种模式下,Bootloader将嵌入式操作系统从FLASH中加载到SDRAM中运行,整个过程是自动的。下载模式就是Bootloader通过某些通信手段将内核映像或根文件系统映像等从PC机中下载到目标板的SDRAM中运行,用户可以利用Bootloader提供的一些令接口来完成自己想要的操作,这种模式主要用于测试和开发。

六、u-boot的启动过程

大多数BootLoader都分为stage1和stage2两大部分,U-boot也不例外。依赖于cpu体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。

1、 stage1(start.s代码结构)

U-boot的stage1代码通常放在start.s文件中,它用汇编语言写成,其主要代码部分如下:

(1) 定义入口。由于一个可执行的image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在rom(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
(2)设置异常向量(exception vector)。
(3)设置CPU的速度、时钟频率及中断控制寄存器。
(4)初始化内存控制器 。
(5)将rom中的程序复制到ram中。
(6)初始化堆栈 。
(7)转到ram中执行,该工作可使用指令ldrpc来完成。

2、 stage2(C语言代码部分)

lib_arm/board.c中的start armboot是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数主要完成如下操作:

(1)调用一系列的初始化函数。
(2)初始化flash设备。
(3)初始化系统内存分配函数。
(4)如果目标系统拥有nand设备,则初始化nand设备。
(5)如果目标系统有显示设备,则初始化该类设备。
(6)初始化相关网络设备,填写ip,c地址等。
(7)进入命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。

七、基于cortex-a8的s5pc100bootloader启动过程分析

s5pc100支持两种启动方式,分别为USB启动方式和NandFlash启动方式:

1. S5PC100 USB启动过程

[1] A8 reset, 执行iROM中的程序
[2] iROM中的程序根据S5PC100的配置管脚(SW1开关4,拨到4对面),判断从哪里启动(USB)
[3] iROM中的程序会初始化USB,然后等待PC机下载程序
[4] 利用DNW程序,从PC机下载SDRAM的初始化程序到iRAM中运行,初始化SDRAM
[5] SDRAM初始化完毕,iROM中的程序继续接管A8, 然后等待PC下载程序(BootLoader)
[6] PC利用DNW下载BootLoader到SDRAM
[7] 在SDRAM中运行BootLoader

2. S5PC100 Nandflash启动过程

[1] A8 reset, 执行IROM中的程序
[2] iROM中的程序根据S5PC100的配置管脚(SW1开关4,拨到靠4那边),判断从哪里启动(Nandflash)
[3] iROM中的程序驱动Nandflash
[4] iROM中的程序会拷贝Nandflash前16k到iRAM
[5] 前16k的程序(BootLoader前半部分)初始化SDRAM,然后拷贝完整的BootLoader到SDRAM并运行
[6] BootLoader拷贝内核到SDRAM,并运行它
[7] 内核运行起来后,挂载rootfs,并且运行系统初始化脚本

八、u-boot移植(基于cortex_a8的s5pc100为例)

1.建立自己的平台

(1).下载源码包2010.03版本,比较稳定
(2).解压后添加我们自己的平台信息,以smdkc100为参考版,移植自己s5pc100的开发板
(3).修改相应目录的文件名,和相应目录的Makefile,指定交叉工具链。
(4).编译
(5).针对我们的平台进行相应的移植,主要包括修改SDRAM的运行地址,从0x20000000
(6).“开关”相应的宏定义
(7).添加Nand和网卡的驱动代码
(8).优化go命令
(9).重新编译 make distclean(彻底删除中间文件和配置文件) make s5pc100_config(配置我们的开发板) make(编译出我们的u-boot.bin镜像文件)
(10).设置环境变量,即启动参数,把编译好的u-boot下载到内存中运行,过程如下:

1. 配置开发板网络

ip地址配置:
$setenv ipaddr 192.168.0.6 配置ip地址到内存的环境变量
$saveenv 保存环境变量的值到nandflash的参数区

网络测试:
在开发开发板上ping虚拟机:
$ ping 192.168.0.157(虚拟机的ip地址)

如果网络测试失败,从下面几个方面检查网络:
1. 网线连接好
2. 开发板和虚拟机的ip地址是否配置在同一个网段
3. 虚拟机网络一定要采用桥接(VM--Setting-->option)
4. 连接开发板时,虚拟机需要设置成静态ip地址

2. 在开发板上,配置tftp服务器(虚拟机)的ip地址
$setenv serverip 192.168.0.157(虚拟机的ip地址)
$saveenv

3. 拷贝u-boot.bin到/tftpboot(虚拟机上的目录)

4. 通过tftp下载u-boot.bin到开发板内存
$ tftp 20008000(内存地址即可) u-boot.bin(要下载的文件名)

如果上面的命令无法正常下载:
1. serverip配置是否正确
2. tftp服务启动失败,重启tftp服务
#sudo service tftpd-hpa restart

5. 烧写u-boot.bin到nandflash的0地址
$nand erase 0(起始地址) 40000(大小) 擦出nandflash 0 - 256k的区域
$nand write 20008000((缓存u-boot.bin的内存地址) 0(nandflash上u-boot的位置) 40000(烧写大小)

6. 切换开发板的启动方式到nandflash
1. 关闭开发板
2. 把SW1的开关4拨到4的那边
3. 启动开发板,它就从nandflash启动

第三部分:kernel的配置、编译、和移植

一、将下载好的linux-2.6.35.tar.bz2拷贝到主目录下解压

二、修改顶层目录下的Makefile,主要修改平台的体系架构和交叉编译器,代码如下:

ARCH ?= $(SUBARCH)
CROSS_COMPILE ?=
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
修改以上代码为:
ARCH ?= arm ---- > 体系架构是arm架构
CROSS_COMPILE ?= arm-cortex_a8-linux-gnueabi- ---- > 交叉编译器是arm-cortex_a8平台的

注意:这两个变量值会直接影响顶层Makefile的编译行为,即选择编译哪些代码,用什么编译器进行编译。

三、拷贝标准版配置文件,目的是得到跟我们开发板相关的配置信息。

$ cp arch/arm/configs/s5pc100_defconfig .config

这里拷贝arch/arm/configs/s5pc100_defconfig到 .config文件是选取跟我们开发板相关的代码。因为Linux支持的平台非常非常多,不仅仅是ARM处理器,当然我们编译的时候只需要编译跟我们平台相关的代码就可以了,平台相关的不需要编译,那么就有个问题,Linux系统中的源代码文件有一万多以个,面对这么庞大的文件数量,我们如何去选择呢?

其实,我们担心的问题也是写操作系统的那哥们早就担心过的问题了,只不过人家已经把这个问题帮我们解决了,我们只需进行很简单的操作,就可以选择出我们要编译的代码,具体的方法就是把相应平台的_deconfig直接拷贝到顶层目录的.config文件中,这样.config文件中就记录了我们要移植平台的平台信息,因为在配置内核时,系统会把所有的配置信息都保存在顶层目录的.config文件中。注意在第一次,进行make menuconfig时,系统会根据我们选取的平台信息自动选取相关的代码和模块,因此我们只需要进入然后再退出,选择保存配置信息就行了,系统会把这些跟我们移植平台相关的所有配置信息全部保存在顶层目录的.config文件中。

四、配置内核

$make menuconfig

注意:第一次进去,不做任何操作,直接推出,在推出时提示是否保存配置信息,一定要保存配置信息,点击“YES”。这样我们的.config中就已经保存了我们开发平台的信息。

在这个环节,我们需要关心一个问题,make menuconfig时,系统到低都做了哪些事情?为什么会出现图形化的界面?图形化的界面中的相关内容是从哪里来的?

图形化的界面当然是由一个特殊的图形库来实现的,还记得第一次make menuconfig时,系统并没有出现图形化的界面,而是报错了,并且提示我们缺少 ncurses-devel ,此时只需要按照要求安装一个libncurses5-dev就行了,sudo apt-get install libncurses5-dev,有了这个图形化库的支持,我们才能够正常显示图形化界面。

好了,图形化界面的问题解决了,那还有另外一个问题就是图形化界面里面的内容是从哪里来的?要回答这个问题,我们就要提一下Linux内核的设计思想了,Linux 内核是以模块的方式来组织这个操作系统的,那么,为什么要用模块的方式来组织呢?模块的概念又是什么呢?在此来一一回答这个问题。

Linux2.6内核的源码树目录下一般都会有两个文件:Kconfig和Makefile。分布在各目录下的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文件相关的内核配置菜单。每个目录都会存放功能相对独立的信息,在每个目录中会存放各个不同的模块信息,比如在/dev/char/目录下就存放了所有字符设备的驱动程序,而这些程序代码在内核中是以模块的形式存在的,也就是说当系统需要这个驱动的时候,会把这个驱动以模块的方式编译到系统的内核中,编译分为静态编译和动态编译,静态编译内核体积比动态编译的体积要大,前面已经说了每个目录下面都会有一个Kconfig的文件,我们还会问,这个文件中都存放了什么信息?前面说了,每个目录的Kconfig文件描述了所属目录源文件相关的内核配置菜单,有其特殊的语法格式,图形化界面的文字正是从这个文件中读取出来的,如果把这个文件中的相应目录文件的信息全部删除,那么在图形化界面中将看不到该模块的信息,因此也不能进行模块的配置。

在内核配置make menuconfig(或xconfig等)时,系统会自动从Kconfig中读出配置菜单,用户配置完后保存到.config(在顶层目录下生成)中。在内核编译时,主Makefile调用这个.config,(.config的重要性就体现在,它保存了我们的所有的配置信息,是我们选取源代码并且进行编译源代码的最终依据!!!)就知道了用户对内核的配置情况。上面的内容说明:Kconfig就是对应着内核的配置菜单。假如要想添加新的驱动到内核的源码中,可以通过修改Kconfig来增加对我们驱动的配置菜单,这样就有途径选择我们的驱动,假如想使这个驱动被编译,还要修改该驱动所在目录下的Makefile。因此,一般添加新的驱动时需要修改的文件有两种,即:Kconfig 和相应目录的Makefile(注意不只是两个),系统移植的重要内容就是给内核添加和删除相应的模块,因此主要修改的内核文件就是Kconfig 和相应目录的Makefile这两个文件。

五、编译内核

$make zImage

通过上述操作我们能够在 arch/arm/boot 目录下生成一个 zImage 文件,这就是经过压缩的内核镜像。

内核的编译过程是非常复杂的,注意这里的编译是静态编译,此时会执行顶层目录下的Makefile中的zImage命令,在执行的过程中,会根据当前目录的.config文件去选择编译源代码。编译内核的具体步骤比较复杂,有时间会另写文章详细描述。

六、通过tftp网络服务下载测试内核

setenv bootcmd tftp 20008000(内存地址) zImage\;go 20008000

setenv bootargs nfs nfsroot=192.168.1.199(虚拟机的ip):/source/rootfs ip=192.168.1.200(开发板的ip) init=/linuxrc(第一个要启动的用户进程) ttySAC0,115200(设置中断为串口1,波特率为:115200)

保存环境变量,复位开发板,测试是否能够正常启动(注意:在此之前应设置好需要nfs挂载的文件系统,最后才能看到效果).内核测试和启动过程也是比较复杂的,在后续的文章中会详细介绍。

第四部分:根文件系统的介绍

由本文的第一张图:Flash存储中存放文件的分布图可知,文件系统的制作和移植是系统移植的最后一道工序了,在这里首先要提几个问题:
1.什么是文件系统?
2.如何实现文件系统?
3.常用的文件系统有哪些?为什么需要这些文件系统?

下面来一一回答这些问题:

文件系统我们在日常生活中则很少听说,但是它确实存在,只是名字不叫文件系统罢了,一般叫资料库。资料库里面的文件众多,我们如何快速准确的找到我们要的那份文件呢?资料库采用了分类索引的方法来实现快速查找。类似于我们学校图书馆的管理方式,一楼可能是哲学类,二楼是社科类的,三楼是电子类的,四楼是计算机类的…………等等,我们把这种进行了分类索引的资料库叫文件系统。

对于计算机而言,文件其实就是资料数据,只能存储在物理介质上面,比如:硬盘,但是我们人不可能自己读取物理介质上的文件,或者自己把文件写入物理介质,物理介质上文件的读写只能采用程序来完成,为了方便实现,程序又被分成了物理介质驱动程序、内容存储程序和文件内容存储程序。物理介质驱动程序专门用于从物理介质上存取数据;内容存储程序用于把文件内容和文件属性信息打包;文件内容存储程序用于把用户输入形成文件内容,或者取得文件内容显示出来。
我们可以把一个文件系统(倒树)分解成多个文件系统(倒树)分别存放到存储介质上,比如:一个存储到光盘里,一个存储到硬盘中,在使用时,我们把光盘里的文件系统的根目录挂到硬盘文件系统的一个目录下面,这样访问这个目录就相当于是访问光盘的根目录了,找到了根目录,我们也就可以访问整个光盘上的文件系统了。

“在Linux系统中一切皆是文件”这句话是我们学习Linux系统的时候常常听到的一句话。虽然有些夸张,但是它揭示了文件系统对于Linux系统的重要性;实际上文件系统对于所有的操作系统都很重要,因为它们把大部分的硬件设备和软件数据以文件的形式进行管理。Linux系统对设备和数据的管理框架图如下:

嵌入式Linux系统移植的四大步骤
【图3】文件系统实现

[说明]

A. VFS(virtual file system)是虚拟文件系统,它管理特殊文件(虚拟文件)、磁盘文件和设备文件

B. fs_operations结构是由一系列文件操作接口函数组成,由文件系统层来完成,为VFS提供文件操作;

C. 在文件系统层,磁盘文件要实现各种文件系统(如:ext2),设备文件要实现各种抽象的设备驱动

D. 在设备驱动层,磁盘驱动要实现各种磁盘的驱动程序,其他设备驱动要实现具体的设备驱动

E. 物理层就是设备自身

为什么会有不同的文件类型?

由于存储介质有很多种,所以没有办法用一种统一的格式存放文件系统到各种不同的存储介质上,而是需要多种不同的存储格式来适应各种存储介质的特性,以求达到存取效率和空间利用率的最优化,这样就需要对每种存储格式制定一个规范,这写规范就叫文件系统类型。常见的文件系统类型有:

1.Dos
FAT16

2.windows
FAT16、FAT32、NTFS

3.Linux
Minix、ext、ext2 、ext3 、ISO9660 、jffs2, yaffs, yaffs2、cramfs, romfs, ramdisk, rootfs、proc、sysfs、usbfs、devpts、 tmpfs & ramfs、 NFS

由此可见,Linux支持的文件系统最多。以不同的介质来分类,如下所示:
? 磁盘
FAT16、 FAT16、FAT32、NTFS、ext、ext2 、ext3、Minix
? 光盘
ISO9660、
? Flash
jffs2, yaffs, yaffs2、cramfs, romfs
? 内存
Ramdisk、tmpfs & ramfs
? 虚拟
rootfs、proc、sysfs、usbfs、devpts、NFS

常用的存储介质理论上都可以用于存储Linux支持的文件系统;因为我们这里只研究嵌入式系统,而嵌入式系统由于体积和移动特性的限制,不能采用磁盘和光盘,所以只能采用flash类的存储设备、内存和虚拟存储设备作为文件系统的存储介质;
flash芯片的驱动程序是由系统来提供,所以它的存取特点完全是flash自身的特点,这时最好有更加适合flash的文件系统——Jffs、Yaffs、Cramfs和Romfs。这些文件系统都是嵌入式Linux系统中常用的文件系统,可以根据特点来选择使用它们,特点如下:

共同点

基于MTD驱动

Jffs

A.针对NOR Flash的实现
B.基于哈希表的日志型文件系统
C.采取损耗平衡技术,每次写入时都会尽量使写入的位置均匀分布
D.可读写,支持数据压缩
E.崩溃/掉电安全保护
F.当文件系统已满或接近满时,因为垃圾收集的关系,运行速度大大放慢

Yaffs

A.针对Nand Flash的实现
B.日志型文件系统
C.采取损耗平衡技术,每次写入时都会尽量使写入的位置均匀分布
D.可读写,不支持数据压缩
E.挂载时间短,占用内存小
F.自带Nandflash驱动,可以不使用VFS和MTD

Cramfs

A.单页压缩,支持随机访问,压缩比高达2:1
B.速度快,效率高
C.只读,有利于保护文件系统免受破坏,提高了系统的可靠性,但是无法对其内容进行扩充

Romfs

A.简单的、紧凑的、只读的文件系统
B.顺序存放数据,因而支持应用程序以XIP(execute In Place,片内运行)方式运行,在系统运行时,节省RAM空间

特有的文件系统类型:Ramdisk文件系统
在Linux系统中,内存经常用于存储文件系统,这种叫做Ramdisk,Ramdisk有两种,一种是完全把内存看成物理存储介质,利用内存模拟磁盘,运用磁盘的文件系统类型;另一种只是在内存中存储了文件系统逻辑结构,运用tmpfs & ramfs文件系统类型:
tmpfs & ramfs

1. 概述

用物理内存模拟磁盘分区,挂载这种分区后,就可以跟读写磁盘文件一样读写这里面的文件,但是操作速度要比磁盘文件快得多;所以一般应用在下面几个方面:
1)读写速度要求快的文件应该放在这种文件系统中
2)磁盘分区为flash的情况下,把需要经常读写的文件放在这种文件系统中,然后定期写回flash
3)系统中的临时文件,如/tmp、/var目录下的文件应该放在这种文件系统中
4)/dev设备文件(因为设备文件随驱动和设备的加载和卸载而变化),应该放在这种文件系统中

2. 特点

1)由于数据都存放在物理内存中,所以系统重启后,这个文件系统中的数据会全部丢失
2)ramfs在没有指定最大的大小值情况下,会自动增长,直到用掉系统中所有的物理内存为止,这时会导致系统的崩溃,建议挂载时最好限定其最大的大小值
3)tmpfs如果指定了大小值,自动增长至大小值后,系统会限定它的大小;这个文件系统占用的物理内存页可以背置换到swap分区,但是ramfs不行

转自:CSDN - victorwjw

围观 538

嵌入式计算不仅需要网络快速、一致的计算,而且也要求系统能够井然有序地将其执行代码和数据,存储在一个“ 共同” 的“ 狭小” 的空间内。

1、鲁棒性法则

嵌入式计算不仅要求系统迅速而有效的计算,而且还要求在某些计算单元出现错误的时候,系统仍然能 够继续正常运行工作。

2、实时性法则

嵌入式系统的计算结果,不仅依赖于系统的逻辑运算之正确性,而且也依赖于这个运算结果的计算时间。

3、冗余度法则

在嵌入式系统具有足够的冗余度之后,系统的“ 初始敏感性” 对于其“最终计算结果” 的影响就变得微乎其微了。

4、结构性法则

对于嵌入式系统而言,其结构复杂性的趋势表明:

a. 系统结构越简单越有效(The simplest is the best);

b. 系统结构越复杂越稳定(More complex is more stable) 。

5、简约性法则

当简约一个嵌入式系统时,系统剩下的功能之 间的互动关系就会变得越来越强;
当系统的功能被简约之后,外来的入侵者之成 功的概率就会变得越来越大。

6、保育性法则

如果在嵌入式系统中要想保留某个系统功能, 最好是将所有的其他功能都看成是 “ 神圣不可侵 犯的 ” ;系统的功能被移出(灭绝)或者生成(入 侵),一定会造成整体(群集)结构及其动态 性能上的重大转变。

从软件系统的角度来讲,普适计算是指使用小型计算设备、在位置不断移动的过程中或在地理位置分布很广的范围内,在不稳定的通信条件下实现联机事务处理和企业核心数据访问。这些小型计算设备,具有多种通信手段,如移动通信网络、卫星等,能与互联网或企业内部网相连,但这种连接不是固定的连接,而是间断的连接。

7、组织性法则

嵌入式互联网(embedded Internet) 最重要的往往不是网络中个体设 备的特质,而是存在于网络中的整体秩序,即 网络秩序。

在一个高冗余度网络中,设备的单一作用已经 不再能够构成影响到系统整体性能的主要因素 了,而起主要作用的是所有结点及其所构成的 连结特征。

8、网络性法则

由一群设备相互作用的嵌入式Internet 结点所构成的网络,其整体所表现出的性质,往往与个别结点的 性质没有重大关系。

9、消息性法则

保证查寻消息:它具有严格的时间敏感或者基本常态 系统操作要求,这类消息要求一个来自系统的时间保证。即一旦由这类消息引起的活动或者任务被执行, 那么在确定的时间间隔内,它们的时间限定性必将被 系统所保证。

最佳效果消息:它具有典型的软时间限定性,即其时 间限定是由活动或者任务本身的时间序列所规定,无 需系统保证就能满足其时间限定性的要求。

10、免疫性法则

嵌入式互联网(embedded Internet) 的免疫系统应当是一个仿生命体机制,免疫功能是一个“前馈”系统,所以要求系统应具有预见能力,从而可以“以(小)毒攻(大)毒”。

11、融合性法则

嵌入式Internet 是一个复杂网络,将复杂网络结构用简单的“组成”来解析,让系统可以由孤立的“组成”来诠释“整体”,或者让系统可以由“结点”来表达“全局”。

12、性价比法则

如果系统A 是系统B 地嵌入式系统,即B(a) ,那么系统A 的成本应不超过系统B 成本的10 %,而系统B(a) 的成本应大于系统A 和系统B 成本之和,系统B(a) 的性价比应提高30 %。

转自:EEChina

围观 329

首先我们来看一下嵌入式WiFi的来源以及与普通WiFi的区别。

我们都知道笔记本、手机、平板电脑等这类产品具有强大的CPU和大容量的存储器进行网络通信数据的处理和存储,因此在使用WiFi时不需要额外的MCU,完全借助其高速处理器和庞大的软件系统。但是对于家电、仪表、LED灯等智能家居产品,因为该类产品的主控芯片可能是成本很低、功能简单的MCU,因此这类产品无法支持普通Wi-Fi的功能。同时,还有一个重要的原因就是普通Wi-F的功耗比较高,而嵌入式WIFI在功耗上做了很大的改善,比较适合对功耗要求高的无线家电设备。

基于上述原因,各个无线厂商相继推出了嵌入式WiFi模块。嵌入式WiFi模块的特点是软硬件集成度高,整个嵌入式WiFi模块集成了射频收发器、MAC、WIFI驱动、所有WIFI协议、无线安全协议、一键连接等。总之,一句话:嵌入式WiFi应物联网而生!

下面我们针对嵌入式WiFi与普通WiFi来进行对比,通过下表的对比,我们大致上可以理解到什么是嵌入式WiFi。

嵌入式WiFi与普通WiFi有什么不同?

在分析WiFi驱动前,分享一下本人对Linux驱动的一些了解,其实纵观Linux众多的设备驱动,几乎都是以总线为载体,所有的数据传输都是基于总线形式的,即使设备没有所谓的总线接口,但是Linux还是会给它添加一条虚拟总线,如platform总线等;介于WIFI的驱动实在是太庞大了,同时又是基于比较复杂的USB总线,所以建议大家先了解一下USB设备驱动和网络设备驱动。

我们要看懂WiFi驱动,首先要明白WiFi的工作原理。从对于支持802.11n、802.11ac这些比较无线标准的WiFi芯片,其驱动程序也会越来越复杂。那么我们怎么入手去了解及分析它呢?

网上很多人分析Linux设备驱动都是从模块加载入手去分析它的驱动源码。以本人从事Linux设备驱动多年的经验,这确实是一条很直观又非常好的思路。但是这只局限于设备功能少、接口较简单、驱动源码较少的设备驱动。对于功能复杂、驱动源码庞大的设备驱动,根据这条思路,很多开发者可能会无耐心走下去,或者会走向死胡同。

现在我们可以这样来看,从硬件层面上看,WIFI设备与CPU通信是通过USB接口的,与其他WiFi设备之间的通信是通过无线射频(RF)。从软件层面上看,Linux操作系统要管理WiFi设备,那么就要将WiFi设备挂载到USB总线上,通过USB子系统实现管理。而同时为了对接网络,又将WiFi设备封装成一个网络设备。

我们以USB接口的WIFI模块进行分析:

(1)从USB总线的角度去看,它是USB设备;
(2)从Linux设备的分类上看,它又是网络设备;
(3)从WIFI本身的角度去看,它又有自己独特的功能及属性,因此它又是一个私有的设备。

通过上述的分析,我们只要抓住这三条线索深入去分析它的驱动源码,整个WIFI驱动框架就会浮现在你眼前。

1、现在我们先从USB设备开始,要写一个USB设备驱动,那么大致步骤如下:

(1)需要针对该设备定义一个USB驱动,对应到代码中即定义一个usb_driver结构体变量。代码如下:
struct usb_driver xxx_usb_wifi_driver;

(2)填充该设备的usb_driver结构体成员变量。代码如下:
static struct usb_driver xxx_usb_wifi_driver = {
.name = "XXX_USB_WIFI",
.probe= xxx_probe,
.disconnect= xxx_disconnect,
.suspend= xxx_suspend,
.resume= xxx_resume,
.id_table= xxx_table,
};

(3)将该驱动注册到USB子系统。代码如下:
usb_register(&xxx_usb_wifi_driver);

以上步骤只是一个大致的USB驱动框架流程,而最大和最复杂的工作是填充usb_driver结构体成员变量。以上步骤的主要工作是将USB接口的WIFI设备挂载到USB总线上,以便Linux系统在USB总线上就能够找到该设备。

2、接下来是网络设备的线索,网络设备驱动大致步骤如下:

(1)定义一个net_device结构体变量ndev。代码如下:
struct net_device *ndev;

(2)初始化ndev变量并分配内存。代码如下:
ndev=alloc_etherdev();

(3)填充ndev -> netdev_ops结构体成员变量。代码如下:
static const struct net_device_ops xxx_netdev_ops= {
.ndo_init= xxx_ndev_init,
.ndo_uninit= xxx _ndev_uninit,
.ndo_open= netdev_open,
.ndo_stop= netdev_close,
.ndo_start_xmit= xxx_xmit_entry,
.ndo_set_mac_address= xxx_net_set_mac_address,
.ndo_get_stats= xxx_net_get_stats,
.ndo_do_ioctl= xxx_ioctl,
};

(4)填充ndev->wireless_handlers结构体成员变量,该变量是无线扩展功能。代码如下:
ndev->wireless_handlers = (struct iw_handler_def *)&xxx_handlers_def;

(5)将ndev设备注册到网络子系统。代码如下:
register_netdev(ndev);

3、WiFi设备本身私有的功能及属性,如自身的配置及初始化、建立与用户空间的交互接口、自身功能的实现等。

(1)自身的配置及初始化。代码如下:
xxx_read_chip_info();
xxx_chip_configure();
xxx_hal_init();

(2)主要是在proc和sys文件系统上建立与用户空间的交互接口。代码如下:
xxx_drv_proc_init();
xxx_ndev_notifier_register();

(3)自身功能的实现,在前面已经讲解过WiFi的网络及接入原理,如扫描等。同时由于WiFi在移动设备中,相对功耗比较大,因此,对于功耗、电源管理也会在驱动中体现。

转自: 嵌入式资讯精选

围观 652

在单片机编程的过程中,如果一名设计者能够同时掌握多门编程语言,那么这名设计者肯定是一位非常优秀的人才。但是想要同时精通汇编、C语言、C++这三门语言实在是太难了,很多初学者在其中一门的学习中就已经到处碰壁,苦不堪言。本文特意为大家整理了拥有嵌入式编程领域多年工作经验的工程师意见,汇总成了一篇能够对嵌入式编程经验有着指导意义的注意事项,感兴趣的朋友快来看一看吧。

在单片机嵌入式编程中,最难的两部分是interrupt和MM(memory manage),之所以有人觉得并不困难,那是因为太多数情况下芯片制造商都已经直接写好,但是如果设计者本身就在为芯片制造商工作,那就必须自己会写配置文件。

这两个东西之所以比较难是因为要用汇编或类C来写,属于比较低层的东西,中断有外部中断和内部中断,外部中断有两种实现模式,硬件中断模式和软件中断模式,相对来说比较简单,属于应用层面的,相比之下,内部中断就要复杂得多,内部中断主要是发生重起,总线出错、溢出、校验出错等情况产生的,很多软件开发人员基本上不写对应的中断服务程序,因为它太难了而且一般也用不到。但是一旦发生,那就是致命错误,因此从整个系统健壮性来考虑必须要有相应的ISR才行,这也是freescale的专家建议的,因所以下面就谈一下嵌入式编程应该注意的问题。

延时

嵌入式编程经常会涉及到硬件的操作,如ADC,打开或者关闭一个电流源,这些都是需要时间的,因此当在发出这些指令的时候立即读取寄存器的值是得不到想要的结果的,而且还找不出原因,有时候需要的延时还比较长,达到ms级,一般情况下us级就够了,根据各芯片的时钟频率而定,不单指MCU的总线时钟频率。

变量

一般来说如果非常明确某个变量的作用域和生命周期就应该定义相对的变量,如const、static等,这样不容易出错,不建议将所有变量都定义成全局变量,这样管理起来比较麻烦,程序一旦出错,破坏性也比较大,函数也是如此,全局变量和通用函数一定要申明,这样在调用的时候不容易出错,而且有些编译器对于未申明的函数是不会报错的,但在调用的时候又会发出类型隐含转换的警告,在这里就不举例子了,总之这点要特别小心。

宏定义

在程序编写过程对于一些特定的数字应该尽量使用宏定义,这样做有个好处就是比较直观,便于日后维护,要不然时间久了看到那个数字根本就想不起它代表什么意思,宏定义并不会给程序带来任何负担,因为它在编译的时候就已经全部替代了,所以尽可以广而用之。值得一提的是宏定义并不局限于使用常量,它可以定义函数,因为它是直接替换,因此避免了入栈和出栈,提高了程序执行的效率,但是同时增加了代码量,因此一般用比较简单的函数,它还有一个缺点是在替换的过程不检查参数类型是否正常,从而增加了安全隐患,解决此问题的方法是使用一个称之为inline的内联函数,它继承了宏定义的优点,又弥补了它的缺点,是个最佳的选择,但是这个属于C++的范畴,有一定的难度,在这里也不多讲,有兴趣的朋友可以参考一下相关资料。

浮点运算

大多数低档次的单片机都是不支持浮点运算的,因此在实际使用过程中也很少用到,因此为了降低成本,一般都去掉了浮点运算模块,这就带来了一个问题,如果万一要用到浮点运算怎么办?细心的朋友可能会发现,即使不具有浮点运算的单片机在仿真调试过程依然可以使用float or double的数据类型进行计算,而且结果也很准确,这是为什么呢?这个因为编译器自动调用了库函数来实现的,一般是通过迭代的方法,因此它的执行效率非常慢,不建议采用此方法,而通常采用的是“定点”的方法来解决这个问题,比如说一个32bit的数据,可以假定它的低8位是小数位,然后移位计算,类似于整数运算,这种方法比较复杂,但是可以非常精确,还有一种方法就是直接放大10的N次方倍进行整数的计算,可以得出近似值,因此为了不增加不必要的麻烦,应该总是尽量避免使用浮点运算,一般情况也都是可以避免的。

watch dog

以三重watch dog为例,watch dog1检查时钟频率,watch dog2监视一小段代码,它必须在一个比较短的时间里喂一次,一般要求在250us到650us之间喂一次,watch dog3监视一大段代码,要求在比较长的时间内喂一次,一般是100ms以内,三个条件必须同时满足才行,这要求对代码的执行过程非常清楚,或者将导致喂狗出错重起。

这里需要向大家强调的是,在单片机嵌入式的编程过程中程序的好坏往往是由细节决定的,一个程序写的是否详细、灵活,是与日积月累的知识积累与实际磨练成正比的。虽然编程是意见非常枯燥甚至乏味的过程,但成功后的喜悦能够让大家相信这份付出是值得的。

来源:互联网

围观 359

作者:胡恩伟

嵌入式MCU与MPU的区分

嵌入式系统中的处理器按照是否集成片上Flash和RAM可以分为MCU(MicroControl Unit—微控制器)和MPU(MicroProcess Unit—微处理器)。典型的MCU如Freescale S08、S12和MPC56xx以及8051单片机等,而典型的MPU如基于ARMCortex A系列内核的i.MX系列处理器:

本文中的观点针对仅针对嵌入式MCU,也就是我们常说的单片机。其为单芯片集成解决方案—片上集成了嵌入式系统工作所需的逻辑计算内核CPU,存储数据/代码的RAM,EEPROM和Flash,内部互联总线—Crossbar、AMBA(APB、AHB以及AXIbus),定时器资源(Timer)、中断控制器(INTC,通用输入输出接口(GPIO),模拟数字转换模块—ADC、DAC和ACMP,段码LCD控制器、TFTLCD控制器,步进电机驱动(SMC),通信接口/控制器—I2C、SPI、UART/SCI、CAN、SDIO/eMMC、以太网MAC等;当然,MPU中也会集成很多嵌入式系统工作所需的大部分片上外设,但因为其计算单元CPU内核运行速度非常快,所以其一般不会再片内集成系统工作所需的RAM和Flash存储器,而是集成SDR/DDR2/3/4等外部SRAM扩展接口和NAND/NOR Flash扩展接口,用户设计基于MPU的硬件系统时还需选择合适的SRAM和外部Flash才可以保证系统正常工作。

当然还有我们常说的CPU(CentralProcess Unit—中央处理器),常见PC上所使用的Intel的x86处理器,比如奔腾、至强、酷睿i3/i5/i7系列等,其片上只集成了中央计算内核单元CPU,少量的一级/二级/三级缓存以及GPU,但不包含中断控制器、定时器等,它需要通过主板进行扩展,更不包含存储器,需要用户在主板上外界DDR内存条和Flash硬盘。

浅谈嵌入式MCU开发中的三个常见误区

按照内核的运行速度和片上集成外设资源的丰富程度以及功耗,MCU、MPU和CPU的对比分布如下:
浅谈嵌入式MCU开发中的三个常见误区

误区一:MCU的程序都是存储在片上Flash上,然后拷贝到RAM中执行的

很多刚接触MCU的人受学校老师讲授计算机硬件和C语言课程时一些观点的影响,认为MCU中程序都是存储在片上Flash上,然后拷贝到RAM中执行的,这其实是错误。原因如下:

1. MCU的片上RAM资源和Flash存储器相比一般都比较小,其比例大概为1:16到1:5,其不可能将存储在Flash中的程序代码全部拷贝到片上RAM中;
以下为Freescale S12G系列、S12XE系列以及MPC574xB/C/D/G系列MCU的片上RAM和Flash存储器资源的对比:

浅谈嵌入式MCU开发中的三个常见误区
浅谈嵌入式MCU开发中的三个常见误区

2. 在嵌入式MCU中内核CPU的工作频率一般为总线频率的两倍(S08和S12(X)系列MCU的内核CPU工作频率固定为总线工作频率的2倍)或者相等(PowerPC MPC560x系列),而挂到Flash的指令/数据总线宽度一般与CPU位宽的1~2倍,虽然Flash的访问频率比较低(几MHz到数十MHz,一般不超过100MHz),而嵌入式MCU内核CPU的运行频率也不高,在300MHz以内,所以总线每次可以从Flash取出2~4条指令(PS:当PowerPC e200内核使用VLE指令集时,大多数指令都为16位长度,若指令/数据总线宽度为64位宽,则一次可以读出4条VLE指令),从而弥补与内核CPU运行速度的差距,保证在硬件物理上实现在Flash取值执行嵌入式MCU程序是没有问题的。

以下为NXP MPC5744P的内部Cross Bar总线互联框图,其指令和数据总线均为64位宽:

浅谈嵌入式MCU开发中的三个常见误区

3. 在嵌入式MCU的硬件设计上没有自动将Flash程序提前拷贝到RAM的机制,在软件设计上也没有相应的代码执行这个拷贝工作--这样的拷贝过程无疑会造成内核CPU资源的浪费,代码搬移的过程总内核CPU无法处理其他任务;(除非是在开发嵌入式MCU的BootLoader时,需要对片上Flash进行擦除和编程,而大多数嵌入式MCU片上都只有一个Flash块(block/partion), 不支持read-while-read操作,所以需要将Flash驱动程序事先拷贝到RAM然后调用—事实上,只需要将Flash擦除和编程命令的launch语句和查询等待命令完成的程序拷贝到RAM执行即可。程序的执行至少包含取指à译码à执行三个环节,其中取指就是从存储器中读出指令,需要访问Flash/RAM)

4. 通过调试嵌入式MCU,在CPU寄存器窗口查看程序运行时PC寄存器的值也可以验证嵌入式MCU程序默认运行时就是在Flash本地执行的、即存储地址与运行时地址相同;

浅谈嵌入式MCU开发中的三个常见误区

由于S12XE系列MCU的Flash分页访问机制,地址0xFE8029其实是其Flash的Page_FE的逻辑地址,对应的Flash物理地址(也称作全局地址--GlobalAddress)为0x7F8029;
浅谈嵌入式MCU开发中的三个常见误区

误区二:工程编译生成的下载文件大小即为最终占用Flash的大小

很多工程师判断一个嵌入式MCU应用工程的编译结果大小往往看工程编译生成的HEX/S19/BIN等下载文件(Flash编程文件)的大小,认为工程编译生成的下载文件大小即为最终占用Flash的大小,这是不正确的。

因为在HEX/S19/BIN等Flash编程文件往往还包含了编译器版本信息,工程配置信息,每行数据/代码的存储地址,长度、校验和以及整个工程的复位运行地址等非常丰富的信息。因为只有具备了这些信息,编程器才找到将编译结果中的数据和代码烧写到Flash/EEPROM存储器的具体地址并保证数据/代码的完整性,通过每一行和整个文件的校验(Verify)来保证整个编程过程的正确完整。

以下以Motorola的S19文件(也称为S-Record)格式进行说明:
S-record每行最大是78个字节,156个字符
S-record 格式如下:

浅谈嵌入式MCU开发中的三个常见误区

其中:

type(类型):2个字符。用来描述记录的类型 (S0,S1,S2,S3,S5,S7,S8,S9)。

count(计数):2个字符。 用来组成和说明了一个16进制的值,显示了在记录中剩余成对字符的计数。

address(地址):4或6或8个字节。用来组成和说明了一个16进制的值,显示了数据应该装载的地址, 这部分的长度取决于载入地址的字节数。2个字节的地址占用4个字符,3个字节的地址占用6个字符,4个字节的地址占用8个字符。

data(数据):0—64字符。用来组成和说明一个代表了内存载入数据或者描述信息的16进制的值。
checksum(校验和):2个字符。这些字符当被配对并换算成16进制数据的时候形成了一个最低有效字符节,该字符节用来表达作为补充数据,地址和数据库的字符对所代表的(字节的)补码的byte总和。即计数值、地址场和数据场的若干字符以两个字符为一对,将它们相加求和,和的溢出部分不计,只保留最低两位字符NN,checksum=0xFF-0xNN。

S0 Record:记录类型是“S0” (0x5330)。地址场没有被用,用零置位(0x0000)。数据场中的信息被划分为以下四个子域:

name(名称):20个字符,用来编码单元名称
ver(版本):2个字符,用来编码版本号
rev(修订版本):2个字符,用来编码修订版本号
description(描述):0-36个字符,用来编码文本注释

此行表示程序的开始,不需烧入memory。

S1 Record:记录类型是“S1” (0x5331)。地址场由2个字节地址来说明。数据场由可载入的数据组成。
S2 Record:记录类型是“S2” (0x5332)。地址场由3个字节地址来说明。数据场由可载入的数据组成。
S3 Record:记录类型是“S3” (0x5333)。地址场由4个字节地址来说明。数据场由可载入的数据组成。
S5 Record:记录类型是“S5” (0x5335)。地址场由2字节的值说明,包含了先前传输的S1、S2、S3记录的计数。没有数据场。
S7 Record:记录类型是“S7” (0x5337)。地址场由4字节的地址说明,包含了开始执行地址。没有数据场。此行表示程序的结束,不需烧入memory。
S8 Record:记录类型是“S8” (0x5338)。地址场由3字节的地址说明,包含了开始执行地址。没有数据场。此行表示程序的结束,不需烧入memory。
S9 Record:记录类型是“S9” (0x5339)。地址场由2字节的地址说明,包含了开始执行地址。没有数据场。此行表示程序的结束,不需烧入memory。

一个具体的S12XEP100的CodeWarrior5.2工程编译后S19文件的大小为6KB,而实际占用Flash的大小只有2445个字节,远小于S19文件的大小,所以判断一个应用工程编译结果所占Flash和RAM的大小,应该看MAP文件中的统计结果,而非S19文件的大小:

浅谈嵌入式MCU开发中的三个常见误区

或者以IDE(比如CodeWarrior 10.6或者S32DS)的Print Size工具从控制台(console)打印出来的text + data来判断,下面是一个具体的MPC5748G S32DSfor Power V1.2工程,其编译及我国所占Flash的大小为23366(text) + 1372(data)=24738 Byte,而其S19文件大小为73KB。
浅谈嵌入式MCU开发中的三个常见误区

误区三:用户应用工程的编译结果建议不能超过MCU片上Flash的80%

通过对以上误区一的分析,嵌入式MCU中用户应用工程的编译结果(数据和程序代码)是一直存储在片上Flash中的,对其下载编程之后在整个产品的生命周期中都不会再改变(除非用户开发了在线/远程升级的BootLoader功能),因此,完全可以将将Flash全部用来保存编译结果,只要在工程链接文件中按照MCU实际存储器大小和地址进行配置,编译链接结果没有存储器溢出即可。用户应用工程的编译结果建议不能超过MCU片上Flash的80%的说法没有任何理论依据。

转自: 汽车电子expert成长之路

围观 609

页面

订阅 RSS - 嵌入式