Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revision | |||
| test-and-measurement:analog-discovery-2:ad2-motor-debugger:start [2022/09/12 18:11] – changed forum.digilentinc.com to forum.digilent.com jeff.greenberg_ni.com | test-and-measurement:analog-discovery-2:ad2-motor-debugger:start [2022/10/04 11:38] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Using the Analog Discovery 2 to Debug Different Motor Controllers ====== | ||
| + | {{ reference: | ||
| + | |||
| + | The Analog Discovery 2 is a useful tool for debugging projects or designs that use a variety of signal types and can be used with the WaveForms SDK to write automated or custom tests. The following example is a replication of a motor controller tester application used by a customer working in consumer electronics. In this particular application multiple motors were used in a system where their functionality directly impacted the safety of the user, so extensive testing was necessary. | ||
| + | |||
| + | As an example of what was done the following guide presents how to create a C/C++ project for the Analog Discovery 2 using WaveForms SDK and how to use it to debug an H-Bridge DC motor controller and a stepper motor controller, both used in an automatic baby swing design. | ||
| + | ---- | ||
| + | |||
| + | ===== Prerequisites ===== | ||
| + | <WRAP group> | ||
| + | == Hardware == | ||
| + | * [[https:// | ||
| + | |||
| + | == Software == | ||
| + | * [[software: | ||
| + | * [[https:// | ||
| + | * C/C++ compiler (this might be included in your IDE) | ||
| + | * [[https:// | ||
| + | </ | ||
| + | |||
| + | **Note:** //WaveForms can be installed by following the [[software: | ||
| + | ---- | ||
| + | |||
| + | ===== Preparation ===== | ||
| + | <WRAP group> | ||
| + | In the following, an existing Arduino motor controller project will be tested and debugged. The project consists of a brushed DC and a stepper motor, with the proper drivers, two potentiometers and a latching hall-effect sensor, all controlled by an Arduino microcontroller. One potentiometer sets the speed of the DC motor and the other sets the position of the stepper (like in the [[https:// | ||
| + | |||
| + | Creating this motor controller project is beyond the scope of this guide, and the testing concepts covered here can be used in a wide variety of projects. To try out this example, you can connect the Analog Discovery 2 to any simple motor driving application, | ||
| + | </ | ||
| + | ---- | ||
| + | |||
| + | ===== Hardware Setup ===== | ||
| + | <WRAP group>< | ||
| + | In the project we want to debug as an example, an H-Bridge DC motor driver and a stepper driver are controlled by an Arduino UNO, according to the input of two potentiometers. One potentiometer sets the speed of the DC motor, while the other sets the position of the stepper. A Hall-effect sensor sends an interrupt signal to the MCU after every half-turn of the DC motor, and the direction of rotation is changed. | ||
| + | |||
| + | In order to test the system first the potentiometer values should be measured, then both motors should be tested by sending digital control signals to the drivers, while the MCU is disabled, then, after enabling the MCU again, it's output signals should be recorded. | ||
| + | |||
| + | To read the voltages on the potentiometers, | ||
| + | |||
| + | **Note:** //Any digital I/O line can be connected to any pin of the MCU/ | ||
| + | |||
| + | **Note:** //The wiring diagram only shows the connections between the Analog Discovery 2 and the other devices.// | ||
| + | </ | ||
| + | {{ reference: | ||
| + | </ | ||
| + | ---- | ||
| + | |||
| + | ===== Creating the Project ===== | ||
| + | <WRAP group> | ||
| + | When the C/C++ project is created, copy the **dwf.lib** file from the WaveForms installation library, // | ||
| + | </ | ||
| + | ---- | ||
| + | |||
| + | ===== Functions for Controlling Different Instruments ===== | ||
| + | In the following, the functions used for controlling the instruments in this example are discussed. For details about other available functions in the WaveForms SDK, check the [[software: | ||
| + | |||
| + | --> Connecting and Disconnecting the Analog Discovery 2 # | ||
| + | <WRAP group>< | ||
| + | To be able to use the Analog Discovery 2, it must be connected. To safely connect the device, the program should check if there is an Analog Discovery 2 connected to the PC. If yes, it also should check, whether there are any devices, which are available (unused by other programs). If an available device is detected, it should be opened, so its device handle (hdwf) can be passed to the other functions. | ||
| + | </ | ||
| + | // | ||
| + | bool init_AD2(HDWF *hdwf, char *error) | ||
| + | { | ||
| + | // detect if AD2 is connected or not | ||
| + | int nr_of_connected_dev; | ||
| + | FDwfEnum(devidDiscovery2, | ||
| + | if (nr_of_connected_dev == 0) | ||
| + | { | ||
| + | strcpy(error, | ||
| + | error[strlen(" | ||
| + | return false; | ||
| + | } | ||
| + | |||
| + | // detect if AD2 is used by other program or not (select the unused one if there are more devices) | ||
| + | int unused_dev_index = -1; | ||
| + | for (int i = 0; i < nr_of_connected_dev; | ||
| + | { | ||
| + | int device_is_used; | ||
| + | FDwfEnumDeviceIsOpened(i, | ||
| + | if (!device_is_used) | ||
| + | { | ||
| + | unused_dev_index = i; //save free device index | ||
| + | break; | ||
| + | } | ||
| + | } | ||
| + | if (unused_dev_index == -1) | ||
| + | { | ||
| + | strcpy(error, | ||
| + | error[strlen(" | ||
| + | return false; | ||
| + | } | ||
| + | |||
| + | // connect to the unused AD2 | ||
| + | if (!FDwfDeviceOpen(unused_dev_index, | ||
| + | { | ||
| + | strcpy(error, | ||
| + | error[strlen(" | ||
| + | return false; | ||
| + | } | ||
| + | return true; | ||
| + | } | ||
| + | </ | ||
| + | <WRAP group>< | ||
| + | When the program finishes using the Analog Discovery 2, it must disconnect it, not to prevent other software from using it. Before disconnecting, | ||
| + | </ | ||
| + | //reset every used instrument and close device | ||
| + | void close_AD2(HDWF *hdwf) | ||
| + | { | ||
| + | FDwfDigitalIOReset(*hdwf); | ||
| + | FDwfAnalogInReset(*hdwf); | ||
| + | FDwfDigitalOutReset(*hdwf); | ||
| + | FDwfDigitalInReset(*hdwf); | ||
| + | FDwfDeviceClose(*hdwf); | ||
| + | return; | ||
| + | } | ||
| + | </ | ||
| + | <-- | ||
| + | |||
| + | --> Static I/O # | ||
| + | <WRAP group>< | ||
| + | To control the state of the Analog Discovery 2's digital I/O pins, the // | ||
| + | |||
| + | The first step is, to get the pins, which are set as output, and the states set on these pins. The function should not modify the state of the other pins, only the given one. | ||
| + | In the next step, a mask is applied to the variable containing the pins used as a digital output, as well as on the variable containing the states of the output pins. The new values are sent back to the Analog Discovery 2. | ||
| + | </ | ||
| + | //write a value on a specific digital i/o pin | ||
| + | void digital_write(HDWF *hdwf, int pin, int value) | ||
| + | { | ||
| + | // get current state | ||
| + | unsigned int previous_mask, | ||
| + | FDwfDigitalIOOutputEnableGet(*hdwf, | ||
| + | FDwfDigitalIOInputStatus(*hdwf, | ||
| + | |||
| + | //set new mask | ||
| + | unsigned int power = 1; //power = 2^pin | ||
| + | for (int i = 0; i < pin; i++) | ||
| + | { | ||
| + | power *= 2; | ||
| + | } | ||
| + | previous_mask |= power; | ||
| + | |||
| + | //set new state | ||
| + | if (value != 0) | ||
| + | { | ||
| + | previous_value |= power; | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | previous_value &= ~power; | ||
| + | } | ||
| + | |||
| + | //output new values | ||
| + | FDwfDigitalIOOutputEnableSet(*hdwf, | ||
| + | FDwfDigitalIOOutputSet(*hdwf, | ||
| + | return; | ||
| + | } | ||
| + | </ | ||
| + | <-- | ||
| + | |||
| + | --> Voltmeter # | ||
| + | <WRAP group>< | ||
| + | To read voltages with the Analog Discovery 2, the // | ||
| + | |||
| + | Firstly, the channel, the range, and the offset are set, then triggering is disabled, then samples are acquired and averaged, if necessary. | ||
| + | </ | ||
| + | //read voltage on a given analog channel | ||
| + | float analog_read(HDWF *hdwf, int channel, int average) | ||
| + | { | ||
| + | //configure the oscilloscope | ||
| + | channel--; | ||
| + | FDwfAnalogInChannelEnableSet(*hdwf, | ||
| + | FDwfAnalogInChannelRangeSet(*hdwf, | ||
| + | FDwfAnalogInChannelOffsetSet(*hdwf, | ||
| + | FDwfAnalogInConfigure(*hdwf, | ||
| + | Wait(2000); | ||
| + | |||
| + | //average samples | ||
| + | float sum = 0; | ||
| + | for (int i = 0; i < average; i++) | ||
| + | { | ||
| + | FDwfAnalogInStatus(*hdwf, | ||
| + | double temporal_voltage; | ||
| + | FDwfAnalogInStatusSample(*hdwf, | ||
| + | sum += temporal_voltage; | ||
| + | Wait(100); | ||
| + | } | ||
| + | sum /= average; //calculate average | ||
| + | return sum; | ||
| + | } | ||
| + | </ | ||
| + | <-- | ||
| + | |||
| + | --> Logic Analyzer # | ||
| + | <WRAP group>< | ||
| + | To acquire digital data with the Analog Discovery 2, the // | ||
| + | |||
| + | As a start, the Static I/O, the Logic Analyzer, and the Pattern Generator instruments should be reset to their default state. Following this, the acquisition sample rate and the output data format should be set. When using Analog Discovery 2, the recommended data format is 16 bits per word, as the device has 16 digital I/O lines. | ||
| + | |||
| + | The acquisition should be triggered by a rising or falling edge on any pin, but it also should have a timeout, to record cases when there are no changes in the signal (can be useful when debugging). | ||
| + | |||
| + | After starting the instrument, data is recorded in a buffer continuously. In a loop, this buffer is continuously saved to an output array, while it is also checked, if there are any corrupt, or lost data words. If the desired sample count is achieved, the loop is exited. | ||
| + | |||
| + | Finally, the recorded data is formatted and the possible errors retuned. | ||
| + | </ | ||
| + | //record digital signals | ||
| + | int digital_read(HDWF *hdwf, bool **digital_data, | ||
| + | { | ||
| + | //reset digital instruments | ||
| + | FDwfDigitalIOReset(*hdwf); | ||
| + | FDwfDigitalOutReset(*hdwf); | ||
| + | FDwfDigitalInReset(*hdwf); | ||
| + | |||
| + | //create array for results | ||
| + | unsigned short *unformatted_data = (unsigned short *)malloc(*buffer_size * sizeof(unsigned short)); | ||
| + | |||
| + | //set up the instrument | ||
| + | FDwfDigitalInAcquisitionModeSet(*hdwf, | ||
| + | double internal_frequency; | ||
| + | FDwfDigitalInInternalClockInfo(*hdwf, | ||
| + | FDwfDigitalInDividerSet(*hdwf, | ||
| + | FDwfDigitalInSampleFormatSet(*hdwf, | ||
| + | |||
| + | //set up the trigger | ||
| + | FDwfDigitalInTriggerPositionSet(*hdwf, | ||
| + | FDwfDigitalInTriggerSourceSet(*hdwf, | ||
| + | FDwfDigitalInTriggerAutoTimeoutSet(*hdwf, | ||
| + | FDwfDigitalInTriggerSet(*hdwf, | ||
| + | |||
| + | //start the aquisition | ||
| + | FDwfDigitalInConfigure(*hdwf, | ||
| + | |||
| + | //wait for trigger/ | ||
| + | int current_sample_count = 0, current_available_sample_count, | ||
| + | bool lost_flag = false, corrupt_flag = false; | ||
| + | STS sts; | ||
| + | while (current_sample_count < *buffer_size) | ||
| + | { | ||
| + | FDwfDigitalInStatus(*hdwf, | ||
| + | |||
| + | //skip this iteration, if recording hasn't started yet | ||
| + | if (current_sample_count == 0 && (sts == DwfStateConfig || sts == DwfStatePrefill || sts == DwfStateArmed)) | ||
| + | { | ||
| + | continue; | ||
| + | } | ||
| + | |||
| + | //check buffer state | ||
| + | FDwfDigitalInStatusRecord(*hdwf, | ||
| + | |||
| + | //count lost samples | ||
| + | current_sample_count += current_lost_sample_count; | ||
| + | |||
| + | //check FIFO overflow | ||
| + | if (current_lost_sample_count != 0) | ||
| + | { | ||
| + | lost_flag = true; | ||
| + | } | ||
| + | if (current_corrupt_sample_count != 0) | ||
| + | { | ||
| + | corrupt_flag = true; | ||
| + | } | ||
| + | |||
| + | //check data availability | ||
| + | if (current_available_sample_count == 0) | ||
| + | { | ||
| + | continue; //if no data is available, skip this iteration | ||
| + | } | ||
| + | |||
| + | //if more data is available, then the buffer size, limit it | ||
| + | if (current_sample_count + current_available_sample_count > *buffer_size) | ||
| + | { | ||
| + | current_available_sample_count = *buffer_size - current_sample_count; | ||
| + | } | ||
| + | |||
| + | //get samples | ||
| + | FDwfDigitalInStatusData(*hdwf, | ||
| + | |||
| + | //count saved samples | ||
| + | current_sample_count += current_available_sample_count; | ||
| + | } | ||
| + | |||
| + | //stop the aquisition | ||
| + | FDwfDigitalInConfigure(*hdwf, | ||
| + | |||
| + | //format data | ||
| + | for (int i = 0; i < *buffer_size; | ||
| + | { | ||
| + | digital_data[0][i] = unformatted_data[i] & 0x0001; | ||
| + | digital_data[1][i] = unformatted_data[i] & 0x0002; | ||
| + | digital_data[2][i] = unformatted_data[i] & 0x0004; | ||
| + | digital_data[3][i] = unformatted_data[i] & 0x0008; | ||
| + | digital_data[4][i] = unformatted_data[i] & 0x0010; | ||
| + | digital_data[5][i] = unformatted_data[i] & 0x0020; | ||
| + | digital_data[6][i] = unformatted_data[i] & 0x0040; | ||
| + | digital_data[7][i] = unformatted_data[i] & 0x0080; | ||
| + | digital_data[8][i] = unformatted_data[i] & 0x0100; | ||
| + | digital_data[9][i] = unformatted_data[i] & 0x0200; | ||
| + | digital_data[10][i] = unformatted_data[i] & 0x0400; | ||
| + | digital_data[11][i] = unformatted_data[i] & 0x0800; | ||
| + | digital_data[12][i] = unformatted_data[i] & 0x1000; | ||
| + | digital_data[13][i] = unformatted_data[i] & 0x2000; | ||
| + | digital_data[14][i] = unformatted_data[i] & 0x4000; | ||
| + | digital_data[15][i] = unformatted_data[i] & 0x8000; | ||
| + | } | ||
| + | |||
| + | //signal errors | ||
| + | if (lost_flag) | ||
| + | { | ||
| + | return 1; | ||
| + | } | ||
| + | if (corrupt_flag) | ||
| + | { | ||
| + | return 2; | ||
| + | } | ||
| + | return 0; | ||
| + | } | ||
| + | </ | ||
| + | <-- | ||
| + | |||
| + | --> Pattern Generator # | ||
| + | <WRAP group>< | ||
| + | To generate a PWM signal with a given duty cycle, the // | ||
| + | |||
| + | The following code snippet initializes a PWM signal on the digital I/O line CH and gradually increases its duty cycle from 0% to 100%, with 1% increase after every 50ms, then stops the signal. | ||
| + | </ | ||
| + | //set up the pwm signal | ||
| + | FDwfDigitalOutEnableSet(*hdwf, | ||
| + | FDwfDigitalOutTypeSet(*hdwf, | ||
| + | double internal_clock_frequency; | ||
| + | FDwfDigitalOutInternalClockInfo(*hdwf, | ||
| + | unsigned int range_min, range_max; | ||
| + | FDwfDigitalOutCounterInfo(*hdwf, | ||
| + | int frequency = 1000; // | ||
| + | unsigned int divider = ceil(internal_clock_frequency / frequency / range_max); | ||
| + | FDwfDigitalOutDividerSet(*hdwf, | ||
| + | unsigned int pulse = round(internal_clock_frequency / frequency / divider); | ||
| + | |||
| + | //varying the speed of the motor | ||
| + | for (int i = 0; i < 100; i++) | ||
| + | { | ||
| + | unsigned int max = pulse * i / 100; | ||
| + | unsigned int min = pulse - max; | ||
| + | FDwfDigitalOutCounterSet(*hdwf, | ||
| + | FDwfDigitalOutConfigure(*hdwf, | ||
| + | Wait(50); | ||
| + | } | ||
| + | |||
| + | //stop the motor | ||
| + | FDwfDigitalOutEnableSet(*hdwf, | ||
| + | </ | ||
| + | <-- | ||
| + | ---- | ||
| + | |||
| + | ===== The Complete Program ===== | ||
| + | <WRAP group> | ||
| + | The complete source code can be downloaded from here: {{ reference: | ||
| + | </ | ||
| + | |||
| + | <WRAP group>< | ||
| + | At the beginning of the project, the connections should be defined. By defining these at the start of the code, it is easier to modify them, if the wiring of the debugger to the MCU, or to the motor drivers is changed. The default state of the motor driver enable pins and the used libraries are also defined here. | ||
| + | </ | ||
| + | //define connections | ||
| + | //digital i/o | ||
| + | #define MCU_RESET 0 | ||
| + | #define DC_AIN1 1 | ||
| + | #define DC_AIN2 2 | ||
| + | #define DC_STBY 3 | ||
| + | #define DC_PWMA 4 | ||
| + | #define STEP_EN 5 | ||
| + | #define STEP_MS1 6 | ||
| + | #define STEP_MS2 7 | ||
| + | #define STEP_DIR 8 | ||
| + | #define STEP_STEP 9 | ||
| + | #define HALL 10 | ||
| + | //analog in | ||
| + | #define POT_SPEED 1 | ||
| + | #define POT_POS 2 | ||
| + | |||
| + | //define defaults | ||
| + | #define DC_ON HIGH //state of the stby pin when the motor is on | ||
| + | #define STEP_ON LOW //state of the en pin when the stepper is on | ||
| + | |||
| + | //define constants | ||
| + | #define TEST_ANALOG_INPUT_AVERAGE 10 //how many measurements should be averaged | ||
| + | #define TEST_STEPS 1024 //how many steps to do with the stepper motor | ||
| + | #define OUTPUT_CSV_HEADERS false // | ||
| + | |||
| + | / | ||
| + | |||
| + | //include headers needed for input/ | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | |||
| + | //include the DWF header and the custom header for this project | ||
| + | #include " | ||
| + | #include " | ||
| + | </ | ||
| + | |||
| + | <WRAP group>< | ||
| + | In the main function, the first step is to connect the Analog Discovery 2. If it can't be connected, the program must be finished, as the rest of the functions can't be called. The function init_AD2() is discussed under the [[test-and-measurement: | ||
| + | </ | ||
| + | //main function | ||
| + | int main(int argc, char **argv) | ||
| + | { | ||
| + | HDWF hdwf; // | ||
| + | char error[512]; //variable for error messages | ||
| + | |||
| + | // initialize the Analog Discovery 2 | ||
| + | if (!init_AD2(& | ||
| + | { | ||
| + | printf(" | ||
| + | _getch(); | ||
| + | return 0; | ||
| + | } | ||
| + | printf(" | ||
| + | Wait(2000); //wait 2 seconds | ||
| + | </ | ||
| + | |||
| + | <WRAP group>< | ||
| + | To test the motor drivers, the microcontroller must be turned off by setting it's RESET pin to low. After that, the voltages on the potentiometers can be read, and the digital_write() and PWM generation functions can be used to test both motors with varying speeds, in both directions. Details about the used functions can be found under the [[test-and-measurement: | ||
| + | </ | ||
| + | //main function | ||
| + | printf(" | ||
| + | |||
| + | // turn the microcontroller off to read/test peripherals | ||
| + | printf(" | ||
| + | digital_write(& | ||
| + | |||
| + | //read voltages on potentiometers | ||
| + | printf(" | ||
| + | float voltage_speed = analog_read(& | ||
| + | printf(" | ||
| + | float voltage_position = analog_read(& | ||
| + | printf(" | ||
| + | |||
| + | //testing the DC motor | ||
| + | printf(" | ||
| + | drive_DC(& | ||
| + | printf(" | ||
| + | drive_DC(& | ||
| + | |||
| + | //testing the stepper | ||
| + | printf(" | ||
| + | drive_STEP(& | ||
| + | printf(" | ||
| + | drive_STEP(& | ||
| + | </ | ||
| + | |||
| + | <WRAP group>< | ||
| + | To record the signals coming from the MCU, it must be turned on, by setting its RESET pin to logic high. After this, the number of samples to record must be specified (data_size), | ||
| + | </ | ||
| + | printf(" | ||
| + | |||
| + | // turn the microcontroller on to read/test it's signals | ||
| + | printf(" | ||
| + | digital_write(& | ||
| + | |||
| + | // record all signals coming from the MCU and the HALL effect sensor | ||
| + | printf(" | ||
| + | |||
| + | //allocate space for the data | ||
| + | unsigned int data_size = 1000000; //specify the number of samples | ||
| + | bool **digital_data; | ||
| + | digital_data = (bool **)malloc(16 * sizeof(bool *)); | ||
| + | for (int i = 0; i < 16; i++) | ||
| + | { | ||
| + | digital_data[i] = (bool *)malloc(data_size * sizeof(bool)); | ||
| + | } | ||
| + | |||
| + | int read_state = digital_read(& | ||
| + | |||
| + | //check for data loss/ | ||
| + | if (read_state == 1) | ||
| + | { | ||
| + | printf(" | ||
| + | } | ||
| + | if (read_state == 2) | ||
| + | { | ||
| + | printf(" | ||
| + | } | ||
| + | |||
| + | //save data in a file | ||
| + | if (!save_data(digital_data, | ||
| + | { | ||
| + | printf(" | ||
| + | _getch(); | ||
| + | return 0; | ||
| + | } | ||
| + | printf(" | ||
| + | </ | ||
| + | |||
| + | <WRAP group>< | ||
| + | When the program is finished, the Analog Discovery 2 must be closed, to make it available to other software. Details about the close_AD2() function can be found under the [[test-and-measurement: | ||
| + | </ | ||
| + | // close opened device | ||
| + | printf(" | ||
| + | close_AD2(& | ||
| + | return 0; | ||
| + | </ | ||
| + | |||
| + | <WRAP group>< | ||
| + | The results of the test are displayed in the console window along with messages about the current operation. | ||
| + | </ | ||
| + | {{ reference: | ||
| + | </ | ||
| + | |||
| + | ===== Data Visualization - Optional ===== | ||
| + | <WRAP group>< | ||
| + | Data exported in a file can be visualized by running the plotting.m MATLAB script or the plotting.py Python program, or you can write your own program to plot the data. | ||
| + | </ | ||
| + | {{ reference: | ||
| + | {{ reference: | ||
| + | </ | ||
| + | ---- | ||
| + | |||
| + | ===== Final Notes ===== | ||
| + | For more guides and example projects for your Analog Discovery 2, please visit its [[test-and-measurement: | ||
| + | |||
| + | For more information about how to use WaveForms SDK, please visit the [[software: | ||
| + | |||
| + | For technical support, please visit the [[https:// | ||
| + | |||
| + | {{tag> | ||

