Interfacing HC-SR4 Ultrasonic Sensor with STM32L1

735 Views Asked by At

I'm trying to control this HC-SR4 Ultrasonic sensor by simply ON an LED when an object closer than 1m is detected. I'm using TIM2 for the trigger signal (Pin PB10), and TIM4 to receive Echo signal (Pin PB6). and the LED is connected to Pin PB7. when I load the code below, the LED simply turns ON, whether there's an object or not, it's just ON.

Any idea where did I go wrong?

 #include <stdio.h>
#include "stm32l1xx.h"                  // Keil::Device:Startup


// switch from HSE to HSI clock 16MHz
void HSI_config(){

RCC->CR |= RCC_CR_HSION; // Turn On HSI oscillator
RCC->CFGR |= RCC_CFGR_SW; // Select HSI clock
RCC->CFGR |= RCC_CFGR_SWS_HSI;
RCC->CR |= RCC_CR_HSIRDY; // wait for HSI stabilize
}

// Configure GPIO Port B
void GPIO_config(){

    RCC->AHBRSTR |= RCC_AHBRSTR_GPIOBRST;           // Reset GPIOB clock 
    RCC->AHBRSTR &= ~RCC_AHBRSTR_GPIOBRST;      // Clear Reset 
    RCC->AHBENR |= RCC_AHBENR_GPIOBEN;        // Enable GPIOB clock 

    //PB6 Echo Pin
    GPIOB->MODER   &=   ~(0x03 << (2*6));     // Clear bit 12 & 13 Alternate function mode 
    GPIOB->MODER   |=   0x02 << (2*6);              // set as Alternate function mode 
    GPIOB->OSPEEDR &=   ~(0x03<< (2*6));            // 40 MHz  speed 
    GPIOB->OSPEEDR |=   0x03<< (2*6);               // 40 MHz  speed 
    GPIOB->PUPDR &=         ~(1<<6);                            // NO PULL-UP PULL-DOWN 
    GPIOB->OTYPER &=        ~(1<<6);                            // PUSH-PULL 
    GPIOB->AFR[0] |=        0x2 << (4*6);                   // set PB pin 6 as AF2 (TIM4_CH1) 

    //PB10 Pluse Generating Pin
    GPIOB->MODER   &=   ~(0x03 << (2*10));    // Clear bit 12 & 13 Alternate function mode 
    GPIOB->MODER   |=   0x02 << (2*10);     // set as Alternate function mode 
    GPIOB->OSPEEDR &=   ~(0x03<< (2*10));           // 40 MHz  speed 
    GPIOB->OSPEEDR |=   0x03<< (2*10);              // 40 MHz  speed 
    GPIOB->PUPDR &=         ~(1<<10);                           // NO PULL-UP PULL-DOWN 
    GPIOB->OTYPER &=        ~(1<<10);                           // PUSH-PULL 
    GPIOB->AFR[1] |=        0x1 << (4*2);                   // set PB pin 10 as AF1 (TIM2_CH3) 

//PB7 LED ON/OFF
    GPIOB->MODER   |=   GPIO_MODER_MODER7_0;     // General purpose output mode
  GPIOB->OSPEEDR |=   GPIO_OSPEEDER_OSPEEDR7;  // Max High speed 50MHz       

}
// CONFIGURE TIM4 FOR RECEIVING INPUT SIGNAL
void TIM4_Enable(){
    RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;                 // ENABLE TIM4 CLOCK
    TIM4->PSC = 15;                                                         // SET APPROPRAIT PRESCALER TO SLOW DOWN THE CLOCK
    TIM4->ARR = 0XFFFF;                                                 // SET MAX PULSE WIDTH OF 65536us FOR 16-BIT TIMER
    TIM4->CCMR1 &= ~TIM_CCMR1_CC1S;                         // CLEAR CAPTURE/COMPARE REGISTER
    TIM4->CCMR1 |= 0X1;                                                 // SELECT CH1 INPUTE CAPTURE 
    TIM4->CCMR1 &= ~TIM_CCMR1_IC1F;                         // DISABLE DIGITAL FILTERING
    TIM4->CCER |= (1<<1 | 1<<3);                                // SELECT BOTH RISING AND FALLING EDGE DETECTION CC1P & CC1NP
    TIM4->CCMR1 &= ~(TIM_CCMR1_IC1PSC);                 // INPUT PRESCALER 0 TO CAPTURE EACH VALID EDGE
    TIM4->CCER |= TIM_CCER_CC1E;                                // ENABLE COUNTER CAPTURE
    TIM4->DIER |= TIM_DIER_CC1IE;                               // ENABLE CH1 CAPTURE/COMPARE INTERRUPT
    NVIC_SetPriority(TIM4_IRQn, 1);                         // SET PRIORITY TO 1
    NVIC_EnableIRQ(TIM4_IRQn);                                  //ENABLE TIM4 INTERRUPT IN NVIC
}

