Initial Release
This commit is contained in:
16
pio/i2c/CMakeLists.txt
Normal file
16
pio/i2c/CMakeLists.txt
Normal 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
144
pio/i2c/i2c.pio
Normal 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
42
pio/i2c/i2c_bus_scan.c
Normal 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
139
pio/i2c/pio_i2c.c
Normal 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
31
pio/i2c/pio_i2c.h
Normal 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
|
||||
Reference in New Issue
Block a user