I would also describe this as elegant, but would like to add the the problem, if you will forgive my intrusion.
I know there are very pricey software packages to work through situations like this, but at the company I work at we cannot afford the cost unless we are sure it does what we need.
Test Driven Development(TDD) is one of the better systems I have heard of for development, and I enjoy it, but the problems that take up my time are normally caused by complex interrupt and hardware events that many would call glitches. It seems like a minor thing to have a problem every 2 hours when the stars align, but if your phone just froze once a week,you would curse the engineers name. In our case, we have to trek into a feed lot when things really break, which, as you can imagine, I like to avoid.
I have seen very intelligent solutions for checking functionality of subsystems, which, if implemented properly, would probably save me 3 hours out of a 50 hour work week, but if there was an intelligent way to find glitch situations it would save me weeks of work looking for the "bug" that happens in the field occasionally under heavy load.
This post probably does not help a large amount, but I find bringing everything into the light makes everything easier to resolve. If there was a TDD method for finding glitch situations, I could get 10s of thousands allocated to pay for it.
-Max
The AVR, like most microcontrollers, uses memory-mapped IO. In a nutshell, this means that a part of the memory space of the microcontroller is reserved for the peripherals. When you change a bit in this area of memory, you're not sending a signal to change a bit of memory, you're sending a signal to change the value of a peripheral.
To understand the details of how this works on your AVR, the Special Function Registers section of AVR-libc (and, more importantly, sfr_defs.h and iom328p.h) are recommended reading.
[Warning: Technical writing ahead, pay close attention] For example, PORTB
is defined as _SFR_IO8(0x05)
in iom328p.h
. In sfr_defs.h
, _SFR_IO8(io_addr)
is defined as _MMIO_BYTE((io_addr) + __SFR_OFFSET)
. __SFR_OFFSET
is 0x20
, the location just after the registers. The space after this address and before RAMSTART (0x100)
is used for the peripherals. Going back to our example, _MMIO_BYTE(mem_addr)
is defined as (*(volatile uint8_t *)(mem_addr))
. Therefore, when you write PORTB
, you're really writing *(volatile uint8_t *)(0x25)
, which defines a byte-wide pointer to the location in memory 0x25
. [/Warning]
So, when (in C) you write PORTB |= 0x01, you're really writing the value 1 to the peripheral at byte 0x25, or port B pin 0. If this pin is configured as output (using DDRB, which is at 0x24), PORTB |= 0x01 will cause port B pin 0 to go high. Phew!
So, if you use the assembly instructions (SBI - Set Bit in I/O Register):
sbi 0x24,1 ;Data direction set to output
sbi 0x25,1 ;Set port B pin 0 high
You'll set the pin high.
Of course, use inline assembler with GCC and #include <avr/io.h>
to get the conventional names and simplify your task.
Note that all of the peripherals use this scheme, not just the IO pins. Read iom328p.h if you're interested in the locations of other peripherals.
Best Answer
Refer to the AVR Instruction Set document.
For the least significant byte you want a
LSL – Logical Shift Left
. Shift a0
in as least significant bit and remember the highest bit in the Carry flag.Then for the subsequent higher bytes, you want a
ROL – Rotate Left trough Carry
. You shift the remembered bit from carry into the right (least significant) bit and the bit that is shifted out at the high side is pushed into carry.You chose to use R18 as LSB and R21 as MSB, nothing wrong with that.
Read the above image from top right (LSB is being processed first) to bottom left.