有人可以解释一下用来设置计时器的奇怪代码吗?


10

在看别人写的草图时,我偶尔会遇到一些看起来像这样的代码:

TCCR1A = 0;
TCCR1B = 0;

TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);

我所知道的是与计时/计时器有关(我认为)。我该如何解密和创建这样的代码?什么是TCCR1ATCCR1BTCNT1CS12TIMSK1,和TOIE1



1
Atmel网站下载适用于您设备的“完整”数据表,并阅读有关计时器的章节。我认为该数据表非常好阅读。
jippie 2014年

Answers:


15

这看起来并不奇怪。这就是普通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);设置位TOIE1TIMSK1。这是通过将二进制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);,因为它会取消该位TOIE1TIMSK1。这是通过以下步骤实现的:获取由产生的位掩码_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是控制PORTCATmega328P 内输出引脚值的寄存器。PINC输入PORTC可用的寄存器。从根本上说,当您使用digitalWriteor digitalRead函数时,此类事情就是在内部发生的。但是,有一个查找操作可将arduino的“引脚号”转换为实际的硬件引脚号,这大约需要50个时钟周期。您可能会猜到,如果您想加快速度,那么在只需要1个操作的操作上浪费50个时钟周期就有点荒谬了。

上述功能可能需要100-200个时钟周期的范围内的某个位置来传输8位。这需要24个引脚写入和8个读取。这比使用这些digital{stuff}功能快很多很多倍。


请注意,此代码还应与Atmega32u4(在Leonardo中使用)一起使用,因为它包含比ATmega328P更多的计时器。
jfpoilpret 2014年

1
@Ricardo-大约90%以上的小型MCU嵌入式代码使用直接寄存器操纵。使用间接实用程序功能执行操作不是通用的IO /外围设备操作模式。有一些用于抽象化硬件控制的工具包(例如Atmel ASF),但通常是为了尽可能减少编译时间而编写的,以减少运行时的开销,并且几乎总是需要通过阅读数据表来真正理解外围设备。
康纳·沃尔夫

1
基本上,arduino的东西,通常说“这里是执行X的函数”,却没有真正去参考实际的文档或硬件如何执行其工作,这是很不正常的。我了解它作为入门工具的价值,但是除了快速制作原型之外,在实际的专业环境中从来没有真正做到过。
康纳·沃尔夫

1
需要明确的是,使arduino代码对于嵌入式MCU固件不常见的原因并非arduino代码独有,它是整个方法的功能。基本上,一旦您对实际的 MCU 有相当的了解,正确地做事(例如直接使用硬件寄存器)就几乎不需要额外的时间。因此,如果您想学习真正的MCU开发人员,那就最好坐下来了解您的MCU 实际在做什么,而不要依赖于别人的抽象,而这种抽象往往很容易泄漏。
康纳·沃尔夫,2014年

1
请注意,我在这里可能有点愤世嫉俗,但是我在arduino社区中看到的许多行为都是在编写反模式。我看到了很多“复制粘贴”程序,将库视为黑盒子,并且在整个社区中只是普遍的不良设计实践。当然,我在EE.stackexchange上相当活跃,所以我可能有一个倾斜的观点,因为我有一些主持人工具,因此看到了很多封闭的问题。我在那儿看到的arduino问题中肯定有一个偏向“告诉我要C&P解决的问题”,而不是“为什么这不起作用”。
康纳·沃尔夫

3

TCCR1A 是定时器/计数器1控制寄存器A

TCCR1B 是定时器/计数器1控制寄存器B

TCNT1 是计时器/计数器1的计数器值

CS12 是定时器/计数器1的第三个时钟选择位

TIMSK1 是定时器/计数器1的中断屏蔽寄存器

TOIE1 是定时器/计数器1溢出中断使能

因此,该代码启用62.5 kHz的定时器/计数器1并将其值设置为34286。然后启用溢出中断,因此当其达到65535时,它将触发中断功能,最有可能标记为 ISR(timer0_overflow_vect)


1

CS12代表TCCR1B寄存器的位2,因此其值为2。

(1 << CS12)取值1(0b00000001)并将其左移2次以获得(0b00000100)。操作顺序指示()中的事情首先发生,因此在对“ | =”求值之前,先完成操作。

(1 << CS10)取值1(0b00000001)并将其左移0次以获得(0b00000001)。操作顺序指示()中的事情首先发生,因此在对“ | =”求值之前,先完成操作。

因此,现在我们得到TCCR1B | = 0b00000101,与TCCR1B = TCCR1B |相同。0b00000101。

由于“ |” 如果为“ OR”,则TCCR1B中除CS12以外的所有其他位均不受影响。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.