Tuesday, June 30, 2015

PMOD used as an Interrupt

Both tutorials 2B and 2d utilize interrupts in the form of the buttons provided on the Zedboard. I decided to use both tutorial codes for reference in programming the rotary encoder for the Viper Telescope.

The rotary/angle encoder is an electro-mechanical chip that converts the angular position of a shaft (usually motor shaft) to a digital value. The encoder we are using will be connected to the motor shaft atop the Viper Telescope and will allow us to determine the position/angle of the telescope based on how far the motors have moved. There are five pins on the chip:

1) CLK
2) DT
3) SW - Resets the encoder to zero (used for calibration)
4) V++ - Power (3.3 Volts is a good operating voltage)
5) Ground

The first two pins CLK and DT are the digital encoding pins. Referring to the Arduino code in Frank/Ryans's blog I found out what these two pins do. Based on which direction the encoder is spun, we get a different sequence of values from each pin. Each pin can only read a 1 or a 0. From testing today, I found that when the shaft is in a "resting mode" the pins will either read both 0 or both 1. You will notice when playing around with the encoder that the shaft moves in specific increments (not a smooth spin, yet more like a gear). There are roughly 20 increments in one full rotation of the shaft. From testing I found that starting at rest a position I will call A, the read value from both pins turns out to be 4. How do I achieve the value of 4 you ask?

Well, in the hardware design I initialized a GPIO to be all INPUTS with a 2 BIT GPIO WIDTH. After that I was able to assign the sites of the GPIO to be the first two pins of the JA PMOD, pins JA1 and JA2. Miranda's blog lists the sites for all the PMOD pins in the implemented design.

So, moving from position A to position B (1 increment in the clockwise direction) I found that the Discrete Read Value on the 2-BIT PMOD Channel I created becomes 0 (instead of 4). So initially I thought that the encoder can only read a 0 or 4 because whether or not I moved in the clockwise or counter-clockwise, I would only get readings of 0 or 4. Then I was more precise with my turning of the shaft and was able to get more values. Halfway in between positions A and B, I get a value of 1 for the read value. From B to halfway to C, I get a value of 3. So a full rotation looks like this:

Position A =    0 = 00
Position A.5 = 1 = 01
Position B =    3 = 11
Position B.5 = 2 = 10
Position C = Position A = 0 = 00 for a full clockwise rotation.



Position A =    0 = 00
Position A.5 = 2 = 10
Position B =    3 = 11
Position B.5 = 1 = 01
Position C = Position A = 0 = 00 for a full clockwise rotation.

The nest step is to work on the interrupt handler that will use the previous read value, combine with the current value in a variable called sum. Depending on the value of sum, I can determine which direction the encoder, and therefore the motor, spun. If it spun clockwise, increase the encoder value by 1 and multiply by 4.5, if it spun counter-clockwise, decrease the encoder value by 1 and multiply it by 4.5


Additional Notes:

1) The interrupt handler for tutorial 2B looks like this:

void BTN_Intr_Handler(void *InstancePtr)
{
// Disable GPIO interrupts
XGpio_InterruptDisable(&BTNInst, BTN_INT);
// Ignore additional button presses
if ((XGpio_InterruptGetStatus(&BTNInst) & BTN_INT) !=
BTN_INT) {
return;
}
btn_value = XGpio_DiscreteRead(&BTNInst, 1);
printf("%d \n \r", btn_value);
// Increment counter based on button value
// Reset if centre button pressed
if(btn_value != 1) //led_data = led_data + btn_value;
//else led_data = 0;
    XGpio_DiscreteWrite(&LEDInst, 1, btn_value);
    (void)XGpio_InterruptClear(&BTNInst, BTN_INT);
    // Enable GPIO interrupts
    XGpio_InterruptEnable(&BTNInst, BTN_INT);
}

To me , it seems as if if the interrupt handler is always initialized and sort of lurking, reading, waiting to do something. In this case it is writing to the LEDs the value given by discrete read.

