打印
[AT32L021]

【AT-START-L021测评】Modbus RTU 从站的实现

[复制链接]
125|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 bingbing12138 于 2024-12-25 23:25 编辑

#申请原创# #技术资源# 非常感谢雅特力官方给我这样一个测评的机会,本次主要使用AT32L021实现ModbusRTU从站功能,支持的功能码 01 02 03 04 05 06 0F 10
一、Modbus标准简介
Modbus 是由 Modicon(现为施耐德电气公司的一个品牌)在 1979 年发明的,是全球第一个真正用于工业现场的总线协议。
ModBus 网络是一个工业通信系统,由带智能终端的可编程序控制器和计算机通过公用线路或局部专用线路连接而成。其系统结构既包括硬件、亦包括软件。它可应用于各种数据采集和过程监控。
为更好地普及和推动 Modbus 在基于以太网上的分布式应用,目前施耐德公司已将 Modbus 协议的所有权移交给 IDA(Interface for Distributed Automation,分布式自动化接口)组织,并成立了Modbus-IDA 组织,为 Modbus 今后的发展奠定了基础。
在中国,Modbus 已经成为国家标准。
标准编号:GB/T19582-2008
标准名称:《基于 Modbus 协议的工业自动化网络规范》
分 3 个部分:
《GB/T 19582.1-2008 第 1 部分:Modbus 应用协议》
《GB/T 19582.2-2008 第 2 部分:Modbus 协议在串行链路上的实现指南》
《GB/T 19582.3-2008 第 3 部分: Modbus 协议在 TCP/IP 上的实现指南》
二、Modbus协议概述


Modbus 串行链路协议是一个主/从协议。该协议位于 OSI 模型的第二层。
一个主从类型的系统有一个向某个“子”节点发出显式命令并处理响应的节点(主节点)。典型的子节点在没有收到主节点的请求时并不主动发送数据,也不与其它子节点通信。
在物理层,Modbus 串行链路系统可以使用不同的物理接口(RS485、RS232)。最常用的是TIA/EIA-485 (RS485) 两线制接口。

三、Modbus 主站/从站协议原理
Modbus 串行链路协议是一个主-从协议。在同一时刻,只有一个主节点连接于总线,一个或多个子节点 (最大编号为 247 ) 连接于同一个串行总线。Modbus 通信总是由主节点发起。子节点在没有收到来自主节点的请求时,从不会发送数据。子节点之间从不会互相通信。主节点在同一时刻只会发起一个Modbus 事务处理。
主节点以两种模式对子节点发出 Modbus 请求:

单播模式:
主节点以特定地址访问某个子节点,子节点接到并处理完请求后,子节点向主节点返回一个报文(一个'应答')。在这种模式,一个 Modbus 事务处理包含 2 个报文:一个来自主节点的请求,一个来自子节点的应答。
每个子节点必须有唯一的地址 (1 到 247),这样才能区别于其它节点被独立的寻址。

广播模式:
主节点向所有的子节点发送请求。对于主节点广播的请求没有应答返回。广播请求一般用于写命令。
所有设备必须接受广播模式的写功能。地址 0 是专门用于表示广播数据的。

地址规则:
Modbus 寻址空间有 256 个不同地址。
0 1~247 248~255
广播地址 子节点单独地址 保留

地址 0 为广播地址。所有的子节点必须识别广播地址。
Modbus 主节点没有地址,只有子节点必须有一个地址。 该地址必须在 Modbus 串行总线上唯一。
四、Modbus报文RTU
由发送设备将 Modbus 报文构造为带有已知起始和结束标记的帧。这使设备可以在报文的开始接收新帧,并且知道何时报文结束。不完整的报文必须能够被检测到而错误标志必须作为结果被设置。
在 RTU 模式,报文帧由时长至少为 3.5 个字符时间的空闲间隔区分。在后续的部分,这个时间区间被称作 t3.5。


整个报文帧必须以连续的字符流发送。
如果两个字符之间的空闲间隔大于 1.5 个字符时间,则报文帧被认为不完整应该被接收节点丢弃。




注 :
RTU 接收驱动程序的实现,由于 t1.5 和 t3.5 的定时,隐含着大量的对中断的管理。在高通信速率下,这导致 CPU 负担加重。因此,在通信速率等于或低于 19200 bps 时,这两个定时必须严格遵守;
对于波特率大于 19200 bps 的情形,应该使用 2 个定时的固定值:

建议的字符间超时时间(t1.5)为 750µs,
帧间的超时时间 (t1.5) 为 1.750ms。
下图表示了对 RTU 传输模式状态图的描述。 "主节点" 和 "子节点" 的不同角度均在相同的图中表示:


