Go to the first, previous, next, last section, table of contents.

ARC Programming Reference

Standard input/output functions

These functions read or write from the standard input and output streams for a process. By default, the standard io streams will be multiplexed serial streams.

Standard ANSI stdio functions

`int getchar();'
`int getc(FILE *stream);'
Both of these functions return the next character from the input buffer that has not yet been read. If there is an error, like when the stream argument is invalid, a -1 is returned. getchar() returns the next character from the default input stream for the process calling it. getc() returns the next character from the given stream. getchar() is frequently used in programs to wait until the user is ready to continue (The old "press any key to continue" prompt). Keep in mind that a buffering getchar will cause execution to continue if there is any past input left in the buffer, so you may want to call clear_serial_output_buffer() immediately before the call to getchar for this purpose.
`int putchar(char ch);'
`int putc(char ch, FILE *stream);'
Both of these functions write the given character to the output queue to be sent over the serial line. Because serial output is buffered, these functions will return after writing to the output buffer. If the buffer is full, these functions will not return until there is sufficient space in the buffer to write the next character. Because all other serial output functions (puts(), printf, and its derivatives) call putchar, this applies to all of them.
`char *gets(char *s);'
Gets a string from over the serial line, echoes it, and places it into the character array s. s must have enough space allocated to handle the input or overflow will occur. gets() takes care of delete and backspace characters appropriately and returns the pointer to the null terminated string upon receiving a return character. I recommend that you do not use gets(s), but rather use fgets(stdin,length,s) with length set to be the space allocated to s. This is a much safer in situations where overflow is possible.
`int sscanf(char *s, char *format, ...);'
Standard sscanf which scans the input string s and writes the appropriate values to it's arguments. A modification of this beyond standard is that a %b format string parses the binary representation of a number.
`int scanf(char *format, ...);'
Standard scanf which calls sscanf with the results of a gets. The input length is limited to 100 characters.
`int puts(char *ptr);'
Writes a null terminated character string to standard output by calling putchar on each character.
`int printf(char *format, ...);'
Standard printf (look in a C reference for specs) using putchar to output each character to standard output the modification that %#b prints the binary representation of a number to # (in the range 1 to 32) binary digits. If # is left out, the defaults is to display 8 bits.
`int sprintf(char *s, char *format, ...);'
Standard sprintf which writes to the character string s instead of an output stream.

Kernel extensions of stdio facilities

`void clear_serial_input_buffer();'
`void clear_serial_output_buffer();'
These functions clear the I/O buffers. This will result in data lossage, but sometimes you want that.
`void wait_for_serial_output_complete();'
This function returns when the standard output buffer is empty.
`int serial_data_received();'
Returns a 1 if there is data in the stadard input buffer, otherwise returns 0. This is a useful loop condition if you want a keyboard hit to cause a loop to exit.
`int serial_input_buffer_depth();'
`int serial_output_buffer_depth();'
These functions return the depth of the standard I/O buffers. The input buffer depth is the number of characters received but not yet recovered through a call to getchar(). The output buffer depth is the number of characters written to the output buffer but not yet removed from the buffer. If the standard output is a multiplexed stream, this depth may reflect the activity of other processes as well.

File input/output functions

These functions open, close, read from or write to files and streams.

The file abstraction module allows io to be treated in similar way to how it would be treated on other c systems. There are still no real files (the memory is not permanent or large enough to really justify it), but the file abstraction provides a consistent interface to io drivers.

The "file" name structure simulates a 2-deep directory hierarchy where the "directory" name is the driver and the "filename" is the name of the stream. Once created with fopen, files exist until all instances of it are destroyed or until reset. If a file with the same name is opened in multiple progrograms, processes, etc. all will get a pointer to the same file object.

This allows for inter-process communication streams, or multiple processes watching the same hardware io stream, etc. However, once anyone removes a character from the file it's gone. There are not independent file pointers.

Files have an input and an output queue (which may point to the same place) which are fixed-length circular buffers. If a write is attempted to a queue which is full, the write will either hang or return -1 depending on the file type.

There are currently four drivers: stream, serial, seriala, and serialb.

The standard files stdin, and stdout refer to the serial stream on the default port which is created automatically for the current process. The standard file stderr refers to the console stream.

The following standard functions are available and should do what you expect:

The following I defined because I thought they would be useful:

`int fflush(FILE *s);'
Waits until the output buffer is empty.
`int fclear(FILE *f)'
Clears the input queue to file f.
`int fdepth(FILE *f)'
Returns the number of bytes in the input queue of the file.

String/memory functions

The following functions are defined as specified in "C A Reference" to conform to ANSI standard:

Malloc functions

`void *malloc(size_t size);'
`void free(void *mem);'
These functions are as documented in C texts. malloc() takes a integral size as an argument and returns a pointer to a block of memory of that size, or NULL if there is not enough memory left. free() takes a pointer returned from a previous call to malloc() and frees that block of memory for reuse. Call free with a non-malloced pointer or a previously freed pointer at your own risk. If free detects that the pointer is not valid it will breakpoint. You may then want to use gdb to find out where it crashed. Note that all of the memory malloced by a process is automatically freed when that process dies. Therefore be careful not to use memory malloced by one process in another process unless you are confident that the proces which did the malloc has a longer lifetime. It is in general safe for child processes to use memory which the parent had mallocced because they will die with the parent.
`void *durable_malloc(size_t size);'
This function is just like malloc except that memory allocated using durable_malloc is not automatically freed when the process which allocated it dies and may exist even across downloads. It is possible for durable_malloc to fail when regular malloc would succeed, as it has a smaller heap to allocate from, so you should use malloc when you don't need this extra feature.

Persistents

Persistents provide a way to store state between resets and downloads, implement inter-program communication, and adjust program parameters from the console.

A persistent is really a permanently allocated 4-byte memory location associated with a unique string. Persistents are created by calls to `init_persistent()' and stored in a hash table. Any program may call `init_persistent' or `lookup_persistent' and receive a pointer to the same permanently allocated memory.

