Example – CMOS IC Tester

Here, we will look at a more full-featured example project, implementing an integrated circuit (IC) tester for 5V logic chips. In addition to probing chips with its GPIO pins, this project also reads a chip description and test program (in the form of a logic table) from an SD card over SPI. User control is added in the form of a serial-based command-line interface.

First, we look at the Makefile for this Nodate project, as found in the root of the project:

ARCH ?= avr

# Board preset.
BOARD ?= arduino_mega_2560

# Set the name of the output (ELF & Hex) file.
OUTPUT := sdinfo

# Add files to include for compilation to these variables.
APP_CPP_FILES = $(wildcard src/*.cpp)
APP_C_FILES = $(wildcard src/*.c)


#
# --- End of user-editable variables --- #
#

# Nodate includes. Requires that the NODATE_HOME environment variable has been set.
APPFOLDER=$(CURDIR)
export

all:
$(MAKE) -C $(NODATE_HOME)

flash:
$(MAKE) -C $(NODATE_HOME) flash

clean:
$(MAKE) -C $(NODATE_HOME) clean

The first item we specify is the architecture we are targeting, since Nodate can be used to target other MCU types as well. Here, we specify AVR as the architecture.

Next, we use the preset for the Arduino Mega 2560 development board. Inside Nodate, we have a number of presets like these, which define a number of details about the board. For the Arduino Mega 2560, we get the following presets:

MCU := atmega2560 
PROGRAMMER := wiring 
VARIANT := mega # "Arduino Mega" board type

If no board preset is defined, one has to define those variables in the project's Makefile and pick an existing value for each variable, each of which is defined as its own Makefile within the Nodate AVR subfolders. Alternatively, one can add one's own MCU, programmer, and (pin) variant file to Nodate, along with a new board preset, and use that.

With the makefile complete it is time to implement the main function:

#include <wiring.h>
#include <SPI.h>
#include <SD.h>


#include "serialcomm.h"

The wiring header provides access to all GPIO-related functionality. Furthermore, we include headers for the SPI bus, the SD card reader device, and a custom class that wraps the serial interface, as we will see in more detail in a moment:

int main () {
init();
initVariant();

Serial.begin(9600);

SPI.begin();

Upon entering the main function, we initialize the GPIO functionality with a call to init(). The next call loads the pin configuration for the particular board we are targeting (the VARIANT variable on the top or in the board preset Makefile).

After this, we start the first serial port with a speed of 9,600 baud, followed by the SPI bus, and finally the output of a welcome message, as follows:

   Serial.println("Initializing SD card...");

if (!SD.begin(53)) {
Serial.println("Initialization failed!");
while (1);
}

Serial.println("initialization done.");

Serial.println("Commands: index, chip");
Serial.print("> ");

An SD card is expected to be attached to the Mega board at this point, containing a list of available chips we can test. Here, pin 53 is the hardware SPI chip-select pin that is conveniently located next to the rest of the SPI pins on the board.

Assuming the board is hooked up properly and the card can be read without issues, we are presented with a command-line prompt on the console screen:

          while (1) {
String cmd;
while (!SerialComm::readLine(cmd)) { }

if (cmd == "index") { readIndex(); }
else if (cmd == "chip") { readChipConfig(); }
else { Serial.println("Unknown command."); }

Serial.print("> ");
}

return 0;
}

This loop simply waits for input to arrive on the serial input, after which it will attempt to execute the received command. The function we call for reading from the serial input is blocking, returning only if it has either received a newline (user pressed Enter), or its internal buffer size was exceeded without receiving a newline. In the latter case, we simply dismiss the input and try to read from the serial input once more. This concludes the main() implementation.

Let's now look at the header of the SerialComm class:

#include <HardwareSerial.h>      // UART.


static const int CHARBUFFERSIZE 64


class SerialComm {
static char charbuff[CHARBUFFERSIZE];

public:
static bool readLine(String &str);
};

We include the header for the hardware serial connection support. This gives us access to the underlying UART peripheral. The class itself is purely static, defining the maximum size of the character buffer, and the function to read a line from the serial input.

Next is its implementation:

#include "serialcomm.h"

char SerialComm::charbuff[CHARBUFFERSIZE];


bool SerialComm::readLine(String &str) {
int index = 0;

while (1) {
while (Serial.available() == 0) { }

char rc = Serial.read();
Serial.print(rc);

if (rc == ' ') {
charbuff[index] = 0;
str = charbuff;
return true;
}

if (rc >= 0x20 || rc == ' ') {
charbuff[index++] = rc;
if (index > CHARBUFFERSIZE) {
return false;
}
}
}

return false;
}

In the while loop, we first enter a loop that runs while there are no characters to be read in the serial input buffer. This makes it a blocking read.

Since we want to be able to see what we're typing, in the next section we echo back any character we have read. After this, we check whether we have received a newline character. If we did, we add a terminating null byte to the local buffer and read it into the String instance we were provided a reference to, after which we return true.

A possible improvement one could implement here is that of a backspace feature, where the user could delete characters in the read buffer by using the backspace key. For this, one would have to add a case for the backspace control character (ASCII 0x8), which would delete the last character from the buffer, and optionally also have the remote terminal delete its last visible character.

With no newline found yet, we continue to the next section. Here, we check whether we have received a valid character considered as ASCII 0x20, or a space. If we did, we continue to add the new character to the buffer and finally check whether we have reached the end of the read buffer. If we did not, we return false to indicate that the buffer is full yet no newline has been found.

Next are the handler functions readIndex() and readChipConfig() for the index and chip commands, respectively:

void readIndex() {
File sdFile = SD.open("chips.idx");
if (!sdFile) {
Serial.println("Failed to open IC index file.");
Serial.println("Please check SD card and try again.");
while(1);
}

Serial.println("Available chips:");
while (sdFile.available()) {
Serial.write(sdFile.read());
}

sdFile.close();
}

This function makes heavy use of the SD and associated File classes from the Arduino SD card library. Essentially, we open the chips index file on the SD card, ensure we got a valid file handle, then proceed to read out and print each line in the file. This file is a simple line-based text-file, with one chip name per line.

At the end of the handler code, we're done reading from SD and the file handle can be closed with sdFile.close(). The same applies to the slightly more lengthy upcoming readChipHandler() implementation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.119.241