关于作者

用户名:folsailor
笔名:folsailor
地区: 大连
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



友情博客

心情链接

访问统计:
文章个数:151
评论个数:19
留言条数:4




Powered by BlogDriver 2.1

基督山

 

何为标准: 满意为标, 适可为准!

文章

我的新邮箱liuchongjie@gmail.com  (作者置顶)

今天我在google上升级了我的帐号,

有了一个新的邮箱,以后打算用这个邮箱了,哈哈!

liuchongjie@gmail.com

 

- 作者: folsailor 2007年03月27日, 星期二 21:08  回复(0) |  引用(0) 加入博采

onepiece 栏目下的文章大多转自--大米动漫部落  (作者置顶)

onepiece 栏目下的文章大多转自

http://www.damic.cn/html/index.html

- 作者: folsailor 2007年03月27日, 星期二 21:00  回复(0) |  引用(0) 加入博采

简历中的自我评价用语-----珍藏版 zz
简历中的自我评价用语-----珍藏版 2007-09-03 21:17

Mature,dynamic and honest.

思想成熟、精明能干、为人诚实。
Excellent ability of systematical management.

有极强的系统管理能力。
Ability to work independent1y,mature and resourcefu1.

能够独立工作、思想成熟、应变能力强。
A person with ability plus flexibility should app1y.

需要有能力及适应力强的人。
A stable personality and high sense of responsibility are desirable.

个性稳重、具高度责任感。
Work well with a multi-cultural and diverse work force.

能够在不同文化和工作人员的背景下出色地工作。
Bright,aggressive applicants.

反应快、有进取心的应聘者。
Ambitious attitude essential.

有雄心壮志。
Initiative,independent and good communication skill.

积极主动、独立工作能力强,并有良好的交际技能。
Willing to work under pressure with leardership quality.

愿意在压力下工作,并具领导素质。
Willing to assume responsibilities.

应聘者须勇于挑重担。
Mature,self-motivated and strong interpersonal skills.

思想成熟、上进心强,并具极丰富的人际关系技巧。
Energetic,fashion-minded person.

精力旺盛、思想新潮。
With a pleasant mature attitude.

开朗成熟。
Strong determination to succeed.

有获得成功的坚定决心。
Strong leadership skills.

有极强的领导艺术。
Ability to work well with others.

能够同他人一道很好地工作。
Highly-motivated and reliable person with excellent health and pleasant personality.

上进心强又可靠者,并且身体健康、性格开朗。
The ability to initiate and operate independently.

有创业能力,并能独立地从业。
Strong leadership skill while possessing a great team spirit.

有很高的领导艺术和很强的集体精神。
Be highly organized and effecient.

工作很有条理,办事效率高。
Willing to learn and progress.

肯学习进取。
Good presentation skills.

有良好的表达能力。
Positive active mind essential.

有积极、灵活的头脑。
Ability to deal with personnel at all levels effectively。

善于同各种人员打交道。
Have positive work attitude and be willing and able to work diligently without supervision。

有积极的工作态度,愿意和能够在没有监督的情况下勤奋地工作。
Young,bright,energetic with strong career-ambition.

年轻、聪明、力充沛,并有很强的事业心。
Good people management and communication skills. Team player.

有良好的人员管理和交际能力。能在集体中发挥带头作用。
Able to work under high pressure and time limitation.

能够在高压力下和时间限制下进行工作。
Be elegant and with nice personality.

举止优雅、个人性格好。
With good managerial skills and organizational capabilities.

有良好的管理艺术和组织能力。
The main qualities required are preparedness to work hard, ability to learn, ambition and good health.

主要必备素质是吃苦耐劳精神好、学习能力优、事业心强和身体棒。
Having good and extensive social connections.

具有良好而广泛的社会关系。
Being active, creative and innonative is a plus.

思想活跃、有首创和革新精神尤佳。   
With good analytical capability.

有较强的分析能力

- 作者: folsailor 2007年09月12日, 星期三 09:57  回复(0) |  引用(0) 加入博采

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

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

2007-08-24      嵌入式在线      收藏 | 打印
      引言

      复杂可编程逻辑器件——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模块,其参数列表如图1所示。

      在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_CONTRL为寄存方式,使用嵌入式阵列;rdaddress、rdclock、data、wraddress、wrclock、q为Used,rden、rdclken、wren、wrclken为Unused。设置完成后如图2所示。

       其它存储器的构造方法

       不同的存储器根据各自特点,应用场合也不尽相同。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字节。其系统结构框图如图3所示,图4给出了具体电路连接图。



       根据设计要求,双端口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),使系统具有可在线更新、升级容易等特点,是一种较为理想的系统及电路实现方法。

- 作者: folsailor 2007年08月31日, 星期五 10:25  回复(1) |  引用(0) 加入博采

多口RAM原理ZZ
双口RAM是常见的共享式多端口存储器,其最大特点是共享存储数据,即一个存储器配备两套***的地址线、数据线和控制线,允许两个***的CPU或控制器同时异步的访问存储单元。这种同时异步的访问存储单元需要内部仲裁控制逻辑的控制(BUSY功能输出),由于两个端口对双口RAM存取时存在以下4种情况:
    1.两个端口不同时对同一地址单元存取数据;
    2.两个端口同时对同一地址单元读出数据;
    3.两个端口同时对同一地址单元写入数据;
    4.两个端口同时对同一地址单元操作,一个写入数据,一个读出数据。
    所以,内部仲裁控制逻辑相应的提供以下功能:
    1.对同一地址单元访问的时序控制;
    2.存储单元数据块的访问权限分配;
    3.信令交换逻辑。

    对同一地址单元访问的时序控制

    当左右端口不对同一地址单元存取时,BUSY R=H,BUSY L=H,可正常存储;当左右端口对同一地址单元存储时,有一个端口的BUSY=L,禁止数据的存取,此时,两个端口中先出现的存储请求信号对应的BUSY=H,允许存储,后出现的存储请求信号对应的BUSY=L,禁止存储(注意:两端口间的存储请求信号出现时间差应满足仲裁最小时间间隔TAPS(IDT7132为5ns),否则仲裁逻辑无法判定哪一个端口的存储请求信号在前);在无法判定哪个端口先出现存储请求信号时,控制线BUSY L和BUSY R只有一个为低电平,不会同时为低电平,这样就避免了双端口存取出现错误。

    存储单元数据块的访问权限分配

    存储单元数据块的访问权限分配只允许在某一时间段内由1个CPU对自定义的某一数据块进行读写操作,这将有助于存储数据的保护,更有效地避免地址冲突。信号量(Semaphore,简称SEM)仲裁闭锁就是一种硬件电路结合软件实现访问权限分配方法。SEM单元是与存储单元无关的***标志单元,两个端口分别采用两个触发器可以实现这一功能。两个触发器在初始化时均使SEM允许输出为高电平,等待双方申请SEM,如果收到一方写入的SEM信号(通常低电平写入),仲裁电路将使其中一个触发器的SEM允许输出端为低电平,而闭锁另一SEM允许输出端使其继续保持高电平。只有当先请求的一方撤消SEM信号,即写入高电平,才使另一SEM允许输出端的闭锁得到解除,恢复等待新的SEM申请。

    信令交换逻辑(signaling logic)

    为了提高数据的交换能力,有些双口RAM采用信令交换逻辑来通知对方。这类似与PC操作系统的进程同步的信箱机制。

    以上是双口RAM自身提供的仲裁逻辑控制,也可采用自行设计的仲裁协议,比如FIFO。

- 作者: folsailor 2007年08月31日, 星期五 10:22  回复(0) |  引用(0) 加入博采

Quartus II开发软件中的宏模块
Quartus II开发软件中的宏模块--存储器宏模块
 
Quartus II开发软件中的宏模块--存储器宏模块
RAM宏模块
宏模块名称   功能描述
csdpram    参数化循环共享双端口RAM
lpm_ram_dp   参数化双端口RAM
lpm_ram_dq   参数化RAM,输入/输出端分离
lpm_ram_io   参数化RAM,输入/输出端公用一个端口
FIFO宏模块
宏模块名称   功能描述
csfifo    参数化循环共享FIFO
dcfifo    参数化双时钟FIFO
scfifo    参数化单时钟FIFO
lpm_fifo    参数化单时钟FIFO
lpm_fifo_dc   参数化双时钟FIFO
ROM的设计
lpm_rom
Quartus II开发软件中的宏模块--时序电路宏模块
 

Quartus II开发软件中的宏模块--时序电路宏模块
触发器
宏模块名称 功能描述
lpm_ff  参数化D或T触发器
lpm_dff  参数化D触发器和移位寄存器
lpm_tff  参数化T触发器
enadff  带使能端的D触发器
expdff  用扩展电路实现的D触发器
7470  带预置和清零端的与门JK触发器
7471  带预置端的JK触发器
7472  带预置和清零端的与门JK触发器
7473  带清零端的双JK触发器
7474  带异步预置和异步清零端的双D触发器
7476  带异步预置和异步清零端的双JK触发器
7478  带异步预置、公共清零和公共时钟端的双JK触发器
74107  带清零端的双JK触发器
74109  带预置和清零端的双JK触发器
74112  带预置和清零端的双JK时钟下降沿触发器
74113  带预置端的双JK时钟下降沿触发器
74114  带异步预置、公共清零和公共时钟端的双JK时钟下降沿触发器
74171  带清零端的4D触发器
74172  带三态输出的多端口寄存器
74173  4位D型寄存器
74174  带公共清零端的16进制D触发器
74174b  带公共清零端的16进制D触发器
74175  带公共时钟和清零端的4D触发器
74273  带异步清零端的8进制触发器
74273b  带异步清零端的8进制触发器
74276  带公共预置和清零端的4JK触发器寄存器
74374  带三态输出和输出使能端的8进制D触发器
74374b  带三态输出和输出使能端的8进制D触发器
74376  带公共时钟和公共清零端4JK触发器
74377  带使能端的8进制D触发器
74377b  带使能端的8进制D触发器
74378  带使能端的16进制D触发器
74379  带使能端的4D触发器
74396  8进制存储寄存器
74548  带三态输出的8位两级流水线寄存器
74670  带三态输出的4位寄存器
74821  带三态输出的10位总线接口触发器
74821b  带三态输出的10位D触发器
74822  带三态反相输出的10位总线接口触发器
74822b  带三态反相输出的10位D触发器
74823  带三态输出的9位总线接口触发器
74823b  带三态输出的9位D触发器
74824  带三态反相输出的9位总线接口触发器
74824b  带三态反相输出的9位D触发器
74825  带三态反相输出的8位总线接口触发器
74825b  带三态输出的8进制D触发器
74826   带三态反相输出的9位总线接口触发器
74826b  带三态反相输出的8进制D触发器

锁存器
宏模块名称 功能描述
lpm_latch 参数化锁存器
explatch 用扩展电路实现的锁存器
Inpltch  用扩展电路实现的输入锁存器
nandltch 用扩展电路实现的SR(非)与非门锁存器
norltch  用扩展电路实现的SR或非门锁存器
7475  4位双稳态锁存器
7477  4位双稳态锁存器
74116  带清零端的双4位锁存器
74259  带清零端、可设定地址的锁存器
74279  4路SR(非)锁存器
74373  带三态输出的8进制透明D锁存器
74373b  带三态输出的8进制透明D锁存器
74375  4位双稳态锁存器
74549  8位二级流水线锁存器
74604  带三态输出的8进制2输入多路锁存器
74841  带三态输出的10位总线接口D锁存器
74841b  带三态输出的10位总线接口D锁存器
74842  带三态输出的10位总线接口D锁存器
74842b  带三态输出的10位总线接口D反相锁存器
74843  带三态输出的9位总线接口D锁存器
74844  带三态输出的9位总线接口D反相锁存器
74845  带三态输出的8位总线接口D锁存器
74846  带三态输出的8位总线接口D反相锁存器
74990  8位透明读回锁存器

计数器
宏模块名称 功能描述
lpm_conter 参数化计数器(仅限FLEX系列器件)
gray4  格雷码计数器
unicnt  通用4位加/减计数器,可异步设置、读取、清零和级联的左/右移位寄存器
16cudslr 16位2进制加/减计数器,带异步设置的左/右移位寄存器
16cudsrb 16位2进制加/减计数器,带异步清零和设置的左/右移位寄存器
4count  4位2进制加/减计数器,同步/异步读取,异步清零
8count  8位2进制加/减计数器,同步/异步读取,异步清零
7468  双10进制计数器
7469  双12进制计数器
7490  10/2进制计数器
7492  12进制计数器
7493  4位2进制计数器
74143  4位计数/锁存器,带7位输出驱动器
74160  4位10进制计数器,同步读取,异步清零
74161  4位2进制加法计数器,同步读取,异步清零
74162  4位2进制加法计数器,同步读取,同步清零
74163  4位2进制加法计数器,同步读取,同步清零
74168  同步4位10进制加/减计数器
74169  同步4位2进制加/减计数器
74176  可预置10进制计数器
74177  可预置2进制计数器
74190  4位10进制加/减计数器,异步读取
74191  4位2进制加/减计数器,异步读取
74192  4位10进制加/减计数器,异步清零
74193  4位2进制加/减计数器,异步清零
74196  可预置10进制计数器
74197  可预置2进制计数器
74290  10进制计数器
74292  可编程分频器/数字定时器
7429  2进制计数器
74294  可编程分频器/数字定时器
74390  双10进制计数器
74393  双4位加法计数器,异步清零
74490  双4位10进制计数器
74568  10进制加/减计数器,同步读取,同步和异步清零
74569  2进制加/减计数器,同步读取,同步和异步清零
74590  8位2进制计数器,带三态输出寄存器
74592  8位2进制计数器,带输入寄存器
74668  同步10进制加/减计数器
74669  同步4位2进制加/减计数器
74690  同步10进制计数器,带输出寄存器,多重三态输出,异步清零
74691  同步2进制计数器,带输出寄存器,多重三态输出,异步清零
74693  同步2进制计数器,带输出寄存器,多重三态输出,同步清零
74696  同步10进制加/减计数器,带输出寄存器,多重三态输出,异步清零
74697  同步2进制加/减计数器,带输出寄存器,多重三态输出,异步清零
74698  同步10进制加/减计数器,带输出寄存器,多重三态输出,同步清零
74699  同步2进制加/减计数器,带输出寄存器,多重三态输出,同步清零

分频器
宏模块名称 功能描述
Freqdiv  2,4,8,16分频器
7456  双时钟5,10分频器
7457  双时钟5,6,10分频器