2) When using the virtual machine, it is important to use PuTTY as the terminal interface in Windows. To do so, first get your program ready set in SDK and Program FPGA. Before running the program, check the Device Manager to determine which COM port the Cypress USB UART cable (this is the usb cable from the Zedboard that communicates with the terminal) is connected to. Then open up PuTTy, select Serial, and enter the correct COM port. Be sure to change the BAUD rate to 115200. 

3) It is possible to engage the pull-up/pull-down resistors on an input pin using a couple different ways:
 a) Using Vivado, open the implemented design and check I/O ports (in window drop-down menu) and select the type of resistor you want under the PullType drop-down menu (it's pretty far to the right in the I/O menu)
b) Using SDK, use SetDataDirection to set inputs and outputs. Using DiscreteWrite and writing outputs high will output voltages that you chose in the hardware design. Setting an input high will engage the internal pull-up resistor in each pin of the PMOD.






Final Working Code for Angle Encoder:

#include "xparameters.h"
#include "xgpio.h"
#include "xscugic.h"
#include "xil_exception.h"
#include "xil_printf.h"
#include <stdio.h>

// Parameter definitions
#define ABOUT_ONE_SECOND 74067512
#define INTC_DEVICE_ID         XPAR_PS7_SCUGIC_0_DEVICE_ID
#define BTNS_DEVICE_ID        XPAR_AXI_GPIO_0_DEVICE_ID
#define LEDS_DEVICE_ID        XPAR_AXI_GPIO_2_DEVICE_ID
#define INTC_GPIO_INTERRUPT_ID XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR

#define BTN_INT             XGPIO_IR_CH1_MASK

XGpio LEDInst, BTNInst;
XScuGic INTCInst;
//static int led_data;
static int btn_value;
static int sum;
float angle;
volatile int lastEncoded=0;
volatile long encoderValue=0;
long lastencoderValue = 0;
//----------------------------------------------------
// PROTOTYPE FUNCTIONS
//----------------------------------------------------
static void BTN_Intr_Handler(void *baseaddr_p);
static int InterruptSystemSetup(XScuGic *XScuGicInstancePtr);
static int IntcInitFunction(u16 DeviceId, XGpio *GpioInstancePtr);

//----------------------------------------------------
// INTERRUPT HANDLER FUNCTIONS
// - called by the timer, button interrupt, performs
// - LED flashing
//----------------------------------------------------
void delay(int nStopValue)
/**
* \brief       Loop for nStopValue iterations to provide a delay.
* \par         Details
*              It is commonly used with the constant 'ABOUT_ONE_SECOND' defined in maximPMOD.h for
*              setting approximate delays
*
* \param[in]   nStopValue    - number of iterations to loop
*
* \retval      None
*/
{
    int i=0;
    int a=0;

    for(i=0;i<nStopValue;i++)
    {
        a=i;
    }
}



void BTN_Intr_Handler(void *InstancePtr)
{
    // Disable GPIO interrupts
    XGpio_InterruptDisable(&BTNInst, BTN_INT);
    // Ignore additional button presses
    if ((XGpio_InterruptGetStatus(&BTNInst) & BTN_INT) !=
            BTN_INT) {
            return;
        }
    btn_value = XGpio_DiscreteRead(&BTNInst, 1);
    delay(10000);
    printf(" Encoded = %d \n \r", btn_value);
    sum = (lastEncoded <<2) | btn_value;
    delay(10000);
    printf(" Sum = %d \n \r", sum);

     if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;

     if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;
     lastEncoded = btn_value; //store this value for next time

     angle=encoderValue*6;
     //printf(" EncoderValue = %ld \n \r", encoderValue);
     //printf(" Angle = %f \n \r", angle);



    // Increment counter based on button value
    // Reset if centre button pressed
    //if(btn_value != 1) //led_data = led_data + btn_value;
    //else led_data = 0;
    XGpio_DiscreteWrite(&LEDInst, 1, btn_value);
    (void)XGpio_InterruptClear(&BTNInst, BTN_INT);
    // Enable GPIO interrupts
    XGpio_InterruptEnable(&BTNInst, BTN_INT);
}