上面状态图的一些解释:
1从 "初始" 态到 “空闲” 态转换需要 t3.5 定时超时: 这保证帧间延迟
1“空闲” 态是没有发送和接收报文要处理的正常状态。
1在 RTU 模式, 当没有活动的传输的时间间隔达 3.5 个字符长时,通信链路被认为在 “空闲” 态。
1当链路空闲时, 在链路上检测到的任何传输的字符被识别为帧起始。 链路变为 "活动" 状态。 然后,当链路上没有字符传输的时间间个达到 t3.5 后,被识别为帧结束。
1检测到帧结束后,完成 CRC 计算和检验。然后,分析地址域以确定帧是否发往此设备,如果不是,则丢弃此帧。 为了减少接收处理时间,地址域可以在一接到就分析,而不需要等到整个帧结束。这样,CRC 计算只需要在帧寻址到该节点 (包括广播帧) 时进行。
五、CRC校验
在 RTU 模式包含一个对全部报文内容执行的,基于循环冗余校验 (CRC - Cyclical RedundancyChecking) 算法的错误检验域。CRC 域检验整个报文的内容。不管报文有无奇偶校验,均执行此检验。
CRC 包含由两个 8 位字节组成的一个 16 位值。
CRC 域作为报文的最后的域附加在报文之后。计算后,首先附加低字节,然后是高字节。CRC 高字节为报文发送的最后一个字节。
附加在报文后面的 CRC 的值由发送设备计算。接收设备在接收报文时重新计算 CRC 的值,并将计算结果于实际接收到的 CRC 值相比较。如果两个值不相等,则为错误。
CRC 的计算, 开始对一个 16 位寄存器预装全 1。然后将报文中的连续的 8 位子节对其进行后续的计算。只有字符中的 8 个数据位参与生成 CRC 的运算,起始位,停止位和校验位不参与 CRC 计算。
CRC 的生成过程中, 每个 8–位字符与寄存器中的值异或。然后结果向最低有效位(LSB)方向移动(Shift) 1 位,而最高有效位(MSB)位置充零。 然后提取并检查 LSB:如果 LSB 为 1, 则寄存器中的值与一个固定的预置值异或;如果 LSB 为 0, 则不进行异或操作。
这个过程将重复直到执行完 8 次移位。完成最后一次(第 8 次)移位及相关操作后,下一个 8 位字节与寄存器的当前值异或,然后又同上面描述过的一样重复 8 次。当所有报文中字节都运算之后得到的寄存器的最终值,就是 CRC。
六、Modubs从站源码
主程序
#include "at32l021_clock.h"
#include "at32l021_board.h"
#include "modbus_slave.h"
#include "bsp_dma.h"
#include "bsp_usart.h"
#include "bsp_time.h"
#include "crc16.h"

//定时器3中断服务函数 处理Modbus主任务
void TMR3_GLOBAL_IRQHandler(void)//1ms中断
{
    if (tmr_flag_get(TMR3, TMR_OVF_FLAG) == SET)
    {
        MODBUS_SLAVE_Pool();
        tmr_flag_clear(TMR3, TMR_OVF_FLAG);
    }
}

//定时器6中断服务函数 计算帧间隔超时计数
void TMR6_GLOBAL_IRQHandler(void)//50us中断
{
    if (tmr_flag_get(TMR6, TMR_OVF_FLAG) == SET)
    {
        timer_record.timer_counter++;
        tmr_flag_clear(TMR6, TMR_OVF_FLAG);
    }
}

//串口2中断服务函数 串口发送完成中断,用于判断报文发送完成从而开启接收
void USART2_IRQHandler(void)
{
    if(usart_interrupt_flag_get(USART2, USART_TDC_FLAG) != RESET)
    {
        RS485_RX_EN();//接收使能
        dma_channel_enable(DMA1_CHANNEL5, TRUE); //DMA接收使能
        usart_interrupt_enable(USART2, USART_TDC_INT, FALSE);//关闭串口发送完成中断
    }
}

crm_clocks_freq_type clocks_struct;
int main(void)
{
    system_clock_config();
    crm_clocks_freq_get(&clocks_struct);
   
    rs485_send_init();//MAX485使能引脚初始化
    bsp_usart_init();//串口2初始化
   
    bsp_dma_uart_tx_init(modbus_slave.tx_buf,TX_LEN_MAX);//初始化串口发送dma
    bsp_dma_uart_rx_init(modbus_slave.rx_buf,RX_LEN_MAX);//初始化串口接收dma
   
    bsp_timer3_init();//1ms
    bsp_timer6_init();//50us
    /*报文接收时间间隔 由于此处使用115200波特率
    Modbus协议栈规定 大于19200 bps时,帧间隔超时时间为1.75ms
    1.75ms = 1750us  1750/50 = 35*/
    timer_record.overtime = 35;
    while(1)
    {
        ;
    }
}
由于内容限制,这里只展示从站主要源码,程序实现Modbus功能码 01 02 03 04 05 06 0F 10:
/************************************************************************/
/*  “文件包含”处理                                                       */
/************************************************************************/
#include "modbus_slave.h"
#include "bsp_dma.h"
#include "bsp_time.h"
#include "bsp_usart.h"
#include "crc16.h"

