随笔 - 8  文章 - 0  评论 - 36  阅读 - 52982
  2013年10月18日

最近一客户要求使用STC12C5A60S2实现Modbus Rtu协议与KEPServerEx V4.0软件通信,采集单片机P2口每位的状态,设置P0口每位的状态,实现三路AD转换其中一路采集的是C02的浓度,以及使用SHT10获取温度和湿度。KEPServerEx V4.0使用TCP通信,而单片机使用的是串口RS232通信,所以增加了TCP转RS232的模块。

本程序相对比较简单,STH10有现成的代码,AD转换直接官方提供的程序,主要需要实现的就是Modbus RTU通信协议的实现。根据对方的要求,P0,P2口的设置需要一位一位的读取或设置,如果按Modbus的内容,可以使用设置线圈状态和读取线圈状态,而AD和温度湿度获取需要读取寄存器,这样一来就需要实现3个功能即对线圈设置线圈就读取寄存器。为了减少功能的实现数量,读取线圈和读取AD等数据都使用读取寄存器的03功能,为了方便能够位下位机传数据所以设置线圈使用06写单个寄存器。这样就只需要实现2个功能即可。在我的前一篇博客中已经介绍了使用单片机与PC进行通信,当时实现了03功能,本次只需要实现06功能即可。

06功能码的通信格式:
 上位机发送数据格式  地址,功能码,数据地址高位,数据地址低位,数据高位,数据低位,CRC低位,CRC 高位,
 下位机回应数据格式  地址,功能码,数据地址高位,数据地址低位,数据高位,数据低位,CRC低位,CRC 高位, 

所以接收到上位机的命令后,只需要判断地址是否为本机地址,功能码是否为相应的06即可,如果是则对后面的数据进行解析,将相应的数据写入对应的寄存器即可。由于上位机每次发来的是对应P0口某一位的状态,所以需要有8个字节来对应8个位。而返回的信息就比较简单了,如果没有错误就直接将接收到的命令返回即可。

unsigned char createRespond_M_6(unsigned char *respondMessage,unsigned char* _messageReceived ,unsigned int* _registerData)
{
    unsigned char  i;
    unsigned char  numberOfPoints = 0;
    unsigned char _slaveAddress = 0 ;
    unsigned char  bytesToSend = 0;
    unsigned char  startAddress=0;
    unsigned char crcCalculation[]={0,0,0};

    unsigned int Address = _messageReceived[3];  //将其转换为int型,由于使用的寄存器地址最多1,20个,所以只取低位即可,高位永远为0,丢弃
                    
    _registerData[Address ]  =    _messageReceived[4]<<8;
    _registerData[Address ]  |=    _messageReceived[5];


    for(i = 0 ; i < 8 ; i ++)
    {
        respondMessage[i] =    _messageReceived[i];
    }
 
      
    return 8;
}

 

由于在通信中要么是读取寄存器的值,要么是读取寄存器的做(其实就是开辟的一个整型数组),那么我们要实现设置P0口的状态和获取P2口的状态就需要在程序中根据需要,不停地更新寄存器的值或者从寄存器中读取数据写入到相应的端口即可,当然AD和温度湿度也是一样的道理。

在主函数的整个while大循环中加入以下代码:

        P2 = 0XFF;
//八路输入由P2口决定,随时更新其寄存器的值,若主机要返回,则直接返回相应寄存器的值 
        
                                    registerData[5] = P2&0x01;            registerData[6] = (P2&0x02)>>1;
        registerData[7] = (P2&0x04)>>2;
        registerData[8] = (P2&0x08)>>3;
        registerData[9] = (P2&0x10)>>4;
        registerData[10] = (P2&0x20)>>5;
        registerData[11] = (P2&0x40)>>6;
        registerData[12] = (P2&0x80)>>7;

 //八路输出由P0输出,只需要由主机设置寄存器值即可更新寄存器的值 。
        P0_0 = registerData[13]&0x01 ;            P0_1 = registerData[14]&0x01 ;
        P0_2 = registerData[15]&0x01 ;
        P0_3 = registerData[16]&0x01 ;
        P0_4 = registerData[17]&0x01 ;
        P0_5 = registerData[18]&0x01 ;
        P0_6 = registerData[19]&0x01 ;
        P0_7 = registerData[20]&0x01 ;

        GetTempAndHumi();

        //检查串口数据并使用ModBus协议进行检验,正确则返回数据,返回的数据在registerData,可以在其他的函数中改变相应的值。                     
       CheckModBusAndRespondToHost();

