Absolutely!!! ---well, within reason! I haven't had occasion to run this kind of process on Kinetis yet, but have done so 'many times' on S08, and the timer-peripheral is (for better or worse) 'very similar'.
Firstly, you run the FTM's channels in 'output compare' mode, table 45-3 in K26 RM, '01 01' -- for this generation, 'toggle output' is probably sufficient here; for other waveform generation you may want to force 'one' or 'zero' on the next time-compare.
Let's say you run FTM at 10MHz, with MOD of course at 0xFFFF (152.6Hz overflow rate) --- said FTM frequency is a tradeoff between frequency-resolution and lowest-possible frequency being >= 1/2 overflow rate. And let's further posit that you want (at this time) to make 2400Hz and 2800Hz. That makes for 4800 edges and 5600 edges, otherwise defined then as 2083 'counts' between edges for 2400.4Hz, 1786 for 2799.5Hz. So then you have (in-psuedo-code) an ISR like this:
FTM0_IRQHandler()
{
if(channel 0 interrupt)
clear interrupt; ////FTM0_C0SC &= ~FTM_CnSC_CHF_MASK;
FTM0_C0V = FTM0_C0V+2083;
if(channel 1 interrupt)
clear interrupt;
FTM0_C1V = FTM0_C1V+1786;
}
Your time-counts would surely be 'pre-calculated unsigned-16bit variables' somewhere, not the shown constants, but that is the idea. It MIGHT be faster to have a uint16_t static-var that keeps the running-timer-count for each channel, like FTM_ch0_next_count += Freq0_count; then just write that to FTM0_C0V, as the process to fetch the peripheral-register is slower than RAM-access (but causes a second global-address-load-instruction and two back-to-back write-ops, so maybe NOT faster??? Interesting...).
This interrupt MUST be allowed to be serviced faster than your lowest half-period, else the 'time for the next event' will already have 'passed' when you get here, meaning this 'simple math' will delay the next edge one whole FTM overflow. This is easiest to achieve if said FTM channel is the highest-level interrupt, and never disabled. See also my priority-comments in:
https://community.nxp.com/message/872684?commentID=872684#comment-872684
As for overhead, we might guess this IRQ to take a microsecond or so, so for our combined 10,400 edges/s we take ~1% of the CPU for these two examples.
FWIW, this is an S08 interrupt-handler for full timer-banged-UART-waveform generation (of a fixed-size 8-byte message):
#define BAUDRATE (1000000/38400) //uS per bit, assume 8MHz Busclk and /8 prescale
/* TPMC0SC: CH1F=0,CH1IE=1,MS0B=0,MS0A=1,ELS0B=1,ELS0A=1,??=0,??=0--to set a 'one' */
#define MARK 0xD8 ////SC values for INVERTED UART waveform (mark=0V, space=Vdd)
#define SPACE 0xDC
static uint8_t serbyte; //Byte currently going out serial stream
static uint8_t ser_bit_cnt; //Bit count within said byte
static uint8_t ser_byte_cnt; //Count within total stream
enum SIOState {SIO_Idle, SIO_SendStrt, SIO_SendByte,
SIO_SendStop, SIO_WaitIdle} SIO_State=SIO_Idle;
ISR (Tim1_OutComp)
{
(void)TPMC1SC;
/* TPMC0SC: CH0F=0 */
clrReg8Bits(TPMC1SC, 0x80); /* Reset compare interrupt request flag */
if( SIO_State != SIO_Idle )
{
setReg16(TPMC1V, TPMC1V+BAUDRATE); /* 'Default' Compare 1 value for next bit */
if( SIO_State == SIO_SendByte )
{
if( (serbyte & 0x01) == 0x01 ) //Set up for next bit
setReg8(TPMC1SC, MARK);
else
setReg8(TPMC1SC, SPACE);
serbyte >>= 1;
ser_bit_cnt--;
if( ser_bit_cnt == 0 )
{
SIO_State = SIO_SendStop;
setReg8(TPMC1SC, MARK);
}
}else if( SIO_State == SIO_SendStop )
{
if( ser_byte_cnt != 8 )
{
SIO_State = SIO_SendByte;
setReg8(TPMC1SC, SPACE);
ser_byte_cnt++;
serbyte = ValidData[ser_byte_cnt];
ser_bit_cnt = 9;
}else
{
SIO_State = SIO_WaitIdle;
setReg16(TPMC1V, TPMCNT+1000); //Wait 1ms from now for 'Idle'
}
}else if( SIO_State == SIO_WaitIdle )
{
if( ser_byte_cnt != 8 )
SIO_State = SIO_SendStop; //Start up bit stream next bit time
else
{
SIO_State = SIO_Idle;
KBISC_KBIE = 1; //Start looking for input edges
PTAD_PTAD4 = 0; //Re-enable debug port serial
}
}
}else
{
setReg16(TPMC1V, TPMCNT+127); //Wait another while of 'nothing'
}
}