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);
}
}
}
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
PulseEndgets set as a side-effect when inmain()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 setsPulseSentto 1, the testif (PulseSent && PulseEnd)is of course true and your code immediately thinks an echo has been detected whiletimespanis probably still 0, certainly less than 5800 (you dividetimespanby a random magic number 58 to getDistance, and then compareDistanceto 100 to decide if LED should be on or off). HOWEVER that isn't your only problem, for exampletimespanis auint8_twhich 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 intimespanfrom 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
overflowinmain(), 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 whenoverflow==1(but not if overflow>1, and couldn't overflow validly be 1 as it is being incremented by the TIM4 interrupt code?) thenDistanceis not recalculated, yet subsequent code then usesDistanceto illuminate/extinguish the LED. This stinks of more logic problems.newcounterandlastcounterare 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.