第一篇:Java系统程序员修炼之道
Java系统程序员修炼之道
——动力节点java 一:Java语言学习
对线程(thread),串行化,反射,网络编程,JNI技术,容器(Map,List, Iterator), 类加载器(ClassLoader),输入输出流,垃圾回收机制,有比较深入的了解,最起码做过项目应用。有过Java项目的性能优化经验,最起码掌握一种性能监视工具的使用,熟悉JVM参数,最起码知道可以在JVM启动时指定不同垃圾回收机制,以及不同垃圾回收机制之间的差别,熟悉JVM参数优化。二:J2EE方面
最好知道JDBC规范是怎么回事情,面对Oracle数据库如果告诉你JDBC驱动不能用了,你还知道有OCI驱动可以。掌握常见的SQL语句,熟悉JMS,JNDI等组件,掌握一套web开发模式,从前台到后台,有能力整合好这样的框架。理解并掌握MVC思想,像SSH已经实现了MVC的分层,几乎不需要你自己再实现,假设你开发一个简单的Swing程序,你能MVC就说明你真的掌握了MVC的精髓。有能力在J2EE前端开发中构建自己的MVC模式,知道什么是WEB2.0,知道什么是SOA,SaaS,SaaP等含义 三:理解并能合理运用设计模式,UML建模
知道并理解设计模式中蕴含的几种基本原则如:里氏替换原则,开闭原则,合成复用原则,依赖倒置原则有很好的理解,并能举例说明。对常用的设计模式如工厂模式,单例模式,观察者模式,责任链模式,桥接模式等知道灵活运用,明白什么是回调(Callback)。最后用一位高人话来总结设计模式,它是为了让软件更容易被别人读懂,更容易维护而产生,设计模式本质是程序员之间的交流,如果A用工厂模式设计一个模块B来接替,A只要说该模块是工厂模式实现,B维护起来应该容易得多,所以设计模式是关于交流,不关于代码。切忌滥用设计模式。学会使用UML建模工具至少熟悉一种URL建模工具。四:注重用户体验,掌握KISS原则,知道欧卡姆剃刀原则
顾客就是上帝这个口号我们已经喊了N年了,程序员的劳动成果最终也需要转换为服务提供给客户,用户体验至关重要,常常看到的场景是功能实现了,软件很难使用,程序员有个很充足的理由我不是美工,其实注重用户体验跟美工八杆子也打不到一起,FoxMail的成功在很大程度是用户体验的成功,友好,清晰的用户提示,强的容错与纠错设计是获得好的用户体验的不二法门。傻瓜相机顾名思义傻子都会使用,这个就著名的KISS原则(Keep itsimple and stupid)意思是UI设计要简单明了,傻子一看就知道怎么用,想想我们做出来的东西,对照说明书都不知道怎么用。另外一个就是最著名的例子IPhone手机外观设计,是典型的欧卡姆剃刀设计原则来完成人机交互。五:自动测试与软件配置管理(SCM)实现
知道什么是软件配置管理,知道Hudson运用该工具SCM,知道怎么获取测试代码覆盖率,Java有效代码行数(NCSS),完成firebug,JDepend等工具集成ant/maven。熟悉并注重在开发过程中使用JUnit单元测试,理解白盒测试规范。六:熟悉常见的网络通信协议
对HTTP协议,知道POST, GET的区别是什么,阅读过HTTP相关的RFC文档。学会使用sniffer工具查看数据包,帮助查找与调试程序,知道TCP与UDP的区别,知道并理解E-Mail发送与接受的协议如SMTP,POP3,IMAP等协议,了解MIME与Base64编码。知道组播是怎么回事情。
七:面向市场,永远对新技术保持渴望
计算机技术的发展日新月异,做为IT行业的软件开发人员要不断的给自己充电,更新自己的技术与时代保持同步,同时还要面向市场,华为总裁任正非说过-“华为的技术革新必须面向市场”,作为程序员同样要有市场意识,很多人都后悔没有在android刚出来的时候加以关注学习。那些很早关注android开发技术的很多程序员因此获得丰厚回报。如今HTML5得到越来越多的浏览器厂家支持,你是否已经跟上脚步,开始学习。八:保持谦虚,三人行必有我师
乔帮主说他要保持初心,努力学习,我等更应该保持谦虚,IT技术发展日新月异,在你眼中不可能实现的技术,也许别人早已经有思路。保持谦虚就有机会吸取别人身上的长处,古人有云:满招损,谦受益。一个得道的高人更是说出了”下下人,上上智”的禅语。永远不要拒绝帮助你周围的人解决难题,解决难题是进步最快途径。不要放弃任何一次可以提升自己技术与能力的机会。
九:养成总结的习惯,不断反思
上学的时候老师常让写小结,也没总结出来所以然,以至于工作以后再也不提这档子事情,建议每个项目做完以后对自己都有个小结,总结自己在项目里面学到了什么,反问自己能不能完成在不需要别人帮助的情况下自己完成这样的系统搭建,是否熟悉与掌握项目中所用到的技术,即使有些东西不是你负责完成的但是什么也不能阻挡一颗求知的心,总结要尽量详细记录你遇到那些难题是怎么一个一个的解决的,下次再遇到你是否可以很快解决或者避免这样的问题。有总结才有提高,孔子曰:学而不思则罔,如果我们只是coding到吐血,不思考,不总结提高,永远不可能有能有本质提高,秦相李斯有云:“泰山不让土壤,故能成其大,河海不择细流,故能就其深”,点滴积累不断总结方能量变导致质变。十:数学功底与算法知识 用任何编程语言开发应用,都离不开核心算法支持,很多国外的软件单单从UI上看,恐怕写几年程序的人都可以模仿,但是UI之下的那些真实深浅不一,相信不是你想模仿就可以模仿的,为什么我们越来越山寨,因为我们没有核心竞争力,对于程序员来说算法与数学显然是他最重要的核心竞争力之一。《算法导论》,《编程珠玑》等书绝对值得读十遍。微软亚洲研究院视觉计算组负责人在一次演讲中说到他们招人的标准是“三好学生– 数学好,编程好,态度好”。可是现实的普遍情况却是-微机原理闹危机,汇编语言不会变,实变函数学十遍。计算机基础知识被大家普遍忽视。从今天开始好好学习吧…… 十一:Java代码反编译与代码保护
Java编译产生字节码,因而可以被轻松的逆向工程(反编译),微软的C#生产的DLL也一样可以被轻松反编译。正式由于这个原因产生了许多Java开源的代码保护工具,而Proguar是其中佼佼者,已经被google集成在android之中用于Java代码保护 十二:努力成为某个行业或者领域骨干
面对漫长的职业生涯,要想不被淘汰,必须具备一招鲜吃遍天下的能力,选择自己感兴趣的方向,努力而深入的研究,计算机技术发展到今天已经细分很细,努力研究一种Java开源框架或者开源HTTP服务器源码或者研究过网络爬虫源码或者WEBKIT内核,不愁没有人要你。如果你是非常了解金融,企业ERP,证券,保险,移动应用行业的应用开发业务的人,一样不用愁工作。这些知识不随语言而改变,努力做一个有核心竞争力的Java程序员。十三:提高语言与书面表达能力,掌握基础的项目管理知识
文档与语言表达能力是最好的向外界展现自己能力的方式,很多程序员编程能力很高,表达能力一般,Linux能够成功,除了归功于网络社区的力量之外,也得益于Linux作者本人给各大基金会写信,宣传推广,试想如果没有良好的书面语言表达能力,即使Linux系统再优秀,却无法被准确表达,失去各大基金会的支持,Linux还会像今天这么好的局面嘛。所以重视文档,重视提升沟通与表达能力,才有可能成为Java系统程序员。掌握基本的2/8原则,学会将模块细化分配给不同的人,预见并控制项目风险,把握项目进度,优化流程,合理的时间管理,了解TDD,熟悉敏捷开发模式,常规软件开发模式。
第二篇:Java程序员修炼之道
从2002开始接触Java学会HelloWorld这么经典的程序到如今不知不觉已经十年啦,十年中
亲耳听到过不少大牛的演讲,见到过项目中的神人在键盘上运指如飞的编程速度,当时就
被震撼了。当编程越来越成体力活,我们还能有自己的思想,还能修炼为Java系统级别的
程序员嘛?学习与修炼以下知识与技能,帮你早日达成愿望。
一:Java语言学习
对线程(thread),串行化,反射,网络编程,JNI技术,容器(Map,List, Iterator), 类加载器
(ClassLoader),输入输出流,垃圾回收机制,有比较深入的了解,最起码做过项目应用。有
过Java项目的性能优化经验,最起码掌握一种性能监视工具的使用,熟悉JVM参数,最起
码知道可以在JVM启动时指定不同垃圾回收机制,以及不同垃圾回收机制之间的
差别,熟悉JVM参数优化。
二:J2EE方面
最好知道JDBC规范是怎么回事情,面对Oracle数据库如果告诉你JDBC驱动不能用了,你
还知道有OCI驱动可以。掌握常见的SQL语句,熟悉JMS,JNDI等组件,掌握一套web开
发模式,从前台到后台,有能力整合好这样的框架。理解并掌握MVC思想,像SSH已经实
现了MVC的分层,几乎不需要你自己再实现,假设你开发一个简单的Swing程序,你能MVC
就说明你真的掌握了MVC的精髓。有能力在J2EE前端开发中构建自己的MVC模式,知道
什么是WEB2.0,知道什么是SOA,SaaS,SaaP等含义
三:理解并能合理运用设计模式,UML建模
知道并理解设计模式中蕴含的几种基本原则如:里氏替换原则,开闭原则,合成复用原则,依赖倒置原则有很好的理解,并能举例说明。对常用的设计模式如工厂模式,单例模式,观
察者模式,责任链模式,桥接模式等知道灵活运用,明白什么是回调(Callback)。最后用一位
高人话来总结设计模式,它是为了让软件更容易被别人读懂,更容易维护而产生,设计模
式本质是程序员之间的交流,如果A用工厂模式设计一个模块B来接替,A只要说该模块是
工厂模式实现,B维护起来应该容易得多,所以设计模式是关于交流,不关于代码。切忌滥
用设计模式。学会使用UML建模工具至少熟悉一种URL建模工具。
四:注重用户体验,掌握KISS原则,知道欧卡姆剃刀原则
顾客就是上帝这个口号我们已经喊了N年了,程序员的劳动成果最终也需要转换为服务提
供给客户,用户体验至关重要,常常看到的场景是功能实现了,软件很难使用,程序员有个
很充足的理由我不是美工,其实注重用户体验跟美工八杆子也打不到一起,FoxMail的成功
在很大程度是用户体验的成功,友好,清晰的用户提示,强的容错与纠错设计是获得好的
用户体验的不二法门。傻瓜相机顾名思义傻子都会使用,这个就著名的KISS原则(Keep it
simple and stupid)意思是UI设计要简单明了,傻子一看就知道怎么用,想想我们做出来的
东西,对照说明书都不知道怎么用。另外一个就是最著名的例子IPhone手机外观设计,是 典型的欧卡姆剃刀设计原则来完成人机交互。
五:自动测试与软件配置管理(SCM)实现
知道什么是软件配置管理,知道Hudson微机原理闹危机,汇编语言不会变,实变函数学
十遍。计算机基础知识被大家普遍忽视。从今天开始好好学习吧……
十一:Java代码反编译与代码保护
Java编译产生字节码,因而可以被轻松的逆向工程(反编译),微软的C#生产的DLL也一样可
以被轻松反编译。正式由于这个原因产生了许多Java开源的代码保护工具,而Proguard是
其中佼佼者,已经被google集成在android之中用于Java代码保护,访问这里了解更多:
http://proguard.sourceforge.net/
十二:努力成为某个行业或者领域骨干
面对漫长的职业生涯,要想不被淘汰,必须具备一招鲜吃遍天下的能力,选择自己感兴趣的
方向,努力而深入的研究,计算机技术发展到今天已经细分很细,努力研究一种Java开源
框架或者开源HTTP服务器源码或者研究过网络爬虫源码或者WEBKIT内核,不愁没有人要
你。如果你是非常了解金融,企业ERP,证券,保险,移动应用行业的应用开发业务的人,一样不用愁工作。这些知识不随语言而改变,努力做一个有核心竞争力的Java程序员。
十三:提高语言与书面表达能力,掌握基础的项目管理知识
文档与语言表达能力是最好的向外界展现自己能力的方式,很多程序员编程能力很高,表达
能力一般,Linux能够成功,除了归功于网络社区的力量之外,也得益于Linux作者本人给各
大基金会写信,宣传推广,试想如果没有良好的书面语言表达能力,即使Linux系统再优秀,却无法被准确表达,失去各大基金会的支持,Linux还会像今天这么好的局面嘛。所以重视
文档,重视提升沟通与表达能力,才有可能成为Java系统程序员。掌握基本的2/8原则,学
会将模块细化分配给不同的人,预见并控制项目风险,把握项目进度,优化流程,合理的时
间管理,了解TDD,熟悉敏捷开发模式,常规软件开发模式。
十四:掌握英语,良好的读写能力
英语是计算机的母语,掌握好英语对于阅读英文资料学习新技术大有帮助,我的建议是尽量
读英文原版书,如果是算法方面的可能会困难一点,但是其它像设计模式,软件工程,OO
编程思想等尽量读原版,提高自己的英文水平,多多访问开发者,code project,程序员天堂,Pc-magazine等英文IT网站。英语绝对是你必须修炼与提高的技能。此外英语好在外资企业
尤其重要,只有外语足够好才可能在外资企业中突破职业瓶颈,向上发展。
第三篇:Java程序员修炼十大过程
Java程序员修炼十大过程
学习java这么久,见到过项目中的神人在键盘上运指如飞的编程速度,当时就被震撼了。当编程越来越成体力活,我们还能有自己的思想,还能修炼为Java系统级别的程序员嘛?学习与修炼以下知识与技能,帮你早日达成愿望。
一、努力成为某个行业或者领域骨干
面对漫长的职业生涯,要想不被淘汰,必须具备一招鲜吃遍天下的能力,选择自己感兴趣的方向,努力而深入的研究,计算机技术发展到今天已经细分很细,努力研究一种Java开源框架或者开源HTTP服务器源码或者研究过网络爬虫源码或者WEBKIT内核,不愁没有人要你。如果你是非常了解金融,企业ERP,证券,保险,移动应用行业的应用开发业务的人,一样不用愁工作。这些知识不随语言而改变,努力做一个有核心竞争力的Java程序员。
二、保持谦虚,三人行必有我师
乔帮主说他要保持初心,努力学习,我等更应该保持谦虚,IT技术发展日新月异,在你眼中不可能实现的技术,也许别人早已经有思路。保持谦虚就有机会吸取别人身上的长处,古人有云:满招损,谦受益。一个得道的高人更是说出了“下下人,上上智”的禅语。永远不要拒绝帮助你周围的人解决难题,解决难题是进步最快途径。不要放弃任何一次可以提升自己技术与能力的机会。
三、熟悉常见的网络通信协议
对HTTP协议,知道POST, GET的区别是什么,阅读过HTTP相关的RFC文档。学会使用sniffer工具查看数据包,帮助查找与调试程序,知道TCP与UDP的区别,知道并理解E-Mail发送与接受的协议如SMTP,POP3,IMAP等协议,了解MIME与Base64编码。知道组播是怎么回事情。
四、理解并能合理运用设计模式,UML建模
知道并理解设计模式中蕴含的几种基本原则如:里氏替换原则,开闭原则,合成复用原则,依赖倒置原则有很好的理解,并能举例说明。对常用的设计模式如工厂模式,单例模式,观察者模式,责任链模式,桥接模式等知道灵活运用,明白什么是回调(Callback)。最后用一位高人话来总结设计模式,它是为了让软件更容易被别人读懂,更容易维护而产生,设计模式本质是程序员之间的交流,如果A用工厂模式设计一个模块B来接替,A只要说该模块是工厂模式实现,B维护起来应该容易得多,所以设计模式是关于交流,不关于代码。切忌滥用设计模式。学会使用UML建模工具至少熟悉一种URL建模工具。
五、自动测试与软件配置管理(SCM)实现
知道什么是软件配置管理,知道Hudson-http://java.net/projects/hudson/运用该工具SCM,知道怎么获取测试代码覆盖率,Java有效代码行数(NCSS),完成firebug,JDepend等工具集成ant/maven.熟悉并注重在开发过程中使用JUnit单元测试,理解白盒测试规范。
六、Java语言学习
对线程(thread),串行化,反射,网络编程,JNI技术,容器(Map,List, Iterator),类加载器(ClassLoader),输入输出流,垃圾回收机制,有比较深入的了解,最起码做过项目应用。有过Java项目的性能优化经验,最起码掌握一种性能监视工具的使用,熟悉JVM参数,最起码知道可以在JVM启动时指定不同垃圾回收机制,以及不同垃圾回收机制之间的差别,熟悉JVM参数优化。
七、掌握英语,良好的读写能力
英语是计算机的母语,掌握好英语对于阅读英文资料学习新技术大有帮助,我的建议是尽量读英
文原版书,如果是算法方面的可能会困难一点,但是其它像设计模式,软件工程,OO编程思想等尽量读原版,提高自己的英文水平,多多访问开发者,code project,程序员天堂,Pc-magazine等英文IT网站。英语绝对是你必须修炼与提高的技能。此外英语好在外资企业尤其重要,只有外语足够好才可能在外资企业中突破职业瓶颈,向上发展。
八、养成总结的习惯,不断反思
上学的时候老师常让写小结,也没总结出来所以然,以至于工作以后再也不提这档子事情,建议每个项目做完以后对自己都有个小结,总结自己在项目里面学到了什么,反问自己能不能完成在不需要别人帮助的情况下自己完成这样的系统搭建,是否熟悉与掌握项目中所用到的技术,即使有些东西不是你负责完成的但是什么也不能阻挡一颗求知的心,总结要尽量详细记录你遇到那些难题是怎么一个一个的解决的,下次再遇到你是否可以很快解决或者避免这样的问题。有总结才有提高,孔子曰:学而不思则罔,如果我们只是coding到吐血,不思考,不总结提高,永远不可能有能有本质提高,秦相李斯有云:“泰山不让土壤,故能成其大,河海不择细流,故能就其深”,点滴积累不断总结方能量变导致质变。
九、Java代码反编译与代码保护
Java编译产生字节码,因而可以被轻松的逆向工程(反编译),微软的C#生产的DLL也一样可以被轻松反编译。正式由于这个原因产生了许多Java开源的代码保护工具,而Proguard是其中佼佼者,已经被google集成在android之中用于Java代码保护。
十、面向市场,永远对新技术保持渴望
计算机技术的发展日新月异,做为IT行业的软件开发人员要不断的给自己充电,更新自己的技术与时代保持同步,同时还要面向市场,华为总裁任正非说过-“华为的技术革新必须面向市场”,作为程序员同样要有市场意识,很多人都后悔没有在android刚出来的时候加以关注学习。那些很早关注android开发技术的很多程序员因此获得丰厚回报。如今HTML5得到越来越多的浏览器厂家
支持,你是否已经跟上脚步,开始学习。
第四篇:程序员修炼 收藏
程序员修炼 收藏
程序就是一系列按步骤进行的操作序列,它有好多种级别,比如最低级的微程序、次低级的汇编程序、高级的各种编程语言程序、最高级的脚本语言程序,也许我列的不对,但没关系,我要说的是不管是那个级别的程序,其本质都是操作的逻辑序列。大多数系统和应用程序都是建立在高级编程语言上的,比如C、C++、C#、FORTRAN、BISIC、JAVA等等,就让我们只关注这一级的编程能力吧。因此如果一个程序员的逻辑能力不高,他永远都不能成为一名具有合格职业水准的程序员,我们在下面的讨论有关编程能力的方方面面,最终都是为了最大程度地提高和实现一名程序员的逻辑能力。
一、掌握基础知识:十六年寒窗的持续积累
从7岁读小学起,经过16年的学习,你从软件专业本科毕业后,必须完成以下几门专业课程的学习:计算机组成、操作系统原理、汇编语言、数据结构、编译原理、数据库原理、软件工程、结构性设计语言(PASCAL、C)、面向对象设计语言(C++、C#)、计算机网络等,你最好还懂一些算法分析、分布式系统、计算机图形学、形式逻辑、人工智能原理、软件设计模式、软件构架/框架等研究生的课程,16年来,你积累的除了知识,更重要的是形成最适合自己的学习方法和工作方法。这些是你具备程序员职业水准的基础能力,不要受什么计算机软件怪杰之类传奇的影响,那不过是小概率事件,而且这些怪杰们就算没有读过软件本科和研究生,也往往自学了大多数专业课程,很可能比在校学习的学生对这些课程的精髓部分理解的更好,还有他们的工作方法和思维方式是特别而高效的,但普遍性差,可以借鉴,不宜模仿。好,所以现在你只需要问问自己,那些课程和知识都学会并掌握了吗?如果是,那就准备好进行实践了。
二、在实践中提高:成为一名高水平的Coder
好了,你毕业了,在校功课都不错,也找了一个专业对口的工作,你想大展鸿图了,可是别急,你的翅膀还不够硬,不信我们说来看看。
通常,你在工作中都会用到某一种单位/公司固定的操作系统和编程语言开发环境,比如Windows、UNIX、LINUX等操作系统,又比如用VC、VB、PB、Delph、JAVA、Motif/XWindow、QT、OpenGL、OpenInventor等编程语言和开发环境,我们在后面把它们合称为开发环境。就在校学习的有关开发环境的知识而言,大概你距工作需要的差距是不小的,当某个操作系统和编程语言环境成为你的饭碗时,就不应也不能用通过课程/认证考试之类的眼光和要求来评价你的能力,即使你能考100分。你需要深入地学习该操作系统和编程语言环境的各类开发手册的所有内容,你会说大多数你都用不上,其实你既对又不对,对的是单从使用的角度而言,你确实用不上开发手册的大多数内容,比如庞大的VC开发类库和复杂的开发环境,你在实际工作中能用到的不到总数的1/10或1/5,不对的地方在于,你用到的部分不是孤立存在的,它们是整个体系中的一部分,只有对整个体系有了一个较完整的了解,才能得心应手、随心所欲地用好你用到的部分,你才算初步具备在这种开发环境下进行Coding的职业水准(还远不够程序员的职业水准呢),而这只是刚开始。如何才能真正掌握一种开发环境的全面的知识呢,最原始的办法就是读开发指南/教程、参考手册,一般来讲,学习开发指南/教程时,你如果是一个认真的人,都会完成5/10~7/10左右内容的学习和练习,如果你想成为职业选手,就应该完成9/10以上内容的学习和练习。参考手册不同,大多数所谓的“程序员”们只是用到了才翻翻,这差的太远了,你应该象读开发指南/教程一样,每个环节都要读,比如VC,参考手册中的每个类,类的每个函数,都要读上几遍,它们往往是一小伙一小伙地纠缠在一起使用的,开始时读得你毫无头绪、心烦意乱,不要紧,还有一手呢,如果你开发环境安装的全面,它们往往都有开发商做的demo例子可看,你就进入另一个境界了,开始时你关注demo中的具体技术,后来你发现这些demo的程序写的都还算不错,结构简单但合理,如果你真的用心,就一定能发现一些个别的demo是极品,它所展现的程序逻辑结构是你设计不出来的,你现在有点更关心它的程序设计构架,甚于对你原始目的(某种相关的技术/技巧)的关注,这时的你,开始了从一名Coder向一名Programmer的转变,你会忍不住要看看开发商提供的源程序,比如.h和.cpp,通常你会找到include路径下所有的.h程序,你才知道,哇!好多好多东东在参考手册中都没提到,你要学的太多了,没时间顾及其它的业余爱好了,现在知道为什么程序员是年轻人的职业了吧,你要有足够多的时间才行,即使你的智商有160。如果你走到这一步,在你工作的团队中,已经是经常有人向你请教技术问题,经常有人请求你帮忙debug,你已是公认的“高手”了,别得意,因为你仍然是个Coder,为什么这么说呢,你想想,你已深入了解了这个开发环境中的各种技能,知道一名Coder如何用好这些东西,可是你能设计的出提供给Coder们用的东西吗?唔„„,你想了想,可能还不太行。对了,就是这样,你还是一名小我境界的程序员呢,本质是个Coder,当然已是一名高水平的Coder了,然而你需要进一步登堂入室才能成为一名真正的程序员。
让我们继续吧,通常你都是从精通一种编程环境开始的,假设你已经较为精通在Windows下用VC开发软件了,这时在技术和技巧方面你将面临一小一大两个挑战,第一个小挑战是如果公司/单位改换了开发环境,比如用LINUX下的QT交互语言工具进行开发,你不过是把前面掌握VC的过程再来一遍,由于在主观上经历了VC工具的学习过程,在客观上各种开发环境都有太多相似的方面,这回你掌握的应该较快。要小心,在这时第一次诱惑之门打开了,因为你感觉良好,看!这回这么快,我就这么好地掌握了新的开发环境,你开始关注其它暂时还用不到的同类环境,比如VB、Delph、JAVA,如饥似渴地掌握各种开发工具,证明自己的学习能力和价值,但你忘了一点,你仍然是个Coder,只不过是一个在好多开发环境下都能编程的Coder,就像你生活在中国,因而精通了汉语,工作需要你又掌握了英语,然后你就来了劲,把俄语、日语、阿拉伯语、拉丁语,等等等等,都学习个遍,我只能说,有点BT。你忘了自己是个职业人,同一类的东西工作中用得到才需学习,太多太多的Coder们喜欢在一起比较和炫耀自己会掌握了几种开发工具,不信你看看招聘时的求职书就知道了,sigh!他们中绝大多数人永远都只能停留在这个层次上,心浮气躁,一生都再也当不成真正的程序员了。总结一下,其实你在这时需要的是对自己掌握新开发环境的能力的自信,而不是一遍遍地重复来证明自己。第二个大挑战就是你明白了只掌握VC是不够的,你发现自己有点浅薄,有很多东东你会用但你不太懂,很多方面支持VC编程的知识你都没掌握,比如操作系统的源码、网络协议知识、Windows 的注册表、进程和线程的基础知识、硬件驱动方面的知识、ActiveX、Windows 庞大的 API,又是一个等等等等,这些基础知识的学习和掌握可是要花费大量时间的,你再一次深切地感到时间太不够用了,因为这时的你大概有许多俗务缠身了,所以有点沮丧,还不用提IT业每天不知有多少新东西在发布,KAO,永远都跟不上,越拉越远了。哎!别气馁,振作一点,你还是忘记了自己是个职业人,既然好多东东在工作中你永远都没机会用,那么干嘛要学呢?用什么才学什么,最多预测到马上要用什么,先一步学什么好了,要知道没有人是真正的、无所不精的全科大夫,除非你是神,但如果你还在耐着性子看这篇文章,你肯定是个人嘛。
OK,一般工作后三五年,你经历了上述过程,经受了诱惑和考验,终于明白了一个道理:你要的是强劲的学习知识的能力,是对某种软件知识/技能的有深度的精通,一种摸到它的根的深度,而不是已掌握的技能的种类和数量。这时无论谁用他掌握了多少种你不会的技能来吓唬你都没用,你对他的层次只有蔑视。通过几年的学习和工作,要记住最重要的一点,永远最重要:对自己学习IT知识能力的自信,一个程序员一生都要不停地进行高强度的学习,用心问问自己,有没有这个自信?别用虚荣心来骗自己哦,如果没有的话,那就不必花费你宝贵的时间向下看了,作者在此感谢你有耐心看到这里,现在建议你关闭这篇文章,趁着年轻,当机立断转行吧!
三、注重逻辑:成为一名职业程序员
好,再前进一点点,你就要成为一名职业程序员了,让我们继续来完成这个任务吧!我们在前一节提到过,“你发现一些个别的demo是极品,它所展现的程序逻辑结构是你设计不出来的,你现在有点更关心它的程序设计构架,甚于对你原始目的(某种相关的技术/技巧)的关注”,其实你是在关注这个demo程序作者的思维逻辑,所有程序的本质就是逻辑。技术你已经较好地掌握了,但只有完成逻辑能力的提高,你才能成为一名职业程序员。打一个比方吧,你会十八般武艺,刀枪棍棒都很精通,但就是力气不够,所以永远都上不了战场,这个力气对程序员而言就是逻辑能力(其本质是一个人的数学修养,注意,不是数学知识)。逻辑能力也是逐步提高的,开始时你一定是用直观的逻辑能力来编程的,怎么想就怎么编,不对就再改,在改进中提高自己的逻辑能力,从直观逻辑能力提高到抽象逻辑能力,这是很正常的。提前说一句吧,到达逻辑能力的至高境界,其表现是用数学语言来描述问题和问题的解决办法,高度抽象!好,说回来吧,你要提高逻辑能力,最快的办法就是读别人写的结构优秀的程序。优秀的代码是百读不厌的(这句话是我抄来的),暂时放放对其中某种技术和技巧的关注吧,你要推导和学习的是这些好程序的逻辑结构,它们是被精心设计出来的。你可以先捂住这个demo程序,自己设计一个功能相同的程序结构,然后比较一下demo的程序结构,如果差距较大,那你就不应简单地改进一下,而是要把demo作者设计的过程在心里复原一遍,做到这一点也许有点困难,但这种事干的多了,你就会越干越快,越来越得心应手,你的逻辑能力飞速提升,你能看得上的逻辑结构优秀的程序开始不多了,下一步就是练习。从工作中开始吧,如果你有空闲,你需要做至少两类练习,一类是算法练习,所有的经典算法都是经典的逻辑,题目有的是,像个好学生一样吧,每年的国内国际编程竞赛都有逻辑要求非常高的题,你可以只选一两道难题来做做。当你可以把复杂的单递归程序(只有A调A)变成非递归程序时,已经不错了,如果你能看得懂双递归程序(A调A、A调B、B调A、B调B都有),我为你鼓掌!你不必往下看了,我有点不好意思啦――班门弄斧,你快滚蛋吧!另一类是把以前和当前你工作中你不满意的程序推倒重新设计一遍,这非常重要,省时省力,因为你熟悉需求,技术上也没问题,目的就是改进程序的逻辑结构,很划算哦,唯一要克服的就是:你对推翻以前工作中那点小小成就的心理障碍,如果你真想优秀,说句粗话:这点心理障碍算个屁,一遍遍反复地推倒已有的成果只能使自己快速进步,放手干吧,没什么好可惜的,马恩早就在《共.产.党宣言》里说过了:在这个过程中,你失去的只有锁链(禁锢你思想的锁链)。
让我们来总结一下,经过自我否定后,再生的你尽管对过去的“业绩”还有一些眷恋,但已是一个初步具备职业水准的程序员了,掌握了相应的技术和技巧,具备了较高的抽象逻辑思维能力,最主要的特征是:能自觉地自我否定,不断地追求更高水平的逻辑能力。
在这个过程中,如果你能注意以下一些小的方面,你前进的步伐也许会快一些。
l 从编译原理的角度来理解你工作中使用的高级语言,如果你做到这一点,至少有两个好处,第一个好处是避免一大堆低水平重复出现的编译错误。一名优秀的Coder平均在一个工作日中应该完成200行以上的源码,其编译错误应该控制在5个以下,要知道这200行源码不是一次完成的,所以大多数情况下你都要追求一次编译通过,而一名职业水准的程序员,应该进一步做到即使用purify这类的工具来检查源码,也不会存在严重的内存泄露。第二个好处是可以提高源码的可读性和效率。规范地编写你的代码使你自己的逻辑清晰,因为你明白多加几个括号和空行、多换行对齐、多注释,编译器是会自动识别的,不影响程序执行的效率,反过来,控制好递归调用和循环内的if语句才是提高程序效率的关键,要全力避免递归,但要深刻理解递归,能通过自己建立堆栈来把递归程序转换成非递归程序,要求还是较高的哦!
l 避免思维陷阱,只要你是人就一定有自己的思维惯性,这一定又会表现在你的程序逻辑中,有时你就是从这个惯性中跳不出来(谁都有这个时候),但要心里有数才行,所以你需要帮助,如果你有几个水平相若或更高的职业伙伴,太好了,当遇到花30分钟还打不下的bug时,就别浪费时间了,找他们吧,最要紧的是能思路清晰明确地表述你的问题,通常你自己在这个过程中或者伙伴中就有人把问题解决了,又快又好。另外,有几个可以良性竞争的职业伙伴是人生的一件幸事,1+1>2,大家各有所长,你最好做到及时公开你的成果,技不压身嘛,IT发展的这么快,你再优秀,那点东东也没有什么值得隐藏的,所以你可以技术或水平不够高,但千万不可以让真正具有职业水准的选手鄙视你的职业品质和行为。
l 有自己debug的特点,下面的说法作者不敢太肯定,只是经验之谈。即使在VC这种高度完善的开放环境下,你仍然应该要求自己仅凭打印语句就能debug。这也有两点好处,第一个好处是,遇到bug你会认真想问题所在,而不是用debug工具一步步简单地追踪卡在哪儿了,你定位bug范围的方式是从大到小、从粗到精,这是一种自顶向下的思维方式,而用工具追踪,容易形成自底向上的思维方式,这不算好,你应该先看到森林,再看到树木。我反复提及:程序就是逻辑过程,大多数程序从main函数开始,是由数据结构和功能子程序组成的一个树形结构的逻辑过程(要认清即使是面向对象的程序语言也是一样的),它的执行过程是深度优先的,但你定位bug应该是广度优先的,好好想想这一点,嗯?第二个好处是强迫你思考并记住而不是用工具看到调用过程,你大脑的抽象逻辑思维能力和胳膊上肌肉的力量一样,都是练出来的,如果你的bug是程序结构上的逻辑错误引起的,这一点就非常重要了,顺便说一句,最难打的bug就是程序逻辑结构错误导致的bug。你要是真正明明白白地认识到这儿了,那我就没什么东西可以告诉你了。总之,程序员的职业水准:生产效率和程序质量,主要是取决于源码中bug的数量和debug的速度,而不是取决于编写源码的速度。给你一个我自己定义的考查一个职业程序员的指标:一个合格水准的职业程序员,编程的时间如果算一份的话,其累计debug的时间不能超过一份,真正职业高手累计debug的时间应该控制在0.5份以下,如何?你关上门悄悄问问自己,你花费在编程和debug上的时间比例是多少?如果你把程序员作为自己一生的职业,那么就永远都要牢记一点:追求做一个0 bug的优秀程序员!这是任何一个想成为职业程序员的人的理想,请相信:坚忍不拔地追求实现这个理想将让你出类拔萃!
l 做好程序的单元测试,这是另一项考查你是否是一名具有合格职业水准的程序员的一个必要指标。其实在你拿到需求的时候就要准备单元测试用例了,并且这些用例将直接影响你的详细设计(有关软件设计本来是该放在第四节讲的)。我们还是打比方吧,当你拿到一个需求时,除了分析它静态的功能外,还应明确它动态的操作/执行过程,把这个动态过程明确地用流程图画出来,比如分为A~Z的26步,其中A又可以进一步分解为A1~A5的5步,直到不能再分解为止。又比如说A3步不可分解了,那么你应该把A3步的正常操作和所有五花八门的异常操作都列出来,确保正常的操作肯定正确,异常的操作起码程序不退出才行。这样你就要写好多好多的测试用例,说句老实话,我也从来不写!但我一般会列一个提纲,比如A3步有正常的操作a、b、c、d、e共5项,异常的操作有f、g、h、i、j、k、l、m、n共9项,你在进行单元测试时都应该跑一遍,这样的程序都还不敢说质量如何好,但起码可以说较稳定吧!如果要想在进行单元测试时干得快、效率高,那么在进行详细设计时,你就应该把A3步中对所有正常操作和异常操作的判断都设计好,在编程实现A3步时,使得程序的结构合理高效,对不对?所以,如果你在工作中是割裂地看待软件工程中从需求、分析、设计、编程、测试等各个环节,恐怕水平很有限喔!但如果你在分析需求时就能看到测试的问题,并改进设计和实现,为此做好相应的准备工作,嘿嘿,整个软件开发过程你的效率会高很多,通常你在一个开发团队中就会高度自信的,你已越过当一名偏颇、露骨的高手的境界,成为一个平静的高手,这可是The best in the best!,用周星星的话说:是高手之高高手,因为别人看不出你高在哪儿,没见你有什么高招或特拚命干,但反正你就是干得又快又好、又省力。关于进行单元测试还有很多复杂的方法,在此本文只提到了最基本的一点,目的是让你在工作上考虑周全、安排有序,其它的自己琢磨吧,没有人能替你吃饱饭!
l 如果你是用C++编程,我再简单谈谈有关内层释放的一个小技巧,就是对所有你编写的类,在构造和析构函数中加打印语句,统计每个类在运行程序时构造和析构的地方,如果是配对的,那么起码没有对象类一级的内层在程序运行结束时没有释放,然后你就可以把打印语句删掉了,招数虽土,但管用!
l 还有其它一些好习惯,在这里我随笔写一些,你要是有不同看法也请一笑过之吧。编程时应该对齐缩进,一个缩进用一个tab键,一般是4个空格,严格遵守开发团队的编程规范也是非常重要的。一个子程序不应超过30行(不算空行),其内多重循环不应超过3层,否则都应该分裂成两个子程序,个别算法程序可以长一些,但也不宜超过200行。通常一个类的所有成员函数总和不宜超过1500行,多了就应该考虑分解成两个类(这个工作最好在设计时就完成)。每完成一小段程序,比如15~30行,就立即编译运行,不要假装高手,先敲它一大堆程序,再编译运行,妄想一次成功,体验一种假爽的、虚荣的快感,或炫耀给别人看,这么做只能证明自己是一个不折不扣的傻瓜,装酷而已。因为只要有一次不成功,你就会花费大量的时间来调程序,别人的进度在这时就远远地超过你了,平常心是道,还是修炼真功夫吧!孙子兵法里关于这一点有明确的阐述,我就不引用了,但建议你真的不要这么干,除非你确实就是这样总是一次就成功的天才,那你还看这篇文章干什么呢?我又不是写给你们这些天才们看的。再就是有学会买好书、读好书,关于计算机和软件方面的书太多了,时间有限,比如有一个叫侯捷的家伙,几乎写的每本书都不错,张国峰的C++编程也不错,这只是我的个人意见啊,好书多着呢,列出来比这篇文章长好多倍,我就不多说了。还有一招,要是你运气好,能搞到一些著名软件系统的源码,好好读读吧,在此我只能告诉你,Linux操作系统的一些源码不错,是开放的,你可以合法地搞到,其它的不要说是我建议你侵犯知识版权啊!
四、天生神力:成为系统分析员
本来就论述如何成为一名职业程序员而言,本文已基本完成任务了,但《菜根谭》有言:竭世机枢,似一滴投于巨壑,穷诸玄辩,若一毫置于太虚。既已乘兴到此,何妨多置一毫于太虚呢,作者不才,干脆尽兴写算了。
你要是运气好,直接进入了一个严格规范生产的软件企业就业,刚开始就应该是按别人做好的软件设计来实现编程,你可以有机会直接学习软件设计,当你积累的足够多了,能够对其中的一些设计提出好的改进建议,而且干得又快又好,就会渐渐地展露头角,我相信你终有一天成为一名软件设计人员(注意,不是软件产品设计人员),步入系统分析员的行列,但这还需其它的一些条件和自我修炼。如果你在一个不规范的软件企业工作,那也不错,你很可能直接就有机会进行软件设计,然后开发、测试,甚至还不得不自己定义需求,把软件开发过程的各个环节走一个遍,当然这样对你的要求更高,而且你也不容易得到及时有益的指点,在正态分布的情况下,你应该是成长的很慢。但不管就业的单位如何,如果你决心要成为顶尖软件职业选手,通常什么客观困难都阻挡不了你,然而你个人的因素可能会阻止你的前进。下面提出的观点纯属一己之见,伤人自尊之处作者在此提前道歉,并建议你除非对本文有强烈的兴趣,否则就请直接看第五节或放下别看了。丑话已说在前头了,在各种软件开发组织的发展过程中的事实也证明,只有少数程序员能成为系统分析员,我想这一点不是我杜撰的吧,因此你要是在看接下来的部分时感到气愤难当,那也实在没着,纯属活该,因为作者只是在说明自己的观点而已,你最多可以呲之以鼻,表示一下你的轻蔑好了,但没有任何理由可以骂人!
作者自己没有到微软面试过,但身处软件行业,关于微软的许多东东当然还是有耳闻的,据说微软招聘一名程序员要过五个已经成为微软程序员的面试关,而且是一票否决制,又据说大多数面试题并非编程,而是一些有关逻辑和智力的题,作者私下也做过许多流传的微软面试题,并对此做法深以为然。程序的本质就是逻辑,所以几十年前就有人提出编程是一门艺术,而艺术是要靠天份的,这一点少有人反对。一个人的逻辑能力可以不断提高,但其能到达的终极逻辑能力的层次必定为其天生智力所限制,这一点就让人不易接受了。好笑啊!人们可以公开承认自己没有某种或全部的艺术天份,但要说自己逻辑天份不够,换句话说承认自己笨、IQ不够高,往往是要怒发冲冠的,其实这又有什么区别呢?话都说到这儿了,再次建议你如果不够自信,就跳过这一节吧,直接看第五节,好吗?
好了,把话题说回来,你已经成为一门合格的职业程序员了,如果要想成为从事软件系统设计的职业系统分析员,第一件事就是悄悄找一个标准智商测试的网站或其它渠道,严格认真的测一测自己的智商,如果IQ低于130(正常智商是110),就请别费劲了,打消掉成为系统分析员的念头吧!好!好!先请你冷静一下,好好想想,其实微软面试时就是在测你的智商和逻辑数学素质呢,这就是本节的标题为“天生神力”的原因,因为设计就是从无到有地进行创造,无论是软件还是其它行业都一样,可以有借鉴的,没有现成的,设计就是创造!如果你IQ在130以上,又决心要当一名职业软件系统分析员,其实你不过是要准备好吃更大的苦而已,有什么好虚荣的呢?
修炼还是从基本功开始的,过程和成为一名职业程序员差不多。必须使用设计工具这一点是不用多说的。在工作中,你基本上遇到的是两类方式的设计,一个是结构化设计,另一个是面向对象设计,就个人经验而言,面向对象的设计更好。如果你工作中不得不采用结构化的设计,你必须熟练地掌握数据流图和控制流图的分析和设计,一般来讲,如果你把一个软件中用到的数据模型设计好了,针对功能化的流程,不难设计出数据流图,但下一步设计控制流图才是挑战,如果你按照需求走不通设计好的控制流图,那么你或别人在按照这个设计编程实现时,必定也走不通,没有奇迹会发生,还是在设计阶段严格要求吧,又有一点需要牢记:返工是最慢的。当你在进行控制流图的设计时,也不要妄想得到需求人员提供给你明确的指点,通常他们要是能够把需求的功能和操作次序写完整的话,你应该就感恩戴德了,从需求中整理出功能、操作的拓扑次序和条件是你作为系统分析员的职责。看看,要是没有一点图论的基础和拓扑学的入门知识,你是当不好一个职业系统分析员的,即使你天赋不错,必要的数学和逻辑素质仍然不可或缺。也不用气馁,永远没有最好的设计,只有更好的设计,反复地进行设计迭代,勇于推翻旧的设计,你将快速进步。如果你在工作中是采用面向对象进行设计的,那就更有利了,有关面向对象设计的书太多了,不用作者在此多费口舌,建议精读一本经典的书,比如北大邵维忠等编译的《面向对象的分析》,有些方法和技巧可能过时,但其逻辑的基本原理是非常正确的,其本质是,你在逻辑上是如何认识这个世界的,你就是如何设计软件体系结构的,然后读读其它书,触类旁通,自己创造机会多实践,成功自然会到来的,总之,不管是结构化设计还是面向对象设计,评价一下自己的软件系统设计方案吧,有好多指标呢,比如是否均匀和平衡?局部独立性强不强?有没有歧异的结构?有没有层次太多或太少?有没有某个层次太大、太广?是不是逻辑结构先复杂了再化简的?还是只会设计简单的,复杂不起来(这一点是笨哦,如果出现多次,请你不要意气用事,转行吧)?最重要的一点,是否容易理解、实现和改进?你自己会得出评价的。如果有机会看到别人的设计,一定不要错过学习的机会,自己推导一遍,认真比较比较,获益会较多。
走到这一步,你就应该关注设计模式了,首先还是学习,这方面的好书有的是,但一般在工作中用到的设计模式较为单一,应该多尝试一下其它的设计模式。其次必须要明白设计模式不是设计思路,也不能代替设计思路,比方你要从A到B修一条路,设计模式只是让你选择,是修水泥的还是柏油的?是高架路还是普通的,但线路必须你自己定,而线路就是设计思路,模式对思路是有影响,但不能代替,所以如果你的智商高达250,我相信你直接用汇编语言也能写出面向对象的程序来。第三在此有一个陷阱,很多系统分析员生搬硬套设计模式,全然不懂如何融会贯通,在你的一项具体工作中,往往是以一种设计模式为主,其它模式为辅的,思维不拘泥于形式才是关键,而且也为你到达更高的软件设计的境界做好准备。
唉!都不知该怎么向下写好了,因为已达到作者水平的极限了,我胡乱说一点,你凑合看吧。软件设计最终的层次是:以无法为有法、以无限为有限,这句话是李小龙说的,不是我说的。再拾人牙慧一把,类比一个故事吧,金大侠在《倚天屠龙记》里讲到张无忌初学太极,学会的标志是把刚学的招数全忘了,记住的是太极的道理和精神,和李小龙有些相似喔,软件设计也一样,忘记所有的设计模式,随心所欲进行设计才是至高境界,所以你能到达多高的软件设计的境界最终将取决于你的哲学素质,这一点实在是不好写啊,你自己领悟吧!作者只有祝福了!
五、职业人的终极目标:全面修炼,成为Leader
这一节更不好写,涉及到太多其它非技术方面的因素,特别是个人人生观和世界观的修炼,如果本帖的点击率超过作者私下期望的一个数值,那我就争取尽力厚着脸皮再补上吧。我只说一句,虽然大家都知道软件开发是一个团队性的工作,但追求参与一个大型软件系统的成功开发,是一名软件人员的本能,就像拿破仑说的不想当元帅的士兵不是好士兵,所以不追求实现大系统的软件人员,也不是一个好的职业软件人员,但你只有成为Leader,领导一个优秀的软件开发团队,才有机会实现这个终极职业目标,对不对? 好吧,不管你现在的感受如何,我都谢谢你能读到这里!我不习惯假谦虚,就不说什么作者水平有限,本文抛砖引玉,欢迎大家批评斧正之类的客套话了,虽然作者水平确实有限。所以我认为你尽管有权砸砖,但实在没必要搞回帖、或回骂、或顶之类的玩意儿,我只是尽兴写一点多年从事软件开发工作的体验,因此接下来我就高挂免战牌,不回复任何回帖了。再次谢谢你能有耐心读到这里!希望本文对你有所裨益,祝你成功!再见!
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/carefree31441/archive/2008/07/10/2635287.aspx
第五篇:C语言嵌入式系统编程修炼之道
C语言嵌入式系统编程修炼之道收藏
C语言嵌入式系统编程修炼之道——背景篇...1 C语言嵌入式系统编程修炼之道——软件架构篇...4 1.模块划分...4 2.多任务还是单任务...5 3.单任务程序典型架构...6 4.中断服务程序...7 5.硬件驱动模块...9 6.C的面向对象化...10 总结...10 C语言嵌入式系统编程修炼之道——内存操作篇...12 1.数据指针...12 2.函数指针...13 3.数组vs.动态申请...14 4.关键字const 15 5.关键字volatile.16 6.CPU字长与存储器位宽不一致处理...17 总结...18 C语言嵌入式系统编程修炼之道——屏幕操作篇...19 1.汉字处理...19 2.系统时间显示...20 3.动画显示...21 4.菜单操作...22 5.模拟MessageBox函数...24 总结...26 C语言嵌入式系统编程修炼之道——键盘操作篇...27 1.处理功能键...27 2.处理数字键...28 3.整理用户输入...29 总结...30 C语言嵌入式系统编程修炼之道——性能优化篇...31 1.使用宏定义...31 2.使用寄存器变量...31 3.内嵌汇编...32 4.利用硬件特性...32 5.活用位操作...33 总结
C语言嵌入式系统编程修炼之道——背景篇 不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力。无疑,汇编语言具备这样的特质。但是,归因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发的一般选择。而与之相比,C语言——一种“高级的低级”语言,则成为嵌入式系统开发的最佳选择。笔者在嵌入式系统项目的开发过程中,一次又一次感受到C语言的精妙,沉醉于C语言给嵌入式开发带来的便利。本文的目的在于进行“C语言嵌入式系统开发的内功心法”秀,一共包括25招。
图1给出了本文的讨论所基于的硬件平台,实际上,这也是大多数嵌入式系统的硬件平台。它包括两部分:
(1)
以通用处理器为中心的协议处理模块,用于网络控制协议的处理;(2)
以数字信号处理器(DSP)为中心的信号处理模块,用于调制、解调和数/模信号转换。
本文的讨论主要围绕以通用处理器为中心的协议处理模块进行,因为它更多地牵涉到具体的C语言编程技巧。而DSP编程则重点关注具体的数字信号处理算法,主要涉及通信领域的知识,不是本文的讨论重点。
着眼于讨论普遍的嵌入式系统C编程技巧,系统的协议处理模块没有选择特别的CPU,而是选择了众所周知的CPU芯片——80186,每一位学习过《微机原理》的读者都应该对此芯片有一个基本的认识,且对其指令集比较熟悉。80186的字长是16位,可以寻址到的内存空间为1MB,只有实地址模式。C语言编译生成的指针为32位(双字),高16位为段地址,低16位为段内编译,一段最多64KB。
图1 系统硬件架构
协议处理模块中的FLASH和RAM几乎是每个嵌入式系统的必备设备,前者用于存储程序,后者则是程序运行时指令及数据的存放位置。系统所选择的FLASH和RAM的位宽都为16位,与CPU一致。
实时钟芯片可以为系统定时,给出当前的年、月、日及具体时间(小时、分、秒及毫秒),可以设定其经过一段时间即向CPU提出中断或设定报警时间到来时向CPU提出中断(类似闹钟功能)。
NVRAM(非易失去性RAM)具有掉电不丢失数据的特性,可以用于保存系统的设置信息,譬如网络协议参数等。在系统掉电或重新启动后,仍然可以读取先前的设置信息。其位宽为8位,比CPU字长小。文章特意选择一个与CPU字长不一致的存储芯片,为后文中一节的讨论创造条件。
UART则完成CPU并行数据传输与RS-232串行数据传输的转换,它可以在接收到[1~MAX_BUFFER]字节后向CPU提出中断,MAX_BUFFER为UART芯片存储接收到字节的最大缓冲区。
键盘控制器和显示控制器则完成系统人机界面的控制。以上提供的是一个较完备的嵌入式系统硬件架构,实际的系统可能包含更少的外设。之所以选择一个完备的系统,是为了后文更全面的讨论嵌入式系统C语言编程技巧的方方面面,所有设备都会成为后文的分析目标。
嵌入式系统需要良好的软件开发环境的支持,由于嵌入式系统的目标机资源受限,不可能在其上建立庞大、复杂的开发环境,因而其开发环境和目标运行环境相互分离。因此,嵌入式应用软件的开发方式一般是,在宿主机(Host)上建立开发环境,进行应用程序编码和交叉编译,然后宿主机同目标机(Target)建立连接,将应用程序下载到目标机上进行交叉调试,经过调试和优化,最后将应用程序固化到目标机中实际运行。
CAD-UL是适用于x86处理器的嵌入式应用软件开发环境,它运行在Windows操作系统之上,可生成x86处理器的目标代码并通过PC机的COM口(RS-232串口)或以太网口下载到目标机上运行,如图2。其驻留于目标机FLASH存储器中的monitor程序可以监控宿主机Windows调试平台上的用户调试指令,获取CPU寄存器的值及目标机存储空间、I/O空间的内容。图2 交叉开发环境
后续章节将从软件架构、内存操作、屏幕操作、键盘操作、性能优化等多方面阐述C语言嵌入式系统的编程技巧。软件架构是一个宏观概念,与具体硬件的联系不大;内存操作主要涉及系统中的FLASH、RAM和NVRAM芯片;屏幕操作则涉及显示控制器和实时钟;键盘操作主要涉及键盘控制器;性能优化则给出一些具体的减小程序时间、空间消耗的技巧。
本文即将讲述的25个主题可分为两类,一类是编程技巧,有很强的适用性;一类则介绍嵌入式系统编程的一般常识,具有一定的理论意义。So, let’s go.C语言嵌入式系统编程修炼之道——软件架构篇 1.模块划分
模块划分的“划”是规划的意思,意指怎样合理的将一个很大的软件划分为一系列功能独立的部分合作完成系统的需求。C语言作为一种结构化的程序设计语言,在模块的划分上主要依据功能(依功能进行划分在面向对象设计中成为一个错误,牛顿定律遇到了相对论),C语言模块化程序设计需理解如下概念:(1)
模块即是一个.c文件和一个.h文件的结合,头文件(.h)中是对于该模块接口的声明;
(2)
某模块提供给其它模块调用的外部函数及数据需在.h中文件中冠以extern关键字声明;
(3)
模块内的函数和全局变量需在.c文件开头冠以static关键字声明;(4)
永远不要在.h文件中定义变量!定义变量和声明变量的区别在于定义会产生内存分配的操作,是汇编阶段的概念;而声明则只是告诉包含该声明的模块在连接阶段从其它模块寻找外部函数和变量。如: /*module1.h*/ int a = 5;
/* 在模块1的.h文件中定义int a */
/*module1.c*/ #include “module1.h”
/* 在模块1中包含模块1的.h文件 */ /*module2.c*/ #include “module1.h”
/* 在模块2中包含模块1的.h文件 */ /*module3.c*/ #include “module1.h”
/* 在模块3中包含模块1的.h文件 */ 以上程序的结果是在模块1、2、3中都定义了整型变量a,a在不同的模块中对应不同的地址单元,这个世界上从来不需要这样的程序。正确的做法是: /*module1.h*/ extern int a;
/* 在模块1的.h文件中声明int a */ /*module1.c*/ #include “module1.h”
/* 在模块1中包含模块1的.h文件 */ int a = 5;
/* 在模块1的.c文件中定义int a */ /*module2.c*/ #include “module1.h”
/* 在模块2中包含模块1的.h文件 */
/*module3.c*/ #include “module1.h”
/* 在模块3中包含模块1的.h文件 */ 这样如果模块1、2、3操作a的话,对应的是同一片内存单元。一个嵌入式系统通常包括两类模块:
(1)硬件驱动模块,一种特定硬件对应一个模块;
(2)软件功能模块,其模块的划分应满足低偶合、高内聚的要求。2.多任务还是单任务
所谓“单任务系统”是指该系统不能支持多任务并发操作,宏观串行地执行一个任务。而多任务系统则可以宏观并行(微观上可能串行)地“同时”执行多个任务。
多任务的并发执行通常依赖于一个多任务操作系统(OS),多任务OS的核心是系统调度器,它使用任务控制块(TCB)来管理任务调度功能。TCB包括任务的当前状态、优先级、要等待的事件或资源、任务程序码的起始地址、初始堆栈指针等信息。调度器在任务被激活时,要用到这些信息。此外,TCB还被用来存放任务的“上下文”(context)。任务的上下文就是当一个执行中的任务被停止时,所要保存的所有信息。通常,上下文就是计算机当前的状态,也即各个寄存器的内容。当发生任务切换时,当前运行的任务的上下文被存入TCB,并将要被执行的任务的上下文从它的TCB中取出,放入各个寄存器中。嵌入式多任务OS的典型例子有Vxworks、ucLinux等。嵌入式OS并非遥不可及的神坛之物,我们可以用不到1000行代码实现一个针对80186处理器的功能最简单的OS内核,作者正准备进行此项工作,希望能将心得贡献给大家。
究竟选择多任务还是单任务方式,依赖于软件的体系是否庞大。例如,绝大多数手机程序都是多任务的,但也有一些小灵通的协议栈是单任务的,没有操作系统,它们的主程序轮流调用各个软件模块的处理程序,模拟多任务环境。3.单任务程序典型架构
(1)从CPU复位时的指定地址开始执行;(2)跳转至汇编代码startup处执行;
(3)跳转至用户主程序main执行,在main中完成: a.初试化各硬件设备;
b.初始化各软件模块; c.进入死循环(无限循环),调用各模块的处理函数
用户主程序和各模块的处理函数都以C语言完成。用户主程序最后都进入了一个死循环,其首选方案是: while(1){ } 有的程序员这样写: for(;;){ } 这个语法没有确切表达代码的含义,我们从for(;;)看不出什么,只有弄明白for(;;)在C语言中意味着无条件循环才明白其意。下面是几个“著名”的死循环:(1)操作系统是死循环;(2)WIN32程序是死循环;(3)嵌入式系统软件是死循环;
(4)多线程程序的线程处理函数是死循环。你可能会辩驳,大声说:“凡事都不是绝对的,2、3、4都可以不是死循环”。Yes,you are right,但是你得不到鲜花和掌声。实际上,这是一个没有太大意义的牛角尖,因为这个世界从来不需要一个处理完几个消息就喊着要OS杀死它的WIN32程序,不需要一个刚开始RUN就自行了断的嵌入式系统,不需要莫名其妙启动一个做一点事就干掉自己的线程。有时候,过于严谨制造的不是便利而是麻烦。君不见,五层的TCP/IP协议栈超越严谨的ISO/OSI七层协议栈大行其道成为事实上的标准? 经常有网友讨论:
printf(“%d,%d”,++i,i++);
/* 输出是什么?*/ c = a+++b;
/* c=? */ 等类似问题。面对这些问题,我们只能发出由衷的感慨:世界上还有很多有意义的事情等着我们去消化摄入的食物。实际上,嵌入式系统要运行到世界末日。4.中断服务程序
中断是嵌入式系统中重要的组成部分,但是在标准C中不包含中断。许多编译开发商在标准C上增加了对中断的支持,提供新的关键字用于标示中断服务程序(ISR),类似于__interrupt、#program interrupt等。当一个函数被定义为ISR的时候,编译器会自动为该函数增加中断服务程序所需要的中断现场入栈和出栈代码。
中断服务程序需要满足如下要求:(1)不能返回值;
(2)不能向ISR传递参数;
(3)ISR应该尽可能的短小精悍;
(4)printf(char * lpFormatString,„)函数会带来重入和性能问题,不能在ISR中采用。
在某项目的开发中,我们设计了一个队列,在中断服务程序中,只是将中断类型添加入该队列中,在主程序的死循环中不断扫描中断队列是否有中断,有则取出队列中的第一个中断类型,进行相应处理。/* 存放中断的队列 */ typedef struct tagIntQueue { int intType;
/* 中断类型 */ struct tagIntQueue *next;}IntQueue;
IntQueue lpIntQueueHead;
__interrupt ISRexample(){
int intType;
intType = GetSystemType();QueueAddTail(lpIntQueueHead, intType);/* 在队列尾加入新的中断 */ } 在主程序循环中判断是否有中断: While(1){ If(!IsIntQueueEmpty())
{
intType = GetFirstInt();
switch(intType)
/* 是不是很象WIN32程序的消息解析函数? */
{
/* 对,我们的中断类型解析很类似于消息驱动 */
case xxx:
/* 我们称其为“中断驱动”吧? */
…
break;
case xxx:
…
break;
…
} }
} 按上述方法设计的中断服务程序很小,实际的工作都交由主程序执行了。5.硬件驱动模块
一个硬件驱动模块通常应包括如下函数:(1)中断服务程序ISR(2)硬件初始化
a.修改寄存器,设置硬件参数(如UART应设置其波特率,AD/DA设备应设置其采样速率等);
b.将中断服务程序入口地址写入中断向量表: /* 设置中断向量表 */
m_myPtr = make_far_pointer(0l);/* 返回void far型指针void far * */
m_myPtr += ITYPE_UART;/* ITYPE_UART: uart中断服务程序 */ /* 相对于中断向量表首地址的偏移 */
*m_myPtr = &UART _Isr;
/* UART _Isr:UART的中断服务程序 */(3)设置CPU针对该硬件的控制线
a.如果控制线可作PIO(可编程I/O)和控制信号用,则设置CPU内部对应寄存器使其作为控制信号;
b.设置CPU内部的针对该设备的中断屏蔽位,设置中断方式(电平触发还是边缘触发)。
(4)提供一系列针对该设备的操作接口函数。例如,对于LCD,其驱动模块应提供绘制像素、画线、绘制矩阵、显示字符点阵等函数;而对于实时钟,其驱动模块则需提供获取时间、设置时间等函数。6.C的面向对象化
在面向对象的语言里面,出现了类的概念。类是对特定数据的特定操作的集合体。类包含了两个范畴:数据和操作。而C语言中的struct仅仅是数据的集合,我们可以利用函数指针将struct模拟为一个包含数据和操作的“类”。下面的C程序模拟了一个最简单的“类”: #ifndef C_Class
#define C_Class struct #endif C_Class A {
C_Class A *A_this;
/* this指针 */
void(*Foo)(C_Class A *A_this);/* 行为:函数指针 */
int a;
/* 数据 */
int b;};我们可以利用C语言模拟出面向对象的三个特性:封装、继承和多态,但是更多的时候,我们只是需要将数据与行为封装以解决软件结构混乱的问题。C模拟面向对象思想的目的不在于模拟行为本身,而在于解决某些情况下使用C语言编程时程序整体框架结构分散、数据和函数脱节的问题。我们在后续章节会看到这样的例子。总结
本篇介绍了嵌入式系统编程软件架构方面的知识,主要包括模块划分、多任务还是单任务选取、单任务程序典型架构、中断服务程序、硬件驱动模块设计等,从宏观上给出了一个嵌入式系统软件所包含的主要元素。
请记住:软件结构是软件的灵魂!结构混乱的程序面目可憎,调试、测试、维护、升级都极度困难。
一个高尚的程序员应该是写出如艺术作品般程序的程序员。
C语言嵌入式系统编程修炼之道——内存操作篇 1.数据指针
在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++以外的其它编程语言基本没有直接访问绝对地址的能力。在嵌入式系统的实际调试中,多借助C语言指针所具有的对绝对地址单元内容的读写能力。以指针直接操作内存多发生在如下几种情况:
(1)
某I/O芯片被定位在CPU的存储空间而非I/O空间,而且寄存器对应于某特定地址;
(2)
两个CPU之间以双端口RAM通信,CPU需要在双端口RAM的特定单元(称为mail box)书写内容以在对方CPU产生中断;
(3)
读取在ROM或FLASH的特定单元所烧录的汉字和英文字模。譬如:
unsigned char *p =(unsigned char *)0xF000FF00;*p=11;以上程序的意义为在绝对地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)写入11。在使用绝对地址指针时,要注意指针自增自减操作的结果取决于指针指向的数据类别。上例中p++后的结果是p= 0xF000FF01,若p指向int,即: int *p =(int *)0xF000FF00;p++(或++p)的结果等同于:p = p+sizeof(int),而p—(或—p)的结果是p = p-sizeof(int)。同理,若执行:
long int *p =(long int *)0xF000FF00;则p++(或++p)的结果等同于:p = p+sizeof(long int),而p—(或—p)的结果是p = p-sizeof(long int)。
记住:CPU以字节为单位编址,而C语言指针以指向的数据类型长度作自增和自减。理解这一点对于以指针直接操作内存是相当重要的。2.函数指针
首先要理解以下三个问题:
(1)C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针;
(2)调用函数实际上等同于“调转指令+参数传递处理+回归位置入栈”,本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器;(3)因为函数调用的本质是跳转到某一个地址单元的code去执行,所以可以“调用”一个根本就不存在的函数实体,晕?请往下看: 请拿出你可以获得的任何一本大学《微型计算机原理》教材,书中讲到,186 CPU启动后跳转至绝对地址0xFFFF0(对应C语言指针是0xF000FFF0,0xF000为段地址,0xFFF0为段内偏移)执行,请看下面的代码:
typedef void(*lpFunction)();
/* 定义一个无参数、无返回类型的 */ /* 函数指针类型 */ lpFunction lpReset =(lpFunction)0xF000FFF0;
/* 定义一个函数指针,指向*/ /* CPU启动后所执行第一条指令的位置 */ lpReset();
/* 调用函数 */ 在以上的程序中,我们根本没有看到任何一个函数实体,但是我们却执行了这样的函数调用:lpReset(),它实际上起到了“软重启”的作用,跳转到CPU启动后第一条要执行的指令的位置。
记住:函数无它,唯指令集合耳;你可以调用一个没有函数体的函数,本质上只是换一个地址开始执行指令!3.数组vs.动态申请
在嵌入式系统中动态内存申请存在比一般系统编程时更严格的要求,这是因为嵌入式系统的内存空间往往是十分有限的,不经意的内存泄露会很快导致系统的崩溃。
所以一定要保证你的malloc和free成对出现,如果你写出这样的一段程序: char * function(void){
char *p;
p =(char *)malloc(…);
if(p==NULL)„;
„
/* 一系列针对p的操作 */ return p;} 在某处调用function(),用完function中动态申请的内存后将其free,如下: char *q = function();„ free(q);上述代码明显是不合理的,因为违反了malloc和free成对出现的原则,即“谁申请,就由谁释放”原则。不满足这个原则,会导致代码的耦合度增大,因为用户在调用function函数时需要知道其内部细节!
正确的做法是在调用处申请内存,并传入function函数,如下: char *p=malloc(…);if(p==NULL)„;function(p);„ free(p);p=NULL;而函数function则接收参数p,如下: void function(char *p){ „
/* 一系列针对p的操作 */ } 基本上,动态申请内存方式可以用较大的数组替换。对于编程新手,笔者推荐你尽量采用数组!嵌入式系统可以以博大的胸襟接收瑕疵,而无法“海纳”错误。毕竟,以最笨的方式苦练神功的郭靖胜过机智聪明却范政治错误走反革命道路的杨康。
给出原则:
(1)尽可能的选用数组,数组不能越界访问(真理越过一步就是谬误,数组越过界限就光荣地成全了一个混乱的嵌入式系统);
(2)如果使用动态申请,则申请后一定要判断是否申请成功了,并且malloc和free应成对出现!4.关键字const const意味着“只读”。区别如下代码的功能非常重要,也是老生长叹,如果你还不知道它们的区别,而且已经在程序界摸爬滚打多年,那只能说这是一个悲哀: const int a;int const a;const int *a;int * const a;int const * a const;(1)关键字const的作用是为给读你代码的人传达非常有用的信息。例如,在函数的形参前添加const关键字意味着这个参数在函数体内不会被修改,属于“输入参数”。在有多个形参的时候,函数的调用者可以凭借参数前是否有const关键字,清晰的辨别哪些是输入参数,哪些是可能的输出参数。
(2)合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改,这样可以减少bug的出现。const在C++语言中则包含了更丰富的含义,而在C语言中仅意味着:“只能读的普通变量”,可以称其为“不能改变的变量”(这个说法似乎很拗口,但却最准确的表达了C语言中const的本质),在编译阶段需要的常数仍然只能以#define宏定义!故在C语言中如下程序是非法的: const int SIZE = 10;char a[SIZE];/* 非法:编译阶段不能用到变量 */ 5.关键字volatile C语言编译器会对用户书写的代码进行优化,譬如如下代码: int a,b,c;a = inWord(0x100);/*读取I/O空间0x100端口的内容存入a变量*/ b = a;a = inWord(0x100);/*再次读取I/O空间0x100端口的内容存入a变量*/ c = a;很可能被编译器优化为: int a,b,c;a = inWord(0x100);/*读取I/O空间0x100端口的内容存入a变量*/ b = a;c = a;但是这样的优化结果可能导致错误,如果I/O空间0x100端口的内容在执行第一次读操作后被其它程序写入新值,则其实第2次读操作读出的内容与第一次不同,b和c的值应该不同。在变量a的定义前加上volatile关键字可以防止编译器的类似优化,正确的做法是: volatile int a;
volatile变量可能用于如下几种情况:
(1)并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类);(2)一个中断服务子程序中会访问到的非自动变量(也就是全局变量);(3)多线程应用中被几个任务共享的变量。6.CPU字长与存储器位宽不一致处理
在背景篇中提到,本文特意选择了一个与CPU字长不一致的存储芯片,就是为了进行本节的讨论,解决CPU字长与存储器位宽不一致的情况。80186的字长为16,而NVRAM的位宽为8,在这种情况下,我们需要为NVRAM提供读写字节、字的接口,如下: typedef unsigned char BYTE;typedef unsigned int WORD;
/* 函数功能:读NVRAM中字节
* 参数:wOffset,读取位置相对NVRAM基地址的偏移
* 返回:读取到的字节值 */ extern BYTE ReadByteNVRAM(WORD wOffset){
LPBYTE lpAddr =(BYTE*)(NVRAM + wOffset * 2);/* 为什么偏移要×2? */
return *lpAddr;}
/* 函数功能:读NVRAM中字
* 参数:wOffset,读取位置相对NVRAM基地址的偏移
* 返回:读取到的字 */ extern WORD ReadWordNVRAM(WORD wOffset){
WORD wTmp = 0;
LPBYTE lpAddr;
/* 读取高位字节 */
lpAddr =(BYTE*)(NVRAM + wOffset * 2);
/* 为什么偏移要×2? */
wTmp +=(*lpAddr)*256;
/* 读取低位字节 */
lpAddr =(BYTE*)(NVRAM +(wOffset +1)* 2);
/* 为什么偏移要×2? */
wTmp += *lpAddr;
return wTmp;}
/* 函数功能:向NVRAM中写一个字节
*参数:wOffset,写入位置相对NVRAM基地址的偏移 *
byData,欲写入的字节 */ extern void WriteByteNVRAM(WORD wOffset, BYTE byData){
… }
/* 函数功能:向NVRAM中写一个字 */ *参数:wOffset,写入位置相对NVRAM基地址的偏移 *
wData,欲写入的字 */ extern void WriteWordNVRAM(WORD wOffset, WORD wData){
… } 子贡问曰:Why偏移要乘以2? 子曰:请看图1,16位80186与8位NVRAM之间互连只能以地址线A1对其A0,CPU本身的A0与NVRAM不连接。因此,NVRAM的地址只能是偶数地址,故每次以2为单位前进!
图1 CPU与NVRAM地址线连接
子贡再问:So why 80186的地址线A0不与NVRAM的A0连接? 子曰:请看《IT论语》之《微机原理篇》,那里面讲述了关于计算机组成的圣人之道。总结
本篇主要讲述了嵌入式系统C编程中内存操作的相关技巧。掌握并深入理解关于数据指针、函数指针、动态申请内存、const及volatile关键字等的相关知识,是一个优秀的C语言程序设计师的基本要求。当我们已经牢固掌握了上述技巧后,我们就已经学会了C语言的99%,因为C语言最精华的内涵皆在内存操作中体现。
我们之所以在嵌入式系统中使用C语言进行程序设计,99%是因为其强大的内存操作能力!
如果你爱编程,请你爱C语言; 如果你爱C语言,请你爱指针; 如果你爱指针,请你爱指针的指针!
C语言嵌入式系统编程修炼之道——屏幕操作篇 1.汉字处理
现在要解决的问题是,嵌入式系统中经常要使用的并非是完整的汉字库,往往只是需要提供数量有限的汉字供必要的显示功能。例如,一个微波炉的LCD上没有必要提供显示“电子邮件”的功能;一个提供汉字显示功能的空调的LCD上不需要显示一条“短消息”,诸如此类。但是一部手机、小灵通则通常需要包括较完整的汉字库。
如果包括的汉字库较完整,那么,由内码计算出汉字字模在库中的偏移是十分简单的:汉字库是按照区位的顺序排列的,前一个字节为该汉字的区号,后一个字节为该字的位号。每一个区记录94个汉字,位号则为该字在该区中的位置。因此,汉字在汉字库中的具体位置计算公式为:94*(区号-1)+位号-1。减1是因为数组是以0为开始而区号位号是以1为开始的。只需乘上一个汉字字模占用的字节数即可,即:(94*(区号-1)+位号-1)*一个汉字字模占用字节数,以16*16点阵字库为例,计算公式则为:(94*(区号-1)+(位号-1))*32。汉字库中从该位置起的32字节信息记录了该字的字模信息。
对于包含较完整汉字库的系统而言,我们可以以上述规则计算字模的位置。但是如果仅仅是提供少量汉字呢?譬如几十至几百个?最好的做法是: 定义宏:
# define EX_FONT_CHAR(value)
# define EX_FONT_UNICODE_VAL(value)(value), # define EX_FONT_ANSI_VAL(value)(value), 定义结构体:
typedef struct _wide_unicode_font16x16 { WORD value;
/* 内码 */ BYTE data[32];/* 字模点阵 */ }Unicode;#define CHINESE_CHAR_NUM „
/* 汉字数量 */ 字模的存储用数组:
Unicode chinese[CHINESE_CHAR_NUM] = { {
EX_FONT_CHAR(“业”)
EX_FONT_UNICODE_VAL(0x4e1a)
{0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0x44, 0x46, 0x24, 0x4c, 0x24, 0x48, 0x14, 0x50, 0x1c, 0x50,0x14, 0x60, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00}
},{
EX_FONT_CHAR(“中”)
EX_FONT_UNICODE_VAL(0x4e2d)
{0x01, 0x00, 0x01, 0x00, 0x21, 0x08, 0x3f, 0xfc, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08,0x3f, 0xf8, 0x21, 0x08, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00}
},{
EX_FONT_CHAR(“云”)
EX_FONT_UNICODE_VAL(0x4e91)
{0x00, 0x00, 0x00, 0x30, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xff, 0xfe, 0x03, 0x00, 0x07, 0x00,0x06, 0x40, 0x0c, 0x20, 0x18, 0x10, 0x31, 0xf8, 0x7f, 0x0c, 0x20, 0x08, 0x00, 0x00}
},{
EX_FONT_CHAR(“件”)
EX_FONT_UNICODE_VAL(0x4ef6)
{0x10, 0x40, 0x1a, 0x40, 0x13, 0x40, 0x32, 0x40, 0x23, 0xfc, 0x64, 0x40, 0xa4, 0x40, 0x28, 0x40, 0x2f, 0xfe,0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40}
} } 要显示特定汉字的时候,只需要从数组中查找内码与要求汉字内码相同的即可获得字模。如果前面的汉字在数组中以内码大小顺序排列,那么可以以二分查找法更高效的查找到汉字的字模。
这是一种很有效的组织小汉字库的方法,它可以保证程序有很好的结构。2.系统时间显示
从NVRAM中可以读取系统的时间,系统一般借助NVRAM产生的秒中断每秒读取一次当前时间并在LCD上显示。关于时间的显示,有一个效率问题。因为时间有其特殊性,那就是60秒才有一次分钟的变化,60分钟才有一次小时变化,如果我们每次都将读取的时间在屏幕上完全重新刷新一次,则浪费了大量的系统时间。
一个较好的办法是我们在时间显示函数中以静态变量分别存储小时、分钟、秒,只有在其内容发生变化的时候才更新其显示。extern void DisplayTime(…){
static BYTE byHour,byMinute,bySecond;
BYTE byNewHour, byNewMinute, byNewSecond;
byNewHour = GetSysHour();
byNewMinute = GetSysMinute();
byNewSecond = GetSysSecond();
if(byNewHour!= byHour)
{ „
/* 显示小时 */ byHour = byNewHour;}
if(byNewMinute!= byMinute)
{ „
/* 显示分钟 */ byMinute = byNewMinute;}
if(byNewSecond!= bySecond)
{ „
/* 显示秒钟 */ bySecond = byNewSecond;} } 这个例子也可以顺便作为C语言中static关键字强大威力的证明。当然,在C++语言里,static具有了更加强大的威力,它使得某些数据和函数脱离“对象”而成为“类”的一部分,正是它的这一特点,成就了软件的无数优秀设计。3.动画显示
动画是无所谓有,无所谓无的,静止的画面走的路多了,也就成了动画。随着时间的变更,在屏幕上显示不同的静止画面,即是动画之本质。所以,在一个嵌入式系统的LCD上欲显示动画,必须借助定时器。没有硬件或软件定时器的世界是无法想像的:
(1)
没有定时器,一个操作系统将无法进行时间片的轮转,于是无法进行多任务的调度,于是便不再成其为一个多任务操作系统;
(2)
没有定时器,一个多媒体播放软件将无法运作,因为它不知道何时应该切换到下一帧画面;
(3)
没有定时器,一个网络协议将无法运转,因为其无法获知何时包传输超时并重传之,无法在特定的时间完成特定的任务。
因此,没有定时器将意味着没有操作系统、没有网络、没有多媒体,这将是怎样的黑暗?所以,合理并灵活地使用各种定时器,是对一个软件人的最基本需求!在80186为主芯片的嵌入式系统中,我们需要借助硬件定时器的中断来作为软件定时器,在中断发生后变更画面的显示内容。在时间显示“xx:xx”中让冒号交替有无,每次秒中断发生后,需调用ShowDot: void ShowDot(){ static BOOL bShowDot = TRUE;
/* 再一次领略static关键字的威力 */ if(bShowDot)
{ showChar(‘:’,xPos,yPos);} else
{ showChar(‘ ’,xPos,yPos);
} bShowDot =!bShowDot;} 4.菜单操作
无数人为之绞尽脑汁的问题终于出现了,在这一节里,我们将看到,在C语言中哪怕用到一丁点的面向对象思想,软件结构将会有何等的改观!笔者曾经是个笨蛋,被菜单搞晕了,给出这样的一个系统: 图1 菜单范例
要求以键盘上的“←→”键切换菜单焦点,当用户在焦点处于某菜单时,若敲击键盘上的OK、CANCEL键则调用该焦点菜单对应之处理函数。我曾经傻傻地这样做着:
/* 按下OK键 */ void onOkKey(){ /* 判断在什么焦点菜单上按下Ok键,调用相应处理函数 */ Switch(currentFocus){ case MENU1:
menu1OnOk();
break;case MENU2:
menu2OnOk();
break;„ } } /* 按下Cancel键 */ void onCancelKey(){ /* 判断在什么焦点菜单上按下Cancel键,调用相应处理函数 */ Switch(currentFocus){ case MENU1:
menu1OnCancel();
break;case MENU2:
menu2OnCancel();
break;„ } } 终于有一天,我这样做了:
/* 将菜单的属性和操作“封装”在一起 */ typedef struct tagSysMenu
{
char *text;
/* 菜单的文本 */
BYTE xPos;/* 菜单在LCD上的x坐标 */
BYTE yPos;/* 菜单在LCD上的y坐标 */
void(*onOkFun)();
/* 在该菜单上按下ok键的处理函数指针 */
void(*onCancelFun)();/* 在该菜单上按下cancel键的处理函数指针 */ }SysMenu, *LPSysMenu;当我定义菜单时,只需要这样: static SysMenu menu[MENU_NUM] = {
{
“menu1”, 0, 48, menu1OnOk, menu1OnCancel
} ,{
“ menu2”, 7, 48, menu2OnOk, menu2OnCancel
} ,{
“ menu3”, 7, 48, menu3OnOk, menu3OnCancel
} ,{
“ menu4”, 7, 48, menu4OnOk, menu4OnCancel
}
… };OK键和CANCEL键的处理变成: /* 按下OK键 */ void onOkKey(){
menu[currentFocusMenu].onOkFun();
} /* 按下Cancel键 */ void onCancelKey(){ menu[currentFocusMenu].onCancelFun();
} 程序被大大简化了,也开始具有很好的可扩展性!我们仅仅利用了面向对象中的封装思想,就让程序结构清晰,其结果是几乎可以在无需修改程序的情况下在系统中添加更多的菜单,而系统的按键处理函数保持不变。面向对象,真神了!5.模拟MessageBox函数
MessageBox函数,这个Windows编程中的超级猛料,不知道是多少入门者第一次用到的函数。还记得我们第一次在Windows中利用MessageBox输出“Hello,World!”对话框时新奇的感觉吗?无法统计,这个世界上究竟有多少程序员学习Windows编程是从MessageBox(“Hello,World!”,„)开始的。在我本科的学校,广泛流传着一个词汇,叫做“‘Hello,World’级程序员”,意指入门级程序员,但似乎“‘Hello,World’级”这个说法更搞笑而形象。
图2 经典的Hello,World!图2给出了两种永恒经典的Hello,World对话框,一种只具有“确定”,一种则包含“确定”、“取消”。是的,MessageBox的确有,而且也应该有两类!这完全是由特定的应用需求决定的。
嵌入式系统中没有给我们提供MessageBox,但是鉴于其功能强大,我们需要模拟之,一个模拟的MessageBox函数为:
/****************************************** /*
函数名称:
MessageBox /*
功能说明:
弹出式对话框,显示提醒用户的信息 /*
参数说明:
lpStr---提醒用户的字符串输出信息
/*
TYPE---输出格式(ID_OK = 0, ID_OKCANCEL = 1)/*
返回值:
返回对话框接收的键值,只有两种 KEY_OK, KEY_CANCEL /****************************************** typedef enum TYPE
{ ID_OK,ID_OKCANCEL
}MSG_TYPE;extern
BYTE MessageBox(LPBYTE lpStr, BYTE TYPE){
BYTE keyValue =-1;
ClearScreen();
/* 清除屏幕 */
DisplayString(xPos,yPos,lpStr,TRUE);/* 显示字符串 */
/* 根据对话框类型决定是否显示确定、取消 */
switch(TYPE)
{
case
ID_OK:
DisplayString(13,yPos+High+1, “ 确定 ”, 0);
break;
case
ID_OKCANCEL:
DisplayString(8, yPos+High+1, “ 确定 ”, 0);
DisplayString(17,yPos+High+1, “ 取消 ”, 0);
break;
default:
break;
}
DrawRect(0, 0, 239, yPos+High+16+4);/* 绘制外框 */
/* MessageBox是模式对话框,阻塞运行,等待按键 */
while((keyValue!= KEY_OK)||(keyValue!= KEY_CANCEL))
{ keyValue = getSysKey();} /* 返回按键类型 */ if(keyValue== KEY_OK){ return ID_OK;} else { return ID_CANCEL;} } 上述函数与我们平素在VC++等中使用的MessageBox是何等的神似啊?实现这个函数,你会看到它在嵌入式系统中的妙用是无穷的。总结
本篇是本系列文章中技巧性最深的一篇,它提供了嵌入式系统屏幕显示方面一些很巧妙的处理方法,灵活使用它们,我们将不再被LCD上凌乱不堪的显示内容所困扰。
屏幕乃嵌入式系统生存之重要辅助,面目可憎之显示将另用户逃之夭夭。屏幕编程若处理不好,将是软件中最不系统、最混乱的部分,笔者曾深受其害。
C语言嵌入式系统编程修炼之道——键盘操作篇 1.处理功能键
功能键的问题在于,用户界面并非固定的,用户功能键的选择将使屏幕画面处于不同的显示状态下。例如,主画面如图1: 图1 主画面
当用户在设置XX上按下Enter键之后,画面就切换到了设置XX的界面,如图2:
图2 切换到设置XX画面
程序如何判断用户处于哪一画面,并在该画面的程序状态下调用对应的功能键处理函数,而且保证良好的结构,是一个值得思考的问题。
让我们来看看WIN32编程中用到的“窗口”概念,当消息(message)被发送给不同窗口的时候,该窗口的消息处理函数(是一个callback函数)最终被调用,而在该窗口的消息处理函数中,又根据消息的类型调用了该窗口中的对应处理函数。通过这种方式,WIN32有效的组织了不同的窗口,并处理不同窗口情况下的消息。
我们从中学习到的就是:
(1)将不同的画面类比为WIN32中不同的窗口,将窗口中的各种元素(菜单、按钮等)包含在窗口之中;
(2)给各个画面提供一个功能键“消息”处理函数,该函数接收按键信息为参数;
(3)在各画面的功能键“消息”处理函数中,判断按键类型和当前焦点元素,并调用对应元素的按键处理函数。
/* 将窗口元素、消息处理函数封装在窗口中 */ struct windows {
BYTE currentFocus;
ELEMENT element[ELEMENT_NUM];
void(*messageFun)(BYTE keyValue);
… };/* 消息处理函数 */ void messageFunction(BYTE keyValue){
BYTE i = 0;
/* 获得焦点元素 */
while((element [i].ID!= currentFocus)&&(i < ELEMENT_NUM))
{
i++;
}
/* “消息映射” */
if(i < ELEMENT_NUM)
{
switch(keyValue)
{
case OK:
element[i].OnOk();
break;
…
}
} } 在窗口的消息处理函数中调用相应元素按键函数的过程类似于“消息映射”,这是我们从WIN32编程中学习到的。编程到了一个境界,很多东西都是相通的了。其它地方的思想可以拿过来为我所用,是为编程中的“拿来主义”。
在这个例子中,如果我们还想玩得更大一点,我们可以借鉴MFC中处理MESSAGE_MAP的方法,我们也可以学习MFC定义几个精妙的宏来实现“消息映射”。2.处理数字键
用户输入数字时是一位一位输入的,每一位的输入都对应着屏幕上的一个显示位置(x坐标,y坐标)。此外,程序还需要记录该位置输入的值,所以有效组织用户数字输入的最佳方式是定义一个结构体,将坐标和数值捆绑在一起: /* 用户数字输入结构体 */ typedef struct tagInputNum
{
BYTE byNum;/* 接收用户输入赋值 */
BYTE xPos;
/* 数字输入在屏幕上的显示位置x坐标 */
BYTE yPos;
/* 数字输入在屏幕上的显示位置y坐标 */
}InputNum, *LPInputNum;那么接收用户输入就可以定义一个结构体数组,用数组中的各位组成一个完整的数字:
InputNum inputElement[NUM_LENGTH];/* 接收用户数字输入的数组 */ /* 数字按键处理函数 */ extern void onNumKey(BYTE num){
if(num==0|| num==1)/* 只接收二进制输入 */
{ /* 在屏幕上显示用户输入 */ DrawText(inputElement[currentElementInputPlace].xPos, inputElement[currentElementInputPlace].yPos, “%1d”, num);
/* 将输入赋值给数组元素 */
inputElement[currentElementInputPlace].byNum = num;
/* 焦点及光标右移 */
moveToRight();
} } 将数字每一位输入的坐标和输入值捆绑后,在数字键处理函数中就可以较有结构的组织程序,使程序显得很紧凑。3.整理用户输入
继续第2节的例子,在第2节的onNumKey函数中,只是获取了数字的每一位,因而我们需要将其转化为有效数据,譬如要转化为有效的XXX数据,其方法是:
/* 从2进制数据位转化为有效数据:XXX */ void convertToXXX(){
BYTE i;
XXX = 0;
for(i = 0;i < NUM_LENGTH;i++)
{
XXX += inputElement[i].byNum*power(2, NUM_LENGTH1);
}
} 反之,我们也可能需要在屏幕上显示那些有效的数据位,因为我们也需要能够反向转化:
/* 从有效数据转化为2进制数据位:XXX */ void convertFromXXX(){
BYTE i;
XXX = 0;
for(i = 0;i < NUM_LENGTH;i++)
{
inputElement[i].byNum = XXX / power(2, NUM_LENGTH1)% 2;
}
} 当然在上面的例子中,因为数据是2进制的,用power函数不是很好的选择,直接用“<< >>”移位操作效率更高,我们仅是为了说明问题的方便。试想,如果用户输入是十进制的,power函数或许是唯一的选择了。总结
本篇给出了键盘操作所涉及的各个方面:功能键处理、数字键处理及用户输入整理,基本上提供了一个全套的按键处理方案。对于功能键处理方法,将LCD屏幕与Windows窗口进行类比,提出了较新颖地解决屏幕、键盘繁杂交互问题的方案。
计算机学的许多知识都具有相通性,因而,不断追赶时髦技术而忽略基本功的做法是徒劳无意的。我们最多需要“精通”三种语言(精通,一个在如今的求职简历里泛滥成灾的词语),最佳拍档是汇编、C、C++(或JAVA),很显然,如果你“精通”了这三种语言,其它语言你应该是可以很快“熟悉”的,否则你就没有“精通”它们。
C语言嵌入式系统编程修炼之道——性能优化篇 1.使用宏定义
在C语言中,宏是产生内嵌代码的唯一方法。对于嵌入式系统而言,为了能达到性能要求,宏是一种很好的代替函数的方法。
写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个:
错误做法:
#define MIN(A,B)(A <= B ? A : B)正确做法:
#define MIN(A,B)((A)<=(B)?(A):(B))对于宏,我们需要知道三点:(1)宏定义“像”函数;
(2)宏定义不是函数,因而需要括上所有“参数”;(3)宏定义可能产生副作用。下面的代码:
least = MIN(*p++, b);将被替换为:
((*p++)<=(b)?(*p++):(b))发生的事情无法预料。
因而不要给宏定义传入有副作用的“参数”。2.使用寄存器变量
当对一个变量频繁被读写时,需要反复访问内存,从而花费大量的存取时间。为此,C语言提供了一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写,从而提高效率。寄存器变量的说明符是register。对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量,而循环计数是应用寄存器变量的最好候选者。(1)
只有局部自动变量和形参才可以定义为寄存器变量。因为寄存器变量属于动态存储方式,凡需要采用静态存储方式的量都不能定义为寄存器变量,包括:模块间全局变量、模块内全局变量、局部static变量;
(2)
register是一个“建议”型关键字,意指程序建议该变量放在寄存器中,但最终该变量可能因为条件不满足并未成为寄存器变量,而是被放在了存储器中,但编译器中并不报错(在C++语言中有另一个“建议”型关键字:inline)。
下面是一个采用寄存器变量的例子: /* 求1+2+3+„.+n的值 */ WORD Addition(BYTE n){ register i,s=0;for(i=1;i<=n;i++){ s=s+i;} return s;} 本程序循环n次,i和s都被频繁使用,因此可定义为寄存器变量。3.内嵌汇编
程序中对时间要求苛刻的部分可以用内嵌汇编来重写,以带来速度上的显著提高。但是,开发和测试汇编代码是一件辛苦的工作,它将花费更长的时间,因而要慎重选择要用汇编的部分。
在程序中,存在一个80-20原则,即20%的程序消耗了80%的运行时间,因而我们要改进效率,最主要是考虑改进那20%的代码。
嵌入式C程序中主要使用在线汇编,即在C程序中直接插入_asm{ }内嵌汇编语句:
/* 把两个输入参数的值相加,结果存放到另外一个全局变量中 */ int result;
void Add(long a, long *b)
{
_asm
{
MOV
AX, a
MOV
BX, b
ADD
AX, [BX]
MOV
result, AX
}
}
4.利用硬件特性
首先要明白CPU对各种存储器的访问速度,基本上是:
CPU内部RAM > 外部同步RAM > 外部异步RAM > FLASH/ROM 对于程序代码,已经被烧录在FLASH或ROM中,我们可以让CPU直接从其中读取代码执行,但通常这不是一个好办法,我们最好在系统启动后将FLASH或ROM中的目标代码拷贝入RAM中后再执行以提高取指令速度; 对于UART等设备,其内部有一定容量的接收BUFFER,我们应尽量在BUFFER被占满后再向CPU提出中断。例如计算机终端在向目标机通过RS-232传递数据时,不宜设置UART只接收到一个BYTE就向CPU提中断,从而无谓浪费中断处理时间;
如果对某设备能采取DMA方式读取,就采用DMA读取,DMA读取方式在读取目标中包含的存储信息较大时效率较高,其数据传输的基本单位是块,而所传输的数据是从设备直接送入内存的(或者相反)。DMA方式较之中断驱动方式,减少了CPU 对外设的干预,进一步提高了CPU与外设的并行操作程度。5.活用位操作
使用C语言的位操作可以减少除法和取模的运算。在计算机程序中数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作,因而,灵活的位操作可以有效地提高程序运行的效率。举例如下: /* 方法1 */ int i,j;i = 879 / 16;j = 562 % 32;
/* 方法2 */ int i,j;i = 879 >> 4;j = 562-(562 >> 5 << 5);对于以2的指数次方为“*”、“/”或“%”因子的数学运算,转化为移位运算“<< >>”通常可以提高算法效率。因为乘除运算指令周期通常比移位运算大。
C语言位运算除了可以提高运算效率外,在嵌入式系统的编程中,它的另一个最典型的应用,而且十分广泛地正在被使用着的是位间的与(&)、或(|)、非(~)操作,这跟嵌入式系统的编程特点有很大关系。我们通常要对硬件寄存器进行位设置,譬如,我们通过将AM186ER型80186处理器的中断屏蔽控制寄存器的第低6位设置为0(开中断2),最通用的做法是: #define INT_I2_MASK
0x0040
wTemp = inword(INT_MASK);outword(INT_MASK, wTemp &~INT_I2_MASK);而将该位设置为1的做法是:
#define INT_I2_MASK
0x0040
wTemp = inword(INT_MASK);outword(INT_MASK, wTemp | INT_I2_MASK);判断该位是否为1的做法是:
#define INT_I2_MASK
0x0040
wTemp = inword(INT_MASK);if(wTemp & INT_I2_MASK){
„
/* 该位为1 */ } 上述方法在嵌入式系统的编程中是非常常见的,我们需要牢固掌握。总结
在性能优化方面永远注意80-20准备,不要优化程序中开销不大的那80%,这是劳而无功的。
宏定义是C语言中实现类似函数功能而又不具函数调用和返回开销的较好方法,但宏在本质上不是函数,因而要防止宏展开后出现不可预料的结果,对宏的定义和使用要慎而处之。很遗憾,标准C至今没有包括C++中inline函数的功能,inline函数兼具无调用开销和安全的优点。
使用寄存器变量、内嵌汇编和活用位操作也是提高程序效率的有效方法。除了编程上的技巧外,为提高系统的运行效率,我们通常也需要最大可能地利用各种硬件设备自身的特点来减小其运转开销,例如减小中断次数、利用DMA传输方式等。