久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 2132|回復: 0
打印 上一主題 下一主題
收起左側

單片機軟件擴展的多串口數據轉發模型

[復制鏈接]
跳轉到指定樓層
樓主
實用型軟件架構
==============
多串口數據交互模型
-------------------

實際需求
~~~~~~~~
最近在做一個西門子 ``Step 200`` 系列的PLC通訊口擴展項目時,遇到了這樣的問題:
``224XP`` ,這個CPU的外部通訊端口只用兩個,在物聯網大火的當下,這樣的擴展口數量,在加入聯網模塊后,顯然無法滿足更多的
聯網需求。當前實際需求如下:

.. csv-table:: **通訊口對應功能**
   :header: "編號", "功能"
   :widths: 5, 10
   :align: center

   01, "PLC串口屏通訊"
   02, "EBM風扇通訊"
   03, "4G/WIFI模塊通訊"
   04, "以太網通訊"

在考慮到成本與技術可行性前提下,盡可能保留產品研發核心技術手段,選用STC8系列單片機對PLC原有的兩個通訊口
利用串口進行擴展。設計思路如下:

.. figure:: mode1.png
    :align: center
    :alt: NULL
    :scale: 70%

    圖 4.1 理論中多串口數據交互模型

從圖中可以看出,數據信息的主要請求目標主要是通過 ``PLC_PORT0`` 獲得PLC內部存儲區數據( ``PLC_PORT1`` 默認用于連接屏幕)。
因此,進行軟件拓展的目標物理鏈路就是 ``PLC_PORT0`` 。

矛盾的產生
~~~~~~~~~~

從上面的模型可以看出,當前工作模式應該是一個多主單從結構。那么按照常理應該是由STC8的4個串口通過輪詢的方式對共享設備PLC
目標地址發出數據請求的命令,隨后由PLC把響應數據返回給當前請求對象。如果嚴格遵循這樣的工作模式,不會存在任何問題。
但是,實際的架構設計需求如下:

.. figure:: mode2.png
    :align: center
    :alt: NULL
    :scale: 70%

    圖 4.2 實際的多串口數據交互模型

.. attention::
    其中每個通訊端口上端的標號都代表在實際的通訊過程中,STC8單片機作為擴展主機時輪詢框架下的調度關系(數字越小,優先級越高;數字相等,代表處于同一優先級)。

這里實際使用的時候是通過 ``PLC_PORT0`` 與 ``STC8_UART4`` 進行物理上的連接,在通過STC8內部軟件協議通過其他串口與拓展設備
進行數據交互。很顯然當前的架構無法滿足這樣的實際需求,矛盾就應運而生了。

.. note::
    既然多主機,單從機的通訊模型無法在PLC作為主機時滿足需求,那么就可以重新考慮另外一種工作模式。為了適應更多可能的情況,
    建立一種不分主從結構的工作模式,在多對象數據交互的基礎上建立一種相對是一對一的通訊機制。

.. figure:: mode3.png
    :align: center
    :alt: NULL
    :scale: 70%

    圖 4.2 改進后的多串口數據交互模型

軟件設計思想
~~~~~~~~~~~~~~

.. figure:: F0.png
    :align: center
    :alt: NULL
    :scale: 70%

    圖 4.3 基礎數據結構

.. note::
    從圖中可以看出,最上層采用的是循環隊列,每個隊列的元素由一條鏈表進行連接,每條鏈表的一個節點代表一幀數據。

.. csv-table:: **單節點上成員描述**
   :header: "標識符", "意義"
   :widths: 8, 20
   :align: center

   "Frame_Flag", "幀標志:由定時器幀中斷機制置為true;輪詢轉發程序轉發當前幀后置為false"
   "Timer_Flag", "幀中斷定時器開啟標志:當任意串口接收中斷收到一個字節數據時設置為true;超時后設置false"
   "Rx_Buffer", "數據幀接收緩沖區"
   "Rx_Length", "當前數據幀長度"
   "OverTime", "幀判定時間:該變量在串口中斷有字節數據接收時會不斷刷新;在幀仲裁定時器中其值不斷減小至0"

**詳細工作原理:**
以PLC通過485總線發送數據為例,假設PLC當前要像EBM請求某一個狀態值,發出一幀數據 ``15 21 01 CA``,此時EBM響應數據為 ``35 01 01 00 CA`` ,則:

1、串口四接收中斷收到PLC發出的第一個字節,打開幀中斷定時器,判斷當前寫指針所對應的鏈表節點幀標志是否為false,條件成立后判斷當前節點幀長度是否溢出,
如果沒有就刷新當前幀鏈表塊中 ``OverTime`` , 最后把當前字節 ``15`` 存到當前幀緩沖區 ``Rx_Buffer`` 的位置上。

