. . . .

Firmware Examples

 

updated:2018.09.07

Prev   Next   Site Map   Home  
Text Size
Please reload page for style change

To clarify core functionality I have removed test, development, and error recovery code and moved block comments from these examples into page text.

Also see



USB-I2C/SPI BRIDGE

ARM+C+USB+I2C+SPI multi-level state machine

DESCRIPTION

This is a portion of firmware that I designed and implemented for an NXP LPC1342 (ARM M3) controller to function as a device development and demonstration platform. It communicates with a host computer via USB and with various embedded devices via I2C or SPI. In its simplest usage model, it is a USB to I2C/SPI bridge. This functionality is implemented in the context of a simple real-time operating system that I designed for broader usage. During development of a product, for example my differential capacitive pointing device, this system can provide real-time operations, for example time-stamped data acquisition, that the host cannot perform. It can also serve as a stand-alone prototype or pre-production demo for a device to be implemented as a single IC or it can serve as an actual USB-interfaced product.

The operating system comprises a simple round-robin task dispatcher and derives its real-time performance from real interrupts, DMA, and coprocessors associated with each task. The tasks are state machines with transitions on underlying events. I2C communication is implemented by a two-level state machine. The upper level transitions on the completion of a transaction, while the lower performs transactions. SPI is only a single level because spin-locking to perform (or fail) a transaction consumes less CPU bandwidth than interrupting on completion.

The hub module is the bridge's communication nexus, where USB, I2C, SPI and task dispatcher exchange data, trigger state transitions in each other, and monitor each other's health. It contains cooperating functions that are invoked by different methods: periodically by the task dispatcher; on USB interrupt, signaling completion of a transaction with the host or certain faults; on low-level system or subordinate state machine events. The different invocation methods provide mutual hung process recovery. For example, the USB interrupt-dispatched service watches for and corrects a stalled task dispatcher. The task dispatcher does the same thing for USB, detecting, for example, an endpoint input buffer that has not been freed and is, therefore, preventing any further host input.

The function targetCmdI2cThrd implements the I2C upper state machine, which effects I2C multiple transaction sequences, including the USB-I2C bridge. The i2c module contains the lower level I2C state machine functions. The lower level state machine effects a single I2C bus transaction.

HUB

hub.c

USHORT  pgmVer = 300; // Be sure to update this before 
                      // programming field units.
UCHAR   devLink;
UCHAR   tchDev;
UCHAR   controller = 1;

enum
{
  TEST_NONE, TEST_I2C = 1, TEST_SPI = 2
} testLink = TEST_I2C;

// Target Commands (OUT reports) and Input Messages (IN reports)
#define HID_OUT_REPORT_SIZE 16 // Benign dup ok in controller.
#define HID_IN_REPORT_SIZE 22  

enum
{
  AUICMD_NONE,
  AUICMD_CONTROL,   // Controller debug command. e.g. 
                    // CONTROL_RESET, CONTROL_BREAK, etc.
  AUICMD_VERDEV,    // Query controller information
  AUICMD_WRITEREG,
  AUICMD_READREG,
  AUICMD_READMULTI,
  AUICMD_READTOUCH, // READMULTI and READTOUCH share structures.
  AUICMD_WATCH,     // Continuously read multiple registers until 
                    // told to stop (by cnt = 0)
};

enum
{
  DEVSTAT_OK,
  DEVSTAT_CMDOVERRUN,
};

#pragma pack(1)
#ifdef __cplusplus
typedef union
{
  USHORT us; 
  struct
  {
    UCHAR h; 
    UCHAR l;
  };
} Dev16;
#else
typedef union
{
  USHORT us;
  struct
  {
    UCHAR h;
    UCHAR l;
  } uc;
} Dev16;
#endif

// OUT report structures
typedef struct
{
  UCHAR cmd;
  UCHAR op;
} CmdControl;

typedef struct
{
  UCHAR cmd;
  UCHAR p0;
  Dev16 addr;
  Dev16 dat;
} CmdWriteReg;

typedef struct
{
  UCHAR cmd;
  UCHAR p0;
  Dev16 addr;
} CmdReadReg;

typedef struct
{
  UCHAR cmd;
  UCHAR cnt;
  UCHAR addr[1];
} CmdReadMulti;

typedef struct
{
  UCHAR cmd;
  UCHAR cnt;
  UCHAR addr[1];
} CmdWatch; // Currently identical to CmdReadMulti.
#define MAX_SIZE_CMDWATCH 11

typedef union
{
  UCHAR       report[ HID_OUT_REPORT_SIZE ];
  CmdControl  control;
  CmdWriteReg writeReg;
  CmdReadReg  readReg;
  CmdReadMulti readMulti; // Use for AUICMD_READMULTI, AUICMD_READTOUCH
  CmdWatch    watch;
} DevCmdMsg;

// IN report structures
typedef struct
{
  UCHAR   link;
  UCHAR   tchDev;
  USHORT  pgmVer;
  UCHAR   controller;
} VerDev;

typedef struct
{
  UCHAR   msgType; // Same as command, i.e. AUICMD_VERDEV, AUICMD_READREG, 
    // etc. This is sparse but matching the command may simplify host.
  UCHAR   status;
  union
  {
  //UCHAR report[ 20 ]; // HID_IN_REPORT_SIZE - 2 ];
    USHORT dat[ ( HID_IN_REPORT_SIZE - 2 ) / 2 ];
    VerDev dev;
  };
} DevHidIn;
#pragma pack()

enum
{
  LINK_UNKNOWN, LINK_I2C, LINK_SPI
}; // DevInReport.link.

enum
{ // CmdControl.cmd
  CONTROL_RESET,
  CONTROL_BREAK,
  CONTROL_DISCONNECT,
  CONTROL_PRE_ADAPTERCHANGE,
  CONTROL_POST_ADAPTERCHANGE
};

DevCmdMsg  cmdMsg;
DevHidIn   inReport;

//I2C_DeviceAddress = 0x2C << 1;

UCHAR regAddr[2] = { 0, 0};

UCHAR  watchCmdBuf[ MAX_SIZE_CMDWATCH ];
CmdWatch* const watchCmd = (CmdWatch*)watchCmdBuf;
T_U32  watchSampTime; // Based on 10msec tick

UCHAR breakDummy;
#define BREAKHERE breakDummy = 1;

UCHAR  transCnt;
enum
{
  STATE_IDLE,
  STATE_WRITEREG_TX, 
    // Waiting for I2C write reg address plus data to target to finish.
  STATE_READREG_TX, 
    // Waiting for I2C write reg address to target to finish.
  STATE_READREG_RX, 
    // Waiting for I2C read reg data from target to finish.
  STATE_READMULTI_TX, 
    // Waiting for I2C write reg address to target to finish.
  STATE_READMULTI_RX,
    // Waiting for I2C read reg data from target to finish.
}   i2cThrdState = STATE_IDLE;

#define CMD_EP_INT EP2_INT
BOOL   doingWatchCycle;
BOOL   killWatch;
T_U32  stat;
BOOL   cmdWaiting = FALSE;
T_U32  cmdStallCount = 0;

void   noTargetThrd( void ) { };

BOOL sampleNow( void )
{
#if 1
  return watchCmd->cmd != AUICMD_NONE && watchCmd->cnt != 0; 
                                    // msTime - watchSampTime > 5;
#else
  u32 dif;
  dif = msTime - watchSampTime;
  if( dif > 0x80000000 )
    dif = -dif;
  return readMultiCmd->cnt != 0 && dif >= 2;
#endif
}

void reportTch( void )
{
  inReport.dat[ watchCmd->cnt ] = watchSampTime = msTime;
  usbWrLep( USB_LEP_HID_IN, (UCHAR*)&inReport, HID_IN_REPORT_SIZE );
}

void targetCmdI2cThrd( void )
{
  if( killWatch )
  {
    i2cInit();
    i2cThrdState = STATE_IDLE;
    transCnt = 0;
    watchCmd->cmd = AUICMD_NONE;
    watchCmd->cnt = 0;
    killWatch = FALSE;
    return;
  }

  if( i2cState == I2C_IDLE ) // Only if last I2C transaction is complete.
  {
    if( doingWatchCycle && cmdMsg.report[0] == AUICMD_READREG )
      breakHere();
    if( i2cThrdState == STATE_IDLE )
    {
      if( sampleNow() )
      {
        transCnt = 0;
        doingWatchCycle = TRUE;
        goto readNextReg;
      }
      switch( cmdMsg.report[0] )
      {
      case AUICMD_WRITEREG:
        i2cTxRx( &cmdMsg.writeReg.addr.uc.h, 4, I2C_TX ); 
                                // Transmit reg address + data to target.
        i2cThrdState = STATE_WRITEREG_TX;
        return;

      case AUICMD_READREG:
        startReadReg:
        inReport.msgType = AUICMD_READREG;
        i2cTxRx( &cmdMsg.readReg.addr.uc.h, 2, I2C_TX );  
            // Transmit reg addr to target.
        i2cThrdState = STATE_READREG_TX;
        return;
      }
    }
    switch( i2cThrdState )
    {
    case STATE_READMULTI_RX:
      if( cmdMsg.report[0] == AUICMD_READREG )
      {
        doingWatchCycle = FALSE;
        goto startReadReg;
      }
      if( ++transCnt < watchCmd->cnt )
      {
        readNextReg:
        regAddr[1] = watchCmd->addr[ transCnt ];
        i2cTxRx( regAddr, 2, I2C_TX );
        i2cThrdState = STATE_READMULTI_TX;
        break;
      }
      // else transCnt = cnt, i.e. done.
      inReport.msgType = watchCmd->cmd;
      if( watchCmd->cmd != AUICMD_READMULTI ) 
        // AUICMD_READTOUCH and AUICMD_WATCH
        inReport.dat[ watchCmd->cnt ] = watchSampTime = msTime;
      if( watchCmd->cmd != AUICMD_WATCH )
        watchCmd->cmd = AUICMD_NONE;
      doingWatchCycle = FALSE;
      // AUICMD_READMULTI. Fall through to post IN report.

    case STATE_READREG_RX: // Waiting for I2C read reg data from target.
// ........ HID_IN_REPORT ........
      usbWrLep( USB_LEP_HID_IN, (UCHAR*)&inReport, HID_IN_REPORT_SIZE );
      goto endCmd;

    case STATE_READMULTI_TX: 
    // Waiting for I2C write reg address to target to finish.
      i2cTxRx( (UCHAR*)( inReport.dat + transCnt ), 2, I2C_RX );
      i2cThrdState = STATE_READMULTI_RX;
      break;

    case STATE_READREG_TX: 
      i2cTxRx( (UCHAR*)&inReport.dat, 2, I2C_RX );
      i2cThrdState = STATE_READREG_RX;
    default:
      break;

    case STATE_WRITEREG_TX: 
    // Waiting for I2C write to target to finish.
      goto endCmd;
    }
  }
  else if( I2C_FAIL( i2cState ) )
  {
    i2cInit();
    usbClr();
    endCmd:
    cmdMsg.report[0] = AUICMD_NONE;
    inReport.status = DEVSTAT_OK;
    i2cThrdState = STATE_IDLE;
  }
}
void targetCmdSpiThrd( void )
{
  switch( cmdMsg.report[0] )
  {
  case AUICMD_WRITEREG:
    spiWriteLdsReg( cmdMsg.writeReg.addr.us, cmdMsg.writeReg.dat.us );
    cmdMsg.report[0] = AUICMD_NONE;
    break;

  case AUICMD_READREG:
    inReport.dat[0] = spiReadLdsReg( cmdMsg.readReg.addr.us );
    break;

  case AUICMD_READMULTI:
    spiReadMultiLdsRegs( inReport.dat, cmdMsg.readMulti.addr,
      cmdMsg.readMulti.cnt );
    break;

  case AUICMD_READTOUCH:
    spiReadMultiLdsRegs( inReport.dat, cmdMsg.readMulti.addr,
      cmdMsg.readMulti.cnt );
    inReport.dat[ cmdMsg.readMulti.cnt ] = msTime;
    break;

  default:
    if( sampleNow() )
    {
      spiReadMultiLdsRegs( inReport.dat, watchCmd->addr, 
        watchCmd->cnt );
      reportTch();
    }
    return;
  }
  usbWrLep( USB_LEP_HID_IN, (UCHAR*)&inReport, 
    HID_IN_REPORT_SIZE ); // HID IN REPORT
  cmdMsg.report[0] = AUICMD_NONE;
}

This is the system's communication nexus. This interfaces host USB communication with an application-specific device (target) via I2C or SPI. This does not initiate operating modes but only responds to host commands. In the simplest case, writing a value to one target register, the host's HID OUT report is simply relayed to the target. For most other commands, a temporary or semi-permanent mode is entered in order to complete the command. For example, reading a target register requires first relaying the command to the target, followed by waiting for and receiving the target response, and finally relaying the response to the host.

The targetUsb function, which is invoked by the USB ISR, parses host OUT reports to determine how to respond to the command. In most cases, if we are not already communicating with the target (via I2C) the command is copied from the RX (OUT endpoint memory) buffer to cmdMsg. If we are already talking to the target, the host command may simply be discarded or saved for delayed execution or an error IN report might be sent to the host. In some cases, targetUsb may set up other conditions for target communication but in no case does it communicate directly with the target.

Communicating with the target is the responsibility of targetCmdThread, which is invoked periodically by the task dispatch loop. It effects a state machine, predicated on a combination of the command function and I2C target communication phases. Most state transitions occur on the I2C idle condition, indicating that the current transaction has completed. If appropriate to the command, targetCmdThread writes into the IN endpoint register, effectively transmitting to the host. If no operation is currently in progress and cmdMsg[0] is AUICMD_NONE then targetCmdThread does nothing. When targetCmdThread is next invoked after targetUsb has copied a host command into cmdMsg, it begins processing that command. The main loop may invoke targetCmdThread many times before the command is completed, at which time, cmdMsg is changed back to AUICMD_NONE, telling targetUsb that the target interface is available for another command.

Most commands persist for a finite period. AUICMD_WATCH is different. It tells targetCmdThread to periodically sample a set of target registers forever until the host tells it to stop.

This ARM-based system communicates with a corresponding Windows-based host. The Windows program is written in C++ while the ARM program is C. The two programs share a common definition of application-level protocol (see AUICMD_READREG et.al.) but with a consistent difference in that Windows automatically puts the report number into the first byte of the receiving buffer for all HID reports. This and a few minor syntax differences are handled by conditional definitions, enabling identical source code.

Read Touch Command

At its core this is the same thing as read multiple channels. It adds a time stamp to the multi-channel read but that could easily be added to the basic multi-channel read. The reason for making it a distinct command is that we may want to do additional processing, for example, limiting the read rate to no faster than channels are updated. However, even if we are reading multiple channels, updating of any one of them affects the position. With 1024 decimation, any channel may update in 2 msec, which would be the desired read rate. With USB2.0 full speed the fastest interrupt rate is 1 msec theoretically and 2 msec in reality, so a speed limiter would serve no purpose. We might want to reply only when at least one channel's value has changed from the previous sample but this may nearly always be true.

Read Multi, Read Touch, Watch Commands

All of these have read multi at their core. For SPI, this is one uninterrupted command but for I2C it is executed by a dispatched state machine. To reduce redundancy in the I2C version, the read multi process is shared by all three commands. For both I2C and SPI, the watch command must be stored separatedly from the general command store cmdMsg because the host must be able to send other commands while watch is running. For I2C to efficiently share the read multi core, the read multi, read touch, and watch command must present a common structure to the code. It is not possible to to this by making each command message a derived structure of this base because the shared portion contains an array of register addresses, which, because it is extensible, must be located at the end of the structure rather than the beginning.

One solution would be to separate the extensible array (and it element count) from the rest of the command, storing other elements (in particular the command ID itself) separately. However, this affords no benefit over simply copying the same information into an instance of the largest command structure, which is (potentially) watch. Therefore, the watch command is used for all commands. For both I2C and SPI, the watch command from the host is simply copied to an instance of the watch structure. For I2C read multi and read touch are piece- wise copied into this same structure. For SPI we can either do the same thing as I2C with read multi and read touch or we can use the general command store, since the code is not shared. Shared code should determine the type of command before accessing elements that don't exist for all.

sampleNow function

To rapidly report touch position changes without reporting excessively, we want to report any change in any channel. The number of channels in the group is irrelevant. With 1024 decimation, each channel converts in 2 msec. Testing shows that a 2 msec sample rate is indistinguishable from 1 msec but 3 msec is noticeably less responsive. To minimize the influence of sampling time on algorithm development, 1 msec is preferred.

reportTch function

To report unchanging samples would seem to be unnecessary or even detrimental, as an unchanged sample could delay reporting a subsequent changed (and presumably more important) sample. Paradoxically, not reporting unchanged samples produces a less-responsive position report because this prevents the sample filter from properly updating. For example, if the touch remains in roughly one place, there may be many identical samples, which would quickly update the filter to that position.

To append the time stamp we need to consider byte order. Since NXP2142 is little-endian, like the Intel CPU in Windows, the host program doesn't need to swap bytes, as it does with data to/from the LDS6xxx chip (we just pass these through here).

targetCmdI2cThrd function

