【国民技术低功耗系列N32L43x测评】06.基于USART实现Agile Modbus主从通讯
<p><strong><span style="color:#e74c3c;">概述</span></strong></p><p>Agile Modbus是轻量型Modbus协议栈,遵循Apache-2.0许可,满足用户任何场景下的使用需求,具有如下特性:</p>
<ul>
<li style="">支持RTU和TCP协议,使用纯C开发,不涉及任何硬件接口,可在任何形式的硬件上直接使用</li>
<li style="">由于其使用纯C开发、不涉及硬件,完全可以在串口上跑TCP协议,在网络上跑RTU协议</li>
<li style="">支持符合Modbus格式的自定义协议</li>
<li style="">同时支持多主机和多从机</li>
<li style="">使用简单,只需要将RTU或TCP句柄初始化好后,调用相应API进行组包和解包即可</li>
</ul>
<p style=""> </p>
<p style=""><strong><span style="color:#e74c3c;">资源占用情况</span></strong></p>
<table class="MsoTableGrid" style="border-collapse:collapse; border:none;Times New Roman"">
<tbody>
<tr>
<td style="border-bottom:1px solid black; border-top:1px solid black; border-right:1px solid black; border-left:1px solid black" valign="top">
<p align="center" style="">开发环境</p>
</td>
<td style="border-bottom:1px solid black; border-top:1px solid black; border-right:1px solid black; border-left:1px solid black" valign="top">
<p align="center" style="">FLASH</p>
</td>
<td style="border-bottom:1px solid black; border-top:1px solid black; border-right:1px solid black; border-left:1px solid black" valign="top">
<p align="center" style="">RAM</p>
</td>
</tr>
<tr>
<td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
<p align="center" style="">KEIL MDK</p>
</td>
<td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
<p align="center" style="">7192</p>
</td>
<td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
<p align="center" style="">0</p>
</td>
</tr>
<tr>
<td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
<p align="center" style="">GCC</p>
</td>
<td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
<p align="center" style="">13970</p>
</td>
<td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
<p align="center" style="">24</p>
</td>
</tr>
</tbody>
</table>
<p style=""> </p>
<p style=""><strong><span style="color:#e74c3c;">硬件环境准备</span></strong></p>
<p style=""></p>
<p style=""> </p>
<p style=""><strong><span style="color:#e74c3c;">在N32L43X系列MCU上使用Agile Modbus</span></strong></p>
<p style="">我们使用MCU的USART2引脚(PA2/PA3)与RS485模块进行连接,然后将RS485模块的AB与USB转RS485工具进行连接,在电脑端使用串口调试助手进行辅助测试。</p>
<p style="">对于USART2除了时行常规配置外(115200/N/8/1),另外使能串口接收中断,将收到的数据存放在队列中,使能接收空闲中断,在接收完成后置接收完成标志位,供agile modbus使用,具体实现代码如下所示:</p>
<pre>
<code class="language-cpp">/* Private variables ---------------------------------------------------------*/
uint8_t RS485_IDLEF = 0;
/*******************************************************************************
* @brief * @param
* @retval
* @attention *******************************************************************************/
void RS485_Init(void)
{
GPIO_InitType GPIO_InitStructure;
NVIC_InitType NVIC_InitStructure;
USART_InitTypeUSART_InitStructure;
QUEUE_INIT(QUEUE_RS485_RX_IDX);
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE);
RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPH_USART2, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF4_USART1;
GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_3;
GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF4_USART1;
GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);
USART_StructInit(&USART_InitStructure);
USART_InitStructure.BaudRate = 115200;
USART_InitStructure.WordLength = USART_WL_8B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
USART_Init(USART2, &USART_InitStructure);
USART_ClrFlag(USART2, USART_FLAG_RXDNE);
USART_ConfigInt(USART2, USART_INT_RXDNE, ENABLE);
USART_ClrFlag(USART2, USART_FLAG_IDLEF);
USART_ConfigInt(USART2, USART_INT_IDLEF, ENABLE);
USART_Enable(USART2, ENABLE);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void USART2_IRQHandler(void)
{
if(USART_GetIntStatus(USART2, USART_INT_IDLEF) != RESET)
{
uint8_t RxData = USART_ReceiveData(USART2);
RS485_IDLEF = 1;
}
if(USART_GetIntStatus(USART2, USART_INT_RXDNE) != RESET)
{
QUEUE_WRITE(QUEUE_RS485_RX_IDX, USART_ReceiveData(USART2));
USART_ClrIntPendingBit(USART2, USART_INT_RXDNE);
}
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void RS485_SendData(uint8_t Data)
{
USART_SendData(USART2, Data);
while (USART_GetFlagStatus(USART2, USART_FLAG_TXDE) == RESET);
}</code></pre>
<p style=""> </p>
<p style=""> </p>
<p style=""><strong><span style="color:#e74c3c;">主机实现步骤</span></strong></p>
<ul>
<li style="">使用agile_modbus_rtu_init函数初始化RTU环境</li>
<li style="">使用agile_modbus_set_slave设置从机地址</li>
<li style="">清空接收缓存</li>
<li style="">使用agile_modbus_serialize_xxx打包请求数据</li>
<li style="">发送数据</li>
<li style="">等待数据接收完成</li>
<li style="">使用agile_modbus_deserialize_xxx解析响应的数据</li>
<li style="">用户处理得到的数据</li>
</ul>
<p style=""> </p>
<p style=""><span style="color:#e74c3c;"><strong>主机实现代码</strong></span></p>
<pre>
<code class="language-cpp">/* Private variables ---------------------------------------------------------*/
agile_modbus_rtu_t rtu_master;
/* Private variables ---------------------------------------------------------*/
uint8_t rtu_master_send_buf;
uint8_t rtu_master_recv_buf;
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/* Exported variables --------------------------------------------------------*/
extern uint8_t RS485_IDLEF;
/* Exported function prototypes ----------------------------------------------*/
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void rtu_master_init(void)
{
agile_modbus_rtu_init(&rtu_master, rtu_master_send_buf, sizeof(rtu_master_send_buf),
rtu_master_recv_buf, sizeof(rtu_master_recv_buf));
agile_modbus_set_slave(&rtu_master._ctx, 1);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void rtu_master_handler(void)
{
uint16_t hold_register;
static int state = 0;
switch(state)
{
case 0:
{
int send_len = 0;
send_len = agile_modbus_serialize_read_registers(&rtu_master._ctx, 0, 10);
if(send_len)
{
for(int i = 0; i < send_len; i++)
{
RS485_SendData(rtu_master._ctx.send_buf);
}
state = 1;
}
}
break;
case 1:
{
if(RS485_IDLEF == 1)
{
RS485_IDLEF = 0;
int recv_len = 0;
while(QUEUE_EMPTY(QUEUE_RS485_RX_IDX) == 0)
{
rtu_master._ctx.read_buf = QUEUE_READ(QUEUE_RS485_RX_IDX);
}
#if 0
printf("\r\nrecv_len[%d] : ", recv_len);
for(int i = 0; i < recv_len; i++)
{
printf("0x%02x ", rtu_master._ctx.read_buf);
}
printf("\r\n");
#endif
if(recv_len)
{
int rc = agile_modbus_deserialize_read_registers(&rtu_master._ctx, recv_len, hold_register);
if(rc >= 0)
{
printf("\r\nHold Register : \r\n");
for(int i = 0; i < 10; i++)
{
printf("0x%02x ", hold_register);
}
printf("\r\n");
}
}
state = 0;
}
}
break;
default:
break;
}
}</code></pre>
<p style=""> </p>
<p style=""> </p>
<p style=""><strong><span style="color:#e74c3c;">主机测试结果</span></strong></p>
<p style=""></p>
<p style=""> </p>
<p style=""><strong><span style="color:#e74c3c;">从机实现步骤</span></strong></p>
<ul>
<li style="">实现agile_modbus_slave_callback_t类型回调函数</li>
<li style="">使用agile_modbus_rtu_init初始化RTU环境</li>
<li style="">使用agile_modbus_set_slave设置从机地址</li>
<li style="">等待数据接收完成</li>
<li style="">使用agile_modbus_slave_handler处理请求数据</li>
<li style="">清空接收缓存(可选)</li>
<li style="">发送数据</li>
</ul>
<p style=""> </p>
<p style=""> </p>
<p style=""><strong><span style="color:#e74c3c;">从机实现代码</span></strong></p>
<pre>
<code class="language-cpp">/* Private define ------------------------------------------------------------*/
#define TAB_MAX_NUM 10
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
agile_modbus_rtu_t rtu_slave;
/* Private variables ---------------------------------------------------------*/
uint8_t rtu_slave_send_buf;
uint8_t rtu_slave_recv_buf;
/* Private variables ---------------------------------------------------------*/
static uint8_t_tab_bits = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
static uint8_t_tab_input_bits = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
static uint16_t _tab_registers = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
static uint16_t _tab_input_registers = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/* Exported variables --------------------------------------------------------*/
extern uint8_t RS485_IDLEF;
/* Exported function prototypes ----------------------------------------------*/
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
int rtu_slave_callback(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info)
{
int function = slave_info->sft->function;
int ret = 0;
switch(function)
{
case AGILE_MODBUS_FC_READ_COILS:
case AGILE_MODBUS_FC_READ_DISCRETE_INPUTS:
{
int address = slave_info->address;
int nb = slave_info->nb;
int send_index = slave_info->send_index;
int is_input = (function == AGILE_MODBUS_FC_READ_DISCRETE_INPUTS);
for(int now_address = address, i = 0; now_address < address + nb; now_address++, i++)
{
if(now_address >= 0 && now_address < TAB_MAX_NUM)
{
int index = now_address - 0;
agile_modbus_slave_io_set(ctx->send_buf + send_index, i, is_input ? _tab_input_bits : _tab_bits);
}
}
}
break;
case AGILE_MODBUS_FC_READ_HOLDING_REGISTERS:
case AGILE_MODBUS_FC_READ_INPUT_REGISTERS:
{
int address = slave_info->address;
int nb = slave_info->nb;
int send_index = slave_info->send_index;
int is_input = (function == AGILE_MODBUS_FC_READ_INPUT_REGISTERS);
for(int now_address = address, i = 0; now_address < address + nb; now_address++, i++)
{
if(now_address >= 0 && now_address < TAB_MAX_NUM)
{
int index = now_address - 0;
agile_modbus_slave_register_set(ctx->send_buf + send_index, i, is_input ? _tab_input_registers : _tab_registers);
}
}
}
break;
case AGILE_MODBUS_FC_WRITE_SINGLE_COIL:
case AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS:
{
int address = slave_info->address;
if(function == AGILE_MODBUS_FC_WRITE_SINGLE_COIL)
{
if (address >= 0 && address < TAB_MAX_NUM)
{
int index = address - 0;
int data= *((int *)slave_info->buf);
_tab_bits = data;
}
}
else
{
int nb = slave_info->nb;
for(int now_address = address, i = 0; now_address < address + nb; now_address++, i++)
{
if(now_address >= 0 && now_address < TAB_MAX_NUM)
{
int index = now_address - 0;
uint8_t status = agile_modbus_slave_io_get(slave_info->buf, i);
_tab_bits = status;
}
}
}
}
break;
case AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER:
case AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
{
int address = slave_info->address;
if(function == AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER)
{
if (address >= 0 && address < TAB_MAX_NUM)
{
int index = address - 0;
int data = *((int *)slave_info->buf);
_tab_registers = data;
}
}
else
{
int nb = slave_info->nb;
for(int now_address = address, i = 0; now_address < address + nb; now_address++, i++)
{
if(now_address >= 0 && now_address < TAB_MAX_NUM)
{
int index = now_address - 0;
uint16_t data = agile_modbus_slave_register_get(slave_info->buf, i);
_tab_registers = data;
}
}
}
}
break;
case AGILE_MODBUS_FC_MASK_WRITE_REGISTER:
{
int address = slave_info->address;
if(address >= 0 && address < TAB_MAX_NUM)
{
int index = address - 0;
uint16_t data = _tab_registers;
uint16_t and_op = (slave_info->buf << 8) + slave_info->buf;
uint16_t or_op= (slave_info->buf << 8) + slave_info->buf;
data = (data & and_op) | (or_op &(~and_op));
_tab_registers = data;
}
}
break;
case AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS:
{
int address = slave_info->address;
int nb = (slave_info->buf << 8) + slave_info->buf;
uint16_t address_write = (slave_info->buf << 8) + slave_info->buf;
int nb_write = (slave_info->buf << 8) + slave_info->buf;
int send_index = slave_info->send_index;
/* Write first. 7 is the offset of the first values to write */
for(int now_address = address_write, i = 0; now_address < address_write + nb_write; now_address++, i++)
{
if(now_address >= 0 && now_address < TAB_MAX_NUM)
{
int index = now_address - 0;
uint16_t data = agile_modbus_slave_register_get(slave_info->buf + 7, i);
_tab_registers = data;
}
}
/* and read the data for the response */
for(int now_address = address, i = 0; now_address < address + nb; now_address++, i++)
{
if(now_address >= 0 && now_address < TAB_MAX_NUM)
{
int index = now_address - 0;
agile_modbus_slave_register_set(ctx->send_buf + send_index, i, _tab_registers);
}
}
}
break;
default:
{
ret = -AGILE_MODBUS_EXCEPTION_ILLEGAL_FUNCTION;
}
break;
}
return ret;
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void rtu_slave_init(void)
{
agile_modbus_rtu_init(&rtu_slave, rtu_slave_send_buf, sizeof(rtu_slave_send_buf),
rtu_slave_recv_buf, sizeof(rtu_slave_recv_buf));
agile_modbus_set_slave(&rtu_slave._ctx, 1);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void rtu_slave_handler(void)
{
if(RS485_IDLEF == 1)
{
RS485_IDLEF = 0;
int recv_len = 0;
while(QUEUE_EMPTY(QUEUE_RS485_RX_IDX) == 0)
{
rtu_slave._ctx.read_buf = QUEUE_READ(QUEUE_RS485_RX_IDX);
}
#if 1
printf("\r\nrecv_len[%d] : ", recv_len);
for(int i = 0; i < recv_len; i++)
{
printf("0x%02x ", rtu_slave._ctx.read_buf);
}
printf("\r\n");
#endif
if(recv_len)
{
int send_len = agile_modbus_slave_handle(&rtu_slave._ctx, recv_len, 1, rtu_slave_callback, NULL);
if(send_len)
{
for(int i = 0; i < send_len; i++)
{
RS485_SendData(rtu_slave._ctx.send_buf);
}
}
}
}
}</code></pre>
<p style=""> </p>
<p style=""> </p>
<p style=""><strong><span style="color:#e74c3c;">从机测试结果</span></strong></p>
<p style=""></p>
<p style=""> </p>
<p style=""><strong><span style="color:#e74c3c;">附件</span></strong></p>
<p style="">软件工程源代码:</p>
这工程很有实用性,先学习了,谢谢分享! lugl4313820 发表于 2022-7-19 07:55
这工程很有实用性,先学习了,谢谢分享!
<p><img height="50" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan33.gif" width="58" /></p>
lugl4313820 发表于 2022-7-19 07:55
这工程很有实用性,先学习了,谢谢分享!
<p>666666666666666666666</p>
<p>为啥keil和gcc差的那么多。</p>
<p>是开源的吗?要版权不</p>
<p> </p>
dwdsp 发表于 2022-7-21 08:46
是开源的吗?要版权不
<p>有链接(https://gitee.com/RT-Thread-Mirror/agile_modbus),自己研究哈</p>
<p>收藏一下</p>
页:
[1]