基于μC/OS-Ⅱ微差压变送器的设计
摘 要
随着工业的发展,越来越多的场合需要对液位、蒸汽、气体等微小压力的变化进行测量,因此有必要设计一款适应以上场合的微差压变送器。本文设计的检测装置主要用于检测环境的微差压,可进一步判断气体流动方向以及测量管道气体流量,然后将测量值转变成4~20mA电流信号输出。本装置可应用于医疗事业,例如保护性隔离、阻断空气中病菌传播、呼吸机压力检测等。
该系统以STM32作为中央处理器,采用SM5651作为压力传感器采集压力数据,采集的模拟量经过运放处理、AD采样、数字滤波后,通过RS485通信模块与PC机通信,将数据实时传输至PC端。支持标准的Modbus通信协议,可通过操作PC端进行Modbus通信地址的修改。同时本系统设计了人机交互界面,通过液晶屏可以实时显示数据,可以通过按键调整压力上限和下限,从而实现低压报警和高压报警功能;能够实时绘制压力随时间变化曲线,实时监控压力变化情况,方便工作人员对数据的观察与分析。
该检测装置创新之处在于采用μC/OS-Ⅱ操作系统,提高系统的实时性,设计了人性化的人机交互界面,可用于数据监控、曲线绘制等;设计了恒流源供电电路为SM5651芯片供电,同时为STM32设计了备用电源以防止断电时后备寄存器数据丢失以及RTC时钟错乱。本变送器具有体积小、适应性强、更加人性化等特点。
关键词:微差压变送器;Modbus通信协议;μC/OS-Ⅱ操作系统;人机交互
1 系统的总体方案
本检测装置采用μC/OS-Ⅱ操作系统,通过SM5651传感器对压力数据进行采集,主控单元对数据进行处理分析,通过D/A以及电压-电流转换模块输出标准的4~20mA电流。通过RS485通信模块将数据传输至PC端,支持标准的Modbus通信协议,可通过PC端修改从机Modbus通信地址。通过人机交互模块实时显示传感器采集的数据以及Modbus通信地址,用于实时监控数据的变化,绘制数据曲线、报警提示。系统的总体框图如图1所示。
2 功能需求分析
(1)环境微差压数据的采集、滤波以及数据显示;
(2)压力数据曲线实时绘制,并支持数据显示与图像显示一键切换功能;
(3)设定报警阈值,即超过设定的上限和下限值会触发报警机制,在显示屏上显示“高压报警”“低压报警”“正常运行”等信息,支持声光报警和消音报警一键切换功能;
(4)支持标准的modbus通信协议,可将数据实时传输至PC端,并可以利用modbus调试精灵修改从机设备的通信地址;
(5)编写时钟显示函数,可实时显示系统当前时间。
3 视频展示
基于μCOS-II微差压变送器的设计
4 程序实现
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "adc.h"
#include "beep.h"
#include "exti.h"
#include "rtc.h"
#include "timer.h"
#include "modbus_uart.h"
#include "modbus.h"
#include "dac.h"
#include "includes.h"
#include "stdbool.h"
/*0:压力值;
1:压力上限;
2:压力下限;
3:运行状态(1:正常,2:低压;3:高压);
4:时;
5:分;
6:秒;
7:本机ID*/
u16 Reg[30];
u16 set_up_limiting=1950; //压力上限
u16 set_down_limiting=100; //压力下限
u16 warn;
bool showimagestatus=false; //用于存放数据_图形界面切换标志
u16 P[54]; //用于存放压力历史数据
u16 tim_hour[40],tim_minute[40],tim_second[40]; //用于存放历史时间
//函数声明
void reg_send(void);
int warning(u8 x);
void cleartingle(u16 ms_time);
void limiting_setting(void);
//开始任务
#define Start_TASK_PRIO 8 //任务优先级
#define Start_STK_SIZE 128 //任务堆栈大小
OS_STK Start_TASK_STK[Start_STK_SIZE]; //任务堆栈
void Start_task(void *p_arg); //任务函数声明
//界面显示任务
#define Display_TASK_PRIO 4 //任务优先级
#define Display_STK_SIZE 128 //任务堆栈大小
OS_STK Display_TASK_STK[Display_STK_SIZE]; //任务堆栈
void Display_task(void *p_arg); //任务函数声明
//通信任务
#define Communication_TASK_PRIO 5 //任务优先级
#define Communication_STK_SIZE 128 //任务堆栈大小
OS_STK Communication_TASK_STK[Communication_STK_SIZE]; //任务堆栈
void Communication_task(void *p_arg); //任务函数声明
//报警任务
#define warning_TASK_PRIO 6 //任务优先级
#define warning_STK_SIZE 128 //任务堆栈大小
OS_TCB warning_TaskTCB; //任务控制块
OS_STK warning_TASK_STK[warning_STK_SIZE]; //任务堆栈
void warning_task(void *p_arg); //任务函数声明
//阈值修改任务
#define Limiting_change_TASK_PRIO 7 //任务优先级
#define Limiting_change_STK_SIZE 128 //任务堆栈大小
OS_STK Limiting_change_TASK_STK[Limiting_change_STK_SIZE]; //任务堆栈
void Limiting_change_task(void *p_arg); //任务函数声明
int main(void)
{
OS_CPU_SR cpu_sr=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
LCD_Init(); //LCD初始化
Adc_Init(); //ADC初始化
Dac1_Init(); //DAC初始化
KEY_Init(); //按键初始化
BEEP_Init(); //蜂鸣器初始化
EXTIX_Init(); //中断初始化
RTC_Init(); //RTC初始化
Timer2_Init(); //定时器初始化
Modbus_Init(); //modbus初始化
LED0=1;
LED1=1;
LED2=1;
BEEP=0;
modbus.myadd=1; //modus从机地址初始化为1
OSInit(); //UCOS初始化
OS_ENTER_CRITICAL(); //进入临界区(关闭中断)
//创建开始任务
OSTaskCreate(
Start_task, //任务函数名
(void*)0, //参数值地址
OS_STK*)&Start_TASK_STK[Start_STK_SIZE-1], //任务堆栈大小
Start_TASK_PRIO //任务优先级
);
OS_EXIT_CRITICAL(); //退出临界区(开中断)
OSStart(); //开始任务
}
//开始任务
void Start_task(void *pdata)
{
OS_CPU_SR cpu_sr=0; //临界区使用
OSStatInit(); //开启任务
OS_ENTER_CRITICAL(); //进入临界区(关闭中断)
//压力采集任务
OSTaskCreate(
Display_task, //任务函数名
(void*)0, //参数值地址
(OS_STK*)&Display_TASK_STK[Display_STK_SIZE-1], //任务堆栈大小
Display_TASK_PRIO //任务优先级
);
//通信任务
OSTaskCreate(
Communication_task, //任务函数名
(void*)0, //参数值地址
(OS_STK*)&Communication_TASK_STK[Communication_STK_SIZE-1], //任务堆栈大小
Communication_TASK_PRIO //任务优先级
);
//报警任务
OSTaskCreate(
warning_task, //任务函数名
(void*)0, //参数值地址
(OS_STK*)&warning_TASK_STK[warning_STK_SIZE-1], //任务堆栈大小
warning_TASK_PRIO //任务优先级
);
//阈值修改任务
OSTaskCreate(
Limiting_change_task, //任务函数名
(void*)0, //参数值地址
(OS_STK*)&Limiting_change_TASK_STK[Limiting_change_STK_SIZE-1], //任务堆栈大小
Limiting_change_TASK_PRIO //任务优先级
);
OSTaskSuspend(Start_TASK_PRIO); //挂起开始任务
OS_EXIT_CRITICAL(); //退出临界区(开中断)
}
//压力采集任务函数
void Display_task(void *p_arg)
{
u8 b;
u8 t=0;
u8 n=0;
u16 adcx,dacx;
u16 pressure,temp;
u16 electric;
u16 j=0,tim_counter=0;
p_arg = p_arg;
while(1)
{
if(KEY2)
{
cleartingle(10);
if(KEY2)
{
while(KEY2);//有此语句只能单次按,无此语句可以按一下连续增加
showimagestatus=!showimagestatus;
}
}
if(!showimagestatus)
{
OSTaskResume(7);
//标题,时间显示
LCD_Clear(WHITE);
BACK_COLOR=WHITE;
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(30,50,200,16,16,"Differential pressure");
LCD_ShowString(65,70,200,16,16,"transmitter");
LCD_ShowString(30,110,200,16,16," - - ");
LCD_ShowString(130,110,200,16,16," : : ");
//显示提示信息
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(30,130,200,16,16,"digit:");
LCD_ShowString(30,150,200,16,16,"Output: . mA");
LCD_ShowString(30,170,200,16,16,"pressure: Pa");
LCD_ShowString(30,190,200,16,16,"Up_limiting: Pa");
LCD_ShowString(30,210,200,16,16,"down_limiting: Pa");
LCD_ShowString(30,230,200,16,16,"ID:");
if(t!=calendar.sec)
{
POINT_COLOR=BLACK;
t=calendar.sec;
LCD_ShowNum(30,110,calendar.w_year,4,16);
LCD_ShowNum(70,110,calendar.w_month,2,16);
LCD_ShowNum(94,110,calendar.w_date,2,16);
LCD_ShowNum(130,110,calendar.hour,2,16);
LCD_ShowNum(154,110,calendar.min,2,16);
LCD_ShowNum(178,110,calendar.sec,2,16);
}
POINT_COLOR=BLACK; //设置字体为黑色
LCD_ShowxNum(156,190,set_up_limiting,4,16,0); //显示上限数值
LCD_ShowxNum(154,210,set_down_limiting,4,16,0); //显示下限数值
LCD_ShowxNum(154,230,modbus.myadd,4,16,0); //显示ID号
adcx=Get_Adc_Average(ADC_Channel_1,10); //获取ADC数字量
pressure=adcx;
DAC_SetChannel1Data(DAC_Align_12b_R,adcx);
dacx=DAC_GetDataOutputValue(DAC_Channel_1);
POINT_COLOR=BLACK; //设置字体为黑色
LCD_ShowxNum(156,130,dacx,4,16,0); //显示ADC/DAC的值
temp=(float)pressure*(2000.0/4095);
pressure=temp;
warn=temp;
LCD_ShowxNum(156,170,pressure,4,16,0); //显示差压值
Reg[0]=pressure;
temp=(float)(temp*16.0/2000+4);
electric=temp;
LCD_ShowxNum(150,150,electric,2,16,0); //显示电流值
temp-=electric;
temp*=1000;
LCD_ShowxNum(170,150,temp,2,16,0x80);
P[j]=pressure;
j++;
if(j==54) j=0;
tim_hour[tim_counter]=calendar.hour;
tim_minute[tim_counter]=calendar.min;
tim_second[tim_counter]=calendar.sec;
tim_counter++;
if(tim_counter==40) tim_counter=0;
cleartingle(500);
}
if(showimagestatus)
{
OSTaskSuspend (7);
LCD_Clear(LIGHTBLUE);
POINT_COLOR=BLUE;
BACK_COLOR=LIGHTBLUE;
LCD_ShowString(80,20,200,16,24,"Image");
LCD_ShowString(20,268,200,16,12,"0");
LCD_ShowString(10,66,200,16,12,"KPa");
LCD_DrawLine(30,280,240,280); //绘制横坐标
LCD_DrawLine(240,280,232,277); //箭头
LCD_DrawLine(240,280,232,283);
LCD_ShowString(25,285,220,12,12,"00:00:00 00:00:00 00:00:00 00:00:00");
LCD_DrawLine(70,280,70,277); //横坐标四个点
LCD_DrawLine(110,280,110,277);
LCD_DrawLine(150,280,150,277);
LCD_DrawLine(190,280,190,277);
LCD_DrawLine(30,280,30,80); //绘制纵坐标
LCD_DrawLine(30,80,27,88); //箭头
LCD_DrawLine(30,80,33,88);
LCD_DrawLine(30,230,33,230); //0.5
LCD_ShowString(5,224,200,16,12,"0.5");
LCD_DrawLine(30,180,33,180); //1.0
LCD_ShowString(5,174,200,16,12,"1.0");
LCD_DrawLine(30,130,33,130); //1.5
LCD_ShowString(5,124,200,16,12,"1.5");
LCD_ShowString(5,80,200,16,12,"2.0");
LCD_ShowString(205,60,200,16,12,"Pa");
//横坐标显示
LCD_ShowxNum(25,285,tim_hour[9],2,12,0x80);
LCD_ShowxNum(43,285,tim_minute[9],2,12,0x80);
LCD_ShowxNum(61,285,tim_second[9],2,12,0x80);
LCD_ShowxNum(79,285,tim_hour[19],2,12,0x80);
LCD_ShowxNum(97,285,tim_minute[19],2,12,0x80);
LCD_ShowxNum(115,285,tim_second[19],2,12,0x80);
LCD_ShowxNum(133,285,tim_hour[29],2,12,0x80);
LCD_ShowxNum(151,285,tim_minute[29],2,12,0x80);
LCD_ShowxNum(169,285,tim_second[29],2,12,0x80);
LCD_ShowxNum(187,285,tim_hour[39],2,12,0x80);
LCD_ShowxNum(205,285,tim_minute[39],2,12,0x80);
LCD_ShowxNum(223,285,tim_second[39],2,12,0x80);
adcx=Get_Adc_Average(ADC_Channel_1,10);//获取ADC数字量
pressure=adcx;
DAC_SetChannel1Data(DAC_Align_12b_R,adcx);
temp=(float)pressure*(2000.0/4095);
pressure=temp;
warn=temp;
POINT_COLOR=RED;
LCD_ShowxNum(175,60,pressure,4,12,0);//显示差压值
LCD_DrawRectangle(170,55,220,72);
for(n=0;n<54;n++)//绘制曲线
{
LCD_DrawLine(4*n+30,-0.1*P[n]+280,4*(n+1)+30,-0.1*P[n+1]+280);
}
for(b=1;b<54;b++)
{
P[53]=pressure;
P[b-1]=P[b];
}
for(b=1;b<40;b++)
{
tim_hour[39]=calendar.hour;
tim_hour[b-1]=tim_hour[b];
tim_minute[39]=calendar.min;
tim_minute[b-1]=tim_minute[b];
tim_second[39]=calendar.sec;
tim_second[b-1]=tim_second[b];
}
cleartingle(500);
}
OSTimeDlyHMSM(0,0,0,500); //延时500ms
}
}
//通信任务函数
void Communication_task(void *p_arg)
{
p_arg = p_arg;
while(1)
{
Mosbus_Event(); //处理MODbus数据
reg_send(); //数据收发
OSTimeDlyHMSM(0,0,0,500); //延时500ms
}
}
//报警任务
void warning_task(void *p_arg)
{
bool waring_status=true;
p_arg = p_arg;
while(1)
{
if(KEY0)
{
cleartingle(5);//延时去抖
if(KEY0)
{
while(KEY0);
waring_status=!waring_status;
}
}
if(waring_status)
{
Reg[3]=warning(1);//声光警报
}
else
{
Reg[3]=warning(0);//消音报警
}
OSTimeDlyHMSM(0,0,0,500); //延时500ms
}
}
//阈值修改任务
void Limiting_change_task(void *p_arg)
{
p_arg = p_arg;
while(1)
{
limiting_setting();
OSTimeDlyHMSM(0,0,0,500); //延时500ms
}
}
void cleartingle(u16 ms_time)
{
u16 i=0;
while(ms_time--)
{
i=12000; // 以ms为单位
while(i--) ;
}
}
void reg_send(void)
{
if(modbus.rcbuf[1]==6&&(modbus.rcbuf[2]*256+modbus.rcbuf[3]==1)) set_up_limiting=Reg[1]; //写压力上限
if(modbus.rcbuf[1]==3&&(modbus.rcbuf[2]*256+modbus.rcbuf[3]==1)) Reg[1]= set_up_limiting; //读压力上限
if(modbus.rcbuf[1]==6&&(modbus.rcbuf[2]*256+modbus.rcbuf[3]==2)) set_down_limiting=Reg[2]; //写压力下限
if(modbus.rcbuf[1]==3&&(modbus.rcbuf[2]*256+modbus.rcbuf[3]==2)) Reg[2]= set_down_limiting; //读压力下限
if(modbus.rcbuf[1]==6&&(modbus.rcbuf[2]*256+modbus.rcbuf[3]==7)) modbus.myadd=Reg[7]; //写ID
if(modbus.rcbuf[1]==3&&(modbus.rcbuf[2]*256+modbus.rcbuf[3]==7)) Reg[7]= modbus.myadd; //读ID
}
//x=1声光报警;x=0消音报警
//返回3:高压报警
//返回2:低压报警
//返回1:正常运行
//
int warning(u8 x)
{
if(warn>set_up_limiting)
{
LED0=0;//红亮
LED1=1;//绿灭
LED2=1;//黄灭
BEEP=x;
LCD_Fill(0,296,240,320,RED);
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,300,200,16,16,"Warning:HIGH");
return 3;
}
if(warn<100)
{
LED2=0;//黄亮
LED0=1;//红灭
LED1=1;//绿灭
BEEP=x;
LCD_Fill(0,296,240,320,RED);
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,300,200,16,16,"Warning:LOW");
return 2;
}
if(warn>100&&warn<set_up_limiting)
{
LED1=0;//绿亮
LED0=1;//红灭
LED2=1;//黄亮
BEEP=0;
LCD_Fill(0,296,240,320,GREEN);
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,300,200,16,16,"Runing...");
return 1;
}
return 0;
}
//上下限设置函数
void limiting_setting(void)
{
//上限加
if(KEY1)
{
cleartingle(10);//延时去抖
if(KEY1)
{
//while(KEY1);//有此语句只能单次按,无此语句可以按一下连续增加
set_up_limiting+=10;
LCD_ShowxNum(156,190,set_up_limiting,4,16,0);
}
}
//上限减
if(KEY3)
{
cleartingle(10);//延时去抖
if(KEY3)
{
//while(!KEY3);//有此语句只能单次按,无此语句可以按一下连续增加
set_up_limiting-=10;
LCD_ShowxNum(156,190,set_up_limiting,4,16,0);
}
}
}
如需全部资料可加qq798860621