# (网页串口)电镜wobble电流上位机控制界面 **Repository Path**: seannaes/wobble_current_controller_for_electron_microscope_centering ## Basic Information - **Project Name**: (网页串口)电镜wobble电流上位机控制界面 - **Description**: 在生物岛实验室玩。电子光学工作——电子枪对中,需要对偏转线圈施加一个扫描电流,查看光斑大小,确定进一步调节方案 需要一个wobble 扫描电流的控制界面,向下位机发送数据,从而控制电流源。 这是一个控制扫描电流的网页。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2022-08-03 - **Last Updated**: 2024-01-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### Wobble电流控制器上位机 ##### 运行方法 ###### 下载和运行 把这个以html为后缀名的文件下载下来,然后双击它。你的浏览器会打开它。推荐使用Chrome浏览器或者Edge浏览器,他们支持串口通信。其他的浏览器可能不能正常工作。当浏览器不支持串口时,会显示以下提示信息 prompt.jpg 此时,就换一个浏览器吧。 ###### 用户界面说明 prompt.jpg 进入界面,点击“选择通信端口”,会弹出连接的设备。 prompt.jpg 点击相应的设备并点击连接。 连接成功后,product ID 和 Vendor ID内会显示一些号码。 若需断开连接,推荐采用**刷新页面**的方式关闭端口。可以按F5,也可以点击浏览器上方的 prompt.jpg 下方的启用状态和电流模式用红色字体标出。启用状态有已启用、已禁用、不知道三种,电流模式有wobble,stable和stop三种。 以下输入框和按钮设定恒定电流 prompt.jpg 以下输入框和按钮输出wobble电流 prompt.jpg 在完成wobble电流的设定后,还应勾选需要使能的通道, prompt.jpg 而后点击“使能勾选的通道”,发送指令,让各个通道同时开始输出。 ##### 背景 给透射电子显微镜的电子枪对中,可以尝试给偏转线圈施加一定比例的电流,找出电子光路系统的特殊点,而后调节对中。在这个过程中,需要两个同步变化的电流,电流强度按一定的频率变化。因此需要有一个能够根据用户配置快速控制电流变化参数的系统。Wobble(摇晃)表示电流在变化。 设计下位机和上位机的分工。下位机的MCU会计算一个时刻所有需要的电流,而后写到DAC芯片上,DAC芯片控制线圈的电流。靠开关管改变电流方向到所需的方向。上位机需要提供用户界面,根据用户提供的配置发送串口数据到下位机。为了能够让多个通道同步,需要有配置参数的命令和使能通道(让通道从初始相位开始输出)的命令。 ##### 通信协议 ##### 上位机设计 ###### 平台 网页编程。采用HTML/CSS/JavaScript设计界面。不使用第三方库。运行环境为浏览器。优点如下 > navigator对象原生支持串口通信,发送接收数据不需要太多代码 > > 可以快速查看源代码并修改,用户和开发者都不需要安装其它编译器或运行环境 > > 浏览器管理能力强,程序不容易崩溃 > > 网页环境,能够快速做出美观的界面 ###### 用户界面设计 使用方法相关的设计请参考上文“用户界面设计”部分。源码中与格式相关的部分主要有 >
让所有界面居中。 > > 用js生成html8个通道的html界面,方便修改,格式统一,且不易出现因为复制粘贴时忘记修改目标号码而导致的bug > > 给各个模块分块,设定边界、宽度、颜色格式 其中,生成html界面的方法是直接从文本生成(调用生成组件的函数太麻烦) ```javascript document.body.onload = addElement; function addElement () { for(let i = 0;i<8;i+=1) { var inner_data; let sub_span; let numstr; numstr = i.toString(); sub_span = document.createElement("div"); sub_span.classList.add("single_channel"); inner_data = '' + '' + ''+ channel_names[i] +'' + ' | 启用状态:' + '不知道 ' + ' | 电流模式:' + '啥也没有' + ' | ' + '' + ' | ' + '' + '' + ' | ' + '' + '' + '' + '' + '
'; sub_span.innerHTML = inner_data; let currentDiv = document.getElementById("control_pannel"); currentDiv.appendChild(sub_span); } } ``` ##### 串口通信 只需要向下位机发送数据。判断浏览器是否支持串口 ```javascript if(!("serial" in navigator)) { alert("你的浏览器不支持串口,请用最新版的Chrome或Edge浏览器打开"); } ``` 开启串口。将函数绑定到按钮上,当用户点击按钮时,就会 ```javascript async function() { // Prompt user to select any serial port. port = await navigator.serial.requestPort(); if(port != null) { // Wait for the serial port to open. await port.open({ baudRate: 9600 }); console.log("Open",port,"at baud rate 9600!"); const {usbProductId, usbVendorId} = port.getInfo(); console.log("Product id: ",usbProductId,"vendor id: ",usbVendorId); document.getElementById("com_product_id").value = usbProductId; document.getElementById("com_vendor_id").value = usbVendorId; writer = port.writable.getWriter(); document.getElementById("port_closer").disabled = false; } } ``` 有了writer之后,就可以发送数据了。根据用户界面和通信协议和计算应该放到Uint8数组里的值,然后发送。 ```javascript switch (states[k]) { case STOP: // 停止状态,发送0电流 document.getElementById("state_" + k.toString()).innerHTML = "stop" var stop_send; stop_send = new Uint8Array([0x55,k+1,1, 0,0,0, 0xff,0xff,0xff, 0xff,0xaa]); writer.write(stop_send); break; case WOBBLE: // 扫描电流模式,发送高电流、低电流、每秒的周期数 document.getElementById("state_" + k.toString()).innerHTML = "wobble" var max,min,period_per_second,max_divide,min_divide; max = Number(document.getElementById("wobble_max_"+k.toString()).value); min = Number(document.getElementById("wobble_min_"+k.toString()).value); if(Math.abs(max) > 0.4 || Math.abs(min) >0.4) { alert("电流太大了,输入电流绝对值放在0.4A及其以内吧"); break; } max_divide = divide_number(max); min_divide = divide_number(min); period_in_second = Number(document.getElementById("wobble_period_"+k.toString()).value); var wobble_send; wobble_send = new Uint8Array([0x55,k+1,2, max_divide[0],max_divide[1],max_divide[2], min_divide[0],min_divide[1],min_divide[2], Math.floor(period_in_second),0xaa]); writer.write(wobble_send); break; case STABLE: // 稳定电流状态,发送电流大小和方向 document.getElementById("state_" + k.toString()).innerHTML = "stable" var stable_current,stable_divide; stable_current = Number(document.getElementById("stable_current_"+k.toString()).value); stable_divide = divide_number(stable_current); var stable_send; // 判断是否超过最大电流 // if(Math.abs(stable_current) > MAX_CURRENT) // stable_current = Math.sign(stable_current) * MAX_CURRENT; if(Math.abs(stable_current) > 0.4) { alert("电流太大了,输入电流绝对值放在0.4A及其以内吧"); break; } stable_send = new Uint8Array([0x55,k+1,1, stable_divide[0],stable_divide[1],stable_divide[2], 0xff,0xff,0xff, 0xff,0xaa]); writer.write(stable_send); break; default: alert("unknown channel state"); break; } ``` ```javascript // 通信协议示例 const data = new Uint8Array([ 0x55, // index 0, byte 1: start 0x01 , // index 1, byte 2: channel 0x01, // index 2, byte 3: mode 0x00, // index 3, byte 4: direction 0x0f, // index 4, byte 5: current absolute high 0xff, // index 5, byte 6: current absolute low 0x00, // index 6, byte 7: direction 0x0f, // index 7, byte 8: current absolute high 0xff, // index 8, byte 9: current absolute low 0x00, // index 9, byte 10: period 0xAA, // index 10, byte 11: end ]); ``` 例如,发送enable信号的函数如下 ```javascript async function send_enable_info(enable_byte) { var cmd = new Uint8Array(11); cmd.fill(0xff); cmd[0] = 0x55; cmd[2] = 3; // wobble 输出使能 cmd[3] = enable_byte; cmd[10] = 0xaa; await writer.write(cmd); } ``` 用按钮触发相应的函数。 ```javascript
```