//----------------------------------------------------
// MAIN FUNCTION
//----------------------------------------------------
int main (void)
{
    printf(" Start Program \r \n");
    int status;
  //----------------------------------------------------
  // INITIALIZE THE PERIPHERALS & SET DIRECTIONS OF GPIO
  //----------------------------------------------------
  // Initialise LEDs
  status = XGpio_Initialize(&LEDInst, LEDS_DEVICE_ID);
  if(status != XST_SUCCESS) return XST_FAILURE;
  // Initialise Push Buttons
  status = XGpio_Initialize(&BTNInst, BTNS_DEVICE_ID);
  if(status != XST_SUCCESS) return XST_FAILURE;
  // Set LEDs direction to outputs
  XGpio_SetDataDirection(&LEDInst, 1, 0x00);
  // Set all buttons direction to inputs
  XGpio_SetDataDirection(&BTNInst, 1, 0b11);
  //XGpio_DiscreteWrite(&BTNInst, 1, 0b11);

  // Initialize interrupt controller
// Initialize interrupt controller
  status = IntcInitFunction(INTC_DEVICE_ID, &BTNInst);
  //printf("Status = %d \r \n", status);
  if(status != XST_SUCCESS) return XST_FAILURE;

 /* angle=encoderValue*4.5;
           printf(" Angle = %f \n \r", angle);
           printf(" EncoderValue = %ld \n \r", encoderValue);
           delay(100);
           */






  while(1);


  return 0;
}

//----------------------------------------------------
// INITIAL SETUP FUNCTIONS
//----------------------------------------------------

int InterruptSystemSetup(XScuGic *XScuGicInstancePtr)
{
    // Enable interrupt
    XGpio_InterruptEnable(&BTNInst, BTN_INT);
    XGpio_InterruptGlobalEnable(&BTNInst);

    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
                                      (Xil_ExceptionHandler)XScuGic_InterruptHandler,
                                      XScuGicInstancePtr);
    Xil_ExceptionEnable();


    return XST_SUCCESS;

}

int IntcInitFunction(u16 DeviceId, XGpio *GpioInstancePtr)
{
    XScuGic_Config *IntcConfig;
    int status;

    // Interrupt controller initialisation
    IntcConfig = XScuGic_LookupConfig(DeviceId);
    status = XScuGic_CfgInitialize(&INTCInst, IntcConfig, IntcConfig->CpuBaseAddress);
    if(status != XST_SUCCESS) return XST_FAILURE;

    // Call to interrupt setup
    status = InterruptSystemSetup(&INTCInst);
    if(status != XST_SUCCESS) return XST_FAILURE;

    // Connect GPIO interrupt to handler
    status = XScuGic_Connect(&INTCInst,
                                 INTC_GPIO_INTERRUPT_ID,
                                 (Xil_ExceptionHandler)BTN_Intr_Handler,
                                 (void *)GpioInstancePtr);
    if(status != XST_SUCCESS) return XST_FAILURE;

    // Enable GPIO interrupts interrupt
    XGpio_InterruptEnable(GpioInstancePtr, 1);
    XGpio_InterruptGlobalEnable(GpioInstancePtr);

    // Enable GPIO and timer interrupts in the controller
    XScuGic_Enable(&INTCInst, INTC_GPIO_INTERRUPT_ID);

    return XST_SUCCESS;
}


*****NOTE: The interrupt handler works well for the most part. Occasionally, when turning the shaft either clockwise or counter-clockwise the pins do not register as I get a value for "sum" that does not match up with any of the "sum" values in the table I made. I have tested the values for "encoded" which is simply the value of 00 01 10 or 11 and sometimes I get an incorrect value from what is actually going on. That error gets compounded when going into the sum loop. I chalked this error up to the fact that I am reading the 2-bit PMOD incorrectly. Therefore I plan on creating two individual read PMODs and then combining the value as seen in the Arduino sample code. Updates coming soon!***** 

No comments:

Post a Comment