Link here

A Turnkeyed C Application Program

Presents and describes a C-language multitasking program that performs data acquisition and control functions for instrument control.

This chapter presents an application program that integrates a range of hardware and software features of the PDQ Single Board Computer (SBC). The application is "turnkeyed", meaning that it can be configured to autostart each time the board is powered up. This chapter explains the elements of the program and provides an example of C coding for the PDQ Board.

The example application reads a voltage using the onboard analog to digital (ATD) converter and outputs two pulse width modulated (PWM) signals. Each of these PWM outputs, when time-averaged using a simple resistor and capacitor low-pass filter, tracks the input voltage. One of the outputs is generated by the PWM subsystem on PORTP of the Freescale HCS12 (9S12) microcontroller. The PWM subsystem creates a pulse train without the need for interrupts or cycle-by-cycle intervention by the processor, as described in the “Pulse-Width Modulated I/O” chapter of this document. The other pulse output is created by the HCS12’s ECT (Enhanced Capture/Timer) subsystem on PORTT. This output is fashioned by an output compare interrupt that generates each signal transition as described in the “Timer Controlled I/O” chapter. In addition, the program calculates and displays the mean and standard deviation of the input signal at the terminal. This application demonstrates how to:

  • Use the pre-coded library routines
  • Take advantage of the “Make” utility which automatically sets up a memory map that is compatible with a turnkeyed application in Flash
  • Use the onboard ATD converter
  • Use floating point mathematics to calculate means and standard deviations
  • Use the PWM subsystem to generate a pulse train output
  • Write and install an output compare interrupt routine that generates a PWM output
  • Write the application using a modular multitasking approach
  • Configure the application to automatically start upon power-up or reset

The program is called TURNKEY.c and it can be found in "Turnkey Demo" project.

Open a new "Turnkey Demo" project for a downloadable version of this code.

Look for this icon under Project→ New Project:
industrial automation control demo
Turnkey Demo

The commented application code is presented at the end of this chapter. The text of this chapter will frequently refer to the code, offering background and explanation. Some of the section titles in this chapter are keyed to the titled sections in the code which are set off with asterisks.

 

Overview of the application

The main activities of this application are performed by 3 modular tasks and one interrupt routine:

  • The first “data gathering” task collects data from channel 0 (or any other channel that you designate) of the 10-bit ATD converter, converts the reading to its equivalent voltage, and saves it in a floating point variable called input_voltage.
  • The second “PWM output” task is responsible for the duty cycle of two pulse trains, each having an average value that matches the latest input_voltage measured by the data gathering task. This task calculates the required duty cycle, high_time and low_time of a pulse width modulated (PWM) output signal. It updates the duty count of the 16-bit resolution PWM01 output under the control of the HCS12’s PWM subsystem on PORTP pin PP1 (pin 15 of the PDQ Digital Field Header). Once configured, this creates a PWM output under hardware control with no intervention from the CPU (Central Processing Unit) aside from updates to the PWM duty register.
  • An interrupt attached to the output compare associated with the Enhanced Capture/Timer channel 0 (ECT0) controls the pulse output signal appearing at PORTT pin PT0 (pin 24 of the PDQ Digital Field Header). The interrupt service routine updates the OC3 timer registers based on the high_time and low_time calculated by the output control task.
  • Once each second for ten seconds, the third “statistics” task samples the input voltage calculated by Task 1 and stores it as a floating point number in a FORTH_ARRAY called Last10Voltages. When 10 seconds have elapsed, this task calculates the mean and standard deviation of the 10 data points and writes them to the serial port for display on the terminal. It then starts filling the array with new data, repeating the entire process.

There is one additional task that is always present in this application: the default task named FORTH_TASK that runs the QED-Forth operating system and executes main(). In the default version of this application we put the QED-Forth task ASLEEP. This prevents the end user of the system from gaining access to the Forth interpreter, and allows the statistics task to use the Serial1 port to send out the data summaries to the terminal. By adding the UseSerial2() command to the statistics task and commenting out the "STATUS = ASLEEP;" line, you can use the interactive QED-Forth task during development and debugging, enabling you to execute commands and monitor the performance of the routines that are being tested.

 

Hardware required for the example application

This sample application requires a PDQ Board or PDQScreen, two resistors, two capacitors, a potentiometer, and a voltmeter to verify proper operation. An optional operational amplifier would allow the output signal to drive loads other than a high impedance voltmeter or oscilloscope. Please be careful not to short pins together when working with the on-board connectors.

A 10 Kohm potentiometer is connected to place a controllable voltage on AN0, which is channel 0 of the ATD converter on the HCS12. The potentiometer may be from 1 Kohm to 100 Kohm in value. The potentiometer is connected between +5VAN and AGND analog ground (pins 2 and 1, respectively, of the Analog Field Header), with the potentiometer’s wiper connected to AN0 (pin 24 of the PDQ Analog Field Header).

PWM outputs are independently generated on two pins, one on PORTP generated by the PWM subsystem, and one on PORTT generated by the ECT subsystem. One pulse train appears on PP1 (that is, pin 1 of PORTP) at pin 15 of the PDQ Digital Field Header. The other pulse train appears on PT0 at pin 24 of the PDQ Digital Field Header. On each of these output pins, a series resistor followed by a capacitor to ground are connected to integrate the square-wave output signal to a steady average voltage across the capacitor. The capacitor voltage can be measured with a high impedance voltmeter or oscilloscope. An optional amplifier can be used if the output must drive lower impedance loads.

The statistical report generated by Task 3 is sent to the terminal via one of the serial ports (serial1 is the default, but this can be changed to serial2 as described below).

 

The memory map

