Merge remote-tracking branch 'qmk 0.17.0' into firmware21

This commit is contained in:
Drashna Jael're
2022-05-29 15:38:33 -07:00
337 changed files with 14741 additions and 2516 deletions

18
.github/workflows/auto_approve.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Automatic Approve
on:
schedule:
- cron: "*/5 * * * *"
jobs:
automatic_approve:
runs-on: ubuntu-latest
if: github.repository == 'qmk/qmk_firmware'
steps:
- uses: mheap/automatic-approve-action@v1
with:
token: ${{ secrets.QMK_BOT_TOKEN }}
workflows: "format.yml,lint.yml,unit_test.yml"
dangerous_files: "lib/python/,Makefile,paths.mk,builddefs/"

View File

@@ -19,11 +19,13 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: qmkfm/base_container container: qmkfm/qmk_cli
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: recursive submodules: recursive
- name: Install dependencies
run: pip3 install -r requirements-dev.txt
- name: Run tests - name: Run tests
run: make test:all run: make test:all

3
.gitignore vendored
View File

@@ -91,3 +91,6 @@ user_song_list.h
compile_commands.json compile_commands.json
.clangd/ .clangd/
.cache/ .cache/
# VIA(L) json files that don't belong in QMK repo
via*.json

View File

@@ -1 +0,0 @@
theme: jekyll-theme-cayman

View File

@@ -97,12 +97,18 @@ ifeq ($(strip $(BOOTLOADER)), halfkay)
OPT_DEFS += -DBOOTLOADER_HALFKAY OPT_DEFS += -DBOOTLOADER_HALFKAY
BOOTLOADER_TYPE = halfkay BOOTLOADER_TYPE = halfkay
# Teensy 2.0
ifeq ($(strip $(MCU)), atmega32u4) ifeq ($(strip $(MCU)), atmega32u4)
BOOTLOADER_SIZE = 512 BOOTLOADER_SIZE = 512
endif endif
# Teensy 2.0++
ifeq ($(strip $(MCU)), at90usb1286) ifeq ($(strip $(MCU)), at90usb1286)
BOOTLOADER_SIZE = 1024 BOOTLOADER_SIZE = 1024
endif endif
# Teensy LC, 3.x
ifneq (,$(filter $(MCU_ORIG), MKL26Z64 MK20DX128 MK20DX256 MK66FX1M0))
FIRMWARE_FORMAT = hex
endif
endif endif
ifeq ($(strip $(BOOTLOADER)), caterina) ifeq ($(strip $(BOOTLOADER)), caterina)
OPT_DEFS += -DBOOTLOADER_CATERINA OPT_DEFS += -DBOOTLOADER_CATERINA
@@ -202,6 +208,10 @@ ifeq ($(strip $(BOOTLOADER)), md-boot)
OPT_DEFS += -DBOOTLOADER_MD_BOOT OPT_DEFS += -DBOOTLOADER_MD_BOOT
BOOTLOADER_TYPE = md_boot BOOTLOADER_TYPE = md_boot
endif endif
ifeq ($(strip $(BOOTLOADER)), wb32-dfu)
OPT_DEFS += -DBOOTLOADER_WB32_DFU
BOOTLOADER_TYPE = wb32_dfu
endif
ifeq ($(strip $(BOOTLOADER_TYPE)),) ifeq ($(strip $(BOOTLOADER_TYPE)),)
$(call CATASTROPHIC_ERROR,Invalid BOOTLOADER,No bootloader specified. Please set an appropriate 'BOOTLOADER' in your keyboard's 'rules.mk' file.) $(call CATASTROPHIC_ERROR,Invalid BOOTLOADER,No bootloader specified. Please set an appropriate 'BOOTLOADER' in your keyboard's 'rules.mk' file.)

View File

@@ -172,13 +172,7 @@ generated-files: $(KEYMAP_OUTPUT)/src/config.h $(KEYMAP_OUTPUT)/src/keymap.c
endif endif
ifeq ($(strip $(CTPC)), yes) include $(BUILDDEFS_PATH)/converters.mk
CONVERT_TO_PROTON_C=yes
endif
ifeq ($(strip $(CONVERT_TO_PROTON_C)), yes)
include platforms/chibios/boards/QMK_PROTON_C/convert_to_proton_c.mk
endif
include $(BUILDDEFS_PATH)/mcu_selection.mk include $(BUILDDEFS_PATH)/mcu_selection.mk
@@ -328,12 +322,18 @@ ifneq ("$(wildcard $(KEYBOARD_PATH_5)/info.json)","")
endif endif
CONFIG_H += $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/layouts.h CONFIG_H += $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/layouts.h
KEYBOARD_SRC += $(KEYBOARD_OUTPUT)/src/default_keyboard.c
$(KEYBOARD_OUTPUT)/src/info_config.h: $(INFO_JSON_FILES) $(KEYBOARD_OUTPUT)/src/info_config.h: $(INFO_JSON_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-config-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/info_config.h) $(eval CMD=$(QMK_BIN) generate-config-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/info_config.h)
@$(BUILD_CMD) @$(BUILD_CMD)
$(KEYBOARD_OUTPUT)/src/default_keyboard.c: $(INFO_JSON_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-keyboard-c --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.c)
@$(BUILD_CMD)
$(KEYBOARD_OUTPUT)/src/default_keyboard.h: $(INFO_JSON_FILES) $(KEYBOARD_OUTPUT)/src/default_keyboard.h: $(INFO_JSON_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-keyboard-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.h) $(eval CMD=$(QMK_BIN) generate-keyboard-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.h)
@@ -344,7 +344,7 @@ $(KEYBOARD_OUTPUT)/src/layouts.h: $(INFO_JSON_FILES)
$(eval CMD=$(QMK_BIN) generate-layouts --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/layouts.h) $(eval CMD=$(QMK_BIN) generate-layouts --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/layouts.h)
@$(BUILD_CMD) @$(BUILD_CMD)
generated-files: $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/default_keyboard.h $(KEYBOARD_OUTPUT)/src/layouts.h generated-files: $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/default_keyboard.c $(KEYBOARD_OUTPUT)/src/default_keyboard.h $(KEYBOARD_OUTPUT)/src/layouts.h
.INTERMEDIATE : generated-files .INTERMEDIATE : generated-files

View File

@@ -4,6 +4,8 @@ endif
.DEFAULT_GOAL := all .DEFAULT_GOAL := all
OPT = g
include paths.mk include paths.mk
include $(BUILDDEFS_PATH)/message.mk include $(BUILDDEFS_PATH)/message.mk

View File

@@ -149,6 +149,11 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
endif endif
endif endif
QUANTUM_PAINTER_ENABLE ?= no
ifeq ($(strip $(QUANTUM_PAINTER_ENABLE)), yes)
include $(QUANTUM_DIR)/painter/rules.mk
endif
VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi
EEPROM_DRIVER ?= vendor EEPROM_DRIVER ?= vendor
ifeq ($(filter $(EEPROM_DRIVER),$(VALID_EEPROM_DRIVER_TYPES)),) ifeq ($(filter $(EEPROM_DRIVER),$(VALID_EEPROM_DRIVER_TYPES)),)
@@ -652,8 +657,9 @@ ifeq ($(strip $(HAPTIC_ENABLE)),yes)
endif endif
ifeq ($(strip $(HD44780_ENABLE)), yes) ifeq ($(strip $(HD44780_ENABLE)), yes)
SRC += platforms/avr/drivers/hd44780.c
OPT_DEFS += -DHD44780_ENABLE OPT_DEFS += -DHD44780_ENABLE
COMMON_VPATH += $(DRIVER_PATH)/lcd
SRC += hd44780.c
endif endif
VALID_OLED_DRIVER_TYPES := SSD1306 custom VALID_OLED_DRIVER_TYPES := SSD1306 custom
@@ -701,7 +707,8 @@ endif
ifeq ($(strip $(UNICODE_COMMON)), yes) ifeq ($(strip $(UNICODE_COMMON)), yes)
OPT_DEFS += -DUNICODE_COMMON_ENABLE OPT_DEFS += -DUNICODE_COMMON_ENABLE
SRC += $(QUANTUM_DIR)/process_keycode/process_unicode_common.c SRC += $(QUANTUM_DIR)/process_keycode/process_unicode_common.c \
$(QUANTUM_DIR)/utf8.c
endif endif
MAGIC_ENABLE ?= yes MAGIC_ENABLE ?= yes

View File

@@ -82,8 +82,8 @@ endif
# -Wall...: warning level # -Wall...: warning level
# -Wa,...: tell GCC to pass this to the assembler. # -Wa,...: tell GCC to pass this to the assembler.
ifeq ($(strip $(LTO_ENABLE)), yes) ifeq ($(strip $(LTO_ENABLE)), yes)
ifeq ($(PLATFORM),CHIBIOS) ifeq ($(PLATFORM),ARM_ATSAM)
$(info Enabling LTO on ChibiOS-targeting boards is known to have a high likelihood of failure.) $(info Enabling LTO on arm_atsam-targeting boards is known to have a high likelihood of failure.)
$(info If unsure, set LTO_ENABLE = no.) $(info If unsure, set LTO_ENABLE = no.)
endif endif
CDEFS += -flto CDEFS += -flto
@@ -316,7 +316,7 @@ gccversion :
@$(BUILD_CMD) @$(BUILD_CMD)
%.uf2: %.hex %.uf2: %.hex
$(eval CMD=$(UF2CONV) $(BUILD_DIR)/$(TARGET).hex -o $(BUILD_DIR)/$(TARGET).uf2 -c -f $(UF2_FAMILY) >/dev/null 2>&1) $(eval CMD=$(UF2CONV) $(BUILD_DIR)/$(TARGET).hex --output $(BUILD_DIR)/$(TARGET).uf2 --convert --family $(UF2_FAMILY) >/dev/null 2>&1)
#@$(SILENT) || printf "$(MSG_EXECUTING) '$(CMD)':\n" #@$(SILENT) || printf "$(MSG_EXECUTING) '$(CMD)':\n"
@$(SILENT) || printf "$(MSG_UF2) $@" | $(AWK_CMD) @$(SILENT) || printf "$(MSG_UF2) $@" | $(AWK_CMD)
@$(BUILD_CMD) @$(BUILD_CMD)

37
builddefs/converters.mk Normal file
View File

@@ -0,0 +1,37 @@
# Note for new boards -- CTPC and CONVERT_TO_PROTON_C are deprecated terms
# and should not be replicated for new boards. These will be removed from
# documentation as well as existing keymaps in due course.
ifeq ($(strip $(CTPC)), yes)
CONVERT_TO_PROTON_C=yes
endif
ifeq ($(strip $(CONVERT_TO_PROTON_C)), yes)
CONVERT_TO=proton_c
cpfirmware: ctpc_warning
.INTERMEDIATE: ctpc_warning
ctpc_warning: elf
$(info @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@)
$(info The `CONVERT_TO_PROTON_C` and `CTPC` options are soon to be deprecated.)
$(info Boards should be changed to use `CONVERT_TO=proton_c` instead.)
$(info @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@)
endif
# TODO: opt in rather than assume everything uses a pro micro
PIN_COMPATIBLE ?= promicro
ifneq ($(CONVERT_TO),)
# glob to search each platfrorm and/or check for valid converter
CONVERTER := $(wildcard $(PLATFORM_PATH)/*/converters/$(PIN_COMPATIBLE)_to_$(CONVERT_TO)/)
ifeq ($(CONVERTER),)
$(call CATASTROPHIC_ERROR,Converting from '$(PIN_COMPATIBLE)' to '$(CONVERT_TO)' not possible!)
endif
TARGET := $(TARGET)_$(CONVERT_TO)
# Configure any defaults
OPT_DEFS += -DCONVERT_TO_$(strip $(shell echo $(CONVERT_TO) | tr '[:lower:]' '[:upper:]'))
OPT_DEFS += -DCONVERTER_ENABLED
VPATH += $(CONVERTER)
# Finally run any converter specific logic
include $(CONVERTER)/converter.mk
endif

View File

@@ -17,6 +17,7 @@ SPACE_CADET_ENABLE ?= yes
GRAVE_ESC_ENABLE ?= yes GRAVE_ESC_ENABLE ?= yes
GENERIC_FEATURES = \ GENERIC_FEATURES = \
CAPS_WORD \
COMBO \ COMBO \
COMMAND \ COMMAND \
DEFERRED_EXEC \ DEFERRED_EXEC \
@@ -25,12 +26,14 @@ GENERIC_FEATURES = \
DYNAMIC_KEYMAP \ DYNAMIC_KEYMAP \
DYNAMIC_MACRO \ DYNAMIC_MACRO \
ENCODER \ ENCODER \
ENCODER_MAP \
GRAVE_ESC \ GRAVE_ESC \
HAPTIC \ HAPTIC \
KEY_LOCK \ KEY_LOCK \
KEY_OVERRIDE \ KEY_OVERRIDE \
LEADER \ LEADER \
PROGRAMMABLE_BUTTON \ PROGRAMMABLE_BUTTON \
SECURE \
SPACE_CADET \ SPACE_CADET \
SWAP_HANDS \ SWAP_HANDS \
TAP_DANCE \ TAP_DANCE \

View File

@@ -9,7 +9,9 @@ ifneq ($(findstring MKL26Z64, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = KINETIS MCU_FAMILY = KINETIS
MCU_SERIES = KL2x MCU_SERIES = KL2x
@@ -36,7 +38,9 @@ ifneq ($(findstring MK20DX128, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = KINETIS MCU_FAMILY = KINETIS
MCU_SERIES = K20x MCU_SERIES = K20x
@@ -63,7 +67,9 @@ ifneq ($(findstring MK20DX256, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = KINETIS MCU_FAMILY = KINETIS
MCU_SERIES = K20x MCU_SERIES = K20x
@@ -90,7 +96,9 @@ ifneq ($(findstring MK66FX1M0, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = KINETIS MCU_FAMILY = KINETIS
MCU_SERIES = MK66F18 MCU_SERIES = MK66F18
@@ -117,7 +125,9 @@ ifneq ($(findstring STM32F042, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32F0xx MCU_SERIES = STM32F0xx
@@ -157,7 +167,9 @@ ifneq ($(findstring STM32F072, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32F0xx MCU_SERIES = STM32F0xx
@@ -192,7 +204,9 @@ ifneq ($(findstring STM32F103, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32F1xx MCU_SERIES = STM32F1xx
@@ -224,7 +238,9 @@ ifneq ($(findstring STM32F303, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32F3xx MCU_SERIES = STM32F3xx
@@ -259,7 +275,9 @@ ifneq ($(findstring STM32F401, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32F4xx MCU_SERIES = STM32F4xx
@@ -299,7 +317,9 @@ ifneq ($(findstring STM32F405, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32F4xx MCU_SERIES = STM32F4xx
@@ -334,7 +354,9 @@ ifneq ($(findstring STM32F407, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32F4xx MCU_SERIES = STM32F4xx
@@ -369,7 +391,9 @@ ifneq ($(findstring STM32F411, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32F4xx MCU_SERIES = STM32F4xx
@@ -409,7 +433,9 @@ ifneq ($(findstring STM32F446, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32F4xx MCU_SERIES = STM32F4xx
@@ -441,7 +467,9 @@ ifneq ($(findstring STM32G431, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32G4xx MCU_SERIES = STM32G4xx
@@ -476,7 +504,9 @@ ifneq ($(findstring STM32G474, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32G4xx MCU_SERIES = STM32G4xx
@@ -511,7 +541,9 @@ ifneq (,$(filter $(MCU),STM32L432 STM32L442))
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32L4xx MCU_SERIES = STM32L4xx
@@ -548,7 +580,9 @@ ifneq (,$(filter $(MCU),STM32L433 STM32L443))
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32L4xx MCU_SERIES = STM32L4xx
@@ -585,7 +619,9 @@ ifneq (,$(filter $(MCU),STM32L412 STM32L422))
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = STM32 MCU_FAMILY = STM32
MCU_SERIES = STM32L4xx MCU_SERIES = STM32L4xx
@@ -602,7 +638,7 @@ ifneq (,$(filter $(MCU),STM32L412 STM32L422))
# <keyboard_dir>/boards/, or drivers/boards/ # <keyboard_dir>/boards/, or drivers/boards/
BOARD ?= GENERIC_STM32_L412XB BOARD ?= GENERIC_STM32_L412XB
PLATFORM_NAME ?= platform_l432 PLATFORM_NAME ?= platform_l412_l422
USE_FPU ?= yes USE_FPU ?= yes
@@ -622,7 +658,9 @@ ifneq ($(findstring WB32F3G71, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = WB32 MCU_FAMILY = WB32
MCU_SERIES = WB32F3G71xx MCU_SERIES = WB32F3G71xx
@@ -642,7 +680,40 @@ ifneq ($(findstring WB32F3G71, $(MCU)),)
USE_FPU ?= no USE_FPU ?= no
# Bootloader address for WB32 DFU # Bootloader address for WB32 DFU
STM32_BOOTLOADER_ADDRESS ?= 0x1FFFE000 WB32_BOOTLOADER_ADDRESS ?= 0x1FFFE000
endif
ifneq ($(findstring WB32FQ95, $(MCU)),)
# Cortex version
MCU = cortex-m3
# ARM version, CORTEX-M0/M1 are 6, CORTEX-M3/M4/M7 are 7
ARMV = 7
## chip/board settings
# - the next two should match the directories in
# <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = WB32
MCU_SERIES = WB32FQ95xx
# Linker script to use
# - it should exist either in <chibios>/os/common/ports/ARMCMx/compilers/GCC/ld/
# or <keyboard_dir>/ld/
MCU_LDSCRIPT ?= WB32FQ95xB
# Startup code to use
# - it should exist in <chibios>/os/common/startup/ARMCMx/compilers/GCC/mk/
MCU_STARTUP ?= wb32fq95xx
# Board: it should exist either in <chibios>/os/hal/boards/,
# <keyboard_dir>/boards/, or drivers/boards/
BOARD ?= GENERIC_WB32_FQ95XX
USE_FPU ?= no
# Bootloader address for WB32 DFU
WB32_BOOTLOADER_ADDRESS ?= 0x1FFFE000 WB32_BOOTLOADER_ADDRESS ?= 0x1FFFE000
endif endif
@@ -657,7 +728,10 @@ ifneq ($(findstring GD32VF103, $(MCU)),)
## chip/board settings ## chip/board settings
# - the next two should match the directories in # - the next two should match the directories in
# <chibios>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES) # <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_PORT_NAME = GD
MCU_FAMILY = GD32V MCU_FAMILY = GD32V
MCU_SERIES = GD32VF103 MCU_SERIES = GD32VF103

View File

@@ -57,6 +57,7 @@ OTHER_OPTION_NAMES = \
HELIX ZINC \ HELIX ZINC \
AUTOLOG_ENABLE \ AUTOLOG_ENABLE \
DEBUG_ENABLE \ DEBUG_ENABLE \
ENCODER_MAP_ENABLE \
ENCODER_ENABLE_CUSTOM \ ENCODER_ENABLE_CUSTOM \
GERMAN_ENABLE \ GERMAN_ENABLE \
HAPTIC_ENABLE \ HAPTIC_ENABLE \
@@ -79,7 +80,9 @@ OTHER_OPTION_NAMES = \
LED_MIRRORED \ LED_MIRRORED \
RGBLIGHT_FULL_POWER \ RGBLIGHT_FULL_POWER \
LTO_ENABLE \ LTO_ENABLE \
PROGRAMMABLE_BUTTON_ENABLE PROGRAMMABLE_BUTTON_ENABLE \
SECURE_ENABLE \
CAPS_WORD_ENABLE
define NAME_ECHO define NAME_ECHO
@printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)" @printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"

View File

@@ -2,6 +2,7 @@ TEST_LIST = $(sort $(patsubst %/test.mk,%, $(shell find $(ROOT_DIR)tests -type f
FULL_TESTS := $(notdir $(TEST_LIST)) FULL_TESTS := $(notdir $(TEST_LIST))
include $(QUANTUM_PATH)/debounce/tests/testlist.mk include $(QUANTUM_PATH)/debounce/tests/testlist.mk
include $(QUANTUM_PATH)/encoder/tests/testlist.mk
include $(QUANTUM_PATH)/sequencer/tests/testlist.mk include $(QUANTUM_PATH)/sequencer/tests/testlist.mk
include $(PLATFORM_PATH)/test/testlist.mk include $(PLATFORM_PATH)/test/testlist.mk

View File

@@ -0,0 +1,35 @@
{
"development_board": {
"promicro": {
"processor": "atmega32u4",
"bootloader": "caterina",
"pin_compatible": "promicro"
},
"elite_c": {
"processor": "atmega32u4",
"bootloader": "atmel-dfu",
"pin_compatible": "promicro"
},
"proton_c": {
"processor": "STM32F303",
"bootloader": "stm32-dfu",
"board": "QMK_PROTON_C",
"pin_compatible": "promicro"
},
"bluepill": {
"processor": "STM32F103",
"bootloader": "stm32duino",
"board": "STM32_F103_STM32DUINO"
},
"blackpill_f401": {
"processor": "STM32F401",
"bootloader": "stm32-dfu",
"board": "BLACKPILL_STM32_F401"
},
"blackpill_f411": {
"processor": "STM32F411",
"bootloader": "stm32-dfu",
"board": "BLACKPILL_STM32_F411"
}
}
}

View File

@@ -3,7 +3,7 @@
{ {
# Format: # Format:
# <config.h key>: {"info_key": <info.json key>, ["value_type": <value_type>], ["to_json": <true/false>], ["to_c": <true/false>]} # <config.h key>: {"info_key": <info.json key>, ["value_type": <value_type>], ["to_json": <true/false>], ["to_c": <true/false>]}
# value_type: one of "array", "array.int", "bool", "int", "hex", "list", "mapping" # value_type: one of "array", "array.int", "bool", "int", "hex", "list", "mapping", "str", "raw"
# to_json: Default `true`. Set to `false` to exclude this mapping from info.json # to_json: Default `true`. Set to `false` to exclude this mapping from info.json
# to_c: Default `true`. Set to `false` to exclude this mapping from config.h # to_c: Default `true`. Set to `false` to exclude this mapping from config.h
# warn_duplicate: Default `true`. Set to `false` to turn off warning when a value exists in both places # warn_duplicate: Default `true`. Set to `false` to turn off warning when a value exists in both places
@@ -11,14 +11,17 @@
"BACKLIGHT_BREATHING": {"info_key": "backlight.breathing", "value_type": "bool"}, "BACKLIGHT_BREATHING": {"info_key": "backlight.breathing", "value_type": "bool"},
"BREATHING_PERIOD": {"info_key": "backlight.breathing_period", "value_type": "int"}, "BREATHING_PERIOD": {"info_key": "backlight.breathing_period", "value_type": "int"},
"BACKLIGHT_PIN": {"info_key": "backlight.pin"}, "BACKLIGHT_PIN": {"info_key": "backlight.pin"},
"BOTH_SHIFTS_TURNS_ON_CAPS_WORD": {"info_key": "caps_word.both_shifts_turns_on", "value_type": "bool"},
"CAPS_WORD_IDLE_TIMEOUT": {"info_key": "caps_word.idle_timeout", "value_type": "int"},
"COMBO_COUNT": {"info_key": "combo.count", "value_type": "int"}, "COMBO_COUNT": {"info_key": "combo.count", "value_type": "int"},
"COMBO_TERM": {"info_key": "combo.term", "value_type": "int"}, "COMBO_TERM": {"info_key": "combo.term", "value_type": "int"},
"DEBOUNCE": {"info_key": "debounce", "value_type": "int"}, "DEBOUNCE": {"info_key": "debounce", "value_type": "int"},
"DEVICE_VER": {"info_key": "usb.device_ver", "value_type": "hex"}, "DEVICE_VER": {"info_key": "usb.device_ver", "value_type": "hex"},
# TODO: Replace ^^^ with vvv # TODO: Replace ^^^ with vvv
#"DEVICE_VER": {"info_key": "usb.device_version", "value_type": "bcd_version"}, #"DEVICE_VER": {"info_key": "usb.device_version", "value_type": "bcd_version"},
"DESCRIPTION": {"info_key": "keyboard_folder", "to_json": false}, "DESCRIPTION": {"info_key": "keyboard_folder", "value_type": "str", "to_json": false},
"DIODE_DIRECTION": {"info_key": "diode_direction"}, "DIODE_DIRECTION": {"info_key": "diode_direction"},
"DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD": {"info_key": "caps_word.double_tap_shift_turns_on", "value_type": "bool"},
"FORCE_NKRO": {"info_key": "usb.force_nkro", "value_type": "bool"}, "FORCE_NKRO": {"info_key": "usb.force_nkro", "value_type": "bool"},
"DYNAMIC_KEYMAP_EEPROM_MAX_ADDR": {"info_key": "dynamic_keymap.eeprom_max_addr", "value_type": "int"}, "DYNAMIC_KEYMAP_EEPROM_MAX_ADDR": {"info_key": "dynamic_keymap.eeprom_max_addr", "value_type": "int"},
"DYNAMIC_KEYMAP_LAYER_COUNT": {"info_key": "dynamic_keymap.layer_count", "value_type": "int"}, "DYNAMIC_KEYMAP_LAYER_COUNT": {"info_key": "dynamic_keymap.layer_count", "value_type": "int"},
@@ -78,6 +81,9 @@
"QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"}, "QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"},
"QMK_LED": {"info_key": "qmk_lufa_bootloader.led"}, "QMK_LED": {"info_key": "qmk_lufa_bootloader.led"},
"QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"}, "QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"},
"SECURE_UNLOCK_SEQUENCE": {"info_key": "secure.unlock_sequence", "value_type": "array.array.int", "to_json": false},
"SECURE_UNLOCK_TIMEOUT": {"info_key": "secure.unlock_timeout", "value_type": "int"},
"SECURE_IDLE_TIMEOUT": {"info_key": "secure.idle_timeout", "value_type": "int"},
"SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "bool"}, "SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "bool"},
"SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"}, "SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"},
"SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"}, "SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"},

View File

@@ -3,13 +3,14 @@
{ {
# Format: # Format:
# <rules.mk key>: {"info_key": <info.json key>, ["value_type": <value_type>], ["to_json": <true/false>], ["to_c": <true/false>]} # <rules.mk key>: {"info_key": <info.json key>, ["value_type": <value_type>], ["to_json": <true/false>], ["to_c": <true/false>]}
# value_type: one of "array", "array.int", "bool", "int", "list", "hex", "mapping" # value_type: one of "array", "array.int", "bool", "int", "list", "hex", "mapping", "str", "raw"
# to_json: Default `true`. Set to `false` to exclude this mapping from info.json # to_json: Default `true`. Set to `false` to exclude this mapping from info.json
# to_c: Default `true`. Set to `false` to exclude this mapping from rules.mk # to_c: Default `true`. Set to `false` to exclude this mapping from rules.mk
# warn_duplicate: Default `true`. Set to `false` to turn off warning when a value exists in both places # warn_duplicate: Default `true`. Set to `false` to turn off warning when a value exists in both places
"BOARD": {"info_key": "board"}, "BOARD": {"info_key": "board"},
"BOOTLOADER": {"info_key": "bootloader", "warn_duplicate": false}, "BOOTLOADER": {"info_key": "bootloader", "warn_duplicate": false},
"BLUETOOTH": {"info_key": "bluetooth.driver"}, "BLUETOOTH": {"info_key": "bluetooth.driver"},
"CAPS_WORD_ENABLE": {"info_key": "caps_word.enabled", "value_type": "bool"},
"FIRMWARE_FORMAT": {"info_key": "build.firmware_format"}, "FIRMWARE_FORMAT": {"info_key": "build.firmware_format"},
"KEYBOARD_SHARED_EP": {"info_key": "usb.shared_endpoint.keyboard", "value_type": "bool"}, "KEYBOARD_SHARED_EP": {"info_key": "usb.shared_endpoint.keyboard", "value_type": "bool"},
"MOUSE_SHARED_EP": {"info_key": "usb.shared_endpoint.mouse", "value_type": "bool"}, "MOUSE_SHARED_EP": {"info_key": "usb.shared_endpoint.mouse", "value_type": "bool"},
@@ -19,7 +20,9 @@
"MCU": {"info_key": "processor", "warn_duplicate": false}, "MCU": {"info_key": "processor", "warn_duplicate": false},
"MOUSEKEY_ENABLE": {"info_key": "mouse_key.enabled", "value_type": "bool"}, "MOUSEKEY_ENABLE": {"info_key": "mouse_key.enabled", "value_type": "bool"},
"NO_USB_STARTUP_CHECK": {"info_key": "usb.no_startup_check", "value_type": "bool"}, "NO_USB_STARTUP_CHECK": {"info_key": "usb.no_startup_check", "value_type": "bool"},
"PIN_COMPATIBLE": {"info_key": "pin_compatible"},
"SECURE_ENABLE": {"info_key": "secure.enabled", "value_type": "bool"},
"SPLIT_KEYBOARD": {"info_key": "split.enabled", "value_type": "bool"}, "SPLIT_KEYBOARD": {"info_key": "split.enabled", "value_type": "bool"},
"SPLIT_TRANSPORT": {"info_key": "split.transport.protocol", "value_type": "str", "to_c": false}, "SPLIT_TRANSPORT": {"info_key": "split.transport.protocol", "to_c": false},
"WAIT_FOR_USB": {"info_key": "usb.wait_for", "value_type": "bool"} "WAIT_FOR_USB": {"info_key": "usb.wait_for", "value_type": "bool"}
} }

View File

@@ -11,6 +11,9 @@
'2_milk': { '2_milk': {
target: 'spaceman/2_milk' target: 'spaceman/2_milk'
}, },
'absinthe': {
target: 'keyhive/absinthe'
},
'aeboards/constellation': { 'aeboards/constellation': {
target: 'aeboards/constellation/rev1' target: 'aeboards/constellation/rev1'
}, },
@@ -26,6 +29,18 @@
alice: { alice: {
target: 'tgr/alice' target: 'tgr/alice'
}, },
amj40: {
target: 'amjkeyboard/amj40'
},
amj60: {
target: 'amjkeyboard/amj60'
},
amj96: {
target: 'amjkeyboard/amj96'
},
amjpad: {
target: 'amjkeyboard/amjpad'
},
angel17: { angel17: {
target: 'angel17/alpha' target: 'angel17/alpha'
}, },
@@ -33,7 +48,10 @@
target: 'angel64/alpha' target: 'angel64/alpha'
}, },
at101_blackheart: { at101_blackheart: {
target: 'at101_bh' target: 'viktus/at101_bh'
},
at101_bh: {
target: 'viktus/at101_bh'
}, },
'atom47/rev2': { 'atom47/rev2': {
target: 'maartenwut/atom47/rev2' target: 'maartenwut/atom47/rev2'
@@ -158,6 +176,9 @@
ergoinu: { ergoinu: {
target: 'dm9records/ergoinu' target: 'dm9records/ergoinu'
}, },
ergosaurus: {
target: 'keyhive/ergosaurus'
},
'exclusive/e85': { 'exclusive/e85': {
target: 'exclusive/e85/hotswap' target: 'exclusive/e85/hotswap'
}, },
@@ -165,7 +186,13 @@
target: 'gh60/revc' target: 'gh60/revc'
}, },
'gmmk/pro': { 'gmmk/pro': {
target: 'gmmk/pro/ansi' target: 'gmmk/pro/rev1/ansi'
},
'gmmk/pro/ansi': {
target: 'gmmk/pro/rev1/ansi'
},
'gmmk/pro/iso': {
target: 'gmmk/pro/rev1/iso'
}, },
'handwired/ferris': { 'handwired/ferris': {
target: 'ferris/0_1' target: 'ferris/0_1'
@@ -212,6 +239,9 @@
'helix/rev2/under/oled': { 'helix/rev2/under/oled': {
target: 'helix/rev2/under' target: 'helix/rev2/under'
}, },
honeycomb: {
target: 'keyhive/honeycomb'
},
id80: { id80: {
target: 'id80/ansi' target: 'id80/ansi'
}, },
@@ -260,6 +290,9 @@
'kyria': { 'kyria': {
target: 'splitkb/kyria' target: 'splitkb/kyria'
}, },
lattice60: {
target: 'keyhive/lattice60'
},
'lazydesigners/the60': { 'lazydesigners/the60': {
target: 'lazydesigners/the60/rev1' target: 'lazydesigners/the60/rev1'
}, },
@@ -392,7 +425,13 @@
target: 'oddball/v1' target: 'oddball/v1'
}, },
omnikey_blackheart: { omnikey_blackheart: {
target: 'omnikey_bh' target: 'viktus/omnikey_bh'
},
omnikey_bh: {
target: 'viktus/omnikey_bh'
},
opus: {
target: 'keyhive/opus'
}, },
'pabile/p20': { 'pabile/p20': {
target: 'pabile/p20/ver1' target: 'pabile/p20/ver1'
@@ -489,6 +528,12 @@
skog: { skog: {
target: 'percent/skog' target: 'percent/skog'
}, },
smallice: {
target: 'keyhive/smallice'
},
southpole: {
target: 'keyhive/southpole'
},
speedo: { speedo: {
target: 'cozykeys/speedo/v2' target: 'cozykeys/speedo/v2'
}, },
@@ -577,7 +622,10 @@
target: 'ymd75/rev1' target: 'ymd75/rev1'
}, },
z150_blackheart: { z150_blackheart: {
target: 'z150_bh' target: 'viktus/z150_bh'
},
z150_bh:{
target: 'viktus/z150_bh'
}, },
zeal60: { zeal60: {
target: 'wilba_tech/zeal60' target: 'wilba_tech/zeal60'
@@ -904,6 +952,9 @@
meishi2: { meishi2: {
target: 'biacco42/meishi2' target: 'biacco42/meishi2'
}, },
melody96: {
target: 'ymdk/melody96'
},
minidox/rev1: { minidox/rev1: {
target: 'maple_computing/minidox/rev1' target: 'maple_computing/minidox/rev1'
}, },
@@ -919,6 +970,18 @@
montex: { montex: {
target: 'idobao/montex/v1' target: 'idobao/montex/v1'
}, },
mt40: {
target: 'mt/mt40'
},
mt64rgb: {
target: 'mt/mt64rgb'
},
mt84: {
target: 'mt/mt84'
},
mt980: {
target: 'mt/mt980'
},
nafuda: { nafuda: {
target: 'salicylic_acid3/nafuda' target: 'salicylic_acid3/nafuda'
}, },
@@ -943,6 +1006,9 @@
namecard2x4: { namecard2x4: {
target: 'takashiski/namecard2x4' target: 'takashiski/namecard2x4'
}, },
navi10: {
target: 'keyhive/navi10'
},
nebula12: { nebula12: {
target: 'spaceholdings/nebula12' target: 'spaceholdings/nebula12'
}, },
@@ -1144,6 +1210,12 @@
underscore33/rev2: { underscore33/rev2: {
target: 'tominabox1/underscore33/rev2' target: 'tominabox1/underscore33/rev2'
}, },
uno: {
target: 'keyhive/uno'
},
ut472: {
target: 'keyhive/ut472'
},
vn66: { vn66: {
target: 'hnahkb/vn66' target: 'hnahkb/vn66'
}, },
@@ -1153,6 +1225,12 @@
wanten: { wanten: {
target: 'qpockets/wanten' target: 'qpockets/wanten'
}, },
'wheatfield/blocked65': {
target: 'mt/blocked65'
},
'wheatfield/split75': {
target: 'mt/split75'
},
whitefox: { whitefox: {
target: 'input_club/whitefox' target: 'input_club/whitefox'
}, },

View File

@@ -41,8 +41,6 @@
"LAYOUT_2x2uC", "LAYOUT_2x2uC",
"LAYOUT_2x3uC", "LAYOUT_2x3uC",
"LAYOUT_625uC", "LAYOUT_625uC",
"LAYOUT_ANSI_DEFAULT",
"LAYOUT_JP",
"LAYOUT_ortho_3x12_1x2uC", "LAYOUT_ortho_3x12_1x2uC",
"LAYOUT_ortho_4x12_1x2uC", "LAYOUT_ortho_4x12_1x2uC",
"LAYOUT_ortho_4x12_1x2uL", "LAYOUT_ortho_4x12_1x2uL",
@@ -57,8 +55,7 @@
"LAYOUT_planck_1x2uR", "LAYOUT_planck_1x2uR",
"LAYOUT_preonic_1x2uC", "LAYOUT_preonic_1x2uC",
"LAYOUT_preonic_1x2uL", "LAYOUT_preonic_1x2uL",
"LAYOUT_preonic_1x2uR", "LAYOUT_preonic_1x2uR"
"LAYOUT_reviung34_2uL"
] ]
}, },
{ {
@@ -71,6 +68,22 @@
"type": "number", "type": "number",
"min": 0.25 "min": 0.25
}, },
"keyboard": {
"oneOf": [
{
"type": "string",
"enum": [
"converter/numeric_keypad_IIe",
"emptystring/NQG",
"maple_computing/christmas_tree/V2017"
]
},
{
"type": "string",
"pattern": "^[0-9a-z][0-9a-z_/]*$"
}
]
},
"mcu_pin_array": { "mcu_pin_array": {
"type": "array", "type": "array",
"items": {"$ref": "#/mcu_pin"} "items": {"$ref": "#/mcu_pin"}

View File

@@ -5,15 +5,24 @@
"type": "object", "type": "object",
"properties": { "properties": {
"keyboard_name": {"$ref": "qmk.definitions.v1#/text_identifier"}, "keyboard_name": {"$ref": "qmk.definitions.v1#/text_identifier"},
"keyboard_folder": {"$ref": "qmk.definitions.v1#/keyboard"},
"maintainer": {"$ref": "qmk.definitions.v1#/text_identifier"}, "maintainer": {"$ref": "qmk.definitions.v1#/text_identifier"},
"manufacturer": {"$ref": "qmk.definitions.v1#/text_identifier"}, "manufacturer": {"$ref": "qmk.definitions.v1#/text_identifier"},
"url": { "url": {
"type": "string", "type": "string",
"format": "uri" "format": "uri"
}, },
"development_board": {
"type": "string",
"enum": ["promicro", "elite_c", "proton_c", "bluepill", "blackpill_f401", "blackpill_f411"]
},
"pin_compatible": {
"type": "string",
"enum": ["promicro"]
},
"processor": { "processor": {
"type": "string", "type": "string",
"enum": ["cortex-m0", "cortex-m0plus", "cortex-m3", "cortex-m4", "MKL26Z64", "MK20DX128", "MK20DX256", "MK66FX1M0", "STM32F042", "STM32F072", "STM32F103", "STM32F303", "STM32F401", "STM32F405", "STM32F407", "STM32F411", "STM32F446", "STM32G431", "STM32G474", "STM32L412", "STM32L422", "STM32L432", "STM32L433", "STM32L442", "STM32L443", "GD32VF103", "WB32F3G71", "atmega16u2", "atmega32u2", "atmega16u4", "atmega32u4", "at90usb162", "at90usb646", "at90usb647", "at90usb1286", "at90usb1287", "atmega32a", "atmega328p", "atmega328", "attiny85", "unknown"] "enum": ["cortex-m0", "cortex-m0plus", "cortex-m3", "cortex-m4", "MKL26Z64", "MK20DX128", "MK20DX256", "MK66FX1M0", "STM32F042", "STM32F072", "STM32F103", "STM32F303", "STM32F401", "STM32F405", "STM32F407", "STM32F411", "STM32F446", "STM32G431", "STM32G474", "STM32L412", "STM32L422", "STM32L432", "STM32L433", "STM32L442", "STM32L443", "GD32VF103", "WB32F3G71", "WB32FQ95", "atmega16u2", "atmega32u2", "atmega16u4", "atmega32u4", "at90usb162", "at90usb646", "at90usb647", "at90usb1286", "at90usb1287", "atmega32a", "atmega328p", "atmega328", "attiny85", "unknown"]
}, },
"audio": { "audio": {
"type": "object", "type": "object",
@@ -83,6 +92,16 @@
"enum": ["COL2ROW", "ROW2COL"] "enum": ["COL2ROW", "ROW2COL"]
}, },
"debounce": {"$ref": "qmk.definitions.v1#/unsigned_int"}, "debounce": {"$ref": "qmk.definitions.v1#/unsigned_int"},
"caps_word": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {"type": "boolean"},
"both_shifts_turns_on": {"type": "boolean"},
"double_tap_shift_turns_on": {"type": "boolean"},
"idle_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"},
},
},
"combo": { "combo": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -193,6 +212,62 @@
"timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"} "timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"}
} }
}, },
"led_matrix": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"layout": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"matrix": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "number",
"min": 0,
"multipleOf": 1
}
},
"x": {"$ref": "qmk.definitions.v1#/key_unit"},
"y": {"$ref": "qmk.definitions.v1#/key_unit"},
"flags": {"$ref": "qmk.definitions.v1#/unsigned_decimal"}
}
}
}
}
},
"rgb_matrix": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"layout": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"matrix": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "number",
"min": 0,
"multipleOf": 1
}
},
"x": {"$ref": "qmk.definitions.v1#/key_unit"},
"y": {"$ref": "qmk.definitions.v1#/key_unit"},
"flags": {"$ref": "qmk.definitions.v1#/unsigned_decimal"}
}
}
}
}
},
"rgblight": { "rgblight": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
@@ -235,6 +310,30 @@
} }
} }
}, },
"secure": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {"type": "boolean"},
"unlock_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"},
"idle_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"},
"unlock_sequence": {
"type": "array",
"minLength": 1,
"maxLength": 5,
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "number",
"min": 0,
"multipleOf": 1
}
}
}
}
},
"split": { "split": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,

View File

@@ -57,7 +57,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* Mode setting comands */ /* Mode setting comands */
#define FLASH_CMD_DP 0xB9 /* DP (Deep Power Down) */ #define FLASH_CMD_DP 0xB9 /* DP (Deep Power Down) */
#define FLASH_CMD_RDP 0xAB /* RDP (Release form Deep Power Down) */ #define FLASH_CMD_RDP 0xAB /* RDP (Release from Deep Power Down) */
/* Status register */ /* Status register */
#define FLASH_FLAG_WIP 0x01 /* Write in progress bit */ #define FLASH_FLAG_WIP 0x01 /* Write in progress bit */

View File

@@ -74,21 +74,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
The sector size of the FLASH in bytes, as specified in the datasheet. The sector size of the FLASH in bytes, as specified in the datasheet.
*/ */
#ifndef EXTERNAL_FLASH_SECTOR_SIZE #ifndef EXTERNAL_FLASH_SECTOR_SIZE
# define EXTERNAL_FLASH_SECTOR_SIZE (4 * 1024) # define EXTERNAL_FLASH_SECTOR_SIZE (4 * 1024L)
#endif #endif
/* /*
The block size of the FLASH in bytes, as specified in the datasheet. The block size of the FLASH in bytes, as specified in the datasheet.
*/ */
#ifndef EXTERNAL_FLASH_BLOCK_SIZE #ifndef EXTERNAL_FLASH_BLOCK_SIZE
# define EXTERNAL_FLASH_BLOCK_SIZE (64 * 1024) # define EXTERNAL_FLASH_BLOCK_SIZE (64 * 1024L)
#endif #endif
/* /*
The total size of the FLASH in bytes, as specified in the datasheet. The total size of the FLASH in bytes, as specified in the datasheet.
*/ */
#ifndef EXTERNAL_FLASH_SIZE #ifndef EXTERNAL_FLASH_SIZE
# define EXTERNAL_FLASH_SIZE (512 * 1024) # define EXTERNAL_FLASH_SIZE (512 * 1024L)
#endif #endif
/* /*

58
drivers/gpio/sn74x154.c Normal file
View File

@@ -0,0 +1,58 @@
/* Copyright 2022
*
* 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 "sn74x154.h"
#include "gpio.h"
#define ADDRESS_PIN_COUNT 4
#ifndef SN74X154_ADDRESS_PINS
# error sn74x154: no address pins defined!
#endif
static const pin_t address_pins[ADDRESS_PIN_COUNT] = SN74X154_ADDRESS_PINS;
void sn74x154_init(void) {
for (int i = 0; i < ADDRESS_PIN_COUNT; i++) {
setPinOutput(address_pins[i]);
writePinLow(address_pins[i]);
}
#if defined(SN74X154_E0_PIN)
setPinOutput(SN74X154_E0_PIN);
writePinHigh(SN74X154_E0_PIN);
#endif
#if defined(SN74X154_E1_PIN)
setPinOutput(SN74X154_E1_PIN);
writePinHigh(SN74X154_E1_PIN);
#endif
}
void sn74x154_set_enabled(bool enabled) {
#if defined(SN74X154_E0_PIN)
writePin(SN74X154_E0_PIN, !enabled);
#endif
#if defined(SN74X154_E1_PIN)
writePin(SN74X154_E1_PIN, !enabled);
#endif
}
void sn74x154_set_addr(uint8_t address) {
for (int i = 0; i < ADDRESS_PIN_COUNT; i++) {
writePin(address_pins[i], address & (1 << i));
}
}

48
drivers/gpio/sn74x154.h Normal file
View File

@@ -0,0 +1,48 @@
/* Copyright 2022
*
* 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/>.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
/**
* Driver for 74x154 4-to-16 decoder/demultiplexer with inverting outputs
* https://assets.nexperia.com/documents/data-sheet/74HC_HCT154.pdf
*/
/**
* Initialize the address and output enable pins.
*/
void sn74x154_init(void);
/**
* Set the enabled state.
*
* When enabled is true, pulls the E0 and E1 pins low.
*
* \param enabled The enable state to set.
*/
void sn74x154_set_enabled(bool enabled);
/**
* Set the output pin address.
*
* The selected output pin will be pulled low, while the remaining output pins will be high.
*
* \param address The address to set, from 0 to 15.
*/
void sn74x154_set_addr(uint8_t address);

View File

@@ -20,11 +20,22 @@
#include "haptic.h" #include "haptic.h"
#include "gpio.h" #include "gpio.h"
#include "usb_device_state.h" #include "usb_device_state.h"
#include <stdlib.h>
bool solenoid_on = false;
bool solenoid_buzzing = false;
uint16_t solenoid_start = 0;
uint8_t solenoid_dwell = SOLENOID_DEFAULT_DWELL; uint8_t solenoid_dwell = SOLENOID_DEFAULT_DWELL;
static pin_t solenoid_pads[] = SOLENOID_PINS;
#define NUMBER_OF_SOLENOIDS (sizeof(solenoid_pads) / sizeof(pin_t))
bool solenoid_on[NUMBER_OF_SOLENOIDS] = {false};
bool solenoid_buzzing[NUMBER_OF_SOLENOIDS] = {false};
uint16_t solenoid_start[NUMBER_OF_SOLENOIDS] = {0};
#ifdef SOLENOID_PIN_ACTIVE_LOW
# define low true
# define high false
#else
# define low false
# define high true
#endif
static bool solenoid_active_state[NUMBER_OF_SOLENOIDS];
extern haptic_config_t haptic_config; extern haptic_config_t haptic_config;
@@ -36,7 +47,7 @@ void solenoid_buzz_off(void) {
haptic_set_buzz(0); haptic_set_buzz(0);
} }
void solenoid_set_buzz(int buzz) { void solenoid_set_buzz(uint8_t buzz) {
haptic_set_buzz(buzz); haptic_set_buzz(buzz);
} }
@@ -44,59 +55,121 @@ void solenoid_set_dwell(uint8_t dwell) {
solenoid_dwell = dwell; solenoid_dwell = dwell;
} }
void solenoid_stop(void) { /**
SOLENOID_PIN_WRITE_INACTIVE(); * @brief Stops a specific solenoid
solenoid_on = false; *
solenoid_buzzing = false; * @param index select which solenoid to check/stop
*/
void solenoid_stop(uint8_t index) {
writePin(solenoid_pads[index], !solenoid_active_state[index]);
solenoid_on[index] = false;
solenoid_buzzing[index] = false;
} }
void solenoid_fire(void) { /**
if (!haptic_config.buzz && solenoid_on) return; * @brief Fires off a specific solenoid
if (haptic_config.buzz && solenoid_buzzing) return; *
* @param index Selects which solenoid to fire
*/
void solenoid_fire(uint8_t index) {
if (!haptic_config.buzz && solenoid_on[index]) return;
if (haptic_config.buzz && solenoid_buzzing[index]) return;
solenoid_on = true; solenoid_on[index] = true;
solenoid_buzzing = true; solenoid_buzzing[index] = true;
solenoid_start = timer_read(); solenoid_start[index] = timer_read();
SOLENOID_PIN_WRITE_ACTIVE(); writePin(solenoid_pads[index], solenoid_active_state[index]);
} }
/**
* @brief Handles selecting a non-active solenoid, and firing it.
*
*/
void solenoid_fire_handler(void) {
#ifndef SOLENOID_RANDOM_FIRE
if (NUMBER_OF_SOLENOIDS > 1) {
uint8_t i = rand() % NUMBER_OF_SOLENOIDS;
if (!solenoid_on[i]) {
solenoid_fire(i);
}
} else {
solenoid_fire(0);
}
#else
for (uint8_t i = 0; i < NUMBER_OF_SOLENOIDS; i++) {
if (!solenoid_on[i]) {
solenoid_fire(i);
break;
}
}
#endif
}
/**
* @brief Checks active solenoid to stop them, and to handle buzz mode
*
*/
void solenoid_check(void) { void solenoid_check(void) {
uint16_t elapsed = 0; uint16_t elapsed[NUMBER_OF_SOLENOIDS] = {0};
if (!solenoid_on) return; for (uint8_t i = 0; i < NUMBER_OF_SOLENOIDS; i++) {
if (!solenoid_on[i]) continue;
elapsed = timer_elapsed(solenoid_start); elapsed[i] = timer_elapsed(solenoid_start[i]);
// Check if it's time to finish this solenoid click cycle // Check if it's time to finish this solenoid click cycle
if (elapsed > solenoid_dwell) { if (elapsed[i] > solenoid_dwell) {
solenoid_stop(); solenoid_stop(i);
return; continue;
} }
// Check whether to buzz the solenoid on and off // Check whether to buzz the solenoid on and off
if (haptic_config.buzz) { if (haptic_config.buzz) {
if ((elapsed % (SOLENOID_BUZZ_ACTUATED + SOLENOID_BUZZ_NONACTUATED)) < SOLENOID_BUZZ_ACTUATED) { if ((elapsed[i] % (SOLENOID_BUZZ_ACTUATED + SOLENOID_BUZZ_NONACTUATED)) < SOLENOID_BUZZ_ACTUATED) {
if (!solenoid_buzzing) { if (!solenoid_buzzing[i]) {
solenoid_buzzing = true; solenoid_buzzing[i] = true;
SOLENOID_PIN_WRITE_ACTIVE(); writePin(solenoid_pads[i], solenoid_active_state[i]);
} }
} else { } else {
if (solenoid_buzzing) { if (solenoid_buzzing[i]) {
solenoid_buzzing = false; solenoid_buzzing[i] = false;
SOLENOID_PIN_WRITE_INACTIVE(); writePin(solenoid_pads[i], !solenoid_active_state[i]);
}
} }
} }
} }
} }
/**
* @brief Initial configuration for solenoids
*
*/
void solenoid_setup(void) { void solenoid_setup(void) {
SOLENOID_PIN_WRITE_INACTIVE(); #ifdef SOLENOID_PINS_ACTIVE_STATE
setPinOutput(SOLENOID_PIN); bool state_temp[] = SOLENOID_PINS_ACTIVE_STATE;
uint8_t bound_check = (sizeof(state_temp) / sizeof(bool));
#endif
for (uint8_t i = 0; i < NUMBER_OF_SOLENOIDS; i++) {
#ifdef SOLENOID_PINS_ACTIVE_STATE
solenoid_active_state[i] = (bound_check - i) ? state_temp[i] : high;
#else
solenoid_active_state[i] = high;
#endif
writePin(solenoid_pads[i], !solenoid_active_state[i]);
setPinOutput(solenoid_pads[i]);
if ((!HAPTIC_OFF_IN_LOW_POWER) || (usb_device_state == USB_DEVICE_STATE_CONFIGURED)) { if ((!HAPTIC_OFF_IN_LOW_POWER) || (usb_device_state == USB_DEVICE_STATE_CONFIGURED)) {
solenoid_fire(); solenoid_fire(i);
}
} }
} }
/**
* @brief stops solenoids prior to device reboot, to prevent them from being locked on
*
*/
void solenoid_shutdown(void) { void solenoid_shutdown(void) {
SOLENOID_PIN_WRITE_INACTIVE(); for (uint8_t i = 0; i < NUMBER_OF_SOLENOIDS; i++) {
writePin(solenoid_pads[i], !solenoid_active_state[i]);
}
} }

