Well I think I've solved it - the problem with the ISR was not the same as the problem with the polled/wait-loop routine.
That the ISR worked if I put the first case (setup & Tx start & device ID) into a normal function:
void iic_trigger_interrupt(uint8 id){ // clear the interrupt MCF_I2C0_I2SR = 0; /* setting in Tx mode */ MCF_I2C0_I2CR |= MCF_I2C_I2CR_MTX; /* send start condition */ MCF_I2C0_I2CR |= MCF_I2C_I2CR_MSTA; /* devide ID to write */ MCF_I2C0_I2DR = id;} This can be called conditionally when the I2C status flag is "idle", but is not as elegant as I was hoping - I like to keep all the code in the ISR if I can.
When I tried triggering the ISR by using the interrupt force register:
MCF_INTC0_INTFRCL |= MCF_INTC_INTFRCL_INTFRC17
It misbehaved. Since TFM doesn't mention clearing or resetting of the INTFRCx register I had not been clearing the INTFRCx register in the ISR, but adding a test & clear fixed it:
if(MCF_INTC0_INTFRCL & MCF_INTC_INTFRCL_INTFRC17){ MCF_INTC0_INTFRCL &= ~MCF_INTC_INTFRCL_INTFRC17;} Kudos to Tom's method of setting a fast timer loop to monitor states, I've been using a scope on the SDA line plus two GPIO pins to indicate status bits from the I2SR register and it really does help nail things down.
So - the answer as is so often the case is that assumption is the mother of all f***ups, with the more-common-than-it-should-be ambiguity of documentation & poor example code by Freescale. Hey ho, I'm happy to have it sorted.
For those playing along at home, this is now my very basic I2C interrupt routine in its entireity, hopefully it will help someone (if only as an example of how not to do it!):
// This could just as well be be a static var inside the ISR // for some applications... iic->state = 0; // External I2C status flag/semaphore iic->data = 0; // Where your data byte will end up__interrupt__ void i2c_handler(){ uint8 ipl = 0; uint8 port = I2C_PORT_INTERNAL; uint8 temp = 0, i = 0, address = 0, id = 0; address = REGISTER_IN_DEVICE; id = I2C_DEVICE_ID; ipl = DisableInts(); // ensure interrupts remain blocked during ISRs // If interrupt was forced, clear it if(MCF_INTC0_INTFRCL & MCF_INTC_INTFRCL_INTFRC17) { MCF_INTC0_INTFRCL &= ~MCF_INTC_INTFRCL_INTFRC17; } if(MCF_I2C0_I2SR & MCF_I2C_I2SR_IAL) // We lost control of the bus { // Arbitration lost MCF_I2C0_I2SR &= ~MCF_I2C_I2SR_IAL; // Clear it iic->state = 7; /* clear the completion transfer flag */ MCF_I2C0_I2SR &= ~MCF_I2C_I2SR_IIF; /* generates stop condition */ MCF_I2C0_I2CR &= ~MCF_I2C_I2CR_MSTA; /* wait for bus to go idle */ while((MCF_I2C0_I2SR & MCF_I2C_I2SR_IBB )) ; } // // What are we doing? // switch(iic->state) { // // Start - setup port, send start & device ID // case 1: // clear the interrupt MCF_I2C0_I2SR = 0; /* setting in Tx mode */ MCF_I2C0_I2CR |= MCF_I2C_I2CR_MTX; /* send start condition */ MCF_I2C0_I2CR |= MCF_I2C_I2CR_MSTA; /* devide ID to write */ MCF_I2C0_I2DR = id; // wait until one byte transfer completion break; // // Started, device ID sent, now send register address // case 2: /* clear the completion transfer flag */ MCF_I2C0_I2SR &= ~MCF_I2C_I2SR_IIF; // Check for ACK if(MCF_I2C0_I2SR & MCF_I2C_I2SR_RXAK) { // Not currently used } /* memory address */ MCF_I2C0_I2DR = address; // wait until one byte transfer completion break; // // Register address sent, restart in read mode // case 3: /* clear the completion transfer flag */ MCF_I2C0_I2SR &= ~MCF_I2C_I2SR_IIF; // Check for ACK if(MCF_I2C0_I2SR & MCF_I2C_I2SR_RXAK) { // Not currently used } /* Send repeated start (was "resend start") */ MCF_I2C0_I2CR |= MCF_I2C_I2CR_RSTA; /* device id to read */ MCF_I2C0_I2DR = (uint8)(id | 0x01); // wait until one byte transfer completion break; // // Send clock pulses to read data // case 4: case 104: case 204: /* clear the completion transfer flag */ MCF_I2C0_I2SR &= ~MCF_I2C_I2SR_IIF; /* setting in Rx mode */ MCF_I2C0_I2CR &= ~MCF_I2C_I2CR_MTX; /* send NO ACK */ MCF_I2C0_I2CR |= MCF_I2C_I2CR_TXAK; /* dummy read */ temp = MCF_I2C0_I2DR; break; // // Read data // case 5: case 105: case 205: /* clear the completion transfer flag */ MCF_I2C0_I2SR &= ~MCF_I2C_I2SR_IIF; /* read data received */ iic->data = MCF_I2C0_I2DR; // This will trigger another read // ...so wait until one byte transfer completion // Freescale just do it and it just works, no-one knows why... break; // // Finish // case 6: case 106: case 206: /* clear the completion transfer flag */ MCF_I2C0_I2SR &= ~MCF_I2C_I2SR_IIF; /* generates stop condition */ MCF_I2C0_I2CR &= ~MCF_I2C_I2CR_MSTA; break; case 7: /* clear the completion transfer flag */ MCF_I2C0_I2SR = 0; break; // // Default / end // case 0: default: // Reset / go round again break; } if(iic->state < 6) // Stop after one run { iic->state++; } else { iic->state = 0; // Wait for next time round. } EnableInts(ipl);}