Skocz do zawartości

Problem z poprawnymi odczytami z czujnika HC SR04 na płytce NUCLEOf103


Pomocna odpowiedź

Napisano

Witam!

Od kilku dni walczę z obsługą czujnika HC SR04 na stm32f103 na płytce NUCLEO. Cały czas uzyskuje błędne odczyty, wyświetlane na interfejsie UART ( zdjęcia z terminala zamieszczane w załącznikach). Po przeszukaniu internetu i różnych rozwiązań napisałem własny program do obsługi tego czujnika, który polega na :

1. wysłaniu impulsu na pinie TRIG o czasie 10[us], pin -> PA1

2. odczycie impulsu naa pinie ECHO z wykorzystaniem przerwania o zboczu narastajacym i opadajacym, pin -> PA6

3.wysłaniu wyniku na UART

Poniżej zamieszczam kod programu :

#include "stm32f10x.h"

#define GPIO_Trig       GPIOA
#define Trig_Pin        GPIO_Pin_1

#define GPIO_Echo       GPIOA
#define Echo_Pin        GPIO_Pin_6
#define EchoLine		EXTI_Line6
#define EchoIRQ		    EXTI9_5_IRQn

#define SR04_Prescaler  31

#define FallingEdge 0
#define RisingEdge 1

//Volatile interrupts
volatile uint8_t PulseTriggerEnded, InterruptEdge, PulseEchoEnded;
volatile uint32_t PulseTime=0;

/*-------------------Konfiguracja zewnetrznego zegara HSE jako zrodla taktujacego system--------------------------------*/
void RCC_Conf(void)
{
ErrorStatus HSEStartUpStatus;

//Reset ustawien RCC
RCC_DeInit();

//Wlacz HSE
RCC_HSEConfig(RCC_HSE_ON);

//Czekaj az HSE bedzie gotowy
HSEStartUpStatus = RCC_WaitForHSEStartUp();

if(HSEStartUpStatus == SUCCESS)
{
	FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

	//zwloka dla pamieci Flash
	FLASH_SetLatency(FLASH_Latency_2);

	//HCLK = SYSCLK
	RCC_HCLKConfig(RCC_SYSCLK_Div1);

	//PCLK2 = HCLK
	RCC_PCLK2Config(RCC_HCLK_Div1);

	//PCLK1 = HCLK/2
	RCC_PCLK1Config(RCC_HCLK_Div2);

	//PPLCLK = 16MHZ * 4 = 64 MHZ - do plytki wlutowano kwarc 16MHz z kondenasorami po 22pF i wykorzystano petle PLL x4
	RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_4);

	//Wlacz PLL
	RCC_PLLCmd(ENABLE);

	//Czekaj az PLL poprawnnie sie uruchomi
	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);

	//PLL bedzie zrodlem sygnalu zegarowego
	RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);


	//Czekaj az PLL bedzie sygnalem zegarowym systemu
	while(RCC_GetSYSCLKSource() != 0x08);
}
}

/*----------------------------------------------------------------------------------------------------------------------------------------------*/



/*-------------Podprogramy do obslugi funkcji printf dla interfejsu UART do wyswietlania wyniku pomiaru na terminalu----------*/

void send_char(char c)
{
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
USART_SendData(USART2, c);
}

void send_string(const char* s)
{
while (*s)
send_char(*s++);
}

int __io_putchar(int c)
{
if (c=='\n')
 {
 send_char('\r');
 }

send_char(c);
return c;
}

volatile uint32_t timer_ms = 0;

void SysTick_Handler()
{
if (timer_ms) {
timer_ms--;
}
}
/*------------------------------------------------------------------------------------------------------------------------------*/



void delay_us(int time)              //funkcja opozniajaca bazujaca na zegarze systemowym
{
timer_ms = time;
while (timer_ms) {};
}



/*----------------Obsluga przerwania od zegara TIM2 dla pinu TRIGGER----------------------*/
void TIM2_IRQHandler()
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //gdy licznik doliczy do 1000 tj 1000us generujemu przerwanie update
{
 TIM_ClearITPendingBit(TIM2, TIM_IT_Update);

}


