打印
[应用方案]

【新唐资料分享月】+基于新唐M051的modbus rtu代码实现

[复制链接]
4430|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Harvard|  楼主 | 2016-12-11 18:05 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
       不多说, 玩工控的对modbus rtu都很熟悉了,本人也是经常使用,一路从51,AVR, 用到新唐, 单片机在变,代码不变.下面要分享的一个简单实用的代码就是基于C的modbus rtu 从机读写的代码, 因为是从老的 8位机上的c代码移植而来,因此,在串口处理和超时处理, CRC校验上,还是力求可移植性 ,并没有过多的与硬件关联,利用上新唐M0的特性.
       后面有时间的话,准备把自己的代码全部修改为适合新唐mcu特点,发灰新唐MCU特性的特有代码;代码如下:
       一, 串口的基本通信部分, 说明: 代码是基于新唐2.0库编写,后期因为3.0库出来以后,部分功能使用到了3.0库,可能有些混乱.




u16  _crc16_update0(u16 crc, u08 a)
{
        int i;
        crc ^= a;
        for (i = 0; i < 8; ++i)
        {
            if (crc & 1)
                crc = (crc >> 1) ^ 0xA001;
            else
                crc = (crc >> 1);
        }
        return crc;
}

/*
** 串行口初始化
** baudrate取值为: 48 96 192
*/

void usart0_init(u16 baudrate)
{
        /* Unlock protected registers */
    SYS_UnlockReg();
   
         /* Reset UART0 module */
    SYS_ResetModule(UART0_RST);
               
        /* Enable UART clock */
     CLK_EnableModuleClock(UART0_MODULE);

        /*---------------------------------------------------------------------------------------------------------*/
        /* Init UART                                                                                               */
        /*---------------------------------------------------------------------------------------------------------*/

    /* 选择uart功能 */
    UART0->FUN_SEL = UART_FUNC_SEL_UART;

    /* Tx FIFO Reset & Rx FIFO Reset & FIFO Mode Enable */
        //_UART_FLUSH_FIFO(UART0,UART_FCR_TFR_Msk | UART_FCR_RFR_Msk);
    UART0->FCR |=UART_FCR_TFR_Msk | UART_FCR_RFR_Msk;

    /* 选择RX FIFO的阀值为1BYTE*/
     _UART_SET_RX_TRG_LEV(UART0,UART_FCR_RFITL_1BYTE);
     
    if( baudrate != 9600 && baudrate != 19200 && baudrate != 4800&&baudrate != 2400 ) baudrate  = 2400;//默认值

    /* Set 115200 baudrate according to 50MHz system clock */
   //UART0->BAUD = UART_BAUD_MODE2 | UART_BAUD_MODE2_DIVIDER(__IRC22M, baudrate);
   UART0->BAUD = UART_BAUD_MODE2 | UART_BAUD_MODE2_DIVIDER(__HXT, baudrate);
   
    /* 设置串口的工作方式 */   
   _UART_SET_DATA_FORMAT(UART0, UART_WORD_LEN_8 | UART_PARITY_NONE | UART_STOP_BIT_1);


        /* Lock protected registers */
    SYS_LockReg();


    /* Enable RDA\RLS\Time-out Interrupt  */
    UART_ENABLE_INT(UART0, (UART_IER_RDA_IEN_Msk ));
   
    /* 开串口中断 */
    NVIC_EnableIRQ(UART02_IRQn);

}
/*
*        通信辅助功能控制开关
*/

void usart0_dis_recv(void)
{
    _UART_DISABLE_INT(UART0,UART_IER_RDA_IEN_Msk);
    //_UART_RS485_SET_RXDISABLE(UART0);
    UART0->FCR |= UART_FCR_RX_DIS_Msk; /*  Disable RS485 RX*/
   
}
void usart0_en_recv (void)
{
    _UART_ENABLE_INT(UART0,UART_IER_RDA_IEN_Msk);
    //_UART_RS485_CLEAR_RXDISABLE(UART0);
    UART0->FCR &= ~ UART_FCR_RX_DIS_Msk;  /* Enable RS485 RX */

     //UART0_SEND = 0;
}

/*超时定时器初始化及其开关控制*/
#define T3_TICK_MIN                1000000/(F_CPU/1024)
#define T3_TICK                        1000        //1ms
#define TIMER3_BGN_VAL          (255-T3_TICK/T3_TICK_MIN)

//static u08 CNT_t15=0;                        //1.5字符时间
//static u08 CNT_t35=0;                        //3.5字符时间
static u08 SYS_t3_cnt_frame=0;                //时间间隔计数器,用于1.5 和3.5字符时间判断
u08     SYS_t3_on = OFF ;     //用于1.5 3.5字符判断的软定时器 开关.

