liuqian 发表于 2016-1-5 12:23:44

初学C#串口编程,接收丢数据,请大家看看问题在哪里

编辑1:修改错别字

数据存在1G的flash中,每个subsector是4K字节,包含292条14字节的log数据,每个log数据头部是4字节的时间码(兼做识别用)
通讯过程如下:
上位机发送 rxlog -1
单片机应答 start:0,total:32768,表示从数据从subsector【0】开始,共用32768个subsector
while(total>0)
{
上位机发送rxlog xxx(subsector);
单片机应答一个subsector的数据;
total--;
}
subsector数据包格式如下:
: Head: 0xFA 0xF5 0xAA 0x55 :n*14 bytes of Log data pack : Tail: 0xFA 0xF5 0xCC 0x33 :CRC32 of Head+Logs+Tail
log数据的时间码不会出现Head和Tail,如果接收中log数据的时间码是Tail,表示这个sebsector结束

每个subsector数据包,给1秒时间,超时没有完成,就重来

运行中出现crc err,看内存中数据是串口接收数据不完整,丢数据非常严重

程序如下:

    public partial class Form1 : Form
    {
      public Form1()
      {
            InitializeComponent();
            //MessageBox.Show("Please confirm SW1 is all-off", "Notice!");

            // got all com ports
            btnOpen.Enabled = false;
            string[] portStr = SerialPort.GetPortNames();
            if (portStr.Length == 0)
            {
                MessageBox.Show("No COM port found!", "Error");
                return;
            }
            // com ports exist
            btnOpen.Enabled = true;
            foreach (string s in System.IO.Ports.SerialPort.GetPortNames())
            {
                cbSerial.Items.Add(s);
            }
            cbSerial.SelectedIndex = 0;
      }


      // comport
      private SerialPort rs232Port = new SerialPort();
      
      // got from "rxlog -1"
      private int startSub, totalSubs;

      // thread for rx
      private Thread getlogThread;
      private delegate void UpdateTextBox(string s);
      private UpdateTextBox updateTextBox;
      // rx flag
      private bool rxLogsInprocess = false;
      private bool rxPause = false;
      private int subInprocess;
      private uint currentDate = 0xFFFFFFFF;

      private subPack subpack = new subPack();
      private enum subStatus { ssHead, ssLog, ssTail, ssCrc32, ssOver };
      private subStatus revSt = subStatus.ssHead;
      private int revCnt = 0;
      private Queue<byte> rs232Buf = new Queue<byte>();

      private void info2textbox(string s)
      {
            textBox.AppendText(s + "\r\n");
            textBox.ScrollToCaret();
      }

      private void btnOpen_Click(object sender, EventArgs e)
      {
            if(rs232Port.IsOpen)
            {
                // if comport is receiving logs, stop it first
                rxPause = true;
                if (rxLogsInprocess)
                {
                  while (rxLogsInprocess) ;
                }
                rs232Port.DataReceived -= byteReceived;
                btnGetLogs.Enabled = false;
                btnOpen.Text = "Open";
                rs232Port.Close();
                cbSerial.Enabled = true;
                return;
            }

            // clear flags
            rxLogsInprocess = false;
            rxPause = false;

            // init rs232port
            rs232Port.PortName = cbSerial.Text;
            rs232Port.BaudRate = 115200;
            rs232Port.Parity = Parity.None;
            rs232Port.StopBits = StopBits.One;
            rs232Port.DataBits = 8;
            rs232Port.Handshake = Handshake.None;
            rs232Port.RtsEnable = false;
            rs232Port.ReceivedBytesThreshold = 1;
            rs232Port.ReadBufferSize = subPack.SUBPACK_SIZE;
            rs232Port.NewLine = "\r\n";
            rs232Port.ReadTimeout = 1000;
            try
            {
                rs232Port.Open();
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
                return;
            }

            // clear buffer
            rs232Port.DiscardInBuffer();
            rs232Port.DiscardOutBuffer();

            btnOpen.Text = "Close";
            cbSerial.Enabled = false;
            
            textBox.Text = "Connecting...";

            // send command "rxlog -1\x0D" to get the log status from module :"Start:0, Total:32767\r\n"
            rs232Port.Write("rxlog -1\x0D");

            try
            {
                string rxLog = rs232Port.ReadLine();
                int s, t;
                s = rxLog.IndexOf("Start:");
                t = rxLog.IndexOf(", Total:");
                if (s != -1 && t != -1)
                {
                  startSub = Int32.Parse(rxLog.Substring(s+6, t-(s+6)));
                  totalSubs = Int32.Parse(rxLog.Substring(t+8));
                  textBox.Text = "Start = " + startSub + ", Total = " + totalSubs+"\r\n";
                }
                else
                {
                  throw new TimeoutException();
                }
            }
            catch (TimeoutException)
            {
                textBox.Text ="Connect failed!";
                return;
            }
            // connect successful, ready to get logs
            btnGetLogs.Enabled = true;
            subInprocess = startSub;
      }

      // 接收字节
      private volatile bool byteRecIsBus = false;
      void byteReceived(object sender, SerialDataReceivedEventArgs e)
      {
            while (byteRecIsBus == true) ;      // avoid reentrant
            byteRecIsBus = true;
            while (rs232Port.BytesToRead > 0)    // data to read
            {
                int n = rs232Port.BytesToRead;
                byte[] buf = new byte;
                rs232Port.Read(buf, 0, n);      // read buffer
                for (int i = 0; i < n; i++)
                {
                  rs232Buf.Enqueue(buf);   // to queue
                }
            }
            byteRecIsBus = false;
      }

      // 启动接收
      private void btnGetLogs_Click(object sender, EventArgs e)
      {
            rs232Port.DataReceived += byteReceived;    // delegate
            btnGetLogs.Enabled = false;                // disable the button
            getlogThread = new Thread(new ThreadStart(getLogAll));      // new thread to rx
            getlogThread.Start();
            updateTextBox = new UpdateTextBox(info2textbox);
      }

      // 接收线程
      private void getLogAll()
      {
            int timeoutCnt = 0;
            int crcErrCnt = 0;
            while (totalSubs > 0)
            {
                // to receive a new subsector, clear the buffer first
                rs232Buf.Clear();
                rs232Port.DiscardInBuffer();
                rs232Port.DiscardOutBuffer();

                // rxlog sub
                rs232Port.Write("rxlog " + subInprocess + "\x0D");
                // receive a sub
                // set timer
                setTimeout(1000);
                // waiting for data
                Thread.Sleep(300);
                try
                {
                  // 1: head : 0xFAF5AA55
                  byte[] headBuf = new byte { 0, 0, 0, 0 };
                  while ((headBuf == 0xFA && headBuf == 0xF5 && headBuf == 0xAA && headBuf == 0x55) == false)
                  {
                        headBuf = headBuf;
                        headBuf = headBuf;
                        headBuf = headBuf;
                        headBuf = getByte();
                  }
                  for (int i = 0; i < subPack.HEAD_SIZE; i++)
                  {
                        subpack.head = headBuf;
                  }

                  // 2&3: log and tail
                  // The first 4 Bytes in a Log is timeStamp
                  // If the timeStamp is 0xFAF5CC33 or LOG_CNT_MAX of Logs have been received, it means the Tail
                  for (int i = 0; i < subPack.LOG_CNT_MAX + 1; i++)
                  {
                        // check first 4-byte
                        int j;
                        for (j = 0; j < 4; j++)
                        {
                            subpack.logsInSub.log14b = getByte();
                        }
                        if ((subpack.logsInSub.getTimestamp() == 0xFAF5CC33) || (i == subPack.LOG_CNT_MAX))
                        {// tail
                            for (j = 0; j < 4; j++)
                            {
                              subpack.tail = subpack.logsInSub.log14b;
                            }
                            subpack.logCnt = i; // how many Logs in this subsector
                            // goto crc32
                            break;
                        }
                        // receive other 10 bytes
                        for (; j < log.LOG_SIZE; j++)
                        {
                            subpack.logsInSub.log14b = getByte();
                        }
                  }
                  // 4: crc32
                  for (int i = 0; i < subPack.CRC32_SIZE; i++)
                  {
                        subpack.crc32 = getByte();
                  }
                }
                catch (TimeoutException)
                {
                  // timeout error, retry this subsector
                  // update textbox
                  if(++timeoutCnt==5)
                  {
                        //??????
                        throw new TimeoutException("Timeout 5 times");
                  }
                  this.BeginInvoke(updateTextBox, "timeout\r\n");
                  continue;
                }

                if (subpack.isCrc32Correct() == false)
                {
                  // crc32 error, retry this subsector
                  if (++crcErrCnt == 5)
                  {
                        //??????
                        throw new TimeoutException("Crc error 5 times");
                  }
                  this.BeginInvoke(updateTextBox, "crc err\r\n");
                  continue;
                }
                // got a sub
                // save to file
                for (int i = 0; i < subpack.logCnt; i++)
                {
                  // check date
                  uint date = subpack.logsInSub.getDate();
                  if (currentDate != date)
                  {// new date, new file
                        currentDate = date;
                  }
                  else
                  {// same date, same file

                  }
                }
                // <- a sub is over
                timeoutCnt = 0;
                crcErrCnt = 0;
                this.BeginInvoke(updateTextBox, "Got subsector:" + subInprocess + "\r\n");
                totalSubs--;
                subInprocess++;
                if (subInprocess >= 32768)
                {
                  subInprocess = 0;
                }
            }
            // over
            btnGetLogs.Enabled = false;

            // ?????
      }

      // 从队列中取出字节
      byte getByte()
      {
            while(rs232Buf.Count==0)
            {
                if (timeoutMsTick)
                {
                  throw new TimeoutException();
                }
            }
            return rs232Buf.Dequeue();
      }

      private void btnBrowse_Click(object sender, EventArgs e)
      {
            System.Windows.Forms.FolderBrowserDialog dlg = new System.Windows.Forms.FolderBrowserDialog();
            dlg.Description = "Select the directory to save log files";
            if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                saveToDir.Text = dlg.SelectedPath.ToString();
            }
      }

      private void Form1_FormClosing(object sender, FormClosingEventArgs e)
      {
            if (rs232Port.IsOpen == true)
            {
                rs232Port.Close();
                // close file


            }
      }
      
      // 定时器 for timeout
      private volatile bool timeoutMsTick;
      private void timeoutMs_Tick(object sender, EventArgs e)
      {
            timeoutMsTick = true;
            timeoutMs.Stop();
      }

      private void setTimeout(int ms)
      {
            timeoutMs.Stop();
            timeoutMsTick = false;
            timeoutMs.Interval = ms>0?ms:1;
            timeoutMs.Start();
      }
      
    }


