Hello Iain,
I previously referred to the use of the fixed system clock (XCLK), which makes use of the external reference clock. The TPMCLK input is a different animal.
I can still see a number of issues with your code. A major one seems to be the scope of some of your variables. How does the bpm() function return the bpm value, since hr is a local variable that will not be visible when the function exits? I would suggest that the function directly return the heart rate (byte) value. I also notice that the function still makes use of the period value, even though this does not seem now appropriate to the ISR code.
Personally, I would not calculate average within the ISR, but would make direct use of total value, perhaps similar to the following example. I have also taken into account a specific no signal case.
// Global variables:volatile int total;volatile byte once = 1, index = 0, cycle = 0, phase;#define NUMREADINGS 4#define MAXLIM 1500#define MINLIM 270byte calc_bpm( void){ dword hr; if (once == 0) { // Next reading is ready hr = 60000L * NUMREADINGS / total; once = 1; // Enable next reading to start } else if (once == 0xFF) { // No signal once = 1; // Enable next reading to start cyle = 0; // Commence new reading cycle index = 0; return 0; // Indicate invalid result } return (byte)hr; // Return heart rate value}
Within the ISR code your intent is a little unclear. The presence of the for loop suggests that you expect to remain within the ISR while four readings are processed, but with no monitoring of the presence of the channel flag. While this might be made to work, it would be a poor coding practice. The ISR should process a single input capture event only, and then exit. Additionallly, the average variable does not have visibility outside of the ISR, yet this seems to be the value from which the heart rate is calculated. The flag clearing is also suspect, with apparent confusion between TPM number and channel number.
You suggest that you will stay with the use of a single TPM channel "for simplicity". However, this will result in some negative effects, some of which I alluded to in a previous post. With the LED remaining on or off for a whole hr period, and then toggling, when the HR signal disappears there would be 50 percent probability that the LED would remain in a continuously on state - probably not desireable. Additionally, all input capture interrupts would cease, leaving no way of indicating the loss of signal to the main function. Therefore, an additional timing function would be required, hence the use of a second TPM channel in output compare mode.
The following untested ISR code is my take on providing a "rolling total" for multiple HR periods.
void interrupt VectorNumber_Vtpm3ch0 ISR_TPM3C0( void){ static word prev, period; static word array[NUMREADINGS]; TPM3C0SC_CH0F = 0; // Clear flag // LED flash control: PTDD_PTDD0 = 1; // Turn LED on TPM3C1V = TPM3C0V + 200 // Update Ch1 OC for 200ms flash phase = 0; // Process input capture value: if (once) { // Test for previous reading processed period = TPM3C0V - prev; prev = TPM3C0V; // Test for valid period range: if ((period <= MAXLIM) && (period >= MINLIM)) { // Ramge is valid if (++index == NUMREADINGS) { index = 0; cycle = 1; // Indicate cycle completed } if (cycle) { total -= array[index]; // Subtract oldest period once = 0; // Flag new reading is ready } array[index] = period; // Update array total += period; // New totalised value else { // Period range is invalid total = 0; cycle = 0; // Commence new reading cycle index = 0; } }}
The following code is the output compare ISR for handling both LED turn off and no signal detection.
void interrupt VectorNumber_Vtpm3ch1 ISR_TPM3C1( void){ TPM3C1SC_CH1F = 0; // Clear flag if (phase == 0) { PTDD_PTDD0 = 0; // Turn LED off TPM3C1V += (MAXLIM - 200); // Ready for no signal test phase = 1; } else if (phase == 1) // No signal state caused interrupt once = 0xFF; // Flag no signal}
Regards,
Mac