I've often seen cli()
and sbi()
in Arduino code. I usually don't mind them as I know what they do (clear or set the bit given as second argument in the register of the microcontroller given as first). I've always thought that those functions where only a clear way to perform simple bit manipulation, which can be quite error-prone. Something like that:
#define cli(reg,bit) (*reg &= ~(1 << bit)
and #define sbi(reg,bit) (*reg |= (1 << bit))
.
Then I found out that the actual implementation is as follows:
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
It's quite like I used to think... but with two extra macros. I looked for them in the Arduino libraries:
#define _BV(bit) (1 << (bit))
and
#define _SFR_BYTE(sfr) _MMIO_BYTE(_SFR_ADDR(sfr))
which contains
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
and
#if _SFR_ASM_COMPAT
#if (__SFR_OFFSET == 0x20)
#define _SFR_ADDR(sfr) _SFR_MEM_ADDR(sfr)
#elif !defined(__ASSEMBLER__)
#define _SFR_ADDR(sfr) (_SFR_IO_REG_P(sfr) ? (_SFR_IO_ADDR(sfr) + 0x20) : _SFR_MEM_ADDR(sfr))
#endif
#else /* !_SFR_ASM_COMPAT */
#define _SFR_ADDR(sfr) _SFR_MEM_ADDR(sfr)
Further research took me to definitions I couldn't understand (for example because they are explained in comments using a lot of acronyms I don't know). So I have the following questions:
- Why is
sfr
casted to*(volatile uint8_t *)
? - What do the acronyms used in that code mean? (
ASM
,SFR
,MMIO
) - Under what circumstances is
__ASSEMBLER__
defined, and where (in which file)? - Why can't we simply write
#define cli(reg,bit) (*reg &= ~(1 << bit)
and#define sbi(reg,bit) (*reg |= (1 << bit))
?