Updates along with SDK1.3.0 release (#181)

Bug fixes and new examples

Co-authored-by: Paulo Marques <pm@quant-insight.com>
Co-authored-by: martin <admin@crossleys.biz>
Co-authored-by: matiasilva <matias.silva@raspberrypi.com>
Co-authored-by: Uri Shaked <uri@urishaked.com>
Co-authored-by: Diego Solano <diegosolano@gmail.com>
Co-authored-by: Andrew Scheller <andrew.scheller@raspberrypi.com>
Co-authored-by: Adrian Hesketh <a-h@users.noreply.github.com>
Co-authored-by: Emircan Gündoğdu <58917386+emircangun@users.noreply.github.com>
Co-authored-by: Josef Wegner <80200012+josefwegner@users.noreply.github.com>
Co-authored-by: pmarques-dev <72901351+pmarques-dev@users.noreply.github.com>
Co-authored-by: Paulo Marques <pm@quant-insight.com>
Co-authored-by: mjcross <mjcross@users.noreply.github.com>
Co-authored-by: martin <admin@crossleys.biz>
This commit is contained in:
Graham Sanderson 2021-11-01 14:41:54 -05:00 committed by GitHub
parent 146680d625
commit f800a7e303
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
150 changed files with 4832 additions and 98 deletions

View File

@ -7,6 +7,10 @@ project(pico_examples C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0")
message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()
set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR})
# Initialize the SDK

