3

前言

众所周知,对于原子和同时读取/写入 16 位 I/O 寄存器(定时器计数器、ICR/OCR、ADC...)的高位和低位部分,AVR 使用影子临时寄存器。例如TCNT1在 ATmega8 上阅读:

uint8_t tl, th;
tl = TCNT1L;   // tl <- TCNT1L, avr_temp <- TCNT1H (atomic)
th = TCNT1H;   // th <- avr_temp

(这里avr_temp是 AVR 临时影子寄存器)。因此,TCNT1H例如,首先阅读是错误的。

问题

将 AVR-GCC 与如下代码一起使用是否安全?

uint16_t ticks;
ticks = TCNT1;
TCNT1 = 0x1234;

AVR-GCC 是否总是为这些操作生成正确的代码?

(似乎是“否”(GCC怎么知道访问指向的内存TCNT1使用AVR影子寄存器?),但是avr-libc定义宏TCNT1以及TCNT1H、TCNT1L和avr- libc'FAQ建议直接使用TCNT1。我我很困惑。)

我测试了 AVR-GCC v4.7.2,它似乎总是生成正确的代码。即使我写了 'TCNT1 |= 1' 它也会产生正确的代码-O3

$ avr-gcc -std=c99 -mmcu=atmega8 -S -O3 -o - 1.c
...
in r24,0x2c     // TCNT1L
in r25,0x2c+1   // TCNT1H
ori r24,1
out 0x2c+1,r25
out 0x2c,r24
...

即使我TCNT1用普通的 16 位变量进行更改,代码也是一样的。那么,“GCC 怎么知道访问指向的内存TCNT1使用 AVR 影子寄存器?” -- 默认情况下,似乎在访问任何16 位变量时总是假设影子寄存器。

4

2 回答 2

3

我不知道它是怎么知道的,但我已经TCNT1在几十个程序中直接使用过,没有问题。您链接的常见问题解答建议您这样做,就像我读过的每个教程一样。

常见问题解答的重点是确保在两个寄存器的写入之间不会发生中断。尽管 avr-gcc 会生成以正确顺序访问它们的代码,但它不能保证它们之间不会发生中断:您必须注意这一点。

于 2013-01-05T13:17:46.467 回答
1

如果您阅读例如 /usr/lib/avr/include/avr/iom32u4.h 的源代码,您会看到:

#define TCNT1 _SFR_MEM16(0x84)

在 /usr/lib/avr/include/avr/sfr_defs.h 中扩展 _SFR_MEM16 宏,您会看到:

#define _SFR_MEM16(mem_addr) _MMIO_WORD(mem_addr)

扩展_MMIO_WORD:

#define _MMIO_WORD(mem_addr) (*(volatile uint16_t *)(mem_addr))

然后查看stdint.h: typedef unsigned int uint16_t;

这里是编译器介入的地方。AVR平台上的int类型是16位,而AVR上的寄存器是8个,所以编译器必须进行2次内存访问(没有为它定义16位内存访问指令)利用)。根据此处的文档:

编译器将根据被访问的寄存器地址选择正确的指令序列来生成。


顺便说一句,我最初认为它是可变属性,但事实证明它是通过芯片的内存映射嵌入到 AVR 架构中的。

于 2017-12-12T17:56:55.947 回答