diff --git a/README.md b/README.md index 3a5f4a6df..28a7fcd51 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ App|Description [hello_adc](adc/hello_adc)|Display the voltage from an ADC input. [joystick_display](adc/joystick_display)|Display a Joystick X/Y input based on two ADC inputs. [adc_console](adc/adc_console)|An interactive shell for playing with the ADC. Includes example of free-running capture mode. +[microphone_adc](adc/microphone_adc)|Read analog values from a microphone and plot the measured sound amplitude. ### Clocks @@ -76,8 +77,16 @@ App|Description App|Description ---|--- [bus_scan](i2c/bus_scan) | Scan the I2C bus for devices and display results. +[bmp280_i2c](i2c/bmp280_i2c) | Read and convert temperature and pressure data from a BMP280 sensor, attached to an I2C bus. [lcd_1602_i2c](i2c/lcd_1602_i2c) | Display some text on a generic 16x2 character LCD display, via I2C. +[mcp9808_i2c](i2c/mcp9808_i2c) | Read temperature, set limits and raise alerts when limits are surpassed. +[lis3dh_i2c](i2c/lis3dh_i2c) | Read acceleration and temperature value from a LIS3DH sensor via I2C +[mma8451_i2c](i2c/mma8451_i2c) | Read acceleration from a MMA8451 accelerometer and set range and precision for the data. +[mpl3115a2_i2c](i2c/mpl3115a2_i2c) | Interface with an MPL3115A2 altimeter, exploring interrupts and advanced board features, via I2C. [mpu6050_i2c](i2c/mpu6050_i2c) | Read acceleration and angular rate values from a MPU6050 accelerometer/gyro, attached to an I2C bus. +[oled_i2c](i2c/oled_i2c) | Convert and display a bitmap on a 128x32 SSD1306-driven OLED display +[pa1010d_i2c](i2c/pa1010d_i2c) | Read GPS location data, parse and display data via I2C. +[pcf8523_i2c](i2c/pcf8523_i2c) | Read time and date values from a real time clock. Set current time and alarms on it. ### Interpolator @@ -172,6 +181,7 @@ App|Description App|Description ---|--- [hello_uart](uart/hello_uart) | Print some text from one of the UART serial ports, without going through `stdio`. +[lcd_uart](uart/lcd_uart) | Display text and symbols on a 16x02 RGB LCD display via UART [uart_advanced](uart/uart_advanced) | Use some other UART features like RX interrupts, hardware control flow, and data formats other than 8n1. ### USB Device diff --git a/adc/CMakeLists.txt b/adc/CMakeLists.txt index 2fbb4fab0..bb5ca0938 100644 --- a/adc/CMakeLists.txt +++ b/adc/CMakeLists.txt @@ -3,4 +3,5 @@ if (NOT PICO_NO_HARDWARE) add_subdirectory(dma_capture) add_subdirectory(hello_adc) add_subdirectory(joystick_display) + add_subdirectory(microphone_adc) endif () diff --git a/adc/microphone_adc/CMakeLists.txt b/adc/microphone_adc/CMakeLists.txt new file mode 100644 index 000000000..87f2c4029 --- /dev/null +++ b/adc/microphone_adc/CMakeLists.txt @@ -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) diff --git a/adc/microphone_adc/README.adoc b/adc/microphone_adc/README.adoc new file mode 100644 index 000000000..853e3acda --- /dev/null +++ b/adc/microphone_adc/README.adoc @@ -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 | http://raspberrypi.com/ +| ICS-40180 microphone breakout board or similar | 1 | https://www.sparkfun.com/products/18011[From SparkFun] +| M/M Jumper wires | 3 | generic part +|=== + + diff --git a/adc/microphone_adc/microphone_adc.c b/adc/microphone_adc/microphone_adc.c new file mode 100644 index 000000000..88c19eb9e --- /dev/null +++ b/adc/microphone_adc/microphone_adc.c @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#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_PIN 0 +#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 + 26); + adc_select_input(ADC_PIN); + + 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; +} diff --git a/adc/microphone_adc/microphone_adc.fzz b/adc/microphone_adc/microphone_adc.fzz new file mode 100644 index 000000000..56b4b9f72 Binary files /dev/null and b/adc/microphone_adc/microphone_adc.fzz differ diff --git a/adc/microphone_adc/microphone_adc_bb.png b/adc/microphone_adc/microphone_adc_bb.png new file mode 100644 index 000000000..38e374cba Binary files /dev/null and b/adc/microphone_adc/microphone_adc_bb.png differ diff --git a/adc/microphone_adc/microphone_adc_plotter.png b/adc/microphone_adc/microphone_adc_plotter.png new file mode 100644 index 000000000..b10f20db6 Binary files /dev/null and b/adc/microphone_adc/microphone_adc_plotter.png differ diff --git a/adc/microphone_adc/plotter.py b/adc/microphone_adc/plotter.py new file mode 100755 index 000000000..4603baacd --- /dev/null +++ b/adc/microphone_adc/plotter.py @@ -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 +# 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() diff --git a/gpio/dht_sensor/README.adoc b/gpio/dht_sensor/README.adoc index 60cd9f874..57079597e 100644 --- a/gpio/dht_sensor/README.adoc +++ b/gpio/dht_sensor/README.adoc @@ -51,7 +51,7 @@ dht.c:: The example code. |=== | *Item* | *Quantity* | Details | Breadboard | 1 | generic part -| Raspberry Pi Pico | 1 | http://raspberrypi.org/ +| Raspberry Pi Pico | 1 | http://raspberrypi.com/ | 10 kΩ resistor | 1 | generic part | M/M Jumper wires | 4 | generic part | DHT-22 sensor | 1 | generic part diff --git a/gpio/hello_7segment/README.adoc b/gpio/hello_7segment/README.adoc index a6680c7d6..02c94d5d9 100644 --- a/gpio/hello_7segment/README.adoc +++ b/gpio/hello_7segment/README.adoc @@ -40,7 +40,7 @@ hello_7segment.c:: The example code. |=== | *Item* | *Quantity* | Details | Breadboard | 1 | generic part -| Raspberry Pi Pico | 1 | http://raspberrypi.org/ +| Raspberry Pi Pico | 1 | http://raspberrypi.com/ | 7 segment LED module | 1 | generic part | 68 ohm resistor | 7 | generic part | DIL push to make switch | 1 | generic switch diff --git a/i2c/CMakeLists.txt b/i2c/CMakeLists.txt index 080153f49..bb48b5b47 100644 --- a/i2c/CMakeLists.txt +++ b/i2c/CMakeLists.txt @@ -1,5 +1,13 @@ if (NOT PICO_NO_HARDWARE) + add_subdirectory(bmp280_i2c) add_subdirectory(bus_scan) add_subdirectory(lcd_1602_i2c) + add_subdirectory(mcp9808_i2c) + add_subdirectory(lis3dh_i2c) + add_subdirectory(mma8451_i2c) + add_subdirectory(mpl3115a2_i2c) add_subdirectory(mpu6050_i2c) + add_subdirectory(oled_i2c) + add_subdirectory(pa1010d_i2c) + add_subdirectory(pcf8523_i2c) endif () diff --git a/i2c/bmp280_i2c/CMakeLists.txt b/i2c/bmp280_i2c/CMakeLists.txt new file mode 100644 index 000000000..c63a7baba --- /dev/null +++ b/i2c/bmp280_i2c/CMakeLists.txt @@ -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) diff --git a/i2c/bmp280_i2c/README.adoc b/i2c/bmp280_i2c/README.adoc new file mode 100644 index 000000000..859814ad1 --- /dev/null +++ b/i2c/bmp280_i2c/README.adoc @@ -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 | http://raspberrypi.com/ +| 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 +|=== + + diff --git a/i2c/bmp280_i2c/bmp280_i2c.c b/i2c/bmp280_i2c/bmp280_i2c.c new file mode 100644 index 000000000..d7e17fd78 --- /dev/null +++ b/i2c/bmp280_i2c/bmp280_i2c.c @@ -0,0 +1,252 @@ +/** + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + **/ + +#include + +#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(¶ms); + + 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, ¶ms); + int32_t pressure = bmp280_convert_pressure(raw_pressure, raw_temperature, ¶ms); + 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; +} diff --git a/i2c/bmp280_i2c/bmp280_i2c.fzz b/i2c/bmp280_i2c/bmp280_i2c.fzz new file mode 100644 index 000000000..6384858e5 Binary files /dev/null and b/i2c/bmp280_i2c/bmp280_i2c.fzz differ diff --git a/i2c/bmp280_i2c/bmp280_i2c_bb.png b/i2c/bmp280_i2c/bmp280_i2c_bb.png new file mode 100644 index 000000000..dcd1e745d Binary files /dev/null and b/i2c/bmp280_i2c/bmp280_i2c_bb.png differ diff --git a/i2c/lcd_1602_i2c/README.adoc b/i2c/lcd_1602_i2c/README.adoc index f6a6d8b1e..5708a9348 100644 --- a/i2c/lcd_1602_i2c/README.adoc +++ b/i2c/lcd_1602_i2c/README.adoc @@ -32,7 +32,7 @@ lcd_1602_i2c.c:: The example code. |=== | *Item* | *Quantity* | Details | Breadboard | 1 | generic part -| Raspberry Pi Pico | 1 | http://raspberrypi.org/ +| Raspberry Pi Pico | 1 | http://raspberrypi.com/ | 1602A based LCD panel 3.3v | 1 | generic part | 1602A to I2C bridge device 3.3v | 1 | generic part | M/M Jumper wires | 4 | generic part diff --git a/i2c/lis3dh_i2c/CMakeLists.txt b/i2c/lis3dh_i2c/CMakeLists.txt new file mode 100644 index 000000000..0b23c9043 --- /dev/null +++ b/i2c/lis3dh_i2c/CMakeLists.txt @@ -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) diff --git a/i2c/lis3dh_i2c/README.adoc b/i2c/lis3dh_i2c/README.adoc new file mode 100644 index 000000000..553ecd0b6 --- /dev/null +++ b/i2c/lis3dh_i2c/README.adoc @@ -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 | http://raspberrypi.org/ +| LIS3DH board| 1 | https://www.adafruit.com/product/2809 +| M/M Jumper wires | 4 | generic part +|=== diff --git a/i2c/lis3dh_i2c/lis3dh_i2c.c b/i2c/lis3dh_i2c/lis3dh_i2c.c new file mode 100644 index 000000000..c5b81838c --- /dev/null +++ b/i2c/lis3dh_i2c/lis3dh_i2c.c @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#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.004; // g per unit + + if (isAccel == true){ + scaling = 64 / senstivity; + } else { + scaling = 64; + } + + // Check if value is < 0 and convert to decimal accordingly + if ((raw_value & 0x8000) == 0x8000) { + raw_value &= 0x7FFF; + *final_value = (-32768 + (float)raw_value) / scaling; + } else { + *final_value = (float)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,®, 1 ,true); + i2c_read_blocking(i2c_default, ADDRESS,&lsb, 1 ,false); + + reg |= 0x01; + i2c_write_blocking(i2c_default, ADDRESS, ®, 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; +} diff --git a/i2c/lis3dh_i2c/lis3dh_i2c.fzz b/i2c/lis3dh_i2c/lis3dh_i2c.fzz new file mode 100644 index 000000000..bdb9ba5b9 Binary files /dev/null and b/i2c/lis3dh_i2c/lis3dh_i2c.fzz differ diff --git a/i2c/lis3dh_i2c/lis3dh_i2c.png b/i2c/lis3dh_i2c/lis3dh_i2c.png new file mode 100644 index 000000000..29e9cfed9 Binary files /dev/null and b/i2c/lis3dh_i2c/lis3dh_i2c.png differ diff --git a/i2c/mcp9808_i2c/CMakeLists.txt b/i2c/mcp9808_i2c/CMakeLists.txt new file mode 100644 index 000000000..4ee1b4975 --- /dev/null +++ b/i2c/mcp9808_i2c/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/i2c/mcp9808_i2c/README.adoc b/i2c/mcp9808_i2c/README.adoc new file mode 100644 index 000000000..f59954bf1 --- /dev/null +++ b/i2c/mcp9808_i2c/README.adoc @@ -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_bb.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 | http://raspberrypi.org/ +| MCP9808 board| 1 | https://www.adafruit.com/product/1782 +| M/M Jumper wires | 4 | generic part +|=== + + diff --git a/i2c/mcp9808_i2c/mcp9808_i2c.c b/i2c/mcp9808_i2c/mcp9808_i2c.c new file mode 100644 index 000000000..f4beabc1e --- /dev/null +++ b/i2c/mcp9808_i2c/mcp9808_i2c.c @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#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, ®_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); + } +} diff --git a/i2c/mcp9808_i2c/mcp9808_i2c.fzz b/i2c/mcp9808_i2c/mcp9808_i2c.fzz new file mode 100644 index 000000000..de373df5d Binary files /dev/null and b/i2c/mcp9808_i2c/mcp9808_i2c.fzz differ diff --git a/i2c/mcp9808_i2c/mcp9808_i2c.png b/i2c/mcp9808_i2c/mcp9808_i2c.png new file mode 100644 index 000000000..c15a3a3bd Binary files /dev/null and b/i2c/mcp9808_i2c/mcp9808_i2c.png differ diff --git a/i2c/mma8451_i2c/CMakeLists.txt b/i2c/mma8451_i2c/CMakeLists.txt new file mode 100644 index 000000000..f056974da --- /dev/null +++ b/i2c/mma8451_i2c/CMakeLists.txt @@ -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) diff --git a/i2c/mma8451_i2c/README.adoc b/i2c/mma8451_i2c/README.adoc new file mode 100644 index 000000000..9c33ee735 --- /dev/null +++ b/i2c/mma8451_i2c/README.adoc @@ -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 | http://raspberrypi.com/ +| MMA8451 board| 1 | https://www.adafruit.com/product/2019 +| M/M Jumper wires | 4 | generic part +|=== + + diff --git a/i2c/mma8451_i2c/mma8451_i2c.c b/i2c/mma8451_i2c/mma8451_i2c.c new file mode 100644 index 000000000..467cc90b4 --- /dev/null +++ b/i2c/mma8451_i2c/mma8451_i2c.c @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#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.81; + 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, ®_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, ®_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, ®_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 +} diff --git a/i2c/mma8451_i2c/mma8451_i2c.fzz b/i2c/mma8451_i2c/mma8451_i2c.fzz new file mode 100644 index 000000000..9e4bf5ec3 Binary files /dev/null and b/i2c/mma8451_i2c/mma8451_i2c.fzz differ diff --git a/i2c/mma8451_i2c/mma8451_i2c.png b/i2c/mma8451_i2c/mma8451_i2c.png new file mode 100644 index 000000000..b55d24df4 Binary files /dev/null and b/i2c/mma8451_i2c/mma8451_i2c.png differ diff --git a/i2c/mpl3115a2_i2c/CMakeLists.txt b/i2c/mpl3115a2_i2c/CMakeLists.txt new file mode 100644 index 000000000..db9a5d7b6 --- /dev/null +++ b/i2c/mpl3115a2_i2c/CMakeLists.txt @@ -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) diff --git a/i2c/mpl3115a2_i2c/README.adoc b/i2c/mpl3115a2_i2c/README.adoc new file mode 100644 index 000000000..4a78a8976 --- /dev/null +++ b/i2c/mpl3115a2_i2c/README.adoc @@ -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 | http://raspberrypi.com/ +| MPL3115A2 altimeter | 1 | https://www.adafruit.com/product/1893[Adafruit] +| M/M Jumper wires | 5 | generic part +|=== diff --git a/i2c/mpl3115a2_i2c/mpl3115a2_i2c.c b/i2c/mpl3115a2_i2c/mpl3115a2_i2c.c new file mode 100644 index 000000000..30baeeaba --- /dev/null +++ b/i2c/mpl3115a2_i2c/mpl3115a2_i2c.c @@ -0,0 +1,203 @@ +/** + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#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, ®, 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, ®, 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 = h * 1.f / 65536; + + // 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 = t * 1.f / 256; +} + +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; +} diff --git a/i2c/mpl3115a2_i2c/mpl3115a2_i2c.fzz b/i2c/mpl3115a2_i2c/mpl3115a2_i2c.fzz new file mode 100644 index 000000000..f2c044f64 Binary files /dev/null and b/i2c/mpl3115a2_i2c/mpl3115a2_i2c.fzz differ diff --git a/i2c/mpl3115a2_i2c/mpl3115a2_i2c_bb.png b/i2c/mpl3115a2_i2c/mpl3115a2_i2c_bb.png new file mode 100644 index 000000000..090f7ea2a Binary files /dev/null and b/i2c/mpl3115a2_i2c/mpl3115a2_i2c_bb.png differ diff --git a/i2c/mpu6050_i2c/README.adoc b/i2c/mpu6050_i2c/README.adoc index f0fde5130..09ccac4f8 100644 --- a/i2c/mpu6050_i2c/README.adoc +++ b/i2c/mpu6050_i2c/README.adoc @@ -35,7 +35,7 @@ mpu6050_i2c.c:: The example code. |=== | *Item* | *Quantity* | Details | Breadboard | 1 | generic part -| Raspberry Pi Pico | 1 | http://raspberrypi.org/ +| Raspberry Pi Pico | 1 | http://raspberrypi.com/ | MPU6050 board| 1 | generic part | M/M Jumper wires | 4 | generic part |=== diff --git a/i2c/oled_i2c/CMakeLists.txt b/i2c/oled_i2c/CMakeLists.txt new file mode 100644 index 000000000..f4d2a261b --- /dev/null +++ b/i2c/oled_i2c/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(oled_i2c + oled_i2c.c + ) + +# Pull in our get you started dependencies +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) diff --git a/i2c/oled_i2c/README.adoc b/i2c/oled_i2c/README.adoc new file mode 100644 index 000000000..f18996173 --- /dev/null +++ b/i2c/oled_i2c/README.adoc @@ -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 | http://raspberrypi.com/ +| SSD1306-based OLED display | 1 | https://www.adafruit.com/product/4440[Adafruit part] +| M/M Jumper wires | 4 | generic part +|=== diff --git a/i2c/oled_i2c/img_to_array.py b/i2c/oled_i2c/img_to_array.py new file mode 100755 index 000000000..b90b40230 --- /dev/null +++ b/i2c/oled_i2c/img_to_array.py @@ -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 + +# 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) diff --git a/i2c/oled_i2c/oled_i2c.c b/i2c/oled_i2c/oled_i2c.c new file mode 100644 index 000000000..2a8c984cd --- /dev/null +++ b/i2c/oled_i2c/oled_i2c.c @@ -0,0 +1,297 @@ +/** + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#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; +} diff --git a/i2c/oled_i2c/oled_i2c.fzz b/i2c/oled_i2c/oled_i2c.fzz new file mode 100644 index 000000000..cdbb271b0 Binary files /dev/null and b/i2c/oled_i2c/oled_i2c.fzz differ diff --git a/i2c/oled_i2c/oled_i2c_bb.png b/i2c/oled_i2c/oled_i2c_bb.png new file mode 100644 index 000000000..29773ded8 Binary files /dev/null and b/i2c/oled_i2c/oled_i2c_bb.png differ diff --git a/i2c/oled_i2c/raspberry26x32.bmp b/i2c/oled_i2c/raspberry26x32.bmp new file mode 100644 index 000000000..9f2f88118 Binary files /dev/null and b/i2c/oled_i2c/raspberry26x32.bmp differ diff --git a/i2c/oled_i2c/raspberry26x32.h b/i2c/oled_i2c/raspberry26x32.h new file mode 100644 index 000000000..a1b632f55 --- /dev/null +++ b/i2c/oled_i2c/raspberry26x32.h @@ -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}; diff --git a/i2c/pa1010d_i2c/CMakeLists.txt b/i2c/pa1010d_i2c/CMakeLists.txt new file mode 100644 index 000000000..b04fac23f --- /dev/null +++ b/i2c/pa1010d_i2c/CMakeLists.txt @@ -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) diff --git a/i2c/pa1010d_i2c/README.adoc b/i2c/pa1010d_i2c/README.adoc new file mode 100644 index 000000000..48877c433 --- /dev/null +++ b/i2c/pa1010d_i2c/README.adoc @@ -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 | http://raspberrypi.org/ +| PA1010D board| 1 | https://shop.pimoroni.com/products/pa1010d-gps-breakout +| M/M Jumper wires | 4 | generic part +|=== + diff --git a/i2c/pa1010d_i2c/pa1010d_i2c.c b/i2c/pa1010d_i2c/pa1010d_i2c.c new file mode 100644 index 000000000..7e895e285 --- /dev/null +++ b/i2c/pa1010d_i2c/pa1010d_i2c.c @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#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(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; +} diff --git a/i2c/pa1010d_i2c/pa1010d_i2c.fzz b/i2c/pa1010d_i2c/pa1010d_i2c.fzz new file mode 100644 index 000000000..94816035e Binary files /dev/null and b/i2c/pa1010d_i2c/pa1010d_i2c.fzz differ diff --git a/i2c/pa1010d_i2c/pa1010d_i2c.png b/i2c/pa1010d_i2c/pa1010d_i2c.png new file mode 100644 index 000000000..b04de724a Binary files /dev/null and b/i2c/pa1010d_i2c/pa1010d_i2c.png differ diff --git a/i2c/pcf8523_i2c/CMakeLists.txt b/i2c/pcf8523_i2c/CMakeLists.txt new file mode 100644 index 000000000..8d47635b3 --- /dev/null +++ b/i2c/pcf8523_i2c/CMakeLists.txt @@ -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) diff --git a/i2c/pcf8523_i2c/README.adoc b/i2c/pcf8523_i2c/README.adoc new file mode 100644 index 000000000..587d53afd --- /dev/null +++ b/i2c/pcf8523_i2c/README.adoc @@ -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::pcf8523_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 | http://raspberrypi.org/ +| PCF8523 board| 1 | https://www.adafruit.com/product/3295 +| M/M Jumper wires | 4 | generic part +|=== + diff --git a/i2c/pcf8523_i2c/pc8523_i2c.fzz b/i2c/pcf8523_i2c/pc8523_i2c.fzz new file mode 100644 index 000000000..4468c176a Binary files /dev/null and b/i2c/pcf8523_i2c/pc8523_i2c.fzz differ diff --git a/i2c/pcf8523_i2c/pc8523_i2c.png b/i2c/pcf8523_i2c/pc8523_i2c.png new file mode 100644 index 000000000..f62ddbd1e Binary files /dev/null and b/i2c/pcf8523_i2c/pc8523_i2c.png differ diff --git a/i2c/pcf8523_i2c/pcf8523_i2c.c b/i2c/pcf8523_i2c/pcf8523_i2c.c new file mode 100644 index 000000000..3120e29f9 --- /dev/null +++ b/i2c/pcf8523_i2c/pcf8523_i2c.c @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#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],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 \ No newline at end of file diff --git a/pio/CMakeLists.txt b/pio/CMakeLists.txt index 8855161ff..fef60d1f8 100644 --- a/pio/CMakeLists.txt +++ b/pio/CMakeLists.txt @@ -6,6 +6,7 @@ if (NOT PICO_NO_HARDWARE) add_subdirectory(hello_pio) add_subdirectory(hub75) add_subdirectory(i2c) + add_subdirectory(i2s_microphone) add_subdirectory(logic_analyser) add_subdirectory(manchester_encoding) add_subdirectory(pio_blink) diff --git a/pio/i2s_microphone/CMakeLists.txt b/pio/i2s_microphone/CMakeLists.txt new file mode 100644 index 000000000..909027ad7 --- /dev/null +++ b/pio/i2s_microphone/CMakeLists.txt @@ -0,0 +1,15 @@ +add_executable(i2s_microphone) + +pico_generate_pio_header(i2s_microphone ${CMAKE_CURRENT_LIST_DIR}/i2s_microphone.pio) + +target_sources(i2s_microphone PRIVATE i2s_microphone.c) + +target_link_libraries(i2s_microphone PRIVATE + pico_stdlib + hardware_pio + ) + +pico_add_extra_outputs(i2s_microphone) + +# add url via pico_set_program_url +example_auto_set_url(i2s_microphone) diff --git a/pio/i2s_microphone/i2s_microphone.c b/pio/i2s_microphone/i2s_microphone.c new file mode 100644 index 000000000..9193263d3 --- /dev/null +++ b/pio/i2s_microphone/i2s_microphone.c @@ -0,0 +1,23 @@ +#include +#include + +#include "pico/stdlib.h" +#include "hardware/pio.h" +#include "i2s_microphone.pio.h" + +#define PIO_INPUT_PIN_BASE 12 +#define PIO_OUTPUT_PIN_BASE 10 + +int main() { + stdio_init_all(); + + // Load the i2s_microphone program, and configure a free state machine + // to run the program. + PIO pio = pio0; + uint offset = pio_add_program(pio, &i2s_microphone_program); + uint sm = pio_claim_unused_sm(pio, true); + i2s_microphone_program_init(pio, sm, offset, PIO_OUTPUT_PIN_BASE, PIO_INPUT_PIN_BASE); + + while (1); + +} diff --git a/pio/i2s_microphone/i2s_microphone.pio b/pio/i2s_microphone/i2s_microphone.pio new file mode 100644 index 000000000..ad70c18eb --- /dev/null +++ b/pio/i2s_microphone/i2s_microphone.pio @@ -0,0 +1,45 @@ +; +; Copyright (c) 2021 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +; I2S microphone data format is 24 bits with 32 bit words for WS toggling +; ws_toggle branch takes 2 cycles +; loop takes 30 cycles +; note: we don't read data during the WS toggling but this is OK as our data is 24 bits long + +.program i2s_microphone + +.side_set 1 ; BCLK pin + +ws_toggle: + mov y, !y side 1 + mov osr, y side 0 + set x, 29 side 1 + out pins, 1 side 0 +loop: + in pins, 1 side 1 + jmp x-- loop side 0 + +% c-sdk { +static inline void i2s_microphone_program_init(PIO pio, uint sm, uint offset, uint clock_pin_base, uint data_pin) { + pio_gpio_init(pio, clock_pin_base); + pio_gpio_init(pio, clock_pin_base + 1); + pio_sm_set_consecutive_pindirs(pio, sm, clock_pin_base, 2, true); + pio_sm_set_pins(pio, sm, 0); + pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, false); + + pio_sm_config c = i2s_microphone_program_get_default_config(offset); + sm_config_set_clkdiv_int_frac(&c, 30, 128); + sm_config_set_sideset_pins(&c, clock_pin_base); + sm_config_set_out_pins(&c, clock_pin_base + 1, 1); + sm_config_set_out_shift(&c, true, false, 32); + sm_config_set_in_shift(&c, false, true, 30); + sm_config_set_in_pins(&c, data_pin); + + pio_sm_init(pio, sm, offset, &c); + + pio_sm_set_enabled(pio, sm, true); +} +%} diff --git a/spi/bme280_spi/README.adoc b/spi/bme280_spi/README.adoc index a96b2c1f5..5921ef526 100644 --- a/spi/bme280_spi/README.adoc +++ b/spi/bme280_spi/README.adoc @@ -42,7 +42,7 @@ bme280_spi.c:: The example code. |=== | *Item* | *Quantity* | Details | Breadboard | 1 | generic part -| Raspberry Pi Pico | 1 | http://raspberrypi.org/ +| Raspberry Pi Pico | 1 | http://raspberrypi.com/ | BME280 board| 1 | generic part | M/M Jumper wires | 6 | generic part |=== diff --git a/spi/mpu9250_spi/README.adoc b/spi/mpu9250_spi/README.adoc index 9e819123f..b83d09b76 100644 --- a/spi/mpu9250_spi/README.adoc +++ b/spi/mpu9250_spi/README.adoc @@ -44,7 +44,7 @@ mpu9250_spi.c:: The example code. |=== | *Item* | *Quantity* | Details | Breadboard | 1 | generic part -| Raspberry Pi Pico | 1 | http://raspberrypi.org/ +| Raspberry Pi Pico | 1 | http://raspberrypi.com/ | MPU9250 board| 1 | generic part | M/M Jumper wires | 6 | generic part |=== diff --git a/uart/CMakeLists.txt b/uart/CMakeLists.txt index 2ca582b94..ab378f754 100644 --- a/uart/CMakeLists.txt +++ b/uart/CMakeLists.txt @@ -1,4 +1,5 @@ if (NOT PICO_NO_HARDWARE) add_subdirectory(hello_uart) + add_subdirectory(lcd_uart) add_subdirectory(uart_advanced) endif () diff --git a/uart/lcd_uart/CMakeLists.txt b/uart/lcd_uart/CMakeLists.txt new file mode 100644 index 000000000..64098821f --- /dev/null +++ b/uart/lcd_uart/CMakeLists.txt @@ -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) diff --git a/uart/lcd_uart/README.adoc b/uart/lcd_uart/README.adoc new file mode 100644 index 000000000..e8caa5c1b --- /dev/null +++ b/uart/lcd_uart/README.adoc @@ -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 | http://raspberrypi.com/ +| 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 +|=== + + diff --git a/uart/lcd_uart/lcd_uart.c b/uart/lcd_uart/lcd_uart.c new file mode 100644 index 000000000..3cefe45df --- /dev/null +++ b/uart/lcd_uart/lcd_uart.c @@ -0,0 +1,174 @@ +/** + * 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 +#include +#include "pico/stdlib.h" +#include "hardware/uart.h" +#include "pico/binary_info.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 + } +} diff --git a/uart/lcd_uart/lcd_uart.fzz b/uart/lcd_uart/lcd_uart.fzz new file mode 100644 index 000000000..9c79e432f Binary files /dev/null and b/uart/lcd_uart/lcd_uart.fzz differ diff --git a/uart/lcd_uart/lcd_uart_bb.png b/uart/lcd_uart/lcd_uart_bb.png new file mode 100644 index 000000000..37de95edb Binary files /dev/null and b/uart/lcd_uart/lcd_uart_bb.png differ