111 changes: 111 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/holly.h
@@ -0,0 +1,111 @@
#pragma once

// HOLLY defines
#define WS2812_PIN 8
#define LED_VCC1_EN 7
#define LED_VCC2_EN 6
#define NUM_SWITCHES 6
#define S1_PIN 27
#define S2_PIN 25
#define S3_PIN 23
#define S4_PIN 21
#define S5_PIN 19
#define S6_PIN 10

#define NUM_LEDS 6
#define MIN_SPARKLES 3
#define MAX_SPARKLES 5
#define FADE_STEPS 10

#define FADE_DELAY 8
#define DWELL_DELAY 10
#define CYCLE_DELAY 10
#define LOOP_DELAY_S 7
#define LOOP_DELAY_MS (LOOP_DELAY_S * 1000)

#if LIB_PICO_STDIO_USB
#define NUM_CYCLES 5 // Enter shutdown early for testing
#else
#define NUM_CYCLES ((3600 * 3) / LOOP_DELAY_S) // 3 hours
#endif

#if LIB_PICO_STDIO_USB
#define DBG_PRINTF(...) printf(__VA_ARGS__)
#else
#define DBG_PRINTF(...)
#endif

// On some samples, the xosc can take longer to stabilize than is usual
#ifndef PICO_XOSC_STARTUP_DELAY_MULTIPLIER
#define PICO_XOSC_STARTUP_DELAY_MULTIPLIER 64
#endif

//------------- UART -------------//
#ifndef PICO_DEFAULT_UART
#define PICO_DEFAULT_UART 1
#endif

#ifndef PICO_DEFAULT_UART_TX_PIN
#define PICO_DEFAULT_UART_TX_PIN 4
#endif

#ifndef PICO_DEFAULT_UART_RX_PIN
#define PICO_DEFAULT_UART_RX_PIN 5
#endif

//------------- LED -------------//
#ifndef PICO_DEFAULT_LED_PIN
#define PICO_DEFAULT_LED_PIN 3
#endif

#ifndef PICO_DEFAULT_WS2812_PIN
#define PICO_DEFAULT_WS2812_PIN 13
#endif

//------------- I2C -------------//
#ifndef PICO_DEFAULT_I2C
#define PICO_DEFAULT_I2C 1
#endif

#ifndef PICO_DEFAULT_I2C_SDA_PIN
#define PICO_DEFAULT_I2C_SDA_PIN 14
#endif

#ifndef PICO_DEFAULT_I2C_SCL_PIN
#define PICO_DEFAULT_I2C_SCL_PIN 15
#endif

//------------- SPI -------------//
#ifndef PICO_DEFAULT_SPI
#define PICO_DEFAULT_SPI 0
#endif

#ifndef PICO_DEFAULT_SPI_TX_PIN
#define PICO_DEFAULT_SPI_TX_PIN 23
#endif

#ifndef PICO_DEFAULT_SPI_RX_PIN
#define PICO_DEFAULT_SPI_RX_PIN 20
#endif

#ifndef PICO_DEFAULT_SPI_SCK_PIN
#define PICO_DEFAULT_SPI_SCK_PIN 22
#endif

//------------- FLASH -------------//

// Use slower generic flash access
#define PICO_BOOT_STAGE2_CHOOSE_GENERIC_03H 1

#ifndef PICO_FLASH_SPI_CLKDIV
#define PICO_FLASH_SPI_CLKDIV 2
#endif

#ifndef PICO_FLASH_SIZE_BYTES
#define PICO_FLASH_SIZE_BYTES (1 * 512 * 1024)
#endif

// All boards have B1 RP2040
#ifndef PICO_RP2040_B0_SUPPORTED
#define PICO_RP2040_B0_SUPPORTED 0
#endif
46 changes: 46 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/holly_lpm.c
@@ -0,0 +1,46 @@
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include "pico/stdlib.h"

#include "clocks.h"
#include "peripherals.h"
#include "sleep.h"

int main() {
configure_clocks();
configure_hardware();

#if LIB_PICO_STDIO_USB
#pragma message "USB is enabled. Deep sleep is disabled!!"
#endif

// Read switch states on boot
sw_t sw_state = read_switches();

// Track number of cycles
uint16_t num = 0;

while (true) {
// Use switch state to choose color and animation
if (sw_state.s6) led_snake(COLOR_MODE_SILVER);
else if (sw_state.s5) led_snake(COLOR_MODE_XMAS);
else if (sw_state.s4) led_snake(COLOR_MODE_RGB);
else if (sw_state.s3) led_sparkle(COLOR_MODE_SILVER);
else if (sw_state.s2) led_sparkle(COLOR_MODE_XMAS);
else led_sparkle(COLOR_MODE_RGB);

// Only enter deep sleep if USB is disabled
#if !LIB_PICO_STDIO_USB
deep_sleep();
restore_clocks();
#else
sleep_ms(LOOP_DELAY_MS);
#endif

// Enter shutdown after NUM_CYCLES
if (++num >= NUM_CYCLES) {
shutdown();
}
}
}
234 changes: 234 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/memmap_custom.ld
@@ -0,0 +1,234 @@
/* Based on GCC ARM embedded samples.
Defines the following symbols for use by code:
__exidx_start
__exidx_end
__etext
__data_start__
__preinit_array_start
__preinit_array_end
__init_array_start
__init_array_end
__fini_array_start
__fini_array_end
__data_end__
__bss_start__
__bss_end__
__end__
end
__HeapLimit
__StackLimit
__StackTop
__stack (== StackTop)
*/