多路复用器
宏模块名称 功能描述
lpm_mux  参数化多路复用器
2lmux  2线-1线多路复用器
16lmux  16线-1线多路复用器
2X8mux  8位总线的2线-1线多路复用器
8lmux  8线-1线多路复用器
74151  8线-1线多路复用器
74151b  8线-1线多路复用器
74153  双4线-1线多路复用器
74157  四2线-1线多路复用器
74158  带反相输出的四2线-1线多路复用器
74251  带三态输出的8线-1线数据选择器
74253  带三态输出的双4线-1线数据选择器
74257  带三态输出的四2线-1线多路复用器
74258  带三态反相输出的四2线-1线多路复用器
74298  带存储功能的四2输入多路复用器
74352  带反相输出的双4线-1线数据选择器/多路复用器
74353  带三态反相输出的双4线-1线数据选择器/多路复用器
74354  带三态输出的8线-1线数据选择器/多路复用器
74356  带三态输出的8线-1线数据选择器/多路复用器
74398  带存储功能的四2输入多路复用器
74399  带存储功能的四2输入多路复用器

移位寄存器
宏模块名称 功能描述
lpm_clshift 参数化组合逻辑移位器
lpm_shiftreg 参数化移位寄存器
barrelst 8位桶形移位器
barrlstb 8位桶形移位器
7491  串入串出移位寄存器
7494  带异步预置和异步清零端的4位移位寄存器
7495  4位并行移位寄存器
7496  5位移位寄存器
7499  带JK串入串出端的4位移位寄存器
74164  串入并出移位寄存器
74164b  串入并出移位寄存器
74165  并行读入8位移位寄存器
74165b  并行读入8位移位寄存器
74166  带时钟禁止端的8位移位寄存器
74178  4位移位寄存器
74179  带清零端的4位移位寄存器
74194  带并行读入端的4位双向移位寄存器
74195  4位并行移位寄存器
74198  8位双向移位寄存器
74199  8位双向移位寄存器
74295  带三态输出端的4位左右移位寄存器
74299  8位通用移位/存储寄存器
74350  带三态输出端的4位移位寄存器
74395  带三态输出端的4位可级联移位寄存器
74589  带输入锁存和三态输出端的8位移位寄存器
74594  带输入锁存的8位移位寄存器
74595  带输入锁存和三态输出端的8位移位寄存器
74597  带输入寄存器的8位移位寄存器
74671  带强制清零和三态输出端的4位通用移位寄存器/锁存器

Quartus II开发软件中的宏模块--运算电路宏模块
 
Quartus II开发软件中的宏模块--运算电路宏模块
加法器和减法器
宏模块名称   功能描述
lpm_add_sub   参数化加法器/减法器
8fadd    8位全加器
8faddb    8位全加器
7480    门控全加器
7482    2位2进制全加器
7483    带快速进位的4位2进制全加器
74183    双进位存储全加器
74283    带快速进位的4位全加器
74385    带清零端的4位加法器/减法器
乘法器
宏模块名称   功能描述
lpm_mult    参数化乘法器
mult2    2位带符号数乘法器
mult24    2X4位并行2进制乘法器
mult4    4位并行2进制乘法器
mult4b    4位并行2进制乘法器
tmult4    4X4位并行2进制乘法器
7497    同步6位速率乘法器
74261    2位并行2进制乘法器
74284    4X4位并行2进制乘法器(输出结果的最高4位)
74285    4X4位并行2进制乘法器(输出结果的最低4位)
除法器
divide和lpm_divide
绝对值运算
lpm_abs
数值比较器
宏模块名称   功能描述
lpm_compare   参数化比较器
8mcomp    8位数值比较器
8mcompb    8位数值比较器
7485    4位数值比较器
74518    8位恒等比较器
74518b    8位恒等比较器
74684    8位数值/恒等比较器
74686    8位数值/恒等比较器
74688    8位恒等比较器
编码器
模块名称   功能描述
74147    10线-3线BCD编码器
74148    8线-3线8进制编码器
74384    带三态输出的8线-3线优先权编码器
译码器
宏模块名称   功能描述
lpm_decode   参数化译码器
16dmux    4位2进制-16线译码器
16ndmux    4位2进制-16线译码器
7442    1线-10线BCD-10进制译码器
7443    余3码-10进制译码器
7444    余3格雷码-10进制译码器
7445    BCD码-10进制译码器
7446    BCD码-7段译码器
7447    BCD码-7段译码器
7448    BCD码-7段译码器
7449    BCD码-7段译码器
74137    带地址锁存的3线-8线译码器
74138    3线-8线译码器
74139    双2线-4线译码器
74145    BCD码-10进制译码器
74154    4线-16线译码器
74155    双2线-4线译码器/多路输出选择器
74156    双2线-4线译码器/多路输出选择器
74246    BCD码-7段译码器
74247    BCD码-7段译码器
74248    BCD码-7段译码器
74445    BCD码-10进制译码器
奇偶校验器
宏模块名称   功能描述
74180    9位奇偶产生器/校验器
74180b    9位奇偶产生器/校验器
74280    9位奇偶产生器/校验器
74280b    9位奇偶产生器/校验器

- 作者: folsailor 2007年08月31日, 星期五 10:18  回复(0) |  引用(0) 加入博采

源码公开的TCP/IP协议栈在远程监测中的应用zz

源码公开的TCP/IP协议栈在远程监测中的应用- -

                                      



作 者:■ 上海大学 张懿慧 陈泉林


摘 要:介绍一个适用于8/16位单片机的嵌入式TCP/IP协议栈(uIP)在发电机远程监测系统中的应用。重点阐述uIP的功能特性、体系结构和相关接口,并详细介绍如何在该协议栈上实现一个嵌入式Web服务器。目前uIP已成功地移植到51单片机上。

关键词:TCP/IP协议栈 uIP 嵌入式Web服务器 远程监测


引 言

  目前,随着互联网的发展,越来越多的工业测控设备已经将网络接入功能作为其默认配置,以实现设备的远程监控和信息分布式处理。笔者曾参与某发电机射频监测仪的开发,该设备主要用于诊断和预警发电机早期故障,并通过RS232接口定时输出电平和状态数据,现场专门设一台PC作接收、显示及存储。每年都要有专家到各发电厂对以往数据作检查和诊断,不胜其烦。因此有必要设计一个RS232到Internet的数据传输模块,以便对发电机的运行状况作远程监测。设计该模块的关键在于如何实现一个嵌入式TCP/IP协议栈,根据以往的经验,自己设计一个协议栈的难度很可能超过应用本身,而采用商业的协议栈似乎又无必要(功能过于复杂),最后笔者选用一种功能简易的免费TCP/IP协议栈uIP 0.9作为设计核心。

1 嵌入式TCP/IP协议栈

  目前,市面上几乎所有的嵌入式TCP/IP协议栈都是根据BSD版的TCP/IP协议栈改写的。在商业嵌入式TCP/IP协议栈大都相当昂贵的情况下,很多人转而使用一些源代码公开的免费协议栈,并加以改造应用。目前较为著名的免费协议栈有:

  lwIP(Light weight TCP/IP Stack)——支持的协议比较完整,一般需要多任务环境支持,代码占用ROM>40KB,不适合8位机系统,没有完整的应用文档;

  uC/IP (TCP/IP stack for uC/OS)——基于uC/OS的任务管理,接口较复杂,没有说明文档。

  笔者采用的协议栈系瑞典计算机科学研究所Adam Dunkels开发的uIP0.9 。其功能特性总结如下:
◇完整的说明文档和公开的源代码(全部用C语言编写,并附有详细注释);
◇极少的代码占用量和RAM资源要求,尤其适用于8/16位单片机(见表1);
◇高度可配置性,以适应不同资源条件和应用场合;
◇支持ARP、IP、ICMP、TCP、UDP(可选)等必要的功能特性;
◇支持多个主动连接和被动连接并发,支持连接的动态分配和释放;
◇简易的应用层接口和设备驱动层接口;
◇完善的示例程序和应用协议实现范例。
        
  正是由于uIP所具有的显著特点,自从0.6版本以来就被移植到多种处理器上,包括MSP430、AVR和Z80等。笔者使用的uIP0.9是2003年11月发布的版本。目前,笔者已将它成功移植到MCS-51 上了。

2 uIP0.9的体系结构

  uIP0.9是一个适用于8/16位机上的小型嵌入式TCP/IP协议栈,简单易用,资源占用少是它的设计特点。它去掉了许多全功能协议栈中不常用的功能,而保留网络通信所必要的协议机制。 其设计重点放在IP、ICMP和TCP协议的实现上,将这三个模块合为一个有机的整体,而将UDP和ARP协议实现作为可选模块。uIP0.9的体系结构如图1所示。
         
  uIP0.9处于网络通信的中间层,其上层协议在这里被称之为应用程序,而下层硬件或固件被称之为网络设备驱动。显然,uIP0.9并不是仅仅针对以太网设计的,它具有媒体无关性。

为了节省资源占用, 简化应用接口, uIP0.9在内部实现上作了特殊的处理。
① 注意各模块的融合,减少处理函数的个数和调用次数,提高代码复用率,以减少ROM占用。
② 基于单一全局数组的收发数据缓冲区,不支持内存动态分配, 由应用负责处理收发的数据。
③ 基于事件驱动的应用程序接口,各并发连接采用轮循处理,仅当网络事件发生时,由uIP内核唤起应用程序处理。这样,uIP用户只须关注特定应用就可以了。传统的TCP/IP实现一般要基于多任务处理环境,而大多数8位机系统不具备这个条件。
④ 应用程序主动参与部分协议栈功能的实现(如TCP的重发机制,数据包分段和流量控制),由uIP内核设置重发事件,应用程序重新生成数据提交发送,免去了大量内部缓存的占用。基于事件驱动的应用接口使得这些实现较为简单。

3 uIP的设备驱动程序接口

  uIP内核中有两个函数直接需要底层设备驱动程序的支持。

  一是uip_input()。当设备驱动程序从网络层收到一个数据包时要调用这个函数,设备驱动程序必须事先将数据包存放到uip_buf[]中,包长放到uip_len,然后交由uip_input()处理。当函数返回时,如果uip_len不为0,则表明有带外数据(如SYN,ACK等)要发送。当需要ARP支持时,还需要考虑更新ARP表或发出ARP请求和回应,示例如下。