// CONFIGURE TIM2 FOR SENDING OUTPUT SIGNAL
void TIM2_Enable(){
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;                 // ENABLE TIM2 CLOCK
    TIM2->PSC = 15;                                                         // SET APPROPRAIT PRESCALER TO SLOW DOWN THE CLOCK
    TIM2->ARR = 0XFFFF;                                                 // SET MAX PULSE WIDTH OF 65536us FOR 16-BIT TIMER

    TIM2->CCMR2 |= TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2; // 111: PWM mode 1 
    TIM2->CCMR2 |= TIM_CCMR2_OC3PE;                         // CH3 Output Preload Enable
    TIM2->CR1 |= TIM_CR1_ARPE;                                  // Auto-reload Prelaod Enable
    TIM2->CCER |= TIM_CCER_CC3E;                                // Enable Output for CH3
    TIM2->EGR |= TIM_EGR_UG;                                        // Force Update
    TIM2->SR &= ~TIM_SR_UIF;                                        // Clear the Update Flag
    TIM2->DIER |= TIM_DIER_UIE;                                 // Enable Interrupt on Update
    TIM2->CR1 |= TIM_CR1_DIR;                                       // Set downcounting counter direction
    TIM2->CR1 |= TIM_CR1_CEN;                                       // Enable Counter
}


    //Initialize the float variables.
    volatile uint8_t timespan = 0;                              // Total pulse width
    volatile uint8_t lastcounter = 0;                           // Timer counter value of the last event
    volatile uint8_t newcounter = 0;                            // Timer counter value of the current event
    volatile uint8_t overflow = 0;                              // Count the number of overflows
    volatile uint8_t PulseEnd = 0;                              // Declare end of pulse

void Echo_TIM4_IRQHandler(){

    if ((TIM4->SR & TIM_SR_UIF) != 0){                  // Check the update even flag
        overflow = overflow + 1;                                    // if UIF = 1, increment overflow counter
        TIM4->SR &= ~TIM_SR_UIF;                                    // clear UIF
    }
    if ((TIM4->SR & TIM_SR_CC1IF) != 0){                // Check capture event flag 
    newcounter = TIM4->CCR1;                                        // read capture value, store as newcounter
    timespan = (newcounter - lastcounter)+(65536 * overflow);   // calculate the total pulse width
    lastcounter = newcounter;                               // save the value of newcounter as lastcounter to be used for the next cycle
    overflow = 0;                                                       // clear overflow counter
    PulseEnd = 1;
}

}

void setSysTick(void){
    // ---------- SysTick timer (1ms) -------- //
    if (SysTick_Config(SystemCoreClock / 1000)) {
        // Capture error
        while (1){};
    }
}

    volatile uint32_t msTicks;      //counts 1ms timeTicks
void SysTick_Handler(void) {
    msTicks++;
}

static void Delay(__IO uint32_t dlyTicks){                                              
  uint32_t curTicks = msTicks;
  while ((msTicks - curTicks) < dlyTicks);
}



    int main(void){

            float Distance = 0.0f;                                              // actual distance in cm
            int Pulsesent = 0;

    HSI_config();
    setSysTick();
    GPIO_config();
    TIM4_Enable();
    Echo_TIM4_IRQHandler();

while(1){

if (Pulsesent == 0) {
    (Pulsesent = 1);
        TIM2_Enable();
}

if(Pulsesent && PulseEnd){
            Pulsesent = 0;

        if(overflow == 1){
                timespan = 0;
            }
            else {
                Distance = (timespan / 58) ;
            }
            Delay(1);
        }   
    if (Distance <= 100){

        GPIOB->BSRRL = (1<<7);
    }
        else {
            GPIOB->BSRRL = (0<<7);

        }   

    }   

}
3

There are 3 best solutions below

2
On

Regardless of the variable states, the register GPIOB->BSRRL will only reset the GPIOB bits that are '1's. GPIOB->BSRRL is the lower 16bits of a 32bit register. The upper 16 bits will set the GPIOB pins when bits that are '1'.

