本帖最后由 袁胜富 于 2023-10-9 20:27 编辑
一、概述
CH32X035的USART接口拥有4个,高级定时器2个,通用定时器1个。在以往的工作中使用了Modbus(RTU)的仪器仪表以及一些工控板,在CH32系列芯片使用体验还是不错的。
于是乎为了和大家分享,我出了这个文章。
二、原理分析和硬件连接
原理分析
Modbus_RTU大量运用在工业控制领域,硬件协议可以选用RS-485、RS-232,TTL-UART。本文选用的是TTL-UART,本评测使用USART1作为数据传输接口,TIM3作为定时器时钟为3.5个字符传输时间时长。
硬件连接
PB10------->UART_TX
PB11--------->UART_RX
LED1-------->PA0
LED2-------->PA1
从原理图来看,低电平点亮LED。
使用宏定义定义接口以及接口函数
/*------------------------------------USART相关宏定义-------------------------------*/
#define MODBUS_USART_PORT USART1
#define MODBUS_USART_IRQn USART1_IRQn
#define MODBUS_USART_IRQHandler USART1_IRQHandler #define MODBUS_USART_PERIPH RCC_APB2Periph_USART1
#define MODBUS_USART_CLOCK_API RCC_APB2PeriphClockCmd #define MODBUS_USART_TX_PORT
GPIOB
#define MODBUS_USART_TX_PIN GPIO_Pin_10
#define MODBUS_USART_TX_PERIPH RCC_APB2Periph_GPIOB
#define MODBUS_USART_RX_PORT GPIOB
#define MODBUS_USART_RX_PIN GPIO_Pin_11
#define MODBUS_USART_RX_PERIPH RCC_APB2Periph_GPIOB
/*------------------------------------TIMER相关宏定义-------------------------------*/
#define MODBUS_TIME_PORT TIM3
#define MODBUS_TIME_IRQn TIM3_IRQn
#define MODBUS_TIME_IRQHandler TIM3_IRQHandler
#define MODBUS_TIME_PERIPH RCC_APB1Periph_TIM3 #define MODBUS_TIME_CLOCK_API RCC_APB1PeriphClockCmd
三、代码
串口接口代码:
#include “port.h”
#include “debug.h”
/* ----------------------- Modbus 包括 ----------------------------------*/
#include “mb.h”
#include “mbport.h”
/* ----------------------- 静态函数 ---------------------------------*/
static void prvvUARTTxReadyISR( void );
static void prvvUARTRxISR( void );
/* ----------------------- Start implementation-----------------------------*/void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/
* If xRXEnable enable enable serial receiveinterrupts.if xTxENable 启用
* 发射器空中断。
*/ /
* 如果 xRXEnable 启用串行接收中断。如果 xTxENable 启用
* 发射器空中断。
*/ if (xRxEnable)
{
//
GPIO_SetBits(GPIOA, GPIO_Pin_8);
USART_ITConfig(MODBUS_USART_PORT、USART_IT_RXNE、启用);使能接收中断
}
else
{
USART_ITConfig(MODBUS_USART_PORT, USART_IT_RXNE, DISABLE); //失能接收中断;
}
if (xTxEnable)
{
// GPIO_ResetBits(GPIOA, GPIO_Pin_8);
USART_ITConfig(MODBUS_USART_PORT、USART_IT_TC、启用);使能发送中断 } else { USART_ITConfig(MODBUS_USART_PORT, USART_IT_TC, DISABLE); //失能发送中断 }}void vMBPortClose( void ){
USART_Cmd(MODBUS_USART_PORT,DISABLE); //关闭usart
}
BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(MODBUS_USART_TX_PERIPH,启用);打开GPIO的时钟
RCC_APB2PeriphClockCmd(MODBUS_USART_RX_PERIPH,ENABLE);打开GPIO的时钟
MODBUS_USART_CLOCK_API(MODBUS_USART_PERIPH,启用);打开usart时钟
GPIO_InitStructure.GPIO_Pin= MODBUS_USART_TX_PIN;USART的TX为复用推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(MODBUS_USART_TX_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin= MODBUS_USART_RX_PIN ;USART的RX为复用推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(MODBUS_USART_RX_PORT,&GPIO_InitStructure);
if (eParity == MB_PAR_NONE) { USART_InitStructure.USART_Parity = USART_Parity_No; //不进行奇偶位检测 } else if (eParity == MB_PAR_EVEN) { USART_InitStructure.USART_Parity = USART_Parity_Even; //进行偶位检测 }
else
{
USART_InitStructure.USART_Parity = USART_Parity_Odd; //进行奇位检测
}
if (ucDataBits == 9)
{
USART_InitStructure.USART_WordLength = USART_WordLength_9b;发送数据为9位 }
else
{ USART_InitStructure.USART_WordLength = USART_WordLength_8b; //发送数据为8位
}
USART_InitStructure.USART_BaudRate = ulBaudRate ;波特率设置
USART_InitStructure.USART_StopBits = USART_StopBits_1;停止位为1
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;无硬件流
USART_InitStructure.USART_Mode = USART_Mode_Rx |USART_Mode_Tx;允许usart接受和发送数据
USART_Init(MODBUS_USART_PORT,&USART_InitStructure);初始化usart
NVIC_InitStructure.NVIC_IRQChannel = MODBUS_USART_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(MODBUS_USART_PORT,启用);启动usart
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_8);
返回 TRUE;
}
BOOL xMBPortSerialPutByte( CHAR ucByte )
{
/* 在 UART 传输缓冲区中放置一个字节。如果 pxMBFrameCBTransmitterEmpty( ) 被
* 调用,则协议栈调用此函数。
*/
USART_SendData(MODBUS_USART_PORT, ucByte);发送一个字节
返回 TRUE;
}
BOOL xMBPortSerialGetByte( CHAR * pucByte )
{
/* 返回 UART 接收缓冲区中的字节。这个函数在 pxMBFrameCBByteReceived( ) 被调用后由协议栈调用
。
*/<B1148> *pucByte = USART_ReceiveData(MODBUS_USART_PORT);接收一个字节
返回 TRUE;
}
/* 为目标处理器的传输缓冲区空中断
*(或等效项)创建中断处理程序。然后
,此函数应 * 调用 pxMBFrameCBTransmitterEmpty( ),它告诉协议栈
可以发送一个新字符。然后协议栈将调用
* xMBPortSerialPutByte( ) 来发送字符。
*/
static void prvvUARTTxReadyISR( void ){
pxMBFrameCBTransmitterEmpty( )
;
}
/* 为目标
* 处理器的接收中断创建中断处理程序。然后,此函数应调用 pxMBFrameCBByteReceived( )。然后,
* 协议栈将调用 xMBPortSerialGetByte() 来检索
* 字符。
*/
static void prvvUARTRxISR( void ){
pxMBFrameCBByteReceived( )
;
}
/**__attribute__MODBUS_USART_IRQHandler MODBUS_USART_PORT_IRQHandler
**
void MODBUS_USART_IRQHandler(void)
{
if (USART_GetITStatus(MODBUS_USART_PORT, USART_IT_RXNE) == SET) //接收中断
{
prvvUARTRxISR();
USART_ClearITPendingBit(MODBUS_USART_PORT、USART_IT_RXNE);
}
if (USART_GetITStatus(MODBUS_USART_PORT, USART_IT_TC) == SET) //发送中断
{
prvvUARTTxReadyISR();
USART_ClearITPendingBit(MODBUS_USART_PORT、USART_IT_TC);
}}
/
****
#include #include---------------------------------------------------------#include
#include
-------------------------------------------------------**
“
/* -----------------------静态函数 ---------------------------------*/
static void prvvTIMERExpiredISR( void );
//当波特率 > 19200bps时,采用定时器时钟固定为1750us ,//当波特率 < 19200bps时,
定时器时钟为3.5个字符传输时间长
/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
uint16_t 预分频器值;
//时基频率 / (1 + Prescaler) = 20KHz
PrescalerValue = (uint16_t)((SystemCoreClock / 2400) - 1);
MODBUS_TIME_CLOCK_API(MODBUS_TIME_PERIPH,启用);
TIM_TimeBaseStructure.TIM_Period = (uint16_t)usTim1Timerout50us;
TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_计数器模式 = TIM_CounterMode_Up;
TIM_TimeBaseInit(MODBUS_TIME_PORT,&TIM_TimeBaseStructure);
TIM_ARRPreloadConfig(MODBUS_TIME_PORT,启用);使能预装 //
初始化中断优先级
NVIC_InitStructure.NVIC_IRQChannel = MODBUS_TIME_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ClearITPendingBit(MODBUS_TIME_PORT、TIM_IT_Update);
TIM_ITConfig(MODBUS_TIME_PORT、TIM_IT_Update、禁用);
TIM_Cmd(MODBUS_TIME_PORT,禁用);
返回 TRUE;
}
inline void vMBPortTimersEnable( ){ /* 启用计时器,并将超时传递给 xMBPortTimersInit( )
*
/
TIM_ClearITPendingBit(MODBUS_TIME_PORT, TIM_IT_Update);
TIM_ITConfig(MODBUS_TIME_PORT、TIM_IT_Update、启用);
TIM_SetCounter(MODBUS_TIME_PORT、0x00000000);
TIM_Cmd(MODBUS_TIME_PORT,启用);
}
inline void vMBPortTimersDisable( ){
TIM_ClearITPendingBit(MODBUS_TIME_PORT, TIM_IT_Update)
;
TIM_ITConfig(MODBUS_TIME_PORT、TIM_IT_Update、禁用);
TIM_SetCounter(MODBUS_TIME_PORT、0x00000000);
/* 禁用任何挂起的计时器。*/
TIM_Cmd(MODBUS_TIME_PORT,禁用);
}
/* 创建一个 ISR,每当计时器过期时都会调用该 ISR。然后,此函数 * 必须调用 pxMBPortCBTimerExpired( ) 来通知协议栈
* 计时器已过期。
*/
static void prvvTIMERExpiredISR( void ){ ( void )pxMBPortCBTimerExpired
( )
;
}
/**__attribute__MODBUS_TIME_IRQHandler MODBUS_TIME_PORT_IRQHandler
**
void MODBUS_TIME_IRQHandler(void)
{
if (TIM_GetITStatus(MODBUS_TIME_PORT, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(MODBUS_TIME_PORT, TIM_IT_Update); prvvTIMERExpiredISR();
}}
Main.c代码 #include “debug.h”#include “port.h”#include “mb.h”
#include “demo.h”
/* Global typedef *//* Global define */
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,启用);打开GPIO的时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_0 |GPIO_Pin_1);
}
在t main(void)
{
eMBErrorCode eStatus;
/*初始化串口*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Delay_Init();
LED_Init();
//初始化freemodbus 设置RTU模式和ID等 eStatus =
eMBInit(MB_RTU, 0x0A,1, 115200, MB_PAR_NONE);
if (MB_ENOERR != eStatus)
{
printf(“freemodbus 设置RTU或模式失败\r\n”);
}
/* 激活协议栈以及串口和定时器而已。*/
eStatus = eMBEnable();
if (MB_ENOERR != eStatus)
{
printf(“使能ModbusProtocolStack失败\r\n”);
}
while(1) {
(void)
eMBPoll();
}
}
四、展示
五、心得与体会
从实验来看,CH32X035跑Modbus_RTU完全没问题,可以应在一般的工业环境中。有兴趣的同学可以试一试。工程代码已放在附件中。
|