点点滴滴学习STM32单片机系列 (二)

本博客的所有原创文章要求署名、非商业用途和保持一致。要求署名必须包含我的网名(geokai)以及文章来源(选择博客首地址或者具体博文地址)。

商业性使用须预先征得本人同意(发送Email到 [email protected]).

因为最近在做Modbus方面的东西,所以准备就这个话题边做边写。

1.写在之前

epoch1

作为一个非电子学专业的人,至少在两年前我是不知道有ModBus这种高层级的通讯协议的。2年前开始学习电子是从接触arduino开始的,探索中弄清了什么是UART,什么又是RS232,RS485.就在这种薄弱的认知下开始自己稍微复杂一点的arduino项目。多传感器数据的获取,然后通过433的模块进行无线传输。

其实仅仅是传输数据还并不困难,我最早的做法是将数据打包成如下的样子。

S:1:25.00:E:S:2:100:E

所有的数据均以S开头E结尾,并且使用:符号隔开。紧随S其后的是传感器编号,而后是传感器数据。这种数据的编码方式比较简单,同时接收方也比较容易解译这些数据。其实这已经有了ModBus或者说通信协议的思想了。(当时觉得自己简直太棒了)可是随后问题就来了,随着我的传感器数据越来越多,433模块单次就无法传输这么多的数据了。会被自动分包,这样就会造成头尾的数据在解译的时候丢失。除此之外传输效率问题也凸显了,因为是字符串传输数字浪费了大量的资源。至此我在传输数据这条路上的第一个时期结束了。总得来说这种方式学习项目凑活,稍微要求可靠性高一点就会出现严重的问题。

epoch2

有了第一个时期的工作经验,我开始探索更加Robust的数据传输方式。这一阶段我开始使用433模块进行双向的Arduino通讯。我开始认识到查询方式的存在。将无脑使劲传数据的方式改为要什么请求什么数据的方式。同时我可以通过指令远程控制另一台机器进行特定操作。我将传输的数据均打包成如下的样子

S:1:255:E

所有的数据均只有简短的一串。还是S开头E结尾。中间分为两部分。不同的是我将中间的第一部分设置为功能码,这些功能码我预先写在程序里。比方说1就是读传感器1的数据,2就是读传感器2的数据,3是打开或关闭继电器1的功能。诸如此类。由于传输的数据少不会出现头尾丢失。同时我的数据获取采用查询方式,所以不会漏数据。此外通信是双向的,可以达到获取和控制两种功能。这种方式我使用了很久。但我仍然意识到数据传输的低效性。但总得来说it works well for a long time.

epoch3

第二阶段基本上在我接触arduino后的半年就结束了。加上很多东西我都做好了,因此很长一段时间我没有再设计什么新的东西。之前我买传感器多买5v或者20ma模拟量输出的传感器,然后自己在用ADC读出来。直到我买了一个含有ModBus协议的传感器,我瞬间明白没文化真可怕。我自己想的协议是多么的幼稚,这种无脑协议70年代前工程师们就使用并抛弃了。并诞生了ModBUS这种robust的工业协议。有查询有响应,由数据有效判断的CRC,由数据传输开始与结束的3.5帧间隔,有充分抽象的功能表。总之robust and extensible.

当然好的东西是有代价的,那就是使用的门槛高了,确切的说是编写单片机程序的复杂度增加了。Arduino上有方便使用的modbus包,可以参看我的其他博文。虽然Arduino已经可以搞定一大堆的工程需求了。但是对于复杂的项目来说还是得上M3,M4内核,AVR似乎还是适用于小项目。

2.代码编写的基本思路

(本人并非专业,只是业余研究,因此思路仅供参考。能不能用到实际工程还需要我验证后继续谈论。)

首先解决主机的代码

1.数据准备的函数

2.使用IT方式发送数据

3.等待从机响应

4.使用DMA方式读取数据,每次1帧,同时将读到的1帧存放在缓存数组中,缓存数组指针后移1位

5.每读完1帧数据就激活一次定时器,定时时间为3.5帧长

6.3.5帧时长超时激活中断

7.中断中将缓存数组转存到modbus接收数组中,同时缓存数组清零

3.CubeMx配置基本代码

使用Stm32F407VET6 单片机进行开发。

1)配置时钟

2)开启uart1的DMA_RX模式,并且为循环接收,设置优先级为6

3)开启TIM9,设置预分频为16799,开启NVIC,设置优先级为5,高于uart的DMA_RX优先级即可

4)开启FreeRTOS

4.KeilV5代码编写关键点

执行HAL_UART_Receive_DMA(huart1, buf, 1)后,系统每接收到一个数据就会进入一次HAL_RXCpltCallBack()函数。这个回调函数是需要自己编写的。既将buf中的数据转存到rec_buff中,并且指针加1.然后重置TIM9定时器,并激活TIM9定时器。正常接收的时候由于接收间隔小于3.5帧,TIM9会还没来得及中断就被重置。

当接收完成后TIM9会引发中断,同时进入中断回调函数。在定时器中断回调函数中我们暂停定时器,同时将rec_buff中的数据再转存到modbus_rec_buff中,并重置rec_buff数组。如果不转存,那么在后续解译接收到的数据的时候可能会接收到新的数据,使得数据无效化。

具体的代码在下期贴上。

猜你喜欢

转载自www.cnblogs.com/geokai/p/10222754.html