EDN首页   博客首页

最新日志

发表于:2007-12-21 20:12:54
标签:c,volatile,嵌入式,编译器  

0

关于c中volatile关键字(转贴)

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
    1). 并行设备的硬件寄存器(如:状态寄存器)
    2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
    3). 多线程应用中被几个任务共享的变量
    回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
    假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
    1). 一个参数既可以是const还可以是volatile吗?解释为什么。
    2). 一个指针可以是volatile 吗?解释为什么。
    3). 下面的函数有什么错误:
         int square(volatile int *ptr)
         {
              return *ptr * *ptr;
         }
    下面是答案:
    1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
    2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
    3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*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;
     }
     
    
    
Volatile 关键字告诉编译器不要持有变量的临时性拷贝。一般用在多线程程序中,以避免在其中一个线程操作该变量时,将其拷贝入寄存器。请看以下情形:

   A线程将变量复制入寄存器,然后进入循环,反复检测寄存器的值是否满足一定条件(它期待B线程改变变量的值。
在此种情况下,当B线程改变了变量的值时,已改变的值对其在寄存器的值没有影响。所以A线程进入死循环。
  
   volatile 就是在此种情况下使用。
 

What does volatile do?

This is probably best explained by comparing the effects that volatile and synchronized have on a method. volatile is a field modifier, while synchronized modifies code blocks and methods. So we can specify three variations of a simple accessor using those two keywords:

         int i1;              int geti1() {return i1;}
volatile int i2;              int geti2() {return i2;}
         int i3; synchronized int geti3() {return i3;}

geti1() accesses the value currently stored in i1 in the current thread. Threads can have local copies of variables, and the data does not have to be the same as the data held in other threads. In particular, another thread may have updated i1 in it's thread, but the value in the current thread could be different from that updated value. In fact Java has the idea of a "main" memory, and this is the memory that holds the current "correct" value for variables. Threads can have their own copy of data for variables, and the thread copy can be different from the "main" memory. So in fact, it is possible for the "main" memory to have a value of 1 for i1, for thread1 to have a value of 2 for i1 and for thread2 to have a value of 3 for i1 if thread1 and thread2 have both updated i1 but those updated value has not yet been propagated to "main" memory or other threads.

On the other hand, geti2() effectively accesses the value of i2 from "main" memory. A volatile variable is not allowed to have a local copy of a variable that is different from the value currently held in "main" memory. Effectively, a variable declared volatile must have it's data synchronized across all threads, so that whenever you access or update the variable in any thread, all other threads immediately see the same value. Of course, it is likely that volatile variables have a higher access and update overhead than "plain" variables, since the reason threads can have their own copy of data is for better efficiency.

Well if volatile already synchronizes data across threads, what is synchronized for? Well there are two differences. Firstly synchronized obtains and releases locks on monitors which can force only one thread at a time to execute a code block, if both threads use the same monitor (effectively the same object lock). That's the fairly well known aspect to synchronized. But synchronized also synchronizes memory. In fact synchronized synchronizes the whole of thread memory with "main" memory. So executing geti3() does the following:

  1. The thread acquires the lock on the monitor for object this (assuming the monitor is unlocked, otherwise the thread waits until the monitor is unlocked).
  2. The thread memory flushes all its variables, i.e. it has all of its variables effectively read from "main" memory (JVMs can use dirty sets to optimize this so that only "dirty" variables are flushed, but conceptually this is the same. See section 17.9 of the Java language specification).
  3. The code block is executed (in this case setting the return value to the current value of i3, which may have just been reset from "main" memory).
  4. (Any changes to variables would normally now be written out to "main" memory, but for geti3() we have no changes.)
  5. The thread releases the lock on the monitor for object this.

So where volatile only synchronizes the value of one variable between thread memory and "main" memory, synchronized synchronizes the value of all variables between thread memory and "main" memory, and locks and releases a monitor to boot. Clearly synchronized is likely to have more overhead than volatile.

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

评论(0) | 阅读(530)
发表于:2007-12-21 13:01:18
标签:忠告,人生,弯路,毕业,成功  

1

少走弯路的十条忠告(转)

文/刘大钟

  刚刚走上社会的年轻人,充满了蓄势待发的豪情、青春的朝气、前卫的思想,梦想着丰富的待遇和轰轰烈烈的事业。可是,社会毕竟是一所包罗万象、喧嚣复杂的大学校,这里没有寒暑假,拒绝虚假和肤浅,更拒绝空想和庸碌,难以预告何时开课何时放学。


  如何在涉世之初少走弯路,有一个好的开端,开始一番成功的事业?以下是一些先行者积累的10条有益的涉世忠告。好好地遵循、把握这些忠告和建议吧,比起所学的课堂课程来,它毫不逊色!


1. 买个闹钟,以便按时叫醒你。贪睡和不守时,都将成为你工作和事业上的绊脚石,任何时候都一样。不仅要学会准时,更要学会提前。就如你坐车去某地,沿途的风景很美,你忍不住下车看一看,后来虽然你还是赶到了某地,却不是准时到达。“闹钟”只是一种简单的标志和提示,真正灵活、实用的时间,掌握在每个人的心中。


2. 如果你不喜欢现在的工作,要么辞职不干,要么就闭嘴不言。初出茅庐,往往眼高手低,心高气傲,大事做不了,小事不愿做。不要养成挑三拣四的习惯。不要雨天烦打伞,不带伞又怕淋雨,处处表现出不满的情绪。记住,不做则已,要做就要做好。


3. 每个人都有孤独的时候。要学会忍受孤独,这样才会成熟起来。年轻人嘻嘻哈哈、打打闹闹惯了,到了一个陌生的环境,面对形形色色的人和事,一下子不知所措起来,有时连一个可以倾心说话的地方也没有。这时,千万别浮躁,学会静心,学会忍受孤独。在孤独中思考,在思考中成熟,在成熟中升华。不要因为寂寞而乱了方寸,而去做无聊无益的事情,白白浪费了宝贵的时间。


4. 走运时要做好倒霉的准备。有一天,一只狐狸走到一个葡萄园外,看见里面水灵灵的葡萄垂涎欲滴。可是外面有栅栏挡着,无法进去。于是它一狠心绝食三日,减肥之后,终于钻进葡萄园内饱餐一顿。当它心满意足地想离开葡萄园时,发觉自己吃得太饱,怎么也钻不出栅栏了。相信任何人都不愿做这样的狐狸。退路同样重要。饱带干粮,晴带雨伞,点滴积累,水到渠成。有的东西今天似乎一文不值,但有朝一日也许就会身价百倍。


5. 不要像玻璃那样脆弱。有的人眼睛总盯着自己,所以长不高看不远;总是喜欢怨天尤人,也使别人无比厌烦。没有苦中苦,哪来甜中甜?不要像玻璃那样脆弱,而应像水晶一样透明,太阳一样辉煌,腊梅一样坚强。既然睁开眼睛享受风的清凉,就不要埋怨风中细小的沙粒。


6. 管住自己的嘴巴。不要谈论自己,更不要议论别人。谈论自己往往会自大虚伪,在名不副实中失去自己。议论别人往往陷入鸡毛蒜皮的是非口舌中纠缠不清。每天下班后和你的那些同事朋友喝酒聊天可不是件好事,因为,这中间往往会把议论同事、朋友当做话题。背后议论人总是不好的,尤其是议论别人的短处,这些会降低你的人格。


7. 机会从不会“失掉”,你失掉了,自有别人会得到。不要凡事在天,守株待兔,更不要寄希望于“机会”。机会只不过是相对于充分准备而又善于创造机会的人而言的。也许,你正为失去一个机会而懊悔、埋怨的时候,机会正被你对面那个同样的“倒霉鬼”给抓住了。没有机会,就要创造机会,有了机会,就要巧妙地抓住。


8. 若电话老是不响,你该打出去。很多时候,电话会给你带来意想不到的收获,它不是花瓶,仅仅成为一种摆设。交了新朋友,别忘了老朋友,朋友多了路好走。交际的一大诀窍就是主动。好的人缘好的口碑,往往助你的事业更上一个台阶。


9. 千万不要因为自己已经到了结婚年龄而草率结婚。想结婚,就要找一个能和你心心相印、相辅相携的伴侣。不要因为放纵和游戏而恋爱,不要因为恋爱而影响工作和事业,更不要因一桩草率而失败的婚姻而使人生受阻。感情用事往往会因小失大。


10. 写出你一生要做的事情,把单子放在皮夹里,经常拿出来看。人生要有目标,要有计划,要有提醒,要有紧迫感。一个又一个小目标串起来,就成了你一生的大目标。生活富足了,环境改善了,不要忘了皮夹里那张看似薄薄的单子。

系统分类: 自由话题   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(282)
发表于:2007-12-21 12:57:46
标签:arm,嵌入式,s3c44b0,起步  

1

ARM无痛苦起步(转)

ARM无痛苦起步(转) 

[ 2006-10-27 18:30:00 | By: huolf ]
 
      首先看看我们要解决的问题。44B0X片内只有几K CACHE,ROM和RAM都是外接的芯片。我们的程序是要写入FLASH中保存,但执行时是拷到SDRAM中执行的(如在ROM中执行速度会较慢)。要做到这一点需要把程序做成两个分程序:一个是实现你的系统功能的主程序,如果你用嵌入式系统,那就是UCOS和UCLINUX之类的程序,这个程序的代码保存在FLASH中,但执行时会拷到RAM中再执行;一个是引导程序,直接在FLASH中执行,负责把初始化芯片和外设,并把主程序从FLASH中拷到RAM中,然后跳到主程序去执行,对应的概念是UBOOT等常见的引导程序,这个程序会被写入0X0开始的地址,开机后自动执行。

    那么我们需要解决以下几个问题:
   1.如何编译和调试主程序
   2.如何使中断跳到RAM中的中断服务程序执行
   3.如何把引导程序和主程序写入FLASH中.

arm(44b0x)无痛苦起步

    这两个星期在看arm的资料,在朋友的帮助下弄明白了是怎么回事后,我觉得我原来看的入门资料总有些说的不详细的地方。所以决定写一个文档,总结开始arm开发的入门知识,给后来的朋友参考,也希望得到高手指正我认识上的错误。我的开发板是s3c44b0x的,2m NOR FLASH在bank0,8m sdram在bank6.
    首先看看我们要解决的问题。有些ARM芯片有内嵌的RAM 和FALSH.这样可以直接在片内运行程序,44B0X片内只有几K CACHE,ROM和RAM都是外接的芯片。我们的程序是要写入FLASH中保存,但执行时是拷到SDRAM中执行的(如在ROM中执行速度会较慢)。要做到这一点需要把程序做成两个分程序:一个是实现你的系统功能的主程序,如果你用嵌入式系统,那就是UCOS和UCLINUX之类的程序,这个程序的代码保存在FLASH中,但执行时会拷到RAM中再执行;一个是引导程序,直接在FLASH中执行,负责把初始化芯片和外设,并把主程序从FLASH中拷到RAM中,然后跳到主程序去执行,对应的概念是UBOOT等常见的引导程序,这个程序会被写入0X0开始的地址,开机后自动执行。
    那么我们需要解决以下几个问题:
   1.如何编译和调试主程序
   2.如何使中断跳到RAM中的中断服务程序执行
   3.如何把引导程序和主程序写入FLASH中.
以下我们来解决这几个问题:
  1 开始在仿真器中写代码和调试
  由于主程序会被拷贝到RAM中执行,则我们应该在编译时就把程序定位到RAM中。这里先要说说几个ADS的参数的意义,在ADS的ARM LINKER页有RO,RW两个参数,此外还有一个ZI没有在页中给出,RO是只读代码的起始地址,由这个地址开始存放编译出来的程序指令;RW是程序的读写段的开始,即你程序中的数据存放的开始地址,ZI紧跟在RW区后,ZI区存放的是需要在程序运行时初始化为0的数据。
了解这几个链接参数的意义后我们可以设置这几个参数了:对于我的44B0X板8M SDRAM在0XC00_0000.因此在开发时把ADS中的RO BASE的地址指定为0XC00_0000;置于RW,在程序完成前可以预先估计一下程序的体积有多大,需要用到的数据区有多大,避免数据区太小或代码区覆盖掉前面的数据区就是了,我用了0XC10_0000,1M的代码空间,其他作数据区。这样,我们编译出来的程序代码就是在0XC00_0000中,可以直接由仿真器写入RAM中运行仿真运行。此外,在linker-〉layout页有个object symbol和section的选项,要求你填入映像文件最开始的object文件名和段名,这两个参数在仿真时不填写也不会影响运行,因为仿真器会自动修改pc指针,但要建立能烧写的映像文件,则一定要填写好,具体填写什么后面分析程序时再讲。
    2中断问题
    和所有单片机一样,ARM复位后从地址0X0开始执行,而0X0后是一串默认的中断向量表。对51这样的芯片,我们会直接在这个中断向量表中填入跳转语句,让它跳到指定的ISR处理中断事件。由于我们的主程序是在RAM中执行的,编译时又和引导程序分开,不可能预先知道我们写的ISR具体地址,而预留的中断向量表只够每个中断一个跳转指令,因此我们需要做二次跳转。在内存中建立一个中断向量表,每个中断对应一个字,存放ISR的地址。尔后,对每个中断写一段短的代码,把ISR地址取出,填入PC。而0X0后面中断向量的跳转指令,则是跳到这小段程序中。
    3烧写flash,ADX中似乎有个写入flash的选项,我自己没有具体用过。但听说用jtag写flash会比较慢。由于nor flash或nand flash都是可以编程烧写的,即我们可以写个程序擦写flash,问题是如何读取编译出来的映像文件。这个也不用担心,adx中有个菜单把文件内容写入指定的地址中,把影响文件指定到一个ram地址,然后就用烧写程序把ram的内容拷入rom中就是了。我们有个boot程序,一个主程序要映射到rom中.假设我把0xc20_0000开始的2m地址作rom的映像,则把boot程序导入0xc20_0000,boot的程序非常短,在0xc20_1000开始放主程序。然后把0xc20_0000到0xc40_0000的内容全部拷入rom中(当然在导入文件前这些ram应该先被清空或写入ff.)。
     让我们来看看相关的代码,具体认识一下该怎么处理前面说的这些问题,还有另外的一些问题。这里使用的代码是在44b0x的application note的第三章中拿出来的,这个文件在网上应该很容易找到。
    程序的入口在44binit.s汇编文件中,其中一个Init 段是整个程序的入口:
AREA Init,CODE,READONLY
ENTRY
b ResetHandler ;for debug
b HandlerUndef ;handlerUndef
b HandlerSWI ;SWI interrupt handler
b HandlerPabort ;handlerPAbort
b HandlerDabort ;handlerDAbort
b . ;handlerReserved
b HandlerIRQ
关键字ENTRY告诉编译器保留这段代码。从代码看INIT段就是要写入0X0地址的原始中断向量,因此把这个文件编译生成的44BINIT.O和INTT填入上面提到的LAYOUT页对应项中。这样编译器会把该段代码编译到0X0地址。(仿真时你可以试试别填这两个项目,看看ADX中的反汇编代码入口被放到哪里)。
这段代码里除了reset句外,有每句都有一个HandlerXXX的标号,这就是前面提到的中断处理程序的入口,它是由前面的一个宏来定义的:
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
sub sp,sp,#4 ;decrement sp(to store jump address)
stmfd sp!,{r0}
;PUSH the work register to stack(lr does't push because it return to original address)
ldr r0,=$HandleLabel;load the address of HandleXXX to r0
ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX
str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack
ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)
MEND
   我自己没有写过宏,所以还是看编译出来的代码比较直接:
    HandlerSWI
        0x0c000198:    e24dd004    ..M.    SUB      r13,r13,#4
        0x0c00019c:    e92d0001    ..-.    STMFD    r13!,{r0}
        0x0c0001a0:    e59f0458    X...    LDR      r0,0xc000600
        0x0c0001a4:    e5900000    ....    LDR      r0,[r0,#0]
        0x0c0001a8:    e58d0004    ....    STR      r0,[r13,#4]
        0x0c0001ac:    e8bd8001    ....    LDMFD    r13!,{r0,pc}
这是ads输出的汇编代码,就是刚才的宏对应swi的一个实例,其中有两句
        LDR      r0,0xc000600
        LDR      r0,[r0,#0]
是把0x0c000600的内容载入r0,再把r0地址的ram单元载入r0.去看看0xc000600的内容,是0X0c7fff08,这是我设定的内存中的中断向量表地址之一,中断向量表的起始地址是0X0c7fff00,因此0X0c7fff08存放的刚好就是swi的isr地址。后面程序就跳到对应的ISR去了。(这段宏程序由于我不熟悉arm的汇编,只看过它怎么执行,实在我不知道中断向量表地址是如何被放入0x0c000600等地址的。希望有高手能再详细解释一下具体的编写,编译方法和原理。)
在c程序中,我们需要给每个中断向量定义一个宏:
   #define pISR_SWI (*(unsigned *)(_ISR_STARTADDRESS+0x08))
_ISR_STARTADDRESS是起始地址0X0c7fff00,假设ISR是以下函数:
void __irq SWI_UserIsr(void){……………}
则在系统初始化时用pISR_EINT0=(unsigned)SWI_UserIsr;这样的语句把ISR的地址填入中断向量表中,对所有中断作同样的处理,然后开中断,系统就能经过上面的宏把跳到ISR执行。
   44binit.s中还有几段值得留意的代码:以下的代码把rw段的数据拷入ram中,并初始化zi段,即把该段清零:
LDR    r0,=|Image$$RO$$Limit|
LDR    r1,=|Image$$RW$$Base|
LDR    r2,=|Image$$ZI$$Base|  
CMP    r0,r1                 
      BEQ    %F1
0     CMP    r1,r3
      LDRCC  r2,[r0],#4
      STRCC  r2,[r1],#4
      BCC    %B0
1     LDR    r1,=|Image$$ZI$$Limit|
      MOV   r2,#0
2     CMP    r3,r1
      STRCC  r2,[r3],#4
      BCC    %B2
来看反汇编的代码:
        0x0c000ae0:    e59f0194    ....    LDR      r0,0xc000c7c
        0x0c000ae4:    e59f1194    ....    LDR      r1,0xc000c80
        0x0c000ae8:    e59f3194    .1..    LDR      r3,0xc000c84
0xc000c7c,开始的三个字的内容是:        
        0x0c000c7c:    0c000e10    ....    DCD    201330192
        0x0c000c80:    0c200000    .. .    DCD    203423744
        0x0c000c84:    0c200000    .. .    DCD    203423744
        0x0c000c88:    0c200004    .. .    DCD    203423748
这些反汇编的代码是一个点led的程序的,可以看出我的小程序代码到0x0c000e10就结束了,0x0c200000是我指定的数据区起始地址。这段程序把|Image$$RO$$Limit| 开始的,长|Image$$ZI$$Base| -|Image$$RW$$Base| 的数据区拷到|Image$$RW$$Base|的对应单元,就是0x0c200000开始的一段ram中。后面还有|Image$$ZI$$Limit|,在我的代码中是0x0c000c88,内容是0x0c200004.这其实表明我的小程序并没有rw区,只有一个初始为0的变量。
   另外还有一段初始化ram控制器的代码:
    ;****************************************************
    ;*    Set memory control registers                    
    ;****************************************************
    ldr        r0,=SMRDATA
    ldmia   r0,{r1-r13}
    ldr        r0,=0x01c80000  ;BWSCON Address
    stmia   r0,{r1-r13},
由于44b0x要求13个控制寄存器要一次完成填入,所以先把参数设定在SMRDATA的地址中,一次载入通用寄存器,在一次填入RAM控制寄存器中。4510的书上介绍调试前需要用SEMEM命令设置这些寄存器,但我自己没有那么做也可以跑的很好,也许是默认已经用了最保守的配置的原因吧!
    其余的代码解释比较清晰了,最后摘出我的LED程序和这个小程序的BOOT程序以及烧写程序。这几个程序的project都包括了44binit.s, option.s, memcfg.s,option.h,44b.h几个从app note中抄来的文件,这里只列了我自己写的主要c代码。其他这些文件我除了把ram和rom的对应配置改了一下外,都没有改动。我的引导程序编译出来是3k,led程序也是3k,因此我把他们分别定位在rom的0x0和0x2000处,一共写了8k。
LED程序中的44BINTT.S程序功能和LOAD中的44BINTT.S是重复的,主要是我懒得去修改这些汇编,由着他们占用一点时间吧!
  load程序负责把从0x20000处开始的4k程序(即led程序)拷到ram 0xc000000中,run函数把pc指到0x0c000000,开始执行led程序:
void (*Run)(void) = (void (*)(void))RAM_ADDR;
void Main(void)
{    INT32U k ;
    INT32U *pulSource = (INT32U*)0x2000,;
    INT32U *pulDest = (INT32U*)0x0c000000;
    rSYSCFG=CACHECFG;
    PortInit();    
    for(k=0;k<2000>         *pulDest++ = *pulSource++;    
    Run();    
}
led程序把两个通用io上连的led作不断的亮和灭:
void Main(void)
{    INT32U k ;
    //INT16U *ptr;
    rSYSCFG=CACHECFG;
    PortInit();    
    while(1)
        {
            LedDisp(0);
            for(k=0;k             LedDisp(3);    
            for(k=0;k         }
}
最后是烧写的程序,详细的代码网上高手们写了不少,我只是给出最简单的实现。烧写时当程序执行到清理完0X0C30_0000到0X0C30_4000的RAM后,让程序中断下来,通过LOAD MEMORY FORM FILE命令把LOAD.BIN导入0X0C30_0000,LED.BIN导入0X0C30_2000中,继续运行程序直到一个LED亮起,烧写就完成了。拔去仿真器后再上电,可以看到两个LED同时亮灭。
#i nclude "option.h"
#i nclude "44b.h"
#i nclude "def.h"
//#i nclude "romdef.h"
//#i nclude "stdio.h"
//#i nclude "stdlib.h"

   #define FLASH_START_ADDR    0X0000
#define FLASH_ADDR_UNLOCK1  0X5555
#define FLASH_ADDR_UNLOCK2  0X2AAA
#define FLASH_DATA_UNLOCK1  0XAAAA
#define FLASH_DATA_UNLOCK2  0X5555
#define FLASH_DATA_WRITE    0XA0A0
#define FLASH_ERASE         0X8080
#define FLASH_ERASE_SECTOR  0X3030
#define FLASH_ERASE_BLOCK   0X5050
#define FLASH_ERASE_CHIP    0X1010
#define FLASH_SID_QUERY     0X9090
#define FLASH_CFI_QUERY     0X9898
#define FLASH_SID_EXIT      0XF0F0
#define FLASH_OP_TIMEOUT    0Xffff

   #define LED_PORTC10     (1 #define LED_PORTC11        (1 #define RAM_ADDR         0xc000000
void (*Run)(void) = (void (*)(void))RAM_ADDR;
void infoFlash(void);
int wait_flash_ready ( INT16U *address, INT16U data );
int writeFlash(INT16U *Address,INT16U Data);
int eraseSector(INT16U* SectorAddr);
int eraseChip(void);

   void PortInit(void);
void LedDisp(int LedStatus);

   //*****************************************
//        FLASH WIRTING
//*****************************************
void Main(void)
{    INT32U k ;
    INT16U *pdist,*psrc;
    rSYSCFG=CACHECFG;
    PortInit();    
    //infoFlash();
    eraseChip();
    psrc="/blog/(INT16U" *)0xc300000;
    for(k=0;k<0x4000>      *psrc++=0x0; //clear ram
    psrc="/blog/(INT16U" *)0xc300000;
    pdist=(INT16U *)0x0;
    for(k=0;k<0x4000 k ram to>         writeFlash(pdist++,*psrc++);
    while(1)
        {
            LedDisp(0);
            for(k=0;k             LedDisp(2);    
            for(k=0;k         }
}

  
//*****************************************
//        init the port
//*****************************************
void PortInit(void)
{

       rPDATC = 0xffff;        //All IO is high
    rPCONC = 0x0f55ff54;    
    rPUPC  = 0x3000;        //PULL UP RESISTOR should be enabled to I/O
}

   //*****************************************
//        light led
//*****************************************
void LedDisp(int LedStatus)
{
    if((LedStatus&0x01)==0x01)
        rPDATC &= (~LED_PORTC10);    //LED ON
    else
        rPDATC |= LED_PORTC10;        //LED OFF
    
    if((LedStatus&0x02)==0x02)
        rPDATC &=(~LED_PORTC11);    //LED ON
    else
        rPDATC |=LED_PORTC11;        //LED OFF
}

   //*****************************************
//        show the flash soft id
//*****************************************
void infoFlash()
{
    int i,j;
    INT16U *pFlash;
    *((volatile INT16U *)FLASH_START_ADDR)=FLASH_SID_EXIT;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK1)=FLASH_DATA_UNLOCK1;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK2)=FLASH_DATA_UNLOCK2;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK1)=FLASH_SID_QUERY;
    for(i=0;i     pFlash=FLASH_START_ADDR;
    i=0;j=0;
    i=(INT16U)*pFlash++;
    j=(INT16U)*pFlash;     
}
//*****************************************
//        erase hold flash
//*****************************************
int eraseChip()
{
    *((volatile INT16U *)FLASH_START_ADDR)=FLASH_SID_EXIT;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK1)=FLASH_DATA_UNLOCK1;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK2)=FLASH_DATA_UNLOCK2;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK1)=FLASH_ERASE;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK1)=FLASH_DATA_UNLOCK1;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK2)=FLASH_DATA_UNLOCK2;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK1)=FLASH_ERASE_CHIP;
    if( wait_flash_ready((INT16U *)FLASH_START_ADDR,0xffff) )
      return 1;
    else return 0;
}

   //*****************************************
//        write one falsh word( 16bit)
//*****************************************
int writeFlash(INT16U *Address,INT16U Data)
{   *((volatile INT16U *)FLASH_START_ADDR)=FLASH_SID_EXIT;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK1)=FLASH_DATA_UNLOCK1;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK2)=FLASH_DATA_UNLOCK2;
    *((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK1)=FLASH_DATA_WRITE;
    *Address=Data;
    if(wait_flash_ready(Address,Data))
      return 1;
    else return 0;
}

   //*****************************************
