From 1faf29549fb5ee4842908cf7d3187d730be295f2 Mon Sep 17 00:00:00 2001 From: MX682X <58419867+MX682X@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:10:33 +0200 Subject: [PATCH] PTC Support --- .github/workflows/compile-examples.yml | 6 + megaavr/libraries/PTC/README.md | 140 +++ .../PTC/examples/mutualcap/mutualcap.ino | 66 + .../mutualcap_low_power.ino | 106 ++ .../self_and_mutual_mix.ino | 74 ++ .../PTC/examples/selfcap/selfcap.ino | 68 + .../selfcap_other_nodes_shield.ino | 62 + .../selfcap_with_dedicated_shield.ino | 67 + megaavr/libraries/PTC/keywords.txt | 84 ++ megaavr/libraries/PTC/library.properties | 10 + megaavr/libraries/PTC/src/ptc.c | 1101 +++++++++++++++++ megaavr/libraries/PTC/src/ptc.h | 301 +++++ megaavr/libraries/PTC/src/ptc_io.h | 424 +++++++ megaavr/libraries/PTC/src/ptc_types.h | 252 ++++ megaavr/platform.txt | 4 +- 15 files changed, 2763 insertions(+), 2 deletions(-) create mode 100644 megaavr/libraries/PTC/README.md create mode 100644 megaavr/libraries/PTC/examples/mutualcap/mutualcap.ino create mode 100644 megaavr/libraries/PTC/examples/mutualcap_low_power/mutualcap_low_power.ino create mode 100644 megaavr/libraries/PTC/examples/self_and_mutual_mix/self_and_mutual_mix.ino create mode 100644 megaavr/libraries/PTC/examples/selfcap/selfcap.ino create mode 100644 megaavr/libraries/PTC/examples/selfcap_other_nodes_shield/selfcap_other_nodes_shield.ino create mode 100644 megaavr/libraries/PTC/examples/selfcap_with_dedicated_shield/selfcap_with_dedicated_shield.ino create mode 100644 megaavr/libraries/PTC/keywords.txt create mode 100644 megaavr/libraries/PTC/library.properties create mode 100644 megaavr/libraries/PTC/src/ptc.c create mode 100644 megaavr/libraries/PTC/src/ptc.h create mode 100644 megaavr/libraries/PTC/src/ptc_io.h create mode 100644 megaavr/libraries/PTC/src/ptc_types.h diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 371d0616..a20b438e 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -95,6 +95,12 @@ jobs: - megaavr/libraries/ZCD/examples/Interrupt - megaavr/libraries/ZCD/examples/Read_state - megaavr/libraries/ZCD/examples/Simple_ZCD + - megaavr/libraries/PTC/examples/mutualcap + - megaavr/libraries/PTC/examples/mutualcap_low_power + - megaavr/libraries/PTC/examples/self_and_mutual_mix + - megaavr/libraries/PTC/examples/selfcap + - megaavr/libraries/PTC/examples/selfcap_other_nodes_shield + - megaavr/libraries/PTC/examples/selfcap_with_dedicated_shield avr-db-series-sketch-paths: | - megaavr/libraries/Opamp/examples/Inverting_amplifier - megaavr/libraries/Opamp/examples/Inverting_amplifier_with_follower diff --git a/megaavr/libraries/PTC/README.md b/megaavr/libraries/PTC/README.md new file mode 100644 index 00000000..8a96e2ab --- /dev/null +++ b/megaavr/libraries/PTC/README.md @@ -0,0 +1,140 @@ +# ptc_touch +An Arduino compatible C library to support the PTC in the AtTiny 1-family as well as the DA family. + +## Features +* Ready to use with minimal knowledge of the peripheral, but can still be well configured (options are similar to the "official" library) +* Supports Mutual-cap and Self-cap (+Shield) sensing technology +* Supports event-based triggering of conversions, as well as window-compared wake-up from stand-by sleep +* On AtTiny: Can be suspended to allow the polled use of ADC0's internal channels (ADC0 ISRs are used by the library) +* Reduced memory footprint due to CPU specific optimizations compared to the "official" implementation + + +## Installation +For the non-Arduino people: all required files are inside the src folder (No Arduino exclusive functions are used in this library). + + +### How to use this library + + +#### Step 1: Memory Initialization +Allocate memory for every single sensing node. The easiest way is to have an array with `cap_sensor_t` elements, e.g. `cap_sensor_t nodes[3];`. Even though it allows you to access the data stored in this structures directly, it is not allowed to change the contents directly. Always use the provided setter functions for this. + +#### Step 2: Node registration +Initialize the node to be either a mutual-cap node with `uint8_t ptc_add_mutualcap_node(cap_sensor_t* node, ptc_ch_bm_t xCh, ptc_ch_bm_t yCh);` or to a self-cap node with `uint8_t ptc_add_selfcap_node(cap_sensor_t* node, ptc_ch_bm_t xCh, ptc_ch_bm_t yCh);`. When everything went without problems, the functions will return the enum `PTC_LIB_SUCCESS`. While mutual-cap requires yCh and xCh not to be 0 (otherwise `PTC_LIB_BAD_ARGUMENT` will be returned), the self-cap requires only the yCh not to be 0. If the xCh is zero, the node will be a normal self-cap node, otherwise it will be of the self-cap with shield type. This becomes relevant when you change between the types, but that will be explained later. This function will also disable the pullup and digital input function of the specified pins. The order, in which this function is called determines the conversion order. The nodes will be also numbered for easier identification. Up to 255 nodes are supported for devices with 16 or less PTC pins. 65k for bigger devices. The nodes are enabled by default, so, in case an added node should be disabled, you can call `uint8_t ptc_disable_node(cap_sensor_t* node);`, and for re-enabling `uint8_t ptc_enable_node(cap_sensor_t* node);`. +Here are some examples: +- `ptc_add_mutualcap_node(&nodes[0], PIN_TO_PTC(PIN_PA7), PIN_TO_PTC(PIN_PA4));` + - PA4 will be sensing, PA7 will be driving +- `ptc_add_selfcap_node(&nodes[1], 0, PIN_TO_PTC(PIN_PA4));` + - No Driving pin, only sensing on PA4 +- `ptc_add_selfcap_node(&nodes[2], PIN_TO_PTC(PIN_PA7), (PIN_TO_PTC(PIN_PA4) | PIN_TO_PTC(PIN_PA5)));` + - useful for wakeups, all "buttons" work as one, no matter on which you press, plus driven shield pin. + + +#### Step 3: Acquisition and Processing +This library requires a regular call to the function `void ptc_process(uint16_t currTime)`. This function handles all background functionality of the library and calls the callback `extern void ptc_event_callback(const uint8_t eventType, cap_sensor_t* node)`, or one of the few sub-callbacks below, when appropriate. First, the function checks if an acquisition was completed (all nodes of the selected type converted). If that's the case, it proceeds to handle the gathered data to handle the respective state machine of each node whose conversion was completed. The more nodes you have, the more time it might take. +The exact workings of this function will exceed the scope of this document. + +This function takes an argument, `uint16_t currTime`, to decide when to start the next acquisition. This, compared to having a, as an example, `millis()` inside the library, offers the user significant flexibility on choosing the time source (e.g. TCB.CNT). The Period can be set by `void ptc_set_acqusition_period(uint16_t period)`. Whenever the currTime minus the currTime when the last acquisition trigger happened is greater or equal the period, a new acquisition is started. +However, if there was a successful call to `uint8_t ptc_suspend(void)`, only the timestamp is updated, but no acquisition is started. This also applies when the library was put into low-power mode, except that conversions can still be triggered by events. +Even though this library can handle three different node types, only one type can be acquired at a time. By default, the type of the first node in the list is used. But the user can use the function `ptc_set_next_conversion_type(uint8_t type)` to change this behavior. + +#### Step 4: Callback +In order to understand what happens, there is an extern callback function defined in the h-file. This means that you are required to have a function in your sketch-file that look like this: `void ptc_event_callback(const uint8_t eventType, cap_sensor_t* node) {`. The reason why I used an extern function and not a pointer to a callback is that this allows the compiler to inline optimizations, like optimizing away calls with eventTypes that don't need handling. +There are following events: +- `PTC_CB_EVENT_WAKE_TOUCH` (node: lowPower): + - This event happens in LP mode only when the Window-Comparator was triggered. +- `PTC_CB_EVENT_WAKE_NO_TOUCH` (node: lowPower): + - This event happens in LP mode only when the Window-Comparator was not triggered. +- `PTC_CB_EVENT_CONV_CMPL` (node: current node): + - This event happens whenever ptc_process finished handling said node +- `PTC_CB_EVENT_CONV_MUTUAL_CMPL`, +- `PTC_CB_EVENT_CONV_SELF_CMPL`, +- `PTC_CB_EVENT_CONV_SHIELD_CMPL` (node: last converted node): + - This events happen whenever ptc_process has handled all nodes of said type. +- `PTC_CB_EVENT_CONV_CALIB` (node: current node): + - This event happens, whenever the calibration was successful. +- `PTC_CB_EVENT_ERR_CALIB` (node: current node): + - This event happens, whenever the calibration failed on said node. +- `PTC_CB_EVENT_TOUCH_DETECT`(node: current node): + - This event triggers shortly before the state-machine is updated to the "touched" state. +- `PTC_CB_EVENT_TOUCH_RELEASE`(node: current node): + - This event triggers shortly before the state-machine is updated to the "not-touched" state. + +You can use a simple switch-case to check for the events in the callback. +Another option is to use following "sub-callbacks". As they are weak and aliased with `ptc_event_callback` it is possible to choose between one function to collect all events, or have dedicated function for each event type. +- `void ptc_event_cb_wake(const ptc_cb_event_t eventType, cap_sensor_t* node);` + - called on `PTC_CB_EVENT_WAKE_*` events +- `void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t* node);` + - called on `PTC_CB_EVENT_CONV_*_CMPL` events +- `void ptc_event_cb_error(const ptc_cb_event_t eventType, cap_sensor_t* node);` + - called on `PTC_CB_EVENT_CONV_CALIB` events +- `void ptc_event_cb_calibration(const ptc_cb_event_t eventType, cap_sensor_t* node);` + - called on `PTC_CB_EVENT_ERR_CALIB` events +- `void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t* node);` + - called on `PTC_CB_EVENT_TOUCH_*` events + + + +### Low Power operation +This library also provides a so-called low power mode. This mode changes the operation of the library and the ADC. It will be set up to wait for Events, however it is up to the user to set up the Event-Routing network. The low power mode also sets up the window-comparator to detect touch events. It should be also noted, that it is not possible to use `ptc_suspend` while the library is in low-power mode. the low-power mode has to be disabled first before being able to suspend the library. + +In order to initialize the low-power mode, just call `uint8_t ptc_lp_init(cap_sensor_t* node)` (to disable: `uint8_t ptc_lp_disable(void)`) with the node you want to have automatically converted. The node must be enabled and must have been calibrated beforehand to work correctly, meaning it had to have a couple finished conversions. + +To check, if the window-comparator was triggered outside of the WAKE events, you can use the function `uint8_t ptc_lp_was_waken(void)` that returns either `PTC_LIB_WAS_WAKEN` or `PTC_LIB_ASLEEP`, at least while the low-power mode is active. + +### Pins and how to use the ptc_add_* functions + +The ptc_add_* functions require a bitmap (ptc_ch_bm_t yCh; ptc_ch_bm_t xCh) of pins, that will be connected to the PTC internals upon the beginning of a conversion. The bit position equals to the PTC pin numbering, that you can find in the PTC column in the datasheet. On Tinies, it is different from the usual numbering, for example PA4 on the 1614 is X0/Y0, resulting in a bitmap of 0x01. On DAs it is more straightforward, PA0 is bitmap 1<<0, PB0 is 1<<8. PORTC is skipped, so PD0 is 1<<16. + +However, to make it easier, a compile-time look-up-table with a macro to access it is provided, PIN_TO_PTC(PIN_Pnx). If the pin is valid, the corresponding bit-map is returned, otherwise null. But this requires the megatiny/Dx core from Spence Konde, as the PIN_Pnx naming is defined there. + +The reason, why a bit-map is used, is to provide a way to connect the electrodes of multiple pins to act as one big electrode. In order to create a node with multiple pins connected, a simple OR operation is enough. + +The ptc_ch_bm_t is a typedef that depends on the pincount of the device and ranges from uint8_t to uint16_t (and up to uint64_t on the DAs). Up to 8 PTC-Pins, uint8_t, up to 16 PTC pins, uint16_t, and so on. + +### PTC Operation +![PTC_InnerWorkings](https://github.com/MX682X/ptc_touch/assets/58419867/823c487c-0633-4031-b381-45e7a32867fb) +(Source: [Microchip's PTC Subsystem Firmware User's Guide](https://web.archive.org/web/20221225142000/https://www.mouser.com/pdfdocs/SAMA5D2_PTC_Firmware_UG.pdf)) +This schematic was made for a different chip, but it is likely to look similar to this on the AVRs. + +Most of the following is a hypothesis based on the publicly available documentation and observation. +The PTC is using a charge transfer between a variable and a fixed capacitance to measure a difference between those two. The variable capacitance in this case is the sensor electrode and the fixed one is the internal Cc. The neat thing about having a dedicated hardware for this, is that Cc is not really fixed, compared to the Sample-and-Hold capacitor of the normal ADC, it can be calibrated to be about the same as the electrode we want to measure (See below). + +In order to measure the capacitance of the node, the PTC performs following Steps: + 1. Charge the electrode to Vdd + 2. Wait for charge to complete + 3. Disconnect node from Vdd and instead connect it to Cc + 4. Measure voltage of Cc + 5. Connect node and Cc to GND to discharge them + +The measured voltage depends on what happens around the electrode. In an idle state, 50% of the charge of the node (and thus voltage) is transferred to Cc, however when something conductive is moved towards the electrode, the capacitance will increase slightly. With a higher capacitance, the node will have a higher charge, meaning less then 50% of the charge can be transferred to Cc until they reach an equal Voltage, which means the overall voltage will be higher, which can be measured by the ADC. + +If you have trouble understanding: Imagine two equally sized volumes connected through a valve. You fill the first up with water, open the valve and look on the second, how high the water has risen. After marking the "idle" level, when you put a finger in the water and see the water rise. This new level is the increase in voltage due to an interference. + +### Calibration / Compensation + +Based on the documentation found online, the PTC has an internal, tunable capacitor connected after the series resistance to ground that is used to compensate the parasitic capacitance of the electrodes. Every node starts with a default compensation value. As soon as the node is enabled, the library attempts to find a compensation setting that will result in an ADC value of about 512 counts (1/2 of ADC resolution). Based on oscilloscope readings, it can also be said that the PTC tries to have a charge of 50% VCC on the electrode when being acquired. This is the also the reason, why the digital input function of the pins is disabled. + +The maximum compensation is about 30pF for Mutual-cap and about 50pF for Self-cap. It is possible to get the compensation capacitance with uint16_t ptc_get_node_cc_femto(cap_sensor_t* node); - however this function has to do a lot of calculations and is thus a bit bloat-y. It will also return the value in femto farrads, to avoid floats. Read more here: https://www.microchipdeveloper.com/touch:guide-to-interpret-cc-calibration-value + +Different pins have a different parasitic capacitance. I suspect this is depends on the internal circuitry and alternative functions, so it's normal to see some difference with pins next to each other. + +### Tuning of nodes + +In order to ease the use of the PTC module, the ptc_add_* functions will initialize the cap_sensor_t struct with some default values, like the CC value mentioned above. That values can be easily changed and will be applied the next time a conversion of said node starts. Here is a list: +- Analog Gain. Increases the sensitivity of the electrode by adjusting a capacitor on a integrator (I think) (1x Gain) +- Digital Gain. Defines the amount of ADC Oversampling. Will not affect the count value, as it is internally right-shifted. (16x Oversampled) +- Charge Share Delay. Affects the Sample length of the ADC. (0 extra clocks) +- Prescaler. It is possible to slow down the ADC clock by adjusting the Prescaler. (Depends on CPU clock, targeted: 1MHz +/- 25%) +- Serial Resistor. Allows to change the serial resistor between the Cc and the node. Fixed at 100k for Self-Cap. Creates RC-low-pass filter. + +If a node is not sensitive enough, you can increase the Analog Gain (if it becomes too sensitive, an increase of the thresholds might be needed). However it is better to have a bigger node to begin with because the bigger the area, the higher is the capacitance delta. + +### Global settings of the State-machine +The state-machine, which changes the node's state between Calibration, touch, no touch, etc. uses some variables that are valid for all nodes, those are: +- `uint16_t force_recal_delta`. Each node has a threshold value that is used to calculate the delta. This Threshold value is drifting over time to adjust for environmental changes. If the threshold value drifts 512 +/- this value, a recalibration of CC is performed. Default: 150 +- `uint8_t touched_detect_nom`. Number of consecutive Measurements (Conversions) that are above the touch threshold until the node becomes "touched". Default: 3 +- `uint8_t untouched_detect_nom`. Number of consecutive measurements that are below the no-touch threshold until the node is fully untouched. Default: 3 +- `uint8_t touched_max_nom`. Number of consecutive measurements plus one in the touched state until a recalibration is forced. Can be disabled by writing 255 to it. Default: 200 +- `uint8_t drift_up_nom`. If the delta is higher then the reference, but lower then the threshold, the amount of consecutive measurements plus one, until the reference is incremented. Can be disabled with 255. Default: 20. +- `uint8_t drift_down_nom`. If the delta is below the reference, the amount of consecutive measurements plus one until the reference is decremented. Can be disabled with 255. Default: 20. diff --git a/megaavr/libraries/PTC/examples/mutualcap/mutualcap.ino b/megaavr/libraries/PTC/examples/mutualcap/mutualcap.ino new file mode 100644 index 00000000..90b484cb --- /dev/null +++ b/megaavr/libraries/PTC/examples/mutualcap/mutualcap.ino @@ -0,0 +1,66 @@ +#include +/* + * This example creates two different sensing nodes. + * PA4 and PA5 are on the Y-Lines, PA6 is the X-Line. + * PTC_CB_EVENT_TOUCH_DETECT and PTC_CB_EVENT_TOUCH_RELEASE can + * be used for quick actions, like switching a pin or variable, + * but it is recommended to use PTC_CB_EVENT_CONV_MUTUAL_CMPL, as + * otherwise the handling of the successive nodes would be delayed. + */ +#define MySerial Serial +#if !defined(MILLIS_USE_TIMERNONE) +cap_sensor_t nodes[2]; + +void setup() { + MySerial.begin(115200); + + // this puts the node on the list and initializes to default values + ptc_add_mutualcap_node(&nodes[0], PIN_TO_PTC(PIN_PA4), PIN_TO_PTC(PIN_PA7)); + ptc_add_mutualcap_node(&nodes[1], PIN_TO_PTC(PIN_PA5), PIN_TO_PTC(PIN_PA7)); + + + // Make sure Serial works + MySerial.println("Hello World!"); +} + +void loop() { + ptc_process(millis()); // main ptc task, requires regular calls +} + +// callbacks that are called by ptc_process at different points to ease user interaction +void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_TOUCH_DETECT == eventType) { + MySerial.print("node touched:"); + MySerial.println(ptc_get_node_id(node)); + } else if (PTC_CB_EVENT_TOUCH_RELEASE == eventType) { + MySerial.print("node released:"); + MySerial.println(ptc_get_node_id(node)); + } +} + +void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_CONV_TYPE_CMPL_MSK & eventType) { + // Do more complex things here + } + (void)node; // remove unused warning +} + +void ptc_event_cb_calibration(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_ERR_CALIB_LOW == eventType) { + MySerial.print("Calib error, Cc too low."); + } else if (PTC_CB_EVENT_ERR_CALIB_HIGH == eventType) { + MySerial.print("Calib error, Cc too high."); + } else if (PTC_CB_EVENT_ERR_CALIB_TO == eventType) { + MySerial.print("Calib error, calculation timeout."); + } else { + MySerial.print("Calib Successful."); + } + MySerial.print(" Node: "); + MySerial.println(ptc_get_node_id(node)); +} +#else +void setup() { +} +void loop() { +} +#endif diff --git a/megaavr/libraries/PTC/examples/mutualcap_low_power/mutualcap_low_power.ino b/megaavr/libraries/PTC/examples/mutualcap_low_power/mutualcap_low_power.ino new file mode 100644 index 00000000..7fc99041 --- /dev/null +++ b/megaavr/libraries/PTC/examples/mutualcap_low_power/mutualcap_low_power.ino @@ -0,0 +1,106 @@ +#include +#include +/* + * This example demonstrates how to use the PTC in sleep mode. + * The library changes the triggering to an Event-System based approach. + * However, only one node can be monitored this way. To allow a wake-up, + * all nodes a connected together in the low-power node. + * PA4 is the X-Line, while PA5, PA6 and PA7 act as Y-Line. + */ +#define MySerial Serial +#if !defined(MILLIS_USE_TIMERNONE) +cap_sensor_t nodes[3]; +cap_sensor_t lp_node; // as an example, could also be part of the array above + +void setup() { + MySerial.begin(115200); + + // this puts the node on the list and initializes to default values + ptc_add_mutualcap_node(&nodes[0], PIN_TO_PTC(PIN_PA4), PIN_TO_PTC(PIN_PA5)); + ptc_add_mutualcap_node(&nodes[1], PIN_TO_PTC(PIN_PA4), PIN_TO_PTC(PIN_PA6)); + ptc_add_mutualcap_node(&nodes[2], PIN_TO_PTC(PIN_PA4), PIN_TO_PTC(PIN_PA7)); + + // make the "low power" node a lumped sensor - this allows to monitor all electrodes at the same time. + // In other words, this creates a single, big button. + ptc_add_mutualcap_node(&lp_node, PIN_TO_PTC(PIN_PA4), (PIN_TO_PTC(PIN_PA5) | PIN_TO_PTC(PIN_PA6) | PIN_TO_PTC(PIN_PA7))); + + RTC.PITCTRLA = RTC_PITEN_bm; // In this example, the PIT is our Event source/timer + EVSYS.CHANNEL0 = EVSYS_CHANNEL0_RTC_PIT_DIV2048_gc; // The user must select the Event Channel + + SLPCTRL.CTRLA = SLEEP_MODE_STANDBY | SLPCTRL_SEN_bm; // The deepest we can go is Stand-by + + // Make sure Serial works + MySerial.println("Hello World!"); +} + +uint32_t nextSleep = 10000; // entering sleep after 10 seconds of inactivity + +void loop() { + uint32_t ms = millis(); + ptc_process(ms); // main ptc task, requires regular calls + if (ms > nextSleep) { + nextSleep = ms + 10000; + MySerial.println("Sleepy..."); + MySerial.flush(); // wait for transmission to finish + + + ptc_lp_init(&lp_node, EVSYS_USER_CHANNEL0_gc); // Will set STARTEI bit to start conversions, needs the desired Event Channel + while (ptc_lp_was_waken() != PTC_LIB_WAS_WAKEN) { + sleep_cpu(); // Make sure the ADC has woken the CPU, otherwise, go back to sleep. + } // The library does not filter wake-ups. After all, something else might have woken the chip. + MySerial.println("I'm Awake!"); + ptc_lp_disable(); // disable the low-power node to restore the usual acquisition process + MySerial.flush(); // wait for finished transmission from Serial + } +} + +// callbacks that are called by ptc_process at different points to ease user interaction +void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t *node) { + uint32_t ms = millis(); + if (PTC_CB_EVENT_TOUCH_DETECT == eventType) { // a low-power node, that has waken the chip won't issue a TOUCH_DETECT + MySerial.print("node touched:"); + MySerial.println(ptc_get_node_id(node)); + nextSleep = ms + 10000; + } else if (PTC_CB_EVENT_TOUCH_RELEASE == eventType) { // however, a TOUCH_RELEASE will be, when the finger is removed again + MySerial.print("node released:"); + MySerial.println(ptc_get_node_id(node)); + nextSleep = ms + 10000; + } +} + +void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_CONV_TYPE_CMPL_MSK & eventType) { + // Do more complex things here + } + (void)node; // remove unused warning +} + +void ptc_event_cb_calibration(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_ERR_CALIB_LOW == eventType) { + MySerial.print("Calib error, Cc too low."); + } else if (PTC_CB_EVENT_ERR_CALIB_HIGH == eventType) { + MySerial.print("Calib error, Cc too high."); + } else if (PTC_CB_EVENT_ERR_CALIB_TO == eventType) { + MySerial.print("Calib error, calculation timeout."); + } else { + MySerial.print("Calib Successful. Time to fix: "); // Time to fix indicates how long the calibration took. Should be < 6 preferably. + MySerial.print(node->lastStateChange); // Values close to 10 might indicate some problems with noise. + } + MySerial.print(" Node: "); + MySerial.println(ptc_get_node_id(node)); +} + +void ptc_event_cb_wake(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_WAKE_TOUCH == eventType) { + // True if the node was touched when a wakeup occurred + } else if (PTC_CB_EVENT_WAKE_NO_TOUCH == eventType) { + // True if the node was no touch when a wakeup occurred + } + (void)node; // remove unused warning +} +#else +void setup() { +} +void loop() { +} +#endif diff --git a/megaavr/libraries/PTC/examples/self_and_mutual_mix/self_and_mutual_mix.ino b/megaavr/libraries/PTC/examples/self_and_mutual_mix/self_and_mutual_mix.ino new file mode 100644 index 00000000..4250c6b4 --- /dev/null +++ b/megaavr/libraries/PTC/examples/self_and_mutual_mix/self_and_mutual_mix.ino @@ -0,0 +1,74 @@ +#include +/* + * This example creates four different sensing nodes. of two different types. + * PA4 and PA5 are the self-cap lines with PB0 acting as shield pin. + * PA6 and PA7 are the Y-Lines with PB1 acting as the X-line. + * PTC_CB_EVENT_CONV_MUTUAL_CMPL and + * PTC_CB_EVENT_CONV_SHIELD_CMPL can be used to change the type that is converted. + * This will create an interlaced conversion, but it is not mandatory to do so. + */ + + +#define MySerial Serial +#if !defined(MILLIS_USE_TIMERNONE) +cap_sensor_t nodes[4]; + + +void setup() { + // put your setup code here, to run once: + ptc_add_selfcap_node(&nodes[0], PIN_TO_PTC(PIN_PB0), PIN_TO_PTC(PIN_PA4)); + ptc_add_selfcap_node(&nodes[1], PIN_TO_PTC(PIN_PB0), PIN_TO_PTC(PIN_PA5)); + + ptc_add_mutualcap_node(&nodes[2], PIN_TO_PTC(PIN_PB1), PIN_TO_PTC(PIN_PA6)); + ptc_add_mutualcap_node(&nodes[3], PIN_TO_PTC(PIN_PB1), PIN_TO_PTC(PIN_PA7)); + + MySerial.begin(115200); + MySerial.println("Hello World!"); +} + +void loop() { + // put your main code here, to run repeatedly: + ptc_process(millis()); // main ptc task, requires regular calls +} + + + +// callbacks that are called by ptc_process at different points to ease user interaction +void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_TOUCH_DETECT == eventType) { + MySerial.print("node touched:"); + MySerial.println(ptc_get_node_id(node)); + } else if (PTC_CB_EVENT_TOUCH_RELEASE == eventType) { + MySerial.print("node released:"); + MySerial.println(ptc_get_node_id(node)); + } +} + +void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_CONV_MUTUAL_CMPL == eventType) { + ptc_set_next_conversion_type(NODE_SELFCAP_SHIELD_bm); + } else if (PTC_CB_EVENT_CONV_SHIELD_CMPL == eventType) { + ptc_set_next_conversion_type(NODE_MUTUAL_bm); + } + (void)node; // remove unused warning +} + +void ptc_event_cb_calibration(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_ERR_CALIB_LOW == eventType) { + MySerial.print("Calib error, Cc too low."); + } else if (PTC_CB_EVENT_ERR_CALIB_HIGH == eventType) { + MySerial.print("Calib error, Cc too high."); + } else if (PTC_CB_EVENT_ERR_CALIB_TO == eventType) { + MySerial.print("Calib error, calculation timeout."); + } else { + MySerial.print("Calib Successful."); + } + MySerial.print(" Node: "); + MySerial.println(ptc_get_node_id(node)); +} +#else +void setup() { +} +void loop() { +} +#endif diff --git a/megaavr/libraries/PTC/examples/selfcap/selfcap.ino b/megaavr/libraries/PTC/examples/selfcap/selfcap.ino new file mode 100644 index 00000000..d62be568 --- /dev/null +++ b/megaavr/libraries/PTC/examples/selfcap/selfcap.ino @@ -0,0 +1,68 @@ +#include +/* + * This example creates three different sensing nodes on pins + * PA4, PA5 and PA6. They get the IDs 0, 1 and 2 respectively. + * PTC_CB_EVENT_TOUCH_DETECT and PTC_CB_EVENT_TOUCH_RELEASE can + * be used for quick actions, like switching a pin or variable, + * but it is recommended to use PTC_CB_EVENT_CONV_SELF_CMPL, as + * otherwise the handling of the successive nodes would be delayed. + */ +#define MySerial Serial +#if !defined(MILLIS_USE_TIMERNONE) +cap_sensor_t nodes[3]; + +void setup() { + MySerial.begin(115200); + + // this puts the node on the list and initializes to default values + ptc_add_selfcap_node(&nodes[0], 0, PIN_TO_PTC(PIN_PA4)); + ptc_add_selfcap_node(&nodes[1], 0, PIN_TO_PTC(PIN_PA5)); + ptc_add_selfcap_node(&nodes[2], 0, PIN_TO_PTC(PIN_PA6)); + + // Make sure Serial works + MySerial.println("Hello World!"); +} + +void loop() { + ptc_process(millis()); // main ptc task, requires regular calls +} + + + +// callbacks that are called by ptc_process at different points to ease user interaction +void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_TOUCH_DETECT == eventType) { + MySerial.print("node touched:"); + MySerial.println(ptc_get_node_id(node)); + } else if (PTC_CB_EVENT_TOUCH_RELEASE == eventType) { + MySerial.print("node released:"); + MySerial.println(ptc_get_node_id(node)); + } +} + +void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_CONV_TYPE_CMPL_MSK & eventType) { + // Do more complex things here + } + (void)node; // remove unused warning +} + +void ptc_event_cb_calibration(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_ERR_CALIB_LOW == eventType) { + MySerial.print("Calib error, Cc too low."); + } else if (PTC_CB_EVENT_ERR_CALIB_HIGH == eventType) { + MySerial.print("Calib error, Cc too high."); + } else if (PTC_CB_EVENT_ERR_CALIB_TO == eventType) { + MySerial.print("Calib error, calculation timeout."); + } else { + MySerial.print("Calib Successful."); + } + MySerial.print(" Node: "); + MySerial.println(ptc_get_node_id(node)); +} +#else +void setup() { +} +void loop() { +} +#endif diff --git a/megaavr/libraries/PTC/examples/selfcap_other_nodes_shield/selfcap_other_nodes_shield.ino b/megaavr/libraries/PTC/examples/selfcap_other_nodes_shield/selfcap_other_nodes_shield.ino new file mode 100644 index 00000000..13b1e6f7 --- /dev/null +++ b/megaavr/libraries/PTC/examples/selfcap_other_nodes_shield/selfcap_other_nodes_shield.ino @@ -0,0 +1,62 @@ +#include +/* + * This example creates three different sensing nodes on pins + * PA4, PA5 and PA6. They get the IDs 0, 1 and 2 respectively. + * PTC_CB_EVENT_TOUCH_DETECT and PTC_CB_EVENT_TOUCH_RELEASE can + * be used for quick actions, like switching a pin or variable, + * but it is recommended to use PTC_CB_EVENT_CONV_SELF_CMPL, as + * otherwise the handling of the successive nodes would be delayed. + * This example demonstrates how to use the other sensor nodes + * as shield. This improves the signal-to-noise ratio. + */ +#define MySerial Serial +#if !defined(MILLIS_USE_TIMERNONE) +cap_sensor_t nodes[3]; + +void setup() { + MySerial.begin(115200); + + // this puts the node on the list and initializes to default values + ptc_add_selfcap_node(&nodes[0], PIN_TO_PTC(PIN_PA5) | PIN_TO_PTC(PIN_PA6), PIN_TO_PTC(PIN_PA4)); + ptc_add_selfcap_node(&nodes[1], PIN_TO_PTC(PIN_PA4) | PIN_TO_PTC(PIN_PA6), PIN_TO_PTC(PIN_PA5)); + ptc_add_selfcap_node(&nodes[2], PIN_TO_PTC(PIN_PA4) | PIN_TO_PTC(PIN_PA5), PIN_TO_PTC(PIN_PA6)); + + + // Make sure Serial works + MySerial.println("Hello World!"); +} + +void loop() { + ptc_process(millis()); // main ptc task, requires regular calls +} + +// callback that is called by ptc_process at different points to ease user interaction +void ptc_event_callback(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_TOUCH_DETECT == eventType) { + MySerial.print("node touched:"); + MySerial.println(ptc_get_node_id(node)); + } else if (PTC_CB_EVENT_TOUCH_RELEASE == eventType) { + MySerial.print("node released:"); + MySerial.println(ptc_get_node_id(node)); + } else if (PTC_CB_EVENT_CONV_SELF_CMPL == eventType) { + // Do more complex things here + } else if (PTC_CB_EVENT_CONV_CALIB & eventType) { + if (PTC_CB_EVENT_ERR_CALIB_LOW == eventType) { + MySerial.print("Calib error, Cc too low."); + } else if (PTC_CB_EVENT_ERR_CALIB_HIGH == eventType) { + MySerial.print("Calib error, Cc too high."); + } else if (PTC_CB_EVENT_ERR_CALIB_TO == eventType) { + MySerial.print("Calib error, calculation timeout."); + } else { + MySerial.print("Calib Successful."); + } + MySerial.print(" Node: "); + MySerial.println(ptc_get_node_id(node)); + } +} +#else +void setup() { +} +void loop() { +} +#endif diff --git a/megaavr/libraries/PTC/examples/selfcap_with_dedicated_shield/selfcap_with_dedicated_shield.ino b/megaavr/libraries/PTC/examples/selfcap_with_dedicated_shield/selfcap_with_dedicated_shield.ino new file mode 100644 index 00000000..c6711032 --- /dev/null +++ b/megaavr/libraries/PTC/examples/selfcap_with_dedicated_shield/selfcap_with_dedicated_shield.ino @@ -0,0 +1,67 @@ +#include +/* + * This example creates three different sensing nodes on pins + * PA4 and PA5. PA6 acts as a dedicated shield pin. They get the IDs 0 and 1 respectively. + * PTC_CB_EVENT_TOUCH_DETECT and PTC_CB_EVENT_TOUCH_RELEASE can + * be used for quick actions, like switching a pin or variable, + * but it is recommended to use PTC_CB_EVENT_CONV_SELF_CMPL, as + * otherwise the handling of the successive nodes would be delayed. + * This example demonstrates how to use the other sensor nodes + * as shield. This improves the signal-to-noise ratio. + */ +#define MySerial Serial +#if !defined(MILLIS_USE_TIMERNONE) +cap_sensor_t nodes[2]; + +void setup() { + MySerial.begin(115200); + + // this puts the node on the list and initializes to default values + ptc_add_selfcap_node(&nodes[0], PIN_TO_PTC(PIN_PA6), PIN_TO_PTC(PIN_PA4)); + ptc_add_selfcap_node(&nodes[1], PIN_TO_PTC(PIN_PA6), PIN_TO_PTC(PIN_PA5)); + + // Make sure Serial works + MySerial.println("Hello World!"); +} + +void loop() { + ptc_process(millis()); // main ptc task, requires regular calls +} + +// callback that is called by ptc_process at different points to ease user interaction +void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_TOUCH_DETECT == eventType) { + MySerial.print("node touched:"); + MySerial.println(ptc_get_node_id(node)); + } else if (PTC_CB_EVENT_TOUCH_RELEASE == eventType) { + MySerial.print("node released:"); + MySerial.println(ptc_get_node_id(node)); + } +} + +void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_CONV_TYPE_CMPL_MSK == eventType) { + // Do more complex things here + } + (void)node; // remove unused warning +} + +void ptc_event_cb_calibration(const ptc_cb_event_t eventType, cap_sensor_t *node) { + if (PTC_CB_EVENT_ERR_CALIB_LOW == eventType) { + MySerial.print("Calib error, Cc too low."); + } else if (PTC_CB_EVENT_ERR_CALIB_HIGH == eventType) { + MySerial.print("Calib error, Cc too high."); + } else if (PTC_CB_EVENT_ERR_CALIB_TO == eventType) { + MySerial.print("Calib error, calculation timeout."); + } else { + MySerial.print("Calib Successful."); + } + MySerial.print(" Node: "); + MySerial.println(ptc_get_node_id(node)); +} +#else +void setup() { +} +void loop() { +} +#endif diff --git a/megaavr/libraries/PTC/keywords.txt b/megaavr/libraries/PTC/keywords.txt new file mode 100644 index 00000000..7babd425 --- /dev/null +++ b/megaavr/libraries/PTC/keywords.txt @@ -0,0 +1,84 @@ +####################################### +# Syntax Coloring Map For ptc_touch +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### +ptc_ch_bm_t KEYWORD1 +ptc_node_state_t KEYWORD1 +ptc_sm_t KEYWORD1 +ptc_ret_t KEYWORD1 +ptc_lib_t KEYWORD1 +cap_sensor_t KEYWORD1 +ptc_lib_sm_set_t KEYWORD1 +PTC_t KEYWORD1 + + + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +ptc_process KEYWORD2 +ptc_event_callback KEYWORD2 +ptc_event_cb_touch KEYWORD2 +ptc_event_cb_wake KEYWORD2 +ptc_event_cb_conversion KEYWORD2 +ptc_event_cb_calibration KEYWORD2 +ptc_event_cb_error KEYWORD2 +ptc_enable_node KEYWORD2 +ptc_disable_node KEYWORD2 +ptc_set_next_conversion_type KEYWORD2 +ptc_node_set_thresholds KEYWORD2 +ptc_node_set_resistor KEYWORD2 +ptc_node_set_prescaler KEYWORD2 +ptc_node_set_gain KEYWORD2 +ptc_add_selfcap_node KEYWORD2 +ptc_add_mutualcap_node KEYWORD2 +ptc_suspend KEYWORD2 +ptc_resume KEYWORD2 +ptc_get_node_cc_femto KEYWORD2 +ptc_get_node_xCh_bm KEYWORD2 +ptc_get_node_yCh_bm KEYWORD2 +ptc_get_node_sensor_value KEYWORD2 +ptc_get_node_touched KEYWORD2 +ptc_get_node_sm KEYWORD2 +ptc_get_node_delta KEYWORD2 +ptc_get_node_state KEYWORD2 +ptc_node_request_recal KEYWORD2 +ptc_init_ADC0 KEYWORD2 +ptc_lp_init KEYWORD2 +ptc_lp_disable KEYWORD2 +ptc_lp_was_waken KEYWORD2 +ptc_get_sm_settings KEYWORD2 + + +####################################### +# Instances (KEYWORD2) +####################################### + + +####################################### +# Constants (LITERAL1) +####################################### + +PTC_CB_EVENT_TOUCH LITERAL1 +PTC_CB_EVENT_WAKE_TOUCH LITERAL1 +PTC_CB_EVENT_WAKE_NO_TOUCH LITERAL1 +PTC_CB_EVENT_CONV_CMPL LITERAL1 +PTC_CB_EVENT_CONV_MUTUAL_CMPL LITERAL1 +PTC_CB_EVENT_CONV_SELF_CMPL LITERAL1 +PTC_CB_EVENT_CONV_SHIELD_CMPL LITERAL1 +PTC_CB_EVENT_CONV_CALIB LITERAL1 +PTC_CB_EVENT_ERR_CALIB LITERAL1 +PTC_CB_EVENT_ERR_CALIB_LOW LITERAL1 +PTC_CB_EVENT_ERR_CALIB_HIGH LITERAL1 +PTC_CB_EVENT_ERR_CALIB_TO LITERAL1 + +NODE_TYPE_NOCONV_bm LITERAL1 +NODE_MUTUAL_bm LITERAL1 +NODE_SELFCAP_bm LITERAL1 +NODE_SHIELD_bm LITERAL1 +NODE_SELFCAP_SHIELD_bm LITERAL1 +NODE_TYPE_bm LITERAL1 diff --git a/megaavr/libraries/PTC/library.properties b/megaavr/libraries/PTC/library.properties new file mode 100644 index 00000000..2a17413a --- /dev/null +++ b/megaavr/libraries/PTC/library.properties @@ -0,0 +1,10 @@ +name=PTC +version=1.1.0 +author=MX682X +maintainer=MX682X +sentence=This library (experimental) allows a straightforward use of the Peripheral Touch Controller (PTC) +paragraph=The PTC contains hardware to assist in touch detection. This library helps taking leverage of this peripheral
Supports only the 1-Series AtTiny chips.
Tested with 1614 so far
feedback appreciated! +category=Sensors +includes=ptc.h +url=https://github.com/SpenceKonde/DxCore/tree/master/megaavr/libraries +architectures=megaavr diff --git a/megaavr/libraries/PTC/src/ptc.c b/megaavr/libraries/PTC/src/ptc.c new file mode 100644 index 00000000..e07164aa --- /dev/null +++ b/megaavr/libraries/PTC/src/ptc.c @@ -0,0 +1,1101 @@ +/* + * Refer to ptc.h file for copyright, changelog, usage and license information + */ + + +#include "ptc.h" + + + +// The following functions are used internally only. +// get the pointer of the last valid node struct can be "firstNode" +cap_sensor_t *ptc_get_last_node(void); + + +uint8_t ptc_append_node(cap_sensor_t *pNewNode); + +// Prepares the ADC/PTC registers for the next conversion batch, starts conversion with firstNode +void ptc_init_conversion(uint8_t nodeType); + +// Starts the conversion of the node that is passed as argument +void ptc_start_conversion(cap_sensor_t *node); + +// State-Machine Handler, interprets the measured values and decides if key is "pressed" +void ptc_process_node_sm(cap_sensor_t *node); + +// Helper function +void ptc_process_measurement(cap_sensor_t *node); + +// Handles (initial) calibration +uint8_t ptc_process_calibrate(cap_sensor_t *node); + +// Handles adjustment of the reference value when a button is not pressed +void ptc_process_adjust(); + +void ptc_set_registers(cap_sensor_t *node); + + +cap_sensor_t *firstNode = NULL; +cap_sensor_t *lowPowerNode = NULL; + +volatile cap_sensor_t *currConvNode = NULL; + +ptc_node_type_t currConvType = 0; // does not "remember" if type was low-power +ptc_node_type_t nextConvType = 0; + +uint16_t acqPeriod = 20; // Period in ms until a new acquisition is started +uint16_t lastAcqTime = 0; // millis value of last acquisition (16-bit) + +ptc_freq_t freq_select = 0; /* FREQ_SEL_0 to FREQ_SEL_15, FREQ_SEL_SPREAD -> CTRLD */ + +volatile ptc_lib_t ptc_lib_state = PTC_LIB_IDLE; + + +/* + * Global settings for the state-machine applied on every node. + * Pointer to this struct can be retrieved by ptc_get_sm_settings() + * + */ +ptc_lib_sm_set_t ptc_sm_settings = { + .force_recal_delta = 150, + .touched_detect_nom = 3, + .untouched_detect_nom = 3, + .touched_max_nom = 200, + .drift_up_nom = 15, + .drift_down_nom = 15 +}; + +ptc_lib_sm_set_t *ptc_get_sm_settings() { + return &ptc_sm_settings; +} + + +#if defined (__PTC_Tiny__) + #define PTC_DEFAULT_SC_CC 0x0567 + #define PTC_DEFAULT_MC_CC 0x0234 + + const uint8_t ptc_a_gain_lut[] = { + 0x3F, 0x1C, 0x0B, + 0x05, 0x03, 0x01, + }; +#elif defined (__PTC_DA__) + #define PTC_DEFAULT_SC_CC 0x00F0 + #define PTC_DEFAULT_MC_CC 0x00A0 + + const uint8_t ptc_a_gain_lut[] = { + 0x1F, 0x0F, 0x07, + 0x03, 0x01 + }; +#endif + + + +__attribute__((weak)) +void ptc_event_callback(const ptc_cb_event_t eventType, cap_sensor_t *node) { + (void)eventType; + (void)node; +} +__attribute__((weak, alias("ptc_event_callback"))) void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t *node); +__attribute__((weak, alias("ptc_event_callback"))) void ptc_event_cb_wake(const ptc_cb_event_t eventType, cap_sensor_t *node); +__attribute__((weak, alias("ptc_event_callback"))) void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t *node); +__attribute__((weak, alias("ptc_event_callback"))) void ptc_event_cb_calibration(const ptc_cb_event_t eventType, cap_sensor_t *node); +__attribute__((weak, alias("ptc_event_callback"))) void ptc_event_cb_error(const ptc_cb_event_t eventType, cap_sensor_t *node); + + + + +/* + * Different Functions that change the behaviour of the supplied node + */ + +// Set the threshold for touch detection and away from touch for a node +uint8_t ptc_node_set_thresholds(cap_sensor_t *node, int16_t th_in, int16_t th_out) { + if (NULL == node) { + return PTC_LIB_BAD_POINTER; + } + node->touch_in_th = th_in; + node->touch_out_th = th_out; + return PTC_LIB_SUCCESS; +} + + +// Change Resistor Setting. Note: Only has an effect on mutual sensors +uint8_t ptc_node_set_resistor(cap_sensor_t *node, uint8_t res) { + PTC_CHECK_FOR_BAD_POINTER(node); + + if (res > RSEL_MAX) { + return PTC_LIB_BAD_ARGUMENT; + } + + if (node->type & NODE_MUTUAL_bm) { + uint8_t presc = node->hw_rsel_presc & 0x0F; + presc |= ((res & 0x0F) << 4); + node->hw_rsel_presc = presc; + return PTC_LIB_SUCCESS; + } + + return PTC_LIB_BAD_ARGUMENT; +} + + +// Change prescaler. Recommended ADC frequency: < 1.5MHz, but max 3 factors below +uint8_t ptc_node_set_prescaler(cap_sensor_t *node, uint8_t presc) { + PTC_CHECK_FOR_BAD_POINTER(node); + + if ((presc > (PTC_PRESC_DEFAULT + 2)) || (presc < PTC_PRESC_DEFAULT)) { + return PTC_LIB_BAD_ARGUMENT; + } + + uint8_t res = node->hw_rsel_presc & 0xF0; + res |= (presc & 0x0F); + node->hw_rsel_presc = res; + return PTC_LIB_SUCCESS; +} + + +uint8_t ptc_node_set_gain(cap_sensor_t *node, uint8_t aGain, uint8_t dGain) { + PTC_CHECK_FOR_BAD_POINTER(node); + +#if defined (__PTC_Tiny__) + if (aGain > 0x05) { + if (__builtin_constant_p(aGain)) { + badArg("Analog Gain too high. Max Analog Gain Value is 0x05 (equals 32x)"); + } + return PTC_LIB_BAD_ARGUMENT; + } +#elif defined (__PTC_DA__) + if (aGain > 0x04) { + if (__builtin_constant_p(aGain)) { + badArg("Analog Gain too high. Max Analog Gain Value is 0x04 (equals 16x)"); + } + return PTC_LIB_BAD_ARGUMENT; + } +#endif + + if (dGain > 0x06) { + if (__builtin_constant_p(dGain)) { + badArg("Digital Gain too high. Max Digital Gain Value is 0x06 (equals 64x)"); + } + return PTC_LIB_BAD_ARGUMENT; + } + node->hw_a_d_gain = NODE_GAIN(aGain, dGain); + return PTC_LIB_SUCCESS; +} + + + +/* + * Two functions to suspend and resume of the normal PTC operation, however, + * this functions will only work when no acquisition is in progress. Neither + * when the library is in low-power mode. This is due to the fact that the + * low-power mode relies on the registers being set once, at the low-power + * initialization. Thus, the user has to disable LP mode, then suspend, resume + * and then re-init low-power mode. + */ +uint8_t ptc_suspend(void) { + if (PTC_LIB_IDLE == ptc_lib_state) { // allow disabling only outside conversions + ptc_lib_state = PTC_LIB_SUSPENDED; + #if defined (__PTC_Tiny__) + PTC.CTRLP = 0x00; + #elif defined (__PTC_DA__) + PTC.CTRLA = 0x00; + #endif + return PTC_LIB_SUCCESS; + } + return PTC_LIB_WRONG_STATE; +} + +void ptc_resume(void) { + if (PTC_LIB_SUSPENDED == ptc_lib_state) { + ptc_lib_state = PTC_LIB_IDLE; + } +} + + +// own implementation of mTC's initADC0(), as the one that comes with the core does not overwrite +// sample number (CTRLB) +void ptc_init_ADC0(void) { + PTC_t *pPTC; + _fastPtr_d(pPTC, &PTC); + #if defined (__PTC_Tiny__) + #if F_CPU > 24000000 + pPTC->CTRLC = ADC_PRESC_DIV32_gc | ADC_REFSEL_VDDREF_gc | ADC_SAMPCAP_bm; + #elif F_CPU >= 12000000 + pPTC->CTRLC = ADC_PRESC_DIV16_gc | ADC_REFSEL_VDDREF_gc | ADC_SAMPCAP_bm; + #elif F_CPU >= 6000000 + pPTC->CTRLC = ADC_PRESC_DIV8_gc | ADC_REFSEL_VDDREF_gc | ADC_SAMPCAP_bm; + #elif F_CPU >= 3000000 + pPTC->CTRLC = ADC_PRESC_DIV4_gc | ADC_REFSEL_VDDREF_gc | ADC_SAMPCAP_bm; + #else + pPTC->CTRLC = ADC_PRESC_DIV2_gc | ADC_REFSEL_VDDREF_gc | ADC_SAMPCAP_bm; + #endif + #if (F_CPU == 6000000 || F_CPU == 12000000 || F_CPU == 24000000 || F_CPU ==25000000) + pPTC->SAMPCTRL = (7); + #elif (F_CPU == 5000000 || F_CPU == 10000000 || F_CPU == 20000000) + pPTC->SAMPCTRL = (13); + #else + pPTC->SAMPCTRL = (10); + #endif + pPTC->CTRLD = ADC_INITDLY_DLY16_gc; + pPTC->CTRLB = ADC_SAMPNUM_ACC1_gc; + pPTC->CTRLA = ADC_ENABLE_bm; + #elif defined (__PTC_DA__) + // PTC can't be used to measure for classical ADC readings (I think) + #endif +} + +uint8_t ptc_add_node(cap_sensor_t *node, uint8_t *pCh, const uint8_t type) { + const uint8_t typesize = sizeof(ptc_ch_arr_t); + + uint8_t hi_node = (uint8_t)((uint16_t)node >> 8); + if ((hi_node < (uint8_t)(RAMSTART >> 8)) || (hi_node >= (uint8_t)(RAMEND >> 8))) { + return PTC_LIB_BAD_POINTER; + } + + + uint8_t status = ptc_append_node(node); + if (status != PTC_LIB_SUCCESS) + return status; + + + memcpy(node->hw_xCh_bm, pCh, typesize * 2); // copies x and y channel + node->type = type; + + #if defined(__PTC_Tiny__) + ptc_ch_bm_t xCh = (ptc_ch_bm_t)pCh[0]; + ptc_ch_bm_t yCh = (ptc_ch_bm_t)pCh[typesize]; + if ((xCh & yCh) != 0) { /* overlap */ + return PTC_LIB_BAD_ARGUMENT; + } + ptc_ch_bm_t pinmask = xCh | yCh; + + if (pinmask == 0) { /* not a single pin selected */ + return PTC_LIB_BAD_ARGUMENT; + } + #elif defined (__PTC_DA__) + uint8_t xCh = 0; + uint8_t yCh = 0; + for (uint8_t i = 0; i < typesize; i++) { + uint8_t orX = pCh[i]; + uint8_t orY = pCh[i + typesize]; + uint8_t overlap = orX & orY; + if (overlap != 0) { + return PTC_LIB_BAD_ARGUMENT; + } + xCh |= orX; + yCh |= orY; + } + if ((xCh | yCh) == 0) { /* not a single pin selected */ + return PTC_LIB_BAD_ARGUMENT; + } + #endif + + node->hw_a_d_gain = NODE_GAIN(0, ADC_SAMPNUM_ACC16_gc); + + if (type & NODE_MUTUAL_bm) { + node->touch_in_th = 10; + node->touch_out_th = 5; + node->hw_compCaps = PTC_DEFAULT_MC_CC; /* value from qTouch */ + node->hw_rsel_presc = NODE_RSEL_PRSC(RSEL_VAL_50, PTC_PRESC_DEFAULT); + } else { /* Selfcap */ + if (yCh == 0) { /* not a single pin selected */ + return PTC_LIB_BAD_ARGUMENT; + } + if (xCh != 0) { + node->type = NODE_SELFCAP_SHIELD_bm; + } + + node->touch_in_th = 20; + node->touch_out_th = 10; + node->hw_compCaps = PTC_DEFAULT_SC_CC; /* value from qTouch */ + node->hw_rsel_presc = NODE_RSEL_PRSC(RSEL_VAL_0, PTC_PRESC_DEFAULT); + } + + #if defined(__PTC_Tiny__) + PTC.PIN_OVR |= pinmask; + uint8_t* pin_lut = (uint8_t* )ptc_ch_to_pin; + for (uint8_t i = 0; i < sizeof(ptc_ch_to_pin); i++) { /* 6 or 14 iterations */ + uint8_t offset = *(pin_lut++); + uint16_t basePointer = (uint16_t)&PORTA; + uint8_t* portSettings = (uint8_t*)(offset + basePointer); + if ((uint8_t)pinmask & 0x01) { + (*portSettings) = PORT_ISC_INPUT_DISABLE_gc; + } + pinmask >>= 1; + } + return PTC_LIB_SUCCESS; + + #elif defined (__PTC_DA__) + /* separate for-loop to avoid changing the PORT settings before checking the complete input */ + uint8_t old_sreg = SREG; + cli(); // disable Interrupts to avoid anyone messing around with PINCONFIG + + PORTA.PINCONFIG = PORT_PULLUPEN_bm | PORT_ISC_INPUT_DISABLE_gc; // this setting is mirrored across all PORTs (guard against ISRs) + uint8_t* pin_upd = (uint8_t*)&(PORTA.PINCTRLUPD); + + uint8_t *end = pCh + typesize; + while (pCh < end) { + if ((uint8_t)(uint16_t) pin_upd != (uint8_t)(uint16_t)&PORTC.PINCTRLUPD) { // double cast to remove warning, skip PORTC + uint8_t pin_bm = *pCh; + pCh += typesize; + pin_bm |= *pCh; + pCh -= (typesize-1); // decrement by one less to increment to next byte + + *pin_upd = pin_bm; + } + pin_upd += sizeof(PORT_t); + } + SREG = old_sreg; + return PTC_LIB_SUCCESS; + #endif +} + + + + +uint8_t ptc_enable_node(cap_sensor_t *node) { + PTC_CHECK_FOR_BAD_POINTER(node); + + node->state.disabled = 0; + return PTC_LIB_SUCCESS; +} + +// Will finish conversion, but not start a new one +uint8_t ptc_disable_node(cap_sensor_t *node) { + PTC_CHECK_FOR_BAD_POINTER(node); + + node->state.disabled = 1; + return PTC_LIB_SUCCESS; +} + + + +// Make the ADC wake up the CPU after the next conversion to allow a drift +uint8_t ptc_lp_force_drift(void) { + if (NULL == lowPowerNode) { + return PTC_LIB_WRONG_STATE; + } + + if (PTC.INTCTRL == ADC_WCMP_bm) { // only do something when we are waiting for a value over the threshold + PTC.INTCTRL = (ADC_RESRDY_bm | ADC_WCMP_bm); + } + + return PTC_LIB_SUCCESS; +} + + + + +// select node type to convert next (Selfcap, Shield, Mutual) +// when not specified, the type of the "firstNode" is always used +void ptc_set_next_conversion_type(ptc_node_type_t type) { + nextConvType = type; +} + +// ev_ch: bitmask of required channels to be used for ADC0/PTC, e.g. channel 2 and 5 -> 0x24 +// this would allow to periodically start ADC0/PIT by a timer without sleep +// user must tell the function which event channel to connect to. +uint8_t ptc_lp_init(cap_sensor_t *node, uint8_t event_ch) { + PTC_CHECK_FOR_BAD_POINTER(node); + + if (NULL != lowPowerNode) { + return PTC_LIB_WRONG_STATE; /* if called while we are already in low power */ + } + + #if defined(__PTC_Tiny__) + EVSYS.ASYNCUSER1 = event_ch; + #elif defined (__PTC_DA__) + EVSYS.USERPTCSTART = event_ch; + #endif + node->state.low_power = 1; + node->stateMachine = PTC_SM_LOW_POWER; + + lowPowerNode = node; + + ptc_init_conversion(node->type); + + return PTC_LIB_SUCCESS; +} + +uint8_t ptc_lp_disable(void) { + if (NULL == lowPowerNode) { + return PTC_LIB_WRONG_STATE; + } + + PTC.INTCTRL = 0; + PTC.EVCTRL = 0; + + lowPowerNode->state.low_power = 0; + lowPowerNode->state.win_comp = 0; + + return PTC_LIB_SUCCESS; +} + +// use this in a conversion complete callback to see if the window comparator was triggered +uint8_t ptc_lp_was_waken(void) { + if (NULL == lowPowerNode) { + return PTC_LIB_WRONG_STATE; + } + + if (1 == lowPowerNode->state.win_comp) { + return PTC_LIB_WAS_WAKEN; + } else { + return PTC_LIB_ASLEEP; + } + + return PTC_LIB_ERROR; +} + + + +/* not recommended to use, as the calculation is bloated */ +uint16_t ptc_get_node_cc_femto(cap_sensor_t *node) { + if (NULL == node) { + return 0; + } + + #if defined (__PTC_Tiny__) + uint16_t retVal = 0; + uint16_t comp = node->hw_compCaps; + for (uint8_t i = 0; i < 3; i++) { + retVal /= 10; /* "skips" last addition */ + uint8_t temp = comp & 0x0F; + retVal += (temp * 675); + comp >>= 4; /* select next field */ + } + retVal += (comp & 0x03) * 6750; + comp >>= 2; + retVal += (comp & 0x03) * 6200; + return retVal; //max 51000 + #elif defined(__PTC_DA__) + uint16_t comp = node->hw_compCaps + 1; // compCaps max 0x3FF + if (node->type != NODE_MUTUAL_bm) + comp *= 2; + + return comp * 31 + (comp >> 2); // *= 31,25 + #else + #endif +} + + +// pass the time, e.g. return value of millis or TCB value to the function instead of having a +// millis in here. improves portability +void ptc_process(uint16_t currTime) { + if ((PTC_LIB_CONV_WCMP | PTC_LIB_CONV_LP) & ptc_lib_state) { + if (NULL != lowPowerNode) { + if (PTC_LIB_CONV_WCMP == ptc_lib_state) { + ptc_event_cb_wake(PTC_CB_EVENT_WAKE_TOUCH, lowPowerNode); + } else { + ptc_event_cb_wake(PTC_CB_EVENT_WAKE_NO_TOUCH, lowPowerNode); + } + ptc_process_measurement(lowPowerNode); + if (NULL == lowPowerNode) { // Due to callbacks, low Power node may have been set to NULL by a disable + currConvType = NODE_TYPE_NOCONV_bm; + ptc_lib_state = PTC_LIB_IDLE; + } else { + PTC.INTCTRL = ADC_WCMP_bm; + ptc_lib_state = PTC_LIB_EVENT; + } + } + } else if (PTC_LIB_CONV_COMPL == ptc_lib_state) { + ptc_lib_state = PTC_LIB_IDLE; + for (cap_sensor_t* node = firstNode; node != NULL; node = node->nextNode) { + ptc_process_measurement(node); + } + ptc_event_cb_conversion((PTC_CB_EVENT_CONV_CMPL | currConvType), (cap_sensor_t *)currConvNode); + currConvType = NODE_TYPE_NOCONV_bm; + } + + if ((currTime - lastAcqTime) >= acqPeriod) { + lastAcqTime += acqPeriod; + if (PTC_LIB_IDLE == ptc_lib_state) { + uint8_t convType; + if (nextConvType == 0) { + convType = firstNode->type; + } else { + convType = nextConvType; + } + ptc_init_conversion(convType); + } + } +} + + + +/* + * Internal functions only below. Not part of the API + */ +void ptc_process_measurement(cap_sensor_t *node) { + if (node->state.error) + return; + + if (node->state.data_ready != 0) { + node->state.data_ready = 0; + ptc_process_node_sm(node); + ptc_event_cb_conversion(PTC_CB_EVENT_CONV_CMPL, node); + } +} + + + +void ptc_process_node_sm(cap_sensor_t *node) { + uint16_t nodeData = node->sensorData; + uint8_t nodeSM = node->stateMachine; + uint8_t lastChange = node->lastStateChange; + uint16_t reference = node->reference; + int16_t nodeDelta = nodeData - reference; + + if (nodeSM == PTC_SM_NOINIT_CAL) { /* Beginning here */ + uint8_t retVal = ptc_process_calibrate(node); + if (PTC_LIB_CALIB_DONE == retVal) { + node->reference = nodeData; + if (node->state.low_power) { + nodeSM = PTC_SM_LOW_POWER; + } else { + nodeSM = PTC_SM_NO_TOUCH; + } + ptc_event_cb_calibration(PTC_CB_EVENT_CONV_CALIB, node); + } else if (PTC_LIB_SUCCESS != retVal || lastChange > 10) { // if we stay here for over 10 conversions, something didn't went right + node->state.error = 1; + if (PTC_LIB_CALIB_TOO_LOW == retVal) { + ptc_event_cb_calibration(PTC_CB_EVENT_ERR_CALIB_LOW, node); + } else if (PTC_LIB_CALIB_TOO_HIGH == retVal) { + ptc_event_cb_calibration(PTC_CB_EVENT_ERR_CALIB_HIGH, node); + } else { + ptc_event_cb_calibration(PTC_CB_EVENT_ERR_CALIB_TO, node); + } + return; + } else if (PTC_LIB_SUCCESS == retVal) { + // calibration didn't finish (yet), but didn't threw an error + } + + } else if (nodeSM & PTC_SM_RECAL_FLT) { /* if the delta was really low, we might have to recalibrate the caps */ + if (nodeDelta > -150) { + nodeSM = PTC_SM_NO_TOUCH; + } else if (lastChange > 3) { + nodeSM = PTC_SM_NOINIT_CAL; + } + + + } else if (nodeSM & PTC_SM_NT_LOW_FLT) { /* no touch, delta below -1, threshold drift */ + if (nodeDelta > 0) { + nodeSM = PTC_SM_NO_TOUCH; + } else if (nodeDelta < -150) { + nodeSM = PTC_SM_RECAL_FLT; + } else if (lastChange > ptc_sm_settings.drift_down_nom) { /* By using '>' the drift can be disabled with integer overflow */ + node->reference--; + nodeSM = PTC_SM_NO_TOUCH; + } + + + } else if (nodeSM & PTC_SM_NO_TOUCH) { /* default State, no touch */ + if (nodeDelta < -150) { /* if the touch value is way too small, something changed with the lines, recalibrate */ + nodeSM = PTC_SM_RECAL_FLT; + } else if (nodeDelta >= node->touch_in_th) { + nodeSM = PTC_SM_TOUCH_IN_FLT; + } else if (reference <= (512 - ptc_sm_settings.force_recal_delta)) { + nodeSM = PTC_SM_NOINIT_CAL; + } else if (reference >= (512 + ptc_sm_settings.force_recal_delta)) { + nodeSM = PTC_SM_NOINIT_CAL; + } else if (nodeDelta < -1) { + nodeSM = PTC_SM_NT_LOW_FLT; + } else if (nodeDelta > 1) { + nodeSM = PTC_SM_NT_HIGH_FLT; + } + + + } else if (nodeSM & PTC_SM_NT_HIGH_FLT) { + if (nodeDelta < 0) { + nodeSM = PTC_SM_NO_TOUCH; + } else if (nodeDelta >= node->touch_in_th) { + nodeSM = PTC_SM_TOUCH_IN_FLT; + } else if (lastChange > ptc_sm_settings.drift_up_nom) { + node->reference++; + nodeSM = PTC_SM_NO_TOUCH; + } + + + } else if (nodeSM & PTC_SM_TOUCH_IN_FLT) { + if (lastChange >= ptc_sm_settings.touched_detect_nom) { + nodeSM = PTC_SM_TOUCH_DETECT; + ptc_event_cb_touch(PTC_CB_EVENT_TOUCH_DETECT, node); + } else if (nodeDelta < node->touch_in_th) { + nodeSM = PTC_SM_NO_TOUCH; + } + + + } else if (nodeSM & PTC_SM_TOUCH_OUT_FLT) { + if (lastChange >= ptc_sm_settings.untouched_detect_nom) { + nodeSM = PTC_SM_NO_TOUCH; + ptc_event_cb_touch(PTC_CB_EVENT_TOUCH_RELEASE, node); + } else if (nodeDelta > node->touch_in_th) { + nodeSM = PTC_SM_TOUCH_DETECT; + } + + + } else if (nodeSM & PTC_SM_TOUCH_DETECT) { + if (lastChange > ptc_sm_settings.touched_max_nom) { + nodeSM = PTC_SM_NOINIT_CAL; + } else if (nodeDelta < node->touch_out_th) { + nodeSM = PTC_SM_TOUCH_OUT_FLT; + } + + + } else if (nodeSM & PTC_SM_LOW_POWER) { + if (0 == node->state.low_power) { + if (nodeDelta > node->touch_in_th) { + nodeSM = PTC_SM_TOUCH_DETECT; + } else { + nodeSM = PTC_SM_NO_TOUCH; + } + lowPowerNode = NULL; + } + if (nodeDelta < -1) { + node->reference--; + PTC.WINHT--; + } else if (nodeDelta > 1) { + node->reference++; + PTC.WINHT++; + } + + + } + + + if (node->stateMachine != nodeSM) { + node->lastStateChange = 0; + node->stateMachine = nodeSM; + } else { + node->lastStateChange = lastChange + 1; + } +} + + + +uint8_t ptc_process_calibrate(cap_sensor_t *node) { + uint16_t rawData = node->sensorData; + + #if defined(__PTC_Tiny__) + uint16_t compensation = node->hw_compCaps; + uint8_t cc_accurate = compensation & 0x0F; + uint8_t cc_fine = ((uint8_t) compensation >> 4) & 0x0F; + uint8_t cc_coarse = (uint8_t)(compensation >> 8) & 0x0F; + uint8_t cc_add_rough = (uint8_t)(compensation >> 8) & 0xC0; + uint8_t cc_rough = (uint8_t)(compensation >> 12) & 0x03; + + //uint8_t err = 0; + + int8_t dirOvf; + int8_t dir; + if (rawData > 0x01FF) { + rawData = rawData - 0x1FF; + if (rawData < 4) { + return PTC_LIB_CALIB_DONE; + } + + if (node->type & NODE_MUTUAL_bm){ + dir = -1; + dirOvf = -6; + rawData /= 2; + } else { + dir = 1; + dirOvf = 6; + if (rawData > 0x0100) { + if (cc_add_rough <= 0xC0) { + cc_add_rough = (cc_add_rough + 0x40); + rawData -= 0xF0; + } + } + } + } else { + rawData = 0x1FF - rawData; + if (rawData < 4) { + return PTC_LIB_CALIB_DONE; + } + + if (node->type & NODE_MUTUAL_bm){ + dir = 1; + dirOvf = 6; + rawData /= 2; + } else { + dir = -1; + dirOvf = -6; + if (rawData > 0x0100) { + if (cc_add_rough >= 0x40) { + cc_add_rough = (cc_add_rough - 0x40); + rawData -= 0xF0; + } + } + } + } + + while (rawData > 0x0001) { + while (rawData > 0x00CF) { + cc_rough += dir; // this algorithm takes advantage of integer underflow + if (cc_rough > 0x03) { // by checking against >0x03, we can also check for 0xFF aka -1 + cc_rough -= dir; // thus saving some flash. +/-1 can be made in one insn + break; // by using sub reg with 0xFF or 0x01 in a reg respectively + } + rawData -= 0xCF; + } + + while (rawData > 0x0015) { + cc_coarse += dir; + if (cc_coarse > 0x0F) + break; + rawData -= 0x15; + } + if (cc_coarse > 0x0F) { + cc_rough += dir; + if (cc_rough > 0x03) { + cc_rough -= dir; + cc_coarse -= dir; + } else { + cc_coarse -= dirOvf; + } + } + + + while (rawData > 0x0001) { + cc_fine += dir; + if (cc_fine > 0x0F) + break; + rawData -= 0x02; + } + + if (cc_fine > 0x0F) { + cc_coarse += dir; + if (cc_coarse > 0x0F) { + cc_rough += dir; + if (cc_rough > 0x03) { + cc_rough -= dir; + cc_coarse -= dir; + cc_fine -= dir; + if (dir < 0) + return PTC_LIB_CALIB_TOO_LOW; + else + return PTC_LIB_CALIB_TOO_HIGH; + } else { + cc_coarse -= dirOvf; + } + } else { + cc_fine -= dirOvf; + } + } /* if (cc_fine > 0x0F) */ + } /* (rawData > 0x0001) */ + + + cc_fine <<= 4; + cc_rough <<= 4; + cc_rough |= cc_add_rough; + cc_rough |= cc_coarse; + cc_fine |= cc_accurate; + node->hw_compCaps = (uint16_t)((cc_rough << 8) | cc_fine); + return PTC_LIB_SUCCESS; + + #elif defined (__PTC_DA__) + uint16_t delta; + uint16_t newCC = node->hw_compCaps; + uint8_t ret_val = PTC_LIB_SUCCESS; + + if (node->type & NODE_MUTUAL_bm) { + if (rawData > 0x1FF) { + delta = (rawData - 0x1FF) >> 2; + newCC -= delta; + } else { + delta = (0x1FF - rawData) >> 2; + newCC += delta; + } + } else { + if (rawData > 0x1FF) { + delta = (rawData - 0x1FF) >> 1; + newCC += delta; + } else { + delta = (0x1FF - rawData) >> 1; + newCC -= delta; + } + } + + if (delta < 4) + return PTC_LIB_CALIB_DONE; + + if (newCC > 0x03FF) { + if (newCC > 0x7FFF) { // underflow + newCC = 0x00; + ret_val = PTC_LIB_CALIB_TOO_LOW; + } else { // overflow + newCC = 0x3FF; + ret_val = PTC_LIB_CALIB_TOO_HIGH; + } + } + node->hw_compCaps = newCC; + return ret_val; + #endif +} + + +void ptc_init_conversion(uint8_t nodeType) { + PTC_t *pPTC; + _fastPtr_d(pPTC, &PTC); + + if (ptc_lib_state & (PTC_LIB_CONV_PROG | PTC_LIB_CONV_LP | PTC_LIB_CONV_WCMP | PTC_LIB_SUSPENDED)) { + return; + } + +#if defined (__PTC_Tiny__) + pPTC->CTRLP = 0x00; + if (nodeType & NODE_MUTUAL_bm) { /* NODE_MUTUAL */ + pPTC->CTRLP = 0xC0 | 0x20; + pPTC->SHIELD = 0x00; + } else if (nodeType & NODE_SHIELD_bm){ /* NODE_SELFCAP */ + pPTC->CTRLP = 0x28; + pPTC->SHIELD = 0x86; + } else if (nodeType & NODE_SELFCAP_bm) { + pPTC->CTRLP = 0x28; + pPTC->SHIELD = 0x00; + } else { + return; + } + + pPTC->CTRLA = 0x00; + + uint8_t freq = freq_select; + if (freq > 0x0F) { /* FREQ_SELECT_15 */ + freq = ADC_ASDV_bm; + } + + pPTC->CTRLD = freq; + pPTC->INTFLAGS = ADC_RESRDY_bm | ADC_WCMP_bm; // clear ISR flags, if there were unhandled + + currConvType = nodeType; + if (NULL != lowPowerNode) { + pPTC->INTCTRL = ADC_WCMP_bm; // Wakeup only above of window + pPTC->CTRLE = ADC_WINCM_ABOVE_gc; + pPTC->WINHT = (lowPowerNode->reference + lowPowerNode->touch_in_th) << (lowPowerNode->hw_a_d_gain & 0x0F); + ptc_lib_state = PTC_LIB_EVENT; + ptc_start_conversion(lowPowerNode); + } else { + pPTC->INTCTRL = ADC_RESRDY_bm; + pPTC->CTRLE = ADC_WINCM_NONE_gc; + ptc_lib_state = PTC_LIB_CONV_PROG; + ptc_start_conversion(firstNode); + } + +#elif defined (__PTC_DA__) + + if (nodeType == NODE_MUTUAL_bm) { + pPTC->CTRL_SC = 0x00; + pPTC->CTRLA = 0x00; + } else if (nodeType & NODE_SELFCAP_bm) { + pPTC->CTRL_SC = 0x01; + pPTC->CTRLA = 0x00; + } else { + return; + } + + + uint8_t freq = freq_select; + if (nodeType == NODE_MUTUAL_bm) { + pPTC->SAMPDLY = 0x00; + if (freq < 0x10) /* freq is 0 - 0x0F, 0x10 is auto variation */ + freq |= ADC_INITDLY_DLY16_gc; + else + freq = ADC_INITDLY_DLY16_gc | ADC_SAMPDLY_DLY15_gc; + pPTC->CTRLD = freq; + } else { + pPTC->CTRLD = ADC_INITDLY_DLY16_gc; + if (freq > 0x0F) + freq = 0x0F; + pPTC->SAMPDLY = freq; + } + + pPTC->INTFLAGS = ADC_RESRDY_bm | ADC_WCMP_bm; // clear ISR flags, if they were unhandled + + currConvType = nodeType; + if (NULL != lowPowerNode) { + pPTC->INTCTRL = ADC_WCMP_bm; // Wakeup only above of window + pPTC->CTRLE = ADC_WINCM_ABOVE_gc; + pPTC->WINHT = (lowPowerNode->reference + lowPowerNode->touch_in_th) << (lowPowerNode->hw_a_d_gain & 0x0F); + ptc_lib_state = PTC_LIB_EVENT; + ptc_start_conversion(lowPowerNode); + } else { + pPTC->INTCTRL = ADC_RESRDY_bm; + pPTC->CTRLE = ADC_WINCM_NONE_gc; + ptc_lib_state = PTC_LIB_CONV_PROG; + ptc_start_conversion(firstNode); + } +#endif +} + +void ptc_start_conversion(cap_sensor_t* node) { + while (1) { + if (NULL == node) { + PTC.INTCTRL = 0; // disable ISR for ADC reuse + ptc_lib_state = PTC_LIB_CONV_COMPL; + return; + } + if ((0 == node->state.disabled) && (node->type == currConvType)) { + break; + } else { + node = node->nextNode; + } + } + + currConvNode = node; + + ptc_set_registers(node); +} + +void ptc_set_registers(cap_sensor_t* node) { + PTC_t *pPTC; + _fastPtr_d(node, node); // Sometimes it takes the compiler a bit more of convincing... + _fastPtr_d(pPTC, &PTC); + + if (NULL == node) + return; + + uint8_t lut_index = 0; + if ((node->state.disabled == 0) && (node->stateMachine != PTC_SM_NOINIT_CAL)) { + lut_index = node->hw_a_d_gain / 16; // A little workaround as >> 4 is kinda broken sometimes. + } + uint8_t analogGain = ptc_a_gain_lut[lut_index]; + + uint8_t chargeDelay = node->hw_csd; + + #if defined(__PTC_Tiny__) + pPTC->XBM = node->hw_xCh_bm[0]; + pPTC->YBM = node->hw_yCh_bm[0]; + + if (chargeDelay < 0x1B) { + chargeDelay += 4; + } else { + chargeDelay = 0x1F; + } + pPTC->SAMPCTRL = chargeDelay; + + + pPTC->CTRLC = (node->hw_rsel_presc & 0x0F) | ADC_REFSEL_VDDREF_gc; + pPTC->CTRLP |= 0x03; + + #elif defined(__PTC_DA__) + memcpy((void *)pPTC->XBM, node->hw_xCh_bm, sizeof(ptc_ch_arr_t)); + memcpy((void *)pPTC->YBM, node->hw_yCh_bm, sizeof(ptc_ch_arr_t)); + + if (chargeDelay < 0x7B) { + chargeDelay += 4; + } else { + chargeDelay = 0x1F; + } + + pPTC->SAMPCTRL = chargeDelay; + pPTC->CTRLC = (node->hw_rsel_presc & 0x0F) | 0x80; + + if (node->type & NODE_SELFCAP_bm) { + pPTC->CTRL_SC = 0x01; + } else { + pPTC->CTRL_SC = 0x00; + } + if (node->type & NODE_SHIELD_bm) { + pPTC->CTRL_SHIELD = 1; + } else { + pPTC->CTRL_SHIELD = 0; + } + #endif + + pPTC->COMP = node->hw_compCaps; + pPTC->AGAIN = analogGain; + pPTC->CTRLB = node->hw_a_d_gain & 0x0F; + pPTC->RSEL = node->hw_rsel_presc / 16; + + pPTC->CTRLA = ADC_RUNSTBY_bm | ADC_ENABLE_bm; /* 0x81 */ + + if (0 == node->state.low_power) { + pPTC->COMMAND = 0x01; // Normal operation: Manual Start + } else { + pPTC->EVCTRL = 0x01; // Low Power: Start by positive flank on event + } +} + + + + +// returns the last node in the list, or NULL if list empty. +cap_sensor_t *ptc_get_last_node(void) { + if (firstNode == NULL) { + return NULL; + } + + cap_sensor_t *node = firstNode; + while (1) { + cap_sensor_t *nextNode = node->nextNode; + if (nextNode == NULL) { + return node; + } else { + node = nextNode; + } + } +} + + +// puts the node to the back of the single-linked list. +uint8_t ptc_append_node(cap_sensor_t *pNewNode) { + + cap_sensor_t *lastNode = ptc_get_last_node(); + if (lastNode == NULL) { + firstNode = pNewNode; + pNewNode->id = 0; + } else if (lastNode != pNewNode) { // Make sure we don't create an endless loop on accident + lastNode->nextNode = pNewNode; + pNewNode->id = lastNode->id + 1; + } else { + return PTC_LIB_ERROR; + } + return PTC_LIB_SUCCESS; +} + +void ptc_eoc(void) { + PTC_t *pPTC; + volatile cap_sensor_t *pCurrentNode; // volatile needed to pass type check + _fastPtr_d(pPTC, &PTC); + _fastPtr_d(pCurrentNode, currConvNode); + + if (NULL == pCurrentNode) { + return; + } + + pPTC->CTRLA = 0x00; + uint8_t flags = pPTC->INTFLAGS; // save the flags before they get cleared by RES read + uint16_t rawVal = pPTC->RES; // clears ISR flags + uint8_t oversampling = pCurrentNode->hw_a_d_gain & 0x0F; + pCurrentNode->sensorData = rawVal >> oversampling; + + //currConvNode->sensorData = pPTC->RES_TRUE; + pCurrentNode->state.data_ready = 1; + + if (pCurrentNode->state.low_power) { + if (flags & ADC_WCMP_bm) { + pCurrentNode->state.win_comp = 1; + ptc_lib_state = PTC_LIB_CONV_WCMP; + } else { + pCurrentNode->state.win_comp = 0; + ptc_lib_state = PTC_LIB_CONV_LP; + } + + } else { + ptc_start_conversion(pCurrentNode->nextNode); + } +} + + +#if defined(__PTC_Tiny__) +ISR(ADC0_RESRDY_vect) { + ptc_eoc(); +} + +ISR(ADC0_WCOMP_vect, ISR_ALIASOF(ADC0_RESRDY_vect)); + +#elif defined(__PTC_DA__) +ISR(PTC_PTC_vect) { + ptc_eoc(); +} +#endif diff --git a/megaavr/libraries/PTC/src/ptc.h b/megaavr/libraries/PTC/src/ptc.h new file mode 100644 index 00000000..2631cb25 --- /dev/null +++ b/megaavr/libraries/PTC/src/ptc.h @@ -0,0 +1,301 @@ +/* + library to use the PTC module in AVR devices + Copyright (c) 2023, MX682X + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + This license applies to all files that are part of this library + (ptc.c, ptc.h, ptc_io.h, ptc_types.h) +*/ + + + +#pragma once +#ifndef PTC_H +#define PTC_H + +#if defined(MEGATINYCORE) || defined(DXCORE) + #include +#else + #include + #ifndef _fastPtr_d + #define _fastPtr_d(_x_, _y_) _x_ = _y_; + #endif +#endif + +#include "ptc_types.h" +#include "ptc_io.h" + + +#ifndef NULL + #define NULL (void*)0 +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define NODE_GAIN(_a_, _d_) (uint8_t)(((_a_) << 4) | ((_d_) & 0x0F)) +#define NODE_RSEL_PRSC(_r_, _p_) (uint8_t)(((_r_) << 4) | ((_p_) & 0x0F)) +#define NUM_TO_BM(__n__) (0x01 << __n__) + + + + + + +#define PTC_CHECK_FOR_BAD_POINTER(__p__) \ + if (NULL == __p__) { \ + if (__builtin_constant_p(__p__)) { \ + badArg("Null pointer detected"); \ + } \ + return PTC_LIB_BAD_POINTER; \ + } + + +void badArg(const char *) __attribute__((weak, error(""))); +void badCall(const char *) __attribute__((weak, error(""))); + + + + +//extern void ptc_conversion_complete(uint8_t type); +//extern void ptc_error_callback(uint8_t source, cap_sensor_t *node); +extern void ptc_event_callback(const ptc_cb_event_t eventType, cap_sensor_t *node); + +extern void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t *node); +extern void ptc_event_cb_wake(const ptc_cb_event_t eventType, cap_sensor_t *node); +extern void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t *node); +extern void ptc_event_cb_calibration(const ptc_cb_event_t eventType, cap_sensor_t *node); +extern void ptc_event_cb_error(const ptc_cb_event_t eventType, cap_sensor_t *node); + + +// Enables the node. Can be called while an acquisition is in progress. +uint8_t ptc_enable_node(cap_sensor_t *node); + +// Disables a node. If the conversion is started, it will be finished +uint8_t ptc_disable_node(cap_sensor_t *node); + +// Can be used outside an acquisition process to select ADC/SELFCAP/MUTUAL/SHIELD +void ptc_set_next_conversion_type(ptc_node_type_t type); + +// Main task handle for PTC. Handles State-Machine, drift, and calibration +void ptc_process(uint16_t currTime); + + +// Set the threshold for touch detection and away from touch for a node +uint8_t ptc_node_set_thresholds(cap_sensor_t *node, int16_t th_in, int16_t th_out); + + +// Change Resistor Setting. Note: Only has an effect on mutual sensors +uint8_t ptc_node_set_resistor(cap_sensor_t *node, uint8_t res); + +// Change prescaler. +uint8_t ptc_node_set_prescaler(cap_sensor_t *node, uint8_t presc); + +uint8_t ptc_node_set_gain(cap_sensor_t *node, uint8_t aGain, uint8_t dGain); + +// this is an internal function, there is no sense in calling it directly +uint8_t ptc_add_node(cap_sensor_t *node, uint8_t *pCh, const uint8_t type); + + +inline uint8_t ptc_add_selfcap_node(cap_sensor_t *node, const ptc_ch_bm_t xCh, const ptc_ch_bm_t yCh) { + if(__builtin_constant_p(yCh)) { + if (yCh == 0) { + badArg("yCh bitmap mustn't be 0 (Pin_Pxn is not a PTC pin)"); + } + if (yCh & xCh) { + badArg("pin bitmap overlap detected"); + } + } + + // this places only the significant number of bits on stack. Significantly reduces register pressure on DAs + const uint8_t tsize = sizeof(ptc_ch_arr_t); + uint8_t pCh[tsize * 2]; + + pCh[0] = (uint8_t)(xCh >> 0); + pCh[0 + tsize] = (uint8_t)(yCh >> 0); + + #if __PTC_Pincount__ >= 8 + pCh[1] = (uint8_t)(xCh >> 8); + pCh[1 + tsize] = (uint8_t)(yCh >> 8); + + #if __PTC_Pincount__ >= 16 + pCh[2] = (uint8_t)(xCh >> 16); + pCh[2 + tsize] = (uint8_t)(yCh >> 16); + pCh[3] = (uint8_t)(xCh >> 24); + pCh[3 + tsize] = (uint8_t)(yCh >> 24); + pCh[4] = (uint8_t)(xCh >> 32); + pCh[4 + tsize] = (uint8_t)(yCh >> 32); + + #if __PTC_Pincount__ >= 40 + pCh[5] = (uint8_t)(xCh >> 40); + pCh[5 + tsize] = (uint8_t)(yCh >> 40); + + #endif + #endif + #endif + + return ptc_add_node(node, pCh, NODE_SELFCAP_bm); +}; + + +inline uint8_t ptc_add_mutualcap_node(cap_sensor_t *node, const ptc_ch_bm_t xCh, const ptc_ch_bm_t yCh) { + if(__builtin_constant_p(yCh) && __builtin_constant_p(xCh)) { + if (yCh == 0) { + badArg("yCh bitmap mustn't be 0 (Pin_Pxn is not a PTC pin)"); + } + if (xCh == 0) { + badArg("xCh bitmap mustn't be 0 (Pin_Pxn is not a PTC pin)"); + } + if (yCh & xCh) { + badArg("pin overlap detected"); + } + } + + // this places only the significant number of bits on stack. Significantly reduces register pressure on DAs + const uint8_t tsize = sizeof(ptc_ch_arr_t); + uint8_t pCh[tsize * 2]; + + pCh[0] = (uint8_t)(xCh >> 0); + pCh[0 + tsize] = (uint8_t)(yCh >> 0); + + #if __PTC_Pincount__ >= 8 + pCh[1] = (uint8_t)(xCh >> 8); + pCh[1 + tsize] = (uint8_t)(yCh >> 8); + + #if __PTC_Pincount__ >= 16 + pCh[2] = (uint8_t)(xCh >> 16); + pCh[2 + tsize] = (uint8_t)(yCh >> 16); + pCh[3] = (uint8_t)(xCh >> 24); + pCh[3 + tsize] = (uint8_t)(yCh >> 24); + pCh[4] = (uint8_t)(xCh >> 32); + pCh[4 + tsize] = (uint8_t)(yCh >> 32); + + #if __PTC_Pincount__ >= 40 + pCh[5] = (uint8_t)(xCh >> 40); + pCh[5 + tsize] = (uint8_t)(yCh >> 40); + + #endif + #endif + #endif + + return ptc_add_node(node, pCh, NODE_MUTUAL_bm); +}; + + + +uint8_t ptc_suspend(void); +void ptc_resume(void); + +// If you want to know the compensation capacitance in femto Farad +uint16_t ptc_get_node_cc_fempto(cap_sensor_t *node); + +ptc_lib_sm_set_t *ptc_get_sm_settings(); + +// X and Y channel bitmasks +inline ptc_ch_bm_t ptc_get_node_xCh_bm(cap_sensor_t *node) { + if (node == NULL) { + return 0x00; + } + ptc_ch_bm_t retval = 0; + memcpy(&retval, node->hw_xCh_bm, sizeof(node->hw_xCh_bm)); + return retval; +} + +inline ptc_ch_bm_t ptc_get_node_yCh_bm(cap_sensor_t *node) { + if (node == NULL) { + return 0x00; + } + ptc_ch_bm_t retval = 0; + memcpy(&retval, node->hw_yCh_bm, sizeof(node->hw_yCh_bm)); + return retval; +} + +// the measured PTC value. 512 is 0. 0x00 means BAD_POINTER +inline uint16_t ptc_get_node_sensor_value(cap_sensor_t *node) { + if (node == NULL) { + return 0x00; + } + return node->sensorData; +} + +// returns true, if node is a valid pointer and node is touched, otherwise false. +// No other return value so there can be easy checks - not null or null +inline uint8_t ptc_get_node_touched(cap_sensor_t *node) { + if (node == NULL) { + return 0x00; + } + + if (node->stateMachine & (PTC_SM_TOUCH_DETECT | PTC_SM_TOUCH_OUT_FLT)) { + return 0x01; + } + return 0x00; +} + + +inline uint8_t ptc_get_node_sm(cap_sensor_t *node) { + PTC_CHECK_FOR_BAD_POINTER(node); + return node->stateMachine; +} + + +inline int16_t ptc_get_node_delta(cap_sensor_t *node) { + if (node == NULL) { + return 0x8000; //-32k - impossible value for normal operation + } + return (node->sensorData - node->reference); +} + +inline uint8_t ptc_get_node_state(cap_sensor_t *node) { + if (node == NULL) { + return 0xFF; + } + return (node->stateMachine); +} + +inline ptc_id_t ptc_get_node_id(cap_sensor_t *node) { + if (node == NULL) { + return 0xFF; + } + return (node->id); +} + + +inline uint8_t ptc_node_request_recal(cap_sensor_t *node) { + PTC_CHECK_FOR_BAD_POINTER(node); + + node->stateMachine = PTC_SM_NOINIT_CAL; + node->lastStateChange = 0; + return PTC_LIB_SUCCESS; +} + + +void ptc_init_ADC0(void); +uint8_t ptc_lp_init(cap_sensor_t *node, uint8_t event_ch); +uint8_t ptc_lp_disable(void); +uint8_t ptc_lp_was_waken(void); + +// Called by the interrupt routine. Saves result and selects next node +extern void ptc_eoc(void); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/megaavr/libraries/PTC/src/ptc_io.h b/megaavr/libraries/PTC/src/ptc_io.h new file mode 100644 index 00000000..f5c1ba35 --- /dev/null +++ b/megaavr/libraries/PTC/src/ptc_io.h @@ -0,0 +1,424 @@ +/* + * Refer to ptc.h file for copyright, changelog, usage and license information + */ + +#pragma once +#ifndef PTC_IO_H +#define PTC_IO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ptc_types.h" + +#if defined (__PTC_Tiny__) + +typedef struct PTC_struct { + register8_t CTRLA; /* Control A */ + register8_t CTRLB; /* Control B */ + register8_t CTRLC; /* Control C */ + register8_t CTRLD; /* Control D */ + register8_t CTRLE; /* Control E */ + register8_t SAMPCTRL; /* Sample Control */ + register8_t MUXPOS; /* Positive mux input */ + register8_t reserved_0; + register8_t COMMAND; /* Command */ + register8_t EVCTRL; /* Event Control */ + register8_t INTCTRL; /* Interrupt Control */ + register8_t INTFLAGS; /* Interrupt Flags */ + register8_t DBGCTRL; /* Debug Control */ + register8_t TEMP; /* Temporary Data */ + _WORDREGISTER(reserved_1); + _WORDREGISTER(RES); /* ADC Accumulator Result */ + _WORDREGISTER(WINLT); /* Window comparator low threshold */ + _WORDREGISTER(WINHT); /* Window comparator high threshold */ + register8_t CALIB; /* Calibration */ + register8_t reserved_2; /* +0x17 */ + register8_t CTRLP; /* +0x18 OR with 0x03 to enable, written 0x28 for Selfcap */ + register8_t RSEL; /* +0x19 */ + _WORDREGISTER(COMP); /* +0x1A */ + register8_t AGAIN; /* +0x1C */ + register8_t reserved_3; /* +0x1D */ + register8_t SHIELD; /* +0x1E if enabled, written 0x86, otherwise 0x00 */ + register8_t reserved_4; /* +0x1F */ + _WORDREGISTER(RES_PTC); /* +0x20 Some Result, written by PTC. Seems to be RES, but left-shifted, or result from PTC accumulated by ADC */ + _WORDREGISTER(PIN_OVR); /* +0x22 all X and Y pins OR'd together at init. Pin Function Overwrite Probably*/ + _WORDREGISTER(reserved_5); /* +0x24 */ + _WORDREGISTER(XBM); /* +0x26 amount of writeable bits depends on chip family */ + _WORDREGISTER(reserved_6); /* +0x28 */ + _WORDREGISTER(YBM); /* +0x2A e.g. 0x3FFF (15 pins) for 1614 with only 6 PTC pins */ + _WORDREGISTER(reserved_7);; /* +0x2C */ +} PTC_t; + +#define PTC (*(PTC_t *) 0x0600) /* Analog to Digital Converter */ +#define RSEL_MAX RSEL_VAL_200 +#define PRSC_MAX ADC_PRESC_DIV256_gc + +#if F_CPU >= 12000000 // 16 MHz / 16 = 1.0 MHz, 20 MHz / 16 = 1.25 MHz + #define PTC_PRESC_DEFAULT ADC_PRESC_DIV16_gc +#elif F_CPU >= 6000000 // 8 MHz / 8 = 1.0 MHz, 10 MHz / 8 = 1.25 MHz + #define PTC_PRESC_DEFAULT ADC_PRESC_DIV8_gc +#elif F_CPU >= 3000000 // 4 MHz / 4 = 1.0 MHz, 5 MHz / 4 = 1.25 MHz + #define PTC_PRESC_DEFAULT ADC_PRESC_DIV4_gc +#else // 1 MHz / 2 = 500 kHz - the lowest setting + #define PTC_PRESC_DEFAULT ADC_PRESC_DIV2_gc +#endif + +#elif defined (__PTC_DA__) + +typedef struct PTC_struct { + register8_t CTRLA; /* 0xC0 [0x81] Control A (RUNSTDBY, ENABLE (0x81))*/ + register8_t CTRL_SC; /* 0xC1 [0x07] 0x01: Selfcap_EN, */ + register8_t CTRLC; /* 0xC2 [0x87] Control C ((node_rsel_prsc & 0x0F) | 0x80 (SampCap? VREF?))*/ + register8_t CTRLD; /* 0xC3 [0x6F] Control D (Max: 0x6F) Is always OR'd with 0x20 or limited to 0x2F */ + register8_t SAMPCTRL; /* 0xC4 [0x7F] SAMPCTRL (Max: 0x7F) */ + register8_t SAMPDLY; /* 0xC5 [0x7F] 0~15 Inserted ADC clocks (freq_option_select) */ + register8_t reserved_0; /* 0xC6 [0xFF] unused */ + register8_t CTRLB; /* 0xC7 [0x07] SAMPNUM (node_oversampling) */ + + register8_t CTRLE; /* 0xC8 [0x07] WINCM probably */ + register8_t RSEL; /* 0xC9 [0x77] Resistor Setting (node_rsel_prsc >> 4) */ + register8_t CC_EN; /* 0xCA [0x01] ?? */ + register8_t CTRL_BOOST; /* 0xCB [0xFF] set to 0x09 if NODE_SELFCAP_SHIELD_2L */ + _DWORDREGISTER(reserved_1); /* 0xCC [0x00] unwritable */ + + register8_t COMMAND; /* 0xD0 [0x01] Command */ + register8_t EVCTRL; /* 0xD1 [0x01] Event Control */ + register8_t INTCTRL; /* 0xD2 [0x07] Interrupt Control: 0x01 - RESRDY, 0x02 - WINCMP */ + register8_t INTFLAGS; /* 0xD3 [0x07] Interrupt Flags */ + register8_t reserved_2; /* 0xD4 [0x02] unknown */ + register8_t DEBUGCTRL; /* 0xD5 [0x01] DBGRUN */ + register8_t reserved_3; /* 0xD6 [0x00] unwritable */ + register8_t STATUS; /* 0xD7 [0x00] 0x20 during operation, unwritable */ + + _WORDREGISTER(reserved_4); /* 0xD8 [0x00] some other result */ + _WORDREGISTER(RES); /* 0xDA [0x00] ADC Result register */ + _WORDREGISTER(WINLT); /* 0xDC [0xFF] Window comparator low threshold */ + _WORDREGISTER(WINHT); /* 0xDD [0xFF] Window comparator high threshold */ + + register8_t CTRL_SHIELD; /* 0xE0 [0x00] Strobing Register, changes between Shield and Mutual ?*/ + register8_t SOME_RES; /* 0xE1 [0x81] some unknown result value */ + _WORDREGISTER(reserved_5); /* 0xE2 [0x00] unwritable */ + _WORDREGISTER(COMP); /* 0xE4 [0x3FF] Compensation */ + register8_t AGAIN; /* 0xE6 [0x1F] Analog Gain */ + register8_t reserved_6; /* 0xE7 [0x00] unwritable */ + + register8_t XBM[6]; /* 0xE8 X Channel bitmask */ + register8_t reserved_7[2]; /* 0xEE [0x00] unused */ + + register8_t YBM[6]; /* 0xF0 Y Channel bitmask */ + register8_t reserved_8[2]; /* 0xF6 [0x00] unused */ + + register8_t XBM_4P[6]; /* 0xF8 X Channel bm when using Boost mode, unused */ + register8_t reserved_9[2]; /* 0xFE [0x00] unused */ +} PTC_t; + +#define PTC (*(PTC_t *) 0x07C0) /* Analog to Digital Converter */ +#define RSEL_MAX RSEL_VAL_200 +#define PRSC_MAX PTC_PRESC_DIV16_gc + +#if F_CPU <= 400000 + #define PTC_PRESC_DEFAULT PTC_PRESC_DIV2_gc +#elif F_CPU <= 800000 + #define PTC_PRESC_DEFAULT PTC_PRESC_DIV4_gc +#elif F_CPU <= 1200000 + #define PTC_PRESC_DEFAULT PTC_PRESC_DIV6_gc +#elif F_CPU <= 1600000 + #define PTC_PRESC_DEFAULT PTC_PRESC_DIV8_gc +#elif F_CPU <= 2000000 + #define PTC_PRESC_DEFAULT PTC_PRESC_DIV10_gc +#elif F_CPU <= 2400000 + #define PTC_PRESC_DEFAULT PTC_PRESC_DIV12_gc +#elif F_CPU <= 2800000 + #define PTC_PRESC_DEFAULT PTC_PRESC_DIV14_gc +#else + #define PTC_PRESC_DEFAULT PTC_PRESC_DIV16_gc +#endif +#else + #warning "Neither __PTC_Tiny__ nor __PTC_DA__ defined" +#endif + + + + + + + + +#define PIN_TO_PTC(__pin__) (((__pin__) < NUM_TOTAL_PINS ) ? digital_pin_to_ptc_bm[__pin__] : 0x00) +static const ptc_ch_bm_t digital_pin_to_ptc_bm [] = { +#if (__PTC_Pincount__ == 6) + 0x01 << 0, //PA4 + 0x01 << 1, //PA5 + 0x01 << 2, //PA6 + 0x01 << 3, //PA7 + 0x00, //PB3 + 0x00, //PB2 + 0x01 << 4, //PB1 + 0x01 << 5, //PB0 + 0x00, //PA1 + 0x00, //PA2 + 0x00, //PA3 + 0x00 //PA0 +#elif (__PTC_Pincount__ == 12) + 0x01 << 0, // 0 PA4 + 0x01 << 1, // 1 PA5 + 0x01 << 2, // 2 PA6 + 0x01 << 3, // 3 PA7 + 0x01 << 12, // 4 PB5 + 0x01 << 13, // 5 PB4 + 0x00, // 6 PB3 + 0x00, // 7 PB2 + 0x01 << 4, // 8 PB1 + // Right side, bottom to top + 0x01 << 5, // 9 PB0 + 0x01 << 6, // 10 PC0 + 0x01 << 7, // 11 PC1 + 0x01 << 8, // 12 PC2 + 0x01 << 9, // 13 PC3 + 0x00, // 14 PA1 + 0x00, // 15 PA2 + 0x00, // 16 PA3 + 0x00 // 17 PA0 +#elif (__PTC_Pincount__ == 14) + 0x01 << 0, // 0 PA4 + 0x01 << 1, // 1 PA5 + 0x01 << 2, // 2 PA6 + 0x01 << 3, // 3 PA7 + 0x00, // 4 PB7 + 0x00, // 5 PB6 + 0x01 << 12, // 6 PB5 + 0x01 << 13, // 7 PB4 + 0x00, // 8 PB3 + 0x00, // 9 PB2 + 0x01 << 4, // 10 PB1 + // Right side, bottom to top + 0x01 << 5, // 11 PB0 + 0x01 << 6, // 12 PC0 + 0x01 << 7, // 13 PC1 + 0x01 << 8, // 14 PC2 + 0x01 << 9, // 15 PC3 + 0x01 << 10, // 16 PC4 + 0x01 << 11, // 17 PC5 + 0x00, // 18 PA1 + 0x00, // 19 PA2 + 0x00, // 20 PA3 + 0x00 // 21 PA0 +#elif (__PTC_Pincount__ == 18) // DA (ToDo) + 0x01ULL << 0, // 0 PA0 + 0x01ULL << 1, // 1 PA1 + 0x01ULL << 2, // 2 PA2/SDA + 0x01ULL << 3, // 3 PA3/SCL + 0x01ULL << 4, // 4 PA4/MOSI + 0x01ULL << 5, // 5 PA5/MISO + 0x01ULL << 6, // 6 PA6/SCK + 0x01ULL << 7, // 7 PA7/SS/CLKOUT + 0x00ULL, // 8 PC0/USART1_Tx + 0x00ULL, // 9 PC1/USART1_Rx + 0x00ULL, // 10 PC2 + 0x00ULL, // 11 PC3 + 0x01ULL << 16, // 12 PD0/AIN0 + 0x01ULL << 17, // 13 PD1/AIN1 + 0x01ULL << 18, // 14 PD2/AIN2 + 0x01ULL << 19, // 15 PD3/AIN3 + 0x01ULL << 20, // 16 PD4/AIN4 + 0x01ULL << 21, // 17 PD5/AIN5 + 0x01ULL << 22, // 18 PD6/AIN6 + 0x01ULL << 23, // 19 PD7/AIN7/AREF + 0x01ULL << 32, // 20 PF0/USART2_Tx/TOSC1 + 0x01ULL << 33, // 21 PF1/USART2_Rx/TOSC2 +#elif (__PTC_Pincount__ == 22) + 0x01ULL << 0, // 0 PA0 + 0x01ULL << 1, // 1 PA1 + 0x01ULL << 2, // 2 PA2/SDA + 0x01ULL << 3, // 3 PA3/SCL + 0x01ULL << 4, // 4 PA4/MOSI + 0x01ULL << 5, // 5 PA5/MISO + 0x01ULL << 6, // 6 PA6/SCK + 0x01ULL << 7, // 7 PA7/SS/CLKOUT + 0x00ULL, // 8 PC0/USART1_Tx + 0x00ULL, // 9 PC1/USART1_Rx + 0x00ULL, // 10 PC2 + 0x00ULL, // 11 PC3 + 0x01ULL << 16, // 12 PD0/AIN0 + 0x01ULL << 17, // 13 PD1/AIN1 + 0x01ULL << 18, // 14 PD2/AIN2 + 0x01ULL << 19, // 15 PD3/AIN3 + 0x01ULL << 20, // 16 PD4/AIN4 + 0x01ULL << 21, // 17 PD5/AIN5 + 0x01ULL << 22, // 18 PD6/AIN6 + 0x01ULL << 23, // 19 PD7/AIN7/AREF + 0x01ULL << 32, // 20 PF0/USART2_Tx/TOSC1 + 0x01ULL << 33, // 21 PF1/USART2_Rx/TOSC2 + 0x01ULL << 34, // 22 PF2/AIN12 + 0x01ULL << 35, // 23 PF3/AIN13 + 0x01ULL << 36, // 24 PF4/AIN14/TCB0 PWM + 0x01ULL << 37, // 25 PF5/AIN15/TCB1 PWM +#elif (__PTC_Pincount__ == 32) + 0x01ULL << 0, // 0 PA0 + 0x01ULL << 1, // 1 PA1 + 0x01ULL << 2, // 2 PA2/SDA + 0x01ULL << 3, // 3 PA3/SCL + 0x01ULL << 4, // 4 PA4/MOSI + 0x01ULL << 5, // 5 PA5/MISO + 0x01ULL << 6, // 6 PA6/SCK + 0x01ULL << 7, // 7 PA7/SS/CLKOUT/LED_BUILTIN + 0x01ULL << 8, // 8 PB0/USART3_Tx + 0x01ULL << 9, // 9 PB1/USART3_Rx + 0x01ULL << 10, // 10 PB2 + 0x01ULL << 11, // 11 PB3 + 0x01ULL << 12, // 12 PB4/(TCB2 PWM) + 0x01ULL << 13, // 13 PB5 + 0x00ULL, // 14 PC0/USART1_Tx + 0x00ULL, // 15 PC1/USART1_Rx + 0x00ULL, // 16 PC2 + 0x00ULL, // 17 PC3 + 0x00ULL, // 18 PC4 + 0x00ULL, // 19 PC5 + 0x00ULL, // 20 PC6 + 0x00ULL, // 21 PC7 + 0x01ULL << 16, // 22 PD0/AIN0 + 0x01ULL << 17, // 23 PD1/AIN1 + 0x01ULL << 18, // 24 PD2/AIN2 + 0x01ULL << 19, // 25 PD3/AIN3 + 0x01ULL << 20, // 26 PD4/AIN4 + 0x01ULL << 21, // 27 PD5/AIN5 + 0x01ULL << 22, // 28 PD6/AIN6 + 0x01ULL << 23, // 29 PD7/AIN7/AREF + 0x01ULL << 24, // 30 PE0/AIN8 + 0x01ULL << 25, // 31 PE1/AIN9 + 0x01ULL << 26, // 32 PE2/AIN10 + 0x01ULL << 27, // 33 PE3/AIN11 + 0x01ULL << 32, // 34 PF0/USART2_Tx/TOSC1 + 0x01ULL << 33, // 35 PF1/USART2_Rx/TOSC2 + 0x01ULL << 34, // 36 PF2/AIN12 + 0x01ULL << 35, // 37 PF3/AIN13 + 0x01ULL << 36, // 38 PF4/AIN14 + 0x01ULL << 37, // 39 PF5/AIN15 +#elif (__PTC_Pincount__ == 46) + 0x01ULL << 0, // 0 PA0 + 0x01ULL << 1, // 1 PA1 + 0x01ULL << 2, // 2 PA2/SDA + 0x01ULL << 3, // 3 PA3/SCL + 0x01ULL << 4, // 4 PA4/MOSI + 0x01ULL << 5, // 5 PA5/MISO + 0x01ULL << 6, // 6 PA6/SCK + 0x01ULL << 7, // 7 PA7/SS/CLKOUT/LED_BUILTIN + 0x01ULL << 8, // 8 PB0/USART3_Tx + 0x01ULL << 9, // 9 PB1/USART3_Rx + 0x01ULL << 10, // 10 PB2 + 0x01ULL << 11, // 11 PB3 + 0x01ULL << 12, // 12 PB4/(TCB2 PWM) + 0x01ULL << 13, // 13 PB5 + 0x01ULL << 14, // 14 PB6 + 0x01ULL << 15, // 15 PB7 + 0x00ULL, // 16 PC0/USART1_Tx + 0x00ULL, // 17 PC1/USART1_Rx + 0x00ULL, // 18 PC2 + 0x00ULL, // 19 PC3 + 0x00ULL, // 20 PC4 + 0x00ULL, // 21 PC5 + 0x00ULL, // 22 PC6 + 0x00ULL, // 23 PC7 + 0x01ULL << 16, // 24 PD0/AIN0 + 0x01ULL << 17, // 25 PD1/AIN1 + 0x01ULL << 18, // 26 PD2/AIN2 + 0x01ULL << 19, // 27 PD3/AIN3 + 0x01ULL << 20, // 28 PD4/AIN4 + 0x01ULL << 21, // 29 PD5/AIN5 + 0x01ULL << 22, // 30 PD6/AIN6 + 0x01ULL << 23, // 31 PD7/AIN7/AREF + 0x01ULL << 24, // 32 PE0/AIN8 + 0x01ULL << 25, // 33 PE1/AIN9 + 0x01ULL << 26, // 34 PE2/AIN10 + 0x01ULL << 27, // 35 PE3/AIN11 + 0x01ULL << 28, // 36 PE4 + 0x01ULL << 29, // 37 PE5 + 0x01ULL << 30, // 38 PE6 + 0x01ULL << 31, // 39 PE7 + 0x01ULL << 32, // 40 PF0/USART2_Tx/TOSC1 + 0x01ULL << 33, // 41 PF1/USART2_Rx/TOSC2 + 0x01ULL << 34, // 42 PF2/AIN12 + 0x01ULL << 35, // 43 PF3/AIN13 + 0x01ULL << 36, // 44 PF4/AIN14 + 0x01ULL << 37, // 45 PF5/AIN15 + 0x01ULL << 40, // 46 PG0 + 0x01ULL << 41, // 47 PG1 + 0x01ULL << 42, // 48 PG2 + 0x01ULL << 43, // 49 PG3 + 0x01ULL << 44, // 50 PG4 + 0x01ULL << 45, // 51 PG5 + 0x01ULL << 46, // 52 PG6 + 0x01ULL << 47, // 53 PG7 +#endif +}; + + +#if defined (PORTA) + #define PORTA_ISC(_pin_) ((0x20 * 0) + 0x10 + _pin_) +#else + #define PORTA_ISC(_pin_) 0x00 +#endif +#if defined (PORTB) + #define PORTB_ISC(_pin_) ((0x20 * 1) + 0x10 + _pin_) +#else + #define PORTB_ISC(_pin_) 0x00 +#endif +#if defined (PORTC) + #define PORTC_ISC(_pin_) ((0x20 * 2) + 0x10 + _pin_) +#else + #define PORTC_ISC(_pin_) 0x00 +#endif +#if defined (PORTD) + #define PORTD_ISC(_pin_) ((0x20 * 3) + 0x10 + _pin_) +#else + #define PORTD_ISC(_pin_) 0x00 +#endif +#if defined (PORTE) + #define PORTE_ISC(_pin_) ((0x20 * 4) + 0x10 + _pin_) +#else + #define PORTE_ISC(_pin_) 0x00 +#endif +#if defined (PORTF) + #define PORTF_ISC(_pin_) ((0x20 * 5) + 0x10 + _pin_) +#else + #define PORTF_ISC(_pin_) 0x00 +#endif +#if defined (PORTG) + #define PORTG_ISC(_pin_) ((0x20 * 6) + 0x10 + _pin_) +#else + #define PORTG_ISC(_pin_) 0x00 +#endif + + +// lookup-table to quickly disable input and pull-up. PTC_Tiny only +static const uint8_t ptc_ch_to_pin [] = { +#if (__PTC_Pincount__ <= 14) + PORTA_ISC(4), + PORTA_ISC(5), + PORTA_ISC(6), + PORTA_ISC(7), + PORTB_ISC(1), /* X4 / Y4 */ + PORTB_ISC(0), +#if (__PTC_Pincount__ == 12 || __PTC_Pincount__ == 14) + PORTC_ISC(0), + PORTC_ISC(1), + PORTC_ISC(2), /* X8 / Y8 */ + PORTC_ISC(3), + PORTC_ISC(4), // 20 pin parts: writing to this location will have no effect, but likely pre-filtered by PIN_TO_PTC anyway + PORTC_ISC(5), // 20 pin parts: writing to this location will have no effect, but likely pre-filtered by PIN_TO_PTC anyway + + PORTB_ISC(5), /* X12 / Y12 */ + PORTB_ISC(6), +#endif +#endif +}; + + +#ifdef __cplusplus +} +#endif + + +#endif /* PTC_TOUCH.H_H_ */ diff --git a/megaavr/libraries/PTC/src/ptc_types.h b/megaavr/libraries/PTC/src/ptc_types.h new file mode 100644 index 00000000..ef74abb4 --- /dev/null +++ b/megaavr/libraries/PTC/src/ptc_types.h @@ -0,0 +1,252 @@ +/* + * Refer to ptc.h file for copyright, changelog, usage and license information + */ + + +#pragma once +#ifndef PTC_TYPES_H +#define PTC_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if (defined(__AVR_ATtiny814__) || defined(__AVR_ATtiny1614__) || defined(__AVR_ATtiny3214__)) + typedef uint8_t ptc_id_t; + typedef uint8_t ptc_ch_bm_t; + typedef uint8_t ptc_ch_arr_t[1]; + #define __PTC_Tiny__ + #define __PTC_Pincount__ 6 +#elif (defined(__AVR_ATtiny816__) || defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__)) + typedef uint8_t ptc_id_t; + typedef uint16_t ptc_ch_bm_t; + typedef uint16_t ptc_ch_arr_t[1]; + #define __PTC_Tiny__ + #define __PTC_Pincount__ 12 +#elif (defined(__AVR_ATtiny817__) || defined(__AVR_ATtiny1617__) || defined(__AVR_ATtiny3217__)) + typedef uint8_t ptc_id_t; + typedef uint16_t ptc_ch_bm_t; + typedef uint16_t ptc_ch_arr_t[1]; + #define __PTC_Tiny__ + #define __PTC_Pincount__ 14 +#elif (defined(__AVR_AVR32DA28__) || defined(__AVR_AVR64DA28__) || defined(__AVR_AVR128DA28__)) + typedef uint16_t ptc_id_t; + typedef uint64_t ptc_ch_bm_t; + typedef uint8_t ptc_ch_arr_t[5]; + #define __PTC_DA__ + #define __PTC_Pincount__ 18 +#elif (defined(__AVR_AVR32DA32__) || defined(__AVR_AVR64DA32__) || defined(__AVR_AVR128DA32__)) + typedef uint16_t ptc_id_t; + typedef uint64_t ptc_ch_bm_t; + typedef uint8_t ptc_ch_arr_t[5]; + #define __PTC_DA__ + #define __PTC_Pincount__ 22 +#elif (defined(__AVR_AVR32DA48__) || defined(__AVR_AVR64DA48__) || defined(__AVR_AVR128DA48__)) + typedef uint16_t ptc_id_t; + typedef uint64_t ptc_ch_bm_t; + typedef uint8_t ptc_ch_arr_t[5]; + #define __PTC_DA__ + #define __PTC_Pincount__ 32 +#elif (defined(__AVR_AVR32DA64__) || defined(__AVR_AVR64DA64__) || defined(__AVR_AVR128DA64__)) + typedef uint16_t ptc_id_t; + typedef uint64_t ptc_ch_bm_t; + typedef uint8_t ptc_ch_arr_t[6]; + #define __PTC_DA__ + #define __PTC_Pincount__ 46 +#else + #error "PTC not supported by this chip" +#endif + + +typedef enum ptc_freq_enum { + FREQ_SEL_0, + FREQ_SEL_1, + FREQ_SEL_2, + FREQ_SEL_3, + FREQ_SEL_4, + FREQ_SEL_5, + FREQ_SEL_6, + FREQ_SEL_7, + FREQ_SEL_8, + FREQ_SEL_9, + FREQ_SEL_10, + FREQ_SEL_11, + FREQ_SEL_12, + FREQ_SEL_13, + FREQ_SEL_14, + FREQ_SEL_15, + FREQ_SEL_SPREAD +} ptc_freq_t; + + +#if defined (__PTC_Tiny__) +typedef enum PTC_PRESC_enum { + PTC_PRESC_DIV2_gc = (0x00<<0), /* CLK_PER divided by 2 */ + PTC_PRESC_DIV4_gc = (0x01<<0), /* CLK_PER divided by 4 */ + PTC_PRESC_DIV8_gc = (0x02<<0), /* CLK_PER divided by 8 */ + PTC_PRESC_DIV16_gc = (0x03<<0), /* CLK_PER divided by 16 */ + PTC_PRESC_DIV32_gc = (0x04<<0), /* CLK_PER divided by 32 */ + PTC_PRESC_DIV64_gc = (0x05<<0), /* CLK_PER divided by 64 */ + PTC_PRESC_DIV128_gc = (0x06<<0), /* CLK_PER divided by 128 */ + PTC_PRESC_DIV256_gc = (0x07<<0) /* CLK_PER divided by 256 */ +} PTC_PRESC_t; + +typedef enum PTC_RSEL_enum { + RSEL_VAL_0, + RSEL_VAL_20, + RSEL_VAL_50, + RSEL_VAL_70, + RSEL_VAL_100, + RSEL_VAL_200 +} PTC_RSEL_t; +#elif defined (__PTC_DA__) +typedef enum PTC_PRESC_enum { + PTC_PRESC_DIV2_gc = (0x00<<0), /* CLK_PER divided by 2 */ + PTC_PRESC_DIV4_gc = (0x01<<0), /* CLK_PER divided by 4 */ + PTC_PRESC_DIV6_gc = (0x02<<0), /* CLK_PER divided by 2 */ + PTC_PRESC_DIV8_gc = (0x03<<0), /* CLK_PER divided by 8 */ + PTC_PRESC_DIV10_gc = (0x04<<0), /* CLK_PER divided by 8 */ + PTC_PRESC_DIV12_gc = (0x05<<0), /* CLK_PER divided by 8 */ + PTC_PRESC_DIV14_gc = (0x06<<0), /* CLK_PER divided by 8 */ + PTC_PRESC_DIV16_gc = (0x07<<0), /* CLK_PER divided by 16 */ +} PTC_PRESC_t; + +typedef enum tag_rsel_val_t { + RSEL_VAL_0, + RSEL_VAL_20, + RSEL_VAL_50, + RSEL_VAL_70, + RSEL_VAL_80, + RSEL_VAL_100, + RSEL_VAL_120, + RSEL_VAL_200 +} PTC_RSEL_t; +#endif + +typedef struct ptc_node_state_type { + uint8_t error: 1; + uint8_t win_comp: 1; + uint8_t low_power: 1; + uint8_t data_ready: 1; + uint8_t disabled: 1; +} ptc_node_state_t; + + +typedef enum ptc_sm_enum { + PTC_SM_NOINIT_CAL = 0x00, + PTC_SM_RECAL_FLT = 0x01, + PTC_SM_NT_LOW_FLT = 0x02, + PTC_SM_NO_TOUCH = 0x04, + PTC_SM_NT_HIGH_FLT = 0x08, + PTC_SM_TOUCH_IN_FLT = 0x10, + PTC_SM_TOUCH_OUT_FLT = 0x20, + PTC_SM_TOUCH_DETECT = 0x40, + PTC_SM_LOW_POWER = 0x80 +} ptc_sm_t; + +typedef enum ptc_ret_enum { + // Normal return types + PTC_LIB_SUCCESS = 0x00, + PTC_LIB_WAS_WAKEN, + PTC_LIB_ASLEEP, + PTC_LIB_CALIB_DONE, + PTC_LIB_CALIB_TOO_LOW, + PTC_LIB_CALIB_TOO_HIGH, + PTC_LIB_CALIB_TOO_LONG, + // Error Return types + PTC_LIB_ERROR = 0x10, + PTC_LIB_BAD_POINTER, + PTC_LIB_BAD_ARGUMENT, + PTC_LIB_WRONG_STATE, + // Node Return types + PTC_NODE_WRONG_STATE = 0x30, + PTC_NODE_TOUCHED, + PTC_NODE_NOT_TOUCHED +} ptc_ret_t; + + +typedef enum ptc_node_type_enum { + NODE_TYPE_NOCONV_bm = (0x00), + NODE_MUTUAL_bm = (0x01), + NODE_RESERVED_bm = (0x02), + NODE_SELFCAP_bm = (0x04), + NODE_SHIELD_bm = (0x08), + NODE_SELFCAP_SHIELD_bm = (NODE_SHIELD_bm | NODE_SELFCAP_bm), // 0x0C + NODE_TYPE_bm = (NODE_MUTUAL_bm | NODE_SELFCAP_bm | NODE_SHIELD_bm), +} ptc_node_type_t; + + +typedef enum ptc_cb_event_enum { + PTC_CB_EVENT_TOUCH = 0x10, + PTC_CB_EVENT_TOUCH_DETECT = (PTC_CB_EVENT_TOUCH | 0x01), // 0x11 + PTC_CB_EVENT_TOUCH_RELEASE = (PTC_CB_EVENT_TOUCH | 0x02), // 0x12 + PTC_CB_EVENT_TOUCH_WAKE = (PTC_CB_EVENT_TOUCH | 0x04), // 0x14 + PTC_CB_EVENT_WAKE_TOUCH = (PTC_CB_EVENT_TOUCH_WAKE | 0x01), // 0x15 + PTC_CB_EVENT_WAKE_NO_TOUCH = (PTC_CB_EVENT_TOUCH_WAKE | 0x02), // 0x16 + PTC_CB_EVENT_CONV_CMPL = 0x20, + PTC_CB_EVENT_CONV_MUTUAL_CMPL = (PTC_CB_EVENT_CONV_CMPL | NODE_MUTUAL_bm), // 0x21 + PTC_CB_EVENT_CONV_SELF_CMPL = (PTC_CB_EVENT_CONV_CMPL | NODE_SELFCAP_bm), // 0x24 + PTC_CB_EVENT_CONV_SHIELD_CMPL = (PTC_CB_EVENT_CONV_CMPL | NODE_SELFCAP_SHIELD_bm), // 0x28 + PTC_CB_EVENT_CONV_TYPE_CMPL_MSK = (NODE_TYPE_bm), // 0x0D + PTC_CB_EVENT_CONV_CALIB = 0x40, + PTC_CB_EVENT_ERR_CALIB_LOW = (PTC_CB_EVENT_CONV_CALIB | 0x01), // 0x41 + PTC_CB_EVENT_ERR_CALIB_HIGH = (PTC_CB_EVENT_CONV_CALIB | 0x02), // 0x42 + PTC_CB_EVENT_ERR_CALIB_TO = (PTC_CB_EVENT_CONV_CALIB | 0x04), // 0x44 + PTC_CB_EVENT_ERR_CALIB_MSK = (0x04 | 0x02 | 0x01), // 0x07 +} ptc_cb_event_t; + + + + + +typedef enum ptc_lib_enum { + PTC_LIB_IDLE = 0x00, + PTC_LIB_CONV_PROG = 0x01, + PTC_LIB_CONV_COMPL = 0x02, + PTC_LIB_EVENT = 0x04, + PTC_LIB_CONV_LP = 0x08, + PTC_LIB_CONV_WCMP = 0x10, + PTC_LIB_SUSPENDED = 0x80 +} ptc_lib_t; + +typedef struct cap_sensor_type { + struct cap_sensor_type *nextNode; + ptc_node_type_t type; + ptc_id_t id; // number for easier identification in the callback + + ptc_ch_arr_t hw_xCh_bm; // the code relies on this bitmaps to be together, + ptc_ch_arr_t hw_yCh_bm; // do not separate them or change order. + uint16_t hw_compCaps; // [13:12] rough; [11:8] course; [7:4] fine; [3:0] accurate (on Tinies only) + uint8_t hw_rsel_presc; // [7:4] RSEL, [3:0] PRESC + uint8_t hw_a_d_gain; // [7:4] Analog Gain, [3:0] Digital Gain /* PTC_AGAIN / CTRLB.SAMPNUM */ + uint8_t hw_csd; // [4:0] Charge Share Delay /* SAMPLEN in SAMPCTRL */ + + ptc_node_state_t state; + uint16_t sensorData; // ADC data, Oversampling-corrected + uint16_t reference; // Compare Value for detection + int16_t touch_in_th; // this value is compared to the sensorData - threshold delta + int16_t touch_out_th; // this value is compared to the sensorData - threshold delta + + uint8_t stateMachine; + uint8_t lastStateChange; // stateChangeCounter +} cap_sensor_t; + + +// Abbreviation: NoM: Number of Measurements +typedef struct ptc_lib_sm_settings_type { + uint16_t force_recal_delta; // if the threshold value exceeds this compared to optimal (512), force recalibration. Default: 150 + uint8_t touched_detect_nom; // NoM above node threshold for the node to become touched. Default: 3 + uint8_t untouched_detect_nom; // NoM below node threshold for the node to become untouched. Default: 3 + uint8_t touched_max_nom; // NoM a touch was detected until a recalibration is forced. Value +1. Disabled with 0xFF. Default: 200 + uint8_t drift_up_nom; // NoM when no touch is detected, until the threshold is increased. Value +1. Disabled with 0xFF. Default: 20 + uint8_t drift_down_nom; // NoM when no touch is detected, until the threshold is decreased. Value +1. Disabled with 0xFF. Default: 20 +} ptc_lib_sm_set_t; + + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/megaavr/platform.txt b/megaavr/platform.txt index b59791a2..6deb8b1d 100644 --- a/megaavr/platform.txt +++ b/megaavr/platform.txt @@ -35,13 +35,13 @@ compiler.warning_flags.all=-Wall -Wextra # Default "compiler.path" is correct, change only if you want to override the initial value compiler.path={runtime.tools.avr-gcc.path}/bin/ compiler.c.cmd=avr-gcc -compiler.c.flags=-c -g -Os {compiler.warning_flags} -std=gnu11 -ffunction-sections -fdata-sections -MMD -flto -fno-fat-lto-objects -mrelax -Werror=implicit-function-declaration -Wundef +compiler.c.flags=-c -g -Os {compiler.warning_flags} -std=gnu11 -ffunction-sections -fdata-sections -fshort-enums -flto -mrelax -MMD -fno-fat-lto-objects -Werror=implicit-function-declaration -Wundef # DxCore has the three additional FLMAP sections for mapped flash compiler.c.elf.flags={compiler.warning_flags} -Os -g -flto -fuse-linker-plugin -mrelax -Wl,--gc-sections,--section-start={build.text_section_start},--section-start=.FLMAP_SECTION1=0x8000,--section-start=.FLMAP_SECTION2=0x10000,--section-start=.FLMAP_SECTION3=0x18000 compiler.c.elf.cmd=avr-gcc compiler.S.flags=-c -g -x assembler-with-cpp -flto -MMD compiler.cpp.cmd=avr-g++ -compiler.cpp.flags=-c -g -Os {compiler.warning_flags} -std=gnu++17 -fpermissive -Wno-sized-deallocation -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -Wno-error=narrowing -MMD -flto -mrelax +compiler.cpp.flags=-c -g -Os {compiler.warning_flags} -std=gnu++17 -ffunction-sections -fdata-sections -fshort-enums -flto -mrelax -MMD -fpermissive -Wno-sized-deallocation -fno-exceptions -fno-threadsafe-statics -Wno-error=narrowing compiler.ar.cmd=avr-gcc-ar compiler.ar.flags=rcs compiler.objcopy.cmd=avr-objcopy