void timer3_on(void)
{
    SYS_t3_cnt_frame = 0 ;
    SYS_t3_on = ON;
}
void timer3_off(void)
{
    SYS_t3_on = OFF;
    /** 帧间隔控制 */
    SYS_t3_cnt_frame = 0;
}

/**
* [url=home.php?mod=space&uid=247401]@brief[/url]       Timer-3 IRQ
*
* @param       None
*
* [url=home.php?mod=space&uid=266161]@return[/url]      None
*
* [url=home.php?mod=space&uid=1543424]@Details[/url]     The TIMER2 default IRQ, declared in startup_M051Series.s.
*/
void TMR3_IRQHandler(void)
{
    /* Clear TIMER2 Timeout Interrupt Flag */
    _TIMER_CLEAR_CMP_INT_FLAG(TIMER3);   

    /* ***********************1- 串口0:超时处理 ***************************/
    if( SYS_t3_on == ON )
    {
          SYS_t3_cnt_frame++;
         
          if ( SYS_t3_cnt_frame > T35)         //a frame was finished
          {
               usart0_dis_recv();          //禁止接收中断

               timer3_off ()          ;   //tunr off timer;

               SYS_t3_cnt_frame = 0 ;   //reset timer

               RxBufPtr--;                         //调整指针        

               fFrameDone    = OK;   //frame is completed
               
               LED_DTU = 1;
           }      
    }
    /* 定时器关,还未进行帧间隔判断 */
   else
   {
        SYS_t3_cnt_frame = 0;
   }
    /* ***********************2- 串口2:超时处理 ***************************/
   /* 直接调用定时器2的回调函数 */
   usart2_timeout();
}

u08     RxBuf[MAX_BUF_LEN]={0,1,2,3,4};
u08     TxBuf[MAX_BUF_LEN];
u08     RxBufPtr             = RX_BUF_BGN_ADR;
u08     TxBufPtr             = TX_BUF_BGN_ADR1;
BOOL    fIsRXING            = NO;          //is receiving now
BOOL    fIsTXING            = NO;          //is transsending now
BOOL    fRxBufFull            = NO;
BOOL    fTxBufFull      = NO;
BOOL    fFrameDone      = NO;                   //one frame finished
BOOL    fFrameBad       = NO;                   //frame was broken
u16     CRC                        = 0xFFFF;           //CRC初始值       


/*接收中断*/
void UART02_IRQHandler( void )
{
    /* 1-定义临时变量获取中断标志   */
    uint32_t u32IntSts= UART0->ISR;
   
    /* 2- 判断是否是RDA中断        */
    // 2-1:串口0处理
     if( UART_GET_INT_FLAG(UART0,UART_ISR_RDA_INT_Msk) )
    {
        /* 通信指示灯处理 */
         LED_DTU = 0;
        
        /* 查询串口的RDA */
        while( UART_IS_RX_READY(UART0) )
        {
            /* Get the character from UART Buffer
            *   BSP接收数据
            */
           RxBuf[RxBufPtr]  = UART_READ(UART0);
           /* 复位定时器  */
           SYS_t3_cnt_frame = 0 ;   

            /*new frame coming*/
            if( RxBufPtr == RX_BUF_BGN_ADR )//新的一帧开始
            {
                CRC = 0xffff        ;                    //准备crc计算,
                timer3_on();                    //开启定时器计时
            }
            /*with a frame and RxBuf is not full*/
            /*when we detect a frame was borken according to t15 ,
            * we keep on receiving in order to synchronize the frame  
            */
            else if( RxBufPtr < MAX_BUF_LEN -1)
            {  
                if( SYS_t3_cnt_frame > T15 && SYS_t3_cnt_frame < T35 )
                {
                     SYS_t3_cnt_frame   = 0 ;   //reset timer
                     fFrameBad          = TRUE; //frame is broken
                }
            }

            /*RxBuf full */
            /*
            **
            */
            else if
            ( RxBufPtr >= MAX_BUF_LEN -1 )
            {
                usart0_dis_recv()    ;                         //stop receving            
                timer3_off ()            ;            //tunr off timer;
                fRxBufFull           = YES ;            //缓冲区已满
                fFrameBad     = TRUE;            //frame is broken
                RxBufPtr --;                             //指针保持不变,防止破坏其他数据区
                LED_DTU = 1;
            }
                CRC = _crc16_update0( CRC,RxBuf[RxBufPtr] );
                RxBufPtr++;                     //adjust pointer
        }//end while
        
     }//end if
    // END 2-1:串口0处理
   
    // 2-1:串口2处理
    UART2_IRQHandler();

}

/*查询发送*/
void usart0_send_byte( u08 txbyte )
{
     //UART0_SEND = 1;
    UART_WRITE( UART0,txbyte );
    UART_WAIT_TX_EMPTY( UART0 );
}

   二, MODBUS RTU解析处理部分;

  2.1 首先构造modbus协议的通信协议(相当于用户应用层的 ),就是规定多少号寄存器,是代表什么变量,取值范围等等;
