EDN首页   博客首页

最新日志

发表于:2008-11-27 13:37:52
标签:无标签

0

十条Linux --新手必知

十条Linux --新手必知

  每一条基本是一个归类,如果新手能事先看纸质的书,能节约自己不少时间,减少对Linux的恐惧感以及挫折感,这些都是基本知识,不需要图形界面。


1. i386启动过程、硬盘分区、单用户进入系统
原因:装系统、grub
、急救系统跟这个很有关系


2. 发行版的概念,明白Linux这个词是一个泛称,熟悉各自发行版的包管理工具,比如rpmaptyum; 清楚各自发行版的手册在哪里,各个软件手册在哪里;

原因:提问时说出发行版的名字和版本,虽然都是
Linux,不少设置在不同发行版是有差异的。新手装软件别无它途,别想着编译HOME 目录了,如果在公共服务器上没权限就自己弄虚拟机吧,现在vmware server都免费了,速度慢点无所谓,学这些基本知识足够了。


3. 系统基本使用,如下命令:(下面的4,5,6跟这个结合起来学习)
bash 环境变量,HOME下几个配置文件PATH的用途 , 能写简单的脚本;*man*cd, pwd, ls, mkdir,rmdir, cp, rm, mv, find,grep/egrep/fgrep, df, du, vim(先这个吧,Emacser就不要争了, nano之类的也不谈),chmod, chown, more/less, head, tail, cat, tar, gzip, bzip2, who, whoami, w, top, ifconfig,ping, tracert, passwd,adduser/useradd, mail/mailx/mutt,mount, umount, clearrsetlftp/ncftp, fdisk/cfdisk/parted,ps, kill, killall, jobs, bg, fg, crontab, at, batch, dmesg,talk, message。

原因:差不多是日常使用命令的
80%了。


4. 文件系统的知识:
Linux目录结构(FHS),每个目录的用途,为什么没有Windows下的盘符,常见文件系统类型 (ext2, ext3,reiserfs, jfs, xfs,ntfs, vfat, iso9660),符号链接和硬链接,特殊文件类型(字符设备文件,块设备文件,socket...),相对路径和绝对路径,/etc/fstab
原因:真是很基本。


5. 权限问题,包括/etc/passwd, /etc/group, /etc/shadow,权限位rwxsSt的概念, uid, gid原因:基本,系统安全首先从这里开始

6.
进程的概念,特别是子进程、父进程的关系,能通过ps命令输出的pid, ppid找出这个关系;信号的概念,知道用kill发信号;前台进程和后台进程;守护进程;管道,输入输出的重定向;原因:常识问题。

7. linux日志系统,了解/var/log下各种日志的用途 原因:系统出了问题先看看有无可疑log



8. 系统启动脚本的熟悉,对init那一套清楚,知道怎么启动、重启、停止服务,怎么往这一套机制里增加服务删除服务  原因:基本,也是学习
shell脚本的好起点。


9. TCP/IP的基本概念和使用,包括:

OSI网络模型 ; 以太网和MAC地址;
IP协议和IP地址,IP地址段的表示法;
ICMPDHCP协议的作用;
DNS系统;
TCP协议和UDP协议,端口的概念,客户机/服务器模型
;



原因:UNIX从一开始就跟网络结下不解之缘,所以了解网络的基本问题是非常必要的。




10. X window的基本概念,弄明白X serverX client的含义,原因:以后就步入X window 的世界了,路还很长,先打点基础。

系统分类: 软件开发   |    用户分类:    |    来源: 转贴

该用户于2008-11-27 13:37:56编辑过该文章

评论(0) | 阅读(86)
发表于:2008-11-4 10:04:13
标签:无标签

0

基于FPGA技术的存储器设计及其应用(转帖)

基于FPGA技术的存储器设计及其应用
引言

  复杂可编程逻辑器件——FPGA技术在近几年的电子设计中应用越来越广泛。FPGA具有的硬件逻辑可编程性、大容量、高速、内嵌存储阵列等特点使其特别适合于高速数据采集、复杂控制逻辑、精确时序逻辑等场合的应用。而应用FPGA中的存储功能目前还是一个较新的技术。本文将介绍在FPGA中构造存储器的方法,特别是结合高速数据采集的特点重点描述双端口RAM的构造方法及其应用。

在FPGA中构造存储器

  许多系列的FPGA芯片内嵌了存储阵列,如ALTERA EPlK50芯片内嵌了5K字节的存储阵列。因此,在FPGA中实现各种存储器,如单/双端口RAM、单/双端口ROM、先进先出存储器FIFO等非常方便,而且具有诸多优点。其硬件可编程的特点允许开发人员灵活设定存储器数据的宽度、存储器的大小、读写控制逻辑等,尤其适用于各种特殊存储要求的场合。FPGA/FPGA器件可工作于百兆频率以上,其构造的存储器存取速度也可达百兆次/秒以上,这样构成的高速存储器能够胜任存储数据量不太大,但速度要求很高的工作场合。

  FPGA中构造存储器主要有两种方法实现。一是通过硬件描述语言如VHDL、AHDL、Verilog HDL等编程实现。二是调用MAX+PLUSⅡ自带的库函数实现。调用库函数方法构造存储器较硬件描述语言输入方式更为方便、灵活、快捷和可靠,故也更常用之。

