NanoVNA-H4 Bluetooth trial #1
This series of articles documents a trial of an Bluetooth link for remote operation of a NanoVNA-H4.
There are risks in fitting a radio transmitter in close proximity to RF measurement equipment. Those risks can be mitigated by just not doing it, but with care, it may be possible to achieve the utility of remote operation without degradation of the instrument.
The Bluetooth adapter is an external HC-05 adapter, <$5 on Aliexpress, configured for 38400bps.
The trial started with NanoVNA-D v1.2.35 firmware.
This should be straightforward as people claim to have this working.
Note than HC-05 modules are not all the same, and different performance may be obtained from different manufacturers products and different firmware versions. The one tested here was labelled hc01.com… but it is Chinese product and Chinese are copyists, it means little. Firmware reports version hc01.com v2.1.
AT+VERSION +VERSION:hc01.comV2.1 OK
The module uses a CSR BC417 Bluetooth chip. The Windows end is unknown, but relatively new.
Some loopback throughput tests using Ezurio terminal
Above, results hint high protocol overhead.
Above, close to ideal throughput for async encoding.
Problems encountered
System hangs sometimes
It sort of worked, but there was excessive delay in running a single scan from NanoVNA-App, though continuous scanning worked ok (after the same start up delay).
To help locate the problem:
- a test was run of the UART connection to the VNA using a USB to serial adapter, so a wired connection; and
- an extended bit error rate test was run on the Bluetooth link looped back at the HC-05.
Both comms links were reliable so the problem was the NanoVNA or NanoVNA-App.
Throughput at 38400 was 77%, quite close to the async encoding limit of 80%. Throughput at 230400 was 42%, not much better than half the async encoding limit of 80%.
A communications trace revealed that the NanoVNA appeared to hang on a pause command.
The issue was reported to Dislord, and the defect in NanoVNA-D v1.2.35 firmware fixed quite quickly (NanoVNA-D v1.2.37).
Downloading screenshots fails
I have a script set that are derived from a script published by Ho-Ro, and my script failed on the slow link.
That turned out to be a read timeout as the transfers are quite large, eg 307k which takes about 80s on an error free uncongested Bluetooth connection at 38400bps.
Higher speeds were tried, 115200bps failed, so I reverted to 38400bps which seems reliable.
In any event, the script needs facility to adjust timeout appropriately.
To do that, a baudrate parameter was added to the script, and it calculates a timeout equal to twice the time to transfer the calculated transfer size. If no baudrate is specified, it defaults to 1s timeout.
Here is the modified script.
#!/usr/bin/python # SPDX-License-Identifier: GPL-3.0-or-later ''' Command line tool to capture a screen shot from NanoVNA or tinySA connect via USB serial, issue the command 'capture' and fetch 320x240 or 480x320 rgb565 pixel. These pixels are converted to rgb888 values that are stored as an image (e.g. png) ''' import argparse from datetime import datetime import serial from serial.tools import list_ports import struct import sys import numpy from PIL import Image import PIL.ImageOps from pathlib import Path import re # ChibiOS/RT Virtual COM Port VID = 0x0483 #1155 PID = 0x5740 #22336 app=Path(__file__).stem print(f'{app}_v0.4') # Get nanovna device automatically def getdevice() -> str: device_list = list_ports.comports() for device in device_list: if device.vid == VID and device.pid == PID: return device raise OSError("device not found") #extract default device name from script name devicename=re.sub(r'capture_(.*)\.py',r'\1',(Path(__file__).name).lower()) # construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument( '-b', '--baud', dest = 'baudrate', help = 'com port' ) ap.add_argument( '-c', '--com', dest = 'comport', help = 'com port' ) ap.add_argument( "-d", "--device", help="device type" ) ap.add_argument( "-o", "--out", help="write the data into file OUT" ) ap.add_argument( "-s", "--scale", help="scale image s*",default=2 ) options = ap.parse_args() nanodevice = options.comport or getdevice() if options.device!=None: devicename = options.device outfile = options.out sf=float(options.scale) print(devicename) if devicename == 'tinysa': width = 480 height = 320 elif devicename == 'nanovnah': width = 320 height = 240 elif devicename == 'tinysaultra': # 4" device width = 480 height = 320 elif devicename == 'nanovnah4': # 4" device width = 480 height = 320 elif devicename == 'tinypfa': # 4" device width = 480 height = 320 else: sys.exit('Unknown device name.'); # NanoVNA sends captured image as 16 bit RGB565 pixel size = width * height crlf = b'\r\n' prompt = b'ch> ' # do the communication if(options.baudrate!=None): baudrate=int(options.baudrate) stimeout=int(size*2*2/baudrate*10) else: baudrate=9600 stimeout=1 print('Setting image donwload timeout to {0:d}s'.format(stimeout)) with serial.Serial( nanodevice, baudrate=baudrate, timeout=1 ) as nano_tiny: # open serial connection nano_tiny.write( b'pause\r' ) # stop screen update echo = nano_tiny.read_until( b'pause' + crlf + prompt ) # wait for completion #print( echo ) nano_tiny.write( b'capture\r' ) # request screen capture echo = nano_tiny.read_until( b'capture' + crlf ) # wait for start of transfer # print( echo ) nano_tiny.timeout=stimeout bytestream = nano_tiny.read( 2 * size ) # print('bytestream ', len(bytestream)) nano_tiny.timeout=1 echo = nano_tiny.read_until( prompt ) # wait for cmd completion # print( echo ) nano_tiny.write( b'resume\r' ) # resume the screen update echo = nano_tiny.read_until( b'resume' + crlf + prompt ) # wait for completion #print( echo ) if len( bytestream ) != 2 * size: print( 'capture error - wrong screen size?' ) sys.exit() # convert bytestream to 1D word array rgb565 = struct.unpack( f'>{size}H', bytestream ) #create new image array a=[0]*3 a=[a]*width a=[a]*height a=numpy.array(a, dtype=numpy.uint8) for x in range (0,height): for y in range (0,width): index = y+width*x pixel = rgb565[index] a[x,y,0]=(pixel&0xf800)>>8 a[x,y,1]=(pixel&0x07e0)>>3 a[x,y,2]=(pixel&0x001f)<<3 image=Image.fromarray(a,"RGB") #some transforms image=image.resize((int(sf*width),int(sf*height))) inverted_image=PIL.ImageOps.invert(image) #save files filename = options.out or datetime.now().strftime( f'{devicename}_%Y%m%d_%H%M%S' ) image.save(filename + '.png') inverted_image.save(filename + 'i.png')
Note that the script picks up a default device name from the script name, so capture_nanovnah4.py defaults to nanovnah4 device type.
UART connector
For more convenient access to the UART pins, I installed a SIL 6w female header and cut a 3x18mm opening in the back for access.
Conclusions
This is not quite the no-brainer. The problems encountered were related to the specific firmware and my script (though Ho-Ro’s script may have the same issue). One is left wondering whether there were unresolved problems in some claimed implementations using this firmware.
That said, the prototype appears to work ok. It now needs to be packaged and some tests made of EMC including noise floor degradation.