The first step in programming an application is assigning the memory areas that will be occupied by the object code, variable area, and the heap which holds FORTH_ARRAYs in paged memory. Fortunately, the Mosaic IDE Plus automatically sets up a versatile default memory map. This is discussed in detail in the chapter titled Using Paged Memory. For reference, here is a brief summary of the main memory areas allocated by GNU C:

  • 0x8000-BFFF (16K per page) in pages 0x00-0x13 holds up to 320Kbytes of compiled application code (on pages 00-0x0F) and debugging headers (starting at page 0x10). Pages 0x00-0x17 can be backed up to shadow flash and restored at each COLD restart using the SAVE.ALL interactive command. Pages 00-0x13 can also be write protected using the interactive WP.ALL command.
  • 0x8000-BFFF (16K per page) in pages 0x14-0x1C is paged RAM. Pages 0x18-1C is a heap area that holds array data. The DEFAULT_HEAPSTART constant equals the heap start xaddress (0x188000), and the DEFAULT_HEAPEND constant equals the end xaddress of this region (0x1CBFFF), as declared in the C:\MosaicPlus\c\libraries\include\mosaic\HEAP.h file.
  • 0x0800-0FFF and 0x2000-7FFF is 26K available common RAM which hold C variables, C arrays, as well as the pfa’s (parameter field areas) of FORTH_ARRAYs.
  • 0x0680-07FF is available EEPROM referenced by the eeprom section {EEPROM at 0x0400-067F is reserved for startup utilities and interrupt revectoring}. To locate a variable in eeprom, append the _eeprom tag, as in:
    int testEEVar  _eeprom;

  • The operating system occupies onchip flash at 0x8000-BFFF on pages 0x38-3F, common flash at 0xC000-FFFF, and reserves 0x8000-BFFF on pages 0x1D-1F for system RAM and memory-mapped device addressing.

The object code ends up in RAM that is backed up to and restored from the shadow flash. The variable area is in common RAM that is accessible regardless of the page, and the heap is located in paged RAM. The variable area includes the areas where variable values are stored, where TASK areas (the tasks’ stacks, buffers, pointers, etc.) are allocated, and includes the parameter fields that hold addressing and dimensioning information associated with heap items. The heap in paged memory holds FORTH_ARRAYs and other data structures. Both variables and the contents of the heap must be subject to rapid change while the application is running, so they can never be placed in Flash.

 

Other memory areas

The paged memory starting at page 0x10 is used to hold the QED-Forth definitions (names and object code) that facilitate interactive debugging. This region is typically not needed after debugging is done, and so it does not have to be included in the final downloaded production image. If interactive function calling capability is required in the final application (for example, if you want to give the end user or a customer service person the ability to execute interactive commands from a terminal), then this area should be included in the final turnkeyed system.

 

ATD data gathering task

The first section of the example application code uses the HCS12’s analog to digital (ATD) converter to convert the voltage input derived from the external potentiometer to a digital value. As explained in the Data Acquisition Using Analog to Digital Conversion chapter of this document, each conversion result is returned as a 10-bit field left justified in a 16-bit integer result, and can be interpreted as a 16-bit unsigned positive result between 0 and 65535. The task converts the ATD reading into its equivalent floating point voltage which spans 0.0 to 5.0 Volts, and stores it in a variable called input_voltage.

We define some simple constants that specify the high and low reference voltages of the ATD, and the number of counts (2^16 = 65536 counts). The CountToVolts() function converts the ATD reading into its equivalent floating point voltage (a number between 0.0 and 5.0 Volts). GatherData() is the activation routine for this task. It calls ATDOn() to power up the ATD, and enters an infinite loop that acquires an ATD sample, converts it to a voltage, and stores it in the variable input_voltage which other tasks can read.

Special storage operators named PeekFloatUninterrupted() and PokeFloatUninterrupted() are declared using the lock() and unlock() keywords. As explained in the How to temporarily disable interrupts section, these functions temporarily disable interrupts and restore the global interrupt enable flag (the I bit in the Condition Code Register) to its prior condition. The uninterruptable peek and poke routines are used because the input_voltage variable holds data that is accessed by more than one task. The uninterruptable store and fetch operators ensure that 32 bit data is not misread because of intervening interrupts or task switches.

The Pause() cooperative task-switch command forces at least one task switch on each pass through the infinite loop of GatherData(); we also rely on the timeslicer to switch tasks every millisecond.

 

Pulse Width Modulation (PWM) task

The goal is to create two PWM outputs, each of whose average voltage is equal to the input voltage read by the data gathering task. The period of each PWM signal is arbitrarily chosen to be 100 milliseconds (ms). This code specifies the activity of the task that calculates the high_time and low_time parameters needed to generate the pulse width modulated output signals. One of the signals is generated by the PWM subsystem on PORTP; after configuration, a simple write to the duty register of this output is sufficient to change the PWM duty cycle to reflect the latest value of the analog input. The other pulse output on PORTT is generated with the help of the ECT0_ID (Enchanced Capture/Timer channel 0) output compare interrupt. This interrupt routine (described in the next section) controls the PORTT output signal subject to the high_time and low_time calculated by this task.

We could perform all of the duty cycle computations in the interrupt service routine that controls the PWM output, but this is not a good practice. Long interrupt service routines can delay the processor’s ability to respond to other interrupts in a timely manner. The best approach is to perform the more time-consuming computational functions in foreground tasks so that the associated background interrupt service routines execute very rapidly.

We define static integer variables to hold the high_time and low_time which, as their names imply, hold the number of timer counts that the PWM signal is high and low. Floating point constants LOW_OUTPUT_VOLTAGE and HIGH_OUTPUT_VOLTAGE are defined to specify the voltage levels corresponding to logic level 0 and logic level 1; for maximum accuracy you could measure the low and high voltage levels on pins PP1 and PT0 and set these constants accordingly.