View File

@@ -45,26 +45,24 @@
# define SOLENOID_BUZZ_NONACTUATED SOLENOID_MIN_DWELL # define SOLENOID_BUZZ_NONACTUATED SOLENOID_MIN_DWELL
#endif #endif
#ifndef SOLENOID_PIN #ifndef SOLENOID_PINS
# error SOLENOID_PIN not defined # ifdef SOLENOID_PIN
#endif # define SOLENOID_PINS \
{ SOLENOID_PIN }
#ifdef SOLENOID_PIN_ACTIVE_LOW
# define SOLENOID_PIN_WRITE_ACTIVE() writePinLow(SOLENOID_PIN)
# define SOLENOID_PIN_WRITE_INACTIVE() writePinHigh(SOLENOID_PIN)
# else # else
# define SOLENOID_PIN_WRITE_ACTIVE() writePinHigh(SOLENOID_PIN) # error SOLENOID_PINS array not defined
# define SOLENOID_PIN_WRITE_INACTIVE() writePinLow(SOLENOID_PIN) # endif
#endif #endif
void solenoid_buzz_on(void); void solenoidbuzz_on(void);
void solenoid_buzz_off(void); void solenoid_buzz_off(void);
void solenoid_set_buzz(int buzz); void solenoid_set_buzz(uint8_t buzz);
void solenoid_set_dwell(uint8_t dwell); void solenoid_set_dwell(uint8_t dwell);
void solenoid_stop(void); void solenoid_stop(uint8_t index);
void solenoid_fire(void); void solenoid_fire(uint8_t index);
void solenoid_fire_handler(void);
void solenoid_check(void); void solenoid_check(void);

284
drivers/lcd/hd44780.c Normal file
View File

@@ -0,0 +1,284 @@
/*
Copyright 2022
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 "hd44780.h"
#include "gpio.h"
#include "progmem.h"
#include "wait.h"
#ifndef HD44780_DATA_PINS
# error hd44780: no data pins defined!
#endif
#ifndef HD44780_RS_PIN
# error hd44780: no RS pin defined!
#endif
#ifndef HD44780_RW_PIN
# error hd44780: no R/W pin defined!
#endif
#ifndef HD44780_E_PIN
# error hd44780: no E pin defined!
#endif
static const pin_t data_pins[4] = HD44780_DATA_PINS;
#ifndef HD44780_DISPLAY_COLS
# define HD44780_DISPLAY_COLS 16
#endif
#ifndef HD44780_DISPLAY_LINES
# define HD44780_DISPLAY_LINES 2
#endif
#ifndef HD44780_DDRAM_LINE0_ADDR
# define HD44780_DDRAM_LINE0_ADDR 0x00
#endif
#ifndef HD44780_DDRAM_LINE1_ADDR
# define HD44780_DDRAM_LINE1_ADDR 0x40
#endif
#define HD44780_INIT_DELAY_MS 16
#define HD44780_ENABLE_DELAY_US 1
static void hd44780_latch(void) {
writePinHigh(HD44780_E_PIN);
wait_us(HD44780_ENABLE_DELAY_US);
writePinLow(HD44780_E_PIN);
}
void hd44780_write(uint8_t data, bool isData) {
writePin(HD44780_RS_PIN, isData);
writePinLow(HD44780_RW_PIN);
for (int i = 0; i < 4; i++) {
setPinOutput(data_pins[i]);
}
// Write high nibble
for (int i = 0; i < 4; i++) {
writePin(data_pins[i], (data >> 4) & (1 << i));
}
hd44780_latch();
// Write low nibble
for (int i = 0; i < 4; i++) {
writePin(data_pins[i], data & (1 << i));
}
hd44780_latch();
for (int i = 0; i < 4; i++) {
writePinHigh(data_pins[i]);
}
}
uint8_t hd44780_read(bool isData) {
uint8_t data = 0;
writePin(HD44780_RS_PIN, isData);
writePinHigh(HD44780_RW_PIN);
for (int i = 0; i < 4; i++) {
setPinInput(data_pins[i]);
}
writePinHigh(HD44780_E_PIN);
wait_us(HD44780_ENABLE_DELAY_US);
// Read high nibble
for (int i = 0; i < 4; i++) {
data |= (readPin(data_pins[i]) << i);
}
data <<= 4;
writePinLow(HD44780_E_PIN);
wait_us(HD44780_ENABLE_DELAY_US);
writePinHigh(HD44780_E_PIN);
wait_us(HD44780_ENABLE_DELAY_US);
// Read low nibble
for (int i = 0; i < 4; i++) {
data |= (readPin(data_pins[i]) << i);
}
writePinLow(HD44780_E_PIN);
return data;
}
bool hd44780_busy(void) {
return hd44780_read(false) & HD44780_BUSY_FLAG;
}
void hd44780_command(uint8_t command) {
while (hd44780_busy())
;
hd44780_write(command, false);
}
void hd44780_data(uint8_t data) {
while (hd44780_busy())
;
hd44780_write(data, true);
}
void hd44780_clear(void) {
hd44780_command(HD44780_CMD_CLEAR_DISPLAY);
}
void hd44780_home(void) {
hd44780_command(HD44780_CMD_RETURN_HOME);
}
void hd44780_on(bool cursor, bool blink) {
if (cursor) {
if (blink) {
hd44780_command(HD44780_CMD_DISPLAY | HD44780_DISPLAY_ON | HD44780_DISPLAY_CURSOR | HD44780_DISPLAY_BLINK);
} else {
hd44780_command(HD44780_CMD_DISPLAY | HD44780_DISPLAY_ON | HD44780_DISPLAY_CURSOR);
}
} else {
hd44780_command(HD44780_CMD_DISPLAY | HD44780_DISPLAY_ON);
}
}
void hd44780_off() {
hd44780_command(HD44780_CMD_DISPLAY);
}
void hd44780_set_cgram_address(uint8_t address) {
hd44780_command(HD44780_CMD_SET_CGRAM_ADDRESS + (address & 0x3F));
}
void hd44780_set_ddram_address(uint8_t address) {
hd44780_command(HD44780_CMD_SET_DDRAM_ADDRESS + (address & 0x7F));
}
void hd44780_init(bool cursor, bool blink) {
setPinOutput(HD44780_RS_PIN);
setPinOutput(HD44780_RW_PIN);
setPinOutput(HD44780_E_PIN);
for (int i = 0; i < 4; i++) {
setPinOutput(data_pins[i]);
}
wait_ms(HD44780_INIT_DELAY_MS);
// Manually configure for 4-bit mode - can't use hd44780_command() yet
// HD44780U datasheet, Fig. 24 (p46)
writePinHigh(data_pins[0]); // Function set
writePinHigh(data_pins[1]); // DL = 1
hd44780_latch();
wait_ms(5);
// Send again
hd44780_latch();
wait_us(64);
// And again (?)
hd44780_latch();
wait_us(64);
writePinLow(data_pins[0]); // DL = 0
hd44780_latch();
wait_us(64);
#if HD44780_DISPLAY_LINES == 1
hd44780_command(HD44780_CMD_FUNCTION); // 4 bit, 1 line, 5x8 dots
#else
hd44780_command(HD44780_CMD_FUNCTION | HD44780_FUNCTION_2_LINES); // 4 bit, 2 lines, 5x8 dots
#endif
hd44780_on(cursor, blink);
hd44780_clear();
hd44780_home();
hd44780_command(HD44780_CMD_ENTRY_MODE | HD44780_ENTRY_MODE_INC);
}
void hd44780_set_cursor(uint8_t col, uint8_t line) {
register uint8_t address = col;
#if HD44780_DISPLAY_LINES == 1
address += HD44780_DDRAM_LINE0_ADDR;
#elif HD44780_DISPLAY_LINES == 2
if (line == 0) {
address += HD44780_DDRAM_LINE0_ADDR;
} else {
address += HD44780_DDRAM_LINE1_ADDR;
}
#endif
hd44780_set_ddram_address(address);
}
void hd44780_define_char(uint8_t index, uint8_t *data) {
hd44780_set_cgram_address((index & 0x7) << 3);
for (uint8_t i = 0; i < 8; i++) {
hd44780_data(data[i]);
}
}
void hd44780_putc(char c) {
while (hd44780_busy())
;
uint8_t current_position = hd44780_read(false);
if (c == '\n') {
hd44780_set_cursor(0, current_position < HD44780_DDRAM_LINE1_ADDR ? 1 : 0);
} else {
#if defined(HD44780_WRAP_LINES)
# if HD44780_DISPLAY_LINES == 1
if (current_position == HD44780_DDRAM_LINE0_ADDR + HD44780_DISPLAY_COLS) {
// Go to start of line
hd44780_set_cursor(0, 0);
}
# elif HD44780_DISPLAY_LINES == 2
if (current_position == HD44780_DDRAM_LINE0_ADDR + HD44780_DISPLAY_COLS) {
// Go to start of second line
hd44780_set_cursor(0, 1);
} else if (current_position == HD44780_DDRAM_LINE1_ADDR + HD44780_DISPLAY_COLS) {
// Go to start of first line
hd44780_set_cursor(0, 0);
}
# endif
#endif
hd44780_data(c);
}
}
void hd44780_puts(const char *s) {
register char c;
while ((c = *s++)) {
hd44780_putc(c);
}
}
#if defined(__AVR__)
void hd44780_define_char_P(uint8_t index, const uint8_t *data) {
hd44780_set_cgram_address(index << 3);
for (uint8_t i = 0; i < 8; i++) {
hd44780_data(pgm_read_byte(data++));
}
}
void hd44780_puts_P(const char *s) {
register char c;
while ((c = pgm_read_byte(s++))) {
hd44780_putc(c);
}
}
#endif

220
drivers/lcd/hd44780.h Normal file
View File

@@ -0,0 +1,220 @@
/*
Copyright 2022
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/>.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
/**
* \defgroup hd44780
*
* HD44780 Character LCD Driver
* \{
*/
/*
* HD44780 instructions
* https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
* Table 6 (p24)
*/
// Clear display
#define HD44780_CMD_CLEAR_DISPLAY 0x01
// Return home
#define HD44780_CMD_RETURN_HOME 0x02
// Entry mode set
#define HD44780_CMD_ENTRY_MODE 0x04
#define HD44780_ENTRY_MODE_INC 0x02 // I/D
#define HD44780_ENTRY_MODE_SHIFT 0x01 // S
// Display on/off control
#define HD44780_CMD_DISPLAY 0x08
#define HD44780_DISPLAY_ON 0x04 // D
#define HD44780_DISPLAY_CURSOR 0x02 // C
#define HD44780_DISPLAY_BLINK 0x01 // B
// Cursor or display shift
#define HD44780_CMD_MOVE 0x10
#define HD44780_MOVE_DISPLAY 0x08 // S/C
#define HD44780_MOVE_RIGHT 0x04 // R/L
// Function set
#define HD44780_CMD_FUNCTION 0x20
#define HD44780_FUNCTION_8_BIT 0x10 // DL
#define HD44780_FUNCTION_2_LINES 0x08 // N
#define HD44780_FUNCTION_5X10_DOTS 0x04 // F
// Set CGRAM address
#define HD44780_CMD_SET_CGRAM_ADDRESS 0x40
// Set DDRAM address
#define HD44780_CMD_SET_DDRAM_ADDRESS 0x80
// Bitmask for busy flag when reading
#define HD44780_BUSY_FLAG 0x80
/**
* \brief Write a byte to the display.
*
* \param data The byte to send to the display.
* \param isData Whether the byte is an instruction or character data.
*/
void hd44780_write(uint8_t data, bool isData);
/**
* \brief Read a byte from the display.
*
* \param isData Whether to read the current cursor position, or the character at the cursor.
*
* \return If `isData` is `true`, the returned byte will be the character at the current DDRAM address. Otherwise, it will be the current DDRAM address and the busy flag.
*/
uint8_t hd44780_read(bool isData);
/**
* \brief Indicates whether the display is currently processing, and cannot accept instructions.
*
* \return `true` if the display is busy.
*/
bool hd44780_busy(void);
/**
* \brief Send a command to the display. Refer to the datasheet for the valid commands.
*
* This function waits for the display to clear the busy flag before sending the command.
*
* \param command The command to send.
*/
void hd44780_command(uint8_t command);
/**
* \brief Send a byte of data to the display.
*
* This function waits for the display to clear the busy flag before sending the data.
*
* \param data The byte of data to send.
*/
void hd44780_data(uint8_t data);
/**
* \brief Clear the display.
*
* This function is called on init.
*/
void hd44780_clear(void);
/**
* \brief Move the cursor to the home position.
*
* This function is called on init.
*/
void hd44780_home(void);
/**
* \brief Turn the display on, and/or set the cursor position.
*
* This function is called on init.
*
* \param cursor Whether to show the cursor.
* \param blink Whether to blink the cursor, if shown.
*/
void hd44780_on(bool cursor, bool blink);
/**
* \brief Turn the display off.
*/
void hd44780_off(void);
/**
* \brief Set the CGRAM address.
*
* This function is used when defining custom characters.
*
* \param address The CGRAM address to move to, from `0x00` to `0x3F`.
*/
void hd44780_set_cgram_address(uint8_t address);
/**
* \brief Set the DDRAM address.
*
* This function is used when printing characters to the display, and setting the cursor.
*
* \param address The DDRAM address to move to, from `0x00` to `0x7F`.
*/
void hd44780_set_ddram_address(uint8_t address);
/**
* \brief Initialize the display.
*
* This function should be called only once, before any of the other functions can be called.
*
* \param cursor Whether to show the cursor.
* \param blink Whether to blink the cursor, if shown.
*/
void hd44780_init(bool cursor, bool blink);
/**
* \brief Move the cursor to the specified position on the display.
*
* \param col The column number to move to, from 0 to 15 on 16x2 displays.
* \param line The line number to move to, either 0 or 1 on 16x2 displays.
*/
void hd44780_set_cursor(uint8_t col, uint8_t line);
/**
* \brief Define a custom character.
*
* \param index The index of the custom character to define, from 0 to 7.
* \param data An array of 8 bytes containing the 5-bit row data of the character, where the first byte is the topmost row, and the least significant bit of each byte is the rightmost column.
*/
void hd44780_define_char(uint8_t index, uint8_t *data);
/**
* \brief Print a character to the display. The newline character will move the cursor to the start of the next line.
*
* The exact character shown may depend on the ROM code of your particular display - refer to the datasheet for the full character set.
*
* \param c The character to print.
*/
void hd44780_putc(char c);
/**
* \brief Print a string of characters to the display.
*
* \param s The string to print.
*/
void hd44780_puts(const char *s);
#if defined(__AVR__) || defined(__DOXYGEN__)
/**
* \brief Define a custom character from PROGMEM.
*
* On ARM devices, this function is simply an alias of hd44780_define_char().
*
* \param index The index of the custom character to define, from 0 to 7.
* \param data A PROGMEM array of 8 bytes containing the 5-bit row data of the character, where the first byte is the topmost row, and the least significant bit of each byte is the rightmost column.
*/
void hd44780_define_char_P(uint8_t index, const uint8_t *data);
/**
* \brief Print a string of characters from PROGMEM to the display.
*
* On ARM devices, this function is simply an alias of hd44780_puts().
*
* \param s The PROGMEM string to print.
*/
void hd44780_puts_P(const char *s);
#else
# define hd44780_define_char_P(index, data) hd44780_define_char(index, data)
# define hd44780_puts_P(s) hd44780_puts(s)
#endif
/** \} */

View File

@@ -57,6 +57,10 @@
# define ISSI_PERSISTENCE 0 # define ISSI_PERSISTENCE 0
#endif #endif
#ifndef ISSI_PWM_FREQUENCY
# define ISSI_PWM_FREQUENCY 0b000 // PFS - IS31FL3737B only
#endif
#ifndef ISSI_SWPULLUP #ifndef ISSI_SWPULLUP
# define ISSI_SWPULLUP PUR_0R # define ISSI_SWPULLUP PUR_0R
#endif #endif
@@ -159,7 +163,7 @@ void IS31FL3737_init(uint8_t addr) {
// Set global current to maximum. // Set global current to maximum.
IS31FL3737_write_register(addr, ISSI_REG_GLOBALCURRENT, 0xFF); IS31FL3737_write_register(addr, ISSI_REG_GLOBALCURRENT, 0xFF);
// Disable software shutdown. // Disable software shutdown.
IS31FL3737_write_register(addr, ISSI_REG_CONFIGURATION, 0x01); IS31FL3737_write_register(addr, ISSI_REG_CONFIGURATION, ((ISSI_PWM_FREQUENCY & 0b111) << 3) | 0x01);
// Wait 10ms to ensure the device has woken up. // Wait 10ms to ensure the device has woken up.
wait_ms(10); wait_ms(10);

View File

@@ -0,0 +1,137 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef QUANTUM_PAINTER_SPI_ENABLE
# include "spi_master.h"
# include "qp_comms_spi.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Base SPI support
bool qp_comms_spi_init(painter_device_t device) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
struct qp_comms_spi_config_t *comms_config = (struct qp_comms_spi_config_t *)driver->comms_config;
// Initialize the SPI peripheral
spi_init();
// Set up CS as output high
setPinOutput(comms_config->chip_select_pin);
writePinHigh(comms_config->chip_select_pin);
return true;
}
bool qp_comms_spi_start(painter_device_t device) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
struct qp_comms_spi_config_t *comms_config = (struct qp_comms_spi_config_t *)driver->comms_config;
return spi_start(comms_config->chip_select_pin, comms_config->lsb_first, comms_config->mode, comms_config->divisor);
}
uint32_t qp_comms_spi_send_data(painter_device_t device, const void *data, uint32_t byte_count) {
uint32_t bytes_remaining = byte_count;
const uint8_t *p = (const uint8_t *)data;
while (bytes_remaining > 0) {
uint32_t bytes_this_loop = bytes_remaining < 1024 ? bytes_remaining : 1024;
spi_transmit(p, bytes_this_loop);
p += bytes_this_loop;
bytes_remaining -= bytes_this_loop;
}
return byte_count - bytes_remaining;
}
void qp_comms_spi_stop(painter_device_t device) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
struct qp_comms_spi_config_t *comms_config = (struct qp_comms_spi_config_t *)driver->comms_config;
spi_stop();
writePinHigh(comms_config->chip_select_pin);
}
const struct painter_comms_vtable_t spi_comms_vtable = {
.comms_init = qp_comms_spi_init,
.comms_start = qp_comms_spi_start,
.comms_send = qp_comms_spi_send_data,
.comms_stop = qp_comms_spi_stop,
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SPI with D/C and RST pins
# ifdef QUANTUM_PAINTER_SPI_DC_RESET_ENABLE
bool qp_comms_spi_dc_reset_init(painter_device_t device) {
if (!qp_comms_spi_init(device)) {
return false;
}
struct painter_driver_t * driver = (struct painter_driver_t *)device;
struct qp_comms_spi_dc_reset_config_t *comms_config = (struct qp_comms_spi_dc_reset_config_t *)driver->comms_config;
// Set up D/C as output low, if specified
if (comms_config->dc_pin != NO_PIN) {
setPinOutput(comms_config->dc_pin);
writePinLow(comms_config->dc_pin);
}
// Set up RST as output, if specified, performing a reset in the process
if (comms_config->reset_pin != NO_PIN) {
setPinOutput(comms_config->reset_pin);
writePinLow(comms_config->reset_pin);
wait_ms(20);
writePinHigh(comms_config->reset_pin);
wait_ms(20);
}
return true;
}
uint32_t qp_comms_spi_dc_reset_send_data(painter_device_t device, const void *data, uint32_t byte_count) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
struct qp_comms_spi_dc_reset_config_t *comms_config = (struct qp_comms_spi_dc_reset_config_t *)driver->comms_config;
writePinHigh(comms_config->dc_pin);
return qp_comms_spi_send_data(device, data, byte_count);
}
void qp_comms_spi_dc_reset_send_command(painter_device_t device, uint8_t cmd) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
struct qp_comms_spi_dc_reset_config_t *comms_config = (struct qp_comms_spi_dc_reset_config_t *)driver->comms_config;
writePinLow(comms_config->dc_pin);
spi_write(cmd);
}
void qp_comms_spi_dc_reset_bulk_command_sequence(painter_device_t device, const uint8_t *sequence, size_t sequence_len) {
for (size_t i = 0; i < sequence_len;) {
uint8_t command = sequence[i];
uint8_t delay = sequence[i + 1];
uint8_t num_bytes = sequence[i + 2];
qp_comms_spi_dc_reset_send_command(device, command);
if (num_bytes > 0) {
qp_comms_spi_dc_reset_send_data(device, &sequence[i + 3], num_bytes);
}
if (delay > 0) {
wait_ms(delay);
}
i += (3 + num_bytes);
}
}
const struct painter_comms_with_command_vtable_t spi_comms_with_dc_vtable = {
.base =
{
.comms_init = qp_comms_spi_dc_reset_init,
.comms_start = qp_comms_spi_start,
.comms_send = qp_comms_spi_dc_reset_send_data,
.comms_stop = qp_comms_spi_stop,
},
.send_command = qp_comms_spi_dc_reset_send_command,
.bulk_command_sequence = qp_comms_spi_dc_reset_bulk_command_sequence,
};
# endif // QUANTUM_PAINTER_SPI_DC_RESET_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endif // QUANTUM_PAINTER_SPI_ENABLE