#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
uip_len = ethernet_devicedriver_poll(); //接收以太网数据包
//(设备驱动程序)
if(uip_len>0){ //收到数据
if(BUF->type = = HTONS(UIP_ETHTYPE_IP)) { //是IP包吗?
uip_arp_ipin(); //去除以太网头结
//构,更新ARP表
uip_input(); //IP包处理
if(uip_len>0){ //有带外回应数据
uip_arp_out(); //加以太网头结构,在主动连接时可能要
//构造ARP请求
ethernet_devicedriver_send(); //发送数据到以太网
//(设备驱动程序)
}
}else if (BUF->type = = HTONS(UIP_ETHTYPE_ARP)) {
//是ARP请求包
uip_arp_arpin(); //如是是ARP回应,更新ARP表;如果是
//请求,构造回应数据包
if(uip_len>0) { //是ARP请求,要发送回应
ethernet_devicedriver_send(); //发ARP回应到以太网上
}
}
另一个需要驱动程序支持的函数是uip_periodic(conn)。这个函数用于uIP内核对各连接的定时轮循,因此需要一个硬件支持的定时程序周期性地用它轮循各连接,一般用于检查主机是否有数据要发送,如有,则构造IP包。使用示例如下。
for(i=0 ; iuip_periodic(i);
if(uip_len > 0){
uip_arp_out();
ethernet_devicedriver_send();
}
}

  从本质上来说, uip_input()和uip_periodic()在内部是一个函数,即uip_process (u8t flag), UIP的设计者将uip_process(UIP_DATA)定义成uip_input(),而将uip_process(UIP_TIMER)定义成uip_periodic(),因此从代码实现上来说是完全复用的。

4 uIP的应用程序接口

  为了将用户的应用程序挂接到uIP中,必须将宏UIP_APPCALL()定义成实际的应用程序函数名, 这样每当某个uIP事件发生时,内核就会调用该应用程序进行处理。如果要加入应用程序状态的话,必须将宏UIP_APPSTATE_SIZE定义成应用程序状态结构体的长度。在应用程序函数中,依靠uIP事件检测函数来决定处理的方法,另外可以通过判断当前连接的端口号来区分处理不同的连接。下面的示例程序是笔者实现的一个Web服务器应用的框架。

#define UIP_APPCALL uip51_appcall
#define UIP_APPSTATE_SIZE sizeof(struct uip51app_state)
struct uip51app_state{
unsigned char *dataptr;
unsigned int dataleft;
};

void uip51_initapp{ //设置主机地址
u16_t ipaddr[2];
uip_ipaddr(ipaddr, 202 ,120,127,192 );
uip_sethostaddr(ipaddr);
uip_listen(HTTP_ORT); //HTTP WEB PORT(80);
}

void uip51_appcall(void){
struct uip51app_state *s;
s = (struct uip51app_state *)uip_conn->appstate;
//获取当前连接状态指针
if(uip_connected()) {
… //有一个客户机连上
}
if(uip_newdata()||uip_rexmit()) { //收到新数据或需要重发
if(uip_datalen()>0){
if(uip_conn->lport = = 80) { //收到GET HTTP请求
update_table_data(); //根据电平状态数据表动态
//生成网页
s->dataptr=newpage;
s->dataleft=2653;
uip_send(s->dataptr,s->dataleft);
//发送长度为2653 B的网页
}
}
}
if(uip_acked()) { //收到客户机的ACK
if(s->dataleft>uip_mss()&&uip_conn->lport = = 80){
//发送长度>最大段长时
s->dataptr+=uip_conn->len; //继续发送剩下的数据
s->dataleft-=uip_conn->len;
uip_send(s->dataptr,s->dataleft);
}
return;
}
if(uip_poll())
{ … //将串口缓存的数据复制到
//电平状态数据表
return;
}
if(uip_timedout()|| //重发确认超时
uip_closed()|| //客户机关闭了连接
uip_aborted()){ //客户机中断连接
return; }
}

5 uIP0.9在发电机远程监测系统中的应用

  笔者设计了一个嵌入式Web模块UIPWEB51,用于将发电机射频监测仪串口输出的数据上网,以实现对发电机工作状态的远程监测,目前已取得初步成功。该模块的硬件框图如图2所示。
       
  单片机采用的是Atmel的AT89C55WD,它内置20KB 程序Flash,512字节RAM,3个定时器/计数器,工作在22.1184MHz时具有约2MIPS的处理速度。 网卡芯片同样采用的是低成本的RTL8019AS, 是一款NE2000兼容的网卡芯片。系统外扩了32KB的SRAM,用于串口数据和网络数据的缓冲,另外还存放了uIP的许多全局变量。

  UIPWEB51的主程序采用中断加轮循的方式,用中断触发的方式接收发电机射频监测仪发出的数据,并设置了一个接收队列暂存这些数据。在程序中轮循有无网络数据包输入,如有则调用uIP的相关处理函数(如上uip_input()使用示例);如无则检测定时轮循中断是否发生。这里将T2设为uIP的定时轮循计数器, 在T2中断中设置轮循标志,一旦主程序检测到这一标志就调用uip_periodic()轮循各连接(如上uip_periodic()使用示例)。

  UIPWeb51的应用程序(如uIP的应用程序接口示例),这个Web服务器首先打开80端口的监听,一旦有客户机要求连上,uIP内部会给它分配一个连接项, 接着等收到客户机IE浏览器发出的“GET HTTP…”请求后, 将发电机电平与状态数据队列中的数据填入网页模板,生成一幅新的网页发给客户机。因为这幅网页的大小已经超过uIP的最大段长(MSS), 因此在uIP内核第一次实际只发出了MSS个字节, 在等到下一次轮循到该连接并且收到上次数据包的ACK时,发送剩下的网页数据。在连接处于空闲的时候(uip_poll()),应用程序可以从串口队列中读出原始数据,经格式处理后再存到发电机电平与状态数据队列中,而在这个队列中保存着当前1min的设备工作数据,以便下次更新网页时使用。在网页中添加了更新按钮,一旦浏览器用户点击了按钮, 浏览器会自动发出CGI请求, UIPWEB51收到后,立即发送包含最新数据的网页。如果uIP接收ACK超时,它会自动设置重发标志,应用程序中可以用uip_rexmit()来检测这个标志,重新生成网页并发送。一旦用户关闭了浏览器,uIP也会自动检测到这一事件(应用程序中可以用uip_closed()来检测),并且释放掉这个连接项。
图3是UIPWEB51的总体程序结构图。
        
6 测试结果

  将uIP0.9配置成允许4个并发连接,1个监听端口, 10个ARP表项,去掉UDP支持,UIP_BUFSIZE=1500和其它优化选项。用KEIL C编译,整个uIP0.9内核模块代码量小于8KB(含Web应用程序),内核对RAM的占用小于2KB(不含网页)。整个系统程序的代码量小于12KB,占用的RAM小于10KB。另外,在公网上测试了该模块的传输速度,大于20Kbps,对于此项应用已达到要求。目前,该模块正准备应用于新一代的发电机射频监测系统中。

                 参考文献
1 RTL8019AS Realtek Full-Duplex Ethernet Controller with Plug and Play Function Specification, 2002
2 ATMEL AT89C55WD datasheet, 2001
3 Adam Dunkels . uIP 0.9 reference manual, 2003
4 Adam Dunkels. uIP - A free Small TCP/IP Stack, 2002
5 Douglas E.Comer. 用TCP/IP进行网际互联(卷1). 林瑶等译. 北京:电子工业出版社, 2001
6 王罡,林立志编著. 基于Windows的TCP/IP编程. 北京: 清华大学出版社, 2000

- 作者: folsailor 2007年08月16日, 星期四 20:10  回复(0) |  引用(0) 加入博采

冷静!

其它没有必要为这样生气了,

反正也快毕业了

既然都是那么长的时间了,反正也习惯了

所以呢,要平静,冷静了,毕业就好

TMD

哈哈

开心快乐的!

所以说呢,平平淡淡,顺顺利利的毕业就好!

要稳要平静要冷静要适应要麻木要一笑了之要淡然无所谓的东西而已!

- 作者: folsailor 2007年08月16日, 星期四 16:05  回复(1) |  引用(0) 加入博采

DSP的特点zz
小申 @ 2006-11-27 11:01


DSP的特点

 

对于没有使用过DSP的初学者来说,第一个困惑就是DSP其他的嵌入式处理器究竟有什么不同,它和单片机,ARM有什么区别。事实上,DSP也是一种嵌入式处理器,它完全可以完成单片机的功能。
唯一的重要的区别在于DSP支持单时钟周期的“乘-加”运算。这几乎是所有厂家的DSP芯片的一个共特征。几乎所有的DSP处理器的指令集中都会有一条MAC指令,这条指令可以把两个操作数从RAM中取出相乘,然后加到一个累加器中,所有这些操作都在一个时钟周期内完成。拥有这样一条指令的处理器就具备了
DSP功能。
具有这条指令就称之为数字信号处理器的原因在于,所有的数字信号处理算法中最为常见的算术操作就是“乘-加”。这是因为数字信号处理中大量使用了内积,或称“点积”的运算。无论是FIR滤波,FFT,信号相关,数字混频,下变频。所有这些数字信号处理的运算经常是将输入信号与一个系数表或者与一个本地参考信号相乘然后积分(累加),这就表现为将两个向量(或称序列)进行点积,在编程上就变成将输入的采样放在一个循环buffer里,本地的系数表或参考信号也放在一个buffer里,然后使用两个指针指向这两个buffer。这样就可以在一个loop里面使用一个MAC指令将二者进行点积运算。这样的点积运算对与处理器来说是最快的,因为仅需一个始终周期就可以完成一次乘加。
了解DSP的这一特点后,当我们设计一个嵌入式系统时,首先要考虑处理器所实现的算法中是否有点积运算
,即是否要经常进行两个数组的乘加,(记住数字滤波,相关等都表现为两个数组的点积)如果有的话,每秒要做多少次,这样就能够决定是否采用DSP,采用多高性能的DSP了。

浮点与定点

浮点与定点也是经常是初学者困惑的问题,在选择DSP器件的时候,是采用浮点还是采用定点,如果用定点是16位还是32位?其实这个问题和你的算法所要求的信号的动态范围有关。
定点的计算不过是把一个数据当作整数来处理,通常AD采样来的都是整数,这个数相对于真实的模拟信号有一个刻度因子,大家都知道用一个16位的AD去采样一个0到5V的信号,那么AD输出的整数除以2^16再乘以5V就是对应的电压。在定点DSP中是直接对这个16位的采样进行处理,并不将它转换成以小数表示的电压,因为定点DSP无法以足够的精度表示一个小数,它只能对整数进行计算。
而浮点DSP的优势在于它可以把这个采样得到的整数转换成小数表示的电压,并不损失精度(这个小数用科学记数法来表示),原因在于科学记数法可以表示很大的动态范围的一个信号,以IEEE754浮点数为例,
单精度浮点格式: [31] 1位符号 [30-23]8位指数 [22-00]23位小数
这样的能表示的最小的数是+-2^-149,最大的数是+-(2-2^23)*2^127.动态范围为20*log(最大的数/最小的数)=1667.6dB 这样大的动态范围使得我们在编程的时候几乎不必考虑乘法和累加的溢出,而如果使用定点处理器编程,对计算结果进行舍入和移位则是家常便饭,这在一定程度上会损失是精度。原因在于定点处理处理的信号的动态范围有限,比如16位定点DSP,可以表示整数范围为1-65536,其动态范围为20*log(65536/1)=96dB.对于32定点DSP,动态范围为20*log(2^32/1)=192dB,远小于32位ieee浮点数的1667.6dB,但是,实际上192dB对绝大多数应用所处理的信号已经足够了。
由于AD转换器的位数限制,一般输入信号的动态范围都比较小,但在DSP的信号处理中,由于点积运算会使中间节点信号的动态范围增加,所以主要考虑信号处理流程中中间结果的动态范围,以及算法对中间结果的精度要求,来选择相应的DSP。另外就是浮点的DSP更易于编程,定点DSP编程中程序员要不断调整中间结果的P,Q值,实际就是不断对中间结果进行移位调整和舍入。

DSP与RTOS

TI的CCS提供BIOS,ADI的VDSP提供VDK,都是基于各自DSP的嵌入式多任务内核。DSP编程可以用单用C,也可以用汇编,或者二者结合,一般软件编译工具都提供了很好的支持。我不想在这里多说BIOS,VDK怎么用这在相应的文档里说的很详细。我想给初学者说说DSP的RTOS原理。用短短几段话说这个复杂的东西也是挑战!^_^

其实DSP的RTOS和基于其他处理器的通用RTOS没什么大的区别,现在几乎人人皆知的uCOSii也很容易移植到DSP上来,只要把寄存器保存与恢复部分和堆栈部分改改就可以。一般在用BIOS和VDK之前,先看看操作系统原理的书比较好。uCOS那本书也不错。
BIOS和VDK其实是一个RTOS内核函数集,DSP的应用程序会和这些函数连接成一个可执行文件。其实实现一个简单的多任务内核并不复杂,首先定义好内核的各种数据结构,然后写一个scheduler函数,功能是从所有就绪任务中(通过查找就绪任务队列或就绪任务表)找出优先级最高的任务,并恢复其执行。然后在此基础上写几个用于任务间通信的函数就可以了,比如event,message box,等等。
RTOS一般采用抢先式的任务调度方式,举例说当任务A等待的资源available的时候,DSP会执行一个任务调度函数scheduler,这个函数会检查当前任务是否比任务A优先级低,如果是的话,就会把它当前挂起,然后把任务A保存在堆栈里寄存器值全部pop到DSP处理器中(这就是所谓的任务现场恢复)。接着scheduler还会把从堆栈中取出任务A挂起时的程序执行的地址,pop到PC,使任务A继续执行。这样当前任务就被任务A抢先了。
使用RTOS之后,每个任务都会有一个主函数,这个函数的起始地址就是该任务的入口。一般每个任务的主函数里有一个死循环,这个循环使该任务周期地执行,完成一部分算法模块的功能,其实这个函数跟普通函数没任何区别,类似于C语言中的main函数。一个任务创建的时候,RTOS会把这个函数入口地址压入任务的堆栈中,好象这个函数(任务)刚发生过一次中断一样。一旦这个新创建任务的优先级在就绪队列中是最高的,RTOS就会从其堆栈中弹出其入口地址开始执行。
有一个疑问是,不使用RTOS,而是简单使用一个主循环在程序中调用各个函数模块,一样可以实现软件的调度执行。那么,这种常用的方法与使用RTOS相比有什么区别呢?其实,使用主循环的方法不过是一种没有优先级的顺序执行的调度策略而已。这种方法的缺点在于,主循环中调用的各个函数是顺序执行的,那么,即使是一个无关紧要的函数(比如闪烁一个LED),只要他不主动返回,也会一直执行直到结束,这时,如果发生一个重要的事件(比如DMA buffer full 中断),就会得不到及时的响应和处理,只能等到那个闪烁LED的函数执行完毕。这样就使整个DSP处理的优先次序十分不合理。而在使用了RTOS之后,当一个重要的事件发生时,中断处理会进入RTOS,并调用scheduler,这时scheduler 会让处理这一事件的任务抢占DSP处理器(因为它的优先级高)。而哪个闪烁LED任务即使晚执行几毫秒都没任何影响。这样整个DSP的调度策略就十分合理。
RTOS要说的内容太多,我只能讲一下自己的一点体会吧

DSP与正(余)弦波

在DSP的应用中,我们经常要用到三角函数,或者合成一个正(余)弦波。这是因为我们喜欢把信号通过傅立叶变换映射到三角函数空间来理解信号的频率特性。信号处理的一些计算技巧都需要在DSP软件中进行三角函数计算。然而三角函数计算是非线性的计算,DSP并没有专门的指令来求一个数的正弦或余弦。于是我们需要用线性方法来近似求解。
一个直接的想法是用多项式拟合,这也正是大多数DSP C编译器提供正余弦库函数所采用的方法。其原理是把三角函数向函数空间{1,x,x^2,x^3....}上投影,从而获得一系列的系数,用这些系数就可以拟合出三角函数。比如,我们在[0,pi/2]区间上拟合sin,只需在matlab中输入以下命令:
x=0:0.05:pi/2;
p=polyfit(x,sin(x),5)
就得到5阶的多项式系数:
p =
0.00581052047605 0.00580963216172 -0.17193865685360
0.00209002716293 0.99969270087312 0.00000809543448
于是在[0,pi/2]区间上:
sin(x)= 0.00000809543448+0.99969270087312*x+ 0.00209002716293*x^2-0.17193865685360*x^3+
0.00580963216172*x^4+0.00581052047605*x^5
于是在DSP程序中,我们可以通过用乘加(MAC)指令计算这个多项式来近似求得sin(x)
当然如果用定点DSP还要把P这个多项式系数表用一定的Q值来改写成定点数。

这样的三角函数计算一般都需要几十个cycle 的开销。这对于某些场合是不能容忍的

另一种更快的方法是借助于查表,比如,我们将[0,pi/2]分成32个区间,每个区间长度就为pi/64,在每个区间上我们使用直线段拟合sin曲线,每个区间线段起点的正弦值和线段斜率事先算好,存在RAM里,这样就需要在在RAM里存储64个
常数:
32个起点的精确的正弦值(事先算好): s[32]={0,sin(pi/64),sin(pi/32),sin(pi/16)....}
32个线段的斜率: f[32]={0.049,.....}
对于输入的每一个x,先根据其大小找到所在区间i,通常x用定点表示,一般取其高几位就是系数i了,然 后通过下式即可求出sin(x):
sin(x)= s[i]*f[i]
这样一般只需几个CYCLE就可以算出正弦值,如果需要更高的精度,可以将区间分得更细,当然,也就需 要更多的RAM去存储常数表。
事实上,不仅三角函数,其他的各种非线性函数都是这样近似计算

- 作者: folsailor 2007年08月15日, 星期三 11:13  回复(0) |  引用(0) 加入博采

规划zz
小申 @ 2006-12-18 14:18

[1]好好规划自己的路,不要跟着感觉走!根据个人的理想决策安排,绝大部分人并不指望成为什么院士或教授,而是希望活得滋润一些,爽一些。那么,就需要慎重安排自己的轨迹。从哪个行业入手,逐渐对该行业深入了解,不要频繁跳槽,特别是不要为了一点工资而转移阵地,从长远看,这点钱根本不算什么,当你对一个行业有那么几年的体会,以后钱根本不是问题。频繁地动荡不是上策,最后你对哪个行业都没有摸透,永远是新手!

    [2]可以做技术,切不可沉湎于技术。千万不可一门心思钻研技术!给自己很大压力,如果你的心思全部放在这上面,那么注定你将成为孔乙己一类的人物!适可而止为之,因为技术只不过是你今后前途的支柱之一,而且还不是最大的支柱,除非你只愿意到老还是个工程师!

    [3]不要去做技术高手,只去做综合素质高手!在企业里混,我们时常瞧不起某人,说他“什么都不懂,凭啥拿那么多钱,凭啥升官!”这是普遍的典型的工程师的迂腐之言。8051很牛吗?人家能上去必然有他的本事,而且是你没有的本事。你想想,老板搞经营那么多年,难道见识不如你这个新兵?人家或许善于管理,善于领会老板意图,善于部门协调等等。因此务必培养自己多方面的能力,包括管理,亲和力,察言观色能力,攻关能力等,要成为综合素质的高手,则前途无量,否则只能躲在角落看示波器!技术以外的技能才是更重要的本事!!从古到今,美国*本,一律如此!

    [4]多交社会三教九流的朋友!不要只和工程师交往,认为有共同语言,其实更重要的是和其他类人物交往,如果你希望有朝一*当老板或高层管理,那么你整*面对的就是这些人。了解他们的经历,思维习惯,爱好,学习他们处理问题的模式,了解社会各个角落的现象和问题,这是以后发展的巨大的本钱,没有这些以后就会笨手笨脚,跌跌撞撞,遇到重重困难,交不少学费,成功的概率大大降低!

    [5]知识涉猎不一定专,但一定要广!多看看其他方面的书,金融,财会,进出口,税务,法律等等,为以后做一些积累,以后的用处会更大!会少交许多学费!!

    [6]抓住时机向技术管理或市场销售方面的转变!要想有前途就不能一直搞开发,适当时候要转变为管理或销售,前途会更大,以前搞技术也没有白搞,以后还用得着。搞管理可以培养自己的领导能力,搞销售可以培养自己的市场概念和思维,同时为自己以后发展积累庞大的人脉!应该说这才是前途的真正支柱!!!

    [7]逐渐克服自己的心里弱点和性格缺陷!多疑,敏感,天真(贬义,并不可爱),犹豫不决,胆怯,多虑,脸皮太薄,心不够黑,教条式思维。。。这些工程师普遍存在的性格弱点必须改变!很难吗?只在床上想一想当然不可能,去帮朋友守一个月地摊,包准有效果,去实践,而不要只想!不克服这些缺点,一切不可能,甚至连项目经理都当不好--尽管你可能技术不错!

    [8]工作的同时要为以后做准备!建立自己的工作环境!及早为自己配置一个工作环境,装备电脑,示波器(可以买个二手的),仿真器,编程器等,业余可以接点活,一方面接触市场,培养市场感觉,同时也积累资金,更重要的是准备自己的产品,咱搞技术的没有钱,只有技术,技术的代表不是学历和证书,而是产品,拿出象样的产品,就可技术转让或与人合作搞企业!先把东西准备好,等待机会,否则,有了机会也抓不住!

    [9]要学会善于推销自己!不仅要能干,还要能说,能写,善于利用一切机会推销自己,树立自己的品牌形象,很必要!要创造条件让别人了解自己,不然老板怎么知道你能干?外面的投资人怎么相信你?提早把自己推销出去,机会自然会来找你!搞个个人主页是个好注意!!特别是培养自己在行业的名气,有了名气,高薪机会自不在话下,更重要的是有合作的机会... 

    [10]该出手时便出手!永远不可能有100%把握!!!条件差不多就要大胆去干,去闯出自己的事业,不要犹豫,不要彷徨,干了不一定成功,但至少为下一次冲击积累了经验,不干永远没出息,而且要干成必然要经历失败。不经历风雨,怎么见彩虹,没有人能随随便便成功

- 作者: folsailor 2007年08月15日, 星期三 11:13  回复(0) |  引用(0) 加入博采

PCB设计经验zz
小申 @ 2006-11-27 11:07


布局:
总体思想:在符合产品电气以及机械结构要求的基础上考虑整体美观,在一个PCB板上,元件的布局要求要均衡,疏密有序。
1.印制板尺寸必须与加工图纸尺寸相符,符合PCB制造工艺要求,放置MARK点。
2.元件在二维、三维空间上有无冲突?
3.元件布局是否疏密有序,排列整齐?是否全部布完?
4.需经常更换的元件能否方便的更换?插件板插入设备是否方便?
5.热敏元件与发热元件之间是否有适当的距离?
6.调整可调元件是否方便?
7.在需要散热的地方,装了散热器没有?空气流是否通畅?
8.信号流程是否顺畅且互连最短?
9.插头、插座等与机械设计是否矛盾?
10.蜂鸣器远离柱形电感,避免干扰声音失真。
11.速度较快的器件如SRAM要尽量的离CPU近。
12.由相同电源供电的器件尽量放在一起。

布线:  
1.走线要有合理的走向:如输入/输出,交流/直流,强/弱信号,高频/低频,高压/低压等...,它们的走向应该是呈线形的(或分离),不得相互交融。其目的是防止相互干扰。最好的走向是按直线,但一般不易实现,避免环形走线。对于是直流,小信号,低电压PCB设计的要求可以低些。输入端与输出端的边线应避免相邻平行, 以免产生反射干扰。必要时应加地线隔离,两相邻层的布线要互相垂直,平行容易产生寄生耦合。
2.选择好接地点:一般情况下要求共点地,数字地与模拟地在电源输入电容处相连。
3.合理布置电源滤波/退耦电容:布置这些电容就应尽量靠近这些元部件,离得太远就没有作用了。在贴片器件的退耦电容最好在布在板子另一面的器件肚子位置,电源和地要先过电容,再进芯片。
4.线条有讲究:有条件做宽的线决不做细;高压及高频线应园滑,不得有尖锐的倒角,拐弯也不得采用直角,一般采用135度角。地线应尽量宽,最好使用大面积敷铜,这对接地点问题有相当大的改善。 设计中应尽量减少过线孔,减少并行的线条密度。
5.尽量加宽电源、地线宽度,最好是地线比电源线宽,它们的关系是:地线>电源线>号线。
6.数字电路与模拟电路的共地处理,现在有许多PCB不再是单一功能电路(数字或模拟电路),而是由数字电路和模拟电路混合构成的。因此在布线时就需要考虑它们之间互相干扰问题,特别是地线上的噪音干扰。
数字电路的频率高,模拟电路的敏感度强,对信号线来说,高频的信号线尽可能远离敏感的模拟电路器件,对地线来说,整个PCB对外界只有一个结点,所以必须在PCB内部进行处理数、模共地的问题,而在板内部数字地和模拟地实际上是分开的它们之间互不相连,只是在PCB与外界连接的接口处(如插头等)。数字地与模拟地有一点短接。
7.信号线布在电(地)层上
在多层印制板布线时,由于在信号线层没有布完的线剩下已经不多,再多加层数就会造成浪费也会给生产增加一定的工作量,成本也相应增加了,为解决这个矛盾,可以考虑在电(地)层上进行布线。首先应考虑用电源层,其次才是地层。因为最好是保留地层的完整性。
8.关键信号的处理,关键信号如时钟线应该进行包地处理,避免产生干扰,同时在晶振器件边做一个焊点使晶振外壳接地。
9.设计规则检查(DRC)
  布线设计完成后,需认真检查布线设计是否符合设计者所制定的规则,同时也需确认所制定的规则是否符合印制板生产工艺的需求,一般检查有如下几个方面:
线与线,线与元件焊盘,线与贯通孔,元件焊盘与贯通孔,贯通孔与贯通孔之间的距离是否合理,是否满足生产要求。
  电源线和地线的宽度是否合适,电源与地线之间是否紧耦合(低的波阻抗)?在PCB中是否还有能让地线加宽的地方。
  对于关键的信号线是否采取了最佳措施,如长度最短,加保护线,输入线及输出线被明显地分开。
  模拟电路和数字电路部分,是否有各自独立的地线。
  后加在PCB中的图形(如图标、注标)是否会造成信号短路。
  对一些不理想的线形进行修改。
    在PCB上是否加有工艺线?阻焊是否符合生产工艺的要求,阻焊尺寸是否合适,字符标志是否压在器件焊盘上,以免影响电装质量。
  多层板中的电源地层的外框边缘是否缩小,如电源地层的铜箔露出板外容易造成短路。
10.关于EMC方面:
a.尽可能选用信号斜率较慢的器件,以降低信号所产生的高频成分。
b.注意高频器件摆放的位置,不要太靠近对外的连接器。
c.注意高速信号的阻抗匹配,走线层及其回流电流路径,以减少高频的反射与辐射。
d.在各器件的电源管脚放置足够与适当的去耦合电容以缓和电源层和地层上的噪声。特别注意电容的频率响应与温度的特性是否符合设计所需。
e电源层比地层内缩20H,H为电源层与地层之间的距离。
11.GERBER输出检查
检查输出的GERBER文件是否按层叠顺序要求输出,在CAM350里查看每一层数据以及DRILL表,同时注意特殊孔如方孔的输出。


总结体会,供参考:
1.要有合理的走向:如输入/输出,交流/直流,强/弱信号,高频/低频,高压/低压等...,它们的走向应该是呈线形的(或分离),不得相互交融。其目的是防止相互干扰。最好的走向是按直线,但一般不易实现,最不利的走向是环形,所幸的是可以设隔离带来改善。对于是直流,小信号,低电压PCB设计的要求可以低些。所以“合理”是相对的。


2.选择好接地点:小小的接地点不知有多少工程技术人员对它做过多少论述,足见其重要性。一般情况下要求共点地,如:前向放大器的多条地线应汇合后再与干线地相连等等...。现实中,因受各种限制很难完全办到,但应尽力遵循。这个问题在实际中是相当灵活的。每个人都有自己的一套解决方案。如能针对具体的电路板来解释就容易理解。


3.合理布置电源滤波/退耦电容:一般在原理图中仅画出若干电源滤波/退耦电容,但未指出它们各自应接于何处。其实这些电容是为开关器件(门电路)或其它需要滤波/退耦的部件而设置的,布置这些电容就应尽量靠近这些元部件,离得太远就没有作用了。有趣的是,当电源滤波/退耦电容布置的合理时,接地点的问题就显得不那么明显。


4.线条有讲究:有条件做宽的线决不做细;高压及高频线应园滑,不得有尖锐的倒角,拐弯也不得采用直角。地线应尽量宽,最好使用大面积敷铜,这对接地点问题有相当大的改善。


5.有些问题虽然发生在后期制作中,但却是PCB设计中带来的,它们是:过线孔太多,沉铜工艺稍有不慎就会埋下隐患。所以,设计中应尽量减少过线孔。同向并行的线条密度太大,焊接时很容易连成一片。所以,线密度应视焊接工艺的水平来确定。焊点的距离太小,不利于人工焊接,只能以降低工效来解决焊接质量。否则将留下隐患。所以,焊点的最小距离的确定应综合考虑焊接人员的素质和工效。


焊盘或过线孔尺寸太小,或焊盘尺寸与钻孔尺寸配合不当。前者对人工钻孔不利,后者对数控钻孔不利。容易将焊盘钻成“c”形,重则钻掉焊盘。导线太细,而大面积的未布线区又没有设置敷铜,容易造成腐蚀不均匀。即当未布线区腐蚀完后,细导线很有可能腐蚀过头,或似断非断,或完全断。所以,设置敷铜的作用不仅仅是增大地线面积和抗干扰。以上诸多因素都会对电路板的质量和将来产品的可靠性大打折扣。

- 作者: folsailor 2007年08月15日, 星期三 11:11  回复(0) |  引用(0) 加入博采

求职DSP中c 必考题zz
 2006-11-27 10:28


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

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)


