# PDI-2025: Practice 1. General Purpose Input/Output port
## Objetive
The purpose of the following practice is to implement a simple driver of the GPIO controller. The selected interface is available in the configuration of the SPIKE simulator that is being used in this laboratory.

In our case the GPIO controller is connected to a 32-bit I/O port. Some of those bits are connected to leds, pushbuttons and switches present on the hardare of the board being simulated.

The implementation of a driver for such a device requires the knowledge of the controller behaviour at a certain level of detail. Each controller register set has a specific layout which must be analysed before to proceed the driver design.
### Schedule
This practice has two execises and is planned to be carried out in 1 week.
## GPIO Controller registers layout
The GPIO controller has 3 registers:
* Input register: Used to read current value of the 32 port.
* Output register: Used to write data to 32 bits port.
* Direction register: Defines which of the 32 bits are used as output. One bit to '1' in the direction register, define the bit as output.
The reset value of these registers is zero.
Is important to remaing that the registers are read/written as a whole. When a 32 bits word is written to the output register only output bits are modified and the others remain unchanged.
On the other hand, when the input register is read, all bits (32) are read and output bits will contain the last value written.

### Direction register (DIR_CTRL_REG)
In order to properly program the direction register it is necessary to know to which bits the leds, switches and pushbuttons on the board are connected.
* ```Switches```: SW[0..3] on GPIO input pins [0..3]
* ```Push buttons```: PBT[0..3] on GPIO input pins [4..7]
* ```Leds```: LEDS[0..3] on GPIO output pins [16..19]
So to program the bits ```GPIO[16..19]``` as output the bits [16..19] of direction register must be set to '1'.

## P1 project creation
In order to do P1 a new eclipse project will be created.
It is posible to create a project from scratch through **File -> New -> C/C++ Project**, however it is easier to copy and paste a previous project and just change a few configuration elements.
### Copy P0 project
Select **Project -> Clean** and clean ```P0``` project to remove all temporary files.
Select the previous project ```P0``` and open contextual menu by clicking mouse right button. Click ```copy``` option to copy current project and ```paste``` it in the project explorer. Eclipse will suggest you a name for the new project but it is possible to change it. Name the new project as ```P1```. These actions could also be done through **Edit** menu.
The new project ```P1``` is created but it maintains some configuration issues that must changed:
* Edit ```runP0``` script and change all ```P0``` project references to ```P1```
* Rename ```runP0``` script to ```runP1```
* Edit ```debugP0``` script and change all ```P0```project references to ```P1```
* Rename ```debugP0``` script to ```debugP1```
<!--
Open project ```Settings```, in **Settings -> Tool Settings -> Cross GCC Compiler -> includes** tab, change ```P0``` project references to ```P1```: ```${workspace_loc:/P0/include}``` -> ```${workspace_loc:/P1/include}```
Open **Run -> Debug Configurations** and change all ```P0``` project references to ```P1```
-->
Now the project ```P1``` is fully functional.
Since this practice has several exercises, the student may create different projects for each one, for example P1_1, P1_2 and so on.
## GPIO Driver: Basic port handling Functions
The work to be done consists of coding a series of functions that allow handling the 32 bits of the GPIO port The functions names and files where they will be placed, are listed in the figure below.