/************************************************************************/
/*  全局变量定义                                                         */
/************************************************************************/
Modbus_Slave_t modbus_slave = {0};
uint16_t test_hold_reg[3] = {0};// 03  06 10 功能码使用
uint8_t test_led[15] = {0};// 01 05  功能码使用
uint8_t test_adc[5] = {0x66, 0x77};// 04  功能码使用
/************************************************************************/
/*  函数声明                                                             */
/************************************************************************/
static void MODBUS_SLAVE_FunctionCodeHandle(void);
static void MODBUS_SLAVE_01H(void);// 01功能码通过修改test_led[15]数组的值
static void MODBUS_SLAVE_02H(void);// 02功能码通过函数MODBUS_SLAVE_BSP_GetKeyState 返回值
static void MODBUS_SLAVE_03H(void);// 03功能码通过读取test_hold_reg[3]数组的值
static void MODBUS_SLAVE_04H(void);// 04功能码通过读取test_adc[5]数组的值
static void MODBUS_SLAVE_05H(void);// 05功能码通过修改test_led[15]数组的值
static void MODBUS_SLAVE_06H(void);// 06功能码通过修改test_hold_reg[3]数组的值
static void MODBUS_SLAVE_10H(void);// 10功能码通过修改多个test_hold_reg[3]数组的值
static void MODBUS_SLAVE_0FH(void);// 0F功能码通过修改多个test_led[15]数组的值
void MODBUS_SLAVE_SendAckErr(uint8_t ErrCode);

//Modbus主程序
void MODBUS_SLAVE_Pool(void)
{
    uBit16 addr;
    uBit16 crc1;
   
    modbus_slave.curr_dma_cnt = dma_data_number_get(DMA1_CHANNEL5);//获取当前串口接收DMA传输计数器中的值
    if (timer_record.timer_start_flag == 0 && modbus_slave.curr_dma_cnt != RX_LEN_MAX)
    {
        timer_record.timer_start_flag = 1;//表示开始接收了
        timer_record.StartTime = timer_record.timer_counter; // 记录开始时间
        
        modbus_slave.last_dma_cnt = modbus_slave.curr_dma_cnt;
    }
    if (timer_record.timer_start_flag == 1)
    {
        if (modbus_slave.curr_dma_cnt == modbus_slave.last_dma_cnt)
        {
            timer_record.Currvalue = timer_record.timer_counter - timer_record.StartTime;//获取经过的时间
            if (timer_record.Currvalue > timer_record.overtime)//判断是否超时
            {
                timer_record.timer_start_flag = 0;//清除接收标志
                modbus_slave.recv_len = RX_LEN_MAX - modbus_slave.curr_dma_cnt;//已经接收的字节数
               
                //成功接收到一帧报文
                bsp_dma_uart_rx_init(modbus_slave.rx_buf,RX_LEN_MAX);//初始化串口接收DMA
                dma_channel_enable(DMA1_CHANNEL5, FALSE);//接收DMA失能
                RS485_TX_EN();//发送使能
               
                if (modbus_slave.recv_len < 4)  /* 接收到的数据小于4个字节就认为错误 */
                {
                    RS485_RX_EN();//接收使能
                    dma_channel_enable(DMA1_CHANNEL5, TRUE); //DMA接收使能
                    goto err_ret;
                }
               
                /* 计算CRC校验和 */
                crc1 = CRC16_Modbus(modbus_slave.rx_buf, modbus_slave.recv_len);
                if (crc1 != 0)
                {
                    RS485_RX_EN();//接收使能
                    dma_channel_enable(DMA1_CHANNEL5, TRUE); //DMA接收使能
                    goto err_ret;
                }
                /* 站地址 (1字节) */
                addr = modbus_slave.rx_buf[0];                                /* 第1字节 站号 */
                if (addr != SADDR485)                                         /* 判断主机发送的命令地址是否符合 */
                {
                    RS485_RX_EN();//接收使能
                    dma_channel_enable(DMA1_CHANNEL5, TRUE); //DMA接收使能
                    goto err_ret;
                }
               
                //功能码处理
                MODBUS_SLAVE_FunctionCodeHandle();
            }
        }
        else
        {
            // 如果 DMA 接收计数值发生变化,则更新计数
            timer_record.StartTime = timer_record.timer_counter; // 更新开始时间
        }
        modbus_slave.last_dma_cnt = modbus_slave.curr_dma_cnt;//更新此次DMA值
    }
err_ret:
    memset(modbus_slave.rx_buf, 0, modbus_slave.recv_len);
    modbus_slave.recv_len = 0;/* 必须清零计数器,方便下次帧同步 */
}

