Being able to talk to various peripherals and sensors
is one of the most useful things a microcontroller can do
and the RP2040 is no exception.
Over the next couple of episodes,
I'm gonna show you how to talk to a sensor
using a few of these protocols.
Specifically, I'm gonna start with I2C and SPI.
And I'm gonna use them to talk to the same sensor
so you can get an idea of how those two protocols differ.
Let's dive in.
(upbeat music)
SPI stands for serial peripheral interface
and Analog Devices has a great tutorial
that breaks down how the protocol works
if you're interested.
It uses four IO lines to send data between a controller
and a peripheral.
One line is the clock,
which is used to synchronize the reading of data.
Another line is master out, slave in,
which you'll also sometimes see
as controller out, peripheral in.
Data comes out of the main controller on this line
and is read by the peripheral or sensor.
There's also master in, slave out.
Data is sent by the peripheral to the controller
on this line.
Finally, the chip select line allows us to control
which peripheral is being communicated with
at any given time.
You have four clocking modes
that you need to pay attention to.
You'll most often find that SPI devices use mode zero,
where the clock idle's low
and data is sampled on the rising edge.
SPI is a bust that allows you to attach multiple peripherals
as shown in this top image.
You will need a CS line for each peripheral.
The controller should drive a specific CS line low
when it wants to talk to the associated peripheral.
Some devices, like LED controllers,
can be daisy chained together
and only use one chip select line.
We'll only need one peripheral,
so we won't need to worry about this for now.
There is no speed limit set for SPI
as it all depends on how fast you can switch your clock line
and if your wires or traces will cleanly carry the signal.
However, many peripheral devices you buy
will have some kind of limit,
so make sure you read the data sheet.
You'll generally see SPI clocked at one or five megahertz
with some devices going up to 50 megahertz.
Let's connect a SPI sensor to our Raspberry Pi Pico
to see how this works.
I'll be using an ADXL343 breakout board from Adafruit.
This is a three axis accelerometer
that can communicate over SPI or I2C,
which is perfect for seeing how both protocols work.
Here is how we'll connect the breakout board to the Pico.
We'll need to give it 3.3 volts from the Pico
and also connect the ground pins.
Note that the silk screen on the breakout board
mostly labels the pins for I2C usage.
The SPI clock line should go to SEL
and the chip select line should go to CS.
Note that you can really any pin
on the Pico for chip select
as we'll be controlling it manually in code.
Controller out, peripheral in is labeled
as SPI TX on the Pico
and should be connected to SDA on the breakout board.
Controller in, peripheral out
is labeled as SPI RX on the Pico
and that one should go to SDO on the sensor.
Note that these pins are part of the SPI zero peripheral
on the Pico.
Here is how I connected my boards.
If you do not already have MicroPython running on the Pico,
you will want to put the Pico into bootloader mode
by holding the boot select button
while plugging in the USB cable.
This is a trick I learned recently
about how to quickly load MicroPython onto the Pico.
Click on the board button on the bottom right of Thonny
and make sure you have the Raspberry Pi Pico selected.
Click configure interpreter.
Click install or update firmware
and click install in the pop-up window.
This will download and install the latest firmware
for MicroPython.
You should be presented with the REPL prompt in the shell.
Whenever you're working with raw data from a sensor,
I always recommend reading the data sheet.
We'll first look at the SPI communication section
to see how to send data to and from the sensor.
The ADXL343 uses polarity in phase one,
which is mode 3.
That's different from the default
so we need to make a note of that.
The first byte consists of a read or write bit,
followed by a multiple bytes bit,
and then the address of the desired register.
We can write data to that register or read data from it,
depending on how the read or write bit was set.
For a read, data is sent out on the SDO line
after the first byte is received.
We can also read or write multiple bytes
if we set the multiple byte bit.
Each subsequent read or write after the first byte
will be from subsequent registers.
So if we read from register 30
and continue reading with the multiple byte bit set,
we'll read from register 31, then 32 and so on.
The other thing we care about is the register map.
If it's available,
I like to read the device ID register first
to make sure we can communicate with the sensor.
This register should return hex E5.
Then we'll need to set the measure bit to one
in the power control register, in order to tell the sensor
to start taking accelerometer readings.
By default, the sensor measures acceleration
in the plus or minus 2G range.
You can increase that range to plus or minus 16G
by setting a few bits in the data format register.
However, we'll leave it at the 2G range default for now.
X, Y, and Z accelerometer data is held
in the data registers.
This paragraph is important as it tells us
how the data is configured.
The data for one access is 16 bits
spread across two registers,
with the least significant byte being read first.
So we'll need to read the bites, swap the order
and change it to a signed integer using two's compliment.
Let's show this in MicroPython first .
At the top import machine, utime, ustruct and sys,
then we'll want to define some constants to help us read
and compute the accelerometer data.
We only care about the device ID power control
and the start of the data registers.
The device ID should read hex E5
and we'll need to do some math
to convert the raw readings into meters per second squared.
We'll control the chip select pin manually,
so we'll set it as an output.
Next we'll initialize the SPI object.
We'll use a one megahertz baudrate to start.
Feel free to try increasing this baudrate later,
but know that you might start running into issues
with breadboards and long jumper wires, the higher you go.
We set the polarity in phase to one,
as we saw on the data sheet,
we'll leave it at eight bits and most significant bit first,
and we define our clock MOSI and MISO pins.
Next, we write a couple of functions
to help us communicate with the sensor.
The first simply sends out two bytes over the SPI bus.
The first byte contains the register address
and the second is the data we want to write
to that register.
Note that we manually set the CS pin to low
during the whole operation,
and then set it back to high, when we're done.
Next, we create a function that reads from one
or more registers.
It sets the read bit high
and sets the multiple byte bit high,
if the color wants more than one bite.
The registered address is the last six bits
of that first byte.
We then send out the first byte
and read a number of bytes as specified by the color.
All of that data is returned in a byte array.
In the main part of the program,
we set the CS pin high initially.
There's a slight bug in MicroPython,
the clock line idols low at first,
when we set polarity to one.
We perform a read that does nothing
to get it to start idling high.
Hopefully this will be fixed in the future.
Next we read the device ID register
and compare the result with the known good ID
we got from the data sheet.
If we don't get that ID,
we know we can't communicate with the chip,
so we throw an error and exit.
Next, we read the contents of the power control register
and set just the measure bit high, which is bit three.
By oaring a one in bit three
with the contents of the data register.
We can just affect that bit
rather than overwrite everything.
We then write that result
back to the power control register.
I like to print out the contents of that register before
and after the write to make sure the right function works.
I'll wait two seconds to give the user time
to read the output before continuing.
In my forever loop,
I read the six consecutive data registers
and use ustruct to convert these sets of two byte arrays
to 16 bit signed integers.
Then I do some math to convert the raw readings
into meters per second squared.
Finally, I print the results to the serial terminal
and sleep for a bit.
Let's save this program somewhere on the computer
and then run it on the Pico.
If it all goes well, it should print out
the contents of the power control register twice
to show us that the writing worked
and then begin giving us accelerometer data.
If I move the board around, you can see how the X, Y,
and Z measurements change.
Now let's do this in C.
I recommend following my guide on Maker.io to set up VS Code
and the tool chain for the Pico C SDK.
Navigate to wherever you keep your Pico projects.
In a previous video,
I showed you how to create a simple blink program.
And that blink program is a very useful template
for making new C programs for the Pico.
So let's copy that folder
and rename it to something like ADXL343_SPI.
You will want to delete the build folder that's in there
as we will run, CMake from scratch
after making some adjustments.
Open VS Code and open that new folder we just made.
In CMake lists change the project name
from blink to ADXL343_SPI.
We'll also need to include the C, SDK SPI library,
so add hardware_SPI in target link libraries.
If you are using the Pico probe for debugging,
you can leave the STDIO_uart as one,
but if not, you'll want to change it to zero
and change STDIO_ USB to one.
Save this file.
If this does not automatically run CMake,
you will need to run, CMake from the console,
or if you have the C make extension installed like I do,
you can click the kit button at the bottom
and click the Arm GCC compiler from the dropdown list.
Then click the CMake button
and click the debug configuration.
This will cause CMake to run and create a new build folder.
In main.c delete everything.
At the top, include the STDIO, Pico STD lib
and hardware SPI libraries.
We'll also want to define some constants
with the register addresses we care about
along with the device ID and some math Constance.
While it's not really necessary for this demo,
I'll go ahead and declare my register read
and write functions, I'll define them next.
Writing to the sensor is pretty simple.
We construct the two byte message
and then send it out over SPI.
Note that we set the CS pin to low first
and set it to high, after the transmission is done.
Reading is a little more complicated.
We figure out if the multiple byte bit should be set
and then construct our message with the read bit set.
This message is sent out using
the SPI, right blocking command,
after dropping the CS line low.
We then immediately read for a number of bytes,
specified by the color
before setting the CS line back to high.
In main, we set our pins and SPI port,
which is SPI zero for the pins we chose.
We then initialize the serial port.
We set our CS pin as output and set it to a default of high.
Then we configure our SPI port at one megahertz
and set the polarity and phase each to one.
We also tell the RP2040 to connect the data and clock pins
to the SPI functions.
Just like we did in MicroPython,
we need to perform one useless read in order to force
the clock pin to idle high.
Then we read the device ID
and compare it to the known good ID value.
We get the value of the power control register
and set the measure bit to high
before writing it back to the power control register.
I like to print out the value of the register before
and after the write, to make sure it was set properly.
We wait for two seconds
and start taking readings from the accelerometer.
We start at the data X zero register.
We read it and the five registers after it
to get all three axis.
We do a little math to convert the reading of each axis
to a 16 bit signed value,
and then convert the raw readings
into meters per second squared.
Finally, we print the results
and sleep for a 10th of a second
before repeating the process.
Save this file.
I'll build the project with make,
if you have the Pico probe
connected with the debugging tools installed,
as I showed in a previous video,
you can simply start the debugging process to run your code.
If not, you'll want to put your Pico into bootloader mode
and copy the .UF2 file over to the RPI-RP2 drive.
You'll find the .UF2 file inside the build folder
of your project directory.
When it's done uploading the Pico should automatically reset
and start running your program.
Open whatever serial program you like to use
and connect it to the Pico with a baud rate of 115,200,
you should see the X, Y,
and Z accelerometer data flying across the screen.
If it's working properly,
you should be able to hold the breadboard steady
and see the effects of gravity.
If you have one access perpendicular to the ground,
it should read plus or minus 9.8 meters per second squared.
I hope this helps give you a good start
using SPI on the Raspberry PI Pico.
On the next episode,
I'm gonna show you how to do the exact same thing,
but using I2C instead.
Stay tuned and happy hacking.
(upbeat music)