0、项目简介
该项目是本校本院系协助工厂开发的一款监控系统,本着资源共享、共同学习的本心笔者将该项目总结与此文章。该项目目前需求较为简单,主要检测5路温度信息并将其信息反映到MCGS组态屏幕上,其中两路使用18B20数字温度传感器,三路使用PT100热敏电阻。接下来笔者会从MCGS、MODBUS总线协议以及PT100驱动电路等几个方面详解介绍。
一、MCGS简介
MCGS是北京昆仑通态自动化软件科技有限公司研发的一款基于windows平台的组态系统。本项目主要使用MCGS的嵌入式版,就是俗称的MCGS组态触摸屏(图一)。该屏幕具有一个9针的接口,支持RS232和RS485通讯协议。(图二,使用界面)本项目主要使用MCGS通过RS485总线使用MODBUS-RTU协议进行组网通讯的。
控制页面
二、MODBUS协议介绍
MODBUS通讯协议是由Modicon在1979年发明的,也是现如今最常用的工业总线协议。
1、MODBUS的寄存器主要分为四种:线圈寄存器、离散输入寄存器、输入寄存器和保持寄存器四种。
2、常用的功能码
功能码 | 描述 | 位/字操作 | 操作数量 |
---|---|---|---|
01H | 读线圈寄存器 | 位操作 | 单个、多个 |
02H | 读离散出入寄存器 | 位操作 | 单个、多个 |
03H | 读保持寄存器 | 字操作 | 单个、多个 |
04H | 读输入寄存器 | 字操作 | 单个、多个 |
05H | 写线圈寄存器 | 位操作 | 单个 |
06H | 写保持寄存器 | 字操作 | 单个 |
... | ... | ... | ... |
在本项目中使用的全部是4区可读写的保持寄存器既功能码只使用03H(读)、06H(写)两个
3、MODBUS报文
MCGS开机便会不停的发送报文读所需要的寄存器。MCGS读报文格式如下
从机地址 | 功能码 | 寄存器起始地址高位 | 寄存器起始地址低位 | 寄存器数量高位 | 寄存器数量低位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
01 | 03 | 00 | 64 | 00 | 01 |
报文不难看出该报文是读1号从机100H地址起一个寄存器数据的报文,该报文响应如下:
从机地址 | 功能码 | 字节数 | 数据高位 | 数据低位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|
01 | 03 | 01 | 00 | 64 |
其他寄存器的读写与响应都大同小异。详细请见MODBUS功能码,在这里MODBUS就不多说了。
三、STM32模拟MODBUS协议
该项目使用主控芯片是以Cortex-M3内核ST公司的STM32F103系列的单片机。
1、RS485转TTL
由于嵌入式芯片使能接收TTL电平,所以有必要将RS485的电平转换为TTL电平。这里我们使用了MAX485芯片,驱动电路如下图,考虑到硬件自动流向控制的稳定性不高,所以该电路设计没有采用自动流向控制。
MAX485驱动电路
2、MAX485
MAX485引脚图与真值表如下图
MAX485引脚图及内部结构
3、MAX485引脚作用
引脚 | 名称 | 功能 |
---|---|---|
1 | RO | 接收器输出 |
2 | RE | 接收器输出使能,为0时允许输出 |
3 | DE | 驱动器接收使能,为1时允许驱动器工作 |
4 | DI | 驱动器输入 |
5 | GND | 地 |
6 | A | 485+ |
7 | B | 485- |
8 | VCC | 驱动器电源,5V |
4、STM32接收代码
由于发送与接收都是以报文的形式,并且需要保证通讯出现错误的时候有较强的判别能力所以采用了TIM2定时器作为接收超时判断。在串口中断程序里面定义两个静态变量用来计数和数据的缓存。
void USART1_IRQHandler()
{
static unsigned char zs_cTime;//保存数据量
static unsigned char zs_cData[20];//保存接收到的数据
unsigned char i;
OSIntEnter();//通知系统进入了一个中断
USART_ClearFlag(USART1, USART_FLAG_RXNE);//清除USARTx的待处理标志位
if(g_Tset>=1)//是否接收数据超时
{
g_Tset = 0;//清零标志位
zs_cTime = 0;//清空计数器
for(i=0;i<20;i++)
{
zs_cData[i] = 0;//清空数组
}
}
TIM_Cmd(TIM2, DISABLE);//关闭定时器
zs_cData[zs_cTime] = USART_ReceiveData(USART1);//接收最近数据
zs_cTime++;
g_cUartData = zs_cData;//读取数据
TIM_SetCounter(TIM2,0);//重新装载Time2的值
TIM_Cmd(TIM2, ENABLE);//启动定时器
OSIntExit();//通知系统一个中断完成
}
TIM2初始化
void _init_Time2(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //0.0025s中断一次
TIM_TimeBaseStructure.TIM_Prescaler = 35999;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 5;//
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);
TIM_PrescalerConfig(TIM2, 35999, TIM_PSCReloadMode_Immediate);
TIM_Cmd(TIM2, DISABLE);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除中断标志位
TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE);//使能中断
}
TIM2中断
void TIM2_IRQHandler()
{
OSIntEnter();//通知系统进入了一个中断
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除中断标志位
TIM_Cmd(TIM2, DISABLE);//关闭定时器
g_Tset = 1;//中断标志位
OSMboxPost(Uart1Box,&g_Tset);//发送标志位
OSIntExit();//通知系统一个中断完成
}
4、CRC计算单元
unsigned char auchCRCHi[]={
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,
0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,
0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,
0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,
0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,
0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,
0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,
0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,
0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,
0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,
0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,
0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,
0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,
0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,
0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,
0x80,0x41,0x00,0xC1,0x81,0x40
};
unsigned char auchCRCLo[]={
0x00,0xC0,0xC1,0x01,0xC3,0x03,0x02,0xC2,0xC6,0x06,
0x07,0xC7,0x05,0xC5,0xC4,0x04,0xCC,0x0C,0x0D,0xCD,
0x0F,0xCF,0xCE,0x0E,0x0A,0xCA,0xCB,0x0B,0xC9,0x09,
0x08,0xC8,0xD8,0x18,0x19,0xD9,0x1B,0xDB,0xDA,0x1A,
0x1E,0xDE,0xDF,0x1F,0xDD,0x1D,0x1C,0xDC,0x14,0xD4,
0xD5,0x15,0xD7,0x17,0x16,0xD6,0xD2,0x12,0x13,0xD3,
0x11,0xD1,0xD0,0x10,0xF0,0x30,0x31,0xF1,0x33,0xF3,
0xF2,0x32,0x36,0xF6,0xF7,0x37,0xF5,0x35,0x34,0xF4,
0x3C,0xFC,0xFD,0x3D,0xFF,0x3F,0x3E,0xFE,0xFA,0x3A,
0x3B,0xFB,0x39,0xF9,0xF8,0x38,0x28,0xE8,0xE9,0x29,
0xEB,0x2B,0x2A,0xEA,0xEE,0x2E,0x2F,0xEF,0x2D,0xED,
0xEC,0x2C,0xE4,0x24,0x25,0xE5,0x27,0xE7,0xE6,0x26,
0x22,0xE2,0xE3,0x23,0xE1,0x21,0x20,0xE0,0xA0,0x60,
0x61,0xA1,0x63,0xA3,0xA2,0x62,0x66,0xA6,0xA7,0x67,
0xA5,0x65,0x64,0xA4,0x6C,0xAC,0xAD,0x6D,0xAF,0x6F,
0x6E,0xAE,0xAA,0x6A,0x6B,0xAB,0x69,0xA9,0xA8,0x68,
0x78,0xB8,0xB9,0x79,0xBB,0x7B,0x7A,0xBA,0xBE,0x7E,
0x7F,0xBF,0x7D,0xBD,0xBC,0x7C,0xB4,0x74,0x75,0xB5,
0x77,0xB7,0xB6,0x76,0x72,0xB2,0xB3,0x73,0xB1,0x71,
0x70,0xB0,0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,
0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54,0x9C,0x5C,
0x5D,0x9D,0x5F,0x9F,0x9E,0x5E,0x5A,0x9A,0x9B,0x5B,
0x99,0x59,0x58,0x98,0x88,0x48,0x49,0x89,0x4B,0x8B,
0x8A,0x4A,0x4E,0x8E,0x8F,0x4F,0x8D,0x4D,0x4C,0x8C,
0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,
0x43,0x83,0x41,0x81,0x80,0x40
};
//数据 长度 返回CRC
unsigned int crc16(unsigned char *puchMsg,unsigned int usDataLen)
{
unsigned char uchCRCHi=0xFF;
unsigned char uchCRCLo=0xFF;
unsigned long uIndex;
while(usDataLen--)
{
uIndex=uchCRCHi^*puchMsg++;
uchCRCHi=uchCRCLo^auchCRCHi[uIndex];
uchCRCLo=auchCRCLo[uIndex];
}
return(uchCRCHi<<8|uchCRCLo);
}
四、恒利源及PT100驱动电路
PT100是工业上常用的温度传感器,该传感器的主要特性就是在0度的时候PT100热敏电阻的阻值为100Ω并且线性相对比较好。
PT100温度-阻值变化曲线
PT100驱动电路方案一:采用单臂电桥的方式R1、R2、R3为电桥电阻,RL为热敏电阻,当环境温度为0度时,PT100表现出的阻值为100Ω,此时V1、V2之间电势差为0.。当环境温度变化,PT100阻值随之变化。而V1、V2也随PT100变化而表现出与PT100的阻值线性变化的电势差。
单臂电桥电路
该电路的优点是设计简易,但是缺点也非常明显要求R1、R2、R3的电阻精度比较高。故本项目采用了第二个方案。
PT100驱动电路方案二:采用恒流源的方式,如下电路图。电路中V0的左端是一个一阶积分电路,既单片机给一个精准频率为1KHz的PWM调制波形,在V0点形成一个基准电压。V0相当于恒压源,故V1=(V2-V0)/2。由虚短V1=V3。因为R3=R2所以V4=2V3=2V1=(V2-V0)。由虚短V2=V5。故Ir6=(V5-V4)/R6。IR6=[V2-(V2-V0)]/R6故IR6=V0/R6。
恒流源电路
通过以上恒流源电路,已知流过PT100的电流IR6,测量V5点的电压既可以得到PT100的阻值。
五、STM32内部ADC与DMA
由于需要保证整个系统的实时性,故不能使用查询法来读取ADC的数据,故使用内部DMA。代码如下(省略了RCC的开启)。
DAM初始化程序
void init_DMA()
{
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel1); //将DMA的通道1寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&AD_Value; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //内存作为数据传输的目的地
DMA_InitStructure.DMA_BufferSize = 3; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在循环缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道
DMA_Cmd(DMA1_Channel1, ENABLE);//打开 DMA
}
ADC初始化程序
void init_ADC()
{
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //设置为连续
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 3; //注意数字3,表示采集ADC1的三个通道
ADC_Init(ADC1, &ADC_InitStructure);
ADC_TempSensorVrefintCmd(ENABLE); //开启内部温度检测的ADC通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,1,ADC_SampleTime_239Cycles5); //注意这里的扫描顺序的RANK参数(即1、2、3)
ADC_RegularChannelConfig(ADC1,ADC_Channel_TempSensor,2,ADC_SampleTime_239Cycles5); //注意扫描周期的设置
ADC_RegularChannelConfig(ADC1,ADC_Channel_Vrefint,3,ADC_SampleTime_239Cycles5);
ADC_DMACmd(ADC1, ENABLE); //使能DMA
ADC_ExternalTrigConvCmd(ADC1, DISABLE);//失能外部触发
ADC_Cmd(ADC1, ENABLE);//使能ADC
ADC_ResetCalibration(ADC1);//重置校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1));//判断是否重置完成
ADC_StartCalibration(ADC1);//开始校准
while(ADC_GetCalibrationStatus(ADC1));//是否校准完成
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//
}
六、电机故障判断
故障判断分为两个方面,第一个方面就是当温度超过某一个设定值时给予报警,但是这样的报警系统会缺乏快速性,也就是说当被测量的物体已经出现故障一段时间之后引起的温度过高被检测到之后才给予报警。显然这样的判断方法是不可取的,或者只能作为辅助判断的标准。
为了提高系统判断的超前性我们需要去求得一段时间内的温度变化的一阶微分以及二阶微分。
假设下图为电机的温度变化曲线。
温度变化曲线
根据变化曲线求出该组数据的二阶导数
温度变化曲线的二阶导数
通过二阶导数不难看出温度变化曲线的弯曲反向,也不难看出此时电机存在堵转现象。
七、总结
该项目主要涉及了MODBUS通讯协议,以及STM32的ADC配合DMA实现多通道的数据采集。笔者能力有限,往大家多指教。