Lecture 18 - Programming the A/D converter 10:10 What we have discussed so far - A/D and D/A conversion formulas - discuss handout with graph - D/A design with R-2R ladder - A/D design This lecture, we are going to focus on developing software for the A/D We will do this by discussing several examples As usual, we are going to make use of the driverlib. The AD converter as a whole is quite complex. Therefore, we will make use of a checklist to verify that we have programmed everything completely. Let us start with the synoptic view of the A/D converter, and enumerate some of the relevant design parameters involved in the use of an ADC. 10:12 Design Decisions for the ADC Simplified Block Diagram Conversion Clock INPUT 0 -|\ | +-------+ INPUT 1 -| \ V | MEM0 | INPUT 2 -| +---> S/H ---> SAR --->| ... | ... | / ^ ^ | MEM31 | INPUT 31 -|/ +---+----+ +-------+ | Analog Mux | Memory Buffer Trigger The following design decisions have to be made, in the process of using the AD converter. Step 1. Configure an IC pin to operate as an analog input channel - The peripherals of boostxl are connected to a specific MSP432 IC pin - That specific MSP432 IC pin must operate as an analog input - That specific MSP432 IC pin also determines which ADC channel should be used Step 2. Configure one or more ADC input channels to be used - When a single channel is used, the ADC will work in 'single-channel' mode - When multiple channels are used, the ADC will work in 'multi-channel' mode The idea of multi-channel mode is that the ADC will alternate between different IC pins while doing A/D conversions. So, while no two analog input channels can be converted exactly at the same time, they will be sampled one after another. Step 3. Configure the output memory buffer location that will be used during the conversion. Each input channel can be mapped to a specific memory buffer location. This is user-defined, and can be anything you want. For example Channel 9 -> ADC MEM0 Channel 15 -> ADC MEM1 and so on. The idea is that a programmer can place the samples in a 'logical' order, independent of how the hardware channels are physically configured. Step 4. Decide on the conversion repetition strategy A full ADC conversion works through the following steps: a. The ADC is 'enabled'. This means that the A/D hardware is powered up. b. The ADC is 'triggered'. This means that the A/D hardware receives a go-ahead to start a conversion. c. The ADC selects a channel and runs the sample-and-hold to read the input voltage Vin from that channel. d. The ADC runs the SAR converter to convert the sampled voltage to a digital code D. The user has control over the sequence a->b->c->d in several different ways, as discussed here (4.) and under the next point (5.) A major design decision is to select what happens after d completes. In 'single-conversion' mode, the ADC goes from a->b->c->d and then stops, so that the next conversion again needs to start from a. In 'repeat conversion' mode, the ADC goes from a->b->c->d, and then cycles in between b->c->d for each conversion. Once it is enabled, it remains powered up. After every conversion (d), it waits for the next trigger (b). The transition c->d is under user control. In 'pulse sample mode', the length of c is automatically determined by the ADC. That is the mode we will use for examples. In 'extended sample mode', the user can control the sample-time, that is, the length of c. Step 5. Decide on the trigger conversion strategy. Every single conversion requires a trigger signal. A 'trigger' decides when an ADC conversion is initiated. The trigger thus decides when the transition b->c (as defined under 4) is made. The trigger is important because (as we will discuss later) the sample instant determines the spectral (frequency) properties of the sampled-data signal. For digital signal processing applications, we want to precisely control the trigger instances. For example, if we want to take samples at 10 KHz, then we need a periodic trigger signal of 100 microseconds. The ADC allows to select triggers from various sources, including timers. In the examples, we will make use of two possible trigger sources: software, or the completion of the previous conversion. In the first case (software triggering), the ADC waits for software to tell it to start the conversion. In the second case (after completion of the previous conversion), the ADC will run as fast as possible, since it will trigger itself as soon as it's possible to do so. Note that the trigger timing (ie. the decision WHEN a conversion starts), is independent of the 'single-conversion' or 'repeat conversion' mode. Every conversion requires a trigger. Step 6. Initialize the A/D Finally, there are additional design decisions, such as the conversion clock (the speed at which the SAR operates), the resolution (14 bit or less), and the output format (signed/unsigned). These parameters are provided during initialization of the ADC. 10:30 Driverlib calls We next discuss the major driverlib calls. We will do this by means of a joystick application. Step 1. Configure an IC pin to operate as an analog input channel ================================================================= Last lecture, we already discussed how to find the connectivity from the joystick terminal (X or Y) to the A/D channel. It involves the following: - Find what header pin on BOOSTXL is used for the joystick terminal - Find the GPIO pin on the corresponding header pin on Launchpad - Find the Analog input channel used for this GPIO pin from MSP432 data sheet We found as an example: Joystick X -> J1.2 -> P6.0 -> A15 ------ ----------- header msp432p4 We can do a similar exercise for the Y channel Joystick Y -> J3.26 -> P4.4 -> A9 ------ ---------- header msp432p4 The call that helps us to configure the pin correctly is GPIO_setAsPeripheralModuleFunctionInputPin To set P6.0 to work as an analog input, we use: GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P6, GPIO_PIN0, GPIO_PRIMARY_MODULE_FUNCTION); To set P4.4 to work as an analog input, we use: GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P4, GPIO_PIN4, GPIO_TERTIARY_MODULE_FUNCTION); Notice that the first call says 'GPIO_PRIMARY_MODULE_FUNCTION' while the second call says 'GPIO_TERTIARY_MODULE_FUNCTION'. To understand why that is the case, you have to consult the chip package in the MSP432P4 datasheet. The pin label for P6.0 is: P6.0/A15 -> A15 is first label after P6.0 The pin label for P4.4 is: P4.4/HSMCLK/SVMHOUT/A9 -> A9 is third label after P4.4 Step 2. Configure one or more ADC input channels to be used ===================================================================== Because we are using two input channels at the same time, we will have to use the ADC in 'multi-channel' mode. There is a single driverlib call that solves this step: ADC14_configureMultiSequenceMode(ADC_MEM0, ADC_MEM1, false); // => repeat mode This selects the ADC to operate in sequence mode, where it will fill up samples in memory ADC_MEMO and ADC_MEM1. It doesn't say exactly what channel is connected to each memory buffer, that connection is made in the following step. This driverlib call also allows to choose beween 'repeat mode' (true) and 'single-conversion mode' false (which was discussed in step 4 above). In case we are only interested in single-conversion mode, there is a different driverlib call that should be used. ADC14_configureSingleSampleMode ( uint32_t memoryDestination, bool repeatMode ) Step 3. Configure the output memory buffer location that will be used ===================================================================== For every buffer location, we now have to tell what channel is connected to it. This is done using ADC14_configureConversionMemory. This configuration also enables the selection of Vref+ and VREF-, as well as single-ended input mode vs differential-input mode. Here is the configuration for the X, Y axis of the joystick: ADC14_configureConversionMemory(ADC_MEM0, ADC_VREFPOS_AVCC_VREFNEG_VSS, ADC_INPUT_A15, // joystick X ADC_NONDIFFERENTIAL_INPUTS); ADC14_configureConversionMemory(ADC_MEM1, ADC_VREFPOS_AVCC_VREFNEG_VSS, ADC_INPUT_A9, // joystick Y ADC_NONDIFFERENTIAL_INPUTS); Step 4. Decide on the conversion repetition strategy ==================================================== We have already done this, with the ADC_configureMultiSequenceMode and ADC14_configureSingleSampleMode Step 5. Decide on the trigger conversion strategy. ==================================================== In this initial example, we keep the trigger under control of the software. This is called 'manual mode' in driverlib. The call ADC14_enableSampleTimer allows a user to select the trigger conversion strategy. ADC14_enableSampleTimer(ADC_MANUAL_ITERATION); Step 6. Initialize the A/D ========================== Finally, we initialize the A/D using the ADC14_initModule call. This call selects the conversion clock. ADC14_initModule(ADC_CLOCKSOURCE_ADCOSC, ADC_PREDIVIDER_1, ADC_DIVIDER_1, 0 ); Doing actual conversions ======================== Once the A/D is set up as above, actual conversions can be completed as follows. First, we enable the A/D and trigger the conversion. ADC14_enableConversion(); ADC14_toggleConversionTrigger(); Next, we wait until the conversion is complete while (ADC14_isBusy()) ; Finally, you read the result back from ADC memory *X = ADC14_getResult(ADC_MEM0); *Y = ADC14_getResult(ADC_MEM1); 10:45 Organizing everything in a HAL The 6 steps above are the logical sequence of design. Of course, you would design a HAL for the ADC that is convenient to use in your cyclic executive. In this case, we use the ADC in manual mode, for two channel at the same time. Here is the HAL we use for it. //----------------------------------------------------------- // initADC is called to initialize the A/D, set up the memory // configuration and the trigger conversion void initADC() { ADC14_enableModule(); // This sets the conversion clock to 3MHz ADC14_initModule(ADC_CLOCKSOURCE_ADCOSC, ADC_PREDIVIDER_1, ADC_DIVIDER_1, 0 ); // This configures the ADC to store output results // in ADC_MEM0 up to ADC_MEM1. Each conversion will // thus use two channels. ADC14_configureMultiSequenceMode(ADC_MEM0, ADC_MEM1, false); // This configures the ADC in manual conversion mode // Software will start each conversion. ADC14_enableSampleTimer(ADC_MANUAL_ITERATION); } //----------------------------------------------------------- // initJoystick is used to set up the A/D channels and the pin // package configuration void initJoyStick() { // This configures ADC_MEM0 to store the result from // input channel A15 (Joystick X), in non-differential input mode // (non-differential means: only a single input pin) // The reference for Vref- and Vref+ are VSS and VCC respectively ADC14_configureConversionMemory(ADC_MEM0, ADC_VREFPOS_AVCC_VREFNEG_VSS, ADC_INPUT_A15, // joystick X ADC_NONDIFFERENTIAL_INPUTS); // This selects the GPIO as analog input // A15 is multiplexed on GPIO port P6 pin PIN0 GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P6, GPIO_PIN0, GPIO_PRIMARY_MODULE_FUNCTION); // This configures ADC_MEM0 to store the result from // input channel A15 (Joystick X), in non-differential input mode // (non-differential means: only a single input pin) // The reference for Vref- and Vref+ are VSS and VCC respectively ADC14_configureConversionMemory(ADC_MEM1, ADC_VREFPOS_AVCC_VREFNEG_VSS, ADC_INPUT_A9, // joystick Y ADC_NONDIFFERENTIAL_INPUTS); // This selects the GPIO as analog input // A9 is multiplexed on GPIO port P4 pin PIN4 GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P4, GPIO_PIN4, GPIO_TERTIARY_MODULE_FUNCTION); } //------------------------------------------------------------ // getSampleJoystick is used to read an actual joystick sample void getSampleJoyStick(unsigned *X, unsigned *Y) { // This starts the conversion process // The S/H will be followed by SAR conversion ADC14_enableConversion(); ADC14_toggleConversionTrigger(); // We wait for the ADC to complete while (ADC14_isBusy()) ; // and we read the output result from buffer ADC_MEM0 *X = ADC14_getResult(ADC_MEM0); *Y = ADC14_getResult(ADC_MEM1); } 10:50 Running the joystick example in automatic conversion mode The second example, msp432-joystickxy-c-lcd, demonstrates the configuration of the ADC when it is in free-running mode. The key difference with the previous lies in the trigger conversion strategy. We now use the following driverlib call: // This configures the ADC in manual conversion mode // Software will start each conversion. ADC14_enableSampleTimer(ADC_AUTOMATIC_ITERATION); Instead of a single HAL API to read the A/D, getSampleJoyStick, we will now use two calls. One call, startADC, starts the first iteration. The second call, getSampleJoyStick, reads the ADC memory: void startADC() { // Starts the ADC with the first conversion // in repeat-mode, subsequent conversions run automatically ADC14_enableConversion(); ADC14_toggleConversionTrigger(); } void getSampleJoyStick(unsigned *X, unsigned *Y) { // ADC runs in continuous mode, we just read the conversion buffers *X = ADC14_getResult(ADC_MEM0); *Y = ADC14_getResult(ADC_MEM1); } Note that in this automatic conversion mode, the conversions run as fast as the ADC can deliver them (given the selected conversion clock). With the parameters chosen, it runs at several hundred of KHz. That means that ADC_MEM0 and ADC_MEM1 can be updated (and overwritten) many times before the software has a chance to call getSampleJoyStick 10:55 Conclusions - Please take a look at the final example, msp432-microphone-lcd, and try to figure out how it works!