对于AD的数据,由于AD的结果是使用的中断方式而不是查询所以在主循环中就看不到了,在中断函数中直接设置寄存器的值,在需要时直接返回。这部分使用了10的AD转换, 所以需要做相应的转换:代码如下:

void adc_isr() interrupt 5 using 1
{
    ADC_CONTR &= !ADC_FLAG;         //Clear ADC interrupt flag


    registerData[ch+2] = ADC_RES<<8;
    registerData[ch+2] |= ADC_LOW2<<6;
    registerData[ch+2] = registerData[ch+2] >>6;


//if you want show 10-bit result, uncomment next line
//    SendData(ADC_LOW2);             //Show ADC low 2-bit result
    
    if (++ch > 2) ch = 0;           //switch to next channel
    ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START | ch;
}

在这里只使用了3路AD转换。
在使用Modbus RTU数据进行寄存器的读写时需要注意的是在上位机比如组态王、KEPServerEx等,在设置寄存器地址时是从1开始,而发送的命令是从0开始,

读写寄存器都位于4区,所以我们 设置的地址一般都为40001.....40002等,对应寄存器0    1。

以下是在KEPServerEx设置的每个端口即AD等所对应的寄存器地址,

测试结果:

在这里AD全为1023是因为开发板全部接了上拉所以都为1023。

在这次开发完成后,客户反应AD测量结果不准确,由于我并没有在客户的现场,无法得知道具体如何,所以我的第一反应是STC12C5A60S2的AD基准电源使用的是VCC,可能是VCC电源与5V差距比较远而引起的,建议客户增加电池做为基准或者提高电源的精度,但对方说比较困难。后来我问他如何发现不准确的,他说是和别的系统测量的结果进行比较的,并且给我发来了他们的传感器资料,我看了一下资料,突然发现传感器输出是0-2v,而STC的AD是0-5V,一下就明白了这中间的问题,让客户将结果X2.5后就正常了。

posted @ 2013-10-18 20:22 东王 阅读(2994) 评论(1) 推荐(0) 编辑
  2013年7月11日
摘要: 最近研究了一下MODBUS通信,在STC12C5A60S2单片机上实现了MODBUS协议的部分功能,方便上位机从单片机系统上获取数据,比如由单片机获取的温度、湿度、或者控制信号的状态等。有了MODBUS协议后,上位机的开发就很方便了,可以使用C#等高级语言通过串口通信,或者使用组态王直接通过串口通信而不需要写代码,这些都归功于MODBUS协议的开放性,很多的组态软件都能提供对他的支持。甚至通过PLC直接可以和单片机通信,比如在PLC系统上没有AD时,可以使用单片也来进行AD,然后通过MODBUS返回给PLC进行相应的控制。通过MODBUS协议可以实现主机和多个从机进行通谢,而每个从机有唯一的地 阅读全文
posted @ 2013-07-11 20:26 东王 阅读(10110) 评论(4) 推荐(4) 编辑
  2013年2月1日
摘要: C#一步一步实现插件框架的示例(三)C#一步一步实现插件框架的示例(二)C#一步一步实现插件框架的示例(一)前面有朋友反应,运行时出现:“ExampleAddin.WorkBenchSingleTon”的类型初始值设定项引发异常错误,这是由于在执行插件的加载过程中出现了错误,比如我们这本篇的例子中,判断查询按钮是否有效时的判断:有这样的语句:var form = WorkBenchSingleTon.WorkBench.ActiveMdiChild;由于最初执行时workbench可能为空,所以就会报错,例子中加了if (WorkBenchSingleTon.WorkBench==null) 阅读全文
posted @ 2013-02-01 12:29 东王 阅读(5228) 评论(6) 推荐(3) 编辑
  2013年1月25日
