diff --git a/epicardium/drivers/ir.c b/epicardium/drivers/ir.c
new file mode 100644
index 0000000000000000000000000000000000000000..c17922da2701b35cb4006124bfb3ab20abc04d7d
--- /dev/null
+++ b/epicardium/drivers/ir.c
@@ -0,0 +1,80 @@
+#include "leds.h"
+#include "pmic.h"
+#include "FreeRTOS.h"
+#include "task.h"
+#include "epicardium.h"
+#include "max32665.h"
+#include "gpio.h"
+#include "modules/modules.h"
+#include "drivers/drivers.h"
+
+#include <stdbool.h>
+
+//#define KHZ38_PERIOD (340)
+#define KHZ38_PERIOD (310)
+
+static const uint32_t NEC_LEAD_IN = 38e3 * 9e-3;
+static const uint32_t NEC_SPACE = 38e3 * 4.5e-3;
+
+static const uint32_t NEC_ONE_T1 = 38e3 * 562.5e-6;
+static const uint32_t NEC_ONE_T2 = 38e3 * 1687.5e-6;
+
+static const uint32_t NEC_ZERO_T1 = 38e3 * 562.5e-6;
+static const uint32_t NEC_ZERO_T2 = 38e3 * 562.5e-6;
+
+static const uint32_t NEC_LEAD_OUT = 38e3 * 562.5e-6;
+
+static inline __attribute__((always_inline)) void
+delay_ticks(uint32_t ticks)
+{
+	volatile uint32_t counter = ticks;
+	while (--counter) {
+	};
+}
+
+static inline __attribute__((always_inline)) void
+epic_ir_transmit_wave(gpio_cfg_t *pin, uint32_t half_period, uint32_t count)
+{
+	while(count--) {
+		GPIO_OutSet(pin);
+		delay_ticks(half_period);
+		GPIO_OutClr(pin);
+		delay_ticks(half_period);
+	}
+}
+
+static void ir_transmit_nec_bit(gpio_cfg_t *pin,uint8_t bit)
+{
+	if(bit) {
+		epic_ir_transmit_wave(pin, KHZ38_PERIOD/2, NEC_ONE_T1);
+		delay_ticks(NEC_ONE_T2 * KHZ38_PERIOD);
+	} else {
+		epic_ir_transmit_wave(pin, KHZ38_PERIOD/2, NEC_ZERO_T1);
+		delay_ticks(NEC_ZERO_T2 * KHZ38_PERIOD);
+	}
+}
+
+static void ir_transmit_nec_byte(gpio_cfg_t *pin, uint8_t byte)
+{
+	for(uint32_t i = 0; i < 8; i++) {
+		ir_transmit_nec_bit(pin, byte & (1<<i));
+	}
+}
+
+void epic_ir_transmit_nec(uint8_t pin, uint8_t address, uint8_t command)
+{
+	gpio_cfg_t *pin_cfg = &gpio_configs[pin];
+
+	//if ((epic_gpio_get_pin_mode(pin) & EPIC_GPIO_MODE_OUT) == 0) {
+	//	epic_gpio_set_pin_mode(pin, EPIC_GPIO_MODE_OUT);
+	//}
+
+	epic_ir_transmit_wave(pin_cfg, KHZ38_PERIOD/2, NEC_LEAD_IN);
+	delay_ticks(NEC_SPACE * KHZ38_PERIOD);
+	ir_transmit_nec_byte(pin_cfg, address);
+	ir_transmit_nec_byte(pin_cfg, ~address);
+	ir_transmit_nec_byte(pin_cfg, command);
+	ir_transmit_nec_byte(pin_cfg, ~command);
+	epic_ir_transmit_wave(pin_cfg, KHZ38_PERIOD/2, NEC_LEAD_OUT);
+}
+
diff --git a/epicardium/drivers/meson.build b/epicardium/drivers/meson.build
index 3ad2367836a0e890cef090d507c552f260192f20..90f6216d3687cd328537245722d5230b0ebc1806 100644
--- a/epicardium/drivers/meson.build
+++ b/epicardium/drivers/meson.build
@@ -4,6 +4,7 @@ driver_sources = files(
   'bme680.c',
   'buttons.c',
   'gpio.c',
+  'ir.c',
   'leds.c',
   'light_sensor.c',
   'max86150.c',
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index bc32e5cab772b6e952cecce64345c757857f3afe..0a79764a9f49efb67cf07335d7ea215cdc614d2f 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -148,6 +148,7 @@ typedef _Bool bool;
 #define API_USB_CDCACM             0x112
 
 #define API_WS2812_WRITE           0x0120
+#define API_IR_TRANSMIT_NEC        0x0121
 
 #define API_CONFIG_GET_STRING      0x130
 #define API_CONFIG_GET_INTEGER     0x131
@@ -2314,7 +2315,7 @@ API(API_USB_CDCACM, int epic_usb_cdcacm(void));
  * .. versionadded:: 1.10
  */
 API(API_WS2812_WRITE, void epic_ws2812_write(uint8_t pin, uint8_t *pixels, uint32_t n_bytes));
-
+API(API_IR_TRANSMIT_NEC, void epic_ir_transmit_nec(uint8_t pin, uint8_t address, uint8_t command));
 
 /**
  * Configuration
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index 01f32c679c012a70d4ed1db0e8711b1bc861e3b6..2edc369b68d3d6d3a9a9ba0784e3ef5137223bf9 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -193,6 +193,7 @@ Q(MAX86150_PROX)
 /* ws2812 */
 Q(ws2812)
 Q(set_all)
+Q(transmit_nec)
 
 /* config */
 Q(config)
diff --git a/pycardium/modules/ws2812.c b/pycardium/modules/ws2812.c
index 4df1d84a047981ef28d00d3f3801d1845cd44a46..180ded36cd2f008b97a64f66de22bec104001c73 100644
--- a/pycardium/modules/ws2812.c
+++ b/pycardium/modules/ws2812.c
@@ -36,10 +36,25 @@ static mp_obj_t mp_ws2812_set_all(mp_obj_t pin, mp_obj_t color_in)
 }
 static MP_DEFINE_CONST_FUN_OBJ_2(ws2812_set_all_obj, mp_ws2812_set_all);
 
+static mp_obj_t mp_ws2812_transmit_nec(mp_obj_t pin, mp_obj_t address, mp_obj_t command)
+{
+	mp_int_t pin_int     = mp_obj_get_int(pin);
+	mp_int_t address_int = mp_obj_get_int(address);
+	mp_int_t command_int = mp_obj_get_int(command);
+
+	/* call epicardium to be fast enough */
+	epic_ir_transmit_nec(pin_int, address_int, command_int);
+
+	return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_3(ws2812_transmit_nec_obj, mp_ws2812_transmit_nec);
+
+
 /* The globals table for this module */
 static const mp_rom_map_elem_t ws2812_module_globals_table[] = {
 	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ws2812) },
 	{ MP_ROM_QSTR(MP_QSTR_set_all), MP_ROM_PTR(&ws2812_set_all_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_transmit_nec), MP_ROM_PTR(&ws2812_transmit_nec_obj) },
 };
 static MP_DEFINE_CONST_DICT(ws2812_module_globals, ws2812_module_globals_table);