if (TIM_GetITStatus(TIM2, TIM_IT_CC1) == SET)
{
 TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); //gdy licznik doliczy do 10 wystawimy na pinie Trig stan wysoki
 GPIO_SetBits(GPIO_Trig, Trig_Pin);
}

if (TIM_GetITStatus(TIM2, TIM_IT_CC2) == SET)
{
 TIM_ClearITPendingBit(TIM2, TIM_IT_CC2); //gdy licznik doliczy do 20 wystawimy na pinie Trig stan niski
 GPIO_ResetBits(GPIO_Trig, Trig_Pin);
}

}
/*-----------------------------------------------------------------------------------------------------------------------------------------------*/

void send_pulse(void) //funkcja do generowania pojedynczego impulsu TRIG = 10us
{
    TIM_Cmd(TIM2,DISABLE);
    TIM_SetCounter(TIM2,0);

 TIM_Cmd(TIM2, ENABLE);
 delay_us(30);

 PulseTriggerEnded = 1; //flaga konca impulsu trig
 TIM_SetCounter(TIM2,0); //wylaczmy i skasujmy licznik  , wyslalismy juz potrzebny impuls
 TIM_Cmd(TIM2,DISABLE);

 InterruptEdge = RisingEdge; //informacja dla przerwania od echo ze czekamy na przerwanie ze zboczem narastajacym

}


void EXTI9_5_IRQHandler(void)
{

if(EXTI_GetITStatus(EchoLine) == SET)
{

	EXTI_ClearITPendingBit(EchoLine);


	if(InterruptEdge == RisingEdge) //impuls powrotny echa ze zboczem narastajacym - zaczynamy odliczac czas impulsu dla echa
	{

		GPIO_SetBits(GPIOA, GPIO_Pin_5);
		PulseEchoEnded = 0;

		TIM_SetCounter(TIM3,0);
		TIM_Cmd(TIM3, DISABLE);
		TIM_Cmd(TIM3, ENABLE); //start liczymy czas dla impulsu echa


		InterruptEdge = FallingEdge; //informacja dla przerwania ze zboczem opadajacym
	}
	else
	{
		PulseTime = TIM_GetCounter(TIM3); //pobieramy czas impulsu echa
		TIM_Cmd(TIM3, DISABLE);  //wylaczamy licznik

		GPIO_ResetBits(GPIOA, GPIO_Pin_5);

		PulseEchoEnded = 1; //flaga informujaca o koncu pomiaru
	}
}
}


int main(void)
{
GPIO_InitTypeDef gpio;
TIM_TimeBaseInitTypeDef tim;
TIM_OCInitTypeDef  channel;
NVIC_InitTypeDef nvic;
EXTI_InitTypeDef exti;
RCC_ClocksTypeDef rc;
USART_InitTypeDef uart;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
SysTick_Config(SystemCoreClock / 1000000);


//Konfiguracja UART

GPIO_StructInit(&gpio);
gpio.GPIO_Pin = GPIO_Pin_2;
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &gpio);

gpio.GPIO_Pin = GPIO_Pin_3;
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio);

USART_StructInit(&uart);
uart.USART_BaudRate = 115200;
USART_Init(USART2, &uart);
USART_Cmd(USART2, ENABLE);


gpio.GPIO_Pin = GPIO_Pin_6;
gpio.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &gpio);

gpio.GPIO_Pin = GPIO_Pin_1;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &gpio);

/*----------------------------TIM2 dla TRIG----------------------------------*/
TIM_TimeBaseStructInit(&tim);
tim.TIM_CounterMode = TIM_CounterMode_Up;
tim.TIM_Prescaler = 32 - 1;  //zegar na lini z TIM2 ma czestotliwosc PCLk1 = HCLK/2 = 32MHZ dzielac przez 32 fTim2 = 1MHZ ->Ttim2=1us
tim.TIM_Period = 1000 - 1; //zliczamy do 1000us
TIM_TimeBaseInit(TIM2, &tim);