So to turn the LED off you will need something similar to GPIOB->BSRRH = (1<<7);. (this will upset Barny though, very specific, nothing portable, requires knowledge of the hardware connected to the chip..etc. ).

2
On

What a mess your code is, and not just your layout, the "logic" of your use of global variables appears to be completely foobar. I think you must put some significant effort in to sort out the design of your code, get advice from an experienced embedded software engineer. Think: this is a miniscule bit of code and you can't get it working, so with your current method of developing how would you ever get something complex working - you need to change your approach.

At least one of the problems with your code, directly related to the symptom you are seeing, is that one of your global variables PulseEnd gets set as a side-effect when in main() you directly call the TIM4 interrupt handler - Why? Perhaps to initialize global variables? Don't do it like this! - and never gets reset anywhere in this code.

Then, first time into the while(1) loop which immediately sets PulseSent to 1, the test if (PulseSent && PulseEnd) is of course true and your code immediately thinks an echo has been detected while timespan is probably still 0, certainly less than 5800 (you divide timespan by a random magic number 58 to get Distance, and then compare Distance to 100 to decide if LED should be on or off). HOWEVER that isn't your only problem, for example timespan is a uint8_t which means it is an 8-bit variable (there's a hint in the type name) which can only be in the range 0-255, yet you try to fill in timespan from the difference between two timer counters which are presumably 16-bit because you add in possible overflow of 65536. So basically your code always immediately thinks there is an echo from less than 100 units distance, and in addition the distance can never exceed 255/58 (4 and a bit), so can never exceed 100 even if the echo timing were working better, so the LED is always on.

Don't know why you have code to handle overflow in main(), and the fact that you are modifying overflow in both main and the TIM4 isr is a red flag, smells of potential for mysterious/transient/rare/untraceable/fatal problems, and I note that when overflow==1 (but not if overflow>1, and couldn't overflow validly be 1 as it is being incremented by the TIM4 interrupt code?) then Distance is not recalculated, yet subsequent code then uses Distance to illuminate/extinguish the LED. This stinks of more logic problems.

newcounter and lastcounter are also uint8_t, but the counters are 16-bit, aren't they? Worth checking. Do you run your compiler with warnings enabled, you should be able to get it to tell you when an expression has to be converted to a more restrictive type by an assignment (e.g. the C192 warning?). If you don't have warnings turn on you should, and you should pay attention to them. In fact turn on -Wall or whatever the keil equivalent is, and check out every single warning preferably to eliminate it by correcting your code. If you can't change your code to eliminate a warning that is strange. And make sure to check for changes in the warnings every time you add significant amounts of code. Of course it is easiest to see a change from zero warnings to >0 warnings, so that is (or should be) another motive for eliminating all warnings.

Couldn't see any code which appeared to handle the case where there isn't ever an echo response (which could also happen if the ultrasonic sensor broke or becomes disconnected). Seems like your code (once the PulseEnd problem is fixed) won't ever try again if it doesn't get an echo, not sure.

Because PulseEnd never gets reset, your code is sending TIM2 pulses to the ultrasonic ranger as fast as it can - well, slowed by a delay(1), which I guess is a 1ms delay - so depending on the hardware it's not clear if it's ever going to actually get an echo response. If you had looked at the output signal to the sensor you would see that.