The default period of the TCNT free-running timer which drives the ECT system is 1.6 microseconds (us). The period of each PWM output is chosen to be 100 ms, which corresponds to 62,500 TCNT counts. For simplicity and symmetry, we also configure the clock source for the PWM01 output to have a 1.6us period. This means that the high time for each of the two PWM outputs should be equal. The high time of the PWM01 output on PORTP pin PP1 is simply the value written to its duty register, so the PWM task performs this write as:

PWMDutyWrite( high_time, PWM01 );  // write high_time to channel PWM01 duty register 

We enforce a minimum time between interrupts via the constant MINIMUM_HIGH_OR_LOW_TIME which is approximately equal to the maximum number of timer counts divided by 1000. That is, we only require 10 bits of resolution from our PWM; after all, the output is meant to mimic an analog input that is converted with 10 bits of resolution (1 part in 1024). The benefit of imposing a minimum high or low time is that by doing so we can ensure that the minimum time between ECT output compare interrupts is more than adequate to allow the interrupt service routine to execute.

We next define a routine that calculates the PWM duty cycle such that the average output signal matches the latest measured input_voltage. The HighAndLowTimes() function converts the calculated duty cycle into the parameters needed by the interrupt service routine, and SetPulseParameters() is the infinite loop that serves as the activation word for the PWM task. Note once again that we have put Pause() in the loop so that both cooperative and timesliced multitasking are used.

 

ECT Output Compare interrupt code

This code defines an interrupt service routine and an installation routine for the ECT0_ID (Enchanced Capture/Timer channel 0) output compare interrupt which controls the PORTT output signal on pin PT0. The relevant HCS12 hardware register names are defined in the hcs12regs.h file in the C:\MosaicPlus\c\libraries\include\mosaic directory.

ECTPulseMaker() is the interrupt service routine that controls the state of the PT0 output bit. It relies on the ability of the output compare (OC) function to automatically change the state of an associated PORTT output bit at a specified time. Specifically, the ECT0 output compare can be configured to automatically change the state of the associated output pin PT0 when the count in the TC0 register matches the contents of the free-running counter (TCNT) register; this is called a “successful output compare”. Invoking the OCAction() function with the OC_SET_ACTION parameter causes the output to go high on the next successful compare, and invoking OCAction() with the OC_CLEAR_ACTION parameter causes the output to go low on the next successful compare. The ECTPulseMaker() routine simply reverses the target pin state and adds the appropriate high_time or low_time increment to the TC0 register to specify when the next interrupt will occur.

Recall that high_time and low_time are calculated by the foreground PWM task, so the interrupt has very little to do and can execute rapidly. In fact, most of the execution time of the ECTPulseMaker() routine is consumed calling the OCAction() function. Calling an operating system function from a C program requires about 3 us (microseconds) of overhead, plus about 2 us per parameter passed to the function. To rigorously optimize the speed of this interrupt service routine, you could eliminate the call to OCAction() and instead write directly to the ECT0 mode and level bits that control the pin action. This would involve studying the Motorola ECT Block User Guide document, defining the appropriate bit masks, and coding the instructions to manipulate them. The service routine as shown is reasonably fast, and uses the built-in driver function to yield simple and maintainable code.

InstallPulseMaker() calls OutputCompare() and OCAction() to enable direct hardware control of PORTT pin PT0 by the ECT0 channel. It calls ATTACH() to post ECTPulseMaker() as the ECT0_ID interrupt handler routine, sets the “fast clear mode” so that the service routine does not have to explicitly clear the interrupt flag bit, and enables the ECT0 interrupt. The SetPulseParameters() PWM task activation routine calls the InstallPulseMaker() initialization routine when the task starts up. Note that MAKE_ISR() must be called with ECTPulseMaker as an argument for ATTACH() to execute properly.

 

Statistics task

The code in this section continuously loads a matrix with one input voltage acquired in each of the last 10 seconds, and writes a 1-line summary of the mean and standard deviation of this data to the terminal every 10 seconds. We define a single-row FORTH_ARRAY called Last10Voltages to hold the data; this array resides in the heap associated with the statistics task. Two variables keep track of the current and prior matrix indices; these aid in managing storage of data into Last10Voltages.

CalcStats() calculates the latest calculated mean and standard deviation values, and ShowStats() writes them to the serial port. ShowStats() uses printf() to print the floating point numbers. The format is specified so that each floating point number will occupy the same number of spaces each time it is displayed. LogDataAndShowStats() fills the Last10Voltages array with measured voltages and calls the subsidiary functions to display the statistical results every 10 seconds.

Statistics() is the activation routine for the task; it dimensions and initializes the data array and enters an infinite loop that logs the data and displays the statistics. If you want to simultaneously debug this program using the serial1 port while seeing the statistics printout on the serial2 port, insert the UseSerial2() command before the infinite loop in the Statistics() routine. The default baud rate for both the serial1 and serial2 ports is 115200 baud; this can easily be changed by using the Baud routine in either C or interactive Forth. Pause() is included in the infinite loop so that both cooperative and timesliced task switching are used.

This task calls the PauseOnKey() function. When you type a Carriage Return (labeled "ENTER" on many keyboards), PauseOnKey() calls Abort() to end the program and enter the Forth monitor. This lets you regain control of the processor.

If you have used PRIORITY.AUTOSTART:
to set up this program to autostart, typing a carriage return followed by NO.AUTOSTART will remove the autostart vector so that other programs may be loaded into the controller.
 

Build and activate the tasks

Now that we have defined the activation routines for the tasks, it is time to execute the TASK statement to name the tasks, allocate their 1K task areas in common RAM and set up the tasks. The routine BuildTasks() initializes the user area and stack frame of each task, and links the tasks into a round-robin loop. The first statement of this routine is very important:

NEXT_TASK  = TASKBASE;

When executed, this command makes the currently operating task (which will always be the default FORTH_TASK) the only task in the round-robin loop. This sets a known startup condition from which the task loop may be constructed.

The command

SERIAL_ACCESS = RELEASE_ALWAYS;