View File

@@ -0,0 +1,51 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifdef QUANTUM_PAINTER_SPI_ENABLE
# include <stdint.h>
# include "gpio.h"
# include "qp_internal.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Base SPI support
struct qp_comms_spi_config_t {
pin_t chip_select_pin;
uint16_t divisor;
bool lsb_first;
int8_t mode;
};
bool qp_comms_spi_init(painter_device_t device);
bool qp_comms_spi_start(painter_device_t device);
uint32_t qp_comms_spi_send_data(painter_device_t device, const void* data, uint32_t byte_count);
void qp_comms_spi_stop(painter_device_t device);
extern const struct painter_comms_vtable_t spi_comms_vtable;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SPI with D/C and RST pins
# ifdef QUANTUM_PAINTER_SPI_DC_RESET_ENABLE
struct qp_comms_spi_dc_reset_config_t {
struct qp_comms_spi_config_t spi_config;
pin_t dc_pin;
pin_t reset_pin;
};
void qp_comms_spi_dc_reset_send_command(painter_device_t device, uint8_t cmd);
uint32_t qp_comms_spi_dc_reset_send_data(painter_device_t device, const void* data, uint32_t byte_count);
void qp_comms_spi_dc_reset_bulk_command_sequence(painter_device_t device, const uint8_t* sequence, size_t sequence_len);
extern const struct painter_comms_with_command_vtable_t spi_comms_with_dc_vtable;
# endif // QUANTUM_PAINTER_SPI_DC_RESET_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endif // QUANTUM_PAINTER_SPI_ENABLE

View File

@@ -0,0 +1,150 @@
// Copyright 2021 Paul Cotter (@gr1mr3aver)
// SPDX-License-Identifier: GPL-2.0-or-later
#include <wait.h>
#include "qp_internal.h"
#include "qp_comms.h"
#include "qp_gc9a01.h"
#include "qp_gc9a01_opcodes.h"
#include "qp_tft_panel.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Driver storage
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
tft_panel_dc_reset_painter_device_t gc9a01_drivers[GC9A01_NUM_DEVICES] = {0};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialization
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool qp_gc9a01_init(painter_device_t device, painter_rotation_t rotation) {
// A lot of these "unknown" opcodes are sourced from other OSS projects and are seemingly required for this display to function.
// clang-format off
const uint8_t gc9a01_init_sequence[] = {
// Command, Delay, N, Data[N]
GC9A01_SET_INTER_REG_ENABLE2, 0, 0,
0xEB, 0, 1, 0x14,
GC9A01_SET_INTER_REG_ENABLE1, 0, 0,
GC9A01_SET_INTER_REG_ENABLE2, 0, 0,
0xEB, 0, 1, 0x14,
0x84, 0, 1, 0x40,
0x85, 0, 1, 0xFF,
0x86, 0, 1, 0xFF,
0x87, 0, 1, 0xFF,
0x88, 0, 1, 0x0A,
0x89, 0, 1, 0x21,
0x8a, 0, 1, 0x00,
0x8b, 0, 1, 0x80,
0x8c, 0, 1, 0x01,
0x8d, 0, 1, 0x01,
0x8e, 0, 1, 0xFF,
0x8f, 0, 1, 0xFF,
GC9A01_SET_FUNCTION_CTL, 0, 2, 0x00, 0x20,
GC9A01_SET_PIX_FMT, 0, 1, 0x55,
0x90, 0, 4, 0x08, 0x08, 0x08, 0x08,
0xBD, 0, 1, 0x06,
0xBC, 0, 1, 0x00,
0xFF, 0, 3, 0x60, 0x01, 0x04,
GC9A01_SET_POWER_CTL_2, 0, 1, 0x13,
GC9A01_SET_POWER_CTL_3, 0, 1, 0x13,
GC9A01_SET_POWER_CTL_4, 0, 1, 0x22,
0xBE, 0, 1, 0x11,
0xE1, 0, 2, 0x10, 0x0E,
0xDF, 0, 3, 0x21, 0x0C, 0x02,
GC9A01_SET_GAMMA1, 0, 6, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A,
GC9A01_SET_GAMMA2, 0, 6, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F,
GC9A01_SET_GAMMA3, 0, 6, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A,
GC9A01_SET_GAMMA4, 0, 6, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F,
0xED, 0, 2, 0x1B, 0x0B,
0xAE, 0, 1, 0x77,
0xCD, 0, 1, 0x63,
0x70, 0, 9, 0x07, 0x07, 0x04, 0x0E, 0x0F, 0x09, 0x07, 0x08, 0x03,
GC9A01_SET_FRAME_RATE, 0, 1, 0x34,
0x62, 0, 12, 0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, 0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70,
0x63, 0, 12, 0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, 0x18, 0x13, 0x71, 0xF3, 0x70, 0x70,
0x64, 0, 7, 0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07,
0x66, 0, 10, 0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00,
0x67, 0, 10, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98,
0x74, 0, 7, 0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00,
0x98, 0, 2, 0x3E, 0x07,
GC9A01_CMD_TEARING_OFF, 0, 0,
GC9A01_CMD_INVERT_OFF, 0, 0,
GC9A01_CMD_SLEEP_OFF, 120, 0,
GC9A01_CMD_DISPLAY_ON, 20, 0
};
// clang-format on
// clang-format on
qp_comms_bulk_command_sequence(device, gc9a01_init_sequence, sizeof(gc9a01_init_sequence));
// Configure the rotation (i.e. the ordering and direction of memory writes in GRAM)
const uint8_t madctl[] = {
[QP_ROTATION_0] = GC9A01_MADCTL_BGR,
[QP_ROTATION_90] = GC9A01_MADCTL_BGR | GC9A01_MADCTL_MX | GC9A01_MADCTL_MV,
[QP_ROTATION_180] = GC9A01_MADCTL_BGR | GC9A01_MADCTL_MX | GC9A01_MADCTL_MY,
[QP_ROTATION_270] = GC9A01_MADCTL_BGR | GC9A01_MADCTL_MV | GC9A01_MADCTL_MY,
};
qp_comms_command_databyte(device, GC9A01_SET_MEM_ACS_CTL, madctl[rotation]);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Driver vtable
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const struct tft_panel_dc_reset_painter_driver_vtable_t gc9a01_driver_vtable = {
.base =
{
.init = qp_gc9a01_init,
.power = qp_tft_panel_power,
.clear = qp_tft_panel_clear,
.flush = qp_tft_panel_flush,
.pixdata = qp_tft_panel_pixdata,
.viewport = qp_tft_panel_viewport,
.palette_convert = qp_tft_panel_palette_convert,
.append_pixels = qp_tft_panel_append_pixels,
},
.rgb888_to_native16bit = qp_rgb888_to_rgb565_swapped,
.num_window_bytes = 2,
.swap_window_coords = false,
.opcodes =
{
.display_on = GC9A01_CMD_DISPLAY_ON,
.display_off = GC9A01_CMD_DISPLAY_OFF,
.set_column_address = GC9A01_SET_COL_ADDR,
.set_row_address = GC9A01_SET_PAGE_ADDR,
.enable_writes = GC9A01_SET_MEM,
},
};
#ifdef QUANTUM_PAINTER_GC9A01_SPI_ENABLE
// Factory function for creating a handle to the ILI9341 device
painter_device_t qp_gc9a01_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode) {
for (uint32_t i = 0; i < GC9A01_NUM_DEVICES; ++i) {
tft_panel_dc_reset_painter_device_t *driver = &gc9a01_drivers[i];
if (!driver->base.driver_vtable) {
driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&gc9a01_driver_vtable;
driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable;
driver->base.native_bits_per_pixel = 16; // RGB565
driver->base.panel_width = panel_width;
driver->base.panel_height = panel_height;
driver->base.rotation = QP_ROTATION_0;
driver->base.offset_x = 0;
driver->base.offset_y = 0;
// SPI and other pin configuration
driver->base.comms_config = &driver->spi_dc_reset_config;
driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin;
driver->spi_dc_reset_config.spi_config.divisor = spi_divisor;
driver->spi_dc_reset_config.spi_config.lsb_first = false;
driver->spi_dc_reset_config.spi_config.mode = spi_mode;
driver->spi_dc_reset_config.dc_pin = dc_pin;
driver->spi_dc_reset_config.reset_pin = reset_pin;
return (painter_device_t)driver;
}
}
return NULL;
}
#endif // QUANTUM_PAINTER_GC9A01_SPI_ENABLE

View File

@@ -0,0 +1,37 @@
// Copyright 2021 Paul Cotter (@gr1mr3aver)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "gpio.h"
#include "qp_internal.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter GC9A01 configurables (add to your keyboard's config.h)
#ifndef GC9A01_NUM_DEVICES
/**
* @def This controls the maximum number of GC9A01 devices that Quantum Painter can communicate with at any one time.
* Increasing this number allows for multiple displays to be used.
*/
# define GC9A01_NUM_DEVICES 1
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter GC9A01 device factories
#ifdef QUANTUM_PAINTER_GC9A01_SPI_ENABLE
/**
* Factory method for an GC9A01 SPI LCD device.
*
* @param panel_width[in] the width of the display panel
* @param panel_height[in] the height of the display panel
* @param chip_select_pin[in] the GPIO pin used for SPI chip select
* @param dc_pin[in] the GPIO pin used for D/C control
* @param reset_pin[in] the GPIO pin used for RST
* @param spi_divisor[in] the SPI divisor to use when communicating with the display
* @param spi_mode[in] the SPI mode to use when communicating with the display
* @return the device handle used with all drawing routines in Quantum Painter
*/
painter_device_t qp_gc9a01_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
#endif // QUANTUM_PAINTER_GC9A01_SPI_ENABLE

View File

@@ -0,0 +1,78 @@
// Copyright 2021 Paul Cotter (@gr1mr3aver)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter GC9A01 command opcodes
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Level 1 command opcodes
#define GC9A01_GET_ID_INFO 0x04 // Get ID information
#define GC9A01_GET_STATUS 0x09 // Get status
#define GC9A01_CMD_SLEEP_ON 0x10 // Enter sleep mode
#define GC9A01_CMD_SLEEP_OFF 0x11 // Exit sleep mode
#define GC9A01_CMD_PARTIAL_ON 0x12 // Enter partial mode
#define GC9A01_CMD_PARTIAL_OFF 0x13 // Exit partial mode
#define GC9A01_CMD_INVERT_ON 0x20 // Enter inverted mode
#define GC9A01_CMD_INVERT_OFF 0x21 // Exit inverted mode
#define GC9A01_CMD_DISPLAY_OFF 0x28 // Disable display
#define GC9A01_CMD_DISPLAY_ON 0x29 // Enable display
#define GC9A01_SET_COL_ADDR 0x2A // Set column address
#define GC9A01_SET_PAGE_ADDR 0x2B // Set page address
#define GC9A01_SET_MEM 0x2C // Set memory
#define GC9A01_SET_PARTIAL_AREA 0x30 // Set partial area
#define GC9A01_SET_VSCROLL 0x33 // Set vertical scroll def
#define GC9A01_CMD_TEARING_ON 0x34 // Tearing line enabled
#define GC9A01_CMD_TEARING_OFF 0x35 // Tearing line disabled
#define GC9A01_SET_MEM_ACS_CTL 0x36 // Set mem access ctl
#define GC9A01_SET_VSCROLL_ADDR 0x37 // Set vscroll start addr
#define GC9A01_CMD_IDLE_OFF 0x38 // Exit idle mode
#define GC9A01_CMD_IDLE_ON 0x39 // Enter idle mode
#define GC9A01_SET_PIX_FMT 0x3A // Set pixel format
#define GC9A01_SET_MEM_CONT 0x3C // Set memory continue
#define GC9A01_SET_TEAR_SCANLINE 0x44 // Set tearing scanline
#define GC9A01_GET_TEAR_SCANLINE 0x45 // Get tearing scanline
#define GC9A01_SET_BRIGHTNESS 0x51 // Set brightness
#define GC9A01_SET_DISPLAY_CTL 0x53 // Set display ctl
#define GC9A01_GET_ID1 0xDA // Get ID1
#define GC9A01_GET_ID2 0xDB // Get ID2
#define GC9A01_GET_ID3 0xDC // Get ID3
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Level 2 command opcodes
#define GC9A01_SET_RGB_IF_SIG_CTL 0xB0 // RGB IF signal ctl
#define GC9A01_SET_BLANKING_PORCH_CTL 0xB5 // Set blanking porch ctl
#define GC9A01_SET_FUNCTION_CTL 0xB6 // Set function ctl
#define GC9A01_SET_TEARING_EFFECT 0xBA // Set backlight ctl 3
#define GC9A01_SET_IF_CTL 0xF6 // Set interface control
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Level 3 command opcodes
#define GC9A01_SET_FRAME_RATE 0xE8 // Set frame rate
#define GC9A01_SET_SPI_2DATA 0xE9 // Set frame rate
#define GC9A01_SET_POWER_CTL_1 0xC1 // Set power ctl 1
#define GC9A01_SET_POWER_CTL_2 0xC3 // Set power ctl 2
#define GC9A01_SET_POWER_CTL_3 0xC4 // Set power ctl 3
#define GC9A01_SET_POWER_CTL_4 0xC9 // Set power ctl 4
#define GC9A01_SET_POWER_CTL_7 0xA7 // Set power ctl 7
#define GC9A01_SET_INTER_REG_ENABLE1 0xFE // Enable Inter Register 1
#define GC9A01_SET_INTER_REG_ENABLE2 0xEF // Enable Inter Register 2
#define GC9A01_SET_GAMMA1 0xF0 //
#define GC9A01_SET_GAMMA2 0xF1
#define GC9A01_SET_GAMMA3 0xF2
#define GC9A01_SET_GAMMA4 0xF3
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MADCTL Flags
#define GC9A01_MADCTL_MY 0b10000000
#define GC9A01_MADCTL_MX 0b01000000
#define GC9A01_MADCTL_MV 0b00100000
#define GC9A01_MADCTL_ML 0b00010000
#define GC9A01_MADCTL_RGB 0b00000000
#define GC9A01_MADCTL_BGR 0b00001000
#define GC9A01_MADCTL_MH 0b00000100

View File

