Ferramenta de Diagnóstico para Porta Serial com Interface C# WinForms

Este artigo descreve a implementação de um utilitário de teste de porta serial usando C# e Windows Forms. O aplicativo permite configurar parâmetros de comunicação serial, enviar e receber dados em formatos ASCII e hexadecimal, e monitorar estatísticas de tráfego. A seguir, apresenta-se o código-fonte modificado com nomes alternativos de variáveis e estrutura lógica ajustada.

Implementação Principal (SerialDebuggerWindow.cs)


using System;
using System.IO.Ports;
using System.Text;
using System.Timers;
using System.Windows.Forms;

namespace SerialTools
{
    public partial class SerialDebuggerWindow : Form
    {
        private SerialPort commPort = new SerialPort();
        private Timer refreshTimer = new Timer(1500);
        private StringBuilder inputBuffer = new StringBuilder();
        private long rxByteCount = 0;
        private long txByteCount = 0;
        private object syncRoot = new object();

        public SerialDebuggerWindow()
        {
            InitializeComponent();
            SetupInterface();
            RefreshAvailablePorts();
            refreshTimer.Elapsed += OnTimerElapsed;
        }

        private void SetupInterface()
        {
            this.Size = new System.Drawing.Size(960, 700);
            configGroup.Text = "Configuração da Porta";
            controlGroup.Text = "Controle de Dados";
            monitorGroup.Text = "Monitoramento";
            
            portSelector.Items.AddRange(SerialPort.GetPortNames());
            baudSelector.Items.AddRange(new object[] { 4800, 9600, 19200, 38400, 57600, 115200 });
            dataBitsSelector.Items.AddRange(new object[] { 7, 8 });
            paritySelector.Items.AddRange(Enum.GetNames(typeof(Parity)));
            stopBitsSelector.Items.AddRange(Enum.GetNames(typeof(StopBits)));

            inputBox.AcceptsReturn = true;
            outputBox.Multiline = true;
            outputBox.ScrollBars = ScrollBars.Vertical;
            outputBox.Font = new System.Drawing.Font("Courier New", 11);

            statusLabel.Text = "Desconectado";
            refreshTimer.Start();
        }

        private void RefreshAvailablePorts()
        {
            portSelector.Items.Clear();
            portSelector.Items.AddRange(SerialPort.GetPortNames());
            if (portSelector.Items.Count > 0)
                portSelector.SelectedIndex = 0;
        }

        private void ToggleConnection(object sender, EventArgs e)
        {
            try
            {
                if (!commPort.IsOpen)
                {
                    ApplyPortSettings();
                    commPort.DataReceived += HandleDataReceived;
                    commPort.Open();
                    connectButton.Text = "Desconectar";
                    statusLabel.Text = $"Conectado: {commPort.PortName}";
                }
                else
                {
                    commPort.Close();
                    connectButton.Text = "Conectar";
                    statusLabel.Text = "Desconectado";
                }
            }
            catch (Exception error)
            {
                MessageBox.Show($"Erro: {error.Message}", "Falha", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void ApplyPortSettings()
        {
            try
            {
                commPort.PortName = portSelector.Text;
                commPort.BaudRate = int.Parse(baudSelector.Text);
                commPort.DataBits = int.Parse(dataBitsSelector.Text);
                commPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), stopBitsSelector.Text);
                commPort.Parity = (Parity)Enum.Parse(typeof(Parity), paritySelector.Text);
                commPort.Handshake = Handshake.None;
            }
            catch (Exception configError)
            {
                throw new InvalidOperationException($"Configuração inválida: {configError.Message}");
            }
        }

        private void HandleDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            string receivedData = commPort.ReadExisting();
            lock (syncRoot)
            {
                inputBuffer.Append($"[{DateTime.Now:HH:mm:ss.fff}] RX: {receivedData}\r\n");
                rxByteCount += receivedData.Length;
            }
            RefreshOutput();
        }

