protocentral-easyemg 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ .pytest_cache/
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ All notable changes to this driver are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.2.0] - 2026-11-07
8
+
9
+ First public release. A port of the
10
+ [Protocentral easyEMG Arduino library](https://github.com/Protocentral/protocentral_easyemg_arduino)
11
+ 1.1.0 for MicroPython and Raspberry Pi.
12
+
13
+ ### Added
14
+ - The Arduino API in snake_case: raw sEMG in µV at the electrodes, sliding-window RMS
15
+ envelope, baseline zeroing, activation threshold and %MVC.
16
+ - `set_mains_notch()`, a notch on every mains harmonic up to 0.45× the sample rate
17
+ (6 Hz wide at the fundamental, 2 Hz on the harmonics), the single `set_notch()`, and
18
+ a 2nd-order digital high-pass. All off by default.
19
+ - Paced sampling: `update()` on a fixed clock (500 SPS by default), with
20
+ `missed_samples()` and `reset_clock()`.
21
+ - `MvcRecorder`, the MVC recording step from the Arduino `06.OpenView` sketch.
22
+ - Low-level ADC passthroughs: `set_range()`, `set_data_rate()`, `set_mode()`,
23
+ `set_mux()` and `read_single_shot()`.
24
+ - Python-only extras: `set_gain()`, `microvolts_per_count()`, `last_envelope_raw()`
25
+ and `.adc`.
26
+ - An example for every Arduino example: `easyemg_simpletest.py` and
27
+ `easyemg_raspberrypi.py` (`01`), `easyemg_plotter.py` (`02`),
28
+ `easyemg_calibration.py` (`03`), `easyemg_envelope.py` (`04`),
29
+ `easyemg_activation_led.py` (`05`), `easyemg_openview.py` (`06`, the same packets for
30
+ OpenView's easyEMG Breakout profile) and `easyemg_diagnostics.py` (`07`).
31
+ - Mocked-bus tests, including each example run end to end, and an `mpy-cross` compile
32
+ in CI, on every push and before every PyPI publish.
33
+
34
+ ### Differences from the Arduino library
35
+ - The driver paces sampling itself, at 500 SPS by default, where the Arduino sketches
36
+ pace at 1000 SPS. `easyemg_openview.py` therefore sends one sample per packet rather
37
+ than averaging two, and its harmonic notch covers 50–200 Hz.
38
+
39
+ Validated with the mocked-bus tests. On an ESP32-S3 (MicroPython 1.29) the driver holds
40
+ 500 SPS through the full filter chain with no missed samples. Further bench results
41
+ will be added to the README as they're measured.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ashwin Whitchurch, Protocentral Electronics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.5
2
+ Name: protocentral-easyemg
3
+ Version: 0.2.0
4
+ Summary: MicroPython + Raspberry Pi driver for the Protocentral easyEMG surface EMG breakout (TLA2024-based)
5
+ Project-URL: Homepage, https://protocentral.com
6
+ Project-URL: Source, https://github.com/Protocentral/protocentral-micropython-easyemg
7
+ Author-email: Protocentral Electronics <info@protocentral.com>
8
+ License: MIT
9
+ License-File: LICENSE.md
10
+ Keywords: easyemg,emg,i2c,micropython,protocentral,qwiic,raspberry-pi,semg,tla2024
11
+ Requires-Python: >=3.9
12
+ Requires-Dist: protocentral-tla20xx>=1.0
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Protocentral easyEMG MicroPython Library
16
+
17
+ [![Tests](https://github.com/Protocentral/protocentral-micropython-easyemg/actions/workflows/test.yml/badge.svg)](https://github.com/Protocentral/protocentral-micropython-easyemg/actions/workflows/test.yml)
18
+ [![Publish to PyPI](https://github.com/Protocentral/protocentral-micropython-easyemg/actions/workflows/publish.yml/badge.svg)](https://github.com/Protocentral/protocentral-micropython-easyemg/actions/workflows/publish.yml)
19
+
20
+ MicroPython library for the **Protocentral easyEMG Breakout**: single-channel surface EMG (sEMG) over I²C / Qwiic. It gives you the raw band-limited waveform and computes the RMS envelope in software. The same driver also runs on a Raspberry Pi.
21
+
22
+ ## Don't have one? [Buy it here](https://protocentral.com/product/easyemg-surface-emg-breakout-qwiic/)
23
+
24
+ ![Protocentral easyEMG](https://raw.githubusercontent.com/Protocentral/protocentral-micropython-easyemg/main/assets/easyemg.jpg)
25
+
26
+ **Wiring, electrode placement and troubleshooting are in the [easyEMG Getting Started guide](https://protocentral.com/product/easyemg-surface-emg-breakout-qwiic/docs/getting-started/).**
27
+
28
+ ## Links
29
+
30
+ | | |
31
+ |---|---|
32
+ | Product page | https://protocentral.com/product/easyemg-surface-emg-breakout-qwiic/ |
33
+ | Getting Started guide | https://protocentral.com/product/easyemg-surface-emg-breakout-qwiic/docs/getting-started/ |
34
+ | Arduino library | https://github.com/Protocentral/protocentral_easyemg_arduino |
35
+ | Hardware design files | https://github.com/Protocentral/protocentral_easyemg_hardware |
36
+
37
+ ## Features
38
+
39
+ - Raw sEMG in µV at the electrodes, on a paced 500 SPS clock with missed-sample counting
40
+ - Sliding-window RMS envelope, activation threshold and %MVC
41
+ - `MvcRecorder`, the same MVC recording step as the Arduino OpenView sketch
42
+ - Optional mains notch (fundamental or every harmonic) and digital high-pass
43
+ - A port of the [Arduino library](https://github.com/Protocentral/protocentral_easyemg_arduino) 1.1.0: the same API in snake_case, and a Python version of every Arduino example
44
+
45
+ > **Not a medical device. For research and education only.** The breakout has no patient isolation: when electrodes are on a person, power the host from a battery or connect through an [easyISO isolated Qwiic adapter](https://protocentral.com/product/easyiso-isolated-qwiic-adapter/). On a mains-powered host you get no EMG at all.
46
+
47
+ ## Installation
48
+
49
+ **MicroPython** (Raspberry Pi Pico, ESP32, …):
50
+
51
+ ```bash
52
+ mpremote mip install github:Protocentral/protocentral-micropython-easyemg
53
+ ```
54
+
55
+ This also installs the [TLA20xx driver](https://github.com/Protocentral/protocentral-micropython-tla20xx). With Thonny, copy `protocentral_easyemg.py` and `protocentral_tla20xx.py` to the board instead.
56
+
57
+ **Raspberry Pi**, with I²C enabled (`sudo raspi-config nonint do_i2c 0`):
58
+
59
+ ```bash
60
+ python3 -m venv ~/easyemg && source ~/easyemg/bin/activate
61
+ pip install protocentral-easyemg
62
+ ```
63
+
64
+ ## Quick Start
65
+
66
+ ```python
67
+ from machine import Pin, I2C
68
+ from protocentral_easyemg import EasyEMG
69
+
70
+ i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000) # Pico: GP5, GP4. Change for your board
71
+ emg = EasyEMG(i2c) # default address 0x48
72
+ if not emg.begin():
73
+ raise OSError("easyEMG not found")
74
+ emg.calibrate_baseline() # muscle relaxed, electrodes on
75
+
76
+ n = 0
77
+ while True:
78
+ if emg.update(): # True once per sample, 500 SPS
79
+ n += 1
80
+ if n % 50 == 0: # print 10 times a second
81
+ print(emg.last_envelope(), emg.missed_samples())
82
+ ```
83
+
84
+ On a Raspberry Pi, replace the I²C lines with `from protocentral_tla20xx_linux import I2C` and `EasyEMG(I2C(bus=1))`.
85
+
86
+ Call `update()` as often as you can; it samples only when the clock is due, and never blocks. If `missed_samples()` grows, print less or lower the rate with `set_sample_rate()`. Filters need that same rate: `set_mains_notch(50, emg.sample_rate_hz())`.
87
+
88
+ ## API Reference
89
+
90
+ | Method | Description |
91
+ |---|---|
92
+ | `EasyEMG(i2c, address=0x48)` / `begin()` | Construct / configure the ADC. `begin()` returns `False` if the board doesn't answer |
93
+ | `update()` | Non-blocking. Takes one sample when the clock is due; `True` if it did |
94
+ | `last_microvolts()` / `last_envelope()` | The latest sample and RMS envelope, in µV at the electrodes |
95
+ | `set_sample_rate(hz)` / `missed_samples()` | Paced clock, default 500 SPS / samples it couldn't take on time |
96
+ | `set_envelope_window(n)` | 1–128 samples, default 64 (128 ms at 500 SPS) |
97
+ | `set_mains_notch(hz, fs)` / `set_notch(hz, fs)` / `set_high_pass(hz, fs)` | Optional filters, off by default |
98
+ | `calibrate_baseline(n)` / `set_baseline(n)` | Measure and subtract the resting offset |
99
+ | `set_activation_threshold(uv)` / `is_active()` | Compare the envelope with a threshold |
100
+ | `set_mvc(uv)` / `read_percent_mvc()` / `MvcRecorder` | %MVC normalisation and the MVC recording step |
101
+
102
+ The full API, including the unpaced reads and low-level ADC passthroughs, is documented in [protocentral_easyemg.py](https://github.com/Protocentral/protocentral-micropython-easyemg/blob/main/protocentral_easyemg.py).
103
+
104
+ ## Examples
105
+
106
+ | # | Example | Description |
107
+ |---|---|---|
108
+ | 01 | `easyemg_simpletest.py` / `easyemg_raspberrypi.py` | Raw µV and envelope (MicroPython / Raspberry Pi) |
109
+ | 02 | `easyemg_plotter.py` | sEMG and envelope for Thonny's plotter |
110
+ | 03 | `easyemg_calibration.py` | Baseline zeroing and your resting noise floor |
111
+ | 04 | `easyemg_envelope.py` | RMS envelope, MVC recording and %MVC |
112
+ | 05 | `easyemg_activation_led.py` | Flex to light an LED, with an automatic threshold and hysteresis |
113
+ | 06 | `easyemg_openview.py` | Stream to [OpenView](https://github.com/Protocentral/protocentral_openview) 3.4+ (**easyEMG Breakout**), with the MVC step. Save it to the board as `main.py`, then close Thonny or `mpremote` before connecting OpenView |
114
+ | 07 | `easyemg_diagnostics.py` | I²C, ADC, sample-rate, noise and mains-hum checks. **Run this first if something is wrong** |
115
+
116
+ The examples default to QT Py ESP32-C3 pins: change the `I2C(...)` line for your board.
117
+
118
+ ## License
119
+
120
+ - **Software:** MIT, see [LICENSE.md](https://github.com/Protocentral/protocentral-micropython-easyemg/blob/main/LICENSE.md)
121
+ - **Hardware:** CERN-OHL-P v2, in the [hardware repo](https://github.com/Protocentral/protocentral_easyemg_hardware)
122
+
123
+ Copyright (c) 2026 Protocentral Electronics.
124
+
125
+ ## Support
126
+
127
+ Open an issue on [GitHub](https://github.com/Protocentral/protocentral-micropython-easyemg/issues) or email support@protocentral.com.
@@ -0,0 +1,113 @@
1
+ # Protocentral easyEMG MicroPython Library
2
+
3
+ [![Tests](https://github.com/Protocentral/protocentral-micropython-easyemg/actions/workflows/test.yml/badge.svg)](https://github.com/Protocentral/protocentral-micropython-easyemg/actions/workflows/test.yml)
4
+ [![Publish to PyPI](https://github.com/Protocentral/protocentral-micropython-easyemg/actions/workflows/publish.yml/badge.svg)](https://github.com/Protocentral/protocentral-micropython-easyemg/actions/workflows/publish.yml)
5
+
6
+ MicroPython library for the **Protocentral easyEMG Breakout**: single-channel surface EMG (sEMG) over I²C / Qwiic. It gives you the raw band-limited waveform and computes the RMS envelope in software. The same driver also runs on a Raspberry Pi.
7
+
8
+ ## Don't have one? [Buy it here](https://protocentral.com/product/easyemg-surface-emg-breakout-qwiic/)
9
+
10
+ ![Protocentral easyEMG](https://raw.githubusercontent.com/Protocentral/protocentral-micropython-easyemg/main/assets/easyemg.jpg)
11
+
12
+ **Wiring, electrode placement and troubleshooting are in the [easyEMG Getting Started guide](https://protocentral.com/product/easyemg-surface-emg-breakout-qwiic/docs/getting-started/).**
13
+
14
+ ## Links
15
+
16
+ | | |
17
+ |---|---|
18
+ | Product page | https://protocentral.com/product/easyemg-surface-emg-breakout-qwiic/ |
19
+ | Getting Started guide | https://protocentral.com/product/easyemg-surface-emg-breakout-qwiic/docs/getting-started/ |
20
+ | Arduino library | https://github.com/Protocentral/protocentral_easyemg_arduino |
21
+ | Hardware design files | https://github.com/Protocentral/protocentral_easyemg_hardware |
22
+
23
+ ## Features
24
+
25
+ - Raw sEMG in µV at the electrodes, on a paced 500 SPS clock with missed-sample counting
26
+ - Sliding-window RMS envelope, activation threshold and %MVC
27
+ - `MvcRecorder`, the same MVC recording step as the Arduino OpenView sketch
28
+ - Optional mains notch (fundamental or every harmonic) and digital high-pass
29
+ - A port of the [Arduino library](https://github.com/Protocentral/protocentral_easyemg_arduino) 1.1.0: the same API in snake_case, and a Python version of every Arduino example
30
+
31
+ > **Not a medical device. For research and education only.** The breakout has no patient isolation: when electrodes are on a person, power the host from a battery or connect through an [easyISO isolated Qwiic adapter](https://protocentral.com/product/easyiso-isolated-qwiic-adapter/). On a mains-powered host you get no EMG at all.
32
+
33
+ ## Installation
34
+
35
+ **MicroPython** (Raspberry Pi Pico, ESP32, …):
36
+
37
+ ```bash
38
+ mpremote mip install github:Protocentral/protocentral-micropython-easyemg
39
+ ```
40
+
41
+ This also installs the [TLA20xx driver](https://github.com/Protocentral/protocentral-micropython-tla20xx). With Thonny, copy `protocentral_easyemg.py` and `protocentral_tla20xx.py` to the board instead.
42
+
43
+ **Raspberry Pi**, with I²C enabled (`sudo raspi-config nonint do_i2c 0`):
44
+
45
+ ```bash
46
+ python3 -m venv ~/easyemg && source ~/easyemg/bin/activate
47
+ pip install protocentral-easyemg
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from machine import Pin, I2C
54
+ from protocentral_easyemg import EasyEMG
55
+
56
+ i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000) # Pico: GP5, GP4. Change for your board
57
+ emg = EasyEMG(i2c) # default address 0x48
58
+ if not emg.begin():
59
+ raise OSError("easyEMG not found")
60
+ emg.calibrate_baseline() # muscle relaxed, electrodes on
61
+
62
+ n = 0
63
+ while True:
64
+ if emg.update(): # True once per sample, 500 SPS
65
+ n += 1
66
+ if n % 50 == 0: # print 10 times a second
67
+ print(emg.last_envelope(), emg.missed_samples())
68
+ ```
69
+
70
+ On a Raspberry Pi, replace the I²C lines with `from protocentral_tla20xx_linux import I2C` and `EasyEMG(I2C(bus=1))`.
71
+
72
+ Call `update()` as often as you can; it samples only when the clock is due, and never blocks. If `missed_samples()` grows, print less or lower the rate with `set_sample_rate()`. Filters need that same rate: `set_mains_notch(50, emg.sample_rate_hz())`.
73
+
74
+ ## API Reference
75
+
76
+ | Method | Description |
77
+ |---|---|
78
+ | `EasyEMG(i2c, address=0x48)` / `begin()` | Construct / configure the ADC. `begin()` returns `False` if the board doesn't answer |
79
+ | `update()` | Non-blocking. Takes one sample when the clock is due; `True` if it did |
80
+ | `last_microvolts()` / `last_envelope()` | The latest sample and RMS envelope, in µV at the electrodes |
81
+ | `set_sample_rate(hz)` / `missed_samples()` | Paced clock, default 500 SPS / samples it couldn't take on time |
82
+ | `set_envelope_window(n)` | 1–128 samples, default 64 (128 ms at 500 SPS) |
83
+ | `set_mains_notch(hz, fs)` / `set_notch(hz, fs)` / `set_high_pass(hz, fs)` | Optional filters, off by default |
84
+ | `calibrate_baseline(n)` / `set_baseline(n)` | Measure and subtract the resting offset |
85
+ | `set_activation_threshold(uv)` / `is_active()` | Compare the envelope with a threshold |
86
+ | `set_mvc(uv)` / `read_percent_mvc()` / `MvcRecorder` | %MVC normalisation and the MVC recording step |
87
+
88
+ The full API, including the unpaced reads and low-level ADC passthroughs, is documented in [protocentral_easyemg.py](https://github.com/Protocentral/protocentral-micropython-easyemg/blob/main/protocentral_easyemg.py).
89
+
90
+ ## Examples
91
+
92
+ | # | Example | Description |
93
+ |---|---|---|
94
+ | 01 | `easyemg_simpletest.py` / `easyemg_raspberrypi.py` | Raw µV and envelope (MicroPython / Raspberry Pi) |
95
+ | 02 | `easyemg_plotter.py` | sEMG and envelope for Thonny's plotter |
96
+ | 03 | `easyemg_calibration.py` | Baseline zeroing and your resting noise floor |
97
+ | 04 | `easyemg_envelope.py` | RMS envelope, MVC recording and %MVC |
98
+ | 05 | `easyemg_activation_led.py` | Flex to light an LED, with an automatic threshold and hysteresis |
99
+ | 06 | `easyemg_openview.py` | Stream to [OpenView](https://github.com/Protocentral/protocentral_openview) 3.4+ (**easyEMG Breakout**), with the MVC step. Save it to the board as `main.py`, then close Thonny or `mpremote` before connecting OpenView |
100
+ | 07 | `easyemg_diagnostics.py` | I²C, ADC, sample-rate, noise and mains-hum checks. **Run this first if something is wrong** |
101
+
102
+ The examples default to QT Py ESP32-C3 pins: change the `I2C(...)` line for your board.
103
+
104
+ ## License
105
+
106
+ - **Software:** MIT, see [LICENSE.md](https://github.com/Protocentral/protocentral-micropython-easyemg/blob/main/LICENSE.md)
107
+ - **Hardware:** CERN-OHL-P v2, in the [hardware repo](https://github.com/Protocentral/protocentral_easyemg_hardware)
108
+
109
+ Copyright (c) 2026 Protocentral Electronics.
110
+
111
+ ## Support
112
+
113
+ Open an issue on [GitHub](https://github.com/Protocentral/protocentral-micropython-easyemg/issues) or email support@protocentral.com.
@@ -0,0 +1,116 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # SPDX-FileCopyrightText: Copyright (c) 2026 Ashwin Whitchurch, Protocentral Electronics
3
+ #
4
+ # easyemg_activation_led.py - Arduino counterpart: 05.MuscleActivation_LED
5
+ #
6
+ # Flex the muscle, the LED lights. Runs on MicroPython and on a Raspberry Pi.
7
+ #
8
+ # The classic muscle-sensor demo, and the starting point for prosthetics and gesture
9
+ # control. The threshold is set from YOUR resting noise floor rather than hard-coded,
10
+ # because the right number depends on electrode placement, skin prep and the person: the
11
+ # script measures the resting envelope, then triggers at a multiple of it.
12
+ #
13
+ # Hysteresis matters. A single threshold makes the LED chatter whenever the envelope sits
14
+ # near it, so this uses separate ON and OFF levels: a Schmitt trigger, in software.
15
+ #
16
+ # FLEX / relax is printed either way, so it works with no LED. Set LED_PIN for yours.
17
+ #
18
+ # For research and education only - not a medical device. With electrodes on a person,
19
+ # power the host from a battery or connect through an easyISO isolated Qwiic adapter.
20
+
21
+ try:
22
+ from machine import Pin, I2C
23
+ # Defaults for the Adafruit QT Py ESP32-C3 (SCL=6, SDA=5). Change for your board.
24
+ i2c = I2C(0, scl=Pin(6), sda=Pin(5), freq=400000)
25
+ except ImportError: # Raspberry Pi
26
+ from protocentral_tla20xx_linux import I2C
27
+ i2c = I2C(bus=1)
28
+ Pin = None
29
+
30
+ from protocentral_easyemg import EasyEMG, sleep_ms
31
+
32
+ # MicroPython: "LED" on a Pico / Pico W, 2 on many ESP32 dev boards. The QT Py ESP32-C3
33
+ # has only an RGB NeoPixel, so wire an LED (with a resistor) to a free pin.
34
+ # Raspberry Pi: a BCM pin number, driven through gpiozero.
35
+ LED_PIN = None
36
+
37
+ TRIGGER_MULTIPLE = 4.0 # ON at 4x the resting noise floor ...
38
+ RELEASE_MULTIPLE = 2.5 # ... OFF below 2.5x
39
+ ENVELOPE_WINDOW = 32 # samples: 64 ms at 500 SPS, quicker to react
40
+ REST_SECONDS = 2
41
+
42
+
43
+ def make_led(pin):
44
+ """A function that sets the LED on (1) or off (0), or None with no LED."""
45
+ if pin is None:
46
+ return None
47
+ if Pin is not None:
48
+ p = Pin(pin, Pin.OUT)
49
+ return p.value
50
+ from gpiozero import LED # Raspberry Pi
51
+ led = LED(pin)
52
+ return lambda v: led.on() if v else led.off()
53
+
54
+
55
+ set_led = make_led(LED_PIN)
56
+ if set_led:
57
+ set_led(0)
58
+
59
+ emg = EasyEMG(i2c) # ships at 0x48
60
+ if not emg.begin():
61
+ raise OSError("easyEMG not found - check wiring and I2C address (0x48)")
62
+
63
+ print("Relax the muscle. Measuring your resting noise floor...")
64
+ sleep_ms(1000)
65
+ emg.calibrate_baseline()
66
+ emg.set_envelope_window(ENVELOPE_WINDOW)
67
+
68
+ # Average the resting envelope over a couple of seconds rather than trusting one reading.
69
+ total = 0.0
70
+ n = 0
71
+ target = REST_SECONDS * emg.sample_rate_hz()
72
+ while n < target:
73
+ if emg.update():
74
+ total += emg.last_envelope()
75
+ n += 1
76
+ resting = total / n
77
+
78
+ # A dead-quiet (or stuck) ADC gives a resting level of 0, and a 0 threshold would light the
79
+ # LED permanently. Hold the floor at one ADC count.
80
+ floor = emg.microvolts_per_count()
81
+ if resting < floor:
82
+ print("Resting level is under one ADC count. If every reading stays at 0, run")
83
+ print("easyemg_diagnostics.py: step 4 tells a quiet board from a stuck ADC.")
84
+ resting = floor
85
+
86
+ on_threshold = resting * TRIGGER_MULTIPLE
87
+ off_threshold = resting * RELEASE_MULTIPLE
88
+ emg.set_activation_threshold(on_threshold)
89
+
90
+ print("Resting level : {:.2f} uV RMS".format(resting))
91
+ print("Trigger at : {:.2f} uV RMS".format(on_threshold))
92
+ print("Release at : {:.2f} uV RMS".format(off_threshold))
93
+ print()
94
+ print("Flex the muscle.")
95
+
96
+ muscle_on = False
97
+ while True:
98
+ if not emg.update():
99
+ continue
100
+ envelope = emg.last_envelope()
101
+
102
+ # Schmitt trigger: different levels for turning on and off.
103
+ if not muscle_on and envelope >= on_threshold:
104
+ muscle_on = True
105
+ if set_led:
106
+ set_led(1)
107
+ print("FLEX {:.1f} uV".format(envelope))
108
+ elif muscle_on and envelope < off_threshold:
109
+ muscle_on = False
110
+ if set_led:
111
+ set_led(0)
112
+ print("relax")
113
+
114
+ # emg.is_active() is the simple single-threshold version, if you don't want
115
+ # hysteresis:
116
+ # if emg.is_active(): ...
@@ -0,0 +1,68 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # SPDX-FileCopyrightText: Copyright (c) 2026 Ashwin Whitchurch, Protocentral Electronics
3
+ #
4
+ # easyemg_calibration.py - Arduino counterpart: 03.Calibration
5
+ #
6
+ # Zeroes the resting offset and measures your noise floor. Runs on MicroPython and on a
7
+ # Raspberry Pi.
8
+ #
9
+ # Do this every session, per subject. The board reads differentially against its own
10
+ # buffered mid-rail, so the resting count is already near zero, but amplifier offset leaves
11
+ # a few counts behind. calibrate_baseline() averages the resting signal and subtracts it.
12
+ #
13
+ # The front-end gain (x51.7) is set by the onboard resistors, so there is nothing to
14
+ # calibrate there. The script prints it for reference.
15
+ #
16
+ # With the muscle relaxed, the resting envelope should sit close to the board's resolution
17
+ # (a few uV). If it is much higher, suspect mains pickup, long electrode leads or poor skin
18
+ # prep, and run easyemg_diagnostics.py.
19
+ #
20
+ # For research and education only - not a medical device. With electrodes on a person,
21
+ # power the host from a battery or connect through an easyISO isolated Qwiic adapter.
22
+
23
+ try:
24
+ from machine import Pin, I2C
25
+ # Defaults for the Adafruit QT Py ESP32-C3 (SCL=6, SDA=5). Change for your board.
26
+ i2c = I2C(0, scl=Pin(6), sda=Pin(5), freq=400000)
27
+ except ImportError: # Raspberry Pi
28
+ from protocentral_tla20xx_linux import I2C
29
+ i2c = I2C(bus=1)
30
+
31
+ from protocentral_easyemg import EasyEMG, sleep_ms
32
+
33
+ BASELINE_SAMPLES = 512
34
+ ENVELOPE_WINDOW = 64 # samples: 128 ms at 500 SPS
35
+ NOISE_SECONDS = 2
36
+
37
+ emg = EasyEMG(i2c) # ships at 0x48
38
+ if not emg.begin():
39
+ raise OSError("easyEMG not found - check wiring and I2C address (0x48)")
40
+
41
+ print("=== easyEMG calibration ===")
42
+ print("Front-end gain : x{:.2f}".format(emg.get_gain()))
43
+ print("ADC full scale : +/-{:.3f} V".format(emg.full_scale_volts()))
44
+ print("Resolution at the skin: {:.2f} uV per count".format(emg.microvolts_per_count()))
45
+ print()
46
+
47
+ print("Relax the muscle completely. Measuring baseline in 3 seconds...")
48
+ sleep_ms(3000)
49
+ emg.calibrate_baseline(BASELINE_SAMPLES)
50
+ print("Baseline offset: {} counts".format(emg.get_baseline()))
51
+ print()
52
+
53
+ # Paced on the driver's clock, so the envelope covers a known time span.
54
+ print("Measuring resting noise floor ({} s)...".format(NOISE_SECONDS))
55
+ emg.set_envelope_window(ENVELOPE_WINDOW)
56
+ n = 0
57
+ target = NOISE_SECONDS * emg.sample_rate_hz()
58
+ while n < target:
59
+ if emg.update():
60
+ n += 1
61
+ print("Resting envelope: {:.2f} uV RMS".format(emg.last_envelope()))
62
+ if emg.missed_samples():
63
+ print("({} samples missed - the host couldn't hold {} SPS)".format(
64
+ emg.missed_samples(), emg.sample_rate_hz()))
65
+ print()
66
+
67
+ print("Copy this line into your own script, after emg.begin():")
68
+ print(" emg.set_baseline({})".format(emg.get_baseline()))