目录:
一、如何从汇编转向PICC
二、浅谈PICC 的位操作
三、PICC 之延时函数和循环体优化
五、在PICC 中使用常数指针
六、PICC 关于unsigned 和 signed 的几个关键问题
七、用PICC 写高效的位移操作
八、C 程序优化
九、关于PIC的C语言中嵌入汇编语言
十、PICC中实现C语言与汇编语言混合编程
----------------------------------------------------------------------------------------------------------------
一、如何从汇编转向PICC
首先要求你要有C 语言的基础。C代码的头文件一定要有#include,它是很多头文件的集合,C 编译器在pic.h 中根据你的芯片自动载入相应的其它头文件。这点比汇编好用。载入的头文件中其实是声明芯片的寄存器和一些函数。顺便摘抄一个片段:
static volatile unsigned char TMR0 @ 0x01;
static volatile unsigned char PCL @ 0x02;
static volatile unsigned char STATUS @ 0x03;
可以看出和汇编的头文件中定义寄存器是差不多的。如下:
TMR0 EQU 0X01;
PCL EQU 0X02;
STATUS EQU 0X03;
都是把无聊的地址定义为大家公认的名字。
1、如何赋值?
如对TMR0 附值,汇编中:
MOVLW 200;
MOVWF TMR0;
当然得保证当前页面在0,不然会出错。
C 语言:
TMR0=200;//无论在任何页面都不会出错。
可以看出来C 是很直接了当的。并且最大好处是操作一个寄存器时候,不用考虑页面的问题。一切由
C 自动完成。
2、如何位操作?
汇编中的位操作是很容易的。在C 中更简单。C 的头文件中已经对所有可能需要位操作的寄存器的每
一位都有定义名称:
如:PORTA 的每一个I/O 口定义为:RA0、RA1、RA2。。。RA7。OPTION 的每一位定义为:PS0、
PS1、PS2 、PSA 、T0SE、T0CS、INTEDG 、RBPU。可以对其直接进行运算和附值。
如:
RA0=0;
RA2=1;
在汇编中是:
BCF PORTA,0;
BSF PORTA,2;
可以看出2 者是大同小异的,只是C 中不需要考虑页面的问题。
3、内存分配问题
在汇编中定义一个内存是一件很小心的问题,要考虑太多的问题,稍微不注意就会出错。比如16 位的
运算等。用C 就不需要考虑太多。下面给个例子:
16 位的除法(C 代码):
INT X=5000;
INT Y=1000;
INT Z=X/Y;
而在汇编中则需要花太多精力。
给一个小的C 代码,用RA0 控制一个LED 闪烁:
#include
void main()
{
int x;
CMCON=0B111; //掉A 口比较器,要是有比较器功能的话。
ADCON1=0B110; //掉A/D 功能,要是有A/D 功能的话。
TRISA=0; //RA 口全为输出。
loop:RA0=!RA0;
for(x=60000;--x;){;} //延时
goto loop;
}
说说RA0=!RA0 的意思:PIC 对PORT 寄存器操作都是先读取----修改----写入。上句的含义是程序先
读RA0,然后取反,最后把运算后的值重新写入RA0,这就实现了闪烁的功能。
----------------------------------------------------------------------------------------------------------------
二、浅谈PICC 的位操作
由于PIC 处理器对位操作是最高效的,所以把一些BOOL 变量放在一个内存的位中,既可以达到运算
速度快,又可以达到最大限度节省空间的目的。在C 中的位操作有多种选择。
*********************************************
如:char x; x=x | 0B00001000;
char x; x=x & 0B11011111;
把上面的变成公式则是:
#define bitset(var,bitno)(var |=1< char x; bitset(x,4); char x; bitclr (x,5); ************************************************* 但上述的方法有缺点,就是对每一位的含义不直观,最好是能在代码中能直观看出每一位代表的意思, 这样就能提高编程效率,避免出错。如果我们想用X 的0-2 位分别表示温度、电压、电流的BOOL 值可以如下: unsigned char x @ 0x20; bit temperature@ (unsigned)&x*8+0; bit voltage@ (unsigned)&x*8+1; bit current@ (unsigned)&x*8+2; 这样定义后X的位就有一个形象化的名字,不再是枯燥的1、2、3、4 等数字了。可以对X 全局修改,也可以对每一位进行操作: char=255; temperature=0; if(voltage)...... ***************************************************************** 还有一个方法是用C 的struct 结构来定义,如: struct cypok{ temperature:1; voltage:1; current:1; none:4; }x @ 0x20; 这样就可以用 x.temperature=0; if(x.current).... 等操作了。 ********************************************************** 上面的方法在一些简单的设计中很有效,但对于复杂的设计中就比较吃力。如象在多路工业控制上。 前端需要分别收集多路的多路信号,然后再设定控制多路的多路输出。如:有2 路控制,每一路的前端信 号有温度、电压、电流。后端控制有电机、喇叭、继电器、LED。如果用汇编来实现的话,是很头疼的事 情,用C 来实现是很轻松的事情,这里也涉及到一点C 的内存管理(其实C 的最大优点就是内存管理)。 采用如下结构: union cypok{ struct out{ motor:1; relay:1; speaker:1; led1:1; led2:1; }out; struct in{ none:5; temperature:1; voltage:1; current:1; }in; char x; }; union cypok an1; union cypok an2; 上面的结构有什么好处呢? 细分了信号的路an1 和an2; 细分了每一路的信号的类型(是前端信号in 还是后端信号out): an1.in ; an1.out; an2.in; an2.out; 然后又细分了每一路信号的具体含义,如: an1.in.temperature; an1.out.motor; an2.in.voltage; an2.out.led2;等 这样的结构很直观的在2 个内存中就表示了2 路信号。并且可以极其方便的扩充。 如添加更多路的信号,只需要添加: union cypok an3; union cypok an4; 从上面就可以看出用C 的巨大好处。 ---------------------------------------------------------------------------------------------------------------- 三、PICC 之延时函数和循环体优化 很多朋友说C 中不能精确控制延时时间,不能象汇编那样直观。其实不然,对延时函数深入了解一下 就能设计出一个理想的框架出来。一般的我们都用for(x=100;--x;){;}此句等同与x=100;while(--x){;}; 或for(x=0;x<100;x++){;}。 来写一个延时函数。 在这里要特别注意:X=100,并不表示只运行100 个指令时间就跳出循环。 可以看看编译后的汇编: x=100;while(--x){;} 汇编后: movlw 100 bcf 3,5 bcf 3,6 movwf _delay l2 decfsz _delay goto l2 return 从代码可以看出总的指令是是303 个,其公式是8+3*(X-1)。注意其中循环周期是X-1 是99 个。这 里总结的是x 为char 类型的循环体,当x 为int 时候,其中受X 值的影响较大。建议设计一个char 类型的 循环体,然后再用一个循环体来调用它,可以实现精确的长时间的延时。下面给出一个能精确控制延时的 函数,此函数的汇编代码是最简洁、最能精确控制指令时间的: void delay(char x,char y){ char z; do{ z=y; do{;}while(--z); }while(--x); } 其指令时间为:7+(3*(Y-1)+7)*(X-1)如果再加上函数调用的call 指令、页面设定、传递参数 花掉的7 个指令。则是:14+(3*(Y-1)+7)*(X-1)。如果要求不是特别严格的延时,可以用这个函数: void delay(){ unsigned int d=1000; while(--d){;} } 此函数在4M 晶体下产生10003us 的延时,也就是10mS。如果把D 改成2000,则是20003uS,以此类推。有朋友不明白,为什么不用while(x--)后减量,来控制设定X 值是多少就循环多少周期呢?现在看看编译它的汇编代码: bcf 3,5 bcf 3,6 movlw 10 movwf _delay l2 decf _delay incfsz _delay,w goto l2 return 可以看出循环体中多了一条指令,不简洁。所以在PICC 中最好用前减量来控制循环体。 再谈谈这样的语句: for(x=100;--x;){;}和for(x=0;x<100;x++){;} 从字面上看2 者意思一样,但可以通过汇编查看代码。后者代码雍长,而前者就很好的汇编出了简洁的代 码。所以在PICC 中最好用前者的形式来写循环体,好的C 编译器会自动把增量循环化为减量循环。因为 这是由处理器硬件特性决定的。PICC 并不是一个很智能的C 编译器,所以还是人脑才是第一的,掌握一些 经验对写出高效,简洁的代码是有好处的。 ---------------------------------------------------------------------------------------------------------------- 四、深入探讨PICC之位操作 1、用位操作来做一些标志位,也就是BOOL变量.可以简单如下定义: bit a,b,c; PICC会自动安排一个内存,并在此内存中自动安排一位来对应a,b,c.由于我们只是用它们来简单的 表示一些0,1信息,所以我们不需要详细的知道它们的地址\位究竟是多少,只管拿来就用好了。 2、要是需要用一个地址固定的变量来位操作,可以参照PIC.H里面定义寄存器。 如:用25H内存来定义8个位变量. static volatile unsigned char myvar @ 0x25; static volatile bit b7 @ (unsigned)&myvar*8+7; static volatile bit b6 @ (unsigned)&myvar*8+6; static volatile bit b5 @ (unsigned)&myvar*8+5; static volatile bit b4 @ (unsigned)&myvar*8+4; static volatile bit b3 @ (unsigned)&myvar*8+3; static volatile bit b2 @ (unsigned)&myvar*8+2; static volatile bit b1 @ (unsigned)&myvar*8+1; static volatile bit b0 @ (unsigned)&myvar*8+0; 这样即可以对myvar操作,也可以对B0--B7直接位操作. 但不好的是,此招在低档片子,如C5X系列上可能会出问题. 还有就是表达起来复杂,你不觉得输入代码累么?呵呵 3、这也是一些常用手法 #define testbit(var, bit) ((var) & (1 <<(bit))) //测试某一位,可以做BOOL运算 #define setbit(var, bit) ((var) |= (1 << (bit))) //把某一位置1 #define clrbit(var, bit) ((var) &= ~(1 << (bit))) //把某一位清0 附上一段代码,可以用MPLAB调试观察 #i nclude "pic.h" #define testbit(var, bit) ((var) & (1 <<(bit))) #define setbit(var, bit) ((var) |= (1 << (bit))) #define clrbit(var, bit) ((var) &= ~(1 << (bit))) char a,b; void main() { char myvar; myvar=0B10101010; a=testbit(myvar,0); setbit(myvar,0); a=testbit(myvar,0); clrbit(myvar,5); b=testbit(myvar,5); if(!testbit(myvar,3)) a=255; else a=100; while(1){;} } 4、用标准C的共用体来表示 #include "pic.h" union var { unsigned char byte; struct { unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1; } bits; }; char a,b; void main() { static union var myvar; myvar.byte=0B10101010; a=myvar.bits.b0; b=myvar.bits.b1; if(myvar.bits.b7) a=255; else a=100; while(1){;} } 5、用指针转换来表示 #include "pic.h" typedef struct { unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1; } bits; //先定义一个变量的位 #define mybit0 (((bits *)&myvar)->b0) //取myvar的地址(&myvar)强制转换成bits 类型的指针 #define mybit1 (((bits *)&myvar)->b1) #define mybit2 (((bits *)&myvar)->b2) #define mybit3 (((bits *)&myvar)->b3) #define mybit4 (((bits *)&myvar)->b4) #define mybit5 (((bits *)&myvar)->b5) #define mybit6 (((bits *)&myvar)->b6) #define mybit7 (((bits *)&myvar)->b7) char myvar; char a,b; void main() { myvar=0B10101010; a=mybit0; b=mybit1; if(mybit7) a=255; else a=100; while(1){;} } 6、5的方法还是烦琐,可以用粘贴符号的形式来简化它。 #include "pic.h" typedef struct { unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1; } bits; #define _paste(a,b) a##b #define bitof(var,num) (((bits *)&(var))->_paste(b,num)) char myvar; char a,b; void main() { a=bitof(myvar,0); b=bitof(myvar,1); if(bitof(myvar,7)) a=255; else a=100; while(1){;} } 有必要说说#define _paste(a,b) a##b 的意思: 此语句是粘贴符号的意思,表示把b 符号粘贴到a 符号之后。 例子中是 a=bitof(myvar,0);--->(((bits*)&(myvar))->_paste(b,0))--->(((bits *)&(var))->b0) 可以看出来,_paste(b,0)的作用是把0 粘贴到了b 后面,成了b0 符号。 总结:C语言的优势是能直接对低层硬件操作,代码可以非常非常接近汇编,上面几个例子的位操作代码 是100%的达到汇编的程度的;另一个优势是可读性高,代码灵活。上面的几个位操作方法任由你选, 你不必担心会产生多余的代码量出来。 ---------------------------------------------------------------------------------------------------------------- 五、在PICC 中使用常数指针 常数指针使用非常灵活,可以给编程带来很多便利。我测试过,PICC 也支持常数指针,并且也会自动分页,实在是一大喜事。 定义一个指向8 位RAM 数据的常数指针(起始为0x00): #define DBYTE ((unsigned char volatile *) 0) 定义一个指向16 位RAM 数据的常数指针(起始为0x00): #define CWORD ((unsigned int volatile *) 0) ((unsigned char volatile *) 0)中的0 表示指向RAM 区域的起始地址,可以灵活修改它。 DBYTE[x]中的x 表示偏移量。 1、下面是一段代码 char a1,a2,a3,a4; #define DBYTE ((unsigned char volatile *) 0) void main(void) //主函数 { long cc=0x89abcdef; a1=DBYTE[0x24]; a2=DBYTE[0x25]; a3=DBYTE[0x26]; a4=DBYTE[0x27]; while(1); } 2、 char a1,a2,a3,a4; #define DBYTE ((unsigned char volatile *) 0) void pp(char y) //子函数 { a1=DBYTE[y++]; a2=DBYTE[y++]; a3=DBYTE[y++]; a4=DBYTE[y]; } void main(void) //主函数 { long cc=0x89abcdef; char x; x=&cc; pp(x); while(1); } 3、 char a1,a2,a3,a4; #define DBYTE ((unsigned char volatile *) 0) void pp(char y) //子函数 { a1=DBYTE[y++]; a2=DBYTE[y++]; a3=DBYTE[y++]; a4=DBYTE[y]; } void main(void) //主函数 { bank1 static long cc=0x89abcdef; char x; x=&cc; pp(x); while(1); } ---------------------------------------------------------------------------------------------------------------- 六、PICC 关于unsigned 和 signed 的几个关键问题 unsigned 是表示一个变量(或常数)是无符号类型。signed 表示有符号。它们表示数值范围不一样。 PICC 默认所有变量都是unsigned 类型的,哪怕你用了signed 变量。因为有符号运算比无符号运算耗资源, 而且MCU 运算一般不涉及有符号运算。在PICC 后面加上-SIGNED_CHAR 后缀可以告诉PICC 把signed 变量当作有符号处理。 在PICC 默认的无符号运算下看这样的语句: char i; for(i=7;i>=0;i--){ ; //中间语句 } 这样的C 代码看上去是没有丁点错误的,但编译后,问题出现了: movlw 7 movwf i loop //中间语句 decf i //只是递减,没有判断语句!!! goto loop 原因是当i 是0 时候,条件还成立,还得循环一次,直到i 成负1 条件才不成立。而PICC 在默认参数下是不能判断负数的,所以编译过程出现问题。那么采用这样的语句来验证: char i; i=7; while(1){ i--; //中间语句 if(i==0)break; //告诉PICC 以判断i 是否是0 来作为条件 } 编译后代码正确: movlw 7 movwf i loop //中间语句
上一篇:PIC单片机-Mplab的使用与实践
下一篇:PIC16F1823开发笔记(三)汇编指令难点分析
推荐阅读最新更新时间:2024-11-12 11:55
推荐帖子
- 这个开关机控制电路怎么分析
- 本帖最后由cl17726于2014-7-3101:09编辑 1)J2应该是个短路帽,接起来会怎样?2)C115/CE1这两个电容干什么的.3)AO4459是PMOS管,BAT54C肖特基二极管,MNBT3904是NPN.XEINT9/XEINT11是两个CPU引脚,短按时候应该是到XEINT9?还是怎么的?怎么判断,时间怎么控制的?4)VDD_3V3作为电源灯指示,1K电阻合理吗,还是其实VDD_IN输入更好呢,按照普通0805红光LED来说.对电路还在学习探索
- cl17726 模拟电子
- 分享一个CCS5.2下CMD文件导致的DM648自启动问题
- CCS5使用的ECLIPS架构,跟以前的CCS3.3几乎完全接不上茬……DSP/BIOS也升了级,好多函数都改了名字。甚至以前的gel文件也被包裹成别的样子……好不容易把程序改到可以仿真运行了,等下载到SPIFLASH启动的时候又出现了问题,程序没能正常启动。后来测试波形发现程序已经完成了加载,但无法正确运行。经过几翻周折,终于找到了原因。发现编译后的.map文件有两个.cinit段和两个.pinit段。而且其中一个显示长度为0,并且处于UNINITIALIZED状态:
- fish001 DSP 与 ARM 处理器
- 【T叔藏书阁】黑客攻防三侠
- T叔有话说:黑客,传说中的神密人物,粗观此三侠,好象也就Dos下的一些操作,哈哈,你也能黑一下的.先给各位拜个早年,恭祝大家身心健康,万事如意,钱包鼓鼓.《黑客攻防实战入门与提高》高清书签版作者:主编:叶刚,陈文萍副主编:朱闻闻,刘生,高赫出版社:科学出版社简介:本书以项目为导向避开了大量理论的学习,以时间为主导,非常适合自学和教学使用。实例丰富,涵盖扫描,嗅探,服务器入侵,脚本入侵,注入攻击等多种黑客攻击首发,读者可同时获取技
- tyw 下载中心专版
- 单片机没法下载
- 求:ISP下载接口电路单片机没法下载并口下载线,网上电路一堆的,,而且电路也简单,你多看几个对比一下,就可以了。一般芯片都是用244,也有的用373,573等。同意楼上的同志不同的单片机不一样,这问题无解...就和让你去买电阻啥都不给你说一情况.实在不好意思,是STC89C52单片机,板子只有ISP下载接口,所以想求一ISP下载电路。STC的单片机ISP下载电路在它的datasheet里面就有啊。就是普通的串口RS232电路。然后在STC的主页下载一个烧录程序安装后就能下载了。
- 8444574 嵌入式系统
- 有关TI芯片TMS320DM642开发
- 刚开始实习的时候就进行TITMS320DM642芯片内部程序的编写,工业相机应用在电子警察系统中,对路口车辆的监控和拍照;主要集中在应用开发领域,了解DSP的基本是使用,建立时钟、任务、软件中断、硬件中断和CSL库的使用,中断采集到原始的图像数据(拜尔、YUV、RGB),进行图像的转换压缩,然后利用网络发送出图像数据到客户端,通过客户端接收到数据,进行违规处理和车牌识别。另外需要处理的是IO、RS232、RS485等接口数据的应用开发。5年后最近在做金属检测机项目,想利用D
- fish001 DSP 与 ARM 处理器
- 一起读《动手学深度学习(PyTorch版)》- 数据操作及线性代数基础
- 环境介绍硬件:JetsonOrinNano8Gpytorch版本:2.1.0a0+41361538.nv23.06python版本:3.8.10torch常用操作importtorchprint("输出torch的版本")print(torch.__version__)print("创建行向量,由0开始,12个整数")x=torch.arange(12)print(x)print("改变形状,变成3行4列的矩阵,不改变数量和值")x1
- LitchiCheng 测评中心专版
设计资源 培训 开发板 精华推荐
- 使用 ROHM Semiconductor 的 BU4213 的参考设计
- 使用 Semtech 的 SC2545 的参考设计
- UC3844B 电压反相电荷泵转换器的典型应用
- STM32F411-BORD
- YuzukiRuler Pro 随身Linux小尺子
- LT8330HS6 48V 升压转换器的典型应用电路
- EVAL-ADV7842-7511P,Advantiv ADV7842/ADV7511 视频编码器/解码器评估板
- LT6656BCS6-3.3 的典型应用,用于基本连接的 3.3V 电压基准
- BeagleBoard,基于低成本 OMAP3530 SoC OMAP 处理器的 USB 供电 BeagleBoard
- DC1466B-C,演示板 LTC2636 八路 12 位 SPI VOUT DAC,具有 10ppm/摄氏度内部基准
- 五一活动上线!动动手,一起来场旧物\"劳动\"大改造吧
- 【在线研讨会】ADI RadioVerse™技术与集成DPD算法的RF收发器AD9375
- 春暖花开,我为TI C2000 LaunchPad“画”外围!
- Vicor 更好的供电方式提升无人机飞行时间飞行半径及有效载荷
- 报名有好礼!50元京东卡等您拿!
- 【免费体验】来这里提交申请,有机会获得英飞凌无人机
- 以科技之力,成就安全 PI与您一起驾驭明天的智慧 答题赢好礼!
- 【边分享,边成长,11月有奖】EEWORLD优秀主题/回复第16期活动开始拉
- 让是德科技带我们一起 了解汽车电子车载系统解决方案 看视频答题赢好礼!
- 轻松注册世健eSHOP,百份奖品大派送!惊喜不断,好礼无限!ipad air2等你拿!