diff --git a/README.md b/README.md index 0dffdd2..ccab566 100644 --- a/README.md +++ b/README.md @@ -216,12 +216,14 @@ App|Description ---|--- [hello_pio](pio/hello_pio) | Absolutely minimal example showing how to control an LED by pushing values into a PIO FIFO. [apa102](pio/apa102) | Rainbow pattern on on a string of APA102 addressable RGB LEDs. +[clocked_input](pio/clocked_input) | Shift in serial data, sampling with an external clock. [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. +[onewire](pio/onewire)| A library for interfacing to 1-Wire devices, with an example for the DS18B20 temperature sensor. [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. [pwm](pio/pwm) | Pulse width modulation on PIO. Use it to gradually fade the brightness of an LED. [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. @@ -232,7 +234,6 @@ App|Description [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. [addition](pio/addition) | Add two integers together using PIO. Only around 8 billion times slower than Cortex-M0+. -[clocked_input](pio/clocked_input) | Shift in serial data, sampling with an external clock. ### PWM diff --git a/pio/onewire/CMakeLists.txt b/pio/onewire/CMakeLists.txt new file mode 100644 index 0000000..4090cb3 --- /dev/null +++ b/pio/onewire/CMakeLists.txt @@ -0,0 +1,15 @@ +add_executable(pio_onewire) + +target_sources(pio_onewire PRIVATE onewire.c) + +add_subdirectory(onewire_library) + +target_link_libraries(pio_onewire PRIVATE + pico_stdlib + hardware_pio + onewire_library) + +pico_add_extra_outputs(pio_onewire) + +# add url via pico_set_program_url +example_auto_set_url(pio_onewire) diff --git a/pio/onewire/README.adoc b/pio/onewire/README.adoc new file mode 100644 index 0000000..a10e02f --- /dev/null +++ b/pio/onewire/README.adoc @@ -0,0 +1,57 @@ += Interfacing 1-Wire devices to the Pico + +This example demonstrates how to use link:https://www.analog.com/en/technical-articles/guide-to-1wire-communication.html[1-Wire] devices with the Raspberry Pi Pico (RP2040). +1-Wire is an interface that enables a master to control several slave devices over a simple shared serial bus. + +The example provides a 1-Wire library that is used to take readings from a set of connected link:https://www.analog.com/media/en/technical-documentation/data-sheets/ds18b20.pdf[DS18B20] 1-Wire temperature sensors. The results are sent to the default serial terminal connected via USB or UART as configured in the SDK. + +The library uses a driver based on the RP2040 PIO state machine to generate accurate bus timings and control the 1-Wire bus via a GPIO pin. + +_1-Wire(R) is a registered trademark of Maxim Integrated Products, Inc._ + +== Wiring information + +Connect one or more DS18B20 sensors to the Pico as shown in the diagram and table below. + +Connect GPIO 15 to 3V3(OUT) with a pull-up resistor of about 4k ohms. + +[[pio_onewire_wiring-diagram]] +[pdfwidth=75%] +.Wiring diagram +image::pio_onewire.png[] + +[[pio_onewire_connections-table]] +.Connections table +[options="header,footer"] +|================================================== +|Pico |pin |DS18B20 |pin / sensor wire +|GND |38 or equivalent |GND |1 / Black +|GPIO 15 |20 |DQ |2 / Yellow +|3V3(OUT)|36 |VDD |3 / Red +|================================================== + +== Bill of materials + +.A list of materials for the example circuit +[[pio_onewire_bom-table]] +[cols=3] +|=== +| *Item* | *Quantity* | *Details* +| Breadboard | 1 | generic part +| Raspberry Pi Pico | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| DS18B20 | 3 | chip or wired sensor +| 3900 ohm resistor | 1 | generic part +| M/M jumper wire | 13 | generic part +|=== + +== List of files +[[pio_onewire_file-list]] +CMakeLists.txt:: CMake file to incorporate the example in the build tree. +onewire.c:: Source code for the example program. +ow_rom.h:: Header file with generic ROM command codes for 1-Wire devices. +ds18b20.h:: Header file with function command codes for the DS18B20 device. +onewire_library/:: Subdirectory containing the 1-Wire library and driver. +onewire_library/CMakeLists.txt:: CMake file to build the 1-Wire library and driver. +onewire_library/onewire_library.c:: Source code for the 1-Wire user functions. +onewire_library/onewire_library.h:: Header file for the 1-Wire user functions and types. +onewire_library/onewire_library.pio:: PIO assembly code for the 1-Wire driver. diff --git a/pio/onewire/ds18b20.h b/pio/onewire/ds18b20.h new file mode 100644 index 0000000..64f4c09 --- /dev/null +++ b/pio/onewire/ds18b20.h @@ -0,0 +1,9 @@ +// Function commands for d218b20 1-Wire temperature sensor +// https://www.analog.com/en/products/ds18b20.html +// +#define DS18B20_CONVERT_T 0x44 +#define DS18B20_WRITE_SCRATCHPAD 0x4e +#define DS18B20_READ_SCRATCHPAD 0xbe +#define DS18B20_COPY_SCRATCHPAD 0x48 +#define DS18B20_RECALL_EE 0xb8 +#define DS18B20_READ_POWER_SUPPLY 0xb4 \ No newline at end of file diff --git a/pio/onewire/onewire.c b/pio/onewire/onewire.c new file mode 100644 index 0000000..8df8ba1 --- /dev/null +++ b/pio/onewire/onewire.c @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2023 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause +**/ + +#include +#include "pico/stdlib.h" +#include "pico/binary_info.h" + +#include "onewire_library.h" // onewire library functions +#include "ow_rom.h" // onewire ROM command codes +#include "ds18b20.h" // ds18b20 function codes + +// Demonstrates the PIO onewire driver by taking readings from a set of +// ds18b20 1-wire temperature sensors. + +int main() { + stdio_init_all(); + + PIO pio = pio0; + uint gpio = 15; + + OW ow; + uint offset; + // add the program to the PIO shared address space + if (pio_can_add_program (pio, &onewire_program)) { + offset = pio_add_program (pio, &onewire_program); + + // claim a state machine and initialise a driver instance + if (ow_init (&ow, pio, offset, gpio)) { + + // find and display 64-bit device addresses + int maxdevs = 10; + uint64_t romcode[maxdevs]; + int num_devs = ow_romsearch (&ow, romcode, maxdevs, OW_SEARCH_ROM); + + printf("Found %d devices\n", num_devs); + for (int i = 0; i < num_devs; i += 1) { + printf("\t%d: 0x%llx\n", i, romcode[i]); + } + putchar ('\n'); + + while (num_devs > 0) { + // start temperature conversion in parallel on all devices + // (see ds18b20 datasheet) + ow_reset (&ow); + ow_send (&ow, OW_SKIP_ROM); + ow_send (&ow, DS18B20_CONVERT_T); + + // wait for the conversions to finish + while (ow_read(&ow) == 0); + + // read the result from each device + for (int i = 0; i < num_devs; i += 1) { + ow_reset (&ow); + ow_send (&ow, OW_MATCH_ROM); + for (int b = 0; b < 64; b += 8) { + ow_send (&ow, romcode[i] >> b); + } + ow_send (&ow, DS18B20_READ_SCRATCHPAD); + int16_t temp = 0; + temp = ow_read (&ow) | (ow_read (&ow) << 8); + printf ("\t%d: %f", i, temp / 16.0); + } + putchar ('\n'); + } + + } else { + puts ("could not initialise the driver"); + } + } else { + puts ("could not add the program"); + } + + while(true); +} \ No newline at end of file diff --git a/pio/onewire/onewire_library/CMakeLists.txt b/pio/onewire/onewire_library/CMakeLists.txt new file mode 100644 index 0000000..cf5a8db --- /dev/null +++ b/pio/onewire/onewire_library/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(onewire_library INTERFACE) +target_sources(onewire_library onewire_library.c) + +# invoke pio_asm to assemble the state machine programs +# +pico_generate_pio_header(onewire_library ${CMAKE_CURRENT_LIST_DIR}/onewire_library.pio) + +target_link_libraries(onewire_library INTERFACE + pico_stdlib + hardware_pio + ) + +# add the `binary` directory so that the generated headers are included in the project +# +target_include_directories(onewire_library INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ) diff --git a/pio/onewire/onewire_library/onewire_library.c b/pio/onewire/onewire_library/onewire_library.c new file mode 100644 index 0000000..1d7c6c8 --- /dev/null +++ b/pio/onewire/onewire_library/onewire_library.c @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2023 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause +**/ + +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/pio.h" + +#include "onewire_library.h" + + +// Create a driver instance and populate the provided OW structure. +// Returns: True on success. +// ow: A pointer to a blank OW structure to hold the driver parameters. +// pio: The PIO hardware instance to use. +// offset: The location of the onewire program in the PIO shared address space. +// gpio: The pin to use for the bus (NB: see the README). +bool ow_init (OW *ow, PIO pio, uint offset, uint gpio) { + int sm = pio_claim_unused_sm (pio, false); + if (sm == -1) { + return false; + } + gpio_init (gpio); // enable the gpio and clear any output value + pio_gpio_init (pio, gpio); // set the function to PIO output + ow->gpio = gpio; + ow->pio = pio; + ow->offset = offset; + ow->sm = (uint)sm; + ow->jmp_reset = onewire_reset_instr (ow->offset); // assemble the bus reset instruction + onewire_sm_init (ow->pio, ow->sm, ow->offset, ow->gpio, 8); // set 8 bits per word + return true; +} + + +// Send a binary word on the bus (LSB first). +// ow: A pointer to an OW driver struct. +// data: The word to be sent. +void ow_send (OW *ow, uint data) { + pio_sm_put_blocking (ow->pio, ow->sm, (uint32_t)data); + pio_sm_get_blocking (ow->pio, ow->sm); // discard the response +} + + +// Read a binary word from the bus. +// Returns: the word read (LSB first). +// ow: pointer to an OW driver struct +uint8_t ow_read (OW *ow) { + pio_sm_put_blocking (ow->pio, ow->sm, 0xff); // generate read slots + return (uint8_t)(pio_sm_get_blocking (ow->pio, ow->sm) >> 24); // shift response into bits 0..7 +} + + +// Reset the bus and detect any connected slaves. +// Returns: true if any slaves responded. +// ow: pointer to an OW driver struct +bool ow_reset (OW *ow) { + pio_sm_exec_wait_blocking (ow->pio, ow->sm, ow->jmp_reset); + if ((pio_sm_get_blocking (ow->pio, ow->sm) & 1) == 0) { // apply pin mask (see pio program) + return true; // a slave pulled the bus low + } + return false; +} + + +// Find ROM codes (64-bit hardware addresses) of all connected devices. +// See https://www.analog.com/en/app-notes/1wire-search-algorithm.html +// Returns: the number of devices found (up to maxdevs) or -1 if an error occurrred. +// ow: pointer to an OW driver struct +// romcodes: location at which store the addresses (NULL means don't store) +// maxdevs: maximum number of devices to find (0 means no limit) +// command: 1-Wire search command (e.g. OW_SEARCHROM or OW_ALARM_SEARCH) +int ow_romsearch (OW *ow, uint64_t *romcodes, int maxdevs, uint command) { + int index; + uint64_t romcode = 0ull; + int branch_point; + int next_branch_point = -1; + int num_found = 0; + bool finished = false; + + onewire_sm_init (ow->pio, ow->sm, ow->offset, ow->gpio, 1); // set driver to 1-bit mode + + while (finished == false && (maxdevs == 0 || num_found < maxdevs )) { + finished = true; + branch_point = next_branch_point; + if (ow_reset (ow) == false) { + num_found = 0; // no slaves present + finished = true; + break; + } + for (int i = 0; i < 8; i += 1) { // send search command as single bits + ow_send (ow, command >> i); + } + for (index = 0; index < 64; index += 1) { // determine romcode bits 0..63 (see ref) + uint a = ow_read (ow); + uint b = ow_read (ow); + if (a == 0 && b == 0) { // (a, b) = (0, 0) + if (index == branch_point) { + ow_send (ow, 1); + romcode |= (1ull << index); + } else { + if (index > branch_point || (romcode & (1ull << index)) == 0) { + ow_send(ow, 0); + finished = false; + romcode &= ~(1ull << index); + next_branch_point = index; + } else { // index < branch_point or romcode[index] = 1 + ow_send (ow, 1); + } + } + } else if (a != 0 && b != 0) { // (a, b) = (1, 1) error (e.g. device disconnected) + num_found = -2; // function will return -1 + finished = true; + break; // terminate for loop + } else { + if (a == 0) { // (a, b) = (0, 1) or (1, 0) + ow_send (ow, 0); + romcode &= ~(1ull << index); + } else { + ow_send (ow, 1); + romcode |= (1ull << index); + } + } + } // end of for loop + + if (romcodes != NULL) { + romcodes[num_found] = romcode; // store the romcode + } + num_found += 1; + } // end of while loop + + onewire_sm_init (ow->pio, ow->sm, ow->offset, ow->gpio, 8); // restore 8-bit mode + return num_found; +} \ No newline at end of file diff --git a/pio/onewire/onewire_library/onewire_library.h b/pio/onewire/onewire_library/onewire_library.h new file mode 100644 index 0000000..fd96312 --- /dev/null +++ b/pio/onewire/onewire_library/onewire_library.h @@ -0,0 +1,17 @@ +#include "hardware/pio.h" +#include "hardware/clocks.h" // for clock_get_hz() in generated header +#include "onewire_library.pio.h" // generated by pioasm + +typedef struct { + PIO pio; + uint sm; + uint jmp_reset; + int offset; + int gpio; +} OW; + +bool ow_init (OW *ow, PIO pio, uint offset, uint gpio); +void ow_send (OW *ow, uint data); +uint8_t ow_read (OW *ow); +bool ow_reset (OW *ow); +int ow_romsearch (OW *ow, uint64_t *romcodes, int maxdevs, uint command); \ No newline at end of file diff --git a/pio/onewire/onewire_library/onewire_library.pio b/pio/onewire/onewire_library/onewire_library.pio new file mode 100644 index 0000000..bc633dc --- /dev/null +++ b/pio/onewire/onewire_library/onewire_library.pio @@ -0,0 +1,96 @@ +; +; Copyright (c) 2023 mjcross +; +; SPDX-License-Identifier: BSD-3-Clause +; + +; Implements a Maxim 1-Wire bus with a GPIO pin. +; +; Place data words to be transmitted in the TX FIFO and read the results from the +; RX FIFO. To reset the bus execute a jump to 'reset_bus' using the opcode from +; the provided function. +; +; At 1us per cycle as initialised below the timings are those recommended by: +; https://www.analog.com/en/technical-articles/1wire-communication-through-software.html +; +; Notes: +; (1) The code will stall with the bus in a safe state if the FIFOs are empty/full. +; (2) The bus must be pulled up with an external pull-up resistor of about 4k. +; The internal GPIO resistors are too high (~50k) to work reliably for this. +; (3) Do not connect the GPIO pin directly to a bus powered at more than 3.3V. + +.program onewire +.side_set 1 pindirs + +PUBLIC reset_bus: + set x, 28 side 1 [15] ; pull bus low 16 +loop_a: jmp x-- loop_a side 1 [15] ; 29 x 16 + set x, 8 side 0 [6] ; release bus 7 +loop_b: jmp x-- loop_b side 0 [6] ; 9 x 7 + + mov isr, pins side 0 ; read all pins to ISR (avoids autopush) 1 + push side 0 ; push result manually 1 + set x, 24 side 0 [7] ; 8 +loop_c: jmp x-- loop_c side 0 [15] ; 25 x 16 + +.wrap_target +PUBLIC fetch_bit: + out x, 1 side 0 ; shift next bit from OSR (autopull) 1 + jmp !x send_0 side 1 [5] ; pull bus low, branch if sending '0' 6 + +send_1: ; send a '1' bit + set x, 2 side 0 [8] ; release bus, wait for slave response 9 + in pins, 1 side 0 [4] ; read bus, shift bit to ISR (autopush) 5 +loop_e: jmp x-- loop_e side 0 [15] ; 3 x 16 + jmp fetch_bit side 0 ; 1 + +send_0: ; send a '0' bit + set x, 2 side 1 [5] ; continue pulling bus low 6 +loop_d: jmp x-- loop_d side 1 [15] ; 3 x 16 + in null, 1 side 0 [8] ; release bus, shift 0 to ISR (autopush) 9 +.wrap +;; (17 instructions) + + +% c-sdk { +static inline void onewire_sm_init (PIO pio, uint sm, uint offset, uint pin_num, uint bits_per_word) { + + // create a new state machine configuration + pio_sm_config c = onewire_program_get_default_config (offset); + + // Input Shift Register configuration settings + sm_config_set_in_shift ( + &c, + true, // shift direction: right + true, // autopush: enabled + bits_per_word // autopush threshold + ); + + // Output Shift Register configuration settings + sm_config_set_out_shift ( + &c, + true, // shift direction: right + true, // autopull: enabled + bits_per_word // autopull threshold + ); + + // configure the input and sideset pin groups to start at `pin_num` + sm_config_set_in_pins (&c, pin_num); + sm_config_set_sideset_pins (&c, pin_num); + + // configure the clock divider for 1 usec per instruction + float div = clock_get_hz (clk_sys) * 1e-6; + sm_config_set_clkdiv (&c, div); + + // apply the configuration and initialise the program counter + pio_sm_init (pio, sm, offset + onewire_offset_fetch_bit, &c); + + // enable the state machine + pio_sm_set_enabled (pio, sm, true); +} + +static inline uint onewire_reset_instr (uint offset) { + // encode a "jmp reset_bus side 0" instruction for the state machine + return pio_encode_jmp (offset + onewire_offset_reset_bus) | pio_encode_sideset (1, 0); +} +%} diff --git a/pio/onewire/ow_rom.h b/pio/onewire/ow_rom.h new file mode 100644 index 0000000..787aed6 --- /dev/null +++ b/pio/onewire/ow_rom.h @@ -0,0 +1,8 @@ +// Generic ROM commands for 1-Wire devices +// https://www.analog.com/en/technical-articles/guide-to-1wire-communication.html +// +#define OW_READ_ROM 0x33 +#define OW_MATCH_ROM 0x55 +#define OW_SKIP_ROM 0xCC +#define OW_ALARM_SEARCH 0xEC +#define OW_SEARCH_ROM 0xF0 \ No newline at end of file diff --git a/pio/onewire/pio_onewire.fzz b/pio/onewire/pio_onewire.fzz new file mode 100644 index 0000000..166c68f Binary files /dev/null and b/pio/onewire/pio_onewire.fzz differ diff --git a/pio/onewire/pio_onewire.png b/pio/onewire/pio_onewire.png new file mode 100644 index 0000000..964328d Binary files /dev/null and b/pio/onewire/pio_onewire.png differ