利用库函数构造双端口RAM

  在MAX+PLUSⅡ中有几个功能单元描述库。prim逻辑元库,包括基本逻辑单元电路,如与、或、非门,触发器、输入、输出引脚等;mf宏功能库,包括TTL数字逻辑单元如74系列芯片;而下文将要详细介绍的参数化双端口RAM模块所在的参数化模块库(mega-lpm)中,包括各种参数化运算模块(加、减、乘、除)、参数化存储模块(单、双端口RAM、ROM、FIFO等)以及参数化计数器、比较器模块等等。库中的这些元件功能逻辑描述经过了优化验证,是数字电路设计中的极好选择。

  mega-lpm库中共有五种参数化双端口RAM模块:ALTDPRAM、LPM_RAM_DP、CSDPRAM、LPM_RAM_DQ和LPM_RAM_IO。其中ALTDPRAM和LPM_RAM_DP模块读写有两套总线,读和写有各自的时钟线、地址总线、数据总线和使能端,可同时进行读写操作。除此之外,ALTDPRAM模块还有一个全局清零端口。CSDPRAM模块则有a、b两组写端时钟线、地址总线、数据总线和使能端,可同时对RAM进行写操作,但对RAM读、写只能分时进行。LPM_RAM_DQ模块相对简单,读与写共用一组地址总线,有各自的数据线和时钟线。LPM_RAM_IO模块只有一组地址总线和数据总线。

  mega-1pm函数库中的双端口RAM模块全是参数化调用,这为设计带来极大的方便。通过对各种参数的取舍、参数设置和组合,再结合读写控制逻辑就可以构造出设计需要的存储器模块。双端口RAM常见的应用模式主要有以下两种:

  1.存储器映像方式。该方式可以随意对存储器的任何单元进行读写操作。其主要应用于多CPU的共享数据存储、数据传送等。该方式中,读、写控制线、地址总线和数据总线有两套。根据两端口之间数据的传送方向为单向或双向,又有单向数据总线和双向数据总线之分。

  2.顺序写方式。该方式对RAM的写操作只能顺序写入。这种情况适用于对象特性与时间紧密相关或传送数据与顺序密切相关的场合,如文件传送、时序过程、波形分析等。根据写控制逻辑的不同,可对RAM进行循环写入或一次写入方式。该方式下的读操作可以是存储器映像读或顺序读,前一种有较大的灵活性,而后一种则类似于FIFO形式。

  在读、写使用独立的地址总线和数据总线时,可以同时对RAM不同单元进行读写操作。根据不同控制逻辑的要求,对读写时钟、时钟使能端口可以适时设置,以满足控制需要。

  下面以LPM_RAM_DP模块为例介绍库函数法构造双端口RAM的步骤。

  首先在MAX+PLUSⅡ中建立一个图形编辑文件。双击文件任意空白处弹出库函数选择窗口。然后从mega-lpm库中选择LPM_RAM_DP模块。

  在LPM_RAM_DP模块中共有9个可配置参数:

LPM_FILE——指定存储器的初始化数据文件;

LPM_INDATA——选择输入数据采用寄存方式还是非寄存方式;

LPM_NUMWORDS——设置存储器的深度(大小);

LPM_OUTDATA——选择输出数据采用寄存方式还是非寄存方式;

LPM_RDADDRESS_CONTROL——决定读地址控制信号是寄存方式还是非寄存方式;

LPM_WIDTH——设置存储数据宽度;

LPM_WIDTHAD——设置地址总线宽度;

LPM_WRADDRESS_CONTROL——选择写地址控制信号是寄存方式还是非寄存方式;

USE_EAB——决定是否使用嵌入式阵列块。

  双击双端口RAM参数列表可弹出引脚/参数设置窗口。在引脚/参数设置窗口可以具体对双端口RAM进行引脚、参数设置。可以根据具体的对存储器的功能要求,决定各种口线的使用与否。例如不想使用rdclken(读时钟使能)信号,则可以将其Status设置为Unused即可。同时还可以通过Inversion项设定该信号的初始状态(初始值)。在窗口的Parameters参数设置处,选择不同的参数项后,通过ParameterValue项可以改变或设置其相应的状态或数值。如想设置存储数据为8位宽度,则选择LPM_WIDTH项,然后将Parameter Value设置为8。

  例如要设计一个11位宽数据,512个存储单元,使用读写同步时钟、不需要读写使能端及时钟使能端的双端口RAM。则可以打开引脚/参数设置窗口,设置LPM_NUMWORDS为512,LPM_WIDTH为11,LPM_WIDTHAD为9,LPM_INDATA、LPM_OUTDATA、LPM_RDADDRESS_CONTROL和LPM_WRADDRESS_CONTROL为寄存方式,使用嵌入式阵列;rdaddress、rdclock、data、wraddress、wrclock、q为Used,rden、rdclken、wren、wrclken为Unused。

其它存储器的构造方法

  不同的存储器根据各自特点,应用场合也不尽相同。ROM存储器主要用来存储“常量”,如系统参数、波形发生器的信源等。先进先出FIFO存储器可用于信号的实时不间断采集,存储、缓冲两个异步时钟之间的数据传输等。

  ROM、FIFO等存储器的调用库函数构造方法与双端口RAM的构造方法类似,在mega-lpm库中调用相应的模块单元即可。其中ROM存储器在库中是LPM_ROM模块,FIFO存储器在库中有CSFIFO、DCFIFO、LPM_FIFO、LPM__FIFO_DC、SCFIFO、SFIFO共六种。需要说明的是由于ROM在实际系统运行时的不可写入性,在ROM构造过程中要对ROM存储器进行数据初始化。这一操作是通过设置PLM_FILE项完成的。在引脚/参数设置窗口的Parameters参数设置处选择该项,再通过ParameterValue项确定相应的数据初始化文件(*.mif)即可。下面是VHDL格式的ROM数据初始化文件(文件可用任何文本编辑器实现):

