Using the quadrature encoder interface on the Tiva C Launchpad

Quadrature encoders are used for measuring motor rotational speed and direction. They can be optical or magnetic just like the one below. This quadrature encoder has two Hall effect sensors which allow a program or quadrature encoder interface measure motor speed and direction.The figures above show the output from the two Hall effect sensors when the motor is running forwards and backwards. Notice how H1 has a falling edge when H2 is high on the left plot and it has a falling edge when H2 is high in the second plot. This allows a quadrature encoder interface to determine the motor direction.To measure motor speed, you need to measure pulse frequency and divide by the number of pulses produced during each cycle. The black disk in the photo above has a number of magnets embedded within it.The outputs from the Hall Effect sensors can be connected to the PhA and PhB inputs of the Tiva C on Port C (PC5,PC6).Code to initialize Quadrature Encoder Interface 1 on the Tiva C is shown below

void initQEI(void)
{

// Quadrature encoder is connected to PC5 and PC6

// These correspond to PhaseA1 and PhaseB1

SYSCTL_RCGCQEI |= (1 << 1); // enable the clock for QEI1

SYSCTL_RCGCGPIO |= (1 << 2); // enable GPIOC

SYSCTL_GPIOHBCTL |= (1 << 2); // enable AHB access to GPIOC

GPIOC_DEN |= (1 << 5) + (1 << 6); // digital mode for bits 5 and 6 of GPIOC

GPIOC_AFSEL |= (1 << 5) + (1 << 6); // alternate function mode for bits 5 and 6

GPIOC_PCTL &= ~((0x0f << 20) + (0x0f << 24)); // zero out pin control value for bits 5 and 6

GPIOC_PCTL |= ((6 << 20) + (6 << 24)); // zero out pin control value for bits 5 and 6

QEI1_CTL = 0x00000020;

QEI1_LOAD = 8000000; // This sets the timing window to 1 second when system clock is 16MHz


QEI1_MAXPOS = 1000;

QEI1_CTL |= 1;

}


The motor velocity can then be read as follows:

int getQEIVelocity()

{

int speed = QEI1_SPEED;

int direction = QEI1_STAT >> 1;

if (direction)

speed = -speed;

return speed;

}

This post was pug together on a mobile phone with a poor Internet connection. It will definitely need editing later 🙂

Revisiting the TM4C123

I have had reason to work with the TIVA C launchpad recently. It’s been around a while and can still be found in various retailers. I had never explored the dedicated PWM system on the device which can generate 16 outputs. Three of these are connected to the onboard RGB LEDs so you can vary brightness of them individually. The configuration is pretty straightforward as shown below:

