From 0f3c0ffbb01ceb6554a06494ab6ffcbe6d736182 Mon Sep 17 00:00:00 2001 From: Robert Foss Date: Wed, 9 Dec 2015 16:35:50 -0500 Subject: [PATCH] Added support for the APA102 LED. --- app/include/user_modules.h | 1 + app/modules/apa102.c | 114 +++++++++++++++++++++++++++++++++++++ docs/en/modules/apa102.md | 31 ++++++++++ 3 files changed, 146 insertions(+) create mode 100644 app/modules/apa102.c create mode 100644 docs/en/modules/apa102.md diff --git a/app/include/user_modules.h b/app/include/user_modules.h index fecdad7c..27c4d4c1 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -14,6 +14,7 @@ #ifndef LUA_CROSS_COMPILER #define LUA_USE_MODULES_ADC +//#define LUA_USE_MODULES_APA102 #define LUA_USE_MODULES_BIT //#define LUA_USE_MODULES_BMP085 #define LUA_USE_MODULES_CJSON diff --git a/app/modules/apa102.c b/app/modules/apa102.c new file mode 100644 index 00000000..7f509f1e --- /dev/null +++ b/app/modules/apa102.c @@ -0,0 +1,114 @@ +#include "lualib.h" +#include "lauxlib.h" +#include "platform.h" +#include "lrotable.h" +#include "c_stdlib.h" +#include "c_string.h" +#include "user_interface.h" + + +#define NOP asm volatile(" nop \n\t") + + +static inline void apa102_send_byte(uint32_t data_pin, uint32_t clock_pin, uint8_t byte) { + int i; + for (i = 0; i < 8; i++) { + if (byte & 0x80) { + GPIO_OUTPUT_SET(data_pin, PLATFORM_GPIO_HIGH); // Set pin high + } else { + GPIO_OUTPUT_SET(data_pin, PLATFORM_GPIO_LOW); // Set pin low + } + GPIO_OUTPUT_SET(clock_pin, PLATFORM_GPIO_HIGH); // Set pin high + byte <<= 1; + NOP; + NOP; + GPIO_OUTPUT_SET(clock_pin, PLATFORM_GPIO_LOW); // Set pin low + NOP; + NOP; + } +} + + +static void apa102_send_buffer(uint32_t data_pin, uint32_t clock_pin, uint32_t *buf, uint32_t nbr_frames) { + int i; + + // Send 32-bit Start Frame that's all 0x00 + apa102_send_byte(data_pin, clock_pin, 0x00); + apa102_send_byte(data_pin, clock_pin, 0x00); + apa102_send_byte(data_pin, clock_pin, 0x00); + apa102_send_byte(data_pin, clock_pin, 0x00); + + // Send 32-bit LED Frames + for (i = 0; i < nbr_frames; i++) { + uint8_t *byte = (uint8_t *) buf++; + + // Set the first 3 bits of that byte to 1. + // This makes the lua interface easier to use since you + // don't have to worry about creating invalid LED Frames. + byte[0] |= 0xE0; + apa102_send_byte(data_pin, clock_pin, byte[0]); + apa102_send_byte(data_pin, clock_pin, byte[1]); + apa102_send_byte(data_pin, clock_pin, byte[2]); + apa102_send_byte(data_pin, clock_pin, byte[3]); + } + + // Send 32-bit End Frames + uint32_t required_postamble_frames = (nbr_frames + 1) / 2; + for (i = 0; i < required_postamble_frames; i++) { + apa102_send_byte(data_pin, clock_pin, 0xFF); + apa102_send_byte(data_pin, clock_pin, 0xFF); + apa102_send_byte(data_pin, clock_pin, 0xFF); + apa102_send_byte(data_pin, clock_pin, 0xFF); + } +} + + +// Lua: apa102.write(data_pin, clock_pin, "string") +// Byte quads in the string are interpreted as (brightness, B, G, R) values. +// Only the first 5 bits of the brightness value is actually used (0-31). +// This function does not corrupt your buffer. +// +// apa102.write(1, 3, string.char(31, 0, 255, 0)) uses GPIO5 for DATA and GPIO0 for CLOCK and sets the first LED green, with the brightness 31 (out of 0-32) +// apa102.write(5, 6, string.char(255, 0, 0, 255):rep(10)) uses GPIO14 for DATA and GPIO12 for CLOCK and sets ten LED to red, with the brightness 31 (out of 0-32). +// Brightness values are clamped to 0-31. +static int apa102_write(lua_State* L) { + uint8_t data_pin = luaL_checkinteger(L, 1); + MOD_CHECK_ID(gpio, data_pin); + uint32_t alt_data_pin = pin_num[data_pin]; + + uint8_t clock_pin = luaL_checkinteger(L, 2); + MOD_CHECK_ID(gpio, clock_pin); + uint32_t alt_clock_pin = pin_num[clock_pin]; + + size_t buf_len; + const char *buf = luaL_checklstring(L, 3, &buf_len); + uint32_t nbr_frames = buf_len / 4; + + if (nbr_frames > 100000) { + return luaL_error(L, "The supplied buffer is too long, and might cause the callback watchdog to bark."); + } + + // Initialize the output pins + platform_gpio_mode(data_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT); + GPIO_OUTPUT_SET(alt_data_pin, PLATFORM_GPIO_HIGH); // Set pin high + platform_gpio_mode(clock_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT); + GPIO_OUTPUT_SET(alt_clock_pin, PLATFORM_GPIO_LOW); // Set pin low + + // Send the buffers + apa102_send_buffer(alt_data_pin, alt_clock_pin, (uint32_t *) buf, (uint32_t) nbr_frames); + return 0; +} + + +#define MIN_OPT_LEVEL 2 +#include "lrodefs.h" +const LUA_REG_TYPE apa102_map[] = +{ + { LSTRKEY( "write" ), LFUNCVAL( apa102_write )}, + { LNILKEY, LNILVAL} +}; + +LUALIB_API int luaopen_apa102(lua_State *L) { + LREGISTER(L, "apa102", apa102_map); + return 1; +} diff --git a/docs/en/modules/apa102.md b/docs/en/modules/apa102.md new file mode 100644 index 00000000..00ccbd9e --- /dev/null +++ b/docs/en/modules/apa102.md @@ -0,0 +1,31 @@ +# APA102 Module + +## apa102.write() +Send ABGR data in 8 bits to a APA102 chain. + +#### Syntax +`apa102.write(data_pin, clock_pin, string)` + +#### Parameters +- `data_pin` any GPIO pin 0, 1, 2, ... +- `clock_pin` any GPIO pin 0, 1, 2, ... +- `string` payload to be sent to one or more APA102 LEDs. + It should be composed from a AGRB quadruplet per element. + - `A1` the first pixel's Intensity channel (0-31) + - `B1` the first pixel's Blue channel (0-255)
+ - `G1` the first pixel's Green channel (0-255) + - `R1` the first pixel's Red channel (0-255) + ... You can connect a lot of APA102 ... + - `A1`, `G2`, `R2`, `B2` are the next APA102s Intensity, Blue, Green and channel parameters + +#### Returns +`nil` + +```lua +a = 31 +g = 0 +r = 255 +b = 0 +leds_bgr = string.char(a, g, r, b, a, g, r, b) +apa102.write(2, 3, leds_abgr) -- turn two APA102s to red, connected to data_pin 2 and clock_pin 3 +```