This implements target communication. Even the simplest case of writing a value to one target register requires a state machine with delay for the transaction to complete in order to avoid collision. More complex operations require additional states. This function acts as an independent thread but, because the OS doesn't support threads, is invoked periodically by a task loop. This function does not process USB OUT reports. The targetUsb function, invoked by the USB ISR, does that job.

Two state indicators, i2cState and i2cThrdState, control the state machine. If state = STATE_IDLE then no transactions are in progress and a new one, if requested by cmdMsg[0] != AUICMD_NONE, may be started. If state != STATE_IDLE, a transaction is in progress. All transactions comprise several states, transistioning on i2cState == I2C_IDLE, which is set by the I2C interrupt. I2C_IDLE only indicates that there is no transaction in progress. i2cState indicates an error, the operation in progress is aborted. Otherwise it continues according to the hard-wired transition function. The I2C interrupt can only tell done or failed.

In case STATE_READMULTI_RX the state machine is waiting for the completion of multiple target register reads as part of processing the AUICMD_READMULTI command. The purpose of the predicate if( cmdMsg.report[0] == AUICMD_READREG ) here is to abort a watch cycle in progress if the host sends an individual read register command at this time. We can't abort in the middle of a two-phase read but STATE_READMULTI_RX occurs after completing the second phase of a read. This point is the soonest that we can abort and it also aborts even after the last register of the group is read, which is the most important because, otherwise, we would be sending a watch packet when the host had requested a single register read. Even though read multi, as an command-response should not be aborted, we aren't distinguishing it from watch here because a read register command immediately after an unfinished read multi is a significant host error.

STATE_READMULTI_RX falls through to STATE_READREG_RX to post IN report. Although the OUT and IN structures for AUICMD_READMULTI and AUICMD_READREG are different, they share the output buffer and can, therefore, share the code to send the output message.

In case STATE_READREG_RX the state machines has been waiting for I2C read reg data from target to finish. The two bytes of reg data are now available and are copied from inReport to the IN report buffer. In the case of fall through from STATE_READMULTI_RX these result from the last register read in the set. At this point we can indicate that the command is done, because the host is not going to send us another command to which we can respond and overwrite our IN report buffer before retrieving our response.

In case STATE_READREG_TX the state machine has been waiting for the completion of I2C write register address to the target. Now, it can read the register data with another I2C transaction. It calls i2cTxRx to write the data directly into inReport for subsequent transfer to the IN report buffer.

The target device in this application (a capacitive button touch device) occasionally fails an I2C transaction. This seems to happen most often (perhaps only) when reading a pad's raw capacitance register. We can simply stop the USB transaction by not writing to IN EP. However, this causes several more similar failures. Previously, these never stopped but that was due to an error in my Windows program, which didn't call CancelIo after HID read timeout. However, after I corrected that omission it seemed that the only way to avoid additional I2C errors was to put something in the IN EP. This should not be dummy data but an error indicator and the host program should deal with it. But this occasionally causes unrecoverable failures. This needs further investigation.

targetCmdSpiThrd

This is the SPI equivalent of targetCmdI2cThrd. Whether the target communicates via I2C or SPI is determined at boot time by the queryTarget function. Once probing determines the link, it cannot be changed without rebooting the system.

hidInProc and hidOutProc functions

The USB ISR directly calls hidInProc and hidOutProc. hidInProc is called only when the IN time (interrupt) occurs and the IN endpoint doesn't already have something to send to the host. We don't have anything to do in this case but the function is here for possible use in error recovery. hidOutProc is called whenever the host sends us something. Note that a breakpoint in either of these will likely cause an unrecoverable error in USB communication.

hidOutProc function

Most of the non-control commands are posted for execution by the target thread. Even if the host is an over-anxious talker, commands from the host won't overrun each other because the USB engine responds with NAK until we read and release the buffer. However, there is the potential for posted command overrun because time is required for the target thread to be dispatched and to execute the command. This is especially true with I2C communication, which requires multiple periods to complete each transaction. Since we have already released the USB input buffer we may receive another command before the previous one has completed. When we receive a command of a type that requires posting, if no command is currently being processed then we copy this to cmdMsg for processing at the next iteration of the target communication thread. Since we are executing in an interrupt at this time, it is possible that we are interrupting the thread but, in this case, the thread will either have already tested the command and is returning or is about to test it and will find only a complete command, as whatever we do in this ISR is atomic relative to the thread. If a posted command has not been completed and this new command is an exact duplicate then just discard the new one. Otherwise we report an overrun but don't interfere with the previously posted command.

For commands that read the attached device, overrun is unlikely because the host waits for an answer before sending another command. For multiple write register commands, the host might overrun. We would detect this but it should not be allowed. Monitoring shows that this doesn't happen during initialization, which would be the only time the situation would arise, so nothing has been done to avoid the possibility. Several solutions exist. One would be to read the usb input register with a flag that says don't clear and then clear it here only if we see that there isn't a previous command or that the new one duplicates the previous. Otherwise, we would set a flag to tell the target thread to interpret the command and clear the buffer. An anxious host would receive NAK in the mean time. Another approach would be to implement a command/answer protocol with the host so that the host would not send another command the target thread finishes any preceding command. These solutions require some work to develop and test. Since the monitor has not reported any overruns, no avoidance scheme has been implemented.

Another type of overrun can occur when the watch command is executing. Once we receive this, unless we receive a command to stop it, the target thread continuously sends touch messages to the host. The thread normally gives priority to current commands over beginning the next watch. But if it had already begun the next watch cycle when the new command posts, if it were to complete the cycle it would send a touch message when the host was expecting the answer to the read register command. To avoid this, the target comm thread aborts a watch cycle in progress if it sees any new read command. There is no reason to abort in case of a new write register command.

checkHub function

This checks the health of the hub processes. It is called periodically by the main loop. It primarily watches for a stalled command endpoint (Lep1 OUT) and clears it if needed.


void hidInProc( void ) { }

void hidOutProc( void )
{
  DevCmdMsg rx;
  T_U32 cnt;

  cnt = usbRdLep( USB_LEP_HID_OUT, rx.report ); 
                // cnt is always HID_OUT_REPORT_SIZE unless error.
  cmdWaiting = FALSE;
  if( cnt > 0 )
  {
    switch( rx.report[0] )
    {
    case AUICMD_CONTROL:
      switch( rx.control.op )
      {
      case CONTROL_RESET:
      case CONTROL_BREAK:
        BREAKHERE
          break;
      case CONTROL_DISCONNECT:
      case CONTROL_PRE_ADAPTERCHANGE:
        if( watchCmd->cmd != AUICMD_NONE )
        {
          if( devLink == LINK_I2C )
            killWatch = TRUE;
          else
          {
            watchCmd->cmd = 0;
            watchCmd->cnt = 0;
          }
        }
        break;

      case CONTROL_POST_ADAPTERCHANGE: // Currently do nothing.
        break;
      }
      break; // AUICMD_CONTROL

    // Request for general controller information.
    case AUICMD_VERDEV: 
      inReport.msgType = AUICMD_VERDEV;
      inReport.status = DEVSTAT_OK;
      inReport.dev.link = devLink;
      inReport.dev.tchDev = tchDev;
      inReport.dev.pgmVer = pgmVer;
      inReport.dev.controller = controller;
      usbWrLep( USB_LEP_HID_IN, (UCHAR*)&inReport, 
                HID_IN_REPORT_SIZE );
      break;

    case AUICMD_WATCH:
      killWatch = FALSE;
      memcpy( watchCmd, rx.report, MAX_SIZE_CMDWATCH );
      watchSampTime = msTime;
      break;

    case AUICMD_READTOUCH:
      watchSampTime = msTime;
            // Fall through
    case AUICMD_READMULTI:
      if( devLink == LINK_I2C )
      {
        watchCmd->cmd = rx.watch.cmd;
        memcpy( &watchCmd->cnt, &rx.readMulti.cnt, 1
          + rx.readMulti.cnt );
        break;
      }
    // Fall through if SPI. It requires no special handling 
    // for these commands.
    default: // AUICMD_WRITEREG, AUICMD_READREG, ...
      if( cmdMsg.report[0] == AUICMD_NONE ) 
        // No command currently being processed.
        memcpy( cmdMsg.report, &rx, cnt );
      else if( memcmp( cmdMsg.report, &rx, cnt ) != 0 )
        inReport.status = DEVSTAT_CMDOVERRUN;
      break;
    } // switch( rxMsg[0]
  }
}

void checkHub( void )
{
  if( LpcUsb->DevIntSt & CMD_EP_INT )
  {
    if( cmdWaiting )
    {
      ++cmdStallCount;
      hidOutProc();
    }
    else
      cmdWaiting = TRUE;
  }
}

I2C

i2c.c
// ................. LPC13xx Registers ......................................
#define I2CSTAT_START       8       // START condition has been transmitted.
#define I2CSTAT_REPSTART    0x10    // REPEAT START condition has been transmitted
#define I2CSTAT_NARB        0x38    // NAK arbitration lost.
// Transmitting state values
#define I2CSTAT_SLAWACK     0x18    // SLA+W sent and ACK received.
#define I2CSTAT_SLAWNAK     0x20    // SLA+W sent and NAK received.
#define I2CSTAT_WDACK       0x28    // Data sent and ACK received.
#define I2CSTAT_WDNAK       0x30    // Data sent and NAK received.
// Receiving state values
#define I2CSTAT_SLARACK     0x40    // SLA+R sent and ACK received.
#define I2CSTAT_SLARNAK     0x48    // SLA+R sent and NAK received.
#define I2CSTAT_RDACK       0x50    // Data received and ACKd.
#define I2CSTAT_RDNAK       0x58    // Data received and NAKd.
#define I2C_AA              0x04
#define I2C_SI              0x08
#define I2C_STO             0x10
#define I2C_STA             0x20
#define I2C_I2EN            0x40

#define I2C_ReadFlag        1
#define I2C_MasterFlag      2
#define I2C_BUSY            4
#define I2CModeFlag             1
#define NHSIntAsserted          2
#define DeviceCommandPending    4

#define SlaveAddr       (0x2C << 1) // Write b0 = 0, read = 1

T_U32 slaveAdrRw;
UCHAR* pI2cDat;
int i2cDatIdx;
int i2cDatCnt;
UCHAR I2C_Mode;
void i2cInit( void )
{
  LpcSyscon->PRESETCTRL |= 3; // De-assert SSP (b0) and I2C (b1) resets.
  LpcSyscon->SYSAHBCLKCTRL |= ( 1 << 5 );
  LpcIocon->PIO0_4 &= ~0x3F; /*  I2C I/O config */
  LpcIocon->PIO0_4 |= 0x01; /* I2C SCL */
  LpcIocon->PIO0_5 &= ~0x3F;
  LpcIocon->PIO0_5 |= 0x01; /* I2C SDA */

  LpcI2c->conClr = I2C_I2EN | I2C_STA | I2C_SI | I2C_AA;

// Enable the I2C interrupt
  NVIC_EnableIRQ( I2C_IRQn );
  LpcI2c->conSet = I2C_I2EN;

    // Initialize local state machine.
  I2C_Mode = 0;
  i2cState = I2C_IDLE;
}

void i2cEndTransaction( void )
{
    //NVIC_DisableIRQ( I2C_IRQn );
  I2C_Mode = 0; // clr BUSY (local state machine)
  LpcI2c->conSet = I2C_STO; // generate STOP
}

void i2cIsr( void )
{
  switch( LpcI2c->stat )
  {
    // ............ Transmitting and Receiving States ................
  case I2CSTAT_START:     // (8)  START condition has been transmitted.
  case I2CSTAT_REPSTART:  // (0x10) REPEAT START condition has been transmitted
    i2cDatIdx = 0;
    LpcI2c->conClr = I2C_STA;
    LpcI2c->dat = slaveAdrRw | ( I2C_Mode & I2C_ReadFlag ); 
                // send address and direction bit
    i2cState = I2C_START;
    break;

default:
    i2cEndTransaction();
    i2cState = I2C_GENFAIL;
        //LpcI2c->conClr  = I2C_AA | I2C_STA;
    LpcI2c->conClr = ( I2C_I2EN | I2C_STA | I2C_SI | I2C_AA );
    break;

    // ............ Write Slave Data Process ....................
  case I2CSTAT_SLAWACK:   // (0x18) SLA+W sent and ACK received.
  case I2CSTAT_WDACK:     // (0x28) Data sent and ACK received.
    if( i2cDatIdx < i2cDatCnt )
    {
      LpcI2c->dat = pI2cDat[ i2cDatIdx++ ];
      i2cState = I2C_ACKD;
    }
    else // no more data to send.
    {
      i2cEndTransaction();
      i2cState = I2C_IDLE; // <<<<<<< Write slave data done
    }
    break;

    // .............. Read Slave Data Process ................................
  case I2CSTAT_SLARACK: // (0x40) SLA+R sent and ACK received.
    if( i2cDatCnt > 1 )
      LpcI2c->conSet = I2C_AA;
    i2cState = I2C_ACKD;
    break;

  case I2CSTAT_RDACK: // (0x50) Data received and ACKd.
    pI2cDat[ i2cDatIdx++ ] = LpcI2c->dat;
    if( i2cDatIdx + 1 >= i2cDatCnt )
      LpcI2c->conClr = I2C_AA; // ACK all RX bytes except the last.
    i2cState = I2C_ACKD;
    break;

  case I2CSTAT_RDNAK: // (0x58) Data received and NAKd.
    pI2cDat[ i2cDatIdx++ ] = LpcI2c->dat;
    i2cEndTransaction();
    if( i2cDatIdx == i2cDatCnt )
    {
    // data received
    /* OSFLoad (TwoArgFunc, DevicePacketDecoder, DeviceBuffer.W[0], 
       DeviceBuffer.W[1]); */
      i2cState = I2C_IDLE; //  Read slave data done.
    }
    else // I2C receive error
    {
      i2cState = I2C_RXFAIL;
      LpcI2c->conClr = ( I2C_I2EN | I2C_STA | I2C_SI | I2C_AA );
    }
    break;
  }
  LpcI2c->conClr = I2C_SI;
  return;
}

BOOL i2cTxRx( UCHAR *dat, UCHAR cnt, I2cTxRx txrx )
{
    //int idx;

  if( I2C_Mode & I2C_BUSY )
    return FALSE;
  i2cState = I2C_INUSE;

  pI2cDat = dat;
  slaveAdrRw = SlaveAddr;
  i2cDatCnt = cnt;
  I2C_Mode = I2C_BUSY | I2C_MasterFlag | txrx;
  LpcI2c->conClr = I2C_SI | I2C_AA; // clr interrupt and acknowledge, just in case
    //NVIC_EnableIRQ( I2C_IRQn );
  LpcI2c->conSet = I2C_STA; // initiate transfer in master mode
    //for( idx = 0 ; idx < 10000 ; idx++ )
    //  breakHere();
  return TRUE;
}

This implements the I2C lower-level state machine, which processes one bus transaction. At the end of a successful transaction it assigns i2cState == I2C_IDLE, telling the upper state machine to advance to advance. It assigns various failure indicator values to i2cState to tell the upper machine to deal with a specific problem.

When the LPC13xx is I2C master, it produces the SCL clock. The clock rated is set by programming the number of PCLK_I2C clocks for high (LPC_I2C->SCLH) and low (LPC_I2C->SCLL) phases. The SCL rate is simply PCLK_I2C / (SCLH+SCLL).

The sample code used 0x180 = 384 for both low and high periods when not in superfast mode. Experiments with monitoring by logic analyzer showed that for 400 KHz operation, the minimum low period of 1.3 usec was achieved with a count of 90 and the minimum high period of .6 usec with a count of 39. Symmetrical 400 KHz slightly violates the low period requirement by being 1. 25 usec but it is close enough to say that 90/90 yields symmetrical 400 KHz (it is actually 385 KHz). Since 384 / 90 = 4. 3, it is likely that the sample code produces 100 KHz operation. For 400 KHz operation, I choose a count of 90 for the low period. For the high period to achieve exactly 400 KHz, the count would be 83 but it is likely that (90-39)/2 = 65 would also be reliable. I'm not using a count of 39 for high because this requires low low value, high current pullups.

Working backwards from measured results and given the User's Manual claim that I2C bit rate = PCLK / (H+L), 100 KHz = PCLK_I2C / (384+384)and PCLK_I2C = 77 MHz. This seems unreasonably high. The manual claims that PCLK_I2C is the system clock. The SystemInit function (in lpc13.c) contains LpcSyscon-& gt; MAINCLKSEL = MAINCLKSEL_Val. MAINCLKSEL_Val is defined as 3 in that file. This selects System PLL clock out as the source for “system clock”. Since the oscillator is 12MHz, this implies that the PLL multiplier is 6.4. At run time the value of SystemCoreClock defined in lpc13.c is 268,443,632. This clock configuration stuff is impossibly complex and I don't think the documentation is correct.

The LPC1343 User's Manual also gives the example of: Given PCLK_I2C = 12 MHz, for 100 KHz H+L = 120, for 400 KHz H+L = 30 Given PCLK_I2C = 20 MHz, for 100 KHz H+L = 200, for 400 KHz H+ L = 50

The compiler produces unoptimized code when a const pointer to structure is used but the debugger doesn't recognize the equivalent macro version. Therefore, the macro version, LpcI2c, is used for code but a const version, lpcI2c, is defined when compiling for debug. The const version can be added to Variables window and inspected.