Persistents age at every reset, and persistents which have not been accessed in long enough (default of 10 resets) are garbage collected.

The type `persistent' is a pointer to a volatile integer. The following is a sample usage of persistents:

persistent foo;
void main()
{
  foo=init_persistent("foo",4);
  if(foo)
    printf("Value stored in foo is %d\n",*foo);
  else
    printf("Persistent table full\n");
}

The size of the persistent table is reconfigurable, but requires a recompile of kernel. The default is 256 entries with 16-character hashing strings, which comes to 8K of memory usage.

`persistent init_persistent(char *s,int defval);'
Look up the string s in the persistent table. If it is already there, return the associated memory pointer. If it is not already there, create it with the provided default value. If there are no slots left in the persistent table, returns NULL. If the string is too long, only the first N characters are used, where N is a length built into the kernel -- defaulting to 16.
`persistent set_persistent(char *s,int val);'
The same as init_persistent, only the value is set to val regardless of whether it was already there or not.
`persistent lookup_persistent(char *s);'
Looks up the string s in the persistent table and returns either the associated memory pointer or NULL if it was not found.
`int delete_persistent(char *s);'
Deletes the persistent from the persistent table on the next reset by setting its age to be ancient. It does not delete the entry due to the danger that someone else might have the handle to the persistent.
`void print_persistents();'
Prints the entire persistent table. This function is called by the "persist" console command with no arguments.

Queues

A queue is a first-in-first-out (FIFO) data structure which is specially designed for use in the multitasking environment with shared memory which kernel provides. So long as only the accessor functions `read_byte_from_queue()' and `write_byte_to_queue()' are used, queue reads and writes are atomic. Therefore queues can be used by multiple processes without extra locking. Queues use interrupt masking to achive atomicity, so it is safe to use them in interrupt handlers (unlike `lock()')

`void initialize_queue(queue *qp,int size,unsigned char *data);'
Given a queue pointer, a size, and a pointer to a block of data of the specified number of bytes, initialize_queue() initializes the queue to be empty. Because queues must conform to certain representation invariants in order to work properly, they should be initialized before they can be used.
`int init_queue(queue *qp,int size);'
`int free_queue(queue *qp);'
init_queue() is just like initialize_queue() except that it mallocs the data buffer of the specified size. If there is not enough memory left to allocate the data, returns -1, otherwise returns 0. There are times you want to use this for convenience. A queue that has been initialized using init_queue must be freed using free_queue() in order to free its storage (or you could just free qp->data, but this has better abstraction qualities).
`queue *create_queue(int size);'
`int destroy_queue(queue *qp);'
These two functions are the same as the ones above, but they will allocate and free the queue as well. It is not really necessary to provide all of these functions, but it's basically free to do so and there are occasions that require each of these methods.
`int write_byte_to_queue(queue *qp,unsigned char b);'
Write a byte to the queue. If the queue is full, then the status code QUEUE_FULL will be returned. Otherwise, b will be atomically added to the end of the queue.
`int read_byte_from_queue(queue *qp);'
Read a byte from the queue. If the queue is empty, then the status code QUEUE_EMPTY will be returned. Otherwise, it will return the first byte in the queue. (The type is int instead of char so that error conditions are unambiguous.)
`int peek_queue(queue *qp);'
Returns QUEUE_EMPTY if the queue is empty, otherwise returns the next byte in the queue without removing it. You are not guaranteed that the same byte will be there on a subsequent read if another process is trying to read the queue.
`int queue_depth(queue *qp);'
Returns the number of bytes currently in the queue.
`void clear_queue(queue *qp);'
Throw away the current contents of a queue and make it empty.

Locks

A concern in multitasking systems with shared memory is achieving mutual exclusion of certain operations. For example, if you had a insertion/deletion table, you would want only one process to be able to add an entry at a time to prevent the possibility of two processes trying to add different entries to the same slot at the same time. The functions in lock.c and lock.h provide one way of achieving this mutual exclusion.

