RSGC ACES' AVR Optimization: Drilling Down

 

With a high-level understanding of microcontroller-based interfacing sufficiently entrenched in your skill set, it's time to examine performance optimization options. Using the 8-bit AVR family of microprocessors as our platform, investigations will include an overview of the native language of the (any) processor (called Assembly), the on-board memories, the ports, the registers, and the peripherals of the devices within ATMEL's 8-bit family. A solid understanding of these concepts will make for an easy transition to ARM and Intel devices on your own.

To better prepare for the depths we will descend to in our remaining terms, we'll begin with a review of some of the basic C language elements. Develop code to confirm the result where requested.

Data Types

  1. Competent low-level coders monitor and manage hardware resources carefully. To this end, one of the first steps is a working familiarity with the memory sizes of the available data types. Review the Standard C Data Types on Wikipedia.
  2. The Free Software Foundation's C toolchain for ATMEL's AVR microcontrollers is known as AVR-libc. The dialect of C is embodied in its compiler, known as AVR-gcc. The first link on the AVR-gcc home page discusses the data type details and the deviations from the C Standard. Confirm the memory sizes of each of the types by running the following code and examine its output on the Serial Monitor (you should familiarize yourself with the sprintf format specifier tags).
    // Purpose:   To confirm fundamental AVR-gcc data type elements and integer storage sizes
    // Reference: https://en.wikipedia.org/wiki/C_data_types
    // Author:    C. D'Arcy
    // Date:      2017 03 17
    // Status:    Working
    #include <limits.h>
    char buff[50];
    char data = 0x80;
    void setup() {
      // Open the serial port at 9600 bps:
      Serial.begin(9600);
      while (!Serial);
      Serial.println("AVR-libc's (implementation of Standard C) Fixed Point Data Types (memory size in bytes):");
      Serial.println(sizeof(char) + String(" :char"));
      Serial.println(sizeof(signed char) + String(" :signed char"));
      Serial.println(sizeof(unsigned char) + String(" :unsigned char"));
      Serial.println(sizeof(short) + String(" :short"));
      Serial.println(sizeof(short int) + String(" :short int"));
      Serial.println(sizeof(signed short) + String(" :signed short"));
      Serial.println(sizeof(signed short int) + String(" :signed short int"));
      Serial.println(sizeof(unsigned short) + String(" :unsigned short"));
      Serial.println(sizeof(unsigned short int) + String(" :unsigned short int"));
      Serial.println(sizeof(int) + String(" :int"));
      Serial.println(sizeof(signed) + String(" :signed"));
      Serial.println(sizeof(signed int) + String(" :signed int"));
      Serial.println(sizeof(unsigned) + String(" :unsigned"));
      Serial.println(sizeof(unsigned int) + String(" :unsigned int"));
    
      Serial.println(sizeof(long) + String(" :long"));
      Serial.println(sizeof(long int) + String(" :long int"));
      Serial.println(sizeof(signed long) + String(" :signed long"));
      Serial.println(sizeof(signed long int) + String(" :signed long int"));
    
      Serial.println(sizeof(unsigned long) + String(" :unsigned long"));
      Serial.println(sizeof(unsigned long int) + String(" :unsigned long int"));
    
      Serial.println(sizeof(long long) + String(" :long long"));
      Serial.println(sizeof(long long int) + String(" :long long int"));
      Serial.println(sizeof(signed long long) + String(" :signed long long"));
      Serial.println(sizeof(signed long long int) + String(" :signed long long int"));
    
      Serial.println(sizeof(unsigned long long) + String(" :unsigned long long"));
      Serial.println(sizeof(unsigned long long int) + String(" :unsigned long long int"));
      // Floating point...
      Serial.println("Standard C Floating Point Data Types (memory size in bytes):");
      Serial.println(sizeof(float) + String(" :float"));
      Serial.println(sizeof(double) + String(" :double"));
      Serial.println(sizeof(long double) + String(" :long double"));
      // limits...
      sprintf(buff, "Signed char:\t[%hi,%hi]", SCHAR_MIN, SCHAR_MAX);
      Serial.println(buff);
      sprintf(buff, "Unsigned char:\t[0,%hu]",UCHAR_MAX);
      Serial.println(buff);
      sprintf(buff, "Signed short:\t(%d,%d)", SHRT_MIN, SHRT_MAX);
      Serial.println(buff);
      sprintf(buff, "Unsigned short:\t[0,%u]", USHRT_MAX);
      Serial.println(buff);
      sprintf(buff, "Signed int:\t[%i,%i]", INT_MIN, INT_MAX);
      Serial.println(buff);
      sprintf(buff, "Unsigned int:\t[0,%u]", UINT_MAX);
      Serial.println(buff);
      sprintf(buff, "Signed long:\t[%li,%li]", LONG_MIN, LONG_MAX);
      Serial.println(buff);
      sprintf(buff, "Unsigned long:\t[0,%lu]", ULONG_LONG_MAX);
      Serial.println(buff);
    }
    
    void loop() {}
    
  3. For efficiency(?) and clarity of exact width, I prefer to employ AVR-libc's definitions/abbreviations for the Standard C integer types. The Arduino include file stdint.h defines these aliases. (typedef keyword explained)
  4. Review the low-half (7 bits) of the ASCII Table. Commit the ASCII values of an A, a, 0, and the space to memory, in ALL bases (binary(2), decimal(10), octal(8) and hexadecimal(16)).
  5. What available Integer data types are available in AVR-gcc? Character?
  6. How do we express an ASCII character constant? Binary? Octal? Decimal? Hexadecimal?
  7. What is the maximum decimal value of an unsigned, 8-bit integer? What is the binary equivalent? Octal? Hexadecimal?

