PWM decoding. Best practice

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

PWM decoding. Best practice

1,969 Views
d0ct0r
Contributor III

Hello Folks,

 

I would like to hear an advise what would be best practice to decode PWM (or sort of PWM) ?

 

To be more detailed, lets take DHT11 Humidity Sensor as an example. This little fellow suppose to send 40 bit sequence as soon as some port send logical '0' to it (see attached file).

 

Here is my code to initiate communication:

 

void HumStartSignal(void)

{

TPM1C0SC = 0x0; // RESET counters for the channels,

TPM1C0SC_CH0F = 0; // Reset ELS0A/B to set port as regular port, Reset CH0F flag.

HUMportDD = 0xFF; // Switch port to OUT

HUMport = 0x00; // Clear port (set Low)

WaitNms(25); // Wait

HUMport = 0x01; // Set pin to HIGH

HUMportDD = 0x00; // Switch port to IN// Set CH0 in Input Capture Mode on falling edges (this command reset counters as well)

 

// CHnF CHnIE MSnB MSnA ELSnB ELSnA 0 0 -> 01001000 / (RE: 01000100 44) / (AE: 01001100) 4C)

TPM1C0SC = 0x48;

}

 

Now, I need your expertise what to do next. For sure I have interrupt function for TPM1 CH0. I did try to use it, but I don't like how its looks like. Also, time to time my whole projects hangs if I start to use it.

 

// Not whole function. just a scetch

TPM1C0SC_CH0F = 0; // ACK channel interrupt. Reading flag, then write a 0 to the bit.

// calculate delta between previous IRQ

delta =TPM1C0V - oldcounter;

oldcounter = TPM1C0V;

if (delta > 40 && delta < 110)

    htvar = ((htvar<<1) | 1) // ONE

else

    htvar = ((htvar <<1) ) // ZERO

 

Then I tried to create function outside of IRQ which cold handle that. However its failed too.

 

Whole idea suppose to be like this:

 

Send ONE to the sensor and right away read its response and parse it somehow.

I'll be appreciate for the ideas and avises. Thanks !

Labels (1)
0 Kudos
5 Replies

778 Views
bigmac
Specialist III

Hello,

 

There is no "best practice" to accomplish this  type of decoding.  You will require a reliable method that takes into account the specific constraints of the particular project.  Depending on those constraints, different approaches may be required.  It is possible that such constraints may cause the decoding to sometimes unavoidably fail.  Under these circumstances it is necessary to be able to detect such a failure, so that a new reading may be commenced.

 

The decoding process is time critical.  To minimize the number of timer interrupts required, it should suffice to measure the period between each pair of negative edges.  The critical timing interval will be for the 0-bit, with a total period of 76 microseconds.  Assuming the bus frequency is 8 MHz, this would correspond with 608 cycles.  Obviously, more cycles would be available at higher bus frequencies.

 

Assuming that TPM channel is set for input capture mode, and that an interrupt will occur on every negative edge, the following operations may need to be completed within the critical limit -

  1. Completion of any ISR current currently being processed when the capture occurs.
  2. The potential execution of all other queued ISRs with higher priority than the TPM channel ISR.
  3. Execution of the TPM channel ISR itself.

You will need to determine the worst case cycles for all ISRs to determine the susceptibility to decode failure.  If items 1 and 2 are present only infrequently, and item 3 easily meets the timing requirement, you might decide to accept the occasional decode failure on the basis that a second attempt then has a high chance of succeeding.

 

An alternative solution is to globally disable interrupts just prior to the completion of the start pulse, and to detect the sequence of input capture events by polling the TPM channel flag.  However, this will mean that all interrupt execution will be delayed by about 4 milliseconds.  The feasibility of this approach will depend on whether the project uses any other time critical processes.  For example, this method would be problematic if you also  required SCI receive operation at 2400 bit/s, or higher.

 

You would process each bit period by comparing with a threshold period of, say 100 microseconds.  A period less than the threshold would represent a 0-bit, and greater than the threshold a 1-bit.  To minimize ISR processing cycles, there may be some advantage in considering judicious use of inline assembly code for the serial data shifting process, where the carry status bit can be directly used.

 

You will also need to detect whether the pulse sequence is actually completed.  I would suggest to setup another TPM channel for software output compare mode, set for a timeout period of, say 150-200us relative to each input capture time.  If the compare event should ever occur, this will immediately indicate an error condition.

 

I notice that the start pulse is not time critical, provided a minimum duration is met.  This is most easily generated if the TPM pin is operating as GPIO at this point.  Input capture mode would be selected after the start pulse is completed.  Whenever the data line has a high state the pin is best set as an input, prior to selection of input capture mode.  The following macros would achieve this (assuming PTAD0 is the data pin).

 

#define DLOW  PTADD_PTADD0 = 1; PTAD_PTAD0 = 0

