diff --git a/CMakeLists.txt b/CMakeLists.txt index 66add69e9..fe1c22545 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,10 @@ project(pico_examples C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +endif() + set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR}) # Initialize the SDK diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..98da77ef7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing to Raspberry Pi Pico C/C++ Examples + +## How to Report a Bug + +We use GitHub to host code, track [issues](https://github.com/raspberrypi/pico-examples/issues) and feature requests, and to accept [pull requests](https://github.com/raspberrypi/pico-examples/pulls). If you find think you have found a bug, please report it by [opening a new issue](https://github.com/raspberrypi/pico-examples/issues/new). Please include as much detail as possible, and ideally some code to reproduce the problem. + +## How to Contribute Code + +In order to contribute new or updated code, you must first create a GitHub account and fork the original repository to your own account. You can make changes, save them in your repository, then [make a pull request](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) against this repository. The pull request will appear [in the repository](https://github.com/raspberrypi/pico-examples/pulls) where it can be assessed by the maintainers, and if appropriate, merged with the official repository. + +**NOTE:** Development takes place on the `develop` branch in this repository. Please open your https://github.com/raspberrypi/pico-examples/pulls[pull request] (PR) against the [`develop`](https://github.com/raspberrypi/pico-examples/tree/develop) branch, pull requests against the `master` branch will automatically CI fail checks and will not be accepted. You will be asked to rebase your PR against `develop` and if you do not do so, your PR will be closed. + +### Code Style + +If you are contributing new or updated code please match the existing code style, particularly: + +* Use 4 spaces for indentation rather than tabs. +* Braces are required for everything except single line `if` statements. +* Opening braces should not be placed on a new line. + +### Licensing + +Code in this repository is lisensed under the [BSD-3 License](LICENSE.TXT). By contributing content to this repository you are agreeing to place your contributions under this licence. diff --git a/README.md b/README.md index 3a5f4a6df..26898a47a 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. +[lis3dh_i2c](i2c/lis3dh_i2c) | Read acceleration and temperature value from a LIS3DH sensor via I2C +[mcp9808_i2c](i2c/mcp9808_i2c) | Read temperature, set limits and raise alerts when limits are surpassed. +[mma8451_i2c](i2c/mma8451_i2c) | Read acceleration from a MMA8451 accelerometer and set range and precision for the data. +[mpl3115a2_i2c](i2c/mpl3115a2_i2c) | Interface with an MPL3115A2 altimeter, exploring interrupts and advanced board features, via I2C. [mpu6050_i2c](i2c/mpu6050_i2c) | Read acceleration and angular rate values from a MPU6050 accelerometer/gyro, attached to an I2C bus. +[oled_i2c](i2c/oled_i2c) | Convert and display a bitmap on a 128x32 SSD1306-driven OLED display +[pa1010d_i2c](i2c/pa1010d_i2c) | Read GPS location data, parse and display data via I2C. +[pcf8523_i2c](i2c/pcf8523_i2c) | Read time and date values from a real time clock. Set current time and alarms on it. ### Interpolator @@ -109,6 +118,7 @@ App|Description [differential_manchester](pio/differential_manchester)| Send and receive differential Manchester-encoded serial (BMC). [hub75](pio/hub75)| Display an image on a 128x64 HUB75 RGB LED matrix. [i2c](pio/i2c)| Scan an I2C bus. +[ir_nec](pio/ir_nec)| Sending and receiving IR (infra-red) codes using the PIO. [logic_analyser](pio/logic_analyser)| Use PIO and DMA to capture a logic trace of some GPIOs, whilst a PWM unit is driving them. [manchester_encoding](pio/manchester_encoding)| Send and receive Manchester-encoded serial. [pio_blink](pio/pio_blink)| Set up some PIO state machines to blink LEDs at different frequencies, according to delay counts pushed into their FIFOs. @@ -116,6 +126,7 @@ App|Description [spi](pio/spi)| Use PIO to erase, program and read an external SPI flash chip. A second example runs a loopback test with all four CPHA/CPOL combinations. [squarewave](pio/squarewave)| Drive a fast square wave onto a GPIO. This example accesses low-level PIO registers directly, instead of using the SDK functions. [st7789_lcd](pio/st7789_lcd)| Set up PIO for 62.5 Mbps serial output, and use this to display a spinning image on a ST7789 serial LCD. +[quadrature_encoder](pio/quadrature_encoder)| A quadrature encoder using PIO to maintain counts independent of the CPU. [uart_rx](pio/uart_rx)| Implement the receive component of a UART serial port. Attach it to the spare Arm UART to see it receive characters. [uart_tx](pio/uart_tx)| Implement the transmit component of a UART serial port, and print hello world. [ws2812](pio/ws2812)| Examples of driving WS2812 addressable RGB LEDs. @@ -172,13 +183,14 @@ App|Description App|Description ---|--- [hello_uart](uart/hello_uart) | Print some text from one of the UART serial ports, without going through `stdio`. +[lcd_uart](uart/lcd_uart) | Display text and symbols on a 16x02 RGB LCD display via UART [uart_advanced](uart/uart_advanced) | Use some other UART features like RX interrupts, hardware control flow, and data formats other than 8n1. ### USB Device #### TinyUSB Examples -All but one of the USB device examples come directly from the TinyUSB device examples directory [here](https://github.com/hathach/tinyusb/tree/master/examples/device). +Most of the USB device examples come directly from the TinyUSB device examples directory [here](https://github.com/hathach/tinyusb/tree/master/examples/device). Those that are supported on RP2040 devices are automatically included as part of the pico-examples build as targets named `tinyusb_dev_`, e.g. https://github.com/hathach/tinyusb/tree/master/examples/device/hid_composite is built as `tinyusb_dev_hid_composite`. @@ -198,11 +210,24 @@ At the time of writing, these examples are available: - tinyusb_dev_hid_multiple_interface - tinyusb_dev_midi_test - tinyusb_dev_msc_dual_lun +- tinyusb_dev_net_lwip_webserver - tinyusb_dev_uac2_headset - tinyusb_dev_usbtmc +- tinyusb_dev_video_capture - tinyusb_dev_webusb_serial -#### Low Level examples +Whilst these examples ably demonstrate how to use TinyUSB in device mode, their `CMakeLists.txt` is set up in a way +tailored to how TinyUSB builds their examples within their source tree. + +For a better example of how to configure `CMakeLists.txt` for using TinyUSB in device mode with the Raspberry Pi SDK +see below: + +#### SDK build example +App|Description +---|--- +[dev_hid_composite](usb/device/dev_hid_composite) | A copy of the TinyUSB device example with the same name, but with a CMakeLists.txt which demonstrates how to add a dependency on the TinyUSB device libraries with the Raspberry Pi Pico SDK + +#### Low Level example App|Description ---|--- [dev_lowlevel](usb/device/dev_lowlevel) | A USB Bulk loopback implemented with direct access to the USB hardware (no TinyUSB) 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..7a9b36c7a --- /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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| ICS-40180 microphone breakout board or similar | 1 | https://www.sparkfun.com/products/18011[From SparkFun] +| M/M Jumper wires | 3 | generic part +|=== + + diff --git a/adc/microphone_adc/microphone_adc.c b/adc/microphone_adc/microphone_adc.c new file mode 100644 index 000000000..2831e9b7f --- /dev/null +++ b/adc/microphone_adc/microphone_adc.c @@ -0,0 +1,49 @@ +/** + * 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_NUM 0 +#define ADC_PIN (26 + ADC_NUM) +#define ADC_VREF 3.3 +#define ADC_RANGE (1 << 12) +#define ADC_CONVERT (ADC_VREF / (ADC_RANGE - 1)) + +int main() { + stdio_init_all(); + printf("Beep boop, listening...\n"); + + bi_decl(bi_program_description("Analog microphone example for Raspberry Pi Pico")); // for picotool + bi_decl(bi_1pin_with_name(ADC_PIN, "ADC input pin")); + + adc_init(); + adc_gpio_init( ADC_PIN); + adc_select_input( ADC_NUM); + + uint adc_raw; + while (1) { + adc_raw = adc_read(); // raw voltage from ADC + printf("%.2f\n", adc_raw * ADC_CONVERT); + sleep_ms(10); + } + + return 0; +} 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/blink/CMakeLists.txt b/blink/CMakeLists.txt index 1bf1d2a4f..63ffbd344 100644 --- a/blink/CMakeLists.txt +++ b/blink/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(blink blink.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(blink pico_stdlib) # create map/bin/hex file etc. diff --git a/clocks/detached_clk_peri/CMakeLists.txt b/clocks/detached_clk_peri/CMakeLists.txt index 009643edb..d3dc75e01 100644 --- a/clocks/detached_clk_peri/CMakeLists.txt +++ b/clocks/detached_clk_peri/CMakeLists.txt @@ -2,11 +2,11 @@ add_executable(clocks_detached_clk_peri detached_clk_peri.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(clocks_detached_clk_peri pico_stdlib) # create map/bin/hex file etc. pico_add_extra_outputs(clocks_detached_clk_peri) # add url via pico_set_program_url -example_auto_set_url(clocks_detached_clk_peri) \ No newline at end of file +example_auto_set_url(clocks_detached_clk_peri) diff --git a/clocks/hello_48MHz/CMakeLists.txt b/clocks/hello_48MHz/CMakeLists.txt index 27290d767..3ca96fc52 100644 --- a/clocks/hello_48MHz/CMakeLists.txt +++ b/clocks/hello_48MHz/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(hello_48MHz hello_48MHz.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies and additional clocks hardware support target_link_libraries(hello_48MHz pico_stdlib hardware_clocks) # create map/bin/hex file etc. diff --git a/clocks/hello_gpout/CMakeLists.txt b/clocks/hello_gpout/CMakeLists.txt index bce0eacf9..057ec3c5f 100644 --- a/clocks/hello_gpout/CMakeLists.txt +++ b/clocks/hello_gpout/CMakeLists.txt @@ -2,11 +2,11 @@ add_executable(hello_gpout hello_gpout.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(hello_gpout pico_stdlib) # create map/bin/hex file etc. pico_add_extra_outputs(hello_gpout) # add url via pico_set_program_url -example_auto_set_url(hello_gpout) \ No newline at end of file +example_auto_set_url(hello_gpout) diff --git a/clocks/hello_resus/CMakeLists.txt b/clocks/hello_resus/CMakeLists.txt index d7fc82816..9d4522e94 100644 --- a/clocks/hello_resus/CMakeLists.txt +++ b/clocks/hello_resus/CMakeLists.txt @@ -2,11 +2,11 @@ add_executable(hello_resus hello_resus.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(hello_resus pico_stdlib) # create map/bin/hex file etc. pico_add_extra_outputs(hello_resus) # add url via pico_set_program_url -example_auto_set_url(hello_resus) \ No newline at end of file +example_auto_set_url(hello_resus) diff --git a/divider/CMakeLists.txt b/divider/CMakeLists.txt index bf9c0ab30..d3a8be900 100644 --- a/divider/CMakeLists.txt +++ b/divider/CMakeLists.txt @@ -2,11 +2,11 @@ add_executable(hello_divider hello_divider.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(hello_divider pico_stdlib) # create map/bin/hex file etc. pico_add_extra_outputs(hello_divider) # add url via pico_set_program_url -example_auto_set_url(hello_divider) \ No newline at end of file +example_auto_set_url(hello_divider) diff --git a/dma/control_blocks/control_blocks.c b/dma/control_blocks/control_blocks.c index 432d4a754..6e0bd7b8b 100644 --- a/dma/control_blocks/control_blocks.c +++ b/dma/control_blocks/control_blocks.c @@ -84,7 +84,7 @@ int main() { c = dma_channel_get_default_config(data_chan); channel_config_set_transfer_data_size(&c, DMA_SIZE_8); - channel_config_set_dreq(&c, DREQ_UART0_TX + 2 * uart_get_index(uart_default)); + channel_config_set_dreq(&c, uart_get_dreq(uart_default, true)); // Trigger ctrl_chan when data_chan completes channel_config_set_chain_to(&c, ctrl_chan); // Raise the IRQ flag when 0 is written to a trigger register (end of chain): diff --git a/gpio/dht_sensor/README.adoc b/gpio/dht_sensor/README.adoc index 60cd9f874..91fc1dc8d 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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ | 10 kΩ resistor | 1 | generic part | M/M Jumper wires | 4 | generic part | DHT-22 sensor | 1 | generic part diff --git a/gpio/hello_7segment/CMakeLists.txt b/gpio/hello_7segment/CMakeLists.txt index bb23d7f32..d0ce3aecf 100644 --- a/gpio/hello_7segment/CMakeLists.txt +++ b/gpio/hello_7segment/CMakeLists.txt @@ -2,11 +2,11 @@ add_executable(hello_7segment hello_7segment.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(hello_7segment pico_stdlib) # create map/bin/hex file etc. pico_add_extra_outputs(hello_7segment) # add url via pico_set_program_url -example_auto_set_url(hello_7segment) \ No newline at end of file +example_auto_set_url(hello_7segment) diff --git a/gpio/hello_7segment/README.adoc b/gpio/hello_7segment/README.adoc index a6680c7d6..0a660348f 100644 --- a/gpio/hello_7segment/README.adoc +++ b/gpio/hello_7segment/README.adoc @@ -40,11 +40,9 @@ hello_7segment.c:: The example code. |=== | *Item* | *Quantity* | Details | Breadboard | 1 | generic part -| Raspberry Pi Pico | 1 | http://raspberrypi.org/ +| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/ | 7 segment LED module | 1 | generic part | 68 ohm resistor | 7 | generic part | DIL push to make switch | 1 | generic switch | M/M Jumper wires | 10 | generic part |=== - - diff --git a/gpio/hello_gpio_irq/CMakeLists.txt b/gpio/hello_gpio_irq/CMakeLists.txt index d12019baa..e514cfbea 100644 --- a/gpio/hello_gpio_irq/CMakeLists.txt +++ b/gpio/hello_gpio_irq/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(hello_gpio_irq hello_gpio_irq.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(hello_gpio_irq pico_stdlib) # create map/bin/hex file etc. diff --git a/hello_world/serial/CMakeLists.txt b/hello_world/serial/CMakeLists.txt index a9e48e1b5..7be690122 100644 --- a/hello_world/serial/CMakeLists.txt +++ b/hello_world/serial/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(hello_serial hello_serial.c ) -# Pull in our pico_stdlib which aggregates commonly used features +# pull in common dependencies target_link_libraries(hello_serial pico_stdlib) # create map/bin/hex/uf2 file etc. diff --git a/hello_world/usb/CMakeLists.txt b/hello_world/usb/CMakeLists.txt index 7368c3d7b..01f8d0636 100644 --- a/hello_world/usb/CMakeLists.txt +++ b/hello_world/usb/CMakeLists.txt @@ -3,7 +3,7 @@ if (TARGET tinyusb_device) hello_usb.c ) - # Pull in our pico_stdlib which aggregates commonly used features + # pull in common dependencies target_link_libraries(hello_usb pico_stdlib) # enable usb output, disable uart output diff --git a/i2c/CMakeLists.txt b/i2c/CMakeLists.txt index 080153f49..1b75de7c3 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(lis3dh_i2c) + add_subdirectory(mcp9808_i2c) + add_subdirectory(mma8451_i2c) + add_subdirectory(mpl3115a2_i2c) add_subdirectory(mpu6050_i2c) + add_subdirectory(oled_i2c) + add_subdirectory(pa1010d_i2c) + add_subdirectory(pcf8523_i2c) endif () 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..bdbe7d4ed --- /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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| BMP280-based breakout board | 1 | https://shop.pimoroni.com/products/bmp280-breakout-temperature-pressure-altitude-sensor[from Pimoroni] +| M/M Jumper wires | 4 | generic part +|=== + + diff --git a/i2c/bmp280_i2c/bmp280_i2c.c b/i2c/bmp280_i2c/bmp280_i2c.c new file mode 100644 index 000000000..004608d57 --- /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(); + +#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) + #warning i2c / bmp280_i2c example requires a board with I2C pins + puts("Default I2C pins were not defined"); +#else + // useful information for picotool + bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); + bi_decl(bi_program_description("BMP280 I2C example for the Raspberry Pi Pico")); + + printf("Hello, BMP280! Reading temperaure and pressure values from sensor...\n"); + + // I2C is "open drain", pull ups to keep signal high when no data is being sent + i2c_init(i2c_default, 100 * 1000); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); + gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); + + // configure BMP280 + bmp280_init(); + + // retrieve fixed compensation params + struct bmp280_calib_param params; + bmp280_get_calib_params(¶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/bus_scan/CMakeLists.txt b/i2c/bus_scan/CMakeLists.txt index b3bc9dc6f..662cfe61b 100644 --- a/i2c/bus_scan/CMakeLists.txt +++ b/i2c/bus_scan/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(i2c_bus_scan bus_scan.c ) -# Pull in our (to be renamed) simple get you started dependencies +# pull in common dependencies and additional i2c hardware support target_link_libraries(i2c_bus_scan pico_stdlib hardware_i2c) # create map/bin/hex file etc. diff --git a/i2c/bus_scan/bus_scan.c b/i2c/bus_scan/bus_scan.c index 1dbc8cde9..96711a5a9 100644 --- a/i2c/bus_scan/bus_scan.c +++ b/i2c/bus_scan/bus_scan.c @@ -38,7 +38,7 @@ int main() { #warning i2c/bus_scan example requires a board with I2C pins puts("Default I2C pins were not defined"); #else - // This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico) + // This example will use I2C0 on the default SDA and SCL pins (GP4, GP5 on a Pico) i2c_init(i2c_default, 100 * 1000); gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); diff --git a/i2c/lcd_1602_i2c/CMakeLists.txt b/i2c/lcd_1602_i2c/CMakeLists.txt index c4eaf9f1b..c6aad7ae1 100644 --- a/i2c/lcd_1602_i2c/CMakeLists.txt +++ b/i2c/lcd_1602_i2c/CMakeLists.txt @@ -2,11 +2,11 @@ add_executable(lcd_1602_i2c lcd_1602_i2c.c ) -# Pull in our (to be renamed) simple get you started dependencies +# pull in common dependencies and additional i2c hardware support target_link_libraries(lcd_1602_i2c pico_stdlib hardware_i2c) # create map/bin/hex file etc. pico_add_extra_outputs(lcd_1602_i2c) # add url via pico_set_program_url -example_auto_set_url(lcd_1602_i2c) \ No newline at end of file +example_auto_set_url(lcd_1602_i2c) diff --git a/i2c/lcd_1602_i2c/README.adoc b/i2c/lcd_1602_i2c/README.adoc index f6a6d8b1e..bdc6147cb 100644 --- a/i2c/lcd_1602_i2c/README.adoc +++ b/i2c/lcd_1602_i2c/README.adoc @@ -32,10 +32,8 @@ lcd_1602_i2c.c:: The example code. |=== | *Item* | *Quantity* | Details | Breadboard | 1 | generic part -| Raspberry Pi Pico | 1 | http://raspberrypi.org/ +| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/ | 1602A based LCD panel 3.3v | 1 | generic part | 1602A to I2C bridge device 3.3v | 1 | generic part | M/M Jumper wires | 4 | generic part |=== - - 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..82967a021 --- /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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| 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..0de5094d3 --- /dev/null +++ b/i2c/lis3dh_i2c/lis3dh_i2c.c @@ -0,0 +1,129 @@ +/** + * 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.004f; // g per unit + + if (isAccel == true) { + scaling = 64 / senstivity; + } else { + scaling = 64; + } + + // raw_value is signed + *final_value = (float) ((int16_t) raw_value) / scaling; +} + +void lis3dh_read_data(uint8_t reg, float *final_value, bool IsAccel) { + // Read two bytes of data and store in a 16 bit data structure + uint8_t lsb; + uint8_t msb; + uint16_t raw_accel; + i2c_write_blocking(i2c_default, ADDRESS, ®, 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..a78c155b3 --- /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.png[] + +== List of Files + +CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree. +mcp9808_i2c.c:: The example code. + +== Bill of Materials + +.A list of materials required for the example +[[mcp9808-bom-table]] +[cols=3] +|=== +| *Item* | *Quantity* | Details +| Breadboard | 1 | generic part +| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| MCP9808 board| 1 | https://www.adafruit.com/product/1782 +| M/M Jumper wires | 4 | generic part +|=== + + diff --git a/i2c/mcp9808_i2c/mcp9808_i2c.c b/i2c/mcp9808_i2c/mcp9808_i2c.c new file mode 100644 index 000000000..f1dda7c86 --- /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; +} + +#ifdef i2c_default +void mcp9808_set_limits() { + + //Set an upper limit of 30°C for the temperature + uint8_t upper_temp_msb = 0x01; + uint8_t upper_temp_lsb = 0xE0; + + //Set a lower limit of 20°C for the temperature + uint8_t lower_temp_msb = 0x01; + uint8_t lower_temp_lsb = 0x40; + + //Set a critical limit of 40°C for the temperature + uint8_t crit_temp_msb = 0x02; + uint8_t crit_temp_lsb = 0x80; + + uint8_t buf[3]; + buf[0] = REG_TEMP_UPPER; + buf[1] = upper_temp_msb; + buf[2] = upper_temp_lsb; + i2c_write_blocking(i2c_default, ADDRESS, buf, 3, false); + + buf[0] = REG_TEMP_LOWER; + buf[1] = lower_temp_msb; + buf[2] = lower_temp_lsb; + i2c_write_blocking(i2c_default, ADDRESS, buf, 3, false); + + buf[0] = REG_TEMP_CRIT; + buf[1] = crit_temp_msb; + buf[2] = crit_temp_lsb;; + i2c_write_blocking(i2c_default, ADDRESS, buf, 3, false); +} +#endif + +int main() { + + stdio_init_all(); + +#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) +#warning i2c/mcp9808_i2c example requires a board with I2C pins + puts("Default I2C pins were not defined"); +#else + printf("Hello, MCP9808! Reading raw data from registers...\n"); + + // This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico) + i2c_init(i2c_default, 400 * 1000); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); + gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); + // Make the I2C pins available to picotool + bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); + + mcp9808_set_limits(); + + uint8_t buf[2]; + uint16_t upper_byte; + uint16_t lower_byte; + + float temperature; + + while (1) { + // Start reading ambient temperature register for 2 bytes + i2c_write_blocking(i2c_default, ADDRESS, ®_TEMP_AMB, 1, true); + i2c_read_blocking(i2c_default, ADDRESS, buf, 2, false); + + upper_byte = buf[0]; + lower_byte = buf[1]; + + //isolates limit flags in upper byte + mcp9808_check_limits(upper_byte & 0xE0); + + //clears flag bits in upper byte + temperature = mcp9808_convert_temp(upper_byte & 0x1F, lower_byte); + printf("Ambient temperature: %.4f°C\n", temperature); + + sleep_ms(1000); + } +#endif +} 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..e3c1084d1 --- /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..d9b328e12 --- /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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| 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..453f5f3df --- /dev/null +++ b/i2c/mma8451_i2c/mma8451_i2c.c @@ -0,0 +1,128 @@ +/** + * 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.81f; + return acceleration; +} + +#ifdef i2c_default +void mma8451_set_state(uint8_t state) { + buf[0] = REG_CTRL_REG1; + buf[1] = state; // Set RST bit to 1 + i2c_write_blocking(i2c_default, ADDRESS, buf, 2, false); +} +#endif + +int main() { + stdio_init_all(); + +#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) +#warning i2c/mma8451_i2c example requires a board with I2C pins + puts("Default I2C pins were not defined"); +#else + printf("Hello, MMA8451! Reading raw data from registers...\n"); + + // This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico) + i2c_init(i2c_default, 400 * 1000); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); + gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); + // Make the I2C pins available to picotool + bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); + + float x_acceleration; + float y_acceleration; + float z_acceleration; + + // Enable standby mode + mma8451_set_state(0x00); + + // Edit configuration while in standby mode + buf[0] = REG_DATA_CFG; + buf[1] = range_config; + i2c_write_blocking(i2c_default, ADDRESS, buf, 2, false); + + // Enable active mode + mma8451_set_state(0x01); + + while (1) { + + // Start reading acceleration registers for 2 bytes + i2c_write_blocking(i2c_default, ADDRESS, ®_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..6ad129b3a --- /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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| MPL3115A2 altimeter | 1 | https://www.adafruit.com/product/1893[Adafruit] +| M/M Jumper wires | 5 | generic part +|=== diff --git a/i2c/mpl3115a2_i2c/mpl3115a2_i2c.c b/i2c/mpl3115a2_i2c/mpl3115a2_i2c.c new file mode 100644 index 000000000..b98a1b449 --- /dev/null +++ b/i2c/mpl3115a2_i2c/mpl3115a2_i2c.c @@ -0,0 +1,206 @@ +/** + * 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 = ((float)h) / 65536.f; + + // 2 temperature registers: MSB (8 bits) and LSB (4 bits, starting from MSB) + // first 8 are integer bits with sign and LSB is fractional bits -> 12 bit signed integer + int16_t t = (int16_t) (((uint16_t) buf[start + 3]) << 8 | buf[start + 4]); + data->temperature = ((float)t) / 256.f; +} + +int main() { + stdio_init_all(); +#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) +#warning i2c / mpl3115a2_i2c example requires a board with I2C pins + puts("Default I2C pins were not defined"); +#else + printf("Hello, MPL3115A2. Waiting for something to interrupt me!...\n"); + + // use default I2C0 at 400kHz, I2C is active low + i2c_init(i2c_default, 400 * 1000); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); + gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); + + gpio_init(INT1_PIN); + gpio_pull_up(INT1_PIN); // pull it up even more! + + // add program information for picotool + bi_decl(bi_program_name("Example in the pico-examples library for the MPL3115A2 altimeter")); + bi_decl(bi_1pin_with_name(16, "Interrupt pin 1")); + bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); + + mpl3115a2_init(); + + gpio_set_irq_enabled_with_callback(INT1_PIN, GPIO_IRQ_LEVEL_LOW, true, &gpio_callback); + + while (1) { + // as interrupt data comes in, let's print the 32 sample average + if (has_new_data) { + float tsum = 0, hsum = 0; + struct mpl3115a2_data_t data; + for (int i = 0; i < MPL3115A2_FIFO_SIZE; i++) { + mpl3115a2_convert_fifo_batch(i * MPL3115A2_DATA_BATCH_SIZE, fifo_data, &data); + tsum += data.temperature; + hsum += data.altitude; + } + printf("%d sample average -> t: %.4f C, h: %.4f m\n", MPL3115A2_FIFO_SIZE, tsum / MPL3115A2_FIFO_SIZE, + hsum / MPL3115A2_FIFO_SIZE); + has_new_data = false; + } + sleep_ms(10); + }; + +#endif + return 0; +} 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/CMakeLists.txt b/i2c/mpu6050_i2c/CMakeLists.txt index e8988564a..83be3d51e 100644 --- a/i2c/mpu6050_i2c/CMakeLists.txt +++ b/i2c/mpu6050_i2c/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(mpu6050_i2c mpu6050_i2c.c ) -# Pull in our (to be renamed) simple get you started dependencies +# pull in common dependencies and additional i2c hardware support target_link_libraries(mpu6050_i2c pico_stdlib hardware_i2c) # create map/bin/hex file etc. diff --git a/i2c/mpu6050_i2c/README.adoc b/i2c/mpu6050_i2c/README.adoc index f0fde5130..da7b96bf5 100644 --- a/i2c/mpu6050_i2c/README.adoc +++ b/i2c/mpu6050_i2c/README.adoc @@ -35,9 +35,7 @@ mpu6050_i2c.c:: The example code. |=== | *Item* | *Quantity* | Details | Breadboard | 1 | generic part -| Raspberry Pi Pico | 1 | http://raspberrypi.org/ +| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/ | MPU6050 board| 1 | generic part | M/M Jumper wires | 4 | generic part |=== - - diff --git a/i2c/mpu6050_i2c/mpu6050_i2c.c b/i2c/mpu6050_i2c/mpu6050_i2c.c index 9bb881590..4f74a6d75 100644 --- a/i2c/mpu6050_i2c/mpu6050_i2c.c +++ b/i2c/mpu6050_i2c/mpu6050_i2c.c @@ -24,8 +24,8 @@ Connections on Raspberry Pi Pico board, other boards may vary. - GPIO PICO_DEFAULT_I2C_SDA_PIN (On Pico this is 4 (pin 6)) -> SDA on MPU6050 board - GPIO PICO_DEFAULT_I2C_SCK_PIN (On Pico this is 5 (pin 7)) -> SCL on MPU6050 board + GPIO PICO_DEFAULT_I2C_SDA_PIN (On Pico this is GP4 (pin 6)) -> SDA on MPU6050 board + GPIO PICO_DEFAULT_I2C_SCL_PIN (On Pico this is GP5 (pin 7)) -> SCL on MPU6050 board 3.3v (pin 36) -> VCC on MPU6050 board GND (pin 38) -> GND on MPU6050 board */ diff --git a/i2c/oled_i2c/CMakeLists.txt b/i2c/oled_i2c/CMakeLists.txt new file mode 100644 index 000000000..0f5c50d82 --- /dev/null +++ b/i2c/oled_i2c/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(oled_i2c + oled_i2c.c + ) + +# pull in common dependencies and additional i2c hardware support +target_link_libraries(oled_i2c pico_stdlib hardware_i2c) + +# create map/bin/hex file etc. +pico_add_extra_outputs(oled_i2c) + +# add url via pico_set_program_url +example_auto_set_url(oled_i2c) diff --git a/i2c/oled_i2c/README.adoc b/i2c/oled_i2c/README.adoc new file mode 100644 index 000000000..c5aea7582 --- /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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| SSD1306-based OLED display | 1 | https://www.adafruit.com/product/4440[Adafruit part] +| M/M Jumper wires | 4 | generic part +|=== 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..8a455b5e3 --- /dev/null +++ b/i2c/oled_i2c/oled_i2c.c @@ -0,0 +1,298 @@ +/** + * 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(); + +#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) +#warning i2c / oled_i2d example requires a board with I2C pins + puts("Default I2C pins were not defined"); +#else + // useful information for picotool + bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); + bi_decl(bi_program_description("OLED I2C example for the Raspberry Pi Pico")); + + printf("Hello, OLED display! Look at my raspberries..\n"); + + // I2C is "open drain", pull ups to keep signal high when no data is being + // sent + i2c_init(i2c_default, 400 * 1000); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); + gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); + + // run through the complete initialization process + oled_init(); + + // initialize render area for entire frame (128 pixels by 4 pages) + struct render_area frame_area = {start_col: 0, end_col : OLED_WIDTH - 1, start_page : 0, end_page : OLED_NUM_PAGES - + 1}; + calc_render_area_buflen(&frame_area); + + // zero the entire display + uint8_t buf[OLED_BUF_LEN]; + fill(buf, 0x00); + render(buf, &frame_area); + + // intro sequence: flash the screen 3 times + for (int i = 0; i < 3; i++) { + oled_send_cmd(0xA5); // ignore RAM, all pixels on + sleep_ms(500); + oled_send_cmd(0xA4); // go back to following RAM + sleep_ms(500); + } + + // render 3 cute little raspberries + struct render_area area = {start_col: 0, end_col : IMG_WIDTH - 1, start_page : 0, end_page : OLED_NUM_PAGES - 1}; + calc_render_area_buflen(&area); + render(raspberry26x32, &area); + for (int i = 1; i < 3; i++) { + uint8_t offset = 5 + IMG_WIDTH; // 5px padding + area.start_col += offset; + area.end_col += offset; + render(raspberry26x32, &area); + } + + // configure horizontal scrolling + oled_send_cmd(OLED_SET_HORIZ_SCROLL | 0x00); + oled_send_cmd(0x00); // dummy byte + oled_send_cmd(0x00); // start page 0 + oled_send_cmd(0x00); // time interval + oled_send_cmd(0x03); // end page 3 + oled_send_cmd(0x00); // dummy byte + oled_send_cmd(0xFF); // dummy byte + + // let's goooo! + oled_send_cmd(OLED_SET_SCROLL | 0x01); + +#endif + return 0; +} 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..0d63d75a0 --- /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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| PA1010D board| 1 | https://shop.pimoroni.com/products/pa1010d-gps-breakout +| M/M Jumper wires | 4 | generic part +|=== + diff --git a/i2c/pa1010d_i2c/pa1010d_i2c.c b/i2c/pa1010d_i2c/pa1010d_i2c.c new file mode 100644 index 000000000..4de0651e3 --- /dev/null +++ b/i2c/pa1010d_i2c/pa1010d_i2c.c @@ -0,0 +1,156 @@ +/** + * 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(const char command[], int com_length) { + // Convert character array to bytes for writing + uint8_t int_command[com_length]; + + for (int i = 0; i < com_length; ++i) { + int_command[i] = command[i]; + i2c_write_blocking(i2c_default, addr, &int_command[i], 1, true); + } +} + +void pa1010d_parse_string(char output[], char protocol[]) { + // Finds location of protocol message in output + char *com_index = strstr(output, protocol); + int p = com_index - output; + + // Splits components of output sentence into array + int no_of_fields = 14; + int max_len = 15; + + int n = 0; + int m = 0; + + char gps_data[no_of_fields][max_len]; + memset(gps_data, 0, sizeof(gps_data)); + + bool complete = false; + while (output[p] != '$' && n < max_len && complete == false) { + if (output[p] == ',' || output[p] == '*') { + n += 1; + m = 0; + } else { + gps_data[n][m] = output[p]; + // Checks if sentence is complete + if (m < no_of_fields) { + m++; + } else { + complete = true; + } + } + p++; + } + + // Displays GNRMC data + // Similarly, additional if statements can be used to add more protocols + if (strcmp(protocol, "GNRMC") == 0) { + printf("Protcol:%s\n", gps_data[0]); + printf("UTC Time: %s\n", gps_data[1]); + printf("Status: %s\n", gps_data[2][0] == 'V' ? "Data invalid. GPS fix not found." : "Data Valid"); + printf("Latitude: %s\n", gps_data[3]); + printf("N/S indicator: %s\n", gps_data[4]); + printf("Longitude: %s\n", gps_data[5]); + printf("E/W indicator: %s\n", gps_data[6]); + printf("Speed over ground: %s\n", gps_data[7]); + printf("Course over ground: %s\n", gps_data[8]); + printf("Date: %c%c/%c%c/%c%c\n", gps_data[9][0], gps_data[9][1], gps_data[9][2], gps_data[9][3], gps_data[9][4], + gps_data[9][5]); + printf("Magnetic Variation: %s\n", gps_data[10]); + printf("E/W degree indicator: %s\n", gps_data[11]); + printf("Mode: %s\n", gps_data[12]); + printf("Checksum: %c%c\n", gps_data[13][0], gps_data[13][1]); + } +} + +void pa1010d_read_raw(char numcommand[]) { + uint8_t buffer[max_read]; + + int i = 0; + bool complete = false; + + i2c_read_blocking(i2c_default, addr, buffer, max_read, false); + + // Convert bytes to characters + while (i < max_read && complete == false) { + numcommand[i] = buffer[i]; + // Stop converting at end of message + if (buffer[i] == 10 && buffer[i + 1] == 10) { + complete = true; + } + i++; + } +} + +#endif + +int main() { + stdio_init_all(); +#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) +#warning i2c/mpu6050_i2c example requires a board with I2C pins + puts("Default I2C pins were not defined"); +#else + + char numcommand[max_read]; + + // Decide which protocols you would like to retrieve data from + char init_command[] = "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n"; + + // This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico) + i2c_init(i2c_default, 400 * 1000); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); + gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); + + // Make the I2C pins available to picotool + bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); + + printf("Hello, PA1010D! Reading raw data from module...\n"); + + pa1010d_write_command(init_command, sizeof(init_command)); + + while (1) { + // Clear array + memset(numcommand, 0, max_read); + // Read and re-format + pa1010d_read_raw(numcommand); + pa1010d_parse_string(numcommand, "GNRMC"); + + // Wait for data to refresh + sleep_ms(1000); + + // Clear terminal + printf("\e[1;1H\e[2J"); + } +#endif + return 0; +} 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..e702dbd84 --- /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::pc8523_i2c.png[] + +== List of Files + +CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree. +pcf8523_i2c.c:: The example code. + +== Bill of Materials + +.A list of materials required for the example +[[pcf8523-bom-table]] +[cols=3] +|=== +| *Item* | *Quantity* | Details +| Breadboard | 1 | generic part +| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| PCF8523 board| 1 | https://www.adafruit.com/product/3295 +| M/M Jumper wires | 4 | generic part +|=== + 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..31046a308 --- /dev/null +++ b/i2c/pcf8523_i2c/pcf8523_i2c.c @@ -0,0 +1,164 @@ +/** + * 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 +*/ + +#ifdef i2c_default + +// By default these devices are on bus address 0x68 +static int addr = 0x68; + +static void pcf8520_reset() { + // Two byte reset. First byte register, second byte data + // There are a load more options to set up the device in different ways that could be added here + uint8_t buf[] = {0x00, 0x58}; + i2c_write_blocking(i2c_default, addr, buf, 2, false); +} + +static void pcf820_write_current_time() { + // buf[0] is the register to write to + // buf[1] is the value that will be written to the register + uint8_t buf[2]; + + //Write values for the current time in the array + //index 0 -> second: bits 4-6 are responsible for the ten's digit and bits 0-3 for the unit's digit + //index 1 -> minute: bits 4-6 are responsible for the ten's digit and bits 0-3 for the unit's digit + //index 2 -> hour: bits 4-5 are responsible for the ten's digit and bits 0-3 for the unit's digit + //index 3 -> day of the month: bits 4-5 are responsible for the ten's digit and bits 0-3 for the unit's digit + //index 4 -> day of the week: where Sunday = 0x00, Monday = 0x01, Tuesday... ...Saturday = 0x06 + //index 5 -> month: bit 4 is responsible for the ten's digit and bits 0-3 for the unit's digit + //index 6 -> year: bits 4-7 are responsible for the ten's digit and bits 0-3 for the unit's digit + + //NOTE: if the value in the year register is a multiple for 4, it will be considered a leap year and hence will include the 29th of February + + uint8_t current_val[7] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + for (int i = 3; i < 10; ++i) { + buf[0] = i; + buf[1] = current_val[i - 3]; + i2c_write_blocking(i2c_default, addr, buf, 2, false); + } +} + +static void pcf8520_read_raw(uint8_t *buffer) { + // For this particular device, we send the device the register we want to read + // first, then subsequently read from the device. The register is auto incrementing + // so we don't need to keep sending the register we want, just the first. + + // Start reading acceleration registers from register 0x3B for 6 bytes + uint8_t val = 0x03; + i2c_write_blocking(i2c_default, addr, &val, 1, true); // true to keep master control of bus + i2c_read_blocking(i2c_default, addr, buffer, 7, false); +} + + +void pcf8520_set_alarm() { + // buf[0] is the register to write to + // buf[1] is the value that will be written to the register + uint8_t buf[2]; + + // Default value of alarm register is 0x80 + // Set bit 8 of values to 0 to activate that particular alarm + // Index 0 -> minute: bits 4-5 are responsible for the ten's digit and bits 0-3 for the unit's digit + // Index 1 -> hour: bits 4-6 are responsible for the ten's digit and bits 0-3 for the unit's digit + // Index 2 -> day of the month: bits 4-5 are responsible for the ten's digit and bits 0-3 for the unit's digit + // Index 3 -> day of the week: where Sunday = 0x00, Monday = 0x01, Tuesday... ...Saturday = 0x06 + + uint8_t alarm_val[4] = {0x01, 0x80, 0x80, 0x80}; + // Write alarm values to registers + for (int i = 10; i < 14; ++i) { + buf[0] = (uint8_t) i; + buf[1] = alarm_val[i - 10]; + i2c_write_blocking(i2c_default, addr, buf, 2, false); + } +} + +void pcf8520_check_alarm() { + // Check bit 3 of control register 2 for alarm flags + uint8_t status[1]; + uint8_t val = 0x01; + i2c_write_blocking(i2c_default, addr, &val, 1, true); // true to keep master control of bus + i2c_read_blocking(i2c_default, addr, status, 1, false); + + if ((status[0] & 0x08) == 0x08) { + printf("ALARM RINGING"); + } else { + printf("Alarm not triggered yet"); + } +} + + +void pcf8520_convert_time(int conv_time[7], const uint8_t raw_time[7]) { + // Convert raw data into time + conv_time[0] = (10 * (int) ((raw_time[0] & 0x70) >> 4)) + ((int) (raw_time[0] & 0x0F)); + conv_time[1] = (10 * (int) ((raw_time[1] & 0x70) >> 4)) + ((int) (raw_time[1] & 0x0F)); + conv_time[2] = (10 * (int) ((raw_time[2] & 0x30) >> 4)) + ((int) (raw_time[2] & 0x0F)); + conv_time[3] = (10 * (int) ((raw_time[3] & 0x30) >> 4)) + ((int) (raw_time[3] & 0x0F)); + conv_time[4] = (int) (raw_time[4] & 0x07); + conv_time[5] = (10 * (int) ((raw_time[5] & 0x10) >> 4)) + ((int) (raw_time[5] & 0x0F)); + conv_time[6] = (10 * (int) ((raw_time[6] & 0xF0) >> 4)) + ((int) (raw_time[6] & 0x0F)); +} +#endif + +int main() { + stdio_init_all(); +#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) +#warning i2c/pcf8520_i2c example requires a board with I2C pins + puts("Default I2C pins were not defined"); +#else + printf("Hello, PCF8520! Reading raw data from registers...\n"); + + // This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico) + i2c_init(i2c_default, 400 * 1000); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); + gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); + // Make the I2C pins available to picotool + bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); + + pcf8520_reset(); + + pcf820_write_current_time(); + pcf8520_set_alarm(); + pcf8520_check_alarm(); + + uint8_t raw_time[7]; + int real_time[7]; + char days_of_week[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + + while (1) { + + pcf8520_read_raw(raw_time); + pcf8520_convert_time(real_time, raw_time); + + printf("Time: %02d : %02d : %02d\n", real_time[2], real_time[1], real_time[0]); + printf("Date: %s %02d / %02d / %02d\n", days_of_week[real_time[4]], real_time[3], real_time[5], real_time[6]); + pcf8520_check_alarm(); + + sleep_ms(500); + + // Clear terminal + printf("\e[1;1H\e[2J"); + } +#endif + return 0; +} + diff --git a/interp/hello_interp/CMakeLists.txt b/interp/hello_interp/CMakeLists.txt index 4df3df271..05b176da7 100644 --- a/interp/hello_interp/CMakeLists.txt +++ b/interp/hello_interp/CMakeLists.txt @@ -3,7 +3,7 @@ if (TARGET hardware_interp) hello_interp.c ) - # Pull in dependencies + # pull in common dependencies and additional interpolator hardware support target_link_libraries(hello_interp pico_stdlib hardware_interp) # create map/bin/hex file etc. @@ -11,4 +11,4 @@ if (TARGET hardware_interp) # add url via pico_set_program_url example_auto_set_url(hello_interp) -endif () \ No newline at end of file +endif () diff --git a/multicore/CMakeLists.txt b/multicore/CMakeLists.txt index c19e40b2b..767e0f8b2 100644 --- a/multicore/CMakeLists.txt +++ b/multicore/CMakeLists.txt @@ -1,6 +1,6 @@ if (NOT PICO_NO_HARDWARE) add_subdirectory(hello_multicore) + add_subdirectory(multicore_fifo_irqs) add_subdirectory(multicore_runner) add_subdirectory(multicore_runner_queue) - add_subdirectory(multicore_fifo_irqs) -endif () \ No newline at end of file +endif () diff --git a/picoboard/blinky/CMakeLists.txt b/picoboard/blinky/CMakeLists.txt index fc7e9393b..e3e57abc7 100644 --- a/picoboard/blinky/CMakeLists.txt +++ b/picoboard/blinky/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(picoboard_blinky blinky.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(picoboard_blinky pico_stdlib) # create map/bin/hex file etc. diff --git a/picoboard/button/CMakeLists.txt b/picoboard/button/CMakeLists.txt index e257a5d37..c3ef5d726 100644 --- a/picoboard/button/CMakeLists.txt +++ b/picoboard/button/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(picoboard_button button.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(picoboard_button pico_stdlib) # create map/bin/hex file etc. diff --git a/pio/CMakeLists.txt b/pio/CMakeLists.txt index 8855161ff..0b9d0b3ac 100644 --- a/pio/CMakeLists.txt +++ b/pio/CMakeLists.txt @@ -6,10 +6,12 @@ if (NOT PICO_NO_HARDWARE) add_subdirectory(hello_pio) add_subdirectory(hub75) add_subdirectory(i2c) + add_subdirectory(ir_nec) add_subdirectory(logic_analyser) add_subdirectory(manchester_encoding) add_subdirectory(pio_blink) add_subdirectory(pwm) + add_subdirectory(quadrature_encoder) add_subdirectory(spi) add_subdirectory(squarewave) add_subdirectory(st7789_lcd) diff --git a/pio/addition/addition.pio b/pio/addition/addition.pio index f3693332e..8eddd0e7b 100644 --- a/pio/addition/addition.pio +++ b/pio/addition/addition.pio @@ -1,3 +1,9 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + .program addition ; Pop two 32 bit integers from the TX FIFO, add them together, and push the diff --git a/pio/hub75/hub75.pio b/pio/hub75/hub75.pio index 2d685ef68..a6fb619cd 100644 --- a/pio/hub75/hub75.pio +++ b/pio/hub75/hub75.pio @@ -1,3 +1,9 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + .program hub75_row ; side-set pin 0 is LATCH diff --git a/pio/ir_nec/CMakeLists.txt b/pio/ir_nec/CMakeLists.txt new file mode 100644 index 000000000..cf6dfa18e --- /dev/null +++ b/pio/ir_nec/CMakeLists.txt @@ -0,0 +1,8 @@ +# build the transmit and receive libraries +# +add_subdirectory(nec_transmit_library) +add_subdirectory(nec_receive_library) + +# build the example program +# +add_subdirectory(ir_loopback) diff --git a/pio/ir_nec/README.adoc b/pio/ir_nec/README.adoc new file mode 100644 index 000000000..1eaa1264c --- /dev/null +++ b/pio/ir_nec/README.adoc @@ -0,0 +1,57 @@ += Sending and receiving IR (infra-red) codes using the PIO + +This example shows how to use the Raspberry Pi Pico (RP2040) to send and receive infra-red frames in the 'NEC' format that is used by many consumer remote control applications. + +It performs a loopback test by transmitting IR codes via an IR LED and receiving them on an IR detector. The results are sent to the default serial terminal connnected via UART or USB as configured in the SDK. + +The tasks of encoding and decoding the data are offloaded to the RP2040 PIO state machines. This releases the main processor cores to concentrate on other tasks. + +== Wiring information + +Connect GPIO 14 to the positive side ('anode') of an IR LED via a suitable series resistor e.g. 1.5k Ohms, and the negative side ('cathode') to ground. The wavelength of the LED is unlikely to be critical so long as it is compatible with your detector. + +Connect GPIO 15 to the output of a 3.3v IR detector such as the VS1838b, and wire the power connections of the detector to 3v3 and ground. The program expects the decoder output to be low (logic '0') when a carrier is detected. + +[[pio_ir_loopback_wiring]] +[pdfwidth=75%] +.Wiring Diagram for IR loopback. +image::pio_ir_loopback.png[] + +== Build information + +The code is organised into three sub-directories. These contain the loopback example and two libraries for the IR transmit and receive functions. + +After a successful build the executable program can be found in the **build/ir_loopback** directory. + +== List of Files + +CMakeLists.txt:: CMake file to incorporate the example in to the examples build tree. +ir_loopback:: A directory containing the code for the loopback example. +CMakeLists.txt::: CMake file to incorporate the example in to the examples build tree. +ir_loopback.c::: The code for the loopback example. +nec_receive_library:: A directory containing the code for the IR receive functions. +CMakeLists.txt::: CMake file to incorporate the IR receive library in to the examples build tree. +nec_receive.c::: The source code for the IR receive functions. +nec_receive.h::: The headers for the IR receive functions. +nec_receive.pio::: The PIO assembler code to receive a frame. +nec_transmit_library:: A directory containing the code for the IR transmit functions. +CMakeLists.txt::: CMake file to incorporate the IR transmit library in to the examples build tree. +nec_transmit.c::: The source code for the IR transmit functions. +nec_transmit.h::: The headers for the IR transmit functions. +nec_carrier_burst.pio::: The PIO assembler code to generate a carrier burst. +nec_carrier_control.pio::: The PIO assembler code to transmit a complete frame. + +== Bill of Materials + +.A list of materials required for the example +[[pio_ir_loopback-bom-table]] +[cols=3] +|=== +| *Item* | *Quantity* | Details +| Breadboard | 1 | generic part +| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| Infra-Red LED | 1 | generic part +| 1500 ohm resistor | 1 | generic part +| Infra-Red Detector | 1 | generic part e.g. VS1838b +| M/M Jumper wires | 5 | generic part +|=== diff --git a/pio/ir_nec/ir_loopback/CMakeLists.txt b/pio/ir_nec/ir_loopback/CMakeLists.txt new file mode 100644 index 000000000..8c7d8f4b7 --- /dev/null +++ b/pio/ir_nec/ir_loopback/CMakeLists.txt @@ -0,0 +1,15 @@ +add_executable (pio_ir_loopback ir_loopback.c) + +# link the executable using the IR transmit and receive libraries +# +target_link_libraries(pio_ir_loopback LINK_PUBLIC + pico_stdlib + hardware_pio + nec_transmit_library + nec_receive_library + ) + +pico_add_extra_outputs(pio_ir_loopback) + +# add url via pico_set_program_url +example_auto_set_url(pio_ir_loopback) diff --git a/pio/ir_nec/ir_loopback/ir_loopback.c b/pio/ir_nec/ir_loopback/ir_loopback.c new file mode 100644 index 000000000..02910d856 --- /dev/null +++ b/pio/ir_nec/ir_loopback/ir_loopback.c @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2021 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdlib.h" + +#include "nec_transmit.h" // include the library headers +#include "nec_receive.h" + +// Infrared loopback example ('NEC' format) +// +// Need to connect an IR LED to GPIO 14 via a suitable series resistor (e.g. 1.5k) +// and an active-low IR detector to GPIO 15 (e.g. VS1838b) +// +// Output is sent to stdout + +int main() { + stdio_init_all(); + + PIO pio = pio0; // choose which PIO block to use (RP2040 has two: pio0 and pio1) + uint tx_gpio = 14; // choose which GPIO pin is connected to the IR LED + uint rx_gpio = 15; // choose which GPIO pin is connected to the IR detector + + // configure and enable the state machines + int tx_sm = nec_tx_init(pio, tx_gpio); // uses two state machines, 16 instructions and one IRQ + int rx_sm = nec_rx_init(pio, rx_gpio); // uses one state machine and 9 instructions + + if (tx_sm == -1 || rx_sm == -1) { + printf("could not configure PIO\n"); + return -1; + } + + // transmit and receive frames + uint8_t tx_address = 0x00, tx_data = 0x00, rx_address, rx_data; + while (true) { + // create a 32-bit frame and add it to the transmit FIFO + uint32_t tx_frame = nec_encode_frame(tx_address, tx_data); + pio_sm_put(pio, tx_sm, tx_frame); + printf("\nsent: %02x, %02x", tx_address, tx_data); + + // allow time for the frame to be transmitted (optional) + sleep_ms(100); + + // display any frames in the receive FIFO + while (!pio_sm_is_rx_fifo_empty(pio, rx_sm)) { + uint32_t rx_frame = pio_sm_get(pio, rx_sm); + + if (nec_decode_frame(rx_frame, &rx_address, &rx_data)) { + printf("\treceived: %02x, %02x", rx_address, rx_data); + } else { + printf("\treceived: %08x", rx_frame); + } + } + + sleep_ms(900); + tx_data += 1; + } +} diff --git a/pio/ir_nec/nec_receive_library/CMakeLists.txt b/pio/ir_nec/nec_receive_library/CMakeLists.txt new file mode 100644 index 000000000..72d296d59 --- /dev/null +++ b/pio/ir_nec/nec_receive_library/CMakeLists.txt @@ -0,0 +1,19 @@ +# build a normal library +# +add_library(nec_receive_library nec_receive.c) + +# invoke pio_asm to assemble the state machine program +# +pico_generate_pio_header(nec_receive_library ${CMAKE_CURRENT_LIST_DIR}/nec_receive.pio) + +target_link_libraries(nec_receive_library PRIVATE + pico_stdlib + hardware_pio + ) + +# add the `binary` directory so that the generated headers are included in the project +# +target_include_directories (nec_receive_library PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ) diff --git a/pio/ir_nec/nec_receive_library/nec_receive.c b/pio/ir_nec/nec_receive_library/nec_receive.c new file mode 100644 index 000000000..b034284b2 --- /dev/null +++ b/pio/ir_nec/nec_receive_library/nec_receive.c @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2021 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// SDK types and declarations +#include "pico/stdlib.h" +#include "hardware/pio.h" +#include "hardware/clocks.h" // for clock_get_hz() + +#include "nec_receive.h" + +// import the assembled PIO state machine program +#include "nec_receive.pio.h" + +// Claim an unused state machine on the specified PIO and configure it +// to receive NEC IR frames on the given GPIO pin. +// +// Returns: the state machine number on success, otherwise -1 +int nec_rx_init(PIO pio, uint pin_num) { + + // disable pull-up and pull-down on gpio pin + gpio_disable_pulls(pin_num); + + // install the program in the PIO shared instruction space + uint offset; + if (pio_can_add_program(pio, &nec_receive_program)) { + offset = pio_add_program(pio, &nec_receive_program); + } else { + return -1; // the program could not be added + } + + // claim an unused state machine on this PIO + int sm = pio_claim_unused_sm(pio, true); + if (sm == -1) { + return -1; // we were unable to claim a state machine + } + + // configure and enable the state machine + nec_receive_program_init(pio, sm, offset, pin_num); + + return sm; +} + + +// Validate a 32-bit frame and store the address and data at the locations +// provided. +// +// Returns: `true` if the frame was valid, otherwise `false` +bool nec_decode_frame(uint32_t frame, uint8_t *p_address, uint8_t *p_data) { + + // access the frame data as four 8-bit fields + // + union { + uint32_t raw; + struct { + uint8_t address; + uint8_t inverted_address; + uint8_t data; + uint8_t inverted_data; + }; + } f; + + f.raw = frame; + + // a valid (non-extended) 'NEC' frame should contain 8 bit + // address, inverted address, data and inverted data + if (f.address != (f.inverted_address ^ 0xff) || + f.data != (f.inverted_data ^ 0xff)) { + return false; + } + + // store the validated address and data + *p_address = f.address; + *p_data = f.data; + + return true; +} diff --git a/pio/ir_nec/nec_receive_library/nec_receive.h b/pio/ir_nec/nec_receive_library/nec_receive.h new file mode 100644 index 000000000..fdd6e23b4 --- /dev/null +++ b/pio/ir_nec/nec_receive_library/nec_receive.h @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2021 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/stdlib.h" +#include "hardware/pio.h" + +// public API + +int nec_rx_init(PIO pio, uint pin); +bool nec_decode_frame(uint32_t sm, uint8_t *p_address, uint8_t *p_data); diff --git a/pio/ir_nec/nec_receive_library/nec_receive.pio b/pio/ir_nec/nec_receive_library/nec_receive.pio new file mode 100644 index 000000000..361548a37 --- /dev/null +++ b/pio/ir_nec/nec_receive_library/nec_receive.pio @@ -0,0 +1,96 @@ +; +; Copyright (c) 2021 mjcross +; +; SPDX-License-Identifier: BSD-3-Clause +; + + +.program nec_receive + +; Decode IR frames in NEC format and push 32-bit words to the input FIFO. +; +; The input pin should be connected to an IR detector with an 'active low' output. +; +; This program expects there to be 10 state machine clock ticks per 'normal' 562.5us burst period +; in order to permit timely detection of start of a burst. The initailisation function below sets +; the correct divisor to achive this relative to the system clock. +; +; Within the 'NEC' protocol frames consists of 32 bits sent least-siginificant bit first; so the +; Input Shift Register should be configured to shift right and autopush after 32 bits, as in the +; initialisation function below. +; +.define BURST_LOOP_COUNTER 30 ; the detection threshold for a 'frame sync' burst +.define BIT_SAMPLE_DELAY 15 ; how long to wait after the end of the burst before sampling + +.wrap_target + +next_burst: + set X, BURST_LOOP_COUNTER + wait 0 pin 0 ; wait for the next burst to start + +burst_loop: + jmp pin data_bit ; the burst ended before the counter expired + jmp X-- burst_loop ; wait for the burst to end + + ; the counter expired - this is a sync burst + mov ISR, NULL ; reset the Input Shift Register + wait 1 pin 0 ; wait for the sync burst to finish + jmp next_burst ; wait for the first data bit + +data_bit: + nop [ BIT_SAMPLE_DELAY - 1 ] ; wait for 1.5 burst periods before sampling the bit value + in PINS, 1 ; if the next burst has started then detect a '0' (short gap) + ; otherwise detect a '1' (long gap) + ; after 32 bits the ISR will autopush to the receive FIFO +.wrap + + +% c-sdk { +static inline void nec_receive_program_init (PIO pio, uint sm, uint offset, uint pin) { + + // Set the GPIO function of the pin (connect the PIO to the pad) + // + pio_gpio_init(pio, pin); + + // Set the pin direction to `input` at the PIO + // + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false); + + // Create a new state machine configuration + // + pio_sm_config c = nec_receive_program_get_default_config (offset); + + // configure the Input Shift Register + // + sm_config_set_in_shift (&c, + true, // shift right + true, // enable autopush + 32); // autopush after 32 bits + + // join the FIFOs to make a single large receive FIFO + // + sm_config_set_fifo_join (&c, PIO_FIFO_JOIN_RX); + + // Map the IN pin group to one pin, namely the `pin` + // parameter to this function. + // + sm_config_set_in_pins (&c, pin); + + // Map the JMP pin to the `pin` parameter of this function. + // + sm_config_set_jmp_pin (&c, pin); + + // Set the clock divider to 10 ticks per 562.5us burst period + // + float div = clock_get_hz (clk_sys) / (10.0 / 526.6e-6); + sm_config_set_clkdiv (&c, div); + + // Apply the configuration to the state machine + // + pio_sm_init (pio, sm, offset, &c); + + // Set the state machine running + // + pio_sm_set_enabled (pio, sm, true); +} +%} diff --git a/pio/ir_nec/nec_transmit_library/CMakeLists.txt b/pio/ir_nec/nec_transmit_library/CMakeLists.txt new file mode 100644 index 000000000..dfb96bf28 --- /dev/null +++ b/pio/ir_nec/nec_transmit_library/CMakeLists.txt @@ -0,0 +1,20 @@ +# build a normal library +# +add_library(nec_transmit_library nec_transmit.c) + +# invoke pio_asm to assemble the PIO state machine programs +# +pico_generate_pio_header(nec_transmit_library ${CMAKE_CURRENT_LIST_DIR}/nec_carrier_burst.pio) +pico_generate_pio_header(nec_transmit_library ${CMAKE_CURRENT_LIST_DIR}/nec_carrier_control.pio) + +target_link_libraries(nec_transmit_library PRIVATE + pico_stdlib + hardware_pio + ) + +# add the `binary` directory so that the generated headers are included in the project +# +target_include_directories (nec_transmit_library PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ) diff --git a/pio/ir_nec/nec_transmit_library/nec_carrier_burst.pio b/pio/ir_nec/nec_transmit_library/nec_carrier_burst.pio new file mode 100644 index 000000000..499e892fc --- /dev/null +++ b/pio/ir_nec/nec_transmit_library/nec_carrier_burst.pio @@ -0,0 +1,61 @@ +; +; Copyright (c) 2021 mjcross +; +; SPDX-License-Identifier: BSD-3-Clause +; + + +.program nec_carrier_burst + +; Generate bursts of carrier. +; +; Repeatedly wait for an IRQ to be set then clear it and generate 21 cycles of +; carrier with 25% duty cycle +; +.define NUM_CYCLES 21 ; how many carrier cycles to generate +.define BURST_IRQ 7 ; which IRQ should trigger a carrier burst +.define public TICKS_PER_LOOP 4 ; the number of instructions in the loop (for timing) + +.wrap_target + set X, (NUM_CYCLES - 1) ; initialise the loop counter + wait 1 irq BURST_IRQ ; wait for the IRQ then clear it +cycle_loop: + set pins, 1 ; set the pin high (1 cycle) + set pins, 0 [1] ; set the pin low (2 cycles) + jmp X--, cycle_loop ; (1 more cycle) +.wrap + + +% c-sdk { +static inline void nec_carrier_burst_program_init(PIO pio, uint sm, uint offset, uint pin, float freq) { + // Create a new state machine configuration + // + pio_sm_config c = nec_carrier_burst_program_get_default_config (offset); + + // Map the SET pin group to one pin, namely the `pin` + // parameter to this function. + // + sm_config_set_set_pins (&c, pin, 1); + + // Set the GPIO function of the pin (connect the PIO to the pad) + // + pio_gpio_init (pio, pin); + + // Set the pin direction to output at the PIO + // + pio_sm_set_consecutive_pindirs (pio, sm, pin, 1, true); + + // Set the clock divider to generate the required frequency + // + float div = clock_get_hz (clk_sys) / (freq * nec_carrier_burst_TICKS_PER_LOOP); + sm_config_set_clkdiv (&c, div); + + // Apply the configuration to the state machine + // + pio_sm_init (pio, sm, offset, &c); + + // Set the state machine running + // + pio_sm_set_enabled (pio, sm, true); +} +%} diff --git a/pio/ir_nec/nec_transmit_library/nec_carrier_control.pio b/pio/ir_nec/nec_transmit_library/nec_carrier_control.pio new file mode 100644 index 000000000..0733afef0 --- /dev/null +++ b/pio/ir_nec/nec_transmit_library/nec_carrier_control.pio @@ -0,0 +1,79 @@ +; +; Copyright (c) 2021 mjcross +; +; SPDX-License-Identifier: BSD-3-Clause +; + + +.program nec_carrier_control + +; Transmit an encoded 32-bit frame in NEC IR format. +; +; Accepts 32-bit words from the transmit FIFO and sends them least-significant bit first +; using pulse position modulation. +; +; Carrier bursts are generated using the nec_carrier_burst program, which is expected to be +; running on a separate state machine. +; +; This program expects there to be 2 state machine ticks per 'normal' 562.5us +; burst period. +; +.define BURST_IRQ 7 ; the IRQ used to trigger a carrier burst +.define NUM_INITIAL_BURSTS 16 ; how many bursts to transmit for a 'sync burst' + +.wrap_target + pull ; fetch a data word from the transmit FIFO into the + ; output shift register, blocking if the FIFO is empty + + set X, (NUM_INITIAL_BURSTS - 1) ; send a sync burst (9ms) +long_burst: + irq BURST_IRQ + jmp X-- long_burst + + nop [15] ; send a 4.5ms space + irq BURST_IRQ [1] ; send a 562.5us burst to begin the first data bit + +data_bit: + out X, 1 ; shift the least-significant bit from the OSR + jmp !X burst ; send a short delay for a '0' bit + nop [3] ; send an additional delay for a '1' bit +burst: + irq BURST_IRQ ; send a 562.5us burst to end the data bit + +jmp !OSRE data_bit ; continue sending bits until the OSR is empty + +.wrap ; fetch another data word from the FIFO + + +% c-sdk { +static inline void nec_carrier_control_program_init (PIO pio, uint sm, uint offset, float tick_rate, int bits_per_frame) { + + // create a new state machine configuration + // + pio_sm_config c = nec_carrier_control_program_get_default_config(offset); + + // configure the output shift register + // + sm_config_set_out_shift (&c, + true, // shift right + false, // disable autopull + bits_per_frame); + + // join the FIFOs to make a single large transmit FIFO + // + sm_config_set_fifo_join (&c, PIO_FIFO_JOIN_TX); + + // configure the clock divider + // + float div = clock_get_hz (clk_sys) / tick_rate; + sm_config_set_clkdiv (&c, div); + + // apply the configuration to the state machine + // + pio_sm_init(pio, sm, offset, &c); + + // set the state machine running + // + pio_sm_set_enabled(pio, sm, true); +} +%} diff --git a/pio/ir_nec/nec_transmit_library/nec_transmit.c b/pio/ir_nec/nec_transmit_library/nec_transmit.c new file mode 100644 index 000000000..03ea93229 --- /dev/null +++ b/pio/ir_nec/nec_transmit_library/nec_transmit.c @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2021 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + +// SDK types and declarations +// +#include "pico/stdlib.h" +#include "hardware/pio.h" +#include "hardware/clocks.h" // for clock_get_hz() +#include "nec_transmit.h" + +// import the assembled PIO state machine programs +#include "nec_carrier_burst.pio.h" +#include "nec_carrier_control.pio.h" + +// Claim an unused state machine on the specified PIO and configure it +// to transmit NEC IR frames on the specificied GPIO pin. +// +// Returns: on success, the number of the carrier_control state machine +// otherwise -1 +int nec_tx_init(PIO pio, uint pin_num) { + + // install the carrier_burst program in the PIO shared instruction space + uint carrier_burst_offset; + if (pio_can_add_program(pio, &nec_carrier_burst_program)) { + carrier_burst_offset = pio_add_program(pio, &nec_carrier_burst_program); + } else { + return -1; + } + + // claim an unused state machine on this PIO + int carrier_burst_sm = pio_claim_unused_sm(pio, true); + if (carrier_burst_sm == -1) { + return -1; + } + + // configure and enable the state machine + nec_carrier_burst_program_init(pio, + carrier_burst_sm, + carrier_burst_offset, + pin_num, + 38.222e3); // 38.222 kHz carrier + + // install the carrier_control program in the PIO shared instruction space + uint carrier_control_offset; + if (pio_can_add_program(pio, &nec_carrier_control_program)) { + carrier_control_offset = pio_add_program(pio, &nec_carrier_control_program); + } else { + return -1; + } + + // claim an unused state machine on this PIO + int carrier_control_sm = pio_claim_unused_sm(pio, true); + if (carrier_control_sm == -1) { + return -1; + } + + // configure and enable the state machine + nec_carrier_control_program_init(pio, + carrier_control_sm, + carrier_control_offset, + 2 * (1 / 562.5e-6f), // 2 ticks per 562.5us carrier burst + 32); // 32 bits per frame + + return carrier_control_sm; +} + + +// Create a frame in `NEC` format from the provided 8-bit address and data +// +// Returns: a 32-bit encoded frame +uint32_t nec_encode_frame(uint8_t address, uint8_t data) { + // a normal 32-bit frame is encoded as address, inverted address, data, inverse data, + return address | (address ^ 0xff) << 8 | data << 16 | (data ^ 0xff) << 24; +} diff --git a/pio/ir_nec/nec_transmit_library/nec_transmit.h b/pio/ir_nec/nec_transmit_library/nec_transmit.h new file mode 100644 index 000000000..39c49375c --- /dev/null +++ b/pio/ir_nec/nec_transmit_library/nec_transmit.h @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2021 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/stdlib.h" +#include "hardware/pio.h" + +// public API + +int nec_tx_init(PIO pio, uint pin); +uint32_t nec_encode_frame(uint8_t address, uint8_t data); diff --git a/pio/ir_nec/pio_ir_loopback.fzz b/pio/ir_nec/pio_ir_loopback.fzz new file mode 100644 index 000000000..9754f1885 Binary files /dev/null and b/pio/ir_nec/pio_ir_loopback.fzz differ diff --git a/pio/ir_nec/pio_ir_loopback.png b/pio/ir_nec/pio_ir_loopback.png new file mode 100644 index 000000000..e5a9e4ab9 Binary files /dev/null and b/pio/ir_nec/pio_ir_loopback.png differ diff --git a/pio/pio_blink/blink.c b/pio/pio_blink/blink.c index 4be445c98..1edc698fc 100644 --- a/pio/pio_blink/blink.c +++ b/pio/pio_blink/blink.c @@ -31,5 +31,5 @@ void blink_pin_forever(PIO pio, uint sm, uint offset, uint pin, uint freq) { pio_sm_set_enabled(pio, sm, true); printf("Blinking pin %d at %d Hz\n", pin, freq); - pio->txf[sm] = clock_get_hz(clk_sys) / 2 * freq; + pio->txf[sm] = clock_get_hz(clk_sys) / (2 * freq); } diff --git a/pio/quadrature_encoder/CMakeLists.txt b/pio/quadrature_encoder/CMakeLists.txt new file mode 100644 index 000000000..b118f3c27 --- /dev/null +++ b/pio/quadrature_encoder/CMakeLists.txt @@ -0,0 +1,18 @@ +add_executable(pio_quadrature_encoder) + +pico_generate_pio_header(pio_quadrature_encoder ${CMAKE_CURRENT_LIST_DIR}/quadrature_encoder.pio) + +target_sources(pio_quadrature_encoder PRIVATE quadrature_encoder.c) + +target_link_libraries(pio_quadrature_encoder PRIVATE + pico_stdlib + pico_multicore + hardware_pio + ) + +pico_enable_stdio_usb(pio_quadrature_encoder 1) + +pico_add_extra_outputs(pio_quadrature_encoder) + +# add url via pico_set_program_url +example_auto_set_url(pio_quadrature_encoder) diff --git a/pio/quadrature_encoder/quadrature_encoder.c b/pio/quadrature_encoder/quadrature_encoder.c new file mode 100644 index 000000000..a11ab89e2 --- /dev/null +++ b/pio/quadrature_encoder/quadrature_encoder.c @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2021 pmarques-dev @ github + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdlib.h" +#include "hardware/pio.h" +#include "hardware/timer.h" + +#include "quadrature_encoder.pio.h" + +// +// ---- quadrature encoder interface example +// +// the PIO program reads phase A/B of a quadrature encoder and increments or +// decrements an internal counter to keep the current absolute step count +// updated. At any point, the main code can query the current count by using +// the quadrature_encoder_*_count functions. The counter is kept in a full +// 32 bit register that just wraps around. Two's complement arithmetic means +// that it can be interpreted as a 32-bit signed or unsigned value, and it will +// work anyway. +// +// As an example, a two wheel robot being controlled at 100Hz, can use two +// state machines to read the two encoders and in the main control loop it can +// simply ask for the current encoder counts to get the absolute step count. It +// can also subtract the values from the last sample to check how many steps +// each wheel as done since the last sample period. +// +// One advantage of this approach is that it requires zero CPU time to keep the +// encoder count updated and because of that it supports very high step rates. +// + +int main() { + int new_value, delta, old_value = 0; + + // Base pin to connect the A phase of the encoder. + // The B phase must be connected to the next pin + const uint PIN_AB = 10; + + stdio_init_all(); + + PIO pio = pio0; + const uint sm = 0; + + uint offset = pio_add_program(pio, &quadrature_encoder_program); + quadrature_encoder_program_init(pio, sm, offset, PIN_AB, 0); + + while (1) { + // note: thanks to two's complement arithmetic delta will always + // be correct even when new_value wraps around MAXINT / MININT + new_value = quadrature_encoder_get_count(pio, sm); + delta = new_value - old_value; + old_value = new_value; + + printf("position %8d, delta %6d\n", new_value, delta); + sleep_ms(100); + } +} + diff --git a/pio/quadrature_encoder/quadrature_encoder.pio b/pio/quadrature_encoder/quadrature_encoder.pio new file mode 100644 index 000000000..b2a0b82f2 --- /dev/null +++ b/pio/quadrature_encoder/quadrature_encoder.pio @@ -0,0 +1,165 @@ +; +; Copyright (c) 2021 pmarques-dev @ github +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program quadrature_encoder + +; this code must be loaded into address 0, but at 29 instructions, it probably +; wouldn't be able to share space with other programs anyway +.origin 0 + + +; the code works by running a loop that continuously shifts the 2 phase pins into +; ISR and looks at the lower 4 bits to do a computed jump to an instruction that +; does the proper "do nothing" | "increment" | "decrement" action for that pin +; state change (or no change) + +; ISR holds the last state of the 2 pins during most of the code. The Y register +; keeps the current encoder count and is incremented / decremented according to +; the steps sampled + +; writing any non zero value to the TX FIFO makes the state machine push the +; current count to RX FIFO between 6 to 18 clocks afterwards. The worst case +; sampling loop takes 14 cycles, so this program is able to read step rates up +; to sysclk / 14 (e.g., sysclk 125MHz, max step rate = 8.9 Msteps/sec) + + +; 00 state + JMP update ; read 00 + JMP decrement ; read 01 + JMP increment ; read 10 + JMP update ; read 11 + +; 01 state + JMP increment ; read 00 + JMP update ; read 01 + JMP update ; read 10 + JMP decrement ; read 11 + +; 10 state + JMP decrement ; read 00 + JMP update ; read 01 + JMP update ; read 10 + JMP increment ; read 11 + +; to reduce code size, the last 2 states are implemented in place and become the +; target for the other jumps + +; 11 state + JMP update ; read 00 + JMP increment ; read 01 +decrement: + ; note: the target of this instruction must be the next address, so that + ; the effect of the instruction does not depend on the value of Y. The + ; same is true for the "JMP X--" below. Basically "JMP Y--, " + ; is just a pure "decrement Y" instruction, with no other side effects + JMP Y--, update ; read 10 + + ; this is where the main loop starts +.wrap_target +update: + ; we start by checking the TX FIFO to see if the main code is asking for + ; the current count after the PULL noblock, OSR will have either 0 if + ; there was nothing or the value that was there + SET X, 0 + PULL noblock + + ; since there are not many free registers, and PULL is done into OSR, we + ; have to do some juggling to avoid losing the state information and + ; still place the values where we need them + MOV X, OSR + MOV OSR, ISR + + ; the main code did not ask for the count, so just go to "sample_pins" + JMP !X, sample_pins + + ; if it did ask for the count, then we push it + MOV ISR, Y ; we trash ISR, but we already have a copy in OSR + PUSH + +sample_pins: + ; we shift into ISR the last state of the 2 input pins (now in OSR) and + ; the new state of the 2 pins, thus producing the 4 bit target for the + ; computed jump into the correct action for this state + MOV ISR, NULL + IN OSR, 2 + IN PINS, 2 + MOV PC, ISR + + ; the PIO does not have a increment instruction, so to do that we do a + ; negate, decrement, negate sequence +increment: + MOV X, !Y + JMP X--, increment_cont +increment_cont: + MOV Y, !X +.wrap ; the .wrap here avoids one jump instruction and saves a cycle too + + + +% c-sdk { + +#include "hardware/clocks.h" +#include "hardware/gpio.h" + +// max_step_rate is used to lower the clock of the state machine to save power +// if the application doesn't require a very high sampling rate. Passing zero +// will set the clock to the maximum, which gives a max step rate of around +// 8.9 Msteps/sec at 125MHz + +static inline void quadrature_encoder_program_init(PIO pio, uint sm, uint offset, uint pin, int max_step_rate) +{ + pio_sm_set_consecutive_pindirs(pio, sm, pin, 2, false); + pio_gpio_init(pio, pin); + gpio_pull_up(pin); + + pio_sm_config c = quadrature_encoder_program_get_default_config(offset); + sm_config_set_in_pins(&c, pin); // for WAIT, IN + sm_config_set_jmp_pin(&c, pin); // for JMP + // shift to left, autopull disabled + sm_config_set_in_shift(&c, false, false, 32); + // don't join FIFO's + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE); + + // passing "0" as the sample frequency, + if (max_step_rate == 0) { + sm_config_set_clkdiv(&c, 1.0); + } else { + // one state machine loop takes at most 14 cycles + float div = (float)clock_get_hz(clk_sys) / (14 * max_step_rate); + sm_config_set_clkdiv(&c, div); + } + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} + + +// When requesting the current count we may have to wait a few cycles (average +// ~11 sysclk cycles) for the state machine to reply. If we are reading multiple +// encoders, we may request them all in one go and then fetch them all, thus +// avoiding doing the wait multiple times. If we are reading just one encoder, +// we can use the "get_count" function to request and wait + +static inline void quadrature_encoder_request_count(PIO pio, uint sm) +{ + pio->txf[sm] = 1; +} + +static inline int32_t quadrature_encoder_fetch_count(PIO pio, uint sm) +{ + while (pio_sm_is_rx_fifo_empty(pio, sm)) + tight_loop_contents(); + return pio->rxf[sm]; +} + +static inline int32_t quadrature_encoder_get_count(PIO pio, uint sm) +{ + quadrature_encoder_request_count(pio, sm); + return quadrature_encoder_fetch_count(pio, sm); +} + +%} + diff --git a/pio/squarewave/generated/squarewave.pio.h b/pio/squarewave/generated/squarewave.pio.h index 627cb52e0..9ec7ea659 100644 --- a/pio/squarewave/generated/squarewave.pio.h +++ b/pio/squarewave/generated/squarewave.pio.h @@ -2,6 +2,8 @@ // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // +#pragma once + #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif diff --git a/pio/squarewave/generated/squarewave_wrap.pio.h b/pio/squarewave/generated/squarewave_wrap.pio.h index c1708ec28..ff90703ce 100644 --- a/pio/squarewave/generated/squarewave_wrap.pio.h +++ b/pio/squarewave/generated/squarewave_wrap.pio.h @@ -2,6 +2,8 @@ // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // +#pragma once + #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif diff --git a/pio/uart_rx/uart_rx.pio b/pio/uart_rx/uart_rx.pio index 2573bf750..54a6577c1 100644 --- a/pio/uart_rx/uart_rx.pio +++ b/pio/uart_rx/uart_rx.pio @@ -71,7 +71,7 @@ static inline void uart_rx_program_init(PIO pio, uint sm, uint offset, uint pin, pio_sm_config c = uart_rx_program_get_default_config(offset); sm_config_set_in_pins(&c, pin); // for WAIT, IN sm_config_set_jmp_pin(&c, pin); // for JMP - // Shift to right, autopull disabled + // Shift to right, autopush disabled sm_config_set_in_shift(&c, true, false, 32); // Deeper FIFO as we're not doing any TX sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); diff --git a/pio/ws2812/generated/ws2812.pio.h b/pio/ws2812/generated/ws2812.pio.h index ca40fc5bd..8cbeea6b7 100644 --- a/pio/ws2812/generated/ws2812.pio.h +++ b/pio/ws2812/generated/ws2812.pio.h @@ -2,6 +2,8 @@ // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // +#pragma once + #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif diff --git a/pio/ws2812/ws2812.c b/pio/ws2812/ws2812.c index b7bb9290b..2ee6c4227 100644 --- a/pio/ws2812/ws2812.c +++ b/pio/ws2812/ws2812.c @@ -12,6 +12,16 @@ #include "hardware/clocks.h" #include "ws2812.pio.h" +#define IS_RGBW true +#define NUM_PIXELS 150 + +#ifdef PICO_DEFAULT_WS2812_PIN +#define WS2812_PIN PICO_DEFAULT_WS2812_PIN +#else +// default to pin 2 if the board doesn't have a default WS2812 pin defined +#define WS2812_PIN 2 +#endif + static inline void put_pixel(uint32_t pixel_grb) { pio_sm_put_blocking(pio0, 0, pixel_grb << 8u); } @@ -71,19 +81,17 @@ const struct { {pattern_greys, "Greys"}, }; -const int PIN_TX = 0; - int main() { //set_sys_clock_48(); stdio_init_all(); - puts("WS2812 Smoke Test"); + printf("WS2812 Smoke Test, using pin %d", WS2812_PIN); // todo get free sm PIO pio = pio0; int sm = 0; uint offset = pio_add_program(pio, &ws2812_program); - ws2812_program_init(pio, sm, offset, PIN_TX, 800000, true); + ws2812_program_init(pio, sm, offset, WS2812_PIN, 800000, IS_RGBW); int t = 0; while (1) { @@ -92,9 +100,9 @@ int main() { puts(pattern_table[pat].name); puts(dir == 1 ? "(forward)" : "(backward)"); for (int i = 0; i < 1000; ++i) { - pattern_table[pat].pat(150, t); + pattern_table[pat].pat(NUM_PIXELS, t); sleep_ms(10); t += dir; } } -} \ No newline at end of file +} diff --git a/pio/ws2812/ws2812_parallel.c b/pio/ws2812/ws2812_parallel.c index 50006569e..9fa03c5ba 100644 --- a/pio/ws2812/ws2812_parallel.c +++ b/pio/ws2812/ws2812_parallel.c @@ -16,7 +16,8 @@ #include "ws2812.pio.h" #define FRAC_BITS 4 -#define PIN_TX 0 +#define NUM_PIXELS 64 +#define WS2812_PIN_BASE 2 // horrible temporary hack to avoid changing pattern code static uint8_t *current_string_out; @@ -174,17 +175,15 @@ void dither_values(const value_bits_t *colors, value_bits_t *state, const value_ } } -#define MAX_LENGTH 100 - -// requested colors * 4 to allow for WRGB -static value_bits_t colors[MAX_LENGTH * 4]; +// requested colors * 4 to allow for RGBW +static value_bits_t colors[NUM_PIXELS * 4]; // double buffer the state of the string, since we update next version in parallel with DMAing out old version -static value_bits_t states[2][MAX_LENGTH * 4]; +static value_bits_t states[2][NUM_PIXELS * 4]; // example - string 0 is RGB only -static uint8_t string0_data[MAX_LENGTH * 3]; -// example - string 1 is WRGB -static uint8_t string1_data[MAX_LENGTH * 4]; +static uint8_t string0_data[NUM_PIXELS * 3]; +// example - string 1 is RGBW +static uint8_t string1_data[NUM_PIXELS * 4]; string_t string0 = { .data = string0_data, @@ -213,7 +212,7 @@ string_t *strings[] = { #define DMA_CHANNELS_MASK (DMA_CHANNEL_MASK | DMA_CB_CHANNEL_MASK) // start of each value fragment (+1 for NULL terminator) -static uintptr_t fragment_start[MAX_LENGTH * 4 + 1]; +static uintptr_t fragment_start[NUM_PIXELS * 4 + 1]; // posted when it is safe to output a new set of values static struct semaphore reset_delay_complete_sem; @@ -286,7 +285,7 @@ int main() { int sm = 0; uint offset = pio_add_program(pio, &ws2812_parallel_program); - ws2812_parallel_program_init(pio, sm, offset, PIN_TX, count_of(strings), 800000); + ws2812_parallel_program_init(pio, sm, offset, WS2812_PIN_BASE, count_of(strings), 800000); sem_init(&reset_delay_complete_sem, 1, 1); // initially posted so we don't block first time dma_init(pio, sm); @@ -300,19 +299,17 @@ int main() { int brightness = 0; uint current = 0; for (int i = 0; i < 1000; ++i) { - int n = 64; - current_string_out = string0.data; current_string_4color = false; - pattern_table[pat].pat(n, t); + pattern_table[pat].pat(NUM_PIXELS, t); current_string_out = string1.data; current_string_4color = true; - pattern_table[pat].pat(n, t); + pattern_table[pat].pat(NUM_PIXELS, t); - transform_strings(strings, count_of(strings), colors, n * 4, brightness); - dither_values(colors, states[current], states[current ^ 1], n * 4); + transform_strings(strings, count_of(strings), colors, NUM_PIXELS * 4, brightness); + dither_values(colors, states[current], states[current ^ 1], NUM_PIXELS * 4); sem_acquire_blocking(&reset_delay_complete_sem); - output_strings_dma(states[current], n * 4); + output_strings_dma(states[current], NUM_PIXELS * 4); current ^= 1; t += dir; diff --git a/pwm/hello_pwm/CMakeLists.txt b/pwm/hello_pwm/CMakeLists.txt index 9c7b0e1c5..e2df80ceb 100644 --- a/pwm/hello_pwm/CMakeLists.txt +++ b/pwm/hello_pwm/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(hello_pwm hello_pwm.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies and additional pwm hardware support target_link_libraries(hello_pwm pico_stdlib hardware_pwm) # create map/bin/hex file etc. diff --git a/pwm/led_fade/CMakeLists.txt b/pwm/led_fade/CMakeLists.txt index 4090f4625..a9f5eea56 100644 --- a/pwm/led_fade/CMakeLists.txt +++ b/pwm/led_fade/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(pwm_led_fade pwm_led_fade.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies and additional pwm hardware support target_link_libraries(pwm_led_fade pico_stdlib hardware_pwm) # create map/bin/hex file etc. diff --git a/pwm/measure_duty_cycle/CMakeLists.txt b/pwm/measure_duty_cycle/CMakeLists.txt index 174e4b9b9..45bd4d623 100644 --- a/pwm/measure_duty_cycle/CMakeLists.txt +++ b/pwm/measure_duty_cycle/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(pwm_measure_duty_cycle measure_duty_cycle.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies and additional pwm hardware support target_link_libraries(pwm_measure_duty_cycle pico_stdlib hardware_pwm) # create map/bin/hex file etc. diff --git a/reset/CMakeLists.txt b/reset/CMakeLists.txt index 29519cddd..6c1996f62 100644 --- a/reset/CMakeLists.txt +++ b/reset/CMakeLists.txt @@ -3,7 +3,7 @@ if (TARGET hardware_reset) hello_reset.c ) - # Pull in our pico_stdlib which pulls in commonly used features + # pull in common dependencies target_link_libraries(hello_reset pico_stdlib) # create map/bin/hex file etc. @@ -11,4 +11,4 @@ if (TARGET hardware_reset) # add url via pico_set_program_url example_auto_set_url(hello_reset) -endif () \ No newline at end of file +endif () diff --git a/reset/hello_reset/CMakeLists.txt b/reset/hello_reset/CMakeLists.txt index c5d433264..fc0854377 100644 --- a/reset/hello_reset/CMakeLists.txt +++ b/reset/hello_reset/CMakeLists.txt @@ -3,7 +3,7 @@ if (TARGET hardware_resets) hello_reset.c ) - # Pull in our pico_stdlib which pulls in commonly used features + # pull in common dependencies and additional reset hardware support target_link_libraries(hello_reset pico_stdlib hardware_resets) # create map/bin/hex file etc. @@ -11,4 +11,4 @@ if (TARGET hardware_resets) # add url via pico_set_program_url example_auto_set_url(hello_reset) -endif () \ No newline at end of file +endif () diff --git a/rtc/hello_rtc/CMakeLists.txt b/rtc/hello_rtc/CMakeLists.txt index 9a5e3794c..b3cc1a25c 100644 --- a/rtc/hello_rtc/CMakeLists.txt +++ b/rtc/hello_rtc/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(hello_rtc hello_rtc.c ) -# Pull in our (to be renamed) simple get you started dependencies +# pull in common dependencies and additional rtc hardware support target_link_libraries(hello_rtc pico_stdlib hardware_rtc) # create map/bin/hex file etc. diff --git a/rtc/rtc_alarm/CMakeLists.txt b/rtc/rtc_alarm/CMakeLists.txt index e93790580..8f0dfa2cb 100644 --- a/rtc/rtc_alarm/CMakeLists.txt +++ b/rtc/rtc_alarm/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(rtc_alarm rtc_alarm.c ) -# Pull in our (to be renamed) simple get you started dependencies +# pull in common dependencies and additional rtc hardware support target_link_libraries(rtc_alarm pico_stdlib hardware_rtc) # create map/bin/hex file etc. diff --git a/rtc/rtc_alarm_repeat/CMakeLists.txt b/rtc/rtc_alarm_repeat/CMakeLists.txt index 5c0155dfb..bd3c9e3e0 100644 --- a/rtc/rtc_alarm_repeat/CMakeLists.txt +++ b/rtc/rtc_alarm_repeat/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(rtc_alarm_repeat rtc_alarm_repeat.c ) -# Pull in our (to be renamed) simple get you started dependencies +# pull in common dependencies and additional rtc hardware support target_link_libraries(rtc_alarm_repeat pico_stdlib hardware_rtc) # create map/bin/hex file etc. diff --git a/spi/CMakeLists.txt b/spi/CMakeLists.txt index 18dd9fe26..6f83ce726 100644 --- a/spi/CMakeLists.txt +++ b/spi/CMakeLists.txt @@ -1,6 +1,6 @@ if (NOT PICO_NO_HARDWARE) - add_subdirectory(spi_flash) - add_subdirectory(mpu9250_spi) add_subdirectory(bme280_spi) + add_subdirectory(mpu9250_spi) add_subdirectory(spi_dma) + add_subdirectory(spi_flash) endif () diff --git a/spi/bme280_spi/CMakeLists.txt b/spi/bme280_spi/CMakeLists.txt index 17ef7ddca..117d01a51 100644 --- a/spi/bme280_spi/CMakeLists.txt +++ b/spi/bme280_spi/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(bme280_spi bme280_spi.c ) -# Pull in our (to be renamed) simple get you started dependencies +# pull in common dependencies and additional spi hardware support target_link_libraries(bme280_spi pico_stdlib hardware_spi) # create map/bin/hex file etc. diff --git a/spi/bme280_spi/README.adoc b/spi/bme280_spi/README.adoc index a96b2c1f5..9a601344d 100644 --- a/spi/bme280_spi/README.adoc +++ b/spi/bme280_spi/README.adoc @@ -42,9 +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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ | BME280 board| 1 | generic part | M/M Jumper wires | 6 | generic part |=== - - diff --git a/spi/bme280_spi/bme280_spi.c b/spi/bme280_spi/bme280_spi.c index bee24c2ec..2beea2803 100644 --- a/spi/bme280_spi/bme280_spi.c +++ b/spi/bme280_spi/bme280_spi.c @@ -13,7 +13,7 @@ /* Example code to talk to a bme280 humidity/temperature/pressure sensor. NOTE: Ensure the device is capable of being driven at 3.3v NOT 5v. The Pico - GPIO (and therefor SPI) cannot be used at 5v. + GPIO (and therefore SPI) cannot be used at 5v. You will need to use a level shifter on the SPI lines if you want to run the board at 5v. @@ -52,7 +52,7 @@ int16_t dig_H2, dig_H4, dig_H5; /* The following compensation functions are required to convert from the raw ADC data from the chip to something usable. Each chip has a different set of compensation parameters stored on the chip at point of manufacture, which are -read from the chip at startup and used inthese routines. +read from the chip at startup and used in these routines. */ int32_t compensate_temp(int32_t adc_T) { int32_t var1, var2, T; diff --git a/spi/mpu9250_spi/CMakeLists.txt b/spi/mpu9250_spi/CMakeLists.txt index e0aef15bd..5e0a6120a 100644 --- a/spi/mpu9250_spi/CMakeLists.txt +++ b/spi/mpu9250_spi/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(mpu9250_spi mpu9250_spi.c ) -# Pull in our (to be renamed) simple get you started dependencies +# pull in common dependencies and additional spi hardware support target_link_libraries(mpu9250_spi pico_stdlib hardware_spi) # create map/bin/hex file etc. diff --git a/spi/mpu9250_spi/README.adoc b/spi/mpu9250_spi/README.adoc index 9e819123f..edd268d91 100644 --- a/spi/mpu9250_spi/README.adoc +++ b/spi/mpu9250_spi/README.adoc @@ -44,9 +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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ | MPU9250 board| 1 | generic part | M/M Jumper wires | 6 | generic part |=== - - diff --git a/spi/spi_dma/spi_dma.c b/spi/spi_dma/spi_dma.c index 9a9b8b3ac..d0018dd44 100644 --- a/spi/spi_dma/spi_dma.c +++ b/spi/spi_dma/spi_dma.c @@ -56,7 +56,7 @@ int main() { printf("Configure TX DMA\n"); dma_channel_config c = dma_channel_get_default_config(dma_tx); channel_config_set_transfer_data_size(&c, DMA_SIZE_8); - channel_config_set_dreq(&c, spi_get_index(spi_default) ? DREQ_SPI1_TX : DREQ_SPI0_TX); + channel_config_set_dreq(&c, spi_get_dreq(spi_default, true)); dma_channel_configure(dma_tx, &c, &spi_get_hw(spi_default)->dr, // write address txbuf, // read address @@ -70,7 +70,7 @@ int main() { // address to increment (so data is written throughout the buffer) c = dma_channel_get_default_config(dma_rx); channel_config_set_transfer_data_size(&c, DMA_SIZE_8); - channel_config_set_dreq(&c, spi_get_index(spi_default) ? DREQ_SPI1_RX : DREQ_SPI0_RX); + channel_config_set_dreq(&c, spi_get_dreq(spi_default, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); dma_channel_configure(dma_rx, &c, diff --git a/spi/spi_flash/CMakeLists.txt b/spi/spi_flash/CMakeLists.txt index c0b54e957..2a0ddcc35 100644 --- a/spi/spi_flash/CMakeLists.txt +++ b/spi/spi_flash/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(spi_flash spi_flash.c ) -# Pull in basic dependencies +# pull in common dependencies and additional spi hardware support target_link_libraries(spi_flash pico_stdlib hardware_spi) # create map/bin/hex file etc. diff --git a/system/CMakeLists.txt b/system/CMakeLists.txt index 514c984c1..d167f8210 100644 --- a/system/CMakeLists.txt +++ b/system/CMakeLists.txt @@ -1,5 +1,5 @@ if (NOT PICO_NO_HARDWARE) - add_subdirectory(narrow_io_write) add_subdirectory(hello_double_tap) + add_subdirectory(narrow_io_write) add_subdirectory(unique_board_id) endif () diff --git a/system/narrow_io_write/CMakeLists.txt b/system/narrow_io_write/CMakeLists.txt index 8f3c31776..5e57819d7 100644 --- a/system/narrow_io_write/CMakeLists.txt +++ b/system/narrow_io_write/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(narrow_io_write narrow_io_write.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(narrow_io_write pico_stdlib) # create map/bin/hex file etc. diff --git a/timer/hello_timer/CMakeLists.txt b/timer/hello_timer/CMakeLists.txt index 058bcaddd..806cf699f 100644 --- a/timer/hello_timer/CMakeLists.txt +++ b/timer/hello_timer/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(hello_timer hello_timer.c ) -# Pull in our (to be renamed) simple get you started dependencies +# pull in common dependencies target_link_libraries(hello_timer pico_stdlib) # create map/bin/hex file etc. diff --git a/timer/periodic_sampler/CMakeLists.txt b/timer/periodic_sampler/CMakeLists.txt index a4094c473..284780fb3 100644 --- a/timer/periodic_sampler/CMakeLists.txt +++ b/timer/periodic_sampler/CMakeLists.txt @@ -3,7 +3,7 @@ if (NOT PICO_TIME_NO_ALARM_SUPPORT) periodic_sampler.c ) - # Pull in our (to be renamed) simple get you started dependencies + # pull in common dependencies target_link_libraries(periodic_sampler pico_stdlib) # create map/bin/hex file etc. 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/hello_uart/CMakeLists.txt b/uart/hello_uart/CMakeLists.txt index 62bb95e9b..0931f384d 100644 --- a/uart/hello_uart/CMakeLists.txt +++ b/uart/hello_uart/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(hello_uart hello_uart.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies target_link_libraries(hello_uart pico_stdlib) # create map/bin/hex file etc. 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..002b3fa98 --- /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 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| 16x2 RGB LCD panel 3.3v | 1 | generic part, https://www.adafruit.com/product/398[available on Adafruit] +| 16x2 LCD backpack | 1 | https://www.adafruit.com/product/781[from Adafruit] +| M/M Jumper wires | 3 | generic part +|=== + + diff --git a/uart/lcd_uart/lcd_uart.c b/uart/lcd_uart/lcd_uart.c new file mode 100644 index 000000000..e68b9e09f --- /dev/null +++ b/uart/lcd_uart/lcd_uart.c @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* Example code to drive a 16x2 LCD panel via an Adafruit TTL LCD "backpack" + + Optionally, the backpack can be connected the VBUS (pin 40) at 5V if + the Pico in question is powered by USB for greater brightness. + + If this is done, then no other connections should be made to the backpack apart + from those listed below as the backpack's logic levels will change. + + Connections on Raspberry Pi Pico board, other boards may vary. + + GPIO 8 (pin 11)-> RX on backpack + 3.3v (pin 36) -> 3.3v on backpack + GND (pin 38) -> GND on backpack +*/ + +#include +#include +#include "pico/stdlib.h" +#include "pico/binary_info.h" +#include "hardware/uart.h" + + // leave uart0 free for stdio +#define UART_ID uart1 +#define BAUD_RATE 9600 +#define UART_TX_PIN 8 +#define LCD_WIDTH 16 +#define LCD_HEIGHT 2 + +// basic commands +#define LCD_DISPLAY_ON 0x42 +#define LCD_DISPLAY_OFF 0x46 +#define LCD_SET_BRIGHTNESS 0x99 +#define LCD_SET_CONTRAST 0x50 +#define LCD_AUTOSCROLL_ON 0x51 +#define LCD_AUTOSCROLL_OFF 0x52 +#define LCD_CLEAR_SCREEN 0x58 +#define LCD_SET_SPLASH 0x40 + +// cursor commands +#define LCD_SET_CURSOR_POS 0x47 +#define LCD_CURSOR_HOME 0x48 +#define LCD_CURSOR_BACK 0x4C +#define LCD_CURSOR_FORWARD 0x4D +#define LCD_UNDERLINE_CURSOR_ON 0x4A +#define LCD_UNDERLINE_CURSOR_OFF 0x4B +#define LCD_BLOCK_CURSOR_ON 0x53 +#define LCD_BLOCK_CURSOR_OFF 0x54 + +// rgb commands +#define LCD_SET_BACKLIGHT_COLOR 0xD0 +#define LCD_SET_DISPLAY_SIZE 0xD1 + +// change to 0 if display is not RGB capable +#define LCD_IS_RGB 1 + +void lcd_write(uint8_t cmd, uint8_t* buf, uint8_t buflen) { + // all commands are prefixed with 0xFE + const uint8_t pre = 0xFE; + uart_write_blocking(UART_ID, &pre, 1); + uart_write_blocking(UART_ID, &cmd, 1); + uart_write_blocking(UART_ID, buf, buflen); + sleep_ms(10); // give the display some time +} + +void lcd_set_size(uint8_t w, uint8_t h) { + // sets the dimensions of the display + uint8_t buf[] = { w, h }; + lcd_write(LCD_SET_DISPLAY_SIZE, buf, 2); +} + +void lcd_set_contrast(uint8_t contrast) { + // sets the display contrast + lcd_write(LCD_SET_CONTRAST, &contrast, 1); +} + +void lcd_set_brightness(uint8_t brightness) { + // sets the backlight brightness + lcd_write(LCD_SET_BRIGHTNESS, &brightness, 1); +} + +void lcd_set_cursor(bool is_on) { + // set is_on to true if we want the blinking block and underline cursor to show + if (is_on) { + lcd_write(LCD_BLOCK_CURSOR_ON, NULL, 0); + lcd_write(LCD_UNDERLINE_CURSOR_ON, NULL, 0); + } else { + lcd_write(LCD_BLOCK_CURSOR_OFF, NULL, 0); + lcd_write(LCD_UNDERLINE_CURSOR_OFF, NULL, 0); + } +} + +void lcd_set_backlight(bool is_on) { + // turn the backlight on (true) or off (false) + if (is_on) { + lcd_write(LCD_DISPLAY_ON, (uint8_t *) 0, 1); + } else { + lcd_write(LCD_DISPLAY_OFF, NULL, 0); + } +} + +void lcd_clear() { + // clear the contents of the display + lcd_write(LCD_CLEAR_SCREEN, NULL, 0); +} + +void lcd_cursor_reset() { + // reset the cursor to (1, 1) + lcd_write(LCD_CURSOR_HOME, NULL, 0); +} + +#if LCD_IS_RGB +void lcd_set_backlight_color(uint8_t r, uint8_t g, uint8_t b) { + // only supported on RGB displays! + uint8_t buf[] = { r, g, b }; + lcd_write(LCD_SET_BACKLIGHT_COLOR, buf, 3); +} +#endif + +void lcd_init() { + lcd_set_backlight(true); + lcd_set_size(LCD_WIDTH, LCD_HEIGHT); + lcd_set_contrast(155); + lcd_set_brightness(255); + lcd_set_cursor(false); +} + +int main() { + stdio_init_all(); + uart_init(UART_ID, BAUD_RATE); + uart_set_translate_crlf(UART_ID, false); + gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART); + + bi_decl(bi_1pin_with_func(UART_TX_PIN, GPIO_FUNC_UART)); + + lcd_init(); + + // define startup sequence and save to EEPROM + // no more or less than 32 chars, if not enough, fill remaining ones with spaces + uint8_t splash_buf[] = "Hello LCD, from Pi Towers! "; + lcd_write(LCD_SET_SPLASH, splash_buf, LCD_WIDTH * LCD_HEIGHT); + + lcd_cursor_reset(); + lcd_clear(); + +#if LCD_IS_RGB + uint8_t i = 0; // it's ok if this overflows and wraps, we're using sin + const float frequency = 0.1f; + float red, green, blue; +#endif + + while (1) { + // send any chars from stdio straight to the backpack + char c = getchar(); + // 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 diff --git a/uart/uart_advanced/CMakeLists.txt b/uart/uart_advanced/CMakeLists.txt index 38cdfeffa..a7cf5638e 100644 --- a/uart/uart_advanced/CMakeLists.txt +++ b/uart/uart_advanced/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(uart_advanced uart_advanced.c ) -# Pull in our pico_stdlib which pulls in commonly used features +# pull in common dependencies and additional uart hardware support target_link_libraries(uart_advanced pico_stdlib hardware_uart) # create map/bin/hex file etc. diff --git a/usb/device/CMakeLists.txt b/usb/device/CMakeLists.txt index ed40042e5..44f0305a9 100644 --- a/usb/device/CMakeLists.txt +++ b/usb/device/CMakeLists.txt @@ -3,4 +3,5 @@ set(BOARD pico_sdk) set(TINYUSB_FAMILY_PROJECT_NAME_PREFIX "tinyusb_dev_") add_subdirectory(${PICO_TINYUSB_PATH}/examples/device tinyusb_device_examples) +add_subdirectory(dev_hid_composite) add_subdirectory(dev_lowlevel) diff --git a/usb/device/dev_hid_composite/CMakeLists.txt b/usb/device/dev_hid_composite/CMakeLists.txt new file mode 100644 index 000000000..8842ed586 --- /dev/null +++ b/usb/device/dev_hid_composite/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.13) + +add_executable(dev_hid_composite) + +target_sources(dev_hid_composite PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/main.c + ${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c + ) + +# Make sure TinyUSB can find tusb_config.h +target_include_directories(dev_hid_composite PUBLIC + ${CMAKE_CURRENT_LIST_DIR}) + +# In addition to pico_stdlib required for common PicoSDK functionality, add dependency on tinyusb_device +# for TinyUSB device support and tinyusb_board for the additional board support library used by the example +target_link_libraries(dev_hid_composite PUBLIC pico_stdlib tinyusb_device tinyusb_board) + +pico_add_extra_outputs(dev_hid_composite) + +# add url via pico_set_program_url +example_auto_set_url(dev_hid_composite) diff --git a/usb/device/dev_hid_composite/LICENSE.TXT b/usb/device/dev_hid_composite/LICENSE.TXT new file mode 100644 index 000000000..ddd4ab410 --- /dev/null +++ b/usb/device/dev_hid_composite/LICENSE.TXT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018, hathach (tinyusb.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/usb/device/dev_hid_composite/README.md b/usb/device/dev_hid_composite/README.md new file mode 100644 index 000000000..3491d44e4 --- /dev/null +++ b/usb/device/dev_hid_composite/README.md @@ -0,0 +1,2 @@ +This is a copy of the hid_composite example from TinyUSB (https://github.com/hathach/tinyusb/tree/master/examples/device/hid_composite) +showing how to build with TinyUSB when using the Raspberry Pi Pico SDK \ No newline at end of file diff --git a/usb/device/dev_hid_composite/main.c b/usb/device/dev_hid_composite/main.c new file mode 100644 index 000000000..fd25e620a --- /dev/null +++ b/usb/device/dev_hid_composite/main.c @@ -0,0 +1,302 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include +#include +#include + +#include "bsp/board.h" +#include "tusb.h" + +#include "usb_descriptors.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +/* Blink pattern + * - 250 ms : device not mounted + * - 1000 ms : device mounted + * - 2500 ms : device is suspended + */ +enum { + BLINK_NOT_MOUNTED = 250, + BLINK_MOUNTED = 1000, + BLINK_SUSPENDED = 2500, +}; + +static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; + +void led_blinking_task(void); +void hid_task(void); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + tusb_init(); + + while (1) + { + tud_task(); // tinyusb device task + led_blinking_task(); + + hid_task(); + } + + return 0; +} + +//--------------------------------------------------------------------+ +// Device callbacks +//--------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +// Invoked when device is unmounted +void tud_umount_cb(void) +{ + blink_interval_ms = BLINK_NOT_MOUNTED; +} + +// Invoked when usb bus is suspended +// remote_wakeup_en : if host allow us to perform remote wakeup +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void) remote_wakeup_en; + blink_interval_ms = BLINK_SUSPENDED; +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +//--------------------------------------------------------------------+ +// USB HID +//--------------------------------------------------------------------+ + +static void send_hid_report(uint8_t report_id, uint32_t btn) +{ + // skip if hid is not ready yet + if ( !tud_hid_ready() ) return; + + switch(report_id) + { + case REPORT_ID_KEYBOARD: + { + // use to avoid send multiple consecutive zero report for keyboard + static bool has_keyboard_key = false; + + if ( btn ) + { + uint8_t keycode[6] = { 0 }; + keycode[0] = HID_KEY_A; + + tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode); + has_keyboard_key = true; + }else + { + // send empty key report if previously has key pressed + if (has_keyboard_key) tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL); + has_keyboard_key = false; + } + } + break; + + case REPORT_ID_MOUSE: + { + int8_t const delta = 5; + + // no button, right + down, no scroll, no pan + tud_hid_mouse_report(REPORT_ID_MOUSE, 0x00, delta, delta, 0, 0); + } + break; + + case REPORT_ID_CONSUMER_CONTROL: + { + // use to avoid send multiple consecutive zero report + static bool has_consumer_key = false; + + if ( btn ) + { + // volume down + uint16_t volume_down = HID_USAGE_CONSUMER_VOLUME_DECREMENT; + tud_hid_report(REPORT_ID_CONSUMER_CONTROL, &volume_down, 2); + has_consumer_key = true; + }else + { + // send empty key report (release key) if previously has key pressed + uint16_t empty_key = 0; + if (has_consumer_key) tud_hid_report(REPORT_ID_CONSUMER_CONTROL, &empty_key, 2); + has_consumer_key = false; + } + } + break; + + case REPORT_ID_GAMEPAD: + { + // use to avoid send multiple consecutive zero report for keyboard + static bool has_gamepad_key = false; + + hid_gamepad_report_t report = + { + .x = 0, .y = 0, .z = 0, .rz = 0, .rx = 0, .ry = 0, + .hat = 0, .buttons = 0 + }; + + if ( btn ) + { + report.hat = GAMEPAD_HAT_UP; + report.buttons = GAMEPAD_BUTTON_A; + tud_hid_report(REPORT_ID_GAMEPAD, &report, sizeof(report)); + + has_gamepad_key = true; + }else + { + report.hat = GAMEPAD_HAT_CENTERED; + report.buttons = 0; + if (has_gamepad_key) tud_hid_report(REPORT_ID_GAMEPAD, &report, sizeof(report)); + has_gamepad_key = false; + } + } + break; + + default: break; + } +} + +// Every 10ms, we will sent 1 report for each HID profile (keyboard, mouse etc ..) +// tud_hid_report_complete_cb() is used to send the next report after previous one is complete +void hid_task(void) +{ + // Poll every 10ms + const uint32_t interval_ms = 10; + static uint32_t start_ms = 0; + + if ( board_millis() - start_ms < interval_ms) return; // not enough time + start_ms += interval_ms; + + uint32_t const btn = board_button_read(); + + // Remote wakeup + if ( tud_suspended() && btn ) + { + // Wake up host if we are in suspend mode + // and REMOTE_WAKEUP feature is enabled by host + tud_remote_wakeup(); + }else + { + // Send the 1st of report chain, the rest will be sent by tud_hid_report_complete_cb() + send_hid_report(REPORT_ID_KEYBOARD, btn); + } +} + +// Invoked when sent REPORT successfully to host +// Application can use this to send the next report +// Note: For composite reports, report[0] is report ID +void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint8_t len) +{ + (void) instance; + (void) len; + + uint8_t next_report_id = report[0] + 1; + + if (next_report_id < REPORT_ID_COUNT) + { + send_hid_report(next_report_id, board_button_read()); + } +} + +// Invoked when received GET_REPORT control request +// Application must fill buffer report's content and return its length. +// Return zero will cause the stack to STALL request +uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) +{ + // TODO not Implemented + (void) instance; + (void) report_id; + (void) report_type; + (void) buffer; + (void) reqlen; + + return 0; +} + +// Invoked when received SET_REPORT control request or +// received data on OUT endpoint ( Report ID = 0, Type = 0 ) +void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) +{ + (void) instance; + + if (report_type == HID_REPORT_TYPE_OUTPUT) + { + // Set keyboard LED e.g Capslock, Numlock etc... + if (report_id == REPORT_ID_KEYBOARD) + { + // bufsize should be (at least) 1 + if ( bufsize < 1 ) return; + + uint8_t const kbd_leds = buffer[0]; + + if (kbd_leds & KEYBOARD_LED_CAPSLOCK) + { + // Capslock On: disable blink, turn led on + blink_interval_ms = 0; + board_led_write(true); + }else + { + // Caplocks Off: back to normal blink + board_led_write(false); + blink_interval_ms = BLINK_MOUNTED; + } + } + } +} + +//--------------------------------------------------------------------+ +// BLINKING TASK +//--------------------------------------------------------------------+ +void led_blinking_task(void) +{ + static uint32_t start_ms = 0; + static bool led_state = false; + + // blink is disabled + if (!blink_interval_ms) return; + + // Blink every interval ms + if ( board_millis() - start_ms < blink_interval_ms) return; // not enough time + start_ms += blink_interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state; // toggle +} diff --git a/usb/device/dev_hid_composite/tusb_config.h b/usb/device/dev_hid_composite/tusb_config.h new file mode 100644 index 000000000..868424e6d --- /dev/null +++ b/usb/device/dev_hid_composite/tusb_config.h @@ -0,0 +1,111 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by board.mk +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_DEVICE_RHPORT_NUM + #define BOARD_DEVICE_RHPORT_NUM 0 +#endif + +// RHPort max operational speed can defined by board.mk +// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed +#ifndef BOARD_DEVICE_RHPORT_SPEED + #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ + CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X) + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED + #else + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + #endif +#endif + +// Device mode with rhport and speed defined by board.mk +#if BOARD_DEVICE_RHPORT_NUM == 0 + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#elif BOARD_DEVICE_RHPORT_NUM == 1 + #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#else + #error "Incorrect RHPort configuration" +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_HID 1 +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 16 + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/usb/device/dev_hid_composite/usb_descriptors.c b/usb/device/dev_hid_composite/usb_descriptors.c new file mode 100644 index 000000000..e760b20ba --- /dev/null +++ b/usb/device/dev_hid_composite/usb_descriptors.c @@ -0,0 +1,227 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "tusb.h" +#include "usb_descriptors.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) + +#define USB_VID 0xCafe +#define USB_BCD 0x0200 + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = USB_BCD, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// HID Report Descriptor +//--------------------------------------------------------------------+ + +uint8_t const desc_hid_report[] = +{ + TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(REPORT_ID_KEYBOARD )), + TUD_HID_REPORT_DESC_MOUSE ( HID_REPORT_ID(REPORT_ID_MOUSE )), + TUD_HID_REPORT_DESC_CONSUMER( HID_REPORT_ID(REPORT_ID_CONSUMER_CONTROL )), + TUD_HID_REPORT_DESC_GAMEPAD ( HID_REPORT_ID(REPORT_ID_GAMEPAD )) +}; + +// Invoked when received GET HID REPORT DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance) +{ + (void) instance; + return desc_hid_report; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +enum +{ + ITF_NUM_HID, + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN) + +#define EPNUM_HID 0x81 + +uint8_t const desc_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval + TUD_HID_DESCRIPTOR(ITF_NUM_HID, 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 5) +}; + +#if TUD_OPT_HIGH_SPEED +// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration + +// other speed configuration +uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN]; + +// device qualifier is mostly similar to device descriptor since we don't change configuration based on speed +tusb_desc_device_qualifier_t const desc_device_qualifier = +{ + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = USB_BCD, + + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0x00 +}; + +// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. +// device_qualifier descriptor describes information about a high-speed capable device that would +// change if the device were operating at the other speed. If not highspeed capable stall this request. +uint8_t const* tud_descriptor_device_qualifier_cb(void) +{ + return (uint8_t const*) &desc_device_qualifier; +} + +// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa +uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + + // other speed config is basically configuration with type = OHER_SPEED_CONFIG + memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN); + desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; + + // this example use the same configuration for both high and full speed mode + return desc_other_speed_config; +} + +#endif // highspeed + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + + // This example use the same configuration for both high and full speed mode + return desc_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "TinyUSB", // 1: Manufacturer + "TinyUSB Device", // 2: Product + "123456", // 3: Serials, should use chip ID +}; + +static uint16_t _desc_str[32]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void) langid; + + uint8_t chr_count; + + if ( index == 0) + { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + }else + { + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; + + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + if ( chr_count > 31 ) chr_count = 31; + + // Convert ASCII string into UTF-16 + for(uint8_t i=0; i CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped +// therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) +{ + printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); + + // Interface protocol (hid_interface_protocol_enum_t) + const char* protocol_str[] = { "None", "Keyboard", "Mouse" }; + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + + printf("HID Interface Protocol = %s\r\n", protocol_str[itf_protocol]); + + // By default host stack will use activate boot protocol on supported interface. + // Therefore for this simple example, we only need to parse generic report descriptor (with built-in parser) + if ( itf_protocol == HID_ITF_PROTOCOL_NONE ) + { + hid_info[instance].report_count = tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT, desc_report, desc_len); + printf("HID has %u reports \r\n", hid_info[instance].report_count); + } + + // request to receive report + // tuh_hid_report_received_cb() will be invoked when report is available + if ( !tuh_hid_receive_report(dev_addr, instance) ) + { + printf("Error: cannot request to receive report\r\n"); + } +} + +// Invoked when device with hid interface is un-mounted +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) +{ + printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); +} + +// Invoked when received report from device via interrupt endpoint +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) +{ + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + + switch (itf_protocol) + { + case HID_ITF_PROTOCOL_KEYBOARD: + TU_LOG2("HID receive boot keyboard report\r\n"); + process_kbd_report( (hid_keyboard_report_t const*) report ); + break; + + case HID_ITF_PROTOCOL_MOUSE: + TU_LOG2("HID receive boot mouse report\r\n"); + process_mouse_report( (hid_mouse_report_t const*) report ); + break; + + default: + // Generic report requires matching ReportID and contents with previous parsed report info + process_generic_report(dev_addr, instance, report, len); + break; + } + + // continue to request to receive report + if ( !tuh_hid_receive_report(dev_addr, instance) ) + { + printf("Error: cannot request to receive report\r\n"); + } +} + +//--------------------------------------------------------------------+ +// Keyboard +//--------------------------------------------------------------------+ + +// look up new key in previous keys +static inline bool find_key_in_report(hid_keyboard_report_t const *report, uint8_t keycode) +{ + for(uint8_t i=0; i<6; i++) + { + if (report->keycode[i] == keycode) return true; + } + + return false; +} + +static void process_kbd_report(hid_keyboard_report_t const *report) +{ + static hid_keyboard_report_t prev_report = { 0, 0, {0} }; // previous report to check key released + + //------------- example code ignore control (non-printable) key affects -------------// + for(uint8_t i=0; i<6; i++) + { + if ( report->keycode[i] ) + { + if ( find_key_in_report(&prev_report, report->keycode[i]) ) + { + // exist in previous report means the current key is holding + }else + { + // not existed in previous report means the current key is pressed + bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT); + uint8_t ch = keycode2ascii[report->keycode[i]][is_shift ? 1 : 0]; + putchar(ch); + if ( ch == '\r' ) putchar('\n'); // added new line for enter key + + fflush(stdout); // flush right away, else nanolib will wait for newline + } + } + // TODO example skips key released + } + + prev_report = *report; +} + +//--------------------------------------------------------------------+ +// Mouse +//--------------------------------------------------------------------+ + +void cursor_movement(int8_t x, int8_t y, int8_t wheel) +{ +#if USE_ANSI_ESCAPE + // Move X using ansi escape + if ( x < 0) + { + printf(ANSI_CURSOR_BACKWARD(%d), (-x)); // move left + }else if ( x > 0) + { + printf(ANSI_CURSOR_FORWARD(%d), x); // move right + } + + // Move Y using ansi escape + if ( y < 0) + { + printf(ANSI_CURSOR_UP(%d), (-y)); // move up + }else if ( y > 0) + { + printf(ANSI_CURSOR_DOWN(%d), y); // move down + } + + // Scroll using ansi escape + if (wheel < 0) + { + printf(ANSI_SCROLL_UP(%d), (-wheel)); // scroll up + }else if (wheel > 0) + { + printf(ANSI_SCROLL_DOWN(%d), wheel); // scroll down + } + + printf("\r\n"); +#else + printf("(%d %d %d)\r\n", x, y, wheel); +#endif +} + +static void process_mouse_report(hid_mouse_report_t const * report) +{ + static hid_mouse_report_t prev_report = { 0 }; + + //------------- button state -------------// + uint8_t button_changed_mask = report->buttons ^ prev_report.buttons; + if ( button_changed_mask & report->buttons) + { + printf(" %c%c%c ", + report->buttons & MOUSE_BUTTON_LEFT ? 'L' : '-', + report->buttons & MOUSE_BUTTON_MIDDLE ? 'M' : '-', + report->buttons & MOUSE_BUTTON_RIGHT ? 'R' : '-'); + } + + //------------- cursor movement -------------// + cursor_movement(report->x, report->y, report->wheel); +} + +//--------------------------------------------------------------------+ +// Generic Report +//--------------------------------------------------------------------+ +static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) +{ + (void) dev_addr; + + uint8_t const rpt_count = hid_info[instance].report_count; + tuh_hid_report_info_t* rpt_info_arr = hid_info[instance].report_info; + tuh_hid_report_info_t* rpt_info = NULL; + + if ( rpt_count == 1 && rpt_info_arr[0].report_id == 0) + { + // Simple report without report ID as 1st byte + rpt_info = &rpt_info_arr[0]; + }else + { + // Composite report, 1st byte is report ID, data starts from 2nd byte + uint8_t const rpt_id = report[0]; + + // Find report id in the arrray + for(uint8_t i=0; iusage_page == HID_USAGE_PAGE_DESKTOP ) + { + switch (rpt_info->usage) + { + case HID_USAGE_DESKTOP_KEYBOARD: + TU_LOG1("HID receive keyboard report\r\n"); + // Assume keyboard follow boot report layout + process_kbd_report( (hid_keyboard_report_t const*) report ); + break; + + case HID_USAGE_DESKTOP_MOUSE: + TU_LOG1("HID receive mouse report\r\n"); + // Assume mouse follow boot report layout + process_mouse_report( (hid_mouse_report_t const*) report ); + break; + + default: break; + } + } +} diff --git a/usb/host/host_cdc_msc_hid/main.c b/usb/host/host_cdc_msc_hid/main.c new file mode 100644 index 000000000..a14be05ea --- /dev/null +++ b/usb/host/host_cdc_msc_hid/main.c @@ -0,0 +1,128 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include +#include +#include + +#include "bsp/board.h" +#include "tusb.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ +void led_blinking_task(void); + +extern void cdc_task(void); +extern void hid_app_task(void); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + + printf("TinyUSB Host CDC MSC HID Example\r\n"); + + tusb_init(); + + while (1) + { + // tinyusb host task + tuh_task(); + led_blinking_task(); + +#if CFG_TUH_CDC + cdc_task(); +#endif + +#if CFG_TUH_HID + hid_app_task(); +#endif + } + + return 0; +} + +//--------------------------------------------------------------------+ +// USB CDC +//--------------------------------------------------------------------+ +#if CFG_TUH_CDC +CFG_TUSB_MEM_SECTION static char serial_in_buffer[64] = { 0 }; + +void tuh_mount_cb(uint8_t dev_addr) +{ + // application set-up + printf("A device with address %d is mounted\r\n", dev_addr); + + tuh_cdc_receive(dev_addr, serial_in_buffer, sizeof(serial_in_buffer), true); // schedule first transfer +} + +void tuh_umount_cb(uint8_t dev_addr) +{ + // application tear-down + printf("A device with address %d is unmounted \r\n", dev_addr); +} + +// invoked ISR context +void tuh_cdc_xfer_isr(uint8_t dev_addr, xfer_result_t event, cdc_pipeid_t pipe_id, uint32_t xferred_bytes) +{ + (void) event; + (void) pipe_id; + (void) xferred_bytes; + + printf(serial_in_buffer); + tu_memclr(serial_in_buffer, sizeof(serial_in_buffer)); + + tuh_cdc_receive(dev_addr, serial_in_buffer, sizeof(serial_in_buffer), true); // waiting for next data +} + +void cdc_task(void) +{ + +} + +#endif + +//--------------------------------------------------------------------+ +// TinyUSB Callbacks +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// Blinking Task +//--------------------------------------------------------------------+ +void led_blinking_task(void) +{ + const uint32_t interval_ms = 1000; + static uint32_t start_ms = 0; + + static bool led_state = false; + + // Blink every interval ms + if ( board_millis() - start_ms < interval_ms) return; // not enough time + start_ms += interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state; // toggle +} diff --git a/usb/host/host_cdc_msc_hid/msc_app.c b/usb/host/host_cdc_msc_hid/msc_app.c new file mode 100644 index 000000000..77a72052d --- /dev/null +++ b/usb/host/host_cdc_msc_hid/msc_app.c @@ -0,0 +1,106 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "tusb.h" + +#if CFG_TUH_MSC + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +static scsi_inquiry_resp_t inquiry_resp; + +bool inquiry_complete_cb(uint8_t dev_addr, msc_cbw_t const* cbw, msc_csw_t const* csw) +{ + if (csw->status != 0) + { + printf("Inquiry failed\r\n"); + return false; + } + + // Print out Vendor ID, Product ID and Rev + printf("%.8s %.16s rev %.4s\r\n", inquiry_resp.vendor_id, inquiry_resp.product_id, inquiry_resp.product_rev); + + // Get capacity of device + uint32_t const block_count = tuh_msc_get_block_count(dev_addr, cbw->lun); + uint32_t const block_size = tuh_msc_get_block_size(dev_addr, cbw->lun); + + printf("Disk Size: %lu MB\r\n", block_count / ((1024*1024)/block_size)); + printf("Block Count = %lu, Block Size: %lu\r\n", block_count, block_size); + + return true; +} + +//------------- IMPLEMENTATION -------------// +void tuh_msc_mount_cb(uint8_t dev_addr) +{ + printf("A MassStorage device is mounted\r\n"); + + uint8_t const lun = 0; + tuh_msc_inquiry(dev_addr, lun, &inquiry_resp, inquiry_complete_cb); +// +// //------------- file system (only 1 LUN support) -------------// +// uint8_t phy_disk = dev_addr-1; +// disk_initialize(phy_disk); +// +// if ( disk_is_ready(phy_disk) ) +// { +// if ( f_mount(phy_disk, &fatfs[phy_disk]) != FR_OK ) +// { +// puts("mount failed"); +// return; +// } +// +// f_chdrive(phy_disk); // change to newly mounted drive +// f_chdir("/"); // root as current dir +// +// cli_init(); +// } +} + +void tuh_msc_umount_cb(uint8_t dev_addr) +{ + (void) dev_addr; + printf("A MassStorage device is unmounted\r\n"); + +// uint8_t phy_disk = dev_addr-1; +// +// f_mount(phy_disk, NULL); // unmount disk +// disk_deinitialize(phy_disk); +// +// if ( phy_disk == f_get_current_drive() ) +// { // active drive is unplugged --> change to other drive +// for(uint8_t i=0; i