为什么Bitshift运营商?

时间:2010-08-03 14:21:36

标签: c microcontroller

我不明白何时以及为什么要使用bitshift运算符进行微芯片编程:例如......

SWITCH_DDR &= ~SWITCH_BIT;   
SWITCH_PORT |= SWITCH_BIT;

为什么要使用这些运算符?

或者...

void SerialInit(void)
{
   UBRRH = ((XTAL / (8 * 250000)) - 1)>>8;   // 250kbps at 16Mhz
   UBRRL = (XTAL / (8 * 250000)) - 1;
   UCSRA = (1<<U2X);
   UCSRB = (1<<TXEN);
   UCSRC = (1<<URSEL) + (1<<UCSZ1) + (1<<UCSZ0);
}

这里怎么了?有人请用0和1来图解说明。或者这是另一个例子:

ulong MesureLC(void)
{
 int i;

 TCCR1B = 0;
 CountHigh = 0;
 TCNT1 = 0;

 for (i=0;i<25000;i++)
 {
  TCCR1B = (1<<CS12) + (1<<CS11) + (1<<CS10);   // WTF ???
  UDR = 0x55;
  while(!(UCSRA & (1<<UDRE)));
 }
 while(!(UCSRA & (1<<TXC)));
 TCCR1B = 0;

 CountLow = TCNT1;
 Count = CountLow + (CountHigh << 16);

 return Count;
}

我需要了解这些东西。任何帮助表示赞赏。

8 个答案:

答案 0 :(得分:17)

有很多原因让人想起为什么你会使用它们。

首先,有时微型控制器型设备的内存很紧,您可能希望尽可能少地存储。

此要求也可能是由于您与之交互的其他设备或软件具有相同的最小空间需求。他们可能会使用子字节数据类型来完成工作,其中一个例子是TCP / IP头,其中字段的范围从单个位(如DF字段)到更多(如IP地址)。

实际上,您提供的第二位代码SerialInit是为串行通信芯片(UART =通用异步接收器和发送器)设置属性。 UCSRx通常代表UART控制/状态寄存器#x,因此您将信息写入相当低级别的设备以控制其行为。

另一个原因是您可能有内存映射I / O.这意味着,虽然您认为您正在写入内存,但您可能会将这些位直接发送到某种I / O设备。

一个典型的例子可能是使用存储器映射字节,其中七段LED被控制。

LED的结构可以是:

+---+---+---+---+---+---+---+---+
| d | e | g | f | a | b | c | p |
+---+---+---+---+---+---+---+---+
  #   #   #   #   #   #   #   #
  #   #   #   #   #   #   #   #===========#
  #   #   #   #   #   #   #               #
  #   #   #   #   #   #   #===========#   #
  #   #   #   #   #   #               #   #
  #   #   #   #   #   #===========#   #   #
  #   #   #   #   #               #   #   #
  #   #   #   #   #=====#         #   #   #
  #   #   #   #         #         #   #   #
  #   #   #   #     +-------+     #   #   #
  #   #   #   #     |aaaaaaa|     #   #   #
  #   #   #   #   +-+-------+-+   #   #   #
  #   #   #   #   |f|       |b|   #   #   #
  #   #   #   #===|f|       |b|===#   #   #
  #   #   #       |f|       |b|       #   #
  #   #   #       +-+-------+-+       #   #
  #   #   #=========|ggggggg|         #   #
  #   #           +-+-------+-+       #   #
  #   #           |e|       |c|       #   #
  #   #===========|e|       |c|=======#   #
  #               |e|       |c|           #
  #               +-+-------+-+ +---+     #
  #=================|ddddddd|   |ppp|=====#
                    +-------+   +---+

其中七个段和点中的每一个由不同的位控制。如果你想打开一个单独的段,而不想按原样保留其他段,你可以使用像你的问题包含的位操作。

举例来说,启用g细分包涉及以下操作:

mmap_byte |= 0x20; // binary 00100000

关闭它涉及:

mmap_byte &= 0xdf; // binary 11011111

您可能还会发现自己处于一个字节中的单个位或一组位控制完全不同的设备而您不希望其中一个位的操作影响另一个的情况。


关于位运算符的作用,它们是处理多位值的运算符,但概念上一次一位:

  • 仅当两个输入均为1时,AND才为1.
  • 如果其中一个或多个输入为1,则OR为1.
  • 只有当其中一个输入为1时,XOR才为1.
  • 仅当输入为0时,NOT才为1.
  • 左移位移位了一定量。
  • 右移将位移位一定量。

现在打折,这些可以最好地描述为真值表。输入可能性位于顶部和左侧,结果位是四个中的一个(在NOT的情况下为两个,因为它只有一个输入)值显示在两个输入的交叉点。