/* Skip 32kB at the start of flash, that's where our main program is */
MEMORY
{
FLASH(rx) : ORIGIN = 0x1000b400, LENGTH = 512k - 0xb400
RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k
SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
}

ENTRY(_entry_point)

SECTIONS
{
.flash_begin : {
__flash_binary_start = .;
} > FLASH

/* boot2 would go here, but we don't want it */

.text : {
__logical_binary_start = .;
KEEP (*(.vectors))
KEEP (*(.binary_info_header))
__binary_info_header_end = .;
KEEP (*(.reset))
/* TODO revisit this now memset/memcpy/float in ROM */
/* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from
* FLASH ... we will include any thing excluded here in .data below by default */
*(.init)
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*)
*(.fini)
/* Pull all c'tors into .text */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* Followed by destructors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)

*(.eh_frame*)
. = ALIGN(4);
} > FLASH

.rodata : {
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*)
. = ALIGN(4);
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
. = ALIGN(4);
} > FLASH

.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > FLASH

__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > FLASH
__exidx_end = .;

/* Machine inspectable binary information */
. = ALIGN(4);
__binary_info_start = .;
.binary_info :
{
KEEP(*(.binary_info.keep.*))
*(.binary_info.*)
} > FLASH
__binary_info_end = .;
. = ALIGN(4);

/* End of .text-like segments */
__etext = .;

.ram_vector_table (COPY): {
*(.ram_vector_table)
} > RAM

.data : {
__data_start__ = .;
*(vtable)

*(.time_critical*)

/* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */
*(.text*)
. = ALIGN(4);
*(.rodata*)
. = ALIGN(4);

*(.data*)

. = ALIGN(4);
*(.after_data.*)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__mutex_array_start = .);
KEEP(*(SORT(.mutex_array.*)))
KEEP(*(.mutex_array))
PROVIDE_HIDDEN (__mutex_array_end = .);

. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP(*(SORT(.preinit_array.*)))
KEEP(*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);

. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);

. = ALIGN(4);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
*(SORT(.fini_array.*))
*(.fini_array)
PROVIDE_HIDDEN (__fini_array_end = .);

*(.jcr)
. = ALIGN(4);
/* All data end */
__data_end__ = .;
} > RAM AT> FLASH

.uninitialized_data (COPY): {
. = ALIGN(4);
*(.uninitialized_data*)
} > RAM

/* Start and end symbols must be word-aligned */
.scratch_x : {
__scratch_x_start__ = .;
*(.scratch_x.*)
. = ALIGN(4);
__scratch_x_end__ = .;
} > SCRATCH_X AT > FLASH
__scratch_x_source__ = LOADADDR(.scratch_x);

.scratch_y : {
__scratch_y_start__ = .;
*(.scratch_y.*)
. = ALIGN(4);
__scratch_y_end__ = .;
} > SCRATCH_Y AT > FLASH
__scratch_y_source__ = LOADADDR(.scratch_y);

.bss : {
. = ALIGN(4);
__bss_start__ = .;
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > RAM

.heap (COPY):
{
__end__ = .;
end = __end__;
*(.heap*)
__HeapLimit = .;
} > RAM

/* .stack*_dummy section doesn't contains any symbols. It is only
* used for linker to calculate size of stack sections, and assign
* values to stack symbols later
*
* stack1 section may be empty/missing if platform_launch_core1 is not used */

/* by default we put core 0 stack at the end of scratch Y, so that if core 1
* stack is not used then all of SCRATCH_X is free.
*/
.stack1_dummy (COPY):
{
*(.stack1*)
} > SCRATCH_X
.stack_dummy (COPY):
{
*(.stack*)
} > SCRATCH_Y

.flash_end : {
__flash_binary_end = .;
} > FLASH

/* stack limit is poorly named, but historically is maximum heap ptr */
__StackLimit = ORIGIN(RAM) + LENGTH(RAM);
__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
__StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
__StackBottom = __StackTop - SIZEOF(.stack_dummy);
PROVIDE(__stack = __StackTop);

/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")

ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary")
/* todo assert on extra code */
}

249 changes: 249 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/peripherals.c
@@ -0,0 +1,249 @@
#include "colors.h"
#include "peripherals.h"

#include <stdio.h>
#include <stdlib.h>
#include "pico/stdlib.h"

#include "hardware/pio.h"
#include "hardware/vreg.h"
#include "ws2812.pio.h"

void configure_hardware(void) {
// Lower core voltage
#if !LIB_PICO_STDIO_USB
vreg_set_voltage(VREG_VOLTAGE_1_00);
#endif

// Init switch GPIOs
gpio_init(S1_PIN);
gpio_init(S2_PIN);
gpio_init(S3_PIN);
gpio_init(S4_PIN);
gpio_init(S5_PIN);
gpio_init(S6_PIN);

gpio_pull_up(S1_PIN);
gpio_pull_up(S2_PIN);
gpio_pull_up(S3_PIN);
gpio_pull_up(S4_PIN);
gpio_pull_up(S5_PIN);
gpio_pull_up(S6_PIN);

// Init PIO
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &ws2812_program);
ws2812_program_init(pio, sm, offset, WS2812_PIN, 800000, false);
}

sw_t read_switches(void) {
sw_t sw;

sw.s1 = !gpio_get(S1_PIN);
sw.s2 = !gpio_get(S2_PIN);
sw.s3 = !gpio_get(S3_PIN);
sw.s4 = !gpio_get(S4_PIN);
sw.s5 = !gpio_get(S5_PIN);
sw.s6 = !gpio_get(S6_PIN);

return sw;
}

