Take a natural course in artificial intelligence, from theory to action, and then click here to enter the portal

In the last article, we completed the prototype of a serial assistant, which realized the basic function of sending and receiving strings, and opened/closed the serial port for exception processing. This article will gradually improve the function according to the process:

1. Conception function

First is the receiving part, to add a “clear receive” button to clear the receiving area; Because serial communication protocols are usually 8bit data (the lower 7bit indicates ASCII code and the higher 1bit indicates parity), as a development and debugging tool, it also needs to display the 8bit code in hexadecimal format for easy debugging, so it also needs to add two single boxes to select ASCII or HEX display.

The next part is the sending part, which corresponds to the previous part. In the debugging process, hexadecimal data needs to be sent directly, so two checkboxes need to be added to select whether to send ASCII code or HEX code. In addition to this function, also need to add automatic send function, automatic send new line function is convenient for debugging;

2. Design the layout

1) RadioButton control (RadioButton)

The receiving data display can only be selected for ASCII display or HEX display at the same time, so radio button controls are used. Only one of the radio button controls in the same group (such as the containers described earlier) can be selected at the same time, which meets our requirements.

2) CheckBox control (CheckBox)

This is usually used to select some optional functions, such as whether to display the data received time, whether to automatically send the new line when sending, whether to enable the auto send function, etc. It has an important property with the previous RadioButton — CHecked: false indicates that the data is not selected. Is selected.

3) Numerical increment and subtraction control (NumericUpDown)

Displays a single value that the user can increase and decrease by clicking the up/down button on the control. Here we use to set the interval for automatic sending.

4) Timer component

It is called a component because, like the serial port before it, it cannot be directly operated by the user; It is a component that raises events at user-defined intervals;

Timer is mainly an Interval attribute, which is used to set a fixed time value. The default unit is ms. After setting the Timer, you can call the start () and stop () methods of the Timer object to start or stop the Timer. After startup, the Timer will trigger the Tick event every Interval milliseconds. If the initial value is set to 100ms, we only need to set a global variable I. When the time is up to I ++, when I ==10, (the Timer is used in the same way as the microcontroller.)

The overall design of the renderings are as follows:

3. Set up the background

According to the previous idea, after the interface layout is completed, it is necessary to start the most important part of a software – setting up the background:

1. Status bar Serial port status display

You can just add the code here without saying much;

Label6.text = "Serial port open "; label6.ForeColor = Color.Green;Copy the code
Label6.text = "Serial port closed "; label6.ForeColor = Color.Red;Copy the code

2. Receiving part

Before, we called serialPort1.readexisting () directly in the serial port receive event to read the entire receive cache, and then append to the receive display text box, but here we need to display the number of received and sent bytes in the bottom status bar, so we can’t read the whole. To read/send byte by byte and count;

1) Class attributes

We define a variable that counts the bytes received. This variable acts like a global variable in C, called a property of a class in C#. This property can be accessed by methods in the class, or by the object itself, as follows:

public partial class Form1 : Form { private long receive_count = 0; // Receives a byte count equivalent to the global variable....... }Copy the code

2) Read the buffer by byte

First get the number of bytes of data in the receive buffer by accessing the BytesToRead**** property of the serial port. Then call the Read (byte[] buffer, int offset, int count) method of the serial port to Read some bytes from the input buffer and write those bytes to the specified offset in the byte array:

Private void SerialPort1_DataReceived(Object sender, System.IO.Ports.SerialDataReceivedEventArgs e) { int num = serialPort1.BytesToRead; Byte [] Received_buf = new byte[num]; // Obtain the number of bytes in the receive buffer. Receive_count += num; // Add nun serialport1. Read(received_buf,0,num); // Insert num bytes into the receive buffer.Copy the code

Received_buf = received_buf = received_buf = received_buf = received_buf To know to receive the contents of a text box shows are presented in the form of a string, that is to say we are appended to the contents of the text box must be a string type, even the hexadecimal display is also convert data into hexadecimal string type display, type tells how the next byte data into type string data;

3) StringBuilder

