0
开发板十分灵活好用啊~再感谢一下电子发烧友论坛给的试用机会。
本次开始ModBus通讯程序的编写,本帖先更新物理层的程序。协议层下一次更新。ModBus通讯协议简单介绍一下: MODBUS 是一项应用层报文传输协议, 用于在通过不同类型的总线或网络连接的设备之间的客户机/服务器通信。MODBUS 是一个请求/应答协议,并且提供功能码规定的服务。MODBUS 功能码是 MODBUS请求/应答 PDU 的元素。本文件的作用是描述 MODBUS 事务处理框架内使用的功能码。
在物理层,Modbus 串行链路系统可以使用不同的物理接口(RS485、RS232)。最常用的是tiA/EIA-485 (RS485) 两线制接口。作为附加的选项,也可以实现 RS485 四线制接口。当只需要短距离的点到点通信时,TIA/EIA-232-E (RS232) 串行接口也可以使用。
Modbus 报文 RTU 帧由发送设备将 Modbus 报文构造为带有已知起始和结束标记的帧。这使设备可以在报文的开始接收新帧,并且知道何时报文结束。不完整的报文必须能够被检测到而错误标志必须作为结果被设置。在 RTU 模式,报文帧由时长至少为 3.5 个字符时间的空闲间隔区分。在后续的部分,这个时间区间被称作 t3.5。
通过上面简单了解,我们知道ModBus的帧格式。以及报文内容。详细说明请看附件文档。
我们使用 STM32F411 Nucleo 开发板的串口1,作为ModBus的收发口。使用 TIM2 作为报文结束 3.5T的时间判断。然后解析接收的数据,最后处理功能码,和用户函数。数据的回发我们使用 DMA2数据流7通道4。
首先初始化串口1,我们使用9600波特率。把PA9(USART1_TX)、PA10(USART1_RX)设置为复用,记得打开内部上拉。使能串口发送DMA和接收中断。
/************************************************************************************
初始化串口1
************************************************************************************/
void UART1_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1,&USART_InitStructure);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
USART_ClearFlag(USART1,USART_FLAG_RXNE);
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
USART_Cmd(USART1,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
初始化定时器2,这里我们使用100M的主频(实际项目中会降频使用),定时器不分频,这样的话我们设置重载寄存器值为10000 。定时器就100us中断一次。因为ModBus报文3.5T时间内没数据认为结束。9600波特率发送一个ModBus数据帧时间为 10*1/9600 = 1.215ms 。 3.5T = 3.65ms 。这里设置100us的中断,判断结尾的时候只要计数36次 即认为报文结束。
/*******************************************************************************
初始化定时器2
*******************************************************************************/
void TIM2_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_DeInit(TIM2);
TIM_TimeBaseInitStructure.TIM_Prescaler = 0;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000; //100us *36=3.5T(9600)
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0x0000;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
TIM_Cmd(TIM2,ENABLE);
}
USART1发送使用的DMA通道初始化。
/************************************************************************************
DMA初始化
************************************************************************************/
void DMA_Uart_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DMA_InitStructure.DMA_Channel = DMA_Channel_4;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
// DMA_InitStructure.DMA_MemoryBaseAddr = ;
// DMA_InitStructure.DMA_BufferSize = ;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream7,&DMA_InitStructure);
DMA_ITConfig(DMA2_Stream7,DMA_IT_TC,ENABLE);
DMA_Cmd(DMA2_Stream7,DISABLE);
}
/*******************************************************************************
接收中断里把数据放到循环缓冲区,并防溢出。这里还对可能出现的移位寄存器溢出等进行了处理。因为我实际调试中,当我暂时暂停调试而上位机不停的发的时候,继续仿真会出现移位寄存器溢出导致HardFault
*******************************************************************************/
void USART1_IRQHandler(void)
{
if(USART_GetFlagStatus(USART1,USART_FLAG_FE) != RESET)
USART_ClearFlag(USART1,USART_FLAG_FE);
if(USART_GetFlagStatus(USART1,USART_FLAG_ORE) != RESET)
{
USART_ClearFlag(USART1,USART_FLAG_ORE);
ModBus1BufLength=0;
ModBus1DataBuf[0] = USART_ReceiveData(USART1);
}
if(ModBus1Time<36)
ModBus1Stop();
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET)
{
if(1==ModBus1OverFlow) ModBus1BufLength = 0;
ModBus1DataBuf[ModBus1BufLength++]=USART_ReceiveData(USART1);
if(255==ModBus1BufLength) ModBus1OverFlow = 1;
ModBus1Start();
USART_ClearFlag(USART1,USART_FLAG_RXNE);
}
}
定时器中断用来计时报文的结束标志,以及处理完成后 等待3.5T再发送的计时。定时器在串口有数据接收的时候打开,串口完成通讯之后自动关闭。
void TIM2_IRQHandler(void)
{
//------------------------------------------------------------------------------
if(ModBus1OverFlow)
{
ModBus1OverFlow=0;
ModBus1BufLength=0;
ModBus1Stop();
}
else if(ModBus1Flag)
{
if(36==++ModBus1Time)
osSignalSet(ModBus1TskId,ModBusRecvFlag);
if(72==ModBus1Time)
{
osSignalSet(ModBus1TskId,ModBusSendFlag);
ModBus1Stop();
}
}
//------------------------------------------------------------------------------
if(ModBus1Flag);
else
{
TIM_SetCounter(TIM2,0);
TIM_ITConfig(TIM2,TIM_IT_Update,DISABLE);
}
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
}
/************************************************************************************
ModBus任务里面暂时只是接收完整报文完成,等待3.5T再发送出去。任务里面等待发送请求而把自身挂起。直到发送事件产生,开启DMA发送。最后清除等待的标志
************************************************************************************/
void ModBus1(void const *arg)
{
SendSet1_Structure.SorcTskID = ModBus1TskId;
for(;;)
{
osSignalWait(ModBusSendFlag,osWaitForever);
ModBusHandle(ModBus1DataBuf,&ModBus1BufLength,ModBus1TskId,USART1);
osSignalWait(ModBusSendOverFlag,osWaitForever);
osSignalClear(ModBus1TskId,(ModBusRecvFlag|ModBusSendFlag|ModBusSendOverFlag)); //
osDelay(1);
}
}
实测完成ModBus通讯物理层。
实测数据:
接收的数据原封不动的发送回去。
观察通讯波形,接收和发送之前的时间差 △X为7ms多一点。符合报文结尾3.5T 加发送间隔3.5T的时间。
下一贴要实现ModBus 协议层的程序,敬请期待~
|
评分
-
查看全部评分
|