双端口RAM在高速数据采集中的应用

  利用传统方法设计的高速数据采集系统由于集成度低、电路复杂,高速运行电路干扰大,电路可靠性低,难以满足高速数据采集工作的要求。应用FPGA可以把数据采集电路中的数据缓存、控制时序逻辑、地址译码、总线接口等电路全部集成进一片芯片中,高集成性增强了系统的稳定性,为高速数据采集提供了理想的解决方案。下面以一个高速数据采集系统为例介绍双端口RAM的应用。

  该系统要求实现对频率为5MHz的信号进行采样,系统的计算处理需要对信号进行波形分析,信号采样时间为25μs。根据设计要求,为保证采样波形不失真,A/D采样频率用80MHz,采样精度为8位数据宽度。计算得出存储容量需要2K字节。

  根据设计要求,双端口RAM的LPM_WIDTH参数设置为8,LPM_WIDTHAD参数设置为11(211=2048),使用读写使能端及读写时钟。ADCLK、WRCLK和地址发生器的计数频率为80MHz。

  A/D转换值对双端口RAM的写时序为顺序写方式,每完成一次A/D转换,存储一次数据,地址加1指向下一单元,因此写地址发生器(RAM_CONTROL)采用递增计数器实现,计数频率与ADCLK、WRCLK一致以保证数据写入时序的正确性。写操作时序由地址和时钟发生器、A/D转换时钟和双端口RAM的写时钟产生。停止采样时AD_STOP有效,写地址发生器停止计数,同时停止对RAM的写操作。将地址发生器的计数值接至DSP总线可以获取采样的首尾指针。地址发生器单元一般用(VHDL)语言编程实现,然后生成符号文件RAM_CONTROL在上层文件调用。其部分VHDL语言程序如下:

  对双端口RAM的读操作采用存储器映像方式,其读出端口接DSP的外扩RAM总线,DSP可随机读取双端口RAM的任一单元数据,以方便波形分析。 由于LPM_RAM_DP模块的读端数据总线q不具有三态特性,因此调用三态缓冲器74244,通过其将输出数据连接到DSP数据总线上。

  在高速数据采集电路中,数据缓存也可以用FIFO或单端口RAM实现。用FIFO进行数据缓存,由于其已经把地址发生部分集成在模块单元内,因此省去了一部分程序编写,但是DSP却不能任意地访问FIFO的存储单元,只能是顺序写入/读出数据,这样设计,系统的灵活性就大大降低。如果DSP的分析计算需要特定单元的数据,则系统的效率和速度会因为无效数据的读取而降低。使用单端口RAM进行数据缓存同样存在一些问题。由RAM侧看,DSP和A/D转换器是挂在一条总线上的,当从RAM向DSP传输数据的时候,A/D转换器就不能有数据传到该总线上,否则会产生总线冲突,引起芯片损坏。解决这个问题就需要增加电路。应用双端口RAM就不存在这个问题,而且使系统结构划分更明确,符合模块化设计思想。

结语

  综上所述,利用FPGA芯片的高速工作特性,以及其内部集成嵌入式阵列和大规模逻辑阵列的特点,设计存储器,三态缓存器、地址发生器、以及复杂的时序逻辑电路等,应用于高速数据采集电路中可以使电路大大简化,性能提高。同时由于FPGA可实现在系统编程(ISP),使系统具有可在线更新、升级容易等特点,是一种较为理想的系统及电路实现方法。

系统分类: CPLD/FPGA   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(100)
发表于:2008-9-12 21:11:30
标签:无标签

2

ARM 程序设计优化策略与技术

ARM 程序设计优化策略与技术(转帖)
[ 2008-7-10 18:47:00 | By: 糯米 ]
 
程序优化是指软件编程结束后,利用软件开发工具对程序进行调整和改进,让程序充分利用资源, 提高运行效率, 缩减代码尺寸的过程。按照优化的侧重点不同, 程序ARM优化可分为运行速度优化和代码尺寸优化。运行速度优化是指在充分掌握软硬件特性的基础上, 通过
应用程序结构调整等手段来降低完成指定任务所需执行的指令数。在同一个处理器上, 经过速度优化的程序比未经优化的程序在完成指定任务时所需的时间更短,即前者比后者具有更高的运行效率。代码尺寸优化是指,采取措施使应用程序在能够正确完成所需功能的前提下, 尽可能减少程序的ARM代码量。

然而在实际的程序设计ARM过程中,程序优化的两个目标(运行速度和代码大小) 通常是互相矛盾的。为了提高程序运行效率,往往要以牺牲存储空间、增加代码量为代价, 例如程序设计中经常使用的以查表代替计算、循环展开等方法就容易导致程序代码量增加。而为了减少程序代码量、压缩存储器空间,可能又要以降低程序运行效率为代价。因此, 在对程序实施优化之前, 应先根据实际需求确定相应的策略。在处理器资源ARM紧张的情况下, 应着重考虑运行速度优化;而在存储器资源使用受限的情况下, 则应优先考虑代码尺寸的优化。

1 程序运行速度优化
程序运行速度优化的方法可分为以下ARM几大类。
1.1 通用的优化方法
(1)减小运算强度

利用左/ 右移位操作代替乘/ 除2 运算:通常需要乘以ARM或除以2 的幂次方都可以通过左移或右移n 位来完成。实际上乘以任何一个整数都可以用移位和加法来代替乘法。ARM 7 中加法和移位可以通过一条指令来完成,且执行时间少于乘法指令。例如: i = i × 5 可以用i = (i<<2) + i 来代替。
利用乘法代替乘方运算:ARM7 核中内建有32 ×8 ARM乘法器, 因此可以通过乘法运算来代替乘方运算以节约乘方函数调用的开销。例如: i = pow(i, 3.0) 可用 i = i×i × i 来代替。
利用与运算代替求余运算:有时可以通过用与(AND )指令代替求余操作(% )来提高效率。例如:i = i % 8 可以用 i = i & 0x07 来代替。
(2)优化循环终止ARM条件
在一个循环结构中,循环的终止条件将严重影响着循环的效率,再加上ARM 指令的条件执行特性,所以在书写循环的终止条件时应尽量使用count-down-to-zero结构。这样编译器可以用一条BNE (若非零则跳转)指令代替CMP (比较)和BLE (若小于则跳转)两条指令,既减小代码尺寸,又加快了运行ARM速度。

(3)使用inline 函数
ARM C 支持 inline 关键字,如果一个函数被设计ARM成一个inline 函数,那么在调用它的地方将会用函数体来替代函数调用语句, 这样将会彻底省去函数调用的开销。使用inline 的最大缺点是函数在被频繁调用时,代码量将增大。

1.2 处理器相关的优化ARM方法
(1)保持流水线畅通
从前面的介绍可知,流水线延迟或阻断会对处理器的性能造成影响,因此应该尽量保持流水线畅通。流水线延迟难以避免, 但可以利用延迟周期进行其它ARM操作。

