In this task, we must implement bidirectional UART communication between two microcontrollers, where each controller manages local inputs and controls remote outputs through serial communication.
Cross-Control Functionality:
Connection Requirements:
Voltage Level Considerations in UART Communication
Why send ADC Maximum Value along with the current ADC value?
Both microcontrollers provide real-time feedback through their respective serial monitors:
So, by connecting and configuring the microcontrollers and UART communication, we can implement the task.
Below are the solutions to the given task using different microcontrollers
We’re using an STM32 NUCLEO-F103RB board, which operates at a 3.3V logic level.
Circuit Diagram
Note: When interfacing UART devices operating at different voltages (e.g., 5 V ↔ 3.3 V), always use a voltage level shifter to ensure safe and reliable communication.
Project Setup in STM32CubeIDE
HAL_Init()
→ Initializes HAL and system tick.SystemClock_Config()
→ Configures system clock (HSI + PLL).MX_GPIO_Init()
→ Initializes GPIO ports.MX_USART1_UART_Init()
→ Configures UART1.MX_USART2_UART_Init()
→ Configures UART2.MX_ADC1_Init()
→ Configures ADC1.ADC Initialization (MX_ADC1_Init)
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK) {
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
Error_Handler();
}
GPIO Initialization (MX_GPIO_Init)
/*Configure GPIO pin : PA6 */
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
UART Initialization (MX_USART1_UART_Init and MX_USART2_UART_Init)
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
Header includes, private defines, and variables
#include <stdio.h>
#include <string.h>
#define ADC_MAX_VALUE 4095
#define ADC_REF_VOLTAGE 3.3
#define VOLTAGE_OFFSET 0.05
#define LED0_PORT GPIOA
#define LED0_PIN GPIO_PIN_6
char uartTXBuffer[50]; //Tx Buffer for UART messages
uint8_t uartRXByte; // Single received byte via UART
uint8_t uartRXBuffer[30]; // Buffer to store complete UART string
uint8_t uartRXIndex = 0; // Index for UART buffer
uint8_t receivedData[30];
volatile uint8_t byteReceivedFlag = 0;
uint32_t adcValue = 0;
const uint8_t adcMaxLowerByte = ADC_MAX_VALUE;
const uint8_t adcMaxUpperByte = ADC_MAX_VALUE >> 8;
uint8_t adcLowerByte = 0;
uint8_t adcUpperByte = 0;
Utility Function and callback
/**
* @brief Prints a string to the serial monitor
* @param msg: Pointer to the null-terminated string to send
* @retval None
*/
static void printOnSerialMonitor(const char *msg) {
HAL_UART_Transmit(&huart2, (uint8_t *)msg, strlen(msg), HAL_MAX_DELAY);
}
/* Start UART RX interrupt for receiving one byte */
void uartStartReception() {
HAL_UART_Receive_IT(&huart1, &uartRXByte, 1);
}
/* UART RX Complete Callback */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
byteReceivedFlag = 1;
// Restart reception for next byte
uartStartReception();
}
}
Main Loop Logic
uartStartReception();
while (1) {
if (byteReceivedFlag) {
byteReceivedFlag = 0;
uint8_t receivedByte = uartRXByte; // Read a single byte
if (receivedByte == 'T') {
HAL_GPIO_TogglePin(LED0_PORT, LED0_PIN);
if (HAL_GPIO_ReadPin(LED0_PORT, LED0_PIN)) {
sprintf(uartTXBuffer, "LED is ON\r\n");
} else {
sprintf(uartTXBuffer, "LED is OFF\r\n");
}
printOnSerialMonitor(uartTXBuffer);
}
if (receivedByte == 0) {
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 20);
adcValue = HAL_ADC_GetValue(&hadc1);
adcLowerByte = adcValue;
adcUpperByte = adcValue >> 8;
uint8_t uartTXByte = '0';
HAL_UART_Transmit(&huart1, &uartTXByte, 1, HAL_MAX_DELAY);
}
if (receivedByte == 1) {
HAL_UART_Transmit(&huart1, &adcMaxLowerByte, 1, HAL_MAX_DELAY);
}
if (receivedByte == 2) {
HAL_UART_Transmit(&huart1, &adcMaxUpperByte, 1, HAL_MAX_DELAY);
}
if (receivedByte == 3) {
HAL_UART_Transmit(&huart1, &adcLowerByte, 1, HAL_MAX_DELAY);
}
if (receivedByte == 4) {
HAL_UART_Transmit(&huart1, &adcUpperByte, 1, HAL_MAX_DELAY);
}
}
}
HAL_UART_Receive_IT(&huart1, &uartRXByte, 1)
→ Starts UART reception in interrupt mode.byteReceivedFlag
is set:byteReceivedFlag = 0
).uartRXByte
):T
':0
:1
to 4
:Flow of Operation
'T', 0, 1, 2, 3, or 4
).The complete STM32CubeIDE project (including .ioc
configuration, main.c
, and HAL files) is available here:
📥 Download Project
Circuit Diagram
Project Setup in STM32CubeIDE
GPIO Initialization (MX_GPIO_Init)
A) PA0 Configuration (Button Input):
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_PIN_0
: Specifies pin 0 of GPIO port AGPIO_MODE_INPUT
: Sets the pin as inputGPIO_PULLUP
: Enables internal pull-up resistor (button will read HIGH when not pressed, LOW when pressed)B) USART TX/RX Pins Configuration
GPIO_InitStruct.Pin = USART_TX_Pin|USART_RX_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
UART Initialization (MX_USART1_UART_Init and MX_USART2_UART_Init)
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
Header Includes, Private Defines, and Variables
A) Includes
#include <string.h>
#include <stdio.h>
B) Defines
#define SWITCH_PORT GPIOA
#define SWITCH_PIN GPIO_PIN_0
C) Global Variables
uint32_t previousMillis = 0;
uint8_t g_debounceDuration = 50; // Minimum time to debounce button (in milliseconds)
uint8_t g_previousButtonState = 1; // Previous state of the button
uint8_t g_currentButtonState = 1; // Current state of the button
uint32_t g_lastDebounceTime = 0; // Time when button state last changed
uint32_t g_pressStartTime; // Time when button press starts
uint32_t g_releaseTime; // Duration of the button press
uint8_t uartTXBuffer[20]; //Tx Buffer for UART messages
uint8_t uartRXByte; // Single received byte via UART
uint8_t uartRXBuffer[10]; // Increased buffer size
volatile uint8_t uartRXIndex = 0; // Index for UART buffer
Utility Functions and Callbacks
A) Mapping Function
uint16_t mapValue(uint16_t input, uint16_t inMin, uint16_t inMax,
uint16_t outMin, uint16_t outMax) {
return (input - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}
B) Millis Function
uint32_t millis() {
return HAL_GetTick();
}
C) Button Debounce Function
//Checks for a debounced button press and returns true if detected, false otherwise.
uint8_t debouncedButtonPressCheck(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin,
uint8_t expectedState) {
uint8_t buttonReading = HAL_GPIO_ReadPin(GPIOx, GPIO_Pin);
// If the button state has changed, reset the debounce timer
if (buttonReading != g_previousButtonState) {
g_lastDebounceTime = millis();
}
g_previousButtonState = buttonReading;
// If the state has remained stable beyond the debounce duration, consider it valid
if ((millis() - g_lastDebounceTime) > g_debounceDuration) {
if (buttonReading != g_currentButtonState) {
g_currentButtonState = buttonReading;
if (g_currentButtonState == expectedState) {
return 1; // Return true if the desired state is detected
}
}
}
return 0; // Return false if no valid press is detected
}
UART Callbacks and Helpers
/* Start UART RX interrupt for receiving one byte */
void uartStartReception() {
HAL_UART_Receive_IT(&huart1, &uartRXByte, 1);
}
/* UART RX Complete Callback */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
uartRXBuffer[uartRXIndex++] = uartRXByte; // Store byte
// Restart reception for next byte
uartStartReception();
}
}
void transmitByteOnUART1(uint8_t byte) {
HAL_UART_Transmit(&huart1, &byte, 1, HAL_MAX_DELAY);
}
Main Loop Logic
A) Initialization
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
uartStartReception();
B) Main Loop
while (1) {
// Main processing every 100ms
if ((millis() - previousMillis) > 100) {
previousMillis = millis();
// Transmit test sequence
for (uint8_t i = 0; i < 5; i++) {
transmitByteOnUART1(i);
HAL_Delay(1);
}
// Process received ADC data
uint16_t adcMaxValue = (uartRXBuffer[2] << 8) | uartRXBuffer[1];
uint16_t adcValue = (uartRXBuffer[4] << 8) | uartRXBuffer[3];
sprintf(uartTXBuffer, "ADC Value = %d\r\n",(int)adcValue);
printOnSerialMonitor(uartTXBuffer);
// Map to PWM range and update
uint16_t brightness = mapValue(adcValue, 0, adcMaxValue, 0, 4095);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, brightness);
}
// Check for button press
if (debouncedButtonPressCheck(SWITCH_PORT, SWITCH_PIN, 0)) {
transmitByteOnUART1('T');
}
}
Flow of Operation
The complete STM32CubeIDE project (including .ioc
configuration, main.c
, and HAL files) is available here:
📥 Download Project
We are using the ESP32 DevKit v4 development board and programming it using the Arduino IDE.
ESP32 has 3 hardware UARTs
Note: UART1 → hardware exists, but on most ESP32 boards, TX is pinned to GPIO9 and RX to GPIO10 (often used internally for flash), so it’s usually not recommended.
Connect either ESP32 to the PC via USB cable for programming/debugging.
1)ESP32 as Microcontroller1
2)ESP32 as Microcontroller2
Note: When interfacing UART devices operating at different voltages (e.g., 5 V ↔ 3.3 V), always use a voltage level shifter to ensure safe logic levels and reliable communication.
1) Code of ESP32 as Microcontroller1
#define POTENTIOMETER_PIN 34 // Analog input pin for potentiometer
#define LED_PIN 4 // Digital output pin for LED
uint16_t potValue = 0; // Stores raw ADC value from potentiometer
// Predefined maximum ADC value (12-bit ESP32: 0–4095) split into two bytes
uint8_t adcMaxLowerByte = 4095;
uint8_t adcMaxUpperByte = 4095 >> 8;
// Variables to hold the split potentiometer reading (LSB/MSB)
uint8_t adcLowerByte = 0;
uint8_t adcUpperByte = 0;
void setup() {
pinMode(LED_PIN, OUTPUT); // LED pin as output
Serial.begin(115200); // Serial communication
Serial2.begin(115200, SERIAL_8N1, 16, 17); //Start UART2 @115200 baudrate, 8N1 format(8 bit data, No parity bit 1, stop bit), RX=GPIO16, TX=GPIO17
void loop() {
// Check if data is received from microcontroller2 over UART2
if (Serial2.available()) {
uint8_t receivedByte = Serial2.read(); // Read incoming command byte
// Command 'T' → toggle LED state
if (receivedByte == 'T') {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
// Send data to serial monitor
Serial.print("LED is ");
Serial.println(digitalRead(LED_PIN) ? "ON" : "OFF");
}
// Command 0 → read potentiometer value and split into 2 bytes
if (receivedByte == 0) {
potValue = analogRead(POTENTIOMETER_PIN); // Read 12-bit ADC value
adcLowerByte = potValue & 0xFF; // Extract lower 8 bits
adcUpperByte = potValue >> 8; // Extract upper 4 bits
Serial2.write(0); // Acknowledge reception
Serial.flush(); // Wait until all data in the TX buffer is transmitted (does NOT clear RX buffer)
}
// Command 1 → send ADC max value (lower byte)
if (receivedByte == 1) {
Serial2.write(adcMaxLowerByte);
}
// Command 2 → send ADC max value (upper byte)
if (receivedByte == 2) {
Serial2.write(adcMaxUpperByte);
}
// Command 3 → send latest potentiometer value (lower byte)
if (receivedByte == 3) {
Serial2.write(adcLowerByte);
}
// Command 4 → send latest potentiometer value (upper byte)
if (receivedByte == 4) {
Serial2.write(adcUpperByte);
}
}
}
Functionality:
Reads the potentiometer value and provides it over UART when requested by MCU2. Also toggles the LED when it receives 'T'.
T
' → toggle LED ON/OFF.0
→ sead potentiometer (ADC 12-bit), split into two bytes, send them back.1
→ send ADC maximum lower byte.2
→ send ADC maximum upper byte.3
→ send potentiometer lower byte (last read).4
→ send potentiometer upper byte (last read).
2)Code of ESP32 as Microcontroller2
#define LED_PIN 4
#define BUTTON_PIN 15
#define DEBOUNCE_DELAY 50 // debounce time in ms
uint8_t rxBuffer[10]; // buffer for UART received data
bool last_button_state = HIGH; // previous raw button state
bool current_button_state = HIGH; // stable debounced state
unsigned long last_debounce_time = 0;
unsigned long previousMillis = 0; // timer for periodic UART task
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP); // button active LOW
Serial.begin(115200);
Serial2.begin(115200, SERIAL_8N1, 16, 17); // UART2: RX=16, TX=17
// attach pin with frequency & resolution
if (!ledcAttach(LED_PIN, 1000, 12)) {
Serial.println("Error: ledcAttach failed");
}
}
void loop() {
// If button is pressed (debounced), send trigger 'T'
if (is_debounced_press(BUTTON_PIN)) {
Serial2.write('T');
}
// Every 100 ms perform UART exchange
if ((millis() - previousMillis) > 100) {
previousMillis = millis();
// Flush any leftover bytes in UART RX buffer
while (Serial2.available()) {
Serial2.read();
}
// Request/receive 5 bytes from other board
for (uint8_t i = 0; i < 5; i++) {
Serial2.write(i); // send request index
while (Serial2.available() < 1)
; // wait for response
rxBuffer[i] = Serial2.read(); // store received byte
delay(1); // small gap
}
// Extract ADC max and ADC current value from received bytes
uint8_t adcMaxLower = rxBuffer[1];
uint8_t adcMaxUpper = rxBuffer[2];
uint8_t adcLower = rxBuffer[3];
uint8_t adcUpper = rxBuffer[4];
uint16_t adcMaxValue = (adcMaxUpper << 8) | adcMaxLower;
uint16_t adcValue = (adcUpper << 8) | adcLower;
Serial.print("Received ADC Value: ");
Serial.println(adcValue);
// Map ADC value to 12-bit PWM range and update LED brightness
uint16_t brightness = map(adcValue, 0, adcMaxValue, 0, 4095);
ledcWrite(LED_PIN, brightness); // write duty cycle to pin
delay(10);
}
}
// Button debounce routine
bool is_debounced_press(int pin) {
bool reading = digitalRead(pin);
// Reset debounce timer on state change
if (reading != last_button_state) {
last_debounce_time = millis();
}
last_button_state = reading;
// Check if stable for debounce delay
if ((millis() - last_debounce_time) > DEBOUNCE_DELAY) {
if (reading != current_button_state) {
current_button_state = reading;
if (current_button_state == LOW) { // pressed
return true;
}
}
}
return false;
}
Functionalty
Reads button, sends 'T' to MCU1 to toggle its LED, requests potentiometer values from MCU1, and uses them to control PWM brightness of its own LED.
Logic
T
' to MCU1.adcMaxValue
(max ADC = 4095)adcValue
(current potentiometer reading)ledcWrite()
.
We are using the Arduino UNO development board and programming it using the Arduino IDE.
In this task, two Arduino UNO boards communicate via UART (Serial Communication) to control the brightness of LED2 and toggle LED1 ON and OFF.
Both Arduinos will log the data as follows:
For proper UART communication between two controllers, both must use the same baud rate (e.g., 9600, 4800, 115200).
1)Arduino UNO as Microcontroller 1
2)Arduino UNO as Microcontroller 2
Note: When interfacing UART devices operating at different voltages (e.g., 5 V ↔ 3.3 V), always use a voltage level shifter to ensure safe logic levels and reliable communication.
1) Code of Arduino UNO as a microcontroller 1:
#include <SoftwareSerial.h>
#define POTENTIOMETER A0
#define LED_PIN 7
SoftwareSerial mySerial(3, 4); // 3-RX, 4-TX (For communication with Board 2)
uint16_t potValue = 0;
uint8_t adcMaxLowerByte = 1023; // Lower byte
uint8_t adcMaxUpperByte = 1023 >> 8; // Upper byte
uint8_t adcLowerByte = 0; // Lower byte
uint8_t adcUpperByte = 0; // Upper byte
void setup() {
pinMode(LED_PIN, OUTPUT);
mySerial.begin(115200); // Initialize software Serial communication
Serial.begin(115200); // Initialize Hardware Serial communication
}
void loop() {
if (mySerial.available()) {
uint8_t receivedByte = mySerial.read(); // Read a single byte
if (receivedByte == 'T') {
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Toggle LED state
Serial.print("LED is ");
Serial.println(digitalRead(LED_PIN) ? "ON" : "OFF"); // Print state
}
if (receivedByte == 0) {
potValue = analogRead(A0); // Read potentiometer value (0-1023)
adcLowerByte = potValue; // Lower byte
adcUpperByte = potValue >> 8; // Upper byte
mySerial.write('0');
}
if (receivedByte == 1) {
mySerial.write(adcMaxLowerByte);
}
if (receivedByte == 2) {
mySerial.write(adcMaxUpperByte);
}
if (receivedByte == 3) {
mySerial.write(adcLowerByte);
}
if (receivedByte == 4) {
mySerial.write(adcUpperByte);
}
}
}
2) Code of Arduino UNO as a microcontroller 2:
#include <SoftwareSerial.h>
#define LED_PIN 9
#define SWITCH_PIN 2
#define DEBOUNCE_DELAY 50 // debounce delay
uint8_t previousValue = 0;
unsigned long previousMillis = 0;
uint8_t txBuffer[20];
uint8_t rxBuffer[10];
uint8_t rxIndex = 0;
bool last_button_state = 1; // Previous button state (1: not pressed, 0: pressed)
bool current_button_state = 1; // Current button state
unsigned long last_debounce_time = 0; // Timestamp of the last button state change
uint16_t adcValue = 0;
SoftwareSerial mySerial(3, 4); // 3-RX, 4-TX (For communication with Board 1)
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(SWITCH_PIN, INPUT_PULLUP);
mySerial.begin(115200); // Initialize Software Serial communication
Serial.begin(115200); // Initialize Hardware Serial communication
}
void loop() {
if (is_debounced_press(SWITCH_PIN)) {
mySerial.write("T\n"); // Send command to toggle LED
}
if((millis() - previousMillis) > 100){
previousMillis = millis();
rxIndex = 0;
for(uint8_t i = 0; i < 5; i++){
mySerial.write(i);
while(mySerial.available() < 1);
rxBuffer[i] = mySerial.read();
delay(1);
}
uint8_t adcMaxValueLowerByte = rxBuffer[1]; // Read the lower byte
uint8_t adcMaxValueUpperByte = rxBuffer[2]; // Read the upper byte
uint8_t adcValueLowerByte = rxBuffer[3]; // Read the lower byte
uint8_t adcValueUpperByte = rxBuffer[4]; // Read the upper byte
// Combine the two bytes into a 16-bit value
uint16_t adcMaxValue = (adcMaxValueUpperByte << 8) | adcMaxValueLowerByte;
// Combine the two bytes into a 16-bit value
uint16_t adcValue = (adcValueUpperByte << 8) | adcValueLowerByte;
// print received brightness value on serial monitor
Serial.print("Received ADC Value: ");
Serial.println(adcValue);
//Map received ADC value to 0 to 255
uint8_t brightness = map(adcValue,0,adcMaxValue,0,255);
analogWrite(LED_PIN, brightness); // Adjust LED brightness
}
}
// Checks if the button is pressed and debounced.
bool is_debounced_press(int button_pin) {
int reading = digitalRead(button_pin);
// If the button state has changed, reset the debounce timer
if (reading != last_button_state) {
last_debounce_time = millis();
}
last_button_state = reading;
// If the button state is stable for more than 50 msec the debounce delay, update the state.
if ((millis() - last_debounce_time) > DEBOUNCE_DELAY) {
if (reading != current_button_state) {
current_button_state = reading;
if (current_button_state == 0) {
return true; // valid press detected
}
}
}
return false; // No valid press detected
}
Includes & Setup:
#include <SoftwareSerial.h>
: Enables software serial communication for both boards.SoftwareSerial mySerial(3, 4);
: Creates a software serial instance on pins 3 (RX) and 4 (TX) for inter-board communication.mySerial.begin(115200);
: Initializes software serial communication at 115200 baud rate.Serial.begin(115200);
: Initializes hardware serial for debugging/logging at 115200 baud rate.
Microcontroller 1 (Transmitter/Receiver) Functionality:
T
': Toggles LED1 and prints status ("ON"/"OFF") to hardware serial0
: Triggers a new potentiometer reading1-4
: Responds with specific bytes of ADC data (max value or current value)
Microcontroller 2 (Receiver/Transmitter) Functionality:
is_debounced_press()
) to detect valid button pressesanalogWrite()
is_debounced_press()
implements proper debouncing:UART communication between two Arduino UNO boards
Hardware Setup
Screen-Shot Of Output