AND | 0 1     OR | 0 1     XOR | 0 1    NOT | 0 1
----+-----    ---+----     ----+----    ----+----
 0  | 0 0      0 | 0 1       0 | 0 1        | 1 0
 1  | 0 1      1 | 1 1       1 | 1 0

一个例子是如果你只想要一个整数的低4位,你和它15(二进制1111)所以:

    201: 1100 1001
AND  15: 0000 1111
------------------
 IS   9  0000 1001

另一个例子是,如果您要将两个4位值打包成8位值,则可以使用所有三个运算符(left-shiftand和{{1 }}):

or
  • packed_val = ((val1 & 15) << 4) | (val2 & 15) 操作将确保两个值只有较低的4位。
  • & 15是一个4位移位,用于将<< 4移动到8位值的前4位。
  • val1只是将这两者结合在一起。

如果|为7且val1为4:

val2

答案 1 :(得分:5)

  1. 如果您具有可以启用或禁用的功能,则可以使用单个位来设置状态:1或0.这样,在1个字节中,您可以设置8个功能。

  2. 如果你需要存储值(比如说从0到7),你只需要3位(二进制000,dec = 0,001 = 1,00 = 2,...,111 = 7)。你还有8位中的5位免费。

  3.   

    这里怎么了?有人请用0和1来图解说明

    1&lt;&lt; 2 = 00000001&lt;&lt; 2 = 00000100 = 4

    4&gt;&gt; 2 = 00000100&gt;&gt; 2 = 00000001 = 1

答案 2 :(得分:4)

我认为真正的问题不是“这到底意味着什么”,而是“为什么开发人员为微控制器编写程序似乎是喜欢这种方式?”

嗯,我不知道他们是否喜欢这样。我根本不是开发人员,我对微控制器有点兴趣,但这就是我的想法。

您有没有看过芯片的文档?以下是Atmel pdf的一个例子。

  

USART控制和状态寄存器 -   UCSRnB

     

•Bit 7 - RXCIEn:RX完成   中断使能n将此位写入   一个启用RXCn中断   旗。 USART接收完成   只有在中断时才会产生中断   RXCIEn位写入1,即   SREG中的全局中断标志是   写入一个和RXCn位   UCSRnA已设置。

     

•Bit 6 - TXCIEn:TX Complete   中断使能n将此位写入   一个启用TXCn中断   旗。 USART传输完成   只有在中断时才会产生中断   TXCIEn位写入1,即   SREG中的全局中断标志是   写入1和TXCn位   UCSRnA已设置。

     

...•第5位 - UDRIEn:......•第4位 -   RXENn:...•Bit 3 - TXENn:...•Bit   2 - UCSZn2:...•位1 - RXB8n:...•   位0 - TXB8n:...

问题是,通过设置或清除某些控制寄存器中的各个位来控制芯片。那些寄存器在文档中有丑陋的名字,而且这些名字也有丑陋的名字。开发环境已经包含了所有宏,其名称与定义的文档类似。

现在假设有人想要在RXCn标志上启用中断并保持所有其他设置不变。这需要在一个特定寄存器中设置一位,而其他位保持不变。运算符| =是最简单的方法

假设寄存器的地址是0x3F(我编了数字,没关系)可以写这个:

*((unsigned char*)0x3F) |= 0x80;// I want to set bit number 7 (RXCIEn)

现在,这有多可读?

在开发环境中,已经定义了像UCSRnBRXCIEn这样的宏,使用它们是显而易见的。只是碰巧RXCIEn是一个位数,而不是该位的值,所以编码与上面相同,必须编写

UCSRnB |= (1 << RXCIEn);

感谢文档

UCSRnB = (1<<RXENn)|(1<<TXENn);

被认为比

更具可读性
*((unsigned char*)0x3F) = 0x18; // I want to set bits number 4 and 3 (RXENn and TXENn)

文档本身充满了使用这些定义的宏的代码示例,我想开发人员已经习惯了这么快,试图找到更好的方法。

答案 3 :(得分:3)

在第一个例子中,您给出了:

SWITCH_DDR &= ~SWITCH_BIT;   
SWITCH_PORT |= SWITCH_BIT;

这不是一个bithift,而是一个位掩码。

具体来说,第一个语句关闭一个值中的给定位,第二个语句打开相同的位,保持所有其他位不变。