is inserted to make sure that all tasks release the serial port after each character is sent or received. In applications in which more than one task tries to access the serial port, this command avoids serial port contention that can effectively “silence” the program.

Note that ReadInputTask and ControlOutputTask are built with a null heap specification because they do not access any heap items. The StatisticsTask, however, does access a heap item. It requires a heap specified by DEFAULT_HEAPSTART and DEFAULT_HEAPEND which are defined in the HEAP.h header file in the C:\MosaicPlus\c\libraries\include\mosaic directory. Note that we also use the same heap specification for the default FORTH_TASK; see the INIT_DEFAULT_HEAP() routine. The FORTH_TASK is not active in the final application, so the sharing of this heap space does not cause a conflict during operation.

ActivateTasks() simply activates each of the three tasks with the appropriate action routine. Each action routine is an infinite loop that performs the desired activity of the task.

 

Define the main routine

The main() function is the top level routine in the application. After execution of the PRIORITY.AUTOSTART: command as explained below, it will be called each time the board is powered up or reset. The operating system always wakes up and enters the default FORTH_TASK upon every restart, so the default QED-Forth task is always the task that calls main().

It is good programming practice to initialize all variables each time the application starts up; this is done by the InitVariables() function. After initializing the variables, main() calls INIT_DEFAULT_HEAP() to initialize the heap of the FORTH_TASK, calls InitElapsedTime() to initialize the elapsed time clock to zero, and builds and activates the tasks.

The next command in main() is

ColdOnReset()

which forces a COLD restart every time the machine is reset. This enhances the operating security of a turnkeyed application by ensuring that every user variable and many hardware registers are initialized after every restart and reset. This command may be commented out during debugging so that restarts do not cause QED-Forth to FORGET all of the defined functions in the application program.

The main() function then puts the default FORTH_TASK asleep by executing:

STATUS = ASLEEP;

This takes effect once multitasking commences, and does not prevent execution of the remainder of main(). This command may be commented out during program development so that the awake QED-Forth task can be used to aid in debugging; in this case, the UseSerial2() command should be inserted in the Statistics() task activation routine as discussed above.

The main() function then RELEASEs the FORTH_TASK’s control of the serial line. This is necessary whenever another task requires access to the serial port. The final commands start the timeslicer (which also starts the elapsed time clock and globally enables interrupts) and Pause() to immediately transfer control to the next task in the round-robin loop. The final Pause() ensures smooth operation in applications where tasks other than QED-Forth require access to the serial port.

 

Compile the program

To compile, assemble and link the program and create the download file, simply:

Open a new "Turnkey Demo" project for a downloadable version of this code.

Look for this icon under Project→ New Project:
factory automation
Turnkey Demo

Once the demo is open, click Build→ Build. When the compilation is complete, you can view the warning messages and highlight the associated source code lines. (None of the warnings adversely affect the operation of this program.)

Make sure that your PDQ Board is turned on and is communicating with the Mosaic Terminal. Then download the program to the PDQ Board by using the terminal’s “Send File” menu item to send the TURNKEY.dlf file.

 

Using SAVE, RESTORE and write-protection during debugging

Note that the download file contains the SAVE.ALL directive, which automatically backs up the code image to the shadow flash, and causes it to be automatically reloaded upon each COLD restart. You can optionally type at the terminal:

WP.ALL↓

to write protect all the protectable pages; to undo this directive, type

WE.ALL↓

to write enable all of the pages. The chapters titled Your First C Program and Writing and Compiling Programs explain these concepts in detail.

 

Going into production

To load a pristine board with the application, simply download the TURNKEY.DLF file, and execute the

PRIORITY.AUTOSTART: MAIN

command to install the autostart vector. Power cycle the Board and it will automatically run the application!

The downloaded program file typically executes the SAVE or SAVE.ALL command which store relevant memory map pointers into reserved locations in EEPROM. You can proceed to interactively test each function in the program one at a time as described here. If a crash occurs, simply type RESTORE to bring back access to all of the interactively callable function names. The use of SAVE and RESTORE can greatly reduce the number of times that you have to re-download your code during debugging.

 

Configure the board to autostart the program

After debugging is completed interactively type the QED-Forth command:

PRIORITY.AUTOSTART: main↓

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 the top of page 0x0F RAM and page 0x0F shadow flash, ensuring that the vector will be present whenever code is restored from the shadow flash.

Then upon the next restart the main() routine will automatically execute. Note that the QED-Forth V6.xx greeting is suppressed when an autostart routine is installed; if desired, you could easily print your own greeting by modifying the main() function.

To erase the autostart vector and return to the QED-Forth prompt, hit the “Enter” key on your terminal to cause the program to return to the Forth monitory, then type at the terminal:

NO.AUTOSTART↓

If the program does not respond to the terminal, activate the Special Cleanup Mode by installing the jumper labeled “Clean” next to the reset button, and then 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 115200, and sets the default serial port as serial1. If you need to change these defaults, invoke the interactive BAUD and/or USE.SERIAL2 commands.

The PRIORITY.AUTOSTART: command is used to configure systems that will go into production. For one-of-a-kind prototypes, another operating system command called simply AUTOSTART: is available that installs the autostart pattern in the on-chip flash that resides in the HCS12 proceessor chip. The pattern installed by AUTOSTART: is in the processor chip and not in the paged RAM along with the program code. Both autostart vectors are useful, but the priority autostart is often preferred because it keeps the autostart vector in the same region of memory as the program code.

You can monitor the operation of the turnkeyed program by connecting a voltmeter or oscilloscope across the filter capacitor on the PWM outputs, and by watching the update of statistics every 10 seconds on your terminal. Adjusting the input potentiometer should result in an output voltage that tracks the input.

 

Turnkeyed application code listing

Listing 14-1

// 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 2009 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 an 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 PDQ Board or PDQScreen.
 
