Airwill的笔记 https://bbs.21ic.com/?5782 [收藏] [复制] [RSS]

日志

晒一晒本人基于 lm3sxxx 的包含T15和T35检测的 MODBUS 标准驱动

热度 2已有 2756 次阅读2010-4-19 08:26 |系统分类:ARM| 单片机

2009-11-24


/*****************************************************************************
文件名:   RS485MOD.h
相关文件:
程序作者: Airwill@163.com
当前版本: V0.1 ( 日期: 2009年11月9日 )
软件版权: (2008 - 2009)  ********有限公司

功能描述:  MODBUS 总线串口通讯服务
          modbus(rtu) 通讯规约定义
通讯方式: RS-485 半双工

更改记录:
更改作者:
更改日期:  

*****************************************************************************/


#ifndef  __LM3S_RS485_MODBUS__
#define  __LM3S_RS485_MODBUS__

#define NOD_ID_MAX 31
#define BUS_MODE  BUS_RTU_MODE                // 定义 MODBUS 方式, 为 ASC, RTU 模式之一
#define PARITY_MODE  PARITY_NONE        // 定义 MODBUS 校验方式
#define MBUS_BAND  9600                         // 定义 MODBUS 通信波特率

#define MODBUS_REG_OFFSET  0x3D0        // 定义 MODBUS 寄存器组首地址  

// MODBUS 功能码定义
#define READ_MULT_REGS  3        // 读多个寄存器
#define WHITE_SING_REG  6        // 写单个寄存器

// MODBUS 异常响应码定义
#define ILLEGAL_     01                // 非**能
#define ILLEGAL_DATA_ADDRESS 02         // 非法数据地址
#define ILLEGAL_DATA_VALUE   03         // 非法数据值  
#define SLAVE_DEVICE_FAILURE 04         // 从站设备故障  
#define ACKNOWLEDGE          05         // 等待确认(命令执行中)  
#define SLAVE_DEVICE_BUSY    06         // 从属设备忙  
#define NEGATIVE_ACKNOWLEDGE 07         //  *
#define MEMORY_PARITY_ERROR  08         // 存储奇偶性差错  



#define BUS_ASC_MODE  0                                // ASC 模式, 暂不支持此模式, 勿使用
#define BUS_RTU_MODE  1                                // RTU 模式
#define PARITY_NONE  UART_CONFIG_PAR_NONE|UART_CONFIG_STOP_TWO        // 无校验
#define PARITY_EVEN  UART_CONFIG_PAR_EVEN        // 偶校验
#define PARITY_ODD   UART_CONFIG_PAR_ODD        // 奇校验
// #define STOP_BITS  2                        不必定义停止位数, 由 modbus 协议规定

#if BUS_MODE > BUS_ASC_MODE
#define UARTCFG  (UART_CONFIG_WLEN_8|PARITY_MODE)
#define DataCheck LRC
#else
#define UARTCFG  (UART_CONFIG_WLEN_7|PARITY_MODE)
#define DataCheck getCRC16
#endif

#if  MBUS_BAND > 19200
#define MBUS_TO_T15   SYSTEM_CLOCK*3/4000
#define MBUS_TO_T35   SYSTEM_CLOCK*7/4000
#else
#define MBUS_TO_T15   SYSTEM_CLOCK*33/U0BANDR/2
#define MBUS_TO_T35   SYSTEM_CLOCK/2*77/U0BANDR
#endif

#define RS485DREN_PORT GPIO_PORTD_BASE        // DE/RE 引脚端口定义
#define RS485DREN_PIN  GPIO_PIN_5                // DE/RE 引脚口线定义

#define UART_INT_ALL 0x7D0
#define SBUFFER  HWREG(MBUS_UBASE + UART_O_DR)
#define MBUS_TIMEREG  HWREG(MBUS_TIMER + TIMER_O_TAR)
#define MBUS_SEND()  HWREG(RS485DREN_PORT+RS485DREN_PIN*4)=RS485DREN_PIN
#define MBUS_RECEIVE()  HWREG(RS485DREN_PORT+RS485DREN_PIN*4)=0
#define MBUS_UBASE UART1_BASE        // UART0_BASE
#define MBUS_TIMER TIMER1_BASE        // UART0_BASE

extern  unsigned short gusMbusErrors;                // 数据传输的校验错误记录

extern unsigned char Mbus_send_buff[];                // 接收缓冲区
extern unsigned char Mbus_rev_buff[];                // 发送缓冲区

extern void MbusReceived(void);

extern unsigned long MbusSend(unsigned char uch);  
extern unsigned char LRC(unsigned char *auchMsg, unsigned long ulDataLen);  
// 函数功能: 计算 crc16 校验
extern unsigned short getCRC16(unsigned char *ptr, unsigned long len);
// 给待发送数据添加 CRC 校验码
extern void MbusSendCRC(void);

// 函数功能: MODBUS 定时服务程序
extern void SerialTmr(void);

// 函数功能: MODBUS 总线初始化     
extern void MbusInitUart(void);

#endif









/*****************************************************************************
            
文件名:   RS485MOD.c
相关文件: RS485MOD.h
程序作者: Airwill@163.com
当前版本: V0.1 ( 日期: 2009年11月9日 )
软件版权: (2008 - 2009)  ********有限公司

功能描述:  MODBUS 总线串口通讯服务
通讯方式: RS-485 半双工
校验方式: CRC16
          采用 modbus-rtu 通讯规约
    1. 串口中断允许自动接收总线上的信息, 当接收的字节后超过 1.5 个字节时间没有
新的字节认为本次接收完成, 接收完成标志置1,丢弃后面再有接收到的数据。
    2.串口接收数据的处理, 当接收完成标志置 1 进入接收数据处理
        2.1. 首先判断接收的第一位数据与本机地址是否相同,如果不相同清空接收缓存
不发送任何信息;
        2.2. 接收的第一位数据与本机地址相同,则对接收缓存中的数据进行crc16校验,
如果接收的校验位与本校验结果不相同清空接收缓存不发送任何信息;
        2.3. 如果crc16校验正确则根据数据串中的命令码执行相应的处理, 将处理结果
放入缓冲区;
        3. 发送处理: serial_tmr 由 Systick 中断调用 (要求执行周期不大于0.5mS), 以
监视接收过程, 发现超过 1.5 个字节时间, 停止接收过程; 发现超过 3.5 个字节时间,
如果发送缓冲有数据, 即启动发送过程

更改记录:
更改作者:
更改日期:  

*****************************************************************************/

#include "../INCLUDE/hw_types.h"
#include "../INCLUDE/hw_gpio.h"
#include "../INCLUDE/hw_ints.h"
#include "../INCLUDE/HW_NVIC.h"
#include "../INCLUDE/HW_UART.h"
#include "../INCLUDE/hw_timer.h"
#include "../driverlib/timer.h"
#include "../driverlib/gpio.h"
#include "../driverlib/UART.h"
#include "RS485MOD.H"

#define NEW_CRC_ARITHMETIC  1                        // 选择 CRC 算法  
#define CRC_SEND_SEQUENCE   1                        // 0 低字节在前, 1 高字节在前

//u8 Com0_id = 0x05;                                        本机串口0的通讯地址
unsigned char Mbus_rev_buff[128];                // 接收缓冲区
unsigned char Mbus_send_buff[128];                // 发送缓冲区
static unsigned long ulMbusIntervar;        // 记录上次串口中断发生时刻
unsigned char Mbus_rev_count = 0;                // 数据接收指针, 指向有效数据最后地址
unsigned char Mbus_rev_endflag = 0;                // 接收完成标志
unsigned char Mbus_send_counter = 0;        // 数据发送指针, 指向有效数据最后地址
unsigned char Mbus_send_idx = 0;                // 发送数据量, 请求发送标志
unsigned char Mbus_rev_flag = 0;                // 数据接收中标志
unsigned char Mbus_err_flag = 0;                // 数据接收过程发生错误标志
unsigned char Mbus_rev_overtmr = 0;                // 数据接收过程发生 T15 超时
unsigned short gusMbusErrors = 0;                // 数据传输的校验错误记录


