aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-bkp.cpp80
-rw-r--r--tests/test-dac.cpp51
-rw-r--r--tests/test-fsmc.cpp122
-rw-r--r--tests/test-print.cpp184
-rw-r--r--tests/test-ring-buffer-insertion.cpp114
-rw-r--r--tests/test-serial-flush.cpp38
-rw-r--r--tests/test-serialusb.cpp126
-rw-r--r--tests/test-servo.cpp152
-rw-r--r--tests/test-session.cpp942
-rw-r--r--tests/test-spi-roundtrip.cpp192
-rw-r--r--tests/test-systick.cpp49
-rw-r--r--tests/test-timers.cpp537
-rw-r--r--tests/test-usart-dma.cpp211
13 files changed, 2798 insertions, 0 deletions
diff --git a/tests/test-bkp.cpp b/tests/test-bkp.cpp
new file mode 100644
index 0000000..719cac7
--- /dev/null
+++ b/tests/test-bkp.cpp
@@ -0,0 +1,80 @@
+#include <stdio.h> // for snprintf()
+
+#include <wirish/wirish.h>
+#include <libmaple/bkp.h>
+#include <libmaple/iwdg.h>
+
+void print_bkp_contents();
+void write_to_bkp(uint16 val);
+
+#define comm Serial2
+
+void setup() {
+ pinMode(BOARD_BUTTON_PIN, INPUT);
+
+ comm.begin(9600);
+ comm.println("*** Beginning BKP test");
+
+ comm.println("Init...");
+ bkp_init();
+ comm.println("Done.");
+
+ print_bkp_contents();
+ write_to_bkp(10);
+ print_bkp_contents();
+
+ comm.println("Enabling backup writes.");
+ bkp_enable_writes();
+ write_to_bkp(20);
+ print_bkp_contents();
+
+ comm.println("Disabling backup writes.");
+ bkp_disable_writes();
+ write_to_bkp(30);
+ print_bkp_contents();
+
+ comm.println("Done testing backup registers; press button to enable "
+ "independent watchdog (in order to cause a reset).");
+ waitForButtonPress(0);
+ iwdg_init(IWDG_PRE_4, 1);
+ comm.println();
+}
+
+void loop() {
+}
+
+void print_bkp_contents() {
+ comm.println("Backup data register contents:");
+ char buf[100];
+ for (int i = 1; i <= BKP_NR_DATA_REGS; i++) {
+ snprintf(buf, sizeof buf, "DR%d: %d ", i, bkp_read(i));
+ comm.print(buf);
+ if (i % 5 == 0) comm.println();
+ }
+ comm.println();
+}
+
+void write_to_bkp(uint16 val) {
+ comm.print("Attempting to write ");
+ comm.print(val);
+ comm.println(" to backup registers...");
+ for (int i = 1; i <= BKP_NR_DATA_REGS; i++) {
+ bkp_write(i, val);
+ }
+ comm.println("Done.");
+}
+
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ init();
+ setup();
+
+ while (1) {
+ loop();
+ }
+ return 0;
+}
+
diff --git a/tests/test-dac.cpp b/tests/test-dac.cpp
new file mode 100644
index 0000000..af188cc
--- /dev/null
+++ b/tests/test-dac.cpp
@@ -0,0 +1,51 @@
+/*
+ * Simple DAC test.
+ *
+ * Author: Marti Bolivar <mbolivar@leaflabs.com>
+ *
+ * This file is released into the public domain.
+ */
+
+#include <wirish/wirish.h>
+#include <libmaple/dac.h>
+
+uint16 count = 0;
+
+void setup() {
+ pinMode(BOARD_LED_PIN, OUTPUT);
+ digitalWrite(BOARD_LED_PIN, HIGH);
+
+ Serial1.begin(9600);
+ Serial1.println("**** Beginning DAC test");
+
+ Serial1.print("Init... ");
+ dac_init(DAC, DAC_CH1 | DAC_CH2);
+ Serial1.println("Done.");
+}
+
+void loop() {
+ toggleLED();
+ delay(100);
+
+ count += 100;
+ if (count > 4095) {
+ count = 0;
+ }
+
+ dac_write_channel(DAC, 1, 4095 - count);
+ dac_write_channel(DAC, 2, count);
+}
+
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (true) {
+ loop();
+ }
+ return 0;
+}
+
diff --git a/tests/test-fsmc.cpp b/tests/test-fsmc.cpp
new file mode 100644
index 0000000..1621317
--- /dev/null
+++ b/tests/test-fsmc.cpp
@@ -0,0 +1,122 @@
+#include <stddef.h> // for ptrdiff_t
+
+#include <wirish/wirish.h>
+#include <libmaple/fsmc.h>
+
+#ifndef BOARD_maple_native
+#error "Sorry, this example only works on Maple Native."
+#endif
+
+// Start of FSMC SRAM bank 1
+static uint16 *const sram_start = (uint16*)0x60000000;
+// End of Maple Native SRAM chip address space (512K 16-bit words)
+static uint16 *const sram_end = (uint16*)0x60100000;
+
+void test_single_write(void);
+void test_all_addresses(void);
+
+void setup() {
+ pinMode(BOARD_LED_PIN, OUTPUT);
+ digitalWrite(BOARD_LED_PIN, HIGH);
+
+ SerialUSB.read();
+ SerialUSB.println("*** Beginning RAM chip test");
+
+ test_single_write();
+ test_all_addresses();
+
+ SerialUSB.println("Tests pass, finished.");
+ SerialUSB.println("***\n");
+}
+
+void loop() {
+}
+
+void test_single_write() {
+ uint16 *ptr = sram_start;
+ uint16 tmp;
+
+ SerialUSB.print("Writing 0x1234... ");
+ *ptr = 0x1234;
+ SerialUSB.println("Done.");
+
+ SerialUSB.print("Reading... ");
+ tmp = *ptr;
+ SerialUSB.print("Done: 0x");
+ SerialUSB.println(tmp, HEX);
+
+ if (tmp != 0x1234) {
+ SerialUSB.println("Mismatch; abort.");
+ ASSERT(0);
+ }
+}
+
+void test_all_addresses() {
+ uint32 start, end;
+ uint16 count = 0;
+ uint16 *ptr;
+
+ SerialUSB.println("Now writing all memory addresses (unrolled loop)");
+ start = micros();
+ for (ptr = sram_start; ptr < sram_end;) {
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ *ptr++ = count++;
+ }
+ end = micros();
+ SerialUSB.print("Done. Elapsed time (us): ");
+ SerialUSB.println(end - start);
+
+ SerialUSB.println("Validating writes.");
+ for (ptr = sram_start, count = 0; ptr < sram_end; ptr++, count++) {
+ uint16 value = *ptr;
+ if (value != count) {
+ SerialUSB.print("mismatch: 0x");
+ SerialUSB.print((uint32)ptr);
+ SerialUSB.print(" = 0x");
+ SerialUSB.print(value, HEX);
+ SerialUSB.print(", should be 0x");
+ SerialUSB.print(count, HEX);
+ SerialUSB.println(".");
+ ASSERT(0);
+ }
+ }
+ SerialUSB.println("Done; all writes seem valid.");
+
+ ptrdiff_t nwrites = sram_end - sram_start;
+ double us_per_write = double(end-start) / double(nwrites);
+ SerialUSB.print("Number of writes = ");
+ SerialUSB.print(nwrites);
+ SerialUSB.print("; avg. time per write = ");
+ SerialUSB.print(us_per_write);
+ SerialUSB.print(" us (");
+ SerialUSB.print(1 / us_per_write);
+ SerialUSB.println(" MHz)");
+}
+
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (true) {
+ loop();
+ }
+
+ return 0;
+}
diff --git a/tests/test-print.cpp b/tests/test-print.cpp
new file mode 100644
index 0000000..bdc1894
--- /dev/null
+++ b/tests/test-print.cpp
@@ -0,0 +1,184 @@
+/*
+ * print-test.cpp
+ *
+ * Tests the various Print methods. (For USBSerial; assuming that
+ * writing a single character works, this should generalize to
+ * HardwareSerial).
+ *
+ * This file is released into the public domain.
+ */
+
+#include <wirish/wirish.h>
+#undef min
+#undef max
+
+// For snprintf()
+#include <stdio.h>
+// The <limits.h> that comes with newlib is missing LLONG_MAX, etc.
+#include <limits>
+
+using namespace std;
+
+#define BUF_SIZE 100
+char buf[BUF_SIZE];
+
+void test_numbers(void);
+void test_base_arithmetic(void);
+void test_floating_point(void);
+
+void print_separator(void);
+
+void setup() {
+ while (!SerialUSB.available())
+ continue;
+ SerialUSB.read();
+}
+
+void loop() {
+ SerialUSB.println("Testing Print methods.");
+ print_separator();
+
+ test_numbers();
+ print_separator();
+
+ test_base_arithmetic();
+ print_separator();
+
+ test_floating_point();
+ print_separator();
+
+ SerialUSB.println("Test finished.");
+ while (true) {
+ continue;
+ }
+}
+
+void test_numbers(void) {
+ SerialUSB.println("Numeric types:");
+
+ SerialUSB.print("unsigned char: ");
+ // prevent Print from treating it as an (extended) ASCII character:
+ SerialUSB.println((uint32)numeric_limits<unsigned char>::max());
+
+ SerialUSB.print("int: ");
+ SerialUSB.print(numeric_limits<int>::min());
+ SerialUSB.print(" -- ");
+ SerialUSB.println(numeric_limits<int>::max());
+
+ SerialUSB.print("unsigned int: ");
+ SerialUSB.print(numeric_limits<unsigned int>::max());
+ SerialUSB.println();
+
+ SerialUSB.print("long: ");
+ SerialUSB.print(numeric_limits<long>::min());
+ SerialUSB.print(" -- ");
+ SerialUSB.println(numeric_limits<long>::max());
+
+ SerialUSB.print("long long: ");
+ SerialUSB.print(numeric_limits<long long>::min());
+ SerialUSB.print(" -- ");
+ SerialUSB.println(numeric_limits<long long>::max());
+
+ SerialUSB.print("unsigned long long: ");
+ SerialUSB.println(numeric_limits<unsigned long long>::max());
+}
+
+void base_test(int base) {
+ SerialUSB.print("\tuint8: ");
+ SerialUSB.println(numeric_limits<uint8>::max(), base);
+ SerialUSB.print("\tint: ");
+ SerialUSB.print(numeric_limits<int>::max(), base);
+ SerialUSB.print(", unsigned int: ");
+ SerialUSB.println(numeric_limits<unsigned int>::max(), base);
+ SerialUSB.print("\tlong: ");
+ SerialUSB.print(numeric_limits<long>::max(), base);
+ SerialUSB.print(", unsigned long: ");
+ SerialUSB.println(numeric_limits<unsigned long>::max(), base);
+ SerialUSB.print("\tlong long: ");
+ SerialUSB.print(numeric_limits<long long>::max(), base);
+ SerialUSB.print(", unsigned long long: ");
+ SerialUSB.println(numeric_limits<unsigned long long>::max(), base);
+}
+
+void test_base_arithmetic(void) {
+ SerialUSB.println("Base arithmetic:");
+
+ SerialUSB.println("Binary:");
+ base_test(BIN);
+
+ SerialUSB.println("Octal:");
+ base_test(OCT);
+
+ SerialUSB.println("Decimal:");
+ base_test(DEC);
+
+ SerialUSB.println("Hexadecimal:");
+ base_test(HEX);
+}
+
+void test_floating_point(void) {
+ double dmax = numeric_limits<double>::max();
+
+ SerialUSB.println("Floating point:");
+
+ SerialUSB.print("println(-5.67): ");
+ SerialUSB.print(-5.67);
+ SerialUSB.print(". println(5.67, 5): ");
+ SerialUSB.println(5.67, 5);
+ SerialUSB.print("println((double)(LLONG_MAX - 10)): ");
+ SerialUSB.print((double)(numeric_limits<long long>::max() - 10));
+ SerialUSB.print("; from snprintf(): ");
+ snprintf(buf, BUF_SIZE, "%.2f",
+ (double)(numeric_limits<long long>::max() - 10));
+ SerialUSB.println(buf);
+ SerialUSB.print("println((double)LLONG_MAX / 2): ");
+ SerialUSB.print((double)(numeric_limits<long long>::max()) / 2);
+ SerialUSB.print("; from snprintf(): ");
+ snprintf(buf, BUF_SIZE, "%.2f",
+ (double)(numeric_limits<long long>::max()) / 2);
+ SerialUSB.println(buf);
+ SerialUSB.print("DBL_MAX: ");
+ SerialUSB.print(dmax);
+ SerialUSB.print("; from snprintf(): ");
+ snprintf(buf, BUF_SIZE, "%g", dmax);
+ SerialUSB.println(buf);
+ SerialUSB.print("-DBL_MAX / 2: ");
+ SerialUSB.print(-dmax / 2.0);
+ SerialUSB.print("; from snprintf(): ");
+ snprintf(buf, BUF_SIZE, "%g", -dmax / 2.0);
+ SerialUSB.println(buf);
+ SerialUSB.print("Double epsilon, round error: ");
+ SerialUSB.print(numeric_limits<double>::epsilon());
+ SerialUSB.print(", ");
+ SerialUSB.println(numeric_limits<double>::round_error());
+
+ SerialUSB.println();
+
+ float fmax = numeric_limits<float>::max();
+
+ SerialUSB.print("println(-5.67f): ");
+ SerialUSB.println(-5.67f);
+ SerialUSB.print("Float max: ");
+ SerialUSB.println(fmax);
+}
+
+void print_separator(void) {
+ SerialUSB.println();
+ SerialUSB.println(" ** ");
+ SerialUSB.println();
+}
+
+// Force init to be called *first*, i.e. before static object allocation.
+// Otherwise, statically allocated objects that need libmaple may fail.
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (1) {
+ loop();
+ }
+ return 0;
+}
diff --git a/tests/test-ring-buffer-insertion.cpp b/tests/test-ring-buffer-insertion.cpp
new file mode 100644
index 0000000..2188b03
--- /dev/null
+++ b/tests/test-ring-buffer-insertion.cpp
@@ -0,0 +1,114 @@
+/*
+ * Simple ring_buffer test.
+ *
+ * Does a basic test of functionality on rb_full_count(), rb_reset(),
+ * rb_push_insert(), and rb_safe_insert().
+ *
+ * To test:
+ *
+ * - Connect a serial monitor to SerialUSB
+ * - Press any key
+ *
+ * This file is released into the public domain.
+ */
+
+#include <wirish/wirish.h>
+
+#include <libmaple/ring_buffer.h>
+
+#define BUF_SIZE 64
+ring_buffer ring_buf;
+ring_buffer *rb;
+uint8 rb_buffer[BUF_SIZE];
+
+void test_rb_push_insert(int num_bytes_to_insert);
+void test_rb_safe_insert(int num_bytes_to_insert);
+void test_rb_insertion_function(int num_bytes_to_insert,
+ int (*insertion_fn)(ring_buffer*, uint8),
+ const char insertion_fn_name[]);
+void print_rb_contents(void);
+
+void setup() {
+ rb = &ring_buf;
+ rb_init(rb, BUF_SIZE, rb_buffer);
+
+ while (!SerialUSB.available())
+ ;
+
+ SerialUSB.println("Beginning test.");
+ SerialUSB.println();
+}
+
+void loop() {
+ test_rb_push_insert(63);
+ SerialUSB.println("------------------------------");
+ test_rb_push_insert(64);
+ SerialUSB.println("------------------------------");
+ test_rb_safe_insert(63);
+ SerialUSB.println("------------------------------");
+ test_rb_safe_insert(64);
+ SerialUSB.println("------------------------------");
+
+ SerialUSB.println();
+ SerialUSB.println("Test finished.");
+ while (true)
+ ;
+}
+
+void test_rb_push_insert(int num_bytes_to_insert) {
+ test_rb_insertion_function(num_bytes_to_insert,
+ rb_push_insert,
+ "rb_push_insert()");
+}
+
+void test_rb_safe_insert(int num_bytes_to_insert) {
+ test_rb_insertion_function(num_bytes_to_insert,
+ rb_safe_insert,
+ "rb_safe_insert()");
+}
+
+void test_rb_insertion_function(int num_bytes_to_insert,
+ int (*insertion_fn)(ring_buffer *, uint8),
+ const char insertion_fn_name[]) {
+ SerialUSB.println("resetting ring buffer.");
+ rb_reset(rb);
+ print_rb_contents();
+
+ SerialUSB.print(insertion_fn_name);
+ SerialUSB.print("-ing ");
+ SerialUSB.print(num_bytes_to_insert);
+ SerialUSB.println(" bytes.");
+ for (uint8 i = 1; i <= num_bytes_to_insert; i++)
+ insertion_fn(rb, i);
+
+ uint16 count = rb_full_count(rb);
+ SerialUSB.print("rb_full_count(rb) = ");
+ SerialUSB.println(count);
+
+ print_rb_contents();
+}
+
+void print_rb_contents() {
+ uint16 count = rb_full_count(rb);
+ SerialUSB.print("ring buffer contents: ");
+ for (uint16 i = 0; i < count; i++) {
+ SerialUSB.print((int)rb_remove(rb));
+ if (i < count - 1) SerialUSB.print(", ");
+ }
+ SerialUSB.println();
+}
+
+// Force init to be called *first*, i.e. before static object allocation.
+// Otherwise, statically allocated objects that need libmaple may fail.
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (true) {
+ loop();
+ }
+ return 0;
+}
diff --git a/tests/test-serial-flush.cpp b/tests/test-serial-flush.cpp
new file mode 100644
index 0000000..409d1f9
--- /dev/null
+++ b/tests/test-serial-flush.cpp
@@ -0,0 +1,38 @@
+/*
+ * Tests the "flush" Serial function.
+ */
+
+#include <wirish/wirish.h>
+
+void setup() {
+ Serial1.begin(9600);
+ Serial1.println("Hello world!");
+}
+
+void loop() {
+ Serial1.println("Waiting for multiple input...");
+ while (Serial1.available() < 5)
+ ;
+ Serial1.println(Serial1.read());
+ Serial1.println(Serial1.read());
+ Serial1.flush();
+
+ if (Serial1.available()) {
+ Serial1.println("FAIL! Still had junk in the buffer...");
+ }
+}
+
+// Force init to be called *first*, i.e. before static object allocation.
+// Otherwise, statically allocated objects that need libmaple may fail.
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (true) {
+ loop();
+ }
+ return 0;
+}
diff --git a/tests/test-serialusb.cpp b/tests/test-serialusb.cpp
new file mode 100644
index 0000000..098e445
--- /dev/null
+++ b/tests/test-serialusb.cpp
@@ -0,0 +1,126 @@
+// Tests SerialUSB functionality.
+
+#include <wirish/wirish.h>
+#include "usb_cdcacm.h"
+
+#define QUICKPRINT 0
+#define BIGSTUFF 1
+#define NUMBERS 2
+#define SIMPLE 3
+#define ONOFF 4
+
+uint32 state = 0;
+
+void setup() {
+ /* Set up the LED to blink */
+ pinMode(BOARD_LED_PIN, OUTPUT);
+
+ /* Set up Serial2 for use as a debug channel */
+ Serial2.begin(9600);
+ Serial2.println("This is the debug channel. Press any key.");
+ while (!Serial2.available())
+ ;
+ Serial2.read();
+}
+
+uint8 c1 = '-';
+
+void loop() {
+ toggleLED();
+ delay(1000);
+
+ if (Serial2.available()) {
+ Serial2.read();
+ state++;
+ }
+
+ switch (state) {
+ case QUICKPRINT:
+ for (int i = 0; i < 30; i++) {
+ usb_cdcacm_putc((char)c1, 1);
+ SerialUSB.print('.');
+ SerialUSB.print('|');
+ }
+ Serial2.println(SerialUSB.pending(), DEC);
+ SerialUSB.println();
+ break;
+ case BIGSTUFF:
+ SerialUSB.println("0123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789"
+ "012345678901234567890");
+ SerialUSB.println((int64)123456789, DEC);
+ SerialUSB.println(3.1415926535);
+ Serial2.println(SerialUSB.pending(), DEC);
+ break;
+ case NUMBERS:
+ SerialUSB.println("Numbers! -----------------------------");
+ Serial2.println("Numbers! -----------------------------");
+ SerialUSB.println('1');
+ Serial2.println('1');
+ SerialUSB.println(1, DEC);
+ Serial2.println(1, DEC);
+ SerialUSB.println(-1, DEC);
+ Serial2.println(-1, DEC);
+ SerialUSB.println(3.14159265);
+ Serial2.println(3.14159265);
+ SerialUSB.println(123456789, DEC);
+ Serial2.println(123456789, DEC);
+ SerialUSB.println(-123456789, DEC);
+ Serial2.println(-123456789, DEC);
+ SerialUSB.println(65535, HEX);
+ Serial2.println(65535, HEX);
+ break;
+ case SIMPLE:
+ Serial2.println("Trying write('a')");
+ SerialUSB.write('a');
+ Serial2.println("Trying write(\"b\")");
+ SerialUSB.write("b");
+ Serial2.println("Trying print('c')");
+ SerialUSB.print('c');
+ Serial2.println("Trying print(\"d\")");
+ SerialUSB.print("d");
+ Serial2.println("Trying print(\"efg\")");
+ SerialUSB.print("efg");
+ Serial2.println("Trying println(\"hij\\n\\r\")");
+ SerialUSB.print("hij\n\r");
+ SerialUSB.write(' ');
+ SerialUSB.println();
+ Serial2.println("Trying println(123456789, DEC)");
+ SerialUSB.println(123456789, DEC);
+ Serial2.println("Trying println(3.141592)");
+ SerialUSB.println(3.141592);
+ Serial2.println("Trying println(\"DONE\")");
+ SerialUSB.println("DONE");
+ break;
+ case ONOFF:
+ Serial2.println("Shutting down...");
+ SerialUSB.println("Shutting down...");
+ SerialUSB.end();
+ Serial2.println("Waiting 4 seconds...");
+ delay(4000);
+ Serial2.println("Starting up...");
+ SerialUSB.begin();
+ SerialUSB.println("Hello World!");
+ Serial2.println("Waiting 4 seconds...");
+ delay(4000);
+ state++;
+ break;
+ default:
+ state = 0;
+ }
+}
+
+// Force init to be called *first*, i.e. before static object allocation.
+// Otherwise, statically allocated objects that need libmaple may fail.
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (true) {
+ loop();
+ }
+ return 0;
+}
diff --git a/tests/test-servo.cpp b/tests/test-servo.cpp
new file mode 100644
index 0000000..6f6e3ba
--- /dev/null
+++ b/tests/test-servo.cpp
@@ -0,0 +1,152 @@
+/*
+ * Basic Servo library test program.
+ *
+ * Setup:
+ *
+ * - Connect a potentiometer to POT_PIN (default pin 15)
+ * - Connect an oscilloscope to SERVO_PIN1 (default pin 5) and
+ * SERVO_PIN2 (default pin 6).
+ * - Connect a serial monitor to SerialUSB
+ *
+ * The potentiometer controls the target angle for each of two Servo
+ * objects, one with angles in [-90, 90], and another in [0, 180].
+ * Servo pulse width range is [1000, 2000].
+ *
+ * Serial2 will tell you what inputs it's giving to each servo object,
+ * and some information it gets back. Pressing the button
+ * detaches/reattaches the Servo objects.
+ *
+ * Tests you should perform:
+ *
+ * - Check calculated pulse widths for each servo's target angle
+ * - Check that calculated pulse widths match actual pulse widths
+ * - Check that the period of the pulse train is roughly 20 ms
+ * - Check that the pulses stop when detached, and resume when reattached
+ * - Check that Servo::write() and Servo::read() round-trip properly
+ *
+ * This file is released into the public domain.
+ */
+
+#include <stdio.h>
+
+#include <wirish/wirish.h>
+
+#include "libraries/Servo/Servo.h"
+
+#define POT_PIN 15
+
+#define MIN_PW 1000
+#define MAX_PW 2000
+
+#define SERVO_PIN1 5
+#define MIN_ANGLE1 0
+#define MAX_ANGLE1 180
+
+#define SERVO_PIN2 6
+#define MIN_ANGLE2 (-90)
+#define MAX_ANGLE2 90
+
+Servo servo1;
+Servo servo2;
+
+#define BUF_SIZE 100
+char buf[BUF_SIZE];
+
+#define print_buf(fmt, ...) do { \
+ snprintf(buf, BUF_SIZE, fmt, __VA_ARGS__); \
+ Serial2.println(buf); } while (0)
+
+int averageAnalogReads(int);
+void attach();
+void detach();
+
+void setup() {
+ pinMode(POT_PIN, INPUT_ANALOG);
+ pinMode(BOARD_BUTTON_PIN, INPUT);
+ pinMode(BOARD_LED_PIN, OUTPUT);
+
+ Serial2.begin(9600);
+
+ servo1.attach(SERVO_PIN1, MIN_PW, MAX_PW, MIN_ANGLE1, MAX_ANGLE1);
+ servo2.attach(SERVO_PIN2, MIN_PW, MAX_PW, MIN_ANGLE2, MAX_ANGLE2);
+
+ ASSERT(servo1.attachedPin() == SERVO_PIN1);
+ ASSERT(servo2.attachedPin() == SERVO_PIN2);
+}
+
+void loop() {
+ delay(250);
+ toggleLED();
+
+ if (isButtonPressed()) {
+ if (servo1.attached()) detach();
+ else attach();
+ }
+
+ if (!servo1.attached()) return;
+
+ int32 average = averageAnalogReads(250);
+ int16 angle1 = (int16)map(average, 0, 4095, MIN_ANGLE1, MAX_ANGLE1);
+ int16 angle2 = (int16)map(average, 0, 4095, MIN_ANGLE2, MAX_ANGLE2);
+
+ print_buf("pot reading = %d, angle 1 = %d, angle 2 = %d.",
+ average, angle1, angle2);
+
+ servo1.write(angle1);
+ servo2.write(angle2);
+
+ int16 read1 = servo1.read();
+ int16 read2 = servo2.read();
+
+ print_buf("write/read angle 1: %d/%d, angle 2: %d/%d",
+ angle1, read1, angle2, read2);
+
+ ASSERT(abs(angle1 - read1) <= 1);
+ ASSERT(abs(angle2 - read2) <= 1);
+
+ print_buf("pulse width 1: %d, pulse width 2: %d",
+ servo1.readMicroseconds(), servo2.readMicroseconds());
+
+ Serial2.println("\n--------------------------\n");
+}
+
+int32 averageAnalogReads(int n) {
+ uint64 total = 0;
+
+ for (int i = 0; i < n; i++) {
+ total += analogRead(POT_PIN);
+ }
+
+ return (int32)(total / n);
+}
+
+void attach() {
+ Serial2.println("attaching");
+ servo1.attach(SERVO_PIN1);
+ servo2.attach(SERVO_PIN2);
+ ASSERT(servo1.attachedPin() == SERVO_PIN1);
+ ASSERT(servo2.attachedPin() == SERVO_PIN2);
+}
+
+void detach() {
+ Serial2.println("detaching");
+ servo1.detach();
+ servo2.detach();
+ ASSERT(!servo1.attached());
+ ASSERT(!servo2.attached());
+}
+
+// Force init to be called *first*, i.e. before static object allocation.
+// Otherwise, statically allocated objects that need libmaple may fail.
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (true) {
+ loop();
+ }
+ return 0;
+}
diff --git a/tests/test-session.cpp b/tests/test-session.cpp
new file mode 100644
index 0000000..2cc000f
--- /dev/null
+++ b/tests/test-session.cpp
@@ -0,0 +1,942 @@
+// Interactive Test Session for LeafLabs Maple
+// Copyright (c) 2010 LeafLabs LLC.
+//
+// Useful for testing Maple features and troubleshooting.
+// Communicates over SerialUSB.
+
+#include <string.h>
+
+#include <wirish/wirish.h>
+
+// ASCII escape character
+#define ESC ((uint8)27)
+
+// Default USART baud rate
+#define BAUD 9600
+
+// Number of times to sample a pin per ADC noise measurement
+#define N_ADC_NOISE_MEASUREMENTS 40
+
+uint8 gpio_state[BOARD_NR_GPIO_PINS];
+
+const char* dummy_data = ("123456789012345678901234567890\r\n"
+ "123456789012345678901234567890\r\n");
+
+// Commands
+void cmd_print_help(void);
+void cmd_adc_stats(void);
+void cmd_stressful_adc_stats(void);
+void cmd_everything(void);
+void cmd_serial1_serial3(void);
+void cmd_serial1_echo(void);
+void cmd_gpio_monitoring(void);
+void cmd_sequential_adc_reads(void);
+void cmd_gpio_qa(void);
+void cmd_sequential_gpio_toggling(void);
+void cmd_gpio_toggling(void);
+void cmd_sequential_debug_gpio_toggling(void);
+void cmd_debug_gpio_toggling(void);
+void cmd_but_test(void);
+void cmd_sequential_pwm_test(void);
+void cmd_servo_sweep(void);
+void cmd_board_info(void);
+
+// Helper functions
+void measure_adc_noise(uint8 pin);
+void fast_gpio(int pin);
+void serial_baud_test(HardwareSerial **serials, int n, unsigned baud);
+void serial_echo_test(HardwareSerial *serial, unsigned baud);
+void init_all_timers(uint16 prescale);
+void enable_usarts(void);
+void disable_usarts(void);
+void print_board_array(const char* msg, const uint8 arr[], int len);
+
+// -- setup() and loop() ------------------------------------------------------
+
+void setup() {
+ // Set up the LED to blink
+ pinMode(BOARD_LED_PIN, OUTPUT);
+
+ // Start up the serial ports
+ Serial1.begin(BAUD);
+ Serial2.begin(BAUD);
+ Serial3.begin(BAUD);
+
+ // Send a message out over SerialUSB interface
+ SerialUSB.println(" ");
+ SerialUSB.println(" __ __ _ _");
+ SerialUSB.println(" | \\/ | __ _ _ __ | | ___| |");
+ SerialUSB.println(" | |\\/| |/ _` | '_ \\| |/ _ \\ |");
+ SerialUSB.println(" | | | | (_| | |_) | | __/_|");
+ SerialUSB.println(" |_| |_|\\__,_| .__/|_|\\___(_)");
+ SerialUSB.println(" |_|");
+ SerialUSB.println(" by leaflabs");
+ SerialUSB.println("");
+ SerialUSB.println("");
+ SerialUSB.println("Maple interactive test program (type '?' for help)");
+ SerialUSB.println("-------------------------------------------------------"
+ "---");
+ SerialUSB.print("> ");
+
+}
+
+void loop () {
+ toggleLED();
+ delay(250);
+
+ while (SerialUSB.available()) {
+ uint8 input = SerialUSB.read();
+ SerialUSB.println((char)input);
+
+ switch(input) {
+ case '\r':
+ break;
+
+ case ' ':
+ SerialUSB.println("spacebar, nice!");
+ break;
+
+ case '?':
+ case 'h':
+ cmd_print_help();
+ break;
+
+ case 'u':
+ SerialUSB.println("Hello World!");
+ break;
+
+ case 'w':
+ Serial1.println("Hello World!");
+ Serial2.println("Hello World!");
+ Serial3.println("Hello World!");
+ break;
+
+ case 'm':
+ cmd_serial1_serial3();
+ break;
+
+ case 'E':
+ cmd_serial1_echo();
+ break;
+
+ case '.':
+ while (!SerialUSB.available()) {
+ Serial1.print(".");
+ Serial2.print(".");
+ Serial3.print(".");
+ SerialUSB.print(".");
+ }
+ break;
+
+ case 'd':
+ SerialUSB.println("Disabling USB. Press BUT to re-enable.");
+ SerialUSB.end();
+ pinMode(BOARD_BUTTON_PIN, INPUT);
+ while (!isButtonPressed())
+ ;
+ SerialUSB.begin();
+ break;
+
+ case 'n':
+ cmd_adc_stats();
+ break;
+
+ case 'N':
+ cmd_stressful_adc_stats();
+ break;
+
+ case 'e':
+ cmd_everything();
+ break;
+
+ case 'W':
+ while (!SerialUSB.available()) {
+ Serial1.print(dummy_data);
+ Serial2.print(dummy_data);
+ Serial3.print(dummy_data);
+ }
+ break;
+
+ case 'U': {
+ SerialUSB.println("Dumping data to USB. Press any key.");
+ int nprints = 0;
+ int start = millis();
+ while (!SerialUSB.available()) {
+ SerialUSB.print(dummy_data);
+ nprints++;
+ }
+ int elapsed = millis() - start;
+ SerialUSB.read(); // consume available character
+ size_t nbytes = nprints * strlen(dummy_data);
+ SerialUSB.println();
+ SerialUSB.print("Sent ");
+ SerialUSB.print(nbytes);
+ SerialUSB.print(" bytes (");
+ SerialUSB.print((nbytes / (double)elapsed) * (1000.0 / 1024.0));
+ SerialUSB.println(" kB/sec)");
+ }
+ break;
+
+ case 'g':
+ cmd_sequential_gpio_toggling();
+ break;
+
+ case 'G':
+ cmd_gpio_toggling();
+ break;
+
+ case 'j':
+ cmd_sequential_debug_gpio_toggling();
+ break;
+
+ case 'J':
+ cmd_debug_gpio_toggling();
+ break;
+
+ case 'B':
+ cmd_but_test();
+ break;
+
+ case 'f':
+ SerialUSB.println("Wiggling D4 as fast as possible in bursts. "
+ "Press any key.");
+ pinMode(4, OUTPUT);
+ while (!SerialUSB.available()) {
+ fast_gpio(4);
+ delay(1);
+ }
+ break;
+
+ case 'p':
+ cmd_sequential_pwm_test();
+ break;
+
+ case '_':
+ SerialUSB.println("Delaying for 5 seconds...");
+ delay(5000);
+ break;
+
+ // Be sure to update cmd_print_help() if you implement these:
+
+ case 't': // TODO
+ SerialUSB.println("Unimplemented.");
+ break;
+
+ case 'T': // TODO
+ SerialUSB.println("Unimplemented.");
+ break;
+
+ case 's':
+ cmd_servo_sweep();
+ break;
+
+ // Be sure to update cmd_print_help() if you implement these:
+
+ case 'i': // TODO
+ SerialUSB.println("Unimplemented.");
+ break;
+
+ case 'I': // TODO
+ SerialUSB.println("Unimplemented.");
+ break;
+
+ case 'r':
+ cmd_gpio_monitoring();
+ break;
+
+ case 'a':
+ cmd_sequential_adc_reads();
+ break;
+
+ case 'b':
+ cmd_board_info();
+ break;
+
+ case '+':
+ cmd_gpio_qa();
+ break;
+
+ default: // -------------------------------
+ SerialUSB.print("Unexpected byte: 0x");
+ SerialUSB.print((int)input, HEX);
+ SerialUSB.println(", press h for help.");
+ }
+
+ SerialUSB.print("> ");
+ }
+}
+
+// -- Commands ----------------------------------------------------------------
+
+void cmd_print_help(void) {
+ SerialUSB.println("");
+ SerialUSB.println("Command Listing");
+ SerialUSB.println("\t?: print this menu");
+ SerialUSB.println("\td: Disable SerialUSB (press button to re-enable)");
+ SerialUSB.println("\th: print this menu");
+ SerialUSB.println("\tw: print Hello World on all 3 USARTS");
+ SerialUSB.println("\tn: measure noise and do statistics");
+ SerialUSB.println("\tN: measure noise and do statistics with background "
+ "stuff");
+ SerialUSB.println("\ta: show realtime ADC info");
+ SerialUSB.println("\t.: echo '.' until new input");
+ SerialUSB.println("\tu: print Hello World on USB");
+ SerialUSB.println("\t_: do as little as possible for a couple seconds "
+ "(delay)");
+ SerialUSB.println("\tp: test all PWM channels sequentially");
+ SerialUSB.println("\tW: dump data as fast as possible on all 3 USARTS");
+ SerialUSB.println("\tU: dump data as fast as possible over USB"
+ " and measure data rate");
+ SerialUSB.println("\tg: toggle GPIOs sequentially");
+ SerialUSB.println("\tG: toggle GPIOs at the same time");
+ SerialUSB.println("\tj: toggle debug port GPIOs sequentially");
+ SerialUSB.println("\tJ: toggle debug port GPIOs simultaneously");
+ SerialUSB.println("\tB: test the built-in button");
+ SerialUSB.println("\tf: toggle pin 4 as fast as possible in bursts");
+ SerialUSB.println("\tr: monitor and print GPIO status changes");
+ SerialUSB.println("\ts: output a sweeping servo PWM on all PWM channels");
+ SerialUSB.println("\tm: output data on USART1 and USART3 at various "
+ "baud rates");
+ SerialUSB.println("\tE: echo data on USART1 at various baud rates");
+ SerialUSB.println("\tb: print information about the board.");
+ SerialUSB.println("\t+: test shield mode (for quality assurance testing)");
+
+ SerialUSB.println("Unimplemented:");
+ SerialUSB.println("\te: do everything all at once until new input");
+ SerialUSB.println("\tt: output a 1khz squarewave on all GPIOs");
+ SerialUSB.println("\tT: output a 1hz squarewave on all GPIOs");
+ SerialUSB.println("\ti: print out a bunch of info about system state");
+ SerialUSB.println("\tI: print out status of all headers");
+}
+
+void cmd_adc_stats(void) {
+ SerialUSB.println("Taking ADC noise stats. Press ESC to stop, "
+ "'R' to repeat same pin, anything else for next pin.");
+
+ uint32 i = 0;
+ while (i < BOARD_NR_ADC_PINS) {
+ measure_adc_noise(boardADCPins[i]);
+
+ SerialUSB.println("----------");
+ uint8 c = SerialUSB.read();
+ if (c == ESC) {
+ break;
+ } else if (c != 'r' && c != 'R') {
+ i++;
+ }
+ }
+}
+
+void cmd_stressful_adc_stats(void) {
+ SerialUSB.println("Taking ADC noise stats under duress. Press ESC to "
+ "stop, 'R' to repeat same pin, anything else for next "
+ "pin.");
+
+ uint32 i = 0;
+ while (i < BOARD_NR_ADC_PINS) {
+ // use PWM to create digital noise
+ for (uint32 j = 0; j < BOARD_NR_PWM_PINS; j++) {
+ if (boardADCPins[i] != boardPWMPins[j]) {
+ pinMode(boardPWMPins[j], PWM);
+ pwmWrite(boardPWMPins[j], 1000 + i);
+ }
+ }
+
+ measure_adc_noise(boardADCPins[i]);
+
+ // turn off the noise
+ for (uint32 j = 0; j < BOARD_NR_PWM_PINS; j++) {
+ if (boardADCPins[i] != boardPWMPins[j]) {
+ pinMode(boardPWMPins[j], OUTPUT);
+ digitalWrite(boardPWMPins[j], LOW);
+ }
+ }
+
+ SerialUSB.println("----------");
+ uint8 c = SerialUSB.read();
+ if (c == ESC) {
+ break;
+ } else if (c != 'r' && c != 'R') {
+ i++;
+ }
+ }
+}
+
+void cmd_everything(void) { // TODO
+ // Be sure to update cmd_print_help() if you implement this.
+
+ // print to usart
+ // print to usb
+ // toggle gpios
+ // enable pwm
+ SerialUSB.println("Unimplemented.");
+}
+
+void cmd_serial1_serial3(void) {
+ HardwareSerial *serial_1_and_3[] = {&Serial1, &Serial3};
+
+ SerialUSB.println("Testing 57600 baud on USART1 and USART3. "
+ "Press any key to stop.");
+ serial_baud_test(serial_1_and_3, 2, 57600);
+ SerialUSB.read();
+
+ SerialUSB.println("Testing 115200 baud on USART1 and USART3. "
+ "Press any key to stop.");
+ serial_baud_test(serial_1_and_3, 2, 115200);
+ SerialUSB.read();
+
+ SerialUSB.println("Testing 9600 baud on USART1 and USART3. "
+ "Press any key to stop.");
+ serial_baud_test(serial_1_and_3, 2, 9600);
+ SerialUSB.read();
+
+ SerialUSB.println("Resetting USART1 and USART3...");
+ Serial1.begin(BAUD);
+ Serial3.begin(BAUD);
+}
+
+void cmd_serial1_echo(void) {
+ SerialUSB.println("Testing serial echo at various baud rates. "
+ "Press any key for next baud rate, or ESC to quit "
+ "early.");
+ while (!SerialUSB.available())
+ ;
+
+ if (SerialUSB.read() == ESC) return;
+ SerialUSB.println("Testing 115200 baud on USART1.");
+ serial_echo_test(&Serial1, 115200);
+
+ if (SerialUSB.read() == ESC) return;
+ SerialUSB.println("Testing 57600 baud on USART1.");
+ serial_echo_test(&Serial1, 57600);
+
+ if (SerialUSB.read() == ESC) return;
+ SerialUSB.println("Testing 9600 baud on USART1.");
+ serial_echo_test(&Serial1, 9600);
+}
+
+void cmd_gpio_monitoring(void) {
+ SerialUSB.println("Monitoring pin state changes. Press any key to stop.");
+
+ for (int i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+ pinMode(i, INPUT_PULLDOWN);
+ gpio_state[i] = (uint8)digitalRead(i);
+ }
+
+ while (!SerialUSB.available()) {
+ for (int i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+
+ uint8 current_state = (uint8)digitalRead(i);
+ if (current_state != gpio_state[i]) {
+ SerialUSB.print("State change on pin ");
+ SerialUSB.print(i, DEC);
+ if (current_state) {
+ SerialUSB.println(":\tHIGH");
+ } else {
+ SerialUSB.println(":\tLOW");
+ }
+ gpio_state[i] = current_state;
+ }
+ }
+ }
+
+ for (int i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+ pinMode(i, OUTPUT);
+ }
+}
+
+void cmd_sequential_adc_reads(void) {
+ SerialUSB.print("Sequentially reading most ADC ports.");
+ SerialUSB.println("Press any key for next port, or ESC to stop.");
+
+ for (uint32 i = 0; i < BOARD_NR_ADC_PINS; i++) {
+ if (boardUsesPin(boardADCPins[i]))
+ continue;
+
+ SerialUSB.print("Reading pin ");
+ SerialUSB.print(boardADCPins[i], DEC);
+ SerialUSB.println("...");
+ pinMode(boardADCPins[i], INPUT_ANALOG);
+ while (!SerialUSB.available()) {
+ int sample = analogRead(boardADCPins[i]);
+ SerialUSB.print(boardADCPins[i], DEC);
+ SerialUSB.print("\t");
+ SerialUSB.print(sample, DEC);
+ SerialUSB.print("\t");
+ SerialUSB.print("|");
+ for (int j = 0; j < 4096; j += 100) {
+ if (sample >= j) {
+ SerialUSB.print("#");
+ } else {
+ SerialUSB.print(" ");
+ }
+ }
+ SerialUSB.print("| ");
+ for (int j = 0; j < 12; j++) {
+ if (sample & (1 << (11 - j))) {
+ SerialUSB.print("1");
+ } else {
+ SerialUSB.print("0");
+ }
+ }
+ SerialUSB.println();
+ }
+ pinMode(boardADCPins[i], OUTPUT);
+ digitalWrite(boardADCPins[i], 0);
+ if (SerialUSB.read() == ESC)
+ break;
+ }
+}
+
+bool test_single_pin_is_high(int high_pin, const char* err_msg) {
+ bool ok = true;
+ for (int i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i)) continue;
+
+ if (digitalRead(i) == HIGH && i != high_pin) {
+ SerialUSB.println();
+ SerialUSB.print("\t*** FAILURE! pin ");
+ SerialUSB.print(i, DEC);
+ SerialUSB.print(' ');
+ SerialUSB.println(err_msg);
+ ok = false;
+ }
+ }
+ return ok;
+}
+
+bool wait_for_low_transition(uint8 pin) {
+ uint32 start = millis();
+ while (millis() - start < 2000) {
+ if (digitalRead(pin) == LOW) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void cmd_gpio_qa(void) {
+ bool all_pins_ok = true;
+ const int not_a_pin = -1;
+ SerialUSB.println("Doing QA testing for unused GPIO pins.");
+
+ for (int i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i)) continue;
+
+ pinMode(i, INPUT);
+ }
+
+ SerialUSB.println("Waiting to start.");
+ ASSERT(!boardUsesPin(0));
+ while (digitalRead(0) == LOW) continue;
+
+ for (int i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i)) {
+ SerialUSB.print("Skipping pin ");
+ SerialUSB.println(i, DEC);
+ continue;
+ }
+ bool pin_ok = true;
+ SerialUSB.print("Checking pin ");
+ SerialUSB.print(i, DEC);
+ while (digitalRead(i) == LOW) continue;
+
+ pin_ok = pin_ok && test_single_pin_is_high(i, "is also HIGH");
+
+ if (!wait_for_low_transition(i)) {
+ SerialUSB.println("Transition to low timed out; something is "
+ "very wrong. Aborting test.");
+ return;
+ }
+
+ pin_ok = pin_ok && test_single_pin_is_high(not_a_pin, "is still HIGH");
+
+ if (pin_ok) {
+ SerialUSB.println(": ok");
+ }
+
+ all_pins_ok = all_pins_ok && pin_ok;
+ }
+
+ if (all_pins_ok) {
+ SerialUSB.println("Finished; test passes.");
+ } else {
+ SerialUSB.println("**** TEST FAILS *****");
+ }
+
+ for (int i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i)) continue;
+
+ pinMode(i, OUTPUT);
+ digitalWrite(i, LOW);
+ gpio_state[i] = 0;
+ }
+}
+
+void cmd_sequential_gpio_toggling(void) {
+ SerialUSB.println("Sequentially toggling all unused pins. "
+ "Press any key for next pin, ESC to stop.");
+
+ for (uint32 i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+
+ SerialUSB.print("Toggling pin ");
+ SerialUSB.print((int)i, DEC);
+ SerialUSB.println("...");
+
+ pinMode(i, OUTPUT);
+ do {
+ togglePin(i);
+ } while (!SerialUSB.available());
+
+ digitalWrite(i, LOW);
+ if (SerialUSB.read() == ESC)
+ break;
+ }
+}
+
+void cmd_gpio_toggling(void) {
+ SerialUSB.println("Toggling all unused pins simultaneously. "
+ "Press any key to stop.");
+
+ for (uint32 i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+ pinMode(i, OUTPUT);
+ }
+
+ while (!SerialUSB.available()) {
+ for (uint32 i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+ togglePin(i);
+ }
+ }
+
+ for (uint32 i = 0; i < BOARD_NR_GPIO_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+ digitalWrite(i, LOW);
+ }
+}
+
+uint8 debugGPIOPins[] = {BOARD_JTMS_SWDIO_PIN,
+ BOARD_JTCK_SWCLK_PIN,
+ BOARD_JTDI_PIN,
+ BOARD_JTDO_PIN,
+ BOARD_NJTRST_PIN};
+
+#define N_DEBUG_PINS 5
+
+void cmd_sequential_debug_gpio_toggling(void) {
+ SerialUSB.println("Toggling all debug (JTAG/SWD) pins sequentially. "
+ "This will permanently disable debug port "
+ "functionality.");
+ disableDebugPorts();
+
+ for (int i = 0; i < N_DEBUG_PINS; i++) {
+ pinMode(debugGPIOPins[i], OUTPUT);
+ }
+
+ for (int i = 0; i < N_DEBUG_PINS; i++) {
+ int pin = debugGPIOPins[i];
+ SerialUSB.print("Toggling pin ");
+ SerialUSB.print(pin, DEC);
+ SerialUSB.println("...");
+
+ pinMode(pin, OUTPUT);
+ do {
+ togglePin(pin);
+ } while (!SerialUSB.available());
+
+ digitalWrite(pin, LOW);
+ if (SerialUSB.read() == ESC)
+ break;
+ }
+
+ for (int i = 0; i < N_DEBUG_PINS; i++) {
+ digitalWrite(debugGPIOPins[i], 0);
+ }
+}
+
+void cmd_debug_gpio_toggling(void) {
+ SerialUSB.println("Toggling debug GPIO simultaneously. "
+ "This will permanently disable JTAG and Serial Wire "
+ "debug port functionality. "
+ "Press any key to stop.");
+ disableDebugPorts();
+
+ for (uint32 i = 0; i < N_DEBUG_PINS; i++) {
+ pinMode(debugGPIOPins[i], OUTPUT);
+ }
+
+ while (!SerialUSB.available()) {
+ for (uint32 i = 0; i < N_DEBUG_PINS; i++) {
+ togglePin(debugGPIOPins[i]);
+ }
+ }
+
+ for (uint32 i = 0; i < N_DEBUG_PINS; i++) {
+ digitalWrite(debugGPIOPins[i], LOW);
+ }
+}
+
+void cmd_but_test(void) {
+ SerialUSB.println("Press the button to test. Press any key to stop.");
+ pinMode(BOARD_BUTTON_PIN, INPUT);
+
+ while (!SerialUSB.available()) {
+ if (isButtonPressed()) {
+ uint32 tstamp = millis();
+ SerialUSB.print("Button press detected, timestamp: ");
+ SerialUSB.println(tstamp);
+ }
+ }
+ SerialUSB.read();
+}
+
+void cmd_sequential_pwm_test(void) {
+ SerialUSB.println("Sequentially testing PWM on all unused pins. "
+ "Press any key for next pin, ESC to stop.");
+
+ for (uint32 i = 0; i < BOARD_NR_PWM_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+
+ SerialUSB.print("PWM out on header D");
+ SerialUSB.print(boardPWMPins[i], DEC);
+ SerialUSB.println("...");
+ pinMode(boardPWMPins[i], PWM);
+ pwmWrite(boardPWMPins[i], 16000);
+
+ while (!SerialUSB.available()) {
+ delay(10);
+ }
+
+ pinMode(boardPWMPins[i], OUTPUT);
+ digitalWrite(boardPWMPins[i], 0);
+ if (SerialUSB.read() == ESC)
+ break;
+ }
+}
+
+void cmd_servo_sweep(void) {
+ SerialUSB.println("Testing all PWM headers with a servo sweep. "
+ "Press any key to stop.");
+ SerialUSB.println();
+
+ disable_usarts();
+ init_all_timers(21);
+
+ for (uint32 i = 0; i < BOARD_NR_PWM_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+ pinMode(boardPWMPins[i], PWM);
+ pwmWrite(boardPWMPins[i], 4000);
+ }
+
+ // 1.25ms = 4096counts = 0deg
+ // 1.50ms = 4915counts = 90deg
+ // 1.75ms = 5734counts = 180deg
+ int rate = 4096;
+ while (!SerialUSB.available()) {
+ rate += 20;
+ if (rate > 5734)
+ rate = 4096;
+ for (uint32 i = 0; i < BOARD_NR_PWM_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+ pwmWrite(boardPWMPins[i], rate);
+ }
+ delay(20);
+ }
+
+ for (uint32 i = 0; i < BOARD_NR_PWM_PINS; i++) {
+ if (boardUsesPin(i))
+ continue;
+ pinMode(boardPWMPins[i], OUTPUT);
+ }
+ init_all_timers(1);
+ enable_usarts();
+}
+
+void cmd_board_info(void) { // TODO print more information
+ SerialUSB.println("Board information");
+ SerialUSB.println("=================");
+
+ SerialUSB.print("* Clock speed (MHz): ");
+ SerialUSB.println(CYCLES_PER_MICROSECOND);
+
+ SerialUSB.print("* BOARD_LED_PIN: ");
+ SerialUSB.println(BOARD_LED_PIN);
+
+ SerialUSB.print("* BOARD_BUTTON_PIN: ");
+ SerialUSB.println(BOARD_BUTTON_PIN);
+
+ SerialUSB.print("* GPIO information (BOARD_NR_GPIO_PINS = ");
+ SerialUSB.print(BOARD_NR_GPIO_PINS);
+ SerialUSB.println("):");
+ print_board_array("ADC pins", boardADCPins, BOARD_NR_ADC_PINS);
+ print_board_array("PWM pins", boardPWMPins, BOARD_NR_PWM_PINS);
+ print_board_array("Used pins", boardUsedPins, BOARD_NR_USED_PINS);
+}
+
+// -- Helper functions --------------------------------------------------------
+
+void measure_adc_noise(uint8 pin) {
+ const int N = 1000;
+ uint16 x;
+ float mean = 0;
+ float delta = 0;
+ float M2 = 0;
+ pinMode(pin, INPUT_ANALOG);
+
+ // Variance algorithm from Welford, via Knuth, by way of Wikipedia:
+ // http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#On-line_algorithm
+ for (int sample = 0; sample < N_ADC_NOISE_MEASUREMENTS; sample++) {
+ for (int i = 1; i <= N; i++) {
+ x = analogRead(pin);
+ delta = x - mean;
+ mean += delta / i;
+ M2 = M2 + delta * (x - mean);
+ }
+ SerialUSB.print("header: D");
+ SerialUSB.print(pin, DEC);
+ SerialUSB.print("\tn: ");
+ SerialUSB.print(N, DEC);
+ SerialUSB.print("\tmean: ");
+ SerialUSB.print(mean);
+ SerialUSB.print("\tvariance: ");
+ SerialUSB.println(M2 / (float)(N-1));
+ }
+
+ pinMode(pin, OUTPUT);
+}
+
+void fast_gpio(int maple_pin) {
+ gpio_dev *dev = PIN_MAP[maple_pin].gpio_device;
+ uint32 bit = PIN_MAP[maple_pin].gpio_bit;
+
+ gpio_write_bit(dev, bit, 1);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+ gpio_toggle_bit(dev, bit);
+}
+
+void serial_baud_test(HardwareSerial **serials, int n, unsigned baud) {
+ for (int i = 0; i < n; i++) {
+ serials[i]->begin(baud);
+ }
+ while (!SerialUSB.available()) {
+ for (int i = 0; i < n; i++) {
+ serials[i]->println(dummy_data);
+ if (serials[i]->available()) {
+ serials[i]->println(serials[i]->read());
+ delay(1000);
+ }
+ }
+ }
+}
+
+void serial_echo_test(HardwareSerial *serial, unsigned baud) {
+ serial->begin(baud);
+ while (!SerialUSB.available()) {
+ if (!serial->available())
+ continue;
+ serial->print(serial->read());
+ }
+}
+
+static uint16 init_all_timers_prescale = 0;
+
+static void set_prescale(timer_dev *dev) {
+ timer_set_prescaler(dev, init_all_timers_prescale);
+}
+
+void init_all_timers(uint16 prescale) {
+ init_all_timers_prescale = prescale;
+ timer_foreach(set_prescale);
+}
+
+void enable_usarts(void) {
+ Serial1.begin(BAUD);
+ Serial2.begin(BAUD);
+ Serial3.begin(BAUD);
+#if defined(BOARD_HAVE_USART4)
+ Serial4.begin(BAUD);
+#endif
+#if defined(BOARD_HAVE_USART5)
+ Serial5.begin(BAUD);
+#endif
+}
+
+void disable_usarts(void) {
+ Serial1.end();
+ Serial2.end();
+ Serial3.end();
+#if defined(BOARD_HAVE_USART4)
+ Serial4.end();
+#endif
+#if defined(BOARD_HAVE_USART5)
+ Serial5.end();
+#endif
+}
+
+void print_board_array(const char* msg, const uint8 arr[], int len) {
+ SerialUSB.print("\t");
+ SerialUSB.print(msg);
+ SerialUSB.print(" (");
+ SerialUSB.print(len);
+ SerialUSB.print("): ");
+ for (int i = 0; i < len; i++) {
+ SerialUSB.print(arr[i], DEC);
+ if (i < len - 1) SerialUSB.print(", ");
+ }
+ SerialUSB.println();
+}
+
+// -- premain() and main() ----------------------------------------------------
+
+// Force init to be called *first*, i.e. before static object allocation.
+// Otherwise, statically allocated objects that need libmaple may fail.
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (1) {
+ loop();
+ }
+ return 0;
+}
diff --git a/tests/test-spi-roundtrip.cpp b/tests/test-spi-roundtrip.cpp
new file mode 100644
index 0000000..ddc9875
--- /dev/null
+++ b/tests/test-spi-roundtrip.cpp
@@ -0,0 +1,192 @@
+/*
+ * Polling SPI loopback test.
+ *
+ * Bob is nowhere to be found, so Alice decides to talk to herself.
+ *
+ * Instructions: Connect SPI2 (Alice) to herself (i.e., MISO to MOSI).
+ * Connect to Alice via SerialUSB. Press any key to start.
+ *
+ * Alice will talk to herself for a little while. The sketch will
+ * report if Alice can't hear anything she says. She'll then start
+ * talking forever at various frequencies, bit orders, and modes. Use
+ * an oscilloscope to make sure she's not trying to lie about any of
+ * those things.
+ *
+ * This file is released into the public domain.
+ *
+ * Author: Marti Bolivar <mbolivar@leaflabs.com>
+ */
+
+#include <wirish/wirish.h>
+
+HardwareSPI alice(2);
+
+#define NFREQS 8
+const SPIFrequency spi_freqs[] = {
+ SPI_140_625KHZ,
+ SPI_281_250KHZ,
+ SPI_562_500KHZ,
+ SPI_1_125MHZ,
+ SPI_2_25MHZ,
+ SPI_4_5MHZ,
+ SPI_9MHZ,
+ SPI_18MHZ,
+};
+
+#define TEST_BUF_SIZE 10
+uint8 test_buf[TEST_BUF_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+void bad_assert(const char* file, int line, const char* exp) {
+ SerialUSB.println();
+ SerialUSB.print("ERROR: FAILED ASSERT(");
+ SerialUSB.print(exp);
+ SerialUSB.print("): ");
+ SerialUSB.print(file);
+ SerialUSB.print(": ");
+ SerialUSB.println(line);
+ throb();
+}
+
+#undef ASSERT
+#define ASSERT(exp) \
+ if (exp) { \
+ } else { \
+ bad_assert(__FILE__, __LINE__, #exp); \
+ }
+
+void haveConversation(uint32 bitOrder);
+void soliloquies(uint32 bitOrder);
+
+void setup() {
+ pinMode(BOARD_LED_PIN, OUTPUT);
+ SerialUSB.read();
+}
+
+void loop() {
+ SerialUSB.println("** Having a conversation, MSB first");
+ haveConversation(MSBFIRST);
+
+ SerialUSB.println("** Having a conversation, LSB first");
+ haveConversation(LSBFIRST);
+
+ SerialUSB.println();
+ SerialUSB.println("*** All done! It looks like everything worked.");
+ SerialUSB.println();
+
+ SerialUSB.println("** Alice will now wax eloquent in various styles. "
+ "Press any key for the next configuration.");
+ soliloquies(MSBFIRST);
+ soliloquies(LSBFIRST);
+
+ while (true)
+ ;
+}
+
+void printFrequencyString(SPIFrequency frequency);
+void chat(SPIFrequency frequency, uint32 bitOrder, uint32 mode);
+
+void haveConversation(uint32 bitOrder) {
+ for (int f = 0; f < NFREQS; f++) {
+ for (int mode = 0; mode < 4; mode++) {
+ chat(spi_freqs[f], bitOrder, mode);
+ delay(10);
+ }
+ }
+}
+
+void chat(SPIFrequency frequency, uint32 bitOrder, uint32 mode) {
+ SerialUSB.print("Having a chat.\tFrequency: ");
+ printFrequencyString(frequency);
+ SerialUSB.print(",\tbitOrder: ");
+ SerialUSB.print(bitOrder == MSBFIRST ? "MSB" : "LSB");
+ SerialUSB.print(",\tmode: ");
+ SerialUSB.print(mode);
+ SerialUSB.print(".");
+
+ SerialUSB.print(" [1] ");
+ alice.begin(frequency, bitOrder, mode);
+
+ SerialUSB.print(" [2] ");
+ uint32 txed = 0;
+ while (txed < TEST_BUF_SIZE) {
+ ASSERT(alice.transfer(test_buf[txed]) == test_buf[txed]);
+ txed++;
+ }
+
+ SerialUSB.print(" [3] ");
+ alice.end();
+
+ SerialUSB.println(" ok.");
+}
+
+void soliloquy(SPIFrequency freq, uint32 bitOrder, uint32 mode);
+
+void soliloquies(uint32 bitOrder) {
+ for (int f = 0; f < NFREQS; f++) {
+ for (int mode = 0; mode < 4; mode++) {
+ soliloquy(spi_freqs[f], bitOrder, mode);
+ }
+ }
+}
+
+void soliloquy(SPIFrequency frequency, uint32 bitOrder, uint32 mode) {
+ const uint8 repeat = 0xAE;
+ SerialUSB.print("Alice is giving a soliloquy (repeating 0x");
+ SerialUSB.print(repeat, HEX);
+ SerialUSB.print("). Frequency: ");
+ printFrequencyString(frequency);
+ SerialUSB.print(", bitOrder: ");
+ SerialUSB.print(bitOrder == MSBFIRST ? "big-endian" : "little-endian");
+ SerialUSB.print(", SPI mode: ");
+ SerialUSB.println(mode);
+
+ alice.begin(frequency, bitOrder, mode);
+ while (!SerialUSB.available()) {
+ alice.write(repeat);
+ delayMicroseconds(200);
+ }
+ SerialUSB.read();
+}
+
+void printFrequencyString(SPIFrequency frequency) {
+ switch (frequency) {
+ case SPI_18MHZ:
+ SerialUSB.print("18 MHz");
+ break;
+ case SPI_9MHZ:
+ SerialUSB.print("9 MHz");
+ break;
+ case SPI_4_5MHZ:
+ SerialUSB.print("4.5 MHz");
+ break;
+ case SPI_2_25MHZ:
+ SerialUSB.print("2.25 MHZ");
+ break;
+ case SPI_1_125MHZ:
+ SerialUSB.print("1.125 MHz");
+ break;
+ case SPI_562_500KHZ:
+ SerialUSB.print("562.500 KHz");
+ break;
+ case SPI_281_250KHZ:
+ SerialUSB.print("281.250 KHz");
+ break;
+ case SPI_140_625KHZ:
+ SerialUSB.print("140.625 KHz");
+ break;
+ }
+}
+
+// Force init to be called *first*, i.e. before static object allocation.
+// Otherwise, statically allocated objects that need libmaple may fail.
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+ while (true) {
+ loop();
+ }
+ return 0;
+}
diff --git a/tests/test-systick.cpp b/tests/test-systick.cpp
new file mode 100644
index 0000000..356f302
--- /dev/null
+++ b/tests/test-systick.cpp
@@ -0,0 +1,49 @@
+// Tests the SysTick enable/disable functions
+
+#include <wirish/wirish.h>
+#include <libmaple/systick.h>
+
+void setup() {
+ pinMode(BOARD_LED_PIN, OUTPUT);
+ pinMode(BOARD_BUTTON_PIN, INPUT);
+}
+
+bool disable = true;
+long time = 0;
+
+void loop() {
+ volatile int i = 0;
+ toggleLED();
+
+ // An artificial delay
+ for(i = 0; i < 150000; i++)
+ ;
+
+ if (isButtonPressed()) {
+ if (disable) {
+ systick_disable();
+ SerialUSB.println("Disabling SysTick");
+ } else {
+ SerialUSB.println("Re-enabling SysTick");
+ systick_enable();
+ }
+ disable = !disable;
+ }
+
+ SerialUSB.println(millis());
+}
+
+// Force init to be called *first*, i.e. before static object allocation.
+// Otherwise, statically allocated object that need libmaple may fail.
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (true) {
+ loop();
+ }
+ return 0;
+}
diff --git a/tests/test-timers.cpp b/tests/test-timers.cpp
new file mode 100644
index 0000000..e646916
--- /dev/null
+++ b/tests/test-timers.cpp
@@ -0,0 +1,537 @@
+//
+// 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>
+#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();
+
+ while (true) {
+ loop();
+ }
+ return 0;
+}
diff --git a/tests/test-usart-dma.cpp b/tests/test-usart-dma.cpp
new file mode 100644
index 0000000..7d6d8b9
--- /dev/null
+++ b/tests/test-usart-dma.cpp
@@ -0,0 +1,211 @@
+/**
+ * @file tests/test-usart-dma.cpp
+ * @author Marti Bolivar <mbolivar@leaflabs.com>
+ *
+ * Simple test of DMA used with a USART receiver.
+ *
+ * Configures a USART receiver for use with DMA. Received bytes are
+ * placed into a buffer, with an interrupt firing when the buffer is
+ * full. At that point, the USART transmitter will print the contents
+ * of the byte buffer. The buffer is continually filled and refilled
+ * in this manner.
+ *
+ * This example isn't very robust; don't use it in production. In
+ * particular, since the buffer keeps filling (DMA_CIRC_MODE is set),
+ * if you keep sending characters after filling the buffer, you'll
+ * overwrite earlier bytes; this may happen before those earlier bytes
+ * are done printing. (Typing quickly and seeing how it affects the
+ * output is a fun way to make sense of how the interrupts and the
+ * main thread of execution interleave.)
+ *
+ * This code is released into the public domain.
+ */
+
+#include <libmaple/dma.h>
+#include <libmaple/usart.h>
+#include <libmaple/gpio.h>
+
+#include <wirish/wirish.h>
+
+/*
+ * Configuration and state
+ */
+
+// Serial port and DMA configuration. You can change these to suit
+// your purposes.
+HardwareSerial *serial = &Serial2;
+#define USART_DMA_DEV DMA1
+#if STM32_MCU_SERIES == STM32_SERIES_F1
+// On STM32F1 microcontrollers (like what's on Maple and Maple Mini),
+// dma tubes are channels.
+#define USART_RX_DMA_TUBE DMA_CH6
+#elif (STM32_MCU_SERIES == STM32_SERIES_F2 || \
+ STM32_MCU_SERIES == STM32_SERIES_F4)
+// On STM32F2 and STM32F4 microcontrollers (Maple 2 will have an F4),
+// dma tubes are streams.
+#define USART_RX_DMA_TUBE DMA_S5
+#else
+#error "unsupported stm32 series"
+#endif
+// The serial port will make a DMA request each time it receives data.
+// This is the dma_request_src we use to tell the DMA tube to handle
+// that DMA request.
+#define USART_DMA_REQ_SRC DMA_REQ_SRC_USART2_RX
+#define BAUD 9600
+
+// This will store the DMA configuration for USART RX.
+dma_tube_config tube_config;
+
+// This will store received USART characters.
+#define BUF_SIZE 20
+char rx_buf[BUF_SIZE];
+
+// The interrupt handler, rx_dma_irq(), sets this to 1.
+volatile uint32 irq_fired = 0;
+// Used to store DMA interrupt status register (ISR) bits inside
+// rx_dma_irq(). This helps explain what's going on inside loop(); see
+// comments below.
+volatile uint32 isr = 0;
+
+/*
+ * Helper functions
+ */
+
+// This is our DMA interrupt handler.
+void rx_dma_irq(void) {
+ irq_fired = 1;
+ isr = dma_get_isr_bits(USART_DMA_DEV, USART_RX_DMA_TUBE);
+}
+
+// Configure the USART receiver for use with DMA:
+// 1. Turn it on.
+// 2. Set the "DMA request on RX" bit in USART_CR3 (USART_CR3_DMAR).
+void setup_usart(void) {
+ serial->begin(BAUD);
+ usart_dev *serial_dev = serial->c_dev();
+ serial_dev->regs->CR3 = USART_CR3_DMAR;
+}
+
+// Set up our dma_tube_config structure. (We could have done this
+// above, when we declared tube_config, but having this function makes
+// it easier to explain what's going on).
+void setup_tube_config(void) {
+ // We're receiving from the USART data register. serial->c_dev()
+ // returns a pointer to the libmaple usart_dev for that serial
+ // port, so this is a pointer to its data register.
+ tube_config.tube_src = &serial->c_dev()->regs->DR;
+ // We're only interested in the bottom 8 bits of that data register.
+ tube_config.tube_src_size = DMA_SIZE_8BITS;
+ // We're storing to rx_buf.
+ tube_config.tube_dst = rx_buf;
+ // rx_buf is a char array, and a "char" takes up 8 bits on STM32.
+ tube_config.tube_dst_size = DMA_SIZE_8BITS;
+ // Only fill BUF_SIZE - 1 characters, to leave a null byte at the end.
+ tube_config.tube_nr_xfers = BUF_SIZE - 1;
+ // Flags:
+ // - DMA_CFG_DST_INC so we start at the beginning of rx_buf and
+ // fill towards the end.
+ // - DMA_CFG_CIRC so we go back to the beginning and start over when
+ // rx_buf fills up.
+ // - DMA_CFG_CMPLT_IE to turn on interrupts on transfer completion.
+ tube_config.tube_flags = DMA_CFG_DST_INC | DMA_CFG_CIRC | DMA_CFG_CMPLT_IE;
+ // Target data: none. It's important to set this to NULL if you
+ // don't have any special (microcontroller-specific) configuration
+ // in mind, which we don't.
+ tube_config.target_data = NULL;
+ // DMA request source.
+ tube_config.tube_req_src = USART_DMA_REQ_SRC;
+}
+
+// Configure the DMA controller to serve DMA requests from the USART.
+void setup_dma_xfer(void) {
+ // First, turn it on.
+ dma_init(USART_DMA_DEV);
+ // Next, configure it by calling dma_tube_cfg(), and check to make
+ // sure it succeeded. DMA tubes have many restrictions on their
+ // configuration, and there are configurations which work on some
+ // types of STM32 but not others. libmaple tries hard to make
+ // things just work, but checking the return status is important!
+ int status = dma_tube_cfg(USART_DMA_DEV, USART_RX_DMA_TUBE, &tube_config);
+ ASSERT(status == DMA_TUBE_CFG_SUCCESS);
+ // Now we'll perform any other configuration we want. For this
+ // example, we attach an interrupt handler.
+ dma_attach_interrupt(USART_DMA_DEV, USART_RX_DMA_TUBE, rx_dma_irq);
+ // Turn on the DMA tube. It will now begin serving requests.
+ dma_enable(USART_DMA_DEV, USART_RX_DMA_TUBE);
+}
+
+/*
+ * setup() and loop()
+ */
+
+void setup(void) {
+ pinMode(BOARD_LED_PIN, OUTPUT);
+ setup_tube_config();
+ setup_dma_xfer();
+ setup_usart();
+}
+
+void loop(void) {
+ toggleLED();
+ delay(100);
+
+ // See if the interrupt handler got called since the last time we
+ // checked.
+ if (irq_fired) {
+ serial->println("** IRQ **");
+ // Notice how the interrupt status register (ISR) bits show
+ // transfer complete _and_ half-complete here, but the ISR
+ // bits we print next will be zero. That's because the
+ // variable "isr" gets set _inside_ rx_dma_irq(). After it
+ // exits, libmaple cleans up by clearing the tube's ISR
+ // bits. (If it didn't, and we forgot to, the interrupt would
+ // repeatedly fire forever.)
+ serial->print("ISR bits: 0x");
+ serial->println(isr, HEX);
+ irq_fired = 0;
+ }
+
+ // Print the ISR bits.
+ //
+ // Notice that the "transfer half-complete" ISR flag gets set when
+ // we reach the rx_buf half-way point. This is true even though we
+ // don't tell the DMA controller to interrupt us on a
+ // half-complete transfer. That is, the ISR bits get set at the
+ // right times no matter what; we just don't get interrupted
+ // unless we asked. (If an error or other problem occurs, the
+ // relevant ISR bits will get set in the same way).
+ serial->print("[");
+ serial->print(millis());
+ serial->print("]\tISR bits: 0x");
+ uint8 isr_bits = dma_get_isr_bits(USART_DMA_DEV, USART_RX_DMA_TUBE);
+ serial->print(isr_bits, HEX);
+
+ // Print the contents of rx_buf. If you keep typing after it fills
+ // up, the new characters will overwrite the old ones, thanks to
+ // DMA_CIRC_MODE.
+ serial->print("\tCharacter buffer contents: '");
+ serial->print(rx_buf);
+ serial->println("'");
+ if (isr_bits == 0x7) {
+ serial->println("** Clearing ISR bits.");
+ dma_clear_isr_bits(USART_DMA_DEV, USART_RX_DMA_TUBE);
+ }
+}
+
+// ------- init() and main() --------------------------------------------------
+
+// Force init to be called *first*, i.e. before static object allocation.
+// Otherwise, statically allocated objects that need libmaple may fail.
+__attribute__((constructor)) void premain() {
+ init();
+}
+
+int main(void) {
+ setup();
+
+ while (true) {
+ loop();
+ }
+ return 0;
+}