Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

[ARM] Interfacing HC-SR04 Ultrasonic Sensor with STM32L1

Status
Not open for further replies.

LightWorks

Newbie level 5
Joined
Jul 30, 2017
Messages
9
Helped
0
Reputation
0
Reaction score
0
Trophy points
1
Activity points
96
Hi All,

Since this is my first post, I'm expecting a decent support from the forum members :-D
before I write my question, I would like to ensure you that I went through the Reference manual, datasheet, and text book (Embedded Systems with ARM Cortex-M Microcontrollers in Assembly Language and C - 2nd Edition), so I'm really doing my best here. However, I'm still unable to make this sensor work.

What I'm trying to do here is to measure the distance by sending a 10us pulse to Trigger-pin in order to start emitting a 40kHz square wave, which should be detected back and enable interrupt whenever a rising or falling edge gets detected. The time it takes between rising and falling edge will decide the distance, after dividing that time with 58 (given in the sensor datasheet) to give distance in centimeters. I have added a short code to enable the LED connected to PB7 whenever the distance is 100cm or less.

Now here is the code I've written. you will notice that the novice coding style, that's fine, I'm self-learning, still on my way there. I could use any advice in that regard too.

Code:
#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 (16MHz)
 while( (RCC->CR & RCC_CR_HSIRDY) == 0);    // Wait until HSI is ready
 RCC->CFGR &= ~RCC_CFGR_SW_HSI;           // Select HSI as system clock
 RCC->CFGR |= RCC_CFGR_SW_HSI;
 while( (RCC->CFGR & RCC_CFGR_SWS)!=RCC_CFGR_SWS_HSI ); // Wait till HSI
}

// 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_UIE;								// UPDATE INTERRUPT ENABLE 
	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 uint16_t timespan = 0;								// Total pulse width
	volatile uint16_t lastcounter = 0;							// Timer counter value of the last event
	volatile uint16_t newcounter = 0;							// Timer counter value of the current event
	volatile int overflow = 0;								// Count the number of overflows
	volatile int 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;
	                PulseEnd = 0;

		if(overflow == 1){
				timespan = 0;
			}
			else {
				Distance = (timespan / 58) ;
			}
			Delay(1);
		}	
	if (Distance <= 100){
		
		GPIOB->BSRRL = (1<<7);
	}
		else {
			GPIOB->BSRRH = (1<<7);
		
		}	

	}	
	
}

The code is built with no errors nor warnings.
Unfortunately the LED never turns On whether there's a close object or not.
here are the troubleshooting that I've done:
- in the debugger, the variable "Distance" value never changes, i.e. = 0
- I tried to measure the voltage at the Trigger pin using a simple multimeter, but I don't think it's fast enough to show 10us square wave..so basically it shows solid 0.00 on the trigger pin
- made some changes on the code, but still won't work.

Things that I'm not sure about (and would love to have an explanation to) are:

- Where/when is the right place to set/reset the variables "PulseSent" and "PulseEnd" ? as I feel these two variables has a major role on why it's not working.
- The function "TIM2_Enable" is used for both configuring TIM2 as well as sending the 10us Pulse. Is this ok, or should I separate them into 2 different functions?

Your support will be appreciated..

Regards,
 

I did not understand why did you put a while (1){} statement within the setSysTick() function.
 

I did not understand why did you put a while (1){} statement within the setSysTick() function.

Hi Andre,

As far as I know (not that much),
Code:
while (1);
is some sort of a superloop, which is used to keep your program from exiting until you're ready, so in that case I guess it gives very short time to capture an error in the result of systick function after diving by 1000.
However, I've tried the same with several other codes and it worked. Furthermore, the same function is used in the demo code "Blinky" which comes with the discovery development board.

I hope you can help me out with this code as I'm really not sure where exactly did it go wrong.
 

As far as I know, once you got inside the while(1) loop, your program will never go away from there. I guess the original purpose of this were to trap the execution of the code and display something from there. If you are stating that this code worked with this function with another board then apologise, I will review the fundamentals of C. Anyway, in your case I would add a LED turn on/off at key parts of the program to check for example if the 'if' 's conditions are being achieved, ie. if(PulseSent && PulseEnd).
 
I've removed the
Code:
while(1);
part from the blinking LED code, and the outcome is exactly the same, it's blinking as it should. Just thought I should mention it.
 

Do you have any access to some sort of oscilloscope, even a cheap $200 USB one with software that runs on your PC? It is going to quickly become impossible to debug your embedded programming issues with only a low end DMM. Is this sensor already built into your Discovery board (I don't have one of these so don't know)? If it is something which you have added/connected yourself, please post schematic of how you have it connected.
 

Do you have any access to some sort of oscilloscope, even a cheap $200 USB one with software that runs on your PC? It is going to quickly become impossible to debug your embedded programming issues with only a low end DMM. Is this sensor already built into your Discovery board (I don't have one of these so don't know)? If it is something which you have added/connected yourself, please post schematic of how you have it connected.

A friend of mine had suggested the same few weeks back, so I'm waiting for the Oscilloscope, Logic Analyzer, and a proper DMM. Till then, I should figure our a good troubleshooting plan.
 

I've noticed that all of the similar cases (interfacing HC-SR04 with STM32 board) are using a different method. They are configuring the Echo & Trigger pins as input and output respectively, plus they are using External Interrupts. On the other hand I'm configuring them both as Alternate Function, where Trigger pin is PWM output, and Echo pin as Input capture, while using the timer interrupt registers.
I might eventually try what others are doing, but I need to know why my method did not work!
 

but I need to know why my method did not work!

Some tips on how to debug the code have already been given as for example at post #4, but so far any result has not been presented, what else do you want to hear to start doing yourself some experiments on that ?
 

