lawrence_jeff

Using Harware QEI on Tiva Launchpad

18 posts in this topic

I got the QEI module working on the new Tiva Launchpad and thought the code might be helpful for others. QEI0 can use either PortD or Port F, since F is used by the LEDs I used PortD - the only gotcha is that PD7 is one of the NMI locked pins by default so I burned lots of time wondering why things weren't quite working. Anyway here is working code:

//*****************************************************************************
//
// qei.c - Example to demonstrate QEI on Tiva Launchpad
	//This setup uses QEI0 P6/PD7, in my testing an arcade trackball is connected. 
	//You can also use QEI1 PC5/PC6 in which case you don't need the PD7 HWREG calls (note: I didn't test this)
//
//
//*****************************************************************************

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_gpio.h"
#include "inc/hw_types.h"
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "driverlib/sysctl.h"
#include "driverlib/pin_map.h"
#include "driverlib/gpio.h"
#include "driverlib/qei.h"

volatile int qeiPosition;


int main(void) {

	// Set the clocking to run directly from the crystal.
	SysCtlClockSet(SYSCTL_SYSDIV_4|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);

	// Enable QEI Peripherals
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_QEI0);

	//Unlock GPIOD7 - Like PF0 its used for NMI - Without this step it doesn't work
	HWREG(GPIO_PORTD_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY; //In Tiva include this is the same as "_DD" in older versions (0x4C4F434B)
	HWREG(GPIO_PORTD_BASE + GPIO_O_CR) |= 0x80;
	HWREG(GPIO_PORTD_BASE + GPIO_O_LOCK) = 0;

	//Set Pins to be PHA0 and PHB0
	GPIOPinConfigure(GPIO_PD6_PHA0);
	GPIOPinConfigure(GPIO_PD7_PHB0);

	//Set GPIO pins for QEI. PhA0 -> PD6, PhB0 ->PD7. I believe this sets the pull up and makes them inputs
	GPIOPinTypeQEI(GPIO_PORTD_BASE, GPIO_PIN_6 |  GPIO_PIN_7);

	//DISable peripheral and int before configuration
	QEIDisable(QEI0_BASE);
	QEIIntDisable(QEI0_BASE,QEI_INTERROR | QEI_INTDIR | QEI_INTTIMER | QEI_INTINDEX);

	// Configure quadrature encoder, use an arbitrary top limit of 1000
	QEIConfigure(QEI0_BASE, (QEI_CONFIG_CAPTURE_A_B  | QEI_CONFIG_NO_RESET 	| QEI_CONFIG_QUADRATURE | QEI_CONFIG_NO_SWAP), 1000);

	// Enable the quadrature encoder.
	QEIEnable(QEI0_BASE);

	//Set position to a middle value so we can see if things are working
	QEIPositionSet(QEI0_BASE, 500);

	//Add qeiPosition as a watch expression to see the value inc/dec
	while (1) //This is the main loop of the program
	{
		qeiPosition = QEIPositionGet(QEI0_BASE);
		SysCtlDelay (1000);
	}
}
Rei Vilo likes this

Share this post


Link to post
Share on other sites

That is awesome, as I want to use my new Tiva Launchpad for a USB HID arcade control interface.  Any chance of writing this up into a library, either for CCS or Energia?

Share this post


Link to post
Share on other sites

I have never been able to get Energia to work on my machine so I'm not sure I would be much help there, and its already part of the CCS TI driver library (most of that code is the one time setup code you only need a line or two to read once setup)

I am interested in your project though - I am working with a group of coworkers building cocktail arcade tables, my goal is to use a Launchpad to interface a Golden Tee trackball and two sets of player controls (24 buttons) for MAME/various other emulators.

 

I have it mostly working on an original Launchpad acting as a USB mouse (for trackball) and keyboard (for buttons). You can see my other posts with example code on a dual Keyboard/HID device. Is this generally what you are trying to accomplish?

