2024年2月27日 星期二
用 dart 語法, 針對 Uint8List 資料流(stream): 內容是 vt100 字串, 僅把 ESC[ ... m 移除並轉換成 utf8 字串
String utf8Log = "";
void stream2utf8(Uint8List data) {
final findLF = data.lastIndexWhere((e) => e == 0xa);// find position of the last \n
final endLF = findLF + 1; // to include LF
final len = remain.length + (findLF < 0 ? data.length : endLF);
final resemble = Uint8List(len);
if (remain.isNotEmpty) resemble.setAll(0, remain);// copy remain to front of resemble
resemble.setRange(remain.length, len, data); // append sublist of data to resemble
if (findLF < 0) { // keep in memory
remain = resemble;
} else { // line feed found, time to flush out
remain = Uint8List.sublistView(data, endLF);
Uint8List afterESC = Uint8List.sublistView(resemble);
while (afterESC.isNotEmpty) { // until empty
final findESC = afterESC.indexOf(0x1b); // find ESC position
utf8Log += utf8.decode(Uint8List.sublistView(afterESC, 0,
findESC < 0 ? afterESC.length : findESC
));
if (findESC < 0) break;
var lenESC = 1; // sequence ^[ ... m seek
if (afterESC[findESC + lenESC] == 0x5b) { // found sequence start '['
do {
lenESC ++; // one byte advance
if (afterESC[findESC + lenESC] == 0x6d) { // found end of character 'm'
lenESC ++;// one byte advance
break;
}
} while (findESC + lenESC < afterESC.length);
if (findESC + lenESC >= afterESC.length) { // todo: other ^[ sequence not yet implement!
afterESC[findESC] = 0x5E; // change ESC from 0x1b to '^' to prevent infinit loop
continue;// backoff to show this sequence
}
} else { // todo: other ^ sequence not yet implement!
afterESC[findESC] = 0x5E; // change ESC from 0x1b to '^' to prevent infinit loop
continue;// backoff to show this sequence
}
afterESC = Uint8List.sublistView(afterESC, findESC + lenESC);// ok, go ahead
}
setState((){}); // update screen if necessary
}
2024年2月19日 星期一
簡單的 flutter app 用來測試 usb serial for android
在 Android 系統上要使用 usb serial port 可以透過 usb-serial-for-android , 它是用 JAVA 語言寫的, 上官網打包先將它下載回來: https://github.com/mik3y/usb-serial-for-android, 將它解壓縮
unzip usb-serial-for-android-master.zip
1. 打開終端機用 flutter 命令產生一個新專案(例如 usbadc):
flutter create usbadc
2. 將上述 usb-serial-for-android 解開的檔案, 只要把目錄 usbSerialForAndroid/src/main/java/com 整個驅動程式的目錄複製到新專案: usbadc/android/app/src/main/java 內, 之後透過 kotin 程式碼呼叫它
3. 編輯 usbadc/android/app/src/main/AndroidManifest.xml 把以下綠色內容貼到標籤 <activity> </activity> 標籤內
<activity>
...
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
4. 接著要產生一個 device_filter 檔案, 把它放到目錄 usbadc/android/app/src/main/res/xml 內:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 0x0403 / 0x60??: FTDI -->
<usb-device vendor-id="1027" product-id="24577" /> <!-- 0x6001: FT232R -->
<usb-device vendor-id="1027" product-id="24592" /> <!-- 0x6010: FT2232H -->
<usb-device vendor-id="1027" product-id="24593" /> <!-- 0x6011: FT4232H -->
<usb-device vendor-id="1027" product-id="24596" /> <!-- 0x6014: FT232H -->
<usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT230X, FT231X, FT234XD -->
<!-- 0x10C4 / 0xEA??: Silabs CP210x -->
<usb-device vendor-id="4292" product-id="60000" /> <!-- 0xea60: CP2102 and other CP210x single port devices -->
<usb-device vendor-id="4292" product-id="60016" /> <!-- 0xea70: CP2105 -->
<usb-device vendor-id="4292" product-id="60017" /> <!-- 0xea71: CP2108 -->
<!-- 0x067B / 0x23?3: Prolific PL2303x -->
<usb-device vendor-id="1659" product-id="8963" /> <!-- 0x2303: PL2303HX, HXD, TA, ... -->
<usb-device vendor-id="1659" product-id="9123" /> <!-- 0x23a3: PL2303GC -->
<usb-device vendor-id="1659" product-id="9139" /> <!-- 0x23b3: PL2303GB -->
<usb-device vendor-id="1659" product-id="9155" /> <!-- 0x23c3: PL2303GT -->
<usb-device vendor-id="1659" product-id="9171" /> <!-- 0x23d3: PL2303GL -->
<usb-device vendor-id="1659" product-id="9187" /> <!-- 0x23e3: PL2303GE -->
<usb-device vendor-id="1659" product-id="9203" /> <!-- 0x23f3: PL2303GS -->
<!-- 0x1a86 / 0x?523: Qinheng CH34x -->
<usb-device vendor-id="6790" product-id="21795" /> <!-- 0x5523: CH341A -->
<usb-device vendor-id="6790" product-id="29987" /> <!-- 0x7523: CH340 -->
<!-- CDC driver -->
<usb-device vendor-id="9025" /> <!-- 0x2341 / ......: Arduino -->
<usb-device vendor-id="5824" product-id="1155" /> <!-- 0x16C0 / 0x0483: Teensyduino -->
<usb-device vendor-id="1003" product-id="8260" /> <!-- 0x03EB / 0x2044: Atmel Lufa -->
<usb-device vendor-id="7855" product-id="4" /> <!-- 0x1eaf / 0x0004: Leaflabs Maple -->
<usb-device vendor-id="3368" product-id="516" /> <!-- 0x0d28 / 0x0204: ARM mbed -->
<usb-device vendor-id="1155" product-id="22336" /><!-- 0x0483 / 0x5740: ST CDC -->
<usb-device vendor-id="11914" product-id="5" /> <!-- 0x2E8A / 0x0005: Raspberry Pi Pico Micropython -->
<usb-device vendor-id="11914" product-id="10" /> <!-- 0x2E8A / 0x000A: Raspberry Pi Pico SDK -->
<usb-device vendor-id="6790" product-id="21972" /><!-- 0x1A86 / 0x55D4: Qinheng CH9102F -->
</resources>
5. 編寫 android 端的主程式 usbadc/android/app/src/main/kotlin/com/example/usbadc/MainActivity.kt 如下:
package com.example.usbadc
import java.nio.ByteBuffer
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import androidx.annotation.NonNull
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.app.PendingIntent
import android.content.Intent
import android.hardware.usb.UsbManager
import com.hoho.android.usbserial.driver.UsbSerialProber
import com.hoho.android.usbserial.driver.UsbSerialPort
class MainActivity: FlutterActivity() {
private lateinit var usbManager: UsbManager
override fun onCreate(savedInstanceState: Bundle?) {// ← ↑ → ↓
super.onCreate(savedInstanceState)
usbManager = getSystemService(USB_SERVICE) as UsbManager
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.apply {
val eventHandler = object: EventChannel.StreamHandler {
override fun onCancel(args: Any?) { }
override fun onListen(args: Any?, sink: EventChannel.EventSink?) {
sink ?. apply {
val flutterSink = this
setupTTY(115200) ?. apply {
val usbSerialPort: UsbSerialPort = this
val packetSize: Int = usbSerialPort.readEndpoint.maxPacketSize
if (packetSize <= 0) return
flutterSink.success("Usb serial port is ready (packet Size = $packetSize).")
val usbBuffer: ByteBuffer = ByteBuffer.allocate(packetSize)
val usbPacket: ByteArray = usbBuffer.array()
var readLoop : Boolean = false
val mainHandler = object: Handler(Looper.getMainLooper()) { // to run ttyRead() in the message loop
override fun handleMessage(msg: Message) {// binding sink: EventChannel.EventSink
when(msg.what) {
0 ->{
flutterSink.endOfStream() // event channel close
looper.quit() // mainHandler quit
}
1 ->{ // read loop
val size = usbSerialPort.read(usbPacket, 1000) // read from usbSerialPort
if (size == packetSize) {
flutterSink.success(usbPacket) // send to flutter
} else if (size > 0) {// retrive part of data in usbPacket
val u8list = ByteArray(size)
System.arraycopy(usbPacket, 0, u8list, 0, size)
flutterSink.success(u8list) // send to flutter
}
sendEmptyMessage(1) // loopback to continue
}
2 ->{ // optional message handle for usbSerialPort write
if (msg.obj is String) {
val bytes: ByteArray = (msg.obj as String).toByteArray()
usbSerialPort.write(bytes, 1000)
}
if (!readLoop) {
readLoop = true// only send once
sendEmptyMessage(1) // trigger to begin read loop
}
}
else -> { }
}
}
}
val msg = mainHandler.obtainMessage().apply {
what = 2
obj = "hi\n"
}
mainHandler.sendMessage(msg)
}
}
}
}
EventChannel(dartExecutor.binaryMessenger, "com.example.ttyUSB").setStreamHandler(eventHandler)
}
}
private fun setupTTY(baudRate: Int): UsbSerialPort? {
val prob = UsbSerialProber.getDefaultProber()
for (device in usbManager.deviceList.values) { // scan all available devices
prob.probeDevice(device) ?. apply { // check only when driver is up
val driver = this
usbManager.openDevice(driver.device) ?. apply {// try to open usb connection
val usbConnection = this
for (port in driver.ports.indices) { // try all available ports
try {
driver.ports[port] . apply { // this is UsbSerialPort
open(usbConnection)
setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE) // set com: baudRate, n, 8, 1
}
return driver.ports[port] // finally UsbSerialPort is ready.
} catch (e: Exception) { }
}
}
}
}
return null
}
}
6. 編寫 flutter 端主程式 usbadc/lib/main.dart 如下
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override Widget build(BuildContext context) => MaterialApp(
title: "demo",// id, not title name!
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
home : const MyHomePage(),
);
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String history = "Hello\n";// init history to show
int total = 0;
int count2Clear = 0;
@override void initState() {
super.initState();
const event = EventChannel('com.example.ttyUSB');
event.receiveBroadcastStream().listen((data) async {
if (count2Clear ++ == 20) {
count2Clear = 0;
history = "";
}
var txt = (data is Uint8List) ? String.fromCharCodes(data) : "$data";// convert data to String
txt = txt.replaceAll("\r","") ;// remove CR
txt = txt.replaceAll("\n",";") ;// change LF to ';'
history += "${++ total}: $txt\n";// history add line number and txt
setState(() {}) ;// update screen
});
}
@override Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("ttyUSB demo"),
actions: [PopupMenuButton<int>(
itemBuilder: (context) => List.generate(1, (idx) => PopupMenuItem(value: idx, child: const Text("關閉"))),
onSelected: (idx) => SystemNavigator.pop() // app finish
)]
),
body: SingleChildScrollView(scrollDirection: Axis.vertical, child: Text(history))
);
}
}
進入專案目錄, 編譯上傳並執行
cd usbadc && flutter run
2024年2月8日 星期四
linux 上解決核心模組 ch34x.ko 掛載 /dev/ttyUSB0 時被強制斷線的問題
參考討論文章: https://unix.stackexchange.com/questions/670636/unable-to-use-usb-dongle-based-on-usb-serial-converter-chip
只要有 root 權限執行以下腳本就能解決, 一勞永逸辦法是將它放在 /etc/rc.local 裡面, 一開機就執行
# ...
for f in /usr/lib/udev/rules.d/*brltty*.rules; do
sudo ln -s /dev/null "/etc/udev/rules.d/$(basename "$f")"
done
sudo udevadm control --reload-rules
2024年1月27日 星期六
量測電感與電容
參考文章: https://www-schiessle-de.translate.goog/emt1/MessKleinKap/MessKleinKap1.htm?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=nui
討論文章: https://www.edaboard.com/threads/why-not-the-lm339-instead-of-lm311-in-aade-lc-meter-clones.183392/
開源碼: https://github.com/coreWeaver/LC-Meter
備註: 透過比較器 LM311 及 L1, C1 產生最高工作頻率 f1, 並利用準確 C0 值去校準 L1 及 C1. 透過串聯 L1 與待測電感或是並聯 C1 與待測電容, 量測出諧振頻率而計算出待測數值. LM311 比較器可以產生 1MHz 頻率, 用 68uH/1000pF 時, 最高工作頻率約 750kHz, 若要改用 LM393 可能要修改 L1/C1 為 100uH/2nF 讓工作頻率低於 500kHz.
2024年1月16日 星期二
使用低功耗低壓差 3.3V LDO 搭配可調 Emitter follower (2N2222 npn 電晶體 + 可變電阻) 調整鋰電池電壓(3.6 ~ 4.2V) 成 1.5V
市面上便宜的時鐘, 內部機芯大多使用晶體振盪器產生固定頻率的脈沖去驅動 Lavet-type 步進馬達, 它只要一個 1.5V 電池就足以推動, 而馬達扭力主要是靠線圈內的電流產生磁力所致, 因此電流才是動力來源. 電壓太高會造成線圈電流飽和導致發熱,可能影響震盪頻率甚至燒毀, 使用鋰電池最怕的是電路短路產生爆炸, 下圖電路中分壓電阻 Ru 用 2k 歐姆用來限制 emiiter follower 的基極(base)電流, 可變電阻用來調整射極端輸出電壓(調整到要能同時推動時/分/秒針), 可變電阻 Rv 用 0 ~10k 歐姆, 計算出主動區輸出大約是 3.3*Rv/(Rv + Ru) - 0.6, 也就是說可調整輸出至 0 ~ 2.15V, 若無法成功運轉, 只要降低 Ru 值再調整可變電阻就可以了.
2024年1月13日 星期六
Linux mint 的狀態欄不小心刪掉了
參考論壇的討論文章: https://forums.linuxmint.com/viewtopic.php?p=2004336#p2004336
開啟終端機 (按快速鍵: Ctrl + alt + T), 再執行以下命令就能恢復:
dconf reset -f /org/cinnamon/
2023年11月16日 星期四
新硬碟在 uefi bios 的主機上, 安裝 linux mint livecd, 預留空間存放 windows10 安裝檔
1. 上 Linux mint 官網下載 iso 檔: https://linuxmint.com 將它燒錄成 DVD, 設定 UEFI BIOS 從 DVD 開機
2. 開啟終端機, 在 nvme SSD 上產生 GPT, ESP 用來安裝 grub2 啟動多重作業系統, 8G NTFS 分區先暫存 Windows 10 安裝檔, EXT4 分區用來裝 Linux 作業系統,先作好這 4 個分割區(BBP + ESP + NTFS + EXT4), 後續視需要再添加(若要安裝 Windows 10 作業系統,大概還需要 NTFS 80G 的空間 ).
sudo gdisk /dev/nvme0n1
partition 1: 16M BBP (0xEF02) BIOS boot partition
partition 2: 4G ESP (0xEF00) EFI system partition
partition 3: 8G NTFS (0x0700) Microsoft basic data
partition 4: 40G EXT4 (0x8300) Linux filesystem
sudo partprobe /dev/nvme0n1
3. 列出分割區資訊
lsblk -oname,label,size,uuid && df
4. 格式化各分割區, 並設定標籤, 將來方便管理
sudo mkfs.fat /dev/nvme0n1p2 -n ESP -F 32
sudo mkfs.ntfs /dev/nvme0n1p3 -L NTFS
sudo mkfs.ext4 /dev/nvme0n1p4 -L LINUX
5. 掛上 ESP
sudo mkdir /mnt/nvme0n1p2 && sudo mount /dev/nvme0n1p2 /mnt/nvme0n1p2
6. 安裝 grub2 到 ESP
sudo grub-install --root-directory=/mnt/nvme0n1p2 /dev/nvme0n1
7. 可以將 livecd 複製存到 ESP 分區的目錄 /boot 裡面, 並編輯 ESP 分區內 /boot/grub/grub.cfg 檔案, 之後就能移除 DVD, 改從 SSD 快速開機
sudo dd if=/dev/loop0 of=/mnt/nvme0n1p2/boot/linuxmint21_2.iso
sudo xed /mnt/nvme0n1p2/boot/grub/grub.cfg
######## grub.cfg 檔案內容, espUUID 記得要修改!!!
set timeout=3
set default=0
menuentry "Linux Mint 21.2 livecd ISO" {
insmod iso9660
set espUUID=D9E6-44CC
search --no-floppy --fs-uuid --set=root $espUUID
set isoFile=/boot/linuxmint21_2.iso
loopback loop $isoFile
linux (loop)/casper/vmlinuz boot=casper iso-scan/filename=$isoFile locale=zh_TW.UTF-8
initrd (loop)/casper/initrd.lz
}
######## grub.cfg 檔案結尾
8. 清除快取並釋放 ESP 分區, 重新開機 (設定好 UEFI BIOS boot source 從 SSD 開機)
sync && sudo umount /mnt/nvme0n1p2 && reboot
9. Linux 開完機, 可以點選桌面的安裝圖示, 將整個作業系統安裝到 EXT4 分區, 只要修改 grub.cfg, 添加啟動 Linux 項目, 其中 ext4UUID 必需修正 !!!
menuentry 'Linux Mint 21.2 Cinnamon' --hotkey=1 {
set ext4UUID=c5bfc0ce-3f68-4bf5-9eb2-8344ff02cd03
search --no-floppy --fs-uuid --set=root $ext4UUID
linux /boot/vmlinuz-5.15.0-76-generic root=UUID=$ext4UUID ro quiet splash
initrd /boot/initrd.img-5.15.0-76-generic
}
10. 複製 Windows10 安裝光碟的內容, 用 SSD 也能快速安裝 Windows10
sudo mkdir /mnt/iso /mnt/nvme0n1p3
sudo mount -o loop ~/Downloads/windows10.iso /mnt/iso
sudo mount /dev/nvme0n1p3 /mnt/nvme0n1p3
sudo cp -r /mnt/iso/boot /mnt/nvme0n1p3
sudo cp -r /mnt/iso/efi /mnt/nvme0n1p3
sudo cp -r /mnt/iso/sources /mnt/nvme0n1p3
sudo cp -r /mnt/iso/support /mnt/nvme0n1p3
sudo cp /mnt/iso/bootmgr /mnt/nvme0n1p3
sudo cp /mnt/iso/bootmgr.efi /mnt/nvme0n1p3
sudo cp /mnt/iso/setup.exe /mnt/nvme0n1p3
sudo cp /mnt/iso/autorun.inf /mnt/nvme0n1p3
sync
11. 修改 grub.cfg, 加入安裝 Windows10 選項, 其中 ntfsUUID 需修正 !!!
menuentry "安裝 Windows 10" {
insmod part_gpt
set ntfsUUID=1AFC6C0B6ED4A89E
search --no-floppy --fs-uuid --set=root $ntfsUUID
chainloader /efi/boot/bootx64.efi
}
12. 當安裝完 Windows10, 修改 grub.cfg, 可以自行添加啟動 Windows10 選項
menuentry "啟動 Windows 10" {
insmod part_gpt
set espUUID=D9E6-44CC
search --no-floppy --fs-uuid --set=root $espUUID
chainloader /EFI/Microsoft/Boot/bootmgfw.efi
}
P.S. 若安裝完 Windows10 或是 Linux mint, 導致無法透過原先的 grub2 開機, 可以調整 UEFI BIOS 開機選項, 或是乾脆使用 Linux mint 開機光碟, 進入 Linux 系統後, 再重新植入 grub2:
sudo mkdir /mnt/nvme0n1p2
sudo mount /dev/nvme0n1p2 /mnt/nvme0n1p2
sudo grub-install --root-directory=/mnt/nvme0n1p2 /dev/nvme0n1
2023年8月22日 星期二
拆開濾水器罐
由於濾水器活動外殼容器(可當成螺帽來看)在下面, 拆開更換濾芯時要注意方向性. 如果由上往下俯視時, 螺帽在下面時, 轉動下面的螺帽逆時針方向是鎖死, 順時鐘方向才是打開. 如果螺帽放在上方, 正如一般螺絲頭的方向(順時針方向是鎖死, 逆時鐘方向是打開).
2023年8月13日 星期日
用 python 解簡單的常微分方程式
# sudo apt install python3-pip # python3 -m venv venv # cd venv # . bin/activate # pip3 install numpy matplotlib import numpy as np import m...
-
1. 目前使用 linux mint 22.1 作業系統可以順利跑起來, 可上官網去下載, 並安裝到硬碟. 2. 安裝 waydroid 可上網站 https://docs.waydro.id 參考看看: https://docs.waydro.id/usage/inst...
-
雜亂無章: 只是粗略筆記 參考: 1. https://docs.kicad.org/7.0/en/pcbnew/pcbnew.html 2. https://techexplorations.com/guides/kicad/2e/pcbnew-layout/ 3. https...




