Use this forum to chat about hardware specific topics for the ESP8266 (peripherals, memory, clocks, JTAG, programming)

User avatar
By Atlantis
#34411 Currently I am experimenting with driving nixie display (https://en.wikipedia.org/wiki/Nixie_tube) from ESP-12.
There is not enough GPIO pins on module itself, so I am using PCF8574 expanders.
I need four lines to drive 74141 hardware driver (it activates one of ten high voltage, open collector outputs, depending on BCD value on 4-bit parallel input), and another 7 to drive multiplexing optocouplers.
There is a need to switch those outputs quite fast.

I looks like this:
1. I need to turn off current display and wait approximately 0.5ms. Otherwise "shadow" of previous digit will be visible (ghosting effect).
2. After that time, I need to load a new value to four lines, connected to 4-bit input on 74141, to connect proper cathode to GND. Then I light display again, by powering on next anode. After few ms point 1. is executed again.

It is happening so fast, that for a human eye all displays are powered on, displaying proper digits.

Currently I use software timer to do pin toggling, but time of execution of timer callback function is nowhere guaranteed. It differs slightly every time, so jitter effect is visible on the oscilloscope - every pulse differs in length. This may not be a problem, but I am looking for better solution.

On small, 8bit MCU hardware timer is perfect for that. I already implemented multiplexed displays, using Atmega microcontrollers.

Can anyone explain to me how to set up hardware timer, to be executed after passing given amount of time, ranging from few hundreds of microseconds, to few milliseconds?

I am also not quite sure about callback function - writing new value to PCF8575 require I2C_master library, which uses a lot of delays and consume some time. Should I only post request to main task in callback function, and perform all further operations there? But will it be any different from using software timer? Will jitter effect be reduced?
User avatar
By dkinzer
#34471
Atlantis wrote:Can anyone explain to me how to set up hardware timer, to be executed after passing given amount of time, ranging from few hundreds of microseconds, to few milliseconds?
You can get intervals of microsecond resolution using os_timer_arm_us() as described in the SDK documentation. The basic idea is that you specify a callback function and then arm the timer. The callback function will be invoked not sooner than the specified time (but it could be later). There may be (probably will be) jitter with this setup, meaning that the invocations won't necessarily be evenly spaced.

Another technique is to configure RTC Timer1 to generate an interrupt at a desired rate. The resolution with this technique is 200nS (using the divide-by-1 prescaler). The code below shows how to do this. I haven't been able to get the NMI capability to work but Espressif support folk ensure me that it works. If you use the regular interrupt (i.e. *not* NMI) there may be some jitter. Even if you do use NMI there may still be jitter but I would expect it to be less.
Code: Select all// definitions for RTC Timer1
#define TIMER1_DIVIDE_BY_1              0x0000
#define TIMER1_DIVIDE_BY_16             0x0004
#define TIMER1_DIVIDE_BY_256            0x0008

#define TIMER1_AUTO_LOAD                0x0040
#define TIMER1_ENABLE_TIMER             0x0080
#define TIMER1_FLAGS_MASK               0x00cc

#define TIMER1_NMI                      0x8000

#define TIMER1_COUNT_MASK               0x007fffff        // 23 bit timer

void ICACHE_FLASH_ATTR
timer1Start(uint32_t ticks, uint16_t flags, void (*handler)(void))
{
    RTC_REG_WRITE(FRC1_LOAD_ADDRESS, ticks & TIMER1_COUNT_MASK);
    RTC_REG_WRITE(FRC1_CTRL_ADDRESS, (flags & TIMER1_FLAGS_MASK) | TIMER1_ENABLE_TIMER);
    RTC_CLR_REG_MASK(FRC1_INT_ADDRESS, FRC1_INT_CLR_MASK);
    if (handler != NULL)
    {
        if (flags & TIMER1_NMI)
            ETS_FRC_TIMER1_NMI_INTR_ATTACH(handler);
        else
            ETS_FRC_TIMER1_INTR_ATTACH((void (*)(void *))handler, NULL);
        TM1_EDGE_INT_ENABLE();
        ETS_FRC1_INTR_ENABLE();
    }
}

