manufacturer of I/O-rich SBCs, operator interfaces, handheld instruments, and development tools for embedded control low cost single board computers, embedded controllers, and operator interfaces for scientific instruments & industrial control development tools for embedded control order our low cost I/O-rich embedded control products embedded controller manufacturer profile single board computers & embedded controllers development tools & starter kits for your embedded design operator interfaces with touchscreens and graphical user interface plug-in expansion modules for digital & analog I/O C language & Forth language integrated development tools, IDE single board and embedded computer accessories embedded controller enclosures, bezels, environmental gaskets

QCard C User Guide

Table of Contents

PART 1 GETTING STARTED

Chapter 1: Getting to Know Your QCard Controller

Chapter 2: Using Your PowerDock

Chapter 3: Your First Program

PART 2 PROGRAMMING THE QCARD CONTROLLER

Chapter 4: The IDE: Writing, Compiling, Downloading and Debugging

Chapter 5: Making Effective Use of Memory

Chapter 6: Real Time Programming

Chapter 7: Failure and Run-Time Error Recovery

PART 3 COMMUNICATIONS, MEASUREMENT, AND CONTROL

Chapter 8: Digital and Timer-Controlled I/O

Chapter 9: Data Acquisition Using Analog to Digital Conversion

Chapter 10: Serial Communications

Chapter 11: The Battery-Backed Real-Time Clock

PART 4 PUTTING IT ALL TOGETHER

Chapter 12: A Turnkeyed Application

Overview of the Application

Hardware Required for the Example Application

The Memory Map

8 Bit A/D Data Gathering Task

Pulse Width Modulation Task

Output Compare 3 Interrupt Code

Assembly Coding a Function Definition

Statistics Task

Build and Activate the Tasks

Define the main Routine

Compile the Program

Using SAVE, RESTORE and Write-Protection During Debugging

Configure the Board to Autostart the Program

Turnkeyed Application Code Listing

PART 5 REFERENCE DATA

Appendix A: QCard Electrical Specifications

Appendix B: Connector Pinouts

Appendix C: Schematics (zip)

Chapter 12

<< Previous | Next>>

Turnkeyed Application Code Listing

Listing 12‑1   Turnkeyed Application

// Turnkeyed Application Code Listing, C Language version.

// This is a complete real-time application program, with instructions on

// how to set up the program to AUTOSTART each time the processor starts up.

 

// Copyright 2004 Mosaic Industries, Inc.  All Rights Reserved.

//  Disclaimer: THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT ANY

//  WARRANTIES OR REPRESENTATIONS EXPRESS OR IMPLIED, INCLUDING, BUT NOT

//  LIMITED TO, ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS

//  FOR A PARTICULAR PURPOSE.

 

// **********Turnkeying a QED Application ***********

//

// This is the code for an example application.  The accompanying chapter

// presents a detailed explanation of this code.  The code can run on a QCard

// with the minimum memory configuration.  To allow display of the statistical

// results calculated by the program, a Keypad/Display Wildcard should be

// attached to the QCard.

 

// Description of the application:

// This program reads an input voltage, creates a pulse width modulated (PWM)

// signal whose average value tracks the input voltage, and reports the

// mean and standard deviation of the input voltage.  The following 3 tasks do

// the work:

 

// Task 1: Reads 8 bit A/D input AN0, measuring the voltage on a potentiometer

//                connected to PortE pin PE0/AN0.

 

// Task 2: Outputs on Port A bit 5 a PWM signal whose average equals the A/D

//                input.

 

// Task 3: Every 1 second puts A/D value into a matrix, and every 10 seconds

//         displays the mean and standard deviation of the last 10 seconds

//         worth of data.

//

// The following issues are addressed:

//

//      Using the default C memory map as set up by the MAKE utility

//      Proper initialization of tasks at startup

//      Autostarting

//      Interrupt service routines and interrupt attaching

//      In-line assembly coding

//      Initialization of the heap memory manager

//      Timesliced multitasking

//      ColdOnReset() for secure restart

//      Floating point calculations

//      Using the display

//      Going into production

 

 

 

// ************** Default Memory Map ************

 

