低容量STM8 Modbus協議移植與裁剪

1.freeModbus開源包的下載

一般STM8用的開發環境是IAR,所以這裏我們就講在IAR下移植FreeModbus,
下載freemodbus-v1.5.0,官方下載地址http://www.freemodbus.org/找到Download
這裏寫圖片描述

點擊freemodbus-v1.5.zip即可下載。
這裏寫圖片描述

2.freeModbus開源包的簡介

打開文件夾的目錄如下
這裏寫圖片描述

然後我們打開主要的文件夾modbus
這裏寫圖片描述
我們可以看到有ascii、functions、include、rtu、tcp以及mb.c源文件
Ascii Modbus ascii通信方式相關文件夾,
Rtu Modbus Rtu 通信方式和CRC校驗相關文件夾,
Tcp Modbus Tcp通信方式相關文件夾,
Functions Modbus 功能函數相關,
Include Modbus 主要頭文件都在這裏,
Mb.c Modbus 最主要的頭文件 ,包含初始化函數,poll函數等等
這些都是移植不需要修改的。涉及到Modbus的協議層,屬於硬件無關層,所以都抽象出來,給下面提夠統一的軟接口就行了。
下面我們看看需要移植的部分,這部分在demo裏面有很多平臺的實例
這裏寫圖片描述

我們打開看看,包含一些比較常見的平臺,比如Atmel公司的ARM處理器,AVR單片機
TI的Msp430單片機,以及Liunx,Windows操作系統下的平臺等等,但是沒有我們想要的STM8處理器的接口。
這裏寫圖片描述
但我們可以依葫蘆畫瓢,移植到其他平臺,這裏我們打開AVR下面目錄看看,主要看port文件夾下面的文件,這些都是我們要移植的,包括串口驅動相關的portserial.c,定時器驅動相關的porttimer.c以及總中斷相關的port.c(這個目錄沒有,我們添加進去)等等。
這裏寫圖片描述

3.導入工程

分析完FreeModbus文件夾後,我們開始移植工作
首先在IAR裏面建立工程,將Modbus文件夾拷貝到工程下面目錄,將port文件夾拷貝到Modbus下面。如下圖所示
這裏寫圖片描述
Port在modbus文件夾下面目錄
這裏寫圖片描述
然後在工程中建立新的Modbus組
這裏寫圖片描述
然後在工程的options菜單下面添加頭文件路徑,其中PROJDIR \爲當前工程目錄
這裏寫圖片描述
在上述步驟完成後,我們開始改寫硬件驅動,主要分三大塊

3.1硬件設備驅動移植

3.1.1串口分析與移植

打開portserial.c對下面函數進行功能完善
Void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable );
BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity );
BOOL xMBPortSerialPutByte( CHAR ucByte );
BOOL xMBPortSerialGetByte( CHAR * pucByte );
以及串口接收中斷服務函數,串口發送完成中斷服務函數。
首先是Void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable );這個函數主要是串口發送和接收的中斷使能。函數的第一個參數爲接收使能布爾型參數,第二個爲發送使能布爾型參數。當xRxEnable爲真時,打開串口接收中斷,爲假時,關閉串口接收中斷;同樣,發送也是一樣。爲了節約使用低容量處理器有限的存儲空間,我們接下來的單片機相關資源的操作都採用寄存器的方式,那麼我們先看STM8寄存器手冊,
這裏寫圖片描述
這裏寫圖片描述
可以很清楚發現UART1_CR2寄存器的第5位和第6位是控制串口接收中斷和串口的發送完成中斷,這裏我們只需要將此兩位置高和置低即可實現相應的使能和禁止功能。所以這裏我們的代碼

void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) //控制串口的收發中斷
{
    if(xRxEnable==TRUE)
    {
            UART1_CR2|=(1<<5);
    }
    else
    {
            UART1_CR2&=~(1<<5);
    }

    if(xTxEnable==TRUE)
    {
            UART1_CR2|=(1<<6);
    }
    else
    {
           UART1_CR2&=~(1<<6);
    }
}

