Question about Bluepill arduino using OnePulse mode

Post here first, or if you can't find a relevant section!
ozcar
Posts: 143
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: Question about Bluepill arduino using OnePulse mode

Post by ozcar »

ag123 wrote: Mon Jan 02, 2023 1:17 pm There is another funky, hardware driven way using a counter is to use a timer and DMA.
Accordingly, a timer can trigger DMA, e.g. to read a set of gpios in sequence, this is actually 'the' way to 'read/write parallel data'.
As DHT11/DHT22 is first triggered using a single pulse from the mcu, this can be done using the usual digitalWrite().
The timer and dma needs to be setup in advance
Once done, enable dma and I think it'd begin reading (sampling) data in sync with the timer.
After this, you can 'parse' that data buffer to decode data sent from the DHT11/DHT22, the downside is this needs some memory.
The benefit is I think this approach may tolerate an RTOS 1ms context switching, as it is all hardware driven.
Xlwww's interrupt method currently also uses an array to store the time values. For both that method and the DMA input capture method, the size of the array elements could be reduced to two bytes (after all it is only a 16-bit timer), or maybe even to 1 byte per entry (would probably still have sufficient resolution to work). However, the interrupt method does allow the entire transmission to be reduced to 5 bytes quite easily, as I already suggested. Reducing the number of elements in the array needed for the DMA method is also possible, but is a bit more tricky.
dannyf wrote: Mon Jan 02, 2023 1:35 pm
Now given there was no DHT11 attached
you can write a dht slave to simulate a dht device, transmitting whatever data you want. making debugging easier. it is the opposite of what the OP wants to do but equally fun :)
That, of course is what I did, except I did not write it from scratch, I found a bloke named Rob Tillaart already wrote such a thing, which needed minimal modification. Rob has also written a DHTxx library, but it looks like that would not be easily adapted for STM32.

For reception, the Adafruit DHTxx library worked for me straight out of the box using the official STM core. I did not try it on the "Roger" core but at a guess, it could work. However, it blocks for around 23-24ms, and disables interrupts for around 4ms after the start pulse.

dannyf wrote: Mon Jan 02, 2023 1:33 pm for a non-blocking implementation, you can use external interrupts, or pin change interrupts, or even input capture. the downside is that the ISR overhead will mean that it is very difficult to implement somethng like thing on a slow mcu.
Using the same method as mentioned in another thread here (viewtopic.php?f=7&t=955&start=20), the total handler_channel_1() interrupt processing time (the "out-to-lunch" time, using the terminology from the other thread) is around 3.4μs. Taking out the "debug" digitalWrite to PB11 cut that down to about 2.5μs. Given the DHT11 pulse times, I don't think I would be too worried. I have a feeling that the interrupt time doing the equivalent thing using the STM core might be a bit longer, but unlikely to be a real concern.
ag123 wrote: Mon Jan 02, 2023 1:42 pm I'd think counting 'ticks' should work but that it'd need to be micros(), perhaps micros() may be appropriate, but one'd need to be careful of wrap around.
For even higher resolution, the DWT cycle counter can be used, but that is totally unnecessary here.

With whatever you use, if the count is treated as an unsigned integer, wrapping around once is not a problem, and wrapping around more than once might well be impossible given the duration of the entire transmission. If xlwww followed my suggestion of setting the timer counter to zero at the end of the start pulse, the 16 bit counter is not going to get anywhere near to wrapping around even once during the transmission.
ag123
Posts: 1655
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: Question about Bluepill arduino using OnePulse mode

Post by ag123 »

actually, if there isn't a need to do a custom implementation, i'd recommend using adafruit's library
https://github.com/adafruit/DHT-sensor-library

I actually started with this library, and 'improved' it slightly using a standalone timer for the duration measurements
viewtopic.php?p=11447#p11447
viewtopic.php?p=3517#p3517

The other tweak is to use pinMode(pin, INPUT_PULLUP), this saves the pull up resistor and allows you to connect stm32 (board) directly to dht11/dht22.
ozcar
Posts: 143
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: Question about Bluepill arduino using OnePulse mode

Post by ozcar »

Always keen to learn about the mysteries of STM32 timers, I spent some more time looking at this.

I reduced the data-capturing ISR from this (before the array bound check was added) :

Code: Select all