// 0-7FFF (32K) in page 4 is user application code (eventually will be ROMmed)

 

// 4600-7FFF (14.5K) in page 0F is RAM Heap area that holds banked array data.

 

// 3000-45FF (5.5K) in page 0F is a heap for the graphics display buffer.

 

// 8E00-ADFF is available common RAM (the C .data and .init sections)

//      which hold C variables, C arrays, and pfa’s (parameter field areas)

//      of FORTH_ARRAYs.

 

// B000-B3FF is 1K 68HC11 on-chip RAM; the top 48 bytes

//      at B3D0-B3FF are reserved.

 

// AEC0-AFFF is available EEPROM referenced by the .eeprom section

//      {EEPROM at AE00-AEBF is reserved for startup utilities

//       and interrupt revectoring}

 

// QED-Forth ROM occupies 0-7FFF on page 0, 0-2FFF on page F,

//      and B400-FFFF in common ROM.

 

 #include <\mosaic\allqed.h>     // include all of the qed and C utilities

 

 

// ************8 BIT A/D Data Gathering Task ***********

 

// This task gathers data from the 8 bit A/D and places it in a variable

// easily accessed by other tasks.

 

// Define the control registers and some useful constants:

 

#define AD8_INPUT_CHANNEL       ((char) 0)

// You can use any channel you want and place the input potentiometer on

// the corresponding A/D8 input pin.  For example, if 0 is your input

// channel, place the input signal on the pin labeled AN0 (pin 10 of

// the analog connector).

 

 

static float input_voltage;

        // Holds voltage corresponding to latest result of A/D input. Because the

        // contents of this variable are shared among several tasks, we use

        // uninterruptable PokeFloatUninterrupted and PeekFloatUninterrupted

        // to access it.

 

 

// Now we convert a measured count from the A/D that ranges from 0 to 255

// into a voltage that ranges from 0.0 Volts (the low reference voltage)

// to nearly 5.0 Volts (the high reference voltage).

// This involves solving the equation:

// Voltage=[(high.ref.voltage -

low.ref.voltage)*measured.count/256]+low.ref.voltage

 

// First let’s define some constants:

#define FULL_SCALE_COUNTS       256     // 256 counts in an 8 bit converter

#define LOW_VREF                0.0

#define HIGH_VREF               5.0

         // NOTE: For maximum accuracy, measure +5VAN with a voltmeter and

         //        set HIGH.REF.VOLTAGE equal to the result.

 

 

_Q float CountToVolts(int count)

        // This routine converts 8 bit A/D measured count n { 0 <= n <= 255 }

        // to the corresponding floating point voltage r

        // {typically, 0.0 <= r <= 5.0 }  by solving the equation:

        // r = [ (high.ref - low.ref) * count / 256 ]  + low.ref

{  return( ((HIGH_VREF - LOW_VREF) * count / FULL_SCALE_COUNTS) + LOW_VREF );

}

 

 

_Q  _protect  void PokeFloatUninterrupted(float value, float* destination )

// stores value into the address specified by destination;

// the _protect keyword guarantees that

// interrupts will be disabled while this function executes;

// this prevents corruption of data when two tasks are accessing the same

// 4-byte variables

{  *destination = value;

}

 _Q  _protect  float PeekFloatUninterrupted(float *  source )

// fetches and returns the contents from the address specified by source;

// the _protect keyword guarantees that

// interrupts will be disabled while this function executes;

// this prevents corruption of data when two tasks are accessing the same

// 4-byte variables

{  return *source;

}

 

 

_Q void GatherData(void)

// This is the activation routine for the data gathering task.

// It continually acquires readings from the A/D, converts the readings

// to voltages, and updates the input_voltage variable that is accessed

// by other tasks.

{       uchar sample;

        float sample_voltage;

        AD8On();                        // make sure A/D is powered up

        while(1)                        // start infinite loop

        {       sample = AD8Sample(AD8_INPUT_CHANNEL);

                sample_voltage = CountToVolts(sample);

                PokeFloatUninterrupted(sample_voltage, &input_voltage);

                Pause();

        }

}

 

 

 

// **************Pulse Width Modulation (PWM) Task **************

 

// This task calculates the high time and low time of the PWM output based on