//        wait for operation finish
//*****************************************
int wait_flash_ready ( INT16U *address, INT16U data )
{
   INT32U tmp;
   INT16U *p;
   tmp =0xff;
   p=address;
    while(((*p)&0x8080)!=(data&0x8080))
    {tmp--;
     if (tmp==0x0)
         return 1;  // timeout
    }
    return 0;
}

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

评论(0) | 阅读(501)
发表于:2007-12-20 15:51:49
标签:linux内核,嵌入式,常见问题  

1

Linux内核/模块开发常见问题集(转贴)

Linux内核/模块开发常见问题集(FAQ)

                                      

  转载自水木清华
  欢迎大家补充


1. 请推荐一些好的Linux内核参考书?

2. 源代码问题
2.1 如何得到某一版本的Linux内核源代码?
2.2 请问xx命令、xx库的源码是哪个文件?
2.3 linux-2.x.x.tar.gz.sign 文件有什么用途?
2.4 请推荐一些源代码查看工具?
2.5 内核patch如patch-2.6.3怎么用?
2.6 如何统计linux内核有多少行代码?
2.7 xx结构的定义在哪个内核源文件中?
2.8 volatile和__volatile__是什么意思?
2.9 do{ ... } while(0)是什么意思?
2.10 list_entry的定义是怎么回事?
2.11 校内查看linux 内核源代码地址

