8. Orangutan Pulse/PWM Inputs

This section of the library makes it easy to measure pulse inputs such as hobby servo RC pulses, PWM frequency and duty cycle, discharge time of a capacitor (measured digitally), etc. For example, with these functions, it becomes simple to program your Orangutan to respond to multiple channels from an RC receiver or to read data from the multitude of sensors that use pulse outputs. The pulses can be connected to any digital input, and there is no limit to the number of pulse inputs you can have (beyond the obvious I/O line and RAM limitations; each pulse channel requires 17 bytes of RAM). These functions are not available within the Arduino environment, which has its own pulse-reading functions.

The pulse-measuring occurs in the background and is entirely interrupt driven, requiring no further action from you once you have initiated it by calling pulse_in_start(). Required memory is allocated using malloc() when pulse-measuring is initiated, which conserves RAM. The pulse_in_stop() function frees this dynamically allocated memory. Once measuring is started, you can use these functions to get information about the most recently received high and low pulse, as well as the current pulse in progress. The system tick counter from OrangutanTime is used to time the pulses, which means pulse width measurements have a resolution of 0.4 µs, and the upper limit on pulse width is approximately 28 minutes.

This section of the library uses the AVR’s pin-change interrupts: PCINT0, PCINT1, and PCINT2 (and PCINT3 on the Orangutan SVP and X2). It will conflict with any other code that uses pin-change interrupts (e.g. OrangutanWheelEncoders).

Pulse Information Variables

The OrangutanPulseIn code uses an array of 17-byte PulseInputStruct structs to keek track of the pulse data; each element of the array represents one pulse input channel. Struct data is automatically updated by the pin-change interrupt service routine (ISR) as pulses are received.

#define LOW_PULSE   1
#define HIGH_PULSE  2
#define ANY_PULSE   3

struct PulseInputStruct
{
  volatile unsigned char* pinRegister;
  unsigned char bitmask;
  volatile unsigned long lastPCTime;
  volatile unsigned char inputState;
  volatile unsigned long lastHighPulse;
  volatile unsigned long lastLowPulse;
  volatile unsigned char newPulse;
};

