Skocz do zawartości

[C] STM32F103 - synchronizacja czasu w kilku mikrokontrolerach / resetowanie licznika w reakcji na z


DonPablo

Pomocna odpowiedź

Cześć, potrzebuję pomysłu na synchronizację timerów w kilku mikrokontrolerach.

Robię na uczelni pewien projekt, który trochę mnie przerasta. Jedną z części jest uruchamianie peryferiów podłączonych do kilku mikrokontrolerów bardzo 'precyzyjnie czasowo' (brakuje mi słowa).

Docelowo ma to wyglądać jak na poniższym schemacie - fanout buffer powiela sygnał 1 PPS wysyłany przez odbiornik GPS i wprowadza go na GPIO mikrokontrolerów.

A, B, C, D, E to różnego rodzaju peryferia dwustanowe - światełka, brzęczyki, przekaźniki, itp.

Zbocze narastające PPS ma zresetować licznik, który to osiągając określone wcześniej wartości (tak jak w tabeli na rysunku poniżej) będzie zmieniał stan przypisanego do tej wartości pinu. Czyli po prostu wartość licznika ma być co sekundę ustawiana na 0, licznik liczyć ma w górę i po doliczeniu do wartości A ustawić na pewien czas (około 20 ms) stan pinu PC8 na wysoki i tak dalej.

Myślę, że te schematy wyglądają sensownie i ma to prawo działać (chyba że jest inaczej - dajcie znać). Cały problem sprowadza się do programowania.

Jak to ugryźć?

Muszę dodać, że na razie projekt jest na etapie walidacji, więc mam do dyspozycji tylko jeden mikrokontroler i fanout buffer.

Generuję 1 PPS na STMie, wprowadzam go do powielacza, a z niego z powrotem na GPIO tego samego STMa. I nie wiem co dalej. Jak zresetować licznik za każdym razem gdy na pinie wejściowym pojawi się zbocze narastające? Jak potem

W reference manualu znalazłem tryb działania external clock source / external trigger - czy to dobry trop?

Byłbym wdzięczny za pokierowanie mnie w tej podróży po mało znanej mi krainie :->

Mam nadzieję, że napisałem zrozumiale i niczego nie pominąłem.

Wrzucam też to, co do tej pory napisałem, chociaż nie wiem czy jest po co.

#include <stdbool.h>
#include "stm32f10x.h"

volatile uint32_t timer_ms = 0;

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

void delay_ms(int time)
{
timer_ms = time;
while(timer_ms > 0){};
}

void RCC_Config_HSI_PLL_Max(void) // 64 MHz
{
RCC_DeInit();

RCC_HSICmd(ENABLE); // enable HSI clock

FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2); // set wait state
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_16); // configure HSI PLL clock
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); // wait till HSI PLL is ready
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // select HSI PLL as system clock source
while(RCC_GetSYSCLKSource() != 0x08); // wait till HSI PLL is used as system clock source
RCC_HCLKConfig(RCC_SYSCLK_Div1); // 64 MHz
RCC_PCLK1Config(RCC_HCLK_Div2); // 32 MHz
RCC_PCLK2Config(RCC_HCLK_Div1); // 64 MHz
}

void InitializeTimer2(void) // 1 PPS signal generation
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

TIM_TimeBaseInitTypeDef tim;
TIM_TimeBaseStructInit(&tim);
tim.TIM_CounterMode = TIM_CounterMode_Up;
tim.TIM_Prescaler = 64000 - 1;
tim.TIM_Period = 1000 - 1;
tim.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &tim);

//	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}

void InitializeTimer3(void) // reset timer on rising edge of 1 PPS signal
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

TIM_ICInitTypeDef tim;
TIM_TimeBaseStructInit(&tim);
tim.TIM_ICPolarity = TIM_ICPolarity_Rising;
tim.TIM_ICSelection = TIM_ICSelection_DirectTI;
tim.TIM_ICPrescaler = TIM_ICPSC_DIV1;
tim.TIM_ICFilter = 0;
tim.TIM_Channel = TIM_Channel_1;
TIM_ICInit(TIM3, &tim);
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);

//	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//	TIM_Cmd(TIM2, ENABLE);
}

void InitializeClockOutput(void) // master clock output
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitTypeDef gpio;
GPIO_StructInit(&gpio);

gpio.GPIO_Pin = GPIO_Pin_5;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &gpio);
}

void InitializeClockInput(void) // slave clock input
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitTypeDef gpio;
GPIO_StructInit(&gpio);

gpio.GPIO_Pin = GPIO_Pin_6;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &gpio);
}

int main(void)
{
//NVIC_InitTypeDef nvic;

RCC_Config_HSI_PLL_Max();
InitializeTimer2();
InitializeClockOutput();
InitializeClockInput();

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

SysTick_Config(SystemCoreClock / 1000);*/

while (1)
{
 int timerValue = TIM_GetCounter(TIM2);
 if(timerValue == 990)
	 GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_SET);
 else if(timerValue == 1000 - 1)
	 GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET);
}
}
Link do komentarza
Share on other sites