2、后續字符 ``21 01 CA`` 的接收操作與第一個字符一致,其中每個字節間間隔由通訊的波特率決定,*<<Timer(OverTime)* ,當接收完這一幀數據后,``OverTime``
值將不會在串口接收中斷中被刷新,而是由幀中斷定時器中不斷減小為0,最終標志該節點上這幀數據接收完成,并把對應的 ``Frame_Flag``
置為true。

3、在主程序輪詢機制中,一旦檢測到有 ``Frame_Flag`` 產生,則利用讀指針訪問當前節點幀緩沖區,對目標設備發出請求命令。

4、響應數據返回給目標對象的工作過程與前三個步驟完全一致。值得注意的是,入果存在對個數據交換序列(:menuselection:`PLC_PORT0-->UART4-->UART3` 和 :menuselection:`UART2-->UART4-->PLC_PORT0` ,
存在相反的公共序列 :menuselection:`PLC_PORT0-->UART4` , :menuselection:`UART4-->PLC_PORT0`),此時如果公用的是同一個緩沖區,且不對不同類型的數據進行分流,將會造成不同請求對象數據響應錯誤,
所以必須加以條件限制。

建立數據結構
~~~~~~~~~~~~~~

.. code-block:: c
   :caption: 1.0.0 基礎數據結構
   :linenos:
   :emphasize-lines: 3,5

    /*鏈隊數據結構*/
    typedef struct
    {
    uint8_t Frame_Flag;             /*幀標志*/
        uint8_t Timer_Flag;             /*打開定時器標志*/
        uint8_t Rx_Buffer[MAX_SIZE];    /*數據接收緩沖區*/
        uint16_t Rx_Length;             /*數據接收長度*/
        uint16_t OverTime;              /*目標設備響應超時時間*/
    }Uart_Queu;

    typedef struct
    {
    Uart_Queu LNode[MAX_NODE];
        /*存儲R ,W指針,表示一個隊列*/
        uint8_t Wptr;
        uint8_t Rptr;
    }Uart_List;

    /*聲明鏈隊*/
    extern Uart_List Uart_LinkList[MAX_LQUEUE];

.. note::
    頂層數據結構采用環形隊列,只不過隊列中的單個元素并不是一個單一的值,而是一個帶有記錄信息的數據塊 ``Uart_Queu`` 。
    這樣做的目的在于,使用的單片機是C51,其本身的串口是不帶有空閑中斷或者DMA這些高級硬件的,那這就需要我們通過軟件算法模擬這一些硬件功能
    來完成功能設計。

.. code-block:: c
   :caption: 1.0.1 改進后基礎數據結構
   :linenos:
   :emphasize-lines: 3,5
        
    /*鏈隊數據結構*/
    typedef struct
    {
    uint8_t Frame_Flag;             /*幀標志*/
        uint8_t Timer_Flag;             /*打開定時器標志*/
        uint8_t Rx_Buffer[MAX_SIZE];    /*數據接收緩沖區*/
        uint16_t Rx_Length;             /*數據接收長度*/
        uint16_t OverTime;              /*目標設備響應超時時間*/
        Uart_Queu *Next;                /*指向下一個節點*/
    }Uart_Queu;

.. note::
    主要改進了隊列下數據塊元素的內存分配方式,由原來的靜態的分配,改為程序運行過程根據實際需求來分配。考慮 ``Malloc`` 函數在
    51編譯器中安全性和適用性,實際使用過程建議非必要情況采用靜態內存分配方式。當然,采用動態內存分配方式,使用循環鏈表將會帶來更多的
    可操作性、靈活性和內存節約。