void handler_channel_1(void) {                           //This function is called when channel 1 is captured.
    if (0b1 & GPIOA_BASE->IDR  >> 0) {                     //If the receiver channel 1 input pulse on A0 is high.
      channel_1_start = TIMER2_BASE->CCR1;                 //Record the start time of the pulse.
      TIMER2_BASE->CCER |= TIMER_CCER_CC1P;                //Change the input capture mode to the falling edge of the pulse.
      digitalWrite(PB11, HIGH);
    }
    else {                                                 //If the receiver channel 1 input pulse on A0 is low.
      channel_1 = TIMER2_BASE->CCR1 - channel_1_start;     //Calculate the total pulse time.
      if (channel_1 < 0)channel_1 += 0xFFFF;               //If the timer has rolled over a correction is needed.
      TIMER2_BASE->CCER &= ~TIMER_CCER_CC1P;               //Change the input capture mode to the rising edge of the pulse.
      DHT11_data[index1++]=channel_1;
      digitalWrite(PB11, LOW);
  //    Serial2.println(index1);
    }
}
to this:

Code: Select all

void handler_channel_fall(void)                       // Called when channel 2 (falling) edge has been captured.
{
      DHT11_data[index1++] = TIMER2_BASE->CCR2 - TIMER2_BASE->CCR1; // fall time - rise time
      if ( index1==41 ) TIMER2_BASE->CCER = 0;        // stop input capture
}
OK, so that no longer reflects the status of the sensor input to PB11, which I assume was for diagnostic purposes. It uses half the number of interrupts compared to the original, as it only interrupts on falling edges, not both rising and falling.

For what it is worth this is the whole thing:

Code: Select all

#include <libmaple/dma.h>

int32_t DHT11=PA0;
volatile uint16_t DHT11_data[50]={0},index1=0;
volatile uint16_t DMA_data_rise[50],DMA_data_fall[50];

void setup() {
   Serial2.begin(115200);
   while ( !Serial2 );
   Serial2.println(F("DHTxx test!"));
   pinMode(PB11, OUTPUT);     // no longer used
   dma_init(DMA1);
   print_reg();
}

void loop() {
  // put your main code here, to run repeatedly:

    int i;

    Request() ;  /* send start pulse */
    
    uint32_t start = millis();
    while ( ( millis()-start < 200 ) & ( index1 != 41 ) ) ; // delay(200) but bail out if expected number of pulses received
    
    if( index1==41 )
    {
      for(i=0;i<index1;i++)
        {
          Serial2.print(DHT11_data[i]);
          Serial2.print("   ");
          Serial2.println(DMA_data_fall[i]-DMA_data_rise[i]);
        }
      Serial2.print(index1);   
      Serial2.println(" OK");
    }
    else
    {
      Serial2.print(index1);      
      Serial2.println(" error");
    }
    
    delay(2000);
}

void Request()      /* Microcontroller send request, first stage */
{
  index1=0;
  for(int i=0;i<50;i++)
      DHT11_data[i] = DMA_data_rise[i] = DMA_data_fall[i]= 0;
  
  // before starting the low pulse, clear settings from last time through, to prevent the pin change from
  // causing input capture interrupt
  TIMER2_BASE->CCER = 0;                   // turn off input capture 
  Timer2.detachInterrupt(0);               // remove interrupt callbacks 
  Timer2.detachInterrupt(TIMER_CH1);       // also clears DIER interrupt enable flags
  Timer2.detachInterrupt(TIMER_CH2);  
 
  pinMode(DHT11, OUTPUT);
  digitalWrite(DHT11,LOW); 
  uint16_t PulseLOW = 25000;  // should > 18ms
  uint16_t PulseHIGH = 10;    // defines small time after the end of the low start pulse before we start looking for 
                              // response - to avoid any noise in the changeover of which side is pulling the line down.
 
  
  Timer2.pause();                          // stop the timer before configuring it
  DMA1_BASE->CCR5 &= ~DMA_CCR_EN;          // make sure dma channels stopped
  DMA1_BASE->CCR7 &= ~DMA_CCR_EN;           
  TIMER2_BASE->DIER &= ~(TIMER_DIER_CC1DE | TIMER_DIER_CC2DE);  // clear DMA requests 
  
  Timer2.setChannel1Mode(TIMER_OUTPUT_COMPARE);
  Timer2.setPrescaleFactor(72);                 // 1 microsecond resolution
  Timer2.setOverflow(PulseLOW + PulseHIGH-1);
  Timer2.setCompare(TIMER_CH1, PulseLOW);
  Timer2.refresh();                             // preload values to take effect right now, and counter is reset
      
  Timer2.attachInterrupt(TIMER_CH1,Response0);  // channel 1 interrupt
  Timer2.attachInterrupt(0,Response1);          // update event (counter wrap) interrupt
  Timer2.resume(); // let timer 2 run
  print_reg(); 
}
 

