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 motor controllers 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 control board and the goals of the application running on the board are to:

  • Read the motor position by decoding the IRC sensor signals. The signals from the IRC sensor are two phase-shifted signals A and B and an IRQ signal. The IRQ signal is generated by the motor and is asserted whenever either of the A/B signals changes.

    You should write an interrupt handler and calculate the position change from changes in A and B signals.

  • Generate the PWM signal to rotate the motor. Use hardware timers dedicated to PWM generation.

  • 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 21, 2017 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   HW-independent hints

3.1.1   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.1.2   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.2   MZAPO board (ARM) hints

3.2.2   FPGA registers

For now, we provide just a quick overview of hardware registers that you can use to control the motors. These registers are implemented in the FPGA, which is a part of the Zynq chip. To control the motor connected to PMOD1 connector write or read the registers described below. The address of the register is the base address 0x43c20000 plus the offset of the register.

Base physical address of the first dcsimpledrv instance  0x43c20000
Base physical address of the second dcsimpledrv instance 0x43c30000

Control Register (CR) [RW]
offset  0x0000

bit  8  IRC_RESET - IRC counter reset
        when 1, bits 31..2 of IRC are fixed on 0
bit  6  PWM_ENABLE - PWM Generator Enable
        when 0, PWM outputs are controlled
                by bits PWM_A_DIRECT and PWM_B_DIRECT
        when 1, PWM is controlled by PWM generator
bit  5  PWM_B_DIRECT - Direct Control of PWM A Output
bit  4  PWM_A_DIRECT - Direct Control of PWM B Output

Status Register (SR) [RO]
offset  0x0004

bit 10  IRC_IRQ_MON - monitors external IRQ signal
bit  9  IRC_B_MON - actual value of IRC input B
bit  8  IRC_A_MON - actual value of IRC input A

PWM Period Setup Register (PWM_PERIOD) [RW]
offset  0x0008

bit 29..0 define period of PWM generated waveform
        basic unit is 10 ns, clock 100 MHz
        maximal possible period to set is about 10 sec
        register can be rewritten asynchronously
        even when PWM is enabled, if the new period
        is shorter than actual ongoing one then period
        is shortened immediately

PWM Duty Control Register (PWM_DUTY) [RW]
offset  0x000C

bit 31  DUTY_DIR_B - request negative polarity of output voltage
        when 1 then output PWM B is asserted on start
               of next PWM period and is hold
               active for clock count defined by DUTY field
        when 0 then PWM B output is negated immediately
bit 30  DUTY_DIR_A - request positive polarity of output voltage
        when 1 then output PWM A is asserted on start
               of next PWM period and is hold
               active for clock count defined by DUTY field
        when 0 then PWM A output is negated immediately
bit 29..0 DUTY - number of 100 Mhz clock intervals to hold output active
        if set during ongoing period before previous DUTY limit
        resets outputs then new value is applied immediately

3.2.3   IRC sensor IRQ handling

To decode the IRC signal reliably, it is necessary to handle the IRQs generated by motor. For that, one needs not only to use the registers described above, but also configure GPIO interrupts. The code below shows how to connect an interrupt service routine (irc_isr) to the hardware IRQ generated by motor hardware. It uses some definitions from xlnx_zynq7k.h header file, which is a part of BSP. 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/xlnx_zynq7k
#include <taskLib.h>
#include <stdio.h>
#include <kernelLib.h>
#include <semLib.h>
#include <intLib.h>
#include <iv.h>

#include <xlnx_zynq7k.h>

SEM_ID irc_sem;
volatile int irc_a, irc_b;

void irc_print_status(void)
{
        while (1) {
                semTake(irc_sem, WAIT_FOREVER);
                printf("a: %d, b: %d\n", irc_a, irc_b);
        }
}

void irc_isr(void)
{
        int sr; /* status register */
        sr = *(volatile uint32_t *) (0x43c20000 + 0x0004);
        irc_a = (sr & 0x100) >> 8;
        irc_b = (sr & 0x200) >> 9;
        semGive(irc_sem);
        *(volatile uint32_t *) (ZYNQ7K_GPIO_BASE + 0x00000298) = 0x4; /* reset (stat) */
}

/*
 *  Enable IRQ
 *
 *  See TRM, 14.2.4 Interrupt Function (pg. 391, pg. 1348). Technical reference
 *  manual link is on rtime HW wiki: https://rtime.felk.cvut.cz/hw/index.php/Zynq
 */
void irc_init(void)
{
        *(volatile uint32_t *) (ZYNQ7K_GPIO_BASE + 0x00000298) = 0x4; /* reset (stat) */
        *(volatile uint32_t *) (ZYNQ7K_GPIO_BASE + 0x00000284) = 0x0; /* set as input (dirm) */
        *(volatile uint32_t *) (ZYNQ7K_GPIO_BASE + 0x0000029c) = 0x4; /* rising edge (type) */
        *(volatile uint32_t *) (ZYNQ7K_GPIO_BASE + 0x000002a0) = 0x0; /* rising edge (polarity) */
        *(volatile uint32_t *) (ZYNQ7K_GPIO_BASE + 0x000002a4) = 0x0; /* rising edge (any) */
        *(volatile uint32_t *) (ZYNQ7K_GPIO_BASE + 0x00000290) = 0x4; /* enable interrupt (en) GPIO2 */

        intConnect(INUM_TO_IVEC(INT_LVL_GPIO), irc_isr, 0);
        intEnable(INT_LVL_GPIO);
}

void irc_disable(void)
{
        *(volatile uint32_t *) (ZYNQ7K_GPIO_BASE + 0x00000294) = 0x4; /* disable interrupt (dis) */

        intDisable(INT_LVL_GPIO);
        intDisconnect(INUM_TO_IVEC(INT_LVL_GPIO), irc_isr, 0);
}

/*
 * Entry point for DKM.
 */
void motor(void)
{
        TASK_ID st;

        irc_init();
        irc_sem = semCCreate(SEM_Q_FIFO, 0);
        st = taskSpawn("irc_st", 100, 0, 4096, (FUNCPTR) irc_print_status, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
        printf("All is ready.\n");

        taskDelay(1000);
        printf("Out of play time.\n");

        irc_disable();
        taskDelete(st);
}

3.3   Midam board (PowerPC) hints

3.3.1   Resources

3.3.2   How to generate PWM on MPC5200

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.

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.3.3   How to handle IRC interrupts

This two phase signal is connected to CPU pins TIMER_4 and TIMER_5 (see RYU_EDU_midam v1.B schematics). These pins should be configured as inputs.

#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.3.4   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.4   Web server

3.4.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.4.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.4.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.4.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.5   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.