diff options
Diffstat (limited to 'docs')
-rw-r--r-- | docs/_summary.md | 1 | ||||
-rw-r--r-- | docs/breaking_changes.md | 4 | ||||
-rw-r--r-- | docs/chibios_upgrade_instructions.md | 56 | ||||
-rw-r--r-- | docs/config_options.md | 29 | ||||
-rw-r--r-- | docs/custom_quantum_functions.md | 8 | ||||
-rw-r--r-- | docs/feature_debounce_type.md | 4 | ||||
-rw-r--r-- | docs/feature_oled_driver.md | 4 | ||||
-rw-r--r-- | docs/feature_rgb_matrix.md | 68 | ||||
-rw-r--r-- | docs/feature_split_keyboard.md | 114 | ||||
-rw-r--r-- | docs/feature_st7565.md | 274 |
10 files changed, 548 insertions, 14 deletions
diff --git a/docs/_summary.md b/docs/_summary.md index 9798ef5127..4141e01e77 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -93,6 +93,7 @@ * Hardware Features * Displays * [HD44780 LCD Controller](feature_hd44780.md) + * [ST7565 LCD Driver](feature_st7565.md) * [OLED Driver](feature_oled_driver.md) * Lighting * [Backlight](feature_backlight.md) diff --git a/docs/breaking_changes.md b/docs/breaking_changes.md index b0d56a81bd..a1a56bd457 100644 --- a/docs/breaking_changes.md +++ b/docs/breaking_changes.md @@ -100,3 +100,7 @@ This happens immediately after the previous `develop` branch is merged. * [ ] `git pull --ff-only` * [ ] `git merge --no-ff develop` * [ ] `git push upstream master` + +## Post-merge operations + +* (Optional) [update ChibiOS + ChibiOS-Contrib on `develop`](chibios_upgrade_instructions.md) diff --git a/docs/chibios_upgrade_instructions.md b/docs/chibios_upgrade_instructions.md new file mode 100644 index 0000000000..40c2faafcf --- /dev/null +++ b/docs/chibios_upgrade_instructions.md @@ -0,0 +1,56 @@ +# ChibiOS Upgrade Procedure + +ChibiOS and ChibiOS-Contrib need to be updated in tandem -- the latter has a branch tied to the ChibiOS version in use and should not be mixed with different versions. + +## Getting ChibiOS + +* `svn` Initialisation: + * Only needed to be done once + * You might need to separately install `git-svn` package in your OS's package manager + * `git svn init --stdlayout --prefix='svn/' http://svn.osdn.net/svnroot/chibios/` + * `git remote add qmk git@github.com:qmk/ChibiOS.git` +* Updating: + * `git svn fetch` + * First time around this will take several hours + * Subsequent updates will be incremental only +* Tagging example (work out which version first!): + * `git tag -a ver20.3.3 -m ver20.3.3 svn/tags/ver20.3.3` + * `git push qmk ver20.3.3` + * `git tag -a breaking_YYYY_qN -m breaking_YYYY_qN svn/tags/ver20.3.3` + * `git push qmk breaking_YYYY_qN` + +## Getting ChibiOS-Contrib + +* `git` Initialisation: + * `git clone git@github.com:qmk/ChibiOS-Contrib` + * `git remote add upstream https://github.com/ChibiOS/ChibiOS-Contrib` + * `git checkout -b chibios-20.3.x upstream/chibios-20.3.x` +* Updating: + * `git fetch --all --tags --prune` + * `git checkout chibios-20.3.x` + * `git pull --ff-only` + * `git push origin chibios-20.3.x` + * `git tag -a breaking_YYYY_qN -m breaking_YYYY_qN chibios-20.3.x` + * `git push origin breaking_YYYY_qN` + +## Updating submodules + +* Update the submodules + * `cd $QMK_FIRMWARE` + * `git checkout develop` + * `git pull --ff-only` + * `git checkout -b chibios-version-bump` + * `cd lib/chibios` + * `git fetch --all --tags --prune` + * `git checkout breaking_YYYY_qN` + * `cd ../chibios-contrib` + * `git fetch --all --tags --prune` + * `git checkout breaking_YYYY_qN` +* Build everything + * `cd $QMK_FIRMWARE` + * `qmk multibuild -j4` + * Make sure there are no errors +* Push to the repo + * `git commit -am 'Update ChibiOS to XXXXXXXXX'` + * `git push --set-upstream origin chibios-version-bump` +* Make a PR to qmk_firmware with the new branch
\ No newline at end of file diff --git a/docs/config_options.md b/docs/config_options.md index d0f0b316e0..980195ac68 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -51,8 +51,10 @@ This is a C header file that is one of the first things included, and will persi * the number of columns in your keyboard's matrix * `#define MATRIX_ROW_PINS { D0, D5, B5, B6 }` * pins of the rows, from top to bottom + * may be omitted by the keyboard designer if matrix reads are handled in an alternate manner. See [low-level matrix overrides](custom_quantum_functions.md?id=low-level-matrix-overrides) for more information. * `#define MATRIX_COL_PINS { F1, F0, B0, C7, F4, F5, F6, F7, D4, D6, B4, D7 }` * pins of the columns, from left to right + * may be omitted by the keyboard designer if matrix reads are handled in an alternate manner. See [low-level matrix overrides](custom_quantum_functions.md?id=low-level-matrix-overrides) for more information. * `#define MATRIX_IO_DELAY 30` * the delay in microseconds when between changing matrix pin state and reading values * `#define UNUSED_PINS { D1, D2, D3, B1, B2, B3 }` @@ -272,7 +274,7 @@ There are a few different ways to set handedness for split keyboards (listed in ### Other Options * `#define USE_I2C` - * For using I2C instead of Serial (defaults to serial) + * For using I2C instead of Serial (default is serial; serial transport is supported on ARM -- I2C is AVR-only) * `#define SOFT_SERIAL_PIN D0` * When using serial, define this. `D0` or `D1`,`D2`,`D3`,`E6`. @@ -280,6 +282,7 @@ There are a few different ways to set handedness for split keyboards (listed in * `#define MATRIX_ROW_PINS_RIGHT { <row pins> }` * `#define MATRIX_COL_PINS_RIGHT { <col pins> }` * If you want to specify a different pinout for the right half than the left half, you can define `MATRIX_ROW_PINS_RIGHT`/`MATRIX_COL_PINS_RIGHT`. Currently, the size of `MATRIX_ROW_PINS` must be the same as `MATRIX_ROW_PINS_RIGHT` and likewise for the definition of columns. + * may be omitted by the keyboard designer if matrix reads are handled in an alternate manner. See [low-level matrix overrides](custom_quantum_functions.md?id=low-level-matrix-overrides) for more information. * `#define DIRECT_PINS_RIGHT { { F1, F0, B0, C7 }, { F4, F5, F6, F7 } }` * If you want to specify a different direct pinout for the right half than the left half, you can define `DIRECT_PINS_RIGHT`. Currently, the size of `DIRECT_PINS` must be the same as `DIRECT_PINS_RIGHT`. @@ -300,7 +303,7 @@ There are a few different ways to set handedness for split keyboards (listed in * `#define SPLIT_USB_DETECT` * Detect (with timeout) USB connection when delegating master/slave * Default behavior for ARM - * Required for AVR Teensy + * Required for AVR Teensy (without hardware mods) * `#define SPLIT_USB_TIMEOUT 2000` * Maximum timeout when detecting master/slave when using `SPLIT_USB_DETECT` @@ -308,6 +311,28 @@ There are a few different ways to set handedness for split keyboards (listed in * `#define SPLIT_USB_TIMEOUT_POLL 10` * Poll frequency when detecting master/slave when using `SPLIT_USB_DETECT` +* `#define FORCED_SYNC_THROTTLE_MS 100` + * Deadline for synchronizing data from master to slave when using the QMK-provided split transport. + +* `#define SPLIT_TRANSPORT_MIRROR` + * Mirrors the master-side matrix on the slave when using the QMK-provided split transport. + +* `#define SPLIT_LAYER_STATE_ENABLE` + * Ensures the current layer state is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_LED_STATE_ENABLE` + * Ensures the current host indicator state (caps/num/scroll) is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_MODS_ENABLE` + * Ensures the current modifier state (normal, weak, and oneshot) is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_WPM_ENABLE` + * Ensures the current WPM is available on the slave when using the QMK-provided split transport. + +* `#define SPLIT_TRANSACTION_IDS_KB .....` +* `#define SPLIT_TRANSACTION_IDS_USER .....` + * Allows for custom data sync with the slave when using the QMK-provided split transport. See [custom data sync between sides](feature_split_keyboard.md#custom-data-sync) for more information. + # The `rules.mk` File This is a [make](https://www.gnu.org/software/make/manual/make.html) file that is included by the top-level `Makefile`. It is used to set some information about the MCU that we will be compiling for as well as enabling and disabling certain features. diff --git a/docs/custom_quantum_functions.md b/docs/custom_quantum_functions.md index 694b421e79..30c637bb49 100644 --- a/docs/custom_quantum_functions.md +++ b/docs/custom_quantum_functions.md @@ -144,6 +144,14 @@ This is useful for setting up stuff that you may need elsewhere, but isn't hardw * Keyboard/Revision: `void matrix_init_kb(void)` * Keymap: `void matrix_init_user(void)` +### Low-level Matrix Overrides Function Documentation :id=low-level-matrix-overrides + +* GPIO pin initialisation: `void matrix_init_pins(void)` + * This needs to perform the low-level initialisation of all row and column pins. By default this will initialise the input/output state of each of the GPIO pins listed in `MATRIX_ROW_PINS` and `MATRIX_COL_PINS`, based on whether or not the keyboard is set up for `ROW2COL`, `COL2ROW`, or `DIRECT_PINS`. Should the keyboard designer override this function, no initialisation of pin state will occur within QMK itself, instead deferring to the keyboard's override. +* `COL2ROW`-based row reads: `void matrix_read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col)` +* `ROW2COL`-based column reads: `void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)` +* `DIRECT_PINS`-based reads: `void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)` + * These three functions need to perform the low-level retrieval of matrix state of relevant input pins, based on the matrix type. Only one of the functions should be implemented, if needed. By default this will iterate through `MATRIX_ROW_PINS` and `MATRIX_COL_PINS`, configuring the inputs and outputs based on whether or not the keyboard is set up for `ROW2COL`, `COL2ROW`, or `DIRECT_PINS`. Should the keyboard designer override this function, no manipulation of matrix GPIO pin state will occur within QMK itself, instead deferring to the keyboard's override. ## Keyboard Post Initialization code diff --git a/docs/feature_debounce_type.md b/docs/feature_debounce_type.md index 3ad74224c1..306185fe83 100644 --- a/docs/feature_debounce_type.md +++ b/docs/feature_debounce_type.md @@ -121,16 +121,16 @@ DEBOUNCE_TYPE = <name of algorithm> Where name of algorithm is one of: * ```sym_defer_g``` - debouncing per keyboard. On any state change, a global timer is set. When ```DEBOUNCE``` milliseconds of no changes has occurred, all input changes are pushed. * This is the current default algorithm. This is the highest performance algorithm with lowest memory usage, and it's also noise-resistant. -* ```sym_eager_pr``` - debouncing per row. On any state change, response is immediate, followed by locking the row ```DEBOUNCE``` milliseconds of no further input for that row. +* ```sym_eager_pr``` - debouncing per row. On any state change, response is immediate, followed by locking the row ```DEBOUNCE``` milliseconds of no further input for that row. For use in keyboards where refreshing ```NUM_KEYS``` 8-bit counters is computationally expensive / low scan rate, and fingers usually only hit one row at a time. This could be appropriate for the ErgoDox models; the matrix is rotated 90°, and hence its "rows" are really columns, and each finger only hits a single "row" at a time in normal use. * ```sym_eager_pk``` - debouncing per key. On any state change, response is immediate, followed by ```DEBOUNCE``` milliseconds of no further input for that key * ```sym_defer_pk``` - debouncing per key. On any state change, a per-key timer is set. When ```DEBOUNCE``` milliseconds of no changes have occurred on that key, the key status change is pushed. +* ```asym_eager_defer_pk``` - debouncing per key. On a key-down state change, response is immediate, followed by ```DEBOUNCE``` milliseconds of no further input for that key. On a key-up state change, a per-key timer is set. When ```DEBOUNCE``` milliseconds of no changes have occurred on that key, the key-up status change is pushed. ### A couple algorithms that could be implemented in the future: * ```sym_defer_pr``` * ```sym_eager_g``` -* ```asym_eager_defer_pk``` ### Use your own debouncing code You have the option to implement you own debouncing algorithm. To do this: diff --git a/docs/feature_oled_driver.md b/docs/feature_oled_driver.md index f3b659b1bc..c90aabb9c6 100644 --- a/docs/feature_oled_driver.md +++ b/docs/feature_oled_driver.md @@ -346,6 +346,10 @@ bool oled_scroll_left(void); // Returns true if the screen was not scrolling or stops scrolling bool oled_scroll_off(void); +// Inverts the display +// Returns true if the screen was or is inverted +bool oled_invert(bool invert); + // Returns the maximum number of characters that will fit on a line uint8_t oled_max_chars(void); diff --git a/docs/feature_rgb_matrix.md b/docs/feature_rgb_matrix.md index bfb3688b67..25ba3ffe32 100644 --- a/docs/feature_rgb_matrix.md +++ b/docs/feature_rgb_matrix.md @@ -228,6 +228,74 @@ Configure the hardware via your `config.h`: ``` --- +### AW20216 :id=aw20216 +There is basic support for addressable RGB matrix lighting with the SPI AW20216 RGB controller. To enable it, add this to your `rules.mk`: + +```makefile +RGB_MATRIX_ENABLE = yes +RGB_MATRIX_DRIVER = AW20216 +``` + +You can use up to 2 AW20216 IC's. Do not specify `DRIVER_<N>_xxx` defines for IC's that are not present on your keyboard. You can define the following items in `config.h`: + +| Variable | Description | Default | +|----------|-------------|---------| +| `DRIVER_1_CS` | (Required) MCU pin connected to first RGB driver chip select line | B13 | +| `DRIVER_2_CS` | (Optional) MCU pin connected to second RGB driver chip select line | | +| `DRIVER_1_EN` | (Required) MCU pin connected to first RGB driver hardware enable line | C13 | +| `DRIVER_2_EN` | (Optional) MCU pin connected to second RGB driver hardware enable line | | +| `DRIVER_1_LED_TOTAL` | (Required) How many RGB lights are connected to first RGB driver | | +| `DRIVER_2_LED_TOTAL` | (Optional) How many RGB lights are connected to second RGB driver | | +| `DRIVER_COUNT` | (Required) How many RGB driver IC's are present | | +| `DRIVER_LED_TOTAL` | (Required) How many RGB lights are present across all drivers | | +| `AW_SCALING_MAX` | (Optional) LED current scaling value (0-255, higher values mean LED is brighter at full PWM) | 150 | +| `AW_GLOBAL_CURRENT_MAX` | (Optional) Driver global current limit (0-255, higher values means the driver may consume more power) | 150 | + +Here is an example using 2 drivers. + +```c +#define DRIVER_1_CS B13 +#define DRIVER_2_CS B14 +// Hardware enable lines may be connected to the same pin +#define DRIVER_1_EN C13 +#define DRIVER_2_EN C13 + +#define DRIVER_COUNT 2 +#define DRIVER_1_LED_TOTAL 66 +#define DRIVER_2_LED_TOTAL 32 +#define DRIVER_LED_TOTAL (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL) +``` + +!> Note the parentheses, this is so when `DRIVER_LED_TOTAL` is used in code and expanded, the values are added together before any additional math is applied to them. As an example, `rand() % (DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL)` will give very different results than `rand() % DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL`. + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + +```c +const aw_led g_aw_leds[DRIVER_LED_TOTAL] = { +/* Each AW20216 channel is controlled by a register at some offset between 0x00 + * and 0xD7 inclusive. + * See drivers/awinic/aw20216.h for the mapping between register offsets and + * driver pin locations. + * driver + * | R location + * | | G location + * | | | B location + * | | | | */ + { 0, CS1_SW1, CS2_SW1, CS3_SW1 }, + { 0, CS4_SW1, CS5_SW1, CS6_SW1 }, + { 0, CS7_SW1, CS8_SW1, CS9_SW1 }, + { 0, CS10_SW1, CS11_SW1, CS12_SW1 }, + { 0, CS13_SW1, CS14_SW1, CS15_SW1 }, + ... + { 1, CS1_SW1, CS2_SW1, CS3_SW1 }, + { 1, CS13_SW1, CS14_SW1, CS15_SW1 }, + { 1, CS16_SW1, CS17_SW1, CS18_SW1 }, + { 1, CS4_SW2, CS5_SW2, CS6_SW2 }, + ... +}; +``` + +--- ## Common Configuration :id=common-configuration diff --git a/docs/feature_split_keyboard.md b/docs/feature_split_keyboard.md index 4ebf585f5c..603c387c2d 100644 --- a/docs/feature_split_keyboard.md +++ b/docs/feature_split_keyboard.md @@ -8,8 +8,7 @@ QMK Firmware has a generic implementation that is usable by any board, as well a For this, we will mostly be talking about the generic implementation used by the Let's Split and other keyboards. -!> ARM is not yet fully supported for Split Keyboards and has many limitations. Progress is being made, but we have not yet reached 100% feature parity. - +!> ARM split supports most QMK subsystems when using the 'serial' and 'serial_usart' drivers. I2C slave is currently unsupported. ## Compatibility Overview @@ -169,7 +168,7 @@ Because not every split keyboard is identical, there are a number of additional #define USE_I2C ``` -This enables I<sup>2</sup>C support for split keyboards. This isn't strictly for communication, but can be used for OLED or other I<sup>2</sup>C-based devices. +This configures the use of I<sup>2</sup>C support for split keyboard transport (AVR only). ```c #define SOFT_SERIAL_PIN D0 @@ -193,20 +192,115 @@ If you're having issues with serial communication, you can change this value, as * **`5`**: about 20kbps ```c -#define SPLIT_MODS_ENABLE +#define FORCED_SYNC_THROTTLE_MS 100 ``` -This enables transmitting modifier state (normal, weak and oneshot) to the non -primary side of the split keyboard. This adds a few bytes of data to the split -communication protocol and may impact the matrix scan speed when enabled. -The purpose of this feature is to support cosmetic use of modifer state (e.g. -displaying status on an OLED screen). +This sets the maximum number of milliseconds before forcing a synchronization of data from master to slave. Under normal circumstances this sync occurs whenever the data _changes_, for safety a data transfer occurs after this number of milliseconds if no change has been detected since the last sync. ```c #define SPLIT_TRANSPORT_MIRROR ``` -This mirrors the master side matrix to the slave side for features that react or require knowledge of master side key presses on the slave side. This adds a few bytes of data to the split communication protocol and may impact the matrix scan speed when enabled. The purpose of this feature is to support cosmetic use of key events (e.g. RGB reacting to Keypresses). +This mirrors the master side matrix to the slave side for features that react or require knowledge of master side key presses on the slave side. The purpose of this feature is to support cosmetic use of key events (e.g. RGB reacting to keypresses). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_LAYER_STATE_ENABLE +``` + +This enables syncing of the layer state between both halves of the split keyboard. The main purpose of this feature is to enable support for use of things like OLED display of the currently active layer. This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_LED_STATE_ENABLE +``` + +This enables syncing of the Host LED status (caps lock, num lock, etc) between both halves of the split keyboard. The main purpose of this feature is to enable support for use of things like OLED display of the Host LED status. This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_MODS_ENABLE +``` + +This enables transmitting modifier state (normal, weak and oneshot) to the non primary side of the split keyboard. The purpose of this feature is to support cosmetic use of modifer state (e.g. displaying status on an OLED screen). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +```c +#define SPLIT_WPM_ENABLE +``` + +This enables transmitting the current WPM to the slave side of the split keyboard. The purpose of this feature is to support cosmetic use of WPM (e.g. displaying the current value on an OLED screen). This adds overhead to the split communication protocol and may negatively impact the matrix scan speed when enabled. + +### Custom data sync between sides :id=custom-data-sync + +QMK's split transport allows for arbitrary data transactions at both the keyboard and user levels. This is modelled on a remote procedure call, with the master invoking a function on the slave side, with the ability to send data from master to slave, process it slave side, and send data back from slave to master. + +To leverage this, a keyboard or user/keymap can define a comma-separated list of _transaction IDs_: + +```c +// for keyboard-level data sync: +#define SPLIT_TRANSACTION_IDS_KB KEYBOARD_SYNC_A, KEYBOARD_SYNC_B +// or, for user: +#define SPLIT_TRANSACTION_IDS_USER USER_SYNC_A, USER_SYNC_B, USER_SYNC_C +``` + +These _transaction IDs_ then need a slave-side handler function to be registered with the split transport, for example: + +```c +typedef struct _master_to_slave_t { + int m2s_data; +} master_to_slave_t; + +typedef struct _slave_to_master_t { + int s2m_data; +} slave_to_master_t; + +void user_sync_a_slave_handler(uint8_t in_buflen, const void* in_data, uint8_t out_buflen, void* out_data) { + const master_to_slave_t *m2s = (const master_to_slave_t*)in_data; + slave_to_master_t *s2m = (slave_to_master_t*)out_data; + s2m->s2m_data = m2s->m2s_data + 5; // whatever comes in, add 5 so it can be sent back +} + +void keyboard_post_init_user(void) { + transaction_register_rpc(USER_SYNC_A, user_sync_a_slave_handler); +} +``` + +The master side can then invoke the slave-side handler - for normal keyboard functionality to be minimally affected, any keyboard- or user-level code attempting to sync data should be throttled: + +```c +void housekeeping_task_user(void) { + if (is_keyboard_master()) { + // Interact with slave every 500ms + static uint32_t last_sync = 0; + if (timer_elapsed32(last_sync) > 500) { + master_to_slave_t m2s = {6}; + slave_to_master_t s2m = {0}; + if(transaction_rpc_exec(USER_SYNC_A, sizeof(m2s), &m2s, sizeof(s2m), &s2m)) { + last_sync = timer_read32(); + dprintf("Slave value: %d\n", s2m.s2m_data); // this will now be 11, as the slave adds 5 + } else { + dprint("Slave sync failed!\n"); + } + } + } +} +``` + +!> It is recommended that any data sync between halves happens during the master side's _housekeeping task_. This ensures timely retries should failures occur. + +If only one-way data transfer is needed, helper methods are provided: + +```c +bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +bool transaction_rpc_send(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer); +bool transaction_rpc_recv(int8_t transaction_id, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +``` + +By default, the inbound and outbound data is limited to a maximum of 32 bytes each. The sizes can be altered if required: + +```c +// Master to slave: +#define RPC_M2S_BUFFER_SIZE 48 +// Slave to master: +#define RPC_S2M_BUFFER_SIZE 48 +``` ### Hardware Configuration Options diff --git a/docs/feature_st7565.md b/docs/feature_st7565.md new file mode 100644 index 0000000000..de3e44d8e9 --- /dev/null +++ b/docs/feature_st7565.md @@ -0,0 +1,274 @@ +# ST7565 LCD Driver + +## Supported Hardware + +LCD modules using ST7565 driver IC, communicating over SPI. + +|Module |IC |Size |Notes | +|------------------------------|-------|------|----------------------------------------------------------| +|Newhaven Display NHD-C12832A1Z|ST7565R|128x32|Used by Ergodox Infinity; primary consumer of this feature| +|Zolentech ZLE12864B |ST7565P|128x64|Requires contrast adjustment | + +## Usage + +To enable the feature, there are three steps. First, when compiling your keyboard, you'll need to add the following to your `rules.mk`: + +```make +ST7565_ENABLE = yes +``` + +Then in your `keymap.c` file, implement the ST7565 task call. This example assumes your keymap has three layers named `_QWERTY`, `_FN` and `_ADJ`: + +```c +#ifdef ST7565_ENABLE +void st7565_task_user(void) { + // Host Keyboard Layer Status + st7565_write_P(PSTR("Layer: "), false); + + switch (get_highest_layer(layer_state)) { + case _QWERTY: + st7565_write_P(PSTR("Default\n"), false); + break; + case _FN: + st7565_write_P(PSTR("FN\n"), false); + break; + case _ADJ: + st7565_write_P(PSTR("ADJ\n"), false); + break; + default: + // Or use the write_ln shortcut over adding '\n' to the end of your string + st7565_write_ln_P(PSTR("Undefined"), false); + } + + // Host Keyboard LED Status + led_t led_state = host_keyboard_led_state(); + st7565_write_P(led_state.num_lock ? PSTR("NUM ") : PSTR(" "), false); + st7565_write_P(led_state.caps_lock ? PSTR("CAP ") : PSTR(" "), false); + st7565_write_P(led_state.scroll_lock ? PSTR("SCR ") : PSTR(" "), false); +} +#endif +``` + +## Logo Example + +In the default font, certain ranges of characters are reserved for a QMK logo. To render this logo to the screen, use the following code example: + +```c +static void render_logo(void) { + static const char PROGMEM qmk_logo[] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0x00 + }; + + st7565_write_P(qmk_logo, false); +} +``` + +## Buffer Read Example +For some purposes, you may need to read the current state of the display buffer. The `st7565_read_raw` function can be used to safely read bytes from the buffer. + +In this example, calling `fade_display` in the `st7565_task_user` function will slowly fade away whatever is on the screen by turning random pixels off over time. +```c +//Setup some mask which can be or'd with bytes to turn off pixels +const uint8_t single_bit_masks[8] = {127, 191, 223, 239, 247, 251, 253, 254}; + +static void fade_display(void) { + //Define the reader structure + display_buffer_reader_t reader; + uint8_t buff_char; + if (random() % 30 == 0) { + srand(timer_read()); + // Fetch a pointer for the buffer byte at index 0. The return structure + // will have the pointer and the number of bytes remaining from this + // index position if we want to perform a sequential read by + // incrementing the buffer pointer + reader = st7565_read_raw(0); + //Loop over the remaining buffer and erase pixels as we go + for (uint16_t i = 0; i < reader.remaining_element_count; i++) { + //Get the actual byte in the buffer by dereferencing the pointer + buff_char = *reader.current_element; + if (buff_char != 0) { + st7565_write_raw_byte(buff_char & single_bit_masks[rand() % 8], i); + } + //increment the pointer to fetch a new byte during the next loop + reader.current_element++; + } + } +} +``` + +## Other Examples + +In split keyboards, it is very common to have two displays that each render different content and are oriented or flipped differently. You can do this by switching which content to render by using the return value from `is_keyboard_master()` or `is_keyboard_left()` found in `split_util.h`, e.g: + +```c +#ifdef ST7565_ENABLE +display_rotation_t st7565_init_user(display_rotation_t rotation) { + if (!is_keyboard_master()) { + return DISPLAY_ROTATION_180; // flips the display 180 degrees if offhand + } + + return rotation; +} + +void st7565_task_user(void) { + if (is_keyboard_master()) { + render_status(); // Renders the current keyboard state (layer, lock, caps, scroll, etc) + } else { + render_logo(); // Renders a static logo + } +} +#endif +``` + +## Basic Configuration + +|Define |Default |Description | +|------------------------|--------------|-----------------------------------------------------------------------------------------------------| +|`ST7565_A0_PIN` |*Not defined* |(Required) The GPIO connected to the display's A0 (data/command) pin | +|`ST7565_RST_PIN` |*Not defined* |(Required) The GPIO connected to the display's reset pin | +|`ST7565_SS_PIN` |*Not defined* |(Required) The GPIO connected to the display's slave select pin | +|`ST7565_SPI_CLK_DIVISOR`|`4` |The SPI clock divisor to use | +|`ST7565_FONT_H` |`"glcdfont.c"`|The font code file to use for custom fonts | +|`ST7565_FONT_START` |`0` |The starting character index for custom fonts | +|`ST7565_FONT_END` |`223` |The ending character index for custom fonts | +|`ST7565_FONT_WIDTH` |`6` |The font width | +|`ST7565_FONT_HEIGHT` |`8` |The font height (untested) | +|`ST7565_TIMEOUT` |`60000` |Turns off the screen after 60000ms of keyboard inactivity. Helps reduce burn-in. Set to 0 to disable.| +|`ST7565_COLUMN_OFFSET` |`0` |Shift output to the right this many pixels. | +|`ST7565_CONTRAST` |`32` |The default contrast level of the display, from 0 to 255. | +|`ST7565_UPDATE_INTERVAL`|`0` |Set the time interval for updating the display in ms. This will improve the matrix scan rate. | + +## Custom sized displays + +The default display size for this feature is 128x32 and all necessary defines are precalculated with that in mind. + +|Define |Default |Description | +|-----------------------|----------|-----------------------------------------------------------------------------------------------------------| +|`ST7565_DISPLAY_WIDTH` |`128` |The width of the display. | +|`ST7565_DISPLAY_HEIGHT`|`32` |The height of the display. | +|`ST7565_MATRIX_SIZE` |`512` |The local buffer size to allocate.<br>`(ST7565_DISPLAY_HEIGHT / 8 * ST7565_DISPLAY_WIDTH)`. | +|`ST7565_BLOCK_TYPE` |`uint16_t`|The unsigned integer type to use for dirty rendering. | +|`ST7565_BLOCK_COUNT` |`16` |The number of blocks the display is divided into for dirty rendering.<br>`(sizeof(ST7565_BLOCK_TYPE) * 8)`.| +|`ST7565_BLOCK_SIZE` |`32` |The size of each block for dirty rendering<br>`(ST7565_MATRIX_SIZE / ST7565_BLOCK_COUNT)`. | + +## API + +```c +// Rotation enum values are flags +typedef enum { + DISPLAY_ROTATION_0, + DISPLAY_ROTATION_180 +} display_rotation_t; + +// Initialize the display, rotating the rendered output based on the define passed in. +// Returns true if the was initialized successfully +bool st7565_init(display_rotation_t rotation); + +// Called at the start of st7565_init, weak function overridable by the user +// rotation - the value passed into st7565_init +// Return new display_rotation_t if you want to override default rotation +display_rotation_t st7565_init_user(display_rotation_t rotation); + +// Clears the display buffer, resets cursor position to 0, and sets the buffer to dirty for rendering +void st7565_clear(void); + +// Renders the dirty chunks of the buffer to display +void st7565_render(void); + +// Moves cursor to character position indicated by column and line, wraps if out of bounds +// Max column denoted by 'st7565_max_chars()' and max lines by 'st7565_max_lines()' functions +void st7565_set_cursor(uint8_t col, uint8_t line); + +// Advances the cursor to the next page, writing ' ' if true +// Wraps to the begining when out of bounds +void st7565_advance_page(bool clearPageRemainder); + +// Moves the cursor forward 1 character length +// Advance page if there is not enough room for the next character +// Wraps to the begining when out of bounds +void st7565_advance_char(void); + +// Writes a single character to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Main handler that writes character data to the display buffer +void st7565_write_char(const char data, bool invert); + +// Writes a string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +void st7565_write(const char *data, bool invert); + +// Writes a string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Advances the cursor to the next page, wiring ' ' to the remainder of the current page +void st7565_write_ln(const char *data, bool invert); + +// Pans the buffer to the right (or left by passing true) by moving contents of the buffer +// Useful for moving the screen in preparation for new drawing +void st7565_pan(bool left); + +// Returns a pointer to the requested start index in the buffer plus remaining +// buffer length as struct +display_buffer_reader_t st7565_read_raw(uint16_t start_index); + +// Writes a string to the buffer at current cursor position +void st7565_write_raw(const char *data, uint16_t size); + +// Writes a single byte into the buffer at the specified index +void st7565_write_raw_byte(const char data, uint16_t index); + +// Sets a specific pixel on or off +// Coordinates start at top-left and go right and down for positive x and y +void st7565_write_pixel(uint8_t x, uint8_t y, bool on); + +// Writes a PROGMEM string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Remapped to call 'void st7565_write(const char *data, bool invert);' on ARM +void st7565_write_P(const char *data, bool invert); + +// Writes a PROGMEM string to the buffer at current cursor position +// Advances the cursor while writing, inverts the pixels if true +// Advances the cursor to the next page, wiring ' ' to the remainder of the current page +// Remapped to call 'void st7565_write_ln(const char *data, bool invert);' on ARM +void st7565_write_ln_P(const char *data, bool invert); + +// Writes a PROGMEM string to the buffer at current cursor position +void st7565_write_raw_P(const char *data, uint16_t size); + +// Can be used to manually turn on the screen if it is off +// Returns true if the screen was on or turns on +bool st7565_on(void); + +// Called when st7565_on() turns on the screen, weak function overridable by the user +// Not called if the screen is already on +void st7565_on_user(void); + +// Can be used to manually turn off the screen if it is on +// Returns true if the screen was off or turns off +bool st7565_off(void); + +// Called when st7565_off() turns off the screen, weak function overridable by the user +// Not called if the screen is already off +void st7565_off_user(void); + +// Returns true if the screen is currently on, false if it is +// not +bool st7565_is_on(void); + +// Basically it's st7565_render, but with timeout management and st7565_task_user calling! +void st7565_task(void); + +// Called at the start of st7565_task, weak function overridable by the user +void st7565_task_user(void); + +// Inverts the display +// Returns true if the screen was or is inverted +bool st7565_invert(bool invert); + +// Returns the maximum number of characters that will fit on a line +uint8_t st7565_max_chars(void); + +// Returns the maximum number of lines that will fit on the display +uint8_t st7565_max_lines(void); +``` |