A C library for interfacing the M5Stack Unit 8Angle with the Raspberry Pi Pico using the Pico SDK.
The Unit 8Angle is an input module featuring 8 adjustable potentiometers, a toggle switch, and 9 individually addressable RGB LEDs (SK6812). It communicates via I2C (default address: 0x43) and is useful for applications requiring multiple analog inputs with visual feedback.
- Analog Input Reading: Read potentiometer values in 8-bit (0-255) or 12-bit (0-4095) resolution
- Normalized Values: Corrects hardware quirks where potentiometers don't reach documented maximum values
- Bulk Operations: Read all potentiometers or set all LEDs efficiently
- Switch State: Read the digital toggle switch state
- RGB LED Control: Set individual LED colors with brightness control
- Device Management: Read firmware version and change I2C address
- Error Handling: All functions return status codes
The library includes corrections for observed hardware behavior:
- 12-bit readings top out at ~4088-4091 instead of 4095
- 8-bit readings top out at 254 instead of 255
- Potentiometer maximum value is at the leftmost physical position
The unit_8angle_get_analog_input_* functions normalize and reverse these values to the full documented range.
All functions return a status code:
typedef enum {
UNIT_8ANGLE_OK = 0,
UNIT_8ANGLE_I2C_ERROR,
UNIT_8ANGLE_I2C_TIMEOUT,
UNIT_8ANGLE_INVALID_INDEX,
UNIT_8ANGLE_NOT_INITIALIZED
} unit_8angle_status_t;unit_8angle_status_t unit_8angle_init(i2c_inst_t *i2c, uint sda_pin, uint scl_pin, uint baudrate);
bool unit_8angle_is_initialized(void);// Single read (normalized values)
unit_8angle_status_t unit_8angle_get_analog_input_12bits(uint8_t index, uint16_t *value);
unit_8angle_status_t unit_8angle_get_analog_input_8bits(uint8_t index, uint8_t *value);
// Bulk read all 8 potentiometers
unit_8angle_status_t unit_8angle_get_all_analog_inputs_12bits(uint16_t values[8]);
unit_8angle_status_t unit_8angle_get_all_analog_inputs_8bits(uint8_t values[8]);unit_8angle_status_t unit_8angle_get_switch_enabled(bool *enabled);typedef struct Unit8angleLedParams {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t brightness;
} Unit8angleLedParams;
unit_8angle_status_t unit_8angle_set_led_color(uint8_t index, Unit8angleLedParams led_params);
unit_8angle_status_t unit_8angle_reset_led_color(uint8_t index);
unit_8angle_status_t unit_8angle_set_all_leds(Unit8angleLedParams led_params);
unit_8angle_status_t unit_8angle_reset_all_leds(void);LED indices: 0-7 correspond to each potentiometer, 8 is the center LED.
UNIT_8ANGLE_LED(red, green, blue, brightness)
UNIT_8ANGLE_LED_OFF
UNIT_8ANGLE_LED_RED(brightness)
UNIT_8ANGLE_LED_GREEN(brightness)
UNIT_8ANGLE_LED_BLUE(brightness)
UNIT_8ANGLE_LED_WHITE(brightness)unit_8angle_status_t unit_8angle_get_firmware_version(uint8_t *version);unit_8angle_status_t unit_8angle_change_i2c_address(uint8_t new_addr);
void unit_8angle_set_device_address(uint8_t addr);
uint8_t unit_8angle_get_device_address(void);unit_8angle_status_t unit_8angle_read(uint8_t reg, uint8_t *buffer, uint8_t length);
unit_8angle_status_t unit_8angle_write(uint8_t reg, const uint8_t *buffer, uint8_t length);#include "pico/stdlib.h"
#include "unit_8angle.h"
int main() {
stdio_init_all();
unit_8angle_status_t status = unit_8angle_init(
i2c_default,
PICO_DEFAULT_I2C_SDA_PIN,
PICO_DEFAULT_I2C_SCL_PIN,
100 * 1000
);
if (status != UNIT_8ANGLE_OK) {
return 1;
}
while (true) {
uint8_t value;
unit_8angle_get_analog_input_8bits(0, &value);
unit_8angle_set_led_color(0, UNIT_8ANGLE_LED(value, 0, 255 - value, 50));
}
}The Unit 8Angle can enter a stalled state where it stops responding to I2C. A full power cycle — not a Pico software reset — is required to recover. The 8Angle's onboard MCU retains its stalled state through a Pico watchdog reboot because its own VCC is unaffected.
Wire a logic-level N-channel MOSFET between the Pico and the 8Angle GND pin. A GPIO output controls the gate, cutting and restoring power programmatically.
| Part | Value | Purpose |
|---|---|---|
| R1 | 100 Ω | Limits gate current during switching transients |
| R2 | 10 kΩ | Pull-down — keeps MOSFET off while GPIO is uninitialized at boot |
| MOSFET | N-ch logic-level, Vgs(th) < 2 V (e.g. 2N7002, BSS138, AO3400) | Switches 8Angle GND |
Logic: GPIO HIGH → MOSFET ON → 8Angle powered. GPIO LOW → MOSFET OFF → power cut.
I2C lines during power cut: VCC stays connected, so the 8Angle's pull-up resistors hold SDA/SCL HIGH (I2C idle state). No backfeed risk. After power is restored, call unit_8angle_init before issuing any I2C commands.
// Call once before unit_8angle_init, passing the GPIO pin driving the MOSFET gate.
void unit_8angle_power_ctrl_init(uint gpio_pin);
// Cut power for off_ms ms, wait UNIT_8ANGLE_POWER_CYCLE_BOOT_MS for boot, then return.
// Caller must call unit_8angle_init afterwards.
void unit_8angle_power_cycle(uint off_ms);
// Returns true if power control was configured via unit_8angle_power_ctrl_init.
bool unit_8angle_power_ctrl_enabled(void);Defaults (overridable via #define before including the header):
| Macro | Default | Description |
|---|---|---|
UNIT_8ANGLE_POWER_CYCLE_OFF_MS |
200 ms | Duration power is cut |
UNIT_8ANGLE_POWER_CYCLE_BOOT_MS |
500 ms | Post-restore delay before I2C is ready |
Usage — see hello_8angle.c for a complete example. The key pattern:
#define POWER_CTRL_PIN 15
unit_8angle_power_ctrl_init(POWER_CTRL_PIN);
// ... init and main loop with error counting ...
if (err_count >= I2C_ERR_THRESHOLD) {
unit_8angle_power_cycle(UNIT_8ANGLE_POWER_CYCLE_OFF_MS);
unit_8angle_init(i2c_default, SDA_PIN, SCL_PIN, 400 * 1000);
err_count = 0;
}MIT