/*********************************************************************
函数名称: MbusUart_Isr
函数功能: MODBUS 串口收发中断服务程序
函数输入: NULL
函数返回: NULL
函数编写: Airwill@163.com
编写日期: 2009年11月12日
函数版本: v0.2
**********************************************************************/
void MbusUart_Isr(void) {
        unsigned long ulisrs, rl_sbufRx;
        ulisrs = HWREG(MBUS_UBASE + UART_O_MIS);
        if (ulisrs & UART_RIS_RXRIS) {
                rl_sbufRx = SBUFFER;
                if (rl_sbufRx & 0xF00) {                        // 接收数据错误
                        Mbus_err_flag = TRUE;  gusMbusErrors++;       
                        ulMbusIntervar = MBUS_TIMEREG;
                } else if (Mbus_err_flag) {
                        ulMbusIntervar = MBUS_TIMEREG;
                } else if (Mbus_rev_flag) {
                        unsigned long ultmr = MBUS_TIMEREG;
                        if ((ulMbusIntervar - ultmr) > MBUS_TO_T35) {
                                Mbus_rev_overtmr = Mbus_err_flag = 1;         // 数据未处理完
                                /* Mbus_rev_buff[0] = rl_sbufRx;                // 不可重新接收数据
                                Mbus_rev_count = 0;                         */
                        } else if ((ulMbusIntervar - ultmr) > MBUS_TO_T15) {
                                Mbus_rev_overtmr = TRUE;        
                        } else if (Mbus_err_flag) { ;                // 接收错误, 不再保存数据
                        } else if (Mbus_rev_overtmr) { ;        // 接收错误, 不再保存数据        
                        } else {
                                unsigned long lcount = ++Mbus_rev_count;
                                Mbus_rev_buff[lcount] = rl_sbufRx;        // 保存数据        
                        }
                        ulMbusIntervar = ultmr;                       
                } else {
                        ulMbusIntervar = MBUS_TIMEREG;                 // 更新间隔记录
                        Mbus_rev_flag = TRUE;                                 // 置接收状态
                        Mbus_rev_overtmr = Mbus_err_flag = 0;
                        Mbus_rev_buff[0] = rl_sbufRx;                 // 保存数据          
                        Mbus_rev_count = 0;
                }
                HWREG(MBUS_UBASE + UART_O_ICR) = UART_INT_ALL;                // 清接收中断
        } else if (ulisrs & UART_RIS_TXRIS)  {                                         // 发送中断
                ulMbusIntervar = MBUS_TIMEREG;        
                if ((Mbus_send_counter) < Mbus_send_idx) {                         // 继续数据发送
                        UARTCharPut(MBUS_UBASE, Mbus_send_buff[Mbus_send_counter++]);
                } else {                                                // 发送结束
                        MBUS_RECEIVE();                                // 设置 485 接收状态
                        Mbus_send_idx = 0;                        // 清除缓冲数据状态        
                        Mbus_send_counter = 0;                // 指向有效数据尾
                }
                HWREG(MBUS_UBASE + UART_O_ICR) = UART_ICR_RXIC;         // 清发送中断
        }
}