Share this post


Link to post
Share on other sites

That would be similar to my intentions.  I was planning on going with a mouse/dual 12 button gamepad HID device.  Was planning on doing a modular control board so that various controls could be swapped in/out depending on what games are being played.  Inputs would be hooked to some form of easily swappable connector (RJ45, DB9, etc...) so that the panel sections could be lifted on, unplugged, and replaced with whatever you want to hook up.  The two QEI inputs could be used for a trackball or two spinners, and the all the other GPIO pins would be mapped to the gamepad inputs.  Could also theoretically add a jumper or switch to switch between the two gamepad HID devices and a steering wheel or yoke, pedals, shifter, etc for driving or flying games.  The idea is to make the design as flexible as possible, with the ultimate goal of possibly marketing it as a superior alternative to the  X Arcade products.

 

Right now I'm more in the concept than design stage.  My programming knowledge is currently limited to Arduino and a small amount of AVR C, I'm more of a hardware guy.  What would be awesome is if the Energia was something more like the newer Arduino 1.0 builds for the Due.  Nice, easy to use, well documented generic USB HID stack.  Use the nice USB HID device descriptor creation utility from USB.org, pop that into an array in your code, create your data packet array, feed that to the USB HID initializer, and just drop updated data into the data array and transmit.

 

Same concept would be great for using the QEI inputs.  Drop all the setup code into a library.  Then all you have to do is make a single call to a setup function that tells it whether to turn on QEI0, QEI1, or both, and then a simple call to retrieve updated movement data from them.  That would be dependent on the Energia devs actually adding proper support for the TM4C first though.

 

Right now, I'm seriously considering switching to the Teensy 3.0 until the hobbyist environment matures a little around the TI products.   They have a fully functional Arduino style IDE and an easy to use USB HID stack.  The only thing it really lacks is hardware QEI support.  There are software QEI examples available for the Teensy though.  That and it costs a bit more and isn't as easy to access all the GPIO pins on.  At my current skill level with C, the Tiva is beginning to look like a $15 blinking paperweight....  It does blink very brightly though :rolleyes: Might be able to use it for the PWM motor speed controller with RPM feedback I need to build though.  Haven't even looked at what's involved in setting up PWM output yet though.

Share this post


Link to post
Share on other sites

That shouldn't be too hard - for the USB stuff just go through the labs I developed and posted on this site. A gamepad is one of the devices I cover (as well as a keyboard/mouse combo that could easily be changed to a mouse/gamepad.) TI support for custom HID descriptors is fairly poor but I believe I made it useable. (Note they are intended for StellarisWare not TivaWare so you need to make sure you are using that)

 

Really once you have the USB stuff presenting your board as a gamepad/mouse - the additional code to read the trackball/spinner inputs and buttons and do the translation to mouse/gamepad values windows/emulators can read isn't too bad. I was planning on building a shield for the launchpad with either RG45 connectors (to simplify wiring to the buttons) or standard screw terminals.

Share this post


Link to post
Share on other sites

I saw your USB tutorials, that is the first thing on my list to start on.  My biggest problem right now is making sense of TI's variable naming convention.  I'm not familiar with Hungarian notation, and I haven't found any good "translations" to make enough sense out of it so I can keep straight what is what....

Share this post


Link to post
Share on other sites

This descriptor put in the usbhidcustom.c file from my framework will make the Launchpad a dual gamepad + mouse device. (Each gamepad has 8 buttons + joystick)

