GPIO, I2C and SPI working
This commit is contained in:
parent
8c3254d251
commit
7f95cc3811
|
@ -415,8 +415,14 @@ void u8g2_ClearDisplay(u8g2_t *u8g2);
|
|||
#define u8g2_GetDisplayWidth(u8g2) ((u8g2)->width)
|
||||
#define u8g2_GetDrawColor(u8g2) ((u8g2)->draw_color)
|
||||
|
||||
#define u8g2_SetI2CAddress(u8g2, address) ((u8g2_GetU8x8(u8g2))->i2c_address = (address))
|
||||
#define u8g2_GetGPIOChip(u8g2) u8x8_GetGPIOChip(u8g2_GetU8x8(u8g2))
|
||||
#define u8g2_SetGPIOChip(u8g2, chip_num) ((u8g2_GetU8x8(u8g2))->gpio_chip_num = (chip_num))
|
||||
#define u8g2_GetSPIBus(u8g2) u8x8_GetSPIBus(u8g2_GetU8x8(u8g2))
|
||||
#define u8g2_SetSPIBus(u8g2, bus_num) ((u8g2_GetU8x8(u8g2))->spi_bus_num = (bus_num))
|
||||
#define u8g2_GetI2CBus(u8g2) u8x8_GetI2CBus(u8g2_GetU8x8(u8g2))
|
||||
#define u8g2_SetI2CBus(u8g2, bus_num) ((u8g2_GetU8x8(u8g2))->i2c_bus_num = (bus_num))
|
||||
#define u8g2_GetI2CAddress(u8g2) u8x8_GetI2CAddress(u8g2_GetU8x8(u8g2))
|
||||
#define u8g2_SetI2CAddress(u8g2, address) ((u8g2_GetU8x8(u8g2))->i2c_address = (address))
|
||||
|
||||
#ifdef U8X8_USE_PINS
|
||||
#define u8g2_SetMenuSelectPin(u8g2, val) u8x8_SetMenuSelectPin(u8g2_GetU8x8(u8g2), (val))
|
||||
|
|
|
@ -346,6 +346,7 @@ struct u8x8_struct
|
|||
uint16_t encoding; /* encoding result for utf8 decoder in next_cb */
|
||||
uint8_t x_offset; /* copied from info struct, can be modified in flip mode */
|
||||
uint8_t is_font_inverse_mode; /* 0: normal, 1: font glyphs are inverted */
|
||||
uint8_t gpio_chip_num; /* gpio chip */
|
||||
uint8_t spi_bus_num; /* spi bus to use */
|
||||
uint8_t i2c_bus_num; /* i2c bus to use */
|
||||
uint8_t i2c_address; /* a valid i2c adr. Initially this is 255, but this is set to something usefull during DISPLAY_INIT */
|
||||
|
@ -375,6 +376,8 @@ struct u8x8_struct
|
|||
|
||||
#define u8x8_GetCols(u8x8) ((u8x8)->display_info->tile_width)
|
||||
#define u8x8_GetRows(u8x8) ((u8x8)->display_info->tile_height)
|
||||
#define u8x8_GetGPIOChip(u8x8) ((u8x8)->gpio_chip_num)
|
||||
#define u8x8_SetGPIOChip(u8x8, chip_num) ((u8x8)->gpio_chip_num = (chip_num))
|
||||
#define u8x8_GetSPIBus(u8x8) ((u8x8)->spi_bus_num)
|
||||
#define u8x8_SetSPIBus(u8x8, bus_num) ((u8x8)->spi_bus_num = (bus_num))
|
||||
#define u8x8_GetI2CBus(u8x8) ((u8x8)->i2c_bus_num)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#include "u8g2port.h"
|
||||
|
||||
// By default, SPI bus /dev/spidev0.0 is used, as defined in port/U8g2lib.h
|
||||
// GPIO chip number for character device
|
||||
#define GPIO_CHIP_NUM 0
|
||||
// SPI bus uses upper 4 bits and lower 4 bits, so 0x10 will be /dev/spidev1.0
|
||||
#define SPI_BUS 0x10
|
||||
#define OLED_SPI_PIN_RES 199
|
||||
#define OLED_SPI_PIN_DC 198
|
||||
|
||||
|
@ -13,9 +16,10 @@ int main(void)
|
|||
|
||||
// Initialization
|
||||
u8g2_Setup_ssd1306_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_arm_linux_hw_spi, u8x8_arm_linux_gpio_and_delay);
|
||||
u8g2_SetGPIOChip(&u8g2, GPIO_CHIP_NUM);
|
||||
u8g2_SetSPIBus(&u8g2, SPI_BUS);
|
||||
u8x8_SetPin(u8g2_GetU8x8(&u8g2), U8X8_PIN_DC, OLED_SPI_PIN_DC);
|
||||
u8x8_SetPin(u8g2_GetU8x8(&u8g2), U8X8_PIN_RESET, OLED_SPI_PIN_RES);
|
||||
// u8x8_SetPin(u8g2_GetU8x8(&u8g2), U8X8_PIN_CS, OLED_SPI_PIN_CS);
|
||||
|
||||
u8g2_InitDisplay(&u8g2);
|
||||
u8g2_SetPowerSave(&u8g2, 0);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "u8g2port.h"
|
||||
|
||||
// GPIO chip number for character device
|
||||
#define GPIO_CHIP_NUM 0
|
||||
|
||||
#define OLED_SPI_PIN_MOSI 13
|
||||
#define OLED_SPI_PIN_SCK 26
|
||||
|
@ -15,6 +17,7 @@ int main(void)
|
|||
|
||||
// Initialization
|
||||
u8g2_Setup_ssd1306_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_4wire_sw_spi, u8x8_arm_linux_gpio_and_delay);
|
||||
u8g2_SetGPIOChip(&u8g2, GPIO_CHIP_NUM);
|
||||
u8x8_SetPin(u8g2_GetU8x8(&u8g2), U8X8_PIN_RESET, OLED_SPI_PIN_RES);
|
||||
u8x8_SetPin(u8g2_GetU8x8(&u8g2), U8X8_PIN_DC, OLED_SPI_PIN_DC);
|
||||
u8x8_SetPin(u8g2_GetU8x8(&u8g2), U8X8_PIN_SPI_DATA, OLED_SPI_PIN_MOSI);
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
#include <u8g2port.h>
|
||||
|
||||
// Set I2C bus and address
|
||||
#define I2C_BUS 0
|
||||
#define I2C_ADDRESS 0x3c * 2
|
||||
|
||||
int main(void)
|
||||
{
|
||||
u8g2_t u8g2;
|
||||
|
||||
// Initialization
|
||||
u8g2_Setup_ssd1306_i2c_128x64_noname_f( &u8g2, U8G2_R0, u8x8_byte_arm_linux_hw_i2c, u8x8_arm_linux_gpio_and_delay);
|
||||
u8g2_SetI2CBus(&u8g2, I2C_BUS);
|
||||
u8g2_SetI2CAddress(&u8g2, I2C_ADDRESS);
|
||||
u8g2_InitDisplay(&u8g2);
|
||||
u8g2_SetPowerSave(&u8g2, 0);
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "u8g2port.h"
|
||||
|
||||
// GPIO chip number for character device
|
||||
#define GPIO_CHIP_NUM 0
|
||||
#define OLED_I2C_PIN_SCL 20
|
||||
#define OLED_I2C_PIN_SDA 21
|
||||
|
||||
|
@ -9,6 +11,7 @@ int main(void)
|
|||
|
||||
// Initialization
|
||||
u8g2_Setup_ssd1306_i2c_128x64_noname_f( &u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_arm_linux_gpio_and_delay);
|
||||
u8g2_SetGPIOChip(&u8g2, GPIO_CHIP_NUM);
|
||||
u8x8_SetPin(u8g2_GetU8x8(&u8g2), U8X8_PIN_I2C_CLOCK, OLED_I2C_PIN_SCL);
|
||||
u8x8_SetPin(u8g2_GetU8x8(&u8g2), U8X8_PIN_I2C_DATA, OLED_I2C_PIN_SDA);
|
||||
u8x8_SetPin(u8g2_GetU8x8(&u8g2), U8X8_PIN_RESET, U8X8_PIN_NONE);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#include <U8g2lib.h>
|
||||
|
||||
// By default, SPI bus /dev/spidev0.0 is used, as defined in port/U8g2lib.h
|
||||
// GPIO chip number for character device
|
||||
#define GPIO_CHIP_NUM 0
|
||||
// SPI bus uses upper 4 bits and lower 4 bits, so 0x10 will be /dev/spidev1.0
|
||||
#define SPI_BUS 0x10
|
||||
#define OLED_SPI_PIN_RES 199
|
||||
#define OLED_SPI_PIN_DC 198
|
||||
|
||||
|
@ -16,8 +19,9 @@ static U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0,
|
|||
|
||||
int main()
|
||||
{
|
||||
u8g2.setGpioChip(GPIO_CHIP_NUM);
|
||||
u8g2.setSpiBus(SPI_BUS);
|
||||
u8g2.begin();
|
||||
u8g2.clearBuffer(); // clear the internal memory
|
||||
u8g2.setFont(u8g2_font_6x13_tr); // choose a suitable font
|
||||
u8g2.drawStr(1, 18, "U8g2 on HW SPI"); // write something to the internal memory
|
||||
u8g2.sendBuffer(); // transfer internal memory to the display
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include <U8g2lib.h>
|
||||
|
||||
// GPIO chip number for character device
|
||||
#define GPIO_CHIP_NUM 0
|
||||
#define OLED_SPI_PIN_MOSI 13
|
||||
#define OLED_SPI_PIN_SCK 26
|
||||
|
||||
|
@ -18,6 +20,7 @@ static U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0,\
|
|||
|
||||
int main()
|
||||
{
|
||||
u8g2.setGpioChip(GPIO_CHIP_NUM);
|
||||
u8g2.begin();
|
||||
u8g2.clearBuffer(); // clear the internal memory
|
||||
u8g2.setFont(u8g2_font_6x13_tr); // choose a suitable font
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
#include <U8g2lib.h>
|
||||
|
||||
// Set I2C bus and address
|
||||
#define I2C_BUS 0
|
||||
#define I2C_ADDRESS 0x3c * 2
|
||||
// Check https://github.com/olikraus/u8g2/wiki/u8g2setupcpp for all supported devices
|
||||
static U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
u8g2.setI2cBus(I2C_BUS);
|
||||
u8g2.setI2CAddress(I2C_ADDRESS);
|
||||
u8g2.begin();
|
||||
u8g2.clearBuffer(); // clear the internal memory
|
||||
u8g2.setFont(u8g2_font_6x13_tr); // choose a suitable font
|
||||
u8g2.drawStr(1, 18, "U8g2 on HW I2C"); // write something to the internal memory
|
||||
u8g2.sendBuffer(); // transfer internal memory to the display
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include <U8g2lib.h>
|
||||
|
||||
// GPIO chip number for character device
|
||||
#define GPIO_CHIP_NUM 0
|
||||
#define OLED_I2C_PIN_SCL 20
|
||||
#define OLED_I2C_PIN_SDA 21
|
||||
|
||||
|
@ -12,6 +14,7 @@ static U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0,
|
|||
|
||||
int main(void)
|
||||
{
|
||||
u8g2.setGpioChip(GPIO_CHIP_NUM);
|
||||
u8g2.begin();
|
||||
u8g2.clearBuffer(); // clear the internal memory
|
||||
u8g2.setFont(u8g2_font_6x13_tr); // choose a suitable font
|
||||
|
|
|
@ -69,6 +69,16 @@ class U8G2 : public Print
|
|||
uint32_t getBusClock(void) { return u8g2_GetU8x8(&u8g2)->bus_clock; }
|
||||
void setBusClock(uint32_t clock_speed) { u8g2_GetU8x8(&u8g2)->bus_clock = clock_speed; }
|
||||
|
||||
uint8_t getGpioChip(void) { return u8x8_GetGPIOChip(u8g2_GetU8x8(&u8g2)); }
|
||||
void setGpioChip(uint8_t num) { u8x8_SetGPIOChip(u8g2_GetU8x8(&u8g2), num); }
|
||||
|
||||
uint8_t getSpiBus(void) { return u8x8_GetSPIBus(u8g2_GetU8x8(&u8g2)); }
|
||||
void setSpiBus(uint8_t num) { u8x8_SetSPIBus(u8g2_GetU8x8(&u8g2), num); }
|
||||
|
||||
uint8_t getI2cBus(void) { return u8x8_GetI2CBus(u8g2_GetU8x8(&u8g2)); }
|
||||
void setI2cBus(uint8_t num) { u8x8_SetI2CBus(u8g2_GetU8x8(&u8g2), num); }
|
||||
|
||||
uint8_t getI2CAddress(void) { return u8g2_GetI2CAddress(&u8g2); }
|
||||
void setI2CAddress(uint8_t adr) { u8g2_SetI2CAddress(&u8g2, adr); }
|
||||
|
||||
|
||||
|
|
|
@ -1,26 +1,18 @@
|
|||
/*
|
||||
* This code is not thread safe and should be reworked to make multiple displays
|
||||
* work in a thread safe way. At this point you can only have one I2C or
|
||||
* SPI bus, but have multiple devices on the bus.
|
||||
* This code should support multiple displays, but only one hardware bus each
|
||||
* for I2C and SPI currently.
|
||||
*/
|
||||
|
||||
#include "u8g2port.h"
|
||||
|
||||
// c-periphery i2c handle
|
||||
static i2c_t *i2c_device;
|
||||
static const char i2c_bus[] = "/dev/i2c-0";
|
||||
// c-periphery I2C handle
|
||||
static i2c_t *i2c_handle = NULL;
|
||||
|
||||
// c-periphery spi handle
|
||||
static spi_t *spi_device;
|
||||
static const char spi_bus[] = "/dev/spidev1.0";
|
||||
|
||||
// TODO: This should be 1 to 1 with pins
|
||||
#if PERIPHERY_GPIO_CDEV_SUPPORT
|
||||
static const char gpio_device[] = "/dev/gpiochip0";
|
||||
#endif
|
||||
// c-periphery SPI handle
|
||||
static spi_t *spi_handle = NULL;
|
||||
|
||||
// c-periphery GPIO pins
|
||||
gpio_t *pins[U8X8_PIN_CNT] = { };
|
||||
static gpio_t *pins[U8X8_PIN_CNT] = { };
|
||||
|
||||
/*
|
||||
* Sleep milliseconds.
|
||||
|
@ -55,25 +47,32 @@ void sleep_ns(unsigned long nanoseconds) {
|
|||
/**
|
||||
* Initialize pin if not set to U8X8_PIN_NONE.
|
||||
*/
|
||||
#if PERIPHERY_GPIO_CDEV_SUPPORT
|
||||
void init_pin(u8x8_t *u8x8, int pin) {
|
||||
char filename[16];
|
||||
if (u8x8->pins[pin] != U8X8_PIN_NONE) {
|
||||
snprintf(filename, sizeof(filename), "/dev/gpiochip%d",
|
||||
u8x8->gpio_chip_num);
|
||||
pins[pin] = gpio_new();
|
||||
if (gpio_open(pins[pin], filename, u8x8->pins[pin], GPIO_DIR_OUT_HIGH)
|
||||
< 0) {
|
||||
fprintf(stderr, "gpio_open(): pin %d, %s\n", u8x8->pins[pin],
|
||||
gpio_errmsg(pins[pin]));
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
void init_pin(u8x8_t *u8x8, int pin) {
|
||||
if (u8x8->pins[pin] != U8X8_PIN_NONE) {
|
||||
pins[pin] = gpio_new();
|
||||
// Support character device
|
||||
#if PERIPHERY_GPIO_CDEV_SUPPORT
|
||||
if (gpio_open(pins[pin], gpio_device, u8x8->pins[pin], GPIO_DIR_OUT_HIGH)
|
||||
< 0) {
|
||||
fprintf(stderr, "gpio_open(): pin %d, %s\n", u8x8->pins[pin], gpio_errmsg(pins[pin]));
|
||||
}
|
||||
// Support deprecated sysfs
|
||||
#else
|
||||
if (gpio_open_sysfs(pins[pin], u8x8->pins[pin], GPIO_DIR_OUT_HIGH)
|
||||
< 0) {
|
||||
fprintf(stderr, "gpio_open_sysfs(): pin %d, %s\n", u8x8->pins[pin],
|
||||
gpio_errmsg(pins[pin]));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Close and free gpio_t for all pins.
|
||||
|
@ -83,6 +82,7 @@ void done_pins() {
|
|||
if (pins[i] != NULL) {
|
||||
gpio_close(pins[i]);
|
||||
gpio_free(pins[i]);
|
||||
pins[i] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,16 +91,22 @@ void done_pins() {
|
|||
* Close and free i2c_t.
|
||||
*/
|
||||
void done_i2c() {
|
||||
i2c_close(i2c_device);
|
||||
i2c_free(i2c_device);
|
||||
if (i2c_handle != NULL) {
|
||||
i2c_close(i2c_handle);
|
||||
i2c_free(i2c_handle);
|
||||
i2c_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Close and free spi_t.
|
||||
*/
|
||||
void done_spi() {
|
||||
spi_close(spi_device);
|
||||
spi_free(spi_device);
|
||||
if (spi_handle != NULL) {
|
||||
spi_close(spi_handle);
|
||||
spi_free(spi_handle);
|
||||
spi_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -250,9 +256,8 @@ uint8_t u8x8_arm_linux_gpio_and_delay(u8x8_t *u8x8, uint8_t msg,
|
|||
}
|
||||
|
||||
/*
|
||||
* I2c callback. This should be reworked to make it thread safe. The static
|
||||
* variables can be corrupted if more than one thread at a time accesses this
|
||||
* function.
|
||||
* I2c callback. The static variables can be corrupted if more than one thread
|
||||
* at a time accesses this function. Calling program neds to be aware of this.
|
||||
*/
|
||||
uint8_t u8x8_byte_arm_linux_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,
|
||||
void *arg_ptr) {
|
||||
|
@ -261,6 +266,7 @@ uint8_t u8x8_byte_arm_linux_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,
|
|||
static uint8_t buf_idx;
|
||||
uint8_t *data;
|
||||
struct i2c_msg msgs[1];
|
||||
char filename[11];
|
||||
|
||||
switch (msg) {
|
||||
case U8X8_MSG_BYTE_SEND:
|
||||
|
@ -273,9 +279,15 @@ uint8_t u8x8_byte_arm_linux_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,
|
|||
break;
|
||||
|
||||
case U8X8_MSG_BYTE_INIT:
|
||||
i2c_device = i2c_new();
|
||||
if (i2c_open(i2c_device, i2c_bus) < 0) {
|
||||
fprintf(stderr, "i2c_open(): %s\n", i2c_errmsg(i2c_device));
|
||||
// Only open bus once
|
||||
if (i2c_handle == NULL) {
|
||||
snprintf(filename, sizeof(filename), "/dev/i2c-%d",
|
||||
u8x8->i2c_bus_num);
|
||||
i2c_handle = i2c_new();
|
||||
if (i2c_open(i2c_handle, filename) < 0) {
|
||||
fprintf(stderr, "i2c_open(): %s\n", i2c_errmsg(i2c_handle));
|
||||
i2c_free(i2c_handle);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -288,7 +300,7 @@ uint8_t u8x8_byte_arm_linux_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,
|
|||
msgs[0].flags = 0; // Write
|
||||
msgs[0].len = buf_idx;
|
||||
msgs[0].buf = buffer;
|
||||
i2c_transfer(i2c_device, msgs, 1);
|
||||
i2c_transfer(i2c_handle, msgs, 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -306,6 +318,7 @@ uint8_t u8x8_byte_arm_linux_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,
|
|||
uint8_t buffer[128];
|
||||
uint8_t buf_idx;
|
||||
uint8_t *data;
|
||||
char filename[15];
|
||||
|
||||
switch (msg) {
|
||||
case U8X8_MSG_BYTE_SEND:
|
||||
|
@ -316,20 +329,26 @@ uint8_t u8x8_byte_arm_linux_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,
|
|||
data++;
|
||||
arg_int--;
|
||||
}
|
||||
spi_transfer(spi_device, buffer, buffer, buf_idx);
|
||||
spi_transfer(spi_handle, buffer, buffer, buf_idx);
|
||||
break;
|
||||
|
||||
case U8X8_MSG_BYTE_INIT:
|
||||
spi_device = spi_new();
|
||||
//u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
|
||||
/* SPI mode has to be mapped to the mode of the current controller, at least Uno, Due, 101 have different SPI_MODEx values */
|
||||
/* 0: clock active high, data out on falling edge, clock default value is zero, takover on rising edge */
|
||||
/* 1: clock active high, data out on rising edge, clock default value is zero, takover on falling edge */
|
||||
/* 2: clock active low, data out on rising edge */
|
||||
/* 3: clock active low, data out on falling edge */
|
||||
if (spi_open(spi_device, spi_bus, u8x8->display_info->spi_mode, 500000)
|
||||
< 0) {
|
||||
fprintf(stderr, "spi_open(): %s\n", spi_errmsg(spi_device));
|
||||
// Only open bus once
|
||||
if (spi_handle == NULL) {
|
||||
// spi_bus_num should be in the format 0x12 for /dev/spidev1.2
|
||||
snprintf(filename, sizeof(filename), "/dev/spidev%d.%d",
|
||||
u8x8->spi_bus_num >> 4, u8x8->spi_bus_num & 0x0f);
|
||||
spi_handle = spi_new();
|
||||
//u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
|
||||
/* SPI mode has to be mapped to the mode of the current controller, at least Uno, Due, 101 have different SPI_MODEx values */
|
||||
/* 0: clock active high, data out on falling edge, clock default value is zero, takover on rising edge */
|
||||
/* 1: clock active high, data out on rising edge, clock default value is zero, takover on falling edge */
|
||||
/* 2: clock active low, data out on rising edge */
|
||||
/* 3: clock active low, data out on falling edge */
|
||||
if (spi_open(spi_handle, filename, u8x8->display_info->spi_mode,
|
||||
500000) < 0) {
|
||||
fprintf(stderr, "spi_open(): %s\n", spi_errmsg(spi_handle));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
Loading…
Reference in New Issue