#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
?; #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)


?; 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
?; 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
?; 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
2 . 写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。


#define MIN(A,B) ((A) <= (B) ? (A) : (B))

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

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有三个明显的作用:
?; 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
?; 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
?; 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。


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呢?我也如下的几下理由:
?; 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
?; 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
?; 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
Volatile

8. 关键字volatile有什么含意?并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
?; 并行设备的硬件寄存器(如:状态寄存器)
?; 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
?; 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
?; 一个参数既可以是const还可以是volatile吗?解释为什么。
?; 一个指针可以是volatile 吗?解释为什么。
?; 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}

下面是答案:
?; 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
?; 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
?; 这段代码有点变态。这段代码的目的是用来返指针*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。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
?; 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
?; 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
?; 用 #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;
}

这个函数有太多的错误了,以至让人不知从何说起了:
?; ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
?; ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
?; 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
?; 与第三点一脉相承,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语言测试题,我怀着愉快的心情写完它,希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年,我就不做现在的工作,也需要找一个。

- 作者: folsailor 2007年08月15日, 星期三 11:10  回复(0) |  引用(0) 加入博采

DSP板/系统的硬件设计和系统调试zz
小申 @ 2006-11-27 10:40

引 言

    随着数字信号处理器(DSP)和现场可编程门阵列器件(FPGA)的发展,采用DSP+FPGA的数字硬件系统显示出其优越性,正愈来愈得到人们重视。通用的DSP优点是通过编程可以应用到广泛的产品中,并且主流制造商生产的DSP 已能满足算法控制结构复杂、运算速度高、寻址方式灵活和通信性能强大等需求。但是传统的DSP 采用冯-诺依曼(Von Neumann)结构或某种类型扩展。此种结构本质上是串行的,因此遇到需处理的数据量大,对处理速度要求高,但是对运算结构相对比较简单的底层信号处理算法来说显不出优点,适合采用FPGA 硬件实现。这样,采用DSP + FPGA 的数字硬件系统就可以把二者优点结合一起,兼顾速度和灵活性,既满足底层信号处理要求,又满足高层信号处理要求。

DSP+FPGA系统特点与组成 

    DSP + FPGA 系统最大优点是结构灵活,有较强的通用性,适合于模化设计,从而能够提高算法效率;同时其开发周期较短,系统容易维护和扩展,适合实时信号处理。
    DSP + FPGA系统的核心由DSP芯片和可重构器件FPGA 组成。另外还包括一些外围的辅助电路,如存储器、先进先出(FIFO)器件及FLASH ROM等。FPGA 电路与DSP 相连,利用DSP 处理器强大的I/O功能实现系统内部的通信。从DSP 角度看,FPGA相当于它的宏功能协处理器。外围电路辅助核心电路进行工作。DSP 和FPGA 各自带有RAM,用于存放处理过程所需要的数据及中间结果。FLASH ROM 中存储了DSP 执行程序和FPGA 的配置数据。先进先出(FIFO)器件则用于实现信号处理中常用到的一些操作,如延迟线、顺序存储等。

DSP 和FPGA 的外围电路及连接线

    DSP 的外围电路主要是FLASH、ROM和SRAM,需要连接地址线、数据线和控制线。它需要连接的连线主要包括DSP 模式选择、时钟模式选择、选择外部时钟或本机晶振产生的时钟﹑JTAG接口和电源等。FPGA外围电路主要是用于配置的PROM、FLASH ROM、模数转换和先进先出(FIFO)器件等。它需要连接的连线主要包括FPGA 模式选择、全局时钟、选择外部时钟或本机晶振产生的时钟、JTAG接口、输出/输入接口、测试口和电源等。
    DSP与FPGA 的连接包括(1)数据线:DSPD0~DSPD15;(2)地址线:DSPA0~DSPA15;(3)控制线:BIO、HCS、DSPPS、DSPDS、DSPIS、DSPMSTRB、DSPIOSTRB、DSPREADY、DSPRW;(4)中断线:DSPINT0、DSPINT1、DSPINT2、DSPINT3;(5)串口线:BFSX0、BDX0、BCLKX0、BFSR0、BDR0、BCLKR0、BFSX1、BDX1、BCLKX1、BFSR1、BDR1、BCLKR1。