static const unsigned char g_pucCustomHidReportDescriptor[]=
{
    UsagePage(USB_HID_GENERIC_DESKTOP),
    Usage(USB_HID_GAMEPAD),
    Collection(USB_HID_APPLICATION),
        Collection(USB_HID_PHYSICAL),

    	ReportID(1),
		//
		// 8 - 1 bit values for the first set of buttons.
		//
		UsagePage(USB_HID_BUTTONS),
		UsageMinimum(1),
		UsageMaximum(8),
		LogicalMinimum(0),
		LogicalMaximum(1),
		ReportSize(1),
		ReportCount(8),
		Input(USB_HID_INPUT_DATA | USB_HID_INPUT_VARIABLE | USB_HID_INPUT_ABS),


		//
		// The X, Y Z and Rx (Will appear as two thumb controls.
		//
		UsagePage(USB_HID_GENERIC_DESKTOP),
		Usage(USB_HID_X),
		Usage(USB_HID_Y),
		LogicalMinimum(-127),
		LogicalMaximum(127),
		ReportSize(8),
		ReportCount(2),
		Input(USB_HID_INPUT_DATA | USB_HID_INPUT_VARIABLE | USB_HID_INPUT_ABS),

        EndCollection,
    EndCollection,

    UsagePage(USB_HID_GENERIC_DESKTOP),
        Usage(USB_HID_GAMEPAD),
        Collection(USB_HID_APPLICATION),
            Collection(USB_HID_PHYSICAL),

        	ReportID(2),
    		//
    		// 8 - 1 bit values for the first set of buttons.
    		//
    		UsagePage(USB_HID_BUTTONS),
    		UsageMinimum(1),
    		UsageMaximum(8),
    		LogicalMinimum(0),
    		LogicalMaximum(1),
    		ReportSize(1),
    		ReportCount(8),
    		Input(USB_HID_INPUT_DATA | USB_HID_INPUT_VARIABLE | USB_HID_INPUT_ABS),

    		//
    		// The X, Y Z and Rx (Will appear as two thumb controls.
    		//
    		UsagePage(USB_HID_GENERIC_DESKTOP),
    		Usage(USB_HID_X),
    		Usage(USB_HID_Y),
    		LogicalMinimum(-127),
    		LogicalMaximum(127),
    		ReportSize(8),
    		ReportCount(2),
    		Input(USB_HID_INPUT_DATA | USB_HID_INPUT_VARIABLE | USB_HID_INPUT_ABS),


            EndCollection,
        EndCollection,

        UsagePage(USB_HID_GENERIC_DESKTOP),
			Usage(USB_HID_MOUSE),
			Collection(USB_HID_APPLICATION),
				Usage(USB_HID_POINTER),
				Collection(USB_HID_PHYSICAL),

					ReportID(3),
					//
					// The buttons.
					//
					UsagePage(USB_HID_BUTTONS),
					UsageMinimum(1),
					UsageMaximum(3),
					LogicalMinimum(0),
					LogicalMaximum(1),

					//
					// 3 - 1 bit values for the buttons.
					//
					ReportSize(1),
					ReportCount(3),
					Input(USB_HID_INPUT_DATA | USB_HID_INPUT_VARIABLE | USB_HID_INPUT_ABS),

					//
					// 1 - 5 bit unused constant value to fill the 8 bits.
					//
					ReportSize(5),
					ReportCount(1),
					Input(USB_HID_INPUT_CONSTANT | USB_HID_INPUT_ARRAY | USB_HID_INPUT_ABS),

					//
					// The X and Y axis.
					//
					UsagePage(USB_HID_GENERIC_DESKTOP),
					Usage(USB_HID_X),
					Usage(USB_HID_Y),
					LogicalMinimum(-127),
					LogicalMaximum(127),

					//
					// 2 - 8 bit Values for x and y.
					//
					ReportSize(8),
					ReportCount(2),
					Input(USB_HID_INPUT_DATA | USB_HID_INPUT_VARIABLE | USB_HID_INPUT_RELATIVE),


				EndCollection,
			EndCollection,

};

This handler will then make all 3 devices do something based on the buttons on the Launchpad:

void
CustomHidChangeHandler(void)
{
    unsigned long ulRetcode;
    unsigned long ulButton, ulButton2;


    //
    // Initialize all keys to 0 - this unpresses a key if you press one
    //
    signed char DeviceReport[3];
    DeviceReport[0]=0; //First 8 buttons
    DeviceReport[1]=0; //X
    DeviceReport[2]=0; //Y

    signed char MouseReport[3];
    MouseReport[0]=0; //Buttons
    MouseReport[1]=0; //X
    MouseReport[2]=0; //Y

    //
    //Read Left button and move mouse if pressed
    //
    ulButton = ROM_GPIOPinRead(GPIO_PORTF_BASE, GPIO_PIN_4);
    if(ulButton == 0)
    {
    	DeviceReport[0]=0x0F;
    	DeviceReport[1]=-60;
    	DeviceReport[2]=-60;

        MouseReport[0]=0; //Buttons
        MouseReport[1]=-10; //X
        MouseReport[2]=-10; //Y
    }

    //
    //Read Right button and sent ABCDEF if pressed
    //
    ulButton2 = ROM_GPIOPinRead(GPIO_PORTF_BASE, GPIO_PIN_0);
    if(ulButton2 == 0)
    {
    	DeviceReport[0]=0xF0;
      	DeviceReport[1]=60;
    	DeviceReport[2]=60;

        MouseReport[0]=0; //Buttons
        MouseReport[1]=10; //X
        MouseReport[2]=10; //Y
    }

    //
    // Send Device Report
    //
	g_eCustomHidState = CUSTOMHID_STATE_SENDING;
	ulRetcode = USBDHIDCustomHidStateChange((void *)&g_sCustomHidDevice, 1, DeviceReport); //0 is passed for reportID since this custom device doesn't have multiple reports

	// Did we schedule the report for transmission?
	//

	if(ulRetcode == CUSTOMHID_SUCCESS)
	{
		// Wait for the host to acknowledge the transmission if all went well.
		//
		if(!WaitForSendIdle(MAX_SEND_DELAY))
		{
			//
			// The transmission failed, so assume the host disconnected and go
			// back to waiting for a new connection.
			//
			g_bConnected = false;
		}
	}


	g_eCustomHidState = CUSTOMHID_STATE_SENDING;
	ulRetcode = USBDHIDCustomHidStateChange((void *)&g_sCustomHidDevice, 2, DeviceReport); //0 is passed for reportID since this custom device doesn't have multiple reports
	//ulRetcode = USBDHIDCustomHidStateChange((void *)&g_sCustomHidDevice, 3, MouseReport); //0 is passed for reportID since this custom device doesn't have multiple reports

	// Did we schedule the report for transmission?
	//

	if(ulRetcode == CUSTOMHID_SUCCESS)
	{
		// Wait for the host to acknowledge the transmission if all went well.
		//
		if(!WaitForSendIdle(MAX_SEND_DELAY))
		{
			//
			// The transmission failed, so assume the host disconnected and go
			// back to waiting for a new connection.
			//
			g_bConnected = false;
		}
	}

	g_eCustomHidState = CUSTOMHID_STATE_SENDING;
	ulRetcode = USBDHIDCustomHidStateChange((void *)&g_sCustomHidDevice, 3, MouseReport); //0 is passed for reportID since this custom device doesn't have multiple reports

	// Did we schedule the report for transmission?
	//

	if(ulRetcode == CUSTOMHID_SUCCESS)
	{
		// Wait for the host to acknowledge the transmission if all went well.
		//
		if(!WaitForSendIdle(MAX_SEND_DELAY))
		{
			//
			// The transmission failed, so assume the host disconnected and go
			// back to waiting for a new connection.
			//
			g_bConnected = false;
		}
	}
}

Share this post


Link to post
Share on other sites

Awesome, now that I think I can work with:)  I'll end up making some modifications to that to suit my purposes, but it gives me somewhere to start.

Share this post


Link to post
Share on other sites

Quick question, do I need to increase the number of interfaces in the CustomHidDescriptor section when using multiple reports?

Share this post