//Modbus功能码处理
static void MODBUS_SLAVE_FunctionCodeHandle(void)
{
    switch (modbus_slave.rx_buf[1])                                /* 第2个字节 功能码 */
    {
        case 0x01:  /* 读取线圈状态(此例程用led代替)*/
            MODBUS_SLAVE_01H();
            break;

        case 0x02:  /* 读取输入状态(按键状态)*/
            MODBUS_SLAVE_02H();
            break;
        
        case 0x03:  /* 读取保持寄存器*/
            MODBUS_SLAVE_03H();
            break;
        
        case 0x04:  /* 读取输入寄存器(ADC的值)*/
            MODBUS_SLAVE_04H();
            break;
        
        case 0x05:  /* 强制单线圈(设置led)*/
            MODBUS_SLAVE_05H();
            break;
        
        case 0x06:  /* 写单个保存寄存器*/
            MODBUS_SLAVE_06H();        
            break;
            
        case 0x10:  /* 写多个保存寄存器*/
            MODBUS_SLAVE_10H();
            break;
        
        case 0x0F:  /* 写多个线圈寄存器*/
            MODBUS_SLAVE_0FH();
            break;
        default:
            modbus_slave.RTU_response_code = RSP_ERR_CMD;
            MODBUS_SLAVE_SendAckErr(modbus_slave.RTU_response_code);        /* 告诉主机命令错误 */
            break;
    }
}


//将两字节转换为16位
uBit16 MODBUS_SLAVE_BEBufToUint16(uBit8 *_pBuf)
{
    return (((uBit16)_pBuf[0] << 8) | _pBuf[1]);
}


//串口发送
void MODBUS_SLAVE_UartSend(uBit8 *buf, uBit16 len)
{
    //发送函数
    bsp_dma_uart_tx_init(modbus_slave.tx_buf, len);//初始化dma,重置传输计数器
    dma_channel_enable(DMA1_CHANNEL4, TRUE); /* usart2 tx begin dma transmitting */
    usart_flag_clear(USART2, USART_TDC_FLAG);
    usart_interrupt_enable(USART2, USART_TDC_INT, TRUE);
}


//发送加crc
void MODBUS_SLAVE_SendWithCRC(uint8_t *buf, uint8_t len)
{
    uint16_t crc;
   
    crc = CRC16_Modbus(buf, len);
    buf[len++] = crc >> 8;
    buf[len++] = crc;
   
    MODBUS_SLAVE_UartSend(buf, len);
}


//发送错误应答  ErrCode : 错误代码
void MODBUS_SLAVE_SendAckErr(uint8_t ErrCode)
{
    modbus_slave.send_len = 0;
   
    modbus_slave.tx_buf[modbus_slave.send_len++] = modbus_slave.rx_buf[0];          /* 485地址 */
    modbus_slave.tx_buf[modbus_slave.send_len++] = modbus_slave.rx_buf[1] | 0x80;   /* 异常的功能码 */
    modbus_slave.tx_buf[modbus_slave.send_len++] = ErrCode;                      /* 错误代码(01,02,03,04) */

    MODBUS_SLAVE_SendWithCRC(modbus_slave.tx_buf, modbus_slave.send_len);
}

//判断LED指示灯是否已经点亮
uint8_t MODBUS_SLAVE_BSPIsLedOn(uint8_t num)
{
    if (num == 1)
    {
        if (test_led[0])
        {
            return 1;
        }
        return 0;
    }
    else if (num == 2)
    {
        if (test_led[1])
        {
            return 1;
        }
        return 0;
    }
    else if (num == 3)
    {
        if (test_led[2])
        {
            return 1;
        }
        return 0;
    }
    else if (num == 4)
    {
        if (test_led[3])
        {
            return 1;
        }
        return 0;
    }

    return 0;
}


//读线圈状态功能码 01H
static void MODBUS_SLAVE_01H(void)
{
    uBit16 reg;
    uBit16 num;
    uBit16 i;
    uBit16 m;
    uBit8 status[10];

    modbus_slave.RTU_response_code = RSP_OK;

    if (modbus_slave.recv_len != 8)
    {
        modbus_slave.RTU_response_code = RSP_ERR_VALUE; // 数据值域错误
        return;
    }
   
    reg = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[2]);   // 寄存器号
    num = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[4]);   // 寄存器个数

    m = (num + 7) / 8;

    if ((reg >= REG_D01) && (num > 0) && (reg + num <= REG_DXX + 1))
    {
        for (i = 0; i < m; i++)
        {
            status[i] = 0;
        }
        for (i = 0; i < num; i++)
        {
            if (MODBUS_SLAVE_BSPIsLedOn(i + 1 + reg - REG_D01)) // 读LED的状态,写入状态寄存器的每一位
            {  
                status[i / 8] |= (1 << (i % 8));
            }
        }
    }
    else
    {
        modbus_slave.RTU_response_code = RSP_ERR_REG_ADDR;  // 寄存器地址错误
    }

    if (modbus_slave.RTU_response_code == RSP_OK)   // 正确应答
    {
        modbus_slave.send_len = 0;
        modbus_slave.tx_buf[modbus_slave.send_len++] = modbus_slave.rx_buf[0];
        modbus_slave.tx_buf[modbus_slave.send_len++] = modbus_slave.rx_buf[1];
        modbus_slave.tx_buf[modbus_slave.send_len++] = m;   // 返回字节数

        for (i = 0; i < m; i++)
        {
            modbus_slave.tx_buf[modbus_slave.send_len++] = status[i];
        }
        MODBUS_SLAVE_SendWithCRC(modbus_slave.tx_buf, modbus_slave.send_len);
    }
    else
    {
        MODBUS_SLAVE_SendAckErr(modbus_slave.RTU_response_code);    // 告诉主机命令错误
    }
}


