Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.build
.mbed
projectfiles
*.py*
mbed-os
BUILD
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
*.txt
10 changes: 10 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}
12 changes: 12 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"files.associations": {
"array": "cpp",
"deque": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"string_view": "cpp",
"initializer_list": "cpp",
"*.tcc": "cpp"
}
}
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Device Driver Tutorial
*Authors: Ben Everson, Wilson Guo*
*Authors: Ben Everson, Wilson Guo, Bowen Quan*

A hands-on introduction to Badgerloop embedded device driver architecture, development, and testing.

Expand Down Expand Up @@ -48,19 +48,19 @@ The bits in the GPIO port register correspond to the level of each GPIO pin. As

I know that was a lot of information, but now you should understand the mechanisms we are going to use to interact with the pins on our MCP23017. This section of the tutorial will involve implementing functions to configure, monitor, and control the pins on an MCP23017.

Let's start by looking at the I2C serial communication class. Take a look at the definition of the I2C class in the [MbedOS API](https://os.mbed.com/docs/mbed-os/v6.15/apis/i2c.html). Since the Mcp23017 class will contain an instance of the I2C class, it's important to unerstand the protected methods we will have at our disposal. These methods are already implemented, but we will be using them to actually send messages on the I2C bus. The two important methods we will use in this tutorial are the `write (int address, const char *data, int length, bool repeated=false)` method and the `read (int address, char *data, int length, bool repeated=false)`.
Let's start by looking at the I2C serial communication class. We will use [Arduino's Wire library](https://www.arduino.cc/reference/en/language/functions/communication/wire/) for this. The important methods we will use are: [write()](https://www.arduino.cc/reference/en/language/functions/communication/wire/write), [read()](https://www.arduino.cc/reference/en/language/functions/communication/wire/read), and other related methods like `beginTransmission()`, `endTransmission()`, `requestFrom()`. Make sure to look at the examples in `write()` and `read()` links to see how they are used and how the related methods are used.

**Note**: The data in an I2C message is always one or more bytes long. This means that we can't just tell the device to write or read a single bit of a register. We will always be dealing with all 8 bits, AKA a byte, at once.

Now that we know what functions we will use to read and write the MCP23017 registers, we can start implementing the functions defined in [include/mcp23017.h](include/mcp23017.h). The stubs are layed out for you in [src/mcp23017.cpp](src/mcp23017.cpp). If you feel confident in implementing these functions, give it a try! Feel free to skip to [Testing](#testing) section once you are done. If you still feel like you need some help, I will go over each of these methods one by one.
Now that we know what functions we will use to read and write the MCP23017 registers, we can start implementing the functions defined in [include/mcp23017.h](include/mcp23017.h). The stubs are laid out for you in [src/mcp23017.cpp](src/mcp23017.cpp). If you feel confident in implementing these functions, give it a try! Feel free to skip to [Testing](#testing) section once you are done. If you still feel like you need some help, I will go over each of these methods one by one.

**Note:** You will see a data type, `uint8_t`, that might not be familiar to you. This data type represents an unsigned 8 bit integer (a byte). It will be used whenever we are dealing with register addresses and values.

**The Constructor**<br>
This function simply needs to record the identifying information that we pass in (the device address) and store the I2C instance that we use to access the bus. You will see how we use these members later when we access the device.

**`get_dir`**<br>
This function should return the value of a specific pin's direction. To do this, we will need to do a few things. First, we need to first specify to the MCP what register we want to read from by writing the proper register offset to it. Second, we will need to read from the iodir register we just specified. Third, since we have to read the value of the entire register (remember we can't just read one bit), we have to do some bitwise arithmetic to figure out what part of the byte we read corresponds to the pin we are looking for. Finally, we can return that value.
This function should return the value of a specific pin's direction. To do this, we will need to do a few things. First, we need to first specify to the MCP what register we want to read from by writing the proper register offset to it. Second, we will need to read **a byte** from the iodir register we just specified. Third, we have to do some bitwise arithmetic to figure out what part of **the byte** we read corresponds to the pin we are looking for. Finally, we can return that value.

**Hint:** You might find use for the and (&) and shift right(>>) operators to filter out the pin you are looking for.

Expand All @@ -78,15 +78,15 @@ I know that was a lot of information, but now you should understand the mechanis
Similar to how `get_dir` was nearly identical to `get_state`, `set_state` is going to be very similar to `set_dir` . There shouldn't be too many changes here.

**`begin`**<br>
This function is responsible for confirming I2C communication between the computer and the device, as well as setting the direction of each pin on the device. While all of the I2C initialization is handled by the `I2C` class, it is often a good idea to perform a verifiable I2C transaction to make sure everything is working correctly. Most I2C devices contain a register that holds what is called a Device ID number. Look in the datasheet and see if you can find this register and the corresponding ID number for the MCP23017. If we can read and verify this number in the `begin` function, we can have confidence that our I2C communication is working properly. You will also be setting the direction of each pin using the function(s) you implemented above. Note that this function takes in an array of 8 integers called `directions`. These will correspond to the direction of each pin in the device's bank.
This function is responsible for confirming I2C communication between the computer and the device, as well as setting the direction of each pin on the device. It is a good idea to perform a verifiable I2C transaction to make sure everything is working correctly. Most I2C devices contain a register that holds what is called a Device ID number, but the MCP23017 doesn't. So, we will use the IODIRA register as our "Device ID" register instead. If we can read and verify the default value of the IODIRA register in the `begin` function, we can have confidence that our I2C communication is working properly. You will also be setting the direction of each pin using the function(s) you implemented above. Note that this function takes in an array of 8 integers called `directions`. These will correspond to the direction of each pin in the device's bank.

## Testing
So you wrote your driver, but now you have to test it. The first step you should take is to make sure the functions you implemented all compile. You can do this by clicking the hammer; make sure that you have chosen `driver-dev-tutorial` as the current project and `Nucleo-F767ZI` as the target. You will have to take some time working through any syntax errors you have. It might get a little frustrating, especially if you aren't very familiar with C++. Feel free to reach out in the firmware channel if you get stuck on anything specific.
To test, first make sure the functions you implemented all compile by clicking the check mark at the bottom bar of VS Code. The target has already been set as the Nucleo F303RE. Work through any syntax errors you may have. It might get a little frustrating, especially if you aren't very familiar with C++. Feel free to reach out in the firmware channel if you get stuck on anything specific.

Now that you have a device class that compiles, you can write code to test your functions. This code should be in the `main` function in [main.cpp](main.cpp). Assume the MCP23017 is connected like so:
![circuit.png](images/circuit.png)
**Note**: This circuit diagram is meant to only convey the connections of the MCP23017 GPIO pins. Not the entire surrounding circuit.

As you can see, two LEDs are tied to pins 0 and 4. There is also a switch connected to pin 7. Can you initialize an Mcp23017 object and use the functions you implemented to turn the LEDs on and read the position of the switch?

We will try to schedule times to get you running your code on this hardware setup. :)
We will try to schedule times to get you running your code on this hardware setup. :)
6 changes: 2 additions & 4 deletions include/mcp23017.h
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
#ifndef __mcp__h__
#define __mcp__h__

