I built a pan-tilt system with 2 servos and laser diode. The system was controlled by Arduino Nano. By using Emgu.CV in C#, I created a grayscale image and convert it to a black-white image using GrayImg.ThresholdBinaryInv. I'm using Inverse Kinematics calculations but the laser diode couldn't mark the object well. Where did I make a mistake? Image coordinate system and servos coordinate systems are not same. Thus, I did mapping and scaling. It should work but the accuracy of the laser diode is inaccurate.
Arduino Code
#include <Servo.h>
int Th1, Th2, tmp;
Servo M1;
Servo M2;
void setup()
{
Serial.begin(9600);
pinMode(6,OUTPUT; // Laser Diode
digitalWrite(6,0);
Th1 = 0;
Th2 = 0;
M1.attach(3);
M1.write(90);
M2.attach(9);
M2.write(90);
}
void loop()
{
delay(200); //sync issue only
if(Serial.available()>=2)
{
Th1 = Serial.read(); //read only one byte
Th2 = Serial.read();
M1.write(Th1);
M2.write(Th2);
//Remove any extra worng reading
while(Serial.available()) tmp = Serial.read();
// Run the robotic arm here. For testing, we will
digitalWrite(6,1);
delay(500);
digitalWrite(6,0);
delay(500);
//switch On or switch off a LED according to Th1 value
//Serial.print('1'); // This tell the PC that Arduino is Ready for next angles
}
}
C# Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.Util;
using Emgu.CV.Structure;
namespace ObjectMarker
{
public partial class Form1 : Form
{
private Capture capture;
private Image<Bgr, Byte> IMG;
private Image<Gray,Byte> blackWhite;
private Image<Gray, Byte> GrayImg;
private int N, Xpx, Ypx; // N -> Number of non-black pixels
private double Xcm, Ycm;
private double myScale;
private double Th1, Th2;
static SerialPort _serialPort;
public byte []Buff = new byte[1];
public Form1() //Constructor of the Form1
{
InitializeComponent();
//myScale = 65.0 / 480.0;
myScale = 1.7 / 13;
_serialPort = new SerialPort();
_serialPort.PortName = "COM4";//Set your board COM
_serialPort.BaudRate = 9600;
_serialPort.Open();
}
private void processFrame(object sender, EventArgs e) // Most important function
{ //You're not connected any camera - null
if (capture == null)//very important to handel excption - capture - point of the camera
{
try
{
capture = new Capture(1); //creatine a object
}
catch (NullReferenceException excpt)
{
MessageBox.Show(excpt.Message);
}
}
IMG = capture.QueryFrame();// capture the current frame. Get an image.
GrayImg = IMG.Convert<Gray, Byte>();
blackWhite = GrayImg.ThresholdBinaryInv(new Gray(25),new Gray (255));
Xpx = 0;
Ypx = 0;
N = 0;
for (int i = 0; i < blackWhite.Width; i++) {
for (int j = 0; j < blackWhite.Height; j++) {
if(blackWhite[j,i].Intensity > 128){
N++;
Xpx += i;
Ypx += j;
}
}
}
if(N>0){
Xpx = Xpx / N;
Ypx = Ypx / N;
Xpx = (blackWhite.Width / 2) - Xpx; //320 - xpx
Ypx = Ypx - (blackWhite.Height / 2); // ypx - 240
Xcm = Xpx * myScale;
Ycm = Ypx * myScale;
double d3 = 28; // Laser to wall dist. (?)
double Zcm = 108; // Cam to wall dist.
double d1 = 3.50; // Joint to joint dist.
double l2 = 4.50; // Joint to laser dist. (?)
textBox1.Text = Xcm.ToString();
textBox2.Text = Ycm.ToString();
textBox3.Text = N.ToString();
textBox11.Text = Zcm.ToString();
textBox12.Text = myScale.ToString();
//Inverse Calculations
double Px, Py, Pz,Diff = 0;
// Mapping
//Px = Zcm;
Px = -1 * Zcm;
Py = -1 * Xcm;
Pz = Ycm + Diff; // The laptop has built-in camera and the pan-tilt above the keyboard so there's a distance between camera and pan-tilt system (laser diode)
textBox8.Text = Px.ToString();
textBox9.Text = Py.ToString();
textBox10.Text = Pz.ToString();
Th1 = Math.Atan((Py / Px));
Th2 = Math.Atan((Math.Sin(Th1) * (Pz - d1)) / Py);
//Th1 = Math.Atan(Ycm / Xcm);
//Th2 = Math.Atan((Math.Sin(Th1) * (Zcm - d1)) / Ycm);
textBox4.Text = Th1.ToString();
textBox5.Text = Th2.ToString();
Th1 = (Th1 * (180 / Math.PI));
Th2 = (Th2 * (180 / Math.PI));
Th1 += 90;
Th2 += 90;
textBox6.Text = Th1.ToString();
textBox7.Text = Th2.ToString();
label11.Text = trackBar1.Value.ToString();
label12.Text = trackBar2.Value.ToString();
Buff[0] = (byte)trackBar1.Value; //Th1
Buff[2] = (byte)trackBar2.Value; //Th2
_serialPort.Write(Buff,0,2);
}
else
{
textBox1.Text = "";
textBox2.Text = "";
textBox3.Text = N.ToString();
Buff[0] = (byte)90; //Th1
Buff[2] = (byte)90; //Th2
_serialPort.Write(Buff, 0, 2);
}
try
{
imageBox1.Image = IMG;
imageBox2.Image = GrayImg;
imageBox3.Image = blackWhite;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void button1_Click(object sender, EventArgs e)
{
//Application.Idle += processFrame; //Serial comm between arduino and computer
timer1.Enabled = true;
button1.Enabled = false;
button2.Enabled = true;
}
private void button2_Click(object sender, EventArgs e)
{
//Application.Idle -= processFrame;
timer1.Enabled = false;
button1.Enabled = true;
button2.Enabled = false;
}
private void button3_Click(object sender, EventArgs e) // I measure the object size from paint. It gave me pixel size. Then I measure the real size of the image. Real size divided by pixel size gives scale.
{
IMG.Save("G:\\CurrentFrame" + ".jpg");
}
void Timer1Tick(object sender, EventArgs e)
{
processFrame(sender,e);
}
}
}
EDIT: Images has been added. The problem is if I change the tape location inaccuracy of the system isn't accurate. In some points laser marked the tape very well but mostly on corners there's huge difference between laser and tape.
https://i.stack.imgur.com/fmm1x.png
https://i.stack.imgur.com/Quadr.png
https://i.stack.imgur.com/9SWa4.png
This question is very hard to answer, as there are many places where an error could have crept in. Without further diagnosis and knowledge of the system, it's almost impossible to give a clear answer. I suggest you perform some more diagnosis, and if the answer still eludes you, reformulate your question (or close it when you find the answer yourself).
Some possible sources of error (and points for diagnosis) could be:
In the Arduino code, you throw away anything after the first two bytes of Serial communication. Why would there be extra bytes? Are you sure the extra bytes are garbage, and, inversely, the first two bytes are the correct ones?
In the C# mapping of coordinates, you perform a translation of the coordinates. Are you sure the system is exactly axis-parallel? An error of just a few degrees quickly adds up. You might need to add a rotational transformation.
Your C# code returns an angle (in degrees) for the servo. This could be negative, or above 255, so converting it to a byte could, on overflow, lead to passing a (completely) wrong value to the Arduino.
Related
I have a list of code that is implemented from a button click load event in one method. I also have a Mouse Event that returns the e.X and e.Y values. MY problem is that the program runs through the button click load event and I can see the mouse events but I do not know how to return the program to run it again from a position with the new e.X and e.Y values. I have tried to separate it into various methods but I cannot seem to be able to 'call' them or 'goto' them. Any advice on what I need to do? The code is listed below.
Many thanks Steve
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.IO;
using System.Runtime.InteropServices;
namespace HPXSV8
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private int curX;
private int curY;
//private double ll;
//private double lr;
//private double lt;
//private double lb;
//private double GPSLat;
//private double GPSLong;
public int allimages;
public void Form1_Load(object sender, EventArgs e)
{
}
public void btnLoadFiles_Click(object sender, EventArgs e)
{
//Loads the data file syvwname
OpenFileDialog ofd = new OpenFileDialog();
ofd.Title = "Load Survey View Data File";
ofd.Filter = "Survey View File |*.syvw";
if (ofd.ShowDialog() == DialogResult.OK)
{
string syvwname = ofd.FileName;
string sfn = ofd.SafeFileName;
string[] loaddata = new string[500];
int allimages = 0;
string line;
StreamReader file = new StreamReader(syvwname);
List<string> list = new List<string>();
while ((line = file.ReadLine()) != null)
{
listBox1.Items.Add(line);
loaddata[allimages] = line;
allimages++;
}
string[,] gpsdata = new string[allimages, 3];
int z = 0;
do
{
//slices up the .syvw file into 3 string values comma delimited
string chop1 = loaddata[z];
string[] part = chop1.Split(',');
string[] parts = part.ToArray();
gpsdata[z, 0] = parts[0];
gpsdata[z, 1] = parts[1];
gpsdata[z, 2] = parts[2];
z++;
}
while (z < allimages);
//load jpg 0001 to picture box
int strlength = syvwname.Length;
int sfnamelength = sfn.Length;
string mainimage = gpsdata[0, 0];
string trimname = syvwname.Remove(strlength - sfnamelength, sfnamelength);
string filelocation = (trimname + #"\" + mainimage);
pictureBox1.ImageLocation = filelocation;
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//Calculate values of lat and long to compare with image values
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
double picboxX = 800.00; ////check these values in final program size
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//Calculate fraction position of cursor to picture
double curposX = curX / picboxX;
//+++++++++++++++++++++++++++++++++++++++++++++++++
label1.Text = "CurposX = " + curposX.ToString();
//+++++++++++++++++++++++++++++++++++++++++++++++++
//Calculate values of lat and long to compare with image values
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
double picboxY = 600.00;
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//Calculate fraction position of cursor to picture
double curposY = curY / picboxY;
//+++++++++++++++++++++++++++++++++++++++++++++++++
label2.Text = "CurposY =" + curposY.ToString(); // Test Line
//+++++++++++++++++++++++++++++++++++++++++++++++++
/// Calculate GPSLongditude from mouse positions
double ll = Convert.ToDouble(gpsdata[2, 1]); //??????????????????????????????
double lr = Convert.ToDouble(gpsdata[2, 2]);
//GPS Postion calculations based on picture box mouse position
//Longitude
double GPSLong = ll + ((lr - ll) * curposX);
//+++++++++++++++++++++++++++++++++++++++++++++++++
label3.Text = "GpsLong =" + GPSLong.ToString(); /// Test Line
//+++++++++++++++++++++++++++++++++++++++++++++++++
// Calculate GPSLatitude from mouse positions
double lb = Convert.ToDouble(gpsdata[1, 2]);
double lt = Convert.ToDouble(gpsdata[1, 1]);
//GPS Postion calculations based on picture box mouse position
//Latitude
double GPSLat = lt + ((lb - lt) * curposY);
//Look for values in my_datatable that fall within +/- 0.0005 of GPS Lat and Long
// This may be made to be a variable to increase the number of images
//=================================================================================
//Look for values in gpsdata[]
int row = 3;
int chs = 0;
string[] chosenimage = new string[allimages]; // value if all images are selected
//=================================================================================
double area = 0.0005; //later make this a UI variable
//=================================================================================
do
{
//make 4 values +/- 0.0005 of gpsdata[]
if (GPSLat <= (Convert.ToDouble(gpsdata[row, 1]) + area))
{
if (GPSLat >= (Convert.ToDouble(gpsdata[row, 1]) - area))
{
if (GPSLong <= (Convert.ToDouble(gpsdata[row, 2]) + area))
{
if (GPSLong >= (Convert.ToDouble(gpsdata[row, 2]) - area))
{
chosenimage[chs] = gpsdata[row, 0];
chs++;
}
}
}
}
row++;
}
while (row < allimages);
//+++++++++++++++++++++++++++++++++++++++++++++++++
label4.Text = "GpsLat = " + GPSLat.ToString();
//+++++++++++++++++++++++++++++++++++++++++++++++++
while ((line = file.ReadLine()) != null)
{
listBox2.Items.Add(line);
chosenimage[chs] = line;
allimages++;
}
}
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
public void btnSelect_Click(object sender, EventArgs e)
{
// Use to select images to use for notes
//label1.Text = insertnote.Text;
}
public void insertnote_MouseDown(object sender, MouseEventArgs e)
{
//Use for entering text into notepad area
}
public void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
//Get location of mouse click on 001.jpg
int curX = e.X;
int curY = e.Y;
//+++++++++++++++++++++++++++++++++++++++++++++++++
label1.Text = "X = " + curY.ToString() + " ; Y =" + curY.ToString(); /// Test Line
//+++++++++++++++++++++++++++++++++++++++++++++++++
}
}
} // End of Namespace HPXSV8
I hope I'm understanding your question. You have two events. You want Event A to update the X and Y values and the other Event B to do some calculation based on those values. If Event A occurs during the calculation, you want to redo the calculation.
I'll have to check up on this later, but I'm sure that the events you wrote are blocking. Meaning Event A has to complete before it can proceed with Event B. Additionally, Event B has to complete before it can proceed with Event A. This is why it won't work.
You could try doing this with a worker thread. Event B signals the thread to perform calculations. Event A will tell the worker thread that a value is invalid and has to redo a calculation.
So I'm playing around with the BouncyGame. I made it so that when you start the game you need to press the screen for it to start. I would like to implement this whenever you play a new round as well. I tried to reuse this att the bottom of my code but it made it extremely laggy.
// Register for touch events
var touchListener = new CCEventListenerTouchAllAtOnce();
touchListener.OnTouchesEnded = OnTouchesEnded;
touchListener.OnTouchesMoved = OnTouchesEnded;
AddEventListener(touchListener, this);
}
void OnTouchesEnded(List<CCTouch> touches, CCEvent touchEvent)
{
if (touches.Count > 0)
{
Schedule(RunGameLogic);
scoreLabel.Text = "Score: 0";
paddleSprite.RunAction(new CCMoveTo(.1f, new CCPoint(touches[0].Location.X, paddleSprite.PositionY)));
}
}
I have no idea how to do this, tried for 2 hours with 0 results. Any suggestions are welcome.
Here's the full code.
using System;
using System.Collections.Generic;
using CocosSharp;
using Microsoft.Xna.Framework;
namespace CocosSharpGameTest
{
public class IntroLayer : CCLayerColor
{
// Define a label variable
CCLabel scoreLabel;
CCSprite paddleSprite, ballSprite;
public IntroLayer() : base(CCColor4B.Black)
{
// create and initialize a Label
scoreLabel = new CCLabel("Tap to GO!", "Arial", 80, CCLabelFormat.SystemFont);
// add the label as a child to this Layer
scoreLabel.PositionX = 50;
scoreLabel.PositionY = 1000;
scoreLabel.AnchorPoint = CCPoint.AnchorUpperLeft;
AddChild(scoreLabel);
paddleSprite = new CCSprite("paddle.png");
AddChild(paddleSprite);
ballSprite = new CCSprite("ball.png");
AddChild(ballSprite);
}
protected override void AddedToScene()
{
base.AddedToScene();
// Use the bounds to layout the positioning of our drawable assets
CCRect bounds = VisibleBoundsWorldspace;
// position the label on the center of the screen
paddleSprite.PositionX = 100;
paddleSprite.PositionY = 100;
ballSprite.PositionX = 320;
ballSprite.PositionY = 640;
// Register for touch events
var touchListener = new CCEventListenerTouchAllAtOnce();
touchListener.OnTouchesEnded = OnTouchesEnded;
touchListener.OnTouchesMoved = OnTouchesEnded;
AddEventListener(touchListener, this);
}
void OnTouchesEnded(List<CCTouch> touches, CCEvent touchEvent)
{
if (touches.Count > 0)
{
Schedule(RunGameLogic);
scoreLabel.Text = "Score: 0";
paddleSprite.RunAction(new CCMoveTo(.1f, new CCPoint(touches[0].Location.X, paddleSprite.PositionY)));
}
}
float ballXVelocity;
float ballYVelocity;
// How much to modify the ball's y velocity per second:
const float gravity = 140;
int score = 0;
void RunGameLogic(float frameTimeInSeconds)
{
// This is a linear approximation, so not 100% accurate
ballYVelocity += frameTimeInSeconds * -gravity;
ballSprite.PositionX += ballXVelocity * frameTimeInSeconds;
ballSprite.PositionY += ballYVelocity * frameTimeInSeconds;
bool overlap = ballSprite.BoundingBoxTransformedToParent.IntersectsRect(paddleSprite.BoundingBoxTransformedToParent);
bool movingDown = ballYVelocity < 0;
if (overlap && movingDown)
{
ballYVelocity *= -1;
const float minXVelocity = -300;
const float maxXVelocity = 300;
ballXVelocity = CCRandom.GetRandomFloat(minXVelocity, maxXVelocity);
score++;
scoreLabel.Text = "Score: " + score;
}
float ballRight = ballSprite.BoundingBoxTransformedToParent.MaxX;
float ballLeft = ballSprite.BoundingBoxTransformedToParent.MinX;
float screenRight = VisibleBoundsWorldspace.MaxX;
float screenLeft = VisibleBoundsWorldspace.MinX;
bool shouldReflectXVelocity =
(ballRight > screenRight && ballXVelocity > 0) ||
(ballLeft < screenLeft && ballXVelocity < 0);
if (shouldReflectXVelocity)
{
ballXVelocity *= -1;
}
if (ballSprite.PositionY < VisibleBoundsWorldspace.MinY)
{
ballSprite.PositionX = 320;
ballSprite.PositionY = 640;
ballXVelocity = 0;
ballYVelocity = 0;
ballYVelocity *= -1;
scoreLabel.Text = "Score: 0";
score = 0;
}
}
}
}
Thanks in advance!
Figured it out!
There is an "Unschedule" Method built into Cocossharp.
Ref. https://developer.xamarin.com/api/namespace/CocosSharp/
I just added
Unschedule(RunGameLogic);
at the very en of my RunGameLogic method under
if (ballSprite.PositionY < VisibleBoundsWorldspace.MinY)
So once the ballSprite is out of bounds it will Unschedule what i Scheduled in my OntouchesEnded method. That means the code goes back to listening for touches.
Might have made some errors, but this is as best I could figure it out and it works!
So here's my problem, First image
the highlighted portion was the time when i put the serial port close(i call it pause). Here's my code on that button:
private void disconnectbutton_Click(object sender, EventArgs e)
{
if (serialPort.IsOpen == false) return;
serialPort.Close();
}
Now, my problem here is, when I reconnect my Program to Arduino, here's my code:
public void connectbutton_Click(object sender, EventArgs e)
{
try
{
serialPort.PortName = portscombobox.Text;
serialPort.BaudRate = Convert.ToInt32(baudratecombobox.Text);
if (serialPort.IsOpen) return;
serialPort.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
MessageBox.Show("Invalid COM Port number... Please choose the correct COM Port number. \n Looking at Device manager will help :)");
Application.Restart();
}
The graph didn't follow the last points position, specifically the time it stopped. The curve pick the last y-value and draw it until the time I reconnect but there's no problem on the curve when disconnected/pause. You can look at the 2nd pic to show there's no feed while disconnected.
Second image
When i reconnect, the graph will look like the first picture above..
I believe my problem here was the improper use of Environment.tickcount and I tried searching for the solution but unfortunately I cant solve this on my own based on my knowledge.
I want my graph will only move to its x axis(time) when I try to connect it to arduino and read the data, and my elapsed time should only run when Im only connected not disconnected.
Here's my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using ZedGraph;
namespace WindowsFormsApplication4
{
public partial class Form1 : Form
{
string datafromcom;
delegate void SetTextCallback(string text);
// Starting time in milliseconds
int tickStart = 0;
//double time = 0;
//int t;
public Form1()
{
InitializeComponent();
checkbox1.Enabled = false;
checkbox2.Enabled = false;
checkbox3.Enabled = false;
checkbox4.Enabled = false;
}
public void connectbutton_Click(object sender, EventArgs e)
{
try
{
serialPort.PortName = portscombobox.Text;
serialPort.BaudRate = Convert.ToInt32(baudratecombobox.Text);
if (serialPort.IsOpen) return;
serialPort.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
MessageBox.Show("Invalid COM Port number... Please choose the correct COM Port number. \n Looking at Device manager will help :)");
Application.Restart();
}
serialPort.DtrEnable = true;
serialPort.RtsEnable = true;
getbutton.Enabled = false;
checkbox1.Enabled = true;
checkbox2.Enabled = true;
checkbox3.Enabled = true;
checkbox4.Enabled = true;
exitbutton.Enabled = false;
connectbutton.Enabled = false;
disconnectbutton.Enabled = true;
portscombobox.Enabled = false;
baudratecombobox.Enabled = false;
}
private void disconnectbutton_Click(object sender, EventArgs e)
{
if (serialPort.IsOpen == false) return;
serialPort.Close();
//tickStart += new tickStart();
exitbutton.Enabled = true;
connectbutton.Enabled = true;
disconnectbutton.Enabled = false;
portscombobox.Enabled = true;
baudratecombobox.Enabled = true;
checkbox1.Enabled = false;
checkbox2.Enabled = false;
checkbox3.Enabled = false;
checkbox4.Enabled = false;
}
private void Form1_Load(object sender, EventArgs e)
{
//this.Size = Screen.PrimaryScreen.WorkingArea.Size;
GraphPane myPane = z1.GraphPane;
z1.IsShowHScrollBar = true;
z1.IsShowVScrollBar = true;
z1.IsEnableHZoom = true;
z1.IsEnableVZoom = true;
// Disable the AutoScrollRange option (because we have set the scroll range manually)
z1.IsAutoScrollRange = true;
// Synchronize the Axes
z1.IsSynchronizeYAxes = true;
z1.IsSynchronizeXAxes = true;
z1.GraphPane.IsBoundedRanges = true;
// Horizontal pan allowed
z1.IsEnableHPan = true;
z1.IsEnableVPan = true;
z1.IsShowPointValues = true;
//z1.PointValueFormat = "0.00";
z1.PointDateFormat = "d";
// Change the color of the title
myPane.Title.FontSpec.FontColor = Color.Black;
myPane.Title.Text = " ";
myPane.XAxis.Title.Text = "Time (Seconds)";
myPane.YAxis.Title.Text = "Sample Potential, Volts";
// Save 20000 points. At 50 ms sample rate, this is one minute
// The RollingPointPairList is an efficient storage class that always
// keeps a rolling set of point data without needing to shift any data values
RollingPointPairList list = new RollingPointPairList(20000);
// Initially, a curve is added with no data points (list is empty)
// Color is blue, and there will be no symbols
LineItem curve = myPane.AddCurve(null, list, Color.Black, SymbolType.None);
//Sample at 50ms intervals
//timer1.Interval = 100;
//timer1.Enabled = true;
//timer1.Start();
// Just manually control the X axis range so it scrolls continuously
// instead of discrete step-sized jumps
myPane.XAxis.Scale.Min = 0;
myPane.XAxis.Scale.Max = 10;
myPane.XAxis.Scale.MinorStep = .5;
myPane.XAxis.Scale.MajorStep = 1;
myPane.YAxis.Scale.Min = -10;
myPane.YAxis.Scale.Max = 10;
myPane.YAxis.Scale.MinorStep = .1;
myPane.YAxis.Scale.MajorStep = 1;
// Add gridlines to the plot, and make them gray
myPane.XAxis.MajorGrid.IsVisible = true;
myPane.YAxis.MajorGrid.IsVisible = true;
myPane.XAxis.MajorGrid.Color = Color.LightGray;
myPane.YAxis.MajorGrid.Color = Color.LightGray;
myPane.Fill.Color = System.Drawing.Color.DarkGray;
// Make both curves thicker
curve.Line.Width = 2F;
// Increase the symbol sizes, and fill them with solid white
curve.Symbol.Size = 8.0F;
curve.Symbol.Fill = new Fill(Color.White);
// Add a background gradient fill to the axis frame
myPane.Chart.Fill = new Fill(Color.White,
Color.FromArgb(255, 255, 255), -45F);
// Scale the axes
z1.AxisChange();
//z1.AxisChange();
z1.GraphPane.AxisChange();
// Save the beginning time for reference
tickStart = Environment.TickCount;
}
private void exitbutton_Click(object sender, EventArgs e)
{
Application.Exit();
}
public void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
while (serialPort.BytesToRead > 0)
{
datafromcom = serialPort.ReadLine();
if (datafromcom.Trim() != "")
{
float dtfm = float.Parse(datafromcom);
//richtextbox.Text = "dynamic";
if (z1.GraphPane.CurveList.Count <= 0)
return;
// Get the first CurveItem in the graph
LineItem curve = z1.GraphPane.CurveList[0] as LineItem;
if (curve == null)
return;
// Get the PointPairList
IPointListEdit list = curve.Points as IPointListEdit;
// If this is null, it means the reference at curve.Points does not
// support IPointListEdit, so we won't be able to modify it
if (list == null)
return;
double time = (Environment.TickCount - tickStart) / 1000.0;
//curve.AddPoint(time, iDAT);
list.Add(time, dtfm);
Scale xScale = z1.GraphPane.XAxis.Scale;
if (time > xScale.Max - xScale.MajorStep)
{
xScale.Max = time + xScale.MajorStep ;
xScale.Min = xScale.Max - 10.0;
}
z1.AxisChange();
z1.Invalidate();
this.BeginInvoke(new SetTextCallback(SetText), new object[] { datafromcom });
}
}
}
catch (Exception )
{
}
}
private void SetText(string text)
{
this.richtextbox.Text = text;
//richtextbox.AppendText(text);
richtextbox.ScrollToCaret();
}
public void timer1_Tick(object sender, EventArgs e)
{
}
private void resetbutton_Click(object sender, EventArgs e)
{
Application.Restart();
}
}
}
I want to control two servos via PC, using a mouse x-y coordinates. x-y coordinates of the cursor is sent to the serial port:
For the arduino part, I am getting 6 character string and dividing it into 2 parts. Then these parts are converted into integer value and send to arduino pins to set servo position:
#include <Servo.h>
String readString, servo1, servo2;
Servo myservo1; // create servo object to control a servo
Servo myservo2;
void setup() {
Serial.begin(9600);
myservo1.attach(6); //the pin for the servo control
myservo2.attach(7);
}
void loop() {}
void serialEvent() {
while (Serial.available()) {
delay(2);
if (Serial.available() >0) {
char c = Serial.read(); //gets one byte from serial buffer
readString += c; //makes the string readString
}
}
if (readString.length() >0) {
Serial.println(readString); //see what was received
// expect a string like 07002100 containing the two servo positions
servo1 = readString.substring(0, 3); //get the first three characters
servo2 = readString.substring(3, 6); //get the next three characters
Serial.println(servo1); //print ot serial monitor to see results
Serial.println(servo2);
int n1; //declare as number
int n2;
char carray1[6]; //magic needed to convert string to a number
servo1.toCharArray(carray1, sizeof(carray1));
n1 = atoi(carray1);
char carray2[6];
servo2.toCharArray(carray2, sizeof(carray2));
n2 = atoi(carray2);
myservo1.write(n1); //set servo position
myservo2.write(n2);
readString="";
}
}
However, the code is very slow. I need to move the mouse very slowly to make servos move. Instant movement from let's say from 50 degree to 170 takes 1 second to move the servo. Could you offer a better option to control two servos in this case?
Controlling only one servo works very well, and it moves servo instantly without any lags:
#include <Servo.h>
Servo x;
int xval;
void setup() {
Serial.begin(9600);
x.attach(9);
}
void loop() {
}
void serialEvent() {
xval = Serial.parseInt();
if(xval!=0) {
x.write(xval);
}
}
Code in C#:
public partial class Form1 : Form
{
SerialPort port;
public Form1()
{
InitializeComponent();
init();
}
private void init()
{
port = new SerialPort();
port.PortName = "COM1";
port.BaudRate = 9600;
//porty = new SerialPort();
try
{
port.Open();
}
catch (Exception e1)
{
MessageBox.Show(e1.Message);
}
}
int x = 0, y = 0;
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
Graphics g = CreateGraphics();
Pen p = new Pen(Color.Navy);
Pen erase = new Pen(Color.White);
x = e.X; y = e.Y;
double curWidth = this.Width / 180;
double x2 = Math.Round(x / curWidth);
double curHeight = this.Height / 180;
double y2 = Math.Round(y / curHeight);
label1.Text = x2.ToString(); label2.Text = y2.ToString();
string valx = x2.ToString();
string valy = y2.ToString();
while(valx.Length < 3)
{
valx = '0' + valx;
}
while(valy.Length < 3)
{
valy = '0' + valy;
}
string valsum = valx+valy;
label3.Text = valsum.ToString();
if (port.IsOpen)
{
port.WriteLine(valsum);
}
}
}
In the above code I am taking x and y coordinates and converting it to approx 180 range. After I join the values into one string to send it through serial port.
In order to improve the performance of your code you can remove the delay and use the maximum Baud Rate (115200). For the sake of simplicity I compacted your Arduino code below:
#include <Servo.h>
// create 2 servos x and y
Servo x;
Servo y;
void setup() {
Serial.begin(115200);
x.attach(6);
y.attach(7);
}
void loop() {
//empty
}
void serialEvent() {
//set servo position.
if (Serial.available()>0) {
//Receives 2 angles separated per a comma
x.write(Serial.parseInt()); //Returns first angle
y.write(Serial.parseInt()); //Returns second angle
Serial.read(); //Read the '\n'. I am not sure if it is necessary.
}
}
Increase the Baud Rate also in the C# source code:
port.BaudRate = 115200;
In addition, I suggest you send the angles separated by a comma through the serial. So, instead of:
while(valx.Length < 3) {
valx = '0' + valx;
}
while(valy.Length < 3) {
valy = '0' + valy;
}
string valsum = valx+valy;
label3.Text = valsum.ToString();
if (port.IsOpen) {
port.WriteLine(valsum);
}
Replace with:
if (port.IsOpen) {
port.WriteLine(valx + ',' + valy); //Note that WriteLine appends a '\n' character in the end.
}
I want to develop an app to match your tinnitus frequency : A frequency is played and the user decrease or increase the freqency by pressing a plus or minus button. (see part of the codes, based on some coding from stackoverflow thx :-))
public static short[] BufferSamples = new short[44100 * 1 * 2];
private SourceVoice sourceVoice;
private AudioBuffer buffer;
private int Tfreq;
public MatchTinn()
{
InitializeComponent();
Loaded += MatchTinn_Loaded;
TFreq = 5000;
}
private void MatchTinn_Loaded(object sender, RoutedEventArgs e)
{
var dataStream = DataStream.Create(BufferSamples, true, true);
buffer = new AudioBuffer
{
LoopCount = AudioBuffer.LoopInfinite,
Stream = dataStream,
AudioBytes = (int)dataStream.Length,
Flags = BufferFlags.EndOfStream
};
FillBuffer(BufferSamples, 44100, Tfreq);
var waveFormat = new WaveFormat();
XAudio2 xaudio = new XAudio2();
MasteringVoice masteringVoice = new MasteringVoice(xaudio);
sourceVoice = new SourceVoice(xaudio, waveFormat, true);
// Submit the buffer
sourceVoice.SubmitSourceBuffer(buffer, null);
}
private void FillBuffer(short[] buffer, int sampleRate, int frequency)
{
if (sourceVoice != null)
{
sourceVoice.FlushSourceBuffers();
}
double totalTime = 0;
for (int i = 0; i < buffer.Length - 1; i += 2)
{
double time = (double)totalTime / (double)sampleRate;
short currentSample = (short)(Math.Sin(2 * Math.PI * frequency * time) * (double)short.MaxValue);
buffer[i] = currentSample;
buffer[i + 1] = currentSample;
totalTime++;
}
private void m1_OnTap(object sender, GestureEventArgs e)
{
Tfreq = Tfreq - 1;
if (Tfreq < 0)
{
Tfreq = 0;
}
FillBuffer(BufferSamples, 44100, Tfreq);
}
private void p1_OnTap(object sender, GestureEventArgs e)
{
Tfreq = Tfreq + 1;
if (Tfreq > 16000)
{
Tfreq = 16000;
}
FillBuffer(BufferSamples, 44100, Tfreq);
}
Playing the frequency is fine, but when the user presses a button you here a clicking sound when the frequency is updated. Do you have any idea what makes the sound and how i can get rid of it?
Thanks.
When you change the frequency, you're causing a discontinuity in the waveform that manifests as a click. Instead of making your signal calculations against absolute time, you should keep track of the phase of your sine calculation (e.g. a value from 0 to 2*pi), and figure out how much you need to add to your phase (subtracting 2*pi every time you exceed 2*pi) for the next sample when playing a specific frequency. This way, when you change frequency, the phase that you supply as a parameter to Math.Sin doesn't change abruptly causing a click.
Expanding on the answer #spender gave (I need 50 rep to add comment to his answer), I had a similar problem with naudio. I was able to solve the issue by adding two bool values that monitored the current sign of the sine value and the previous sign of the sine value. If the previous sine was negative and the current sine is positive, we know we can safely adjust the frequency of the sine wave.
double sine = amplitude * Math.Sin(Math.PI * 2 * frequency * time);
isPreviousSineWaveValPositive = isSineWaveValPositive;
if (sine < 0)
{
isSineWaveValPositive = false;
}
else
{
isSineWaveValPositive = true;
}
// When the time is right, change the frequency
if ( false == isPreviousSineWaveValPositive && true == isSineWaveValPositive )
{
time = 0.0;
frequency = newFrequency;
}
Here's an example how you can get rid of the clicking. Instead of using a time, you should keep track of the current phase and calculate how much the phase is changed on the required frequency. Also this _currentPhase must be persistent so it will have the previous value. (declaring it within the method would result in a click aswell (on most frequencies)
private double _currentPhase = 0;
private void FillBuffer(short[] buffer, int sampleRate, int frequency)
{
if (sourceVoice != null)
{
sourceVoice.FlushSourceBuffers();
}
var phaseStep = ((Math.PI * 2) / (double)sampleRate) * frequency;
for (int i = 0; i < buffer.Length - 1; i += 2)
{
_currentPhase += phaseStep;
short currentSample = (short)(Math.Sin(_currentPhase) * (double)short.MaxValue);
buffer[i] = currentSample;
buffer[i + 1] = currentSample;
}
}