//02读取输入状态
uint8_t MODBUS_SLAVE_BSP_GetKeyState(uint8_t _ucKeyID)
{
    return 1;//此处应该返回按键按下状态,这里模拟都为1
}

//读取输入状态功能码 02H
static void MODBUS_SLAVE_02H(void)
{
    uint16_t reg;
    uint16_t num;
    uint16_t i;
    uint16_t m;
    uint8_t status[10];

    modbus_slave.RTU_response_code = RSP_OK;

    if (modbus_slave.recv_len != 8)
    {
        modbus_slave.RTU_response_code = RSP_ERR_VALUE; /* 数据值域错误 */
        return;
    }

    reg = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[2]);   /* 寄存器号 */
    num = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[4]);   /* 寄存器个数 */

    m = (num + 7) / 8;
    if ((reg >= REG_T01) && (num > 0) && (reg + num <= REG_TXX + 1))
    {
        for (i = 0; i < m; i++)
        {
            status[i] = 0;
        }
        for (i = 0; i < num; i++)
        {
            if (MODBUS_SLAVE_BSP_GetKeyState(reg - REG_T01 + i))
            {
                status[i / 8] |= (1 << (i % 8));
            }
        }
    }
    else
    {
        modbus_slave.RTU_response_code = RSP_ERR_REG_ADDR;  /* 寄存器地址错误 */
    }

    if (modbus_slave.RTU_response_code == RSP_OK)   /* 正确应答 */
    {
        modbus_slave.send_len = 0;
        modbus_slave.tx_buf[modbus_slave.send_len++] = modbus_slave.rx_buf[0];
        modbus_slave.tx_buf[modbus_slave.send_len++] = modbus_slave.rx_buf[1];
        modbus_slave.tx_buf[modbus_slave.send_len++] = m;   /* 返回字节数 */

        for (i = 0; i < m; i++)
        {
            modbus_slave.tx_buf[modbus_slave.send_len++] = status[i];   /* T01-02状态 */
        }
        MODBUS_SLAVE_SendWithCRC(modbus_slave.tx_buf, modbus_slave.send_len);
    }
    else
    {
        MODBUS_SLAVE_SendAckErr(modbus_slave.RTU_response_code);    /* 告诉主机命令错误 */
    }
}

//读取保持寄存器的值
uint8_t MODBUS_SLAVE_ReadRegValue(uint16_t reg_addr, uint8_t *reg_value)
{
    uint16_t value;

    switch (reg_addr)   /* 判断寄存器地址 */
    {
        case SLAVE_REG_P01:
            value = test_hold_reg[0];
            break;

        case SLAVE_REG_P02:
            value = test_hold_reg[1];   /* 将寄存器值读出 */
            break;

        default:
            return 0;   /* 参数异常,返回 0 */
    }

    reg_value[0] = value >> 8;
    reg_value[1] = value;

    return 1;   /* 读取成功 */
}


//读取保持寄存器功能码 03H
static void MODBUS_SLAVE_03H(void)
{
    uint16_t reg;
    uint16_t num;
    uint16_t i;
    uint8_t reg_value[64];

    modbus_slave.RTU_response_code = RSP_OK;

    if (modbus_slave.recv_len != 8) /* 03H命令必须是8个字节 */
    {
        modbus_slave.RTU_response_code = RSP_ERR_VALUE; /* 数据值域错误 */
        goto err_ret;
    }

    reg = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[2]);   /* 寄存器号 */
    num = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[4]);   /* 寄存器个数 */
    if (num > sizeof(reg_value) / 2)
    {
        modbus_slave.RTU_response_code = RSP_ERR_VALUE; /* 数据值域错误 */
        goto err_ret;
    }

    for (i = 0; i < num; i++)
    {
        if (MODBUS_SLAVE_ReadRegValue(reg, ®_value[2 * i]) == 0) /* 读出寄存器值放入reg_value */
        {
            modbus_slave.RTU_response_code = RSP_ERR_REG_ADDR;  /* 寄存器地址错误 */
            break;
        }
        reg++;
    }