23
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,23 @@
# Contributing to Raspberry Pi Pico C/C++ Examples
## How to Report a Bug
We use GitHub to host code, track [issues](https://github.com/raspberrypi/pico-examples/issues) and feature requests, and to accept [pull requests](https://github.com/raspberrypi/pico-examples/pulls). If you find think you have found a bug, please report it by [opening a new issue](https://github.com/raspberrypi/pico-examples/issues/new). Please include as much detail as possible, and ideally some code to reproduce the problem.
## How to Contribute Code
In order to contribute new or updated code, you must first create a GitHub account and fork the original repository to your own account. You can make changes, save them in your repository, then [make a pull request](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) against this repository. The pull request will appear [in the repository](https://github.com/raspberrypi/pico-examples/pulls) where it can be assessed by the maintainers, and if appropriate, merged with the official repository.
**NOTE:** Development takes place on the `develop` branch in this repository. Please open your https://github.com/raspberrypi/pico-examples/pulls[pull request] (PR) against the [`develop`](https://github.com/raspberrypi/pico-examples/tree/develop) branch, pull requests against the `master` branch will automatically CI fail checks and will not be accepted. You will be asked to rebase your PR against `develop` and if you do not do so, your PR will be closed.
### Code Style
If you are contributing new or updated code please match the existing code style, particularly:
* Use 4 spaces for indentation rather than tabs.
* Braces are required for everything except single line `if` statements.
* Opening braces should not be placed on a new line.
### Licensing
Code in this repository is lisensed under the [BSD-3 License](LICENSE.TXT). By contributing content to this repository you are agreeing to place your contributions under this licence.

View File

@ -20,6 +20,7 @@ App|Description
[hello_adc](adc/hello_adc)|Display the voltage from an ADC input.
[joystick_display](adc/joystick_display)|Display a Joystick X/Y input based on two ADC inputs.
[adc_console](adc/adc_console)|An interactive shell for playing with the ADC. Includes example of free-running capture mode.
[microphone_adc](adc/microphone_adc)|Read analog values from a microphone and plot the measured sound amplitude.
### Clocks
@ -76,8 +77,16 @@ App|Description
App|Description
---|---
[bus_scan](i2c/bus_scan) | Scan the I2C bus for devices and display results.
[bmp280_i2c](i2c/bmp280_i2c) | Read and convert temperature and pressure data from a BMP280 sensor, attached to an I2C bus.
[lcd_1602_i2c](i2c/lcd_1602_i2c) | Display some text on a generic 16x2 character LCD display, via I2C.
[lis3dh_i2c](i2c/lis3dh_i2c) | Read acceleration and temperature value from a LIS3DH sensor via I2C
[mcp9808_i2c](i2c/mcp9808_i2c) | Read temperature, set limits and raise alerts when limits are surpassed.
[mma8451_i2c](i2c/mma8451_i2c) | Read acceleration from a MMA8451 accelerometer and set range and precision for the data.
[mpl3115a2_i2c](i2c/mpl3115a2_i2c) | Interface with an MPL3115A2 altimeter, exploring interrupts and advanced board features, via I2C.
[mpu6050_i2c](i2c/mpu6050_i2c) | Read acceleration and angular rate values from a MPU6050 accelerometer/gyro, attached to an I2C bus.
[oled_i2c](i2c/oled_i2c) | Convert and display a bitmap on a 128x32 SSD1306-driven OLED display
[pa1010d_i2c](i2c/pa1010d_i2c) | Read GPS location data, parse and display data via I2C.
[pcf8523_i2c](i2c/pcf8523_i2c) | Read time and date values from a real time clock. Set current time and alarms on it.
### Interpolator
@ -109,6 +118,7 @@ App|Description
[differential_manchester](pio/differential_manchester)| Send and receive differential Manchester-encoded serial (BMC).
[hub75](pio/hub75)| Display an image on a 128x64 HUB75 RGB LED matrix.
[i2c](pio/i2c)| Scan an I2C bus.
[ir_nec](pio/ir_nec)| Sending and receiving IR (infra-red) codes using the PIO.
[logic_analyser](pio/logic_analyser)| Use PIO and DMA to capture a logic trace of some GPIOs, whilst a PWM unit is driving them.
[manchester_encoding](pio/manchester_encoding)| Send and receive Manchester-encoded serial.
[pio_blink](pio/pio_blink)| Set up some PIO state machines to blink LEDs at different frequencies, according to delay counts pushed into their FIFOs.
@ -116,6 +126,7 @@ App|Description
[spi](pio/spi)| Use PIO to erase, program and read an external SPI flash chip. A second example runs a loopback test with all four CPHA/CPOL combinations.
[squarewave](pio/squarewave)| Drive a fast square wave onto a GPIO. This example accesses low-level PIO registers directly, instead of using the SDK functions.
[st7789_lcd](pio/st7789_lcd)| Set up PIO for 62.5 Mbps serial output, and use this to display a spinning image on a ST7789 serial LCD.
[quadrature_encoder](pio/quadrature_encoder)| A quadrature encoder using PIO to maintain counts independent of the CPU.
[uart_rx](pio/uart_rx)| Implement the receive component of a UART serial port. Attach it to the spare Arm UART to see it receive characters.
[uart_tx](pio/uart_tx)| Implement the transmit component of a UART serial port, and print hello world.
[ws2812](pio/ws2812)| Examples of driving WS2812 addressable RGB LEDs.
@ -172,13 +183,14 @@ App|Description
App|Description
---|---
[hello_uart](uart/hello_uart) | Print some text from one of the UART serial ports, without going through `stdio`.
[lcd_uart](uart/lcd_uart) | Display text and symbols on a 16x02 RGB LCD display via UART
[uart_advanced](uart/uart_advanced) | Use some other UART features like RX interrupts, hardware control flow, and data formats other than 8n1.
### USB Device
#### TinyUSB Examples
All but one of the USB device examples come directly from the TinyUSB device examples directory [here](https://github.com/hathach/tinyusb/tree/master/examples/device).
Most of the USB device examples come directly from the TinyUSB device examples directory [here](https://github.com/hathach/tinyusb/tree/master/examples/device).
Those that are supported on RP2040 devices are automatically included as part of the pico-examples
build as targets named `tinyusb_dev_<example_name>`, e.g. https://github.com/hathach/tinyusb/tree/master/examples/device/hid_composite
is built as `tinyusb_dev_hid_composite`.
@ -198,11 +210,24 @@ At the time of writing, these examples are available:
- tinyusb_dev_hid_multiple_interface
- tinyusb_dev_midi_test
- tinyusb_dev_msc_dual_lun
- tinyusb_dev_net_lwip_webserver
- tinyusb_dev_uac2_headset
- tinyusb_dev_usbtmc
- tinyusb_dev_video_capture
- tinyusb_dev_webusb_serial
#### Low Level examples
Whilst these examples ably demonstrate how to use TinyUSB in device mode, their `CMakeLists.txt` is set up in a way
tailored to how TinyUSB builds their examples within their source tree.
For a better example of how to configure `CMakeLists.txt` for using TinyUSB in device mode with the Raspberry Pi SDK
see below:
#### SDK build example
App|Description
---|---
[dev_hid_composite](usb/device/dev_hid_composite) | A copy of the TinyUSB device example with the same name, but with a CMakeLists.txt which demonstrates how to add a dependency on the TinyUSB device libraries with the Raspberry Pi Pico SDK
#### Low Level example
App|Description
---|---
[dev_lowlevel](usb/device/dev_lowlevel) | A USB Bulk loopback implemented with direct access to the USB hardware (no TinyUSB)

View File

@ -3,4 +3,5 @@ if (NOT PICO_NO_HARDWARE)
add_subdirectory(dma_capture)
add_subdirectory(hello_adc)
add_subdirectory(joystick_display)
add_subdirectory(microphone_adc)
endif ()

View File

@ -0,0 +1,12 @@
add_executable(microphone_adc
microphone_adc.c
)
# pull in common dependencies and adc hardware support
target_link_libraries(microphone_adc pico_stdlib hardware_adc)
# create map/bin/hex file etc.
pico_add_extra_outputs(microphone_adc)
# add url via pico_set_program_url
example_auto_set_url(microphone_adc)

View File

@ -0,0 +1,48 @@
= Attaching a microphone using the ADC
This example code shows how to interface the Raspberry Pi Pico with a standard analog microphone via the onboard analog to digital converter (ADC). In this example, we use an ICS-40180 breakout board by SparkFun but any analog microphone should be compatible with this tutorial. SparkFun have https://learn.sparkfun.com/tutorials/mems-microphone-hookup-guide[written a guide] for this board that goes into more detail about the board and how it works.
[TIP]
======
An analog to digital converter (ADC) is responsible for reading continually varying input signals that may range from 0 to a specified reference voltage (in the Pico's case this reference voltage is set by the supply voltage and can be measured on pin 35, ADC_VREF) and converting them into binary, i.e. a number that can be digitally stored.
======
The Pico has a 12-bit ADC (ENOB of 8.7-bit, see https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf[RP2040 datasheet section 4.9.3 for more details]), meaning that a read operation will return a number ranging from 0 to 4095 (2^12 - 1) for a total of 4096 possible values. Therefore, the resolution of the ADC is 3.3/4096, so roughly steps of 0.8 millivolts. The SparkFun breakout uses an OPA344 operational amplifier to boost the signal coming from the microphone to voltage levels that can be easily read by the ADC. An important side effect is that a bias of 0.5*Vcc is added to the signal, even when the microphone is not picking up any sound.
The ADC provides us with a raw voltage value but when dealing with sound, we're more interested in the amplitude of the audio signal. This is defined as one half the peak-to-peak amplitude. Included with this example is a very simple Python script that will plot the voltage values it receives via the serial port. By tweaking the sampling rates, and various other parameters, the data from the microphone can be analysed in various ways, such as in a Fast Fourier Transform to see what frequencies make up the signal.
[[microphone_adc_plotter_image]]
[pdfwidth=75%]
.Example output from included Python script
image::microphone_adc_plotter.png[]
== Wiring information
Wiring up the device requires 3 jumpers, to connect VCC (3.3v), GND, and AOUT. The example here uses ADC0, which is GP26. Power is supplied from the 3.3V pin.
WARNING: Most boards will take a range of VCC voltages from the Pico's default 3.3V to the 5 volts commonly seen on other microcontrollers. Ensure your board doesn't output an analogue signal greater than 3.3V as this may result in permanent damage to the Pico's ADC.
[[ics-40180-adc_wiring]]
[pdfwidth=75%]
.Wiring Diagram for ICS-40180 microphone breakout board.
image::microphone_adc_bb.png[]
== List of Files
CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree.
microphone_adc.c:: The example code.
== Bill of Materials
.A list of materials required for the example
[[ics-40180-adc-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| ICS-40180 microphone breakout board or similar | 1 | https://www.sparkfun.com/products/18011[From SparkFun]
| M/M Jumper wires | 3 | generic part
|===

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/adc.h"
#include "hardware/uart.h"
#include "pico/binary_info.h"
/* Example code to extract analog values from a microphone using the ADC
with accompanying Python file to plot these values
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO 26/ADC0 (pin 31)-> AOUT or AUD on microphone board
3.3v (pin 36) -> VCC on microphone board
GND (pin 38) -> GND on microphone board
*/
#define ADC_NUM 0
#define ADC_PIN (26 + ADC_NUM)
#define ADC_VREF 3.3
#define ADC_RANGE (1 << 12)
#define ADC_CONVERT (ADC_VREF / (ADC_RANGE - 1))
int main() {
stdio_init_all();
printf("Beep boop, listening...\n");
bi_decl(bi_program_description("Analog microphone example for Raspberry Pi Pico")); // for picotool
bi_decl(bi_1pin_with_name(ADC_PIN, "ADC input pin"));
adc_init();
adc_gpio_init( ADC_PIN);
adc_select_input( ADC_NUM);
uint adc_raw;
while (1) {
adc_raw = adc_read(); // raw voltage from ADC
printf("%.2f\n", adc_raw * ADC_CONVERT);
sleep_ms(10);
}
return 0;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

77
adc/microphone_adc/plotter.py Executable file
View File

@ -0,0 +1,77 @@
#!/usr/bin/env python3
# Grabs raw data from the Pico's UART and plots it as received
# Install dependencies:
# python3 -m pip install pyserial matplotlib
# Usage: python3 plotter <port>
# eg. python3 plotter /dev/ttyACM0
# see matplotlib animation API for more: https://matplotlib.org/stable/api/animation_api.html
import serial
import sys
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.lines import Line2D
# disable toolbar
plt.rcParams['toolbar'] = 'None'
class Plotter:
def __init__(self, ax):
self.ax = ax
self.maxt = 250
self.tdata = [0]
self.ydata = [3.3/2]
self.line = Line2D(self.tdata, self.ydata)
self.ax.add_line(self.line)
self.ax.set_ylim(0, 3.3)
self.ax.set_xlim(0, self.maxt)
def update(self, y):
lastt = self.tdata[-1]
if lastt - self.tdata[0] >= self.maxt: # drop old frames
self.tdata = self.tdata[1:]
self.ydata = self.ydata[1:]
self.ax.set_xlim(self.tdata[0], self.tdata[0] + self.maxt)
t = lastt + 1
self.tdata.append(t)
self.ydata.append(y)
self.line.set_data(self.tdata, self.ydata)
return self.line,
def serial_getter():
# grab fresh ADC values
# note sometimes UART drops chars so we try a max of 5 times
# to get proper data
while True:
for i in range(5):
line = ser.readline()
try:
line = float(line)
except ValueError:
continue
break
yield line
if len(sys.argv) < 2:
raise Exception("Ruh roh..no port specified!")
ser = serial.Serial(sys.argv[1], 115200, timeout=1)
fig, ax = plt.subplots()
plotter = Plotter(ax)
ani = animation.FuncAnimation(fig, plotter.update, serial_getter, interval=1,
blit=True, cache_frame_data=False)
ax.set_xlabel("Samples")
ax.set_ylabel("Voltage (V)")
fig.canvas.manager.set_window_title('Microphone ADC example')
fig.tight_layout()
plt.show()

View File

@ -2,7 +2,7 @@ add_executable(blink
blink.c
)
# Pull in our pico_stdlib which pulls in commonly used features
# pull in common dependencies
target_link_libraries(blink pico_stdlib)
# create map/bin/hex file etc.

View File

@ -2,11 +2,11 @@ add_executable(clocks_detached_clk_peri
detached_clk_peri.c
)
# Pull in our pico_stdlib which pulls in commonly used features
# pull in common dependencies
target_link_libraries(clocks_detached_clk_peri pico_stdlib)
# create map/bin/hex file etc.
pico_add_extra_outputs(clocks_detached_clk_peri)
# add url via pico_set_program_url
example_auto_set_url(clocks_detached_clk_peri)
example_auto_set_url(clocks_detached_clk_peri)

View File

@ -2,7 +2,7 @@ add_executable(hello_48MHz
hello_48MHz.c
)
# Pull in our pico_stdlib which pulls in commonly used features
# pull in common dependencies and additional clocks hardware support
target_link_libraries(hello_48MHz pico_stdlib hardware_clocks)
# create map/bin/hex file etc.

View File

@ -2,11 +2,11 @@ add_executable(hello_gpout
hello_gpout.c
)
# Pull in our pico_stdlib which pulls in commonly used features
# pull in common dependencies
target_link_libraries(hello_gpout pico_stdlib)
# create map/bin/hex file etc.
pico_add_extra_outputs(hello_gpout)
# add url via pico_set_program_url
example_auto_set_url(hello_gpout)
example_auto_set_url(hello_gpout)

View File

@ -2,11 +2,11 @@ add_executable(hello_resus
hello_resus.c
)
# Pull in our pico_stdlib which pulls in commonly used features
# pull in common dependencies
target_link_libraries(hello_resus pico_stdlib)
# create map/bin/hex file etc.
pico_add_extra_outputs(hello_resus)
# add url via pico_set_program_url
example_auto_set_url(hello_resus)
example_auto_set_url(hello_resus)

View File

@ -2,11 +2,11 @@ add_executable(hello_divider
hello_divider.c
)
# Pull in our pico_stdlib which pulls in commonly used features
# pull in common dependencies
target_link_libraries(hello_divider pico_stdlib)
# create map/bin/hex file etc.
pico_add_extra_outputs(hello_divider)
# add url via pico_set_program_url
example_auto_set_url(hello_divider)
example_auto_set_url(hello_divider)

View File

@ -84,7 +84,7 @@ int main() {
c = dma_channel_get_default_config(data_chan);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_dreq(&c, DREQ_UART0_TX + 2 * uart_get_index(uart_default));
channel_config_set_dreq(&c, uart_get_dreq(uart_default, true));
// Trigger ctrl_chan when data_chan completes
channel_config_set_chain_to(&c, ctrl_chan);
// Raise the IRQ flag when 0 is written to a trigger register (end of chain):

View File

@ -51,7 +51,7 @@ dht.c:: The example code.
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | http://raspberrypi.org/
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| 10 kΩ resistor | 1 | generic part
| M/M Jumper wires | 4 | generic part
| DHT-22 sensor | 1 | generic part

View File

@ -2,11 +2,11 @@ add_executable(hello_7segment
hello_7segment.c
)
# Pull in our pico_stdlib which pulls in commonly used features
# pull in common dependencies
target_link_libraries(hello_7segment pico_stdlib)
# create map/bin/hex file etc.
pico_add_extra_outputs(hello_7segment)
# add url via pico_set_program_url
example_auto_set_url(hello_7segment)
example_auto_set_url(hello_7segment)

View File

@ -40,11 +40,9 @@ hello_7segment.c:: The example code.
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | http://raspberrypi.org/
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| 7 segment LED module | 1 | generic part
| 68 ohm resistor | 7 | generic part
| DIL push to make switch | 1 | generic switch
| M/M Jumper wires | 10 | generic part
|===

View File

@ -2,7 +2,7 @@ add_executable(hello_gpio_irq
hello_gpio_irq.c
)
# Pull in our pico_stdlib which pulls in commonly used features
# pull in common dependencies
target_link_libraries(hello_gpio_irq pico_stdlib)
# create map/bin/hex file etc.

View File

@ -2,7 +2,7 @@ add_executable(hello_serial
hello_serial.c
)
# Pull in our pico_stdlib which aggregates commonly used features
# pull in common dependencies
target_link_libraries(hello_serial pico_stdlib)
# create map/bin/hex/uf2 file etc.

View File

@ -3,7 +3,7 @@ if (TARGET tinyusb_device)
hello_usb.c
)
# Pull in our pico_stdlib which aggregates commonly used features
# pull in common dependencies
target_link_libraries(hello_usb pico_stdlib)
# enable usb output, disable uart output

View File

@ -1,5 +1,13 @@
if (NOT PICO_NO_HARDWARE)
add_subdirectory(bmp280_i2c)
add_subdirectory(bus_scan)
add_subdirectory(lcd_1602_i2c)
add_subdirectory(lis3dh_i2c)
add_subdirectory(mcp9808_i2c)
add_subdirectory(mma8451_i2c)
add_subdirectory(mpl3115a2_i2c)
add_subdirectory(mpu6050_i2c)
add_subdirectory(oled_i2c)
add_subdirectory(pa1010d_i2c)
add_subdirectory(pcf8523_i2c)
endif ()

View File

@ -0,0 +1,12 @@
add_executable(bmp280_i2c
bmp280_i2c.c
)
# pull in common dependencies and additional i2c hardware support
target_link_libraries(bmp280_i2c pico_stdlib hardware_i2c)
# create map/bin/hex file etc.
pico_add_extra_outputs(bmp280_i2c)
# add url via pico_set_program_url
example_auto_set_url(bmp280_i2c)

View File

@ -0,0 +1,41 @@
= Attaching a BMP280 temp/pressure sensor via I2C
This example code shows how to interface the Raspberry Pi Pico with the popular BMP280 temperature and air pressure sensor manufactured by Bosch. A similar variant, the BME280, exists that can also measure humidity. There is another example that uses the BME280 device but talks to it via SPI as opposed to I2C.
The code reads data from the sensor's registers every 500 milliseconds and prints it via the onboard UART. This example operates the BMP280 in _normal_ mode, meaning that the device continuously cycles between a measurement period and a standby period at a regular interval we can set. This has the advantage that subsequent reads do not require configuration register writes and is the recommended mode of operation to filter out short-term disturbances.
[TIP]
======
The BMP280 is highly configurable with 3 modes of operation, various oversampling levels, and 5 filter settings. Find the datasheet online (https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf) to explore all of its capabilities beyond the simple example given here.
======
== Wiring information
Wiring up the device requires 4 jumpers, to connect VCC (3.3v), GND, SDA and SCL. The example here uses the default I2C port 0, which is assigned to GPIO 4 (SDA) and 5 (SCL) in software. Power is supplied from the 3.3V pin from the Pico.
WARNING: The BMP280 has a maximum supply voltage rating of 3.6V. Most breakout boards have voltage regulators that will allow a range of input voltages of 2-6V, but make sure to check beforehand.
[[bmp280_i2c_wiring]]
[pdfwidth=75%]
.Wiring Diagram for BMP280 sensor via I2C.
image::bmp280_i2c_bb.png[]
== List of Files
CMakeLists.txt:: CMake file to incorporate the example into the examples build tree.
bmp280_i2c.c:: The example code.
== Bill of Materials
.A list of materials required for the example
[[bmp280_i2c-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| BMP280-based breakout board | 1 | https://shop.pimoroni.com/products/bmp280-breakout-temperature-pressure-altitude-sensor[from Pimoroni]
| M/M Jumper wires | 4 | generic part
|===

252
i2c/bmp280_i2c/bmp280_i2c.c Normal file
View File

@ -0,0 +1,252 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
**/
#include <stdio.h>
#include "hardware/i2c.h"
#include "pico/binary_info.h"
#include "pico/stdlib.h"
/* Example code to talk to a BMP280 temperature and pressure sensor
NOTE: Ensure the device is capable of being driven at 3.3v NOT 5v. The Pico
GPIO (and therefore I2C) cannot be used at 5v.
You will need to use a level shifter on the I2C lines if you want to run the
board at 5v.
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO PICO_DEFAULT_I2C_SDA_PIN (on Pico this is GP4 (pin 6)) -> SDA on BMP280
board
GPIO PICO_DEFAULT_I2C_SCK_PIN (on Pico this is GP5 (pin 7)) -> SCL on
BMP280 board
3.3v (pin 36) -> VCC on BMP280 board
GND (pin 38) -> GND on BMP280 board
*/
// device has default bus address of 0x76
#define ADDR _u(0x76)
// hardware registers
#define REG_CONFIG _u(0xF5)
#define REG_CTRL_MEAS _u(0xF4)
#define REG_RESET _u(0xE0)
#define REG_TEMP_XLSB _u(0xFC)
#define REG_TEMP_LSB _u(0xFB)
#define REG_TEMP_MSB _u(0xFA)
#define REG_PRESSURE_XLSB _u(0xF9)
#define REG_PRESSURE_LSB _u(0xF8)
#define REG_PRESSURE_MSB _u(0xF7)
// calibration registers
#define REG_DIG_T1_LSB _u(0x88)
#define REG_DIG_T1_MSB _u(0x89)
#define REG_DIG_T2_LSB _u(0x8A)
#define REG_DIG_T2_MSB _u(0x8B)
#define REG_DIG_T3_LSB _u(0x8C)
#define REG_DIG_T3_MSB _u(0x8D)
#define REG_DIG_P1_LSB _u(0x8E)
#define REG_DIG_P1_MSB _u(0x8F)
#define REG_DIG_P2_LSB _u(0x90)
#define REG_DIG_P2_MSB _u(0x91)
#define REG_DIG_P3_LSB _u(0x92)
#define REG_DIG_P3_MSB _u(0x93)
#define REG_DIG_P4_LSB _u(0x94)
#define REG_DIG_P4_MSB _u(0x95)
#define REG_DIG_P5_LSB _u(0x96)
#define REG_DIG_P5_MSB _u(0x97)
#define REG_DIG_P6_LSB _u(0x98)
#define REG_DIG_P6_MSB _u(0x99)
#define REG_DIG_P7_LSB _u(0x9A)
#define REG_DIG_P7_MSB _u(0x9B)
#define REG_DIG_P8_LSB _u(0x9C)
#define REG_DIG_P8_MSB _u(0x9D)
#define REG_DIG_P9_LSB _u(0x9E)
#define REG_DIG_P9_MSB _u(0x9F)
// number of calibration registers to be read
#define NUM_CALIB_PARAMS 24
struct bmp280_calib_param {
// temperature params
uint16_t dig_t1;
int16_t dig_t2;
int16_t dig_t3;
// pressure params
uint16_t dig_p1;
int16_t dig_p2;
int16_t dig_p3;
int16_t dig_p4;
int16_t dig_p5;
int16_t dig_p6;
int16_t dig_p7;
int16_t dig_p8;
int16_t dig_p9;
};
#ifdef i2c_default
void bmp280_init() {
// use the "handheld device dynamic" optimal setting (see datasheet)
uint8_t buf[2];
// 500ms sampling time, x16 filter
const uint8_t reg_config_val = ((0x04 << 5) | (0x05 << 2)) & 0xFC;
// send register number followed by its corresponding value
buf[0] = REG_CONFIG;
buf[1] = reg_config_val;
i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
// osrs_t x1, osrs_p x4, normal mode operation
const uint8_t reg_ctrl_meas_val = (0x01 << 5) | (0x03 << 2) | (0x03);
buf[0] = REG_CTRL_MEAS;
buf[1] = reg_ctrl_meas_val;
i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
}
void bmp280_read_raw(int32_t* temp, int32_t* pressure) {
// BMP280 data registers are auto-incrementing and we have 3 temperature and
// pressure registers each, so we start at 0xF7 and read 6 bytes to 0xFC
// note: normal mode does not require further ctrl_meas and config register writes
uint8_t buf[6];
i2c_write_blocking(i2c_default, ADDR, (uint8_t*)REG_PRESSURE_MSB, 1, true); // true to keep master control of bus
i2c_read_blocking(i2c_default, ADDR, buf, 6, false); // false - finished with bus
// store the 20 bit read in a 32 bit signed integer for conversion
*pressure = (buf[0] << 12) | (buf[1] << 4) | (buf[2] >> 4);
*temp = (buf[3] << 12) | (buf[4] << 4) | (buf[5] >> 4);
}
void bmp280_reset() {
// reset the device with the power-on-reset procedure
uint8_t buf[2] = { REG_RESET, 0xB6 };
i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
}
// intermediate function that calculates the fine resolution temperature
// used for both pressure and temperature conversions
int32_t bmp280_convert(int32_t temp, struct bmp280_calib_param* params) {
// use the 32-bit fixed point compensation implementation given in the
// datasheet
int32_t var1, var2;
var1 = ((((temp >> 3) - ((int32_t)params->dig_t1 << 1))) * ((int32_t)params->dig_t2)) >> 11;
var2 = (((((temp >> 4) - ((int32_t)params->dig_t1)) * ((temp >> 4) - ((int32_t)params->dig_t1))) >> 12) * ((int32_t)params->dig_t3)) >> 14;
return var1 + var2;
}
int32_t bmp280_convert_temp(int32_t temp, struct bmp280_calib_param* params) {
// uses the BMP280 calibration parameters to compensate the temperature value read from its registers
int32_t t_fine = bmp280_convert(temp, params);
return (t_fine * 5 + 128) >> 8;
}
int32_t bmp280_convert_pressure(int32_t pressure, int32_t temp, struct bmp280_calib_param* params) {
// uses the BMP280 calibration parameters to compensate the pressure value read from its registers
int32_t t_fine = bmp280_convert(temp, params);
int32_t var1, var2;
uint32_t converted = 0.0;
var1 = (((int32_t)t_fine) >> 1) - (int32_t)64000;
var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * ((int32_t)params->dig_p6);
var2 += ((var1 * ((int32_t)params->dig_p5)) << 1);
var2 = (var2 >> 2) + (((int32_t)params->dig_p4) << 16);
var1 = (((params->dig_p3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + ((((int32_t)params->dig_p2) * var1) >> 1)) >> 18;
var1 = ((((32768 + var1)) * ((int32_t)params->dig_p1)) >> 15);
if (var1 == 0) {
return 0; // avoid exception caused by division by zero
}
converted = (((uint32_t)(((int32_t)1048576) - pressure) - (var2 >> 12))) * 3125;
if (converted < 0x80000000) {
converted = (converted << 1) / ((uint32_t)var1);
} else {
converted = (converted / (uint32_t)var1) * 2;
}
var1 = (((int32_t)params->dig_p9) * ((int32_t)(((converted >> 3) * (converted >> 3)) >> 13))) >> 12;
var2 = (((int32_t)(converted >> 2)) * ((int32_t)params->dig_p8)) >> 13;
converted = (uint32_t)((int32_t)converted + ((var1 + var2 + params->dig_p7) >> 4));
return converted;
}
void bmp280_get_calib_params(struct bmp280_calib_param* params) {
// raw temp and pressure values need to be calibrated according to
// parameters generated during the manufacturing of the sensor
// there are 3 temperature params, and 9 pressure params, each with a LSB
// and MSB register, so we read from 24 registers
uint8_t buf[NUM_CALIB_PARAMS] = { 0 };
i2c_write_blocking(i2c_default, ADDR, (uint8_t*)REG_DIG_T1_LSB, 1, true); // true to keep master control of bus
// read in one go as register addresses auto-increment
i2c_read_blocking(i2c_default, ADDR, buf, NUM_CALIB_PARAMS, false); // false, we're done reading
// store these in a struct for later use
params->dig_t1 = (uint16_t)(buf[1] << 8) | buf[0];
params->dig_t2 = (int16_t)(buf[3] << 8) | buf[2];
params->dig_t3 = (int16_t)(buf[5] << 8) | buf[4];
params->dig_p1 = (uint16_t)(buf[7] << 8) | buf[6];
params->dig_p2 = (int16_t)(buf[9] << 8) | buf[8];
params->dig_p3 = (int16_t)(buf[11] << 8) | buf[10];
params->dig_p4 = (int16_t)(buf[13] << 8) | buf[12];
params->dig_p5 = (int16_t)(buf[15] << 8) | buf[14];
params->dig_p6 = (int16_t)(buf[17] << 8) | buf[16];
params->dig_p7 = (int16_t)(buf[19] << 8) | buf[18];
params->dig_p8 = (int16_t)(buf[21] << 8) | buf[20];
params->dig_p9 = (int16_t)(buf[23] << 8) | buf[22];
}
#endif
int main() {
stdio_init_all();
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
#warning i2c / bmp280_i2c example requires a board with I2C pins
puts("Default I2C pins were not defined");
#else
// useful information for picotool
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
bi_decl(bi_program_description("BMP280 I2C example for the Raspberry Pi Pico"));
printf("Hello, BMP280! Reading temperaure and pressure values from sensor...\n");
// I2C is "open drain", pull ups to keep signal high when no data is being sent
i2c_init(i2c_default, 100 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
// configure BMP280
bmp280_init();
// retrieve fixed compensation params
struct bmp280_calib_param params;
bmp280_get_calib_params(&params);
int32_t raw_temperature;
int32_t raw_pressure;
sleep_ms(250); // sleep so that data polling and register update don't collide
while (1) {
bmp280_read_raw(&raw_temperature, &raw_pressure);
int32_t temperature = bmp280_convert_temp(raw_temperature, &params);
int32_t pressure = bmp280_convert_pressure(raw_pressure, raw_temperature, &params);
printf("Pressure = %.3f kPa\n", pressure / 1000.f);
printf("Temp. = %.2f C\n", temperature / 100.f);
// poll every 500ms
sleep_ms(500);
}
#endif
return 0;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

@ -2,7 +2,7 @@ add_executable(i2c_bus_scan
bus_scan.c
)
# Pull in our (to be renamed) simple get you started dependencies
# pull in common dependencies and additional i2c hardware support
target_link_libraries(i2c_bus_scan pico_stdlib hardware_i2c)
# create map/bin/hex file etc.

View File

@ -38,7 +38,7 @@ int main() {
#warning i2c/bus_scan example requires a board with I2C pins
puts("Default I2C pins were not defined");
#else
// This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico)
// This example will use I2C0 on the default SDA and SCL pins (GP4, GP5 on a Pico)
i2c_init(i2c_default, 100 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);

View File

@ -2,11 +2,11 @@ add_executable(lcd_1602_i2c
lcd_1602_i2c.c
)
# Pull in our (to be renamed) simple get you started dependencies
# pull in common dependencies and additional i2c hardware support
target_link_libraries(lcd_1602_i2c pico_stdlib hardware_i2c)
# create map/bin/hex file etc.
pico_add_extra_outputs(lcd_1602_i2c)
# add url via pico_set_program_url
example_auto_set_url(lcd_1602_i2c)
example_auto_set_url(lcd_1602_i2c)

View File

@ -32,10 +32,8 @@ lcd_1602_i2c.c:: The example code.
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | http://raspberrypi.org/
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| 1602A based LCD panel 3.3v | 1 | generic part
| 1602A to I2C bridge device 3.3v | 1 | generic part
| M/M Jumper wires | 4 | generic part
|===

View File

@ -0,0 +1,12 @@
add_executable(lis3dh_i2c
lis3dh_i2c.c
)
# pull in common dependencies and additional i2c hardware support
target_link_libraries(lis3dh_i2c pico_stdlib hardware_i2c)
# create map/bin/hex file etc.
pico_add_extra_outputs(lis3dh_i2c)
# add url via pico_set_program_url
example_auto_set_url(lis3dh_i2c)

View File

@ -0,0 +1,39 @@
= Attaching a LIS3DH Nano Accelerometer via i2c.
This example shows you how to interface the Raspberry Pi Pico to the LIS3DH accelerometer and temperature sensor.
======
The code reads and displays the acceleration values of the board in the 3 axes and the ambient temperature value. The datasheet for the sensor can be found at https://www.st.com/resource/en/datasheet/cd00274221.pdf. The device is being operated on 'normal mode' and at a frequency of 1.344 kHz (this can be changed by editing the ODR bits of CTRL_REG4). The range of the data is controlled by the FS bit in CTRL_REG4 and is equal to ±2g in this example. The sensitivity depends on the operating mode and data range; exact values can be found on page 10 of the datasheet. In this case, the sensitivity value is 4mg (where g is the value of gravitational acceleration on the surface of Earth). In order to use the auxiliary ADC to read temperature, the we must set the BDU bit to 1 in CTRL_REG4 and the ADC_EN bit to 1 in TEMP_CFG_REG. Temperature is communicated through ADC 3.
======
[NOTE]
======
The sensor doesn't have features to eliminate any offsets in the data and they would be needed to be taken into account in the code.
======
== Wiring information
Wiring up the device requires 4 jumpers, to connect VIN, GND, SDA and SCL. The example here uses I2C port 0, which is assigned to GPIO 4 (SDA) and 5 (SCL) in software. Power is supplied from the 3V pin.
[[lis3dh_i2c_wiring]]
[pdfwidth=75%]
.Wiring Diagram for LIS3DH.
image::lis3dh_i2c.png[]
== List of Files
CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree.
lis3dh_i2c.c:: The example code.
== Bill of Materials
.A list of materials required for the example
[[lis3dh-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| LIS3DH board| 1 | https://www.adafruit.com/product/2809
| M/M Jumper wires | 4 | generic part
|===

129
i2c/lis3dh_i2c/lis3dh_i2c.c Normal file
View File

@ -0,0 +1,129 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
/* Example code to talk to a LIS3DH Mini GPS module.
This example reads data from all 3 axes of the accelerometer and uses an auxillary ADC to output temperature values.
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO PICO_DEFAULT_I2C_SDA_PIN (On Pico this is 4 (physical pin 6)) -> SDA on LIS3DH board
GPIO PICO_DEFAULT_I2C_SCK_PIN (On Pico this is 5 (physical pin 7)) -> SCL on LIS3DH board
3.3v (physical pin 36) -> VIN on LIS3DH board
GND (physical pin 38) -> GND on LIS3DH board
*/
// By default this device is on bus address 0x18
const int ADDRESS = 0x18;
const uint8_t CTRL_REG_1 = 0x20;
const uint8_t CTRL_REG_4 = 0x23;
const uint8_t TEMP_CFG_REG = 0xC0;
#ifdef i2c_default
void lis3dh_init() {
uint8_t buf[2];
// Turn normal mode and 1.344kHz data rate on
buf[0] = CTRL_REG_1;
buf[1] = 0x97;
i2c_write_blocking(i2c_default, ADDRESS, buf, 2, false);
// Turn block data update on (for temperature sensing)
buf[0] = CTRL_REG_4;
buf[1] = 0x80;
i2c_write_blocking(i2c_default, ADDRESS, buf, 2, false);
// Turn auxillary ADC on
buf[0] = TEMP_CFG_REG;
buf[1] = 0xC0;
i2c_write_blocking(i2c_default, ADDRESS, buf, 2, false);
}
void lis3dh_calc_value(uint16_t raw_value, float *final_value, bool isAccel) {
// Convert with respect to the value being temperature or acceleration reading
float scaling;
float senstivity = 0.004f; // g per unit
if (isAccel == true) {
scaling = 64 / senstivity;
} else {
scaling = 64;
}
// raw_value is signed
*final_value = (float) ((int16_t) raw_value) / scaling;
}
void lis3dh_read_data(uint8_t reg, float *final_value, bool IsAccel) {
// Read two bytes of data and store in a 16 bit data structure
uint8_t lsb;
uint8_t msb;
uint16_t raw_accel;
i2c_write_blocking(i2c_default, ADDRESS, &reg, 1, true);
i2c_read_blocking(i2c_default, ADDRESS, &lsb, 1, false);
reg |= 0x01;
i2c_write_blocking(i2c_default, ADDRESS, &reg, 1, true);
i2c_read_blocking(i2c_default, ADDRESS, &msb, 1, false);
raw_accel = (msb << 8) | lsb;
lis3dh_calc_value(raw_accel, final_value, IsAccel);
}
#endif
int main() {
stdio_init_all();
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
#warning i2c/lis3dh_i2c example requires a board with I2C pins
puts("Default I2C pins were not defined");
#else
printf("Hello, LIS3DH! Reading raw data from registers...\n");
// This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico)
i2c_init(i2c_default, 400 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
// Make the I2C pins available to picotool
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
float x_accel, y_accel, z_accel, temp;
lis3dh_init();
while (1) {
lis3dh_read_data(0x28, &x_accel, true);
lis3dh_read_data(0x2A, &y_accel, true);
lis3dh_read_data(0x2C, &z_accel, true);
lis3dh_read_data(0x0C, &temp, false);
// Display data
printf("TEMPERATURE: %.3f%cC\n", temp, 176);
// Acceleration is read as a multiple of g (gravitational acceleration on the Earth's surface)
printf("ACCELERATION VALUES: \n");
printf("X acceleration: %.3fg\n", x_accel);
printf("Y acceleration: %.3fg\n", y_accel);
printf("Z acceleration: %.3fg\n", z_accel);
sleep_ms(500);
// Clear terminal
printf("\e[1;1H\e[2J");
}
#endif
return 0;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View File

@ -0,0 +1,12 @@
add_executable(mcp9808_i2c
mcp9808_i2c.c
)
# pull in common dependencies and additional i2c hardware support
target_link_libraries(mcp9808_i2c pico_stdlib hardware_i2c)
# create map/bin/hex file etc.
pico_add_extra_outputs(mcp9808_i2c)
# add url via pico_set_program_url
example_auto_set_url(mcp9808_i2c)

View File

@ -0,0 +1,37 @@
= Attaching a MCP9808 digital temperature sensor via I2C
This example code shows how to interface the Raspberry Pi Pico to the MCP9808 digital temperature sensor board.
======
This example reads the ambient temperature value each second from the sensor and sets upper, lower and critical limits for the temperature and checks if alerts need to be raised. The CONFIG register can also be used to check for an alert if the critical temperature is surpassed.
======
== Wiring information
Wiring up the device requires 4 jumpers, to connect VDD, GND, SDA and SCL. The example here uses I2C port 0, which is assigned to GPIO 4 (SDA) and 5 (SCL) in software. Power is supplied from the VSYS pin.
[[mcp9808_i2c_wiring]]
[pdfwidth=75%]
.Wiring Diagram for MCP9808.
image::mcp9808_i2c.png[]
== List of Files
CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree.
mcp9808_i2c.c:: The example code.
== Bill of Materials
.A list of materials required for the example
[[mcp9808-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| MCP9808 board| 1 | https://www.adafruit.com/product/1782
| M/M Jumper wires | 4 | generic part
|===

View File

@ -0,0 +1,147 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
/* Example code to talk to a MCP9808 ±0.5°C Digital temperature Sensor
This reads and writes to registers on the board.
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO PICO_DEFAULT_I2C_SDA_PIN (On Pico this is GP4 (physical pin 6)) -> SDA on MCP9808 board
GPIO PICO_DEFAULT_I2C_SCK_PIN (On Pico this is GP5 (physcial pin 7)) -> SCL on MCP9808 board
Vsys (physical pin 39) -> VDD on MCP9808 board
GND (physical pin 38) -> GND on MCP9808 board
*/
//The bus address is determined by the state of pins A0, A1 and A2 on the MCP9808 board
static uint8_t ADDRESS = 0x18;
//hardware registers
const uint8_t REG_POINTER = 0x00;
const uint8_t REG_CONFIG = 0x01;
const uint8_t REG_TEMP_UPPER = 0x02;
const uint8_t REG_TEMP_LOWER = 0x03;
const uint8_t REG_TEMP_CRIT = 0x04;
const uint8_t REG_TEMP_AMB = 0x05;
const uint8_t REG_RESOLUTION = 0x08;
void mcp9808_check_limits(uint8_t upper_byte) {
// Check flags and raise alerts accordingly
if ((upper_byte & 0x40) == 0x40) { //TA > TUPPER
printf("Temperature is above the upper temperature limit.\n");
}
if ((upper_byte & 0x20) == 0x20) { //TA < TLOWER
printf("Temperature is below the lower temperature limit.\n");
}
if ((upper_byte & 0x80) == 0x80) { //TA > TCRIT
printf("Temperature is above the critical temperature limit.\n");
}
}
float mcp9808_convert_temp(uint8_t upper_byte, uint8_t lower_byte) {
float temperature;
//Check if TA <= 0°C and convert to denary accordingly
if ((upper_byte & 0x10) == 0x10) {
upper_byte = upper_byte & 0x0F;
temperature = 256 - (((float) upper_byte * 16) + ((float) lower_byte / 16));
} else {
temperature = (((float) upper_byte * 16) + ((float) lower_byte / 16));
}
return temperature;
}
#ifdef i2c_default
void mcp9808_set_limits() {
//Set an upper limit of 30°C for the temperature
uint8_t upper_temp_msb = 0x01;
uint8_t upper_temp_lsb = 0xE0;
//Set a lower limit of 20°C for the temperature
uint8_t lower_temp_msb = 0x01;
uint8_t lower_temp_lsb = 0x40;
//Set a critical limit of 40°C for the temperature
uint8_t crit_temp_msb = 0x02;
uint8_t crit_temp_lsb = 0x80;
uint8_t buf[3];
buf[0] = REG_TEMP_UPPER;
buf[1] = upper_temp_msb;
buf[2] = upper_temp_lsb;
i2c_write_blocking(i2c_default, ADDRESS, buf, 3, false);
buf[0] = REG_TEMP_LOWER;
buf[1] = lower_temp_msb;
buf[2] = lower_temp_lsb;
i2c_write_blocking(i2c_default, ADDRESS, buf, 3, false);
buf[0] = REG_TEMP_CRIT;
buf[1] = crit_temp_msb;
buf[2] = crit_temp_lsb;;
i2c_write_blocking(i2c_default, ADDRESS, buf, 3, false);
}
#endif
int main() {
stdio_init_all();
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
#warning i2c/mcp9808_i2c example requires a board with I2C pins
puts("Default I2C pins were not defined");
#else
printf("Hello, MCP9808! Reading raw data from registers...\n");
// This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico)
i2c_init(i2c_default, 400 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
// Make the I2C pins available to picotool
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
mcp9808_set_limits();
uint8_t buf[2];
uint16_t upper_byte;
uint16_t lower_byte;
float temperature;
while (1) {
// Start reading ambient temperature register for 2 bytes
i2c_write_blocking(i2c_default, ADDRESS, &REG_TEMP_AMB, 1, true);
i2c_read_blocking(i2c_default, ADDRESS, buf, 2, false);
upper_byte = buf[0];
lower_byte = buf[1];
//isolates limit flags in upper byte
mcp9808_check_limits(upper_byte & 0xE0);
//clears flag bits in upper byte
temperature = mcp9808_convert_temp(upper_byte & 0x1F, lower_byte);
printf("Ambient temperature: %.4f°C\n", temperature);
sleep_ms(1000);
}
#endif
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

@ -0,0 +1,11 @@
add_executable(mma8451_i2c
mma8451_i2c.c
)
# pull in common dependencies and additional i2c hardware support
target_link_libraries(mma8451_i2c pico_stdlib hardware_i2c)
# create map/bin/hex file etc.
pico_add_extra_outputs(mma8451_i2c)
# add url via pico_set_program_url
example_auto_set_url(mma8451_i2c)

View File

@ -0,0 +1,37 @@
= Attaching a MMA8451 3-axis digital accelerometer via I2C
This example code shows how to interface the Raspberry Pi Pico to the MMA8451 digital accelerometer sensor board.
======
This example reads and displays the acceleration values of the board in the 3 axis. It also allows the user to set the trade-off between the range and precision based on the values they require. Values often have an offset which can be accounted for by writing to the offset correction registers. The datasheet for the sensor can be found at https://cdn-shop.adafruit.com/datasheets/MMA8451Q-1.pdf for additional information.
======
== Wiring information
Wiring up the device requires 4 jumpers, to connect VIN, GND, SDA and SCL. The example here uses I2C port 0, which is assigned to GPIO 4 (SDA) and 5 (SCL) in software. Power is supplied from the VSYS pin.
[[mma8451_i2c_wiring]]
[pdfwidth=75%]
.Wiring Diagram for MMA8451.
image::mma8451_i2c.png[]
== List of Files
CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree.
mma8451_i2c.c:: The example code.
== Bill of Materials
.A list of materials required for the example
[[mma8451-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| MMA8451 board| 1 | https://www.adafruit.com/product/2019
| M/M Jumper wires | 4 | generic part
|===

View File

@ -0,0 +1,128 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
/* Example code to talk to a MMA8451 triple-axis accelerometer.
This reads and writes to registers on the board.
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO PICO_DEFAULT_I2C_SDA_PIN (On Pico this is GP4 (physical pin 6)) -> SDA on MMA8451 board
GPIO PICO_DEFAULT_I2C_SCK_PIN (On Pico this is GP5 (physcial pin 7)) -> SCL on MMA8451 board
VSYS (physical pin 39) -> VDD on MMA8451 board
GND (physical pin 38) -> GND on MMA8451 board
*/
const uint8_t ADDRESS = 0x1D;
//hardware registers
const uint8_t REG_X_MSB = 0x01;
const uint8_t REG_X_LSB = 0x02;
const uint8_t REG_Y_MSB = 0x03;
const uint8_t REG_Y_LSB = 0x04;
const uint8_t REG_Z_MSB = 0x05;
const uint8_t REG_Z_LSB = 0x06;
const uint8_t REG_DATA_CFG = 0x0E;
const uint8_t REG_CTRL_REG1 = 0x2A;
// Set the range and precision for the data
const uint8_t range_config = 0x01; // 0x00 for ±2g, 0x01 for ±4g, 0x02 for ±8g
const float count = 2048; // 4096 for ±2g, 2048 for ±4g, 1024 for ±8g
uint8_t buf[2];
float mma8451_convert_accel(uint16_t raw_accel) {
float acceleration;
// Acceleration is read as a multiple of g (gravitational acceleration on the Earth's surface)
// Check if acceleration < 0 and convert to decimal accordingly
if ((raw_accel & 0x2000) == 0x2000) {
raw_accel &= 0x1FFF;
acceleration = (-8192 + (float) raw_accel) / count;
} else {
acceleration = (float) raw_accel / count;
}
acceleration *= 9.81f;
return acceleration;
}
#ifdef i2c_default
void mma8451_set_state(uint8_t state) {
buf[0] = REG_CTRL_REG1;
buf[1] = state; // Set RST bit to 1
i2c_write_blocking(i2c_default, ADDRESS, buf, 2, false);
}
#endif
int main() {
stdio_init_all();
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
#warning i2c/mma8451_i2c example requires a board with I2C pins
puts("Default I2C pins were not defined");
#else
printf("Hello, MMA8451! Reading raw data from registers...\n");
// This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico)
i2c_init(i2c_default, 400 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
// Make the I2C pins available to picotool
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
float x_acceleration;
float y_acceleration;
float z_acceleration;
// Enable standby mode
mma8451_set_state(0x00);
// Edit configuration while in standby mode
buf[0] = REG_DATA_CFG;
buf[1] = range_config;
i2c_write_blocking(i2c_default, ADDRESS, buf, 2, false);
// Enable active mode
mma8451_set_state(0x01);
while (1) {
// Start reading acceleration registers for 2 bytes
i2c_write_blocking(i2c_default, ADDRESS, &REG_X_MSB, 1, true);
i2c_read_blocking(i2c_default, ADDRESS, buf, 2, false);
x_acceleration = mma8451_convert_accel(buf[0] << 6 | buf[1] >> 2);
i2c_write_blocking(i2c_default, ADDRESS, &REG_Y_MSB, 1, true);
i2c_read_blocking(i2c_default, ADDRESS, buf, 2, false);
y_acceleration = mma8451_convert_accel(buf[0] << 6 | buf[1] >> 2);
i2c_write_blocking(i2c_default, ADDRESS, &REG_Z_MSB, 1, true);
i2c_read_blocking(i2c_default, ADDRESS, buf, 2, false);
z_acceleration = mma8451_convert_accel(buf[0] << 6 | buf[1] >> 2);
// Display acceleration values
printf("ACCELERATION VALUES: \n");
printf("X acceleration: %.6fms^-2\n", x_acceleration);
printf("Y acceleration: %.6fms^-2\n", y_acceleration);
printf("Z acceleration: %.6fms^-2\n", z_acceleration);
sleep_ms(500);
// Clear terminal
printf("\e[1;1H\e[2J");
}
#endif
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View File

@ -0,0 +1,12 @@
add_executable(mpl3115a2_i2c
mpl3115a2_i2c.c
)
# pull in common dependencies and additional i2c hardware support
target_link_libraries(mpl3115a2_i2c pico_stdlib hardware_i2c)
# create map/bin/hex file etc.
pico_add_extra_outputs(mpl3115a2_i2c)
# add url via pico_set_program_url
example_auto_set_url(mpl3115a2_i2c)

View File

@ -0,0 +1,40 @@
= Attaching an MPL3115A2 altimeter via I2C
This example code shows how to interface the Raspberry Pi Pico to an MPL3115A2 altimeter via I2C. The MPL3115A2 has onboard pressure and temperature sensors which are used to estimate the altitude. In comparison to the BMP- family of pressure and temperature sensors, the MPL3115A2 has two interrupt pins for ultra low power operation and takes care of the sensor reading compensation on the board! It also has multiple modes of operation and impressive operating conditions.
The board used in this example https://www.adafruit.com/product/1893[comes from Adafruit], but any MPL3115A2 breakouts should work similarly.
The MPL3115A2 makes available two ways of reading its temperature and pressure data. The first is known as polling, where the Pico will continuously read data out of a set of auto-incrementing registers which are refreshed with new data every so often. The second, which this example will demonstrate, uses a 160-byte first-in-first-out (FIFO) queue and configurable interrupts to tell the Pico when to read data. More information regarding when the interrupts can be triggered available https://www.nxp.com/docs/en/data-sheet/MPL3115A2.pdf[in the datasheet]. This example waits for the 32 sample FIFO to overflow, detects this via an interrupt pin, and then averages the 32 samples taken. The sensor is configured to take a sample every second.
Bit math is used to convert the temperature and altitude data from the raw bits collected in the registers. Take the temperature calculation as an example: it is a 12-bit signed number with 8 integer bits and 4 fractional bits. First, we read the 2 8-bit registers and store them in a buffer. Then, we concatenate them into one unsigned 16-bit integer starting with the OUT_T_MSB register, thus making sure that the last bit of this register is aligned with the MSB in our 16 bit unsigned integer so it is correctly interpreted as the signed bit when we later cast this to a signed 16-bit integer. Finally, the entire number is converted to a float implicitly when we multiply it by 1/2^8 to shift it 8 bits to the right of the decimal point. Though only the last 4 bits of the OUT_T_LSB register hold data, this does not matter as the remaining 4 are held at zero and "disappear" when we shift the decimal point left by 8. Similar logic is applied to the altitude calculation.
TIP: Choosing the right sensor for your project among so many choices can be hard! There are multiple factors you may have to consider in addition to any constraints imposed on you. Cost, operating temperature, sensor resolution, power consumption, ease of use, communication protocols and supply voltage are all but a few factors that can play a role in sensor choice. For most hobbyist purposes though, the majority of sensors out there will do just fine!
== Wiring information
Wiring up the device requires 5 jumpers, to connect VCC (3.3v), GND, INT1, SDA and SCL. The example here uses I2C port 0, which is assigned to GPIO 4 (SDA) and GPIO 5 (SCL) by default. Power is supplied from the 3.3V pin.
NOTE: The MPL3115A2 has a 1.6-3.6V voltage supply range. This means it can work with the Pico's 3.3v pins out of the box but our Adafruit breakout has an onboard voltage regulator for good measure. This may not always be true of other sensors, though.
[[mpl3115a2_i2c_wiring]]
[pdfwidth=75%]
.Wiring Diagram for MPL3115A2 altimeter.
image::mpl3115a2_i2c_bb.png[]
== List of Files
CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree.
mpl3115a2_i2c.c:: The example code.
== Bill of Materials
.A list of materials required for the example
[[mpl3115a2-i2c-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| MPL3115A2 altimeter | 1 | https://www.adafruit.com/product/1893[Adafruit]
| M/M Jumper wires | 5 | generic part
|===

View File

@ -0,0 +1,206 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/gpio.h"
#include "hardware/i2c.h"
/* Example code to talk to an MPL3115A2 altimeter sensor via I2C
See accompanying documentation in README.adoc or the C++ SDK booklet.
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO PICO_DEFAULT_I2C_SDA_PIN (On Pico this is 4 (pin 6)) -> SDA on MPL3115A2 board
GPIO PICO_DEFAULT_I2C_SCK_PIN (On Pico this is 5 (pin 7)) -> SCL on MPL3115A2 board
GPIO 16 -> INT1 on MPL3115A2 board
3.3v (pin 36) -> VCC on MPL3115A2 board
GND (pin 38) -> GND on MPL3115A2 board
*/
// 7-bit address
#define ADDR 0x60
#define INT1_PIN _u(16)
// following definitions only valid for F_MODE > 0 (ie. if FIFO enabled)
#define MPL3115A2_F_DATA _u(0x01)
#define MPL3115A2_F_STATUS _u(0x00)
#define MPL3115A2_F_SETUP _u(0x0F)
#define MPL3115A2_INT_SOURCE _u(0x12)
#define MPL3115A2_CTRLREG1 _u(0x26)
#define MPL3115A2_CTRLREG2 _u(0x27)
#define MPL3115A2_CTRLREG3 _u(0x28)
#define MPL3115A2_CTRLREG4 _u(0x29)
#define MPL3115A2_CTRLREG5 _u(0x2A)
#define MPL3115A2_PT_DATA_CFG _u(0x13)
#define MPL3115A2_OFF_P _u(0x2B)
#define MPL3115A2_OFF_T _u(0x2C)
#define MPL3115A2_OFF_H _u(0x2D)
#define MPL3115A2_FIFO_DISABLED _u(0x00)
#define MPL3115A2_FIFO_STOP_ON_OVERFLOW _u(0x80)
#define MPL3115A2_FIFO_SIZE 32
#define MPL3115A2_DATA_BATCH_SIZE 5
#define MPL3115A2_ALTITUDE_NUM_REGS 3
#define MPL3115A2_ALTITUDE_INT_SIZE 20
#define MPL3115A2_TEMPERATURE_INT_SIZE 12
#define MPL3115A2_NUM_FRAC_BITS 4
#define PARAM_ASSERTIONS_ENABLE_I2C 1
volatile uint8_t fifo_data[MPL3115A2_FIFO_SIZE * MPL3115A2_DATA_BATCH_SIZE];
volatile bool has_new_data = false;
struct mpl3115a2_data_t {
// Q8.4 fixed point
float temperature;
// Q16.4 fixed-point
float altitude;
};
void copy_to_vbuf(uint8_t buf1[], volatile uint8_t buf2[], int buflen) {
for (size_t i = 0; i < buflen; i++) {
buf2[i] = buf1[i];
}
}
#ifdef i2c_default
void mpl3115a2_read_fifo(volatile uint8_t fifo_buf[]) {
// drains the 160 byte FIFO
uint8_t reg = MPL3115A2_F_DATA;
uint8_t buf[MPL3115A2_FIFO_SIZE * MPL3115A2_DATA_BATCH_SIZE];
i2c_write_blocking(i2c_default, ADDR, &reg, 1, true);
// burst read 160 bytes from fifo
i2c_read_blocking(i2c_default, ADDR, buf, MPL3115A2_FIFO_SIZE * MPL3115A2_DATA_BATCH_SIZE, false);
copy_to_vbuf(buf, fifo_buf, MPL3115A2_FIFO_SIZE * MPL3115A2_DATA_BATCH_SIZE);
}
uint8_t mpl3115a2_read_reg(uint8_t reg) {
uint8_t read;
i2c_write_blocking(i2c_default, ADDR, &reg, 1, true); // keep control of bus
i2c_read_blocking(i2c_default, ADDR, &read, 1, false);
return read;
}
void mpl3115a2_init() {
// set as altimeter with oversampling ratio of 128
uint8_t buf[] = {MPL3115A2_CTRLREG1, 0xB8};
i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
// set data refresh every 2 seconds, 0 next bits as we're not using those interrupts
buf[0] = MPL3115A2_CTRLREG2, buf[1] = 0x00;
i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
// set both interrupts pins to active low and enable internal pullups
buf[0] = MPL3115A2_CTRLREG3, buf[1] = 0x01;
i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
// enable FIFO interrupt
buf[0] = MPL3115A2_CTRLREG4, buf[1] = 0x40;
i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
// tie FIFO interrupt to pin INT1
buf[0] = MPL3115A2_CTRLREG5, buf[1] = 0x40;
i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
// set p, t and h offsets here if needed
// eg. 2's complement number: 0xFF subtracts 1 meter
//buf[0] = MPL3115A2_OFF_H, buf[1] = 0xFF;
//i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
// do not accept more data on FIFO overflow
buf[0] = MPL3115A2_F_SETUP, buf[1] = MPL3115A2_FIFO_STOP_ON_OVERFLOW;
i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
// set device active
buf[0] = MPL3115A2_CTRLREG1, buf[1] = 0xB9;
i2c_write_blocking(i2c_default, ADDR, buf, 2, false);
}
void gpio_callback(uint gpio, uint32_t events) {
// if we had enabled more than 2 interrupts on same pin, then we should read
// INT_SOURCE reg to find out which interrupt triggered
// we can filter by which GPIO was triggered
if (gpio == INT1_PIN) {
// FIFO overflow interrupt
// watermark bits set to 0 in F_SETUP reg, so only possible event is an overflow
// otherwise, we would read F_STATUS to confirm it was an overflow
printf("FIFO overflow!\n");
// drain the fifo
mpl3115a2_read_fifo(fifo_data);
// read status register to clear interrupt bit
mpl3115a2_read_reg(MPL3115A2_F_STATUS);
has_new_data = true;
}
}
#endif
void mpl3115a2_convert_fifo_batch(uint8_t start, volatile uint8_t buf[], struct mpl3115a2_data_t *data) {
// convert a batch of fifo data into temperature and altitude data
// 3 altitude registers: MSB (8 bits), CSB (8 bits) and LSB (4 bits, starting from MSB)
// first two are integer bits (2's complement) and LSB is fractional bits -> makes 20 bit signed integer
int32_t h = (int32_t) ((uint32_t) buf[start] << 24 | buf[start + 1] << 16 | buf[start + 2] << 8);
data->altitude = ((float)h) / 65536.f;
// 2 temperature registers: MSB (8 bits) and LSB (4 bits, starting from MSB)
// first 8 are integer bits with sign and LSB is fractional bits -> 12 bit signed integer
int16_t t = (int16_t) (((uint16_t) buf[start + 3]) << 8 | buf[start + 4]);
data->temperature = ((float)t) / 256.f;
}
int main() {
stdio_init_all();
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
#warning i2c / mpl3115a2_i2c example requires a board with I2C pins
puts("Default I2C pins were not defined");
#else
printf("Hello, MPL3115A2. Waiting for something to interrupt me!...\n");
// use default I2C0 at 400kHz, I2C is active low
i2c_init(i2c_default, 400 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
gpio_init(INT1_PIN);
gpio_pull_up(INT1_PIN); // pull it up even more!
// add program information for picotool
bi_decl(bi_program_name("Example in the pico-examples library for the MPL3115A2 altimeter"));
bi_decl(bi_1pin_with_name(16, "Interrupt pin 1"));
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
mpl3115a2_init();
gpio_set_irq_enabled_with_callback(INT1_PIN, GPIO_IRQ_LEVEL_LOW, true, &gpio_callback);
while (1) {
// as interrupt data comes in, let's print the 32 sample average
if (has_new_data) {
float tsum = 0, hsum = 0;
struct mpl3115a2_data_t data;
for (int i = 0; i < MPL3115A2_FIFO_SIZE; i++) {
mpl3115a2_convert_fifo_batch(i * MPL3115A2_DATA_BATCH_SIZE, fifo_data, &data);
tsum += data.temperature;
hsum += data.altitude;
}
printf("%d sample average -> t: %.4f C, h: %.4f m\n", MPL3115A2_FIFO_SIZE, tsum / MPL3115A2_FIFO_SIZE,
hsum / MPL3115A2_FIFO_SIZE);
has_new_data = false;
}
sleep_ms(10);
};
#endif
return 0;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

View File

@ -2,7 +2,7 @@ add_executable(mpu6050_i2c
mpu6050_i2c.c
)
# Pull in our (to be renamed) simple get you started dependencies
# pull in common dependencies and additional i2c hardware support
target_link_libraries(mpu6050_i2c pico_stdlib hardware_i2c)
# create map/bin/hex file etc.

View File

@ -35,9 +35,7 @@ mpu6050_i2c.c:: The example code.
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | http://raspberrypi.org/
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| MPU6050 board| 1 | generic part
| M/M Jumper wires | 4 | generic part
|===

View File

@ -24,8 +24,8 @@
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO PICO_DEFAULT_I2C_SDA_PIN (On Pico this is 4 (pin 6)) -> SDA on MPU6050 board
GPIO PICO_DEFAULT_I2C_SCK_PIN (On Pico this is 5 (pin 7)) -> SCL on MPU6050 board
GPIO PICO_DEFAULT_I2C_SDA_PIN (On Pico this is GP4 (pin 6)) -> SDA on MPU6050 board
GPIO PICO_DEFAULT_I2C_SCL_PIN (On Pico this is GP5 (pin 7)) -> SCL on MPU6050 board
3.3v (pin 36) -> VCC on MPU6050 board
GND (pin 38) -> GND on MPU6050 board
*/

View File

@ -0,0 +1,12 @@
add_executable(oled_i2c
oled_i2c.c
)
# pull in common dependencies and additional i2c hardware support
target_link_libraries(oled_i2c pico_stdlib hardware_i2c)
# create map/bin/hex file etc.
pico_add_extra_outputs(oled_i2c)
# add url via pico_set_program_url
example_auto_set_url(oled_i2c)

76
i2c/oled_i2c/README.adoc Normal file
View File

@ -0,0 +1,76 @@
= Attaching an OLED display via I2C
This example code shows how to interface the Raspberry Pi Pico with an 128x32 OLED display board based on the SSD1306 display driver, datasheet https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf[here].
The code displays a series of tiny raspberries that scroll horizontally, in the process showing you how to initialize the display, write to the entire display, write to only a portion of the display, and configure scrolling.
The SSD1306 is operated via a list of versatile commands (see datasheet) that allows the user to access all the capabilities of the driver. After sending a slave address, the data that follows can be either a command, flags to follow up a command or data to be written directly into the display's RAM. A control byte is required for each write after the slave address so that the driver knows what type of data is being sent.
This display is 32 pixels high by 128 pixels wide. These 32 vertical pixels are partitioned into 4 pages, each 8 pixels in height. In RAM, this looks roughly like:
[NOTE]
======
The SSD1306 can drive displays that are up to 64 pixels high and 128 pixels wide.
======
----
| COL0 | COL1 | COL2 | COL3 | ... | COL126 | COL127 |
PAGE 0 | | | | | | | |
PAGE 1 | | | | | | | |
PAGE 2 | | | | | | | |
PAGE 3 | | | | | | | |
--------------------------------------------------------------
----
Within each page, we have:
----
| COL0 | COL1 | COL2 | COL3 | ... | COL126 | COL127 |
COM 0 | | | | | | | |
COM 1 | | | | | | | |
: | | | | | | | |
COM 7 | | | | | | | |
-------------------------------------------------------------
----
[NOTE]
======
There is a difference between columns in RAM and the actual segment pads that connect the driver to the display. The RAM addresses COL0 - COL127 are mapped to these segment pins SEG0 - SEG127 by default. The distinction between these two is important as we can for example, easily mirror contents of RAM without rewriting a buffer.
======
The driver has 3 modes of transferring the pixels in RAM to the display (provided that the driver is set to use its RAM content to drive the display, ie. command 0xA4 is sent). We choose horizontal addressing mode which, after setting the column address and page address registers to our desired start positions, will increment the column address register until the OLED display width is reached (127 in our case) after which the column address register will reset to its starting value and the page address is incremented. Once the page register reaches the end, it will wrap around as well. Effectively, this scans across the display from top to bottom, left to right in blocks that are 8 pixels high. When a byte is sent to be written into RAM, it sets all the rows for the current position of the column address register. So, if we send 10101010, and we are on PAGE 0 and COL1, COM0 is set to 1, COM1 is set to 0, COM2 is set to 1, and so on. Effectively, the byte is "transposed" to fill a single page's column. The datasheet has further information on this and the two other modes.
Horizontal addressing mode has the key advantage that we can keep one single 512 byte buffer (128 columns x 4 pages and each byte fills a page's rows) and write this in one go to the RAM (column address auto increments on writes as well as reads) instead of working with 2D matrices of pixels and adding more overhead.
[NOTE]
======
* The SSD1306 is able to drive 128x64 displays but as our display is 128x32, only half of the COM (common) pins are connected to the display.
* The specific display model being used is UG-2832HSWEG02
======
== Wiring information
Wiring up the device requires 4 jumpers, to connect VCC (3.3v), GND, SDA and SCL and optionally a 5th jumper for the driver RESET pin. The example here uses the default I2C port 0, which is assigned to GPIO 4 (SDA) and 5 (SCL) in software. Power is supplied from the 3.3V pin from the Pico.
[[oled_i2c_wiring]]
[pdfwidth=75%]
.Wiring Diagram for oled display via I2C.
image::oled_i2c_bb.png[]
== List of Files
CMakeLists.txt:: CMake file to incorporate the example into the examples build tree.
oled_i2c.c:: The example code.
== Bill of Materials
.A list of materials required for the example
[[oled_i2c-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| SSD1306-based OLED display | 1 | https://www.adafruit.com/product/4440[Adafruit part]
| M/M Jumper wires | 4 | generic part
|===

81
i2c/oled_i2c/img_to_array.py Executable file
View File

@ -0,0 +1,81 @@
#!/usr/bin/env python3
# Converts a grayscale image into a format able to be
# displayed by the SSD1306 driver in horizontal addressing mode
# usage: python3 img_to_array.py <logo.bmp>
# depends on the Pillow library
# `python3 -m pip install --upgrade Pillow`
from PIL import Image
import sys
from pathlib import Path
OLED_HEIGHT = 32
OLED_WIDTH = 128
OLED_PAGE_HEIGHT = 8
if len(sys.argv) < 2:
print("No image path provided.")
sys.exit()
img_path = sys.argv[1]
try:
im = Image.open(img_path)
except OSError:
raise Exception("Oops! The image could not be opened.")
img_width = im.size[0]
img_height = im.size[1]
if img_width > OLED_WIDTH or img_height > OLED_HEIGHT:
print(f'Your image is f{img_width} pixels wide and {img_height} pixels high, but...')
raise Exception(f"OLED display only {OLED_WIDTH} pixels wide and {OLED_HEIGHT} pixels high!")
if not (im.mode == "1" or im.mode == "L"):
raise Exception("Image must be grayscale only")
# black or white
out = im.convert("1")
img_name = Path(im.filename).stem
# `pixels` is a flattened array with the top left pixel at index 0
# and bottom right pixel at the width*height-1
pixels = list(out.getdata())
# swap white for black and swap (255, 0) for (1, 0)
pixels = [0 if x == 255 else 1 for x in pixels]
# our goal is to divide the image into 8-pixel high pages
# and turn a pixel column into one byte, eg for one page:
# 0 1 0 ....
# 1 0 0
# 1 1 1
# 0 0 1
# 1 1 0
# 0 1 0
# 1 1 1
# 0 0 1 ....
# we get 0x6A, 0xAE, 0x33 ... and so on
# as `pixels` is flattened, each bit in a column is IMG_WIDTH apart from the next
buffer = []
for i in range(img_height // OLED_PAGE_HEIGHT):
start_index = i*img_width*OLED_PAGE_HEIGHT
for j in range(img_width):
out_byte = 0
for k in range(OLED_PAGE_HEIGHT):
out_byte |= pixels[k*img_width + start_index + j] << k
buffer.append(f'{out_byte:#04x}')
buffer = ", ".join(buffer)
buffer_hex = f'static uint8_t {img_name}[] = {{{buffer}}}\n'
with open(f'{img_name}.h', 'wt') as file:
file.write(f'#define IMG_WIDTH {img_width}\n')
file.write(f'#define IMG_HEIGHT {img_height}\n\n')
file.write(buffer_hex)

298
i2c/oled_i2c/oled_i2c.c Normal file
View File

@ -0,0 +1,298 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
#include "raspberry26x32.h"
/* Example code to talk to an SSD1306-based OLED display
NOTE: Ensure the device is capable of being driven at 3.3v NOT 5v. The Pico
GPIO (and therefore I2C) cannot be used at 5v.
You will need to use a level shifter on the I2C lines if you want to run the
board at 5v.
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO PICO_DEFAULT_I2C_SDA_PIN (on Pico this is GP4 (pin 6)) -> SDA on display
board
GPIO PICO_DEFAULT_I2C_SCK_PIN (on Pico this is GP5 (pin 7)) -> SCL on
display board
3.3v (pin 36) -> VCC on display board
GND (pin 38) -> GND on display board
*/
// commands (see datasheet)
#define OLED_SET_CONTRAST _u(0x81)
#define OLED_SET_ENTIRE_ON _u(0xA4)
#define OLED_SET_NORM_INV _u(0xA6)
#define OLED_SET_DISP _u(0xAE)
#define OLED_SET_MEM_ADDR _u(0x20)
#define OLED_SET_COL_ADDR _u(0x21)
#define OLED_SET_PAGE_ADDR _u(0x22)
#define OLED_SET_DISP_START_LINE _u(0x40)
#define OLED_SET_SEG_REMAP _u(0xA0)
#define OLED_SET_MUX_RATIO _u(0xA8)
#define OLED_SET_COM_OUT_DIR _u(0xC0)
#define OLED_SET_DISP_OFFSET _u(0xD3)
#define OLED_SET_COM_PIN_CFG _u(0xDA)
#define OLED_SET_DISP_CLK_DIV _u(0xD5)
#define OLED_SET_PRECHARGE _u(0xD9)
#define OLED_SET_VCOM_DESEL _u(0xDB)
#define OLED_SET_CHARGE_PUMP _u(0x8D)
#define OLED_SET_HORIZ_SCROLL _u(0x26)
#define OLED_SET_SCROLL _u(0x2E)
#define OLED_ADDR _u(0x3C)
#define OLED_HEIGHT _u(32)
#define OLED_WIDTH _u(128)
#define OLED_PAGE_HEIGHT _u(8)
#define OLED_NUM_PAGES OLED_HEIGHT / OLED_PAGE_HEIGHT
#define OLED_BUF_LEN (OLED_NUM_PAGES * OLED_WIDTH)
#define OLED_WRITE_MODE _u(0xFE)
#define OLED_READ_MODE _u(0xFF)
struct render_area {
uint8_t start_col;
uint8_t end_col;
uint8_t start_page;
uint8_t end_page;
int buflen;
};
void fill(uint8_t buf[], uint8_t fill) {
// fill entire buffer with the same byte
for (int i = 0; i < OLED_BUF_LEN; i++) {
buf[i] = fill;
}
};
void fill_page(uint8_t *buf, uint8_t fill, uint8_t page) {
// fill entire page with the same byte
memset(buf + (page * OLED_WIDTH), fill, OLED_WIDTH);
};
// convenience methods for printing out a buffer to be rendered
// mostly useful for debugging images, patterns, etc
void print_buf_page(uint8_t buf[], uint8_t page) {
// prints one page of a full length (128x4) buffer
for (int j = 0; j < OLED_PAGE_HEIGHT; j++) {
for (int k = 0; k < OLED_WIDTH; k++) {
printf("%u", (buf[page * OLED_WIDTH + k] >> j) & 0x01);
}
printf("\n");
}
}
void print_buf_pages(uint8_t buf[]) {
// prints all pages of a full length buffer
for (int i = 0; i < OLED_NUM_PAGES; i++) {
printf("--page %d--\n", i);
print_buf_page(buf, i);
}
}
void print_buf_area(uint8_t *buf, struct render_area *area) {
// print a render area of generic size
int area_width = area->end_col - area->start_col + 1;
int area_height = area->end_page - area->start_page + 1; // in pages, not pixels
for (int i = 0; i < area_height; i++) {
for (int j = 0; j < OLED_PAGE_HEIGHT; j++) {
for (int k = 0; k < area_width; k++) {
printf("%u", (buf[i * area_width + k] >> j) & 0x01);
}
printf("\n");
}
}
}
void calc_render_area_buflen(struct render_area *area) {
// calculate how long the flattened buffer will be for a render area
area->buflen = (area->end_col - area->start_col + 1) * (area->end_page - area->start_page + 1);
}
#ifdef i2c_default
void oled_send_cmd(uint8_t cmd) {
// I2C write process expects a control byte followed by data
// this "data" can be a command or data to follow up a command
// Co = 1, D/C = 0 => the driver expects a command
uint8_t buf[2] = {0x80, cmd};
i2c_write_blocking(i2c_default, (OLED_ADDR & OLED_WRITE_MODE), buf, 2, false);
}
void oled_send_buf(uint8_t buf[], int buflen) {
// in horizontal addressing mode, the column address pointer auto-increments
// and then wraps around to the next page, so we can send the entire frame
// buffer in one gooooooo!
// copy our frame buffer into a new buffer because we need to add the control byte
// to the beginning
// TODO find a more memory-efficient way to do this..
// maybe break the data transfer into pages?
uint8_t *temp_buf = malloc(buflen + 1);
for (int i = 1; i < buflen + 1; i++) {
temp_buf[i] = buf[i - 1];
}
// Co = 0, D/C = 1 => the driver expects data to be written to RAM
temp_buf[0] = 0x40;
i2c_write_blocking(i2c_default, (OLED_ADDR & OLED_WRITE_MODE), temp_buf, buflen + 1, false);
free(temp_buf);
}
void oled_init() {
// some of these commands are not strictly necessary as the reset
// process defaults to some of these but they are shown here
// to demonstrate what the initialization sequence looks like
// some configuration values are recommended by the board manufacturer
oled_send_cmd(OLED_SET_DISP | 0x00); // set display off
/* memory mapping */
oled_send_cmd(OLED_SET_MEM_ADDR); // set memory address mode
oled_send_cmd(0x00); // horizontal addressing mode
/* resolution and layout */
oled_send_cmd(OLED_SET_DISP_START_LINE); // set display start line to 0
oled_send_cmd(OLED_SET_SEG_REMAP | 0x01); // set segment re-map
// column address 127 is mapped to SEG0
oled_send_cmd(OLED_SET_MUX_RATIO); // set multiplex ratio
oled_send_cmd(OLED_HEIGHT - 1); // our display is only 32 pixels high
oled_send_cmd(OLED_SET_COM_OUT_DIR | 0x08); // set COM (common) output scan direction
// scan from bottom up, COM[N-1] to COM0
oled_send_cmd(OLED_SET_DISP_OFFSET); // set display offset
oled_send_cmd(0x00); // no offset
oled_send_cmd(OLED_SET_COM_PIN_CFG); // set COM (common) pins hardware configuration
oled_send_cmd(0x02); // manufacturer magic number
/* timing and driving scheme */
oled_send_cmd(OLED_SET_DISP_CLK_DIV); // set display clock divide ratio
oled_send_cmd(0x80); // div ratio of 1, standard freq
oled_send_cmd(OLED_SET_PRECHARGE); // set pre-charge period
oled_send_cmd(0xF1); // Vcc internally generated on our board
oled_send_cmd(OLED_SET_VCOM_DESEL); // set VCOMH deselect level
oled_send_cmd(0x30); // 0.83xVcc
/* display */
oled_send_cmd(OLED_SET_CONTRAST); // set contrast control
oled_send_cmd(0xFF);
oled_send_cmd(OLED_SET_ENTIRE_ON); // set entire display on to follow RAM content
oled_send_cmd(OLED_SET_NORM_INV); // set normal (not inverted) display
oled_send_cmd(OLED_SET_CHARGE_PUMP); // set charge pump
oled_send_cmd(0x14); // Vcc internally generated on our board
oled_send_cmd(OLED_SET_SCROLL | 0x00); // deactivate horizontal scrolling if set
// this is necessary as memory writes will corrupt if scrolling was enabled
oled_send_cmd(OLED_SET_DISP | 0x01); // turn display on
}
void render(uint8_t *buf, struct render_area *area) {
// update a portion of the display with a render area
oled_send_cmd(OLED_SET_COL_ADDR);
oled_send_cmd(area->start_col);
oled_send_cmd(area->end_col);
oled_send_cmd(OLED_SET_PAGE_ADDR);
oled_send_cmd(area->start_page);
oled_send_cmd(area->end_page);
oled_send_buf(buf, area->buflen);
}
#endif
int main() {
stdio_init_all();
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
#warning i2c / oled_i2d example requires a board with I2C pins
puts("Default I2C pins were not defined");
#else
// useful information for picotool
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
bi_decl(bi_program_description("OLED I2C example for the Raspberry Pi Pico"));
printf("Hello, OLED display! Look at my raspberries..\n");
// I2C is "open drain", pull ups to keep signal high when no data is being
// sent
i2c_init(i2c_default, 400 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
// run through the complete initialization process
oled_init();
// initialize render area for entire frame (128 pixels by 4 pages)
struct render_area frame_area = {start_col: 0, end_col : OLED_WIDTH - 1, start_page : 0, end_page : OLED_NUM_PAGES -
1};
calc_render_area_buflen(&frame_area);
// zero the entire display
uint8_t buf[OLED_BUF_LEN];
fill(buf, 0x00);
render(buf, &frame_area);
// intro sequence: flash the screen 3 times
for (int i = 0; i < 3; i++) {
oled_send_cmd(0xA5); // ignore RAM, all pixels on
sleep_ms(500);
oled_send_cmd(0xA4); // go back to following RAM
sleep_ms(500);
}
// render 3 cute little raspberries
struct render_area area = {start_col: 0, end_col : IMG_WIDTH - 1, start_page : 0, end_page : OLED_NUM_PAGES - 1};
calc_render_area_buflen(&area);
render(raspberry26x32, &area);
for (int i = 1; i < 3; i++) {
uint8_t offset = 5 + IMG_WIDTH; // 5px padding
area.start_col += offset;
area.end_col += offset;
render(raspberry26x32, &area);
}
// configure horizontal scrolling
oled_send_cmd(OLED_SET_HORIZ_SCROLL | 0x00);
oled_send_cmd(0x00); // dummy byte
oled_send_cmd(0x00); // start page 0
oled_send_cmd(0x00); // time interval
oled_send_cmd(0x03); // end page 3
oled_send_cmd(0x00); // dummy byte
oled_send_cmd(0xFF); // dummy byte
// let's goooo!
oled_send_cmd(OLED_SET_SCROLL | 0x01);
#endif
return 0;
}

BIN
i2c/oled_i2c/oled_i2c.fzz Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

View File

@ -0,0 +1,4 @@
#define IMG_WIDTH 26
#define IMG_HEIGHT 32
static uint8_t raspberry26x32[] = { 0x0, 0x0, 0xe, 0x7e, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfc, 0xf8, 0xfc, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7e, 0x1e, 0x0, 0x0, 0x0, 0x80, 0xe0, 0xf8, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf8, 0xe0, 0x80, 0x0, 0x0, 0x1e, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x1e, 0x0, 0x0, 0x0, 0x3, 0x7, 0xf, 0x1f, 0x1f, 0x3f, 0x3f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x7f, 0x3f, 0x3f, 0x1f, 0x1f, 0xf, 0x7, 0x3, 0x0, 0x0};

View File

@ -0,0 +1,12 @@
add_executable(pa1010d_i2c
pa1010d_i2c.c
)
# pull in common dependencies and additional i2c hardware support
target_link_libraries(pa1010d_i2c pico_stdlib hardware_i2c)
# create map/bin/hex file etc.
pico_add_extra_outputs(pa1010d_i2c)
# add url via pico_set_program_url
example_auto_set_url(pa1010d_i2c)

View File

@ -0,0 +1,42 @@
= Attaching a PA1010D Mini GPS module via I2C
This example code shows how to interface the Raspberry Pi Pico to the PA1010D Mini GPS module
======
This allows you read basic location and time data from the Recommended Minimum Specific GNSS Sentence (GNRMC protocol) and displays it in a user-friendly format. The datasheet for the module can be found on https://cdn-learn.adafruit.com/assets/assets/000/084/295/original/CD_PA1010D_Datasheet_v.03.pdf?1573833002. The output sentence is read and parsed to split the data fields into a 2D character array, which are then individually printed out. The commands to use different protocols and change settings are found on https://www.sparkfun.com/datasheets/GPS/Modules/PMTK_Protocol.pdf. Additional protocols can be used by editing the init_command array.
======
[NOTE]
======
Each command requires a checksum after the asterisk. The checksum can be calculated for your command using the following website: https://nmeachecksum.eqth.net/.
The GPS needs to be used outdoors in open skies and requires about 15 seconds to acquire a satellite signal in order to display valid data. When the signal is detected, the device will blink a green LED at 1 Hz.
======
== Wiring information
Wiring up the device requires 4 jumpers, to connect VDD, GND, SDA and SCL. The example here uses I2C port 0, which is assigned to GPIO 4 (SDA) and 5 (SCL) in software. Power is supplied from the 3V pin.
[[pa1010d_i2c_wiring]]
[pdfwidth=75%]
.Wiring Diagram for PA1010D.
image::pa1010d_i2c.png[]
== List of Files
CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree.
pa1010d_i2c.c:: The example code.
== Bill of Materials
.A list of materials required for the example
[[pa1010d-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| PA1010D board| 1 | https://shop.pimoroni.com/products/pa1010d-gps-breakout
| M/M Jumper wires | 4 | generic part
|===

View File

@ -0,0 +1,156 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
#include "string.h"
/* Example code to talk to a PA1010D Mini GPS module.
This example reads the Recommended Minimum Specific GNSS Sentence, which includes basic location and time data, each second, formats and displays it.
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO PICO_DEFAULT_I2C_SDA_PIN (On Pico this is 4 (physical pin 6)) -> SDA on PA1010D board
GPIO PICO_DEFAULT_I2C_SCK_PIN (On Pico this is 5 (physical pin 7)) -> SCL on PA1010D board
3.3v (physical pin 36) -> VCC on PA1010D board
GND (physical pin 38) -> GND on PA1010D board
*/
const int addr = 0x10;
const int max_read = 250;
#ifdef i2c_default
void pa1010d_write_command(const char command[], int com_length) {
// Convert character array to bytes for writing
uint8_t int_command[com_length];
for (int i = 0; i < com_length; ++i) {
int_command[i] = command[i];
i2c_write_blocking(i2c_default, addr, &int_command[i], 1, true);
}
}
void pa1010d_parse_string(char output[], char protocol[]) {
// Finds location of protocol message in output
char *com_index = strstr(output, protocol);
int p = com_index - output;
// Splits components of output sentence into array
int no_of_fields = 14;
int max_len = 15;
int n = 0;
int m = 0;
char gps_data[no_of_fields][max_len];
memset(gps_data, 0, sizeof(gps_data));
bool complete = false;
while (output[p] != '$' && n < max_len && complete == false) {
if (output[p] == ',' || output[p] == '*') {
n += 1;
m = 0;
} else {
gps_data[n][m] = output[p];
// Checks if sentence is complete
if (m < no_of_fields) {
m++;
} else {
complete = true;
}
}
p++;
}
// Displays GNRMC data
// Similarly, additional if statements can be used to add more protocols
if (strcmp(protocol, "GNRMC") == 0) {
printf("Protcol:%s\n", gps_data[0]);
printf("UTC Time: %s\n", gps_data[1]);
printf("Status: %s\n", gps_data[2][0] == 'V' ? "Data invalid. GPS fix not found." : "Data Valid");
printf("Latitude: %s\n", gps_data[3]);
printf("N/S indicator: %s\n", gps_data[4]);
printf("Longitude: %s\n", gps_data[5]);
printf("E/W indicator: %s\n", gps_data[6]);
printf("Speed over ground: %s\n", gps_data[7]);
printf("Course over ground: %s\n", gps_data[8]);
printf("Date: %c%c/%c%c/%c%c\n", gps_data[9][0], gps_data[9][1], gps_data[9][2], gps_data[9][3], gps_data[9][4],
gps_data[9][5]);
printf("Magnetic Variation: %s\n", gps_data[10]);
printf("E/W degree indicator: %s\n", gps_data[11]);
printf("Mode: %s\n", gps_data[12]);
printf("Checksum: %c%c\n", gps_data[13][0], gps_data[13][1]);
}
}
void pa1010d_read_raw(char numcommand[]) {
uint8_t buffer[max_read];
int i = 0;
bool complete = false;
i2c_read_blocking(i2c_default, addr, buffer, max_read, false);
// Convert bytes to characters
while (i < max_read && complete == false) {
numcommand[i] = buffer[i];
// Stop converting at end of message
if (buffer[i] == 10 && buffer[i + 1] == 10) {
complete = true;
}
i++;
}
}
#endif
int main() {
stdio_init_all();
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
#warning i2c/mpu6050_i2c example requires a board with I2C pins
puts("Default I2C pins were not defined");
#else
char numcommand[max_read];
// Decide which protocols you would like to retrieve data from
char init_command[] = "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n";
// This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico)
i2c_init(i2c_default, 400 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
// Make the I2C pins available to picotool
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
printf("Hello, PA1010D! Reading raw data from module...\n");
pa1010d_write_command(init_command, sizeof(init_command));
while (1) {
// Clear array
memset(numcommand, 0, max_read);
// Read and re-format
pa1010d_read_raw(numcommand);
pa1010d_parse_string(numcommand, "GNRMC");
// Wait for data to refresh
sleep_ms(1000);
// Clear terminal
printf("\e[1;1H\e[2J");
}
#endif
return 0;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

View File

@ -0,0 +1,12 @@
add_executable(pcf8523_i2c
pcf8523_i2c.c
)
# pull in common dependencies and additional i2c hardware support
target_link_libraries(pcf8523_i2c pico_stdlib hardware_i2c)
# create map/bin/hex file etc.
pico_add_extra_outputs(pcf8523_i2c)
# add url via pico_set_program_url
example_auto_set_url(pcf8523_i2c)

View File

@ -0,0 +1,35 @@
= Attaching a PCF8523 Real Time Clock via I2C
This example code shows how to interface the Raspberry Pi Pico to the PCF8523 Real Time Clock
======
This example allows you to set the current time and date to initialise it and then refreshes it every half-second. Additionally it lets you set an alarm for a particular time + date and raises an alert accordingly. More information about the module is available at https://learn.adafruit.com/adafruit-pcf8523-real-time-clock.
======
== Wiring information
Wiring up the device requires 4 jumpers, to connect VDD, GND, SDA and SCL. The example here uses I2C port 0, which is assigned to GPIO 4 (SDA) and 5 (SCL) in software. Power is supplied from the 5V pin.
[[pcf8523_i2c_wiring]]
[pdfwidth=75%]
.Wiring Diagram for PCF8523.
image::pc8523_i2c.png[]
== List of Files
CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree.
pcf8523_i2c.c:: The example code.
== Bill of Materials
.A list of materials required for the example
[[pcf8523-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| PCF8523 board| 1 | https://www.adafruit.com/product/3295
| M/M Jumper wires | 4 | generic part
|===

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@ -0,0 +1,164 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
/* Example code to talk to a PCF8520 Real Time Clock module
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO PICO_DEFAULT_I2C_SDA_PIN (On Pico this is 4 (physical pin 6)) -> SDA on PCF8520 board
GPIO PICO_DEFAULT_I2C_SCK_PIN (On Pico this is 5 (physical pin 7)) -> SCL on PCF8520 board
5V (physical pin 40) -> VCC on PCF8520 board
GND (physical pin 38) -> GND on PCF8520 board
*/
#ifdef i2c_default
// By default these devices are on bus address 0x68
static int addr = 0x68;
static void pcf8520_reset() {
// Two byte reset. First byte register, second byte data
// There are a load more options to set up the device in different ways that could be added here
uint8_t buf[] = {0x00, 0x58};
i2c_write_blocking(i2c_default, addr, buf, 2, false);
}
static void pcf820_write_current_time() {
// buf[0] is the register to write to
// buf[1] is the value that will be written to the register
uint8_t buf[2];
//Write values for the current time in the array
//index 0 -> second: bits 4-6 are responsible for the ten's digit and bits 0-3 for the unit's digit
//index 1 -> minute: bits 4-6 are responsible for the ten's digit and bits 0-3 for the unit's digit
//index 2 -> hour: bits 4-5 are responsible for the ten's digit and bits 0-3 for the unit's digit
//index 3 -> day of the month: bits 4-5 are responsible for the ten's digit and bits 0-3 for the unit's digit
//index 4 -> day of the week: where Sunday = 0x00, Monday = 0x01, Tuesday... ...Saturday = 0x06
//index 5 -> month: bit 4 is responsible for the ten's digit and bits 0-3 for the unit's digit
//index 6 -> year: bits 4-7 are responsible for the ten's digit and bits 0-3 for the unit's digit
//NOTE: if the value in the year register is a multiple for 4, it will be considered a leap year and hence will include the 29th of February
uint8_t current_val[7] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
for (int i = 3; i < 10; ++i) {
buf[0] = i;
buf[1] = current_val[i - 3];
i2c_write_blocking(i2c_default, addr, buf, 2, false);
}
}
static void pcf8520_read_raw(uint8_t *buffer) {
// For this particular device, we send the device the register we want to read
// first, then subsequently read from the device. The register is auto incrementing
// so we don't need to keep sending the register we want, just the first.
// Start reading acceleration registers from register 0x3B for 6 bytes
uint8_t val = 0x03;
i2c_write_blocking(i2c_default, addr, &val, 1, true); // true to keep master control of bus
i2c_read_blocking(i2c_default, addr, buffer, 7, false);
}
void pcf8520_set_alarm() {
// buf[0] is the register to write to
// buf[1] is the value that will be written to the register
uint8_t buf[2];
// Default value of alarm register is 0x80
// Set bit 8 of values to 0 to activate that particular alarm
// Index 0 -> minute: bits 4-5 are responsible for the ten's digit and bits 0-3 for the unit's digit
// Index 1 -> hour: bits 4-6 are responsible for the ten's digit and bits 0-3 for the unit's digit
// Index 2 -> day of the month: bits 4-5 are responsible for the ten's digit and bits 0-3 for the unit's digit
// Index 3 -> day of the week: where Sunday = 0x00, Monday = 0x01, Tuesday... ...Saturday = 0x06
uint8_t alarm_val[4] = {0x01, 0x80, 0x80, 0x80};
// Write alarm values to registers
for (int i = 10; i < 14; ++i) {
buf[0] = (uint8_t) i;
buf[1] = alarm_val[i - 10];
i2c_write_blocking(i2c_default, addr, buf, 2, false);
}
}
void pcf8520_check_alarm() {
// Check bit 3 of control register 2 for alarm flags
uint8_t status[1];
uint8_t val = 0x01;
i2c_write_blocking(i2c_default, addr, &val, 1, true); // true to keep master control of bus
i2c_read_blocking(i2c_default, addr, status, 1, false);
if ((status[0] & 0x08) == 0x08) {
printf("ALARM RINGING");
} else {
printf("Alarm not triggered yet");
}
}
void pcf8520_convert_time(int conv_time[7], const uint8_t raw_time[7]) {
// Convert raw data into time
conv_time[0] = (10 * (int) ((raw_time[0] & 0x70) >> 4)) + ((int) (raw_time[0] & 0x0F));
conv_time[1] = (10 * (int) ((raw_time[1] & 0x70) >> 4)) + ((int) (raw_time[1] & 0x0F));
conv_time[2] = (10 * (int) ((raw_time[2] & 0x30) >> 4)) + ((int) (raw_time[2] & 0x0F));
conv_time[3] = (10 * (int) ((raw_time[3] & 0x30) >> 4)) + ((int) (raw_time[3] & 0x0F));
conv_time[4] = (int) (raw_time[4] & 0x07);
conv_time[5] = (10 * (int) ((raw_time[5] & 0x10) >> 4)) + ((int) (raw_time[5] & 0x0F));
conv_time[6] = (10 * (int) ((raw_time[6] & 0xF0) >> 4)) + ((int) (raw_time[6] & 0x0F));
}
#endif
int main() {
stdio_init_all();
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
#warning i2c/pcf8520_i2c example requires a board with I2C pins
puts("Default I2C pins were not defined");
#else
printf("Hello, PCF8520! Reading raw data from registers...\n");
// This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico)
i2c_init(i2c_default, 400 * 1000);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
// Make the I2C pins available to picotool
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
pcf8520_reset();
pcf820_write_current_time();
pcf8520_set_alarm();
pcf8520_check_alarm();
uint8_t raw_time[7];
int real_time[7];
char days_of_week[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
while (1) {
pcf8520_read_raw(raw_time);
pcf8520_convert_time(real_time, raw_time);
printf("Time: %02d : %02d : %02d\n", real_time[2], real_time[1], real_time[0]);
printf("Date: %s %02d / %02d / %02d\n", days_of_week[real_time[4]], real_time[3], real_time[5], real_time[6]);
pcf8520_check_alarm();
sleep_ms(500);
// Clear terminal
printf("\e[1;1H\e[2J");
}
#endif
return 0;
}

View File

@ -3,7 +3,7 @@ if (TARGET hardware_interp)
hello_interp.c
)
# Pull in dependencies
# pull in common dependencies and additional interpolator hardware support
target_link_libraries(hello_interp pico_stdlib hardware_interp)
# create map/bin/hex file etc.
@ -11,4 +11,4 @@ if (TARGET hardware_interp)
# add url via pico_set_program_url
example_auto_set_url(hello_interp)
endif ()
endif ()

View File

@ -1,6 +1,6 @@
if (NOT PICO_NO_HARDWARE)
add_subdirectory(hello_multicore)
add_subdirectory(multicore_fifo_irqs)
add_subdirectory(multicore_runner)
add_subdirectory(multicore_runner_queue)
add_subdirectory(multicore_fifo_irqs)
endif ()
endif ()

View File

@ -2,7 +2,7 @@ add_executable(picoboard_blinky
blinky.c
)
# Pull in our pico_stdlib which pulls in commonly used features
# pull in common dependencies
target_link_libraries(picoboard_blinky pico_stdlib)
# create map/bin/hex file etc.

View File

@ -2,7 +2,7 @@ add_executable(picoboard_button
button.c
)
# Pull in our pico_stdlib which pulls in commonly used features
# pull in common dependencies
target_link_libraries(picoboard_button pico_stdlib)
# create map/bin/hex file etc.

View File

@ -6,10 +6,12 @@ if (NOT PICO_NO_HARDWARE)
add_subdirectory(hello_pio)
add_subdirectory(hub75)
add_subdirectory(i2c)
add_subdirectory(ir_nec)
add_subdirectory(logic_analyser)
add_subdirectory(manchester_encoding)
add_subdirectory(pio_blink)
add_subdirectory(pwm)
add_subdirectory(quadrature_encoder)
add_subdirectory(spi)
add_subdirectory(squarewave)
add_subdirectory(st7789_lcd)

View File

@ -1,3 +1,9 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program addition
; Pop two 32 bit integers from the TX FIFO, add them together, and push the

View File

@ -1,3 +1,9 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program hub75_row
; side-set pin 0 is LATCH

View File

@ -0,0 +1,8 @@
# build the transmit and receive libraries
#
add_subdirectory(nec_transmit_library)
add_subdirectory(nec_receive_library)
# build the example program
#
add_subdirectory(ir_loopback)

57
pio/ir_nec/README.adoc Normal file
View File

@ -0,0 +1,57 @@
= Sending and receiving IR (infra-red) codes using the PIO
This example shows how to use the Raspberry Pi Pico (RP2040) to send and receive infra-red frames in the 'NEC' format that is used by many consumer remote control applications.
It performs a loopback test by transmitting IR codes via an IR LED and receiving them on an IR detector. The results are sent to the default serial terminal connnected via UART or USB as configured in the SDK.
The tasks of encoding and decoding the data are offloaded to the RP2040 PIO state machines. This releases the main processor cores to concentrate on other tasks.
== Wiring information
Connect GPIO 14 to the positive side ('anode') of an IR LED via a suitable series resistor e.g. 1.5k Ohms, and the negative side ('cathode') to ground. The wavelength of the LED is unlikely to be critical so long as it is compatible with your detector.
Connect GPIO 15 to the output of a 3.3v IR detector such as the VS1838b, and wire the power connections of the detector to 3v3 and ground. The program expects the decoder output to be low (logic '0') when a carrier is detected.
[[pio_ir_loopback_wiring]]
[pdfwidth=75%]
.Wiring Diagram for IR loopback.
image::pio_ir_loopback.png[]
== Build information
The code is organised into three sub-directories. These contain the loopback example and two libraries for the IR transmit and receive functions.
After a successful build the executable program can be found in the **build/ir_loopback** directory.
== List of Files
CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree.
ir_loopback:: A directory containing the code for the loopback example.
CMakeLists.txt::: CMake file to incorporate the example in to the examples build tree.
ir_loopback.c::: The code for the loopback example.
nec_receive_library:: A directory containing the code for the IR receive functions.
CMakeLists.txt::: CMake file to incorporate the IR receive library in to the examples build tree.
nec_receive.c::: The source code for the IR receive functions.
nec_receive.h::: The headers for the IR receive functions.
nec_receive.pio::: The PIO assembler code to receive a frame.
nec_transmit_library:: A directory containing the code for the IR transmit functions.
CMakeLists.txt::: CMake file to incorporate the IR transmit library in to the examples build tree.
nec_transmit.c::: The source code for the IR transmit functions.
nec_transmit.h::: The headers for the IR transmit functions.
nec_carrier_burst.pio::: The PIO assembler code to generate a carrier burst.
nec_carrier_control.pio::: The PIO assembler code to transmit a complete frame.
== Bill of Materials
.A list of materials required for the example
[[pio_ir_loopback-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| Infra-Red LED | 1 | generic part
| 1500 ohm resistor | 1 | generic part
| Infra-Red Detector | 1 | generic part e.g. VS1838b
| M/M Jumper wires | 5 | generic part
|===

View File

@ -0,0 +1,15 @@
add_executable (pio_ir_loopback ir_loopback.c)
# link the executable using the IR transmit and receive libraries
#
target_link_libraries(pio_ir_loopback LINK_PUBLIC
pico_stdlib
hardware_pio
nec_transmit_library
nec_receive_library
)
pico_add_extra_outputs(pio_ir_loopback)
# add url via pico_set_program_url
example_auto_set_url(pio_ir_loopback)

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2021 mjcross
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "nec_transmit.h" // include the library headers
#include "nec_receive.h"
// Infrared loopback example ('NEC' format)
//
// Need to connect an IR LED to GPIO 14 via a suitable series resistor (e.g. 1.5k)
// and an active-low IR detector to GPIO 15 (e.g. VS1838b)
//
// Output is sent to stdout
int main() {
stdio_init_all();
PIO pio = pio0; // choose which PIO block to use (RP2040 has two: pio0 and pio1)
uint tx_gpio = 14; // choose which GPIO pin is connected to the IR LED
uint rx_gpio = 15; // choose which GPIO pin is connected to the IR detector
// configure and enable the state machines
int tx_sm = nec_tx_init(pio, tx_gpio); // uses two state machines, 16 instructions and one IRQ
int rx_sm = nec_rx_init(pio, rx_gpio); // uses one state machine and 9 instructions
if (tx_sm == -1 || rx_sm == -1) {
printf("could not configure PIO\n");
return -1;
}
// transmit and receive frames
uint8_t tx_address = 0x00, tx_data = 0x00, rx_address, rx_data;
while (true) {
// create a 32-bit frame and add it to the transmit FIFO
uint32_t tx_frame = nec_encode_frame(tx_address, tx_data);
pio_sm_put(pio, tx_sm, tx_frame);
printf("\nsent: %02x, %02x", tx_address, tx_data);
// allow time for the frame to be transmitted (optional)
sleep_ms(100);
// display any frames in the receive FIFO
while (!pio_sm_is_rx_fifo_empty(pio, rx_sm)) {
uint32_t rx_frame = pio_sm_get(pio, rx_sm);
if (nec_decode_frame(rx_frame, &rx_address, &rx_data)) {
printf("\treceived: %02x, %02x", rx_address, rx_data);
} else {
printf("\treceived: %08x", rx_frame);
}
}
sleep_ms(900);
tx_data += 1;
}
}

View File

@ -0,0 +1,19 @@
# build a normal library
#
add_library(nec_receive_library nec_receive.c)
# invoke pio_asm to assemble the state machine program
#
pico_generate_pio_header(nec_receive_library ${CMAKE_CURRENT_LIST_DIR}/nec_receive.pio)
target_link_libraries(nec_receive_library PRIVATE
pico_stdlib
hardware_pio
)
# add the `binary` directory so that the generated headers are included in the project
#
target_include_directories (nec_receive_library PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)

View File

@ -0,0 +1,79 @@
/**
* Copyright (c) 2021 mjcross
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// SDK types and declarations
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/clocks.h" // for clock_get_hz()
#include "nec_receive.h"
// import the assembled PIO state machine program
#include "nec_receive.pio.h"
// Claim an unused state machine on the specified PIO and configure it
// to receive NEC IR frames on the given GPIO pin.
//
// Returns: the state machine number on success, otherwise -1
int nec_rx_init(PIO pio, uint pin_num) {
// disable pull-up and pull-down on gpio pin
gpio_disable_pulls(pin_num);
// install the program in the PIO shared instruction space
uint offset;
if (pio_can_add_program(pio, &nec_receive_program)) {
offset = pio_add_program(pio, &nec_receive_program);
} else {
return -1; // the program could not be added
}
// claim an unused state machine on this PIO
int sm = pio_claim_unused_sm(pio, true);
if (sm == -1) {
return -1; // we were unable to claim a state machine
}
// configure and enable the state machine
nec_receive_program_init(pio, sm, offset, pin_num);
return sm;
}
// Validate a 32-bit frame and store the address and data at the locations
// provided.
//
// Returns: `true` if the frame was valid, otherwise `false`
bool nec_decode_frame(uint32_t frame, uint8_t *p_address, uint8_t *p_data) {
// access the frame data as four 8-bit fields
//
union {
uint32_t raw;
struct {
uint8_t address;
uint8_t inverted_address;
uint8_t data;
uint8_t inverted_data;
};
} f;
f.raw = frame;
// a valid (non-extended) 'NEC' frame should contain 8 bit
// address, inverted address, data and inverted data
if (f.address != (f.inverted_address ^ 0xff) ||
f.data != (f.inverted_data ^ 0xff)) {
return false;
}
// store the validated address and data
*p_address = f.address;
*p_data = f.data;
return true;
}

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) 2021 mjcross
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/stdlib.h"
#include "hardware/pio.h"
// public API
int nec_rx_init(PIO pio, uint pin);
bool nec_decode_frame(uint32_t sm, uint8_t *p_address, uint8_t *p_data);

View File

@ -0,0 +1,96 @@
;
; Copyright (c) 2021 mjcross
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program nec_receive
; Decode IR frames in NEC format and push 32-bit words to the input FIFO.
;
; The input pin should be connected to an IR detector with an 'active low' output.
;
; This program expects there to be 10 state machine clock ticks per 'normal' 562.5us burst period
; in order to permit timely detection of start of a burst. The initailisation function below sets
; the correct divisor to achive this relative to the system clock.
;
; Within the 'NEC' protocol frames consists of 32 bits sent least-siginificant bit first; so the
; Input Shift Register should be configured to shift right and autopush after 32 bits, as in the
; initialisation function below.
;
.define BURST_LOOP_COUNTER 30 ; the detection threshold for a 'frame sync' burst
.define BIT_SAMPLE_DELAY 15 ; how long to wait after the end of the burst before sampling
.wrap_target
next_burst:
set X, BURST_LOOP_COUNTER
wait 0 pin 0 ; wait for the next burst to start
burst_loop:
jmp pin data_bit ; the burst ended before the counter expired
jmp X-- burst_loop ; wait for the burst to end
; the counter expired - this is a sync burst
mov ISR, NULL ; reset the Input Shift Register
wait 1 pin 0 ; wait for the sync burst to finish
jmp next_burst ; wait for the first data bit
data_bit:
nop [ BIT_SAMPLE_DELAY - 1 ] ; wait for 1.5 burst periods before sampling the bit value
in PINS, 1 ; if the next burst has started then detect a '0' (short gap)
; otherwise detect a '1' (long gap)
; after 32 bits the ISR will autopush to the receive FIFO
.wrap
% c-sdk {
static inline void nec_receive_program_init (PIO pio, uint sm, uint offset, uint pin) {
// Set the GPIO function of the pin (connect the PIO to the pad)
//
pio_gpio_init(pio, pin);
// Set the pin direction to `input` at the PIO
//
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
// Create a new state machine configuration
//
pio_sm_config c = nec_receive_program_get_default_config (offset);
// configure the Input Shift Register
//
sm_config_set_in_shift (&c,
true, // shift right
true, // enable autopush
32); // autopush after 32 bits
// join the FIFOs to make a single large receive FIFO
//
sm_config_set_fifo_join (&c, PIO_FIFO_JOIN_RX);
// Map the IN pin group to one pin, namely the `pin`
// parameter to this function.
//
sm_config_set_in_pins (&c, pin);
// Map the JMP pin to the `pin` parameter of this function.
//
sm_config_set_jmp_pin (&c, pin);
// Set the clock divider to 10 ticks per 562.5us burst period
//
float div = clock_get_hz (clk_sys) / (10.0 / 526.6e-6);
sm_config_set_clkdiv (&c, div);
// Apply the configuration to the state machine
//
pio_sm_init (pio, sm, offset, &c);
// Set the state machine running
//
pio_sm_set_enabled (pio, sm, true);
}
%}

View File

@ -0,0 +1,20 @@
# build a normal library
#
add_library(nec_transmit_library nec_transmit.c)
# invoke pio_asm to assemble the PIO state machine programs
#
pico_generate_pio_header(nec_transmit_library ${CMAKE_CURRENT_LIST_DIR}/nec_carrier_burst.pio)
pico_generate_pio_header(nec_transmit_library ${CMAKE_CURRENT_LIST_DIR}/nec_carrier_control.pio)
target_link_libraries(nec_transmit_library PRIVATE
pico_stdlib
hardware_pio
)
# add the `binary` directory so that the generated headers are included in the project
#
target_include_directories (nec_transmit_library PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)

View File

@ -0,0 +1,61 @@
;
; Copyright (c) 2021 mjcross
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program nec_carrier_burst
; Generate bursts of carrier.
;
; Repeatedly wait for an IRQ to be set then clear it and generate 21 cycles of
; carrier with 25% duty cycle
;
.define NUM_CYCLES 21 ; how many carrier cycles to generate
.define BURST_IRQ 7 ; which IRQ should trigger a carrier burst
.define public TICKS_PER_LOOP 4 ; the number of instructions in the loop (for timing)
.wrap_target
set X, (NUM_CYCLES - 1) ; initialise the loop counter
wait 1 irq BURST_IRQ ; wait for the IRQ then clear it
cycle_loop:
set pins, 1 ; set the pin high (1 cycle)
set pins, 0 [1] ; set the pin low (2 cycles)
jmp X--, cycle_loop ; (1 more cycle)
.wrap
% c-sdk {
static inline void nec_carrier_burst_program_init(PIO pio, uint sm, uint offset, uint pin, float freq) {
// Create a new state machine configuration
//
pio_sm_config c = nec_carrier_burst_program_get_default_config (offset);
// Map the SET pin group to one pin, namely the `pin`
// parameter to this function.
//
sm_config_set_set_pins (&c, pin, 1);
// Set the GPIO function of the pin (connect the PIO to the pad)
//
pio_gpio_init (pio, pin);
// Set the pin direction to output at the PIO
//
pio_sm_set_consecutive_pindirs (pio, sm, pin, 1, true);
// Set the clock divider to generate the required frequency
//
float div = clock_get_hz (clk_sys) / (freq * nec_carrier_burst_TICKS_PER_LOOP);
sm_config_set_clkdiv (&c, div);
// Apply the configuration to the state machine
//
pio_sm_init (pio, sm, offset, &c);
// Set the state machine running
//
pio_sm_set_enabled (pio, sm, true);
}
%}

View File

@ -0,0 +1,79 @@
;
; Copyright (c) 2021 mjcross
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program nec_carrier_control
; Transmit an encoded 32-bit frame in NEC IR format.
;
; Accepts 32-bit words from the transmit FIFO and sends them least-significant bit first
; using pulse position modulation.
;
; Carrier bursts are generated using the nec_carrier_burst program, which is expected to be
; running on a separate state machine.
;
; This program expects there to be 2 state machine ticks per 'normal' 562.5us
; burst period.
;
.define BURST_IRQ 7 ; the IRQ used to trigger a carrier burst
.define NUM_INITIAL_BURSTS 16 ; how many bursts to transmit for a 'sync burst'
.wrap_target
pull ; fetch a data word from the transmit FIFO into the
; output shift register, blocking if the FIFO is empty
set X, (NUM_INITIAL_BURSTS - 1) ; send a sync burst (9ms)
long_burst:
irq BURST_IRQ
jmp X-- long_burst
nop [15] ; send a 4.5ms space
irq BURST_IRQ [1] ; send a 562.5us burst to begin the first data bit
data_bit:
out X, 1 ; shift the least-significant bit from the OSR
jmp !X burst ; send a short delay for a '0' bit
nop [3] ; send an additional delay for a '1' bit
burst:
irq BURST_IRQ ; send a 562.5us burst to end the data bit
jmp !OSRE data_bit ; continue sending bits until the OSR is empty
.wrap ; fetch another data word from the FIFO
% c-sdk {
static inline void nec_carrier_control_program_init (PIO pio, uint sm, uint offset, float tick_rate, int bits_per_frame) {
// create a new state machine configuration
//
pio_sm_config c = nec_carrier_control_program_get_default_config(offset);
// configure the output shift register
//
sm_config_set_out_shift (&c,
true, // shift right
false, // disable autopull
bits_per_frame);
// join the FIFOs to make a single large transmit FIFO
//
sm_config_set_fifo_join (&c, PIO_FIFO_JOIN_TX);
// configure the clock divider
//
float div = clock_get_hz (clk_sys) / tick_rate;
sm_config_set_clkdiv (&c, div);
// apply the configuration to the state machine
//
pio_sm_init(pio, sm, offset, &c);
// set the state machine running
//
pio_sm_set_enabled(pio, sm, true);
}
%}

View File

@ -0,0 +1,78 @@
/**
* Copyright (c) 2021 mjcross
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// SDK types and declarations
//
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/clocks.h" // for clock_get_hz()
#include "nec_transmit.h"
// import the assembled PIO state machine programs
#include "nec_carrier_burst.pio.h"
#include "nec_carrier_control.pio.h"
// Claim an unused state machine on the specified PIO and configure it
// to transmit NEC IR frames on the specificied GPIO pin.
//
// Returns: on success, the number of the carrier_control state machine
// otherwise -1
int nec_tx_init(PIO pio, uint pin_num) {
// install the carrier_burst program in the PIO shared instruction space
uint carrier_burst_offset;
if (pio_can_add_program(pio, &nec_carrier_burst_program)) {
carrier_burst_offset = pio_add_program(pio, &nec_carrier_burst_program);
} else {
return -1;
}
// claim an unused state machine on this PIO
int carrier_burst_sm = pio_claim_unused_sm(pio, true);
if (carrier_burst_sm == -1) {
return -1;
}
// configure and enable the state machine
nec_carrier_burst_program_init(pio,
carrier_burst_sm,
carrier_burst_offset,
pin_num,
38.222e3); // 38.222 kHz carrier
// install the carrier_control program in the PIO shared instruction space
uint carrier_control_offset;
if (pio_can_add_program(pio, &nec_carrier_control_program)) {
carrier_control_offset = pio_add_program(pio, &nec_carrier_control_program);
} else {
return -1;
}
// claim an unused state machine on this PIO
int carrier_control_sm = pio_claim_unused_sm(pio, true);
if (carrier_control_sm == -1) {
return -1;
}
// configure and enable the state machine
nec_carrier_control_program_init(pio,
carrier_control_sm,
carrier_control_offset,
2 * (1 / 562.5e-6f), // 2 ticks per 562.5us carrier burst
32); // 32 bits per frame
return carrier_control_sm;
}
// Create a frame in `NEC` format from the provided 8-bit address and data
//
// Returns: a 32-bit encoded frame
uint32_t nec_encode_frame(uint8_t address, uint8_t data) {
// a normal 32-bit frame is encoded as address, inverted address, data, inverse data,
return address | (address ^ 0xff) << 8 | data << 16 | (data ^ 0xff) << 24;
}

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) 2021 mjcross
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/stdlib.h"
#include "hardware/pio.h"
// public API
int nec_tx_init(PIO pio, uint pin);
uint32_t nec_encode_frame(uint8_t address, uint8_t data);

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

View File

@ -31,5 +31,5 @@ void blink_pin_forever(PIO pio, uint sm, uint offset, uint pin, uint freq) {
pio_sm_set_enabled(pio, sm, true);
printf("Blinking pin %d at %d Hz\n", pin, freq);
pio->txf[sm] = clock_get_hz(clk_sys) / 2 * freq;
pio->txf[sm] = clock_get_hz(clk_sys) / (2 * freq);
}

View File

@ -0,0 +1,18 @@
add_executable(pio_quadrature_encoder)
pico_generate_pio_header(pio_quadrature_encoder ${CMAKE_CURRENT_LIST_DIR}/quadrature_encoder.pio)
target_sources(pio_quadrature_encoder PRIVATE quadrature_encoder.c)
target_link_libraries(pio_quadrature_encoder PRIVATE
pico_stdlib
pico_multicore
hardware_pio
)
pico_enable_stdio_usb(pio_quadrature_encoder 1)
pico_add_extra_outputs(pio_quadrature_encoder)
# add url via pico_set_program_url
example_auto_set_url(pio_quadrature_encoder)

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2021 pmarques-dev @ github
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/timer.h"
#include "quadrature_encoder.pio.h"
//
// ---- quadrature encoder interface example
//
// the PIO program reads phase A/B of a quadrature encoder and increments or
// decrements an internal counter to keep the current absolute step count
// updated. At any point, the main code can query the current count by using
// the quadrature_encoder_*_count functions. The counter is kept in a full
// 32 bit register that just wraps around. Two's complement arithmetic means
// that it can be interpreted as a 32-bit signed or unsigned value, and it will
// work anyway.
//
// As an example, a two wheel robot being controlled at 100Hz, can use two
// state machines to read the two encoders and in the main control loop it can
// simply ask for the current encoder counts to get the absolute step count. It
// can also subtract the values from the last sample to check how many steps
// each wheel as done since the last sample period.
//
// One advantage of this approach is that it requires zero CPU time to keep the
// encoder count updated and because of that it supports very high step rates.
//
int main() {
int new_value, delta, old_value = 0;
// Base pin to connect the A phase of the encoder.
// The B phase must be connected to the next pin
const uint PIN_AB = 10;
stdio_init_all();
PIO pio = pio0;
const uint sm = 0;
uint offset = pio_add_program(pio, &quadrature_encoder_program);
quadrature_encoder_program_init(pio, sm, offset, PIN_AB, 0);
while (1) {
// note: thanks to two's complement arithmetic delta will always
// be correct even when new_value wraps around MAXINT / MININT
new_value = quadrature_encoder_get_count(pio, sm);
delta = new_value - old_value;
old_value = new_value;
printf("position %8d, delta %6d\n", new_value, delta);
sleep_ms(100);
}
}

Some files were not shown because too many files have changed in this diff Show More