// Description of the application:
// This program reads an input voltage, creates two pulse width modulated (PWM)
// signals whose average values track the input voltage, and reports the
// mean and standard deviation of the input voltage.
// One of the PWM signals is created using the HCS12 PWM subsystem on PORTP,
// and the other PWM output is created using the HCS12 ECT (Enhanced Capture/Timer)
// subsystem on PORTT.
// The following 3 tasks do the work:
 
// Task 1: Reads 10-bit Analog To Digital (ATD) input AN0,
//         measuring the voltage on a potentiometer
//         connected to AN0 (pin 24 of the Analog Field Header).
 
// Task 2:
//         1. This task outputs on PORTP bit 1 (PP1 is pin 15 of the
//         Digital Field Header) a PWM created without interrupts by the
//         16-bit concatenated PWM01 output operated by the
//         PWM subsystem of the HCS12 processor on PORTP.
//         2. This task outputs on PORTT bit 0 (PT0 is pin 24 of the Digital Field
//         Header) a PWM signal whose average equals the ATD input.  This PWM is
//         generated using an Output Compare associated with the ECT0_ID interrupt.
 
// Task 3: Every 1 second puts ATD voltage 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 Mosaic IDE Plus
//      Proper initialization of tasks at startup
//      Autostarting
//      Interrupt service routines and interrupt attaching
//      Initialization of the heap memory manager
//      Timesliced multitasking
//      ColdOnReset() for secure restart
//      Floating point calculations
//      Going into production
 
 
 
// ************************ Default Memory Map ************************
 
// The "Making Effective Use of Memory" chapter in the C User Guide
// provides a complete description of the PDQ memory map.  This is a brief summary:
 
// 0x8000-BFFF (16K per page) in pages 0x00-0x13 holds up to 320Kbytes of
// compiled application code.  Pages 0x00-0x17 can be backed up to shadow flash
// and restored at each COLD restart using the SAVE.ALL interactive command.
// Pages 00-0x13 can also be write protected using WP.ALL.
 
// 0x8000-BFFF (16K per page) in pages 0x14-0x1C is paged RAM.
// Pages 0x14-1C are allocated as a heap area that holds FORTH_ARRAY data.
// The DEFAULT_HEAPSTART constant equals the heap start xaddress (0x188000), and
// the DEFAULT_HEAPEND constant equals the end xaddress of this region (0x1CBFFF),
// as declared in the C:\MosaicPlus\c\libraries\include\mosaic\heap.h file.
 
// 0x0800-0FFF and 0x2000-7FFF is 26K available common RAM
//      which hold C variables, C arrays,
//      as well as the pfa’s (parameter field areas) of FORTH_ARRAYs.
 
// 0x0680-07FF is available EEPROM referenced by the eeprom section
//      {EEPROM at 0x0400-067F is reserved for startup utilities
//       and interrupt revectoring}.
//       To locate a variable in eeprom, append the _eeprom tag, as in:
//          int testEEVar  _eeprom ;
 
// The operating system occupies onchip flash at 0x8000-BFFF on pages 0x38-3F,
//      common flash at 0xC000-FFFF, and reserves 0x8000-BFFF
//      on pages 0x1D-1F for system RAM and memory-mapped device addressing.
 
#include <mosaic\allqed.h>     // include all of the ansi C and mosaic functions
 
 
// *********************** ATD Data Gathering Task *********************
 
// This task gathers data from the Analog To Digital (ATD) converter
// and places it in a variable that is easily accessed by other tasks.
 
// Define the control registers and some useful constants:
 
#define ATD_INPUT_CHANNEL       0
// You can use any channel you want and place the input potentiometer on
// the corresponding ATD input pin.  For example, if 0 is your input
// channel, place the input signal on the pin labeled AN0 (pin24 of
// the Analog Field Header).
 
 
static float input_voltage;
// Holds voltage corresponding to latest result of ATD input. Because the
// contents of this variable are shared among several tasks, we use
// uninterruptable PokeFloatUninterrupted and PeekFloatUninterrupted to access it.
 
// The ATD reading is a 10-bit field left-justified in a 16-bit number.
// As explained in detail in the "Data Acquisition" chapter of the C User Guide,
// this ATD value can be treated as a 16-bit value that ranges from 0 to 65535.
// We want to convert this 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/65536]+low.ref.voltage
// See the "Data Acquisition" chapter in the C User Manual for a
// discussion of this equation.
 
 
// First let’s define some constants:
#define FULL_SCALE_COUNTS       65536  // left-justified value is treated as 16 bits
#define LOW_VREF                0.0
#define HIGH_VREF               5.0
// NOTE: For maximum accuracy, place a voltmeter between AGND and +5VAN
//       (pins 1 and 2, respectively, on the PDQ Analog Field Header)
//       to measure +5VAN, and set HIGH_VREF equal to the result.
//       Or, if you want to use a signal other than the AGND analog ground
//       as your voltage ground, measure both signals with respect to your
//       chosen ground, and set the LOW_REF and HIGH_REF constants accordingly.
 
 
float _Q CountToVolts(uint count)
// This routine converts a 16-bit ATD measured count n { 0 <= n <= 65535 }
// to the corresponding floating point voltage r
// {typically, 0.0 <= r <= 5.0 }  by solving the equation:
// r = [ (high.ref - low.ref) * count / 65536 ]  + low.ref
{  return( ((HIGH_VREF - LOW_VREF) * count / FULL_SCALE_COUNTS) + LOW_VREF );
}
 
 
void _Q PokeFloatUninterrupted(float value, float* destination )
// stores value into the address specified by destination;
// the lock() and unlock() keywords guarantee that
// interrupts are disabled while the store operation occurs;
// this prevents corruption of data when two tasks are accessing the same
// 4-byte variables
{  uint prior_ccr_state = lock();  // save prior I-bit, disable irqs
   *destination = value;           // store the value while irqs are disabled
   unlock(prior_ccr_state);       // restore prior I-bit state
}
 