Some tips on how to debug the code have already been given as for example at post #4, but so far any result has not been presented, what else do you want to hear to start doing some experiments on that ?

Apologies for the late reply.
Alright, so I've implemented the troubleshooting method recommended by you, and I was able to point out some issues, some I could resolve and other I couldn't.
I've made some minor updates on the code:

Code:
#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       
	
}

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);
}
	
	//Initialize the timers variables.
	volatile uint16_t timespan = 0;								// Total pulse width
	volatile uint16_t lastcounter = 0;							// Timer counter value of the last event
	volatile uint16_t newcounter = 0;							// Timer counter value of the current event
	volatile uint16_t overflow = 0;								// Count the number of overflows
	volatile uint16_t PulseEnd = 0;								// Declare end of pulse

// INITIALIZE TIM2, SET AS PWM OUTPUT

void TIM2_Init(){
	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

}

// CONFIGURE TIM2 FOR TRIGGER PULSE

void TIM2_Trigger (){
		TIM2->CR1 &= ~TIM_CR1_CEN;							// Disable Counter
		TIM2->CNT = 0;									// Set Timer count to 0
		PulseEnd = 0;
		overflow = 0;
		TIM2->CR1 |= TIM_CR1_CEN;						        // Enable Counter
		TIM2->CCR3 = 10;
	}
	

// INITIALIZE TIM2, SET AS  THROUGH INPUT CAPTURE

void TIM4_Init(){
	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->DIER |= DIER_UIE;                                                 // Enable Interrupt Update
	NVIC_SetPriority(TIM4_IRQn, 1);					// SET PRIORITY TO 1
	NVIC_EnableIRQ(TIM4_IRQn);						//ENABLE TIM4 INTERRUPT IN NVIC
}

// CONFIGURE TIM4 FOR READING ECHO PULSE

void TIM4_Echo_Read(){
	
	TIM4->CCER |= TIM_CCER_CC1E;					// ENABLE COUNTER CAPTURE
	TIM4->DIER |= TIM_DIER_CC1IE;					// ENABLE CH1 CAPTURE/COMPARE INTERRUPT
	if (TIM_SR_UIF != 0){					                // Check the update event 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)+(0XFFFF * 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
}
	if (TIM4->CCR1 == TIM4->ARR){                                      // set PulseEnd if input-capture counter reaches 65536
		PulseEnd = 1;
	}
}

	int main(void){
	
			float Distance = 0.0f;						// actual distance in cm
			int Pulsesent = 0;
		
	HSI_config();
	setSysTick();
	GPIO_config();
	TIM2_Init();
	TIM4_Init();
	
	
while(1){

if (Pulsesent == 0) {
	
		Pulsesent = 1;
		TIM2_Trigger();
                TIM4_Echo_Read();
}

if(Pulsesent && PulseEnd){
			Pulsesent = 0;
	
		if(overflow >= 1){
				timespan = 0;
			}
			else {
				Distance = (timespan / 58) ;
			}
			PulseEnd = 0;
			Delay(1);
			
		}	

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

	}	
	
}

Changes I made and problems I noticed when troubleshooting:

1- Moved the following lines from TIM4_Init() to TIM4_Echo_Read()
Code:
TIM4->CCER |= TIM_CCER_CC1E;		// ENABLE COUNTER CAPTURE
   TIM4->DIER |= TIM_DIER_CC1IE;			// ENABLE CH1 CAPTURE/COMPARE INTERRUPT
Just thought it should be part of the read function & not the initialization. Not sure if this would have an impact.

2- In the TIM4_Echo_Read() function, the following condition is always being skipped (i.e. it's never more than 0), where actually when I run the debugger it shows that both (TIM4->SR) & (TIM_SR_UIF) are not equal to 0.
Code:
 if ((TIM4->SR & TIM_SR_UIF) != 0) {
overflow = overflow + 1;					// if UIF = 1, increment overflow counter
TIM4->SR &= ~TIM_SR_UIF;				// clear UIF
	}
So I removed the (TIM4->SR) part as you can see in the updated code above. Now the condition runs through, and overflow is equal to 1.

3- The main issue I'm facing, and I don't have an explanation to it is, although the input capture counter is enabled via TIM4->CCER |= TIM_CCER_CC1E; I still get a 0 value when I try to read it through newcounter = TIM4->CCR1;, Note that the counter is enabled at the first line of the function TIM4_Echo_Read(). this was checked through the debugger.

4- Similarly, I keep getting a 0 value for the timespan. I know that the newcounter value is already 0, and I would be getting the wrong timespan value, but the second part of the equation shouldn't be 0, it should be something like this: timespan = (0 - 0)+(0XFFFF * 1). why do I get a 0 value in timespan ?
of course this means a 0 value of Distance at a later part of the code.

I'm still going through the code, & still waiting for my oscilloscope.

Thoughts?

Regards,
 
Last edited:

Do you have any access to some sort of oscilloscope, even a cheap $200 USB one with software that runs on your PC? It is going to quickly become impossible to debug your embedded programming issues with only a low end DMM. Is this sensor already built into your Discovery board (I don't have one of these so don't know)? If it is something which you have added/connected yourself, please post schematic of how you have it connected.

I forgot to answer the other part of the question. It's a standalone sensor, not included as part of the board.
here's the layout.
schm.png

I'm using a voltage divider to reduce the voltage, trying to avoid damaging the board pins.

- - - Updated - - -

Some other change that I made, I've changed the function void TIM4_Echo_Read() to int TIM4_Echo_Read()
and added return(timespan); at the end of the function.
:grin:
 

Status
Not open for further replies.

Similar threads

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top