Engineering 15 Lab 4
Alex al-Benn, Brian
van der Park, Aroné Dobois
9 February 1972
Figure 1. Our Source of Inspiration
Task
1
Description
This code causes the LED on RB1 to change state once per second. It configures the timer to use the external oscillator, use a prescaler of 4:1. The timer is initialized with a count value of 57344, so that in 8192 counts it will overflow. In the ISR, the timer register is reset to this value, resulting in oscillations every 1 second. In the ISR, the led attached to RB1 is toggled.
Code
#INT_TIMER1
// interrupt triggered when timer overflows
void timer1_isr() {
output_toggle(pin_b1);
set_timer1(57344); // 65536 - 32768 / 4 = 57344; overflow
once every 32768 / 4=8192 counts
}
void main() {
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_psp(PSP_DISABLED);
setup_spi(FALSE);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
setup_timer_1(T1_EXTERNAL
| T1_EXTERNAL_SYNC | T1_DIV_BY_4 | T1_CLK_OUT);
//turn on timer1
setup_timer_2(T2_DISABLED,0,1);
set_timer1(57344); //initialize timer1
enable_interrupts(INT_TIMER1); // turn on timer1 interrupt
enable_interrupts(GLOBAL); // enable interrupts globally.
while (1) { }
}
Task
2
Description
Timer 1 is configured to be driven by the external 32768Hz oscillator, and with a prescaler of 4:1. Since we want the colons to blink each second, they must be toggled every 0.5 second. As a result, the timer register is initialized to 65536-4096=61440, so that the timer fires twice as rapidly as in part 1. In the ISR, the colon flag is toggled, and every other time the ISR is run, the number of seconds is incremented.
In the main() loop the number of minutes is incremented when seconds reaches 60, and the number of hours is also appropriately incremented or reset. Finally, the time is formatted by a printf statement and displayed on the LCD using the code developed in the previous lab.
Code
#include
"C:\Documents and Settings\abenn1\My Documents\Lab 4\lab 4.h"
//
insert LCD code here (see task 3/4/Extension for code)
int hours;
int minutes;
int seconds;
short colonBlink;
#INT_TIMER1
// interrupt triggered when timer overflows
void timer1_isr() {
if (colonBlink == 0)
seconds++;
colonBlink
= !colonBlink;
set_timer1(61440); // 32768 / 4 = 8192; overflow once every
4096 counts
}
void main() {
int
ch;
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_psp(PSP_DISABLED);
setup_spi(FALSE);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
setup_timer_1(T1_EXTERNAL
| T1_EXTERNAL_SYNC | T1_DIV_BY_4 | T1_CLK_OUT);
//turn on timer1
setup_timer_2(T2_DISABLED,0,1);
set_timer1(61440); //initialize timer1
hours = 12;
minutes = 59;
seconds = 55;
colonBlink
= 1;
lcd_init();
lcd_clear();
enable_interrupts(INT_TIMER1); // turn
on timer1 interrupt
enable_interrupts(GLOBAL); // enable
interrupts globally.
while (1) {
if (seconds ==
60) {
seconds = 0;
minutes++;
if (minutes
== 60) {
minutes =
0;
hours++;
if (hours
== 13) {
hours =
1;
}
}
}
lcd_line2();
ch
= colonBlink==1 ? ':' : ' ';
printf(lcd_putc,
"%2d%C%02d%C%02d", hours, ch, minutes, ch, seconds);
delay_ms(50);
}
}
Tasks
3, 4, Extension
Description
For Task 3, we developed an algorithm to determine which of the sixteen keys on a keypad are pressed when the user presses a key. When a key is pressed, one of the upper four bits on port B changes state, triggering an interrupt. The ISR scans these top four bits to determine which column the keypress occurred in; then it switches the lower four bits on port B to inputs and reads those in, to determine the row of the keypress. The key pressed is determined from this row-column combination. The key is decoded by a table lookup, and the associated character is output to the display.
Task 4 consisted of combining Tasks 2 and 3, so the codebases were simply merged to create the code below, with the exception of a few LCD formatting calls, which were changed in order to be sure that the output is displayed in the correct locations.
For the Extension, we introduced a small state machine to enable the user to set the time. The user enters hour-set mode by pressing C, and is prompted for input; upon pressing C again, the hour is set and minute-set mode is entered with an appropriate prompt. Pressing C a third time sets the minutes, resets the seconds to 0, and exits the state machine. A few extra utility functions for storing character input and state data were added as well as code to decode character input to a numerical value.
Code
#include
"\\data-software\Classes\Natural Sciences +
Engineering\Engineering\Transfer\e15baah\Lab4\lab 4 part3.h"
#include
<stdlib.h>
//Use
fast_io so port B pins don't change direction when we
access the
//entire byte at once. (i.e., output_b(32)
doesn't make all pins outputs)
#use
fast_io(B)
#define
LCD_EN pin_a1
#define
LCD_RW pin_a2
#define
LCD_RS pin_a3
#define
LINE1 1
#define
LINE2 2
void write4( int data)
{
output_low( LCD_RW );
output_d( data );
delay_us(2);
output_high( LCD_EN );
delay_us(3);
output_low( LCD_EN );
delay_us(2);
}
void write8(int data)
{
write4( (data
& 0xF0) >> 4 );
write4( data
& 0x0F );
}
void writecmd8(int cmd)
{
output_low( LCD_RS );
write8(cmd);
delay_ms(10);
}
void writedata8(int data)
{
output_high( LCD_RS );
write8(data);
delay_ms(10);
}
void lcd_clear()
{
writecmd8(0x01);
delay_ms(15);
}
void lcd_home()
{
writecmd8(0x02);
delay_ms(15);
}
void lcd_ddram_addr(int
addr)
{
/* lower 7 bits are data address */
writecmd8(addr | 0x80);
}
void lcd_moveto(int
line, int c)
{
if (line==2)
c += 0x40;
lcd_ddram_addr(c);
}
void lcd_line1()
{
lcd_ddram_addr(0x00);
}
void lcd_line2()
{
lcd_ddram_addr(0x40);
}
void lcd_putc(int
ch)
{
if (ch == '\n')
{
lcd_line2();
}
else
{
writedata8(ch);
}
}
void lcd_init()
{
/* clear all port data */
output_low( LCD_EN );
output_low( LCD_RW );
output_low( LCD_RS );
output_d( 0 );
/* 4-bit LCD initialization sequence */
delay_ms(16); /* wait more than 15ms */
write4( 0x03 );
delay_ms(5); /* wait more than 4.1ms */
write4( 0x03 );
delay_ms(1); /* wait more than 100us */
write4( 0x03 );
delay_ms(5);
write4( 0x02 );
delay_ms(5);
/* hardware should be initialized at this
point */
/* configure the lcd properties */
writecmd8( 0x01 );
/* clear display */
writecmd8( 0x28 );
/* function set 4 bit IF N=1(2line) F=0(5x7) */
writecmd8( 0x06 );
/* entry mode set I/D=1 S=0 */
writecmd8( 0x0C );
/* display on, C=0 B=0 */
lcd_ddram_addr(0x00);
}
int key_press_event,
ctr, key_code;
#INT_RB
void key_isr() {
int
i,scan;
// debounce: waits
for the input to stabilize before
// rechecking whether a key was actually pressed
delay_ms(1);
if (input_b() == 0xF0)
return;
// keep a count of the interrupts
ctr++;
// read the upper four bits of port B
key_code = input_b()
& 0xF0;
//scan through setting B0 through B3 set
high
scan = 1;
for(i = 0; i < 4; i++)
{
output_b(scan);
if (key_code != (input_b() &
0xF0)) {
break;
}
scan = scan
<< 1;
}
output_b(0x00);
// ‘or’ together the lower four bits with
the upper four bits
key_code |= (~scan
& 0xF);
// signal to the main loop that
// a key was pressed
key_press_event =
1;
}
//
experimentally determined key map table
static
int keypad_map[16] = {
0x77, 0x7b, 0x7d, 0x7e, 0xb7, 0xbb, 0xbd, 0xbe, 0xd7, 0xdb, 0xdd, 0xde, 0xe7,
0xeb, 0xed, 0xee};
static
char ascii_map[16] = { '1', '2',
'3', 'A', '4',
'5', '6', 'B',
'7', '8', '9',
'C', '*', '0',
'#', 'D' };
//
translation utility
char getascii(int
keycode)
{
int
i;
for (i=0;i<16;i++)
if (keycode==keypad_map[i])
return ascii_map[i];
return '\0'; // if the key was not found, return NULL
character
}
//
********** CLOCK
code from part 2 ****************** //
int hours;
int minutes;
int seconds;
short colonBlink;
#INT_TIMER1
// interrupt triggered when timer overflows
void timer1_isr() {
if (colonBlink == 0)
seconds++;
colonBlink
= !colonBlink;
set_timer1(61440); // 32768 / 4 = 8192; overflow once every
4096 counts
}
int set_state;
char buf[16];
int bufct;
//
****** EXTENSION
Code for setting the time ****** //
enum {SET_HOURS, SET_MINUTES,
SET_NONE};
//
clear the input buffer
void bufclear()
{
bufct
= 0;
}
//
add a character to the input buffer
//
if we are in a SET_HOURS or SET_MINUTES state
void bufcharin(int
ch)
{
if (set_state == SET_NONE)
bufct
= 0;
else if (bufct < 15)
buf[bufct++] = ch;
}
//
returns a numerical value of the
//
string in the buffer, clamped to the
//
min, max parameters
int getbufval(int min, int max)
{
int
val;
if (bufct < 16)
buf[bufct] = '\0';
else
return 0;
val
= atoi(buf);
if (val < min)
val
= min;
if (val > max)
val
= max;
return val;
}
void main()
{
int
ch;
int
evtctr=0;
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_psp(PSP_DISABLED);
setup_spi(FALSE);
/* SETUP THE TIMERS */
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
setup_timer_2(T2_DISABLED,0,1);
setup_timer_1(T1_EXTERNAL
| T1_EXTERNAL_SYNC | T1_DIV_BY_4 | T1_CLK_OUT);
//turn on timer1
set_timer1(61440); //initialize timer1
/* INITIALIZE THE CLOCK */
hours = 12;
minutes = 59;
seconds = 55;
colonBlink
= 1;
/* INTIALIZE THE LCD */
lcd_init();
lcd_clear();
lcd_home();
/* INITIALIZE THE STATE VARIABLES (key
event, clock set)
key_code = 0;
key_press_event=0;
ctr
= 0;
set_state =
SET_NONE;
bufct
= 0;
/* welcome message */
printf(lcd_putc,"What time is it?");
delay_ms(1000);
lcd_clear();
/* configure the port B */
SET_TRIS_B(0xF0);
// B7,B6,B5,B4 are inputs - B3,B2,B1,B0 are outputs
port_b_pullups(0xF0); // B7,B6,B5,B4 have pullups. B3,B2,B1,B0 do not
output_b(0x00); // Set B3, B2, B1, B0 to zero.
/* enable interrupts, obviously */
enable_interrupts(INT_TIMER1); // turn
on timer1 interrupt
enable_interrupts(INT_RB); // turn on B
port interrupt
enable_interrupts(GLOBAL); // enable
interrupts globally.
lcd_home();
while(1) {
if (key_press_event)
{
evtctr++;
key_press_event=0;
// clear the screen when ‘A’ is
pressed
if (getascii(key_code)=='A') {
lcd_clear();
evtctr = 0;
}
else if (getascii(key_code)=='C')
{
// state machine implementing time
set feature
switch(set_state)
{
case
SET_HOURS:
hours =
getbufval(1, 12);
bufclear();
lcd_clear();
lcd_line2();
printf(lcd_putc,
">MIN, then C: ");
set_state
= SET_MINUTES;
evtctr = 0;
break;
case
SET_MINUTES:
minutes
= getbufval(0, 59);
seconds
= 0;
bufclear();
lcd_clear();
lcd_line2();
printf(lcd_putc,
"Time set.");
set_state
= SET_NONE;
evtctr = 0;
break;
case
SET_NONE:
evtctr = 0;
lcd_clear();
lcd_line2();
printf(lcd_putc,
">HR, then C: ");
set_state
= SET_HOURS;
bufclear();
break;
}
} else {
// read and translate the character
ch = getascii(key_code);
bufcharin(ch); // add it to
the input buffer
// set the LCD cursor position
if (set_state == SET_NONE)
lcd_moveto(LINE2, evtctr);
else
lcd_moveto(LINE2, 13+evtctr);
lcd_putc(ch);
}
// scroll back to beginning of line
if (evtctr == 15)
evtctr=0;
}
// clock update code from task 2
if (seconds ==
60)
{
seconds = 0;
minutes++;
if (minutes
== 60) {
minutes =
0;
hours++;
if (hours
== 13) {
hours =
1;
}
}
}
// print the time
lcd_moveto(LINE1, 0);
ch
= colonBlink==1 ? ':' : ' ';
printf(lcd_putc,
"%2d%C%02d%C%02d", hours, ch, minutes, ch, seconds);
delay_ms(10);
}
}