// the value of the input_voltage which is updated by the data gathering task.

// The goal is to set the duty cycle of the PWM output so that the average of

// the PWM signal equals the input_voltage. This is achieved by solving the

// equation:

//      DutyCycle = (input_voltage - low.output) / (high.output - low.output)

//      in which low.output and high.output are the voltage levels that appear

//      on the PWM output pin in the low and high states, respectively.

 

 

// Given the duty cycle, and given our choice of a PWM period of 130 msec, we

// calculate the high.time and low.time of the PWM signal in terms of the timer

// counts; each timer count equals 2 microseconds.  high_time is the number of

// timer counts that the signal is in the high state and low_time is the number

// of timer counts that the signal is in the low state during each 130 msec

// period.  high_time and low_time are calculated as:

//      high_time = Duty.cycle * TIMER_COUNTS_PER_PERIOD

//      low_time = TIMER_COUNTS_PER_PERIOD - high_time

//              where TIMER_COUNTS_PER_PERIOD = 130 msec/2 microsec = 65,000.

 

 

// We also “clamp” high_time and low_time to a minimum value to prevent a

// situation where interrupts are requested so rapidly that the processor

// can’t service them.

 

// The OC3 (output compare 3) interrupt code in the subsequent section uses

// these calculated values to pulse width modulate the PORTA output port pin

// PA5.

 // We define some variables and constants:

 

static uint high_time;   // the number of timer counts that PWM output is high

static uint low_time;    // the number of timer counts that PWM output is low

static float duty_cycle;

 

#define LOW_OUTPUT_VOLTAGE      0.0     // the voltage on PA5 when it is low

#define HIGH_OUTPUT_VOLTAGE     5.0     // the voltage on PA5 when it is high

// NOTE: for maximum accuracy, the voltage output of pin PA5 could be measured

//   in the low and high states and LOW_OUTPUT_VOLTAGE and HIGH_OUTPUT_VOLTAGE

//   set accordingly.

 

#define MS_PER_PERIOD   130    // use a 130 msec period for PWM output

                               // NOTE: the timer “rolls over” every 131 msec

 

#define TIMER_COUNTS_PER_PERIOD 65000

//   130 milliseconds corresponds to 65,000 counts of 2 microseconds (usec)

//   each on the free-running timer.  QED-Forth sets the contents of TMSK2 so

//   that each timer count represents 2 usec.  This is true whether the crystal

//   speed is 8 MHz or 16MHz.  We assume here that the programmer has not

//   installed a different value in TMSK2 using InstallRegisterInits().

 

 

#define MINIMUM_HIGH_OR_LOW_TIME ((int) 250)    // corresponds to 500usec

// we choose a minimum high or low time approximately equal to the maximum

// number of timer counts divided by 256.  That is, we only require 8 bits of

// resolution from our PWM.  The benefit of imposing a minimum high or low time

// is that bey doing so we can ensure that the minimum time between PWM

// interrupts is 500 usec.  This is more than sufficient time to allow the

// service routine to execute.

 

 

_Q void CalculateDutyCycle(void)

// implements the equation:

//     duty_cycle = (input_voltage-low.output)/(high_output-low_output)

// duty_cycle is in the range 0.0 to 1.0

{   duty_cycle = (input_voltage - LOW_OUTPUT_VOLTAGE)

                               / (HIGH_OUTPUT_VOLTAGE - LOW_OUTPUT_VOLTAGE);

    duty_cycle = MIN((float) 1.0, duty_cycle) ;

    duty_cycle = MAX((float) 0.0, duty_cycle) ; // clamp to 0<=duty_cycle<=1.0

}

 

 

_Q void HighAndLowTimes(void)

// saves high and low times as 16bit integer counts in the variables

// high_time and low_time using the equations:

//     high_time = duty_cycle * TIMER_COUNTS_PER_PERIOD

//     low_time  = TIMER_COUNTS_PER_PERIOD - high_time

// both high_time and low_time are clamped to a minimum so that timer interrupts

// don’t occur more frequently than they can be reliably serviced.

