持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
上一节,我们已经实现了硬件的连接,已经通过常见的串口工具,实现了对硬件连接的测试,那么接下来。我们就要实现一个小的winfrom,来读取温湿度变送器的数据。
- 打开vs,新建一个winform项目
起名做thsensor的项目
使用.net5开发
默认是由一个helloword窗口,如图,点击运行,会自动弹出helloworld窗口
- 写一个串口操作类,用来通过USB转485模块,使用485通讯 右键项目名称,选择添加,添加一个新项,选择类,名字为comport,具体实现如下,注意namespace使用thsensor(注意,net5.0 需要安装nuget扩展包System.IO.Ports)
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace THSensor
{
internal class comport
{
/// <summary>
/// 串口操作函数
/// </summary>
public static class ComPort
{
/// <summary>
/// 打开串口
/// </summary>
/// <param name="ComName">串口名称</param>
/// <param name="baudrate">波特率</param>
/// <returns></returns>
public static SerialPort V_OpenPort8N1(String ComName, int baudrate)
{
try
{
//初始化串口对象
SerialPort port = new SerialPort(ComName);
//波特率
port.BaudRate = baudrate;
//奇偶校验
port.Parity = Parity.None;
//停止位
port.StopBits = StopBits.One;
//数据位
port.DataBits = 8;
//流控
port.Handshake = Handshake.None;
//打开发送
port.RtsEnable = true;
//打开串口
port.Open();
return port;
}
catch (Exception e)
{
Console.WriteLine("串口-- {0} -- 初始化失败" + e.Message);
return null;
}
}
/// <summary>
/// 关闭串口
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public static bool ClosePort(SerialPort port)
{
if (port.IsOpen)
port.Close();
return true;
}
/// <summary>
/// 扫描串口号
/// </summary>
/// <param name="debug">日志输出</param>
/// <returns>返回串口个数</returns>
public static string[] V_ScanPort(bool debug = false)
{
string[] Serial_Name = SerialPort.GetPortNames();
if (debug)
{
if (Serial_Name.Length > 0)
{
Console.WriteLine(string.Format("获取到{0}个串口", Serial_Name.Length));
foreach (string str in Serial_Name)
{
Console.WriteLine(str);
}
}
}
return Serial_Name;
}
}
}
新建一个类,叫做thsensor.cs,实现温湿度的指令下发和解析
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using System.Threading;
namespace THSensor
{
/// <summary>
/// 温湿度传感器,默认地址001
/// </summary>
internal class THSensor
{
/// <summary>
/// 读取温湿度值
/// </summary>
/// <param name="addr">地址</param>
/// <param name="port">端口</param>
/// <returns></returns>
public static string ReadTHDataFromSensor(SerialPort port, byte addr, bool debug = false)
{
if (port.IsOpen)
{
byte[] recdata = new byte[16];
byte[] sendata = new byte[16];
//addr
sendata[0] = addr;
//cmd
sendata[1] = 0x03;
//reg addr
sendata[2] = 0x00;
sendata[3] = 0x00;
//data len
sendata[4] = 0x00;
sendata[5] = 0x02;
//crc
byte[] crc = new byte[2];
crc = VTool.CRCCalc(sendata, 6);
sendata[6] = crc[0];
sendata[7] = crc[1];
//发送
port.Write(sendata, 0, 8);
//等待
Thread.Sleep(50);
//读取
if (port.BytesToRead > 0)
{
int RecvLen = port.BytesToRead;
//将数据按照字节依次读取
for (int i = 0; i < RecvLen; i++)
{
recdata[i] = (byte)port.ReadByte();
}
//crc
crc = VTool.CRCCalc(recdata, RecvLen - 2);
if (recdata[RecvLen - 2] == crc[0] &&
recdata[RecvLen - 1] == crc[1])
{
if (debug)
Console.WriteLine($"数据:{VTool.HexToString(recdata, RecvLen)}");
return PraseData(recdata, debug);
}
}
}
return null;
}
/// <summary>
/// 解析数据
/// </summary>
/// <param name="databuf"></param>
/// <returns></returns>
private static string PraseData(byte[] databuf, bool debug)
{
double currentemp;
double currenthumi;
//湿度
char temp = (char)((databuf[3] << 8) + databuf[4]);
currenthumi = temp / 10.0;
//温度
temp = (char)((databuf[5] << 8) + databuf[6]);
//零下温度
if (temp > 0xFF00)
{
temp = (char)(temp - 1);
temp = (char)~temp;
currentemp = temp / 10.0;
if (debug)
Console.WriteLine($"温度:-{currentemp} 湿度:{currenthumi}");
return $"-{currentemp}&{currenthumi}";
}
else
{
currentemp = temp / 10.0;
if (debug)
Console.WriteLine($"温度:{currentemp} 湿度:{currenthumi}");
return $"{currentemp}&{currenthumi}";
}
}
}
}
实现一个工具类,vtool.cs 包含一些常用的工具函数
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace THSensor
{
/// <summary>
/// 工具类
/// </summary>
internal static class VTool
{
/// <summary>
/// hex byte数组转string
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string HexToString(byte[] data, int len)
{
StringBuilder ret = new StringBuilder();
for (int loop = 0; loop < len; loop++)
{
//{0:X2} 大写
ret.AppendFormat(" {0:X2}", data[loop]);
}
return ret.ToString();
}
/// <summary>
/// CRC 16校验
/// </summary>
/// <param name="crcbuf">校验数据</param>
/// <param name="datalen">有效数据长度</param>
/// <returns>高低8位</returns>
public static byte[] CRCCalc(Byte[] crcbuf, int datalen)
{
//计算并填写CRC校验码
int crc = 0xffff;
byte[] ret = new byte[2];
for (int n = 0; n < datalen; n++)
{
byte i;
crc = crc ^ crcbuf[n];
for (i = 0; i < 8; i++)
{
int TT;
TT = crc & 1;
crc = crc >> 1;
crc = crc & 0x7fff;
if (TT == 1)
{
crc = crc ^ 0xa001;
}
crc = crc & 0xffff;
}
}
ret[0] = (byte)((crc & 0xff));
ret[1] = (byte)((crc >> 8) & 0xff);
return ret;
}
}
}
至此,我们基本的操作类和工具类已经实现完毕,接下来就是做界面了。 界面的制作我们下一节细讲