#include "mbed.h"
#include "stdint.h"
#include <Wire.h>


class Mcp23017 {
private:
int set_dir(int pin, uint8_t dir);
uint8_t get_dir(int pin);
int addr;
I2C* i2cBus;

public:
Mcp23017(int addr, I2C* i2cBus);
Mcp23017(int addr);
int begin(uint8_t directions[8]);
uint8_t get_state(int pin);
int set_state(int pin, uint8_t val);
Expand Down
46 changes: 46 additions & 0 deletions lib/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.

The source code of each library should be placed in an own separate directory
("lib/your_library_name/[here are source files]").

For example, see a structure of the following two libraries `Foo` and `Bar`:

|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c

and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>

int main (void)
{
...
}

```

PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.

More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html
10 changes: 0 additions & 10 deletions main.cpp

This file was deleted.

1 change: 0 additions & 1 deletion mbed-os.lib

This file was deleted.

9 changes: 0 additions & 9 deletions mbed_app.json

This file was deleted.

14 changes: 14 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:nucleo_f303re]
platform = ststm32
board = nucleo_f303re
framework = arduino
27 changes: 27 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
al#include <Arduino.h>
#include "mcp23017.h"

// TODO: declare mcp23017 object
Mcp23017 mcp23017(0x20);


void setup() {
// TODO: initialize i2c and mcp23017 object
Wire.begin();
uint8_t directions[8] = {1, 1, 1, 1, 1, 1, 0};
mcp23017.begin(directions);
//Start serial
Serial.begin(9600);
Serial.println("Starting process...");
}

void loop() {
// TODO: Write tests here

//Toggles the state of the first pin and prints it
Serial.println(mcp23017.get_state(0));
mcp23017.set_state(0, 1);
Serial.println(mcp23017.get_state(0));
mcp23017.set_state(0, 0);
delay(1000); //delay so it doesn't loop so quickly
}
105 changes: 93 additions & 12 deletions src/mcp23017.cpp
Original file line number Diff line number Diff line change
@@ -1,41 +1,122 @@
#include "mcp23017.h"
#include <errno.h>


// TODO (optional): Define macros for useful register below:

#define IO_DIR_ADDR 0x00
#define GPIO_ADDR 0x09

// TODO: Initialize i2cBus member
Mcp23017::Mcp23017(int addr, I2C* i2cBus) {

}
Mcp23017::Mcp23017(int addr) : addr(addr) {} //member initialization

uint8_t Mcp23017::get_dir(int pin) {
return 0;
// first establish a connection with the i2bus
Wire.beginTransmission(Mcp23017::addr);
//Specify the register
Wire.write(IO_DIR_ADDR);
Wire.endTransmission();

// request 1 byte from the register at 0x00 (iodir)
// and store in the variable byteRead
Wire.requestFrom(IO_DIR_ADDR, 1);
uint8_t byteRead;
while (Wire.available()){
byteRead = Wire.read();
}
//moves desired bit to LSB and compares to 1
uint8_t pinDir = (byteRead >> pin) & 1;
return pinDir;
}


// TODO: Read from state register
uint8_t Mcp23017::get_state(int pin) {
return 0;
// Same functionality as above method
Wire.beginTransmission(Mcp23017::addr);
//Specify the register
Wire.write(GPIO_ADDR);
Wire.endTransmission();

Wire.requestFrom(GPIO_ADDR, 1);
uint8_t byteRead;
while (Wire.available()){
byteRead = Wire.read();
}
uint8_t pinDir = (byteRead >> pin) & 1;
return pinDir;
}

// TODO: Write to directions register
int Mcp23017::set_dir(int pin, uint8_t dir) {
return 0;
Wire.beginTransmission(Mcp23017::addr);
Wire.write(IO_DIR_ADDR);
Wire.endTransmission();

Wire.requestFrom(IO_DIR_ADDR, 1);
uint8_t byte;
while (Wire.available()){
byte = Wire.read();
}
if(dir == 1){ //If we want the pin to be on, we use OR logic
byte |= (dir << pin); // shift the mask by the pin amount
}
else if(dir == 0){ //Use AND NOT logic to get the pin to be off
byte &= ~(1 << pin);
}
Wire.beginTransmission(Mcp23017::addr);
Wire.write(IO_DIR_ADDR);
Wire.write(byte);
Wire.endTransmission();

return get_dir(pin); //checks if what we did worked
}

// TODO: Write to state register
int Mcp23017::set_state(int pin, uint8_t val) {
return 0;
Wire.beginTransmission(Mcp23017::addr);
Wire.write(GPIO_ADDR);
Wire.endTransmission();

Wire.requestFrom(GPIO_ADDR, 1);
uint8_t byte;
while (Wire.available()){
byte = Wire.read();
}
if(val == 1){
byte |= (val << pin);
}
else if(val == 0){
byte &= ~(1 << pin);
}

Wire.beginTransmission(Mcp23017::addr);
Wire.write(GPIO_ADDR);
Wire.write(byte);
Wire.endTransmission();

return get_state(pin);
}


// Verifies that the device is accessible over I2C and sets pin directions
int Mcp23017::begin(uint8_t directions[8]) {
int rc;

Wire.beginTransmission(Mcp23017::addr);
Wire.write(IO_DIR_ADDR);
Wire.endTransmission();

Wire.requestFrom(IO_DIR_ADDR, 1);
uint8_t rc;
while (Wire.available()){
rc = Wire.read();
}
if(rc != 0xFF){ //according to manual, all of the bits should be on at first
return 0; //failed
}

// TODO: Add device ID check
//set the direction of each of the pins to the desired value
for(int i = 0; i < 8; i++){
set_dir(i, directions[i]);
}

return 0;
return 1; //succeeded
}
11 changes: 11 additions & 0 deletions test/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

This directory is intended for PlatformIO Test Runner and project tests.

Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.

More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html