1   Assignment

The goal of the semestral work is to create a digital motor controller. Your program will control the position of the motor according to the set-point given by the position of another motor, moved by hand (steer-by-wire). The set-point will be transferred between the two motors using UDP messages. The actual state of the controller and its history will be published as live graphs over the HTTP protocol.

image0

1.1   Version Control

Work in couples and use a version control system. We expect commits from both of you in the repository. You can use any open source version control software. We manage GIT repositories that you can use, but it is possible to use any other repository (and version control system) which we can access.

1.2   More Details

The motor is connected to the MPC5200B-based board and the goals of the control application are to:

  • Read the motor position by decoding the IRC sensor signal. This two phase signal is connected to CPU signals TIMER_4 and TIMER_5 (see RYU_EDU_midam v1.B schematics) that should be configured as inputs. The motor contains a circuit for detecting changes in the IRC signals and asserts IRQ0 interrupt signal on every change.

  • Generate the PWM signal to rotate the motor. Power stage inputs are connected to the CPU outputs of TIMER_1 and TIMER_2. The corresponding hardware timers in the CPU can be configured to generate the PWM signal.

  • Implement PID controller to control the shaft position.

  • Implement UDP communication from one board to another. Use standard BSD sockets API.

  • The controller should contain a web-server, which serves live graphs of at least:

    • actual motor position,
    • requested position and
    • current PWM duty cycle.

    The graphs should show at least 2 seconds of history.

There is a more detailed description of the motor, its connection to PC and basic implementation hints. The document deals with motor control under RT Linux (different RTOS than VxWorks), but the general ideas presented there can be helpful even for VxWorks.

2   Assessment

Semestral work assessment comprises the following independently assessed parts:

2.1   Motor control program (max. 18 points)

Item Points
IRC reading through interrupts 1*
Hardware generated PWM at 20 kHz 1*
P(ID) or better controller (no ad-hoc solution) 1*
Quality of regulation (no oscillations, fast response, minimal steady state error, ...) 0 – 2
Set-point specification (choose one of the following): *
— From second motor through UDP communication 6
— From terminal via keyboard 1
Controller "debugging" (choose one of the following): *
— Web page with live graphs 7

— ASCII "bargraph" of action value (PWM width). Something like:

-0.5 .....=====|..........
1

Rows marked by * represent the minimum for "zápočet".

2.2   Use of version control system (max. 4 points)

You must use a distributed version control system. Git is strongly recommended. See the instructions for using Git on our server. There will be two check dates:

  1. December 14, 2016 and
  2. when submitting the work.

We will evaluate the items from the table below.

Item (evaluation date) Points
At least one commit every week (1,2) 1
Commit messages accurately describe the content of the commit (2) 1
No generated files (e.g. .o) are added in the repository (2) 1

It is required that both persons from the team have commits in the repository.

2.3   Programmer's documentation (max. 3 points)

Use tools that generate the documentation automatically from the source code (e.g. Doxygen). Documentation should contain:

Item Points
Short textual description of the application in the introduction (or on the main HTML page). 0.5
Instructions for compiling and running your application. 0.5
Screenshot of your web-based (or text-based) user interface 0.5
Data-Flow Diagram of your application i.e. how individual components (e.g. IRQ handler, controller, web-server) exchange data. Note: You need to draw this picture yourself; Doxygen cannot generate it automatically. 1
Description of global functions and variables (created by you) including the description of function parameters and return values. 0.5

Look at example documentation created with Doxygen and the corresponding source code. Text-based part of the documentation is contained in file CANR_16X.H.

3   Hints

3.1   Resources

3.2   Things to avoid

(you should know why from lectures)

  • Calling blocking functions (e.g. printf()) in interrupt handlers.
  • Using floating point operations in interrupt handlers.

3.3   Working with floating point numbers

You must include the VX_FP_TASK option when creating a task that does any of the following:

  • Performs floating-point operations.
  • Calls any function that returns a floating-point value.
  • Calls any function that takes a floating-point value as an argument.

For example:

tid = taskSpawn ("tMyTask", 90, VX_FP_TASK, 20000, myFunc, 2387, 0, 0, 0, 0, 0, 0, 0, 0, 0);

Some routines perform floating-point operations internally. The VxWorks documentation for each of these routines clearly states the need to use the VX_FP_TASK option.

3.4   How to generate PWM on MPC5200

PWM generation is controlled by setting GPT (General Purpose Timers) processor registers. The addresses and symbolic names of GPT registers are defined in/opt/WindRiver/vxworks-6.9/target/h/arch/ppc/ppc5200.h and /opt/WindRiver/vxworks-6.9/target/config/lite5200b. In ppc5200.h, you can find a section with useful macros that simplify the work with GPT registers.

To successfully include this file, it is necessary to modify search path in project properties "Build Properties->Build Paths". Add there a line containing: -I$(WIND_BASE)/target/config/lite5200b. Then include the following lines in your source code:

#include "vxWorks.h"
#include <arch/ppc/ppc5200.h>
#include <lite5200b.h>