FPGA 的编程设计过程

    用户可编程FPGA 芯片体系结构由二个主要的可配置的元件组成:可配置的逻辑模块(configurable logic blocks-CLBs)和输入/输出模块(input/output blocks IOBs)。CLBs 提供构造逻辑功能;IOBs 提供可配置的逻辑模块与包装引脚之间的接口;CLBs 之间的相互连接通过一个通用的路由矩阵(a general routing matrix -GRM)。GRM由位于水平方向和垂直方向路由通道交叉路口的路由开关阵列构成。构成一个通用模块的每一CLB也提供本地的路由资源把CLB连接到GRM。
    FPGA的编程设计过程。首先,从数字系统的行为级模拟得到各个行为模块的逻辑要求,包括组合逻辑和时序逻辑两方面要求,即得到NGD文件。其次,通过对NGD文件进行优化、组合和映射,创建基本逻辑单元。每一个基本逻辑单元由功能产生器(F/G)、D型触发器、进位逻辑等组成。然后,把基本逻辑单元包装成可配置的逻辑块CLBs,其中一小部分包装成输入/输出模块IOBs或TBUF模块。输出没有布局和布线的NCD文件。最后,把NCD文件中每一个可配置的逻辑块CLBs和每一个输入/输出块IOBs分别放置到FPGA 芯片上一个特定位置,称为布局。并且连通路由,完成布线。如果设计结果不能达到定时指标或不能完全接通,需要重新进行布局和布线。以上设计开发过程可以借助于开发工具及加入适当的手工设置操作完成。对于FPGA的布局有以下指导原则:
门阵列小于100K系统的I/O的布局原则
    在FPGA顶部和底部的I/O引脚用作控制信号,即信号路由方向是垂直方向;在FPGA左部和右部的I/O引脚用作数据总线,因为FPGA 的体系结构适合数据作水平方向流动。
100K系统的I/O的布局原则
    门阵列大于100K系统的I/O的布局原则与小于100K系统相同。需要附加考虑下列问题。第一,可配置逻辑组的控制信号和数据总线尽量接近内部逻辑。第二,主要的信号可以放置在接近芯片的中间位置,以便容易接入到水平方向的长线。
数据总线布局
    当算术运算的比特数大于5比特,需要使用进位逻辑。进位链路的布线需要使用垂直方向。它同时影响到内部逻辑单元和I/O布局。开关同时输出信号会引起地线上反弹,为了减少这种反弹现象,需要把开关输出线分散开,因而把数据总线的一半安排到芯片的左端I/O,另一半安排到芯片的右端I/O,保持数据的流动的方向仍然是左右方向流动。
内部逻辑布局技巧
    当进行内部逻辑布局时,特别是进行逻辑进位或TBUF设计时,内部逻辑应安排成垂直队列,这是一种关键的技巧。数据路途元件配置应使得数据交互地通过电路片,不管是从左到右,还是从右到左都是认可的。应使用最短的路由,以至节点之间的延迟最小。为了避免路由阻塞,应使得路由的总趋势不要互相成十字交叉。
TBUF布局
    双向数据总线长线沿着水平方向放置,三相使能控制线沿着垂直方向放置。布局和布线时,软件会按照上述原则自动地排列TBUF。在大型的FPGA 的芯片中,沿着水平方向的长线可以在中间断开,使TBUF的版图使用半长线,以便增加利用率。如果你的设计不能满足性能指标,或者布线不能完全接通,可以通过下列途径来改进:可以增加定时约束;在关键路由上减少逻辑;增加在布局和布线上的考虑和编辑版面图等。
FPGA的配置
    XILINX公司生产的FPGA ,它的配置是通过装载配置数据到内部配置存储器实现的。注意:如果装载一个不正确的比特流,会引起配置的失败并损害装置。配置使用的引脚有专用的,也有作为一般目的用的输入和输出引脚。一旦配置完成后,后者可重复使用。专用的引脚包含:模式引脚(M2,M1, M0)、配置时钟引脚(CCLK)、PROGRAM 引脚、DONE 引脚和边界扫描引脚(TDI, TDO, TMS, TCK)。依赖于配置模式的选择,CCLK 能够由FPGA 产生,或者由外部产生。FPGA 支持四种配置模式:主串模式、从串模式、选择MAP 模式和边界扫描模式(JTAG)。通过边界扫描口的配置总是有效的,它独立于模式选择。选择边界扫描模式能够简单地转向其它模式。

- 作者: folsailor 2007年08月15日, 星期三 11:08  回复(0) |  引用(0) 加入博采

2812快速算法--小申
小申 @ 2006-12-07 09:45

文章来源:国外电子元器件

摘要:论述了以DSP芯片TMS320F2812为核心的一种测量仪器的组成原理、设计思想以及快速定点算法的实现方法,同时对定点和浮点算法结果进行了比较。

    关键词:定点芯片;浮点运算;快速算法;系统配置;TMS320F2812

1 TMS320F2812简介

TMS320F2812是TI公司的一款用于控制的高性能、多功能、高性价比的32位定点DSP芯片。该芯片兼容TMS320LF2407指令系统最高可在150MHz主频下工作,并带有18k×16位0等待周期片上SRAM和128k×16位片上FLASH(存取时间36ns)。其片上外设主要包括2×8路12位ADC(最快80ns转换时间)、2路SCI、1路SPI、1路McBSP、1路eCAN等,并带有两个事件管理模块(EVA、EVB),分别包括6路PWM/CMP、2路QEP、3路CAP、2路16位定时器(或TxPWM/TxCMP)。另外,该器件还有3个独立的32位CPU定时器,以及多达56个独立编程的GPIO引脚,可外扩大于1M×16位程序和数据存储器。TMS320F2812采用哈佛总线结构,具有密码保护机制,可进行双16×16乘加和32×32乘加操作,因而可兼顾控制和快速运算的双重功能。

通过对TMS320F2812定点DSP芯片合理的系统配置和编程可实现快速运算,本文着重对此加以说明。

2 TMS320F2812基本系统配置

2.1 TMS320F2812时钟

TMS320F2812的片上外设按输入时钟可分为如下4个组:

(1)SYSOUTCLK组:包括CPU定时器和eCAN总线,可由PLLCR寄存器动态地修改;

(2)OSCCLK组:主要是看门狗电路,由WDC寄存器设置分频系数;

(3)低速组:有SCI、SPI、McBSP,可由LOSPCP寄存器设置分频系数;

(4)高速组:包括EVA/B、ADC,可由HISPCP寄存器设置分频系数。

为了使系统具有较快的工作速度,除了定时器和SCI等少数需要低速时钟的地方,其它外设均可以150MHz时钟工作。



    2.2 存储空间    3.2 FIR滤波器编程

图1所示是TMS320F2812的内部存储空间映射图。TMS320F2812为哈佛(Harvard)结构的DSP,即在同一个时钟周期内可同时进行一次取指令、读数据和写数据的操作。在逻辑上有4M×16位程序空间和4M×16位数据空间,但物理上已将程序空间和数据空间统一为一个4M×16位的存储空间,各总线按优先级由高到低的顺序为:数据写、程序写、数据读、程序读。其中由CY7C1041扩展的256k×16位SARAM位于Zone 6(0x100000~0x13FFFF),存取时间不小于12ns;128k×16位FLASH空间(0x3D8000~ 0x3F7FFF)取指时间不小于36ns。为了尽可能提高器件的工作速度,在对FLASH寄存器编程使其在较高速度下工作的同时,可将时间要求比较严格的程序(如时延计算子程序、FIR滤波子程序等)、变量(如FIR滤波器系数、自适应算法的权向量等)各堆栈空间搬移到H0、L0、L1、M0、M1空间来运行。

2.3 中断

TMS320F28x系列DSP片上都有非常丰富的外设,每个片上外设均可产生1个或多个中断请求。中断由两级组成,其中一级是PIE中断,另一级是CPU中断。CPU中断有32个中断源,包括RESET、NMI、EMUINT、ILLEGAL、12个用户定义的软件中断USER1~USER12和16个可屏蔽中断(INT1~INT14、RTOSINT和DLOGINT)。所有软件中断均属于非屏蔽中断。由于CPU没有足够的中断源来管理所有的片上外设中断请求,所以在TMS320F28x系列DSP中设置了一个外设中断扩展控制器(PIE)来管理片上外设和外部引脚引起的中断请求。

PIE中断共有96个,被分为12个组,每组内有8个片上外设中断请求,96个片上外设中断请求信号可记为INTx.y(x=1,2,…,12;y=1,2,…,8)。每个组输出一个中断请求信号给CPU,即PIE的输出INTx(x=1,2,…,…12)对应CPU中断输入的INT1~INT12。TMS320F28x系列DSP的96个可能的PIE中断源中有45个被TMS320F2812使用,其余的被保留作以后的DSP器件使用。

ADC、定时器、SCI编程等均以中断方式进行,可提高CPU的利用率。

2.4 复位引导

图2所示是TMS320F2812的片上引导ROM空间映射。其此导程序配置在图2中的0x3FFC00~0x3FFFBF,根据图1,设置VMAP=1,MP/MC=0,ENPIE=0,复位向量指向片上0x3FFFC0,而片上0x3FFFC0中内容为0x3FFC00,即指向图2中的引导程序。配置表2中的GPIOF4(SCITXDA)=1,则转向FLASH中的0x3F7FF6开始执行程序,最后在0x3F7FF6片设置跳转指令指向用户程序的开始处,以开始运行用户程序。由于在实际应用中使用了PIE中断,因此,在用户应用程序中,应首先初始化PIE中断向量表,然后使能PIE。

3 编程设计

编程是实现系统正常工作和快速运算必不可少的重要环节。在系统配置合理的条件下,用定点芯片实现快速运算的关键用整数取代浮点数进行计算处理。用C编译器时,为产生最优代码,应遵循以下原则:

(1)将除法转换为乘法,尽量使编译器产生MAC指令,以充分利用DSP的硬件乘法器资源进行快速运算,且应使MAC的操作数为局部变量以分配到寄存器中(或到一个累加器中)。

(2)尽可能使用静态直接插入函数,以节省函数调用的额外开销。

(3)对FOR循环的上限,使用常数或具有常数属性的变量可产生重复指令RPT。

3.1 ADC编程

TMS20F2812带有两个8选1多路切换器和双采样/保持器的12位ADC,模拟量输入范围为0~3V,最快转换速率为80ns,选用10kSPS采样率,并采用EVA的定时器(0.1ms)自动触发方式,可同时采样4个通道,并采用每次转换结束的中断方式来纪录采样结果(右移4位)。

转换结果=(212-1)×(输入的模拟信号-ADCLO)/3

ADC转换时,首先初始化DSP系统,然后设置PIE中断矢量表,再初始化ADC模块,接着将ADC中断的入口地址装入中断矢量表并开中断,然后再启动0.1ms定时器,同时等待ADC中断,最后在ADC中断中读取ADC转换结果,并用软件启动下一次中断。

目标信号对某些低频干扰非常敏感,它将直接响应到定位结果和数据的有效性。为了在滤波后不影响时延数据的计算,可采用线性相位的FIR滤波器。滤波器系数h(i)用MATLAB的产生,并在变成整形然后固化到程序中,这样做(而不是单独计算滤波器系数)的目的是为了实现快速滤波而不会过多增加整个测量系统定位计算的时间。

3.3 定位算法的移植

由于定位算法采用自适应时延估计法,因此计算量非常庞大,对DSP芯片性能要求较高。TMS320F2812具有32位硬件乘法器和累加器,其RPT指令非常适合循环计算,处理能力可达150MIPS,因而具有较高的性能。但它是一款定点处理芯片,需要使用定点算法来解决处理量大的问题。因此,对初始数据、权矢量应采用16位整形变量(Q=12:由ADC转换精度决定),而循环计算中产生的中间结果则使用32位整形变量(Q=20:在结果不溢出的情况下尽量满足计算精度);至于对三角函数等的运算,可用查表法并利用图2中的表格来进行快速计算。

C编译器带有浮点运算库,因此可将浮点算法和定点算法的结果进行比较,对于4路各1024点数据处理,用浮点算法实现约需3.6秒,而用定点算法只需1.3秒。

另外,还可对算法进行优化。第一是将经常使用的中间变量配置到等待周期为0的内存中;第二是采用FLASH加速技术(使能FOPT寄存器的ENPIPE位实现预指机制的FLASH流水线模式),这样可以达到100~120MIPS的处理能力,大大高于其本身36ns的读取能力。需要注意的是,由于TMS320F2812的保护机制,对FLASH寄存器进行存取的这段程序必须搬移到L0、L1中执行。尽管这样,将这段对时间要求比较荷记得的算法移植到内存H0中,可以达到最高150MIPS的处理速度,并能使用函数memcpy()完成程序的搬移。

- 作者: folsailor 2007年08月15日, 星期三 11:03  回复(0) |  引用(0) 加入博采

.cmd文件的编写--小申zz
小申 @ 2006-11-27 21:28

链接命令文件.cmd将链接信息放在一个文件中,以便在多次使用同样的链接信息时调用。在命令文件中两个十分有用的伪指令MEMORY&SECTION,来指定实际应用中的存储器结构和运行地址的映射.MEMORY用来指定目标存储器结构,Memory下可以通过PAGE选项配置地址空间。链接器把每一页当做一个独立的存储空间,通常情况下,PAGE0代表程序存储器用来存放程序,PAGE1代表数据存储器,用来存放数据。由编译器生成的可重定位的代码和数据块叫做SECTION块,Section块用来控制段的构成与地址分配。对于不同的系统配置,Section的分配方式也不相同,链接器通过Section来控制地址的分配,所以Section的分配成了配置.cmd文件的重要环节。以下是Section的定义及分配的详细介绍。
   它分成两个基本的部分:
1.被初始化的Section(包含数据表和可执行代码)
  .text它包含所有的可执行代码和常数,必须放在程序页
  .cinit它包含初始化的变量和常量表,要求放在程序页
  .pinit 它包括全局构造器(C++)初始化的变量表,要求放在程序页
  .const它包括符串,声明,以及被明确初始化过的全局和静态变量,要求放在低地址数据页。
  .econst他是在使用大存储器模式时使用的,包括字符串,声明,以及被明确初始化过的全局变量和静态变量,可以在数据页的任何地方。
  .switch它包括为转换声明设置的表格,可以放在程序页,也可以放在低地址的数据页。