Link to post
Share on other sites

Important to note the example above is fairly simple and not very efficient (in the fact that it sends 3 different reports, one for each device in the descriptor and waits for each reply before sending the next one) in reality once you understand what the code is doing you probably want to read all your buttons/joysticks then create one larger report that includes the identifier and info for each HID device and send that all at once in one USBDHIDCustomHidStateChange call.  

Share this post


Link to post
Share on other sites

Ok, makes sense.  I'm starting to get an idea of what I'm doing.  Quite the change from Arduino and AVR C.

 

Also, and this will probably sound stupid, but I haven't been able to figure it out.  I found the prefix table in the migration document, along with the C99 variable types, which made the Hungarian notation that TI uses make a lot more sense.  However what does the "&g_" prefix in front of some of the variables indicate (ie. "&g_sCustomHidDevice")?  The "s" if I'm not mistaken indicates it is a structure variable?

Share this post


Link to post
Share on other sites

I am using a 80 mhz tiva tm4c123 in energia. I want to use the QEI velocity register by loading the QEI LOAD register. There must be a system clock clocking this register. What is the frequency to the qei timer? (what register determines the frequency this module)?

Share this post


Link to post
Share on other sites

Hi,

 

i'm trying tu use a qei too, but i always get errors like

error: 'GPIO_PD6_PHA0' was not declared in this scope

also with the code-example above...

 

can someone tell me what i'am doing wrong? i think GPIO_PD6_PHA0 is declared in pin_map.h ... 

 

i am using EK-TM4C123GXL

Share this post


Link to post
Share on other sites

ok if i use

GPIOPinConfigure(0x00031806);  // 0x00031806 =>GPIO_PD6_PHA0
GPIOPinConfigure(0x00031C06);  // 0x00031C06 => GPIO_PD7_PHB0

it works...

But is there any possibility to use automapping?

Share this post


Link to post
Share on other sites

I have played with QEI. I have two quadrature encoders with index. But where they are? FR4133 was close . And I have made the simple emulator of encoder with index.
I have connected FR4133 with Tiva. P1.3->PD3+PE1 (INDEX&Interrupt) ; P1.4->PD7 (PhB) ; P1.5->PD6 (PhA)
The code from here  and from http://forum.43oh.com/topic/8875-problems-using-qei-with-ek-tm4c123gxl/ was used for Tiva program.  
 

#include <stdint.h>
#include <stdbool.h>

#include "inc/hw_gpio.h"
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"

#include "driverlib/gpio.h"
#define PART_TM4C123GH6PM
#include "driverlib/pin_map.h"
#include "driverlib/qei.h"
#include "driverlib/sysctl.h"
#define I PE_1 

volatile uint32_t pos1;
volatile uint32_t  pos_index ;    

volatile uint32_t vel;
 volatile int32_t dir;