`int test_and_set(lock_t *flag);'
Given a pointer to a lock flag, this function will atomically test and set the value pointed at (by using the 68000 tas instruction). It will return 0 if someone else already had the lock, and 1 if no one else already had the lock.
`void lock(lock_t *flag);'
`void unlock(lock_t *flag);'
Given a pointer to a lock flag, lock() will defer in a loop so long as someone else is holding the lock, and return when it has achieved the lock itself. Unlock clears a lock, and should ALWAYS be used after lock() in all possible execution paths so that the next thread (or the same thread on another pass) can continue. *** IMPORTANT *** Lock should not be used by interrupt routines, as it runs the risk of leaving the processor running at too high an inerrupt level and blocking other interrupts which can mess things up. There is a bit of a safety net built in which prevents lock from blocking if the interrupt level is non-zero. However, this means that interrupt routines can break the invariants that lock was designed to protect -- so just don't do it.

Interrupt functions

These functions affect the interrupt vector table directly.

`void install_exception_handler(int i,void *p);'
Installs the pointer p to an interrupt handler to correspond to interrupt i (in the range 0 to 256). After calling this function once to set up the vector, when the processor is interrupted with exception number i, it will call the handler pointed at by p. The handler pointed to by p must be in assembler in order to preserve registers properly and execute the needed rte (return from exception) instruction instead of the default rts (return from subroutine) instruction. Here is an example of a suitable interrupt handler -- you should change the names of the functions, keeping in mind that C variable and function names have a prepended underscore when you refer to them in assembler, and vice versa (so "foo" in C becomes `_foo' in assembler, and `_bar' in assembler is `bar' in C).
asm(".globl _my_handler
_my_handler:
     moveml #0xc0c0,sp@-   /* push registers d0, d1, a0, a1 */
     jsr    _my_c_handler   /* call your c handler */
     moveml sp@+,#0x0303   /* pop registers d0, d1, a0, a1 */
     rte
");
void my_c_handler()
{
  /* Do what you want to do on the interrupt here */
}
void main()
{
  install_exception_handler(INTERRUPT_NUM,my_handler);
  /* Now your exception handler is installed */
}
*** IMPORTANT *** When user code exits, it is necessary to remove all exception vectors that point into that code and replace them with something safer. Therefore install_exception_handler() also keeps track of which exception vectors a process installs and removes them when the process exits. Keep this in mind and make sure that the process which calls install_exception_handler() is at least as long lived as any process that depends on that exception vector being set.
`void *get_exception_vector(int i);'
Returns the pointer to the interrupt handler for interrupt i.
`uint *get_vbr();'
You probably won't need to use this function because install_exception_handler() exists to protect you from it, but it will return to you the vector base register currently being used by the processor. The VBR is used during exception handling to know where the vector table starts so the correct interrupt handler can be used automatically. Be aware that with the way the system software is currently arranged, if you change the VBR you will likely be hurting yourself. Also, there is a default place that the system software puts the vector table and install_exception_handler() assumes that it doesn't move.

Multitasking

The multitasker takes care of process table management, process scheduling, crash handing, and time keeping (with time kept in swapper ticks). The scheduling interrupt on the 332 runs on the periodic interrupt so that it makes no assumptions about the hardware function allocation (in other words, it would be in many ways more convenient to use a TPU interrupt, but it would conflict with someone using all 16 timer lines for servos). The period of the scheduling interrupt is programmable. It is also possible for a process to give up the CPU before its allocated time is up by calling defer() or one of its derivatives. This uses a trap to cause an immediate swap to occur. Process scheduling is round robin. When the current process is preempted or defers the next process in the table is allowed to run.

I have been warned that I use the word "swap" incorrectly because swap implies that things are being put on disk. None of the 332 systems this software has yet been run on even have disks, so hopefully this potential confusion is not a problem. "Swap" is simply a more convenient term than "context switch" or anything else I can think of, so I will use it.

I have also been warned that there is a distinction between processes and threads. To many people, "processes" implies heavy-weight, virtual memory changing, belonging to different programs, UNIX style processes, whereas "threads" implies threads of execution which have shared memory space and come from the same program. Kernel processes are not exactly like either of these -- all processes have shared memory space, and an arbitrary number of processes can be associated with a program.

There is a limit to the number of processes which can be active at a given time. Currently that limit is 32. I could change this if it ever becomes necessary.

The functions described in this section make up the user's interface to the multitasker.

Process creation/deletion

`typedef void (*ps_func)();'
`pid start_process(const char *name,ps_func f,int stack_size,int time_slice);'
`pid start_adult_process(const char *name,ps_func f,int stack_size,int time_slice);'
Both of these functions create and return the pid of a new process with the specified name, stack_size bytes of stack, time_slice scheduler ticks between being swapped in and being preempted (if it doesn't defer beforehand), and which is started with a call to function f (a void (void) function). The difference between start_process() and start_adult_process() is how they react to their parent being killed. start_adult_process() starts a process with a completely independent lifespan -- it will live until function f exits or until it is killed. start_process() starts a standard child process which will die on the same conditions or when its parent dies, whichever comes first.
`int kill_process(pid id);'
kill_process() kills the process with the given pid. If the process is valid and killable (not root or gdb), kill_process() kills it and returns 1. Otherwise it does nothing and returns 0.
`pid get_current_pid();'
Returns the pid of the process currently executing.
`pid lookup_pid_by_name(char *name);'
Given a name, lookup_pid_by_name will either return the pid of the first process in the table with that name, or INVALID_PROCESS if it does not exist. This is the function called by the kill console command when it is given a string argument.

Scheduler control

`int set_swap_tick_period(int ns);'
`int get_swap_tick_period();'
set_swap_tick_period() sets the period of the scheduler interrupt to the number of nanoseconds specified and returns the period as actually set (which will probably differ due to the granular nature of the periodic interrupt setup). get_swap_tick_period() returns the current scheduler tick period.
`int swap_cycle_time();'
Returns the average number of scheduler ticks that it is taking to completely cycle through the process table. This number will range from 0 (if everyone is deferring) to the sum of the number of ticks alotted to all the processes (if no one is deferring). This number allows you to have an idea of the processor load.
`void enable_process_swapping();'
`void disable_process_swapping();'
These two functions allow the scheduler interrupt to be enabled and disabled. Disabling the interrupt allows CPU-hungry applications, such as software frame grabbing, to proceed uninterrupted. However, be aware that while process swapping is disabled the time will not increment so by using these functions you are accepting that you will have inconsistent time.

Defer

`void defer();'
defer() allows a process to give up the rest of its time and allow the next process in the cycle to be swapped in right away. defer() uses a trap to achieve this, as successful context switching requires the use of an rte instruction to get the status register right. Be aware that defer() has no effect on when the next regularly spaced scheduler tick will fall. Therefore, after a defer the next tick will be short. Therefore, if you care about a process getting a certain amount of work done before it is preempted you should let it have at least 2 ticks because the first one may not be complete.
`void defer_on_input_pending(FILE *f);'
`defer_on_input_pending()' marks the current process as an io_waiting process and it will not be swapped in until there is something in the input buffer of file f. This is automatically called by getchar() on an empty input buffer.
`void defer_until(int endtime);'
`defer_until()' marks the current process as a sleeping process and it will not be swapped in until the time is greater than or equal to endtime. This is automatically called by sleep() when the duration of the sleep exceeds the number of ticks left to the process.
`void hog_processor();'
`hog_processor()' gives the current process 256 more scheduler ticks. This is useful if you want to be fairly confident that you can carry out a computation uninterrupted but do not want to prevent the steady march of time.

Manipulation of running processes

`int set_process_time_slice(pid id,int new_slice);'
Allows the time slice of a running process to be changed. Returns 1 if the pid was valid and -1 if the pid was invalid.
`int get_process_time_slice(pid id);'
Returns the current time slice for a process, or -1 if the pid is invalid.
`FILE *get_process_instream(pid id);'
Returns the input stream of a process so that another process could talk to it over its standard text input stream if it wanted to.

Time functions

`int time();'
Returns the current time;
`int sleep(int counts);'
Sleeps for an amount of time greater than or equal to counts scheduler ticks. The sleeping time may not be anywhere near accurate if someone is turning off scheduler interrupts, and may be larger by an amount less than or equal to swap_cycle_time().

Printing functions

`void print_process_regs(pid id,FILE *s);'
Print the registers of the given process to the given stream.
`void print_process_table();'
Print the process table. This is what is called by the ps console command. An example of the output from this function follows:
Multitasking: 
swap cycle time = 1, expected = 16, load average = 6%
PID     Status  Ticks  Low     Water   High    PC      Name
0       Running 5      13f800  13fd10  140000  702     Root process
33      IO Wait 10     130ca4  131334  1314a4  50d0    Debug Process
66      Crashed 1      1315e8  1318b0  1319e8  118144  Dummy process

332 hardware registers

The hardware registers for the 332 are memory mapped as bitfield structures with the same names as in the Motorola documentation. See the file mem.h in the kernel subdirectory of the main 332 tree for details. Basically you have the following pointers to the main blocks of 332 hardware registers, which work in both your programs and in gdb:

`simptr sim;'
Pointer to the system integration module memory map. Some examples are: sim->syncr - the clock control register sim->porte - the data byte for port E sim->picr.piv - the periodic control register interrupt vector number Generally, if you are tempted to use these registers in your programs you might want to check sys.h and sys.c to see if there is a function that already does what you want.
`csmptr csm;'
Pointer to the chip select module memory map. Some examples are: csm->boot - the complete chip select settings for CSBOOT csm->csch[3].dsack - the data strobe acknowdge delay for CS3 Functions for setting up chip selects and changing their dsack delay can be found in sys.h and sys.c.
`qsmptr qsm;'
Pointer to the queued serial module memory map. This includes the control and data registers for the queued serial module and the asynchronous serial module both. Some examples are: qsm->tmcr.qmcr.iarb - the interrupt arbitration value for qsm qsm->scbr - the asynchronous serial baud rate conrol register Functions for controlling the asynchronous serial module can be found in sys.h, sys.c, stdio.h, and stdio.c. The queued serial module is not curently built into kernel in any way. This is a planned improvement.
`tpu_control_ptr tpu_control;'
Pointer to the timer processor unit configuration and control registers. Some examples are: tpu_control->cier - channel interrupt enable register tpu_control->tmcr.stop - bit for stopping the entire TPU Functions for controlling the TPU are found in TPU.h and TPU.c.

Actual memory map structures:

typedef volatile struct simmap {
  word mcr;
  word simtr;
  struct syncr_struct {
    uint w :1;
    uint x :1;
    uint y :6;
    uint ediv :1;
    uint   :2;
    volatile uint slimp :1;
    volatile uint slock :1;
    uint rsten :1;
    uint stsim :1;
    uint stext :1;} syncr;
  byte u1;  byte rsr;
  word simtre;
  uint u2;

  word u3;
  byte u4;   volatile byte porte;
  byte u5;   volatile byte eport;
  byte u6;   byte ddre;
  byte u7;   byte pepar;
  byte u8;   volatile byte portf;
  byte u9;   volatile byte fport;
  byte u10;   byte ddrf;
  byte u11;   byte pfpar;
  struct sypcr_struct {
     uint u12 :8;

     uint swe :1;                       /* watchdog enable */
     uint swp :1;                       /* watchdog prescale */
     uint swt :2;                       /* watchdog timing */
     uint hme :1;                       /* halt monitor enable */
     uint bme :1;                       /* bus monitor enable */
     uint bmt :2;                       /* bus monitor timing */
  } sypcr;                             /* system protection ctrl register */
  struct picr_struct {
    uint     :5;
    uint pirq:3;
    uint piv :8;} picr;
  struct pitr_struct {
    uint     :7;
    uint ptp :1;
    uint pitr:8;} pitr;
  byte u13;   byte swsr;
  word ua28;
  word ua2a;
  word ua2c;
  word ua2e;
  word tstmsra; /* a30 */
  word tstmsrb;
  byte tstsca;   byte tstscb;
  word tstrc;
  word creg;
  word dreg;
  uint u15;
  byte u16;   byte cspdr;} *simptr;

extern const simptr sim;

struct chip_select_channel {
    uint addr :13;
    uint blksz :3;
    uint mode :1;
    uint byte :2;
    uint rw :2;
    uint strb :1;
    uint dsack :4;
    uint space :2;
    uint ipl :3;
    uint avec :1;};

typedef struct chip_select_map {
  struct cspar_struct {
    uint   :2;
    uint cs5 :2;
    uint cs4 :2;
    uint cs3 :2;
    uint cs2 :2;
    uint cs1 :2;
    uint swsr1 :2;
    uint swsr2 :2;  /* end of first word */
    
    uint   :6;
    uint cs10 :2;
    uint cs9 :2;
    uint cs8 :2;
    uint cs7 :2;
    uint cs6 :2;} cspar;
  
  struct chip_select_channel boot;
  struct chip_select_channel csch[10];} *csmptr;

extern const csmptr csm;

struct synch_ser_comnd {
  uint cont   :1;
  uint bitse  :1;
  uint dt     :1;
  uint dsck   :1;
  uint psc3   :1;
  uint psc2   :1;
  uint psc1   :1;
  uint psc0ss :1;};

typedef volatile struct queued_serial_module {
  struct qmcr_struct {
    uint stop :1;
    uint frz1 :1;
    uint frz0 :1;
    uint      :5;
    uint supv :1;
    uint      :3;
    uint iarb :4;} qmcr;
  word qtest;
  struct qilr_struct {
    uint        :2;
    uint ilqspi :3;
    uint ilsci  :3;
    uint qivr   :8;} qir;
  word r0;
  word scbr;  /* baud control register */
  struct sccr_struct {
    uint    :1;
    uint loops :1;
    uint woms  :1;
    uint ilt   :1;
    uint pt    :1;
    uint pe    :1;
    uint m     :1;
    uint wake  :1;
    uint tie   :1;
    uint tcie  :1;
    uint rie   :1;
    uint ilie  :1;
    uint te    :1;
    uint re    :1;
    uint rwu   :1;
    uint sbk   :1;} sccr;
  volatile struct scsr_struct {
    uint      :7;
    volatile uint tdre :1;
    volatile uint tc   :1;
    volatile uint rdrf :1;
    volatile uint raf  :1;
    volatile uint idle :1;
    volatile uint or   :1;
    volatile uint nf   :1;
    volatile uint fe   :1;
    volatile uint pf   :1;} scsr;
  volatile word scdr;
  uint r1;
  byte r2;    byte qpdr;
  union qpar_qddr_union {
    struct qpar_struct {
      uint       :1;
      uint pcs  :3;
      uint pcs0ss :1;
      uint       :1;
      uint mosi  :1;
      uint miso  :1;
      uint       :8;} qpar;
    struct qddr_struct {
      uint       :8;
      uint txd   :1;
      uint pcs   :3;
      uint pcs0ss :1;
      uint sck   :1;
      uint mosi  :1;
      uint miso  :1;} qddr;
  } qpar_qddr;
  struct spcr0_struct {
    uint mstr    :1;
    uint womq    :1;
    uint bits    :4;
    uint cpol    :1;
    uint cpha    :1;
    uint spbr    :8;} spcr0;
  struct spcr1_struct {
    uint spe     :1;
    uint dsckl   :7;
    uint dtl     :8;} spcr1;
  struct spcr2_struct {
    uint spifie  :1;
    uint wren    :1;
    uint wrto    :1;
    uint         :1;
    uint endqp   :4;
    uint         :4;
    uint newqp   :4;} spcr2;
  struct spcr3_spsr_struct {
      uint       :5;
      uint loopq :1;
      uint hmie  :1;
      uint halt  :1;
      uint spif  :1;
      uint modf  :1;
      uint halta :1;
      uint       :1;
      uint cptqp :4;} spcr3_spsr;
  byte r3[0xe0];
  word rec_ram[16];
  word tran_ram[16];
  byte comd_ram[16];} *qsmptr;

extern const qsmptr qsm;

typedef struct tpu_control_struct {
  struct tmcr_struct {
    uint stop :1;
    uint tcr1_prescale :2;
    uint tcr2_prescale :2;
    uint emu :1;
    uint t2cg :1;
    uint stf :1;
    uint supv :1;
    uint psck :1;
    uint      :2;
    uint interrupt_arbitration :4;} tmcr;
  word ttcr;
  word dscr;
  word dssr;
  struct ticr_struct {
    uint      :5; 
    uint interrupt_request_level :3;
    uint interrupt_base_vector :8;} ticr;
  word cier;
  word cfs[4];
  word hsf[2];
  volatile word hsrf[2];
  word cpr[2];
  volatile word cisr;} *tpu_control_ptr;

extern const tpu_control_ptr tpu_control;

332 System Integration Module functions

The

General Use functions

`int period_of(int hz);'
Given rate in hertz, period_of returns period in ns
`int rate_of(int ns);'
Given period in nanoseconds, rate_of returns rate in hertz

Digital I/O support

`void set_porte_output(byte mask);'
`void set_porte_input(byte mask);'
Sets port E lines masked as 1s in argument to output/input
`int read_porte();'
Returns the current value on port E data pins. The bits only have meaning if they are configured to port E input.
`void write_porte(byte data);'
Writes the data to port E data pins. Only has effect for bits configured for port E output
`void set_porte_bus_control(byte mask);'
Sets masked port E lines to alternate bus control functions
`void set_portf_output(byte mask);'
`void set_portf_input(byte mask);'
Sets port F lines masked as 1s in argument to output/input
`int read_portf();'
Returns the current value on port F data pins. The bits only have meaning if they are configured to port F input.
`void write_portf(byte data);'
Writes the data to port F data pins. Only has effect for bits configured for port F output
`void set_portf_interrupt_request(byte mask);'
Sets masked port F lines to alternate interrupt functions
`** In theory, the port C functions should work, but I have not really'
tested them yet. Port C coexists with the chip selects, so be very careful in using these functions, as bashing the chip selects on the ROM or RAM will crash you right away.
`void set_portc_output(byte mask);'
Sets masked port C lines to output function. The MSB when configured for output becomes the Eclock. The other bits are normally chip selects and can be configured for discrete output.
`void write_portc(byte data);'
Writes the data to port C data pins. Only has effect for bits configured for port C output and never for the MSB.

Clock control

`int clock_rate();'
Returns the current system clock rate in hertz. This function is memoized so calculation is not usually necessary so don't stress about using this function.
`int clock_period();'
Returns the current system clock period in nanoseconds.
`int set_clock_rate(int speed);'
Set_clock_rate takes an argument in hertz, sets the clock to the closest possible approximation of that speed, and returns the clock rate actually set (in hertz).
`void wait_clock_lock();'
This probably isn't practical, but after a clock rate change this will wait until the clock source has locked onto the new speed after a clock speed change.

Chip select module

enum cs_pin {output,default_config,cs_8bit,cs_16bit};
enum cs_size {s2k,s8k,s16k,s64k,s128k,s256k,s512k,s1m};
enum cs_mode {async,sync};
enum cs_byte {off,lower,upper,both};
enum cs_rw {read=1,write,rw};
enum cs_strobe {as,ds};
enum cs_space {cpu,user,supervisor,u_s};
enum cs_avec {avec_off,avec_on};
`int initialize_chip_select(int num,enum cs_pin pin,void *base_addr,'
enum cs_size size,enum cs_mode mode, enum cs_byte byte_config, enum cs_rw rw,enum cs_strobe strobe, byte dsack_delay,enum cs_space space, byte interrupt_level, enum cs_avec avec); Configures the chip select num to the options provided. Num may range from 0 to 10 (returns -1 if out of range), but on the Motorola bcc board uses chip selects 0, 1 and 2 and the Vesta board uses chip selects 0 and 1 for main ram. The cs_pin argument should only be either cs_8bit for 8 bit transfers or cs_16bit for 16 bit transfers. Dsack_delay can be from 0 to 13 wait states per access (with 14 meaning it can go real fast and 15 meaning external ack), and interrupt_level from 1 to 7 (with 0 meaning all interrupts allowed). The other parameters should be self explanatory from the enums listed above, and for details of operation refer to the 68332 user's manual pages 4-27 to 4-45. The format of this instruction corresponds directly to the format of table 4-18 on page 4-38 of the text. After configuring a chip select using this function, any reference to the block of memory starting at the base_address (which must lie on an appropriate boundary) and the block of memory the size of which is specified by size will cause the appropriate chip select to give your peripheral an active low chip select signal telling it to either drive the bus if the operation is a read or take data from the bus if the operation is a write. This effectively memory maps your external device for you!
`void set_dsack(int cs,int val);'
This function will set the dsack for the specified chip select to the specified number of wait states. See above for description of the numbers.
`int set_cs_size(int cs,int val);'
Assuming that the chip select cs is already configured, this function will set the block size for the chip select to the smallest block size greater than or equal to the length in bytes specified by val. The value returned is the actual length in bytes that the chip select was set to. Val should be a power of 2, but enven then only a limited set of lengths are possible. The possible lengths and their representations in hexidecimal are as follows:
2k = 0x800        16k = 0x4000   128k = 0x20000   512k = 0x80000
8k = 0x2000       64k = 0x10000  256k = 0x40000   1M   = 0x100000

Asynchronous Serial configuration

`int baud_rate();'
Returns the current baud rate of the 68332.
`int set_baud_rate(int baud);'
Given a nominal baud rate to shoot for, this will set the asynchronous baud rate to as close to that as possible and return the exact baud as set (usually some error necessary).
`int recalibrate_baud();'
You should not ever need to call this as you should only change the clock speed by calling set_clock_rate which does this automatically, but this function will recalibrate the system to maintain a stable baud rate despite changes in system clock speed.

332 TPU functions

This module includes functions for affecting the Timer Processor Unit as a whole, and general functions for affecting the actions of each pin.

For all the functions which take a channel number as an argument, the channel number should be in the range 0 to 15, which correspond directly with the TPU pin number. Therefore pin TPU0 corresponds to an argument 0, TPU15 to 15, etc. This argument could also be channel_of(ptr) where ptr is the handle returned from an initialization function on a channel.

`#define channel_of(ptr) {blah...}'
Given a pointer to the parameter space of a TPU function (which is what the initialization functions return) this will tell you what channel that pointer corresponds to. Here is the intended use: If you had a pwm channel that you initialized by:
pwm pptr=pwm_init(blah blah);
that you wanted to disable temporarily (or call any of the general TPU functions that require a channel as an argument), you could use:
set_timer_priority(channel_of(pptr),0);
and not have to keep track of what channel it was initialized on. This operation is quite efficient (it only requires bit masking, not any sort of table lookup), and I recommend using it for better code readability.

TPU module configuration

These functions affect the configuration and operation of the TPU as a whole.

`void stop_tpu();'
Stops the TPU. No channels are serviced. The TCRs don't increment. It's dead, Jim.
`int tpu_stopped();'
Returns 1 if TPU is stopped, 0 if not.
`void start_tpu();'
Restarts the TPU after it has been stopped.
`void initialize_tpu_interrupts();'
Calling this function configures the TPU to allow it to trigger CPU interrupts. This function is automatically called by kernel, so you should not need to call it.
`void disable_tpu_interrupts();'
This will uniformly prevent the TPU interrupts from being acknowledged by the CPU in case you don't want it to bother you for some amount of time...
`void enable_tpu_interrupts();'
This will undo the effect of the previous function and reenable TPU interrupts

TCR control

The time base for the TPU channel functions can either be TCR1 or TCR2. Both are 16-bit free-running counters. TCR1 is always a divisor of the system clock. TCR2 is affected by the pin T2CLK. These functions affect the TCRs.

enum psck_enum {psck_div32,psck_div4};
enum tcr_prescale {div1,div2,div4,div8};
enum tcr2_pin_use {pin_is_clock_source,pin_is_gate_of_div8_clock};
typedef enum tcr_source_enum {tcr1= 1, tcr2= 2} tcr_source;
`int initialize_tpu_speeds(enum psck_enum psck,enum tcr_prescale t1p,enum tcr_prescale t2p);'
It is possible to set the divisor from the system clock (psck), the prescales from that clock source to TCR1, and the prescale for TCR2 **ONLY ONCE BETWEEN RESETS**. Worse than that, the same register that controls the the prescales also controls the interrupt arbitration id, which must be set during kernel initialization. Therefore, the first time the user try to change the prescales it will not work. The most recently requested prescale values are stored in persistents, and at the next reset the values will be set correctly. initialize_tpu_speeds will return 1 if the values actually in the register match the requested values, and 0 if they don't. The best thing to do if this returns 0 is to request that the user hit reset, and it will be set right the next time. The system software specifically does not try to set the prescales, and you should make sure that your code either tries to set it only once, or that all places which try to set the prescales agree on what they should be. YOU SPECIFICALLY *CANNOT* CHANGE THESE VALUES DYNAMICALLY. The following table gives the results of setting the prescales for a 16.7MHz system clock. If the system clock is a different speed, scale these numbers accordingly.
TCR1 prescale |  psck=psck_div32      |  psck=psck_div4       |
              |div from sys | speed   | div from sys | speed  |
    div1      |     32  **  |  2us    |       4      | 250 ns |
    div2      |     64      |  4us    |       8      | 500 ns |
    div4      |    128      |  8us    |      16      |   1 us |
    div8      |    256      | 16us    |      32      |   2 us |

TCR2 prescale | divide by | div from sys | div from external  |
    div1      |      1 ** |       8      |          1         |
    div2      |      2    |      16      |          2         |
    div4      |      4    |      32      |          4         |
    div8      |      8    |      64      |          8         |
`void set_tcr2_source(enum tcr2_pin_use);'
`int calculate_tcr1_speed();'
You shouldn't need to call this as it is automatically called if you change the clock speed or prescale using the functions provided. However, it pre-calculates and stores the period and rate of TCR1 and returns the rate in hertz.
`int tcr1_rate();'
Returns TCR1 speed in hertz.
`int tcr1_period();'
Returns TCR1 period in nanoseconds.
`int period_to_tcr1_counts(int period,int *error);'
Returns the number of TCR1 counts that comes closest to while not exceeding the period argument in nanoseconds and stores the error in nanoseconds of the resulting value in *error. TCR1 is only a 16-bit counter, so you should check that the value returned is less than 65000 before using it for TPU functions. If the value is too large, consider setting the TCR1 prescale and/or PSCK to values that cause TCR1 to run slower. See `initialize_tpu_speeds()'.
`int rate_to_tcr1_counts(int hertz,int *error);'
Same as above, but argument is in hertz. Error is still in nanoseconds. The same warnings about overrunning 16 bits apply as above.
`int tcr1_counts_to_period(int counts);'
Returns the period in nanoseconds corresponding to the given number of TCR1 counts.
`int tcr1_counts_to_rate(int counts);'
Returns the rate in hertz corresponding to the given number of TCR1 counts.

Channel control

All built-in TPU functions require the channel control field to be set as part of channel initialization. The channel control structure actually consists of three fields which affect the TPU hardware latches associated with each pin -- pin state control, pin action control, and time base/directionality seletion.

The actual types used to map on to the channel control field of the TPU register are as follows. The reason a union is used rather than just a plain struct is that you may want to load all the fields at once for efficiency (and therefore use the `w' part), or you may want to load each field inividually without worrying about shifts and masks (and therefore use the `bf' part).