2. 未被初始化的Section(为程序运行中创建和存放的变量在存储器中保留空间)
  .bss它为全局变量和静态变量保留空间.在程序开始运行时,C导入路径把数据从.cinit节复制出去然后存在.bss节中.要求放在低地址的数据页.
  .ebbs它是在远访问(C)和大存储器模式下使用,它为全局变量和静态变量保留空间.在程序开始运行时,C导入路径把数据从.cinit段复制出去然后存在.ebss节中。可以存放在数据页的任何地方。
  .stack为C系统堆栈保留空间,这部分存储器为用来将声明传给函数及为局部变量留出空间。要求放在低地址的数据页。
  .system动态存储器分配保留空间,这个空间用于malloc函数,如果不使用malloc函数,这个段的大小就是0。要求放在低地址的数据页。
  .esystem动态存储器分配保留空间,这个空间用于外部malloc函数,如果不使用外部malloc函数,这个段的大小就是0。可也放在数据页的任何地方。

- 作者: folsailor 2007年08月15日, 星期三 11:02  回复(0) |  引用(0) 加入博采

常见问题--小申zz
小申 @ 2006-11-27 14:28

0.查找芯片资料
1.找不到元件库怎么办?
2.Protel中常用元件的封装
3.Protel常见操作问题
4.由SCH生成PCB时提示出错(Protel)
5.电容,二极管,三极管,有源晶振等器件的极性
6.不同逻辑电平的接口
7.电阻,电容值的识别
8.一些术语的解释
9.常见软件破解

   PAGE 0 查找芯片资料

哪里找芯片资料:
www.datasheet.com.cn (首选!还包括一些应用电路图,现在经常打不开,建议用alldatasheet)
www.21ic.com   (国内最大的芯片网站,包括报价。建议作为首选!)
www.21icsearch.com  (直达21IC网站的搜索页面)
www.ic.net.cn
www.icbase.com  (力源公司)
www.icbase.com/newweb1/icsearch/searchcenter.asp    (直达力源的搜索页面)
www.google.com    (通用搜索引擎)
www.baidu.com     (通用搜索引擎)
还有各大半导体厂商的主页

    PAGE 1 找不到元件库怎么办?

画元件库是做电路的一个基本能力。
如果是分离器件或者接插件之类,毫无疑问,必须自己做!而且建议买到货了以后量着做。
免得拿到板子以后发现自己买的插座死活装不进去,哪怕只差半毫米。呵呵!
如果有什么芯片的库找不到,首先建议开代理到protel自己的主页看看,它主页有很多比较新的元件库。似乎EDA的FTP也有同样的一份。
如果protel的最新库里面还是找不到这个芯片,那么就应该自己画了。不要怕麻烦,自己画
还是放心一些,同时也可以熟悉一下引脚。
不推荐到BBS上来问,原因很简单:
复杂的库是别人费了很长时间做出来的,没有义务白送给你。当然,有好心人愿意给你也可以。这要看运气了,可以作为一种手段,但是不能完全指望。
至于简单的库嘛,有你发文章问的这点时间,自己就可以做出来了,还省得欠别人一个人情!

     PAGE 2  Protel中常用元件的封装

以下元件在Protel DOS Schematic Libraries.ddb,Miscellaneous Devices.ddb(以上
是schlib)Advpcb.ddb,Transistors.ddb,General IC.ddb(以上是PCBlib)等库文件中,可以使用通配符“*”进行查找
                               直插                    表贴
电阻,小电感                    axial0.3/axial0.4       0805/0603等
小电容                          RAD0.1/ RAD0.2          0805/0603等
电解电容                        (RB.2/.4)               1210/1812/2220等
小功率三极管                    TO-92A/B                SOT-23
大功率三极管(三端稳压)          T0-220
小功率二极管                    DIODE-0.4               自己做
双列IC                          DIPxx                   SO-xx    xx代表引脚数
有源晶振                        DIP14(保留四个顶点,去掉中间10个焊盘)
四方型IC                        大部分需要自己用向导画,尺寸参照datasheet
接插件                          SIPxx/IDCxx,DB9/DB25(注意male/female的区别)等
电位器,开关,继电器等          买好了元件,量好尺寸自己画
提醒:*使用封装时最好少用水平/垂直翻转功能
     *自己建好的元件库或者PCB,一定要1:1的打印出来,和实际比较,以确保无误
     *有条件的话,尽量先买好器件,再定封装,可以节省很多眼泪

    PAGE 3  Protel操作问题:

★如何将原理图中的电路粘贴到Word中
tools->preferences->Graphical Editing,取消Add Template to Clipboard,然后复制
★取消备份及DDB文件减肥:
"File"菜单左边一个向下的灰色箭头
prference-->create backup files
design utilities-->perform compact after closing
★提示:"the maximum number of licenses has been exceeded..."
因为同一局域网内有多台机器在使用盗版Protel的缘故,不影响使用。或者拔掉网线即可
★PCB中去掉铺铜:
edit--->delete即可
★自己画的元件,一拿出来跑到离光标十万八千里的地方去了:
 画元件得时候没有画在坐标原点附近.在元件库里修改,然后Update

   PAGE 4 由SCH生成PCB时提示出错(Protel)

sch编辑界面中选择design-->updatepcb,在出现的对话框中按“Preview Change”按钮,选中 Only show Errors会列出所有错误
      错误类型                                解决办法
1.footprint not found           确保所有的器件都指定了封装
                                确保指定的封装名与PCB中的封装名一致
                                确保你的库已经打开或者被添加
 2.node not found               确认没有“footprint not found” 类型的错误
                                编辑PCBlib,将对应引脚名改成没有找到的那个node
3.Duplicate sheet number        degisn-options-organization,给每张子电路图编号
4.二极管的引脚错误              这是Protel99SE的一个经典问题,它的pcb库和sch库的引脚名                              不同,   一个叫1,2;一个叫A,K,
                                修改办法:新建一个封装,统一用12或者AK

                          
     PAGE5  电容,二极管,三极管等器件的极性问题:

直插铝电解:负极附近有黑色的“-”标记,如果没有剪腿的话,长腿为正
贴片钽电解:有横杠的一头为正
二极管:    有圈的一头为负
小功率三极管                                     ____________________________

贴片(SOT-23):           直插(TO-92)             |    有源晶振                |


     C                        /^^/|             |   (以DIP14的引脚位置作参考)|


    _[]_ (俯视图)            |^^^||(正视图)     |           ___              |


   |    |                    |___||             |   NC  1 -|。 |-14  VCC     |


   []--[]                    | | |              |          |   |             |


   B    E                    | | |              |   GND 7 -|___|-8   OUT     |


                             E B C              |____________________________|


     PAGE6  不同逻辑电平的接口问题:
CMOS<-->TTL     电源电压相同的条件下可以兼容
3.3V--->5V      一般可以直接驱动(以datasheet为准!)
5V--->3.3V      74LVT245/74LVT16245
5V<-->3.3V      74LVC4245/74LVC16245
ECL-->TTL       MC10125
TTL-->ECL       MC10124

  
    PAGE 7 电阻,电容值的识别

色环电阻:
黑 棕 红 橙 黄 绿 蓝 紫 灰 白
0  1  2  3  4  5  6  7  8  9
最后一环表示精度,离其他几环比较远(一般是棕色)
环表示阶数(10^n)
前面的是有效数字
例: “绿棕黑黑棕”这个电阻是510欧
小电容:
通常以三位数标注,以pf为单位
前两位是有效数字,最后一位表示阶数(为0时,可以空缺):
例:“332”这个电容是3300 pf
   “471”这个电容是470pf
   “47”这个电容是47pF

    PAGE 8  一些基本概念和术语

OC(Open Drain):
  集电极(漏极)开路输出,使用上拉电阻后可以适应不同的接口电平,并具有“线与”的功能(即两个输出端直接相连就可以实现“and”逻辑)
上拉下拉:
  即将输出节点通过电阻连接到电源(上拉)或地(下拉)。OC输出器件必须使用上拉电阻以输出高电平,此外为了加大输出引脚的驱动能力也常使用上拉电阻(典型的如单片机)。
  阻值的选择原则包括:从节约功耗及芯片的灌电流能力考虑应当足够大;从确保足够的驱动电流考虑应当足够小;对于高速电路,过大的上拉电阻可能边沿变平缓。综合考虑以上三点,通常在1k到10k之间选取。对下拉电阻也有类似道理。
rail-to-rail:
  即“轨到轨”,指输出(或输入)电压范围与电源电压相等或近似相等,在低电压和单电源系统中很有用。
TBD:
  To Be Determined,未定参数,常见于Preview版datasheet,通常意味着芯片还没有正式投产

    PAGE 9   软件破解问题

1.为什么我的modelsim5.7x在打开wave文件时总会退出?(感谢windsoul热心指导 0 && image.height>0){if(image.width>=510){this.width=510;this.height=image.height*510/image.width;}}" border=0>
答:在使用cross的modelsimkgv2003.exe生成的license.dat时可能会出现以上情况,
解决方法是使用::URL::0 && image.height>0){if(image.width>=510){this.width=510;this.height=image.height*510/image.width;}}" align=absBottom border=0>ftp://ia.hust.edu.cn/Incoming/IC%20design/modelsim57/vlm.exe
替换modelsim_57x\win32\vlm.exe

- 作者: folsailor 2007年08月15日, 星期三 11:02  回复(0) |  引用(0) 加入博采

电容--小申zz
小申 @ 2006-11-27 11:12

《中庸》有云:喜怒哀乐之未发谓之中,发而皆中节谓之和。电容极高明而道中庸,过犹不及。

不要轻视电容哦。。。什么地方都有如果用得不好,死得难看的,所以首先。。。

A、什么是好电容。

1.电容容量越大越好。
很多人在电容的替换中往往爱用大容量的电容。我们知道虽然电容越大,为IC提供的电流补偿的能力越强。且不说电容容量的增大带来的体积变大,增加成本的同时还影响空气流动和散热。关键在于电容上存在寄生电感,电容放电回路会在某个频点上发生谐振。在谐振点,电容的阻抗小。因此放电回路的阻抗最小,补充能量的效果也最好。但当频率超过谐振点时,放电回路的阻抗开始增加,电容提供电流能力便开始下降。电容的容值越大,谐振频率越低,电容能有效补偿电流的频率范围也越小。从保证电容提供高频电流的能力的角度来说,电容越大越好的观点是错误的,一般的电路设计中都有一个参考值的。

2.同样容量的电容,并联越多的小电容越好,
耐压值、耐温值、容值、ESR(等效电阻)等是电容的几个重要参数,对于ESR自然是越低越好。ESR与电容的容量、频率、电压、温度等都有关系。当电压固定时候,容量越大,ESR越低。在板卡设计中采用多个小电容并连多是出与PCB空间的限制,这样有的人就认为,越多的并联小电阻,ESR越低,效果越好。理论上是如此,但是要考虑到电容接脚焊点的阻抗,采用多个小电容并联,效果并不一定突出。

3.ESR越低,效果越好。
结合我们上面的提高的供电电路来说,对于输入电容来说,输入电容的容量要大一点。相对容量的要求,对ESR的要求可以适当的降低。因为输入电容主要是耐压,其次是吸收MOSFET的开关脉冲。对于输出电容来说,耐压的要求和容量可以适当的降低一点。ESR的要求则高一点,因为这里要保证的是足够的电流通过量。但这里要注意的是ESR并不是越低越好,低ESR电容会引起开关电路振荡。而消振电路复杂同时会导致成本的增加。板卡设计中,这里一般有一个参考值,此作为元件选用参数,避免消振电路而导致成本的增加。

4.好电容代表着高品质。
“唯电容论”曾经盛极一时,一些厂商和媒体也刻意的把这个事情做成一个卖点。在板卡设计中,电路设计水平是关键。和有的厂商可以用两相供电做出比一些厂商采用四相供电更稳定的产品一样,一味的采用高价电容,不一定能做出好产品。衡量一个产品,一定要全方位多角度的去考虑,切不可把电容的作用有意无意的夸大。

B、电容爆浆之面面谈

爆浆的种类:

分两类,输入电容爆浆和输出电容爆浆。

对于输入电容来说,就是我是说的C1,C1对由电源接收到的电流进行过滤。

输入电容爆浆和电源输入电流的品质有关。过多的毛刺电压,峰值电压过高,电流不稳定等都使电容过于充放电过于频繁,长时间处于这类工作环境下的电容,内部温度升高很快。超过泄爆口的承受极限就会发生爆浆。

对于输出电容来说,就我说的C2,对经电源模块调整后的电流进行滤波。此处电流经过一次过滤,比较平稳,发生爆浆的可能性相对来说小了不少。但如果环境温度过高,电容同样容易发生爆浆。 

爆,报也。
采用垃圾东西自然要爆,报应啊。
欲知过去因者,见其现在果;欲知未来果者,见其现在因。

爆浆的原因:

电容爆浆的原因有很多,比如电流大于允许的稳波电流、使用电压超出工作电压、逆向电压、频繁的充放电等。但是最直接的原因就是高温。
我们知道电容有一个重要的参数就是耐温值,指的就是电容内部电解液的沸点。当电容的内部温度达到电解液的沸点时,电解液开始沸腾,电容内部的压力升高,当压力超过泄爆口的承受极限就发生了爆浆。所以说温度是导致电容爆浆的直接原因。
电容设计使用寿命大约为2万小时,受环境温度的影响也很大。电容的使用寿命随温度的增加而减小,实验证明环境温度每升高10℃,电容的寿命就会减半。主要原因就是温度加速化学反应而使介质随时间退化失效,这样电容寿命终结。
为了保证电容的稳定性,电容在插板前要经过长时间的高温环境的测试。即使是在100℃,高品质的电容也可以工作几千个小时。同时,我们提到的电容的寿命是指电容在使用过程中,电容容量不会超过标准范围变化的10%。电容寿命指的是电容容量的问题,而不是设计寿命到达之后就发生爆浆。只是无法保证电容的设计的容量标准。

所以,短时期内,正常使用的板卡电容就发生爆浆的情况,这就是电容品质问题。
另外,不正常的使用情况也有可能发生电容爆浆的情况。比如热插拔电脑配件也会导致板卡局部电路电流、电压的剧烈变化,从而引发电容使用故障。

- 作者: folsailor 2007年08月15日, 星期三 11:01  回复(0) |  引用(0) 加入博采

C中难点--小申zz
小申 @ 2006-11-27 11:14


