Skip to content

Commit

Permalink
checkpoint on the road to lint
Browse files Browse the repository at this point in the history
  • Loading branch information
MX682X committed Sep 27, 2024
1 parent e64a2fa commit 9f88fc5
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 107 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/compile-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 17 additions & 17 deletions megaavr/libraries/PTC/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ An Arduino compatible C library to support the PTC in the AtTiny 1-family as wel
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
### How to use this library


#### Step 1: Memory Initialization
Expand All @@ -31,7 +31,7 @@ Here are some examples:


#### 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.
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.
Expand Down Expand Up @@ -60,7 +60,7 @@ There are following events:
- `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.
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
Expand Down Expand Up @@ -97,7 +97,7 @@ The ptc_ch_bm_t is a typedef that depends on the pincount of the device and rang
(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 publically available documentation and observation.
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:
Expand All @@ -109,32 +109,32 @@ In order to measure the capacitance of the node, the PTC performs following Step

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 trough 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.
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_fempto(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 fempto farrads, to avoid floats. Read more here: https://www.microchipdeveloper.com/touch:guide-to-interpret-cc-calibration-value
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.
- 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.
- `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.
60 changes: 60 additions & 0 deletions megaavr/libraries/PTC/examples/mutualcap/mutualcap.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include <ptc.h>
/*
* 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

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));
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ void setup() {
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!");
}
Expand All @@ -43,9 +43,9 @@ void loop() {
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) {
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!");
Expand All @@ -55,7 +55,7 @@ void loop() {
}

// 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) {
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:");
Expand All @@ -68,14 +68,14 @@ void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t* node) {
}
}

void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t* 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) {
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) {
Expand All @@ -90,11 +90,11 @@ void ptc_event_cb_calibration(const ptc_cb_event_t eventType, cap_sensor_t* node
MySerial.println(ptc_get_node_id(node));
}

void ptc_event_cb_wake(const ptc_cb_event_t eventType, cap_sensor_t* 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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ void loop() {


// 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) {
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));
Expand All @@ -43,7 +43,7 @@ void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t* node) {
}
}

void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t* 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) {
Expand All @@ -52,7 +52,7 @@ void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t* node)
(void)node; // remove unused warning
}

void ptc_event_cb_calibration(const ptc_cb_event_t eventType, cap_sensor_t* node) {
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) {
Expand Down
6 changes: 3 additions & 3 deletions megaavr/libraries/PTC/examples/selfcap/selfcap.ino
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ void loop() {


// 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) {
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));
Expand All @@ -40,14 +40,14 @@ void ptc_event_cb_touch(const ptc_cb_event_t eventType, cap_sensor_t* node) {
}
}

void ptc_event_cb_conversion(const ptc_cb_event_t eventType, cap_sensor_t* 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) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ void loop() {
}

// 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) {
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));
Expand Down
Loading

0 comments on commit 9f88fc5

Please sign in to comment.