221 lines
6.8 KiB
Python
221 lines
6.8 KiB
Python
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"))
|
||
try:
|
||
|
||
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)
|
||
except:
|
||
pass
|
||
finally:
|
||
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())
|