Moderator's
comment: Here's a program that is an elegant solution to providing a
sequencer that will turn pins on the LaunchPad on and off in any
sequence and with arbitrary time delays between. The pins going high
(on) and low (off) can be used to light LEDs or activate things like
external relays. Let's hear Bill tell all about it.
Environmental Sequencer
Code and pictures by
Bill Hastings
Overview
This project started out as a lighting simulator, but quickly turned into a general purpose, time based sequencer. Using it, I've written a simple two-light flasher, two-way traffic lights, chasing light marquees, and simulated house lighting. One of the goals was to write it to make it fairly easy for non-coders to create custom sequences. I've reduced the instruction set to a small set that allow for turning pins on, off or toggled and adding delays.
Technical Info
I've not included a schematic since this was made to be general purpose. I have included a couple of sample sequences for you to play with. The first is an alternating flasher that uses the on-board LEDs. The second is a two-way traffic signal.
I have avoided using names like LED for pin names, since the output can be used to control equipment other than lighting. I have used the terms ON and OFF to signify HIGH and LOW respectively.
I have used macros (don't worry if you don't understand that) to replace arcane instructions for accessing the I/O. For example, instead of P1OUT |= BIT0 to turn P1.0 ON, you can now use, "P1_BIT_ON(BIT0)".
Available sequence instructions are:
- P1_ALL_ON() - Turn all Port 1 pins on (high)
- P1_ALL_OFF() - Turn all Port 1 pins off (low)
- P1_BIT_ON(BITx) - Turn Port 1 bit x on (high)
- P1_BIT_OFF(BITx) - Turn Port 1 bit x off (low)
- P1_BIT_TOGGLE(BITx) - Toggle state of Port 1 bit x
- P2_ALL_ON() - Turn all Port 2 pins on (high)
- P2_ALL_OFF() - Turn all Port 2 pins off (low)
- P2_BIT_ON(BITx) - Turn Port 2 bit x on (high)
- P2_BIT_OFF(BITx) - Turn Port 2 bit x off (low)
- P2_BIT_TOGGLE(BITx) - Toggle state of Port 2 bit x
- DELAY_TENTHS(X) - Provide a delay of x tenths of a second
- DELAY_SEC(X) - Provide a delay of x seconds
- DELAY_MIN(X) - Provide a delay of x minutes
The default low frequency clock runs at a modest 12000 Hz. Unfortunately, it is not very stable and can vary from 4000Hz to 20000Hz, depending on temperature and other environmental conditions. If you require more precise timing, a crystal is provided with the LaunchPad kit, but your need to solder it in yourself. Google "MSP430" and "crystal" and you'll find plenty of articles on how to install the crystal. Once installed, you need to modify the main.c file and uncomment the "//#define USEXTAL" by removing the 2 /'s at the beginning of the line. It should look like this "#define USEXTAL".
The theory of operation is pretty simple. Output pins are set to either ON (high) or OFF (low). When a delay is requested, Timer A continuously increments a counter every 0.1 second. The delay routine waits for the requested time to elapse, then returns so that the next sequence instruction can be executed.
Future
- Use multiple timers, allowing for asynchronous executions.
- Ability to turn on/off specialty lighting effects like Hoffy's welder or fireplace lighting.
- Please pass along any of your ideas.
Additional Moderator's comments: Although Bill is properly concerned about the accuracy of the timers if run without the crystal in place, for most model railroad applications using the LaunchPad as it comes should not be a problem. Unless you are doing something requiring precise timing like a speedometer or, possibly, a fast clock, approximate times should be sufficient.
Bill's code is very heavily commented (the sign of a veteran coder) so refer to the code itself for most of the information that you would need to understand it or modify it.
However, for the non-coders, you will want be able to change the sequences programmed into this code to suit your application. Look towards the end of the code for these two markers:
// BEGIN #####
// END #####
These two lines are comment lines (they begin with the double slash "//") that Bill has used to mark the place in the code where the instructions to turn pins ON and OFF should be located. Between these two markers you will find additional comments by Bill to help you understand how this works. Then you will come across this series of instructions:
// Simple 2 light flasher example - Uses onboard LEDs on P1.0 and P1.6 P1_BIT_TOGGLE(BIT0); // green LED on DELAY_SECS(1); // wait 1 second P1_BIT_TOGGLE(BIT0); // green LED off P1_BIT_TOGGLE(BIT6); // red LED on DELAY_SECS(1); // wait 1 second P1_BIT_TOGGLE(BIT6); // red LED off and go back to the beginning
The first line is a comment, of course. Then begins a series of the instructions that Bill has developed. Earlier in the program, all pins have been turned OFF, so the first instruction:
P1_BIT_TOGGLE(BIT0); // green LED on
"toggles" (that is, reverses the state of) Port 1, Bit 0 (found on pin 2) from OFF to ON. This port/pin is the one that drives one of the on-board LEDs. The next instruction:
DELAY_SECS(1); // wait 1 second
delays for 1 second. The following instruction:
P1_BIT_TOGGLE(BIT0); // green LED off
"toggles" the same port/bit/pin from ON to OFF. And so on with the next instruction working on the other on-board LED. The LEDs are thereby turned on for one second, turned off and the other turned on, and off, etc. indefinitely. BTW Bill has his red and green LEDs reversed in the comments to this section of the code (green is red and vice versa).
To show how this works, I changed the timing in the first 'delay' instruction to 5 seconds on, by changing it to the following:
DELAY_SECS(5); // wait 5 second
while leaving the green LED at 1 second. The asymmetric flashing that this produced is shown in the video.
When you are ready to create your own sequences, you'll delete all of the instructions in the block above and replace them with instructions chosen from the list given in Bill's write-up or the same list in the comments embedded in the code.
When you add your own instructions to create your own sequences be sure to include the semicolon at the end of the executable part of the instruction and the "//" before your comments, if any. For example the instruction as written in Bill's write-up:
P1_ALL_ON() - Turn all Port 1 pins on (high)
Should be entered into the code like this:
P1_ALL_ON(); //Turn all Port 1 pins on (high)
Notice the semi-colon and the // added before the comment part of the line.
Bill has provided an additional way to get your instructions into the code. The instructions can be written into a text file and then the file is "included" via the "#include" directive. In the section of the code between the // BEGIN ##### and the // END ##### you will find these instructions which have been commented out:
// example of separate file inclusion //#include "simple_flasher.esq"
The .esq file type is Bill's invention to hold instructions for his sequencer code. Bill has provided files for the alternate flasher, and a traffic signal. These can be found here:
To include the instructions as a file: remove any in-line instructions; uncomment the #include statement; make sure that the include statement has the name of the file that you want to use; and make the file available to the compiler. Code Composer Studio creates a workspace on your computer's disk entitled "workspace_v5_2" (for the current version of CCS). In that directory you will find a folder that bears your project's name. Drop the .esq file into that folder. Build the project per normal and the compiler inserts the instructions form the file in-line during the compile process. The code will now execute the instructions from the file.
Bill's .esq file format will be a way that people can exchange instructions for his innovative sequencer in a ready-to-use format.
The code file can be found here:
The code listing follows.
/******************************************************* * Environmental Sequencer * * COPYRIGHT © 2013 Bill Hastings * * Provided under a Creative Commons Attribution, * Non-Commercial Share Alike,3.0 Unported License * * TARGETED TO MSP430 LANUCHPAD * W/MSP430G2553 PROCESSOR * *****************************************************/ #include <msp430g2553.h> //#define USEXTAL // uncomment this line to use the crystal clock #ifdef USEXTAL #define TRIGGER 3277 // 1/10 of crystal frequency 32768Hz #else #define TRIGGER 1200 // 1/10 of nominal VLO frequency 12000Hz #endif // pin access macros #define P1_ALL_OFF P1OUT = 0 #define P1_ALL_ON P1OUT = 0xFF #define P1_BIT_ON(bit) (P1OUT |= bit) #define P1_BIT_OFF(bit) (P1OUT &= ~bit) #define P1_BIT_TOGGLE(bit) (P1OUT ^= bit) #define P2_ALL_OFF P2OUT = 0 #define P2_ALL_ON P2OUT = 0x3F #define P2_BIT_ON(bit) (P2OUT |= bit) #define P2_BIT_OFF(bit) (P2OUT &= ~bit) #define P2_BIT_TOGGLE(bit) (P2OUT ^= bit) // delay macros #define DELAY_TENTHS(delay) doDelayTenths(delay) #define DELAY_SECS(delay) doDelayTenths(delay * 10) #define DELAY_MINS(delay) doDelayTenths(delay * 10 *60) // routine definitions void doDelayTenths(unsigned long delay); // global(s) volatile unsigned long counter = 0; // main routine void main(void) { // initializations WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer P1DIR = 0xFF; // Set all P1 pins to output P1_ALL_OFF; // Set all P1 pins off P2DIR = 0x3F; // Set all P2 pins to output P2_ALL_OFF; // Set all P2 pins off #ifndef USEXTAL BCSCTL3 |= LFXT1S_2; // use VLO #endif TA1CCR0 = TRIGGER; // Count limit (16 bit) TA1CCTL0 = CCIE; // Enable Timer A1 interrupts TA1CTL = TASSEL_1 + MC_1; // Timer A1 with ACLK, count UP _BIS_SR(GIE); // interrupts enabled // run loop while(1) { /* * Available sequence instructions are: * P1_ALL_ON() - Turn all Port 1 pins on (high) * P1_ALL_OFF() - Turn all Port 1 pins off (low) * P1_BIT_ON(BITx) - Turn Port 1 bit x on (high) * P1_BIT_OFF(BITx) - Turn Port 1 bit x off (low) * P1_BIT_TOGGLE(BITx) - Toggle state of Port 1 bit x * * P2_ALL_ON() - Turn all Port 2 pins on (high) * P2_ALL_OFF() - Turn all Port 2 pins off (low) * P2_BIT_ON(BITx) - Turn Port 2 bit x on (high) * P2_BIT_OFF(BITx) - Turn Port 2 bit x off (low) * P2_BIT_TOGGLE(BITx) - Toggle state of Port 2 bit x * * DELAY_TENTHS(x) Provide a delay of x tensths of a second * DELAY_SECS(x) Provide a delay of x seconds * DELAY_MINS(x) Provide a delay of x minutes * * Things to remember: * 1) To use the crystal clock, you must uncomment "//#define USEXTAL" (line 14), * AND have installed the crystal on your LaunchPad * 2) When editing your sequence, CASE IS IMPORTANT * - p1_all_on() does not equal P1_ALL_ON() * 3) When using the P1_BIT calls, the argument should only use BIT0 through BIT7 * 4) When using the P2_BIT calls, the argument should only use BIT0 through BIT4 * 5) Multiple BIT arguments can be specified for P1_BIT * and P2_BIT operations. For example, if you wanted to turn on both * BIT0 and BIT6 on P1, you could use, "P1_BIT_ON(BIT0 + BIT6);" * 6) Inline sequence instructions should go between the lines * "// BEGIN #####" (line 101) and "// END #####" * 7) Use "#include "<filename>" to include sequence instructions from a separate file * 8) Complete each instruction line with a ; (semi-colon) * 8) All output pins start in the off (low) state */ // BEGIN ##### /* * Below are examples of both file-based and inline sequences * Currently it is using inline. To use file-based inclusion, * comment out all of the inline instructions, and uncomment * the #include "simple_flasher.esq". To go back, just reverse * it - uncomment the inline instructions and comment the * #include. */ // example of separate file inclusion //#include "simple_flasher.esq" // example of inline instructions // Simple 2 light flasher example - Uses onboard LEDs on P1.0 and P1.6 P1_BIT_TOGGLE(BIT0); // green LED on DELAY_SECS(1); // wait 1 second P1_BIT_TOGGLE(BIT0); // green LED off P1_BIT_TOGGLE(BIT6); // red LED on DELAY_SECS(1); // wait 1 second P1_BIT_TOGGLE(BIT6); // red LED off and go back to the beginning // END ##### } // end while } // end main // delay by x tenths of a second void doDelayTenths(unsigned long x) { counter = 0; while(counter < x) {} } #pragma vector=TIMER1_A0_VECTOR // Timer1 A0 interrupt service routine __interrupt void Timer1_A0 (void) { counter++; }.
No comments:
Post a Comment