// 9A) Timer 2 v režimu PWM input. Měří frekvenci a střídu externího signálu a čas od času posílá USARTem na terminál // bez optimalizace na rychlost. Od určité frekvence je lepší periodu měřit jako počet impulzů za čas než jako množství času na impulz. #include "stm32f0xx.h" #include "stm32f0xx_ll_bus.h" #include "stm32f0xx_ll_rcc.h" #include "stm32f0xx_ll_gpio.h" #include "stm32f0xx_ll_utils.h" #include "stm32f0xx_ll_usart.h" #include "stm32f0xx_ll_tim.h" #include "stdio.h" void init_clock(void); void init_gpio(void); void init_usart1(void); void init_tim2(void); void usart1_puts(char *Buffer); char text[40]; volatile uint32_t t1,t2; // aktuální hodnoty periody a šířky pulzu uint32_t t1_calc,t2_calc; // kopie konkrétní dvojice t1 a t2 uint32_t freq=0; // frekvence (jednotkou 0.01Hz) uint32_t dcl=0; // střída (jednotkou 0.01%) int main(void){ init_clock(); // 48MHz (HSE bypass) init_gpio(); // LED na PC9 init_usart1(); // PA9 Tx (115200b/s) init_tim2(); // "PWM input režim" LL_Init1msTick(SystemCoreClock); // kvůli "delay" funkci while (1){ LL_mDelay(200); // ~5x za sekundu pošli výsledek měření na terminál __disable_irq(); // během kopírování t1 a t2 se hodnota žádné z nich nesmí změnit ! t1_calc=t1; // zkopírujeme si aktuální informaci o střídě a periodě t2_calc=t2; __enable_irq(); // trocha celočíselné matematiky (berte hodně s rezervou !) // když to má význam bývá lepší posílat surová data než je takhle kazit freq=100*(uint64_t)48000000/(uint64_t)t2_calc; // jednotkou frekvence je 0.01Hz dcl=(10000*(uint64_t)t1_calc)/(uint64_t)t2_calc; // jednotkou střídy je 0.01% // trocha formátování, zjišťování celé části, desetinné části čísla atd. snprintf(text,sizeof(text),"%01lu.%02lu Hz, %01u.%02u %%\n\r",freq/100,freq%100,(uint8_t)(dcl/100),(uint8_t)(dcl%100)); usart1_puts(text); // pošli zprávu do PC // celé to trvá cca 2ms } } void init_tim2(void){ LL_TIM_InitTypeDef tim; LL_TIM_StructInit(&tim); LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2); tim.Autoreload = 0xFFFFFFFF; // strop na maximum (nechceme si omezovat rozsah měření) tim.Prescaler = 0; // prescaler na minimum (nechceme si ničit přesnost měření - zničíme ji až pak výpočtem) LL_TIM_Init(TIM2,&tim); // do Capture registru 1 zavedeme signál TI2FP1 (z kanálu TIM2_CH2) s detekcí sestupné hrany, bez filtru, bez předděličky LL_TIM_IC_Config(TIM2,LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_INDIRECTTI | LL_TIM_ICPSC_DIV1 | LL_TIM_IC_FILTER_FDIV1 | LL_TIM_IC_POLARITY_FALLING); // do Capture registru 2 zavedeme signál TI2FP2 (z kanálu TIM2_CH2) s detekcí vzestupné hrany, bez filtru, bez předděličky LL_TIM_IC_Config(TIM2,LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI | LL_TIM_ICPSC_DIV1 | LL_TIM_IC_FILTER_FDIV1 | LL_TIM_IC_POLARITY_RISING); // jako trigger slouží signál TI2FP2 (vzestupná hrana na TIM2_CH2 - začátek signálu) LL_TIM_SetTriggerInput(TIM2,LL_TIM_TS_TI2FP2); // specifikujeme jak se má s příchozím triggrem naložit - resetovat timer (od vzestupné hrany začínáme měřit čas) LL_TIM_SetSlaveMode(TIM2,LL_TIM_SLAVEMODE_RESET); // povolíme si přerušení od kanálu 2 (dokončení měření) NVIC_SetPriority(TIM2_IRQn,2); NVIC_EnableIRQ(TIM2_IRQn); LL_TIM_EnableIT_CC2(TIM2); // povolíme oba kanály (input capture 1 a 2) LL_TIM_CC_EnableChannel(TIM2,LL_TIM_CHANNEL_CH1 | LL_TIM_CHANNEL_CH2); LL_TIM_EnableCounter(TIM2); // spustíme timer } // přerušení od timeru 2 void TIM2_IRQHandler(void){ // nezkoumám zdroj přerušení (vím že to je CC2, jiný jsem nepovolil) LL_TIM_ClearFlag_CC2(TIM2); // mažu vlajku přerušení t1=LL_TIM_IC_GetCaptureCH1(TIM2); // čas příchodu sestupné hrany (trvání pulzu log.1) t2=LL_TIM_IC_GetCaptureCH2(TIM2); // čas příchodu vzestupné hrany (trvání celé periody) } void usart1_puts(char *Buffer){ while(*Buffer){ // než narazíš na konec řetězce (znak /0) while(!LL_USART_IsActiveFlag_TXE(USART1)){}; // čekej než bude volno v Tx Bufferu LL_USART_TransmitData8(USART1,*Buffer++); // předej znak k odeslání } } void init_usart1(void){ LL_USART_InitTypeDef usart; LL_GPIO_InitTypeDef gp; // konfigurace pinů - PA9 jako TX LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA); LL_GPIO_StructInit(&gp); gp.Pin = LL_GPIO_PIN_9; gp.Mode = LL_GPIO_MODE_ALTERNATE; gp.Speed = LL_GPIO_SPEED_HIGH; gp.Alternate = LL_GPIO_AF_1; gp.Pull = LL_GPIO_PULL_NO; LL_GPIO_Init(GPIOA,&gp); // konfigurace USART LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_USART1); // clock pro USART1 z APB1 // USART1 si může vybírat z vícero zdrojů clocku (aby mohl běžet v režimu spánku) LL_RCC_SetUSARTClockSource(LL_RCC_USART1_CLKSOURCE_PCLK1); // clock z APB1 // konfigurace USARTu usart.BaudRate = 115200; // 115200 b/s, formát 8N1 (8bit, no parity, 1 stopbit) usart.DataWidth = LL_USART_DATAWIDTH_8B; usart.HardwareFlowControl = LL_USART_HWCONTROL_NONE; // řízení toku nepoužíváme usart.OverSampling = LL_USART_OVERSAMPLING_16; // pokud není nouze o rychlost raději 16x usart.Parity = LL_USART_PARITY_NONE; usart.StopBits = LL_USART_STOPBITS_1; usart.TransferDirection = LL_USART_DIRECTION_TX; // pouze vysíláme LL_USART_Init(USART1,&usart); // můžete hlídat návratovou hodnotu ... LL_USART_Enable(USART1); } // vstup PA1 do TIM2 void init_gpio(void){ LL_GPIO_InitTypeDef gp; LL_GPIO_StructInit(&gp); LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA); gp.Pin = LL_GPIO_PIN_1 ; gp.Mode = LL_GPIO_MODE_ALTERNATE; // Pin bude patřit timeru gp.Alternate = LL_GPIO_AF_2; // TIM2_CH2 LL_GPIO_Init(GPIOA,&gp); } // 48MHz z externího 8MHz signálu void init_clock(void){ LL_UTILS_PLLInitTypeDef pll; LL_UTILS_ClkInitTypeDef clk; pll.Prediv = LL_RCC_PREDIV_DIV_2; // 8MHz / 2 = 4MHz pll.PLLMul = LL_RCC_PLL_MUL_12; // 4MHz * 12 = 48MHz clk.AHBCLKDivider = LL_RCC_SYSCLK_DIV_1; // APB i AHB bez předděličky clk.APB1CLKDivider = LL_RCC_APB1_DIV_1; // voláme konfiguraci clocku LL_PLL_ConfigSystemClock_HSE(8000000,LL_UTILS_HSEBYPASS_ON,&pll,&clk); // aktualizuj proměnnou SystemCoreClock SystemCoreClockUpdate(); }