假设SWITCH_DDR和SWITCH_PORT是控制某些设备行为的特殊内存值。每个位都打开/关闭一个功能。如果要单独控制给定的功能,则必须能够在不干扰其他功能的情况下进行更改。 我们还要说SWITCH_BIT控制的特性是一个字节中最左边的位。所以SWITCH_BIT的值为0x80(二进制为10000000)。 当您执行第一个语句时,您将使用〜运算符(以二进制形式获取01111111)并将二进制AND应用于SWITCH_DDR来反转SWITCH_BIT。这有效地清除了最左边的位并使其他位保持不变。 第二个语句使用二进制OR,因此结果相反。

现在,关于轮班操作,有很多应用程序,(很多已经在另一个答案中提到过),我将简单解释你发布的代码中的具体用法。所以你有:

void SerialInit(void)
{
   UBRRH = ((XTAL / (8 * 250000)) - 1)>>8;   // 250kbps at 16Mhz
   UBRRL = (XTAL / (8 * 250000)) - 1;
   UCSRA = (1<<U2X);
   UCSRB = (1<<TXEN);
   UCSRC = (1<<URSEL) + (1<<UCSZ1) + (1<<UCSZ0);
}

这里的内容类似于上一个示例(设置值中的特定位),但由于这种情况存在一些差异:

a)虽然在第一个例子中你已经制作了位掩码(SWITCH_BIT),但这里的常量只是位的位置。例如,U2X包含需要打开/关闭(从右到左)的位的位置,而不是其位掩码。执行(1<<U2X)可以有效地生成相应的位掩码。如果需要打开的位是最左边的(正如我在SWITCH_BIT示例中所做的那样),U2X将为7,并且移位的结果将与二进制中的10000000相同。

b)在最后一行:

UCSRC = (1<<URSEL) + (1<<UCSZ1) + (1<<UCSZ0);

这是组合每个常量产生的位掩码(其值再次是右边的位位置)。问题是程序员决定使用+运算符而不是二进制OR,因为他/她知道当你没有“碰撞”位时,它们的结果是相同的。就个人而言,我总是使用二进制OR,以明确我正在做的不是算术加法。

c)最后,关于第一行

UBRRH = ((XTAL / (8 * 250000)) - 1)>>8;
似乎更复杂,显然是时钟分频因素,我必须先看一下文档才能更好地理解。我现在不担心,它可能会及时变得清晰。

答案 4 :(得分:2)

答案 5 :(得分:1)

一个位移操作符有两个主要类型(我不是在谈论方向):移位和旋转。

此外,两者都有两个方向,所以通常你有四个方向:

  • 左移位
  • 右移
  • 向左旋转
  • 向右旋转

前两个将位数移位到一个方向。任何“脱落”结束的位都会消失。在另一端“出现”的任何位都为零。

通常,您还可以指定移动值的位数。

所以:

1000 shl 1 = 0000 (the 1 fell off the end, and a 0 appeared on the other end)
1000 shr 1 = 0100 (a zero fell off the right end)

旋转不会丢失掉落的位,而是将它们旋转回另一侧。

1000 rol 1 = 0001 (the 1 was rotated back in on the other side)

您可以将这两项操作视为:

  • 对于移位,数字在两端包含无限数量的零,在移动时跟随值
  • 对于旋转,数字在两个方向上重复无限数,在移动时跟随值

还有一种旋转方式,通过进位旋转,在进程中使用进位标志作为额外位。

如果进位标志从0开始,则会发生以下情况:

1000 rcl 1 = 0000 (rcl = rotate through carry to left)
0000 rcl 1 = 0001 (now the 1 came back, it was temporarily stored in carry flag)

最后一个可以在机器代码中用于将单个位从一个寄存器移动到另一个寄存器:

rcl ax, 1    ; rotate AX-register, 16-bit, left 1 bit, through carry
rcr bx, 1    ; rotate BX-register, 16-bit, right 1 bit, through carry

这里我们从AX中取出最左边的位,暂时将其旋转到进位标志,然后将其旋转回BX的最左边位。

现在,您通常可以将移位与其他按位运算符组合使用。例如,要设置值的位N(其中N是从0开始,而位0是最右边的那个),您可以这样做:

value = value OR (1 shl N)

这里我们首先将值向左移动1 N次。如果N为0,则根本不会移位该位。

然后我们将该变换的结果与现有值进行OR运算并存储。 OR具有组合1的效果,因此如果任一值在特定位置具有1位,则结果在该位置也是1位。

所以换班:

1 shl 0 = 00000001 shl 0 = 00000001
1 shl 1 = 00000001 shl 0 = 00000010
1 shl 2 = 00000001 shl 0 = 00000100
1 shl 3 = 00000001 shl 0 = 00001000
1 shl 4 = 00000001 shl 0 = 00010000
1 shl 5 = 00000001 shl 0 = 00100000
1 shl 6 = 00000001 shl 0 = 01000000
1 shl 7 = 00000001 shl 0 = 10000000