To wygląda na tryb "one-pulse mode", gdzie możesz zaprogramować opóźnienie i długość impulsu po zewnętrznym wyzwoleniu (TIM1/8 - Advanced Control Timer).

Na pewno musisz pracować z zegarem wewnętrznym, bo będziesz taktował timer z głównego generatora procesora. Zewnętrzny jest tylko trigger, który powinien zerować i stratować timer i jego prescaler.

Nie wiem o jakich precyzjach czasowych mylisz, ale jeśli potrzebujesz naprawdę ścisło, to pomyślcie nad taktowaniem wszystkich procesorów z jednego wspólnego zegara.

Możecie też wykorzystać tryb Input Capture timera w połączeniu z jego wyjściami Output Compare. Wtedy wejście zatrzaskuje chwilę czasową gdy się pojawiło, program (obsługi tego przerwania) liczy opóźnienia od tej chwili i odpowiednio programuje rejestry TIMx_CCRx. Sygnały na wyjściach pojawią się wtedy "same", wyzwolone przez timer w odpowiednich chwilach. To kwestia organizacji programu.

Ale tak jeszcze na marginesie: czy naprawdę timingi diodek i przekaźników będą tak ścisłe, że musisz używać sprzętowych timerów i synchronizacji? Nikt nie zauważy zwłoki zapalenia LEDa z błędem nawet 10ms a sam przekaźnik tyle czasu to się w ogóle załącza. A system przerwań i prędkość tych procesorów pozwalają na zrobienie tego z dokładnością nawet 1us. Wystarczy doprowadzić 1pps do wejścia przerwania o wysokim priorytecie, gdzie będziesz programowo startował timer systemowy. Czy możesz zdradzić jakie jest zastosowanie tego urządzenia? Chyba się tym przejąłeś i postawiłeś sobie zupełnie niepotrzebne wymagania. Czasem tak jest, gdy nie panujesz nad całością a chcesz coś zrobić jak najlepiej. Czy mógłbyś podać przykładową sekwencję sygnałów wyjściowych, co dokładnie będziesz nimi sterował i po co?

Link do komentarza
Share on other sites

Co do zastosowania, to trochę uprościłem pisząc "światełka, brzęczyki [...]", dlatego że faktyczne urządzenia peryferyjne będą sterowane dwustanowo (albo będą włączone albo wyłączone - bez stanów przejściowych).

W rzeczywistości jest to pewien moduł do AUV (robota podwodnego), który ma za zadanie wyzwalać różnego rodzaju źródła światła (moduły LED, diody laserowe) i systemy akwizycji obrazu (kamery CMOS). Ma to służyć do gromadzenia informacji o ukształtowaniu powierzchni zbiornika wodnego i pozyskiwaniu fotografii tych powierzchni. Na podstawie analizy fotografii będzie badany materiał powierzchni (spektrografia, transmisja-absorpcja, te sprawy) - dlatego zdjęć potrzeba dużo, w oświetleniu różnymi długościami światła. Dodając, że robot ma poruszać się dosyć szybko (1 m/s), czas rzeczywiście jest istotny. Diody laserowe to część skanera triangulacyjnego. Muszę uninąć sytuacji, w której jednocześnie dioda laserowa i LED będą uruchomione jednocześnie, bo może to spowodować jakieś opóźnienie (nie obchodzi mnie w tej chwili po jakim czasie diody faktycznie gasną - to zadanie innego zespołu).

To tyle tytułem wyjaśnienia 🙂

Trafiłem do projektu bez odpowiedniego doświadczenia, ale taryfy ulgowej nie dostaję i wciąż muszę to "dowieźć".

Więc tak wygląda obecnie mój kod:

- sygnał 1 PPS symuluję ustawiając stan wysoki na pinie PA15 przy wykorzystaniu przerwania od TIM2

- kanał CH2 timera TIM1 próbowałem skonfigurować jako Input Capture - przemapowując częsciowo pin PB0

- poprowadziłem przewód połączeniowy z pinu PA15 do wejścia wspomnianego wcześniej układu powielacza (fanout buffer)

- wyjście tego układu połączyłem z pinem PB0 skonfigurowanym jako Alternative Function Push-Pull, więc stan tego wejścia będzie się zmieniał co pół sekundy

- dodałem fragment:

TIM_SelectInputTrigger(TIM1, TIM_TS_TI2FP2);
TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset);
TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);

który, jak zrozumiałem, spowoduje że z każdym zboczem narastającym licznik TIM1 będzie zaczynał od zera, o co właśnie mi chodziło w pierwszym poście

- żeby sprawdzić czy rzeczywiście tak jest, w IRS sprawdzam czy flaga przerwania CH2 TIM1 jest ustawiona, i jeśli jest, to chciałem zaświecić diodą (PA5 - tak tylko, dla sprawdzenia), no ale niestety dioda się nie zaświeca