/* Modbus地址与实际内部变量的映射表 */
const   u08 * modbus_tab[]  =
{
    /* 2- 只读区域:  地址: 400001 - 400080 ,协议中规定的各种数据地址 */
    MODBUS_ADDR_TAB,
};

      MODBUS_ADDR_TAB,是一个特殊处理手段, 完成了modbus线性寄存器地址与散落在RAM里面的各个变量的对应工作,适合于应用协议数据量不是特别多的情况.
    具体的构造如下,笨办法,谅解.

/* 通信点表与实际的变量映射 ,空白处用Space16替代  */

#define ADR400001          SYS_longitude_hi              //经度
#define ADR400002          SYS_longitude_low             // 纬度
#define ADR400003          SYS_latitude_hi     
#define ADR400004          SYS_latitude_low        
#define ADR400005          SYS_runstate      
#define ADR400006          SYS_current        
#define ADR400007          SYS_voltage                  
#define ADR400008          SYS_kw            
#define ADR400009          SYS_oil_pressure   
#define ADR400010          SYS_oil_temperatue
                                 
#define ADR400011           SYS_gas_level
#define ADR400012           SYS_current_A
#define ADR400013           SYS_current_B
#define ADR400014           SYS_current_C
#define ADR400015           Space16
#define ADR400016           SYS_total_alarm     
#define ADR400017           SYS_alarm1         
#define ADR400018           SYS_alarm2         
#define ADR400019           SYS_alarm3         
#define ADR400020           SYS_alarm4         
#define ADR400021           SYS_alarm5         
#define ADR400022           SYS_alarm6         
#define ADR400023           SYS_alarm7         
#define ADR400024           SYS_alarm8         
#define ADR400025           SYS_alarm9         
#define ADR400026           SYS_alarm10         
#define ADR400027           SYS_alarm11         
#define ADR400028           SYS_alarm12         
#define ADR400029           SYS_alarm13         
#define ADR400030           SYS_alarm14         
                                      
#define ADR400031           SYS_alarm15
#define ADR400032           SYS_his_ID            
#define ADR400033           SYS_his_begin_year   
#define ADR400034           SYS_his_begin_month   
#define ADR400035           SYS_his_begin_day     
#define ADR400036           SYS_his_begin_hour   
#define ADR400037           SYS_his_begin_minute  
#define ADR400038           SYS_his_begin_second  
#define ADR400039           SYS_his_end_year      
#define ADR400040           SYS_his_end_month     
#define ADR400041           SYS_his_end_day      
#define ADR400042           SYS_his_end_hour      
#define ADR400043           SYS_his_end_minute   
#define ADR400044           SYS_his_end_second   
#define ADR400045           SYS_his_length_hour   
#define ADR400046           SYS_his_length_minute
#define ADR400047           SYS_his_kwh_low  //注意顺序      
#define ADR400048           SYS_his_kwh_hi
      
#define ADR400049           SYS_his_gas_cost   

   
#define ADR400050           SYS_his_longitude_hi
#define ADR400051           SYS_his_longitude_low
#define ADR400052           SYS_his_latitude_hi  
#define ADR400053           SYS_his_latitude_low
#define ADR400054           Space16                 //预留1
#define ADR400055           Space16
#define ADR400056           Space16
#define ADR400057           Space16
#define ADR400058           Space16


#define ADR400059           SYS_his_alarm1  
#define ADR400060           SYS_his_alarm2  
#define ADR400061           SYS_his_alarm3  
#define ADR400062           SYS_his_alarm4  
#define ADR400063           SYS_his_alarm5  
#define ADR400064           SYS_his_alarm6  
#define ADR400065           SYS_his_alarm7  
#define ADR400066           SYS_his_alarm8  
#define ADR400067           SYS_his_alarm9  
#define ADR400068           SYS_his_alarm10
#define ADR400069           SYS_his_alarm11
#define ADR400070           SYS_his_alarm12
#define ADR400071           SYS_his_alarm13
#define ADR400072           SYS_his_alarm14
#define ADR400073           SYS_his_alarm15

#define ADR400074           SYS_his_handshake
#define ADR400075           Space16
#define ADR400076           Space16
#define ADR400077           Space16
#define ADR400078           Space16
#define ADR400079           Space16
#define ADR400080           Space16




/* 2- 利用宏定义通信地址表格: 防止源码中太长,影响c代码可读性
*     也可将本文件通过include 插入到modbus.c文件中
*/

