Project tutorial

Arduino - Web Oscilloscope (Support Trigger) © GPL3+

This Arduino-powered Web Oscilloscope supports trigger, and changing voltage/div, offset and time / div. It supports 6 channels.

  • 3,736 views
  • 8 comments
  • 35 respects

Components and supplies

About this project

Demonstration

Features

  • Support 6 channels
  • Support single trigger, multiple triggers
  • Selectable trigger mode: falling, rising, falling, and rising
  • Settable trigger value
  • Adjusting time division via a web knob
  • Adjusting voltage division for each channel via a web knob
  • Adjusting display offset for each channel via a web knob
  • Tap or Click to show/hide setting area
  • Work on cross platform: Window, Linux, iOS, Android... (because of characteristic of web), which has a web browser

User Interface

How It Works

Because Web Oscilloscope is modification of Web Serial Monitor on PHPoC, I will explain how Web Serial Monitor works first.

PHPoC shield has a built-in embedded web app, called "Web Serial Monitor". It's similar to Serial Monitor on Arduino IDE. The difference between Serial Monitor on Arduino IDE is that:

  • Serial Monitor on Arduino IDE: Reading data from arduino tx pin via USB cable.
  • Web Serial Monitor on PHPoC Shield: Reading data from arduino tx pin via Internet

In detail, PHPoC Shield communicate with Arduino via SPI. When user access Web Serial Monitor from a web browser. After page is loaded, the page make a WebSocket connection to PHPoC Shield. At this time, PHPoC Shield is stacked on Arduino, it it capture data from Arduino TX pin, and then send this data to Web Serial Monitor on web browser via WebSocket. Web Serial receives the data and display on web.

This lets user not only see the data on PC but also see data on the mobile or any devices that support a web browser

Now let's see how Web Oscilloscope works.

Web Oscilloscope works the same as Web Serial Monitor, except for the last part.

In stead of displaying raw data on web, Web Oscilloscope visualize the data on the grapth. Other functions are added, which allow user adjust the UI and perform trigger.

About data, which is sent from Arduino

Arduino read data from ADC pin (from A0 to A5) and read timestamp (in microseconds)

ADC data is converted to voltage and print to serial port along with timestamp according to the following format:

  • Firstly, timestamp, follow by a blank or "\t" character.
  • And then, followed by voltage data. data of each channel is separated by a blank or "\t" character.
  • The end is newline character

Note: Maximum channel is 6, but you can also send only data of one, two only three channel.

Source Code

Souce code includes two parts:

  • Arduino Code (see the WebOscilloscope.ino)
  • Web app code (oscilloscope.php): this is user interface code. It needs to be uploaded to PHPoC Shield according to this instruction

How to Use

Access Web Oscilloscope from web browser on your PC or smart phone by typing: http://replace_ip_address/oscilloscope.php

How to measure signal: Just need uses two wire, one to GND and one to any Analog pin of Arduino (A0 to A5). Connect these two pins to the point we want to check the signal.

Since there is 6 channel, we need 6 ground wires. For convenience, however Arduino has only two GND pin. I do as below to extend more GND pins.

Code

WebOscilloscope.inoArduino
This is arduino code. We do NOT need to send all 6 channel data. As you can see, i commented data channel 3
#include <SPI.h>
#include <Phpoc.h>

#define AREF 5.0
#define ADC_MAX 1023.0


float ratio = AREF / ADC_MAX;

void setup() {

  Serial.begin(115200);
 
  Phpoc.begin();
}

