- Pre-Lab preparation
- Part 1: Synchronize repositories and create a new project
- Part 2: GPIO control registers
- Part 3: GPIO library files
- Challenges
- References
- Arduino Uno board, USB cable
- Breadboard
- 2 LEDs
- 1 two-color LED
- 4 resistors
- 1 push button
- Jumper wires
- Logic analyzer
After completing this lab you will be able to:
- Configure input/output ports of AVR using control registers
- Use ATmega328P manual and find information
- Understand the difference between header and source files
- Create your own library
- Understand how to call a function with pointer parameters
The purpose of this laboratory exercise is to learn how to create your own library in C. Specifically, it will be a library for controlling GPIO (General Purpose Input/Output) pins with help of control registers.
-
Fill in the following table and enter the number of bits and numeric range for the selected data types defined by C.
Data type Number of bits Range Description uint8_t
8 0, 1, ..., 255 Unsigned 8-bit integer int8_t
uint16_t
int16_t
float
-3.4e+38, ..., 3.4e+38 Single-precision floating-point void
-- -- Depending on the context, it means no type, no value or no parameters -
Any function in C contains a declaration (function prototype) and definition (block of code, body of the function), and each declared function can be executed (called). Study this article and complete the missing sections in the following user defined function declaration, definition, and call.
#include <avr/io.h> // Function declaration (prototype) uint16_t calculate(uint8_t, *** ); int main(void) { uint8_t a = 210; uint8_t b = 15; uint16_t c; // Function call c = *** (a, b); // Infinite loop while (1) ; // Will never reach this return 0; } // Function definition (body) *** calculate(uint8_t x, uint8_t y) { uint16_t result; // result = x^2 + 2xy + y^2 result = x*x; *** *** return result; }
-
Find differences between
variable
andpointer
in C. What mean notations*variable
and&variable
?
When you begin working, ensure that you synchronize the contents of your working folder and local repository with the remote version on GitHub. This practice ensures that none of your changes are lost.
-
In your working directory, use Source Control (Ctrl+Shift+G) in Visual Studio Code or Git Bash (on Windows) or Terminal (on Linux) to update the local repository.
Help: Useful bash and git commands are
cd
- Change working directory.mkdir
- Create directory.ls
- List information about files in the current directory.pwd
- Print the name of the current working directory.git status
- Get state of working directory and staging area.git pull
- Update local repository and working folder. -
In Visual Studio Code create a new PlatformIO project
lab2-gpio_library
forArduino Uno
board and change project location to your local repository folderDocuments/avr-course
. -
IMPORTANT: Rename
LAB2-GPIO_LIBRARY > src > main.cpp
file tomain.c
, ie change the extension to.c
.
AVR microcontroller associates pins into so-called ports, which are marked with the letters A, B, C, etc. Each of the pins is controlled separately and can function as an input (entry) or output (exit) point of the microcontroller. Control is possible exclusively by software via control registers.
There are three control registers for each port: DDR, PORT and PIN, supplemented by the letter designation of the port. For port A these are registers DDRA, PORTA and PINA, for port B registers DDRB, PORTB, PINB, etc.
DDR (Data Direction Register) is used to set the input/output direction of port communication, PORT register is the output data port and PIN register works for reading input values from the port.
A detailed description of working with input/output ports can be found in ATmega328P datasheet in section I/O-Ports.
-
Copy/paste your solution with two LEDs from Lab1 to
LAB2-GPIO_LIBRARY > src > main.c
source file. Compile (build) the project and note the first-version size in bytes.Version Size [B] Ver. 1: Arduino-style Ver. 2: Registers Ver. 3: Library functions -
Use the datasheet to find out the meaning of the DDRB and PORTB control register values and their combinations. (Let PUD (Pull-up Disable) bit in MCUCR (MCU Control Register) is 0 by default.)
DDRB PORTB Direction Internal pull-up resistor Description 0 0 input no Tri-state, high-impedance 0 1 1 0 1 1 -
To control individual bit(s) within a register, the following binary operations are used.
|
OR&
AND^
XOR~
NOT<<
binary shift to left
b a b OR a b AND a b XOR a NOT b 0 0 0 0 0 1 0 1 1 0 1 1 1 0 1 0 1 0 1 1 1 1 0 0 // Set bit value to 1 reg = reg & (1<<bit); // Clear bit value to 0 reg = reg & ~(1<<bit); // Toggle bit value reg = reg ^ (1<<bit);
-
Comment Arduino-style defines and functions, use binary operations with control registers DDRB, PORTB and rewrite the application. Note the second-version size after the compilation.
... // #include "Arduino.h" // #define PB5 13 // In Arduino world, PB5 is called "13" // #define PB0 8 int main(void) { uint8_t led_value = 0; // Local variable to keep LED status // Set pins where LEDs are connected as output // Ver 1: Arduino style // pinMode(LED_GREEN, OUTPUT); // pinMode(LED_RED, OUTPUT); // Ver 2: Low-level (register) style DDRB = DDRB | (1<<LED_GREEN); ... }
For clarity and efficiency of the code, the individual parts of the application in C are divided into two types of files: header files and source files. Note that together they form one module.
Header file is a file with extension .h
and generally contains definitions of data types, function prototypes and C preprocessor commands. Source file has the extension .c
and is used to implement the code. It is bad practice to mix usage of the two although it is possible.
C programs are highly dependent on functions. Functions are the basic building blocks of C programs and every C program is combination of one or more functions. There are two types of functions in C: built-in functions which are the part of C compiler and user defined functions which are written by programmers according to their requirement.
To use a user-defined function, there are three parts to consider:
-
Function declaration or Function prototype (
*.h
file) -
Function definition (
*.c
file) -
Function call (
*.c
file)
A function prototype is simply the declaration of a function that specifies function's name, parameters and return type. It doesn't contain function body. A function prototype gives information to the compiler that the function may later be used in the program.
A function definition contains the block of code to perform a specific task.
By calling the function, the control of the program is transferred to the function.
A header file can be shared between several source files by including it with the C preprocessing directive #include
. If a header file happens to be included twice, the compiler will process its contents twice and it will result in an error. The standard way to prevent this is to enclose the entire real contents of the file in a conditional, like this:
#ifndef HEADER_FILE_NAME // Preprocessor directive allows for conditional compilation. If not defined.
# define HEADER_FILE_NAME // Definition of constant within your source code.
// The body of entire header file
#endif // The #ifndef directive must be closed by an #endif
This construct is commonly known as a wrapper #ifndef
. When the header is included again, the conditional will be false, because HEADER_FILE_NAME
is already defined. The preprocessor will skip over the entire contents of the file, and the compiler will not see it twice.
-
In PlatformIO project, create a new folder
LAB2-GPIO_LIBRARY > lib > gpio
. Within this folder, create two new filesgpio.c
andgpio.h
. See the project structure:LAB2-GPIO_LIBRARY // PlatfomIO project ├── include // No need this ├── lib // Libraries │ └── gpio // Our new GPIO library │ ├── gpio.c │ └── gpio.h ├── src // Source file(s) │ └── main.c ├── test // No need this └── platformio.ini // Project Configuration File
-
Copy/paste header file to
gpio.h
-
Copy/paste library source file to
gpio.c
-
Include header file to
src > main.c
:// Include the header file of library you are using #include <gpio.h> int main(void) { ... }
-
-
Go through both files and make sure you understand each line. The GPIO library defines the following functions.
Return Function name Function parameters Description void
GPIO_mode_output
volatile uint8_t *reg, uint8_t pin
Configure one output pin void
GPIO_mode_input_pullup
volatile uint8_t *reg, uint8_t pin
Configure one input pin and enable pull-up resistor void
GPIO_write_low
volatile uint8_t *reg, uint8_t pin
Write one pin to low value void
GPIO_write_high
volatile uint8_t *reg, uint8_t pin
Write one pin to high value uint8_t
GPIO_read
volatile uint8_t *reg, uint8_t pin
Read a value from input pin Note: Suggestions for other features you can add are:
void GPIO_mode_input_nopull(volatile uint8_t *reg, uint8_t pin)
Configure one input pin without pull-up resistorvoid GPIO_toggle(volatile uint8_t *reg, uint8_t pin)
Toggle one pin value
The register name parameter must be
volatile
to avoid a compiler warning. Note that the C notation*variable
representing a pointer to memory location where the variable's value is stored. Notation&variable
is address-of-operator and gives an address reference of variable.// Do not forget to include GPIO header file #include <gpio.h> int main(void) { ... // Examples of various function calls GPIO_mode_output(&DDRB, LED_GREEN); // Set output mode in DDRB reg ... GPIO_write_low(&PORTB, LED_GREEN); // Set output low in PORTB reg .... temp = GPIO_read(&PIND, BTN); // Read input value from PIND reg ... }
Note: Understanding C Pointers: A Beginner's Guide is available here. Explanation of how to pass an IO port as a parameter to a function is given here.
-
In
main.c
comment binary operations with control registers (DDRB, PORTB) and rewrite the application with library functions. Note its size after the compilation as third-verion. Try to optimize code to the most effective way.// Do not forget to include GPIO header file #include <gpio.h> int main(void) { uint8_t led_value = 0; // Local variable to keep LED status // Set pins where LEDs are connected as output // Ver 1: Arduino style // pinMode(LED_GREEN, OUTPUT); // pinMode(LED_RED, OUTPUT); // Ver 2: Low-level (register) style // DDRB = DDRB | (1<<LED_GREEN); // DDRB = DDRB | (1<<LED_RED); // Ver 3: Library function style GPIO_mode_output(&DDRB, LED_GREEN); ... }
-
On a breadboard, connect a two-color LED (3-pin LED) and resistors to pins PB2 and PB3. Develop the code to achieve alternating blinking of two LEDs.
-
(Optional) On a breadboard, connect an active-low push button to pin PD2. In your code, activate the internal pull-up resistor on this pin. Make the LEDs blink only when the button is pressed.
-
After completing your work, ensure that you synchronize the contents of your working folder with both the local and remote repository versions. This practice guarantees that none of your changes are lost. You can achieve this by using Source Control (Ctrl+Shift+G) in Visual Studio Code or by utilizing Git commands.
Help: Useful git commands are
git status
- Get state of working directory and staging area.git add
- Add new and modified files to the staging area.git commit
- Record changes to the local repository.git push
- Push changes to remote repository.git pull
- Update local repository and working folder. Note that, a brief description of useful git commands can be found here and detailed description of all commands is here.
-
Complete declarations (
*.h
) and definitions (*.c
) of GPIO suggested functionsGPIO_mode_input_nopull()
andGPIO_toggle()
. -
Connect at least five LEDs and one push button to the microcontroller and program an application in Knight Rider style. When you press and release a push button once, the LEDs starts to switched on and off; ensure that only one of LEDs is switched on at a time. Do not implement the blinking speed changing.
-
Simulate the Knight Rider application in SimulIDE.
-
Draw a schematic of Knight Rider application. The image can be drawn on a computer or by hand. Always name all components, their values and pin names!
-
Parewa Labs Pvt. Ltd. C User-defined functions
-
avr-libc. How do I pass an IO port as a parameter to a function?
-
Tomas Fryza. Useful Git commands
-
LEDnique. LED pinouts - 2, 3, 4-pin and more