.. code-block:: c
   :caption: 1.0.2 串口幀中斷機制設計
   :linenos:
   :emphasize-lines: 3,5

   /**
    * @brief    定時器0的中斷服務函數
    * @details  
    * @param    None
    * @retval   None
    */
    void Timer0_ISR() interrupt 1
    {

        if(COM_UART1.LNode[COM_UART1.Wptr].Timer_Flag)
            /*以太網串口接收字符間隔超時處理*/
            SET_FRAME(COM_UART1);
        if(COM_UART2.LNode[COM_UART2.Wptr].Timer_Flag)
            /*4G/WiFi串口接收字符間隔超時處理*/
            SET_FRAME(COM_UART2);
        if(COM_UART3.LNode[COM_UART3.Wptr].Timer_Flag)
            /*RS485串口接收字符間隔超時處理*/
            SET_FRAME(COM_UART3);
        if(COM_UART4.LNode[COM_UART4.Wptr].Timer_Flag)
            /*PLC串口接收字符間隔超時處理*/
            SET_FRAME(COM_UART4);
    }

    /**
    * @brief    串口4中斷函數
    * @details  使用的是定時器4作為波特率發生器,PLC口用
    * @param    None
    * @retval   None
    */
    void Uart4_Isr() interrupt 18
    {   /*發送中斷*/
        if (S4CON & S4TI)
        {
            S4CON &= ~S4TI;
            /*發送完成,清除占用*/
            Uart4.Uartx_busy = false;
        }
        /*接收中斷*/
        if (S4CON & S4RI)
        {
            S4CON &= ~S4RI;

            /*當收到數據時打開幀中斷定時器*/
            COM_UART4.LNode[COM_UART4.Wptr].Timer_Flag = true;
            /*當前節點還沒有收到一幀數據*/
            if (!COM_UART4.LNode[COM_UART4.Wptr].Frame_Flag)
            {
                /*刷新幀超時時間*/
                COM_UART4.LNode[COM_UART4.Wptr].OverTime = MAX_SILENCE;
                if (COM_UART4.LNode[COM_UART4.Wptr].Rx_Length < MAX_SIZE)
                { /*把數據存到當前節點的緩沖區*/
                    COM_UART4.LNode[COM_UART4.Wptr].Rx_Buffer[COM_UART4.LNode[COM_UART4.Wptr].Rx_Length++] = S4BUF;
                }
            }
        }
    }

.. note::
    因為硬件定時器數量有限,所以幾個串口的幀中斷機定時器均采用了 ``Timer0`` 進行仲裁,可能會存在中斷延時的問題,在硬件定時器資源充足情況下,盡可能選用硬件定時器較佳。

.. code-block:: c
   :caption: 1.0.3 幀中斷宏
   :linenos:
   :emphasize-lines: 3,5

    /*置位目標串口接收幀標志*/
    #define SET_FRAME(COM_UARTx) (COM_UARTx.LNode[COM_UARTx.Wptr].OverTime ? \
    (COM_UARTx.LNode[COM_UARTx.Wptr].OverTime--): \
    ((COM_UARTx.LNode[COM_UARTx.Wptr].Frame_Flag = true), \
    (COM_UARTx.Wptr = ((COM_UARTx.Wptr + 1U) % MAX_NODE)), \
    (COM_UARTx.LNode[COM_UARTx.Wptr].Timer_Flag = false)))

最后,有了這些軟件機制,僅僅只需要編寫對應的邏輯就可以了。

