This package contains a simple test of tests for various elements of the SmartBoard hardware, which is a simple baseboard designed for easy embedding. It is able to run both a semi-automatic test suite as well as allow interactive testing.

Dependencies:   EthernetNetIf NTPClient_NetServices mbed

This program is most of what you need to test your SmartBoard baseboard hardware. It provides a means to test the following:

  • Two channels of CAN (with a loopback cable)
  • RS-232 Ports
  • Analog inputs
  • PWM outputs
  • Ethernet port
  • Real time clock
  • micro SD
  • USB Host port

SmartBoard_Tester.cpp

Committer:
WiredHome
Date:
2011-01-24
Revision:
2:02e7d896824f
Parent:
1:586392c0e935
Child:
3:2b4fe31e8d15

File content as of revision 2:02e7d896824f:

/// @file SmartBoard_Tester.cpp is the simple test framework
///
/// This file contains the startup, interactive, and test code
/// to evaluate the SmartBoard baseboard.
///
/// @note Copyright © 2011 by Smartware Computing, all rights reserved.
/// @author David Smart
///
#include "mbed.h"
#include "SmartBoard.h"
#include "ShowTime.h"
#include "EthernetNetIf.h"
#include "NTPClient.h"
#include "SDFileSystem.h"
#include "MSCFileSystem.h"

Serial pc(USBTX, USBRX);    ///!< Used as the console for interactively reporting progress

const char * TicTocServer = "ntp.okstate.edu";  ///!< time server since it is closer than "0.uk.pool.ntp.org"
const int tzOffsetHr = -6;      ///!< time zone offset hours to print time in local time
const int tzOffsetMin = 0;      ///!< time zone offset minutes to print time in local time

void LED_Tests(void);
void PWM_Tests(void);
void AnalogIn_Tests(void);
void RTC_Tests(void);
void RTC_Set(void);
void MicroSD_Tests(void);
void RS_232_Tests(void);
void CAN_Tests(void);
void Ethernet_Tests(void);
void USBHost_Tests(void);

/// TestVector will execute a given test, based on the parameter
///
/// It can show the list of available commands, as for an interactive
/// test session, or it can simply execute the chosen test. This is
/// used for both the automated testing and the interactive testing,
/// so a couple of the commands for interactive would not be used
/// for the automated testing.
/// '?' causes it to display the available commands.
///
/// @param i contains the single character value indicating the operation
///         to perform.
/// @returns false if the input paramter was 'X'.
/// @returns true if the input parameters was not 'X'.
///
bool TestVector(int i) {
    bool r = true;  ///!< expect to return true

    switch (i) {
        default:
        case '?':
            pc.printf("Commands:\r\n"
                      "  L   LED_Tests();\r\n"
                      "  P   PWM_Tests(); // note interaction between LEDs and PWMs\r\n"
                      "  A   AnalogIn_Tests();\r\n"
                      "  R   RTC_Tests();\r\n"
                      "  M   MicroSD_Tests();\r\n"
                      "  S   RS_232_Tests();\r\n"
                      "  C   CAN_Tests();\r\n"
                      "  E   Ethernet_Tests();\r\n"
                      "  U   USBHost_Tests();\r\n"
                      "  X   eXit to automatic testing\r\n");
            break;
        case 'X':
            r = false;
            break;
        case 'L':
            LED_Tests();
            break;
        case 'P':
            PWM_Tests();
            break;
        case 'A':
            AnalogIn_Tests();
            break;
        case 'R':
            RTC_Tests();
            break;
        case 'r':
            RTC_Set();
            break;
        case 'M':
            MicroSD_Tests();
            break;
        case 'S':
            RS_232_Tests();
            break;
        case 'C':
            CAN_Tests();
            break;
        case 'E':
            Ethernet_Tests();
            break;
        case 'U':
            USBHost_Tests();
            break;
    }
    return r;
}