#define DHIGH PTADD_PTADD0 = 0

 

Once input capture mode is selected, the pin will be an input, independent of the GPIO data direction.

 

To use the TPM module for longer timing durations, such as the start pulse, rather than set a large prescale value so that the overflow period is greater than the timing duration, you might alternatively generate a shorter, periodic "tick" interval.  In this case, a tick period of 2ms would be appropriate.  For the start pulse duration, you would count 20 of these intervals.

 

Regards,

Mac

0 Kudos

778 Views
d0ct0r
Contributor III

 

Mac,

 

Many thanks indeed for your quick response !

 

Yes, the microseconds is a challenge to handle. Even my bus is 24Mhz, its not enough to calculate something between of pulses.

 

Basically the device send CRC at the and of sequence. 40 bit is a 5 byte. First four is data and last one is CRC. So, it will be easy to detect the failure. Also, project is not critical to the other interrupt ruitines during the reading of this particular sensor.

 

Here is what I have now:

 

This function looks exactly as you mention: set port to OUT, send logical zero, switch to input mode, turn on TPM CH0

to capture.

 

void HumStartSignal(void)

{

h_cnt=0;

TPM1C0SC = 0x0; // RESET counters for the channels,

TPM1C0SC_CH0F = 0; // Reset ELS0A/B to set port as regular port, Reset CH0F flag.

 

HUMportDD = 0xFF; // Switch port to OUT

HUMport = 0x00; // Clear port (set Low)

WaitNms(25); // Wait 25ms

HUMport = 0x01; // Set pin to HIGH

HUMportDD = 0x00; // Switch port to IN

// Set CH0 in Input Capture Mode on falling edges (this command reset counters as well)

// CHnF CHnIE MSnB MSnA ELSnB ELSnA 0 0

TPM1C0SC = 0x48;

}

 

Here is IRQ routine:

 

interrupt void TPM1C0_ISR(void)

{

TPM1C0SC_CH0F = 0; // ACK channel interrupt. Reading flag, then write a 0 to the bit.

h_var[h_cnt++] = TPM1C0V;

}

 

And here is external function to analyse the h_var array:

 

unsigned long HumReadByte(void)

{

unsigned char i;

unsigned long num = 0;

int delta;

 

for(i=0; i<h_cnt; i+=2){

  delta = h_tik[i+1] - h_tik[i];

  if(delta > 70 && delta < 110)

      num = ((num << 1) | 1);

  if(delta > 50 && delta < 70)

            num = (num << 1);

  }

return num;

}

 

Then in main loop I call them like this:

 

HumStartSignal();

WaitNms(50); // wait 50 ms

num = HumReadByte();

 

So, the idea is to use Interrupt to gather "time stamps" for each event. And then analyse it in the main program.

Unfortunately, for some uncertain reason this code cannot be handled by debugger. It goes through only if I commented out HumReadByte().

I did try various value for stack in PRM. The MAP file shows this one:

Summary of section sizes per section type:

 

READ_ONLY (R): 10CB (dec: 4299)

READ_WRITE (R/W): 599 (dec: 1433)

NO_INIT (N/I): AC (dec: 172)

 

I don't think its a memory issue, since JM60 has 60K ROM plus 4K RAM. There is no warnings. Its just hang.

 

Sometimes debugger jump to this line:

INIT_SP_FROM_STARTUP_DESC(); /*lint !e960 MISRA 14.3 REQ, not a null statement (instead: several HLI statements) */

 

Regards,

d0ct0r

 

0 Kudos

778 Views
bigmac
Specialist III

Hello,

 


d0ct0r wrote:

 

Yes, the microseconds is a challenge to handle. Even my bus is 24Mhz, its not enough to calculate something between of pulses.

 

Basically the device send CRC at the and of sequence. 40 bit is a 5 byte. First four is data and last one is CRC. So, it will be easy to detect the failure. Also, project is not critical to the other interrupt ruitines during the reading of this particular sensor.

 


Firstly, if you are able to globally disable interrupts for a period of 4 ms, I suggest that you use polling, rather than interrupts.  This should simplify the coding, and will avoid the cycle overhead associated with each interrupt.

 

I disagree with your statement that it is not possible to do any processing for the previous data bit, within the period of the next data bit.  The data bit processing code that I managed to achieve requires about 128 cycles (checked with FCS).  This is well within the worst case 1820 cycles available with a 24MHz bus frequency.

Even though a checksum byte (not a CRC) is present in the data, a timeout process must also be present to prevent the possibility that you will wait indefinitely (or until COP reset occurs), for a capture event that never arrives.

 

The following untested code snippet makes use of the TPM overflow to time the start bit duration, again using polling.  The timeout period is accomplished using channel 1 of the TPM.  The static variables used by the code, including a 5-byte array for the result, are placed within zero page RAM, for better cycle efficiency.

 