然后是OR:

???????? OR 00100000 = ??1?????, where ? means whatever it was before

让我看一下您发布的几行代码:

UBRRH = ((XTAL / (8 * 250000)) - 1)>>8;   // 250kbps at 16Mhz
UBRRL = (XTAL / (8 * 250000)) - 1;

第一个进行计算,(XTAL / (8 * 250000)) - 1,我不知道背后的目的。然而,这是正常的数学,所以它计算了一些东西。我们称之为频率(根据评论判断。)

这个值计算两次,所以让我们重写上面的语句:

UBRRH = value >>8;   // 250kbps at 16Mhz
UBRRL = value;

我必须猜测,但我猜测UBRRH和UBRRL都被声明为“BYTE”类型,这意味着它们每个可以存储最多8位的值。这意味着代码实际上是这样的:

  1. UBRRH取“值”的高8位,将它们转换为低8位,然后存储它们。因为它只存储一个字节,所以它会切断其余部分,这意味着它会抓取8-15位
  2. UBRRL取低8位,其余部分砍掉,这意味着抓取0-7位
  3. 由于两者的名称以LH结尾,因此符合这一假设。

答案 6 :(得分:1)

按位运算符运算(顾名思义就是位)。在您给出的第一个代码块中,这些运算符用于设置和重置特定位。为简单起见,假设SWITCH_DDR是8位整数,SWITCH_BIT也是8位整数,其常量值为2:

SWITCH_DDR = 00000000;  // initial value of SWITCH_DDR is 0
SWITCH_BIT = 00000010;

然后,您可以使用按位OR将SWITCH_DDR的特定位设置为1:

SWITCH_DDR |= SWITCH_BIT; // SWITCH_DDR is 00000010 now

要验证SWITCH_BIT位是否已设置,请使用AND运算符:

TEMP = 10101010 & SWITCH_BIT; // TEMP is 00000010 now (1 in TEMP is set only if there's 1 in both operands)
if (TEMP == SWITCH_BIT) // The condition is true
{ /* Do something */ }
TEMP = SWITCH_DDR & SWITCH_BIT;  // TEMP is again 00000010 because we set it to 00000010 before and the AND operator doesn't therefore change anything
if (TEMP == SWITCH_BIT)  // The condition is also true
{ /* Do something */ }

要取消设置特定位,您可以使用:

TEMP = ~SWITCH_BIT;  // TEMP is now 11111101
SWITCH_DDR &= TEMP;  // This preserves everything (because 1 & 1 = 1 and 1 & 0 = 0) but the SWITCH_BIT bit which will be always set to 0 (anything & 0 = 0)

移位运算符只是向左或向右移位:

RESULT = 10010010 << 1;  // RESULT is 00100100
RESULT <<= 1;  // RESULT is 01001000
RESULT <<= 2;  // RESULT is 00100000
RESULT >>= 1;  // RESULT is 00010000

移位运算符有一个特殊之处 - 您可以使用它们进行快速除法/乘法,功率为2:

RESULT = 3 << 1;  // Result is 6 (3 * 2)
RESULT = 5 << 2;  // Result is 20 (5 * 4)
RESULT = 1 << 7;  // Result is 128 (1 * 128)
RESULT = 36 >> 1; // Result is 18 (36 / 2)
RESULT = 35 >> 1; // Result is 17 (35 / 2)

答案 7 :(得分:0)

值得注意的是,顺便说一句,在许多小型处理器上都有专门的指令来设置和清除I / O端口的各个位。我建议你找一些关于你将要使用的处理器的东西,因为最佳的编码风格各不相同。例如,考虑语句组:

  some_port |= 8;  /* Statement #1a -- Note value is a power of 2 */
  some_port |= 2;  /* Statement #1b -- Note value is a power of 2 */

  some_port |= 9;  /* Statement #2 -- Note value is not a power of 2 */

在某些处理器(例如ARM或Z80)上,如果some_port被声明为volatile(它应该是),则每个语句将是三条指令,因此第一个序列将占用第一个序列的两倍时间和空间。在其他一些(例如PIC)上,前两个语句将是每个一个指令,而第二个将是两个指令,因此两个序列将占用相同的时间和空间。还有一些人会为另一个人提供空间优势,并为另一个人带来时间优势。

Time/space for 1st and 2nd methods of setting bits
(time in cycles unless noted)
1st  2nd
2/4  2/3  8051 (standard)
4/4  3/3  8051 (accelerated clone)
2/2  2/2  PIC
10/4 8/6  6805