void Response0()                             // channel interrupt, end the "start" low pulse
{
    digitalWrite(DHT11,HIGH);                // end of low start pulse
    pinMode(DHT11, INPUT_PULLUP);            // release so sensor can drive the line
    Timer2.detachInterrupt(TIMER_CH1);       // remove this channel interrupt callback
}

void Response1()                             // end of "deaf" period
{
    TIMER2_BASE->CR1 &= ~TIMER_CR1_CEN;      // stop the timer while we mess with it
    DMA1_BASE->CCR5 &= ~DMA_CCR_EN;          // make sure dma channels stopped too 
    DMA1_BASE->CCR7 &= ~DMA_CCR_EN;     
    Timer2.detachInterrupt(0);               // remove this update event callback
    TIMER2_BASE->CCR1=0;                     // in case we get a falling edge before first rising edge
    
    TIMER2_BASE->DIER |= TIMER_DIER_CC1DE    // Capture channel 1 DMA request enable (for rising edges)
                       | TIMER_DIER_CC2DE;   // Capture channel 2 DMA request enable (for falling edges)
    
    TIMER2_BASE->CCMR1 = TIMER_CCMR1_CC1S_INPUT_TI1  // map TI1 to channel 1 to capture rising edges
                       | TIMER_CCMR1_CC2S_INPUT_TI1; // also map TI1 to channel 2 to capture falling edges
                    
    TIMER2_BASE->ARR = 0xFFFF;
    TIMER2_BASE->DCR = 0;                    // no DMA burst
    
    DMA1_BASE->CPAR5 = (uint32_t)&TIMER2_BASE->CCR1; // from 
    DMA1_BASE->CMAR5 = (uint32_t)DMA_data_rise;      // to  
    DMA1_BASE->CNDTR5 = 50;                  // number of entries
    DMA1_BASE->CCR5 = DMA_CCR_PL_MEDIUM      // priority
                    | DMA_CCR_MSIZE_16BITS   // memory size
                    | DMA_CCR_PSIZE_32BITS   // peripheral size
                    | DMA_CCR_MINC           // memory increment 
                    | DMA_CCR_DIR_FROM_PER   // direction, from peripheral         
                    | DMA_CCR_EN;            // enable dma channel 

    DMA1_BASE->CPAR7 = (uint32_t)&TIMER2_BASE->CNT; // DMA reading CCR2 will clear SR/CC2IF, resulting in no interrupt callback. 
                                                    // Substitute CNT to allow both DMA and interrupt methods for testing.
                                                    // Given the counting rate, this does not make a big difference.
    DMA1_BASE->CMAR7 = (uint32_t)DMA_data_fall  ;   // to  
    DMA1_BASE->CNDTR7 = 50;                  // number of entries
    DMA1_BASE->CCR7 = DMA_CCR_PL_MEDIUM      // priority
                    | DMA_CCR_MSIZE_16BITS   // memory size
                    | DMA_CCR_PSIZE_32BITS   // peripheral size
                    | DMA_CCR_MINC           // memory increment 
                    | DMA_CCR_DIR_FROM_PER   // direction, from peripheral         
                    | DMA_CCR_EN;            // enable dma channel                     

    Timer2.refresh();                        // preload values to take effect right now, and reset counter
    Timer2.attachInterrupt(TIMER_CH2,handler_channel_fall); 
    TIMER2_BASE->CCER = TIMER_CCER_CC1E      // channel 1 capture enabled, CC1P = 0 for rising edge
                      | TIMER_CCER_CC2E      // channel 2 capture enabled
                      | TIMER_CCER_CC2P;     // CC2P = 1 for falling edge      
    TIMER2_BASE->CR1 |= TIMER_CR1_CEN;       // start the timer again
}