/*********************************************************************
函数名称: SerialTmr
函数功能: MODBUS 定时服务程序
函数输入: NULL
函数返回: NULL
函数编写: Airwill@163.com
编写日期: 2009年11月12日
函数版本: v0.2
**********************************************************************/
void SerialTmr(void) {
        unsigned long ultmr = MBUS_TIMEREG;
        if ((ulMbusIntervar - ultmr) > MBUS_TO_T35) {
                Mbus_err_flag = 0;
                if (Mbus_send_idx) {
                        if (Mbus_send_counter ==0) {
                                Mbus_send_counter = 1;
                                UARTCharPut(MBUS_UBASE, Mbus_send_buff[0]);        // 启动数据发送
                        }
                }
        } else if ((ulMbusIntervar - ultmr) > MBUS_TO_T15) {
                if (Mbus_rev_count) {
                        Mbus_rev_endflag = TRUE;         // 请求应用服务
                } else if (Mbus_send_counter) { // 发送超时, 重新发送
                        Mbus_send_counter = 0;
                }
        }
}

// 写总线发送缓冲
unsigned long MbusSend(unsigned char uch) {
        unsigned long dcp = Mbus_send_idx;
        if (dcp >= sizeof(Mbus_send_buff))  return FALSE;
        else {
                Mbus_send_buff[dcp++] = uch;
                Mbus_send_idx = dcp;                         // 指向未用首地址
                return TRUE;
        }
}

/*********************************************************************
函数输入: unsigned char *auchMsg ;           要计算  LRC  的报文  
          unsigned short usDataLen ;   报文的字节数   
函数返回: unsigned  char 类型的 LRC  结果     
**********************************************************************/
unsigned char LRC(unsigned char *auchMsg, unsigned long ulDataLen) {
        unsigned char uchLRC = 0;   // LRC 初始化   
        while (ulDataLen--)                  // 完成整个报文缓冲区   
                uchLRC += *auchMsg++;   // 缓冲区字节相加,无进位   
        return ((unsigned char)(-((char)uchLRC)));   // 返回二进制补码   
}

/*********************************************************************
函数名称: getCRC16
函数功能: 计算 crc16 校验
函数输入: 字节指针 *ptr,数据长度 len
函数返回: 双字节 crc 校验
函数编写: Airwill@163.com
编写日期: 2009年11月11日
函数版本: v0.2
**********************************************************************/
#if NEW_CRC_ARITHMETIC >0
const unsigned short CrcLoopup[16] = {
 0x0000, 0xCC01, 0xD801, 0x1400,  0xF001, 0x3C00, 0x2800, 0xE401,
 0xA001, 0x6C00, 0x7800, 0xB401,  0x5000, 0x9C01, 0x8801, 0x4400
};
unsigned short getCRC16(unsigned char *ptr, unsigned long len) {
 unsigned short crc = 0xFFFF;    // CRC 初始化  
 if (len == 0)  len = 1; 
 do {  
  unsigned long ucdat = *ptr++;  
  unsigned long ucrct;
  crc ^= ucdat; ucrct = crc % 16;
  crc = crc /16 ^ CrcLoopup[ucrct]; 
  ucrct = crc % 16;
  crc = crc /16 ^ CrcLoopup[ucrct]; 
 } while (--len); 
 return(crc);  
}
#else
unsigned short getCRC16(unsigned char *ptr, unsigned long len) {
        int i;
        unsigned short crc = 0xFFFF;    // CRC 初始化   
        if (len == 0)  len = 1;  
        do {   
                crc ^= *ptr++;  
                for (i=7; i>=0; i--) {
                        if (crc &1) {       
                                crc >>= 1;  
                                crc ^= 0xA001;       
                        } else  crc >>= 1;       
                }  
        } while (--len);  
        return(crc);
}
#endif

// 给待发送数据添加 CRC 校验码
void MbusSendCRC(void) {
        unsigned short scrc = getCRC16(Mbus_send_buff, Mbus_send_idx);
#if CRC_SEND_SEQUENCE  > 0                        // 0 低字节在前, 1 高地址在前
        MbusSend(scrc / 256);
        MbusSend(scrc % 256);
#else
        MbusSend(scrc % 256);
        MbusSend(scrc / 256);
#endif
}