@@ -0,0 +1,121 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "qp_internal.h"
#include "qp_comms.h"
#include "qp_ili9163.h"
#include "qp_ili9xxx_opcodes.h"
#include "qp_tft_panel.h"
#ifdef QUANTUM_PAINTER_ILI9163_SPI_ENABLE
# include "qp_comms_spi.h"
#endif // QUANTUM_PAINTER_ILI9163_SPI_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common
// Driver storage
tft_panel_dc_reset_painter_device_t ili9163_drivers[ILI9163_NUM_DEVICES] = {0};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialization
bool qp_ili9163_init(painter_device_t device, painter_rotation_t rotation) {
// clang-format off
const uint8_t ili9163_init_sequence[] = {
// Command, Delay, N, Data[N]
ILI9XXX_CMD_RESET, 120, 0,
ILI9XXX_CMD_SLEEP_OFF, 5, 0,
ILI9XXX_SET_PIX_FMT, 0, 1, 0x55,
ILI9XXX_SET_GAMMA, 0, 1, 0x04,
ILI9XXX_ENABLE_3_GAMMA, 0, 1, 0x01,
ILI9XXX_SET_FUNCTION_CTL, 0, 2, 0xFF, 0x06,
ILI9XXX_SET_PGAMMA, 0, 15, 0x36, 0x29, 0x12, 0x22, 0x1C, 0x15, 0x42, 0xB7, 0x2F, 0x13, 0x12, 0x0A, 0x11, 0x0B, 0x06,
ILI9XXX_SET_NGAMMA, 0, 15, 0x09, 0x16, 0x2D, 0x0D, 0x13, 0x15, 0x40, 0x48, 0x53, 0x0C, 0x1D, 0x25, 0x2E, 0x34, 0x39,
ILI9XXX_SET_FRAME_CTL_NORMAL, 0, 2, 0x08, 0x02,
ILI9XXX_SET_POWER_CTL_1, 0, 2, 0x0A, 0x02,
ILI9XXX_SET_POWER_CTL_2, 0, 1, 0x02,
ILI9XXX_SET_VCOM_CTL_1, 0, 2, 0x50, 0x63,
ILI9XXX_SET_VCOM_CTL_2, 0, 1, 0x00,
ILI9XXX_CMD_PARTIAL_OFF, 0, 0,
ILI9XXX_CMD_DISPLAY_ON, 20, 0
};
// clang-format on
qp_comms_bulk_command_sequence(device, ili9163_init_sequence, sizeof(ili9163_init_sequence));
// Configure the rotation (i.e. the ordering and direction of memory writes in GRAM)
const uint8_t madctl[] = {
[QP_ROTATION_0] = ILI9XXX_MADCTL_BGR,
[QP_ROTATION_90] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MX | ILI9XXX_MADCTL_MV,
[QP_ROTATION_180] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MX | ILI9XXX_MADCTL_MY,
[QP_ROTATION_270] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MV | ILI9XXX_MADCTL_MY,
};
qp_comms_command_databyte(device, ILI9XXX_SET_MEM_ACS_CTL, madctl[rotation]);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Driver vtable
const struct tft_panel_dc_reset_painter_driver_vtable_t ili9163_driver_vtable = {
.base =
{
.init = qp_ili9163_init,
.power = qp_tft_panel_power,
.clear = qp_tft_panel_clear,
.flush = qp_tft_panel_flush,
.pixdata = qp_tft_panel_pixdata,
.viewport = qp_tft_panel_viewport,
.palette_convert = qp_tft_panel_palette_convert,
.append_pixels = qp_tft_panel_append_pixels,
},
.rgb888_to_native16bit = qp_rgb888_to_rgb565_swapped,
.num_window_bytes = 2,
.swap_window_coords = false,
.opcodes =
{
.display_on = ILI9XXX_CMD_DISPLAY_ON,
.display_off = ILI9XXX_CMD_DISPLAY_OFF,
.set_column_address = ILI9XXX_SET_COL_ADDR,
.set_row_address = ILI9XXX_SET_PAGE_ADDR,
.enable_writes = ILI9XXX_SET_MEM,
},
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SPI
#ifdef QUANTUM_PAINTER_ILI9163_SPI_ENABLE
// Factory function for creating a handle to the ILI9163 device
painter_device_t qp_ili9163_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode) {
for (uint32_t i = 0; i < ILI9163_NUM_DEVICES; ++i) {
tft_panel_dc_reset_painter_device_t *driver = &ili9163_drivers[i];
if (!driver->base.driver_vtable) {
driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&ili9163_driver_vtable;
driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable;
driver->base.panel_width = panel_width;
driver->base.panel_height = panel_height;
driver->base.rotation = QP_ROTATION_0;
driver->base.offset_x = 0;
driver->base.offset_y = 0;
driver->base.native_bits_per_pixel = 16; // RGB565
// SPI and other pin configuration
driver->base.comms_config = &driver->spi_dc_reset_config;
driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin;
driver->spi_dc_reset_config.spi_config.divisor = spi_divisor;
driver->spi_dc_reset_config.spi_config.lsb_first = false;
driver->spi_dc_reset_config.spi_config.mode = spi_mode;
driver->spi_dc_reset_config.dc_pin = dc_pin;
driver->spi_dc_reset_config.reset_pin = reset_pin;
return (painter_device_t)driver;
}
}
return NULL;
}
#endif // QUANTUM_PAINTER_ILI9163_SPI_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,37 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "gpio.h"
#include "qp_internal.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ILI9163 configurables (add to your keyboard's config.h)
#ifndef ILI9163_NUM_DEVICES
/**
* @def This controls the maximum number of ILI9163 devices that Quantum Painter can communicate with at any one time.
* Increasing this number allows for multiple displays to be used.
*/
# define ILI9163_NUM_DEVICES 1
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ILI9163 device factories
#ifdef QUANTUM_PAINTER_ILI9163_SPI_ENABLE
/**
* Factory method for an ILI9163 SPI LCD device.
*
* @param panel_width[in] the width of the display panel
* @param panel_height[in] the height of the display panel
* @param chip_select_pin[in] the GPIO pin used for SPI chip select
* @param dc_pin[in] the GPIO pin used for D/C control
* @param reset_pin[in] the GPIO pin used for RST
* @param spi_divisor[in] the SPI divisor to use when communicating with the display
* @param spi_mode[in] the SPI mode to use when communicating with the display
* @return the device handle used with all drawing routines in Quantum Painter
*/
painter_device_t qp_ili9163_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
#endif // QUANTUM_PAINTER_ILI9163_SPI_ENABLE

View File

@@ -0,0 +1,128 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "qp_internal.h"
#include "qp_comms.h"
#include "qp_ili9341.h"
#include "qp_ili9xxx_opcodes.h"
#include "qp_tft_panel.h"
#ifdef QUANTUM_PAINTER_ILI9341_SPI_ENABLE
# include <qp_comms_spi.h>
#endif // QUANTUM_PAINTER_ILI9341_SPI_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common
// Driver storage
tft_panel_dc_reset_painter_device_t ili9341_drivers[ILI9341_NUM_DEVICES] = {0};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialization
bool qp_ili9341_init(painter_device_t device, painter_rotation_t rotation) {
// clang-format off
const uint8_t ili9341_init_sequence[] = {
// Command, Delay, N, Data[N]
ILI9XXX_CMD_RESET, 120, 0,
ILI9XXX_CMD_SLEEP_OFF, 5, 0,
ILI9XXX_POWER_CTL_A, 0, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
ILI9XXX_POWER_CTL_B, 0, 3, 0x00, 0xD9, 0x30,
ILI9XXX_POWER_ON_SEQ_CTL, 0, 4, 0x64, 0x03, 0x12, 0x81,
ILI9XXX_SET_PUMP_RATIO_CTL, 0, 1, 0x20,
ILI9XXX_SET_POWER_CTL_1, 0, 1, 0x26,
ILI9XXX_SET_POWER_CTL_2, 0, 1, 0x11,
ILI9XXX_SET_VCOM_CTL_1, 0, 2, 0x35, 0x3E,
ILI9XXX_SET_VCOM_CTL_2, 0, 1, 0xBE,
ILI9XXX_DRV_TIMING_CTL_A, 0, 3, 0x85, 0x10, 0x7A,
ILI9XXX_DRV_TIMING_CTL_B, 0, 2, 0x00, 0x00,
ILI9XXX_SET_BRIGHTNESS, 0, 1, 0xFF,
ILI9XXX_ENABLE_3_GAMMA, 0, 1, 0x00,
ILI9XXX_SET_GAMMA, 0, 1, 0x01,
ILI9XXX_SET_PGAMMA, 0, 15, 0x0F, 0x29, 0x24, 0x0C, 0x0E, 0x09, 0x4E, 0x78, 0x3C, 0x09, 0x13, 0x05, 0x17, 0x11, 0x00,
ILI9XXX_SET_NGAMMA, 0, 15, 0x00, 0x16, 0x1B, 0x04, 0x11, 0x07, 0x31, 0x33, 0x42, 0x05, 0x0C, 0x0A, 0x28, 0x2F, 0x0F,
ILI9XXX_SET_PIX_FMT, 0, 1, 0x05,
ILI9XXX_SET_FRAME_CTL_NORMAL, 0, 2, 0x00, 0x1B,
ILI9XXX_SET_FUNCTION_CTL, 0, 2, 0x0A, 0xA2,
ILI9XXX_CMD_PARTIAL_OFF, 0, 0,
ILI9XXX_CMD_DISPLAY_ON, 20, 0
};
// clang-format on
qp_comms_bulk_command_sequence(device, ili9341_init_sequence, sizeof(ili9341_init_sequence));
// Configure the rotation (i.e. the ordering and direction of memory writes in GRAM)
const uint8_t madctl[] = {
[QP_ROTATION_0] = ILI9XXX_MADCTL_BGR,
[QP_ROTATION_90] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MX | ILI9XXX_MADCTL_MV,
[QP_ROTATION_180] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MX | ILI9XXX_MADCTL_MY,
[QP_ROTATION_270] = ILI9XXX_MADCTL_BGR | ILI9XXX_MADCTL_MV | ILI9XXX_MADCTL_MY,
};
qp_comms_command_databyte(device, ILI9XXX_SET_MEM_ACS_CTL, madctl[rotation]);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Driver vtable
const struct tft_panel_dc_reset_painter_driver_vtable_t ili9341_driver_vtable = {
.base =
{
.init = qp_ili9341_init,
.power = qp_tft_panel_power,
.clear = qp_tft_panel_clear,
.flush = qp_tft_panel_flush,
.pixdata = qp_tft_panel_pixdata,
.viewport = qp_tft_panel_viewport,
.palette_convert = qp_tft_panel_palette_convert,
.append_pixels = qp_tft_panel_append_pixels,
},
.rgb888_to_native16bit = qp_rgb888_to_rgb565_swapped,
.num_window_bytes = 2,
.swap_window_coords = false,
.opcodes =
{
.display_on = ILI9XXX_CMD_DISPLAY_ON,
.display_off = ILI9XXX_CMD_DISPLAY_OFF,
.set_column_address = ILI9XXX_SET_COL_ADDR,
.set_row_address = ILI9XXX_SET_PAGE_ADDR,
.enable_writes = ILI9XXX_SET_MEM,
},
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SPI
#ifdef QUANTUM_PAINTER_ILI9341_SPI_ENABLE
// Factory function for creating a handle to the ILI9341 device
painter_device_t qp_ili9341_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode) {
for (uint32_t i = 0; i < ILI9341_NUM_DEVICES; ++i) {
tft_panel_dc_reset_painter_device_t *driver = &ili9341_drivers[i];
if (!driver->base.driver_vtable) {
driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&ili9341_driver_vtable;
driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable;
driver->base.native_bits_per_pixel = 16; // RGB565
driver->base.panel_width = panel_width;
driver->base.panel_height = panel_height;
driver->base.rotation = QP_ROTATION_0;
driver->base.offset_x = 0;
driver->base.offset_y = 0;
// SPI and other pin configuration
driver->base.comms_config = &driver->spi_dc_reset_config;
driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin;
driver->spi_dc_reset_config.spi_config.divisor = spi_divisor;
driver->spi_dc_reset_config.spi_config.lsb_first = false;
driver->spi_dc_reset_config.spi_config.mode = spi_mode;
driver->spi_dc_reset_config.dc_pin = dc_pin;
driver->spi_dc_reset_config.reset_pin = reset_pin;
return (painter_device_t)driver;
}
}
return NULL;
}
#endif // QUANTUM_PAINTER_ILI9341_SPI_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,37 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "gpio.h"
#include "qp_internal.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ILI9341 configurables (add to your keyboard's config.h)
#ifndef ILI9341_NUM_DEVICES
/**
* @def This controls the maximum number of ILI9341 devices that Quantum Painter can communicate with at any one time.
* Increasing this number allows for multiple displays to be used.
*/
# define ILI9341_NUM_DEVICES 1
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ILI9341 device factories
#ifdef QUANTUM_PAINTER_ILI9341_SPI_ENABLE
/**
* Factory method for an ILI9341 SPI LCD device.
*
* @param panel_width[in] the width of the display panel
* @param panel_height[in] the height of the display panel
* @param chip_select_pin[in] the GPIO pin used for SPI chip select
* @param dc_pin[in] the GPIO pin used for D/C control
* @param reset_pin[in] the GPIO pin used for RST
* @param spi_divisor[in] the SPI divisor to use when communicating with the display
* @param spi_mode[in] the SPI mode to use when communicating with the display
* @return the device handle used with all drawing routines in Quantum Painter
*/
painter_device_t qp_ili9341_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
#endif // QUANTUM_PAINTER_ILI9341_SPI_ENABLE

View File

@@ -0,0 +1,100 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ILI9xxx command opcodes
#define ILI9XXX_CMD_NOP 0x00 // No operation
#define ILI9XXX_CMD_RESET 0x01 // Software reset
#define ILI9XXX_GET_ID_INFO 0x04 // Get ID information
#define ILI9XXX_GET_STATUS 0x09 // Get status
#define ILI9XXX_GET_PWR_MODE 0x0A // Get power mode
#define ILI9XXX_GET_MADCTL 0x0B // Get MADCTL
#define ILI9XXX_GET_PIX_FMT 0x0C // Get pixel format
#define ILI9XXX_GET_IMG_FMT 0x0D // Get image format
#define ILI9XXX_GET_SIG_MODE 0x0E // Get signal mode
#define ILI9XXX_GET_SELF_DIAG 0x0F // Get self-diagnostics
#define ILI9XXX_CMD_SLEEP_ON 0x10 // Enter sleep mode
#define ILI9XXX_CMD_SLEEP_OFF 0x11 // Exist sleep mode
#define ILI9XXX_CMD_PARTIAL_ON 0x12 // Enter partial mode
#define ILI9XXX_CMD_PARTIAL_OFF 0x13 // Exit partial mode
#define ILI9XXX_CMD_INVERT_ON 0x20 // Enter inverted mode
#define ILI9XXX_CMD_INVERT_OFF 0x21 // Exit inverted mode
#define ILI9XXX_SET_GAMMA 0x26 // Set gamma params
#define ILI9XXX_CMD_DISPLAY_OFF 0x28 // Disable display
#define ILI9XXX_CMD_DISPLAY_ON 0x29 // Enable display
#define ILI9XXX_SET_COL_ADDR 0x2A // Set column address
#define ILI9XXX_SET_PAGE_ADDR 0x2B // Set page address
#define ILI9XXX_SET_MEM 0x2C // Set memory
#define ILI9XXX_SET_COLOR 0x2D // Set color
#define ILI9XXX_GET_MEM 0x2E // Get memory
#define ILI9XXX_SET_PARTIAL_AREA 0x30 // Set partial area
#define ILI9XXX_SET_VSCROLL 0x33 // Set vertical scroll def
#define ILI9XXX_CMD_TEARING_ON 0x34 // Tearing line enabled
#define ILI9XXX_CMD_TEARING_OFF 0x35 // Tearing line disabled
#define ILI9XXX_SET_MEM_ACS_CTL 0x36 // Set mem access ctl
#define ILI9XXX_SET_VSCROLL_ADDR 0x37 // Set vscroll start addr
#define ILI9XXX_CMD_IDLE_OFF 0x38 // Exit idle mode
#define ILI9XXX_CMD_IDLE_ON 0x39 // Enter idle mode
#define ILI9XXX_SET_PIX_FMT 0x3A // Set pixel format
#define ILI9XXX_SET_MEM_CONT 0x3C // Set memory continue
#define ILI9XXX_GET_MEM_CONT 0x3E // Get memory continue
#define ILI9XXX_SET_TEAR_SCANLINE 0x44 // Set tearing scanline
#define ILI9XXX_GET_TEAR_SCANLINE 0x45 // Get tearing scanline
#define ILI9XXX_SET_BRIGHTNESS 0x51 // Set brightness
#define ILI9XXX_GET_BRIGHTNESS 0x52 // Get brightness
#define ILI9XXX_SET_DISPLAY_CTL 0x53 // Set display ctl
#define ILI9XXX_GET_DISPLAY_CTL 0x54 // Get display ctl
#define ILI9XXX_SET_CABC 0x55 // Set CABC
#define ILI9XXX_GET_CABC 0x56 // Get CABC
#define ILI9XXX_SET_CABC_MIN 0x5E // Set CABC min
#define ILI9XXX_GET_CABC_MIN 0x5F // Set CABC max
#define ILI9XXX_GET_ID1 0xDA // Get ID1
#define ILI9XXX_GET_ID2 0xDB // Get ID2
#define ILI9XXX_GET_ID3 0xDC // Get ID3
#define ILI9XXX_SET_RGB_IF_SIG_CTL 0xB0 // RGB IF signal ctl
#define ILI9XXX_SET_FRAME_CTL_NORMAL 0xB1 // Set frame ctl (normal)
#define ILI9XXX_SET_FRAME_CTL_IDLE 0xB2 // Set frame ctl (idle)
#define ILI9XXX_SET_FRAME_CTL_PARTIAL 0xB3 // Set frame ctl (partial)
#define ILI9XXX_SET_INVERSION_CTL 0xB4 // Set inversion ctl
#define ILI9XXX_SET_BLANKING_PORCH_CTL 0xB5 // Set blanking porch ctl
#define ILI9XXX_SET_FUNCTION_CTL 0xB6 // Set function ctl
#define ILI9XXX_SET_ENTRY_MODE 0xB7 // Set entry mode
#define ILI9XXX_SET_LIGHT_CTL_1 0xB8 // Set backlight ctl 1
#define ILI9XXX_SET_LIGHT_CTL_2 0xB9 // Set backlight ctl 2
#define ILI9XXX_SET_LIGHT_CTL_3 0xBA // Set backlight ctl 3
#define ILI9XXX_SET_LIGHT_CTL_4 0xBB // Set backlight ctl 4
#define ILI9XXX_SET_LIGHT_CTL_5 0xBC // Set backlight ctl 5
#define ILI9XXX_SET_LIGHT_CTL_7 0xBE // Set backlight ctl 7
#define ILI9XXX_SET_LIGHT_CTL_8 0xBF // Set backlight ctl 8
#define ILI9XXX_SET_POWER_CTL_1 0xC0 // Set power ctl 1
#define ILI9XXX_SET_POWER_CTL_2 0xC1 // Set power ctl 2
#define ILI9XXX_SET_VCOM_CTL_1 0xC5 // Set VCOM ctl 1
#define ILI9XXX_SET_VCOM_CTL_2 0xC7 // Set VCOM ctl 2
#define ILI9XXX_POWER_CTL_A 0xCB // Set power control A
#define ILI9XXX_POWER_CTL_B 0xCF // Set power control B
#define ILI9XXX_DRV_TIMING_CTL_A 0xE8 // Set driver timing control A
#define ILI9XXX_DRV_TIMING_CTL_B 0xEA // Set driver timing control B
#define ILI9XXX_POWER_ON_SEQ_CTL 0xED // Set Power on sequence control
#define ILI9XXX_SET_NVMEM 0xD0 // Set NVMEM data
#define ILI9XXX_GET_NVMEM_KEY 0xD1 // Get NVMEM protect key
#define ILI9XXX_GET_NVMEM_STATUS 0xD2 // Get NVMEM status
#define ILI9XXX_GET_ID4 0xD3 // Get ID4
#define ILI9XXX_SET_PGAMMA 0xE0 // Set positive gamma
#define ILI9XXX_SET_NGAMMA 0xE1 // Set negative gamma
#define ILI9XXX_SET_DGAMMA_CTL_1 0xE2 // Set digital gamma ctl 1
#define ILI9XXX_SET_DGAMMA_CTL_2 0xE3 // Set digital gamma ctl 2
#define ILI9XXX_ENABLE_3_GAMMA 0xF2 // Enable 3 gamma
#define ILI9XXX_SET_IF_CTL 0xF6 // Set interface control
#define ILI9XXX_SET_PUMP_RATIO_CTL 0xF7 // Set pump ratio control
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MADCTL Flags
#define ILI9XXX_MADCTL_MY 0b10000000
#define ILI9XXX_MADCTL_MX 0b01000000
#define ILI9XXX_MADCTL_MV 0b00100000
#define ILI9XXX_MADCTL_ML 0b00010000
#define ILI9XXX_MADCTL_RGB 0b00000000
#define ILI9XXX_MADCTL_BGR 0b00001000
#define ILI9XXX_MADCTL_MH 0b00000100

View File

@@ -0,0 +1,125 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "qp_internal.h"
#include "qp_comms.h"
#include "qp_ssd1351.h"
#include "qp_ssd1351_opcodes.h"
#include "qp_tft_panel.h"
#ifdef QUANTUM_PAINTER_SSD1351_SPI_ENABLE
# include "qp_comms_spi.h"
#endif // QUANTUM_PAINTER_SSD1351_SPI_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common
// Driver storage
tft_panel_dc_reset_painter_device_t ssd1351_drivers[SSD1351_NUM_DEVICES] = {0};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialization
bool qp_ssd1351_init(painter_device_t device, painter_rotation_t rotation) {
tft_panel_dc_reset_painter_device_t *driver = (tft_panel_dc_reset_painter_device_t *)device;
// clang-format off
const uint8_t ssd1351_init_sequence[] = {
// Command, Delay, N, Data[N]
SSD1351_COMMANDLOCK, 5, 1, 0x12,
SSD1351_COMMANDLOCK, 5, 1, 0xB1,
SSD1351_DISPLAYOFF, 5, 0,
SSD1351_CLOCKDIV, 5, 1, 0xF1,
SSD1351_MUXRATIO, 5, 1, 0x7F,
SSD1351_DISPLAYOFFSET, 5, 1, 0x00,
SSD1351_SETGPIO, 5, 1, 0x00,
SSD1351_FUNCTIONSELECT, 5, 1, 0x01,
SSD1351_PRECHARGE, 5, 1, 0x32,
SSD1351_VCOMH, 5, 1, 0x05,
SSD1351_NORMALDISPLAY, 5, 0,
SSD1351_CONTRASTABC, 5, 3, 0xC8, 0x80, 0xC8,
SSD1351_CONTRASTMASTER, 5, 1, 0x0F,
SSD1351_SETVSL, 5, 3, 0xA0, 0xB5, 0x55,
SSD1351_PRECHARGE2, 5, 1, 0x01,
SSD1351_DISPLAYON, 5, 0,
};
// clang-format on
qp_comms_bulk_command_sequence(device, ssd1351_init_sequence, sizeof(ssd1351_init_sequence));
// Configure the rotation (i.e. the ordering and direction of memory writes in GRAM)
const uint8_t madctl[] = {
[QP_ROTATION_0] = SSD1351_MADCTL_BGR | SSD1351_MADCTL_MY,
[QP_ROTATION_90] = SSD1351_MADCTL_BGR | SSD1351_MADCTL_MX | SSD1351_MADCTL_MY | SSD1351_MADCTL_MV,
[QP_ROTATION_180] = SSD1351_MADCTL_BGR | SSD1351_MADCTL_MX,
[QP_ROTATION_270] = SSD1351_MADCTL_BGR | SSD1351_MADCTL_MV,
};
qp_comms_command_databyte(device, SSD1351_SETREMAP, madctl[rotation]);
qp_comms_command_databyte(device, SSD1351_STARTLINE, (rotation == QP_ROTATION_0 || rotation == QP_ROTATION_90) ? driver->base.panel_height : 0);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Driver vtable
const struct tft_panel_dc_reset_painter_driver_vtable_t ssd1351_driver_vtable = {
.base =
{
.init = qp_ssd1351_init,
.power = qp_tft_panel_power,
.clear = qp_tft_panel_clear,
.flush = qp_tft_panel_flush,
.pixdata = qp_tft_panel_pixdata,
.viewport = qp_tft_panel_viewport,
.palette_convert = qp_tft_panel_palette_convert,
.append_pixels = qp_tft_panel_append_pixels,
},
.rgb888_to_native16bit = qp_rgb888_to_rgb565_swapped,
.num_window_bytes = 1,
.swap_window_coords = true,
.opcodes =
{
.display_on = SSD1351_DISPLAYON,
.display_off = SSD1351_DISPLAYOFF,
.set_column_address = SSD1351_SETCOLUMN,
.set_row_address = SSD1351_SETROW,
.enable_writes = SSD1351_WRITERAM,
},
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SPI
#ifdef QUANTUM_PAINTER_SSD1351_SPI_ENABLE
// Factory function for creating a handle to the SSD1351 device
painter_device_t qp_ssd1351_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode) {
for (uint32_t i = 0; i < SSD1351_NUM_DEVICES; ++i) {
tft_panel_dc_reset_painter_device_t *driver = &ssd1351_drivers[i];
if (!driver->base.driver_vtable) {
driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&ssd1351_driver_vtable;
driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable;
driver->base.panel_width = panel_width;
driver->base.panel_height = panel_height;
driver->base.rotation = QP_ROTATION_0;
driver->base.offset_x = 0;
driver->base.offset_y = 0;
driver->base.native_bits_per_pixel = 16; // RGB565
// SPI and other pin configuration
driver->base.comms_config = &driver->spi_dc_reset_config;
driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin;
driver->spi_dc_reset_config.spi_config.divisor = spi_divisor;
driver->spi_dc_reset_config.spi_config.lsb_first = false;
driver->spi_dc_reset_config.spi_config.mode = spi_mode;
driver->spi_dc_reset_config.dc_pin = dc_pin;
driver->spi_dc_reset_config.reset_pin = reset_pin;
return (painter_device_t)driver;
}
}
return NULL;
}
#endif // QUANTUM_PAINTER_SSD1351_SPI_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,37 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "gpio.h"
#include "qp_internal.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter SSD1351 configurables (add to your keyboard's config.h)
#ifndef SSD1351_NUM_DEVICES
/**
* @def This controls the maximum number of SSD1351 devices that Quantum Painter can communicate with at any one time.
* Increasing this number allows for multiple displays to be used.
*/
# define SSD1351_NUM_DEVICES 1
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter SSD1351 device factories
#ifdef QUANTUM_PAINTER_SSD1351_SPI_ENABLE
/**
* Factory method for an SSD1351 SPI OLED device.
*
* @param panel_width[in] the width of the display panel
* @param panel_height[in] the height of the display panel
* @param chip_select_pin[in] the GPIO pin used for SPI chip select
* @param dc_pin[in] the GPIO pin used for D/C control
* @param reset_pin[in] the GPIO pin used for RST
* @param spi_divisor[in] the SPI divisor to use when communicating with the display
* @param spi_mode[in] the SPI mode to use when communicating with the display
* @return the device handle used with all drawing routines in Quantum Painter
*/
painter_device_t qp_ssd1351_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
#endif // QUANTUM_PAINTER_SSD1351_SPI_ENABLE

View File

@@ -0,0 +1,48 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter SSD1351 command opcodes
// System function commands
#define SSD1351_SETCOLUMN 0x15
#define SSD1351_SETROW 0x75
#define SSD1351_WRITERAM 0x5C
#define SSD1351_READRAM 0x5D
#define SSD1351_SETREMAP 0xA0
#define SSD1351_STARTLINE 0xA1
#define SSD1351_DISPLAYOFFSET 0xA2
#define SSD1351_DISPLAYALLOFF 0xA4
#define SSD1351_DISPLAYALLON 0xA5
#define SSD1351_NORMALDISPLAY 0xA6
#define SSD1351_INVERTDISPLAY 0xA7
#define SSD1351_FUNCTIONSELECT 0xAB
#define SSD1351_DISPLAYOFF 0xAE
#define SSD1351_DISPLAYON 0xAF
#define SSD1351_PRECHARGE 0xB1
#define SSD1351_DISPLAYENHANCE 0xB2
#define SSD1351_CLOCKDIV 0xB3
#define SSD1351_SETVSL 0xB4
#define SSD1351_SETGPIO 0xB5
#define SSD1351_PRECHARGE2 0xB6
#define SSD1351_SETGRAY 0xB8
#define SSD1351_USELUT 0xB9
#define SSD1351_PRECHARGELEVEL 0xBB
#define SSD1351_VCOMH 0xBE
#define SSD1351_CONTRASTABC 0xC1
#define SSD1351_CONTRASTMASTER 0xC7
#define SSD1351_MUXRATIO 0xCA
#define SSD1351_COMMANDLOCK 0xFD
#define SSD1351_HORIZSCROLL 0x96
#define SSD1351_STOPSCROLL 0x9E
#define SSD1351_STARTSCROLL 0x9F
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SETREMAP (MADCTL) Flags
#define SSD1351_MADCTL_MY 0b00010000
#define SSD1351_MADCTL_MX 0b00000010
#define SSD1351_MADCTL_MV 0b00000001
#define SSD1351_MADCTL_RGB 0b01100000
#define SSD1351_MADCTL_BGR 0b01100100

View File

@@ -0,0 +1,144 @@
// Copyright 2021 Paul Cotter (@gr1mr3aver)
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "qp_internal.h"
#include "qp_comms.h"
#include "qp_st7789.h"
#include "qp_st77xx_opcodes.h"
#include "qp_st7789_opcodes.h"
#include "qp_tft_panel.h"
#ifdef QUANTUM_PAINTER_ST7789_SPI_ENABLE
# include "qp_comms_spi.h"
#endif // QUANTUM_PAINTER_ST7789_SPI_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common
// Driver storage
tft_panel_dc_reset_painter_device_t st7789_drivers[ST7789_NUM_DEVICES] = {0};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Automatic viewport offsets
#ifndef ST7789_NO_AUTOMATIC_OFFSETS
static inline void st7789_automatic_viewport_offsets(painter_device_t device, painter_rotation_t rotation) {
struct painter_driver_t *driver = (struct painter_driver_t *)device;
// clang-format off
const struct {
uint16_t offset_x;
uint16_t offset_y;
} rotation_offsets_240x240[] = {
[QP_ROTATION_0] = { .offset_x = 0, .offset_y = 0 },
[QP_ROTATION_90] = { .offset_x = 0, .offset_y = 0 },
[QP_ROTATION_180] = { .offset_x = 0, .offset_y = 80 },
[QP_ROTATION_270] = { .offset_x = 80, .offset_y = 0 },
};
// clang-format on
if (driver->panel_width == 240 && driver->panel_height == 240) {
driver->offset_x = rotation_offsets_240x240[rotation].offset_x;
driver->offset_y = rotation_offsets_240x240[rotation].offset_y;
}
}
#endif // ST7789_NO_AUTOMATIC_OFFSETS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialization
bool qp_st7789_init(painter_device_t device, painter_rotation_t rotation) {
// clang-format off
const uint8_t st7789_init_sequence[] = {
// Command, Delay, N, Data[N]
ST77XX_CMD_RESET, 120, 0,
ST77XX_CMD_SLEEP_OFF, 5, 0,
ST77XX_SET_PIX_FMT, 0, 1, 0x55,
ST77XX_CMD_INVERT_ON, 0, 0,
ST77XX_CMD_NORMAL_ON, 0, 0,
ST77XX_CMD_DISPLAY_ON, 20, 0
};
// clang-format on
qp_comms_bulk_command_sequence(device, st7789_init_sequence, sizeof(st7789_init_sequence));
// Configure the rotation (i.e. the ordering and direction of memory writes in GRAM)
const uint8_t madctl[] = {
[QP_ROTATION_0] = ST77XX_MADCTL_RGB,
[QP_ROTATION_90] = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MX | ST77XX_MADCTL_MV,
[QP_ROTATION_180] = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MX | ST77XX_MADCTL_MY,
[QP_ROTATION_270] = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MY,
};
qp_comms_command_databyte(device, ST77XX_SET_MADCTL, madctl[rotation]);
#ifndef ST7789_NO_AUTOMATIC_VIEWPORT_OFFSETS
st7789_automatic_viewport_offsets(device, rotation);
#endif // ST7789_NO_AUTOMATIC_VIEWPORT_OFFSETS
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Driver vtable
const struct tft_panel_dc_reset_painter_driver_vtable_t st7789_driver_vtable = {
.base =
{
.init = qp_st7789_init,
.power = qp_tft_panel_power,
.clear = qp_tft_panel_clear,
.flush = qp_tft_panel_flush,
.pixdata = qp_tft_panel_pixdata,
.viewport = qp_tft_panel_viewport,
.palette_convert = qp_tft_panel_palette_convert,
.append_pixels = qp_tft_panel_append_pixels,
},
.rgb888_to_native16bit = qp_rgb888_to_rgb565_swapped,
.num_window_bytes = 2,
.swap_window_coords = false,
.opcodes =
{
.display_on = ST77XX_CMD_DISPLAY_ON,
.display_off = ST77XX_CMD_DISPLAY_OFF,
.set_column_address = ST77XX_SET_COL_ADDR,
.set_row_address = ST77XX_SET_ROW_ADDR,
.enable_writes = ST77XX_SET_MEM,
},
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SPI
#ifdef QUANTUM_PAINTER_ST7789_SPI_ENABLE
// Factory function for creating a handle to the ST7789 device
painter_device_t qp_st7789_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode) {
for (uint32_t i = 0; i < ST7789_NUM_DEVICES; ++i) {
tft_panel_dc_reset_painter_device_t *driver = &st7789_drivers[i];
if (!driver->base.driver_vtable) {
driver->base.driver_vtable = (const struct painter_driver_vtable_t *)&st7789_driver_vtable;
driver->base.comms_vtable = (const struct painter_comms_vtable_t *)&spi_comms_with_dc_vtable;
driver->base.panel_width = panel_width;
driver->base.panel_height = panel_height;
driver->base.rotation = QP_ROTATION_0;
driver->base.offset_x = 0;
driver->base.offset_y = 0;
driver->base.native_bits_per_pixel = 16; // RGB565
// SPI and other pin configuration
driver->base.comms_config = &driver->spi_dc_reset_config;
driver->spi_dc_reset_config.spi_config.chip_select_pin = chip_select_pin;
driver->spi_dc_reset_config.spi_config.divisor = spi_divisor;
driver->spi_dc_reset_config.spi_config.lsb_first = false;
driver->spi_dc_reset_config.spi_config.mode = spi_mode;
driver->spi_dc_reset_config.dc_pin = dc_pin;
driver->spi_dc_reset_config.reset_pin = reset_pin;
return (painter_device_t)driver;
}
}
return NULL;
}
#endif // QUANTUM_PAINTER_ST7789_SPI_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,44 @@
// Copyright 2021 Paul Cotter (@gr1mr3aver)
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "gpio.h"
#include "qp_internal.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ST7789 configurables (add to your keyboard's config.h)
#ifndef ST7789_NUM_DEVICES
/**
* @def This controls the maximum number of ST7789 devices that Quantum Painter can communicate with at any one time.
* Increasing this number allows for multiple displays to be used.
*/
# define ST7789_NUM_DEVICES 1
#endif
// Additional configuration options to be copied to your keyboard's config.h (don't change here):
// If you know exactly which offsets should be used on your panel with respect to selected rotation, then this config
// option allows you to save some flash space -- you'll need to invoke qp_set_viewport_offsets() instead from your keyboard.
// #define ST7789_NO_AUTOMATIC_VIEWPORT_OFFSETS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ST7789 device factories
#ifdef QUANTUM_PAINTER_ST7789_SPI_ENABLE
/**
* Factory method for an ST7789 SPI LCD device.
*
* @param panel_width[in] the width of the display panel
* @param panel_height[in] the height of the display panel
* @param chip_select_pin[in] the GPIO pin used for SPI chip select
* @param dc_pin[in] the GPIO pin used for D/C control
* @param reset_pin[in] the GPIO pin used for RST
* @param spi_divisor[in] the SPI divisor to use when communicating with the display
* @param spi_mode[in] the SPI mode to use when communicating with the display
* @return the device handle used with all drawing routines in Quantum Painter
*/
painter_device_t qp_st7789_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
#endif // QUANTUM_PAINTER_ST7789_SPI_ENABLE

View File

@@ -0,0 +1,64 @@
// Copyright 2021 Paul Cotter (@gr1mr3aver)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ST7789 additional command opcodes
// System function commands
#define ST7789_GET_SELF_DIAG 0x0F // Get self-diagnostic result
#define ST7789_SET_VERT_SCRL 0x33 // Set vertical scroll definition
#define ST7789_SET_VERT_SCRL_ADDR 0x37 // SEt Vertical scroll start address
#define ST7789_SET_MEM_CONT 0x3C // Memory Write continue
#define ST7789_GET_MEM_CONT 0x3E // Memory Read continue
#define ST7789_SET_TEAR_LINE 0x44 // Set tear scanline
#define ST7789_GET_TEAR_LINE 0x45 // Get tear scanline
#define ST7789_SET_BRIGHTNESS 0x51 // Set display brightness
#define ST7789_GET_BRIGHTNESS 0x52 // Get display brightness
#define ST7789_SET_CTRL 0x53 // Set CTRL display
#define ST7789_GET_CTRL 0x54 // Get CTRL display value
#define ST7789_SET_CAB_COLOR 0x55 // Set content adaptive brightness control and color enhancement
#define ST7789_GET_CAB_COLOR 0x56 // Get content adaptive brightness control and color enhancement
#define ST7789_SET_CAB_BRIGHTNESS 0x5E // Set content adaptive minimum brightness
#define ST7789_GET_CAB_BRIGHTNESS 0x5F // Get content adaptive minimum brightness
#define ST7789_GET_ABC_SELF_DIAG 0x68 // Get Auto brightness control self diagnostics
// Panel Function Commands
#define ST7789_SET_RAM_CTL 0xB0 // Set RAM control
#define ST7789_SET_RGB_CTL 0xB1 // Set RGB control
#define ST7789_SET_PORCH_CTL 0xB2 // Set Porch control
#define ST7789_SET_FRAME_RATE_CTL_1 0xB3 // Set frame rate control 1
#define ST7789_SET_PARTIAL_CTL 0xB5 // Set Partial control
#define ST7789_SET_GATE_CTL 0xB7 // Set gate control
#define ST7789_SET_GATE_ON_TIMING 0xB8 // Set gate on timing adjustment
#define ST7789_SET_DIGITAL_GAMMA_ON 0xBA // Enable digital gamma
#define ST7789_SET_VCOM 0xBB // Set VCOM
#define ST7789_SET_POWER_SAVE 0xBC // Set power saving mode
#define ST7789_SET_DISP_OFF_POWER 0xBD // Set display off power saving
#define ST7789_SET_LCM_CTL 0xC0 // Set LCM control
#define ST7789_SET_IDS 0xC1 // Set IDs
#define ST7789_SET_VDV_VRH_ON 0xC2 // Set VDV and VRH command enable
#define ST7789_SET_VRH 0xC3 // Set VRH
#define ST7789_SET_VDV 0xC4 // Set VDV
#define ST7789_SET_VCOM_OFFSET 0xC5 // Set VCOM offset ctl
#define ST7789_SET_FRAME_RATE_CTL_2 0xC6 // Set frame rate control 2
#define ST7789_SET_CABC_CTL 0xC7 // Set CABC Control
#define ST7789_GET_REG_1 0xC8 // Get register value selection1
#define ST7789_GET_REG_2 0xCA // Get register value selection2
#define ST7789_SET_PWM_FREQ 0xCC // Set PWM frequency
#define ST7789_SET_POWER_CTL_1 0xD0 // Set power ctl 1
#define ST7789_SET_VAP_VAN_ON 0xD2 // Enable VAP/VAN signal output
#define ST7789_SET_CMD2_ENABLE 0xDF // Enable command 2
#define ST7789_SET_PGAMMA 0xE0 // Set positive gamma
#define ST7789_SET_NGAMMA 0xE1 // Set negative gamma
#define ST7789_SET_DIGITAL_GAMMA_RED 0xE2 // Set digital gamma lookup table for red
#define ST7789_SET_DIGITAL_GAMMA_BLUE 0xE3 // Get digital gamma lookup table for blue
#define ST7789_SET_GATE_CTL_2 0xE4 // Set gate control 2
#define ST7789_SET_SPI2_ENABLE 0xE7 // Enable SPI2
#define ST7789_SET_POWER_CTL_2 0xE8 // Set power ctl 2
#define ST7789_SET_EQ_TIME_CTL 0xE9 // Set equalize time control
#define ST7789_SET_PROG_CTL 0xEC // Set program control
#define ST7789_SET_PROG_MODE_ENABLE 0xFA // Set program mode enable
#define ST7789_SET_NVMEM 0xFC // Set NVMEM data
#define ST7789_SET_PROG_ACTION 0xFE // Set program action

View File

@@ -0,0 +1,51 @@
// Copyright 2021 Paul Cotter (@gr1mr3aver)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter ST77XX command opcodes
// System function commands
#define ST77XX_CMD_NOP 0x00 // No operation
#define ST77XX_CMD_RESET 0x01 // Software reset
#define ST77XX_GET_ID_INFO 0x04 // Get ID information
#define ST77XX_GET_STATUS 0x09 // Get status
#define ST77XX_GET_PWR_MODE 0x0A // Get power mode
#define ST77XX_GET_MADCTL 0x0B // Get mem access ctl
#define ST77XX_GET_PIX_FMT 0x0C // Get pixel format
#define ST77XX_GET_IMG_FMT 0x0D // Get image format
#define ST77XX_GET_SIG_MODE 0x0E // Get signal mode
#define ST77XX_CMD_SLEEP_ON 0x10 // Enter sleep mode
#define ST77XX_CMD_SLEEP_OFF 0x11 // Exist sleep mode
#define ST77XX_CMD_PARTIAL_ON 0x12 // Enter partial mode
#define ST77XX_CMD_NORMAL_ON 0x13 // Exit partial mode
#define ST77XX_CMD_INVERT_OFF 0x20 // Exit inverted mode
#define ST77XX_CMD_INVERT_ON 0x21 // Enter inverted mode
#define ST77XX_SET_GAMMA 0x26 // Set gamma params
#define ST77XX_CMD_DISPLAY_OFF 0x28 // Disable display
#define ST77XX_CMD_DISPLAY_ON 0x29 // Enable display
#define ST77XX_SET_COL_ADDR 0x2A // Set column address
#define ST77XX_SET_ROW_ADDR 0x2B // Set page (row) address
#define ST77XX_SET_MEM 0x2C // Set memory
#define ST77XX_GET_MEM 0x2E // Get memory
#define ST77XX_SET_PARTIAL_AREA 0x30 // Set partial area
#define ST77XX_CMD_TEARING_OFF 0x34 // Tearing line disabled
#define ST77XX_CMD_TEARING_ON 0x35 // Tearing line enabled
#define ST77XX_SET_MADCTL 0x36 // Set mem access ctl
#define ST77XX_CMD_IDLE_OFF 0x38 // Exit idle mode
#define ST77XX_CMD_IDLE_ON 0x39 // Enter idle mode
#define ST77XX_SET_PIX_FMT 0x3A // Set pixel format
#define ST77XX_GET_ID1 0xDA // Get ID1
#define ST77XX_GET_ID2 0xDB // Get ID2
#define ST77XX_GET_ID3 0xDC // Get ID3
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MADCTL Flags
#define ST77XX_MADCTL_MY 0b10000000
#define ST77XX_MADCTL_MX 0b01000000
#define ST77XX_MADCTL_MV 0b00100000
#define ST77XX_MADCTL_ML 0b00010000
#define ST77XX_MADCTL_RGB 0b00000000
#define ST77XX_MADCTL_BGR 0b00001000
#define ST77XX_MADCTL_MH 0b00000100

View File

@@ -0,0 +1,130 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "color.h"
#include "qp_internal.h"
#include "qp_comms.h"
#include "qp_draw.h"
#include "qp_tft_panel.h"
#define BYTE_SWAP(x) (((((uint16_t)(x)) >> 8) & 0x00FF) | ((((uint16_t)(x)) << 8) & 0xFF00))
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Native pixel format conversion
uint16_t qp_rgb888_to_rgb565(uint8_t r, uint8_t g, uint8_t b) {
uint16_t rgb565 = (((uint16_t)r) >> 3) << 11 | (((uint16_t)g) >> 2) << 5 | (((uint16_t)b) >> 3);
return rgb565;
}
uint16_t qp_rgb888_to_rgb565_swapped(uint8_t r, uint8_t g, uint8_t b) {
uint16_t rgb565 = (((uint16_t)r) >> 3) << 11 | (((uint16_t)g) >> 2) << 5 | (((uint16_t)b) >> 3);
return BYTE_SWAP(rgb565);
}
uint16_t qp_rgb888_to_bgr565(uint8_t r, uint8_t g, uint8_t b) {
uint16_t bgr565 = (((uint16_t)b) >> 3) << 11 | (((uint16_t)g) >> 2) << 5 | (((uint16_t)r) >> 3);
return bgr565;
}
uint16_t qp_rgb888_to_bgr565_swapped(uint8_t r, uint8_t g, uint8_t b) {
uint16_t bgr565 = (((uint16_t)b) >> 3) << 11 | (((uint16_t)g) >> 2) << 5 | (((uint16_t)r) >> 3);
return BYTE_SWAP(bgr565);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter API implementations
// Power control
bool qp_tft_panel_power(painter_device_t device, bool power_on) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
struct tft_panel_dc_reset_painter_driver_vtable_t *vtable = (struct tft_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable;
qp_comms_command(device, power_on ? vtable->opcodes.display_on : vtable->opcodes.display_off);
return true;
}
// Screen clear
bool qp_tft_panel_clear(painter_device_t device) {
struct painter_driver_t *driver = (struct painter_driver_t *)device;
driver->driver_vtable->init(device, driver->rotation); // Re-init the LCD
return true;
}
// Screen flush
bool qp_tft_panel_flush(painter_device_t device) {
// No-op, as there's no framebuffer in RAM for this device.
return true;
}
// Viewport to draw to
bool qp_tft_panel_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
struct tft_panel_dc_reset_painter_driver_vtable_t *vtable = (struct tft_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable;
// Fix up the drawing location if required
left += driver->offset_x;
right += driver->offset_x;
top += driver->offset_y;
bottom += driver->offset_y;
// Check if we need to manually swap the window coordinates based on whether or not we're in a sideways rotation
if (vtable->swap_window_coords && (driver->rotation == QP_ROTATION_90 || driver->rotation == QP_ROTATION_270)) {
uint16_t temp;
temp = left;
left = top;
top = temp;
temp = right;
right = bottom;
bottom = temp;
}
if (vtable->num_window_bytes == 1) {
// Set up the x-window
uint8_t xbuf[2] = {left & 0xFF, right & 0xFF};
qp_comms_command_databuf(device, vtable->opcodes.set_column_address, xbuf, sizeof(xbuf));
// Set up the y-window
uint8_t ybuf[2] = {top & 0xFF, bottom & 0xFF};
qp_comms_command_databuf(device, vtable->opcodes.set_row_address, ybuf, sizeof(ybuf));
} else if (vtable->num_window_bytes == 2) {
// Set up the x-window
uint8_t xbuf[4] = {left >> 8, left & 0xFF, right >> 8, right & 0xFF};
qp_comms_command_databuf(device, vtable->opcodes.set_column_address, xbuf, sizeof(xbuf));
// Set up the y-window
uint8_t ybuf[4] = {top >> 8, top & 0xFF, bottom >> 8, bottom & 0xFF};
qp_comms_command_databuf(device, vtable->opcodes.set_row_address, ybuf, sizeof(ybuf));
}
// Lock in the window
qp_comms_command(device, vtable->opcodes.enable_writes);
return true;
}
// Stream pixel data to the current write position in GRAM
bool qp_tft_panel_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) {
qp_comms_send(device, pixel_data, native_pixel_count * sizeof(uint16_t));
return true;
}
// Convert supplied palette entries into their native equivalents
bool qp_tft_panel_palette_convert(painter_device_t device, int16_t palette_size, qp_pixel_t *palette) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
struct tft_panel_dc_reset_painter_driver_vtable_t *vtable = (struct tft_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable;
for (int16_t i = 0; i < palette_size; ++i) {
RGB rgb = hsv_to_rgb_nocie((HSV){palette[i].hsv888.h, palette[i].hsv888.s, palette[i].hsv888.v});
palette[i].rgb565 = vtable->rgb888_to_native16bit(rgb.r, rgb.g, rgb.b);
}
return true;
}
// Append pixels to the target location, keyed by the pixel index
bool qp_tft_panel_append_pixels(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices) {
uint16_t *buf = (uint16_t *)target_buffer;
for (uint32_t i = 0; i < pixel_count; ++i) {
buf[pixel_offset + i] = palette[palette_indices[i]].rgb565;
}
return true;
}

View File

@@ -0,0 +1,67 @@
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "color.h"
#include "qp_internal.h"
#ifdef QUANTUM_PAINTER_SPI_ENABLE
# include "qp_comms_spi.h"
#endif // QUANTUM_PAINTER_SPI_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common TFT panel implementation using D/C, and RST pins.
typedef uint16_t (*rgb888_to_native_uint16_t)(uint8_t r, uint8_t g, uint8_t b);
// Driver vtable with extras
struct tft_panel_dc_reset_painter_driver_vtable_t {
struct painter_driver_vtable_t base; // must be first, so it can be cast to/from the painter_driver_vtable_t* type
// Conversion function for palette entries
rgb888_to_native_uint16_t rgb888_to_native16bit;
// Number of bytes for transmitting x/y coordinates
uint8_t num_window_bytes;
// Whether or not the x/y coords should be swapped on 90/270 rotation
bool swap_window_coords;
// Opcodes for normal display operation
struct {
uint8_t display_on;
uint8_t display_off;
uint8_t set_column_address;
uint8_t set_row_address;
uint8_t enable_writes;
} opcodes;
};
// Device definition
typedef struct tft_panel_dc_reset_painter_device_t {
struct painter_driver_t base; // must be first, so it can be cast to/from the painter_device_t* type
union {
#ifdef QUANTUM_PAINTER_SPI_ENABLE
// SPI-based configurables
struct qp_comms_spi_dc_reset_config_t spi_dc_reset_config;
#endif // QUANTUM_PAINTER_SPI_ENABLE
// TODO: I2C/parallel etc.
};
} tft_panel_dc_reset_painter_device_t;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Forward declarations for injecting into concrete driver vtables
bool qp_tft_panel_power(painter_device_t device, bool power_on);
bool qp_tft_panel_clear(painter_device_t device);
bool qp_tft_panel_flush(painter_device_t device);
bool qp_tft_panel_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom);
bool qp_tft_panel_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count);
bool qp_tft_panel_palette_convert(painter_device_t device, int16_t palette_size, qp_pixel_t *palette);
bool qp_tft_panel_append_pixels(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices);
uint16_t qp_rgb888_to_rgb565(uint8_t r, uint8_t g, uint8_t b);
uint16_t qp_rgb888_to_rgb565_swapped(uint8_t r, uint8_t g, uint8_t b);
uint16_t qp_rgb888_to_bgr565(uint8_t r, uint8_t g, uint8_t b);
uint16_t qp_rgb888_to_bgr565_swapped(uint8_t r, uint8_t g, uint8_t b);

View File

@@ -120,7 +120,7 @@ __attribute__((unused)) static enum ps2_mouse_mode_e {
enum ps2_mouse_command_e { enum ps2_mouse_command_e {
PS2_MOUSE_RESET = 0xFF, PS2_MOUSE_RESET = 0xFF,
PS2_MOUSE_RESEND = 0xFE, PS2_MOUSE_RESEND = 0xFE,
PS2_MOSUE_SET_DEFAULTS = 0xF6, PS2_MOUSE_SET_DEFAULTS = 0xF6,
PS2_MOUSE_DISABLE_DATA_REPORTING = 0xF5, PS2_MOUSE_DISABLE_DATA_REPORTING = 0xF5,
PS2_MOUSE_ENABLE_DATA_REPORTING = 0xF4, PS2_MOUSE_ENABLE_DATA_REPORTING = 0xF4,
PS2_MOUSE_SET_SAMPLE_RATE = 0xF3, PS2_MOUSE_SET_SAMPLE_RATE = 0xF3,

View File

@@ -7,6 +7,7 @@
// Masks for Cirque Register Access Protocol (RAP) // Masks for Cirque Register Access Protocol (RAP)
#define WRITE_MASK 0x80 #define WRITE_MASK 0x80
#define READ_MASK 0xA0 #define READ_MASK 0xA0
#define FILLER_BYTE 0xFC
extern bool touchpad_init; extern bool touchpad_init;
@@ -16,11 +17,11 @@ void RAP_ReadBytes(uint8_t address, uint8_t* data, uint8_t count) {
uint8_t cmdByte = READ_MASK | address; // Form the READ command byte uint8_t cmdByte = READ_MASK | address; // Form the READ command byte
if (touchpad_init) { if (touchpad_init) {
if (spi_start(CIRQUE_PINNACLE_SPI_CS_PIN, CIRQUE_PINNACLE_SPI_LSBFIRST, CIRQUE_PINNACLE_SPI_MODE, CIRQUE_PINNACLE_SPI_DIVISOR)) { if (spi_start(CIRQUE_PINNACLE_SPI_CS_PIN, CIRQUE_PINNACLE_SPI_LSBFIRST, CIRQUE_PINNACLE_SPI_MODE, CIRQUE_PINNACLE_SPI_DIVISOR)) {
spi_write(cmdByte); spi_write(cmdByte); // write command byte, receive filler
spi_read(); // filler spi_write(FILLER_BYTE); // write & receive filler
spi_read(); // filler spi_write(FILLER_BYTE); // write & receive filler
for (uint8_t i = 0; i < count; i++) { for (uint8_t i = 0; i < count; i++) {
data[i] = spi_read(); // each sepsequent read gets another register's contents data[i] = spi_write(FILLER_BYTE); // write filler, receive data on the third filler send
} }
} else { } else {
#ifdef CONSOLE_ENABLE #ifdef CONSOLE_ENABLE

View File

@@ -1,6 +1,7 @@
/* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com> /* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
* Copyright 2019 Sunjun Kim * Copyright 2019 Sunjun Kim
* Copyright 2020 Ploopy Corporation * Copyright 2020 Ploopy Corporation
* Copyright 2022 Ulrich Spörlein
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -83,7 +84,11 @@
# define MAX_CPI 0x77 # define MAX_CPI 0x77
#endif #endif
bool _inBurst = false; static const pin_t pins[] = PMW3360_CS_PINS;
#define NUMBER_OF_SENSORS (sizeof(pins) / sizeof(pin_t))
// per-sensor driver state
static bool _inBurst[NUMBER_OF_SENSORS] = {0};
#ifdef CONSOLE_ENABLE #ifdef CONSOLE_ENABLE
void print_byte(uint8_t byte) { void print_byte(uint8_t byte) {
@@ -92,18 +97,18 @@ void print_byte(uint8_t byte) {
#endif #endif
#define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt))) #define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))
bool pmw3360_spi_start(void) { bool pmw3360_spi_start(int8_t index) {
bool status = spi_start(PMW3360_CS_PIN, PMW3360_SPI_LSBFIRST, PMW3360_SPI_MODE, PMW3360_SPI_DIVISOR); bool status = spi_start(pins[index], PMW3360_SPI_LSBFIRST, PMW3360_SPI_MODE, PMW3360_SPI_DIVISOR);
// tNCS-SCLK, 120ns // tNCS-SCLK, 120ns
wait_us(1); wait_us(1);
return status; return status;
} }
spi_status_t pmw3360_write(uint8_t reg_addr, uint8_t data) { spi_status_t pmw3360_write(int8_t index, uint8_t reg_addr, uint8_t data) {
pmw3360_spi_start(); pmw3360_spi_start(index);
if (reg_addr != REG_Motion_Burst) { if (reg_addr != REG_Motion_Burst) {
_inBurst = false; _inBurst[index] = false;
} }
// send address of the register, with MSBit = 1 to indicate it's a write // send address of the register, with MSBit = 1 to indicate it's a write
@@ -114,13 +119,13 @@ spi_status_t pmw3360_write(uint8_t reg_addr, uint8_t data) {
wait_us(35); wait_us(35);
spi_stop(); spi_stop();
// tSWW/tSWR (=180us) minus tSCLK-NCS. Could be shortened, but is looks like a safe lower bound // tSWW/tSWR (=180us) minus tSCLK-NCS. Could be shortened, but it looks like a safe lower bound
wait_us(145); wait_us(145);
return status; return status;
} }
uint8_t pmw3360_read(uint8_t reg_addr) { uint8_t pmw3360_read(int8_t index, uint8_t reg_addr) {
pmw3360_spi_start(); pmw3360_spi_start(index);
// send adress of the register, with MSBit = 0 to indicate it's a read // send adress of the register, with MSBit = 0 to indicate it's a read
spi_write(reg_addr & 0x7f); spi_write(reg_addr & 0x7f);
// tSRAD (=160us) // tSRAD (=160us)
@@ -136,75 +141,24 @@ uint8_t pmw3360_read(uint8_t reg_addr) {
return data; return data;
} }
bool pmw3360_init(void) { bool pmw3360_check_signature(int8_t index) {
setPinOutput(PMW3360_CS_PIN); uint8_t pid = pmw3360_read(index, REG_Product_ID);
uint8_t iv_pid = pmw3360_read(index, REG_Inverse_Product_ID);
spi_init(); uint8_t SROM_ver = pmw3360_read(index, REG_SROM_ID);
_inBurst = false; return (pid == firmware_signature[0] && iv_pid == firmware_signature[1] && SROM_ver == firmware_signature[2]); // signature for SROM 0x04
spi_stop();
pmw3360_spi_start();
spi_stop();
pmw3360_write(REG_Shutdown, 0xb6); // Shutdown first
wait_ms(300);
pmw3360_spi_start();
wait_us(40);
spi_stop();
wait_us(40);
// power up, need to first drive NCS high then low, see above.
pmw3360_write(REG_Power_Up_Reset, 0x5a);
wait_ms(50);
// read registers and discard
pmw3360_read(REG_Motion);
pmw3360_read(REG_Delta_X_L);
pmw3360_read(REG_Delta_X_H);
pmw3360_read(REG_Delta_Y_L);
pmw3360_read(REG_Delta_Y_H);
pmw3360_upload_firmware();
spi_stop();
wait_ms(10);
pmw3360_set_cpi(PMW3360_CPI);
wait_ms(1);
pmw3360_write(REG_Config2, 0x00);
pmw3360_write(REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -127, 127));
pmw3360_write(REG_Lift_Config, PMW3360_LIFTOFF_DISTANCE);
bool init_success = pmw3360_check_signature();
#ifdef CONSOLE_ENABLE
if (init_success) {
dprintf("pmw3360 signature verified");
} else {
dprintf("pmw3360 signature verification failed!");
}
#endif
writePinLow(PMW3360_CS_PIN);
return init_success;
} }
void pmw3360_upload_firmware(void) { void pmw3360_upload_firmware(int8_t index) {
// Datasheet claims we need to disable REST mode first, but during startup // Datasheet claims we need to disable REST mode first, but during startup
// it's already disabled and we're not turning it on ... // it's already disabled and we're not turning it on ...
// pmw3360_write(REG_Config2, 0x00); // disable REST mode // pmw3360_write(index, REG_Config2, 0x00); // disable REST mode
pmw3360_write(REG_SROM_Enable, 0x1d); pmw3360_write(index, REG_SROM_Enable, 0x1d);
wait_ms(10); wait_ms(10);
pmw3360_write(REG_SROM_Enable, 0x18); pmw3360_write(index, REG_SROM_Enable, 0x18);
pmw3360_spi_start(); pmw3360_spi_start(index);
spi_write(REG_SROM_Load_Burst | 0x80); spi_write(REG_SROM_Load_Burst | 0x80);
wait_us(15); wait_us(15);
@@ -216,39 +170,88 @@ void pmw3360_upload_firmware(void) {
} }
wait_us(200); wait_us(200);
pmw3360_read(REG_SROM_ID); pmw3360_read(index, REG_SROM_ID);
pmw3360_write(REG_Config2, 0x00); pmw3360_write(index, REG_Config2, 0x00);
} }
bool pmw3360_check_signature(void) { bool pmw3360_init(int8_t index) {
uint8_t pid = pmw3360_read(REG_Product_ID); if (index >= NUMBER_OF_SENSORS) {
uint8_t iv_pid = pmw3360_read(REG_Inverse_Product_ID); return false;
uint8_t SROM_ver = pmw3360_read(REG_SROM_ID); }
return (pid == firmware_signature[0] && iv_pid == firmware_signature[1] && SROM_ver == firmware_signature[2]); // signature for SROM 0x04 spi_init();
// power up, need to first drive NCS high then low.
// the datasheet does not say for how long, 40us works well in practice.
pmw3360_spi_start(index);
wait_us(40);
spi_stop();
wait_us(40);
pmw3360_write(index, REG_Power_Up_Reset, 0x5a);
wait_ms(50);
// read registers and discard
pmw3360_read(index, REG_Motion);
pmw3360_read(index, REG_Delta_X_L);
pmw3360_read(index, REG_Delta_X_H);
pmw3360_read(index, REG_Delta_Y_L);
pmw3360_read(index, REG_Delta_Y_H);
pmw3360_upload_firmware(index);
spi_stop();
wait_ms(10);
pmw3360_set_cpi(PMW3360_CPI);
wait_ms(1);
pmw3360_write(index, REG_Config2, 0x00);
pmw3360_write(index, REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -127, 127));
pmw3360_write(index, REG_Lift_Config, PMW3360_LIFTOFF_DISTANCE);
bool init_success = pmw3360_check_signature(index);
#ifdef CONSOLE_ENABLE
if (init_success) {
dprintf("pmw3360 signature verified");
} else {
dprintf("pmw3360 signature verification failed!");
}
#endif
return init_success;
} }
// Only support reading the value from sensor #0, no one is using this anyway.
uint16_t pmw3360_get_cpi(void) { uint16_t pmw3360_get_cpi(void) {
uint8_t cpival = pmw3360_read(REG_Config1); uint8_t cpival = pmw3360_read(0, REG_Config1);
return (uint16_t)((cpival + 1) & 0xFF) * CPI_STEP; return (uint16_t)((cpival + 1) & 0xFF) * CPI_STEP;
} }
// Write same CPI to all sensors.
void pmw3360_set_cpi(uint16_t cpi) { void pmw3360_set_cpi(uint16_t cpi) {
uint8_t cpival = constrain((cpi / CPI_STEP) - 1, 0, MAX_CPI); uint8_t cpival = constrain((cpi / CPI_STEP) - 1, 0, MAX_CPI);
pmw3360_write(REG_Config1, cpival); for (size_t i = 0; i < NUMBER_OF_SENSORS; i++) {
pmw3360_write(i, REG_Config1, cpival);
}
} }
report_pmw3360_t pmw3360_read_burst(void) { report_pmw3360_t pmw3360_read_burst(int8_t index) {
report_pmw3360_t report = {0}; report_pmw3360_t report = {0};
if (index >= NUMBER_OF_SENSORS) {
if (!_inBurst) { return report;
#ifdef CONSOLE_ENABLE
dprintf("burst on");
#endif
pmw3360_write(REG_Motion_Burst, 0x00);
_inBurst = true;
} }
pmw3360_spi_start(); if (!_inBurst[index]) {
#ifdef CONSOLE_ENABLE
dprintf("burst on for index %d", index);
#endif
pmw3360_write(index, REG_Motion_Burst, 0x00);
_inBurst[index] = true;
}
pmw3360_spi_start(index);
spi_write(REG_Motion_Burst); spi_write(REG_Motion_Burst);
wait_us(35); // waits for tSRAD_MOTBR wait_us(35); // waits for tSRAD_MOTBR
@@ -261,7 +264,7 @@ report_pmw3360_t pmw3360_read_burst(void) {
report.mdy = spi_read(); report.mdy = spi_read();
if (report.motion & 0b111) { // panic recovery, sometimes burst mode works weird. if (report.motion & 0b111) { // panic recovery, sometimes burst mode works weird.
_inBurst = false; _inBurst[index] = false;
} }
spi_stop(); spi_stop();

View File

@@ -52,8 +52,14 @@
# define ROTATIONAL_TRANSFORM_ANGLE 0x00 # define ROTATIONAL_TRANSFORM_ANGLE 0x00
#endif #endif
// Support single and plural spellings
#ifndef PMW3360_CS_PINS
# ifndef PMW3360_CS_PIN # ifndef PMW3360_CS_PIN
# error "No chip select pin defined -- missing PMW3360_CS_PIN" # error "No chip select pin defined -- missing PMW3360_CS_PIN or PMW3360_CS_PINS"
# else
# define PMW3360_CS_PINS \
{ PMW3360_CS_PIN }
# endif
#endif #endif
typedef struct { typedef struct {
@@ -66,10 +72,8 @@ typedef struct {
int8_t mdy; int8_t mdy;
} report_pmw3360_t; } report_pmw3360_t;
bool pmw3360_init(void); bool pmw3360_init(int8_t index);
void pmw3360_upload_firmware(void);
bool pmw3360_check_signature(void);
uint16_t pmw3360_get_cpi(void); uint16_t pmw3360_get_cpi(void);
void pmw3360_set_cpi(uint16_t cpi); void pmw3360_set_cpi(uint16_t cpi);
/* Reads and clears the current delta values on the sensor */ /* Reads and clears the current delta values on the sensor */
report_pmw3360_t pmw3360_read_burst(void); report_pmw3360_t pmw3360_read_burst(int8_t index);

View File

@@ -154,9 +154,11 @@
#define FIRMWARE_VERSION_SIZE 17 #define FIRMWARE_VERSION_SIZE 17
#define DYNAMIC_KEYMAP_EEPROM_ADDR (EECONFIG_SIZE + FIRMWARE_VERSION_SIZE) #define DYNAMIC_KEYMAP_EEPROM_ADDR (EECONFIG_SIZE + FIRMWARE_VERSION_SIZE)
#define EEPROM_I2C_24LC128
#ifdef EEPROM_I2C
# define DYNAMIC_KEYMAP_EEPROM_MAX_ADDR 16383 # define DYNAMIC_KEYMAP_EEPROM_MAX_ADDR 16383
# define DYNAMIC_KEYMAP_LAYER_COUNT 8 # define DYNAMIC_KEYMAP_LAYER_COUNT 8
#define EEPROM_I2C_24LC128 #endif
#define AUDIO_PIN A5 #define AUDIO_PIN A5
#define AUDIO_PIN_ALT A4 #define AUDIO_PIN_ALT A4

View File

@@ -52,7 +52,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
), ),
[MDIA] = LAYOUT_moonlander( [MDIA] = LAYOUT_moonlander(
LED_LEVEL,_______,_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, RESET, LED_LEVEL,_______,_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, QK_BOOT,
_______, _______, _______, KC_MS_U, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_MS_U, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
_______, _______, KC_MS_L, KC_MS_D, KC_MS_R, _______, _______, _______, _______, _______, _______, _______, _______, KC_MPLY, _______, _______, KC_MS_L, KC_MS_D, KC_MS_R, _______, _______, _______, _______, _______, _______, _______, _______, KC_MPLY,
_______, _______, _______, _______, _______, _______, _______, _______, KC_MPRV, KC_MNXT, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_MPRV, KC_MNXT, _______, _______,

View File

@@ -163,7 +163,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
* `-----------------------------------------------------------------------------------' * `-----------------------------------------------------------------------------------'
*/ */
[_ADJUST] = LAYOUT_planck_grid( [_ADJUST] = LAYOUT_planck_grid(
_______, RESET, DEBUG, RGB_TOG, RGB_MOD, RGB_HUI, RGB_HUD, RGB_SAI, RGB_SAD, RGB_VAI, RGB_VAD, KC_DEL , _______, QK_BOOT, DEBUG, RGB_TOG, RGB_MOD, RGB_HUI, RGB_HUD, RGB_SAI, RGB_SAD, RGB_VAI, RGB_VAD, KC_DEL ,
_______, _______, MU_MOD, AU_ON, AU_OFF, AG_NORM, AG_SWAP, QWERTY, COLEMAK, DVORAK, PLOVER, _______, _______, _______, MU_MOD, AU_ON, AU_OFF, AG_NORM, AG_SWAP, QWERTY, COLEMAK, DVORAK, PLOVER, _______,
_______, MUV_DE, MUV_IN, MU_ON, MU_OFF, MI_ON, MI_OFF, TERM_ON, TERM_OFF, _______, _______, _______, _______, MUV_DE, MUV_IN, MU_ON, MU_OFF, MI_ON, MI_OFF, TERM_ON, TERM_OFF, _______, _______, _______,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______

View File

@@ -1,5 +1,9 @@
"""Functions for working with config.h files. """Functions for working with config.h files.
""" """
from pygments.lexers.c_cpp import CLexer
from pygments.token import Token
from pygments import lex
from itertools import islice
from pathlib import Path from pathlib import Path
import re import re
@@ -10,6 +14,14 @@ from qmk.comment_remover import comment_remover
default_key_entry = {'x': -1, 'y': 0, 'w': 1} default_key_entry = {'x': -1, 'y': 0, 'w': 1}
single_comment_regex = re.compile(r'\s+/[/*].*$') single_comment_regex = re.compile(r'\s+/[/*].*$')
multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE) multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE)
layout_macro_define_regex = re.compile(r'^#\s*define')
def _get_chunks(it, size):
"""Break down a collection into smaller parts
"""
it = iter(it)
return iter(lambda: tuple(islice(it, size)), ())
def strip_line_comment(string): def strip_line_comment(string):
@@ -51,7 +63,7 @@ def find_layouts(file):
file_contents = file_contents.replace('\\\n', '') file_contents = file_contents.replace('\\\n', '')
for line in file_contents.split('\n'): for line in file_contents.split('\n'):
if line.startswith('#define') and '(' in line and 'LAYOUT' in line: if layout_macro_define_regex.match(line.lstrip()) and '(' in line and 'LAYOUT' in line:
# We've found a LAYOUT macro # We've found a LAYOUT macro
macro_name, layout, matrix = _parse_layout_macro(line.strip()) macro_name, layout, matrix = _parse_layout_macro(line.strip())
@@ -169,3 +181,110 @@ def _parse_matrix_locations(matrix, file, macro_name):
matrix_locations[identifier] = [row_num, col_num] matrix_locations[identifier] = [row_num, col_num]
return matrix_locations return matrix_locations
def _coerce_led_token(_type, value):
""" Convert token to valid info.json content
"""
value_map = {
'NO_LED': None,
'LED_FLAG_ALL': 0xFF,
'LED_FLAG_NONE': 0x00,
'LED_FLAG_MODIFIER': 0x01,
'LED_FLAG_UNDERGLOW': 0x02,
'LED_FLAG_KEYLIGHT': 0x04,
'LED_FLAG_INDICATOR': 0x08,
}
if _type is Token.Literal.Number.Integer:
return int(value)
if _type is Token.Literal.Number.Float:
return float(value)
if _type is Token.Literal.Number.Hex:
return int(value, 0)
if _type is Token.Name and value in value_map.keys():
return value_map[value]
def _parse_led_config(file, matrix_cols, matrix_rows):
"""Return any 'raw' led/rgb matrix config
"""
file_contents = file.read_text(encoding='utf-8')
file_contents = comment_remover(file_contents)
file_contents = file_contents.replace('\\\n', '')
matrix_raw = []
position_raw = []
flags = []
found_led_config = False
bracket_count = 0
section = 0
for _type, value in lex(file_contents, CLexer()):
# Assume g_led_config..stuff..;
if value == 'g_led_config':
found_led_config = True
elif value == ';':
found_led_config = False
elif found_led_config:
# Assume bracket count hints to section of config we are within
if value == '{':
bracket_count += 1
if bracket_count == 2:
section += 1
elif value == '}':
bracket_count -= 1
else:
# Assume any non whitespace value here is important enough to stash
if _type in [Token.Literal.Number.Integer, Token.Literal.Number.Float, Token.Literal.Number.Hex, Token.Name]:
if section == 1 and bracket_count == 3:
matrix_raw.append(_coerce_led_token(_type, value))
if section == 2 and bracket_count == 3:
position_raw.append(_coerce_led_token(_type, value))
if section == 3 and bracket_count == 2:
flags.append(_coerce_led_token(_type, value))
# Slightly better intrim format
matrix = list(_get_chunks(matrix_raw, matrix_cols))
position = list(_get_chunks(position_raw, 2))
matrix_indexes = list(filter(lambda x: x is not None, matrix_raw))
# If we have not found anything - bail
if not section:
return None
# TODO: Improve crude parsing/validation
if len(matrix) != matrix_rows and len(matrix) != (matrix_rows / 2):
raise ValueError("Unable to parse g_led_config matrix data")
if len(position) != len(flags):
raise ValueError("Unable to parse g_led_config position data")
if len(matrix_indexes) and (max(matrix_indexes) >= len(flags)):
raise ValueError("OOB within g_led_config matrix data")
return (matrix, position, flags)
def find_led_config(file, matrix_cols, matrix_rows):
"""Search file for led/rgb matrix config
"""
found = _parse_led_config(file, matrix_cols, matrix_rows)
if not found:
return None
# Expand collected content
(matrix, position, flags) = found
# Align to output format
led_config = []
for index, item in enumerate(position, start=0):
led_config.append({
'x': item[0],
'y': item[1],
'flags': flags[index],
})
for r in range(len(matrix)):
for c in range(len(matrix[r])):
index = matrix[r][c]
if index is not None:
led_config[index]['matrix'] = [r, c]
return led_config

View File

@@ -16,7 +16,8 @@ import_names = {
# A mapping of package name to importable name # A mapping of package name to importable name
'pep8-naming': 'pep8ext_naming', 'pep8-naming': 'pep8ext_naming',
'pyusb': 'usb.core', 'pyusb': 'usb.core',
'qmk-dotty-dict': 'dotty_dict' 'qmk-dotty-dict': 'dotty_dict',
'pillow': 'PIL'
} }
safe_commands = [ safe_commands = [
@@ -51,6 +52,7 @@ subcommands = [
'qmk.cli.generate.dfu_header', 'qmk.cli.generate.dfu_header',
'qmk.cli.generate.docs', 'qmk.cli.generate.docs',
'qmk.cli.generate.info_json', 'qmk.cli.generate.info_json',
'qmk.cli.generate.keyboard_c',
'qmk.cli.generate.keyboard_h', 'qmk.cli.generate.keyboard_h',
'qmk.cli.generate.layouts', 'qmk.cli.generate.layouts',
'qmk.cli.generate.rgb_breathe_table', 'qmk.cli.generate.rgb_breathe_table',
@@ -67,6 +69,7 @@ subcommands = [
'qmk.cli.multibuild', 'qmk.cli.multibuild',
'qmk.cli.new.keyboard', 'qmk.cli.new.keyboard',
'qmk.cli.new.keymap', 'qmk.cli.new.keymap',
'qmk.cli.painter',
'qmk.cli.pyformat', 'qmk.cli.pyformat',
'qmk.cli.pytest', 'qmk.cli.pytest',
'qmk.cli.via2json', 'qmk.cli.via2json',

View File

@@ -9,7 +9,7 @@ from milc import cli
from qmk.path import normpath from qmk.path import normpath
from qmk.c_parse import c_source_files from qmk.c_parse import c_source_files
c_file_suffixes = ('c', 'h', 'cpp') c_file_suffixes = ('c', 'h', 'cpp', 'hpp')
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms') core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
ignored = ('tmk_core/protocol/usb_hid', 'platforms/chibios/boards') ignored = ('tmk_core/protocol/usb_hid', 'platforms/chibios/boards')

View File

@@ -1,7 +1,7 @@
"""This script automates the generation of the QMK API data. """This script automates the generation of the QMK API data.
""" """
from pathlib import Path from pathlib import Path
from shutil import copyfile import shutil
import json import json
from milc import cli from milc import cli
@@ -12,28 +12,42 @@ from qmk.json_encoders import InfoJSONEncoder
from qmk.json_schema import json_load from qmk.json_schema import json_load
from qmk.keyboard import find_readme, list_keyboards from qmk.keyboard import find_readme, list_keyboards
TEMPLATE_PATH = Path('data/templates/api/')
BUILD_API_PATH = Path('.build/api_data/')
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.") @cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.")
@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times.")
@cli.subcommand('Creates a new keymap for the keyboard of your choosing', hidden=False if cli.config.user.developer else True) @cli.subcommand('Creates a new keymap for the keyboard of your choosing', hidden=False if cli.config.user.developer else True)
def generate_api(cli): def generate_api(cli):
"""Generates the QMK API data. """Generates the QMK API data.
""" """
api_data_dir = Path('api_data') if BUILD_API_PATH.exists():
v1_dir = api_data_dir / 'v1' shutil.rmtree(BUILD_API_PATH)
shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH)
v1_dir = BUILD_API_PATH / 'v1'
keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything
keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets
keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name
keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization
usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target
if not api_data_dir.exists(): # Filter down when required
api_data_dir.mkdir() keyboard_list = list_keyboards()
if cli.args.filter:
kb_list = []
for keyboard_name in keyboard_list:
if any(i in keyboard_name for i in cli.args.filter):
kb_list.append(keyboard_name)
keyboard_list = kb_list
kb_all = {} kb_all = {}
usb_list = {} usb_list = {}
# Generate and write keyboard specific JSON files # Generate and write keyboard specific JSON files
for keyboard_name in list_keyboards(): for keyboard_name in keyboard_list:
kb_all[keyboard_name] = info_json(keyboard_name) kb_all[keyboard_name] = info_json(keyboard_name)
keyboard_dir = v1_dir / 'keyboards' / keyboard_name keyboard_dir = v1_dir / 'keyboards' / keyboard_name
keyboard_info = keyboard_dir / 'info.json' keyboard_info = keyboard_dir / 'info.json'
@@ -47,7 +61,7 @@ def generate_api(cli):
cli.log.debug('Wrote file %s', keyboard_info) cli.log.debug('Wrote file %s', keyboard_info)
if keyboard_readme_src: if keyboard_readme_src:
copyfile(keyboard_readme_src, keyboard_readme) shutil.copyfile(keyboard_readme_src, keyboard_readme)
cli.log.debug('Copied %s -> %s', keyboard_readme_src, keyboard_readme) cli.log.debug('Copied %s -> %s', keyboard_readme_src, keyboard_readme)
if 'usb' in kb_all[keyboard_name]: if 'usb' in kb_all[keyboard_name]:

View File

@@ -5,10 +5,9 @@ from pathlib import Path
from dotty_dict import dotty from dotty_dict import dotty
from milc import cli from milc import cli
from qmk.info import info_json from qmk.info import info_json, keymap_json_config
from qmk.json_schema import json_load, validate from qmk.json_schema import json_load
from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.keymap import locate_keymap
from qmk.commands import dump_lines from qmk.commands import dump_lines
from qmk.path import normpath from qmk.path import normpath
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
@@ -84,7 +83,7 @@ def generate_config_items(kb_info_json, config_h_lines):
for config_key, info_dict in info_config_map.items(): for config_key, info_dict in info_config_map.items():
info_key = info_dict['info_key'] info_key = info_dict['info_key']
key_type = info_dict.get('value_type', 'str') key_type = info_dict.get('value_type', 'raw')
to_config = info_dict.get('to_config', True) to_config = info_dict.get('to_config', True)
if not to_config: if not to_config:
@@ -95,7 +94,12 @@ def generate_config_items(kb_info_json, config_h_lines):
except KeyError: except KeyError:
continue continue
if key_type.startswith('array'): if key_type.startswith('array.array'):
config_h_lines.append('')
config_h_lines.append(f'#ifndef {config_key}')
config_h_lines.append(f'# define {config_key} {{ {", ".join(["{" + ",".join(list(map(str, x))) + "}" for x in config_value])} }}')
config_h_lines.append(f'#endif // {config_key}')
elif key_type.startswith('array'):
config_h_lines.append('') config_h_lines.append('')
config_h_lines.append(f'#ifndef {config_key}') config_h_lines.append(f'#ifndef {config_key}')
config_h_lines.append(f'# define {config_key} {{ {", ".join(map(str, config_value))} }}') config_h_lines.append(f'# define {config_key} {{ {", ".join(map(str, config_value))} }}')
@@ -112,6 +116,11 @@ def generate_config_items(kb_info_json, config_h_lines):
config_h_lines.append(f'#ifndef {key}') config_h_lines.append(f'#ifndef {key}')
config_h_lines.append(f'# define {key} {value}') config_h_lines.append(f'# define {key} {value}')
config_h_lines.append(f'#endif // {key}') config_h_lines.append(f'#endif // {key}')
elif key_type == 'str':
config_h_lines.append('')
config_h_lines.append(f'#ifndef {config_key}')
config_h_lines.append(f'# define {config_key} "{config_value}"')
config_h_lines.append(f'#endif // {config_key}')
elif key_type == 'bcd_version': elif key_type == 'bcd_version':
(major, minor, revision) = config_value.split('.') (major, minor, revision) = config_value.split('.')
config_h_lines.append('') config_h_lines.append('')
@@ -175,10 +184,7 @@ def generate_config_h(cli):
""" """
# Determine our keyboard/keymap # Determine our keyboard/keymap
if cli.args.keymap: if cli.args.keymap:
km = locate_keymap(cli.args.keyboard, cli.args.keymap) kb_info_json = dotty(keymap_json_config(cli.args.keyboard, cli.args.keymap))
km_json = json_load(km)
validate(km_json, 'qmk.keymap.v1')
kb_info_json = dotty(km_json.get('config', {}))
else: else:
kb_info_json = dotty(info_json(cli.args.keyboard)) kb_info_json = dotty(info_json(cli.args.keyboard))

View File

@@ -10,6 +10,7 @@ DOCS_PATH = Path('docs/')
BUILD_PATH = Path('.build/') BUILD_PATH = Path('.build/')
BUILD_DOCS_PATH = BUILD_PATH / 'docs' BUILD_DOCS_PATH = BUILD_PATH / 'docs'
DOXYGEN_PATH = BUILD_PATH / 'doxygen' DOXYGEN_PATH = BUILD_PATH / 'doxygen'
MOXYGEN_PATH = BUILD_DOCS_PATH / 'internals'
@cli.subcommand('Build QMK documentation.', hidden=False if cli.config.user.developer else True) @cli.subcommand('Build QMK documentation.', hidden=False if cli.config.user.developer else True)
@@ -34,10 +35,10 @@ def generate_docs(cli):
'stdin': DEVNULL, 'stdin': DEVNULL,
} }
cli.log.info('Generating internal docs...') cli.log.info('Generating docs...')
# Generate internal docs # Generate internal docs
cli.run(['doxygen', 'Doxyfile'], **args) cli.run(['doxygen', 'Doxyfile'], **args)
cli.run(['moxygen', '-q', '-g', '-o', BUILD_DOCS_PATH / 'internals_%s.md', DOXYGEN_PATH / 'xml'], **args) cli.run(['moxygen', '-q', '-g', '-o', MOXYGEN_PATH / '%s.md', DOXYGEN_PATH / 'xml'], **args)
cli.log.info('Successfully generated internal docs to %s.', BUILD_DOCS_PATH) cli.log.info('Successfully generated docs to %s.', BUILD_DOCS_PATH)

View File

@@ -0,0 +1,75 @@
"""Used by the make system to generate keyboard.c from info.json.
"""
from milc import cli
from qmk.info import info_json
from qmk.commands import dump_lines
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.path import normpath
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
def _gen_led_config(info_data):
"""Convert info.json content to g_led_config
"""
cols = info_data['matrix_size']['cols']
rows = info_data['matrix_size']['rows']
config_type = None
if 'layout' in info_data.get('rgb_matrix', {}):
config_type = 'rgb_matrix'
elif 'layout' in info_data.get('led_matrix', {}):
config_type = 'led_matrix'
lines = []
if not config_type:
return lines
matrix = [['NO_LED'] * cols for i in range(rows)]
pos = []
flags = []
led_config = info_data[config_type]['layout']
for index, item in enumerate(led_config, start=0):
if 'matrix' in item:
(x, y) = item['matrix']
matrix[x][y] = str(index)
pos.append(f'{{ {item.get("x", 0)},{item.get("y", 0)} }}')
flags.append(str(item.get('flags', 0)))
if config_type == 'rgb_matrix':
lines.append('#ifdef RGB_MATRIX_ENABLE')
lines.append('#include "rgb_matrix.h"')
elif config_type == 'led_matrix':
lines.append('#ifdef LED_MATRIX_ENABLE')
lines.append('#include "led_matrix.h"')
lines.append('__attribute__ ((weak)) led_config_t g_led_config = {')
lines.append(' {')
for line in matrix:
lines.append(f' {{ {",".join(line)} }},')
lines.append(' },')
lines.append(f' {{ {",".join(pos)} }},')
lines.append(f' {{ {",".join(flags)} }},')
lines.append('};')
lines.append('#endif')
return lines
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.c for.')
@cli.subcommand('Used by the make system to generate keyboard.c from info.json', hidden=True)
def generate_keyboard_c(cli):
"""Generates the keyboard.h file.
"""
kb_info_json = info_json(cli.args.keyboard)
# Build the layouts.h file.
keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', '']
keyboard_h_lines.extend(_gen_led_config(kb_info_json))
# Show the results
dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet)

View File

@@ -34,7 +34,7 @@ def generate_rgb_breathe_table(cli):
""" """
breathe_values = [0] * 256 breathe_values = [0] * 256
for pos in range(0, 256): for pos in range(0, 256):
breathe_values[pos] = (int)((math.exp(math.sin((pos/255) * math.pi)) - cli.args.center / math.e) * (cli.args.max / (math.e - 1 / math.e))) # noqa: yapf insists there be no whitespace around / breathe_values[pos] = (int)((math.exp(math.sin((pos / 255) * math.pi)) - cli.args.center / math.e) * (cli.args.max / (math.e - 1 / math.e)))
values_template = '' values_template = ''
for s in range(0, 3): for s in range(0, 3):
@@ -46,7 +46,7 @@ def generate_rgb_breathe_table(cli):
values_template += ' ' if pos % 8 == 0 else '' values_template += ' ' if pos % 8 == 0 else ''
values_template += '0x{:02X}'.format(breathe_values[pos]) values_template += '0x{:02X}'.format(breathe_values[pos])
values_template += ',' if (pos + step) < 256 else '' values_template += ',' if (pos + step) < 256 else ''
values_template += '\n' if (pos+step) % 8 == 0 else ' ' # noqa: yapf insists there be no whitespace around + values_template += '\n' if (pos + step) % 8 == 0 else ' '
values_template += '#endif' values_template += '#endif'
values_template += '\n\n' if s < 2 else '' values_template += '\n\n' if s < 2 else ''

View File

@@ -5,10 +5,9 @@ from pathlib import Path
from dotty_dict import dotty from dotty_dict import dotty
from milc import cli from milc import cli
from qmk.info import info_json from qmk.info import info_json, keymap_json_config
from qmk.json_schema import json_load, validate from qmk.json_schema import json_load
from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.keymap import locate_keymap
from qmk.commands import dump_lines from qmk.commands import dump_lines
from qmk.path import normpath from qmk.path import normpath
from qmk.constants import GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE from qmk.constants import GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE
@@ -21,7 +20,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
return None return None
info_key = info_dict['info_key'] info_key = info_dict['info_key']
key_type = info_dict.get('value_type', 'str') key_type = info_dict.get('value_type', 'raw')
try: try:
rules_value = kb_info_json[info_key] rules_value = kb_info_json[info_key]
@@ -34,6 +33,8 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
return f'{rules_key} ?= {"yes" if rules_value else "no"}' return f'{rules_key} ?= {"yes" if rules_value else "no"}'
elif key_type == 'mapping': elif key_type == 'mapping':
return '\n'.join([f'{key} ?= {value}' for key, value in rules_value.items()]) return '\n'.join([f'{key} ?= {value}' for key, value in rules_value.items()])
elif key_type == 'str':
return f'{rules_key} ?= "{rules_value}"'
return f'{rules_key} ?= {rules_value}' return f'{rules_key} ?= {rules_value}'
@@ -49,10 +50,7 @@ def generate_rules_mk(cli):
""" """
# Determine our keyboard/keymap # Determine our keyboard/keymap
if cli.args.keymap: if cli.args.keymap:
km = locate_keymap(cli.args.keyboard, cli.args.keymap) kb_info_json = dotty(keymap_json_config(cli.args.keyboard, cli.args.keymap))
km_json = json_load(km)
validate(km_json, 'qmk.keymap.v1')
kb_info_json = dotty(km_json.get('config', {}))
else: else:
kb_info_json = dotty(info_json(cli.args.keyboard)) kb_info_json = dotty(info_json(cli.args.keyboard))

View File

@@ -11,8 +11,8 @@ from qmk.json_encoders import InfoJSONEncoder
from qmk.constants import COL_LETTERS, ROW_LETTERS from qmk.constants import COL_LETTERS, ROW_LETTERS
from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.keyboard import keyboard_completer, keyboard_folder, render_layouts, render_layout, rules_mk from qmk.keyboard import keyboard_completer, keyboard_folder, render_layouts, render_layout, rules_mk
from qmk.info import info_json, keymap_json
from qmk.keymap import locate_keymap from qmk.keymap import locate_keymap
from qmk.info import info_json
from qmk.path import is_keyboard from qmk.path import is_keyboard
UNICODE_SUPPORT = sys.stdout.encoding.lower().startswith('utf') UNICODE_SUPPORT = sys.stdout.encoding.lower().startswith('utf')
@@ -135,7 +135,7 @@ def print_parsed_rules_mk(keyboard_name):
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.') @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')
@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') @cli.argument('-km', '--keymap', help='Keymap to show info for (Optional).')
@cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.') @cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.')
@cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.') @cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.')
@cli.argument('-f', '--format', default='friendly', arg_only=True, help='Format to display the data in (friendly, text, json) (Default: friendly).') @cli.argument('-f', '--format', default='friendly', arg_only=True, help='Format to display the data in (friendly, text, json) (Default: friendly).')
@@ -161,7 +161,14 @@ def info(cli):
print_parsed_rules_mk(cli.config.info.keyboard) print_parsed_rules_mk(cli.config.info.keyboard)
return False return False
# default keymap stored in config file should be ignored
if cli.config_source.info.keymap == 'config_file':
cli.config_source.info.keymap = None
# Build the info.json file # Build the info.json file
if cli.config.info.keymap:
kb_info_json = keymap_json(cli.config.info.keyboard, cli.config.info.keymap)
else:
kb_info_json = info_json(cli.config.info.keyboard) kb_info_json = info_json(cli.config.info.keyboard)
# Output in the requested format # Output in the requested format
@@ -178,11 +185,12 @@ def info(cli):
cli.log.error('Unknown format: %s', cli.args.format) cli.log.error('Unknown format: %s', cli.args.format)
return False return False
# Output requested extras
if cli.config.info.layouts: if cli.config.info.layouts:
show_layouts(kb_info_json, title_caps) show_layouts(kb_info_json, title_caps)
if cli.config.info.matrix: if cli.config.info.matrix:
show_matrix(kb_info_json, title_caps) show_matrix(kb_info_json, title_caps)
if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file': if cli.config.info.keymap:
show_keymap(kb_info_json, title_caps) show_keymap(kb_info_json, title_caps)

View File

@@ -116,6 +116,13 @@ def lint(cli):
if not keymap_check(kb, cli.config.lint.keymap): if not keymap_check(kb, cli.config.lint.keymap):
ok = False ok = False
# Check if all non-data driven macros exist in <keyboard.h>
for layout, data in keyboard_info['layouts'].items():
# Matrix data should be a list with exactly two integers: [0, 1]
if not data['c_macro'] and not all('matrix' in key_data.keys() or len(key_data) == 2 or all(isinstance(n, int) for n in key_data) for key_data in data['layout']):
cli.log.error(f'{kb}: "{layout}" has no "matrix" definition in either "info.json" or "<keyboard>.h"!')
ok = False
# Report status # Report status
if not ok: if not ok:
failed.append(kb) failed.append(kb)

View File

@@ -14,7 +14,7 @@ from qmk.git import git_get_username
from qmk.json_schema import load_jsonschema from qmk.json_schema import load_jsonschema
from qmk.path import keyboard from qmk.path import keyboard
from qmk.json_encoders import InfoJSONEncoder from qmk.json_encoders import InfoJSONEncoder
from qmk.json_schema import deep_update from qmk.json_schema import deep_update, json_load
from qmk.constants import MCU2BOOTLOADER from qmk.constants import MCU2BOOTLOADER
COMMUNITY = Path('layouts/community/') COMMUNITY = Path('layouts/community/')
@@ -23,13 +23,14 @@ TEMPLATE = Path('data/templates/keyboard/')
# defaults # defaults
schema = dotty(load_jsonschema('keyboard')) schema = dotty(load_jsonschema('keyboard'))
mcu_types = sorted(schema["properties.processor.enum"], key=str.casefold) mcu_types = sorted(schema["properties.processor.enum"], key=str.casefold)
dev_boards = sorted(schema["properties.development_board.enum"], key=str.casefold)
available_layouts = sorted([x.name for x in COMMUNITY.iterdir() if x.is_dir()]) available_layouts = sorted([x.name for x in COMMUNITY.iterdir() if x.is_dir()])
def mcu_type(mcu): def mcu_type(mcu):
"""Callable for argparse validation. """Callable for argparse validation.
""" """
if mcu not in mcu_types: if mcu not in (dev_boards + mcu_types):
raise ValueError raise ValueError
return mcu return mcu
@@ -176,14 +177,14 @@ https://docs.qmk.fm/#/compatible_microcontrollers
MCU? """ MCU? """
# remove any options strictly used for compatibility # remove any options strictly used for compatibility
filtered_mcu = [x for x in mcu_types if not any(xs in x for xs in ['cortex', 'unknown'])] filtered_mcu = [x for x in (dev_boards + mcu_types) if not any(xs in x for xs in ['cortex', 'unknown'])]
return choice(prompt, filtered_mcu, default=filtered_mcu.index("atmega32u4")) return choice(prompt, filtered_mcu, default=filtered_mcu.index("atmega32u4"))
@cli.argument('-kb', '--keyboard', help='Specify the name for the new keyboard directory', arg_only=True, type=keyboard_name) @cli.argument('-kb', '--keyboard', help='Specify the name for the new keyboard directory', arg_only=True, type=keyboard_name)
@cli.argument('-l', '--layout', help='Community layout to bootstrap with', arg_only=True, type=layout_type) @cli.argument('-l', '--layout', help='Community layout to bootstrap with', arg_only=True, type=layout_type)
@cli.argument('-t', '--type', help='Specify the keyboard MCU type', arg_only=True, type=mcu_type) @cli.argument('-t', '--type', help='Specify the keyboard MCU type (or "development_board" preset)', arg_only=True, type=mcu_type)
@cli.argument('-u', '--username', help='Specify your username (default from Git config)', dest='name') @cli.argument('-u', '--username', help='Specify your username (default from Git config)', dest='name')
@cli.argument('-n', '--realname', help='Specify your real name if you want to use that. Defaults to username', arg_only=True) @cli.argument('-n', '--realname', help='Specify your real name if you want to use that. Defaults to username', arg_only=True)
@cli.subcommand('Creates a new keyboard directory') @cli.subcommand('Creates a new keyboard directory')
@@ -198,7 +199,6 @@ def new_keyboard(cli):
real_name = cli.args.realname or cli.config.new_keyboard.name if cli.args.realname or cli.config.new_keyboard.name else prompt_name(user_name) real_name = cli.args.realname or cli.config.new_keyboard.name if cli.args.realname or cli.config.new_keyboard.name else prompt_name(user_name)
default_layout = cli.args.layout if cli.args.layout else prompt_layout() default_layout = cli.args.layout if cli.args.layout else prompt_layout()
mcu = cli.args.type if cli.args.type else prompt_mcu() mcu = cli.args.type if cli.args.type else prompt_mcu()
bootloader = select_default_bootloader(mcu)
if not validate_keyboard_name(kb_name): if not validate_keyboard_name(kb_name):
cli.log.error('Keyboard names must contain only {fg_cyan}lowercase a-z{fg_reset}, {fg_cyan}0-9{fg_reset}, and {fg_cyan}_{fg_reset}! Please choose a different name.') cli.log.error('Keyboard names must contain only {fg_cyan}lowercase a-z{fg_reset}, {fg_cyan}0-9{fg_reset}, and {fg_cyan}_{fg_reset}! Please choose a different name.')
@@ -208,6 +208,16 @@ def new_keyboard(cli):
cli.log.error(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.') cli.log.error(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.')
return 1 return 1
# Preprocess any development_board presets
if mcu in dev_boards:
defaults_map = json_load(Path('data/mappings/defaults.json'))
board = defaults_map['development_board'][mcu]
mcu = board['processor']
bootloader = board['bootloader']
else:
bootloader = select_default_bootloader(mcu)
tokens = { # Comment here is to force multiline formatting tokens = { # Comment here is to force multiline formatting
'YEAR': str(date.today().year), 'YEAR': str(date.today().year),
'KEYBOARD': kb_name, 'KEYBOARD': kb_name,

View File

@@ -0,0 +1,2 @@
from . import convert_graphics
from . import make_font

View File

@@ -0,0 +1,86 @@
"""This script tests QGF functionality.
"""
import re
import datetime
from io import BytesIO
from qmk.path import normpath
from qmk.painter import render_header, render_source, render_license, render_bytes, valid_formats
from milc import cli
from PIL import Image
@cli.argument('-v', '--verbose', arg_only=True, action='store_true', help='Turns on verbose output.')
@cli.argument('-i', '--input', required=True, help='Specify input graphic file.')
@cli.argument('-o', '--output', default='', help='Specify output directory. Defaults to same directory as input.')
@cli.argument('-f', '--format', required=True, help='Output format, valid types: %s' % (', '.join(valid_formats.keys())))
@cli.argument('-r', '--no-rle', arg_only=True, action='store_true', help='Disables the use of RLE when encoding images.')
@cli.argument('-d', '--no-deltas', arg_only=True, action='store_true', help='Disables the use of delta frames when encoding animations.')
@cli.subcommand('Converts an input image to something QMK understands')
def painter_convert_graphics(cli):
"""Converts an image file to a format that Quantum Painter understands.
This command uses the `qmk.painter` module to generate a Quantum Painter image defintion from an image. The generated definitions are written to a files next to the input -- `INPUT.c` and `INPUT.h`.
"""
# Work out the input file
if cli.args.input != '-':
cli.args.input = normpath(cli.args.input)
# Error checking
if not cli.args.input.exists():
cli.log.error('Input image file does not exist!')
cli.print_usage()
return False
# Work out the output directory
if len(cli.args.output) == 0:
cli.args.output = cli.args.input.parent
cli.args.output = normpath(cli.args.output)
# Ensure we have a valid format
if cli.args.format not in valid_formats.keys():
cli.log.error('Output format %s is invalid. Allowed values: %s' % (cli.args.format, ', '.join(valid_formats.keys())))
cli.print_usage()
return False
# Work out the encoding parameters
format = valid_formats[cli.args.format]
# Load the input image
input_img = Image.open(cli.args.input)
# Convert the image to QGF using PIL
out_data = BytesIO()
input_img.save(out_data, "QGF", use_deltas=(not cli.args.no_deltas), use_rle=(not cli.args.no_rle), qmk_format=format, verbose=cli.args.verbose)
out_bytes = out_data.getvalue()
# Work out the text substitutions for rendering the output data
subs = {
'generated_type': 'image',
'var_prefix': 'gfx',
'generator_command': f'qmk painter-convert-graphics -i {cli.args.input.name} -f {cli.args.format}',
'year': datetime.date.today().strftime("%Y"),
'input_file': cli.args.input.name,
'sane_name': re.sub(r"[^a-zA-Z0-9]", "_", cli.args.input.stem),
'byte_count': len(out_bytes),
'bytes_lines': render_bytes(out_bytes),
'format': cli.args.format,
}
# Render the license
subs.update({'license': render_license(subs)})
# Render and write the header file
header_text = render_header(subs)
header_file = cli.args.output / (cli.args.input.stem + ".qgf.h")
with open(header_file, 'w') as header:
print(f"Writing {header_file}...")
header.write(header_text)
header.close()
# Render and write the source file
source_text = render_source(subs)
source_file = cli.args.output / (cli.args.input.stem + ".qgf.c")
with open(source_file, 'w') as source:
print(f"Writing {source_file}...")
source.write(source_text)
source.close()

View File

@@ -0,0 +1,87 @@
"""This script automates the conversion of font files into a format QMK firmware understands.
"""
import re
import datetime
from io import BytesIO
from qmk.path import normpath
from qmk.painter_qff import QFFFont
from qmk.painter import render_header, render_source, render_license, render_bytes, valid_formats
from milc import cli
@cli.argument('-f', '--font', required=True, help='Specify input font file.')
@cli.argument('-o', '--output', required=True, help='Specify output image path.')
@cli.argument('-s', '--size', default=12, help='Specify font size. Default 12.')
@cli.argument('-n', '--no-ascii', arg_only=True, action='store_true', help='Disables output of the full ASCII character set (0x20..0x7E), exporting only the glyphs specified.')
@cli.argument('-u', '--unicode-glyphs', default='', help='Also generate the specified unicode glyphs.')
@cli.argument('-a', '--no-aa', arg_only=True, action='store_true', help='Disable anti-aliasing on fonts.')
@cli.subcommand('Converts an input font to something QMK understands')
def painter_make_font_image(cli):
# Create the font object
font = QFFFont(cli)
# Read from the input file
cli.args.font = normpath(cli.args.font)
font.generate_image(cli.args.font, cli.args.size, include_ascii_glyphs=(not cli.args.no_ascii), unicode_glyphs=cli.args.unicode_glyphs, use_aa=(False if cli.args.no_aa else True))
# Render out the data
font.save_to_image(normpath(cli.args.output))
@cli.argument('-i', '--input', help='Specify input graphic file.')
@cli.argument('-o', '--output', default='', help='Specify output directory. Defaults to same directory as input.')
@cli.argument('-n', '--no-ascii', arg_only=True, action='store_true', help='Disables output of the full ASCII character set (0x20..0x7E), exporting only the glyphs specified.')
@cli.argument('-u', '--unicode-glyphs', default='', help='Also generate the specified unicode glyphs.')
@cli.argument('-f', '--format', required=True, help='Output format, valid types: %s' % (', '.join(valid_formats.keys())))
@cli.argument('-r', '--no-rle', arg_only=True, action='store_true', help='Disable the use of RLE to minimise converted image size.')
@cli.subcommand('Converts an input font image to something QMK firmware understands')
def painter_convert_font_image(cli):
# Work out the format
format = valid_formats[cli.args.format]
# Create the font object
font = QFFFont(cli.log)
# Read from the input file
cli.args.input = normpath(cli.args.input)
font.read_from_image(cli.args.input, include_ascii_glyphs=(not cli.args.no_ascii), unicode_glyphs=cli.args.unicode_glyphs)
# Work out the output directory
if len(cli.args.output) == 0:
cli.args.output = cli.args.input.parent
cli.args.output = normpath(cli.args.output)
# Render out the data
out_data = BytesIO()
font.save_to_qff(format, (False if cli.args.no_rle else True), out_data)
# Work out the text substitutions for rendering the output data
subs = {
'generated_type': 'font',
'var_prefix': 'font',
'generator_command': f'qmk painter-convert-font-image -i {cli.args.input.name} -f {cli.args.format}',
'year': datetime.date.today().strftime("%Y"),
'input_file': cli.args.input.name,
'sane_name': re.sub(r"[^a-zA-Z0-9]", "_", cli.args.input.stem),
'byte_count': out_data.getbuffer().nbytes,
'bytes_lines': render_bytes(out_data.getbuffer().tobytes()),
'format': cli.args.format,
}
# Render the license
subs.update({'license': render_license(subs)})
# Render and write the header file
header_text = render_header(subs)
header_file = cli.args.output / (cli.args.input.stem + ".qff.h")
with open(header_file, 'w') as header:
print(f"Writing {header_file}...")
header.write(header_text)
header.close()
# Render and write the source file
source_text = render_source(subs)
source_file = cli.args.output / (cli.args.input.stem + ".qff.c")
with open(source_file, 'w') as source:
print(f"Writing {source_file}...")
source.write(source_text)
source.close()

View File

@@ -12,8 +12,7 @@ from milc import cli
def pytest(cli): def pytest(cli):
"""Run several linting/testing commands. """Run several linting/testing commands.
""" """
nose2 = cli.run(['nose2', '-v', '-t' nose2 = cli.run(['nose2', '-v', '-t', 'lib/python', *cli.args.test], capture_output=False, stdin=DEVNULL)
'lib/python', *cli.args.test], capture_output=False, stdin=DEVNULL)
flake8 = cli.run(['flake8', 'lib/python'], capture_output=False, stdin=DEVNULL) flake8 = cli.run(['flake8', 'lib/python'], capture_output=False, stdin=DEVNULL)
return flake8.returncode | nose2.returncode return flake8.returncode | nose2.returncode

View File

@@ -228,7 +228,7 @@ def dump_lines(output_file, lines, quiet=True):
output_file.parent.mkdir(parents=True, exist_ok=True) output_file.parent.mkdir(parents=True, exist_ok=True)
if output_file.exists(): if output_file.exists():
output_file.replace(output_file.parent / (output_file.name + '.bak')) output_file.replace(output_file.parent / (output_file.name + '.bak'))
output_file.write_text(generated) output_file.write_text(generated, encoding='utf-8')
if not quiet: if not quiet:
cli.log.info(f'Wrote {output_file.name} to {output_file}.') cli.log.info(f'Wrote {output_file.name} to {output_file}.')

View File

@@ -14,7 +14,7 @@ QMK_FIRMWARE_UPSTREAM = 'qmk/qmk_firmware'
MAX_KEYBOARD_SUBFOLDERS = 5 MAX_KEYBOARD_SUBFOLDERS = 5
# Supported processor types # Supported processor types
CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66FX1M0', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71' CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66FX1M0', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95'
LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None
VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85'
@@ -43,6 +43,7 @@ MCU2BOOTLOADER = {
"STM32L443": "stm32-dfu", "STM32L443": "stm32-dfu",
"GD32VF103": "gd32v-dfu", "GD32VF103": "gd32v-dfu",
"WB32F3G71": "wb32-dfu", "WB32F3G71": "wb32-dfu",
"WB32FQ95": "wb32-dfu",
"atmega16u2": "atmel-dfu", "atmega16u2": "atmel-dfu",
"atmega32u2": "atmel-dfu", "atmega32u2": "atmel-dfu",
"atmega16u4": "atmel-dfu", "atmega16u4": "atmel-dfu",

View File

@@ -8,10 +8,11 @@ from dotty_dict import dotty
from milc import cli from milc import cli
from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS
from qmk.c_parse import find_layouts from qmk.c_parse import find_layouts, parse_config_h_file, find_led_config
from qmk.json_schema import deep_update, json_load, validate from qmk.json_schema import deep_update, json_load, validate
from qmk.keyboard import config_h, rules_mk from qmk.keyboard import config_h, rules_mk
from qmk.keymap import list_keymaps from qmk.keymap import list_keymaps, locate_keymap
from qmk.commands import parse_configurator_json
from qmk.makefile import parse_rules_mk_file from qmk.makefile import parse_rules_mk_file
from qmk.math import compute from qmk.math import compute
@@ -68,12 +69,16 @@ def info_json(keyboard):
# Merge in the data from info.json, config.h, and rules.mk # Merge in the data from info.json, config.h, and rules.mk
info_data = merge_info_jsons(keyboard, info_data) info_data = merge_info_jsons(keyboard, info_data)
info_data = _extract_rules_mk(info_data) info_data = _process_defaults(info_data)
info_data = _extract_config_h(info_data) info_data = _extract_rules_mk(info_data, rules_mk(str(keyboard)))
info_data = _extract_config_h(info_data, config_h(str(keyboard)))
# Ensure that we have matrix row and column counts # Ensure that we have matrix row and column counts
info_data = _matrix_size(info_data) info_data = _matrix_size(info_data)
# Merge in data from <keyboard.c>
info_data = _extract_led_config(info_data, str(keyboard))
# Validate against the jsonschema # Validate against the jsonschema
try: try:
validate(info_data, 'qmk.api.keyboard.v1') validate(info_data, 'qmk.api.keyboard.v1')
@@ -166,28 +171,46 @@ def _extract_pins(pins):
return [_pin_name(pin) for pin in pins.split(',')] return [_pin_name(pin) for pin in pins.split(',')]
def _extract_direct_matrix(direct_pins): def _extract_2d_array(raw):
"""Return a 2d array of strings
""" """
""" out_array = []
direct_pin_array = []
while direct_pins[-1] != '}': while raw[-1] != '}':
direct_pins = direct_pins[:-1] raw = raw[:-1]
for row in direct_pins.split('},{'): for row in raw.split('},{'):
if row.startswith('{'): if row.startswith('{'):
row = row[1:] row = row[1:]
if row.endswith('}'): if row.endswith('}'):
row = row[:-1] row = row[:-1]
direct_pin_array.append([]) out_array.append([])
for pin in row.split(','): for val in row.split(','):
if pin == 'NO_PIN': out_array[-1].append(val)
pin = None
direct_pin_array[-1].append(pin) return out_array
def _extract_2d_int_array(raw):
"""Return a 2d array of ints
"""
ret = _extract_2d_array(raw)
return [list(map(int, x)) for x in ret]
def _extract_direct_matrix(direct_pins):
"""extract direct_matrix
"""
direct_pin_array = _extract_2d_array(direct_pins)
for i in range(len(direct_pin_array)):
for j in range(len(direct_pin_array[i])):
if direct_pin_array[i][j] == 'NO_PIN':
direct_pin_array[i][j] = None
return direct_pin_array return direct_pin_array
@@ -205,6 +228,21 @@ def _extract_audio(info_data, config_c):
info_data['audio'] = {'pins': audio_pins} info_data['audio'] = {'pins': audio_pins}
def _extract_secure_unlock(info_data, config_c):
"""Populate data about the secure unlock sequence
"""
unlock = config_c.get('SECURE_UNLOCK_SEQUENCE', '').replace(' ', '')[1:-1]
if unlock:
unlock_array = _extract_2d_int_array(unlock)
if 'secure' not in info_data:
info_data['secure'] = {}
if 'unlock_sequence' in info_data['secure']:
_log_warning(info_data, 'Secure unlock sequence is specified in both config.h (SECURE_UNLOCK_SEQUENCE) and info.json (secure.unlock_sequence) (Value: %s), the config.h value wins.' % info_data['secure']['unlock_sequence'])
info_data['secure']['unlock_sequence'] = unlock_array
def _extract_split_main(info_data, config_c): def _extract_split_main(info_data, config_c):
"""Populate data about the split configuration """Populate data about the split configuration
""" """
@@ -270,13 +308,15 @@ def _extract_split_transport(info_data, config_c):
info_data['split']['transport']['protocol'] = 'i2c' info_data['split']['transport']['protocol'] = 'i2c'
elif 'protocol' not in info_data.get('split', {}).get('transport', {}): # Ignore transport defaults if "SPLIT_KEYBOARD" is unset
elif 'enabled' in info_data.get('split', {}):
if 'split' not in info_data: if 'split' not in info_data:
info_data['split'] = {} info_data['split'] = {}
if 'transport' not in info_data['split']: if 'transport' not in info_data['split']:
info_data['split']['transport'] = {} info_data['split']['transport'] = {}
if 'protocol' not in info_data['split']['transport']:
info_data['split']['transport']['protocol'] = 'serial' info_data['split']['transport']['protocol'] = 'serial'
@@ -400,18 +440,16 @@ def _extract_device_version(info_data):
info_data['usb']['device_version'] = f'{major}.{minor}.{revision}' info_data['usb']['device_version'] = f'{major}.{minor}.{revision}'
def _extract_config_h(info_data): def _extract_config_h(info_data, config_c):
"""Pull some keyboard information from existing config.h files """Pull some keyboard information from existing config.h files
""" """
config_c = config_h(info_data['keyboard_folder'])
# Pull in data from the json map # Pull in data from the json map
dotty_info = dotty(info_data) dotty_info = dotty(info_data)
info_config_map = json_load(Path('data/mappings/info_config.json')) info_config_map = json_load(Path('data/mappings/info_config.json'))
for config_key, info_dict in info_config_map.items(): for config_key, info_dict in info_config_map.items():
info_key = info_dict['info_key'] info_key = info_dict['info_key']
key_type = info_dict.get('value_type', 'str') key_type = info_dict.get('value_type', 'raw')
try: try:
if config_key in config_c and info_dict.get('to_json', True): if config_key in config_c and info_dict.get('to_json', True):
@@ -443,6 +481,9 @@ def _extract_config_h(info_data):
elif key_type == 'int': elif key_type == 'int':
dotty_info[info_key] = int(config_c[config_key]) dotty_info[info_key] = int(config_c[config_key])
elif key_type == 'str':
dotty_info[info_key] = config_c[config_key].strip('"')
elif key_type == 'bcd_version': elif key_type == 'bcd_version':
major = int(config_c[config_key][2:4]) major = int(config_c[config_key][2:4])
minor = int(config_c[config_key][4]) minor = int(config_c[config_key][4])
@@ -461,6 +502,7 @@ def _extract_config_h(info_data):
# Pull data that easily can't be mapped in json # Pull data that easily can't be mapped in json
_extract_matrix_info(info_data, config_c) _extract_matrix_info(info_data, config_c)
_extract_audio(info_data, config_c) _extract_audio(info_data, config_c)
_extract_secure_unlock(info_data, config_c)
_extract_split_main(info_data, config_c) _extract_split_main(info_data, config_c)
_extract_split_transport(info_data, config_c) _extract_split_transport(info_data, config_c)
_extract_split_right_pins(info_data, config_c) _extract_split_right_pins(info_data, config_c)
@@ -469,10 +511,21 @@ def _extract_config_h(info_data):
return info_data return info_data
def _extract_rules_mk(info_data): def _process_defaults(info_data):
"""Process any additional defaults based on currently discovered information
"""
defaults_map = json_load(Path('data/mappings/defaults.json'))
for default_type in defaults_map.keys():
thing_map = defaults_map[default_type]
if default_type in info_data:
for key, value in thing_map.get(info_data[default_type], {}).items():
info_data[key] = value
return info_data
def _extract_rules_mk(info_data, rules):
"""Pull some keyboard information from existing rules.mk files """Pull some keyboard information from existing rules.mk files
""" """
rules = rules_mk(info_data['keyboard_folder'])
info_data['processor'] = rules.get('MCU', info_data.get('processor', 'atmega32u4')) info_data['processor'] = rules.get('MCU', info_data.get('processor', 'atmega32u4'))
if info_data['processor'] in CHIBIOS_PROCESSORS: if info_data['processor'] in CHIBIOS_PROCESSORS:
@@ -491,7 +544,7 @@ def _extract_rules_mk(info_data):
for rules_key, info_dict in info_rules_map.items(): for rules_key, info_dict in info_rules_map.items():
info_key = info_dict['info_key'] info_key = info_dict['info_key']
key_type = info_dict.get('value_type', 'str') key_type = info_dict.get('value_type', 'raw')
try: try:
if rules_key in rules and info_dict.get('to_json', True): if rules_key in rules and info_dict.get('to_json', True):
@@ -523,6 +576,9 @@ def _extract_rules_mk(info_data):
elif key_type == 'int': elif key_type == 'int':
dotty_info[info_key] = int(rules[rules_key]) dotty_info[info_key] = int(rules[rules_key])
elif key_type == 'str':
dotty_info[info_key] = rules[rules_key].strip('"')
else: else:
dotty_info[info_key] = rules[rules_key] dotty_info[info_key] = rules[rules_key]
@@ -537,6 +593,46 @@ def _extract_rules_mk(info_data):
return info_data return info_data
def find_keyboard_c(keyboard):
"""Find all <keyboard>.c files
"""
keyboard = Path(keyboard)
current_path = Path('keyboards/')
files = []
for directory in keyboard.parts:
current_path = current_path / directory
keyboard_c_path = current_path / f'{directory}.c'
if keyboard_c_path.exists():
files.append(keyboard_c_path)
return files
def _extract_led_config(info_data, keyboard):
"""Scan all <keyboard>.c files for led config
"""
cols = info_data['matrix_size']['cols']
rows = info_data['matrix_size']['rows']
# Assume what feature owns g_led_config
feature = "rgb_matrix"
if info_data.get("features", {}).get("led_matrix", False):
feature = "led_matrix"
# Process
for file in find_keyboard_c(keyboard):
try:
ret = find_led_config(file, cols, rows)
if ret:
info_data[feature] = info_data.get(feature, {})
info_data[feature]["layout"] = ret
except Exception as e:
_log_warning(info_data, f'led_config: {file.name}: {e}')
return info_data
def _matrix_size(info_data): def _matrix_size(info_data):
"""Add info_data['matrix_size'] if it doesn't exist. """Add info_data['matrix_size'] if it doesn't exist.
""" """
@@ -753,10 +849,43 @@ def find_info_json(keyboard):
# Add in parent folders for least specific # Add in parent folders for least specific
for _ in range(5): for _ in range(5):
info_jsons.append(keyboard_parent / 'info.json') if keyboard_parent == base_path:
if keyboard_parent.parent == base_path:
break break
info_jsons.append(keyboard_parent / 'info.json')
keyboard_parent = keyboard_parent.parent keyboard_parent = keyboard_parent.parent
# Return a list of the info.json files that actually exist # Return a list of the info.json files that actually exist
return [info_json for info_json in info_jsons if info_json.exists()] return [info_json for info_json in info_jsons if info_json.exists()]
def keymap_json_config(keyboard, keymap):
"""Extract keymap level config
"""
keymap_folder = locate_keymap(keyboard, keymap).parent
km_info_json = parse_configurator_json(keymap_folder / 'keymap.json')
return km_info_json.get('config', {})
def keymap_json(keyboard, keymap):
"""Generate the info.json data for a specific keymap.
"""
keymap_folder = locate_keymap(keyboard, keymap).parent
# Files to scan
keymap_config = keymap_folder / 'config.h'
keymap_rules = keymap_folder / 'rules.mk'
keymap_file = keymap_folder / 'keymap.json'
# Build the info.json file
kb_info_json = info_json(keyboard)
# Merge in the data from keymap.json
km_info_json = keymap_json_config(keyboard, keymap) if keymap_file.exists() else {}
deep_update(kb_info_json, km_info_json)
# Merge in the data from config.h, and rules.mk
_extract_rules_mk(kb_info_json, parse_rules_mk_file(keymap_rules))
_extract_config_h(kb_info_json, parse_config_h_file(keymap_config))
return kb_info_json

View File

@@ -75,8 +75,8 @@ class InfoJSONEncoder(QMKJSONEncoder):
"""Encode info.json dictionaries. """Encode info.json dictionaries.
""" """
if obj: if obj:
if self.indentation_level == 4: if set(("x", "y")).issubset(obj.keys()):
# These are part of a layout, put them on a single line. # These are part of a layout/led_config, put them on a single line.
return "{ " + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items())) + " }" return "{ " + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items())) + " }"
else: else:

View File

@@ -68,6 +68,10 @@ def create_validator(schema):
schema_store = compile_schema_store() schema_store = compile_schema_store()
resolver = jsonschema.RefResolver.from_schema(schema_store[schema], store=schema_store) resolver = jsonschema.RefResolver.from_schema(schema_store[schema], store=schema_store)
# TODO: Remove this after the jsonschema>=4 requirement had time to reach users
try:
return jsonschema.Draft202012Validator(schema_store[schema], resolver=resolver).validate
except AttributeError:
return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate

View File

@@ -218,7 +218,7 @@ def render_key_rect(textpad, x, y, w, h, label, style):
label_blank = ' ' * label_len label_blank = ' ' * label_len
label_border = box_chars['h'] * label_len label_border = box_chars['h'] * label_len
label_middle = label + ' '*label_leftover # noqa: yapf insists there be no whitespace around * label_middle = label + ' ' * label_leftover
top_line = array('u', box_chars['tl'] + label_border + box_chars['tr']) top_line = array('u', box_chars['tl'] + label_border + box_chars['tr'])
lab_line = array('u', box_chars['v'] + label_middle + box_chars['v']) lab_line = array('u', box_chars['v'] + label_middle + box_chars['v'])
@@ -245,10 +245,10 @@ def render_key_isoenter(textpad, x, y, w, h, label, style):
if len(label) > label_len: if len(label) > label_len:
label = label[:label_len] label = label[:label_len]
label_blank = ' ' * (label_len-1) # noqa: yapf insists there be no whitespace around - and * label_blank = ' ' * (label_len - 1)
label_border_top = box_chars['h'] * label_len label_border_top = box_chars['h'] * label_len
label_border_bottom = box_chars['h'] * (label_len-1) # noqa label_border_bottom = box_chars['h'] * (label_len - 1)
label_middle = label + ' '*label_leftover # noqa label_middle = label + ' ' * label_leftover
top_line = array('u', box_chars['tl'] + label_border_top + box_chars['tr']) top_line = array('u', box_chars['tl'] + label_border_top + box_chars['tr'])
lab_line = array('u', box_chars['v'] + label_middle + box_chars['v']) lab_line = array('u', box_chars['v'] + label_middle + box_chars['v'])
@@ -277,10 +277,10 @@ def render_key_baenter(textpad, x, y, w, h, label, style):
if len(label) > label_len: if len(label) > label_len:
label = label[:label_len] label = label[:label_len]
label_blank = ' ' * (label_len-3) # noqa: yapf insists there be no whitespace around - and * label_blank = ' ' * (label_len - 3)
label_border_top = box_chars['h'] * (label_len-3) # noqa label_border_top = box_chars['h'] * (label_len - 3)
label_border_bottom = box_chars['h'] * label_len label_border_bottom = box_chars['h'] * label_len
label_middle = label + ' '*label_leftover # noqa label_middle = label + ' ' * label_leftover
top_line = array('u', box_chars['tl'] + label_border_top + box_chars['tr']) top_line = array('u', box_chars['tl'] + label_border_top + box_chars['tr'])
mid_line = array('u', box_chars['v'] + label_blank + box_chars['v']) mid_line = array('u', box_chars['v'] + label_blank + box_chars['v'])

268
lib/python/qmk/painter.py Normal file
View File

@@ -0,0 +1,268 @@
"""Functions that help us work with Quantum Painter's file formats.
"""
import math
import re
from string import Template
from PIL import Image, ImageOps
# The list of valid formats Quantum Painter supports
valid_formats = {
'pal256': {
'image_format': 'IMAGE_FORMAT_PALETTE',
'bpp': 8,
'has_palette': True,
'num_colors': 256,
'image_format_byte': 0x07, # see qp_internal_formats.h
},
'pal16': {
'image_format': 'IMAGE_FORMAT_PALETTE',
'bpp': 4,
'has_palette': True,
'num_colors': 16,
'image_format_byte': 0x06, # see qp_internal_formats.h
},
'pal4': {
'image_format': 'IMAGE_FORMAT_PALETTE',
'bpp': 2,
'has_palette': True,
'num_colors': 4,
'image_format_byte': 0x05, # see qp_internal_formats.h
},
'pal2': {
'image_format': 'IMAGE_FORMAT_PALETTE',
'bpp': 1,
'has_palette': True,
'num_colors': 2,
'image_format_byte': 0x04, # see qp_internal_formats.h
},
'mono256': {
'image_format': 'IMAGE_FORMAT_GRAYSCALE',
'bpp': 8,
'has_palette': False,
'num_colors': 256,
'image_format_byte': 0x03, # see qp_internal_formats.h
},
'mono16': {
'image_format': 'IMAGE_FORMAT_GRAYSCALE',
'bpp': 4,
'has_palette': False,
'num_colors': 16,
'image_format_byte': 0x02, # see qp_internal_formats.h
},
'mono4': {
'image_format': 'IMAGE_FORMAT_GRAYSCALE',
'bpp': 2,
'has_palette': False,
'num_colors': 4,
'image_format_byte': 0x01, # see qp_internal_formats.h
},
'mono2': {
'image_format': 'IMAGE_FORMAT_GRAYSCALE',
'bpp': 1,
'has_palette': False,
'num_colors': 2,
'image_format_byte': 0x00, # see qp_internal_formats.h
}
}
license_template = """\
// Copyright ${year} QMK -- generated source code only, ${generated_type} retains original copyright
// SPDX-License-Identifier: GPL-2.0-or-later
// This file was auto-generated by `${generator_command}`
"""
def render_license(subs):
license_txt = Template(license_template)
return license_txt.substitute(subs)
header_file_template = """\
${license}
#pragma once
#include <qp.h>
extern const uint32_t ${var_prefix}_${sane_name}_length;
extern const uint8_t ${var_prefix}_${sane_name}[${byte_count}];
"""
def render_header(subs):
header_txt = Template(header_file_template)
return header_txt.substitute(subs)
source_file_template = """\
${license}
#include <qp.h>
const uint32_t ${var_prefix}_${sane_name}_length = ${byte_count};
// clang-format off
const uint8_t ${var_prefix}_${sane_name}[${byte_count}] = {
${bytes_lines}
};
// clang-format on
"""
def render_source(subs):
source_txt = Template(source_file_template)
return source_txt.substitute(subs)
def render_bytes(bytes, newline_after=16):
lines = ''
for n in range(len(bytes)):
if n % newline_after == 0 and n > 0 and n != len(bytes):
lines = lines + "\n "
elif n == 0:
lines = lines + " "
lines = lines + " 0x{0:02X},".format(bytes[n])
return lines.rstrip()
def clean_output(str):
str = re.sub(r'\r', '', str)
str = re.sub(r'[\n]{3,}', r'\n\n', str)
return str
def rescale_byte(val, maxval):
"""Rescales a byte value to the supplied range, i.e. [0,255] -> [0,maxval].
"""
return int(round(val * maxval / 255.0))
def convert_requested_format(im, format):
"""Convert an image to the requested format.
"""
# Work out the requested format
ncolors = format["num_colors"]
image_format = format["image_format"]
# Ensure we have a valid number of colors for the palette
if ncolors <= 0 or ncolors > 256 or (ncolors & (ncolors - 1) != 0):
raise ValueError("Number of colors must be 2, 4, 16, or 256.")
# Work out where we're getting the bytes from
if image_format == 'IMAGE_FORMAT_GRAYSCALE':
# If mono, convert input to grayscale, then to RGB, then grab the raw bytes corresponding to the intensity of the red channel
im = ImageOps.grayscale(im)
im = im.convert("RGB")
elif image_format == 'IMAGE_FORMAT_PALETTE':
# If color, convert input to RGB, palettize based on the supplied number of colors, then get the raw palette bytes
im = im.convert("RGB")
im = im.convert("P", palette=Image.ADAPTIVE, colors=ncolors)
return im
def convert_image_bytes(im, format):
"""Convert the supplied image to the equivalent bytes required by the QMK firmware.
"""
# Work out the requested format
ncolors = format["num_colors"]
image_format = format["image_format"]
shifter = int(math.log2(ncolors))
pixels_per_byte = int(8 / math.log2(ncolors))
(width, height) = im.size
expected_byte_count = ((width * height) + (pixels_per_byte - 1)) // pixels_per_byte
if image_format == 'IMAGE_FORMAT_GRAYSCALE':
# Take the red channel
image_bytes = im.tobytes("raw", "R")
image_bytes_len = len(image_bytes)
# No palette
palette = None
bytearray = []
for x in range(expected_byte_count):
byte = 0
for n in range(pixels_per_byte):
byte_offset = x * pixels_per_byte + n
if byte_offset < image_bytes_len:
# If mono, each input byte is a grayscale [0,255] pixel -- rescale to the range we want then pack together
byte = byte | (rescale_byte(image_bytes[byte_offset], ncolors - 1) << int(n * shifter))
bytearray.append(byte)
elif image_format == 'IMAGE_FORMAT_PALETTE':
# Convert each pixel to the palette bytes
image_bytes = im.tobytes("raw", "P")
image_bytes_len = len(image_bytes)
# Export the palette
palette = []
pal = im.getpalette()
for n in range(0, ncolors * 3, 3):
palette.append((pal[n + 0], pal[n + 1], pal[n + 2]))
bytearray = []
for x in range(expected_byte_count):
byte = 0
for n in range(pixels_per_byte):
byte_offset = x * pixels_per_byte + n
if byte_offset < image_bytes_len:
# If color, each input byte is the index into the color palette -- pack them together
byte = byte | ((image_bytes[byte_offset] & (ncolors - 1)) << int(n * shifter))
bytearray.append(byte)
if len(bytearray) != expected_byte_count:
raise Exception(f"Wrong byte count, was {len(bytearray)}, expected {expected_byte_count}")
return (palette, bytearray)
def compress_bytes_qmk_rle(bytearray):
debug_dump = False
output = []
temp = []
repeat = False
def append_byte(c):
if debug_dump:
print('Appending byte:', '0x{0:02X}'.format(int(c)), '=', c)
output.append(c)
def append_range(r):
append_byte(127 + len(r))
if debug_dump:
print('Appending {0} byte(s):'.format(len(r)), '[', ', '.join(['{0:02X}'.format(e) for e in r]), ']')
output.extend(r)
for n in range(0, len(bytearray) + 1):
end = True if n == len(bytearray) else False
if not end:
c = bytearray[n]
temp.append(c)
if len(temp) <= 1:
continue
if debug_dump:
print('Temp buffer state {0:3d} bytes:'.format(len(temp)), '[', ', '.join(['{0:02X}'.format(e) for e in temp]), ']')
if repeat:
if temp[-1] != temp[-2]:
repeat = False
if not repeat or len(temp) == 128 or end:
append_byte(len(temp) if end else len(temp) - 1)
append_byte(temp[0])
temp = [temp[-1]]
repeat = False
else:
if len(temp) >= 2 and temp[-1] == temp[-2]:
repeat = True
if len(temp) > 2:
append_range(temp[0:(len(temp) - 2)])
temp = [temp[-1], temp[-1]]
continue
if len(temp) == 128 or end:
append_range(temp)
temp = []
repeat = False
return output

View File

@@ -0,0 +1,401 @@
# Copyright 2021 Nick Brassel (@tzarc)
# SPDX-License-Identifier: GPL-2.0-or-later
# Quantum Font File "QFF" Font File Format.
# See https://docs.qmk.fm/#/quantum_painter_qff for more information.
from pathlib import Path
from typing import Dict, Any
from colorsys import rgb_to_hsv
from PIL import Image, ImageDraw, ImageFont, ImageChops
from PIL._binary import o8, o16le as o16, o32le as o32
from qmk.painter_qgf import QGFBlockHeader, QGFFramePaletteDescriptorV1
from milc.attrdict import AttrDict
import qmk.painter
def o24(i):
return o16(i & 0xFFFF) + o8((i & 0xFF0000) >> 16)
########################################################################################################################
class QFFGlyphInfo(AttrDict):
def __init__(self, *args, **kwargs):
super().__init__()
for n, value in enumerate(args):
self[f'arg:{n}'] = value
for key, value in kwargs.items():
self[key] = value
def write(self, fp, include_code_point):
if include_code_point is True:
fp.write(o24(ord(self.code_point)))
value = ((self.data_offset << 6) & 0xFFFFC0) | (self.w & 0x3F)
fp.write(o24(value))
########################################################################################################################
class QFFFontDescriptor:
type_id = 0x00
length = 20
magic = 0x464651
def __init__(self):
self.header = QGFBlockHeader()
self.header.type_id = QFFFontDescriptor.type_id
self.header.length = QFFFontDescriptor.length
self.version = 1
self.total_file_size = 0
self.line_height = 0
self.has_ascii_table = False
self.unicode_glyph_count = 0
self.format = 0xFF
self.flags = 0
self.compression = 0xFF
self.transparency_index = 0xFF # TODO: Work out how to retrieve the transparent palette entry from the PIL gif loader
def write(self, fp):
self.header.write(fp)
fp.write(
b'' # start off with empty bytes...
+ o24(QFFFontDescriptor.magic) # magic
+ o8(self.version) # version
+ o32(self.total_file_size) # file size
+ o32((~self.total_file_size) & 0xFFFFFFFF) # negated file size
+ o8(self.line_height) # line height
+ o8(1 if self.has_ascii_table is True else 0) # whether or not we have an ascii table present
+ o16(self.unicode_glyph_count & 0xFFFF) # number of unicode glyphs present
+ o8(self.format) # format
+ o8(self.flags) # flags
+ o8(self.compression) # compression
+ o8(self.transparency_index) # transparency index
)
@property
def is_transparent(self):
return (self.flags & 0x01) == 0x01
@is_transparent.setter
def is_transparent(self, val):
if val:
self.flags |= 0x01
else:
self.flags &= ~0x01
########################################################################################################################
class QFFAsciiGlyphTableV1:
type_id = 0x01
length = 95 * 3 # We have 95 glyphs: [0x20...0x7E]
def __init__(self):
self.header = QGFBlockHeader()
self.header.type_id = QFFAsciiGlyphTableV1.type_id
self.header.length = QFFAsciiGlyphTableV1.length
# Each glyph is key=code_point, value=QFFGlyphInfo
self.glyphs = {}
def add_glyph(self, glyph: QFFGlyphInfo):
self.glyphs[ord(glyph.code_point)] = glyph
def write(self, fp):
self.header.write(fp)
for n in range(0x20, 0x7F):
self.glyphs[n].write(fp, False)
########################################################################################################################
class QFFUnicodeGlyphTableV1:
type_id = 0x02
def __init__(self):
self.header = QGFBlockHeader()
self.header.type_id = QFFUnicodeGlyphTableV1.type_id
self.header.length = 0
# Each glyph is key=code_point, value=QFFGlyphInfo
self.glyphs = {}
def add_glyph(self, glyph: QFFGlyphInfo):
self.glyphs[ord(glyph.code_point)] = glyph
def write(self, fp):
self.header.length = len(self.glyphs.keys()) * 6
self.header.write(fp)
for n in sorted(self.glyphs.keys()):
self.glyphs[n].write(fp, True)
########################################################################################################################
class QFFFontDataDescriptorV1:
type_id = 0x04
def __init__(self):
self.header = QGFBlockHeader()
self.header.type_id = QFFFontDataDescriptorV1.type_id
self.data = []
def write(self, fp):
self.header.length = len(self.data)
self.header.write(fp)
fp.write(bytes(self.data))
########################################################################################################################
def _generate_font_glyphs_list(use_ascii, unicode_glyphs):
# The set of glyphs that we want to generate images for
glyphs = {}
# Add ascii charset if requested
if use_ascii is True:
for c in range(0x20, 0x7F): # does not include 0x7F!
glyphs[chr(c)] = True
# Append any extra unicode glyphs
unicode_glyphs = list(unicode_glyphs)
for c in unicode_glyphs:
glyphs[c] = True
return sorted(glyphs.keys())
class QFFFont:
def __init__(self, logger):
self.logger = logger
self.image = None
self.glyph_data = {}
self.glyph_height = 0
return
def _extract_glyphs(self, format):
total_data_size = 0
total_rle_data_size = 0
converted_img = qmk.painter.convert_requested_format(self.image, format)
(self.palette, _) = qmk.painter.convert_image_bytes(converted_img, format)
# Work out how many bytes used for RLE vs. non-RLE
for _, glyph_entry in self.glyph_data.items():
glyph_img = converted_img.crop((glyph_entry.x, 1, glyph_entry.x + glyph_entry.w, 1 + self.glyph_height))
(_, this_glyph_image_bytes) = qmk.painter.convert_image_bytes(glyph_img, format)
this_glyph_rle_bytes = qmk.painter.compress_bytes_qmk_rle(this_glyph_image_bytes)
total_data_size += len(this_glyph_image_bytes)
total_rle_data_size += len(this_glyph_rle_bytes)
glyph_entry['image_uncompressed_bytes'] = this_glyph_image_bytes
glyph_entry['image_compressed_bytes'] = this_glyph_rle_bytes
return (total_data_size, total_rle_data_size)
def _parse_image(self, img, include_ascii_glyphs: bool = True, unicode_glyphs: str = ''):
# Clear out any existing font metadata
self.image = None
# Each glyph is key=code_point, value={ x: ?, w: ? }
self.glyph_data = {}
self.glyph_height = 0
# Work out the list of glyphs required
glyphs = _generate_font_glyphs_list(include_ascii_glyphs, unicode_glyphs)
# Work out the geometry
(width, height) = img.size
# Work out the glyph offsets/widths
glyph_pixel_offsets = []
glyph_pixel_widths = []
pixels = img.load()
# Run through the markers and work out where each glyph starts/stops
glyph_split_color = pixels[0, 0] # top left pixel is the marker color we're going to use to split each glyph
glyph_pixel_offsets.append(0)
last_offset = 0
for x in range(1, width):
if pixels[x, 0] == glyph_split_color:
glyph_pixel_offsets.append(x)
glyph_pixel_widths.append(x - last_offset)
last_offset = x
glyph_pixel_widths.append(width - last_offset)
# Make sure the number of glyphs we're attempting to generate matches the input image
if len(glyph_pixel_offsets) != len(glyphs):
self.logger.error('The number of glyphs to generate doesn\'t match the number of detected glyphs in the input image.')
return
# Set up the required metadata for each glyph
for n in range(0, len(glyph_pixel_offsets)):
self.glyph_data[glyphs[n]] = QFFGlyphInfo(code_point=glyphs[n], x=glyph_pixel_offsets[n], w=glyph_pixel_widths[n])
# Parsing was successful, keep the image in this instance
self.image = img
self.glyph_height = height - 1 # subtract the line with the markers
def generate_image(self, ttf_file: Path, font_size: int, include_ascii_glyphs: bool = True, unicode_glyphs: str = '', include_before_left: bool = False, use_aa: bool = True):
# Load the font
font = ImageFont.truetype(str(ttf_file), int(font_size))
# Work out the max font size
max_font_size = font.font.ascent + abs(font.font.descent)
# Work out the list of glyphs required
glyphs = _generate_font_glyphs_list(include_ascii_glyphs, unicode_glyphs)
baseline_offset = 9999999
total_glyph_width = 0
max_glyph_height = -1
# Measure each glyph to determine the overall baseline offset required
for glyph in glyphs:
(ls_l, ls_t, ls_r, ls_b) = font.getbbox(glyph, anchor='ls')
glyph_width = (ls_r - ls_l) if include_before_left else (ls_r)
glyph_height = font.getbbox(glyph, anchor='la')[3]
if max_glyph_height < glyph_height:
max_glyph_height = glyph_height
total_glyph_width += glyph_width
if baseline_offset > ls_t:
baseline_offset = ls_t
# Create the output image
img = Image.new("RGB", (total_glyph_width + 1, max_font_size * 2 + 1), (0, 0, 0, 255))
cur_x_pos = 0
# Loop through each glyph...
for glyph in glyphs:
# Work out this glyph's bounding box
(ls_l, ls_t, ls_r, ls_b) = font.getbbox(glyph, anchor='ls')
glyph_width = (ls_r - ls_l) if include_before_left else (ls_r)
glyph_height = ls_b - ls_t
x_offset = -ls_l
y_offset = ls_t - baseline_offset
# Draw each glyph to its own image so we don't get anti-aliasing applied to the final image when straddling edges
glyph_img = Image.new("RGB", (glyph_width, max_font_size), (0, 0, 0, 255))
glyph_draw = ImageDraw.Draw(glyph_img)
if not use_aa:
glyph_draw.fontmode = "1"
glyph_draw.text((x_offset, y_offset), glyph, font=font, anchor='lt')
# Place the glyph-specific image in the correct location overall
img.paste(glyph_img, (cur_x_pos, 1))
# Set up the marker for start of each glyph
pixels = img.load()
pixels[cur_x_pos, 0] = (255, 0, 255)
# Increment for the next glyph's position
cur_x_pos += glyph_width
# Add the ending marker so that the difference/crop works
pixels = img.load()
pixels[cur_x_pos, 0] = (255, 0, 255)
# Determine the usable font area
dummy_img = Image.new("RGB", (total_glyph_width + 1, max_font_size + 1), (0, 0, 0, 255))
bbox = ImageChops.difference(img, dummy_img).getbbox()
bbox = (bbox[0], bbox[1], bbox[2] - 1, bbox[3]) # remove the unused end-marker
# Crop and re-parse the resulting image to ensure we're generating the correct format
self._parse_image(img.crop(bbox), include_ascii_glyphs, unicode_glyphs)
def save_to_image(self, img_file: Path):
# Drop out if there's no image loaded
if self.image is None:
self.logger.error('No image is loaded.')
return
# Save the image to the supplied file
self.image.save(str(img_file))
def read_from_image(self, img_file: Path, include_ascii_glyphs: bool = True, unicode_glyphs: str = ''):
# Load and parse the supplied image file
self._parse_image(Image.open(str(img_file)), include_ascii_glyphs, unicode_glyphs)
return
def save_to_qff(self, format: Dict[str, Any], use_rle: bool, fp):
# Drop out if there's no image loaded
if self.image is None:
self.logger.error('No image is loaded.')
return
# Work out if we want to use RLE at all, skipping it if it's not any smaller (it's applied per-glyph)
(total_data_size, total_rle_data_size) = self._extract_glyphs(format)
if use_rle:
use_rle = (total_rle_data_size < total_data_size)
# For each glyph, work out which image data we want to use and append it to the image buffer, recording the byte-wise offset
img_buffer = bytes()
for _, glyph_entry in self.glyph_data.items():
glyph_entry['data_offset'] = len(img_buffer)
glyph_img_bytes = glyph_entry.image_compressed_bytes if use_rle else glyph_entry.image_uncompressed_bytes
img_buffer += bytes(glyph_img_bytes)
font_descriptor = QFFFontDescriptor()
ascii_table = QFFAsciiGlyphTableV1()
unicode_table = QFFUnicodeGlyphTableV1()
data_descriptor = QFFFontDataDescriptorV1()
data_descriptor.data = img_buffer
# Check if we have all the ASCII glyphs present
include_ascii_glyphs = all([chr(n) in self.glyph_data for n in range(0x20, 0x7F)])
# Helper for populating the blocks
for code_point, glyph_entry in self.glyph_data.items():
if ord(code_point) >= 0x20 and ord(code_point) <= 0x7E and include_ascii_glyphs:
ascii_table.add_glyph(glyph_entry)
else:
unicode_table.add_glyph(glyph_entry)
# Configure the font descriptor
font_descriptor.line_height = self.glyph_height
font_descriptor.has_ascii_table = include_ascii_glyphs
font_descriptor.unicode_glyph_count = len(unicode_table.glyphs.keys())
font_descriptor.is_transparent = False
font_descriptor.format = format['image_format_byte']
font_descriptor.compression = 0x01 if use_rle else 0x00
# Write a dummy font descriptor -- we'll have to come back and write it properly once we've rendered out everything else
font_descriptor_location = fp.tell()
font_descriptor.write(fp)
# Write out the ASCII table if required
if font_descriptor.has_ascii_table:
ascii_table.write(fp)
# Write out the unicode table if required
if font_descriptor.unicode_glyph_count > 0:
unicode_table.write(fp)
# Write out the palette if required
if format['has_palette']:
palette_descriptor = QGFFramePaletteDescriptorV1()
# Helper to convert from RGB888 to the QMK "dialect" of HSV888
def rgb888_to_qmk_hsv888(e):
hsv = rgb_to_hsv(e[0] / 255.0, e[1] / 255.0, e[2] / 255.0)
return (int(hsv[0] * 255.0), int(hsv[1] * 255.0), int(hsv[2] * 255.0))
# Convert all palette entries to HSV888 and write to the output
palette_descriptor.palette_entries = list(map(rgb888_to_qmk_hsv888, self.palette))
palette_descriptor.write(fp)
# Write out the image data
data_descriptor.write(fp)
# Now fix up the overall font descriptor, then write it in the correct location
font_descriptor.total_file_size = fp.tell()
fp.seek(font_descriptor_location, 0)
font_descriptor.write(fp)

View File

@@ -0,0 +1,408 @@
# Copyright 2021 Nick Brassel (@tzarc)
# SPDX-License-Identifier: GPL-2.0-or-later
# Quantum Graphics File "QGF" Image File Format.
# See https://docs.qmk.fm/#/quantum_painter_qgf for more information.
from colorsys import rgb_to_hsv
from types import FunctionType
from PIL import Image, ImageFile, ImageChops
from PIL._binary import o8, o16le as o16, o32le as o32
import qmk.painter
def o24(i):
return o16(i & 0xFFFF) + o8((i & 0xFF0000) >> 16)
########################################################################################################################
class QGFBlockHeader:
block_size = 5
def write(self, fp):
fp.write(b'' # start off with empty bytes...
+ o8(self.type_id) # block type id
+ o8((~self.type_id) & 0xFF) # negated block type id
+ o24(self.length) # blob length
)
########################################################################################################################
class QGFGraphicsDescriptor:
type_id = 0x00
length = 18
magic = 0x464751
def __init__(self):
self.header = QGFBlockHeader()
self.header.type_id = QGFGraphicsDescriptor.type_id
self.header.length = QGFGraphicsDescriptor.length
self.version = 1
self.total_file_size = 0
self.image_width = 0
self.image_height = 0
self.frame_count = 0
def write(self, fp):
self.header.write(fp)
fp.write(
b'' # start off with empty bytes...
+ o24(QGFGraphicsDescriptor.magic) # magic
+ o8(self.version) # version
+ o32(self.total_file_size) # file size
+ o32((~self.total_file_size) & 0xFFFFFFFF) # negated file size
+ o16(self.image_width) # width
+ o16(self.image_height) # height
+ o16(self.frame_count) # frame count
)
########################################################################################################################
class QGFFrameOffsetDescriptorV1:
type_id = 0x01
def __init__(self, frame_count):
self.header = QGFBlockHeader()
self.header.type_id = QGFFrameOffsetDescriptorV1.type_id
self.frame_offsets = [0xFFFFFFFF] * frame_count
self.frame_count = frame_count
def write(self, fp):
self.header.length = len(self.frame_offsets) * 4
self.header.write(fp)
for offset in self.frame_offsets:
fp.write(b'' # start off with empty bytes...
+ o32(offset) # offset
)
########################################################################################################################
class QGFFrameDescriptorV1:
type_id = 0x02
length = 6
def __init__(self):
self.header = QGFBlockHeader()
self.header.type_id = QGFFrameDescriptorV1.type_id
self.header.length = QGFFrameDescriptorV1.length
self.format = 0xFF
self.flags = 0
self.compression = 0xFF
self.transparency_index = 0xFF # TODO: Work out how to retrieve the transparent palette entry from the PIL gif loader
self.delay = 1000 # Placeholder until it gets read from the animation
def write(self, fp):
self.header.write(fp)
fp.write(b'' # start off with empty bytes...
+ o8(self.format) # format
+ o8(self.flags) # flags
+ o8(self.compression) # compression
+ o8(self.transparency_index) # transparency index
+ o16(self.delay) # delay
)
@property
def is_transparent(self):
return (self.flags & 0x01) == 0x01
@is_transparent.setter
def is_transparent(self, val):
if val:
self.flags |= 0x01
else:
self.flags &= ~0x01
@property
def is_delta(self):
return (self.flags & 0x02) == 0x02
@is_delta.setter
def is_delta(self, val):
if val:
self.flags |= 0x02
else:
self.flags &= ~0x02
########################################################################################################################
class QGFFramePaletteDescriptorV1:
type_id = 0x03
def __init__(self):
self.header = QGFBlockHeader()
self.header.type_id = QGFFramePaletteDescriptorV1.type_id
self.header.length = 0
self.palette_entries = [(0xFF, 0xFF, 0xFF)] * 4
def write(self, fp):
self.header.length = len(self.palette_entries) * 3
self.header.write(fp)
for entry in self.palette_entries:
fp.write(b'' # start off with empty bytes...
+ o8(entry[0]) # h
+ o8(entry[1]) # s
+ o8(entry[2]) # v
)
########################################################################################################################
class QGFFrameDeltaDescriptorV1:
type_id = 0x04
length = 8
def __init__(self):
self.header = QGFBlockHeader()
self.header.type_id = QGFFrameDeltaDescriptorV1.type_id
self.header.length = QGFFrameDeltaDescriptorV1.length
self.left = 0
self.top = 0
self.right = 0
self.bottom = 0
def write(self, fp):
self.header.write(fp)
fp.write(b'' # start off with empty bytes...
+ o16(self.left) # left
+ o16(self.top) # top
+ o16(self.right) # right
+ o16(self.bottom) # bottom
)
########################################################################################################################
class QGFFrameDataDescriptorV1:
type_id = 0x05
def __init__(self):
self.header = QGFBlockHeader()
self.header.type_id = QGFFrameDataDescriptorV1.type_id
self.data = []
def write(self, fp):
self.header.length = len(self.data)
self.header.write(fp)
fp.write(bytes(self.data))
########################################################################################################################
class QGFImageFile(ImageFile.ImageFile):
format = "QGF"
format_description = "Quantum Graphics File Format"
def _open(self):
raise NotImplementedError("Reading QGF files is not supported")
########################################################################################################################
def _accept(prefix):
"""Helper method used by PIL to work out if it can parse an input file.
Currently unimplemented.
"""
return False
def _save(im, fp, filename):
"""Helper method used by PIL to write to an output file.
"""
# Work out from the parameters if we need to do anything special
encoderinfo = im.encoderinfo.copy()
append_images = list(encoderinfo.get("append_images", []))
verbose = encoderinfo.get("verbose", False)
use_deltas = encoderinfo.get("use_deltas", True)
use_rle = encoderinfo.get("use_rle", True)
# Helper for inline verbose prints
def vprint(s):
if verbose:
print(s)
# Helper to iterate through all frames in the input image
def _for_all_frames(x: FunctionType):
frame_num = 0
last_frame = None
for frame in [im] + append_images:
# Get number of of frames in this image
nfr = getattr(frame, "n_frames", 1)
for idx in range(nfr):
frame.seek(idx)
frame.load()
copy = frame.copy().convert("RGB")
x(frame_num, copy, last_frame)
last_frame = copy
frame_num += 1
# Collect all the frame sizes
frame_sizes = []
_for_all_frames(lambda idx, frame, last_frame: frame_sizes.append(frame.size))
# Make sure all frames are the same size
if len(list(set(frame_sizes))) != 1:
raise ValueError("Mismatching sizes on frames")
# Write out the initial graphics descriptor (and write a dummy value), so that we can come back and fill in the
# correct values once we've written all the frames to the output
graphics_descriptor_location = fp.tell()
graphics_descriptor = QGFGraphicsDescriptor()
graphics_descriptor.frame_count = len(frame_sizes)
graphics_descriptor.image_width = frame_sizes[0][0]
graphics_descriptor.image_height = frame_sizes[0][1]
vprint(f'{"Graphics descriptor block":26s} {fp.tell():5d}d / {fp.tell():04X}h')
graphics_descriptor.write(fp)
# Work out the frame offset descriptor location (and write a dummy value), so that we can come back and fill in the
# correct offsets once we've written all the frames to the output
frame_offset_location = fp.tell()
frame_offsets = QGFFrameOffsetDescriptorV1(graphics_descriptor.frame_count)
vprint(f'{"Frame offsets block":26s} {fp.tell():5d}d / {fp.tell():04X}h')
frame_offsets.write(fp)
# Helper function to save each frame to the output file
def _write_frame(idx, frame, last_frame):
# If we replace the frame we're going to output with a delta, we can override it here
this_frame = frame
location = (0, 0)
size = frame.size
# Work out the format we're going to use
format = encoderinfo["qmk_format"]
# Convert the original frame so we can do comparisons
converted = qmk.painter.convert_requested_format(this_frame, format)
graphic_data = qmk.painter.convert_image_bytes(converted, format)
# Convert the raw data to RLE-encoded if requested
raw_data = graphic_data[1]
if use_rle:
rle_data = qmk.painter.compress_bytes_qmk_rle(graphic_data[1])
use_raw_this_frame = not use_rle or len(raw_data) <= len(rle_data)
image_data = raw_data if use_raw_this_frame else rle_data
# Work out if a delta frame is smaller than injecting it directly
use_delta_this_frame = False
if use_deltas and last_frame is not None:
# If we want to use deltas, then find the difference
diff = ImageChops.difference(frame, last_frame)
# Get the bounding box of those differences
bbox = diff.getbbox()
# If we have a valid bounding box...
if bbox:
# ...create the delta frame by cropping the original.
delta_frame = frame.crop(bbox)
delta_location = (bbox[0], bbox[1])
delta_size = (bbox[2] - bbox[0], bbox[3] - bbox[1])
# Convert the delta frame to the requested format
delta_converted = qmk.painter.convert_requested_format(delta_frame, format)
delta_graphic_data = qmk.painter.convert_image_bytes(delta_converted, format)
# Work out how large the delta frame is going to be with compression etc.
delta_raw_data = delta_graphic_data[1]
if use_rle:
delta_rle_data = qmk.painter.compress_bytes_qmk_rle(delta_graphic_data[1])
delta_use_raw_this_frame = not use_rle or len(delta_raw_data) <= len(delta_rle_data)
delta_image_data = delta_raw_data if delta_use_raw_this_frame else delta_rle_data
# If the size of the delta frame (plus delta descriptor) is smaller than the original, use that instead
# This ensures that if a non-delta is overall smaller in size, we use that in preference due to flash
# sizing constraints.
if (len(delta_image_data) + QGFFrameDeltaDescriptorV1.length) < len(image_data):
# Copy across all the delta equivalents so that the rest of the processing acts on those
this_frame = delta_frame
location = delta_location
size = delta_size
converted = delta_converted
graphic_data = delta_graphic_data
raw_data = delta_raw_data
rle_data = delta_rle_data
use_raw_this_frame = delta_use_raw_this_frame
image_data = delta_image_data
use_delta_this_frame = True
# Write out the frame descriptor
frame_offsets.frame_offsets[idx] = fp.tell()
vprint(f'{f"Frame {idx:3d} base":26s} {fp.tell():5d}d / {fp.tell():04X}h')
frame_descriptor = QGFFrameDescriptorV1()
frame_descriptor.is_delta = use_delta_this_frame
frame_descriptor.is_transparent = False
frame_descriptor.format = format['image_format_byte']
frame_descriptor.compression = 0x00 if use_raw_this_frame else 0x01 # See qp.h, painter_compression_t
frame_descriptor.delay = frame.info['duration'] if 'duration' in frame.info else 1000 # If we're not an animation, just pretend we're delaying for 1000ms
frame_descriptor.write(fp)
# Write out the palette if required
if format['has_palette']:
palette = graphic_data[0]
palette_descriptor = QGFFramePaletteDescriptorV1()
# Helper to convert from RGB888 to the QMK "dialect" of HSV888
def rgb888_to_qmk_hsv888(e):
hsv = rgb_to_hsv(e[0] / 255.0, e[1] / 255.0, e[2] / 255.0)
return (int(hsv[0] * 255.0), int(hsv[1] * 255.0), int(hsv[2] * 255.0))
# Convert all palette entries to HSV888 and write to the output
palette_descriptor.palette_entries = list(map(rgb888_to_qmk_hsv888, palette))
vprint(f'{f"Frame {idx:3d} palette":26s} {fp.tell():5d}d / {fp.tell():04X}h')
palette_descriptor.write(fp)
# Write out the delta info if required
if use_delta_this_frame:
# Set up the rendering location of where the delta frame should be situated
delta_descriptor = QGFFrameDeltaDescriptorV1()
delta_descriptor.left = location[0]
delta_descriptor.top = location[1]
delta_descriptor.right = location[0] + size[0]
delta_descriptor.bottom = location[1] + size[1]
# Write the delta frame to the output
vprint(f'{f"Frame {idx:3d} delta":26s} {fp.tell():5d}d / {fp.tell():04X}h')
delta_descriptor.write(fp)
# Write out the data for this frame to the output
data_descriptor = QGFFrameDataDescriptorV1()
data_descriptor.data = image_data
vprint(f'{f"Frame {idx:3d} data":26s} {fp.tell():5d}d / {fp.tell():04X}h')
data_descriptor.write(fp)
# Iterate over each if the input frames, writing it to the output in the process
_for_all_frames(_write_frame)
# Go back and update the graphics descriptor now that we can determine the final file size
graphics_descriptor.total_file_size = fp.tell()
fp.seek(graphics_descriptor_location, 0)
graphics_descriptor.write(fp)
# Go back and update the frame offsets now that they're written to the file
fp.seek(frame_offset_location, 0)
frame_offsets.write(fp)
########################################################################################################################
# Register with PIL so that it knows about the QGF format
Image.register_open(QGFImageFile.format, QGFImageFile, _accept)
Image.register_save(QGFImageFile.format, _save)
Image.register_save_all(QGFImageFile.format, _save)
Image.register_extension(QGFImageFile.format, f".{QGFImageFile.format.lower()}")
Image.register_mime(QGFImageFile.format, f"image/{QGFImageFile.format.lower()}")

View File

@@ -248,7 +248,7 @@ def test_clean():
def test_generate_api(): def test_generate_api():
result = check_subcommand('generate-api', '--dry-run') result = check_subcommand('generate-api', '--dry-run', '--filter', 'handwired/pytest')
check_returncode(result) check_returncode(result)
@@ -263,7 +263,7 @@ def test_generate_config_h():
result = check_subcommand('generate-config-h', '-kb', 'handwired/pytest/basic') result = check_subcommand('generate-config-h', '-kb', 'handwired/pytest/basic')
check_returncode(result) check_returncode(result)
assert '# define DEVICE_VER 0x0001' in result.stdout assert '# define DEVICE_VER 0x0001' in result.stdout
assert '# define DESCRIPTION handwired/pytest/basic' in result.stdout assert '# define DESCRIPTION "handwired/pytest/basic"' in result.stdout
assert '# define DIODE_DIRECTION COL2ROW' in result.stdout assert '# define DIODE_DIRECTION COL2ROW' in result.stdout
assert '# define MANUFACTURER none' in result.stdout assert '# define MANUFACTURER none' in result.stdout
assert '# define PRODUCT pytest' in result.stdout assert '# define PRODUCT pytest' in result.stdout

View File

@@ -63,3 +63,7 @@ void bootloader_jump(void) {
while (1) while (1)
; // Wait on timeout ; // Wait on timeout
} }
__attribute__((weak)) void mcu_reset(void) {
NVIC_SystemReset();
}

View File

@@ -0,0 +1,9 @@
// Copyright 2022 QMK
// SPDX-License-Identifier: GPL-2.0-or-later
#include "hardware_id.h"
hardware_id_t get_hardware_id(void) {
hardware_id_t id = {0};
return id;
}

View File

@@ -31,3 +31,10 @@ __attribute__((weak)) void bootloader_jump(void) {
for (;;) for (;;)
; ;
} }
__attribute__((weak)) void mcu_reset(void) {
// watchdog reset
wdt_enable(WDTO_250MS);
for (;;)
;
}

View File

@@ -37,3 +37,12 @@ __attribute__((weak)) void bootloader_jump(void) {
while (1) { while (1) {
} }
} }
__attribute__((weak)) void mcu_reset(void) {
// setup watchdog timeout
wdt_enable(WDTO_60MS);
// wait for watchdog timer to trigger
while (1) {
}
}

