65.9K
CodeProject 正在变化。 阅读更多。
Home

微控制器和 PC 之间的高速串行端口位传输

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (5投票s)

2012年8月22日

CPOL

3分钟阅读

viewsIcon

27564

downloadIcon

1660

一种通过串行端口在微控制器通信中进行高速位传输的好方法。

介绍 

RS232毫无疑问地在工业应用中是可靠的。因此,每个开发工业应用程序的人都倾向于使用串口传输数据。但在某些应用中,115200波特率显得太慢,特别是当您有一个基于微控制器的硬件,它将其处理结果在其中断服务例程中传输到PC时。因为它意味着每个字节的传输大约需要100微秒。另一方面,在某些应用中,您不一定需要从硬件向PC传输一个字节,硬件只想发送一个标志(位)来触发或改变软件中的状态。现在您可能会想“有没有一种快速的方法,可以在10微秒或更短的时间内,通过串口将一位从硬件传输到PC?”

幸运的是,在这个时间限制内,微控制器和PC之间进行位传输是可能的。 如果您可以处理一些控制信号,例如CTS(允许发送)或DSR(数据设置就绪),它们都是PC的输入。 您将找到一种方法来监听串口信号,以检测单独线程中这些信号状态的变化。

背景  

我还不信任C#及其串口组件用于工业应用。我总是用C++开发这样的应用程序。但是,当您开发工业应用程序时,您将与硬件工程师一起工作,他们总是有一些资源限制,例如内存、时钟频率等等。硬件,特别是微控制器中的主要限制之一是,通常诸如编码器脉冲之类的输入信号连接到微控制器的可中断输入,并且当代码进入中断服务例程时,它应该尽快退出,以免错过下一个脉冲。因此,如果硬件应该与PC通信并告知信号变化,则它没有足够的时间将一个完美的字节传输到PC。

使用代码

首先,您需要通过添加*SerialPort.h*和*SerialPort.cpp*文件将CSerialPort类添加到您的C++项目中。在这个类中,您有两个Init()方法。其中一个使用FILE_FLAG_NO_BUFFERING作为常用数据传输的dwFlagsAndAttributes参数:

LONG CSerialComm:: Init(CString szPortName, DWORD dwBaudRate, 
       BYTE byParity,BYTE byStopBits, BYTE byByteSize, DWORD timeout)
{
    //open the COM Port
    m_hCommPort = ::CreateFile(szPortName,
                           GENERIC_READ|GENERIC_WRITE, //access ( read and write)
                   0, //(share) 0:cannot share the COM port
                   0, //security  (None)                
                   OPEN_EXISTING, // creation : open_existing
                   FILE_FLAG_NO_BUFFERING, // common send/receive operation
                   0// no templates file for COM port...
                   );

另一个使用FILE_FLAG_OVERLAPPED作为dwFlagsAndAttributes参数来捕获串口上的CTS或DSR信号变化

LONG CSerialComm:: Init2Signal(CString szPortName, DWORD dwBaudRate, 
       BYTE byParity,BYTE byStopBits,BYTE byByteSize, DWORD timeout)
{

    //open the COM Port
    m_hCommPort = ::CreateFile(szPortName,
    GENERIC_READ|GENERIC_WRITE,//access ( read and write)
       0,    //(share) 0:cannot share the COM port
       0,    //security  (None)                
       OPEN_EXISTING,// creation : open_existing
       FILE_FLAG_OVERLAPPED,// we want overlapped operation
       0// no templates file for COM port...
       );

在示例代码中,我首先在通用数据传输模式下初始化串口,以便与硬件通信

LONG lError = m_hSerialPort.Init(m_strSerialPortName, 115200, 0, 1, 8, 100); 
if(lError == NO_ERROR)
    return TRUE;
else
    return FALSE;

此通信可能包括获取硬件确认和设置其参数,这些参数取决于您的应用程序:

BYTE aData[1];
aData[0] = COMMAND_HARDWARE_ACKNOWLEDGE;
m_hSerialPort.Write(aData, 1);
BYTE btResponse = 0;
m_hSerialPort.Read(btResponse);
if(btResponse != COMMAND_HARDWARE_ACKNOWLEDGE)
    m_strStatus = _T("Not Connected");
else
    m_strStatus = _T("Connected");
m_hSerialPort.UnInit();
UpdateData(FALSE);

但是,当您想要启动信号捕获过程时,串口将在捕获串口信号变化模式下启动

pDlg->m_hSerialPort.UnInit();
LONG lError = pDlg->m_hSerialPort.Init2Signal(pDlg->m_strSerialPortName, 115200, 0, 1, 8, 100);
if(lError != NO_ERROR)
{
  pDlg->SetDlgItemTextW(IDC_EDIT_STATUS, 
     _T("Can not Init Serial Port in signaling mode!"));
  return 1;
}

实际上,将启动一个线程来侦听串口信号变化

m_bListenOnSerialPort = TRUE;
ResetEvent(m_hWaitForSerialPortListenTerminate);
m_thListenOnSerialPort = CreateThread( NULL,    // default security attributes
                    0,      // use default stack size  
                    ListenOnSerialPort,     // thread function 
                    this,    // argument to thread function 
                    0,    // use default creation flags 
                    &dwThreadId);    // returns the thread identifier

当按下停止按钮时,侦听线程将终止,并且端口将重新初始化为通用模式

m_bListenOnSerialPort = FALSE;
DWORD dwResult = WaitForSingleObject(m_hWaitForSerialPortListenTerminate, 5000);
if (dwResult != WAIT_OBJECT_0)
{
    m_hSerialPort.UnInit();
    LONG lError = m_hSerialPort.Init(m_strSerialPortName, 115200, 0, 1, 8, 100);
    if(lError != NO_ERROR)
    SetDlgItemTextW(IDC_EDIT_STATUS, _T("Can not Init Serial Port in usual mode!"));
}

但是,在监听模式下,代码中有两个关键点:首先,等待对象会保持线程,直到信号状态发生变化

DWORD dwRet = GetLastError();
if( ERROR_IO_PENDING == dwRet)
{
    WaitForSingleObject(overlapped.hEvent, INFINITE);
    if (dwEvtMask & EV_DSR) 
    {
         DWORD lpModemStat = 0;
         ::GetCommModemStatus(m_hCommPort, &lpModemStat);

        if(lpModemStat == MS_DSR_ON)
            btResult = btResult | DSR_ON;
        else
            btResult = btResult | DSR_OFF;
    }

    if (dwEvtMask & EV_CTS) 
    {
        // To do. 
        DWORD lpModemStat = 0;
        ::GetCommModemStatus(m_hCommPort, &lpModemStat);
        if(lpModemStat == MS_CTS_ON)
            btResult = btResult | CTS_ON;
        else
            btResult = btResult | CTS_OFF;
    }

}

其次是任何信号更改发生后的作业

BYTE bStatus = pDlg->m_hSerialPort.GetPortStatus();
CString msg = _T(" ");
if((bStatus & CTS_OFF) > 0)
{
    if(bCtsStatus == TRUE)
    {
        msg += _T("CTS switched off");
        bCtsStatus = FALSE;
    }
}
if ((bStatus & CTS_ON) > 0)
{
    if(bCtsStatus == FALSE)
    {
        msg += _T("CTS switched on");
        bCtsStatus = TRUE;
    }
}
if ((bStatus & DSR_OFF) > 0)
{
    if(bDsrStatus == TRUE)
    {
        msg += _T("DSR switched off");
        bDsrStatus = FALSE;
    }
}
if ((bStatus & DSR_ON) > 0)
{
    if(bDsrStatus == FALSE)
    {
        msg += _T("DSR switched on");
        bDsrStatus = TRUE;
    }
}

您可以更改此作业的代码以执行应用程序所需的任何操作。

关注点

如果您能理解他们的局限性并做出正确的决定以找到硬件和软件细节,您就可以与您的硬件人员打交道。感谢 Kavosh 公司的我的同事 Reza Mesbah,他是硬件人员。

© . All rights reserved.