liuqian 发表于 2016-1-5 14:37:18

本帖最后由 liuqian 于 2016-1-5 14:44 编辑

接收部分,不用queue,改用数组,改善很明显
之前几乎每个包都有crc err,而且会有多次重发,改良以后,crc err很少出现(几十个包出现一次),没有出现连续2次crc err的

      // 接收字节
      private volatile bool byteRecIsBus = false;
      private volatile int rs232BufferWrIndex = 0;
      private volatile byte[] rs232Buffer = new byte;
      void byteReceived(object sender, SerialDataReceivedEventArgs e)
      {
            while (byteRecIsBus == true) ;      // avoid reentrant
            byteRecIsBus = true;
            while (rs232Port.BytesToRead > 0)    // data to read
            {
                int n = rs232Port.BytesToRead;
                byte[] buf = new byte;
                rs232Port.Read(buf, 0, n);      // read buffer
                foreach (byte b in buf)
                {
                  rs232Buffer = b;
                  //rs232Buf.Enqueue(b);   // to queue
                }
            }
            byteRecIsBus = false;
      }

      // 从队列中取出字节
      private volatile int rs232BufferRdIndex = 0;
      byte getByte()
      {
            while (rs232BufferWrIndex == rs232BufferRdIndex)
            {
                if (timeoutMsTick)
                {
                  throw new TimeoutException();
                }
            }
            byte b = rs232Buffer;
            return b;
      }

      private void rs232BufferReset()
      {
            rs232BufferRdIndex = 0;
            rs232BufferWrIndex = 0;
      }

墨非 发表于 2016-1-5 20:18:10

本帖最后由 墨非 于 2016-1-5 20:20 编辑

粗扫了一下 ,byteReceived 里while (byteRecIsBus == true) ;占着CPU干等 为何不干脆释放掉CPU呢
要不 用:

private static object byteReceivedLock = new object();

void byteReceived(object sender, SerialDataReceivedEventArgs e)
{
    lock (byteReceivedLock)
    {
         //接收代码
    }
}
         
页: [1]
查看完整版本: 初学C#串口编程,接收丢数据,请大家看看问题在哪里