aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/chibios/serial.c290
-rw-r--r--quantum/stm32/halconf.h4
2 files changed, 292 insertions, 2 deletions
diff --git a/drivers/chibios/serial.c b/drivers/chibios/serial.c
new file mode 100644
index 000000000..26c680653
--- /dev/null
+++ b/drivers/chibios/serial.c
@@ -0,0 +1,290 @@
+/*
+ * WARNING: be careful changing this code, it is very timing dependent
+ */
+
+#include "quantum.h"
+#include "serial.h"
+#include "wait.h"
+
+#include "hal.h"
+
+// TODO: resolve/remove build warnings
+#if defined(RGBLIGHT_ENABLE) && defined(RGBLED_SPLIT) && defined(PROTOCOL_CHIBIOS) && defined(WS2812_DRIVER_BITBANG)
+# warning "RGBLED_SPLIT not supported with bitbang WS2812 driver"
+#endif
+
+// default wait implementation cannot be called within interrupt
+// this method seems to be more accurate than GPT timers
+#if PORT_SUPPORTS_RT == FALSE
+# error "chSysPolledDelayX method not supported on this platform"
+#else
+# undef wait_us
+# define wait_us(x) chSysPolledDelayX(US2RTC(STM32_SYSCLK, x))
+#endif
+
+#ifndef SELECT_SOFT_SERIAL_SPEED
+# define SELECT_SOFT_SERIAL_SPEED 1
+// TODO: correct speeds...
+// 0: about 189kbps (Experimental only)
+// 1: about 137kbps (default)
+// 2: about 75kbps
+// 3: about 39kbps
+// 4: about 26kbps
+// 5: about 20kbps
+#endif
+
+// Serial pulse period in microseconds. At the moment, going lower than 12 causes communication failure
+#if SELECT_SOFT_SERIAL_SPEED == 0
+# define SERIAL_DELAY 12
+#elif SELECT_SOFT_SERIAL_SPEED == 1
+# define SERIAL_DELAY 16
+#elif SELECT_SOFT_SERIAL_SPEED == 2
+# define SERIAL_DELAY 24
+#elif SELECT_SOFT_SERIAL_SPEED == 3
+# define SERIAL_DELAY 32
+#elif SELECT_SOFT_SERIAL_SPEED == 4
+# define SERIAL_DELAY 48
+#elif SELECT_SOFT_SERIAL_SPEED == 5
+# define SERIAL_DELAY 64
+#else
+# error invalid SELECT_SOFT_SERIAL_SPEED value
+#endif
+
+inline static void serial_delay(void) { wait_us(SERIAL_DELAY); }
+inline static void serial_delay_half(void) { wait_us(SERIAL_DELAY / 2); }
+inline static void serial_delay_blip(void) { wait_us(1); }
+inline static void serial_output(void) { setPinOutput(SOFT_SERIAL_PIN); }
+inline static void serial_input(void) { setPinInputHigh(SOFT_SERIAL_PIN); }
+inline static bool serial_read_pin(void) { return !!readPin(SOFT_SERIAL_PIN); }
+inline static void serial_low(void) { writePinLow(SOFT_SERIAL_PIN); }
+inline static void serial_high(void) { writePinHigh(SOFT_SERIAL_PIN); }
+
+void interrupt_handler(void *arg);
+
+// Use thread + palWaitLineTimeout instead of palSetLineCallback
+// - Methods like setPinOutput and palEnableLineEvent/palDisableLineEvent
+// cause the interrupt to lock up, which would limit to only receiving data...
+static THD_WORKING_AREA(waThread1, 128);
+static THD_FUNCTION(Thread1, arg) {
+ (void)arg;
+ chRegSetThreadName("blinker");
+ while (true) {
+ palWaitLineTimeout(SOFT_SERIAL_PIN, TIME_INFINITE);
+ interrupt_handler(NULL);
+ }
+}
+
+static SSTD_t *Transaction_table = NULL;
+static uint8_t Transaction_table_size = 0;
+
+void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size) {
+ Transaction_table = sstd_table;
+ Transaction_table_size = (uint8_t)sstd_table_size;
+
+ serial_output();
+ serial_high();
+}
+
+void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size) {
+ Transaction_table = sstd_table;
+ Transaction_table_size = (uint8_t)sstd_table_size;
+
+ serial_input();
+
+ palEnablePadEvent(PAL_PORT(SOFT_SERIAL_PIN), PAL_PAD(SOFT_SERIAL_PIN), PAL_EVENT_MODE_FALLING_EDGE);
+ chThdCreateStatic(waThread1, sizeof(waThread1), HIGHPRIO, Thread1, NULL);
+}
+
+// Used by the master to synchronize timing with the slave.
+static void __attribute__((noinline)) sync_recv(void) {
+ serial_input();
+ // This shouldn't hang if the slave disconnects because the
+ // serial line will float to high if the slave does disconnect.
+ while (!serial_read_pin()) {
+ }
+
+ serial_delay();
+}
+
+// Used by the slave to send a synchronization signal to the master.
+static void __attribute__((noinline)) sync_send(void) {
+ serial_output();
+
+ serial_low();
+ serial_delay();
+
+ serial_high();
+}
+
+// Reads a byte from the serial line
+static uint8_t __attribute__((noinline)) serial_read_byte(void) {
+ uint8_t byte = 0;
+ serial_input();
+ for (uint8_t i = 0; i < 8; ++i) {
+ byte = (byte << 1) | serial_read_pin();
+ serial_delay();
+ }
+
+ return byte;
+}
+
+// Sends a byte with MSB ordering
+static void __attribute__((noinline)) serial_write_byte(uint8_t data) {
+ uint8_t b = 8;
+ serial_output();
+ while (b--) {
+ if (data & (1 << b)) {
+ serial_high();
+ } else {
+ serial_low();
+ }
+ serial_delay();
+ }
+}
+
+// interrupt handle to be used by the slave device
+void interrupt_handler(void *arg) {
+ chSysLockFromISR();
+
+ sync_send();
+
+ // read mid pulses
+ serial_delay_blip();
+
+ uint8_t checksum_computed = 0;
+ int sstd_index = 0;
+
+#ifdef SERIAL_USE_MULTI_TRANSACTION
+ sstd_index = serial_read_byte();
+ sync_send();
+#endif
+
+ SSTD_t *trans = &Transaction_table[sstd_index];
+ for (int i = 0; i < trans->initiator2target_buffer_size; ++i) {
+ trans->initiator2target_buffer[i] = serial_read_byte();
+ sync_send();
+ checksum_computed += trans->initiator2target_buffer[i];
+ }
+ checksum_computed ^= 7;
+ uint8_t checksum_received = serial_read_byte();
+ sync_send();
+
+ // wait for the sync to finish sending
+ serial_delay();
+
+ uint8_t checksum = 0;
+ for (int i = 0; i < trans->target2initiator_buffer_size; ++i) {
+ serial_write_byte(trans->target2initiator_buffer[i]);
+ sync_send();
+ serial_delay_half();
+ checksum += trans->target2initiator_buffer[i];
+ }
+ serial_write_byte(checksum ^ 7);
+ sync_send();
+
+ // wait for the sync to finish sending
+ serial_delay();
+
+ *trans->status = (checksum_computed == checksum_received) ? TRANSACTION_ACCEPTED : TRANSACTION_DATA_ERROR;
+
+ // end transaction
+ serial_input();
+
+ // TODO: remove extra delay between transactions
+ serial_delay();
+
+ chSysUnlockFromISR();
+}
+
+/////////
+// start transaction by initiator
+//
+// int soft_serial_transaction(int sstd_index)
+//
+// Returns:
+// TRANSACTION_END
+// TRANSACTION_NO_RESPONSE
+// TRANSACTION_DATA_ERROR
+// this code is very time dependent, so we need to disable interrupts
+#ifndef SERIAL_USE_MULTI_TRANSACTION
+int soft_serial_transaction(void) {
+ int sstd_index = 0;
+#else
+int soft_serial_transaction(int sstd_index) {
+#endif
+
+ if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR;
+ SSTD_t *trans = &Transaction_table[sstd_index];
+
+ // TODO: remove extra delay between transactions
+ serial_delay();
+
+ // this code is very time dependent, so we need to disable interrupts
+ chSysLock();
+
+ // signal to the slave that we want to start a transaction
+ serial_output();
+ serial_low();
+ serial_delay_blip();
+
+ // wait for the slaves response
+ serial_input();
+ serial_high();
+ serial_delay();
+
+ // check if the slave is present
+ if (serial_read_pin()) {
+ // slave failed to pull the line low, assume not present
+ dprintf("serial::NO_RESPONSE\n");
+ chSysUnlock();
+ return TRANSACTION_NO_RESPONSE;
+ }
+
+ // if the slave is present syncronize with it
+
+ uint8_t checksum = 0;
+ // send data to the slave
+#ifdef SERIAL_USE_MULTI_TRANSACTION
+ serial_write_byte(sstd_index); // first chunk is transaction id
+ sync_recv();
+#endif
+ for (int i = 0; i < trans->initiator2target_buffer_size; ++i) {
+ serial_write_byte(trans->initiator2target_buffer[i]);
+ sync_recv();
+ checksum += trans->initiator2target_buffer[i];
+ }
+ serial_write_byte(checksum ^ 7);
+ sync_recv();
+
+ serial_delay();
+ serial_delay(); // read mid pulses
+
+ // receive data from the slave
+ uint8_t checksum_computed = 0;
+ for (int i = 0; i < trans->target2initiator_buffer_size; ++i) {
+ trans->target2initiator_buffer[i] = serial_read_byte();
+ sync_recv();
+ checksum_computed += trans->target2initiator_buffer[i];
+ }
+ checksum_computed ^= 7;
+ uint8_t checksum_received = serial_read_byte();
+
+ sync_recv();
+ serial_delay();
+
+ if ((checksum_computed) != (checksum_received)) {
+ dprintf("serial::FAIL[%u,%u,%u]\n", checksum_computed, checksum_received, sstd_index);
+ serial_output();
+ serial_high();
+
+ chSysUnlock();
+ return TRANSACTION_DATA_ERROR;
+ }
+
+ // always, release the line when not in use
+ serial_high();
+ serial_output();
+
+ chSysUnlock();
+ return TRANSACTION_END;
+}
diff --git a/quantum/stm32/halconf.h b/quantum/stm32/halconf.h
index 533803a25..b6c7b392c 100644
--- a/quantum/stm32/halconf.h
+++ b/quantum/stm32/halconf.h
@@ -203,7 +203,7 @@
* @note Disabling this option saves both code and data space.
*/
# if !defined(PAL_USE_CALLBACKS) || defined(__DOXYGEN__)
-# define PAL_USE_CALLBACKS FALSE
+# define PAL_USE_CALLBACKS TRUE
# endif
/**
@@ -211,7 +211,7 @@
* @note Disabling this option saves both code and data space.
*/
# if !defined(PAL_USE_WAIT) || defined(__DOXYGEN__)
-# define PAL_USE_WAIT FALSE
+# define PAL_USE_WAIT TRUE
# endif
/*===========================================================================*/
href='#n560'>560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
## gMock for Dummies {#GMockForDummies}

<!-- GOOGLETEST_CM0013 DO NOT DELETE -->

### What Is gMock?

When you write a prototype or test, often it's not feasible or wise to rely on
real objects entirely. A **mock object** implements the same interface as a real
object (so it can be used as one), but lets you specify at run time how it will
be used and what it should do (which methods will be called? in which order? how
many times? with what arguments? what will they return? etc).

**Note:** It is easy to confuse the term *fake objects* with mock objects. Fakes
and mocks actually mean very different things in the Test-Driven Development
(TDD) community:

*   **Fake** objects have working implementations, but usually take some
    shortcut (perhaps to make the operations less expensive), which makes them
    not suitable for production. An in-memory file system would be an example of
    a fake.
*   **Mocks** are objects pre-programmed with *expectations*, which form a
    specification of the calls they are expected to receive.

If all this seems too abstract for you, don't worry - the most important thing
to remember is that a mock allows you to check the *interaction* between itself
and code that uses it. The difference between fakes and mocks shall become much
clearer once you start to use mocks.

**gMock** is a library (sometimes we also call it a "framework" to make it sound
cool) for creating mock classes and using them. It does to C++ what
jMock/EasyMock does to Java (well, more or less).

When using gMock,

1.  first, you use some simple macros to describe the interface you want to
    mock, and they will expand to the implementation of your mock class;
2.  next, you create some mock objects and specify its expectations and behavior
    using an intuitive syntax;
3.  then you exercise code that uses the mock objects. gMock will catch any
    violation to the expectations as soon as it arises.

### Why gMock?

While mock objects help you remove unnecessary dependencies in tests and make
them fast and reliable, using mocks manually in C++ is *hard*:

*   Someone has to implement the mocks. The job is usually tedious and
    error-prone. No wonder people go great distance to avoid it.
*   The quality of those manually written mocks is a bit, uh, unpredictable. You
    may see some really polished ones, but you may also see some that were
    hacked up in a hurry and have all sorts of ad hoc restrictions.
*   The knowledge you gained from using one mock doesn't transfer to the next