Additional examples for specific h/w by our interns (#171)

adc/microphone_adc - Read analog values from a microphone and plot the measured sound amplitude.
i2c/bmp280_i2c - Read and convert temperature and pressure data from a BMP280 sensor, attached to an I2C bus.
i2c/lis3dh_i2c - Read acceleration and temperature value from a LIS3DH sensor via I2C
i2c/mcp9808_i2c - Read temperature, set limits and raise alerts when limits are surpassed.
i2c/mma8451_i2c - Read acceleration from a MMA8451 accelerometer and set range and precision for the data.
i2c/mpl3115a2_i2c - Interface with an MPL3115A2 altimeter, exploring interrupts and advanced board features, via I2C.
i2c/oled_i2c - Convert and display a bitmap on a 128x32 SSD1306-driven OLED display
i2c/pa1010d_i2c - Read GPS location data, parse and display data via I2C.
i2c/pcf8523_i2c - Read time and date values from a real time clock. Set current time and alarms on it.
uart/lcd_uart - Display text and symbols on a 16x02 RGB LCD display via UART
This commit is contained in:
Graham Sanderson 2021-10-25 12:30:57 -05:00 committed by GitHub
parent fabb762f75
commit 6e647c6f26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 2440 additions and 0 deletions

View File

@ -20,6 +20,7 @@ App|Description
[hello_adc](adc/hello_adc)|Display the voltage from an ADC input. [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. [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. [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 ### Clocks
@ -76,8 +77,16 @@ App|Description
App|Description App|Description
---|--- ---|---
[bus_scan](i2c/bus_scan) | Scan the I2C bus for devices and display results. [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. [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. [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 ### Interpolator
@ -172,6 +181,7 @@ App|Description
App|Description App|Description
---|--- ---|---
[hello_uart](uart/hello_uart) | Print some text from one of the UART serial ports, without going through `stdio`. [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. [uart_advanced](uart/uart_advanced) | Use some other UART features like RX interrupts, hardware control flow, and data formats other than 8n1.
### USB Device ### USB Device

View File

@ -3,4 +3,5 @@ if (NOT PICO_NO_HARDWARE)
add_subdirectory(dma_capture) add_subdirectory(dma_capture)
add_subdirectory(hello_adc) add_subdirectory(hello_adc)
add_subdirectory(joystick_display) add_subdirectory(joystick_display)
add_subdirectory(microphone_adc)
endif () 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

@ -1,5 +1,13 @@
if (NOT PICO_NO_HARDWARE) if (NOT PICO_NO_HARDWARE)
add_subdirectory(bmp280_i2c)
add_subdirectory(bus_scan) add_subdirectory(bus_scan)
add_subdirectory(lcd_1602_i2c) 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(mpu6050_i2c)
add_subdirectory(oled_i2c)
add_subdirectory(pa1010d_i2c)
add_subdirectory(pcf8523_i2c)
endif () 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();
// 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"));
#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
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

@ -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;
}
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);
}
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));
#endif
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);
}
}

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,126 @@
/**
* 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;
}
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);
}
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

@ -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();
// 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"));
#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
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
*/
// By default these devices are on bus address 0x68
static int addr = 0x68;
#ifdef i2c_default
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);
}
}
#endif
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));
}
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");
}
return 0;
}
#endif

View File

@ -1,4 +1,5 @@
if (NOT PICO_NO_HARDWARE) if (NOT PICO_NO_HARDWARE)
add_subdirectory(hello_uart) add_subdirectory(hello_uart)
add_subdirectory(lcd_uart)
add_subdirectory(uart_advanced) add_subdirectory(uart_advanced)
endif () endif ()

View File

