• 热释电矢量传感器设计


     1  概述

    使用4个热释电传感器组成一个2X2的矩阵。通过曲线的相位差、 峰峰值等特征量来计算相关信息。本文使用STM32单片机设计、制作了热释电传感器矩阵;使用C#.NET设计了上位机软件。为以上研究做了试验平台。

    2 硬件电路设计

    2.1  热释电传感器介绍

        热释电红外探测器的信号产生原理主要是利用热释电材料的热释电效应。热释电材料是一种压电材料,是不具有中心对称性的晶体(如钽酸锂、硫酸三甘肽等),当这类晶体发生温度变化时使得晶体内部的原子位置变化,导致了晶体某一方向产生表面极化电荷的现象,会使晶体两端产生一个微小的电压差。如图2.1 示意了热释电效应的基本原理。如果当温度保持不变的时候,该电压会因为与空气中离散的荷中和而消失掉,所以只有温度场变化时热释电传感器才会敏感变化。

    图2.1热释电效应

    当热释电材料表面受到经菲涅尔透镜调制过的人体红外辐射照射后,晶体表面温度便会上升𝛥𝑇,使垂直于自发极化轴方向的晶体单位表面上的电荷𝛥𝑞发生自极化,晶体的电容值表示为𝐶,两电极之间的电压便可表示为𝛥𝑉 = 𝛥𝑞/𝐶。通过检测这一变化微弱的电压信号可以获取温度场变化趋势。本文采用D203s型号传感器,其等效电路如下图所示。当移动人体穿过其感应区时,热释电传感器输出信号反应了各热释电元件接收热辐射的通量变化量之差。连续时刻,当两片热释电元件接收的热辐射量差值不同时,便会产生一个变化的电压输出信号。

    D203s等效电路

    当人体沿一定方向、一定距离移动时将产生电压输出信号。图中可以看出,热释电探头的两片热释电元件分别对应两个柱形视场区域并且将其感应视场划分为盲区和高敏感区。当人体穿过其中一个敏感区域时,便会对相应的热释电元件产生一个阶跃输入,从而通过后续电路产生热释电压;而当人体穿过两敏感区的中间盲区时,由于人体红外能不能辐射到热释电元件,导致不会产生对应的输出电压。因此可以根据传感器输出时域电压信号序列,寻找一些可用特征以实现人体的检测、定位与识别。其中传感器输出信号与移动速度、距离等之间的关系如下所示:

    其中 f 为输出信号的频率(HZ),Vb 为目标移动速度(m/s),fb为菲涅尔透镜

    焦距(mm), s 为敏感元件大小(mm), L为目标距传感器距离(m)。

    2.2 工作原理示意图

    2.2 采集电路设计

    根据 PIR 传感器的工作原理,当人体走过或者在菲涅尔透镜明暗相间的区域运动时,会使热释电元件表面上产生微弱的电流,然后由电路转换为电压信号输出,这个电压信号的幅值非常小,大概在 1mV 左右。信号幅值太小会导致容易受到外界环境的干扰,这样采集的数据就没有分析的意义。为了减小实验误差增加数据可靠性,设计了如下图所示的信号调理电路。

    图2.3信号调理电路

       传感器在供电时需要10K限流电阻,输出需要47K的匹配电阻。在经过RC滤波电路后,经过两级放大电路放大,本文采用LM358运算放大器组成放大电路。一级放大和二级放大电路间采用电容耦合,这样可以去掉传感器输出的直流分量。第二级放大电路倍数可调,通过测试选取合适放大倍数。第二级放大电路参考电压设置在1.65V,主要是因为后去ADC为0~3.3V输入。通过信号调理电路可以得到0~3.3V的电压信号,方便ADC转换。

    2.3单片机电路

    采用DC5V供电,电路内需要供电的芯片有3.3V和5V两种。其中D203S及其信号处理部分、蜂鸣器采用5V供电,单片机、通信芯片等采用3.3V供电。为方便操作电源输如后增加自锁电源开关,开关后设计电容滤波来提高5V电压的精度、减少文波干扰。3.3V电源使用lm1117-3.3V电源芯片,该芯片可提供500ma的稳定电流,足够系统使用。如图2.4(a)为电源电路部分。设计电源指示灯一个,用于检测3.3V电源工作与否。

    单片机最小系统如图2.4(b)所示,包括了晶振电路、boot电路、以及ADC电源部分。同时设计了复位电路,通过电阻、电容的充放电时间来实现单片机的复位过程。为了方便观测程序的运行与否设计了可控LED指示灯一个,通过定时器控制闪烁提示使用者系统健康状况。单片机采用SW调试方式,该方式仅需要4跟线就可完成程序的烧写、在线调试等功能。设计了4个独立按键,用于按键操作。同时设计了一个有源蜂鸣器电路,用于产生提示、告警声音。

    (a)电源电路

    (b)单片机电路

    (c)外围电路

    (d)按键与蜂鸣器

    图2.4 单片机系统电路设计

      2.4串行通信电路

    串行通信指数据是一位一位按顺序传送的通信方式,相对于并行通信,其突出优点是只需要一对传输线,缺点是传输速度较低。串行通信的传输方向通常有三种,单工、半双工和全双工。单向顾名思义只允许数据向一个方向传输;半双工允许两个方向传输,但在同一时刻只能往一个方向传输;全双工则允许双向同时传输数据。

    串行通信有两种基本通信方式,异步通信和同步通信。异步通信不需要传输时钟信号,通信双方的时钟是各自独立的,但双方必须是相同的通信速率和数据格式,即相同的通信协议。其帧格式一般为:起始位+数据位+奇偶校验位+停止位。同步通信需要时钟同步,双方的数据收发是同时进行的。同步通信中一般字符与字符之间没有间隙,相对于异步通信不需要起始位和停止位,只需要同步信号和同步开始时的同步字符。波特率也称码元速率,为单位时间内传输符号的个数或载波调制状态改变次数,单位是Bd(Baud)。比特率又称码率,表示单位时间内传输的比特数,单位是bps(bit/s)。比特率和波特率的转换关系与信号的调制方式有关,通常为:比特率=波特率*调制位数(调制位数即码元位数)。在异步串行通信中,由于采用二进制编码,码元位数为一,通常情况下比特率=波特率。

    串行异步通信

    RS-232是美国电子工业联盟制定的串行数据通信接口标准,原始编号全称是EIA-RS-232(简称RS232),它被广泛用于DCE(Data Communication Equipment ) 和 DTE(Data Terminal Equipment) 之间的连接。DCE可以理解为数据通信端,比如modem设备;DTE可以理解为数据终端,比如电脑。最早的台式电脑都会保留9针的232接口,用于串口通信,目前基本被USB接口取代。现在RS232接口常用于仪器仪表设备,PLC以及嵌入式领域当作调试口来使用。图2.5给出了本文设计的RS232接口电路,为了调试方便保留了TTL电平接口(P2)。

    图2.5通信电路

    PCB设计

    3  软件设计

    软件由传感器系统软件和上位机软件组成,两个软件间通过异步串行通信进行数据交互。

    3.1单片机软件设计

    单片机软件架构如下图所示:包括驱动层,数据层和逻辑控制层。(具体源代码参见附录 单片机程序源码)

    单片机软件设计流程如下图所示。首先初始化,然后通过定时器定时循环,采集4个热释电传感器数据,然后通过串口发送给上位机。同时预留了4个按键和一个蜂鸣器用于后续工作储备。

    主函数代码,其它详见附录。

    1. // 主函数 程序从这里开始
    2. int main(void)
    3. {
    4. u8 i=0,k=0;
    5. u8 sum;//发送数据的校验
    6. //串口有关变量
    7. u8 number=0;
    8. u8 State1=0x7F;
    9. u8 State2=0x0F;
    10. /*通信状态有关变量*/
    11. //接收标志
    12. u8 nGotTDZHT=0;
    13. u16 nBufTD=0;
    14. u8 nGotTDZHTBY=0;
    15. u16 nBufTDBY=0;
    16. //主控计算机接收标志
    17. u8 nGotMPC=0;
    18. u16 nBufMPC=0;
    19. //串口2的一些变量初始化
    20. Inform_OUTPUT.DATA.Frame_Head1=0xAA;
    21. Inform_OUTPUT.DATA.LEN=14;
    22. Inform_OUTPUT.DATA.state1=State1;
    23. Inform_OUTPUT.DATA.state2=State2;
    24. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    25. delay_init(168);
    26. LED_Init();//LED初始化
    27. // 定时器初始化
    28. TIM3_Int_Init(1000-1,168-1);
    29. RS422_Init(115200);//初始化串口2用于连接工控机
    30. DMA1_Config(DMA1_Stream6,DMA_Channel_4,(u32)&USART2->DR,(u32)BUF2,SENTDATA_LEN);//串口2DMA初始化
    31. Adc_Init();
    32. delay_us(2000);
    33. delay_ms(100);
    34. Beep=0;//关闭beep
    35. while(1)
    36. {
    37. //接收到工控机数据(预留)
    38. if(New_PC==1)
    39. {
    40. New_PC=0;
    41. nGotMPC=5;
    42. nBufMPC++;
    43. State2=State2|0X01;
    44. }
    45. if(TIME_on==1) //控制周期时间到,进行ADC和数据发送
    46. {
    47. TIME_on=0;
    48. //接收MIPC监控
    49. if(nGotMPC>0)
    50. {
    51. nGotMPC--;
    52. State2=State2|0X01;
    53. }
    54. else
    55. {
    56. State2=State2&0XFC;
    57. }
    58. // 读取ADC获得传感器采样值
    59. getAdcValues();
    60. i=i+1;
    61. if(i==50) //控制D1亮
    62. {
    63. i=0;
    64. LED1=!LED1;
    65. }
    66. //串口2,给工控机发送数据
    67. Inform_OUTPUT.DATA.Sonser1Value=AdcValue[0];
    68. Inform_OUTPUT.DATA.Sonser2Value=AdcValue[1];
    69. Inform_OUTPUT.DATA.Sonser3Value=AdcValue[2];
    70. Inform_OUTPUT.DATA.Sonser4Value=AdcValue[3];
    71. number++;
    72. number=number%16;
    73. Inform_OUTPUT.DATA.number=number<<4;
    74. sum=0;
    75. for(k=1;k//计算校验和
    76. {
    77. if(k==3) continue;
    78. sum += Inform_OUTPUT.ARRAY[k];
    79. }
    80. Inform_OUTPUT.DATA.checksum=sum;
    81. // 发送数据
    82. memcpy(BUF2,&Inform_OUTPUT,SENTDATA_LEN);
    83. DMA_ClearFlag(DMA1_Stream6, DMA_FLAG_TCIF6);//
    84. MYDMA_TxEnable(DMA1_Stream6,SENTDATA_LEN,USART2);
    85. }
    86. else
    87. {
    88. }
    89. }
    90. }

    3.2  上位机软件设计

    C#是一种高级编程语言,当今主流开发语言占有率排名前五,该语言由微软2000年推出、面世,被微软誉为.NET平台最佳编程语言。C#以.NET类库为基础,是兼顾系统开发与应用程序开发的最佳语言。继承了C/C++简明的编程语法,封装了大多数C++类库。

    C#.NET可以编写四类应用程序:

    (1)桌面应用程序,主要包括Winform和WPF。二者都是优秀的桌面解决方案首选。Winform的使用者居多,主要因为Winform控件较丰富,其他语言也都用相应的winform开发环境,转行C#后选择Winform入手较快。

    (2)Windows Store 应用程序。主要应用于移动、触摸设备的开发。

    (3)Web应用程序。通过Web浏览器可以查看的Web页面,如ASP.NET。

        (4)WCF服务。一种灵活的分布式应用程序解决方案,可以通过局域网、广域网在各种操作系统间传递任何数据。

    本文将使用Visual Studio 2010和C#语言开发一款基于.NET的winform应用程序。根据系统需求本文将针对应用程序在以下几个方面展开研究:

    (1)用户系统

    (2)可视化界面

    (3)通信系统

    (4)数据库系统

    应用程序主题框架如下图所示:

    考虑到程序的运行效率,将采用多线程方案。在windows系统中每一个应用程序都是一个独立的进程,进程之间不能共享资源。和进程类似,每一个进程中,独立运行的程序片段叫做线程,线程间可以共享本进程中的公共资源。每个线程间是并列关系,在CPU资源充足的情况下,他们是并行运行的。

    多线程可以提高应用程序的响应。特别是复杂的图形界面,如果应用程序在等待某一资源,不会按时处理图形界面,这将导致图形界面出现卡顿。如果此时采用多线程技术,将等待处理交给后台线程处理,这样等待时间过长时,就不会影响图形界面的更新。

    C#通过 System.Threading.Thread类实现线程的创建和管理。创建一个线程:

        Thread WriteLogThread = new Thread(new ThreadStart(WritingLog));

    WriteLogThread.Start();

        其中线程名 WriteLogThread,线程函数 WritingLog。WriteLogThread.Start()将开启线程,线程启动后将调用WritingLog()函数。我将在此函数中写线程要执行的程序。具体使用请见CSDN Thread类。

    3.2.1 曲线图界面

    GDI全称Graphics Device Interface(图形设备接口),主要负责Window系统上的图形和图像输出,开发人员利用其后续版本GDI+内置函数可以方便在设备处理和动态显示图形图像。GDI+提供的主要功能包含二维几何图形的处理、图形图像显示、排版等。System. Drawing. dll程序集中就已经封装了GDI+的功能,在开始使用GDI+类时,需在项目工程中添加System. Drawing. dll的引用,通过GDI+技术程序员可以编写与图形设备进行交互的Windows或Web图形应用程序,应用程序与图形设备之间不能直接通信,而GDI+库的作用就是将程序发送的数据转换并传递给设备。

    通过GDI+提供的API函数可以实现曲线图的绘制。本文使用GDI+、checkbox等控件实现了曲线图绘制。如下图所示,具有多种颜色的曲线图,曲线图上部为曲线名称(图例),向下有四行数字分别是鼠标当前值,圈取区间最大、最小以及平均值。改曲线图可以完好的显示出实时变化的传感器曲线和各项计算结果,有助于传感器数据的理解与应用。

    3.2.2通信程序

    软件通过SerialPort实现异步串行通讯。

    由于串口不知道数据何时到达,因此有线程实时读串口和事件触发方式两种串口数据的读取方法。实质事件方式也是在一个独立线程中处理的。

    串口的初始化如下:

    建立串口处理事件:

    1. serialPort1.DataReceived +=
    2.  new SerialDataReceivedEventHandler(this.serialPort1_DataReceived);

    开启串口:

    1. serialPort1.PortName = toolStripComboBox1.Text;
    2.      serialPort1.BaudRate = 115200;
    3. serialPort1.Open();

    串口接收处理:通过接收中断处理数据,中断函数如下所示,其中数据处理也在其中。

       

    1. private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
    2.         {
    3.             int i=0;
    4.             if(portOpen)
    5.             nbyte2rec = serialPort1.BytesToRead;
    6.             while (nbyte2rec >= 18&&portOpen)
    7.             {
    8.                 int res = serialPort1.ReadByte();
    9.                 if (res == 0xAA)
    10.                 {
    11.                     nRecBuf++;
    12.                     if (nBuf < 15) nBuf++; else nBuf = 0;
    13.                     //recbuffer[0] = 0xAA;
    14.                     serialPort1.Read(recbuffer, 1, 18);
    15.                     // byte[] cheak = BytesCheck.GetCRC16ByPoly(recbuffer,0x0000, 0x8005, 0x0000,false,false,true, 1, 16);//0x8005
    16.                     sum = recbuffer[1] + recbuffer[2];
    17.                     for ( i = 4; i < 18; i++)
    18.                     {
    19.                         sum += recbuffer[i];
    20.                     }
    21.                     sum &= 0xFF;
    22.                     if (sum != recbuffer[3])
    23.                     {
    24.                         nErrBuf++;
    25.                     }
    26.                     this.nGot = 10;
    27.                     _nBuf = recbuffer[2] >> 4;
    28.                     if (frist)
    29.                     {
    30.                         frist = false;
    31.                         RecFirstTime = DateTime.Now;
    32.                         nBuf = _nBuf;
    33.                     }
    34.                     else
    35.                     {
    36.                         if (nBuf != _nBuf)
    37.                         {
    38.                             if (_nBuf - nBuf < 0)
    39.                             {
    40.                                 nLoseBuf += _nBuf - nBuf + 16;
    41.                             }
    42.                             else
    43.                             {
    44.                                 nLoseBuf += _nBuf - nBuf;
    45.                             }
    46.                             nBuf = _nBuf;
    47.                         }
    48.                     }
    49.                     this.DataLen = recbuffer[1];
    50.                     nGroup = recbuffer[2] & 0x0F;
    51.                     if (nGroup == 0)
    52.                     {
    53.                         //Sovalue[0] = BitConverter.ToUInt16(recbuffer, 4);
    54.                         //Sovalue[1] = BitConverter.ToUInt16(recbuffer, 6);
    55.                         //Sovalue[2] = BitConverter.ToUInt16(recbuffer, 8);
    56.                         //Sovalue[3] = BitConverter.ToUInt16(recbuffer, 10);
    57.                         RunTim=(DateTime.Now-StartTime).TotalSeconds;
    58.                         for (i = 0; i < 4; i++)
    59.                         {
    60.                             Sovalue[i] =Math.Min(4095, (int)BitConverter.ToUInt16(recbuffer, 4+i*2));
    61.                            
    62.                         }
    63.                         RDSoValues[0] = 0.05F * Kso[0] * (Sovalue[1] - D0so[0]) + 0.95F * RDSoValues[0];
    64.                         RDSsum[0] = RDSoValues[0];
    65.                         RDSoValues[1] = 0.05F * Kso[1] * (Sovalue[0] - D0so[1]) + 0.95F * RDSoValues[1];
    66.                         RDSsum[1] = RDSoValues[1];
    67.                         RDSoValues[2] = 0.05F * Kso[2] * (Sovalue[2] - D0so[2]) + 0.95F * RDSoValues[2];
    68.                         RDSsum[2] = RDSoValues[2];
    69.                         RDSoValues[3] = 0.05F * Kso[3] * (Sovalue[3] - D0so[3]) + 0.95F * RDSoValues[3];
    70.                         RDSsum[3] = RDSoValues[3];
    71.                         coudis(RDSoValues);
    72.                         //dis
    73.                         drawlinesControl1.Lines[0].Add(RDSoValues[0]);
    74.                         drawlinesControl1.Lines[1].Add(vuVpp[0]);
    75.                         drawlinesControl1.Lines[2].Add(dis[0]*1000);
    76.                         drawlinesControl1.Lines[3].Add(RDSoValues[1]);
    77.                         drawlinesControl1.Lines[4].Add(vuVpp[1]);
    78.                         drawlinesControl1.Lines[5].Add(dis[1] * 1000);
    79.                         drawlinesControl1.Lines[6].Add(RDSoValues[2]);
    80.                         drawlinesControl1.Lines[7].Add(vuVpp[2]);
    81.                         drawlinesControl1.Lines[8].Add(dis[2] * 1000);
    82.                         drawlinesControl1.Lines[9].Add(RDSoValues[3]);
    83.                         drawlinesControl1.Lines[10].Add(vuVpp[3]);
    84.                         drawlinesControl1.Lines[11].Add(dis[3] * 1000);
    85.                         //_list_V1.Add(RunTim, RDSoValues[1]);
    86.                         //_list_V2.Add(RunTim, RDSoValues[0]);
    87.                         //_list_V3.Add(RunTim, RDSoValues[2]);
    88.                         //_list_V4.Add(RunTim, RDSoValues[3]);
    89.                     }
    90.                 }
    91.                 nbyte2rec = serialPort1.BytesToRead;
    92.             }
    93.         }

    3.2.3定时器与界面刷新

    上位机与下位机一样,需要定时器定时刷新、更新界面显示。

    初始化定时器时间,单位毫秒;

    timer1.Interval = 100;

    启动定时器

    timer1.Start();

    定时中断函数,函数内为界面刷新命令。

    1. private void timer1_Tick(object sender, EventArgs e)
    2.         {
    3. //数据显示刷新
    4.             label_S1.Text = dis[0].ToString("F2")+" m";
    5.             label_S2.Text = dis[1].ToString("F2") + " m";
    6.             label_S3.Text = dis[2].ToString("F2") + " m";
    7.             label_S4.Text = dis[3].ToString("F2") + " m";
    8.             drawlinesControl1._Refresh();//绘图控件刷新
    9.         }

    附录I

    电路设计原理图和PCB:https://download.csdn.net/download/w267309080/88298439

    单片机软件源码:https://download.csdn.net/download/w267309080/88298418

    上位机软件源码:https://download.csdn.net/download/w267309080/88298400

  • 相关阅读:
    VCL界面组件DevExpress VCL v22.1 - 发布全新的Shell组件
    10 种保证接口数据安全的方案!
    redis学习七redis的集群:主从复制、CAP、PAXOS、Cluster分片集群(二)
    PHP Hyperf框架 RPC调用内存泄露
    云原生之快速使用Nacos Spring Cloud
    【MySQL】用户管理
    MATLAB车牌是如何精准定位的???!!!【深度好文】
    教你使用Docker部署node项目
    Spring中的IOC控制Mybatis案例
    【餐厅点餐平台|三】模块设计
  • 原文地址:https://blog.csdn.net/w267309080/article/details/132660968