/*********************************************************************
函数名称: ModbusInvalidComm
函数功能: MODBUS 无效命令的处理程序   
函数输入: NONE
函数返回: NONE
函数编写: Airwill@163.com
编写日期: 2009年11月11日
函数版本: v0.1
**********************************************************************/
void ModbusInvalidComm(void) {
        MbusSend(nodid);
        MbusSend(Mbus_rev_buff[0] | 0x80);
        MbusSend(ILLEGAL_);
        MbusSendCRC();
}

/*********************************************************************
函数名称: MbusInitUart
函数功能: MODBUS 总线初始化     
函数输入: NONE
函数返回: NONE
函数编写: Airwill@163.com
编写日期: 2009年11月11日
函数版本: v0.1
**********************************************************************/
void MbusInitUart(void) {
        UARTConfigSet1(MBUS_UBASE, UARTCFG);     
        HWREG(GPIO_PORTD_BASE + GPIO_O_AFSEL) |= 12;
        MBUS_RECEIVE();                                                        // 设置 485 接收状态
        HWREG(MBUS_TIMER + TIMER_O_CTL) = 0;         // 关闭
        HWREG(MBUS_TIMER + TIMER_O_TAMR) = 2;         // 设置超时计时
        HWREG(MBUS_TIMER + TIMER_O_CTL) = TIMER_CTL_TAEN;                 // 启动定时器  
        HWREG(MBUS_UBASE + UART_O_IM)= UART_INT_RX|UART_INT_TX; // 允许中断
        HWREG(NVIC_EN0) = 1 << (INT_UART1 - 16);
}       


static unsigned short ussends[128];        // 数据缓冲

// 获取寄存器值: 读取指定代号的寄存器, 写到指定的地址, 如果非法代号, 返回错误代码
extern unsigned long GetRegVal(unsigned short * uspaddr, unsigned short usregid);
// 写寄存器值命令处理程序: 写指定代号的寄存器, 如果非法代号, 返回错误代码  
extern unsigned long SetRegVal(unsigned short usregid, unsigned long ulvar);

/*********************************************************************
函数名称: MbusReadRegs
函数功能: MODBUS 读多个寄存器命令的处理程序   
函数输入: NONE
函数返回: NONE
函数编写: Airwill@163.com
编写日期: 2009年11月13日
函数版本: v0.1
**********************************************************************/
void MbusReadRegs(void) {
        unsigned long uladdr;
        uladdr = Mbus_rev_buff[2] * 256 + Mbus_rev_buff[3];
        if (Mbus_rev_count != 7) {                        
                ModbusInvalidComm();          // 非法命令长度 return;       
        } else if (uladdr < MODBUS_REG_OFFSET) {
                MbusSend(nodid);
                MbusSend(Mbus_rev_buff[0] | 0x80);
                MbusSend(ILLEGAL_DATA_ADDRESS);         // 非法数据地址
                MbusSendCRC();
        } else {
                unsigned long ulregs = Mbus_rev_buff[4] * 256 + Mbus_rev_buff[5];  
                unsigned short * uspbuff;
                unsigned long ulloop = uladdr - MODBUS_REG_OFFSET;
                unsigned long ulsends;
                if (ulregs ==0)  ulregs = 1;                // 强制不少于 1 个
                ulsends = ulregs;
                uspbuff = ussends;
                do {
                        unsigned long ulres = GetRegVal(uspbuff++, ulloop++);
                        if (ulres) {
                                MbusSend(nodid);
                                MbusSend(Mbus_rev_buff[0] | 0x80);
                                MbusSend(ulres);                         // 发送返回的故障
                                MbusSendCRC();        return;                       
                        }
                } while (--ulregs);
                MbusSend(nodid);
                MbusSend(Mbus_rev_buff[0]);
                MbusSend(ulsends * 2);                                 // 数据包长度
                uspbuff = ussends;
                do {
                        unsigned long ulvar = *uspbuff++;
                        MbusSend(ulvar / 256);
                        MbusSend(ulvar);
                } while (--ulsends);
                MbusSendCRC();
        }
}

