Just another blog

+menu-


ECAP PWM capture in Linux

I have added LV-Maxsonar-EZ2 (ultrasound proximity sensor) support. Because Maxsonar’s RS232 output is not actual UART (signal is inverted), thus Beagle’s UART cannot be used without additional circuits, I have decided to use Maxsonar’s PWM output. But there is no support for ECAP PWM capture in Linux kernel.

So, I had to use mmap. There are some minor issues like I do not read SYSCLK (L3_FREQ) so I do not know accurate time of ON and OFF cycle but because PWM period does not change and I am interested in ratios only I do not need it. Here are some pieces of source code to make ECAP running, I hope it helps someone:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h>
#include <errno.h>
#include <cstdint>

// register address
static const size_t ECAP0_BASE_ADDRESS = 0x48300100;
static const size_t ECAP2_BASE_ADDRESS = 0x48304100;

// block size
static const size_t EACP_MEM_SIZE = 0x0180;

// register offsets
static const unsigned int ECAP_TSCTR = 0x00;
static const unsigned int ECAP_CTRPHS = 0x04;
static const unsigned int ECAP_CAP1 = 0x08;
static const unsigned int ECAP_CAP2 = 0x0C;
static const unsigned int ECAP_CAP3 = 0x10;
static const unsigned int ECAP_CAP4 = 0x14;
static const unsigned int ECAP_ECCTL1 = 0x28;
static const unsigned int ECAP_ECCTL2 = 0x2A;
static const unsigned int ECAP_ECEINT = 0x2C;
static const unsigned int ECAP_ECFLG = 0x2E;
static const unsigned int ECAP_ECCLR = 0x30;
static const unsigned int ECAP_ECFRC = 0x32;
static const unsigned int ECAP_REVID = 0x5C;

// CTR overflow flag (to detect PWM recepion stopped)
static const unsigned int CTR_OVF_MASK = 0x0020;

// from ARM Reference Manual
#define EC_RISING 0x0
#define EC_FALLING 0x1

#define EC_ABS_MODE 0x0
#define EC_DELTA_MODE 0x1

#define EC_BYPASS 0x0
#define EC_DIV1 0x0
#define EC_DIV2 0x1
#define EC_DIV4 0x2
#define EC_DIV6 0x3
#define EC_DIV8 0x4
#define EC_DIV10 0x5

#define EC_CONTINUOUS 0x0
#define EC_ONESHOT 0x1

#define EC_EVENT1 0x0
#define EC_EVENT2 0x1
#define EC_EVENT3 0x2
#define EC_EVENT4 0x3

#define EC_ARM 0x1

#define EC_FREEZE 0x0
#define EC_RUN 0x1

#define EC_SYNCIN 0x0
#define EC_CTR_PRD 0x1
#define EC_SYNCO_DIS 0x2

#define EC_CAP_MODE 0x0
#define EC_APWM_MODE 0x1

#define EC_ACTV_HI 0x0
#define EC_ACTV_LO 0x1

#define EC_DISABLE 0x0
#define EC_ENABLE 0x1
#define EC_FORCE 0x1

// bit positions in registers
#define ECCTL1_CAP1POL 0
#define ECCTL1_CAP2POL 2
#define ECCTL1_CAP3POL 4
#define ECCTL1_CAP4POL 6
#define ECCTL1_CTRRST1 1
#define ECCTL1_CTRRST2 3
#define ECCTL1_CTRRST3 5
#define ECCTL1_CTRRST4 7
#define ECCTL1_CAPLDEN 8
#define ECCTL1_PRESCAL 9
#define ECCTL1_FREESFT 14

#define ECCTL2_CONTONE 0
#define ECCTL2_STOPWRP 1
#define ECCTL2_REARM   3
#define ECCTL2_TSCSTOP 4
#define ECCTL2_SYNCIEN 5
#define ECCTL2_SYNCOSEL 6
#define ECCTL2_SWSYNC  8
#define ECCTL2_CAPAPWM 9
#define ECCTL2_APWMPOL 10

...

/*
1. Set ECAP mux (not shown here)
*/

...

/*
2. Open /dev/mem and map register addresses
*/

int fd = open("/dev/mem", O_RDWR);

// set ECAP2
long pageSize = sysconf(_SC_PAGE_SIZE);
// get page base address
size_t baseAddr = ECAP2_BASE_ADDRESS / (size_t)pageSize;
baseAddr *= (size_t)pageSize;
size_t addrOffset = ECAP2_BASE_ADDRESS - baseAddr;
// map
void *gpio_addr = mmap(0, EACP_MEM_SIZE + addrOffset, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseAddr);

// map was successful
if (gpio_addr != MAP_FAILED)
{
	gpio_addr = (void *)((unsigned char *)gpio_addr + m_addrOffset);
}

...

/*
3. set ECAP module to continuesly detect PWM on ECAP1 and ECAP2 registers
*/

*(uint16_t *)((unsigned char *)gpio_addr + ECAP_ECCTL1) = 
	(EC_FALLING << ECCTL1_CAP1POL) | (EC_RISING << ECCTL1_CAP2POL) | (EC_FALLING << ECCTL1_CAP3POL) | (EC_RISING << ECCTL1_CAP4POL) | 
	(EC_DELTA_MODE << ECCTL1_CTRRST1) | (EC_DELTA_MODE << ECCTL1_CTRRST2) | (EC_DELTA_MODE << ECCTL1_CTRRST3) | (EC_DELTA_MODE << ECCTL1_CTRRST4) | 
	(EC_ENABLE << ECCTL1_CAPLDEN) | (EC_DIV1 << ECCTL1_PRESCAL) | (3 << ECCTL1_FREESFT);
*(uint16_t *)((unsigned char *)gpio_addr + ECAP_ECCTL2) = 
	(EC_CAP_MODE << ECCTL2_CAPAPWM) | (EC_CONTINUOUS << ECCTL2_CONTONE) | (EC_SYNCO_DIS << ECCTL2_SYNCOSEL) | (EC_DISABLE << ECCTL2_SYNCIEN) | 
	(EC_RUN << ECCTL2_TSCSTOP) | (1 << ECCTL2_STOPWRP);

...

/*
4. any time read ECAP1 and ECAP2 to get ON and OFF duty
*/

uint32_t T1 = *(uint32_t *)((unsigned char *)gpio_addr + ECAP_CAP1);    // on1
uint32_t T2 = *(uint32_t *)((unsigned char *)gpio_addr + ECAP_CAP2);    // off1

...

/*
5. detect overflow (optional)
*/

if (((*(uint16_t *)(addrChar + ECAP_ECFLG)) & CTR_OVF_MASK) != 0)
{
	*(uint16_t *)(addrChar + ECAP_ECCLR) |= CTR_OVF_MASK;
	...
}

2 Responses to ECAP PWM capture in Linux

  1. Attila Fodor says:

    Hi,

    Did you actually get it to work this way? I’ve loaded up the correct DT layer, and tried to write a test program using your code, but I haven’t been successful (T1 and T2 registers store the value of 273 both).
    I’m not a low-level guy but I need to measure servo signals, but I just can’t do it. I neither want to use PRUs, nor compiling a kernel, I’m not a software engineer 🙂
    Please get back to me, I’d really appreciate it.

    • Hi,
      Yes I made ECAP working (LV-Maxsonar-EZ2 is useless due to vibrations, but this is another story). Do not forget you have to set ECAP pins first. The code only shows how to read ECAP values. You can go to download section (http://www.honzinovo.cz/projects/heli/download) and download the zip file.