1 背景
最近有一個modbus通信協議的需求,借此機會總結一下modbus在rtthread上的應用。
2 RS485
2.1 簡介
RS485通信接口一般應用在物聯網自動化場景,只有2根線,工作在半雙工模式。
2.2 與RS232對比
2.3 正點原子開發板上的應用電路
RS485低電平是接收模式,高電平是發送模式,在發送函數中發送之前切換為發送狀態,發送完后切換為接收狀態
3 modbus
3.1 1.5T和3.5T
modbus協議規定字節間隔為1.5個字符,幀間隔為3.5個字符若串口通信參數設置為(注:開始位固定為1):數據位8,奇偶校驗位1,停止位1,波特率9600bps,
則傳輸一個字符(即1個字節)的時間為:(1+8+1+1)/9600=0.00114583s=1.1454583ms
1.5字符間隔=1.5x1.1454583ms=1.71818745ms
3.5字符間隔=3.5x1.1454583ms=4.00910405ms
38400bps,則傳輸一個字符(即1個字節)的時間為:
(1+8+1+1)/38400=0.00028645=0.286ms
1.5字符間隔=1.5x0.286ms=0.4ms
3.5字符間隔=3.5x0.286ms=1ms
3.2 libmodbus
libmodbus是一個基于C語言實現的Modbus驅動庫,作者是Stephane,支持Linux, Mac OS X, FreeBSD, QNX and Win32操作系統,主要應用在PC上,用來開發上位機,也可以對源代碼進行交叉編譯,以適配更多的平臺,比如ARM Linux。 源代碼開源,遵循 LGPL-2.1 許可。
4 相關代碼
該版本不需要支持select和poll機制
4.1 宏定義
#define _RESPONSE_TIMEOUT 500000
#define _BYTE_TIMEOUT 5000
#define HAVE_DECL_TIOCM_RTS 1
4.2 初始化
int rc = 0;
uint8_t mb_reply[MODBUS_TCP_MAX_ADU_LENGTH];
uint16_t tab_reg[64] = {0};
char dev_name[32] ="/dev/uart2";
#ifndef RT_USING_POSIX_STDIO
sprintf(dev_name,"%s","uart2");
#endif
ctx = modbus_new_rtu(dev_name, 115200, 'N', 8, 1);
rt_kprintf("ctx =[%x]\\n",ctx);
modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
modbus_rtu_set_rts(ctx, RS485_RE, MODBUS_RTU_RTS_UP);
modbus_set_slave(ctx, CONFIG_SLAVE_ID); /* 設置從機地址 */
modbus_set_debug(ctx,1);
modbus_set_response_timeout(ctx, 0, 1000000);
mb_mapping = modbus_mapping_new(0, 0, CONFIG_REG_HOLD_MAX, 0);
if (mb_mapping == RT_NULL)
{
rt_kprintf("modbus_mapping_new failed! \\n");
modbus_free(ctx);
return;
}
mb_mapping->tab_registers[0] = 'R';
mb_mapping->tab_registers[1] = 'T';
mb_mapping->tab_registers[2] = '-';
mb_mapping->tab_registers[3] = 'T';
mb_mapping->tab_registers[4] = 'h';
mb_mapping->tab_registers[5] = 'r';
mb_mapping->tab_registers[6] = 'e';
mb_mapping->tab_registers[7] = 'a';
mb_mapping->tab_registers[8] = 'd';
mb_mapping->tab_registers[0x0b] = 0x1234;
#ifndef RT_USING_POSIX_STDIO
rt_sem_init(&ctx->rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
#endif
modbus_connect(ctx);
int num = 0;
4.3 接收處理
_modbus_receive_msg
兼容了兩種不同的方式
4.3.1 方式1 檢查串口隊列是否有數據或者是否滿
select實際上是讓線程進入睡眠,直到有事件響應就喚醒,同時檢查串口隊列中是否有數據。
4.3.1.1 接收處理函數
int rc;
#ifdef RT_USING_POSIX_STDIO
fd_set rset;
#else
uint8_t *rset;
#endif
struct timeval tv;
struct timeval *p_tv;
int length_to_read;
int msg_length = 0;
_step_t step;
if (ctx->debug) {
if (msg_type == MSG_INDICATION) {
rt_kprintf("Waiting for a indication...\\n");
} else {
rt_kprintf("Waiting for a confirmation...\\n");
}
}
/* Add a file descriptor to the set */
#ifdef RT_USING_POSIX_STDIO
FD_ZERO(&rset);
FD_SET(ctx->s, &rset);
#endif
/* We need to analyse the message step by step. At the first step, we want
* to reach the function code because all packets contain this
* information. */
step = _STEP_SLAVE_ID;
length_to_read = ctx->backend->header_length + 1;
if (msg_type == MSG_INDICATION)
{
/* Wait for a message, we don't know when the message will be
* received */
p_tv = NULL;
}
else
{
tv.tv_sec = ctx->response_timeout.tv_sec;
tv.tv_usec = ctx->response_timeout.tv_usec;
p_tv = &tv;
}
length_to_read = ctx->backend->header_length + 1;
if (msg_type == MSG_INDICATION) {
/* Wait for a message, we don't know when the message will be
* received */
p_tv = NULL;
} else {
tv.tv_sec = ctx->response_timeout.tv_sec;
tv.tv_usec = ctx->response_timeout.tv_usec;
p_tv = &tv;
}
while (length_to_read != 0)
{
uint32_t get_tick = rt_tick_get();
rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read);
rt_kprintf("takes ms=[%d]\\n",rt_tick_get() - get_tick);
if (rc == -1)
{
_error_print(ctx, "select");
if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK)
{
int saved_errno = errno;
if (errno == ETIMEDOUT) {
_sleep_response_timeout(ctx);
modbus_flush(ctx);
} else if (errno == EBADF) {
modbus_close(ctx);
modbus_connect(ctx);
}
errno = saved_errno;
}
return -1;
}
rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read);
if (rc == 0)
{
errno = ECONNRESET;
rc = -1;
}
if (rc == -1) {
_error_print(ctx, "read");
if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
(errno == ECONNRESET || errno == ECONNREFUSED ||
errno == EBADF)) {
int saved_errno = errno;
modbus_close(ctx);
modbus_connect(ctx);
/* Could be removed by previous calls */
errno = saved_errno;
}
return -1;
}
/* Display the hex code of each character received */
if (ctx->debug) {
int i;
for (i=0; i < rc; i++)
printf("<%.2X>", msg[msg_length + i]);
}
/* Sums bytes received */
msg_length += rc;
/* Computes remaining bytes */
length_to_read -= rc;
if (length_to_read == 0)
{
switch (step)
{
case _STEP_SLAVE_ID:
{
if (CONFIG_SLAVE_ID != msg[0])
{
break;
}
else
{
step = _STEP_FUNCTION;
}
}
case _STEP_FUNCTION:
{
length_to_read = compute_meta_length_after_function(
msg[ctx->backend->header_length],
msg_type);
if (length_to_read != 0) {
step = _STEP_META;
} /* else switches straight to the next step */
}
break;
case _STEP_META:
length_to_read = compute_data_length_after_meta(
ctx, msg, msg_type);
if ((msg_length + length_to_read) > (int)ctx->backend->max_adu_length) {
errno = EMBBADDATA;
_error_print(ctx, "too many data");
return -1;
}
step = _STEP_DATA;
break;
default:
break;
}
}
if (length_to_read > 0 &&
(ctx->byte_timeout.tv_sec > 0 || ctx->byte_timeout.tv_usec > 0)) {
/* If there is no character in the buffer, the allowed timeout
interval between two consecutive bytes is defined by
byte_timeout */
tv.tv_sec = ctx->byte_timeout.tv_sec;
tv.tv_usec = ctx->byte_timeout.tv_usec;
p_tv = &tv;
}
/* else timeout isn't set again, the full response must be read before
expiration of response timeout (for CONFIRMATION only) */
}
if (ctx->debug)
rt_kprintf("\\n");
return ctx->backend->check_integrity(ctx, msg, msg_length);
4.3.1.2從串口設備讀數據
4.3.1.2 從串口設備讀數據_modbus_rtu_recv
#if defined(_WIN32)
return win32_ser_read(&((modbus_rtu_t *)ctx->backend_data)->w_ser, rsp, rsp_length);
#else
#ifdef RT_USING_POSIX_STDIO
return read(ctx->s, rsp, rsp_length);
#else
return rt_device_read(ctx->dev, 0,rsp, rsp_length);
#endif
4.3.1.3 檢查串口隊列
int poll_get(modbus_t *ctx)
{
int mask = 0;
rt_base_t level;
struct rt_serial_rx_fifo* rx_fifo;
//rt_poll_add(&(device->wait_queue), req);
struct rt_serial_device *serial;
serial = (struct rt_serial_device *)ctx->dev;
rx_fifo = (struct rt_serial_rx_fifo*) serial->serial_rx;
level = rt_hw_interrupt_disable();
if ((rx_fifo->get_index != rx_fifo->put_index) || (rx_fifo->get_index == rx_fifo->put_index && rx_fifo->is_full == RT_TRUE))
{
mask = 1;
}
rt_hw_interrupt_enable(level);
return mask;
}
static int _modbus_rtu_select(modbus_t *ctx, void *rset,
struct timeval *tv, int length_to_read)
{
int s_rc;
#if defined(_WIN32)
s_rc = win32_ser_select(&((modbus_rtu_t *)ctx->backend_data)->w_ser,
length_to_read, tv);
if (s_rc == 0) {
errno = ETIMEDOUT;
return -1;
}
if (s_rc < 0) {
return -1;
}
#else
#ifdef RT_USING_POSIX_STDIO
fd_set *new_rset = (fd_set *)rset;
while ((s_rc = select(ctx->s+1, new_rset, NULL, NULL, tv)) == -1) {
if (errno == EINTR) {
if (ctx->debug) {
fprintf(stderr, "A non blocked signal was caught\\n");
}
/* Necessary after an error */
FD_ZERO(new_rset);
FD_SET(ctx->s, new_rset);
} else {
return -1;
}
}
#else
uint32_t msec = 0;
if (tv)
{
msec = tv->tv_sec * 1000 + tv->tv_usec / 1000;
}
else
{
msec = 1000;
}
uint32_t ms_delay_ivt = 10;
uint32_t get_tick = rt_tick_get();
if (msec < ms_delay_ivt)
{
ms_delay_ivt = msec;
}
while (1)
{
s_rc = poll_get(ctx);
if ((s_rc) || ((rt_tick_get() - get_tick)>msec))
{
break;
}
rt_thread_mdelay(ms_delay_ivt);
}
#endif
if (s_rc == 0)
{
/* Timeout */
errno = ETIMEDOUT;
return -1;
}
#endif
4.3.2 方式2
4.3.2.1 直接等待接收數據
不使用先select后rec的方式,而是直接等待接收串口數據
int rc;
#ifdef RT_USING_POSIX_STDIO
fd_set rset;
#else
uint8_t *rset;
#endif
struct timeval tv;
struct timeval *p_tv;
int length_to_read;
int msg_length = 0;
_step_t step;
if (ctx->debug) {
if (msg_type == MSG_INDICATION) {
rt_kprintf("Waiting for a indication...\\n");
} else {
rt_kprintf("Waiting for a confirmation...\\n");
}
}
/* Add a file descriptor to the set */
#ifdef RT_USING_POSIX_STDIO
FD_ZERO(&rset);
FD_SET(ctx->s, &rset);
#endif
/* We need to analyse the message step by step. At the first step, we want
* to reach the function code because all packets contain this
* information. */
step = _STEP_SLAVE_ID;
length_to_read = ctx->backend->header_length + 1;
if (msg_type == MSG_INDICATION)
{
/* Wait for a message, we don't know when the message will be
* received */
p_tv = NULL;
}
else
{
tv.tv_sec = ctx->response_timeout.tv_sec;
tv.tv_usec = ctx->response_timeout.tv_usec;
p_tv = &tv;
}
length_to_read = ctx->backend->header_length + 1;
if (msg_type == MSG_INDICATION) {
/* Wait for a message, we don't know when the message will be
* received */
p_tv = NULL;
} else {
tv.tv_sec = ctx->response_timeout.tv_sec;
tv.tv_usec = ctx->response_timeout.tv_usec;
p_tv = &tv;
}
while (length_to_read != 0)
{
uint32_t msec = 0;
if (p_tv)
{
msec = (p_tv->tv_sec * 1000) + (p_tv->tv_usec / 1000);
}
else
{
msec = 500;
}
uint32_t i = 0;
uint32_t tick = rt_tick_get();
while (rt_tick_get() <= (tick + rt_tick_from_millisecond(msec)) && i < (length_to_read))
{
i += _rym_read_data(ctx, msg + msg_length,length_to_read);
//rt_thread_mdelay(5);
}
rt_kprintf("i=%d\\r\\n",i);
rc = i;
if (rc == 0)
{
errno = ECONNRESET;
rc = -1;
}
if (rc == -1) {
_error_print(ctx, "read");
if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
(errno == ECONNRESET || errno == ECONNREFUSED ||
errno == EBADF)) {
int saved_errno = errno;
modbus_close(ctx);
modbus_connect(ctx);
/* Could be removed by previous calls */
errno = saved_errno;
}
return -1;
}
/* Display the hex code of each character received */
if (ctx->debug) {
int i;
for (i=0; i < rc; i++)
printf("<%.2X>", msg[msg_length + i]);
}
/* Sums bytes received */
msg_length += rc;
/* Computes remaining bytes */
length_to_read -= rc;
if (length_to_read == 0)
{
switch (step)
{
case _STEP_SLAVE_ID:
{
if (CONFIG_SLAVE_ID != msg[0])
{
break;
}
else
{
step = _STEP_FUNCTION;
}
}
case _STEP_FUNCTION:
{
length_to_read = compute_meta_length_after_function(
msg[ctx->backend->header_length],
msg_type);
if (length_to_read != 0) {
step = _STEP_META;
} /* else switches straight to the next step */
}
break;
case _STEP_META:
length_to_read = compute_data_length_after_meta(
ctx, msg, msg_type);
if ((msg_length + length_to_read) > (int)ctx->backend->max_adu_length) {
errno = EMBBADDATA;
_error_print(ctx, "too many data");
return -1;
}
step = _STEP_DATA;
break;
default:
break;
}
}
if (length_to_read > 0 &&
(ctx->byte_timeout.tv_sec > 0 || ctx->byte_timeout.tv_usec > 0)) {
/* If there is no character in the buffer, the allowed timeout
interval between two consecutive bytes is defined by
byte_timeout */
tv.tv_sec = ctx->byte_timeout.tv_sec;
tv.tv_usec = ctx->byte_timeout.tv_usec;
p_tv = &tv;
}
/* else timeout isn't set again, the full response must be read before
expiration of response timeout (for CONFIRMATION only) */
}
if (ctx->debug)
rt_kprintf("\\n");
return ctx->backend->check_integrity(ctx, msg, msg_length);
4.3.2.2 超時等待接收
static rt_size_t _rym_read_data(modbus_t *ctx,rt_uint8_t *buf,rt_size_t len)
{
/* we should already have had the code */
rt_size_t readlen = 0;
do
{
readlen += rt_device_read(ctx->dev,
0, buf + readlen, len - readlen);
if (readlen >= len)
return readlen;
}
while (rt_sem_take(&ctx->rx_sem, 5) == RT_EOK);
return readlen;
}
4.4 應用部分
while (1)
{
if (0 == send_type)
{
rc = modbus_receive(ctx, mb_reply);
if (rc > 0)
{
modbus_reply(ctx, mb_reply, rc, mb_mapping);
uint8_t idx=0;
rt_kprintf("check [",mb_mapping->tab_registers[0x0b]);
for(idx=0; idx<0xFC; idx++)
{
rt_kprintf("[%04x] ",mb_mapping->tab_registers[idx]);
if (idx%16==0)
{
rt_kprintf("\\n");
}
}
rt_kprintf("]\\n");
}
}
else
{
memset(tab_reg, 0, 64 * 2);
int regs = modbus_read_registers(ctx, 0, 20, tab_reg);
rt_kprintf("[%4d][read num = %d]", num, regs);
num++;
int i;
for (i = 0; i < 20; i++)
{
rt_kprintf("<%#x>", tab_reg[i]);
}
rt_kprintf("\\n");
rt_thread_mdelay(2000);
}
}
modbus_close(ctx);
modbus_free(ctx);
}
static int rtu_test_init(void)
{
rt_pin_mode(RS485_RE, PIN_MODE_OUTPUT);
rt_thread_t tid;
tid = rt_thread_create("mb_test",
mb_slave_thread, RT_NULL,
2048,
12, 10);
if (tid != RT_NULL)
rt_thread_startup(tid);
return RT_EOK;
}
INIT_APP_EXPORT(rtu_test_init);
int cmd_modbus_send(int argc, char **argv)
{
uint16_t set_addr = strtoul(argv[1], 0, 16);
uint16_t set_data = strtoul(argv[2], 0, 16);
send_type = 1;
//int
int res = modbus_write_register(ctx, set_addr,set_data);
rt_kprintf("res = [%d]\\n",res);
return 0;
}
MSH_CMD_EXPORT_ALIAS(cmd_modbus_send, mod,mod [addr][data]);
5 測試
5.1 從機測試
5.1.1 上位機
使用modbus poll上位機進行測試,從機地址是0x30,讀取保持寄存器地址是0x100,個數是100
5.1.2 日志
Sending request using RTS signal
check [[0052]
[0054] [002d] [0054] [0068] [0072] [0065] [0061] [0064] [0000] [0000] [1234] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000]
[0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] [0000] ]
Waiting for a indication...
takes ms=[710]
takes ms=[0]
takes ms=[0]
<30><03><01><00><00><64><41><30><03><01><00><00><64><41>
[30][03][C8][00][52][00][54][00][2D][00][54][00][68][00][72][00][65][00][61][00][64][00][00][00][00][12][34][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][F2][BC]
Sending request using RTS signal
5.2 主機測試
5.2.1 上位機
使用Modbus Slave上位機,從機地址是0x30,讀取保持寄存器地址是0,個數是20
5.2.2 日志
rst rc=[20]
[ 629][read num = 20]<1><100><3><4><5><6><7><8><9><77><0><0><0><0><0><0><0><0><0>
[30][03][00][00][00][14][41][E4]
Sending request using RTS signal
Waiting for a confirmation...
takes ms=[237]
takes ms=[0]
takes ms=[1]
<00><61> <30><03><28><00><01><01><00><00><03><00><04><00><05><00><06><00><07><00><08><00><09><00><0A><00><7B><00><00><00><00><00><00><00><00><00><00><00><00><00><00><00><00><00><00><72>
rst rc=[20]
[ 630][read num = 20]<1><100><3><4><5><6><7><8><9><7b><0><0><0><0><0><0><0><0><0>
[30][03][00][00][00][14][41][E4]
Sending request using RTS signal
Waiting for a confirmation...
takes ms=[226]
takes ms=[0]
takes ms=[1]
<30><03><28><00><01><01><00><00><03><00><04><00><05><00><06><00><07><00><08><00><09><00><0A><00><80><00><00><00><00><00><00><00>
rst rc=[20]
[ 631][read num = 20]<1><100><3><4><5><6><7><8><9><80><0><0><0><0><0><0><0><0><0>
[30][03][00][00][00][14][41][E4]
Sending request using RTS signal
Waiting for a confirmation...
takes ms=[227]
takes ms=[0]
<00><00><00><00><00><00><00><00><00><00><00> <30><03><28>takes ms=[0]
<00><01><01><00><00><03><00><04><00><05><00><06><00><07><00><08><00><09><00><0A><00><84><00><00><00><00><00><00><00><00><00><00>
rst rc=[20]
[ 632][read num = 20]<1><100><3><4><5><6><7><8><9><84><0><0><0><0><0><0><0><0><0>
[30][03][00][00][00][14][41][E4]
Sending request using RTS signal
Waiting for a confirmation...
takes ms=[236]
takes ms=[0]
takes ms=[0]
<00><00><00><00><00><00><00><00><56> <30><03><28><00><01><01><00><00><03><00><04><00><05><00><06><00><07><00><08><00><09><00><0A><00><89><00><00><00><00><00><00><00><00><00><00><00><00><00>
rst rc=[20]
[ 633][read num = 20]<1><100><3><4><5><6><7><8><9><89><0><0><0><0><0><0><0><0><0>
6 總結
6.1modbus的應用場景非常廣泛,無論在哪個領域都會有它的身影,所以掌握它是很有必要的,還是需要多做點項目多應用,多多刻意地練習。
6.2 主機模式下接收不穩定,有時校驗不通過
看了下代碼,應該是解碼方式的問題,后續再優化一下解碼步驟
-
通信協議
+關注
關注
28文章
858瀏覽量
40269 -
RS485
+關注
關注
39文章
1143瀏覽量
82148 -
MODBUS
+關注
關注
28文章
1765瀏覽量
76846 -
RS232
+關注
關注
13文章
739瀏覽量
94247 -
RTThread
+關注
關注
8文章
132瀏覽量
40811
發布評論請先 登錄
相關推薦
評論