今天开始攒代码了,,, nrf 52xxx MODBUS
注意一点:
MODBUS数据域是大端的但是CRC校验码是小端的。
#define SYS_BUSY_MS (g_run2MsTick<<1)
2毫秒定时器判断一帧数据接收完成
void uart_chk_rcv_over_timer_handler(void *p_text)
{
++g_run2MsTick;
//如果正在接收 且 空闲时间大于1个tick 认为接收一帧结束
if((g_uartCrl.is_rcv_busy == UART_RCV_BUSY) \
&& (SYS_BUSY_MS > (2 + g_uartCrl.rcvTime)))
{
g_RS485BuffCtrl.rxLen = g_uartCrl.now_rcv_tail;
g_RS485BuffCtrl.rxFlag = 1;
g_uartCrl.now_rcv_tail = 0;
g_uartCrl.is_rcv_busy = UART_RCV_IDLE;
}
}
串口中断事件处理:
a、接收数据事件:接收数据并设置接收忙碌状态标志
b、发送完成事件: 切换 485方向为输入
/**@snippet [Handling the data received over UART] */
static void uart_event_handle(app_uart_evt_t * p_event)
{
switch (p_event->evt_type)
{
case APP_UART_DATA_READY:
g_uartCrl.is_rcv_busy = UART_RCV_BUSY;
if(g_uartCrl.now_rcv_tail >= RS485_RX_MAX)
g_uartCrl.now_rcv_tail = 0;
UNUSED_VARIABLE(app_uart_get((uint8_t *)&g_RS485BuffCtrl.rxBuff[g_uartCrl.now_rcv_tail++]));
g_uartCrl.rcvTime = SYS_BUSY_MS;
break;
case APP_UART_COMMUNICATION_ERROR:
// APP_ERROR_HANDLER(p_event->data.error_communication);
break;
case APP_UART_FIFO_ERROR:
APP_ERROR_HANDLER(p_event->data.error_code);
break;
case APP_UART_TX_EMPTY:
DIR_485_IN();
break;
default:
break;
}
}
485 硬件驱动层
#include "app_inc.h"
#include "app_uart.h"
#include "nordic_common.h"
#include "nrf.h"
#include "boards.h"
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif
UartCtrol g_uartCrl = UartCtrolDefault;
/**@brief 将串口引脚设为普通IO口工作模式
*/
void SetUartPinDefaultMode()
{
if(g_uartCrl.pinMode == UART_PIN_MODE_UART)
{
app_uart_close();
g_uartCrl.pinMode = UART_PIN_MODE_DEFAULT;
}
}
/**@brief 将串口引脚设为串口工作模式
*/
void SetUartPinUartMode(void)
{
if(g_uartCrl.pinMode == UART_PIN_MODE_DEFAULT)
{
uart_init();
g_uartCrl.pinMode = UART_PIN_MODE_UART;
}
}
/**@brief 485初始化
*/
void Rs485Init(void)
{
SetUartPinUartMode();
DIR_485_INIT();
DIR_485_IN();
}
/**@brief 串口发送函数
*/
void UartSend(uint8_t *p_data,uint8_t len)
{
uint8_t i ;
SetUartPinUartMode();DIR_485_OUT();
for(i = 0;i < len;i++)
{
app_uart_put(p_data[i]);
}
}
#ifndef __UART_H_
#define __UART_H_
#include <stdint.h>
#include <stdbool.h>
#define UART_RCV_BUSY true ///< 接收忙碌,接收中断置1
#define UART_RCV_IDLE false ///< 接收空闲,超时检测置0
#define UART_PIN_MODE_UART true
#define UART_PIN_MODE_DEFAULT false
//485 收发控制
#define DIR_485_INIT() do{nrf_gpio_cfg_output(RS485_DIR_PIN);}while(0)
#define DIR_485_IN() do{nrf_gpio_pin_write(RS485_DIR_PIN,0);}while(0)
#define DIR_485_OUT() do{nrf_gpio_pin_write(RS485_DIR_PIN,1);}while(0)
/*串口接收和发送控制*/
typedef struct {
bool pinMode; ///< 串口引脚工作模式
bool is_rcv_busy; ///< 没有数据在接收 1:有数据正在接收
uint8_t now_rcv_tail; ///< 现在这一帧的接收字节数
uint64_t rcvTime; ///< 接受最近一个字节的时刻
}UartCtrol;
#define UartCtrolDefault \
{ \
.pinMode = UART_PIN_MODE_DEFAULT, \
.is_rcv_busy = UART_RCV_IDLE, \
.now_rcv_tail = 0, \
.rcvTime = 0, \
};
/*var declare................................*/
extern UartCtrol g_uartCrl;
/*fun declare................................*/
void Rs485Init(void);
void UartSend(uint8_t *p_data,uint8_t len);
#endif
modbus.c
#include "modbus.h"
#include "app_inc.h"
#include "app_uart.h"
typedef MBErrType ModbusParseRet; ///< Modbus 解析结果
typedef struct {
const uint8_t funCode; ///< 功能码
ModbusParseRet (*parse)(uint8_t *rcv, uint8_t rcvlen, uint8_t *pack); ///< 处理函数
}ModbusRtuNode;
#define MB_DEV_ADDR 1 ///< @todo: how to set addr?
#define MbParse(name) static ModbusParseRet name (uint8_t *rcv, uint8_t rcvlen, uint8_t *pack)
static uint8_t s_packLen = 0; ///< 回复数据中功能码和crc之间的byte数
/** @brief 根据ModBus规则计算CRC16
* @para _pBuf:待计算数据缓冲区,计算得到的结果存入_pBuf的最后两字节
* _usLen:待计算数据长度(字节数)
* @retval 16位校验值
*/
static uint16_t getModbusCRC16(uint8_t *_pBuf,uint16_t _usLen)
{
unsigned short CRCValue = 0xFFFF; ///< 初始化CRC变量各位为1
unsigned char i,j;
for(i=0;i<_usLen;++i){
CRCValue ^= *(_pBuf+i); ///< 当前数据异或CRC低字节
for(j=0;j<8;++j){ ///< 一个字节重复右移8次
if((CRCValue & 0x01) == 0x01) ///< 判断右移前最低位是否为1
CRCValue = (CRCValue >> 1)^0xA001; ///< 如果为1则右移并异或表达式
else
CRCValue >>= 1; ///< 否则直接右移一位
}
}
return CRCValue;
}
/** @brief 判断通信接收的寄存器地址是否属于正常范围
* @retval true 非法地址 false 合法地址
*/
static inline bool MbRegAddrIllegal(uint16_t addr)
{
if((addr >= MB_INPUT_REG_START) && (addr <= MB_INPUT_REG_END))
return false;
if((addr >= MB_HOLD_REG_START) && (addr <= MB_HOLD_REG_END))
return false;
return true;
}
/** @brief 打包 MODBUS 响应帧
* @para pdu-包括功能码和数据域 pduLen-功能码和数据域的长度
*/
static void PackMbRsp(uint8_t *pdu, uint8_t pduLen)
{
uint8_t *pTx = g_RS485TxBuff;
uint16_t crc;
if((NULL == pdu) || (1 >= pduLen))
return;
pTx[0] = MB_DEV_ADDR;
memcpy(&pTx[1], pdu, pduLen);
crc = getModbusCRC16(pTx, pduLen + 1);
pTx[pduLen + 1] = crc & 0xFF;
pTx[pduLen + 2] = crc >> 8;
//g_RS485BuffCtrl.txLen = pduLen + 3; g_RS485BuffCtrl.txFlag = true;
UartSend(pTx, pduLen + 3);
}
/** @brief 打包 MODBUS 异常响应帧
* @para funcode-功能码 errCode-错误码
*/
static void PackMbErrRsp(uint8_t funcode, uint8_t errCode)
{
uint8_t *pTx = g_RS485TxBuff;
uint16_t crc;
if(MODBUS_NO_ERR == errCode)
return;
pTx[0] = MB_DEV_ADDR;
pTx[1] = funcode | 0x80;
pTx[2] = errCode;
crc = getModbusCRC16(pTx, 3);
pTx[3] = crc & 0xFF;
pTx[4] = crc >> 8;
UartSend(pTx, 5);
}
MbParse(GetInputReg)
{
///< Parse the data and package the reply
return MODBUS_NO_ERR;
}
MbParse(GetHoldReg)
{
///< Parse the data and package the reply
return MODBUS_NO_ERR;
}
MbParse(SetSingleHoldReg)
{
///< Parse the data and package the reply
return MODBUS_NO_ERR;
}
MbParse(SetMultiHoldReg)
{
///< Parse the data and package the reply
return MODBUS_NO_ERR;
}
ModbusRtuNode s_modbusList[] =
{
{MODBUS_GET_HOLD_REG, GetHoldReg },
{MODBUS_GET_INPUT_REG, GetInputReg },
{MODBUS_SET_SINGLE_HOLD_REG, SetSingleHoldReg },
{MODBUS_SET_MULTI_HOLD_REG, SetMultiHoldReg },
};
void ModbusParseTask(void)
{
uint8_t i, len;
uint8_t mbErr = MODBUS_NO_ERR;
uint16_t rcvCrc;
uint8_t *pRx = g_RS485BuffCtrl.parseBuff;
uint8_t *packData = g_RS485PackBuff;
uint8_t rcvDevAddr, rcvFuncode;
uint16_t rcvRegAddr;
if((g_RS485BuffCtrl.rxFlag == false) || (g_RS485BuffCtrl.txFlag == true))
return ;
len = g_RS485BuffCtrl.rxLen;
memcpy(pRx, g_RS485BuffCtrl.rxBuff, len);
g_RS485BuffCtrl.rxFlag = false;
rcvDevAddr = pRx[0];
rcvFuncode = pRx[1];
rcvRegAddr = (pRx[2] << 8) + pRx[3];
rcvCrc = (pRx[len-1] << 8) + pRx[len-2];
/* modbus rtu link layer :
||==============================================||
|| dev addr | Fun code | Data | Crc ||
||------------+------------+----------+---------||
|| 1 byte | 1 byte | n byte | 2 byte ||
||==============================================||*/
if(rcvDevAddr != MB_DEV_ADDR) ///< dev addr err
return;
if(rcvCrc != getModbusCRC16(pRx, len - 2)) ///< crc err
return;
for(i = 0; i < ARRAY_SIZE(s_modbusList); i++)
if(rcvFuncode == s_modbusList[i].funCode)
break;
if(i >= ARRAY_SIZE(s_modbusList)) ///< funcode err
{
PackMbErrRsp(rcvFuncode, MODBUS_ILLEGAL_FUN);
return;
}
if(MbRegAddrIllegal(rcvRegAddr)) ///< reg addr err
{
PackMbErrRsp(rcvFuncode, MODBUS_ILLEGAL_ADDR);
return;
}
if(NULL != s_modbusList[i].parse) ///< normal parse
mbErr = s_modbusList[i].parse(&pRx[2], len-4, &packData[1]);
if(MODBUS_NO_ERR != mbErr)
{
PackMbErrRsp(rcvFuncode, mbErr); ///< response err ret
}
else
{
packData[0] = rcvFuncode;
PackMbRsp(packData, s_packLen+1); ///< response normal data
}
}
modbus.h
#ifndef __MODBUS_H_
#define __MODBUS_H_
#include <stdint.h>
enum ModbusFuncode{
MODBUS_GET_HOLD_REG = 0x03, ///< 读取保持寄存器
MODBUS_GET_INPUT_REG = 0x04, ///< 读取输入寄存器
MODBUS_SET_SINGLE_HOLD_REG = 0x06, ///< 写入单个保持寄存器
MODBUS_SET_MULTI_HOLD_REG = 0x10, ///< 写入多个保持寄存器
};
typedef enum ModbusErrCode {
MODBUS_NO_ERR, ///< 没有异常
MODBUS_ILLEGAL_FUN, ///< 不支持的功能码
MODBUS_ILLEGAL_ADDR, ///< 不支持的地址
MODBUS_ILLEGAL_VAL, ///< 不支持的数据
MODUBS_SLAVE_TROUBLE, ///< 从机故障
MODBUS_CONFIRM, ///< 正在确认,防止主站发生接收超时错误
MODBUS_SLAVE_BUSY, ///< 从属设备忙
} MBErrType;
enum ModbusReg{
///< .............. 输入寄存器 (30001 ~ )
MB_INPUT_REG_START = 30001,
MB_INPUT_REG_END = 31000,
///< .............. 保持寄存器 (40001 ~ )
MB_HOLD_REG_START = 40001,
MB_HOLD_REG_END = 41000,
};
void ModbusParseTask(void); ///< MODUBS 通信任务
#endif