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.

 

Example:

 

1. Initialize the XCL module and configure to output a one-shot pulse:
xcl_enable(XCL_SYNCHRONOUS);
xcl_port(PD);
xcl_tc_type(TC16);
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;
PORTD.PIN2CTRL = PORT_INVEN_bm | PORT_OPC_TOTEM_gc | PORT_ISC_BOTHEDGES_gc;

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
    xcl_disable_oc0();
    // Start at CNT
    xcl_tc16_set_count(_wait_ms + _pulse_len_ms);
    // Pulse begins at CMP and ends at BOT
    xcl_tc16_set_compare(_pulse_len_ms);
    // Start timer
    xcl_enable_oc0();
    xcl_tc_restart(RESTART_TIMER0);
}

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!