#define MODBUS_ADDR_TAB     \
                            \
        (u08 *) &SYS_longitude+1,                     ((u08 *) &SYS_longitude)+0,\
        ((u08 *) &SYS_longitude)+3,                 ((u08 *) &SYS_longitude)+2,\
        (u08 *) &SYS_latitude+1,                     ((u08 *) &SYS_latitude)+0,\
        ((u08 *) &SYS_latitude)+3,                 ((u08 *) &SYS_latitude)+2,\
                &HIGH_( ADR400005 ),                                      &LOW_( ADR400005 ), \
                &HIGH_( ADR400006 ),                                      &LOW_( ADR400006 ), \
                &HIGH_( ADR400007 ),                                      &LOW_( ADR400007 ), \
                &HIGH_( ADR400008 ),                                      &LOW_( ADR400008 ), \
                &HIGH_( ADR400009 ),                                      &LOW_( ADR400009 ), \
                &HIGH_( ADR400010 ),                                      &LOW_( ADR400010 ), \
                &HIGH_( ADR400011 ),                                      &LOW_( ADR400011 ), \
                &HIGH_( ADR400012 ),                                      &LOW_( ADR400012 ), \
                &HIGH_( ADR400013 ),                                      &LOW_( ADR400013 ), \
                &HIGH_( ADR400014 ),                                      &LOW_( ADR400014 ), \
                &HIGH_( ADR400015 ),                                      &LOW_( ADR400015 ), \
                &HIGH_( ADR400016 ),                                      &LOW_( ADR400016 ), \
                &HIGH_( ADR400017 ),                                      &LOW_( ADR400017 ), \
                &HIGH_( ADR400018 ),                                      &LOW_( ADR400018 ), \
                &HIGH_( ADR400019 ),                                      &LOW_( ADR400019 ), \
                &HIGH_( ADR400020 ),                                      &LOW_( ADR400020 ), \
                &HIGH_( ADR400021 ),                                      &LOW_( ADR400021 ), \
                &HIGH_( ADR400022 ),                                      &LOW_( ADR400022 ), \
                &HIGH_( ADR400023 ),                                      &LOW_( ADR400023 ), \
                &HIGH_( ADR400024 ),                                      &LOW_( ADR400024 ), \
                &HIGH_( ADR400025 ),                                      &LOW_( ADR400025 ), \
                &HIGH_( ADR400026 ),                                      &LOW_( ADR400026 ), \
                &HIGH_( ADR400027 ),                                      &LOW_( ADR400027 ), \
                &HIGH_( ADR400028 ),                                      &LOW_( ADR400028 ), \
                &HIGH_( ADR400029 ),                                      &LOW_( ADR400029 ), \
                &HIGH_( ADR400030 ),                                      &LOW_( ADR400030 ), \
                &HIGH_( ADR400031 ),                                      &LOW_( ADR400031 ), \
                &HIGH_( ADR400032 ),                                      &LOW_( ADR400032 ), \
                &HIGH_( ADR400033 ),                                      &LOW_( ADR400033 ), \
                &HIGH_( ADR400034 ),                                      &LOW_( ADR400034 ), \
                &HIGH_( ADR400035 ),                                      &LOW_( ADR400035 ), \
                &HIGH_( ADR400036 ),                                      &LOW_( ADR400036 ), \
                &HIGH_( ADR400037 ),                                      &LOW_( ADR400037 ), \
                &HIGH_( ADR400038 ),                                      &LOW_( ADR400038 ), \
                &HIGH_( ADR400039 ),                                      &LOW_( ADR400039 ), \
                &HIGH_( ADR400040 ),                                      &LOW_( ADR400040 ), \
                &HIGH_( ADR400041 ),                                      &LOW_( ADR400041 ), \
                &HIGH_( ADR400042 ),                                      &LOW_( ADR400042 ), \
                &HIGH_( ADR400043 ),                                      &LOW_( ADR400043 ), \
                &HIGH_( ADR400044 ),                                      &LOW_( ADR400044 ), \
                &HIGH_( ADR400045 ),                                      &LOW_( ADR400045 ), \
                &HIGH_( ADR400046 ),                                      &LOW_( ADR400046 ), \
                &HIGH_( ADR400047 ),                                      &LOW_( ADR400047 ), \
                &HIGH_( ADR400048 ),                                      &LOW_( ADR400048 ), \
                &HIGH_( ADR400049 ),                                      &LOW_( ADR400049 ), \
        (u08 *) &SYS_his_longitude+1,                     ((u08 *) &SYS_his_longitude)+0,\
        ((u08 *) &SYS_his_longitude)+3,                 ((u08 *) &SYS_his_longitude)+2,\
        (u08 *) &SYS_his_latitude+1,                     ((u08 *) &SYS_his_latitude)+0,\
        ((u08 *) &SYS_his_latitude)+3,                 ((u08 *) &SYS_his_latitude)+2,\
                &HIGH_( ADR400054 ),                                      &LOW_( ADR400054 ), \
                &HIGH_( ADR400055 ),                                      &LOW_( ADR400055 ), \
                &HIGH_( ADR400056 ),                                      &LOW_( ADR400056 ), \
                &HIGH_( ADR400057 ),                                      &LOW_( ADR400057 ), \
                &HIGH_( ADR400058 ),                                      &LOW_( ADR400058 ), \
                &HIGH_( ADR400059 ),                                      &LOW_( ADR400059 ), \
                &HIGH_( ADR400060 ),                                      &LOW_( ADR400060 ), \
                &HIGH_( ADR400061 ),                                      &LOW_( ADR400061 ), \
                &HIGH_( ADR400062 ),                                      &LOW_( ADR400062 ), \
                &HIGH_( ADR400063 ),                                      &LOW_( ADR400063 ), \
                &HIGH_( ADR400064 ),                                      &LOW_( ADR400064 ), \
                &HIGH_( ADR400065 ),                                      &LOW_( ADR400065 ), \
                &HIGH_( ADR400066 ),                                      &LOW_( ADR400066 ), \
                &HIGH_( ADR400067 ),                                      &LOW_( ADR400067 ), \
                &HIGH_( ADR400068 ),                                      &LOW_( ADR400068 ), \
                &HIGH_( ADR400069 ),                                      &LOW_( ADR400069 ), \
                &HIGH_( ADR400070 ),                                      &LOW_( ADR400070 ), \
                &HIGH_( ADR400071 ),                                      &LOW_( ADR400071 ), \
                &HIGH_( ADR400072 ),                                      &LOW_( ADR400072 ), \
                &HIGH_( ADR400073 ),                                      &LOW_( ADR400073 ), \
                &HIGH_( ADR400074 ),                                      &LOW_( ADR400074 ), \
                &HIGH_( ADR400075 ),                                      &LOW_( ADR400075 ), \
                &HIGH_( ADR400076 ),                                      &LOW_( ADR400076 ), \
                &HIGH_( ADR400077 ),                                      &LOW_( ADR400077 ), \
                &HIGH_( ADR400078 ),                                      &LOW_( ADR400078 ), \
                &HIGH_( ADR400079 ),                                      &LOW_( ADR400079 ), \
                &HIGH_( ADR400080 ),                                      &LOW_( ADR400080 )