3. 模块编程问题
3.1 模块编程需要注意什么?
3.2 为什么insmod一个模块时显示版本不匹配?
3.3 为什么出现Unresolved Symbol错误?
3.4 为什么出现no license错误?
3.5 为什么看不到用printk打印的信息?

4. 内核开发问题
4.1 怎么制作、使用patch文件?
4.2 在内核中可以使用系统调用吗?
4.3 在内核中怎么打开并操作一个文件?
4.4 在内核中读写文件时为什么会出现EFAULT(-14)错误?
4.5 怎么在系统中增加一个自己的系统调用?
4.6 怎么在内核中加入我自己的驱动程序?
4.7 怎么通过程序得到cpu和mem使用率?
4.8 如何获得高精度的系统时间?
4.9 怎么进行系统性能调谐?
4.10 内核中怎么进行互斥?

5. 其它问题
5.1 如何学习Linux内核?
5.2 如何下载精华区?
5.3 init进程是核心进程吗?init与初始进程是不是一回事?
5.4 initrd(.img)有什么用?

6. 关于本FAQ
7. Changelog


1. 请推荐一些好的Linux内核参考书?
  a.《Linux Device Drivers, 2nd Edition》,有中文译本
  b.《Understanding the Linux Kernel, 2nd Edition》
  c.《Linux内核源代码情景分析》,分上下两册
  d.《边干边学-Linux内核指导》
  e.《Linux内核2.4版源代码分析大全》
  f.《Linux Kernel Development》
  g.《IA-64 Linux Kernel: Design and Implementation》
  注:a电子版可在http://www.oreilly.com/catalog/linuxdrive2/下载;
      f和g比较新,在国内比较难买到。
    也可以版面查询文章标题 "Linux内核书籍推荐&介绍"