We need to traverse the entire received_buf array, each byte data into characters, then add to our overall String (to be sent to the receiving text box to display the complete String), but the type String is not allowed to make any changes to the content, what’s more, we need to traverse the additional characters, The StringBuilder not only allows arbitrary changes to the content, but also provides useful methods such as Append, Remove, Replace, Length, ToString, etc. At this time, it is very easy to construct a string. The code is as follows:

public partial class Form1 : Form { private StringBuilder sb = new StringBuilder(); // To avoid repeated calls in the receiver handler function, declare it as a global variable.Copy the code

 

Private void SerialPort1_DataReceived(Object sender, System. IO. Ports. SerialDataReceivedEventArgs e) {/ / after the second step the code in sb. The Clear (); Foreach (byte b in received_buf) {sb.append (b.tostring ()); foreach (byte b in received_buf) {sb.append (b.tostring ()); } Invoke invoke ((EventHandler)(delegate {textBox_receive.appendText (sb.tostring ())); label7.Text = "Rx:" + receive_count.ToString() + "Bytes"; })); } // code omitted}Copy the code

Let’s run it to see what it looks like:

 

As you can see, when we send the character “1”, the status bar shows that we received 1byte data, indicating that the count is normal, but we received the character “49”. This is because the received byte data stores the ASCII code value, and the ToString () method of the byte object is called, as shown in the following figure. This method converts the ASCII value 49 to the string “49” instead of the corresponding ASCII character ‘1’;

Encoding Class (Encoding Class)

Following the previous problem, we need to convert byte to the corresponding ASCII code, which is called decoding (the process of converting a series of encoded bytes into a set of characters). Similarly, the process of converting a set of characters into a series of bytes is called encoding;

There are two ways to convert ASCII code: The first method is implemented using the ASCII attribute of Encoding Class, and the second method is implemented using the ASCIIEncoing Class derived from Encoding Class. Then call the GetString (Byte[]) method to decode the entire array to ASCII as follows:

 

sb.Append(Encoding.ASCII.GetString(received_buf)); // Decode the entire array to AN ASCII arrayCopy the code

Run it again and you can see the normal display:

5) Byte values are converted to hexadecimal characters for display

In section 3, we analyzed byte.toString (), which can convert byte type directly to character display. For example, if we receive the ASCII value of character 1 is 49, we can convert 49 directly to “49” display. In this case, we need to display 49 in hex. This is to display “31” (0x31). There is no real change in this conversion, just a number conversion, so the format control ToString (String) method is used, as shown in the following figure:

In this case, we need to convert it to a 2-bit hexadecimal text display. In addition, since ASCII and HEX can only be displayed at the same time, we also need to check whether the radio button is selected. The code is as follows:

If (radioButton2.Checked) {foreach (byte B in received_buf) {sb.append (b.tostring ("X2") + "); Sb.append (encoding.ascii.GetString(received_buf)); // Convert byte data to 2-bit hexadecimal text, separated by Spaces. // Decode the whole array into an ASCII array}Copy the code

Run it again to see the final result (first send “Mculover66” and enter, then send “1” and enter) :

6) DateTime Struct

Struct (DateTime Struct); Struct (DateTime Struct);

private DateTime current_time = new DateTime(); // Declare as a global variable to avoid repeated calls in receive handlersCopy the code

Current_time is a DateTime type, which is converted to text by calling the ToString(String) method, as shown in the following figure:

In the display, it is still necessary to judge whether the user is selected, the code is as follows:

 

// To access UI resources, Invoke(EventHandler)(delegate {if (checkbox1.checked) {// Display current_time = system.datetime.now; Textbox_receiver.appendtext (current_time.tostring ("HH:mm:ss") + "" + sb.tostring ()); } else {// Do not display the time textbox_receive.appendText (sb.tostring ()); } label7.Text = "Rx:" + receive_count.ToString() + "Bytes"; }));Copy the code

Run it again to see the effect:

 

7) Clear the receive button

There is no need to say more here, directly paste the code:

private void button3_Click(object sender, EventArgs e) { textBox_receive.Text = ""; // Empty the receive textBox_sess. Text = ""; // Clear the send text box receive_count = 0; Label7.text = "Rx:" + receive_count.tostring () + "Bytes"; // Refresh interface}Copy the code

 

3. Sending part

First of all, in order to avoid sending errors, we will send button failure at startup, only after successfully opened to enable, disable after closing, this part of the code is simple, self-written;

1) Byte count + send new line

With that in mind, it’s easy to implement these two features. Note the difference between Write and WriteLine:

2) Simple application of regular expressions

