![]() |
|
Rabbit 2000 Microprocessor User's Manual |
![]() |
![]() |
![]() |
17. Other Rabbit Software
17.1 Power Management Support
The power consumption and speed of operation can be throttled up and down with rough synchronism. This is done by changing the clock speed or the clock doubler. The range of control is quite wide: the speed can vary by a factor of 16 when the main clock is driving the processor. In addition, the main clock can be switched to the 32.768 kHz clock. In this case, the slowdown is very dramatic, a factor of perhaps 500. In this ultra slow mode, each clock takes about 30 µs, and a typical instruction takes 150 µs to execute. At this speed, the periodic interrupt cannot operate because the interrupt routine would execute too slowly to keep up with an interrupt every 16 clocks. Only about 3 instructions could be executed between ticks.
A different set of rules applies in the ultra slow or "sleepy" mode. The Rabbit 2000 automatically disables periodic interrupts when the clock mode is switched to 32 kHz or one of the multiples of 32 kHz. This means that the periodic-interrupt hardware does not function when running at any of these 32 kHz clock speeds simply because there are not enough clock cycles available to service the interrupt. Hence virtual watchdogs (which depend on the periodic interrupt) cannot be used in the sleepy mode. The user must set up an endless loop to determine when to exit sleepy mode. A routine,
updateTimers()
, is provided to update the system timer variables by directly reading the real-time clock and to hit the watchdog while in sleepy mode. If the user's routine cannot get around the loop in the maximum watchdog timer time-out time, the user should put several calls toupdateTimers()
in the loop. The user should avoid indiscriminate direct access to the watchdog timer and real-time clock. The least significant bits of the real-time clock cannot be read in ultra slow mode because they count fast compared to the instruction execution time. To reduce bus activity and thus power consumption, it is useful to multiply zero by zero. This requires 12 clocks for one memory cycle and reduces power consumption. Typically a number ofmul
instructions can be executed between each test of the condition being waited for.Dynamic C libraries also provide functions to change clock speeds to enter and exit sleepy mode. See the Rabbit 2000 Designer's Handbook chapter Low Power Design and Support for more details.
17.2 Reading and Writing I/O Registers
The Rabbit has two I/O spaces: internal I/O registers and external I/O registers.
17.2.1 Using Assembly Language
The fastest way to read and write I/O registers in Dynamic C is to use a short segment of assembly language inserted in the C program. Access is the same as for accessing data memory except that the instruction is preceded by a prefix (
ioi
orioe
) to indicate the internal or external I/O space.For example.
// compute value and write to Port A Data Register
value=x+y
#asm
ld a,(value) ; value to write
ioi ld (PADR),a ; write value to PADR
#endasmIn the example above the
ioi
prefix changes a store to memory to a store to an internal I/O port. The prefixioe
is used for writes to external I/O ports.17.2.2 Using Library Functions
Dynamic C functions are available to read and write I/O registers. These functions are provided for convenience. For speed, assembly code is recommended. For a complete description of the functions noted in this section, refer to the Dynamic C User's Manual or from the Help menu in Dynamic C, access the HTML Function Reference or Function Lookup options.
To read internal I/O registers, there are two functions.
int RdPortI(int PORT) ; // returns PORT, high byte zero
int BitRdPortI(int PORT, int bitcode); // bit code 0-7To write internal I/O registers, there are two functions.
void WrPortI(int PORT, char *PORTShadow, int value);
void BitWrPortI(int PORT, char *PORTShadow, int value, int bitcode);The external registers are also accessible with Dynamic C functions.
int RdPortE(int PORT) ; // returns PORT, high byte zero
int BitRdPortE(int PORT, int bitcode); // bit code 0-7
int WrPortE(int PORT, char *PORTShadow, int value);
int BitWrPortE(int PORT, char *PORTShadow, int value, int bitcode);In order to read a port the following code could be used:
k=RdPortI(PADR); // returns Port A Data Register17.3 Shadow Registers
Many of the registers of the Rabbit's internal I/O devices are write-only. This saves gates on the chip, making possible greater capability at lower cost. Write-only registers are easier to use if a memory location, called a shadow register, is associated with each write-only register. To make shadow register names easy to remember, the word shadow is appended to the register name. For example the register GOCR (Global Output Control register) has the shadow
GOCRShadow
. Some shadow registers are defined in the BIOS source code as shown below.
char GCSRShadow; // Global Control Status Register
char GOCRShadow; // Global Output Control Register
char GCDRShadow; // Global Clock Doubler RegisterIf the port is a write-only port, the shadow register can be used to find out the port's contents. For example GCSR has a number of write-only bits. These can be read by consulting the shadow, provided that the shadow register is always updated when writing to the register.
k=GCSRShadow;17.3.1 Updating Shadow Registers
If the address of a shadow register is passed as an argument to one of the functions that write to the internal or external I/O registers, then the shadow register will be updated as well as the specified I/O register.
A
NULL
pointer may replace the pointer to a shadow register as an argument toWrPortI()
andWrPortE()
; the shadow register associated with the port will not be updated. A pointer to the shadow register is mandatory forBitWrPortI()
andBitWrPortE()
.17.3.2 Interrupt While Updating Registers
When manipulating I/O registers and shadow registers, the programmer must keep in mind that an interrupt can take place in the middle of the sequence of operations, and then the interrupt routine may manipulate the same registers. If this possibility exists, then a solution must be crafted for the particular situation. Usually it is not necessary to disable the interrupts while manipulating registers and their associated shadow registers.
17.3.2.1 Atomic Instruction
As an example, consider the parallel port D data direction register (PDDDR). This register is write only, and it contains 8 bits corresponding to the 8 I/O pins of parallel port D. If a bit in this register is a "1," the corresponding port pin is an output, otherwise it is an input. It is easy to imagine a situation where different parts of the application, such as an interrupt routine and a background routine, need to be in charge of different bits in the PDDDR register. The following code sets a bit in the shadow and then sets the I/O register. If an interrupt takes place between the
set
and theldd
, and changes the shadow register and PDDDR, the correct value will still be set in PDDDR.
ld hl,PDDDRShadow ; point to shadow register
ld de,PDDDR ; set de to point to I/O reg
set 5,(hl) ; set bit 5 of shadow register
; use ldd instruction for atomic transfer
ioi ldd ; (io de)<-(hl) side effect: hl--, de--In this case, the
ldd
instruction when used with an I/O prefix provides a convenient data move from a memory location to an I/O location. Importantly, theldd
instruction is an atomic operation so there is no danger that an interrupt routine could change the shadow register during the move to the PDDDR register.17.3.2.2 Non-atomic Instructions
If the following two instructions were used instead of the
ldd
instruction,
ld a,(hl)
ld (PDDDR),a ; output to PDDDRthen an interrupt could take place after the first instruction, change the shadow register and the PDDDR register, and then after a return from the interrupt, the second instruction would execute and store an obsolete copy of the shadow register in the PDDDR, setting it to a wrong value.
17.3.3 Write-only Registers Without Shadow Registers
Shadow register are not needed for many of the registers that can be written to. In some cases, writing to registers is used as a handy way of changing a peripheral's state, and the data bits written are ignored. For example, a write to the status register in the Rabbit serial ports is used to clear the transmitter interrupt request, but the data bits are ignored, and the status register is actually a read-only register except for the special functionality attached to the act of writing the register. An illustration of a write-only register for which a shadow is unnecessary is the transmitter data register in the Rabbit serial port. The transmitter data register is a write-only register, but there is little reason to have a shadow register since any data bits stored are transmitted promptly on the serial port.
17.4 Timer and Clock Usage
The battery-backable real-time clock is a 48 bit counter that counts at 32768 counts per second. The counting frequency comes from the 32.768 kHz oscillator which is separate from the main oscillator. Two other important devices are also powered from the 32.768 kHz oscillator: the periodic interrupt and the watchdog timer. It is assumed that all measurements of time will derive from the real-time clock and not the main processor clock which operates at a much higher frequency (e.g. 22.1184 MHz). This allows the main processor oscillator to use less expensive ceramic resonators rather than quartz crystals. Ceramic resonators typically have an error of 5 parts in 1000, while crystals are much more accurate, to a few seconds per day.
Two library functions are provided to read and write the real-time clock:
unsigned long int read_rtc(void) ; // read bits 15-46 rtc
void write_rtc(unsigned long int time) ; // write bits 15-46
// note: bits 0-14 and bit 47 are zeroedHowever, it is not intended that the real-time clock be read and written frequently. The procedure to read it is lengthy and has an uncertain execution time. The procedure for writing the clock is even more complicated. Instead, Dynamic C software maintains a long variable
SEC_TIMER
in memory.SEC_TIMER
is synchronized with the real-time clock when the Virtual Driver starts, and updated every second by the periodic interrupt. It may be read or written directly by the user's programs. SinceSEC_TIMER
is driven by the same oscillator as the real-time clock there is no relative gain or loss of time between the two. A millisecond timer variable,MS_TIMER
, is also maintained by the Virtual Driver.Two utility routines are provided that can be used to convert times between the traditional format (10-Jan-2000 17:34:12) and the seconds since 1-Jan-1980 format.
// converts time structure to seconds
unsigned long mktime(struct tm *timeptr);
// seconds to structure
unsigned int mktm(struct tm *timeptr, unsigned long time);The format of the structure used is the following
struct tm {
char tm_sec; // seconds 0-59
char tm_min; // 0-59
char tm_hour; // 0-59
char tm_mday; // 1-31
char tm_mon; // 1-12
char tm_year; // 00-150 (1900-2050)
char tm_wday; // 0-6 0==sunday
};The day of the week is not used to compute the long seconds, but it is generated when computing from long seconds to the structure. A utility program,
setclock.c
, is available to set the date and time in the real-time clock from the Dynamic C STDIO console.
Rabbit Semiconductor www.rabbit.com |
![]() |
![]() |
![]() |