2. 源代码问题
2.1 如何得到某一版本的Linux内核源代码?
   a. http://www.kernel.orgftp://ftp.kernel.org,这是Linux内核版本的发布
      网站。
   b. 很多镜像或本地网站也提供部分Linux内核版本的下载,多用ftp搜索引擎。
      linuxaid.com提供的mirror:
      ftp://ftp.linuxaid.lkams.kernel.org/pub/mirrors/kernel/linux/kernel
   c. 一般的Linux发行版如Redhat之类会随盘提供相应的内核源代码,不过这个源代
      码往往是改动过的,与标准Linux内核有差异。

2.2 请问xx命令、xx库的源码是哪个文件?
   a. 一个系统除了内核以外,还需要有shell、gcc等一系列工具和命令以及C库等一
      系列库,这些作为应用程序其源代码都不在内核中,需要另外下载相应的源代码。
   b. 对于Redhat系统,可以用rpm -qf命令来查找某一命令所在的软件包,然后再找
      相应的源代码包安装。
   c. gnu.org有很多软件源代码如bash/glibc/binutils/make/gcc的源代码。
   d. 可在http://www.rpmfind.nethttp://www.google.com去搜一搜。

2.3 linux-2.x.x.tar.gz.sign 文件有什么用途?
    这是一个数字签名文件,用来校验linux-2.x.x.tar.gz这个文件在签名后是没有
    被第三方修改过,更详细的信息参考http://www.kernel.org/signature.html

2.4 请推荐一些源代码查看工具?
   a. Windows系统可以用Source Insight,Linux系统可以用Source Navigator。
   b. vim或emacs编辑器,配合cscope、ctags、etags等交叉索引工具。
   c. vim或emacs编辑器,配合grep、egrep等文本搜索工具,不过最好要对源代码目
      录结构有所熟悉
   d. LXR,以网页的形式通过浏览器浏览,安装复杂(debian下安装容易,请版面
搜寻lxr)
    校外:可以直接访问http://lxr.linux.no/source/在线阅读Linux内核源代码。
    校内:可以访问http://10.214.14.127/lxr/http/source/,如果需要特定版本
可以联系vatano,只有空间足够就放上去。
   e. GNU global,可以在命令行用,也可以生成hypertext,类似lxr,但更省事。

2.5 内核patch如patch-2.6.3怎么用?
   a. 内核patch一般是针对前一个版本,如patch-2.6.3是针对2.6.2的内核。
   b. 内核patch一般是和ChangeLog对应,如patch-2.6.3对应于ChangeLog-2.6.3。
   c. 在内核patch中查找Makefile关键字可得到相关信息,如在patch-2.6.0中有:
        diff -Nru a/Makefile b/Makefile
        --- a/Makefile  Wed Dec 17 19:00:07 2003
        +++ b/Makefile  Wed Dec 17 19:00:07 2003
        @@ -1,7 +1,7 @@
        VERSION = 2
        PATCHLEVEL = 6
        SUBLEVEL = 0
        -EXTRAVERSION = -test11
        +EXTRAVERSION =
   d. 找到了针对的内核就可以用patch来升级内核了。

2.6 如何统计linux内核有多少行代码?
   尝试以下shell命令:
   find /usr/src/linux-2.x.x  -name "*.[chS]" | xargs cat | wc -l

2.7 xx结构的定义在哪个内核源文件中?
   a. 请使用源码查看工具,见问题2.4。
   b. 如果用grep等文本搜索工具,主要在include/linux和include/asm两个目录下
      搜索。

2.8 volatile和__volatile__是什么意思?
   a. volatile是C语言定义的关键字,gcc为了需要又定义了__volatile__,它和
      volatile表达的是同一意思。
   b. volatile的本意是"易变的",由于访问寄存器的速度快于访存,所以编译器一般
      都会作优化以减少访存。如果变量加上volatile修饰,则编译器就不会对此变量
      的读写操作进行优化,即不通过寄存器缓冲而直接访存。
   c. __asm__ __volatile__一起指示编译器不要改动优化后面的汇编语句。

2.9 do{ ... } while(0)是什么意思?
   a. 主要是为了避免宏在不同情况展开可能会出现的一些错误。
   b. 在http://www.kernelnewbies.org/faq/上有详细介绍。

2.10 list_entry的定义是怎么回事?
   a. list_entry的定义在内核源文件include/linux/list.h中:
      #define list_entry(ptr, type, member) \
        ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
   b. 其功能是根据list_head型指针ptr换算成其宿主结构的起始地址,该宿主结构是
      type型的,而ptr在其宿主结构中定义为member成员。如下图:

      req-->|type型对象起始地址
            |
            |... ...
      ptr-->|ptr指针所指的member成员地址
            |
            |... ...

     ptr指向图中所示的位置,通过(unsigned long)(&((type*)0)->member)得到ptr
     和req之间的差值,ptr减去这个差值就得到了type型宿主结构的指针req,返回
     类型为(type*)。

2.11 校内查看Linux内核源代码的地址
http://10.214.14.127/lxr/http/source/

3. 模块编程问题
3.1 模块编程需要注意什么?
   a. 在gcc编译选项中增加-c
   b. 在gcc编译选项中定义两个宏:-DMODULE -D__KERENL__
      或直接在源文件中定义这两个宏:
      #define MODULE
      #define __KERNEL__
   c. 在源文件中包括module.h文件:
      #i nclude
   d. 假定你现在运行的内核的源码目录绝对路径是MyKernelSrcPath,在gcc编译时
      增加选项:
         -I $MyKernelSrcPath/include (如-I /usr/src/linux/include)
      注意MyKernelSrcPath必须是指向与当前运行的系统内核匹配的(版本一致)、
      能编译成功的(保证源代码目录的完整性)、已经config过的(保证有.config和
      include/linux/autoconf.h文件)内核源码目录
      注意:通常不要 -I /usr/include/linux
   e. 如果要用inline功能,需要在gcc编译选项中增加-O2
   f. init_module()函数必须return 0,否则会出现Device or resource busy错误。

3.2 为什么insmod一个模块时显示版本不匹配?
   a. 见上面3.1->d
   b. 某些时候用insmod -f能够成功加载,但需谨慎使用。

3.3 为什么出现Unresolved Symbol错误?
   a. 首先查看文件/proc/ksyms,看内核有没有输出这个符号,不同的内核版本如
      2.2和2.4输出的符号会有些变化。
   b. 如果内核输出的符号带有版本控制信息如符号printk_R12345678,则性质同
      问题3.2。
   c. 注意:现在有很多版本都不输出sys_call_table了,另想办法吧!

3.4 为什么出现no license错误?
   在源文件加入下面一行(加在文件头部,尾部均可):
   MODULE_LICENSE("GPL");

3.5 为什么看不到用printk打印的信息?
   a. 打印消息受级别的限制,消息级别可以通过printk设置,如:
        printk("something");  /* 其中0<=n<=7 */
      假设控制台的消息级别为m, 当n      这样一方面可以提高要打印消息本身的级别(数字越小级别越高),
      另一方面可以改变控制台的消息级别(可从1到8),如改为8可用以下命令:
       # echo "8" > /proc/sys/kernel/printk
   b. 用dmesg命令看。
   c. 当系统运行klogd和syslogd时,内核消息就会由klogd分发到syslogd,
      syslogd会根据配置文件/etc/syslog.conf作相应处理,具体可以查看syslogd
      和syslog.conf的man页。


4. 内核开发问题
4.1 怎么制作、使用patch文件?
   a. patch文件是由diff命令生成的,使用patch文件用patch命令,具体可查看diff
      和patch的man页和info。
   b. diff命令的常用选项组合是urN,如:
        diff -urN linux/ my_linux/ >mypatch.diff

4.2 在内核中可以使用系统调用吗?
   a. 可以。内核源代码中就有使用系统调用的例子,如open()、execve()等。
   b. 在内核中使用系统调用必须要在源文件中包括以下两行:
      #define __KERNEL_SYSCALLS__
      #i nclude
   c. 内核中使用系统调用的相关定义可查看文件include/asm/unistd.h。
      如果要用的系统调用该文件中没有定义,可以按照其格式自行添加。
   d. 如果要在模块中使用系统调用,必须要自己定义errno如:
      int errno;
      内核在lib/errno.c中定义了errno,但该符号不导出,所以模块编程时需要自己
      定义errno,用以存放系统调用出错号。

4.3 在内核中怎么打开并操作一个文件?
   a. 直接用open()、read()等系统调用,见问题4.2。
   b. 用filp_open()函数打开文件,得到struct file *的指针fp。
      使用指针fp进行相应操作,如读文件可以用fp->f_ops->read。
      最后用filp_close()函数关闭文件。
      filp_open()、filp_close()函数在fs/open.c定义,在include/linux/fs.h中
      声明。
   c. 自己写包装函数,可参照文件fs/exec.c中的open_exec()和kernel_read()函数。
      在http://www.linuxforum.net/forum/showflat.php?Cat=&Board=linuxK
       &Number=363455&page=&view=&sb=&o=&vc=1上有些代码可以参照。

4.4 在内核中读写文件时为什么会出现EFAULT(-14)错误?
   a. 内核文件系统提供的read()和write()之类的函数,期望是对用户态程序服务的,
      所以它会验证读写缓冲区不超过用户空间的上限即0xC000 0000。但现在内核中
      要读写文件,缓冲区在内核中即地址会超过0xC000 0000。
   b. 在读写文件前先得到当前fs:mm_segment_t old_fs=get_fs();
      并设置当前fs为内核fs:set_fs(KERNEL_DS);
      在读写文件后再恢复原先fs: set_fs(old_fs);
      set_fs()、get_fs()等相关宏在文件include/asm/uaccess.h中定义。

4.5 怎么在系统中增加一个自己的系统调用?
    去http://www.linuxaid.com.cn/engineer/ideal/kernel/new_syscall.htm
    和http://www.xenotime.net/linux/syscall_ex/看看。

4.6 怎么在内核中加入自己的驱动程序?
   a. 去http://www-900.ibm.com/developerWorks/cn/linux/kernel/l-kerconf/
      index.shtml看看,了解一下整个内核的配置编译系统。
   b. 在相应位置建立自己的源码目录、文件、Makefile等。
   c. 修改上层Makefile,把自己的程序加入到内核编译系统中。
   d. 修改上层Config.in,把自己的程序加入到内核配置系统中。
   e. 确保自己的初始化函数被调用。有两种方法,一是显式调用,即在原来的系统
     初始化函数中直接加入对自己的调用,如字符设备就在drivers/char/mem.c中的
     chr_dev_init()函数中加入,块设备就在drivers/block/ll_rw_blk.c中的
     blk_dev_init()函数中加入。另一种方法是用initcall,用宏module_init来申
     明你的初始化函数,操作系统在初始化到一定阶段后会自动通过init/main.c中
     的do_initcalls()函数来统一调用这些初始化函数。module_init宏在文件
     include/linux/init.h中定义。

