# PDI-2025: Practice 2-A: Implementation of an UART basic driver for data transmission
## Objective
The purpose of the following practice is to implement a simple driver of an UART interface. UART interface is available in the configuration of the simulator that is being used in this laboratory. Specifically, it is the UART (Universal Asynchronous Receiver-Transmitter), which allows asynchronous serial communication with other devices, both in input and output modes.

### Schedule
This practice is planned to be carried out in 1 week.
### P2-A project creation
In order to create the ```P2-A``` project follow the same steps as for creating ```P1```.
## UART Controller registers layout
UART controller has 5 registers. Two of them are data registers, Data_R y Data_W, which are intended, respectively, to read the data received and write the data to be transmitted. The third one is the Status register, that provides information about the current status of the interface. The fourth is the Ctrl register, that controls the UART. Finally, the Scaler register, which is used to set the baudrate at which the UART will work. The following figure shows a schematic of the registers of the controller:

SPIKE places the UART registers in the range of memory addresses ```0xFC001000-0x0xFC00100F```.
*Data_R* and *Data_W* registers have the same address ```0xFC001000```. The UART controller internally selects Data_R in the read operations and Data_W in the write operations. The fact that two registers share the same address is not unusual, since the controllers are designed trying to reduce the number of addresses used. However, it has an implication for debugging, since it will not be possible to inspect the value that is written in Data_W.
The *Status* register address is ```0xFC001004```, while *Ctrl* and *Scaler* are located respectively at ```0xFC001008``` and ```0xFC00100C```.
### UART Status and Control registers description
#### Status Register
The meaning of each bit in the status register is shown below:

The control of the UART used in this practice requires firstly knowing the behaviour of the Transmission *FIFO Full bit* (TF) of the *Status* register (see figure above). This bit shows if the transmission fifo is full (value of TF = 1 ). The algorithm to be used to transmit will simply consist of checking the value of the TFF bit before writing to the Data_W register. If it is 1, the fifo is full, and the driver will wait in a loop for a certain time for the bit to change its value. If after this time TFF remains at 1, it will be considered that there is an error and that it is not possible to transmit the data. In addition, in order to ensure that all the data in the FIFO has been transmitted, the Transmit FIFO Empty(TE) bit, which takes a value of 1 when the transmit FIFO is empty, will be monitored.
#### Control Register
The control register is used to configure various features of the controller. The meaning of each bit in the control register is shown below:

## UART Driver: Basic Transmission Functions
The work to be done consists of coding a series of functions that allow sending and receiving information through the UART. The functions names and files where they will be placed, are listed in the figure below.