void print_reg()
{
      Serial2.println("TIMER2_BASE->CR1 ="+ (String)TIMER2_BASE->CR1+';');
      Serial2.println("TIMER2_BASE->CR2 =" +(String)TIMER2_BASE->CR2+';');
      Serial2.println("TIMER2_BASE->SMCR ="+ (String)TIMER2_BASE->SMCR+';');
      Serial2.println("TIMER2_BASE->DIER ="+ (String)TIMER2_BASE->DIER+';');
      Serial2.println("TIMER2_BASE->SR = "+(String)TIMER2_BASE->SR+';');
      Serial2.println("TIMER2_BASE->EGR = "+(String)TIMER2_BASE->EGR+';');
      Serial2.println("TIMER2_BASE->CCMR1 = "+(String)TIMER2_BASE->CCMR1+';');
      Serial2.println("TIMER2_BASE->CCMR2 = "+(String)TIMER2_BASE->CCMR2+';');
      Serial2.println("TIMER2_BASE->CCR1 = "+(String)TIMER2_BASE->CCR1+';');
      Serial2.println("TIMER2_BASE->CCR2 = "+(String)TIMER2_BASE->CCR2+';');
      Serial2.println("TIMER2_BASE->CCER = "+(String)TIMER2_BASE->CCER+';') ;
      Serial2.println("TIMER2_BASE->PSC = "+(String)TIMER2_BASE->PSC+';');
      Serial2.println("TIMER2_BASE->ARR = "+(String)TIMER2_BASE->ARR+';');
      Serial2.println("TIMER2_BASE->DCR = "+(String)TIMER2_BASE->DCR+';');
      Serial2.println("TIMER2_BASE->CNT = "+(String)TIMER2_BASE->CNT+';');
      Serial2.println("DMA1_BASE->CCR5 = "+(String)DMA1_BASE->CCR5+';');
      Serial2.println("DMA1_BASE->CNDTR5 = "+(String)DMA1_BASE->CNDTR5+';');
      Serial2.println("DMA1_BASE->CPAR5 = "+(String)DMA1_BASE->CPAR5+';');
      Serial2.println("DMA1_BASE->CMAR5 = "+(String)DMA1_BASE->CMAR5+';');
      Serial2.println("DMA1_BASE->CCR7 = "+(String)DMA1_BASE->CCR7+';');
      Serial2.println("DMA1_BASE->CNDTR7 = "+(String)DMA1_BASE->CNDTR7+';');
      Serial2.println("DMA1_BASE->CPAR7 = "+(String)DMA1_BASE->CPAR7+';');
      Serial2.println("DMA1_BASE->CMAR7 = "+(String)DMA1_BASE->CMAR7+';');
      Serial2.println();
}

void handler_channel_fall(void)                       // Called when channel 2 (falling) edge has been captured.
{
      DHT11_data[index1++] = TIMER2_BASE->CCR2 - TIMER2_BASE->CCR1; // fall time - rise time
      if ( index1==41 ) TIMER2_BASE->CCER = 0;        // stop input capture
}
xlwww wrote: Mon Jan 02, 2023 5:10 pm I want DMA to pass the captured data directly into the array, but I didn't find the relevant code, I think this will double the efficiency
So, as a bonus, the above code captures the times using the ISR, but simultaneously does the same using DMA, so you can take your pick.

It works for my pretend DHT11, I got it to print out the times as captured by the two methods. The counts occasionally differ by 1 because I had to cheat a little to get both methods to work at the same time.

Code: Select all

17:16:55.027 -> 82   82
17:16:55.027 -> 26   26
17:16:55.027 -> 27   27
17:16:55.027 -> 27   27
17:16:55.027 -> 27   27
17:16:55.027 -> 27   27
17:16:55.027 -> 27   27
17:16:55.027 -> 27   27
17:16:55.027 -> 71   71
17:16:55.027 -> 71   71
17:16:55.027 -> 71   71
17:16:55.027 -> 71   71
17:16:55.027 -> 71   71
17:16:55.027 -> 27   27
17:16:55.027 -> 70   71
17:16:55.027 -> 26   26
17:16:55.027 -> 27   27
17:16:55.027 -> 27   27
17:16:55.027 -> 27   27
17:16:55.027 -> 27   27
17:16:55.027 -> 26   27
17:16:55.027 -> 26   26
17:16:55.027 -> 26   26
17:16:55.027 -> 70   70
17:16:55.027 -> 72   72
17:16:55.027 -> 71   71
17:16:55.027 -> 71   71
17:16:55.027 -> 70   71
17:16:55.027 -> 26   26
17:16:55.027 -> 26   26
17:16:55.027 -> 70   70
17:16:55.027 -> 71   71
17:16:55.027 -> 72   72
17:16:55.027 -> 71   71
17:16:55.027 -> 70   71
17:16:55.027 -> 26   26
17:16:55.027 -> 70   70
17:16:55.027 -> 70   70
17:16:55.027 -> 71   71
17:16:55.027 -> 71   71
17:16:55.027 -> 71   71
17:16:55.027 -> 41 OK
xlwww
Posts: 14
Joined: Tue Dec 27, 2022 9:19 am

Re: Question about Bluepill arduino using OnePulse mode

Post by xlwww »

This looks great, I will try it. Obviously you already know a lot about STM32!
Post Reply

Return to “General discussion”