By default, SerialPort uses ASCIIEncoding to encode the characters
You're confusing methods, which read/write strings or chars, with methods, which read/write bytes.
E.g., when you'll call this:
port.Write("абв")
you'll get "???" (0x3F 0x3F 0x3F) in the port buffer by default. On the other hand, this call:
// this is equivalent of sending "абв" in Windows-1251 encoding
port.Write(new byte[] { 0xE0, 0xE1, 0xE2 }, 0, 3)
will write sequence 0xE0 0xE1 0xE2 directly, without replacing bytes to 0x3F value.
UPD.
Let's look into source code:
public void Write(string text)
{
// preconditions checks are omitted
byte[] bytes = this.encoding.GetBytes(text);
this.internalSerialStream.Write(bytes, 0, bytes.Length, this.writeTimeout);
}
public void Write(byte[] buffer, int offset, int count)
{
// preconditions checks are omitted
this.internalSerialStream.Write(buffer, offset, count, this.writeTimeout);
}
Do you see the difference?
Method, that accepts string, converts strings to a byte array, using current encoding for port. Method, that accepts byte array, writes it directly to a stream, which is wrapper around native API.
And yes, documentation fools you.
Answer from Dennis on Stack OverflowBy default, SerialPort uses ASCIIEncoding to encode the characters
You're confusing methods, which read/write strings or chars, with methods, which read/write bytes.
E.g., when you'll call this:
port.Write("абв")
you'll get "???" (0x3F 0x3F 0x3F) in the port buffer by default. On the other hand, this call:
// this is equivalent of sending "абв" in Windows-1251 encoding
port.Write(new byte[] { 0xE0, 0xE1, 0xE2 }, 0, 3)
will write sequence 0xE0 0xE1 0xE2 directly, without replacing bytes to 0x3F value.
UPD.
Let's look into source code:
public void Write(string text)
{
// preconditions checks are omitted
byte[] bytes = this.encoding.GetBytes(text);
this.internalSerialStream.Write(bytes, 0, bytes.Length, this.writeTimeout);
}
public void Write(byte[] buffer, int offset, int count)
{
// preconditions checks are omitted
this.internalSerialStream.Write(buffer, offset, count, this.writeTimeout);
}
Do you see the difference?
Method, that accepts string, converts strings to a byte array, using current encoding for port. Method, that accepts byte array, writes it directly to a stream, which is wrapper around native API.
And yes, documentation fools you.
This
port.Encoding = System.Text.Encoding.UTF8;
string testStr = "TEST";
port.Write(testStr);
and this
byte[] buf = System.Text.Encoding.UTF8.GetBytes(testStr);
port.Write(buf, 0, buf.Length);
will result in the same bytes being transmitted. In the latter one the Encoding of the serial port could be anything.
The serial port encoding only matters for methods that read or write strings.
Sending one byte at a time to serial port.
arduino uno - Write array to serial port - Arduino Stack Exchange
c# - Serial Port Communication - Code Review Stack Exchange
How could I send an array to serial port?
I guess you thought about avoiding of Thread.Sleep() method, not Wait() you mentioned in title. I can't test your code because I don't have either COM port nor motor. I would like to give you some general tips how to improve your code.
Let's implement some SOLID and DRY principles. I would start with class Motor which will represent your device. You can implement for example singleton class for this purpose. This helps you to separate your motor logic from other logic such is user interface. I can also imagine your port settings as a part of application configuration.
Motor.cs
public sealed class Motor : IDisposable
{
public static readonly Motor Instance = new Motor();
private Motor()
{
MotorPort = new SerialPort();
MotorPort.PortName = "COM6";
MotorPort.Handshake = Handshake.RequestToSend;
MotorPort.ReceivedBytesThreshold = 8;
MotorPort.DataReceived += new SerialDataReceivedEventHandler(dataRecievedHandler);
MotorPort.Open();
}
public SerialPort MotorPort { get; private set; }
public decimal Position { get; private set; }
public void StopRotate() { }
public void StartRotate(string speed) { }
public void MoveToPosition(int position) { }
private void dataRecievedHandler(object sender, SerialDataReceivedEventArgs e)
{
}
public void Dispose()
{
if (MotorPort.IsOpen)
{
MotorPort.Close();
}
}
}
Instead of using global fields (M, buf), encapsulate them into object and set the proper access modifier or better create properties. Use meaningful names your fields and properties. Use Capitalization Convention.
You can isolate your repeating strings into separate static class to make your string constant repository which would be more easily maintainable against direct using of particular string.
StringConstants.cs
static class StringConstants
{
public const string Prefix = "SR,00,002,";
public const string PrefixCrLf = "SR,00,002\r\n";
}
Extension methods can simplify your code and make it more readable.
Extensions.cs
static class Extensions
{
public static decimal GetDecimalOutput(this SerialPort port)
{
return decimal.Parse(Regex.Split(port.ReadLine(), StringConstants.Prefix)[1]);
}
public static void SendCommand(this SerialPort port)
{
port.WriteLine(StringConstants.PrefixCrLf);
}
}
If you have implemented Motor.cs methods, you can use it or extend it.
Form1.cs
private void btnTest_Click(object sender, EventArgs e)
{
Motor.Instance.StartRotate(speed);
Thread.Sleep(5000);
Motor.Instance.StopRotate();
Motor.Instance.MoveToPosition(0);
//read sensor
serialPort2.SendCommand();
decimal caliber = serialPort2.GetDecimalOutput();
do
{
serialPort2.SendCommand();
actualValue = serialPort2.GetDecimalOutput();
}
while (actualValue <= caliber);
}
I am not experienced in serial port programming, but I am pretty sure that event SerialPort.DataReceived should be used to invoke subscribed method as you do with your serialPort3. I don't know exactly why you are using Thread.Sleep(), because I cannot see getPosi(C); method.
Just a minor thing that jumped out at me:
private void rotate(byte[] A, string Test) //Rotate Motor Right
{
A = MB.TMCL_RMR(Test);
serialPort3.Write(A, 0, A.Length);
}
Why are you passing in a byte array to just replace it with something else on the first line?
private void SendRotateCommand(string speed)
{
var commandBytes = MB.TMCL_RMR(speed);
motorPort.Write(commandBytes, 0, commandBytes.Length);
}
I've guessed that the Test parameter was actually the rotation speed from a comment in the method that calls it.
You need to improve your naming throughout. Your code is very simple but is very difficult to follow simply because the naming is so poor.
Another small thing:
public byte[] GET_POSI()
{
byte[] E = new byte[9];
E[0] = 0x1;
E[1] = 0x6;
E[2] = 0x1;
E[3] = 0x0;
E[4] = 0x0;
E[5] = 0x0;
E[6] = 0x0;
E[7] = 0x0;
E[8] = 0x08;
return E;
}
Could be written more simply as:
public byte[] GET_POSI()
{
return new byte[] { 1, 6, 1, 0, 0, 0, 0, 0, 8 };
}
I can't really understand what you're trying to save the position for. Or even how you're attempting to do it. You are sending messages to the motor and it's sending things back. You get a message, you figure out what it means and act on it.
You're also mixing DataReceived with SerialPort.ReadLine they both use the same underlying memory stream and you should be using one or the other - not both.
You're also freezing your UI for large chunks of time while you wait for operations to complete. It's fine to accept the command from the user and then update the UI as the action progresses. E.g. show the current position as the motor is rotating, when it gets to the right point, stop it.
By modifying the code in Rion's answer below, I was able to test this and got some surprising results. I used the following code (modified from Rion, thanks):
class Program {
static void Main(string[] args) {
// Create a stopwatch for performance testing
var stopwatch = new Stopwatch();
// Test content
var data = GetTestingBytes();
var ports = SerialPort.GetPortNames();
using (SerialPort port = new SerialPort(ports[0], 115200, Parity.None, 8, StopBits.One)) {
port.Open();
// Looping Test
stopwatch.Start();
foreach (var item in data) {
port.BaseStream.WriteByte(item);
}
stopwatch.Stop();
Console.WriteLine($"Loop Test: {stopwatch.Elapsed}");
stopwatch.Start();
port.Write(data, 0, data.Length);
stopwatch.Stop();
Console.WriteLine($"All At Once Test: {stopwatch.Elapsed}");
}
Console.Read();
}
static byte[] GetTestingBytes() {
var str = String.Join(",", Enumerable.Range(0, 1000).Select(x => Guid.NewGuid()).ToArray());
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
}
The results were extremely surprising; using the method that takes an array of bytes took almost exactly twice as long at 12.5818728 seconds compared to 6.2935748 seconds when just calling WriteByte repeatedly. This was the opposite result than I was expecting. Either way, I wasn't expecting one method to be twice as fast as the other!
If anyone can figure out why this is the case, I would love to know!
Based on a quick glance at the source, it looks like the SerialPort.Write() method actually just points to the Write() method of the underlying stream anyways :
public void Write(byte[] buffer, int offset, int count)
{
if (!IsOpen)
throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
if (buffer==null)
throw new ArgumentNullException("buffer", SR.GetString(SR.ArgumentNull_Buffer));
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
if (count < 0)
throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
if (buffer.Length - offset < count)
throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
if (buffer.Length == 0) return;
internalSerialStream.Write(buffer, offset, count, writeTimeout);
}
If I had to wager a guess, I would assume that the difference between the two performance wise might be negligible. I suppose if you had some test data, you could create a StopWatch to actually time the differences between both approaches.
Update with Performance Tests
I didn't test this using SerialPort objects, but instead opted for basic MemoryStream ones as the real heart of the question seemed to be if writing bytes in a loop is more performant than writing them using a byte[].
For test data, I simply generated 1000 random Guid objects and concatenated them into a string :
static byte[] GetTestingBytes()
{
var str = String.Join(",", Enumerable.Range(0, 1000).Select(x => Guid.NewGuid()).ToArray());
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
As far as the tests themselves go :
// Create a stopwatch for performance testing
var stopwatch = new Stopwatch();
// Test content
var data = GetTestingBytes();
// Looping Test
using (var loop = new MemoryStream())
{
stopwatch.Start();
foreach (var item in data)
{
loop.WriteByte(item);
}
stopwatch.Stop();
Console.WriteLine($"Loop Test: {stopwatch.Elapsed}");
}
// Buffered Test
using (var buffer = new MemoryStream())
{
stopwatch.Start();
buffer.Write(data, 0, data.Length);
stopwatch.Stop();
Console.WriteLine($"Buffer Test: {stopwatch.Elapsed}");
}
After running the tests a few times, the averages after 1000 tests broke down as follows :
LOOP: 00:00:00.0976584
BUFFER: 00:00:00.0976629
So the looping approach, at least in the context of using MemoryStream objects, appears to be the winner.
You can see the entire testing code here if you want to run it yourself.
They are not Unknow characters, that's what you printed with:
int i = Serial.write(buf, sizeof(buf));
Just check an ASCII table buf[0] = 125 = '{'
With write() you are writing raw data without any kind of format. Your first byte is the value 125, in binary 01111101. This byte correspond to the character { if it is intepreted as char. Your serial communication interprets the incoming byte as char, so it prints '{`.
If you want to print 125 as string on a serial communication, you have to send buf[] = {49, 50, 53}. Or you have to convert your interget into a string.
what's also wrong is that you are using the byte type with values higher than 255. Try changing to int16_t.