static inline void put_pixel(uint32_t pixel_grb) {
pio_sm_put_blocking(pio0, 0, pixel_grb << 8u);
}

static inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) {
return ((uint32_t) (r) << 8) | ((uint32_t) (g) << 16) | (uint32_t) (b);
}

static inline void enable_led_vcc(uint8_t index) {
if (index == 1) {
gpio_init(LED_VCC1_EN);
gpio_set_dir(LED_VCC1_EN, GPIO_OUT);
gpio_put(LED_VCC1_EN, 1);

} else if (index == 2) {
gpio_init(LED_VCC2_EN);
gpio_set_dir(LED_VCC2_EN, GPIO_OUT);
gpio_put(LED_VCC2_EN, 1);
}
sleep_ms(1);
}

static inline void disable_led_vcc(void) {
gpio_init(LED_VCC1_EN);
gpio_set_dir(LED_VCC1_EN, GPIO_IN);
gpio_init(LED_VCC2_EN);
gpio_set_dir(LED_VCC2_EN, GPIO_IN);
}

static void set_led(uint8_t led_index, uint8_t r,uint8_t g, uint8_t b) {
// Write up to led_index with zero brightness
for (int l=0; l<led_index; l++) {
put_pixel(0);
}

// Write led_index with desired color
put_pixel(urgb_u32(r, g, b));
}

static inline void fade_in(uint8_t l, uint8_t r, uint8_t g, uint8_t b) {
uint8_t ri=0, gi=0, bi=0;
for (int s=0; s<FADE_STEPS; s++) {
ri = (ri >= r) ? r : ++ri;
gi = (gi >= g) ? g : ++gi;
bi = (bi >= b) ? b : ++bi;
set_led(l, ri, gi, bi);
sleep_ms(FADE_DELAY);
}
}

static inline void fade_out(uint8_t l, uint8_t r, uint8_t g, uint8_t b) {
uint8_t ri=r, gi=g, bi=b;
for (int s=1; s<=FADE_STEPS; s++) {
ri = (ri > 0) ? --ri : 0;
gi = (gi > 0) ? --gi : 0;
bi = (bi > 0) ? --bi : 0;
set_led(l, ri, gi, bi);
sleep_ms(FADE_DELAY);
}
}

void clear_leds(void) {
for (int l=0; l<NUM_LEDS; l++) {
enable_led_vcc(1);
if (l > 2) {
enable_led_vcc(2);
}

set_led(l, 0, 0, 0);
}
disable_led_vcc();
}

// Snake animation with various colors
void led_snake(uint8_t mode) {
DBG_PRINTF("led_snake mode %d\n", mode);

uint8_t num_colors;
if (mode == COLOR_MODE_SILVER) num_colors = NUM_SILVER_COLORS;
else if (mode == COLOR_MODE_XMAS) num_colors = NUM_XMAS_COLORS;
else num_colors = NUM_RGB_COLORS;
uint8_t p = num_colors, c = num_colors;

for (int l=0; l<NUM_LEDS; l++) {
// Ensure we don't get the same color twice in a row
while(c == p) {
c = rand() % num_colors;
}
p = c;

uint8_t r, g, b;
if (mode == COLOR_MODE_SILVER) {
r=silver_lut[c][R], g=silver_lut[c][G], b=silver_lut[c][B];
} else if (mode == COLOR_MODE_XMAS) {
r=xmas_lut[c][R], g=xmas_lut[c][G], b=xmas_lut[c][B];
} else {
r=rgb_lut[c][R], g=rgb_lut[c][G], b=rgb_lut[c][B];
}

enable_led_vcc(1);
if (l > 2) {
enable_led_vcc(2);
}

DBG_PRINTF("c/l (r/g/b): %d/%d (%d/%d/%d)\n", c, l, r, g, b);

fade_in(l, r, g, b);
sleep_ms(DWELL_DELAY);
fade_out(l, r, g, b);

disable_led_vcc();
sleep_ms(CYCLE_DELAY);
}
}

// Sparkle animation with various colors
void led_sparkle(uint8_t mode) {
DBG_PRINTF("led_sparkle mode %d\n", mode);

uint8_t num_colors;
if (mode == COLOR_MODE_SILVER) num_colors = NUM_SILVER_COLORS;
else if (mode == COLOR_MODE_XMAS) num_colors = NUM_XMAS_COLORS;
else num_colors = NUM_RGB_COLORS;
uint8_t p = num_colors, c = num_colors;

uint8_t n = rand() % (MAX_SPARKLES + 1 - MIN_SPARKLES) + MIN_SPARKLES;

for (int i=0; i<n; i++) {

// Ensure we don't get the same color twice in a row
while(c == p) {
c = rand() % num_colors;
}
p = c;

// Ensure we don't get the same LED twice in a row
static uint8_t q = NUM_LEDS, l = NUM_LEDS;
while(l == q) {
l = rand() % NUM_LEDS;
}
q = l;

uint8_t c = rand() % num_colors;

uint8_t r, g, b;
if (mode == COLOR_MODE_SILVER) {
r=silver_lut[c][R], g=silver_lut[c][G], b=silver_lut[c][B];
} else if (mode == COLOR_MODE_XMAS) {
r=xmas_lut[c][R], g=xmas_lut[c][G], b=xmas_lut[c][B];
} else {
r=rgb_lut[c][R], g=rgb_lut[c][G], b=rgb_lut[c][B];
}

enable_led_vcc(1);
if (l > 2) {
enable_led_vcc(2);
}

DBG_PRINTF("n/c/l (r/g/b): %d/%d/%d (%d/%d/%d)\n", n, c, l, r, g, b);

fade_in(l, r, g, b);
sleep_ms(DWELL_DELAY);
fade_out(l, r, g, b);

disable_led_vcc();
sleep_ms(CYCLE_DELAY);
}
}

