Merge remote-tracking branch 'qmk 0.17.0' into firmware21
This commit is contained in:
18
.github/workflows/auto_approve.yml
vendored
Normal file
18
.github/workflows/auto_approve.yml
vendored
Normal 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/"
|
||||||
4
.github/workflows/unit_test.yml
vendored
4
.github/workflows/unit_test.yml
vendored
@@ -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
3
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
theme: jekyll-theme-cayman
|
|
||||||
@@ -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.)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
37
builddefs/converters.mk
Normal 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
|
||||||
@@ -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 \
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
35
data/mappings/defaults.json
Normal file
35
data/mappings/defaults.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"},
|
||||||
|
|||||||
@@ -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"}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -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
58
drivers/gpio/sn74x154.c
Normal 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
48
drivers/gpio/sn74x154.h
Normal 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);
|
||||||
@@ -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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
284
drivers/lcd/hd44780.c
Normal 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
220
drivers/lcd/hd44780.h
Normal 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
|
||||||
|
|
||||||
|
/** \} */
|
||||||
@@ -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);
|
||||||
|
|||||||
137
drivers/painter/comms/qp_comms_spi.c
Normal file
137
drivers/painter/comms/qp_comms_spi.c
Normal 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
|
||||||
51
drivers/painter/comms/qp_comms_spi.h
Normal file
51
drivers/painter/comms/qp_comms_spi.h
Normal 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
|
||||||
150
drivers/painter/gc9a01/qp_gc9a01.c
Normal file
150
drivers/painter/gc9a01/qp_gc9a01.c
Normal 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
|
||||||
37
drivers/painter/gc9a01/qp_gc9a01.h
Normal file
37
drivers/painter/gc9a01/qp_gc9a01.h
Normal 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
|
||||||
78
drivers/painter/gc9a01/qp_gc9a01_opcodes.h
Normal file
78
drivers/painter/gc9a01/qp_gc9a01_opcodes.h
Normal 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
|
||||||
121
drivers/painter/ili9xxx/qp_ili9163.c
Normal file
121
drivers/painter/ili9xxx/qp_ili9163.c
Normal 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
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
37
drivers/painter/ili9xxx/qp_ili9163.h
Normal file
37
drivers/painter/ili9xxx/qp_ili9163.h
Normal 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
|
||||||
128
drivers/painter/ili9xxx/qp_ili9341.c
Normal file
128
drivers/painter/ili9xxx/qp_ili9341.c
Normal 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
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
37
drivers/painter/ili9xxx/qp_ili9341.h
Normal file
37
drivers/painter/ili9xxx/qp_ili9341.h
Normal 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
|
||||||
100
drivers/painter/ili9xxx/qp_ili9xxx_opcodes.h
Normal file
100
drivers/painter/ili9xxx/qp_ili9xxx_opcodes.h
Normal 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
|
||||||
125
drivers/painter/ssd1351/qp_ssd1351.c
Normal file
125
drivers/painter/ssd1351/qp_ssd1351.c
Normal 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
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
37
drivers/painter/ssd1351/qp_ssd1351.h
Normal file
37
drivers/painter/ssd1351/qp_ssd1351.h
Normal 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
|
||||||
48
drivers/painter/ssd1351/qp_ssd1351_opcodes.h
Normal file
48
drivers/painter/ssd1351/qp_ssd1351_opcodes.h
Normal 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
|
||||||
144
drivers/painter/st77xx/qp_st7789.c
Normal file
144
drivers/painter/st77xx/qp_st7789.c
Normal 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
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
44
drivers/painter/st77xx/qp_st7789.h
Normal file
44
drivers/painter/st77xx/qp_st7789.h
Normal 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
|
||||||
64
drivers/painter/st77xx/qp_st7789_opcodes.h
Normal file
64
drivers/painter/st77xx/qp_st7789_opcodes.h
Normal 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
|
||||||
51
drivers/painter/st77xx/qp_st77xx_opcodes.h
Normal file
51
drivers/painter/st77xx/qp_st77xx_opcodes.h
Normal 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
|
||||||
130
drivers/painter/tft_panel/qp_tft_panel.c
Normal file
130
drivers/painter/tft_panel/qp_tft_panel.c
Normal 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;
|
||||||
|
}
|
||||||
67
drivers/painter/tft_panel/qp_tft_panel.h
Normal file
67
drivers/painter/tft_panel/qp_tft_panel.h
Normal 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);
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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, _______, _______,
|
||||||
|
|||||||
@@ -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, _______, _______, _______,
|
||||||
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______
|
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______
|
||||||
|
|||||||
Submodule lib/chibios updated: d7b9d1c87f...257302333c
Submodule lib/chibios-contrib updated: d1c2126d1c...2a6b73ff51
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
75
lib/python/qmk/cli/generate/keyboard_c.py
Executable file
75
lib/python/qmk/cli/generate/keyboard_c.py
Executable 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)
|
||||||
@@ -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 ''
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
2
lib/python/qmk/cli/painter/__init__.py
Normal file
2
lib/python/qmk/cli/painter/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import convert_graphics
|
||||||
|
from . import make_font
|
||||||
86
lib/python/qmk/cli/painter/convert_graphics.py
Normal file
86
lib/python/qmk/cli/painter/convert_graphics.py
Normal 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()
|
||||||
87
lib/python/qmk/cli/painter/make_font.py
Normal file
87
lib/python/qmk/cli/painter/make_font.py
Normal 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()
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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}.')
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
268
lib/python/qmk/painter.py
Normal 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
|
||||||
401
lib/python/qmk/painter_qff.py
Normal file
401
lib/python/qmk/painter_qff.py
Normal 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)
|
||||||
408
lib/python/qmk/painter_qgf.py
Normal file
408
lib/python/qmk/painter_qgf.py
Normal 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()}")
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|||||||
9
platforms/arm_atsam/hardware_id.c
Normal file
9
platforms/arm_atsam/hardware_id.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -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 (;;)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 (;;)
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 */
|
|
||||||
@@ -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))
|
|
||||||
|
|
||||||
/**@}*/
|
|
||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
19
platforms/avr/hardware_id.c
Normal file
19
platforms/avr/hardware_id.c
Normal 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
Reference in New Issue
Block a user