摘要: C#一步一步实现插件框架的示例(一)C#一步一步实现插件框架的示例(二)前两篇我们已经实现了功能按钮与界面的分离,其实也只是工具栏与其执行的功能代码与界面的分离,其作用还非常有限,因为他无法获取到主窗口的任何东西,无法进行操作,比如主界面有一个TextBox,那么这个按钮还不能访问到,也无法对其进行操作。我们今天就来完成View的设计,让工具栏的按钮能够访问窗口中的内容,同时窗口中的内容也是根据插件来自动生成。首先我们来实现View插件的生成代码。View即为主窗口中的一个mdi窗口生成一个接口:IViewContent public interface IViewContent ... 阅读全文
posted @ 2013-01-25 22:33 东王 阅读(6475) 评论(8) 推荐(5) 编辑
  2013年1月19日
摘要: 前一篇链接:C#一步一步实现插件框架的示例(一)今天我们再接着前一篇来完善插件功能。在前一篇中我们将生成插件按钮的代码直接写在了WorkBench中,无法体现插件式开发的优越性,现在我们来对其进行分离。首先对WorkBench类中的构造函数进行修改,删除原来的生成按钮功能的函数,改变后如下: public WorkBench() { InitializeComponent(); this.Controls.Add(ToolBar); Application.Idle += new EventHandler(Appl... 阅读全文
posted @ 2013-01-19 18:37 东王 阅读(8225) 评论(5) 推荐(3) 编辑
摘要: 像我这样的菜鸟,写程序一般就是拖控件,双击,然后写上执行的代码,这样在窗口中就有很多事件代码,如果要实现各按钮的状态,那得在很多地方修改代码,极为复杂.通过参考CSHARPDEVELOP的代码就说明和网上各位朋友的示例,在这里,自己实现了一个很简单的插件程序,方便程序的开发,每个功能可以独立开发,也方便维护.现在给大家讲讲其方法.先上张图片:由于我这个插件使用了DEVEXPRESS的控件,所有要运行就需要安装,在这里我们就不使用DEV的控件了.首先,我们需要定义一个接口,该接口定义了一个工具栏的按钮要执行的动作:ICommnd public interface ICommand { ... 阅读全文
posted @ 2013-01-19 00:23 东王 阅读(13537) 评论(12) 推荐(5) 编辑
  2013年1月11日
摘要: 以前写过一篇文章是实现界面与功能的分离,基于插件的方式,要实现一个功能需要实现一个类,该类继承于:AbstractToolButtonCommand,而AbstractToolButtonCommand又继承于:DevExpress.XtraBars.BarButtonItem类,这样在覆写父类的一些属性和方法的时候,VS的提示功能会列出很多属性,不方便开发,本次对整个结构进行了调整。AbstractToolButtonCommand类只实现了一些必要的接口如: public abstract class AbstractToolButtonCommand : AbstractCommand, 阅读全文
posted @ 2013-01-11 21:57 东王 阅读(5329) 评论(0) 推荐(1) 编辑
摘要: 网上有很多关于SingleTon的用法说明及实现,大多都提供了很多种实现的方法,包括如何保证在多线程时也只提供一个实例。对于他的作用都是说只提供一个实例,但是到底有什么作用,对于我们这些初学者来说并不知道如何使用。经过自己的摸索,目前了解到了一些用法,不知道对不对,请高手们指点:比如我们在程序中设置了一个主窗口:public partial class DefaultWorkBench :From { public DefaultWorkBench() { InitializeComponent(); }}由于这个窗口我们只需要一个就行了,所以我们使用SingleTon模式。publ... 阅读全文
posted @ 2013-01-11 18:44 东王 阅读(1084) 评论(0) 推荐(0) 编辑
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示