err_ret:
    if (modbus_slave.RTU_response_code == RSP_OK)   /* 正确应答 */
    {
        modbus_slave.send_len = 0;
        modbus_slave.tx_buf[modbus_slave.send_len++] = modbus_slave.rx_buf[0];
        modbus_slave.tx_buf[modbus_slave.send_len++] = modbus_slave.rx_buf[1];
        modbus_slave.tx_buf[modbus_slave.send_len++] = num * 2; /* 返回字节数 */

        for (i = 0; i < num; i++)
        {
            modbus_slave.tx_buf[modbus_slave.send_len++] = reg_value[2*i];
            modbus_slave.tx_buf[modbus_slave.send_len++] = reg_value[2*i+1];
        }
        MODBUS_SLAVE_SendWithCRC(modbus_slave.tx_buf, modbus_slave.send_len);   /* 发送正确应答 */
    }
    else
    {
        MODBUS_SLAVE_SendAckErr(modbus_slave.RTU_response_code);    /* 发送错误应答 */
    }
}

//读取输入寄存器功能码 04H
static void MODBUS_SLAVE_04H(void)
{
    uint16_t reg;
    uint16_t num;
    uint16_t i;
    uint16_t status[10];

    memset(status, 0, 10);

    modbus_slave.RTU_response_code = RSP_OK;

    if (modbus_slave.recv_len != 8)
    {
        modbus_slave.RTU_response_code = RSP_ERR_VALUE; /* 数据值域错误 */
        goto err_ret;
    }

    reg = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[2]);   /* 寄存器号 */
    num = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[4]);   /* 寄存器个数 */

    if ((reg >= REG_A01) && (num > 0) && (reg + num <= REG_AXX + 1))
    {
        for (i = 0; i < num; i++)
        {
            switch (reg)
            {
                /* 测试参数 */
                case REG_A01:
                      status[i] = test_adc[0];
                    break;
                    
                default:
                    status[i] = 0;
                    break;
            }
            reg++;
        }
    }
    else
    {
        modbus_slave.RTU_response_code = RSP_ERR_REG_ADDR;  /* 寄存器地址错误 */
    }

err_ret:
    if (modbus_slave.RTU_response_code == RSP_OK)   /* 正确应答 */
    {
        modbus_slave.send_len = 0;
        modbus_slave.tx_buf[modbus_slave.send_len++] = modbus_slave.rx_buf[0];
        modbus_slave.tx_buf[modbus_slave.send_len++] = modbus_slave.rx_buf[1];
        modbus_slave.tx_buf[modbus_slave.send_len++] = num * 2; /* 返回字节数 */

        for (i = 0; i < num; i++)
        {
            modbus_slave.tx_buf[modbus_slave.send_len++] = status[i] >> 8;
            modbus_slave.tx_buf[modbus_slave.send_len++] = status[i] & 0xFF;
        }
        MODBUS_SLAVE_SendWithCRC(modbus_slave.tx_buf, modbus_slave.send_len);
    }
    else
    {
        MODBUS_SLAVE_SendAckErr(modbus_slave.RTU_response_code);    /* 告诉主机命令错误 */
    }
}

//强制单线圈功能码 05H
static void MODBUS_SLAVE_05H(void)
{
    uint16_t reg;
    uint16_t value;

    modbus_slave.RTU_response_code = RSP_OK;

    if (modbus_slave.recv_len != 8)
    {
        modbus_slave.RTU_response_code = RSP_ERR_VALUE; /* 数据值域错误 */
        goto err_ret;
    }

    reg = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[2]);   /* 寄存器号 */
    value = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[4]); /* 数据 */

    if ((value>>8) != 0x00 && (value>>8) != 0xFF)
    {
        modbus_slave.RTU_response_code = RSP_ERR_VALUE; /* 数据值域错误 */
        goto err_ret;
    }

    //此处处理可以优化
    if (value>>8)//右移8位为0xFF 则值1 表示打开
        value = 1;
    else//右移8位为0x00 则值0 表示关闭
        value = 0;
   
    if (reg == REG_D01)
    {
        test_led[0] = value;//模拟LED状态
    }
    else if (reg == REG_D02)
    {
        test_led[1] = value;
    }
    else if (reg == REG_D03)
    {
        test_led[2] = value;
    }
    else if (reg == REG_D04)
    {
        test_led[3] = value;//模拟LED状态
    }
    else
    {
        modbus_slave.RTU_response_code = RSP_ERR_REG_ADDR;  /* 寄存器地址错误 */
    }
err_ret:
    if (modbus_slave.RTU_response_code == RSP_OK)   /* 正确应答 */
    {
        //此命令码应答报文与请求报文一致
        memcpy(modbus_slave.tx_buf, modbus_slave.rx_buf, modbus_slave.recv_len - 2);//不复制crc位
        modbus_slave.send_len = (modbus_slave.recv_len - 2);//不复制crc位
        MODBUS_SLAVE_SendWithCRC(modbus_slave.tx_buf, modbus_slave.send_len);
    }
    else
    {
        MODBUS_SLAVE_SendAckErr(modbus_slave.RTU_response_code);    /* 告诉主机命令错误 */
    }
}