LOAD/STORE 指令中的自动索引(auto-indexing)功能就是为利用ARM流水线延迟周期而设计的。当流水线处于延迟周期时, 处理器的执行单元被占用, 算术逻辑单元ARM(ALU )和桶形移位器却可能处于空闲状态,此时可以利用它们来完成往基址寄存器上加一个偏移量的操作,
供后面的指令使用。例如:指令 LDR R1, [R2], #4 完成 R1= *R2 及 R2 += 4 两个操作,是后索引(post-indexing)的例子;而指令 LDR R1, [R2, #4]! 完成 R1 = *(R2 + 4) 和 R2 +=4 两个操作,是前索引(pre-indexing)的例子。

流水线阻断的情况可通过循环拆解等方法加以改善。一个循环可以考虑拆解以减小跳转指令在循环指令中所占的比重, 进而提高代码效率。下面以一个内存复制函数加以ARM说明。

void memcopy(char *to, char *from, unsigned int nbytes)
{
while(nbytes--)ARM
*to++ = *from++;
}

为简单起见,这里假设nbytes 为16 的ARM倍数(省略对余数的处理)。上面的函数每处理一个字节就要进行一次判断和跳转, 对其中的循环体可作如下拆解:

void memcopy(char *to, char *from, unsigned int nbytes)
{
while(nbytes) {
*to++ = *from++;
*to++ = *from++;ARM
*to++ = *from++;
*to++ = *from++;
nbytes - = 4;
}
}

这样一来, 循环体中的指令数增加了,循环次数却减少了。跳转指令ARM带来的负面影响得以削弱。利用ARM 7 处理器32 位字长的特性, 上述代码可进一步作如下调整:

void memcopy(char *to, char *from, unsigned int nbytes)ARM
{
int *p_to = (int *)to;
int *p_from = (int *)from;
while(nbytes) {
*p_to++ = *p_from++;
*p_to++ = *p_from++;
*p_to++ = *p_from++;
*p_to++ = *p_from++;
nbytes - = 16;
}
}
经过优化后,一次循环可以处理16 个字节。跳转指令带来的影响ARM进一步得到减弱。不过可以看出, 调整后的代码在代码量方面有所增加。

(2)使用寄存器变量
CPU 对寄存器的存取要比对内存的存取快得多ARM, 因此为变量分配一个寄存器, 将有助于代码的优化和运行效率的提高。整型、指针、浮点等类型的变量都可以分配寄存器; 一个结构的部分或者全部也可以分配寄存器。给循环体中需要频繁访问的变量分配寄存器也能在
一定程度上提高程序效率。

1.3 指令集相关的优化方法
有时可以利用ARM7 指令集的特点对程序ARM进行优化。
(1)避免除法
ARM 7 指令集中没有除法指令,其除法是通过调用C 库函数实现的。一个32 位的除法通常需要20~140 个时钟周期。因此, 除法成了一个程序效率的瓶颈, 应尽量避免使用。有些除法可用乘法代替,例如: if ( (x / y) > z)可变通为 if ( x > (y × z)) 。在能满足精度,且存储器空间
冗余的情况下, 也可考虑使用查表法代替除法。当除数为2 的ARM幂次方时, 应用移位操作代替除法。

(2)利用条件执行
ARM 指令集的一个重要特征就是所有的指令均可包含一个可选的条件码。当程序状态寄存器(PSR )中的条件码标志满足指定条件时, 带条件码的指令才能执行。利用条件执行通常可以省去单独的判断ARM指令,因而可以减小代码尺寸并提高程序效率。

(3)使用合适的变量类型
ARM 指令集支持有符号/ 无符号的8 位、16 位、32位整型及浮点型变量。恰当的使用变量的类型,不仅可以节省代码,并且可以提高代码运行效率。应该尽可能地避免使用char、short 型的ARM局部变量,因为操作8 位/16 位局部变量往往比操作3 2 位变量需要更多指令, 请对比下列3 个函数和它们的汇编代码。

intwordinc(inta) wordinc
{ ADD a1,a1,#1
return a + 1; MOV pc,lr
} shortinc
shortshortinc(shorta) ADD a1,a1,#1
{ MOV a1,a1,LSL #16
return a + 1; MOV a1,a1,ASR #16ARM
} MOV pc,lr
charcharinc(chara) charinc
{ ADD a1,a1,#1
return a + 1; AND a1,a1,#&ff
} MOV pc,lr
可以看出, 操作3 2 位变量所需的指令要少于操作8位及16 位变量。

1.4 存储器相关的优化方法
(1)用查表代替计算
在处理器资源紧张而存储器资源相对富裕的情况下, 可以用牺牲存储空间换取运行速度的办法。例如需要频繁计算正弦或余弦函数值时,可预先将函数值计算出来置于内存中供以后ARM查找。

(2)充分利用片内RAM
一些厂商出产的ARM 芯片内集成有一定容量的RAM,如Atmel 公司的AT91R40807 内有128KB 的RAM,夏普公司的LH75400/LH75401 内有32KB 的RAM。处理器对片内RAM 的访问速度要快于对外部RAM 的访问,所以应尽可能将程序调入片内RAM 中运行。若因程序太大无法完全放入片内RAM ,可考虑ARM将使用最频繁的数据或程序段调入片内RAM 以提高程序运行效率。

1.5 编译器相关的优化方法
多数编译器都支持对程序速度和程序大小的优化,有些编译器还允许用户选择可供优化的内容及优化的程度。相比前面的各种优化方法, 通过设置编译器选项对程序进行优化不失为一种简单有效的途径。

2 代码尺寸优化
精简指令集计算机的一个重要特点是指令长度固定, 这样做可以简化指令译码的过程,但却容易导致代码尺寸增加。为避免这个问题,可以考虑采取以下措施来缩减程序ARM代码量。