The LPC13xx User Manual tells combinations of STA, STO, SI, and AA to set and clear as response to I2C0STAT (LPC_I2C->STAT) (Master mode) states. The information is incorrect and should not be used. Flags: STO = STOP

i2cInit

For FAST_MODE_PLUS, operation above 400 KHz (up to 1 MHz) use:
LpcIocon->PIO0_4 |= (0x1<<9);
LpcIocon->PIO0_5 |= (0x1<<9);
Sample code has symmetrical 32+32 for Fast Plus.
LPC_I2C->SCLL = 32;
LPC_I2C->SCLH = 32;
For 100 KHz operation use symmetrical 384+384. For symmetrical approximate 400 KHz (actually 384 Khz) use 90+90. For exact 400 KHz, use 90 low and 83 high counts. For minimum low and high use 90 low and 39 high but this requires low value, high power pullups.
LpcI2c->scll = 90;
LpcI2c->sclh = 83;

i2cIsr

I2C interrupt handler. Supports only master mode. Error-free transmit is I2CSTAT_START -> I2CSTAT_SLAWACK -> I2CSTAT_WDACK repeat per byte until all data has been transmitted. Error-free receive is I2CSTAT_START -> I2CSTAT_SLARACK -> I2CSTAT_RDACK repeat per byte received.

The following cases have not been tested:
I2CSTAT_NARB: (0x38) NAK arbitration lost. Only if multiple masters.
I2CSTAT_SLAWNAK: (0x20) SLA+W sent and NAK received.
I2CSTAT_SLARNAK: (0x48) SLA+R sent and NAK received.
I2CSTAT_WDNAK: (0x30) Data sent and NAK received.

i2cTxRx

This is a non-blocking process to set up I2C transmit or receive. The LPC1343 User's Manual does not tell how to do this and the sample code they provide only does this in a blocking function, which can stall my system. I have determined by experiment the commands and states needed to do this. This is not based on NXP's official documentation.




STEPPER MOTOR CONTROLLER

MC68340+C+FPGA+SPI synchronized stepper motors controller

Description



This is part of a stepper motor control system, in which one MC68340 (Motorola CPU32-based micro-controller) and an FPGA (Altera EPF10K30) directly control as many as 20 synchronized stepper motors. I designed the complete system, including FPGA logic, which was implemented in VHDL by another engineer. I implemented the firmware.

The CPU calculates step pattern changes for all active motors for a given period. This is done in an ISR triggered on a real-time clock. The update calculations are thus performed periodically but not deterministically, as the calculation time depends on the number of active motors and the number of step changes they each have in the update period. The CPU writes the patterns into dual-port memory in the FPGA. The FPGA reads these patterns out at the step resolution rate, which is much faster than the ISR rate and is absolutely deterministic with sub-microsecond precision. Two ping-pong pattern pages enable the FPGA to play one coherent period while the CPU simultaneously prepares the next.

At each step period, the FPGA sends the pattern for all motors out on a high-speed differential (RS422) SPI bus, which I designed. All receivers on the bus hold the previous pattern while the new pattern ripples through the chain. When the FPGA broadside loads the new pattern, all motors are updated at exactly the same time, affording precise synchronization.

The system supports full stepping and micro-stepping. For full stepping, the CPU produces a standard four-phase gray code on two bits, which directly control the inputs of a driver, such as the 3717 (ST et. al.). For micro-stepping, the CPU generates a step plus direction code, also on two bits. These control a simple translator, which generates gray code phase signals and an 8-level current setting (with three binary-weighted resistors serving as a DAC) which controls the same kind of driver. I implemented this initially in a GAL with PALASM code and later in a Xilinx PLD with Verilog.

Only the ISR is shown here. It is a small piece of the complete motor control system, which includes absolute, relative, to-flag, and timed move commands. It supports table-driven arbitrary ramp profiles. A configuration program (part of my overall robotic mesh development system) generates a variety of ramps, including linear and piece-wise logarithmic, from parametric statements. To facilitate ramp development by experiment, I implemented a dedicated ramp heap with LRU (Least Recently Used) garbage collection providing virtually infinite memory in the relatively limited memory of the controller.

stpmtr.c

stpmtr.c

USHORT defUpRamp[] = { 652, 558, 478, 409, 351, 300, 257, 220, 188, 161};
/* Default up ramp generated from script statement 50 to 200 linear 15% */
USHORT defDownRamp[] = { 163, 194, 231, 275, 327, 389, 463, 551, 656};
/* Default down ramp generated from 200 to 50 linear 20% */
USHORT defRecoilRamp[] = { 656, 656};
#define DEFAULT_SLEW_STEP 200
#define DEFAULT_HOLD_STEP 16303 /* Default hold time .5 second / 30.67 usec. */


/* UC3717 stepper driver power control patterns are:
OFF: I1/I0 = 11. LOW: I1/I0 = 10. MED: I1/I0 = 01. HIGH: I1/I0 = 00. Assume
that power is written as one USHORT comprising I0 (MSB) followed by I1 (LSB)
in the event array. */
#define USP_OFF  0x8080  /* I0/I1 = 11 */
#define USP_LOW  0x0080  /* I0/I1 = 01 */
#define USP_MED  0x8000  /* I0/I1 = 10 */
#define USP_HIGH 0x0000  /* I0/I1 = 00 */

enum
{
  MTR_PABIT = 0, MTR_PBBIT = 1, MTR_I0BIT = 2, MTR_I1BIT = 3
};
#define MPWR( M, P ) ( P + (M * 4 + MTR_I0BIT) * 256 + M * 4 + MTR_I1BIT ) 

#define PHASES(M)                                           \
{   /* PLUS Phase patterns.    Initial PHA = 1, PHB = 1 */  \
    M*4 + MTR_PBBIT,        /* PHB->0: PHA = 1, PHB = 0 */  \
    M*4 + MTR_PABIT,        /* PHA->0: PHA = 0, PHB = 0 */  \
    M*4 + MTR_PBBIT + 0x80, /* PHB->1: PHA = 0, PHB = 1 */  \
    M*4 + MTR_PABIT + 0x80, /* PHA->1: PHA = 1, PHB = 1 */  \
    /* MINUS Phase patterns    Initial PHA = 1, PHB = 1 */  \
    M*4 + MTR_PABIT,        /* PHA->0: PHA = 0, PHB = 1 */  \
    M*4 + MTR_PBBIT,        /* PHB->0: PHA = 0, PHB = 0 */  \
    M*4 + MTR_PABIT + 0x80, /* PHA->1: PHA = 1, PHB = 0 */  \
    M*4 + MTR_PBBIT + 0x80  /* PHB->1: PHA = 1, PHB = 1 */  }

enum
{
  MTRCMD_NOTHING, MTRCMD_SUSPEND, MTRCMD_RESET
}; /* mtrCommand. */
UCHAR   mtrCommand;

enum
{
  MM_BUSY = -1, MM_OK = 0, MM_NOMOVE
};

typedef struct
{
  USHORT  cnt;  /* Max steps in ramp. Working count may be less for small moves. */
  USHORT  *src; /* Ramp source pointer. Note for dynamically stored ramps
                   this points to RampDesc.steps, not RampDesc. */
  USHORT  *rmp; /* Incrementing ramp pointer. Manager reloads from src. */
}   RampRef;
enum
{
  RIDX_UP, RIDX_DOWN, RIDX_RECOIL
};

enum
{
  MTR_FULLSTEP, MTR_MICROSTEP
}; 

typedef struct
{
    /* Permanent values for this motor. */
  UCHAR   rotate[8]; /* 4 PLUS followed by 4 MINUS rotation patterns. */
  USHORT  offPow;    /* Motor off power pattern. */
  USHORT  hardStopPow;

    /* Programmable values. */
  UCHAR   mtype;      /* MTR_FULLSTEP, MTR_MICROSTEP */
  UCHAR   pulse;      /* Pulse bit pattern if MTR_MICROSTEP. */
  USHORT  perPow[6]; /* Power pattern for UPRAMP, SLEW, DOWNRAMP, RECOIL,
                      HOLD, IDLE. Use MPWR( M, P ) for assignment. */
  USHORT  perEnd[4]; /* End position for UPRAMP, SLEW, DOWNRAMP, RECOIL. */
  SCHAR   perNext[4]; /* Next state after UPRAMP, SLEW, DOWNRAMP, RECOIL. */
  RampRef perRamp[3]; /* up, down, recoil. Indexed by RIDX_, not MS_. */
  USHORT  slewStep;
  USHORT  holdStep;
  UCHAR   hasRamp;    /* TRUE if ramps (from heap, not embedded defaults)
                         ever assigned to this motor. */
  UCHAR   hasMoved;   /* TRUE if ever moved. */

    /* Initialized elements. */
  UCHAR   asymTraj; /* Asymetrical trajectory. Used to 
                       select truncation type for short moves. */
  UCHAR   unit;     /* Remote command unit for reporting move error. */
  USHORT  seqId;    /* Script (or interactive) who last (or currently) commanded. */
  USHORT  cmdNum;   /* Commanding script command number. */
  USHORT  position; /* Current position. */
  USHORT  incr;     /* Position increment. 1 for PLUS, 0xFFFF for MINUS. */
  SCHAR   state;    /* Current state. */
  UCHAR   stepIdx;  /* Rotation index. PLUS = 0-3, MINUS = 4-7. */

    /* Scratchpad. */
  USHORT  nextStep;
  USHORT  pow; /* Current power pattern (to determine if change needed). */
}   Stepper;

#define STEPPER_INIT(M)                                             \
{   PHASES(M),          /* rotate */                                \
    MPWR(M,USP_OFF),    /* offPow */                                \
    MPWR(M,USP_HIGH),   /* hardStopPow */                           \
    MTR_FULLSTEP,       /* mtype */                                 \
    M*4 + MTR_PABIT + 0x80, /* pulse (if MTR_MICROSTEP) */          \
    MPWR(M,USP_LOW),    /* perPow[ MS_UPRAMP ] */                   \
    MPWR(M,USP_LOW),    /* perPow[ MS_SLEW ] */                     \
    MPWR(M,USP_LOW),    /* perPow[ MS_DOWNRAMP ] */                 \
    MPWR(M,USP_LOW),    /* perPow[ MS_RECOIL ] */                   \
    MPWR(M,USP_LOW),    /* perPow[ MS_HOLD ] */                     \
    MPWR(M,USP_OFF),    /* perPow[ MS_IDLE ] */                     \
    0,                  /* perEnd[ MS_UPRAMP ] */                   \
    0,                  /* perEnd[ MS_SLEW ] */                     \
    0,                  /* perEnd[ MS_DOWNRAMP ] */                 \
    0,                  /* perEnd[ MS_RECOIL ] */                   \
    MS_SLEW,            /* perNext[ MS_UPRAMP ] */                  \
    MS_DOWNRAMP,        /* perNext[ MS_SLEW ] */                    \
    MS_HOLD,            /* perNext[ MS_DOWNRAMP ] */                \
    MS_HOLD,            /* perNext[ MS_RECOIL ] */                  \
    DIM( defUpRamp ), defUpRamp, 0,  /* perRamp[up].cnt/src/rmp */  \
    DIM( defDownRamp ), defDownRamp, 0,     /* perRamp[down] */     \
    DIM( defRecoilRamp ), defRecoilRamp, 0, /* perRamp[recoil] */   \
    DEFAULT_SLEW_STEP,  /* slewStep */                              \
    DEFAULT_HOLD_STEP,  /* holdStep */                              \
    0,                  /* hasRamp */                               \
    0,                  /* hasMoved */                              \
    0,                  /* seqId */                                 \
    TRUE,               /* asymTraj */                              \
    0,                  /* unit */                                  \
    0,                  /* cmdNum */                                \
    0,                  /* Position */                              \
    1,                  /* incr */                                  \
    MS_OFF,             /* state */                                 \
    0,                  /* stepIdx */                               \
    256,                /* nextStep */                              \
    MPWR(M, USP_OFF),   /* pow */                                   \
}
Stepper steppers[] = { STEPPER_INIT(0),
  STEPPER_INIT(1), STEPPER_INIT(2), STEPPER_INIT(3), STEPPER_INIT(4),
  STEPPER_INIT(5), STEPPER_INIT(6), STEPPER_INIT(7), STEPPER_INIT(8),
  STEPPER_INIT(9), STEPPER_INIT(10), STEPPER_INIT(11), STEPPER_INIT(12),
  STEPPER_INIT(13), STEPPER_INIT(14), STEPPER_INIT(15), STEPPER_INIT(16),
  STEPPER_INIT(17), STEPPER_INIT(18),
  STEPPER_INIT( STEPPER_COUNT - 1 )}; /* This array should be reduced by
                          editing to match STEPPER_COUNT in analyz.h. */

Stepper *loMotor;
Stepper *hiMotor;

UCHAR   *page0Events; /* Allocated at boot time to get 8K alignment. */
UCHAR   *page1Events; 

UCHAR changeDir[] =   { 4, 7, 6, 5, 0, 3, 2, 1}; /* dirIdx change to change
direction without losing phasing. rotate[0] <-> rotate[4], etc. This assumes
that dirIdx is pre-incremented in the original direction, i.e. it would select
the next step in that direction. */

typedef struct RampDesc
{
  struct RampDesc *next;
  USHORT  id; /* 0 = dead. 1 to RAMP_DEBUG - 1 = script temp. RAMP_DEBUG to
        RAMP_PERM - 1 = interactive temp. RAMP_PERM to FFFF = permanent. */
  UCHAR   stepCnt;
  USHORT  steps[1];
}   RampDesc;
UCHAR   rampMem[ 8000 ]; /* This could also be allocated as needed. */
RampDesc *lastRamp; /* = 0 in prepSteppers. */

typedef packed struct
{
  UCHAR   motor;
  UCHAR   condition;  /* WAITMTR_STOP, etc. */
  USHORT  position;   /* If condition = GREATER or LESS */
  UCHAR   reqUnit;    /* Unit ID of requester for done message routing. */
}   StepperWait;
/* The motor, condition, and position elements of StepperWait are packed
* to allow the group to be compared as a ULONG. They must not change in size
* and the structure must be packed. */

StepperWait remWaits[16];

short remWaitCnt;
static UCHAR mtrDoneMsg[ DmPackSize( SIZE_STEPPERDONEARG ) ] =
{ SIZE_STEPPERDONEARG + 1, DM_STEPPERDONE};
#define doneMsg ((Dmsg*)&mtrDoneMsg)

ULONG   isrExeTime = 0;

#define AddPowerEventW(P) *((USHORT*)evPtr)++ = P, cnt += 2
#define AddPowerEvent(P) *evPtr++ = HIBYTE(P), \
*evPtr++ = LOBYTE(P), cnt += 2