//读取保持寄存器的值
uint8_t MODBUS_SLAVE_WriteRegValue(uint16_t reg_addr, uint16_t reg_value)
{
    switch (reg_addr)   /* 判断寄存器地址 */
    {
        case SLAVE_REG_P01:
            test_hold_reg[0] = reg_value;   /* 将值写入保存寄存器 */
            break;
        
        case SLAVE_REG_P02:
            test_hold_reg[1] = reg_value;   /* 将值写入保存寄存器 */
            break;
        
        default:
            return 0;   /* 参数异常,返回 0 */
    }

    return 1;   /* 读取成功 */
}

//写单个寄存器功能码 06H
static void MODBUS_SLAVE_06H(void)
{
    uint16_t reg;
    uint16_t value;

    modbus_slave.RTU_response_code = RSP_OK;

    if (modbus_slave.recv_len != 8)
    {
        modbus_slave.RTU_response_code = RSP_ERR_VALUE; /* 数据值域错误 */
        goto err_ret;
    }

    reg = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[2]);   /* 寄存器号 */
    value = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[4]); /* 寄存器值 */

    if (MODBUS_SLAVE_WriteRegValue(reg, value) == 1)    /* 该函数会把写入的值存入寄存器 */
    {
        ;
    }
    else
    {
        modbus_slave.RTU_response_code = RSP_ERR_REG_ADDR;  /* 寄存器地址错误 */
    }

err_ret:
    if (modbus_slave.RTU_response_code == RSP_OK)   /* 正确应答 */
    {
        //此命令码应答报文与请求报文一致
        memcpy(modbus_slave.tx_buf, modbus_slave.rx_buf, modbus_slave.recv_len - 2);//不复制crc位
        modbus_slave.send_len = (modbus_slave.recv_len - 2);//不复制crc位
        MODBUS_SLAVE_SendWithCRC(modbus_slave.tx_buf, modbus_slave.send_len);
    }
    else
    {
        MODBUS_SLAVE_SendAckErr(modbus_slave.RTU_response_code);    /* 告诉主机命令错误 */
    }
}

//写多个寄存器功能码 10H
static void MODBUS_SLAVE_10H(void)
{
    uint16_t reg_addr;
    uint16_t reg_num;
    uint8_t byte_num;
    uint8_t i;
    uint16_t value;

    modbus_slave.RTU_response_code = RSP_OK;

    if (modbus_slave.recv_len < 11)
    {
        modbus_slave.RTU_response_code = RSP_ERR_VALUE; /* 数据值域错误 */
        goto err_ret;
    }

    reg_addr = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[2]);  /* 寄存器号 */
    reg_num = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[4]);   /* 寄存器个数 */
    byte_num = modbus_slave.rx_buf[6];  /* 后面的数据体字节数 */

    if (byte_num != 2 * reg_num)
    {
        ;
    }

    for (i = 0; i < reg_num; i++)
    {
        value = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[7 + 2 * i]); /* 寄存器值 */

        if (MODBUS_SLAVE_WriteRegValue(reg_addr + i, value) == 1)
        {
            ;
        }
        else
        {
            modbus_slave.RTU_response_code = RSP_ERR_REG_ADDR;  /* 寄存器地址错误 */
            break;
        }
    }

err_ret:
    if (modbus_slave.RTU_response_code == RSP_OK)   /* 正确应答 */
    {
        //此命令码应答报文与请求报文一致
        memcpy(modbus_slave.tx_buf, modbus_slave.rx_buf, 6);//不复制crc位
        modbus_slave.send_len = 6;//不复制crc位
        MODBUS_SLAVE_SendWithCRC(modbus_slave.tx_buf, modbus_slave.send_len);
    }
    else
    {
        MODBUS_SLAVE_SendAckErr(modbus_slave.RTU_response_code);    /* 告诉主机命令错误 */
    }
}

//写多个线圈
uint8_t MODBUS_SLAVE_BSPSetLed(uint8_t num, uint8_t value)
{
    static uint8_t last, last_flag;
    if (num / 8)
    {
        num = 8;
        last++;
    }
    else
    {
        num %= 8;
        last_flag = 1;
    }
    for (uint16_t i = 0; i < num; i++)
    {
        if (last_flag == 0)
        {
            test_led[i+((last - 1) * 8)] = (value & (0x01 << i)) >> i;
        }
        if (last_flag == 1)
        {
            test_led[i + (last * 8)] = (value & (0x01 << i)) >> i;
        }
        
    }
   
    if (last_flag)
    {
        last_flag = 0;
        last = 0;
    }
    return 0;
}