typedef union {
  word w;
  struct {
    uint    :7;
    uint tbs:4;
    uint pac:3;
    uint psc:2;} bf;} channel_control;

enum pac_input {no_detect_edge,rising_edge,falling_edge,either_edge};
enum tbs_input {i_c1_m1,i_c1_m2,i_c2_m1,i_c2_m2};

enum psc_output {follow_PAC,force_high,force_low,no_force};
enum pac_output {no_change,high_on_match,low_on_match,
                   toggle_on_match,preserve_pac};
enum tbs_output {o_c1_m1=4,o_c1_m2,o_c2_m1,o_c2_m2,preserve_tbs};

Pin State Control (psc)

Controls the immediate action of a pin in response to initialization. This field only affects output channels (and therefore is not even an argument to the function to set the channel control of an input). The values are as follows:

`follow_PAC'
Do what the Pin action control field specifies
`force_high'
Force output high on initialization
`force_low'
Force output low on initialization
`no_force'
Leave the pin state as you found it

Pin Action Control:

Controls the action of a pin in response to a match or transition. The meaning of this field is affected by whether the pin is an input or output.

Time Base/Directionality Selection (tbs)

Controls whether the channel is input or output, and whether it uses TCR1 or TCR2. It is actually possible to match on and capture different TCRs, but in general you want to use the same TCR for both.

