Friday, March 13, 2015

XMEGA 32E5: One-shot pulse using the XCL module

As the XCL (short for XMEGA Custom Logic) module found in the new XMEGA E series is a completely new peripheral not found in any previous Atmel chips, documentation and example code is fairly scarce. After taking the plunge and attempting to implement a one-shot pulse as described in AT01084 (Section 4.8.4), I found that the latest Atmel Software Framework (v3.22.0) has forgotten this particular XCL timer/counter operating mode! Perhaps it just isn't as popular as the other modes? But it's incredibly useful if you need a simple way to send a pulse to turn on an external device such as a PC (without using software delays or interrupts in your code).

Anyway, to be more specific on the omission: ASF is missing the One-Shot PWM mode enumeration in the xcl_tc_mode_t enum type (bug report).

Here are the modes listed in AT01084 (Section 4.8):

Not a big deal - we just need to manually specify the MODE mask (0x03) when calling the xcl_tc_mode() ASF function. After that, it's smooth sailing!

In the below example, we will output a logic-low pulse with a fixed duration (after a certain optional delay) on Port D Pin 2.

A few notes before we get into it:
  • With a 2 MHz peripheral clock:
    • 16-bit XCL timer/counter allows a pulse length of up to ~33.5 seconds.
    • 8-bit XCL timer/counter allows a pulse length of up to ~131 milliseconds.
  • Pulses can be output on either PD2 and/or PD3:
    • 16-bit XCL timer/counter: the pulse can be outputted on PD2 only.
    • 8-bit XCL timer/counter: in this case, as we have two separate timers, two independent pulses with different timing characteristics can be outputted on PD2 and PD3.




1. Initialize the XCL module and configure to output a one-shot pulse:
xcl_tc_mode(0x03);  // MODE[1:0] = 2b'11 : One-shot PWM mode
xcl_tc_source_clock(DIV1024); // Starts timer

2. Set OC0 (PD2) as an output pin and also set inverted so we get a low pulse:
PORTD.DIR = 0x04;

3. Function to generate the one-shot pulse:
void pulse(uint16_t wait_ms, uint16_t pulse_len_ms)
    uint16_t _wait_ms = wait_ms / ((1024.0 / F_CPU) * 1000);
    uint16_t _pulse_len_ms = pulse_len_ms / ((1024.0 / F_CPU) * 1000);
    // Stop any currently in progress pulses
    // Start at CNT
    xcl_tc16_set_count(_wait_ms + _pulse_len_ms);
    // Pulse begins at CMP and ends at BOT
    // Start timer

4. Now we simply call the above function with our desired parameters:
pulse(500, 1000); // Delay 500 ms then output 1000 ms pulse on OC0

5. Voila! No interrupts or software delays needed! Easy!