4.7 怎么通过程序得到cpu和mem使用率?
   a. 这些信息的最终来源都是/proc目录下的文件,如/proc/stat等。
   b. procps包下的命令如top、vmstat等实现了这些功能,可以参照其源代码。
   c. procps包可从Redhat发行版中得到,也可从http://www.surriel.com/procps/
     处获得。

4.8 如何获得高精度的系统时间?
   a. Linux中jiffy是时钟的基本单位,对于一般的系统来说配置成10ms。大多数时
     钟相关的系统调用都是基于jiffy,所以精度不会太高。
   b. 可以考虑使用TSC(time stamp counter)、rtc(real time clock)等寄存器来获得
     高精度时钟,具体可查看相关的硬件手册。

4.9 怎么进行系统性能调谐?
   a. IBM developworks:
        http://www-900.ibm.com/developerWorks/cn/linux/l-kperf/index.shtml
        http://www-900.ibm.com/developerWorks/cn/linux/management/tune/index.sht
ml
   b. Linux Performance Tuning项目:http://linuxperf.nl.linux.org/
   c. http://www.fixdown.com/article/article/724.htm

4.10 内核中怎么进行互斥?
    a. Linux内核中有两种机制实现互斥:semaphore和spinlock。semaphore是让进
      程睡眠等待资源,这一般假设无法预测资源什么时候可以获得;spin_lock一般
      用在SMP中,它假设所等待的资源马上就会被释放,所以循环等待资源。
      semaphore只能用于非中断环境(典型的中断环境过程包括象timer之类的中断
      服务程序,softirq等)的进程间互斥,spinlock可以用于所有的进程间包括不同
      cpu的进程间的互斥,spinlock主要用于保护短小的临界区,使用时必须要特别注
      意死锁问题。
    b. semaphore是通过进程调度来实现互斥的。进程请求获取semaphore时,如果
      semaphore空闲则该进程获得semaphore,设置标志并返回;如果semaphore忙
      (其它用户已经获得semaphore)则系统构建等待队列并通过进程调度机制让本进
      程睡眠。进程释放semaphore时,系统按一定规则通过等待队列唤醒一个睡眠进
      程。对semaphore可执行up()和down()操作,详见include/asm/semaphore.h文件。
    c. spinlock主要是为SMP互斥而引入的。在请求获取spinlock时,如果空闲则获得
      spinlock,设置标志并返回。如果spinlock已经被其它用户获得而处于忙状态,
      系统就会一直占用CPU资源,不停查询spinlock的状态直到获得spinlock。


5. 其它问题
5.1 如何学习Linux内核?
    请先阅读本版精华区内核学习目录的相关文章。

5.2 如何下载精华区?
   a. 除了88提供的下载,还可以通过脚本下载-_-

5.3 init进程是核心进程吗?init与初始进程是不是一回事?
    Linux操作系统在系统初始化之初就捏造了一个原始进程(原始进程在系统初始化
   完毕后就演化成idle进程),当系统初始化进行到一定阶段,原始进程会创建(通
   过kernel_thread()函数)出来init进程,init进程继续进行系统初始化工作并在最
   后执行execve("/sbin/init",...),这样init就从原来的核心进程摇身一变成用户
   进程(用户程序/sbin/init)了。init进程的pid为1,原始进程(idle进程)的
   pid为0。所有其它的进程都由init进程派生,用ps或pstree命令可以看到这一点。

5.4 initrd(.img)有什么用?
   a. initrd(.img)是一个文件系统映像,里面一般包含一些特殊的硬件模块尤其是存
     储设备如scsi/raid/ext3模块,以便在保持内核足够小的同时又支持尽可能多的硬
     件设备,常被安装程序使用。
     initrd(.img)也不是必需的,只要必要的模块编译进内核就可以不用initrd(.img)。
   b. 在使用了initrd(.img)时,系统引导的大致过程如下:
    1)Loader程序(如lilo和grub)加载内核和initrd(.img)
    2)内核解压缩initrd(.img)为正常的RAM盘文件系统并挂接为根分区
    3)执行linuxrc,在此过程中会加载硬件模块
    4)在linuxrc终止后,真正的根文件系统被挂接
    5)在根文件系统上完成正常的引导过程。对于正常的系统而言,执行/sbin/init,
      这时控制就会转到正常的大家所熟知的启动过程。而对于安装程序,只需将控制
      转到安装过程的第一阶段,由它完成后续的安装环境的加载,设备的进一步初始
      化等操作。
   c. 要使用initrd(.img)首先内核必须配置成支持initrd:
        CONFIG_BLK_DEV_RAM=y
        CONFIG_BLK_DEV_INITRD=y
      其次要在Loader脚本中增加相应指示。如在grub.conf中增加一行:
        initrd /boot/initrd-2.4.20.img
   d. 可用mkinitrd命令创建initrd(.img)文件:
        mkinitrd  imagefilename  kernelversion
      如对于2.4.20的内核可以:
        mkinitrd /boot/initrd-2.4.20.img 2.4.20
   e. 具体可查看Documentation/initrd.txt和man mkiinitrd。mkinitrd命令执行的详
      细过程可以直接查看/sbin/mkinitrd(shell脚本)文件。


6. 关于本FAQ
   本FAQ主要根据本版以前的文章整理而成。
   特别感谢mada、pepp等网友提出宝贵意见!


7. Changelog
   2004/05/10 junky
   --发布FAQ 1.0
   --调整了目录结构
   --增加了Q2.5,Q2.6,Q5.1,Q5.2,Q5.4
   --Q1增加了2本新书
   --Q2.2增加了gnu.org的链接
   --Q3.1增加了init_module()返回0
   --Q3.2增加了insmod -f
   --Q4.2增加了errno的说明
   --Q4.9改名并增加了IBM developworks的链接

   2004/07/07 junky
   --删除Q2.11

   --增加Q2.11
   --修改Q1, Q2.4, Q5.2
   2004/12/15 vatano

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

评论(0) | 阅读(313)
发表于:2007-9-18 16:35:06
标签:C  CONST  

1

C中CONST的使用

基本解释

  const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程度上可以提高程序的健壮性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一些帮助。
虽然这听起来很简单,但实际上,const的使用也是c语言中一个比较微妙的地方,微妙在何处呢?请看下面几个问题。

  问题:const变量 & 常量

  为什么我象下面的例子一样用一个const变量来初始化数组,ANSI C的编译器会报告一个错误呢?



const int n = 5;
int a[n];



  答案与分析:

  1)、这个问题讨论的是“常量”与“只读变量”的区别。常量肯定是只读的,例如5, “abc”,等,肯定是只读的,因为程序中根本没有地方存放它的值,当然也就不能够去修改它。而“只读变量”则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。C语言关键字const就是用来限定一个变量不允许被改变的修饰符(Qualifier)。上述代码中变量n被修饰为只读变量,可惜再怎么修饰也不是常量。而ANSI C规定数组定义时维度必须是“常量”,“只读变量”也是不可以的。

  2)、注意:在ANSI C中,这种写法是错误的,因为数组的大小应该是个常量,而const int n,n只是一个变量(常量 != 不可变的变量,但在标准C++中,这样定义的是一个常量,这种写法是对的),实际上,根据编译过程及内存分配来看,这种用法本来就应该是合理的,只是ANSI C对数组的规定限制了它。

  3)、那么,在ANSI C 语言中用什么来定义常量呢?答案是enum类型和#define宏,这两个都可以用来定义常量。


问题:const变量 & const 限定的内容

  下面的代码编译器会报一个错误,请问,哪一个语句是错误的呢?



typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;

  答案与分析:

  问题出在p2++上。

  1)、const使用的基本形式: const char m;

  限定m不可变。

  2)、替换1式中的m, const char *pm;

  限定*pm不可变,当然pm是可变的,因此问题中p1++是对的。

  3)、替换1式char, const newType m;

  限定m不可变,问题中的charptr就是一种新类型,因此问题中p2不可变,p2++是错误的。

  问题:const变量 & 字符串常量

  请问下面的代码有什么问题?

