Add pio onewire example (#334)

* Adds PIO onewire example

Co-authored-by: martin <admin@crossleys.biz>
This commit is contained in:
mjcross
2023-03-26 22:48:23 +01:00
committed by GitHub
parent 56b4522ce4
commit 9a82398d82
12 changed files with 434 additions and 1 deletions

View File

@@ -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}
)

View File

@@ -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;
}

View File

@@ -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);

View File

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