【国民技术低功耗系列N32L43x测评】06.基于USART实现Agile Modbus主从通讯
[复制链接]
概述
Agile Modbus是轻量型Modbus协议栈,遵循Apache-2.0许可,满足用户任何场景下的使用需求,具有如下特性:
- 支持RTU和TCP协议,使用纯C开发,不涉及任何硬件接口,可在任何形式的硬件上直接使用
- 由于其使用纯C开发、不涉及硬件,完全可以在串口上跑TCP协议,在网络上跑RTU协议
- 支持符合Modbus格式的自定义协议
- 同时支持多主机和多从机
- 使用简单,只需要将RTU或TCP句柄初始化好后,调用相应API进行组包和解包即可
资源占用情况
开发环境
|
FLASH
|
RAM
|
KEIL MDK
|
7192
|
0
|
GCC
|
13970
|
24
|
硬件环境准备
在N32L43X系列MCU上使用Agile Modbus
我们使用MCU的USART2引脚(PA2/PA3)与RS485模块进行连接,然后将RS485模块的AB与USB转RS485工具进行连接,在电脑端使用串口调试助手进行辅助测试。
对于USART2除了时行常规配置外(115200/N/8/1),另外使能串口接收中断,将收到的数据存放在队列中,使能接收空闲中断,在接收完成后置接收完成标志位,供agile modbus使用,具体实现代码如下所示:
/* Private variables ---------------------------------------------------------*/
uint8_t RS485_IDLEF = 0;
/*******************************************************************************
* [url=home.php?mod=space&uid=159083]@brief[/url] * @param
* @retval
* [url=home.php?mod=space&uid=1020061]@attention[/url] *******************************************************************************/
void RS485_Init(void)
{
GPIO_InitType GPIO_InitStructure;
NVIC_InitType NVIC_InitStructure;
USART_InitType USART_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);
}
主机实现步骤
- 使用agile_modbus_rtu_init函数初始化RTU环境
- 使用agile_modbus_set_slave设置从机地址
- 清空接收缓存
- 使用agile_modbus_serialize_xxx打包请求数据
- 发送数据
- 等待数据接收完成
- 使用agile_modbus_deserialize_xxx解析响应的数据
- 用户处理得到的数据
主机实现代码
/* Private variables ---------------------------------------------------------*/
agile_modbus_rtu_t rtu_master;
/* Private variables ---------------------------------------------------------*/
uint8_t rtu_master_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
uint8_t rtu_master_recv_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
/* 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[10];
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[i]);
}
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[recv_len++] = 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[i]);
}
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[i]);
}
printf("\r\n");
}
}
state = 0;
}
}
break;
default:
break;
}
}
主机测试结果
从机实现步骤
- 实现agile_modbus_slave_callback_t类型回调函数
- 使用agile_modbus_rtu_init初始化RTU环境
- 使用agile_modbus_set_slave设置从机地址
- 等待数据接收完成
- 使用agile_modbus_slave_handler处理请求数据
- 清空接收缓存(可选)
- 发送数据
从机实现代码
/* 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[AGILE_MODBUS_MAX_ADU_LENGTH];
uint8_t rtu_slave_recv_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
/* Private variables ---------------------------------------------------------*/
static uint8_t _tab_bits [TAB_MAX_NUM] = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
static uint8_t _tab_input_bits [TAB_MAX_NUM] = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
static uint16_t _tab_registers [TAB_MAX_NUM] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
static uint16_t _tab_input_registers[TAB_MAX_NUM] = {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[index] : _tab_bits[index]);
}
}
}
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[index] : _tab_registers[index]);
}
}
}
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[index] = 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[index] = 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[index] = 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[index] = 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[index];
uint16_t and_op = (slave_info->buf[0] << 8) + slave_info->buf[1];
uint16_t or_op = (slave_info->buf[2] << 8) + slave_info->buf[3];
data = (data & and_op) | (or_op &(~and_op));
_tab_registers[index] = data;
}
}
break;
case AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS:
{
int address = slave_info->address;
int nb = (slave_info->buf[0] << 8) + slave_info->buf[1];
uint16_t address_write = (slave_info->buf[2] << 8) + slave_info->buf[3];
int nb_write = (slave_info->buf[4] << 8) + slave_info->buf[5];
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[index] = 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[index]);
}
}
}
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[recv_len++] = 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[i]);
}
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[i]);
}
}
}
}
}
从机测试结果
附件
软件工程源代码:
Template.zip
(44.58 MB, 下载次数: 434)
|