Channel Control functions

`channel_control channel_control_input(enum pac_input pac,enum tbs_input tbs);'
`channel_control channel_control_output(enum psc_output psc,enum pac_output pac,enum tbs_output tbs);'
These functions return the value of the channel_control parameter required for initializing TPU functions. These functions take their arguments and pack them appropriately to insert into the TPU parameter ram or pass to channel initialization functions. Example of using these functions follow:

TPU Interrupt support

`void install_tpu_exception_handler(int ch,void *p);'
This function installs the given function as the interrupt handler for the given channel. This DOES NOT enable the interrupts on the channel and should be called before enabling the interrupts on a channel.
`void set_timer_interrupt_enable(int ch,int val);'
This function enables or disables interrupts on the given TPU channel. If val==0 the given channel will not interrupt the CPU, when an interrupt-inducing event occurs (transition, match, whatever), but will instead just set a bit in the timer interrupt status field (which can be polled using timer_interrupt_status). If val==1, then exception handler previously installed by a call to install_tpu_exception_handler() will be called when an interrupt occurs.
`int timer_interrupt_status(int ch);'
This is useless if the interrupts are enabled on the given channel. If interrupts are disabled, timer_interrupt_status will return 0 if no interrupt has occurred, or 1 if an interrupt has occured. After reading that an interrupt has occurred, you must clear the status flag by a call to reset_timer_interrupt. Interrupts are disabled by default, or by calling set_timer_interrupts_enable(ch,0) at any time.
`void reset_timer_interrupt(int ch);'
If interrupts are disabled, use this to reset the interrupt status bit after to allow polling for a new interrupt. If interrupts are enabled, you can call this from your interrupt routine to reset for the next interrupt. Note that if you are using intstall_tpu_exception_handler, you do not need to call this in your interrupt as the status bit is reset automatically. However, it doesn't hurt to call it anyway. If you are using your own interrupt wrapper, you should call definitely call this, as failing to will cause the interrupt to be repeatedly handled and prevent other processing from happening.
`int wait_for_timer_interrupt(int channel);'
*** Only use this function on channels for which interrupts are disabled!! ***** This function will loop until the status bit of the given channel is set, then it will reset the status bit and return.

General timer channel configuration

`void set_timer_priority(int ch,int val);'
Sets the timer priority for the given channel to val where val is in the range of 0 (disable service to the channel) to 3 (this is a really important channel, service promptly). See the Motorola TPU reference manual for more details about how priority affects service latency.
`int wait_for_host_service_complete(int channel);'
The CPU communicates with the TPU through shared parameter space. Therefore when the CPU requests service from the TPU there may be some delay before the request is completed. Call this function if you need to wait until a service request has been completed before continuing with program execution. In most cases this is not necessary. It is only necessary when synchronization between the CPU and TPU is required.

Channel functions

Please note the following general things about using channel functions:

DIO

To use these functions you must `#include "tpu/dio.h"'.

