Initial Release

This commit is contained in:
graham sanderson
2021-01-20 10:49:34 -06:00
commit 46078742c7
245 changed files with 21157 additions and 0 deletions

16
pio/i2c/CMakeLists.txt Normal file
View File

@@ -0,0 +1,16 @@
add_executable(pio_i2c_bus_scan)
pico_generate_pio_header(pio_i2c_bus_scan ${CMAKE_CURRENT_LIST_DIR}/i2c.pio)
target_sources(pio_i2c_bus_scan PRIVATE
i2c_bus_scan.c
pio_i2c.c
pio_i2c.h
)
target_link_libraries(pio_i2c_bus_scan PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_i2c_bus_scan)
# add url via pico_set_program_url
example_auto_set_url(pio_i2c_bus_scan)

144
pio/i2c/i2c.pio Normal file
View File

@@ -0,0 +1,144 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program i2c
.side_set 1 opt pindirs
; TX Encoding:
; | 15:10 | 9 | 8:1 | 0 |
; | Instr | Final | Data | NAK |
;
; If Instr has a value n > 0, then this FIFO word has no
; data payload, and the next n + 1 words will be executed as instructions.
; Otherwise, shift out the 8 data bits, followed by the ACK bit.
;
; The Instr mechanism allows stop/start/repstart sequences to be programmed
; by the processor, and then carried out by the state machine at defined points
; in the datastream.
;
; The "Final" field should be set for the final byte in a transfer.
; This tells the state machine to ignore a NAK: if this field is not
; set, then any NAK will cause the state machine to halt and interrupt.
;
; Autopull should be enabled, with a threshold of 16.
; Autopush should be enabled, with a threshold of 8.
; The TX FIFO should be accessed with halfword writes, to ensure
; the data is immediately available in the OSR.
;
; Pin mapping:
; - Input pin 0 is SDA, 1 is SCL (if clock stretching used)
; - Jump pin is SDA
; - Side-set pin 0 is SCL
; - Set pin 0 is SDA
; - OUT pin 0 is SDA
; - SCL must be SDA + 1 (for wait mapping)
;
; The OE outputs should be inverted in the system IO controls!
; (It's possible for the inversion to be done in this program,
; but costs 2 instructions: 1 for inversion, and one to cope
; with the side effect of the MOV on TX shift counter.)
do_nack:
jmp y-- entry_point ; Continue if NAK was expected
irq wait 0 rel ; Otherwise stop, ask for help
do_byte:
set x, 7 ; Loop 8 times
bitloop:
out pindirs, 1 [7] ; Serialise write data (all-ones if reading)
nop side 1 [2] ; SCL rising edge
wait 1 pin, 1 [4] ; Allow clock to be stretched
in pins, 1 [7] ; Sample read data in middle of SCL pulse
jmp x-- bitloop side 0 [7] ; SCL falling edge
; Handle ACK pulse
out pindirs, 1 [7] ; On reads, we provide the ACK.
nop side 1 [7] ; SCL rising edge
wait 1 pin, 1 [7] ; Allow clock to be stretched
jmp pin do_nack side 0 [2] ; Test SDA for ACK/NAK, fall through if ACK
public entry_point:
.wrap_target
out x, 6 ; Unpack Instr count
out y, 1 ; Unpack the NAK ignore bit
jmp !x do_byte ; Instr == 0, this is a data record.
out null, 32 ; Instr > 0, remainder of this OSR is invalid
do_exec:
out exec, 16 ; Execute one instruction per FIFO word
jmp x-- do_exec ; Repeat n + 1 times
.wrap
% c-sdk {
#include "hardware/clocks.h"
#include "hardware/gpio.h"
static inline void i2c_program_init(PIO pio, uint sm, uint offset, uint pin_sda, uint pin_scl) {
assert(pin_scl == pin_sda + 1);
pio_sm_config c = i2c_program_get_default_config(offset);
// IO mapping
sm_config_set_out_pins(&c, pin_sda, 1);
sm_config_set_set_pins(&c, pin_sda, 1);
sm_config_set_in_pins(&c, pin_sda);
sm_config_set_sideset_pins(&c, pin_scl);
sm_config_set_jmp_pin(&c, pin_sda);
sm_config_set_out_shift(&c, false, true, 16);
sm_config_set_in_shift(&c, false, true, 8);
float div = (float)clock_get_hz(clk_sys) / (32 * 100000);
sm_config_set_clkdiv(&c, div);
// Try to avoid glitching the bus while connecting the IOs. Get things set
// up so that pin is driven down when PIO asserts OE low, and pulled up
// otherwise.
gpio_pull_up(pin_scl);
gpio_pull_up(pin_sda);
uint32_t both_pins = (1u << pin_sda) | (1u << pin_scl);
pio_sm_set_pins_with_mask(pio, sm, both_pins, both_pins);
pio_sm_set_pindirs_with_mask(pio, sm, both_pins, both_pins);
pio_gpio_init(pio, pin_sda);
gpio_set_oeover(pin_sda, GPIO_OVERRIDE_INVERT);
pio_gpio_init(pio, pin_scl);
gpio_set_oeover(pin_scl, GPIO_OVERRIDE_INVERT);
pio_sm_set_pins_with_mask(pio, sm, 0, both_pins);
// Clear IRQ flag before starting
hw_clear_bits(&pio->inte0, 1u << sm);
hw_clear_bits(&pio->inte1, 1u << sm);
pio->irq = 1u << sm;
// Configure and start SM
pio_sm_init(pio, sm, offset + i2c_offset_entry_point, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
.program set_scl_sda
.side_set 1 opt
; Assemble a table of instructions which software can select from, and pass
; into the FIFO, to issue START/STOP/RSTART. This isn't intended to be run as
; a complete program.
set pindirs, 0 side 0 [7] ; SCL = 0, SDA = 0
set pindirs, 1 side 0 [7] ; SCL = 0, SDA = 1
set pindirs, 0 side 1 [7] ; SCL = 1, SDA = 0
set pindirs, 1 side 1 [7] ; SCL = 1, SDA = 1
% c-sdk {
// Define order of our instruction table
enum {
I2C_SC0_SD0 = 0,
I2C_SC0_SD1,
I2C_SC1_SD0,
I2C_SC1_SD1
};
%}

42
pio/i2c/i2c_bus_scan.c Normal file
View File

@@ -0,0 +1,42 @@
#include <stdio.h>
#include "pico/stdlib.h"
#include "pio_i2c.h"
#define PIN_SDA 2
#define PIN_SCL 3
bool reserved_addr(uint8_t addr) {
return (addr & 0x78) == 0 || (addr & 0x78) == 0x78;
}
int main() {
stdio_init_all();
PIO pio = pio0;
uint sm = 0;
uint offset = pio_add_program(pio, &i2c_program);
i2c_program_init(pio, sm, offset, PIN_SDA, PIN_SCL);
printf("\nPIO I2C Bus Scan\n");
printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
for (int addr = 0; addr < (1 << 7); ++addr) {
if (addr % 16 == 0) {
printf("%02x ", addr);
}
// Perform a 0-byte read from the probe address. The read function
// returns a negative result NAK'd any time other than the last data
// byte. Skip over reserved addresses.
int result;
if (reserved_addr(addr))
result = -1;
else
result = pio_i2c_read_blocking(pio, sm, addr, NULL, 0);
printf(result < 0 ? "." : "@");
printf(addr % 16 == 15 ? "\n" : " ");
}
printf("Done.\n");
return 0;
}

139
pio/i2c/pio_i2c.c Normal file
View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pio_i2c.h"
const int PIO_I2C_ICOUNT_LSB = 10;
const int PIO_I2C_FINAL_LSB = 9;
const int PIO_I2C_DATA_LSB = 1;
const int PIO_I2C_NAK_LSB = 0;
bool pio_i2c_check_error(PIO pio, uint sm) {
return !!(pio->irq & (1u << sm));
}
void pio_i2c_resume_after_error(PIO pio, uint sm) {
pio_sm_drain_tx_fifo(pio, sm);
pio_sm_exec(pio, sm, (pio->sm[sm].execctrl & PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS) >> PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
pio->irq = 1u << sm;
}
void pio_i2c_rx_enable(PIO pio, uint sm, bool en) {
if (en)
hw_set_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);
else
hw_clear_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);
}
static inline void pio_i2c_put16(PIO pio, uint sm, uint16_t data) {
while (pio_sm_is_tx_fifo_full(pio, sm))
;
*(io_rw_16 *)&pio->txf[sm] = data;
}
// If I2C is ok, block and push data. Otherwise fall straight through.
void pio_i2c_put_or_err(PIO pio, uint sm, uint16_t data) {
while (pio_sm_is_tx_fifo_full(pio, sm))
if (pio_i2c_check_error(pio, sm))
return;
if (pio_i2c_check_error(pio, sm))
return;
*(io_rw_16 *)&pio->txf[sm] = data;
}
uint8_t pio_i2c_get(PIO pio, uint sm) {
return (uint8_t)pio_sm_get(pio, sm);
}
void pio_i2c_start(PIO pio, uint sm) {
pio_i2c_put_or_err(pio, sm, 1u << PIO_I2C_ICOUNT_LSB); // Escape code for 2 instruction sequence
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]); // We are already in idle state, just pull SDA low
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]); // Also pull clock low so we can present data
}
void pio_i2c_stop(PIO pio, uint sm) {
pio_i2c_put_or_err(pio, sm, 2u << PIO_I2C_ICOUNT_LSB);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]); // SDA is unknown; pull it down
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]); // Release clock
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]); // Release SDA to return to idle state
};
void pio_i2c_repstart(PIO pio, uint sm) {
pio_i2c_put_or_err(pio, sm, 3u << PIO_I2C_ICOUNT_LSB);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD1]);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]);
}
static void pio_i2c_wait_idle(PIO pio, uint sm) {
// Finished when TX runs dry or SM hits an IRQ
pio->fdebug = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm);
while (!(pio->fdebug & 1u << (PIO_FDEBUG_TXSTALL_LSB + sm) || pio_i2c_check_error(pio, sm)))
tight_loop_contents();
}
int pio_i2c_write_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *txbuf, uint len) {
int err = 0;
pio_i2c_start(pio, sm);
pio_i2c_rx_enable(pio, sm, false);
pio_i2c_put16(pio, sm, (addr << 2) | 1u);
while (len && !pio_i2c_check_error(pio, sm)) {
if (!pio_sm_is_tx_fifo_full(pio, sm)) {
--len;
pio_i2c_put_or_err(pio, sm, (*txbuf++ << PIO_I2C_DATA_LSB) | ((len == 0) << PIO_I2C_FINAL_LSB) | 1u);
}
}
pio_i2c_stop(pio, sm);
pio_i2c_wait_idle(pio, sm);
if (pio_i2c_check_error(pio, sm)) {
err = -1;
pio_i2c_resume_after_error(pio, sm);
pio_i2c_stop(pio, sm);
}
return err;
}
int pio_i2c_read_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *rxbuf, uint len) {
int err = 0;
pio_i2c_start(pio, sm);
pio_i2c_rx_enable(pio, sm, true);
while (!pio_sm_is_rx_fifo_empty(pio, sm))
(void)pio_i2c_get(pio, sm);
pio_i2c_put16(pio, sm, (addr << 2) | 3u);
uint32_t tx_remain = len; // Need to stuff 0xff bytes in to get clocks
bool first = true;
while ((tx_remain || len) && !pio_i2c_check_error(pio, sm)) {
if (tx_remain && !pio_sm_is_tx_fifo_full(pio, sm)) {
--tx_remain;
pio_i2c_put16(pio, sm, (0xffu << 1) | (tx_remain ? 0 : (1u << PIO_I2C_FINAL_LSB) | (1u << PIO_I2C_NAK_LSB)));
}
if (!pio_sm_is_rx_fifo_empty(pio, sm)) {
if (first) {
// Ignore returned address byte
(void)pio_i2c_get(pio, sm);
first = false;
}
else {
--len;
*rxbuf++ = pio_i2c_get(pio, sm);
}
}
}
pio_i2c_stop(pio, sm);
pio_i2c_wait_idle(pio, sm);
if (pio_i2c_check_error(pio, sm)) {
err = -1;
pio_i2c_resume_after_error(pio, sm);
pio_i2c_stop(pio, sm);
}
return err;
}

31
pio/i2c/pio_i2c.h Normal file
View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PIO_I2C_H
#define _PIO_I2C_H
#include "i2c.pio.h"
// ----------------------------------------------------------------------------
// Low-level functions
void pio_i2c_start(PIO pio, uint sm);
void pio_i2c_stop(PIO pio, uint sm);
void pio_i2c_repstart(PIO pio, uint sm);
bool pio_i2c_check_error(PIO pio, uint sm);
void pio_i2c_resume_after_error(PIO pio, uint sm);
// If I2C is ok, block and push data. Otherwise fall straight through.
void pio_i2c_put_or_err(PIO pio, uint sm, uint16_t data);
uint8_t pio_i2c_get(PIO pio, uint sm);
// ----------------------------------------------------------------------------
// Transaction-level functions
int pio_i2c_write_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *txbuf, uint len);
int pio_i2c_read_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *rxbuf, uint len);
#endif