void led_test(void) {
for (int l=0; l<NUM_LEDS; l++) {
// Turn on LEDs
enable_led_vcc(1);
if (l > 2) {
enable_led_vcc(2);
}

// Cycle through each RGB color on each LED
for (int c=0; c<NUM_RGB_COLORS; c++) {
uint8_t r=rgb_lut[c][R], g=rgb_lut[c][G], b=rgb_lut[c][B];
DBG_PRINTF("c/l (r/g/b): %d/%d (%d/%d/%d)\n", c, l, r, g, b);
fade_in(l, r, g, b);
sleep_ms(10 * CYCLE_DELAY);
fade_out(l, r, g, b);
}
sleep_ms(10 * CYCLE_DELAY);
// Cycle through each xmas color on each LED
for (int c=0; c<NUM_XMAS_COLORS; c++) {
uint8_t r=xmas_lut[c][R], g=xmas_lut[c][G], b=xmas_lut[c][B];
DBG_PRINTF("c/l (r/g/b): %d/%d (%d/%d/%d)\n", c, l, r, g, b);
fade_in(l, r, g, b);
sleep_ms(10 * CYCLE_DELAY);
fade_out(l, r, g, b);
}
disable_led_vcc();
}
}
24 changes: 24 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/peripherals.h
@@ -0,0 +1,24 @@
#pragma once

#include <stdint.h>

#define COLOR_MODE_RGB 0
#define COLOR_MODE_XMAS 1
#define COLOR_MODE_SILVER 2

typedef struct {
uint8_t s1 : 1;
uint8_t s2 : 1;
uint8_t s3 : 1;
uint8_t s4 : 1;
uint8_t s5 : 1;
uint8_t s6 : 1;
uint8_t unused : 2;
} sw_t;

void configure_hardware(void);
sw_t read_switches(void);
void clear_leds(void);
void led_test(void);
void led_sparkle(uint8_t mode);
void led_snake(uint8_t mode);
73 changes: 73 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/pico_sdk_import.cmake
@@ -0,0 +1,73 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake

# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()

if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()

if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()

if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()

set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")

if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
endif ()

if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()

get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()

set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()

set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)

include(${PICO_SDK_INIT_CMAKE_FILE})
47 changes: 47 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/sleep.c
@@ -0,0 +1,47 @@
#include "sleep.h"

#include <stdio.h>
#include <stdlib.h>
#include "pico/stdlib.h"

#include "hardware/clocks.h"
#include "hardware/structs/scb.h"
#include "hardware/sync.h"
#include "hardware/rtc.h"
#include "hardware/vreg.h"

// Go to sleep until woken up by the RTC
static void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) {
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS;
clocks_hw->sleep_en1 = 0x0;

rtc_set_alarm(t, callback);

uint save = scb_hw->scr;
scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS;

__wfi();
}

void deep_sleep(void) {
datetime_t t = {
.year = 2023,
.month = 1,
.day = 1,
.dotw = 0,
.hour = 0,
.min = 0,
.sec = 0
};
rtc_init();
rtc_set_datetime(&t);
t.sec = LOOP_DELAY_S;

sleep_goto_sleep_until(&t, NULL);
}

void shutdown(void) {
DBG_PRINTF("Entering shutdown!\n");
hw_write_masked(&vreg_and_chip_reset_hw->bod, 0, VREG_AND_CHIP_RESET_BOD_BITS);
hw_write_masked(&vreg_and_chip_reset_hw->vreg, 0, VREG_AND_CHIP_RESET_VREG_EN_BITS);
}
4 changes: 4 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/sleep.h
@@ -0,0 +1,4 @@
#pragma once

void deep_sleep(void);
void shutdown(void);
374 changes: 374 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/util/uf2conv.py
@@ -0,0 +1,374 @@
#!/usr/bin/env python3
# yapf: disable
import sys
import struct
import subprocess
import re
import os
import os.path
import argparse
import json
from time import sleep


UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
UF2_MAGIC_END = 0x0AB16F30 # Ditto

INFO_FILE = "/INFO_UF2.TXT"

appstartaddr = 0x2000
familyid = 0x0


def is_uf2(buf):
w = struct.unpack("<II", buf[0:8])
return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1

def is_hex(buf):
try:
w = buf[0:30].decode("utf-8")
except UnicodeDecodeError:
return False
if w[0] == ':' and re.match(b"^[:0-9a-fA-F\r\n]+$", buf):
return True
return False

