libmaple/examples/test-timers.cpp

538 lines
14 KiB
C++
Raw Permalink Normal View History

//
// This is a mostly Wirish-free timer test. Wirish usage is minimized
// because this is a test of the C timer interface in
// <libmaple/timer.h>, 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 <libmaple/libmaple.h>
#include <libmaple/gpio.h>
#include <libmaple/usart.h>
#include <libmaple/systick.h>
Move public headers to include directories; related cleanups. Move libmaple/*.h to (new) libmaple/include/libmaple/. The new accepted way to include a libmaple header foo.h is with: #include <libmaple/foo.h> This is more polite in terms of the include namespace. It also allows us to e.g. implement the Arduino SPI library at all (which has header SPI.h; providing it was previously impossible on case-insensitive filesystems due to libmaple's spi.h). Similarly for Wirish. The old include style (#include "header.h") is now deprecated. libmaple/*.h: - Change include guard #defines from _FOO_H_ to _LIBMAPLE_FOO_H_. - Add license headers where they're missing - Add conditional extern "C" { ... } blocks where they're missing (they aren't always necessary, but we might was well do it against the future, while we're at it.). - Change includes from #include "foo.h" to #include <libmaple/foo.h>. - Move includes after extern "C". - Remove extra trailing newlines Note that this doesn't include the headers under libmaple/usb/ or libmaple/usb/usb_lib. These will get fixed later. libmaple/*.c: - Change includes from #include "foo.h" to #include <libmaple/foo.h>. Makefile: - Add I$(LIBMAPLE_PATH)/include/libmaple to GLOBAL_FLAGS. This allows for users (including Wirish) to migrate their code, but should go away ASAP, since it slows down compilation. Wirish: - Move wirish/**/*.h to (new) wirish/include/wirish/. This ignores the USB headers, which, as usual, are getting handled after everything else. - Similarly generify wirish/boards/ structure. For each supported board "foo", move wirish/boards/foo.h and wirish/boards/foo.cpp to wirish/boards/foo/include/board/board.h and wirish/boards/foo/board.cpp, respectively. Also remove the #ifdef hacks around the .cpp files. - wirish/rules.mk: put wirish/boards/foo/include in the include path (and add wirish/boards/foo/board.cpp to the list of sources to be compiled). This allows saying: #include <board/board.h> instead of the hack currently in place. We can allow the user to override this setting later to make adding custom board definitions easier. - Disable -Werror in libmaple/rules.mk, as the current USB warnings don't let the olimex_stm32_h103 board compile. We can re-enable -Werror once we've moved the board-specific bits out of libmaple proper. libraries, examples: - Update includes accordingly. - Miscellaneous cosmetic fixups. Signed-off-by: Marti Bolivar <mbolivar@leaflabs.com>
2011-11-15 18:45:43 +01:00
#include <libmaple/timer.h>
#include <wirish/boards.h>
//
// 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();
2011-05-02 20:33:05 +02:00
while (true) {
loop();
}
return 0;
}