#define MODBUS_ADDR_MAX 80         
                                

[53O52ZWSI5TQ(A3`Z_5T`K.png (45.62 KB )

[53O52ZWSI5TQ(A3`Z_5T`K.png
沙发
Harvard|  楼主 | 2016-12-11 18:06 | 只看该作者
本帖最后由 Harvard 于 2016-12-11 18:15 编辑

而 实际变量的定义如下:



/*********************************************************************************************
*  2 -上位机通信电表  
**********************************************************************************************/
/* 2-1 : 定义需要提供给组态查询的通信变量 */

float    SYS_longitude          = 120.271723000;//经纬度
float    SYS_latitude           = 31.922272000 ;
u16      SYS_longitude_hi       = 0 ;//经纬度
u16      SYS_longitude_low      = 0  ;
u16      SYS_latitude_hi        = 0 ;//经纬度
u16      SYS_latitude_low       = 0  ;
u16      SYS_runstate           = 0;//经纬度
u16      SYS_current            = 0;
u16      SYS_voltage            = 0;
u16      SYS_kw                 = 0;
u16      SYS_oil_pressure       = 0;
int16    SYS_oil_temperatue     = 0;
int16    SYS_gas_level          = 0;//燃油液位

/* 预留:1-4, 400012-15 */
u16      SYS_current_A          = 0;
u16      SYS_current_B          = 0;
u16      SYS_current_C          = 0;


/*400016 */
u16      SYS_total_alarm        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm1             = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm2             = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm3             = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm4             = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm5             = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm6             = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm7             = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm8             = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm9             = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm10            = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm11            = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm12            = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm13            = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm14            = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_alarm15            = 0;    //报警汇总: 1:报警, 0正常

/*3- 历史数据相关:用于历史数据的发送.(把所有的记录挨个移入SYS_hix_xx系列;) */
u16      SYS_his_ID             =   1;      //当前读取的历史记录号
u16      SYS_his_begin_year     =   2016;   //当前这条记录其实年份
u16      SYS_his_begin_month    =   5;     //当前这条记录其实年份
u16      SYS_his_begin_day      =   8;     //当前这条记录其实年份
u16      SYS_his_begin_hour     =   8;     //当前这条记录其实年份
u16      SYS_his_begin_minute   =   8;      //当前这条记录其实年份
u16      SYS_his_begin_second   =   8;      //当前这条记录其实年份
u16      SYS_his_end_year       =   2016;    //当前这条记录其实年份
u16      SYS_his_end_month      =    4;      //当前这条记录其实年份
u16      SYS_his_end_day        =    23;      //当前这条记录其实年份
u16      SYS_his_end_hour       =   12;      //当前这条记录其实年份
u16      SYS_his_end_minute     =    8;      //当前这条记录其实年份
u16      SYS_his_end_second     =    8;      //当前这条记录其实年份
u16      SYS_his_length_hour    =   4;      //本次发电时长:小时
u16      SYS_his_length_minute  =   0;      //本次发电时长,分钟
u16      SYS_his_kwh_hi         =   0;      //单词发电量高低16位
u16      SYS_his_kwh_low        =   10;     //单词发电量高低16位
u16      SYS_his_gas_cost       =   0;      //油耗

u32     SYS_his_kwh = 0;    //单词发电量

u16      SYS_his_longitude_hi   =  0;     //经度高低16位
u16      SYS_his_longitude_low  =  0;     //经度高低16位
u16      SYS_his_latitude_hi    =  0;     //纬度高低16位
u16      SYS_his_latitude_low   =  0;     //纬度高低16位

float   SYS_his_longitude       = 0;    //历史记录:经度;
float   SYS_his_latitude        = 0;    //历史记录:纬度;

u16      SYS_his_alarm1         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm2         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm3         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm4         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm5         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm6         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm7         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm8         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm9         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm10        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm11        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm12        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm13        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm14        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_his_alarm15        = 0;    //报警汇总: 1:报警, 0正常

u16      SYS_his_handshake      = 0;    //通信握手信号
                                        //1:HMI读到1,存数据库后,写0,
                                        //2:单片机读到0,move最老一条数据,写1,并且定时加1
                                        //3:单片机发现当前未运行或者正在运行,没有历史数据,不写1"


/*3- 历史记录相关 */
u16      SYS_rec_ID             =   1;      //当前读取的历史记录号 :
u16      SYS_rec_begin_year     =   2016;   //当前这条记录其实年份
u16      SYS_rec_begin_month    =   5;     //当前这条记录其实年份
u16      SYS_rec_begin_day      =   8;     //当前这条记录其实年份
u16      SYS_rec_begin_hour     =   8;     //当前这条记录其实年份
u16      SYS_rec_begin_minute   =   8;      //当前这条记录其实年份
u16      SYS_rec_begin_second   =   8;      //当前这条记录其实年份
u16      SYS_rec_end_year       =   2016;    //当前这条记录其实年份
u16      SYS_rec_end_month      =    4;      //当前这条记录其实年份
u16      SYS_rec_end_day        =    23;      //当前这条记录其实年份
u16      SYS_rec_end_hour       =   12;      //当前这条记录其实年份
u16      SYS_rec_end_minute     =    8;      //当前这条记录其实年份
u16      SYS_rec_end_second     =    8;      //当前这条记录其实年份
u16      SYS_rec_length_hour    =   4;      //本次发电时长:小时
u16      SYS_rec_length_minute  =   0;      //本次发电时长,分钟
u16      SYS_rec_kwh_hi         =   0;      //单词发电量高低16位
u16      SYS_rec_kwh_low        =   10;      //单词发电量高低16位
u16      SYS_rec_gas_cost       =   0;      //油耗

u32     SYS_rec_kwh = 0;    //单词发电量

u16      SYS_rec_longitude_hi   =  0;     //经度高低16位
u16      SYS_rec_longitude_low  =  0;     //经度高低16位
u16      SYS_rec_latitude_hi    =  0;     //纬度高低16位
u16      SYS_rec_latitude_low   =  0;     //纬度高低16位
           
float   SYS_rec_longitude       = 0;    //历史记录:经度;
float   SYS_rec_latitude        = 0;    //历史记录:纬度;
           
u16      SYS_rec_alarm1         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm2         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm3         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm4         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm5         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm6         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm7         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm8         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm9         = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm10        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm11        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm12        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm13        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm14        = 0;    //报警汇总: 1:报警, 0正常
u16      SYS_rec_alarm15        = 0;    //报警汇总: 1:报警, 0正常

u16      SYS_rec_handshake      = 1;    //通信握手信号
                                        //1:HMI读到1,存数据库后,写0,
                                        //2:单片机读到0,move最老一条数据,写1,并且定时加1
                                        //3:单片机发现当前未运行或者正在运行,没有历史数据,不写1"



   2.2   协议相关的三个函数

sendframe();  cmd_proc(); dtu_mod_proc();  分别用于发送一阵数据,主要是回应主机; 协议解析具体实现, modbus协议处理状态机函数;

只要在没有大的死等延时的后台循环中,插入dtu_mod_proc函数,就可以实现modbus rtu 从机模式的读写功能了. 当然前提是正确的初始化串口,设定正确的波特率和modbus 从机地址;


void dtu_mod_process(void)
{
        if( fFrameDone == OK )                                //接收已经完成
        {
                fFrameDone = NO;                        //清除标志
                usart0_dis_recv();                                //禁止接收中断
                if ( fFrameBad ==TRUE )             //数据帧被破换,不予处理
                {
                         fFrameBad = FALSE;             //清除错误标志
                }
                else                                                         //数据帧正常则解析协议
                {
                         cmd_proc();       
                }
                        RxBufPtr   = RX_BUF_BGN_ADR;//准备下次接收
                        usart0_en_recv();                //重新接收
        }
       
        if ( fRxBufFull == YES )                              //数据帧错误
        {
                 fFrameBad  =  FALSE;                     //清除错误标志
         RxBufPtr   =  RX_BUF_BGN_ADR;        //准备下次接收
        }
                 fRxBufFull =  NO          ;             //清空缓冲区
                 usart0_en_recv();                          //重新接收

}

/*命令处理*/
void cmd_proc(void)
{
        u16 reg_adr =0; //临时 存放要读写的寄存器的地址
//        u16 reg_n =0;   //临时 存放要读写的寄存器的数目
        u08 reg_errcode =0;//出错时的错误编码  
//        u08 i;

        ////////////////////////////////////////////=======================CRC 校验               
        if ( CRC != 0 )                           
        {
        ;
                //usart0_send_byte(0);           //错误代码0: crc 校验错误
                return ;
        }
                CRC = 0xffff;        //准备计算crc       
               
        ///////////////////////////////////////////========================判断地址是否正确
        if( SYS_485_adr != RxBuf[0] && 0 != RxBuf[0] )
        {
                ;
        // usart0_send_byte(1);          //错误代码1 通信地址错误
                return ;
        }

                TxBuf[0] = SYS_485_adr ;//respond frame's header
                TxBuf[1] = RxBuf[1];//respond frame's function code                       
                CRC      = _crc16_update0( CRC ,TxBuf[0] );
                CRC      = _crc16_update0( CRC ,TxBuf[1] );
        ////////////////////////////////////////// ========================识别功能码
        if( RxBuf[1]==0x07 )                      //----------------fast read version
        {
                TxBuf[2]= (HVERSION<<4)+SVERSION;
                CRC = _crc16_update0( CRC ,TxBuf[2] );
                TxBuf[3] = CRC%256;
                TxBuf[4] = CRC/256;
                TxBufPtr = 4;
                send_frame ();
        }
        else
        if( RxBuf[1]==0x03  )         //-------------------------读寄存器
        {
                u08  cnt;
                         TxBuf[2]   = RxBuf[5]*2;                         //字节数
                         CRC                 = _crc16_update0( CRC ,TxBuf[2] );
                         TxBufPtr   = 3;                   //调整发送指针,
            
                for( cnt =0 ; cnt< TxBuf[2] ; )//RxBuf[5]--word counts
                {
                    TxBuf[TxBufPtr]
                        = *((u08 *) modbus_tab [cnt +RxBuf[3]*2]);//RxBuf[3]中为起始地址低8位
                        CRC         = _crc16_update0( CRC ,TxBuf[TxBufPtr] );
                        cnt         =  cnt + 1;
                        TxBufPtr    =  TxBufPtr + 1  ;
                }
                        TxBuf[TxBufPtr] = CRC%256;
                        TxBufPtr        = TxBufPtr + 1;        //调整发送指针
                        TxBuf[TxBufPtr] = CRC/256;
                        send_frame();//发送         
         }
        else
        if( RxBuf[1]==0x10 )                                         //--------------------写寄存器
        {
                u08  cnt;
        TxBuf[2]   = RxBuf[2];//写起始地址高8位,一般为0
        TxBuf[3]   = RxBuf[3];//写起始地址低8位
        TxBuf[4]   = RxBuf[4];//欲写寄存器数目高8位,一般为0
        TxBuf[5]   = RxBuf[5];//欲写寄存器数目低8位

        //不允许写长度为0
        if(RxBuf[6] < 2)         goto err_ret;               
        //通信协议的规范性检测,从数据帧长度判断       
        if( (6 + 2 +  RxBuf[6] )  != RxBufPtr )  goto err_ret;//指令格式不正确
         
        for( cnt =0 ; cnt< RxBuf[6] ;cnt++ )     //RxBuf[6]--byte counts
        {
         
          *((u08 *) modbus_tab[ cnt +RxBuf[3]*2] )
            
            =   RxBuf[ 7 + cnt ];            //RxBuf[3]中为起始地址低8位,乘以2,是将之转换为内部的字节地址
        }

        for ( cnt = 0 ; cnt < 4 ; cnt ++ ) //发送字节指针为0-7,其中:0 .1 为固定,其crc已经计算好
        {
            CRC = _crc16_update0( CRC ,TxBuf[2+cnt] );//2-5
        }
        TxBuf[6] = CRC%256;
        TxBuf[7] = CRC/256;                                
        TxBufPtr = 7;                    //调整发送指针
        send_frame();                    //发送
        
        err_ret:         TxBufPtr = TX_BUF_BGN_ADR;    //after sending ,reset ptr
        
     }
        else
        if( RxBuf[1]==0x06 )                                        //----------写寄存器(单个寄存器)
        {
                /* 先获取寄存器地址 */
                reg_adr =((u16) RxBuf[2]<<8) + (u16) RxBuf[3]        ;        //        reg_adr = (u16) RxBuf[3]        ;

        /* 最大写入地址限定 */
        if(reg_adr > MODBUS_ADDR_MAX )       
                {
                        reg_errcode = 2;//非法的数据地址
                       
                        goto err_ret_06;
                }
                /* 预写一次寄存器值
            */
                 *((u08 *)(modbus_tab [reg_adr*2]))=  RxBuf[4] ;
                 *((u08 *)(modbus_tab [reg_adr*2+1]))=RxBuf[5] ;
         
        ///////发送会送数据帧
        TxBuf[0] = RxBuf[0];
        TxBuf[1] = RxBuf[1];
        TxBuf[2] = RxBuf[2];
        TxBuf[3] = RxBuf[3];
        TxBuf[4] = RxBuf[4];
        TxBuf[5] = RxBuf[5];
        TxBuf[6] = RxBuf[6];
        TxBuf[7] = RxBuf[7];
        TxBufPtr = 7;                //调整发送指针
        send_frame();                //发送                               
                       
        ///////保存参数
        //save_user_data_to_flash();   //保存更改
        
        /////////////错误处理:
        err_ret_06:
        /* 如果是非法的数据地址 */
        if( reg_errcode == 2 )
        {
            TxBuf[0] = RxBuf[0];
            TxBuf[1] = 0x86;
            CRC =0xffff;
            CRC      = _crc16_update0( CRC ,TxBuf[0] );
            CRC      = _crc16_update0( CRC ,TxBuf[1] );
            TxBuf[2] = reg_errcode;
            CRC      = _crc16_update0( CRC ,TxBuf[2] );
            TxBuf[3] = CRC%256;
            TxBuf[4] = CRC/256;
            TxBufPtr = 4;
            send_frame();
        }
        else
        if( reg_errcode == 3 )
        {
            TxBuf[0] = RxBuf[0];
            TxBuf[1] = 0x86;
            CRC =0xffff;
            CRC      = _crc16_update0( CRC ,TxBuf[0] );
            CRC      = _crc16_update0( CRC ,TxBuf[1] );
            TxBuf[2] = reg_errcode;
            CRC      = _crc16_update0( CRC ,TxBuf[2] );
            TxBuf[3] = CRC%256;
            TxBuf[4] = CRC/256;
            TxBufPtr = 4;
            send_frame();
        }
            
                        TxBufPtr = TX_BUF_BGN_ADR;    //after sending ,reset ptr         
    }
        
        

}

/*发送一一帧数据*/
void send_frame(void)
{
        u08  i;
    /* 输出指示灯 */
    LED_OUT = 0;
   
        for( i = 0 ; i <= TxBufPtr; i++ ) //TxBufPtr为发送缓冲区指示器
        {
                usart0_send_byte(TxBuf[i]);
        }
        LED_OUT =1;
   
                TxBufPtr = TX_BUF_BGN_ADR;    //after sending ,reset ptr
}




欢迎用到的朋友交流,比较普通,但是可以做简单的modbus rtu通信应用的代码, 后面有时候就会继续优化改进.目前是一个简易可用的实现版本.















            

使用特权

评论回复
板凳
gejigeji521| | 2016-12-11 18:57 | 只看该作者
对这方面真心不熟悉,好好研究一下楼主分享的资料学习一下。

使用特权

评论回复
地板
henryyam| | 2016-12-11 19:52 | 只看该作者
谢谢分享!先收藏了

使用特权

评论回复
5
Harvard|  楼主 | 2016-12-16 09:01 | 只看该作者
希望 可以对需要的朋友有用

使用特权

评论回复
6
捉虫天师| | 2016-12-16 18:12 | 只看该作者
收下了,这一块儿太深奥了。

使用特权

评论回复
7
a20084666| | 2017-1-10 00:07 | 只看该作者
写的不错,

使用特权

评论回复
8
a20084666| | 2017-1-10 00:08 | 只看该作者
楼主你用的串口助手不好,有bug,建议用sscom

使用特权

评论回复
9
Harvard|  楼主 | 2017-1-10 10:33 | 只看该作者
谢谢 提醒  哈哈  已经用了

使用特权

评论回复
10
aksu| | 2020-8-10 09:11 | 只看该作者
你好,我在使用新唐 m051 用的是3.0的库,做串口功能时遇到些问题向你请教一下。
1.硬件已经外接485芯片,软件要配置485功能吗、还是配置普通串功能就好了?看芯片资料上有几种模式可配置,不是很明白、期待你的回复。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:单片机工作者

74

主题

1730

帖子

21

粉丝