void loop() {
  //read system time
  unsigned long time_a = micros();
 
  // read the analog value  and convert to voltage:
  float voltageChannel0 = analogRead(A0) * ratio;
  float voltageChannel1 = analogRead(A1) * ratio;
  float voltageChannel2 = analogRead(A2) * ratio;
  float voltageChannel3 = analogRead(A3) * ratio;
  float voltageChannel4 = analogRead(A4) * ratio;
  float voltageChannel5 = analogRead(A5) * ratio;

  // send system time first
  Serial.print(time_a);
  Serial.print(" ");

  // send value of each channel, seperated by " " or "\t".
  Serial.print(voltageChannel0);
  Serial.print(" ");
  Serial.print(voltageChannel1);
  Serial.print(" ");
  Serial.print(voltageChannel2);
  Serial.print(" ");
  //Serial.print(voltageChannel3);
  //Serial.print(" ");
  Serial.print(voltageChannel4);
  Serial.print(" ");
  //the last channel must send with new line charaters
  Serial.println(voltageChannel5);
}
oscilloscope.phpPHP
This is Web code.
<!DOCTYPE html>
<html>
<head>
<title>PHPoC Shield - Web Oscilloscope for Arduino</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { text-align:center; background-color: #595959 ; color: white; display: flex; justify-content:space-between;}
.graph_container {display:inline-block;}
.setting_container {display:inline-block; float:right; background-color: #595959; padding: 0px; width: 260px;}
.setting {
    justify-content: center;
	width: 260px;
	padding: 3px;
	display: flex;
	justify-content:space-between;
}
.button {
    background-color: #999999;
    border: none;
    color: white;
    padding: 5px;
    text-align: center;
    display: inline-block;
	font-weight: bold;
	font-size: 115%;
}
.button2, .select {
    border: none;
    padding: 5px;
    text-align: center;
    display: inline-block;
	font-weight: bold;
	font-size: 115%;
}
.select {width: 100%; background-color: #F2F2F2; color: #4CAF50;}
.button_channel {border-radius: 50%; width: 14%;}
.button_trigger {border-radius: 4px; width: 60%;}
.button_go {border-radius: 4px; width: 35%;}
.button_mode {border-radius: 8px; width: 32%;}
.button_setting {border-radius: 45%; width: 33%;}
.input_text {width: 50%; background-color: #F2F2F2; color: #4CAF50;}
.label {width: 50%; background-color: transparent; color: #4CAF50;}
button:focus{
    outline: none;
}
#knob {
	padding: 5px;
}
</style>
<script>
var COLOR_BACKGROUND	= "#404040";
var COLOR_TEXT			= "#FFFFFF";
var COLOR_BOUND			= "#FFFFFF";
var COLOR_GRIDLINE		= "gray";
var COLOR_LINE = ["#33FFFF", "#FF00FF", "#FF0000", "#00FF00", "#FF8C00", "#666666"];
//var COLOR_LINE = ["#0000FF", "#FF0000", "#00FF00", "#FF8C00", "#00FF00"];
//var COLOR_LINE = ["#33FFFF", "#FF0000", "#00FF00", "#FF8C00", "#00FF00"];
//var COLOR_LINE = ["#0000FF", "#FF0000", "#009900", "#FF9900", "#CC00CC", "#666666", "#00CCFF", "#000000"];

var LEGEND_WIDTH = 10;
var X_AXIS_TITLE_HEIGHT	= 40;
var Y_AXIS_VALUE_WIDTH	= 100;
var PLOT_AREA_PADDING = 2;
var X_GRIDLINE_NUM = 5;
var Y_GRIDLINE_NUM = 7;

var offset_y = [5, 10, 15, 20, 25, 30];
var voltage_div = [5, 5, 5, 5, 5, 5];
var time_div_ms = 1000; // ms
var full_screen = false;

var WSP_WIDTH;
var WSP_HEIGHT;
var X_AXIS_MIN_MS = 0;
var X_AXIS_MAX_MS = time_div_ms * X_GRIDLINE_NUM;
var Y_AXIS_MIN = 0;
var Y_AXIS_MAX = 35;

var PLOT_AREA_WIDTH;
var PLOT_AREA_HEIGHT;
var PLOT_AREA_PIVOT_X;
var PLOT_AREA_PIVOT_Y;

var knob_center_x;
var knob_center_y;
var knob_radius = 60;
var knob_angle = 0;
var knob_last_angle = 0;
var knob_click_state = 0;
var knob_mouse_xyra = {x:0, y:0, r:0.0, a:0.0};
var MIN_TOUCH_RADIUS = 20;
var MAX_TOUCH_RADIUS = 60;

var ws;
var canvas;
var knob;
var ctx1;
var ctx2;

var buffer = "";
var live_data = [];
var live_time_us = [];
var trigger_data = [];
var trigger_time_us = [];

var BASE_TIME_MS;
var DISPLAY_LIVE = 0;
var DISPLAY_TRIGGER = 1;

var TRIGGER_FALLING	= 0;
var TRIGGER_RISING	= 1;
var TRIGGER_BOTH	= 2;

var TRIGGER_STATE_IDLE	= 0;
var TRIGGER_STATE_RUN	= 1;
var TRIGGER_STATE_TRIGGERED	= 2;

var mode = "AUTO"; // no trigger
var display_mode = DISPLAY_LIVE;
var selected_channel = 0;
var setting_option = "OFFSET";
var trigger_state = TRIGGER_STATE_IDLE;
var trigger_type = TRIGGER_BOTH;
var trigger_value = 1.5;
var previous_state_value = 0;
var stop_points = [];
var trigger_count = 0;
var time_diff_us = 0;
var arduino_last_time_us = 0;
var arduino_time_overflow_count = 0;

function init() {
	canvas = document.getElementById("graph");
	//canvas.style.backgroundColor = COLOR_BACKGROUND;
	canvas.addEventListener("click", function(event){
		full_screen = !full_screen;
		canvas_resize();
	});
	
	knob = document.getElementById("knob");
	knob.addEventListener("touchstart", mouse_down);
	knob.addEventListener("touchend", mouse_up);
	knob.addEventListener("touchmove", mouse_move);
	knob.addEventListener("mousedown", mouse_down);
	knob.addEventListener("mouseup", mouse_up);
	knob.addEventListener("mousemove", mouse_move);

	var button_channel = document.getElementsByClassName("button_channel");
	for(var i = 0; i < button_channel.length; i++)
		button_channel[i].addEventListener("click", function(event){
			var channelId = parseInt(event.target.id);
			if(channelId < live_data.length)
				selected_channel = channelId;
			event.target.style.backgroundColor = "white"; 
		});

	document.getElementById("trigger_type").addEventListener("change", function(event){ trigger_type = parseInt(event.target.value);});

	document.getElementById("GO").addEventListener("click", function(event){ 
			if((mode == "SINGLE" && (trigger_state == TRIGGER_STATE_IDLE ||(trigger_state == TRIGGER_STATE_TRIGGERED &&  display_mode == DISPLAY_TRIGGER))) || mode == "NORMAL" && trigger_state == TRIGGER_STATE_IDLE) {
				trigger_state = TRIGGER_STATE_RUN;
				stop_points.splice(0, stop_points.length);
				event.target.style.backgroundColor = "white";
				trigger_value = parseFloat(document.getElementById("trigger_value").value);
				var latest_value = live_data[selected_channel][live_data[selected_channel].length - 1];
				previous_state_value = (latest_value - trigger_value) < 0 ? 0 : 1;
			}
		});

	var button_mode = document.getElementsByClassName("button_mode");
	for(var i = 0; i < button_mode.length; i++)
		button_mode[i].addEventListener("click", function(event){
				if(mode != event.target.id) {
					trigger_state = TRIGGER_STATE_IDLE;
					trigger_count = 0;
				}
				mode = event.target.id; event.target.style.backgroundColor = "white";
			});

	var button_setting = document.getElementsByClassName("button_setting");
	for(var i = 0; i < button_setting.length; i++)
		button_setting[i].addEventListener("click", function(event){ setting_option = event.target.id; event.target.style.backgroundColor = "white"; });

	BASE_TIME_MS = (new Date()).getTime();

	canvas_resize();

	var ws_host_addr = "<?echo _SERVER("HTTP_HOST")?>";

	if((navigator.platform.indexOf("Win") != -1) && (ws_host_addr.charAt(0) == "[")) {
		// network resource identifier to UNC path name conversion
		ws_host_addr = ws_host_addr.replace(/[\[\]]/g, '');
		ws_host_addr = ws_host_addr.replace(/:/g, "-");
		ws_host_addr += ".ipv6-literal.net";
	}

	ws = new WebSocket("ws://" + ws_host_addr + "/serial_monitor", "uint8.phpoc");
	ws.onopen = ws_onopen;
	ws.onclose = ws_onclose;
	ws.onmessage = ws_onmessage;
	ws.binaryType = "arraybuffer";
}
function ws_onopen() {
	ws.send("wsm_baud=115200\r\n");
}
function ws_onclose() {
	alert("WebSocket was closed. Please reload page!");
	ws.onopen = null;
	ws.onclose = null;
	ws.onmessage = null;
	ws = null;
}
function ws_onmessage(e_msg) {
	e_msg = e_msg || window.event; // MessageEvent

	var u8view = new Uint8Array(e_msg.data);
	buffer += String.fromCharCode.apply(null, u8view);
	buffer = buffer.replace(/\r\n/g, "\n");
	buffer = buffer.replace(/\r/g, "\n");

	while(buffer.indexOf("\n") == 0)
		buffer = buffer.substr(1);

	if(buffer.lastIndexOf("\n") <= 0)
		return;

	var pos = buffer.lastIndexOf("\n");
	var str = buffer.substr(0, pos);
	var new_sample_arr = str.split("\n");
	buffer = buffer.substr(pos + 1);

	for(var si = 0; si < new_sample_arr.length; si++) {
		var str = new_sample_arr[si];
		var arr = [];

		if(str.indexOf("\t") > 0)
			arr = str.split("\t");
		else
			arr = str.split(" ");

		var time_us = parseInt(arr[0]);

		// to avoid arduino time overflow and reset to zero
		if(time_us < arduino_last_time_us) {
			arduino_time_overflow_count++;
		}

		arduino_last_time_us = time_us;
		time_us += arduino_time_overflow_count * 4294967295; // unsigned long max value

		var cur_time_ms = (new Date()).getTime() - BASE_TIME_MS;

		time_diff_us = cur_time_ms * 1000 - time_us;

		for(var id = 1; id < arr.length; id++) {
			var value = parseFloat(arr[id]);
			var i = id - 1;

			if(isNaN(value))
				continue;

			if(i >= live_data.length) {
				live_data.push([value]); // new channel
				live_time_us.push([time_us]);
			} else {
				live_data[i].push(value);
				live_time_us[i].push(time_us);
			}

			if(i == selected_channel)
				handle_trigger(value);
		}

		if(live_data.length > 0) {
			for(var i = 0; i < live_data.length; i++) {
				if(live_data[i].length > 1) {
					while((time_us - live_time_us[i][0]) > (X_AXIS_MAX_MS * 1000)) {
						live_data[i].splice(0, 1);
						live_time_us[i].splice(0, 1);
					}
				}
			}
		}
	}
}
function handle_trigger(value) {
	if(value != trigger_value) {
		var current_state_value = (value - trigger_value) < 0 ? 0 : 1;

		if(current_state_value != previous_state_value) {
			var is_triggered = false;

			if(trigger_type == TRIGGER_BOTH)
				is_triggered = true;
			else if(trigger_type == TRIGGER_RISING && previous_state_value == 0)
				is_triggered = true;
			else if(trigger_type == TRIGGER_FALLING && previous_state_value == 1)
				is_triggered = true;

			previous_state_value = current_state_value;

			if(is_triggered) {
				if(trigger_state != TRIGGER_STATE_IDLE && (mode == "NORMAL" || (mode == "SINGLE" && trigger_state == TRIGGER_STATE_RUN))) {
					var cur_time_ms = (new Date()).getTime() - BASE_TIME_MS;
					var stop_time_ms = Math.round(cur_time_ms + X_AXIS_MAX_MS / 2);
					trigger_state = TRIGGER_STATE_TRIGGERED;
					stop_points.push({copied:false, stop_time_ms: stop_time_ms}); // not stop immidiately 
				}
			}
		}
	}
}
function map(x, in_min, in_max, out_min, out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
function update_loop() {
	update_data();
	update_view();
}
function update_data() {
	var cur_time_ms = (new Date()).getTime() - BASE_TIME_MS;

	if(trigger_state == TRIGGER_STATE_TRIGGERED && stop_points.length > 0 && stop_points[0].stop_time_ms < cur_time_ms) {
		display_mode = DISPLAY_TRIGGER;

		if(!stop_points[0].copied) {
			trigger_data = JSON.parse(JSON.stringify(live_data)); // deep copy
			trigger_time_us = JSON.parse(JSON.stringify(live_time_us)); // deep copy
			stop_points[0].copied = true;
			trigger_count++;
		}

		if(stop_points.length > 1) {
			if(stop_points[1].stop_time_ms < cur_time_ms) {
				stop_points.splice(0, 1);
			}
		}
	} else {
		if(display_mode == DISPLAY_TRIGGER) {
			display_mode = DISPLAY_LIVE;
			trigger_data.splice(0, trigger_data.length);
			trigger_time_us.splice(0, trigger_time_us.length);
		}
	}
}
function update_view() {
	//ctx1.font = "16px Arial";
	ctx1.textBaseline = "middle";

	ctx1.clearRect(0, 0, WSP_WIDTH, WSP_HEIGHT);
	ctx1.save();

	ctx1.translate(PLOT_AREA_PIVOT_X, PLOT_AREA_PIVOT_Y);
	ctx1.fillStyle = "black";
	ctx1.fillRect(0, -PLOT_AREA_HEIGHT, PLOT_AREA_WIDTH, PLOT_AREA_HEIGHT);

	var time_div_text = time_div_ms.toFixed(2) + " ms / div";
	ctx1.fillStyle = COLOR_TEXT;
	ctx1.textAlign = "center";
	ctx1.fillText(time_div_text, PLOT_AREA_WIDTH / 2, X_AXIS_TITLE_HEIGHT / 2);

	draw_gridline();
	draw_graph();
	ctx1.restore();

	draw_knob(knob_radius, knob_angle);
	update_button();
}
function draw_gridline() {
	ctx1.strokeStyle = COLOR_GRIDLINE;
	ctx1.lineWidth = 1;

	for(var i = 0; i <= Y_GRIDLINE_NUM; i++) {
		var y_gridline_px = -map(i, 0, Y_GRIDLINE_NUM, 0, (PLOT_AREA_HEIGHT - 2 * PLOT_AREA_PADDING));
		y_gridline_px = Math.round(y_gridline_px) - PLOT_AREA_PADDING + 0.5;
		ctx1.beginPath();
		ctx1.moveTo(0, y_gridline_px);
		ctx1.lineTo(PLOT_AREA_WIDTH, y_gridline_px);
		ctx1.stroke();
	}

	for(var i = 0; i <= X_GRIDLINE_NUM; i++) {
		var x_gridline_px = map(i, 0, X_GRIDLINE_NUM, 0, PLOT_AREA_WIDTH - 2 * PLOT_AREA_PADDING);
		x_gridline_px = Math.round(x_gridline_px) + PLOT_AREA_PADDING + 0.5;
		ctx1.beginPath();
		ctx1.moveTo(x_gridline_px, 0);
		ctx1.lineTo(x_gridline_px, -PLOT_AREA_HEIGHT);
		ctx1.stroke();
	}
}
function draw_graph() {
	var data_display;
	var time_display;

	if(display_mode == DISPLAY_LIVE) {
		data_display = live_data;
		time_display = live_time_us;
	} else {
		data_display = trigger_data;
		time_display = trigger_time_us;
	}

	var line_num = data_display.length;
	
	if(!line_num)
		return;

	var root_time_us;

	if(display_mode == DISPLAY_LIVE)
		root_time_us = ((new Date()).getTime() - BASE_TIME_MS) * 1000;
	else
		root_time_us = time_display[0][time_display[0].length - 1] + time_diff_us;

	ctx1.textAlign = "left";
	ctx1.lineWidth = 2;

	for(var channel = 0; channel < line_num; channel++) {
		var sample_num = data_display[channel].length;

		if(sample_num >= 2) {
			var offset = -map(offset_y[channel], Y_AXIS_MIN, Y_AXIS_MAX, 0, PLOT_AREA_HEIGHT);
			ctx1.strokeStyle = COLOR_LINE[channel];
			ctx1.fillStyle = COLOR_LINE[channel];
			ctx1.fillText("CH" + channel + " " + voltage_div[channel].toFixed(1) + " v / div", -Y_AXIS_VALUE_WIDTH, offset);

			ctx1.beginPath();

			for(var i = sample_num - 1; i >= 0; i--) {
				var sample_time_us = time_display[channel][i] + time_diff_us;
				var sample_value = data_display[channel][i];

				x_value = sample_time_us - (root_time_us - (X_AXIS_MAX_MS * 1000));

				if(x_value > 0) {
					y_value = map(sample_value, 0, 5, 0, voltage_div[channel]);
					y_value += offset_y[channel];

					x_px = map(x_value, X_AXIS_MIN_MS * 1000, X_AXIS_MAX_MS * 1000, 0, PLOT_AREA_WIDTH);
					y_px = -map(y_value, Y_AXIS_MIN, Y_AXIS_MAX, 0, PLOT_AREA_HEIGHT) - PLOT_AREA_PADDING;

					if(i == sample_num - 1)
						ctx1.moveTo(x_px, y_px);
					else
						ctx1.lineTo(x_px, y_px);
				}
			}

			ctx1.stroke();
		}
	}
}
function draw_knob(radius, angle) {
	ctx2.save();

	//ctx2.shadowBlur = 2;
	//ctx2.shadowColor = "LightGray";
	ctx2.translate(knob_center_x, knob_center_y);
	ctx2.rotate(-angle * Math.PI / 180);
	var grd = ctx2.createLinearGradient(-radius, 0, radius, 0);
	grd.addColorStop(0, "#DC143C");
	grd.addColorStop(0.1, "#DC143C");
	grd.addColorStop(0.30, "#F5D6D6");
	grd.addColorStop(0.47, "#DC143C");
	grd.addColorStop(0.53, "#DC143C");
	grd.addColorStop(0.70, "#F5D6D6");
	grd.addColorStop(0.9, "#DC143C");
	grd.addColorStop(1, "#DC143C");
	ctx2.fillStyle = grd;
	ctx2.beginPath();
	ctx2.arc(0, 0, radius, 0, 2 * Math.PI);
	ctx2.fill();
	ctx2.fillStyle = "#DC143C";
	ctx2.beginPath();
	ctx2.arc(0, 0, radius / 4, 0, 2 * Math.PI);
	ctx2.fill();
	ctx2.restore();
}
function update_button() {
	var button_channel = document.getElementsByClassName("button_channel");
	for(var i = 0; i < button_channel.length; i++) {
		if(button_channel[i].id == selected_channel) {
			button_channel[i].style.backgroundColor = COLOR_LINE[i];
			button_channel[i].style.border = "3px solid " + COLOR_LINE[i];
		} else {
			button_channel[i].style.backgroundColor = "#999999";

			if(i < live_data.length) {
				button_channel[i].style.border = "3px solid " + COLOR_LINE[i];
			}
			else {
				button_channel[i].style.border = "none";
			}
		}
	}

	if((mode == "SINGLE" && (trigger_state == TRIGGER_STATE_IDLE ||(trigger_state == TRIGGER_STATE_TRIGGERED &&  display_mode == DISPLAY_TRIGGER))) || mode == "NORMAL" && trigger_state == TRIGGER_STATE_IDLE)
		document.getElementById("GO").style.backgroundColor = "#4CAF50";
	else
		document.getElementById("GO").style.backgroundColor = "#999999";

	if(trigger_state != TRIGGER_STATE_TRIGGERED) {
		document.getElementById("TRIGGER").style.backgroundColor = "#999999";
	} else {
		document.getElementById("TRIGGER").style.backgroundColor = "#4CAF50";
	}
	document.getElementById("TRIGGER").innerHTML = "TRIGGER (" + trigger_count + ")";

	var button_mode = document.getElementsByClassName("button_mode");
	for(var i = 0; i < button_mode.length; i++) {
		if(button_mode[i].id == mode)
			button_mode[i].style.backgroundColor = "#4CAF50";
		else
			button_mode[i].style.backgroundColor = "#999999";
	}

	var button_setting = document.getElementsByClassName("button_setting");
	for(var i = 0; i < button_setting.length; i++) {
		if(button_setting[i].id == setting_option)
			button_setting[i].style.backgroundColor = "#DC143C";
		else
			button_setting[i].style.backgroundColor = "#999999";
	}
}
function check_update_xyra(event, knob_mouse_xyra) {
	var x, y, r, a;

	if(event.touches) {
		var touches = event.touches;

		x = (touches[0].pageX - touches[0].target.offsetLeft) - knob_center_x;
		y = knob_center_y - (touches[0].pageY - touches[0].target.offsetTop);
	} else {
		x = event.offsetX - knob_center_x;
		y = knob_center_y - event.offsetY;
	}

	/* cartesian to polar coordinate conversion */
	r = Math.sqrt(x * x + y * y);
	a = Math.atan2(y, x);

	//knob_mouse_xyra = {x:x, y:y, r:r, a:a};
	knob_mouse_xyra.x = x;
	knob_mouse_xyra.y = y;
	knob_mouse_xyra.r = r;
	knob_mouse_xyra.a = a;

	if((r >= MIN_TOUCH_RADIUS) && (r <= MAX_TOUCH_RADIUS))
		return true;
	else
		return false;
}
function mouse_down() {
	event.preventDefault();
	if(event.touches && (event.touches.length > 1))
		knob_click_state = event.touches.length;

	if(knob_click_state > 1)
		return;

	if(check_update_xyra(event, knob_mouse_xyra)) {
		knob_click_state = 1;
		knob_last_angle = knob_mouse_xyra.a / Math.PI * 180.0;
	}
}
function mouse_up() {
	event.preventDefault();
	knob_click_state = 0;
}
function mouse_move() {
	event.preventDefault();
	var angle_pos, angle_offset;

	if(event.touches && (event.touches.length > 1))
		knob_click_state = event.touches.length;

	if(knob_click_state != 1)
		return;

	if(!check_update_xyra(event, knob_mouse_xyra)) {
		knob_click_state = 0;
		return;
	}

	angle_pos = knob_mouse_xyra.a / Math.PI * 180.0;

	if(angle_pos < 0.0)
		angle_pos = angle_pos + 360.0;

	angle_offset = angle_pos - knob_last_angle;
	knob_last_angle = angle_pos;

	if(angle_offset > 180.0)
		angle_offset = -360.0 + angle_offset;
	else if(angle_offset < -180.0)
		angle_offset = 360 + angle_offset;

	knob_angle += angle_offset;

	if(setting_option == "TIME") {
		time_div_ms -= Math.round(angle_offset / 90 * time_div_ms);
		X_AXIS_MAX_MS = time_div_ms * X_GRIDLINE_NUM;
	} else if(setting_option == "VOLTAGE") {
		voltage_div[selected_channel] -= angle_offset / 90 * voltage_div[selected_channel];
	} else if(setting_option == "OFFSET") {
		offset_y[selected_channel] -= angle_offset / 90 * 5;
	}
}
function canvas_resize() {
	canvas.width = 0; // to avoid wrong screen size
	canvas.height = 0;

	var width = window.innerWidth - 20;
	var height = window.innerHeight - 20;

	if(full_screen) {
		document.getElementById("setting_container").style.display = "none";
		WSP_WIDTH = width;
		WSP_HEIGHT = height;
	} else {
		if(height >= 1.5 * width) {
			document.getElementById("setting_container").style.display = "block";
			WSP_WIDTH = width;
		} else {
			document.getElementById("setting_container").style.display = "inline-block";
			WSP_WIDTH = width - document.getElementById('setting_container').offsetWidth;
			WSP_HEIGHT = height;
		}
	}

	canvas.width = WSP_WIDTH;
	canvas.height = WSP_HEIGHT;

	PLOT_AREA_WIDTH		= WSP_WIDTH - Y_AXIS_VALUE_WIDTH;
	PLOT_AREA_HEIGHT	= WSP_HEIGHT - X_AXIS_TITLE_HEIGHT;
	PLOT_AREA_PIVOT_X	= Y_AXIS_VALUE_WIDTH;
	PLOT_AREA_PIVOT_Y	= PLOT_AREA_HEIGHT;

	knob.width = 250 - 8 * 2;
	knob.height = 150 - 8 * 2;

	knob_center_x = Math.round(knob.width / 2 );
	knob_center_y = Math.round(knob.height / 2);

	ctx1 = canvas.getContext("2d");
	ctx2 = knob.getContext("2d");

	ctx1.font = "14px Arial";
}

setInterval(update_loop, 1000 / 60);
window.onload = init;
</script>
</head>
<body onresize="canvas_resize()">

<div class="graph_container" id="graph_container">
	<canvas id="graph"></canvas>
</div>

<div class="setting_container" id="setting_container">
	<div class="setting" id="setting_channel">
	<button class="button button_channel" id="0">0</button>
	<button class="button button_channel" id="1">1</button>
	<button class="button button_channel" id="2">2</button>
	<button class="button button_channel" id="3">3</button>
	<button class="button button_channel" id="4">4</button>
	<button class="button button_channel" id="5">5</button>
	</div>

	<div class="setting">
		<input type="text" class="button2 label" value="Trigger Value: " disabled>
		<input type="text" class="button2 input_text" id="trigger_value" value="1.5">
	</div>

	<div class="setting">
	<select class="select" id="trigger_type">
		<option value="0">Falling</option>
		<option value="1">Rising</option>
		<option value="2" selected>Falling & Rising</option>
	</select>
	</div>

	<div class="setting" id="setting_trigger">
	<button class="button button_trigger" id="TRIGGER" disabled>TRIGGER</button>
	<button class="button button_go" id="GO">GO</button>
	</div>

	<div class="setting" id="setting_mode">
	<button class="button button_mode" id="AUTO">AUTO</button>
	<button class="button button_mode" id="NORMAL">NORMAL</button>
	<button class="button button_mode" id="SINGLE">SINGLE</button>
	</div>

	<canvas id="knob"></canvas>

	<div class="setting" id="setting_option">
	<button class="button button_setting" id="TIME">ms / div</button><br>
	<button class="button button_setting" id="VOLTAGE">V / div</button><br>
	<button class="button button_setting" id="OFFSET">Offset</button><br>
	</div>
</div>
</body>
</html>

Schematics

wiring
Hardware suetsx2ur7

Comments

Similar projects you might like

Arduino Bluetooth Basic Tutorial

by Mayoogh Girish

  • 454,633 views
  • 42 comments
  • 236 respects

Home Automation Using Raspberry Pi 2 And Windows 10 IoT

Project tutorial by Anurag S. Vasanwala

  • 285,473 views
  • 95 comments
  • 671 respects

Security Access Using RFID Reader

by Aritro Mukherjee

  • 229,311 views
  • 38 comments
  • 236 respects

OpenCat

Project in progress by Team Petoi

  • 195,898 views
  • 154 comments
  • 1,361 respects
Add projectSign up / Login