mirror of
https://github.com/ttrftech/NanoVNA.git
synced 2025-12-06 03:31:59 +01:00
Merge branch 'ttrftech-master'
This commit is contained in:
commit
e37ef6eabc
2
Makefile
2
Makefile
|
|
@ -229,7 +229,7 @@ flash: build/ch.bin
|
|||
dfu-util -d 0483:df11 -a 0 -s 0x08000000:leave -D build/ch.bin
|
||||
|
||||
dfu:
|
||||
printf "reset dfu\r" >/dev/cu.usbmodem401
|
||||
-@printf "reset dfu\r" >/dev/cu.usbmodem401
|
||||
|
||||
TAGS: Makefile
|
||||
@etags *.[ch] NANOVNA_STM32_F072/*.[ch] $(shell find ChibiOS/os/hal/ports/STM32/STM32F0xx ChibiOS/os -name \*.\[ch\] -print)
|
||||
|
|
|
|||
196
main.c
196
main.c
|
|
@ -39,7 +39,10 @@ static void apply_error_term_at(int i);
|
|||
static void cal_interpolate(int s);
|
||||
|
||||
void apply_edelay_at(int i);
|
||||
void sweep(void);
|
||||
void set_frequencies(uint32_t start, uint32_t stop, int16_t points);
|
||||
void update_frequencies(void);
|
||||
|
||||
bool sweep(bool break_on_operation);
|
||||
|
||||
static MUTEX_DECL(mutex);
|
||||
|
||||
|
|
@ -49,11 +52,10 @@ static MUTEX_DECL(mutex);
|
|||
int32_t frequency_offset = 5000;
|
||||
int32_t frequency = 10000000;
|
||||
int8_t drive_strength = DRIVE_STRENGTH_AUTO;
|
||||
int8_t frequency_updated = FALSE;
|
||||
int8_t sweep_enabled = TRUE;
|
||||
int8_t sweep_once = FALSE;
|
||||
int8_t cal_auto_interpolate = TRUE;
|
||||
uint16_t redraw_request = 0; // contains REDRAW_XXX flags
|
||||
int8_t stop_the_world = FALSE;
|
||||
int16_t vbat = 0;
|
||||
|
||||
|
||||
|
|
@ -64,32 +66,38 @@ static THD_FUNCTION(Thread1, arg)
|
|||
chRegSetThreadName("sweep");
|
||||
|
||||
while (1) {
|
||||
if (stop_the_world) {
|
||||
__WFI();
|
||||
continue;
|
||||
bool completed = false;
|
||||
if (sweep_enabled || sweep_once) {
|
||||
chMtxLock(&mutex);
|
||||
completed = sweep(true);
|
||||
sweep_once = FALSE;
|
||||
chMtxUnlock(&mutex);
|
||||
} else {
|
||||
__WFI();
|
||||
}
|
||||
|
||||
if (sweep_enabled) {
|
||||
chMtxLock(&mutex);
|
||||
sweep();
|
||||
chMtxUnlock(&mutex);
|
||||
} else {
|
||||
__WFI();
|
||||
ui_process();
|
||||
}
|
||||
|
||||
if (vbat != -1) {
|
||||
if (vbat != -1) {
|
||||
adc_stop(ADC1);
|
||||
vbat = adc_vbat_read(ADC1);
|
||||
touch_start_watchdog();
|
||||
draw_battery_status();
|
||||
}
|
||||
|
||||
/* calculate trace coordinates and plot only if scan completed */
|
||||
if (completed) {
|
||||
plot_into_index(measured);
|
||||
redraw_request |= REDRAW_CELLS;
|
||||
}
|
||||
|
||||
/* plot trace and other indications as raster */
|
||||
draw_all(completed); // flush markmap only if scan completed to prevent remaining traces
|
||||
|
||||
chMtxUnlock(&mutex);
|
||||
}
|
||||
|
||||
/* calculate trace coordinates */
|
||||
plot_into_index(measured);
|
||||
|
||||
/* plot trace as raster */
|
||||
draw_all();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,6 +228,12 @@ static void cmd_resume(BaseSequentialStream *chp, int argc, char *argv[])
|
|||
(void)chp;
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
// restore frequencies array and cal
|
||||
update_frequencies();
|
||||
if (cal_auto_interpolate && (cal_status & CALSTAT_APPLY))
|
||||
cal_interpolate(lastsaveid);
|
||||
|
||||
resume_sweep();
|
||||
}
|
||||
|
||||
|
|
@ -462,13 +476,15 @@ static void cmd_data(BaseSequentialStream *chp, int argc, char *argv[])
|
|||
if (sel == 0 || sel == 1) {
|
||||
chMtxLock(&mutex);
|
||||
for (i = 0; i < sweep_points; i++) {
|
||||
chprintf(chp, "%f %f\r\n", measured[sel][i][0], measured[sel][i][1]);
|
||||
if (frequencies[i] != 0)
|
||||
chprintf(chp, "%f %f\r\n", measured[sel][i][0], measured[sel][i][1]);
|
||||
}
|
||||
chMtxUnlock(&mutex);
|
||||
} else if (sel >= 2 && sel < 7) {
|
||||
chMtxLock(&mutex);
|
||||
for (i = 0; i < sweep_points; i++) {
|
||||
chprintf(chp, "%f %f\r\n", cal_data[sel-2][i][0], cal_data[sel-2][i][1]);
|
||||
if (frequencies[i] != 0)
|
||||
chprintf(chp, "%f %f\r\n", cal_data[sel-2][i][0], cal_data[sel-2][i][1]);
|
||||
}
|
||||
chMtxUnlock(&mutex);
|
||||
} else {
|
||||
|
|
@ -506,10 +522,7 @@ static void cmd_capture(BaseSequentialStream *chp, int argc, char *argv[])
|
|||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
// pause sweep
|
||||
stop_the_world = TRUE;
|
||||
|
||||
chThdSleepMilliseconds(1000);
|
||||
chMtxLock(&mutex);
|
||||
|
||||
// use uint16_t spi_buffer[1024] (defined in ili9341) for read buffer
|
||||
uint16_t *buf = &spi_buffer[0];
|
||||
|
|
@ -532,7 +545,7 @@ static void cmd_capture(BaseSequentialStream *chp, int argc, char *argv[])
|
|||
}
|
||||
//*/
|
||||
|
||||
stop_the_world = FALSE;
|
||||
chMtxUnlock(&mutex);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
|
@ -633,48 +646,13 @@ ensure_edit_config(void)
|
|||
cal_status = 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void cmd_scan(BaseSequentialStream *chp, int argc, char *argv[])
|
||||
{
|
||||
float gamma[2];
|
||||
int i;
|
||||
int32_t freq, step;
|
||||
int delay;
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
pause_sweep();
|
||||
chMtxLock(&mutex);
|
||||
|
||||
freq = frequency0;
|
||||
step = (frequency1 - frequency0) / (sweep_points-1);
|
||||
set_frequency(freq);
|
||||
delay = 4;
|
||||
for (i = 0; i < sweep_points; i++) {
|
||||
freq = freq + step;
|
||||
wait_dsp(delay);
|
||||
delay = set_frequency(freq);
|
||||
palClearPad(GPIOC, GPIOC_LED);
|
||||
calculate_gamma(gamma);
|
||||
palSetPad(GPIOC, GPIOC_LED);
|
||||
chprintf(chp, "%d %d\r\n", gamma[0], gamma[1]);
|
||||
}
|
||||
chMtxUnlock(&mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
// main loop for measurement
|
||||
void sweep(void)
|
||||
bool sweep(bool break_on_operation)
|
||||
{
|
||||
int i;
|
||||
int delay;
|
||||
|
||||
rewind:
|
||||
frequency_updated = FALSE;
|
||||
//delay = 3;
|
||||
|
||||
for (i = 0; i < sweep_points; i++) {
|
||||
delay = set_frequency(frequencies[i]);
|
||||
int delay = set_frequency(frequencies[i]);
|
||||
tlv320aic3204_select_in3(); // CH0:REFLECT
|
||||
wait_dsp(delay);
|
||||
|
||||
|
|
@ -699,15 +677,51 @@ void sweep(void)
|
|||
if (electrical_delay != 0)
|
||||
apply_edelay_at(i);
|
||||
|
||||
ui_process();
|
||||
if (redraw_request)
|
||||
break; // return to redraw screen asap.
|
||||
|
||||
if (frequency_updated)
|
||||
goto rewind;
|
||||
// back to toplevel to handle ui operation
|
||||
if (operation_requested && break_on_operation)
|
||||
return false;
|
||||
}
|
||||
|
||||
transform_domain();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cmd_scan(BaseSequentialStream *chp, int argc, char *argv[])
|
||||
{
|
||||
int32_t start, stop;
|
||||
int16_t points = sweep_points;
|
||||
|
||||
if (argc != 2 && argc != 3) {
|
||||
chprintf(chp, "usage: sweep {start(Hz)} {stop(Hz)} [points]\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
start = atoi(argv[0]);
|
||||
stop = atoi(argv[1]);
|
||||
if (start == 0 || stop == 0 || start > stop) {
|
||||
chprintf(chp, "frequency range is invalid\r\n");
|
||||
return;
|
||||
}
|
||||
if (argc == 3) {
|
||||
points = atoi(argv[2]);
|
||||
if (points <= 0 || points > sweep_points) {
|
||||
chprintf(chp, "sweep points exceeds range\r\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pause_sweep();
|
||||
chMtxLock(&mutex);
|
||||
set_frequencies(start, stop, points);
|
||||
if (cal_auto_interpolate && (cal_status & CALSTAT_APPLY))
|
||||
cal_interpolate(lastsaveid);
|
||||
|
||||
sweep_once = TRUE;
|
||||
chMtxUnlock(&mutex);
|
||||
|
||||
// wait finishing sweep
|
||||
while (sweep_once)
|
||||
chThdSleepMilliseconds(10);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -742,32 +756,37 @@ update_marker_index(void)
|
|||
}
|
||||
|
||||
void
|
||||
update_frequencies(void)
|
||||
set_frequencies(uint32_t start, uint32_t stop, int16_t points)
|
||||
{
|
||||
int i;
|
||||
int32_t span;
|
||||
int32_t start;
|
||||
uint32_t span = (stop - start) / 1000; /* prevents overflow because of maximum of int32_t(2.147e+9) */
|
||||
for (i = 0; i < points; i++)
|
||||
frequencies[i] = start + span * i / (points - 1) * 1000;
|
||||
for (; i < sweep_points; i++)
|
||||
frequencies[i] = 0;
|
||||
}
|
||||
|
||||
void
|
||||
update_frequencies(void)
|
||||
{
|
||||
uint32_t start, stop;
|
||||
if (frequency1 > 0) {
|
||||
start = frequency0;
|
||||
span = (frequency1 - frequency0)/100;
|
||||
stop = frequency1;
|
||||
} else {
|
||||
int center = frequency0;
|
||||
span = -frequency1;
|
||||
int32_t center = frequency0;
|
||||
int32_t span = -frequency1;
|
||||
start = center - span/2;
|
||||
span /= 100;
|
||||
stop = center + span/2;
|
||||
}
|
||||
|
||||
for (i = 0; i < sweep_points; i++)
|
||||
frequencies[i] = start + span * i / (sweep_points - 1) * 100;
|
||||
|
||||
set_frequencies(start, stop, sweep_points);
|
||||
update_marker_index();
|
||||
|
||||
frequency_updated = TRUE;
|
||||
// set grid layout
|
||||
update_grid();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
freq_mode_startstop(void)
|
||||
{
|
||||
|
|
@ -801,7 +820,6 @@ void
|
|||
set_sweep_frequency(int type, float frequency)
|
||||
{
|
||||
int32_t freq = frequency;
|
||||
bool cal_applied = cal_status & CALSTAT_APPLY;
|
||||
switch (type) {
|
||||
case ST_START:
|
||||
freq_mode_startstop();
|
||||
|
|
@ -881,7 +899,7 @@ set_sweep_frequency(int type, float frequency)
|
|||
break;
|
||||
}
|
||||
|
||||
if (cal_auto_interpolate && cal_applied)
|
||||
if (cal_auto_interpolate && (cal_status & CALSTAT_APPLY))
|
||||
cal_interpolate(lastsaveid);
|
||||
}
|
||||
|
||||
|
|
@ -915,8 +933,7 @@ static void cmd_sweep(BaseSequentialStream *chp, int argc, char *argv[])
|
|||
chprintf(chp, "%d %d %d\r\n", frequency0, frequency1, sweep_points);
|
||||
return;
|
||||
} else if (argc > 3) {
|
||||
chprintf(chp, "usage: sweep {start(Hz)} [stop] [points]\r\n");
|
||||
return;
|
||||
goto usage;
|
||||
}
|
||||
if (argc >= 2) {
|
||||
if (strcmp(argv[0], "start") == 0) {
|
||||
|
|
@ -944,12 +961,18 @@ static void cmd_sweep(BaseSequentialStream *chp, int argc, char *argv[])
|
|||
|
||||
if (argc >= 1) {
|
||||
int32_t value = atoi(argv[0]);
|
||||
if (value == 0)
|
||||
goto usage;
|
||||
set_sweep_frequency(ST_START, value);
|
||||
}
|
||||
if (argc >= 2) {
|
||||
int32_t value = atoi(argv[1]);
|
||||
set_sweep_frequency(ST_STOP, value);
|
||||
}
|
||||
return;
|
||||
usage:
|
||||
chprintf(chp, "usage: sweep {start(Hz)} [stop(Hz)]\r\n");
|
||||
chprintf(chp, "\tsweep {start|stop|center|span|cw} {freq(Hz)}\r\n");
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1718,7 +1741,8 @@ static void cmd_frequencies(BaseSequentialStream *chp, int argc, char *argv[])
|
|||
(void)argc;
|
||||
(void)argv;
|
||||
for (i = 0; i < sweep_points; i++) {
|
||||
chprintf(chp, "%d\r\n", frequencies[i]);
|
||||
if (frequencies[i] != 0)
|
||||
chprintf(chp, "%d\r\n", frequencies[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1888,7 +1912,7 @@ static const ShellCommand commands[] =
|
|||
{ "power", cmd_power },
|
||||
{ "sample", cmd_sample },
|
||||
//{ "gamma", cmd_gamma },
|
||||
//{ "scan", cmd_scan },
|
||||
{ "scan", cmd_scan },
|
||||
{ "sweep", cmd_sweep },
|
||||
{ "test", cmd_test },
|
||||
{ "touchcal", cmd_touchcal },
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ void redraw_marker(int marker, int update_info);
|
|||
void trace_get_info(int t, char *buf, int len);
|
||||
void plot_into_index(float measured[2][101][2]);
|
||||
void force_set_markmap(void);
|
||||
void draw_all(void);
|
||||
void draw_all(bool flush);
|
||||
|
||||
void draw_cal_status(void);
|
||||
|
||||
|
|
|
|||
20
plot.c
20
plot.c
|
|
@ -1248,7 +1248,7 @@ draw_cell(int m, int n)
|
|||
}
|
||||
|
||||
void
|
||||
draw_all_cells(void)
|
||||
draw_all_cells(bool flush_markmap)
|
||||
{
|
||||
int m, n;
|
||||
for (m = 0; m < (area_width+CELLWIDTH-1) / CELLWIDTH; m++)
|
||||
|
|
@ -1257,17 +1257,19 @@ draw_all_cells(void)
|
|||
draw_cell(m, n);
|
||||
}
|
||||
|
||||
// keep current map for update
|
||||
swap_markmap();
|
||||
// clear map for next plotting
|
||||
clear_markmap();
|
||||
if (flush_markmap) {
|
||||
// keep current map for update
|
||||
swap_markmap();
|
||||
// clear map for next plotting
|
||||
clear_markmap();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
draw_all(void)
|
||||
draw_all(bool flush)
|
||||
{
|
||||
draw_all_cells();
|
||||
|
||||
if (redraw_request & REDRAW_CELLS)
|
||||
draw_all_cells(flush);
|
||||
if (redraw_request & REDRAW_FREQUENCY)
|
||||
draw_frequencies();
|
||||
if (redraw_request & REDRAW_CAL_STATUS)
|
||||
|
|
@ -1285,7 +1287,7 @@ redraw_marker(int marker, int update_info)
|
|||
if (update_info)
|
||||
markmap[current_mappage][0] = 0xffff;
|
||||
|
||||
draw_all_cells();
|
||||
draw_all_cells(TRUE);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -3,8 +3,19 @@ import serial
|
|||
import numpy as np
|
||||
import pylab as pl
|
||||
import scipy.signal as signal
|
||||
import time
|
||||
import struct
|
||||
from serial.tools import list_ports
|
||||
|
||||
VID = 0x0483 #1155
|
||||
PID = 0x5740 #22336
|
||||
|
||||
# Get nanovna device automatically
|
||||
def getport() -> str:
|
||||
device_list = list_ports.comports()
|
||||
for device in device_list:
|
||||
if device.vid == VID and device.pid == PID:
|
||||
return device.device
|
||||
raise OSError("device not found")
|
||||
|
||||
REF_LEVEL = (1<<9)
|
||||
|
||||
|
|
@ -31,7 +42,7 @@ class NanoVNA:
|
|||
def frequencies(self):
|
||||
return self._frequencies
|
||||
|
||||
def set_sweep(self, start = 1e6, stop = 900e6, points = None):
|
||||
def set_frequencies(self, start = 1e6, stop = 900e6, points = None):
|
||||
if points:
|
||||
self.points = points
|
||||
self._frequencies = np.linspace(start, stop, self.points)
|
||||
|
|
@ -50,6 +61,12 @@ class NanoVNA:
|
|||
self.serial.write(cmd.encode())
|
||||
self.serial.readline() # discard empty line
|
||||
|
||||
def set_sweep(self, start, stop):
|
||||
if start is not None:
|
||||
self.send_command("sweep start %d\r" % start)
|
||||
if stop is not None:
|
||||
self.send_command("sweep stop %d\r" % stop)
|
||||
|
||||
def set_frequency(self, freq):
|
||||
if freq is not None:
|
||||
self.send_command("freq %d\r" % freq)
|
||||
|
|
@ -128,19 +145,6 @@ class NanoVNA:
|
|||
d = data.strip().split(' ')
|
||||
return (int(d[0])+int(d[1])*1.j)/REF_LEVEL
|
||||
|
||||
def fetch_scan(self, port = None):
|
||||
self.set_port(port)
|
||||
self.send_command("scan\r")
|
||||
data = self.fetch_data()
|
||||
x = []
|
||||
for line in data.split('\n'):
|
||||
if line:
|
||||
x.append([int(d) for d in line.strip().split(' ')])
|
||||
x = np.array(x)
|
||||
freqs = x[:,0]
|
||||
gammas = x[:,1]+x[:,2]*1j
|
||||
return gammas / REF_LEVEL, freqs
|
||||
|
||||
def reflect_coeff_from_rawwave(self, freq = None):
|
||||
ref, samp = self.fetch_rawwave(freq)
|
||||
if self.filter:
|
||||
|
|
@ -162,7 +166,7 @@ class NanoVNA:
|
|||
def pause(self):
|
||||
self.send_command("pause\r")
|
||||
|
||||
def scan(self, port = None):
|
||||
def scan_gamma0(self, port = None):
|
||||
self.set_port(port)
|
||||
return np.vectorize(self.gamma)(self.frequencies)
|
||||
|
||||
|
|
@ -189,6 +193,29 @@ class NanoVNA:
|
|||
x.append(float(line))
|
||||
self._frequencies = np.array(x)
|
||||
|
||||
def send_scan(self, start = 1e6, stop = 900e6, points = None):
|
||||
if points:
|
||||
self.send_command("scan %d %d %d\r"%(start, stop, points))
|
||||
else:
|
||||
self.send_command("scan %d %d\r"%(start, stop))
|
||||
|
||||
def scan(self):
|
||||
segment_length = 101
|
||||
array0 = []
|
||||
array1 = []
|
||||
freqs = self._frequencies
|
||||
while len(freqs) > 0:
|
||||
seg_start = freqs[0]
|
||||
seg_stop = freqs[segment_length-1] if len(freqs) >= segment_length else freqs[-1]
|
||||
length = segment_length if len(freqs) >= segment_length else len(freqs)
|
||||
#print((seg_start, seg_stop, length))
|
||||
self.send_scan(seg_start, seg_stop, length)
|
||||
array0.extend(self.data(0))
|
||||
array1.extend(self.data(1))
|
||||
freqs = freqs[segment_length:]
|
||||
self.resume()
|
||||
return (array0, array1)
|
||||
|
||||
def capture(self):
|
||||
from PIL import Image
|
||||
self.send_command("capture\r")
|
||||
|
|
@ -331,6 +358,15 @@ if __name__ == '__main__':
|
|||
parser.add_option("-c", "--scan", dest="scan",
|
||||
action="store_true", default=False,
|
||||
help="scan by script", metavar="SCAN")
|
||||
parser.add_option("-S", "--start", dest="start",
|
||||
type="float", default=1e6,
|
||||
help="start frequency", metavar="START")
|
||||
parser.add_option("-E", "--stop", dest="stop",
|
||||
type="float", default=900e6,
|
||||
help="stop frequency", metavar="STOP")
|
||||
parser.add_option("-N", "--points", dest="points",
|
||||
type="int", default=101,
|
||||
help="scan points", metavar="POINTS")
|
||||
parser.add_option("-P", "--port", type="int", dest="port",
|
||||
help="port", metavar="PORT")
|
||||
parser.add_option("-d", "--dev", dest="device",
|
||||
|
|
@ -341,7 +377,7 @@ if __name__ == '__main__':
|
|||
help="gain (0-95)", metavar="GAIN")
|
||||
parser.add_option("-O", "--offset", type="int", dest="offset",
|
||||
help="offset frequency", metavar="OFFSET")
|
||||
parser.add_option("-S", "--strength", type="int", dest="strength",
|
||||
parser.add_option("--strength", type="int", dest="strength",
|
||||
help="drive strength(0-3)", metavar="STRENGTH")
|
||||
parser.add_option("-v", "--verbose",
|
||||
action="store_true", dest="verbose", default=False,
|
||||
|
|
@ -353,7 +389,7 @@ if __name__ == '__main__':
|
|||
help="capture current display to FILE", metavar="FILE")
|
||||
(opt, args) = parser.parse_args()
|
||||
|
||||
nv = NanoVNA(opt.device or '/dev/cu.usbmodem401')
|
||||
nv = NanoVNA(opt.device or getport())
|
||||
|
||||
if opt.capture:
|
||||
print("capturing...")
|
||||
|
|
@ -379,14 +415,17 @@ if __name__ == '__main__':
|
|||
print(np.average(samp[0::2] * samp[1::2]))
|
||||
pl.show()
|
||||
exit(0)
|
||||
if opt.start or opt.stop or opt.points:
|
||||
nv.set_frequencies(opt.start, opt.stop, opt.points)
|
||||
plot = opt.phase or opt.plot or opt.vswr or opt.delay or opt.groupdelay or opt.smith or opt.unwrapphase or opt.polar or opt.tdr
|
||||
if plot:
|
||||
if opt.scan:
|
||||
p = int(opt.port) if opt.port else 0
|
||||
if opt.scan or opt.points > 101:
|
||||
s = nv.scan()
|
||||
s = s[p]
|
||||
else:
|
||||
p = 0
|
||||
if opt.port:
|
||||
p = int(opt.port)
|
||||
if opt.start or opt.stop:
|
||||
nv.set_sweep(opt.start, opt.stop)
|
||||
s = nv.data(p)
|
||||
if opt.smith:
|
||||
nv.smith(s)
|
||||
|
|
|
|||
Loading…
Reference in a new issue