For PWM generation, the following registers are important: CIR PWMCR and EMSR. The upper word of CIR register contains prescaler (clock multiplier), which should be set to 0x1. The rest of the register indicates the length of the entire PWM period. The value to write there can be calculated by dividing the processor bus frequency (132 MHz) with desired PWM frequency (20 kHz). The upper word of PWMCR register contains PWM pulse duration in the same units as the PWM period in CIR register. Beware that specifying longer pulse width than the period means that the timer output will not change! Bit 23 in the lower word is used to set PWM polarity (1 - an active width of the PWM output is zero, 0 - otherwise). Finally, writing 0x3 to EMSR register will start generation of PWM signal.

The above procedure must be repeated for both PWM channels. The connection between motor inputs and CPU timer channels can be seen in RYU_EDU board schematics. You can also take some inspiration from the Linux driver mentioned above.

Example code:

#include <intLib.h>
#include <iv.h>
#include <lite5200b.h>
#include <arch/ppc/ppc5200.h>

#define MPC52xx_GPT_MODE_DISABLED 0
#define MPC52xx_GPT_MODE_INCAPT   1
#define MPC52xx_GPT_MODE_OUTCMP   2
#define MPC52xx_GPT_MODE_PWM      3
#define MPC52xx_GPT_MODE_GPIO     4

#define MPC52xx_GPT_MODE_GPIO_IN   (0<<4)
#define MPC52xx_GPT_MODE_GPIO_OUT0 (2<<4)
#define MPC52xx_GPT_MODE_GPIO_OUT1 (1<<4)

#define MPC52xx_GPT_STATUS_PIN    (1<<8)
#define MPC52xx_GPT_PWM_OP        (1<<8) /* PWM polarity */


#define PWMF_TIMER_NUM            2
#define PWMB_TIMER_NUM        3
#define PWM_PERIOD        6600         /* = 132 000 kHz / 20 kHz  */


void pwm_width(int width)
{
    if (width >= 0) {
        if(width > PWM_PERIOD) width = PWM_PERIOD;
        *GPT_PWMCR(PWMF_TIMER_NUM) = (width<<16) | MPC52xx_GPT_PWM_OP;
        *GPT_PWMCR(PWMB_TIMER_NUM) = (0)         | MPC52xx_GPT_PWM_OP;
    }
    else {
        width = -width;
        if(width > PWM_PERIOD) width = PWM_PERIOD;
        *GPT_PWMCR(PWMF_TIMER_NUM) = (0)            | MPC52xx_GPT_PWM_OP;
        *GPT_PWMCR(PWMB_TIMER_NUM) = (width<<16) | MPC52xx_GPT_PWM_OP;
    }
}

void init_pwm()
{
    pwm_width(0);

    *GPT_CIR(PWMF_TIMER_NUM) = (1<<16) | PWM_PERIOD;
    *GPT_CIR(PWMB_TIMER_NUM) = (1<<16) | PWM_PERIOD;

    *GPT_EMSR(PWMF_TIMER_NUM) = MPC52xx_GPT_MODE_PWM;
    *GPT_EMSR(PWMB_TIMER_NUM) = MPC52xx_GPT_MODE_PWM;
}

void disable_pwm()
{
    *GPT_EMSR(PWMF_TIMER_NUM) = 0;
    *GPT_EMSR(PWMB_TIMER_NUM) = 0;
}

3.5   How to handle IRC interrupts

#include <intLib.h>
#include <iv.h>
#include <lite5200b.h>
#include <arch/ppc/ppc5200.h>

#define MPC52xx_GPT_MODE_DISABLED 0
#define MPC52xx_GPT_MODE_INCAPT   1
#define MPC52xx_GPT_MODE_OUTCMP   2
#define MPC52xx_GPT_MODE_PWM      3
#define MPC52xx_GPT_MODE_GPIO     4

#define MPC52xx_GPT_MODE_GPIO_IN   (0<<4)
#define MPC52xx_GPT_MODE_GPIO_OUT0 (2<<4)
#define MPC52xx_GPT_MODE_GPIO_OUT1 (1<<4)

#define MPC52xx_GPT_STATUS_PIN    (1<<8)
#define MPC52xx_GPT_PWM_OP        (1<<8) /* PWM polarity */

#define IRCA_TIMER_NUM                  4
#define IRCB_TIMER_NUM                  5

void irc_irq_handler(int val)
{
   unsigned int stateA;
   unsigned int stateB;

   stateA = (*GPT_SR(IRCA_TIMER_NUM) & MPC52xx_GPT_STATUS_PIN) != 0;
   stateB = (*GPT_SR(IRCB_TIMER_NUM) & MPC52xx_GPT_STATUS_PIN) != 0;

   m5200IntAck(INUM_IRQ0);
}

void irc_init()
{
   // Initialize GPIOs for IRC input
   *GPT_EMSR(IRCA_TIMER_NUM) = MPC52xx_GPT_MODE_GPIO | MPC52xx_GPT_MODE_GPIO_IN;
   *GPT_EMSR(IRCB_TIMER_NUM) = MPC52xx_GPT_MODE_GPIO | MPC52xx_GPT_MODE_GPIO_IN;

   // Configure interrupt controller: Rising edge triggers interrupt
   *ICTL_EEETR |= ICTL_EEETR_ETYPE0_RISING_EDGE;

   intConnect(INUM_TO_IVEC(INUM_IRQ0),  irc_irq_handler, 0);

   // Enable interrupt
   intEnable(INUM_IRQ0);
}

void irc_disable()
{
   *GPT_EMSR(IRCA_TIMER_NUM) = 0;
   *GPT_EMSR(IRCB_TIMER_NUM) = 0;

   intDisable(INUM_IRQ0);
   intDisconnect(INUM_TO_IVEC(INUM_IRQ0),  irc_irq_handler, 0);
}

Warning

Don't forget to declare global variables used both from interrupt and task context as volatile!

3.6   Use of on-board LEDs

To control the on-board LEDs, it is necessary to set four registers. Their definition and useful macros can be found again in ppc5200.h header file (section GPIO). More detailed description can be found in MPC5200B User's manual starting from section 7.3.2.1. Since the PSC3 port, where are the LEDs connected to, can be multiplexed between GPIO, UART and SPI it is first necessary to configure it as GPIO by zeroing bits 20-23 in PCR register. Furthermore, we have to enable them by settings the same bits in SEN register. This made the pins PSC3.0 through PSC3.3 available to us. By setting additional bits, we can also enable PSC3.6 and PSC3.7 if needed. What remains is to set GPIO as outputs by settings bits 20-23 in SDD register. The voltage on the pins is controlled by setting/clearing the corresponding bits in SDO register.

The code below implements the above procedure.

// PSC3 = GPIO
*GPIO_PCR &= 0xFFFFF0FF;
// Enable PSC3.0 to PSC3.3
*GPIO_SEN |= 0x00000F00;
// PSC3 -> OUT_MODE
*GPIO_SDD |= 0x00000F00;
// PSC3 OUT 0 - switch on all LEDs
*GPIO_SDO = 0x00000F00;

3.7   Web server

3.7.1   Skeleton of a simple web server

Example of communication between WWW browser and server

Web server skeleton:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <sockLib.h>
#include <string.h>

#define SERVER_PORT     80 /* Port 80 is reserved for HTTP protocol */
#define SERVER_MAX_CONNECTIONS  20

void www(int argc, char *argv[])
{
  int s;
  int newFd;
  struct sockaddr_in serverAddr;
  struct sockaddr_in clientAddr;
  int sockAddrSize;

  sockAddrSize = sizeof(struct sockaddr_in);
  bzero((char *) &serverAddr, sizeof(struct sockaddr_in));
  serverAddr.sin_family = AF_INET;
  serverAddr.sin_port = htons(SERVER_PORT);
  serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

  s=socket(AF_INET, SOCK_STREAM, 0);
  if (s<0)
  {
    printf("Error: www: socket(%d)\n", s);
    return;
  }


  if (bind(s, (struct sockaddr *) &serverAddr, sockAddrSize) == ERROR)
  {
    printf("Error: www: bind\n");
    return;
  }

  if (listen(s, SERVER_MAX_CONNECTIONS) == ERROR)
  {
    perror("www listen");
    close(s);
    return;
  }

  printf("www server running\n");

  while(1)
  {
    /* accept waits for somebody to connect and the returns a new file descriptor */
    if ((newFd = accept(s, (struct sockaddr *) &clientAddr, &sockAddrSize)) == ERROR)
    {
      perror("www accept");
      close(s);
      return;
    }

    /* The client connected from IP address inet_ntoa(clientAddr.sin_addr)
       and port ntohs(clientAddr.sin_port).

       Start a new task for each request. The task will parse the request
       and sends back the response.

       Don't forget to close newFd at the end */
  }
}

3.7.2   Using fdopen() to generate server responses

The easiest way to generate webserver responses is to use fdopen() function to "convert" the file descriptor returned by accept() to a FILE pointer. Then you can use all standard libc functions such as printf() to generate the response. For example:

FILE *f = fdopen(newFd, "w");
fprintf(f, "HTTP/1.0 200 OK\r\n\r\n");
fprintf(f, "Current time is %ld.\n", time(NULL));
fclose(f);

3.7.3   How to refresh a web page periodically?

Use javascript code in the onload attribute of the body element as in the example below. This example reloads the page approximately every 100 milliseconds and shows actual time in milliseconds.

<html>
  <head>
    <title>Test</title>
  </head>
  <body onload="setTimeout(function(){location.reload()}, 100);">
    <script>document.write(Date.now());</script>
  </body>
</html>

3.7.4   Drawing graphs as SVG

Look at this example for how to include SVG graphics in the html page.

Direct link to the SVG source

3.8   FAQ

Q: Why do not I receive any characters from keyboard in my program?

A: VxWorks shell "eats up" entered characters. Type exit to quit the shell.