void ICACHE_FLASH_ATTR
timer1Stop(void)
{
    ETS_FRC1_INTR_DISABLE();
    TM1_EDGE_INT_DISABLE();
    RTC_REG_WRITE(FRC1_CTRL_ADDRESS, 0);
}

It is important to know that RTC Timer1 is also used by the PWM routines - these two uses are mutually exclusive.
User avatar
By Atlantis
#34484 I've already used hw_timer library, to do similar think. It is also using interrupt of the same timer.
Of course I can't write to PCF8574s directly in the interrupt handling function, because I2C uses delays. I must redirect this to main task, so there is still slight jitter visible.
Because I need to execute this action in non regular intervals of time, I set timer as non-repetive, to be reinitiated every time. Is it guaranteed, that it will continue forever? For example is execution of timer interrupt or system task is guaranteed, or may be "skipped" somehow?
User avatar
By 3pei
#62506
dkinzer wrote:
Atlantis wrote:Can anyone explain to me how to set up hardware timer, to be executed after passing given amount of time, ranging from few hundreds of microseconds, to few milliseconds?
You can get intervals of microsecond resolution using os_timer_arm_us() as described in the SDK documentation. The basic idea is that you specify a callback function and then arm the timer. The callback function will be invoked not sooner than the specified time (but it could be later). There may be (probably will be) jitter with this setup, meaning that the invocations won't necessarily be evenly spaced.

Another technique is to configure RTC Timer1 to generate an interrupt at a desired rate. The resolution with this technique is 200nS (using the divide-by-1 prescaler). The code below shows how to do this. I haven't been able to get the NMI capability to work but Espressif support folk ensure me that it works. If you use the regular interrupt (i.e. *not* NMI) there may be some jitter. Even if you do use NMI there may still be jitter but I would expect it to be less.
Code: Select all// definitions for RTC Timer1
#define TIMER1_DIVIDE_BY_1              0x0000
#define TIMER1_DIVIDE_BY_16             0x0004
#define TIMER1_DIVIDE_BY_256            0x0008

#define TIMER1_AUTO_LOAD                0x0040
#define TIMER1_ENABLE_TIMER             0x0080
#define TIMER1_FLAGS_MASK               0x00cc

#define TIMER1_NMI                      0x8000

#define TIMER1_COUNT_MASK               0x007fffff        // 23 bit timer

void ICACHE_FLASH_ATTR
timer1Start(uint32_t ticks, uint16_t flags, void (*handler)(void))
{
    RTC_REG_WRITE(FRC1_LOAD_ADDRESS, ticks & TIMER1_COUNT_MASK);
    RTC_REG_WRITE(FRC1_CTRL_ADDRESS, (flags & TIMER1_FLAGS_MASK) | TIMER1_ENABLE_TIMER);
    RTC_CLR_REG_MASK(FRC1_INT_ADDRESS, FRC1_INT_CLR_MASK);
    if (handler != NULL)
    {
        if (flags & TIMER1_NMI)
            ETS_FRC_TIMER1_NMI_INTR_ATTACH(handler);
        else
            ETS_FRC_TIMER1_INTR_ATTACH((void (*)(void *))handler, NULL);
        TM1_EDGE_INT_ENABLE();
        ETS_FRC1_INTR_ENABLE();
    }
}

void ICACHE_FLASH_ATTR
timer1Stop(void)
{
    ETS_FRC1_INTR_DISABLE();
    TM1_EDGE_INT_DISABLE();
    RTC_REG_WRITE(FRC1_CTRL_ADDRESS, 0);
}

It is important to know that RTC Timer1 is also used by the PWM routines - these two uses are mutually exclusive.


Can anybody help to confirm that we can get the 200nS resolution?

I did the same thing, but when I set the interval less than 10us, the interrupt handler is not repeated as it has been set, or some calls are missed. The handler code doesn't have much thing todo, it just have some simple calculation and a GPIO ouput. I have tried DIVIDED_BY_1 but it makes nothing different.