2.1 使用多寄存器操作指令
ARM 指令集中的多寄存器操作指令LDM/STM 可以加载/ 存储多个寄存器,这在保存/ 恢复寄存器组的状态及进行大块数据复制时非常有效。例如要将寄存器R4~R12 及R14 的内容保存到堆栈中,若用STR 指令共需要10 条,而一条STMEA R13!, {R4 ?? R12, R14} 指令就能达到相同的目的,节省的指令存储空间相当可观。不过需要注意的是, 虽然一条LDM/STM 指令能代替多条LDR/STR 指令,但这并不意味着程序运行速度得到了ARM提高。实际上处理器在执行LDM/STM 指令的时候还是将它拆分成多条单独的LDR/STR 指令来执行。

2.2 合理安排变量顺序
ARM 7 处理器要求ARM程序中的32 位/16 位变量必须按字/ 半字对齐,这意味着如果变量顺序安排不合理, 有可能会造成存储空间的浪费。例如:一个结构体中的4个32 位int 型变量i1 ~ i4 和4 个8 位char 型变量c1 ~ c4,若按照i1、c1、i2、c2、i3、c3、i4、c4 的顺序交错存放时, 由于整型变量的对齐会导致位于2 个整型变量中间的那个8 位char 型变量实际占用32 位的存储器,这样就造成了存储空间的浪费。为避免这种情况, 应将int 型变量和char 型变量按类似i1、i2、i3、i4、c1、c2、c3、c4 的顺序连续存放。

2.3 使用Thumb 指令
为了从根本上有效ARM降低代码尺寸,ARM 公司开发了16 位的Thumb 指令集。Thumb 是ARM 体系结构的扩充。Thumb 指令集是大多数常用32 位ARM 指令压缩成16 位宽指令的集合。在执行时,16 位指令透明的实时解压成32 位ARM 指令并没有性能损失。而且程序在Thumb状态和ARM 状态之间切换是零开销的。与等价的32 位ARM 代码相比,Thumb 代码节省的存储器空间可高达35% 以上。

结语
综上所述,优化的过程是ARM在透彻了解软/ 硬件结构和特性的前提下,充分利用硬件资源,不断调整程序结构使之趋于合理的过程。其目的是最大程度发挥处理器效能,最大限度利用资源,尽可能提高程序在特定硬件平台上的性能。随着ARM 处理器在通信及消费电子等行业中的应用日趋广泛,优化技术将在基于ARM 处理器的程序设计过程中发挥越来越重要的作用。

值得注意的是,程序的优化通常只是软件设计需要达到的诸多目标之一, 优化应在不影响程序正确性、健壮性、可移植性及可维护性的前提下进行。片面追求程序的优化往往会影响健壮性、可移植性等重要目标。

系统分类: 嵌入式   |    用户分类:    |    来源: 无分类

评论(0) | 阅读(185)
发表于:2008-7-13 21:54:54
标签:无标签

1

嵌入式研发工程师面试试题大全(ANSI CC++方面的知识 )

嵌入式研发工程师面试试题大全(ANSI CC++方面的知识 )
 


一.ANSI C/C++方面的知识
1、简答题。
1、 如何在C中初始化一个字符数组。
逐个字符没有考虑到字符串结束符‘\0’,所以会产生意想不到的错误。
  比如以下程序:
int main()
{
       int i;
    char p[6] = {''''a'''',''''b'''',''''c'''',''''d'''',''''e'''',''''f''''};
    printf("%s",p);
    while(1);
    return 0;
}
运行后显示: abcdef@
问题3:const & 指针
类型声明中const用来修饰一个常量,有如下两种写法,那么,请问,下面分别用const限定不可变的内容是什么?    
1)、const在前面
a. const int nValue; //nValue是const
把类型int撇开,变量nValue作为一个整体,因此 nValue是const型;
b. const char *pContent; //*pContent是const, pContent可变
把类型char撇开,变量 *pContent作为一个整体,因此 *pContent是const型;
c. const (char *) pContent;//pContent是const,*pContent可变
把类型char * 撇开,注意这里(char * )是一个整体,而变量 pContent作为一个整体,因此 pContent是const型;
d. char* const pContent; //pContent是const,*pContent可变
const与变量间没有类型,变量 pContent作为一个整体,因此 pContent是const型;
e. const char* const pContent; //pContent和*pContent都是const
这里分为两层,外层:把类型char 撇开,变量 * const pContent作为一个整体,因此 * pContent是const型;内层:没有类型,因此 pContent 是 const 型。
2)、const在后面,与上面的声明对等 (这类型更容易判断)
a.  int const nValue; // nValue是const
    const与变量之间没有类型,const后面那部分整体是const型,因此nValue是const型
b.  char const * pContent;// *pContent是const, pContent可变
    const与变量之间没有类型,const后面那部分整体是const型,因此 * pContent是const型
c.  (char *) const pContent;//pContent是const,*pContent可变
    const与变量之间没有类型,const后面那部分整体是const型,因此 pContent是const型
d.  char* const pContent;// pContent是const,*pContent可变
    const与变量之间没有类型,const后面那部分整体是const型,因此 pContent是const型
e.  char const* const pContent;// pContent和*pContent都是const
分为两层,外层:撇开类型char,const后面那部分整体* const pContent是const型,因此 * pContent是const型;内层:const与pContent之间无类型,因此pContent是const型。
  
C++中CONST
  C中常用:#define 变量名 变量值定义一个值替代,然而却有个致命缺点:缺乏类型检测机制,这样预处理理在C++中成为可能引发错误的隐患,于是引入const.
const使用:
1. 用于指针的两种情况:const是一个左结合的类型修饰符.
  int const *A; //A可变,*A不可变
  int *const A; //A不可变,*A可变
2.限定函数的传递值参数:
void function(const int Var); //传递过来的参数在函数内不可以改变.
3.限定函数返回值型.
const int function(); //此时const无意义
const myclassname function(); //函数返回自定义类型myclassname.


20、C语言的volatile的含义是什么。使用时会对编译器有什么暗示。
         volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化,但有可能会读脏数据。当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
下面是volatile变量的几个例子:
    1). 并行设备的硬件寄存器(如:状态寄存器)
    2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
    3). 多线程应用中被几个任务共享的变量
        嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
Volatile的完全扩展:
    1). 一个参数既可以是const还可以是volatile吗?解释为什么。