//写多个线圈功能码 0FH
static void MODBUS_SLAVE_0FH(void)
{
    uint16_t reg_num;
    uint8_t byte_num;
    uint8_t i;
    uint16_t value;

    modbus_slave.RTU_response_code = RSP_OK;

    if (modbus_slave.recv_len < 10)
    {
        modbus_slave.RTU_response_code = RSP_ERR_VALUE;                        /* 数据值域错误 */
        goto err_ret;
    }

    reg_num = MODBUS_SLAVE_BEBufToUint16(&modbus_slave.rx_buf[4]);                /* 寄存器个数 */
    byte_num = modbus_slave.rx_buf[6];                                        /* 后面的数据体字节数 */

    //判断寄存器数量与字节数是否对应
    if (reg_num % 8)//寄存器数量不是整数字节,需要+1之后比较
    {
        if (byte_num != ((reg_num / 8) + 1))
        {
            ;
        }
    }
    else//寄存器数量是整数字节直接进行比较
    {
        if (byte_num != (reg_num / 8))
        {
            ;
        }
    }
   
    for (i = 0; i < byte_num; i++)
    {
        value = modbus_slave.rx_buf[7 + i];/* 寄存器值 */
        MODBUS_SLAVE_BSPSetLed(reg_num, value);
        reg_num -= 8;
    }

err_ret:
    if (modbus_slave.RTU_response_code == RSP_OK)   /* 正确应答 */
    {
        //此命令码应答报文与请求报文一致
        memcpy(modbus_slave.tx_buf, modbus_slave.rx_buf, 6);//不复制crc位
        modbus_slave.send_len = 6;//不复制crc位
        MODBUS_SLAVE_SendWithCRC(modbus_slave.tx_buf, modbus_slave.send_len);
    }
    else
    {
        MODBUS_SLAVE_SendAckErr(modbus_slave.RTU_response_code);    /* 告诉主机命令错误 */
    }
}

#ifndef  _MODBUS_SLAVE_H_
#define  _MODBUS_SLAVE_H_

/************************************************************************/
/*  “文件包含”处理                                                       */
/************************************************************************/
#include "data_types.h"


#ifdef __cplusplus
extern "C" {
#endif

/************************************************************************/
/*  宏定义                                                               */
/************************************************************************/
#define SADDR485            (1)     // 从机地址
#define RX_LEN_MAX          (255)   // 报文接收缓冲区大小
#define TX_LEN_MAX          (255)   // 报文发送缓冲区大小

/* 01H 读强制单线圈 */
/* 05H 写强制单线圈 */
#define REG_D01     0x1000
#define REG_D02     0x1002
#define REG_D03     0x1003
#define REG_D04     0x1004
#define REG_DXX     REG_D04

/* 02H 读取输入状态 */
#define REG_T01     0x2001
#define REG_T02     0x2002
#define REG_T03     0x2003
#define REG_TXX     REG_T03

/* 03H 读保持寄存器 */
/* 06H 写保持寄存器 */
/* 10H 写多个保存寄存器 */
#define SLAVE_REG_P01       0x3001
#define SLAVE_REG_P02       0x3002

/* 04H 读取输入寄存器(模拟信号) */
#define REG_A01     0x4001
#define REG_AXX     REG_A01


/* RTU 应答代码 */
#define RSP_OK              (0)     // 成功
#define RSP_ERR_CMD         (0x01)  // 不支持的功能码
#define RSP_ERR_REG_ADDR    (0x02)  // 寄存器地址错误
#define RSP_ERR_VALUE       (0x03)  // 数据值域错误
#define RSP_ERR_WRITE       (0x04)  // 写入失败
     
/************************************************************************/
/*  类型定义                                                             */
/************************************************************************/
typedef struct
{
    uBit8 rx_buf[RX_LEN_MAX];       // 报文接收处理缓冲区
    uBit8 tx_buf[TX_LEN_MAX];       // 报文发送处理缓冲区
    uBit16 curr_dma_cnt;            // 表示当前DMA传输计数器中的值
    uBit16 last_dma_cnt;            // 表示上次DMA传输计数器中的值
    uBit16 recv_len;                // 接收的字节数
    uBit16 send_len;                // 发送的字节数
    uBit8 RTU_response_code;        // RTU应答码
}Modbus_Slave_t;



/************************************************************************/
/*  全局变量声明                                                         */
/************************************************************************/
extern Modbus_Slave_t modbus_slave;


/************************************************************************/
/*  函数声明                                                             */
/************************************************************************/
void MODBUS_SLAVE_Pool(void);


#ifdef __cplusplus
}
#endif

#endif

七、效果展示(由于篇幅限制只展示部分功能码)

01功能码:


05功能码:


0F功能码:







95765676c17285eb78.png (31.19 KB )

95765676c17285eb78.png

80885676c1b551b1de.png (316.78 KB )

80885676c1b551b1de.png

AT32L021C8T7_Modbus_slave .zip

991.58 KB

源码

使用特权

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

本版积分规则

1

主题

1

帖子

0

粉丝