然後就是BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )函數的實現。
看函數的參數,第一個是端口,第二個是波特率,第三個是數據位,第四個是校驗方式
因爲本實驗移植採用的STM8103F3C6,只帶一個Uart串口,所以第一個參數就不用去實現了。
第二個參數是波特率,我們可以看單片機的寄存器手冊
這裏寫圖片描述
這裏寫圖片描述

波特率的設置是通過時鐘與波特率分頻的比值決定的。通過查看寄存器,很容易發現,波特率比率由兩個8位寄存器構成,但是寄存器的排列方式不是按照一般數據存儲方式。UART_BRR1爲波特率分頻係數的4至11位數據,UART_BRR2的低四位爲波特率分頻係數的第四位,而UART_BRR2的高四位爲波特率分頻係數的12至15位。所以這裏我們需要進行轉換
UART1_BRR2 = div & 0x0f;
UART1_BRR2 |= ((div & 0xf000) >> 8);
UART1_BRR1 = ((div & 0x0ff0) >> 4);
Div爲波特率分頻係數
那麼ulBaudRate = f/Div;(f爲系統的時鐘)。
接着我們分析第三個參數,數據位,如下圖,UART_CR1寄存器第4位,當爲0時,是一個起始位,8個
這裏寫圖片描述

數據位,n個停止位,這裏的停止位需要通過UART_CR3寄存器設定;當爲1時,是一個起始位,9個數據位,一個停止位。然後我們去分析UART_CR3寄存器
這裏寫圖片描述
通過查看寄存器,發現停止位有1,1.5,2等3種情況。一般應用下,我們都是用的一個停止位,故這裏可以直接保留默認值就行了。所以代碼可以

if(ucDataBits == 8)
{
   UART1_CR1&=~(1<<4);
}else
{
   UART1_CR1|=(1<<4);
}

最後我們看看串口奇偶校驗參數實現,查看寄存器UART1_CR1
這裏寫圖片描述

第1位 PS 奇偶檢驗選擇 當爲0時,偶檢驗;當爲1時奇校驗。
第2位 奇偶校驗使能位 當爲0時,禁用校驗;當爲1時使能校驗。
這些代碼之前注意參數eMBParity eParity,位枚舉型變量,看看定義
這裏寫圖片描述
所以代碼爲

switch(eParity)
{
    case MB_PAR_NONE:
        UART1_CR1&=~(1<<2);
        break;
    case MB_PAR_ODD: 
        UART1_CR1|=(1<<2);
        UART1_CR1|=(1<<1);
        break;
    case MB_PAR_EVEN:
        UART1_CR1|=(1<<2);
        UART1_CR1&=~(1<<1);
        break;
    default:break;
}

至此串口的主要功能基本實現差不多,還有BOOL xMBPortSerialPutByte( CHAR ucByte );
BOOL xMBPortSerialGetByte( CHAR * pucByte )主要是數據的寫入和讀取,也就是直接讀取和寫入UART_DR就行了。

BOOL xMBPortSerialPutByte( CHAR ucByte )
{
    while((UART1_SR & 0x80)==0x00);
      UART1_DR=ucByte;
    return TRUE;
}
// 串口收
BOOL xMBPortSerialGetByte( CHAR * pucByte )
{

    *pucByte = UART1_DR;
    return TRUE;
}

3.1.2 中斷分析與移植

除此之外,我們還要對兩個中斷服務函數進行處理。

/* Create an interrupt handler for the transmit buffer empty interrupt
 * (or an equivalent) for your target processor. This function should then
 * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
 * a new character can be sent. The protocol stack will then call 
 * xMBPortSerialPutByte( ) to send the character.
 */
 void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}

/* Create an interrupt handler for the receive interrupt for your target
 * processor. This function should then call pxMBFrameCBByteReceived( ). The
 * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
 * character.
 */
void prvvUARTRxISR( void )
{
     pxMBFrameCBByteReceived(  );
}