void interrupt stpmtrIsr()
{
UCHAR   *evPtr; /* motor event page pointer. */
  Stepper *mp;
  USHORT  cnt;
  USHORT  pageStep;
  USHORT  nextPageStep;
  USHORT  usv;
  UCHAR   isr;
  UCHAR   event;
  ULONG   begTime;
  ULONG   exeTime;

  begTime = readCodeTimer();
  isr = MtrScanControl->isr;
  if( isr & SISR_MTROVR )
  {
    systemError |= ERR_MTRPAGE;
    MtrScanControl->isr = 0;
  }
  if( !( isr & SISR_MTRTOGGLE ) )
    return;
  evPtr = ( isr & SISR_MTRPAGE ) ? page0Events : page1Events;

  switch( mtrCommand )
  {
  case MTRCMD_SUSPEND :
    return;
  case MTRCMD_RESET :
/* Set every bit in the motor MOSI scan chain high by filling the event page
with x80, x81, x82...xCF and setting four event counts for each motor. */
    for( event = 0x80 ; event <= 0x80 + STEPPER_COUNT * 4 - 1 ; event++ )
      *evPtr++ = event;
    for( cnt = 0 ; cnt < STEPPER_COUNT ; cnt++ )
      MtrPageCounts[ cnt ] = 4;
    mtrCommand = MTRCMD_NOTHING;
    return;
  }
  if( hiMotor < loMotor )
    return;

/* MOTOR STATE SCAN LOOP */
  cnt = 0;  /* AddPowerEvent(W) modifies this. */
  pageStep = 0xFFFF;
  for( mp = loMotor ; mp <= hiMotor ; mp++ )
  {
    switch( mp->state )
    {
    case MS_START:
      if( mp->mtype == MTR_MICROSTEP )
      { /* Set direction bit according to initial stepIdx */
        *evPtr++ = mp->stepIdx < 4 ? mp->rotate[ 0 ] :
        mp->rotate[0] + 0x80;
        cnt++;
      }
      mp->state = MS_UPRAMP;
      usv = mp->perPow[ MS_UPRAMP ];
      setpow:
      if( mp->pow != usv )
      {
        AddPowerEvent( usv ); /* Will be 2 events in slot 0 */
        mp->pow = usv;
      }
      break;

    case MS_STOPHARD:
      mp->state = MS_HOLD;
      usv = mp->hardStopPow;
      goto setpow;

    case MS_STOPOFF:
      mp->state = MS_OFF;
      usv = mp->offPow;
      goto setpow;

    case MS_IDLEPOWER:
      mp->state = MS_IDLE;
      usv = mp->perPow[ MS_IDLE ];
      goto setpow;

    case MS_IDLING:
    case MS_OFF:
      break;

    default:
      if( ( mp->nextStep -= 256 ) < 256 )
      {   /* This motor has a step on this page. Is it the earliest? */
        if( mp->nextStep < pageStep )
          pageStep = mp->nextStep;
      }
    }
  }
  MtrPageCounts[0] = cnt;
  if( pageStep == 0xFFFF )  /* No step events */
    return;
  if( pageStep != 0 && cnt > 0 )
    cnt = 0;

/*  PAGE STEP SCAN LOOP */
  while( 1 ) /* Do until all motors' next step lies beyond this page. */
  {
    nextPageStep = 0x0100; /* Initial value allows any motor with a step
on this page to define the next page step unless replaced by an earlier one. */

    for( mp = loMotor ; mp <= hiMotor ; mp++ )
    {
      if( mp->nextStep > 255 )
        continue; /* Not active on this page (this includes OFF). */
      if( mp->nextStep == pageStep )
      {   
/* This motor has an event at the earliest step determined by previous scan. */
        dostate:
        switch( mp->state )
        {
        case MS_UPRAMP :
          if( mp->position == mp->perEnd[ MS_UPRAMP ] )
          {
            mp->state = mp->perNext[ MS_UPRAMP ];
            changestate:
            usv = mp->perPow[ mp->state ];
            if( mp->pow != usv )
            {
              AddPowerEvent( usv ); 
              mp->pow = usv;
            }
            goto dostate;
          }
          usv = *mp->perRamp[ RIDX_UP ].rmp++;
          break;

        case MS_DOWNRAMP :
          if( mp->position == mp->perEnd[ MS_DOWNRAMP ] )
          {
            mp->state = mp->perNext[ MS_DOWNRAMP ];
            goto changestate;
          }
          usv = *mp->perRamp[ RIDX_DOWN ].rmp++;
          break;

        case MS_RECOIL :
          mp->incr = 0 - mp->incr; /* Negate step increment. 1 vs. -1 */
          mp->stepIdx = changeDir[ mp->stepIdx ];
          mp->state = MS_RECOIL2;
                    /* Fall through. */
        case MS_RECOIL2:
          if( mp->position == mp->perEnd[ MS_RECOIL ] )
          {
            mp->state = mp->perNext[ MS_RECOIL ];
            goto changestate;
          }
          usv = *mp->perRamp[ RIDX_RECOIL ].rmp++;
          break;

        case MS_SLEW :
          if( mp->position == mp->perEnd[ MS_SLEW ] &&
            mp->position != 0xFFFF ) /* FFFF = forever. */
          {
            mp->state = MS_DOWNRAMP; /* mp->perNext[ MS_SLEW ]; */
            goto changestate;
          }
          usv = mp->slewStep;
          break;

        case MS_HOLD :
          mp->nextStep += mp->holdStep;
          mp->state = MS_IDLE;
          goto checkevent; /* Skip rotation but not whether the end
                         of hold period is the next page event. */

        case MS_IDLE :
          mp->state = MS_IDLING;
          mp->nextStep = 256; /* Mark inactive. */
          if( mp->pow != mp->perPow[ MS_IDLE ] )
          {
            mp->pow = mp->perPow[ MS_IDLE ];
            AddPowerEvent( mp->pow );
          }
          continue;
        }
        mp->nextStep += usv;
        mp->position += mp->incr; /* Advance position by 
                                       +1 if CW, -1 if CCW. */
        if( mp->mtype == MTR_FULLSTEP )
        {
          *evPtr++ = mp->rotate[ mp->stepIdx ]; /* Write this event. */
/* Advance rotation pattern index. 0-3 if PLUS or 4-7 if MINUS. */
          if( ( mp->stepIdx & 3 ) == 3 )
            mp->stepIdx &= ~3;
          else
            mp->stepIdx++;
        }
        else
          *evPtr++ = mp->pulse ^= 0x80;
        cnt++; /* Count this step event. */
      }   /* This motor has an event in earliest active slot */
      checkevent:
/* Whether the motor stepped or not, see if its next event is the next page
* event (by being earlier than all others seen so far in this iteration). */
      if( mp->nextStep < nextPageStep )
        nextPageStep = mp->nextStep;
    }   /* Iterate through active motor list range. */

    if( cnt != 0 )
      MtrPageCounts[ pageStep ] = cnt; /* Tell number of events at this slot. */
    if( nextPageStep > 255 )
      break; /* No more motors step in this page. */

        /* Go through the motor list again. */
    pageStep = nextPageStep; /* Carry earliest next page step over 
                                to next iteration. */
    cnt = 0;
  }
  exeTime = begTime - readCodeTimer();
  if( exeTime > isrExeTime )
    isrExeTime = exeTime;
}

The central feature of the motor control firmware is the ISR but many support functions are needed for its optimal execution. I have implemented "code hoisting" at every opportunity to minimize the amount of work that the ISR must do even when this increases overall complexity. Many structures and macros are defined to enable code (especially in the ISR) to reflect the abstract motor control intent without having to subsequently translate into the physical hardware domain. In a number of cases I have designed two versions of something, a fast one that will only work under certain conditions and a slower one that has less stringent requirements and is easier to use during program development.

PHASES(M) defines the phase patterns for motor M in terms of its scan chain bits. Initial motor phase pattern is 11. From there, PLUS rotation is 10, 00, 01, 11. MINUS is 01, 00, 10, 11. After its first move, a motor may be in any phase state. This is recorded in Stepper.stepIdx, which is a 2-bit (mod 4) counter. plusRotate and minusRotate arrays contain the motor's step event words. These are unique to each motor, being based on the motor's scan chain position.

Abstract Motor State

Each motor state from UPRAMP through MS_IDLE can have a power of OFF, LOW, MED, or HIGH. MS_HOLD means the motor is not moving but is being held by a strong magnetic detent. MS_IDLE provides a weak (LOW) or very weak (OFF) detent.

ISR

The function stpmtrIsr is the motor control state machine. It is invoked on interrupt from the FPGA normally at page toggle (it can also be on page overrun or scan chain integrity test failure). It comprises three phases. First it checks for overall motor control system status changes. In the second phase, Motor State Scan, it checks all active motors to determine which, if any, have a step or power event in the next time period. In the third phase, Page Step Scan Loop, it writes the events into time slots in the event page. Both the second and third phases iterate over the same motor list and they could be combined but this would be less efficient than the two fully optimized processes.

Count page carry over by subtracting 256 (one page of 32 usec. slots) from this motor's next step time. If the result is less than 256 then the motor has at least one step in this period. Note initial nextStep of 0x0100 to cause the motor to take its first step at slot 0 of the next page flip after the page in which the power is set (MS_START).

The definition of AddPowerEvent exemplifies the issue of speed vs. ease of use. The generic AddPowerEvent is safe without alignment but the generated code is really bad if P is complex, e.g. array element. AddPowerEventW code is good but can only be used if word-aligned. The two versions have identical intent.

#define AddPowerEventW(P) *((USHORT*)evPtr)++ = P, cnt += 2

#define AddPowerEvent(P) *evPtr++ = HIBYTE(P), *evPtr++ = LOBYTE(P), cnt += 2

AddPowerEventW was originally safe to use during the motor scan loop but not after support was added for micro-stepping and other synchronous motors. For these motors the direction bit is assigned independently of stepping and only needs to be done once at the beginning of a move. This could be done in the Page Step Scan Loop on the first step of a motor but this approach would require checking at every step of a motor. Doing it, instead, in the MS_START state of each motor in Motor State Scan Loop eliminates redundant checking but means that single-bit events can be added to slot 0 before all of the power pairs have been added, introducing the possibility of evPtr pointing to a misaligned word. Therefore, the less efficient AddPowerEvent is required. Another approach would be another scan through the motors looking for synchronous motors that are starting. If there are few, this might yield better performance.

Motor State Scan Loop

The motor state scan iterates over the canonical motor list from the first and last currently active motors, identified respectively by loMotor and hiMotor pointers into the steppers array of Stepper structs. For each motor in the active range:

At case MS_START, if the motor is configured for micro-stepping, set direction bit according to initial stepIdx passed by the move command processor. Using stepIdx in this way hides whether the motor is microstepper or full stepper from the move command processor. Use this motor's rotate[0] pattern, which normally clears PHB, to get the bit address with state bit clear.