void config_QEI()
{
         
  
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
        
        //Unlock GPIOD7
  //      HWREG(GPIO_PORTD_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY;
  //      HWREG(GPIO_PORTD_BASE + GPIO_O_CR) |= 0x80;
  //      HWREG(GPIO_PORTD_BASE + GPIO_O_AFSEL) &= ~0x80;   
  //      HWREG(GPIO_PORTD_BASE + GPIO_O_DEN) |= 0x80;
  //      HWREG(GPIO_PORTD_BASE + GPIO_O_LOCK) = 0;

        
        // Enable QEI Peripherals
        SysCtlPeripheralEnable(SYSCTL_PERIPH_QEI0);
        
        //Set Pins to be PHA0 and PHB0
	GPIOPinConfigure(GPIO_PD6_PHA0); //GPIOPinConfigure(0x00031806);  //0x00031806 =>GPIO_PD6_PHA0
	GPIOPinConfigure(GPIO_PD7_PHB0); //GPIOPinConfigure(0x00031C06);  // 0x00031C06 => GPIO_PD7_PHB0
        GPIOPinConfigure(GPIO_PD3_IDX0); //  // GPIO_PD3_IDX0
        //Set GPIO pins for QEI
	GPIOPinTypeQEI(GPIO_PORTD_BASE, (GPIO_PIN_3 |GPIO_PIN_6 | GPIO_PIN_7 ));
//        GPIOPinTypeQEI(GPIO_PORTD_BASE, (GPIO_PIN_6 | GPIO_PIN_7 ));
        //HWREG(GPIO_PORTD_BASE + GPIO_O_LOCK) = GPIO_LOCK_M; 
         
        // Configure quadrature encoder, use an arbitrary top limit of 1000-1 and enable QEI 
	QEIConfigure(QEI0_BASE,(QEI_CONFIG_CAPTURE_A_B | QEI_CONFIG_RESET_IDX | QEI_CONFIG_QUADRATURE | QEI_CONFIG_NO_SWAP), 999);
        
        QEIEnable(QEI0_BASE);
        
        //Set position to a middle value
	QEIPositionSet(QEI0_BASE,500);

        //Configure and enable velocity
	QEIVelocityConfigure(QEI0_BASE, QEI_VELDIV_1, SysCtlClockGet()/2); // Divide by clock speed to get counts/sec
	QEIVelocityEnable(QEI0_BASE);

}
void setup()
{
  config_QEI();
  pinMode(I, INPUT);
  Serial.begin(9600);
  Serial.println("Start:");
  Serial.println("--------");
attachInterrupt(I, blink, RISING ); 
}

void loop()
{
  
  // detachInterrupt(I) ;
 
  Serial.print("pos_index="); Serial.println(pos_index);
  Serial.print("pos1="); Serial.println(pos1);
  Serial.print("vel=");Serial.println(vel);
  Serial.print("dir=");Serial.println(dir);
  Serial.println("--------");
  delay(1000);
  
}
void blink()
{
  vel = QEIVelocityGet(QEI0_BASE);
  dir = QEIDirectionGet(QEI0_BASE);
  pos_index = QEIPositionGet(QEI0_BASE);
  delayMicroseconds(400); 
   pos1 =QEIPositionGet(QEI0_BASE); 
}

This is  the code of emulator
 


#include <msp430.h>
 #include "LCD_Launchpad.h"
 #define M P2_6
  #define M1 P1_2
  #define IDX P1_3
   #define PA P1_4
    #define PB P1_5
 
  
 
 int t=500 ;
  int n=0 ;
 LCD_LAUNCHPAD myLCD ; 

void setup()
{
  // put your setup code here, to run once:
 myLCD.init();
  pinMode(M, INPUT_PULLUP);
  pinMode(M1, INPUT_PULLUP);
   pinMode(IDX, OUTPUT);
  pinMode(PA, OUTPUT);
   pinMode(PB, OUTPUT);
   digitalWrite(PA, LOW);
   digitalWrite(PB, LOW);
   digitalWrite(IDX, LOW);
  Serial.begin(9600) ; 
   myLCD.clear(); 
   Serial.println(100); 
}

void loop()
{
  // put your main code here, to run repeatedly:
  n=n+1 ;
digitalWrite(PB, HIGH) ;  
delayMicroseconds(t); 
if(n==1) {digitalWrite(IDX, LOW); }
digitalWrite(PA, HIGH);
delayMicroseconds(t);
digitalWrite(PB, LOW);
delayMicroseconds(t);
digitalWrite(PA, LOW);
delayMicroseconds(t);
digitalWrite(PB, LOW);
if(n==1000) {n=0 ; digitalWrite(IDX, HIGH); }

}

This is result

 

pos_index=999
pos1=4
vel=6282
dir=1
--------
pos_index=999
pos1=4
vel=6282
dir=1
--------
pos_index=999
pos1=4
vel=6282
dir=1
--------......

This is useful system for studying QIE and QIE API
 

Fmilburn likes this

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now