Just another blog

+menu-


  • Category Archives Beaglebone
  • 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;
    	...
    }