AVR Optimization: Register-Level Coding of the ATtiny84 (Preparations for the Dolgin Development Platform) |
Your exceptional CHUMP journey has prepared you for your next privilege: the deepest possible drilling into the hardware and software of modern microcontroller technology. The carefully designed sequence of mid-level coding exercises below, exploiting familiar ACES' optical devices, is the final preparation for Session 5: Pure Assembly Language Programming of the AVR ATtiny84 on the Dolgin Development Platform (DDP), in 2021.
Although these C coding exercises can be considered high-level, they call for a deeper understanding of the underlying architecture of the AVR family of MCUs, in particular, the ATtiny84. Hence, I refer to this coding as mid-level. The magic behind function such as pinMode(), digitalWrite(), digitalRead(), and shiftOut() for example, are exposed and rewritten, in some cases, more efficiently. Direct port bit manipulation of the MCUs' General Purpose Input/Output (GPIO) Registers are called for to achieve improved performance. Conveniently, your CHUMP experience has given you the necessary insights that were lacking at the end of Grade 11 to guide these next steps.
The graphic to the right is the general layout of SRAM on the ATtiny84. Compare that to the SRAM of the ATmega328P. Similar.
The exercises on this page are to be undertaken on a breadboard ATtiny84 hardware platform you are required to construct (see below). Although this tutorial from MIT's 2014 High-Low Tech group on the virtues of the AVR tiny family is showing its age, it is worth reviewing, as a starter.
The AVR ATtiny84
The pinout diagram below will facilitate your build. The correlation between hardware pin numbers and the software pin numbers is highlighted in Brown.
Finally, familiarize yourself with these C header files that are #included, by default, into the Arduino IDE's Toolchain, based on the target Board you select.
ATtiny84 GPIO Register Reference
ATtiny84 Register-Level Exercises
You may wish to create a folder entitled RegisterLevelCoding or something along those lines, to house this unique set of sketches I am asking you to develop.
1. General Purpose Input/Output (GPIO) Tests.
After days of preparation, I have settled on the Task below as your first experience with mid (or register) level coding.
Task.
3. Mid-Level ATtiny84 Makeover of the Classic I/O Functions
This is a large pool we're wading into, so let's start in the shallow end. Some of the first Arduino functions you were introduced to were pinMode(), digitalWrite(), digitalRead(), and shiftOut(). Those simple blinking LEDs provided early, but important, confirmation that your software and hardware efforts were meshing, and on the right track. The only problem was that there was too much 'behind the curtain' magic that, over a year later, we need to explore in order to optimize our future engineering objectives.
Task.
4. First Look at Custom Libraries
You've employed a number of libraries over the past year or so, from the Arduino Core libraries (automatically included in every project) to third party libraries (through the explicit use of the #include directive) that abstract the functionality of complex devices and tasks through convenient object instantiation and subsequent member function calls. Well, it's time to construct YOUR first custom library, containing the ATtiny84 functions from these practice exercises. In doing so, future projects can reuse this code to yield the desired outcomes in less time, and with improved confidence.
Task 1. Separation of Code. First, identify code that is reusable and confirm that it can exist in a separate file.
Task 2. Centralized Access to Common Code. Now we can place the reusable code in a folder that is accessible to future projects!
These are some of the familiar ACES optical devices we'll apply our nascent mid-level coding practices to ...
Your Session 4 loot bag includes two 3mm bicolor LEDs (red/green). Place its two leads in PA6-7, with the longer lead in PA6.
Create the project BicolorAlternation that employs direct, register-level GPIO Port manipulation resulting in red/green alternate flashing every half second.
6. PlayByte. 5mm Red/Blue Bicolor LED.
Your Session 4 loot bag also includes a 5mm bicolor LED (red/blue). Place its three leads in PA0-2, with the second longest lead in PA0.
Create the project PlayByte that employs direct, register-level GPIO Port manipulation resulting in a continuous, 8-frame animated sequence that 'plays' a byte in an LSBFIRST order. The code is to be Red for 0 and Blue for 1. Each colour is to be held for 1 second, with no delay or gap in between. At the end of the 8-bit colour sequence, turn the LEDs off for 3 seconds before repeating, indefinitely.
Example. The image (above right) reflects the playing of B10011101 (or 0x9D or 0235 or 157).
7. ASCII Alphabet on a 7-Segment Display.
Your ICS3U experience introduced you to the concept of a segment map (array) for an ASCII letter lookup table (LuT). The code can be found at AVRFoundations: Write7SegUpperCaseCharacters.ino.
Obtain the array code for the segment map and drop it into a new project entitled ASCIIAlphabet. Develop register-level code that makes optimum use and efficiency of the ATtiny84's ports to continuously cycle through the LuT, displaying the letter representation on your 7-segment display device.
8. 10-LED Bargraph.
(The ATtiny84 is just that, tiny, so this exercise may push the limits of the MCUs GPIOs). Create the project BargraphAnimation that uses a 1:1 GPIO port pin to LED mapping to duplicate the animation depicted in the image to the right. Strive to make it as efficient as possible.
9. Register-Level 74HC595 Shiftout (Morland Bargraph).
The previous exercise left little GPIO room for anything else. We need to drive a 10-LED bargraph with fewer port pins, so we return to the 74HC595 shift register. However, in this iteration, we forego the use of the core shiftOut() function in favour of our own register-level bit manipulation strategy.
First, review the primer below on the 75HC595.
Create the project ShiftOutAnimation that duplicates the result of the previous exercise but only requires three ATtiny84 GPIO pins, as opposed to ten.
10. 64-LED 2D Matrix.
11. ACES' CharlieStick. TBC.
12. ACES CharlieMatrix. TBC.
A shift register is a device typically used to expand the number of pins of a microcontroller. The most common design by far is based on the 74HC595 architecture. Using (as few as) three pins of the MCU (coloured) you are able to control 8 (or more) data lines (QA..QH). In addition to the three pins that control shifting, two other pins (Output Enable and Master Reset) provide additional (Active Low) control over the state of the 8-stage internal register set and output latches.
The IC accepts bits serially and presents them on output pins in parallel.
Two sets of naming conventions for pins tend to confuse those new to the IC. The image below tries to address this issue.
2. Waveform Timing Details. The digital orchestration of serial input bit stream is summarized in a waveform or timing diagram. A more detailed explanation of the mechanics of Serial to Parallel Shifting-Out with the 74HC595 can be found by following the link to the Arduino tutorial. Let's attempt to decipher the timing diagram, extracted from the 74HC595 datasheet,
As you are well aware, the immediate benefit of the Arduino shiftOut function is to hide the details of the digital dance allowing the higher-level programmer to concentrate on more macro concepts. However, hiding details always comes at a cost; if not performance then, at the least, in understanding. Our goal is to solidify our lower-level coding skills through direct handling of the signals on the three control pins, thereby bringing you closer to the AVR and IC hardware. This brings all sorts of future dividends.
3. Prototyping Platform. Insert your Morland Bargraph into the DDP in the most feature-suitable position (supply and PWM) as shown to the right.
4. Register-Level shiftout. With the signals and waveforms of the 74HC595's timing diagram understood, we can now tackle the low-level responsibilities of the waveform ourselves through direct register manipulation. Remember, our goal is to enlighten, not suggest this is the preferred alternative in all cases.
shiftOut
header function. We'll use a purely lowercase shiftout
to avoid compiler confusion,
// LSBFIRST:0 MSBFIRST:1Complete the body of the function, hard-coding the port manipulation for this platform.
void shiftout(uint8_t order, uint8_t value)
loop()
function that exercises your shiftout() function to display an interesting pattern on the bargraph.