CMPE 401 - Computer Interfacing

Assignment #4 Solutions

Due: In the CMPE 401 assignment box at 15:45 on Friday, Nov. 19, 2004


  1. Design a task, written in ANSI C for the MicroC/OS-II environment, that starts off polling two 16-bit hardware registers at addresses $AC3000 and $AC3002, respectively, sixteen times per second. These registers record the present X and Y position coordinates of a mouse pointing device. If the mouse position has changed since the last polling operation, then the task is to obtain the system time (in ticks) and to post a message to a message queue that contains the system time along with the present X and Y positions of the mouse. In addition, the polling frequency is to be increased to 32 times per second immediately after a mouse movement has been detected. When the mouse position has not changed for three consecutive polling operations, the polling frequency is to be lowered back to 16 times per second. Your solution does not need to provide the MicroC/OS-II initialization code nor the start-up task. Assume that the system hardware timer produces 64 ticks per second.

    [25 marks]
    #include <stdlib.h> /* required to use itoa */ #define MOUSE_X_ADDR 0xAC3000 #define MOUSE_Y_ADDR 0xAC3002 #define SLOW_SCAN_PERIOD 4 /* 64 / 4 = 16 Hz */ #define FAST_SCAN_PERIOD 2 /* 64 / 2 = 32 Hz */ #define QUEUE_SIZE 100 #define SS_LEN 8 /* length of substring is 8 chars */ x_posn_ptr = MOUSE_X_ADDR; /* initialize the register pointers */ y_posn_ptr = MOUSE_Y_ADDR; OS_EVENT *queue_ecb_ptr; /* create pointer to queue ECB */ void *queue_ptr[QUEUE_SIZE]; /* allocate memory for msg queue */ /* The following line should be in the start task */ queue_ecb_ptr = OSQCreate( **queue_ptr, (INT16U)QUEUE_SIZE ); void MousePollTask( void *data ) { INT8U err; /* var for return codes */ INT32U sys_time; INT16U last_x, last_y, curr_x, curr_y; INT16U *x_posn_ptr, *y_posn_ptr; INT16U scan_period = SLOW_SCAN_PERIOD; integer time_out = 0; char move_msg[] = "time = tttttttt X = xxxxxxxx Y = yyyyyyyy\n"; char substring[SS_LEN]; int t_offset = 7; /* offset to first time char in move_msg */ int x_offset = 22; /* offset to first x posn char in move_msg */ int y_offset = 37; /* offset to first y posn char in move_msg */ last_x = *x_posn_ptr; /* initialize the last mouse position */ last_y = *y_posn_ptr; OSTimeDly( scan_period ); /* first scanning delay */ while ( TRUE ) { curr_x = *x_posn_ptr; /* poll current mouse position */ curr_y = *y_posn_ptr; if ( (last_x != curr_x) || (last_y != curr_y) ) then { /* mouse position changed */ sys_time = OSTimeGet(); substring = itoa( (int)sys_time, substring, SS_LEN ); for (i=0; i<SS_LEN; i++) { /* load time chars */ move_msg[t_offset + i] = substring[i]; } substring = itoa( (int)curr_x, substring, SS_LEN ); for (i=0; i<SS_LEN; i++) { /* load X posn chars */ move_msg[x_offset + i] = substring[i]; } substring = itoa( (int)curr_y, substring, SS_LEN ); for (i=0; i<SS_LEN; i++) { /* load Y posn chars */ move_msg[y_offset + i] = substring[i]; } err = OSQPost( queue_ptr, move_msg ); scan_period = FAST_SCAN_PERIOD; time_out = 3; /* scan rapidly for at least next 3 times */ } else { /* mouse position did not change */ if ( time_out > 0) time_out--; /* leave scan_period as is */ else /* have timed out; go back to slower scanning period */ scan_period = SLOW_SCAN_PERIOD; } last_x = curr_x; /* curr posn now becomes last posn */ last_y = curr_y; OSTimeDelay( scan_period ); } }

  2. Design a program in 68000 asssembly language that implements a circular buffer that has the capacity to store N long-word pointers, where N is a symbolic constant. The empty and full flags are to be implemented as byte-sized flags. The read and write pointers are to be implemented as 16-bit offsets. Provide separate routines for initializing the circular buffer, writing a pointer from the buffer, and reading a pointer from the buffer. The pointer parameters are to be passed in register A0. Be sure to exploit the available instruction types and addressing modes to make accesses to the circular buffer as fast as possible. Document your design with a diagram that shows the relevant data structures on a Motorola-style memory "ladder" diagram.

    [25 marks]
              .org   0x10000    /* arbitrary place to put the data structure */
              .equ   N, 1024    /* 1024 is an arbitrary buffer capacity */
    EMPTY:    .byte  0
    FULL:     .byte  0
    RD_PTR:   .word  0
    WR_PTR:   .word  0
    CIRC_BUF: .org   CIRC_BUF + (N * 4)  /* reserve space for N longwords */
    
            .org    0x20000     /* arbitrary place to put the code */
    INIT_CIRC_BUF:
            CLR.B   EMPTY
            CLR.B   FULL
            CLR.W   RD_PTR
            CLR.W   WR_PTR
            RTS
    
    WRITE_BUF:
            MOVEM.L A1-A2,-(SP)
            TST.B   FULL           /* is the buffer full? */
            BNE     EXIT_WR_BUF    /* if yes, exit */
            LEA.L   CIRC_BUF,A1
            MOVE.W  WR_PTR,A2      /* get write index */
            LSL.W   #2,A2          /* multiply index by 4 for longwords */
            MOVE.L  A0,0(A1,A2.W)  /* fetch the stored pointer */
            ADDQ.W  #1,A2          /* increment the index */
            CMPI.W  #N,A2          /* check for index wrap-around */
            BLT     LD_WR_PTR
            CLR.W   A2             /* reset index back to 0 */
    LD_WR_PTR:
            MOVE.W  A2,WR_PTR      /* update stored write index */
    EXIT_WR_BUF:
            MOVEM.L (SP)+,A1-A2
            RTS
    
    READ_BUF:
            MOVEM.L A1-A2,-(SP)
            TST.B   EMPTY          /* is the buffer full? */  
            BNE     EXIT_RD_BUF    /* if yes, exit */
            LEA.L   CIRC_BUF,A1
            MOVE.W  RD_PTR,A2      /* get read index */
            LSL.W   #2,A2          /* multiply index by 4 for longwords */
            MOVE.L  0(A1,A2.W),A0  /* fetch the stored pointer */
            ADDQ.W  #1,A2          /* increment the index */
            CMPI.W  #N,A2          /* check for index wrap-around */
            BLT     LD_RD_PTR
            CLR.W   A2             /* reset index back to 0 */
    LD_RD_PTR:
            MOVE.W  A2,RD_PTR      /* update stored read index */
    EXIT_RD_BUF:
            MOVEM.L (SP)+,A1-A2
            RTS
    
             +--------+--------+
    0x010000 |      EMPTY      | 0x010001
             +--------+--------+
             |      FULL       |
             +--------+--------+
             |      RD_PTR     |
             +--------+--------+
             |      WR_PTR     |
             +--------+--------+
    0x010008 |CIRC_BUF         | 0x010009
             +--------+--------+
             |                 |
                      *
                      *
                      *
    0x011006 |                 | 0x011007
             +--------+--------+
    
  3. Lecture slide 10-7 contains two illustrations that show the internal structure of a hybrid stepper motor. Note that both the rotor and the stator are split into upper and lower halves. Assume, for the rest of this question, that the upper and lower halves are aligned with each other (and so we need only consider one set of halves). The left side of slide 10-7 shows a rotor with 10 teeth, which are labelled "a" to "j". The stator is shown with 8 teeth that are labelled as being of types 1, 1', 2 and 2'. There are in fact only two windings in the stator, which we will call #1 and #2. The labels 1 and 1' indicate that when winding #1 is energized, the stator teeth labelled 1 and 1' assume opposite magnetic polarities; similarly, energizing winding #2 causes the teeth labelled 2 and 2' to have opposite magnetic polarities. Reversing the direction of the current flowing through winding #1 will reverse the magnetic polarities of the stator teeth of types 1 and 1'. Reversing the current direction in winding #2 has the same effect on stator teeth of types 2 and 2'.
    1. The rotor shown at the left of slide 10-7 is shown with rotor teeth "a" and "f" aligned with stator teeth of type 1. The next rotational increment would have rotor teeth "b" and "g" aligned with stator teeth of type 2. Determine the rotational increment, in degrees, corresponding this one minimum-sized step. Be sure to show your intermediate work.
    2. Construct a table that has rows for the first ten angular rotor positions going clockwise assuming a full step control sequence. Additional columns of the table should show the corresponding rotor positions (in degrees with respect to the first position shown in slide 10-7) and the current strength and direction in the two stator windings.
    3. Construct the same type of table for the first ten angular rotor positions going counter-clockwise assuming a half step control sequence. What would be the minimum-sized rotational increment in rotor position?

    [20 marks]
    1. The angular spacing between the 8 stator teeth is 360/8 = 45 degrees, while the angular spacing between the rotor teeth is 360/10 = 36 degrees. For the rotor to advance one step, the angular displacement must thefore be 45-36 = 9 degrees.

    2. Assuming that the rotor position shown in slide 10-7 is at a stable step position, then the full step sequence going clockwise for the first 10 steps is as follows:
      Position
      Number
      Teeth
      Alignment
      Rotor
      Angle
      Winding 1
      Current
      Winding 2
      Current
      #1 a - 1 0 deg forward off
      #2 b - 2 9 deg off forward
      #3 c - 1' 18 deg reverse off
      #4 d - 2' 27 deg off reverse
      #5 e - 1 36 deg forward off
      #6 f - 2 45 deg off forward
      #7 g - 1' 54 deg reverse off
      #8 h - 2' 63 deg off reverse
      #9 i - 1 72 deg forward off
      #10 j - 2 81 deg off forward

    3. The half step sequence, going counter-clockwise, would be as follows:
      Position
      Number
      Teeth
      Alignment
      Rotor
      Angle
      Winding 1
      Current
      Winding 2
      Current
      #1 a - 1 0 deg forward off
      #2 aj - 12' -4.5 deg forward reverse
      #3 j - 2' -9.0 deg off reverse
      #4 ij - 1'2' -13.5 deg reverse reverse
      #5 i - 1' -18.0 deg reverse off
      #6 hi - 1'2 -22.5 deg reverse forward
      #7 h - 2 -27.0 deg off forward
      #8 gh - 12 -31.5 deg forward forward
      #9 g - 1 -36.0 deg forward off
      #10 fg - 12' -40.5 deg forward reverse
      The rotational increment, going counter-clockwise, is -4.5 degrees.

  4. Briefly describe how the TPU can be programmed to produce up to 16 different user interrupts. Which TPU registers are involved in interrupt control? Briefly describe how each of these registers is used. Where are the TPU exception vectors located in the exception vector table of the 68332 system? Note that once a TPU interrupt has occurred, the corresponding CISR bit must be written to 0 to clear the interrupt condition (it will not be cleared automatically by just reading the CISR).

    [15 marks]
    Interrupt conditions can be defined for each of the 16 TPU channels. The conditions or events that activate the interrupt for any given channel depend on the function that is associated with the channel during TPU initialization. The TPU channel interrupts can be enabled to produce interrupt signals from the TPU by setting the corresponding bits in the 16-bit-wide Channel Interrupt Enable Register (CIER, at offset $00A).

    The Channel Interrupt Status Register (CISR, at offset $020) can be read to determine which interrupts are currently active. After an ISR bit is set by an active interrupt-producing event, the bit must be explicitly cleared by the interrupt service routine after the interrupt-producing event has been handled.

    TPU interrupts can be either autovectored or user vectored. Autovectoring is set up in the System Integration Module, or is forced for all system interrupts by asserting the AVEC input pin signal active (this is what we did in lab exercise #4).

    The Interrupt Configuration Register (ICR, at offset $008) stores the TPU interrupt priority level in bits #10, 9 and 8. This priority level is shared by all 16 possible TPU channel interrupts. In addition, the ICR stores the channel interrupt base vector in bits #7, 6, 5 and 4. This vector gives the upper four bits of the exception vector numbers used by all TPU interrupts when user vectoring is being used. The lower four bits of the exception vector numbers are defined by the channel number. Thus the 16 TPU exception vectors lie in a contiguous block in the CPU's exception vector table.

  5. Consult the course textbook and/or on-line TPU documentation and then briefly explain, in your own words, how the pulse-width modulation (PWM) built-in function (code $9 in the channel function select registers) is used.

    [15 marks]
    The PWM function is used to produce a train of binary pulses as an output signal on any desired TPU channel(s). The period of the signal is written by the CPU as the PWMPER parameter (the word at parameter RAM offset $04). The width of each pulse is written by the CPU as the PWMHI parameter (the word at parameter RAM offset $06). Both PWMPER and PWMHI represent time as counts of either TCR1 or TCR2. The clock that is actually used is specified by bits 8 down to 5 in the CHANNEL_CONTROL parameter (bits 7 down to 0 in the word at parameter RAM offset $00). The field value 0b0100 forces the use of TCR1, while the field value 0b0111 forces the use of TCR2.

    Bits 1 and 0 of CHANNEL_CONTROL are used to hold pin commands: Command 0b01 causes normal operation (a train of output pulses). Command 0b10 force the output signal to stay low.

    Note that the PWMHI and PWMPER parameters must be written together as a longword write into the parameter RAM at offset $04. (Note: Perhaps the hardware gets confused if this is not done.) The period and/or pulse width can be changed in this way at any time by the CPU, but the change will not take effect on the output signal until the start of the next output pulse. If the change needs to take effect right away, then ths can be done by issuing a $01 service request.

    Parameter RAM word PWMRIS is updated automatically by the TPU. It gives the clock count that corresponds to the next expected rising transition in the output signal (that is, the start time of the next output pulse).

    If the PWM is enabled to produce interrupts, then an interrupt will be triggered at the start (rising edge) of every output pulse.