float  _Q PeekFloatUninterrupted(float *  source )
// fetches and returns the contents from the address specified by source;
// the lock() and unlock() keywords guarantee that
// interrupts are disabled while the fetch operation occurs;
// this prevents corruption of data when two tasks are accessing the same
// 4-byte variables
{  float source_contents;          // declare local variables
   uint prior_ccr_state;
   prior_ccr_state = lock();       // save prior I-bit, disable irqs
   source_contents =  *source;     // fetch the value while irqs are disabled
   unlock(prior_ccr_state);       // restore prior I-bit state
   return source_contents;
}
 
 
void _Q GatherData(void)
// This is the activation routine for the data gathering task.
// It continually acquires readings from the ATD, converts the readings
// to voltages, and updates the input_voltage variable that is accessed
// by other tasks.
{       uint sample;
        float sample_voltage;
        ATDOn(ATD_INPUT_CHANNEL);       // turn on ATD containing the input channel
        while(1)                        // start infinite loop
        {       sample = ATDSingle(ATD_INPUT_CHANNEL);  // get 1 sample
                sample_voltage = CountToVolts(sample);  // convert to volts
                PokeFloatUninterrupted(sample_voltage, &input_voltage); // store it
                Pause();                // let other tasks run
        }
}
 
 
// ********************* Pulse Width Modulation (PWM) Task ********************
 
// This task oversees the generation of two pulse-width modulated (PWM) outputs:
// 1. One of the outputs is generated by the hardware PWM subsystem on the HCS12
// processor which drives the PORTP pins.  This concatenated 16-bit-resolution
// PWM01 output appears at PORTP pin PP1 (pin 15 of the PDQ Digital Field Header).
// See the "PWM" chapter in the C User Manual.
// 2. The other pulse output is generated by the Enhanced Capture/Timer (ECT)
// subsystem on the HCS12 processor.  An Output Compare channel on ECT0 generates
// the pulse train on PORTT pin PT0 (pin 24 of the PDQ Digital Field Header).
// An interrupt service routine responds to the ECT0_ID interrupt to form
// each edge of this signal.
// See the "Timer Controlled I/O" chapter in the C User Manual.
 
// This PWM task's activation routine calculates the high time and low time
// of the PWM outputs based on the value of the input_voltage which is updated
// by the data gathering task.  A 100 millisecond (ms) period is used
// for both of the PWM outputs; consequently, we want their duty cycles to be equal.
// The goal is to set the duty cycle of each PWM output so that the time-average of
// the PWM signal equals the input_voltage (the analog voltage at the ATD input pin).
// 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 logic-low and logic-high
//      voltage levels that appear on the digital PWM output pins (PP1 and PT0)
//      in the low and high states, respectively.
 
 
// Given the duty cycle, and given our choice of a PWM period of 100 msec, we
// calculate the high_time and low_time of the PWM signal in terms of the timer
// counts; each timer count equals 1.6 microseconds, which is the default
// ECT timebase set by the operating system.  We assume here that the programmer
// has not installed a different value using ECTPrescaler().
// 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 100 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 = 100 msec/1.6 microsec = 62,500.
 
// We also "clamp" high_time and low_time to a minimum value to match the
// PWM resolution to that of the incoming ATD signal (which has 10 bit resolution)
// and to prevent a situation where ECT interrupts are requested so rapidly
// that the processor can’t service one before the next interrupt request occurs.
 
// The ECT0 (output compare channel 0 of the ECT Enhanced Capture/Timer system)
// interrupt code in the subsequent section uses these calculated values
// to pulse width modulate the PORTT output port pin PT0.
 
// 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;
// the fractional portion of the full scale voltage represented by the analog input
 
#define LOW_OUTPUT_VOLTAGE      0.0    // = voltage on the PT output when it is low
#define HIGH_OUTPUT_VOLTAGE     5.0    // = voltage on the PT output when it is high
// NOTE: for maximum accuracy, the voltage outputs of pins PT0 and PP1 could be
//   measured in the low and high states and LOW_OUTPUT_VOLTAGE and
//   HIGH_OUTPUT_VOLTAGE could be set accordingly.
 
#define MS_PER_PERIOD   100    // use a 100 msec period for PWM output
                               // NOTE: the timer "rolls over" every 104.86 msec
 
