Application Note Using the Z32F128 Quadrature Encoder Peripheral AN039001-0616 Abstract This application note describes the Quadrature Encoder peripheral on the Z32F128 device and provides two examples of peripheral usage. The quadrature encoder decodes the signals received from an encoder and provides the information without CPU intervention. The Rotary Encoder example demonstrates the use of the encoder with a rotary dial (not a potentiometer), which is generally used with audio equipment. Rotary dials typically only use Phase A and Phase B lines with no index line. The Motor Encoder example demonstrates the use of the encoder with a motor to determine speed and direction. These encoders use Phase A, Phase B, and Phase Z (index) lines to provide the information. The positioning is automatically included in the peripheral, although not used in the example. Note: The source code file associated with this application note, AN0390-SC01, requires the ZNEO32! Pack file version 1.0.2 or higher and has been tested with Keil MDK version 5.x. (IAR Workbench and GNU for ARM Embedded toolchains to be added before release). Features The main features of the Quadrature Encoder Peripheral include: • Tracking encoder pulses to determine time, location, and direction • Calculating speed • No MCU intervention required (except to retrieve/calculate information) AN039001-0616 Page 1 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Discussion A rotary encoder is similar to a potentiometer in form factor, but without a rotational stop. Its shaft can be infinitely rotated in either a clockwise or counterclockwise direction. Typical applications of a rotary encoder include motor control, angular or position measurement, control knobs, and mechanical mice. There are several types of rotary encoders, the most common of which are classified as absolute and incremental rotary encoders. Other classifications are based on the number of pulses, positions or steps per revolution that a rotary encoder turns, its mechanical interface style, and sensors used inside the encoder. A number of rotary encoders include an integrated pushbutton switch which can be used to trigger an event when the shaft of the encoder is pressed. The two types of encoders can be defined as follows: • Absolute rotary encoder – An absolute rotary encoder produces a digital output value proportional to the angle of the shaft. • Incremental rotary encoder – An incremental rotary encoder produces two digital output signals. The phase relationship between these two signals determines whether the shaft of the encoder is rotating clockwise or counterclockwise. Inside the incremental rotary encoder are a slotted wheel and two pairs of LEDs and sensors, as shown in Figure 1 (Some encoders use capacitive sensors to track the transitions; however the same principle applies). When the shaft is rotated, the slotted wheel cuts the light from the LEDs and passes it to the sensors such that the sensors receive alternate ON and OFF light signals. AN039001-0616 Page 2 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Figure 1. Encoder Operation The sensors next produce an output signal, as shown in Figures 2 and 3. Figure 2 shows a clockwise direction, where Phase A leads Phase B. Figure 3 shows a counter-clockwise direction, where Phase B leads Phase A. Figure 2. Clockwise Signals from Encoder AN039001-0616 Page 3 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Figure 3. Counterclockwise Signals from Encoder Hardware Architecture The Z32F128 Quadrature Encoder peripheral receives pulses from the input of Timer 0 (Phase A), Timer 1 (Phase B), and Timer 2 (Phase Z) and processes the information to determine position and direction. Quadrature Encoder In Quadrature Encoder Mode, Timer 0–Timer 3 are used for the input signals, holding the counter information, and issuing the interrupts when necessary. Figure 4 shows the quadrature encoder counter block. Figure 4. Quadrature Encoder Counter Block The input for Timer 3 is not used. The position and revolution counters both use the Timer 2 input (as the Phase Z, or Index input). AN039001-0616 Page 4 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Figure 5. Quadrature Encoder Input Block The Phase A and Phase B inputs are controlled by the timer clock configuration (in the CR1 register). This provides multiple options for tracking the period for phase A and Phase B. By using the Capture clock, the timer can show just the pulses (useful for Rotary dials) or be configured to use the system clock to time each pulse. Phase Z uses the PCLK to detect edges but does not provide timing information. The Position (Timer 2) maintains the count of the pulses from Phase A/Phase B in relation to the Index pulse. By clearing the counter when the index pulse is received, the number of pulses per revolution can be AN039001-0616 Page 5 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note determined. The CNT register in Timer 2 contains the pulse count for the current revolution. If the Clear mode is set to none, the Timer 2 count will continue indefinitely. Figure 6. Timer 2 Position Block The Quadrature Encoder peripheral is very versatile and has multiple configuration options. The Quadrature Encoder controller register is called the Timer Group Encoder Control Register (TGECR). This register must be set up and put into the Quadrature Encoder mode before setting up Timers 0–3. The register contains the counter direction control. Each timer’s count can be affected by the direction control. The Revolution Counter (RDIRCON) refers to Timer 3, Position Counter (PDIRCON) refers to Timer 2, BDIRCON refers to Phase B, and ADIRCON refers to Phase A. If the bit is set, that register will add the count or subtract the count, depending on the direction of the rotation. All three inputs can be detected on the Rising or Falling edges. In addition, Phase A/Phase B can be detected on both edges (QDPHBEG, QDPHAEG, QDPHZEG). The ability to swap Phase A and Phase B signals (QDPHSWAP) also exists. The only bit that must be set to use the Quadrature Encoder is QDMOD. When this bit is set, Timer 0–Timer 3 are used for the Quadrature Decoder. After the Quadrature Encoder is configured and enabled, the Timers can be set up for retrieval of the required information. AN039001-0616 Page 6 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note TGECR Timer Group Encoder Control Register TGECR=0x4000_3140 AN039001-0616 PDIRCON 7 6 5 4 3 2 0 0 0 0 00 00 0 0 RW RW RW RW RW RW RW RW 11 RDIRCON 10 PDIRCON 9 BDIRCON 8 ADIRCON 7 6 QDPHBEG[1:0] 5 4 QDPHAEG[1:0] 3 QDPHZEG 2 QDPHSWAP 0 QDMOD 1 0 QDMOD 0 8 QDPHSWAP 0 9 QDPHZEG 0 10 QDPHAEG 0 11 QDPHBEG 12 ADIRCON 13 BDIRCON 14 RDIRCON 15 0 0 RW Revolution counter direction control 0 DIR status not affect to the counter 1 DIR status will change count direction Position counter direction control 0 DIR status not affect to the counter 1 DIR status will change count direction Phase B counter direction control 0 DIR status not affect to the counter 1 DIR status will change count direction Phase A counter direction control 0 DIR status not affect to the counter 1 DIR status will change count direction Quadrature mode phase B count for position count 00 Rising edge count 01 Falling edge count 1X Both edge count Quadrature mode phase A count for position count 00 Rising edge count 01 Falling edge count 1X Both edge count Quadrature mode phase Z count for revolution 0 PHZ rising edge count 1 PHZ falling edge count Quadrature input swap 0 No swap 1 Swap PHA and PHB Quadrature decoder mode 0 Normal timer mode 1 Quadrature decoder count mode Timer0 is phase A counter Timer1 is phase B counter Timer2 is position counter Timer3 is revolution counter Page 7 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note The Timer Operation in QMOD All timers being used for QMOD (Timer 0–3) must be in Capture Mode (Timer register CR1_MODE = 0x03) to be active in QMOD. If Timer 2 or Timer 3 is not necessary for the implementation, the timer can be used in any mode other than Capture. Timer 0 and Timer 1 (Phase A and Phase B) must be used in QMOD. All timers that are to be used must be enabled in the System Control Unit (SCU) peripheral and in CR2 (Timer register). Timer 0 and Timer 1 (Phase A, Phase B) The clock configuration (Timer register CR1_CKSEL) configures the timer clock to count. If the clock is set to TnC input, the counter will provide just the period count, whereas if the clock is set to a system clock, then the timing of the period is tracked. Typically, for a rotary dial, the time between pulses is not as important as the pulse count. Therefore, using the TnC input as the clock, the system can determine the rotation points. The Clear Mode bits of Timers 0 and 1 control when the timer counter is cleared. The options are: on the rising edge, falling edge, or both edges of the pulse received on the Timer 0,1 input pin. The timers can also be set to not clear the counter. The ADC Trigger can be activated so that when the pulse comes in, the ADC can be triggered to retrieve a reading. The Timer GRA register contains the pulse period count. If the timer clock is a system timer, this is the length of the period in clock ticks. If the timer clock is the input, this is the last count value. The Timer GRB register contains the previous GRA register value. The Phase A/Phase B interrupts are the direction change and revolution (phase period). The status register of Timer 0/1 also provides the direction bit. The normal timer interrupts such as overflow are also available for when the timer count exceeds the 16-bit value. Timer 2 Position Timer (Phase Z) The Position timer uses the system clock to check the edge for the pulse. The Timer Clock configuration is not used, since this timer counts the pulses from Timer 0/Timer 1 while waiting for an index (mechanical revolution) pulse. Note: Some encoders provide multiple index counts per mechanical revolution, so this must be taken into account. The Timer GRB register contains the number of pulses received between revolutions. If the clear mode is set to none, it will continue to increase and rollover; otherwise, it will provide the number of pulses per index pulse. The only interrupts this timer will receive are the Quadrature Revolution Flag, MFA (Match A Flag bit), MFB (Match B Flag bit) and Overflow. AN039001-0616 Page 8 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Note: Since this captures both A and B, the position counter is incremented once for Phase A and once for Phase B. Timer 3 Mechanical Revolution Counter Timer 3 counts the index pulses (mechanical revolutions). GRA and GRB Timer registers are not used. The only interrupts this timer will receive are the Quadrature Revolution Flag, MFA, MFB, and Overflow. Software Implementation The firmware examples in this application note are built on the Z32F128 Development board. Two examples are provided, as described in this section. Rotary Dial Encoder Keeps track of a rotary encoder position and direction, using an incremental Rotary Encoder from Bournes, part number PEC12R-4230F-S0024. This is a 24 position Rotary Dial. Motor Encoder Keeps track of motor speed and direction using an incremental encoder from CUI Inc, part number AMT103-V. Both examples use the same main.c file. This implements the main()function that initializes the system clock and calls the QECODE_Init() function to initialize and start the Quadrature Encoder peripheral. The main function then goes into an infinite loop doing nothing. The only difference between the two is the include file for the header. This is handled by the variable _USE_ROTARY_DIAL_, which is defined in the project settings. AN0390-SC01 Directory Structure AN0390-SC01 – – – – – – AN039001-0616 ARM – Contains project and supporting files for Keil MDK µVision EWARM – Contains project and supporting files for IAR Workbench GCC – Contains makefile and supporting files for GNU for ARM embedded inc –Include files for project src – Source files for project AN0390.txt – Abstract for AN0390 firmware Page 9 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Rotary Dial Encoder The Rotary Dial encoder uses the rotaryquad.c file. This file can be configured for two options. One option is to use a variable to hold the position of the dial, while the other option is to use the Timer 0 CNT to maintain the position. This is controlled by the _USE_T0CNT_COUNTER_ defined variable in the rotaryquad.c header file. The QECODE_Init() function does the majority of the work. This sets up the TGECR register then the timers, interrupt, and starts the timers. The interrupt simply keeps the counter in the 24 position range. To access or view the current position, retrieve either the Timer 0 CNT register or the RDPosition variable values. QECODE_Init() The timer input pins being used are Phase A (Timer 0 in: PD10) and Phase B (Timer 1 in: PD11). First, ensure Timers 0–1 and Port D are enabled in the SCU, using the PER1 register. The PCER1 register turns on the Clock for Port D and the Timers. Next, set up Port D. The timer input pins are set to alternative function 2, with input direction. The port must be enabled for writing through the PORTEN register. To ensure the pins are safe, the CR (direction) is configured first. The direction is a 2-bit value, so the bits must be cleared. A temporary variable is used so the port never goes to output, which could cause a problem. The bits for pins PD10 and PD11 are set to input (0x2). The MR register is handled in the same manner. The temporary variable is used to make sure the MR register never goes to a bad state that could cause any problems. Again, the bits for pins PD10 and PD11 are set to alternative function 2 (0x2). The PORTEN register is cleared to lock the pins. The TGECR register must be set up before the timers, so it is handled next. The QDMOD bit is set to enable the Quadrature Mode and the ADIRCON bit is set to change the count direction when the direction bit is set. This means the count will add the count in one direction but subtract the count in the other, which allows the rotary dial position to be easily identified. Phase A and Phase B are both configured in Capture Mode, with the clock selected being the Capture Input. This provides a single count for every rotation of a phase (either positive or negative, since the ADIRCON bit is set). If the T0 CNT register is used for the position indicator, the clear mode is set to none, so it does not get cleared on completion of the period. Otherwise, the clear mode is set to clear on falling edge (0x01), always giving a 1 or -1 for every rotation (phase period). Timer 0 (Phase A) controls the action, so that the timer will respond to the Rotation Interrupt. The Change Direction interrupt is not necessary for this implementation because the Quadrature Encoder handles the direction change via counting changes because of the ADIRCON bit being set. The Interrupt for TIMER0 is enabled in the NVIC. AN039001-0616 Page 10 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note The two timers are cleared and enabled, so the quadrature encoder now captures and handles the data. Timer0_IRQHandler() There are two implementations, both doing basically the same thing. When the Timer0 CNT register contains the position, the only work required is to make sure the count does not exceed the maximum position or is less than the minimum position. MAXPOS, MINPOS defines these values. Currently, it is set up for 24 and the code wraps the counter. This means that when the counter exceeds MAXPOS, the counter is cleared and if the count equals MINPOS, the count is set to MAXPOS and continues counting. This allows a rotary dial of any position count to be used for multi-turn counting. There can be multiple interrupts and the count is only to be checked after it has changed. When the MFA flag is set, the count has been completed for this revolution which is when the count is actually being checked. When the Dial position is contained in a variable, the interrupt handling is almost the same. When the interrupt is entered, the Status Register (SR) contains the reason for the interrupt(s). The MFA register is only updated when the MFA bit is set. However, there can be multiple interrupts, depending on when the interrupt condition is detected. To account for this, the Status Register is checked for the MFA flag and if it is set, the MFA value is added to the RDPosition variable. The RDPosition variable is then checked for Minimum and Maximum values and wraps. Because the MFA value may be higher than one (if an interrupt was missed for some reason), the MAXPOS is either added or subtracted from the value, to keep it within the desired range. Motor Encoder The Motor Encoder uses the quadencode.c file for quadrature encoder handling. Similar to the Rotary Dial encoder, the main work occurs in the QENCODE_Init() function to set up the peripheral. After it is set up, the interrupt on the index line calculates the speed. QECODE_Init() The Timer input pins used are Phase A (Timer 0 in: PD10), Phase B (Timer 1 in: PD11), Phase Z (Timer 2 in: PD12). Timer 3 is used for the revolution counter with no input. First, ensure Timers 0–3 and Port D are enabled in the SCU, using the PER1 register. The PCER1 register turns on the clock for Port D and the timers. Next, set up Port D. The timer input pins are set to alternative function 2, with input direction. The port must be enabled for writing through the PORTEN register. To ensure the pins are safe, the CR (direction) is configured first. The direction is a 2-bit value, so the bits must be cleared. A temporary variable is used so the port never goes to output, which could cause a problem. The bits for pins PD10, PD11, and PD12 are set to input (0x2). AN039001-0616 Page 11 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note The MR register is handled in the same manner. The temporary variable is used to ensure the MR register never goes to a bad state that could cause any problems. Again, the bits for pins PD10, PD11, and PD12 are set to alternative function 2 (0x2). The PORTEN register is cleared to lock the pins. The TGECR register must be set up before the timers, so it is handled next. The QDMOD bit is set to enable the Quadrature Mode. No other bits need to be set, since the count direction will not be changed. All four timers are configured in Capture Mode. Timer 0 and Timer 1 (Phase A/B) are set with the clock selected being PCLK/4 (giving a 18 Mhz timer clock). This provides the timer for determining the speed of the motor. Timer 0 is configured with no clear mode. Timer 0 is only a 16-bit timer; however, to calculate the speed, the timer count needs to be a 32-bit number. To accomplish this, the overflow interrupt is requested and upon receiving an interrupt, a variable is incremented, providing the high word of the 32-bit count. All other timers are configured for a clear mode, either rising or falling edges. Timer 3 is configured to enable the QRIE interrupt to receive the Revolution interrupt, providing speed calculation on every mechanical revolution. The interrupts for TIMER0 and TIMER3 are enabled in the NVIC. All four timers are cleared and enabled, so the quadrature encoder is fully functional. Timer0_IRQHandler() The only processing needed in the Timer 0 handling is to increment the Overflow variable, providing the high word of the 32-bit count. The direction bit is only active on Phase A and Phase B interrupts, so the Dir variable is also updated on every overflow interrupt. This prevents the Dir variable from being updated as soon as the motor changes direction but the few milliseconds between the overflow interrupt do not cause any issues. Timer3_IRQHandler() The Timer 3 interrupt occurs on every index pulse (revolution). When an index pulse is received, with the encoder that is being used, the index is the mechanical revolution. On every mechanical revolution, the speed is calculated using the following equation: (Timer clock * 60) / ticks between revolutions The result is Revolutions Per Minute. The ticks between revolutions is held in the overflow variable (high word of the 32-bit value) and the Timer 0 CNT register (low word of the 32- bit value). Timer clock is a macro, which is defined based on the clock selected in the CR1 register of Timer 0. After the value has been retrieved, the Timer 0 CNT register and the Overflow variable are cleared for the next revolution. Another way to calculate speed is to set Timer 0 to Clear mode on falling edge and in Timer 0 interrupt: (Timer Clock * 60) / ( (Timer 0 GRA register) * (Timer 2 GRB register) ) Timer 0 GRA contains the time for the Phase A period and Timer 2 GRB is the total phase periods (both A and B) in a revolution. AN039001-0616 Page 12 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Note: If Clear Mode is on both edges, then the Timer 2 GRB register should be divided by 2, because the Period in A is only half the cycle (both rising and following edges clear). Note: The developer can determine how to implement the data from the quadrature encoder. This application note only demonstrates the use of the peripheral. AN039001-0616 Page 13 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Testing Procedure The application can be tested using the toolchain of choice. The example shown here is for the Keil MDK, however, other toolchains will be similar. Motor Encoder 1. Connect the Phase A (PD10), Phase B (PD11), Index (PD12), Ground (GND), and VCC (VCC) lines to the encoder and the development board. Figure 7. Example of Connection 2. Load the AN0390.uvmpw project in the μVision IDE. 3. Right click the AN0390_Encoder project in the project window and select Set as Active Project. 4. Select Project → Rebuild from the menu. When building is complete, there should be 0 errors and 0 warnings. 5. Select Debug → Start/Stop Debug Session from the menu. 6. Verify the Watch window is open and both dir and speed variables are being watched. 7. Select Debug → Run from the menu (or press F5) to start the application. With the motor running, the speed variable being watched will be updated with the speed and the direction will show a 1 for clockwise, 0 for counter clockwise. AN039001-0616 Page 14 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Figure 8. Motor Control Speed and Direction Output Rotary Encoder The Rotary Encoder being used requires a filter circuit to operate correctly (from the encoder datasheet). Figure 9 shows the filter circuit diagram. AN039001-0616 Page 15 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Figure 9. Filter Circuit Diagram Terminal A is wired to Phase A (PD10) and Terminal B is wired to Phase B (PD11). 1. Connect the Terminal A (PD10), Terminal B (PD11), Ground (GND), and VCC (VCC) lines from the filter to the development board. AN039001-0616 Page 16 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Figure 10. Example of Connection 2. Load the AN0390.uvmpw project in the µVision IDE. 3. Right click the AN0390_Rotary project in the project window and select Set as Active Project. If compiling for using the Timer CNT register as the position indicator, open the rotaryquad.h file and change _USE_T0CNT_COUNTER_ to ‘1’. 4. Select Project → Rebuild from the menu. When building is complete, there should be 0 errors and 0 warnings. 5. Select Debug → Start/Stop Debug Session from the menu. 6. If using the Timer CNT register as the position indicator and T0 is not already displayed, select Peripherals → System Viewer → Timer → T0. This will show the T0 registers. The CNT register contains the position of the rotary dial. AN039001-0616 Page 17 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Figure 11. Dial Position Output Using the Timer CNT Register If not using the Timer CNT register, verify the Watch window is open and the RDPosition variable is being watched. AN039001-0616 Page 18 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Figure 12. Rotary Dial Position Output Using the RDPosition Variable AN039001-0616 Page 19 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Equipment Used The following equipment is required for the firmware used in this application note: • Z32F128 Development Kit • Cortex M3 Toolchain, such as the Keil MDK toolchain • Incremental Encoder with index, such as CUI AMT103-V • Incremental Rotary Encoder, such as Bournes PEC12R-4030F-S0024 • Breadboard for Rotary Encoder filter, with four 10 K resistors, two 0.01 uF capacitors Summary The Quadrature Encoder Peripheral on the Z32F128 MCU is extremely versatile and can accommodate many usage models. This application note shows only two basic operations; however, there are several other combinations that can be used. The peripheral performs the work without MCU intervention, freeing up the MCU to perform additional activities. References Documents associated with this application note are listed below. Each of these documents can be obtained from the Zilog website by clicking the link associated with its document number where indicated • Z32F128 MCU Product Specification (PS0345) • Z32F128 Evaluation Kit User Manual (UM0277) • ZNEO32! CMSIS Pack File (Version 1.0.2 or higher) AN039001-0616 Page 20 of 21 Using the Z32F128 Quadrature Encoder Peripheral Application Note Customer Support To share comments, get your technical questions answered, or report issues you may be experiencing with our products, please visit Zilog’s Technical Support page at http://support.zilog.com. To learn more about this product, find additional documentation, or to discover other facets about Zilog product offerings, please visit the Zilog Knowledge Base at http:// zilog.com/kb or consider participating in the Zilog Forum at http://zilog.com/forum. This publication is subject to replacement by a later edition. To determine whether a later edition exists, please visit the Zilog website at http://www.zilog.com. Warning: DO NOT USE THIS PRODUCT IN LIFE SUPPORT SYSTEMS. LIFE SUPPORT POLICY ZILOG’S PRODUCTS ARE NOT AUTHORIZED FOR USE AS CRITICAL COMPONENTS IN LIFE SUPPORT DEVICES OR SYSTEMS WITHOUT THE EXPRESS PRIOR WRITTEN APPROVAL OF THE PRESIDENT AND GENERAL COUNSEL OF ZILOG CORPORATION. As used herein Life support devices or systems are devices which (a) are intended for surgical implant into the body, or (b) support or sustain life and whose failure to perform when properly used in accordance with instructions for use provided in the labeling can be reasonably expected to result in a significant injury to the user. A critical component is any component in a life support device or system whose failure to perform can be reasonably expected to cause the failure of the life support device or system or to affect its safety or effectiveness. Document Disclaimer ©2016 Zilog, Inc. All rights reserved. Information in this publication concerning the devices, applications, or technology described is intended to suggest possible uses and may be superseded. ZILOG, INC. DOES NOT ASSUME LIABILITY FOR OR PROVIDE A REPRESENTATION OF ACCURACY OF THE INFORMATION, DEVICES, OR TECHNOLOGY DESCRIBED IN THIS DOCUMENT. ZILOG ALSO DOES NOT ASSUME LIABILITY FOR INTELLECTUAL PROPERTY INFRINGEMENT RELATED IN ANY MANNER TO USE OF INFORMATION, DEVICES, OR TECHNOLOGY DESCRIBED HEREIN OR OTHERWISE. The information contained within this document has been verified according to the general principles of electrical and mechanical engineering. ZNEO32! and Z32F128 are trademarks or registered trademarks of Zilog, Inc. All other product or service names are the property of their respective owners. AN039001-0616 Page 21 of 21