This is an important, important, important knowledge — regular expressions! The data we want to send is 0x31, so the function should be designed to send 0x31 when the user typed “31” in HEX mode. This is not difficult, just extract the string every two characters and convert it into a hexadecimal byte value. Write (byte[] buffer, int offset, int count); if the user enters multiple hexadecimal characters at the same time, the data will be sent.

At this time you need to use the regular expression, the user can input hexadecimal data separated by any number of Spaces, and then we use the regular expression to match the space, and replace with “”, equivalent to delete the space, so the whole string traversal, with the method just sent one by one can!

The complete send code is as follows:

 

private void button2_Click(object sender, EventArgs e) { byte[] temp = new byte[1]; If (serialPort1.IsOpen) {int num = 0; // The serial port is enabled. If (radioButton4.checked) {// Send in HEX mode // First use regular expressions to match the hexadecimal characters in the user input string buf = textbox_send-text;  string pattern = @"\s"; string replacement = ""; Regex rgx = new Regex(pattern); string send_data = rgx.Replace(buf, replacement); Num = (send_data.length - send_data.length % 2) / 2; for (int i = 0; i < num; i++) { temp[0] = Convert.ToByte(send_data.Substring(i * 2, 2), 16); serialPort1.Write(temp, 0, 1); If (send_data.length % 2!); // If (send_data.length % 2!); = 0) {temp[0] = convert. ToByte(send_data.substring (textbox_send-text.length -1,1), 16); serialPort1.Write(temp, 0, 1); num++; If (checkbox3.checked) {// Automatically send a new line serialPort1.writeline (""); If (checkbox3.checked) {// Automatically send a new line serialPort1.writeline (textbox_send.text); // Automatically send a new line serialPort1.writeline (textbox_send.text); num = textBox_send.Text.Length + 2; } else {// Do not send a new line serialPort1.write (textbox_send-text); num = textBox_send.Text.Length; } } send_count += num; Label8.text = "Tx:" + send_count.tostring () + "Bytes"; }} catch (Exception ex) {serialport1.close (); / / catch exceptions, create a new object, before can no longer use serialPort1 = new System. IO. Ports. SerialPort (); // Refresh the COM port option combobox1.items.clear (); comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames()); / / ring and display the exception to the user System. Media. SystemSounds. Beep. The Play (); Button1.text = "open serial port "; button1.BackColor = Color.ForestGreen; MessageBox.Show(ex.Message); comboBox1.Enabled = true; comboBox2.Enabled = true; comboBox3.Enabled = true; comboBox4.Enabled = true; comboBox5.Enabled = true; }}Copy the code

Here’s how it works:

3) Timer component

Automatic sending function is the last function we built, the second section of the timer component has said that the timer and the MCU timer usage is basically the same, so, the general idea is as follows: When checked automatically send boxes will be on the right side of the numerical control increase or decrease the value assigned to the timer as a fixed value, at the same time will be on the right side of the numerical control fail choice, then when the timer time arrives, the timer values and call the send button callback function, when send for check automatically, stop the timer, can choose the right numerical control at the same time, the code is as follows:

private void checkBox2_CheckedChanged(object sender, EventArgs e) {if (checkbox2.checked) {// Start sending numericupdown1. Enabled = false; Timer1. Interval = (int) numericupdown1. Value; // Timer1.start (); // Start timer label6.Text = "serial port open" + "automatic sending..." ; } else {// Auto send function is not selected, stop auto send numericupdown1. Enabled = true; Timer1.stop (); // Stop timer label6.Text = "Serial port open "; }} private void timer1_Tick(Object Sender, EventArgs e) {button2_Click(button2, new EventArgs()); // Call the send button callback function}Copy the code

Run it to see what it looks like: