Using the Analog in and LCD, turn the MBED into a simple Oscilloscope

Dependencies:   C12832_lcd DebounceIn mbed

/media/uploads/trichards1138/snapshot_20140609.jpg /media/uploads/trichards1138/snapshot_20140609_2.jpg

Introduction:

The MBED Oscilloscope uses the Analog Input on pin 17 to display connected waveforms from another MBED or a function generator. Note that the above left photo shows the Ain 1/8" phone jack on the side of the MBED being used as the input for a function generator. The crude photo on the right shows the waveform displayed on the MBED LCD screen (this is using the Application board available to plug the MBED processor board into. It includes the LCD screen as well as buttons, pots, temperature sensor, wifly socket, speaker, etc).

The waveform that is displayed on the MBED Oscilloscope is: /media/uploads/trichards1138/waveform.jpg This is from a Digilent combination function generator, Oscilloscope, and logic analyzer. The arbitrary waveform generator shown is one of the features of the unit and allows output of several types of signals from its analog output.

Notice that the frequency is set to 1kHz. The MBED Oscilloscope is set to 512 uS per division (due to display limitations, the settings cannot be displayed with the waveform). The displayed waveform shows a complete period in approximately 2 divisions. The scope is indicating that the period is 1mS or 1kHz.

Capabilities and Limitations:

The scope can display waveforms up to 5kHz with some clarity (although 5kHz is pushing its resolution 2-3kHz is a good practical limit). For displaying waveforms from a function generator running on another MBED, this is not a significant limitation. Except for the square-wave function, the function generator output of the MBED is around 1kHz realistically (the square-wave can output much higher frequencies since it only has two levels). (NOTE: this limitation is not strictly due to the ADC input. it also has to do with loop delay i.e. display update times...etc)