The bottom five struct variables are declared “volatile” because their values are changed by the pin-changed interrupt service routine (ISR), so we require the compiler to load them from RAM every time they are referenced. If a variable is not declared “volatile”, the compiler will eliminate what it decides are superfluous reads of a variable from RAM and instead work with that variable in local registers, which saves time and code space, but this can cause situations where you fail to detect when an ISR has changed that variable’s value.

  • pinRegister & bitmask : Used by the pin-change ISR to determine the input state of the channel. You should not use these two struct variables in your programs because they might be removed in future versions of the library.
  • lastPCTime : The system tick counter value when the last state change happened on this channel. You can use (get_ticks()-lastPCTime) to figure out how long ago (in units of 0.4 µs) the last pulse ended.
  • inputState : This byte has a value of 1 (HIGH) if the channel is high, else it has a value of 0 (LOW).
  • lastHighPulse : The width of the last detected high pulse in system ticks (units of 0.4 µs). A high pulse is one that starts with a channel transition from low to high and ends with a channel transition from high to low.
  • lastLowPulse : The width of the last detected low pulse in system ticks (units of 0.4 µs). A low pulse is one that starts with a channel transition from high to low and ends with a channel transition from low to high.
  • newPulse : The bits of this byte are set whenever a new pulse occurs, which lets you tell when new data has arrived. The LOW_PULSE bit is set when a low pulse finishes, and the HIGH_PULSE bit is set when a high pulse finishes. The functions in this section clear the newPulse flags after returning them. Once set, the flags stay set until they are read. Masking newPulse with the keyword ANY_PULSE with the code (newPulse&ANY_PULSE) will be non-zero if either new-pulse bit flag is set.
  • Reference

    C++ methods are shown in red.

    C/C++ functions are shown in green.

    static unsigned char OrangutanPulseIn::start(const unsigned char pulse_pins[], unsigned char num_pins)

    unsigned char pulse_in_start(const unsigned char pulse_pins[], unsigned char num_pins)

    Configures the AVR’s pin-change interrupts to measure pulses on the specified pins. The num_pins parameter should be the length of the pulse_pins array. A nonzero return value indicates that the needed memory could not be allocated.

    The pulse_pins parameter should be the RAM address of an array of AVR I/O pin numbers defined using the IO_* keywords provided by the library (e.g. IO_D1 for a pulse input on pin PD1). This function does not configure the pulse_pins as digital inputs, which makes it possible to measure pulses on pins being used as outputs. AVR I/O pins default to digital inputs with the internal pull-up disabled after a reset or power-up.

    Example

    // configure pins PD0 and PD1 as pulse input channels
    pulse_in_start((unsigned char[]) {IO_D0, IO_D1}, 2);
    // configure pins PD0 and PD1 as pulse input channels
    OrangutanPulseIn::start((unsigned char[]) {IO_D0, IO_D1}, 2);

    static void OrangutanPulseIn::stop()

    void pulse_in_stop()

    Disables all pin-change interrupts and frees up the memory that was dynamically allocated by the pulse_in_start() function. This can be useful if you no longer want state changes of your pulse-measuring channels to interrupt program execution.

    static void OrangutanPulseIn::getPulseInfo(unsigned char channel, struct PulseInputStruct* pulse_info)

    void get_pulse_info(unsigned char channel, struct PulseInputStruct* pulse_info)

    This function uses the pulse_info pointer to return a snapshot of the pulse state for the specified channel, channel. After get_pulse_info() returns, pulse_info will point to a copy of the PulseInputStruct that is automatically maintained by the the pin-change ISR for the specified channel. Additionally, after the copy is made, this function clears the newPulse flags (both high pulse and low pulse) in the original, ISR-maintained PulseInputStruct. Since pulse_info is a copy, the pin-change ISR will never change the pulse_info data. Working with pulse_info is especially useful if you need to be sure that all of your puse data for a single channel came from the same instant in time, since pin-change interrupts are disabled while the ISR-maintained PulseInputStruct is being copied to pulse_info.

    The argument channel should be a number from 0 to one less than the total number of channels used (num_pins-1); the channel acts as an index to the pulse_pins array supplied to the pulse_in_start() function.

    See the “Pulse Information Variables” section at the top of this page for documentation of the members of the PulseInputStruct pulse_info, and see the pulsein1 sample program for an example of how to use get_pulse_info() as the basis of your pulse-reading code.

    Example

    // configure pins PD0 and PD1 as pulse input channels
    pulse_in_start((unsigned char[]) {IO_D0, IO_C3}, 2);
    
    struct PulseInputStruct pulseInfo;
    get_pulse_info(1, &pulseInfo);  // get pulse info for pin PC3
    if (pulseInfo.newPulse & HIGH_PULSE)
      someFunction(pulseInfo.lastHighPulse);
    // configure pins PD0 and PD1 as pulse input channels
    OrangutanPulseIn::start((unsigned char[]) {IO_D0, IO_C3}, 2);
    
    struct PulseInputStruct pulseInfo;
    OrangutanPulseIn::getPulseInfo(1, &pulseInfo);  // get pulse info for pin PC3
    if (pulseInfo.newPulse & HIGH_PULSE)
      someFunction(pulseInfo.lastHighPulse);

    static unsigned char OrangutanPulseIn::newPulse(unsigned char channel)

    unsigned char new_pulse(unsigned char channel)

    This function returns the newPulse flags for the specified pulse input channel and then clears them, so subsequent new_pulse() calls will return zero until the next new pulse is received. The return value of this fuction will be 0 only if no new pulses have been received. It will be non-zero if a new high or low pulse has been received. You can check the type of new pulse by looking at the HIGH_PULSE and LOW_PULSE bits of the return value.

    Example

    // check for new pulses on pulse input channel 0:
    unsigned char newPulse = new_pulse(0);
    if (newPulse)  // if there has been a new pulse of any kind
      doSomething();
    if (newPulse & HIGH_PULSE)  // if there has been a new high pulse
      doSomethingElse();
    // check for new pulses on pulse input channel 0:
    unsigned char newPulse = OrangutanPulseIn::newPulse(0);
    if (newPulse)  // if there has been a new pulse of any kind
      doSomething();
    if (newPulse & HIGH_PULSE)  // if there has been a new high pulse
      doSomethingElse();

    static unsigned char OrangutanPulseIn::newHighPulse(unsigned char channel)

    unsigned char new_high_pulse(unsigned char channel)

    This function returns the newPulse flags if there is a new high pulse on the specified pulse input channel (i.e. the return value is non-zero), else it returns zero. It also clears the HIGH_PULSE bit of the newPulse flag byte, so subsequent new_high_pulse() calls will return zero until the next new high pulse is received. The LOW_PULSE bit is not changed by this function.

    static unsigned char OrangutanPulseIn::newLowPulse(unsigned char channel)

    unsigned char new_low_pulse(unsigned char channel)

    This function returns the newPulse flags if there is a new low pulse on the specified pulse input channel (i.e. the return value is non-zero), else it returns zero. It also clears the LOW_PULSE bit of the newPulse flag byte, so subsequent new_low_pulse() calls will return zero until the next new low pulse is received. The HIGH_PULSE bit is not changed by this function.

    static unsigned long OrangutanPulseIn::getLastHighPulse(unsigned char channel)

    unsigned long get_last_high_pulse(unsigned char channel)

    This function returns the length in system ticks (units of 0.4 µs) of the last complete high pulse received on the specified pulse input channel. It gives you (interrupt-safe) access to the lastHighPulse member of the ISR-maintained PulseInputStruct for the specified channel. A high pulse is one that starts with a channel transition from low to high and ends with a channel transition from high to low. Note that if the last high “pulse” was longer than 28.6 minutes, the value returned by this function will have overflowed and the result will be incorrect. You should disregard the first high pulse after the pulse input line has been in a steady-high state for more than 28. minutes.

    static unsigned long OrangutanPulseIn::getLastLowPulse(unsigned char channel)

    unsigned long get_last_low_pulse(unsigned char channel)

    This function returns the length in system ticks (units of 0.4 µs) of the last complete low pulse received on the specified pulse input channel. It gives you (interrupt-safe) access to the lastLowPulse member of the ISR-maintained PulseInputStruct for the specified channel. A low pulse is one that starts with a channel transition from high to low and ends with a channel transition from low to high. Note that if the last low “pulse” was longer than 28.6 minutes, the value returned by this function will have overflowed and the result will be incorrect. You should disregard the first low pulse after the pulse input line has been in a steady-low state for more than 28. minutes.

    static void OrangutanPulseIn::getCurrentState(unsigned char channel, unsigned long* pulse_width, unsigned char* state)

    void get_current_pulse_state(unsigned char channel, unsigned long* pulse_width, unsigned char* state)

    This function gives you information about what is currently happening on the specified pulse input channel by using the argument pulse_width to return the number of system ticks that have elapsed since the last complete pulse ended (in units of 0.4 µs) and by using the argument state to return whether the voltage on the input channel is currently HIGH (1) or LOW (0). The pulse_width and state arguments are pointers to integer types whose values are respectively set (in an ISR-safe way) based on the lastPCTime and inputState members of the ISR-maintained PulseInputStruct for the specified channel. Specifically, *pulse_width is set to: get_ticks()-pulseInfo[channel].lastPCTime

    Example

    unsigned long pulse_width;
    unsigned char state;
    // get info for pulse input channel channel 0:
    get_current_pulse_state(0, &pulse_width, &state);
    if (pulse_width > 250000 && state == HIGH)
    {
      // if more than 100 ms have passed since last pulse ended
      // and the pulse input channel is currently high
      doSomething();
    }
    unsigned long pulse_width;
    unsigned char state;
    // get info for pulse input channel channel 0:
    OrangutanPulseIn::getCurrentState(0, &pulse_width, &state);
    if (pulse_width > 250000 && state == HIGH)
    {
      // if more than 100 ms have passed since last pulse ended
      // and the pulse input channel is currently high
      doSomething();
    }

    unsigned long OrangutanPulseIn::toMicroseconds(unsigned long pulse_width)

    unsigned long pulse_to_microseconds(unsigned long pulse_width)

    Converts the provided pulse_width argument from system ticks to microseconds (this is the same as multiplying pulse_width by 0.4 µs, but without using floating-point math) and returns the result. The same result could be achieved by calling ticks_to_microseconds(pulse_width).

    Example

    // if last high pulse was longer than 1500 microseconds
    if (pulse_to_microseconds(get_last_high_pulse(0)) > 1500)
      doSomething();
    // if last high pulse was longer than 1500 microseconds
    if (OrangutanPulseIn::toMicroseconds(OrangutanPulseIn::getLastHighPulse(0)) > 1500)
      doSomething();