{

  high_time = MAX((TIMER_COUNTS_PER_PERIOD *

                 duty_cycle),MINIMUM_HIGH_OR_LOW_TIME);

  low_time = MAX((TIMER_COUNTS_PER_PERIOD - high_time),MINIMUM_HIGH_OR_LOW_TIME);

  high_time = TIMER_COUNTS_PER_PERIOD - low_time;

  // make sure that high_time + low_time is exactly one period despite clamping

}

 

 _Q void SetPulseParameters( void )

        // this is the activation roution for the pwm task.  It updates the

        // values in high_time and low_time for use by the oc3 interrupt which

        // generates the pwm output waveform on PORTA pin PA5.

{       while(1)

        {       CalculateDutyCycle();

                HighAndLowTimes();

                Pause();

        }

}

 

 

// *************OC3 Interrupt Code ***************

// This interrupt routine generates a pulse width modulated output on Port A

// bit 5 based on the duty cycle calculated by the PWM task.

// There are 4 steps involved in coding an interrupt service routine:

//      1.  Name all required hardware registers (see the QEDREGS.H file in the

//              \MOSAIC\FABIUS\INCLUDE\MOSAIC directory),

//              and name all required bit masks with appropriate mnemonics.

//      2.  Use C or assembly code to define an interrupt handler which

//              must clear the interrupt request flag and perform required

//              service actions.

//      3.  Install the interrupt handler using the Attach() function.

//      4.  Write routines to enable and disable the interrupt. (We combine

//          steps 3 and 4 in a routine that ATTACHes and enables the interrupt).

 

 

 

// Define output mode configuration flags and masks that specify action

// to be performed when a successful output compare occurs:

#define PA5_MASK                0x20  // mask for setting/resetting PA5

#define OC3_MASK                0x20  // to set/clr OC3 interrupt flag & mask

#define OC3_MODE_MASK           0x20  // mask in TCTL1; enables PA5 pin control

#define OC3_LEVEL_MASK          0x10  // mask in TCTL1; controls level of PA5

 

// Summary of the functions of these registers: OC3 (output compare 3) is

// associated with pin PA5 on PORTA.  We can write a 16 bit count into the TOC3

// register, and when the count in TOC3 matches the count in the main counter

// TCNT, an interrupt request can occur.  The request only occurs if interrupts

// are globally enabled and if the OC3 interrupt is locally enabled.  The OC3

// interrupt is locally enabled by setting the bit specified by the OC3_MASK in

// the TMSK1 register.  When the interrupt request occurs, the 68HC11

// automatically sets the bit specified by OC3_MASK in the TFLG1 (timer flag

// #1) register.  Our interrupt service routine must clear this bit (oddly

// enough, interrupt request bits are cleared by writing a 1 to the bit

// position!) The register TCTL1 controls whether the state of the PA5 bit is

// automatically changed when TOC3 matches TCNT.  In this application we enable

// this automatic “pin control” action by setting the bit specified by

// OC3_MODE_MASK in TCTL1.  The bit specified by OC3_LEVEL_MASK in TCTL1 then

// controls the level (high or low) to which PA5 is set upon a successful

// output compare.

 

 _Q void OC3Service(void)

{       char temp = OC3_LEVEL_MASK;

        TFLG1 = OC3_MASK;  // reset the oc3 interrupt flag so that new oc3

                           // interrupts will be recognized. because the flag

                           // is cleared by writing a one to it we can use a

                           // assignment command without affecting other bits.

        if( temp &= TCTL1 )   // look at the oc3/pa5 pin output level

        {       TCTL1 &= ~OC3_LEVEL_MASK; // AND with complement of mask

                TOC3 += high_time;

        }                  // if the output level just toggled high we’ll

                           // set the mode/level bit so the next output

                           // compare forces the pin low after the high_time

        else               // else if the output level just toggled low...

        {       TCTL1 |= OC3_LEVEL_MASK;

                TOC3 += low_time;  // set the mode/level bit so the next output

        }                       // compare forces the pin high after low_time

}

 

 

// Pass the required constants through to the assembler:

// Don’t insert any leading spaces on a line that contains an EQU directive.

#asm

TOC3            EQU     $801A

TCTL1           EQU     $8020

TFLG1           EQU     $8023

 

