RTEMS on BeagleBone Black Wireless – Part 3

At the end of part 2 I was finally ready to start work on the eQEP driver for the Beaglebone Black BSP for RTEMS. As I’d mentioned previously in part 1, the BSP has been contributed to by a number of authors and it is lacking a little consistency. All the register address and bitmask #defines have ended up being dumped in the am335x.h header which makes finding things a bit of a chore. It also means that header is full of registers and bitmasks that will only ever be used by a single driver, so they don’t all need to be in a common header.

With these things in mind, when I started writing the eQEP driver, I decided I would try and keep the general API style consistent with what has already been developed in the BSP, but also try and tidy things up a bit and be a bit more consistent with the RTEMS coding conventions.

The PWM module had a function to initialise the L3 and L4_PER system clocks for a given PWMSS module, however this is something that needs to be done by any of the PWMSS functions – ePWM, eQEP or eCAP. Therefore I extracted that function and the BBB_PWMSS enum type into a new pwmss.h header and source file. I also took the opportunity to significantly simplify the implementation of the pwmss_module_clk_config function.

Next I defined my eQEP interface. It has ended up looking like the following (at the time of writing this blog post):

/**
 * @file
 *
 * @ingroup arm_beagle
 *
 * @brief eQEP (enhanced Quadrature Encoder Pulse) support API.
 */

/**
 * Copyright (c) 2020 James Fitzsimons <james.fitzsimons@gmail.com>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 *
 * For details of the Enhanced Quadrature Encoder Pulse (eQEP) Module refer to
 * page 2511 of the TI Technical Reference Manual
 * (https://www.ti.com/lit/ug/spruh73q/spruh73q.pdf)
 * This driver supports using the QEP modules in Quadrature-clock Mode.
 * Direction-count Mode is not currently supported. Similarly the QEPI: Index
 * or Zero Marker and QEPS: Strobe Input pins are not currently supported.
 */

#ifndef LIBBSP_ARM_BEAGLE_QEP_H
#define LIBBSP_ARM_BEAGLE_QEP_H

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#define AM335X_EQEP_REGS                       (0x00000180)
#define AM335X_EQEP_0_REGS                     (AM335X_PWMSS0_MMAP_ADDR + AM335X_EQEP_REGS)
#define AM335X_EQEP_1_REGS                     (AM335X_PWMSS1_MMAP_ADDR + AM335X_EQEP_REGS)
#define AM335X_EQEP_2_REGS                     (AM335X_PWMSS2_MMAP_ADDR + AM335X_EQEP_REGS)

/* eQEP registers of the PWMSS modules - see page 1672 of the TRM for details */
#define AM335x_EQEP_QPOSCNT       0x0   /* eQEP Position Counter */
#define AM335x_EQEP_QPOSINIT      0x4   /* eQEP Position Counter Initialization */
#define AM335x_EQEP_QPOSMAX       0x8   /* eQEP Maximum Position Count */
#define AM335x_EQEP_QPOSCMP       0xC   /* eQEP Position-Compare */
#define AM335x_EQEP_QPOSILAT      0x10  /* eQEP Index Position Latch */
#define AM335x_EQEP_QPOSSLAT      0x14  /* eQEP Strobe Position Latch */
#define AM335x_EQEP_QPOSLAT       0x18  /* eQEP Position Counter Latch */
#define AM335x_EQEP_QUTMR         0x1C  /* eQEP Unit Timer */
#define AM335x_EQEP_QUPRD         0x20  /* eQEP Unit Period */
#define AM335x_EQEP_QWDTMR        0x24  /* eQEP Watchdog Timer */
#define AM335x_EQEP_QWDPRD        0x26  /* eQEP Watchdog Period */
#define AM335x_EQEP_QDECCTL       0x28  /* eQEP Decoder Control */
#define AM335x_EQEP_QEPCTL        0x2A  /* eQEP Control */
#define AM335x_EQEP_QCAPCTL       0x2C  /* eQEP Capture Control */
#define AM335x_EQEP_QPOSCTL       0x2E  /* eQEP Position-Compare Control */
#define AM335x_EQEP_QEINT         0x30  /* eQEP Interrupt Enable */
#define AM335x_EQEP_QFLG          0x32  /* eQEP Interrupt Flag */
#define AM335x_EQEP_QCLR          0x34  /* eQEP Interrupt Clear */
#define AM335x_EQEP_QFRC          0x36  /* eQEP Interrupt Force */
#define AM335x_EQEP_QEPSTS        0x38  /* eQEP Status */
#define AM335x_EQEP_QCTMR         0x3A  /* eQEP Capture Timer */
#define AM335x_EQEP_QCPRD         0x3C  /* eQEP Capture Period */
#define AM335x_EQEP_QCTMRLAT      0x3E  /* eQEP Capture Timer Latch */
#define AM335x_EQEP_QCPRDLAT      0x40  /* eQEP Capture Period Latch */
#define AM335x_EQEP_REVID         0x5C  /* eQEP Revision ID */
/* bitmasks for eQEP registers  */
#define AM335x_EQEP_QEPCTL_UTE    (1 << 1)
#define AM335x_EQEP_QEPCTL_QCLM   (1 << 2)
#define AM335x_EQEP_QEPCTL_PHEN   (1 << 3)
#define AM335x_EQEP_QEPCTL_IEL    (1 << 4)
#define AM335x_EQEP_QEPCTL_SWI    (1 << 7)
#define AM335x_EQEP_QDECCTL_QSRC  (3 << 14)
#define AM335x_EQEP_CLK_EN        (1 << 4)
#define AM335x_EQEP_QEINT_UTO     (1 << 11)

