$24
In this lab we will introduce a structure for designing a task scheduler for state machines. We will build a simple task scheduler that will process state machines according to the period specified by each state machine task. We will use the scheduler to implement a producer-consumer problem where we take input via a keypad, and use the LCD to output the characters pressed on the keypad.
#include
As we add more functionality to our code it can become a bit cluttered. You are welcome to continue copy and pasting all of the support code directly into the .c file. Alternatively, you may use the following .h filesto increase readability.
If you choose to download and use these .h files, a tutorial on how to include these files can be found here
If you are using the CS 120B Project template we have already included the pathway to the following directory shown below. You are welcome to place your .h files here:
C:\Documents and Settings\Embedded Systems Lab\My Documents\Atmel Studio\include\ucr
If you do not use the .h files, you will need to copy paste the appropriate support code
into the keypad and LCD code below.
The .h files are designed to work with the task scheduler.
Keypad
A keypad is comprised of several buttons. If each button had its own pin, the keypad below would require 16 pins.
Figure 1: Keypad GH5004-ND
To reduce pin count, keypads commonly have a row/column arrangement as shown below.
Figure 2: High-Level Connection diagram for Keypad
Figure 3: Keypad GH5004-NDPins - C4C3C2C1 R4R3R2R1 (left to right in figure)
Each row has a pin (R1-R4), and each column has a pin (C1-C4), for a total of 8 pins. Pressing a button uniquely connects one column pin with one row pin. For example, pressing the upper-left button connects pin C1 with pin R1. Pressing the bottom-right button connects C4 and R4. Datasheet
To accomplish accepting input from 16 buttons with only 8 pins a technique known as time multiplexing is employed. The idea is simple, we shall use common row wires and common column wires the achieve our lower pin count. This however causes a problem, by sharing the rows and columns we have cross talk. To overcome this, we will selectively enable one column at a time, check the 4 pins connected to that row, and then continue by enabling the next column, repeating the process for all columns. This is time multiplexing -- simultaneous transmission of several messages along a single channel of communication by having those signals transmit at specific times (in this case a specific sequence).
We can get away with this because the microcontroller can operate much faster than humans can react/perceive. In the time it takes a person to press one of the buttons, the microcontroller can make many passes of the keypad to check for input. Thus the process is transparent to the user
Connect the keypad to port C as shown (R1 connected to PC0, …, C4 to PC7).
Actually, you can move the keypad further left
for more stability (partly covering the wires).
Figure 4: Shown setup
Keypad Connections
Keypad
1
2
3
4
5
6
7
8
Pin #
Term
R1
R2
R3
R4
C1
C2
C3
C4
AVR
C0
C1
C2
C3
C4
C5
C6
C7
Port
Output
Output
Output
Output
Input
Input
Input
Input
In order to get a correct keypad input, each termC1-C4 from figure 2 must be checked if the voltage is logical low; the code belows shows the checking of each column.
The following keypad test program repeatedly scans the keypad and checks for particular buttons being pressed, lighting five LEDs on port B accordingly. The program is unfinished but should work for buttons 1, 2, and *. Put five LEDs on PB4-PB0 and test the program.
Note: Don’t forget to uncheck the JTAG fuse as we are using port C.
#include <avr/io.h
#include <ucr/bit.h
Returns '\0' if no key pressed, else returns char '1', '2', ... '9', 'A', ...
If multiple keys pressed, returns leftmost-topmost one
Keypad must be connected to port C
/* Keypad arrangement
PC4 PC5 PC6 PC7
col 1 2 3 4
row
1
PC0 1
| 2
| 3
| A
PC1 2
4
| 5
| 6
| B
PC2 3
7
| 8
| 9
| C
PC3 4
*|0
|#|D
*/
unsigned char GetKeypadKey() {
PORTC = 0xEF; // Enable col 4 with 0, disable others with 1’s
asm("nop"); // add a delay to allow PORTC to stabilize before checking if (GetBit(PINC,0)==0) { return('1'); } if (GetBit(PINC,1)==0) { return('4'); }
if (GetBit(PINC,2)==0) { return('7'); }
if (GetBit(PINC,3)==0) { return('*'); }
// Check keys in col 2
PORTC = 0xDF; // Enable col 5 with 0, disable others with 1’s
asm("nop"); // add a delay to allow PORTC to stabilize before checking if (GetBit(PINC,0)==0) { return('2'); }
... *****FINISH*****
Check keys in col 3
PORTC = 0xBF; // Enable col 6 with 0, disable others with 1’s
asm("nop"); // add a delay to allow PORTC to stabilize before checking
... *****FINISH*****
Check keys in col 4
... *****FINISH*****
return('\0'); // default value
}
int main(void)
{
unsigned char x;
DDRB = 0xFF; PORTB = 0x00; // PORTB set to output, outputs init 0s
DDRC = 0xF0; PORTC = 0x0F; // PC7..4 outputs init 0s, PC3..0 inputs init 1s while(1) {
x = GetKeypadKey();
switch (x) {
case '\0': PORTB = 0x1F; break; // All 5 LEDs on
case '1': PORTB = 0x01; break; // hex equivalent
case '2': PORTB = 0x02; break;
. . . ***** FINISH *****
case 'D': PORTB = 0x0D; break;
case '*': PORTB = 0x0E; break;
case '0': PORTB = 0x00; break;
case '#': PORTB = 0x0F; break;
default: PORTB = 0x1B; break; // Should never occur. Middle LED
off.
}
}
}
LCD Display:
LCD Display Pin Connections
LCD
1
2
3
4`
5
6
7-14
15-16
PIN #
Potentiometer
AVR
AVR
AVR
5
(10KΩ)
Vcc-
Connection
GND
PORT
GND
PORT
PORT
Volts
thru. to
GND
A0
A1
D0-D7
GND
Here are the files required to run the LCD display. Make sure to change the values of DATA_BUS, CONTROL_BUS, RS, andEin io.cto reflect the new connections of the LCD screen.
io.h
io.c
Building the Scheduler:
A scheduler is code whose purpose is, given multiple tasks, to execute each task at the appropriate time. PES describes a task scheduler in detail. We will define a structure called a taskthat represents a process in our operating system. The task structure should contain all of the information that represents that process, such as period, state, etc... The heart of each task is the function that it will be executing. Each of these functions will be defined as a global function, and we use function pointers in the task struct to point to the appropriate function. Function pointers work just like a pointer to a char or integer, but they have some specific syntax on how they must be called and defined. One additional change is we now pass the state
variable for each task as part of the function call; there is no longer a global state variable.
For more information on function pointers see:http://www.newty.de/fpt/fpt.html
Sample Task Scheduler:
#include <avr/io.h
#include <avr/interrupt.h
#include <ucr/bit.h
#include <ucr/timer.h
#include <stdio.h
//--------Find GCD function --------------------------------------------------
unsigned long int findGCD(unsigned long int a, unsigned long int b)
{
unsigned long int c;
while(1){
c = a%b;
if(c==0){return b;}
a = b;
b = c;
}
return 0;
}
//--------End find GCD function ----------------------------------------------
//--------Task scheduler data structure---------------------------------------
// Struct for Tasks represent a running process in our simple real-time operating system. typedef struct task {
/*Tasks should have members that include: state, period,
a measurement of elapsed time, and a function pointer.*/ signed char state; //Task's current state unsigned long int period; //Task period
unsigned long int elapsedTime; //Time elapsed since last task tick int (*TickFct)(int); //Task tick function
} task;
//--------End Task scheduler data structure-----------------------------------
//--------Shared Variables----------------------------------------------------
unsigned char SM2_output = 0x00;
unsigned char SM3_output = 0x00;
unsigned char pause = 0;
//--------End Shared Variables------------------------------------------------
//--------User defined FSMs---------------------------------------------------
//Enumeration of states.
enum SM1_States { SM1_wait, SM1_press, SM1_release };
// Monitors button connected to PA0.
// When button is pressed, shared variable "pause" is toggled.
int SMTick1(int state) {
// Local Variables
unsigned char press = ~PINA & 0x01;
//State machine transitions
switch (state) {
case SM1_wait: if (press == 0x01) { // Wait for button press state = SM1_press;
case SM1_press:
case SM1_release:
default:
}
}
break;
state = SM1_release;
break;
if (press == 0x00) { // Wait for button release state = SM1_wait;
}
break;
state = SM1_wait; // default: Initial state break;
//State machine actions
switch(state) {
case SM1_wait: break;
case SM1_press: pause = (pause == 0) ? 1 : 0; // toggle pause
break;
case SM1_release: break;
default: break;
}
return state;
}
//Enumeration of states.
enum SM2_States { SM2_wait, SM2_blink };
If paused: Do NOT toggle LED connected to PB0
If unpaused: toggle LED connected to PB0
int SMTick2(int state) {
//State machine transitions
switch (state) {
case SM2_wait: if (pause == 0) { // If unpaused, go to blink state state = SM2_blink;
case SM2_blink:
default:
}
}
break;
if (pause == 1) { // If paused, go to wait state state = SM2_wait;
}
break;
state = SM2_wait;
break;
//State machine actions
switch(state) {
case SM2_wait: break;
case SM2_blink: SM2_output = (SM2_output == 0x00) ? 0x01 : 0x00; //toggle LED
break;
default: break;
}
return state;
}
//Enumeration of states.
enum SM3_States { SM3_wait, SM3_blink };
If paused: Do NOT toggle LED connected to PB1
If unpaused: toggle LED connected to PB1
int SMTick3(int state) {
//State machine transitions
switch (state) {
case SM3_wait: if (pause == 0) { // If unpaused, go to blink state state = SM3_blink;
case SM3_blink:
default:
}
}
break;
if (pause == 1) { // If paused, go to wait state state = SM3_wait;
}
break;
state = SM3_wait;
break;
//State machine actions
switch(state) {
case SM3_wait: break;
case SM3_blink: SM3_output = (SM3_output == 0x00) ? 0x02 : 0x00; //toggle LED
break;
default: break;
}
return state;
}
//Enumeration of states.
enum SM4_States { SM4_display };
Combine blinking LED outputs from SM2 and SM3, and output on PORTB int SMTick4(int state) {
Local Variables
unsigned char output;
//State machine transitions
switch (state) {
case SM4_display: break;
default: state = SM4_display;
break;
}
//State machine actions
switch(state) {
case SM4_display: output = SM2_output | SM3_output; // write shared outputs // to local variables
break;
default:
}
break;
PORTB = output;
// Write combined, shared output variables to PORTB
return state;
}
--------END User defined FSMs-----------------------------------------------
Implement scheduler code from PES.
int main()
{
Set Data Direction Registers
Buttons PORTA[0-7], set AVR PORTA to pull down logic DDRA = 0x00; PORTA = 0xFF;
DDRB = 0xFF; PORTB = 0x00;
. . . etc
Period for the tasks
unsigned long int SMTick1_calc = 50;
unsigned long int SMTick2_calc = 500;
unsigned long int SMTick3_calc = 1000;
unsigned long int SMTick4_calc = 10;
//Calculating GCD
unsigned long int tmpGCD = 1;
tmpGCD = findGCD(SMTick1_calc, SMTick2_calc);
tmpGCD = findGCD(tmpGCD, SMTick3_calc);
tmpGCD = findGCD(tmpGCD, SMTick4_calc);
//Greatest common divisor for all tasks or smallest time unit for tasks.
unsigned long int GCD = tmpGCD;
//Recalculate GCD periods for scheduler
unsigned long int SMTick1_period = SMTick1_calc/GCD; unsigned long int SMTick2_period = SMTick2_calc/GCD; unsigned long int SMTick3_period = SMTick3_calc/GCD; unsigned long int SMTick4_period = SMTick4_calc/GCD;
//Declare an array of tasks
static task task1, task2, task3, task4;
task *tasks[] = { &task1, &task2, &task3, &task4 };
const unsigned short numTasks = sizeof(tasks)/sizeof(task*);
// Task 1
task1.state = -1;//Task initial state.
task1.period = SMTick1_period;//Task Period.
task1.elapsedTime = SMTick1_period;//Task current elapsed time.
task1.TickFct = &SMTick1;//Function pointer for the tick.
// Task 2
task2.state = -1;//Task initial state.
task2.period = SMTick2_period;//Task Period.
task2.elapsedTime = SMTick2_period;//Task current elapsed time.
task2.TickFct = &SMTick2;//Function pointer for the tick.
// Task 3
task3.state = -1;//Task initial state.
task3.period = SMTick3_period;//Task Period.
task3.elapsedTime = SMTick3_period; // Task current elasped time.
task3.TickFct = &SMTick3; // Function pointer for the tick.
// Task 4
task4.state = -1;//Task initial state.
task4.period = SMTick4_period;//Task Period.
task4.elapsedTime = SMTick4_period; // Task current elasped time.
task4.TickFct = &SMTick4; // Function pointer for the tick.
Set the timer and turn it on TimerSet(GCD);
TimerOn();
unsigned short i; // Scheduler for-loop iterator while(1) {
// Scheduler code
for ( i = 0; i < numTasks; i++ ) {
// Task is ready to tick
if ( tasks[i]-elapsedTime == tasks[i]-period ) { // Setting next state for task
tasks[i]-state = tasks[i]-TickFct(tasks[i]-state);
Reset the elapsed time for next tick. tasks[i]-elapsedTime = 0;
}
tasks[i]-elapsedTime += 1;
}
while(!TimerFlag);
TimerFlag = 0;
}
Error: Program should not exit! return 0;
}
Pre-lab
Have your board wired up as above and ready to use (soldering of the LCD header must be completed before lab). Complete the GetKeyPad() function and be able to demo its fully working functionality (0 ~ 9, A ~ D, *, # ). Be able to demo your LCD works.
Exercise 1
Modify the keypad code to be in an SM task. Then, modify the keypad SM to utilize the simple task scheduler format (refer to PES Chp 7). All code from here on out should use the task scheduler.
Exercise 2
Use the LCD code, along with a button and/or time delay to display the message "CS120B is Legend... wait for it DARY!"The string will not fit on the display all at once, so you will need to come up with some way to paginate or scroll the text.
Note:If your LCD is exceptionally dim, adjust the resistance provided by the potentiometer connected to Pin #3.
Video Demonstration: http://youtu.be/eAtBTUr_cm8
Exercise 3
Combine the functionality of the keypad and LCD so when keypad is pressed and released, the character of the button pressed is displayed on the LCD, and stays displayed until a different button press occurs (May be accomplished with two tasks: LCD interface & modified test harness).
Video Demonstration: http://youtu.be/ZCadEA3ryPM
Exercise 4 (Challenge)
Notice that you can visually see the LCD refresh each character (display a lengthy string then update to a different lengthy string). Design a system where a single character is updated in the displayed string rather than the entire string itself. Use the functions provided in “io.c”.
An example behavior would be to initially display a lengthy string, such as “Congratulations!”. The first keypad button pressed changes the first character ‘C’ to the button pressed. The second keypad press changes the second character to the second button pressed, etc. No refresh should be observable during the character update.
Video Demonstration: http://youtu.be/M_BC9VuaIt8
Exercise 5 (Challenge)
Using both rows of the LCD display, design a game where a player controlled character avoids oncoming obstacles. Three buttons are used to operate the game.
Criteria:
Use the cursor as the player controlled character.
Choose a character like ‘#’, ‘*’, etc. to represent the obstacles.
One button is used to pause/start the game.
Two buttons are used to control the player character. One button moves the player to the top row. The other button moves the player to the bottom row.
A character position change should happen immediately after pressing the button.
Minimum requirement is to have one obstacle on the top row and one obstacle on the bottom row. You may add more if you are feeling up to the challenge.
Choose a reasonable movement speed for the obstacles (100ms or more).
If an obstacle collides with the player, the game is paused, and a “game over” message is displayed. The game is restarted when the pause button is pressed.
Hints:
Due to the noticeable refresh rate observed when using LCD_DisplayString, instead use the combination of LCD_Cursor and LCD_WriteData to keep noticeable refreshing to a minimum.
LCD cursor positions range between 1 and 32 (NOT 0 and 31).
As always, dividing the design into multiple, smaller synchSMs can result in a cleaner, simpler design.
Video Demonstration: http://youtu.be/mDewFJsnbEg
Each student must submit an their .c source files according to instructions in the lab submission guidelines. Post any questions or problems you encounter to the wiki and discussion boards on iLearn.