是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
    2). 一个指针可以是volatile 吗?解释为什么。
是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
    3). 下面的函数有什么错误:
         int square(volatile int *ptr)
         {
              return *ptr * *ptr;
         }
    这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
    int square(volatile int *ptr)
    {
         int a,b;
         a = *ptr;
         b = *ptr;
         return a * b;
     }
    由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
     long square(volatile int *ptr)
     {
            int a;
            a = *ptr;
            return a * a;
     }

系统分类: 嵌入式   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(303)
发表于:2008-7-12 23:58:28
标签:无标签

1

想成为嵌入式程序员应知道的0x10个基本问题

想成为嵌入式程序员应知道的0x10个基本问题 

推荐C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为带面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。
从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?这个愚蠢的问题吗?如要你答出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上而不上在嵌入式系统上。如果上述任何问题的答案是“是”的话,那么我知道我得认真考虑我是否应该去做这份工作。
从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者C语言的水平。不管怎么样,看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮住。这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。
预处理器(Preprocessor)

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
&#8226;; #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
&#8226;; 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
&#8226;; 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
&#8226;; 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。


2 . 写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
这个测试是为下面的目的而设的:
&#8226;; 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
&#8226;; 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
&#8226;; 懂得在宏中小心地把参数用括号括起来
&#8226;; 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b);

3. 预处理器标识#error的目的是什么?
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
死循环(Infinite loops)

4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1)
{
?}
一些程序员更喜欢如下方案:
for(;
{
?}
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
数据声明(Data declarations)


5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )

答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?


Static
6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
&#8226;; 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
&#8226;; 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
&#8226;; 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。


Const
7.关键字const有什么含意?
我只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?

const int a;
int const a;
const int *a;
int * const a;
int const * a const;

/******/
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
&#8226;; 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
&#8226;; 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
&#8226;; 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。


Volatile
8. 关键字volatile有什么含意?并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
&#8226;; 并行设备的硬件寄存器(如:状态寄存器)
&#8226;; 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
&#8226;; 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
&#8226;; 一个参数既可以是const还可以是volatile吗?解释为什么。
&#8226;; 一个指针可以是volatile 吗?解释为什么。
&#8226;; 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}

下面是答案:
&#8226;; 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
&#8226;; 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
&#8226;; 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}

位操作(Bit manipulation)

9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
&#8226;; 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
&#8226;; 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
&#8226;; 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:


#define BIT3 (0x1 << 3)
static int a;

void set_bit3(void) {
a |= BIT3;
}
void clear_bit3(void) {
a &= ~BIT3;
}

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。
访问固定的内存位置(Accessing fixed memory locations)

10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;

A more obscure approach is:
一个较晦涩的方法是:

*(int * const)(0x67a9) = 0xaa55;

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

中断(Interrupts)

11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}

这个函数有太多的错误了,以至让人不知从何说起了:
&#8226;; ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
&#8226;; ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
&#8226;; 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
&#8226;; 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。

*****
代码例子(Code examples)

12 . 下面的代码输出是什么,为什么?

void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 ”>6”。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。
13. 评价下面的代码片断:

unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */

对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:

unsigned int compzero = ~0;

这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧…

动态内存分配(Dynamic memory allocation)
14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?
这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这么一个小节目:
下面的代码片段的输出是什么,为什么?

char *ptr;
if ((ptr = (char *)malloc(0)) ==
NULL)
else
puts("Got a null pointer");
puts("Got a valid pointer");

这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是“Got a valid pointer”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。
Typedef
:
15 Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

#define dPS struct s *
typedef struct s * tPS;

以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

dPS p1,p2;
tPS p3,p4;

第一个扩展为

struct s * p1, p2;

.
上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。

晦涩的语法

16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c;
c = a+++b;

这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:

c = a++ + b;

因此, 这段代码持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。
好了,伙计们,你现在已经做完所有的测试了。这就是我出的C语言测试题,我怀着愉快的心情写完它,希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年,我就不做现在的工作,也需要找一个。

系统分类: 嵌入式   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(320)
发表于:2008-5-28 11:15:14
标签:无标签

0

[转载]AD采样注意的事项

[转载]AD采样注意的事项

主要针对高精度测量类的AD.  

1:参考电压需要足够精确,推荐使用外部高精准参考电压.  
2:如果PGA可调,增益系数一般是越小噪声越低.  
3:一般最好用到满量程,此时AD精度不浪费.  
4:如果有偏置,需要进行自校.  
5:请注意在使用DEMO板调试时,会由调试口导入PC噪声,由信号连接线导入外部噪声,因此建议使用屏蔽电缆传输信号.  
6:板上注意模拟电源和数字电源,以及模拟地和数字地要分开,减少耦合噪声路径.  
7:使用差分输入可以减少共模噪声,但是差模噪声会增大.  
8:如果是片内集成AD的MCU,支持高速时钟,如果不影响性能,内部工作时钟越低,对您的AD采样引起的干扰越小,如果是板上就需要注意走线和分区.  
9:信号输入前级接滤波电路,一般一阶RC电路较多,注意Fc=1/1000~1/100 采样频率,电阻和电容的参数注意选取.信号接入后级接滤波电路最好采用sinc滤波方式.注意输入偏置电流会限制您外部的滤波电阻阻值的大小.  
R x Ib < 1LSB.  
有的片内AD还有集成输入Buffer,有助与抑制您的噪声,一般是分两当,看输入信号范围和满量程之间的关系.  
AD分为很多中,SAR,FLASH,并行比较型,逐次逼近型,Delta sigma型,一般是速度越高,精度越高越贵,所以ADI之类的公司一直那么富裕,赚黑钱......  
针对不同场合不同成本不同要求分别选用.  
还得注意是您的Layout.  

经验数据:做到以上几点,您的分辨率会提高好几位.

系统分类: 测试测量   |    用户分类: 无分类    |    来源: 转贴

评论(0) | 阅读(296)
发表于:2008-5-28 11:12:13
标签:无标签

1

开发usb驱动程序的方法(转帖)

开发usb驱动程序的方法
开始驱动程序设计