.. code-block:: c
   :caption: 1.0.4 多串口數據輪詢處理機制
   :linenos:
   :emphasize-lines: 3,5

    /*設置隊列讀指針*/
    #define SET_RPTR(x) ((COM_UART##x).Rptr = (((COM_UART##x).Rptr + 1U) % MAX_NODE))                  
    /*設置隊列寫指針*/
    #define SET_WPTR(x) ((COM_UART##x).Wptr = (((COM_UART##x).Wptr + 1U) % MAX_NODE))

    /*串口一對一數據轉發數據結構*/
    typedef struct
    {
        SEL_CHANNEL Source_Channel; /*數據起源通道*/
        SEL_CHANNEL Target_Channel; /*數據交付通道*/
        void (*pHandle)(void);
    } ComData_Handle;

    /*定義當前串口交換序列*/
    const ComData_Handle ComData_Array[] =
    {
        {CHANNEL_PLC, CHANNEL_RS485, Plc_To_Rs485},
        {CHANNEL_WIFI, CHANNEL_PLC, Wifi_To_Plc},
    };

    /*增加映射關系時,計算出當前關系數*/
    #define COMDATA_SIZE (sizeof(ComData_Array) / sizeof(ComData_Handle))

    /**
    * @brief    串口1對1數據轉發
    * @details  
    * @param    None
    * @retval   None
    */
    void Uart_DataForward(SEL_CHANNEL Src, SEL_CHANNEL Dest)
    {
        uint8_t i = 0;

        for (i = 0; i < COMDATA_SIZE; i++)
        {
            if ((Src == ComData_Array[ i].Source_Channel) && (Dest == ComData_Array[ i].Target_Channel))[ i][ i]
            {
            ComData_Array[ i].pHandle();[ i]
            }
        }
    }

    /**
    * @brief    串口事件處理
    * @details  
    * @param    None
    * @retval   None
    */
    void Uart_Handle(void)
    {
        /*數據交換序列1:PLC與RS485進行數據交換*/
        Uart_DataForward(CHANNEL_PLC, CHANNEL_RS485);
        /*數據交換序列2:WIFI與PLC進行數據交換*/
        Uart_DataForward(CHANNEL_WIFI, CHANNEL_PLC);
    }

    /**
    * @brief    PLC數據交付到RS485
    * @details  
    * @param    None
    * @retval   None
    */
    void Plc_To_Rs485(void)
    {
        /*STC串口4收到PLC發出的數據*/
        if ((COM_UART4.LNode[COM_UART4.Rptr].Frame_Flag)) //&& (COM_UART4.LNode[COM_UART4.Rptr].Rx_Length)
        {                                                
            /*如果串口4接收到的數據幀不是EBM所需的,過濾掉*/
            if (COM_UART4.LNode[COM_UART4.Rptr].Rx_Buffer[0] != MODBUS_SLAVEADDR)
            {   /*標記該接收幀以進行處理*/
                COM_UART4.LNode[COM_UART4.Rptr].Frame_Flag = false;
                /*允許485發送*/
                USART3_EN = 1;
                /*數據轉發給RS485時,數據長度+1,可以保證MAX3485芯片能夠最后一位數據剛好不停止在串口的停止位上*/
                Uartx_SendStr(&Uart3, COM_UART4.LNode[COM_UART4.Rptr].Rx_Buffer, COM_UART4.LNode[COM_UART4.Rptr].Rx_Length + 1U);
                /*接收到數據長度置為0*/
                COM_UART4.LNode[COM_UART4.Rptr].Rx_Length = 0;
                /*發送中斷結束后,清空對應接收緩沖區*/
                memset(&COM_UART4.LNode[COM_UART4.Rptr].Rx_Buffer[0], 0, MAX_SIZE);
                /*發送完一幀數據后拉低*/
                USART3_EN = 0;
                /*讀指針指到下一個節點*/
                SET_RPTR(4);
            }

            /*目標設備發出應答*/
            if ((COM_UART3.LNode[COM_UART3.Rptr].Frame_Flag)) //&& (COM_UART3.LNode[COM_UART3.Rptr].Rx_Length)
            {
                /*標記該接收幀已經進行處理*/
                COM_UART3.LNode[COM_UART3.Rptr].Frame_Flag = false;
                /*數據返回給請求對象*/
                Uartx_SendStr(&Uart4, COM_UART3.LNode[COM_UART3.Rptr].Rx_Buffer, COM_UART3.LNode[COM_UART3.Rptr].Rx_Length);
                /*接收到數據長度置為0*/
                COM_UART3.LNode[COM_UART3.Rptr].Rx_Length = 0;
                /*發送中斷結束后,清空對應接收緩沖區*/
                memset(&COM_UART3.LNode[COM_UART3.Rptr].Rx_Buffer[0], 0, MAX_SIZE);
                /*讀指針指到下一個節點*/
                SET_RPTR(3);
            }
        }
    }

以上圖文的pdf格式文檔下載(內容和本網頁上的一模一樣,方便保存): sphinx.pdf (427.3 KB, 下載次數: 6)

評分

參與人數 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎勵!

查看全部評分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 久久久久久国产免费视网址 | 国产一区免费 | av三级| 亚洲一区视频在线 | 亚洲成av人影片在线观看 | 免费观看黄色一级片 | 久久久亚洲成人 | 91精品国产91久久综合桃花 | 操操日 | jlzzjlzz欧美大全 | 久久这里只有精品首页 | 精品欧美一区二区三区久久久 | 亚洲欧美在线免费观看 | 色爱综合网 | 一区在线视频 | 国产精品美女久久久久久久久久久 | 日日日干干干 | 久久高清免费视频 | 99视频免费 | 亚洲精品欧美一区二区三区 | 国产激情在线 | 精品福利一区 | 成人三级在线观看 | 天天色官网 | 亚洲国产精品激情在线观看 | 日韩精品一区二区三区中文在线 | 最近中文字幕在线视频1 | 欧美一区中文字幕 | 成人三级视频在线观看 | 先锋资源站 | 日韩在线资源 | 国产精品不卡一区 | 激情欧美日韩一区二区 | 欧美日韩一区二区三区在线观看 | 欧美一级片在线播放 | 亚洲狠狠 | 在线成人免费视频 | 日本一道本 | 国产999精品久久久久久 | 国产一区二区三区四 | 精品欧美一区免费观看α√ |