这篇文章主要是介绍一些在复习C语言的过程中笔者个人认为比较重点的地方,较好的掌握这些重点会使对C的运用更加得心应手。此外会包括一些细节、易错的地方。涉及的主要内容包括:变量的作用域和存储类别、函数、数组、字符串、指针、文件、链表等。一些最基本的概念在此就不多作解释了,仅希望能有只言片语给同是C语言初学者的学习和上机过程提供一点点的帮助。

变量作用域和存储类别:

了解了基本的变量类型后,我们要进一步了解它的存储类别和变量作用域问题。

变量类别 子类别
局部变量 静态变量(离开函数,变量值仍保留)
自动变量
寄存器变量
全局变量 静态变量(只能在本文件中用)
非静态变量(允许其他文件使用)

换一个角度

变量类别 子类别
静态存储变量  静态局部变量(函数) 
静态全局变量(本文件) 
非静态全局/外部变量(其他文件引用) 
动态存储变量 自动变量
寄存器变量
形式参数

extern型的存储变量在处理多文件问题时常能用到,在一个文件中定义extern型的变量即说明这个变量用的是其他文件的。顺便说一下,笔者在做课设时遇到out of memory的错误,于是改成做多文件,再把它include进来(注意自己写的*.h要用“”不用<>),能起到一定的效用。static 型的在读程序写结果的试题中是个考点。多数时候整个程序会出现多个定义的变量在不同的函数中,考查在不同位置同一变量的值是多少。主要是遵循一个原则,只要本函数内没有定义的变量就用全局变量(而不是main里的),全局变量和局部变量重名时局部变量起作用,当然还要注意静态与自动变量的区别。

函数:

对于函数最基本的理解是从那个叫main的单词开始的,一开始总会觉得把语句一并写在main里不是挺好的么,为什么偏择出去。其实这是因为对函数还不够熟练,否则函数的运用会给我们编程带来极大的便利。我们要知道函数的返回值类型,参数的类型,以及调用函数时的形式。事先的函数说明也能起到一个提醒的好作用。所谓形参和实参,即在调用函数时写在括号里的就是实参,函数本身用的就是形参,在画流程图时用平行四边形表示传参。

函数的另一个应用例子就是递归了,笔者开始比较头疼的问题,反应总是比较迟钝,按照老师的方法,把递归的过程耐心准确的逐级画出来,学习的效果还是比较好的,会觉得这种递归的运用是挺巧的,事实上,著名的八皇后、汉诺塔等问题都用到了递归。