void initPWM()
{
    SYSCTL_RCGCPWM |= (1 << 1); // turn on PWM1
    SYSCTL_RCGC2 = SYSCTL_RCGC2 | (1 << 5); // turn on GPIOF
    SYSCTL_GPIOHBCTL = SYSCTL_GPIOHBCTL | (1 << 5); // turn on AHB access to GPIOF

    // Will drive the LED's using PWM
    // PF1 -> pin 29 -> Red  -> M1PWM5 Module 1, PWM Gen 2, GPIOPCTL value = 5
    // PF2 -> pin 30 -> Blue -> M1PWM6 Module 1, PWM Gen 3, GPIOPCTL value = 5
    // PF3 -> pin 31 -> Green-> M1PWM7 Module 1, PWM Gen 3, GPIOPCTL value = 5
    GPIOF_AFSEL |= (1<<3) | (1 << 2) | (1 << 1); // select alternative function for GPIOF1,2,3
    GPIOF_DEN = GPIOF_DEN | ( (1 << 3) | (1 << 2 ) | (1 << 1) ); // digital mode bits 1,2,3 of GPIOF
    GPIOF_PCTL = (5 << 12) | (5 << 8) | (5 << 4);

    PWM1_PWMENABLE |= (1 << 7)| (1 << 6) | (1 << 5);
    PWM1_PWM2LOAD = 50000000/25000; // 25kHz assuming system clock is 50MHz
    PWM1_PWM3LOAD = 50000000/25000; // 25kHz assuming system clock is 50MHz
    // PWM counter will count down.  When it reaches 0 the output is set to zero
    // when it counts down to the value in the CMPA or CMPB register the output is
    // driven high.  This means that the duty is proportional to the value in CMPA or CMPB
    PWM1_PWM2GENB = (2 << 0) + (3 << 10); // Drive high on match, low on zero (gen b)
    PWM1_PWM3GENA = (2 << 0) + (3 << 6); // Drive high on match, low on zero (gen a)
    PWM1_PWM3GENB = (2 << 0) + (3 << 10); // Drive high on match, low on zero (gen b)
    PWM1_PWM2CMPB = 0;
    PWM1_PWM3CMPA = 0;
    PWM1_PWM3CMPB = 0;
    PWM1_PWM2CTL |= (1 << 0); // enable pwm block
    PWM1_PWM3CTL |= (1 << 0); // enable pwm block
    PWM1_PWMSYNC = 0x0f; // synchronize all counters
}
void setRed(uint32_t Percent)
{
    Percent = (Percent * PWM1_PWM2LOAD)/100;
    PWM1_PWM2CMPB = Percent;
}
void setBlue(uint32_t Percent)
{
    Percent = (Percent * PWM1_PWM3LOAD)/100;
    PWM1_PWM3CMPA = Percent;
}
void setGreen(uint32_t Percent)
{
    Percent = (Percent * PWM1_PWM3LOAD)/100;
    PWM1_PWM3CMPB = Percent;
}

The helper functions setRed,setBlue,setGreen take a single argument which represents the percentage duty. This cuts down resolution but is fine for our purposes. (The maximum possible resolution in this example is 1 in 2000 which is the reload value used by the PWM generator blocks). Full code is available over here on github. Code was developed using Code Composer Studio from Texas Instruments.

Multi-threading on the Tiva C Launchpad

Threads and processes

A process is a running program. Multitasking operating systems (e.g Linux, Windows etc.) run a number of processes simultaneously. Each process has a global (or static) memory area, a stack and code. Processes in multitasking OS’s are protected from one another using a hardware based memory management unit. A Scheduler allocates CPU time to each process. The simplest scheduler is a “round-robin” scheduler which allows each process run for a short time before switching to the next allowing each process a turn on the CPU.

multitasking

Threads are similar to processes in some ways however they share the same global/static data as well as the same code but have separate stacks.

threads1

Threads can be scheduled just like processes and so appear to operate in parallel – this is multi-threading.

threads2

Context switching

Each process or thread switch involves a context change: the current processor state (all of its register contents) must be saved and the processor state for the next thread or process loaded.  The image below illustrates a context change from Thread 1 to Thread 2

context_change

The context change is triggered by a timer interrupt and the ARM Cortex processors have a special timer aimed at just this role : the SysTick timer. In the following example the SysTick timer is configured to interrupt the CPU every millisecond which triggers a context change.

ARM Cortex M0 Exception handling

The following registers are placed on the interrupted thread stack (Process Stack) automatically following an interrupt (such as SysTick)

Address Contents
SP Prior to interrupt ????????
SP + 0x0000001C xPSR
SP + 0x00000018 PC
SP + 0x00000014 LR
SP + 0x00000010 R12
SP + 0x0000000C R3
SP + 0x00000008 R2
SP + 0x00000004 R1
SP + 0x00000000 R0

Why not save all of the registers? It is too slow (your ISR may not be changing all registers).

Why just these ones? R0-R3 typically are used for argument passing and should always be preserved by ISR’s. R12 is used by some compilers in their inner function call glue. The LR may hold a function return address. PC must be remembered so we know where to go back to and xPSR must be remembered for the flags.

For a full context switch, the remaining registers must be placed on the Process Stack also.