View File

@@ -15,5 +15,14 @@
*/ */
#include "bootloader.h" #include "bootloader.h"
#include <avr/wdt.h>
__attribute__((weak)) void bootloader_jump(void) {} __attribute__((weak)) void bootloader_jump(void) {}
__attribute__((weak)) void mcu_reset(void) {
// setup watchdog timeout
wdt_enable(WDTO_60MS);
// wait for watchdog timer to trigger
while (1) {
}
}

View File

@@ -34,8 +34,15 @@ __attribute__((weak)) void bootloader_jump(void) {
UCSR1B = 0; UCSR1B = 0;
_delay_ms(5); // 5 seems to work fine _delay_ms(5); // 5 seems to work fine
// watchdog reset
reset_key = BOOTLOADER_RESET_KEY; reset_key = BOOTLOADER_RESET_KEY;
// watchdog reset
wdt_enable(WDTO_250MS);
for (;;)
;
}
__attribute__((weak)) void mcu_reset(void) {
// watchdog reset
wdt_enable(WDTO_250MS); wdt_enable(WDTO_250MS);
for (;;) for (;;)
; ;

View File

@@ -17,6 +17,7 @@
#include "bootloader.h" #include "bootloader.h"
#include <avr/interrupt.h> #include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h> #include <util/delay.h>
__attribute__((weak)) void bootloader_jump(void) { __attribute__((weak)) void bootloader_jump(void) {
@@ -126,3 +127,12 @@ __attribute__((weak)) void bootloader_jump(void) {
asm volatile("jmp 0x1FC00"); asm volatile("jmp 0x1FC00");
#endif #endif
} }
__attribute__((weak)) void mcu_reset(void) {
// setup watchdog timeout
wdt_enable(WDTO_60MS);
// wait for watchdog timer to trigger
while (1) {
}
}

View File

@@ -54,3 +54,12 @@ __attribute__((weak)) void bootloader_jump(void) {
#endif #endif
[bootaddrme] "M"((((FLASH_SIZE - BOOTLOADER_SIZE) >> 1) >> 8) & 0xff), [bootaddrlo] "M"((((FLASH_SIZE - BOOTLOADER_SIZE) >> 1) >> 0) & 0xff)); [bootaddrme] "M"((((FLASH_SIZE - BOOTLOADER_SIZE) >> 1) >> 8) & 0xff), [bootaddrlo] "M"((((FLASH_SIZE - BOOTLOADER_SIZE) >> 1) >> 0) & 0xff));
} }
__attribute__((weak)) void mcu_reset(void) {
// setup watchdog timeout
wdt_enable(WDTO_15MS);
// wait for watchdog timer to trigger
while (1) {
}
}