I've worked out where your magic number 58 comes from, and assume that TIM4 is running at 10KHz, speed of sound in air 340m/s, distance to target 1m (so the pulse has to travel 2m) would take 5.882ms. Seems like you are embedding a 1.4%-ish scaling error (that's 1.4 cm at your target range of 100cm) by rounding 58.82 down to 58, if the calculation has to be integer (but it doesn't because Distance is declared as floating) then using 59 is only 0.3% scaling error, yet you could trivially do the calculation in floating point and have very little error. I'm guessing the magic number 15 stored in PSC sets the timer speed. I hate magic numbers.

Anyway, when/if you really have to use global variables in a complex dance like this, develop a process of sitting down with someone else to review what you are doing and write out on some paper how the variables interact between initialization, main() and the interrupt routines and be rigorously aware that any change to their handling means you need to sit down and work out that dance again on a new piece of paper, referring to how it used to work which is written down on a previous bit of paper. And try your hardest to develop unit tests that at least allow you to have some level of confidence that after changes to your code in main() it still stands a chance of working before it gets near the real hardware where, as you are discovering, it can be very difficult to understand what is actually going on.

0
On

I would like to share the updated code, which actually works (no library needed):

#include <stdio.h>
#include "stm32l1xx.h"                  // Keil::Device:Startup

        //Initialize the timers variables.
    volatile int timespan = 0;                              // Total pulse width
    volatile int lastcounter = 0;                           // Timer counter value of the last event
    volatile int newcounter = 0;                            // Timer counter value of the current event
    volatile int overflow = 0;                              // Count the number of overflows


    void SysTick_Handler(void);
    void SetHSI(void);
    void LED_GPIO(void);    
    void TIM4_C1_Init(void);
    void TIM2_C3_Init(void);
    void TIM4_IRQHandler(void);
    void LED (void);

        void setSysTick(void){
    // ---------- SysTick timer (1ms) -------- //
    if (SysTick_Config(SystemCoreClock / 1000)) {
    while (1);  // Capture error
    }
}
    volatile uint32_t msTicks=0; //counts 1ms timeTicks 
    void SysTick_Handler(void) {
    msTicks++;
}

static void Delay(__IO uint32_t dlyTicks){ 
  uint32_t curTicks; 
    curTicks = msTicks;
    while ((msTicks - curTicks) < dlyTicks);
}

        int main(void){
          SysTick_Handler();
            setSysTick();
            SetHSI();                             
            LED_GPIO();

            TIM2_C3_Init();
            TIM4_C1_Init();
while(1){

    LED();

Delay(100);
    }
}

/*----------------------------------------------------------------------------
  set HSI as SystemCoreClock (HSE is not populated on STM32L-Discovery board)
 *----------------------------------------------------------------------------*/
void SetHSI(void) {

// Turn on HSI (16MHz)
RCC->CR |= RCC_CR_HSION;
// Wait until HSI is ready
while( (RCC->CR & RCC_CR_HSIRDY) == 0);
// Select HSI as system clock
RCC->CFGR &= ~RCC_CFGR_SW_HSI;
RCC->CFGR |= RCC_CFGR_SW_HSI;
while( (RCC->CFGR & RCC_CFGR_SWS)!=RCC_CFGR_SWS_HSI ); // Wait till HSI
}

// Configure GPIO Port B
void LED_GPIO(void){


    RCC->AHBENR |= RCC_AHBENR_GPIOBEN;        // Enable GPIOB clock 

//PB7 LED ON/OFF
    GPIOB->MODER   |=   GPIO_MODER_MODER7_0;     // General purpose output mode
  GPIOB->OSPEEDR |=   GPIO_OSPEEDER_OSPEEDR7;  // Max High speed 50MHz


}

// CONFIGURE TIM2 FOR SENDING OUTPUT SIGNAL
void TIM2_C3_Init(void){

    RCC->AHBENR |= RCC_AHBENR_GPIOBEN;        // Enable GPIOB clock 

//PB10 Pluse Generating Pin
    GPIOB->MODER   &=   ~(0x03 << (2*10));     // Clear bit 12 & 13 Alternate function mode 
    GPIOB->MODER   |=   0x02 << (2*10);                 // set as Alternate function mode 
    GPIOB->OSPEEDR &=   ~(0x03<< (2*10));           // 40 MHz  speed 
    GPIOB->OSPEEDR |=   0x03<< (2*10);              // 40 MHz  speed 
    GPIOB->PUPDR &=         ~(1<<10);                           // NO PULL-UP PULL-DOWN 
    GPIOB->OTYPER &=        ~(1<<10);                           // PUSH-PULL 
    GPIOB->AFR[1] |=        0x1 << (4*2);                   // set PB pin 10 as AF1 (TIM2_CH3)

    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;                 // ENABLE TIM2 CLOCK
    TIM2->PSC = 159;                                                        // SET APPROPRAIT PRESCALER TO SLOW DOWN THE CLOCK
    TIM2->ARR = 0XFFFF;                                                 // SET MAX PULSE WIDTH OF 65536us FOR 16-BIT TIMER
    TIM2->CR1 |= TIM_CR1_DIR;                                       // Set downcounting counter direction
    TIM2->CCMR2 &= ~(TIM_CCMR2_OC3M);                       // Clear OC3M (Channel 3)
  TIM2->CCMR2 |= TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2;
    TIM2->CCMR2 |= TIM_CCMR2_OC3PE;                         // CH3 Output Preload Enable
    TIM2->CR1 |= TIM_CR1_ARPE;                                  // Auto-reload Prelaod Enable
    TIM2->CCER |= TIM_CCER_CC3E;                                // Enable Output for CH3
    TIM2->EGR |= TIM_EGR_UG;                                        // Force Update
    TIM2->SR &= ~TIM_SR_UIF;                                        // Clear the Update Flag
    TIM2->DIER |= TIM_DIER_UIE;                                 // Enable Interrupt on Update
    TIM2->CCR3 &= ~(TIM_CCR3_CCR3);                     // Clear CCR3 (Channel 3) 
    TIM2->CCR3 |= 0x1;                                            // Load the register 
    TIM2->CR1 |= TIM_CR1_CEN;                           // Enable the counter
}


// CONFIGURE TIM4 FOR RECEIVING INPUT SIGNAL
void TIM4_C1_Init(void){
    RCC->AHBENR |= RCC_AHBENR_GPIOBEN;        // Enable GPIOB clock 
    GPIOB->MODER   &=   ~(0x03 << 12);     // Clear bit 12 & 13 Alternate function mode 
    GPIOB->MODER   |=   (0x02 << 12);               // set as Alternate function mode 
    GPIOB->OSPEEDR &=   ~(0x03<< 12);           // 40 MHz  speed 
    GPIOB->OSPEEDR |=   (0x03<< 12);                // 40 MHz  speed 
    GPIOB->PUPDR &=         ~(0X3<<12);                         // NO PULL-UP PULL-DOWN 
    GPIOB->OTYPER &=        ~(1<<6);                            // PUSH-PULL 
    GPIOB->AFR[0] &= ~GPIO_AFRL_AFRL6;  // Clear pin 6 for alternate function
    GPIOB->AFR[0] |=        0x2 << (4*6);                   // set PB pin 6 as AF2 (TIM4_CH1) 

    RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;                 // ENABLE TIM4 CLOCK
    TIM4->PSC = 15;                                     // SET APPROPRAIT PRESCALER TO SLOW DOWN THE CLOCK                                              
    TIM4->CCMR1 &= ~TIM_CCMR1_CC1S;                         // CLEAR CAPTURE/COMPARE REGISTER
    TIM4->CCMR1 |= 0X1;                                                 // SELECT CH1 INPUTE CAPTURE 
    TIM4->CCMR1 &= ~TIM_CCMR1_IC1F;                         // DISABLE DIGITAL FILTERING
    TIM4->CCER |= (1<<1 | 1<<3);                                // SELECT BOTH RISING AND FALLING EDGE DETECTION CC1P & CC1NP
    TIM4->CCMR1 &= ~(TIM_CCMR1_IC1PSC);                 // INPUT PRESCALER 0 TO CAPTURE EACH VALID EDGE
    TIM4->CCER |= TIM_CCER_CC1E;                                // ENABLE COUNTER CAPTURE
    TIM4->DIER |= TIM_DIER_CC1IE;                               // ENABLE CH1 CAPTURE/COMPARE INTERRUPT
    TIM4->DIER |= TIM_DIER_CC1DE;   
    TIM4->DIER |= TIM_DIER_UIE;                                 // UPDATE INTERRUPT ENABLE
    TIM4->CR1 &= ~TIM_CR1_DIR;                                      // Set downcounting counter direction
    TIM4->CR1 |= TIM_CR1_CEN;                                       // Enable the counter
    NVIC_SetPriority(TIM4_IRQn, 1);                         // SET PRIORITY TO 1
    NVIC_EnableIRQ(TIM4_IRQn);                                  //ENABLE TIM4 INTERRUPT IN NVIC


}

void TIM4_IRQHandler(void){

    if ((TIM4->SR & TIM_SR_UIF) != 0){                  // Check the update event flag
        overflow++;                                 // if UIF = 1, increment overflow counter
        TIM4->SR &= ~TIM_SR_UIF;                                    // clear UIF
    }
    if ((TIM4->SR & TIM_SR_CC1IF) != 0){                // Check capture event flag 
    newcounter = TIM4->CCR1;                                        // read capture value, store as newcounter
    timespan = (newcounter - lastcounter)+(65536 * overflow);   // calculate the total pulse width
    lastcounter = newcounter;                               // save the value of newcounter as lastcounter to be used for the next cycle
    overflow = 0;                                                       // clear overflow counter
    }

}

void LED (void){

    float Distance;                                             // actual distance in cm
    Distance = (timespan / 58.0);

    if (Distance > 0.0 && Distance <= 100.0){

        GPIOB->BSRRL = (1<<7);
    }
        else {
        GPIOB->BSRRH = (1<<7);

        }   
    }