
Real-time Systems – SMD138
Lab 1
C programming on bare metal
The purpose of this assignment is to exercise C programming immediately
on to of the bare metal computer hardware, and to concretely illustrate
some of the fundamental limitations of ordinary sequential programming.
The tasks to be solved might
therefore appear a bit artificial, but they have been chosen more
because of their clarity than because of their realism.
The assignment should be carried out in C targeting the AVR Butterfly board. Note: in order
to make the on-board processor run at its maximum speed, its CPU clock
prescaler functionality must be disabled. This is achieved by writing
the byte values 0x80 followed by 0x00 to the special register CLKPR
when your program starts up. See the ATmega169
manual, pages 29-31, for further info. Moreover, you won't need to
worry about termination in any of these tasks. The intended behavior of
the programs you will write is that they will continue to run until the
Butterfly board is reset.
Part
1
The first task is to establish some means of printing numeric output on
the LCD display. To this end you should design a C function writeChar(char ch,
int pos), that activates display segments at character position pos such
that a visible representation of the character ch is
obtained. The function should simply do nothing if the pos
parameter is outside the range supported by the display (that is, 0..5).
Furthermore, because this assignment isn't primarily about graphics
design, you don't have to support the entire ASCII character range – it
will be perfectly ok just to print a blank representation of any
character outside the numeric characters range ('0'..'9').
Important information regarding the LCD control registers and the
segment layout can be found in the AVR LCD driver
manual, section 3, and in the ATmega169
manual, pages 209-224. For device initialization it is recommended
that the following values are used: drive time 300 microseconds,
contrast control voltage 3.35 V, external asynchronous clock source,
1/3 bias, 1/4 duty cycle, 25 segments enabled, prescaler setting N=16,
clock divider setting D=8, LCD enabled, low power waveform, no frame
interrupt, no blanking. It is part of the assignment to figure out the
actual control register bit patterns that correspond to these settings.
When you have verified that individual digits can be properly displayed
at the desired position, write a function writeLong(long i)
that first converts the long integer i to a
string of characters, then calls writeChar
with a proper position value for each of these characters. In case the
string representation of i requires
more than six characters, the function should only display the six least significant digits.
Finally, demonstrate your LCD driver by implementing a program that
continuously computes increasing prime numbers and prints the on the
display. The prime numbers shall be computed in the simplest possible
way: a
long
variable is incremented for each turn in a loop, and a helper
function is_prime(long
i)
is called to determine whether the value is a prime number and thus to
be printed. The initial value for this variable can be any value you
find meaningful. Implementing is_prime(i)
is tentatively done by computing i % n
(i.e.,
the remainder from division i/n) for
all 2 <=
n
< i, and returning false (0) if any such expression is 0,
true
(1) otherwise.
Wrap up you solution in a function primes()
that is called directly from main()
after device initialization.
Part 2
The second task is to write a program that makes a segment of the LCD
display blink (i.e., turn on and off) with a steady frequency of 1 Hz.
The segment chosen should be one of the special symbols that are not
part of the six generic character display areas. Unfortunately the
documentation is a bit vague regarding which bits in the LCD control
registers that connect to these symbols, thus some amount of
experimentation will be required. However, the possibilities are quite
limited: the only control register bits not already tied to the generic
character segments are bits 1, 2, 5 and 6 of registers LCDDR0, LCDDR1
and LCDDR2; as well as bit 0 of registers LCDDR3, LCDDR8, LCDDR13 and
LCDDR18.
For timing purposes, the 16-bit Timer/Counter1 unit should be used.
Details of this device can be found in pages 95-123 of the ATmega169 manual; however, most of this
information is beyond the scope of this assignment. All we will need is
a continuously running timer that can be read at any time and doesn't
wrap around too often. A suitable configuration is to let
Timer/Counter1 use the 8 MHz system clock with a prescaling factor of
256, this should make the 16 bits in register TCNT1 wrap around
approximately every 2.1 seconds. The only register whose default values
need to be changed in order to achieve this is TCCR1B.
Concretely, this program should maintain an unsigned integer variable
representing the next timer value to wait for, and use busy-waiting to
stop execution until register TCNT1 has reached that value. Note that
blinking the display requires updating in two phases, that preferably
last half the desired period. Note also that some special measures need
to be taken to ensure proper operation when the timer wraps around to
zero.
Wrap up you solution in a function blink()
that is called directly from main()
after device initialization.
Part
3
The third part of this assignments is about dealing with external
input. The task is to write a program that toggles between two LCD
display segments (pick two that aren't used in any of the previous
parts of the assignment) whenever the small on-board joystick is
activated. The joystick is connected to certain bits of I/O ports B and
E; here it is recommended that bit 7 of port B is used, which
corresponds to downward movement of the joystick.
Details on how to operate the I/O ports is contained in pages 55-78 of
the ATmega169 manual; however, as with the
section on 16-bit timers, most of this information is beyond the scope
of the assignment. What is necessary to know is that the I/O ports can
function as both inputs and outputs, and that the output register for
port x (PORTx) controls pull-up resistors for
the input register of port x (PINx)
whenever port x is configured
for input (the latter is controlled individually
for each bit of port x by the
settings in register DDRx).
All ports are
configured as inputs by default, but in order to obtain proper operation of the joystick
switches, the pull-up resistors must be activated for the corresponding
pins. For our purposes, this amounts to setting bit 7 high in register
PORTB during program initialization.
The program should be implemented using busy-waiting for changes on bit
7 in register PINB. Notice that the joystick switches are active low,
which means that they will cause a 0 in the input bit position as long
as the switch is pressed, and a 1 otherwise.
Wrap up you solution in a function button()
that is called directly from main()
after device initialization.
Part 4
The final part of the assignment consists of putting all the previous
parts together as one single application. This will most certainly
require some modifications to your existing code; in fact, the purpose
of this part is specifically to draw your attention to these
modifications. To this end, you will
not be allowed to modify any of your existing functions, instead
you should create new copies of any functions that need do be changed.
Try out your program for different start values for the prime number
computations, for example 1, 5000, and 25000. How do these values
affect the display of the 1 Hz blinking segment? How is the
response time of the joystick affected? What would it take to ensure a
stable blinking display and a responsive joystick, that are independent
of the currently calculated prime number?
What is there to say about the structure
of
your combined program? Was some clarity of your individual solutions
lost in the adaption process? Why wasn't it possible to simply call the
original functions one after another in the main program? If you didn't
modify is_prime()
in your solution to part 4, what stopped you from doing that in order
to obtain constant response times?