At the conclusion of the motor state scan loop, the power event count is unconditionally written into slot 0 of the event page. If the first scan through the motor list revealed no steps on this page then return; if there were any power events, the FPGA will process them based on the indicated count. If there is at least one step event in slot 0 (pageStep = 0) then the count written into MtrPageCounts will be overwritten by the total count, including the power events. If pageStep indicates that there is no step event in slot 0, the FPGA will use the recorded power event count and the event counter should be reinitialized to 0 before the motor list is scanned again (to record step events and find the next page step.

Page Step Scan Loop

This operates on the same motor list as the preceding Motor State Scan, but it is able to use overall information accumulated in the preceding phase to execute more efficiently. It iterates over the active motor range repeatedly, calculating each active motor's next step until all active motors' next step lies beyond this page. Each scan determines the earliest step of all motors. The next scan programs the step event for each motor that occurs at this position and calculates that motor's next step while looking for the next earliest page step. Begin scan with cnt = 0 or the number of power control events (2 per power setting). Initial nextPageStep is 0x0100 so that on the first motor to step, its updated next step can just be compared to nextPageStep. A larger value would also suffice but then the nextPageStep would be reassigned even if the first motor's next Step lies in a future page, which just wastes time.

If a motor has a step event, the appropriate pattern change depends on whether it is full- or micro-stepping. If the motor is a full-stepper then write the next gray code pattern bit. If it is a micro-stepper then toggle the step bit. In both cases only one bit at a time changes. With a micro-stepper it is always the PHA bit, which is used as the step strobe. With a full-stepper, it alternates between PHA and PHB. Micro-steppers use PHB for direction.

For each time slot in the event page, the motor control pattern changes are written. If all motors were stepping, 0 cnt would mean that there are no more events on this page. However, the state machine also transitions to idle and hold states, which may break the loop without generating events if the power setting doesn't change. Therefore, we can't break just because cnt is 0 but must check nextPageStep to determine whether there might be further activity.




M6801 ASM SATELLITE TV CONTROLLER

M6801+ASM+RealTime+AI+motor+SPI+IR+radio+video+audio

I designed all digital hardware and firmware for the ASTEC/BSR SRX500 Satellite TV receiver/controller. The business goal was to make satellite television a consumer product-- inexpensive, reliable, and easy to set up and use. Too many essential functions required either a sophisticated user or expensive military technology. To succeed my firmware had to automatically and quickly find all geosynchronous satellites regardless of receiving antenna (dish) location; to automatically find audio subcarriers separated by frequencies smaller than the aperture of the discriminating filter (i.e. to circumvent a fundamental law of physics); to balance dish direction and continuously-variable polarization to achieve the radio signal delivering the best video; to control motors, displays, buttons, and IR remote control; to be reliable and to have built-in self-checking and diagnostics; and to cost almost nothing.

I succeeded by using cheap feedback where others tried to use expensive precision. For example, instead of demanding an expensive filter that could resolve the sub-carrier frequency separation, I invented an unprecedented alternating forward-inverse PLL process (Patent #4,783,848). To find the satellites, I invented a fast searching process based on the satellites' own signals. In 15 minutes, with no prior knowledge, my system would find the precise locations of all 24 satellites (Patent #4,801,940).

One primitive 8-bit controller does everything. I designed a real-time OS around the fixed timing constraints of the multiplexed LED displays. In each time slot, the display state machine advances and one or more other real-time functions are performed. Periodic real-time functions execute completely in their time slot. Those that are aperiodic or consume too much time for a slot are divided into state machines. The fixed utilization schedule simplifies timing analysis to guarantee critical real-time task completion. AI functions, such as finding satellites and sub-carriers, execute in the foreground. They execute to completion in whatever time is not used by the ISRs. The system is so efficient that users never perceive any hesitation.

The code example here is the foreground process for finding satellites. Almost nothing is assumed. The program first tries to drive the azimuth and elevation motors to their limits, which it will subsequently use in its search. If the elevation motor seems to fail, it does a single-axis search. If the azimuth motor fails, it aborts and reports an error. It then points the dish straight up and begins moving it in an expanding circle until it finds a satellite radio signal. It quickly finds a satellite's precise location using three levels of progressively finer resolution. This effectively performs global optimization first so that no time is wasted. Instead of wasting time hunting for each next satellite, it moves the dish immediately to the location predicted by extrapolating from the positions of satellites that it has already found. At that location, it performs another two-phase search, expanding circular followed by progressive refinement.

"6803"
;X500SET--FORGROUND MODULE. SATELLITE SEEKING FUNCTIONS 
;-------------------------------------------------------------------------------------------
                INCLUDE X500ADDR
; 
                GLOBAL SETUP,GETNEWSAT
; 
                EXT INKEY,DIV1616,OUTSTAT,PREKEY,WAITNEXT,MEMDEL,WAITQK,DEL1MS                  ;X500SYS
;               EXT NOISEREAD 
                EXT WADIS3,READADC,DISSAT,DISPCORE,STATMESS;DEL300                              ;X500SYS
                EXT ADJVOL,LOWCHAN,LOWCHANX,RDNOISE                                             ;X500CH 
                EXT FINDSAT,AZSRCH,NEARPRO,FINDSAT2,SATREQ                                      ;X500SRCH 
                EXT NEWCODE                                                                     ;X500PAR
                EXT GOTOPOS,XCSEEK,POSMOT,FULLSET,HOMER                                         ;X500INIT 
; 
MANDN           EQU 16H         ;MANUAL DISH DOWN KEY 
MANUP           EQU 12H 
MANW            EQU 0EH 
MANE            EQU 0AH         ;THESE ARE USED WITH SETUP TO REQUEST SEEKING AT VARIOUS LEVELS 
SYSCHK          EQU 1EH         ;LOCAL KEY USED TO SELECT AFC DEFEAT FLAG TOGGLE FROM SETUP-SETUP 
ALTH            EQU 8           ;THRESHOLD FOR INITIAL Q AT A POSITION TO SUPPORT DISH REALIGNMENT
WATH            EQU 8           ;WIDE AREA THRESHOLD FOR INITIAL SEEK2
ACSSTH          EQU 6*8         ;AC SIGNAL SENSE THRESHOLD FOR VALID SATELLITE (TAKEN 8 TIMES FOR AVERAGE)
SSSAM           EQU 14          ;NUMBER OF SS SAMPLES TAKEN FOR AC SS SAT RECOGNITION 
;+++++++++++++++THRESHOLD FOR OPTIONAL ZERO-IN RESCINDER+++++++++++++++++++++++++++++++++++++++++ 
;ZITH            EQU 5           ;ZERO-IN THRESHOLD " " 
;AWFUL           EQU 0E0H        ;NOISE THRESHOLD FOR REJECTING ZERO-IN 
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
FINTH           EQU 16          ;FINAL THRESHOLD FOR IMPROVING CHANNEL Q
L1SAMP          EQU 240         ;DIVIDER FOR SEEK LEVEL ONE AZ RESOLUTION 
; 
;---------------------------------------------------------------------------------------------- 
;AM_PA          FDB S16g.AN.S16h.AN.S16a.AN.S16b.AN.S16c.AN.S16u.AN.S16p
;               FCB SSa.AN.SSb.AN.SSc.AN.SSf.AN.SSg.AN.SSe
;AM_NP          FDB S16g.AN.S16h.AN.S16k.AN.S16r.AN.S16d.AN.S16c
;               FCB SSa.AN.SSb.AN.SSf.AN.SSg.AN.SSe 
NAM_DSH         FDB S16u.AN.S16p
                FCB SSg 
NAM_QP          FDB S16g.AN.S16h.AN.S16a.AN.S16r.AN.S16d.AN.S16c.AN.S16b.AN.S16f.AN.S16e
                FCB SSa.AN.SSb.AN.SSf.AN.SSg.AN.SSe 
; 
SEEK3TAB        FCB 0,2         ;VECTOR 13: dAZ=0,  dEL=+2
                FCB 84H,0       ;VECTOR 12: dAZ=-4, dEL=0 
                FCB 84H,0       ;VECTOR 11: dAZ=-4, dEL=0 
                FCB 84H,0       ;VECTOR 10: dAZ=-4, dEL=0 
                FCB 82H,2       ;VECTOR 9 : dAZ=-2, dEL=+2
                FCB 82H,2       ;VECTOR 8 : dAZ=-2, dEL=+2
                FCB 82H,2       ;VECTOR 7 : dAZ=-2, dEL=+2
                FCB 0,0         ;VECTOR 6 : dAZ=0,  dEL=0 
                FCB 0,0         ;VECTOR 5 : dAZ=0,  dEL=0 
                FCB 82H,82H     ;VECTOR 4 : dAZ=-2, dEL=-2
                FCB 82H,82H     ;VECTOR 3 : dAZ=-2, dEL=-2
                FCB 84H,0       ;VECTOR 2 : dAZ=-4, dEL=0 
                FCB 82H,2       ;VECTOR 1 : dAZ=-2, dEL=+2
; 
;THE FOLLOWING VECTOR TABLE IS USED BY SETUP-SETUP TO SELECT TESTING FUNCTIONS INSTEAD OF HOMING
TESTTAB         FDB AFCTOG      ;TOGGLE DEFEAT AFC
                FDB VNRTOG      ;TOGGLE VNR 
                FDB CLAMPAGC
; 
;***************SETUP COMMAND DECODING********************************* 
SETUP           JSR PREKEY      ;WAIT FOR KEY1 INACTIVE OR KEY2 ASSERT
                BNE CHKK2       ;IF RETURN IS KEY2 ACTIVE THEN GO CHECK ITS VALUE 
                JSR WAITQK      ;WAIT FOR QUICK (.5SEC) NEXT KEY
                BEQ SETUPEX     ;IF KEY1 NOT ACTIVE IN .5 SEC THEN EXIT 
                LDAA KEY1 
                CMPA #SETKEY
                BNE SETUPEX 
; 
;---------------OPERATOR HAS REQUESTED PARTIAL SETUP----------------------------------------
                JSR SATREQ      ;GIVE OPERATOR THE OPPORTUNITY TO RECOVER POSITION BASED ON SATELLITE 
                BEQ SATHOME     ;IF VALID INPUT THEN GO HOME ON SATELLITE 
                LDAA KEY1 
                CMPA #SYSCHK
                BEQ UTILITY 
                JMP WADIS3      ;ABORT UNLESS OPERATOR NAMES A SATELLITE TO HOME ON 
;               JMP HOMER       ;IF NEITHER VALID SAT NAME NOR SYSCHK THEN HOME ON SWITCHES 
;-----------------------------------------------------------------------------------------------
;SETUP-SETUP-SYSCHK OPENS UP UTILITIES FOR TESTING THE RECEIVER 
UTILITY         JSR WAITNEXT    ;GET SPECIFIC FUNCTION REQUEST KEY
                BEQ PARTSET     ;IF NO FURTHER INPUT THEN ABORT 
;DECODE AND EXECUTE REQUESTED TESTING FUNCTION
                LDAB KEY1 
                SUBB #3         ;REDUCE KEY ARRAY TO BEGIN AT 0 
                CMPB #3         ;COMPARE TO HIGHEST TEST KEY+1
                BCC PARTSET     ;ABORT IF NON-FUNCTIONAL KEY
                LSLB            ;TIMES TWO FOR BYTES/VECTOR 
                LDX #TESTTAB
                ABX             ;POINT TO THE VECTOR FOR THE REQUESTED COMMAND
                LDX 0,X 
                JMP 0,X         ;JUMP TO COMMAND
; 
;SETUP-SETUP-CHANNEL UP SELECTS AFC DEFEAT TOGGLE 
AFCTOG          COM TESTATE 
PARTSET         JMP WADIS3
; 
;SETUP-SETUP-DISCRETE SELECTS VNR POSITION TOGGLE 
VNRTOG          LDAB SO2STAT    ;GET PRESENT STATUS 
                EORB #01000000B ;TOGGLE VNR 
; 
CHANGST         JSR OUTSTAT 
                BRA PARTSET 
; 
;AUDIO SEEK DOWN IS USED FOR TEMPORARY FUNCTION= TOGGLE CLAMP AGC TO TEST SAT SENSE 
CLAMPAGC        LDAB SO2STAT
                EORB #00100000B 
                BRA CHANGST 
; 
;---------------------------------------------------------------------------------------------- 
SATHOME         LDD AZOFF,X     ;GET THIS SATELLITE`S AZ
                STD MOTAZ 
                LDD AZOFF+2,X   ;GET ITS ELEVATION
                STD MOTEL 
SETUPEX         JMP WADIS3
; 
;---------------------------------------------------------------------------------------------- 
SEEK1V          JMP SEEK1 
SEEK2V          JMP SEEK2 
SEEK3V          JMP SEEK3 
SEEK4V          JMP SEEK4 
; 
CHKK2           LDAA KEY2 
                CMPA #MANDN 
                BEQ SEEK1V
                LDAB #DUMSAT
                STAB SATELLITE
                CMPA #MANUP 
                BEQ SEEK2V
                CMPA #MANW
                BEQ SEEK3V
                CMPA #MANE
                BEQ SEEK4V
                CMPA #MEMKEY
                BNE SETUPEX 
                JSR MEMDEL      ;WAIT FOR SIMULTANEOUS KEY1 AND MEMORY
                BNE SETUPEX     ;IF KEY2 IS NOT MEMORY WITHIN 2 SEC, THEN ABORT COMMAND 
;*************************************************************************
;OPERATOR HAS REQUESTED FULL SETUP. IN ADDITION TO PARTIAL ALSO ESTABLISH LIMIT SWITCH BACKOFF COUNTS 
;AND PROVIDE FOR PARENT CODE ENTRY. 
                LDAA #0FFH
                STAA WORSTQ     ;INITIALIZE HERE DUE TO CHANGES 2-1-86. SEE LINES 546,605 
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                JSR FULLSET 
                BCC GETFKEY 
                JSR SEEK3       ;DO INITIAL SEEK3 IF AZ AXIS IS OPERATIONAL 
GETFKEY         JSR PREKEY      ;WAIT FOR KEY 1 ACTIVITY TO END 
                LDX #NAM_DSH
                JMP DISPCORE    ;DISPLAY "--" 
;AITPAR         JSR INKEY 
;               BEQ WAITPAR     ;KEEP WAITING FOR FINAL KEY 
;               CMPA #PARKEY
;               BNE NOPARC
;               JMP NEWCODE     ;GO TO X500PAR TO ALLOW NEW PARENT CODE ENTRY 
;OPARC          LDX #NAM_NP     ;POINT TO NO PARENT DISPLAY 
;               JMP DISPCORE
; 
;***************SEEKING******************************************************************** 
;NOTE THAT THE FOLLOWING MOTOR SUB-PROCEDURE OPERATES ONLY ON AZ MOVEMENT 
XCMICRO         DEC FGTEMP16    ;ADVANCE SKIP COUNTER 
                BNE XCMEX 
                LDAA #L1SAMP
                STAA FGTEMP16   ;RE-INITITIALIZE SKIP COUNTER 
;+++++++++++++++OPTIONAL REPLACEMENT FOR 0 NOISE REJECT++++++++++++++++++++++++++++++++++ 
;               JSR NOISEREAD   ;RETURNS NOISE IN ACCA (MAY TRY 4 TIMES TO ELIMINATE <10H)
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
;               LDAB #ADCNOISE
;               JSR READADC 
;               CMPA #10
;               BCS XCMEX       ;REJECT SPURIOUS READINGS TOO LOW TO BE REAL
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
                JSR RDNOISE     ;RETURNS AVERAGE OF 8 READINGS IN ACCA
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
                CMPA FGTEMP11   ;COMPARE NOISE TO LOWEST FOUND SO FAR 
                BCC XCMEX       ;LEAVE PREVIOUS RECORD UNLESS PRESENT IS BETTER (LOWER) 
;TO RECORD BEST AZ, CONSIDER THAT MOTAZ= EXPECTED DESTINATION, FREQ= REMAINING COUNTS,
;AND DIRECTION IS DETERMINED BY EL STEP COUNT, WHERE ODD=EAST, EVEN=WEST
                STAA FGTEMP11   ;REPLACE OLD BEST NOISE FIGURE
                LDAA FGTEMP17   ;GET STEP EL COUNT
                RORA            ;TEST B0
                BCC BACKAZ      ;IF EVEN THEN AZ IS MOVING BACKWARDS
                LDD MOTAZ       ;GET EXPECTED DESTINATION 
                SUBD FREQ       ;COMPENSATE FOR REMAINING MOTOR COUNTS
                BRA SAVEAZ
BACKAZ          LDD MOTAZ 
                ADDD FREQ       ;IF GOING WEST THEN REAL AZ IS GREATER THEN MOTAZ 
SAVEAZ          STD FGTEMP12    ;RECORD AZ OF THIS BEST POSITION
                LDD MOTEL       ;GET EL OF THIS SWEEP 
                STD FGTEMP14    ;RECORD EL BEST 
XCMEX           JMP XCSEEK      ;EXIT THROUGH END COUNT OR KEY ABORT
; 
;******************************************************************************************** 
SCANCALC        JSR LOWCHAN     ;SET UP LOWEST NOISE CHANNEL AT THIS LOCATION 
QCALC           LDD OLDQ        ;GET COMPUTED Q OF THE PREVIOUS CHANNEL SCAN
;IF GREATER RESOLUTION IS NEEDED THEN USE OLDQ*4 AS DIVIDEND
                STD DEND        ;PASS DIVIDEND ARGUMENT 
;               LDAB FGTEMP26   ;RETRIEVE BEST CHANNEL`S SS 
                LDAB #80H       ;USE IMM VALUE UNTIL TO IGNORE SS FOR Q UNTIL HARDWARE
;IMPROVES. AS OF 2/1/86, NO SAT SS=0, WHICH MAKES ZEROING-IN IMPOSSIBLE TO CONTROL
                LDAA #1         ;PRECOMPENSATE TO AVOID UNDERFLOW 
                CLR FGTEMP30    ;INITIALIZE HIGH BYTE FOR COMPUTATION 
                SUBD FGTEMP30   ;SUBTRACT NOISE FIGURE OF THIS CHANNEL
                STD OLDQ        ;AFTER COMPUTING NEW Q, STORE IT IN OLD FOR NEXT COMPARE
                SUBD DEND       ;NEWQ-OLDQ= IMPROVEMENT 
                BLS NOBETTER    ;IF Z=1 OR C=1 THEN NO IMPROVEMENT
                JSR DIV1616     ;%dQ=OLDQ/(NEWQ-OLDQ) 
                LDD QUO         ;RETRIEVE %dQ, IGNORING REMAINDER--ALSO V<--0 
                TSTA
                BEQ CALCRET     ;IF HIGH BYTE=0 THEN LEAVE LOW AS THE ANSWER
                LDAB #0FFH      ;ELSE TRUNCATE TO MINIMUM IMPROVEMENT 
CALCRET         RTS             ;%dQ IN ACCB, WHERE FF=MINIMUM IMPROVEMENT, 00=MAX
NOBETTER        LDD OLDQ        ;RETRIEVE NEW Q WHICH WAS SAVED IN OLDQ REGISTER
                STD WORSTQ      ;SAVE IT FOR NEW REFERENCE, SINCE IT WAS LOWER THAN WORSTQ
                SEV             ;FLAG RESULT
                RTS 
; 
;-------------------------------------------------------------------------------------------
MOVERRV         JMP MOVERR      ;VECTOR TO SUPPORT LONG BRANCH
; 
;SATELLITE UP/DOWN JUMP INTO THIS POINT AFTER LOCATING SATELLITE (@X). U/D REQUESTS NO NAME 
;COMPARE OF AZSRCH BY FGTEMP11=FF 
GETNEWSAT       CLR FGTEMP22    ;DEFAULT FLAG INITIAL Q GOOD ENOUGH TO REALIGN DISH POSITION IN MEMORY
                LDD AZOFF,X 
                STD FGTEMP7     ;PASS DESTINATION AZ TO POSITIONER
                LDD AZOFF+2,X 
                STD FGTEMP9     ;PASS DESTINATION EL TO POSITIONER
                LDAB #78
                JSR ADJVOL      ;MUTE WHILE MOVING TO A NEW SATELLITE 
                JSR GOTOPOS 
                BCS MOVERRV     ;ABORT IF MOTOR FAILURE 
;+++++++++++++++OPTION TO SKIP POSITION UPDATE++++++++++++++++++++++++++++++++++++++++++++++++++
                LDAA FGTEMP11   ;GET REQUESTED SATELLITE NAME 
                CMPA SATELLITE  ;IS THIS A REPEAT REQUEST?
                BEQ TRUEUP      ;REPEAT REQUESTS GET LEVEL ONE SEEK 
                JMP FINDSAT     ;IF A NEW ONE THEN JUST FIND AND DISPLAY
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
TRUEUP          LDD #NEARPRO    ;POINT TO NEAREST AZ PROCEDURE
                JSR AZSRCH      ;FIND NEAREST SATELLITE 
                STX SATPNTR 
MICROHUNT       LDD WORSTQ
                STD OLDQ        ;SCANCALC WILL COMPARE NEW Q TO WORST, IE RELATIVE TO BACKGROUND
                BSR SCANCALC    ;FIND LOWEST NOISE CHANNEL (AND CALC %dQ FOR INITIAL SEEK2) 
                BVS NOALIGN     ;IF NOT BETTER THAN BACKGROUND THEN DON`T REALIGN DISH
                CMPB #ALTH      ;Q MUST BE BETTER THAN BACKGROUND BY THIS THRESHOLD 
                BCS REFEROK     ;IF REFERENCE QUALITY IS GOOD THEN MICRO SEEK IS UNRESTRICTED 
NOALIGN         LDAA FGSEMA 
                BITA #00010000B ;IS SAT SEEK IN PROGRESS? 
                BNE REFEROK     ;MICRO SEEK IS STILL ALLOWED IF SEEK, BECAUSE DISH IS NOT REALIGNED ANYWAY
;SINCE SEEK IS NOT IN PROGRESS THIS PROGRAM IS BEING EXECUTED TO FINE TUNE A REQUESTED SATELLITE. BUT 
;THE INITIAL Q IS TOO LOW TO RELIABLY REALIGN THE RECORDED DISH POSITION
                LDX #NAM_QP 
                JSR STATMESS    ;INFORM USER THAT POOR QUALITY PRECLUDES DISH UPDATE
                COM FGTEMP22    ;FLAG THAT AZ AND EL SHOULD NOT BE CHANGED
REFEROK         LDAA FGSEMA     ;REPEAT TEST FOR SEEK BECAUSE SOME CALLERS ENTER HERE 
                ANDA #00010000B ;ISOLATE SEEK IN PROGRESS FLAG FOR PATCH SIZING 
                STAA FGTEMP30   ;PASS TO PATCH CALCULATIONS 
;               LDAA SYSTAT 
;               BITA #00010000B ;TEST SEEK 1 DISABLE FLAG 
;               BNE MOVERRV     ;RETURN MOVE ERROR IF SEEK1 DISABLED
                LDAA #0E0H
                STAA FGTEMP11   ;IF NO NOISE IS BETTER THAN THIS THEN RETURN TO INITIAL POSITION
                LDD AZDEG2
;+++++++++++++++FOR 4 DEGREE SMALL AZ PATCH USE THE FOLLOWING CODE++++++++++++++++++++++
;               TST FGTEMP30
;               BEQ PATOFF      ;LEAVE 2 DEGREE OFFSET FOR NON-SEEKING
;               LSLD            ;IF SEEKING THEN USE 4 DEGREE OFFSET--8 DEGREE PATCH
;+++++++++++++++FOR 2 DEGREE SMALL AZ PATCH USE THE FOLLOWING CODE++++++++++++++++++++++++++
                TST FGTEMP30    ;IF POS ALIGN THEN 0, IF SEEK THEN NZ 
                BNE PATOFF      ;IF SEEKING THEN USE 2 DEGREE OFFSET TO STARTING POINT
                LSRD            ;USE 1 DEGREE OFFSET FOR 2 DEGREE SMALL PATCH 
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
PATOFF          STD FGTEMP16    ;SET UP AZ PATCH OFFSET ARGUMENT
                LDD MOTAZ 
                STD FGTEMP12    ;INITIALIZE BEST AZ 
                SUBD FGTEMP16   ;SUBTRACT PATCH OFFSET
                STD FGTEMP7     ;SET UP MICRO PATCH STARTING AZ FOR SEEKING 
                LDD MOTEL 
                STD FGTEMP14    ;INITIALIZE BEST EL 
                SUBD ELDEG
;+++++++++++++++IF EL STEP SIZE IS 2 DEGREES THEN SUBTRACT ELDEG2 INSTEAD OF ELDEG++++++++++++
;+++++++++++++++IF EL STEP IS 1/2 DEGREE THEN FIRST SET UP 1/2 DEG IN TEMP THEN SUBTRACT+++++++ 
                STD FGTEMP9     ;SET UP MICRO PATCH STARTING EL 
                JSR GOTOPOS     ;POSITION DISH AT UPPER LEFT CORNER OF 1 DEG PATCH
                BCS MOVERRV     ;IF ANY MOTOR ABORT THEN ABANDON MOVES
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
;               JSR DEL300
;+++++++++++++++3 PASS EL PATCH++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
                LDAB #03        ;THREE PASSES IF 1 DEGREE PATCH 
;+++++++++++++++5 PASS EL PATCH+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;               LDAB #05        ;EL SCAN COUNTER ASSUMING EL OPERATIONAL
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
                LDAA SYSTAT 
                BITA #00100000B ;IS EL MOTOR OPERATIONAL? 
                BNE ELOK
                LDAB #1         ;USE ONLY ONE AZ SCAN IF NO EL
ELOK            LDAA #L1SAMP    ;SKIP COUNTER VALUE FOR AZ SCAN 
                STD FGTEMP16    ;INITIALIZE XC SKIP COUNTER AND EL SCAN COUNT 
MICLOOP         LDD AZDEG2
;+++++++++++++++IF SMALL PATCH AZ IS 4 DEGREE THEN USE AZDEG4 INSTEAD OF AZDEG2+++++++++++++++
                TST FGTEMP30
                BEQ PASSPAT     ;SKIP DOUBLING IF NOT SEEKING 
                LSLD            ;DOUBLE AZ SWING IF SEEKING 
PASSPAT         STD FGTEMP18    ;USE FOR TEMPORARY STORE OF AZ STEP SIZE
                LDAA FGTEMP17   ;RETRIEVE EL STEP COUNTER 
                RORA
                BCC EVENEL      ;GO SETUP AZ DIRECTION FOR EVEN STEPS 
;IF NEXT EL STEP COUNT IS ODD THEN AZ DIRECTION IS EAST, IE HIGHER COUNT
                LDD MOTAZ 
                ADDD FGTEMP18   ;CALCULATE NEXT AZ POSITION 
                BRA SETAZ 
EVENEL          LDD MOTAZ 
                SUBD FGTEMP18   ;CALCULATE NEXT AZ POSITION TOWARD HOME 
SETAZ           STD FGTEMP7     ;PASS AZ DESTINATION TO POSITIONER
                LDD MOTEL 
                STD FGTEMP9     ;ENSURE SAME EL EVEN WHEN PREVIOUS WAS OVERSHOOT
                LDD #XCMICRO    ;POINT TO MICRO SEEK EXIT PROCEDURE 
                JSR POSMOT
                BCS MOVERR
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
;               JSR DEL300
;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                LDD ELDEG 
;+++++++++++++++IF 2 DEGREE STEPS THEN USE ELDEG2 INSTEAD OF ELDEG++++++++++++++++++++++++++
;+++++++++++++++IF 1/2 DEGREE STEPS THEN LSRD++++++++++++++++++++++++++++++++++++++++++++ 
                ADDD MOTEL      ;STEP 1 (OR 1/2 OR 2) DEGREE OF EL ON EVERY PASS
                STD FGTEMP9 
                LDD MOTAZ 
                STD FGTEMP7     ;ENSURE SAME AZ ON EL STEPS 
                JSR GOTOPOS     ;STEP EL WITHOUT CHECKING NOISE 
                BCS MOVERR      ;ABORT ON ANY MOTOR FAILURE, INCLUDING KEY PRESSED
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
;               JSR DEL300
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
                DEC FGTEMP17    ;ADVANCE EL STEP COUNTER
                BNE MICLOOP 
; 
                LDD FGTEMP12
                STD FGTEMP7     ;TRANSFER BEST AZ TO POSITIONER 
                LDD FGTEMP14
                STD FGTEMP9     ;BEST EL
                JSR GOTOPOS     ;MOVE BACK TO THE BEST POSITION 
                BCS MOVERR
                LDX SATPNTR 
                LDAA FGSEMA 
                BITA #00010000B ;IS SAT SEEK IN PROGRESS? 
                BNE MICROEX     ;DON`T UPDATE POSITION WHILE TRYING TO ESTABLISH SAT POSITION 
                LDAA SATELLITE
                CMPA #DUMSAT
                BEQ ENDMICRO    ;DON`T UPDATE POSITION BASED ON DUMMY 
                TST FGTEMP22    ;WHEN FINE-TUNING, CHECK ON INITIAL Q FLAG
                BNE ENDMICRO    ;IF IT WASN`T GOOD ENOUGH THEN DON`T CHANGE AZ AND EL 
;NO NEED TO EXPLICITLY CHECK FOR DUMSAT BECAUSE IT CAN ONLY BE FOUND BY SEEKING--NOT BY NAME REQUEST
                LDD MOTAZ       ;GET COUNTER POSITION 
                STD AZOFF,X     ;UPDATE SAT POSITION IN DATA BASE 
                LDD MOTEL       ;GET EL COUNTER POSITION
                STD AZOFF+2,X   ;UPDATE SAT EL IN DATA BASE 
ENDMICRO        JMP FINDSAT2
MICROEX         CLR FGTEMP19    ;RETURN ARG= NOT ABORTED
                RTS 
; 
;-------------------------------------------------------------------------------------------
MOVERR          LDAA #0FFH
                STAA FGTEMP11   ;TELL AZSEARCH TO IGNORE NAME 
                STAA FGTEMP19   ;RETURN "SEEK ABORTED" TO SEEK3 
                LDAA SATELLITE
                CMPA #DUMSAT
                BEQ REDISDUM    ;REDISPLAY DUMMY AND THEN RETURN TO CALLER
                JMP FINDSAT 
; 
;***************SETUP AND DISH POSITIONING*************************************************** 
SETSEEK         LDAA FGSEMA 
                ORAA #00010000B 
                STAA FGSEMA     ;FLAG SEEK IN PROGRESS
                RTS 
; 
CLRSEEK         LDAA FGSEMA 
                ANDA #11101111B 
                STAA FGSEMA     ;FLAG SEEK NO LONGER IN PROGRESS
REDISDUM        JMP DISSAT      ;REDISPLAY NAME AND RETURN TO CALLER--MOVERR MAY JMP HERE 
; 
;-------------------------------------------------------------------------------------------- 
SEEK1           BSR SETSEEK 
                JSR MICROHUNT 
                BRA CLRSEEK 
; 
;---------------BEGIN SEEK LEVEL 2 PROCEDURES---------------------------------------------- 
STEPAZ          LDD MOTAZ 
                TST FGTEMP20    ;IF 0 THEN DIRECTION IS EAST, IE DEFAULT
                BNE AZWEST      ;GO PREPARE WESTWARD STEP 
                ADDD AZDEG4 
;+++++++++++++++USE AZDEG2 FOR SMALLER PATCH++++++++++++++++++++++++++++++++++++++++++++++
PASSAZ          STD FGTEMP7     ;STEP AZ ARGUMENT 
                LDD MOTEL 
                STD FGTEMP9     ;LEAVE EL CONSTANT FOR AZ STEP
                JSR GOTOPOS 
AZSTEX          RTS             ;IF C=1 THEN MOTOR ERROR
AZWEST          SUBD AZDEG4 
;+++++++++++++++USE AZDEG2 FOR SMALLER PATCH++++++++++++++++++++++++++++++++++++++++
                BCC PASSAZ      ;IF NOT UNDERFLOW OF AZ LOCATION THEN GO STEP IT
                BRA AZSTEX      ;RETURN C=1 IF POSITION ERROR 
; 
ELSTEPS         LDAB SYSTAT 
                BITB #00100000B ;IS EL MOTOR OPERATIONAL? 
                BEQ GOODELX     ;IF NOT THEN SKIP EL MOVES
                STAA FGTEMP28   ;INITIALIZE EL STEP COUNT 
ELLOOP          LDAA FGTEMP27   ;AZ STEP COUNT
                RORA            ;TEST O/E 
                BCC AZEVEN      ;IF AZ STEP IS EVEN THEN EL DIRECTION IS DOWN 
                LDD MOTEL 
                SUBD ELDEG2     ;ON ODD AZ STEPS EL DIRECTION IS UP 
                BRA STEPEL
AZEVEN          LDD MOTEL 
                ADDD ELDEG2 
STEPEL          STD FGTEMP9     ;PASS TO MOTOR POSITIONER 
                LDD MOTAZ 
                STD FGTEMP7     ;DON`T CHANGE AZ AT THIS TIME 
                JSR GOTOPOS 
                BCS ELSEX       ;RETURN C=1 TO STOP SCANNING IF POSITION ERROR
                JSR SCANCHAN    ;SCAN CHANNELS
                BCS ELSEX       ;IF SCAN STOP THEN RETURN WITH C=1
                DEC FGTEMP28    ;ADVANCE EL STEP COUNT
                BNE ELLOOP
GOODELX         CLC             ;RETURN ARGUMENT TO CONTINUE SCANNING 
ELSEX           RTS 
; 
;SEEK LEVEL 2 BEGINS HERE 
SEEK2           CLR FGTEMP20    ;DEFAULT DIRECTION IS EAST
                LDAA KEYSTAT
                BITA #00001000B 
                BNE SEEK2       ;WAIT FOR KEY2 ACTIVITY TO END
                JSR PREKEY      ;WAIT FOR KEY1 TO END OR KEY TO REACTIVATE
                BEQ BEGS2       ;IF KEY1 DISSERTS BEFORE ANOTHER KEY2 THEN LEAVE EAST DEFAULT 
;ASSUME THAT ANY KEY2 REACTIVATION INVOKES WEST SEEK WHETHER UP DISH OR NOT 
                COM FGTEMP20    ;FLAG WEST DIRECTION
BEGS2           CLR FGTEMP27    ;DUMMY AZ COUNT IS EVEN TO REQUEST DOWN EL
                LDAA #2         ;REQUEST 2 STEPS OF EL FOR INITIAL HALF SWING 
                BSR ELSTEPS 
                BCS MOVERRV3    ;IF ERROR THEN END SEEKING
                BSR STEPAZ      ;STEP AZ 2 DEGREES (WEST OR EAST) 
                BCS MOVERRV3
                LDAA #3 
                STAA FGTEMP27   ;INITIALIZE AZ STEP COUNT 
AZLOOP          BSR SCANCHAN
                BCS MOVERRV3
                LDAA #4         ;INITIAL EL STEP COUNT
                BSR ELSTEPS 
                BCS MOVERRV3    ;END SEEKING IF MOTOR FAILURE 
                JSR STEPAZ
                BCS MOVERRV3
                DEC FGTEMP27    ;ADVANCE AZ STEP COUNT
                BNE AZLOOP
                BSR SCANCHAN
                BCS MOVERRV3
                LDAA #2         ;REQUEST 2 STEPS OF EL FOR FINAL HALF SWING 
                BSR ELSTEPS 
                BCS MOVERRV3    ;IF ERROR THEN END SEEKING
                CLR FGTEMP19    ;RETURN STATUS SEEK IS NOT ABORTED
                RTS 
; 
MOVERRV3        JMP MOVERR
;---------------------------------------------------------------------------------------------
;+++++++++++++++OPTIONAL SUBROUTINE FOR READING AC SS FOR ONE 17MSEC PERIOD+++++++++++++++++
SSACRD          LDAA #SSSAM     ;NUMBER OF SAMPLES TO BE TAKEN
                STAA FGTEMP21   ;INITIALIZE LOOP COUNT
                CLRB
                STAB FGTEMP24   ;INITIALIZE HIGHEST SS
                COMB
                STAB FGTEMP30   ;INIT LOWEST SS FOR AC CALCULATIONS 
SSACL           JSR DEL1MS
                LDAB #ADCSIG
                JSR READADC 
                CMPA FGTEMP24   ;COMPARE TO HIGHEST READING SO FAR
                BCS NOTHIGH     ;DON`T REPLACE HIGHEST
                STAA FGTEMP24   ;UNLESS THIS READING IS HIGHER
NOTHIGH         CMPA FGTEMP30   ;COMPARE TO LOWEST
                BCC NOTLOW      ;LEAVE LOWEST 
                STAA FGTEMP30   ;UNLESS THIS ONE IS LOWER 
NOTLOW          DEC FGTEMP21
                BNE SSACL 
                LDAA FGTEMP24   ;RECOVER HIGHEST SS READING 
                SUBA FGTEMP30   ;SUBTRACT LOWEST READING
                RTS 
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
; 
SCANCHAN        TST SEEKAUTO    ;INIT TO 0FFH IN X500INIT, CLEARED BY NAMING Z0 
                BEQ NOTAUTO     ;IF NOT AUTO SEEKING THEN SIMPLY DO LOWCHAN 
                LDD WORSTQ
                STD OLDQ        ;SET UP TO COMPARE NEW Q TO WORST INSTEAD OF PREVIOUS 
                JSR SCANCALC    ;SCAN CHANNELS, COMPUTE NEW Q AND RETURN %dQ RELATIVE TO BACKGROUND 
                BVS SCANCONT    ;RETURN TO CONTINUE SCANNING IF NEWQ EVEN WORSE 
                CMPB #WATH      ;COMPARE %dQ TO REFERENCE. FF<1%, 00=100% 
                BCC SCANCONT    ;IF NOT ENOUGH BETTER TO POSSIBLY BE A SAT THEN RETURN C=0 TO CONTINUE
;THE Q AT THIS POSITION REPRESENTS A SUBSTANTIAL IMPROVEMENT OVER BACKGROUND, SO INVESTIGATE FURTHER
                JSR SEEK1       ;MOVE CLOSE ENOUGH TO GET VALID FINAL ZERO TEST RESULTS 
;+++++++OPTIONAL AVERAGE SEVERAL AC SS READINGS AND COMPARE TO THRESHOLD FOR BACKOUT++++++++++++++++++
                LDAA #8         ;NUMBER OF AC SS READINGS TO BE TAKEN 
                STAA FGTEMP26   ;INITIALIZE SAMPLE COUNT
                CLRA
                CLRB
SSLOOP          STD SSACC       ;REUSE THIS DOUBLE FOR SS TOTAL 
                BSR SSACRD
                TAB 
                CLRA
                ADDD SSACC
                DEC FGTEMP26
                BNE SSLOOP
                SUBD #ACSSTH    ;COMPARE TOTAL OF 8 READINGS TO THRESHOLD 
                BCS SCANCONT    ;IF AC SS NOT ADEQUATE THEN BACK OUT AND CONTINUE SEARCHING 
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
;this is the    BSR CMPTOWQ     ;NOW CHECK Q AT CLOSER-IN LOCATION
;version in     BVS SCANCONT
;use as of      CMPB #ZITH      ;THIS SHOULD REPRESENT A Q CLOSE TO GOOD SATELLITE
;10/15/85       BCC SCANCONT    ;IF NOT THEN CONTINUE HIGHER LEVEL SEEKING
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
                JSR SEEK1       ;FIND BEST CHANNEL FOR FURTHER SEEKING AND ESTABLISH REFERENCE Q
ZEROLOOP        TST FGTEMP19    ;IF MOVE ERROR (MOTOR OR USER) THEN FGTEMP19=FF 
                BNE SCANSTOP    ;RETURN, INDICATING NOT CONTINUE IN RESPONSE TO MOVE ERROR
;REPEAT SEARCH ON THE SAME CHANNEL
                JSR SETSEEK     ;FLAG SEEK IN PROGRESS TO DIRECT MICROHUNT
                JSR REFEROK     ;DO SEEK1, SKIPPING CHANNEL SCANNING
                JSR CLRSEEK     ;FLAG SEEK NO LONGER IN PROGRESS
                LDAB #ADCSIG
                JSR READADC     ;RETURNS SS IN ACCA 
                STAA FGTEMP26   ;PASS TO Q CALCULATOR 
                JSR RDNOISE     ;RETURNS AVERAGE OF 8 READINGS IN ACCA
                STAA FGTEMP31   ;PASS NOISE TO QCALC
                TST FGTEMP19    ;DID MOTOR OPERATE OK IN MICROHUNT? 
                BNE MOVERRV2
                JSR QCALC 
                BVS SCANSTOP    ;STOP ZEROING IN IF CONTINUED SEEKING AFFORDS NO IMPROVEMENT
                CMPB #FINTH     ;COMPARE %dQ TO THRESHOLD FOR FINAL ZEROING-IN
                BCS ZEROLOOP    ;IF THIS SEEK YIELDS SUBSTANTIAL IMPROVEMENT THEN REPEAT
SCANSTOP        SEC             ;RETURN ARGUMENT= STOP SCANNING 
SCANCHEX        RTS 
NOTAUTO         JSR LOWCHAN 
SCANCONT        CLC             ;FLAG TO CONTINUE SCANNING
                RTS 
; 
MOVERRV2        JMP MOVERR
; 
;******************************************************************************************** 
SEEK3           LDAA #13
                STAA FGTEMP29   ;INITIALIZE SCAN COUNTER
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
;               LDAA #0FFH
;               STAA WORSTQ     ;REINITIALIZE TO VERY GOOD AT EVERY L3 TO REDUCE PROMISCUITY
;THIS HAS NOW BEEN PLACED AT FULL SYS INIT AND L4 BECAUSE LESS OFTEN CAN NOW BE SUPPORTED 
;DUE TO CHANGES OF 2-1-86. SEE LINES 140 AND 605
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
L3LOOP          JSR SEEK2 
                LDAA FGTEMP19   ;TEST FOR STILL 0 
                BNE SEEKDONE
                LDX #SEEK3TAB-2 ;POINT TO BASE OF OFFSET TABLE (-2 BECAUSE SCAN0 DOESN`T EXECUTE) 
                LDAB FGTEMP29   ;GET SCAN COUNT 
                LSLB            ;TIMES TWO FOR TWO BYTES/SCAN 
                ABX             ;POINT TO AZ OFFSET TO NEXT SCAN BEGIN
                LDD MOTAZ 
                STD FGTEMP12    ;PASS PRESENT AZ POSITION TO CALCULATOR 
                LDD AZDEG 
                LSLD
                LSLD
                LSLD            ;CALCULATE 8 DEGREES FOR AZ PATCH SIZE
                BSR POSCALC 
                STD FGTEMP7     ;PASS NEXT AZ TO POSITIONER 
                INX             ;POINT TO EL OFFSET FOR NEXT SCAN 
                LDD MOTEL 
                STD FGTEMP12    ;PASS PRESENT EL TO CALCULATOR
                LDD ELDEG 
                LSLD
                LSLD            ;CALCULATE 4 EL DEGREES EL PATCH SIZE 
                BSR POSCALC 
                STD FGTEMP9     ;PASS NEXT EL TO POSITIONER 
                JSR GOTOPOS 
                BCS MOVERRV2    ;EXIT IF MOTOR ERROR
                DEC FGTEMP29    ;NEXT SCAN
                BNE L3LOOP
SEEKDONE        RTS 
; 
POSCALC         PSHA
                LDAA 0,X        ;GET THE VECTOR MULTIPLIER FOR THIS AXIS ON THIS SCAN 
                ANDA #0FH       ;REMOVE DIRECTION FLAG B7 
                CMPA #1         ;ONLY 0,1,AND 2 ARE USED AS MULTIPLIERS 
                BCS ZEROFF      ;IF <1, IE 0, THEN NO OFFSET THIS TIME
                BEQ ONEOFF      ;IF OFFSET IS ONE PATCH SIZE THEN GO RECOVER ACCA 
                PULA            ;RECOVER HIGH BYTE OF PATCH SIZE ARGUMENT 
                LSLD            ;THIS TIME USE 2*PATCH SIZE 
CALCDIR         TST 0,X         ;TEST DIRECTION FLAG B7 
                BPL COMPNEW     ;GO COMPUTE NEW AXIS POSITION IF POSITIVE OFFSET
                COMA
                COMB
                ADDD #1         ;COMPUTE 2`S COMP FOR NEGATIVE OFFSET 
COMPNEW         ADDD FGTEMP12   ;COMPUTE THE NEW AXIS LOCATION
                RTS 
; 
ZEROFF          PULA            ;ADJUST STACK 
                CLRA
                CLRB
                BRA COMPNEW 
ONEOFF          PULA            ;RECOVER HIGH BYTE OF PATCH SIZE
                BRA CALCDIR 
; 
;********************************************************************************************** 
SEEK4           LDAA #0FFH
                STAA WORSTQ     ;THIS PROVIDES A MEANS TO REESTABLISH A STANDARD FOR QP 
;(AND, LESS IMPORTANTLY, SEEKING) WITHOUT HAVING TO REINITIALIZE THE ENTIRE DATA BASE 
;THIS HAS BEEN ALLOWED BY CHANGES 2-1-86 (SEE LINES 140, 546) 
;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                LDAA FGSEMA 
                EORA #00000100B 
                STAA FGSEMA     ;TOGGLE AUTO SCAN FLAG
                JMP WADIS3


Z8 PORT MORPHING DRAM INTERFACE

Z8+ASM+uniqueI/O printer buffer


;Z8 printer buffer
;
CONFIGA: EQU 50H 
;RECONSTRUCTED "A" SWITCHES, WHERE  b0 SELECTS TEST MODE IF 0
;
CONFIGB: EQU 51H 
;RECONSTRUCTED "B" SWITCHES, WHERE b0-2 SELECTS INPUT BAUD AND
;b4-6 SELECTS OUTPUT: 0h=19200, 1h=9600, 2h=4800, 3h=2400, 
;4h=1200, 5h=600, 6h=300, 7h=150 AND b3 SELECTS INPUT TYPE, b7 
;SELECTS OUTPUT TYPE--0=SERIAL, 1=PARALLEL
;
ERRFLG: EQU 5FH ;SUBROUTINE STATE LINKER
;b0 SET IN RX IF ADVANCING HEAD OVERTAKES TAIL. CHECKED AND
;RESET BY REFRESHER
;b1 SET IN WDI (WHICH HANDLES TX) IF ADVANCING TAIL OVERTAKES
;HEAD. THIS IS CLEARED WHENEVER RX ADVANCES HEAD POINTER
;b2 SET IN WDI IF REFAV FALLS BELOW REFCNT. CLEARED BY REFR
;
TXJMP: EQU 52H ;RR52 USED AS INDIRECT JUMP
RXJMP: EQU 54H ;RR54 JUMP
;
HEAD: EQU 10H ;RR10 POINTER TO QUEUE FOR INPUT 
TAIL: EQU 12H ;RR12 POINTER TO OUTPUT
REFCNT: EQU 1FH ;REFRESH COUNTER
REFAV: EQU 14H ;RR14
;REFRESH PERIODS AVAILABLE IS REINITIALIZED TO 680d (2A8h) AT
;THE BEGINNING OF EVERY 4MSEC CYCLE
;
P3MLAT: EQU 01010101B ;CONTROL BYTE TO READ DRAMS
P3MULAT: EQU 01010001B ;UNLATCH P0 FOR OUTPUT
P01REF: EQU 65H ;MODE CONTROL FOR REFRESHING
P01RD: EQU 75H ;MODE FOR READING DRAMS
;USE 75H ALSO FOR READ AND WRITE PARALLEL I/O
P01WR: EQU 34H ;MODE TO WRITE DRAMS, READ SWITCHES
;
;INTERRUPTS: 0 AND 4 ARE IGNORED (TX DONE IS POLLED). 2 MAY BE
;USED FOR FUTURE TEST OR HOST CONTROL
IRQ0:   DW BEGIN
IRQ1:   DW RXPARA ;PARALLEL HOST INPUT IRQ
IRQ2:   DW BEGIN
IRQ3:   DW RXSER ;SERIAL HOST INPUT
IRQ4:   DW BEGIN
IRQ5:   DW WDI ;WATCHDOG OCCURS EVERY 416.6 USEC
;WDI UPDATES REFAV AND POLLS PRINTER READY/DONE. FUTURE SYN-
;THESIZED SERIAL TX WILL USE TO SEND BITS AT UP TO 2400 BAUD
;
BEGIN:  
    DI
    LD 2,#80H ;P2=1XX00XX0
    SRP 0F0H
    LD R1,#0F0H ;ENABLE CLK OUTPUT
    LD R2,#80H ;T1 USED FOR 2400 BAUD WDI
    LD R3,#00001111B ;PRE1 SCALE=3, INTERNAL, CONTINUOUS MODE
    LD R6,#46H ;P2M
    LD R7,#P3MULAT ;PREPARE TO DRIVE SWITCH MATRIX
    LD R8,#P01WR
    LD R9,#29H ;IPR=XX-1-01-0-0-1
    LD R10,#10H ;IRQ
;IRQ IS INITIALIZED WITH TX DONE TO ENABLE FIRST SERIAL TX 
    LD R11,#10100000B ;IMR
    LD R15,#7FH ;SPL (INTERNAL)
;
    CLR HEAD
    CLR HEAD+1
    CLR TAIL
    CLR TAIL+1
    CLR REFCNT ;REFRESH COUNTER
    LD 16H,#44H ;DUMMY EXTERNAL ADH
;
    SRP 50H
    LD R15,#00000010B ;INIT ERRFLG TAIL=HEAD
    LD R2,#NORTX/256
    LD R3,#NORTX\256
    LD R4,#NORRX/256
    LD R5,#NORRX\256
;NOW READ CONFIG. SWITCHES BY STEPPING A 0 THROUGH P0
    LD R13,#0FEH ;OUTPUT MASK AND COUNTER
    LD R0,#0FFH ;INITIALIZE DESTINATION "A"
    LD R1,#0FFH ;DESTINATION "B"
SWLP:   
    LD 0,R13 ;DRIVE ONE BIT LOW ON P0
    TM 2,#00000100B ;TEST "A" FOR 0
    JR NZ,TSTB ;LEAVE DEFAULT BIT=1
    AND R0,R13 ;REVERSE BIT TO 0
TSTB:   
    TM 2,#00000010B ;TEST "B"
    JR NZ,SWLPEND ;LEAVE DEFAULT 1
    AND R1,R13 ;OR CHANGE TO 0
SWLPEND:
    RL R13 ;SHIFT MASK/COUNTER
    JR C,SWLP ;LOOP ENDS WHEN 0 SHIFTS INTO C
;
;SET UP SERIAL TIMER--FOR NOW ASSUME THAT ONLY ONE I/O MAY BE
;SERIAL AT A TIME
    LD R13,CONFIGB ;MOVE BAUD TO TEMP 
    TM CONFIGB,#00001000B ;INPUT TYPE, 0=SER
    JR Z,SERIN ;LEAVE LOW NIBBLE IF SER IN
;SET UP PARALLEL INPUT CONTROLS
    OR IMR,#10000010B ;ENABLE IRQ1
    SWAP R13 ;SET UP OUTPUT BAUD CONFIG
    JR SETBAUD ;ASSUME SERIAL OUT
;BAUD CAN BE SET EVEN IF I/O ARE BOTH SERIAL BECAUSE OUTPUT
;IS NOT INTERRUPT DRIVEN BUT POLLED
SERIN:  
    OR IMR,#10001000B ;ENABLE SERIAL RX IRQ
    OR 2,#00100000B ;SIGNAL NOT RDY TO HOST
SETBAUD: 
    AND R13,#00000111B ;REMOVE ALL BUT BAUD
    LD R12,#10000000B ;INITIALIZE RATE
    INC R13 ;ADJUST FOR ALGORITHM
BAUDM:  
    RL R12 ;RATE *2. INITIALLY 1H
    DJNZ R13,BAUDM
    LD T0,R12
;19200=1,9600=2,4800=4,2400=8,1200=16,600=32,300=64,150=128
    LD PRE0,#00001101B ;SCALE=3, CONT.MODE
    OR TMR,#00000011B ;LOAD, START T0    
;
;SELECT NORMAL OR TEST MODE BEFORE ENTERING REFRESH LOOP  
    TM CONFIGA,#1 ;SET Z IF TEST MODE
    JR NZ,REFINIT
;TEST MODE MAY BE AUGMENTED TO TEST CPU, RAM, INTERFACE, ETC.
    LD TAIL,#TSTPAT/256
    LD TAIL+1,#TSTPAT\256
    LD TXJMP,#TSTTX/256
    LD TXJMP+1,#TSTTX\256
;
REFINIT:
    LD P01M,#P01REF
    SRP 10H
;NOW DO ONE COMPLETE REFRESH CYCLE TO SATISFY DRAMS
INIREF: 
    LD 1,R15 ;REFRESH 
    DJNZ R15,INIREF
    OR TMR,#00001100B ;LOAD/START WATCHDOG
;WDI PERIOD=417 USEC, IE SAME AS 2400 BAUD
    SRP 10H
REFLOOP:
    LD R4,#2
    LD R5,#0A8H
;INITIALIZE RR14H TO THE NUMBER OF REFRESHES POSSIBLE IN 4MSEC
;MINUS ONE WDI PERIOD
    TM ERRFLG,#1
    JR NZ,CLRERR ;LEAVE RX CONTROLS
;IF DURING THE PREVIOUS 4MSEC CYCLE THE HEAD WRAPPED AROUND
;THE QUEUE AND CAUGHT UP WITH THE TAIL, IE SKIP RX ON NEXT
;CYCLE. IF NO CRASH PREVIOUSLY THEN ENABLE RX
    TM CONFIGB,#00001000B ;TEST INPUT TYPE S/P
    JR NZ,ENPRX ;GO ENABLE PARALL. RX
    OR IMR,#10001000B ;EN SER. IRQ
    AND 2,#11011111B ;ASSERT RDY
    JR CLRERR
ENPRX:  
    OR IMR,#10000010B ;ENABLE PARALL. IRQ
    OR 2,#00100000B ;SIGNAL BUSY!
;PARALLEL IS PLACED HERE SO THAT BUSY! TO EI DELAY IS LESS   
CLRERR: 
    AND ERRFLG,#00000010B ;CLEAR ALL BUT TAIL OVRN
    EI
REFRESH:
    LD 1,R15 ;OUTPUT ROW ADDRESS
    DJNZ R15,REFRESH
    DI
    JR REFLOOP
;
RXPARA: 
    LD P01M,#P01RD ;CONFIG FOR PARA
    LDC R14,@RR6
;READ PARALLEL INPUT MUST USE ADDRESS IN EXTERNAL RANGE IN
;ORDER TO ISSUE DS. REG 16H IS DUMMY ADDRESS 
    LD P01M,#P01WR
    LD P3M,#P3MULAT
    LD 0,R14 ;OUTPUT DATA ON P0
    LDE @RR0,R0 ;WRITE TO @HEAD 
    LD P01M,#P01REF ;RESTORE REFR.MODE
    AND ERRFLG,#1 ;CLEAR TAIL OVR HEAD
    INCW HEAD
    CP R0,R2 ;COMPARE HEAD HIGH TO TAIL HIGH
    JR NE,PRXEND
    CP R1,R3 
;COMPARE HEAD TO TAIL LOW IF HIGH BYTES ARE EQUAL
    JR NE,PRXEND ;LEAVE BUSY!
;HEAD CRASHED INTO TAIL 
    AND 2,#11011111B ;ASSERT BUSY
    AND IMR,#11111101B
PRXEND: 
    IRET

See published article Popular Micro Configured For Direct DRAM Support

The purpose of this was to demonstrate the prowess of my Z8 in-circuit emulator, which was the first ICE to support micro-controllers without interfering with their I/O ports. In fact, I had chosen the Z8 to demonstrate my In-System Emulation concept because of its unprecedented port flexibility. The printer buffer was a deliberately extreme, but not pointless, example at the time. Today's computers and printers provide their own effective buffering.

The printer buffer accepts a serial or parallel data stream from a computer and doles it out to a serial or parallel printer. The implicit parallel-serial conversion function can be useful but the primary function is to enable the computer to rapidly send data to a slow printer. This requires a relatively large amount of RAM memory for buffering the data. DRAM could be cheaper than SRAM but its cost-per-bit advantage is eaten up by the higher cost of its complex interface. I wanted to show how the Z8's port flexibility could replace external circuitry, yielding the most cost-effective solution.

The Z8 and DRAM both use multiplexing to reduce pin count. The Z8 multiplexes data and address (low byte) on its external memory expansion port while DRAM multiplexes row address and column address. With minimal circuitry, as my article shows, we can connect these ports with correct voltages and timing but this would seem to serve no purpose. My firmware gives it purpose. It outputs the full DRAM address by writing the column address (as data) to the row address. This takes care of addressing the memory but leaves the Z8 with no natural data interface. Configuring another I/O port as latching input or output provides the data interface.

RXSER:  
    LD R14,0F0H ;GET SERIAL INPUT
    LD P01M,#P01WR
    LD P3M,#P3MULAT
    LD 0,R14 ;OUTPUT DATA ON P0
    LDE @RR0,R0 ;WRITE TO @HEAD 
    LD P01M,#P01REF ;RESTORE REFR.MODE
    AND ERRFLG,#1 ;CLEAR TAIL OVR HEAD
    INCW HEAD
    CP R0,R2 ;COMPARE HEAD HIGH TO TAIL HIGH
    JR NE,SRXEND
    CP R1,R3 
;COMPARE HEAD TO TAIL LOW IF HIGH BYTES ARE EQUAL
    JR NE,SRXEND ;LEAVE RDY
;HEAD CRASHED INTO TAIL     
    OR 2,#00100000B ;DISASSERT RDY
    AND IMR,#11110111B
SRXEND: 
    IRET
;
WDI:    
    TM ERRFLG,#00000100B ;CHECK REFR ERR
    JR NZ,OVFLEX
;EXIT IMMEDIATELY IF ALREADY NO TIME LEFT FOR I/O
;THIS WILL BE SOMEWHAT MODIFIED BY SYNTHETIC SERIAL TX
    SUB REFAV+1,#55H
    SBC REFAV,#0
;55H (85D) REFRESH SLOTS ARE LOST DURING EVERY WDI PERIOD
;EVERY TIME REFRESH BEGINS A NEW CYCLE, REFAV IS RESET TO 680d
;IF WDI FINDS REFAV FALLING BELOW REFRESH ADDRESSES LEFT THEN
;IT WILL STOP ALL FURTHER I/O PROCESSING. REFAV ACTUALLY LEADS
;REAL CYCLES LOST BY ONE FULL WDI PERIOD TO COMPENSATE FOR
;UNPREDICTABLE INPUT INTERRUPTS
    JR NZ,REFOK
;THERE IS NO NEED TO CHECK LOW COUNT IF REFAV HIGH IS 1 OR 2
    CP R5,R15 ;REFAV LOW-CURRENT REFRESH ADDR
    JR UGT,REFOK
;THERE IS NO MORE TIME IN THIS 4MSEC PERIOD FOR I/O
    OR ERRFLG,#00000100B
    TM CONFIGB,#00001000B ;IS INPUT S OR P?
    JR NZ,STPPIN ;GO STOP PARALLEL
    OR 2,#00100000B
    AND IMR,#11110111B
    IRET
    NOP
    NOP ;BUFFER
STPPIN: 
    AND 2,#11011111B
    AND IMR,#11111101B
OVFLEX: 
    IRET
    NOP
    NOP ;BUFFER
;
REFOK:  
    TM 2,#01000000B ;CHECK PRINTER RDY/BUSY
    JR NZ,OVFLEX
    JP @TXJMP
;
NORTX:  
    TM ERRFLG,#00000010B ;TAIL OVERRAN HEAD?
    JR NZ,STXEND ;SKIP TX UNLESS MORE INPUT
    TM CONFIGB,#10000000B
    JR NZ,PARAOUT ;GO TX PARALLEL
    TM IRQ,#00010000B
;TEMPORARILY USE AUTO SERIAL. SYNTHESIZED WON'T USE IRQ4
    JR Z,OVFLEX ;RETURN IF...
;PREVIOUS TX IS NOT THROUGH, ELSE GET NEXT FROM @TAIL
    AND IRQ,#11101111B ;CLEAR PREVIOUS DONE
    LD P3M,#P3MLAT
    LD P01M,#P01RD
    LDC @RR2,R2
    LD SIO,0 ;TX LATCHED DRAM DATA
    LD P3M,#P3MULAT
    LD P01M,#P01REF
    INCW TAIL
    CP R2,R0 ;COMPARE TAIL HIGH TO HEAD HIGH
    JR NE,STXEND
    CP R3,R1 
;COMPARE TAIL TO HEAD LOW IF HIGH BYTES ARE EQUAL
    JR NE,STXEND ;EXIT IF NO CRASH
;TAIL CRASHED INTO HEAD, IE PRINTER IS FASTER THAN HOST
    OR ERRFLG,#00000010B
STXEND: 
    IRET    
    NOP
    NOP ;BUFFER
;
PARAOUT:
    LD P3M,#P3MLAT
    LD P01M,#P01RD
    LDC @RR2,R2
    LD R14,0 ;RETRIEVE DATA
    LD P3M,#P3MULAT
;TESTS REVEALED THAT W/O THIS CHANGE TO P3M, MEMORY FAILED
    OR 2,#00010000B ;ENABLE PARA WRITE
    LDC @RR6,R14 ;ANY ADDR OK
    OR 2,#00001000B ;ASSERT STRB
    AND 2,#11100111B ;DISASSERT STROBE...
;AND DISABLE PARALLEL WRITE. STRB IS INVERTED BY A GATE
    LD P01M,#P01REF
    INCW TAIL
    CP R2,R0 ;COMPARE TAIL HIGH TO HEAD HIGH
    JR NE,PTXEND
    CP R3,R1 
;COMPARE TAIL TO HEAD LOW IF HIGH BYTES ARE EQUAL
    JR NE,PTXEND ;EXIT IF NO CRASH
;TAIL CRASHED INTO HEAD, IE PRINTER IS FASTER THAN HOST
    OR ERRFLG,#00000010B
PTXEND: 
    IRET    
    NOP
    NOP ;BUFFER
;
TSTTX:  
    TM CONFIGB,#10000000B
    JR NZ,TSTPTX ;GO TX PARALLEL
    TM IRQ,#00010000B
;TEMPORARILY USE AUTO SERIAL. SYNTHESIZED WON'T USE IRQ4
    JR Z,TSTEX ;RETURN IF...
;PREVIOUS TX IS NOT THROUGH, ELSE GET NEXT FROM @TAIL
    AND IRQ,#11101111B ;CLEAR PREVIOUS TX DONE
    LDC R14,@RR2 ;FETCH CHARACTER...
;FROM INTERNAL ROM STRING
    CP R14,#'$' ;IS IT TERMINATOR?
    JR EQ,TSTEND
    LD 0F0H,R14 ;MOVE IT TO SERIAL REG.  
    INCW TAIL
TSTEX:  
    IRET    
    NOP
    NOP ;BUFFER
;
TSTPTX: 
    LDC R14,@RR2 ;FETCH CHARACTER
    CP R14,#'$'
    JR EQ,TSTEND
    LD P01M,#P01RD
    OR 2,#00010000B ;ENABLE PARA WRITE
    LDC @RR0,R14 ;ANY ADDR OK
    OR 2,#00001000B ;ASSERT STRB
    AND 2,#11100111B ;DISASSERT STROBE...
;AND DISABLE PARALLEL WRITE. STRB IS INVERTED BY A GATE
    LD P01M,#P01REF
    INCW TAIL
    IRET
    NOP
    NOP ;BUFFER 
;
TSTEND: 
    JP BEGIN ;ENDLESS LOOP
;
NORRX:
TSTRX:
;
    DB 255,255,255,255,255,255,255,255,255,255,255,255,255
;
TSTPAT: DB 'THIS TEST DETERMINES WHETHER THE PRINT BUFFER, AS'
    DB ' CONFIGURED, CAN TALK TO THE PRINTER',13,10,'$'



Z8 IN-SITU EMULATOR

Z8+ASM+unique hardware/software interaction


;BREAK PROGRAM RESIDES IN CONTROLLER SPACE 9600H-97FFH, WHILE
;PB AND 8681 SEE IT AT 0200-03FFH, AND SL'S AT 1200-13FFH.
;SINCE A9 IS IGNORED, RESET AND IRQ VECTORS WILL WORK IN THIS
;PHYSICAL MEMORY SPACE
RSERWR: EQU 02D4H ;SERIAL WRITE SUBROUTINE RELOCATED ADDR  
BREAK:  ORG 9600H
    DB 0,15,0,15,0,15,0,15,0,15,0,15 ;IRQ VECTORS FOR 8681 OR PB
    JR BEGBRK ;PB OR 8681 RESET BEGIN
    ORG 0FH ;ALL TARGETS USE IRET @0FH
    IRET ;USED BY ALL POSSIBLE TARGETS
;FOR SL'S PUT IRET (#BF) AT 0, 3, 6, 9 ,C 
;
    ORG 9612H ;BREAK JP VECTORS HERE, 212H/1212H
;FIRST 3 BYTES ARE EITHER DI AND NOPS OR PART 2 OF RDXMEM
BEGBRK: 
    DI
    NOP
    NOP
;ALTERNATIVE IS JP XMRD (8D,03/13,D6)
;----------------------------
;SAVE FLAGS, V, S, Z, THEN DISABLE TIMERS AND SAVE C FLAG
    JR OV,WV1 ;GO WRITE V=1
    NOP ;BUFFER PREFETCH
    NOP ;WRITE V=0
TSTS:   
    JR MI,WS1 ;GO WRITE S=1
    NOP
    NOP ;WRITE S=0
TSTZ:   
    JR Z,WZ1 ;GO WRITE Z=1
    NOP
    NOP ;WRITE Z=0
STPT:   
    AND TMR,#0F5H ;DISABLE T0,T1
    JR C,WC1 ;GO WRITE C=1
    NOP
    JR SVR7A ;JUMP AROUND 1'S WRITE JUMPS
    NOP
WV1:    
    JR TSTS ;WRITE V=1
    NOP
WS1:    
    JR TSTZ ;WRITE S=1
    NOP
WZ1:    
    JR STPT ;WRITE Z=1
    NOP
WC1:   
     NOP ;WRITE C=1
;
;SAVE R7A TO FREE FOR LOOP COUNTER
SVR7A:  
    RR 7AH ;SET UP B0 FOR TESTING
    JR C,W7A01 ;GO WRITE B0=1
    NOP ;PRE-FETCH BUFFER
    NOP ;WRITE B0=0
R7A1:   
    RR 7AH
    JR C,W7A11 ;GO WRITE B1=1
    NOP
    NOP ;WRITE B1=0
R7A2:   
    RR 7AH
    JR C,W7A21
    NOP
    NOP ;WRITE B2=0
R7A3:   
    RR 7AH
    JR C,W7A31
    NOP
    NOP ;WRITE B3=0
R7A4:   
    RR 7AH
    JR C,W7A41
    NOP
    NOP ;WRITE B4=0
R7A5:   
    RR 7AH
    JR C,W7A51
    NOP
    NOP ;WRITE B5=0
R7A6:   
    RR 7AH
    JR C,W7A61
    NOP
    NOP ;WRITE B6=0
R7A7:   
    RR 7AH
    JR C,W7A71
    NOP
    JR SVR7B ;JUMP AROUND JUMP TABLE AND WRITE B7=0
    NOP
W7A01:  
    JR R7A1 ;WRITE B0=1
    NOP
W7A11:  
    JR R7A2 ;WRITE B1=1
    NOP
W7A21:  
    JR R7A3 ;WRITE B2=1
    NOP
W7A31:  
    JR R7A4 ;WRITE B3=1
    NOP
W7A41:  
    JR R7A5 ;WRITE B4=1
    NOP
W7A51:  
    JR R7A6 ;WRITE B5=1
    NOP
W7A61:  
    JR R7A7 ;WRITE B6=1
    NOP
W7A71:  
    NOP ;WRITE B7=1
;
SVR7B:  
    LD 7AH,#8 ;INIT LOOP COUNTER
R7BST:  
    RR 7BH
    JR C,W7B1 ;GO WRITE BIT=1
    NOP ;BUFFER
    NOP ;WRITE BIT=0
R7BLP:  
    DEC 7AH
    JR NZ,R7BST
    NOP
    JR SVR7C ;JUMP AROUND BIT=1 WRITE
    NOP
W7B1:   
    JR R7BLP ;WRITE BIT=1
;
SVR7C:  
    LD 7AH,#8
R7CST:  
    RR 7CH
    JR C,W7C1
    NOP
    NOP ;WRITE BIT=0
R7CLP:  
    DEC 7AH
    JR NZ,R7CST
    NOP
    JR SVR7D
    NOP
W7C1:   
    JR R7CLP ;WRITE BIT=1
;
SVR7D:  
    LD 7AH,#8
R7DST:  
    RR 7DH
    JR C,W7D1
    NOP
    NOP ;WRITE BIT=0
R7DLP:  
    DEC 7AH
    JR NZ,R7DST
    NOP
    JR SVR7E
    NOP
W7D1:   
    JR R7DLP ;WRITE BIT=1
;
SVR7E:  
    LD 7AH,#8
R7EST:  
    RR 7EH
    JR C,W7E1
    NOP
    NOP ;WRITE BIT=0
R7ELP:  
    DEC 7AH
    JR NZ,R7EST
    NOP
    JR SVSPL
    NOP
W7E1:   
    JR R7ELP ;WRITE BIT=1
;
SVSPL:  
    LD 7EH,SPL ;SAVE SPL IN ORDER TO MAKE TEMPORARY STACK
;SET UP A NEW STACK @78H, ASSUMING THAT STACK IS INTERNAL OR 
;EXTERNAL AND RAM EXISTS AT CURRENT SPH+78H. IF NEITHER IS TRUE
;THEN OPERATOR CAN DISABLE WITH 3 NOPS IN PLACE OF LD SPL.
    LD SPL,#78H ;OPTIONAL NOP*3
    POP 7CH ;SAVE STACK @XX78H IN R7C
    POP 7DH ;SAVE STACK @XX79H
;NOW SPL=7AH AND NEXT PUSH ACCESSES SPXX79H
    JR SVR7F ;SKIP OVER SERIAL WRITE SUBROUTINE
;
SERWR:  
    LD 7AH,#8 ;INIT SHIFT COUNT
SERST:  
    RR 7BH ;CALLER PUTS SOURCE DATA IN R7BH
    JR C,SERW1 ;GO WRITE BIT=1
    NOP
    NOP ;WRITE BIT=0
SERLP:  
    DEC 7AH  
    JR NZ,SERST
    NOP
    RET

I had been using my universal development system as a simple ROM emulator for “burn and crash” program development with a Z8 piggyback, which was a standard Z8 with a ROM socket on top of the IC package for program memory. The ROM emulator was certainly better than a physical EPROM especially since the socket was only good for a few dozen insert-remove cycles at best. I wanted to be able to read the target's internal data (and, through this, all target system data as well) but the Z8 target had no means of writing to the program interface. Not only did the chip not have data drivers, but the CPU was a full Harvard architecture and had no instructions for writing its control store.

I invented a means by which the CPU could transmit data through its program interface. To paraphrase, “where it reads is what it writes”. The debug program, which engages when the target hits a breakpoint, shifts a byte through carry and branches on carry clear or set for each bit. Thus, the pattern of branches tells the bit pattern of the byte. I designed a simple logic analyzer to record the serial bit stream resulting from the Z8 target applying this process to all of its internal and any specified external data. My debugger (also using a Z8) would then reconstruct the data from this record.

This example is the target's data upload function. It is a small but critical part of my Z8 development system, which Synertek (Z8 second source) sold as the MDT20.

SERW1:  
    JR SERLP ;WRITE BIT=1 (TRIGGER @E4H)
SVR7F:  
    LD 7BH,7FH
RCADH:  
    CALL RSERWR ;SAVE R7F TO BE USED FOR SOURCE POINTER
    ;SUBROUTINE ADDR=02D4H(DEFAULT) FOR PB/8681 
    ;BUT 12D4H FOR SL'S SAVE REGISTERS 00-77H  
    CLR 7FH ;INITIALIZE SOURCE POINTER
ARRLP:  
    LD 7BH,@7FH
ARCADH: 
    CALL RSERWR ;02D4H OR 12D4H

    INC 7FH
    CP 7FH,#78H
    JR NE,ARRLP ;CONTINUE SENDING ARRAY, STOPPING AT 78H
    JR INDADH ;SKIP OVER VECTORS

;-------------------------------------------
    ORG BREAK+256
;2ND PAGE IRQ VECTORS ARE FOR 8681 ONLY SINCE ALL OTHERS HAVE
;A8=0 GUARANTEED.
    DB 0,15,0,15,0,15,0,15,0,15,0,15
    JP 3E6H ;GOTO SETUP AND EXCUTE JAM IF RESET
;
;SET UP RR30H WITH SERWR ADDR TO ALLOW INDIRECT CALL AND THUS
;FEWER INSTANCES OF TARGET-SPECIFIC CODING 
INDADH: 
    LD 30H,#2 ;USE 2 IF PB/8681 OR 12H IF SL TARGET
    LD 31H,#0D4H

;TO WRITE (SERIAL) TRUE VALUES OF R78H AND R79H, FIRST RESTORE
;STACK @78 AND @79H IN CASE STACK WAS INTERNAL AT BREAK
    PUSH 7DH ;RESTORE STACK @79H--INTERNAL OR EXTERNAL
    PUSH 7CH ;STACK @78H
    LD 70H,78H ;COPY R78H
    LD 71H,79H ;COPY R79 
;AGAIN SAVE TOP OF STACK FROM SUBROUTINE CALL
    POP 7CH ;STACK @78-->7C
    POP 7DH ;STACK @79-->7D
    LD 7BH,70H
    CALL @30H ;WRITE R78H
    LD 7BH,71H 
    CALL @30H ;WRITE R79H
;SAVE SPECIAL REGISTERS EXCLUDING WRITE-ONLY'S
    LD 7FH,#0F0H
SPRLP1: 
    LD 7BH,@7FH
    CALL @30H ;WRITE RF0, RF1, RF2H
    INC 7FH
    CP 7FH,#0F3H
    JR NE,SPRLP1
    LD 7BH,0F4H
    CALL @30H ;WRITE RF4H
    LD 7FH,#0FAH
SPRLP2: 
    LD 7BH,@7FH
    CALL @30H ;WRITE RFA-RFEH
    INC 7FH
    CP 7FH,#0FFH
    JR NE,SPRLP2
    LD 7BH,7EH ;GET ORIGINALLY SAVED SPL
    CALL @30H
;NOW RETRIEVE ORIGINAL STACK @78 AND @79, WHICH WERE SAVED IN
;R7C AND R7D AFTER THEIR ORGINALS WERE WRITTEN
    LD 7BH,7CH
    CALL @30H ;WRITE STACK@78H
    LD 7BH,7DH
    CALL @30H ;WRITE STACK@79H
;
;BEGIN USER-SUPPLIED BREAK ROUTINE
    JR BRKEND 
;USER MAY INSERT ANY TARGET ROUTINES, BUT MAY NOT USE R78H-7FH
;AND MUST VECTOR TO BRKEND WHEN DONE
;-----------------------
    ORG 97D6H
;2ND PART OF READ XMEM. FIRST PART,DURING RST,READ X INTO R7B
XMRD:   
    CALL RSERWR ;ADDR=02D4H FOR PB/8681 12D4H 
;FOR SL'S. ----------------------------
    LD RP,73H ;RESTORE PREVIOUS RP FROM COPY MADE IN RST
;
BRKEND: 
    PUSH 7DH ;RESTORE STACK@79H
    PUSH 7CH ;STACK@78H
    LD SPL,7EH ;RESTORE ORIGINAL FROM COPY
    LD 30H,#3 ;BRK JAM ADH. USE 13H FOR SL'S

    LD 31H,#0F5H ;ADL
    LD 32H,30H
    SUB 32H,#3 ;RESTART JAM ADH=0 FOR PB/8681, 10 FOR SL'S 
    LD 33H,#12H ;ADL VECTORS TO SL'S JUMP TO BEGRST
    JR FINBRK

;------------------------------
    ORG 97F5H ;PRE-JAM MUST BE LOCATED AT TARGET'S 3F5H
    JP @30H ;PRE-JAM LOOP 
;
;GAP ACCOMDATES CODE IN RESTART 
    ORG 97FEH
FINBRK: 
    JP @30H ;JUMP TO PRE-JAM LOOP

;BREAK WRITE ORDER IS DUMMY, DUMMY, V,S,Z,C,7A,7B,7C,7D,7E,7F,
;00-79 (TRUE VALUES OF 78,79), F0-F2,F4,FA,FF, STACK@78,@79.
;R30-35,7A-7F HAVE ALL BEEN CHANGED




Prev   Next   Site Map   Home   Top   Valid HTML   Valid CSS