aboutsummaryrefslogtreecommitdiffstats
path: root/docs/adc_driver.md
blob: f8fb94094e84b4a5ef6aded94712dd06f58bdb67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# 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](feature_encoders.md).

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
SRC += analog.c
```

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

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-based, whereas the F3 has 4 ADCs and the channels are 1 based. This is because the F0 uses the `ADCv1` implementation of the ADC, whereas the F3 uses the `ADCv3` implementation.

|ADC|Channel|STM32F0XX|STM32F3XX|
|---|-------|---------|---------|
|1  |0      |`A0`     |         |
|1  |1      |`A1`     |`A0`     |
|1  |2      |`A2`     |`A1`     |
|1  |3      |`A3`     |`A2`     |
|1  |4      |`A4`     |`A3`     |
|1  |5      |`A5`     |`F4`     |
|1  |6      |`A6`     |`C0`     |
|1  |7      |`A7`     |`C1`     |
|1  |8      |`B0`     |`C2`     |
|1  |9      |`B1`     |`C3`     |
|1  |10     |`C0`     |`F2`     |
|1  |11     |`C1`     |         |
|1  |12     |`C2`     |         |
|1  |13     |`C3`     |         |
|1  |14     |`C4`     |         |
|1  |15     |`C5`     |         |
|1  |16     |         |         |
|2  |1      |         |`A4`     |
|2  |2      |         |`A5`     |
|2  |3      |         |`A6`     |
|2  |4      |         |`A7`     |
|2  |5      |         |`C4`     |
|2  |6      |         |`C0`     |
|2  |7      |         |`C1`     |
|2  |8      |         |`C2`     |
|2  |9      |         |`C3`     |
|2  |10     |         |`F2`     |
|2  |11     |         |`C5`     |
|2  |12     |         |`B2`     |
|2  |13     |         |         |
|2  |14     |         |         |
|2  |15     |         |         |
|2  |16     |         |         |
|3  |1      |         |`B1`     |
|3  |2      |         |`E9`     |
|3  |3      |         |`E13`    |
|3  |4      |         |         |
|3  |5      |         |         |
|3  |6      |         |`E8`     |
|3  |7      |         |`D10`    |
|3  |8      |         |`D11`    |
|3  |9      |         |`D12`    |
|3  |10     |         |`D13`    |
|3  |11     |         |`D14`    |
|3  |12     |         |`B0`     |
|3  |13     |         |`E7`     |
|3  |14     |         |`E10`    |
|3  |15     |         |`E11`    |
|3  |16     |         |`E12`    |
|4  |1      |         |`E14`    |
|4  |2      |         |`B12`    |
|4  |3      |         |`B13`    |
|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     |         |         |

## 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`.|
|`analogRead(pin)`           |Reads the value from the specified Arduino pin, eg. `4` for ADC6 on the ATmega32U4.                                |
|`analogReadPin(pin)`        |Reads the value from the specified QMK pin, eg. `F6` for ADC6 on the ATmega32U4.                                   |
|`pinToMux(pin)`             |Translates a given QMK 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

Note that care was taken to match all of the functions used for AVR devices, however complications in the ARM platform prevent that from always being possible. For example, the `STM32` chips do not have assigned Arduino pins. We could use the default pin numbers, but those numbers change based on the package type of the device. For this reason, please specify your target pins with their identifiers (`A0`, `F3`, etc.). Also note that there are some variants of functions that accept the target ADC for the pin. Some pins can be used for multiple ADCs, and this specified can help you pick which ADC will be used to interact with that pin.

|Function                    |Description                                                                                                         |
|----------------------------|--------------------------------------------------------------------------------------------------------------------|
|`analogReadPin(pin)`        |Reads the value from the specified QMK 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 QMK 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 QMK 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.