def convert_from_uf2(buf):
global appstartaddr
global familyid
numblocks = len(buf) // 512
curraddr = None
currfamilyid = None
families_found = {}
prev_flag = None
all_flags_same = True
outp = []
for blockno in range(numblocks):
ptr = blockno * 512
block = buf[ptr:ptr + 512]
hd = struct.unpack(b"<IIIIIIII", block[0:32])
if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
print("Skipping block at " + ptr + "; bad magic")
continue
if hd[2] & 1:
# NO-flash flag set; skip block
continue
datalen = hd[4]
if datalen > 476:
assert False, "Invalid UF2 data size at " + ptr
newaddr = hd[3]
if (hd[2] & 0x2000) and (currfamilyid == None):
currfamilyid = hd[7]
if curraddr == None or ((hd[2] & 0x2000) and hd[7] != currfamilyid):
currfamilyid = hd[7]
curraddr = newaddr
if familyid == 0x0 or familyid == hd[7]:
appstartaddr = newaddr
padding = newaddr - curraddr
if padding < 0:
assert False, "Block out of order at " + ptr
if padding > 10*1024*1024:
assert False, "More than 10M of padding needed at " + ptr
if padding % 4 != 0:
assert False, "Non-word padding size at " + ptr
while padding > 0:
padding -= 4
outp.append(b"\x00\x00\x00\x00")
if familyid == 0x0 or ((hd[2] & 0x2000) and familyid == hd[7]):
outp.append(block[32 : 32 + datalen])
curraddr = newaddr + datalen
if hd[2] & 0x2000:
if hd[7] in families_found.keys():
if families_found[hd[7]] > newaddr:
families_found[hd[7]] = newaddr
else:
families_found[hd[7]] = newaddr
if prev_flag == None:
prev_flag = hd[2]
if prev_flag != hd[2]:
all_flags_same = False
if blockno == (numblocks - 1):
print("--- UF2 File Header Info ---")
families = load_families()
for family_hex in families_found.keys():
family_short_name = ""
for name, value in families.items():
if value == family_hex:
family_short_name = name
print("Family ID is {:s}, hex value is 0x{:08x}".format(family_short_name,family_hex))
print("Target Address is 0x{:08x}".format(families_found[family_hex]))
if all_flags_same:
print("All block flag values consistent, 0x{:04x}".format(hd[2]))
else:
print("Flags were not all the same")
print("----------------------------")
if len(families_found) > 1 and familyid == 0x0:
outp = []
appstartaddr = 0x0
return b"".join(outp)

def convert_to_carray(file_content):
outp = "#pragma once\n\n"
outp += f"// generated with {' '.join(str(v) for v in sys.argv[:])}\n"
outp += f"const unsigned long bindata_len = {hex(len(file_content))};\n"
outp += f"uint8_t __attribute__((aligned(512), section( \".rodata\"))) bindata[{len(file_content)}] = {{"
for i in range(len(file_content)):
if i % 16 == 0:
outp += "\n"
outp += "0x%02x, " % file_content[i]
outp += "\n};\n"
return bytes(outp, "utf-8")

def convert_to_uf2(file_content):
global familyid
datapadding = b""
while len(datapadding) < 512 - 256 - 32 - 4:
datapadding += b"\x00\x00\x00\x00"
numblocks = (len(file_content) + 255) // 256
outp = []
for blockno in range(numblocks):
ptr = 256 * blockno
chunk = file_content[ptr:ptr + 256]
flags = 0x0
if familyid:
flags |= 0x2000
hd = struct.pack(b"<IIIIIIII",
UF2_MAGIC_START0, UF2_MAGIC_START1,
flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
while len(chunk) < 256:
chunk += b"\x00"
block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
assert len(block) == 512
outp.append(block)
return b"".join(outp)

class Block:
def __init__(self, addr):
self.addr = addr
self.bytes = bytearray(256)

def encode(self, blockno, numblocks):
global familyid
flags = 0x0
if familyid:
flags |= 0x2000
if devicetype:
flags |= 0x8000
hd = struct.pack("<IIIIIIII",
UF2_MAGIC_START0, UF2_MAGIC_START1,
flags, self.addr, 256, blockno, numblocks, familyid)
hd += self.bytes[0:256]
if devicetype:
hd += bytearray(b'\x08\x29\xa7\xc8')
hd += bytearray(devicetype.to_bytes(4, 'little'))
while len(hd) < 512 - 4:
hd += b"\x00"
hd += struct.pack("<I", UF2_MAGIC_END)
return hd

def convert_from_hex_to_uf2(buf):
global appstartaddr
appstartaddr = None
upper = 0
currblock = None
blocks = []
for line in buf.split('\n'):
if line[0] != ":":
continue
i = 1
rec = []
while i < len(line) - 1:
rec.append(int(line[i:i+2], 16))
i += 2
tp = rec[3]
if tp == 4:
upper = ((rec[4] << 8) | rec[5]) << 16
elif tp == 2:
upper = ((rec[4] << 8) | rec[5]) << 4
elif tp == 1:
break
elif tp == 0:
addr = upper + ((rec[1] << 8) | rec[2])
if appstartaddr == None:
appstartaddr = addr
i = 4
while i < len(rec) - 1:
if not currblock or currblock.addr & ~0xff != addr & ~0xff:
currblock = Block(addr & ~0xff)
blocks.append(currblock)
currblock.bytes[addr & 0xff] = rec[i]
addr += 1
i += 1
numblocks = len(blocks)
resfile = b""
for i in range(0, numblocks):
resfile += blocks[i].encode(i, numblocks)
return resfile

def to_str(b):
return b.decode("utf-8")

def get_drives():
drives = []
if sys.platform == "win32":
r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk",
"get", "DeviceID,", "VolumeName,",
"FileSystem,", "DriveType"])
for line in to_str(r).split('\n'):
words = re.split('\s+', line)
if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
drives.append(words[0])
else:
searchpaths = ["/media"]
if sys.platform == "darwin":
searchpaths = ["/Volumes"]
elif sys.platform == "linux":
searchpaths += ["/media/" + os.environ["USER"], '/run/media/' + os.environ["USER"]]