typedef struct dio_struct {
  channel_control ctl;
  volatile word pin_level;
  word match_rate;} *dio;

enum dio_hsf {transition=0,match=1,cpu_sample=3};
`dio dio_output_init(int ch,int state);'
Initializes the channel to discrete output with initial value state (either 1=HIGH or 0=LOW). Subsequent changes to the output level can be made by calling dio_out on the initialized channel.
`void dio_out(dio dptr,int state);'
Set pre-initialized discrete output channel to given state.
`dio dio_input_init(int ch,int match_rate,enum dio_hsf hsf,int priority,channel_control ctl);'
Initializes the channel to discrete input, updating either on input transition, a a constant rate match_rate (this parameter is meaningless if hsf!=match...), or when the cpu requests update.
`int dio_state(dio dptr);'
Returns a word (lower two bytes of int...) corresponding to the last 16 states of the input channel.
`int dio_now(dio dptr);'
Returns 0 if current (or most recently sampled) discrete input state is low and non-zero (actually 0x8000 'cause it's cheap) otherwise.

PWM

To use these functions you must `#include "tpu/pwm.h"'.

enum pwm_hsrf {pwm_initialize=2,pwm_update=1};

typedef struct pwm_struct {
  channel_control ctl;
  volatile word oldris;
  word pwmhi;
  word pwmper;
  volatile word pwmris;
  word slate;} *pwm;