|`#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 12-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 a 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. Please consult the corresponding `hal_adc_lld.h` in ChibiOS for your specific microcontroller for further documentation on your available options.|
|ADC_RESOLUTION     |`int` |`ADC_CFGR1_RES_12BIT`|The resolution of your result. We choose 12 bit by default, but you can opt for 12, 10, 8, or 6 bit. Please consult the corresponding `hal_adc_lld.h` in ChibiOS for your specific microcontroller for further documentation on your available options.|
o hold the sense data for the last issued SCSI command, which is returned to the host after a SCSI REQUEST SENSE * command is issued. This gives information on exactly why the last command failed to complete. */ static SCSI_Request_Sense_Response_t SenseData = { .ResponseCode = 0x70, .AdditionalLength = 0x0A, }; /** Main routine to process the SCSI command located in the Command Block Wrapper read from the host. This dispatches * to the appropriate SCSI command handling routine if the issued command is supported by the device, else it returns * a command failure due to a ILLEGAL REQUEST. * * \return Boolean true if the command completed successfully, false otherwise */ bool SCSI_DecodeSCSICommand(void) { bool CommandSuccess = false; /* Run the appropriate SCSI command hander function based on the passed command */ switch (CommandBlock.SCSICommandData[0]) { case SCSI_CMD_INQUIRY: CommandSuccess = SCSI_Command_Inquiry(); break; case SCSI_CMD_REQUEST_SENSE: CommandSuccess = SCSI_Command_Request_Sense(); break; case SCSI_CMD_READ_CAPACITY_10: CommandSuccess = SCSI_Command_Read_Capacity_10(); break; case SCSI_CMD_SEND_DIAGNOSTIC: CommandSuccess = SCSI_Command_Send_Diagnostic(); break; case SCSI_CMD_WRITE_10: CommandSuccess = SCSI_Command_ReadWrite_10(DATA_WRITE); break; case SCSI_CMD_READ_10: CommandSuccess = SCSI_Command_ReadWrite_10(DATA_READ); break; case SCSI_CMD_MODE_SENSE_6: CommandSuccess = SCSI_Command_ModeSense_6(); break; case SCSI_CMD_TEST_UNIT_READY: case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: case SCSI_CMD_VERIFY_10: /* These commands should just succeed, no handling required */ CommandSuccess = true; CommandBlock.DataTransferLength = 0; break; default: /* Update the SENSE key to reflect the invalid command */ SCSI_SET_SENSE(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_ASENSE_INVALID_COMMAND, SCSI_ASENSEQ_NO_QUALIFIER); break; } /* Check if command was successfully processed */ if (CommandSuccess) { SCSI_SET_SENSE(SCSI_SENSE_KEY_GOOD, SCSI_ASENSE_NO_ADDITIONAL_INFORMATION, SCSI_ASENSEQ_NO_QUALIFIER); return true; } return false; } /** Command processing for an issued SCSI INQUIRY command. This command returns information about the device's features * and capabilities to the host. * * \return Boolean true if the command completed successfully, false otherwise. */ static bool SCSI_Command_Inquiry(void) { uint16_t AllocationLength = SwapEndian_16(*(uint16_t*)&CommandBlock.SCSICommandData[3]); uint16_t BytesTransferred = MIN(AllocationLength, sizeof(InquiryData)); /* Only the standard INQUIRY data is supported, check if any optional INQUIRY bits set */ if ((CommandBlock.SCSICommandData[1] & ((1 << 0) | (1 << 1))) || CommandBlock.SCSICommandData[2]) { /* Optional but unsupported bits set - update the SENSE key and fail the request */ SCSI_SET_SENSE(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_ASENSE_INVALID_FIELD_IN_CDB, SCSI_ASENSEQ_NO_QUALIFIER); return false; } /* Write the INQUIRY data to the endpoint */ Endpoint_Write_Stream_LE(&InquiryData, BytesTransferred, NULL); /* Pad out remaining bytes with 0x00 */ Endpoint_Null_Stream((AllocationLength - BytesTransferred), NULL); /* Finalize the stream transfer to send the last packet */ Endpoint_ClearIN(); /* Succeed the command and update the bytes transferred counter */ CommandBlock.DataTransferLength -= BytesTransferred; return true; } /** Command processing for an issued SCSI REQUEST SENSE command. This command returns information about the last issued command, * including the error code and additional error information so that the host can determine why a command failed to complete. * * \return Boolean true if the command completed successfully, false otherwise. */ static bool SCSI_Command_Request_Sense(void) { uint8_t AllocationLength = CommandBlock.SCSICommandData[4]; uint8_t BytesTransferred = MIN(AllocationLength, sizeof(SenseData)); /* Send the SENSE data - this indicates to the host the status of the last command */ Endpoint_Write_Stream_LE(&SenseData, BytesTransferred, NULL); /* Pad out remaining bytes with 0x00 */ Endpoint_Null_Stream((AllocationLength - BytesTransferred), NULL); /* Finalize the stream transfer to send the last packet */ Endpoint_ClearIN(); /* Succeed the command and update the bytes transferred counter */ CommandBlock.DataTransferLength -= BytesTransferred; return true; } /** Command processing for an issued SCSI READ CAPACITY (10) command. This command returns information about the device's capacity * on the selected Logical Unit (drive), as a number of OS-sized blocks. * * \return Boolean true if the command completed successfully, false otherwise. */ static bool SCSI_Command_Read_Capacity_10(void) { /* Send the total number of logical blocks in the current LUN */ Endpoint_Write_32_BE(LUN_MEDIA_BLOCKS - 1); /* Send the logical block size of the device (must be 512 bytes) */ Endpoint_Write_32_BE(VIRTUAL_MEMORY_BLOCK_SIZE); /* Check if the current command is being aborted by the host */ if (IsMassStoreReset) return false; /* Send the endpoint data packet to the host */ Endpoint_ClearIN(); /* Succeed the command and update the bytes transferred counter */ CommandBlock.DataTransferLength -= 8; return true; } /** Command processing for an issued SCSI SEND DIAGNOSTIC command. This command performs a quick check of the Dataflash ICs on the * board, and indicates if they are present and functioning correctly. Only the Self-Test portion of the diagnostic command is * supported. * * \return Boolean true if the command completed successfully, false otherwise. */ static bool SCSI_Command_Send_Diagnostic(void) { /* Check to see if the SELF TEST bit is not set */ if (!(CommandBlock.SCSICommandData[1] & (1 << 2))) { /* Only self-test supported - update SENSE key and fail the command */ SCSI_SET_SENSE(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_ASENSE_INVALID_FIELD_IN_CDB, SCSI_ASENSEQ_NO_QUALIFIER); return false; } /* Check to see if all attached Dataflash ICs are functional */ if (!(DataflashManager_CheckDataflashOperation())) { /* Update SENSE key with a hardware error condition and return command fail */ SCSI_SET_SENSE(SCSI_SENSE_KEY_HARDWARE_ERROR, SCSI_ASENSE_NO_ADDITIONAL_INFORMATION, SCSI_ASENSEQ_NO_QUALIFIER); return false; } /* Succeed the command and update the bytes transferred counter */ CommandBlock.DataTransferLength = 0; return true; } /** Command processing for an issued SCSI READ (10) or WRITE (10) command. This command reads in the block start address * and total number of blocks to process, then calls the appropriate low-level Dataflash routine to handle the actual * reading and writing of the data. * * \param[in] IsDataRead Indicates if the command is a READ (10) command or WRITE (10) command (DATA_READ or DATA_WRITE) * * \return Boolean true if the command completed successfully, false otherwise. */ static bool SCSI_Command_ReadWrite_10(const bool IsDataRead) { uint32_t BlockAddress; uint16_t TotalBlocks; /* Check if the disk is write protected or not */ if ((IsDataRead == DATA_WRITE) && DISK_READ_ONLY) { /* Block address is invalid, update SENSE key and return command fail */ SCSI_SET_SENSE(SCSI_SENSE_KEY_DATA_PROTECT, SCSI_ASENSE_WRITE_PROTECTED, SCSI_ASENSEQ_NO_QUALIFIER); return false; } BlockAddress = SwapEndian_32(*(uint32_t*)&CommandBlock.SCSICommandData[2]); TotalBlocks = SwapEndian_16(*(uint16_t*)&CommandBlock.SCSICommandData[7]); /* Check if the block address is outside the maximum allowable value for the LUN */ if (BlockAddress >= LUN_MEDIA_BLOCKS) { /* Block address is invalid, update SENSE key and return command fail */ SCSI_SET_SENSE(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_ASENSE_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE, SCSI_ASENSEQ_NO_QUALIFIER); return false; } #if (TOTAL_LUNS > 1) /* Adjust the given block address to the real media address based on the selected LUN */ BlockAddress += ((uint32_t)CommandBlock.LUN * LUN_MEDIA_BLOCKS); #endif /* Determine if the packet is a READ (10) or WRITE (10) command, call appropriate function */ if (IsDataRead == DATA_READ) DataflashManager_ReadBlocks(BlockAddress, TotalBlocks); else DataflashManager_WriteBlocks(BlockAddress, TotalBlocks); /* Update the bytes transferred counter and succeed the command */ CommandBlock.DataTransferLength -= ((uint32_t)TotalBlocks * VIRTUAL_MEMORY_BLOCK_SIZE); return true; } /** Command processing for an issued SCSI MODE SENSE (6) command. This command returns various informational pages about * the SCSI device, as well as the device's Write Protect status. * * \return Boolean true if the command completed successfully, false otherwise. */ static bool SCSI_Command_ModeSense_6(void) { /* Send an empty header response with the Write Protect flag status */ Endpoint_Write_8(0x00); Endpoint_Write_8(0x00); Endpoint_Write_8(DISK_READ_ONLY ? 0x80 : 0x00); Endpoint_Write_8(0x00); Endpoint_ClearIN(); /* Update the bytes transferred counter and succeed the command */ CommandBlock.DataTransferLength -= 4; return true; }