【AT32】 AT32 移植 Freemodbus 主站
基于野火开发板 at32f437zgt6芯片 和at32 官方开发工具
移植了网上一套开源的freemodbus 主站
这里对modbus 协议不做过多的讲解 主要已实现代码为主
AT32 Work Bench
参考之前我之前的配置 与stm32cubemx软件差不多
注意485芯片的收发脚配置即可
AT32 IDE
说实话这软件太垃圾了 elf文件不能生成就 报错不能生成 具体原因都不能跳转过去 有的地方函数不存在 或者写错根本不提示 比stm32 cubeide差了不是一点半点
freemodbus 文件架构讲解 需要的文件如下
用户主要就是要修改port层 函数 任何一个中间件都是如此
先修改porttimer层 开启一个定时器将时钟设置为 20Khz
主要修改以下函数 我这里使用的tim6 这是第一个最简单的普通定时器
/** FreeModbus Libary: RT-Thread Port* Copyright (C) 2013 Armink <armink.ztl@gmail.com>** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the Free Software* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA** File: $Id: porttimer_m.c,v 1.60 2013/08/13 15:07:05 Armink add Master Functions$*//* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "wk_tmr.h"
#include <stdio.h>
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mb_m.h"
#include "mbport.h"
#include "mbconfig.h"#define Modbus_TIM TMR6#if MB_MASTER_RTU_ENABLED > 0 || MB_MASTER_ASCII_ENABLED > 0
/* ----------------------- Variables ----------------------------------------*/
static USHORT usT35TimeOut50us;static void prvvTIMERExpiredISR(void);
static void timer_timeout_ind(void* parameter);/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR(void);/* ----------------------- Start implementation -----------------------------*/
BOOL xMBMasterPortTimersInit(USHORT usTimeOut50us)
{usT35TimeOut50us = usTimeOut50us;tmr_cnt_dir_set(Modbus_TIM, TMR_COUNT_UP);tmr_period_buffer_enable(Modbus_TIM, FALSE);tmr_base_init(Modbus_TIM, usTimeOut50us, 14399); //初始化时钟频率为20Khz 288Mhz时钟tmr_counter_enable(Modbus_TIM, TRUE);tmr_primary_mode_select(Modbus_TIM, TMR_PRIMARY_SEL_RESET);tmr_interrupt_enable(Modbus_TIM, TMR_OVF_INT, TRUE);return TRUE;
}void vMBMasterPortTimersT35Enable()
{/* Set current timer mode, don't change it.*/vMBMasterSetCurTimerMode(MB_TMODE_T35);/* 设置T3.5超时时间 */tmr_period_value_set(Modbus_TIM, usT35TimeOut50us - 1);/* 清除计数器和标志位 */tmr_counter_value_set(Modbus_TIM, 0);tmr_flag_clear(Modbus_TIM, TMR_OVF_FLAG);/* 使能定时器 */tmr_counter_enable(Modbus_TIM, TRUE);
}void vMBMasterPortTimersConvertDelayEnable()
{/* Set current timer mode, don't change it.*/vMBMasterSetCurTimerMode(MB_TMODE_CONVERT_DELAY);/* 设置转换延时时间 */tmr_period_value_set(Modbus_TIM, MB_MASTER_DELAY_MS_CONVERT * 20 - 1);/* 清除计数器和标志位 */tmr_counter_value_set(Modbus_TIM, 0);tmr_flag_clear(Modbus_TIM, TMR_OVF_FLAG);/* 使能定时器 */tmr_counter_enable(Modbus_TIM, TRUE);
}void vMBMasterPortTimersRespondTimeoutEnable()
{/* Set current timer mode, don't change it.*/vMBMasterSetCurTimerMode(MB_TMODE_RESPOND_TIMEOUT);/* 设置响应超时时间 */tmr_period_value_set(Modbus_TIM, MB_MASTER_TIMEOUT_MS_RESPOND * 20 - 1);/* 清除计数器和标志位 */tmr_counter_value_set(Modbus_TIM, 0);tmr_flag_clear(Modbus_TIM, TMR_OVF_FLAG);/* 使能定时器 */tmr_counter_enable(Modbus_TIM, TRUE);
}void vMBMasterPortTimersDisable()
{/* 禁用定时器 */tmr_counter_enable(Modbus_TIM, FALSE);
}void prvvTIMERExpiredISR(void)
{(void) pxMBMasterPortCBTimerExpired();
}static void timer_timeout_ind(void* parameter)
{prvvTIMERExpiredISR();
}
/*** @brief This function handles TIM4 global interrupt.*/
void Modbus_TIM_GLOBAL_IRQHandler(void)
{if(tmr_flag_get(Modbus_TIM, TMR_OVF_FLAG) != RESET){/* 清除中断标志位 */tmr_flag_clear(Modbus_TIM, TMR_OVF_FLAG);//printf("TIM_OVER\r\n");gpio_bits_set(GPIOA, GPIO_PINS_11);/* 调用Modbus定时器超时处理函数 */prvvTIMERExpiredISR();}
}#endif
注意Modbus_TIM_GLOBAL_IRQHandler需要放在 tim6的中断服务函数中调用
然后修改 portserial_m.c文件 这个是串口端口的修改
/** FreeModbus Libary: RT-Thread Port* Copyright (C) 2013 Armink <armink.ztl@gmail.com>** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the Free Software* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA** File: $Id: portserial_m.c,v 1.60 2013/08/13 15:07:05 Armink add Master Functions $*/#include "port.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "mbconfig.h"
#include "wk_usart.h"
#include "at32f435_437.h"
#include "at32f435_437_usart.h"
#include "wk_system.h"
#if MB_MASTER_RTU_ENABLED > 0 || MB_MASTER_ASCII_ENABLED > 0
/* ----------------------- Static variables ---------------------------------*/
/* 定义 485收发控制脚 */
#define MODBUS_MASTER_USE_CONTROL_PIN TRUE
#define MODBUS_MASTER_GPIO_PORT GPIOB
#define MODBUS_MASTER_GPIO_PIN GPIO_PINS_3
/* ----------------------- Defines ------------------------------------------*/
/* serial transmit event */
#define EVENT_SERIAL_TRANS_START (1<<0)/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR(void);
static void prvvUARTRxISR(void);//static void serial_soft_trans_irq(void* parameter);/* ----------------------- Start implementation -----------------------------*/
BOOL xMBMasterPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits,eMBParity eParity)
{
#if 1/* 配置串口参数 */usart_type* rs485_port;if(ucPORT ==1)rs485_port = USART1;if(ucPORT ==2)rs485_port = USART2;if(ucPORT ==3)rs485_port = USART3;if(ucPORT ==4)rs485_port = UART4;/* 配置校验位 */switch(eParity){case MB_PAR_NONE:usart_parity_selection_config(rs485_port, USART_PARITY_NONE);usart_init(rs485_port, ulBaudRate, USART_DATA_8BITS, USART_STOP_1_BIT);break;case MB_PAR_ODD:usart_parity_selection_config(rs485_port, USART_PARITY_ODD);usart_init(rs485_port, ulBaudRate, USART_DATA_9BITS, USART_STOP_1_BIT);break;case MB_PAR_EVEN:usart_parity_selection_config(rs485_port, USART_PARITY_EVEN);usart_init(rs485_port, ulBaudRate, USART_DATA_9BITS, USART_STOP_1_BIT);break;default:return FALSE;}usart_transmitter_enable(rs485_port, TRUE);usart_receiver_enable(rs485_port, TRUE);/* 使能串口 */usart_enable(rs485_port, TRUE);
#endif return TRUE;
}void vMBMasterPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable* transmitter empty interrupts.*/if(xRxEnable){#if defined(MODBUS_MASTER_USE_CONTROL_PIN) wk_delay_ms(1);gpio_bits_reset(MODBUS_MASTER_GPIO_PORT, MODBUS_MASTER_GPIO_PIN);wk_delay_ms(2);#endifusart_interrupt_enable(USART2,USART_RDBF_INT,TRUE); //开启接收中断}else{#if defined(MODBUS_MASTER_USE_CONTROL_PIN)wk_delay_ms(1);gpio_bits_set(MODBUS_MASTER_GPIO_PORT, MODBUS_MASTER_GPIO_PIN);wk_delay_ms(2);#endifusart_interrupt_enable(USART2,USART_RDBF_INT,FALSE); //关闭接收中断}if(xTxEnable){#if defined(MODBUS_MASTER_USE_CONTROL_PIN)wk_delay_ms(1);gpio_bits_set(MODBUS_MASTER_GPIO_PORT, MODBUS_MASTER_GPIO_PIN);wk_delay_ms(2);#endifusart_interrupt_enable(USART2,USART_TDBE_INT,TRUE);}else{#if defined(MODBUS_MASTER_USE_CONTROL_PIN) wk_delay_ms(1);gpio_bits_reset(MODBUS_MASTER_GPIO_PORT, MODBUS_MASTER_GPIO_PIN);wk_delay_ms(2);#endifusart_interrupt_enable(USART2,USART_TDBE_INT,FALSE);}
}void vMBMasterPortClose(void)
{/* 关闭串口 */usart_enable(USART2, FALSE);return;
}BOOL xMBMasterPortSerialPutByte(CHAR ucByte)
{/* 发送一个字节 */usart_data_transmit(USART2, (uint16_t) ucByte);//uart_data_tx(USART1,ucByte);return TRUE;
}BOOL xMBMasterPortSerialGetByte(CHAR * pucByte)
{/* 接收一个字节 */*pucByte = usart_data_receive(USART2);return TRUE;
}/* * Create an interrupt handler for the transmit buffer empty interrupt* (or an equivalent) for your target processor. This function should then* call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that* a new character can be sent. The protocol stack will then call * xMBPortSerialPutByte( ) to send the character.*/
void prvvUARTTxReadyISR(void)
{/* 更新状态机 */pxMBMasterFrameCBTransmitterEmpty();
}/* * Create an interrupt handler for the receive interrupt for your target* processor. This function should then call pxMBFrameCBByteReceived( ). The* protocol stack will then call xMBPortSerialGetByte( ) to retrieve the* character.*/
void prvvUARTRxISR(void)
{/* 更新状态机 */pxMBMasterFrameCBByteReceived();
}/*** @brief This function handles USART4 global interrupt.*/
void Modbus_IRQHandler(void)
{//串口接收中断if(usart_interrupt_flag_get(USART2, USART_RDBF_FLAG) != RESET){prvvUARTRxISR(); //串口接收中断调用函数usart_flag_clear(USART2, USART_RDBF_FLAG);}//发送中断if(usart_interrupt_flag_get(USART2, USART_TDBE_FLAG) != RESET){prvvUARTTxReadyISR(); //串口发送中断调用函数usart_flag_clear(USART2, USART_TDBE_FLAG);}
}
#endif
同样的中断函数需要放在串口的中断调用
然后创建应用层调用初始化 和启动 函数
for(;;){poll_status = eMBMasterPoll();//更新事件wk_delay_ms(5);printf_count = printf_count+5;if(poll_status != MB_ENOERR ){printf("Poll_ERROR Type %d ",poll_status);}//应用层事件switch(APP_RUN_STATE){case 0x03 : //读保持寄存器{eMBMasterReqErrCode status;status = eMBMasterReqReadHoldingRegister(1,0,10 ,10);if(status==MB_MRE_NO_ERR){
// __NOP;__asm__ volatile ("nop"); //调试点}APP_RUN_STATE =0;break;}case 0x10 : //写多个保持寄存器{eMBMasterReqWriteMultipleHoldingRegister( 1,0,10,rs485_write_data,10);APP_RUN_STATE =0;break;}default :break;}if(printf_count >= 1000) //每隔一s打印信息{for(int i = 0;i<10;i++){printf("%d",usMRegHoldBuf[0][i]);}printf("\n");APP_RUN_STATE = 0x03; //重新读取保持寄存器信息printf_count=0;}}
在主函数循环调用eMBMasterPoll函数
在这里要注意 调用一个请求函数后 必须释放线程给eMBMasterPoll函数
带调用否则会一直通讯失败 这是很多人失败的原因
实验效果
使用modbus slave 软件模拟一个从站 然后将收到的数据串口打印
可以看到通讯成功 而且可以修改寄存器的值 串口打印的也会改变
参考文件
代码仓库