PA5_MASK        EQU     $205

OC3_MASK        EQU     $20

OC3_MODE_MASK   EQU     $20

OC3_LEVEL_MASK  EQU     $10

NOT_OC3_LEVEL_MASK EQU  $EF

 

#endasm

 

 _Q void AssembledOC3Service( void )

// This interrupt service routine performs the same functions as the high level

// service routine named OC3Service.  This alternative interrupt service routine

// is assembly coded to illustrate the principles of assembly coding with

// this environment.  This routine it executes in under 45 usec

// including the call and return overhead. The high level version

// is nearly as fast.

// NOTE: #asm and #endasm in-line assembly directives

//               must be the first on a line,

//  AND:  each line of assembly code MUST START WITH A TAB OR SPACE,

//  BUT:  labels must NOT be preceeded by a tab or space.

//  The comment character for assembly code is the * as shown below.

{

#asm

        ldab TCTL1

        bitb #OC3_LEVEL_MASK    * Look at the OC3/PA5 pin output level

        beq is_low

        * If output just went high, clear the level bit in TCTL1: next OC3 clrs PA5

                andb #NOT_OC3_LEVEL_MASK

                stab TCTL1

                ldd TOC3

                addd high_time

                bra finished

is_low

     * If output just went low, set the level bit in TCTL1: next OC3 sets PA5

                orab #OC3_LEVEL_MASK

                stab TCTL1

                ldd TOC3

                addd low_time

finished

        std TOC3           * update the OC count for the next output compare

        ldaa #OC3_MASK     * Reset the OC3 interrupt flag by writing a 1

        staa TFLG1         * so that new OC3 interrupts will be recognized.

#endasm

}

 

 

 

_Q void InstallOC3( void )

// This installation routine enables the “pin control” function, ATTACHES the

// service routine, configures PortA pin 5 as an output, initializes the output

// compare register TOC3, clears the OC3 interrupt request flag (thus ignoring

// prior interrupt requests), and locally enables the OC3 interrupt.

// PWM can begin when interrupts are globally enabled.

{

        TMSK1 &= ~OC3_MASK;                 // Disable OC3 interrupts.

        TCTL1 |= OC3_MODE_MASK;

        // Set the OC3 mode bit so that an output compare sets or clears PA5 pin

        // depending on the level bit which is toggled by the interrupt routine.

//      ATTACH(AssembledOC3Service, OC3_ID); // optional assembly service routine

        ATTACH(OC3Service, OC3_ID);          // use the C-coded version

        TCTL1 &= ~OC3_LEVEL_MASK; // Set to low before pulses start.

        CFORC |= OC3_MASK;                   // and init by forcing a compare.

        TOC3 = TCNT + TIMER_COUNTS_PER_PERIOD; // start after a min 130ms delay

        TFLG1 = OC3_MASK;                       // clear interrupt flag OC3F

        TMSK1 |= OC3_MASK;                      // set OC3I to locally enable OC3

}

 

 

// **************Statistics Task ******************

 

// This task samples the analog input voltage (see task 1) once per second,

// stores it in a matrix, and calculates and prints the mean and standard

// deviation of the data every 10 seconds.

 

 

FORTH_ARRAY Last10Voltages;

        // row matrix;  holds 1 voltage per second for past 10 seconds

 

// these index variables keep track of where data is placed in Last10Voltages:

static int matrix_index, last_index;

 

// these hold the derived statistics:

static float mean, standard_deviation;

 

_Q float RowMean(int rownum, FORTH_ARRAY * array_ptr)

// sums the elements in the specified row of the specified matrix and

// divides by the number of elements in the row to calculate the mean.

{       float accumulator = 0.0;

        int i, numcolumns = NUMCOLUMNS(array_ptr); // see \Mosaic\ARRAY.H file

        for(i=0; i < numcolumns; i++)

                accumulator += FARRAYFETCH(float, rownum, i, array_ptr);

        return (accumulator / numcolumns);

}

 

_Q float RowStandardDeviation(int rownum, FORTH_ARRAY* array_ptr, float row_mean)

// subtracts the row mean from each element and sums the squares of all

// resulting differences in the specified row of the specified matrix