char *p = "i'm hungry!";
p[0]= 'I';

  答案与分析:

  上面的代码可能会造成内存的非法写操作。分析如下, “i'm hungry”实质上是字符串常量,而常量往往被编译器放在只读的内存区,不可写。p初始指向这个只读的内存区,而p[0] = 'I'则企图去写这个地方,编译器当然不会答应。

  问题:const变量 & 字符串常量2

  请问char a[3] = "abc" 合法吗?使用它有什么隐患?

  答案与分析:

  在标准C中这是合法的,但是它的生存环境非常狭小;它定义一个大小为3的数组,初始化为“abc”,,注意,它没有通常的字符串终止符'\0',因此这个数组只是看起来像C语言中的字符串,实质上却不是,因此所有对字符串进行处理的函数,比如strcpy、printf等,都不能够被使用在这个假字符串上。

  问题5:const & 指针

  类型声明中const用来修饰一个常量,有如下两种写法,那么,请问,下面分别用const限定不可变的内容是什么?

  1)、const在前面

const int nValue; //nValue是const
const char *pContent; //*pContent是const, pContent可变
const (char *) pContent;//pContent是const,*pContent可变
char* const pContent; //pContent是const,*pContent可变
const char* const pContent; //pContent和*pContent都是const

  2)、const在后面,与上面的声明对等

int const nValue; // nValue是const
char const * pContent;// *pContent是const, pContent可变
(char *) const pContent;//pContent是const,*pContent可变
char* const pContent;// pContent是const,*pContent可变
char const* const pContent;// pContent和*pContent都是const


  答案与分析:

  const和指针一起使用是C语言中一个很常见的困惑之处,在实际开发中,特别是在看别人代码的时候,常常会因为这样而不好判断作者的意图,下面讲一下我的判断原则:

  沿着*号划一条线,如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。你可以根据这个规则来看上面声明的实际意义,相信定会一目了然。

  另外,需要注意:对于const (char *) ; 因为char *是一个整体,相当于一个类型(如 char),因此,这是限定指针是const。


另=======

const用于函数时出现三个位置:
例如:
const returnVal function (const list_array)const;

形式不过如此
第一个const意思是:返回值是常量
第二个const意思是:函数过程中不能修改list_array的值
第三个const意思是:函数过程不能隐式的修改function参数的值

===

zzhttp://publishblog.blogchina.com/blog/tb.b?diaryID=3217823

const char*, char const*, char*const的区别问题几乎是C++面试中每次都会有的题目。
事实上这个概念谁都有只是三种声明方式非常相似很容易记混。
Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:
把一个声明从右向左读。
char * const cp; ( * 读成 pointer to )
cp is a const pointer to char
const char * p;
p is a pointer to const char;
char const * p;
同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。


另:下面定义的一个指向字符串的常量指针:
  char * const prt1 = stringprt1;
  其中,ptr1是一个常量指针。因此,下面赋值是非法的。
  ptr1 = stringprt2;
  而下面的赋值是合法的:
  *ptr1 = "m";
  因为指针ptr1所指向的变量是可以更新的,不可更新的是常量指针ptr1所指的方向(别的字符串)。
  下面定义了一个指向字符串常量的指针:
  const * ptr2 = stringprt1;
  其中,ptr2是一个指向字符串常量的指针。ptr2所指向的字符串不能更新的,而ptr2是可以更新的。因此,
  *ptr2 = "x";
  是非法的,而:
  ptr2 = stringptr2;
  是合法的。
  所以,在使用const修饰指针时,应该注意const的位置。定义一个指向字符串的指针常量和定义一个指向字符串常量的指针时,const修饰符的位置不同,前者const放在*和指针名之间,后者const放在类型说明符前。

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

评论(7) | 阅读(2124)
发表于:2007-9-13 9:39:18
标签:RAM  ROM  单片机  

1

RAM和ROM的比较

RAM - Random Access Memory 随机存储器
ROM - Read Only Memory 只读存储器

RAM(Random Access Memory)的全名为随机存取记忆体,它相当于PC机上的移动存储,用来存储和保存数据的。它在任何时候都可以读写,RAM通常是作为操作系统或其他正在运行程序的临时存储介质(可称作系统内存)。
不过,当电源关闭时RAM不能保留数据,如果需要保存数据,就必须把它们写入到一个长期的存储器中(例如硬盘)。正因为如此,有时也将RAM称作“可变存储器”。RAM内存可以进一步分为静态RAM(SRAM)和动态内存(DRAM)两大类。DRAM由于具有较低的单位容量价格,所以被大量的采用作为系统的主记忆。
RAM和ROM相比,两者的最大区别是RAM在断电以后保存在上面的数据会自动消失,而ROM就不会。4~8M的RAM对于一般应用程序的运行已经足够,如果追求多媒体功能,64M的RAM容量也只能够算是基本要求。


ROM(Read Only Memory)的全名为唯读记忆体,它相当于PC机上的硬盘,用来存储和保存数据。ROM数据不能随意更新,但是在任何时候都可以读取。即使是断电,ROM也能够保留数据。但是资料一但写入后只能用特殊方法或根本无法更改,因此ROM常在嵌入式系统中担任存放作业系统的用途。现在市面上主流的PDA的ROM大小是64MB以及128MB。
RAM和ROM相比,两者的最大区别是RAM在断电以后保存在上面的数据会自动消失,而ROM就不会。

系统分类: 单片机   |    用户分类: 无分类    |    来源: 无分类

评论(0) | 阅读(979)
发表于:2007-9-12 9:37:57
标签:RS232  电平  

1

rs232 rs485 rs422 rs423 的一些比较

RS232接口是标准串行接口,其通讯距离小于15 m,传输速率小于20 kb/s。RS232标准是按负逻辑定义的,他的“1”电平在-5~-15 V之间,“0”电平在+5~+15 V之间。虽然RS232应用很广,但由于数据传输速率慢,通讯距离短,特别是在100 m以上的远程通讯中难以让人满意,因此通常采用RS422,RS449,RS423及RS485等接口标准来实现远程通讯。RS485标准的通信最长距离是1200米(4000英呎),或是最多并联32个通信单元,