for rootpath in searchpaths:
if os.path.isdir(rootpath):
for d in os.listdir(rootpath):
if os.path.isdir(rootpath):
drives.append(os.path.join(rootpath, d))


def has_info(d):
try:
return os.path.isfile(d + INFO_FILE)
except:
return False

return list(filter(has_info, drives))


def board_id(path):
with open(path + INFO_FILE, mode='r') as file:
file_content = file.read()
return re.search("Board-ID: ([^\r\n]*)", file_content).group(1)


def list_drives():
for d in get_drives():
print(d, board_id(d))


def write_file(name, buf):
with open(name, "wb") as f:
f.write(buf)
print("Wrote %d bytes to %s" % (len(buf), name))


def load_families():
# The expectation is that the `uf2families.json` file is in the same
# directory as this script. Make a path that works using `__file__`
# which contains the full path to this script.
filename = "uf2families.json"
pathname = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
with open(pathname) as f:
raw_families = json.load(f)

families = {}
for family in raw_families:
families[family["short_name"]] = int(family["id"], 0)

return families


def main():
global appstartaddr, familyid
def error(msg):
print(msg, file=sys.stderr)
sys.exit(1)
parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
help='input file (HEX, BIN or UF2)')
parser.add_argument('-b', '--base', dest='base', type=str,
default="0x2000",
help='set base address of application for BIN format (default: 0x2000)')
parser.add_argument('-f', '--family', dest='family', type=str,
default="0x0",
help='specify familyID - number or name (default: 0x0)')
parser.add_argument('-t' , '--device-type', dest='devicetype', type=str,
help='specify deviceTypeID extension tag - number')
parser.add_argument('-o', '--output', metavar="FILE", dest='output', type=str,
help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
parser.add_argument('-d', '--device', dest="device_path",
help='select a device path to flash')
parser.add_argument('-l', '--list', action='store_true',
help='list connected devices')
parser.add_argument('-c', '--convert', action='store_true',
help='do not flash, just convert')
parser.add_argument('-D', '--deploy', action='store_true',
help='just flash, do not convert')
parser.add_argument('-w', '--wait', action='store_true',
help='wait for device to flash')
parser.add_argument('-C', '--carray', action='store_true',
help='convert binary file to a C array, not UF2')
parser.add_argument('-i', '--info', action='store_true',
help='display header information from UF2, do not convert')
args = parser.parse_args()
appstartaddr = int(args.base, 0)

families = load_families()

if args.family.upper() in families:
familyid = families[args.family.upper()]
else:
try:
familyid = int(args.family, 0)
except ValueError:
error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))

global devicetype
devicetype = int(args.devicetype, 0) if args.devicetype else None

if args.list:
list_drives()
else:
if not args.input:
error("Need input file")
with open(args.input, mode='rb') as f:
inpbuf = f.read()
from_uf2 = is_uf2(inpbuf)
ext = "uf2"
if args.deploy:
outbuf = inpbuf
elif from_uf2 and not args.info:
outbuf = convert_from_uf2(inpbuf)
ext = "bin"
elif from_uf2 and args.info:
outbuf = ""
convert_from_uf2(inpbuf)
elif is_hex(inpbuf):
outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
elif args.carray:
outbuf = convert_to_carray(inpbuf)
ext = "h"
else:
outbuf = convert_to_uf2(inpbuf)
if not args.deploy and not args.info:
print("Converted to %s, output size: %d, start address: 0x%x" %
(ext, len(outbuf), appstartaddr))
if args.convert or ext != "uf2":
if args.output == None:
args.output = "flash." + ext
if args.output:
write_file(args.output, outbuf)
if ext == "uf2" and not args.convert and not args.info:
drives = get_drives()
if len(drives) == 0:
if args.wait:
print("Waiting for drive to deploy...")
while len(drives) == 0:
sleep(0.1)
drives = get_drives()
elif not args.output:
error("No drive to deploy.")
for d in drives:
print("Flashing %s (%s)" % (d, board_id(d)))
write_file(d + "/NEW.UF2", outbuf)


if __name__ == "__main__":
main()
217 changes: 217 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/util/uf2families.json
@@ -0,0 +1,217 @@
[
{
"id": "0x16573617",
"short_name": "ATMEGA32",
"description": "Microchip (Atmel) ATmega32"
},
{
"id": "0x1851780a",
"short_name": "SAML21",
"description": "Microchip (Atmel) SAML21"
},
{
"id": "0x1b57745f",
"short_name": "NRF52",
"description": "Nordic NRF52"
},
{
"id": "0x1c5f21b0",
"short_name": "ESP32",
"description": "ESP32"
},
{
"id": "0x1e1f432d",
"short_name": "STM32L1",
"description": "ST STM32L1xx"
},
{
"id": "0x202e3a91",
"short_name": "STM32L0",
"description": "ST STM32L0xx"
},
{
"id": "0x21460ff0",
"short_name": "STM32WL",
"description": "ST STM32WLxx"
},
{
"id": "0x2abc77ec",
"short_name": "LPC55",
"description": "NXP LPC55xx"
},
{
"id": "0x300f5633",
"short_name": "STM32G0",
"description": "ST STM32G0xx"
},
{
"id": "0x31d228c6",
"short_name": "GD32F350",
"description": "GD32F350"
},
{
"id": "0x04240bdf",
"short_name": "STM32L5",
"description": "ST STM32L5xx"
},
{
"id": "0x4c71240a",
"short_name": "STM32G4",
"description": "ST STM32G4xx"
},
{
"id": "0x4fb2d5bd",
"short_name": "MIMXRT10XX",
"description": "NXP i.MX RT10XX"
},
{
"id": "0x53b80f00",
"short_name": "STM32F7",
"description": "ST STM32F7xx"
},
{
"id": "0x55114460",
"short_name": "SAMD51",
"description": "Microchip (Atmel) SAMD51"
},
{
"id": "0x57755a57",
"short_name": "STM32F4",
"description": "ST STM32F4xx"
},
{
"id": "0x5a18069b",
"short_name": "FX2",
"description": "Cypress FX2"
},
{
"id": "0x5d1a0a2e",
"short_name": "STM32F2",
"description": "ST STM32F2xx"
},
{
"id": "0x5ee21072",
"short_name": "STM32F1",
"description": "ST STM32F103"
},
{
"id": "0x621e937a",
"short_name": "NRF52833",
"description": "Nordic NRF52833"
},
{
"id": "0x647824b6",
"short_name": "STM32F0",
"description": "ST STM32F0xx"
},
{
"id": "0x68ed2b88",
"short_name": "SAMD21",
"description": "Microchip (Atmel) SAMD21"
},
{
"id": "0x6b846188",
"short_name": "STM32F3",
"description": "ST STM32F3xx"
},
{
"id": "0x6d0922fa",
"short_name": "STM32F407",
"description": "ST STM32F407"
},
{
"id": "0x6db66082",
"short_name": "STM32H7",
"description": "ST STM32H7xx"
},
{
"id": "0x70d16653",
"short_name": "STM32WB",
"description": "ST STM32WBxx"
},
{
"id": "0x7eab61ed",
"short_name": "ESP8266",
"description": "ESP8266"
},
{
"id": "0x7f83e793",
"short_name": "KL32L2",
"description": "NXP KL32L2x"
},
{
"id": "0x8fb060fe",
"short_name": "STM32F407VG",
"description": "ST STM32F407VG"
},
{
"id": "0xada52840",
"short_name": "NRF52840",
"description": "Nordic NRF52840"
},
{
"id": "0xbfdd4eee",
"short_name": "ESP32S2",
"description": "ESP32-S2"
},
{
"id": "0xc47e5767",
"short_name": "ESP32S3",
"description": "ESP32-S3"
},
{
"id": "0xd42ba06c",
"short_name": "ESP32C3",
"description": "ESP32-C3"
},
{
"id": "0x2b88d29c",
"short_name": "ESP32C2",
"description": "ESP32-C2"
},
{
"id": "0x332726f6",
"short_name": "ESP32H2",
"description": "ESP32-H2"
},
{
"id": "0xe48bff56",
"short_name": "RP2040",
"description": "Raspberry Pi RP2040"
},
{
"id": "0x00ff6919",
"short_name": "STM32L4",
"description": "ST STM32L4xx"
},
{
"id": "0x9af03e33",
"short_name": "GD32VF103",
"description": "GigaDevice GD32VF103"
},
{
"id": "0x4f6ace52",
"short_name": "CSK4",
"description": "LISTENAI CSK300x/400x"
},
{
"id": "0x6e7348a8",
"short_name": "CSK6",
"description": "LISTENAI CSK60xx"
},
{
"id": "0x11de784a",
"short_name": "M0SENSE",
"description": "M0SENSE BL702"
},
{
"id": "0x4b684d71",
"short_name": "MaixPlay-U4",
"description": "Sipeed MaixPlay-U4(BL618)"
},
{
"id": "0x9517422f",
"short_name": "RZA1LU",
"description": "Renesas RZ/A1LU (R7S7210xx)"
}
]
115 changes: 115 additions & 0 deletions keyboards/nullbitsco/holly/holly_lpm/ws2812.pio.h
@@ -0,0 +1,115 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //

#pragma once

#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif

// ------ //
// ws2812 //
// ------ //

#define ws2812_wrap_target 0
#define ws2812_wrap 3

#define ws2812_T1 2
#define ws2812_T2 5
#define ws2812_T3 3

static const uint16_t ws2812_program_instructions[] = {
// .wrap_target
0x6221, // 0: out x, 1 side 0 [2]
0x1123, // 1: jmp !x, 3 side 1 [1]
0x1400, // 2: jmp 0 side 1 [4]
0xa442, // 3: nop side 0 [4]
// .wrap
};

#if !PICO_NO_HARDWARE
static const struct pio_program ws2812_program = {
.instructions = ws2812_program_instructions,
.length = 4,
.origin = -1,
};

static inline pio_sm_config ws2812_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}

#include "hardware/clocks.h"
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = ws2812_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
div = (div < 1.0) ? 1 : div;
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}

#endif

// --------------- //
// ws2812_parallel //
// --------------- //

#define ws2812_parallel_wrap_target 0
#define ws2812_parallel_wrap 3

#define ws2812_parallel_T1 2
#define ws2812_parallel_T2 5
#define ws2812_parallel_T3 3

static const uint16_t ws2812_parallel_program_instructions[] = {
// .wrap_target
0x6020, // 0: out x, 32
0xa10b, // 1: mov pins, !null [1]
0xa401, // 2: mov pins, x [4]
0xa103, // 3: mov pins, null [1]
// .wrap
};