/*********************************************************************
函数名称: MbusWriteReg
函数功能: MODBUS 写单个寄存器命令的处理程序   
函数输入: NONE
函数返回: NONE
函数编写: Airwill@163.com
编写日期: 2009年11月13日
函数版本: v0.1
**********************************************************************/
void MbusWriteReg(void) {
        unsigned long uladdr;
        uladdr = Mbus_rev_buff[2] * 256 + Mbus_rev_buff[3];
        if (Mbus_rev_count != 7) {                        
                ModbusInvalidComm();                 // 非法命令长度  return;
        } else if (uladdr < MODBUS_REG_OFFSET) {
                MbusSend(nodid);
                MbusSend(Mbus_rev_buff[0] | 0x80);
                MbusSend(ILLEGAL_DATA_ADDRESS);         // 非法数据地址
                MbusSendCRC();
        } else         {
                unsigned long ulvar = Mbus_rev_buff[4] * 256 + Mbus_rev_buff[5];
                ulvar = SetRegVal(uladdr - MODBUS_REG_OFFSET, ulvar);  
                MbusSend(nodid);
                if (ulvar) {
                        MbusSend(Mbus_rev_buff[0] | 0x80);
                        MbusSend(ulvar);                                 // 发送返回的故障
                        MbusSendCRC();               
                } else {
                        MbusSend(Mbus_rev_buff[0]);         // 回送命令字
                        MbusSend(uladdr / 256);                        // 回送地址码
                        MbusSend(uladdr);
                        GetRegVal(ussends, uladdr - MODBUS_REG_OFFSET);
                        ulvar = ussends[0];                         // 读取刚写过的寄存器
                        MbusSend(ulvar / 256);
                        MbusSend(ulvar);  
                        MbusSendCRC();               
                }
        }
}

/*********************************************************************
函数名称: MbusReceived
函数功能: MODBUS 接收数据的校验和命令解释
函数输入: NONE
函数返回: NONE
函数编写: Airwill@163.com
编写日期: 2009年11月13日
函数版本: v0.1
特别要求: 将本函数置于主循环, 并尽可能多地被执行, 以提高 modbus 的响应速度
**********************************************************************/
void MbusReceived(void) {  
        if (Mbus_rev_endflag)  {                                // 接收完成标志处理, 否则退出
                if (Mbus_rev_buff[0] == nodid) {        // 若地址错误, 不应答       
                        unsigned short crcresult;
                        unsigned long ulen = Mbus_rev_count;  
#if CRC_SEND_SEQUENCE  > 0                        // 0 低字节在前, 1 高地址在前
                        crcresult = Mbus_rev_buff[ulen -1]*256 + Mbus_rev_buff[ulen];
#else
                        crcresult = *(unsigned short *)(&Mbus_rev_buff[ulen -1]);
#endif
                        // 若 crc 校验错误, 不应答
                        if (crcresult == getCRC16(Mbus_rev_buff, ulen -1)) {
                                unsigned int buscomm;
                                MBUS_SEND();                                // 准备发送 SETBIT(PORTC,PC6);
                                buscomm = Mbus_rev_buff[1];
                                if (buscomm == READ_MULT_REGS) {                 // 读取保持寄存器
                                        MbusReadRegs();                                             
                                } else if (buscomm == WHITE_SING_REG) {        // 预置单寄存器
                                        MbusWriteReg();               
                                } else {        
                                        ModbusInvalidComm();                                // 无效命令字
                                }
                        }
                }
                Mbus_rev_flag = Mbus_rev_endflag = 0;
                Mbus_rev_count = 0;
        }                 
}
//////////////////////////////////////////////////////////////////////////


路过

鸡蛋

鲜花

握手

雷人

发表评论 评论 (2 个评论)

回复 ddc21ic 2013-10-8 11:59
  
回复 gaoyang9992006 2013-12-27 18:03
学习学习,太好了