文件组织结构
方案一: 一个模块.c配一个模块.h 模块.c中调用模块.h 模块.h中定义模块用到的宏,extern声明用到的外部变量和外部函数 模块.c模块化,申请用到的变量,定义相关的函数 用一个common.h提取公共的h 特点:明晰,模块化 方案二: 模块.c对应模块.h,但模块.c中不调用模块.h而调用各个模块.h的最会文件define.h 变量,可以单独弄个c文件申请,单独弄个h文件声明,然后汇入总文件define.h中;比较通用和符合规范,但破坏了模块化 变量,也可以模块.c中定义,然后弄个独立h文件声明供调用,有冲突部分 变量,或者直接在相应的模块.h中声明一下,有冲突 函数,不extern,直接在模块.h中提供接口 特点:相对省事,会有冲突 关于中断的组织: 有的地方支持中断分散开,这样可分布到不同的模块中,比如AVR中IAR环境下用ISR() 但是像PIC中等器件只有一个中断入口,只能用一个中断函数 完全模块化分开interrupt不通用,像PICC中不支持多个interrupt的形式,只能一个interrupt然后if判断;IAR中用过ISR()定义过多个 multiple interrupt functions (_is and _isr) defined for device with only one interrupt vector Mid-Range PIC devices have many sources of interrupt, but only one interrupt vector, and hence should only have one interrupt function defined. 文件组织结构 1).每个模块都对应自己的头文件,声明自己用到的宏定义和对外的函数接口,然后将该头文件加入汇总文件中 2).对于变量,使用到的全局变量在自己模块函数内定义,并且到总声明文件中做声明;或用一个总的文件定义外部变量,然后一个总的声明文件? –全局变量的组织形式 a 一个C中专门盛放外部变量,对应一个h文件供统一声明调用 -规范 b 每个模块中就近定义外部变量,对应一个统一h文件声明-不怎么规范 c 每个模块中就近定义外部变量,在某个模块的头文件中用到时专门声明-规范 目前使用的b,既占用了模块化的优点,又占用了省事的优点 程序用到什么函数到什么模块中去查找,用到什么函数extern 声明一下 还是对外的接口比较好
程序架构
事件与状态:状态切换和状态呈现分层
一种思路是在操作时直接执行动作,另一种思路时操作只改变系统的状态标志,然后在定时器等地方实现状态程序,对于状态复杂的系统,适合采用后者 我的习惯处理是:状态的进入,状态的切换 通过某个键或者组合键或者长按操作进入某种状态 按某个键递增,长按快速递增 按某个键递减,长按快速递减 每个状态按取消则返回上一步,按确认则进入下一步;如果长时间没有操作则定时取消 状态的切换采用一个状态变量标志和控制,每个状态对应一个固定的值,可采用宏替换更容易标识和理解
循环和中断
初始化 主循环 外部中断 一个情况:主循环和中断共同调用函数 解决办法:还是在中断中弄个标志,出来在循环中处理 曾经遇到一个问题,一个commu_process函数在中断里面是可以生效的,但是放到主循环里面没有生效,似乎没有进入主循环,不应该的呀 后来通过debug模式进行调试,发现循环是进入了,但是又被中断中途打断了,原来是启用了其他定时器但是没有在中断里处理清除标志,导致总是在中断里跑 经验:没用的中断不要启用,启用的中断就要在中断处理里面清除标志,才能返回主循环处理其他事情
循环中时间片
这个思想非常重要,是一个重要经验 每片不占太多时间,不要在一处太多时间,分成状态机,分case,让每次只处理一小段,保障整个循环能流程循环,及时照顾到各个任务 循环中不要用一段程序中占用太多时间的情况,要分成case 状态循环,保障整个循环的畅通! 不仅状态用状态机 事件也分片段,每次处理一个片段
定时 通讯等中断
执行一些需要及时改变状态的任务,不执行大块内容
复杂多任务系统
多任务系统 定时器中控制任务调度变量 比如数 码管动态扫描,能够做到40Hz 就可以了,又如键盘扫描,能够做到20Hz(经验值),基本上 也就不会丢有效按键键值了,再如LCD刷新,我觉得做到 10Hz 就可以了 每一个子任务就必须简单,每次“关照”时间最好不要超过定时中断节拍周期(5ms 或 10ms,初学者 要对 ms 有一个概念,机器周期为 us 级的单片机,1ms 可以执行上千条指令,对于像数码管 扫描,键盘扫描,LCD显示等常规任务都是绰绰有余的,只是遇到大型计算,数据排序就显 得短了) 关于任务优先级的问题:一个复杂系统,多个任务之间总有“轻重缓急”之区别,那些 需要严格实时的任务通常用中断实现,中断能够保证第一时间相应 关于“长”任务的问题 怎么设计任务体解决“长”的问题。进一步研究这些器件发 现,真正需要 CPU“关照”它们的时间并不长,关键是等待结果要很长时间。解决的办法就 是把类似的器件驱动分成多个段:初始化段、启动段、读结果段,而在需要花长时间等待时 间段,不要 CPU关照,允许 CPU去关照其它任务 主程序巧妙实现优先级设定: for(i=0;i<MAX_TASK;i++) if (task_delay[i]==0) {run(task[i]); break;} //就绪任务调度 这里的 break 将跳出 for 循环,使得每次重新任务调度总是从 task0 开始,就意味着优先 级高的任务就绪会先执行。这样task0具有最高优先级,task1、task2、task3优先级依次降低
命名习惯
所有端口类宏都以下划线开头 变量替换宏用大写,配合下划线 变量用小写 函数用小写配合下划线 宏中 类的观念 POS_定义位置的 包括数组中元素位置和变量中位的位置 VARPOS_ BITPOS_ OBJ_是调用对象的 ATTRI_属性 DIR_表示方向 OPOBJ_操作对象 或OPADDR_ 要大写都大写,要小写都小写,不大小写混合使用,麻烦
驱动层到系统层:先做通驱动,然后调系统状态
系统分层便于多进程系统的处理 不分层的系统比较直接,直接由输入到输出(如直接由按键控制LED),分层处理则输入影响到系统变量,然后系统输出依据中间的变量 底层驱动模块做好 上层系统管理 定时扫描按键 按键进行状态的切换 任务管理 可采用按照状态作为顶层类,是面向过程的思路;也可按照对象作为大类,对对象不同状态进行处理,是面向对象的思路;流程图辅助 一 底层驱动编写 1.指示和驱动: led,beep 循环中 定时器中 2.按键输入: 定时器 LCD: 3.通讯: 二 上层系统完善 状态切换 从底层到上层系统,程序是一个工程,需要一点点发现和完善 程序分层 底层为最底层,做驱动 第二层管输入,可支持多种输入都影响到一套变量 第三层,对系统状态和事件进行处理
程序优化
合并 精简 一些warning也要去掉 可能是这些导致了错误 提取公共部分 采用优化的编译器 删除没有用的程序 Error [1347] ; 0. can’t find 0x0 words (0x0 withtotal) for psect “strings” in segment “CODE” (largest unused contiguous range 0x7D) ********** Build failed! ************** 屏蔽一点程序就可以了 选择编译器很重要,Lite版本基本不能用,编译80%多的换成pro编译成40%,lite版本程序空间不够用的换pro编译成50%多 编译报错,好像是空间不够?? 主程序的代码过大引起的。PIC中的代码的大小是512bytes所以尽量要把模块细化。 C语言优化 使用宏而不是函数 以空间换时间 对于以2的指数次方为”*“、”/“或”%”因子的数学运算,转化为移位运算”<< >>”通常可以提高算法效率。因为乘除运算指令周期通常比移位运算大。 在空间紧张的时候就突出程序压缩优化的必要性了 删除不用的程序 有的能合并的程序合并
其他
注释
多加些注释方便自己以后浏览时快速恢复记忆,也方便别的阅读时理解 尽量考虑用移植性强的方法 要想办法增加程序灵活性,比如添加模块开关,方便不同情况下方便移植
增加程序移植性的一些方法
1.文件组织结构 编程规范化声明,为了规范化,便于移植和相互交流 0).按照功能设立不同的模块 1).每个模块都对应自己的头文件,声明自己用到的宏定义和对外的函数接口,然后将该头文件加入汇总文件中 2).对于变量,使用到的全局变量在自己模块函数内定义,并且到总声明文件中做声明 2.模块设立开关 支持在不同状态下启用和禁用 方便灵活扩展 3.有时设立备用占位 如果是潜在可能的但是目前没有用到,可以设立占位 方便扩展 4.多用表意宏替换 //宏定义n=0/1增强移植性 #define UCSZn2 UCSZ02 #define UCSZn1 UCSZ01 #define UCSZn0 UCSZ00 #define UCSRnB UCSR0B #define UCSRnC UCSR0C #define USBSn USBS #define UPMn1 UPM1 #define UPMn0 UPM0 #define U2Xn U2X0 #define RXENn RXEN0 #define RXCIEn RXCIE0 #define UCSRnA UCSR0A #define UDRn UDR0 #define UBRRn UBRR #define TXENn TXEN0 #define UDREn UDRE0 #define TXCIEn TXCIE0 #define UDRIEn UDRIE0 #define TXCn TXC0 #define RXCn RXC0 #define setbit(byte, n) (byte =(1<<n)) #define setbits2(byte,m,n) (byte =(1<<m)(1<<n)) //(byte =(1<<m)(1<<n)) 则是直接赋值把其他位屏蔽掉了 #define setbits3(byte,x,y,z) (byte =(1<<x)(1<<y)(1<<z)) //one time op #define setbits4(byte,a,b,c,d) (byte =(1<<a)(1<<b)(1<<c)(1<<d)) #define clrbit(byte, n) (byte &=(1<<n)) #define clrbits2(byte,m,n) (byte &=((1<<m))&(~(1<<n))) #define getbit(PINX, n) (PINX &(1<<n) ) //实际得到的可能不直接是1,但已反映1
语言和算法片段
if VS switch
在互斥事件中采用if或许会出现判断前面之后再判断后面从而出错,switch本身具有互斥性,可以避免 CASE的可break跳出 if的后面的还可以顺序执行 else if如果级别太多,用case 比较好
无动作自动返回的实现
有按键就喂一下,赋予一个大值,不喂养就到头自动返回 背光可以弄一块,是一样的 在按键单击 长按等地方喂养autoreturn,定时器中递减,主循环中检查如果等于0则返回系统initial状态
捕捉变化
历史记录
延时开关
初始状态记录 变化记录一个 期望状态 如果从初始状态quasi=0变动过来则 记录quasi =1 并且定时器赋值 重复期望状态 如果是quasi=1 并且定时器到头 则执行动作
设立模块使能开关
方便多种情况下控制 配置模块使能,方便根据需要启用或者禁用模块
一定上限的计数器
cnt++;if(cnt>1000) cnt=1000; 没有下面的好: if(cnt<1000) cnt++;else {}
不用条件判断的取反操作
不用条件判断的 bit=!bit unchar=(unchar+1)%2
define和typedef
- #define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查,不关含义是否正确照样带入,只有在编译已被展开的源程序时才会发现可能的错误并报错。例如: #define PI 3.1415926 程序中的:area=PI*r*r 会替换为3.1415926*r*r 如果你把#define语句中的数字9 写成字母g 预处理也照样带入。 2)typedef是在编译时处理的。它在自己的作用域内给一个已经存在的类型一个别名,但是You cannot use the typedef specifier inside a function definition。
乘法处理
#include<16c71.h> #include<math.h> unsigned int a, b; unsigned long c; void main() { a=200; b=2; c=a*b; } /*得不到正确的结果c=400*/ 原因是Mplab-C以8×8乘法方式来编译c=a*b,返回单字节结果给c,结果的溢出被忽略。改上例中的“c=a*b;”表达式为“c=a;c=c*b;”,最为安全(对加法的处理同上)。
剥离运算
取出16位的高八位 第八位 移位运算 或者配合逻辑运算 商余运算
全角符号
error: stray ‘\161’ in program 今天有发现了这个错误,终于有点明白了,如果代码中含有全角标点的话,就会这样
死循环
典型死循环 for(;;);
延时
一种是硬延时,一种是在节拍中延时
养成变量赋初值的习惯
出现一些错误找不到原因,将变量赋初值就好了 已经有两三回了,遇到一些莫名奇妙的问题,这些问题用常规的分析很难分析出来 不如这个变量不可以,类似的另一个变量则可以 后来发现是赋初值问题,正常的是申请变量时候就赋值了,另一个是后来才赋值 就这都不一样,妈的,怎么个规律 反正养成赋值的习惯得了
感悟
在实例中演绎,每一个实际项目都是一块基石 建立自己的程序库,后面很多都是在原来基础上抄抄改改
操作寄存器或者用库函数
经常操作寄存器,能够对底层理解比较深,如果总是调用库函数而不问所以然,则理解不深入;建议在理解操作寄存器的基础上,自己封装成函数使用
利用库函数
提供商一方往往提供很多库函数,便于加速开发过程
常出现错误、警告注意
防止数组越界,数组越界就导致CPU异常,要先界定 如果声明是有返回值而无返回,比如int main,会warning: control reaches end of non-void function,可加上return 0; 使用但未声明,warning: implicit declaration of function … 警告:变量定义但未使用
注意容易进入异常的情况
数组越界,所以养成数组限定数组序号大小的习惯 除数是0,出现除数是0的情况也要进入异常
变量
20110820 以前结构体用的少,现在觉得是时候让结构体像常用的if for switch 数组等一样成为常用习惯了,理解一下,结构体,就是把一组变量组成一个类而已。而和枚举结合使用,我觉得是把整个结构体成员捆绑成一组 大量变量更改成结构体元素形式,便于归类,同时定义也方便,一个地方定时即可,不用多而杂 使用方法: C中:struct DIMMER mydimmer; h中 struct MYMP3 { unsigned char buf_read_from_sd[MAX_BYTES_READ_ONCE_FROM_SD]; //[2][] unsigned char current_buf; unsigned char buf32[32]; DWORD file_size; unsigned char file_is_end; DWORD total_read_times_from_sd; //按每次读取的量,需要读取多少次 DWORD current_read_time_from_sd; //当前读取了多少次MAX_BYTES_READ_ONCE_FROM_SD unsigned int left_bytes_of_the_final_sd_read; //最后一次读sd的数目,以及播放最后一次的数目 unsigned long current_byte_number_in_buf; //必须很大 unsigned char attri; unsigned char file_is_found; unsigned long play_state; unsigned char play_or_stop; unsigned char lock_me; unsigned char change_dir; unsigned char volume; unsigned char total_file_cnt_to_play; unsigned char current_file_no_in_play; //当前播放文件序号 unsigned char current_page; unsigned char read_from_sd_step; unsigned char new_play_step2_step; unsigned char file_being_open; unsigned char play_state_last; }; extern struct MYMP3 mymp3; 以上结构体包含了一系列数组,另外还可以: /* struct DIMMER { unsigned char delay2turnon_xof200; unsigned char delay2turnon_xof200_set; unsigned char slowly_change_direction; unsigned char slowly_change_xof200_from; unsigned char slowly_change_xof200_to; }mydimmer[10]; */
获取帮助
数据手册 示例应用 开发环境自带的示例 网上资料 利用现有比葫芦 先了解一些理论知识,增加分析判断能力
流程图
逻辑复杂的,理不清楚头绪的,先画流程图再写程序
杂烩
自己编程不够细心,一段程序中往往有很多小错误,一遍遍调试才发现,对于反复烧录到芯片中的情况不太适合
编写完程序要检查一遍再说
有时候与其阅读和修改别人的程序,还不如自己重新编写来得省事 阅读别人的程序:绘制流程图 积累程序片段非常重要 else if如果级别太多,用case 比较好 复杂编程,要先理清思路,然后再编写才行 做输出时,最好使用LATD形式,正常情况下PORTD和LATD状态保持一致,除非端口处有实际异常;做输入时候,LATD和PORTD未必一致