Create in the include directory the file ```riscv_uart.h``` with the following content:
```c=
#ifndef RISCV_UART_H_
#define RISCV_UART_H_
#include "riscv_types.h"
int8_t riscv_putchar(char c);
int32_t riscv_getchar();
int8_t riscv_uart_tx_fifo_is_empty();
void riscv_uart_enable_TX();
void riscv_uart_disable_TX();
void riscv_uart_enable_RX();
void riscv_uart_disable_RX();
#endif /* RISCV_UART_H_ */
```
Create in the directory src the file ```riscv_uart.c``` with the following content in which is declared the structure ```UART_regs``` that facilitates the access to UART registers
```c=
#include "riscv_uart.h"
struct UART_regs
{
/** \brief UART Data Register */
volatile uint32_t Data; /* 0xFC001000 */
/** \brief UART Status Register */
volatile uint32_t Status; /* 0xFC001004 */
/** \brief UART Control Register */
volatile uint32_t Ctrl; /* 0xFC001008 */
/** \brief UART Scaler Register */
volatile uint32_t Scaler; /* 0xFC00100C */
};
```
---
Add to file ```riscv_uart.h``` the following macro declarations. Each value represent a mask that defines the position of a bit. The first mask ```riscv_UART_DR```, is already defined.
See the previous definition of the ```Status``` and ```Control``` registers in section [UART Status and Control registers description](#UART-Status-and-Control-registers-description) to assign de right value to each macro.
```c=
// STATUS REGISTER MASKS
#define RISCV_UART_DR 0x00000001 // UART DATA READY
#define RISCV_UART_TF (TO DEFINE) // UART TX FIFO is full
#define RISCV_UART_TE (TO DEFINE) // UART TX FIFO is empty
// CONTROL REGISTER MASKS
#define RISCV_UART_RXE (TO DEFINE) /* RX enable, RX enable, RE control register */
#define RISCV_UART_TXE (TO DEFINE) /* TX enable, TX enable, TE control register */
#define RISCV_UART_LB (TO DEFINE) /* Loop Back enable */
#define RISCV_UART_RI (TO DEFINE) /* Receiver interrupt enable */
```
:::spoiler
```c=
// STATUS REGISTER MASKS
#define RISCV_UART_DR 0x00000001 // UART DATA READY
#define RISCV_UART_TF 0x00000200 // UART TX FIFO is full
#define RISCV_UART_TE 0x00000004 // UART TX FIFO is empty
// CONTROL REGISTER MASKS
#define RISCV_UART_RXE 0x00000001 /* RX enable, RE control register */
#define RISCV_UART_TXE 0x00000002 /* TX enable, TE control register */
#define RISCV_UART_LB 0x00000080 /* Loop Back enable */
#define RISCV_UART_RI 0x00000004 /* Receiver interrupt enable */
```
:::
---
Add to file ```riscv_uart.c``` the declaration of the pointer ```pUART_REGS```. Complete the code so that this pointer points to the address ```0xFC001000```, which is the address where the UART registers are placed
```c=
volatile struct UART_regs * const pUART_REGS= /* to complete */;
```
:::spoiler
```c=
volatile struct UART_regs * const pUART_REGS= (struct UART_regs *)0xFC001000;
```
:::
---
Complete the macro ```riscv_UART_TF_IS_FULL``` and the functions void ```riscv_uart_enable_TX```, ```riscv_uart_disable_TX```, ```riscv_putchar```, ```riscv_uart_tx_fifo_is_empty``` (all of them must be placed in ```riscv_uart.c``` file ).
* ```riscv_UART_TF_IS_FULL``` is a macro that must return 0 only if the TF bit of the Status register is set to 0, indicating that the transmission fifo queue is not full, and a character can be inserted in the transmission fifo
* ```riscv_uart_enable_TX``` must **set** trasmission enable,```TE``` bit in control register. In order to modify bits in the control register, bitwise operators must be used.
* ```riscv_uart_disable_TX``` must **clear** trasmission enable,```TE``` bit in control register
* ```riscv_putchar``` after ```TF``` check, write the character ```c``` passed as a parameter, in the UART Data register. If after an interval (controlled by the ```write_timeout``` variable) the FIFO is still full, the character will not be written in the Data register and the function will return a value other than 0, indication of an error situation.
* ```riscv_uart_tx_fifo_is_empty``` must return ```true``` if the ```TE``` bit in the Status register is set, indicating that the transmit FIFO queue is empty. Return ```false``` otherwise.
```c=
//***************************
#define riscv_UART_TF_IS_FULL() ( /* TO COMPLETE */ )
//***************************
int8_t riscv_putchar(char c)
{
uint32_t write_timeout=0;
while( (riscv_UART_TF_IS_FULL()) && (write_timeout < 0xAAAAA))
{
write_timeout++;
}
if(write_timeout < 0xAAAAA)
//TO COMPLETE. Write the param c in the Data register
// return !0 (true) if write_timeout reach maximun value
// 0 (false) otherwise
return (write_timeout >= 0xAAAAA);
}
//***************************
int8_t riscv_uart_tx_fifo_is_empty()
{
// Return 0 if TE Status bit is not set, !0 otherwise
return ( /* TO COMPLETE */ );
}
//***************************
void riscv_uart_enable_TX()
{
// Set TE bit in Control register
/* TO COMPLETE */
}
//***************************
void riscv_uart_disable_TX()
{
// Clear TE bit in Control register
/* TO COMPLETE */
}
```
:::spoiler
```c=
//***************************
#define riscv_UART_TF_IS_FULL() (RISCV_UART_TF & pUART_REGS->Status)
//***************************
int8_t riscv_putchar(char c)
{
uint32_t write_timeout=0;
while( (riscv_UART_TF_IS_FULL()) && (write_timeout < 0xAAAAA))
{
write_timeout++;
}
if(write_timeout < 0xAAAAA)
pUART_REGS->Data=c;
// return 1 (true) if write_timeout reach maximun value
// 0 (false) otherwise
return (write_timeout >= 0xAAAAA);
}
//***************************
int8_t riscv_uart_tx_fifo_is_empty()
{
return (RISCV_UART_TE & pUART_REGS->Status);
}
//***************************
void riscv_uart_enable_TX()
{
pUART_REGS->Ctrl |= RISCV_UART_TXE;
}
//***************************
void riscv_uart_disable_TX()
{
pUART_REGS->Ctrl &= ~RISCV_UART_TXE;
}
```
:::
### First main test
Type the following ```main``` program and build example.
```c=
#include "riscv_types.h"
#include "riscv_uart.h"
// Other includes
#include "log.h"
int main()
{
// TX enable
riscv_uart_enable_TX();
// First test, console output
riscv_putchar('P');
riscv_putchar('2');
riscv_putchar('\n');
// Wait until TX fifo is empty
do {
// do nothing
} while( !riscv_uart_tx_fifo_is_empty() );
/* This is the same using a while loop
while(!riscv_uart_tx_fifo_is_empty())
; // empty statement
*/
return 0;
}
```
In order to test the program follow the steps below:
* Use favorites toolbar link on the left to open ```cutecom```. Just in case that it don't exist, open a new operating system Command Window (Terminal) using  icon and type ```cutecom```. This opens a basic terminal monitor.
* Once the terminal is open connect it to ```/dev/tnt3``` device. Select ```/dev/tnt3``` and press ```Open``` button.
* Finally test the implementation and verify that the output is as expected by typing ```./runP2-A``` in eclipse terminal window.
The program must show the following output in the terminal window.

### Student work
Press the ```Hex output``` checkbox, run again the program and see the values written in the terminal monitor.
:::success
:raising_hand: What does these values mean?
:::
---
Add to ```riscv_uart.h``` the following funtion prototype:
```c=
//transmits through the UART the string passed as a parameter
int8_t riscv_print_string(char* str);
```
Add the implementation of the function to ```riscv_uart.c```
:::info
:information_source: For ```riscv_print_string``` implementation, remember that the last character of a string is the '\0'. The function loop through the char array pointed by ```str``` and send each individual char using ```riscv_putchar```.
Do not forget to declare all the variables you may need. Also include all the headers files that may be required
:::
Finally test the implementation and verify that the output is as expected. For example use the following ```main``` program.
```c=
#include "riscv_types.h"
#include "riscv_uart.h"
int main()
{
char * pchar="Device Programming\n";
// TX enable
riscv_uart_enable_TX();
// First test, console output
riscv_putchar('P');
riscv_putchar('2');
riscv_putchar('\n');
// print string test
riscv_print_string(pchar);
// Wait until TX fifo is empty
do {
// do nothing
} while( !riscv_uart_tx_fifo_is_empty() );
/* This is the same using a while loop
while(!riscv_uart_tx_fifo_is_empty())
; // empty statement
*/
return 0;
}
```
:::spoiler
```c=
int8_t riscv_print_string(char* str)
{
int8_t error=0;
uint32_t i = 0;
while( str[i] != '\0' && (!error) )
{
error = riscv_putchar( str[i] );
i++;
}
while(!riscv_uart_tx_fifo_is_empty())
; // empty statement
return error;
}
``````
:::
---
## UART Driver: Basic Receive Functions
It is possible to read caracters typed in the terminal window and process them in a program.
### riscv_uart_enable_RX and riscv_uart_disable_RX functions
In order to receive, the receiver enable, ```RE```bit, in control register must be set. Code ```riscv_uart_enable_RX``` and ```riscv_uart_disable_RX``` functions and add them to ```riscv_uart.c``` file. The declaration of both functions are in ```riscv_uart.h```
### riscv_getchar function
Code the ```riscv_getchar``` according to the following flow diagram and add it to ```riscv_uart.c``` file. The funtion prototype is already in ```riscv_uart.h```

Test the implementation of ```riscv_getchar``` with the following ```main``` program.
```c=
// TX/RX enable
riscv_uart_enable_TX();
riscv_uart_enable_RX();
riscv_print_string("Type a caracter string: ");
do
{
// Blocking loop until a char is received
do {
rec = riscv_getchar();
} while( rec == -1 );
car = (char)(rec);
printf("Recibido: [%c:%d]\n", (char)car, car);
} while (rec != 27);
```
:::info
:information_source: Do not forget to declare all the variables you may need. Also include all the headers files that may be required
:::
Type different strings in cutecom window and see the received values. Select different settings for line termination (LF, CR, CR/LF, NONE). Type also hex values and see the values received.
:::success
:raising_hand: What does CR and LF mean? See this video [CRLF](https://www.youtube.com/watch?v=VdVvXr5ZigM)
:raising_hand: The program ends when receive the char code 27 (0x1B). What does this code mean?
In order to avoid ad-hoc values in code, add to ```riscv_uart.h``` the following definition:
```c=
//transmits through the UART the string passed as a parameter
#define ESC 27
```
and change the main loop condition to:
```c=
} while (rec != ESC);
```
This improves the readability of the code.
:::
:::spoiler
```c=
int32_t riscv_getchar()
{
int32_t uart_rx_data;
if ( RISCV_UART_DR & pUART_REGS->Status)
{
uart_rx_data = pUART_REGS->Data;
}
else
{
uart_rx_data = -1;
}
return uart_rx_data;
}
``````
:::
### Student work: Parsing the fields of a Telecommand Header
Lets type the following sequence as hex values in ```cutecom``` window: ```0x1B 0x2C 0xC0 0x01 0x00 0x05```, use ```None``` as line termination and assume that the sequence corresponds to a telecommand primary header. See [https://hackmd.io/@parraman/prog-lab-gfie-practica-1#Parte-1-lectura-del-telecomando-e-identificación-de-campos](https://hackmd.io/@parraman/prog-lab-gfie-practica-1#Parte-1-lectura-del-telecomando-e-identificaci%C3%B3n-de-campos) to remain the fields of a TC structure.

Write a ```main``` program that runs as follows:
1. The start of TC is always ```0x1B``` since APID must not change. All data received before will be ignored.
2. After ```0x1B``` is read, wait and read the following five ```uint8_t``` data and saved them in an array.
3. Decode de packet header and print the values of the following packet fields:
* TYPE
* PKT SEC HDR FLAG
* APID
* SEQUENCE FLAGS
* PACKET SEQUENCE
* PKT DATA LEN
4. Send an ack to indicate that the packet has been processed. The ACK will be the string "OK".
5. Wait for next start of TC. Write a new sequence corresponding to a different TC, changing de values of some fields (```SEQUENCE FLAGS```, ```PACKET SEQUENCE```, ```PKT DATA LEN``` ) and test if the decoding procedures runs as expected.
:::info
:information_source: Try to reuse as much as posible al the decoding code you have already written for the subject *Fundamentos de la Programación*

:::
:::spoiler
The meaning of the different fields is as follows:
```
PACKET PRIMARY HEADER ... 1B 2C C0 01 00 05
VERSION: 0
TYPE: 1 (TC)
PKT SEC HDR FLAG: 1 (YES)
APID: 812 (0x32C)
SEQUENCE FLAGS: 3
PACKET SEQUENCE: 1 (0x1)
PKT DATA LEN: 5 (6 bytes - 1)
```