在看别人写的草图时,我偶尔会遇到一些看起来像这样的代码:
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);
我所知道的是与计时/计时器有关(我认为)。我该如何解密和创建这样的代码?什么是TCCR1A
,TCCR1B
,TCNT1
,CS12
,TIMSK1
,和TOIE1
?
在看别人写的草图时,我偶尔会遇到一些看起来像这样的代码:
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);
我所知道的是与计时/计时器有关(我认为)。我该如何解密和创建这样的代码?什么是TCCR1A
,TCCR1B
,TCNT1
,CS12
,TIMSK1
,和TOIE1
?
Answers:
这看起来并不奇怪。这就是普通MCU代码的外观。
您这里拥有的是内存映射外设概念的示例。基本上,MCU硬件在分配给它的MCU的SRAM地址空间中具有特殊的位置。如果写入这些地址,则写入地址n的字节的位将控制外设m的行为。
基本上,某些存储区从SRAM单元到硬件几乎没有连线。如果您向该字节中的该位写入“ 1”,则会将该SRAM单元设置为逻辑高电平,然后打开硬件的某些部分。
如果您查看MCU的标头,则有很大的关键字<->地址映射表。这是TCCR1B
在编译时解决诸如...之类的问题的方式。
这种内存映射机制在MCU中非常广泛地使用。arduino中的ATmega MCU,PIC,ARM,MSP430,STM32和STM8 MCU系列以及许多我不立即熟悉的MCU都使用它。
Arduino代码很奇怪,它的功能可以间接访问MCU控制寄存器。尽管看上去有些“更”,但它也慢得多,并且使用了更多的程序空间。
在ATmega328P数据表中,所有神秘的常量都进行了详细描述,如果您有兴趣进行其他操作,而又不时地切换arduino上的引脚,则应该阅读该常量。
从上面链接的数据表中选择节选:
因此,例如,TIMSK1 |= (1 << TOIE1);
设置位TOIE1
在TIMSK1
。这是通过将二进制1(0b00000001
)向左移一位TOIE1
,并TOIE1
在头文件中将其定义为0来实现的。然后将其按位或为当前值TIMSK1
,这实际上将该位设置为高。
查看文档的第0位TIMSK1
,我们可以看到它被描述为
当该位被写为1,并且状态寄存器中的I标志被置1(全局允许中断)时,定时器/计数器1溢出中断被使能。设置TIFR1中的TOV1标志时,将执行相应的中断向量(请参见第57页上的“中断”)。
所有其他行应以相同的方式解释。
一些注意事项:
您可能还会看到类似的信息TIMSK1 |= _BV(TOIE1);
。_BV()
是最初来自AVR libc实现的常用宏。在功能上与相同,具有更好的可读性。_BV(TOIE1)
(1 << TOIE1)
另外,您可能还会看到诸如:TIMSK1 &= ~(1 << TOIE1);
或的行TIMSK1 &= ~_BV(TOIE1);
。这有相反的功能TIMSK1 |= _BV(TOIE1);
,因为它会取消该位TOIE1
中TIMSK1
。这是通过以下步骤实现的:获取由产生的位掩码_BV(TOIE1)
,对其进行按位NOT运算(~
),然后TIMSK1
按此NOTed值(即0b11111110)进行ANDing操作。
请注意,在所有这些情况下,诸如(1 << TOIE1)
或之类的东西的值_BV(TOIE1)
都在编译时解决,因此它们在功能上会减小为一个简单的常量,因此在运行时无需花费执行时间即可进行计算。
正确编写的代码通常会在代码内联注释,其中详细说明了分配给寄存器的功能。这是我最近编写的一个相当简单的soft-SPI例程:
uint8_t transactByteADC(uint8_t outByte)
{
// Transfers one byte to the ADC, and receives one byte at the same time
// does nothing with the chip-select
// MSB first, data clocked on the rising edge
uint8_t loopCnt;
uint8_t retDat = 0;
for (loopCnt = 0; loopCnt < 8; loopCnt++)
{
if (outByte & 0x80) // if current bit is high
PORTC |= _BV(ADC_MOSI); // set data line
else
PORTC &= ~(_BV(ADC_MOSI)); // else unset it
outByte <<= 1; // and shift the output data over for the next iteration
retDat <<= 1; // shift over the data read back
PORTC |= _BV(ADC_SCK); // Set the clock high
if (PINC & _BV(ADC_MISO)) // sample the input line
retDat |= 0x01; // and set the bit in the retval if the input is high
PORTC &= ~(_BV(ADC_SCK)); // set clock low
}
return retDat;
}
PORTC
是控制PORTC
ATmega328P 内输出引脚值的寄存器。PINC
是输入值PORTC
可用的寄存器。从根本上说,当您使用digitalWrite
or digitalRead
函数时,此类事情就是在内部发生的。但是,有一个查找操作可将arduino的“引脚号”转换为实际的硬件引脚号,这大约需要50个时钟周期。您可能会猜到,如果您想加快速度,那么在只需要1个操作的操作上浪费50个时钟周期就有点荒谬了。
上述功能可能需要100-200个时钟周期的范围内的某个位置来传输8位。这需要24个引脚写入和8个读取。这比使用这些digital{stuff}
功能快很多很多倍。
CS12代表TCCR1B寄存器的位2,因此其值为2。
(1 << CS12)取值1(0b00000001)并将其左移2次以获得(0b00000100)。操作顺序指示()中的事情首先发生,因此在对“ | =”求值之前,先完成操作。
(1 << CS10)取值1(0b00000001)并将其左移0次以获得(0b00000001)。操作顺序指示()中的事情首先发生,因此在对“ | =”求值之前,先完成操作。
因此,现在我们得到TCCR1B | = 0b00000101,与TCCR1B = TCCR1B |相同。0b00000101。
由于“ |” 如果为“ OR”,则TCCR1B中除CS12以外的所有其他位均不受影响。