From 130e7fd781700c52cac35253493628ffadabccd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Thu, 8 Jun 2017 14:18:09 +0200 Subject: [PATCH] new lib files added + more reliable power button ahndling --- CMakeLists.txt | 7 +++ Makefile | 5 +- lib/color.c | 103 ++++++++++++++++++++++++++++++++++++++ lib/color.h | 57 +++++++++++++++++++++ lib/debounce.c | 49 ++++++++++++++++++ lib/debounce.h | 84 +++++++++++++++++++++++++++++++ lib/wsrgb.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/wsrgb.h | 46 +++++++++++++++++ main.c | 86 +++++++++++++++++++++++++++++--- 9 files changed, 561 insertions(+), 7 deletions(-) create mode 100644 lib/color.c create mode 100644 lib/color.h create mode 100644 lib/debounce.c create mode 100644 lib/debounce.h create mode 100644 lib/wsrgb.c create mode 100644 lib/wsrgb.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e909fd..a07feb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,16 +7,23 @@ set(CMAKE_CXX_STANDARD GNU99) set(SOURCE_FILES main.c + debo_config.h lib/calc.h lib/iopins.c lib/iopins.h lib/adc.c lib/adc.h + lib/debounce.c + lib/debounce.h lib/nsdelay.h lib/spi.c lib/spi.h lib/usart.c lib/usart.h + lib/color.c + lib/color.h + lib/wsrgb.c + lib/wsrgb.h pinout.h) include_directories(lib diff --git a/Makefile b/Makefile index c785a9e..1f54b4a 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,9 @@ OBJS += lib/usart.o OBJS += lib/iopins.o OBJS += lib/spi.o OBJS += lib/adc.o +OBJS += lib/debounce.o +OBJS += lib/wsrgb.o +OBJS += lib/color.o # Dirs with header files INCL_DIRS = . lib/ @@ -44,7 +47,7 @@ CFLAGS = -std=gnu99 -mmcu=$(MCU) $(DEFS) $(INCL_DIRS:%=-I%) CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -ffreestanding CFLAGS += -Wall -Wno-main -Wno-strict-prototypes -Wno-comment CFLAGS += -g2 -Wextra -Wfatal-errors -Wno-unused-but-set-variable -CFLAGS += -ffunction-sections -fdata-sections -Os +CFLAGS += -ffunction-sections -fdata-sections -Os -Wno-unused-parameter LDFLAGS = -Wl,--gc-sections -Wl,--relax -lm diff --git a/lib/color.c b/lib/color.c new file mode 100644 index 0000000..ef7b0bb --- /dev/null +++ b/lib/color.c @@ -0,0 +1,103 @@ +#include +#include +#include + +#include "iopins.h" +#include "nsdelay.h" +#include "color.h" + + +// --- HSL --- + +#ifdef HSL_LINEAR +const uint8_t FADE_128[] = +{ + 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, + 5, 5, 6, 6, 6, 7, 7, 8, 8, 8, 9, 10, 10, 10, 11, 12, 13, 14, + 14, 15, 16, 17, 18, 20, 21, 22, 24, 26, 27, 28, 30, 31, 32, 34, 35, 36, + 38, 39, 40, 41, 42, 44, 45, 46, 48, 49, 50, 52, 54, 56, 58, 59, 61, 63, + 65, 67, 68, 69, 71, 72, 74, 76, 78, 80, 82, 85, 88, 90, 92, 95, 98, 100, + 103, 106, 109, 112, 116, 119, 122, 125, 129, 134, 138, 142, 147, 151, + 153, 156, 160, 163, 165, 170, 175, 180, 185, 190, 195, 200, 207, 214, 218, + 221, 225, 228, 232, 234, 241, 248, 254, 255 +}; +#endif + +// based on: https://github.com/lewisd32/avr-hsl2rgb +xrgb_t hsl_xrgb(const hsl_t cc) +{ + // 0 .. 256*3 + const uint16_t hh = (uint16_t) cc.h * 3; + const uint8_t hue_mod = hh % 256; + + uint8_t r_temp, g_temp, b_temp; + if (hh < 256) + { + r_temp = hue_mod ^ 255; + g_temp = hue_mod; + b_temp = 0; + } + else if (hh < 512) + { + r_temp = 0; + g_temp = hue_mod ^ 255; + b_temp = hue_mod; + } + else if (hh < 768) + { + r_temp = hue_mod; + g_temp = 0; + b_temp = hue_mod ^ 255; + } + else + { + r_temp = 0; + g_temp = 0; + b_temp = 0; + } + + const uint8_t inverse_sat = (cc.s ^ 255); + + xrgb_t rgb; + + uint8_t t8; + uint16_t t16; + +#ifdef HSL_LINEAR + const uint8_t bri = FADE_128[cc.l >> 1]; +#else + const uint8_t bri = cc.l; +#endif + + t8 = r_temp; + t16 = t8 * cc.s + t8; + t16 = t16 + t8; + t8 = t16 >> 8; + t8 = t8 + inverse_sat; + t16 = t8 * bri; + t16 = t16 + t8; + t8 = t16 >> 8; + rgb.r = t8; + + t8 = g_temp; + t16 = t8 * cc.s; + t16 = t16 + t8; + t8 = t16 >> 8; + t8 = t8 + inverse_sat; + t16 = t8 * bri; + t16 = t16 + t8; + t8 = t16 >> 8; + rgb.g = t8; + + t8 = b_temp; + t16 = t8 * cc.s; + t16 = t16 + t8; + t8 = t16 >> 8; + t8 = t8 + inverse_sat; + t16 = t8 * bri; + t16 = t16 + t8; + t8 = t16 >> 8; + rgb.b = t8; + + return rgb; +} diff --git a/lib/color.h b/lib/color.h new file mode 100644 index 0000000..7fef13a --- /dev/null +++ b/lib/color.h @@ -0,0 +1,57 @@ +#pragma once + +// --- color types --- +// +// The XXXc macros don't use cast, so they can be used in array initializers. +// +// xrgb ... 3-byte true-color RGB (8 bits per component) +// rgb24 ... 24-bit color value, with equal nr of bits per component +// +// XX_r (_g, _b) ... extract component from the color, and convert it to 0..255 + +// Define HSL_LINEAR to get more linear brightness in hsl->rgb conversion + + +typedef struct +{ + uint8_t r; + uint8_t g; + uint8_t b; +} xrgb_t; + + +typedef uint32_t rgb24_t; + +#define xrgb(rr, gg, bb) ((xrgb_t)xrgbc(rr, gg, bb)) +// xrgb for constant array declarations +#define xrgbc(rr, gg, bb) { .r = ((uint8_t)(rr)), .g = ((uint8_t)(gg)), .b = ((uint8_t)(bb)) } +#define xrgb_r(c) ((uint8_t)(c.r)) +#define xrgb_g(c) ((uint8_t)(c.g)) +#define xrgb_b(c) ((uint8_t)(c.b)) +#define xrgb_rgb24(c) ((((rgb24_t)c.r) << 16) | (((rgb24_t)c.g) << 8) | (((rgb24_t)c.b))) +#define xrgb_rgb15(c) (((((rgb15_t)c.r) & 0xF8) << 7) | ((((rgb15_t)c.g) & 0xF8) << 2) | ((((rgb15_t)c.b) & 0xF8) >> 3)) +#define xrgb_rgb12(c) (((((rgb12_t)c.r) & 0xF0) << 4) | ((((rgb12_t)c.g) & 0xF0)) | ((((rgb12_t)c.b) & 0xF0) >> 4)) +#define xrgb_rgb6(c) (((((rgb6_t)c.r) & 0xC0) >> 2) | ((((rgb6_t)c.g) & 0xC0) >> 4) | ((((rgb6_t)c.b) & 0xC0) >> 6)) + +#define rgb24c(r,g,b) (((((rgb24_t)r) & 0xFF) << 16) | ((((rgb24_t)g) & 0xFF) << 8) | (((rgb24_t)b) & 0xFF)) +#define rgb24(r,g,b) ((rgb24_t) rgb24(r,g,b)) + +#define rgb24_r(c) ((((rgb24_t) (c)) >> 16) & 0xFF) +#define rgb24_g(c) ((((rgb24_t) (c)) >> 8) & 0xFF) +#define rgb24_b(c) ((((rgb24_t) (c)) >> 0) & 0xFF) +#define rgb24_xrgb(c) xrgb(rgb24_r(c), rgb24_g(c), rgb24_b(c)) +#define rgb24_xrgbc(c) xrgbc(rgb24_r(c), rgb24_g(c), rgb24_b(c)) + +#define add_xrgb(x, y) ((xrgb_t) { (((y).r > (255 - (x).r)) ? 255 : ((x).r + (y).r)), (((y).g > (255 - (x).g)) ? 255 : ((x).g + (y).g)), (((y).b > 255 - (x).b) ? 255 : ((x).b + (y).b)) }) + + +// HSL data structure +typedef struct +{ + uint8_t h; + uint8_t s; + uint8_t l; +} hsl_t; + +/* Convert HSL to XRGB */ +xrgb_t hsl_xrgb(const hsl_t color); diff --git a/lib/debounce.c b/lib/debounce.c new file mode 100644 index 0000000..8b7b49c --- /dev/null +++ b/lib/debounce.c @@ -0,0 +1,49 @@ +#include +#include + +#include "debounce.h" +#include "calc.h" +#include "iopins.h" + +/** Debounce data array */ +uint8_t debo_next_slot = 0; + +/** bit - range 0-63 */ +uint8_t debo_register(PORT_P reg, uint8_t bit, bool invert, DebouncerCallback callback) +{ + debo_slots[debo_next_slot] = (debo_slot_t) { + .reg = reg, + .bit = bit | ((invert & 1) << 7) | (get_bit_p(reg, bit) << 6), // bit 7 = invert, bit 6 = state + .count = 0, + .callback = callback, + }; + + return debo_next_slot++; +} + +/** Check debounced pins, should be called periodically. */ +void debo_tick(void) +{ + for (uint8_t i = 0; i < debo_next_slot; i++) { + // current pin value + bool value = get_bit_p(debo_slots[i].reg, debo_slots[i].bit & 0x3F) + ^get_bit(debo_slots[i].bit, 7); + + if (value != get_bit(debo_slots[i].bit, 6)) { + // different pin state than last recorded state + if (debo_slots[i].count < DEBO_TICKS) { + debo_slots[i].count++; + } else { + // overflown -> latch value + set_bit(debo_slots[i].bit, 6, value); // set state bit + debo_slots[i].count = 0; + // Fire the callback, if not NULL + if (debo_slots[i].callback) { + debo_slots[i].callback(i, value); + } + } + } else { + debo_slots[i].count = 0; // reset the counter + } + } +} diff --git a/lib/debounce.h b/lib/debounce.h new file mode 100644 index 0000000..e6e519c --- /dev/null +++ b/lib/debounce.h @@ -0,0 +1,84 @@ +#ifndef DEBOUNCE_H +#define DEBOUNCE_H + +// +// An implementation of button debouncer. +// +// ---- +// +// You must provide a config file debo_config.h (next to your main.c) +// +// A pin is registered like this: +// +// #define BTN1 12 // pin D12 +// #define BTN2 13 +// +// debo_add(BTN0); // The function returns number assigned to the pin (0, 1, ...) +// debo_add_rev(BTN1); // active low +// debo_register(&PINB, PB2, 0); // direct access - register, pin & invert +// +// Then periodically call the tick function (perhaps in a timer interrupt): +// +// debo_tick(); +// +// To check if input is active, use +// +// debo_get_pin(0); // state of input #0 (registered first) +// debo_get_pin(1); // state of input #1 (registered second) +// + +#include +#include +#include + +#include "calc.h" +#include "iopins.h" + +// Update as needed +#define DEBO_CHANNELS 5 +#define DEBO_TICKS 50 // ms + +/** Debouncer callback; state - state after applying inverse bit */ +typedef void(*DebouncerCallback)(uint8_t n, bool state); + +/* Internal debouncer entry */ +typedef struct +{ + PORT_P reg; // pointer to IO register + uint8_t bit; // bits 6 and 7 of this hold "state" & "invert" flag + uint8_t count; // number of ticks this was in the new state + DebouncerCallback callback; +} debo_slot_t; + +debo_slot_t debo_slots[DEBO_CHANNELS]; + +/** + * Add a pin for debouncing (low level function) + * + * @param pin_reg_pointer - PINx pointer + * @param bit - number of the debounced bit in the register (0-7) + * @param invert - invert the value when reading (for buttons to GND, for example) + */ +uint8_t debo_register(PORT_P pin_reg_pointer, uint8_t bit, bool invert, DebouncerCallback cbk); + +/** + * Add a pin for debouncing (must be used with constant args). + * Returns index in debouncer, used for callbacks. + * + * Arg: pin number, defined in iopins.h + */ +#define debo_add(pin, callback) debo_register(&_pin(pin), _pn(pin), false, callback) +#define debo_add_rev(pin, callback) debo_register(&_pin(pin), _pn(pin), true, callback) + +/** + * Check debounced pins, should be called periodically. + * Updates internal states and fires callbacks. + */ +void debo_tick(void); + +/** + * Get a value of debounced pin (after debounce - latched state) + */ +#define debo_get_pin(i) (get_bit(debo_slots[i].bit, 6) ^ get_bit(debo_slots[i].bit, 7)) + +#endif diff --git a/lib/wsrgb.c b/lib/wsrgb.c new file mode 100644 index 0000000..a84e22a --- /dev/null +++ b/lib/wsrgb.c @@ -0,0 +1,131 @@ +#include +#include +#include + +#include "iopins.h" +#include "nsdelay.h" + +#include "wsrgb.h" +#include "color.h" + +/* Driver code for WS2812B */ + +void ws_init() +{ + as_output(WS_PIN); +} + +/** Wait long enough for the colors to show */ +void ws_show() +{ + delay_ns_c(WS_T_LATCH, 0); +} + +/** Send one byte to the RGB strip */ +void ws_send_byte(const uint8_t bb) +{ + for (volatile int8_t i = 7; i >= 0; --i) { + if ((bb) & (1 << i)) { + pin_up(WS_PIN); + delay_ns_c(WS_T_1H, -2); + + pin_down(WS_PIN); + delay_ns_c(WS_T_1L, -10); + } else { + pin_up(WS_PIN); + delay_ns_c(WS_T_0H, -2); + + pin_down(WS_PIN); + delay_ns_c(WS_T_0L, -10); + } + } +} + + +/** Send R,G,B color to the strip */ +void ws_send_rgb(const uint8_t r, const uint8_t g, const uint8_t b) +{ + ws_send_byte(g); + ws_send_byte(r); + ws_send_byte(b); +} + + +/** Send a RGB struct */ +void ws_send_xrgb(xrgb_t xrgb) +{ + ws_send_byte(xrgb.g); + ws_send_byte(xrgb.r); + ws_send_byte(xrgb.b); +} + + +/** Send color hex */ +void ws_send_rgb24(rgb24_t rgb) +{ + ws_send_byte(rgb24_g(rgb)); + ws_send_byte(rgb24_r(rgb)); + ws_send_byte(rgb24_b(rgb)); +} + +/** Send array of colors */ +void ws_send_xrgb_array(const xrgb_t rgbs[], const uint8_t length) +{ + for (uint8_t i = 0; i < length; i++) { + const xrgb_t c = rgbs[i]; + ws_send_byte(c.g); + ws_send_byte(c.r); + ws_send_byte(c.b); + } +} + +/** Send array of colors */ +void ws_send_rgb24_array(const rgb24_t rgbs[], const uint8_t length) +{ + for (uint8_t i = 0; i < length; i++) { + const rgb24_t c = rgbs[i]; + ws_send_byte(rgb24_g(c)); + ws_send_byte(rgb24_r(c)); + ws_send_byte(rgb24_b(c)); + } +} + +//#define ws_send_rgb24_array(rgbs, length) __ws_send_array_proto((rgbs), (length), rgb24) + +// prototype for sending array. it's ugly, sorry. +/*#define __ws_send_array_proto(rgbs, length, style) { \ + for (uint8_t __rgb_sap_i = 0; __rgb_sap_i < length; __rgb_sap_i++) { \ + style ## _t __rgb_sap2 = (rgbs)[__rgb_sap_i]; \ + ws_send_ ## style(__rgb_sap2); \ + } \ +}*/ + + +// /** Send a 2D array to a zig-zag display */ +// #define ws_send_xrgb_array_zigzag(rgbs, width, height) { \ +// int8_t __rgb_sxaz_y, __rgb_sxaz_x; \ +// for(__rgb_sxaz_y = 0; __rgb_sxaz_y < (height); __rgb_sxaz_y ++) { \ +// for(__rgb_sxaz_x = 0; __rgb_sxaz_x < (width); __rgb_sxaz_x++) { \ +// ws_send_xrgb((rgbs)[__rgb_sxaz_y][__rgb_sxaz_x]); \ +// } \ +// __rgb_sxaz_y++; \ +// for(__rgb_sxaz_x = (width) - 1; __rgb_sxaz_x >= 0; __rgb_sxaz_x--) { \ +// ws_send_xrgb((rgbs)[__rgb_sxaz_y][__rgb_sxaz_x]); \ +// } \ +// } \ +// } + + +// /* Send a linear array to a zig-zag display as a n*m board (row-by-row) +// #define ws_send_xrgb_array_zigzag_linear(rgbs, width, height) { \ +// int8_t __rgb_sxazl_x, __rgb_sxazl_y; \ +// for(__rgb_sxazl_y = 0; __rgb_sxazl_y < (height); __rgb_sxazl_y++) { \ +// for(__rgb_sxazl_x = 0; __rgb_sxazl_x < (width); __rgb_sxazl_x++) { \ +// ws_send_xrgb((rgbs)[__rgb_sxazl_y * (width) + __rgb_sxazl_x]); \ +// } \ +// __rgb_sxazl_y++; \ +// for(__rgb_sxazl_x = width-1; __rgb_sxazl_x >=0; __rgb_sxazl_x--) { \ +// ws_send_xrgb((rgbs)[__rgb_sxazl_y * (width) + __rgb_sxazl_x]); \ +// } \ +// } \ +// } diff --git a/lib/wsrgb.h b/lib/wsrgb.h new file mode 100644 index 0000000..243b7ff --- /dev/null +++ b/lib/wsrgb.h @@ -0,0 +1,46 @@ +#pragma once + +// +// Utils for driving a WS2812 RGB LED strips, and color manipulation in general. +// +// Timing is critical! +// + +#include + +#include "iopins.h" +#include "color.h" + +// Config +#define WS_T_1H 700 +#define WS_T_1L 150 +#define WS_T_0H 150 +#define WS_T_0L 700 +#define WS_T_LATCH 7000 +#define WS_PIN 2 + +// --- functions for RGB strips --- + +/** Initialize OI */ +void ws_init(); + +/** Wait long enough for the colors to show */ +void ws_show(); + +/** Send one byte to the RGB strip */ +void ws_send_byte(const uint8_t bb); + +/** Send R,G,B color to the strip */ +void ws_send_rgb(const uint8_t r, const uint8_t g, const uint8_t b); + +/** Send a RGB struct */ +void ws_send_xrgb(xrgb_t xrgb); + +/** Send color hex */ +void ws_send_rgb24(rgb24_t rgb); + +/** Send array of colors */ +void ws_send_xrgb_array(const xrgb_t rgbs[], const uint8_t length); + +/** Send array of colors */ +void ws_send_rgb24_array(const rgb24_t rgbs[], const uint8_t length); diff --git a/main.c b/main.c index bfc7df4..027ca02 100644 --- a/main.c +++ b/main.c @@ -11,6 +11,7 @@ #include "lib/usart.h" #include "lib/spi.h" #include "lib/adc.h" +#include "lib/debounce.h" #include "pinout.h" @@ -30,8 +31,8 @@ void setup_io(void) as_input(PIN_KEY_4); as_output(PIN_NEOPIXEL); - as_output(PIN_NEOPIXEL_PWRN); - pin_up(PIN_NEOPIXEL_PWRN); // turn neopixels OFF + pin_up(PIN_NEOPIXEL_PWRN); // turn neopixels OFF - it's a PMOS + as_output(PIN_NEOPIXEL_PWRN); // configure DDR for output (pull-up becomes hard up) as_input(PIN_PWR_KEY); as_output(PIN_PWR_HOLD); @@ -61,13 +62,86 @@ ISR(TIMER2_OVF_vect) { // convert in background if (adc_ready()) { - disp_brightness = 255 - adc_read_8bit(); + disp_brightness = 255 - adc_read_8bit(); // inverse adc_start_conversion(LIGHT_ADC_CHANNEL); } OCR2B = disp_brightness; } + +// --- Debouncer slot allocation constants --- +volatile bool booting = true; +volatile uint16_t time_ms = 0; + +// (normally those would be retvals from debo_add()) +#define DB_KEY_POWER 0 +#define DB_KEY_1 1 +#define DB_KEY_2 2 +#define DB_KEY_3 3 +#define DB_KEY_4 4 + +volatile uint16_t time_pwr_pressed = 0; + +/** Power button state changed */ +void key_cb_power(uint8_t num, bool state) +{ + if (state) { + time_pwr_pressed = time_ms; + } else { + if (booting) { + // Ignore this one - user still holding BTN after power ON + usart_puts("Power button released, leaving boot mode.\r\n"); + booting = false; + return; + } + } +} + +/** Button state changed */ +void key_cb_button(uint8_t num, bool state) +{ + // TODO + // num - 1,2,3,4 + usart_puts("BTN "); + usart_tx('0'+num); + usart_tx(' '); + usart_tx('0'+state); + usart_puts("\r\n"); +} + +void setup_debouncer(void) +{ + // Debouncer config + debo_add(PIN_PWR_KEY, key_cb_power); + debo_add(PIN_KEY_1, key_cb_button); + debo_add(PIN_KEY_2, key_cb_button); + debo_add(PIN_KEY_3, key_cb_button); + debo_add(PIN_KEY_4, key_cb_button); + + // Timer 1 - CTC, to 16000 (1 ms interrupt) + OCR1A = 16000; + TIMSK1 |= _BV(OCIE1A); + TCCR1B |= _BV(WGM12) | _BV(CS10); +} + +ISR(TIMER1_COMPA_vect) +{ + // Tick 1 ms + debo_tick(); + time_ms++; + + // Shut down by just holding the button - better feedback for user + if (debo_get_pin(DB_KEY_POWER) + && !booting + && (time_ms - time_pwr_pressed > 1000)) { + usart_puts("Power OFF\r\n"); + // shut down + pin_down(PIN_PWR_HOLD); + } +} + + /** * Main function */ @@ -84,8 +158,6 @@ void main() pin_up(D13); // the on-board LED (also SPI clk) - indication for the user // Stay on - hold the EN pin high pin_up(PIN_PWR_HOLD); - // Wait for user to release the power key (no debounce needed here) - while (pin_is_high(PIN_PWR_KEY)); // SPI conf // TODO verify the cpha and cpol. those seem to work, but it's a guess @@ -93,6 +165,8 @@ void main() adc_init(ADC_PRESC_128); setup_pwm(); + setup_debouncer(); + // Turn neopixels power ON - voltage will have stabilized by now // and no glitches should occur pin_down(PIN_NEOPIXEL_PWRN); @@ -112,7 +186,7 @@ void main() _delay_ms(100); - sprintf(buf, "%d\n", disp_brightness); + sprintf(buf, "BRT = %d\r\n", disp_brightness); usart_puts(buf); } }