RS-232、RS-422与RS-485标准及应用
一、RS-232、RS-422与RS-485的由来
RS-232、RS-422与RS-485都是串行数据接口标准,最初都是由电子工业协会(EIA)制订并发布的,RS-232在1962年发布,命名为EIA-232-E,作为工业标准,以保证不同厂家产品之间的兼容。RS-422由RS-232发展而来,它是为弥补RS-232之不足而提出的。为改进RS-232 通信距离短、速率低的缺点,RS-422定义了一种平衡通信接口,将传输速率提高到10Mb/s,传输距离延长到4000英尺(速率低于100kb/s时),并允许在一条平衡总线上连接最多10个接收器。RS-422是一种单机发送、多机接收的单向、平衡传输规范,被命名为TIA/EIA-422-A标 准。为扩展应用范围,EIA又于1983年在RS-422基础上制定了RS-485标准,增加了多点、双向通信能力,即允许多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保 护特性,扩展了总线共模范围,后命名为TIA/EIA-485-A标准。由于EIA提出的建议标准都是以“RS”作为前缀,所以在通讯工业领域,仍然习惯将上述标准以RS作前缀称谓。
RS-232、RS-422与RS-485标准只对接口的电气特性做出规定,而不涉及接插件、电缆或协议,在此基础上用户可以建立自己的高层通信协议。因此在视频界的应用,许多厂家都建立了一套高层通信协议,或公开或厂家独家使用。如录像机厂家中的Sony与松下对录像机的RS-422控制协议是有差异的,视频服务器上的控制协议则更多了,如Louth、Odetis协议是公开的,而ProLINK则是基于Profile上的。
二、RS-232串行接口标准
目前RS-232是PC机与通信工业中应用最广泛的一种串行接口。RS-232被定义为一种在低速率串行通讯中增加通讯距离的单端标准。RS-232采取不平衡传输方式,即所谓单端通讯图1 收、发端的数据信号是相对于信号地,如从DTE设备发出的数据在使用DB25连接器时是2脚相对7脚(信号地)的电平,DB25各引脚定义参见图1。典型的RS-232信号在正负电平之间摆动,在发送数据时,发送端驱动器输出正电平在+5~+15V,负电平在-5~-15V电平。当无数据传输时,线上为TTL,从开始传送数据到结束,线上电平从TTL电平到RS-232电平再返回TTL电平。接收器典型的工作电平在+3~+12V与-3~-12V。由于发送电平与接收电平的差仅为2V至3V左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其传送距离最大为约15 米,最高速率为20kb/s。RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其驱动器负载为3~7kù。所以RS-232适合本地设备之间的通信。其有关电气参数参见表1。
规定 RS232 RS422 R485
工作方式 单端 差分 差分
节点数 1收、1发 1发10收 1发32收
最大传输电缆长度 50英尺 400英尺 400英尺
最大传输速率 20Kb/S 10Mb/s 10Mb/s
最大驱动输出电压 +/-25V -0.25V~+6V -7V~+12V
驱动器输出信号电平
负载 +/-5V~+/-15V +/-2.0V +/-1.5V
表1
三、RS-422与RS-485串行接口标准
1.平衡传输
RS-422、RS-485与RS-232不一样,数据信号采用差分传输方式,也称作平衡传输,它使
用一对双绞线,将其中一线定义为A,另一线定义为B,如图2。
图2
通常情况下,发送驱动器A、B之间的正电平在+2~+6V,是一个逻辑状态,负电平在-2
~6V,是另一个逻辑状态。另有一个信号地C,在RS-485中还有一“使能”端,而在RS-422中
这是可用可不用的。“使能”端是用于控制发送驱动器与传输线的切断与连接。当“使能”
端起作用时,发送驱动器处于高阻状态,称作“第三态”,即它是有别于逻辑“1”与“0”
的第三态。
接收器也作与发送端相对的规定,收、发端通过平衡双绞线将AA与BB对应相连,当在收
端AB之间有大于+200mV的电平时,输出正逻辑电平,小于-200mV时,输出负逻辑电平。接收
器接收平衡线上的电平范围通常在200mV至6V之间。参见图3。
图3
2.RS-422电气规定
(负载最小值)
驱动器输出信号电平
(空载最大值)
空载 +/-25V +/-6V +/-6V
驱动器负载阻抗(ù) 3K~7K 100 54
摆率(最大值) 30V/ìs N/A N/A
接收器输入电压范围 +/-15V -10V~+10V -7V~+12V
接收器输入门限 +/-3V +/-200mV +/-200mV
接收器输入电阻(ù) 3K~7K 4K(最小) ≥12K
驱动器共模电压 -3V~+3V -1V~+3V
接收器共模电压 -7V~+7V -7V~+12V
RS-422标准全称是“平衡电压数字接口电路的电气特性”,它定义了接口电路的特性。
图5是典型的RS-422四线接口。实际上还有一根信号地线,共5根线。图4是其DB9连接器引脚定义。由于接收器采用高输入阻抗和发送驱动器比RS232更强的驱动能力,故允许在相同传输线上连接多个接收节点,最多可接10个节点。即一个主设备(Master),其余为从设备(Salve),从设备之间不能通信,所以RS-422支持点对多的双向通信。接收器输入阻抗为4k,故发端最大负载能力是10×4k+100ù(终接电阻)。RS-422四线接口由于采用单独的发送和接收通道,因此不必控制数据方向,各装置之间任何必须的信号交换均可以按软件方式(XON/XOFF握手)或硬件方式(一对单独的双绞线)。
RS-422的最大传输距离为4000英尺(约1219米),最大传输速率为10Mb/s。其平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能达到最大传输距离。只有在很短的距离下才能获得最高速率传输。一般100米长的双绞线上所能获得的最大传输速率仅为1Mb/s。
RS-422需要一终接电阻,要求其阻值约等于传输电缆的特性阻抗。在矩距离传输时可不需终接电阻,即一般在300米以下不需终接电阻。终接电阻接在传输电缆的最远端。
RS-422有关电气参数见表1
3.RS-485电气规定
由于RS-485是从RS-422基础上发展而来的,所以RS-485许多电气规定与RS-422相仿。如都采用平衡传输方式、都需要在传输线上接终接电阻等。RS-485可以采用二线与四线方式,二线制可实现真正的多点双向通信,参见图6。而采用四线连接时,与RS-422一样只能实现点对多的通信,即只能有一个主(Master)设备,其余为从设备,但它比RS-422有改进, 无论四线还是二线连接方式总线上可多接到32 个设备。参见图7。
RS-485与RS-422的不同还在于其共模输出电压是不同的,RS-485是-7V至+12V之间,而RS-422在-7V至+7V之间,RS-485接收器最小输入阻抗为12k剑鳵S-422是4k健; 旧峡梢运 礴S-485满足所有RS-422的规范,所以RS-485的驱动器可以用在RS-422网络中应用。
RS-485有关电气规定参见表1。
RS-485与RS-422一样,其最大传输距离约为1219米,最大传输速率为10Mb/s。平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能使用规定最长的电缆长度。只有在图4 图5图6 图7 很短的距离下才能获得最高速率传输。一般100米长双绞线最大传输速率仅为1Mb/s。
RS-485需要2个终接电阻,其阻值要求等于传输电缆的特性阻抗。在矩距离传输时可不需终接电阻,即一般在300米以下不需终接电阻。终接电阻接在传输总线的两端。
四、RS-422与RS-485的网络安装注意要点
RS-422可支持10个节点,RS-485支持32个节点,因此多节点构成网络。网络拓扑一般采用终端匹配的总线型结构,不支持环形或星形网络。在构建网络时,应注意如下几点:
1.采用一条双绞线电缆作总线,将各个节点串接起来,从总线到每个节点的引出线长度应尽量短,以便使引出线中的反射信号对总线信号的影响最低。图8所示为实际应用中常见的一些错误连接方式(a,c,e)和正确的连接方式(b,d,f)。a,c,e这三种网络连接尽管不正确,在短距离、低速率仍可能正常工作,但随着通信距离的延长或通信速率的提高,其不良影响会越来越严重,主要原因是信号在各支路末端反射后与原信号叠加,会造成信号质量下降。
2.应注意总线特性阻抗的连续性,在阻抗不连续点就会发生信号的反射。下列几种情况易产生这种不连续性:总线的不同区段采用了不同电缆,或某一段总线上有过多收发器紧靠在一起安装,再者是过长的分支线引出到总线。
总之,应该提供一条单一、连续的信号通道作为总线。
图8
五、RS-422与RS-485传输线上匹配的一些说明
对RS-422与RS-485总线网络一般要使用终接电阻进行匹配。但在短距离与低速率下可以不用考虑终端匹配。那么在什么情况下不用考虑匹配呢?理论上,在每个接收数据信号的中点进行采样时,只要反射信号在开始采样时衰减到足够低就可以不考虑匹配。但这在实际上难以掌握,美国MAXIM公司有篇文章提到一条经验性的原则可以用来判断在什么样的数据速率和电缆长度时需要进行匹配:当信号的转换时间(上升或下降时间)超过电信号沿总线单向传输所需时间的3倍以上时就可以不加匹配。例如具有限斜率特性的RS-485接口MAX483输出信号的上升或下降时间最小为250ns,典型双绞线上的信号传输速率约为0.2m/ns(24AWG PVC电缆),那么只要数据速率在250kb/s以内、电缆长度不超过16米,采用MAX483作为RS-485接口时就可以不加终端匹配。
一般终端匹配采用终接电阻方法,前文已有提及,RS-422在总线电缆的远端并接电阻,RS-485则应在总线电缆的开始和末端都需并接终接电阻。终接电阻一般在RS-422网络中取100 ù,在RS-485网络中取120ù。相当于电缆特性阻抗的电阻,因为大多数双绞线电缆特性阻抗大约在100~120ù。这种匹配方法简单有效,但有一个缺点,匹配电阻要消耗较大功率,对于功耗限制比较严格的系统不太适合。
另外一种比较省电的匹配方式是RC匹配,如图9。利用一只电容C隔断直流成分可以节省大部分功率。但电容C的取值是个难点,需要在功耗和匹配质量间进行折衷。
还有一种采用二极管的匹配方法,如图10。这种方案虽未实现真正的“匹配”,但它利用二极管的钳位作用能迅速削弱反射信号,达到改善信号质量的目的。节能效果显著。
六、RS-422与RS-485的接地问题
电子系统接地是很重要的,但常常被忽视。接地处理不当往往会导致电子系统不能稳定工作甚至危及系统安全。RS-422与RS-485传输网络的接地同样也