#if !PICO_NO_HARDWARE
static const struct pio_program ws2812_parallel_program = {
.instructions = ws2812_parallel_program_instructions,
.length = 4,
.origin = -1,
};

static inline pio_sm_config ws2812_parallel_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + ws2812_parallel_wrap_target, offset + ws2812_parallel_wrap);
return c;
}

#include "hardware/clocks.h"
static inline void ws2812_parallel_program_init(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, float freq) {
for(uint i=pin_base; i<pin_base+pin_count; i++) {
pio_gpio_init(pio, i);
}
pio_sm_set_consecutive_pindirs(pio, sm, pin_base, pin_count, true);
pio_sm_config c = ws2812_parallel_program_get_default_config(offset);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_out_pins(&c, pin_base, pin_count);
sm_config_set_set_pins(&c, pin_base, pin_count);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
int cycles_per_bit = ws2812_parallel_T1 + ws2812_parallel_T2 + ws2812_parallel_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}

#endif

56 changes: 56 additions & 0 deletions keyboards/nullbitsco/holly/info.json
@@ -0,0 +1,56 @@
{
"keyboard_name": "HOLLY",
"manufacturer": "nullbits",
"url": "https://nullbits.co/holly",
"maintainer": "jaygreco",
"usb": {
"vid": "0x6E61",
"pid": "0x6062",
"device_version": "0.0.1",
"no_startup_check": true
},
"debounce": 10,
"layouts": {
"LAYOUT": {
"layout": [
{"x": 0, "y": 0, "matrix": [0, 0]},
{"x": 1, "y": 0, "matrix": [0, 1]},
{"x": 2, "y": 0, "matrix": [0, 2]},
{"x": 0, "y": 1, "matrix": [1, 0]},
{"x": 1, "y": 1, "matrix": [1, 1]},
{"x": 2, "y": 1, "matrix": [1, 2]}
]
}
},
"qmk": {
"tap_keycode_delay": 10
},
"processor": "RP2040",
"bootloader": "rp2040",
"matrix_pins": {
"direct": [
["GP27", "GP25", "GP23"],
["GP21", "GP19", "GP10"]
]
},
"rgblight": {
"led_count": 6,
"max_brightness": 64,
"animations": {
"breathing": true,
"rainbow_mood": true,
"rainbow_swirl": true,
"snake": true,
"knight": true,
"christmas": true,
"static_gradient": true,
"rgb_test": true,
"alternating": true,
"twinkle": true
}
},
"ws2812": {
"pin": "GP8",
"driver": "vendor"
}
}
14 changes: 14 additions & 0 deletions keyboards/nullbitsco/holly/keymaps/via/holly_via.json
@@ -0,0 +1,14 @@
{
"name": "HOLLY",
"vendorId": "0x6E61",
"productId": "0x6062",
"keycodes": ["qmk_lighting"],
"menus": ["qmk_rgblight"],
"matrix": {"rows": 2, "cols": 3},
"layouts": {
"keymap": [
[{"c": "#777777"}, "0,0", "0,1", "0,2"],
["1,0", "1,1", "1,2"]
]
}
}
34 changes: 34 additions & 0 deletions keyboards/nullbitsco/holly/keymaps/via/keymap.c
@@ -0,0 +1,34 @@
/*
Copyright 2021 Jay Greco
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 <http://www.gnu.org/licenses/>.
*/

#include QMK_KEYBOARD_H

enum layer_names {
_BASE,
_VIA1,
_VIA2,
_VIA3
};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {

[_BASE] = LAYOUT(
KC_F1, KC_F2, KC_F3,
KC_F4, KC_F5, KC_F6
),

};
9 changes: 9 additions & 0 deletions keyboards/nullbitsco/holly/mcuconf.h
@@ -0,0 +1,9 @@
// Copyright 2022 Jay Greco
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include_next <mcuconf.h>

#undef RP_ADC_USE_ADC1
#define RP_ADC_USE_ADC1 TRUE
12 changes: 12 additions & 0 deletions keyboards/nullbitsco/holly/readme.md
@@ -0,0 +1,12 @@
# HOLLY

![HOLLY](https://nullbits.co/static/img/holly1.jpg)

A 6-switch RP2040 macropad and ornament built by nullbits.

Insert a CR2032 coin cell into the coin cell holder and slide the switch to the left (away from the USB connector) to enter ornament mode. The LEDs will sparkle for several hours before shutting off to conserve power.

In order to enter the bootloader, hold switch #1 while plugging in the USB cable.

To build in QMK, clone or symlink the `holly` directory into your QMK firmware directory: `keyboards/nullbitsco/`.
Then, build with `qmk compile -kb nullbitsco/holly -km all`.
16 changes: 16 additions & 0 deletions keyboards/nullbitsco/holly/rules.mk
@@ -0,0 +1,16 @@
# Build Options
# change yes to no to disable
#
BOOTMAGIC_ENABLE = no # Enable Bootmagic Lite
MOUSEKEY_ENABLE = yes # Mouse keys
EXTRAKEY_ENABLE = yes # Audio control and System control
CONSOLE_ENABLE = yes # Console for debug
COMMAND_ENABLE = no # Commands for debug and configuration
NKRO_ENABLE = no # Enable N-Key Rollover
BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality
RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow
AUDIO_ENABLE = no # Audio output
ENCODER_ENABLE = no # Use rotary encoder
VIA_ENABLE = yes # Enable VIA

SRC += analog.c