网站定制价格,山东网站开发制作,外贸电商网站开发,各省网站备案时长一、简介 该仪表可以实时显示位移传感器的测量值#xff0c;并可设定阈值等。先谈谈简单的使用方法#xff0c;通过说明书#xff0c;我们可以知道长按SET键可以进入参数选择状态#xff0c;按“↑”“↓”可以选择该组参数的上一个或者下一个参数。 从参数一览中可以看到有…一、简介 该仪表可以实时显示位移传感器的测量值并可设定阈值等。先谈谈简单的使用方法通过说明书我们可以知道长按SET键可以进入参数选择状态按“↑”“↓”可以选择该组参数的上一个或者下一个参数。 从参数一览中可以看到有不同组的参数当我们第一次进入参数选择状态时会进入第一组参数可以设置不同的阈值。只不过由于是数码管显示字母时会用一些比较奇怪的表达比如“5”其实就是“S”可以通过对照参数表获取不同字母的显示。 如果想进入其他组参数可以在第一组参数中通过“↑”或“↓”找到最后一个oA然后按“←”开始设置参数当把4位数码管都设为1即输入密码1111后再按下SET确定就可以解锁密码了。此时可以通过长按SET键切换到其他组参数。 更多功能可自行查看数据手册不过要注意的是说明书中的参数并非全部与实际仪器一一对应实际仪器有时会缺少一两个参数。 二、串口使用 串口使用的是RS485电平或者RS232千万要注意的就是A、B的接线不要接错。这个一般需要按照说明书的来说明书上说“7”对应的是“B”“8”对应的是“A”如果仪器上贴着的标签是相反的那么可以先按照说明书上的接法如果不行再按照仪器上的不要忘记接地。 接下来说说具体的串口通信仪器默认波特率是9600通信协议是Modbus-RTU。这里推荐使用Modbus-RTU协议。 这个通信过程并非是仪表主动不断地发送测量数据给上位机而是需要你先发送相应命令给仪器然后接收仪器数据。使用过程中倒是发现一些与说明书不同吻合的地方比如读取测量值这一步按理来说应答应如下但实际过程中接收的是“01 04 04 42 47 3F 3F 3F 28”即多了一个04 不过当我们需要连续读取时就会发现单次发送实在是麻烦现在可以有下面几种方法连续发下 1使用llcom 这个串口助手可以写lua脚本以实现自动发送数据并读取数据保存 chenxuuu/llcom: 功能强大的串口工具。支持Lua自动化处理、串口调试、WinUSB、串口曲线、TCP测试、MQTT测试、编码转换、乱码恢复等功能 (github.com) 下面是可以发送命令并把读取数据保存起来的lua脚本。只不过lua脚本很难运行什么GUI自然就无法显示图表 -- 发送数据中间间隔时间单位ms
local sendDelay 100-- 生成16进制数据“01 04 0000 0002 71CB”
local usartData 01040000000271CB
usartDatausartData:fromHex()-- 获取当前日期和时间
local function get_current_datetime()local datetime os.date(%Y%m%d_%H%M%S)return datetime
end-- 生成带有日期和时间的文件名
local function generate_filename()local datetime get_current_datetime()return D:/Script/python/expr_com/data_log/log_ .. datetime .. .txt
end-- 打开文件如果文件不存在则创建如果存在则覆盖
local filePath generate_filename()
local file, err io.open(filePath, w)
if not file then-- 如果文件打开失败输出错误信息print(无法打开文件: .. err)
elseprint(文件成功打开: .. filePath)
endlocal value_str -- 发送数据的函数
apiSetCb(uart, function(data)-- 写入数据value_str data:toHex():sub(7, 14)file:write(value_str .. \n) -- 添加换行符以便区分不同数据print(value_str)
end)-- 循环发送任务
sys.taskInit(function()while true do-- 发送数据apiSendUartData(usartData)sys.wait(sendDelay)end
end)-- 确保在脚本结束时关闭文件
--[[
atexit(function()if file thenfile:close()print(文件已关闭)end
end)
]]2使用Python脚本 使用Python脚本直接打开串口然后发送命令并读取数据需要注意的是下面脚本里指定了一个串口你需要打开设备管理器来找到实际串口并修改脚本里的串口为实际串口号。同时注意波特率设置。 由于仪器发送的其实是浮点数据的实际表达所以下面脚本就自动做了这个转换 可惜的是使用Python后无法很好地实时更新数据到图表中下面也就没有添加这个功能。 import serial
import struct
import time
import os
from datetime import datetime# 配置串口
ser serial.Serial(portCOM7, # 根据实际情况修改端口号baudrate115200, # 波特率timeout1 # 超时设置
)# 要发送的16进制数据
send_data bytes.fromhex(01040000000271CB)# 确保 data_log 目录存在
log_dir data_log
os.makedirs(log_dir, exist_okTrue)# 获取当前日期和时间
current_time datetime.now().strftime(%Y%m%d_%H%M%S)
log_file_path os.path.join(log_dir, flog_{current_time}.csv)# 打开日志文件
with open(log_file_path, w) as log_file:log_file.write(Timestamp,Value\n) # 写入表头try:while True:# 发送16进制数据ser.write(send_data)# 从串口读取9字节的数据并提取中间的5-8位16进制数据data ser.read(9)[3:7]# 将16进制数据转换为32位浮点数try:float_data struct.unpack(f, data)[0] # f 表示大端模式print(fdata: {float_data}) # 打印浮点数# 获取当前时间戳timestamp datetime.now().strftime(%Y-%m-%d %H:%M:%S)# 写入日志文件log_file.write(f{timestamp},{float_data}\n)log_file.flush() # 立即写入文件except struct.error as e:print(fError unpacking data: {e})# 每100毫秒即每秒10次更新一次time.sleep(0.1)except KeyboardInterrupt:print(Program terminated by user)finally:# 关闭串口ser.close()CRC校验计算 补充一点为了可以使用其他命令需要计算16位CRC校验值。比如下面可以数据手册上的读取测量值命令的CRC校验值“01 04 0000 0002 71CB”其中71CB是16位校验值在下面脚本中输入前面命令“01 04 0000 0002”即可 import crcmoddef calculate_crc16(data):# 创建一个CRC16校验对象crc16_func crcmod.mkCrcFun(0x18005, initCrc0xFFFF, revTrue, xorOut0x0000)# 将16进制字符串转换为字节byte_data bytes.fromhex(data)# 计算CRC16校验码crc_value crc16_func(byte_data)# 交换高低字节crc_value ((crc_value 0xFF) 8) | ((crc_value 0xFF00) 8)return crc_value# 输入的16进制内容
hex_data 01 04 0000 0002# 去除空格并计算CRC16
hex_data_no_spaces hex_data.replace( , )
crc16_result calculate_crc16(hex_data_no_spaces)# 打印结果
print(fCRC16:{crc16_result:04X})8位16进制字符串转为32位浮点数据 def hex_to_float(hex_str):# 将 16 进制字符串转换为 32 位无符号整数uint_value int(hex_str, 16)# 将 32 位无符号整数转换为浮点数float_value struct.unpack(!f, struct.pack(!I, uint_value))[0]return float_value3使用WPF 就功能而言这个方法我是最满意的可以自己定制化写一个专用的串口助手。不过它们各有缺陷llcom虽然可以使用lua脚本做很多自动化处理但无法显示图表。python虽然可以显示图表但实时更新的效果并不好。使用WPF虽然可以实现很多功能但需要搭建Visual Studio环境并且需要写不少代码。 由于时间限制目前我只实现了简单的定时发送转为浮点数据的功能。后续我会添加显示图表的功能记录日志的功能还没有做好使用会出问题。并且我暂时并不打算在这上面花太多时间所以没怎么考虑界面设计。 因此界面很简单使用起来也很简单。 XAML文件 Window x:Classexpr_com.MainWindowxmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentationxmlns:xhttp://schemas.microsoft.com/winfx/2006/xamlTitleexpr_com Height700 Width1000GridGrid.RowDefinitionsRowDefinition HeightAuto /!-- 可用串口 --RowDefinition HeightAuto /!-- 波特率 --RowDefinition HeightAuto /!-- 打开串口按钮 --RowDefinition HeightAuto /!-- 发送数据 --RowDefinition HeightAuto /!-- 16进制复选框 --RowDefinition HeightAuto /!-- 16进制显示复选框 --RowDefinition HeightAuto /!-- 单次发送和定时循环发送按钮 --RowDefinition HeightAuto /!-- 周期秒 --RowDefinition HeightAuto /!-- 记录数据复选框 --RowDefinition HeightAuto /!-- 处理数据复选框及参数 --RowDefinition Height* /!-- 接收数据框 --RowDefinition Height* /!-- 处理数据框 --/Grid.RowDefinitionsStackPanel OrientationHorizontal Grid.Row0 Margin10,10,10,0Label Content可用串口 Width80 /ComboBox NamecmbPorts SelectionChangedcmbPorts_SelectionChanged Width150 //StackPanelStackPanel OrientationHorizontal Grid.Row1 Margin10,0,10,0Label Content波特率 Width80 /TextBox NametxtBaudRate Text115200 Width100 //StackPanelButton NamebtnOpenPort Content打开串口 ClickbtnOpenPort_Click Width120 Margin10,0,10,10 Grid.Row2 /StackPanel OrientationHorizontal Grid.Row3 Margin10,0,10,0Label Content发送数据 Width80 /TextBox NametxtSendData Text01040000000271CB Width200 //StackPanelCheckBox NamechkHex Content16进制 Margin10,0,10,10 Grid.Row4 /CheckBox NamechkHexDisplay Content16进制显示 Margin10,0,10,10 Grid.Row5 /StackPanel OrientationHorizontal Grid.Row6 Margin10,0,10,0Button NamebtnSendOnce Content单次发送 ClickbtnSendOnce_Click Width120 /Button NamebtnStartLoopSend Content定时循环发送 ClickbtnStartLoopSend_Click Width120 Margin10,0,0,0 //StackPanelStackPanel OrientationHorizontal Grid.Row7 Margin10,0,10,0Label Content周期秒 Width80 /TextBox NametxtPeriod Text0.1 Width50 //StackPanelCheckBox NamechkLog Content记录数据 Margin10,0,10,10 Grid.Row8 /StackPanel OrientationHorizontal Grid.Row9 Margin10,0,10,0CheckBox NamechkProcessData Content处理数据 Margin0,0,10,0 /Label Content起始位置 Width80 /TextBox NametxtStartIndex Text7 Width50 Margin0,0,10,0 /Label Content长度 Width50 /TextBox NametxtLength Text8 Width50 //StackPanelTextBox NametxtReceivedData IsReadOnlyTrue Margin10,0,10,10 Grid.Row10 VerticalScrollBarVisibilityAuto /Grid Grid.Row11 Margin10,0,10,10Grid.ColumnDefinitionsColumnDefinition Width*/ColumnDefinition Width*//Grid.ColumnDefinitionsTextBox NametxtExtractedData IsReadOnlyTrue VerticalScrollBarVisibilityAuto Grid.Column0 /TextBox NametxtFloatData IsReadOnlyTrue VerticalScrollBarVisibilityAuto Grid.Column1 //Grid/Grid
/Window CS代码 using System;
using System.IO.Ports;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.Timers;namespace expr_com
{public partial class MainWindow : Window{private SerialPort? _serialPort; // 声明为可为 null 的类型private System.Timers.Timer? _timer; // 声告为可为 null 的类型public MainWindow(){InitializeComponent();LoadAvailablePorts();_serialPort null; // 初始化为 null_timer null; // 初始化为 null}private void LoadAvailablePorts(){cmbPorts.ItemsSource SerialPort.GetPortNames();}private void cmbPorts_SelectionChanged(object sender, SelectionChangedEventArgs e){if (cmbPorts.SelectedItem ! null){txtBaudRate.Focus();}}private void btnOpenPort_Click(object sender, RoutedEventArgs e){if (cmbPorts.SelectedItem null){MessageBox.Show(请选择一个串口。, 错误, MessageBoxButton.OK, MessageBoxImage.Error);return;}if (_serialPort null || !_serialPort.IsOpen){try{_serialPort new SerialPort(cmbPorts.SelectedItem.ToString(), int.Parse(txtBaudRate.Text)){ReadTimeout 1000, // 增加读取超时时间WriteTimeout 500,DataBits 8,StopBits StopBits.One,Parity Parity.None};// 注册 DataReceived 事件_serialPort.DataReceived SerialPort_DataReceived;_serialPort.Open();btnOpenPort.Content 关闭串口;// 显示成功消息txtReceivedData.AppendText(← 串口已打开。 Environment.NewLine);}catch (Exception ex){MessageBox.Show($打开串口失败: {ex.Message}, 错误, MessageBoxButton.OK, MessageBoxImage.Error);txtReceivedData.AppendText($← 打开串口失败: {ex.Message} Environment.NewLine);}}else{_serialPort.DataReceived - SerialPort_DataReceived; // 取消注册 DataReceived 事件_serialPort.Close();btnOpenPort.Content 打开串口;// 显示成功消息txtReceivedData.AppendText(← 串口已关闭。 Environment.NewLine);}}private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e){try{string data _serialPort.ReadExisting();if (!string.IsNullOrEmpty(data)){Dispatcher.Invoke(() {if (chkHexDisplay.IsChecked true){// 将接收到的数据转换为16进制字符串byte[] bytes Encoding.ASCII.GetBytes(data);string hexData BitConverter.ToString(bytes).Replace(-, );txtReceivedData.AppendText($→ {hexData} Environment.NewLine);if (chkProcessData.IsChecked true){ProcessData(hexData);}}else{txtReceivedData.AppendText($→ {data} Environment.NewLine);}// 立即滚动到底部txtReceivedData.ScrollToEnd();});}}catch (Exception ex){// 处理其他异常Dispatcher.Invoke(() {txtReceivedData.AppendText($→ 读取错误: {ex.Message} Environment.NewLine);});}}private void LogData(string data){string logFilePath Path.Combine(data_log, $log_{DateTime.Now:yyyyMMdd_HHmmss}.csv);File.AppendAllText(logFilePath, ${DateTime.Now:yyyy-MM-dd HH:mm:ss},{data}{Environment.NewLine});}private async void btnSendOnce_Click(object sender, RoutedEventArgs e){await SendDataAsync(false);}private async void btnStartLoopSend_Click(object sender, RoutedEventArgs e){if (_timer null || !_timer.Enabled){try{double period double.Parse(txtPeriod.Text);_timer new System.Timers.Timer(period * 1000); // 转换为毫秒_timer.Elapsed OnTimedEvent;_timer.AutoReset true;_timer.Enabled true;btnStartLoopSend.Content 停止循环发送;// 显示启动消息txtReceivedData.AppendText($← 定时循环发送已启动周期: {period} 秒。 Environment.NewLine);}catch (Exception ex){MessageBox.Show($设置定时发送失败: {ex.Message}, 错误, MessageBoxButton.OK, MessageBoxImage.Error);txtReceivedData.AppendText($← 设置定时发送失败: {ex.Message} Environment.NewLine);}}else{_timer.Enabled false;_timer.Dispose();_timer null;btnStartLoopSend.Content 定时循环发送;// 显示停止消息txtReceivedData.AppendText(← 定时循环发送已停止。 Environment.NewLine);}}private async void OnTimedEvent(object source, ElapsedEventArgs e){try{await Dispatcher.InvokeAsync(async () {await SendDataAsync(true);});}catch (Exception ex){// 处理其他异常await Dispatcher.InvokeAsync(() {txtReceivedData.AppendText($← 发送错误: {ex.Message} Environment.NewLine);});}}private async Task SendDataAsync(bool isLoop){try{if (_serialPort ! null _serialPort.IsOpen){string sendData txtSendData.Text.Trim();if (string.IsNullOrEmpty(sendData)){throw new InvalidOperationException(发送数据不能为空。);}if (chkHex.IsChecked true){byte[] hexData Convert.FromHexString(sendData);_serialPort.Write(hexData, 0, hexData.Length);}else{_serialPort.WriteLine(sendData);}// 显示发送数据txtReceivedData.AppendText($← {sendData} Environment.NewLine);if (chkLog.IsChecked true){LogData(sendData);}}else{// 串口未打开显示错误信息txtReceivedData.AppendText(← 串口未打开无法发送数据。 Environment.NewLine);}}catch (Exception ex){// 处理其他异常txtReceivedData.AppendText($← 发送错误: {ex.Message} Environment.NewLine);}}private void ProcessData(string hexData){try{// 从文本框中获取用户输入的起始位置和长度int startIndex int.Parse(txtStartIndex.Text) - 1; // 减1是为了适应索引从0开始int length int.Parse(txtLength.Text);// 去除空格string hexDataWithoutSpaces hexData.Replace( , );// 检查数据长度是否足够if (hexDataWithoutSpaces.Length startIndex length){// 截取指定位置和长度的数据string hexDataToProcess hexDataWithoutSpaces.Substring(startIndex, length);// 将16进制字符串转换为字节数组byte[] bytes Convert.FromHexString(hexDataToProcess);// 检查字节顺序if (BitConverter.IsLittleEndian){// 如果系统是小端序而数据是大端序则需要反转字节顺序Array.Reverse(bytes);}// 将字节数组转换为浮点数float floatValue BitConverter.ToSingle(bytes, 0);// 追加结果txtExtractedData.AppendText(${hexDataToProcess} Environment.NewLine);txtFloatData.AppendText(${floatValue} Environment.NewLine);// 立即滚动到底部txtExtractedData.ScrollToEnd();txtFloatData.ScrollToEnd();}else{txtExtractedData.AppendText(数据长度不足无法处理。 Environment.NewLine);}}catch (FormatException ex){txtExtractedData.AppendText($格式错误: {ex.Message} Environment.NewLine);}}}
} 实际效果 单次发送 循环发送