Serial Input/Output

  1. Review Arduino's Serial Class and familiarize yourself with as many of its functions as possible.
  2. What function(s) does the Serial Monitor perform? What type and range of data is it capable of accurately displaying?
  3. Describe an algorithm the print statement could use to convert an integer to a string of ASCII characters. (itoa)
  4. What is the difference between Serial.write(65); and Serial.print(65);
  5. Test and explain the Serial Monitor's output in response to Serial.write(605);
  6. Develop code that will echo the input of a single character into the Serial Monitor's Input box using the .write() function and the .print() function in as all possible bases (2,8, 10, and 16).
  7. Examine the code below. If you entered your name in the Input box of the Serial Monitor, what would be displayed on Send?
      void loop(){
       while (Serial.available() == 0);
         uint8_t data = Serial.read();
         Serial.println(data);
         // Serial.write(data);
      }
      
    Predict the result of disabling the println statement and enabling the write function. Try it.

Arithmetic Operators

  1. Launch your computer's Calculator application and select Programming mode to reveal one of the two interfaces above. There are number of extremely useful resources on both versions. Explore them.
  2. What is the most efficient way to double an 8-bit unsigned integer? Quadruple? Test your ideas on the calculator.
  3. What results from an overflow of the 8-bit unsigned result?
  4. What is the most efficient way to divide an 8-bit integer by 2? By 4? By 32?
  5. Consider the follow fragment and predict the output before running it. If the result did not match your prediction consider why?
      uint8_t value = 200;
      Serial.println(value<<1);
      value <<= 1;
      Serial.println(value);
      
  6. You're familiar with the binary representation of how unsigned (positive) integers are stored in memory. What about negative integer values? How are they represented? Review the fragment below and predict the output before trying it.
      int8_t number = -1;
      Serial.println(number,BIN);
      Serial.println((uint8_t)number,BIN);
      

Bit Manipulation

Although a byte (8 bits) is the smallest memory size that can be reserved for a variable, the individual bits within a byte can be assigned different roles in the world of microcontrollers. The ability to set (1), clear (0) or toggle an individual bit, or group of bits, while leaving the others unaffected, is an essential assembly skill. You're familiarity with digital logic gates (ie AND, OR, NOT, etc.) is about to prove handy.

  1. In most high-level languages, logical (false, true) expressions are manipulated with the &&, || and ! (AND, OR, NOT, respectively) operators. The equivalent binary operators, that operate on the each of the bits 0 and 1 within a byte, are &, | and ~, respectively. Grab a pencil, determine the output of the fragment below, and confirm.
      Serial.println(11&5);
      Serial.println(11|5);
      Serial.println(~5);
    
  2. Review this list of C Operators, Expressions and Precedence.
  3. Again, you'll need a pencil for this one. Predict the output of the fragment below and confirm.
      Serial.println(15&7<<1);
      Serial.println(0xAA&036);
      Serial.println(254|1);
      Serial.println(~10^1<<2);
      Serial.println(value&1);
    
  4. Assume we have declared a variable as uint8_t data and assigned it a value. Develop an expression to accomplish each of the following and confirm.
    1. Clear bit 0
    2. Clear bits 3 and 5
    3. Set bit 4
    4. Toggle bit 6
    5. Swap nibbles

I/O Registers (PORTs)

  1. The digital I/O pins are grouped into sets of 8, called Ports, named A, B, C, and so on. A quick review of the Arduino Pin Mapping diagram reveals there is no PortA, but there is a PortB, PortC and PortD. PortD can be found on chip pins 2-6,11-13. Bits 6 and 7 of PortC appear to be inaccessible and the bits of PortB can be found on both sides of this DIL-28 package. Adding to the confusion is the somewhat random mapping from the chip pins to the header pins. A review of the Port resources for the ATtiny85 reveals only 6 available pins of a PortB (PB0-PB5).
  2. As can be seen on p. 615 of the ATmega328 datasheet, starting with address 32 (0x20) of SRAM, ATMEL defines bytes at fixed locations associated with the Input/Output pins of each Port. These bytes (registers) are different for each microcontroller in the family. There are 3 registers assigned to each Port (PORTn, DDRn, and PINn).

External Interrupts (Great Reference: Nick Gammon's Notes)

  1. Solutions to the Problem of Mechanical Switch Bounce: Software, Hardware
  2. Digital Logic Gates in support of Memory Elements. Recall Tim's Grade 10 ISP: Digital Key Lock Code
  3. Hardware debounce option: The SR Latch
  4. Review the high-level Arduino C function attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);
  5. Click on the image below left to review the major blocks in the architecture of the simplest of processors. We'll study this in detail next year but, for Interrupts, it's the Program Counter that is most significant.

    Simple As Possible Processor ATmega328 Interrupt Vector Table

  6. The Status Register (SREG)

  7. Review Chapter 13, External Interrupts in the ATmega328P Datasheet.