下面的文字是从Microsoft的DDK帮助中节选出来的,它让我们明 白在开始设计驱动程序应该注意些什么问题,这些都是具有普遍 意义的开发准则。应该支持哪些I/O请求在开始写任何代码之前, 应该首先确定我们的驱动程序应该处理哪些IRP例程。
如果你在设计一个设备驱动程序,你应该支持和其他相同类型 设备的NT驱动程序相同的IRP_MJ_XXX和IOCTL请求代码。
如果你是在设计一个中间层NT驱动程序,应该首先确认你下层 驱动程序所管理的设备,因为一个高层的驱动程序必须具有低层 驱动程序绝大多数IRP_MJ_XXX例程入口。高层驱动程序在接到I/O 请求时,在确定自身IRP当前堆栈单元参数有效的前提下 ,设置好IRP中下一个低层驱动程序的堆栈单元,然后再调用IoCallDriver 将请求传递给下层驱动程序处理。
一旦决定好了你的驱动程序应该处理哪些IRP_MJ_XXX,就可以开始 确定驱动程序应该有多少个Dispatch例程。当然也可以考虑把某些 RP_MJ_XXX处理的例程合并为同一例程处理。例如在ChangerDisk和 VDisk里,对IRP_MJ_CREATE和IRP_MJ_CLOSE处理的例程就是同一函数。 对IRP_MJ_READ和IRP_MJ_WRITE处理的例程也是同一个函数。
应该有多少个Device对象?
一个驱动程序必须为它所管理的每个可能成为I/O请求的目标的物理和逻辑设备创建一个命名Device对象。一些低层的驱动程序还可能要创建一些不确定数目的Device对象。例如一个硬盘驱动程序必须为每一个物理硬盘创建一个Device对象,同时还必须为每个物理磁盘上的每个逻辑分区创建一个Device对象。
一个高层驱动驱动程序必须为它所代表的虚拟设备创建一个Device 对象,这样更高层的驱动程序才能连接它们的Device对象到这个驱动程序的Device对象。另外,一个高层驱动程序通常为它低层驱动 程序所创建的Device对象创建一系列的虚拟或逻辑Device对象。
尽管你可以分阶段来设计你的驱动程序,因此一个处在开发阶段的 驱动程序不必一开始就创建出所有它将要处理的所有Device对象。 但从一开始就确定好你最终要创建的所有Device对象将有助于设计者所要解决的任何同步问题。另外,确定所要创建的Device对象还有助于你定义Device对象的Device Extension的内容和数据结构。
开始驱动程序开发
驱动程序的开发是一个从粗到细逐步求精的过程。NT DDK的src\ 目录下有一个庞大的样板代码,几乎覆盖了所有类型的设备驱动程序、高层驱动程序和过滤器驱动程序。在开始开发你的驱动程序之前,你应该在这个样板库下面寻找是否有和你所要开发的类似类型的例程。例如我们所开发的驱动程序,虽然DDK对USB描述得不是很详细,我们还是可以在src\storage\class目录发现很多和USB设备有关的驱动程序。下面我们来看开发驱动程序的基本步骤。

最简的驱动程序框架

1、 写一个DriverEntry例程,在里面调用IoCreateDevice创建 一个Device对象。
2、 写一个处理IRP_MJ_CREATE请求的Dispatch例程的基本框架 (参见DDK Kernel-Mode Drivers 4.4.3描述的一个DispatchCreate 例程所要完成的最基本工作。当然写了DispatchCreate例程后, 要在DriverEntry例程为IRP_MJ_CREATE初始化例程入口)。如果驱动程序创建了多于一个Device对象,则必须为IRP_MJ_CLOSE 请求写一个例程,该例程通常情况下可以和DispatchCreate共用一个例程,参见参见DDK Kernel-Mode Drivers 4.4.3。
3、 编译连接你的驱动程序。
用下面的方法来测试你的驱动程序。
首先按上面介绍的方法安装好驱动程序。
其次我们还得为NT逻辑设备名称和目标Device对象名称之间建立 起符号连接,我们在前面已经知道Device对象名称对WIN32用户模式 是不可见的,是不能直接通过API来访问的,WIN 32 API只能访问NT 逻辑设备名称。我们可以通过修改注册表来建立这两种名称之间的符 号连接。运行REGEDT32.EXE在\HKEY_LOCAL_MACHINE\ System\ CurrentControlSet\Control\ Session Manager\ DOS Devices下建立起符号连接(这种符号连接也可以在驱动程序里调用函数 IoCreateSymbolicLink来创建)。
重新启动系统。
编写一个简单的测试程序调用WIN32API CreateFile函数以刚才你命名的NT逻辑设备名打开这个设备。如果打开成功,那么你也就成功地写出了一个最简单的驱动程序了。
支持更多的设备I/O请求

例如你的驱动程序可能需要对IRP_MJ_READ请求做出响应(完成后可用WIN32 API ReadFile函数进行测试)。如果你的驱动程序需要能够手工卸载,那么还必须对IRP_MJ_CLOSE做出响应。为你所需要处理IRP_MJ_XXX写好处理例程,并在DriverEntry里面初始化好这些例 程入口。
一个低层的驱动程序可能需要最起码一个StartIo,ISR和DpcForIsr 例程,可能需要一个SynchCritSection例程,如果设备使用了DMA, 那么可能还需要一个AdapterControl例程。关于这些例程,请参考 DDK相应文档。
对于高层驱动程序可能需要一个或多个IoCompletion例程,最起码 完成检查I/O状态块然后调用IoCompleteRequest的工作。 如果需要,还要对Device Extension数据结构和内容做些修改

开发usb驱动程序的方法(连载二)

NT还有更多其他的对象,例如中断对象、Controller对象、定时器对象等等,但在我们开发的驱动程序中并没有用到,因此在这里不做介绍。
I/O缓冲策略
很明显的,驱动程序和客户应用程序经常需要进行数据交换,但我们知道驱动程序和客户应用程序可能不在同一个地址空间,因此操作系统必须解决两者之间的数据交换。这就就设计到设备的I/O缓冲策略。

