The daytimer-alarm (DTA) is a time management tool, an advanced user-friendly alarm clock. Its key features are:
This system is implemented a Control module that interfaces with a keypad driver and an LCD driver.
Time and date are tracked using a series of cascaded counters with different maximum and minimum values. User-programmed alarms are stored in RAM on-chip.
Our target device is the EPF10K20RC240-4 on a UP1 board. The following is a block diagram of the chip's inputs and outputs.
The project utilized 1058 logic cells, or 91% of the available space on the FPGA.
On-chip RAM was used for two purposes: to store user-programmed alarm events, and to store read-only data for the LCD. In total, we used 4 EABs out of the available 6.
The maximum speed of the system, as measured by the registered performance timing analyzer, is about 7 MHz, so its minimum clock period is about 140ns. For this reason, we have used a counter as a clock divider to multiply the period of the 25.175MHz system clock by 4, getting a 160ns clock.
Table of Contents
Abstract
*IC Data Sheet
*Logic cell usage
*EAB usage
*Speed
*Overview
*Physical User Interface
*Operation
*Start-up
*Setting the time
*The normal state
*Selecting an event
*Viewing and editing an event
*When an alarm is triggered
*Design Details
*Entity Hierarchy
*Overall Design
*General-purpose counters
*Time and date
*keypad driver
*LCD driver
*Control structure
*Control state machine
*LCD state machine
*Alarm programming
*Design Verification
*List of test cases
*Control
*Data entry and verification
*Time and Date tracking
*Keypad
*LCD
*Top-level simulation
*Keypad
*Time and Date handling
*Incrementing the time and date
*Leap years
*Clock generation
*Reading and Writing alarm events
*Control simulation/State machine transitions
*FixedScreenPrinter
*SpecialScreenPrinter
*State Transitions
*Initial to set_time
*Set_time to normal
*Normal to select_weekly_event (and vice versa)
*Select_weekly_event to edit_weekly_event (and vice versa)
*Normal to alarm (and vice versa)
*LCD
*Prototype Testing
*Hardware setup
*Testing
*Keypad design modifications
*Physical operation
*LCD design modifications
*Reset handling
*Handshaking with Control
*Hierarchically organized entities
*General-purpose entities
*Packages
*
The Calendar Alarm is an enhanced alarm clock. It displays the date as well as the time on a 16×2 LCD, and it can be programmed with up to eight different alarms, each on a particular day of the week. Entering the current time or the time for an alarm is convenient and easy, owing to a keypad interface.
The project's 16-button keypad has the layout shown below. Unused keys are left blank.
0 |
1 |
2 |
3 |
4 |
5 |
5 |
7 |
8 |
9 |
|
|
E |
< |
> |
|
(E = enter)
A 2-row, 16 column LCD is used for presenting information and prompts to the user. A single LED is used to indicate when an alarm is active.
On reset, the LCD becomes blank:
After the reset button has been released, the user is prompted to enter a new system time. A cursor indicates where the user's input would appear, and text above the cursor specifies what fields should be entered and in what order. Except for the weekday, the fields are in order of descending significance.
YyyyMmDdHhMmSsW
_
After the user has typed in a new time and hit the Enter key, the system enters its normal state. Generally, the time shown will be whatever the user entered earlier, but if the user provided an invalid number for any field, then the number in that field will be defaulted to its smallest allowable value.
Although the user initially needs to supply the day of the week, the weekday and the day will automatically be updated henceforth in accordance with the rules of the Gregorian Calendar. (A year that is divisible by 4 is a leap year, unless its last two digits are "00", in which case, it is a leap year if the century is divisible by 4)
YyyyMmDdHhMmSsW
199811230859592
If the user hits any key while in the normal state, the system enters the event selection state, from which the user may view and edit any of the 8 programmable alarms. To view/edit one, the user hits a number key from 0 to 7. Hitting any other key will exit to the normal state.
Event? (0-7)
Upon choosing an alarm number, the user is shown the time and day of the week on which the chosen alarm is set to go off. A cursor on the first digit allows the user to overwrite the alarm. Note that invalid field entries are allowed here. Entering invalid data effectively deletes or deactivates an alarm.
HHMMW
06002
HHMMW
07002
An alarm may only be triggered while the system is in the normal state. When the current hour, minute and weekday matches one of the programmed events, an LED lights up for 5 seconds or until the user hits a key.
Our source code is provided in Appendix A.
The following diagram depicts the hierarchy of the entities used in our design. Each entity is described in more detail later.
The figure below shows the overall design of our system.
Two general-purpose counters were designed for use in the project. They had the same functionality, with the exception that one counted binary numbers of arbitrary width (entity counter), while the other counted two-digit BCD numbers (entity BCDtwoDigitCounter). Both counters had inputs with which the programmer could specify the minimum and maximum counter values. They could be loaded with an input count when their load enable pins were held high, but in the event of an input count outside of the allowed range, the counter would be reset to the minimum value. A count enable pin allowed the counters to be incremented, and when they overflowed, an output was set to 1 to indicate the overflow.
Time and date matters are handled by the TimeDate entity, which contains several counter and BCDtwoDigitCounter entities, each one keeping track of a different time/date field. BCD counters are utilized in order to simplify the process of converting field values to LCD characters. Since each BCD digit corresponds exactly to a character, no tedious binary-to-decimal conversions are necessary.
The counters are arranged such that the overflow for a less significant field is the increment for a more significant field. For example, the overflow of the seconds counter is the count_enable for the minutes counter.
The counter for each field has a Min_Count and Max_Count input appropriate to the field's data, so for example, the hour counter has a Min_Count of "00" and a Max_Count of "23". The months counter is different from all the others, however, because of the fact that months contain varying numbers of days. The Max_Count for the month is supplied by a MaxDaySelector entity. The inputs of the MaxDaySelector are those which are necessary for determining the month's length: The value of the month itself, and the value of the year. MaxDaySelector chooses the month's Max_Count appropriately on the basis of these inputs. The calendar scheme used is the Gregorian Calendar.
Note that one of the fields in TimeDate is the day of the week. While this can certainly be calculated from the available data, we have decided to let the user choose the day of the week while entering the current time and date, the reason being that day-of-the-week computation is quite complicated and would be extremely slow and resource-consuming to implement. The formula is given below. In particular, note that there is a multiplication by 2.6, which can be performed either as a floating-point calculation or as times 13, divided by 5. Without a microprocessor, both approaches would be impractical.
Formula to determine the day of the week:
W = (k + floor(2.6m - 0.2) - 2C + Y + floor(Y/4) + floor(C/4)) mod 7
where floor() denotes the integer floor function,
k is day (1 to 31)
m is month (1 = March, ..., 10 = December, 11 = Jan, 12 = Feb) Treat Jan & Feb as months of the preceding year
C is century (1987 has C = 19)
Y is year (1987 has Y = 87 except Y = 86 for Jan & Feb)
W is week day (0 = Sunday, ..., 6 = Saturday)
(source: quoted from the article How to determine the day of the week, given the month, day and year, from the Sci.Math FAQ, maintained by Alex Lopez-Ortiz, document revision: Fri Feb 20 21:45:30 EST 1998)
Interfacing between the circuit and the user is the keydecoder entity, which helps the daytimer system determine which of the 16 keys the user presses.
Using pull up resistors, the signals Y1-Y4 are held normally high. When no key is pressed, the signals X1-X4 are connected to ground.
The entity will wait until a key is pressed, and at that moment the entity will ignore other keypresses and wait for 1 ms to avoid the mistake of taking a bounce as an input. After waiting for 1 ms, the entity will start scanning the columns from X1 to X4 to determine which key was pressed. When the key is released, presumably causing it to bounce, another 1 ms delay occurs to avoid interpreting the oscillation of the signals as a valid key press. The KeyDataAvailable signal is set to high when the key press is ready to be output to the control, and it will be set low when the Ack (acknowledge from control) is high.
The diagrams below show the physical layout of the keypad and the block diagram of the entity:
The LCD driver handles printing instructions from Control. Control can send one of nine pre-defined commands to the driver for execution. After each command is sent, a delay timer inside the LCD driver will count for a specific pre-set time to let the command take effect on the LCD. The commands are:
NO_ACTION: keep the LCDready signal on, send zero to all output. Control can send any other command any time.
REFRESH_SCREEN: send the LCD command to hardware to clear the display screen. This keeps LCDready down for a period of delay, so Control cannot send another command while this is taking effect.
PRINT_CHAR: write the character given by the input vector character to the LCD. This keeps LCDready down for a preset period of delay, so Control cannot send another command.
BACKUP_CURSOR: back the display cursor on the LCD by one space. This also keeps LCDready down for a while.
ADVANCE_CURSOR: advance the display cursor by one space on the LCD. This keeps LCDready down for a period of time.
LCD_FUNCTION_SET: a command to set the LCD’s display function. This keeps the LCDready signal down.
LCD_DISPLAY_ON: a command to turn on the LCD’s display mode. This is one of the LCD’s hardware commands. This keeps the LCDready signal down.
LCD_ENTRY_MODE: a command to set the mode (8-bit or 4-bit) for entering data on the LCD. This is used for setting the LCD upon power up or reset. This keeps the LCDready signal down.
CURSOR_HOME: return the display cursor to the start of the LCD screen. This keeps the LCDready signal down.
Whenever Control requires a sequence of commands to be sent to the physical LCD, such as during reset, where four commands need to be sent to the display one after another, it will send individual commands one by one to the LCD driver. The LCD driver creates a small delay (length set in LCD_Package.vhd) after each command to the LCD display, so they can take effect before the next one arrives. While each command is being sent, the enable (E) signal going to the LCD will rise for one clock cycle and drop, in order to enable the command to take effect.
All the output lines are sent to the LCD, except LCDready, which is a flow control signal going to Control. Only when LCDready is on can Control send a command to LCD driver. While the delay counter is running for each command, the LCDready signal is set low, so no other commands can be sent by Control. DB is the 8-bit data channel to the LCD, and sends characters to be printed or LCD commands. RS and RW are command lines to the LCD. E is the enable signal for the LCD, and it needs to be set high, then set low. The command takes effect on the falling edge.
All inputs are directly connected from Control. Clock provides the system clock, important since all commands are synchronous and take effect on the rising edge of the clock. Reset resets the delay timer and assumes the NO_ACTION command. Instruction sends the particular command into LCD driver. Character is the character to be output when the instruction is PRINT_CHAR.
The Control entity is a large one, consisting of many sub-entities. It is primarily a state machine, where each state can be directly identified with one of the screens that the user can see and interact with:
State machine outputs are handled in one of two ways. If the keypad driver indicates to Control that a key was pressed, then the value of the keypress is examined and appropriate action is taken depending on what the key was. This action may be an output to the LCD driver, a state transition, an assignment of values to registers within the Control unit, or a combination of the above. When a key has not been pressed (i.e., most of the time) a different set of actions is performed during each clock cycle.
At the same time that the main state machine is running, there is another state machine running inside Control: the LCD state machine. Its behaviour is depicted in the figure shown below:
The job of the LCD state machine is to ensure that the display is kept up-tp-date.
Whenever the main control state machine changes its state, the LCD state machine clears the screen and redraws it in two stages. The first line of every screen is fixed, so in the first redrawing stage, corresponding to the FIXED_TEXT state, 16 characters are fetched from ROM and output to the screen one by one, under the control of the FixedScreenPrinter entity. The set of characters used is dependent on the status of the controller state machine. The character data is stored in a single EAB using the lpm_rom component.
After the first line has been printed, the LCD state machine enters the VARIABLE_TEXT state and it activates the SpecialScreenPrinter entity. The second line of the display may or may not contain text, but when it does, that text is, in some manner, dynamic. It could be the current time, or the time for an alarm. In either case, the data used for printing the second line must be obtained from an external source. The SpecialScreenPrinter entity takes this data and outputs it to the screen one by one. If the state is one that requires the user to edit data, the SpecialScreenPrinter will also reset the cursor to the beginning of the second line after it has finished printing.
In the USER_INITIATED state, all display initialization has been completed, and any further output to the LCD in the current controller state will be initiated by the user's input from the keypad.
Alarm events are defined by the hour, minute, and weekday on which they are to occur. Each one of consists of a group of bits stored in RAM, managed by an lpm_ram_dq component. There are eight events in total.
Every minute, while in the normal Control state, the current hour, minute, and weekday, as stored in the TimeDate component, is compared to each of the eight events stored in RAM. On the very first match, the Control state is switched to the alarm state.
When a user selects an alarm event for viewing or editing, the address input of the lpm_ram_dq is set to the user's choice or 0 to 7. When the user has finished editing the fields for an event, the registers containing the user's input are loaded into RAM at the selected address.
Each alarm event is stored as a single word. Given that there are 5 BCD digits per event, and 4 bits per BCD digit, 20 embedded cells are required for alarm programming. Due to the fact that there are 8 embedded cells per EAB, the alarm programming used a total of 3 EABs.
All simulation waveforms are provided in Appendix B.
This is a brief summary of the test cases that we have considered. Details are provided in later sections of this report.
State machine transitions:
Also verify correctness of state machine outputs (eg., commands to LCD)
It was impractical to simulate the complete chip because the complete system utilizes long delays, the two most notable of which are a one-second "interrupt" and an LCD delay timer which generates delays on the order of 4 microseconds.
Instead, we have opted to conduct simulations of the major entities within the chip. The complete design should work, assuming that the timing and interfaces are set up properly between the keydecoder, Control, ClockGenerator, and LCD entities (the four chip-level entities--see heirarchy diagram).
To justify our approach to simulation, we must first summarize how the top-level entities interact with each other.
Most of the action happens in Control and its sub-entities.
The keydecoder and ClockGenerator interface are quite straightforward, as mentioned above. The only interface left to consider is the LCD.
The LCD interface can be simulated by manually dropping the LCDready signal when an instruction is sent from Control to the LCD. For speed, however, most of the simulation of Control has LCDready high. In a few places, LCDready is dropped low as it would in practice, but it is never held low for its "true" duration, because the true duration would turn the simulation into a nightmare of perpetual waiting. The LCD delays constitute one major reason for not testing the whole chip together.
Following are simulation details for entities in the chip. The source code for each entity can be found later in this report. Simulation waveforms, if applicable, are included after the code that they simulate.
Two different test cases were chosen to test the decoder:
First test case: The input vector, Y0-Y3, was set to "1110" with debounce taken into consideration. The input vector was set to "1110" to simulate the situation in which the key 3 is pressed on the keyboard.
For a given '0' in the Y vector, there are 4 possible keys that may have been pressed. It is expected that the X0-X3 outputs would respond to the change in Y by scanning for each of the four possibilities. It would do this by changing X0-3 from "0000" to the sequence "1110", "1101", "1011", "0111". The key that was pressed is determined by finding the X pattern that matches the Y pattern.
Second test case: The input vector, Y0-Y3, was set to "0111" with debounce taken into consideration. It was set to this value to simulate the ENT key being pressed.
In both test cases, the KeyDataAvailable signal goes high after the identity of the key has been determined, and it resmains high until it gets an acknowledge from the Control entity, or until the button is released, whichever. In the actual test case that we used, the Controller acknowledge came first.
In the first case, the output vector was produce "0011" which correspond to the key 3 and the data available was set high at the same time when the state is at ScanX3. This corresponds to the expected results and indicates that the decoder build functions as designed. The value of KeyData keeps switching since the input "1110" is treated as all the 4 keys, 0, 1, 2, 3 are all pressed.
In the second case, the output bit-vector did not produce "0110" and the data available signal did not trigger to high, hence it indicates that without debounce the decoder will treat the input data as glitches from debounce.
From the two test cases, the conclusion gathered is that the decoder functions as designed as seen from the two test cases.
TimeDate, MaxDaySelector, counter, BCDtwoDigitCounter, ClockGenerator
Incrementing the time and date
To test all six fields at once for correct increment-and-overflow behaviour, the ultimate boundary case, 9999 December 31 23:59:59, was loaded so that incrementing the seconds would cause all the other fields to increment and overflow. The system did so correctly, ending up at 1 Jan 1 00:00:00.
Leap year testing was performed by setting the time and date to 23:59:59 on February 28. On a non-leap year, it advanced to March 1, while on a leap year, it advanced to February 29, then March 1. The following years were tested (leap years according to the Gregorian Calendar are marked by an asterisk) 1900, 1999, 2000*. Note that 1900, though divisible by 4, is not a leap year because when the first two digits of a year are zero, the year is a leap year only if the century is divisible by 4.
The above simulation of the TimeDate entity shows that the counter and BCDtwoDigitCounter entities work correctly, since they are used in TimeDate itself. In fact, we have included a separate simulation of the counter entity to make its operation clearer. The ClockGenerator entity is a straightforward application of the counter entity. It merely uses the counter's overflow output as its own clock outputs. The one-second clock uses a counter that overflows after 2,175,000 global clock ticks, and the system clock uses a counter that overflows after 4 global clock ticks.
Reading and Writing alarm events
Entity covered: WeeklyEventManager
To test that events stored in RAM could be read correctly, we used the memory initialization feature of MAX+plus II to initialize the memory from an MIF file. We then set the event ID input for WeeklyEventManager to point to each event in turn. The entity correctly output the data in RAM for each event.
To test writing events, we set a test group of values on the inputs, and asserted the load pin, then we noted that the output data changed to match the data that we had just loaded into RAM.
As a final test, we set the input data to be the same as one of the events already initialized in RAM. After asserting the event-check pin, the system scanned through all stored events and indicated that there was a match.
Control simulation/State machine transitions
Control, FixedScreenPrinter, SpecialScreenPrinter, WeeklyEventManager, TimeDate, BCDtoLCD,
MaxDaySelector, BCDtwoDigitCounter, counter
The simulation of the Control entity demonstrates several aspects of the system: the state machine transitions, and the I/O and memory management that occurs as part of each state.
Regarding the simulation waveforms for Control, a few remarks are in order.
First, note that the simulation has the oneSecondClock go high twice, even though the simulation is only 71ns long. This is necessary to demonstrate certain aspects of the system. We certainly do not wish to actually wait for 1s! (As would have been the case if we wired the whole circuit together.)
Second, note that the LCDready signal is usually held high. In reality, it would drop low for about 4 microseconds after each LCD instruction. In some places in the simulation, it is held low for just a few clock cycles to roughly emulate what it would really do. Most of the time, though, it is kept high to avoid the unnecessary tedium of adjusting it for every LCD instruction.
The FixedScreenPrinter is used to output fixed text to the LCD, with the actual text depending on what state the controller is in.
The SpecialScreenPrinter is used to output dynamic text to the LCD. Immediately after the state changes and the LCD refreshes the screen with fixed text, the SpecialScreenPrinter adds extra text corresponding to the system time or to an event in RAM. See the simulation for a graphical representation of the process of drawing on the screen. This entity is used in the following states: normal, edit_weekly_event, and alarm
This state transition is triggered by a one-second clock after the system has been reset to the
initial state. When this happens, the screen is refreshed with a header on the first line, and a cursor on the bottom line. At this point, the user may type in a new system tiime.
When the user hits the ENT key (keyData = C), the state changes to the normal state, where the current time appears on the screen.
Normal to select_weekly_event (and vice versa)
If, in the normal state, the user hits any key (other than the last one, RST), the state changes to a selection screen, at which the user can choose to view/edit an event from 0 to 7. If the user hits any other key (except RST = F), the system returns to the normal state.
Select_weekly_event to edit_weekly_event (and vice versa)
If the user hits 0 to 7 while in the select state, the data for the chosen event is printed onscreen. The user can type in new values at this point and hit ENT to save, or hit an unlabelled key to quit without saving. In either case, the system will go back to the selection state. In the simulation, an event is typed in and the enter key pressed. Back in the selection screen, the event is reselected to verify that changes were indeed saved.
Normal to alarm (and vice versa)
The event that was entered above was set up to be exactly one second later than the system time entered in the set_time part of the simulation. To test a normal to alarm transition, the one-second clock was brought high. This caused the system to reprint the time, and assert the LEDalarm output. After a key was pressed, the system returned to normal.
Entities covered: LCD.vhd, LCD_DelayTimer.vhd
Test Cases:
The LCD test consisted of sending all the possible commands to the instruction input vector of the LCD controller. The output lines were then observed to see if they functioned according to specifications. With all of these test cases, a long period (>4 clock cycles) of 0 input in the instruction vector was set between each of the eight commands. The outputs DB, RS, RW, E, and LCDready were then observed. The delay counter was set to 3 counts (rather than the usual tens of thousands) in order to make the output waveform shorter and save paper. The following commands were tested:
REFRESH_SCREEN (instruction=0001):
The DB vector output "00000001", which was the correct LCD command for clear screen. The RW and RS signals were both set low in the correct manner, enable was on for one clock cycled and dropped, and LCDready was lowered for the duration of the delay.
PRINT_CHAR (instruction=0010, character=01110011):
The DB vector output "01110011", which was the value of character. The RW signal was set low in the correct manner, and RS was set high since this was the correct value for a character send. E was on for a clock cycle in the correct manner, and LCDready was dropped then raised for the duration.
BACKUP_CURSOR (instruction=0011):
DB output "00010000", which was the correct command for backing up the cursor. The RW and RS signals were set low. The E and LCDready signals also behaved as they should.
ADVANCE_CURSOR (instruction=0100):
DB output "00010100", which was the correct command. RW, RS, E, and LCDready all behaved correctly.
LCD_FUNCTION_SET (instruction=0101):
DB output "00111000", which was the correct command. RW, RS, E, and LCDready all behaved correctly.
LCD_DISPLAY_ON (instruction=0110):
DB output "00001110", which was the correct command. RW, RS, E, and LCDready all behaved correctly.
LCD_ENTRY_MODE (instruction=0111):
DB output "00000110", which was the correct command. RW, RS, E, and LCDready all behaved correctly.
CURSOR_HOME (instruction=1000):
DB output "00000010", which was the correct command. RW, RS, E, and LCDready all behaved correctly.
NO_ACTION (instruction=0000):
DB output "00000000", which was basically no action. RW, RS, and E were all set low. LCDready was high. This meant the LCD driver was idle and ready for a new command.
The test cases thus showed that the LCD driver behaved as it should, and all the commands were interpreted correctly. The code and annotated simulation waveforms are attached in the appendix.
The prototype system consisted of an Altera UP1 board, a keypad, a 16x2 character Sharp LCD, and associated wiring and resistors.
We attached the keypad and LCD to the chip via FLEX Expansion Bus A.
The global clock was connected to the 25.175MHz oscillator, and the global reset was connected to FLEX push-button B.
The alarm LED was mapped to FLEX digit 2, segment "a".
We also mapped the three horizontal segments of FLEX digit 1 to the controller state so that we would be able to easily determine what state the system was in.
We tested the system by powering it up and hitting the reset button, then hitting keypad keys in response to what we saw on the LCD and on the UP1 LEDs. We defined "correct" operation as the same behaviour as that described in the project overview. Deviations from the described behaviour were considered errors in the design.
Our initial tests showed that the LCD was blank, and the keypad caused no visible reaction.
Our initial keypad design did not work because the voltage levels from the keypad were too small in the case of active high signals, and they were too high for active low signals. Voltage division across the pull-up resistors and the keypad internal resistance was to blame for the bad signals.
We had to modify the keypad design such that keypad entity "scans" each column, one at a time, to detect a low signal. The row in which the key is pressed will give out a low signal when the entity "scans" that particular columns, else a high signal will be detected.
After consulting with Edgar Wong from the Advanced Entry System group, we concluded that the LCD was being incorrectly reset, so we modified our reset instruction sequence, and found that with the new sequence, the LCD reset correctly, and could show characters on the screen.
Unfortunately, after correctly resetting the LCD, we found that the characters being sent to the screen were incomplete and in seemingly random order. This problem was interpreted to be a symptom of improper handshaking between the Control entity and the LCD driver entity.
We totally reworked the handshaking mechanism by introducing a state machine to observe the LCD driver's LCDready signal. The addition of intensive handshaking code.
Resource-motivated modifications
The changes that we had to make to the design resulted in greater logic cell usage and a marked increase in the usage of logic cell interconnects. We had to replace the Gregorian calendar in Control with the less resource-demanding Julian calendar, and we also had to removed the input-count error-checking feature in the counters.
The source files included here are the following:
Hierarchically organized entities
Chip.vhd
ClockGenerator.vhd
keydecoder.vhd
Control.vhd
TimeDate.vhd
MaxDaySelector.vhd
WeeklyEventManager.vhd
SpecialScreenPrinter.vhd
FixedScreenPrinter.vhd
BCDtoLCD.vhd
Oscillator.vhd
LCD.vhd
LCDdelayTimer.vhd
counter.vhd
BCDtwoDigitCounter.vhd
constants.vhd
Control_Package.vhd
BCD_Package.vhd
LCD_Package.vhd
Time_Package.vhd
Appendix B: Simulation Waveforms
The following simulation waveforms are supplied: