注册 登录
电子工程世界-论坛 返回首页 EEWORLD首页 频道 EE大学堂 下载中心 Datasheet 专题
sattic的个人空间 https://home.eeworld.com.cn/space-uid-530499.html [收藏] [复制] [分享] [RSS]
日志

modbusRTU Slave

已有 748 次阅读2014-11-27 09:15 |个人分类:modbus| modbus, RTU, 串口

1、 协议,不说,文库 modbus通讯协议,有中文的

2、 环境stm32f103vc,裸奔,有系统可用openmodbus,仅做实验还可以uc/modbus,有slave源码,这两个都没试验过

3、只实现功能,不讨论效率,我自认为我的代码效率不高,我接受的底线就是不要polling

4、资源,需要一个串口,好像是废话,再需要一个定时器,用于判断帧尾,st定时器多的用不完,找个最简单的tim7吧

5、扯了好多没用的

先初始化串口,打开发送接收中断

初始化定时器,时间 = 串口发送1字节时间 x 10,不用开始工作,待命吧

额,不是 串口发送1字节时间 x 3.5吗?那是最小时间,我跟master程序员协商过了,最小10个吧,master流程,发送请求,等待应答(应答要么得到正确应答,要不不耐烦了timeout)。

代码

void modbusRTU_open(uint8_t Addr, uint16_t * pBuffer, uint16_t Length)

{

GPIO_InitTypeDef GPIO_InitStructure;

USART_InitTypeDef USART_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);

RCC_APB2PeriphClockCmd (RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

TIM_DeInit(TIM7);

TIM_TimeBaseStructure.TIM_Period = 10000;

TIM_TimeBaseStructure.TIM_Prescaler = 36;

TIM_TimeBaseStructure.TIM_ClockDivision = 0;

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;


TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure);

/* Configure USART1 Rx (PA10) as input floating */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOA, &GPIO_InitStructure);


/* Configure USART1 Tx (PA9) as alternate function push-pull */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_InitStructure.USART_BaudRate = 9600;

USART_InitStructure.USART_WordLength = USART_WordLength_8b;

USART_InitStructure.USART_StopBits = USART_StopBits_2;

USART_InitStructure.USART_Parity = USART_Parity_No ;

USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

USART_Init(USART1, &USART_InitStructure);


USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

USART_Cmd(USART1, ENABLE);

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

ClintAddr = Addr;

ClintBufferLength = Length;

pClintBuffer = pBuffer;

}

6、 3个参数什么意思

Addr,节点地址

pBuffer,数据区首地址

Length,数据区长度,单位WORD 16bit

保存这几个参数到全局变量,好了,不会丢了

额,指针,数组,我没有检查指针合法和数组边界,手有点发抖,有点后怕,万一指针非法,数组越界,后果不堪设想,怎么办,用的时候小心吧

关于数据区,modbus会请求某个地址数据的读或写,这个地址和实际存储空间要一一映射好,置于数据存储是否连续没啥要求,看着处理吧,连续也许会省很大事

7、 好了,接收数据吧

先来个缓冲存储接收数据

uint8_t ReceiveCache[128];

uint8_t ReceiveCacheCount;

uint8_t ReceiveCachePointer;

缓冲大小和master沟通吧,能放下最大数据帧就好,再来个帧个数计数,不然我怎么知道接收了多少数据,还要有个offset,不然我怎么知道下一个数据该存储那个位置

缓冲区有了,可以接收了吧,接收中断代码

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)

{

USART_ClearITPendingBit(USART1, USART_IT_RXNE);

ReceiveCache[ReceiveCachePointer] = USART_ReceiveData(USART1);

ReceiveCachePointer++;

modbusRTU_TimerStart();

}

最后一句什么用,每次接收中断,重新开始计时,还记得tim7是干啥的吗。能看看里面的内容么,of coursevoid modbusRTU_TimerStart(void)

{

TIM_SetCounter(TIM7, 1); //为什么会是1而不是0,您随意,我不差1个数

TIM_ClearITPendingBit(TIM7, TIM_IT_Update);

TIM_ITConfig(TIM7, TIM_IT_Update, ENABLE);

TIM_Cmd(TIM7, ENABLE);

}

8、 数据放到缓冲区了,怎么用,不急,还没有帧尾呢

什么时候帧尾,当然是我们的tim溢出中断,如果一直没有数据被接收,也就没有接收中断,很快它就溢出了

“不要发了,让它溢出~!”。“收到!马上停工...”

“oh,中断了”,帧尾来了,处理他吧,等等,怎么才能让别人知道帧尾呢,顶一个标识吧 全局变量

uint8_t ReceiveFrameFlag;

中断代码

if(TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)

{

TIM_ClearITPendingBit(TIM7, TIM_IT_Update);

TIM_ITConfig(TIM7, TIM_IT_Update, DISABLE);

TIM_Cmd(TIM7, DISABLE);

TIM_SetCounter(TIM7, 1);

ReceiveFrameFlag = 1; //完成一帧

ReceiveCacheCount = ReceiveCachePointer; //数据帧长度

ReceiveCachePointer = 0;

}

怎么处理数据,管他呢,我只管判断帧尾

9、数据处理

void modbusRTU_main(void)

{

uint8_t modbus_cmd;

uint16_t modbus_addr;

uint16_t modbus_length;

// uint16_t modbus_crc;

if(ReceiveFrameFlag == 1)

{

ReceiveFrameFlag = 0; //告诉你们,你们的数据我处理了

//数据要在这里处理,此处省略代码若干

}

}

到底怎么处理..好吧

9.1、 判断地址,你想独享总线,那是不可能的,还记得存储在ClintAddr里面的节点地址么,比较一下

if(ReceiveCache[0] != ClintAddr)

{

return; //不是叫我,我才懒得搭理呢! 美得你,你无权

}

9.2、 数据接收正确吗,验证下吧

if(modbusRTU_CRC(ReceiveCache, ReceiveCacheCount) != 0x0000)

{

return; //数据出错,毫不客气的丢弃

}

你判断的神马?

uint16_t modbusRTU_CRC(uint8_t *pBuffer, uint16_t Length); CRC校验,问问度娘吧 CRC16

参数又是指针和长度……怎么办,我手发抖,精神紧张,程序不要崩溃啊

额,你不要吧非法的参数给我,又怎么能崩溃呢

9.3、 都对了,处理吧

modbus_cmd = ReceiveCache[1]; //第二字节是请求代码

TransmitCache[0] = ClintAddr;

TransmitCache[1] = modbus_cmd;

if(modbus_cmd!=0x03 && modbus_cmd!=0x06 && modbus_cmd!=0x10)

{

return; //我们只响应这几个,别的不管,我的master只要这几个

}

有个地方,TransmitCache[0]是什么,刚才说到接收需要缓冲区,这个是发送缓冲区,一会说吧,先把接收到的请求处理完了吧

9.3、 获取数据区地址

modbus_addr = (uint16_t)(ReceiveCache[2]<<8 | ReceiveCache[3]);

9.4、 根据请求散转

switch(modbus_cmd)

{

case 0x03:

modbus_length = (uint16_t)(ReceiveCache[4]<<8 | ReceiveCache[5]);

modbusRTU_Cmd03(modbus_addr, modbus_length); //处理03

break;

case 0x06:

modbusRTU_Cmd06(modbus_addr, &ReceiveCache[4]); //处理06

break;

case 0x10:

modbus_length = (uint16_t)(ReceiveCache[4]<<8 | ReceiveCache[5]);

modbusRTU_Cmd16(modbus_addr, modbus_length, &ReceiveCache[7]); //处理16

break;

default:

break;

}

这里面的三个处理函数

void modbusRTU_Cmd03(uint16_t Addr, uint16_t Length);

void modbusRTU_Cmd06(uint16_t Addr, uint8_t *pdata);

void modbusRTU_Cmd16(uint16_t Addr, uint16_t Length, uint8_t *pdata);

什么意思,怎么实现,看协议,看协议,需要一遍一遍的看

好吧,我贴个03的

void modbusRTU_Cmd03(uint16_t Addr, uint16_t Length)

{

uint16_t i;

if(Addr+Length >= ClintBufferLength)

{

return; //检查地址和数据长度,这个事master传来的数据,我对他不是很放心。

//其实他的水平真的很高,不是对他不放心,而是自己

}

TransmitCache[2] = Length * 2;

for(i=0; i<Length; i++)

{

TransmitCache[3+(i*2)] = pClintBuffer[i+Addr]>>8;

TransmitCache[4+(i*2)] = pClintBuffer[i+Addr]&0xFF;

}

i = modbusRTU_CRC(&TransmitCache[0],(Length*2)+3);

TransmitCacheCount = (Length*2)+5;

TransmitCache[3+(Length*2)] = i>>8;

TransmitCache[4+(Length*2)] = i&0xFF;

modbusRTU_Transmit();

}

最后一句是什么?

9.5、 回应,好,先来说说发送缓冲吧,定义

uint8_t TransmitCache[128];//长度问问你的master最长请求多么长的数据

uint8_t TransmitCacheCount;

uint8_t TransmitCachePointer;

为什么需要缓冲,我算出一个来发送一个不行么

两个问题,一、你想polling吗,我不接受;二、如果你两个字节之间间隔时间大于 一个字节时间x3.5,master可就要认为我帧尾了。所以要先放缓冲,准备好了一痛全发走

前面三个函数给我们准备好缓冲区了,连回应数据帧长度也给出了,哦还有crc也有了,真好,只需要发走就行了

那么他最后那个函数就是发送

void modbusRTU_Transmit(void)

{

USART_ClearITPendingBit(USART1, USART_IT_TXE);

USART_ITConfig(USART1, USART_IT_TXE, ENABLE); //先打开中断

TransmitCachePointer = 1; //为什么会是1而不是0

USART_SendData(USART1, TransmitCache[0]);//因为第0个在这,不然你怎么触发发送中断呢,先塞进一个数去,发送完了就有了

}

中断中还要把剩余的发送走

if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)

{

USART_ClearITPendingBit(USART1, USART_IT_TXE);

USART_SendData(USART1, TransmitCache[TransmitCachePointer]);

TransmitCachePointer++;

if(TransmitCachePointer >= TransmitCacheCount) //判断发送完成

{

TransmitFrameFlag = 1;

TransmitCachePointer = 1;

USART_ITConfig(USART1, USART_IT_TXE, DISABLE); //没有需要发送的了,再来中断怎么办,关了吧,不会来了吧

}

}

10、 还有什么,恩,有

void modbusRTU_main(void);这个什么时候调用,他可是处理数据最重要的函数啊。

我先问,需要实时吗?需要,为啥不用RTOS,稳定,实时性好。

那怎么办,尽最大努力。放到主循环,每次扫描吧,省事。我的程序需要很长时间才能运行回来,怎么办,把你所有的polling都处理掉吧,别让他们烧内核了。

我放到定时器中断里定时扫描行么,那你为什么不直接放在帧尾定时器tim7中断中直接处理了呢,那样更快!只不过中断时间变长,

有问题吗,当然有。数据区是临界资源,哦,没有操作系统,那也这么叫吧。你的一切读写操作都要关中断,防止在你操作时中断打断你当前的操作,这么简单吗?恩。

11、 测试吧,好

数据区

uint16_t buffer[128]; //一定是16bit,modbus 03 06 16请求的单位就是WORD

测试代码

int main(void)

{

modbusRTU_open(2,buffer,32); //设置节点地址2,数据长度32,我没有越界!

while(1)

{

modbusRTU_main();

}

}

12、 还有什么

差错控制

不支持的请求功能码要响应

错误的数据地址要响应

错误的数据长度要响应

读写数据区数据出错要响应

怎么实现…… 告诉你的master程序员,你能响应哪些,约定地址范围,就可以了。

一定要响应吗,跟你的master商议,如果一定要响应,在前面检测请求功能码,地址和长度的时候给个响应就行了。

我约定的是超时,也就是不响应,master会重新请求

13、最后

能提高效率吗,那你为什么不选个串口有485功能的的,我对st单片机串口的了解不深,对他深恶痛绝,但是我知道有很多单片机的串口有超时,有地址功能,接收也有FIFO或DMA,那样帧尾就自动判断了,而且地址也自动判断了,接收到的就是你想要的帧

需要源码

251088486@qq.com

可以mail我,我回尽最大努力回复

自我感觉比较胖,可以叫我二师兄或二师弟吧,也可以叫Sattic

欢迎批评指正

全部作者的其他最新日志
评论 (0 个评论)

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 注册

热门文章