@ -0,0 +1,17 @@
add_executable(lcd_uart
lcd_uart.c
)
# pull in common dependencies and additional uart hardware support
target_link_libraries(lcd_uart pico_stdlib hardware_uart)
# enable usb output and uart output
# modify here as required
pico_enable_stdio_usb(lcd_uart 1)
pico_enable_stdio_uart(lcd_uart 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(lcd_uart)
# add url via pico_set_program_url
example_auto_set_url(lcd_uart)

39
uart/lcd_uart/README.adoc Normal file
View File

@ -0,0 +1,39 @@
= Attaching a 16x2 LCD via TTL
This example code shows how to interface the Raspberry Pi Pico to one of the very common 16x2 LCD character displays. Due to the large number of pins these displays use, they are commonly used with extra drivers or backpacks. In this example, we will use an Adafruit LCD display backpack, which supports communication over USB or TTL. A monochrome display with an RGB backlight is also used, but the backpack is compatible with monochrome backlight displays too. There is another example that uses I2C to control a 16x2 display.
The backpack processes a set of commands that are documented https://learn.adafruit.com/usb-plus-serial-backpack/command-reference[here] and preceded by the "special" byte 0xFE. The backpack does the ASCII character conversion and even supports custom character creation. In this example, we use the Pico's primary UART (uart0) to read characters from our computer and send them via the other UART (uart1) to print them onto the LCD. We also define a special startup sequence and vary the display's backlight color.
NOTE: You can change where stdio output goes (Pico's USB, uart0 or both) with CMake directives. The CMakeLists.txt file shows how to enable both.
== Wiring information
Wiring up the backpack to the Pico requires 3 jumpers, to connect VCC (3.3v), GND, TX. The example here uses both of the Pico's UARTs, one (uart0) for stdio and the other (uart1) for communication with the backpack. Pin 8 is used as the TX pin. Power is supplied from the 3.3V pin. To connect the backpack to the display, it is common practice to solder it onto the back of the display, or during the prototyping stage to use the same parallel lanes on a breadboard.
NOTE: While this display will work at 3.3V, it will be quite dim. Using a 5V source will make it brighter.
[[lcd_uart_wiring]]
[pdfwidth=75%]
.Wiring Diagram for LCD with TTL backpack.
image::lcd_uart_bb.png[]
== List of Files
CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree.
lcd_uart.c:: The example code.
== Bill of Materials
.A list of materials required for the example
[[lcd_uart-bom-table]]
[cols=3]
|===
| *Item* | *Quantity* | Details
| Breadboard | 1 | generic part
| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/
| 16x2 RGB LCD panel 3.3v | 1 | generic part, https://www.adafruit.com/product/398[available on Adafruit]
| 16x2 LCD backpack | 1 | https://www.adafruit.com/product/781[from Adafruit]
| M/M Jumper wires | 3 | generic part
|===

173
uart/lcd_uart/lcd_uart.c Normal file
View File

@ -0,0 +1,173 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/* Example code to drive a 16x2 LCD panel via an Adafruit TTL LCD "backpack"
Optionally, the backpack can be connected the VBUS (pin 40) at 5V if
the Pico in question is powered by USB for greater brightness.
If this is done, then no other connections should be made to the backpack apart
from those listed below as the backpack's logic levels will change.
Connections on Raspberry Pi Pico board, other boards may vary.
GPIO 8 (pin 11)-> RX on backpack
3.3v (pin 36) -> 3.3v on backpack
GND (pin 38) -> GND on backpack
*/
#include <stdio.h>
#include <math.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/uart.h"
// leave uart0 free for stdio
#define UART_ID uart1
#define BAUD_RATE 9600
#define UART_TX_PIN 8
#define LCD_WIDTH 16
#define LCD_HEIGHT 2
// basic commands
#define LCD_DISPLAY_ON 0x42
#define LCD_DISPLAY_OFF 0x46
#define LCD_SET_BRIGHTNESS 0x99
#define LCD_SET_CONTRAST 0x50
#define LCD_AUTOSCROLL_ON 0x51
#define LCD_AUTOSCROLL_OFF 0x52
#define LCD_CLEAR_SCREEN 0x58
#define LCD_SET_SPLASH 0x40
// cursor commands
#define LCD_SET_CURSOR_POS 0x47
#define LCD_CURSOR_HOME 0x48
#define LCD_CURSOR_BACK 0x4C
#define LCD_CURSOR_FORWARD 0x4D
#define LCD_UNDERLINE_CURSOR_ON 0x4A
#define LCD_UNDERLINE_CURSOR_OFF 0x4B
#define LCD_BLOCK_CURSOR_ON 0x53
#define LCD_BLOCK_CURSOR_OFF 0x54
// rgb commands
#define LCD_SET_BACKLIGHT_COLOR 0xD0
#define LCD_SET_DISPLAY_SIZE 0xD1
// change to 0 if display is not RGB capable
#define LCD_IS_RGB 1
void lcd_write(uint8_t cmd, uint8_t* buf, uint8_t buflen) {
// all commands are prefixed with 0xFE
const uint8_t pre = 0xFE;
uart_write_blocking(UART_ID, &pre, 1);
uart_write_blocking(UART_ID, &cmd, 1);
uart_write_blocking(UART_ID, buf, buflen);
sleep_ms(10); // give the display some time
}
void lcd_set_size(uint8_t w, uint8_t h) {
// sets the dimensions of the display
uint8_t buf[] = { w, h };
lcd_write(LCD_SET_DISPLAY_SIZE, buf, 2);
}
void lcd_set_contrast(uint8_t contrast) {
// sets the display contrast
lcd_write(LCD_SET_CONTRAST, &contrast, 1);
}
void lcd_set_brightness(uint8_t brightness) {
// sets the backlight brightness
lcd_write(LCD_SET_BRIGHTNESS, &brightness, 1);
}
void lcd_set_cursor(bool is_on) {
// set is_on to true if we want the blinking block and underline cursor to show
if (is_on) {
lcd_write(LCD_BLOCK_CURSOR_ON, NULL, 0);
lcd_write(LCD_UNDERLINE_CURSOR_ON, NULL, 0);
} else {
lcd_write(LCD_BLOCK_CURSOR_OFF, NULL, 0);
lcd_write(LCD_UNDERLINE_CURSOR_OFF, NULL, 0);
}
}
void lcd_set_backlight(bool is_on) {
// turn the backlight on (true) or off (false)
if (is_on) {
lcd_write(LCD_DISPLAY_ON, (uint8_t *) 0, 1);
} else {
lcd_write(LCD_DISPLAY_OFF, NULL, 0);
}
}
void lcd_clear() {
// clear the contents of the display
lcd_write(LCD_CLEAR_SCREEN, NULL, 0);
}
void lcd_cursor_reset() {
// reset the cursor to (1, 1)
lcd_write(LCD_CURSOR_HOME, NULL, 0);
}
#if LCD_IS_RGB
void lcd_set_backlight_color(uint8_t r, uint8_t g, uint8_t b) {
// only supported on RGB displays!
uint8_t buf[] = { r, g, b };
lcd_write(LCD_SET_BACKLIGHT_COLOR, buf, 3);
}
#endif
void lcd_init() {
lcd_set_backlight(true);
lcd_set_size(LCD_WIDTH, LCD_HEIGHT);
lcd_set_contrast(155);
lcd_set_brightness(255);
lcd_set_cursor(false);
}
int main() {
stdio_init_all();
uart_init(UART_ID, BAUD_RATE);
uart_set_translate_crlf(UART_ID, false);
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
bi_decl(bi_1pin_with_func(UART_TX_PIN, GPIO_FUNC_UART));
lcd_init();
// define startup sequence and save to EEPROM
// no more or less than 32 chars, if not enough, fill remaining ones with spaces
uint8_t splash_buf[] = "Hello LCD, from Pi Towers! ";
lcd_write(LCD_SET_SPLASH, splash_buf, LCD_WIDTH * LCD_HEIGHT);
lcd_cursor_reset();
lcd_clear();
#if LCD_IS_RGB
uint8_t i = 0; // it's ok if this overflows and wraps, we're using sin
const float frequency = 0.1;
float red, green, blue;
#endif
while (1) {
// send any chars from stdio straight to the backpack
char c = uart_getc(uart_default);
// any bytes not followed by 0xFE (the special command) are interpreted
// as text to be displayed on the backpack, so we just send the char
// down the UART byte pipe!
if (c < 128) uart_putc_raw(UART_ID, c); // skip extra non-ASCII chars
#if LCD_IS_RGB
// change the display color on keypress, rainbow style!
red = sin(frequency * i + 0) * 127 + 128;
green = sin(frequency * i + 2) * 127 + 128;
blue = sin(frequency * i + 4) * 127 + 128;
lcd_set_backlight_color(red, green, blue);
i++;
#endif
}
}

BIN
uart/lcd_uart/lcd_uart.fzz Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB