[转贴]gcc经验点滴(二)---鱼,熊掌.AVR的两种位操作的比较(wjc3k发于21ic)(位域与C位操
转贴前言:一直看到做51转做AVR的朋友,想继续使用bit这样的操作,讨论也不少。其实对于设计人员的未来考虑,还是使用纯C比较好,毕竟适应性强。C51增加的bit、sbit是考虑了51的SRAM小的特点的。移植性就受到影响了。能放弃,尽量放弃。-------------------------------------------
wjc3k 发表于 3/5/2003 10:39:38 AM AVR 单片机
大家好,得到大家的大力支持,本小小菜现如今胆儿大了点,脸皮厚了点儿,又来献丑啦。今儿就不说没用的话了,免得鸡蛋横飞过来啦。
AVR的两种位操作的比较(位域方式和移位宏方式)
测试环境如下:
硬件:AT90S2313
软件: WiinAVR gcc3.3 -Os级优化(最小size)。
说明:
由于AVR不支持位操作,所以必须通过软件来实现。下面对我所知道的两种方法进行一个简单的比较。
1、位域方式。先定义一个位域,
typedef struct _bit_struct
{
unsigned char bit0 : 1 ;
unsigned char bit1 : 1 ;
unsigned char bit2 : 1 ;
unsigned char bit3 : 1 ;
unsigned char bit4 : 1 ;
unsigned char bit5 : 1 ;
unsigned char bit7 : 1 ;
unsigned char bit6 : 1 ;
}bit_field;
再用一个宏 ,来指向要操作的位。
#define LED GET_BITFIELD(PORTB).bit0
#define BUTTON GET_BITFIELD(PINB).bit7
使用时只需要直接赋值即可:如LED = 0 ,LED = 1,或者直接判断 LED==0 , LED ==1.
这种方法类似C51中的位操作。直接。
2、位移宏方式。主要有三个.
#define Set_Bit(val, bitn) (val |=(1<<(bitn)))
#define Clr_Bit(val, bitn) (val&=~(1<<(bitn)))
#define Get_Bit(val, bitn) (val &(1<<(bitn)) )
三个分别用来设置某一位,清除某一位,取某一位的值.
使用方法为.Set_Bit(PORTA,3); Clr_Bit(PORTB,2); Get_Bit(val,5);
3、测试程序.
说明,假设PORTB.7接按纽,PORTB.0 接LED
测试程序完成如下操作。
当BUTTON == 0时 ,LED输出1 否则输出0,
这样的目的是即测试了输入,又测试了输出1和输出0,相对全面一点。C代码如下.
// testled.c 测试AVR的位操作.
// 这是gcc;如是其它编译器,请修改。
#include <avr/io.h>
// 定义一个寄存器(Register)或端口(Port)的八个位
typedef struct _bit_struct
{
unsigned char bit0 : 1 ;
unsigned char bit1 : 1 ;
unsigned char bit2 : 1 ;
unsigned char bit3 : 1 ;
unsigned char bit4 : 1 ;
unsigned char bit5 : 1 ;
unsigned char bit7 : 1 ;
unsigned char bit6 : 1 ;
}bit_field;
//定义一个宏,用来得到每一位的值
#define GET_BITFIELD(addr) (*((volatilebit_field *) (addr)))
//定义每一个位
#define LED GET_BITFIELD(PORTB).bit0
#define BUTTON GET_BITFIELD(PINB).bit7
#define Set_Bit(val, bitn) (val |=(1<<(bitn)))
#define Clr_Bit(val, bitn) (val&=~(1<<(bitn)))
#define Get_Bit(val, bitn) (val &(1<<(bitn)) )
int main( void )
{
DDRB = 0x41; //配置PB0为输出,PB7为输入
if ( BUTTON==0 ) LED = 1; else LED = 0;
//if(!Get_Bit(PINB,7) )Set_Bit(PORTB,0); else Clr_Bit(PORTB,0);
while(1);
}
// ---------------------- end -----------------------------
4、测试过程。
a.先使用位域方式。
主程序中使用 if ( BUTTON==0 ) LED = 1; else LED = 0;
结果如下:
int main( void )
{
4a: cf ed ldi r28, 0xDF ; 223
4c: d0 e0 ldi r29, 0x00 ; 0
4e: de bf out 0x3e, r29 ; 62
50: cd bf out 0x3d, r28 ; 61
DDRB = 0x41; //配置PB0为输出,PB7为输入
52: 81 e4 ldi r24, 0x41 ; 65
54: 87 bb out 0x17, r24 ; 23
if ( BUTTON==0 ) LED = 1; else LED = 0;
56: 86 b3 in r24, 0x16 ; 22
58: e8 2f mov r30, r24
5a: ff 27 eor r31, r31
5c: 80 81 ld r24, Z
5e: 86 fd sbrc r24, 6
60: 07 c0 rjmp .+14 ; 0x70
62: 88 b3 in r24, 0x18 ; 24
64: e8 2f mov r30, r24
66: ff 27 eor r31, r31
68: 80 81 ld r24, Z
6a: 81 60 ori r24, 0x01 ; 1
6c: 80 83 st Z, r24
6e: 06 c0 rjmp .+12 ; 0x7c
70: 88 b3 in r24, 0x18 ; 24
72: e8 2f mov r30, r24
74: ff 27 eor r31, r31
76: 80 81 ld r24, Z
78: 8e 7f andi r24, 0xFE ; 254
7a: 80 83 st Z, r24
while(1);
7c: ff cf rjmp .-2 ; 0x7c
main函数共52Bytes.其中,从lst文件看得出:main函数的初始化用了4条指令,8Bytes. 最后一句while(1);用了1条指令2Bytes.( for循环和do-while也是)
DDRB=0x41用了2条指令4Bytes. 计算一下:52-8-4-2=38Bytes,即if ( BUTTON==0 ) LED = 1; else LED = 0; 这句用了19条指令38Bytes. (居然运用了3个寄存器白r24,r30,r31,和一个Z,代码真是苦涩,,我看不懂,准备以后作代码加密用:).)
b.使用移位宏方式。
将 if ( BUTTON==0 ) LED = 1; else LED = 0;换为等效的 if(!Get_Bit(PINB,7) )Set_Bit(PORTB,0); else Clr_Bit(PORTB,0);
结果,main函数仅24Bytes.其它代码一样,略去. 所以,上面这句代码仅用了24-14=10Bytes ,5条指令。生成的代码如下:
56: b7 99 sbic 0x16, 7 ; 22
58: 02 c0 rjmp .+4 ; 0x5e
5a: c0 9a sbi 0x18, 0 ; 24
5c: 01 c0 rjmp .+2 ; 0x60
5e: c0 98 cbi 0x18, 0 ; 24
5. 菜论:鱼和熊掌。
由于AVR可以对I/O脚进行sbic,sbi,cbi,这样的位操作,所以使用I/O脚操作时,移位宏可以产生高效的代码。
例如,要实现上面的几个简单的指令,为了实现LED=1这样的类似C51的sbit的效果,我必须多付出(38-10=28Bytes)的代价。
6......
对于I/O脚,可以产生这样高效的代码,是因为有sbi和cbi这样的指令,那么对于一般的变量,又如何呢?................ 通用MCU8位16位24位32位变量定义头文件MCUBIT.H
PICC菜鸟玩PICC位变量潇洒走一回
本人对移植的看法
-----此内容被hotpower于2005-01-12,19:21:46编辑过 好贴子啊,不顶不行的! 3/5/2003 好早啊
atmel 的application note上面也有,很清楚。 看了不顶,是BC 我也有些小方法,呵呵,见笑了:
看下下面的程序吧:
/********************************************************************
*主 页 :HTTP://WWW.QLMCU.COM
*
*程序功能 :流水灯的左移右移实验
*
*应用软件 :WinAVR
*
*版 本 :WinAVR-20050214-install
*
*硬 件 :WS9500
*
*创建时间 :2005-11-10
*
*编 写: benladn911
*
*注:为了有更多实用的实验程序供大家学习,部分程序参考网上的资源,
* 在此谢谢这些无私奉献的朋友!!!
*
********************************************************************/
#include <avr/io.h>
#include <avr/delay.h>
//注: 内部函数_delay_ms() 最高延时262.144mS@1MHz 即 32.768ms@8MHz
// 该函数可以实现较精确的定时for()/while()指令很难计算延时时间
// 为了使 _delay_ms()函数的延时正确,须在makefile中设定F_CPU为实际的系统时钟频
// 本范例为6MHz外部晶振振荡器 即 F_CPU=6000000
//-----------位操作定义------------------------<<<<<<<<<<<<<<<<<<<<<<<<<这里
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define LED_ONcbi(PORTD,7)//在程序用LED_ON代替cbi(PORTD,7)<<<<<<<<<<<<<<
//---------------------------------------------------------------
//内部函数_delay_ms() 最高延时262.144mS@1MHz 即 32.768ms@8MHz
void delay_ms(unsigned int ms);//----ms级延时
//内部函数_delay_us() 最高延时768 us@1MHz 即 96 us@8MHz
void delay_us(unsigned int us);//----us级延时
//--------------------------------------------------------------------------
int main(void)
{
unsigned char i,j; //定义变量
DDRA=0xFF; //定义了端口PORTA全部为输出
PORTA=0xFF; //PA口设为输出高电平,灯灭PORTA
//第一种方法选通PD7(LED的电源控制的端)
//DDRD=0xFF; //PORTD全部设置为输出
//PORTD=PORTD & 0x7F; //0x7F=0b0111 1111
//第二种方法选通PD7(LED的电源控制的端)
//DDRD=0xFF; //PORTD全部设置为输出
//PORTD=0x7F; //0b0111 1111
//第三种方法选通PD7(LED的电源控制的端)
sbi(DDRD,7);//对PD7的DDR清零,PD7设置为输出
LED_ON;//PD7输出低电平
while(1)
{
i=0x01;
for (j=0;j<8;j++)//循环8次,即PA0~~PA7轮流闪亮
{
PORTA=~i; //反相输出,低电平有效
delay_ms(100);
i=i<<1; //左移一位
// 0b00000001 PB0
// 0b00000010 PB1
// 0b00000100 PB2
// 0b00001000 PB3
// 0b00010000 PB4
// 0b00100000 PB5
// 0b01000000 PB6
// 0b10000000 PB7
}
i=0x80;
for (j=0;j<8;j++)//循环8次,即PA0~~PA7轮流闪亮
{
PORTA=~i; //反相输出,低电平有效,
delay_ms(100);
i=i>>1; //右移一位
}
}
}
//----------------ms级延时---------------
void delay_ms(unsigned int ms)
{
unsigned int i;
for(i=0;i<ms;i++) _delay_ms(1); //延时 i*ms= 毫秒,可自行调节
}
//----------------us级延时---------------
void delay_us(unsigned int us)
{
unsigned int i;
for(i=0;i<us;i++) _delay_us(1); //延时 i*us= 毫秒,可自行调节
} 哈哈,现在被AVR感化了,反而见了bit就晕...
(1 << bit)挺好的,见了它就知道和位有关了,出错几率反而更小了. 新手学习中,致谢 不错,谢了 顶~~~~~~~~~~ 顶一下 用字节进行逻辑运算吗!很简单!就可以对任何位置位,清0 顶~~~~~~~~ 又学到了知识,非常感谢. 为什么我按楼主写的测试位操作不行呢?哪位老兄给解释一下!单片机是MAGE8的。 看了就长见识了。呵呵。顶了 本来已经忘了这个话题,我一直这样用的:
#define GET_BITFIELD(addr) (*((volatilebit_field *)(addr)))
#define GETBIT(address,b) GET_BITFIELD(&address).bit##b
#define PD7__ GETBIT(PORTD,7)
#define PD6__ GETBIT(PORTD,6)
#define PD5__ GETBIT(PORTD,5)
#define PD4__ GETBIT(PORTD,4)
#define PD3__ GETBIT(PORTD,3)
#define PD2__ GETBIT(PORTD,2)
#define PD1__ GETBIT(PORTD,1)
#define PD0__ GETBIT(PORTD,0)
#define nRF905_MOSI PD7__
#define nRF905_SCK PD6__
注意GET_BITFIELD(&address).bit##b中的address前面的取址符"&",这是唯一与楼主所讲的不一样的地方,大家可以该该再看看
下面是if(dat&0x80)nRF905_MOSI=1;else nRF905_MOSI=0;的汇编代码:
if(dat&0x80)
3da: 87 ff sbrs r24, 7
3dc: 02 c0 rjmp .+4 ; 0x3e2 <SpiWriteByte+0xa>
nRF905_MOSI=1;
3de: 5f 9a sbi 0x0b, 7 ; 11
3e0: 01 c0 rjmp .+2 ; 0x3e4 <SpiWriteByte+0xc>
else
nRF905_MOSI=0;
3e2: 5f 98 cbi 0x0b, 7 ; 11
nRF905_SCK=1;
3e4: 5e 9a sbi 0x0b, 6 ; 11
究其原因,AVR标准头文件中的是这样定义一个寄存器的:
#define PIND _SFR_IO8(0x10)
实际上是这样的
#define PIND (*(volatile uint8_t *)(0x10))
可以看出PIND已经成为指向地址0x10的内容,,而我们是要对地址进行位操作,所以需要用"&PIND"将其还原为地址再使用.
-----此内容被alexant于2006-07-06,23:32:47编辑过
-----此内容被alexant于2006-07-06,23:45:22编辑过 ^_^
真正感觉到C的灵活
顶 有启发,好文 好,以后就这样用呵。谢谢了,顶起来! WinAvr 2006.4.21版有 <avr/portpins.h> 请问这一块,最后一部分是什么意思?
#define GET_BITFIELD(addr) (*((volatilebit_field *)(addr))) * bit_field就是楼主帖子里的那个位域啊:
typedef struct _bit_struct
{
unsigned char bit0 : 1 ;
unsigned char bit1 : 1 ;
unsigned char bit2 : 1 ;
unsigned char bit3 : 1 ;
unsigned char bit4 : 1 ;
unsigned char bit5 : 1 ;
unsigned char bit6 : 1 ;
unsigned char bit7 : 1 ;
}bit_field;
#define GET_BITFIELD(addr) (*((volatilebit_field *)(addr)))是将addr强制为位域指针,方便其他的宏来访问addr内容的某个位.
不知这样解释是否达意,还望各位多多赐教! 强烈支持hotpower 菜农 学习中 学习中 请问可以这样定义吗?
typedef struct _bit_struct
{
unsigned char bit0 : 8 ;
unsigned long bit1 : 17 ;
unsigned char bit2 : 7 ;
unsigned char bit3 : 8 ;
}bit_field;
如果这样定义的话,占用的字节数会是多少?5个?
谢谢! 不错.如果先定义好led的状态,用起来更方便一点.如:
#define led_on PORTB&=0xfe//置0
#define led_off PORTB|=0x01//置1
#define led_cpl PORTB^=0x01//取反
要开led,直接led_on,程序看起来更直观. 请问可以这样定义吗?
typedef struct _bit_struct
{
unsigned char bit0 : 8 ;
unsigned long bit1 : 17 ;
unsigned char bit2 : 7 ;
unsigned char bit3 : 8 ;
}bit_field;
如果这样定义的话,占用的字节数会是多少?5个?
谢谢!
在网上找了一下,只有一个网友说像上面这样定义是不行的,但没有验证。
这里有没有大虾讲解多一下?? 敝人验证了一下,证明是可以这样定义的(在winavr中)
而且在网上找了一个网友发表的文章,他说编译器不同可能压缩的方法就不同。
所以敝人所说可以这么定义,而且内存定位是压缩排列的前提是使用GCC的winavr,其他情况没有验证。
还有提个现象:ouravr上回答他人问题的人真的越来越少了,有时候急的时候真等也等不到呀…… 顶一下 我也来顶一下,受益非浅,谢谢各位了 谢谢,什么时候我才能上传我的东西跟大家分享呢,哈哈,初学者的心愿! 好贴!不过我还是喜欢用"与","或" 进行字节操作. 顶!
对于bit变量采用下面的方法操作也很方便:
typedef struct {
unsigned BIT0: 1;
unsigned BIT1: 1;
unsigned BIT2: 1;
unsigned BIT3: 1;
unsigned BIT4: 1;
unsigned BIT5: 1;
unsigned BIT6: 1;
unsigned BIT7: 1;
}AVRBIT;//定义一个只能按位域寻址的新变量类型
typedef union {
AVRBIT BIT;//可以按位域寻址
unsigned char BYTE;//可以按字节寻址
}AVRBITBYTE;//定义一个既能按位域寻址也可按字节寻址的新变量类型
AVRBITBYTErWorkFlag;
#define fPowerOn rWorkFlag.BIT.BIT0
#define fModelWork rWorkFlag.BIT.BIT1
#define fOnWork rWorkFlag.BIT.BIT2
#define fSelfDisp rWorkFlag.BIT.BIT6
#define fSetFlash5s rWorkFlag.BIT.BIT7
int main( void )
{
// DDRB = 0x41; //配置PB0为输出,PB7为输入
while(1)
{
if ( fPowerOn==1 ) fOnWork = 1; else fOnWork = 0;
if ( fPowerOn==0 )fPowerOn =1;
}
} 各位的方法都有一个不足的地方:不能定义多于1位的bitfield。
例如:atmega64外接了一个29SF040,地址A0-A18,按32Kbytes分页,29SF040的A0-A14接到MCU地址总线,A15-A18做分页地址,连接到一般I/O上,例如PORTF0-PORTF3。
在程序中如何方便地定义和访问这种多于1位的位域?即某个位域变量可以从一个字节的bit0到bit7中的任何一位开始,宽度可以从1位到7位。 楼上的,给你地址你看看,这儿有介绍.
http://www.programfan.com/article/showarticle.asp?id=2695 可能我没说明白意思,算了,我直接给出我的答案:终极位域定义。
下面是头文件:
---------------------------------------------
#ifndef __BITWISE_H
#define __BITWISE_H
#ifdef __cplusplus
extern "C" {
#endif
#define _BITFIELD_(_W) \
typedef union { \
struct { \
uint8_t used :_W; \
}; \
uint8_t dummy; \
} _bit_0_ ## _W ## _t
#define _BITFIELD1_(_S, _W) \
typedef union { \
struct { \
uint8_t res :_S; \
uint8_t used :_W; \
}; \
uint8_t dummy; \
} _bit_ ## _S ## _ ## _W ## _t
_BITFIELD_ (1);
_BITFIELD_ (2);
_BITFIELD_ (3);
_BITFIELD_ (4);
_BITFIELD_ (5);
_BITFIELD_ (6);
_BITFIELD_ (7);
_BITFIELD_ (8);
_BITFIELD1_ (1, 1);
_BITFIELD1_ (1, 2);
_BITFIELD1_ (1, 3);
_BITFIELD1_ (1, 4);
_BITFIELD1_ (1, 5);
_BITFIELD1_ (1, 6);
_BITFIELD1_ (1, 7);
_BITFIELD1_ (2, 1);
_BITFIELD1_ (2, 2);
_BITFIELD1_ (2, 3);
_BITFIELD1_ (2, 4);
_BITFIELD1_ (2, 5);
_BITFIELD1_ (2, 6);
_BITFIELD1_ (3, 1);
_BITFIELD1_ (3, 2);
_BITFIELD1_ (3, 3);
_BITFIELD1_ (3, 4);
_BITFIELD1_ (3, 5);
_BITFIELD1_ (4, 1);
_BITFIELD1_ (4, 2);
_BITFIELD1_ (4, 3);
_BITFIELD1_ (4, 4);
_BITFIELD1_ (5, 1);
_BITFIELD1_ (5, 2);
_BITFIELD1_ (5, 3);
_BITFIELD1_ (6, 1);
_BITFIELD1_ (6, 2);
_BITFIELD1_ (7, 1);
#define SFR(_P, _S, _W) (* (_bit_ ## _S ## _ ## _W ## _t volatile *) (_SFR_ADDR (_P))).used
#ifdef __cplusplus
}
#endif
#endif
/* EOF */
-------------------end--end--end---------------------------
使用方法:
要使用的位域变量或寄存器,只需按下面的规则定义一下就可以了:
#define <BITFIELD_VAL> SFR(<BYTE_ADDR>,<START_BIT>,<BIT_WIDTH>)
BITFIELD_VAL: 要使用的位域变量名。
BYTE_ADDR: 所需的位域变量所在的字节地址。
START_BIT: 所需的位域变量在该字节的起始位置。
BIT_WIDTH: 位域变量的宽度。
比如:我要定义PORTF3-PORTF6作为一个整体变量使用,变量名为PAGE_REG,我只需定义:
#define PAGE_REG SFR (PORTF, 3, 4)
在使用时就直接读写 PAGE_REG 就可以了,决不会影响 PORTF0, PORTF1, PORTF2, PORTF7的值。
再举例:定时器预分频设置:
#define T0_CK SFR (TCCR0, 0, 3)
使用时:
T0_CK = 0; // stop
T0_CK = 1; // OSC
T0_CK = 2; // OSC/8
T0_CK = 3; // OSC/64
T0_CK = 4; // OSC/256
等等。
是不是很方便? 学习中! 指针没学好,看到指针就晕了 为什么我按楼主和5楼写的测试位操作不行呢?哪位老兄给解释一下!用studio4.12仿真的。
./emotion/em045.gif GET_BITFIELD(&address).bit##b
在这个式子中最后的##b,其中##是代表什么意思?
有谁可以解释一下的吗 ? ps : 是连接的意思,刚查到 ! 个人有个人的习惯,不必说谁好谁坏;就像学英语是美式好还是英式好,一言难尽。
对从C51转过来搞AVR的人来说,对位操作还是情有独钟的。
感谢楼主的分享。 我非常赞成snow_xsj的观点 我用了楼主的方法,但在编译后出现了很多警告:
2549: warning: cast to pointer from integer of different size,
我的引用如下:
unsigned char flag4;
#define image_off GET_BITFIELD(flag4).bit0
#define image_art GET_BITFIELD(flag4).bit1
#define image_wb GET_BITFIELD(flag4).bit2
#define picture_lr GET_BITFIELD(flag4).bit3
#define picture_ud GET_BITFIELD(flag4).bit4
#define mask_on GET_BITFIELD(flag4).bit5
#define ext_on GET_BITFIELD(flag4).bit6
#define alarm_on GET_BITFIELD(flag4).bit7
#define GET_BITFIELD(addr) (*((volatilebit_field *) (addr)))
/*########################################################*/
typedef struct _bit_struct
{
unsignedbit0 : 1 ;
unsignedbit1 : 1 ;
unsignedbit2 : 1 ;
unsignedbit3 : 1 ;
unsignedbit4 : 1 ;
unsignedbit5 : 1 ;
unsignedbit6 : 1 ;
unsignedbit7 : 1 ;
}bit_field;
/*########################################################*/
请各位大虾帮我看看是哪里出了问题,如何更改? 6......
对于I/O脚,可以产生这样高效的代码,是因为有sbi和cbi这样的指令,那么对于一般的变量,又如何呢?................
期待有人答复这一点,因为我编了108K的程序,RAM也用了3.4K,再往后编时,为了节省RAM,怕不够用,就用了一些位域来定义一些标志变量,但不是I/0,这样是不是代码加长了呢? 好啊,顶 好,学习了.谢谢各位!!小弟祝大家天天开心. 学习! 在IAR下,用位域读写IO口非常高效的。 帮助很大 对于AVR的为操作 我一般是喜欢宏定义
例如 :#define ledon (PORTB|=(1<<PB0);
AVR猎手的这个更不错 ./emotion/em021.gif./emotion/em021.gif
//-----------位操作定义------------------------<<<<<<<<<<<<<<<<<<<<<<<<<这里
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) 学习! 记号 好帖,不过有些地方还不是很明白 我和16楼的用法一样 先顶一下,待会再看 顶 应该好好看看! 这个不错,收藏了 LZ,你错了.16楼的才是正确的:
*************************************************************************
【16楼】 alexant
注意GET_BITFIELD(&address).bit##b中的address前面的取址符"&",这是唯一与楼主所讲的不一样的地方,大家可以该该再看看
下面是if(dat&0x80)nRF905_MOSI=1;else nRF905_MOSI=0;的汇编代码:
if(dat&0x80)
3da: 87 ff sbrs r24, 7
3dc: 02 c0 rjmp .+4 ; 0x3e2 <SpiWriteByte+0xa>
nRF905_MOSI=1;
3de: 5f 9a sbi 0x0b, 7 ; 11
3e0: 01 c0 rjmp .+2 ; 0x3e4 <SpiWriteByte+0xc>
else
nRF905_MOSI=0;
3e2: 5f 98 cbi 0x0b, 7 ; 11
nRF905_SCK=1;
3e4: 5e 9a sbi 0x0b, 6 ; 11
究其原因,AVR标准头文件中的是这样定义一个寄存器的:
#define PIND _SFR_IO8(0x10)
实际上是这样的
#define PIND (*(volatile uint8_t *)(0x10))
可以看出PIND已经成为指向地址0x10的内容,,而我们是要对地址进行位操作,所以需要用"&PIND"将其还原为地址再使用. // testled.c 测试AVR的位操作.
// 这是gcc;如是其它编译器,请修改。
#include <avr/io.h>
typedef union {
unsigned char BYTE;//可以按字节寻址
struct {
unsigned char BIT0 :1;
unsigned char BIT1 :1;
unsigned char BIT2 :1;
unsigned char BIT3 :1;
unsigned char BIT4 :1;
unsigned char BIT5 :1;
unsigned char BIT6 :1;
unsigned char BIT7 :1;
} BIT; //定义一个只能按位域寻址的新变量类型
} BITFIELD; //定义一个既能按位域寻址也可按字节寻址的新变量类型
#define GET_FIELD(addr) (*((volatileBITFIELD *) (&addr))) //使addr所指寄存器既能按位域寻址也可按字节寻址
#define GET_BIT(addr,b) GET_FIELD(addr).BIT.BIT##b //定义一个宏,用来得到每一位的值
#define nRF905_MOSI GET_BIT(PORTD,6)
#define Set_Bit(val, bitn) (val |=(1<<(bitn)))
#define Clr_Bit(val, bitn) (val&=~(1<<(bitn)))
#define Get_Bit(val, bitn) (val &(1<<(bitn)) )
int main( void )
{
DDRB = 0xff; //配置PB0为输出,PB7为输入
while(1)
{
nRF905_MOSI=1;
nRF905_MOSI=0;
Set_Bit(PORTB,0);
Clr_Bit(PORTB,0);
}
}
// ---------------------- end -----------------------------
******************测试结果***********************************
---- main.c ---------------------------------------------------------------------------------------
32: {
+00000049: EF8F SER R24 Set Register
+0000004A: BB87 OUT 0x17,R24 Out to I/O location
36: nRF905_MOSI=1;
+0000004B: 9A96 SBI 0x12,6 Set bit in I/O register
37: nRF905_MOSI=0;
+0000004C: 9896 CBI 0x12,6 Clear bit in I/O register
38: Set_Bit(PORTB,0);
+0000004D: 9AC0 SBI 0x18,0 Set bit in I/O register
39: Clr_Bit(PORTB,0);
+0000004E: 98C0 CBI 0x18,0 Clear bit in I/O register
+0000004F: CFFB RJMP PC-0x0004 Relative jump
***************
结果一样,根本不存在代码效率问题.所以bit field法,大家可以放心用,真的方便多了 各有高招,乱七八糟 <--- 瞎说的
可否综合一下 仿真测试了一下,38楼JohnLee的可用:
#ifndef __MYBIT_H__
#define __MYBIT_H__
#ifdef __cplusplus
extern "C" {
#endif
// AVR 位操作宏
// Compiler: WinAVR
#include <avr/io.h>
/***********************************************************************************/
// Set, Clear and Reverse a bit in a byte
#define SBI__(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#define CBI__(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define RBI__(sfr, bit) (_SFR_BYTE(sfr) ^= _BV(bit))
/***********************************************************************************
使用方法:
要使用的位域变量或寄存器,只需按下面的规则定义一下就可以了:
#define <BITFIELD_VAL> SFR__(<BYTE_ADDR>,<START_BIT>,<BIT_WIDTH>)
BITFIELD_VAL: 要使用的位域变量名。
BYTE_ADDR: 所需的位域变量所在的字节地址。
START_BIT: 所需的位域变量在该字节的起始位置。
BIT_WIDTH: 位域变量的宽度。
比如:我要定义PORTF3-PORTF6作为一个整体变量使用,变量名为PAGE_REG,我只需定义:
#define PAGE_REG SFR__ (PORTF, 3, 4)
在使用时就直接读写 PAGE_REG 就可以了,决不会影响 PORTF0, PORTF1, PORTF2, PORTF7的值。
再举例:定时器预分频设置:
#define T0_CK SFR__ (TCCR0, 0, 3)
使用时:
T0_CK = 0; // stop
T0_CK = 1; // OSC
T0_CK = 2; // OSC/8
T0_CK = 3; // OSC/64
T0_CK = 4; // OSC/256
等等。
***********************************************************************************/
#define _BITFIELD0_(_S, _W) \
typedef union { \
struct { \
uint8_t used:_W; \
}; \
uint8_t dummy; \
} _bit_##_S##_##_W##_t;
#define _BITFIELD1_(_S, _W) \
typedef union { \
struct { \
uint8_t res :_S; \
uint8_t used:_W; \
}; \
uint8_t dummy; \
} _bit_##_S##_##_W##_t;
_BITFIELD0_ (0, 1);
_BITFIELD0_ (0, 2);
_BITFIELD0_ (0, 3);
_BITFIELD0_ (0, 4);
_BITFIELD0_ (0, 5);
_BITFIELD0_ (0, 6);
_BITFIELD0_ (0, 7);
_BITFIELD0_ (0, 8);
_BITFIELD1_ (1, 1);
_BITFIELD1_ (1, 2);
_BITFIELD1_ (1, 3);
_BITFIELD1_ (1, 4);
_BITFIELD1_ (1, 5);
_BITFIELD1_ (1, 6);
_BITFIELD1_ (1, 7);
_BITFIELD1_ (2, 1);
_BITFIELD1_ (2, 2);
_BITFIELD1_ (2, 3);
_BITFIELD1_ (2, 4);
_BITFIELD1_ (2, 5);
_BITFIELD1_ (2, 6);
_BITFIELD1_ (3, 1);
_BITFIELD1_ (3, 2);
_BITFIELD1_ (3, 3);
_BITFIELD1_ (3, 4);
_BITFIELD1_ (3, 5);
_BITFIELD1_ (4, 1);
_BITFIELD1_ (4, 2);
_BITFIELD1_ (4, 3);
_BITFIELD1_ (4, 4);
_BITFIELD1_ (5, 1);
_BITFIELD1_ (5, 2);
_BITFIELD1_ (5, 3);
_BITFIELD1_ (6, 1);
_BITFIELD1_ (6, 2);
_BITFIELD1_ (7, 1);
// P: the port; S: start bit; W: bits width
#define SFR__(_P, _S, _W)(* (_bit_##_S##_##_W##_t volatile *) (_SFR_ADDR (_P))).used
/***********************************************************************************/
#ifdef __TEST_MYBIT_H__
#include <avr/io.h>
#include "mybit.h"
#define LED6_ON PORTD |=_BV(6) //位置高
#define LED6_OFFPORTD &= ~_BV(6) //位置低
#define LED6_ALTPORTD ^=_BV(6) //位取反
#define LED6_READ PIND &_BV(6) //位读
#define LED6SFR__ (PORTD, 6, 1)
#define LED67 SFR__ (PORTD, 6, 2)
int main(void)
{
DDRD = 0xFF;
LED6_ON;
LED6_OFF;
LED6_ALT;
SBI__(PORTD, 6);
CBI__(PORTD, 6);
RBI__(PORTD, 6);
LED6 = 0b0;
LED67 = 0b10;
LED6 = LED6_READ;
return 0;
}
/***********************************************************************************
@00000049: main
---- mybit.h --------------------------------------------------------------------------------------
13: {
+00000049: EF8F SER R24 Set Register
+0000004A: BB81 OUT 0x11,R24 Out to I/O location
16: LED6_ON;
+0000004B: 9A96 SBI 0x12,6 Set bit in I/O register
17: LED6_OFF;
+0000004C: 9896 CBI 0x12,6 Clear bit in I/O register
18: LED6_ALT;
+0000004D: B382 IN R24,0x12 In from I/O location
+0000004E: E490 LDI R25,0x40 Load immediate
+0000004F: 2789 EOR R24,R25 Exclusive OR
+00000050: BB82 OUT 0x12,R24 Out to I/O location
20: SBI__(PORTD, 6);
+00000051: 9A96 SBI 0x12,6 Set bit in I/O register
21: CBI__(PORTD, 6);
+00000052: 9896 CBI 0x12,6 Clear bit in I/O register
22: RBI__(PORTD, 6);
+00000053: B382 IN R24,0x12 In from I/O location
+00000054: 2789 EOR R24,R25 Exclusive OR
+00000055: BB82 OUT 0x12,R24 Out to I/O location
24: LED6 = 0b0;
+00000056: 9896 CBI 0x12,6 Clear bit in I/O register
25: LED67 = 0b10;
+00000057: B382 IN R24,0x12 In from I/O location
+00000058: 738F ANDI R24,0x3F Logical AND with immediate
+00000059: 6880 ORI R24,0x80 Logical OR with immediate
+0000005A: BB82 OUT 0x12,R24 Out to I/O location
26: LED6 = LED6_READ;
+0000005B: B380 IN R24,0x10 In from I/O location
+0000005C: 9896 CBI 0x12,6 Clear bit in I/O register
29: }
***********************************************************************************/
#endif
/***********************************************************************************/
#ifdef __cplusplus
}
#endif
#endif //define __MYBIT_H__
/* ---------------------------------- end of file ------------------------------------- */ 顶起来了!!!! 好多高手,赶快学习 MARK 我是这么用的:
//标识符合并宏
#define __ID_COMBINE(left, right) (left##right)
//标识符合并中间宏
#define _ID_COMBINE(l, r) __ID_COMBINE(l, r)
#define _PORT(a) _ID_COMBINE(PORT, a)
#define _DDR(a) _ID_COMBINE(DDR, a)
#define _PIN(a) _ID_COMBINE(PIN, a)
#define _SET_BIT(target, bit) ((target) |= (1 << (bit)))
#define _CLR_BIT(target, bit) ((target) &= ~(1 << (bit)))
#define _GET_BIT(target, bit) ((target) & (1 << (bit)))
#define _FLP_BIT(target, bit) ((target) ^= (1 << (bit)))
#define _PIN_HI(flag, bit) (_SET_BIT(_PORT(flag), bit))
#define _PIN_LO(flag, bit) (_CLR_BIT(_PORT(flag), bit))
#define _PIN_GT(flag, bit) (_GET_BIT(_PIN(flag), bit))
#define _PIN_OUT(flag, bit) _DDR(flag) |= BIT(bit)
#define _PIN_IN(flag, bit) _DDR(flag) &= ~BIT(bit)
#define _PIN_IN_PH(flag, bit) _PIN_IN(flag, bit); _PIN_HI(flag, bit)
//LCD
#define _LCD_nRST_OUT _PIN_OUT(B, 1)
#define _LCD_nRST_HI _PIN_HI(B, 1)
#define _LCD_nRST_LO _PIN_LO(B, 1)
#define _LCD_nCS1_OUT _PIN_OUT(A, 6)
#define _LCD_nCS1_HI _PIN_HI(A, 6)
#define _LCD_nCS1_LO _PIN_LO(A, 6)
#define _LCD_nCS2_OUT _PIN_OUT(A, 7)
#define _LCD_nCS2_HI _PIN_HI(A, 7)
#define _LCD_nCS2_LO _PIN_LO(A, 7)
#define _LCD_E_OUT _PIN_OUT(C, 7)
#define _LCD_E_HI _PIN_HI(C, 7)
#define _LCD_E_LO _PIN_LO(C, 7)
#define _LCD_RnW_OUT _PIN_OUT(A, 5)
#define _LCD_RnW_HI _PIN_HI(A, 5)
#define _LCD_RnW_LO _PIN_LO(A, 5)
#define _LCD_DnI_OUT _PIN_OUT(B, 0)
#define _LCD_DnI_HI _PIN_HI(B, 0)
#define _LCD_DnI_LO _PIN_LO(B, 0)
#define _LCD_DATA_IN _DDR(D) = 0x00
#define _LCD_DATA_OUT _DDR(D) = 0xFF
#define _LCD_OUT(x) _PORT(D) = x
#define _LCD_IN(x) x = _PIN(D)
void HD61202_MoveCursorToChar(BYTE x, BYTE yCh)
{
if(x < _SCREEN_BLOCK_WIDTH)
{
_LCD_nCS1_HI;
_LCD_nCS2_LO;
}
else
{
_LCD_nCS1_LO;
_LCD_nCS2_HI;
}
HD61202_CMD(_DISP_PAGE(yCh));
HD61202_CMD(_DISP_Y(x));
} 学习中,顶................ 学习啦! 标记 谢谢,mark mark 果然好贴!!! 顶 好贴再顶! mark 顶了!
不错! 好贴,收藏! 好贴,转载了 以后学 好贴!顶起! 高手如云,值得学习学习!!!!!!!!!! 学习 记好! 好资料,值得学习!!! 好贴 学习了,谢谢分享。 mark1 mark 好 好贴,看了这么多,我还是用 PORTD |=_BV(PD1);这样的东西,大家一看就知道。效率是编译器的事。 mark 我就是因为移位操作,我的ad 转换结果就错了,上面的写的很好;谢谢分享 好贴! 好贴~~~顶一个! Mark mark
页:
[1]
2