When an input is configured as an Analog input to the ADC, it is not 5V tolerant. The voltage on the pin must not exceed the reference-voltage-plus (set to approx 3V on the MBED board). So the amplitude input is limited to 3V (Although I've input signals at 3.3V with no immediate issues). Again this is not a significant limitation if debugging signals from another MBED. However, if you are using an external function generator, you must ensure you do not overdrive the input.

Usage:

Using the scope is fairly straight forward. Once you have the waveform source connected to pin 17 (either via the 1/8" phone jack or a wire directly connected to p17 through the header), the waveform should be displayed.

To change the time-base, use the right and left joystick buttons. You can check the current setting by pressing the middle button of the joystick. As long as you keep the middle button pressed, the settings page will be displayed. It shows the current settings of the time-base, vertical or volt-base, and the trigger mode. The photo below shows the settings page: /media/uploads/trichards1138/snapshot_20140609_8.jpg

Again, I apologize for the crudeness of the photo (a photographer I am not). Note that the time-base will change based on whether the grid is turned on. In the first photo above you see the grid lines painted on the display. You have the option to turn those off and there are instances where it is desirable. When this is done, the entire horizontal screen is viewed as one division. So the time-base will change accordingly.

Once you release the center button, the display will return to the waveform screen after about a second. If however you press the center button again within that window, you will be taken to the trigger selection window: /media/uploads/trichards1138/snapshot_20140609_9.jpg

There are five trigger modes:

1. Rising-Edge Main Signal (this finds the rising edge on the signal connected to pin 17)

2. Falling-Edge Main Signal (Same as above but finds the falling edge)

3. Rising-Edge Ext Signal (this finds the rising edge on the signal connected to pin 18)

4. Falling-Edge Ext Signal (Sam as in 3 for falling edge)

5. Auto or No Trigger (this does not look for a trigger - useful for dc signals)

Note that only three modes are displayed on the screen. The screen will scroll as you move the selection down. This is done with the "up" and "down" buttons on the joystick. Make your selection by pressing the center button again.

Once you make your trigger selection, you will then be taken to the grid selection menu: /media/uploads/trichards1138/snapshot_20140609_10.jpg

This allows the choice of whether to display the grid. Again there are situations you may want to disable the grid. Make the grid selection in the same way by pressing the center button of the joystick.

Once this selection is made, you will return the the waveform display.

The only additional control it connected to pot2. This is the pot furthest from the display. It controls the trigger level.

Know Issues:

1. The External triggering is not working at all right now. For some reason having two active signals connected to two different ADC inputs causes crosstalk that distorts both signals. I haven't looked into this yet so avoid using external triggering for the present. I will update when and if it is fixed. (any input or pointers would be appreciated)

2. The time base needs to be calibrated a little better. It is pretty close in most cases and good enough for rough work, but you would not want to keep time with it. It is a little like whack-a-mole and may never be perfect, but it can be improved.

3. The timeouts for the triggering mechanism may need some tweaking at some point as well. I think it is pretty good but may cause some fringe problems.

4. The trigger level is not currently displayed anywhere and should be part of the settings page. This would need to be added to a second settings page (and the menu system might get cumbersome at some point).

5. The pot is a little jittery for the trigger level. However, adding another menu to set it with the joystick will further push the problem in 4 of the menu system getting cumbersome. It may be good enough with some tweaks.

Future enhancements:

1. Getting the external triggering working is the first order of business.

2. Getting the time-base closer to reality will make things nicer.

3. I believe the grid would be nicer if the lines were dotted.

4. A classmate suggested adding FFT capabilities for a limited spectrum analyzer.

Any other suggestions?

Accessories:

I put together some faux scope probes by purchasing an inexpensive 3-foot audio cable (it is not shielded as the frequencies we are dealing with are fairly low). I cut this in half and soldered on some clips to connect the center and ground connections for the Ain 1/8" phone jack. The cable I got was actually stereo and I used a DMM to find which bare-wire was connected to pin 17 on the MBED. This accessory does make it nice in most cases.

However you can achieve the same results by connecting two wires directly one to pin 17 and the other to GND on the header connections of the application board directly to the input signal and GND of your waveform output.

main.cpp

Committer:
trichards1138
Date:
2014-06-09
Revision:
4:8eeaddfebd37
Parent:
3:fe3bab42f046

File content as of revision 4:8eeaddfebd37:

//************************************************************************************
// Main.cpp - main entry point and code for MBED based Oscilloscope.  
//
//	Author: Terry Richards
//	Date:   June 10, 2014
//
#include "mbed.h"
#include "C12832_lcd.h"
#include "DebounceIn.h"
#include "osc.h"

Serial pc(USBTX, USBRX);//debug output
C12832_LCD LCD;			//main LCD class
AnalogIn Ain(p17);		//main analog input
AnalogIn Aintrig(p18);	//External trigger input

//AnalogIn pot1(p19);
AnalogIn pot2(p20);		//sets trigger level
DebounceIn Update(p14);	//menu selection
DebounceIn Up(p15);		//menu selection and vert base set
DebounceIn Down(p12);	//menu selection and vert base set
DebounceIn Left(p13);	//horiz (time) base set
DebounceIn Right(p16);	//horiz (time) base set

//Repaint the LCD with the menu strings for the 
//current menu.  
//Inputs - An array of menustrings and the first string
//         to put on the display
//Outputs - a freshly painted display with the menu
#define FIRST_TEXTLINE		3	//Positions on the LCD
#define SECOND_TEXTLINE		13
#define THIRD_TEXTLINE		23
int rePaint(char menustrings[][MENU_LENGTH], int first)
{
    LCD.cls();
    LCD.locate( 0, FIRST_TEXTLINE );
    LCD.printf(menustrings[first]);
    LCD.locate( 0, SECOND_TEXTLINE );
    LCD.printf(menustrings[first+1]);
    LCD.locate( 0, THIRD_TEXTLINE );
    LCD.printf(menustrings[first+2]);
    return 0;
}
// Display a menu and allow scrolling until user
// makes a selection.  Show the current selection
// with a row of asterisks (which scroll with the
// up and down buttons).
// Inputs - The current selection and window min
//          and max and strings for the current menu.
//          These will change depending on the menu
//          being displayed.  The sel_start is where
//          the selection asterisks start for the 
//          current display stings.
// Outputs - A selection from the user from the current
//           menu.
//
#define CLEARTORUN			6
#define SETTORUN			4
int display_menu(	int *cur_sel, 
					int *cur_min, 
					int *cur_max, 
					char menustrings[][MENU_LENGTH], 
					int sel_start, 
					int menu_end )
{
   while( 1 ) {
        if( Down ) {
             if( *cur_sel != menu_end ) {
                memset( &menustrings[*cur_sel][sel_start-1], ' ' , CLEARTORUN);
                *cur_sel += 1;
                memset( &menustrings[*cur_sel][sel_start], '*', SETTORUN);
                if( *cur_sel > *cur_max ) {
                    *cur_max += 1;
                    *cur_min += 1;
                    rePaint(menustrings, *cur_min);
                }
                else
                    rePaint(menustrings, *cur_min);
            }
            while( Down )
                wait_ms(50);
        }
        if( Up ) {
            if( *cur_sel != 0 ) {
                memset( &menustrings[*cur_sel][sel_start-1], ' ', CLEARTORUN);
                *cur_sel -= 1;
                memset( &menustrings[*cur_sel][sel_start], '*', SETTORUN);
                if( *cur_sel < *cur_min ) {
                    *cur_max -= 1;
                    *cur_min -= 1;
                    rePaint(menustrings, *cur_min);
                }
                else
                    rePaint(menustrings, *cur_min);
            }
            while( Up )
                wait_ms(50);	//Smooth things out
        }
        if( Update ) {	//When user presses center button, cur_sel has his selection
        	return 0;
        }
    }
}
//Update the vertical and horizontal bases when the user presses the up or down joystick
// buttons.  The horizontal is the time base, and the vertical is the volt base.
// Inputs - none
// Outputs - updated vertical and horizontal bases
#define VBASEMAX		17
#define TBASEMAX		9
void Update_params(void)
{	//divisions  1=128uS, 5=640uS, 10=1280uS, 20=2560uS, 50=6.4mS, 100=12.8mS, 500=64mS, 1000=128mS
	unsigned int next_tbase[] = { 1, 5, 10, 20, 50, 100, 500, 700, 1000 };
	float next_vbase[] = { 0.2, 0.4, 0.6, 0.8, 1.0, 1.4, 1.8, 2.0, 2.4, 2.8, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0 }; 
	static int vsel=4, tsel=0;
	if( Up ) {
		while( Up )
			wait_ms(1);
		vsel++;
		if( vsel == VBASEMAX )
			vsel = VBASEMAX-1;
		volt_base = next_vbase[vsel];
		}
	if( Down ) {
		while( Down )
			wait_ms(1);
		vsel--;
		if( vsel == -1 )
			vsel = 0;
		volt_base = next_vbase[vsel];
	}
	if( Right ) {
		while( Right )
			wait_ms(1);
		tsel++;
		if( tsel == TBASEMAX )
			tsel = TBASEMAX-1;
		time_base = next_tbase[tsel];
	}
	if( Left ) {
		while( Left )
			wait_ms(1);
		tsel--;
		if( tsel == -1 )
			tsel = 0;
		time_base = next_tbase[tsel];
	}
	tbsel = tsel;	//Global copy for menu
	//TODO: may need to add menu to update the trigger
	//level with the joystick.  Pot is a little twitchy
	trigger = pot2.read();	//set the trigger level
}
// Find an edge (either rising or falling based on trigger mode) and use
// it to trigger a sweep of the scope display.  The search for an edge is
// timed in case there is not a periodic waveform currently connected to
// Ain.  This ensures the menu is not delayed too much.
// Inputs - none
// Outputs - time sync with a correct edge of the waveform
//           or quick return if "no_trigger" is selected.
//The trigger loops below take approx 20us per loop
#define TRIG_TIMEOUT		1000	//1000*20us=20milliseconds
bool get_trigger(void)
{
	//add more timeout as the timebase increases
	//TODO: may need to be calibrated more fine grain at some point
	int timeout=TRIG_TIMEOUT + (tbsel*tbsel*TRIG_TIMEOUT);
	//capture a rising edge on the main signal - default mode
	if( trigger_mode == main_rising_edge ) {	
        while( (Ain.read() > trigger) && timeout )
        	timeout--;
        if( !timeout )
        	return false;
        else
        	timeout = TRIG_TIMEOUT + (tbsel*TRIG_TIMEOUT);
        while( (Ain.read() < trigger) && timeout )
        	timeout--;
        if( !timeout )
        	return false;
        else
        	return true;
    //capture a falling edge on the main signal
    } else if( trigger_mode == main_falling_edge ) {	
    	while( (Ain.read() < trigger) && timeout )
       		timeout--;
       	if( !timeout )
       		return false;
       	else
       		timeout = TRIG_TIMEOUT + (tbsel*TRIG_TIMEOUT);
       	while( (Ain.read() > trigger) && timeout ) 
       		timeout--;
       	if( !timeout )
       		return false;
       	else
       		return true;
    //capture a rising edge on an external signal
    } else if( trigger_mode == ext_rising_edge ) { 
       	while( (Aintrig.read() > trigger) && timeout )
        	timeout--;
        if( !timeout )
        	return false;
        else
        	timeout = TRIG_TIMEOUT + (tbsel*TRIG_TIMEOUT);
        while( (Aintrig.read() < trigger) && timeout )
        	timeout--;
        if( !timeout )
        	return false;
        else
        	return true;
    //capture a falling edge on an external signal
    } else if( trigger_mode == ext_falling_edge ) {
    	while( (Aintrig.read() < trigger) && timeout )
       		timeout--;
       	if( !timeout )
       		return false;
       	else
       		timeout = TRIG_TIMEOUT + (tbsel*TRIG_TIMEOUT);
       	while( (Aintrig.read() > trigger) && timeout )
       		timeout--;
       	if( !timeout )
       		return false;
       	else
       		return true;
    } else if( trigger_mode == auto_no_trigger )
    	//if auto no-trigger...do nothing. just return
   	 	return true;
   	return false;	//Can't get here, but silence the warning	
}

//This routine only gets called if the "draw grid" mode is selected. The
// default is "no grid" so the display is a continuous display with no
// lines.  The grid is more useful for some waveforms than others.  It is
// a matter of taste as to if the user wants to display with or without grid.
// Inputs - none
// Outputs - the display with grid lines painted.
#define NUMHORIZPIXELS		128
#define NUMVERTPIXELS		32
#define FIRSTVERT			21
#define SECONDVERT			42
#define THIRDVERT			63
#define FOURTHVERT			84
#define FIFTHVERT			105
#define FIRSTHORIZ			11
#define SECONDHORIZ			22
void place_grid(void)
{
	LCD.line(FIRSTVERT, 0, FIRSTVERT, NUMVERTPIXELS-1, 1);
	LCD.line(SECONDVERT, 0, SECONDVERT, NUMVERTPIXELS-1, 1);
	LCD.line(THIRDVERT, 0, THIRDVERT, NUMVERTPIXELS-1, 1);
	LCD.line(FOURTHVERT, 0, FOURTHVERT, NUMVERTPIXELS-1, 1);
	LCD.line(FIFTHVERT, 0, FIFTHVERT, NUMVERTPIXELS-1, 1);
	LCD.line(0, FIRSTHORIZ, NUMHORIZPIXELS-1, FIRSTHORIZ, 1);
	LCD.line(0, SECONDHORIZ, NUMHORIZPIXELS-1, SECONDHORIZ, 1);
}

void menu(void) 
{
	int timeout=100;
	//Corrects the faster time bases for loop delay
	//TODO: These need to be further calibrated.  Maybe re-architected.
	unsigned int tbase_correction[] = { 24, 6, 3, 3, 1, 1, 1, 1, 1 };
	//While user has center button pressed, display the settings
    while( Update ) {
    	LCD.cls();
    	LCD.locate( 0, FIRST_TEXTLINE );
    	LCD.printf("Vertical Base: %3.1f V/div", draw_grid? volt_base/3: volt_base );
    	LCD.locate( 0, SECOND_TEXTLINE );
    	LCD.printf("Horiz Base: %d uS/div", draw_grid? (((time_base*tbase_correction[tbsel])*128)/6): ((time_base*24)*128));
    	LCD.locate( 0, THIRD_TEXTLINE );
    	switch( trigger_mode ) {
    		case main_rising_edge:
    			LCD.printf("Trigger Mode: Main rising");
    			break;
    		case main_falling_edge:
   				LCD.printf("Trigger Mode: Main falling");
    			break;
    		case ext_rising_edge:
   				LCD.printf("Trigger Mode: Ext rising");
    			break;
    		case ext_falling_edge:
   				LCD.printf("Trigger Mode: Ext falling");
    			break;
    		case auto_no_trigger:
   				LCD.printf("Trigger Mode: No Trigger");
    	}
       	wait_ms(200);
    }
    //Wait a while in case user presses center again
    while( !Update ) {
       	--timeout;
       	if( !timeout ) 
       		break;	//If we hit time out, break out
     	wait_ms(10);
    }
    if( timeout ) {		//If we didn't time out...continue
       	timeout = 100;	//If we did..we just exit and go back to scope
        //User may be hitting center again, if so display tigger menu
        if( Update ) {	//user hit update again
        	while( Update )
        		wait_ms(1);		//ensure we only process on hit
        	rePaint(trigstrings, trig_cur_min);
        	//Get a new trigger mode
        	display_menu(&trig_cur_sel, &trig_cur_min, &trig_cur_max,
        		trigstrings, SELECTION_START, MAX_TRIG_MENU ); 
        	trigger_mode = (trigger_mode_type)trig_cur_sel;
        }
        while( !Update ) {	//See if user wants to update grid settings
        	--timeout;
        	if( !timeout )
            	break;	//If we hit time out, break out
            wait_ms(10);
        }
        if( timeout ) {		//If we didn't time out..continue
        	if( Update ) {	//user hit update again
        		while( Update )
        			wait_ms(1);		//ensure we only process one hit
        		rePaint(gridstrings, grid_cur_min);
        		//Get new grid settings
        		display_menu(&grid_cur_sel, &grid_cur_min, &grid_cur_max,
        			gridstrings, SELECTION_START, MAX_GRID_MENU );
        		if( grid_cur_sel )
        			draw_grid = true;
        		else
        			draw_grid = false;
        	}
        	while( Update )		//trickle out last switch hits
        		wait_ms(1);
        }
    }
}
	
//Main entry point and control loop for the program.  All routines
// are called from here (and return here).
//
int main()
{
    pc.baud(115200);	//debug output
    bool skip_first_trig = false;
	unsigned int i;
    unsigned int h = LCD.height()-2, w = LCD.width(), hhh;
    while(true) {       // thread loop
    	if( get_trigger() || skip_first_trig ) {	//go get a trigger
			LCD.cls();
			LCD.rect(0,0,NUMHORIZPIXELS-1, NUMVERTPIXELS-1, 1);	//place boarder
			if(draw_grid)
				place_grid();			
        	for (i=0; i<w; i++) {	//do a sweep
           		//scale the incoming signal to the screen size and vertical
           		//base setting.
           		hhh = (int)((h)-(h*volt_base*Ain.read()));
           		//does not paint the screen
           		LCD.pixel(i, hhh ,1);           // print pixel
           		//implements the horizontal or time-base
           		wait_us(time_base);
        	}
        	//paint the screen
        	LCD.copy_to_lcd();  // LCD.pixel does not update the lcd 
        }
        skip_first_trig = false;
        Update_params();	//see if we changed time or volt bases
        if( Update ) {	//Hit the center button once to display stats
        	skip_first_trig = true;	//ensure quick screen update after menu
        	menu();		//go get user input..Now on HID time
        } else
        	wait_ms(50);	//Delay to smooth twitchy waveforms
    }
}

#if 0
Testing:
1. You need another MBED with a function generator running, or a suitable waveform generator (suitable in
that it must generate waveforms in the frequency band of 1Hz to 2kHz, and amplitudes between 0 and 3.3 Volts).
2. Waveforms greater than 2kHz are displayable on the MBED Oscilloscope, but are not very interesting.  They
are two fast for the MBED processor to keep up with.  The Analog Input takes 20 uS to get a reading so it is
limited in the frequencies it can handle.
3. See the writeup on the MBED site for instructons for the menuing system that sets the scope parameters.
4. Connect the output of the other MBED or the function generator to pin 17 of the MBED running the Osc 
software, or use a "scope probe" connected to the Ain 1/8 phone jack on the side of the MBED running the Osc
software.  Note that the other jack (Aout) can be used as an external trigger if desired, however the two jacks are
so close together on the application board that you will need to find very small 1/8 phone jacks to fit two
together side-by-side.
5. "Scope probes" can be constructed by obtaining an audio cable (stereo or mono will work), cut it in half so 
you have two cables with bare wires on one end and an 1/8 phone jack on the other.  Use a DMM to determine the 
bare wires that are connected to the p17 and GND on the MBED (you can do this most easily by plugging the cable
into the Ain jack and connecting one probe of the DMM to p17 and the other to the bare wires and see which one
has continuity.  Then do the same with GND).  Now solder a clip to both the p17 wire and the GND wire so you 
can connect these to your device-under-test.
#endif