// Static variables:#pragma DATA_SEG __SHORT_SEG MY_ZEROPAGEstatic word oldval, delta;static byte result[5];   // Array for sensor read result#pragma DATA_SEG DEFAULT #define THRESHOLD   2400     // 100us @ 24 MHz bus frequency#define MAXTIME     4800     // 200us @ 24 MHz bus frequency                             // TPM1 prescale = 1#define DATA_LOW    PTEDD_PTEDD2 = 1; PTED_PTED2 = 0#define DATA_HIGH   PTEDD_PTEDD2 = 0/***********************************************************************/byte read_sensor( void){   byte bitcnt = 40;   byte startcnt = 8;           // Start pulse duration (2.73 ms increment)   DATA_LOW;                    // Commence start pulse   TPM1SC = 0x08;               // TPM1 - bus clock, prescale 1, no int.   do {                        // Start pulse timing      __RESET_WATCHDOG();      TPM1SC_TOF = 0;           // Clear overflow flag      while (!TPM1SC_TOF);      // Wait for overflow flag set   } while (--startcnt);        // Loop until timing is complete   DisableInterrupts;      // Global disable   DATA_HIGH;       // End of start pulse   TPM1C0SC = 0x08;             // Input capture on falling edge (polling)   TPM1C0SC_CH0F = 0;           // Clear capture flag   TPM1C1SC = 0x10;             // Software output compare (polling)      TPM1C1V = TPM1CNT + MAXTIME; // Set timeout interval   TPM1C1SC_CH1F = 0;           // Clear timeout flag   while (!TPM1C0SC_CH0F) {     // Wait for first capture event      if (TPM1C1SC_CH1F) {      // Test for timeout         TPM1C0SC = 0;          // Disable TPM capture channel         EnableInterrupts;         return 1;              // Indicate read error      }   }   TPM1C0SC_CH0F = 0;           // Clear capture flag      // Process first "response" bit   TPM1C1V = TPM1C0V + MAXTIME; // Set timeout interval   TPM1C1SC_CH1F = 0;           // Ensure timeout flag is clear   while (!TPM1C0SC_CH0F) {     // Wait for capture event      if (TPM1C1SC_CH1F) {      // Test for bit timeout         TPM1C0SC = 0;          // Disable TPM capture channel         EnableInterrupts;         return 1;              // Indicate read error      }   }   TPM1C0SC_CH0F = 0;           // Clear capture flag   oldval = TPM1C0V;      // Process the data bits   do {                               asm {                     // Shift result ready for next data bit         lsl  result:4         rol  result:3                        rol  result:2                        rol  result:1                        rol  result:0                     }      TPM1C1V = oldval + MAXTIME; // Set next timeout interval      TPM1C1SC_CH1F = 0;        // Ensure timeout flag is clear      while (!TPM1C0SC_CH0F) {  // Wait for capture event         if (TPM1C1SC_CH1F) {   // Test for bit timeout            TPM1C0SC = 0;       // Disable TPM capture channel            EnableInterrupts;            return 1;           // Indicate read error         }      }      TPM1C0SC_CH0F = 0;        // Clear capture flag      delta = TPM1C0V - oldval; // Period of bit sequence      oldval = TPM1C0V;         // Update value      if (delta >= THRESHOLD)   // Test current bit state         result[4] |= 1;   } while (--bitcnt);      TPM1C0SC = 0;                // Disable TPM capture channel   EnableInterrupts;   return 0;                    // Indicate valid read}

 

Regards,

Mac

 

0 Kudos

778 Views
d0ct0r
Contributor III

Mac,

 

You've made my day !!! Much thanks ! Will keep your solution as a reference.

 

Meantime, I solve the constrains with Development Tool to upgrade USBDM from 4.9.3 to 4.9.4b. I was close to give up Motorola MCU thinking its a software limitation for code development. I am happy that it was a some bug outside of MCU and CodeWarrior.  Will continue to explore Freescale world. Once again - thanks for your posts.

0 Kudos

778 Views
d0ct0r
Contributor III

This is correction for HumreadByte:

 

unsigned short HumReadByte (void)

{

unsignedchar i;

unsigned long num = 0;

unsigned int count = 0;

int delta;

 

for(i=0; i<h_cnt; i++) {

            delta = h_tik[i+1] - h_tik[i];

            num = ((num << 1) | 1);

if(delta > 50 && delta < 70)

            num = (num << 1);

}

return num;

}

 

I did analyse its output. It looks promissing. I got 41 bits. First one could be ignored - its a begin of the cycle. It has 126 ticks duration (my tick is 1.333uS long - 24mhz with prescale equal to 32)

 

The things which puzzled me a lot - is why I need to comment out the other subrotine(s) to be able to run the code mentioned above. For me its looks like its not enough memory. But as I said early - there should be plenty available.

0 Kudos