Eric, an independent embedded-systems consultant from Redmond, WA, won the 1993 Motorola 68HC16 design contest and was a finalist in the 1994 Motorola TPU design contest. He can be contacted at eric@digex.wa.com or by phone at 206-885-4107.
I was recently involved in a Motorola 68332-based design project that required an external, battery-powered timekeeping device and a small amount of nonvolatile memory. Initially, we chose the Dallas Semiconductor 1202 serial timekeeping chip as the external device because it provides a 3-wire synchronous serial interface, a real-time clock/calendar function that produces data nearly identical to the ANSI C function localtime(), and 24 bytes of static RAM (SRAM) retained by the battery which powers the clock.
Even though their synchronous interfaces aren't directly compatible and they expect data in opposite bit order, tying the 68332 to the 1202 appeared straightforward enough because of the flexibility of Motorola's Queued Serial Peripheral Interface (QSPI). On the 1202 side, the critical connections are reset, clock, and bidirectional data, as described in Figure 1. The key 68332 connections, on the other hand, are clock, data out, data in, and chip selects.
Since the interface protocol was fixed by the 1202, we made accommodations both in the external hardware and QSPI configuration. For instance, the 1202 monitors its own reset line: When its reset line goes high, the 1202 begins latching data on the rising edges of the clock. The first bit in is the least-significant bit of a command byte. After receiving the eighth bit, the 1202 interprets the command. If a read command was issued, the 1202 drives the data line following the falling edge of the clock; see Figure 2. If a write-data command was issued, the 1202 continues latching data bits on each rising edge of the clock.
Motorola's 68332 QSPI is flexible in terms of data rates, clock polarity, and clocking edge. The chip selects can be programmed to assume any desired pattern before, during, and after a serial transaction. You can programmatically configure the setup time between the assertion of the chip selects and the first edge of the clock, as well as the hold time during which the chip selects are held asserted after the last data bit has been clocked. The flexibility of the interface is useful but requires careful setup of the low-level driver routines. We also used the QSPI to serial load a sizable field-programmable gate array (FPGA) from program ROM at 4 Mbits/sec.
The first problem we confronted in the design was how to split the single bidirectional data line at the 1202 into the two unidirectional data lines at the 68332. We did this by implementing the simple gating circuit controlled by PCS1; see Figure 3. If PCS1 is high, data output from the MOSI output of the 68332 is transmitted to the 1202. If PCS1 is low, data transmitted by the 1202 is directed to the MISO input of the 68332. The reset line of the 1202 is driven directly from the PCS2 chip select. The serial-clock output from the 68332 is connected to the serial-clock input on the 1202. The QSPI is a full-duplex interface. Data is normally both transmitted and received on every clock. In this system, the data received while transmitting commands is ignored.
The command/data protocol of the 1202 commences with a command byte that's transmitted to the 1202. This byte contains a read/write bit which determines the direction of data flow for bits following the command byte. You can request the transfer of a single byte or a whole block (burst) of bytes. We used both modes in our design. The clock-burst command causes the transfer of the entire set of clock/calendar registers. Since the QSPI can transfer up to 16 packets of 1 to 16 bits each, it worked well to transfer the command byte and then the eight bytes associated with the clock/calendar.
Listing One is the include file which is referenced by all listings presented in this article. Listing Two is the software that drives the QSPI. The set_clkW() and time() routines--the entry points for upper-level application code--always reconfigure the QSPI for use in communicating with the 1202. This is necessary because the interface is used to talk to the FPGA mentioned earlier. Note that these routines bit reverse the data sent to the clock because the QSPI uses MSB-first transactions while the 1202 is just the opposite. Also note that the set_clkW() function verifies the operation by calling time() after setting the time via set_clkV(). The command and configuration registers need be set up only once using qspi_initV().
Two control bits in the 1202 must be manipulated: the clock-halt bit (which must be set to 0 for the clock function to work) and the write-protect bit (which must be 0 before the clock or SRAM can be written to). This write-protect bit feature led me to curse the 1202 databook.
Initially, I thought I could do a burst read or write of the clock registers and access only the seven bytes I was interested in; see Figure 4. I thought it would be proper to create a separate routine to set or clear the write-protect bit; see qset_writeproV() in Listing Two. Then I could also use this function when accessing SRAM. To set the clock, I would clear the write-protect bit, do a burst transfer of the seven clock registers, and then set the write-protect bit.
After prototyping and testing the code, I discovered that no matter how hard I tried, I couldn't set the clock. Finally, I noticed a small box tucked away at the bottom of the transfer chart that was apparently associated with the burst-mode transfer diagram; see Figure 2. According to the box, all registers associated with the clock have to be transferred. After setting the transfer for eight bytes, I could suddenly set the clock. I had been led astray by the fact that the prototype burst-mode read transferred only seven bytes with no ill effect. I'll say here what the databook should have said about burst-mode transfers: You must transfer all eight bytes of the clock/calendar function or all 24 bytes of the RAM registers when doing burst writes. If you don't transfer the complete set, the entire operation is discarded.
Individual routines (not included in Listing Two) handled transactions with the SRAM in the clock chip. These routines differed from time() and set_clkW() in that no bit reversing was needed, the address in the command byte was different, and the number of bytes transferred was different. Finally, qset_writeproV(ON) is called after writes to SRAM.
Having discovered the solution to the clock-set problem, I reviewed the databook to see if I'd missed anything else. I had: The timing diagram shows that the SCLK input must be low when reset is de-asserted and high when reset is reasserted at the end of the transfer. I hadn't noticed this before, and the setup I created for the QSPI didn't operate that way. In general, the QSPI serial clock starts and ends on the same phase. Since everything seemed to work fine, I ignored this discrepancy.
As the specifications were firmed up for the project, it became necessary to provide some sort of unique serialization for every unit. One project engineer noted that the Dallas Semiconductor 2404 provides a real-time clock, SRAM, and a 64-bit ROM containing a unique bit pattern. Examination of the databook revealed that the device used the same 3-wire interface as the 1202, had a real-time clock/calendar in binary format, and provided substantially more SRAM. Since we were due to get actual hardware "real soon now," I didn't bother to wire the 2404 in the prototype. A month later, I wished I had.
The 2404 contains three main functions: the serial-number ROM, SRAM, and timekeeping. The ROM can be accessed only through a "1-wire" interface--a real nuisance because this forced us to add another connection in the circuit and more driver software to the project. I had expected that all functionality would be available from either interface. Fortunately the 68332 has plenty of I/O, so the extra bit was readily available.
The 1-wire interface uses a protocol whereby the CPU initiates every bit transfer by driving the wire low for one msec; see Figure 5. If the CPU is transmitting, it either keeps the line low for 15 or more msecs, or drives it high. Thus if the 2404 sees a low pulse less than 15 msecs long, it interprets it as a 1. If the CPU is reading data, it still drives the line low for one msec. It then tri-states its driver and delays about 13 msecs before sampling the line. The 2404 will hold the line low for a 0 bit or leave the line alone for a 1. The line is pulled high by an external 4.7-KW resistor.
This interface has several critical timing requirements. First, there is a required reset sequence; see Figure 6. The 480-msec recovery period after the end of the initial reset pulse is required. The CPU must not attempt further communication during this time. I tried it, sending a command byte right after the end of the "presence pulse" sent by the 2404. Response to the command was correct in every way except that it could only pull the interface down to about 1.8 volts for a logic 0. (If you see middle-level signals like this, take a good look at your timing.)
Listing Three is the C code necessary to read the serial number from the 1-wire port. Note that I used delays longer than necessary to meet the specifications of the 2404. I wanted the routine to function properly if the CPU was ever upgraded from 16 to 20 MHz. Also note that this routine will probably fail if interrupts are enabled. This forced me to read the 1-wire interface once at start-up to get the serial number, then never use it again. All other transactions are handled on the 3-wire interface.
One problem with the 1-wire interface stemmed from our choice of the RMC pin on the MC68332 to drive the interface. We made this decision because we didn't need the read-modify-write status it provided as a default function. I configured the pin for discrete I/O, but under very unusual circumstances, the pin seemed to have a mind of its own. The CPU32 core has a reset instruction that resets all peripheral functions but not the actual CPU core. I implemented a software reset by executing a reset instruction and then fetching the reset vector and stack pointer. I believe (but can't confirm) that the reset instruction was not fully resetting all peripheral functions because RMC output, even though it was programmed as discrete I/O, would track bus grant activity (every refresh cycle). The pulsing on the I/O line completely disrupted all "1-wire" transactions--but only when the discrete output was set low. I circumvented this by changing the reset mechanism to cause a double-bus fault followed by a halt-monitor reset that actually drives the hardware-reset line low.
The specification for both the 2404 and the 1202 says that for write transactions, the clock must start low and end high. I was able to ignore this for the 1202 but the 2404 would not function properly unless this requirement was met. The QSPI is very flexible, but you cannot easily configure separate starting and ending states for the clock. You can, however, implement what is needed. The key is that while the QSPI is running, the pins behave according to the QSPI configuration and control registers. When the QSPI is inactive, the pins assume that the states defined are the normal port-D data and data-direction registers.
Listing Four shows how I accomplish the needed change in the clock state. Basically, I start the QSPI with the default state of the clock pin low. Immediately after transmission starts, I change the default state of the clock pin to high and the default state of the chip-select signal that controls reset to the 2404. After the QSPI finishes transmission, the clock pin is left high and the reset line to the 2404 is not de-asserted. In a sense, the 2404 is still waiting for more bits. At this point, the code asserts the reset pin, then lowers the clock output. Since the QSPI drives the clock low before turning it over to the default pin control, I am actually transmitting an extra bit. This extra bit could cause trouble if ignored because it writes into the next byte in the 2404. However, this was not a problem in this application.
The 2404 contains a port selector which arbitrates control between the 1- and 3-wire interfaces. If both ports are in the reset state, the first one to come out of reset will have control. The 1-wire port, however, will keep control unless a second reset/presence pulse pair is generated at the end of all 1-wire transactions. If you forget this, you won't be able to get the 3-wire interface to respond. Also, be sure that there are no glitches on the 1-wire line that could simulate a reset pulse. This could easily happen if the I/O pin were improperly configured by start-up code.
Having handled all the aforementioned issues, I was frustrated to find that the 2404 still did not respond properly to the interface. The project leader proposed eliminating the 2404 from the design because we couldn't figure out the problem and we needed to move forward. However, I wanted to give the 2404 one last shot. During one final late-night call, a Dallas Semiconductor engineer correctly suspected ringing on the clock line, so I inserted a series-softener (33
) resistor at the SCK output from the 68332. This cleaned up the ringing, but there were still random failures.
In this design, the FPGA controls DRAM refresh. I was enabling the FPGA, reading the serial number from the 2404, and then reading configuration information from the 2404 SRAM, which was used to set the refresh rate in the FPGA. It turned out that the power-up refresh rate of the FPGA was so fast that it severely impacted the timing of the 1-wire interface code and would often--but not always--lock up the 2404. Reading the 1-wire interface before enabling the FPGA circumvented this problem. The 2404 stayed in the design, and the interface has been rock solid ever since.
Woehr, Jack J. "Programming the Motorola 68332." Dr. Dobb's Journal (August 1993).
Figure 1 68332 and 1202 synchronous serial interfaces. Figure 2 1202 protocol. Figure 3 68332-to-1202 interface. Figure 4 1202 command/register map. Figure 5 2404 1-wire data formats. Figure 6 2404 reset sequence.
/* qspi.h. Contains MC68332 specific definitions for use with the QSPI */ #define UBYTE unsigned char /* An 8 bit value */ #define UWORD unsighed short /* A 16 bit value */ #define ON 1 #define OFF 0 /* Pointers to internal registers are used for ease in debugging. The pointers ** are const, the registers they point to are volatile. */ typedef volatile UBYTE * const RegPtrB; /* for internal control registers */ typedef volatile UWORD * const RegPtrW; /* for internal control registers */ /* 68332 specific defines and pointers used in this file */ #define REG_BASE 0xfff000 /* Base address for internal registers */ RegPtrB r32_qpdrPB = (RegPtrB)(REG_BASE + 0xc15); /* QSPI default data Reg */ RegPtrB r32_qparPB = (RegPtrB)(REG_BASE + 0xc16); /* QSPI Pin Assignment Reg */ RegPtrB r32_qddrPB = (RegPtrB)(REG_BASE + 0xc17); /* QSPI Data Direction Reg */ /* Bits defines for *r32_qparPB & *r32_qddrPB */ #define TXD B7 /* transmit data */ #define PCS3 B6 /* peripheral chip select 3 */ #define PCS2 B5 /* peripheral chip select 2 */ #define PCS1 B4 /* peripheral chip select 1 */ #define PCS0 B3 /* peripheral chip select 0 */ #define SCK B2 /* serial clock */ #define MOSI B1 /* master out slave in */ #define MISO B0 /* master in slave out */ RegPtrW r32_qcr0PW = (RegPtrW)(REG_BASE + 0xc18); /* QSPI Control Register 0 */ #define MSTR B15 /* master/slave mode select */ #define WOMQ B14 /* wired-or mode for QSPI pins */ #define CPOL B9 /* clock polarity */ #define CPHA B8 /* clock phase */ RegPtrW r32_qcr1PW = (RegPtrW)(REG_BASE + 0xc1a); /* QSPI Control Register 1 */ #define SPE B15 /* QSPI enable */ RegPtrW r32_qcr2PW = (RegPtrW)(REG_BASE + 0xc1c); /* QSPI Control Register 2 */ #define SPIFIE B15 /* SPI finished interrupt enable */ #define WREN B14 /* wrap enable */ #define WRTO B13 /* wrap to */ #define NEWQP B0 /* new queue pointer */ #define ENDQP B8 /* ending queue pointer */ RegPtrB r32_qcr3PB = (RegPtrB)(REG_BASE + 0xc1e); /* QSPI Control Register 3 */ #define LOOPQ B2 /* QSPI loop mode */ #define HMIE B1 /* HALTA and MODF interrupt enable */ #define HALT B0 /* halt */ RegPtrB r32_qsrPB = (RegPtrB)(REG_BASE + 0xc1f); /* QSPI Status Register */ #define SPIF B7 /* QSPI finished flag */ #define MODF B6 /* mode fault flag */ #define HALTA B5 /* halt acknowledge flag */ #define CPTQP B0 /* completed queue pointer */ RegPtrW r32_qrxPW = (RegPtrW)(REG_BASE + 0xd00); /* QSPI receive RAM */ RegPtrW r32_qtxPW = (RegPtrW)(REG_BASE + 0xd20); /* QSPI transmit RAM */ RegPtrB r32_qccPB = (RegPtrB)(REG_BASE + 0xd40); /* QSPI control RAM */ #define CONT B7 /* continue */ #define BITSE B6 /* bits per transfer enable */ #define DT B5 /* delay after transfer */ #define DSCK B4 /* PCS to SCK delay */ #define PCS3C B3 /* peripheral chip select 3 control */ #define PCS2C B2 /* peripheral chip select 2 control */ #define PCS1C B1 /* peripheral chip select 1 control */ #define PCS0C B0 /* peripheral chip select 0 control */
/* C code for DS1202 3 wire interface. This file contains handlers for the
** MC68332 QSPI interface to the Dallas 1202 clock/calander time functions. */
#include <string.h> /* for memcmp def */
#include <time.h> /* for time_t and struct tm defs */
#include "qspi.h" /* MC68332 QSPI specific definitions */
/* Private declarations */
static UWORD qspi_lockW; /* Lock semiphore, 1 if locked, 0 if not */
static UBYTE bitrevB( UBYTE ); /* bit reverses the arg */
static UBYTE spif_validB; /* becomes valid after 1st xmit */
static UBYTE *qbufPB; /* pointer into QSPI transmit buffer */
/* qspi_initV -- Initial config of QSPI. Called at power-up. */
void qspi_initV( void )
{
/* Port Q pin assignments: use all but PCS3 */
*r32_qparPB = PCS2+PCS1+PCS0+MOSI+MISO; /* Pin Assign: use all but PCS3 */
/* Default Pin state: all high except MISO */
*r32_qpdrPB = TXD+PCS3+PCS2+PCS1+PCS0+SCK+MOSI;
/* Pin dir: all out except MISO */
*r32_qddrPB = TXD+PCS3+PCS2+PCS1+PCS0+SCK+MOSI;
qspi_lockW = 0; /* initialize the lock on QSPI */
/* QSPI must be further separately initialized by qcfg4fpgaV() or
** qcfg4clkV() for their separate and incompatible purposes. */
}
/* Function: qcfg4clkV. Description: configures the QSPI to talk to the clock
** chip. Arguments: none. Returns: void */
static void qcfg4clkV( void )
{
while( ( *r32_qsrPB & SPIF ) == 0 ) ; /* Wait until QSPI is finished */
*r32_qpdrPB = 0xff; /* Default Pin state: all high */
/* Master mode, no wired OR, 16 bit default, SCK inactive low, data change
** on falling edge, captured on rising edge, 1.05 MHz clock rate */
*r32_qcr0PW = MSTR + 0x008;
}
/* Function: tobinN
** Description: Converts BCD in lower byte in UWORD to int
** Arguments: UWORD containing BCD in lower byte
** Returns: integer equivilent to BCD value.
** Caveats: No check is done on validity of argument */
int tobinN( UWORD valW )
{
return( ( ( (valW >> 4) & 0x0f ) * 10 ) + ( valW & 0x0f ) );
}
/* Function: tobcdB
** Description: Converts byte argument to BCD
** Arguments: UBYTE value to be converted to BCD ( 0 - 99 )
** Returns: UBYTE BCD equivilent of argument
** Caveats: No check is done on validity of argument */
UBYTE tobcdB( UBYTE valB )
{
UBYTE tmpB;
tmpB = valB/10;
tmpB <<= 4;
return( tmpB += valB % 10 );
}
/* Function: qset_writeproV
** Description: Sets or clears the write protect bit in the DS1202
** Arguments: int: 0 to set write protect off, 1 to set it on
** Returns: void
** Caveats: Routine does not verify that the WP bit was properly set */
static void qset_writeproV( int on_offN )
{
while( ( *r32_qsrPB & SPIF ) == 0 ) ; /* Wait until QSPI is finished */
/* Set up the clock command and data starting at slot 6 */
/* Xfer 16 bits, PCS1 low, stop, Send command byte and WP byte */
*(r32_qccPB + 6) = CONT+BITSE+DT+DSCK+PCS2C+PCS0C;
/* Setup the transmit RAM. Need to encode & reverse the bits to be sent */
/* Bits are sent MSB first */
if( on_offN ) /* if wants write protect set */
*(r32_qtxPW+0) = 0x7101; /* Control byte 8e, set write protect */
else
*(r32_qtxPW+0) = 0x7100; /* Control byte 8e, clr write protect */
*r32_qsrPB = 0; /* Clear SPIF flag */
/* send the command and data to the clock */
*r32_qcr2PW = 0x0606; /* Start = slot 6, end = 6, no Wrap or loop */
/* Enable xmit, selects lead clock by 1.8 uSec, delay 8 Usec at end */
*r32_qcr1PW = SPE + 0x1f04;
while( ( *r32_qsrPB & SPIF ) == 0 ) ; /* Wait until QSPI is finished */
/* write protect should now be configured */
}
/* Function: set_clkW
** Description: Sets the clock from a time_t value.
** Arguments: pointer to struct tm
** Returns: void
** Caveats: This routine does not verify it's actions */
static void set_clkV( struct tm *timePH )
{
UWORD tmpW;
while ( lockW( &qspi_lockW ) ) ; /* wait here until we own the QSPI */
qcfg4clkV(); /* set up pin config for DS1202 */
/* first Unset write protect */
qset_writeproV( OFF );
/* Set up the clock data and commands. */
/* Xfer 8 bits, PCS1 low, continue, Send command byte */
*(r32_qccPB + 0) = CONT+DSCK+PCS2C+PCS0C;
/* Xfer 16 bits, PCS1 low, continue, Send seconds and minutes bytes */
*(r32_qccPB + 1) = CONT+BITSE+PCS2C+PCS0C;
/* Xfer 16 bits, PCS1 low, continue, Send hours and day of month */
*(r32_qccPB + 2) = CONT+BITSE+PCS2C+PCS0C;
/* Xfer 16 bits, PCS1 low, continue, send month and day of week */
*(r32_qccPB + 3) = CONT+BITSE+PCS2C+PCS0C;
/* Xfer 16, PCS1 low, delay, stop, send year and write protect */
*(r32_qccPB + 4) = DT+BITSE+PCS2C+PCS0C;
/* Setup the transmit RAM. Need to encode & reverse the bits to be sent */
*(r32_qtxPW+0) = 0x007d; /* Control byte 0xbe, clock burst write */
tmpW = bitrevB( tobcdB( (UBYTE)timePH->tm_sec ) ) << 8;
tmpW |= bitrevB( tobcdB( (UBYTE)timePH->tm_min ) );
*(r32_qtxPW+1) = tmpW; /* seconds and minutes bytes */
tmpW = bitrevB( tobcdB( (UBYTE)timePH->tm_hour ) ) << 8;
tmpW |= bitrevB( tobcdB( (UBYTE)timePH->tm_mday ) );
*(r32_qtxPW+2) = tmpW; /* hours and day of month */
tmpW = bitrevB( tobcdB( (UBYTE)timePH->tm_mon ) ) << 8;
tmpW |= bitrevB( tobcdB( (UBYTE)timePH->tm_wday + 1 ) );
*(r32_qtxPW+3) = tmpW; /* month and day of week */
tmpW = bitrevB( tobcdB( (UBYTE)timePH->tm_year ) ) << 8;
tmpW |= 0x0001; /* add in write protect bit */
*(r32_qtxPW+4) = tmpW; /* year and write protect */
*r32_qsrPB = 0; /* Clear SPIF flag */
/* send the data to the clock */
*r32_qcr2PW = 0x0400; /* Start = slot 0, end = 4, no Wrap or int. */
/* Enable xmit, selects 1.8 uSec lead, delay 8 Usec at end */
*r32_qcr1PW = SPE + 0x1f04;
while( ( *r32_qsrPB & SPIF ) == 0 ) ; /* Wait until QSPI is finished */
/* Clock should now be set */
qspi_lockW = 0; /* release the lock on QSPI */
}
/* Function: time
** Description: Gets the current time from the DS1202.
** Arguments: pointer to time_t or NULL
** Returns: ULONG time value */
time_t time( time_t *timePT )
{
struct tm mytimeH;
time_t timeL;
while ( lockW( &qspi_lockW ) ) ; /* wait here until we own the QSPI */
/* set up pin config */
qcfg4clkV();
/* Set up the clock data and commands */
/* Transfer 8 bits with PCS1 low (xmit), continue; xmits command byte */
*(r32_qccPB + 8) = CONT+DSCK+PCS2C+PCS0C; /* Also allow 1 usec setup */
/* Transfer 16 bits with PCS1&2 low (rcv), continue; get secs & mins */
*(r32_qccPB + 9) = CONT+BITSE+PCS0C;
/* Transfer 16 bits with PCS1&2 low (rcv), continue; get hrs and DoM */
*(r32_qccPB + 0xa) = CONT+BITSE+PCS0C;
/* Transfer 16 bits with PCS1&2 low (rcv), continue; get month & DoW */
*(r32_qccPB + 0xb) = CONT+BITSE+PCS0C;
/* Transfer 8 bits with PCS1&2 low (rcv), delay and stop; get year */
*(r32_qccPB + 0xc) = DT+PCS0C;
*(r32_qtxPW+8) = 0x00fd; /* Control byte, read clock */
/* set up the start and end */
*r32_qcr2PW = 0x0c08; /* Start = cmd 8, end = 0xc, no Wrap or int. */
/* Enable xmit, selects 1.8 uSec lead, delay 8 Usec at end */
*r32_qcr1PW = SPE + 0x1f04;
while( ( *r32_qsrPB & SPIF ) == 0 ) ; /* Wait until QSPI is finished */
/* OK, now convert clock/cal data to time_t */
mytimeH.tm_sec = tobinN( *(r32_qrxPW+9) ); /* get seconds */
mytimeH.tm_min = tobinN( *(r32_qrxPW+9) >> 8); /* get minutes */
mytimeH.tm_hour = tobinN( *(r32_qrxPW+0xa) ); /* get hours */
mytimeH.tm_mday = tobinN( *(r32_qrxPW+0xa) >> 8); /* get day of month */
mytimeH.tm_mon = tobinN( *(r32_qrxPW+0xb) - 1); /* get month */
mytimeH.tm_wday = tobinN( *(r32_qrxPW+0xb) >> 8); /* get day of week */
mytimeH.tm_year = tobinN( *(r32_qrxPW+0xc) ); /* get year */
qspi_lockW = 0; /* release the lock on QSPI */
timeL = mktime( &mytimeH ); /* convert struct to time_t */
if (timePT)
return( *timePT = (time_t)timeL );
else
return(timeL);
}
/* Function: bitrevB
** Description: Reverses the bits in the argument. This
** routine is used when sending data between the QSPI and the
** clock. QSPI expects MSB first, the clock expects lsb first.
** For data RAM in the clock we just let the data get stored backwards.
** Arguments: UBYTE
** Returns: UBYTE contains mirror reflection of argument */
static UBYTE bitrevB( register UBYTE dataB ) /* bit reverses the arg */
{
register UWORD rdataW; /* These reg type better be used! */
rdataW = 0x0100; /* done when this bit has shifted out */
while( rdataW & 0xff00 ) /* While the bit is still in there */
{
rdataW <<= 1; /* Move outgoing left */
rdataW += dataB & 1; /* Transfer incoming to outgoing */
dataB >>= 1; /* Move incoming right */
}
return (UBYTE) rdataW;
}
/* Function: set_clkW
** Description: Sets the clock to the time specified by the argument
** Arguments: time_t time (seconds since 00:00 1 Jan 1970)
** Returns: int 0 on success, else verification failure */
int set_clkN( time_t stimeL )
{
time_t checktimeL;
set_clkV( localtime( &stimeL ) ); /* set the clock */
checktimeL = time( NULL ); /* Now go read it back*/
if( checktimeL != stimeL ) /* if not same as what we requested */
return( 1 ); /* Failed to set */
else
return( 0 ); /* success */
}
/* DS2404 one wire interface code. This file contains the code reading the
** serial number from the clock chip. The routines contain hardcoded delays
** based on CPU execution speed I have weighted the delays to the heavy side
** so that if the CPU is converted to 20Mhz, things should still work here. The
** timings and algorithms will make more sense if you've read the Dallas book.
*/
#include "qspi.h" /* for generic typedefs and defines only */
/* local defines */
#define SNCLKBIT 0x08 /* This bit corresponds to the I/O pin used for the
** single wire interface to the clock chip. The name
** stands for Serial Number Clock Bit :-) */
#define Delay( time ) for(ii = time; ii; ii--) /* Simple uSec delay */
#define SetInput *r32_peddrPB &= ~SNCLKBIT /* make pin an input */
#define SetOutput *r32_peddrPB |= SNCLKBIT /* make pin an output */
#define SetHigh *r32_pedrPB |= SNCLKBIT /* Set output high */
#define SetLow *r32_pedrPB &= ~SNCLKBIT /* Set output low */
/* globals */
UBYTE serial_numberAB[6]; /* Unique ID from DS2404 */
UWORD id_statusW; /* zero if ID is valid */
/* private variables */
static UBYTE my_crcB;
/* function pre_declarations */
static void send_oneV( void );
static void send_zeroV( void );
static void send_byteV( UBYTE );
static UBYTE get_byteB( void );
static UWORD wait_highW( void );
static UWORD wait_lowW( void );
/* Functions */
/* Function: get_idW
** Description: Reads the Serial number from the clock chip using the 1 wire
** interface protocol. If anything goes wrong with the read, the
** serial number is set to all zeros. There must be no interrupts
** during the operation of this routine.
** arguments: None
** returns: UWORD 0 on success, else failure */
UWORD get_idW( void )
{
register UWORD ii;
UWORD resultW;
int i;
UBYTE tmpB, fam_codeB, crcB;
for( i = 0; i < 6; i++ )
serial_numberAB[i] = 0; /* Initialize serial number */
SetOutput; /* Do the special initialization sequence */
SetHigh; /* Make sure line is high for a little while */
SetHigh; /* delay several uSec */
SetLow; /* Ok, start reset pulse here */
Delay( 326 ); /* delay 800 uSec */
SetInput; /* End of driving reset pulse */
if( wait_highW() ) return 1; /* wait for line to go back high */
if( wait_lowW() ) return 2; /* wait for clock to drive line low */
if( wait_highW() ) return 3; /* wait for clock to release line back high */
Delay( 326 ); /* delay 800 uSec */
my_crcB = 0; /* Start with a fresh CRC */
SetHigh; /* Make sure line is high */
SetOutput;
send_byteV( 0x0f ); /* Send Read ROM command */
fam_codeB = get_byteB(); /* Get family code, (Not used) */
for( i = 0; i < 6; i++ ) /* Get serial number */
serial_numberAB[i] = get_byteB();
tmpB = my_crcB; /* save computed CRC */
crcB = get_byteB(); /* Get expected CRC */
if( tmpB != crcB )
resultW = 4;
else
resultW = 0;
Delay( 100 ); /* Delay a bit before issuing the reset pulse */
SetLow; /* reset the chip so we can use 3 wire I/F */
SetOutput;
Delay( 326 );
SetInput;
Delay( 326 ); /* delay 800 uSec */
return resultW;
}
/* Function: send_byteV
** Description: sends one byte to the clock chip using the 1-wire
** interface protocol. Bits are sent LSB first
** arguments: UBYTE value to be sent
** returns: Void */
static void send_byteV( UBYTE valB )
{
int i;
for( i = 0; i < 8; i++ )
{
if( valB & 1 ) /* send the bit */
send_oneV();
else
send_zeroV();
valB >>= 1; /* shift to next bit */
}
}
/* Function: get_byteB
** Description: sends one byte to the clock chip using the 1 wire
** interface protocol. Bits are sent LSB first. CRC using
** X**8 + X**5 + X**4 + 1 is calculated over bits. Global
** my_crcB is updated.
** arguments: None
** returns: UBYTE value received. */
static UBYTE get_byteB( void )
{
register UWORD ii;
int i;
UBYTE valB, tmpB;
for( i = 0; i < 8; i++ )
{
SetLow; /* Send start of signal pulse */
SetOutput;
SetLow; /* dely to give chip a chance to see it */
SetInput; /* Turn pin into input */
if( *r32_pedrPB & SNCLKBIT ) /* read within 15 uSec of going low */
tmpB = 1; /* if bit is high */
else
tmpB = 0;
valB >>= 1; /* bits come in LSB first, get ready for nxt */
if( tmpB ) valB |= 0x80; /* add in one bit */
tmpB = (tmpB ^ my_crcB) & 1; /* tmpB = XOR of input and CRC bit 0 */
my_crcB >>= 1; /* Always do shift */
if ( tmpB ) /* if result of XOR was 1 */
my_crcB ^= 0x8c; /* add in new bits */
wait_highW(); /* make sure chip has released line */
Delay( 24 ); /* wait a while to meet clock's time slot reqmnts */
} /* end of for each bit in byte */
return valB;
}
/* Function: send_oneV
** Description: sends a one bit to the clock chip using the 1 wire
** interface protocol. The function waits the appropriate time
** after transmitting so the inter-bit timing reqirements are met.
** arguments: UBYTE value to be sent
** returns: Void */
static void send_oneV( void )
{
register UWORD ii;
SetLow; /* Send "start of bit pulse" */
SetLow; /* Keep it there long enough for clock chip */
SetHigh; /* deassert pin because we're sending a '1' */
Delay( 29 ); /* Delay 100 uSec */
}
/* Function: send_zeroV
** Description: sends a zero bit to the clock chip using the 1 wire
** interface protocol. The function waits the appropriate time
** after transmitting so the inter-bit timing reqirements are met.
** arguments: UBYTE value to be sent
** returns: Void */
static void send_zeroV( void )
{
register UWORD ii;
SetLow; /* Send "start of bit pulse" */
Delay( 29 ); /* Keep pin low for 100 uSec so chip reads a zero */
SetHigh; /* deassert pin */
}
/* Function: wait_highW
** Description: waits for data pin to go high. Will time out after a while.
** Assumes that we're in input mode.
** arguments: none
** returns: UWORD 0 if got signal in time, 1 if timed out */
static UWORD wait_highW( void )
{
register UWORD i;
for( i = 100; i; i-- ) /* This timeout should be excessive */
{ /* for any normal situation */
if( *r32_pedrPB & SNCLKBIT ) /* if bit is high */
return(0); /* return success */
}
return(1); /* return failure if timed out */
}
/* Function: wait_lowW
** Description: waits for data pin to go low. Will time out after a while
** Assumes that we're in input mode.
** arguments: none
** returns: UWORD 0 if got signal in time, 1 if timed out */
static UWORD wait_lowW( void )
{
register UWORD i;
for( i = 100; i; i-- ) /* This timeout should be excessive */
{ /* for any normal situation */
if( ! (*r32_pedrPB & SNCLKBIT) ) /* if bit is low */
return(0); /* return success */
}
return(1); /* return failure if timed out */
}
/* DS2404 Three wire interface code. This file contains handlers for the
** MC68332 QSPI interface to the Dallas DS2404 clock/calander time functions.*/
#include <string.h> /* for memcmp def */
#include <time.h> /* for time_t and struct tm defs */
#include "qspi.h" /* For MC68332 QSPI specific defs */
/* The following functions were shown in listing 1 */
extern UBYTE bitrevB( UBYTE ); /* bit reverses the arg */
extern void qcfg4clkV( void ); /* Config QSPI for clock work */
/* Private declarations */
extern UWORD qspi_lockW; /* Lock semiphore, 1 if locked, 0 if not */
static UBYTE spif_validB; /* becomes valid after 1st xmit */
static UBYTE *qbufPB; /* pointer into QSPI transmit buffer */
/* Function: set_clkW
** Description: Sets the clock from a time value. Note: the Dallas 2404
** requires the clock to be low when reset is deasserted and high
** when reset is asserted (writes only). Therefore, those
** sequences below that write, contain some tricks with default
** values of the chip selects and SCLK outputs.
** Arguments: time_t time value
** Returns: UWORD 0 on success, 1 if readback failure */
UWORD set_clkW( time_t stimeL )
{
UBYTE *qxbufPB;
time_t timeL;
while ( lockW( &qspi_lockW ) ) ; /* wait here until we own the QSPI */
qcfg4clkV(); /* set up pin config */
/* Set up the clock data and commands. */
timeL = stimeL; /* For debug loop (preserves timeL) */
/* Xfer 8 bits, PCS1 low, continue, Send write scratchpad cmd */
*(r32_qccPB + 0) = CONT+DSCK+PCS2C+PCS0C;
/* Xfer 16 bits, PCS1 low, continue, Send TA1 and TA2 */
*(r32_qccPB + 1) = CONT+BITSE+PCS2C+PCS0C;
/* Xfer 16 bits, PCS1 low, continue, Send Ctrl reg and second fractions */
*(r32_qccPB + 2) = CONT+BITSE+PCS2C+PCS0C;
/* Xfer 16 bits, PCS1 low, continue, send low word seconds */
*(r32_qccPB + 3) = CONT+BITSE+PCS2C+PCS0C;
/* Xfer 16, PCS1 low, delay, stop, send high word seconds */
*(r32_qccPB + 4) = DT+BITSE+PCS2C+PCS0C;
/* Setup the transmit RAM. Need to unravel & reverse the bits to be sent */
*(r32_qtxPW+0) = 0x00f0; /* Control byte, write scratchpad */
*(r32_qtxPW+1) = 0x8040; /* address 201 */
*(r32_qtxPW+2) = 0x0a00; /* CTRL REG=enable osc, no interval, 0 fracs */
qxbufPB = (UBYTE *) (r32_qtxPW+3); /* get byte ptr to xmit buf */
/* Now move the time into transmit RAM. */
*qxbufPB++ = bitrevB( (UBYTE)timeL );
timeL >>= 8;
*qxbufPB++ = bitrevB( (UBYTE)timeL );
timeL >>= 8;
*qxbufPB++ = bitrevB( (UBYTE)timeL );
timeL >>= 8;
*qxbufPB = bitrevB( (UBYTE)timeL );
*r32_qsrPB = 0; /* Clear SPIF flag */
/* prepare to send the command and data to the clock */
*r32_qcr2PW = 0x0400; /* Start = cmd 0, end = 4, no Wrap or int. */
/* We disable interrupts because we must change the default pin states
** before the QSPI finishes it's transaction. We only have a few tens
** of microseconds to work with. */
asm(" move.w #$2700,sr"); /* Disable interrupts */
/* Enable xmit, selects 1.4 uSec lead, delay 8 Usec at end */
*r32_qcr1PW = SPE + 0x1404;
*r32_qpdrPB = 0xef; /* immediately set default CLK high, PCS1 low */
asm(" move.w #$2000,sr"); /* Re-enable interrupts */
while( ( *r32_qsrPB & SPIF ) == 0 ) ; /* Wait until QSPI is finished */
*r32_qpdrPB = 0xff; /* set everything High */
*r32_qpdrPB = 0xfb; /* set CLK low for next xmit */
*r32_qsrPB = 0; /* Clear SPIF flag */
/* OK, let's verify the scratchpad */
/* Xfer 8 bits, PCS1 low, continue, send read scratchpad command */
*(r32_qccPB + 0) = CONT+DSCK+PCS2C+PCS0C;
/* Xfer 16 bits, PCS1+PCS2 low, continue, Read TA1 & TA2 */
*(r32_qccPB + 1) = CONT+BITSE+PCS0C;
/* Xfer 8 bits, PCS1+PCS2 low, continue, Read ES */
*(r32_qccPB + 2) = CONT+PCS0C;
/* Xfer 16 bits, PCS1+PCS2 low, continue, Read CTRL REG & fraction secs */
*(r32_qccPB + 3) = CONT+BITSE+PCS0C;
/* Xfer 16 bits, PCS1+PCS2 low, continue, Read low word of seconds */
*(r32_qccPB + 4) = CONT+BITSE+PCS0C;
/* Xfer 16, PCS1+PCS2 low, delay, stop, Read high word of seconds */
*(r32_qccPB + 5) = DT+BITSE+PCS0C;
*r32_qcr2PW = 0x0500; /* Start = slot 0, end = 5, no Wrap or int. */
*(r32_qtxPW+0) = 0x0055; /* Control byte 0xaa, read scratchpad */
/* Enable xmit, selects 1.4 uSec lead, delay 8 Usec at end */
*r32_qcr1PW = SPE + 0x1404;
while( ( *r32_qsrPB & SPIF ) == 0 ) ; /* Wait until QSPI is finished */
/* verify that what we received is what we sent */
if( memcmp( r32_qtxPW + 2, r32_qrxPW + 3, 6 ) )
{
qspi_lockW = 0; /* release the lock on QSPI */
return( 1 ); /* Bomb out if not */
}
/* OK, time data is OK in scratchpad RAM. Need to copy to time regs
** DO this by sending copy scratchpad command and security keys. */
*r32_qsrPB = 0; /* Clear SPIF flag */
/* Xfer 16 bits, PCS1 low, continue, Send TA1 and TA2 */
*(r32_qccPB + 1) = CONT+BITSE+PCS2C+PCS0C;
/* Xfer 8, PCS1 low, delay, stop. Send ES, only sending 4 bytes total */
*(r32_qccPB + 2) = DT+PCS2C+PCS0C;
*(r32_qtxPW+0) = 0x00aa; /* Control byte, copy scratchpad */
*(r32_qtxPW+1) = 0x8040; /* address 201 */
*(r32_qtxPW+2) = 0x00e4; /* E/S byte showing load end address of 7.
** We actually only write to 201 - 206 but the
** funky clock deal cause one bit of 207 to be
** written. PF flag set too. */
/* send the data to the clock */
*r32_qcr2PW = 0x0200; /* Start = cmd 0, end = 2, no Wrap or int. */
/* We disable interrupts because we must change the default pin states
** before the QSPI finishes it's transaction. We only have a few tens
** of microseconds to work with. */
asm(" move.w #$2700,sr"); /* Disable interrupts */
*r32_qcr1PW = SPE + 0x1404; /* Enable transmission, delay 8 Usec at end */
*r32_qpdrPB = 0xef; /* set default CLK high, PCS1 low */
asm(" move.w #$2000,sr"); /* Enable interrupts */
while( ( *r32_qsrPB & SPIF ) == 0 ) ; /* Wait until QSPI is finished */
*r32_qpdrPB = 0xff; /* set everything High */
*r32_qpdrPB = 0xfb; /* set CLK low */
/* Clock should now be set */
qspi_lockW = 0; /* release the lock on QSPI */
return 0;
}
/* Function: time
** Description: Gets the current time from the clock. Scratchpad is not used.
** Time is read directly from the registers.
** Arguments: none
** Returns: time_t time value */
time_t time( time_t *timePT )
{
int i;
UBYTE *qrbufPB;
time_t timeL;
while ( lockW( &qspi_lockW ) ) ; /* wait here until we own the QSPI */
/* set up pin config */
qcfg4clkV();
/* Set up the clock data and commands */
/* Transfer 8 bits with PCS1 low (xmit), continue; xmits command byte */
*(r32_qccPB + 8) = CONT+DSCK+PCS2C+PCS0C; /* Also allow 1 usec setup */
/* Xfer 16 bits, PCS1 low (xmit), continue; xmits address */
*(r32_qccPB + 9) = CONT+BITSE+PCS2C+PCS0C;
/* Xfer 16 bits, PCS2&1 low (recv), continue; receives low word of time */
*(r32_qccPB + 0xa) = CONT+BITSE+PCS0C;
/* Xfer 16 bits, PCS2&1 low (recv), delay, stop; rcvs high word of time */
*(r32_qccPB + 0xb) = DT+BITSE+PCS0C;
/* Tx Data RAM gets command byte */
*(r32_qtxPW + 8) = 0x000f; /* Command byte 0xf0, read memory */
*(r32_qtxPW + 9) = 0xc040; /* memory address is 0x0203 */
*r32_qsrPB = 0; /* Clear SPIF flag */
/* send the data to the clock */
*r32_qcr2PW = 0x0b08; /* Start = cmd 8, end = 0xb, no Wrap, int. */
*r32_qcr1PW = SPE + 0x1404; /* Enable transmission, delay 8 Usec at end */
while( ( *r32_qsrPB & SPIF ) == 0 ) ; /* Wait until QSPI is finished */
/* Now unscramble the receive RAM. Need to reverse the bits and the byte
** order */
qrbufPB = (UBYTE *) (r32_qrxPW+0xc); /* get ptr just past recv buf */
for( i = 0; i < 4; i++ )
{
timeL <<= 8;
timeL += bitrevB( *(--qrbufPB) );
}
qspi_lockW = 0; /* release the lock on QSPI */
if (timePT)
return( *timePT = (time_t)timeL );
else
return(timeL);
}
Copyright © 1995, Dr. Dobb's Journal