// // This is a mostly Wirish-free timer test. Wirish usage is minimized // because this is a test of the C timer interface in // , so it's good if it can be made to work even // when most or all of Wirish is missing. Because of that, you may // need to customize the following output configuration: // // Output is printed: // - on COMM_USART, // - via TX pin on port COMM_USART_PORT, bit COMM_USART_TX_BIT // - via RX pin on port COMM_USART_PORT, bit COMM_USART_RX_BIT // - at COMM_USART_BAUD baud. #define COMM_USART USART6 #define COMM_USART_BAUD 115200 #define COMM_USART_PORT GPIOG #define COMM_USART_TX_BIT 14 #define COMM_USART_RX_BIT 9 // Other optional configuration below. #include #include #include #include #include #include // // Configuration // // More output if true static bool verbose = true; // Timers to test // FIXME use feature test macros for smaller MCUs static timer_dev *timers[] = { // Available on all currently supported MCUs TIMER1, TIMER2, TIMER3, TIMER4, // Available on F1 (HD and up), F2 TIMER5, TIMER6, TIMER7, TIMER8, // Available on F1 (XL), F2 TIMER9, TIMER10, TIMER11, TIMER12, TIMER13, TIMER14, }; // // Test routines // typedef void (*timer_test_t)(timer_dev *); static void runTest(const char description[], timer_test_t test); static void runTests(void); static void testGetAndSetCount(timer_dev*); static void testPauseAndResume(timer_dev*); static void testTimerChannels(timer_dev*); // // Helpers // static void initTimer(timer_dev *dev); static int timerNumber(timer_dev *dev); // Hack: a systick-based delay, useful until delay_us() is fixed static void _delay(uint32 msec); // Wirish-less USART initialization routine static void init_usart(usart_dev *dev, gpio_dev *gdev, uint8 tx, uint8 rx); // Return whether or not the timer has capture/compare channel `ch'. // TODO: does something like this belong in the standard timer library? static bool timer_has_cc_ch(timer_dev *dev, int ch); // Printing routines and variants for verbose mode static void putstr(const char str[]); static void println(void); static void putstrln(const char str[]); static void putudec(uint32 val); static void puttimn(timer_dev *dev); static void v_putstr(const char str[]); static void v_println(); static void v_putstrln(const char str[]); static void v_putudec(uint32 val); static void v_puttimn(timer_dev *dev); // Used to visually separate output from different tests static void printBanner(void); // // Handler state // static int count1 = 0; static int count2 = 0; static int count3 = 0; static int count4 = 0; static int timer_num; // Current timer we're considering // // Timer capture/compare interrupt handlers // // These are shared between timers. The global variable timer_num // controls which timer they affect. // static void handler1(void); static void handler2(void); static void handler3(void); static void handler4(void); static voidFuncPtr handlers[] = {handler1, handler2, handler3, handler4}; // // setup() and loop() // void setup() { init_usart(COMM_USART, COMM_USART_PORT, COMM_USART_TX_BIT, COMM_USART_RX_BIT); _delay(5); println(); printBanner(); putstr("Initializing timers...\r\n"); timer_foreach(initTimer); putstr("Done. Running tests.\r\n"); runTests(); printBanner(); putstr("Done testing timers.\r\n"); } void loop() { } // // Test routine implementations // static void runTests(void) { runTest("timer_get_count()/timer_set_count()", testGetAndSetCount); runTest("timer_pause()/timer_resume()", testPauseAndResume); runTest("capture/compare channels and interrupts", testTimerChannels); } static void runTest(const char description[], timer_test_t test) { printBanner(); putstr("Testing "); putstr(description); putstrln("."); timer_foreach(test); } static void testGetAndSetCount(timer_dev *dev) { unsigned before, after; unsigned val_to_set = 1234; timer_pause(dev); before = timer_get_count(dev); timer_set_count(dev, val_to_set); after = timer_get_count(dev); timer_resume(dev); if (after != val_to_set) { puttimn(dev); putstr(": "); putstr("*** FAIL: get/set count for "); puttimn(dev); putstr("."); putstr("Start count = "); putudec(before); putstr(". Count set to "); putudec(val_to_set); putstr(", and now count is = "); putudec(after); println(); } else if (verbose) { puttimn(dev); putstr(": "); putstrln("[ok]"); } } // This hack works on all currently supported STM32 series, but you // may need to do something smarter in the future. The assertions // ensure that our assumptions hold for your target. static timer_dev *getDifferentTimerOnSameBusAs(timer_dev *dev) { rcc_clk_domain dev_domain = rcc_dev_clk(dev->clk_id); ASSERT(RCC_APB1 == dev_domain || RCC_APB2 == dev_domain); ASSERT(rcc_dev_clk(TIMER1->clk_id) == RCC_APB2); ASSERT(rcc_dev_clk(TIMER2->clk_id) == RCC_APB1); ASSERT(rcc_dev_clk(TIMER8->clk_id) == RCC_APB2); ASSERT(rcc_dev_clk(TIMER3->clk_id) == RCC_APB1); if (dev->clk_id == RCC_TIMER1) { return TIMER8; } if (dev->clk_id == RCC_TIMER2) { return TIMER3; } return dev_domain == RCC_APB2 ? TIMER1 : TIMER2; } // Rough test of pause and resume. // // Approximately half the time, dev is in the "pause" state and the // timer doesn't increment, while another timer (`base_dev') on the // same bus continues. dev and base_dev have identical start counts // and prescalers. // // Since dev and base_dev share a bus (and thus a base clock), and we // configure them to have the same prescaler and start count, the // ratio of their end counts should be approximately 1 : 2. We check // to make sure this is true, up to tolerance `epsilon'. static void testPauseAndResume(timer_dev *dev) { timer_dev *base_dev = getDifferentTimerOnSameBusAs(dev); unsigned start_count = 0, reload = 65535; // This prescaler should be enough to ensure that we don't // overflow, while still giving us a reasonably large number of // timer ticks. uint16 prescaler = CYCLES_PER_MICROSECOND * 50; double epsilon = .02; if (rcc_dev_clk(base_dev->clk_id) != rcc_dev_clk(dev->clk_id)) { putstrln("*** ERROR: cannot run test. Bus info is messed up."); return; } // Pause and set up timers timer_pause(base_dev); timer_pause(dev); timer_set_count(base_dev, start_count); timer_set_count(dev, start_count); timer_set_reload(base_dev, reload); timer_set_reload(dev, reload); timer_set_prescaler(base_dev, prescaler); timer_set_prescaler(dev, prescaler); timer_generate_update(base_dev); timer_generate_update(dev); // Resume the timers and run the test ASSERT(timer_get_count(base_dev) == start_count); ASSERT(timer_get_count(dev) == start_count); timer_resume(base_dev); timer_resume(dev); _delay(1000); timer_pause(dev); _delay(1000); timer_pause(base_dev); // Check the results unsigned dev_count = timer_get_count(dev); unsigned base_count = timer_get_count(base_dev); double count_ratio = ((double)dev_count / base_count); bool fail = false; if (count_ratio > 0.5 + epsilon || count_ratio < 0.5 - epsilon) { fail = true; } if (fail || verbose) { puttimn(dev); putstr(" vs. "); puttimn(base_dev); putstr(": "); if (fail) putstr("*** FAIL: "); else putstr("[ok] "); putstr("(dev = "); putudec(dev_count); putstr(") / (base = "); putudec(base_count); putstr(") = "); // hack hack hack putudec((int)count_ratio); count_ratio -= (int)count_ratio; putstr("."); int cr_x_100 = (int)(count_ratio * 100); int hundredths = cr_x_100 % 10; cr_x_100 /= 10; int tenths = cr_x_100 % 10; putudec(tenths); putudec(hundredths); println(); } } // This function touches every capture/compare channel of a given // timer. The channel counts should be equal within a timer // regardless of other interrupts on the system (note that this // doesn't really test timers with only a single capture/compare // channel; for that, you'll want to do visual inspection of timers // that share a bus, in verbose mode). static void testTimerChannels(timer_dev *dev) { switch (dev->type) { case TIMER_BASIC: v_putstr("Skipping basic timer "); v_puttimn(dev); v_println(); return; case TIMER_ADVANCED: case TIMER_GENERAL: // Set up v_puttimn(dev); v_println(); v_putstr("\tchannels: "); timer_num = timerNumber(dev); timer_pause(dev); count1 = 0; count2 = 0; count3 = 0; count4 = 0; timer_set_reload(dev, 0xFFFF); timer_set_prescaler(dev, 1); for (int c = 1; c <= 4; c++) { if (timer_has_cc_ch(dev, c)) { v_putudec(c); v_putstr("\t"); timer_set_compare(dev, c, 0xFFFF); timer_set_mode(dev, c, TIMER_OUTPUT_COMPARE); timer_attach_interrupt(dev, c, handlers[c - 1]); } } v_println(); // Run test timer_generate_update(dev); timer_resume(dev); _delay(250); timer_pause(dev); // Print results v_putstr("\tcounts: "); bool fail = false; bool mismatched[4] = {false, false, false, false}; int counts[4]; counts[0] = count1; counts[1] = count2; counts[2] = count3; counts[3] = count4; bool first = true; int first_count = -1; for (int c = 1; c <= 4; c++) { if (timer_has_cc_ch(dev, c)) { if (first) { first_count = counts[c - 1]; first = false; } if (!first && (counts[c - 1] != first_count)) { mismatched[c - 1] = true; fail = true; } v_putudec(counts[c - 1]); v_putstr("\t"); } } v_println(); if (fail) { for (int i = 0; i < 4; i++) { if (mismatched[i]) { putstr("*** FAIL: mismatch on "); puttimn(dev); putstr(", channel "); putudec(i + 1); putstr(": expected "); putudec(first_count); putstr(", got "); putudec(counts[i]); println(); } } } else { puttimn(dev); putstrln(" [ok]"); } v_println(); // Clean up for (int c = 1; c <= 4; c++) { if (timer_has_cc_ch(dev, c)) { timer_set_mode(dev, c, TIMER_DISABLED); } } break; } } // // Helper implementations // static void _delay(uint32 msec) { uint32 end = systick_uptime() + msec; while (systick_uptime() < end) ; } static void init_usart(usart_dev *dev, gpio_dev *gdev, uint8 tx, uint8 rx) { usart_config_gpios_async(dev, gdev, rx, gdev, tx, 0); usart_init(dev); usart_set_baud_rate(dev, USART_USE_PCLK, COMM_USART_BAUD); usart_enable(dev); } static bool timer_has_cc_ch(timer_dev *dev, int ch) { ASSERT(1 <= ch && ch <= 4); if (dev->type == TIMER_BASIC) return false; int tn = timerNumber(dev); return (// TIM1-5 and 8 have all four channels (tn <= 5 || tn == 8) || // TIM9 and 12 only have channels 1 and 2 ((tn == 9 || tn == 12) && ch <= 2) || // All other general purpose timers only have channel 1 (ch == 1)); } static void putstr(const char str[]) { usart_putstr(COMM_USART, str); } static void println(void) { putstr("\r\n"); } static void putstrln(const char str[]) { putstr(str); println(); } static void putudec(uint32 val) { usart_putudec(COMM_USART, val); } static void puttimn(timer_dev *dev) { putstr("TIM"); putudec(timerNumber(dev)); } static void v_putstr(const char str[]) { if (verbose) putstr(str); } static void v_println() { if (verbose) println(); } __attribute__((unused)) /* (shut up, gcc) */ static void v_putstrln(const char str[]) { if (verbose) putstrln(str); } static void v_putudec(uint32 val) { if (verbose) putudec(val); } static void v_puttimn(timer_dev *dev) { if (verbose) puttimn(dev); } // Used to visually separate output from different tests static void printBanner(void) { putstrln("-----------------------------------------------------"); } static void initTimer(timer_dev *dev) { v_puttimn(dev); timer_init(dev); switch (dev->type) { case TIMER_ADVANCED: case TIMER_GENERAL: v_putstr(" channels "); for (int c = 1; c <= 4; c++) { if (timer_has_cc_ch(dev, c)) { v_putudec(c); v_putstr(" "); timer_set_mode(dev, c, TIMER_OUTPUT_COMPARE); } } break; case TIMER_BASIC: break; } v_println(); } static int timerNumber(timer_dev *dev) { switch (dev->clk_id) { case RCC_TIMER1: return 1; case RCC_TIMER2: return 2; case RCC_TIMER3: return 3; case RCC_TIMER4: return 4; case RCC_TIMER5: return 5; case RCC_TIMER6: return 6; case RCC_TIMER7: return 7; case RCC_TIMER8: return 8; case RCC_TIMER9: return 9; case RCC_TIMER10: return 10; case RCC_TIMER11: return 11; case RCC_TIMER12: return 12; case RCC_TIMER13: return 13; case RCC_TIMER14: return 14; default: ASSERT(0); return 0; } } // // IRQ Handlers // static void handler1(void) { count1++; } static void handler2(void) { count2++; } static void handler3(void) { count3++; } static void handler4(void) { count4++; } // // init() and main() // __attribute__((constructor)) void premain() { init(); } int main(void) { setup(); while (true) { loop(); } return 0; }