ceezblog.com

I like cat, isn't it obvious?

Author: Simon

  • Revisit my homemade T12 soldering station

    TLDR; I upgrade my soldering station. Design and print standalone stand for the handle. Include some ranting and story behind it.

    My first homemade soldering station was made 8 years, more or less. It still work just fine. During the years, I’ve made a few more giving to friends. They haven’t complained, yet! (haha).

    I have opensourced my solder station here https://github.com/ceezblog/T12-Solder-station

    My first soldering station was HYLKKO 936, a Yihua 936 clone and chinesium copy of Hakko FX888D. As you might have guessed it, it wasn’t that bad, certainly an upgrade from cheap soldering iron that has a no temperature control circuitry. It took quite sometime for the iron tip (900M series tip) to get up to temperature. There was no sleep function, pretty much the tip stay hot up to target temperature as long as it powered.

    The problem is, I forgot to turn it off a dozen times. Yes, I left the solder iron 300°C hot for half a day until I turned off the light, going bed. And just at that moment I saw the only frigging LED of the solder station was blinking on for a brief moment… That only red LED turns on when the temp rising up but off when reaching the correct temp. If it is idling at target temperature, that LED only turns for half a second then off for 10-20 seconds. The good news was it didn’t pump power to the iron tip constantly, but only top it up to keep the temp at target. But still, leaving something burning hot like that unattended was really really bad idea.

    There are two scenarios here:

    • They forgot to put an extra LED or on the face plate to help user like me to turn it off, or just want shave off $0.001 cost of an LED and a resistor.
    • They don’t care about user experience, the iron does get hot and that is enough, wrap it and sell it

    I tend to think the latter is the case here. This is what China is well-known of, the Chabuduo mindset, google it.

    At some point, I had to make a simple buzzer that beeps every 5 minutes to remind me to turn it off. Now it is permanently a heat set press jig for installing threaded brass insert. It did serve its job to help me build my own soldering station though.

    Recently, I moved house, set up new home-lab and I pretty much rearranged everything. I have 2 active soldering stations + hot air station now, exclude that weird heat set press jig.

    Before that, the soldering iron stand was bolted to the side of the case as one piece, very convenience, having them easy to pack up to go. The case was ABS print, still strong after years of service.

    Imagine when I stack 2 of them on top of each other. Yeah, kind of awkward with the iron stand on the side. Thus, I need a standalone stand for the handle.

    What a big deal? just go buy a stand from aliexpress or go search Makerworld, printable and print one? Yeah, nah.

    I have a custom optical sensor (photointerrupter) to detect if the handle is docking, so it will trigger sleep function of the station. The sleep function will keep the tip around 200°C for 5 minutes then turn off the heater completely afterward. This is just for safegarding for user who forgets to turn off their stuffs like me! So, even the case I forgot to turn soldering station off for days, it’d still be safe, the iron would be room temperature cold.

    When the handle is put in the stand, the black lever is pushed down and blocks the IR beam –> hence it called photointerrupter. Pretty much the same as off-the-shelf part, but I don’t have to make a small PCB to hold it. Making PCB and design a way to mount that photointerrupter was kind too much to do. So I rolled with my custom design, it was easier, I don’t have to go back and forth between fusion and kicad.

    Some pictures compare the first PCB vs the second iteration

    The second iteration has some differences in pin configuration as well as minor change in circuitry:

    • Different pins for driving the LCD0802
    • Employ AMS1117 as post filtering, improve temperature reading. First iteration use mini360 directly without post filtering
    • Use a simple LED (forward bias) instead of zenner diode (reverse bias) to clamp voltage at input of LMV321
    • Use 0805 thermistor just to measure temperature inside the case, no need to measure temperature at the MOSFET

    Differences in docking sensors

    The left one used TCRT5000 in a module for line tracking sensor, as my first experiment. It was basically 2 LEDs, IR diode and IR phototransistor put inside a small plastic case. It works by detecting reflecting IR beam from a white-ish surface for a HIGH level output, while dark surface reflect much less IR beam for a LOW level output.

    But of course it only works with bright color handle! Yeah nah ~_~.

    Then I did some experiments with this reflection method. It never worked correctly. Then I changed the sensor design to interrupting instead of reflecting and it worked much more reliable.

    If you read to this point, congratulation! You are just bored and have nothing to do 😀

    And you probably wonder why would I want 2 soldering stations? The answer is: desodering SMD stuffs using 2 soldering irons, like 0805 or similar size. I just need to heat up 2 ends of the part and pick it up just like a tweezer.

    Hotair station is handy for SMD, but sometime this tweezer trick is a lot faster and safer especially working with PCB that has plastic connector such as JST XH. You simply don’t want to deform the connector, yeah?

    So yeah, after 8 years the very first soldering station I made still runs just fine. The cost of each station is about AU$60. Compare to Hakko FX951 ~AU$400 of the same function, it’s not bad at all.

    If you want the stand design by any chances, you can go to my github https://github.com/ceezblog/T12-Solder-station and look the the STL file in folder Enclosure

  • Writing windows app to communicate with UART Transparent BLE module – C# winform

    This post is about how to write a C# winform app to connect to your project using BLE module. This post is from point of view of an electronic engineer instead of software engineer and just focus on the BLE communication programming part only, especially for those UART transparent BLE modules, like RN4871. There is no complete sample in this post but just some hints and code snippet here and there to help with programming.

    Introduction

    Imagine, you write a windows app and you need to collect data from your microcontroller project, then pretty much you need to find a way to link them together, yeah? There are obvious choices:

    • USB to UART dongle and wire it up directly to UART port on your microcontroller
    • Implement wifi and IoT (internet of thing) software stack on your project and communicate with your host app via TCP-IP or so, like ESP8266 or ESP32
    • Use some transceiver modules and convert UART into RF, like RF24L01, CC1101, or just simply bit-bang data over a pair of 315 MHz transceiver modules
    • Use Bluetooth (known as BT classic) over virtual COM port, by using some module like HC-05
    • Or just use Bluetooth LE (BLE or bluetooth low energy)

    Below is the pictures that I stole from the internet. And yes, I have tried them all.

    If we wound back to 20-30 years ago, the obvious choice is BT classic and virtual COM port for a commercial product. Unless your project needs to dump a huge chunk of data without any loss, then you should choose TCP-IP over ethernet cable or wifi instead. But now, BLE is so popular these days, why not choosing BLE instead, yeah?

    Before I wrote SVI-Toolset app, I really struggled with researching BLE communication programming on windows PC: there was almost no windows app, nor there was a solid sample code for windows PC to connect to a BLE module. I am talking about off-the-shelf module like RN4871, CC2541… The only really working sample I could find was BLE console. It was around Feb 2023 when I started experiment with BLE and untill today, Aug 2025, I still couldn’t find a working sample from M$.

    Of course, there are some paid framework to work with BLE, which framework also compatible across muliple platforms, like Windows, MacOS, Linux… But for some small fry like me, it’s not possible to invest in a large sum of money and time for that.

    Okay, enough ranting, let’s dig into the good stuff!

    Prepare visual studio project

    You might need newest visual studio. At this time of writing, VS Community 2022 is free. Make sure you install WinUI application development (or windows ux for older visual studio).

    Just start a new winform project as usual, nothing special here. The trick is to add reference to windows ux library that provides headers for Bluetooth or Bluetooth Low Energy. Go to project list panel, add reference and browse to the header file:

    C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.22000.0\Windows.winmd

    If you can’t find this folder, then just download and install Windows SDK or choose a newer version that you have. I still use old version just fine, which version was for windows 10, instead of using newest version 10.0.26100

    You actually can use VS2013 and install Windows SDK for the same header instead of using VS2022.

    If you double-click on the the assembly name you will see all of the supported classes that this header provides. Among tons of classes this windows ux supports, we are interested in:

    So, just add a winform dialog maybe, and a couple of labels, buttons… Then <view code> of your “form1.cs” and add those classes to your code.

    Sorry if I am mixing terms from C++ and C#, I code embedded C more often than winform C#.

    C#
    using Windows.Devices.Bluetooth;using Windows.Devices.Bluetooth.Advertisement;using Windows.Devices.Bluetooth.GenericAttributeProfile;using Windows.Devices.Enumeration;using Windows.Devices.Radios;

    How BLE works? View from a different angle

    I’ll spare you the boring detail about BLE, GATT profile, GATT service… whatever. You can read it here https://www.bluetooth.com/. However, I am sure after you read/watch a sh!t load of those documents and videos about BLE, still you couldn’t write program to talk to a BLE module. But of course I have to include a tiny bit of info about this, just enough for you to write code.

    Okay, before I go on, I advice you to forget everything you know about BT classic and all definition of server/client or host/client you understand so far. I could have sworn those BLE definitions were made to brainf~ck with us. Just try not to compare BLE with everything you know, okay?

    Every communication should be 2-way, yeah? Unless your project is about just sending out data, like temperature sensor or an SOS beacon. So that you need send and receive which is 2-way communication between 2 devices. BLE stuff doesn’t provide a direct definition of send and receive like a conventional communication like UART or SPI. but for BLE, you have multiple GATT services (or GATT profiles) on a single device, but most of the time you have only 1 or 2 services. Each service may be for different purposes.

    You should only use your BLE device as BLE server and run the GATT services that your PC app can send request to. Below is what a BLE device should be like

    So each BLE differenciates to another by their name and their MAC address while GATT services and Characteristics are distinguish by their UUIDs. Those UUIDs above were mocked up, btw. Basically, it is just the same as company tag, division tag, and individual worker tag. You can define your own tags to BLE device as you like. Some of off-the-shelf BLE modules do allow you to change those tags, others don’t.

    Each characteristic could have multiple properties: read, write, notify… You don’t have to understand those properties. I can say each characteristic is a basket to hold the message so it can be 2-way or one-way. Normally we set UART-TX on one characteristic and UART-RX on another characteristic to elimate confusion. Datasheet of the UART transparent BLE module will tell you exactly which characteristic is to send or receive of UART.

    For of RN4871 (page 65 RN4871 user’s guide)

    • Service UUID: 49535343-FE7D-4AE5-8FA9-9FAFD205E455
    • Characteristic UUID for UART-TX: 49535343-1E4D-4BD9-BA61-23C647249616
    • Characteristic UUID for UART-TX: 49535343-8841-43F4-A8D4-ECBE34729BB3

    For WCH CH9141 (page 6 WCH BLE-TPT.pdf)

    • Transparent UART service UUID: 0xFFF0
    • Characteristic UUID for UART-TX: 0xFFF1
    • Characteristic UUID for UART-TX: 0xFFF2

    Code from PC side

    The work flow of communication to BLE device from PC app

    You have to declare in your code a few objects to deal with the hierarchy structure of BLE.

    C#
    BluetoothLEDevice bleObj;GattDeviceService gattSer;GattCharacteristic gattRX;GattCharacteristic gattTX;

    Your PC app must manage the BLE devices on its own. It seems more work, but it’s actually better for the user. Think about the old way, user has to find the correct COM port to connect to. It’s more trouble if user plugs in many devices that pop up as COM port, such as Arduino Leonardo, CP2102, CH910F. Arduino Leonardo driver is the most troublesome, as it pops up as different COM port when plugged in different USB port. Additionally, most users are not tech savvy who can open device manager to read the COM port number.

    To simplify, You need to

    • Scan for nearby BLE
    • Select the correct BLE – store it’s Mac address for future use
    • Assign that BLE device to bleObj
    • Assign correct Gatt Service to gattSer
    • Assign correct Gatt Characteristics to corresponding gattRX and gattTX
    • Add event listener that monitors ValueChanged of gattRX
    • Use gattRX object to receive data
    • Use gattTX object to send data

    Easy peasy, lemon squezzy!

    Scan for BLE devices

    There are two class you can use for scanning nearby BLE devices: BluetoothLEAdvertisementWatcher and DeviceWatcher

    Ok, so BLE is totally completely different beast to BT classic. You DO NOT go to Bluetooth and devices to add a new BLE device. In fact, you don’t have to pair with BLE device using system. This is true for Windows and Android. I don’t know about iOS or linux though.

    The ble “connected” status is just a virtual concept, as ble device only wake up, send data, then go back to sleep. Mostly. There is no need for pair or sync. Of course, BLE is not for transmitting data securely, so keep in mind that you should obscure your data before sending over BLE, by encoding or encrypting the payload.

    You have to manage BLE device inside your code: scan, connect and disconnect! Yup, disconnect just makes killing the “virtual link” between PC app and BLE device quicker. There is a timeout before the BLE module decides to accept “new link” and broadcast its advertisement again, for RN4871 this is about 5s.

    Okay, both BluetoothLEAdvertisementWatcher and DeviceWatcher can be used for scanning BLE beacons to detect nearby BLE devices, they are practically doing the same thing.

    The only different is BluetoothLEAdvertisementWatcher only listen for BLE beacon or BLE advertisement messages. This can be super useful to filter out non BLE device nearby. While DeviceWatcher will monitor all nearby devices.

    Using BluetoothLEAdvertisementWatcher is super easy

    C#
    // short versionBluetoothLEAdvertisementWatcher ble_watcher;ble_watcher = new BluetoothLEAdvertisementWatcher();ble_watcher.Received += (BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) => { /* add new device to the list here */ };// run the watcher when you need to scan nearby ble devicesble_watcher.Start();

    Longer version of

    C#
    // a class to hold string data, similar to struct of C++class My_BLE_Device {	public string name;  public ulong address;  public short RSSI;  public My_BLE_Device(string device_name, ulong device_address, short my_RSSI){  	name = device_name;    address = device_address;    RSSI = my_RSSI;  }}List<My_BLE_Device> _ble_dev_list = new List<My_BLE_Device>();void scan() {	BluetoothLEAdvertisementWatcher ble_watcher;	ble_watcher = new BluetoothLEAdvertisementWatcher();	ble_watcher.Received += BLE_watcher_receive;		// run the watcher when you need to scan nearby ble devices	ble_watcher.Start();}// callback function when a new BLE device pops up in the scannerprivate void BLE_watcher_receive(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) {  if (args.Advertisement.LocalName.Contains("SV") || args.Advertisement.LocalName.Contains("BLE")) { // found device  var found_ble_dev = new My_BLE_Device(args.Advertisement.LocalName, args.BluetoothAddress, args.RawSignalStrengthInDBm);  // check in our list if not exist  if (_ble_dev_list.Count == 0 || !_ble_dev_list.Exists(x => x.address.Equals(found_ble_dev.address))) {  	_ble_dev_list.Add(found_ble_dev);    }  }}

    Just have to add event listenner to run a callback function when receive an advertisement beacon. You get RSSI (received signal strength indicator) coming along with ble advertisement beacon.

    As sample code above, I only choose to add ble devices which name contains “SV” or “BLE”.

    In the other hand, using DeviceWatcher is a little bit of dark magic involved

    Below is some code I took from BLE Console app

    C#
    List<DeviceInformation> _deviceList = new List<DeviceInformation>();string _aqsAllBLEDevices = "(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")";string[] _requestedBLEProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.Bluetooth.Le.IsConnectable", };deviceWatcher = DeviceInformation.CreateWatcher(_aqsAllBLEDevices, _requestedBLEProperties, DeviceInformationKind.AssociationEndpoint);deviceWatcher.Updated += (_, __) => { }; // add an empty inline function for this event listenerdeviceWatcher.Added += (DeviceWatcher sender, DeviceInformation devInfo) => { if (_deviceList.FirstOrDefault(d => d.Id.Equals(devInfo.Id) || d.Name.Equals(devInfo.Name)) == null) _deviceList.Add(devInfo); };deviceWatcher.Start();

    There are a few voodoo stuffs to put into the initial constructor there, alright. You will get more detail about the device this way but you don’t have RSSI information. For my need, RSSI is more important than extra detail about a BLE device.

    Just a note: ESP32’s BLE stack does not play nice with BluetoothLEAdvertisementWatcher. Somehow ESP32 doesn’t advertise it’s ble name, it’s just blank! So, I suggest to use both if you are connect to ESP32 to fix this. If you plan to use ESP32 as a transparent UART passthrough for your project, I advise you not to go into this rabbit hole. Although ESP32 allows you to freely program ESP32 to do whatever you want it to do, but the lack of DMA stuffs of arduino framework, make it very difficult to do it correctly.

    Connect, Send and Receive data from BLE module

    So, you have a list of BLE candidates, you choose one to connect to and then you should check if the BLE device is the correct one.

    One way to do that is to match the UUIDs of the target BLE device with the UUIDs from the datasheet. The code blow is to check if the BLE device has the same UUIDs of TX and RX for RN4871 BLE module.

    C#
    async Task Connect_BLE(ulong dev_address){    // Try assign a BLE device using its address to bleObj    bleObj = await BluetoothLEDevice.FromBluetoothAddressAsync(dev_address).AsTask().TimeoutAfter(10000);    // Go through all of its available services    var result = await bleObj.GetGattServicesAsync(BluetoothCacheMode.Uncached);    if (result.Status == 0) { // status = 0 = no problem        bool found = false;        foreach (GattDeviceService ser in result.Services) { //search through services to get our target services            if (ser.Uuid.ToString().Equals("49535343-fe7d-4ae5-8fa9-9fafd205e455")) { // found our Gatt service for RN4871                gattSer = ser;                found = true;            }        }        if (!found) {    //if not found the correct gatt service            bleObj.Dispose(); //we got squat, so dispose of this object            return;        }				// we have alread found correct characteristic with the same unique id        var result2 = await gattSer.GetCharacteristicsAsync();        if (result2.Status == GattCommunicationStatus.Success && result2.Characteristics.Count>1) {            gattRX = result2.Characteristics[0];    // first characteristic should be RX            gattTX = result2.Characteristics[1];    // second characteristic should be TX            // check if UUIDs are match            if (!gattRX.Uuid.ToString().Equals("49535343-1e4d-4bd9-ba61-23c647249616") || !gattTX.Uuid.ToString().Equals("49535343-8841-43f4-a8d4-ecbe34729bb3")) {                bleObj.Dispose(); // no match --> dispose                return;            }        }        else {            bleObj.Dispose();            return;        }        // looking good, we got a solid connection        // Subcribe to value_changed on RX characteristic => callback Characteristic_ValueChanged()        var status = await gattRX.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);        if (status == GattCommunicationStatus.Success) {            gattRX.ValueChanged += Characteristic_ValueChanged;            Callback_DeviceConnected(); // run some routine after have a solid link to BLE        }        return; // Connect successfully     }        // handle error    tb_Stat.AppendText("\r\nTimeout - Connect fail.");}

    Once UUIDs are verified, you have a solid target to read and write data to. So just use gattRX object and gattTX object to send and receive data.

    To “connect” or establish a “link” to BLE module, you just write something to BLE device and wait for response. Like the sample below, I write a change of configuration and set it to notify on the receiving GATT characteristic.

    C#
    var status = await gattRX.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);if (status == GattCommunicationStatus.Success){    gattRX.ValueChanged += Characteristic_ValueChanged; // monitor if value of this characteristic changed    isConnect = true;    Callback_DeviceConnected();}

    If success, then the app knows that BLE module is ready to reply to request of the app. Again, there is no definition of “connected” concept for BLE, I just make it up for more intuitive usage.

    On PC side, you will have send function like this

    C#
    // global declareGattCharacteristic gattTX;async Task SendData_BLE(byte[] data){    if (!isConnect) return;    var writer = new DataWriter();    writer.WriteBytes(data);    // WriteByte used for simplicity    await gattTX.WriteValueAsync(writer.DetachBuffer());}// in data preparation functionbyte[] data = new byte[5];data[0] = (byte)_BLE_MSG_ID.BM_REQUEST_BATTERY_VOLTAGE;data[1] = (byte)'0';data[2] = (byte)'0';data[3] = (byte)_BLE_MSG_ID.BM_SEPARATOR;_ = SendData_BLE(data); // assign an empty holder for this task

    So, you write some data to a GATT service, that assosiates with TX line of UART, and magically on the other end of BLE module, it spits out the same data on its UART port.

    Receive function is like this

    C#
    // global declareGattCharacteristic gattRX;// add event listener for when the value of that GATT service has changedgattRX.ValueChanged -= Characteristic_ValueChanged;// Callback funtion for event listenervoid Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args){    byte[] data;    CryptographicBuffer.CopyToByteArray(args.CharacteristicValue, out data);    ProcessData(data);}

    You add an event listener to GATT characteristic object and then when data poured in, the callback function will be invoked with the message from UART port.

    The problem is, one BLE message contains multiple bytes (characters) in one payload while each payload on UART line is just a single byte. A broastcasting interval must be introduced to send a bunch of bytes after some time, like 100ms for CH9141 and about 50ms for RN4871. Basically, after 50ms, BLE module just dumps all the data it currently holds to PC app.

    The maximum size of payload (or MTU, Maximum Transmission Unit) is dictated by the firmware of that BLE module. RN4871 fw1.1.8 only does 20 chars as payload while RN4871 fw1.3.0 can do 50 chars as payload. So that your message will be cut into 2 ble messages instead of 1.

    On the PC app, if you receive each payload and process each payload individually, you might have corrupted data.

    To overcome this, you can build yourself a custom protocol to recognize your data package with unique identifiers, like

    Message #1Message #2
    AAAAhello_to_my_friendsZZZZ

    AAAA marks the beginning of your data and ZZZZ marks the end of data. So that you just collect multiple messages continuously but only process those messages when you see both AAAA and ZZZZ in the data you collected.

    You can implement a fix-frame format like below for each BLE payload, assuming each data field is a 16bit number

    C#
    // 4 data fields separated by comma in a single ble payload[data_field_1],[data_field_2],[data_field_3],[data_field_4]// expand it into individual byte[byte1][byte2][,][byte4][byte5][,][byte7][byte8]...

    You can use your own creativity to make a suitable frame format for you.

    For ESP32, it is possible to change the MTU from default 23 bytes to a higher number such as 500 bytes. Which indeed will give you more flexibility to frame your data.

    Anyhow, I had a few bad experience with ESP32, both hardware and software. It still leaves bad taste in my mouth after about 5 years already. So that I don’t recommend using ESP32 for something that needs to be reliable and long lasting.

    Additional security stuff on the BLE device side

    Most of the UART transparent BLE modules don’t have a bluetooth profile, which auto “connects” to last paired device and does not allow new device to pair with, like bluetooth HID or bluetooth a2dp… Any client (your pc app from different PC) can connect to it at will without any pin code or so.

    Basically, if only you have the app and only you have the BLE device, then there is not a possibility someone tamper your device over BLE connection. But if you use this on a commercial product, this could be very bad. Imagine that you can freely pair with your neighbour BT speaker and you play heavy rock music at 2AM in the morning. Yeah, that problem.

    The PIN code paring of BLE stack is quite finicky and doesn’t work. So that you should implement software password check for yourself:

    • The device still allows connection but for a few seconds, just like you have 60s to disable house alarm after you unlock front door
    • Sustain a connection only after client sends correct pin code
    • Disconnect if the client doesn’t send correct pin code after a few tries
    • Reject connection after a delay, sending a disconnect notification. The time delay would deter brute force attack

    You should manage these code in your application microcontroller instead, which microcontroller that BLE module wires to via UART port.

    You can reject connection by reset the BLE module (pull RST pin to ground), disrupt power to the BLE module or even “enter programming” and issue a disconnect command manually.

    If your product is used or is going to be used by a large number users you should take security seriously.

    In summarise

    This post is not a tutorial for you to write C# code to connect to your BLE. It’s just some pointers and a few code snipet here and there.

    1. Choose a suitable BLE module as transparent UART passthrough.
    2. Read datasheet for its UUIDs for connecting to.
    3. Prepare Visual Studio project with reference to Windows UX package that provides Bluetooth relate classes
    4. Write your winform app that can
      • Manage the list of available BLE devices
      • Check if the select device is the correct target
      • Send BLE messages
      • Receive BLE messages
    5. Write pin code feature in your application microcontroller

    And that’s that!

  • Hello world, again and again!

    Start getting my blog running again. It’s painfully slow. You know, it’s life.