//將收到的數據再發送出去
#pragma vector= UART1_R_RXNE_vector
__interrupt void UART1_R_RXNE_IRQHandler(void)
{

      if(UART1_SR&(1<<3))
      {
       // UART1_SR&=~(1<<3);
      }
      else
      {
        prvvUARTRxISR();//接受中斷

        //UART1_SR&=~(1<<5);
      }

      return;
}

//將收到的數據再發送出去
#pragma vector= UART1_T_TC_vector
__interrupt void UART1_T_TC_IRQHandler(void)
{

      prvvUARTTxReadyISR();//發送完成中斷
      //UART1_SR&=~(1<<6);

      return;
}

3.1.3 定時器分析與移植

下面就開始定時器的移植,定時器的寄存器功能設置和上面講解的串口寄存器設置類似,所以下面只適當註釋下代碼,相信大家很容易看懂。定時器的移植主要是對porttimer.c中
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us );
void vMBPortTimersEnable( );
void vMBPortTimersDisable( );
prvvTIMERExpiredISR( );函數的回調
xMBPortTimersInit( USHORT usTim1Timerout50us );主要是50us時基,用於產生和判斷1.5-3.5個字符時間,作爲產生和判斷數據幀的結束標準。那麼,產生50us時基我們可以採用定時器分頻做到,首先1/50us轉換成頻率是20kHz,即定時器的計數器頻率爲20kHz時,計數器每增一或減一,所需要時間就是50us.所以我們分頻係數爲8MHz/20KHz = 400;而 USHORT usTim1Timerout50us參數爲50us的個數,

    /* Set the Prescaler value */
    TIM1_PSCRH = (unsigned char)((period >> 8)&0xff);
    TIM1_PSCRL = (unsigned char)(period&0xff);

    usTim1Timerout50us = usTim1Timerout50us-1;

    TIM1_ARRH = (unsigned char)((usTim1Timerout50us >> 8)&0xff);
    TIM1_ARRL = (unsigned char)(usTim1Timerout50us&0xff);

    /* Set the Repetition Counter value */
    TIM1_RCR = 0;
    /* Set the ARPE Bit */
    TIM1_CR1 |= MASK_TIM1_CR1_ARPE;
    /* Enable the Interrupt Upmode sources */
    TIM1_IER |= 0x01;
    /* set or Reset the CEN Bit */
    TIM1_CR1 |= MASK_TIM1_CR1_CEN;


 那麼 xMBPortTimersInit( USHORT  usTim1Timerout50us )函數實現爲:

    unsigned int period = 400-1;//分頻係數
    TIM1_PSCRH = (unsigned char)((period >> 8)&0xff);
    TIM1_PSCRL = (unsigned char)(period&0xff);

    usTim1Timerout50us = usTim1Timerout50us-1;

    TIM1_ARRH = (unsigned char)((usTim1Timerout50us >> 8)&0xff);
    TIM1_ARRL = (unsigned char)(usTim1Timerout50us&0xff);



    TIM1_RCR = 0;
    TIM1_CR1 |= MASK_TIM1_CR1_ARPE;
    TIM1_IER |= 0x01;//使能中斷
    TIM1_CR1 |= MASK_TIM1_CR1_CEN;//使能計數器

然後就是實現vMBPortTimersEnable( ),vMBPortTimersDisable( );打開和關閉時鐘

void vMBPortTimersEnable() //打開時鐘
{
        TIM1_SR1 &= ~(1<<0);//清除標誌
        TIM1_IER |= (1<<0);//使能中斷
        TIM1_CNTRH = 0x00;
        TIM1_CNTRL = 0x00;  //計數器清零
        TIM1_CR1 |= MASK_TIM1_CR1_CEN;//使能定時器
}

void vMBPortTimersDisable() //關閉時鐘
{
      TIM1_CR1 &= ~MASK_TIM1_CR1_CEN;//關閉定時器
      TIM1_CNTRH = 0x00;
      TIM1_CNTRL = 0x00;  //計數器清零
      TIM1_IER &= ~(1<<0);//關閉中斷
      TIM1_SR1 &= ~(1<<0);//清除標誌
}