#define TIMER_COUNTS_PER_PERIOD         62500
//   100 milliseconds corresponds to 62500 counts of 1.6 microseconds (usec)
//   each on the free-running timer.  The operating system sets the default
//   TCNT period to 1.6 usec.  We assume here that the programmer has not
//   installed a different value using ECTPrescaler().
//   For symmetry and simplicity, we also set the clock source period of the
//   PWM01 channel to equal 1.6us.
 
 
#define MINIMUM_HIGH_OR_LOW_TIME  ((int) 60)    // under 100usec
// we choose a minimum high or low time approximately equal to the maximum
// number of timer counts divided by 1000.  That is, we only require 10 bits of
// resolution from our PWM; after all, this is the resolution of the ATD reading
// that we are attempting to mimic.
 
 
void _Q CalcDutyCycle(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
{ float latest_input = PeekFloatUninterrupted(&input_voltage);
  duty_cycle = (latest_input - 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
}
 
 
void _Q CalcHighAndLowTimes(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
}
 
 
// ************* PWM Output Using HCS12's PWM Subsystem *********
 
// PWM01    is a 16-bit concatenated PWM output comprising 8-bit channels 0&1.
// Its output appears on pwm1 pin = PP1 (pin 15 of the PDQ Digital Field Header).
// The driving clock period is chosen to be 1.6us to equal the ECT driving clock.
// PWM01 is declared as an active-high, non-center-aligned output.
// This means that the "duty" value written to the PWM01 channel
// should equal the high_time value calculated above.
// Reasoning: high_time is the 16-bit high time of a PWM signal with 100msec period
// and 1.6us driving clock.
// This is the definition of the "duty" value of an active-high 16-bit PWM signal.
// Thus, we can use the PWMDutyWrite() function to update the duty cycle
// as shown in the SetPulseParameters() function below.
 
#define CLOCKA_1_6USEC  32
// prescaler for clockA period = 32 * 0.05us Eclk.period = 1.6us
 
 
void _Q Setup16BitPWM(void)
// initialize PWM01 output on pin PP1 for 1.6us driving clock period,
// 16-bit concatenated channel, starting at 50% duty cycle.
{ PWMPrescaler(PWM_CLOCKA, CLOCKA_1_6USEC); // clockA period = 1.6us
  PWMUnscaledClock(PWM01);      // PWM01 uses unscaled clockA @ 1.6us
// now call PWMSetup() for PWM01, the next comment line recaps the prototype:
// PWMSetup( active_high, scaled_clock, centered, period, duty, channel_id )
  PWMSetup(TRUE, FALSE, FALSE, TIMER_COUNTS_PER_PERIOD, TIMER_COUNTS_PER_PERIOD/2, PWM01);
  PWMEnable(PWM01);         // enable PWM01, starts at 50% duty cycle
}
 
// ********************* ECT Output Compare Interrupt Code **********************
 
// This interrupt routine generates a pulse width modulated output on PortT
// pin PT0 based on the duty cycle calculated by the PWM task.
// We use C to define an interrupt handler.
//   Unless the "Fast Clear Mode" (available only for ATD and ECT systems),
//   the handler must clear the interrupt request flag by writing a 1 to it.
//   In this case, we use the fast clear mode.
// We install the interrupt handler using the Attach() function,
// and locally enable the interrupt.
 
#define PT0_ID 0         // name the channel_id that we're using
// don't confuse this with the kernel's ECT0_ID which is an argument for ATTACH
 
int  current_state = 0;  // used by ISR to track state
 
void _Q ECTPulseMaker( void )  // interrupt service routine for output compare 0
{    if( current_state == 0 )          // if output was low, and just toggled high...
   {    OCAction(OC_CLEAR_ACTION, PT0_ID);  // go low upon next successful compare
      TC0 += high_time;    // this access to TC0 auto-clears the flag bit
                           // if the output level just toggled high, the next output
   }                       // compare forces the pin low after the high_time
   else                            // else if the output level just toggled low...
   {    OCAction(OC_SET_ACTION, PT0_ID);  // set the mode/level bit so the next output
      TC0 += low_time;             // compare forces the pin high after low_time
   }                               // this access to TC0 auto-clears the flag bit
   current_state = !current_state; // toggle the state variable
}
 
MAKE_ISR(ECTPulseMaker); // needed for Attach() to work
 
void _Q InstallPulseMaker ( void )   // installs PWM output compare on PT0
{    uint current_tcnt_contents = TCNT;  // get current TCNT contents
     OutputCompare(PT0_ID);              // make ECT0/PT0 an output compare
   OCRegWrite((current_tcnt_contents - 1), PT0_ID); // start after 1 rollover period
   current_state = 0;                  // declare current state as low, and...
    OCAction(OC_SET_ACTION, PT0_ID);    // ...go active high upon successful compare
    ATTACH(ECTPulseMaker, ECT0_ID);     // post the interrupt handler
    ECTFastClear();                 // fast clear mode simplifies the ISR
    ECTInterruptEnable(PT0_ID);         // enable output compare 0 interrupt
}     // NOTE: the calling routine must ensure that interrupts are globally enabled!
 
 
 
// ************************* PWM Task Activation Routine ***********************
 
void  _Q 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 ECT0_ID output compare interrupt
// which generates the pwm output waveform on pin PT0.
// It also updates the PWM01 channel's duty parameter to control the
// output waveform on pin PP1.
// PWM01    is a 16-bit concatenated PWM output comprising 8-bit channels 0&1.
// Its driving clock period is chosen to be 1.6us to equal the ECT driving clock.
// PWM01 is declared as an active-high, non-center-aligned output by Setup16BitPWM().
// This means that the "duty" value written to the PWM01 channel
// should equal the high_time value calculated above.
// Reasoning: high_time is the 16-bit high time of a PWM signal with 100msec period
// and 1.6us driving clock.
// This is the definition of the "duty" value of an active-high 16-bit PWM signal.
// Thus, we use the PWMDutyWrite() function to update the duty cycle.
{  InstallPulseMaker();    // install ECT0 interrupt handler for pin PT0
   Setup16BitPWM();        // install PWM01 on pin PP1
    while(1)
   {  CalcDutyCycle();
      CalcHighAndLowTimes();
      PWMDutyWrite( high_time, PWM01 );  // write high_time to duty, explained above.
      Pause();              // let other tasks run
   }
}
 
// ************************** Statistics Task **************************
 
// This task samples the analog input voltage collected by the GatherData task
// once per second, stores it in a matrix, and calculates and prints to the terminal
// the mean and standard deviation of the data every 10 seconds.
// This task could easily use standard C arrays, but Forth arrays are
// used to show how paged RAM can be used to hold data structures.
// This use of paged RAM is especially useful when large amounts of data are involve.
 
 
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;
 
float _Q 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 INCLUDE\MOSAIC\ARRAY.h file
    for(i=0; i < numcolumns; i++)
            accumulator += FARRAYFETCH(float, rownum, i, array_ptr);
    return (accumulator / numcolumns);
}
 
float _Q 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);
}
 
void _Q 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);
}
 
void  _Q 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);
}
 
// The message on the terminal screen will look like this:
// Mean = x.xxx Volts  Standard Deviation = x.xxx Volts
 
void  _Q ShowStats( void )
        // writes a 1-line summary to terminal.
{ printf("\nMean = %5.4g Volts  Standard Deviation = %5.4g Volts",mean,standard_deviation);
  fflush(stdout);
}
 