读写请求的I/O缓冲策略

前面说到通过设置Device对象的Flag可以选择控制处理读写请求的I/O缓冲策略。下面对这些缓冲策略分别做一介绍。
1、缓冲I/O(DO_BUFFERED_IO)
在读写请求的一开始,I/O管理器检查用户缓冲区的可访问性,然后分配与调用者的缓冲区一样大的非分页池,并把它的地址放在IRP的AssociatedIrp.SystemBuffer域中。驱动程序就利用这个域来进行实际数据的传输。
对于IRP_MJ_READ读请求,I/O管理器还把IRP的UserBuffer域设置 成调用者缓冲区的用户空间地址。当请求完成时,I/O管理器利用 这个地址将数据从驱动程序的系统空间拷贝回调用者的缓冲区。对 于IRP_MJ_WRITE写请求,UserBuffer被设置为NULL,并把用户缓冲 区的数据拷贝到系统缓冲区中。
2、 直接I/O(DO_DIRECT_IO)
I/O管理器首先检查用户缓冲区的可访问性,并在物理内存中锁定它。然后它为该缓冲区创建一个内存描述表(MDL),并把MDL的地址 存放在IRP的MdlAddress域中。AssociatedIrp.SystemBuffer和 UserBuffer都被设置为NULL。驱动程序可以调用函数 MmGetSystemAddressForMdl得到用户缓冲区的系统空间地址,从而 进行数据操作。这个函数将调用者的缓冲区映射到非份页的地址空 间。驱动程序完成I/O请求后,系统自动从系统空间解除缓冲区的映射。
3、 这两种方法都不是
这种情况比较少用,因为这需要驱动程序自己来处理缓冲问题。 I/O管理器仅把调用者缓冲区的用户空间地址放到IRP的UserBuffer 域中。我们并不推荐这种方式。


IOCTL缓冲区的缓冲策略

IOCTL请求涉及来自调用者的输入缓冲区和返回到调用者的输出 缓冲区。为了理解IOCTL请求,我们先来看看WIN32 API DeviceIoControl函数的原型。
BOOL DeviceIoControl (
HANDLE hDevice, // 设备句柄
DWORD dwIoControlCode, // IOCTL请求操作代码
LPVOID lpInBuffer, // 输入缓冲区地址
DWORD nInBufferSize, // 输入缓冲区大小
LPVOID lpOutBuffer, // 输出缓冲区地址
DWORD nOutBufferSize, // 输出缓冲区大小
LPDWORD lpBytesReturned, // 存放返回字节数的指针
LPOVERLAPPED lpOverlapped // 用于同步操作的Overlapped结构体指针
);
IOCTL请求有四种缓冲策略,下面一一介绍。
1、 输入输出缓冲I/O(METHOD_BUFFERED)
I/O管理器首先分配一个非分页池,它足够大地存放调用者的输入或输出缓冲区(不管哪个更大)。非分页缓冲区的地址放在IRP的AssociatedIrp.SystemBuffer域中,然后把IOCTL的输入数据拷贝 到这个非份页缓冲区中,并把IRP的UserBuffer域设置成调用者输出缓冲区的用户空间地址。当驱动程序完成IOCTL请求时,I/O管理器将这个非份页缓冲区中的数据拷贝到调用者的输出缓冲区。注意这里同一个非份页池同时用于输入和输出缓冲区,因此驱动程序在向缓冲区写东西之前应该把输入的所有数据读出来。
2、 直接输入缓冲输出I/O(METHOD_IN_DIRECT)
I/O管理器首先检查调用者输入缓冲区的可访问性,并在物理内存中将其锁定。然后为该输入缓冲区创建一个MDL,并把指定该MDL的指针存放到IRP的MdlAddress域中。同时,I/O管理器还在非份页池中分配一输出缓冲区,并把这个缓冲区的地址存放在IRP的AssociatedIrp.SystemBuffer域中,并把IRP的UserBuffer域设置成调用者输出缓冲区的用户空间地址。当驱动程序完成IOCTL请求时,I/O管理器将非份页缓冲区中的数据拷贝到调用者的输出缓冲区。
3、 缓冲输入直接输出I/O(METHOD_OUT_DIRECT)
I/O管理器首先检查调用者输出缓冲区的可访问性,并在物理内存中将其锁定。然后为该输出缓冲区创建一个MDL,并把指定该MDL的指针存放到IRP的MdlAddress域中。同时,I/O管理器还在非份页池中分配一输入缓冲区,并把这个缓冲区的地址存放在IRP的AssociatedIrp.SystemBuffer域中, 同时把调用者用户输入缓冲区中的数据拷贝到系统缓冲区中,并把IRP的 UserBuffer域设置为NULL。
4、 上面三种方法都不是(METHOD_NEITHER)
I/O管理器把调用者的输入缓冲区的地址放到IRP当前I/O堆栈单元的Parameters.Devi ceIoControl.TypeInputBuffer域中,把输出缓冲 区的地址存放到IRP的UserBuffer域中。这两个地址都是用户空间地 址。

从上面的说明可以看出,在执行缓冲I/O时,I/O管理器将在非份页池 中分配内存,如果调用者的缓冲区比较大时,分配的非份页池也将 比较大。非份页池是系统比较宝贵的资源,因此,如果调用者的缓 冲区比较大时,我们一般采用直接I/O的方式(例如磁盘读写请求等), 这样不仅节省系统资源,另一方面由于省去了I/O管理器在系统缓冲 区和调用者缓冲区之间的数据拷贝,也提高了效率,这对存在大量 数据传送的驱动程序尤其明显。
可以注意到DDK中的Samples下,几乎所有的例程的读写请求都是直 接I/O的,而对于IOCTL请求则是缓冲区I/O的居多

开发usb驱动程序的方法(连载三)

NT驱动程序的分层结构

驱动程序是指管理某个外围设备的一段程序代码。NT采用更灵活的分层驱动方法,允许杂应用程序和硬件之间 存在几个驱动程序层次。分层机制允许NT更加广泛地定义驱动程序