diff options
author | Ryan <[email protected]> | 2024-06-02 12:42:24 +1000 |
---|---|---|
committer | GitHub <[email protected]> | 2024-06-02 12:42:24 +1000 |
commit | 78a0adfbb4d2c4e12f93f2a62ded0020d406243e (patch) | |
tree | e6e123b36569559233025add654580e090dc0154 /docs/drivers | |
parent | fa6d23235bf429446250cd5212e209d5fbfdbac2 (diff) | |
download | qmk_firmware-78a0adfbb4d2c4e12f93f2a62ded0020d406243e.tar.gz qmk_firmware-78a0adfbb4d2c4e12f93f2a62ded0020d406243e.zip |
[docs] Organize driver & feature docs into subfolders (#23848)
Co-authored-by: Nick Brassel <[email protected]>
Diffstat (limited to 'docs/drivers')
-rw-r--r-- | docs/drivers/adc.md | 173 | ||||
-rw-r--r-- | docs/drivers/apa102.md | 49 | ||||
-rw-r--r-- | docs/drivers/audio.md | 239 | ||||
-rw-r--r-- | docs/drivers/eeprom.md | 180 | ||||
-rw-r--r-- | docs/drivers/flash.md | 26 | ||||
-rw-r--r-- | docs/drivers/gpio.md | 44 | ||||
-rw-r--r-- | docs/drivers/i2c.md | 290 | ||||
-rw-r--r-- | docs/drivers/serial.md | 397 | ||||
-rw-r--r-- | docs/drivers/spi.md | 167 | ||||
-rw-r--r-- | docs/drivers/uart.md | 122 | ||||
-rw-r--r-- | docs/drivers/ws2812.md | 253 |
11 files changed, 1940 insertions, 0 deletions
diff --git a/docs/drivers/adc.md b/docs/drivers/adc.md new file mode 100644 index 0000000000..d89068c2ae --- /dev/null +++ b/docs/drivers/adc.md @@ -0,0 +1,173 @@ +# ADC Driver + +QMK can leverage the Analog-to-Digital Converter (ADC) on supported MCUs to measure voltages on certain pins. This can be useful for implementing things such as battery level indicators for Bluetooth keyboards, or volume controls using a potentiometer, as opposed to a [rotary encoder](../features/encoders). + +This driver currently supports both AVR and a limited selection of ARM devices. The values returned are 10-bit integers (0-1023) mapped between 0V and VCC (usually 5V or 3.3V for AVR, 3.3V only for ARM), however on ARM there is more flexibility in control of operation through `#define`s if you need more precision. + +## Usage + +To use this driver, add the following to your `rules.mk`: + +```make +ANALOG_DRIVER_REQUIRED = yes +``` + +Then place this include at the top of your code: + +```c +#include "analog.h" +``` + +## Channels + +### AVR + +|Channel|AT90USB64/128|ATmega16/32U4|ATmega32A|ATmega328/P| +|-------|-------------|-------------|---------|----------| +|0 |`F0` |`F0` |`A0` |`C0` | +|1 |`F1` |`F1` |`A1` |`C1` | +|2 |`F2` | |`A2` |`C2` | +|3 |`F3` | |`A3` |`C3` | +|4 |`F4` |`F4` |`A4` |`C4` | +|5 |`F5` |`F5` |`A5` |`C5` | +|6 |`F6` |`F6` |`A6` |* | +|7 |`F7` |`F7` |`A7` |* | +|8 | |`D4` | | | +|9 | |`D6` | | | +|10 | |`D7` | | | +|11 | |`B4` | | | +|12 | |`B5` | | | +|13 | |`B6` | | | + +<sup>\* The ATmega328/P possesses two extra ADC channels; however, they are not present on the DIP pinout, and are not shared with GPIO pins. You can use `adc_read()` directly to gain access to these.</sup> + +### ARM + +#### STM32 + +Note that some of these pins are doubled-up on ADCs with the same channel. This is because the pins can be used for either ADC. + +Also note that the F0 and F3 use different numbering schemes. The F0 has a single ADC and the channels are 0-indexed, whereas the F3 has 4 ADCs and the channels are 1-indexed. This is because the F0 uses the `ADCv1` implementation of the ADC, whereas the F3 uses the `ADCv3` implementation. + +|ADC|Channel|STM32F0xx|STM32F1xx|STM32F3xx|STM32F4xx| +|---|-------|---------|---------|---------|---------| +|1 |0 |`A0` |`A0` | |`A0` | +|1 |1 |`A1` |`A1` |`A0` |`A1` | +|1 |2 |`A2` |`A2` |`A1` |`A2` | +|1 |3 |`A3` |`A3` |`A2` |`A3` | +|1 |4 |`A4` |`A4` |`A3` |`A4` | +|1 |5 |`A5` |`A5` |`F4` |`A5` | +|1 |6 |`A6` |`A6` |`C0` |`A6` | +|1 |7 |`A7` |`A7` |`C1` |`A7` | +|1 |8 |`B0` |`B0` |`C2` |`B0` | +|1 |9 |`B1` |`B1` |`C3` |`B1` | +|1 |10 |`C0` |`C0` |`F2` |`C0` | +|1 |11 |`C1` |`C1` | |`C1` | +|1 |12 |`C2` |`C2` | |`C2` | +|1 |13 |`C3` |`C3` | |`C3` | +|1 |14 |`C4` |`C4` | |`C4` | +|1 |15 |`C5` |`C5` | |`C5` | +|1 |16 | | | | | +|2 |0 | |`A0`¹ | |`A0`² | +|2 |1 | |`A1`¹ |`A4` |`A1`² | +|2 |2 | |`A2`¹ |`A5` |`A2`² | +|2 |3 | |`A3`¹ |`A6` |`A3`² | +|2 |4 | |`A4`¹ |`A7` |`A4`² | +|2 |5 | |`A5`¹ |`C4` |`A5`² | +|2 |6 | |`A6`¹ |`C0` |`A6`² | +|2 |7 | |`A7`¹ |`C1` |`A7`² | +|2 |8 | |`B0`¹ |`C2` |`B0`² | +|2 |9 | |`B1`¹ |`C3` |`B1`² | +|2 |10 | |`C0`¹ |`F2` |`C0`² | +|2 |11 | |`C1`¹ |`C5` |`C1`² | +|2 |12 | |`C2`¹ |`B2` |`C2`² | +|2 |13 | |`C3`¹ | |`C3`² | +|2 |14 | |`C4`¹ | |`C4`² | +|2 |15 | |`C5`¹ | |`C5`² | +|2 |16 | | | | | +|3 |0 | |`A0`¹ | |`A0`² | +|3 |1 | |`A1`¹ |`B1` |`A1`² | +|3 |2 | |`A2`¹ |`E9` |`A2`² | +|3 |3 | |`A3`¹ |`E13` |`A3`² | +|3 |4 | |`F6`¹ | |`F6`² | +|3 |5 | |`F7`¹ |`B13` |`F7`² | +|3 |6 | |`F8`¹ |`E8` |`F8`² | +|3 |7 | |`F9`¹ |`D10` |`F9`² | +|3 |8 | |`F10`¹ |`D11` |`F10`² | +|3 |9 | | |`D12` |`F3`² | +|3 |10 | |`C0`¹ |`D13` |`C0`² | +|3 |11 | |`C1`¹ |`D14` |`C1`² | +|3 |12 | |`C2`¹ |`B0` |`C2`² | +|3 |13 | |`C3`¹ |`E7` |`C3`² | +|3 |14 | | |`E10` |`F4`² | +|3 |15 | | |`E11` |`F5`² | +|3 |16 | | |`E12` | | +|4 |1 | | |`E14` | | +|4 |2 | | |`E15` | | +|4 |3 | | |`B12` | | +|4 |4 | | |`B14` | | +|4 |5 | | |`B15` | | +|4 |6 | | |`E8` | | +|4 |7 | | |`D10` | | +|4 |8 | | |`D11` | | +|4 |9 | | |`D12` | | +|4 |10 | | |`D13` | | +|4 |11 | | |`D14` | | +|4 |12 | | |`D8` | | +|4 |13 | | |`D9` | | +|4 |14 | | | | | +|4 |15 | | | | | +|4 |16 | | | | | + +<sup>¹ As of ChibiOS 20.3.4, the ADC driver for STM32F1xx devices supports only ADC1, therefore any configurations involving ADC2 or ADC3 cannot actually be used. In particular, pins `F6`…`F10`, which are present at least on some STM32F103x[C-G] devices, cannot be used as ADC inputs because of this driver limitation.</sup> + +<sup>² Not all STM32F4xx devices have ADC2 and/or ADC3, therefore some configurations shown in this table may be unavailable; in particular, pins `F4`…`F10` cannot be used as ADC inputs on devices which do not have ADC3. Check the device datasheet to confirm which pin functions are supported.</sup> + +#### RP2040 + +RP2040 has only a single ADC (`ADCD1` in ChibiOS); in the QMK API the index for that ADC is 0. + +|Channel|Pin | +|-------|-------------------| +|0 |`GP26` | +|1 |`GP27` | +|2 |`GP28` | +|3 |`GP29` | +|4 |Temperature sensor*| + + +<sup>* The temperature sensor is disabled by default and needs to be enabled by the RP2040-specific function: `adcRPEnableTS(&ADCD1)`. The ADC must be initialized before calling that function; an easy way to ensure that is to perform a dummy conversion.</sup> + +## Functions + +### AVR + +|Function |Description | +|----------------------------|-------------------------------------------------------------------------------------------------------------------| +|`analogReference(mode)` |Sets the analog voltage reference source. Must be one of `ADC_REF_EXTERNAL`, `ADC_REF_POWER` or `ADC_REF_INTERNAL`.| +|`analogReadPin(pin)` |Reads the value from the specified pin, eg. `F6` for ADC6 on the ATmega32U4. | +|`pinToMux(pin)` |Translates a given pin to a mux value. If an unsupported pin is given, returns the mux value for "0V (GND)". | +|`adc_read(mux)` |Reads the value from the ADC according to the specified mux. See your MCU's datasheet for more information. | + +### ARM + +|Function |Description | +|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|`analogReadPin(pin)` |Reads the value from the specified pin, eg. `A0` for channel 0 on the STM32F0 and ADC1 channel 1 on the STM32F3. Note that if a pin can be used for multiple ADCs, it will pick the lower numbered ADC for this function. eg. `C0` will be channel 6 of ADC 1 when it could be used for ADC 2 as well.| +|`analogReadPinAdc(pin, adc)`|Reads the value from the specified pin and ADC, eg. `C0, 1` will read from channel 6, ADC 2 instead of ADC 1. Note that the ADCs are 0-indexed for this function. | +|`pinToMux(pin)` |Translates a given pin to a channel and ADC combination. If an unsupported pin is given, returns the mux value for "0V (GND)". | +|`adc_read(mux)` |Reads the value from the ADC according to the specified pin and ADC combination. See your MCU's datasheet for more information. | + +## Configuration + +## ARM + +The ARM implementation of the ADC has a few additional options that you can override in your own keyboards and keymaps to change how it operates. Please consult the corresponding `hal_adc_lld.h` in ChibiOS for your specific microcontroller for further documentation on your available options. + +|`#define` |Type |Default |Description | +|---------------------|------|----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|`ADC_CIRCULAR_BUFFER`|`bool`|`false` |If `true`, then the implementation will use a circular buffer. | +|`ADC_NUM_CHANNELS` |`int` |`1` |Sets the number of channels that will be scanned as part of an ADC operation. The current implementation only supports `1`. | +|`ADC_BUFFER_DEPTH` |`int` |`2` |Sets the depth of each result. Since we are only getting a 10-bit result by default, we set this to 2 bytes so we can contain our one value. This could be set to 1 if you opt for an 8-bit or lower result.| +|`ADC_SAMPLING_RATE` |`int` |`ADC_SMPR_SMP_1P5` |Sets the sampling rate of the ADC. By default, it is set to the fastest setting. | +|`ADC_RESOLUTION` |`int` |`ADC_CFGR1_RES_10BIT` or `ADC_CFGR_RES_10BITS`|The resolution of your result. We choose 10 bit by default, but you can opt for 12, 10, 8, or 6 bit. Different MCUs use slightly different names for the resolution constants. | diff --git a/docs/drivers/apa102.md b/docs/drivers/apa102.md new file mode 100644 index 0000000000..88868a73b5 --- /dev/null +++ b/docs/drivers/apa102.md @@ -0,0 +1,49 @@ +# APA102 Driver {#apa102-driver} + +This driver provides support for APA102 addressable RGB LEDs. They are similar to the [WS2812](ws2812) LEDs, but have increased data and refresh rates. + +## Usage {#usage} + +In most cases, the APA102 driver code is automatically included if you are using either the [RGBLight](../features/rgblight) or [RGB Matrix](../features/rgb_matrix) feature with the `apa102` driver set, and you would use those APIs instead. + +However, if you need to use the driver standalone, add the following to your `rules.mk`: + +```make +APA102_DRIVER_REQUIRED = yes +``` + +You can then call the APA102 API by including `apa102.h` in your code. + +## Basic Configuration {#basic-configuration} + +Add the following to your `config.h`: + +|Define |Default |Description | +|---------------------------|-------------|------------------------------------------------------------------| +|`APA102_DI_PIN` |*Not defined*|The GPIO pin connected to the DI pin of the first LED in the chain| +|`APA102_CI_PIN` |*Not defined*|The GPIO pin connected to the CI pin of the first LED in the chain| +|`APA102_DEFAULT_BRIGHTNESS`|`31` |The default global brightness level of the LEDs, from 0 to 31 | + +## API {#api} + +### `void apa102_setleds(rgb_led_t *start_led, uint16_t num_leds)` + +Send RGB data to the APA102 LED chain. + +#### Arguments {#api-apa102-setleds-arguments} + + - `rgb_led_t *start_led` + A pointer to the LED array. + - `uint16_t num_leds` + The length of the LED array. + +--- + +### `void apa102_set_brightness(uint8_t brightness)` + +Set the global brightness. + +#### Arguments {#api-apa102-set-brightness-arguments} + + - `uint8_t brightness` + The brightness level to set, from 0 to 31. diff --git a/docs/drivers/audio.md b/docs/drivers/audio.md new file mode 100644 index 0000000000..c764c97369 --- /dev/null +++ b/docs/drivers/audio.md @@ -0,0 +1,239 @@ +# Audio Driver {#audio-driver} + +The [Audio feature](../features/audio) breaks the hardware specifics out into separate, exchangeable driver units, with a common interface to the audio-"core" - which itself handles playing songs and notes while tracking their progress in an internal state, initializing/starting/stopping the driver as needed. + +Not all MCUs support every available driver, either the platform-support is not there (yet?) or the MCU simply does not have the required hardware peripheral. + + +## AVR {#avr} + +Boards built around an Atmega32U4 can use two sets of PWM capable pins, each driving a separate speaker. +The possible configurations are: + +| | Timer3 | Timer1 | +|--------------|-------------|--------------| +| one speaker | C4,C5 or C6 | | +| one speaker | | B4, B5 or B7 | +| two speakers | C4,C5 or C6 | B4, B5 or B7 | + +Currently there is only one/default driver for AVR based boards, which is automatically configured to: + +```make +AUDIO_DRIVER = pwm_hardware +``` + + +## ARM {#arm} + +For Arm based boards, QMK depends on ChibiOS - hence any MCU supported by the later is likely usable, as long as certain hardware peripherals are available. + +Supported wiring configurations, with their ChibiOS/MCU peripheral requirement are listed below; +piezo speakers are marked with :one: for the first/primary and :two: for the secondary. + + | driver | GPTD6<br>Tim6 | GPTD7<br>Tim7 | GPTD8<br>Tim8 | PWMD1<sup>1</sup><br>Tim1_Ch1 | + |--------------|------------------------------------------|------------------------|---------------|-------------------------------| + | dac_basic | A4+DACD1 = :one: | A5+DACD2 = :one: | state | | + | | A4+DACD1 = :one: + Gnd | A5+DACD2 = :two: + Gnd | state | | + | | A4+DACD1 = :two: + Gnd | A5+DACD2 = :one: + Gnd | state | | + | | A4+DACD1 = :one: + Gnd | | state | | + | | | A5+DACD2 = :one: + Gnd | state | | + | dac_additive | A4+DACD1 = :one: + Gnd | | | | + | | A5+DACD2 = :one: + Gnd | | | | + | | A4+DACD1 + A5+DACD2 = :one: <sup>2</sup> | | | | + | pwm_software | state-update | | | any = :one: | + | pwm hardware | state-update | | | A8 = :one: <sup>3</sup> | + + +<sup>1</sup>: the routing and alternate functions for PWM differ sometimes between STM32 MCUs, if in doubt consult the data-sheet +<sup>2</sup>: one piezo connected to A4 and A5, with AUDIO_PIN_ALT_AS_NEGATIVE set +<sup>3</sup>: TIM1_CH1 = A8 on STM32F103C8, other combinations are possible, see Data-sheet. configured with: AUDIO_PWM_DRIVER and AUDIO_PWM_CHANNEL + + + +### DAC basic {#dac-basic} + +The default driver for ARM boards, in absence of an overriding configuration. +This driver needs one Timer per enabled/used DAC channel, to trigger conversion; and a third timer to trigger state updates with the audio-core. + +Additionally, in the board config, you'll want to make changes to enable the DACs, GPT for Timers 6, 7 and 8: + +```c +//halconf.h: +#define HAL_USE_DAC TRUE +#define HAL_USE_GPT TRUE +#include_next <halconf.h> +``` + +```c +// mcuconf.h: +#include_next <mcuconf.h> +#undef STM32_DAC_USE_DAC1_CH1 +#define STM32_DAC_USE_DAC1_CH1 TRUE +#undef STM32_DAC_USE_DAC1_CH2 +#define STM32_DAC_USE_DAC1_CH2 TRUE +#undef STM32_GPT_USE_TIM6 +#define STM32_GPT_USE_TIM6 TRUE +#undef STM32_GPT_USE_TIM7 +#define STM32_GPT_USE_TIM7 TRUE +#undef STM32_GPT_USE_TIM8 +#define STM32_GPT_USE_TIM8 TRUE +``` + +::: tip +Note: DAC1 (A4) uses TIM6, DAC2 (A5) uses TIM7, and the audio state timer uses TIM8 (configurable). +::: + +You can also change the timer used for the overall audio state by defining the driver. For instance: + +```c +#define AUDIO_STATE_TIMER GPTD9 +``` + +### DAC additive {#dac-additive} + +only needs one timer (GPTD6, Tim6) to trigger the DAC unit to do a conversion; the audio state updates are in turn triggered during the DAC callback. + +Additionally, in the board config, you'll want to make changes to enable the DACs, GPT for Timer 6: + +```c +//halconf.h: +#define HAL_USE_DAC TRUE +#define HAL_USE_GPT TRUE +#include_next <halconf.h> +``` + +```c +// mcuconf.h: +#include_next <mcuconf.h> +#undef STM32_DAC_USE_DAC1_CH1 +#define STM32_DAC_USE_DAC1_CH1 TRUE +#undef STM32_DAC_USE_DAC1_CH2 +#define STM32_DAC_USE_DAC1_CH2 TRUE +#undef STM32_GPT_USE_TIM6 +#define STM32_GPT_USE_TIM6 TRUE +``` + +### DAC Config + +| Define | Defaults | Description | +| -------------------------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `AUDIO_DAC_SAMPLE_MAX` | `4095U` | Highest value allowed. Lower value means lower volume. And 4095U is the upper limit, since this is limited to a 12 bit value. Only effects non-pregenerated samples. | +| `AUDIO_DAC_OFF_VALUE` | `AUDIO_DAC_SAMPLE_MAX / 2` | The value of the DAC when not playing anything. Some setups may require a high (`AUDIO_DAC_SAMPLE_MAX`) or low (`0`) value here. | +| `AUDIO_MAX_SIMULTANEOUS_TONES` | __see next table__ | The number of tones that can be played simultaneously. A value that is too high may freeze the controller or glitch out when too many tones are being played. | +| `AUDIO_DAC_SAMPLE_RATE` | __see next table__ | Effective bit rate of the DAC (in hertz), higher limits simultaneous tones, and lower sacrifices quality. | +| `AUDIO_DAC_BUFFER_SIZE` | __see next table__ | Number of samples generated every refill. Too few may cause excessive CPU load; too many may cause freezes, RAM or flash exhaustion or lags during matrix scanning. | + +There are a number of predefined quality settings that you can use, with "sane minimum" being the default. You can use custom values by simply defining the sample rate, number of simultaneous tones and buffer size, instead of using one of the listed presets. + +| Define | Sample Rate | Simultaneous tones | Buffer size | +| --------------------------------- | ----------- | ------------------- | ----------- | +| `AUDIO_DAC_QUALITY_VERY_LOW` | `11025U` | `8` | `64U` | +| `AUDIO_DAC_QUALITY_LOW` | `22050U` | `4` | `128U` | +| `AUDIO_DAC_QUALITY_HIGH` | `44100U` | `2` | `256U` | +| `AUDIO_DAC_QUALITY_VERY_HIGH` | `88200U` | `1` | `256U` | +| `AUDIO_DAC_QUALITY_SANE_MINIMUM` | `16384U` | `8` | `64U` | + +#### Notes on buffer size {#buffer-size} + +By default, the buffer size attempts to keep to these constraints: + +* The interval between buffer refills can't be too short, since the microcontroller would then only be servicing buffer refills and would freeze up. +* On the additive driver, the interval between buffer refills can't be too long, since matrix scanning would suffer lengthy pauses every so often, which would delay key presses or releases or lose some short taps altogether. +* The interval between buffer refills is kept to a minimum, which allows notes to stop as soon as possible after they should. +* For greater compatibility, the buffer size should be a power of 2. +* The buffer size being too large causes resource exhaustion leading to build failures or freezing at runtime: RAM usage (on the additive driver) or flash usage (on the basic driver). + +You can lower the buffer size if you need a bit more space in your firmware, or raise it if your keyboard freezes up. + + +```c + /* zero crossing (or approach, whereas zero == DAC_OFF_VALUE, which can be configured to anything from 0 to DAC_SAMPLE_MAX) + * ============================*=*========================== AUDIO_DAC_SAMPLE_MAX + * * * + * * * + * --------------------------------------------------------- + * * * } AUDIO_DAC_SAMPLE_MAX/100 + * --------------------------------------------------------- AUDIO_DAC_OFF_VALUE + * * * } AUDIO_DAC_SAMPLE_MAX/100 + * --------------------------------------------------------- + * * + * * * + * * * + * =====*=*================================================= 0x0 + */ +``` + + +### PWM hardware {#pwm-hardware} + +This driver uses the ChibiOS-PWM system to produce a square-wave on specific output pins that are connected to the PWM hardware. +The hardware directly toggles the pin via its alternate function. See your MCU's data-sheet for which pin can be driven by what timer - looking for TIMx_CHy and the corresponding alternate function. + +A configuration example for the STM32F103C8 would be: +```c +//halconf.h: +#define HAL_USE_PWM TRUE +#define HAL_USE_PAL TRUE +#include_next <halconf.h> +``` + +```c +// mcuconf.h: +#include_next <mcuconf.h> +#undef STM32_PWM_USE_TIM1 +#define STM32_PWM_USE_TIM1 TRUE +``` + +If we now target pin A8, looking through the data-sheet of the STM32F103C8, for the timers and alternate functions +- TIM1_CH1 = PA8 <- alternate0 +- TIM1_CH2 = PA9 +- TIM1_CH3 = PA10 +- TIM1_CH4 = PA11 + +with all this information, the configuration would contain these lines: +```c +//config.h: +#define AUDIO_PIN A8 +#define AUDIO_PWM_DRIVER PWMD1 +#define AUDIO_PWM_CHANNEL 1 +``` + +ChibiOS uses GPIOv1 for the F103, which only knows of one alternate function. +On 'larger' STM32s, GPIOv2 or GPIOv3 are used; with them it is also necessary to configure `AUDIO_PWM_PAL_MODE` to the correct alternate function for the selected pin, timer and timer-channel. + +You can also use the Complementary output (`TIMx_CHyN`) for PWM on supported controllers. To enable this functionality, you will need to make the following changes: +```c +// config.h: +#define AUDIO_PWM_COMPLEMENTARY_OUTPUT +``` + +### PWM software {#pwm-software} + +This driver uses the PWM callbacks from PWMD1 with TIM1_CH1 to toggle the selected AUDIO_PIN in software. +During the same callback, with AUDIO_PIN_ALT_AS_NEGATIVE set, the AUDIO_PIN_ALT is toggled inversely to AUDIO_PIN. This is useful for setups that drive a piezo from two pins (instead of one and Gnd). + +You can also change the timer used for software PWM by defining the driver. For instance: + +```c +#define AUDIO_STATE_TIMER GPTD8 +``` + + +### Testing Notes {#testing-notes} + +While not an exhaustive list, the following table provides the scenarios that have been partially validated: + +| | DAC basic | DAC additive | PWM hardware | PWM software | +| ------------------------ | ------------------ | ------------------ | ------------------ | ------------------ | +| Atmega32U4 | :o: | :o: | :heavy_check_mark: | :o: | +| RP2040 | :x: | :x: | :heavy_check_mark: | ? | +| STM32F103C8 (bluepill) | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | +| STM32F303CCT6 (proton-c) | :heavy_check_mark: | :heavy_check_mark: | ? | :heavy_check_mark: | +| STM32F405VG | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| L0xx | :x: (no Tim8) | ? | ? | ? | + +:heavy_check_mark: : works and was tested +:o: : does not apply +:x: : not supported by MCU + +*Other supported ChibiOS boards and/or pins may function, it will be highly chip and configuration dependent.* diff --git a/docs/drivers/eeprom.md b/docs/drivers/eeprom.md new file mode 100644 index 0000000000..82630c501d --- /dev/null +++ b/docs/drivers/eeprom.md @@ -0,0 +1,180 @@ +# EEPROM Driver Configuration {#eeprom-driver-configuration} + +The EEPROM driver can be swapped out depending on the needs of the keyboard, or whether extra hardware is present. + +Selecting the EEPROM driver is done in your keyboard's `rules.mk`: + +Driver | Description +-----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +`EEPROM_DRIVER = vendor` (default) | Uses the on-chip driver provided by the chip manufacturer. For AVR, this is provided by avr-libc. This is supported on ARM for a subset of chips -- STM32F3xx, STM32F1xx, and STM32F072xB will be emulated by writing to flash. STM32L0xx and STM32L1xx will use the onboard dedicated true EEPROM. Other chips will generally act as "transient" below. +`EEPROM_DRIVER = i2c` | Supports writing to I2C-based 24xx EEPROM chips. See the driver section below. +`EEPROM_DRIVER = spi` | Supports writing to SPI-based 25xx EEPROM chips. See the driver section below. +`EEPROM_DRIVER = transient` | Fake EEPROM driver -- supports reading/writing to RAM, and will be discarded when power is lost. +`EEPROM_DRIVER = wear_leveling` | Frontend driver for the wear_leveling system, allowing for EEPROM emulation on top of flash -- both in-MCU and external SPI NOR flash. + +## Vendor Driver Configuration {#vendor-eeprom-driver-configuration} + +#### STM32 L0/L1 Configuration {#stm32l0l1-eeprom-driver-configuration} + +::: warning +Resetting EEPROM using an STM32L0/L1 device takes up to 1 second for every 1kB of internal EEPROM used. +::: + +`config.h` override | Description | Default Value +------------------------------------|--------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------- +`#define STM32_ONBOARD_EEPROM_SIZE` | The size of the EEPROM to use, in bytes. Erase times can be high, so it's configurable here, if not using the default value. | Minimum required to cover base _eeconfig_ data, or `1024` if VIA is enabled. + +## I2C Driver Configuration {#i2c-eeprom-driver-configuration} + +Currently QMK supports 24xx-series chips over I2C. As such, requires a working i2c_master driver configuration. You can override the driver configuration via your config.h: + +`config.h` override | Description | Default Value +------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------ +`#define EXTERNAL_EEPROM_I2C_BASE_ADDRESS` | Base I2C address for the EEPROM -- shifted left by 1 as per i2c_master requirements | 0b10100000 +`#define EXTERNAL_EEPROM_I2C_ADDRESS(addr)` | Calculated I2C address for the EEPROM | `(EXTERNAL_EEPROM_I2C_BASE_ADDRESS)` +`#define EXTERNAL_EEPROM_BYTE_COUNT` | Total size of the EEPROM in bytes | 8192 +`#define EXTERNAL_EEPROM_PAGE_SIZE` | Page size of the EEPROM in bytes, as specified in the datasheet | 32 +`#define EXTERNAL_EEPROM_ADDRESS_SIZE` | The number of bytes to transmit for the memory location within the EEPROM | 2 +`#define EXTERNAL_EEPROM_WRITE_TIME` | Write cycle time of the EEPROM, as specified in the datasheet | 5 +`#define EXTERNAL_EEPROM_WP_PIN` | If defined the WP pin will be toggled appropriately when writing to the EEPROM. | _none_ + +Some I2C EEPROM manufacturers explicitly recommend against hardcoding the WP pin to ground. This is in order to protect the eeprom memory content during power-up/power-down/brown-out conditions at low voltage where the eeprom is still operational, but the i2c master output might be unpredictable. If a WP pin is configured, then having an external pull-up on the WP pin is recommended. + +Default values and extended descriptions can be found in `drivers/eeprom/eeprom_i2c.h`. + +Alternatively, there are pre-defined hardware configurations for available chips/modules: + +Module | Equivalent `#define` | Source +-----------------|---------------------------------|------------------------------------------ +CAT24C512 EEPROM | `#define EEPROM_I2C_CAT24C512` | <https://www.sparkfun.com/products/14764> +RM24C512C EEPROM | `#define EEPROM_I2C_RM24C512C` | <https://www.sparkfun.com/products/14764> +24LC32A EEPROM | `#define EEPROM_I2C_24LC32A` | <https://www.microchip.com/en-us/product/24LC32A> +24LC64 EEPROM | `#define EEPROM_I2C_24LC64` | <https://www.microchip.com/en-us/product/24LC64> +24LC128 EEPROM | `#define EEPROM_I2C_24LC128` | <https://www.microchip.com/en-us/product/24LC128> +24LC256 EEPROM | `#define EEPROM_I2C_24LC256` | <https://www.sparkfun.com/products/525> +MB85RC256V FRAM | `#define EEPROM_I2C_MB85RC256V` | <https://www.adafruit.com/product/1895> + +::: tip +If you find that the EEPROM is not cooperating, ensure you've correctly shifted up your EEPROM address by 1. For example, the datasheet might state the address as `0b01010000` -- the correct value of `EXTERNAL_EEPROM_I2C_BASE_ADDRESS` needs to be `0b10100000`. +::: + +## SPI Driver Configuration {#spi-eeprom-driver-configuration} + +Currently QMK supports 25xx-series chips over SPI. As such, requires a working spi_master driver configuration. You can override the driver configuration via your config.h: + +`config.h` override | Default Value | Description +-----------------------------------------------|---------------|------------------------------------------------------------------------------------- +`#define EXTERNAL_EEPROM_SPI_SLAVE_SELECT_PIN` | _none_ | SPI Slave select pin in order to inform that the EEPROM is currently being addressed +`#define EXTERNAL_EEPROM_SPI_CLOCK_DIVISOR` | `64` | Clock divisor used to divide the peripheral clock to derive the SPI frequency +`#define EXTERNAL_EEPROM_BYTE_COUNT` | `8192` | Total size of the EEPROM in bytes +`#define EXTERNAL_EEPROM_PAGE_SIZE` | `32` | Page size of the EEPROM in bytes, as specified in the datasheet +`#define EXTERNAL_EEPROM_ADDRESS_SIZE` | `2` | The number of bytes to transmit for the memory location within the EEPROM + +Default values and extended descriptions can be found in `drivers/eeprom/eeprom_spi.h`. + +Alternatively, there are pre-defined hardware configurations for available chips/modules: + +Module | Equivalent `#define` | Source +-----------------|---------------------------------|------------------------------------------ +MB85RS64V FRAM | `define EEPROM_SPI_MB85RS64V` | <https://www.adafruit.com/product/1897> + +::: warning +There's no way to determine if there is an SPI EEPROM actually responding. Generally, this will result in reads of nothing but zero. +::: + +## Transient Driver configuration {#transient-eeprom-driver-configuration} + +The only configurable item for the transient EEPROM driver is its size: + +`config.h` override | Description | Default Value +------------------------------- | ----------------------------------------- | ------------- +`#define TRANSIENT_EEPROM_SIZE` | Total size of the EEPROM storage in bytes | 64 + +Default values and extended descriptions can be found in `drivers/eeprom/eeprom_transient.h`. + +## Wear-leveling Driver Configuration {#wear_leveling-eeprom-driver-configuration} + +The wear-leveling driver uses an algorithm to minimise the number of erase cycles on the underlying MCU flash memory. + +There is no specific configuration for this driver, but the wear-leveling system used by this driver may need configuration. See the [wear-leveling configuration](#wear_leveling-configuration) section for more information. + +# Wear-leveling Configuration {#wear_leveling-configuration} + +The wear-leveling driver has a few possible _backing stores_ that may be used by adding to your keyboard's `rules.mk` file: + +Driver | Description +----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +`WEAR_LEVELING_DRIVER = embedded_flash` | This driver is used for emulating EEPROM by writing to embedded flash on the MCU. +`WEAR_LEVELING_DRIVER = spi_flash` | This driver is used to address external SPI NOR Flash peripherals. +`WEAR_LEVELING_DRIVER = rp2040_flash` | This driver is used to write to the same storage the RP2040 executes code from. +`WEAR_LEVELING_DRIVER = legacy` | This driver is the "legacy" emulated EEPROM provided in historical revisions of QMK. Currently used for STM32F0xx and STM32F4x1, but slated for deprecation and removal once `embedded_flash` support for those MCU families is complete. + +::: warning +All wear-leveling drivers require an amount of RAM equivalent to the selected logical EEPROM size. Increasing the size to 32kB of EEPROM requires 32kB of RAM, which a significant number of MCUs simply do not have. +::: + +## Wear-leveling Embedded Flash Driver Configuration {#wear_leveling-efl-driver-configuration} + +This driver performs writes to the embedded flash storage embedded in the MCU. In most circumstances, the last few of sectors of flash are used in order to minimise the likelihood of collision with program code. + +Configurable options in your keyboard's `config.h`: + +`config.h` override | Default | Description +-----------------------------------------|-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +`#define WEAR_LEVELING_EFL_FIRST_SECTOR` | _unset_ | The first sector on the MCU to use. By default this is not defined and calculated at runtime based on the MCU. However, different flash sizes on MCUs may require custom configuration. +`#define WEAR_LEVELING_EFL_FLASH_SIZE` | _unset_ | Allows overriding the flash size available for use for wear-leveling. Under normal circumstances this is automatically calculated and should not need to be overridden. Specifying a size larger than the amount actually available in flash will usually prevent the MCU from booting. +`#define WEAR_LEVELING_LOGICAL_SIZE` | `(backing_size/2)` | Number of bytes "exposed" to the rest of QMK and denotes the size of the usable EEPROM. +`#define WEAR_LEVELING_BACKING_SIZE` | `2048` | Number of bytes used by the wear-leveling algorithm for its underlying storage, and needs to be a multiple of the logical size. +`#define BACKING_STORE_WRITE_SIZE` | _automatic_ | The byte width of the underlying write used on the MCU, and is usually automatically determined from the selected MCU family. If an error occurs in the auto-detection, you'll need to consult the MCU's datasheet and determine this value, specifying it directly. + +::: warning +If your MCU does not boot after swapping to the EFL wear-leveling driver, it's likely that the flash size is incorrectly detected, usually as an MCU with larger flash and may require overriding. +::: + +## Wear-leveling SPI Flash Driver Configuration {#wear_leveling-flash_spi-driver-configuration} + +This driver performs writes to an external SPI NOR Flash peripheral. It also requires a working configuration for the SPI NOR Flash peripheral -- see the [flash driver](flash) documentation for more information. + +Configurable options in your keyboard's `config.h`: + +`config.h` override | Default | Description +----------------------------------------------------|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------- +`#define WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_COUNT` | `1` | Number of blocks in the external flash used by the wear-leveling algorithm. +`#define WEAR_LEVELING_EXTERNAL_FLASH_BLOCK_OFFSET` | `0` | The index first block in the external flash used by the wear-leveling algorithm. +`#define WEAR_LEVELING_LOGICAL_SIZE` | `((block_count*block_size)/2)` | Number of bytes "exposed" to the rest of QMK and denotes the size of the usable EEPROM. Result must be <= 64kB. +`#define WEAR_LEVELING_BACKING_SIZE` | `(block_count*block_size)` | Number of bytes used by the wear-leveling algorithm for its underlying storage, and needs to be a multiple of the logical size. +`#define BACKING_STORE_WRITE_SIZE` | `8` | The write width used whenever a write is performed on the external flash peripheral. + +::: warning +There is currently a limit of 64kB for the EEPROM subsystem within QMK, so using a larger flash is not going to be beneficial as the logical size cannot be increased beyond 65536. The backing size may be increased to a larger value, but erase timing may suffer as a result. +::: + +## Wear-leveling RP2040 Driver Configuration {#wear_leveling-rp2040-driver-configuration} + +This driver performs writes to the same underlying storage that the RP2040 executes its code. + +Configurable options in your keyboard's `config.h`: + +`config.h` override | Default | Description +------------------------------------------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------- +`#define WEAR_LEVELING_RP2040_FLASH_SIZE` | `PICO_FLASH_SIZE_BYTES` | Number of bytes of flash on the board. +`#define WEAR_LEVELING_RP2040_FLASH_BASE` | `(flash_size-sector_size)` | The byte-wise location that the backing storage should be located. +`#define WEAR_LEVELING_LOGICAL_SIZE` | `(backing_size/2)` | Number of bytes "exposed" to the rest of QMK and denotes the size of the usable EEPROM. +`#define WEAR_LEVELING_BACKING_SIZE` | `8192` | Number of bytes used by the wear-leveling algorithm for its underlying storage, and needs to be a multiple of the logical size as well as the sector size. +`#define BACKING_STORE_WRITE_SIZE` | `2` | The write width used whenever a write is performed on the external flash peripheral. + +## Wear-leveling Legacy EEPROM Emulation Driver Configuration {#wear_leveling-legacy-driver-configuration} + +This driver performs writes to the embedded flash storage embedded in the MCU much like the normal Embedded Flash Driver, and is only for use with STM32F0xx and STM32F4x1 devices. This flash implementation is still currently provided as the EFL driver is currently non-functional for the previously mentioned families. + +By default, `1024` bytes of emulated EEPROM is provided: + +MCU | EEPROM Provided | Flash Used +----------|-----------------|-------------- +STM32F042 | `1024` bytes | `2048` bytes +STM32F070 | `1024` bytes | `2048` bytes +STM32F072 | `1024` bytes | `2048` bytes +STM32F401 | `1024` bytes | `16384` bytes +STM32F411 | `1024` bytes | `16384` bytes + +Under normal circumstances configuration of this driver requires intimate knowledge of the MCU's flash structure -- reconfiguration is at your own risk and will require referring to the code. diff --git a/docs/drivers/flash.md b/docs/drivers/flash.md new file mode 100644 index 0000000000..4160721350 --- /dev/null +++ b/docs/drivers/flash.md @@ -0,0 +1,26 @@ +# FLASH Driver Configuration {#flash-driver-configuration} + +The FLASH driver can be swapped out depending on the needs of the keyboard, or whether extra hardware is present. + +Driver | Description +-----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +`FLASH_DRIVER = spi` | Supports writing to almost all NOR Flash chips. See the driver section below. + + +## SPI FLASH Driver Configuration {#spi-flash-driver-configuration} + +Currently QMK supports almost all NOR Flash chips over SPI. As such, requires a working spi_master driver configuration. You can override the driver configuration via your config.h: + +`config.h` override | Description | Default Value +-----------------------------------------------|--------------------------------------------------------------------------------------|----------------- +`#define EXTERNAL_FLASH_SPI_SLAVE_SELECT_PIN` | SPI Slave select pin in order to inform that the FLASH is currently being addressed | _none_ +`#define EXTERNAL_FLASH_SPI_CLOCK_DIVISOR` | Clock divisor used to divide the peripheral clock to derive the SPI frequency | `8` +`#define EXTERNAL_FLASH_PAGE_SIZE` | The Page size of the FLASH in bytes, as specified in the datasheet | `256` +`#define EXTERNAL_FLASH_SECTOR_SIZE` | The sector size of the FLASH in bytes, as specified in the datasheet | `(4 * 1024)` +`#define EXTERNAL_FLASH_BLOCK_SIZE` | The block size of the FLASH in bytes, as specified in the datasheet | `(64 * 1024)` +`#define EXTERNAL_FLASH_SIZE` | The total size of the FLASH in bytes, as specified in the datasheet | `(512 * 1024)` +`#define EXTERNAL_FLASH_ADDRESS_SIZE` | The Flash address size in bytes, as specified in datasheet | `3` + +::: warning +All the above default configurations are based on MX25L4006E NOR Flash. +::: diff --git a/docs/drivers/gpio.md b/docs/drivers/gpio.md new file mode 100644 index 0000000000..9ce4f2aa20 --- /dev/null +++ b/docs/drivers/gpio.md @@ -0,0 +1,44 @@ +# GPIO Control {#gpio-control} + +QMK has a GPIO control abstraction layer which is microcontroller agnostic. This is done to allow easy access to pin control across different platforms. + +## Macros {#macros} + +The following macros provide basic control of GPIOs and are found in `platforms/<platform>/gpio.h`. + +|Macro |Description | +|-------------------------------------|---------------------------------------------------------------------| +|`gpio_set_pin_input(pin)` |Set pin as input with high impedance (High-Z) | +|`gpio_set_pin_input_high(pin)` |Set pin as input with builtin pull-up resistor | +|`gpio_set_pin_input_low(pin)` |Set pin as input with builtin pull-down resistor (unavailable on AVR)| +|`gpio_set_pin_output(pin)` |Set pin as output (alias of `gpio_set_pin_output_push_pull`) | +|`gpio_set_pin_output_push_pull(pin)` |Set pin as output, push/pull mode | +|`gpio_set_pin_output_open_drain(pin)`|Set pin as output, open-drain mode (unavailable on AVR and ATSAM) | +|`gpio_write_pin_high(pin)` |Set pin level as high, assuming it is an output | +|`gpio_write_pin_low(pin)` |Set pin level as low, assuming it is an output | +|`gpio_write_pin(pin, level)` |Set pin level, assuming it is an output | +|`gpio_read_pin(pin)` |Returns the level of the pin | +|`gpio_toggle_pin(pin)` |Invert pin level, assuming it is an output | + +## Advanced Settings {#advanced-settings} + +Each microcontroller can have multiple advanced settings regarding its GPIO. This abstraction layer does not limit the use of architecture-specific functions. Advanced users should consult the datasheet of their desired device. For AVR, the standard `avr/io.h` library is used; for STM32, the ChibiOS [PAL library](https://chibios.sourceforge.net/docs3/hal/group___p_a_l.html) is used. + +## Atomic Operation {#atomic-operation} + +The above functions are not always guaranteed to work atomically. Therefore, if you want to prevent interruptions in the middle of operations when using multiple combinations of the above functions, use the following `ATOMIC_BLOCK_FORCEON` macro. + +eg. +```c +void some_function(void) { + // some process + ATOMIC_BLOCK_FORCEON { + // Atomic Processing + } + // some process +} +``` + +`ATOMIC_BLOCK_FORCEON` forces interrupts to be disabled before the block is executed, without regard to whether they are enabled or disabled. Then, after the block is executed, the interrupt is enabled. + +Note that `ATOMIC_BLOCK_FORCEON` can therefore be used if you know that interrupts are enabled before the execution of the block, or if you know that it is OK to enable interrupts at the completion of the block. diff --git a/docs/drivers/i2c.md b/docs/drivers/i2c.md new file mode 100644 index 0000000000..10949ed59e --- /dev/null +++ b/docs/drivers/i2c.md @@ -0,0 +1,290 @@ +# I2C Master Driver {#i2c-master-driver} + +The I2C Master drivers used in QMK have a set of common functions to allow portability between MCUs. + +## Usage {#usage} + +In most cases, the I2C Master driver code is automatically included if you are using a feature or driver which requires it, such as [OLED](../features/oled_driver). + +However, if you need to use the driver standalone, add the following to your `rules.mk`: + +```make +I2C_DRIVER_REQUIRED = yes +``` + +You can then call the I2C API by including `i2c_master.h` in your code. + +## I2C Addressing {#note-on-i2c-addresses} + +All of the addresses expected by this driver should be pushed to the upper 7 bits of the address byte. Setting +the lower bit (indicating read/write) will be done by the respective functions. Almost all I2C addresses listed +on datasheets and the internet will be represented as 7 bits occupying the lower 7 bits and will need to be +shifted to the left (more significant) by one bit. This is easy to do via the bitwise shift operator `<< 1`. + +You can either do this on each call to the functions below, or once in your definition of the address. For example, if your device has an address of `0x18`: + +```c +#define MY_I2C_ADDRESS (0x18 << 1) +``` + +See https://www.robot-electronics.co.uk/i2c-tutorial for more information about I2C addressing and other technical details. + +## AVR Configuration {#avr-configuration} + +The following defines can be used to configure the I2C master driver: + +|`config.h` Override|Description |Default | +|-------------------|---------------------|--------| +|`F_SCL` |Clock frequency in Hz|`400000`| + +No further setup is required - just connect the `SDA` and `SCL` pins of your I2C devices to the matching pins on the MCU: + +|MCU |`SCL`|`SDA`| +|------------------|-----|-----| +|ATmega16/32U4 |`D0` |`D1` | +|AT90USB64/128 |`D0` |`D1` | +|ATmega32A |`C0` |`C1` | +|ATmega328/P |`C5` |`C4` | + +::: tip +The ATmega16/32U2 does not possess I2C functionality, and so cannot use this driver. +::: + +## ChibiOS/ARM Configuration {#arm-configuration} + +You'll need to determine which pins can be used for I2C -- a an example, STM32 parts generally have multiple I2C peripherals, labeled I2C1, I2C2, I2C3 etc. + +To enable I2C, modify your board's `halconf.h` to enable I2C: + +```c +#define HAL_USE_I2C TRUE +``` + +Then, modify your board's `mcuconf.h` to enable the peripheral you've chosen, for example: + +```c +#undef STM32_I2C_USE_I2C2 +#define STM32_I2C_USE_I2C2 TRUE +``` + +|`mcuconf.h` Setting |Description |Default| +|----------------------------|----------------------------------------------------------------------------------|-------| +|`STM32_I2C_BUSY_TIMEOUT` |Time in milliseconds until the I2C command is aborted if no response is received |`50` | +|`STM32_I2C_XXX_IRQ_PRIORITY`|Interrupt priority for hardware driver XXX (THIS IS AN EXPERT SETTING) |`10` | +|`STM32_I2C_USE_DMA` |Enable/Disable the ability of the MCU to offload the data transfer to the DMA unit|`TRUE` | +|`STM32_I2C_XXX_DMA_PRIORITY`|Priority of DMA unit for hardware driver XXX (THIS IS AN EXPERT SETTING) |`1` | + +Configuration-wise, you'll need to set up the peripheral as per your MCU's datasheet -- the defaults match the pins for a Proton-C, i.e. STM32F303. + +|`config.h` Overrride |Description |Default| +|------------------------|--------------------------------------------------------------|-------| +|`I2C_DRIVER` |I2C peripheral to use - I2C1 -> `I2CD1`, I2C2 -> `I2CD2` etc. |`I2CD1`| +|`I2C1_SCL_PIN` |The pin definition for SCL |`B6` | +|`I2C1_SCL_PAL_MODE` |The alternate function mode for SCL |`4` | +|`I2C1_SDA_PIN` |The pin definition for SDA |`B7` | +|`I2C1_SDA_PAL_MODE` |The alternate function mode for SDA |`4` | + +The following configuration values depend on the specific MCU in use. + +### I2Cv1 {#arm-configuration-i2cv1} + +* STM32F1xx +* STM32F2xx +* STM32F4xx +* STM32L0xx +* STM32L1xx + +See [this page](https://www.playembedded.org/blog/stm32-i2c-chibios/#7_I2Cv1_configuration_structure) for the I2Cv1 configuration structure. + +|`config.h` Override|Default | +|-------------------|----------------| +|`I2C1_OPMODE` |`OPMODE_I2C` | +|`I2C1_CLOCK_SPEED` |`100000` | +|`I2C1_DUTY_CYCLE` |`STD_DUTY_CYCLE`| + +### I2Cv2 {#arm-configuration-i2cv2} + +* STM32F0xx +* STM32F3xx +* STM32F7xx +* STM32L4xx + +See [this page](https://www.playembedded.org/blog/stm32-i2c-chibios/#8_I2Cv2_I2Cv3_configuration_structure) for the I2Cv2 configuration structure. + +|`config.h` Override |Default| +|---------------------|-------| +|`I2C1_TIMINGR_PRESC` |`0U` | +|`I2C1_TIMINGR_SCLDEL`|`7U` | +|`I2C1_TIMINGR_SDADEL`|`0U` | +|`I2C1_TIMINGR_SCLH` |`38U` | +|`I2C1_TIMINGR_SCLL` |`129U` | + +## API {#api} + +### `void i2c_init(void)` {#api-i2c-init} + +Initialize the I2C driver. This function must be called only once, before any of the below functions can be called. + +This function is weakly defined, meaning it can be overridden if necessary for your particular use case: + +```c +void i2c_init(void) { + gpio_set_pin_input(B6); // Try releasing special pins for a short time + gpio_set_pin_input(B7); + wait_ms(10); // Wait for the release to happen + + palSetPadMode(GPIOB, 6, PAL_MODE_ALTERNATE(4) | PAL_STM32_OTYPE_OPENDRAIN | PAL_STM32_PUPDR_PULLUP); // Set B6 to I2C function + palSetPadMode(GPIOB, 7, PAL_MODE_ALTERNATE(4) | PAL_STM32_OTYPE_OPENDRAIN | PAL_STM32_PUPDR_PULLUP); // Set B7 to I2C function +} +``` + +--- + +### `i2c_status_t i2c_transmit(uint8_t address, uint8_t *data, uint16_t length, uint16_t timeout)` {#api-i2c-transmit} + +Send multiple bytes to the selected I2C device. + +#### Arguments {#api-i2c-transmit-arguments} + + - `uint8_t address` + The 7-bit I2C address of the device. + - `uint8_t *data` + A pointer to the data to transmit. + - `uint16_t length` + The number of bytes to write. Take care not to overrun the length of `data`. + - `uint16_t timeout` + The time in milliseconds to wait for a response from the target device. + +#### Return Value {#api-i2c-transmit-return} + +`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`. + +--- + +### `i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)` {#api-i2c-receive} + +Receive multiple bytes from the selected I2C device. + +#### Arguments {#api-i2c-receive-arguments} + + - `uint8_t address` + The 7-bit I2C address of the device. + - `uint8_t *data` + A pointer to the buffer to read into. + - `uint16_t length` + The number of bytes to read. Take care not to overrun the length of `data`. + - `uint16_t timeout` + The time in milliseconds to wait for a response from the target device. + +#### Return Value {#api-i2c-receive-return} + +`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`. + +--- + +### `i2c_status_t i2c_write_register(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)` {#api-i2c-write-register} + +Writes to a register with an 8-bit address on the I2C device. + +#### Arguments {#api-i2c-write-register-arguments} + + - `uint8_t devaddr` + The 7-bit I2C address of the device. + - `uint8_t regaddr` + The register address to write to. + - `uint8_t *data` + A pointer to the data to transmit. + - `uint16_t length` + The number of bytes to write. Take care not to overrun the length of `data`. + - `uint16_t timeout` + The time in milliseconds to wait for a response from the target device. + +#### Return Value {#api-i2c-write-register-return} + +`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`. + +--- + +### `i2c_status_t i2c_write_register16(uint8_t devaddr, uint16_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)` {#api-i2c-write-register16} + +Writes to a register with a 16-bit address (big endian) on the I2C device. + +#### Arguments {#api-i2c-write-register16-arguments} + + - `uint8_t devaddr` + The 7-bit I2C address of the device. + - `uint16_t regaddr` + The register address to write to. + - `uint8_t *data` + A pointer to the data to transmit. + - `uint16_t length` + The number of bytes to write. Take care not to overrun the length of `data`. + - `uint16_t timeout` + The time in milliseconds to wait for a response from the target device. + +#### Return Value {#api-i2c-write-register16-return} + +`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`. + +--- + +### `i2c_status_t i2c_read_register(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)` {#api-i2c-read-register} + +Reads from a register with an 8-bit address on the I2C device. + +#### Arguments {#api-i2c-read-register-arguments} + + - `uint8_t devaddr` + The 7-bit I2C address of the device. + - `uint8_t regaddr` + The register address to read from. + - `uint16_t length` + The number of bytes to read. Take care not to overrun the length of `data`. + - `uint16_t timeout` + The time in milliseconds to wait for a response from the target device. + +#### Return Value {#api-i2c-read-register-return} + +`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`. + +--- + +### `i2c_status_t i2c_read_register16(uint8_t devaddr, uint16_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)` {#api-i2c-read-register16} + +Reads from a register with a 16-bit address (big endian) on the I2C device. + +#### Arguments {#api-i2c-read-register16-arguments} + + - `uint8_t devaddr` + The 7-bit I2C address of the device. + - `uint16_t regaddr` + The register address to read from. + - `uint16_t length` + The number of bytes to read. Take care not to overrun the length of `data`. + - `uint16_t timeout` + The time in milliseconds to wait for a response from the target device. + +#### Return Value {#api-i2c-read-register16-return} + +`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`. + +--- + +### `i2c_status_t i2c_ping_address(uint8_t address, uint16_t timeout)` {#api-i2c-ping-address} + +Pings the I2C bus for a specific address. + +On ChibiOS a "best effort" attempt is made by reading a single byte from register 0 at the requested address. This should generally work except for I2C devices that do not not respond to a register 0 read request, which will result in a false negative result (unsucessful response to ping attempt). + +This function is weakly defined, meaning it can be overridden if necessary for your particular use case: + +#### Arguments + + - `uint8_t address` + The 7-bit I2C address of the device (ie. without the read/write bit - this will be set automatically). + - `uint16_t timeout` + The time in milliseconds to wait for a response from the target device. + +#### Return Value + +`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`. diff --git a/docs/drivers/serial.md b/docs/drivers/serial.md new file mode 100644 index 0000000000..364e951b86 --- /dev/null +++ b/docs/drivers/serial.md @@ -0,0 +1,397 @@ +# 'serial' Driver + +The Serial driver powers the [Split Keyboard](../features/split_keyboard) feature. Several implementations are available that cater to the platform and capabilites of MCU in use. Note that none of the drivers support split keyboards with more than two halves. + +| Driver | AVR | ARM | Connection between halves | +| --------------------------------------- | ------------------ | ------------------ | --------------------------------------------------------------------------------------------- | +| [Bitbang](#bitbang) | :heavy_check_mark: | :heavy_check_mark: | Single wire communication. One wire is used for reception and transmission. | +| [USART Half-duplex](#usart-half-duplex) | | :heavy_check_mark: | Efficient single wire communication. One wire is used for reception and transmission. | +| [USART Full-duplex](#usart-full-duplex) | | :heavy_check_mark: | Efficient two wire communication. Two distinct wires are used for reception and transmission. | + +::: tip +Serial in this context should be read as **sending information one bit at a time**, rather than implementing UART/USART/RS485/RS232 standards. +::: + +<hr> + +## Bitbang + +This is the Default driver, absence of configuration assumes this driver. It works by [bit banging](https://en.wikipedia.org/wiki/Bit_banging) a GPIO pin using the CPU. It is therefore not as efficient as a dedicated hardware peripheral, which the Half-duplex and Full-duplex drivers use. + +::: warning +On ARM platforms the bitbang driver causes connection issues when using it together with the bitbang WS2812 driver. Choosing alternate drivers for both serial and WS2812 (instead of bitbang) is strongly recommended. +::: + +### Pin configuration + +``` + LEFT RIGHT ++-------+ SERIAL +-------+ +| SSP |-----------------| SSP | +| | VDD | | +| |-----------------| | +| | GND | | +| |-----------------| | ++-------+ +-------+ +``` + +One GPIO pin is needed for the bitbang driver, as only one wire is used for receiving and transmitting data. This pin is referred to as the `SOFT_SERIAL_PIN` (SSP) in the configuration. A TRS or USB cable provides enough conductors for this driver to function. + +### Setup + +To use the bitbang driver follow these steps to activate it. + +1. Change the `SERIAL_DRIVER` to `bitbang` in your keyboards `rules.mk` file: + +```make +SERIAL_DRIVER = bitbang +``` + +2. Configure the GPIO pin of your keyboard via the `config.h` file: + +```c +#define SOFT_SERIAL_PIN D0 // or D1, D2, D3, E6 +``` + +3. On ARM platforms you must turn on ChibiOS `PAL_USE_CALLBACKS` feature: + +* In `halconf.h` add the line `#define PAL_USE_CALLBACKS TRUE`. + +<hr> + +## USART Half-duplex + +Targeting ARM boards based on ChibiOS, where communication is offloaded to a USART hardware device that supports Half-duplex operation. The advantages over bitbanging are fast, accurate timings and reduced CPU usage. Therefore it is advised to choose Half-duplex over Bitbang if MCU is capable of utilising Half-duplex, and Full-duplex can't be used instead (e.g. lack of available GPIO pins, or imcompatible PCB design). + +### Pin configuration + +``` + LEFT RIGHT ++-------+ | | +-------+ +| | R R | | +| | | SERIAL | | | +| TX |-----------------| TX | +| | VDD | | +| |-----------------| | +| | GND | | +| |-----------------| | ++-------+ +-------+ +``` + +Only one GPIO pin is needed for the Half-duplex driver, as only one wire is used for receiving and transmitting data. This pin is referred to as the `SERIAL_USART_TX_PIN` in the configuration. Ensure that the pin chosen for split communication can operate as the TX pin of the contoller's USART peripheral. A TRS or USB cable provides enough conductors for this driver to function. As the split connection is configured to operate in open-drain mode, an **external pull-up resistor is needed to keep the line high**. Resistor values of 1.5kΩ to 8.2kΩ are known to work. + +::: warning +***Note:*** A pull-up resistor isn't required for RP2040 controllers configured with PIO subsystem. +::: + +### Setup + +To use the Half-duplex driver follow these steps to activate it. If you target the Raspberry Pi RP2040 PIO implementation, start at step 2. + +1. Change the `SERIAL_DRIVER` to `usart` in your keyboards `rules.mk` file: + +```make +SERIAL_DRIVER = usart +``` + +Skip to step 3. + +2. (RP2040 + PIO only!) Change the `SERIAL_DRIVER` to `vendor` in your keyboards `rules.mk` file: + +```make +SERIAL_DRIVER = vendor +``` + +3. Configure the hardware of your keyboard via the `config.h` file: + +```c +#define SERIAL_USART_TX_PIN B6 // The GPIO pin that is used split communication. +``` + +For STM32 MCUs several GPIO configuration options can be changed as well. See the section ["Alternate Functions for selected STM32 MCUs"](#alternate-functions-for-selected-stm32-mcus). + +```c +#define USART1_REMAP // Remap USART TX and RX pins on STM32F103 MCUs, see table below. +#define SERIAL_USART_TX_PAL_MODE 7 // Pin "alternate function", see the respective datasheet for the appropriate values for your MCU. default: 7 +``` + +4. Decide either for `SERIAL`, `SIO`, or `PIO` subsystem. See section ["Choosing a driver subsystem"](#choosing-a-driver-subsystem). + +<hr> + +## USART Full-duplex + +Targeting ARM boards based on ChibiOS where communication is offloaded to an USART hardware device. The advantages over bitbanging are fast, accurate timings and reduced CPU usage; therefore it is advised to choose this driver over all others where possible. Due to its internal design Full-duplex is slightly more efficient than the Half-duplex driver, but Full-duplex should be primarily chosen if Half-duplex operation is not supported by the controller's USART peripheral. + +### Pin configuration + +``` + LEFT RIGHT ++-------+ +-------+ +| | SERIAL | | +| TX |-----------------| RX | +| | SERIAL | | +| RX |-----------------| TX | +| | VDD | | +| |-----------------| | +| | GND | | +| |-----------------| | ++-------+ +-------+ +``` + +Two GPIO pins are needed for the Full-duplex driver, as two distinct wires are used for receiving and transmitting data. The pin transmitting data is the `TX` pin and refereed to as the `SERIAL_USART_TX_PIN`, the pin receiving data is the `RX` pin and refereed to as the `SERIAL_USART_RX_PIN` in this configuration. Please note that `TX` pin of the master half has to be connected with the `RX` pin of the slave half and the `RX` pin of the master half has to be connected with the `TX` pin of the slave half! Usually this pin swap has to be done outside of the MCU e.g. with cables or on the PCB. Some MCUs like the STM32F303 used on the Proton-C allow this pin swap directly inside the MCU. A TRRS or USB cable provides enough conductors for this driver to function. + +To use this driver the USART peripherals `TX` and `RX` pins must be configured with the correct Alternate-functions. If you are using a Proton-C development board everything is already setup, same is true for STM32F103 MCUs. For MCUs which are using a modern flexible GPIO configuration you have to specify these by setting `SERIAL_USART_TX_PAL_MODE` and `SERIAL_USART_RX_PAL_MODE`. Refer to the corresponding datasheets of your MCU or find those settings in the section ["Alternate Functions for selected STM32 MCUs"](#alternate-functions-for-selected-stm32-mcus). + +### Setup + +To use the Full-duplex driver follow these steps to activate it. If you target the Raspberry Pi RP2040 PIO implementation, start at step 2 + +1. Change the `SERIAL_DRIVER` to `usart` in your keyboards `rules.mk` file: + +```make +SERIAL_DRIVER = usart +``` + +Skip to step 3 + +2. (RP2040 + PIO only!) Change the `SERIAL_DRIVER` to `vendor` in your keyboards `rules.mk` file: + +```make +SERIAL_DRIVER = vendor +``` + +3. Configure the hardware of your keyboard via the `config.h` file: + +```c +#define SERIAL_USART_FULL_DUPLEX // Enable full duplex operation mode. +#define SERIAL_USART_TX_PIN B6 // USART TX pin +#define SERIAL_USART_RX_PIN B7 // USART RX pin +``` + +For STM32 MCUs several GPIO configuration options, including the ability for `TX` to `RX` pin swapping, can be changed as well. See the section ["Alternate Functions for selected STM32 MCUs"](#alternate-functions-for-selected-stm32-mcus). + +```c +#define SERIAL_USART_PIN_SWAP // Swap TX and RX pins if keyboard is master halve. (Only available on some MCUs) +#define USART1_REMAP // Remap USART TX and RX pins on STM32F103 MCUs, see table below. +#define SERIAL_USART_TX_PAL_MODE 7 // Pin "alternate function", see the respective datasheet for the appropriate values for your MCU. default: 7 +``` + +4. Decide either for `SERIAL`, `SIO`, or `PIO` subsystem. See section ["Choosing a driver subsystem"](#choosing-a-driver-subsystem). + +<hr> + +## Choosing a driver subsystem + +### The `SERIAL` driver + +The `SERIAL` Subsystem is supported for the majority of ChibiOS MCUs and should be used whenever supported. Follow these steps in order to activate it: + +1. In your keyboards `halconf.h` add: + +```c +#define HAL_USE_SERIAL TRUE +``` + +2. In your keyboards `mcuconf.h`: activate the USART peripheral that is used on your MCU. The shown example is for an STM32 MCU, so this will not work on MCUs by other manufacturers. You can find the correct names in the `mcuconf.h` files of your MCU that ship with ChibiOS. + +Just below `#include_next <mcuconf.h>` add: + +```c +#include_next <mcuconf.h> + +#undef STM32_SERIAL_USE_USARTn +#define STM32_SERIAL_USE_USARTn TRUE +``` + +Where 'n' matches the peripheral number of your selected USART on the MCU. + +3. In you keyboards `config.h`: override the default USART `SERIAL` driver if you use a USART peripheral that does not belong to the default selected `SD1` driver. For instance, if you selected `STM32_SERIAL_USE_USART3` the matching driver would be `SD3`. + +```c + #define SERIAL_USART_DRIVER SD3 + ``` + +### The `SIO` driver + +The `SIO` Subsystem was added to ChibiOS with the 21.11 release and is only supported on selected MCUs. It should only be chosen when the `SERIAL` subsystem is not supported by your MCU. + +Follow these steps in order to activate it: + +1. In your keyboards `halconf.h` add: + +```c +#define HAL_USE_SIO TRUE +``` + +2. In your keyboards `mcuconf.h:` activate the USART peripheral that is used on your MCU. The shown example is for an STM32 MCU, so this will not work on MCUs by other manufacturers. You can find the correct names in the `mcuconf.h` files of your MCU that ship with ChibiOS. + +Just below `#include_next <mcuconf.h>` add: + +```c +#include_next <mcuconf.h> + +#undef STM32_SIO_USE_USARTn +#define STM32_SIO_USE_USARTn TRUE +``` + +Where 'n' matches the peripheral number of your selected USART on the MCU. + +3. In the keyboard's `config.h` file: override the default USART `SIO` driver if you use a USART peripheral that does not belong to the default selected `SIOD1` driver. For instance, if you selected `STM32_SERIAL_USE_USART3` the matching driver would be `SIOD3`. + +```c + #define SERIAL_USART_DRIVER SIOD3 + ``` + +### The `PIO` driver + +The `PIO` subsystem is a Raspberry Pi RP2040 specific implementation, using an integrated PIO peripheral and is therefore only available on this MCU. Because of the flexible nature of PIO peripherals, **any** GPIO pin can be used as a `TX` or `RX` pin. Half-duplex and Full-duplex operation modes are fully supported with this driver. Half-duplex uses the built-in pull-ups and GPIO manipulation of the RP2040 to drive the line high by default, thus an external pull-up resistor **is not required**. + +Optionally, the PIO peripheral utilized for split communication can be changed with the following define in config.h: +```c +#define SERIAL_PIO_USE_PIO1 // Force the usage of PIO1 peripheral, by default the Serial implementation uses the PIO0 peripheral +``` + +The Serial PIO program uses 2 state machines, 13 instructions and the complete interrupt handler of the PIO peripheral it is running on. + +<hr> + +## Advanced Configuration + +There are several advanced configuration options that can be defined in your keyboards `config.h` file: + +### Baudrate + +If you're having issues or need a higher baudrate with serial communication, you can change the baudrate which in turn controls the communication speed for serial. You want to lower the baudrate if you experience failed transactions. + +```c +#define SELECT_SOFT_SERIAL_SPEED {#} +``` + +| Speed | Bitbang | Half-duplex and Full-duplex | +| ----- | -------------------------- | --------------------------- | +| `0` | 189000 baud (experimental) | 460800 baud | +| `1` | 137000 baud (default) | 230400 baud (default) | +| `2` | 75000 baud | 115200 baud | +| `3` | 39000 baud | 57600 baud | +| `4` | 26000 baud | 38400 baud | +| `5` | 20000 baud | 19200 baud | + +Alternatively you can specify the baudrate directly by defining `SERIAL_USART_SPEED`. + +### Timeout + +This is the default time window in milliseconds in which a successful communication has to complete. Usually you don't want to change this value. But you can do so anyways by defining an alternate one in your keyboards `config.h` file: + +```c +#define SERIAL_USART_TIMEOUT 20 // USART driver timeout. default 20 +``` + +<hr> + +## Troubleshooting + +If you're having issues withe serial communication, you can enable debug messages that will give you insights which part of the communication failed. The enable these messages add to your keyboards `config.h` file: + +```c +#define SERIAL_DEBUG +``` + +::: tip +The messages will be printed out to the `CONSOLE` output. For additional information, refer to [Debugging/Troubleshooting QMK](../faq_debug). +::: + +## Alternate Functions for selected STM32 MCUs + +Pins for USART Peripherals with + +### STM32F303 / Proton-C [Datasheet](https://www.st.com/resource/en/datasheet/stm32f303cc.pdf) + +Pin Swap available: :heavy_check_mark: + +| Pin | Function | Mode | +| ---------- | -------- | ---- | +| **USART1** | | | +| PA9 | TX | AF7 | +| PA10 | RX | AF7 | +| PB6 | TX | AF7 | +| PB7 | RX | AF7 | +| PC4 | TX | AF7 | +| PC5 | RX | AF7 | +| PE0 | TX | AF7 | +| PE1 | RX | AF7 | +| **USART2** | | | +| PA2 | TX | AF7 | +| PA3 | RX | AF7 | +| PA14 | TX | AF7 | +| PA15 | RX | AF7 | +| PB3 | TX | AF7 | +| PB4 | RX | AF7 | +| PD5 | TX | AF7 | +| PD6 | RX | AF7 | +| **USART3** | | | +| PB10 | TX | AF7 | +| PB11 | RX | AF7 | +| PC10 | TX | AF7 | +| PC11 | RX | AF7 | +| PD8 | TX | AF7 | +| PD9 | RX | AF7 | + +### STM32F072 [Datasheet](https://www.st.com/resource/en/datasheet/stm32f072c8.pdf) + +Pin Swap available: :heavy_check_mark: + +| Pin | Function | Mode | +| ------ | -------- | ---- | +| USART1 | | | +| PA9 | TX | AF1 | +| PA10 | RX | AF1 | +| PB6 | TX | AF0 | +| PB7 | RX | AF0 | +| USART2 | | | +| PA2 | TX | AF1 | +| PA3 | RX | AF1 | +| PA14 | TX | AF1 | +| PA15 | RX | AF1 | +| USART3 | | | +| PB10 | TX | AF4 | +| PB11 | RX | AF4 | +| PC4 | TX | AF1 | +| PC5 | RX | AF1 | +| PC10 | TX | AF1 | +| PC11 | RX | AF1 | +| PD8 | TX | AF0 | +| PD9 | RX | AF0 | +| USART4 | | | +| PA0 | TX | AF4 | +| PA1 | RX | AF4 | + +### STM32F103 Medium Density (C8-CB) [Datasheet](https://www.st.com/resource/en/datasheet/stm32f103c8.pdf) + +Pin Swap available: N/A + +TX Pin is always Alternate Function Push-Pull, RX Pin is always regular input pin for any USART peripheral. **For STM32F103 no additional Alternate Function configuration is necessary. QMK is already configured.** + +Pin remapping: + +The pins of USART Peripherals use default Pins that can be remapped to use other pins using the AFIO registers. Default pins are marked **bold**. Add the appropriate defines to your config.h file. + +| Pin | Function | Mode | USART_REMAP | +| ---------- | -------- | ---- | ------------------- | +| **USART1** | | | | +| **PA9** | TX | AFPP | | +| **PA10** | RX | IN | | +| PB6 | TX | AFPP | USART1_REMAP | +| PB7 | RX | IN | USART1_REMAP | +| **USART2** | | | | +| **PA2** | TX | AFPP | | +| **PA3** | RX | IN | | +| PD5 | TX | AFPP | USART2_REMAP | +| PD6 | RX | IN | USART2_REMAP | +| **USART3** | | | | +| **PB10** | TX | AFPP | | +| **PB11** | RX | IN | | +| PC10 | TX | AFPP | USART3_PARTIALREMAP | +| PC11 | RX | IN | USART3_PARTIALREMAP | +| PD8 | TX | AFPP | USART3_FULLREMAP | +| PD9 | RX | IN | USART3_FULLREMAP | diff --git a/docs/drivers/spi.md b/docs/drivers/spi.md new file mode 100644 index 0000000000..ddc35de851 --- /dev/null +++ b/docs/drivers/spi.md @@ -0,0 +1,167 @@ +# SPI Master Driver {#spi-master-driver} + +The SPI Master drivers used in QMK have a set of common functions to allow portability between MCUs. + +## Usage {#usage} + +In most cases, the SPI Master driver code is automatically included if you are using a feature or driver which requires it, such as [OLED](../features/oled_driver). + +However, if you need to use the driver standalone, add the following to your `rules.mk`: + +```make +SPI_DRIVER_REQUIRED = yes +``` + +You can then call the SPI API by including `spi_master.h` in your code. + +## AVR Configuration {#avr-configuration} + +No special setup is required - just connect the `SS`, `SCK`, `MOSI` and `MISO` pins of your SPI devices to the matching pins on the MCU: + +|MCU |`SS`|`SCK`|`MOSI`|`MISO`| +|-----------------|----|-----|------|------| +|ATmega16/32U2/4 |`B0`|`B1` |`B2` |`B3` | +|AT90USB64/128/162|`B0`|`B1` |`B2` |`B3` | +|ATmega32A |`B4`|`B7` |`B5` |`B6` | +|ATmega328/P |`B2`|`B5` |`B3` |`B4` | + +You may use more than one slave select pin, not just the `SS` pin. This is useful when you have multiple devices connected and need to communicate with them individually. +`SPI_SS_PIN` can be passed to `spi_start()` to refer to `SS`. + +## ChibiOS/ARM Configuration {#arm-configuration} + +You'll need to determine which pins can be used for SPI -- as an example, STM32 parts generally have multiple SPI peripherals, labeled SPI1, SPI2, SPI3 etc. + +To enable SPI, modify your board's `halconf.h` to enable SPI: + +```c +#define HAL_USE_SPI TRUE +#define SPI_USE_WAIT TRUE +#define SPI_SELECT_MODE SPI_SELECT_MODE_PAD +``` + +Then, modify your board's `mcuconf.h` to enable the peripheral you've chosen, for example: + +```c +#undef STM32_SPI_USE_SPI2 +#define STM32_SPI_USE_SPI2 TRUE +``` + +Configuration-wise, you'll need to set up the peripheral as per your MCU's datasheet -- the defaults match the pins for a Proton-C, i.e. STM32F303. + +|`config.h` Override|Description |Default| +|-------------------|-------------------------------------------------------------|-------| +|`SPI_DRIVER` |SPI peripheral to use - SPI1 -> `SPID1`, SPI2 -> `SPID2` etc.|`SPID2`| +|`SPI_SCK_PIN` |The pin to use for SCK |`B13` | +|`SPI_SCK_PAL_MODE` |The alternate function mode for SCK |`5` | +|`SPI_MOSI_PIN` |The pin to use for MOSI |`B15` | +|`SPI_MOSI_PAL_MODE`|The alternate function mode for MOSI |`5` | +|`SPI_MISO_PIN` |The pin to use for MISO |`B14` | +|`SPI_MISO_PAL_MODE`|The alternate function mode for MISO |`5` | + +As per the AVR configuration, you may choose any other standard GPIO as a slave select pin, which should be supplied to `spi_start()`. + +If a complete SPI interface is not required, then the following can be done to disable certain SPI pins, so they don't occupy a GPIO unnecessarily: + - in `config.h`: `#define SPI_MISO_PIN NO_PIN` + - in `config.h`: `#define SPI_MOSI_PIN NO_PIN` + - in `mcuconf.h`: `#define SPI_SELECT_MODE SPI_SELECT_MODE_NONE`, in this case the `slavePin` argument passed to `spi_start()` may be `NO_PIN` if the slave select pin is not used. + +## API {#api} + +### `void spi_init(void)` {#api-spi-init} + +Initialize the SPI driver. This function must be called only once, before any of the below functions can be called. + +--- + +### `bool spi_start(pin_t slavePin, bool lsbFirst, uint8_t mode, uint16_t divisor)` {#api-spi-start} + +Start an SPI transaction. + +#### Arguments {#api-spi-start-arguments} + + - `pin_t slavePin` + The QMK pin to assert as the slave select pin, eg. `B4`. + - `bool lsbFirst` + Determines the endianness of the transmission. If `true`, the least significant bit of each byte is sent first. + - `uint8_t mode` + The SPI mode to use: + + |Mode|Clock Polarity |Clock Phase | + |----|--------------------|-----------------------| + |`0` |Leading edge rising |Sample on leading edge | + |`1` |Leading edge rising |Sample on trailing edge| + |`2` |Leading edge falling|Sample on leading edge | + |`3` |Leading edge falling|Sample on trailing edge| + + - `uint16_t divisor` + The SPI clock divisor, will be rounded up to the nearest power of two. This number can be calculated by dividing the MCU's clock speed by the desired SPI clock speed. For example, an MCU running at 8 MHz wanting to talk to an SPI device at 4 MHz would set the divisor to `2`. + +#### Return Value {#api-spi-start-return} + +`false` if the supplied parameters are invalid or the SPI peripheral is already in use, or `true`. + +--- + +### `spi_status_t spi_write(uint8_t data)` {#api-spi-write} + +Write a byte to the selected SPI device. + +#### Arguments {#api-spi-write-arguments} + + - `uint8_t data` + The byte to write. + +#### Return Value {#api-spi-write-return} + +`SPI_STATUS_TIMEOUT` if the timeout period elapses, or `SPI_STATUS_SUCCESS`. + +--- + +### `spi_status_t spi_read(void)` {#api-spi-read} + +Read a byte from the selected SPI device. + +#### Return Value {#api-spi-read-return} + +`SPI_STATUS_TIMEOUT` if the timeout period elapses, or the byte read from the device. + +--- + +### `spi_status_t spi_transmit(const uint8_t *data, uint16_t length)` {#api-spi-transmit} + +Send multiple bytes to the selected SPI device. + +#### Arguments {#api-spi-transmit-arguments} + + - `const uint8_t *data` + A pointer to the data to write from. + - `uint16_t length` + The number of bytes to write. Take care not to overrun the length of `data`. + +#### Return Value {#api-spi-transmit-return} + +`SPI_STATUS_TIMEOUT` if the timeout period elapses, `SPI_STATUS_ERROR` if some other error occurs, otherwise `SPI_STATUS_SUCCESS`. + +--- + +### `spi_status_t spi_receive(uint8_t *data, uint16_t length)` {#api-spi-receive} + +Receive multiple bytes from the selected SPI device. + +#### Arguments {#api-spi-receive-arguments} + + - `uint8_t *data` + A pointer to the buffer to read into. + - `uint16_t length` + The number of bytes to read. Take care not to overrun the length of `data`. + +#### Return Value {#api-spi-receive-return} + +`SPI_STATUS_TIMEOUT` if the timeout period elapses, `SPI_STATUS_ERROR` if some other error occurs, otherwise `SPI_STATUS_SUCCESS`. + +--- + +### `void spi_stop(void)` {#api-spi-stop} + +End the current SPI transaction. This will deassert the slave select pin and reset the endianness, mode and divisor configured by `spi_start()`. diff --git a/docs/drivers/uart.md b/docs/drivers/uart.md new file mode 100644 index 0000000000..23f5b3d6e4 --- /dev/null +++ b/docs/drivers/uart.md @@ -0,0 +1,122 @@ +# UART Driver {#uart-driver} + +The UART drivers used in QMK have a set of common functions to allow portability between MCUs. + +Currently, this driver does not support enabling hardware flow control (the `RTS` and `CTS` pins) if available, but may do so in future. + +## Usage {#usage} + +In most cases, the UART driver code is automatically included if you are using a feature or driver which requires it. + +However, if you need to use the driver standalone, add the following to your `rules.mk`: + +```make +UART_DRIVER_REQUIRED = yes +``` + +You can then call the UART API by including `uart.h` in your code. + +## AVR Configuration {#avr-configuration} + +No special setup is required - just connect the `RX` and `TX` pins of your UART device to the opposite pins on the MCU: + +|MCU |`TX`|`RX`|`CTS`|`RTS`| +|-------------|----|----|-----|-----| +|ATmega16/32U2|`D3`|`D2`|`D7` |`D6` | +|ATmega16/32U4|`D3`|`D2`|`D5` |`B7` | +|AT90USB64/128|`D3`|`D2`|*n/a*|*n/a*| +|ATmega32A |`D1`|`D0`|*n/a*|*n/a*| +|ATmega328/P |`D1`|`D0`|*n/a*|*n/a*| + +## ChibiOS/ARM Configuration {#arm-configuration} + +You'll need to determine which pins can be used for UART -- as an example, STM32 parts generally have multiple UART peripherals, labeled USART1, USART2, USART3 etc. + +To enable UART, modify your board's `mcuconf.h` to enable the peripheral you've chosen, for example: + +```c +#undef STM32_SERIAL_USE_USART2 +#define STM32_SERIAL_USE_USART2 TRUE +``` + +Configuration-wise, you'll need to set up the peripheral as per your MCU's datasheet -- the defaults match the pins for a Proton-C, i.e. STM32F303. + +| `config.h` override | Description | Default Value | +| --------------------------- | --------------------------------------------------------------- | ------------- | +| `#define UART_DRIVER` | USART peripheral to use - USART1 -> `SD1`, USART2 -> `SD2` etc. | `SD1` | +| `#define UART_TX_PIN` | The pin to use for TX | `A9` | +| `#define UART_TX_PAL_MODE` | The alternate function mode for TX | `7` | +| `#define UART_RX_PIN` | The pin to use for RX | `A10` | +| `#define UART_RX_PAL_MODE` | The alternate function mode for RX | `7` | +| `#define UART_CTS_PIN` | The pin to use for CTS | `A11` | +| `#define UART_CTS_PAL_MODE` | The alternate function mode for CTS | `7` | +| `#define UART_RTS_PIN` | The pin to use for RTS | `A12` | +| `#define UART_RTS_PAL_MODE` | The alternate function mode for RTS | `7` | + +## API {#api} + +### `void uart_init(uint32_t baud)` {#api-uart-init} + +Initialize the UART driver. This function must be called only once, before any of the below functions can be called. + +#### Arguments {#api-uart-init-arguments} + + - `uint32_t baud` + The baud rate to transmit and receive at. This may depend on the device you are communicating with. Common values are 1200, 2400, 4800, 9600, 19200, 38400, 57600, and 115200. + +--- + +### `void uart_write(uint8_t data)` {#api-uart-write} + +Transmit a single byte. + +#### Arguments {#api-uart-write-arguments} + + - `uint8_t data` + The byte to write. + +--- + +### `uint8_t uart_read(void)` {#api-uart-read} + +Receive a single byte. + +#### Return Value {#api-uart-read-return} + +The byte read from the receive buffer. This function will block if the buffer is empty (ie. no data to read). + +--- + +### `void uart_transmit(const uint8_t *data, uint16_t length)` {#api-uart-transmit} + +Transmit multiple bytes. + +#### Arguments {#api-uart-transmit-arguments} + + - `const uint8_t *data` + A pointer to the data to write from. + - `uint16_t length` + The number of bytes to write. Take care not to overrun the length of `data`. + +--- + +### `void uart_receive(char *data, uint16_t length)` {#api-uart-receive} + +Receive multiple bytes. + +#### Arguments {#api-uart-receive-arguments} + + - `uint8_t *data` + A pointer to the buffer to read into. + - `uint16_t length` + The number of bytes to read. Take care not to overrun the length of `data`. + +--- + +### `bool uart_available(void)` {#api-uart-available} + +Return whether the receive buffer contains data. Call this function to determine if `uart_read()` will return data immediately. + +#### Return Value {#api-uart-available-return} + +`true` if the receive buffer length is non-zero. diff --git a/docs/drivers/ws2812.md b/docs/drivers/ws2812.md new file mode 100644 index 0000000000..61addf1917 --- /dev/null +++ b/docs/drivers/ws2812.md @@ -0,0 +1,253 @@ +# WS2812 Driver {#ws2812-driver} + +This driver provides support for WorldSemi addressable RGB(W) LEDs, and compatible equivalents: + + * WS2811, WS2812, WS2812B, WS2812C, etc. + * SK6812, SK6812MINI, SK6805 + +These LEDs are often called "addressable" because instead of using a wire per color (and per LED), each LED contains a small microchip that understands a special protocol sent over a single wire. +The LEDs can be chained together, and the remaining data is passed on to the next. In this way, you can easily control the color of many LEDs using a single GPIO. + +## Usage {#usage} + +In most cases, the WS2812 driver code is automatically included if you are using either the [RGBLight](../features/rgblight) or [RGB Matrix](../features/rgb_matrix) feature with the `ws2812` driver set, and you would use those APIs instead. + +However, if you need to use the driver standalone, add the following to your `rules.mk`: + +```make +WS2812_DRIVER_REQUIRED = yes +``` + +You can then call the WS2812 API by including `ws2812.h` in your code. + +## Basic Configuration {#basic-configuration} + +Add the following to your `config.h`: + +|Define |Default |Description | +|-------------------|-----------------------|------------------------------------------------------------------------------------------------| +|`WS2812_DI_PIN` |*Not defined* |The GPIO pin connected to the DI pin of the first LED in the chain | +|`WS2812_LED_COUNT` |*Not defined* |Number of LEDs in the WS2812 chain - automatically set when RGBLight or RGB Matrix is configured| +|`WS2812_TIMING` |`1250` |The total length of a bit (TH+TL) in nanoseconds | +|`WS2812_T1H` |`900` |The length of a "1" bit's high phase in nanoseconds | +|`WS2812_T0H` |`350` |The length of a "0" bit's high phase in nanoseconds | +|`WS2812_TRST_US` |`280` |The length of the reset phase in microseconds | +|`WS2812_BYTE_ORDER`|`WS2812_BYTE_ORDER_GRB`|The byte order of the RGB data | +|`WS2812_RGBW` |*Not defined* |Enables RGBW support (except `i2c` driver) | + +### Timing Adjustment {#timing-adjustment} + +The WS2812 LED communication protocol works by encoding a "1" bit with a long high pulse (T<sub>1</sub>H), and a "0" bit with a shorter pulse (T<sub>0</sub>H). The total cycle length of a bit is the same. +The "reset" pulse (T<sub>RST</sub>) latches the sent RGB data to all of the LEDs and denotes a completed "frame". + +Some WS2812 variants have slightly different timing parameter requirements, which can be accounted for if necessary using the above `#define`s in your `config.h`. + +### Byte Order {#byte-order} + +Some WS2812 variants may have their color components in a different physical or logical order. For example, the WS2812B-2020 has physically swapped red and green LEDs, which causes the wrong color to be displayed, because the default order of the bytes sent over the wire is defined as GRB. +If you find your LED colors are consistently swapped, you may need to change the byte order by adding the following to your `config.h`: + +```c +#define WS2812_BYTE_ORDER WS2812_BYTE_ORDER_GRB +``` + +Where the byte order may be one of: + +|Byte Order|Known Devices | +|----------|----------------------------| +|`GRB` |Most WS2812s, SK6812, SK6805| +|`RGB` |WS2812B-2020 | +|`BGR` |TM1812 | + +### RGBW Support {#rgbw-support} + +Rendering the color white with RGB LEDs is typically inconsistent due to inherent variations between each individual LED die. However, some WS2812 variants (such as SK6812RGBW) also possess a white LED along with the red, green, and blue channels, which allows for a more accurate white to be displayed. + +QMK can automatically convert the RGB data to be sent to the LEDs to mix in the white channel: + +``` +w = min(r, g, b) +r -= w +g -= w +b -= w +``` + +Thus, an RGB triplet of `255,255,255` will simply turn on the white LED fully (`0,0,0,255`). + +To enable RGBW conversion, add the following to your `config.h`: + +```c +#define WS2812_RGBW +``` + +## Driver Configuration {#driver-configuration} + +Driver selection can be configured in `rules.mk` as `WS2812_DRIVER`, or in `info.json` as `ws2812.driver`. Valid values are `bitbang` (default), `i2c`, `spi`, `pwm`, `vendor`, or `custom`. See below for information on individual drivers. + +### Bitbang Driver {#bitbang-driver} + +This is the default WS2812 driver. It operates by "bit-banging" ie. directly toggling the GPIO. + +Please note that on AVR devices, due to the tight timing requirements longer chains and/or heavy CPU loads may cause visible lag. Unfortunately this driver is usually the only option for AVR. + +```make +WS2812_DRIVER = bitbang +``` + +### I2C Driver {#i2c-driver} + +A specialized driver mainly used for PS2AVRGB (Bootmapper Client) boards, which possess an ATtiny85 that handles the WS2812 LEDs. + +```make +WS2812_DRIVER = i2c +``` + +The following `#define`s apply only to the `i2c` driver: + +|Define |Default|Description | +|--------------------|-------|---------------------------------| +|`WS2812_I2C_ADDRESS`|`0xB0` |The I2C address of the ATtiny85. | +|`WS2812_I2C_TIMEOUT`|`100` |The I2C timeout, in milliseconds.| + +### PIO Driver {#pio-driver} + +This driver is RP2040-only, and leverages the onboard PIO (programmable I/O) system and DMA to offload processing from the CPU. + +The WS2812 PIO program uses one state machine, six instructions and one DMA interrupt handler callback. Due to the implementation the time resolution for this driver is 50 ns - any value not specified in this interval will be rounded to the next matching interval. + +```make +WS2812_DRIVER = vendor +``` + +### PWM Driver {#pwm-driver} + +This driver is ARM-only, and leverages the onboard PWM peripheral and DMA to offload processing from the CPU. + +```make +WS2812_DRIVER = pwm +``` + +### SPI Driver {#spi-driver} + +This driver is ARM-only, and leverages the onboard SPI peripheral and DMA to offload processing from the CPU. The DI pin **must** be connected to the MOSI pin on the MCU, and all other SPI pins **must** be left unused. This is also very dependent on your MCU's SPI peripheral clock speed, and may or may not be possible depending on the MCU selected. + +```make +WS2812_DRIVER = spi +``` + +## ChibiOS/ARM Configuration {#arm-configuration} + +The following defines apply only to ARM devices: + +|Define |Default |Description | +|------------|------------------------------|---------------------------------------------------------------------------------| +|`WS2812_T1L`|`(WS2812_TIMING - WS2812_T1H)`|The length of a "1" bit's low phase in nanoseconds (bitbang and PIO drivers only)| +|`WS2812_T0L`|`(WS2812_TIMING - WS2812_T0H)`|The length of a "0" bit's low phase in nanoseconds (bitbang and PIO drivers only)| + +### Push-Pull and Open Drain {#push-pull-open-drain} + +By default, the GPIO used for data transmission is configured as a *push-pull* output, meaning the pin is effectively always driven either to VCC or to ground. + +For situations where the logic level voltage is lower than the power supply voltage, however, this can pose an issue. The solution is to configure the pin for *open drain* mode instead, and use a pullup resistor between the DI pin and VCC. In this mode, the MCU can only pull the GPIO *low*, or leave it floating. The pullup resistor is then responsible for pulling the line high, when the MCU is not driving the GPIO. + +To configure the DI pin for open drain configuration, add the following to your `config.h`: + +```c +#define WS2812_EXTERNAL_PULLUP +``` + +### SPI Driver {#arm-spi-driver} + +Depending on the ChibiOS board configuration, you may need to enable SPI at the keyboard level. For STM32, this would look like: + +`halconf.h`: +```c +#define HAL_USE_SPI TRUE +``` +`mcuconf.h`: +```c +#undef STM32_SPI_USE_SPI1 +#define STM32_SPI_USE_SPI1 TRUE +``` + +The following `define`s apply only to the `spi` driver: + +|Define |Default |Description | +|--------------------------------|-------------|-------------------------------------------------------------------------------| +|`WS2812_SPI_DRIVER` |`SPID1` |The SPI driver to use | +|`WS2812_SPI_MOSI_PAL_MODE` |`5` |The MOSI pin alternative function to use | +|`WS2812_SPI_SCK_PIN` |*Not defined*|The SCK pin - required for F072 and possibly others | +|`WS2812_SPI_SCK_PAL_MODE` |`5` |The SCK pin alternative function to use - required for F072 and possibly others| +|`WS2812_SPI_DIVISOR` |`16` |The divisor used to adjust the baudrate | +|`WS2812_SPI_USE_CIRCULAR_BUFFER`|*Not defined*|Enable a circular buffer for improved rendering | + +#### Setting the Baudrate {#arm-spi-baudrate} + +To adjust the SPI baudrate, you will need to derive the target baudrate from the clock tree provided by STM32CubeMX, and add the following to your `config.h`: + +```c +#define WS2812_SPI_DIVISOR 16 +``` + +Only divisors of 2, 4, 8, 16, 32, 64, 128 and 256 are supported on STM32 devices. Other MCUs may have similar constraints -- check the reference manual for your respective MCU for specifics. + +#### Circular Buffer {#arm-spi-circular-buffer} + +A circular buffer can be enabled if you experience flickering. + +To enable the circular buffer, add the following to your `config.h`: + +```c +#define WS2812_SPI_USE_CIRCULAR_BUFFER +``` + +### PIO Driver {#arm-pio-driver} + +The following `#define`s apply only to the PIO driver: + +|Define |Default |Description | +|---------------------|-------------|---------------------------------------| +|`WS2812_PIO_USE_PIO1`|*Not defined*|Use the PIO1 peripheral instead of PIO0| + +### PWM Driver {#arm-pwm-driver} + +Depending on the ChibiOS board configuration, you may need to enable PWM at the keyboard level. For STM32, this would look like: + +`halconf.h`: +```c +#define HAL_USE_PWM TRUE +``` +`mcuconf.h`: +```c +#undef STM32_PWM_USE_TIM2 +#define STM32_PWM_USE_TIM2 TRUE +``` + +The following `#define`s apply only to the `pwm` driver: + +|Define |Default |Description | +|---------------------------------|--------------------|------------------------------------------------------------------------------------------| +|`WS2812_PWM_DRIVER` |`PWMD2` |The PWM driver to use | +|`WS2812_PWM_CHANNEL` |`2` |The PWM channel to use | +|`WS2812_PWM_PAL_MODE` |`2` |The pin alternative function to use | +|`WS2812_PWM_DMA_STREAM` |`STM32_DMA1_STREAM2`|The DMA Stream for `TIMx_UP` | +|`WS2812_PWM_DMA_CHANNEL` |`2` |The DMA Channel for `TIMx_UP` | +|`WS2812_PWM_DMAMUX_ID` |*Not defined* |The DMAMUX configuration for `TIMx_UP` - only required if your MCU has a DMAMUX peripheral| +|`WS2812_PWM_COMPLEMENTARY_OUTPUT`|*Not defined* |Whether the PWM output is complementary (`TIMx_CHyN`) | + +::: tip +Using a complementary timer output (`TIMx_CHyN`) is possible only for advanced-control timers (1, 8 and 20 on STM32), and the `STM32_PWM_USE_ADVANCED` option in `mcuconf.h` must be set to `TRUE`. Complementary outputs of general-purpose timers are not supported due to ChibiOS limitations. +::: + +## API {#api} + +### `void ws2812_setleds(rgb_led_t *ledarray, uint16_t number_of_leds)` {#api-ws2812-setleds} + +Send RGB data to the WS2812 LED chain. + +#### Arguments {#api-ws2812-setleds-arguments} + + - `rgb_led_t *ledarray` + A pointer to the LED array. + - `uint16_t number_of_leds` + The length of the LED array. |