看一下你的SerialInit函数。
虽然你没有提供很多细节,但仍然足够理解。显然你正在初始化串口,前几行是时钟分频器。由于芯片可以以不同的时钟运行,而且芯片通常不会猜测您想要的时钟,比如9600波特率,因此您必须进行数学计算,将12mhz参考时钟降低到250000,采用8倍过采样。我猜那个数字是0x200,但为了举例,假设该数字是0x234。 UBBRH寄存器似乎是高半部分寄存器,因此需要上位字节,显然是上8位(或更少),UBBRL是该除数的较低半部分,因此需要较低的8位。
0x0234的上8位是0x02,要从0x0234得到0x02,您需要进行移位。
0x0234 = 0b0000001000110100(其中0b表示二进制,而0x表示十六进制)
这不是旋转,而是逻辑移位,C语言中没有旋转(也没有算术移位)。这意味着我们向右移动的位数最终会落在一个位桶中。
因此,0b0000001000110100向右移动8次变为0bxxxxxxxx00000010。XX是新移入的位,在C中实际上为零。但这里并不重要,因为要写入的寄存器是8位的,因此只有较低的8位有效。
因此,在这两行代码之间,我们计算了串行时钟的除数0x234,并将0x02写入上除数寄存器,将0x34写入下除数寄存器。
接下来的一行UCSRA = (1 << U2X);
显然有一个寄存器需要设置单个位。我不知道U2X是什么,但假设它是5。1 << 5表示取0x01或0b00000001。向左移动五次意味着将存在的位向左移动,在C中在右侧带入5个零。变量顶部的位、最左侧的5位掉落到位桶中。
所以
0b00000001
添加五个零以可视化
0b0000000100000
然后从左边削掉五个
0b00100000
我们最终得到0x20。
UCSRB行的工作方式相同。
UCSRC行也是相同的,但是设置了三个位。
UCSRC = (1 << URSEL) | (1 << UCSZ1) | (1 << UCSZ0);
这里是一个关于IT技术的例子。在你提供的代码中,URSEL、UCSZ1和UCSZ0都没有被定义,所以我将使用一些数字来填充这些未定义的部分。
UCSRC = (1 << 5) | (1 << 2) | (1 << 4);
我们像处理UCSRA和UCSRB一样,对这些数字进行可视化处理。
将五个零左移
0b100000
在左侧添加零,使其成为完整的8位数
0b00100000
一个有两个零,一个有四个零
0b100加上填充后是0b00000100
0b10000加上填充后是0b00010000
到目前为止,三个组件分别是:
0b00100000
0b00000100
0b00010000
当它们相加时,结果为
0b00110100 = 0x34
这个值就是写入寄存器的值。
现在你必须小心使用加法而不是或运算。如果你没有注意到你的定义或者没有仔细查看,你可能会发现同一个位被两个名称定义,而你可能不知道它是相同的位,加法会搞砸它,或运算则不会。例如,如果URSEL和UCSZ1恰好是相同的位
UCSRC = (1 << URSEL) + (1 << UCSZ1) + (1 << UCSZ0);
UCSRC = (1 << 5) + (1 << 5) + (1 << 4);
你会得到
0b00100000
0b00100000
0b00010000
这些数字相加得到
0b01010000
而你可能希望将它们进行或运算并得到
0b00110000
还有其他情况下,或运算是不好的,你需要使用加法,所以在进行这种计算时,你必须了解你的数字而不仅仅是定义的名称。
通常为什么要进行这种位移的形式,特别是在微控制器和驱动程序中,是因为设备中的寄存器可能定义了多个东西。串口就是一个完美的例子,假设你有一个控制寄存器,它看起来像这样:
0b0SPPLLLL
其中SS是停止位,1=2个停止位,0=1个停止位
PP是奇偶校验,0b00 = 无奇偶校验,0b01 = 偶校验,0b10 = 奇校验
LLLL是长度,0b1000 = 8,0b0111 = 7位等等
你经常会发现这样的寄存器代码:
SCONTROL = (0 << 7)|(2 << 4)|(8 << 0);
除了硬编码数字被替换为定义外:
SCONTROL = (ONESTOPBIT << STOPBITS)|(NOPARITY << PARITYBITS)|(DATABITS8 << DATABITS);
移位运算允许程序员独立考虑每个字段,而不使用混乱、错误和非常糟糕(永远不要使用)的位字段。
按位与非是一种不必处理变量长度的简单方法
SWITCH_DDR &= ~SWITCH_BIT;
SWITCH_PORT |= SWITCH_BIT;
因此,如果您想读取-修改-写入某些内容,并且说低3位是要修改的内容,而不想弄乱寄存器中的其他位,您可能会做以下操作:
ra = SOMEREGISTER;
ra&=~7;
ra|=newnumber&7
SOMEREGISTER = ra;
ra&=~7 的意思是
从
0x0000....0000111 开始
取补码,即将零变成一,将一变成零
0x1111....1111000
与 ra 中原有的值进行按位与运算,与 1 进行按位与运算表示保留原有的值,与 0 进行按位与运算则表示清零,因此,低三位被强制为 0,其他无关紧要的位不变。然后,或入新数字以将低三位设置为您想要更改的内容,然后写回到寄存器中已更改的值。
然后,使用反转掩码的 and 操作可能比自己进行数学计算更容易, ~7 而不是 0xFFFF...FF8。它的帮助在于您可能只需要一个定义:
#define SOMEMASK 0x7
然后使用
ra&=~SOMEMASK;
ra|=newvalue&SOMEMASK.
您甚至可以更聪明地说:
您甚至不必考虑 0x7 是三个 1。您只需在其中放置一个 3 即可。
|
代表按位或,&
代表按位与,<<
和>>
是位移运算符。只要你理解二进制数字,这就是你需要知道的全部。 - tdammers