TIM_OCStructInit(&channel);
channel.TIM_OCMode = TIM_OCMode_Timing;

channel.TIM_Pulse = 10; //generuj przerwanie jak zliczy do 10
TIM_OC1Init(TIM2, &channel);

channel.TIM_OCMode = TIM_OCMode_Timing;

channel.TIM_Pulse = 20; //generuj przerwanie jak zliczy do 20
TIM_OC2Init(TIM2, &channel);

TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC1|TIM_IT_CC2, ENABLE); //


nvic.NVIC_IRQChannel = TIM2_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = 0;
nvic.NVIC_IRQChannelSubPriority = 0;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);

/*----------------------------TIM2 dla TRIG----------------------------------*/

/*---------------------------------------------------------------------------*/
/*----------------------------TIM3 dla ECHO----------------------------------*/

tim.TIM_CounterMode = TIM_CounterMode_Up;
tim.TIM_Prescaler = 32 - 1;  //zegar na lini z TIM3 ma czestotliwosc PCLk1 = HCLK/2 = 32MHZ dzielac przez 32 fTim3 = 1MHZ ->Ttim2=1us
tim.TIM_Period = 2000 - 1; //zliczamy do 2000us
TIM_TimeBaseInit(TIM3, &tim);

nvic.NVIC_IRQChannel = EchoIRQ;
nvic.NVIC_IRQChannelPreemptionPriority = 0;
nvic.NVIC_IRQChannelSubPriority = 0;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);


GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource6);

exti.EXTI_Line = EchoLine;
exti.EXTI_LineCmd = ENABLE;
exti.EXTI_Mode = EXTI_Mode_Interrupt;
exti.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_Init(&exti);

/*----------------------------TIM3 dla ECHO----------------------------------*/

RCC_GetClocksFreq(&rc);

float Distance = 0.0f, TimeOfFlight = 0.0f;


float TTimePerCnt = (SR04_Prescaler+1)/(float)rc.PCLK1_Frequency;  //(31+1)/32 MHZ = 1us rowny okresowi zegara TIM2


const float SpeedOfSound = 340.3f; //predkosc dzwieku przy 15 stC

uint8_t PulseTriggerSent = 0;

   /*GPIO_SetBits(GPIOA, GPIO_Pin_5);
   delay_us(5000000);
   GPIO_ResetBits(GPIOA, GPIO_Pin_5);
   delay_us(5000000);*/

while(1)
{


 if(PulseTriggerSent == 0)
 {
		PulseTriggerSent = 1;
		send_pulse();
 }

 if(PulseTriggerSent && PulseTriggerEnded && PulseEchoEnded)
 {




	    TimeOfFlight = TTimePerCnt*((float)PulseTime); //okres zegara x ilosc zliczonych impulsow
	    Distance = ((SpeedOfSound*TimeOfFlight)/2.0f); //d = 340 [m/s] * x [us] /2
		delay_us(200000); //200ms
	    PulseTriggerSent = 0;



 }





 printf("czas = %f [s] ,odleglosc od przeszkody = (%f [m])   \n",TimeOfFlight,Distance);


}



}

Bardzo proszę o pomoc w rozwiązaniu mojego problemu z obsługą tego czujnika, która przecież nie może być taka trudna.

Podsumowując :

- bardzo złe wyniki pomiarów

- powyżej 15 cm w ogóle czujnik nic nie widzi i często się zawiesza

PS:

dodatkowo pod zasilanie podpiąłem równolegle kondensatory 2x100uf (bez kondensatorów w sumie nie ma różnicy w odczycie)

Pomiar dla 10 cm :

Pomiar dla 20 cm :

  • Lubię! 1

