Add support for reading and writing to I2C devices from lua (#934)

* Add i2c read support to lua

* Add i2c write support to lua

With memory permissions before write

* Adjust i2c write

* Fix inverted id check, better permissions

* Change write to not return anything on success

* Add some documentation for lua i2c

* Change write length from 1024 to 64

* Add whitelist for i2c writing

* Move i2c module into preload

* Add registers and bitmasks for mcu

Add documentation to lua-doc

* Fix missing column divider in lua-doc

* Add ~= 0 then to lua example

* Add some more registers
This commit is contained in:
Tage Mellemstrand 2026-03-25 23:18:03 +01:00 committed by GitHub
parent 468a91b414
commit 584b68b7b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 715 additions and 0 deletions

View File

@ -0,0 +1,147 @@
#ifndef NO_LUA
#include "gm9internali2c.h"
#include "fsperm.h"
#include "system/i2c.h"
typedef struct {
uint8_t dev_id;
uint8_t reg_addr_start;
uint8_t reg_addr_end;
} WhiteListPair;
static const WhiteListPair WriteWhitelist[] = {
// Device_id, Register_address, Register_address_end
{ I2C_DEV_MCU, 0x15, 0x17 },
{ I2C_DEV_MCU, 0x1c, 0x1f },
{ I2C_DEV_MCU, 0x28, 0x29 },
{ I2C_DEV_MCU, 0x2a, 0x2d },
};
static bool IsWriteAllowed(uint8_t dev_id, uint8_t reg_addr)
{
for (unsigned i = 0; i < sizeof(WriteWhitelist)/sizeof(WriteWhitelist[0]); i++) {
if (WriteWhitelist[i].dev_id == dev_id &&
reg_addr >= WriteWhitelist[i].reg_addr_start &&
reg_addr <= WriteWhitelist[i].reg_addr_end)
return true;
}
return false;
}
static int i2c_read(lua_State *L) {
int dev_id = luaL_checkinteger(L, 1);
int reg_addr = luaL_checkinteger(L, 2);
int length = luaL_checkinteger(L, 3);
if (dev_id < 0 || dev_id > 17) {
return luaL_error(L, "Invalid device ID: %d (must be 0-17)", dev_id);
}
if (length <= 0 || length > 64) {
return luaL_error(L, "Invalid length: %d (must be 1-64)", length);
}
if (reg_addr < 0 || reg_addr > 255) {
return luaL_error(L, "Invalid register address: %d (must be 0-255)", reg_addr);
}
// Create a buffer for the read data
u8 *buffer = malloc(length);
if (!buffer) {
return luaL_error(L, "Memory allocation failed");
}
bool success = I2C_readRegBuf((I2cDevice)dev_id, (u8)reg_addr, buffer, (u32)length);
if (success) {
// Create a Lua table to hold the byte values
lua_createtable(L, length, 0);
// Fill the table with byte values as integers
for (int i = 0; i < length; i++) {
lua_pushinteger(L, buffer[i]); // Push byte value as integer
lua_rawseti(L, -2, i + 1); // Set table[i+1] = byte
}
free(buffer);
return 1;
} else {
free(buffer);
return luaL_error(L, "I2C read failed");
}
}
static int i2c_write(lua_State *L) {
SetWritePermissionsLuaError(L, PERM_MEMORY);
int dev_id = luaL_checkinteger(L, 1);
int reg_addr = luaL_checkinteger(L, 2);
if (!lua_istable(L, 3)) {
return luaL_error(L, "Third parameter must be a table of byte values");
}
int length = lua_rawlen(L, 3);
if (dev_id < 0 || dev_id > 17) {
return luaL_error(L, "Invalid device ID: %d (must be 0-17)", dev_id);
}
if (length <= 0 || length > 64) {
return luaL_error(L, "Invalid data length: %d (must be 1-64)", length);
}
if (reg_addr < 0 || reg_addr > 255) {
return luaL_error(L, "Invalid register address: %d (must be 0-255)", reg_addr);
}
if (!IsWriteAllowed(dev_id, reg_addr)) {
return luaL_error(L, "Write to device %d, register %d is not allowed", dev_id, reg_addr);
}
// Create a buffer for the write data
u8 *buffer = malloc(length);
if (!buffer) {
return luaL_error(L, "Memory allocation failed");
}
// Extract byte values from Lua table
for (int i = 0; i < length; i++) {
lua_rawgeti(L, 3, i + 1); // Get table[i+1]
if (!lua_isinteger(L, -1)) {
free(buffer);
return luaL_error(L, "Table element %d is not an integer", i + 1);
}
int value = lua_tointeger(L, -1);
if (value < 0 || value > 255) {
free(buffer);
return luaL_error(L, "Table element %d is out of range: %d (must be 0-255)", i + 1, value);
}
buffer[i] = (u8)value;
lua_pop(L, 1); // Remove the value from stack
}
bool success = I2C_writeRegBuf((I2cDevice)dev_id, (u8)reg_addr, buffer, (u32)length);
free(buffer);
if (success) {
return 0;
} else {
return luaL_error(L, "I2C write failed");
}
}
static const luaL_Reg i2c[] = {
{"read", i2c_read},
{"write", i2c_write},
{NULL, NULL}
};
int gm9lua_open_internali2c(lua_State* L) {
luaL_newlib(L, i2c);
return 1;
}
#endif

View File

@ -0,0 +1,6 @@
#pragma once
#include "gm9lua.h"
#define GM9LUA_I2CLIBNAME "_i2c"
int gm9lua_open_internali2c(lua_State* L);

View File

@ -13,6 +13,7 @@
#include "gm9os.h" #include "gm9os.h"
#include "gm9ui.h" #include "gm9ui.h"
#include "gm9title.h" #include "gm9title.h"
#include "gm9internali2c.h"
#include "gm9internalfs.h" #include "gm9internalfs.h"
#include "gm9internalsys.h" #include "gm9internalsys.h"
@ -90,6 +91,12 @@ void CheckWritePermissionsLuaError(lua_State* L, const char* path) {
} }
} }
void SetWritePermissionsLuaError(lua_State* L, u32 perm) {
if (!SetWritePermissions(perm, true)) {
luaL_error(L, "failed to set write permissions");
}
}
static const luaL_Reg gm9lualibs[] = { static const luaL_Reg gm9lualibs[] = {
// built-ins // built-ins
{LUA_GNAME, luaopen_base}, {LUA_GNAME, luaopen_base},
@ -109,6 +116,7 @@ static const luaL_Reg gm9lualibs[] = {
// gm9 custom internals (usually wrapped by a pure lua module) // gm9 custom internals (usually wrapped by a pure lua module)
{GM9LUA_INTERNALFSLIBNAME, gm9lua_open_internalfs}, {GM9LUA_INTERNALFSLIBNAME, gm9lua_open_internalfs},
{GM9LUA_INTERNALSYSLIBNAME, gm9lua_open_internalsys}, {GM9LUA_INTERNALSYSLIBNAME, gm9lua_open_internalsys},
{GM9LUA_I2CLIBNAME, gm9lua_open_internali2c},
{NULL, NULL} {NULL, NULL}
}; };

View File

@ -45,5 +45,6 @@ static inline bool CheckLuaArgCountPlusExtra(lua_State* L, int argcount, const c
int LoadLuaFile(lua_State* L, const char* filename); int LoadLuaFile(lua_State* L, const char* filename);
u32 GetFlagsFromTable(lua_State* L, int pos, u32 flags_ext_starter, u32 allowed_flags); u32 GetFlagsFromTable(lua_State* L, int pos, u32 flags_ext_starter, u32 allowed_flags);
void CheckWritePermissionsLuaError(lua_State* L, const char* path); void CheckWritePermissionsLuaError(lua_State* L, const char* path);
void SetWritePermissionsLuaError(lua_State* L, u32 perm);
#endif #endif
bool ExecuteLuaScript(const char* path_script); bool ExecuteLuaScript(const char* path_script);

240
data/luapackages/i2c.lua Normal file
View File

@ -0,0 +1,240 @@
local i2c = {}
i2c.read = _i2c.read
i2c.write = _i2c.write
-- List of devices on the I2C bus
i2c.dev = {
POWER = 0, -- Unconfirmed
CAMERA = 1, -- Unconfirmed
CAMERA2 = 2, -- Unconfirmed
MCU = 3,
CAMERA3 = 4,
LCD1 = 5,
LCD2 = 6,
GYRO3 = 9,
GYRO = 10,
GYRO2 = 11,
DEBUG_PAD = 12,
IR = 13,
EEPROM = 14, -- Unconfirmed
NFC = 15,
QTM = 16,
N3DS_HID = 17
}
-- Sub-tables for specific devices, containing register addresses and some bitmasks
i2c.mcu = {
-- Bitmasks for the 4-byte interupt registers.
interrupt_1 = {
POWER_BUTTON_HELD = 1 << 1, -- The power button was held.
HOME_BUTTON_PRESS = 1 << 2, -- The HOME button was pressed.
HOME_BUTTON_RELEASE = 1 << 3, -- The HOME button was released.
WLAN_SWITCH_TRIGGER = 1 << 4, -- The WiFi switch was triggered.
SHELL_CLOSE = 1 << 5, -- The shell was closed (or sleep switch turned off).
SHELL_OPEN = 1 << 6, -- The shell was opened (or sleep switch turned on).
FATAL_HW_ERROR = 1 << 7, -- MCU watchdog reset occurred.
},
interrupt_2 = {
AC_ADAPTER_REMOVED = 1 << 0, -- The AC adapter was disconnected.
AC_ADAPTER_PLUGGED_IN = 1 << 1, -- The AC adapter was connected.
RTC_ALARM = 1 << 2, -- The RTC alarm time has been reached.
ACCELEROMETER_I2C_MANUAL_IO = 1 << 3, -- The manual accelerometer I²C read/write operation has completed.
ACCELEROMETER_NEW_SAMPLE = 1 << 4, -- A new accelerometer sample is available.
CRITICAL_BATTERY = 1 << 5, -- The battery is critically low.
CHARGING_STOP = 1 << 6, -- The battery stopped charging.
CHARGING_START = 1 << 7, -- The battery started charging.
},
interrupt_3 = {
VOL_SLIDER = 1 << 6, -- The position of the volume slider changed.
},
interrupt_4 = {
LCD_OFF = 1 << 0, -- The LCDs turned off.
LCD_ON = 1 << 1, -- The LCDs turned on.
BOT_BACKLIGHT_OFF = 1 << 2, -- The bottom screen backlight turned off.
BOT_BACKLIGHT_ON = 1 << 3, -- The bottom screen backlight turned on.
TOP_BACKLIGHT_OFF = 1 << 4, -- The top screen backlight turned off.
TOP_BACKLIGHT_ON = 1 << 5, -- The top screen backlight turned on.
},
-- Register addresses
reg = {
VERSION_HIGH = 0x0, -- Major version of the MCU firmware.
VERSION_LOW = 0x1, -- Minor version of the MCU firmware.
RESET_EVENTS = 0x2, -- @ref mcu.reset_event_flags
VCOM_TOP = 0x3, -- Flicker/VCOM value for the top screen.
VCOM_BOTTOM = 0x4, -- Flicker/VCOM value for the bottom screen.
FIRMWARE_UPLOAD_0 = 0x5, -- Firmware upload register.
FIRMWARE_UPLOAD_1 = 0x6, -- Firmware upload register.
FIRMWARE_UPLOAD_2 = 0x7, -- Firmware upload register.
RAW_3D_SLIDER_POSITION = 0x8, -- Position of the 3D slider.
VOLUME_SLIDER_POSITION = 0x9, -- Position of the volume slider.
BATTERY_PCB_TEMPERATURE = 0xA, -- Temperature of the battery, measured on a sensor on the PCB.
BATTERY_PERCENTAGE_INT = 0xB, -- Integer part of the battery percentage.
BATTERY_PERCENTAGE_FRAC = 0xC, -- Fractional part of the battery percentage.
BATTERY_VOLTAGE = 0xD, -- Voltage of the battery, in units of 20 mV.
HW_STATUS = 0xE, -- Hardware status bits
POWER_STATUS = 0xF, -- @ref mcu.power_status_flags
LEGACY_VERSION_HIGH = 0xF, -- (Old MCU_FIRM only) Major firmware version.
LEGACY_VERSION_LOW = 0x10, -- (Old MCU_FIRM only) Minor firmware version.
LEGACY_FIRM_UPLOAD = 0x3B, -- (Old MCU_FIRM only) Firmware upload register.
RECEIVED_IRQS_1 = 0x10, -- Bitmask of received IRQs. @ref mcu.interrupt_1
RECEIVED_IRQS_2 = 0x11, -- Bitmask of received IRQs. @ref mcu.interrupt_2
RECEIVED_IRQS_3 = 0x12, -- Bitmask of received IRQs. @ref mcu.interrupt_3
RECEIVED_IRQS_4 = 0x13, -- Bitmask of received IRQs. @ref mcu.interrupt_4
UNUSED_RO_14 = 0x14, -- Unused read-only register.
USERDATA_RAM_15 = 0x15, -- Unused register, supports writing
USERDATA_RAM_16 = 0x16, -- Unused register, supports writing
USERDATA_RAM_17 = 0x17, -- Unused register, supports writing
IRQ_MASK_1 = 0x18, -- Bitmask of enabled IRQs. @ref mcu.interrupt_1
IRQ_MASK_2 = 0x19, -- Bitmask of enabled IRQs. @ref mcu.interrupt_2
IRQ_MASK_3 = 0x1A, -- Bitmask of enabled IRQs. @ref mcu.interrupt_3
IRQ_MASK_4 = 0x1B, -- Bitmask of enabled IRQs. @ref mcu.interrupt_4
USERDATA_RAM_1C = 0x1C, -- Unused register, supports writing
USERDATA_RAM_1D = 0x1D, -- Unused register, supports writing
USERDATA_RAM_1E = 0x1E, -- Unused register, supports writing
USERDATA_RAM_1F = 0x1F, -- Unused register, supports writing
PWR_CTL = 0x20, -- @ref mcu.power_trigger
LCD_PWR_CTL = 0x22, -- LCD Power control.
MCU_RESET_CTL = 0x23, -- Writing 'r' to this register resets the MCU. Stubbed on retail.
FORCE_SHUTDOWN_DELAY = 0x24, -- The amount of time, in units of 0.125s, for which the power button needs to be held to trigger a hard shutdown.
VOLUME_UNK_25 = 0x25, -- Unknown register. Used in mcu::SND
UNK_26 = 0x26, -- Unknown register. Used in mcu::CDC
VOLUME_SLIDER_RAW = 0x27, -- Raw value of the volume slider, in the range of 0-255
LED_BRIGHTNESS_STATE = 0x28, -- Brightness of the status LEDs.
POWER_LED_STATE = 0x29, -- @ref mcu.power_led_state
WLAN_LED_STATE = 0x2A, -- Controls the WiFi LED.
CAMERA_LED_STATE = 0x2B, -- Controls the camera LED (on models that have one).
LED_3D_STATE = 0x2C, -- Controls the 3D LED (on models that have one).
NOTIFICATION_LED_STATE = 0x2D, -- Controls the info (notification) LED.
NOTIFICATION_LED_CYCLE_STATE = 0x2E, -- Bit 0 is set if the info (notification) LED has started a new cycle of its pattern.
RTC_TIME_SECOND = 0x30, -- RTC second.
RTC_TIME_MINUTE = 0x31, -- RTC minute.
RTC_TIME_HOUR = 0x32, -- RTC hour.
RTC_TIME_WEEKDAY = 0x33, -- RTC day of the week.
RTC_TIME_DAY = 0x34, -- RTC day of the month.
RTC_TIME_MONTH = 0x35, -- RTC month.
RTC_TIME_YEAR = 0x36, -- RTC year.
RTC_TIME_CORRECTION = 0x37, -- RTC subsecond (RSUBC) correction value.
RTC_ALARM_MINUTE = 0x38, -- RTC alarm minute.
RTC_ALARM_HOUR = 0x39, -- RTC alarm hour.
RTC_ALARM_DAY = 0x3A, -- RTC alarm day.
RTC_ALARM_MONTH = 0x3B, -- RTC alarm month.
RTC_ALARM_YEAR = 0x3C, -- RTC alarm year.
TICK_COUNTER_LSB = 0x3D, -- MCU tick counter value (low byte).
TICK_COUNTER_MSB = 0x3E, -- MCU tick counter value (high byte).
UNK_3F = 0x3F, -- Unknown register
SENSOR_CONFIG = 0x40, -- @ref mcu.sensor_config
ACCELEROMETER_MANUAL_REGID_R = 0x41, -- Hardware register ID for use in manual accelerometer I²C reads.
ACCELEROMETER_MANUAL_REGID_W = 0x43, -- Hardware reigster ID for use in manual accelerometer I²C writes.
ACCELEROMETER_MANUAL_IO = 0x44, -- Data register for manual accelerometer reads/writes.
ACCELEROMETER_OUTPUT_X_LSB = 0x45, -- Accelerometer X coordinate (low byte).
ACCELEROMETER_OUTPUT_X_MSB = 0x46, -- Accelerometer X coordinate (high byte).
ACCELEROMETER_OUTPUT_Y_LSB = 0x47, -- Accelerometer Y coordinate (low byte).
ACCELEROMETER_OUTPUT_Y_MSB = 0x48, -- Accelerometer Y coordinate (high byte).
ACCELEROMETER_OUTPUT_Z_LSB = 0x49, -- Accelerometer Z coordinate (low byte).
ACCELEROMETER_OUTPUT_Z_MSB = 0x4A, -- Accelerometer Z coordinate (high byte).
PEDOMETER_STEPS_LOWBYTE = 0x4B, -- Pedometer step count (low byte).
PEDOMETER_STEPS_MIDDLEBYTE = 0x4C, -- Pedometer step count (middle byte).
PEDOMETER_STEPS_HIGHBYTE = 0x4D, -- Pedometer step count (high byte).
PEDOMETER_CNT = 0x4E, -- @ref mcu.pedometer_control
PEDOMETER_STEP_DATA = 0x4F, -- Step history and time of last update.
PEDOMETER_WRAP_MINUTE = 0x50, -- The minute within each RTC hour at which the step history should roll into the next hour.
PEDOMETER_WRAP_SECOND = 0x51, -- The second within each RTC hour at which the step history should roll into the next hour.
VOLUME_CALIBRATION_MIN = 0x58, -- Lower bound of sound volume.
VOLUME_CALIBRATION_MAX = 0x59, -- Upper bound of sound volume.
STORAGE_AREA_OFFSET = 0x60, -- Offset within the storage area to read/write to.
STORAGE_AREA = 0x61, -- Input/output byte for write/read operations in the storage area.
INFO = 0x7F, -- System information register.
},
-- Bitmasks for the reset event flags
reset_event_flags = {
RTC_TIME_LOST = 1 << 0, -- RTC time was lost (as is the case when the battery is removed).
WATCHDOG_RESET = 1 << 1, -- MCU Watchdog reset occurred.
},
-- Bitmasks for the power status
power_status_flags = {
SHELL_OPEN = 1 << 1, -- Set if the shell is open.
ADAPTER_CONNECTED = 1 << 3, -- Set if the AC adapter is connected.
CHARGING = 1 << 4, -- Set if the battery is charging.
BOTTOM_BL_ON = 1 << 5, -- Set if the bottom backlight is on.
TOP_BL_ON = 1 << 6, -- Set if the top backlight is on.
LCD_ON = 1 << 7 -- Set if the LCDs are on.
},
-- Bitmasks for the pedometer control
pedometer_control = {
CLEAR = 1 << 0, -- Clear the step history.
STEPS_FULL = 1 << 4 -- Set when the step history is full.
},
-- Bitmasks for the power signal triggers
power_trigger = {
SHUTDOWN = 1 << 0, -- Turn off the system.
RESET = 1 << 1, -- Reset the MCU.
REBOOT = 1 << 2, -- Reboot the system.
LGY_SHUTDOWN = 1 << 3, -- Turn off the system. (Used by LgyBg)
SLEEP = 1 << 4, -- Signal to enter sleep mode.
OLDMCU_BL_OFF = 1 << 4, -- (Old MCU_FIRM only) turn the backlights off.
OLDMCU_BL_ON = 1 << 5, -- (Old MCU_FIRM only) turn the backlights on.
OLDMCU_LCD_OFF = 1 << 6, -- (Old MCU_FIRM only) turn the LCDs off.
OLDMCU_LCD_ON = 1 << 7, -- (Old MCU_FIRM only) turn the LCDs on.
},
power_led_state = {
NORMAL = 0, -- Fade power LED to blue, while checking for battery percentage.
FADE_BLUE = 1, -- Fade power LED to blue.
SLEEP_MODE = 2, -- The power LED pulses blue slowly, as it does in sleep mode.
OFF = 3, -- Power LED fades off.
RED = 4, -- Power LED instantaneously turns red.
BLUE = 5, -- Power LED instantaneously turns blue.
BLINK_RED = 6, -- Power LED and info (notification) LED blink red, as they do when the battery is critically low.
},
-- WiFi mode register values
wifi_mode = {
CTR = 0, -- 3DS WiFi mode.
MP = 1, -- DS[i] WiFi mode ("MP").
},
-- Sensor configuration register values
sensor_config = {
ACCELEROMETER_ENABLE = 1 << 0, -- If set, the accelerometer is enabled.
PEDOMETER_ENABLE = 1 << 1, -- If set, the pedometer is enabled.
},
-- Accelerometer scale settings
accelerometer_scale = {
SCALE_2G = 0x0, -- -2G to 2G
SCALE_4G = 0x1, -- -4G to 4G
SCALE_8G = 0x3, -- -8G to 8G
}
}
return i2c

View File

@ -6,3 +6,4 @@ fs = require('fs')
util = require('util') util = require('util')
io = require('io') io = require('io')
sys = require('sys') sys = require('sys')
i2c = require('i2c')

View File

@ -1127,3 +1127,315 @@ Determines if the currently executing script was directly run, or was imported b
> This is not well tested. > This is not well tested.
* **Returns:** `true` if the current script was imported as a module * **Returns:** `true` if the current script was imported as a module
---
### `i2c` module
#### i2c.dev ####
A list of devices for use with `i2c.read` and `i2c.write`
Lua | Value
-- | --
POWER | 0
POWER | 1
CAMERA2 | 2
MCU | 3
CAMERA3 | 4
LCD1 | 5
LCD2 | 6
GYRO3 | 9
GYRO | 10
GYRO2 | 11
DEBUG_PAD | 12
IR | 13
EEPROM | 14
NFC | 15
QTM | 16
N3DS_HID | 17
#### i2c.mcu ####
`i2c.mcu` contains registers and bitmasks for registers
##### i2c.mcu.reg #####
Contains some registers in the MCU
Lua | Value | Notes
-- | -- | --
VERSION_HIGH | 0x0 | Major version of the MCU firmware.
VERSION_LOW | 0x1 | Minor version of the MCU firmware.
RESET_EVENTS | 0x2 | @ref mcu.reset_event_flags
VCOM_TOP | 0x3 | Flicker/VCOM value for the top screen.
VCOM_BOTTOM | 0x4 | Flicker/VCOM value for the bottom screen.
FIRMWARE_UPLOAD_0 | 0x5 | Firmware upload register.
FIRMWARE_UPLOAD_1 | 0x6 | Firmware upload register.
FIRMWARE_UPLOAD_2 | 0x7 | Firmware upload register.
RAW_3D_SLIDER_POSITION | 0x8 | Position of the 3D slider.
VOLUME_SLIDER_POSITION | 0x9 | Position of the volume slider.
BATTERY_PCB_TEMPERATURE | 0xA | Temperature of the battery, measured on a sensor on the PCB.
BATTERY_PERCENTAGE_INT | 0xB | Integer part of the battery percentage.
BATTERY_PERCENTAGE_FRAC | 0xC | Fractional part of the battery percentage.
BATTERY_VOLTAGE | 0xD | Voltage of the battery, in units of 20 mV.
HW_STATUS | 0xE | Hardware status bits
POWER_STATUS | 0xF | @ref mcu.power_status_flags
LEGACY_VERSION_HIGH | 0xF | (Old MCU_FIRM only) Major firmware version.
LEGACY_VERSION_LOW | 0x10 | (Old MCU_FIRM only) Minor firmware version.
LEGACY_FIRM_UPLOAD | 0x3B | (Old MCU_FIRM only) Firmware upload register.
RECEIVED_IRQS_1 | 0x10 | Bitmask of received IRQs. @ref mcu.interrupt_1
RECEIVED_IRQS_2 | 0x11 | Bitmask of received IRQs. @ref mcu.interrupt_2
RECEIVED_IRQS_3 | 0x12 | Bitmask of received IRQs. @ref mcu.interrupt_3
RECEIVED_IRQS_4 | 0x13 | Bitmask of received IRQs. @ref mcu.interrupt_4
UNUSED_RO_14 | 0x14 | Unused read-only register.
USERDATA_RAM_15 | 0x15 | Unused register, supports writing
USERDATA_RAM_16 | 0x16 | Unused register, supports writing
USERDATA_RAM_17 | 0x17 | Unused register, supports writing
IRQ_MASK_1 | 0x18 | Bitmask of enabled IRQs. @ref mcu.interrupt_1
IRQ_MASK_2 | 0x19 | Bitmask of enabled IRQs. @ref mcu.interrupt_2
IRQ_MASK_3 | 0x1A | Bitmask of enabled IRQs. @ref mcu.interrupt_3
IRQ_MASK_4 | 0x1B | Bitmask of enabled IRQs. @ref mcu.interrupt_4
USERDATA_RAM_1C | 0x1C | Unused register, supports writing
USERDATA_RAM_1D | 0x1D | Unused register, supports writing
USERDATA_RAM_1E | 0x1E | Unused register, supports writing
USERDATA_RAM_1F | 0x1F | Unused register, supports writing
PWR_CTL | 0x20 | @ref mcu.power_trigger
LCD_PWR_CTL | 0x22 | LCD Power control
MCU_RESET_CTL | 0x23 | Writing 'r' to this register resets the MCU. Stubbed on retail.
FORCE_SHUTDOWN_DELAY | 0x24 | The amount of time, in units of 0.125s, for which the power button needs to be held to trigger a hard shutdown.
VOLUME_UNK_25 | 0x25 | Unknown register. Used in mcu::SND
UNK_26 | 0x26 | Unknown register. Used in mcu::CDC
VOLUME_SLIDER_RAW | 0x27 | Raw value of the volume slider, in the range of 0-255
LED_BRIGHTNESS_STATE | 0x28 | Brightness of the status LEDs.
POWER_LED_STATE | 0x29 | @ref mcu.power_led_state
WLAN_LED_STATE | 0x2A | Controls the WiFi LED.
CAMERA_LED_STATE | 0x2B | Controls the camera LED (on models that have one).
LED_3D_STATE | 0x2C | Controls the 3D LED (on models that have one).
NOTIFICATION_LED_STATE | 0x2D | Controls the info (notification) LED.
NOTIFICATION_LED_CYCLE_STATE | 0x2E | Bit 0 is set if the info (notification) LED has started a new cycle of its pattern.
RTC_TIME_SECOND | 0x30 | RTC second.
RTC_TIME_MINUTE | 0x31 | RTC minute.
RTC_TIME_HOUR | 0x32 | RTC hour.
RTC_TIME_WEEKDAY | 0x33 | RTC day of the week.
RTC_TIME_DAY | 0x34 | RTC day of the month.
RTC_TIME_MONTH | 0x35 | RTC month.
RTC_TIME_YEAR | 0x36 | RTC year.
RTC_TIME_CORRECTION | 0x37 | RTC subsecond (RSUBC) correction value.
RTC_ALARM_MINUTE | 0x38 | RTC alarm minute.
RTC_ALARM_HOUR | 0x39 | RTC alarm hour.
RTC_ALARM_DAY | 0x3A | RTC alarm day.
RTC_ALARM_MONTH | 0x3B | RTC alarm month.
RTC_ALARM_YEAR | 0x3C | RTC alarm year.
TICK_COUNTER_LSB | 0x3D | MCU tick counter value (low byte).
TICK_COUNTER_MSB | 0x3E | MCU tick counter value (high byte).
UNK_3F | 0x3F | Unknown register
SENSOR_CONFIG | 0x40 | @ref mcu.sensor_config
ACCELEROMETER_MANUAL_REGID_R | 0x41 | Hardware register ID for use in manual accelerometer I²C reads.
ACCELEROMETER_MANUAL_REGID_W | 0x43 | Hardware reigster ID for use in manual accelerometer I²C writes.
ACCELEROMETER_MANUAL_IO | 0x44 | Data register for manual accelerometer reads/writes.
ACCELEROMETER_OUTPUT_X_LSB | 0x45 | Accelerometer X coordinate (low byte).
ACCELEROMETER_OUTPUT_X_MSB | 0x46 | Accelerometer X coordinate (high byte).
ACCELEROMETER_OUTPUT_Y_LSB | 0x47 | Accelerometer Y coordinate (low byte).
ACCELEROMETER_OUTPUT_Y_MSB | 0x48 | Accelerometer Y coordinate (high byte).
ACCELEROMETER_OUTPUT_Z_LSB | 0x49 | Accelerometer Z coordinate (low byte).
ACCELEROMETER_OUTPUT_Z_MSB | 0x4A | Accelerometer Z coordinate (high byte).
PEDOMETER_STEPS_LOWBYTE | 0x4B | Pedometer step count (low byte).
PEDOMETER_STEPS_MIDDLEBYTE | 0x4C | Pedometer step count (middle byte).
PEDOMETER_STEPS_HIGHBYTE | 0x4D | Pedometer step count (high byte).
PEDOMETER_CNT | 0x4E | @ref mcu.pedometer_control
PEDOMETER_STEP_DATA | 0x4F | Step history and time of last update.
PEDOMETER_WRAP_MINUTE | 0x50 | The minute within each RTC hour at which the step history should roll into the next hour.
PEDOMETER_WRAP_SECOND | 0x51 | The second within each RTC hour at which the step history should roll into the next hour.
VOLUME_CALIBRATION_MIN | 0x58 | Lower bound of sound volume.
VOLUME_CALIBRATION_MAX | 0x59 | Upper bound of sound volume.
STORAGE_AREA_OFFSET | 0x60 | Offset within the storage area to read/write to.
STORAGE_AREA | 0x61 | Input/output byte for write/read operations in the storage area.
INFO | 0x7F | System information register.
##### i2c.mcu.interrupt_1 #####
Bitmask for interrupts (1)
Lua | Bit | Notes
-- | -- | --
POWER_BUTTON_PRESS | 0 | The power button was pressed
POWER_BUTTON_HELD | 1 | The power button was held
HOME_BUTTON_PRESS | 2 | The HOME button was pressed
HOME_BUTTON_RELEASE | 3 | The HOME button was released
WLAN_SWITCH_TRIGGER | 4 | The WiFi switch was triggered
SHELL_CLOSE | 5 | The shell was closed (or sleep switch turned off)
SHELL_OPEN | 6 | The shell was opened (or sleep switch turned on)
FATAL_HW_ERROR | 7 | MCU watchdog reset occurred
##### i2c.mcu.interrupt_2 #####
Bitmask for interrupts (2)
Lua | Bit | Notes
-- | -- | --
AC_ADAPTER_REMOVED | 0 | The AC adapter was disconnected
AC_ADAPTER_PLUGGED_IN | 1 | The AC adapter was connected
RTC_ALARM | 2 | The RTC alarm time has been reached
ACCELEROMETER_I2C_MANUAL_IO | 3 | The manual accelerometer I²C read/write operation has completed
ACCELEROMETER_NEW_SAMPLE | 4 | A new accelerometer sample is available
CRITICAL_BATTERY | 5 | The battery is critically low
CHARGING_STOP | 6 | The battery stopped charging
CHARGING_START | 7 | The battery started charging
##### i2c.mcu.interrupt_3 #####
Bitmask for interrupts (3)
Lua | Bit | Notes
-- | -- | --
VOL_SLIDER | 6 | The position of the volume slider changed
##### i2c.mcu.interrupt_4 #####
Bitmask for interrupts (4)
Lua | Bit | Notes
-- | -- | --
LCD_OFF | 0 | The LCDs turned off.
LCD_ON | 1 | The LCDs turned on.
BOT_BACKLIGHT_OFF | 2 | The bottom screen backlight turned off.
BOT_BACKLIGHT_ON | 3 | The bottom screen backlight turned on.
TOP_BACKLIGHT_OFF | 4 | The top screen backlight turned off.
TOP_BACKLIGHT_ON | 5 | The top screen backlight turned on.
##### i2c.mcu.reset_event_flags #####
Bitmasks for the reset event flags
Lua | Bit | Notes
-- | -- | --
RTC_TIME_LOST | 0 | RTC time was lost (as is the case when the battery is removed).
WATCHDOG_RESET | 1 | MCU Watchdog reset occurred.
##### i2c.mcu.power_status_flags #####
Bitmasks for the power status
Lua | Bit | Notes
-- | -- | --
SHELL_OPEN | 1 | Set if the shell is open.
ADAPTER_CONNECTED | 3 | Set if the AC adapter is connected.
CHARGING | 4 | Set if the battery is charging.
BOTTOM_BL_ON | 5 | Set if the bottom backlight is on.
TOP_BL_ON | 6 | Set if the top backlight is on.
LCD_ON | 7 | Set if the LCDs are on.
##### i2c.mcu.pedometer_control #####
Bitmasks for the pedometer control
Lua | Bit | Notes
-- | -- | --
CLEAR | 0 | Clear the step history.
STEPS_FULL | 4 | Set when the step history is full.
##### i2c.mcu.power_trigger #####
Bitmasks for the power signal triggers
Lua | Bit | Notes
-- | -- | --
SHUTDOWN | 0 | Turn off the system.
RESET | 1 | Reset the MCU.
REBOOT | 2 | Reboot the system.
LGY_SHUTDOWN | 3 | Turn off the system. (Used by LgyBg)
SLEEP | 4 | Signal to enter sleep mode.
OLDMCU_BL_OFF | 4 | (Old MCU_FIRM only) turn the backlights off.
OLDMCU_BL_ON | 5 | (Old MCU_FIRM only) turn the backlights on.
OLDMCU_LCD_OFF | 6 | (Old MCU_FIRM only) turn the LCDs off.
OLDMCU_LCD_ON | 7 | (Old MCU_FIRM only) turn the LCDs on.
##### i2c.mcu.power_led_state #####
Lua | Value | Notes
-- | -- | --
NORMAL | 0 | Fade power LED to blue, while checking for battery percentage.
FADE_BLUE | 1 | Fade power LED to blue.
SLEEP_MODE | 2 | The power LED pulses blue slowly, as it does in sleep mode.
OFF | 3 | Power LED fades off.
RED | 4 | Power LED instantaneously turns red.
BLUE | 5 | Power LED instantaneously turns blue.
BLINK_RED | 6 | Power LED and info (notification) LED blink red, as they do when the battery is critically low.
##### i2c.mcu.wifi_mode #####
WiFi mode register values
Lua | Value | Notes
-- | -- | --
CTR | 0 | 3DS WiFi mode.
MP | 1 | DS[i] WiFi mode ("MP").
##### i2c.mcu.sensor_config #####
Sensor configuration register values
Lua | Bit | Notes
-- | -- | --
ACCELEROMETER_ENABLE | 0 | If set, the accelerometer is enabled.
PEDOMETER_ENABLE | 1 | If set, the pedometer is enabled.
##### i2c.mcu.accelerometer_scale #####
Accelerometer scale settings
Lua | Value | Notes
-- | -- | --
SCALE_2G | 0x0 | -2G to 2G
SCALE_4G | 0x1 | -4G to 4G
SCALE_8G | 0x3 | -8G to 8G
#### i2c.read
* `table i2c.read(int device_id, int register_address, int length)`
Read data from an I²C device register. Returns the data as a table of integers (0-255) for direct math operations.
* **Arguments**
* `device_id` - I²C device ID (0-17).
* `register_address` - Register address to read from (0-255)
* `length` - Number of bytes to read (1-64)
* **Returns:** Table of byte values as integers
* **Throws**
* `"Invalid device ID: ## (must be 0-17)"` - device ID is outside valid range
* `"Invalid register address: ## (must be 0-255)"` - register address is outside valid range
* `"Invalid length: ## (must be 1-64)"` - length is outside valid range
* `"I2C read failed"` - hardware I²C operation failed
Example:
```lua
-- Read voltage from MCU device
local voltage_raw = i2c.read(i2c.dev.MCU, i2c.mcu.reg.BATTERY_VOLTAGE, 1)
local voltage = voltage_raw[1] * 5 / 256.0
print("System voltage:", voltage, "V")
-- Read Power status
local power_state = i2c.read(i2c.dev.MCU, i2c.mcu.reg.POWER_STATUS, 1)
if power_state[1] & i2c.mcu.power_status_flags.SHELL_OPEN ~= 0 then
print("Shell is open")
end
if power_state[1] & i2c.mcu.power_status_flags.CHARGING ~= 0 then
print("Charging")
end
```
#### i2c.write
* `void i2c.write(int device_id, int register_address, table data)`
> [!WARNING]
> Writing data to random registers can lead to bricking!.
Write data to an I²C device register. Requires memory write permissions.
Not all registers are supported for writing.
* **Allowed registers (MCU)**
* `mcu.reg.USERDATA_RAM_` - Registers 0x15-0x17 and 0x1C-0x1F
* `mcu.reg.LED_BRIGHTNESS_STATE` - Brightness of the status LEDs.
* `mcu.reg.POWER_LED_STATE` - Sets the state of the power LED
* `mcu.reg.WLAN_LED_STATE` - Sets the state of the wifi LED
* `mcu.reg.CAMERA_LED_STATE` - Sets the state of the camera LED
* `mcu.reg.LED_3D_STATE` - Sets the state of the 3D LED
* `mcu.reg.NOTIFICATION_LED_STATE` - Sets the state of the info (notification) LED
* **Arguments**
* `device_id` - I²C device ID (0-17).
* `register_address` - Register address to write to (0-255)
* `data` - Table of byte values to write (each value must be 0-255)
* **Throws**
* `"Invalid device ID: ## (must be 0-17)"` - device ID is outside valid range
* `"Invalid register address: ## (must be 0-255)"` - register address is outside valid range
* `"Write to device ##, register ## is not allowed"` - The device/register is not on the allowed write list
* `"Third parameter must be a table of byte values"` - data parameter is not a table
* `"Invalid data length: ## (must be 1-64)"` - data table length is outside valid range
* `"Table element ## is not an integer"` - data table contains non-integer values
* `"Table element ## is out of range: ## (must be 0-255)"` - data table contains values outside byte range
* `"I2C write failed"` - hardware I²C operation failed
* Permission errors if memory write access is denied