/**
 * @brief The set of possible eQEP Position Counter Input Modes
 *
 * Enumerated type to define various modes for the eQEP module.
 */
typedef enum {
  QUADRATURE_COUNT = 0,
  DIRECTION_COUNT,
  UP_COUNT,
  DOWN_COUNT
} BBB_QEP_COUNT_MODE;

/**
 * @brief The set of possible modes for Quadrature decode
 *
 */
typedef enum {
  ABSOLUTE = 0,
  RELATIVE
} BBB_QEP_QUADRATURE_MODE;

/**
 * @brief The set of possible eQEP input pins
 *
 */
typedef enum {
  BBB_P8_11_2B_IN,
  BBB_P8_12_2A_IN,
  BBB_P8_15_2_STROBE,
  BBB_P8_16_2_IDX,
  BBB_P8_31_1_IDX,
  BBB_P8_32_1_STROBE,
  BBB_P8_33_1B_IN,
  BBB_P8_35_1A_IN,
  BBB_P8_39_2_IDX,
  BBB_P8_40_2_STROBE,
  BBB_P8_41_2A_IN,
  BBB_P8_42_2B_IN,
  BBB_P9_25_0_STROBE,
  BBB_P9_27_0B_IN,
  BBB_P9_41_0_IDX,
  BBB_P9_42_0A_IN
} bbb_qep_pin_t;


typedef struct {
  BBB_PWMSS pwmss_id;
  BBB_QEP_COUNT_MODE count_mode;
  BBB_QEP_QUADRATURE_MODE quadrature_mode;
} bbb_eqep_t;


/* The pin mux modes for the QEP input pins on the P8 and P9 headers */
#define BBB_P8_11_MUX_QEP 4
#define BBB_P8_12_MUX_QEP 4
#define BBB_P8_15_MUX_QEP 4
#define BBB_P8_16_MUX_QEP 4
#define BBB_P8_31_MUX_QEP 2
#define BBB_P8_32_MUX_QEP 2
#define BBB_P8_33_MUX_QEP 2
#define BBB_P8_35_MUX_QEP 2
#define BBB_P8_39_MUX_QEP 3
#define BBB_P8_40_MUX_QEP 3
#define BBB_P8_41_MUX_QEP 3
#define BBB_P8_42_MUX_QEP 3
#define BBB_P9_25_MUX_QEP 1
#define BBB_P9_27_MUX_QEP 1
#define BBB_P9_41_MUX_QEP 1
#define BBB_P9_42_MUX_QEP 1


/**
 * @brief This function intilizes clock for pwm sub system.
 *
 * @param PWMSS_ID It is the instance number of EPWM of pwm sub system.
 *
 * @return true if successful
 * @return false if not successful
 *
 **/

// Need to configure the CLKCONFIG register
// 5 eQEPCLKSTOP_REQ R/W 0h This bit controls the clkstop_req input to the eQEP module
// 4 eQEPCLK_EN R/W 1h This bit controls the clk_en input to the eQEP module.

// Need to read the CLKSTATUS register
// 5 eQEP_CLKSTOP_ACK R 0h This bit is the clkstop_req_ack status output of the eQEP module.
// 4 eQEP_CLK_EN_ACK R 0h This bit is the clk_en status output of the eQEP module.
rtems_status_code beagle_qep_init(bbb_eqep_t* eqep);

rtems_status_code beagle_qep_enable(BBB_PWMSS pwmss_id);

rtems_status_code beagle_qep_disable(BBB_PWMSS pwmss_id);

rtems_status_code beagle_qep_pinmux_setup(bbb_qep_pin_t pin_no, BBB_PWMSS pwm_id);

int32_t beagle_qep_get_position(BBB_PWMSS pwmss_id);

rtems_status_code beagle_qep_set_position(BBB_PWMSS pwmss_id);

rtems_status_code beagle_qep_set_count_mode(BBB_PWMSS pwmss_id, BBB_QEP_COUNT_MODE mode);

BBB_QEP_COUNT_MODE beagle_qep_get_count_mode(BBB_PWMSS pwmss_id);

uint8_t beagle_qep_get_mode(BBB_PWMSS pwmss_id);

rtems_status_code beagle_qep_set_mode(BBB_PWMSS pwmss_id, uint8_t mode);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* LIBBSP_ARM_BEAGLE_QEP_H */

The only real snag I hit during implementation was after I had finished writing all the initialisation code and my test app and it didn’t work. I had a rotatory encoder hooked up to the BBBW and had confirmed using Linux that the hardware worked as expected.

Strangely my RTEMS implementation refused to change the count value, and after running through the code several times double checking each register address and bitmask I decided to double check with Linux again. This time I also hooked my oscilloscope so I could see the pulses being generated by the encoder.

Booting back into the RTEMS code I didn’t see the encoder pulses being generated on the oscilloscope, and it didn’t take me too long to realise why. The FDT overlay that sets up the pinmux for the Linux driver enables the internal pullup resistors on the A & B input pins for the eQEP. The Linux implementation was working because the BBBW was pulling the pins up to 3.3v and the encoder was pulling them down to GND each pulse. The RTEMS implementation had put the pins in the right mux mode, but hadn’t enabled the internal pullups so the input pins were sitting at GND and no amount of turning the encoder was going to generate a pulse.

Once I’d fixed the pin configuration code it all started working! Here is a pic of the test setup and a screen shot of the terminal output to prove it.

Now that I have the basic driver working I need to tidy up the code, implement support for some of the other features and then submit a patch.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s