/// main is the main startup code.
///
/// This initializes the test environment, shows a banner,
/// and starts the automated testing.
/// It also detects if the user is attempting to interact, and
/// between each test category there is the possibility to transfer
/// to the interactive test mode.
/// When in interactive test mode, the user determines which test
/// to run. The user can also exit interactive mode back to the
/// automated test mode.
///
/// @returns never
///
int main() {
    bool init = true;                   ///!< init is slightly different
    bool interactive = false;           ///!< track when in interactive mode
    int test = 0;                       ///!< which test to run
    char TestList[] = "XLPARrMSCEU";    ///!< list of valid test commands AUTOTESTS are uppercase

    while (1) {
        if (pc.readable() || init) {
            pc.printf("\r\n\r\n");
            pc.printf("SmartBoard Hardware Tester\r\n");
            pc.printf("  SmartBoard Hardware    v0.05\r\n");
            pc.printf("  SmartBoard Software    v0.09\r\n");
            pc.printf("\r\n");
            pc.printf("                      [USB]       [Eth/USB]    \r\n");
            pc.printf(" +---------------+------------+---+-------+---+\r\n");
            pc.printf(" |O [RS232 1-2]  |  |       | |   |       |  O|\r\n");
            pc.printf(" |               |  |microSD| |   |       |   |\r\n");
            pc.printf(" |S              |  |       | |   |       |  C|\r\n");
            pc.printf(" |P              |  +-------+ |   |       |  A|\r\n");
            pc.printf(" |I              |            |   |Yl   Gr|  N|\r\n");
            pc.printf(" |1              |            |   +-------+  1|\r\n");
            pc.printf(" |-              |            |              -|\r\n");
            pc.printf(" |2              |     RTC    |              2|\r\n");
            pc.printf(" |               |  (Battery) |               |\r\n");
            pc.printf(" |               |            |               |\r\n");
            pc.printf(" |               | 1  2  3  4 |               |\r\n");
            pc.printf(" |               +------------+               |\r\n");
            pc.printf(" |O[Analog In ]        O        [PWM Out]    O|\r\n");
            pc.printf(" +--------------------------------------------+\r\n");
            pc.printf("\r\n");
            init = false;
        }
        if (pc.readable()) {
            interactive = true;
            while (pc.readable())
                (void)pc.getc();

            while (interactive) {
                pc.printf("> ");
                int i = pc.getc();
                pc.putc(i);
                pc.putc('\r');
                pc.putc('\n');
                interactive = TestVector(i);
            }
        } else {
            if (test == 0)
                pc.printf("\x07"); // Bell character indicating start of tests
            TestVector(TestList[test++]);
            if (TestList[test] == '\0')
                test = 0;
            wait(5.0);  // Extra pause
        }
    }
}

/// LED_Tests performs some simple digital output to the
/// LEDs.
///
/// It will attempt to exercise the LEDs on the Ethernet ports
/// as well, but by jumper configuration these may not be available.
///
void LED_Tests(void) {
    int l;
    int i;
    struct {
        const char * name;
        DigitalOut led;
    } Leds[] = {
        {"Ethernet Green", ETHERGREEN},
        {"Ethernet Yellow", ETHERYELLOW},
        {"Led 1", LED1},
        {"Led 2", LED2},
        {"Led 3", LED3},
        {"Led 4", LED4}
    };
    const int numLeds = sizeof(Leds) / sizeof(Leds[0]);

    printf("LED Test:\r\n");
    for (l=0; l<numLeds; l++) {
        printf("   Blink %s LED 3 times\r\n", Leds[l].name);
        for (i=0; i<3; i++) {
            Leds[l].led = true;
            wait(0.4);
            Leds[l].led = false;
            wait(0.4);
        }
    }
}

/// PWM_Tests performs some simple pwm output to the
/// PWM channels and the LEDs.
///
/// It will attempt to exercise the outputs with a simple ramping
/// signal, but by jumper configuration these may not be available.
///
void PWM_Tests(void) {
    int l;
    int i;
    float f;
    struct {
        const char * name;
        PwmOut pwm;
    } Pwms[] = {
        {"PWM 1", p21},
        {"PWM 2", p22},
        {"PWM 3", p23},
        {"PWM 4", p24},
        {"PWM 5", p25},
        {"PWM 6", p26},
        {"Led 1", LED1},
        {"Led 2", LED2},
        {"Led 3", LED3},
        {"Led 4", LED4}
    };
    const int numPwms = sizeof(Pwms) / sizeof(Pwms[0]);

    printf("PWM Test:\r\n");
    for (l=0; l<numPwms; l++) {
        printf("   Ramp %s PWM 3 times\r\n", Pwms[l].name);
        for (i=0; i<3; i++) {
            for (f=0.0; f<=1.0; f+= 0.1) {
                Pwms[l].pwm = f;
                wait(0.1);
            }
        }
        Pwms[l].pwm = 0;    // off when done
    }
}