void _Q LogDataAndShowStats( void )
// increments matrix_index every second,
// loads input_voltage data collected by task 1 into Last10Voltages array,
// and displays statistics to terminal 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();       // write summary line
                      }
           }
}
 
void  _Q Statistics( void )
// this is the activation routine for the statistics task;
// it calculates and displays the mean and standard deviation of the data.
// NOTE: if you want to interactively debug on the serial1 port
//  while seeing the statistics printout on the serial2 port,
//  comment in the UseSerial2(); statement.  The default serial2 baudrate = 57600.
// This task calls PauseOnKey(). When you type a Carriage Return ("ENTER"),
// PauseOnKey() calls ABORT to end the program and enter the Forth monitor
// This lets you regain control of the processor.
{   DIM(float, 1, 10, &Last10Voltages);     // dimension as a 1-row array
    InitFPArray(0.0, &Last10Voltages);      // initialize array
    last_index = -1;                        // initialize last_index
//  UseSerial2();     // optional: this directs printout to serial2 port.
    printf("\nStarting Statistics task, will print once every 10 seconds...");
     printf("\nType a carriage return to abort the program\n");
    while(1)
       {  LogDataAndShowStats();         // calc and display statistics
          Pause();                       // let other tasks run
          PauseOnKey();  // pauses/resumes when key received; aborts when CR entered
       }
}
 
// ********************* BUILD TASKS  ****************************
 
// First declare the tasks and allocate their 1K task areas:
// FORTH_TASK (see USER.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 INCLUDE\MOSAIC\MTASKER.h file.
 
void _Q 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);
}  // default heap start and end are defined in C:\MosaicPlus\c\libraries\include\mosaic\HEAP.h
 
 
void _Q ActivateTasks( void )
// associate activation routines with each of the tasks.
// we pass a function pointer and task base address to the ACTIVATE routine.
{    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 PDQ Board is powered up or reset, the main routine will
// automatically be executed.
// main() zeros the variable area and initializes the heap.
// During debugging, you can comment out the ASLEEP command in main and comment in
// the UseSerial2() command in Statistics to allow Serial1 to be used for
// debugging via the terminal.  The FORTH task which runs main() has access to
// all defined function names declared using the _Q keyword;
// the names are sent to the board via the .DLF download file.
// main() initializes the variables and elapsed time clock,
// 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 command which
// specifies a COLD restart can be inserted;
// this command is "commented out" in the code shown here.
 
 
void _Q InitVariables(void)
// init static variables  at runtime, and
// it's a good idea to erase the parameter field struct of a Forth array at startup.
{
    input_voltage = 0;
    current_state = 0;
    high_time = low_time = 0;
    // erase the parameter field struct of a Forth array:
    FillMany  ((xaddr) &Last10Voltages, (long) sizeof(FORTH_ARRAY), (char) 0);
    matrix_index = last_index = 0;
    mean = standard_deviation = duty_cycle = 0.0;
}
 
 
int 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
    BuildTasks();           // initialize user areas of the tasks
    ActivateTasks();        // associate action routine with each task
// the following command is removed during debugging;present in final version
    //  ColdOnReset();      // ensures full initialization upon each reset
// the following ASLEEP command can be removed during debugging
// if the UseSerial2() command is inserted in the Statistics() function:
    STATUS = ASLEEP;        // puts forth task asleep so statistics can print
    RELEASE(SERIAL);        // in case another tasks needs to use serial
    StartTimeslicer();      // starts elapsed time clock, enables interrupts
    Pause();                // start next task immediately
    return 0;
}
 
 
// To compile this file, click Build -> Build from the toolbar in the Mosaic IDE Plus.
// Then use the terminal to download the resulting turnkey.dlf file
// into the controller.
// Note that by default the download file will automatically back up the code
// image to shadowed flash.
// To make sure this option is enabled, go to
//    Project -> Project compiler options
// and check the checkbox next to:
//    "Include save-to-flash instructions in download file"
// This option causes the program to be automatically reloaded into memory
// upon each COLD restart.
 
// Be sure to select:
//    Set Autostart in download file
// this will install main() as the routine that is automatically executed
// each time the board is reset or powered on.
// This is done via the priority.autostart vector
// at the top of page 0x0F shadow flash AND page 0x0F SRAM.
 
// You can optionally select:
//    Enable Region 1 (Pages 0-F) Write Protect
//    Enable Region 2 (Pages 10-17) Write Protect
// to write protect all the protectable pages; The C User Guide
// document explains these options.
 
// With Autostart enabled, the next restart will automatically execute main().
// NOTE:   To erase the autostart vector and return to the QED-Forth prompt,
//         type a CR ("ENTER") to abort the program, then 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 the jumper
//         labeled "Clean" 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 115200, default serial = serial1 port;
//         invoke BAUD and/or USE.SERIAL2 if you need to change the defaults).
//
 
 
// ************** GOING INTO PRODUCTION ***************
 
// To make sure your project is ready to load onto a pristine board,
// check that at minimum both of these options are enabled:
//    Set Autostart in download file
//    "Include save-to-flash instructions in download file"
// Remember, these options can be accessed via the toolbar of the Mosaic IDE Plus
// Click, Project -> Project compiler options
// Now, simply download the TURNKEY.DLF file, power cycle the Board
// and it will automatically run the application!

Turnkeyed Application



See also → A Brief Introduction to Forth Programming

 
This page is about: How to Write Multitasking Instrument Control Program, Analog to Digital (ATD) Conversion Task, Pulse Width Modulation (PWM) Task, Statistics Task, how to Compile Program, Going into Production with Embedded Computer – Presents a C-language multitasking example program for data acquisition and control of electronic instruments, using ATD, PWM, array data storage, and statistical calculations. autostart, save, restore
 
 
Navigation