import time import os import sys import serial import csv from tsapython import tinySA import datetime import numpy as np import matplotlib.pyplot as plt import progressbar ser = serial.Serial("/dev/ttyACM0", 115200) max_x = 250 max_y = 200 max_z = 150 min_z = 48 z_search = 100 prboffs_x = -10.0 prboffs_y = 25.0 prboffs_z = 0.0 feedrate = 5000 now = datetime.datetime.now() def convert_data_to_arrays(start, stop, pts, data): # using the start and stop frequencies, and the number of points, freq_arr = np.linspace( start, stop, pts ) # note that the decimals might go out to many places. # you can truncate this because it’s only used # for plotting in this example # As of the Jan. 2024 build in some data returned with SWEEP or SCAN calls there is error data. # https://groups.io/g/tinysa/topic/tinasa_ultra_sweep_command/104194367 # this shows up as "-:.000000e+01". # TEMP fix - replace the colon character with a -10. This puts the 'filled in' points around the noise floor. # more advanced filtering should be applied for actual analysis. data1 = bytearray(data.replace(b"-:.0", b"-10.0")) # get both values in each row returned (for reference) # data_arr = [list(map(float, line.split())) for line in data.decode('utf-8').split('\n') if line.strip()] # get first value in each returned row data_arr = [ float(line.split()[0]) for line in data1.decode("utf-8").split("\n") if line.strip() ] return freq_arr, data_arr def writeCSV(freq_array, data, csv_filename, args): try: with open(csv_filename, "w", newline="") as csvfile: writer = csv.writer(csvfile, delimiter=";", quoting=csv.QUOTE_MINIMAL) writer.writerow( [f'# Nearfield Scan {now.strftime("%Y-%m-%d %H:%M")}, {args.desc}'] ) row = [] row.append("x") row.append("y") for elem in freq_array: row.append(str(elem)) writer.writerow(row) for key in data: row = [] row.append(key[0]) row.append(key[1]) for i in range(len(data[key])): row.append(data[key][i]) writer.writerow(row) except IOError: print("I/O error") return def initXYZ(): ser.write(str.encode("M17 \r\n")) ser.write(str.encode("M84 X Y Z S12000\r\n")) ser.write(str.encode("M84 E\r\n")) ser.write(str.encode("G28\r\n")) # set to absolute movement ser.write(str.encode("G90\r\n")) time.sleep(20) def main(): import argparse import configparser parser = argparse.ArgumentParser( prog="3D NearField Scanner", description="Uses a 3D Printer and a Tiny SA for measureing the near field of a PCB", epilog="=^.^=", ) parser.add_argument("--fstart", type=float, default=1e6) parser.add_argument("--fstop", type=float, default=800e6) parser.add_argument("--pts", type=int, default=450) parser.add_argument( "--rbw", help="RBW in kHz", default="auto", choices=["200", "1", "3", "10", "30", "100", "300", "600", "850"], ) parser.add_argument("--xstart", type=int, default=0, help="x start in mm") parser.add_argument("--xend", type=int, default=250, help="x end in mm") parser.add_argument("--ystart", type=int, default=0, help="y start in mm") parser.add_argument("--yend", type=int, default=200, help="y end in mm") parser.add_argument("--xpts", type=int, default=10, help="number of x points") parser.add_argument("--ypts", type=int, default=10, help="y number of y points") parser.add_argument("--desc", type=str, default="", help="EUT Description") parser.add_argument("--out", "-o", type=str, default="nearfieldscan", help="outfile Name") parser.add_argument("--home", "-H", action="store_true", help="home printer") args = parser.parse_args() if args.home: initXYZ() progress=0 b = progressbar.ProgressBar(maxval=args.xpts*args.ypts) b.start() # scan surface ser.write(str.encode(f"G1 Z{min_z}\r\n")) input("Attach Probe and press Enter to continue...") print("Starting Measurement") # create a new tinySA object tsa = tinySA() # set the return message preferences tsa.set_verbose(False) # detailed messages tsa.set_error_byte_return(True) # get explicit b'ERROR' if error thrown # attempt to autoconnect found_bool, connected_bool = tsa.autoconnect() # if port closed, then return error message if connected_bool == False: print("ERROR: could not connect to port") else: # if port found and connected, then complete task(s) and disconnect if args.rbw == "auto": tsa.set_rbw_auto() else: tsa.rbw(int(args.rbw)) datadict = {} ser.write(str.encode(f"G1 Z{z_search} F{feedrate}\r\n")) for y in np.linspace(args.ystart, args.yend, args.ypts,endpoint=True): if y >= max_y: break else: ser.write(str.encode(f"G1 X{args.xstart+prboffs_x} Y{y+prboffs_y} F{feedrate}\r\n")) time.sleep(2) for x in np.linspace(args.xstart, args.xend, args.xpts, endpoint=True): if x >= max_x: break else: ser.write(str.encode(f"G1 X{x+prboffs_x} Z{z_search} F{feedrate}\r\n")) time.sleep(1) ser.write(str.encode(f"G1 Z{min_z} F{feedrate}\r\n")) ser.write(str.encode("M18 \r\n")) # disable steppers time.sleep(2) tsa.resume() # scan data_bytes = tsa.scan(args.fstart, args.fstop, args.pts, 2) tsa.wait time.sleep(2) ser.write(str.encode("M17 X Y Z\r\n")) # enable steppers ser.write(str.encode(f"G1 Z{z_search} F{feedrate}\r\n")) freq_arr, data_arr = convert_data_to_arrays( args.fstart, args.fstop, args.pts, data_bytes ) datadict[(x, y)] = data_arr time.sleep(1) progress+=1 b.update(progress + 1) b.finish() writeCSV( freq_arr, datadict, f'{now.strftime("%Y-%m-%d_%H%M")}_{args.out}.csv', args, ) tsa.resume() # resume so screen isn't still frozen tsa.disconnect() if __name__ == "__main__": sys.exit(main())