/// AnalogIn_Tests takes a few sample measurements on each channel
///
/// It samples each channel a number of times and presents the
/// converted results on the console.
///
void AnalogIn_Tests(void) {
    int l;
    int i;
    const int samples = 20;
    struct {
        const char * name;
        AnalogIn in;
    } Analogs[] = {
        {"Ain 1", p15},
        {"Ain 2", p16},
        {"Ain 3", p17},
        {"Ain 4", p18},
        {"Ain 5", p19},
        {"Ain 6", p20}
    };
    const int numAnalogs = sizeof(Analogs) / sizeof(Analogs[0]);

    printf("Analog Test:\r\n");
    for (l=0; l<numAnalogs; l++) {
        for (i=0; i<samples; i++) {
            uint16_t raw = Analogs[l].in.read_u16();
            float flt = Analogs[l].in.read();
            printf("    Analog %i is %04X, %3.2f, %3.2fv\r", l, raw, flt, flt*3.3);
            wait(0.1);
        }
        printf("\n");
    }
}

/// RTC_Tests will perform simple tests on the Real Time Clock
///
/// It will first sample the time from the RTC and later restore
/// it as best it can.
/// In the middle of that it will set the clock, then simply show
/// the time once per second for 5 seconds. After this it
/// will restore the clock at best it can.
///
void RTC_Tests(void) {
    time_t x;
    int i;
    const int oldTime = 1256729737;

    printf("RTC Test:\r\n");
    ShowTime(tzOffsetHr, tzOffsetMin);
    x = time(NULL);         // Save the time before the test
    printf("    Saving current time(%d)\r\n", x);

    set_time(oldTime);  // Set RTC time to Wed, 28 Oct 2009 11:35:37
    printf("    Set time to Wed, 28 Oct 2009 11:35:37\r\n");

    for (i=0; i<5; i++) {
        ShowTime();
        wait(1.0);
    }
    set_time(x + time(NULL) - 1256729737);          // Approximately restored
    ShowTime(tzOffsetHr, tzOffsetMin);
    wait(1.0);
    ShowTime(tzOffsetHr, tzOffsetMin);
}

/// GetNumber will get from the user a number using the
/// specified number of digits.
///
/// They can enter a number from 0 to 10^(digits-1)
///
/// @param digits is the number of digits to enter
/// @param pValue is a pointer to where to store the result
/// @returns true if a number was entered
/// @returns false if they entered a non-digit
///
int GetNumber(int digits, int * pValue) {
    int tempValue = 0;
    int i;

    while (digits--) {
        i = pc.getc();
        if (i == ' ') i = '0';      // special case for leading blank
        if (i >= '0' && i <= '9') {
            pc.putc(i);
            tempValue = tempValue * 10 + (i - '0');
        } else
            return false;
    }
    *pValue = tempValue;
    return true;
}

/// RTC_Set will interactively set the Real Time Clock
///
/// It will allow you to enter the date and time information
/// and then create a timestamp. After this, it will set
/// the RTC to that timestamp.
///
void RTC_Set(void) {
    time_t seconds = time(NULL);
    struct tm *t = localtime(&seconds);
    int i;

    printf("RTC Set:\r\n");
    ShowTime(seconds, tzOffsetHr, tzOffsetMin);
    printf("    Enter the time MM/DD/YYYY HH:MM:SS\r\n");
    while (1) {
        printf("                 > ");
        if (GetNumber(2, &t->tm_mon)) {
            pc.putc('/');
            if (!GetNumber(2, &t->tm_mday))
                continue;
            pc.putc('/');
            if (!GetNumber(4, &t->tm_year))
                continue;
        } else {
            pc.printf("%02d/%02d/%04d", t->tm_mon+1, t->tm_mday, t->tm_year+1900);
        }
        pc.putc(' ');
        if (!GetNumber(2, &t->tm_hour)) {
            pc.printf("%02d:%02d:%02d\r\n", t->tm_hour, t->tm_min, t->tm_sec);
            break;          // they can bail here
        }
        pc.putc(':');
        if (!GetNumber(2, &t->tm_min))
            continue;
        pc.putc(':');
        if (!GetNumber(2, &t->tm_sec))
            continue;
        else {
            t->tm_mon--; // 1-12 => 0-11
            t->tm_year -= 1900;
            t->tm_hour -= tzOffsetHr;
            t->tm_min -= tzOffsetMin;
            pc.printf("\r\n");
            // convert to timestamp and display (1256729737)
            time_t seconds = mktime(t);
            set_time(seconds);
            break;
        }
    }
    for (i=0; i<5; i++) {
        ShowTime(tzOffsetHr, tzOffsetMin);
        wait(1.0);
    }
}


/// Ethernet_Tests will attempt to test the Ethernet interface
///
/// It will connect to the network - if possible, then it will
/// try to connect to a network time server and set the clock,
/// using hard coded time server and time zone offset values.
///
/// It appears that the Ethernet interface cannot be instantiated,
/// destroyed, and later instantiated again (it would reliably "hang").
/// So, this test is "runonce" protected.
///
void Ethernet_Tests(void) {
    EthernetNetIf eth;
    NTPClient ntp;
    static bool runonce = true;

    printf("Ethernet Test:\r\n");
    if (runonce) {
        EthernetErr ethErr = eth.setup();
        if (ethErr) {
            printf("Error %d in setup.\r\n", ethErr);
            return;
        }
        printf("    Ethernet Setup OK\r\n");
        ShowTime(0, tzOffsetHr, tzOffsetMin);
        printf("    Setting clock to %s\r\n", TicTocServer);
        Host server(IpAddr(), 123, TicTocServer);
        ntp.setTime(server);
        printf("    Clock was set.\r\n");
        wait(1.0);
        ShowTime(0, tzOffsetHr, tzOffsetMin);
        runonce = false;
    } else {
        printf("   only runs once per cold-boot.\r\n");
    }
}

/// isPrint tests the passed in character for being 'printable'
///
/// It will evaluate the character on a simple ASCII range of
/// characters 0 to 127.
///
/// @param i is the character to test
/// @returns true if the character is in the printable set (including <space>)
/// @returns false if the character is not printable (may be control code or extended character)
///
bool isPrint(char i) {
    if (i >= ' ' && i <= '~')
        return true;
    else
        return false;
}

/// MicroSD_Tests attempts to access and write a file on the micro SD card
///
/// It will mount the file system, then attempt to write a simple
/// file on the micro SD card.
///
void MicroSD_Tests(void) {
    SDFileSystem sd(p5, p6, p7, p8, "ud"); // the pinout on the mbed Cool Components workshop board
    DigitalIn cardIn(p11);
    FILE *fp;
    char buffer[50];

    printf("SD File System Tests:\r\n");
    printf("    Card Detection: %s\r\n", (cardIn) ? "in" : "out");
    if (cardIn) {
        mkdir("/ud/testDir", 0777);
        fp = fopen("/ud/testDir/sdtest.txt", "w");
        if (fp == NULL) {
            printf("    Could not open file for write\r\n");
        } else {
            fprintf(fp, "Write a message to the micro SD card!\n");
            fclose(fp);
            printf("    Closed file.\r\n");
            fp = fopen("/ud/testDir/sdtest.txt", "r");
            if (fp) {
                printf("    Reading file back.\r\n");
                if (fgets(buffer, sizeof(buffer), fp)) {
                    while (strlen(buffer) > 0 && !isPrint(buffer[strlen(buffer)-1]))
                        buffer[strlen(buffer)-1] = '\0';    // chomp the <LF>
                    printf("    Read: {%s}\r\n", buffer);
                }
                fclose(fp);
            }
        }
    }
    printf("    test complete!\r\n");
}


/// USBHost_Tests attempts to access and write a file on USB stick
///
/// It will mount the file system, then attempt to write a simple
/// file on the USB interface.
///
void USBHost_Tests(void) {
    MSCFileSystem fs ("fs");
    FILE *fp;
    char buffer[50];

    printf("USB Host Tests: [installed memory stick required]\r\n");
    fp = fopen("/fs/hello.txt","w");
    if (fp) {
        printf("    Writing to hello.txt file\r\n");
        fprintf(fp,"Hello world!\r\n");
        fclose (fp);
        printf("    Closed file.\r\n");
        fp = fopen("/fs/hello.txt", "r");
        if (fp) {
            printf("    Reading file back.\r\n");
            if (fgets(buffer, sizeof(buffer), fp)) {
                if (strlen(buffer) > 2)
                    buffer[strlen(buffer)-2] = '\0';    // chomp the <LF>
                printf("    Read: {%s}\r\n", buffer);
            }
            fclose(fp);
        }
    }
}


/// FormatMicroseconds will format a number of microseconds int ascii seconds.
///
/// It will support formatting the number with decimal and thousands
/// separators.
///
/// @param value to format
/// @returns the formatted string
///
char * FormatMicroseconds(int value) {
    static char result[15] = "";
    int uSec = value % 1000000;
    int Sec = value / 1000000;
    
    if (Sec > 1000)
        sprintf(result, "%3i,%3i.%06i", Sec/1000, Sec%1000, uSec);
    else
        sprintf(result, "%7i.%06i", Sec, uSec);
    return result;
}


/// ShowCANMessage will print to the console as specified
///
/// This format is used in other tools, and is not explained
/// here except in the code.
///
/// @param tx indicates it is a transmit message when non-zero, receive otherwise
/// @param ch is the communication channel of this message
/// @msg is the CAN message to be shown
/// @uSec is the timestamp in microseconds
/// @returns nothing
///
void ShowCANMessage(int tx, int ch, CANMessage msg, int uSec) {
    pc.printf("%c %s %d %08X %02X ", (tx) ? 't' : 'r', (msg.format == CANExtended) ? "xtd" : "nrm", ch, msg.id, msg.len);
    for (int d=0; d<8; d++) {
        if (d < msg.len)
            pc.printf("%02X ", msg.data[d]);
        else
            pc.printf("   ");
    }
    pc.printf("0   0 %s\r\n", FormatMicroseconds(uSec));
}


/// CAN_Tests will send some packets on one CAN port and expect them on the other
///
/// It will attempt to send 10 messages on one port and expect that
/// all 10 messages were received on the other port. The two ports should
/// be wired from one to the other with a loop-back cable and a termination
/// resistor.
///
void CAN_Tests(void) {
    CAN can1(p9, p10);
    CAN can2(p30, p29);
    char Txcounter = 0;
    char Rxcounter = 0;
    CANMessage tMsg;
    CANMessage rMsg;
    Timer timer;
    int i;

    pc.printf("CAN Tests:\r\n");
    i = can1.frequency(250000);
    pc.printf("    can1 frequency set: %i\r\n", i);
    i = can2.frequency(250000);
    pc.printf("    can2 frequency set: %i\r\n", i);
    timer.start();
    for (i=0; i<25; i++) {  // gets a few more passes to receive the messages

        for (int x=0; x<8; x++)
            tMsg.data[x] = (char)(rand());
        tMsg.data[0] = Txcounter;
        tMsg.id = rand();
        tMsg.len = rand() % 9;
        tMsg.type = CANData;
        tMsg.format  = (rand() & 1) ? CANExtended : CANStandard;

        if (Txcounter < 10 && can1.write(tMsg)) {
            pc.printf("    ");
            ShowCANMessage(1, 1, tMsg, timer.read_us());
            Txcounter++;
            //wait(0.05);
        }
        if (can2.read(rMsg)) {
            Rxcounter++;
            pc.printf("    ");
            ShowCANMessage(0, 2, rMsg, timer.read_us());
        }
        wait(0.005);
    }
    if (Txcounter == Rxcounter)
        printf("    passed.\r\n");
    else
        printf("    **** Txcounter (%d) != Rxcounter (%d) ****\r\n", Txcounter, Rxcounter);
}

/// RS_232_Tests will say hello on each of the RS-232 channels
///
/// It will print a hello text string out each of the ports.
///
void RS_232_Tests(void) {
    Serial s1(p13, p14);
    Serial s2(p28, p27);

    pc.printf("RS-232 Tests:\r\n");
    s1.printf("    Hello going out S1\r\n");
    s2.printf("    Hello going out S2\r\n");
    pc.printf("    end tests.\r\n");
}