例子:
long fun(int n)
{
long s;
if(n==1||n==2) s=2;
   else s=n-fun(n-1);
return s;
}
main()
{
printf("%ld",fun(4));

 

数组:

分为一维数组和多维数组,其存储方式画为表格的话就会一目了然,其实就是把相同类型的变量有序的放在一起。因此,在处理比较多的数据时(这也是大多数的情况)数组的应用范围是非常广的。

具体的实际应用不便举例,而且绝大多数是与指针相结合的,笔者个人认为学习数组在更大程度上是为学习指针做一个铺垫。作为基础的基础要明白几种基本操作:即数组赋值、打印、排序(冒泡排序法和选择排序法)、查找。这些都不可避免的用到循环,如果觉得反应不过来,可以先一点点的把循环展开,就会越来越熟悉,以后自己编写一个功能的时候就会先找出内在规律,较好的运用了。另外数组做参数时,一维的[]里可以是空的,二维的第一个[]里可以是空的但是第二个[]中必须规定大小。

冒泡法排序函数:
void bubble(int a[],int n)
{
int i,j,k;
for(i=1,i<n;i++)
   for(j=0;j<n-i;j++)
   if(a[j]>a[j+1])
    {
    k=a[j];
       a[j]=a[j+1];
       a[j+1]=k;
       }
}

选择法排序函数:
void sort(int a[],int n)
{
int i,j,k,t;
for(i=0,i<n-1;i++)
   {
   k=i;
   for(j=i+1;j<n;j++)
      if(a[k]<a[j]) k=j;
      if(k!=i)
         {
         t=a[i];
         a[i]=a[k];
         a[k]=t;
         }
   }
}

折半查找函数(原数组有序):
void search(int a[],int n,int x)
{
int left=0,right=n-1,mid,flag=0;
while((flag==0)&&(left<=right))
   {
   mid=(left+right)/2;
   if(x==a[mid])
      {
      printf("%d%d",x,mid);
      flag =1;
      }
      else if(x<a[mid]) right=mid-1;
                   else left=mid+1;
   }

相关常用的算法还有判断回文,求阶乘,Fibanacci数列,任意进制转换,杨辉三角形计算等等。

字符串:

字符串其实就是一个数组(指针),在scanf的输入列中是不需要在前面加“&”符号的,因为字符数组名本身即代表地址。值得注意的是字符串末尾的‘{post.abstract}’,如果没有的话,字符串很有可能会不正常的打印。另外就是字符串的定义和赋值问题了,笔者有一次的比较综合的上机作业就是字符串打印老是乱码,上上下下找了一圈问题,最后发现是因为

char *name; 

而不是

char name[10]; 

前者没有说明指向哪儿,更没有确定大小,导致了乱码的错误,印象挺深刻的。

另外,字符串的赋值也是需要注意的,如果是用字符指针的话,既可以定义的时候赋初值,即

char *a="Abcdefg"; 

也可以在赋值语句中赋值,即

char *a;
a="Abcdefg"; 

但如果是用字符数组的话,就只能在定义时整体赋初值,即char a[5]={"abcd"};而不能在赋值语句中整体赋值。

常用字符串函数列表如下,要会自己实现:

函数作用  函数调用形式  备注 
字符串拷贝函数 strcpy(char*,char *) 后者拷贝到前者
字符串追加函数 strcat(char*,char *) 后者追加到前者后,返回前者,因此前者空间要足够大
字符串比较函数 strcmp(char*,char *) 前者等于、小于、大于后者时,返回0、正值、负值。注意,不是比较长度,是比较字符ASCII码的大小,可用于按姓名字母排序等。
字符串长度 strlen(char *) 返回字符串的长度,不包括'{post.abstract}'.转义字符算一个字符。
字符串型->整型 atoi(char *)
整型->字符串型 itoa(int,char *,int) 做课设时挺有用的
sprintf(char *,格式化输入) 赋给字符串,而不打印出来。课设时用也比较方便

注:对字符串是不允许做==或!=的运算的,只能用字符串比较函数

指针:

指针可以说是C语言中最关键的地方了,其实这个“指针”的名字对于这个概念的理解是十分形象的。首先要知道,指针变量的值(即指针变量中存放的值)是指针(即地址)。指针变量定义形式中:基本类型 *指针变量名 中的“*”代表的是这是一个指向该基本类型的指针变量,而不是内容的意思。在以后使用的时候,如*ptr=a时,“*”才表示ptr所指向的地址里放的内容是a。

指针比较典型又简单的一应用例子是两数互换,看下面的程序,

swap(int c,int d)
{
int t;
t=c;
c=d;
d=t;
}
main()
{
int a=2,b=3;
swap(a,b);
printf(“%d,%d”,a,b);

这是不能实现a和b的数值互换的,实际上只是形参在这个函数中换来换去,对实参没什么影响。现在,用指针类型的数做为参数的话,更改如下:

swap(#3333FF *p1,int *p2)
{
int t;
t=*p1;
*p1=*p2;
*p2=t;
}
main()
{
int a=2,b=3;
int *ptr1,*ptr2;
ptr1=&a;
ptr2=&b;
swap(prt1,ptr2);
printf(“%d,%d”,a,b);

这样在swap中就把p1,p2 的内容给换了,即把a,b的值互换了。

指针可以执行增、减运算,结合++运算符的法则,我们可以看到:

*++s  取指针变量加1以后的内容
 
*s++  取指针变量所指内容后s再加1
(*s)++  指针变量指的内容加1

指针和数组实际上几乎是一样的,数组名可以看成是一个常量指针,一维数组中ptr=&b[0]则下面的表示法是等价的:

a[3]等价于*(a+3)
ptr[3]等价于*(ptr+3)

下面看一个用指针来自己实现atoi(字符串型->整型)函数:

int atoi(char *s)
{
int sign=1,m=0;
if(*s=='+'||*s=='-') /*判断是否有符号*/
sign=(*s++=='+')?1:-1; /*用到三目运算符*/
while(*s!='{post.abstract}') /*对每一个字符进行操作*/
   {
   m=m*10+(*s-'0');
   s++; /*指向下一个字符*/
   }
return m*sign;

指向多维数组的指针变量也是一个比较广泛的运用。例如数组a[3][4],a代表的实际是整个二维数组的首地址,即第0行的首地址,也就是一个指针变量。而a+1就不是简单的在数值上加上1了,它代表的不是a[0][1],而是第1行的首地址,&a[1][0]。

指针变量常用的用途还有把指针作为参数传递给其他函数,即指向函数的指针。
看下面的几行代码:

void Input(ST *);
void Output(ST *);
void Bubble(ST *);
void Find(ST *);
void Failure(ST *);
/*函数声明:这五个函数都是以一个指向ST型(事先定义过)结构的指针变量作为参数,无返回值。*/

void (*process[5])(ST *)={Input,Output,Bubble,Find,Failure};
/*process被调用时提供5种功能不同的函数共选择(指向函数的指针数组)*/

printf("\nChoose:\n?");
scanf("%d",&choice);
if(choice>=0&&choice<=4)
(*process[choice])(a); /*调用相应的函数实现不同功能*;/ 

总之,指针的应用是非常灵活和广泛的,不是三言两语能说完的,上面几个小例子只是个引子,实际编程中,会逐渐发现运用指针所能带来的便利和高效率。

文件:

函数调用形式  说明 
fopen("路径","打开方式") 打开文件
fclose(FILE *) 防止之后被误用
fgetc(FILE *) 从文件中读取一个字符
fputc(ch,FILE *) 把ch代表的字符写入这个文件里
fgets(FILE *) 从文件中读取一行
fputs(FILE *) 把一行写入文件中
fprintf(FILE *,"格式字符串",输出表列) 把数据写入文件
fscanf(FILE *,"格式字符串",输入表列) 从文件中读取
fwrite(地址,sizeof(),n,FILE *) 把地址中n个sizeof大的数据写入文件里
fread(地址,sizeof(),n,FILE *) 把文件中n个sizeof大的数据读到地址里
rewind(FILE *) 把文件指针拨回到文件头
fseek(FILE *,x,0/1/2) 移动文件指针。第二个参数是位移量,0代表从头移,1代表从当前位置移,2代表从文件尾移。
feof(FILE *) 判断是否到了文件末尾

文件打开方式  说明 
r  打开只能读的文件
w  建立供写入的文件,如果已存在就抹去原有数据
a  打开或建立一个把数据追加到文件尾的文件
r+  打开用于更新数据的文件
w+  建立用于更新数据的文件,如果已存在就抹去原有数据
a+  打开或建立用于更新数据的文件,数据追加到文件尾

注:以上用于文本文件的操作,如果是二进制文件就在上述字母后加“b”。

我们用文件最大的目的就是能让数据保存下来。因此在要用文件中数据的时候,就是要把数据读到一个结构(一般保存数据多用结构,便于管理)中去,再对结构进行操作即可。例如,文件aa.data中存储的是30个学生的成绩等信息,要遍历这些信息,对其进行成绩输出、排序、查找等工作时,我们就把这些信息先读入到一个结构数组中,再对这个数组进行操作。如下例:

#include<stdio.h>
#include<stdlib.h>
#define N 30
typedef struct student /*定义储存学生成绩信息的数组*/
{
char *name;
int chinese;
int maths;
int phy;
int total;
}ST;

main()
{
ST a[N]; /*存储N个学生信息的数组*/
FILE *fp;
void (*process[3])(ST *)={Output,Bubble,Find}; /*实现相关功能的三个函数*/
int choice,i=0;
Show();
printf("\nChoose:\n?");
scanf("%d",&choice);
while(choice>=0&&choice<=2)
   {
   fp=fopen("aa.dat","rb");
   for(i=0;i<N;i++)
      fread(&a[i],sizeof(ST),1,fp); /*把文件中储存的信息逐个读到数组中去*/
   fclose(fp);
   (*process[choice])(a); /*前面提到的指向函数的指针,选择操作*/
   printf("\n");
   Show();
   printf("\n?");
   scanf("%d",&choice);
   }
}

void Show()
{
printf("\n****Choices:****\n0.Display the data form\n1.Bubble it according to the total score\n2.Search\n3.Quit!\n");
}

void Output(ST *a) /*将文件中存储的信息逐个输出*/
{
int i,t=0;
printf("Name Chinese Maths Physics Total\n");
for(i=0;i<N;i++)
   {
   t=a[i].chinese+a[i].maths+a[i].phy;
   a[i].total=t;
   printf("%4s%8d%8d%8d%8d\n",a[i].name,a[i].chinese,a[i].maths,a[i].phy,a[i].total);
   }
}

void Bubble(ST *a) /*对数组进行排序,并输出结果*/
{
int i,pass;
ST m;
for(pass=0;pass<N-1;pass++)
   for(i=0;i<N-1;i++)
      if(a[i].total<a[i+1].total)
         {
         m=a[i]; /*结构互换*/
         a[i]=a[i+1];
         a[i+1]=m;
         }
Output(a);
}

void Find(ST *a)
{
int i,t=1;
char m[20];
printf("\nEnter the name you want:");
scanf("%s",m);
for(i=0;i<N;i++)
   if(!strcmp(m,a[i].name)) /*根据姓名匹配情况输出查找结果*/
   {
   printf("\nThe result is:\n%s, Chinese:%d, Maths:%d,     Physics:%d,Total:%d\n",m,a[i].chinese,a[i].maths,a[i].phy,a[i].total);
   t=0;
   }
if(t)
   printf("\nThe name is not in the list!\n");
}
 

链表:
链表是C语言中另外一个难点。牵扯到结点、动态分配空间等等。用结构作为链表的结点是非常适合的,例如:

struct node
{
int data;
struct node *next;
}; 

其中next是指向自身所在结构类型的指针,这样就可以把一个个结点相连,构成链表。

链表结构的一大优势就是动态分配存储,不会像数组一样必须在定义时确定大小,造成不必要的浪费。用malloc和free函数即可实现开辟和释放存储单元。其中,malloc的参数多用sizeof运算符计算得到。

链表的基本操作有:正、反向建立链表;输出链表;删除链表中结点;在链表中插入结点等等,都是要熟练掌握的,初学者通过画图的方式能比较形象地理解建立、插入等实现的过程。

typedef struct node
{
char data;
struct node *next;
}NODE; /*结点*/

正向建立链表:
NODE *create()
{
char ch='a';
NODE *p,*h=NULL,*q=NULL;
while(ch<'z')
   {
   p=(NODE *)malloc(sizeof(NODE)); /*强制类型转换为指针*/
   p->data=ch;
   if(h==NULL) h=p;
      else q->next=p;
   ch++;
   q=p;
   }
q->next=NULL; /*链表结束*/
return h;

 

逆向建立:

NODE *create()
{
char ch='a';
NODE *p,*h=NULL;
while(ch<='z')
   {
   p=(NODE *)malloc(sizeof(NODE));
   p->data=ch;
   p->next=h; /*不断地把head往前挪*/
   h=p;
 nbsp; ch++;
   }
return h;

 

用递归实现链表逆序输出:

void output(NODE *h)
{
if(h!=NULL)
   {
   output(h->next);
   printf("%c",h->data);
   }

插入结点(已有升序的链表):

NODE *insert(NODE *h,int x)
{
NODE *new,*front,*current=h;
while(current!=NULL&&(current->data<x)) /*查找插入的位置*/
   {
   front=current;
   current=current->next;
   }
new=(NODE *)malloc(sizeof(NODE));
new->data=x;
new->next=current;
if(current==h) /*判断是否是要插在表头*/
   h=new;
else front->next=new;
return h;

 

删除结点:

NODE *delete(NODE *h,int x)
{
NODE *q,*p=h;
while(p!=NULL&&(p->data!=x))
   {
   q=p;
   p=p->next;
   }
if(p->data==x) /*找到了要删的结点*/
   {
   if(p==h) /*判断是否要删表头*/
   h=h->next;
      else q->next=p->next;
   free(p); /*释放掉已删掉的结点*/
   }
return h;

 

经常有链表相关的程序填空题,做这样的题要注意看下面提到的变量是否定义了,用到的变量是否赋初值了,是否有给分配空间的没有分配空间,最后看看返回值是否正确。

笔者水平有限,难免有疏漏、错误的地方,浅显之处,还望指正见谅。上述内容仅是个提示作用,并不包括C语言的全部内容

- 作者: folsailor 2007年08月15日, 星期三 11:01  回复(0) |  引用(0) 加入博采

指针ZZ
小申 @ 2006-11-27 11:16

为初学者服务。这是我的帖子的宗旨。我也是个初学者(强调了无数遍了) ,我以我的理解把初学者觉得难懂的东西用浅显的语言写出来。由于小学时语文 没学好,所以竭尽全力也未必能达到这个目的。尽力而为吧。
指针是c和c++中的难点和重点。我只精通dos下的basic。c语言的其它各种特 性,在basic中都有类似的东西。只有指针,是baisc所不具备的。指针是c的灵魂 。
我不想重复大多数书上说得很清楚的东西,我只是把我看过的书中说得不清 楚或没有说,而我又觉得我理解得有点道理的东西写出来。我的目的是:
1。通过写这些东西,把我脑袋中关于c的模糊的知识清晰化。
2。给初学者们一点提示。
3。赚几个经验值。(因为贴这些东西没有灌水之嫌啊)
第一章。指针的概念


指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。

要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的 类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。让 我们分别说明。
先声明几个指针放着做例子:
例一:
(1)int *ptr;
(2)char *ptr;
(3)int **ptr;
(4)int (*ptr)[3];
(5)int *(*ptr)[4];
如果看不懂后几个例子的话,请参阅我前段时间贴出的文?lt;<如何理解c和c
++的复杂类型声明>>。

1。 指针的类型。
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的 类型:
(1)int *ptr; //指针的类型是int *
(2)char *ptr; //指针的类型是char *
(3)int **ptr; //指针的类型是 int **
(4)int (*ptr)[3]; //指针的类型是 int(*)[3]
(5)int *(*ptr)[4]; //指针的类型是 int *(*)[4]
怎么样?找出指针的类型的方法是不是很简单?

2。指针所指向的类型。
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译 器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符 *去掉,剩下的就是指针所指向的类型。例如:
(1)int *ptr; //指针所指向的类型是int
(2)char *ptr; //指针所指向的的类型是char
(3)int **ptr; //指针所指向的的类型是 int *
(4)int (*ptr)[3]; //指针所指向的的类型是 int()[3]
(5)int *(*ptr)[4]; //指针所指向的的类型是 int *()[4]
在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越 来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的 类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。我看了不 少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。

3。 指针的值,或者叫指针所指向的内存区或地址。
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。
指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块
内存区域,就相当于说该指针的值是这块内存区域的首地址。
指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指向的类型是什么?该指针指向了哪里?
4。 指针本身所占据的内存区。
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。
指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。

第二章。指针的算术运算


指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如:
例二:
1。 char a[20];
2。 int *ptr=a;
...
...
3。 ptr++;
在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整 形变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。
由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了数组a中从第4号单元开始的四个字节。
我们可以用一个指针和一个循环来遍历一个数组,看例子: 
例三:
int array[20];
int *ptr=array;
...
//此处略去为整型数组赋值的代码。
...
for(i=0;i<20;i++)
{
(*ptr)++;
ptr++;
}
这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1,所
以每次循环都能访问数组的下一个单元。
再看例子:
例四:
1。 char a[20];
2。 int *ptr=a;
...
...
3。 ptr+=5;
在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5 乘sizof(int),在32位程序中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。
如果上例中,ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。

总结一下,一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字节。就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。
一个指针ptrold减去一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节,就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。

第三章。运算?amp;和*

这里&是取地址运算符,*是...书上叫做"间接运算符"。
&a的运算结果是一个指针,指针的类型是a的类型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。
*p的运算结果就五花八门了。总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址是p所指向的地址。
例五:
int a=12;
int b;
int *p;
int **ptr;
p=&a;//&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址 是a的地址。
*p=24;//*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地 址,显然,*p就是变量a。
ptr=&p;//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int
**。该指针所指向的类型是p的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。
*ptr=&b;//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所 指向的类型是一样的,所以用&b来给*ptr赋值就是毫无问题的了。
**ptr=34;//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指
针再做一次*运算,结果就是一个int类型的变量。

第四章。指针表达式。


一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表达式。
下面是一些指针表达式的例子:
例六:
int a,b;
int array[10];
int *pa;
pa=&a;//&a是一个指针表达式。
int **ptr=&pa;//&pa也是一个指针表达式。
*ptr=&b;//*ptr和&b都是指针表达式。
pa=array;
pa++;//这也是指针表达式。
例七:
char *arr[20];
char **parr=arr;//如果把arr看作指针的话,arr也是指针表达式
char *str;
str=*parr;//*parr是指针表达式
str=*(parr+1);//*(parr+1)是指针表达式
str=*(parr+2);//*(parr+2)是指针表达式


由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。
好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。
在例七中,&a不是一个左值,因为它还没有占据明确的内存。*ptr是一个左值,因为*ptr这个指针已经占据了内存,其实*ptr就是指针pa,既然pa已经在内存中有了自己的位置,那么*ptr当然也有了自己的位置。

第五章。数组和指针的关系


如果对声明数组的语句不太明白的话,请参阅我前段时间贴出的文?lt;<如何理解c和c++的复杂类型声明>>。
数组的数组名其实可以看作一个指针。看下例:
例八:
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
...
...
value=array[0];//也可写成:value=*array;
value=array[3];//也可写成:value=*(array+3);
value=array[4];//也可写成:value=*(array+4);
上例中,一般而言数组名array代表数组本身,类型是int [10],但如果把a
rray看做指针的话,它指向数组的第0个单元,类型是int *,所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+3)等于3。其它依此类推。 
例九:
char *str[3]={
"Hello,this is a sample!",
"Hi,good morning.",
"Hello world"
};
char s[80];
strcpy(s,str[0]);//也可写成strcpy(s,*str);
strcpy(s,str[1]);//也可写成strcpy(s,*(str+1));
strcpy(s,str[2]);//也可写成strcpy(s,*(str+2));
上例中,str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str当作一个指针的话,它指向数组的第0号单元,它的类型是char**,它指向的类型是char *。
*str也是一个指针,它的类型是char*,它所指向的类型是char,它指向的地址是字符串"Hello,this is a sample!"的第一个字符的地址,即’H’的地址。
str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char *。
*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,good morning."的第一个字符’H’,等等。

下面总结一下数组的数组名的问题。声明了一个数组TYPE array[n],则数组名称array就有了两重含义:第一,它代表整个数组,它的类型是TYPE [n];第二,它是一个指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。
在不同的表达式中数组名array可以扮演不同的角色。
在表达式sizeof(array)中,数组名array代表数组本身,故这时sizeof函数
测出的是整个数组的大小。
在表达式*array中,array扮演的是指针,因此这个表达式的结果就是数组第0号单元的值。sizeof(*array)测出的是数组单元的大小。
表达式array+n(其中n=0,1,2,....。)中,array扮演的是指针,故arr
ay+n的结果是一个指针,它的类型是TYPE*,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。
例十:
int array[10];
int (*ptr)[10];
ptr=&array;
上例中ptr是一个指针,它的类型是int (*)[10],他指向的类型是int [10]
,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array代表数组本身。

本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究
竟是指针自身类型的大小呢还是指针所指向的类型的大小?答案是前者。例如:

int (*ptr)[10];
则在32位程序中,有:
sizeof(int(*)[10])==4
sizeof(int [10])==40
sizeof(ptr)==4
实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。

第六章。指针和结构类型的关系


可以声明一个指向结构类型对象的指针。
例十一:
struct MyStruct
{
int a;
int b;
int c;
}
MyStruct ss={20,30,40};//声明了结构对象ss,并把ss的三个成员初始
化为20,30和40。
MyStruct *ptr=&ss;//声明了一个指向结构对象ss的指针。它的类型是
MyStruct*,它指向的类型是MyStruct。
int *pstr=(int*)&ss;//声明了一个指向结构对象ss的指针。但是它的
类型和它指向的类型和ptr是不同的。

请问怎样通过指针ptr来访问ss的三个成员变量?
答案:
ptr->a;
ptr->b;
ptr->c;
又请问怎样通过指针pstr来访问ss的三个成员变量?
答案:
*pstr;//访问了ss的成员a。
*(pstr+1);//访问了ss的成员b。
*(pstr+2)//访问了ss的成员c。
呵呵,虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用p
str来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指
针来访问数组的各个单元:
例十二:
int array[3]={35,56,37};
int *pa=array;
通过指针pa访问数组array的三个单元的方法是:
*pa;//访问了第0号单元
*(pa+1);//访问了第1号单元
*(pa+2);//访问了第2号单元
从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。
所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干?quot;填充字节",这就导致各个成员之间可能会有若干个字节的空隙。
所以,在例十二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员间到底有没有填充字节,
嘿,这倒是个不错的方法。
通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。

第七章。指针和函数的关系


可以把一个指针声明成为一个指向函数的指针。
int fun1(char*,int);
int (*pfun1)(char*,int);
pfun1=fun1;
....
....
int a=(*pfun1)("abcdefg",7);//通过函数指针调用函数。
可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为
实参。
例十三:
int fun(char*);
int a;
char str[]="abcdefghijklmn";
a=fun(str);
...
...
int fun(char*s)
{
int num=0;
for(int i=0;i
{
num+=*s;s++;
}
return num;
)
这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说了,数组的名字也是一个指针。在函数调用中,当把str作为实参传递给形参s后,实际是把str的值传递给了s,s所指向的地址就和str所指向的地址一致,但是str和s各自占用各自的存储空间。在函数体内对s进行自加1运算,并不意味着同时对str进行了自加1运算。

第八章。指针类型转换


当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。
例十四:
1。 float f=12.3;
2。 float *fptr=&f;
3。 int *p;
在上面的例子中,假如我们想让指针p指向实数f,应该怎么搞?是用下面的语句吗?
p=&f;
不对。因为指针p的类型是int*,它指向的类型是int。表达式&f的结果是一
个指针,指针的类型是float*,它指向的类型是float。两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行"强制类型转换":
p=(int*)&f; 如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*和TYPE,
那么语法格式是:
(TYPE*)p;
这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。

一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也会发生指针类型的转换。
例十五:
void fun(char*);
int a=125,b;
fun((char*)&a);
...
...
void fun(char*s)
{
char c;
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;
}
}
注意这是一个32位程序,故int类型占了四个字节,char类型占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a的结果是一个指针,它的类型是int *,它指向的类型是int。形参这个指针的类型是char*,它指向的类型是char。这样,在实参和形参的结合过程中,我们必须进行一次从int*类型到char*类型的转换。结合这个例子,我们可以这样来想象编译器进行转换的过程:编译器先构造一个临时指针 char*temp,然后执行temp=(char*)&a,最后再把temp的值传递给s。所以最后的结果是:s的类型是char*,它指向的类型是char,它指向的地址就是a的首地址。

我们已经知道,指针的值就是指针指向的地址,在32位程序中,指针的值其实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:
unsigned int a;
TYPE *ptr;//TYPE是int,char或结构类型等等类型。
...
...
a=20345686;
ptr=20345686;//我们的目的是要使指针ptr指向地址20345686(十进制

ptr=a;//我们的目的是要使指针ptr指向地址20345686(十进制)
编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法:
unsigned int a;
TYPE *ptr;//TYPE是int,char或结构类型等等类型。
...
...
a=某个数,这个数必须代表一个合法的地址;
ptr=(TYPE*)a;//呵呵,这就可以了。
严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a的值当作一个地址来看待。
上面强调了a的值必须代表一个合法的地址,否则的话,在你使用ptr的时候,就会出现非法操作错误。

想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完全可以。下面的例子演示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:
例十六:
int a=123,b;
int *ptr=&a;
char *str;
b=(int)ptr;//把指针ptr的值当作一个整数取出来。
str=(char*)b;//把这个整数的值当作一个地址赋给指针str。

好了,现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。

第九章。指针的安全问题


看下面的例子:
例十七:
char s=’a’;
int *ptr;
ptr=(int*)&s;
*ptr=1298;
指针ptr是一个int*类型的指针,它指向的类型是int。它指向的地址就是s的首地址。在32位程序中,s占一个字节,int类型占四个字节。最后一条语句不但改变了s所占的一个字节,还把和s相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。
让我们再来看一例:
例十八:
1。 char a;
2。 int *ptr=&a;
...
...
3。 ptr++;
4。 *ptr=115;
该例子完全可以通过编译,并能执行。但是看到没有?第3句对指针ptr进行自加1运算后,ptr指向了和整形变量a相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要的数据,甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。
在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。
在指针的强制类型转换:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的类型)大
于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是不安全的。至于为什么,读者结合例十七来想一想,应该会明白的。

- 作者: folsailor 2007年08月15日, 星期三 10:56  回复(0) |  引用(0) 加入博采

几种开源的TCP/IP协议栈分析zz
几种开源的TCP/IP协议栈分析
作者:本站整理  来源:网络  发布时间:2006-12-24 23:31:43  发布人:szetom

减小字体 增大字体