Niestety nie posiadam oscyloskopu ani analizatora logicznego. Kupiłem nowy 2 czujnik ale on też wskazuje podobne odczyty. Moduły, które są teraz produkowane zawierają mniej elementów pasywnych, może coś poszło nie tak. Siedzę nad tym czujnikiem już kilka dni i cały czas nie mogę zrozumieć dlaczego nie działa jak powinien 🙁

  • Lubię! 1

Szkoda że nie masz sprzętu. Podróbka Saleae kosztuje 40zł, a daje bardzo duże możliwości w zakresie diagnostyki.

Załóżmy, że chociaż jeden z sensorów jest ok.

Próbowałeś napisać sobie program, który mierzyłby czas trwania impulsu echo poprzez zwykły polling pinu?

Wygląda na to, że w programie czai się kilka błędów. Zacznijmy od podstawowego:

tim.TIM_Prescaler = 32 - 1

Lepiej użyć preskalera o wartości 64, wtedy faktycznie częstotliwość wyniesie 1MHz, a nie 2MHz.

To powinno sporo poprawić w działaniu. Kolejna poprawka to wywołanie RCC_Conf(). Całkiem ładna funkcja, ale jakoś mało używana. Na szczęście domyślny kod wygenerowany dla Nucleo też używa 64MHz, więc i tak działa - ale mniej stabilnie, bo na RC zamiast używać kwarca.

Na mojej płytce udało mi się wygenerować impuls wyzwalający - niestety nie mam w tej chwili czujnika HC-SR04, więc więcej teraz nie przetestuję.

Ja się jeszcze zastanawiam, czy nie zasilasz czasem tego HC SR-04 napięciem 3.3V? Bo on wymaga 5V do działania. HC SR-04+ działa na 3V.

Układ jest zasilany napięciem 5V, a na pinie echo zastosowałem dodatkowo dzielnik napięciowy aby do STM podawać max 3V3.

Wygląda na to, że w programie czai się kilka błędów. Zacznijmy od podstawowego:
tim.TIM_Prescaler = 32 - 1

url][/url][/center]

Dzielnik jest dobry ponieważ szyna APB1 taktowana jest z częstotliwością(max 36MHZ) a tu HCLK/2 = 64/2 =32MHZ, zewnetrzny kwac działa z fHSE=64MHZ (doczytałem o tym w książce K.Paprocki Mikrokontrolery STM32 w praktyce). Funkcja RCE_conf() musi działać pisałem ją w oparciu o listing z powyższej książki.

Jakie są inne błędy w programie Elvis?

Szkoda że nie masz sprzętu. Podróbka Saleae kosztuje 40zł, a daje bardzo duże możliwości w zakresie diagnostyki.

Załóżmy, że chociaż jeden z sensorów jest ok.

Próbowałeś napisać sobie program, który mierzyłby czas trwania impulsu echo poprzez zwykły polling pinu?

Jeszcze nie pisałem programu na pooling, spróbuje to zrobić i zamówię sobie analizer z allegro 😉

  • Lubię! 1

Dzielnik jest niestety zły. To prawda, że APB1 jest taktowana częstotliwością HCLK/2, ale timery są wyjątkiem - pracują z pełną prędkością APB1 x 2, czyli HCLK.

Ale żeby nie było, że bezgranicznie wierzę dokumentacji - uruchamiałem Twój program i sprawdzałem analizatorem wyniki. Trzeba ustawić preskaler na 64, żeby działało poprawnie, tym razem producent nie oszukał.

Nie twierdzę, że RCE_conf() jest zła - po prostu nie jest używana. Dla mnie była to zaleta, bo program działał pomimo braku kwarca 16MHz 🙂

Na początek spróbuj poprawić preskaler i zwiększ szerokość impulsu wyzwalającego, teraz generujesz ok. 5-6 us, więc sporo za mało (minimum 10us).

  • Lubię! 1

Elvis dzięki, zmieniłem wartość preskalera na 64 i czujnik działa poprawnie. Generuje dobry impuls 10us ,a nie jak wczesniej 5-6 us.

Temat zamknięty, wszystko działa ! 😉

  • Lubię! 1

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...