// and takes the square root of the sum to calculate the standard deviation.

{       float accumulator = 0.0, temp;

        int i, numcolumns = NUMCOLUMNS(array_ptr); // see \Mosaic\ARRAY.H file

        for(i=0; i < numcolumns; i++)

            {   temp = FARRAYFETCH(float, rownum, i, array_ptr) - row_mean;

                accumulator += (temp * temp);

            }

        return sqrt(accumulator);

}

 

_Q void InitFPArray(float value, FORTH_ARRAY * array_ptr)

// stores specified floating point value in each element of the array

// do not use this function to init arrays that hold char, int or long data

{       int r, c,

            numcolumns = NUMCOLUMNS(array_ptr),   // see \Mosaic\ARRAY.H file

            numrows = NUMROWS(array_ptr);

        for(c=0; c< numcolumns; c++)

                for(r=0; r < numrows; r++)

                        FARRAYSTORE(value, r, c, array_ptr);

}

 _Q void CalcStats( void )

// This routine calculates mean and standard deviation of the

// Last10Voltages matrix and stores them in variables.

{

        mean = RowMean(0, &Last10Voltages);                     // row# = 0

        standard_deviation = RowStandardDeviation(0, &Last10Voltages, mean);

}

 

 

// These routines manage the LCD display.

// The message on the display will look like this:

//      Mean =

//            x.xxx   Volts

//      Standard Deviation =

//            x.xxx   Volts

 

 

 

#define  FP_OFFSET      4       // character offset to start of mean or

                                // std.deviation in display line

#define VOLTS_OFFSET 14         // character offset to start of “Volts” label

                                // in display line

 

#define MAX_CHARS_PER_DISPLAY_LINE 40

// large enough for 4x20 character display as well as 16 x 40 graphics display

 

#define SPACE   0x20            // ascii space {blank}

 

 

#define  DISPLAY_CONFIG_BYTE    ((uchar*) 0xAE1E) // see grphext.c file

#define  TOSHIBA_VS_HITACHI_MASK 0x40      // 1 = toshiba; 0 = hitachi

 

char  linebuffer[MAX_CHARS_PER_DISPLAY_LINE+1];

        // single line buffer to assist in writing to display

        // NOTE: it’s all right if this buffer is longer than the display line;

        //      StrToDisplay() will ignore the extra characters.

 

 

_Q uint Toshiba(void)

        // returns flag, = true if toshiba display; false if hitachi

{

        return (((*DISPLAY_CONFIG_BYTE) & TOSHIBA_VS_HITACHI_MASK) ? 1 : 0);

}

 

_Q void BlankDisplayString(char* string_base, int numelements)

// writes “blanks” into the specified string, taking into account that

// Toshiba graphics displays in text mode use 0x00 as the code for BL

// numelements includes the terminating null character

{       int i;

        char blankchar = (Toshiba() ? 0 : SPACE);

        for( i=0; i < (numelements-1); i++)

                string_base[i] = blankchar;

        string_base[numelements-1] = 0; // terminating null

}

 

 _Q void SetupDisplay( void )

        // writes headings to display; leaves numbers blank.

{       xaddr display_buffer_base = DisplayBuffer();

        int numlines = LinesPerDisplay();

        int chars_per_line = CharsPerDisplayLine();

        char blankchar = (Toshiba() ? 0 : SPACE);

        FillMany(display_buffer_base, (numlines * chars_per_line), blankchar);

               // FillMany() blanks the display buffer // blank display buffer

        STRING_TO_DISPLAY(“mean = “, 0, 0 );                // line 0

        STRING_TO_DISPLAY(“volts”, 1, VOLTS_OFFSET);        // line 1

        STRING_TO_DISPLAY(“standard deviation =“, 2, 0 );   // line 2

        STRING_TO_DISPLAY(“volts”, 3, VOLTS_OFFSET);        // line 3

        UpdateDisplay();                 // write buffer contents to display

}

 

// sprintf(char* s, const char *format, ... ) does formatted write to specified

// string.  Returns #chars assigned,

// not including the terminating null that it writes after the last char.

// Returns a negative number if an error occurs.

 

 

 