Co tu jest nie tak? W dokumentacjach STM32 panuje taki chaos, że nie potrafię się połapać. To poniżej jest najlepszą wersją na jaką wpadłem, ale nadal złą.

#include <stdbool.h>
#include "stm32f10x.h"

void RCC_Config_HSI_PLL_Max(void) // 64 MHz
{
RCC_DeInit();

RCC_HSICmd(ENABLE); // enable HSI clock

FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2); // set wait state
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_16); // configure HSI PLL clock
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); // wait till HSI PLL is ready
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // select HSI PLL as system clock source
while(RCC_GetSYSCLKSource() != 0x08); // wait till HSI PLL is used as system clock source
RCC_HCLKConfig(RCC_SYSCLK_Div1); // 64 MHz
RCC_PCLK1Config(RCC_HCLK_Div2); // 32 MHz
RCC_PCLK2Config(RCC_HCLK_Div1); // 64 MHz
}

void InitializeTimer2(void) // 1 PPS signal generation
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

TIM_TimeBaseInitTypeDef tim;
TIM_TimeBaseStructInit(&tim);
tim.TIM_CounterMode = TIM_CounterMode_Up;
tim.TIM_Prescaler = 64000 - 1;
tim.TIM_Period = 500 - 1;
tim.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &tim);

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}

void InitializeClockOutput(void) // master clock output
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitTypeDef gpio;
GPIO_StructInit(&gpio);

gpio.GPIO_Pin = GPIO_Pin_5;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &gpio);

gpio.GPIO_Pin = GPIO_Pin_15;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &gpio);
}

int main(void)
{
TIM_ICInitTypeDef tim;
GPIO_InitTypeDef gpio;
NVIC_InitTypeDef nvic;

// TIM1 clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

// GPIOB clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

// AFIO clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

GPIO_PinRemapConfig(GPIO_PartialRemap_TIM1, ENABLE);

// TIM1 channel 2 (PB0) configuration (alternate function)
gpio.GPIO_Pin = GPIO_Pin_0;
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio);

// Enable the TIM1 global interrupt
nvic.NVIC_IRQChannel = TIM1_CC_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = 0x00;
nvic.NVIC_IRQChannelSubPriority = 0x01;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);

   /* TIM2 interrupt configuration */
   nvic.NVIC_IRQChannel = TIM2_IRQn;
   nvic.NVIC_IRQChannelPreemptionPriority = 0x00;
   nvic.NVIC_IRQChannelSubPriority = 0x02;
   nvic.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&nvic);

// TIM1 configuration: Input Capture mode
// External signal is connected to TIM1 CH2 pin (PB0)
// Rising edge is used as an active edge,

tim.TIM_Channel = TIM_Channel_2;
tim.TIM_ICPolarity = TIM_ICPolarity_Rising;
tim.TIM_ICSelection = TIM_ICSelection_DirectTI;
tim.TIM_ICPrescaler = TIM_ICPSC_DIV1;
tim.TIM_ICFilter = 0x5;

TIM_ICInit(TIM1, &tim);

TIM_SelectInputTrigger(TIM1, TIM_TS_TI2FP2);
TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset);
TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);

// assuming PCLK1 ticks at 64[MHz] -> slow down timer to 640 KHz
TIM_PrescalerConfig(TIM1, 100, TIM_PSCReloadMode_Immediate);

// TIM enable counter
TIM_Cmd(TIM1, ENABLE);

// Enable the CC2 Interrupt Request
TIM_ITConfig(TIM1, TIM_IT_CC2, ENABLE);

RCC_Config_HSI_PLL_Max();
InitializeTimer2();
InitializeClockOutput();

while (1)
{
}
}
void TIM1_CC_IRQHandler(void)
{
// capture/compare interrupt source ?
if(TIM_GetITStatus(TIM1, TIM_IT_CC2) == SET)
{
	// clear TIM1 capture/compare interrupt pending bit
	TIM_ClearITPendingBit(TIM1, TIM_IT_CC2);
	// switch on the LED
	GPIO_SetBits(GPIOA, GPIO_Pin_5);
}

else if(TIM_GetITStatus(TIM1, TIM_IT_CC2) == RESET)
{
	// clear TIM1 capture/compare interrupt pending bit
	TIM_ClearITPendingBit(TIM1, TIM_IT_CC2);
	// switch off the LED
	GPIO_ResetBits(GPIOA, GPIO_Pin_5);
}

TIM_ClearFlag(TIM1, TIM_FLAG_CC2);
}

void TIM2_IRQHandler()
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);

	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_15))
		GPIO_ResetBits(GPIOA, GPIO_Pin_15);
	else
		GPIO_SetBits(GPIOA, GPIO_Pin_15);
}
}
Link do komentarza
Share on other sites

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...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.