Create in the include directory the file ```gpio_drv.h``` with the following content:
```c=
/*
* gpio_drv.h
*
* Created on: <creation date>
* Author: <student name>
*/
#ifndef _GPIO_DRV_H_
#define _GPIO_DRV_H_
#define INPUT 0 // Input reg
#define OUTPUT 1 // Output reg
#define DIRECTION 2 // Direction
#define SW_0_MASK 0x00000001 // First switch
#define SW_1_MASK (TO DEFINE) // Second switch
#define SW_2_MASK (TO DEFINE) // Third switch
#define SW_3_MASK (TO DEFINE) // Fourth switch
#define LED_0_MASK 0x00010000 // First led
#define LED_1_MASK (TO DEFINE) // Second led
#define LED_2_MASK (TO DEFINE) // Third led
#define LED_3_MASK (TO DEFINE) // Fourth led
void gpio_set_direction( uint32_t direction );
uint32_t gpio_get_direction( void );
void gpio_write( uint32_t output );
uint32_t gpio_read();
#endif /* _GPIO_DRV_H_ */
```
---
Each macro value represent a mask that defines the position of a bit. The masks ```SW_0_MASK``` and ```LED_0_MASK```, are already defined. Assign de right value to the others.
:::spoiler
```c=
#define SW_0_MASK 0x00000001 // First switch
#define SW_1_MASK 0x00000002 // Second switch
#define SW_2_MASK 0x00000004 // Third switch
#define SW_3_MASK 0x00000008 // Fourth switch
#define LED_0_MASK 0x00010000 // First led
#define LED_1_MASK 0x00020000 // Second led
#define LED_2_MASK 0x00040000 // Third led
#define LED_3_MASK 0x00080000 // Fourth led
```
:::
---
Create in the directory src the file ```gpio_drv.c``` and type the following code:
```c=
/*
* gpio_drv.c
*
* Created on: <creation date>
* Author: <student name>
*/
#include "riscv_types.h"
#include "gpio_drv.h"
volatile uint32_t *GPIO = /* TO COMPLETE */
void gpio_set_direction( uint32_t direction )
{
GPIO[DIRECTION] = direction;
}
uint32_t gpio_get_direction( void )
{
return GPIO[DIRECTION];
}
void gpio_write( uint32_t output )
{
// Copy output to GPIO output register
/* TO COMPLETE */
}
uint32_t gpio_read( void )
{
// Return GPIO input register value
/* TO COMPLETE */
}
```
---
Complete the code so that ```GPIO``` pointer points to the address ```0xFC083000```, which is the address where the GPIO registers are placed. Complete also ```gpio_write``` and ```gpio_read``` functions.
:::spoiler
```c=
volatile uint32_t *GPIO = (uint32_t *)0xFC083000;
//-----
void gpio_write( uint32_t output )
{
GPIO[OUTPUT] = output;
}
uint32_t gpio_read( void )
{
return GPIO[INPUT];
}
```
:::
---
The GPIO pointer uses the ```volatile``` attribute in its definition.
:::success
:raising_hand: Please, try to obtain information about the ```volatile``` attribute by your self and why must be used in this case.
:::
:::spoiler
The volatile atribute forces the compiler to ***NOT*** optimize the conditional execution code based on the value those fields can take in the code. For example:
```c=
uint8_t aux;
aux = 0;
if(aux > 0) {
// The optimizer does not include this code, nor the
// the condition if(aux > 0), due to aux has been set
// to 0 and the condition is never met
} else {
// Code
}
```
Even more, these values are never saved to cache memory, this means that the software always get the value from the main memory. This allows these fields to be modified via hardware, and the software always checks the value they have, regardless of whether it has just been assigned to a constant value.
:::
---
### First main test
Type the following ```main``` program:
```c=
int main()
{
uint32_t input;
uint32_t dir;
// Enable output on bits 19..16
dir = gpio_get_direction();
dir |= 0x000F0000; // It should be defined as a mask
gpio_set_direction( dir );
printf("P1_1\n");
while(1)
{
// Get GPIO bits
input = gpio_read();
if ( input & SW_0_MASK )
{
// Turns LED0 ON only if it is OFF
// if ( !(input & LED_0_MASK) )
if ( (input & LED_0_MASK) == 0 )
{
printf("Turn LED0 on\n");
input |= LED_0_MASK;
gpio_write( input );
}
}
else
{
// Turns LED0 OFF only if it is ON
// if ( input & LED_0_MASK )
if ( (input & LED_0_MASK) != 0 )
{
printf("Turn LED0 off\n");
input &= ~LED_0_MASK;
gpio_write( input );
}
}
} // while(1)
return 0;
}
```
Build and run the project by typing ```./runP1``` in terminal window. Change the value of the switches and see how the status of the leds changes.
## Student work
* Extend the code to handle all the switches. Switch one must handle led one and so on.
* Change the code replacing the four ```if``` chain and use a for loop.
:::spoiler
```c=
#define N 4
output_mask = LED_0_MASK;
input_mask = SW_0_MASK;
for ( int i = 0; i < N; i++ )
{
if ( input & input_mask )
{
// Switch led on if it is off
if ( (input & output_mask) == 0 )
{
input |= output_mask;
gpio_write( input );
}
}
else
{
// Switch led off if it is on
if ( (input & output_mask) != 0 )
{
input &= ~output_mask;
gpio_write( input );
}
}
output_mask <<= 1;
input_mask <<= 1
}
```
Another solution using arrays to establish the relationship between switches and LEDs
```c=
#define N 4
uint32_t output_mask[N] = { LED_0_MASK, LED_1_MASK, LED_2_MASK, LED_3_MASK };
uint32_t input_mask[N] = { SW_0_MASK, SW_1_MASK, SW_2_MASK, SW_3_MASK };
for ( int i = 0; i < N; i++ )
{
if ( input & input_mask[i] )
{
// Switch led on if it is off
if ( (input & output_mask[i]) == 0 )
{
input |= output_mask[i];
gpio_write( input );
}
}
else
{
// Switch led off if it is on
if ( (input & output_mask[i]) != 0 )
{
input &= ~output_mask[i];
gpio_write( input );
}
}
}
```
:::
### Second exercise
Add the following code belonging to ```delay``` function to the main code, just above ```main``` function. This function will be used just to spend time.
```c=
void delay( uint64_t ticks )
{
uint64_t cnt = 0;
do { cnt++; } while ( cnt < ticks );
}
```
Change ```main``` function acording to the following code.
```c=
int main()
{
uint32_t mask = LED_0_MASK;
uint32_t dir;
printf("P1_2\n");
// Enable output on bits 19..16
dir = gpio_get_direction();
dir |= 0x000F0000;
gpio_set_direction( dir );
printf ( "Dir: %08X\n", gpio_get_direction() );
while(1)
{
gpio_write( mask );
printf("INPUT VALUE: %08X\n", gpio_read() );
mask <<= 1;
if ( mask == 0x00100000 )
{
mask = LED_0_MASK;
}
delay( 10000000 );
}
return 0;
}
```
Build and run the program. Which graphical effect is carried out by the leds?
:::success
:raising_hand: Answer the following cuestions:
* Why is ```mask``` variable compared to 0x00100000? What does this value mean?
* The leds lights for left to right, change the code in order to do it for right to left.
* Implement any other graphic effect you can think of, for example make the leds blink.
:::
<!--
## Student work
* Why is ```mask``` variable compared to 0x00100000? What does this value mean?
* The leds lights for left to right, change the code in order to do it for right to left.
* Implement any other graphic effect you can think of.
-->
### Third exercise
In this exercise we are going to use the push buttons. These elements generate the following signal without bouncing, when pressed:

:::success
:raising_hand: Please, try to obtain information about the ```bouncing``` effect when real buttons are pressed/released and how could be avoided.

:::
## Student work
Code a program that reads the state of the BT0 input pin, detects the rising edge and toggles the state of LED0. In this way and starting from an initial state of LED0 off, when pressing the button the LED will turn on. When pressing the button again, the LED will turn off and so on.
Complete the program to service the remaining three buttons and modify the status of the corresponding LEDs.
:::spoiler
```c=
#include "riscv_types.h"
#include "gpio_drv.h"
#include "log.h"
/*
- Push buttons
BTN[0..3]: GPIO inputs [4..7]
- Switches
SW[0..3] GPIO inputs [0..3]
- LED[0..3]: GPIO0 outputs [16..19]
*/
int main()
{
uint32_t dir, input_current, input_previous;
uint32_t button_current, button_previous;
printf("P1_EDGE\n");
// Enable output on bits 19..16
dir = gpio_get_direction();
dir |= 0x000F0000;
gpio_set_direction( dir );
input_current = gpio_read();
while(1)
{
input_previous = input_current;
input_current = gpio_read();
button_previous = input_previous & BT_0_MASK;
button_current = input_current & BT_0_MASK;
// if ( button_current && !button_previous ) // Rising edge
if ( !button_current && button_previous ) // Falling edge
{
if ( input_current & LED_0_MASK ) // If LED is on
{
// switch it off
input_current &= ~LED_0_MASK;
gpio_write( input_current );
}
else
{
// switch it on
input_current |= LED_0_MASK;
gpio_write( input_current );
}
} // if
} // while
return 0;
}
/*
Handling the four buttons using a ´´´for´´´ loop
#define N 4
int main()
{
uint32_t dir, input_current, input_previous;
uint32_t button_current, button_previous;
uint32_t button_mask, led_mask;
printf("P1_EDGE\n");
// Enable output on bits 19..16
dir = gpio_get_direction();
dir |= 0x000F0000;
gpio_set_direction( dir );
input_current = gpio_read();
while(1)
{
input_previous = input_current;
input_current = gpio_read();
button_mask = BT_0_MASK;
led_mask = LED_0_MASK;
for (int i=0; i < 4; i++ ) // There are four buttons...
{
button_current = input_current & button_mask;
button_previous = input_previous & button_mask;
// if ( button_current && !button_previous ) // Rising edge
if ( !button_current && button_previous ) // Falling edge
{
if ( input_current & led_mask ) // If LED is on
{
// switch it off
input_current &= ~led_mask;
gpio_write( input_current );
}
else
{
// switch it on
input_current |= led_mask;
gpio_write( input_current );
}
}
led_mask <<= 1;
button_mask <<= 1;
}
// delay( 10000000 );
}
return 0;
}
*/
```