以及在中斷服務函數中添加,溢出中斷中調用prvvTIMERExpiredISR( )函數進行協議處理

#pragma vector=TIM1_OVR_UIF_vector
__interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler(void)
{
  prvvTIMERExpiredISR( );
  TIM1_SR1 &= ~(1<<0);//清除標誌
}

最後還要實現
void EXIT_CRITICAL_SECTION(void)//退出超臨界 開總中斷
void ENTER_CRITICAL_SECTION(void)//進入超臨界 關總中斷

void ENTER_CRITICAL_SECTION(void)//進入超臨界 關總中斷
{
        asm("sim");
}

void EXIT_CRITICAL_SECTION(void)//退出超臨界 開總中斷
{
       asm("rim");
}

別忘了加入頭文件#include “intrinsics.h”

到這裏,整個Modbus的移植算是完成了。

3.2 api功能的實現

但在這裏,沒有讀寫寄存器,線圈的相關操作函數,我們可以根據自己的需求進行添加和裁剪。
其中聲明的函數:

eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs );
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode );
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode );
eMBErrorCode eMBRegDiscreteCB(UCHAR* pucRegBuffer,USHORT usAddress,USHORT usNDiscrete );

這裏我們測試,就實現eMBRegHoldingCB保持寄存器的功能函數

eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          iRegIndex;
    USHORT *        pusRegHoldingBuf;
    USHORT          REG_HOLDING_START;
    USHORT          REG_HOLDING_NREGS;
    USHORT          usRegHoldStart;

    pusRegHoldingBuf = usSRegHoldBuf;
    REG_HOLDING_START = S_REG_HOLDING_START;
    REG_HOLDING_NREGS = S_REG_HOLDING_NREGS;
    usRegHoldStart = usSRegHoldStart;

    usAddress--;//FreeModbus功能函數中已經加1,爲保證與緩衝區首地址一致,故減1
    if( ( usAddress >= REG_HOLDING_START ) &&
        ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
    {
        iRegIndex = usAddress - usRegHoldStart;
        switch ( eMode )
        {
            // Pass current register values to the protocol stack. 
        case MB_REG_READ:
            while( usNRegs > 0 )
            {
                *pucRegBuffer++ = ( unsigned char )( pusRegHoldingBuf[iRegIndex] >> 8 );
                *pucRegBuffer++ = ( unsigned char )( pusRegHoldingBuf[iRegIndex] & 0xFF );
                iRegIndex++;
                usNRegs--;
            }
            break;

            // Update current register values with new values from the protocol stack.

        case MB_REG_WRITE:
            while( usNRegs > 0 )
            {
                pusRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                pusRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
            break;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

這段代碼直接移植的Armink大神開源代碼裏面保持寄存器的實現函數。移植後這些後,我們就可以再主函數中添加

int main( void )
{    
    System_Clock_Init();
    Led_Init();

    eMBInit(MB_RTU, 0x01, 1, 9600, MB_PAR_NONE);//初始化 FreeModbus 爲RTU模式 從機地址爲1 Uart1 9600 無校驗
    eMBEnable();    


    while (1)
    {
        eMBPoll();
    }
}

3.3 測試

連接好串口與電腦
這裏寫圖片描述
打開Modbus調試精靈,設置好串口號,波特率,校驗位,數據位,停止位和設備的地址
這裏寫圖片描述
通過對寄存器的讀取,可以看出通信正常。

4 優化

爲了使Modbus協議棧佔用資源空間達到最小,我們對其進行裁剪
那麼對mbconfig.h裏面的內容進行修改,將沒有用到的ASCII、TCP進行禁用,那麼在編譯器進行編譯時,就沒有對ASCII、TCP相關的模塊功能進行編譯了。

#define MB_ASCII_ENABLED                        ( 0 )

#define MB_RTU_ENABLED                          ( 1 )

#define MB_TCP_ENABLED                          ( 0 )

#define MB_ASCII_TIMEOUT_SEC                    ( 0 )
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章