        private void SendHexData(object sender, EventArgs e)
        {
            try
            {
                byte[] dataPacket = ConvertHexToBytes(inputBox.Text);
                commPort.Write(dataPacket, 0, dataPacket.Length);
                txByteCount += dataPacket.Length;
                LogMessage($"Envio HEX: {inputBox.Text}");
            }
            catch
            {
                MessageBox.Show("Formato hexadecimal inválido", "Erro de Entrada", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        private void SendTextData(object sender, EventArgs e)
        {
            string textContent = inputBox.Text;
            commPort.Write(textContent);
            txByteCount += textContent.Length;
            LogMessage($"Envio ASCII: {textContent}");
        }

        private void RefreshOutput()
        {
            if (InvokeRequired)
            {
                Invoke(new Action(() =>
                {
                    outputBox.Text = inputBuffer.ToString();
                    rxCounterLabel.Text = $"{rxByteCount} bytes";
                    txCounterLabel.Text = $"{txByteCount} bytes";
                    statusLabel.Text = commPort.IsOpen ? "Ativo" : "Inativo";
                }));
            }
        }

        private byte[] ConvertHexToBytes(string hexString)
        {
            if (hexString.Length % 2 != 0) throw new ArgumentException("Formato hexadecimal inválido");
            
            byte[] result = new byte[hexString.Length / 2];
            for (int index = 0; index < hexString.Length; index += 2)
            {
                result[index / 2] = Convert.ToByte(hexString.Substring(index, 2), 16);
            }
            return result;
        }

        private void LogMessage(string message)
        {
            lock (syncRoot)
            {
                inputBuffer.Append($"[{DateTime.Now:HH:mm:ss.fff}] LOG: {message}\r\n");
            }
        }

        private void OnTimerElapsed(object sender, ElapsedEventArgs e)
        {
            RefreshAvailablePorts();
        }

        private void WindowClosing(object sender, FormClosingEventArgs e)
        {
            if (commPort.IsOpen) commPort.Close();
            refreshTimer.Stop();
        }
    }
}

Definição da Interface (SerialDebuggerWindow.Deisgner.cs)

O código do designer define a estrutura da interface com grupos para configuração, operação de dados e monitoramento. Os controles incluem caixas de seleção para portas, baud rate, bits de dados, paridade e stop bits, além de botões para conexão e envio de dados, e caixas de texto para entrada e saída.

Módulos de Funcionalidade

Cálculo de CRC16:


public static class ChecksumCalculator
{
    public static ushort ComputeCrc16(byte[] payload)
    {
        ushort accumulator = 0xFFFF;
        foreach (byte element in payload)
        {
            accumulator ^= (ushort)(element << 8);
            for (int bitCount = 0; bitCount < 8; bitCount++)
            {
                if ((accumulator & 0x8000) != 0)
                {
                    accumulator = (ushort)((accumulator << 1) ^ 0xA001);
                }
                else
                {
                    accumulator <<= 1;
                }
            }
        }
        return accumulator;
    }
}

Gerenciamento de Logs:


public class FileLogger
{
    public void RecordToDisk(string destination, string content)
    {
        try
        {
            File.AppendAllText(destination,
                $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} | {content}\r\n");
        }
        catch (Exception ioError)
        {
            MessageBox.Show($"Falha ao gravar: {ioError.Message}");
        }
    }
}

Monitoramento de Tráfego:


public class TrafficAnalyzer
{
    private long previousRx = 0;
    private long previousTx = 0;
    
    public (double DownloadSpeed, double UploadSpeed) CalculateRates(long currentRx, long currentTx)
    {
        double download = (currentRx - previousRx) / 1024.0;
        double upload = (currentTx - previousTx) / 1024.0;
        
        previousRx = currentRx;
        previousTx = currentTx;
        
        return (Math.Round(download, 2), Math.Round(upload, 2));
    }
}

Configuração e Execução

Pré-requisitos:

  • Runtime .NET Framework 4.8 ou superior
  • Ambiente de desenvolvimento Visual Studio 2019+
  • Sistema operacional Windows 10 ou 11

Processo de Compilação:

  1. Criar um projeto Windows Forms no Visual Studio
  2. Inserir os componentes de interface e lógica conforme os trechos de código
  3. Definir SerialDebuggerWindow como formulário inicial
  4. Executar a compilação e teste

Modo de Uso:

  • Selecionar a porta COM e configurar parâmetros na seção superior
  • Utiliazr os botões para enviar dados em formato texto ou hexadecimal
  • Monitorar as mensagens de recepção e transmissão na área de saída
  • As estatísticas de bytes são atualizadas em tempo real

Possíveis Extensões

  • Adicionar suporte a protocolos como Modbus RTU com decodificação automática
  • Integrar geração de portas seriais virtuais para testes isolados
  • Implementar criptografia AES para comunicação segura
  • Desenvolver interface de rede para acesso remoto à porta serial

Tags: C# WinForms SerialPort diagnóstico ComunicaçãoSerial

Publicado em 7-1 04:26