_Q void ShowStats( void )

 // displays mean and standard deviation of Last10Voltages on LCD display

{       int num_chars_placed;

        BlankDisplayString(linebuffer, MAX_CHARS_PER_DISPLAY_LINE);

        num_chars_placed = sprintf(linebuffer, “%7.3f”, mean);

          // use field width = 7 chars; 3 digits to right of the decimal point

        if(num_chars_placed > 0)

                STRING_TO_DISPLAY(linebuffer, 1, FP_OFFSET);     // line 1

        BlankDisplayString(linebuffer, MAX_CHARS_PER_DISPLAY_LINE);

        num_chars_placed = sprintf(linebuffer, “%7.3f”, standard_deviation);

        if(num_chars_placed > 0)

                STRING_TO_DISPLAY(linebuffer, 3, FP_OFFSET);    // line 3

        UpdateDisplay();                                 // write to display

}

 

 

_Q void LogDataAndShowStats( void )

// increments matrix_index every second,

// loads input_voltage data collected by task 1 into Last10Voltages array,

// and displays statistics on LCD display every 10 seconds

{       float latest_input;

          // we need this temporary because we must do an uninterrupted fetch

          // from input_voltage, then pass the result to FARRAYSTORE

        matrix_index = (ReadElapsedSeconds()) % 10; // 0<= matrix_index <= 9

        if(matrix_index != last_index) // if a second has elapsed...

                {  latest_input = PeekFloatUninterrupted(&input_voltage);

                   FARRAYSTORE(latest_input, 0, matrix_index, &Last10Voltages);

                                                // store reading in matrix

                   last_index = matrix_index; // update last_index

                   if(matrix_index == 9)        // if 10 seconds have elapsed...

                           { CalcStats();       // calculate new statistics

                             ShowStats();       // update display

                           }

                }

}

 _Q void Statistics( void )

// this is the activation routine for the statistics task;

// it calculates and displays the mean and standard deviation of the data.

{

        DIM(float, 1, 10, &Last10Voltages);     // dimension as a 1-row array

        InitFPArray(0.0, &Last10Voltages);      // initialize array

        last_index = -1;                        // initialize last_index

        SetupDisplay();                         // write headings to display

        while(1)

           {     LogDataAndShowStats();          // calc and display statistics

                 Pause();

           }

}

 

 

// ********** BUILD TASKS***********

 

// Note: we’ll keep the Forth interpreter task active during

// development/debugging, and put it ASLEEP in the final turnkeyed version.

 

// First declare the tasks and allocate their 1K task areas:

// FORTH_TASK (see MTASKER.H) is the default task running QED-Forth;

// this task is automatically built and started upon each reset/restart;

// in the autostart routine FORTH_TASK puts itself ASLEEP so the end

// user can’t run Forth.

 

TASK ReadInputTask;        // data gathering task base xaddr

 

TASK ControlOutputTask;    // PWM task base xaddr

 

TASK StatisticsTask;       // statistics reporting task

 

// BUILD_C_TASK(HeapStart,HeapEnd,Base) and

// ACTIVATE(function_ptr, task_base_addr)

// macros are defined in \mosaic\mtasker.h file.

 

_Q void BuildTasks( void )

// Empties the round robin task loop and then

// carefully builds the tasks every time we start up.

// Note that only the statistics task has access to the heap.

{

        NEXT_TASK = TASKBASE;    // important! empties task loop before building

        SERIAL_ACCESS = RELEASE_ALWAYS; // allow sharing of serial ports

        BUILD_C_TASK(0,0,&ReadInputTask);

        BUILD_C_TASK(0,0,&ControlOutputTask);

        BUILD_C_TASK(DEFAULT_HEAPSTART,DEFAULT_HEAPEND,&StatisticsTask);

}

 

 

_Q void ActivateTasks( void )

        // associate activation routines with each of the tasks.

{

        ACTIVATE(GatherData, &ReadInputTask);

        ACTIVATE(SetPulseParameters, &ControlOutputTask);

        ACTIVATE(Statistics, &StatisticsTask);

}

 

 // ********************* SET UP AUTOSTART ROUTINE *********************

 

