# (网页串口)电镜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浏览器,他们支持串口通信。其他的浏览器可能不能正常工作。当浏览器不支持串口时,会显示以下提示信息
此时,就换一个浏览器吧。
###### 用户界面说明
进入界面,点击“选择通信端口”,会弹出连接的设备。
点击相应的设备并点击连接。
连接成功后,product ID 和 Vendor ID内会显示一些号码。
若需断开连接,推荐采用**刷新页面**的方式关闭端口。可以按F5,也可以点击浏览器上方的
下方的启用状态和电流模式用红色字体标出。启用状态有已启用、已禁用、不知道三种,电流模式有wobble,stable和stop三种。
以下输入框和按钮设定恒定电流
以下输入框和按钮输出wobble电流
在完成wobble电流的设定后,还应勾选需要使能的通道,
而后点击“使能勾选的通道”,发送指令,让各个通道同时开始输出。
##### 背景
给透射电子显微镜的电子枪对中,可以尝试给偏转线圈施加一定比例的电流,找出电子光路系统的特殊点,而后调节对中。在这个过程中,需要两个同步变化的电流,电流强度按一定的频率变化。因此需要有一个能够根据用户配置快速控制电流变化参数的系统。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
```