diff --git a/.gitignore b/.gitignore
index 2bb52c9..63625a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..080e70d
--- /dev/null
+++ b/.vscode/extensions.json
@@ -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"
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..356c0c8
--- /dev/null
+++ b/.vscode/settings.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 51aa6c7..d55cef0 100644
--- a/README.md
+++ b/README.md
@@ -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.
@@ -48,11 +48,11 @@ 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.
@@ -60,7 +60,7 @@ I know that was a lot of information, but now you should understand the mechanis
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`**
- 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.
@@ -78,10 +78,10 @@ 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`**
- 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:

@@ -89,4 +89,4 @@ Now that you have a device class that compiles, you can write code to test your
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. :)
\ No newline at end of file
diff --git a/include/mcp23017.h b/include/mcp23017.h
index b0306de..f3bb469 100644
--- a/include/mcp23017.h
+++ b/include/mcp23017.h
@@ -1,8 +1,7 @@
#ifndef __mcp__h__
#define __mcp__h__
-#include "mbed.h"
-#include "stdint.h"
+#include
class Mcp23017 {
@@ -10,10 +9,9 @@ class Mcp23017 {
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);
diff --git a/lib/README b/lib/README
new file mode 100644
index 0000000..2593a33
--- /dev/null
+++ b/lib/README
@@ -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
+#include
+
+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
diff --git a/main.cpp b/main.cpp
deleted file mode 100644
index a9373c7..0000000
--- a/main.cpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#include "mbed.h"
-#include "mcp23017.h"
-
-
-// TODO: Write tests here
-int main(int argc, char **argv)
-{
- printf("Hello world!\n");
- return 0;
-}
diff --git a/mbed-os.lib b/mbed-os.lib
deleted file mode 100644
index 2b07903..0000000
--- a/mbed-os.lib
+++ /dev/null
@@ -1 +0,0 @@
-https://github.com/ARMmbed/mbed-os.git#2eb06e76208588afc6cb7580a8dd64c5429a10ce
\ No newline at end of file
diff --git a/mbed_app.json b/mbed_app.json
deleted file mode 100644
index 589e589..0000000
--- a/mbed_app.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "requires": ["bare-metal"],
- "target_overrides": {
- "*": {
- "target.printf_lib": "std",
- "target.c_lib": "small"
- }
- }
-}
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
new file mode 100644
index 0000000..87af89e
--- /dev/null
+++ b/platformio.ini
@@ -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
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..ca63a1e
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,28 @@
+al#include
+#include "mcp23017.h"
+
+// TODO: declare mcp23017 object
+Mcp23017 mcp23017(0x20);
+
+
+void setup() {
+ // TODO: initialize i2c and mcp23017 object
+ Wire.begin();
+ uint8_t directions[8] = {0, 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);
+ delay(1000); //delay so we can see the pin state change
+ Serial.println(mcp23017.get_state(0));
+ mcp23017.set_state(0, 0);
+ delay(1000); //delay so it doesn't loop so quickly
+}
diff --git a/src/mcp23017.cpp b/src/mcp23017.cpp
index 693ac79..c40e119 100644
--- a/src/mcp23017.cpp
+++ b/src/mcp23017.cpp
@@ -1,41 +1,122 @@
#include "mcp23017.h"
-#include
-
// 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
}
diff --git a/test/README b/test/README
new file mode 100644
index 0000000..9b1e87b
--- /dev/null
+++ b/test/README
@@ -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