Arduino C: Towards Better Code (originally developed in April 2021. Last Update: 2022 09 25) |
You will recall that our first course placed a premium on the BUILD quality of your breadboard prototypes, as G. Davidge's (ACES '24) example to the right reflects. Yes, quality takes time, planning, and care but the manintenance dividends are substantial. This expectation of QUALITY continues in your hardware pursuits and expands to include ACES' other two preferred domains introduced in our second and third courses: software and design.
Software and hardware form the backbone of modern telecommunications. As you begin your second course within RSGC's ACES program it is your teacher's intention to place you on the best footing from the outset to ensure your achievements are optimized for performance and to develop habits that will stick with you for life, no matter where it leads. To do so, requires tradeoffs at every stage of growth and development, as your skills mature. This is a familiar pattern that will repeat itself for as long as you pursue a craft of any sort in that, the farther you travel down a road, the more you understand, inviting you to regularly reach for the Reset switch.
The motivational brilliance behind the development of the Arduino Platform included providing curious minds with simple, seamless access to the power of electronics based on the (AVR) microcontroller. To flatten the learning curve, the developers had to, necessarily, hide many of the complexities that would have made the curve too steep for beginners. ICS3U-E ACES are no longer beginners and aiming their futures towards undergraduate engineering programs. It is time to draw back the curtains that hide many of the Arduino's critical details, thereby opening their own doors to efficiency and optimization of the platform.
With months of code development ahead of you will eventually come to appreciate the areas of concentration below, many of which were prompted by reading the code ACES that have gone before you have included in their DERs or were highlighted in their videos.
0. Orientation/Configuration of your Arduino Software 'Integrated Development Environment' (IDE)
There are many tools to develop software for your Arduino (AVR MCUs) but their basic IDE is the best utility for ACES to start with as it offers realtively easy access to many fundamentals resources that combine to produce and flash executable code. To maximize and optimize your Arduino IDE development cycle there are a few features that we need to consider, once your IDE is installed.
1. The Arduino IDE's Verify and Upload Buttons
while (!Serial.available()) { ch = Serial.read(); if (ch == 'A') nextChar = 'B'; if (ch == 'B') nextChar = 'C'; if (ch == 'C') nextChar = 'D'; if (ch == 'D') nextChar = 'E'; if (ch == 'E') nextChar = 'F'; if (ch != 'A') nextChar = '?' }Here is the corrected version of the above if..else sequence. An even better way is to forget the if..else ladder and employ a switch..case statement.
3. Functions. See here...
A common code requirement of many UIs that deserves some attention is the menu. Presentation and response-handling are two of a number of considerations we'll explore in this segment. To keep things simple we'll employ a character-based approach using the Serial Monitor. Graphic adaptations for LCDs, and OLEDs are left to your own efforts.
When it comes to iteration, limit their implementations to one of two alternatives: the (0..many) conditional while() loop and the 'unconditional' for() loop. There is a seldom-used useful third option for 1..many situations: do..while() that is ideal for this situation.
`¬(PvvQ)⟺(¬P)^^(¬Q) and ¬(P^^Q)⟺(¬ P)vv(¬Q)`
where,
5. Data Memory Options and Considerations
The 8-bit AVR family of MCUs offer three onboard memories: Program Flash, SRAM, and EEPROM. Whereas the sizes of these memories vary within the families, their nature does not. SRAM is volatile (contents lost when power removed) , Flash and EEPROM are non-volatile (contents persist when power is removed). Click on the image to the right to reacquaint yourself with the sizes of each of the three memories on the Arduino's ATmega328p.
The three memory clearly needs to accommodate both program code and data. Whereas your program code must reside in Program Flash, you can place your data in any of three areas! Knowing the sizes and access times for each are an important considerations in develop efficient applications.
Exploration. In this segment of Towards Better Code we'll demonstrate the placement of data in each of the three memories and discuss the strengths and limitations of each strategy. For the data, we'll use a 10-byte array of segment maps for a common 7-segment display. Below is an image of the circuit you may wish to construct yourself if you have the means.
6. C-Style Strings and Formatted Output
Few of C/C++ coding concepts are as challenging as working with strings. Part of the problem arises from the fact that C and C++ handle strings differently; both being vulnerable to the particular dialect and compiler being employed. The philosophical foundation of the Arduino Project as cross-platform, open source, C and C++ compatible, and specifically tailored to AVR 8-bit hardware, was a tall order back in 2005, that came with tradeoffs. These noble goals envisioned by the Arduino/MIT community, embodied in the toolchain provided by AVR-libc, are what current Arduino developers must adhere to.
The Arduino core set of libraries includes WString. This library provides a String class that facilitates seamless manipulation of C-Style strings and you are encouraged to review both its documentation and set of examples within the Arduino IDE.
Since even a modest attempt to discuss the nuances of string handling in the Arduino C environment is beyond the time allotment of this segment, I will restrict my comments to a few general principles.
C-Style Strings
char str1[] = "hi!"; // compiler can determine array size char str2[20]= "hi!"; // plenty of room char str3[4] = "hi!"; // min size, must allow room for null char str4[] = {'h', 'i', '!', '\0'}; // the above are equivalent to this
Formatted Output
The primary purpose of the previous glimpse of C-style strings was to lay the context for its application to structured formatting of text output, either to the Serial Monitor or other devices such as a character LCD or OLED display.
7. Pointer Access Operators (& and *)
Like many coding topics, there are only limited resources within our course that can be allocated for discussions of purely software intricacies within a hardware course. On the other hand, in order to fill a huge gap in Ontario's Ministry of Education curriculum our ACES course attempts to fulfill the expectations of the software course code you are being granted (ICS3U). To this end, let's take a (very) brief look at C/C++'s powerful duo of pointer access operators, & and *. Microchip offers good explanations to begin with (although their navigation system is horrible)
& - The Reference (Address of) Operator
Your nascent familiarity with the reference operator, & assists us with a common memory issue. Resolving a recent issue with a student prompts me to comment on C and it's relatively loose array bounds checking.
Bounds Checking
Memory Allocation
10. Register-Level Digital I/O
The software capabilities afforded by high level Arduino C offer users a learning curve with a fairly gentle slope. Your marvelous Grade 11 prototypes are visually-inspiring testaments to what your imagination and ingenuity have managed to do with the C as the software tool.
As ACES II winds down and some of you prepare to engage ACES III, the time is right to refocus our priorities on the optimization of the AVR-8 microcontroller. In this way your embedded systems will take up less memory, run faster and draw less power, among other advantages. This path is best served next year by introducing a lower-level software tool that offers closer access to the circuitry of the MCU. Microcontrollers and CPUs have a native language called Machine Language that would require the embedded engineer to code in binary or hexadecimal. Since this would be too painful and time-consuming an exercise MCU/CPU manufacturers offer a language that is just as efficient as machine language and avoids the bulkiness of high-level C. This language is referred to as Assembly Language which we will introduce in ACES III.
The embedded engineer requires a strong grasp of the hardware architecture of the target device. In this segment of Towards Better Code you take your first steps in drawing back the curtains that high-level C coding has imposed to manipulate the ATmega328p's digital I/O pins directly. As was discussed in class, each of the AVR MCU's digital I/O pins are mapped to a triad of 8-bit registers. These registers are located in SRAM as can be seen in the graphic below. The triad of registers are called, PINX, DDRX and PORTX, where X is either B, C, and D. The Arduino Pin Mapping diagram to the right reveals the connection between the high-level pin numbers (red) and the corresponding lower-level register bits (black).
Our introductory examples in this section will continue to use C as the code base, but since the header file iom328p.h is included in the Arduino C toolchain, our high-level code can refer to, and manipulate, individual bits of the I/O register triads, by name!
Register-Level Input
To complete this introduction to register-level IO it is necessary to provide an example demonstrating the use of the PINX register of the ports' triad of registers. To this point in the course, buttons or other digital devices generating digital signal states have been read using Arduino C's digitalRead() statement. This example will demonstrate the digital input using a register-level approach.
11. Reusable Code - Your Own Custom Library
Imagine for a moment, if a stipulation of our ACES version of ICS3U-E back in September precluded the use of third-party libraries. It would be doable, but progress would be slow. Libraries introduce a tradeoff: they hide the complexities (aka. abstraction) of a device or application in exchange for end users' ease-of-use. We've reached the stage in our course where it is time to examine the process of developing your own libraries as sources of both abstraction (aka. black box) and reusable data and code.
In programming for the ATmega328P (either the UNO or Nano) you became familiar with predefined constants such as, LED_BUILTIN. Tucked deep inside your Arduino IDE's installation, and included automatically in the toolchain, is a library file, called variant.h, that contain this statement (and numerous others),
#define LED_BUILTIN 13
Libraries are an integral part of the development cycle as they reduce development time and ensure greater code accuracy. It would be both instructive and efficient for you to begin to assemble reusable software assets inside your own library of support resources. For our immediate generic purposes we will name our library, ICS3U21.h
#include "ICS3U21.h"
12. Recursion: Setting the Stage
Our final two classes together will introduce concepts that you will engage over the course of the next few years, starting with your Calculus course next year.
Iteration (aka looping) is an indispensable software technique that can be brought to bear on a wide variety of programming tasks that involve require a repetitive strategy. There are, however, numerous problems of repeatable form that do not lend themselves to loops. Network analysis and optimization on weighted graphs that require depth searching is one such context that does not lend itself to iterative approaches.
Recursion is the primary alternative to iteration. From a coding standpoint it is elegant and deceptively simple in appearance, however, its performance can be taxing from a hardware perspective, especially for MCUs. Indeed, recursion should never be used if an interative solution would suffice, due to the overhead recursion imposes to support it.
Finally, It takes practice to become proficient in its use but, when you eventually 'get the hang of recursion' you may very well begin to view the world, in particular nature, in a very different light. We'll start with some simple recursive algorithms/functions from the world of mathematics.
A. Task. A Gentle Introduction to Recursive Functions. (See pp. 124-127)
Your familiarity with Binomial Theorem as a guided tool for the expansion of binomials in Grade 11. The formula also comes in handy early in your first Calculus course as it provides the foundation for the Power Rule for differentiation As a result, it doesn't hurt to take another look at the superimposed coefficients provided by Pascal's Triangle. First, let's restate the Binomial Theorem,
`(a+b)^n = \sum_{r=0}^n ((n),(r)) a^{n-r}b^r`
It is now clear that Pascal's coefficients can be computed directly `((n),(r))` as was done in the previous exercise. There is no need to develop the previous rows. On the other hand, there is great value is exploring how this could be done through a recursive approach. Elements `p`,of Pascal's Triangle, expressed recursively, can be defined as follows,
`p(n,0) = p(n,n)=1, p(n,r)=p(n-1,r-1)+p(n-1,r)`
13. Charlieplexing on the RSGC ACES' SMT Charliestick
The final segment of this Session encourages you to appreciate that both software and hardware techniques can be optimized for improved performance. What may alternatively be referred to as 'doing more with less' this demonstration takes a register-level software approach to port manipulation while exploiting the tri-state architecture (referred to as Hi-Z - impedance) of the AVR's digital IO pins.
While not intended necessarily to have these techniques find their way into your late Grade 11 projects, introducing them now will give you time to reflect and explore the concepts this summer as (some of) you prepare for the deepening study of embedded prototype development in our Grade 12 course.