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 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 to updateTimers() 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 of mul 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 or ioe) 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
>     #endasm

> In the example above the ioi prefix changes a store to memory to a store to an internal I/O port. The prefix ioe 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 Premier 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-7

> To 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 Register

> ### 17.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 Register

> If 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 to WrPortI() and WrPortE(); the shadow register associated with the port will not be updated. A pointer to the shadow register is mandatory for BitWrPortI() and BitWrPortE().

> #### 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 the ldd, 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, the ldd 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 PDDDR

> then 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 zeroed

> However, 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. Since SEC_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 http://www.rabbitsemiconductor.com Voice: (530) 757-8400 FAX: (530) 757-8402 sales@rabbitsemiconductor.com