From f7ff3326e01efd780d6bfa3b43edbcad6deeac64 Mon Sep 17 00:00:00 2001 From: di0ib Date: Tue, 6 Dec 2016 18:58:47 -1000 Subject: [PATCH] nano --- keyboard/nano/Makefile | 136 +++++++++ keyboard/nano/config.h | 83 ++++++ keyboard/nano/keymap_common.h | 42 +++ keyboard/nano/keymap_nano.c | 84 ++++++ keyboard/nano/led.c | 38 +++ keyboard/nano/light_ws2812.c | 181 ++++++++++++ keyboard/nano/light_ws2812.h | 73 +++++ keyboard/nano/matrix.c | 179 ++++++++++++ keyboard/nano/pcb/nano.zip | Bin 0 -> 37060 bytes keyboard/nano/pcb/readme.md | 16 ++ keyboard/nano/rgblight.c | 505 ++++++++++++++++++++++++++++++++++ keyboard/nano/rgblight.h | 87 ++++++ 12 files changed, 1424 insertions(+) create mode 100644 keyboard/nano/Makefile create mode 100644 keyboard/nano/config.h create mode 100644 keyboard/nano/keymap_common.h create mode 100644 keyboard/nano/keymap_nano.c create mode 100644 keyboard/nano/led.c create mode 100644 keyboard/nano/light_ws2812.c create mode 100644 keyboard/nano/light_ws2812.h create mode 100644 keyboard/nano/matrix.c create mode 100644 keyboard/nano/pcb/nano.zip create mode 100644 keyboard/nano/pcb/readme.md create mode 100644 keyboard/nano/rgblight.c create mode 100644 keyboard/nano/rgblight.h diff --git a/keyboard/nano/Makefile b/keyboard/nano/Makefile new file mode 100644 index 00000000..722fcc75 --- /dev/null +++ b/keyboard/nano/Makefile @@ -0,0 +1,136 @@ +#---------------------------------------------------------------------------- +# On command line: +# +# make all = Make software. +# +# make clean = Clean out built project files. +# +# make coff = Convert ELF to AVR COFF. +# +# make extcoff = Convert ELF to AVR Extended COFF. +# +# make program = Download the hex file to the device. +# Please customize your programmer settings(PROGRAM_CMD) +# +# make teensy = Download the hex file to the device, using teensy_loader_cli. +# (must have teensy_loader_cli installed). +# +# make dfu = Download the hex file to the device, using dfu-programmer (must +# have dfu-programmer installed). +# +# make flip = Download the hex file to the device, using Atmel FLIP (must +# have Atmel FLIP installed). +# +# make dfu-ee = Download the eeprom file to the device, using dfu-programmer +# (must have dfu-programmer installed). +# +# make flip-ee = Download the eeprom file to the device, using Atmel FLIP +# (must have Atmel FLIP installed). +# +# make debug = Start either simulavr or avarice as specified for debugging, +# with avr-gdb or avr-insight as the front end for debugging. +# +# make filename.s = Just compile filename.c into the assembler code only. +# +# make filename.i = Create a preprocessed source file for use in submitting +# bug reports to the GCC project. +# +# To rebuild project do "make clean" then "make all". +#---------------------------------------------------------------------------- + +# Target file name (without extension). +TARGET = nano + +# Directory common source filess exist +TMK_DIR = ../../tmk_core + +# Directory keyboard dependent files exist +TARGET_DIR = . + +# project specific files +SRC = matrix.c \ + led.c \ + rgblight.c \ + light_ws2812.c + +ifdef KEYMAP + SRC := keymap_$(KEYMAP).c $(SRC) +else + SRC := keymap_nano.c $(SRC) +endif + +CONFIG_H = config.h + + +# MCU name +#MCU = at90usb1287 +MCU = atmega32u4 + +# Processor frequency. +# This will define a symbol, F_CPU, in all source code files equal to the +# processor frequency in Hz. You can then use this symbol in your source code to +# calculate timings. Do NOT tack on a 'UL' at the end, this will be done +# automatically to create a 32-bit value in your source code. +# +# This will be an integer division of F_USB below, as it is sourced by +# F_USB after it has run through any CPU prescalers. Note that this value +# does not *change* the processor frequency - it should merely be updated to +# reflect the processor speed set externally so that the code can use accurate +# software delays. +F_CPU = 16000000 + + +# +# LUFA specific +# +# Target architecture (see library "Board Types" documentation). +ARCH = AVR8 + +# Input clock frequency. +# This will define a symbol, F_USB, in all source code files equal to the +# input clock frequency (before any prescaling is performed) in Hz. This value may +# differ from F_CPU if prescaling is used on the latter, and is required as the +# raw input clock is fed directly to the PLL sections of the AVR for high speed +# clock generation for the USB and other AVR subsections. Do NOT tack on a 'UL' +# at the end, this will be done automatically to create a 32-bit value in your +# source code. +# +# If no clock division is performed on the input clock inside the AVR (via the +# CPU clock adjust registers or the clock division fuses), this will be equal to F_CPU. +F_USB = $(F_CPU) + +# Interrupt driven control endpoint task(+60) +OPT_DEFS += -DINTERRUPT_CONTROL_ENDPOINT + + +# Boot Section Size in *bytes* +# Teensy halfKay 512 +# Teensy++ halfKay 1024 +# Atmel DFU loader 4096 +# LUFA bootloader 4096 +# USBaspLoader 2048 +OPT_DEFS += -DBOOTLOADER_SIZE=4096 + + +# Build Options +# comment out to disable the options. +# +BOOTMAGIC_ENABLE = yes # Virtual DIP switch configuration(+1000) +#MOUSEKEY_ENABLE = yes # Mouse keys(+4700) +EXTRAKEY_ENABLE = yes # Audio control and System control(+450) +CONSOLE_ENABLE = yes # Console for debug(+400) +COMMAND_ENABLE = yes # Commands for debug and configuration +#SLEEP_LED_ENABLE = yes # Breathing sleep LED during USB suspend +NKRO_ENABLE = yes # USB Nkey Rollover - not yet supported in LUFA + + +# Optimize size but this may cause error "relocation truncated to fit" +#EXTRALDFLAGS = -Wl,--relax + +# Search Path +VPATH += $(TARGET_DIR) +VPATH += $(TMK_DIR) + +include $(TMK_DIR)/protocol/lufa.mk +include $(TMK_DIR)/common.mk +include $(TMK_DIR)/rules.mk diff --git a/keyboard/nano/config.h b/keyboard/nano/config.h new file mode 100644 index 00000000..0be42907 --- /dev/null +++ b/keyboard/nano/config.h @@ -0,0 +1,83 @@ +/* +Copyright 2012 Jun Wako + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef CONFIG_H +#define CONFIG_H + + +/* USB Device descriptor parameter */ +#define VENDOR_ID 0xFEED +#define PRODUCT_ID 0x0A0C +#define DEVICE_VER 0x00AA +#define MANUFACTURER di0ib +#define PRODUCT The Nano Keyboard +#define DESCRIPTION A tactile switch keyboard + +/* key matrix size */ +#define MATRIX_ROWS 1 +#define MATRIX_COLS 8 + +/* define if matrix has ghost */ +//#define MATRIX_HAS_GHOST + +/* Set 0 if debouncing isn't needed */ +#define DEBOUNCE 5 + +/* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */ +#define LOCKING_SUPPORT_ENABLE +/* Locking resynchronize hack */ +#define LOCKING_RESYNC_ENABLE + +/* key combination for command */ +#define IS_COMMAND() ( \ + keyboard_report->mods == (MOD_BIT(KC_LSHIFT) | MOD_BIT(KC_RSHIFT)) \ +) + +/* ws2812 RGB LED */ +#define ws2812_PORTREG PORTB +#define ws2812_DDRREG DDRB +#define ws2812_pin PB5 +#define RGBLED_NUM 3 // Number of LEDs +#ifndef RGBLIGHT_HUE_STEP +#define RGBLIGHT_HUE_STEP 10 +#endif +#ifndef RGBLIGHT_SAT_STEP +#define RGBLIGHT_SAT_STEP 17 +#endif +#ifndef RGBLIGHT_VAL_STEP +#define RGBLIGHT_VAL_STEP 17 +#endif + +/* + * Feature disable options + * These options are also useful to firmware size reduction. + */ + +/* disable debug print */ +//#define NO_DEBUG + +/* disable print */ +//#define NO_PRINT + +/* disable action features */ +//#define NO_ACTION_LAYER +//#define NO_ACTION_TAPPING +//#define NO_ACTION_ONESHOT +//#define NO_ACTION_MACRO +//#define NO_ACTION_FUNCTION + +#endif diff --git a/keyboard/nano/keymap_common.h b/keyboard/nano/keymap_common.h new file mode 100644 index 00000000..32d39b86 --- /dev/null +++ b/keyboard/nano/keymap_common.h @@ -0,0 +1,42 @@ +/* +Copyright 2012,2013 Jun Wako + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + + +#ifndef KEYMAP_COMMON_H +#define KEYMAP_COMMON_H + +#include +#include +#include "keycode.h" +#include "action.h" +#include "action_macro.h" +#include "report.h" +#include "host.h" +#include "print.h" +#include "debug.h" +#include "keymap.h" +#include "rgblight.h" + +#define KEYMAP( \ + K00, K01, K02, K03, \ + K04, K05, K06, K07 \ +) \ +{ \ + { KC_##K00, KC_##K01, KC_##K02, KC_##K03, KC_##K04, KC_##K05, KC_##K06, KC_##K07 } \ +} + +#endif diff --git a/keyboard/nano/keymap_nano.c b/keyboard/nano/keymap_nano.c new file mode 100644 index 00000000..33733c39 --- /dev/null +++ b/keyboard/nano/keymap_nano.c @@ -0,0 +1,84 @@ +#include "keymap_common.h" + +const uint8_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + +KEYMAP( + FN0, VOLD, MUTE, VOLU, + MPRV, MPLY, MSTP, MNXT +), + +KEYMAP( + TRNS, F2, F3, F4, + F5, F6, F7, F8 +), +}; + +enum function_id { + RGBLED_TOGGLE, + RGBLED_STEP_MODE, + RGBLED_INCREASE_HUE, + RGBLED_DECREASE_HUE, + RGBLED_INCREASE_SAT, + RGBLED_DECREASE_SAT, + RGBLED_INCREASE_VAL, + RGBLED_DECREASE_VAL, +}; + +const action_t PROGMEM fn_actions[] = { + [0] = ACTION_LAYER_TAP_KEY(1, KC_ESC), + [1] = ACTION_FUNCTION(RGBLED_TOGGLE), + [2] = ACTION_FUNCTION(RGBLED_STEP_MODE), + [3] = ACTION_FUNCTION(RGBLED_INCREASE_HUE), + [4] = ACTION_FUNCTION(RGBLED_DECREASE_HUE), + [5] = ACTION_FUNCTION(RGBLED_INCREASE_SAT), + [6] = ACTION_FUNCTION(RGBLED_DECREASE_SAT), + [7] = ACTION_FUNCTION(RGBLED_INCREASE_VAL), + [8] = ACTION_FUNCTION(RGBLED_DECREASE_VAL), +}; + +void action_function(keyrecord_t *record, uint8_t id, uint8_t opt) { + switch (id) { + case RGBLED_TOGGLE: + if (record->event.pressed) { + rgblight_toggle(); + } + break; + case RGBLED_INCREASE_HUE: + if (record->event.pressed) { + rgblight_increase_hue(); + } + break; + case RGBLED_DECREASE_HUE: + if (record->event.pressed) { + rgblight_decrease_hue(); + } + break; + case RGBLED_INCREASE_SAT: + if (record->event.pressed) { + rgblight_increase_sat(); + } + break; + case RGBLED_DECREASE_SAT: + if (record->event.pressed) { + rgblight_decrease_sat(); + } + break; + case RGBLED_INCREASE_VAL: + if (record->event.pressed) { + rgblight_increase_val(); + } + break; + case RGBLED_DECREASE_VAL: + if (record->event.pressed) { + rgblight_decrease_val(); + } + break; + case RGBLED_STEP_MODE: + if (record->event.pressed) { + rgblight_step(); + } + break; + } +} + + diff --git a/keyboard/nano/led.c b/keyboard/nano/led.c new file mode 100644 index 00000000..2966b8b3 --- /dev/null +++ b/keyboard/nano/led.c @@ -0,0 +1,38 @@ +/* +Copyright 2012 Jun Wako + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include +#include "stdint.h" +#include "led.h" + + +void led_set(uint8_t usb_led) +{ + if (usb_led & (1< +#include +#include +#include "debug.h" + +// Setleds for standard RGB +void inline ws2812_setleds(struct cRGB *ledarray, uint16_t leds) +{ + ws2812_setleds_pin(ledarray,leds, _BV(ws2812_pin)); +} + +void inline ws2812_setleds_pin(struct cRGB *ledarray, uint16_t leds, uint8_t pinmask) +{ + ws2812_DDRREG |= pinmask; // Enable DDR + ws2812_sendarray_mask((uint8_t*)ledarray,leds+leds+leds,pinmask); + _delay_us(50); +} + +// Setleds for SK6812RGBW +void inline ws2812_setleds_rgbw(struct cRGBW *ledarray, uint16_t leds) +{ + ws2812_DDRREG |= _BV(ws2812_pin); // Enable DDR + ws2812_sendarray_mask((uint8_t*)ledarray,leds<<2,_BV(ws2812_pin)); + _delay_us(80); +} + +void ws2812_sendarray(uint8_t *data,uint16_t datlen) +{ + ws2812_sendarray_mask(data,datlen,_BV(ws2812_pin)); +} + +/* + This routine writes an array of bytes with RGB values to the Dataout pin + using the fast 800kHz clockless WS2811/2812 protocol. +*/ + +// Timing in ns +#define w_zeropulse 350 +#define w_onepulse 900 +#define w_totalperiod 1250 + +// Fixed cycles used by the inner loop +#define w_fixedlow 2 +#define w_fixedhigh 4 +#define w_fixedtotal 8 + +// Insert NOPs to match the timing, if possible +#define w_zerocycles (((F_CPU/1000)*w_zeropulse )/1000000) +#define w_onecycles (((F_CPU/1000)*w_onepulse +500000)/1000000) +#define w_totalcycles (((F_CPU/1000)*w_totalperiod +500000)/1000000) + +// w1 - nops between rising edge and falling edge - low +#define w1 (w_zerocycles-w_fixedlow) +// w2 nops between fe low and fe high +#define w2 (w_onecycles-w_fixedhigh-w1) +// w3 nops to complete loop +#define w3 (w_totalcycles-w_fixedtotal-w1-w2) + +#if w1>0 + #define w1_nops w1 +#else + #define w1_nops 0 +#endif + +// The only critical timing parameter is the minimum pulse length of the "0" +// Warn or throw error if this timing can not be met with current F_CPU settings. +#define w_lowtime ((w1_nops+w_fixedlow)*1000000)/(F_CPU/1000) +#if w_lowtime>550 + #error "Light_ws2812: Sorry, the clock speed is too low. Did you set F_CPU correctly?" +#elif w_lowtime>450 + #warning "Light_ws2812: The timing is critical and may only work on WS2812B, not on WS2812(S)." + #warning "Please consider a higher clockspeed, if possible" +#endif + +#if w2>0 +#define w2_nops w2 +#else +#define w2_nops 0 +#endif + +#if w3>0 +#define w3_nops w3 +#else +#define w3_nops 0 +#endif + +#define w_nop1 "nop \n\t" +#define w_nop2 "rjmp .+0 \n\t" +#define w_nop4 w_nop2 w_nop2 +#define w_nop8 w_nop4 w_nop4 +#define w_nop16 w_nop8 w_nop8 + +void inline ws2812_sendarray_mask(uint8_t *data,uint16_t datlen,uint8_t maskhi) +{ + uint8_t curbyte,ctr,masklo; + uint8_t sreg_prev; + + masklo =~maskhi&ws2812_PORTREG; + maskhi |= ws2812_PORTREG; + sreg_prev=SREG; + cli(); + + while (datlen--) { + curbyte=*data++; + + asm volatile( + " ldi %0,8 \n\t" + "loop%=: \n\t" + " out %2,%3 \n\t" // '1' [01] '0' [01] - re +#if (w1_nops&1) +w_nop1 +#endif +#if (w1_nops&2) +w_nop2 +#endif +#if (w1_nops&4) +w_nop4 +#endif +#if (w1_nops&8) +w_nop8 +#endif +#if (w1_nops&16) +w_nop16 +#endif + " sbrs %1,7 \n\t" // '1' [03] '0' [02] + " out %2,%4 \n\t" // '1' [--] '0' [03] - fe-low + " lsl %1 \n\t" // '1' [04] '0' [04] +#if (w2_nops&1) + w_nop1 +#endif +#if (w2_nops&2) + w_nop2 +#endif +#if (w2_nops&4) + w_nop4 +#endif +#if (w2_nops&8) + w_nop8 +#endif +#if (w2_nops&16) + w_nop16 +#endif + " out %2,%4 \n\t" // '1' [+1] '0' [+1] - fe-high +#if (w3_nops&1) +w_nop1 +#endif +#if (w3_nops&2) +w_nop2 +#endif +#if (w3_nops&4) +w_nop4 +#endif +#if (w3_nops&8) +w_nop8 +#endif +#if (w3_nops&16) +w_nop16 +#endif + + " dec %0 \n\t" // '1' [+2] '0' [+2] + " brne loop%=\n\t" // '1' [+3] '0' [+4] + : "=&d" (ctr) + : "r" (curbyte), "I" (_SFR_IO_ADDR(ws2812_PORTREG)), "r" (maskhi), "r" (masklo) + ); + } + + SREG=sreg_prev; +} diff --git a/keyboard/nano/light_ws2812.h b/keyboard/nano/light_ws2812.h new file mode 100644 index 00000000..54eef22d --- /dev/null +++ b/keyboard/nano/light_ws2812.h @@ -0,0 +1,73 @@ +/* + * light weight WS2812 lib include + * + * Version 2.3 - Nev 29th 2015 + * Author: Tim (cpldcpu@gmail.com) + * + * Please do not change this file! All configuration is handled in "ws2812_config.h" + * + * License: GNU GPL v2 (see License.txt) + + + */ + +#ifndef LIGHT_WS2812_H_ +#define LIGHT_WS2812_H_ + +#include +#include +//#include "ws2812_config.h" + +/* + * Structure of the LED array + * + * cRGB: RGB for WS2812S/B/C/D, SK6812, SK6812Mini, SK6812WWA, APA104, APA106 + * cRGBW: RGBW for SK6812RGBW + */ + +struct cRGB { uint8_t g; uint8_t r; uint8_t b; }; +struct cRGBW { uint8_t g; uint8_t r; uint8_t b; uint8_t w;}; + + + +/* User Interface + * + * Input: + * ledarray: An array of GRB data describing the LED colors + * number_of_leds: The number of LEDs to write + * pinmask (optional): Bitmask describing the output bin. e.g. _BV(PB0) + * + * The functions will perform the following actions: + * - Set the data-out pin as output + * - Send out the LED data + * - Wait 50�s to reset the LEDs + */ + +void ws2812_setleds (struct cRGB *ledarray, uint16_t number_of_leds); +void ws2812_setleds_pin (struct cRGB *ledarray, uint16_t number_of_leds,uint8_t pinmask); +void ws2812_setleds_rgbw(struct cRGBW *ledarray, uint16_t number_of_leds); + +/* + * Old interface / Internal functions + * + * The functions take a byte-array and send to the data output as WS2812 bitstream. + * The length is the number of bytes to send - three per LED. + */ + +void ws2812_sendarray (uint8_t *array,uint16_t length); +void ws2812_sendarray_mask(uint8_t *array,uint16_t length, uint8_t pinmask); + + +/* + * Internal defines + */ +#ifndef CONCAT +#define CONCAT(a, b) a ## b +#endif +#ifndef CONCAT_EXP +#define CONCAT_EXP(a, b) CONCAT(a, b) +#endif + +// #define ws2812_PORTREG CONCAT_EXP(PORT,ws2812_port) +// #define ws2812_DDRREG CONCAT_EXP(DDR,ws2812_port) + +#endif /* LIGHT_WS2812_H_ */ diff --git a/keyboard/nano/matrix.c b/keyboard/nano/matrix.c new file mode 100644 index 00000000..08d051c4 --- /dev/null +++ b/keyboard/nano/matrix.c @@ -0,0 +1,179 @@ +/* +Copyright 2012 Jun Wako + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +/* + * scan matrix + */ +#include +#include +#include +#include +#include "print.h" +#include "debug.h" +#include "util.h" +#include "matrix.h" +#include "rgblight.h" + +#ifndef DEBOUNCE +# define DEBOUNCE 5 +#endif +static uint8_t debouncing = DEBOUNCE; + +/* matrix state(1:on, 0:off) */ +static matrix_row_t matrix[MATRIX_ROWS]; +static matrix_row_t matrix_debouncing[MATRIX_ROWS]; + +static matrix_row_t read_cols(void); +static void init_cols(void); +static void unselect_rows(void); +static void select_row(uint8_t row); + + +inline +uint8_t matrix_rows(void) +{ + return MATRIX_ROWS; +} + +inline +uint8_t matrix_cols(void) +{ + return MATRIX_COLS; +} + +void matrix_init(void) +{ + // initialize row and col + unselect_rows(); + init_cols(); + + // initialize matrix state: all keys off + for (uint8_t i=0; i < MATRIX_ROWS; i++) { + matrix[i] = 0; + matrix_debouncing[i] = 0; + } + + rgblight_init();} + +uint8_t matrix_scan(void) +{ + for (uint8_t i = 0; i < MATRIX_ROWS; i++) { + select_row(i); + _delay_us(30); // without this wait read unstable value. + matrix_row_t cols = read_cols(); + if (matrix_debouncing[i] != cols) { + matrix_debouncing[i] = cols; + if (debouncing) { + debug("bounce!: "); debug_hex(debouncing); debug("\n"); + } + debouncing = DEBOUNCE; + } + unselect_rows(); + } + + if (debouncing) { + if (--debouncing) { + _delay_ms(1); + } else { + for (uint8_t i = 0; i < MATRIX_ROWS; i++) { + matrix[i] = matrix_debouncing[i]; + } + } + } + + return 1; +} + +bool matrix_is_modified(void) +{ + if (debouncing) return false; + return true; +} + +inline +bool matrix_is_on(uint8_t row, uint8_t col) +{ + return (matrix[row] & ((matrix_row_t)1<LRXvnZg zs3|l(A|u?WIMI(!j;YE_Ov-A?u*fMg$ttSKRBAXWGS#}dJ8S&CIsE&f+N#&0=H<%>tqFUuv^Xf1pr6LO`rRLqPn;nEz7y|FqiAv)c}^VZ;12 z=oOQ1Q&UPc*9$=%LX-_pM#TrQTN|+9A<3rQuV}a4V-qi`3+Lq)Vi|Umc=!NF%47p# z7xG1>2W~8%7t5-)h*(#qUS+EQn-xo!z630LSNXC2i(bEK9`59O)#20qjT?%i<2-3extEU^`@_aH-A@&)XFiw-&8Q$f_I3mh_bMB+3Dl0qU7&zNd^8i%*N%4jdfY zVb~IDyk%C8>0K827AY%dzaAJV4Q&ZWjd z>2V2b?0OahZj+f~Wn$iy-J!40{~gmxs+ZTr8Yhh9N` z4(PieR!o*+SUW^#!Li#!lpH$~mhqr_4jG_5u6UZ*;~*J4vR!2Xy4qk%udinU)$&dd zq^=Mf@#l=aZ@1}?kRB5~j`U^!6hy$`HrlvJ<0DA$%{GwlnU>eA_jGNEn!_CLL@zB@61G|`;Pc#?QO|oG?85Nwvzj)?u!yQ{!9#?!=MT+${vbQ@ zPy{&9=Prf=0a;#If>5TTiIYOoDB5$Z;c~Vea)c5!C|Y*Zgmp?OW0e>+DX{hs0WXC) zNTkQ=hOgg<=5ppan_REor9`cv)+f(Q%bCtp#ZYISBfo_j#Q!LHMDz~( z-@)_z)e*}91p$GD{J(;yp>UBp69-~LAHI_58qjYi>ufX?jO&mLDb#$S_0Y7BqivTEMh_kXZ?Kc5rLv2O zVHDZ*BH-k-(PMFqoYipWw{RLxa#`xP0u+8{X6tolG zfD_y6YjcbdR4$y~H;&&`Q+u5qI&|~rWU^vVuAF#7vSMR)Vk8k2x$!sU;?0r;$K=Zt zV}e|G%x$tw&H4cWF~)?n*Im(;+do^^9FBlqyuYVT#hj9Q8e)6uBjUMCiZ?r{zeV(O zC90;QgA&+^C#e$+iGPjWg~Z4^OyYTg_Ihg5Q!&S)!m zZPs1OWKt}<{BTKD=u?^71lY9_3*&GfRc*OSXfq0Nad+Tnu+10q;Ac3?@Av0#j1EU4 zJUp$AMB+bu22qS?1$N=~+$|IBz8!oBVaP2CMo#vadQi9L6x$jL??fO-h)GaUU{D|> zc+hx9n1oD}o@1H)Zp1oae}$w>r;nkCp^_q(f})I@gGYIO*ghHp7;gNhc|YpHL)9vN zA=A%LPVW70eA(N4`!@d%U-u~g3twUi7ikj?pzqjWPcq)&YxSaB0l&$*E!Oa!Z6jF_qX#;?6@wHIAGx| zG3S+DyQPtHstAUt)V>Yd`y-n?NyaK0JHhMv%kE`QSGq18ryQGl)B{9(1VP{lCf|DYaGmJ7EdCVn2ic*jfT{1+hZ3d9of~1OH?;i>hylkYCNx_ z*ye=ee~vjkL-}z1#Oc!*U_oTi*N8SmDPnP%saiiF1*GXP(jaI@IuVrU1$9E*=g{O! z#*cGTBoGG(2t=4eSr~D0@>&uf&|w3EB&%!skJ54xX`q0K_%-G;OV=vMt`xL4V47G| z9y9P)O0qZA@qz7Qm05<`W~SQfT;23u(?IrRY>D_Mp;KMAsuGhoCexthT_-aUeA$k*h65X&mF2 z`U97e>#FgMCXy0)X+|>|lbVxGN0JEzxp4Gi(sUh0#NC^pSQj#-*&c!_vuTydjxR2caBDiA;|CxDqtdf47%M6!XlE9ENVS+iQp;G~rCOIx(QXOBDnUO% zldr&}o1#feJQ_Coq8}e;0va$xp+h}y_?Y527}#TYbAyLXmoCzE(qIwl9y=M#DE*6E+oO;Z?66UX(Oe*zh-*>1$vT63XU>1px)U?VN%8 zf16K!)>=4My3fl=@YU~t|HgmmvtQc{90UY3;s3(FhB81$(GgqrOSkl{$USu$UG%vN zz=bfuE(8G`wwYpNjcq~-36WwA`t7Xk{4}F=(+UIrwM8V?sH1XY0Cw=C?em4xz#-t$ zUjc315qPpxhNitR!kgw;HZe8z@-Q|h>A7W}=bxKrPvaFy%L?=21?=AL&P4zD+VkcN z^z{7Il!}b-M_>4 zSpW=tyLsjk@(21}PA{A^gYQmmKJtz>@4+`GmpXpR;!l7rz(sZ&no%iVjsk03T)?MS z#|zP}Vg&67_+~Sl_v(cd7lltekP`t+Dm{|b#2KlizWcZWdQKc!)y^A!@Vx7nmk`2?XSgCS(k+mH z^o*-`xp@Wf5a(U!;j>aXeh`Yr#F|w>%+U~z+6BjJC$sJ2(1sk#qKujistDCU>M)L3 zN5m$F=@=Up&>I%8)j&>3RdD`6WqyG{6fHSJ{Tl;kR!v20He_8)IBSP(EXgnsg}sDR z1(^}TUu}gl`Xe?bY-QBSE4GKDOUek}a9)yU@0Wtq38gJJ86$3u*HljS%BFe5)TViQ ztX89}<)j($)c!e~#l>Ombk$ea&KjQpLpK*83tfSi)-(?enxNIGV?9HTjJUj&Os!4x zV;RN_v!Hadd6{7;bW_T;FO;&aY`d<=hf$CY&$D`i#jyR{X4P!9Qx;52J2gvn_EEgn zAd}CC$D8Y~!2lr1gAKA;s5O^rjKB{C@O6Rze#+(Sr6L1`FrO<%i$CQWFiH=M*K|4`_p` zd}e!+|57v2dm+L$Vn!9E)ZZfMAXahpCe%}3?pcso+cO(ORBv2NiKAC}!pxetsdZs0 z3M?HD$?-)XyTqxrq8QP6VM5_o4$M7DSnp5N($@?YoKx=yWzVX$8pL_5YAw&PFf;Bv z&dy``mfypgIJ#?N@5@4}m`~Dk6jzki=7;BdjijFmcLguvudU}=M(RdKFa1_Td1$?g zH;mhOG7P^jMD%46&?0YT@YeJ|WTZR#YkKfq332joic@?&Z(NZbR?ltnTwBC^529uf zE^8~6E?E-0C|M*9YQyX7uun^Cd<668W)xE8;x`Ssn>g|QQUX`BinuuHF_&^impIL( z%Lp8uNdJET#W0_%3w5&^BrgGYMmV<73Sml5Q|f1bIuc2*3HY53 zKFPr#tEy<(+l5m5A+2W5(o^*kMVkB&P3ba)x!u`2MpQKUu9>6IRAg8n!QXyxtZnSR zTZ~r}Y!+{fsj6tD5eiTfmrZ~UOU%jowpeF>_Ct37;02$Mbfr4o@f~ z>@142^o{oX&AGO~+3om8(RrbOPnb)suvXq@4rX_6lzP8tzoFo?dDb;MG1Z1`p8Q=U zjj?0$Xu9@n{rs*>wSOR8fvX-pySQ#-mlj1`ii}HQR%23iQQ$5gpv9DmA5at)R@4K< zHc_zPMWZJ*M?q`L=1LHa@n_>OVHc^$8H{g~=x+0X`-92C-*kK`hJXvy@H+SVw>lu0 zmfzooZI#dT;izXat1EO*E*R4~^dPFri$7?kgt2#i$hZjgOpvRd--|7q+hBNsD%?BH z2Q@hl|I4HNHjjhjR48GcvaAinG?lp8vM29)bG2o{K8Dzl6_9prLdDaD@-}hOJQ~Yg!`%7{KX1IcAU^3ktt!wl_I;FPO!V)d=WQ1=1GiXAla9@Je%Hd_D{c}znlc`nguw-(#YlRxVgylaS(OlS)b+)NMTjNlh>O7Y z``RC>Z`&Odw^ahryj~|w)3vXS6r6khFI5@=?~Z7nFE=Z_Z$`cEGo%5}n*pB(BfT#o zACIITJC2`wzd!Gdj5OeLLd!FQ~^Z~sOH0>C$)AGac(&#WRJ&)Wf? zWoJ92pRXfVA9VpQdm^7A0j~!mpU-=}?;pxuk48slYyXrR)tP@@qJ17ngKx4(KTl^q z-nYSJy&o3=(+{Ms;Hf;L&u7xl$L-J9kF;K0*#P6>UYVCy)__OLfRB}cC*0nbyR%R5 z)aT1pz}w!H{~NBz$Iib?$IpS^0dH*qPo4oUPrcx`-uH_K$F|;A&)(0YfR7u`-nWGS z;Kxm!oOaY`K#y?OasYOP_G!#-L`cdhm}|WHNTxgC3TlD?C!oy9mo4n zChU6K)so$q5tFhnsLGhhdb^4kGYEt_r98XSht_NVEzI9HI3(Y2f|u)h8Jf#wlVs5V z$YXA&A}1X|%2bg}{wnEv4MD8+83>_s@1+1PAR_k;a^uHkT+J^oyE5_Io^>C`22H=A zAB6EAJbY;>ods=E>(FbjP$m=PUywXi-`HC*2cbkuh{$F*?%|?vqKA)06zoMGo+Q|;ySCWw#(jVRvo-zoa12M-Mzh5r? zVMt!0o1`JS9_K4(uG$z#Z|bsVDW2hLP35p3-Kx9 zOhceU8bf647~vvteGaQRW`8}db;-W2H9GIy&2AUG8^l+bh3OOm_gHy#rTeOF&`QZL zE8(;4v8ZsEU_|zf&%z5_y~b)XUB8%OM79tgVMw-IZe;mB9K>8TBpeSiP5w0|GO<7^C?rlm=K)_MYDL!q+e7K{oI5t_6vHF{c~ z$D=nRuSW~{2k{L1geZ*At(R#Ew!C>R>anhlYW%JK(Ih0!$=|mbWl-PJ-SbCE###?R zKd@bjMYTkajqA#xX}srYQMJ(7sB>@nS;hWtFCh9n-0^7wd8KdtX~%in)&v-H-!PJ$ zQHe(DhaIdNk=2u^^EEiY2(oc!A1B6SOHgdwg8zA`->|zl4ay^TYHYX@yeo|XTM)SM z%~>Y3PlpjM^s)}2Zq}6#5OG|-mRx3G?=BLWtHGWfcZtRMzK+0lrgz~uvdP7iToydAA=sbUthxz?V|N)U?t;E z3W-~Kn7UwG{F-H!v#KRj^W0fuX!sqjez+(u`^mG|{l!NRC7099*#`L?`0`A5mVKAp zC`;Ejw42x839eAfgixj_|p;yz+<*jCKnTqw=)iy35Jx@M%5zMY4J zAp@6)Iou7n1^AshshWuvAm>;;V;xAgL~_Oyy8&H9Sb!9Rfa8(sLQ zj@$r3nfhB;3o{5B@|Lh*48m4ZLv13ZbMjM#)*BVq6 zRXIyk<(Hgul5o)5T;qfSY@tm{zBfcx8W&l0=d&wiI&gm+3`h!=^h5H~*Nk{?3{Qz2 z*N^oayQc-YfS*IDQAXN8b*W`ev(wp9m1GN+5Jfqo_zLScBl5o>Ga>Oq9J{w* zJ~jYntb>(3Tpu;vGQ3sq0W;MuUb49*+EV}O+fdy=bj%w>c0Lhlj0#%sF}YQs;1O^6 zMRQ%frKgU+=n%DkHn}t8%Gv(k2SLFaXF(gzrS6{ZkAHi7=mcsmuNVeSV2ApDUa26> z)|j5x@g|ux<%4Su^c=LtYK8tSM-qd{|JhVI!^enogE#A1#cyGCEcAT?C9;h!)NFxw zRp#=z8(^D`b^e5VgjjUB$vul)@8Fm_dnv2j-I^*&GsuRizvJ>zob9)bd}kI;2e3XD z%S(`*lM0SbDKN?>m)eYDlRsXow6d`-zC_+t8C;!C*Li?i7D3+g~Zehd9vJ}stjmH*2APnZ=HC0b$d9O=Rax%PZ} zKUO)N1;5~P7*t%WAPuiyYEK|neq3q;^Us;qIg|f!_7nn*E`-y8M$FrDVy{C8ikw1> z#M>v;b68xdGu)l6l>fB$hzGlePsVJ%tRTt>%Z@eKTimgirkg6vk;w&5wvdGG;g3zSSBXq}Q0;tNT+dNyPr?f60zGb|8=l3edh$^PRZ@@P+ z`}l97!a3$YmQBjC-8kcUN%v(IaN&sy1nK4hChz`8aVn4c8w`}k57MK_$3jQ*Y8a1b zr-y5sB5y%qb>I2Kgn3W>!PEG57-`?>duk~k?h!V*DXe{GZ@D~;;GhI+q{ z(zfyn-mWhW;1Jhr30VnfVxRmvkO|@Sy zA*Gv+X#U19)Vrl?NW0$dtW+uSD=Nn)L%9#G26HN`@G_+v?E@0EgjZ&EzQx)JVTMf> zIpjHn`^3fi=)TB0xwQ@jjy%?nu>Vk^<}koZ_P>vs!B^?xPd7b zPP=4HdI9!1k@|@MeenY3aOO%5{7De)sM=y_0C=Ni#-|L3)4PbTJri$E;TZS5!*1;j#D)oevUMW_2TXl;s!0?CGT@5yfVKs@Z))+ z_a)kBj?AR8MObu{C_axHgbN=#%D^c^v7%Jl=nYjyfKWBc>0-@|o6-hyjMc5_YH$=E z*K+pxK0eRuNb>0oK10?KOdJuZ?VR&FERji(-fvuQwjB)9w=})*7RWc2=&>I4c`Hf2 zC9xY-*86MXk3k+qiawQ^FNC)OYaB%y?_S~G3~y=xV!j9@7aUTKK)C8lH@zl_(Gk4r zE6H;!eTGz>;k@XZ{e!Pc#1%7ICNyjHDR;U#4Vgfkpr|88d;uA$43;Py@^H?bgv%no z{iW|VWiFj8=3V!=GPtfe?tpkaK7QCOL{6N%#S9I8=p2Tg=f*O>KrAyzs)&4~Ek0HU zQt(~#5{d|sizhUTEV&5*1*%IdqZy>(m-Sb$=F_&$rV!@@FBfVth~xc*G(FA=8S}<+ z;?Lyo3_Z3UrV*C_B=KkT3B(;=#wU)m1zo_)4n^+SH%lv6h5n%(D{J~(=POfIzazBb zrwqi5m03gIF*+_=g#!Df{F_wGt{!ZCQXm_!j+6AxJ>+SKHHR;Aru2~Up zAo;%0go75bEUa8>qg|1D`@vgs8o8mk@21K*B`rZXqff}|S){J~=`YpNX@9UeP#9~) zQoBNmp=wr_zNALl(*Q3)#`{#Jk-3;il2AcpcEzcZOyGOFhN$CJDM+vk@I;MJ>p4#a zT2L5h-ESd71~k=O|KeI2^C10E)tRe+f*obFlNmQa5??DY>wm}CTnKKqTH7zwg2+dc z4UO1e+feV=o$~$sRU9C0kCH6x-U|!A--)}Db}19aBet~eqwCQd|GjEW+e<8eC5~7X zvAZo6B5mv~c7Y))Wu3Foh2jbT+uU=}DF{X!b5S)so@7Lk?cA?;a{E-P!O#J!Uxz59 zu(%!}>U2zPsUG~omK>kfs|8Melgr|n6aw#wfg%?^B(h1Rt=nwf2q^>`&m8UVDKYZ| zY~9i__oNHaTaWQChkv;C}eam45Uh7|AS_V--f?tny%UuJ8?|NQIvyZ-bB z8*7?|wo+%7inelL_#f+KnMgA&=Hva^D#+@^qvM;kIk!1w0;h;y5BT%9@EVwTpbGqp3%wkl`c(b}GvZY(lTeW*_BNfVN z$e*URgHQcTOuLO1?H78$69w1QFrXfWxZQ~Ox$}v zH&Ue;Dy01xvZJKWa4V{J?2QHaJ;=AjO?_I1mYm|1q*BTt{^z=HWohShcW0*!>Yt&N zJ44}09%69pt-m#EL~cWuc2YlOqx4;iDyW3_V!I`zZZLVQ|BverUHg|IxG-5L30QJ$$i9~~S=~Tcf*PGNn zB*s>mJfsJ0qqUF7h35imF(*bsC;At?CB}VVCO$?ET%U4YYwM*o0FSHE=q{GE3v766wG~VJ+w`M zSLd{i)O_X~59wKc6m<)UFA|+tLzV0rJmr}DWVgS{jmHZ0JXusN;c@zlU;E7GC$I{M zm1;jXHFeeXJTwbEJ>ruQ(|pE%%cycD3gjbbL>eUM_>h)*Nm^VLJAYsNR=P}J^QdeC zaQ;p}G*hg>kaNa4ukSUGtiEQAhmnrkxVg%aKVSM7GP#?V+0@P}E%5}_HGXmP{|Jz| z?2K_Vm!or>bXY6P;0D&T2E)#)0|m!eWlYJ<@9gwv z-Jy;yVY!S;NKNn??}B6y1jxD8z2mr%txh(FoI87W@%va5}P@? zJ-<6udJ#Fm8Vo}(GTF#VVH)0Q5`#Ryh_H56F2RXcYR9CiQpW9x=oeNg7E7YBOmA)k zSQ2OPeXKZM4MZkjjX(6~lesE7r`1KS!3H53EakL^!!+~BZEHSBa&gC&U=-t8T)hNU zI#VlAB50KK_P>DCf41C9^V*|xZD_U^j9ty^WeJED;_@$0)UI4z)^HYU)I|2G>Ja3x z2E%$uoBO%m6NeK_E1Xg(lF+GfBbOLyo6RM!(_rpbzd(zNZ8R-7l_y z*?iOaWcM#0b`Rt+J=<}$N-*Pk%M1s|x)-qXrW_}w4@k7*N<=NCN0V=zEO+ubtT27w zs|C3dUAv3+B8DfPR;o66Xbn^!=#<9saTk6)z8Yn)G0*pi*F&V9ZzK5kGV+14ZPsgvQ)Nw-Ge;U2o!e*MTf$z|6xwRK@k-&|Z$Gp_>t~6Q_uBPJ z1mLB4&kH-kp;N|Ku&9L60t=l>?Q~1=>`#$xc$vMlND?~)k=Zk2Ohlno5^Q zsue3b_Bs8<0_s?@lC*OUW=fPCJR>dtfD9ztaIM`xwPMj=bLn!5+IHl6eOTGQZ}8S> zcbboSz6t(~RTn{j*S1T1mrWv(K^3|fyxTJX2bt9^{zD8kwC^PsDj$-n|DIugUjlNF zh{#pd)oW~uOnC`=RVvs(SIrq;@t?2}t*B0tg21J#7QVDXD71->+Q5~C4pOXHe;11= z6%T7NTo}?r7ATIyqcfJ5ICLfp-AWg%s4+uywe3@-eps<9BsHEhUy=Qx#$HcQ5i=IG zq`pcxCB2Y%&}j&m%>bSkNa?xBV7BeX@GBhcXP4+Hl7R-6<8(7l{-lcK=MB8f3HPz6 zdEwhEi-Az1K7tJkQGlq8*robtbC2PtO>ZG?_q_q&bEx=iH}MTCgep)pE6iP0Q8Oqj z69FZHX@HXpyU@MDvFs|b&Z=s3DH0RpaT|18w}`L>G(4jpG@83v@-m^ltP1^Yd$sBt z(!5?8&V@M}sZ1tRc!jxEYsI=l;kirWgsQTn7q-&2a6owa=VDSZ+5U?76h^3Lc?(8L zC$rWTv$$bTsqh_w+hCCFWJ`nMH$bp)S+-U+3Kuf?IfQ4iYw-&vZKk1s)q`$9+vBA@ zZgHO)!yb?-?_>GMTVKJJSxB`_R=swXSLCgT{UJJi)OH7G5ReC*|gBQ=xk69c^F7juuMYNtT?Yj zKDUj7d571N+UO>2UhWD}ew@zqC)@FjV(^wbl3et0_FpAg>~KgoUma=S&`#W?A> z&_EkgHvP1LL z0k;Ia6>rGCS=47cNAS*N;eu4wIjFljF?L$T(;%O;9e&ps-qSk`GSl73W>MQvIm-EZ zMco-iH9Wh|IQ1H&nLLP0pNT^Z5yAqHT<{cr!V!WnYtx^>13@6?a1&n96-VodYwQ4{ z1Wb$xvf12_WTPK3msTTZ&;{g+Me%&NX%01|tHUhqhbcb8+|}g=?Kz{iLb)zu{2}*I zI;-iyn*gbrHRDr&si1Oi+J+r}ktWp3`DLx;KFuW0SzA4O*p^Tz_7awb;sw$sbC#WK z?hezkRP=rH+tslh5jz*9DXe~~qj>q*nM3pN%G0GfWr`NvQQ4gf+TT)OrtfVmOq2UN zS?o;}_ZGHgY2~(sfNUww`{o_W!`N&$HZQN%m6$WJVeh3vUbWn6h-V|))t04O*%jo9 z8N(JzluTI#PmYHO>vF0Ox})Q=>Mt`F<>j@Yn1OF%p4F5V;?&16dd44ARUf&qi9e?9 zi0#}LX>}{&iJCD+P@A0NW0-wYnWSx7lYSIDOl=%E{E{PSmlrau&Fi(9W1bx+$mLK> zYe_rV;I(T<>?dW*VqILVEcc&b6(9C&>0r}yrSTolWU$~Uzf6cTqX;rWA!n>o-2QzP zvzG2-sbB&SJKP#~X0|FF-}V?DeELND7F9zzgv=X_6$cZ-oJ(~(NiT1x7;MJVSO!sSm-k zpY%g_n@n9jA=9=)>pQr^h67h&yg8AaEdR`s3HDhyH>D%bVdeEkA} z+_)23F%-F0G3lN{3U;%yrO)&20y&}W)EkCsy30K zQg^SArm57DBwUCMj74zHQtU?_j3j+}rW2i3h-w04*(SE>VYXR8glx^koEl+fUTy_r~{;ItE9opCarK~tZSnoAbrCWsZm;E zizFvGt*6Hk+~IAw?a^{hlMd-g^Pqy1*N9$umKR?Dwx)R5xfd=G$VLyW-XJ3DY)AW#D>pcr17?}~WJSg!ab)!9M z6#_+!pNbdXb9(rYdX>RtRWlJw(8F&9J^WMe$g4y*I;S+xp*K3vhnHWMzwzy;_A;Yn zj(FDaX zh0c4o!ajTt+hEc2G+Aqs?(HodwJPuh`sRBqV7;> zfpg}nREnOx)Q!zB2vLDtmnqJ#E!UiGTh9KqnUMsCsqmE=P;?k0(f{2+`k?q~+}#JM z;5y?230xlWfYw~J@6Nr#Jisx-3afxHAGJ39astvQx(?3lQHByPb7I~xb5m467f82a zqwnyhbAA;hqq#>ElSC8GoRCIZp{un|6qBLLhB0TkU#v%KB!~>L;vdwI#DEq4fei8x)L{c z;%CCvl#u7j3k%0?mLAMsqT=~`s%{|IS7NPE9I^txNR%SG(M5@pvhOp^ZjNS})%N!J zI^YcjbC@h)Pkb{a-k#fYj1Qt63pjR`+XN zcU){ob`8zyTJ{_GDN8-AJd`b`lr*eMbz{wITl9ci9W6AGAp6devr4$PZnXwU-}@7< ze{fk|iq>0MZgLu?&vVa1mk%JcfGP2g;s=oAJm~jIiBb zw?avxNIsMLODtEM2B9M^t7_`98PC4$a$|lL(_Yo>^l!$*&R*d=67c{#j_-r2T_M@O zDZ~1B#rRcQk^3W;uXEymzvR|UbZ~nx@?AQAZVFoTvZL8%2-SMazVpOI=CD0mj?(%a zVZ~o@w%G*W?#DPy?juGMCGAoEy*QmjD*N$6Tl6C&{=LQeZ!gpU8ERYrw;(GEscDF- zQ$V=j1#NXmHr=SEr;@$H#9jwlTUkHn(#hld;64WQQn~Q8u(5vn97fJma{fjG=jB!0 zk3Prisev~nTWhSdA}nO21Koc}DBak#pA0^L?{f8Hbn->YdF0d**Gk=Nbjg>mpq$Nb zZ?^E_aNRgD3S9fOK-A2&1BD^zuBpYBQ@gPrp|gyqhl=MqZq9lg-Dhz#w7pS(C^(a! zZET4Nk5jE2&*%=FCRA=l1yN-PX!8RGXLeof35nn>Uu@knPthAfDjOt($SB>47GK+g zI8v}zVZg^W8q+=yos9d8(6d| zWad%`z3l!s{6dbs{f(J{vrEW6V&zZT!5X+Y!G;cQKoO#S+wd2K&F{6_v2gkWD0rJW zTwtDq!emg=pH2EEc9ybHxPDO#Xe6CB? zB2tMLv*RHBa!qk4M%-zbc^Xpe$pz!nrpAc5VZ`FE`iYt|=+d;f9$Aj? z)|4Qm5KcBs7QCSM+V%(23wxCHnG-Gjx>%BFW2;KPcSy5j7u6*dxJW*HOPvO^HDh6!6I$N?pg=6G(Xk*$Om$Bh6D9M;*+x^S zA~h(h3tBc`JeECM*SDo-w$I7Fs7jeg!URc1I~yET7OLsOfp;V9yPJtAsrRgObH(;x z=gGg4AQ89N8FV4Em~5!IFYr~h=1&mNPw1laxt8WyB5UBJX?^2-NElz%k0YaYE7&m1 z89VgQ`zfk*x?1`rZzJfGeBo}$e(%28;)Wq+A-|?@BrPJu{OHwY=|}q52ZYN^t)$mt6!dES|y~? zFq2AbbYE$i#}*-rw)9#m)&{%`MTnBoAk@Qt49=G$_LI8%4Ybxo_we%J+56YNd6fIz zYTlMa_%kz`50o@*c=vK3SN~YX@60*Cn0ipYM@aU~+M1dA&IR%kdy%I7sKbHNTbK!gcMC_QGHG7~z_ZnC=be7hUXkKNAYN zl5C9rWYR;Je)*R4g{gANf?=8eyXH$2BjcRno=v!JS{k=z3RY!;fH)>uM2rxkgxk-t z{mz`qLF1buO3-`fqC@e|GHe;A+JHIwvW?Itx`nBfb+bYoD++#A(f98T7+EWgUXN^* zRa{QATr^`Z_2e;EpT*8J7@$50hu0%}K36(*wb?sA7;o<`+8FR5CQXP@z+z)A!5 z6=-@zyy=z7j!5s0<^_Ho`jHSyB`6_LuX>F4=XM zBhxn;AdCi2lL*o-2nQLv4uxsoNUgCXl~(ce&(GE@K?sn2Pg9ZP1+(0WXHfqbf#~PF zE|1O~${5z7d_2XqRw8Ov3pJ;=F_%x7XWZ&H0y)}c;{sL8Csh*7bncbE*Gn`KX>322 zV;n{&Joih+@nTsRk7fqxs;&g|=W0^*Ej2YgM-CsZofGs!@xN2F_K%bKXc{o^Qq*Em zP@-m`)dem*&{F-L-R~fL!q;WQTdED3>7>=xei9#$ zeGq-u^a-5J_I6f`d6cx+lxDOn$7*^bQ^v{>&ugs!2>zS5yE7^EE2Z5{J3O4mqxzIC zZJ!|tRb_XF)~N2N6PJmiu6}Xekc_xG-R99;MnBtP(9ZA$NT<4;Pf+P`?ei$~6QetT zs(HKWLsKG|l1h2@&yU;Pen|QDuL$Z=5%_d(9nJ6Wa0Q9K`!S8x0!5z3?(K+09NICE>4v#)ycoaSw1yDk$MAGnil54=4+i;YC*V(fn%*;$}sm z?^4S|o|7eQ;q&|uJjOXm>#?}JGiLzT71VLW@7a|i2Rh@(Tx@2Q&%w%bi0iOVu@aF3 zeQhTY{9?B-HRm)a=|Vkx#Bw>t(No%lNK5(!mu@M?^&^{1S(hp4DV@CgxEjQd|9%uz z+~~~D{Z6u7S`phM&!}tE{|UPFd#&E`9am?Mcm1wIRk>F+EOsd;aWO`fNWNvsfIyS9 z!Y)3fJQtTkbT8KjT5{KXm1<|KSg65mr_5!*x2ixlWFPARzp6 zT2p8&1#0e}v6#!9pU$tsn-&jopnpNvkY8AjRt&mBzq8a2APGF!*v{W!)==a|RY9#& z?$dH~f<*`@>S1icK?UZ?Agov#n-bLSo)94NL$9`)UY8nUCxz4~lEcZ4r5xIo6gw%x zYp)=Z@~;l=+vnkR4ecc@qKFrzPX=(V9T*vaiW9}IR7M4CqM>~^&3lN*sg;i6k?c&O z&i>qS^HC!QGN68v;~lxkRat6bS&;rF8~FBfxp)aE{g849oxM>j`P0gW`~a`f2F=xZ zD+dMQe(5c4n=;Z2*4roKhfyE!M|OEZhs`IQThK;7`mn6a8S=mE3;g*0T<~8Dpu$7# zB*2Uf4cNwufL40#qt#d+@zsaUEUrD5jM!YDJ|*mfE;&KvBzWJ>f=!B5<5Z4L+8?#%TQlce>w2|wExWPWZ0Jpy3s-nB1 zTcBJ5YZ`y(H)``R#c}a;Oycd}VC!qAHq8#`M{wkRF!Wj3QPkR_W}AbZ%-Qo=$)Qjb z(yg?e`v+S@iwYM%SpdX~_=RiT%L8?7KV8s{A4kQdTn$ay-eW-ZCTU$ z6mZR3u$qrph)cO_d3I^2Yogfwoo-lE?|?b5nRZ*fU1xz5fy;$=-5GSbVW#^=miWIF z@K&OL+x;Ol#6oO|?{{@{wm7duxt1$Hnpn~y;$`XCTkJgwu0KTpvno8W&`+M&RY>RX z61V}Q{LCUI*xI*5$)ft1tWK89Y_~Xk5PJZtWU-Pa&6Q}pd<|!d#rh$n@W+32a4t?G zp`qZZ^`W(+kUo_HI<*caJhV`CGZcf>gTd6cKg65O!=XtNOLMuw-F2m0!rBDD0uA&N7Lq6IFt|zZ_qnhYob(NHwi!7yWCrno}cF?Lzusl2#wVc{X8cP_*EE`5?1tU!wr*5i12{B)y!FfV=kwdGOYqZPk zLTaF8v5Ppre0xOXfD4dH56J7_3Z_0obyyf{VL-V#WKGF?012;0QR}}&zh6Kf3oG$m z^!}2(u8O!zaBOKBdKpS=+qiaY?`|Ge>F^_F%_|#CpQET#0PjVY{16ggH+VY4R*g(gahC{y+A`htfAf*5&DX`L(GwsrfM$6!<<}30; z#Z&Vb?U@P7HNT=lUWYTN+b8W^DIfj2g`qpq!=tWZ*Oh-=>L%&p+v@9cSoKDhvyo2q zV-_l7zwf1sQ3i8dZ~0 z0`(f}y@WO-{DKY?_+MFq+HpGzyZ~BC?abWc%v={vPWEpE6MugwvwmOpW`CPRjTbj< zSIm?c(yabuD0XPSt}N$oKb}DaW7Z%SaNv5oC!Wh7wfnmPt^#zeO}>4jF+lyr-?@k! z`)=u#LK^**PfCLN)~n$-oP0vZPBDY?re^z~IXPrT`gnplLNUi2SfP51S2xo740o() z0GxDBsSg+*3qpEWQD@r;%A)-6stUq9 zMGw}K4Y=`^z1t|44Zp}a+b^@|X%2n&G`bH(%syySJaTWw-%`#MC|j_yk;Ir?5J^V`^d8Dab;^-k>Fz*LC91KN@(pi--6= z+u=!**d;#yj%okLW4(pd7q%#4+M|97u0w5PJ#dKHP}Z0kWwEuxu|uL+GjKhLHZ)Hg zyd%*a6l_Q;BW0&-e$5wshS(tIHvLlTIntCNy!0%PG9cUfzIFsoF|&IA`EK2<2TYyl z=RNFp4vsm2Z2)Tit=++~TaJPs*y@5f!C$V(k&FzuaL3txme8e@*i3$-h3fEifW5;< z8qm^8&oLu%q{wiUnyv!@G{C!P^?g2rw;f6YavlA>98*urTqeov zl?Ptr*U9flmyz#!#nQWcSA+@i|4J>T>|w3E;(3H?=F&Z@bR}fMhT&41SiVv9|7w7B zzM{3?cn@Mzzzd}YcJcH26s}c#Pqi20mSsK>=V3@auuv>8`GKZWEPQBOjw|^3Tdoh^ z5-2A*xJ!CXr?CC0ysNW*TT6ya*QHr@F$YIiR0s6m>*}K3uK*9C#%OyCS_#Nwu|)U% z^pigXQiN}Wrs8y}-S(o!&`tDJT6dS--|Ta!A74(=Ky27DesZPHO=OR@mkZ^PNi0=S z6g$Gg>MFP;mWzP}1e?j8kU;T7Tzkc#z?SNMHnWL-8^^?tHJ)>cv7w$6o46fxkTRRfvkd5C*n`0^#4MTgI$!&0U%F`*@$$x=4==Larn{->l zeMm-%ZlzZ0FN3-HwW)xxj#cL`^apq~YrG?kCa^O7+7yDweB7TacO`yCILc%>uXdipkX4i*Suy zs6L+EyjOAdHO7NDzeFD%)-cKb@Azq+np{LfxU?i~_kLOak;VZb#i{X_^0qov&H^$G z&x=>^HJMXT@=rB!Hsw;bgGE)9z9-c3U8=B`;BoxLABp`J%9W48M2zcK8n<)NQBtfel%@x+C|RE6oj_k+bZKUh2l75 zd3w{=>BrpP)1G=@x`tpq)7cGanA3gqe1cUgOfp*ma z@4mbG7n>6TV{soo2pKoRiJe4LR?*SZxTksBC!|jwml;5giB(IZTNx8gQoFteUrMI` z@3ijHAtJW=uU5*-ce)h9b~4YRf?>hEmA<(JvyXoX3`(Y>T?V1@_#x%aWynQPnXb!_ zZLoc4=hx)&{^hUAwnTQB0eWh?z}Zr@0RK#-inx7UJpj)JGWURcL{(ABOyi5ywn~+z z$3+%mJSB`%85x8EygXLn`9}5SQsaN;{e=%hIuqm;9z1PVci*eamk+s}XlwDz87RvT zWB(LeBzRcl7ogjjNm!dJ7Jd2<;Qj570RjLlW`=v@)1j_$ie!e)caj;~!kW-uGx zBxcnbR0b|IY#HW%Xizb?2zTg(nr%pUUp0w4@8=PPgPQZ7Kt^jN=;GckBFLNgn9alA zPLz^E+FQY6q{)3;Yw{s!qhZGN)9@WEWvD~Y9p}+F*-mZ?f1>90wQvT~m2W>q5iB#I zWdg#TQeov`a`g!-wcpI=*4jGBO?<%R5!!|K&fI^i?UqgpoyRv+Vc(6?n z{evWoR(cx8o85(-RTl6pwda!6gQ`Cb*9*%G0&%c&U1<$~n=e0gkrKijHDX)mMA#`} zQm5OcH}6ah3|ns3Y8g+sjd0kh_zYT!`ducRZHF3?m3cZe&uN!^gI0~kr*AA~qGudA zv$tfnM%X~iK&Gy>o`L@KC(-3)JAj6xByDCGb>PNc0~*NgFa&U>;NJN)((r&dur_rq zI>aIePO5sq zx(aI=)1`^Skb_*u-9&|!;dt5i3H1M2x*6k{|E|9skAMJ=q72{pGTtkt{g^e2Qb%B( zU%M84Zc1=WEl)Gqt=lKtf<>1pc8&`Sz6r%eL*pD}*NvY0Z?C-kU*@!A?)Jh4vr6XW zy;=PEt>=VC{}VhIxCXT9QB>D~H)=jk)(0r8y!zWpxrbxi0Knt4v9kd@7p?T(0S{7^ z^hRFAT|0tF-kjw2YM^2#l0>Xe2`z17^N*8igmGf%os>LI@jln&J}2`j^vat*VO+wb zdL?x-xOh7g5nu`U4s8Ga6fb0z%O;au2UiKrn_ufl?V4OvCN>YGOwyseYy!y71dyI`a`rxewo4!);QES=(uv zX+(BWaDty)n}GheBD(dSDYIk(4f`(pMS|EJrm7yPOj1kksRXtZHN>K0j@!Y81H=IW zOE;+Shd=5YxPQAuQVu?Q6OaF2#ikwlt8;hgaGC=*XG{jM?MRDAgMl8M0XhcmHAO3} z$8p9?2!@(H+ndMph z88=`MsmJ&0K9X8|U$S|4ul4>!ukYM1!^y=58HYm$*!GKiN{fL*$7LO{w6A_Y&~KoP zgedoqgkS*jjzpPu0?>ZZ)max_v%ocC)AG9`N6g+D;mz*pT*`1l!S}0}7b7M4al`~*C0NCtcPo#6 zrEf^Ws_t%n9BJ@--%{&~T*Gz1JHVtiW?lmnHJ`iw2q3dltM->i7(;gZEiOrXD(KW3 z{bAjn00~7E!BgqSk>wouj9inSxD@7Di5j!Y6ul?G8J$zJ;t6j?z5^_0(CxqL%593H zl_2|oit)23>=ZIzsZ)%$ATF_kojY89L(J8_lA~+!u%2%*vNZR>uuR9Pw*y63pSER3 zGmdwQqF&tX;-I1xIkoUN4kg`jXdmf*_SM1*v*2|8Gj(3J+3l7BEv@V_xLBXC2T{u} z;bijyI~!bY20RG5Hm#_AM*?cyt^CB_tT8+mTzPu+tz?ig8U?#;OAc7AoKA9YJQ@`e#89&`cfpG~Uy)OfrWVreu9>62G!{H-9tlcaG2Mo2zo;0z zz3@5ESTln%QLTYy_fcr$gZtDG+8#f)C2(&HyIIaX7%BKmtuMe$egxxv~BYV)@;DKc~u_t3PhK zSyJ-|yZ!TfGqlGAv}`NOwO#gB^^5kWlk0XDnr6O>k3NP5Z;V6bKf{N(!u;W<30Mq# zx~GAs=;-2=Hrxgi=jZ7s2;~Q@dF|EHGHINK>A$lOBE04s;C(1JY53nYXehpdd$GjJ=&2Ru_>BE&oKP0ufG% z;?!(*!~n^R5s8(sfD4C4?92A(?hA_W4^WBx1oNK}{C71PX-N&hrZ=XNaO>Z4JM9o$ zU}~So8CM$KvFD07!aLUI;)>rz-^m_N|E_yM$bRg_#}Gma?txH0X&HeH-1lms?z)|K zN{ShcnEJU(!eWL;yBtWcw(&SWQj?!;gtZu+k)RE@ZF}wE{`G9ddo0osu061-Jppp1`P+q zuggnMFBxok-BpgDhV*WL+c4c ztQh$j9CLJ?YcMHVjDmcGhM3G5w3`CK;IYe}*|_V!gl|Q%;=ITR9vpNA3BVn790|}J z`*+fKWi{BevYp-szITtfJlb0gl#@TRToK}lGr~V)Yp-bE2g>gd z-#X0QtcRwIl+fdVEVwp??Ha>vM3n{R@<=#KjGhLL&^DE!M2iQ(tEMm0W8|OE63|gq zi8@PMEi-M^WMARd%;na&^GLO{nCeT%#DXK(!HqZHd!)@_W9gGHUNQKn@)F`R#>;gB zN)dL7!{a8DOX~F$?A_1OElu*K5+8bg&92)22%_}cN?2Is;1^g2tYA+K4NJq(%cak0 z!b})jh)C$)l(+BMJ=yQT`DyZ())BX8FR9I%#ZQbuub@+Ap_g0KlS-7v`a%C}IO4C= zGxA)96PiRxSEHQxi=QBFNnbOTdlP2<(@|^DepIE|>Z?ZvbrD(PC2c~#r!PIXD(I8E z+2ll@`xp2+_J{;GQ_5O6@b!_+)U~10&^4K!@g4DtTYJJa7gS39p;zci0p++~SWjI{ zVT(oWYl#W^)M9AoQb&jt6UypAw9a|%HuWQL&N-wLl#|OV$R&X*@t54G3`+~Us1wkY zu2F2iL_E}UbI#QElTR~-Q2JuzePmR~5Zl=XL{l5iWlBVTaf-XPM zShYH2gwdI}?VSm^+Gevt$9tUe38tV0F%IXgM~@1;o5p7_1ut-7Ow%7G!~o=^qZ$*B=+*E2q{0-jleoBL|JCTLrLGcSi>+a>4zDDMs$#N zvS2>FTE8J{Va|>olO~$DGD(_x7t(9G()`ZyW?BA>`kjnJ&aobhBvUA=Vil_D29~=# zywWcV6K*|Xt2R?;vbkby;X`CC1DM+h$@I{InbGQed-trM)y1Adt*3su%x#p@dStk0 zVNJdnXHCsG*Cb%!!MK8int`=6MP$>-kGcqqmFnEU{hrIA#{pg8UGKROfL@5~4)ckY z`f1|z`7xTt;}B30IJgi!M#|QE-d48`A?6qo=UwN6WVwIyJ%-!B7W=Mo%dDTjW362< zQzXxOn8rKcMP%zsQ-_% z9vaoC(WuCV0-f$iEL$qXC&B0o+)F&di}u4O-Q97Dz7$vqI)K z{QJWjLtVBpr;4jm4$`y#j6ZX)+ilMPdny`MIjgiSJb}|fn;J>Aa_*I4+?1F}rN|mf zI5V*f7r*{+jupJo9<$&(<;?V@D9?T`?-D4FiX7IRC|)W0O|kPk?xq0PJ1p?L8A6ai zDRUx6%Y2YQ805uPH?*d^lxpfeKo`uy`LO04aIk#U`$t>hzi18UV|)1C?_m z=EKx4lh8j&u94QG9un^8PAExFERRwPxvc{|-O=1PFi-KKomzx~_MHg?QZWhCaVF>>Ex^fdR^otd^;pI+C&cBGoWk@Y1$%-K zRL~rRCv~(7%z>%qs^|cBUd<2XR{0kZW0gHua&!Aq(EL4Q3VWWglw1mbU3+l^&8S3a zgU&EYNX^JMG_}EtS>7D?d{e#e=Z>Kx`Sp=d(fTEpgY86%*(&y!jV8)3p*y4=Kg6@od28jiP-$`;S(`9V#DH^^229UH$eCoz{pS$tP0gbyGPMR)goYc zgmNKZMUp#zEJl)Mz* zT3IL|{EqPp31b+(4V8%YyZRVbpPC}MG^!WE8R!O;*}Lj zZFHnc7op{S#)f!+X44oXq9z~vxC>EImez^-A$j625V0vU0XI20Q*xR zkE?e=Q@a?+mQQUYYMX*|NuI5mIt^M9X4x~;+9yq8>Xn1fE?q3fSG4cHC5R*JA?m5t zjq(m16TfGMITsy6dWAv3hH8$yh{~al4H`}pbZ@;ICwk_RAdrnZL@U)5a)eWB1r?Gsg=5J z)dyxO@>Di0%4ozmva7a*?mo+IJe?)Hi@$@~oTA zTv|h5jtnZ+eO=%b7l;;f&>D2anr-e&u0$7tyEUG4(KySOfz5XRspa3gWG40f>oYsu zYyh2t_Yka^MMp^u*OR!a(9{i$KimiE{20DgSIR66?R|x66k=MkfRZ9G%0Dz1%R?t@ z-C=|6>78+cQRctZOHZ625e))6f8_YdOWwI4Ycc8P;h$f->MFdq3R@>s5D>Ks;e&EW z+ff?#32L>1Xgi)h$(Ff~ZZuA2J5qxwd&yF&c6VT*7o|oFj+7opvt}&IXt{P<(CN1W zMN7&_jfs1yHtccZXj@r=t54RUNYtN)pa&2boD-(%gtlml|6fJaHJR(rcP1nhD2F8! z@k=?!3P9h%^nY=hhyi0~$P(U0dVaWuQ746G0S$b@Ln4askNX?JYm}AGsHz~NAHapl z4@8B+f~8?KFcdN3Vk0)ddf+54Y)NE+7bcQshkSF6#wPtWCX4UR0|#9SHPPcw4QL3Y zA1wjbgP2C;5yaUTRs&S>M1uw>9FpdJ>h<~TukQX!n`d*APzF|W^#uwX(0jA=V3o893)-*w59go$ZRxRc3t9oSLEFE2HIiEjj4IQr63c4ef_1n+tWcD!HKpB&_V5zbnYY&1`Bvcq zrs5>C_HK2JKh>VUwOzN(IVNRA-LC$%iRVJ>Wwg`V0zWMJhdldsKeLDC!;#GKv@ z_veQQ{BWpH#Fl8$6YSEUHA}pdd!L|76K~${51lvAFw|nvn#;FQxCsj6LRX0MYnJ^N z^WOrMKF@a3?EX=L#Um}eg2o_9Fheo5c?s;|+{7RK;{2E_@yWl*%o(1@7dJDEeZy#3 zOIiY!%ZMHZ2toI`^YncUZed7S9dV`Y1!IfYE%0&omiz|id_^||K$uq{%k|MYj2>9m za!6uIOSlv!;%Ufde%c}I>Ran>jk4BnIu`uxSd~wyA5}Rf1d;L|buFPTvENxi!i3+F zx7hPz#7LYR4BI;=7u3$^&7_HIKtSqSmNni$mr@HCBVg$8WN*6K{G36~T8p9rokMxL zrj-~%EWo+A(0donLw0gHh#FM5^5!H!r{Xlu!{o=b zd?bus`qr3;*1d%ndkG7E);;Ib#;Og6t2}w0`DR{!^j& z@o`kmhO7QJhd7CPQzzGm#xkAZXhAjzQhX1#LA>;Ju5k-$)p>=)(LSCGef?^wR#|{^ z>;I>7@aVdj9-~S_jbHTFyQJIeFS^mw=ClZ~^VEt8r zsHo@u?JW}=o9X~@zNr4VK;7H!2b+m-%3B;-cB&e~tc#J|KbAS(Th6x89%}vhTUJiO zd{6CLu%vR%QDho{Ox8SJ(wF$;bLUg+8ISJ?aKaj?@p)6)KX7 z^BF$Volv#lbc;mSFMJ~aEdiU&wB;J~dh|7cD{-NyrIDIkE{61UZ(82>f5T4%7uMdZ zVR6|2h;RMy2||h$)7Weql--?CI||`q+HNVY!8R>~*{eBC^xb&42%Gh;shsV)3hKkB zkYDxcZ!1{Yk_@r-2e75E?g%$cp*E})c+%dKlT26VL>f4a(hGTkqDtffbM+nF+O2PdUkT5U;bb zmZ;-1D#(55m}7U0jcsXh?DfP^?S+|LF|Al{T7yvb0LpD$P3|Xt}tV;4gFjUdTmos6o&}Bgx$5T!J6eN&l%shfkHv>zf+$Lpr2G~ z1o8hsj-f+{i5olD;o*A@SFw^w`4YwY z_rSOZ$nJjNKa9@;ws@<~14b6Kf}S^^_f#X+@DDFHVV8Cl2~Sd@;I|h|)tHbX6Zalc zIF`6aInWAZM=Q`Atiw$Nw%Jdin^Y~>-&fH$DO~wh{U7Y@=u8i7|3@LRm2(970hS0J zuc7z#JZWEp+pvS73h36W#%PpzByo!CEN@{r>0?q6&Gv*kEdz|n5Z?C0Brli}htr{y zrBuXd*$8Xn7I+~Np@{R6ejws4UGw*r{)dU^`ffd8oS69lGi@25_?Bve^Bh&5`ZK&2 z{Ydy2L915dsa+eb)*#oX^Ecz%4m(PK-az@i?* zshHkrs{eLP>To4L!rlrr>#S)+79t;%@Vb!cS<5spK^8whuL&8C0j@b@AeD06L(~yo zj_e&f)zlvRfuUh#%;7~8IRk}Q!JJ5r2VjHs6+=VpfV@N1AeVjU6wpEdY=KCKoRcmAG%t+EeI>0}X!m*nOgjE_MAnC5^8g)}_7X zlymd!XVcyrgg(1+P1qv)sn)seWw{MW%2)u(2fEl~j}hf&%S+%QR}iC(2_-KdZN#00t!p( z!On@xFzvBLqHr8b*TyTj?mA3=D=+(Z4IyYlzT{`dlNRnskn~kfMIugS5R(%P5}GcY=*P0qj>YHYu_s2o-wKF=EKtUZROQnf1iqQCadt7_ZgLHFjsar|R{ zFR1*1kpZiR>;tk6v4D4h6PnO|6}ebuJZ%tnW+JLh1&Ucm96u%MGebap6nOxzfUVWku}QCcgil|m{Pdv?58)-m*)#ds^n2rK zXScU(-g6w<_J2+0`sOWd2KSLsv1@geG^x=i^>P=JFyT&wL^i2ui=K$u2wz@PZFlOE zPVD@Ksk7&JGIA5rCj4PTt>iw%5&Y$;-}%q8S(*&)Lk)uAT;`k49y8(F%crbzvdzO5 zSf+7IPPmRE++-%r4dz?3l?P0e0Bj!!;>>ey?d{Wo{hh;DCb$mA{ogvsFpjzI>5LvL z(5`hMfQlw{8tFMrm4f<@-^T0Y<81%ZKCam@1p04tk4YL;#h({es_l3{!&NTpph9WI zsmz+@-;5+!ZF4@gOqbivr9rJQz>^{3qa&vNl zXiqldsYYaNbLm#aCJN+p-9oRA-cUNs*N@?v>0ezUMqAOKT#fEw}o z0e3pg?@=Oq$lCBT;!R&BW_1&1fm;o#nvBgoMh@HFDM^2hPDXeQERO~{bsXNdt$13d zpQBDTvg*ux6uo9RvaE!FXggw`d-F_$Y?(fSK-UG{aXfaw+R_ppTx_uPU?5hz;_5Dr>=e+ z$=!J;RAd=vSEYHdq+7JPI!(>ki`TTnYa1+k)@)hmhgtZ{;ONXUm@4sn$~Z{t44pe- z*4z>ACrWbug$4N|R2i{?b0H{KD(&rNrh;GyM1|y0&|+|u@YlNZtoW=^%K;@rBw3_| z(p7o`x|tYtDC@Kbq~tt+JR~~v|GEwCeT@+|P{%pPUQmqg87L#V6g@^0=jNlOlCC5!$G9Qo-Ai%LHL~DGX?X28l!2fBw(7GCH zu>MY8HjfvR)-Pn8}~7_=V~xVO~al&!GTt z6teLhpA>4TI0f4mm%;RZU%2cytbpXtCp?=F0htf{pays`At|_JM9n^5xsBd!A8^j7 zYK=$hcep#Yc>V&>*D~NDpqplmdM@W6U4(qkQg&eRAq`@KL)Quo=2sT7vcxR#`Z5fj z4V{6{fls(~&HG8!ecbUkV{X^{i4DKhf9i4E|ZD1-_GxN!JQ{-%UYAt=k)i#JiZ(N_N$o{-Y)P? zXXiQ7N&+S1WDi`+Qu%3*(qqS~%Pbu@%wwGWoK-b*yU(#T2wO0EfO;`RtB_nw65)}% z7}z;z^XT%(2jex3PsjU!tQ3Oe z8Q#~v+|uz*z_ki91#dqVvC$HBcC$LWK+nQKVOo}^0fHKEh@dUE6Vx)jFG2@Tf9k!k zSKb=Ne^2N-&3BBug_ULR9=}uQHRwpaQpEDml}>i-Ffq-1uj3;am-IxSm*9ua-Z-Gk zCNfwmv+hhZF)e0>*3a#)NSid`rST_BH(MNm z1kc3uWi<*-h%uJviRZ-5~SIWSoOq<+t8B0 zDh?1lgEZeY9zdP#k#3k;4WGM9n8+GHPsF_6s~6qEte+mx>h}ZWgDP@a9f?BOe=PnQ z1pXJ#Dq{lU^-m9@VAIqF!G9W}xEfS|c7${@TZWh(U{%7J84`z?(R9nx*U~@U2#2y# z9HFOAky?8~>WTSk3&~_mAAS8F05xfeXa672Xr$>+qE&T%1FmEb6#4_LZDzgbv-LF} z-@|e=b&WcmqeQDHiERIbJNDhYPgpOhK1UuN{DO5if<% zD<0$TzjISn2oyFhfbMqnWr2=SI%_m4@;16N%jvJoph67|V+ohDzr#-eB9!4(ZubRF zSFX`XV~6IwOpZT+Q{1t;LZnp<8IbdN^s{`gF*@?%_|kHKJ9as?02kW@`2{XDQ=opa zWZBUJs&+G;v)6Kk;M$J<#Z2&?4vQtpMlQ*oI}$?e{-s9bCyM!n-7v~iTs@M;gA>D# z@1fA5|sjd<^t zNlgE>OXWEi3dI^fMQ$O)Y30_}ZaN|#YmX%-w-VMby~zbk0rmq5Ug7fH@wB#I5&E*&Uoab zNWKMmrtcK|sQVa2SxO}mhi=8)gy;6!Xm^mW_+675sd{*)REr#V&0fSS54}htUlA9s ztlCo?CoNtYF0I~uO$P^lTEjxDKDJ3uV`VC$vuyoX5pST~__NT&xzW6y^PS~dA2_fu*5?`u;3bmOk{ zS$e;1IsLS-UHt8^82J&rg$U{IJ87BxV}Y)r+;t|u{63t1NAq^Q;y43A!+_eQ>kbs0 zp&BseD*ujirKYzjQiJ~5QZg#65z%2(otE9;Egyqu)6NlEgZV1Ld1T8K5>C9wY6E;Y z{Dx%ek8Ati1iJp`hPay%jwv~y)(2gKiUKQo2iQ7{X2BOKp_sl3jWA>1SM>?ypsg*P z+-lnXRlz0ZP%VPrx6(_obDIDZ?YZhHM%w_(C=Y+`wp@#qPtz2O41=754^ z08`~C(!2qy9G8<+pEOxo%iV9c+k8#;+uw+C8CYo@mBpwxC{_Kel5M2F>QwX&E?y;p zAbUJdc~`>+h>A1$JeA03!R1b*cJ;cu9T<10gEFAQlS{3Wny0aZz$RL<2i^Zp8A;qx zpjVU{Letx{TS66kOUKv#2%KkcvCWm~YgMV9PNut~CF{G&6 zqS;Eynx3j7SQ4x`j&D-7drcd+FIz;r3~Nx`%d!yff^hbPwlg9p9ZA;hUEotJtx$4% zEVlSL3_>WtV2knIqHL4R1mhnKlVCaoKA~n(c{5Bt5SMYS=zs2~KX?u(B;3R^&rTR} zS_4Iq$gi=A<}(4b%T#yle{L-4KmDEsbxdjgqy`)g{Szz~-acdrrj73=17;&QV|_S}%ku zx|mh}5zK6sTUT}(w8CFz2CK&5bx0OFB}Xh?MQdg>P=|<^5bP+r9GB7Txspjio7t+bR z>_vUDVpFR#npgQ~G1b-c>ji0g)a)(1gDJBs#cS*Y6sGk~fjgmhqMuQ3Y0u&po{C&I z!DGx&9m8kUYN0*-ZCcay?D!U)h9*WwfAB9w>YF$!MKi8Ki)N{MsjlH*Dp!4m2^=9| z#SvzpreyGO$$FA~QLh5J)x2_AY(TRpqU7d!tKsLdEQTsRdtUyP%wMP7h{$@J%qk{xgkn~Fl z0moVW2^cCr^EP$V4)^V6m%p3LOk?;AoPB9b>^+b7wLjssTXXH~K!8MCe+it?GsheT z-E${C@jbz<=;eSU>qIWKk>8zbBTbQ+O|&#PB`}Eaa!&tp_y^Jy^%&Gf!|ebx6oVeE z7Bs^j&W60JC?^YL%L8VhSx%o$w%h47A%Pqu!*yf1lb+&NT9GA!pGY z--Lh=L%)jCRi+uQz=zo0B1eoo{Una6>%g@Jy8TP`KZI*qQV|vm516!nbniCBGv#9T zg};z4ljW2>hW>wRnMsBg1|5E8KCJ=ESpabmg6YJ<$vAvJ#xjfXGlweRyvA?!)xb*F zLqp`;pvTnSA&{q zD^rV?ziiZsA>!(w-@B2}H#4F~X-qQG#%Dq0aPe~fT}nD>5G83YTJ1jsOo96h*ILJ3Zd-Bmjm>nr9+H2oCHDX z8ka?h5lbKoq0!K&Ishx1G2(8HG7KLkN&anJly6kiGV*wbEU$++go{Kb6=vefZZH zcpi3vnv$CP|3-2Mdl5X<3?1niy5&JL3%$Lo)kOc*LS1;BR%->Wg{ z<-S}nSWsje)43du$G!>af?d$2m#4MKNl>5k*p&?WrJDAfl)8ar;CWmtS&z7H>lhW& zN6br=^3$F}Vy3CCMwOo02mO~8#j}DBh!r8gsvYFNVw{zbMGn?eId$m=@2P6W1g27r zF5DVb%YI-S|2DA!%0E!^Cmo=|oL5@o&nlDHPx-7xG<+S(OWLVX0eK z+J?YzVChdyj<&hL{~XAq(~XV40`o+RYW?{wkKgXgYbtV`8(&-$9&+HxKYWDmVe*no z+c9-16VqamrgoJpJj-tP_ypg6G3-5Asu`jpjqmk=7cK=3ZE1YcE+gRcQH@HztU<#d z*Dv;8aCSzCGKmB}dqDnFtUhK3jQ{|-Rg^EzyT>iPC*|fg*|3>81mB29rtaLA)j-~A zkwnl3yvi17imfd;iB`)muje@T7r|Y{TWhF*gJLoyJx6nv_H5QU+a}yaq1~lLpIz#WexdMi$3F z&`Grno5$E|{Pb`1!>k6ZuXG#IJ1%zm4A^CP_J%&|Ap^SnoKRde5)`KNFW4D ziCGYyk*vN3r;9@&qR?$qd=v*8Se;lVa7B6EqCYQ+9?Jv96AobC#CWdpYA^L3kXGn zHrxgy1Er;5_g}+Hk-Nt|$VPdcV+WDvq$b%9Sk;|>bu&c_k!81ykTVLNrpWfCe}8qoXggNb&qL4_0n%L2%U0e?WFD-HX!vQLxIny$hSTv-RItFY854GwQN$$gO3(oJ@Q*qa| z)*~&hpAeZ5lR1N)p6~_vA6RM?b}MT%xPQ?aHe=PF6M$N0wx@Vz<`2;Are{B}UP%vT z)|j1Os!b#8PuTm%tLtbXefp$F-ntWb>rl@+2a?@hVdC=94bFkHEZ`URaJcEdMPcAr zDNIZB13;XOSCsqozsb;;YS}hRG>`e0k%>cNTI^&tvF8?riiYBxxH6pYkn}rd<;*cW z0CE$ws16$9@={T$g z)7RW_1!1t|9??7ldL6CCVbzx6-+>%VYL)5?O{NRaaQ*0q9YtQYwCuOG)ovVy$4&Avf7-G@2^AC0DbpcZ~H%un9_!JCLEt% zs$McwPe39dh!+wI>{H$qjVpLLr~r-c1i7f98PxlF+MUZoAsXb1mo~8sNo(FR2ZrBwFPE)pgxbO)OnJbTBUjM6iS+p-NEc zJemR$2sP5A_ZkRAiU<->K@0*CdMFBnA|*-_1f(iWLX{#YLGU3`Bq-7a{Fd|m;U~Ut z_slu7d(Q9f%)L8zX71d%bER&Gnj)J4*hH2Q``Q=Dg#u%}N9Uq6KJ_$;)xFFRQJ`<4 zsrdW+eJY~!t7M7yre9s(OKbeYzk%VDe_CB+wU*cQzxo!9WQ>VeHAiN_>0765?|%j>(_ww>%9sdm#2+$EZFu<=_h2R`usCP7sNR}UDN#p zL}w_FV6V3H5o|+})HFO`?T*zeMfbIf9>xtn8gw*&B-m6Fo4mVTSBI2aV#>OBlW_dC zf>$hZ6)-ZL@Zz)-hQi{J@X*$kc6ca}wXm^3D0(g|+fd2zPQaL(Kw9N%oV^QdU+5aK zeWGv84Ki8et9)}j61|Ln0vzYj;g@Akx9*&Y5lE5XRxuO!3WV8VjF3Z^ebq@ujyk2t z*g2s#w&{`HSJsQb&Zk=GEcvXmN3Y+?oQRxIN`LBCAXHkzJgF3U=IZIWC(FPVkc1PH zHOX?BzHBArcz2gw$`HD8HF}~tRNE&dq5MRk!AxDHqq0-c>s4F7t!3LPAGCrG99INE!-TaqyZ2mnp^&>YS`RWG z;xNuDd4>Phpy`+8uug3}c&epsg>tnxA%9Q9);9gI3dRYTR#Uu3`PhRMUh7DS`W828 ze5~143tl(zFE&#`{6rcbu`X{O zDOI0{T>k(B640+n>vjJ72m4@t)PkR)PF)#~3Ki4lba@-D(gR9E;DU%iJHL>I~^s;Dmx_i%!uS1WS^z(Bf!FYte@{&~Jn~+dVyQp-|dyPjG<683poyXTm9`3Sj*4yH|r&UpL6nnH6 z@hp6x!pz2J%AIGu~jn+cC+z*-v6_t0QdRKbivzpV91ds43Y#sg|0c zGOG}pahg;&3ux{!l8UbtZ**K^jsKpfF_?c+V`GLh>z>E5vT6P6s#^*fVU)(t&t`_F zbXug(w(9c?Xyt%t1iw2W^@y9TWY?s5nW5NLyOtN9Uokw~^R1yh$vPOmEHI&U{pV;8 zGLoiym)9($Og1jzqT64P(hEh0;L}&&X9LYqCZV?(eWA)bORbmn997;KH743DurAn% z+I9J=)@!yMw?+%FEpPUGRKi(wD;n@uBuRFfdIoZM=Ve_!O1k0VAtj;_oR4AlAlrz= zHMo2giL<+G-;(8z(6@CU$r1wmi^@YeiWOGJE1lSylT*DpQYg-K@ok!-Nb>Gq!;*<4J)nR zla#haL3Cf3#HAryS+5=C6@-+H8L%ARo=zReK#w;+fCf$NvfAauwZ3zUkA8N zmYEW}?!@(M65Gvq_8!`BzFPiTyQC12X*GL)#~$X#HTc5=SZo^^v23sg;saa`B>yBa z7SNqVJ^Vf!!_suFxLIRjJD7Ihz%_H44az)!w3xb-1fD_#96#4|i&w`|bZQm$j8}yA zHo5p2lKd@k(6^j1T*7C64RE0nKc1c&PK}$rukjQ-yEO0r_?&O;RPM3s{EPDvR2A^! z;nCYGsWn9mQN_BYGy?}Ohz7R;BcHKUon4@+W`$hoTgGwz`s>o+%nL$!3E7-!fd{>D z#=44?Gaedbd&BdLRibSF+JoDs9OfPG%Ge%qnHu3olHkWxo!Ihw&Nv3*%0Km-d(O}w z_ch3Palh>1kAyRO+CTmF`+l-q)@NWm1_FUtLG))`bo(jyRyyfGpmZkCznp<>W{OKO zOvsYrh+aW$Ljz~-ycs3>eRl7VvrZMplS}&&MQJ=|Zky-cTP`n$U}3WF;p|)AR9=0( zHSWlovmdB#-&6vV8#QlR#5lA%r;&8aq`Ow$bCc^H(kOJl9+E=5@c ztCSQuXijXHm-i#aYr^uH9Y!$LFU>@j%DfsHe3Xk}xJNgOX}A?qX&|++^?b0mif4In zd8`xL=<}ydyI;5)Ywf7e8hwYi8;eM^oxBHYTjbq;ZeE!EH~~$#IbR=EPrXR%?TF5= zzjWs)4mVS$snjQR(kO3>=xsMo7<<^3wJ~#P$Y)x!CPdG?@AbX8I@>3jDie;Cl@o<6;i?}*rJat zO65L`&OU+~qAW`%TnU&VvI;QvnBzpS|k19#Khw>2$<>9pa z`fXkIe%wU4m`FVHnxboSC8jGpyGyvr{b4eJfF}?MQG^r@BmASR)7qSTSxi6GC390{ zJ1;n*PCgbd6webc5+{il%tx{i`&#^KJ@>V3_7FRe-(~&*9Mawc$UO63Wv&Ux9PO7c z#nA!CoE?yP)hO|2e;>@#>?|8+%$2@G!%0Bq5`&jEhsz<)V6s#s@0Xkps!%8eu{QHQ zg1@^bB=l}WBg!&-VDm>fc-CAVKwH=h!b@hmgxrv=C^@<@?)4XhR3;sT#rdkqhzV}zlry5LNz1c&F zXr+{vcXPXAy4QW)l`eUIepTOP^I;xCjv+=87j$Vn$mHPVTg09iw|M`OHNtmeL8f7&E== z;aG1UjN-p~ijcSJ>@0u!l6-+%aVJPAhH^QcEg&;p@JBacxYdAq0xlMFvVNl*FMoQ1 zWq7vB-#;^b`-ZdgNPY_(*V)O9`x|QPxTY*}L6A;RP>@qlS}>bqR8b=i{!Zch{%xWT zLM#r~MvyTwHZayQMj5LoqKA$PNG(LTKQaEG?H$a$M|WADjvfU1)!Hv?iCbs^ZT}!~ z1L^lm$1T9wEx^Gw0E5Lk7+p3)ozpTgI7}jI(Gmg&ex3=2{}TR1A_U0(M&kcah-a7P zf&vuEfyD6(4&*4noByS7PDdYo`1>KZ>f>dAN^yY?qgjCx{f;*Kqx3#rs;X}Q^cv(a z+7oCDzoRw(Ko@O=KmW!I0x`h;K(ii3oBdI8vouKJSQrQdGon9K?|ab0Xw5&+hZXfN p#>L)0j5aep%(cT!{ueqT?l4*tKr;cg1cBIqTM^(^$b*BY{{TCvCh`CP literal 0 HcmV?d00001 diff --git a/keyboard/nano/pcb/readme.md b/keyboard/nano/pcb/readme.md new file mode 100644 index 00000000..e85191b6 --- /dev/null +++ b/keyboard/nano/pcb/readme.md @@ -0,0 +1,16 @@ +EasyEDA ordering info: + + 34.3mm Max* 17.8mm Max; + Layers: 2; + PCB Thickness: 1.6mm; + PCB Qty.: 30; + PCB Color: Green; + Surface Finish: HASL; + Copper Weight: 1; + Panelized PCBs: 1 + + + +Gerber files released under https://creativecommons.org/licenses/by-sa/4.0/ + +![Creative Commons Attribution-ShareAlike 4.0 International License](https://i.creativecommons.org/l/by-sa/4.0/88x31.png) diff --git a/keyboard/nano/rgblight.c b/keyboard/nano/rgblight.c new file mode 100644 index 00000000..552456aa --- /dev/null +++ b/keyboard/nano/rgblight.c @@ -0,0 +1,505 @@ +#include +#include +#include +#include "progmem.h" +#include "timer.h" +#include "rgblight.h" +#include "debug.h" + +const uint8_t DIM_CURVE[] PROGMEM = { + 0, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 11, 11, 11, + 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, + 15, 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, + 20, 20, 21, 21, 22, 22, 22, 23, 23, 24, 24, 25, 25, 25, 26, 26, + 27, 27, 28, 28, 29, 29, 30, 30, 31, 32, 32, 33, 33, 34, 35, 35, + 36, 36, 37, 38, 38, 39, 40, 40, 41, 42, 43, 43, 44, 45, 46, 47, + 48, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 64, 65, 66, 68, 69, 70, 71, 73, 74, 75, 76, 78, 79, 81, 82, + 83, 85, 86, 88, 90, 91, 93, 94, 96, 98, 99, 101, 103, 105, 107, 109, + 110, 112, 114, 116, 118, 121, 123, 125, 127, 129, 132, 134, 136, 139, 141, 144, + 146, 149, 151, 154, 157, 159, 162, 165, 168, 171, 174, 177, 180, 183, 186, 190, + 193, 196, 200, 203, 207, 211, 214, 218, 222, 226, 230, 234, 238, 242, 248, 255, +}; +const uint8_t RGBLED_BREATHING_TABLE[] PROGMEM = {0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,17,18,20,21,23,25,27,29,31,33,35,37,40,42,44,47,49,52,54,57,59,62,65,67,70,73,76,79,82,85,88,90,93,97,100,103,106,109,112,115,118,121,124,127,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173,176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215,218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244,245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255,255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246,245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220,218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179,176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131,128,124,121,118,115,112,109,106,103,100,97,93,90,88,85,82,79,76,73,70,67,65,62,59,57,54,52,49,47,44,42,40,37,35,33,31,29,27,25,23,21,20,18,17,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0}; +const uint8_t RGBLED_BREATHING_INTERVALS[] PROGMEM = {30, 20, 10, 5}; +const uint8_t RGBLED_RAINBOW_MOOD_INTERVALS[] PROGMEM = {120, 60, 30}; +const uint8_t RGBLED_RAINBOW_SWIRL_INTERVALS[] PROGMEM = {100, 50, 20}; +const uint8_t RGBLED_SNAKE_INTERVALS[] PROGMEM = {100, 50, 20}; +const uint8_t RGBLED_KNIGHT_INTERVALS[] PROGMEM = {100, 50, 20}; + +rgblight_config_t rgblight_config; +rgblight_config_t inmem_config; +struct cRGB led[RGBLED_NUM]; +uint8_t rgblight_inited = 0; + + +void sethsv(uint16_t hue, uint8_t sat, uint8_t val, struct cRGB *led1) { + /* convert hue, saturation and brightness ( HSB/HSV ) to RGB + The DIM_CURVE is used only on brightness/value and on saturation (inverted). + This looks the most natural. + */ + uint8_t r, g, b; + + val = pgm_read_byte(&DIM_CURVE[val]); + sat = 255 - pgm_read_byte(&DIM_CURVE[255 - sat]); + + uint8_t base; + + if (sat == 0) { // Acromatic color (gray). Hue doesn't mind. + r = val; + g = val; + b = val; + } else { + base = ((255 - sat) * val) >> 8; + + switch (hue / 60) { + case 0: + r = val; + g = (((val - base)*hue) / 60) + base; + b = base; + break; + + case 1: + r = (((val - base)*(60 - (hue % 60))) / 60) + base; + g = val; + b = base; + break; + + case 2: + r = base; + g = val; + b = (((val - base)*(hue % 60)) / 60) + base; + break; + + case 3: + r = base; + g = (((val - base)*(60 - (hue % 60))) / 60) + base; + b = val; + break; + + case 4: + r = (((val - base)*(hue % 60)) / 60) + base; + g = base; + b = val; + break; + + case 5: + r = val; + g = base; + b = (((val - base)*(60 - (hue % 60))) / 60) + base; + break; + } + } + setrgb(r,g,b, led1); +} + +void setrgb(uint8_t r, uint8_t g, uint8_t b, struct cRGB *led1) { + (*led1).r = r; + (*led1).g = g; + (*led1).b = b; +} + + +uint32_t eeconfig_read_rgblight(void) { + return eeprom_read_dword(EECONFIG_RGBLIGHT); +} +void eeconfig_write_rgblight(uint32_t val) { + eeprom_write_dword(EECONFIG_RGBLIGHT, val); +} +void eeconfig_write_rgblight_default(void) { + dprintf("eeconfig_write_rgblight_default\n"); + rgblight_config.enable = 1; + rgblight_config.mode = 1; + rgblight_config.hue = 200; + rgblight_config.sat = 204; + rgblight_config.val = 204; + eeconfig_write_rgblight(rgblight_config.raw); +} +void eeconfig_debug_rgblight(void) { + dprintf("rgblight_config eprom\n"); + dprintf("rgblight_config.enable = %d\n", rgblight_config.enable); + dprintf("rghlight_config.mode = %d\n", rgblight_config.mode); + dprintf("rgblight_config.hue = %d\n", rgblight_config.hue); + dprintf("rgblight_config.sat = %d\n", rgblight_config.sat); + dprintf("rgblight_config.val = %d\n", rgblight_config.val); +} + +void rgblight_init(void) { + debug_enable = 1; // Debug ON! + dprintf("rgblight_init called.\n"); + rgblight_inited = 1; + dprintf("rgblight_init start!\n"); + if (!eeconfig_is_enabled()) { + dprintf("rgblight_init eeconfig is not enabled.\n"); + eeconfig_init(); + eeconfig_write_rgblight_default(); + } + rgblight_config.raw = eeconfig_read_rgblight(); + if (!rgblight_config.mode) { + dprintf("rgblight_init rgblight_config.mode = 0. Write default values to EEPROM.\n"); + eeconfig_write_rgblight_default(); + rgblight_config.raw = eeconfig_read_rgblight(); + } + eeconfig_debug_rgblight(); // display current eeprom values + + rgblight_timer_init(); // setup the timer + + if (rgblight_config.enable) { + rgblight_mode(rgblight_config.mode); + } +} + +void rgblight_increase(void) { + uint8_t mode; + if (rgblight_config.mode < RGBLIGHT_MODES) { + mode = rgblight_config.mode + 1; + } + rgblight_mode(mode); +} + +void rgblight_decrease(void) { + uint8_t mode; + if (rgblight_config.mode > 1) { //mode will never < 1, if mode is less than 1, eeprom need to be initialized. + mode = rgblight_config.mode-1; + } + rgblight_mode(mode); +} + +void rgblight_step(void) { + uint8_t mode; + mode = rgblight_config.mode + 1; + if (mode > RGBLIGHT_MODES) { + mode = 1; + } + rgblight_mode(mode); +} + +void rgblight_mode(uint8_t mode) { + if (!rgblight_config.enable) { + return; + } + if (mode<1) { + rgblight_config.mode = 1; + } else if (mode > RGBLIGHT_MODES) { + rgblight_config.mode = RGBLIGHT_MODES; + } else { + rgblight_config.mode = mode; + } + eeconfig_write_rgblight(rgblight_config.raw); + dprintf("rgblight mode: %u\n", rgblight_config.mode); + if (rgblight_config.mode == 1) { + rgblight_timer_disable(); + } else if (rgblight_config.mode >=2 && rgblight_config.mode <=23) { + // MODE 2-5, breathing + // MODE 6-8, rainbow mood + // MODE 9-14, rainbow swirl + // MODE 15-20, snake + // MODE 21-23, knight + rgblight_timer_enable(); + } + rgblight_sethsv(rgblight_config.hue, rgblight_config.sat, rgblight_config.val); +} + +void rgblight_toggle(void) { + rgblight_config.enable ^= 1; + eeconfig_write_rgblight(rgblight_config.raw); + dprintf("rgblight toggle: rgblight_config.enable = %u\n", rgblight_config.enable); + if (rgblight_config.enable) { + rgblight_mode(rgblight_config.mode); + } else { + rgblight_timer_disable(); + _delay_ms(50); + rgblight_set(); + } +} + + +void rgblight_increase_hue(void){ + uint16_t hue; + hue = (rgblight_config.hue+RGBLIGHT_HUE_STEP) % 360; + rgblight_sethsv(hue, rgblight_config.sat, rgblight_config.val); +} +void rgblight_decrease_hue(void){ + uint16_t hue; + if (rgblight_config.hue-RGBLIGHT_HUE_STEP <0 ) { + hue = (rgblight_config.hue+360-RGBLIGHT_HUE_STEP) % 360; + } else { + hue = (rgblight_config.hue-RGBLIGHT_HUE_STEP) % 360; + } + rgblight_sethsv(hue, rgblight_config.sat, rgblight_config.val); +} +void rgblight_increase_sat(void) { + uint8_t sat; + if (rgblight_config.sat + RGBLIGHT_SAT_STEP > 255) { + sat = 255; + } else { + sat = rgblight_config.sat+RGBLIGHT_SAT_STEP; + } + rgblight_sethsv(rgblight_config.hue, sat, rgblight_config.val); +} +void rgblight_decrease_sat(void){ + uint8_t sat; + if (rgblight_config.sat - RGBLIGHT_SAT_STEP < 0) { + sat = 0; + } else { + sat = rgblight_config.sat-RGBLIGHT_SAT_STEP; + } + rgblight_sethsv(rgblight_config.hue, sat, rgblight_config.val); +} +void rgblight_increase_val(void){ + uint8_t val; + if (rgblight_config.val + RGBLIGHT_VAL_STEP > 255) { + val = 255; + } else { + val = rgblight_config.val+RGBLIGHT_VAL_STEP; + } + rgblight_sethsv(rgblight_config.hue, rgblight_config.sat, val); +} +void rgblight_decrease_val(void) { + uint8_t val; + if (rgblight_config.val - RGBLIGHT_VAL_STEP < 0) { + val = 0; + } else { + val = rgblight_config.val-RGBLIGHT_VAL_STEP; + } + rgblight_sethsv(rgblight_config.hue, rgblight_config.sat, val); +} + +void rgblight_sethsv_noeeprom(uint16_t hue, uint8_t sat, uint8_t val){ + inmem_config.raw = rgblight_config.raw; + if (rgblight_config.enable) { + struct cRGB tmp_led; + sethsv(hue, sat, val, &tmp_led); + inmem_config.hue = hue; + inmem_config.sat = sat; + inmem_config.val = val; + // dprintf("rgblight set hue [MEMORY]: %u,%u,%u\n", inmem_config.hue, inmem_config.sat, inmem_config.val); + rgblight_setrgb(tmp_led.r, tmp_led.g, tmp_led.b); + } +} +void rgblight_sethsv(uint16_t hue, uint8_t sat, uint8_t val){ + if (rgblight_config.enable) { + if (rgblight_config.mode == 1) { + // same static color + rgblight_sethsv_noeeprom(hue, sat, val); + } else { + // all LEDs in same color + if (rgblight_config.mode >= 2 && rgblight_config.mode <= 5) { + // breathing mode, ignore the change of val, use in memory value instead + val = rgblight_config.val; + } else if (rgblight_config.mode >= 6 && rgblight_config.mode <= 14) { + // rainbow mood and rainbow swirl, ignore the change of hue + hue = rgblight_config.hue; + } + } + rgblight_config.hue = hue; + rgblight_config.sat = sat; + rgblight_config.val = val; + eeconfig_write_rgblight(rgblight_config.raw); + dprintf("rgblight set hsv [EEPROM]: %u,%u,%u\n", rgblight_config.hue, rgblight_config.sat, rgblight_config.val); + } +} + +void rgblight_setrgb(uint8_t r, uint8_t g, uint8_t b){ + // dprintf("rgblight set rgb: %u,%u,%u\n", r,g,b); + for (uint8_t i=0;i>8)&0xff; + OCR3AL = RGBLED_TIMER_TOP&0xff; + SREG = sreg; +} +void rgblight_timer_enable(void) { + TIMSK3 |= _BV(OCIE3A); + dprintf("TIMER3 enabled.\n"); +} +void rgblight_timer_disable(void) { + TIMSK3 &= ~_BV(OCIE3A); + dprintf("TIMER3 disabled.\n"); +} +void rgblight_timer_toggle(void) { + TIMSK3 ^= _BV(OCIE3A); + dprintf("TIMER3 toggled.\n"); +} + +ISR(TIMER3_COMPA_vect) { + // Mode = 1, static light, do nothing here + if (rgblight_config.mode>=2 && rgblight_config.mode<=5) { + // mode = 2 to 5, breathing mode + rgblight_effect_breathing(rgblight_config.mode-2); + + } else if (rgblight_config.mode>=6 && rgblight_config.mode<=8) { + rgblight_effect_rainbow_mood(rgblight_config.mode-6); + } else if (rgblight_config.mode>=9 && rgblight_config.mode<=14) { + rgblight_effect_rainbow_swirl(rgblight_config.mode-9); + } else if (rgblight_config.mode>=15 && rgblight_config.mode<=20) { + rgblight_effect_snake(rgblight_config.mode-15); + } else if (rgblight_config.mode>=21 && rgblight_config.mode<=23) { + rgblight_effect_knight(rgblight_config.mode-21); + } +} + +// effects +void rgblight_effect_breathing(uint8_t interval) { + static uint8_t pos = 0; + static uint16_t last_timer = 0; + + if (timer_elapsed(last_timer)=RGBLED_NUM) k=RGBLED_NUM-1; + if (i==k) { + sethsv(rgblight_config.hue, rgblight_config.sat, rgblight_config.val, &preled[i]); + } + } + } + if (RGBLIGHT_EFFECT_KNIGHT_OFFSET) { + for (i=0;iRGBLED_NUM+RGBLIGHT_EFFECT_KNIGHT_LENGTH) { + pos = RGBLED_NUM+RGBLIGHT_EFFECT_KNIGHT_LENGTH-1; + increament = 1; + } else { + pos += 1; + } + } + +} diff --git a/keyboard/nano/rgblight.h b/keyboard/nano/rgblight.h new file mode 100644 index 00000000..fd39ead0 --- /dev/null +++ b/keyboard/nano/rgblight.h @@ -0,0 +1,87 @@ +#ifndef RGBLIGHT_H +#define RGBLIGHT_H + +#ifndef RGBLIGHT_MODES +#define RGBLIGHT_MODES 23 +#endif + +#ifndef RGBLIGHT_EFFECT_SNAKE_LENGTH +#define RGBLIGHT_EFFECT_SNAKE_LENGTH 7 +#endif + +#ifndef RGBLIGHT_EFFECT_KNIGHT_LENGTH +#define RGBLIGHT_EFFECT_KNIGHT_LENGTH 7 +#endif +#ifndef RGBLIGHT_EFFECT_KNIGHT_OFFSET +#define RGBLIGHT_EFFECT_KNIGHT_OFFSET 11 +#endif + +#ifndef RGBLIGHT_EFFECT_DUALKNIGHT_LENGTH +#define RGBLIGHT_EFFECT_DUALKNIGHT_LENGTH 4 +#endif + +#ifndef RGBLIGHT_HUE_STEP +#define RGBLIGHT_HUE_STEP 10 +#endif +#ifndef RGBLIGHT_SAT_STEP +#define RGBLIGHT_SAT_STEP 17 +#endif +#ifndef RGBLIGHT_VAL_STEP +#define RGBLIGHT_VAL_STEP 17 +#endif + +#define RGBLED_TIMER_TOP F_CPU/(256*64) + +#include +#include +#include "eeconfig.h" +#include "light_ws2812.h" + +typedef union { + uint32_t raw; + struct { + bool enable :1; + uint8_t mode :6; + uint16_t hue :9; + uint8_t sat :8; + uint8_t val :8; + }; +} rgblight_config_t; + +void rgblight_init(void); +void rgblight_increase(void); +void rgblight_decrease(void); +void rgblight_toggle(void); +void rgblight_step(void); +void rgblight_mode(uint8_t mode); +void rgblight_set(void); +void rgblight_increase_hue(void); +void rgblight_decrease_hue(void); +void rgblight_increase_sat(void); +void rgblight_decrease_sat(void); +void rgblight_increase_val(void); +void rgblight_decrease_val(void); +void rgblight_sethsv(uint16_t hue, uint8_t sat, uint8_t val); +void rgblight_setrgb(uint8_t r, uint8_t g, uint8_t b); + +#define EECONFIG_RGBLIGHT (uint8_t *)7 +uint32_t eeconfig_read_rgblight(void); +void eeconfig_write_rgblight(uint32_t val); +void eeconfig_write_rgblight_default(void); +void eeconfig_debug_rgblight(void); + +void sethsv(uint16_t hue, uint8_t sat, uint8_t val, struct cRGB *led1); +void setrgb(uint8_t r, uint8_t g, uint8_t b, struct cRGB *led1); +void rgblight_sethsv_noeeprom(uint16_t hue, uint8_t sat, uint8_t val); + +void rgblight_timer_init(void); +void rgblight_timer_enable(void); +void rgblight_timer_disable(void); +void rgblight_timer_toggle(void); +void rgblight_effect_breathing(uint8_t interval); +void rgblight_effect_rainbow_mood(uint8_t interval); +void rgblight_effect_rainbow_swirl(uint8_t interval); +void rgblight_effect_snake(uint8_t interval); +void rgblight_effect_knight(uint8_t interval); + +#endif