`pwm pwm_init(int ch,int hightime,int period,int priority,channel_control ctl);'
Initializes the channel to pulse width modulated output with the given period and hightime (where hightime/period=duty cycle of the wave).
`void pwm_set_times(pwm pptr,int high,int per);'
Changes the high time and period on an initialized pwm channel to the given values.
`void pwm_disable(pwm pptr);'
`void pwm_enable(pwm pptr,int priority);'
pwm_disable really just sets the channel priority regiter to 0, but results in suspending the output of a pwm channel without otherwise disturbing it. pwm_enable undoes this hold and assigns the channel the given priority.

ITC

To use these functions you must `#include "tpu/itc.h"'.

typedef struct itc_struct {
  channel_control ctl;
  volatile word lb; 
  word max_count;
  volatile word trans_count;
  volatile word final_trans_time;
  volatile word last_trans_time;} *itc;

enum itc_hsf {single_no_links,continual_no_links,
                single_links,continual_links};
`itc itc_init(int ch,int max_cnt,enum itc_hsf hsf,int priority,channel_control ctl);'
Initializes a channel for input timer capture/transition count. Note that links are not yet supported in this version of the libraries, so the valid values of hsf are single_no_links for one-shot action and continual_no_links for continuous action.
`void set_itc_max_cnt(itc iptr,int cnt);'
Allows the maximum number of counts for an initialized itc channel to be reset.
`int get_itc_last_period(itc iptr);'
Returns the last period accumulated by an initialized itc channel.
`int reset_itc_counts(itc iptr);'
Resets the number of counts an itc channel has accumulated

OC

To use these functions `#include "tpu/oc.h"'.

This is unbelievably complex. I don't fully understand it yet. I'll document output compare when I understand it...

typedef struct oc_struct {
  channel_control ctl;
  word offset;
  word rat_ref1;
  word ref2_3;  
  volatile word ref_time;
  volatile word actual_match_time;} *oc;
  
enum oc_hsrf {host_match=1,continuous_pulse=3};
enum oc_hsf {all=0,TCR_only=2};

#define TCR1 0xec
#define TCR2 0xee
#define TCR1_time() ((int)*(word *)0xffffec)
#define TCR2_time() ((int)*(word *)0xffffee)
#define timeptr_of(p) (((word)(p)) & 0xff)

oc oc_init(int ch,word timeptr,int priority,channel_control ctl);

oc oc_pulse_init(int ch,word timeptr,int width,int priority);

void oc_initiate(oc optr,int width);
int oc_timecalc(oc optr,int timeptr);

PPWA

To use these functions you must `#include "tpu/ppwa.h"'

typedef struct ppwa_struct {
  channel_control ctl;
  word max_period_cnt; 
  volatile word last_accum;
  volatile word accum;
  volatile word accum_rate_ub; 
  volatile word ppwa_lw;} *ppwa;

enum ppwa_hsf {period_24=0,period_16,pulse_24,pulse_16};
`ppwa ppwa_init(int ch,int max_counts,int accum_rate,int priority,enum ppwa_hsf hsf,channel_control ctl);'
Initializes the channel to execute pulse width or period accumulation for max_counts waveforms between interrupts and place the finished results in its parameter space. For consistency, if initializing ppwa channels for period measurement, the pac field of channel_control should be to detect falling edges and for pulse width measurement it should be to detect rising edges.
`void ppwa_set_accum_rate(ppwa pptr,int val);'
Allows resetting of the accum_rate for an initialized ppwa channel.
`void ppwa_reset_high_byte(ppwa pptr);'
For 24bit measurement, the high byte does not automatically get reset so you can use this function to do that.
`void ppwa_set_max_counts(ppwa pptr,int val);'
Allows resetting of the max_count for an initialized ppwa channel.
`int ppwa_full_period(ppwa pptr);'
Returns the full period for 24bit accumulation. This function does not, however, clear the upper byte of the accumulator so be careful to use this in concert with ppwa_reset_high_byte().
`int ppwa_ave_period(ppwa pptr);'
Divides the accumulated periods/pulse widths by the number of counts it represents and returns that value (rounded to an integer...).
`int ppwa_last_pulse_time(ppwa pptr);'
Returns the last TCR1 time at which a pulse was detected.

Go to the first, previous, next, last section, table of contents.