View File

@@ -1,542 +0,0 @@
/****************************************************************************
Title: HD44780U LCD library
Author: Peter Fleury <pfleury@gmx.ch> http://tinyurl.com/peterfleury
License: GNU General Public License Version 3
File: $Id: lcd.c,v 1.15.2.2 2015/01/17 12:16:05 peter Exp $
Software: AVR-GCC 3.3
Target: any AVR device, memory mapped mode only for AT90S4414/8515/Mega
DESCRIPTION
Basic routines for interfacing a HD44780U-based text lcd display
Originally based on Volker Oth's lcd library,
changed lcd_init(), added additional constants for lcd_command(),
added 4-bit I/O mode, improved and optimized code.
Library can be operated in memory mapped mode (LCD_IO_MODE=0) or in
4-bit IO port mode (LCD_IO_MODE=1). 8-bit IO port mode not supported.
Memory mapped mode compatible with Kanda STK200, but supports also
generation of R/W signal through A8 address line.
USAGE
See the C include lcd.h file for a description of each function
*****************************************************************************/
#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include "hd44780.h"
/*
** constants/macros
*/
#define DDR(x) (*(&x - 1)) /* address of data direction register of port x */
#if defined(__AVR_ATmega64__) || defined(__AVR_ATmega128__)
/* on ATmega64/128 PINF is on port 0x00 and not 0x60 */
# define PIN(x) (&PORTF == &(x) ? _SFR_IO8(0x00) : (*(&x - 2)))
#else
# define PIN(x) (*(&x - 2)) /* address of input register of port x */
#endif
#if LCD_IO_MODE
# define lcd_e_delay() _delay_us(LCD_DELAY_ENABLE_PULSE)
# define lcd_e_high() LCD_E_PORT |= _BV(LCD_E_PIN);
# define lcd_e_low() LCD_E_PORT &= ~_BV(LCD_E_PIN);
# define lcd_e_toggle() toggle_e()
# define lcd_rw_high() LCD_RW_PORT |= _BV(LCD_RW_PIN)
# define lcd_rw_low() LCD_RW_PORT &= ~_BV(LCD_RW_PIN)
# define lcd_rs_high() LCD_RS_PORT |= _BV(LCD_RS_PIN)
# define lcd_rs_low() LCD_RS_PORT &= ~_BV(LCD_RS_PIN)
#endif
#if LCD_IO_MODE
# if LCD_LINES == 1
# define LCD_FUNCTION_DEFAULT LCD_FUNCTION_4BIT_1LINE
# else
# define LCD_FUNCTION_DEFAULT LCD_FUNCTION_4BIT_2LINES
# endif
#else
# if LCD_LINES == 1
# define LCD_FUNCTION_DEFAULT LCD_FUNCTION_8BIT_1LINE
# else
# define LCD_FUNCTION_DEFAULT LCD_FUNCTION_8BIT_2LINES
# endif
#endif
#if LCD_CONTROLLER_KS0073
# if LCD_LINES == 4
# define KS0073_EXTENDED_FUNCTION_REGISTER_ON 0x2C /* |0|010|1100 4-bit mode, extension-bit RE = 1 */
# define KS0073_EXTENDED_FUNCTION_REGISTER_OFF 0x28 /* |0|010|1000 4-bit mode, extension-bit RE = 0 */
# define KS0073_4LINES_MODE 0x09 /* |0|000|1001 4 lines mode */
# endif
#endif
/*
** function prototypes
*/
#if LCD_IO_MODE
static void toggle_e(void);
#endif
/*
** local functions
*/
/*************************************************************************
delay for a minimum of <us> microseconds
the number of loops is calculated at compile-time from MCU clock frequency
*************************************************************************/
#define delay(us) _delay_us(us)
#if LCD_IO_MODE
/* toggle Enable Pin to initiate write */
static void toggle_e(void) {
lcd_e_high();
lcd_e_delay();
lcd_e_low();
}
#endif
/*************************************************************************
Low-level function to write byte to LCD controller
Input: data byte to write to LCD
rs 1: write data
0: write instruction
Returns: none
*************************************************************************/
#if LCD_IO_MODE
static void lcd_write(uint8_t data, uint8_t rs) {
unsigned char dataBits;
if (rs) { /* write data (RS=1, RW=0) */
lcd_rs_high();
} else { /* write instruction (RS=0, RW=0) */
lcd_rs_low();
}
lcd_rw_low(); /* RW=0 write mode */
if ((&LCD_DATA0_PORT == &LCD_DATA1_PORT) && (&LCD_DATA1_PORT == &LCD_DATA2_PORT) && (&LCD_DATA2_PORT == &LCD_DATA3_PORT) && (LCD_DATA0_PIN == 0) && (LCD_DATA1_PIN == 1) && (LCD_DATA2_PIN == 2) && (LCD_DATA3_PIN == 3)) {
/* configure data pins as output */
DDR(LCD_DATA0_PORT) |= 0x0F;
/* output high nibble first */
dataBits = LCD_DATA0_PORT & 0xF0;
LCD_DATA0_PORT = dataBits | ((data >> 4) & 0x0F);
lcd_e_toggle();
/* output low nibble */
LCD_DATA0_PORT = dataBits | (data & 0x0F);
lcd_e_toggle();
/* all data pins high (inactive) */
LCD_DATA0_PORT = dataBits | 0x0F;
} else {
/* configure data pins as output */
DDR(LCD_DATA0_PORT) |= _BV(LCD_DATA0_PIN);
DDR(LCD_DATA1_PORT) |= _BV(LCD_DATA1_PIN);
DDR(LCD_DATA2_PORT) |= _BV(LCD_DATA2_PIN);
DDR(LCD_DATA3_PORT) |= _BV(LCD_DATA3_PIN);
/* output high nibble first */
LCD_DATA3_PORT &= ~_BV(LCD_DATA3_PIN);
LCD_DATA2_PORT &= ~_BV(LCD_DATA2_PIN);
LCD_DATA1_PORT &= ~_BV(LCD_DATA1_PIN);
LCD_DATA0_PORT &= ~_BV(LCD_DATA0_PIN);
if (data & 0x80) LCD_DATA3_PORT |= _BV(LCD_DATA3_PIN);
if (data & 0x40) LCD_DATA2_PORT |= _BV(LCD_DATA2_PIN);
if (data & 0x20) LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN);
if (data & 0x10) LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN);
lcd_e_toggle();
/* output low nibble */
LCD_DATA3_PORT &= ~_BV(LCD_DATA3_PIN);
LCD_DATA2_PORT &= ~_BV(LCD_DATA2_PIN);
LCD_DATA1_PORT &= ~_BV(LCD_DATA1_PIN);
LCD_DATA0_PORT &= ~_BV(LCD_DATA0_PIN);
if (data & 0x08) LCD_DATA3_PORT |= _BV(LCD_DATA3_PIN);
if (data & 0x04) LCD_DATA2_PORT |= _BV(LCD_DATA2_PIN);
if (data & 0x02) LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN);
if (data & 0x01) LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN);
lcd_e_toggle();
/* all data pins high (inactive) */
LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN);
LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN);
LCD_DATA2_PORT |= _BV(LCD_DATA2_PIN);
LCD_DATA3_PORT |= _BV(LCD_DATA3_PIN);
}
}
#else
# define lcd_write(d, rs) \
if (rs) \
*(volatile uint8_t *)(LCD_IO_DATA) = d; \
else \
*(volatile uint8_t *)(LCD_IO_FUNCTION) = d;
/* rs==0 -> write instruction to LCD_IO_FUNCTION */
/* rs==1 -> write data to LCD_IO_DATA */
#endif
/*************************************************************************
Low-level function to read byte from LCD controller
Input: rs 1: read data
0: read busy flag / address counter
Returns: byte read from LCD controller
*************************************************************************/
#if LCD_IO_MODE
static uint8_t lcd_read(uint8_t rs) {
uint8_t data;
if (rs)
lcd_rs_high(); /* RS=1: read data */
else
lcd_rs_low(); /* RS=0: read busy flag */
lcd_rw_high(); /* RW=1 read mode */
if ((&LCD_DATA0_PORT == &LCD_DATA1_PORT) && (&LCD_DATA1_PORT == &LCD_DATA2_PORT) && (&LCD_DATA2_PORT == &LCD_DATA3_PORT) && (LCD_DATA0_PIN == 0) && (LCD_DATA1_PIN == 1) && (LCD_DATA2_PIN == 2) && (LCD_DATA3_PIN == 3)) {
DDR(LCD_DATA0_PORT) &= 0xF0; /* configure data pins as input */
lcd_e_high();
lcd_e_delay();
data = PIN(LCD_DATA0_PORT) << 4; /* read high nibble first */
lcd_e_low();
lcd_e_delay(); /* Enable 500ns low */
lcd_e_high();
lcd_e_delay();
data |= PIN(LCD_DATA0_PORT) & 0x0F; /* read low nibble */
lcd_e_low();
} else {
/* configure data pins as input */
DDR(LCD_DATA0_PORT) &= ~_BV(LCD_DATA0_PIN);
DDR(LCD_DATA1_PORT) &= ~_BV(LCD_DATA1_PIN);
DDR(LCD_DATA2_PORT) &= ~_BV(LCD_DATA2_PIN);
DDR(LCD_DATA3_PORT) &= ~_BV(LCD_DATA3_PIN);
/* read high nibble first */
lcd_e_high();
lcd_e_delay();
data = 0;
if (PIN(LCD_DATA0_PORT) & _BV(LCD_DATA0_PIN)) data |= 0x10;
if (PIN(LCD_DATA1_PORT) & _BV(LCD_DATA1_PIN)) data |= 0x20;
if (PIN(LCD_DATA2_PORT) & _BV(LCD_DATA2_PIN)) data |= 0x40;
if (PIN(LCD_DATA3_PORT) & _BV(LCD_DATA3_PIN)) data |= 0x80;
lcd_e_low();
lcd_e_delay(); /* Enable 500ns low */
/* read low nibble */
lcd_e_high();
lcd_e_delay();
if (PIN(LCD_DATA0_PORT) & _BV(LCD_DATA0_PIN)) data |= 0x01;
if (PIN(LCD_DATA1_PORT) & _BV(LCD_DATA1_PIN)) data |= 0x02;
if (PIN(LCD_DATA2_PORT) & _BV(LCD_DATA2_PIN)) data |= 0x04;
if (PIN(LCD_DATA3_PORT) & _BV(LCD_DATA3_PIN)) data |= 0x08;
lcd_e_low();
}
return data;
}
#else
# define lcd_read(rs) (rs) ? *(volatile uint8_t *)(LCD_IO_DATA + LCD_IO_READ) : *(volatile uint8_t *)(LCD_IO_FUNCTION + LCD_IO_READ)
/* rs==0 -> read instruction from LCD_IO_FUNCTION */
/* rs==1 -> read data from LCD_IO_DATA */
#endif
/*************************************************************************
loops while lcd is busy, returns address counter
*************************************************************************/
static uint8_t lcd_waitbusy(void)
{
register uint8_t c;
/* wait until busy flag is cleared */
while ((c = lcd_read(0)) & (1 << LCD_BUSY)) {
}
/* the address counter is updated 4us after the busy flag is cleared */
delay(LCD_DELAY_BUSY_FLAG);
/* now read the address counter */
return (lcd_read(0)); // return address counter
} /* lcd_waitbusy */
/*************************************************************************
Move cursor to the start of next line or to the first line if the cursor
is already on the last line.
*************************************************************************/
static inline void lcd_newline(uint8_t pos) {
register uint8_t addressCounter;
#if LCD_LINES == 1
addressCounter = 0;
#endif
#if LCD_LINES == 2
if (pos < (LCD_START_LINE2))
addressCounter = LCD_START_LINE2;
else
addressCounter = LCD_START_LINE1;
#endif
#if LCD_LINES == 4
# if KS0073_4LINES_MODE
if (pos < LCD_START_LINE2)
addressCounter = LCD_START_LINE2;
else if ((pos >= LCD_START_LINE2) && (pos < LCD_START_LINE3))
addressCounter = LCD_START_LINE3;
else if ((pos >= LCD_START_LINE3) && (pos < LCD_START_LINE4))
addressCounter = LCD_START_LINE4;
else
addressCounter = LCD_START_LINE1;
# else
if (pos < LCD_START_LINE3)
addressCounter = LCD_START_LINE2;
else if ((pos >= LCD_START_LINE2) && (pos < LCD_START_LINE4))
addressCounter = LCD_START_LINE3;
else if ((pos >= LCD_START_LINE3) && (pos < LCD_START_LINE2))
addressCounter = LCD_START_LINE4;
else
addressCounter = LCD_START_LINE1;
# endif
#endif
lcd_command((1 << LCD_DDRAM) + addressCounter);
} /* lcd_newline */
/*
** PUBLIC FUNCTIONS
*/
/*************************************************************************
Send LCD controller instruction command
Input: instruction to send to LCD controller, see HD44780 data sheet
Returns: none
*************************************************************************/
void lcd_command(uint8_t cmd) {
lcd_waitbusy();
lcd_write(cmd, 0);
}
/*************************************************************************
Send data byte to LCD controller
Input: data to send to LCD controller, see HD44780 data sheet
Returns: none
*************************************************************************/
void lcd_data(uint8_t data) {
lcd_waitbusy();
lcd_write(data, 1);
}
/*************************************************************************
Set cursor to specified position
Input: x horizontal position (0: left most position)
y vertical position (0: first line)
Returns: none
*************************************************************************/
void lcd_gotoxy(uint8_t x, uint8_t y) {
#if LCD_LINES == 1
lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x);
#endif
#if LCD_LINES == 2
if (y == 0)
lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x);
else
lcd_command((1 << LCD_DDRAM) + LCD_START_LINE2 + x);
#endif
#if LCD_LINES == 4
if (y == 0)
lcd_command((1 << LCD_DDRAM) + LCD_START_LINE1 + x);
else if (y == 1)
lcd_command((1 << LCD_DDRAM) + LCD_START_LINE2 + x);
else if (y == 2)
lcd_command((1 << LCD_DDRAM) + LCD_START_LINE3 + x);
else /* y==3 */
lcd_command((1 << LCD_DDRAM) + LCD_START_LINE4 + x);
#endif
} /* lcd_gotoxy */
/*************************************************************************
*************************************************************************/
int lcd_getxy(void) {
return lcd_waitbusy();
}
/*************************************************************************
Clear display and set cursor to home position
*************************************************************************/
void lcd_clrscr(void) {
lcd_command(1 << LCD_CLR);
}
/*************************************************************************
Set cursor to home position
*************************************************************************/
void lcd_home(void) {
lcd_command(1 << LCD_HOME);
}
/*************************************************************************
Display character at current cursor position
Input: character to be displayed
Returns: none
*************************************************************************/
void lcd_putc(char c) {
uint8_t pos;
pos = lcd_waitbusy(); // read busy-flag and address counter
if (c == '\n') {
lcd_newline(pos);
} else {
#if LCD_WRAP_LINES == 1
# if LCD_LINES == 1
if (pos == LCD_START_LINE1 + LCD_DISP_LENGTH) {
lcd_write((1 << LCD_DDRAM) + LCD_START_LINE1, 0);
}
# elif LCD_LINES == 2
if (pos == LCD_START_LINE1 + LCD_DISP_LENGTH) {
lcd_write((1 << LCD_DDRAM) + LCD_START_LINE2, 0);
} else if (pos == LCD_START_LINE2 + LCD_DISP_LENGTH) {
lcd_write((1 << LCD_DDRAM) + LCD_START_LINE1, 0);
}
# elif LCD_LINES == 4
if (pos == LCD_START_LINE1 + LCD_DISP_LENGTH) {
lcd_write((1 << LCD_DDRAM) + LCD_START_LINE2, 0);
} else if (pos == LCD_START_LINE2 + LCD_DISP_LENGTH) {
lcd_write((1 << LCD_DDRAM) + LCD_START_LINE3, 0);
} else if (pos == LCD_START_LINE3 + LCD_DISP_LENGTH) {
lcd_write((1 << LCD_DDRAM) + LCD_START_LINE4, 0);
} else if (pos == LCD_START_LINE4 + LCD_DISP_LENGTH) {
lcd_write((1 << LCD_DDRAM) + LCD_START_LINE1, 0);
}
# endif
lcd_waitbusy();
#endif
lcd_write(c, 1);
}
} /* lcd_putc */
/*************************************************************************
Display string without auto linefeed
Input: string to be displayed
Returns: none
*************************************************************************/
void lcd_puts(const char *s)
/* print string on lcd (no auto linefeed) */
{
register char c;
while ((c = *s++)) {
lcd_putc(c);
}
} /* lcd_puts */
/*************************************************************************
Display string from program memory without auto linefeed
Input: string from program memory be be displayed
Returns: none
*************************************************************************/
void lcd_puts_p(const char *progmem_s)
/* print string from program memory on lcd (no auto linefeed) */
{
register char c;
while ((c = pgm_read_byte(progmem_s++))) {
lcd_putc(c);
}
} /* lcd_puts_p */
/*************************************************************************
Initialize display and select type of cursor
Input: dispAttr LCD_DISP_OFF display off
LCD_DISP_ON display on, cursor off
LCD_DISP_ON_CURSOR display on, cursor on
LCD_DISP_CURSOR_BLINK display on, cursor on flashing
Returns: none
*************************************************************************/
void lcd_init(uint8_t dispAttr) {
#if LCD_IO_MODE
/*
* Initialize LCD to 4 bit I/O mode
*/
if ((&LCD_DATA0_PORT == &LCD_DATA1_PORT) && (&LCD_DATA1_PORT == &LCD_DATA2_PORT) && (&LCD_DATA2_PORT == &LCD_DATA3_PORT) && (&LCD_RS_PORT == &LCD_DATA0_PORT) && (&LCD_RW_PORT == &LCD_DATA0_PORT) && (&LCD_E_PORT == &LCD_DATA0_PORT) && (LCD_DATA0_PIN == 0) && (LCD_DATA1_PIN == 1) && (LCD_DATA2_PIN == 2) && (LCD_DATA3_PIN == 3) && (LCD_RS_PIN == 4) && (LCD_RW_PIN == 5) && (LCD_E_PIN == 6)) {
/* configure all port bits as output (all LCD lines on same port) */
DDR(LCD_DATA0_PORT) |= 0x7F;
} else if ((&LCD_DATA0_PORT == &LCD_DATA1_PORT) && (&LCD_DATA1_PORT == &LCD_DATA2_PORT) && (&LCD_DATA2_PORT == &LCD_DATA3_PORT) && (LCD_DATA0_PIN == 0) && (LCD_DATA1_PIN == 1) && (LCD_DATA2_PIN == 2) && (LCD_DATA3_PIN == 3)) {
/* configure all port bits as output (all LCD data lines on same port, but control lines on different ports) */
DDR(LCD_DATA0_PORT) |= 0x0F;
DDR(LCD_RS_PORT) |= _BV(LCD_RS_PIN);
DDR(LCD_RW_PORT) |= _BV(LCD_RW_PIN);
DDR(LCD_E_PORT) |= _BV(LCD_E_PIN);
} else {
/* configure all port bits as output (LCD data and control lines on different ports */
DDR(LCD_RS_PORT) |= _BV(LCD_RS_PIN);
DDR(LCD_RW_PORT) |= _BV(LCD_RW_PIN);
DDR(LCD_E_PORT) |= _BV(LCD_E_PIN);
DDR(LCD_DATA0_PORT) |= _BV(LCD_DATA0_PIN);
DDR(LCD_DATA1_PORT) |= _BV(LCD_DATA1_PIN);
DDR(LCD_DATA2_PORT) |= _BV(LCD_DATA2_PIN);
DDR(LCD_DATA3_PORT) |= _BV(LCD_DATA3_PIN);
}
delay(LCD_DELAY_BOOTUP); /* wait 16ms or more after power-on */
/* initial write to lcd is 8bit */
LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN); // LCD_FUNCTION>>4;
LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN); // LCD_FUNCTION_8BIT>>4;
lcd_e_toggle();
delay(LCD_DELAY_INIT); /* delay, busy flag can't be checked here */
/* repeat last command */
lcd_e_toggle();
delay(LCD_DELAY_INIT_REP); /* delay, busy flag can't be checked here */
/* repeat last command a third time */
lcd_e_toggle();
delay(LCD_DELAY_INIT_REP); /* delay, busy flag can't be checked here */
/* now configure for 4bit mode */
LCD_DATA0_PORT &= ~_BV(LCD_DATA0_PIN); // LCD_FUNCTION_4BIT_1LINE>>4
lcd_e_toggle();
delay(LCD_DELAY_INIT_4BIT); /* some displays need this additional delay */
/* from now the LCD only accepts 4 bit I/O, we can use lcd_command() */
#else
/*
* Initialize LCD to 8 bit memory mapped mode
*/
/* enable external SRAM (memory mapped lcd) and one wait state */
MCUCR = _BV(SRE) | _BV(SRW);
/* reset LCD */
delay(LCD_DELAY_BOOTUP); /* wait 16ms after power-on */
lcd_write(LCD_FUNCTION_8BIT_1LINE, 0); /* function set: 8bit interface */
delay(LCD_DELAY_INIT); /* wait 5ms */
lcd_write(LCD_FUNCTION_8BIT_1LINE, 0); /* function set: 8bit interface */
delay(LCD_DELAY_INIT_REP); /* wait 64us */
lcd_write(LCD_FUNCTION_8BIT_1LINE, 0); /* function set: 8bit interface */
delay(LCD_DELAY_INIT_REP); /* wait 64us */
#endif
#if KS0073_4LINES_MODE
/* Display with KS0073 controller requires special commands for enabling 4 line mode */
lcd_command(KS0073_EXTENDED_FUNCTION_REGISTER_ON);
lcd_command(KS0073_4LINES_MODE);
lcd_command(KS0073_EXTENDED_FUNCTION_REGISTER_OFF);
#else
lcd_command(LCD_FUNCTION_DEFAULT); /* function set: display lines */
#endif
lcd_command(LCD_DISP_OFF); /* display off */
lcd_clrscr(); /* display clear */
lcd_command(LCD_MODE_DEFAULT); /* set entry mode */
lcd_command(dispAttr); /* display/cursor control */
} /* lcd_init */