Address Contents
SP Prior to interrupt ????????
SP + 0x0000001C xPSR
SP + 0x00000018 PC
SP + 0x00000014 LR
SP + 0x00000010 R12
SP + 0x0000000C R3
SP + 0x00000008 R2
SP + 0x00000004 R1
SP + 0x00000000 R0
SP – 0x00000004 R11
SP – 0x00000008 R10
SP – 0x0000000C R9
SP – 0x00000010 R8
SP – 0x00000014 R7
SP – 0x00000018 R6
SP – 0x0000001C R5
SP – 0x00000020 R4

It is not possible carry this out in the C language so a little inline assembler is needed here to complete the context change.


// Preserve remaining registers on stack of thread that is being suspended (Thread A)
asm(" cpsid i "); // disable interrupts during thread switch
asm(" MRS R0,PSP "); // get Thread A stack pointer
asm(" SUB R0,#32"); // Make room for the other registers : R4-R11 = 8 x 4 = 32 bytes
asm(" STMIA R0! , { R4-R7 } "); // Can only do a multiple store on registers up to R7
asm(" MOV R4,R8 "); // Copy higher registers to lower ones
asm(" MOV R5,R9 ");
asm(" MOV R6,R10 ");
asm(" MOV R7,R11 ");
asm(" STMIA R0! , { R4-R7 } "); // and repeat the multiple register store
// Locate the Thread Control Block (TCB) for Thread A
asm(" LDR R0,=TCB_Size "); // get the size of each TCB
asm(" LDR R0,[R0] ");
asm(" LDR R1,=ThreadIndex "); // Which one is being used right now?
asm(" LDR R1,[R1] ");
asm(" MUL R1,R0,R1 "); // Calculate offset of Thread A TCB from start of TCB array
asm(" LDR R0,=Threads "); // point to start of TCB array
asm(" ADD R1,R0,R1 "); // add offset to get pointer to Thread A TCB
asm(" MRS R0,PSP "); // get Thread A stack pointer
// Save Thread A's stack pointer (adjusted for new registers being pushed
asm(" SUB R0,#32 "); // Adjust for the other registers : R4-R11 = 8 x 4 = 32 bytes
asm(" STR R0,[R1] "); // Save Thread A Stack pointer to the TCB (first entry = Saved stack pointer)

// Update the ThreadIndex
ThreadIndex++;
if (ThreadIndex >= ThreadCount)
  ThreadIndex = 0;

// Locate the Thread Control Block (TCB) for Thread B
asm(" LDR R0,=TCB_Size "); // get the size of each TCB
asm(" LDR R0,[R0] ");
asm(" LDR R1,=ThreadIndex "); // Which one is being used right now?
asm(" LDR R1,[R1] ");
asm(" MUL R1,R0,R1 "); // Calculate offset of Thread A TCB from start of TCB array
asm(" LDR R0,=Threads "); // point to start of TCB array
asm(" ADD R1,R0,R1 "); // add offset to get pointer to Thread B TCB
asm(" LDR R0,[R1] "); // read saved Thread B Stack pointer
asm(" ADD R0,#16 "); // Skip past saved low registers for the moment
asm(" LDMIA R0!,{R4-R7} "); // read saved registers
asm(" MOV R8,R4 "); // Copy higher registers to lower ones
asm(" MOV R9,R5 ");
asm(" MOV R10,R6 ");
asm(" MOV R11,R7 ");
asm(" LDR R0,[R1] "); // read saved Thread B Stack pointer
asm(" LDMIA R0!,{R4-R7} "); // read saved LOW registers
asm(" LDR R0,[R1] "); // read saved Thread B Stack pointer
asm(" ADD R0,#32 "); // re-adjust saved stack pointer
asm(" MSR PSP,R0 "); // write Thread B stack pointer

Threads are managed using a structure called a Thread Control Block which is defined as follows:


typedef struct {
uint32_t *ThreadStack;
void (*ThreadFn )();
uint32_t Attributes;
} ThreadControlBlock;

Implementation

A demonstrator application with three threads was developed for the Tiva C Launchpad.  Each thread flashes an LED on the board at a different rate.  The trickiest part to get right was the initial launching of the thread switching which involved a little bit of stack fiddling.  Code is available over here on Github