// We’ll designate the top level word main as the PRIORITY.AUTOSTART routine.

// Every time the QED-Board is powered up or reset, the main routine will

// automatically be executed by the default FORTH_TASK

// main() zeros the variable area and initializes the heap.

// During debugging, the FORTH task which runs main() has access to

// all defined function names declared using the _Q keyword;

// the names are sent to the QCard via the .DLF download file.

// This helps to debug the program.

// main() initializes the elapsed time clock, installs the

// OC3 interrupt service routines, and builds and activates the tasks.

// It releases control of the serial line, starts the timeslicer, and PAUSEs

// to begin execution of the application.

// After debugging is complete, the optional commands which

// specify a COLD restart and which put the FORTH_TASK ASLEEP can be inserted;

// these commands are “commented out” in the code shown here.

 

 

_Q void InitVariables(void)

{       // init static variables & strings at runtime, and delete array

        // to clean up the heap before allocating heap items

        input_voltage = 0;

        high_time = low_time = 0;

        DELETED(&Last10Voltages);

        matrix_index = last_index = 0;

        mean = standard_deviation = 0.0;

        BlankDisplayString(linebuffer, MAX_CHARS_PER_DISPLAY_LINE);

}

 

 

void main(void)

        // this is the highest level routine in the turnkeyed application.

{

        InitVariables();        // init variables, delete arrays

        INIT_DEFAULT_HEAP();    // it’s important to init heap at startup

        InitElapsedTime();      // initialize qed elapsed time clock

        InstallOC3();           // install service routine for pwm generation

        BuildTasks();           // initialize user areas of the tasks

        ActivateTasks();        // associate action routine with each task

// the following 2 commands are removed during debugging;present in final version

        //  ColdOnReset();      // ensures full initialization upon each reset

        //  STATUS = ASLEEP;    // puts forth task asleep;

        RELEASE(SERIAL);        // in case another tasks needs to use serial

        StartTimeslicer();      // starts elapsed time clock, enables interrupts

        Pause();                // start next task immediately

}

 

 

 

// Now from your terminal, type:

//   CFA.FOR  main    PRIORITY.AUTOSTART

 

// this will install main() as the routine that is automatically executed

// each time the board is reset or powered on.

 // The PRIORITY.AUTOSTART routine initializes the priority.autostart vector

// at locations 7FFA-7FFF on page 4 which will be in Flash.

 

// Then upon the next restart the main() routine will automatically execute.

// NOTE:   To erase the autostart vector and return to QED-Forth,

//         type at the terminal:

//                      NO.AUTOSTART

//         If you have put the FORTH_TASK asleep so that it does not

//         respond to the terminal,

//         activate the Special Cleanup Mode by installing jumper J1 and

//         pressing the reset button.

//         The Special Cleanup Mode erases the autostart vector and restores the

//         board to a “pristine” condition (it also sets the default

//         baud rate to 19200, and the LCD display type to 4x20 character

//         display; use Baud1AtStartup() and IsDisplay() if you need to

//         change these defaults).

//

// ********* BURNING THE FINAL APPLICATION ***********

 

// To generate a Flash chip that contains this application,

// download the TURNKEY.DLF file, and execute the

//  CFA.FOR MAIN  PRIORITY.AUTOSTART

// command to install the autostart vector.

// Before resetting the board or typing main (which could put FORTH asleep

// and make communications difficult), we’ll make an image of page 4

// which holds the object code and autostart vector:

// To make a Motorola S2-record file to send to the Flash programmer,

// set your terminal to capture incoming text to a disk file using the

// “Save File” menu item, and interactively type from the terminal:

//      HEX  0 4  DIN 0  8000  DUMP.S2

// Close the file when the dump terminates, and use Textpad to remove

// any extraneous file contents such as the DUMP.S2 command and Forth’s

// “ok” prompts.  Then send the resulting file to a Flash Programmer and

// burn a 512k Flash chip.

// Power up the QCard and it will automatically run the application!

<< Previous | Next>>


Home|Site Map|Products|Manuals|Resources|Order|About Us
Copyright (c) 2006 Mosaic Industries, Inc.
Your source for single board computers, embedded controllers, and operator interfaces for instruments and automation