View File

@@ -1,348 +0,0 @@
/*************************************************************************
Title : C include file for the HD44780U LCD library (lcd.c)
Author: Peter Fleury <pfleury@gmx.ch> http://tinyurl.com/peterfleury
License: GNU General Public License Version 3
File: $Id: lcd.h,v 1.14.2.4 2015/01/20 17:16:07 peter Exp $
Software: AVR-GCC 4.x
Hardware: any AVR device, memory mapped mode only for AVR with
memory mapped interface (AT90S8515/ATmega8515/ATmega128)
***************************************************************************/
/**
@mainpage
Collection of libraries for AVR-GCC
@author Peter Fleury pfleury@gmx.ch http://tinyurl.com/peterfleury
@copyright (C) 2015 Peter Fleury, GNU General Public License Version 3
@file
@defgroup pfleury_lcd LCD library <lcd.h>
@code #include <lcd.h> @endcode
@brief Basic routines for interfacing a HD44780U-based character LCD display
LCD character displays can be found in many devices, like espresso machines, laser printers.
The Hitachi HD44780 controller and its compatible controllers like Samsung KS0066U have become an industry standard for these types of displays.
This library allows easy interfacing with a HD44780 compatible display and can be
operated in memory mapped mode (LCD_IO_MODE defined as 0 in the include file lcd.h.) or in
4-bit IO port mode (LCD_IO_MODE defined as 1). 8-bit IO port mode is not supported.
Memory mapped mode is compatible with old Kanda STK200 starter kit, but also supports
generation of R/W signal through A8 address line.
@see The chapter <a href=" http://homepage.hispeed.ch/peterfleury/avr-lcd44780.html" target="_blank">Interfacing a HD44780 Based LCD to an AVR</a>
on my home page, which shows example circuits how to connect an LCD to an AVR controller.
@author Peter Fleury pfleury@gmx.ch http://tinyurl.com/peterfleury
@version 2.0
@copyright (C) 2015 Peter Fleury, GNU General Public License Version 3
*/
#pragma once
#include <inttypes.h>
#include <avr/pgmspace.h>
#if (__GNUC__ * 100 + __GNUC_MINOR__) < 405
# error "This library requires AVR-GCC 4.5 or later, update to newer AVR-GCC compiler !"
#endif
/**@{*/
/*
* LCD and target specific definitions below can be defined in a separate include file with name lcd_definitions.h instead modifying this file
* by adding -D_LCD_DEFINITIONS_FILE to the CDEFS section in the Makefile
* All definitions added to the file lcd_definitions.h will override the default definitions from lcd.h
*/
#ifdef _LCD_DEFINITIONS_FILE
# include "lcd_definitions.h"
#endif
/**
* @name Definition for LCD controller type
* Use 0 for HD44780 controller, change to 1 for displays with KS0073 controller.
*/
#ifndef LCD_CONTROLLER_KS0073
# define LCD_CONTROLLER_KS0073 0 /**< Use 0 for HD44780 controller, 1 for KS0073 controller */
#endif
/**
* @name Definitions for Display Size
* Change these definitions to adapt setting to your display
*
* These definitions can be defined in a separate include file \b lcd_definitions.h instead modifying this file by
* adding -D_LCD_DEFINITIONS_FILE to the CDEFS section in the Makefile.
* All definitions added to the file lcd_definitions.h will override the default definitions from lcd.h
*
*/
#ifndef LCD_LINES
# define LCD_LINES 2 /**< number of visible lines of the display */
#endif
#ifndef LCD_DISP_LENGTH
# define LCD_DISP_LENGTH 16 /**< visibles characters per line of the display */
#endif
#ifndef LCD_LINE_LENGTH
# define LCD_LINE_LENGTH 0x40 /**< internal line length of the display */
#endif
#ifndef LCD_START_LINE1
# define LCD_START_LINE1 0x00 /**< DDRAM address of first char of line 1 */
#endif
#ifndef LCD_START_LINE2
# define LCD_START_LINE2 0x40 /**< DDRAM address of first char of line 2 */
#endif
#ifndef LCD_START_LINE3
# define LCD_START_LINE3 0x14 /**< DDRAM address of first char of line 3 */
#endif
#ifndef LCD_START_LINE4
# define LCD_START_LINE4 0x54 /**< DDRAM address of first char of line 4 */
#endif
#ifndef LCD_WRAP_LINES
# define LCD_WRAP_LINES 0 /**< 0: no wrap, 1: wrap at end of visibile line */
#endif
/**
* @name Definitions for 4-bit IO mode
*
* The four LCD data lines and the three control lines RS, RW, E can be on the
* same port or on different ports.
* Change LCD_RS_PORT, LCD_RW_PORT, LCD_E_PORT if you want the control lines on
* different ports.
*
* Normally the four data lines should be mapped to bit 0..3 on one port, but it
* is possible to connect these data lines in different order or even on different
* ports by adapting the LCD_DATAx_PORT and LCD_DATAx_PIN definitions.
*
* Adjust these definitions to your target.\n
* These definitions can be defined in a separate include file \b lcd_definitions.h instead modifying this file by
* adding \b -D_LCD_DEFINITIONS_FILE to the \b CDEFS section in the Makefile.
* All definitions added to the file lcd_definitions.h will override the default definitions from lcd.h
*
*/
#define LCD_IO_MODE 1 /**< 0: memory mapped mode, 1: IO port mode */
#if LCD_IO_MODE
# ifndef LCD_PORT
# define LCD_PORT PORTA /**< port for the LCD lines */
# endif
# ifndef LCD_DATA0_PORT
# define LCD_DATA0_PORT LCD_PORT /**< port for 4bit data bit 0 */
# endif
# ifndef LCD_DATA1_PORT
# define LCD_DATA1_PORT LCD_PORT /**< port for 4bit data bit 1 */
# endif
# ifndef LCD_DATA2_PORT
# define LCD_DATA2_PORT LCD_PORT /**< port for 4bit data bit 2 */
# endif
# ifndef LCD_DATA3_PORT
# define LCD_DATA3_PORT LCD_PORT /**< port for 4bit data bit 3 */
# endif
# ifndef LCD_DATA0_PIN
# define LCD_DATA0_PIN 4 /**< pin for 4bit data bit 0 */
# endif
# ifndef LCD_DATA1_PIN
# define LCD_DATA1_PIN 5 /**< pin for 4bit data bit 1 */
# endif
# ifndef LCD_DATA2_PIN
# define LCD_DATA2_PIN 6 /**< pin for 4bit data bit 2 */
# endif
# ifndef LCD_DATA3_PIN
# define LCD_DATA3_PIN 7 /**< pin for 4bit data bit 3 */
# endif
# ifndef LCD_RS_PORT
# define LCD_RS_PORT LCD_PORT /**< port for RS line */
# endif
# ifndef LCD_RS_PIN
# define LCD_RS_PIN 3 /**< pin for RS line */
# endif
# ifndef LCD_RW_PORT
# define LCD_RW_PORT LCD_PORT /**< port for RW line */
# endif
# ifndef LCD_RW_PIN
# define LCD_RW_PIN 2 /**< pin for RW line */
# endif
# ifndef LCD_E_PORT
# define LCD_E_PORT LCD_PORT /**< port for Enable line */
# endif
# ifndef LCD_E_PIN
# define LCD_E_PIN 1 /**< pin for Enable line */
# endif
#elif defined(__AVR_AT90S4414__) || defined(__AVR_AT90S8515__) || defined(__AVR_ATmega64__) || defined(__AVR_ATmega8515__) || defined(__AVR_ATmega103__) || defined(__AVR_ATmega128__) || defined(__AVR_ATmega161__) || defined(__AVR_ATmega162__)
/*
* memory mapped mode is only supported when the device has an external data memory interface
*/
# define LCD_IO_DATA 0xC000 /* A15=E=1, A14=RS=1 */
# define LCD_IO_FUNCTION 0x8000 /* A15=E=1, A14=RS=0 */
# define LCD_IO_READ 0x0100 /* A8 =R/W=1 (R/W: 1=Read, 0=Write */
#else
# error "external data memory interface not available for this device, use 4-bit IO port mode"
#endif
/**
* @name Definitions of delays
* Used to calculate delay timers.
* Adapt the F_CPU define in the Makefile to the clock frequency in Hz of your target
*
* These delay times can be adjusted, if some displays require different delays.\n
* These definitions can be defined in a separate include file \b lcd_definitions.h instead modifying this file by
* adding \b -D_LCD_DEFINITIONS_FILE to the \b CDEFS section in the Makefile.
* All definitions added to the file lcd_definitions.h will override the default definitions from lcd.h
*/
#ifndef LCD_DELAY_BOOTUP
# define LCD_DELAY_BOOTUP 16000 /**< delay in micro seconds after power-on */
#endif
#ifndef LCD_DELAY_INIT
# define LCD_DELAY_INIT 5000 /**< delay in micro seconds after initialization command sent */
#endif
#ifndef LCD_DELAY_INIT_REP
# define LCD_DELAY_INIT_REP 64 /**< delay in micro seconds after initialization command repeated */
#endif
#ifndef LCD_DELAY_INIT_4BIT
# define LCD_DELAY_INIT_4BIT 64 /**< delay in micro seconds after setting 4-bit mode */
#endif
#ifndef LCD_DELAY_BUSY_FLAG
# define LCD_DELAY_BUSY_FLAG 4 /**< time in micro seconds the address counter is updated after busy flag is cleared */
#endif
#ifndef LCD_DELAY_ENABLE_PULSE
# define LCD_DELAY_ENABLE_PULSE 1 /**< enable signal pulse width in micro seconds */
#endif
/**
* @name Definitions for LCD command instructions
* The constants define the various LCD controller instructions which can be passed to the
* function lcd_command(), see HD44780 data sheet for a complete description.
*/
/* instruction register bit positions, see HD44780U data sheet */
#define LCD_CLR 0 /* DB0: clear display */
#define LCD_HOME 1 /* DB1: return to home position */
#define LCD_ENTRY_MODE 2 /* DB2: set entry mode */
#define LCD_ENTRY_INC 1 /* DB1: 1=increment, 0=decrement */
#define LCD_ENTRY_SHIFT 0 /* DB2: 1=display shift on */
#define LCD_ON 3 /* DB3: turn lcd/cursor on */
#define LCD_ON_DISPLAY 2 /* DB2: turn display on */
#define LCD_ON_CURSOR 1 /* DB1: turn cursor on */
#define LCD_ON_BLINK 0 /* DB0: blinking cursor ? */
#define LCD_MOVE 4 /* DB4: move cursor/display */
#define LCD_MOVE_DISP 3 /* DB3: move display (0-> cursor) ? */
#define LCD_MOVE_RIGHT 2 /* DB2: move right (0-> left) ? */
#define LCD_FUNCTION 5 /* DB5: function set */
#define LCD_FUNCTION_8BIT 4 /* DB4: set 8BIT mode (0->4BIT mode) */
#define LCD_FUNCTION_2LINES 3 /* DB3: two lines (0->one line) */
#define LCD_FUNCTION_10DOTS 2 /* DB2: 5x10 font (0->5x7 font) */
#define LCD_CGRAM 6 /* DB6: set CG RAM address */
#define LCD_DDRAM 7 /* DB7: set DD RAM address */
#define LCD_BUSY 7 /* DB7: LCD is busy */
/* set entry mode: display shift on/off, dec/inc cursor move direction */
#define LCD_ENTRY_DEC 0x04 /* display shift off, dec cursor move dir */
#define LCD_ENTRY_DEC_SHIFT 0x05 /* display shift on, dec cursor move dir */
#define LCD_ENTRY_INC_ 0x06 /* display shift off, inc cursor move dir */
#define LCD_ENTRY_INC_SHIFT 0x07 /* display shift on, inc cursor move dir */
/* display on/off, cursor on/off, blinking char at cursor position */
#define LCD_DISP_OFF 0x08 /* display off */
#define LCD_DISP_ON 0x0C /* display on, cursor off */
#define LCD_DISP_ON_BLINK 0x0D /* display on, cursor off, blink char */
#define LCD_DISP_ON_CURSOR 0x0E /* display on, cursor on */
#define LCD_DISP_ON_CURSOR_BLINK 0x0F /* display on, cursor on, blink char */
/* move cursor/shift display */
#define LCD_MOVE_CURSOR_LEFT 0x10 /* move cursor left (decrement) */
#define LCD_MOVE_CURSOR_RIGHT 0x14 /* move cursor right (increment) */
#define LCD_MOVE_DISP_LEFT 0x18 /* shift display left */
#define LCD_MOVE_DISP_RIGHT 0x1C /* shift display right */
/* function set: set interface data length and number of display lines */
#define LCD_FUNCTION_4BIT_1LINE 0x20 /* 4-bit interface, single line, 5x7 dots */
#define LCD_FUNCTION_4BIT_2LINES 0x28 /* 4-bit interface, dual line, 5x7 dots */
#define LCD_FUNCTION_8BIT_1LINE 0x30 /* 8-bit interface, single line, 5x7 dots */
#define LCD_FUNCTION_8BIT_2LINES 0x38 /* 8-bit interface, dual line, 5x7 dots */
#define LCD_MODE_DEFAULT ((1 << LCD_ENTRY_MODE) | (1 << LCD_ENTRY_INC))
/**
* @name Functions
*/
/**
@brief Initialize display and select type of cursor
@param dispAttr \b LCD_DISP_OFF display off\n
\b LCD_DISP_ON display on, cursor off\n
\b LCD_DISP_ON_CURSOR display on, cursor on\n
\b LCD_DISP_ON_CURSOR_BLINK display on, cursor on flashing
@return none
*/
extern void lcd_init(uint8_t dispAttr);
/**
@brief Clear display and set cursor to home position
@return none
*/
extern void lcd_clrscr(void);
/**
@brief Set cursor to home position
@return none
*/
extern void lcd_home(void);
/**
@brief Set cursor to specified position
@param x horizontal position\n (0: left most position)
@param y vertical position\n (0: first line)
@return none
*/
extern void lcd_gotoxy(uint8_t x, uint8_t y);
/**
@brief Display character at current cursor position
@param c character to be displayed
@return none
*/
extern void lcd_putc(char c);
/**
@brief Display string without auto linefeed
@param s string to be displayed
@return none
*/
extern void lcd_puts(const char *s);
/**
@brief Display string from program memory without auto linefeed
@param progmem_s string from program memory be be displayed
@return none
@see lcd_puts_P
*/
extern void lcd_puts_p(const char *progmem_s);
/**
@brief Send LCD controller instruction command
@param cmd instruction to send to LCD controller, see HD44780 data sheet
@return none
*/
extern void lcd_command(uint8_t cmd);
/**
@brief Send data byte to LCD controller
Similar to lcd_putc(), but without interpreting LF
@param data byte to send to LCD controller, see HD44780 data sheet
@return none
*/
extern void lcd_data(uint8_t data);
/**
@brief macros for automatically storing string constant in program memory
*/
#define lcd_puts_P(__s) lcd_puts_p(PSTR(__s))
/**@}*/

View File

@@ -20,10 +20,11 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "ws2812.h"
#include <avr/interrupt.h> #include <avr/interrupt.h>
#include <avr/io.h> #include <avr/io.h>
#include <util/delay.h> #include <util/delay.h>
#include "ws2812.h"
#include "pin_defs.h"
#define pinmask(pin) (_BV((pin)&0xF)) #define pinmask(pin) (_BV((pin)&0xF))

View File

@@ -0,0 +1,19 @@
// Copyright 2022 QMK
// SPDX-License-Identifier: GPL-2.0-or-later
// For some reason this bit is undocumented for some AVR parts and not defined in their avr-libc IO headers
// See https://stackoverflow.com/questions/12350914/how-to-read-atmega-32-signature-row
#ifndef SIGRD
# define SIGRD 5
#endif // SIGRD
#include <avr/boot.h>
#include "hardware_id.h"
hardware_id_t get_hardware_id(void) {
hardware_id_t id = {0};
for (uint8_t i = 0; i < 10; i += 1) {
((uint8_t*)&id)[i] = boot_signature_byte_get(i + 0x0E);
}
return id;
}

Some files were not shown because too many files have changed in this diff Show More