::enabled() { return true; };
#define DEBUG_PINS_ENABLED(p) (__debug_pin_settings::enabled())
#endif
#define DEBUG_PINS_SET(p, v) if (DEBUG_PINS_ENABLED(p)) gpio_set_mask((unsigned)(v)<restart_on_next = false;
i2c->hw->enable = 0;
// Configure as a fast-mode master with RepStart support, 7-bit addresses
i2c->hw->con =
I2C_IC_CON_SPEED_VALUE_FAST << I2C_IC_CON_SPEED_LSB |
I2C_IC_CON_MASTER_MODE_BITS |
I2C_IC_CON_IC_SLAVE_DISABLE_BITS |
I2C_IC_CON_IC_RESTART_EN_BITS |
I2C_IC_CON_TX_EMPTY_CTRL_BITS;
// Set FIFO watermarks to 1 to make things simpler. This is encoded by a register value of 0.
i2c->hw->tx_tl = 0;
i2c->hw->rx_tl = 0;
// Always enable the DREQ signalling -- harmless if DMA isn't listening
i2c->hw->dma_cr = I2C_IC_DMA_CR_TDMAE_BITS | I2C_IC_DMA_CR_RDMAE_BITS;
// Re-sets i2c->hw->enable upon returning:
return i2c_set_baudrate(i2c, baudrate);
}
void i2c_deinit(i2c_inst_t *i2c) {
i2c_reset(i2c);
}
uint i2c_set_baudrate(i2c_inst_t *i2c, uint baudrate) {
invalid_params_if(I2C, baudrate == 0);
// I2C is synchronous design that runs from clk_sys
uint freq_in = clock_get_hz(clk_sys);
// TODO there are some subtleties to I2C timing which we are completely ignoring here
uint period = (freq_in + baudrate / 2) / baudrate;
uint lcnt = period * 3 / 5; // oof this one hurts
uint hcnt = period - lcnt;
// Check for out-of-range divisors:
invalid_params_if(I2C, hcnt > I2C_IC_FS_SCL_HCNT_IC_FS_SCL_HCNT_BITS);
invalid_params_if(I2C, lcnt > I2C_IC_FS_SCL_LCNT_IC_FS_SCL_LCNT_BITS);
invalid_params_if(I2C, hcnt < 8);
invalid_params_if(I2C, lcnt < 8);
// Per I2C-bus specification a device in standard or fast mode must
// internally provide a hold time of at least 300ns for the SDA signal to
// bridge the undefined region of the falling edge of SCL. A smaller hold
// time of 120ns is used for fast mode plus.
uint sda_tx_hold_count;
if (baudrate < 1000000) {
// sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns)
// Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint.
// Add 1 to avoid division truncation.
sda_tx_hold_count = ((freq_in * 3) / 10000000) + 1;
} else {
// sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns)
// Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint.
// Add 1 to avoid division truncation.
sda_tx_hold_count = ((freq_in * 3) / 25000000) + 1;
}
assert(sda_tx_hold_count <= lcnt - 2);
i2c->hw->enable = 0;
// Always use "fast" mode (<= 400 kHz, works fine for standard mode too)
hw_write_masked(&i2c->hw->con,
I2C_IC_CON_SPEED_VALUE_FAST << I2C_IC_CON_SPEED_LSB,
I2C_IC_CON_SPEED_BITS
);
i2c->hw->fs_scl_hcnt = hcnt;
i2c->hw->fs_scl_lcnt = lcnt;
i2c->hw->fs_spklen = lcnt < 16 ? 1 : lcnt / 16;
hw_write_masked(&i2c->hw->sda_hold,
sda_tx_hold_count << I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD_LSB,
I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD_BITS);
i2c->hw->enable = 1;
return freq_in / period;
}
void i2c_set_slave_mode(i2c_inst_t *i2c, bool slave, uint8_t addr) {
invalid_params_if(I2C, addr >= 0x80); // 7-bit addresses
invalid_params_if(I2C, i2c_reserved_addr(addr));
i2c->hw->enable = 0;
uint32_t ctrl_set_if_master = I2C_IC_CON_MASTER_MODE_BITS | I2C_IC_CON_IC_SLAVE_DISABLE_BITS;
uint32_t ctrl_set_if_slave = I2C_IC_CON_RX_FIFO_FULL_HLD_CTRL_BITS;
if (slave) {
hw_write_masked(&i2c->hw->con,
ctrl_set_if_slave,
ctrl_set_if_master | ctrl_set_if_slave
);
i2c->hw->sar = addr;
} else {
hw_write_masked(&i2c->hw->con,
ctrl_set_if_master,
ctrl_set_if_master | ctrl_set_if_slave
);
}
i2c->hw->enable = 1;
}
static int i2c_write_blocking_internal(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop,
check_timeout_fn timeout_check, struct timeout_state *ts) {
invalid_params_if(I2C, addr >= 0x80); // 7-bit addresses
invalid_params_if(I2C, i2c_reserved_addr(addr));
// Synopsys hw accepts start/stop flags alongside data items in the same
// FIFO word, so no 0 byte transfers.
invalid_params_if(I2C, len == 0);
invalid_params_if(I2C, ((int)len) < 0);
i2c->hw->enable = 0;
i2c->hw->tar = addr;
i2c->hw->enable = 1;
bool abort = false;
bool timeout = false;
uint32_t abort_reason = 0;
int byte_ctr;
int ilen = (int)len;
for (byte_ctr = 0; byte_ctr < ilen; ++byte_ctr) {
bool first = byte_ctr == 0;
bool last = byte_ctr == ilen - 1;
i2c->hw->data_cmd =
bool_to_bit(first && i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB |
bool_to_bit(last && !nostop) << I2C_IC_DATA_CMD_STOP_LSB |
*src++;
// Wait until the transmission of the address/data from the internal
// shift register has completed. For this to function correctly, the
// TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag
// was set in i2c_init.
do {
if (timeout_check) {
timeout = timeout_check(ts);
abort |= timeout;
}
tight_loop_contents();
} while (!timeout && !(i2c->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_TX_EMPTY_BITS));
// If there was a timeout, don't attempt to do anything else.
if (!timeout) {
abort_reason = i2c->hw->tx_abrt_source;
if (abort_reason) {
// Note clearing the abort flag also clears the reason, and
// this instance of flag is clear-on-read! Note also the
// IC_CLR_TX_ABRT register always reads as 0.
i2c->hw->clr_tx_abrt;
abort = true;
}
if (abort || (last && !nostop)) {
// If the transaction was aborted or if it completed
// successfully wait until the STOP condition has occured.
// TODO Could there be an abort while waiting for the STOP
// condition here? If so, additional code would be needed here
// to take care of the abort.
do {
if (timeout_check) {
timeout = timeout_check(ts);
abort |= timeout;
}
tight_loop_contents();
} while (!timeout && !(i2c->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_STOP_DET_BITS));
// If there was a timeout, don't attempt to do anything else.
if (!timeout) {
i2c->hw->clr_stop_det;
}
}
}
// Note the hardware issues a STOP automatically on an abort condition.
// Note also the hardware clears RX FIFO as well as TX on abort,
// because we set hwparam IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0.
if (abort)
break;
}
int rval;
// A lot of things could have just happened due to the ingenious and
// creative design of I2C. Try to figure things out.
if (abort) {
if (timeout)
rval = PICO_ERROR_TIMEOUT;
else if (!abort_reason || abort_reason & I2C_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK_BITS) {
// No reported errors - seems to happen if there is nothing connected to the bus.
// Address byte not acknowledged
rval = PICO_ERROR_GENERIC;
} else if (abort_reason & I2C_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK_BITS) {
// Address acknowledged, some data not acknowledged
rval = byte_ctr;
} else {
//panic("Unknown abort from I2C instance @%08x: %08x\n", (uint32_t) i2c->hw, abort_reason);
rval = PICO_ERROR_GENERIC;
}
} else {
rval = byte_ctr;
}
// nostop means we are now at the end of a *message* but not the end of a *transfer*
i2c->restart_on_next = nostop;
return rval;
}
int i2c_write_blocking(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop) {
return i2c_write_blocking_internal(i2c, addr, src, len, nostop, NULL, NULL);
}
int i2c_write_blocking_until(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop,
absolute_time_t until) {
timeout_state_t ts;
return i2c_write_blocking_internal(i2c, addr, src, len, nostop, init_single_timeout_until(&ts, until), &ts);
}
int i2c_write_timeout_per_char_us(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop,
uint timeout_per_char_us) {
timeout_state_t ts;
return i2c_write_blocking_internal(i2c, addr, src, len, nostop,
init_per_iteration_timeout_us(&ts, timeout_per_char_us), &ts);
}
static int i2c_read_blocking_internal(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop,
check_timeout_fn timeout_check, timeout_state_t *ts) {
invalid_params_if(I2C, addr >= 0x80); // 7-bit addresses
invalid_params_if(I2C, i2c_reserved_addr(addr));
invalid_params_if(I2C, len == 0);
invalid_params_if(I2C, ((int)len) < 0);
i2c->hw->enable = 0;
i2c->hw->tar = addr;
i2c->hw->enable = 1;
bool abort = false;
bool timeout = false;
uint32_t abort_reason;
int byte_ctr;
int ilen = (int)len;
for (byte_ctr = 0; byte_ctr < ilen; ++byte_ctr) {
bool first = byte_ctr == 0;
bool last = byte_ctr == ilen - 1;
while (!i2c_get_write_available(i2c))
tight_loop_contents();
i2c->hw->data_cmd =
bool_to_bit(first && i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB |
bool_to_bit(last && !nostop) << I2C_IC_DATA_CMD_STOP_LSB |
I2C_IC_DATA_CMD_CMD_BITS; // -> 1 for read
do {
abort_reason = i2c->hw->tx_abrt_source;
abort = (bool) i2c->hw->clr_tx_abrt;
if (timeout_check) {
timeout = timeout_check(ts);
abort |= timeout;
}
} while (!abort && !i2c_get_read_available(i2c));
if (abort)
break;
*dst++ = (uint8_t) i2c->hw->data_cmd;
}
int rval;
if (abort) {
if (timeout)
rval = PICO_ERROR_TIMEOUT;
else if (!abort_reason || abort_reason & I2C_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK_BITS) {
// No reported errors - seems to happen if there is nothing connected to the bus.
// Address byte not acknowledged
rval = PICO_ERROR_GENERIC;
} else {
// panic("Unknown abort from I2C instance @%08x: %08x\n", (uint32_t) i2c->hw, abort_reason);
rval = PICO_ERROR_GENERIC;
}
} else {
rval = byte_ctr;
}
i2c->restart_on_next = nostop;
return rval;
}
int i2c_read_blocking(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop) {
return i2c_read_blocking_internal(i2c, addr, dst, len, nostop, NULL, NULL);
}
int i2c_read_blocking_until(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, absolute_time_t until) {
timeout_state_t ts;
return i2c_read_blocking_internal(i2c, addr, dst, len, nostop, init_single_timeout_until(&ts, until), &ts);
}
int i2c_read_timeout_per_char_us(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop,
uint timeout_per_char_us) {
timeout_state_t ts;
return i2c_read_blocking_internal(i2c, addr, dst, len, nostop,
init_per_iteration_timeout_us(&ts, timeout_per_char_us), &ts);
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_i2c/include/hardware/i2c.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_I2C_H
#define _HARDWARE_I2C_H
#include "pico.h"
#include "pico/time.h"
#include "hardware/structs/i2c.h"
#include "hardware/regs/dreq.h"
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_I2C, Enable/disable assertions in the I2C module, type=bool, default=0, group=hardware_i2c
#ifndef PARAM_ASSERTIONS_ENABLED_I2C
#define PARAM_ASSERTIONS_ENABLED_I2C 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
/** \file hardware/i2c.h
* \defgroup hardware_i2c hardware_i2c
*
* I2C Controller API
*
* The I2C bus is a two-wire serial interface, consisting of a serial data line SDA and a serial clock SCL. These wires carry
* information between the devices connected to the bus. Each device is recognized by a unique 7-bit address and can operate as
* either a “transmitter” or “receiver”, depending on the function of the device. Devices can also be considered as masters or
* slaves when performing data transfers. A master is a device that initiates a data transfer on the bus and generates the
* clock signals to permit that transfer. The first byte in the data transfer always contains the 7-bit address and
* a read/write bit in the LSB position. This API takes care of toggling the read/write bit. After this, any device addressed
* is considered a slave.
*
* This API allows the controller to be set up as a master or a slave using the \ref i2c_set_slave_mode function.
*
* The external pins of each controller are connected to GPIO pins as defined in the GPIO muxing table in the datasheet. The muxing options
* give some IO flexibility, but each controller external pin should be connected to only one GPIO.
*
* Note that the controller does NOT support High speed mode or Ultra-fast speed mode, the fastest operation being fast mode plus
* at up to 1000Kb/s.
*
* See the datasheet for more information on the I2C controller and its usage.
*
* \subsection i2c_example Example
* \addtogroup hardware_i2c
* \include bus_scan.c
*/
typedef struct i2c_inst i2c_inst_t;
// PICO_CONFIG: PICO_DEFAULT_I2C, Define the default I2C for a board, min=0, max=1, group=hardware_i2c
// PICO_CONFIG: PICO_DEFAULT_I2C_SDA_PIN, Define the default I2C SDA pin, min=0, max=29, group=hardware_i2c
// PICO_CONFIG: PICO_DEFAULT_I2C_SCL_PIN, Define the default I2C SCL pin, min=0, max=29, group=hardware_i2c
/** The I2C identifiers for use in I2C functions.
*
* e.g. i2c_init(i2c0, 48000)
*
* \ingroup hardware_i2c
* @{
*/
extern i2c_inst_t i2c0_inst;
extern i2c_inst_t i2c1_inst;
#define i2c0 (&i2c0_inst) ///< Identifier for I2C HW Block 0
#define i2c1 (&i2c1_inst) ///< Identifier for I2C HW Block 1
#if !defined(PICO_DEFAULT_I2C_INSTANCE) && defined(PICO_DEFAULT_I2C)
#define PICO_DEFAULT_I2C_INSTANCE (__CONCAT(i2c,PICO_DEFAULT_I2C))
#endif
#ifdef PICO_DEFAULT_I2C_INSTANCE
#define i2c_default PICO_DEFAULT_I2C_INSTANCE
#endif
/** @} */
// ----------------------------------------------------------------------------
// Setup
/*! \brief Initialise the I2C HW block
* \ingroup hardware_i2c
*
* Put the I2C hardware into a known state, and enable it. Must be called
* before other functions. By default, the I2C is configured to operate as a
* master.
*
* The I2C bus frequency is set as close as possible to requested, and
* the actual rate set is returned
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param baudrate Baudrate in Hz (e.g. 100kHz is 100000)
* \return Actual set baudrate
*/
uint i2c_init(i2c_inst_t *i2c, uint baudrate);
/*! \brief Disable the I2C HW block
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
*
* Disable the I2C again if it is no longer used. Must be reinitialised before
* being used again.
*/
void i2c_deinit(i2c_inst_t *i2c);
/*! \brief Set I2C baudrate
* \ingroup hardware_i2c
*
* Set I2C bus frequency as close as possible to requested, and return actual
* rate set.
* Baudrate may not be as exactly requested due to clocking limitations.
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param baudrate Baudrate in Hz (e.g. 100kHz is 100000)
* \return Actual set baudrate
*/
uint i2c_set_baudrate(i2c_inst_t *i2c, uint baudrate);
/*! \brief Set I2C port to slave mode
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param slave true to use slave mode, false to use master mode
* \param addr If \p slave is true, set the slave address to this value
*/
void i2c_set_slave_mode(i2c_inst_t *i2c, bool slave, uint8_t addr);
// ----------------------------------------------------------------------------
// Generic input/output
struct i2c_inst {
i2c_hw_t *hw;
bool restart_on_next;
};
/*! \brief Convert I2C instance to hardware instance number
* \ingroup hardware_i2c
*
* \param i2c I2C instance
* \return Number of I2C, 0 or 1.
*/
static inline uint i2c_hw_index(i2c_inst_t *i2c) {
invalid_params_if(I2C, i2c != i2c0 && i2c != i2c1);
return i2c == i2c1 ? 1 : 0;
}
static inline i2c_hw_t *i2c_get_hw(i2c_inst_t *i2c) {
i2c_hw_index(i2c); // check it is a hw i2c
return i2c->hw;
}
static inline i2c_inst_t *i2c_get_instance(uint instance) {
static_assert(NUM_I2CS == 2, "");
invalid_params_if(I2C, instance >= NUM_I2CS);
return instance ? i2c1 : i2c0;
}
/*! \brief Attempt to write specified number of bytes to address, blocking until the specified absolute time is reached.
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param addr 7-bit address of device to write to
* \param src Pointer to data to send
* \param len Length of data in bytes to send
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
* and the next transfer will begin with a Restart rather than a Start.
* \param until The absolute time that the block will wait until the entire transaction is complete. Note, an individual timeout of
* this value divided by the length of data is applied for each byte transfer, so if the first or subsequent
* bytes fails to transfer within that sub timeout, the function will return with an error.
*
* \return Number of bytes written, or PICO_ERROR_GENERIC if address not acknowledged, no device present, or PICO_ERROR_TIMEOUT if a timeout occurred.
*/
int i2c_write_blocking_until(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop, absolute_time_t until);
/*! \brief Attempt to read specified number of bytes from address, blocking until the specified absolute time is reached.
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param addr 7-bit address of device to read from
* \param dst Pointer to buffer to receive data
* \param len Length of data in bytes to receive
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
* and the next transfer will begin with a Restart rather than a Start.
* \param until The absolute time that the block will wait until the entire transaction is complete.
* \return Number of bytes read, or PICO_ERROR_GENERIC if address not acknowledged, no device present, or PICO_ERROR_TIMEOUT if a timeout occurred.
*/
int i2c_read_blocking_until(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, absolute_time_t until);
/*! \brief Attempt to write specified number of bytes to address, with timeout
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param addr 7-bit address of device to write to
* \param src Pointer to data to send
* \param len Length of data in bytes to send
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
* and the next transfer will begin with a Restart rather than a Start.
* \param timeout_us The time that the function will wait for the entire transaction to complete. Note, an individual timeout of
* this value divided by the length of data is applied for each byte transfer, so if the first or subsequent
* bytes fails to transfer within that sub timeout, the function will return with an error.
*
* \return Number of bytes written, or PICO_ERROR_GENERIC if address not acknowledged, no device present, or PICO_ERROR_TIMEOUT if a timeout occurred.
*/
static inline int i2c_write_timeout_us(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop, uint timeout_us) {
absolute_time_t t = make_timeout_time_us(timeout_us);
return i2c_write_blocking_until(i2c, addr, src, len, nostop, t);
}
int i2c_write_timeout_per_char_us(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop, uint timeout_per_char_us);
/*! \brief Attempt to read specified number of bytes from address, with timeout
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param addr 7-bit address of device to read from
* \param dst Pointer to buffer to receive data
* \param len Length of data in bytes to receive
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
* and the next transfer will begin with a Restart rather than a Start.
* \param timeout_us The time that the function will wait for the entire transaction to complete
* \return Number of bytes read, or PICO_ERROR_GENERIC if address not acknowledged, no device present, or PICO_ERROR_TIMEOUT if a timeout occurred.
*/
static inline int i2c_read_timeout_us(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, uint timeout_us) {
absolute_time_t t = make_timeout_time_us(timeout_us);
return i2c_read_blocking_until(i2c, addr, dst, len, nostop, t);
}
int i2c_read_timeout_per_char_us(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, uint timeout_per_char_us);
/*! \brief Attempt to write specified number of bytes to address, blocking
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param addr 7-bit address of device to write to
* \param src Pointer to data to send
* \param len Length of data in bytes to send
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
* and the next transfer will begin with a Restart rather than a Start.
* \return Number of bytes written, or PICO_ERROR_GENERIC if address not acknowledged, no device present.
*/
int i2c_write_blocking(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop);
/*! \brief Attempt to read specified number of bytes from address, blocking
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param addr 7-bit address of device to read from
* \param dst Pointer to buffer to receive data
* \param len Length of data in bytes to receive
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
* and the next transfer will begin with a Restart rather than a Start.
* \return Number of bytes read, or PICO_ERROR_GENERIC if address not acknowledged or no device present.
*/
int i2c_read_blocking(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop);
/*! \brief Determine non-blocking write space available
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \return 0 if no space is available in the I2C to write more data. If return is nonzero, at
* least that many bytes can be written without blocking.
*/
static inline size_t i2c_get_write_available(i2c_inst_t *i2c) {
const size_t IC_TX_BUFFER_DEPTH = 16;
return IC_TX_BUFFER_DEPTH - i2c_get_hw(i2c)->txflr;
}
/*! \brief Determine number of bytes received
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \return 0 if no data available, if return is nonzero at
* least that many bytes can be read without blocking.
*/
static inline size_t i2c_get_read_available(i2c_inst_t *i2c) {
return i2c_get_hw(i2c)->rxflr;
}
/*! \brief Write direct to TX FIFO
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param src Data to send
* \param len Number of bytes to send
*
* Writes directly to the I2C TX FIFO which is mainly useful for
* slave-mode operation.
*/
static inline void i2c_write_raw_blocking(i2c_inst_t *i2c, const uint8_t *src, size_t len) {
for (size_t i = 0; i < len; ++i) {
// TODO NACK or STOP on end?
while (!i2c_get_write_available(i2c))
tight_loop_contents();
i2c_get_hw(i2c)->data_cmd = *src++;
}
}
/*! \brief Read direct from RX FIFO
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param dst Buffer to accept data
* \param len Number of bytes to read
*
* Reads directly from the I2C RX FIFO which is mainly useful for
* slave-mode operation.
*/
static inline void i2c_read_raw_blocking(i2c_inst_t *i2c, uint8_t *dst, size_t len) {
for (size_t i = 0; i < len; ++i) {
while (!i2c_get_read_available(i2c))
tight_loop_contents();
*dst++ = (uint8_t)i2c_get_hw(i2c)->data_cmd;
}
}
/**
* \brief Pop a byte from I2C Rx FIFO.
* \ingroup hardware_i2c
*
* This function is non-blocking and assumes the Rx FIFO isn't empty.
*
* \param i2c I2C instance.
* \return uint8_t Byte value.
*/
static inline uint8_t i2c_read_byte_raw(i2c_inst_t *i2c) {
i2c_hw_t *hw = i2c_get_hw(i2c);
assert(hw->status & I2C_IC_STATUS_RFNE_BITS); // Rx FIFO must not be empty
return (uint8_t)hw->data_cmd;
}
/**
* \brief Push a byte into I2C Tx FIFO.
* \ingroup hardware_i2c
*
* This function is non-blocking and assumes the Tx FIFO isn't full.
*
* \param i2c I2C instance.
* \param value Byte value.
*/
static inline void i2c_write_byte_raw(i2c_inst_t *i2c, uint8_t value) {
i2c_hw_t *hw = i2c_get_hw(i2c);
assert(hw->status & I2C_IC_STATUS_TFNF_BITS); // Tx FIFO must not be full
hw->data_cmd = value;
}
/*! \brief Return the DREQ to use for pacing transfers to/from a particular I2C instance
* \ingroup hardware_i2c
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param is_tx true for sending data to the I2C instance, false for receiving data from the I2C instance
*/
static inline uint i2c_get_dreq(i2c_inst_t *i2c, bool is_tx) {
static_assert(DREQ_I2C0_RX == DREQ_I2C0_TX + 1, "");
static_assert(DREQ_I2C1_RX == DREQ_I2C1_TX + 1, "");
static_assert(DREQ_I2C1_TX == DREQ_I2C0_TX + 2, "");
return DREQ_I2C0_TX + i2c_hw_index(i2c) * 2 + !is_tx;
}
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_interp/CMakeLists.txt
================================================
pico_simple_hardware_target(interp)
================================================
FILE: pico-sdk/src/rp2_common/hardware_interp/include/hardware/interp.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_INTERP_H
#define _HARDWARE_INTERP_H
#include "pico.h"
#include "hardware/structs/interp.h"
#include "hardware/regs/sio.h"
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_INTERP, Enable/disable assertions in the interpolation module, type=bool, default=0, group=hardware_interp
#ifndef PARAM_ASSERTIONS_ENABLED_INTERP
#define PARAM_ASSERTIONS_ENABLED_INTERP 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
/** \file hardware/interp.h
* \defgroup hardware_interp hardware_interp
*
* Hardware Interpolator API
*
* Each core is equipped with two interpolators (INTERP0 and INTERP1) which can be used to accelerate
* tasks by combining certain pre-configured simple operations into a single processor cycle. Intended
* for cases where the pre-configured operation is repeated a large number of times, this results in
* code which uses both fewer CPU cycles and fewer CPU registers in the time critical sections of the
* code.
*
* The interpolators are used heavily to accelerate audio operations within the SDK, but their
* flexible configuration make it possible to optimise many other tasks such as quantization and
* dithering, table lookup address generation, affine texture mapping, decompression and linear feedback.
*
* Please refer to the RP2040 datasheet for more information on the HW interpolators and how they work.
*/
#define interp0 interp0_hw
#define interp1 interp1_hw
/** \brief Interpolator configuration
* \defgroup interp_config interp_config
* \ingroup hardware_interp
*
* Each interpolator needs to be configured, these functions provide handy helpers to set up configuration
* structures.
*
*/
typedef struct {
uint32_t ctrl;
} interp_config;
static inline uint interp_index(interp_hw_t *interp) {
valid_params_if(INTERP, interp == interp0 || interp == interp1);
return interp == interp1 ? 1 : 0;
}
/*! \brief Claim the interpolator lane specified
* \ingroup hardware_interp
*
* Use this function to claim exclusive access to the specified interpolator lane.
*
* This function will panic if the lane is already claimed.
*
* \param interp Interpolator on which to claim a lane. interp0 or interp1
* \param lane The lane number, 0 or 1.
*/
void interp_claim_lane(interp_hw_t *interp, uint lane);
// The above really should be called this for consistency
#define interp_lane_claim interp_claim_lane
/*! \brief Claim the interpolator lanes specified in the mask
* \ingroup hardware_interp
*
* \param interp Interpolator on which to claim lanes. interp0 or interp1
* \param lane_mask Bit pattern of lanes to claim (only bits 0 and 1 are valid)
*/
void interp_claim_lane_mask(interp_hw_t *interp, uint lane_mask);
/*! \brief Release a previously claimed interpolator lane
* \ingroup hardware_interp
*
* \param interp Interpolator on which to release a lane. interp0 or interp1
* \param lane The lane number, 0 or 1
*/
void interp_unclaim_lane(interp_hw_t *interp, uint lane);
// The above really should be called this for consistency
#define interp_lane_unclaim interp_unclaim_lane
/*! \brief Determine if an interpolator lane is claimed
* \ingroup hardware_interp
*
* \param interp Interpolator whose lane to check
* \param lane The lane number, 0 or 1
* \return true if claimed, false otherwise
* \see interp_claim_lane
* \see interp_claim_lane_mask
*/
bool interp_lane_is_claimed(interp_hw_t *interp, uint lane);
/*! \brief Release previously claimed interpolator lanes \see interp_claim_lane_mask
* \ingroup hardware_interp
*
* \param interp Interpolator on which to release lanes. interp0 or interp1
* \param lane_mask Bit pattern of lanes to unclaim (only bits 0 and 1 are valid)
*/
void interp_unclaim_lane_mask(interp_hw_t *interp, uint lane_mask);
/*! \brief Set the interpolator shift value
* \ingroup interp_config
*
* Sets the number of bits the accumulator is shifted before masking, on each iteration.
*
* \param c Pointer to an interpolator config
* \param shift Number of bits
*/
static inline void interp_config_set_shift(interp_config *c, uint shift) {
valid_params_if(INTERP, shift < 32);
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_SHIFT_BITS) |
((shift << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) & SIO_INTERP0_CTRL_LANE0_SHIFT_BITS);
}
/*! \brief Set the interpolator mask range
* \ingroup interp_config
*
* Sets the range of bits (least to most) that are allowed to pass through the interpolator
*
* \param c Pointer to interpolation config
* \param mask_lsb The least significant bit allowed to pass
* \param mask_msb The most significant bit allowed to pass
*/
static inline void interp_config_set_mask(interp_config *c, uint mask_lsb, uint mask_msb) {
valid_params_if(INTERP, mask_msb < 32);
valid_params_if(INTERP, mask_lsb <= mask_msb);
c->ctrl = (c->ctrl & ~(SIO_INTERP0_CTRL_LANE0_MASK_LSB_BITS | SIO_INTERP0_CTRL_LANE0_MASK_MSB_BITS)) |
((mask_lsb << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) & SIO_INTERP0_CTRL_LANE0_MASK_LSB_BITS) |
((mask_msb << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB) & SIO_INTERP0_CTRL_LANE0_MASK_MSB_BITS);
}
/*! \brief Enable cross input
* \ingroup interp_config
*
* Allows feeding of the accumulator content from the other lane back in to this lanes shift+mask hardware.
* This will take effect even if the interp_config_set_add_raw option is set as the cross input mux is before the
* shift+mask bypass
*
* \param c Pointer to interpolation config
* \param cross_input If true, enable the cross input.
*/
static inline void interp_config_set_cross_input(interp_config *c, bool cross_input) {
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_CROSS_INPUT_BITS) |
(cross_input ? SIO_INTERP0_CTRL_LANE0_CROSS_INPUT_BITS : 0);
}
/*! \brief Enable cross results
* \ingroup interp_config
*
* Allows feeding of the other lane’s result into this lane’s accumulator on a POP operation.
*
* \param c Pointer to interpolation config
* \param cross_result If true, enables the cross result
*/
static inline void interp_config_set_cross_result(interp_config *c, bool cross_result) {
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_CROSS_RESULT_BITS) |
(cross_result ? SIO_INTERP0_CTRL_LANE0_CROSS_RESULT_BITS : 0);
}
/*! \brief Set sign extension
* \ingroup interp_config
*
* Enables signed mode, where the shifted and masked accumulator value is sign-extended to 32 bits
* before adding to BASE1, and LANE1 PEEK/POP results appear extended to 32 bits when read by processor.
*
* \param c Pointer to interpolation config
* \param _signed If true, enables sign extension
*/
static inline void interp_config_set_signed(interp_config *c, bool _signed) {
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_SIGNED_BITS) |
(_signed ? SIO_INTERP0_CTRL_LANE0_SIGNED_BITS : 0);
}
/*! \brief Set raw add option
* \ingroup interp_config
*
* When enabled, mask + shift is bypassed for LANE0 result. This does not affect the FULL result.
*
* \param c Pointer to interpolation config
* \param add_raw If true, enable raw add option.
*/
static inline void interp_config_set_add_raw(interp_config *c, bool add_raw) {
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_ADD_RAW_BITS) |
(add_raw ? SIO_INTERP0_CTRL_LANE0_ADD_RAW_BITS : 0);
}
/*! \brief Set blend mode
* \ingroup interp_config
*
* If enabled, LANE1 result is a linear interpolation between BASE0 and BASE1, controlled
* by the 8 LSBs of lane 1 shift and mask value (a fractional number between 0 and 255/256ths)
*
* LANE0 result does not have BASE0 added (yields only the 8 LSBs of lane 1 shift+mask value)
*
* FULL result does not have lane 1 shift+mask value added (BASE2 + lane 0 shift+mask)
*
* LANE1 SIGNED flag controls whether the interpolation is signed or unsig
*
* \param c Pointer to interpolation config
* \param blend Set true to enable blend mode.
*/
static inline void interp_config_set_blend(interp_config *c, bool blend) {
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_BLEND_BITS) |
(blend ? SIO_INTERP0_CTRL_LANE0_BLEND_BITS : 0);
}
/*! \brief Set interpolator clamp mode (Interpolator 1 only)
* \ingroup interp_config
*
* Only present on INTERP1 on each core. If CLAMP mode is enabled:
* - LANE0 result is a shifted and masked ACCUM0, clamped by a lower bound of BASE0 and an upper bound of BASE1.
* - Signedness of these comparisons is determined by LANE0_CTRL_SIGNED
*
* \param c Pointer to interpolation config
* \param clamp Set true to enable clamp mode
*/
static inline void interp_config_set_clamp(interp_config *c, bool clamp) {
c->ctrl = (c->ctrl & ~SIO_INTERP1_CTRL_LANE0_CLAMP_BITS) |
(clamp ? SIO_INTERP1_CTRL_LANE0_CLAMP_BITS : 0);
}
/*! \brief Set interpolator Force bits
* \ingroup interp_config
*
* ORed into bits 29:28 of the lane result presented to the processor on the bus.
*
* No effect on the internal 32-bit datapath. Handy for using a lane to generate sequence
* of pointers into flash or SRAM
*
* \param c Pointer to interpolation config
* \param bits Sets the force bits to that specified. Range 0-3 (two bits)
*/
static inline void interp_config_set_force_bits(interp_config *c, uint bits) {
invalid_params_if(INTERP, bits > 3);
// note cannot use hw_set_bits on SIO
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_FORCE_MSB_BITS) |
(bits << SIO_INTERP0_CTRL_LANE0_FORCE_MSB_LSB);
}
/*! \brief Get a default configuration
* \ingroup interp_config
*
* \return A default interpolation configuration
*/
static inline interp_config interp_default_config(void) {
interp_config c = {0};
// Just pass through everything
interp_config_set_mask(&c, 0, 31);
return c;
}
/*! \brief Send configuration to a lane
* \ingroup interp_config
*
* If an invalid configuration is specified (ie a lane specific item is set on wrong lane),
* depending on setup this function can panic.
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane to set
* \param config Pointer to interpolation config
*/
static inline void interp_set_config(interp_hw_t *interp, uint lane, interp_config *config) {
invalid_params_if(INTERP, lane > 1);
invalid_params_if(INTERP, config->ctrl & SIO_INTERP1_CTRL_LANE0_CLAMP_BITS &&
(!interp_index(interp) || lane)); // only interp1 lane 0 has clamp bit
invalid_params_if(INTERP, config->ctrl & SIO_INTERP0_CTRL_LANE0_BLEND_BITS &&
(interp_index(interp) || lane)); // only interp0 lane 0 has blend bit
interp->ctrl[lane] = config->ctrl;
}
/*! \brief Directly set the force bits on a specified lane
* \ingroup hardware_interp
*
* These bits are ORed into bits 29:28 of the lane result presented to the processor on the bus.
* There is no effect on the internal 32-bit datapath.
*
* Useful for using a lane to generate sequence of pointers into flash or SRAM, saving a subsequent
* OR or add operation.
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane to set
* \param bits The bits to set (bits 0 and 1, value range 0-3)
*/
static inline void interp_set_force_bits(interp_hw_t *interp, uint lane, uint bits) {
// note cannot use hw_set_bits on SIO
interp->ctrl[lane] = interp->ctrl[lane] | (bits << SIO_INTERP0_CTRL_LANE0_FORCE_MSB_LSB);
}
typedef struct {
uint32_t accum[2];
uint32_t base[3];
uint32_t ctrl[2];
} interp_hw_save_t;
/*! \brief Save the specified interpolator state
* \ingroup hardware_interp
*
* Can be used to save state if you need an interpolator for another purpose, state
* can then be recovered afterwards and continue from that point
*
* \param interp Interpolator instance, interp0 or interp1.
* \param saver Pointer to the save structure to fill in
*/
void interp_save(interp_hw_t *interp, interp_hw_save_t *saver);
/*! \brief Restore an interpolator state
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param saver Pointer to save structure to reapply to the specified interpolator
*/
void interp_restore(interp_hw_t *interp, interp_hw_save_t *saver);
/*! \brief Sets the interpolator base register by lane
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1 or 2
* \param val The value to apply to the register
*/
static inline void interp_set_base(interp_hw_t *interp, uint lane, uint32_t val) {
interp->base[lane] = val;
}
/*! \brief Gets the content of interpolator base register by lane
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1 or 2
* \return The current content of the lane base register
*/
static inline uint32_t interp_get_base(interp_hw_t *interp, uint lane) {
return interp->base[lane];
}
/*! \brief Sets the interpolator base registers simultaneously
* \ingroup hardware_interp
*
* The lower 16 bits go to BASE0, upper bits to BASE1 simultaneously.
* Each half is sign-extended to 32 bits if that lane’s SIGNED flag is set.
*
* \param interp Interpolator instance, interp0 or interp1.
* \param val The value to apply to the register
*/
static inline void interp_set_base_both(interp_hw_t *interp, uint32_t val) {
interp->base01 = val;
}
/*! \brief Sets the interpolator accumulator register by lane
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \param val The value to apply to the register
*/
static inline void interp_set_accumulator(interp_hw_t *interp, uint lane, uint32_t val) {
interp->accum[lane] = val;
}
/*! \brief Gets the content of the interpolator accumulator register by lane
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \return The current content of the register
*/
static inline uint32_t interp_get_accumulator(interp_hw_t *interp, uint lane) {
return interp->accum[lane];
}
/*! \brief Read lane result, and write lane results to both accumulators to update the interpolator
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \return The content of the lane result register
*/
static inline uint32_t interp_pop_lane_result(interp_hw_t *interp, uint lane) {
return interp->pop[lane];
}
/*! \brief Read lane result
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \return The content of the lane result register
*/
static inline uint32_t interp_peek_lane_result(interp_hw_t *interp, uint lane) {
return interp->peek[lane];
}
/*! \brief Read lane result, and write lane results to both accumulators to update the interpolator
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \return The content of the FULL register
*/
static inline uint32_t interp_pop_full_result(interp_hw_t *interp) {
return interp->pop[2];
}
/*! \brief Read lane result
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \return The content of the FULL register
*/
static inline uint32_t interp_peek_full_result(interp_hw_t *interp) {
return interp->peek[2];
}
/*! \brief Add to accumulator
* \ingroup hardware_interp
*
* Atomically add the specified value to the accumulator on the specified lane
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \param val Value to add
*/
static inline void interp_add_accumulater(interp_hw_t *interp, uint lane, uint32_t val) {
interp->add_raw[lane] = val;
}
/*! \brief Get raw lane value
* \ingroup hardware_interp
*
* Returns the raw shift and mask value from the specified lane, BASE0 is NOT added
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \return The raw shift/mask value
*/
static inline uint32_t interp_get_raw(interp_hw_t *interp, uint lane) {
return interp->add_raw[lane];
}
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_interp/interp.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "hardware/interp.h"
#include "hardware/structs/sio.h"
#include "hardware/claim.h"
check_hw_size(interp_hw_t, SIO_INTERP1_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET);
check_hw_layout(sio_hw_t, interp, SIO_INTERP0_ACCUM0_OFFSET);
static_assert(NUM_DMA_CHANNELS <= 16, "");
static uint8_t _claimed;
static inline uint interp_lane_bit(interp_hw_t * interp, uint lane) {
return (interp_index(interp) << 1u) | lane;
}
void interp_claim_lane(interp_hw_t *interp, uint lane) {
valid_params_if(INTERP, lane < 2);
hw_claim_or_assert((uint8_t *) &_claimed, interp_lane_bit(interp, lane), "Lane is already claimed");
}
void interp_claim_lane_mask(interp_hw_t *interp, uint lane_mask) {
valid_params_if(INTERP, lane_mask && lane_mask <= 0x3);
if (lane_mask & 1u) interp_claim_lane(interp, 0);
if (lane_mask & 2u) interp_claim_lane(interp, 1);
}
void interp_unclaim_lane(interp_hw_t *interp, uint lane) {
valid_params_if(INTERP, lane < 2);
hw_claim_clear((uint8_t *) &_claimed, interp_lane_bit(interp, lane));
}
bool interp_lane_is_claimed(interp_hw_t *interp, uint lane) {
valid_params_if(INTERP, lane < 2);
return hw_is_claimed((uint8_t *) &_claimed, interp_lane_bit(interp, lane));
}
void interp_unclaim_lane_mask(interp_hw_t *interp, uint lane_mask) {
valid_params_if(INTERP, lane_mask <= 0x3);
if (lane_mask & 1u) interp_unclaim_lane(interp, 0);
if (lane_mask & 2u) interp_unclaim_lane(interp, 1);
}
void interp_save(interp_hw_t *interp, interp_hw_save_t *saver) {
saver->accum[0] = interp->accum[0];
saver->accum[1] = interp->accum[1];
saver->base[0] = interp->base[0];
saver->base[1] = interp->base[1];
saver->base[2] = interp->base[2];
saver->ctrl[0] = interp->ctrl[0];
saver->ctrl[1] = interp->ctrl[1];
}
void interp_restore(interp_hw_t *interp, interp_hw_save_t *saver) {
interp->accum[0] = saver->accum[0];
interp->accum[1] = saver->accum[1];
interp->base[0] = saver->base[0];
interp->base[1] = saver->base[1];
interp->base[2] = saver->base[2];
interp->ctrl[0] = saver->ctrl[0];
interp->ctrl[1] = saver->ctrl[1];
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_irq/CMakeLists.txt
================================================
pico_simple_hardware_target(irq)
# additional sources/libraries
target_sources(hardware_irq INTERFACE ${CMAKE_CURRENT_LIST_DIR}/irq_handler_chain.S)
pico_mirrored_target_link_libraries(hardware_irq INTERFACE pico_sync)
================================================
FILE: pico-sdk/src/rp2_common/hardware_irq/include/hardware/irq.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_IRQ_H
#define _HARDWARE_IRQ_H
// These two config items are also used by assembler, so keeping separate
// PICO_CONFIG: PICO_MAX_SHARED_IRQ_HANDLERS, Maximum number of shared IRQ handlers, default=4, advanced=true, group=hardware_irq
#ifndef PICO_MAX_SHARED_IRQ_HANDLERS
#define PICO_MAX_SHARED_IRQ_HANDLERS 4
#endif
// PICO_CONFIG: PICO_DISABLE_SHARED_IRQ_HANDLERS, Disable shared IRQ handlers, type=bool, default=0, group=hardware_irq
#ifndef PICO_DISABLE_SHARED_IRQ_HANDLERS
#define PICO_DISABLE_SHARED_IRQ_HANDLERS 0
#endif
// PICO_CONFIG: PICO_VTABLE_PER_CORE, user is using separate vector tables per core, type=bool, default=0, group=hardware_irq
#ifndef PICO_VTABLE_PER_CORE
#define PICO_VTABLE_PER_CORE 0
#endif
#ifndef __ASSEMBLER__
#include "pico.h"
#include "hardware/address_mapped.h"
#include "hardware/regs/intctrl.h"
#include "hardware/regs/m0plus.h"
/** \file irq.h
* \defgroup hardware_irq hardware_irq
*
* Hardware interrupt handling
*
* The RP2040 uses the standard ARM nested vectored interrupt controller (NVIC).
*
* Interrupts are identified by a number from 0 to 31.
*
* On the RP2040, only the lower 26 IRQ signals are connected on the NVIC; IRQs 26 to 31 are tied to zero (never firing).
*
* There is one NVIC per core, and each core's NVIC has the same hardware interrupt lines routed to it, with the exception of the IO interrupts
* where there is one IO interrupt per bank, per core. These are completely independent, so, for example, processor 0 can be
* interrupted by GPIO 0 in bank 0, and processor 1 by GPIO 1 in the same bank.
*
* \note That all IRQ APIs affect the executing core only (i.e. the core calling the function).
*
* \note You should not enable the same (shared) IRQ number on both cores, as this will lead to race conditions
* or starvation of one of the cores. Additionally, don't forget that disabling interrupts on one core does not disable interrupts
* on the other core.
*
* There are three different ways to set handlers for an IRQ:
* - Calling irq_add_shared_handler() at runtime to add a handler for a multiplexed interrupt (e.g. GPIO bank) on the current core. Each handler, should check and clear the relevant hardware interrupt source
* - Calling irq_set_exclusive_handler() at runtime to install a single handler for the interrupt on the current core
* - Defining the interrupt handler explicitly in your application (e.g. by defining void `isr_dma_0` will make that function the handler for the DMA_IRQ_0 on core 0, and
* you will not be able to change it using the above APIs at runtime). Using this method can cause link conflicts at runtime, and offers no runtime performance benefit (i.e, it should not generally be used).
*
* \note If an IRQ is enabled and fires with no handler installed, a breakpoint will be hit and the IRQ number will
* be in register r0.
*
* \section interrupt_nums Interrupt Numbers
*
* Interrupts are numbered as follows, a set of defines is available (intctrl.h) with these names to avoid using the numbers directly.
*
* IRQ | Interrupt Source
* ----|-----------------
* 0 | TIMER_IRQ_0
* 1 | TIMER_IRQ_1
* 2 | TIMER_IRQ_2
* 3 | TIMER_IRQ_3
* 4 | PWM_IRQ_WRAP
* 5 | USBCTRL_IRQ
* 6 | XIP_IRQ
* 7 | PIO0_IRQ_0
* 8 | PIO0_IRQ_1
* 9 | PIO1_IRQ_0
* 10 | PIO1_IRQ_1
* 11 | DMA_IRQ_0
* 12 | DMA_IRQ_1
* 13 | IO_IRQ_BANK0
* 14 | IO_IRQ_QSPI
* 15 | SIO_IRQ_PROC0
* 16 | SIO_IRQ_PROC1
* 17 | CLOCKS_IRQ
* 18 | SPI0_IRQ
* 19 | SPI1_IRQ
* 20 | UART0_IRQ
* 21 | UART1_IRQ
* 22 | ADC0_IRQ_FIFO
* 23 | I2C0_IRQ
* 24 | I2C1_IRQ
* 25 | RTC_IRQ
*
*/
// PICO_CONFIG: PICO_DEFAULT_IRQ_PRIORITY, Define the default IRQ priority, default=0x80, group=hardware_irq
#ifndef PICO_DEFAULT_IRQ_PRIORITY
#define PICO_DEFAULT_IRQ_PRIORITY 0x80
#endif
#define PICO_LOWEST_IRQ_PRIORITY 0xff
#define PICO_HIGHEST_IRQ_PRIORITY 0x00
// PICO_CONFIG: PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY, Set default shared IRQ order priority, default=0x80, group=hardware_irq
#ifndef PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY
#define PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY 0x80
#endif
#define PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY 0xff
#define PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY 0x00
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_IRQ, Enable/disable assertions in the IRQ module, type=bool, default=0, group=hardware_irq
#ifndef PARAM_ASSERTIONS_ENABLED_IRQ
#define PARAM_ASSERTIONS_ENABLED_IRQ 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
/*! \brief Interrupt handler function type
* \ingroup hardware_irq
*
* All interrupts handlers should be of this type, and follow normal ARM EABI register saving conventions
*/
typedef void (*irq_handler_t)(void);
static inline void check_irq_param(__unused uint num) {
invalid_params_if(IRQ, num >= NUM_IRQS);
}
/*! \brief Set specified interrupt's priority
* \ingroup hardware_irq
*
* \param num Interrupt number \ref interrupt_nums
* \param hardware_priority Priority to set.
* Numerically-lower values indicate a higher priority. Hardware priorities
* range from 0 (highest priority) to 255 (lowest priority) though only the
* top 2 bits are significant on ARM Cortex-M0+. To make it easier to specify
* higher or lower priorities than the default, all IRQ priorities are
* initialized to PICO_DEFAULT_IRQ_PRIORITY by the SDK runtime at startup.
* PICO_DEFAULT_IRQ_PRIORITY defaults to 0x80
*/
void irq_set_priority(uint num, uint8_t hardware_priority);
/*! \brief Get specified interrupt's priority
* \ingroup hardware_irq
*
* Numerically-lower values indicate a higher priority. Hardware priorities
* range from 0 (highest priority) to 255 (lowest priority) though only the
* top 2 bits are significant on ARM Cortex-M0+. To make it easier to specify
* higher or lower priorities than the default, all IRQ priorities are
* initialized to PICO_DEFAULT_IRQ_PRIORITY by the SDK runtime at startup.
* PICO_DEFAULT_IRQ_PRIORITY defaults to 0x80
*
* \param num Interrupt number \ref interrupt_nums
* \return the IRQ priority
*/
uint irq_get_priority(uint num);
/*! \brief Enable or disable a specific interrupt on the executing core
* \ingroup hardware_irq
*
* \param num Interrupt number \ref interrupt_nums
* \param enabled true to enable the interrupt, false to disable
*/
void irq_set_enabled(uint num, bool enabled);
/*! \brief Determine if a specific interrupt is enabled on the executing core
* \ingroup hardware_irq
*
* \param num Interrupt number \ref interrupt_nums
* \return true if the interrupt is enabled
*/
bool irq_is_enabled(uint num);
/*! \brief Enable/disable multiple interrupts on the executing core
* \ingroup hardware_irq
*
* \param mask 32-bit mask with one bits set for the interrupts to enable/disable \ref interrupt_nums
* \param enabled true to enable the interrupts, false to disable them.
*/
void irq_set_mask_enabled(uint32_t mask, bool enabled);
/*! \brief Set an exclusive interrupt handler for an interrupt on the executing core.
* \ingroup hardware_irq
*
* Use this method to set a handler for single IRQ source interrupts, or when
* your code, use case or performance requirements dictate that there should
* no other handlers for the interrupt.
*
* This method will assert if there is already any sort of interrupt handler installed
* for the specified irq number.
*
* \param num Interrupt number \ref interrupt_nums
* \param handler The handler to set. See \ref irq_handler_t
* \see irq_add_shared_handler()
*/
void irq_set_exclusive_handler(uint num, irq_handler_t handler);
/*! \brief Get the exclusive interrupt handler for an interrupt on the executing core.
* \ingroup hardware_irq
*
* This method will return an exclusive IRQ handler set on this core
* by irq_set_exclusive_handler if there is one.
*
* \param num Interrupt number \ref interrupt_nums
* \see irq_set_exclusive_handler()
* \return handler The handler if an exclusive handler is set for the IRQ,
* NULL if no handler is set or shared/shareable handlers are installed
*/
irq_handler_t irq_get_exclusive_handler(uint num);
/*! \brief Add a shared interrupt handler for an interrupt on the executing core
* \ingroup hardware_irq
*
* Use this method to add a handler on an irq number shared between multiple distinct hardware sources (e.g. GPIO, DMA or PIO IRQs).
* Handlers added by this method will all be called in sequence from highest order_priority to lowest. The
* irq_set_exclusive_handler() method should be used instead if you know there will or should only ever be one handler for the interrupt.
*
* This method will assert if there is an exclusive interrupt handler set for this irq number on this core, or if
* the (total across all IRQs on both cores) maximum (configurable via PICO_MAX_SHARED_IRQ_HANDLERS) number of shared handlers
* would be exceeded.
*
* \param num Interrupt number \ref interrupt_nums
* \param handler The handler to set. See \ref irq_handler_t
* \param order_priority The order priority controls the order that handlers for the same IRQ number on the core are called.
* The shared irq handlers for an interrupt are all called when an IRQ fires, however the order of the calls is based
* on the order_priority (higher priorities are called first, identical priorities are called in undefined order). A good
* rule of thumb is to use PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY if you don't much care, as it is in the middle of
* the priority range by default.
*
* \note The order_priority uses \em higher values for higher priorities which is the \em opposite of the CPU interrupt priorities passed
* to irq_set_priority() which use lower values for higher priorities.
*
* \see irq_set_exclusive_handler()
*/
void irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_priority);
/*! \brief Remove a specific interrupt handler for the given irq number on the executing core
* \ingroup hardware_irq
*
* This method may be used to remove an irq set via either irq_set_exclusive_handler() or
* irq_add_shared_handler(), and will assert if the handler is not currently installed for the given
* IRQ number
*
* \note This method may *only* be called from user (non IRQ code) or from within the handler
* itself (i.e. an IRQ handler may remove itself as part of handling the IRQ). Attempts to call
* from another IRQ will cause an assertion.
*
* \param num Interrupt number \ref interrupt_nums
* \param handler The handler to removed.
* \see irq_set_exclusive_handler()
* \see irq_add_shared_handler()
*/
void irq_remove_handler(uint num, irq_handler_t handler);
/*! \brief Determine if the current handler for the given number is shared
* \ingroup hardware_irq
*
* \param num Interrupt number \ref interrupt_nums
* \return true if the specified IRQ has a shared handler
*/
bool irq_has_shared_handler(uint num);
/*! \brief Get the current IRQ handler for the specified IRQ from the currently installed hardware vector table (VTOR)
* of the execution core
* \ingroup hardware_irq
*
* \param num Interrupt number \ref interrupt_nums
* \return the address stored in the VTABLE for the given irq number
*/
irq_handler_t irq_get_vtable_handler(uint num);
/*! \brief Clear a specific interrupt on the executing core
* \ingroup hardware_irq
*
* This method is only useful for "software" IRQs that are not connected to hardware (i.e. IRQs 26-31)
* as the the NVIC always reflects the current state of the IRQ state of the hardware for hardware IRQs, and clearing
* of the IRQ state of the hardware is performed via the hardware's registers instead.
*
* \param int_num Interrupt number \ref interrupt_nums
*/
static inline void irq_clear(uint int_num) {
*((volatile uint32_t *) (PPB_BASE + M0PLUS_NVIC_ICPR_OFFSET)) = (1u << ((uint32_t) (int_num & 0x1F)));
}
/*! \brief Force an interrupt to be pending on the executing core
* \ingroup hardware_irq
*
* This should generally not be used for IRQs connected to hardware.
*
* \param num Interrupt number \ref interrupt_nums
*/
void irq_set_pending(uint num);
/*! \brief Perform IRQ priority initialization for the current core
*
* \note This is an internal method and user should generally not call it.
*/
void irq_init_priorities(void);
/*! \brief Claim ownership of a user IRQ on the calling core
* \ingroup hardware_irq
*
* User IRQs are numbered 26-31 and are not connected to any hardware, but can be triggered by \ref irq_set_pending.
*
* \note User IRQs are a core local feature; they cannot be used to communicate between cores. Therfore all functions
* dealing with Uer IRQs affect only the calling core
*
* This method explicitly claims ownership of a user IRQ, so other code can know it is being used.
*
* \param irq_num the user IRQ to claim
*/
void user_irq_claim(uint irq_num);
/*! \brief Mark a user IRQ as no longer used on the calling core
* \ingroup hardware_irq
*
* User IRQs are numbered 26-31 and are not connected to any hardware, but can be triggered by \ref irq_set_pending.
*
* \note User IRQs are a core local feature; they cannot be used to communicate between cores. Therfore all functions
* dealing with Uer IRQs affect only the calling core
*
* This method explicitly releases ownership of a user IRQ, so other code can know it is free to use.
*
* \note it is customary to have disabled the irq and removed the handler prior to calling this method.
*
* \param irq_num the irq irq_num to unclaim
*/
void user_irq_unclaim(uint irq_num);
/*! \brief Claim ownership of a free user IRQ on the calling core
* \ingroup hardware_irq
*
* User IRQs are numbered 26-31 and are not connected to any hardware, but can be triggered by \ref irq_set_pending.
*
* \note User IRQs are a core local feature; they cannot be used to communicate between cores. Therfore all functions
* dealing with Uer IRQs affect only the calling core
*
* This method explicitly claims ownership of an unused user IRQ if there is one, so other code can know it is being used.
*
* \param required if true the function will panic if none are available
* \return the user IRQ number or -1 if required was false, and none were free
*/
int user_irq_claim_unused(bool required);
/*
*! \brief Check if a user IRQ is in use on the calling core
* \ingroup hardware_irq
*
* User IRQs are numbered 26-31 and are not connected to any hardware, but can be triggered by \ref irq_set_pending.
*
* \note User IRQs are a core local feature; they cannot be used to communicate between cores. Therfore all functions
* dealing with Uer IRQs affect only the calling core
*
* \param irq_num the irq irq_num
* \return true if the irq_num is claimed, false otherwise
* \sa user_irq_claim
* \sa user_irq_unclaim
* \sa user_irq_claim_unused
*/
bool user_irq_is_claimed(uint irq_num);
#ifdef __cplusplus
}
#endif
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_irq/irq.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "hardware/irq.h"
#include "hardware/regs/m0plus.h"
#include "hardware/platform_defs.h"
#include "hardware/structs/scb.h"
#include "hardware/claim.h"
#include "pico/mutex.h"
#include "pico/assert.h"
extern void __unhandled_user_irq(void);
#if PICO_VTABLE_PER_CORE
static uint8_t user_irq_claimed[NUM_CORES];
static inline uint8_t *user_irq_claimed_ptr(void) {
return &user_irq_claimed[get_core_num()];
}
#else
static uint8_t user_irq_claimed;
static inline uint8_t *user_irq_claimed_ptr(void) {
return &user_irq_claimed;
}
#endif
static inline irq_handler_t *get_vtable(void) {
return (irq_handler_t *) scb_hw->vtor;
}
static inline void *add_thumb_bit(void *addr) {
return (void *) (((uintptr_t) addr) | 0x1);
}
static inline void *remove_thumb_bit(void *addr) {
return (void *) (((uintptr_t) addr) & (uint)~0x1);
}
static void set_raw_irq_handler_and_unlock(uint num, irq_handler_t handler, uint32_t save) {
// update vtable (vtable_handler may be same or updated depending on cases, but we do it anyway for compactness)
get_vtable()[VTABLE_FIRST_IRQ + num] = handler;
__dmb();
spin_unlock(spin_lock_instance(PICO_SPINLOCK_ID_IRQ), save);
}
void irq_set_enabled(uint num, bool enabled) {
check_irq_param(num);
irq_set_mask_enabled(1u << num, enabled);
}
bool irq_is_enabled(uint num) {
check_irq_param(num);
return 0 != ((1u << num) & *((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ISER_OFFSET)));
}
void irq_set_mask_enabled(uint32_t mask, bool enabled) {
if (enabled) {
// Clear pending before enable
// (if IRQ is actually asserted, it will immediately re-pend)
*((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ICPR_OFFSET)) = mask;
*((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ISER_OFFSET)) = mask;
} else {
*((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ICER_OFFSET)) = mask;
}
}
void irq_set_pending(uint num) {
check_irq_param(num);
*((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ISPR_OFFSET)) = 1u << num;
}
#if !PICO_DISABLE_SHARED_IRQ_HANDLERS
// limited by 8 bit relative links (and reality)
static_assert(PICO_MAX_SHARED_IRQ_HANDLERS >= 1 && PICO_MAX_SHARED_IRQ_HANDLERS < 0x7f, "");
// note these are not real functions, they are code fragments (i.e. don't call them)
extern void irq_handler_chain_first_slot(void);
extern void irq_handler_chain_remove_tail(void);
extern struct irq_handler_chain_slot {
// first 3 half words are executable code (raw vtable handler points to one slot, and inst3 will jump to next
// in chain (or end of chain handler)
uint16_t inst1;
uint16_t inst2;
uint16_t inst3;
union {
// when a handler is removed while executing, it needs an extra instruction, which overwrites
// the link and the priority; this is ok because no one else is modifying the chain, as
// the chain is effectively core local, and the user code which might still need this link
// disable the IRQ in question before updating, which means we aren't executing!
struct {
int8_t link;
uint8_t priority;
};
uint16_t inst4;
};
irq_handler_t handler;
} irq_handler_chain_slots[PICO_MAX_SHARED_IRQ_HANDLERS];
static int8_t irq_hander_chain_free_slot_head;
static inline bool is_shared_irq_raw_handler(irq_handler_t raw_handler) {
return (uintptr_t)raw_handler - (uintptr_t)irq_handler_chain_slots < sizeof(irq_handler_chain_slots);
}
bool irq_has_shared_handler(uint irq_num) {
check_irq_param(irq_num);
irq_handler_t handler = irq_get_vtable_handler(irq_num);
return handler && is_shared_irq_raw_handler(handler);
}
#else
#define is_shared_irq_raw_handler(h) false
bool irq_has_shared_handler(uint irq_num) {
return false;
}
#endif
irq_handler_t irq_get_vtable_handler(uint num) {
check_irq_param(num);
return get_vtable()[16 + num];
}
void irq_set_exclusive_handler(uint num, irq_handler_t handler) {
check_irq_param(num);
#if !PICO_NO_RAM_VECTOR_TABLE
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
uint32_t save = spin_lock_blocking(lock);
__unused irq_handler_t current = irq_get_vtable_handler(num);
hard_assert(current == __unhandled_user_irq || current == handler);
set_raw_irq_handler_and_unlock(num, handler, save);
#else
panic_unsupported();
#endif
}
irq_handler_t irq_get_exclusive_handler(uint num) {
check_irq_param(num);
#if !PICO_NO_RAM_VECTOR_TABLE
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
uint32_t save = spin_lock_blocking(lock);
irq_handler_t current = irq_get_vtable_handler(num);
spin_unlock(lock, save);
if (current == __unhandled_user_irq || is_shared_irq_raw_handler(current)) {
return NULL;
}
return current;
#else
panic_unsupported();
#endif
}
#if !PICO_DISABLE_SHARED_IRQ_HANDLERS
static uint16_t make_branch(uint16_t *from, void *to) {
uint32_t ui_from = (uint32_t)from;
uint32_t ui_to = (uint32_t)to;
int32_t delta = (int32_t)(ui_to - ui_from - 4);
assert(delta >= -2048 && delta <= 2046 && !(delta & 1));
return (uint16_t)(0xe000 | ((delta >> 1) & 0x7ff));
}
static void insert_branch_and_link(uint16_t *from, void *to) {
uint32_t ui_from = (uint32_t)from;
uint32_t ui_to = (uint32_t)to;
uint32_t delta = (ui_to - ui_from - 4) / 2;
assert(!(delta >> 11u));
from[0] = (uint16_t)(0xf000 | ((delta >> 11u) & 0x7ffu));
from[1] = (uint16_t)(0xf800 | (delta & 0x7ffu));
}
static inline void *resolve_branch(uint16_t *inst) {
assert(0x1c == (*inst)>>11u);
int32_t i_addr = (*inst) << 21u;
i_addr /= (int32_t)(1u<<21u);
return inst + 2 + i_addr;
}
// GCC produces horrible code for subtraction of pointers here, and it was bugging me
static inline int8_t slot_diff(struct irq_handler_chain_slot *to, struct irq_handler_chain_slot *from) {
static_assert(sizeof(struct irq_handler_chain_slot) == 12, "");
int32_t result = 0xaaaa;
// return (to - from);
// note this implementation has limited range, but is fine for plenty more than -128->127 result
pico_default_asm (
"subs %1, %2\n"
"adcs %1, %1\n" // * 2 (and + 1 if negative for rounding)
"muls %0, %1\n"
"lsrs %0, %0, #20\n"
: "+l" (result), "+l" (to)
: "l" (from)
:
);
return (int8_t)result;
}
static inline int8_t get_slot_index(struct irq_handler_chain_slot *slot) {
return slot_diff(slot, irq_handler_chain_slots);
}
#endif
void irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_priority) {
check_irq_param(num);
#if PICO_NO_RAM_VECTOR_TABLE
panic_unsupported()
#elif PICO_DISABLE_SHARED_IRQ_HANDLERS
irq_set_exclusive_handler(num, handler);
#else
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
uint32_t save = spin_lock_blocking(lock);
hard_assert(irq_hander_chain_free_slot_head >= 0); // we must have a slot
struct irq_handler_chain_slot *slot = &irq_handler_chain_slots[irq_hander_chain_free_slot_head];
int8_t slot_index = irq_hander_chain_free_slot_head;
irq_hander_chain_free_slot_head = slot->link;
irq_handler_t vtable_handler = get_vtable()[16 + num];
if (!is_shared_irq_raw_handler(vtable_handler)) {
// start new chain
hard_assert(vtable_handler == __unhandled_user_irq);
struct irq_handler_chain_slot slot_data = {
.inst1 = 0xa100, // add r1, pc, #0
.inst2 = make_branch(&slot->inst2, (void *) irq_handler_chain_first_slot), // b irq_handler_chain_first_slot
.inst3 = 0xbd01, // pop {r0, pc}
.link = -1,
.priority = order_priority,
.handler = handler
};
*slot = slot_data;
vtable_handler = (irq_handler_t)add_thumb_bit(slot);
} else {
assert(!((((uintptr_t)vtable_handler) - ((uintptr_t)irq_handler_chain_slots) - 1)%sizeof(struct irq_handler_chain_slot)));
struct irq_handler_chain_slot *prev_slot = NULL;
struct irq_handler_chain_slot *existing_vtable_slot = remove_thumb_bit((void *) vtable_handler);
struct irq_handler_chain_slot *cur_slot = existing_vtable_slot;
while (cur_slot->priority > order_priority) {
prev_slot = cur_slot;
if (cur_slot->link < 0) break;
cur_slot = &irq_handler_chain_slots[cur_slot->link];
}
if (prev_slot) {
// insert into chain
struct irq_handler_chain_slot slot_data = {
.inst1 = 0x4801, // ldr r0, [pc, #4]
.inst2 = 0x4780, // blx r0
.inst3 = prev_slot->link >= 0 ?
make_branch(&slot->inst3, resolve_branch(&prev_slot->inst3)) : // b next_slot
0xbd01, // pop {r0, pc}
.link = prev_slot->link,
.priority = order_priority,
.handler = handler
};
// update code and data links
prev_slot->inst3 = make_branch(&prev_slot->inst3, slot),
prev_slot->link = slot_index;
*slot = slot_data;
} else {
// update with new chain head
struct irq_handler_chain_slot slot_data = {
.inst1 = 0xa100, // add r1, pc, #0
.inst2 = make_branch(&slot->inst2, (void *) irq_handler_chain_first_slot), // b irq_handler_chain_first_slot
.inst3 = make_branch(&slot->inst3, existing_vtable_slot), // b existing_slot
.link = get_slot_index(existing_vtable_slot),
.priority = order_priority,
.handler = handler
};
*slot = slot_data;
// fixup previous head slot
existing_vtable_slot->inst1 = 0x4801; // ldr r0, [pc, #4]
existing_vtable_slot->inst2 = 0x4780; // blx r0
vtable_handler = (irq_handler_t)add_thumb_bit(slot);
}
}
set_raw_irq_handler_and_unlock(num, vtable_handler, save);
#endif
}
void irq_remove_handler(uint num, irq_handler_t handler) {
#if !PICO_NO_RAM_VECTOR_TABLE
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
uint32_t save = spin_lock_blocking(lock);
irq_handler_t vtable_handler = get_vtable()[16 + num];
if (vtable_handler != __unhandled_user_irq && vtable_handler != handler) {
#if !PICO_DISABLE_SHARED_IRQ_HANDLERS
if (is_shared_irq_raw_handler(vtable_handler)) {
// This is a bit tricky, as an executing IRQ handler doesn't take a lock.
// First thing to do is to disable the IRQ in question; that takes care of calls from user code.
// Note that a irq handler chain is local to our own core, so we don't need to worry about the other core
bool was_enabled = irq_is_enabled(num);
irq_set_enabled(num, false);
__dmb();
// It is possible we are being called while an IRQ for this chain is already in progress.
// The issue we have here is that we must not free a slot that is currently being executed, because
// inst3 is still to be executed, and inst3 might get overwritten if the slot is re-used.
// By disallowing other exceptions from removing an IRQ handler (which seems fair)
// we now only have to worry about removing a slot from a chain that is currently executing.
// Note we expect that the slot we are deleting is the one that is executing.
// In particular, bad things happen if the caller were to delete the handler in the chain
// before it. This is not an allowed use case though, and I can't imagine anyone wanting to in practice.
// Sadly this is not something we can detect.
uint exception = __get_current_exception();
hard_assert(!exception || exception == num + VTABLE_FIRST_IRQ);
struct irq_handler_chain_slot *prev_slot = NULL;
struct irq_handler_chain_slot *existing_vtable_slot = remove_thumb_bit((void *) vtable_handler);
struct irq_handler_chain_slot *to_free_slot = existing_vtable_slot;
while (to_free_slot->handler != handler) {
prev_slot = to_free_slot;
if (to_free_slot->link < 0) break;
to_free_slot = &irq_handler_chain_slots[to_free_slot->link];
}
if (to_free_slot->handler == handler) {
int8_t next_slot_index = to_free_slot->link;
if (next_slot_index >= 0) {
// There is another slot in the chain, so copy that over us, so that our inst3 points at something valid
// Note this only matters in the exception case anyway, and it that case, we will skip the next handler,
// however in that case its IRQ cause should immediately cause re-entry of the IRQ and the only side
// effect will be that there was potentially brief out of priority order execution of the handlers
struct irq_handler_chain_slot *next_slot = &irq_handler_chain_slots[next_slot_index];
to_free_slot->handler = next_slot->handler;
to_free_slot->priority = next_slot->priority;
to_free_slot->link = next_slot->link;
to_free_slot->inst3 = next_slot->link >= 0 ?
make_branch(&to_free_slot->inst3, resolve_branch(&next_slot->inst3)) : // b mext_>slot->next_slot
0xbd01; // pop {r0, pc}
// add old next slot back to free list
next_slot->link = irq_hander_chain_free_slot_head;
irq_hander_chain_free_slot_head = next_slot_index;
} else {
// Slot being removed is at the end of the chain
if (!exception) {
// case when we're not in exception, we physically unlink now
if (prev_slot) {
// chain is not empty
prev_slot->link = -1;
prev_slot->inst3 = 0xbd01; // pop {r0, pc}
} else {
// chain is not empty
vtable_handler = __unhandled_user_irq;
}
// add slot back to free list
to_free_slot->link = irq_hander_chain_free_slot_head;
irq_hander_chain_free_slot_head = get_slot_index(to_free_slot);
} else {
// since we are the last slot we know that our inst3 hasn't executed yet, so we change
// it to bl to irq_handler_chain_remove_tail which will remove the slot.
// NOTE THAT THIS TRASHES PRIORITY AND LINK SINCE THIS IS A 4 BYTE INSTRUCTION
// BUT THEY ARE NOT NEEDED NOW
insert_branch_and_link(&to_free_slot->inst3, (void *) irq_handler_chain_remove_tail);
}
}
} else {
assert(false); // not found
}
irq_set_enabled(num, was_enabled);
}
#else
assert(false); // not found
#endif
} else {
vtable_handler = __unhandled_user_irq;
}
set_raw_irq_handler_and_unlock(num, vtable_handler, save);
#else
panic_unsupported();
#endif
}
void irq_set_priority(uint num, uint8_t hardware_priority) {
check_irq_param(num);
// note that only 32 bit writes are supported
io_rw_32 *p = (io_rw_32 *)((PPB_BASE + M0PLUS_NVIC_IPR0_OFFSET) + (num & ~3u));
*p = (*p & ~(0xffu << (8 * (num & 3u)))) | (((uint32_t) hardware_priority) << (8 * (num & 3u)));
}
uint irq_get_priority(uint num) {
check_irq_param(num);
// note that only 32 bit reads are supported
io_rw_32 *p = (io_rw_32 *)((PPB_BASE + M0PLUS_NVIC_IPR0_OFFSET) + (num & ~3u));
return (uint8_t)(*p >> (8 * (num & 3u)));
}
#if !PICO_DISABLE_SHARED_IRQ_HANDLERS
// used by irq_handler_chain.S to remove the last link in a handler chain after it executes
// note this must be called only with the last slot in a chain (and during the exception)
void irq_add_tail_to_free_list(struct irq_handler_chain_slot *slot) {
irq_handler_t slot_handler = (irq_handler_t) add_thumb_bit(slot);
assert(is_shared_irq_raw_handler(slot_handler));
uint exception = __get_current_exception();
assert(exception);
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
uint32_t save = spin_lock_blocking(lock);
int8_t slot_index = get_slot_index(slot);
if (slot_handler == get_vtable()[exception]) {
get_vtable()[exception] = __unhandled_user_irq;
} else {
bool __unused found = false;
// need to find who points at the slot and update it
for(uint i=0;ilink = irq_hander_chain_free_slot_head;
irq_hander_chain_free_slot_head = slot_index;
spin_unlock(lock, save);
}
#endif
void irq_init_priorities() {
#if PICO_DEFAULT_IRQ_PRIORITY != 0
static_assert(!(NUM_IRQS & 3), "");
uint32_t prio4 = (PICO_DEFAULT_IRQ_PRIORITY & 0xff) * 0x1010101u;
io_rw_32 * p = (io_rw_32 *)(PPB_BASE + M0PLUS_NVIC_IPR0_OFFSET);
for (uint i = 0; i < NUM_IRQS / 4; i++) {
*p++ = prio4;
}
#endif
}
static uint get_user_irq_claim_index(uint irq_num) {
invalid_params_if(IRQ, irq_num < FIRST_USER_IRQ || irq_num >= NUM_IRQS);
// we count backwards from the last, to match the existing hard coded uses of user IRQs in the SDK which were previously using 31
static_assert(NUM_IRQS - FIRST_USER_IRQ <= 8, ""); // we only use a single byte's worth of claim bits today.
return NUM_IRQS - irq_num - 1u;
}
void user_irq_claim(uint irq_num) {
hw_claim_or_assert(user_irq_claimed_ptr(), get_user_irq_claim_index(irq_num), "User IRQ is already claimed");
}
void user_irq_unclaim(uint irq_num) {
hw_claim_clear(user_irq_claimed_ptr(), get_user_irq_claim_index(irq_num));
}
int user_irq_claim_unused(bool required) {
int bit = hw_claim_unused_from_range(user_irq_claimed_ptr(), required, 0, NUM_USER_IRQS - 1, "No user IRQs are available");
if (bit >= 0) bit = (int)NUM_IRQS - bit - 1;
return bit;
}
bool user_irq_is_claimed(uint irq_num) {
return hw_is_claimed(user_irq_claimed_ptr(), get_user_irq_claim_index(irq_num));
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_irq/irq_handler_chain.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
#include "hardware/irq.h"
#include "pico/asm_helper.S"
pico_default_asm_setup
#if !PICO_DISABLE_SHARED_IRQ_HANDLERS
.data
.align 2
.global irq_handler_chain_slots
.global irq_handler_chain_first_slot
.global irq_handler_chain_remove_tail
//
// These Slots make up the code and structure of the handler chains; the only external information are the VTABLE entries
// (obviously one set per core) and a free list head. Each individual handler chain starts with the VTABLE entry I
// pointing at the address of slot S (with thumb bit set). Thus each slot which is part of a chain is executble.
//
// The execution jumps (via branch instruction) from one slot to the other, then jumps to the end of chain handler.
// The entirety of the state needed to traverse the chain is contained within the slots of the chain, which is why
// a VTABLE entry is all that is needed per chain (rather than requiring a separarte set of head pointers)
//
irq_handler_chain_slots:
.set next_slot_number, 1
.rept PICO_MAX_SHARED_IRQ_HANDLERS
// a slot is executable and is always 3 instructions long.
.hword 0 // inst1 (either: ldr r0, [pc, #4] or for the FIRST slot : add r1, pc, #0 )
.hword 0 // inst2 ( blx r0 b irq_handler_chain_first_slot )
.hword 0 // inst3 (either: b next_slot or for the LAST pop {pc} )
// next is a single byte index of next slot in chain (or -1 to end)
.if next_slot_number == PICO_MAX_SHARED_IRQ_HANDLERS
.byte 0xff
.else
.byte next_slot_number
.endif
// next is the 8 bit unsigned priority
.byte 0x00
1:
// and finally the handler function pointer
.word 0x00000000
.set next_slot_number, next_slot_number + 1
.endr
irq_handler_chain_first_slot:
push {r0, lr} // Save EXC_RETURN token, so `pop {r0, pc}` will return from interrupt
// Note that r0 does not NEED to be saved, however we must maintain
// an 8 byte stack alignment, and this is the cheapest way to do so
ldr r0, [r1, #4] // Get `handler` field of irq_handler_chain_slot
adds r1, #1 // r1 points to `inst3` field of slot struct. Set Thumb bit on r1,
mov lr, r1 // and copy to lr, so `inst3` is executed on return from handler
bx r0 // Enter handler
irq_handler_chain_remove_tail:
mov r0, lr // Get start of struct. This function was called by a bl at offset +4,
subs r0, #9 // so lr points to offset +8. Note also lr has its Thumb bit set!
ldr r1, =irq_add_tail_to_free_list
blx r1
pop {r0, pc} // Top of stack is EXC_RETURN
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_pio/CMakeLists.txt
================================================
pico_simple_hardware_target(pio)
# additional libraries
pico_mirrored_target_link_libraries(hardware_pio INTERFACE
hardware_gpio
hardware_claim
)
================================================
FILE: pico-sdk/src/rp2_common/hardware_pio/include/hardware/pio.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_PIO_H
#define _HARDWARE_PIO_H
#include "pico.h"
#include "hardware/address_mapped.h"
#include "hardware/structs/pio.h"
#include "hardware/gpio.h"
#include "hardware/regs/dreq.h"
#include "hardware/pio_instructions.h"
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PIO, Enable/disable assertions in the PIO module, type=bool, default=0, group=hardware_pio
#ifndef PARAM_ASSERTIONS_ENABLED_PIO
#define PARAM_ASSERTIONS_ENABLED_PIO 0
#endif
/** \file hardware/pio.h
* \defgroup hardware_pio hardware_pio
*
* Programmable I/O (PIO) API
*
* A programmable input/output block (PIO) is a versatile hardware interface which
* can support a number of different IO standards. There are two PIO blocks in the RP2040.
*
* Each PIO is programmable in the same sense as a processor: the four state machines independently
* execute short, sequential programs, to manipulate GPIOs and transfer data. Unlike a general
* purpose processor, PIO state machines are highly specialised for IO, with a focus on determinism,
* precise timing, and close integration with fixed-function hardware. Each state machine is equipped
* with:
* * Two 32-bit shift registers – either direction, any shift count
* * Two 32-bit scratch registers
* * 4×32 bit bus FIFO in each direction (TX/RX), reconfigurable as 8×32 in a single direction
* * Fractional clock divider (16 integer, 8 fractional bits)
* * Flexible GPIO mapping
* * DMA interface, sustained throughput up to 1 word per clock from system DMA
* * IRQ flag set/clear/status
*
* Full details of the PIO can be found in the RP2040 datasheet.
*/
#ifdef __cplusplus
extern "C" {
#endif
static_assert(PIO_SM0_SHIFTCTRL_FJOIN_RX_LSB == PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB + 1, "");
/** \brief FIFO join states
* \ingroup hardware_pio
*/
enum pio_fifo_join {
PIO_FIFO_JOIN_NONE = 0,
PIO_FIFO_JOIN_TX = 1,
PIO_FIFO_JOIN_RX = 2,
};
/** \brief MOV status types
* \ingroup hardware_pio
*/
enum pio_mov_status_type {
STATUS_TX_LESSTHAN = 0,
STATUS_RX_LESSTHAN = 1
};
typedef pio_hw_t *PIO;
/** Identifier for the first (PIO 0) hardware PIO instance (for use in PIO functions).
*
* e.g. pio_gpio_init(pio0, 5)
*
* \ingroup hardware_pio
*/
#define pio0 pio0_hw
/** Identifier for the second (PIO 1) hardware PIO instance (for use in PIO functions).
*
* e.g. pio_gpio_init(pio1, 5)
*
* \ingroup hardware_pio
*/
#define pio1 pio1_hw
/** \brief PIO state machine configuration
* \defgroup sm_config sm_config
* \ingroup hardware_pio
*
* A PIO block needs to be configured, these functions provide helpers to set up configuration
* structures. See \ref pio_sm_set_config
*
*/
/** \brief PIO Configuration structure
* \ingroup sm_config
*
* This structure is an in-memory representation of the configuration that can be applied to a PIO
* state machine later using pio_sm_set_config() or pio_sm_init().
*/
typedef struct {
uint32_t clkdiv;
uint32_t execctrl;
uint32_t shiftctrl;
uint32_t pinctrl;
} pio_sm_config;
static inline void check_sm_param(__unused uint sm) {
valid_params_if(PIO, sm < NUM_PIO_STATE_MACHINES);
}
static inline void check_sm_mask(__unused uint mask) {
valid_params_if(PIO, mask < (1u << NUM_PIO_STATE_MACHINES));
}
static inline void check_pio_param(__unused PIO pio) {
valid_params_if(PIO, pio == pio0 || pio == pio1);
}
/*! \brief Set the 'out' pins in a state machine configuration
* \ingroup sm_config
*
* Can overlap with the 'in', 'set' and 'sideset' pins
*
* \param c Pointer to the configuration structure to modify
* \param out_base 0-31 First pin to set as output
* \param out_count 0-32 Number of pins to set.
*/
static inline void sm_config_set_out_pins(pio_sm_config *c, uint out_base, uint out_count) {
valid_params_if(PIO, out_base < 32);
valid_params_if(PIO, out_count <= 32);
c->pinctrl = (c->pinctrl & ~(PIO_SM0_PINCTRL_OUT_BASE_BITS | PIO_SM0_PINCTRL_OUT_COUNT_BITS)) |
(out_base << PIO_SM0_PINCTRL_OUT_BASE_LSB) |
(out_count << PIO_SM0_PINCTRL_OUT_COUNT_LSB);
}
/*! \brief Set the 'set' pins in a state machine configuration
* \ingroup sm_config
*
* Can overlap with the 'in', 'out' and 'sideset' pins
*
* \param c Pointer to the configuration structure to modify
* \param set_base 0-31 First pin to set as
* \param set_count 0-5 Number of pins to set.
*/
static inline void sm_config_set_set_pins(pio_sm_config *c, uint set_base, uint set_count) {
valid_params_if(PIO, set_base < 32);
valid_params_if(PIO, set_count <= 5);
c->pinctrl = (c->pinctrl & ~(PIO_SM0_PINCTRL_SET_BASE_BITS | PIO_SM0_PINCTRL_SET_COUNT_BITS)) |
(set_base << PIO_SM0_PINCTRL_SET_BASE_LSB) |
(set_count << PIO_SM0_PINCTRL_SET_COUNT_LSB);
}
/*! \brief Set the 'in' pins in a state machine configuration
* \ingroup sm_config
*
* Can overlap with the 'out', 'set' and 'sideset' pins
*
* \param c Pointer to the configuration structure to modify
* \param in_base 0-31 First pin to use as input
*/
static inline void sm_config_set_in_pins(pio_sm_config *c, uint in_base) {
valid_params_if(PIO, in_base < 32);
c->pinctrl = (c->pinctrl & ~PIO_SM0_PINCTRL_IN_BASE_BITS) |
(in_base << PIO_SM0_PINCTRL_IN_BASE_LSB);
}
/*! \brief Set the 'sideset' pins in a state machine configuration
* \ingroup sm_config
*
* Can overlap with the 'in', 'out' and 'set' pins
*
* \param c Pointer to the configuration structure to modify
* \param sideset_base 0-31 base pin for 'side set'
*/
static inline void sm_config_set_sideset_pins(pio_sm_config *c, uint sideset_base) {
valid_params_if(PIO, sideset_base < 32);
c->pinctrl = (c->pinctrl & ~PIO_SM0_PINCTRL_SIDESET_BASE_BITS) |
(sideset_base << PIO_SM0_PINCTRL_SIDESET_BASE_LSB);
}
/*! \brief Set the 'sideset' options in a state machine configuration
* \ingroup sm_config
*
* \param c Pointer to the configuration structure to modify
* \param bit_count Number of bits to steal from delay field in the instruction for use of side set (max 5)
* \param optional True if the topmost side set bit is used as a flag for whether to apply side set on that instruction
* \param pindirs True if the side set affects pin directions rather than values
*/
static inline void sm_config_set_sideset(pio_sm_config *c, uint bit_count, bool optional, bool pindirs) {
valid_params_if(PIO, bit_count <= 5);
valid_params_if(PIO, !optional || bit_count >= 1);
c->pinctrl = (c->pinctrl & ~PIO_SM0_PINCTRL_SIDESET_COUNT_BITS) |
(bit_count << PIO_SM0_PINCTRL_SIDESET_COUNT_LSB);
c->execctrl = (c->execctrl & ~(PIO_SM0_EXECCTRL_SIDE_EN_BITS | PIO_SM0_EXECCTRL_SIDE_PINDIR_BITS)) |
(bool_to_bit(optional) << PIO_SM0_EXECCTRL_SIDE_EN_LSB) |
(bool_to_bit(pindirs) << PIO_SM0_EXECCTRL_SIDE_PINDIR_LSB);
}
/*! \brief Set the state machine clock divider (from integer and fractional parts - 16:8) in a state machine configuration
* \ingroup sm_config
*
* The clock divider can slow the state machine's execution to some rate below
* the system clock frequency, by enabling the state machine on some cycles
* but not on others, in a regular pattern. This can be used to generate e.g.
* a given UART baud rate. See the datasheet for further detail.
*
* \param c Pointer to the configuration structure to modify
* \param div_int Integer part of the divisor
* \param div_frac Fractional part in 1/256ths
* \sa sm_config_set_clkdiv()
*/
static inline void sm_config_set_clkdiv_int_frac(pio_sm_config *c, uint16_t div_int, uint8_t div_frac) {
invalid_params_if(PIO, div_int == 0 && div_frac != 0);
c->clkdiv =
(((uint)div_frac) << PIO_SM0_CLKDIV_FRAC_LSB) |
(((uint)div_int) << PIO_SM0_CLKDIV_INT_LSB);
}
static inline void pio_calculate_clkdiv_from_float(float div, uint16_t *div_int, uint8_t *div_frac) {
valid_params_if(PIO, div >= 1 && div <= 65536);
*div_int = (uint16_t)div;
if (*div_int == 0) {
*div_frac = 0;
} else {
*div_frac = (uint8_t)((div - (float)*div_int) * (1u << 8u));
}
}
/*! \brief Set the state machine clock divider (from a floating point value) in a state machine configuration
* \ingroup sm_config
*
* The clock divider slows the state machine's execution by masking the
* system clock on some cycles, in a repeating pattern, so that the state
* machine does not advance. Effectively this produces a slower clock for the
* state machine to run from, which can be used to generate e.g. a particular
* UART baud rate. See the datasheet for further detail.
*
* \param c Pointer to the configuration structure to modify
* \param div The fractional divisor to be set. 1 for full speed. An integer clock divisor of n
* will cause the state machine to run 1 cycle in every n.
* Note that for small n, the jitter introduced by a fractional divider (e.g. 2.5) may be unacceptable
* although it will depend on the use case.
*/
static inline void sm_config_set_clkdiv(pio_sm_config *c, float div) {
uint16_t div_int;
uint8_t div_frac;
pio_calculate_clkdiv_from_float(div, &div_int, &div_frac);
sm_config_set_clkdiv_int_frac(c, div_int, div_frac);
}
/*! \brief Set the wrap addresses in a state machine configuration
* \ingroup sm_config
*
* \param c Pointer to the configuration structure to modify
* \param wrap_target the instruction memory address to wrap to
* \param wrap the instruction memory address after which to set the program counter to wrap_target
* if the instruction does not itself update the program_counter
*/
static inline void sm_config_set_wrap(pio_sm_config *c, uint wrap_target, uint wrap) {
valid_params_if(PIO, wrap < PIO_INSTRUCTION_COUNT);
valid_params_if(PIO, wrap_target < PIO_INSTRUCTION_COUNT);
c->execctrl = (c->execctrl & ~(PIO_SM0_EXECCTRL_WRAP_TOP_BITS | PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS)) |
(wrap_target << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) |
(wrap << PIO_SM0_EXECCTRL_WRAP_TOP_LSB);
}
/*! \brief Set the 'jmp' pin in a state machine configuration
* \ingroup sm_config
*
* \param c Pointer to the configuration structure to modify
* \param pin The raw GPIO pin number to use as the source for a `jmp pin` instruction
*/
static inline void sm_config_set_jmp_pin(pio_sm_config *c, uint pin) {
valid_params_if(PIO, pin < 32);
c->execctrl = (c->execctrl & ~PIO_SM0_EXECCTRL_JMP_PIN_BITS) |
(pin << PIO_SM0_EXECCTRL_JMP_PIN_LSB);
}
/*! \brief Setup 'in' shifting parameters in a state machine configuration
* \ingroup sm_config
*
* \param c Pointer to the configuration structure to modify
* \param shift_right true to shift ISR to right, false to shift ISR to left
* \param autopush whether autopush is enabled
* \param push_threshold threshold in bits to shift in before auto/conditional re-pushing of the ISR
*/
static inline void sm_config_set_in_shift(pio_sm_config *c, bool shift_right, bool autopush, uint push_threshold) {
valid_params_if(PIO, push_threshold <= 32);
c->shiftctrl = (c->shiftctrl &
~(PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_BITS |
PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS |
PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS)) |
(bool_to_bit(shift_right) << PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_LSB) |
(bool_to_bit(autopush) << PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB) |
((push_threshold & 0x1fu) << PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB);
}
/*! \brief Setup 'out' shifting parameters in a state machine configuration
* \ingroup sm_config
*
* \param c Pointer to the configuration structure to modify
* \param shift_right true to shift OSR to right, false to shift OSR to left
* \param autopull whether autopull is enabled
* \param pull_threshold threshold in bits to shift out before auto/conditional re-pulling of the OSR
*/
static inline void sm_config_set_out_shift(pio_sm_config *c, bool shift_right, bool autopull, uint pull_threshold) {
valid_params_if(PIO, pull_threshold <= 32);
c->shiftctrl = (c->shiftctrl &
~(PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS |
PIO_SM0_SHIFTCTRL_AUTOPULL_BITS |
PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS)) |
(bool_to_bit(shift_right) << PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB) |
(bool_to_bit(autopull) << PIO_SM0_SHIFTCTRL_AUTOPULL_LSB) |
((pull_threshold & 0x1fu) << PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB);
}
/*! \brief Setup the FIFO joining in a state machine configuration
* \ingroup sm_config
*
* \param c Pointer to the configuration structure to modify
* \param join Specifies the join type. \see enum pio_fifo_join
*/
static inline void sm_config_set_fifo_join(pio_sm_config *c, enum pio_fifo_join join) {
valid_params_if(PIO, join == PIO_FIFO_JOIN_NONE || join == PIO_FIFO_JOIN_TX || join == PIO_FIFO_JOIN_RX);
c->shiftctrl = (c->shiftctrl & (uint)~(PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS | PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS)) |
(((uint)join) << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB);
}
/*! \brief Set special 'out' operations in a state machine configuration
* \ingroup sm_config
*
* \param c Pointer to the configuration structure to modify
* \param sticky to enable 'sticky' output (i.e. re-asserting most recent OUT/SET pin values on subsequent cycles)
* \param has_enable_pin true to enable auxiliary OUT enable pin
* \param enable_pin_index pin index for auxiliary OUT enable
*/
static inline void sm_config_set_out_special(pio_sm_config *c, bool sticky, bool has_enable_pin, uint enable_pin_index) {
c->execctrl = (c->execctrl &
(uint)~(PIO_SM0_EXECCTRL_OUT_STICKY_BITS | PIO_SM0_EXECCTRL_INLINE_OUT_EN_BITS |
PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS)) |
(bool_to_bit(sticky) << PIO_SM0_EXECCTRL_OUT_STICKY_LSB) |
(bool_to_bit(has_enable_pin) << PIO_SM0_EXECCTRL_INLINE_OUT_EN_LSB) |
((enable_pin_index << PIO_SM0_EXECCTRL_OUT_EN_SEL_LSB) & PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS);
}
/*! \brief Set source for 'mov status' in a state machine configuration
* \ingroup sm_config
*
* \param c Pointer to the configuration structure to modify
* \param status_sel the status operation selector. \see enum pio_mov_status_type
* \param status_n parameter for the mov status operation (currently a bit count)
*/
static inline void sm_config_set_mov_status(pio_sm_config *c, enum pio_mov_status_type status_sel, uint status_n) {
valid_params_if(PIO, status_sel == STATUS_TX_LESSTHAN || status_sel == STATUS_RX_LESSTHAN);
c->execctrl = (c->execctrl
& ~(PIO_SM0_EXECCTRL_STATUS_SEL_BITS | PIO_SM0_EXECCTRL_STATUS_N_BITS))
| ((((uint)status_sel) << PIO_SM0_EXECCTRL_STATUS_SEL_LSB) & PIO_SM0_EXECCTRL_STATUS_SEL_BITS)
| ((status_n << PIO_SM0_EXECCTRL_STATUS_N_LSB) & PIO_SM0_EXECCTRL_STATUS_N_BITS);
}
/*! \brief Get the default state machine configuration
* \ingroup sm_config
*
* Setting | Default
* --------|--------
* Out Pins | 32 starting at 0
* Set Pins | 0 starting at 0
* In Pins (base) | 0
* Side Set Pins (base) | 0
* Side Set | disabled
* Wrap | wrap=31, wrap_to=0
* In Shift | shift_direction=right, autopush=false, push_threshold=32
* Out Shift | shift_direction=right, autopull=false, pull_threshold=32
* Jmp Pin | 0
* Out Special | sticky=false, has_enable_pin=false, enable_pin_index=0
* Mov Status | status_sel=STATUS_TX_LESSTHAN, n=0
*
* \return the default state machine configuration which can then be modified.
*/
static inline pio_sm_config pio_get_default_sm_config(void) {
pio_sm_config c = {0, 0, 0, 0};
sm_config_set_clkdiv_int_frac(&c, 1, 0);
sm_config_set_wrap(&c, 0, 31);
sm_config_set_in_shift(&c, true, false, 32);
sm_config_set_out_shift(&c, true, false, 32);
return c;
}
/*! \brief Apply a state machine configuration to a state machine
* \ingroup hardware_pio
*
* \param pio Handle to PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param config the configuration to apply
*/
static inline void pio_sm_set_config(PIO pio, uint sm, const pio_sm_config *config) {
check_pio_param(pio);
check_sm_param(sm);
pio->sm[sm].clkdiv = config->clkdiv;
pio->sm[sm].execctrl = config->execctrl;
pio->sm[sm].shiftctrl = config->shiftctrl;
pio->sm[sm].pinctrl = config->pinctrl;
}
/*! \brief Return the instance number of a PIO instance
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \return the PIO instance number (either 0 or 1)
*/
static inline uint pio_get_index(PIO pio) {
check_pio_param(pio);
return pio == pio1 ? 1 : 0;
}
/*! \brief Setup the function select for a GPIO to use output from the given PIO instance
* \ingroup hardware_pio
*
* PIO appears as an alternate function in the GPIO muxing, just like an SPI
* or UART. This function configures that multiplexing to connect a given PIO
* instance to a GPIO. Note that this is not necessary for a state machine to
* be able to read the *input* value from a GPIO, but only for it to set the
* output value or output enable.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param pin the GPIO pin whose function select to set
*/
static inline void pio_gpio_init(PIO pio, uint pin) {
check_pio_param(pio);
valid_params_if(PIO, pin < 32);
gpio_set_function(pin, pio == pio0 ? GPIO_FUNC_PIO0 : GPIO_FUNC_PIO1);
}
/*! \brief Return the DREQ to use for pacing transfers to/from a particular state machine FIFO
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param is_tx true for sending data to the state machine, false for receiving data from the state machine
*/
static inline uint pio_get_dreq(PIO pio, uint sm, bool is_tx) {
static_assert(DREQ_PIO0_TX1 == DREQ_PIO0_TX0 + 1, "");
static_assert(DREQ_PIO0_TX2 == DREQ_PIO0_TX0 + 2, "");
static_assert(DREQ_PIO0_TX3 == DREQ_PIO0_TX0 + 3, "");
static_assert(DREQ_PIO0_RX0 == DREQ_PIO0_TX0 + NUM_PIO_STATE_MACHINES, "");
static_assert(DREQ_PIO1_RX0 == DREQ_PIO1_TX0 + NUM_PIO_STATE_MACHINES, "");
check_pio_param(pio);
check_sm_param(sm);
return sm + (is_tx ? 0 : NUM_PIO_STATE_MACHINES) + (pio == pio0 ? DREQ_PIO0_TX0 : DREQ_PIO1_TX0);
}
typedef struct pio_program {
const uint16_t *instructions;
uint8_t length;
int8_t origin; // required instruction memory origin or -1
} __packed pio_program_t;
/*! \brief Determine whether the given program can (at the time of the call) be loaded onto the PIO instance
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param program the program definition
* \return true if the program can be loaded; false if there is not suitable space in the instruction memory
*/
bool pio_can_add_program(PIO pio, const pio_program_t *program);
/*! \brief Determine whether the given program can (at the time of the call) be loaded onto the PIO instance starting at a particular location
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param program the program definition
* \param offset the instruction memory offset wanted for the start of the program
* \return true if the program can be loaded at that location; false if there is not space in the instruction memory
*/
bool pio_can_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset);
/*! \brief Attempt to load the program, panicking if not possible
* \ingroup hardware_pio
*
* \see pio_can_add_program() if you need to check whether the program can be loaded
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param program the program definition
* \return the instruction memory offset the program is loaded at
*/
uint pio_add_program(PIO pio, const pio_program_t *program);
/*! \brief Attempt to load the program at the specified instruction memory offset, panicking if not possible
* \ingroup hardware_pio
*
* \see pio_can_add_program_at_offset() if you need to check whether the program can be loaded
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param program the program definition
* \param offset the instruction memory offset wanted for the start of the program
*/
void pio_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset);
/*! \brief Remove a program from a PIO instance's instruction memory
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param program the program definition
* \param loaded_offset the loaded offset returned when the program was added
*/
void pio_remove_program(PIO pio, const pio_program_t *program, uint loaded_offset);
/*! \brief Clears all of a PIO instance's instruction memory
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
*/
void pio_clear_instruction_memory(PIO pio);
/*! \brief Resets the state machine to a consistent state, and configures it
* \ingroup hardware_pio
*
* This method:
* - Disables the state machine (if running)
* - Clears the FIFOs
* - Applies the configuration specified by 'config'
* - Resets any internal state e.g. shift counters
* - Jumps to the initial program location given by 'initial_pc'
*
* The state machine is left disabled on return from this call.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param initial_pc the initial program memory offset to run from
* \param config the configuration to apply (or NULL to apply defaults)
*/
void pio_sm_init(PIO pio, uint sm, uint initial_pc, const pio_sm_config *config);
/*! \brief Enable or disable a PIO state machine
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param enabled true to enable the state machine; false to disable
*/
static inline void pio_sm_set_enabled(PIO pio, uint sm, bool enabled) {
check_pio_param(pio);
check_sm_param(sm);
pio->ctrl = (pio->ctrl & ~(1u << sm)) | (bool_to_bit(enabled) << sm);
}
/*! \brief Enable or disable multiple PIO state machines
* \ingroup hardware_pio
*
* Note that this method just sets the enabled state of the state machine;
* if now enabled they continue exactly from where they left off.
*
* \see pio_enable_sm_mask_in_sync() if you wish to enable multiple state machines
* and ensure their clock dividers are in sync.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param mask bit mask of state machine indexes to modify the enabled state of
* \param enabled true to enable the state machines; false to disable
*/
static inline void pio_set_sm_mask_enabled(PIO pio, uint32_t mask, bool enabled) {
check_pio_param(pio);
check_sm_mask(mask);
pio->ctrl = (pio->ctrl & ~mask) | (enabled ? mask : 0u);
}
/*! \brief Restart a state machine with a known state
* \ingroup hardware_pio
*
* This method clears the ISR, shift counters, clock divider counter
* pin write flags, delay counter, latched EXEC instruction, and IRQ wait condition.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
*/
static inline void pio_sm_restart(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
hw_set_bits(&pio->ctrl, 1u << (PIO_CTRL_SM_RESTART_LSB + sm));
}
/*! \brief Restart multiple state machine with a known state
* \ingroup hardware_pio
*
* This method clears the ISR, shift counters, clock divider counter
* pin write flags, delay counter, latched EXEC instruction, and IRQ wait condition.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param mask bit mask of state machine indexes to modify the enabled state of
*/
static inline void pio_restart_sm_mask(PIO pio, uint32_t mask) {
check_pio_param(pio);
check_sm_mask(mask);
hw_set_bits(&pio->ctrl, (mask << PIO_CTRL_SM_RESTART_LSB) & PIO_CTRL_SM_RESTART_BITS);
}
/*! \brief Restart a state machine's clock divider from a phase of 0
* \ingroup hardware_pio
*
* Each state machine's clock divider is a free-running piece of hardware,
* that generates a pattern of clock enable pulses for the state machine,
* based *only* on the configured integer/fractional divisor. The pattern of
* running/halted cycles slows the state machine's execution to some
* controlled rate.
*
* This function clears the divider's integer and fractional phase
* accumulators so that it restarts this pattern from the beginning. It is
* called automatically by pio_sm_init() but can also be called at a later
* time, when you enable the state machine, to ensure precisely consistent
* timing each time you load and run a given PIO program.
*
* More commonly this hardware mechanism is used to synchronise the execution
* clocks of multiple state machines -- see pio_clkdiv_restart_sm_mask().
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
*/
static inline void pio_sm_clkdiv_restart(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
hw_set_bits(&pio->ctrl, 1u << (PIO_CTRL_CLKDIV_RESTART_LSB + sm));
}
/*! \brief Restart multiple state machines' clock dividers from a phase of 0.
* \ingroup hardware_pio
*
* Each state machine's clock divider is a free-running piece of hardware,
* that generates a pattern of clock enable pulses for the state machine,
* based *only* on the configured integer/fractional divisor. The pattern of
* running/halted cycles slows the state machine's execution to some
* controlled rate.
*
* This function simultaneously clears the integer and fractional phase
* accumulators of multiple state machines' clock dividers. If these state
* machines all have the same integer and fractional divisors configured,
* their clock dividers will run in precise deterministic lockstep from this
* point.
*
* With their execution clocks synchronised in this way, it is then safe to
* e.g. have multiple state machines performing a 'wait irq' on the same flag,
* and all clear it on the same cycle.
*
* Also note that this function can be called whilst state machines are
* running (e.g. if you have just changed the clock divisors of some state
* machines and wish to resynchronise them), and that disabling a state
* machine does not halt its clock divider: that is, if multiple state
* machines have their clocks synchronised, you can safely disable and
* reenable one of the state machines without losing synchronisation.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param mask bit mask of state machine indexes to modify the enabled state of
*/
static inline void pio_clkdiv_restart_sm_mask(PIO pio, uint32_t mask) {
check_pio_param(pio);
check_sm_mask(mask);
hw_set_bits(&pio->ctrl, (mask << PIO_CTRL_CLKDIV_RESTART_LSB) & PIO_CTRL_CLKDIV_RESTART_BITS);
}
/*! \brief Enable multiple PIO state machines synchronizing their clock dividers
* \ingroup hardware_pio
*
* This is equivalent to calling both pio_set_sm_mask_enabled() and
* pio_clkdiv_restart_sm_mask() on the *same* clock cycle. All state machines
* specified by 'mask' are started simultaneously and, assuming they have the
* same clock divisors, their divided clocks will stay precisely synchronised.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param mask bit mask of state machine indexes to modify the enabled state of
*/
static inline void pio_enable_sm_mask_in_sync(PIO pio, uint32_t mask) {
check_pio_param(pio);
check_sm_mask(mask);
hw_set_bits(&pio->ctrl,
((mask << PIO_CTRL_CLKDIV_RESTART_LSB) & PIO_CTRL_CLKDIV_RESTART_BITS) |
((mask << PIO_CTRL_SM_ENABLE_LSB) & PIO_CTRL_SM_ENABLE_BITS));
}
/*! \brief PIO interrupt source numbers for pio related IRQs
* \ingroup hardware_pio
*/
enum pio_interrupt_source {
pis_interrupt0 = PIO_INTR_SM0_LSB,
pis_interrupt1 = PIO_INTR_SM1_LSB,
pis_interrupt2 = PIO_INTR_SM2_LSB,
pis_interrupt3 = PIO_INTR_SM3_LSB,
pis_sm0_tx_fifo_not_full = PIO_INTR_SM0_TXNFULL_LSB,
pis_sm1_tx_fifo_not_full = PIO_INTR_SM1_TXNFULL_LSB,
pis_sm2_tx_fifo_not_full = PIO_INTR_SM2_TXNFULL_LSB,
pis_sm3_tx_fifo_not_full = PIO_INTR_SM3_TXNFULL_LSB,
pis_sm0_rx_fifo_not_empty = PIO_INTR_SM0_RXNEMPTY_LSB,
pis_sm1_rx_fifo_not_empty = PIO_INTR_SM1_RXNEMPTY_LSB,
pis_sm2_rx_fifo_not_empty = PIO_INTR_SM2_RXNEMPTY_LSB,
pis_sm3_rx_fifo_not_empty = PIO_INTR_SM3_RXNEMPTY_LSB,
};
/*! \brief Enable/Disable a single source on a PIO's IRQ 0
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param source the source number (see \ref pio_interrupt_source)
* \param enabled true to enable IRQ 0 for the source, false to disable.
*/
static inline void pio_set_irq0_source_enabled(PIO pio, enum pio_interrupt_source source, bool enabled) {
check_pio_param(pio);
invalid_params_if(PIO, source >= 12);
if (enabled)
hw_set_bits(&pio->inte0, 1u << source);
else
hw_clear_bits(&pio->inte0, 1u << source);
}
/*! \brief Enable/Disable a single source on a PIO's IRQ 1
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param source the source number (see \ref pio_interrupt_source)
* \param enabled true to enable IRQ 0 for the source, false to disable.
*/
static inline void pio_set_irq1_source_enabled(PIO pio, enum pio_interrupt_source source, bool enabled) {
check_pio_param(pio);
invalid_params_if(PIO, source >= 12);
if (enabled)
hw_set_bits(&pio->inte1, 1u << source);
else
hw_clear_bits(&pio->inte1, 1u << source);
}
/*! \brief Enable/Disable multiple sources on a PIO's IRQ 0
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param source_mask Mask of bits, one for each source number (see \ref pio_interrupt_source) to affect
* \param enabled true to enable all the sources specified in the mask on IRQ 0, false to disable all the sources specified in the mask on IRQ 0
*/
static inline void pio_set_irq0_source_mask_enabled(PIO pio, uint32_t source_mask, bool enabled) {
check_pio_param(pio);
invalid_params_if(PIO, source_mask > PIO_INTR_BITS);
if (enabled) {
hw_set_bits(&pio->inte0, source_mask);
} else {
hw_clear_bits(&pio->inte0, source_mask);
}
}
/*! \brief Enable/Disable multiple sources on a PIO's IRQ 1
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param source_mask Mask of bits, one for each source number (see \ref pio_interrupt_source) to affect
* \param enabled true to enable all the sources specified in the mask on IRQ 1, false to disable all the source specified in the mask on IRQ 1
*/
static inline void pio_set_irq1_source_mask_enabled(PIO pio, uint32_t source_mask, bool enabled) {
check_pio_param(pio);
invalid_params_if(PIO, source_mask > PIO_INTR_BITS);
if (enabled) {
hw_set_bits(&pio->inte1, source_mask);
} else {
hw_clear_bits(&pio->inte1, source_mask);
}
}
/*! \brief Enable/Disable a single source on a PIO's specified (0/1) IRQ index
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param irq_index the IRQ index; either 0 or 1
* \param source the source number (see \ref pio_interrupt_source)
* \param enabled true to enable the source on the specified IRQ, false to disable.
*/
static inline void pio_set_irqn_source_enabled(PIO pio, uint irq_index, enum pio_interrupt_source source, bool enabled) {
invalid_params_if(PIO, irq_index > 1);
if (irq_index) {
pio_set_irq1_source_enabled(pio, source, enabled);
} else {
pio_set_irq0_source_enabled(pio, source, enabled);
}
}
/*! \brief Enable/Disable multiple sources on a PIO's specified (0/1) IRQ index
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param irq_index the IRQ index; either 0 or 1
* \param source_mask Mask of bits, one for each source number (see \ref pio_interrupt_source) to affect
* \param enabled true to enable all the sources specified in the mask on the specified IRQ, false to disable all the sources specified in the mask on the specified IRQ
*/
static inline void pio_set_irqn_source_mask_enabled(PIO pio, uint irq_index, uint32_t source_mask, bool enabled) {
invalid_params_if(PIO, irq_index > 1);
if (irq_index) {
pio_set_irq1_source_mask_enabled(pio, source_mask, enabled);
} else {
pio_set_irq0_source_mask_enabled(pio, source_mask, enabled);
}
}
/*! \brief Determine if a particular PIO interrupt is set
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param pio_interrupt_num the PIO interrupt number 0-7
* \return true if corresponding PIO interrupt is currently set
*/
static inline bool pio_interrupt_get(PIO pio, uint pio_interrupt_num) {
check_pio_param(pio);
invalid_params_if(PIO, pio_interrupt_num >= 8);
return pio->irq & (1u << pio_interrupt_num);
}
/*! \brief Clear a particular PIO interrupt
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param pio_interrupt_num the PIO interrupt number 0-7
*/
static inline void pio_interrupt_clear(PIO pio, uint pio_interrupt_num) {
check_pio_param(pio);
invalid_params_if(PIO, pio_interrupt_num >= 8);
pio->irq = (1u << pio_interrupt_num);
}
/*! \brief Return the current program counter for a state machine
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \return the program counter
*/
static inline uint8_t pio_sm_get_pc(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
return (uint8_t) pio->sm[sm].addr;
}
/*! \brief Immediately execute an instruction on a state machine
* \ingroup hardware_pio
*
* This instruction is executed instead of the next instruction in the normal control flow on the state machine.
* Subsequent calls to this method replace the previous executed
* instruction if it is still running. \see pio_sm_is_exec_stalled() to see if an executed instruction
* is still running (i.e. it is stalled on some condition)
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param instr the encoded PIO instruction
*/
inline static void pio_sm_exec(PIO pio, uint sm, uint instr) {
check_pio_param(pio);
check_sm_param(sm);
pio->sm[sm].instr = instr;
}
/*! \brief Determine if an instruction set by pio_sm_exec() is stalled executing
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \return true if the executed instruction is still running (stalled)
*/
static inline bool pio_sm_is_exec_stalled(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
return !!(pio->sm[sm].execctrl & PIO_SM0_EXECCTRL_EXEC_STALLED_BITS);
}
/*! \brief Immediately execute an instruction on a state machine and wait for it to complete
* \ingroup hardware_pio
*
* This instruction is executed instead of the next instruction in the normal control flow on the state machine.
* Subsequent calls to this method replace the previous executed
* instruction if it is still running. \see pio_sm_is_exec_stalled() to see if an executed instruction
* is still running (i.e. it is stalled on some condition)
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param instr the encoded PIO instruction
*/
static inline void pio_sm_exec_wait_blocking(PIO pio, uint sm, uint instr) {
check_pio_param(pio);
check_sm_param(sm);
pio_sm_exec(pio, sm, instr);
while (pio_sm_is_exec_stalled(pio, sm)) tight_loop_contents();
}
/*! \brief Set the current wrap configuration for a state machine
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param wrap_target the instruction memory address to wrap to
* \param wrap the instruction memory address after which to set the program counter to wrap_target
* if the instruction does not itself update the program_counter
*/
static inline void pio_sm_set_wrap(PIO pio, uint sm, uint wrap_target, uint wrap) {
check_pio_param(pio);
check_sm_param(sm);
valid_params_if(PIO, wrap < PIO_INSTRUCTION_COUNT);
valid_params_if(PIO, wrap_target < PIO_INSTRUCTION_COUNT);
pio->sm[sm].execctrl =
(pio->sm[sm].execctrl & ~(PIO_SM0_EXECCTRL_WRAP_TOP_BITS | PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS)) |
(wrap_target << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) |
(wrap << PIO_SM0_EXECCTRL_WRAP_TOP_LSB);
}
/*! \brief Set the current 'out' pins for a state machine
* \ingroup hardware_pio
*
* Can overlap with the 'in', 'set' and 'sideset' pins
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param out_base 0-31 First pin to set as output
* \param out_count 0-32 Number of pins to set.
*/
static inline void pio_sm_set_out_pins(PIO pio, uint sm, uint out_base, uint out_count) {
check_pio_param(pio);
check_sm_param(sm);
valid_params_if(PIO, out_base < 32);
valid_params_if(PIO, out_count <= 32);
pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~(PIO_SM0_PINCTRL_OUT_BASE_BITS | PIO_SM0_PINCTRL_OUT_COUNT_BITS)) |
(out_base << PIO_SM0_PINCTRL_OUT_BASE_LSB) |
(out_count << PIO_SM0_PINCTRL_OUT_COUNT_LSB);
}
/*! \brief Set the current 'set' pins for a state machine
* \ingroup hardware_pio
*
* Can overlap with the 'in', 'out' and 'sideset' pins
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param set_base 0-31 First pin to set as
* \param set_count 0-5 Number of pins to set.
*/
static inline void pio_sm_set_set_pins(PIO pio, uint sm, uint set_base, uint set_count) {
check_pio_param(pio);
check_sm_param(sm);
valid_params_if(PIO, set_base < 32);
valid_params_if(PIO, set_count <= 5);
pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~(PIO_SM0_PINCTRL_SET_BASE_BITS | PIO_SM0_PINCTRL_SET_COUNT_BITS)) |
(set_base << PIO_SM0_PINCTRL_SET_BASE_LSB) |
(set_count << PIO_SM0_PINCTRL_SET_COUNT_LSB);
}
/*! \brief Set the current 'in' pins for a state machine
* \ingroup hardware_pio
*
* Can overlap with the 'out', 'set' and 'sideset' pins
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param in_base 0-31 First pin to use as input
*/
static inline void pio_sm_set_in_pins(PIO pio, uint sm, uint in_base) {
check_pio_param(pio);
check_sm_param(sm);
valid_params_if(PIO, in_base < 32);
pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~PIO_SM0_PINCTRL_IN_BASE_BITS) |
(in_base << PIO_SM0_PINCTRL_IN_BASE_LSB);
}
/*! \brief Set the current 'sideset' pins for a state machine
* \ingroup hardware_pio
*
* Can overlap with the 'in', 'out' and 'set' pins
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param sideset_base 0-31 base pin for 'side set'
*/
static inline void pio_sm_set_sideset_pins(PIO pio, uint sm, uint sideset_base) {
check_pio_param(pio);
check_sm_param(sm);
valid_params_if(PIO, sideset_base < 32);
pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~PIO_SM0_PINCTRL_SIDESET_BASE_BITS) |
(sideset_base << PIO_SM0_PINCTRL_SIDESET_BASE_LSB);
}
/*! \brief Write a word of data to a state machine's TX FIFO
* \ingroup hardware_pio
*
* This is a raw FIFO access that does not check for fullness. If the FIFO is
* full, the FIFO contents and state are not affected by the write attempt.
* Hardware sets the TXOVER sticky flag for this FIFO in FDEBUG, to indicate
* that the system attempted to write to a full FIFO.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param data the 32 bit data value
*
* \sa pio_sm_put_blocking()
*/
static inline void pio_sm_put(PIO pio, uint sm, uint32_t data) {
check_pio_param(pio);
check_sm_param(sm);
pio->txf[sm] = data;
}
/*! \brief Read a word of data from a state machine's RX FIFO
* \ingroup hardware_pio
*
* This is a raw FIFO access that does not check for emptiness. If the FIFO is
* empty, the hardware ignores the attempt to read from the FIFO (the FIFO
* remains in an empty state following the read) and the sticky RXUNDER flag
* for this FIFO is set in FDEBUG to indicate that the system tried to read
* from this FIFO when empty. The data returned by this function is undefined
* when the FIFO is empty.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
*
* \sa pio_sm_get_blocking()
*/
static inline uint32_t pio_sm_get(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
return pio->rxf[sm];
}
/*! \brief Determine if a state machine's RX FIFO is full
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \return true if the RX FIFO is full
*/
static inline bool pio_sm_is_rx_fifo_full(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
return (pio->fstat & (1u << (PIO_FSTAT_RXFULL_LSB + sm))) != 0;
}
/*! \brief Determine if a state machine's RX FIFO is empty
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \return true if the RX FIFO is empty
*/
static inline bool pio_sm_is_rx_fifo_empty(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
return (pio->fstat & (1u << (PIO_FSTAT_RXEMPTY_LSB + sm))) != 0;
}
/*! \brief Return the number of elements currently in a state machine's RX FIFO
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \return the number of elements in the RX FIFO
*/
static inline uint pio_sm_get_rx_fifo_level(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
uint bitoffs = PIO_FLEVEL_RX0_LSB + sm * (PIO_FLEVEL_RX1_LSB - PIO_FLEVEL_RX0_LSB);
const uint32_t mask = PIO_FLEVEL_RX0_BITS >> PIO_FLEVEL_RX0_LSB;
return (pio->flevel >> bitoffs) & mask;
}
/*! \brief Determine if a state machine's TX FIFO is full
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \return true if the TX FIFO is full
*/
static inline bool pio_sm_is_tx_fifo_full(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
return (pio->fstat & (1u << (PIO_FSTAT_TXFULL_LSB + sm))) != 0;
}
/*! \brief Determine if a state machine's TX FIFO is empty
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \return true if the TX FIFO is empty
*/
static inline bool pio_sm_is_tx_fifo_empty(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
return (pio->fstat & (1u << (PIO_FSTAT_TXEMPTY_LSB + sm))) != 0;
}
/*! \brief Return the number of elements currently in a state machine's TX FIFO
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \return the number of elements in the TX FIFO
*/
static inline uint pio_sm_get_tx_fifo_level(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
unsigned int bitoffs = PIO_FLEVEL_TX0_LSB + sm * (PIO_FLEVEL_TX1_LSB - PIO_FLEVEL_TX0_LSB);
const uint32_t mask = PIO_FLEVEL_TX0_BITS >> PIO_FLEVEL_TX0_LSB;
return (pio->flevel >> bitoffs) & mask;
}
/*! \brief Write a word of data to a state machine's TX FIFO, blocking if the FIFO is full
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param data the 32 bit data value
*/
static inline void pio_sm_put_blocking(PIO pio, uint sm, uint32_t data) {
check_pio_param(pio);
check_sm_param(sm);
while (pio_sm_is_tx_fifo_full(pio, sm)) tight_loop_contents();
pio_sm_put(pio, sm, data);
}
/*! \brief Read a word of data from a state machine's RX FIFO, blocking if the FIFO is empty
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
*/
static inline uint32_t pio_sm_get_blocking(PIO pio, uint sm) {
check_pio_param(pio);
check_sm_param(sm);
while (pio_sm_is_rx_fifo_empty(pio, sm)) tight_loop_contents();
return pio_sm_get(pio, sm);
}
/*! \brief Empty out a state machine's TX FIFO
* \ingroup hardware_pio
*
* This method executes `pull` instructions on the state machine until the TX
* FIFO is empty. This disturbs the contents of the OSR, so see also
* pio_sm_clear_fifos() which clears both FIFOs but leaves the state machine's
* internal state undisturbed.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
*
* \sa pio_sm_clear_fifos()
*/
void pio_sm_drain_tx_fifo(PIO pio, uint sm);
/*! \brief set the current clock divider for a state machine using a 16:8 fraction
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param div_int the integer part of the clock divider
* \param div_frac the fractional part of the clock divider in 1/256s
*/
static inline void pio_sm_set_clkdiv_int_frac(PIO pio, uint sm, uint16_t div_int, uint8_t div_frac) {
check_pio_param(pio);
check_sm_param(sm);
invalid_params_if(PIO, div_int == 0 && div_frac != 0);
pio->sm[sm].clkdiv =
(((uint)div_frac) << PIO_SM0_CLKDIV_FRAC_LSB) |
(((uint)div_int) << PIO_SM0_CLKDIV_INT_LSB);
}
/*! \brief set the current clock divider for a state machine
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \param div the floating point clock divider
*/
static inline void pio_sm_set_clkdiv(PIO pio, uint sm, float div) {
check_pio_param(pio);
check_sm_param(sm);
uint16_t div_int;
uint8_t div_frac;
pio_calculate_clkdiv_from_float(div, &div_int, &div_frac);
pio_sm_set_clkdiv_int_frac(pio, sm, div_int, div_frac);
}
/*! \brief Clear a state machine's TX and RX FIFOs
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
*/
static inline void pio_sm_clear_fifos(PIO pio, uint sm) {
// changing the FIFO join state clears the fifo
check_pio_param(pio);
check_sm_param(sm);
hw_xor_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS);
hw_xor_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS);
}
/*! \brief Use a state machine to set a value on all pins for the PIO instance
* \ingroup hardware_pio
*
* This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set values on all 32 pins,
* before restoring the state machine's pin configuration to what it was.
*
* This method is provided as a convenience to set initial pin states, and should not be used against a state machine that is enabled.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3) to use
* \param pin_values the pin values to set
*/
void pio_sm_set_pins(PIO pio, uint sm, uint32_t pin_values);
/*! \brief Use a state machine to set a value on multiple pins for the PIO instance
* \ingroup hardware_pio
*
* This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set values on up to 32 pins,
* before restoring the state machine's pin configuration to what it was.
*
* This method is provided as a convenience to set initial pin states, and should not be used against a state machine that is enabled.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3) to use
* \param pin_values the pin values to set (if the corresponding bit in pin_mask is set)
* \param pin_mask a bit for each pin to indicate whether the corresponding pin_value for that pin should be applied.
*/
void pio_sm_set_pins_with_mask(PIO pio, uint sm, uint32_t pin_values, uint32_t pin_mask);
/*! \brief Use a state machine to set the pin directions for multiple pins for the PIO instance
* \ingroup hardware_pio
*
* This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set pin directions on up to 32 pins,
* before restoring the state machine's pin configuration to what it was.
*
* This method is provided as a convenience to set initial pin directions, and should not be used against a state machine that is enabled.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3) to use
* \param pin_dirs the pin directions to set - 1 = out, 0 = in (if the corresponding bit in pin_mask is set)
* \param pin_mask a bit for each pin to indicate whether the corresponding pin_value for that pin should be applied.
*/
void pio_sm_set_pindirs_with_mask(PIO pio, uint sm, uint32_t pin_dirs, uint32_t pin_mask);
/*! \brief Use a state machine to set the same pin direction for multiple consecutive pins for the PIO instance
* \ingroup hardware_pio
*
* This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set the pin direction on consecutive pins,
* before restoring the state machine's pin configuration to what it was.
*
* This method is provided as a convenience to set initial pin directions, and should not be used against a state machine that is enabled.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3) to use
* \param pin_base the first pin to set a direction for
* \param pin_count the count of consecutive pins to set the direction for
* \param is_out the direction to set; true = out, false = in
*/
void pio_sm_set_consecutive_pindirs(PIO pio, uint sm, uint pin_base, uint pin_count, bool is_out);
/*! \brief Mark a state machine as used
* \ingroup hardware_pio
*
* Method for cooperative claiming of hardware. Will cause a panic if the state machine
* is already claimed. Use of this method by libraries detects accidental
* configurations that would fail in unpredictable ways.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
*/
void pio_sm_claim(PIO pio, uint sm);
/*! \brief Mark multiple state machines as used
* \ingroup hardware_pio
*
* Method for cooperative claiming of hardware. Will cause a panic if any of the state machines
* are already claimed. Use of this method by libraries detects accidental
* configurations that would fail in unpredictable ways.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm_mask Mask of state machine indexes
*/
void pio_claim_sm_mask(PIO pio, uint sm_mask);
/*! \brief Mark a state machine as no longer used
* \ingroup hardware_pio
*
* Method for cooperative claiming of hardware.
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
*/
void pio_sm_unclaim(PIO pio, uint sm);
/*! \brief Claim a free state machine on a PIO instance
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param required if true the function will panic if none are available
* \return the state machine index or -1 if required was false, and none were free
*/
int pio_claim_unused_sm(PIO pio, bool required);
/*! \brief Determine if a PIO state machine is claimed
* \ingroup hardware_pio
*
* \param pio The PIO instance; either \ref pio0 or \ref pio1
* \param sm State machine index (0..3)
* \return true if claimed, false otherwise
* \see pio_sm_claim
* \see pio_claim_sm_mask
*/
bool pio_sm_is_claimed(PIO pio, uint sm);
#ifdef __cplusplus
}
#endif
#endif // _PIO_H_
================================================
FILE: pico-sdk/src/rp2_common/hardware_pio/include/hardware/pio_instructions.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_PIO_INSTRUCTIONS_H
#define _HARDWARE_PIO_INSTRUCTIONS_H
#include "pico.h"
/** \brief PIO instruction encoding
* \defgroup pio_instructions pio_instructions
* \ingroup hardware_pio
*
* Functions for generating PIO instruction encodings programmatically. In debug builds
*`PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS` can be set to 1 to enable validation of encoding function
* parameters.
*
* For fuller descriptions of the instructions in question see the "RP2040 Datasheet"
*/
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS, Enable/disable assertions in the PIO instructions, type=bool, default=0, group=pio_instructions
#ifndef PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS
#define PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
enum pio_instr_bits {
pio_instr_bits_jmp = 0x0000,
pio_instr_bits_wait = 0x2000,
pio_instr_bits_in = 0x4000,
pio_instr_bits_out = 0x6000,
pio_instr_bits_push = 0x8000,
pio_instr_bits_pull = 0x8080,
pio_instr_bits_mov = 0xa000,
pio_instr_bits_irq = 0xc000,
pio_instr_bits_set = 0xe000,
};
#ifndef NDEBUG
#define _PIO_INVALID_IN_SRC 0x08u
#define _PIO_INVALID_OUT_DEST 0x10u
#define _PIO_INVALID_SET_DEST 0x20u
#define _PIO_INVALID_MOV_SRC 0x40u
#define _PIO_INVALID_MOV_DEST 0x80u
#else
#define _PIO_INVALID_IN_SRC 0u
#define _PIO_INVALID_OUT_DEST 0u
#define _PIO_INVALID_SET_DEST 0u
#define _PIO_INVALID_MOV_SRC 0u
#define _PIO_INVALID_MOV_DEST 0u
#endif
/*! \brief Enumeration of values to pass for source/destination args for instruction encoding functions
* \ingroup pio_instructions
*
* \note Not all values are suitable for all functions. Validity is only checked in debug mode when
* `PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS` is 1
*/
enum pio_src_dest {
pio_pins = 0u,
pio_x = 1u,
pio_y = 2u,
pio_null = 3u | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_DEST,
pio_pindirs = 4u | _PIO_INVALID_IN_SRC | _PIO_INVALID_MOV_SRC | _PIO_INVALID_MOV_DEST,
pio_exec_mov = 4u | _PIO_INVALID_IN_SRC | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC,
pio_status = 5u | _PIO_INVALID_IN_SRC | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_DEST,
pio_pc = 5u | _PIO_INVALID_IN_SRC | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC,
pio_isr = 6u | _PIO_INVALID_SET_DEST,
pio_osr = 7u | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST,
pio_exec_out = 7u | _PIO_INVALID_IN_SRC | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC | _PIO_INVALID_MOV_DEST,
};
static inline uint _pio_major_instr_bits(uint instr) {
return instr & 0xe000u;
}
static inline uint _pio_encode_instr_and_args(enum pio_instr_bits instr_bits, uint arg1, uint arg2) {
valid_params_if(PIO_INSTRUCTIONS, arg1 <= 0x7);
#if PARAM_ASSERTIONS_ENABLED(PIO_INSTRUCTIONS)
uint32_t major = _pio_major_instr_bits(instr_bits);
if (major == pio_instr_bits_in || major == pio_instr_bits_out) {
assert(arg2 && arg2 <= 32);
} else {
assert(arg2 <= 31);
}
#endif
return instr_bits | (arg1 << 5u) | (arg2 & 0x1fu);
}
static inline uint _pio_encode_instr_and_src_dest(enum pio_instr_bits instr_bits, enum pio_src_dest dest, uint value) {
return _pio_encode_instr_and_args(instr_bits, dest & 7u, value);
}
/*! \brief Encode just the delay slot bits of an instruction
* \ingroup pio_instructions
*
* \note This function does not return a valid instruction encoding; instead it returns an encoding of the delay
* slot suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when
* combining the results of this function with the results of \ref pio_encode_sideset and \ref pio_encode_sideset_opt
* as they share the same bits within the instruction encoding.
*
* \param cycles the number of cycles 0-31 (or less if side set is being used)
* \return the delay slot bits to be ORed with an instruction encoding
*/
static inline uint pio_encode_delay(uint cycles) {
// note that the maximum cycles will be smaller if sideset_bit_count > 0
valid_params_if(PIO_INSTRUCTIONS, cycles <= 0x1f);
return cycles << 8u;
}
/*! \brief Encode just the side set bits of an instruction (in non optional side set mode)
* \ingroup pio_instructions
*
* \note This function does not return a valid instruction encoding; instead it returns an encoding of the side set bits
* suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when
* combining the results of this function with the results of \ref pio_encode_delay as they share the same bits
* within the instruction encoding.
*
* \param sideset_bit_count number of side set bits as would be specified via `.sideset` in pioasm
* \param value the value to sideset on the pins
* \return the side set bits to be ORed with an instruction encoding
*/
static inline uint pio_encode_sideset(uint sideset_bit_count, uint value) {
valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 1 && sideset_bit_count <= 5);
valid_params_if(PIO_INSTRUCTIONS, value <= ((1u << sideset_bit_count) - 1));
return value << (13u - sideset_bit_count);
}
/*! \brief Encode just the side set bits of an instruction (in optional -`opt` side set mode)
* \ingroup pio_instructions
*
* \note This function does not return a valid instruction encoding; instead it returns an encoding of the side set bits
* suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when
* combining the results of this function with the results of \ref pio_encode_delay as they share the same bits
* within the instruction encoding.
*
* \param sideset_bit_count number of side set bits as would be specified via `.sideset opt` in pioasm
* \param value the value to sideset on the pins
* \return the side set bits to be ORed with an instruction encoding
*/
static inline uint pio_encode_sideset_opt(uint sideset_bit_count, uint value) {
valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 1 && sideset_bit_count <= 4);
valid_params_if(PIO_INSTRUCTIONS, value <= ((1u << sideset_bit_count) - 1));
return 0x1000u | value << (12u - sideset_bit_count);
}
/*! \brief Encode an unconditional JMP instruction
* \ingroup pio_instructions
*
* This is the equivalent of `JMP `
*
* \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_jmp(uint addr) {
return _pio_encode_instr_and_args(pio_instr_bits_jmp, 0, addr);
}
/*! \brief Encode a conditional JMP if scratch X zero instruction
* \ingroup pio_instructions
*
* This is the equivalent of `JMP !X `
*
* \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_jmp_not_x(uint addr) {
return _pio_encode_instr_and_args(pio_instr_bits_jmp, 1, addr);
}
/*! \brief Encode a conditional JMP if scratch X non-zero (and post-decrement X) instruction
* \ingroup pio_instructions
*
* This is the equivalent of `JMP X-- `
*
* \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_jmp_x_dec(uint addr) {
return _pio_encode_instr_and_args(pio_instr_bits_jmp, 2, addr);
}
/*! \brief Encode a conditional JMP if scratch Y zero instruction
* \ingroup pio_instructions
*
* This is the equivalent of `JMP !Y `
*
* \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_jmp_not_y(uint addr) {
return _pio_encode_instr_and_args(pio_instr_bits_jmp, 3, addr);
}
/*! \brief Encode a conditional JMP if scratch Y non-zero (and post-decrement Y) instruction
* \ingroup pio_instructions
*
* This is the equivalent of `JMP Y-- `
*
* \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_jmp_y_dec(uint addr) {
return _pio_encode_instr_and_args(pio_instr_bits_jmp, 4, addr);
}
/*! \brief Encode a conditional JMP if scratch X not equal scratch Y instruction
* \ingroup pio_instructions
*
* This is the equivalent of `JMP X!=Y `
*
* \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_jmp_x_ne_y(uint addr) {
return _pio_encode_instr_and_args(pio_instr_bits_jmp, 5, addr);
}
/*! \brief Encode a conditional JMP if input pin high instruction
* \ingroup pio_instructions
*
* This is the equivalent of `JMP PIN `
*
* \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_jmp_pin(uint addr) {
return _pio_encode_instr_and_args(pio_instr_bits_jmp, 6, addr);
}
/*! \brief Encode a conditional JMP if output shift register not empty instruction
* \ingroup pio_instructions
*
* This is the equivalent of `JMP !OSRE `
*
* \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_jmp_not_osre(uint addr) {
return _pio_encode_instr_and_args(pio_instr_bits_jmp, 7, addr);
}
static inline uint _pio_encode_irq(bool relative, uint irq) {
valid_params_if(PIO_INSTRUCTIONS, irq <= 7);
return (relative ? 0x10u : 0x0u) | irq;
}
/*! \brief Encode a WAIT for GPIO pin instruction
* \ingroup pio_instructions
*
* This is the equivalent of `WAIT GPIO `
*
* \param polarity true for `WAIT 1`, false for `WAIT 0`
* \param gpio The real GPIO number 0-31
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_wait_gpio(bool polarity, uint gpio) {
return _pio_encode_instr_and_args(pio_instr_bits_wait, 0u | (polarity ? 4u : 0u), gpio);
}
/*! \brief Encode a WAIT for pin instruction
* \ingroup pio_instructions
*
* This is the equivalent of `WAIT PIN `
*
* \param polarity true for `WAIT 1`, false for `WAIT 0`
* \param pin The pin number 0-31 relative to the executing SM's input pin mapping
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_wait_pin(bool polarity, uint pin) {
return _pio_encode_instr_and_args(pio_instr_bits_wait, 1u | (polarity ? 4u : 0u), pin);
}
/*! \brief Encode a WAIT for IRQ instruction
* \ingroup pio_instructions
*
* This is the equivalent of `WAIT IRQ `
*
* \param polarity true for `WAIT 1`, false for `WAIT 0`
* \param relative true for a `WAIT IRQ REL`, false for regular `WAIT IRQ `
* \param irq the irq number 0-7
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_wait_irq(bool polarity, bool relative, uint irq) {
valid_params_if(PIO_INSTRUCTIONS, irq <= 7);
return _pio_encode_instr_and_args(pio_instr_bits_wait, 2u | (polarity ? 4u : 0u), _pio_encode_irq(relative, irq));
}
/*! \brief Encode an IN instruction
* \ingroup pio_instructions
*
* This is the equivalent of `IN , `
*
* \param src The source to take data from
* \param count The number of bits 1-32
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_in(enum pio_src_dest src, uint count) {
valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_IN_SRC));
return _pio_encode_instr_and_src_dest(pio_instr_bits_in, src, count);
}
/*! \brief Encode an OUT instruction
* \ingroup pio_instructions
*
* This is the equivalent of `OUT , `
*
* \param dest The destination to write data to
* \param count The number of bits 1-32
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_out(enum pio_src_dest dest, uint count) {
valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_OUT_DEST));
return _pio_encode_instr_and_src_dest(pio_instr_bits_out, dest, count);
}
/*! \brief Encode a PUSH instruction
* \ingroup pio_instructions
*
* This is the equivalent of `PUSH , `
*
* \param if_full true for `PUSH IF_FULL ...`, false for `PUSH ...`
* \param block true for `PUSH ... BLOCK`, false for `PUSH ...`
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_push(bool if_full, bool block) {
return _pio_encode_instr_and_args(pio_instr_bits_push, (if_full ? 2u : 0u) | (block ? 1u : 0u), 0);
}
/*! \brief Encode a PULL instruction
* \ingroup pio_instructions
*
* This is the equivalent of `PULL , `
*
* \param if_empty true for `PULL IF_EMPTY ...`, false for `PULL ...`
* \param block true for `PULL ... BLOCK`, false for `PULL ...`
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_pull(bool if_empty, bool block) {
return _pio_encode_instr_and_args(pio_instr_bits_pull, (if_empty ? 2u : 0u) | (block ? 1u : 0u), 0);
}
/*! \brief Encode a MOV instruction
* \ingroup pio_instructions
*
* This is the equivalent of `MOV , `
*
* \param dest The destination to write data to
* \param src The source to take data from
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_mov(enum pio_src_dest dest, enum pio_src_dest src) {
valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, src & 7u);
}
/*! \brief Encode a MOV instruction with bit invert
* \ingroup pio_instructions
*
* This is the equivalent of `MOV , ~`
*
* \param dest The destination to write inverted data to
* \param src The source to take data from
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_mov_not(enum pio_src_dest dest, enum pio_src_dest src) {
valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, (1u << 3u) | (src & 7u));
}
/*! \brief Encode a MOV instruction with bit reverse
* \ingroup pio_instructions
*
* This is the equivalent of `MOV , ::`
*
* \param dest The destination to write bit reversed data to
* \param src The source to take data from
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_mov_reverse(enum pio_src_dest dest, enum pio_src_dest src) {
valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, (2u << 3u) | (src & 7u));
}
/*! \brief Encode a IRQ SET instruction
* \ingroup pio_instructions
*
* This is the equivalent of `IRQ SET `
*
* \param relative true for a `IRQ SET REL`, false for regular `IRQ SET `
* \param irq the irq number 0-7
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_irq_set(bool relative, uint irq) {
return _pio_encode_instr_and_args(pio_instr_bits_irq, 0, _pio_encode_irq(relative, irq));
}
/*! \brief Encode a IRQ WAIT instruction
* \ingroup pio_instructions
*
* This is the equivalent of `IRQ WAIT `
*
* \param relative true for a `IRQ WAIT REL`, false for regular `IRQ WAIT `
* \param irq the irq number 0-7
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_irq_wait(bool relative, uint irq) {
return _pio_encode_instr_and_args(pio_instr_bits_irq, 1, _pio_encode_irq(relative, irq));
}
/*! \brief Encode a IRQ CLEAR instruction
* \ingroup pio_instructions
*
* This is the equivalent of `IRQ CLEAR `
*
* \param relative true for a `IRQ CLEAR REL`, false for regular `IRQ CLEAR `
* \param irq the irq number 0-7
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_irq_clear(bool relative, uint irq) {
return _pio_encode_instr_and_args(pio_instr_bits_irq, 2, _pio_encode_irq(relative, irq));
}
/*! \brief Encode a SET instruction
* \ingroup pio_instructions
*
* This is the equivalent of `SET , `
*
* \param dest The destination to apply the value to
* \param value The value 0-31
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_set(enum pio_src_dest dest, uint value) {
valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_SET_DEST));
return _pio_encode_instr_and_src_dest(pio_instr_bits_set, dest, value);
}
/*! \brief Encode a NOP instruction
* \ingroup pio_instructions
*
* This is the equivalent of `NOP` which is itself encoded as `MOV y, y`
*
* \return The instruction encoding with 0 delay and no side set value
* \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
*/
static inline uint pio_encode_nop(void) {
return pio_encode_mov(pio_y, pio_y);
}
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_pio/pio.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "hardware/claim.h"
#include "hardware/pio.h"
#include "hardware/pio_instructions.h"
// sanity check
check_hw_layout(pio_hw_t, sm[0].clkdiv, PIO_SM0_CLKDIV_OFFSET);
check_hw_layout(pio_hw_t, sm[1].clkdiv, PIO_SM1_CLKDIV_OFFSET);
check_hw_layout(pio_hw_t, instr_mem[0], PIO_INSTR_MEM0_OFFSET);
check_hw_layout(pio_hw_t, inte0, PIO_IRQ0_INTE_OFFSET);
check_hw_layout(pio_hw_t, txf[1], PIO_TXF1_OFFSET);
check_hw_layout(pio_hw_t, rxf[3], PIO_RXF3_OFFSET);
check_hw_layout(pio_hw_t, ints1, PIO_IRQ1_INTS_OFFSET);
static_assert(NUM_PIO_STATE_MACHINES * NUM_PIOS <= 8, "");
static uint8_t claimed;
void pio_sm_claim(PIO pio, uint sm) {
check_sm_param(sm);
uint which = pio_get_index(pio);
if (which) {
hw_claim_or_assert(&claimed, NUM_PIO_STATE_MACHINES + sm, "PIO 1 SM (%d - 4) already claimed");
} else {
hw_claim_or_assert(&claimed, sm, "PIO 0 SM %d already claimed");
}
}
void pio_claim_sm_mask(PIO pio, uint sm_mask) {
for(uint i = 0; sm_mask; i++, sm_mask >>= 1u) {
if (sm_mask & 1u) pio_sm_claim(pio, i);
}
}
void pio_sm_unclaim(PIO pio, uint sm) {
check_sm_param(sm);
uint which = pio_get_index(pio);
hw_claim_clear(&claimed, which * NUM_PIO_STATE_MACHINES + sm);
}
int pio_claim_unused_sm(PIO pio, bool required) {
// PIO index is 0 or 1.
uint which = pio_get_index(pio);
uint base = which * NUM_PIO_STATE_MACHINES;
int index = hw_claim_unused_from_range((uint8_t*)&claimed, required, base,
base + NUM_PIO_STATE_MACHINES - 1, "No PIO state machines are available");
return index >= (int)base ? index - (int)base : -1;
}
bool pio_sm_is_claimed(PIO pio, uint sm) {
check_sm_param(sm);
uint which = pio_get_index(pio);
return hw_is_claimed(&claimed, which * NUM_PIO_STATE_MACHINES + sm);
}
static_assert(PIO_INSTRUCTION_COUNT <= 32, "");
static uint32_t _used_instruction_space[2];
static int _pio_find_offset_for_program(PIO pio, const pio_program_t *program) {
assert(program->length <= PIO_INSTRUCTION_COUNT);
uint32_t used_mask = _used_instruction_space[pio_get_index(pio)];
uint32_t program_mask = (1u << program->length) - 1;
if (program->origin >= 0) {
if (program->origin > 32 - program->length) return -1;
return used_mask & (program_mask << program->origin) ? -1 : program->origin;
} else {
// work down from the top always
for (int i = 32 - program->length; i >= 0; i--) {
if (!(used_mask & (program_mask << (uint) i))) {
return i;
}
}
return -1;
}
}
bool pio_can_add_program(PIO pio, const pio_program_t *program) {
uint32_t save = hw_claim_lock();
bool rc = -1 != _pio_find_offset_for_program(pio, program);
hw_claim_unlock(save);
return rc;
}
static bool _pio_can_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset) {
valid_params_if(PIO, offset < PIO_INSTRUCTION_COUNT);
valid_params_if(PIO, offset + program->length <= PIO_INSTRUCTION_COUNT);
if (program->origin >= 0 && (uint)program->origin != offset) return false;
uint32_t used_mask = _used_instruction_space[pio_get_index(pio)];
uint32_t program_mask = (1u << program->length) - 1;
return !(used_mask & (program_mask << offset));
}
bool pio_can_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset) {
uint32_t save = hw_claim_lock();
bool rc = _pio_can_add_program_at_offset(pio, program, offset);
hw_claim_unlock(save);
return rc;
}
static void _pio_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset) {
if (!_pio_can_add_program_at_offset(pio, program, offset)) {
panic("No program space");
}
for (uint i = 0; i < program->length; ++i) {
uint16_t instr = program->instructions[i];
pio->instr_mem[offset + i] = pio_instr_bits_jmp != _pio_major_instr_bits(instr) ? instr : instr + offset;
}
uint32_t program_mask = (1u << program->length) - 1;
_used_instruction_space[pio_get_index(pio)] |= program_mask << offset;
}
// these assert if unable
uint pio_add_program(PIO pio, const pio_program_t *program) {
uint32_t save = hw_claim_lock();
int offset = _pio_find_offset_for_program(pio, program);
if (offset < 0) {
panic("No program space");
}
_pio_add_program_at_offset(pio, program, (uint)offset);
hw_claim_unlock(save);
return (uint)offset;
}
void pio_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset) {
uint32_t save = hw_claim_lock();
_pio_add_program_at_offset(pio, program, offset);
hw_claim_unlock(save);
}
void pio_remove_program(PIO pio, const pio_program_t *program, uint loaded_offset) {
uint32_t program_mask = (1u << program->length) - 1;
program_mask <<= loaded_offset;
uint32_t save = hw_claim_lock();
assert(program_mask == (_used_instruction_space[pio_get_index(pio)] & program_mask));
_used_instruction_space[pio_get_index(pio)] &= ~program_mask;
hw_claim_unlock(save);
}
void pio_clear_instruction_memory(PIO pio) {
uint32_t save = hw_claim_lock();
_used_instruction_space[pio_get_index(pio)] = 0;
for(uint i=0;iinstr_mem[i] = pio_encode_jmp(i);
}
hw_claim_unlock(save);
}
// Set the value of all PIO pins. This is done by forcibly executing
// instructions on a "victim" state machine, sm. Ideally you should choose one
// which is not currently running a program. This is intended for one-time
// setup of initial pin states.
void pio_sm_set_pins(PIO pio, uint sm, uint32_t pins) {
check_pio_param(pio);
check_sm_param(sm);
uint32_t pinctrl_saved = pio->sm[sm].pinctrl;
uint32_t execctrl_saved = pio->sm[sm].execctrl;
hw_clear_bits(&pio->sm[sm].execctrl, 1u << PIO_SM0_EXECCTRL_OUT_STICKY_LSB);
uint remaining = 32;
uint base = 0;
while (remaining) {
uint decrement = remaining > 5 ? 5 : remaining;
pio->sm[sm].pinctrl =
(decrement << PIO_SM0_PINCTRL_SET_COUNT_LSB) |
(base << PIO_SM0_PINCTRL_SET_BASE_LSB);
pio_sm_exec(pio, sm, pio_encode_set(pio_pins, pins & 0x1fu));
remaining -= decrement;
base += decrement;
pins >>= 5;
}
pio->sm[sm].pinctrl = pinctrl_saved;
pio->sm[sm].execctrl = execctrl_saved;
}
void pio_sm_set_pins_with_mask(PIO pio, uint sm, uint32_t pinvals, uint32_t pin_mask) {
check_pio_param(pio);
check_sm_param(sm);
uint32_t pinctrl_saved = pio->sm[sm].pinctrl;
uint32_t execctrl_saved = pio->sm[sm].execctrl;
hw_clear_bits(&pio->sm[sm].execctrl, 1u << PIO_SM0_EXECCTRL_OUT_STICKY_LSB);
while (pin_mask) {
uint base = (uint)__builtin_ctz(pin_mask);
pio->sm[sm].pinctrl =
(1u << PIO_SM0_PINCTRL_SET_COUNT_LSB) |
(base << PIO_SM0_PINCTRL_SET_BASE_LSB);
pio_sm_exec(pio, sm, pio_encode_set(pio_pins, (pinvals >> base) & 0x1u));
pin_mask &= pin_mask - 1;
}
pio->sm[sm].pinctrl = pinctrl_saved;
pio->sm[sm].execctrl = execctrl_saved;
}
void pio_sm_set_pindirs_with_mask(PIO pio, uint sm, uint32_t pindirs, uint32_t pin_mask) {
check_pio_param(pio);
check_sm_param(sm);
uint32_t pinctrl_saved = pio->sm[sm].pinctrl;
uint32_t execctrl_saved = pio->sm[sm].execctrl;
hw_clear_bits(&pio->sm[sm].execctrl, 1u << PIO_SM0_EXECCTRL_OUT_STICKY_LSB);
while (pin_mask) {
uint base = (uint)__builtin_ctz(pin_mask);
pio->sm[sm].pinctrl =
(1u << PIO_SM0_PINCTRL_SET_COUNT_LSB) |
(base << PIO_SM0_PINCTRL_SET_BASE_LSB);
pio_sm_exec(pio, sm, pio_encode_set(pio_pindirs, (pindirs >> base) & 0x1u));
pin_mask &= pin_mask - 1;
}
pio->sm[sm].pinctrl = pinctrl_saved;
pio->sm[sm].execctrl = execctrl_saved;
}
void pio_sm_set_consecutive_pindirs(PIO pio, uint sm, uint pin, uint count, bool is_out) {
check_pio_param(pio);
check_sm_param(sm);
valid_params_if(PIO, pin < 32u);
uint32_t pinctrl_saved = pio->sm[sm].pinctrl;
uint32_t execctrl_saved = pio->sm[sm].execctrl;
hw_clear_bits(&pio->sm[sm].execctrl, 1u << PIO_SM0_EXECCTRL_OUT_STICKY_LSB);
uint pindir_val = is_out ? 0x1f : 0;
while (count > 5) {
pio->sm[sm].pinctrl = (5u << PIO_SM0_PINCTRL_SET_COUNT_LSB) | (pin << PIO_SM0_PINCTRL_SET_BASE_LSB);
pio_sm_exec(pio, sm, pio_encode_set(pio_pindirs, pindir_val));
count -= 5;
pin = (pin + 5) & 0x1f;
}
pio->sm[sm].pinctrl = (count << PIO_SM0_PINCTRL_SET_COUNT_LSB) | (pin << PIO_SM0_PINCTRL_SET_BASE_LSB);
pio_sm_exec(pio, sm, pio_encode_set(pio_pindirs, pindir_val));
pio->sm[sm].pinctrl = pinctrl_saved;
pio->sm[sm].execctrl = execctrl_saved;
}
void pio_sm_init(PIO pio, uint sm, uint initial_pc, const pio_sm_config *config) {
valid_params_if(PIO, initial_pc < PIO_INSTRUCTION_COUNT);
// Halt the machine, set some sensible defaults
pio_sm_set_enabled(pio, sm, false);
if (config) {
pio_sm_set_config(pio, sm, config);
} else {
pio_sm_config c = pio_get_default_sm_config();
pio_sm_set_config(pio, sm, &c);
}
pio_sm_clear_fifos(pio, sm);
// Clear FIFO debug flags
const uint32_t fdebug_sm_mask =
(1u << PIO_FDEBUG_TXOVER_LSB) |
(1u << PIO_FDEBUG_RXUNDER_LSB) |
(1u << PIO_FDEBUG_TXSTALL_LSB) |
(1u << PIO_FDEBUG_RXSTALL_LSB);
pio->fdebug = fdebug_sm_mask << sm;
// Finally, clear some internal SM state
pio_sm_restart(pio, sm);
pio_sm_clkdiv_restart(pio, sm);
pio_sm_exec(pio, sm, pio_encode_jmp(initial_pc));
}
void pio_sm_drain_tx_fifo(PIO pio, uint sm) {
uint instr = (pio->sm[sm].shiftctrl & PIO_SM0_SHIFTCTRL_AUTOPULL_BITS) ? pio_encode_out(pio_null, 32) :
pio_encode_pull(false, false);
while (!pio_sm_is_tx_fifo_empty(pio, sm)) {
pio_sm_exec(pio, sm, instr);
}
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_pll/CMakeLists.txt
================================================
pico_simple_hardware_target(pll)
pico_mirrored_target_link_libraries(hardware_pll INTERFACE hardware_clocks)
================================================
FILE: pico-sdk/src/rp2_common/hardware_pll/include/hardware/pll.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_PLL_H
#define _HARDWARE_PLL_H
#include "pico.h"
#include "hardware/structs/pll.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file hardware/pll.h
* \defgroup hardware_pll hardware_pll
*
* Phase Locked Loop control APIs
*
* There are two PLLs in RP2040. They are:
* - pll_sys - Used to generate up to a 133MHz system clock
* - pll_usb - Used to generate a 48MHz USB reference clock
*
* For details on how the PLLs are calculated, please refer to the RP2040 datasheet.
*/
typedef pll_hw_t *PLL;
#define pll_sys pll_sys_hw
#define pll_usb pll_usb_hw
#ifndef PICO_PLL_VCO_MIN_FREQ_KHZ
#ifndef PICO_PLL_VCO_MIN_FREQ_MHZ
#define PICO_PLL_VCO_MIN_FREQ_KHZ (750 * KHZ)
#else
#define PICO_PLL_VCO_MIN_FREQ_KHZ (PICO_PLL_VCO_MIN_FREQ_MHZ * KHZ)
#endif
#endif
#ifndef PICO_PLL_VCO_MAX_FREQ_KHZ
#ifndef PICO_PLL_VCO_MAX_FREQ_MHZ
#define PICO_PLL_VCO_MAX_FREQ_KHZ (1600 * KHZ)
#else
#define PICO_PLL_VCO_MAX_FREQ_KHZ (PICO_PLL_VCO_MAX_FREQ_MHZ * KHZ)
#endif
#endif
/*! \brief Initialise specified PLL.
* \ingroup hardware_pll
* \param pll pll_sys or pll_usb
* \param ref_div Input clock divider.
* \param vco_freq Requested output from the VCO (voltage controlled oscillator)
* \param post_div1 Post Divider 1 - range 1-7. Must be >= post_div2
* \param post_div2 Post Divider 2 - range 1-7
*/
void pll_init(PLL pll, uint ref_div, uint vco_freq, uint post_div1, uint post_div2);
/*! \brief Release/uninitialise specified PLL.
* \ingroup hardware_pll
*
* This will turn off the power to the specified PLL. Note this function does not currently check if
* the PLL is in use before powering it off so should be used with care.
*
* \param pll pll_sys or pll_usb
*/
void pll_deinit(PLL pll);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_pll/pll.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// For frequency and PLL definitions etc.
#include "hardware/clocks.h"
#include "hardware/pll.h"
#include "hardware/resets.h"
/// \tag::pll_init_calculations[]
void pll_init(PLL pll, uint refdiv, uint vco_freq, uint post_div1, uint post_div2) {
uint32_t ref_freq = XOSC_KHZ * KHZ / refdiv;
// Check vco freq is in an acceptable range
assert(vco_freq >= (PICO_PLL_VCO_MIN_FREQ_KHZ * KHZ) && vco_freq <= (PICO_PLL_VCO_MAX_FREQ_KHZ * KHZ));
// What are we multiplying the reference clock by to get the vco freq
// (The regs are called div, because you divide the vco output and compare it to the refclk)
uint32_t fbdiv = vco_freq / ref_freq;
/// \end::pll_init_calculations[]
// fbdiv
assert(fbdiv >= 16 && fbdiv <= 320);
// Check divider ranges
assert((post_div1 >= 1 && post_div1 <= 7) && (post_div2 >= 1 && post_div2 <= 7));
// post_div1 should be >= post_div2
// from appnote page 11
// postdiv1 is designed to operate with a higher input frequency than postdiv2
// Check that reference frequency is no greater than vco / 16
assert(ref_freq <= (vco_freq / 16));
// div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10
uint32_t pdiv = (post_div1 << PLL_PRIM_POSTDIV1_LSB) |
(post_div2 << PLL_PRIM_POSTDIV2_LSB);
/// \tag::pll_init_finish[]
if ((pll->cs & PLL_CS_LOCK_BITS) &&
(refdiv == (pll->cs & PLL_CS_REFDIV_BITS)) &&
(fbdiv == (pll->fbdiv_int & PLL_FBDIV_INT_BITS)) &&
(pdiv == (pll->prim & (PLL_PRIM_POSTDIV1_BITS | PLL_PRIM_POSTDIV2_BITS)))) {
// do not disrupt PLL that is already correctly configured and operating
return;
}
uint32_t pll_reset = (pll_usb_hw == pll) ? RESETS_RESET_PLL_USB_BITS : RESETS_RESET_PLL_SYS_BITS;
reset_block(pll_reset);
unreset_block_wait(pll_reset);
// Load VCO-related dividers before starting VCO
pll->cs = refdiv;
pll->fbdiv_int = fbdiv;
// Turn on PLL
uint32_t power = PLL_PWR_PD_BITS | // Main power
PLL_PWR_VCOPD_BITS; // VCO Power
hw_clear_bits(&pll->pwr, power);
// Wait for PLL to lock
while (!(pll->cs & PLL_CS_LOCK_BITS)) tight_loop_contents();
// Set up post dividers
pll->prim = pdiv;
// Turn on post divider
hw_clear_bits(&pll->pwr, PLL_PWR_POSTDIVPD_BITS);
/// \end::pll_init_finish[]
}
void pll_deinit(PLL pll) {
// todo: Make sure there are no sources running from this pll?
pll->pwr = PLL_PWR_BITS;
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_pwm/CMakeLists.txt
================================================
pico_simple_hardware_headers_only_target(pwm)
================================================
FILE: pico-sdk/src/rp2_common/hardware_pwm/include/hardware/pwm.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_PWM_H
#define _HARDWARE_PWM_H
#include "pico.h"
#include "hardware/structs/pwm.h"
#include "hardware/regs/dreq.h"
#ifdef __cplusplus
extern "C" {
#endif
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PWM, Enable/disable assertions in the PWM module, type=bool, default=0, group=hardware_pwm
#ifndef PARAM_ASSERTIONS_ENABLED_PWM
#define PARAM_ASSERTIONS_ENABLED_PWM 0
#endif
/** \file hardware/pwm.h
* \defgroup hardware_pwm hardware_pwm
*
* Hardware Pulse Width Modulation (PWM) API
*
* The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or
* measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable
* PWM outputs. All 30 GPIOs can be driven by the PWM block.
*
* The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a
* toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of
* time spent at the high signal level is known as the duty cycle of the signal.
*
* The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then
* immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after
* reaching TOP, until it reaches 0 again.
*
* \subsection pwm_example Example
* \addtogroup hardware_pwm
* \include hello_pwm.c
*/
/** \brief PWM Divider mode settings
* \ingroup hardware_pwm
*
*/
enum pwm_clkdiv_mode
{
PWM_DIV_FREE_RUNNING = 0, ///< Free-running counting at rate dictated by fractional divider
PWM_DIV_B_HIGH = 1, ///< Fractional divider is gated by the PWM B pin
PWM_DIV_B_RISING = 2, ///< Fractional divider advances with each rising edge of the PWM B pin
PWM_DIV_B_FALLING = 3 ///< Fractional divider advances with each falling edge of the PWM B pin
};
enum pwm_chan
{
PWM_CHAN_A = 0,
PWM_CHAN_B = 1
};
typedef struct {
uint32_t csr;
uint32_t div;
uint32_t top;
} pwm_config;
static inline void check_slice_num_param(__unused uint slice_num) {
valid_params_if(PWM, slice_num < NUM_PWM_SLICES);
}
/** \brief Determine the PWM slice that is attached to the specified GPIO
* \ingroup hardware_pwm
*
* \return The PWM slice number that controls the specified GPIO.
*/
static inline uint pwm_gpio_to_slice_num(uint gpio) {
valid_params_if(PWM, gpio < NUM_BANK0_GPIOS);
return (gpio >> 1u) & 7u;
}
/** \brief Determine the PWM channel that is attached to the specified GPIO.
* \ingroup hardware_pwm
*
* Each slice 0 to 7 has two channels, A and B.
*
* \return The PWM channel that controls the specified GPIO.
*/
static inline uint pwm_gpio_to_channel(uint gpio) {
valid_params_if(PWM, gpio < NUM_BANK0_GPIOS);
return gpio & 1u;
}
/** \brief Set phase correction in a PWM configuration
* \ingroup hardware_pwm
*
* \param c PWM configuration struct to modify
* \param phase_correct true to set phase correct modulation, false to set trailing edge
*
* Setting phase control to true means that instead of wrapping back to zero when the wrap point is reached,
* the PWM starts counting back down. The output frequency is halved when phase-correct mode is enabled.
*/
static inline void pwm_config_set_phase_correct(pwm_config *c, bool phase_correct) {
c->csr = (c->csr & ~PWM_CH0_CSR_PH_CORRECT_BITS)
| (bool_to_bit(phase_correct) << PWM_CH0_CSR_PH_CORRECT_LSB);
}
/** \brief Set PWM clock divider in a PWM configuration
* \ingroup hardware_pwm
*
* \param c PWM configuration struct to modify
* \param div Value to divide counting rate by. Must be greater than or equal to 1.
*
* If the divide mode is free-running, the PWM counter runs at clk_sys / div.
* Otherwise, the divider reduces the rate of events seen on the B pin input (level or edge)
* before passing them on to the PWM counter.
*/
static inline void pwm_config_set_clkdiv(pwm_config *c, float div) {
valid_params_if(PWM, div >= 1.f && div < 256.f);
c->div = (uint32_t)(div * (float)(1u << PWM_CH0_DIV_INT_LSB));
}
/** \brief Set PWM clock divider in a PWM configuration using an 8:4 fractional value
* \ingroup hardware_pwm
*
* \param c PWM configuration struct to modify
* \param integer 8 bit integer part of the clock divider. Must be greater than or equal to 1.
* \param fract 4 bit fractional part of the clock divider
*
* If the divide mode is free-running, the PWM counter runs at clk_sys / div.
* Otherwise, the divider reduces the rate of events seen on the B pin input (level or edge)
* before passing them on to the PWM counter.
*/
static inline void pwm_config_set_clkdiv_int_frac(pwm_config *c, uint8_t integer, uint8_t fract) {
valid_params_if(PWM, integer >= 1);
valid_params_if(PWM, fract < 16);
c->div = (((uint)integer) << PWM_CH0_DIV_INT_LSB) | (((uint)fract) << PWM_CH0_DIV_FRAC_LSB);
}
/** \brief Set PWM clock divider in a PWM configuration
* \ingroup hardware_pwm
*
* \param c PWM configuration struct to modify
* \param div Integer value to reduce counting rate by. Must be greater than or equal to 1.
*
* If the divide mode is free-running, the PWM counter runs at clk_sys / div.
* Otherwise, the divider reduces the rate of events seen on the B pin input (level or edge)
* before passing them on to the PWM counter.
*/
static inline void pwm_config_set_clkdiv_int(pwm_config *c, uint div) {
valid_params_if(PWM, div >= 1 && div < 256);
pwm_config_set_clkdiv_int_frac(c, (uint8_t)div, 0);
}
/** \brief Set PWM counting mode in a PWM configuration
* \ingroup hardware_pwm
*
* \param c PWM configuration struct to modify
* \param mode PWM divide/count mode
*
* Configure which event gates the operation of the fractional divider.
* The default is always-on (free-running PWM). Can also be configured to count on
* high level, rising edge or falling edge of the B pin input.
*/
static inline void pwm_config_set_clkdiv_mode(pwm_config *c, enum pwm_clkdiv_mode mode) {
valid_params_if(PWM, mode == PWM_DIV_FREE_RUNNING ||
mode == PWM_DIV_B_RISING ||
mode == PWM_DIV_B_HIGH ||
mode == PWM_DIV_B_FALLING);
c->csr = (c->csr & ~PWM_CH0_CSR_DIVMODE_BITS)
| (((uint)mode) << PWM_CH0_CSR_DIVMODE_LSB);
}
/** \brief Set output polarity in a PWM configuration
* \ingroup hardware_pwm
*
* \param c PWM configuration struct to modify
* \param a true to invert output A
* \param b true to invert output B
*/
static inline void pwm_config_set_output_polarity(pwm_config *c, bool a, bool b) {
c->csr = (c->csr & ~(PWM_CH0_CSR_A_INV_BITS | PWM_CH0_CSR_B_INV_BITS))
| ((bool_to_bit(a) << PWM_CH0_CSR_A_INV_LSB) | (bool_to_bit(b) << PWM_CH0_CSR_B_INV_LSB));
}
/** \brief Set PWM counter wrap value in a PWM configuration
* \ingroup hardware_pwm
*
* Set the highest value the counter will reach before returning to 0. Also known as TOP.
*
* \param c PWM configuration struct to modify
* \param wrap Value to set wrap to
*/
static inline void pwm_config_set_wrap(pwm_config *c, uint16_t wrap) {
c->top = wrap;
}
/** \brief Initialise a PWM with settings from a configuration object
* \ingroup hardware_pwm
*
* Use the \ref pwm_get_default_config() function to initialise a config structure, make changes as
* needed using the pwm_config_* functions, then call this function to set up the PWM.
*
* \param slice_num PWM slice number
* \param c The configuration to use
* \param start If true the PWM will be started running once configured. If false you will need to start
* manually using \ref pwm_set_enabled() or \ref pwm_set_mask_enabled()
*/
static inline void pwm_init(uint slice_num, pwm_config *c, bool start) {
check_slice_num_param(slice_num);
pwm_hw->slice[slice_num].csr = 0;
pwm_hw->slice[slice_num].ctr = PWM_CH0_CTR_RESET;
pwm_hw->slice[slice_num].cc = PWM_CH0_CC_RESET;
pwm_hw->slice[slice_num].top = c->top;
pwm_hw->slice[slice_num].div = c->div;
pwm_hw->slice[slice_num].csr = c->csr | (bool_to_bit(start) << PWM_CH0_CSR_EN_LSB);
}
/** \brief Get a set of default values for PWM configuration
* \ingroup hardware_pwm
*
* PWM config is free-running at system clock speed, no phase correction, wrapping at 0xffff,
* with standard polarities for channels A and B.
*
* \return Set of default values.
*/
static inline pwm_config pwm_get_default_config(void) {
pwm_config c = {0, 0, 0};
pwm_config_set_phase_correct(&c, false);
pwm_config_set_clkdiv_int(&c, 1);
pwm_config_set_clkdiv_mode(&c, PWM_DIV_FREE_RUNNING);
pwm_config_set_output_polarity(&c, false, false);
pwm_config_set_wrap(&c, 0xffffu);
return c;
}
/** \brief Set the current PWM counter wrap value
* \ingroup hardware_pwm
*
* Set the highest value the counter will reach before returning to 0. Also
* known as TOP.
*
* The counter wrap value is double-buffered in hardware. This means that,
* when the PWM is running, a write to the counter wrap value does not take
* effect until after the next time the PWM slice wraps (or, in phase-correct
* mode, the next time the slice reaches 0). If the PWM is not running, the
* write is latched in immediately.
*
* \param slice_num PWM slice number
* \param wrap Value to set wrap to
*/
static inline void pwm_set_wrap(uint slice_num, uint16_t wrap) {
check_slice_num_param(slice_num);
pwm_hw->slice[slice_num].top = wrap;
}
/** \brief Set the current PWM counter compare value for one channel
* \ingroup hardware_pwm
*
* Set the value of the PWM counter compare value, for either channel A or channel B.
*
* The counter compare register is double-buffered in hardware. This means
* that, when the PWM is running, a write to the counter compare values does
* not take effect until the next time the PWM slice wraps (or, in
* phase-correct mode, the next time the slice reaches 0). If the PWM is not
* running, the write is latched in immediately.
*
* \param slice_num PWM slice number
* \param chan Which channel to update. 0 for A, 1 for B.
* \param level new level for the selected output
*/
static inline void pwm_set_chan_level(uint slice_num, uint chan, uint16_t level) {
check_slice_num_param(slice_num);
hw_write_masked(
&pwm_hw->slice[slice_num].cc,
((uint)level) << (chan ? PWM_CH0_CC_B_LSB : PWM_CH0_CC_A_LSB),
chan ? PWM_CH0_CC_B_BITS : PWM_CH0_CC_A_BITS
);
}
/** \brief Set PWM counter compare values
* \ingroup hardware_pwm
*
* Set the value of the PWM counter compare values, A and B.
*
* The counter compare register is double-buffered in hardware. This means
* that, when the PWM is running, a write to the counter compare values does
* not take effect until the next time the PWM slice wraps (or, in
* phase-correct mode, the next time the slice reaches 0). If the PWM is not
* running, the write is latched in immediately.
*
* \param slice_num PWM slice number
* \param level_a Value to set compare A to. When the counter reaches this value the A output is deasserted
* \param level_b Value to set compare B to. When the counter reaches this value the B output is deasserted
*/
static inline void pwm_set_both_levels(uint slice_num, uint16_t level_a, uint16_t level_b) {
check_slice_num_param(slice_num);
pwm_hw->slice[slice_num].cc = (((uint)level_b) << PWM_CH0_CC_B_LSB) | (((uint)level_a) << PWM_CH0_CC_A_LSB);
}
/** \brief Helper function to set the PWM level for the slice and channel associated with a GPIO.
* \ingroup hardware_pwm
*
* Look up the correct slice (0 to 7) and channel (A or B) for a given GPIO, and update the corresponding
* counter compare field.
*
* This PWM slice should already have been configured and set running. Also be careful of multiple GPIOs
* mapping to the same slice and channel (if GPIOs have a difference of 16).
*
* The counter compare register is double-buffered in hardware. This means
* that, when the PWM is running, a write to the counter compare values does
* not take effect until the next time the PWM slice wraps (or, in
* phase-correct mode, the next time the slice reaches 0). If the PWM is not
* running, the write is latched in immediately.
*
* \param gpio GPIO to set level of
* \param level PWM level for this GPIO
*/
static inline void pwm_set_gpio_level(uint gpio, uint16_t level) {
valid_params_if(PWM, gpio < NUM_BANK0_GPIOS);
pwm_set_chan_level(pwm_gpio_to_slice_num(gpio), pwm_gpio_to_channel(gpio), level);
}
/** \brief Get PWM counter
* \ingroup hardware_pwm
*
* Get current value of PWM counter
*
* \param slice_num PWM slice number
* \return Current value of the PWM counter
*/
static inline uint16_t pwm_get_counter(uint slice_num) {
check_slice_num_param(slice_num);
return (uint16_t)(pwm_hw->slice[slice_num].ctr);
}
/** \brief Set PWM counter
* \ingroup hardware_pwm
*
* Set the value of the PWM counter
*
* \param slice_num PWM slice number
* \param c Value to set the PWM counter to
*
*/
static inline void pwm_set_counter(uint slice_num, uint16_t c) {
check_slice_num_param(slice_num);
pwm_hw->slice[slice_num].ctr = c;
}
/** \brief Advance PWM count
* \ingroup hardware_pwm
*
* Advance the phase of a running the counter by 1 count.
*
* This function will return once the increment is complete.
*
* \param slice_num PWM slice number
*/
static inline void pwm_advance_count(uint slice_num) {
check_slice_num_param(slice_num);
hw_set_bits(&pwm_hw->slice[slice_num].csr, PWM_CH0_CSR_PH_ADV_BITS);
while (pwm_hw->slice[slice_num].csr & PWM_CH0_CSR_PH_ADV_BITS) {
tight_loop_contents();
}
}
/** \brief Retard PWM count
* \ingroup hardware_pwm
*
* Retard the phase of a running counter by 1 count
*
* This function will return once the retardation is complete.
*
* \param slice_num PWM slice number
*/
static inline void pwm_retard_count(uint slice_num) {
check_slice_num_param(slice_num);
hw_set_bits(&pwm_hw->slice[slice_num].csr, PWM_CH0_CSR_PH_RET_BITS);
while (pwm_hw->slice[slice_num].csr & PWM_CH0_CSR_PH_RET_BITS) {
tight_loop_contents();
}
}
/** \brief Set PWM clock divider using an 8:4 fractional value
* \ingroup hardware_pwm
*
* Set the clock divider. Counter increment will be on sysclock divided by this value, taking into account the gating.
*
* \param slice_num PWM slice number
* \param integer 8 bit integer part of the clock divider
* \param fract 4 bit fractional part of the clock divider
*/
static inline void pwm_set_clkdiv_int_frac(uint slice_num, uint8_t integer, uint8_t fract) {
check_slice_num_param(slice_num);
valid_params_if(PWM, integer >= 1);
valid_params_if(PWM, fract < 16);
pwm_hw->slice[slice_num].div = (((uint)integer) << PWM_CH0_DIV_INT_LSB) | (((uint)fract) << PWM_CH0_DIV_FRAC_LSB);
}
/** \brief Set PWM clock divider
* \ingroup hardware_pwm
*
* Set the clock divider. Counter increment will be on sysclock divided by this value, taking into account the gating.
*
* \param slice_num PWM slice number
* \param divider Floating point clock divider, 1.f <= value < 256.f
*/
static inline void pwm_set_clkdiv(uint slice_num, float divider) {
check_slice_num_param(slice_num);
valid_params_if(PWM, divider >= 1.f && divider < 256.f);
uint8_t i = (uint8_t)divider;
uint8_t f = (uint8_t)((divider - i) * (0x01 << 4));
pwm_set_clkdiv_int_frac(slice_num, i, f);
}
/** \brief Set PWM output polarity
* \ingroup hardware_pwm
*
* \param slice_num PWM slice number
* \param a true to invert output A
* \param b true to invert output B
*/
static inline void pwm_set_output_polarity(uint slice_num, bool a, bool b) {
check_slice_num_param(slice_num);
hw_write_masked(&pwm_hw->slice[slice_num].csr, bool_to_bit(a) << PWM_CH0_CSR_A_INV_LSB | bool_to_bit(b) << PWM_CH0_CSR_B_INV_LSB,
PWM_CH0_CSR_A_INV_BITS | PWM_CH0_CSR_B_INV_BITS);
}
/** \brief Set PWM divider mode
* \ingroup hardware_pwm
*
* \param slice_num PWM slice number
* \param mode Required divider mode
*/
static inline void pwm_set_clkdiv_mode(uint slice_num, enum pwm_clkdiv_mode mode) {
check_slice_num_param(slice_num);
valid_params_if(PWM, mode == PWM_DIV_FREE_RUNNING ||
mode == PWM_DIV_B_RISING ||
mode == PWM_DIV_B_HIGH ||
mode == PWM_DIV_B_FALLING);
hw_write_masked(&pwm_hw->slice[slice_num].csr, ((uint)mode) << PWM_CH0_CSR_DIVMODE_LSB, PWM_CH0_CSR_DIVMODE_BITS);
}
/** \brief Set PWM phase correct on/off
* \ingroup hardware_pwm
*
* \param slice_num PWM slice number
* \param phase_correct true to set phase correct modulation, false to set trailing edge
*
* Setting phase control to true means that instead of wrapping back to zero when the wrap point is reached,
* the PWM starts counting back down. The output frequency is halved when phase-correct mode is enabled.
*/
static inline void pwm_set_phase_correct(uint slice_num, bool phase_correct) {
check_slice_num_param(slice_num);
hw_write_masked(&pwm_hw->slice[slice_num].csr, bool_to_bit(phase_correct) << PWM_CH0_CSR_PH_CORRECT_LSB, PWM_CH0_CSR_PH_CORRECT_BITS);
}
/** \brief Enable/Disable PWM
* \ingroup hardware_pwm
*
* When a PWM is disabled, it halts its counter, and the output pins are left
* high or low depending on exactly when the counter is halted. When
* re-enabled the PWM resumes immediately from where it left off.
*
* If the PWM's output pins need to be low when halted:
*
* - The counter compare can be set to zero whilst the PWM is enabled, and
* then the PWM disabled once both pins are seen to be low
*
* - The GPIO output overrides can be used to force the actual pins low
*
* - The PWM can be run for one cycle (i.e. enabled then immediately disabled)
* with a TOP of 0, count of 0 and counter compare of 0, to force the pins
* low when the PWM has already been halted. The same method can be used
* with a counter compare value of 1 to force a pin high.
*
* Note that, when disabled, the PWM can still be advanced one count at a time
* by pulsing the PH_ADV bit in its CSR. The output pins transition as though
* the PWM were enabled.
*
* \param slice_num PWM slice number
* \param enabled true to enable the specified PWM, false to disable.
*/
static inline void pwm_set_enabled(uint slice_num, bool enabled) {
check_slice_num_param(slice_num);
hw_write_masked(&pwm_hw->slice[slice_num].csr, bool_to_bit(enabled) << PWM_CH0_CSR_EN_LSB, PWM_CH0_CSR_EN_BITS);
}
/** \brief Enable/Disable multiple PWM slices simultaneously
* \ingroup hardware_pwm
*
* \param mask Bitmap of PWMs to enable/disable. Bits 0 to 7 enable slices 0-7 respectively
*/
static inline void pwm_set_mask_enabled(uint32_t mask) {
pwm_hw->en = mask;
}
/*! \brief Enable PWM instance interrupt
* \ingroup hardware_pwm
*
* Used to enable a single PWM instance interrupt.
*
* \param slice_num PWM block to enable/disable
* \param enabled true to enable, false to disable
*/
static inline void pwm_set_irq_enabled(uint slice_num, bool enabled) {
check_slice_num_param(slice_num);
if (enabled) {
hw_set_bits(&pwm_hw->inte, 1u << slice_num);
} else {
hw_clear_bits(&pwm_hw->inte, 1u << slice_num);
}
}
/*! \brief Enable multiple PWM instance interrupts
* \ingroup hardware_pwm
*
* Use this to enable multiple PWM interrupts at once.
*
* \param slice_mask Bitmask of all the blocks to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc.
* \param enabled true to enable, false to disable
*/
static inline void pwm_set_irq_mask_enabled(uint32_t slice_mask, bool enabled) {
valid_params_if(PWM, slice_mask < 256);
if (enabled) {
hw_set_bits(&pwm_hw->inte, slice_mask);
} else {
hw_clear_bits(&pwm_hw->inte, slice_mask);
}
}
/*! \brief Clear a single PWM channel interrupt
* \ingroup hardware_pwm
*
* \param slice_num PWM slice number
*/
static inline void pwm_clear_irq(uint slice_num) {
pwm_hw->intr = 1u << slice_num;
}
/*! \brief Get PWM interrupt status, raw
* \ingroup hardware_pwm
*
* \return Bitmask of all PWM interrupts currently set
*/
static inline uint32_t pwm_get_irq_status_mask(void) {
return pwm_hw->ints;
}
/*! \brief Force PWM interrupt
* \ingroup hardware_pwm
*
* \param slice_num PWM slice number
*/
static inline void pwm_force_irq(uint slice_num) {
pwm_hw->intf = 1u << slice_num;
}
/*! \brief Return the DREQ to use for pacing transfers to a particular PWM slice
* \ingroup hardware_pwm
*
* \param slice_num PWM slice number
*/
static inline uint pwm_get_dreq(uint slice_num) {
static_assert(DREQ_PWM_WRAP1 == DREQ_PWM_WRAP0 + 1, "");
static_assert(DREQ_PWM_WRAP7 == DREQ_PWM_WRAP0 + 7, "");
check_slice_num_param(slice_num);
return DREQ_PWM_WRAP0 + slice_num;
}
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_resets/CMakeLists.txt
================================================
pico_simple_hardware_headers_only_target(resets)
================================================
FILE: pico-sdk/src/rp2_common/hardware_resets/include/hardware/resets.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_RESETS_H
#define _HARDWARE_RESETS_H
#include "pico.h"
#include "hardware/structs/resets.h"
/** \file hardware/resets.h
* \defgroup hardware_resets hardware_resets
*
* Hardware Reset API
*
* The reset controller allows software control of the resets to all of the peripherals that are not
* critical to boot the processor in the RP2040.
*
* \subsubsection reset_bitmask
* \addtogroup hardware_resets
*
* Multiple blocks are referred to using a bitmask as follows:
*
* Block to reset | Bit
* ---------------|----
* USB | 24
* UART 1 | 23
* UART 0 | 22
* Timer | 21
* TB Manager | 20
* SysInfo | 19
* System Config | 18
* SPI 1 | 17
* SPI 0 | 16
* RTC | 15
* PWM | 14
* PLL USB | 13
* PLL System | 12
* PIO 1 | 11
* PIO 0 | 10
* Pads - QSPI | 9
* Pads - bank 0 | 8
* JTAG | 7
* IO Bank 1 | 6
* IO Bank 0 | 5
* I2C 1 | 4
* I2C 0 | 3
* DMA | 2
* Bus Control | 1
* ADC 0 | 0
*
* \subsection reset_example Example
* \addtogroup hardware_resets
* \include hello_reset.c
*/
#ifdef __cplusplus
extern "C" {
#endif
/// \tag::reset_funcs[]
/*! \brief Reset the specified HW blocks
* \ingroup hardware_resets
*
* \param bits Bit pattern indicating blocks to reset. See \ref reset_bitmask
*/
static inline void reset_block(uint32_t bits) {
hw_set_bits(&resets_hw->reset, bits);
}
/*! \brief bring specified HW blocks out of reset
* \ingroup hardware_resets
*
* \param bits Bit pattern indicating blocks to unreset. See \ref reset_bitmask
*/
static inline void unreset_block(uint32_t bits) {
hw_clear_bits(&resets_hw->reset, bits);
}
/*! \brief Bring specified HW blocks out of reset and wait for completion
* \ingroup hardware_resets
*
* \param bits Bit pattern indicating blocks to unreset. See \ref reset_bitmask
*/
static inline void unreset_block_wait(uint32_t bits) {
hw_clear_bits(&resets_hw->reset, bits);
while (~resets_hw->reset_done & bits)
tight_loop_contents();
}
/// \end::reset_funcs[]
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_rtc/CMakeLists.txt
================================================
pico_simple_hardware_target(rtc)
pico_mirrored_target_link_libraries(hardware_rtc INTERFACE hardware_irq hardware_resets hardware_clocks)
================================================
FILE: pico-sdk/src/rp2_common/hardware_rtc/include/hardware/rtc.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_RTC_H
#define _HARDWARE_RTC_H
#include "pico.h"
#include "hardware/structs/rtc.h"
/** \file hardware/rtc.h
* \defgroup hardware_rtc hardware_rtc
*
* Hardware Real Time Clock API
*
* The RTC keeps track of time in human readable format and generates events when the time is equal
* to a preset value. Think of a digital clock, not epoch time used by most computers. There are seven
* fields, one each for year (12 bit), month (4 bit), day (5 bit), day of the week (3 bit), hour (5 bit)
* minute (6 bit) and second (6 bit), storing the data in binary format.
*
* \sa datetime_t
*
* \subsection rtc_example Example
* \addtogroup hardware_rtc
*
* \include hello_rtc.c
*/
#ifdef __cplusplus
extern "C" {
#endif
/*! Callback function type for RTC alarms
* \ingroup hardware_rtc
*
* \sa rtc_set_alarm()
*/
typedef void (*rtc_callback_t)(void);
/*! \brief Initialise the RTC system
* \ingroup hardware_rtc
*/
void rtc_init(void);
/*! \brief Set the RTC to the specified time
* \ingroup hardware_rtc
*
* \note Note that after setting the RTC date and time, a subsequent read of the values (e.g. via rtc_get_datetime()) may not
* reflect the new setting until up to three cycles of the potentially-much-slower RTC clock domain have passed. This represents a period
* of 64 microseconds with the default RTC clock configuration.
*
* \param t Pointer to a \ref datetime_t structure contains time to set
* \return true if set, false if the passed in datetime was invalid.
*/
bool rtc_set_datetime(datetime_t *t);
/*! \brief Get the current time from the RTC
* \ingroup hardware_rtc
*
* \param t Pointer to a \ref datetime_t structure to receive the current RTC time
* \return true if datetime is valid, false if the RTC is not running.
*/
bool rtc_get_datetime(datetime_t *t);
/*! \brief Is the RTC running?
* \ingroup hardware_rtc
*
*/
bool rtc_running(void);
/*! \brief Set a time in the future for the RTC to call a user provided callback
* \ingroup hardware_rtc
*
* \param t Pointer to a \ref datetime_t structure containing a time in the future to fire the alarm. Any values set to -1 will not be matched on.
* \param user_callback pointer to a \ref rtc_callback_t to call when the alarm fires
*/
void rtc_set_alarm(datetime_t *t, rtc_callback_t user_callback);
/*! \brief Enable the RTC alarm (if inactive)
* \ingroup hardware_rtc
*/
void rtc_enable_alarm(void);
/*! \brief Disable the RTC alarm (if active)
* \ingroup hardware_rtc
*/
void rtc_disable_alarm(void);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_rtc/rtc.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
#include "hardware/irq.h"
#include "hardware/rtc.h"
#include "hardware/resets.h"
#include "hardware/clocks.h"
// Set this when setting an alarm
static rtc_callback_t _callback = NULL;
static bool _alarm_repeats = false;
bool rtc_running(void) {
return (rtc_hw->ctrl & RTC_CTRL_RTC_ACTIVE_BITS);
}
void rtc_init(void) {
// Get clk_rtc freq and make sure it is running
uint rtc_freq = clock_get_hz(clk_rtc);
assert(rtc_freq != 0);
// Take rtc out of reset now that we know clk_rtc is running
reset_block(RESETS_RESET_RTC_BITS);
unreset_block_wait(RESETS_RESET_RTC_BITS);
// Set up the 1 second divider.
// If rtc_freq is 400 then clkdiv_m1 should be 399
rtc_freq -= 1;
// Check the freq is not too big to divide
assert(rtc_freq <= RTC_CLKDIV_M1_BITS);
// Write divide value
rtc_hw->clkdiv_m1 = rtc_freq;
}
static bool valid_datetime(datetime_t *t) {
// Valid ranges taken from RTC doc. Note when setting an RTC alarm
// these values are allowed to be -1 to say "don't match this value"
if (!(t->year >= 0 && t->year <= 4095)) return false;
if (!(t->month >= 1 && t->month <= 12)) return false;
if (!(t->day >= 1 && t->day <= 31)) return false;
if (!(t->dotw >= 0 && t->dotw <= 6)) return false;
if (!(t->hour >= 0 && t->hour <= 23)) return false;
if (!(t->min >= 0 && t->min <= 59)) return false;
if (!(t->sec >= 0 && t->sec <= 59)) return false;
return true;
}
bool rtc_set_datetime(datetime_t *t) {
if (!valid_datetime(t)) {
return false;
}
// Disable RTC
rtc_hw->ctrl = 0;
// Wait while it is still active
while (rtc_running()) {
tight_loop_contents();
}
// Write to setup registers
rtc_hw->setup_0 = (((uint32_t)t->year) << RTC_SETUP_0_YEAR_LSB ) |
(((uint32_t)t->month) << RTC_SETUP_0_MONTH_LSB) |
(((uint32_t)t->day) << RTC_SETUP_0_DAY_LSB);
rtc_hw->setup_1 = (((uint32_t)t->dotw) << RTC_SETUP_1_DOTW_LSB) |
(((uint32_t)t->hour) << RTC_SETUP_1_HOUR_LSB) |
(((uint32_t)t->min) << RTC_SETUP_1_MIN_LSB) |
(((uint32_t)t->sec) << RTC_SETUP_1_SEC_LSB);
// Load setup values into rtc clock domain
rtc_hw->ctrl = RTC_CTRL_LOAD_BITS;
// Enable RTC and wait for it to be running
rtc_hw->ctrl = RTC_CTRL_RTC_ENABLE_BITS;
while (!rtc_running()) {
tight_loop_contents();
}
return true;
}
bool rtc_get_datetime(datetime_t *t) {
// Make sure RTC is running
if (!rtc_running()) {
return false;
}
// Note: RTC_0 should be read before RTC_1
uint32_t rtc_0 = rtc_hw->rtc_0;
uint32_t rtc_1 = rtc_hw->rtc_1;
t->dotw = (int8_t) ((rtc_0 & RTC_RTC_0_DOTW_BITS ) >> RTC_RTC_0_DOTW_LSB);
t->hour = (int8_t) ((rtc_0 & RTC_RTC_0_HOUR_BITS ) >> RTC_RTC_0_HOUR_LSB);
t->min = (int8_t) ((rtc_0 & RTC_RTC_0_MIN_BITS ) >> RTC_RTC_0_MIN_LSB);
t->sec = (int8_t) ((rtc_0 & RTC_RTC_0_SEC_BITS ) >> RTC_RTC_0_SEC_LSB);
t->year = (int16_t) ((rtc_1 & RTC_RTC_1_YEAR_BITS ) >> RTC_RTC_1_YEAR_LSB);
t->month = (int8_t) ((rtc_1 & RTC_RTC_1_MONTH_BITS) >> RTC_RTC_1_MONTH_LSB);
t->day = (int8_t) ((rtc_1 & RTC_RTC_1_DAY_BITS ) >> RTC_RTC_1_DAY_LSB);
return true;
}
void rtc_enable_alarm(void) {
// Set matching and wait for it to be enabled
hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MATCH_ENA_BITS);
while(!(rtc_hw->irq_setup_0 & RTC_IRQ_SETUP_0_MATCH_ACTIVE_BITS)) {
tight_loop_contents();
}
}
static void rtc_irq_handler(void) {
// Always disable the alarm to clear the current IRQ.
// Even if it is a repeatable alarm, we don't want it to keep firing.
// If it matches on a second it can keep firing for that second.
rtc_disable_alarm();
if (_alarm_repeats) {
// If it is a repeatable alarm, re enable the alarm.
rtc_enable_alarm();
}
// Call user callback function
if (_callback) {
_callback();
}
}
static bool rtc_alarm_repeats(datetime_t *t) {
// If any value is set to -1 then we don't match on that value
// hence the alarm will eventually repeat
if (t->year < 0) return true;
if (t->month < 0) return true;
if (t->day < 0) return true;
if (t->dotw < 0) return true;
if (t->hour < 0) return true;
if (t->min < 0) return true;
if (t->sec < 0) return true;
return false;
}
void rtc_set_alarm(datetime_t *t, rtc_callback_t user_callback) {
rtc_disable_alarm();
// Only add to setup if it isn't -1
rtc_hw->irq_setup_0 = ((t->year < 0) ? 0 : (((uint32_t)t->year) << RTC_IRQ_SETUP_0_YEAR_LSB )) |
((t->month < 0) ? 0 : (((uint32_t)t->month) << RTC_IRQ_SETUP_0_MONTH_LSB)) |
((t->day < 0) ? 0 : (((uint32_t)t->day) << RTC_IRQ_SETUP_0_DAY_LSB ));
rtc_hw->irq_setup_1 = ((t->dotw < 0) ? 0 : (((uint32_t)t->dotw) << RTC_IRQ_SETUP_1_DOTW_LSB)) |
((t->hour < 0) ? 0 : (((uint32_t)t->hour) << RTC_IRQ_SETUP_1_HOUR_LSB)) |
((t->min < 0) ? 0 : (((uint32_t)t->min) << RTC_IRQ_SETUP_1_MIN_LSB )) |
((t->sec < 0) ? 0 : (((uint32_t)t->sec) << RTC_IRQ_SETUP_1_SEC_LSB ));
// Set the match enable bits for things we care about
if (t->year >= 0) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_YEAR_ENA_BITS);
if (t->month >= 0) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MONTH_ENA_BITS);
if (t->day >= 0) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_DAY_ENA_BITS);
if (t->dotw >= 0) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_DOTW_ENA_BITS);
if (t->hour >= 0) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_HOUR_ENA_BITS);
if (t->min >= 0) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_MIN_ENA_BITS);
if (t->sec >= 0) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_SEC_ENA_BITS);
// Does it repeat? I.e. do we not match on any of the bits
_alarm_repeats = rtc_alarm_repeats(t);
// Store function pointer we can call later
_callback = user_callback;
irq_set_exclusive_handler(RTC_IRQ, rtc_irq_handler);
// Enable the IRQ at the peri
rtc_hw->inte = RTC_INTE_RTC_BITS;
// Enable the IRQ at the proc
irq_set_enabled(RTC_IRQ, true);
rtc_enable_alarm();
}
void rtc_disable_alarm(void) {
// Disable matching and wait for it to stop being active
hw_clear_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MATCH_ENA_BITS);
while(rtc_hw->irq_setup_0 & RTC_IRQ_SETUP_0_MATCH_ACTIVE_BITS) {
tight_loop_contents();
}
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_spi/CMakeLists.txt
================================================
pico_simple_hardware_target(spi)
pico_mirrored_target_link_libraries(hardware_spi INTERFACE hardware_resets hardware_clocks)
================================================
FILE: pico-sdk/src/rp2_common/hardware_spi/include/hardware/spi.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_SPI_H
#define _HARDWARE_SPI_H
#include "pico.h"
#include "hardware/structs/spi.h"
#include "hardware/regs/dreq.h"
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_SPI, Enable/disable assertions in the SPI module, type=bool, default=0, group=hardware_spi
#ifndef PARAM_ASSERTIONS_ENABLED_SPI
#define PARAM_ASSERTIONS_ENABLED_SPI 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
/** \file hardware/spi.h
* \defgroup hardware_spi hardware_spi
*
* Hardware SPI API
*
* RP2040 has 2 identical instances of the Serial Peripheral Interface (SPI) controller.
*
* The PrimeCell SSP is a master or slave interface for synchronous serial communication with peripheral devices that have
* Motorola SPI, National Semiconductor Microwire, or Texas Instruments synchronous serial interfaces.
*
* Controller can be defined as master or slave using the \ref spi_set_slave function.
*
* Each controller can be connected to a number of GPIO pins, see the datasheet GPIO function selection table for more information.
*/
// PICO_CONFIG: PICO_DEFAULT_SPI, Define the default SPI for a board, min=0, max=1, group=hardware_spi
// PICO_CONFIG: PICO_DEFAULT_SPI_SCK_PIN, Define the default SPI SCK pin, min=0, max=29, group=hardware_spi
// PICO_CONFIG: PICO_DEFAULT_SPI_TX_PIN, Define the default SPI TX pin, min=0, max=29, group=hardware_spi
// PICO_CONFIG: PICO_DEFAULT_SPI_RX_PIN, Define the default SPI RX pin, min=0, max=29, group=hardware_spi
// PICO_CONFIG: PICO_DEFAULT_SPI_CSN_PIN, Define the default SPI CSN pin, min=0, max=29, group=hardware_spi
/**
* Opaque type representing an SPI instance.
*/
typedef struct spi_inst spi_inst_t;
/** Identifier for the first (SPI 0) hardware SPI instance (for use in SPI functions).
*
* e.g. spi_init(spi0, 48000)
*
* \ingroup hardware_spi
*/
#define spi0 ((spi_inst_t *)spi0_hw)
/** Identifier for the second (SPI 1) hardware SPI instance (for use in SPI functions).
*
* e.g. spi_init(spi1, 48000)
*
* \ingroup hardware_spi
*/
#define spi1 ((spi_inst_t *)spi1_hw)
#if !defined(PICO_DEFAULT_SPI_INSTANCE) && defined(PICO_DEFAULT_SPI)
#define PICO_DEFAULT_SPI_INSTANCE (__CONCAT(spi,PICO_DEFAULT_SPI))
#endif
#ifdef PICO_DEFAULT_SPI_INSTANCE
#define spi_default PICO_DEFAULT_SPI_INSTANCE
#endif
/** \brief Enumeration of SPI CPHA (clock phase) values.
* \ingroup hardware_spi
*/
typedef enum {
SPI_CPHA_0 = 0,
SPI_CPHA_1 = 1
} spi_cpha_t;
/** \brief Enumeration of SPI CPOL (clock polarity) values.
* \ingroup hardware_spi
*/
typedef enum {
SPI_CPOL_0 = 0,
SPI_CPOL_1 = 1
} spi_cpol_t;
/** \brief Enumeration of SPI bit-order values.
* \ingroup hardware_spi
*/
typedef enum {
SPI_LSB_FIRST = 0,
SPI_MSB_FIRST = 1
} spi_order_t;
// ----------------------------------------------------------------------------
// Setup
/*! \brief Initialise SPI instances
* \ingroup hardware_spi
* Puts the SPI into a known state, and enable it. Must be called before other
* functions.
*
* \note There is no guarantee that the baudrate requested can be achieved exactly; the nearest will be chosen
* and returned
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param baudrate Baudrate requested in Hz
* \return the actual baud rate set
*/
uint spi_init(spi_inst_t *spi, uint baudrate);
/*! \brief Deinitialise SPI instances
* \ingroup hardware_spi
* Puts the SPI into a disabled state. Init will need to be called to reenable the device
* functions.
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
*/
void spi_deinit(spi_inst_t *spi);
/*! \brief Set SPI baudrate
* \ingroup hardware_spi
*
* Set SPI frequency as close as possible to baudrate, and return the actual
* achieved rate.
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param baudrate Baudrate required in Hz, should be capable of a bitrate of at least 2Mbps, or higher, depending on system clock settings.
* \return The actual baudrate set
*/
uint spi_set_baudrate(spi_inst_t *spi, uint baudrate);
/*! \brief Get SPI baudrate
* \ingroup hardware_spi
*
* Get SPI baudrate which was set by \see spi_set_baudrate
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \return The actual baudrate set
*/
uint spi_get_baudrate(const spi_inst_t *spi);
/*! \brief Convert SPI instance to hardware instance number
* \ingroup hardware_spi
*
* \param spi SPI instance
* \return Number of SPI, 0 or 1.
*/
static inline uint spi_get_index(const spi_inst_t *spi) {
invalid_params_if(SPI, spi != spi0 && spi != spi1);
return spi == spi1 ? 1 : 0;
}
static inline spi_hw_t *spi_get_hw(spi_inst_t *spi) {
spi_get_index(spi); // check it is a hw spi
return (spi_hw_t *)spi;
}
static inline const spi_hw_t *spi_get_const_hw(const spi_inst_t *spi) {
spi_get_index(spi); // check it is a hw spi
return (const spi_hw_t *)spi;
}
/*! \brief Configure SPI
* \ingroup hardware_spi
*
* Configure how the SPI serialises and deserialises data on the wire
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param data_bits Number of data bits per transfer. Valid values 4..16.
* \param cpol SSPCLKOUT polarity, applicable to Motorola SPI frame format only.
* \param cpha SSPCLKOUT phase, applicable to Motorola SPI frame format only
* \param order Must be SPI_MSB_FIRST, no other values supported on the PL022
*/
static inline void spi_set_format(spi_inst_t *spi, uint data_bits, spi_cpol_t cpol, spi_cpha_t cpha, __unused spi_order_t order) {
invalid_params_if(SPI, data_bits < 4 || data_bits > 16);
// LSB-first not supported on PL022:
invalid_params_if(SPI, order != SPI_MSB_FIRST);
invalid_params_if(SPI, cpol != SPI_CPOL_0 && cpol != SPI_CPOL_1);
invalid_params_if(SPI, cpha != SPI_CPHA_0 && cpha != SPI_CPHA_1);
// Disable the SPI
uint32_t enable_mask = spi_get_hw(spi)->cr1 & SPI_SSPCR1_SSE_BITS;
hw_clear_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_SSE_BITS);
hw_write_masked(&spi_get_hw(spi)->cr0,
((uint)(data_bits - 1)) << SPI_SSPCR0_DSS_LSB |
((uint)cpol) << SPI_SSPCR0_SPO_LSB |
((uint)cpha) << SPI_SSPCR0_SPH_LSB,
SPI_SSPCR0_DSS_BITS |
SPI_SSPCR0_SPO_BITS |
SPI_SSPCR0_SPH_BITS);
// Re-enable the SPI
hw_set_bits(&spi_get_hw(spi)->cr1, enable_mask);
}
/*! \brief Set SPI master/slave
* \ingroup hardware_spi
*
* Configure the SPI for master- or slave-mode operation. By default,
* spi_init() sets master-mode.
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param slave true to set SPI device as a slave device, false for master.
*/
static inline void spi_set_slave(spi_inst_t *spi, bool slave) {
// Disable the SPI
uint32_t enable_mask = spi_get_hw(spi)->cr1 & SPI_SSPCR1_SSE_BITS;
hw_clear_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_SSE_BITS);
if (slave)
hw_set_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_MS_BITS);
else
hw_clear_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_MS_BITS);
// Re-enable the SPI
hw_set_bits(&spi_get_hw(spi)->cr1, enable_mask);
}
// ----------------------------------------------------------------------------
// Generic input/output
/*! \brief Check whether a write can be done on SPI device
* \ingroup hardware_spi
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \return false if no space is available to write. True if a write is possible
*/
static inline bool spi_is_writable(const spi_inst_t *spi) {
return (spi_get_const_hw(spi)->sr & SPI_SSPSR_TNF_BITS);
}
/*! \brief Check whether a read can be done on SPI device
* \ingroup hardware_spi
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \return true if a read is possible i.e. data is present
*/
static inline bool spi_is_readable(const spi_inst_t *spi) {
return (spi_get_const_hw(spi)->sr & SPI_SSPSR_RNE_BITS);
}
/*! \brief Check whether SPI is busy
* \ingroup hardware_spi
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \return true if SPI is busy
*/
static inline bool spi_is_busy(const spi_inst_t *spi) {
return (spi_get_const_hw(spi)->sr & SPI_SSPSR_BSY_BITS);
}
/*! \brief Write/Read to/from an SPI device
* \ingroup hardware_spi
*
* Write \p len bytes from \p src to SPI. Simultaneously read \p len bytes from SPI to \p dst.
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param src Buffer of data to write
* \param dst Buffer for read data
* \param len Length of BOTH buffers
* \return Number of bytes written/read
*/
int spi_write_read_blocking(spi_inst_t *spi, const uint8_t *src, uint8_t *dst, size_t len);
/*! \brief Write to an SPI device, blocking
* \ingroup hardware_spi
*
* Write \p len bytes from \p src to SPI, and discard any data received back
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param src Buffer of data to write
* \param len Length of \p src
* \return Number of bytes written/read
*/
int spi_write_blocking(spi_inst_t *spi, const uint8_t *src, size_t len);
/*! \brief Read from an SPI device
* \ingroup hardware_spi
*
* Read \p len bytes from SPI to \p dst.
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
* \p repeated_tx_data is output repeatedly on TX as data is read in from RX.
* Generally this can be 0, but some devices require a specific value here,
* e.g. SD cards expect 0xff
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param repeated_tx_data Buffer of data to write
* \param dst Buffer for read data
* \param len Length of buffer \p dst
* \return Number of bytes written/read
*/
int spi_read_blocking(spi_inst_t *spi, uint8_t repeated_tx_data, uint8_t *dst, size_t len);
// ----------------------------------------------------------------------------
// SPI-specific operations and aliases
// FIXME need some instance-private data for select() and deselect() if we are going that route
/*! \brief Write/Read half words to/from an SPI device
* \ingroup hardware_spi
*
* Write \p len halfwords from \p src to SPI. Simultaneously read \p len halfwords from SPI to \p dst.
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
*
* \note SPI should be initialised with 16 data_bits using \ref spi_set_format first, otherwise this function will only read/write 8 data_bits.
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param src Buffer of data to write
* \param dst Buffer for read data
* \param len Length of BOTH buffers in halfwords
* \return Number of halfwords written/read
*/
int spi_write16_read16_blocking(spi_inst_t *spi, const uint16_t *src, uint16_t *dst, size_t len);
/*! \brief Write to an SPI device
* \ingroup hardware_spi
*
* Write \p len halfwords from \p src to SPI. Discard any data received back.
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
*
* \note SPI should be initialised with 16 data_bits using \ref spi_set_format first, otherwise this function will only write 8 data_bits.
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param src Buffer of data to write
* \param len Length of buffers
* \return Number of halfwords written/read
*/
int spi_write16_blocking(spi_inst_t *spi, const uint16_t *src, size_t len);
/*! \brief Read from an SPI device
* \ingroup hardware_spi
*
* Read \p len halfwords from SPI to \p dst.
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
* \p repeated_tx_data is output repeatedly on TX as data is read in from RX.
* Generally this can be 0, but some devices require a specific value here,
* e.g. SD cards expect 0xff
*
* \note SPI should be initialised with 16 data_bits using \ref spi_set_format first, otherwise this function will only read 8 data_bits.
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param repeated_tx_data Buffer of data to write
* \param dst Buffer for read data
* \param len Length of buffer \p dst in halfwords
* \return Number of halfwords written/read
*/
int spi_read16_blocking(spi_inst_t *spi, uint16_t repeated_tx_data, uint16_t *dst, size_t len);
/*! \brief Return the DREQ to use for pacing transfers to/from a particular SPI instance
* \ingroup hardware_spi
*
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
* \param is_tx true for sending data to the SPI instance, false for receiving data from the SPI instance
*/
static inline uint spi_get_dreq(spi_inst_t *spi, bool is_tx) {
static_assert(DREQ_SPI0_RX == DREQ_SPI0_TX + 1, "");
static_assert(DREQ_SPI1_RX == DREQ_SPI1_TX + 1, "");
static_assert(DREQ_SPI1_TX == DREQ_SPI0_TX + 2, "");
return DREQ_SPI0_TX + spi_get_index(spi) * 2 + !is_tx;
}
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_spi/spi.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "hardware/resets.h"
#include "hardware/clocks.h"
#include "hardware/spi.h"
static inline void spi_reset(spi_inst_t *spi) {
invalid_params_if(SPI, spi != spi0 && spi != spi1);
reset_block(spi == spi0 ? RESETS_RESET_SPI0_BITS : RESETS_RESET_SPI1_BITS);
}
static inline void spi_unreset(spi_inst_t *spi) {
invalid_params_if(SPI, spi != spi0 && spi != spi1);
unreset_block_wait(spi == spi0 ? RESETS_RESET_SPI0_BITS : RESETS_RESET_SPI1_BITS);
}
uint spi_init(spi_inst_t *spi, uint baudrate) {
spi_reset(spi);
spi_unreset(spi);
uint baud = spi_set_baudrate(spi, baudrate);
spi_set_format(spi, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
// Always enable DREQ signals -- harmless if DMA is not listening
hw_set_bits(&spi_get_hw(spi)->dmacr, SPI_SSPDMACR_TXDMAE_BITS | SPI_SSPDMACR_RXDMAE_BITS);
// Finally enable the SPI
hw_set_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_SSE_BITS);
return baud;
}
void spi_deinit(spi_inst_t *spi) {
hw_clear_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_SSE_BITS);
hw_clear_bits(&spi_get_hw(spi)->dmacr, SPI_SSPDMACR_TXDMAE_BITS | SPI_SSPDMACR_RXDMAE_BITS);
spi_reset(spi);
}
uint spi_set_baudrate(spi_inst_t *spi, uint baudrate) {
uint freq_in = clock_get_hz(clk_peri);
uint prescale, postdiv;
invalid_params_if(SPI, baudrate > freq_in);
// Disable the SPI
uint32_t enable_mask = spi_get_hw(spi)->cr1 & SPI_SSPCR1_SSE_BITS;
hw_clear_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_SSE_BITS);
// Find smallest prescale value which puts output frequency in range of
// post-divide. Prescale is an even number from 2 to 254 inclusive.
for (prescale = 2; prescale <= 254; prescale += 2) {
if (freq_in < (prescale + 2) * 256 * (uint64_t) baudrate)
break;
}
invalid_params_if(SPI, prescale > 254); // Frequency too low
// Find largest post-divide which makes output <= baudrate. Post-divide is
// an integer in the range 1 to 256 inclusive.
for (postdiv = 256; postdiv > 1; --postdiv) {
if (freq_in / (prescale * (postdiv - 1)) > baudrate)
break;
}
spi_get_hw(spi)->cpsr = prescale;
hw_write_masked(&spi_get_hw(spi)->cr0, (postdiv - 1) << SPI_SSPCR0_SCR_LSB, SPI_SSPCR0_SCR_BITS);
// Re-enable the SPI
hw_set_bits(&spi_get_hw(spi)->cr1, enable_mask);
// Return the frequency we were able to achieve
return freq_in / (prescale * postdiv);
}
uint spi_get_baudrate(const spi_inst_t *spi) {
uint prescale = spi_get_const_hw(spi)->cpsr;
uint postdiv = ((spi_get_const_hw(spi)->cr0 & SPI_SSPCR0_SCR_BITS) >> SPI_SSPCR0_SCR_LSB) + 1;
return clock_get_hz(clk_peri) / (prescale * postdiv);
}
// Write len bytes from src to SPI. Simultaneously read len bytes from SPI to dst.
// Note this function is guaranteed to exit in a known amount of time (bits sent * time per bit)
int __not_in_flash_func(spi_write_read_blocking)(spi_inst_t *spi, const uint8_t *src, uint8_t *dst, size_t len) {
invalid_params_if(SPI, 0 > (int)len);
// Never have more transfers in flight than will fit into the RX FIFO,
// else FIFO will overflow if this code is heavily interrupted.
const size_t fifo_depth = 8;
size_t rx_remaining = len, tx_remaining = len;
while (rx_remaining || tx_remaining) {
if (tx_remaining && spi_is_writable(spi) && rx_remaining < tx_remaining + fifo_depth) {
spi_get_hw(spi)->dr = (uint32_t) *src++;
--tx_remaining;
}
if (rx_remaining && spi_is_readable(spi)) {
*dst++ = (uint8_t) spi_get_hw(spi)->dr;
--rx_remaining;
}
}
return (int)len;
}
// Write len bytes directly from src to the SPI, and discard any data received back
int __not_in_flash_func(spi_write_blocking)(spi_inst_t *spi, const uint8_t *src, size_t len) {
invalid_params_if(SPI, 0 > (int)len);
// Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX
// is full, PL022 inhibits RX pushes, and sets a sticky flag on
// push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set.
for (size_t i = 0; i < len; ++i) {
while (!spi_is_writable(spi))
tight_loop_contents();
spi_get_hw(spi)->dr = (uint32_t)src[i];
}
// Drain RX FIFO, then wait for shifting to finish (which may be *after*
// TX FIFO drains), then drain RX FIFO again
while (spi_is_readable(spi))
(void)spi_get_hw(spi)->dr;
while (spi_get_hw(spi)->sr & SPI_SSPSR_BSY_BITS)
tight_loop_contents();
while (spi_is_readable(spi))
(void)spi_get_hw(spi)->dr;
// Don't leave overrun flag set
spi_get_hw(spi)->icr = SPI_SSPICR_RORIC_BITS;
return (int)len;
}
// Read len bytes directly from the SPI to dst.
// repeated_tx_data is output repeatedly on SO as data is read in from SI.
// Generally this can be 0, but some devices require a specific value here,
// e.g. SD cards expect 0xff
int __not_in_flash_func(spi_read_blocking)(spi_inst_t *spi, uint8_t repeated_tx_data, uint8_t *dst, size_t len) {
invalid_params_if(SPI, 0 > (int)len);
const size_t fifo_depth = 8;
size_t rx_remaining = len, tx_remaining = len;
while (rx_remaining || tx_remaining) {
if (tx_remaining && spi_is_writable(spi) && rx_remaining < tx_remaining + fifo_depth) {
spi_get_hw(spi)->dr = (uint32_t) repeated_tx_data;
--tx_remaining;
}
if (rx_remaining && spi_is_readable(spi)) {
*dst++ = (uint8_t) spi_get_hw(spi)->dr;
--rx_remaining;
}
}
return (int)len;
}
// Write len halfwords from src to SPI. Simultaneously read len halfwords from SPI to dst.
int __not_in_flash_func(spi_write16_read16_blocking)(spi_inst_t *spi, const uint16_t *src, uint16_t *dst, size_t len) {
invalid_params_if(SPI, 0 > (int)len);
// Never have more transfers in flight than will fit into the RX FIFO,
// else FIFO will overflow if this code is heavily interrupted.
const size_t fifo_depth = 8;
size_t rx_remaining = len, tx_remaining = len;
while (rx_remaining || tx_remaining) {
if (tx_remaining && spi_is_writable(spi) && rx_remaining < tx_remaining + fifo_depth) {
spi_get_hw(spi)->dr = (uint32_t) *src++;
--tx_remaining;
}
if (rx_remaining && spi_is_readable(spi)) {
*dst++ = (uint16_t) spi_get_hw(spi)->dr;
--rx_remaining;
}
}
return (int)len;
}
// Write len bytes directly from src to the SPI, and discard any data received back
int __not_in_flash_func(spi_write16_blocking)(spi_inst_t *spi, const uint16_t *src, size_t len) {
invalid_params_if(SPI, 0 > (int)len);
// Deliberately overflow FIFO, then clean up afterward, to minimise amount
// of APB polling required per halfword
for (size_t i = 0; i < len; ++i) {
while (!spi_is_writable(spi))
tight_loop_contents();
spi_get_hw(spi)->dr = (uint32_t)src[i];
}
while (spi_is_readable(spi))
(void)spi_get_hw(spi)->dr;
while (spi_get_hw(spi)->sr & SPI_SSPSR_BSY_BITS)
tight_loop_contents();
while (spi_is_readable(spi))
(void)spi_get_hw(spi)->dr;
// Don't leave overrun flag set
spi_get_hw(spi)->icr = SPI_SSPICR_RORIC_BITS;
return (int)len;
}
// Read len halfwords directly from the SPI to dst.
// repeated_tx_data is output repeatedly on SO as data is read in from SI.
int __not_in_flash_func(spi_read16_blocking)(spi_inst_t *spi, uint16_t repeated_tx_data, uint16_t *dst, size_t len) {
invalid_params_if(SPI, 0 > (int)len);
const size_t fifo_depth = 8;
size_t rx_remaining = len, tx_remaining = len;
while (rx_remaining || tx_remaining) {
if (tx_remaining && spi_is_writable(spi) && rx_remaining < tx_remaining + fifo_depth) {
spi_get_hw(spi)->dr = (uint32_t) repeated_tx_data;
--tx_remaining;
}
if (rx_remaining && spi_is_readable(spi)) {
*dst++ = (uint16_t) spi_get_hw(spi)->dr;
--rx_remaining;
}
}
return (int)len;
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_sync/CMakeLists.txt
================================================
pico_simple_hardware_target(sync)
================================================
FILE: pico-sdk/src/rp2_common/hardware_sync/include/hardware/sync.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_SYNC_H
#define _HARDWARE_SYNC_H
#include "pico.h"
#include "hardware/address_mapped.h"
#include "hardware/regs/sio.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file hardware/sync.h
* \defgroup hardware_sync hardware_sync
*
* Low level hardware spin locks, barrier and processor event APIs
*
* Spin Locks
* ----------
*
* The RP2040 provides 32 hardware spin locks, which can be used to manage mutually-exclusive access to shared software
* and hardware resources.
*
* Generally each spin lock itself is a shared resource,
* i.e. the same hardware spin lock can be used by multiple higher level primitives (as long as the spin locks are neither held for long periods, nor
* held concurrently with other spin locks by the same core - which could lead to deadlock). A hardware spin lock that is exclusively owned can be used
* individually without more flexibility and without regard to other software. Note that no hardware spin lock may
* be acquired re-entrantly (i.e. hardware spin locks are not on their own safe for use by both thread code and IRQs) however the default spinlock related
* methods here (e.g. \ref spin_lock_blocking) always disable interrupts while the lock is held as use by IRQ handlers and user code is common/desirable,
* and spin locks are only expected to be held for brief periods.
*
* The SDK uses the following default spin lock assignments, classifying which spin locks are reserved for exclusive/special purposes
* vs those suitable for more general shared use:
*
* Number (ID) | Description
* :---------: | -----------
* 0-13 | Currently reserved for exclusive use by the SDK and other libraries. If you use these spin locks, you risk breaking SDK or other library functionality. Each reserved spin lock used individually has its own PICO_SPINLOCK_ID so you can search for those.
* 14,15 | (\ref PICO_SPINLOCK_ID_OS1 and \ref PICO_SPINLOCK_ID_OS2). Currently reserved for exclusive use by an operating system (or other system level software) co-existing with the SDK.
* 16-23 | (\ref PICO_SPINLOCK_ID_STRIPED_FIRST - \ref PICO_SPINLOCK_ID_STRIPED_LAST). Spin locks from this range are assigned in a round-robin fashion via \ref next_striped_spin_lock_num(). These spin locks are shared, but assigning numbers from a range reduces the probability that two higher level locking primitives using _striped_ spin locks will actually be using the same spin lock.
* 24-31 | (\ref PICO_SPINLOCK_ID_CLAIM_FREE_FIRST - \ref PICO_SPINLOCK_ID_CLAIM_FREE_LAST). These are reserved for exclusive use and are allocated on a first come first served basis at runtime via \ref spin_lock_claim_unused()
*/
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_SYNC, Enable/disable assertions in the HW sync module, type=bool, default=0, group=hardware_sync
#ifndef PARAM_ASSERTIONS_ENABLED_SYNC
#define PARAM_ASSERTIONS_ENABLED_SYNC 0
#endif
/** \brief A spin lock identifier
* \ingroup hardware_sync
*/
typedef volatile uint32_t spin_lock_t;
// PICO_CONFIG: PICO_SPINLOCK_ID_IRQ, Spinlock ID for IRQ protection, min=0, max=31, default=9, group=hardware_sync
#ifndef PICO_SPINLOCK_ID_IRQ
#define PICO_SPINLOCK_ID_IRQ 9
#endif
// PICO_CONFIG: PICO_SPINLOCK_ID_TIMER, Spinlock ID for Timer protection, min=0, max=31, default=10, group=hardware_sync
#ifndef PICO_SPINLOCK_ID_TIMER
#define PICO_SPINLOCK_ID_TIMER 10
#endif
// PICO_CONFIG: PICO_SPINLOCK_ID_HARDWARE_CLAIM, Spinlock ID for Hardware claim protection, min=0, max=31, default=11, group=hardware_sync
#ifndef PICO_SPINLOCK_ID_HARDWARE_CLAIM
#define PICO_SPINLOCK_ID_HARDWARE_CLAIM 11
#endif
// PICO_CONFIG: PICO_SPINLOCK_ID_RAND, Spinlock ID for Random Number Generator, min=0, max=31, default=12, group=hardware_sync
#ifndef PICO_SPINLOCK_ID_RAND
#define PICO_SPINLOCK_ID_RAND 12
#endif
// PICO_CONFIG: PICO_SPINLOCK_ID_OS1, First Spinlock ID reserved for use by low level OS style software, min=0, max=31, default=14, group=hardware_sync
#ifndef PICO_SPINLOCK_ID_OS1
#define PICO_SPINLOCK_ID_OS1 14
#endif
// PICO_CONFIG: PICO_SPINLOCK_ID_OS2, Second Spinlock ID reserved for use by low level OS style software, min=0, max=31, default=15, group=hardware_sync
#ifndef PICO_SPINLOCK_ID_OS2
#define PICO_SPINLOCK_ID_OS2 15
#endif
// PICO_CONFIG: PICO_SPINLOCK_ID_STRIPED_FIRST, Lowest Spinlock ID in the 'striped' range, min=0, max=31, default=16, group=hardware_sync
#ifndef PICO_SPINLOCK_ID_STRIPED_FIRST
#define PICO_SPINLOCK_ID_STRIPED_FIRST 16
#endif
// PICO_CONFIG: PICO_SPINLOCK_ID_STRIPED_LAST, Highest Spinlock ID in the 'striped' range, min=0, max=31, default=23, group=hardware_sync
#ifndef PICO_SPINLOCK_ID_STRIPED_LAST
#define PICO_SPINLOCK_ID_STRIPED_LAST 23
#endif
// PICO_CONFIG: PICO_SPINLOCK_ID_CLAIM_FREE_FIRST, Lowest Spinlock ID in the 'claim free' range, min=0, max=31, default=24, group=hardware_sync
#ifndef PICO_SPINLOCK_ID_CLAIM_FREE_FIRST
#define PICO_SPINLOCK_ID_CLAIM_FREE_FIRST 24
#endif
#ifdef PICO_SPINLOCK_ID_CLAIM_FREE_END
#warning PICO_SPINLOCK_ID_CLAIM_FREE_END has been renamed to PICO_SPINLOCK_ID_CLAIM_FREE_LAST
#endif
// PICO_CONFIG: PICO_SPINLOCK_ID_CLAIM_FREE_LAST, Highest Spinlock ID in the 'claim free' range, min=0, max=31, default=31, group=hardware_sync
#ifndef PICO_SPINLOCK_ID_CLAIM_FREE_LAST
#define PICO_SPINLOCK_ID_CLAIM_FREE_LAST 31
#endif
/*! \brief Insert a SEV instruction in to the code path.
* \ingroup hardware_sync
* The SEV (send event) instruction sends an event to both cores.
*/
#if !__has_builtin(__sev)
__force_inline static void __sev(void) {
pico_default_asm_volatile ("sev");
}
#endif
/*! \brief Insert a WFE instruction in to the code path.
* \ingroup hardware_sync
*
* The WFE (wait for event) instruction waits until one of a number of
* events occurs, including events signalled by the SEV instruction on either core.
*/
#if !__has_builtin(__wfe)
__force_inline static void __wfe(void) {
pico_default_asm_volatile ("wfe");
}
#endif
/*! \brief Insert a WFI instruction in to the code path.
* \ingroup hardware_sync
*
* The WFI (wait for interrupt) instruction waits for a interrupt to wake up the core.
*/
#if !__has_builtin(__wfi)
__force_inline static void __wfi(void) {
pico_default_asm_volatile("wfi");
}
#endif
/*! \brief Insert a DMB instruction in to the code path.
* \ingroup hardware_sync
*
* The DMB (data memory barrier) acts as a memory barrier, all memory accesses prior to this
* instruction will be observed before any explicit access after the instruction.
*/
__force_inline static void __dmb(void) {
pico_default_asm_volatile("dmb" : : : "memory");
}
/*! \brief Insert a DSB instruction in to the code path.
* \ingroup hardware_sync
*
* The DSB (data synchronization barrier) acts as a special kind of data
* memory barrier (DMB). The DSB operation completes when all explicit memory
* accesses before this instruction complete.
*/
__force_inline static void __dsb(void) {
pico_default_asm_volatile("dsb" : : : "memory");
}
/*! \brief Insert a ISB instruction in to the code path.
* \ingroup hardware_sync
*
* ISB acts as an instruction synchronization barrier. It flushes the pipeline of the processor,
* so that all instructions following the ISB are fetched from cache or memory again, after
* the ISB instruction has been completed.
*/
__force_inline static void __isb(void) {
pico_default_asm_volatile("isb" ::: "memory");
}
/*! \brief Acquire a memory fence
* \ingroup hardware_sync
*/
__force_inline static void __mem_fence_acquire(void) {
// the original code below makes it hard for us to be included from C++ via a header
// which itself is in an extern "C", so just use __dmb instead, which is what
// is required on Cortex M0+
__dmb();
//#ifndef __cplusplus
// atomic_thread_fence(memory_order_acquire);
//#else
// std::atomic_thread_fence(std::memory_order_acquire);
//#endif
}
/*! \brief Release a memory fence
* \ingroup hardware_sync
*
*/
__force_inline static void __mem_fence_release(void) {
// the original code below makes it hard for us to be included from C++ via a header
// which itself is in an extern "C", so just use __dmb instead, which is what
// is required on Cortex M0+
__dmb();
//#ifndef __cplusplus
// atomic_thread_fence(memory_order_release);
//#else
// std::atomic_thread_fence(std::memory_order_release);
//#endif
}
/*! \brief Save and disable interrupts
* \ingroup hardware_sync
*
* \return The prior interrupt enable status for restoration later via restore_interrupts()
*/
__force_inline static uint32_t save_and_disable_interrupts(void) {
uint32_t status;
pico_default_asm_volatile(
"mrs %0, PRIMASK\n"
"cpsid i"
: "=r" (status) ::);
return status;
}
/*! \brief Restore interrupts to a specified state
* \ingroup hardware_sync
*
* \param status Previous interrupt status from save_and_disable_interrupts()
*/
__force_inline static void restore_interrupts(uint32_t status) {
pico_default_asm_volatile("msr PRIMASK,%0"::"r" (status) : );
}
/*! \brief Get HW Spinlock instance from number
* \ingroup hardware_sync
*
* \param lock_num Spinlock ID
* \return The spinlock instance
*/
__force_inline static spin_lock_t *spin_lock_instance(uint lock_num) {
invalid_params_if(SYNC, lock_num >= NUM_SPIN_LOCKS);
return (spin_lock_t *) (SIO_BASE + SIO_SPINLOCK0_OFFSET + lock_num * 4);
}
/*! \brief Get HW Spinlock number from instance
* \ingroup hardware_sync
*
* \param lock The Spinlock instance
* \return The Spinlock ID
*/
__force_inline static uint spin_lock_get_num(spin_lock_t *lock) {
invalid_params_if(SYNC, (uint) lock < SIO_BASE + SIO_SPINLOCK0_OFFSET ||
(uint) lock >= NUM_SPIN_LOCKS * sizeof(spin_lock_t) + SIO_BASE + SIO_SPINLOCK0_OFFSET ||
((uint) lock - SIO_BASE + SIO_SPINLOCK0_OFFSET) % sizeof(spin_lock_t) != 0);
return (uint) (lock - (spin_lock_t *) (SIO_BASE + SIO_SPINLOCK0_OFFSET));
}
/*! \brief Acquire a spin lock without disabling interrupts (hence unsafe)
* \ingroup hardware_sync
*
* \param lock Spinlock instance
*/
__force_inline static void spin_lock_unsafe_blocking(spin_lock_t *lock) {
// Note we don't do a wfe or anything, because by convention these spin_locks are VERY SHORT LIVED and NEVER BLOCK and run
// with INTERRUPTS disabled (to ensure that)... therefore nothing on our core could be blocking us, so we just need to wait on another core
// anyway which should be finished soon
while (__builtin_expect(!*lock, 0));
__mem_fence_acquire();
}
/*! \brief Release a spin lock without re-enabling interrupts
* \ingroup hardware_sync
*
* \param lock Spinlock instance
*/
__force_inline static void spin_unlock_unsafe(spin_lock_t *lock) {
__mem_fence_release();
*lock = 0;
}
/*! \brief Acquire a spin lock safely
* \ingroup hardware_sync
*
* This function will disable interrupts prior to acquiring the spinlock
*
* \param lock Spinlock instance
* \return interrupt status to be used when unlocking, to restore to original state
*/
__force_inline static uint32_t spin_lock_blocking(spin_lock_t *lock) {
uint32_t save = save_and_disable_interrupts();
spin_lock_unsafe_blocking(lock);
return save;
}
/*! \brief Check to see if a spinlock is currently acquired elsewhere.
* \ingroup hardware_sync
*
* \param lock Spinlock instance
*/
inline static bool is_spin_locked(spin_lock_t *lock) {
check_hw_size(spin_lock_t, 4);
uint lock_num = spin_lock_get_num(lock);
return 0 != (*(io_ro_32 *) (SIO_BASE + SIO_SPINLOCK_ST_OFFSET) & (1u << lock_num));
}
/*! \brief Release a spin lock safely
* \ingroup hardware_sync
*
* This function will re-enable interrupts according to the parameters.
*
* \param lock Spinlock instance
* \param saved_irq Return value from the \ref spin_lock_blocking() function.
*
* \sa spin_lock_blocking()
*/
__force_inline static void spin_unlock(spin_lock_t *lock, uint32_t saved_irq) {
spin_unlock_unsafe(lock);
restore_interrupts(saved_irq);
}
/*! \brief Initialise a spin lock
* \ingroup hardware_sync
*
* The spin lock is initially unlocked
*
* \param lock_num The spin lock number
* \return The spin lock instance
*/
spin_lock_t *spin_lock_init(uint lock_num);
/*! \brief Release all spin locks
* \ingroup hardware_sync
*/
void spin_locks_reset(void);
/*! \brief Return a spin lock number from the _striped_ range
* \ingroup hardware_sync
*
* Returns a spin lock number in the range PICO_SPINLOCK_ID_STRIPED_FIRST to PICO_SPINLOCK_ID_STRIPED_LAST
* in a round robin fashion. This does not grant the caller exclusive access to the spin lock, so the caller
* must:
*
* -# Abide (with other callers) by the contract of only holding this spin lock briefly (and with IRQs disabled - the default via \ref spin_lock_blocking()),
* and not whilst holding other spin locks.
* -# Be OK with any contention caused by the - brief due to the above requirement - contention with other possible users of the spin lock.
*
* \return lock_num a spin lock number the caller may use (non exclusively)
* \see PICO_SPINLOCK_ID_STRIPED_FIRST
* \see PICO_SPINLOCK_ID_STRIPED_LAST
*/
uint next_striped_spin_lock_num(void);
/*! \brief Mark a spin lock as used
* \ingroup hardware_sync
*
* Method for cooperative claiming of hardware. Will cause a panic if the spin lock
* is already claimed. Use of this method by libraries detects accidental
* configurations that would fail in unpredictable ways.
*
* \param lock_num the spin lock number
*/
void spin_lock_claim(uint lock_num);
/*! \brief Mark multiple spin locks as used
* \ingroup hardware_sync
*
* Method for cooperative claiming of hardware. Will cause a panic if any of the spin locks
* are already claimed. Use of this method by libraries detects accidental
* configurations that would fail in unpredictable ways.
*
* \param lock_num_mask Bitfield of all required spin locks to claim (bit 0 == spin lock 0, bit 1 == spin lock 1 etc)
*/
void spin_lock_claim_mask(uint32_t lock_num_mask);
/*! \brief Mark a spin lock as no longer used
* \ingroup hardware_sync
*
* Method for cooperative claiming of hardware.
*
* \param lock_num the spin lock number to release
*/
void spin_lock_unclaim(uint lock_num);
/*! \brief Claim a free spin lock
* \ingroup hardware_sync
*
* \param required if true the function will panic if none are available
* \return the spin lock number or -1 if required was false, and none were free
*/
int spin_lock_claim_unused(bool required);
/*! \brief Determine if a spin lock is claimed
* \ingroup hardware_sync
*
* \param lock_num the spin lock number
* \return true if claimed, false otherwise
* \see spin_lock_claim
* \see spin_lock_claim_mask
*/
bool spin_lock_is_claimed(uint lock_num);
// no longer use __mem_fence_acquire here, as it is overkill on cortex M0+
#define remove_volatile_cast(t, x) ({__compiler_memory_barrier(); Clang_Pragma("clang diagnostic push"); Clang_Pragma("clang diagnostic ignored \"-Wcast-qual\""); (t)(x); Clang_Pragma("clang diagnostic pop"); })
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_sync/sync.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "hardware/sync.h"
#include "hardware/claim.h"
static_assert(PICO_SPINLOCK_ID_STRIPED_LAST >= PICO_SPINLOCK_ID_STRIPED_FIRST, "");
static uint8_t striped_spin_lock_num = PICO_SPINLOCK_ID_STRIPED_FIRST;
static uint32_t claimed;
static void check_lock_num(uint __unused lock_num) {
invalid_params_if(SYNC, lock_num >= 32);
}
void spin_locks_reset(void) {
for (uint i = 0; i < NUM_SPIN_LOCKS; i++) {
spin_unlock_unsafe(spin_lock_instance(i));
}
}
spin_lock_t *spin_lock_init(uint lock_num) {
assert(lock_num < NUM_SPIN_LOCKS);
spin_lock_t *lock = spin_lock_instance(lock_num);
spin_unlock_unsafe(lock);
return lock;
}
uint next_striped_spin_lock_num() {
uint rc = striped_spin_lock_num++;
if (striped_spin_lock_num > PICO_SPINLOCK_ID_STRIPED_LAST) {
striped_spin_lock_num = PICO_SPINLOCK_ID_STRIPED_FIRST;
}
return rc;
}
void spin_lock_claim(uint lock_num) {
check_lock_num(lock_num);
hw_claim_or_assert((uint8_t *) &claimed, lock_num, "Spinlock %d is already claimed");
}
void spin_lock_claim_mask(uint32_t mask) {
for(uint i = 0; mask; i++, mask >>= 1u) {
if (mask & 1u) spin_lock_claim(i);
}
}
void spin_lock_unclaim(uint lock_num) {
check_lock_num(lock_num);
spin_unlock_unsafe(spin_lock_instance(lock_num));
hw_claim_clear((uint8_t *) &claimed, lock_num);
}
int spin_lock_claim_unused(bool required) {
return hw_claim_unused_from_range((uint8_t*)&claimed, required, PICO_SPINLOCK_ID_CLAIM_FREE_FIRST, PICO_SPINLOCK_ID_CLAIM_FREE_LAST, "No spinlocks are available");
}
bool spin_lock_is_claimed(uint lock_num) {
check_lock_num(lock_num);
return hw_is_claimed((uint8_t *) &claimed, lock_num);
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_timer/CMakeLists.txt
================================================
pico_simple_hardware_target(timer)
pico_mirrored_target_link_libraries(hardware_timer INTERFACE hardware_claim hardware_irq)
================================================
FILE: pico-sdk/src/rp2_common/hardware_timer/include/hardware/timer.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_TIMER_H
#define _HARDWARE_TIMER_H
#include "pico.h"
#include "hardware/structs/timer.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file hardware/timer.h
* \defgroup hardware_timer hardware_timer
*
* Low-level hardware timer API
*
* This API provides medium level access to the timer HW.
* See also \ref pico_time which provides higher levels functionality using the hardware timer.
*
* The timer peripheral on RP2040 supports the following features:
* - single 64-bit counter, incrementing once per microsecond
* - Latching two-stage read of counter, for race-free read over 32 bit bus
* - Four alarms: match on the lower 32 bits of counter, IRQ on match.
*
* By default the timer uses a one microsecond reference that is generated in the Watchdog (see Section 4.8.2) which is derived
* from the clk_ref.
*
* The timer has 4 alarms, and can output a separate interrupt for each alarm. The alarms match on the lower 32 bits of the 64
* bit counter which means they can be fired a maximum of 2^32 microseconds into the future. This is equivalent to:
* - 2^32 ÷ 10^6: ~4295 seconds
* - 4295 ÷ 60: ~72 minutes
*
* The timer is expected to be used for short sleeps, if you want a longer alarm see the \ref hardware_rtc functions.
*
* \subsection timer_example Example
* \addtogroup hardware_timer
*
* \include hello_timer.c
*
* \see pico_time
*/
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_TIMER, Enable/disable assertions in the timer module, type=bool, default=0, group=hardware_timer
#ifndef PARAM_ASSERTIONS_ENABLED_TIMER
#define PARAM_ASSERTIONS_ENABLED_TIMER 0
#endif
static inline void check_hardware_alarm_num_param(__unused uint alarm_num) {
invalid_params_if(TIMER, alarm_num >= NUM_TIMERS);
}
/*! \brief Return a 32 bit timestamp value in microseconds
* \ingroup hardware_timer
*
* Returns the low 32 bits of the hardware timer.
* \note This value wraps roughly every 1 hour 11 minutes and 35 seconds.
*
* \return the 32 bit timestamp
*/
static inline uint32_t time_us_32(void) {
return timer_hw->timerawl;
}
/*! \brief Return the current 64 bit timestamp value in microseconds
* \ingroup hardware_timer
*
* Returns the full 64 bits of the hardware timer. The \ref pico_time and other functions rely on the fact that this
* value monotonically increases from power up. As such it is expected that this value counts upwards and never wraps
* (we apologize for introducing a potential year 5851444 bug).
*
* \return the 64 bit timestamp
*/
uint64_t time_us_64(void);
/*! \brief Busy wait wasting cycles for the given (32 bit) number of microseconds
* \ingroup hardware_timer
*
* \param delay_us delay amount in microseconds
*/
void busy_wait_us_32(uint32_t delay_us);
/*! \brief Busy wait wasting cycles for the given (64 bit) number of microseconds
* \ingroup hardware_timer
*
* \param delay_us delay amount in microseconds
*/
void busy_wait_us(uint64_t delay_us);
/*! \brief Busy wait wasting cycles for the given number of milliseconds
* \ingroup hardware_timer
*
* \param delay_ms delay amount in milliseconds
*/
void busy_wait_ms(uint32_t delay_ms);
/*! \brief Busy wait wasting cycles until after the specified timestamp
* \ingroup hardware_timer
*
* \param t Absolute time to wait until
*/
void busy_wait_until(absolute_time_t t);
/*! \brief Check if the specified timestamp has been reached
* \ingroup hardware_timer
*
* \param t Absolute time to compare against current time
* \return true if it is now after the specified timestamp
*/
static inline bool time_reached(absolute_time_t t) {
uint64_t target = to_us_since_boot(t);
uint32_t hi_target = (uint32_t)(target >> 32u);
uint32_t hi = timer_hw->timerawh;
return (hi >= hi_target && (timer_hw->timerawl >= (uint32_t) target || hi != hi_target));
}
/*! Callback function type for hardware alarms
* \ingroup hardware_timer
*
* \param alarm_num the hardware alarm number
* \sa hardware_alarm_set_callback()
*/
typedef void (*hardware_alarm_callback_t)(uint alarm_num);
/*! \brief cooperatively claim the use of this hardware alarm_num
* \ingroup hardware_timer
*
* This method hard asserts if the hardware alarm is currently claimed.
*
* \param alarm_num the hardware alarm to claim
* \sa hardware_claiming
*/
void hardware_alarm_claim(uint alarm_num);
/*! \brief cooperatively claim the use of this hardware alarm_num
* \ingroup hardware_timer
*
* This method attempts to claim an unused hardware alarm
*
* \return alarm_num the hardware alarm claimed or -1 if requires was false, and none are available
* \sa hardware_claiming
*/
int hardware_alarm_claim_unused(bool required);
/*! \brief cooperatively release the claim on use of this hardware alarm_num
* \ingroup hardware_timer
*
* \param alarm_num the hardware alarm to unclaim
* \sa hardware_claiming
*/
void hardware_alarm_unclaim(uint alarm_num);
/*! \brief Determine if a hardware alarm has been claimed
* \ingroup hardware_timer
*
* \param alarm_num the hardware alarm number
* \return true if claimed, false otherwise
* \see hardware_alarm_claim
*/
bool hardware_alarm_is_claimed(uint alarm_num);
/*! \brief Enable/Disable a callback for a hardware timer on this core
* \ingroup hardware_timer
*
* This method enables/disables the alarm IRQ for the specified hardware alarm on the
* calling core, and set the specified callback to be associated with that alarm.
*
* This callback will be used for the timeout set via hardware_alarm_set_target
*
* \note This will install the handler on the current core if the IRQ handler isn't already set.
* Therefore the user has the opportunity to call this up from the core of their choice
*
* \param alarm_num the hardware alarm number
* \param callback the callback to install, or NULL to unset
*
* \sa hardware_alarm_set_target()
*/
void hardware_alarm_set_callback(uint alarm_num, hardware_alarm_callback_t callback);
/**
* \brief Set the current target for the specified hardware alarm
* \ingroup hardware_timer
*
* This will replace any existing target
*
* @param alarm_num the hardware alarm number
* @param t the target timestamp
* @return true if the target was "missed"; i.e. it was in the past, or occurred before a future hardware timeout could be set
*/
bool hardware_alarm_set_target(uint alarm_num, absolute_time_t t);
/**
* \brief Cancel an existing target (if any) for a given hardware_alarm
* \ingroup hardware_timer
*
* @param alarm_num the hardware alarm number
*/
void hardware_alarm_cancel(uint alarm_num);
/**
* \brief Force and IRQ for a specific hardware alarm
* \ingroup hardware_timer
*
* This method will forcibly make sure the current alarm callback (if present) for the hardware
* alarm is called from an IRQ context after this call. If an actual callback is due at the same
* time then the callback may only be called once.
*
* Calling this method does not otherwise interfere with regular callback operations.
*
* @param alarm_num the hardware alarm number
*/
void hardware_alarm_force_irq(uint alarm_num);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_timer/timer.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "hardware/timer.h"
#include "hardware/irq.h"
#include "hardware/sync.h"
#include "hardware/claim.h"
check_hw_layout(timer_hw_t, ints, TIMER_INTS_OFFSET);
static hardware_alarm_callback_t alarm_callbacks[NUM_TIMERS];
static uint32_t target_hi[NUM_TIMERS];
static uint8_t timer_callbacks_pending;
static_assert(NUM_TIMERS <= 4, "");
static uint8_t claimed;
void hardware_alarm_claim(uint alarm_num) {
check_hardware_alarm_num_param(alarm_num);
hw_claim_or_assert(&claimed, alarm_num, "Hardware alarm %d already claimed");
}
void hardware_alarm_unclaim(uint alarm_num) {
check_hardware_alarm_num_param(alarm_num);
hw_claim_clear(&claimed, alarm_num);
}
bool hardware_alarm_is_claimed(uint alarm_num) {
check_hardware_alarm_num_param(alarm_num);
return hw_is_claimed(&claimed, alarm_num);
}
int hardware_alarm_claim_unused(bool required) {
return hw_claim_unused_from_range(&claimed, required, 0, NUM_TIMERS - 1, "No timers available");
}
/// tag::time_us_64[]
uint64_t time_us_64() {
// Need to make sure that the upper 32 bits of the timer
// don't change, so read that first
uint32_t hi = timer_hw->timerawh;
uint32_t lo;
do {
// Read the lower 32 bits
lo = timer_hw->timerawl;
// Now read the upper 32 bits again and
// check that it hasn't incremented. If it has loop around
// and read the lower 32 bits again to get an accurate value
uint32_t next_hi = timer_hw->timerawh;
if (hi == next_hi) break;
hi = next_hi;
} while (true);
return ((uint64_t) hi << 32u) | lo;
}
/// end::time_us_64[]
/// \tag::busy_wait[]
void busy_wait_us_32(uint32_t delay_us) {
if (0 <= (int32_t)delay_us) {
// we only allow 31 bits, otherwise we could have a race in the loop below with
// values very close to 2^32
uint32_t start = timer_hw->timerawl;
while (timer_hw->timerawl - start < delay_us) {
tight_loop_contents();
}
} else {
busy_wait_us(delay_us);
}
}
void busy_wait_us(uint64_t delay_us) {
uint64_t base = time_us_64();
uint64_t target = base + delay_us;
if (target < base) {
target = (uint64_t)-1;
}
absolute_time_t t;
update_us_since_boot(&t, target);
busy_wait_until(t);
}
void busy_wait_ms(uint32_t delay_ms)
{
if (delay_ms <= 0x7fffffffu / 1000) {
busy_wait_us_32(delay_ms * 1000);
} else {
busy_wait_us(delay_ms * 1000ull);
}
}
void busy_wait_until(absolute_time_t t) {
uint64_t target = to_us_since_boot(t);
uint32_t hi_target = (uint32_t)(target >> 32u);
uint32_t hi = timer_hw->timerawh;
while (hi < hi_target) {
hi = timer_hw->timerawh;
tight_loop_contents();
}
while (hi == hi_target && timer_hw->timerawl < (uint32_t) target) {
hi = timer_hw->timerawh;
tight_loop_contents();
}
}
/// \end::busy_wait[]
static inline uint harware_alarm_irq_number(uint alarm_num) {
return TIMER_IRQ_0 + alarm_num;
}
static void hardware_alarm_irq_handler(void) {
// Determine which timer this IRQ is for
uint alarm_num = __get_current_exception() - VTABLE_FIRST_IRQ - TIMER_IRQ_0;
check_hardware_alarm_num_param(alarm_num);
hardware_alarm_callback_t callback = NULL;
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
uint32_t save = spin_lock_blocking(lock);
// Clear the timer IRQ (inside lock, because we check whether we have handled the IRQ yet in alarm_set by looking at the interrupt status
timer_hw->intr = 1u << alarm_num;
// Clear any forced IRQ
hw_clear_bits(&timer_hw->intf, 1u << alarm_num);
// make sure the IRQ is still valid
if (timer_callbacks_pending & (1u << alarm_num)) {
// Now check whether we have a timer event to handle that isn't already obsolete (this could happen if we
// were already in the IRQ handler before someone else changed the timer setup
if (timer_hw->timerawh >= target_hi[alarm_num]) {
// we have reached the right high word as well as low word value
callback = alarm_callbacks[alarm_num];
timer_callbacks_pending &= (uint8_t)~(1u << alarm_num);
} else {
// try again in 2^32 us
timer_hw->alarm[alarm_num] = timer_hw->alarm[alarm_num]; // re-arm the timer
}
}
spin_unlock(lock, save);
if (callback) {
callback(alarm_num);
}
}
void hardware_alarm_set_callback(uint alarm_num, hardware_alarm_callback_t callback) {
// todo check current core owner
// note this should probably be subsumed by irq_set_exclusive_handler anyway, since that
// should disallow IRQ handlers on both cores
check_hardware_alarm_num_param(alarm_num);
uint irq_num = harware_alarm_irq_number(alarm_num);
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
uint32_t save = spin_lock_blocking(lock);
if (callback) {
if (hardware_alarm_irq_handler != irq_get_vtable_handler(irq_num)) {
// note that set_exclusive will silently allow you to set the handler to the same thing
// since it is idempotent, which means we don't need to worry about locking ourselves
irq_set_exclusive_handler(irq_num, hardware_alarm_irq_handler);
irq_set_enabled(irq_num, true);
// Enable interrupt in block and at processor
hw_set_bits(&timer_hw->inte, 1u << alarm_num);
}
alarm_callbacks[alarm_num] = callback;
} else {
alarm_callbacks[alarm_num] = NULL;
timer_callbacks_pending &= (uint8_t)~(1u << alarm_num);
irq_remove_handler(irq_num, hardware_alarm_irq_handler);
irq_set_enabled(irq_num, false);
}
spin_unlock(lock, save);
}
bool hardware_alarm_set_target(uint alarm_num, absolute_time_t target) {
bool missed;
uint64_t now = time_us_64();
uint64_t t = to_us_since_boot(target);
if (now >= t) {
missed = true;
} else {
missed = false;
// 1) actually set the hardware timer
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
uint32_t save = spin_lock_blocking(lock);
uint8_t old_timer_callbacks_pending = timer_callbacks_pending;
timer_callbacks_pending |= (uint8_t)(1u << alarm_num);
timer_hw->intr = 1u << alarm_num; // clear any IRQ
timer_hw->alarm[alarm_num] = (uint32_t) t;
// Set the alarm. Writing time should arm it
target_hi[alarm_num] = (uint32_t)(t >> 32u);
// 2) check for races
if (!(timer_hw->armed & 1u << alarm_num)) {
// not armed, so has already fired .. IRQ must be pending (we are still under lock)
assert(timer_hw->ints & 1u << alarm_num);
} else {
if (time_us_64() >= t) {
// we are already at or past the right time; there is no point in us racing against the IRQ
// we are about to generate. note however that, if there was already a timer pending before,
// then we still let the IRQ fire, as whatever it was, is not handled by our setting missed=true here
missed = true;
if (timer_callbacks_pending != old_timer_callbacks_pending) {
// disarm the timer
timer_hw->armed = 1u << alarm_num;
// clear the IRQ...
timer_hw->intr = 1u << alarm_num;
// ... including anything pending on the processor - perhaps unnecessary, but
// our timer flag says we aren't expecting anything.
irq_clear(harware_alarm_irq_number(alarm_num));
// and clear our flag so that if the IRQ handler is already active (because it is on
// the other core) it will also skip doing anything
timer_callbacks_pending = old_timer_callbacks_pending;
}
}
}
spin_unlock(lock, save);
// note at this point any pending timer IRQ can likely run
}
return missed;
}
void hardware_alarm_cancel(uint alarm_num) {
check_hardware_alarm_num_param(alarm_num);
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
uint32_t save = spin_lock_blocking(lock);
timer_hw->armed = 1u << alarm_num;
timer_callbacks_pending &= (uint8_t)~(1u << alarm_num);
spin_unlock(lock, save);
}
void hardware_alarm_force_irq(uint alarm_num) {
check_hardware_alarm_num_param(alarm_num);
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
uint32_t save = spin_lock_blocking(lock);
timer_callbacks_pending |= (uint8_t)(1u << alarm_num);
spin_unlock(lock, save);
hw_set_bits(&timer_hw->intf, 1u << alarm_num);
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_uart/CMakeLists.txt
================================================
pico_simple_hardware_target(uart)
pico_mirrored_target_link_libraries(hardware_uart INTERFACE hardware_resets hardware_clocks)
================================================
FILE: pico-sdk/src/rp2_common/hardware_uart/include/hardware/uart.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_UART_H
#define _HARDWARE_UART_H
#include "pico.h"
#include "hardware/structs/uart.h"
#include "hardware/regs/dreq.h"
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_UART, Enable/disable assertions in the UART module, type=bool, default=0, group=hardware_uart
#ifndef PARAM_ASSERTIONS_ENABLED_UART
#define PARAM_ASSERTIONS_ENABLED_UART 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
// PICO_CONFIG: PICO_UART_ENABLE_CRLF_SUPPORT, Enable/disable CR/LF translation support, type=bool, default=1, group=hardware_uart
#ifndef PICO_UART_ENABLE_CRLF_SUPPORT
#define PICO_UART_ENABLE_CRLF_SUPPORT 1
#endif
// PICO_CONFIG: PICO_UART_DEFAULT_CRLF, Enable/disable CR/LF translation on UART, type=bool, default=0, depends=PICO_UART_ENABLE_CRLF_SUPPORT, group=hardware_uart
#ifndef PICO_UART_DEFAULT_CRLF
#define PICO_UART_DEFAULT_CRLF 0
#endif
// PICO_CONFIG: PICO_DEFAULT_UART, Define the default UART used for printf etc, min=0, max=1, group=hardware_uart
// PICO_CONFIG: PICO_DEFAULT_UART_TX_PIN, Define the default UART TX pin, min=0, max=29, group=hardware_uart
// PICO_CONFIG: PICO_DEFAULT_UART_RX_PIN, Define the default UART RX pin, min=0, max=29, group=hardware_uart
// PICO_CONFIG: PICO_DEFAULT_UART_BAUD_RATE, Define the default UART baudrate, max=921600, default=115200, group=hardware_uart
#ifndef PICO_DEFAULT_UART_BAUD_RATE
#define PICO_DEFAULT_UART_BAUD_RATE 115200 ///< Default baud rate
#endif
/** \file hardware/uart.h
* \defgroup hardware_uart hardware_uart
*
* Hardware UART API
*
* RP2040 has 2 identical instances of a UART peripheral, based on the ARM PL011. Each UART can be connected to a number
* of GPIO pins as defined in the GPIO muxing.
*
* Only the TX, RX, RTS, and CTS signals are
* connected, meaning that the modem mode and IrDA mode of the PL011 are not supported.
*
* \subsection uart_example Example
* \addtogroup hardware_uart
*
* \code
* int main() {
*
* // Initialise UART 0
* uart_init(uart0, 115200);
*
* // Set the GPIO pin mux to the UART - 0 is TX, 1 is RX
* gpio_set_function(0, GPIO_FUNC_UART);
* gpio_set_function(1, GPIO_FUNC_UART);
*
* uart_puts(uart0, "Hello world!");
* }
* \endcode
*/
// Currently always a pointer to hw but it might not be in the future
typedef struct uart_inst uart_inst_t;
/** The UART identifiers for use in UART functions.
*
* e.g. uart_init(uart1, 48000)
*
* \ingroup hardware_uart
* @{
*/
#define uart0 ((uart_inst_t *)uart0_hw) ///< Identifier for UART instance 0
#define uart1 ((uart_inst_t *)uart1_hw) ///< Identifier for UART instance 1
/** @} */
#if !defined(PICO_DEFAULT_UART_INSTANCE) && defined(PICO_DEFAULT_UART)
#define PICO_DEFAULT_UART_INSTANCE (__CONCAT(uart,PICO_DEFAULT_UART))
#endif
#ifdef PICO_DEFAULT_UART_INSTANCE
#define uart_default PICO_DEFAULT_UART_INSTANCE
#endif
/*! \brief Convert UART instance to hardware instance number
* \ingroup hardware_uart
*
* \param uart UART instance
* \return Number of UART, 0 or 1.
*/
static inline uint uart_get_index(uart_inst_t *uart) {
invalid_params_if(UART, uart != uart0 && uart != uart1);
return uart == uart1 ? 1 : 0;
}
static inline uart_inst_t *uart_get_instance(uint instance) {
static_assert(NUM_UARTS == 2, "");
invalid_params_if(UART, instance >= NUM_UARTS);
return instance ? uart1 : uart0;
}
static inline uart_hw_t *uart_get_hw(uart_inst_t *uart) {
uart_get_index(uart); // check it is a hw uart
return (uart_hw_t *)uart;
}
/** \brief UART Parity enumeration
* \ingroup hardware_uart
*/
typedef enum {
UART_PARITY_NONE,
UART_PARITY_EVEN,
UART_PARITY_ODD
} uart_parity_t;
// ----------------------------------------------------------------------------
// Setup
/*! \brief Initialise a UART
* \ingroup hardware_uart
*
* Put the UART into a known state, and enable it. Must be called before other
* functions.
*
* This function always enables the FIFOs, and configures the UART for the
* following default line format:
*
* - 8 data bits
* - No parity bit
* - One stop bit
*
* \note There is no guarantee that the baudrate requested will be possible, the nearest will be chosen,
* and this function will return the configured baud rate.
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param baudrate Baudrate of UART in Hz
* \return Actual set baudrate
*/
uint uart_init(uart_inst_t *uart, uint baudrate);
/*! \brief DeInitialise a UART
* \ingroup hardware_uart
*
* Disable the UART if it is no longer used. Must be reinitialised before
* being used again.
*
* \param uart UART instance. \ref uart0 or \ref uart1
*/
void uart_deinit(uart_inst_t *uart);
/*! \brief Set UART baud rate
* \ingroup hardware_uart
*
* Set baud rate as close as possible to requested, and return actual rate selected.
*
* The UART is paused for around two character periods whilst the settings are
* changed. Data received during this time may be dropped by the UART.
*
* Any characters still in the transmit buffer will be sent using the new
* updated baud rate. uart_tx_wait_blocking() can be called before this
* function to ensure all characters at the old baud rate have been sent
* before the rate is changed.
*
* This function should not be called from an interrupt context, and the UART
* interrupt should be disabled before calling this function.
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param baudrate Baudrate in Hz
* \return Actual set baudrate
*/
uint uart_set_baudrate(uart_inst_t *uart, uint baudrate);
/*! \brief Set UART flow control CTS/RTS
* \ingroup hardware_uart
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param cts If true enable flow control of TX by clear-to-send input
* \param rts If true enable assertion of request-to-send output by RX flow control
*/
static inline void uart_set_hw_flow(uart_inst_t *uart, bool cts, bool rts) {
hw_write_masked(&uart_get_hw(uart)->cr,
(bool_to_bit(cts) << UART_UARTCR_CTSEN_LSB) | (bool_to_bit(rts) << UART_UARTCR_RTSEN_LSB),
UART_UARTCR_RTSEN_BITS | UART_UARTCR_CTSEN_BITS);
}
/*! \brief Set UART data format
* \ingroup hardware_uart
*
* Configure the data format (bits etc) for the UART.
*
* The UART is paused for around two character periods whilst the settings are
* changed. Data received during this time may be dropped by the UART.
*
* Any characters still in the transmit buffer will be sent using the new
* updated data format. uart_tx_wait_blocking() can be called before this
* function to ensure all characters needing the old format have been sent
* before the format is changed.
*
* This function should not be called from an interrupt context, and the UART
* interrupt should be disabled before calling this function.
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param data_bits Number of bits of data. 5..8
* \param stop_bits Number of stop bits 1..2
* \param parity Parity option.
*/
void uart_set_format(uart_inst_t *uart, uint data_bits, uint stop_bits, uart_parity_t parity);
/*! \brief Setup UART interrupts
* \ingroup hardware_uart
*
* Enable the UART's interrupt output. An interrupt handler will need to be installed prior to calling
* this function.
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param rx_has_data If true an interrupt will be fired when the RX FIFO contains data.
* \param tx_needs_data If true an interrupt will be fired when the TX FIFO needs data.
*/
static inline void uart_set_irq_enables(uart_inst_t *uart, bool rx_has_data, bool tx_needs_data) {
// Both UARTRXINTR (RX) and UARTRTINTR (RX timeout) interrupts are
// required for rx_has_data. RX asserts when >=4 characters are in the RX
// FIFO (for RXIFLSEL=0). RT asserts when there are >=1 characters and no
// more have been received for 32 bit periods.
uart_get_hw(uart)->imsc = (bool_to_bit(tx_needs_data) << UART_UARTIMSC_TXIM_LSB) |
(bool_to_bit(rx_has_data) << UART_UARTIMSC_RXIM_LSB) |
(bool_to_bit(rx_has_data) << UART_UARTIMSC_RTIM_LSB);
if (rx_has_data) {
// Set minimum threshold
hw_write_masked(&uart_get_hw(uart)->ifls, 0 << UART_UARTIFLS_RXIFLSEL_LSB,
UART_UARTIFLS_RXIFLSEL_BITS);
}
if (tx_needs_data) {
// Set maximum threshold
hw_write_masked(&uart_get_hw(uart)->ifls, 0 << UART_UARTIFLS_TXIFLSEL_LSB,
UART_UARTIFLS_TXIFLSEL_BITS);
}
}
/*! \brief Test if specific UART is enabled
* \ingroup hardware_uart
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \return true if the UART is enabled
*/
static inline bool uart_is_enabled(uart_inst_t *uart) {
return !!(uart_get_hw(uart)->cr & UART_UARTCR_UARTEN_BITS);
}
/*! \brief Enable/Disable the FIFOs on specified UART
* \ingroup hardware_uart
*
* The UART is paused for around two character periods whilst the settings are
* changed. Data received during this time may be dropped by the UART.
*
* Any characters still in the transmit FIFO will be lost if the FIFO is
* disabled. uart_tx_wait_blocking() can be called before this
* function to avoid this.
*
* This function should not be called from an interrupt context, and the UART
* interrupt should be disabled when calling this function.
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param enabled true to enable FIFO (default), false to disable
*/
void uart_set_fifo_enabled(uart_inst_t *uart, bool enabled);
// ----------------------------------------------------------------------------
// Generic input/output
/*! \brief Determine if space is available in the TX FIFO
* \ingroup hardware_uart
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \return false if no space available, true otherwise
*/
static inline bool uart_is_writable(uart_inst_t *uart) {
return !(uart_get_hw(uart)->fr & UART_UARTFR_TXFF_BITS);
}
/*! \brief Wait for the UART TX fifo to be drained
* \ingroup hardware_uart
*
* \param uart UART instance. \ref uart0 or \ref uart1
*/
static inline void uart_tx_wait_blocking(uart_inst_t *uart) {
while (uart_get_hw(uart)->fr & UART_UARTFR_BUSY_BITS) tight_loop_contents();
}
/*! \brief Determine whether data is waiting in the RX FIFO
* \ingroup hardware_uart
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \return true if the RX FIFO is not empty, otherwise false.
*
*/
static inline bool uart_is_readable(uart_inst_t *uart) {
// PL011 doesn't expose levels directly, so return values are only 0 or 1
return !(uart_get_hw(uart)->fr & UART_UARTFR_RXFE_BITS);
}
/*! \brief Write to the UART for transmission.
* \ingroup hardware_uart
*
* This function will block until all the data has been sent to the UART
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param src The bytes to send
* \param len The number of bytes to send
*/
static inline void uart_write_blocking(uart_inst_t *uart, const uint8_t *src, size_t len) {
for (size_t i = 0; i < len; ++i) {
while (!uart_is_writable(uart))
tight_loop_contents();
uart_get_hw(uart)->dr = *src++;
}
}
/*! \brief Read from the UART
* \ingroup hardware_uart
*
* This function blocks until len characters have been read from the UART
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param dst Buffer to accept received bytes
* \param len The number of bytes to receive.
*/
static inline void uart_read_blocking(uart_inst_t *uart, uint8_t *dst, size_t len) {
for (size_t i = 0; i < len; ++i) {
while (!uart_is_readable(uart))
tight_loop_contents();
*dst++ = (uint8_t) uart_get_hw(uart)->dr;
}
}
// ----------------------------------------------------------------------------
// UART-specific operations and aliases
/*! \brief Write single character to UART for transmission.
* \ingroup hardware_uart
*
* This function will block until the entire character has been sent
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param c The character to send
*/
static inline void uart_putc_raw(uart_inst_t *uart, char c) {
uart_write_blocking(uart, (const uint8_t *) &c, 1);
}
/*! \brief Write single character to UART for transmission, with optional CR/LF conversions
* \ingroup hardware_uart
*
* This function will block until the character has been sent
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param c The character to send
*/
static inline void uart_putc(uart_inst_t *uart, char c) {
#if PICO_UART_ENABLE_CRLF_SUPPORT
extern short uart_char_to_line_feed[NUM_UARTS];
if (uart_char_to_line_feed[uart_get_index(uart)] == c)
uart_putc_raw(uart, '\r');
#endif
uart_putc_raw(uart, c);
}
/*! \brief Write string to UART for transmission, doing any CR/LF conversions
* \ingroup hardware_uart
*
* This function will block until the entire string has been sent
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param s The null terminated string to send
*/
static inline void uart_puts(uart_inst_t *uart, const char *s) {
#if PICO_UART_ENABLE_CRLF_SUPPORT
bool last_was_cr = false;
while (*s) {
// Don't add extra carriage returns if one is present
if (last_was_cr)
uart_putc_raw(uart, *s);
else
uart_putc(uart, *s);
last_was_cr = *s++ == '\r';
}
#else
while (*s)
uart_putc(uart, *s++);
#endif
}
/*! \brief Read a single character from the UART
* \ingroup hardware_uart
*
* This function will block until a character has been read
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \return The character read.
*/
static inline char uart_getc(uart_inst_t *uart) {
char c;
uart_read_blocking(uart, (uint8_t *) &c, 1);
return c;
}
/*! \brief Assert a break condition on the UART transmission.
* \ingroup hardware_uart
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param en Assert break condition (TX held low) if true. Clear break condition if false.
*/
void uart_set_break(uart_inst_t *uart, bool en);
/*! \brief Set CR/LF conversion on UART
* \ingroup hardware_uart
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param translate If true, convert line feeds to carriage return on transmissions
*/
void uart_set_translate_crlf(uart_inst_t *uart, bool translate);
/*! \brief Wait for the default UART's TX FIFO to be drained
* \ingroup hardware_uart
*/
static inline void uart_default_tx_wait_blocking(void) {
#ifdef uart_default
uart_tx_wait_blocking(uart_default);
#else
assert(false);
#endif
}
/*! \brief Wait for up to a certain number of microseconds for the RX FIFO to be non empty
* \ingroup hardware_uart
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param us the number of microseconds to wait at most (may be 0 for an instantaneous check)
* \return true if the RX FIFO became non empty before the timeout, false otherwise
*/
bool uart_is_readable_within_us(uart_inst_t *uart, uint32_t us);
/*! \brief Return the DREQ to use for pacing transfers to/from a particular UART instance
* \ingroup hardware_uart
*
* \param uart UART instance. \ref uart0 or \ref uart1
* \param is_tx true for sending data to the UART instance, false for receiving data from the UART instance
*/
static inline uint uart_get_dreq(uart_inst_t *uart, bool is_tx) {
static_assert(DREQ_UART0_RX == DREQ_UART0_TX + 1, "");
static_assert(DREQ_UART1_RX == DREQ_UART1_TX + 1, "");
static_assert(DREQ_UART1_TX == DREQ_UART0_TX + 2, "");
return DREQ_UART0_TX + uart_get_index(uart) * 2 + !is_tx;
}
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_uart/uart.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "hardware/address_mapped.h"
#include "hardware/platform_defs.h"
#include "hardware/uart.h"
#include "hardware/structs/uart.h"
#include "hardware/resets.h"
#include "hardware/clocks.h"
#include "hardware/timer.h"
#include "pico/assert.h"
#include "pico.h"
check_hw_layout(uart_hw_t, fr, UART_UARTFR_OFFSET);
check_hw_layout(uart_hw_t, dmacr, UART_UARTDMACR_OFFSET);
#if PICO_UART_ENABLE_CRLF_SUPPORT
short uart_char_to_line_feed[NUM_UARTS];
#endif
/// \tag::uart_reset[]
static inline void uart_reset(uart_inst_t *uart) {
invalid_params_if(UART, uart != uart0 && uart != uart1);
reset_block(uart_get_index(uart) ? RESETS_RESET_UART1_BITS : RESETS_RESET_UART0_BITS);
}
static inline void uart_unreset(uart_inst_t *uart) {
invalid_params_if(UART, uart != uart0 && uart != uart1);
unreset_block_wait(uart_get_index(uart) ? RESETS_RESET_UART1_BITS : RESETS_RESET_UART0_BITS);
}
/// \end::uart_reset[]
/// \tag::uart_init[]
uint uart_init(uart_inst_t *uart, uint baudrate) {
invalid_params_if(UART, uart != uart0 && uart != uart1);
if (clock_get_hz(clk_peri) == 0) {
return 0;
}
uart_reset(uart);
uart_unreset(uart);
#if PICO_UART_ENABLE_CRLF_SUPPORT
uart_set_translate_crlf(uart, PICO_UART_DEFAULT_CRLF);
#endif
// Any LCR writes need to take place before enabling the UART
uint baud = uart_set_baudrate(uart, baudrate);
uart_set_format(uart, 8, 1, UART_PARITY_NONE);
// Enable FIFOs (must be before setting UARTEN, as this is an LCR access)
hw_set_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_FEN_BITS);
// Enable the UART, both TX and RX
uart_get_hw(uart)->cr = UART_UARTCR_UARTEN_BITS | UART_UARTCR_TXE_BITS | UART_UARTCR_RXE_BITS;
// Always enable DREQ signals -- no harm in this if DMA is not listening
uart_get_hw(uart)->dmacr = UART_UARTDMACR_TXDMAE_BITS | UART_UARTDMACR_RXDMAE_BITS;
return baud;
}
/// \end::uart_init[]
void uart_deinit(uart_inst_t *uart) {
invalid_params_if(UART, uart != uart0 && uart != uart1);
uart_reset(uart);
}
static uint32_t uart_disable_before_lcr_write(uart_inst_t *uart) {
// Notes from PL011 reference manual:
//
// - Before writing the LCR, if the UART is enabled it needs to be
// disabled and any current TX + RX activity has to be completed
//
// - There is a BUSY flag which waits for the current TX char, but this is
// OR'd with TX FIFO !FULL, so not usable when FIFOs are enabled and
// potentially nonempty
//
// - FIFOs can't be set to disabled whilst a character is in progress
// (else "FIFO integrity is not guaranteed")
//
// Combination of these means there is no general way to halt and poll for
// end of TX character, if FIFOs may be enabled. Either way, there is no
// way to poll for end of RX character.
//
// So, insert a 15 Baud period delay before changing the settings.
// 15 Baud is comfortably higher than start + max data + parity + stop.
// Anything else would require API changes to permit a non-enabled UART
// state after init() where settings can be changed safely.
uint32_t cr_save = uart_get_hw(uart)->cr;
if (cr_save & UART_UARTCR_UARTEN_BITS) {
hw_clear_bits(&uart_get_hw(uart)->cr,
UART_UARTCR_UARTEN_BITS | UART_UARTCR_TXE_BITS | UART_UARTCR_RXE_BITS);
uint32_t current_ibrd = uart_get_hw(uart)->ibrd;
uint32_t current_fbrd = uart_get_hw(uart)->fbrd;
// Note: Maximise precision here. Show working, the compiler will mop this up.
// Create a 16.6 fixed-point fractional division ratio; then scale to 32-bits.
uint32_t brdiv_ratio = 64u * current_ibrd + current_fbrd;
brdiv_ratio <<= 10;
// 3662 is ~(15 * 244.14) where 244.14 is 16e6 / 2^16
uint32_t scaled_freq = clock_get_hz(clk_peri) / 3662ul;
uint32_t wait_time_us = brdiv_ratio / scaled_freq;
busy_wait_us(wait_time_us);
}
return cr_save;
}
static void uart_write_lcr_bits_masked(uart_inst_t *uart, uint32_t values, uint32_t write_mask) {
invalid_params_if(UART, uart != uart0 && uart != uart1);
// (Potentially) Cleanly handle disabling the UART before touching LCR
uint32_t cr_save = uart_disable_before_lcr_write(uart);
hw_write_masked(&uart_get_hw(uart)->lcr_h, values, write_mask);
uart_get_hw(uart)->cr = cr_save;
}
/// \tag::uart_set_baudrate[]
uint uart_set_baudrate(uart_inst_t *uart, uint baudrate) {
invalid_params_if(UART, baudrate == 0);
uint32_t baud_rate_div = (8 * clock_get_hz(clk_peri) / baudrate);
uint32_t baud_ibrd = baud_rate_div >> 7;
uint32_t baud_fbrd;
if (baud_ibrd == 0) {
baud_ibrd = 1;
baud_fbrd = 0;
} else if (baud_ibrd >= 65535) {
baud_ibrd = 65535;
baud_fbrd = 0;
} else {
baud_fbrd = ((baud_rate_div & 0x7f) + 1) / 2;
}
uart_get_hw(uart)->ibrd = baud_ibrd;
uart_get_hw(uart)->fbrd = baud_fbrd;
// PL011 needs a (dummy) LCR_H write to latch in the divisors.
// We don't want to actually change LCR_H contents here.
uart_write_lcr_bits_masked(uart, 0, 0);
// See datasheet
return (4 * clock_get_hz(clk_peri)) / (64 * baud_ibrd + baud_fbrd);
}
/// \end::uart_set_baudrate[]
void uart_set_format(uart_inst_t *uart, uint data_bits, uint stop_bits, uart_parity_t parity) {
invalid_params_if(UART, data_bits < 5 || data_bits > 8);
invalid_params_if(UART, stop_bits != 1 && stop_bits != 2);
invalid_params_if(UART, parity != UART_PARITY_NONE && parity != UART_PARITY_EVEN && parity != UART_PARITY_ODD);
uart_write_lcr_bits_masked(uart,
((data_bits - 5u) << UART_UARTLCR_H_WLEN_LSB) |
((stop_bits - 1u) << UART_UARTLCR_H_STP2_LSB) |
(bool_to_bit(parity != UART_PARITY_NONE) << UART_UARTLCR_H_PEN_LSB) |
(bool_to_bit(parity == UART_PARITY_EVEN) << UART_UARTLCR_H_EPS_LSB),
UART_UARTLCR_H_WLEN_BITS |
UART_UARTLCR_H_STP2_BITS |
UART_UARTLCR_H_PEN_BITS |
UART_UARTLCR_H_EPS_BITS);
}
void uart_set_fifo_enabled(uart_inst_t *uart, bool enabled) {
uint32_t lcr_h_fen_bits = 0;
if (enabled) {
lcr_h_fen_bits = UART_UARTLCR_H_FEN_BITS;
}
uart_write_lcr_bits_masked(uart, lcr_h_fen_bits, UART_UARTLCR_H_FEN_BITS);
}
void uart_set_break(uart_inst_t *uart, bool en) {
uint32_t lcr_h_brk_bits = 0;
if (en) {
lcr_h_brk_bits = UART_UARTLCR_H_BRK_BITS;
}
uart_write_lcr_bits_masked(uart, lcr_h_brk_bits, UART_UARTLCR_H_BRK_BITS);
}
void uart_set_translate_crlf(uart_inst_t *uart, bool crlf) {
#if PICO_UART_ENABLE_CRLF_SUPPORT
uart_char_to_line_feed[uart_get_index(uart)] = crlf ? '\n' : 0x100;
#else
panic_unsupported();
#endif
}
bool uart_is_readable_within_us(uart_inst_t *uart, uint32_t us) {
uint32_t t = time_us_32();
do {
if (uart_is_readable(uart)) {
return true;
}
} while ((time_us_32() - t) <= us);
return false;
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_vreg/CMakeLists.txt
================================================
pico_simple_hardware_target(vreg)
================================================
FILE: pico-sdk/src/rp2_common/hardware_vreg/include/hardware/vreg.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_VREG_H
#define _HARDWARE_VREG_H
#include "pico.h"
#include "hardware/structs/vreg_and_chip_reset.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file vreg.h
* \defgroup hardware_vreg hardware_vreg
*
* Voltage Regulation API
*
*/
/** Possible voltage values that can be applied to the regulator
*/
enum vreg_voltage {
VREG_VOLTAGE_0_85 = 0b0110, ///< 0.85v
VREG_VOLTAGE_0_90 = 0b0111, ///< 0.90v
VREG_VOLTAGE_0_95 = 0b1000, ///< 0.95v
VREG_VOLTAGE_1_00 = 0b1001, ///< 1.00v
VREG_VOLTAGE_1_05 = 0b1010, ///< 1.05v
VREG_VOLTAGE_1_10 = 0b1011, ///< 1.10v
VREG_VOLTAGE_1_15 = 0b1100, ///< 1.15v
VREG_VOLTAGE_1_20 = 0b1101, ///< 1.20v
VREG_VOLTAGE_1_25 = 0b1110, ///< 1.25v
VREG_VOLTAGE_1_30 = 0b1111, ///< 1.30v
VREG_VOLTAGE_MIN = VREG_VOLTAGE_0_85, ///< Always the minimum possible voltage
VREG_VOLTAGE_DEFAULT = VREG_VOLTAGE_1_10, ///< Default voltage on power up.
VREG_VOLTAGE_MAX = VREG_VOLTAGE_1_30, ///< Always the maximum possible voltage
};
/*! \brief Set voltage
* \ingroup hardware_vreg
*
* \param voltage The voltage (from enumeration \ref vreg_voltage) to apply to the voltage regulator
**/
void vreg_set_voltage(enum vreg_voltage voltage);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_vreg/vreg.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
#include "hardware/vreg.h"
void vreg_set_voltage(enum vreg_voltage voltage) {
hw_write_masked(&vreg_and_chip_reset_hw->vreg, ((uint)voltage) << VREG_AND_CHIP_RESET_VREG_VSEL_LSB, VREG_AND_CHIP_RESET_VREG_VSEL_BITS);
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_watchdog/CMakeLists.txt
================================================
pico_simple_hardware_target(watchdog)
================================================
FILE: pico-sdk/src/rp2_common/hardware_watchdog/include/hardware/watchdog.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_WATCHDOG_H
#define _HARDWARE_WATCHDOG_H
#include "pico.h"
#include "hardware/structs/watchdog.h"
/** \file hardware/watchdog.h
* \defgroup hardware_watchdog hardware_watchdog
*
* Hardware Watchdog Timer API
*
* Supporting functions for the Pico hardware watchdog timer.
*
* The RP2040 has a built in HW watchdog Timer. This is a countdown timer that can restart parts of the chip if it reaches zero.
* For example, this can be used to restart the processor if the software running on it gets stuck in an infinite loop
* or similar. The programmer has to periodically write a value to the watchdog to stop it reaching zero.
*
* \subsection watchdog_example Example
* \addtogroup hardware_watchdog
* \include hello_watchdog.c
*/
#ifdef __cplusplus
extern "C" {
#endif
/*! \brief Define actions to perform at watchdog timeout
* \ingroup hardware_watchdog
*
* \note If \ref watchdog_start_tick value does not give a 1MHz clock to the watchdog system, then the \p delay_ms
* parameter will not be in microseconds. See the datasheet for more details.
*
* By default the SDK assumes a 12MHz XOSC and sets the \ref watchdog_start_tick appropriately.
*
* \param pc If Zero, a standard boot will be performed, if non-zero this is the program counter to jump to on reset.
* \param sp If \p pc is non-zero, this will be the stack pointer used.
* \param delay_ms Initial load value. Maximum value 0x7fffff, approximately 8.3s.
*/
void watchdog_reboot(uint32_t pc, uint32_t sp, uint32_t delay_ms);
/*! \brief Start the watchdog tick
* \ingroup hardware_watchdog
*
* \param cycles This needs to be a divider that when applied to the XOSC input, produces a 1MHz clock. So if the XOSC is
* 12MHz, this will need to be 12.
*/
void watchdog_start_tick(uint cycles);
/*! \brief Reload the watchdog counter with the amount of time set in watchdog_enable
* \ingroup hardware_watchdog
*
*/
void watchdog_update(void);
/**
* \brief Enable the watchdog
* \ingroup hardware_watchdog
*
* \note If \ref watchdog_start_tick value does not give a 1MHz clock to the watchdog system, then the \p delay_ms
* parameter will not be in microseconds. See the datasheet for more details.
*
* By default the SDK assumes a 12MHz XOSC and sets the \ref watchdog_start_tick appropriately.
*
* This method sets a marker in the watchdog scratch register 4 that is checked by \ref watchdog_enable_caused_reboot.
* If the device is subsequently reset via a call to watchdog_reboot (including for example by dragging a UF2
* onto the RPI-RP2), then this value will be cleared, and so \ref watchdog_enable_caused_reboot will
* return false.
*
* \param delay_ms Number of milliseconds before watchdog will reboot without watchdog_update being called. Maximum of 0x7fffff, which is approximately 8.3 seconds
* \param pause_on_debug If the watchdog should be paused when the debugger is stepping through code
*/
void watchdog_enable(uint32_t delay_ms, bool pause_on_debug);
/**
* \brief Did the watchdog cause the last reboot?
* \ingroup hardware_watchdog
*
* @return true If the watchdog timer or a watchdog force caused the last reboot
* @return false If there has been no watchdog reboot since the last power on reset. A power on reset is typically caused by a power cycle or the run pin (reset button) being toggled.
*/
bool watchdog_caused_reboot(void);
/**
* \brief Did watchdog_enable cause the last reboot?
* \ingroup hardware_watchdog
*
* Perform additional checking along with \ref watchdog_caused_reboot to determine if a watchdog timeout initiated by
* \ref watchdog_enable caused the last reboot.
*
* This method checks for a special value in watchdog scratch register 4 placed there by \ref watchdog_enable.
* This would not be present if a watchdog reset is initiated by \ref watchdog_reboot or by the RP2040 bootrom
* (e.g. dragging a UF2 onto the RPI-RP2 drive).
*
* @return true If the watchdog timer or a watchdog force caused (see \ref watchdog_caused_reboot) the last reboot
* and the watchdog reboot happened after \ref watchdog_enable was called
* @return false If there has been no watchdog reboot since the last power on reset, or the watchdog reboot was not caused
* by a watchdog timeout after \ref watchdog_enable was called.
* A power on reset is typically caused by a power cycle or the run pin (reset button) being toggled.
*/
bool watchdog_enable_caused_reboot(void);
/**
* @brief Returns the number of microseconds before the watchdog will reboot the chip.
* \ingroup hardware_watchdog
*
* @return The number of microseconds before the watchdog will reboot the chip.
*/
uint32_t watchdog_get_count(void);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_watchdog/watchdog.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
#include "hardware/watchdog.h"
#include "hardware/structs/watchdog.h"
#include "hardware/structs/psm.h"
/// \tag::watchdog_start_tick[]
void watchdog_start_tick(uint cycles) {
// Important: This function also provides a tick reference to the timer
watchdog_hw->tick = cycles | WATCHDOG_TICK_ENABLE_BITS;
}
/// \end::watchdog_start_tick[]
// Value to load when updating the watchdog
// tag::watchdog_update[]
static uint32_t load_value;
void watchdog_update(void) {
watchdog_hw->load = load_value;
}
// end::watchdog_update[]
uint32_t watchdog_get_count(void) {
return (watchdog_hw->ctrl & WATCHDOG_CTRL_TIME_BITS) / 2 ;
}
// tag::watchdog_enable[]
// Helper function used by both watchdog_enable and watchdog_reboot
void _watchdog_enable(uint32_t delay_ms, bool pause_on_debug) {
hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
// Reset everything apart from ROSC and XOSC
hw_set_bits(&psm_hw->wdsel, PSM_WDSEL_BITS & ~(PSM_WDSEL_ROSC_BITS | PSM_WDSEL_XOSC_BITS));
uint32_t dbg_bits = WATCHDOG_CTRL_PAUSE_DBG0_BITS |
WATCHDOG_CTRL_PAUSE_DBG1_BITS |
WATCHDOG_CTRL_PAUSE_JTAG_BITS;
if (pause_on_debug) {
hw_set_bits(&watchdog_hw->ctrl, dbg_bits);
} else {
hw_clear_bits(&watchdog_hw->ctrl, dbg_bits);
}
if (!delay_ms) {
hw_set_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_TRIGGER_BITS);
} else {
// Note, we have x2 here as the watchdog HW currently decrements twice per tick
load_value = delay_ms * 1000 * 2;
if (load_value > 0xffffffu)
load_value = 0xffffffu;
watchdog_update();
hw_set_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
}
}
// end::watchdog_enable[]
#define WATCHDOG_NON_REBOOT_MAGIC 0x6ab73121
void watchdog_enable(uint32_t delay_ms, bool pause_on_debug) {
// update scratch[4] to distinguish from magic used for reboot to specific address, or 0 used to reboot
// into regular flash path
watchdog_hw->scratch[4] = WATCHDOG_NON_REBOOT_MAGIC;
_watchdog_enable(delay_ms, pause_on_debug);
}
void watchdog_reboot(uint32_t pc, uint32_t sp, uint32_t delay_ms) {
check_hw_layout(watchdog_hw_t, scratch[7], WATCHDOG_SCRATCH7_OFFSET);
// Clear enable before setting up scratch registers
hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
if (pc) {
pc |= 1u; // thumb mode
watchdog_hw->scratch[4] = 0xb007c0d3;
watchdog_hw->scratch[5] = pc ^ -0xb007c0d3;
watchdog_hw->scratch[6] = sp;
watchdog_hw->scratch[7] = pc;
// printf("rebooting %08x/%08x in %dms...\n", (uint) pc, (uint) sp, (uint) delay_ms);
} else {
watchdog_hw->scratch[4] = 0;
// printf("rebooting (regular)) in %dms...\n", (uint) delay_ms);
}
// Don't pause watchdog for debug
_watchdog_enable(delay_ms, 0);
}
bool watchdog_caused_reboot(void) {
// If any reason bits are set this is true
return watchdog_hw->reason;
}
bool watchdog_enable_caused_reboot(void) {
return watchdog_hw->reason && watchdog_hw->scratch[4] == WATCHDOG_NON_REBOOT_MAGIC;
}
================================================
FILE: pico-sdk/src/rp2_common/hardware_xosc/CMakeLists.txt
================================================
pico_simple_hardware_target(xosc)
pico_mirrored_target_link_libraries(hardware_xosc INTERFACE hardware_clocks)
================================================
FILE: pico-sdk/src/rp2_common/hardware_xosc/include/hardware/xosc.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_XOSC_H
#define _HARDWARE_XOSC_H
#include "pico.h"
#include "hardware/structs/xosc.h"
// Allow lengthening startup delay to accommodate slow-starting oscillators
// PICO_CONFIG: PICO_XOSC_STARTUP_DELAY_MULTIPLIER, Multiplier to lengthen xosc startup delay to accommodate slow-starting oscillators, type=int, min=1, default=1, group=hardware_xosc
#ifndef PICO_XOSC_STARTUP_DELAY_MULTIPLIER
#define PICO_XOSC_STARTUP_DELAY_MULTIPLIER 1
#endif
#ifdef __cplusplus
extern "C" {
#endif
/** \file hardware/xosc.h
* \defgroup hardware_xosc hardware_xosc
*
* Crystal Oscillator (XOSC) API
*/
/*! \brief Initialise the crystal oscillator system
* \ingroup hardware_xosc
*
* This function will block until the crystal oscillator has stabilised.
**/
void xosc_init(void);
/*! \brief Disable the Crystal oscillator
* \ingroup hardware_xosc
*
* Turns off the crystal oscillator source, and waits for it to become unstable
**/
void xosc_disable(void);
/*! \brief Set the crystal oscillator system to dormant
* \ingroup hardware_xosc
*
* Turns off the crystal oscillator until it is woken by an interrupt. This will block and hence
* the entire system will stop, until an interrupt wakes it up. This function will
* continue to block until the oscillator becomes stable after its wakeup.
**/
void xosc_dormant(void);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/hardware_xosc/xosc.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
// For frequency related definitions etc
#include "hardware/clocks.h"
#include "hardware/platform_defs.h"
#include "hardware/regs/xosc.h"
#include "hardware/xosc.h"
#if XOSC_KHZ < (1 * KHZ) || XOSC_KHZ > (50 * KHZ)
// Note: Although an external clock can be supplied up to 50 MHz, the maximum frequency the
// XOSC cell is specified to work with a crystal is less, please see the RP2040 Datasheet.
#error XOSC_KHZ must be in the range 1,000-50,000KHz i.e. 1-50MHz XOSC frequency
#endif
#define STARTUP_DELAY (((XOSC_KHZ + 128) / 256) * PICO_XOSC_STARTUP_DELAY_MULTIPLIER)
// The DELAY field in xosc_hw->startup is 14 bits wide.
#if STARTUP_DELAY >= (1 << 13)
#error PICO_XOSC_STARTUP_DELAY_MULTIPLIER is too large: XOSC STARTUP.DELAY must be < 8192
#endif
void xosc_init(void) {
// Assumes 1-15 MHz input, checked above.
xosc_hw->ctrl = XOSC_CTRL_FREQ_RANGE_VALUE_1_15MHZ;
// Set xosc startup delay
xosc_hw->startup = STARTUP_DELAY;
// Set the enable bit now that we have set freq range and startup delay
hw_set_bits(&xosc_hw->ctrl, XOSC_CTRL_ENABLE_VALUE_ENABLE << XOSC_CTRL_ENABLE_LSB);
// Wait for XOSC to be stable
while(!(xosc_hw->status & XOSC_STATUS_STABLE_BITS));
}
void xosc_disable(void) {
uint32_t tmp = xosc_hw->ctrl;
tmp &= (~XOSC_CTRL_ENABLE_BITS);
tmp |= (XOSC_CTRL_ENABLE_VALUE_DISABLE << XOSC_CTRL_ENABLE_LSB);
xosc_hw->ctrl = tmp;
// Wait for stable to go away
while(xosc_hw->status & XOSC_STATUS_STABLE_BITS);
}
void xosc_dormant(void) {
// WARNING: This stops the xosc until woken up by an irq
xosc_hw->dormant = XOSC_DORMANT_VALUE_DORMANT;
// Wait for it to become stable once woken up
while(!(xosc_hw->status & XOSC_STATUS_STABLE_BITS));
}
================================================
FILE: pico-sdk/src/rp2_common/pico_async_context/CMakeLists.txt
================================================
pico_add_library(pico_async_context_base NOFLAG)
target_include_directories(pico_async_context_base_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_sources(pico_async_context_base INTERFACE
${CMAKE_CURRENT_LIST_DIR}/async_context_base.c
)
pico_mirrored_target_link_libraries(pico_async_context_base INTERFACE pico_platform)
pico_add_library(pico_async_context_poll)
target_sources(pico_async_context_poll INTERFACE
${CMAKE_CURRENT_LIST_DIR}/async_context_poll.c
)
pico_mirrored_target_link_libraries(pico_async_context_poll INTERFACE pico_async_context_base)
pico_add_library(pico_async_context_threadsafe_background)
target_sources(pico_async_context_threadsafe_background INTERFACE
${CMAKE_CURRENT_LIST_DIR}/async_context_threadsafe_background.c
)
pico_mirrored_target_link_libraries(pico_async_context_threadsafe_background INTERFACE pico_async_context_base)
pico_add_library(pico_async_context_freertos)
target_sources(pico_async_context_freertos INTERFACE
${CMAKE_CURRENT_LIST_DIR}/async_context_freertos.c
)
pico_mirrored_target_link_libraries(pico_async_context_freertos INTERFACE pico_async_context_base)
================================================
FILE: pico-sdk/src/rp2_common/pico_async_context/async_context_base.c
================================================
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/async_context_base.h"
bool async_context_base_add_at_time_worker(async_context_t *self, async_at_time_worker_t *worker) {
async_at_time_worker_t **prev = &self->at_time_list;
while (*prev) {
if (worker == *prev) {
return false;
}
prev = &(*prev)->next;
}
*prev = worker;
worker->next = NULL;
return true;
}
bool async_context_base_remove_at_time_worker(async_context_t *self, async_at_time_worker_t *worker) {
async_at_time_worker_t **prev = &self->at_time_list;
while (*prev) {
if (worker == *prev) {
*prev = worker->next;
return true;
}
prev = &(*prev)->next;
}
return false;
}
bool async_context_base_add_when_pending_worker(async_context_t *self, async_when_pending_worker_t *worker) {
async_when_pending_worker_t **prev = &self->when_pending_list;
while (*prev) {
if (worker == *prev) {
return false;
}
prev = &(*prev)->next;
}
*prev = worker;
worker->next = NULL;
return true;
}
bool async_context_base_remove_when_pending_worker(async_context_t *self, async_when_pending_worker_t *worker) {
async_when_pending_worker_t **prev = &self->when_pending_list;
while (*prev) {
if (worker == *prev) {
*prev = worker->next;
return true;
}
prev = &(*prev)->next;
}
return false;
}
async_at_time_worker_t *async_context_base_remove_ready_at_time_worker(async_context_t *self) {
async_at_time_worker_t **best_prev = NULL;
if (self->at_time_list) {
absolute_time_t earliest = get_absolute_time();
for (async_at_time_worker_t **prev = &self->at_time_list; *prev; prev = &(*prev)->next) {
if (absolute_time_diff_us((*prev)->next_time, earliest) >= 0) {
earliest = (*prev)->next_time;
assert(!is_at_the_end_of_time(earliest)); // should never be less than now
best_prev = prev;
}
}
}
async_at_time_worker_t *rc;
if (best_prev) {
rc = *best_prev;
*best_prev = rc->next;
} else {
rc = NULL;
}
return rc;
}
void async_context_base_refresh_next_timeout(async_context_t *self) {
absolute_time_t earliest = at_the_end_of_time;
for (async_at_time_worker_t *worker = self->at_time_list; worker; ) {
async_at_time_worker_t *next = worker->next;
if (absolute_time_diff_us(worker->next_time, earliest) > 0) {
earliest = worker->next_time;
}
worker = next;
}
self->next_time = earliest;
}
absolute_time_t async_context_base_execute_once(async_context_t *self) {
async_at_time_worker_t *at_time_worker;
while (NULL != (at_time_worker = async_context_base_remove_ready_at_time_worker(self))) {
at_time_worker->do_work(self, at_time_worker);
}
for(async_when_pending_worker_t *when_pending_worker = self->when_pending_list; when_pending_worker; when_pending_worker = when_pending_worker->next) {
if (when_pending_worker->work_pending) {
when_pending_worker->work_pending = false;
when_pending_worker->do_work(self, when_pending_worker);
}
}
async_context_base_refresh_next_timeout(self);
return self->next_time;
}
bool async_context_base_needs_servicing(async_context_t *self) {
absolute_time_t now = get_absolute_time();
if (self->at_time_list) {
for (async_at_time_worker_t *worker = self->at_time_list; worker; worker = worker->next) {
if (absolute_time_diff_us(worker->next_time, now) >= 0) {
return true;
}
}
}
for(async_when_pending_worker_t *when_pending_worker = self->when_pending_list; when_pending_worker; when_pending_worker = when_pending_worker->next) {
if (when_pending_worker->work_pending) {
return true;
}
}
return false;
}
================================================
FILE: pico-sdk/src/rp2_common/pico_async_context/async_context_freertos.c
================================================
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
#include "pico/async_context_freertos.h"
#include "pico/async_context_base.h"
#include "pico/sync.h"
#include "hardware/irq.h"
#include "semphr.h"
#if configNUM_CORES > 1 && !defined(configUSE_CORE_AFFINITY)
#error async_context_freertos requires configUSE_CORE_AFFINITY under SMP
#endif
static const async_context_type_t template;
static void async_context_freertos_acquire_lock_blocking(async_context_t *self_base);
static void async_context_freertos_release_lock(async_context_t *self_base);
static void async_context_freertos_lock_check(async_context_t *self_base);
static TickType_t sensible_ticks_until(absolute_time_t until) {
TickType_t ticks;
int64_t delay_us = absolute_time_diff_us(get_absolute_time(), until);
if (delay_us <= 0) {
ticks = 0;
} else {
static const uint32_t max_delay = 60000000;
uint32_t delay_us_32 = delay_us > max_delay ? max_delay : (uint32_t) delay_us;
ticks = pdMS_TO_TICKS((delay_us_32+999)/1000);
// we want to round up, as both rounding down to zero is wrong (may produce no delays
// where delays are needed), but also we don't want to wake up, and then realize there
// is no work to do yet!
ticks++;
}
return ticks;
}
static void process_under_lock(async_context_freertos_t *self) {
#ifndef NDEBUG
async_context_freertos_lock_check(&self->core);
#endif
bool repeat;
do {
repeat = false;
absolute_time_t next_time = async_context_base_execute_once(&self->core);
TickType_t ticks;
if (is_at_the_end_of_time(next_time)) {
ticks = portMAX_DELAY;
} else {
ticks = sensible_ticks_until(next_time);
}
if (ticks) {
// last parameter (timeout) is also 'ticks', since there is no point waiting to change the period
// for longer than the period itself!
repeat = pdFALSE == xTimerChangePeriod(self->timer_handle, ticks, ticks);
} else {
repeat = true;
}
} while (repeat);
}
static void async_context_task(__unused void *vself) {
async_context_freertos_t *self = (async_context_freertos_t *)vself;
do {
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
if (self->task_should_exit) break;
async_context_freertos_acquire_lock_blocking(&self->core);
process_under_lock(self);
async_context_freertos_release_lock(&self->core);
__sev(); // it is possible regular code is waiting on a WFE on the other core
} while (!self->task_should_exit);
vTaskDelete(NULL);
}
static void async_context_freertos_wake_up(async_context_t *self_base) {
async_context_freertos_t *self = (async_context_freertos_t *)self_base;
if (self->task_handle) {
if (portCHECK_IF_IN_ISR()) {
vTaskNotifyGiveFromISR(self->task_handle, NULL);
xSemaphoreGiveFromISR(self->work_needed_sem, NULL);
} else {
// we don't want to wake ourselves up (we will only ever be called
// from the async_context_task if we own the lock, in which case processing
// will already happen when the lock is finally unlocked
if (xTaskGetCurrentTaskHandle() != self->task_handle) {
xTaskNotifyGive(self->task_handle);
xSemaphoreGive(self->work_needed_sem);
} else {
#ifndef NDEBUG
async_context_freertos_lock_check(self_base);
#endif
}
}
}
}
static void timer_handler(__unused TimerHandle_t handle)
{
async_context_freertos_t *self = (async_context_freertos_t *)pvTimerGetTimerID(handle);
async_context_freertos_wake_up(&self->core);
}
bool async_context_freertos_init(async_context_freertos_t *self, async_context_freertos_config_t *config) {
memset(self, 0, sizeof(*self));
self->core.type = &template;
self->core.flags = ASYNC_CONTEXT_FLAG_CALLBACK_FROM_NON_IRQ;
self->core.core_num = get_core_num();
self->lock_mutex = xSemaphoreCreateRecursiveMutex();
self->work_needed_sem = xSemaphoreCreateBinary();
self->timer_handle = xTimerCreate( "async_context_timer", // Just a text name, not used by the kernel.
portMAX_DELAY,
pdFALSE, // The timers will auto-reload themselves when they expire.
self,
timer_handler);
if (!self->lock_mutex ||
!self->work_needed_sem ||
!self->timer_handle ||
pdPASS != xTaskCreate(async_context_task, "async_context_task", config->task_stack_size, self,
config->task_priority, &self->task_handle)) {
async_context_deinit(&self->core);
return false;
}
#if configNUM_CORES > 1
UBaseType_t core_id = config->task_core_id;
if (core_id == (UBaseType_t)-1) {
core_id = portGET_CORE_ID();
}
// we must run on a single core
vTaskCoreAffinitySet(self->task_handle, 1u << core_id);
#endif
return true;
}
static uint32_t end_task_func(void *param) {
async_context_freertos_t *self = (async_context_freertos_t *)param;
// we will immediately exit
self->task_should_exit = true;
return 0;
}
void async_context_freertos_deinit(async_context_t *self_base) {
async_context_freertos_t *self = (async_context_freertos_t *)self_base;
if (self->task_handle) {
async_context_execute_sync(self_base, end_task_func, self_base);
}
if (self->timer_handle) {
xTimerDelete(self->timer_handle, 0);
}
if (self->lock_mutex) {
vSemaphoreDelete(self->lock_mutex);
}
if (self->work_needed_sem) {
vSemaphoreDelete(self->work_needed_sem);
}
memset(self, 0, sizeof(*self));
}
void async_context_freertos_acquire_lock_blocking(async_context_t *self_base) {
async_context_freertos_t *self = (async_context_freertos_t *)self_base;
// Lock the other core and stop low_prio_irq running
assert(!portCHECK_IF_IN_ISR());
xSemaphoreTakeRecursive(self->lock_mutex, portMAX_DELAY);
self->nesting++;
}
void async_context_freertos_lock_check(__unused async_context_t *self_base) {
#ifndef NDEBUG
async_context_freertos_t *self = (async_context_freertos_t *)self_base;
// Lock the other core and stop low_prio_irq running
assert(xSemaphoreGetMutexHolder(self->lock_mutex) == xTaskGetCurrentTaskHandle());
#endif
}
typedef struct sync_func_call{
async_when_pending_worker_t worker;
SemaphoreHandle_t sem;
uint32_t (*func)(void *param);
void *param;
uint32_t rc;
} sync_func_call_t;
static void handle_sync_func_call(async_context_t *context, async_when_pending_worker_t *worker) {
sync_func_call_t *call = (sync_func_call_t *)worker;
call->rc = call->func(call->param);
xSemaphoreGive(call->sem);
async_context_remove_when_pending_worker(context, worker);
}
uint32_t async_context_freertos_execute_sync(async_context_t *self_base, uint32_t (*func)(void *param), void *param) {
async_context_freertos_t *self = (async_context_freertos_t*)self_base;
hard_assert(xSemaphoreGetMutexHolder(self->lock_mutex) != xTaskGetCurrentTaskHandle());
sync_func_call_t call;
call.worker.do_work = handle_sync_func_call;
call.func = func;
call.param = param;
call.sem = xSemaphoreCreateBinary();
async_context_add_when_pending_worker(self_base, &call.worker);
async_context_set_work_pending(self_base, &call.worker);
xSemaphoreTake(call.sem, portMAX_DELAY);
vSemaphoreDelete(call.sem);
return call.rc;
}
void async_context_freertos_release_lock(async_context_t *self_base) {
async_context_freertos_t *self = (async_context_freertos_t *)self_base;
bool do_wakeup = false;
if (self->nesting == 1) {
// note that we always do a processing on outermost lock exit, to facilitate cases
// like lwIP where we have no notification when lwIP timers are added.
//
// this operation must be done from the right task
if (self->task_handle != xTaskGetCurrentTaskHandle()) {
// note we defer the wakeup until after we release the lock, otherwise it can be wasteful
// (waking up the task, but then having it block immediately on us)
do_wakeup = true;
} else {
process_under_lock(self);
}
}
--self->nesting;
xSemaphoreGiveRecursive(self->lock_mutex);
if (do_wakeup) {
async_context_freertos_wake_up(self_base);
}
}
static bool async_context_freertos_add_at_time_worker(async_context_t *self_base, async_at_time_worker_t *worker) {
async_context_freertos_acquire_lock_blocking(self_base);
bool rc = async_context_base_add_at_time_worker(self_base, worker);
async_context_freertos_release_lock(self_base);
return rc;
}
static bool async_context_freertos_remove_at_time_worker(async_context_t *self_base, async_at_time_worker_t *worker) {
async_context_freertos_acquire_lock_blocking(self_base);
bool rc = async_context_base_remove_at_time_worker(self_base, worker);
async_context_freertos_release_lock(self_base);
return rc;
}
static bool async_context_freertos_add_when_pending_worker(async_context_t *self_base, async_when_pending_worker_t *worker) {
async_context_freertos_acquire_lock_blocking(self_base);
bool rc = async_context_base_add_when_pending_worker(self_base, worker);
async_context_freertos_release_lock(self_base);
return rc;
}
static bool async_context_freertos_remove_when_pending_worker(async_context_t *self_base, async_when_pending_worker_t *worker) {
async_context_freertos_acquire_lock_blocking(self_base);
bool rc = async_context_base_remove_when_pending_worker(self_base, worker);
async_context_freertos_release_lock(self_base);
return rc;
}
static void async_context_freertos_set_work_pending(async_context_t *self_base, async_when_pending_worker_t *worker) {
worker->work_pending = true;
async_context_freertos_wake_up(self_base);
}
static void async_context_freertos_wait_until(async_context_t *self_base, absolute_time_t until) {
assert(!portCHECK_IF_IN_ISR());
TickType_t ticks = sensible_ticks_until(until);
vTaskDelay(ticks);
}
static void async_context_freertos_wait_for_work_until(async_context_t *self_base, absolute_time_t until) {
async_context_freertos_t *self = (async_context_freertos_t *)self_base;
assert(!portCHECK_IF_IN_ISR());
while (!time_reached(until)) {
TickType_t ticks = sensible_ticks_until(until);
if (!ticks || xSemaphoreTake(self->work_needed_sem, ticks)) return;
}
}
static const async_context_type_t template = {
.type = ASYNC_CONTEXT_FREERTOS,
.acquire_lock_blocking = async_context_freertos_acquire_lock_blocking,
.release_lock = async_context_freertos_release_lock,
.lock_check = async_context_freertos_lock_check,
.execute_sync = async_context_freertos_execute_sync,
.add_at_time_worker = async_context_freertos_add_at_time_worker,
.remove_at_time_worker = async_context_freertos_remove_at_time_worker,
.add_when_pending_worker = async_context_freertos_add_when_pending_worker,
.remove_when_pending_worker = async_context_freertos_remove_when_pending_worker,
.set_work_pending = async_context_freertos_set_work_pending,
.poll = 0,
.wait_until = async_context_freertos_wait_until,
.wait_for_work_until = async_context_freertos_wait_for_work_until,
.deinit = async_context_freertos_deinit,
};
================================================
FILE: pico-sdk/src/rp2_common/pico_async_context/async_context_poll.c
================================================
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
#include "pico/async_context_poll.h"
#include "pico/async_context_base.h"
#include "pico/sync.h"
static void noop(__unused async_context_t *context) { }
static const async_context_type_t template;
bool async_context_poll_init_with_defaults(async_context_poll_t *self) {
memset(self, 0, sizeof(*self));
self->core.core_num = get_core_num();
self->core.type = &template;
self->core.flags = ASYNC_CONTEXT_FLAG_POLLED | ASYNC_CONTEXT_FLAG_CALLBACK_FROM_NON_IRQ;
sem_init(&self->sem, 1, 1);
return true;
}
static void async_context_poll_wake_up(async_context_t *self_base) {
sem_release(&((async_context_poll_t *)self_base)->sem);
}
static void async_context_poll_requires_update(async_context_t *self_base, async_when_pending_worker_t *worker) {
worker->work_pending = true;
async_context_poll_wake_up(self_base);
}
static void async_context_poll_poll(async_context_t *self_base) {
async_context_base_execute_once(self_base);
}
static void async_context_poll_wait_until(__unused async_context_t *self_base, absolute_time_t until) {
sleep_until(until);
}
static void async_context_poll_wait_for_work_until(async_context_t *self_base, absolute_time_t until) {
absolute_time_t next_time = self_base->next_time;
async_context_poll_t *self = (async_context_poll_t *)self_base;
sem_acquire_block_until(&self->sem, absolute_time_min(next_time, until));
}
static void async_context_poll_lock_check(async_context_t *self_base) {
if (__get_current_exception() || get_core_num() != self_base->core_num) {
panic("async_context_poll context check failed (IRQ or wrong core)");
}
}
uint32_t async_context_poll_execute_sync(__unused async_context_t *context, uint32_t (*func)(void *param), void *param) {
return func(param);
}
static const async_context_type_t template = {
.type = ASYNC_CONTEXT_POLL,
.acquire_lock_blocking = noop,
.release_lock = noop,
.lock_check = async_context_poll_lock_check,
.execute_sync = async_context_poll_execute_sync,
.add_at_time_worker = async_context_base_add_at_time_worker,
.remove_at_time_worker = async_context_base_remove_at_time_worker,
.add_when_pending_worker = async_context_base_add_when_pending_worker,
.remove_when_pending_worker = async_context_base_remove_when_pending_worker,
.set_work_pending = async_context_poll_requires_update,
.poll = async_context_poll_poll,
.wait_until = async_context_poll_wait_until,
.wait_for_work_until = async_context_poll_wait_for_work_until,
.deinit = noop,
};
================================================
FILE: pico-sdk/src/rp2_common/pico_async_context/async_context_threadsafe_background.c
================================================
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
#include "pico/async_context_threadsafe_background.h"
#include "pico/async_context_base.h"
#include "pico/sync.h"
#include "hardware/irq.h"
static const async_context_type_t template;
static async_context_threadsafe_background_t *async_contexts_by_user_irq[NUM_USER_IRQS];
static void low_priority_irq_handler(void);
static void process_under_lock(async_context_threadsafe_background_t *self);
static int64_t alarm_handler(alarm_id_t id, void *user_data);
#ifndef ASYNC_CONTEXT_THREADSAFE_BACKGROUND_DEFAULT_LOW_PRIORITY_IRQ_HANDLER_PRIORITY
#define ASYNC_CONTEXT_THREADSAFE_BACKGROUND_DEFAULT_LOW_PRIORITY_IRQ_HANDLER_PRIORITY PICO_LOWEST_IRQ_PRIORITY
#endif
#ifndef ASYNC_CONTEXT_THREADSAFE_BACKGROUND_ALARM_POOL_MAX_ALARMS
#define ASYNC_CONTEXT_THREADSAFE_BACKGROUND_ALARM_POOL_MAX_ALARMS 4
#endif
async_context_threadsafe_background_config_t async_context_threadsafe_background_default_config(void) {
async_context_threadsafe_background_config_t config = {
.low_priority_irq_handler_priority = ASYNC_CONTEXT_THREADSAFE_BACKGROUND_DEFAULT_LOW_PRIORITY_IRQ_HANDLER_PRIORITY,
.custom_alarm_pool = NULL,
};
return config;
}
static inline uint recursive_mutex_enter_count(recursive_mutex_t *mutex) {
return mutex->enter_count;
}
static inline lock_owner_id_t recursive_mutex_owner(recursive_mutex_t *mutex) {
return mutex->owner;
}
static void async_context_threadsafe_background_wake_up(async_context_t *self_base) {
async_context_threadsafe_background_t *self = (async_context_threadsafe_background_t *)self_base;
#if ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
if (self_base->core_num == get_core_num()) {
// on same core, can dispatch directly
irq_set_pending(self->low_priority_irq_num);
} else {
// remove the existing alarm (it may have already fired) so we don't overflow the pool with repeats
//
// note that force_alarm_id is not protected here, however if we miss removing one, they will fire
// almost immediately anyway (since they were set in the past)
alarm_id_t force_alarm_id = self->force_alarm_id;
if (force_alarm_id > 0) {
alarm_pool_cancel_alarm(self->alarm_pool, force_alarm_id);
}
// we cause an early timeout (0 is always in the past) on the alarm_pool core
// note that by the time this returns, the timer may already have fired, so we
// may end up setting self->force_alarm_id to a stale timer id, but that is fine as we
// will harmlessly cancel it again next time
self->force_alarm_id = alarm_pool_add_alarm_at_force_in_context(self->alarm_pool, from_us_since_boot(0),
alarm_handler, self);
}
#else
// on same core, can dispatch directly
irq_set_pending(self->low_priority_irq_num);
#endif
sem_release(&self->work_needed_sem);
}
// Prevent background processing in pensv and access by the other core
// These methods are called in pensv context and on either core
// They can be called recursively
static inline void lock_acquire(async_context_threadsafe_background_t *self) {
// Lock the other core and stop low_prio_irq running
recursive_mutex_enter_blocking(&self->lock_mutex);
}
static void async_context_threadsafe_background_lock_check(async_context_t *self_base) {
async_context_threadsafe_background_t *self = (async_context_threadsafe_background_t *)self_base;
// Lock the other core and stop low_prio_irq running
if (recursive_mutex_enter_count(&self->lock_mutex) < 1 || recursive_mutex_owner(&self->lock_mutex) != lock_get_caller_owner_id()) {
panic_compact("async_context lock_check failed");
}
}
#if ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
typedef struct sync_func_call{
async_when_pending_worker_t worker;
semaphore_t sem;
uint32_t (*func)(void *param);
void *param;
uint32_t rc;
} sync_func_call_t;
static void handle_sync_func_call(async_context_t *context, async_when_pending_worker_t *worker) {
sync_func_call_t *call = (sync_func_call_t *)worker;
call->rc = call->func(call->param);
sem_release(&call->sem);
async_context_remove_when_pending_worker(context, worker);
}
#endif
static void lock_release(async_context_threadsafe_background_t *self) {
bool outermost = 1 == recursive_mutex_enter_count(&self->lock_mutex);
// note that it is *not* a requirement to have low_prio_irq_missed handled on the
// same core and in low-priority riq, as we are only *logically* a single thread. the user
// is already free to call from either core, and this would only happen on a different
// core, if the user *themselves* is acquiring the lock from other cores anyway
#if ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
bool wake_other_core = false;
#endif
if (outermost) {
// note that we always do a processing on outermost lock exit, to facilitate cases
// like lwIP where we have no notification when lwIP timers are added.
#if ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
if (self->core.core_num == get_core_num()) {
process_under_lock(self);
} else if (async_context_base_needs_servicing(&self->core)) {
// have to wake up other core
wake_other_core = true;
}
#else
process_under_lock(self);
#endif
}
recursive_mutex_exit(&self->lock_mutex);
#if ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
if (wake_other_core) {
async_context_threadsafe_background_wake_up(&self->core);
}
#endif
}
uint32_t async_context_threadsafe_background_execute_sync(async_context_t *self_base, uint32_t (*func)(void *param), void *param) {
async_context_threadsafe_background_t *self = (async_context_threadsafe_background_t*)self_base;
#if ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
if (self_base->core_num != get_core_num()) {
hard_assert(!recursive_mutex_enter_count(&self->lock_mutex));
sync_func_call_t call;
call.worker.do_work = handle_sync_func_call;
call.func = func;
call.param = param;
sem_init(&call.sem, 0, 1);
async_context_add_when_pending_worker(self_base, &call.worker);
async_context_set_work_pending(self_base, &call.worker);
sem_acquire_blocking(&call.sem);
return call.rc;
}
#endif
// short-circuit if we are on the right core
lock_acquire(self);
uint32_t rc = func(param);
lock_release(self);
return rc;
}
static bool low_prio_irq_init(async_context_threadsafe_background_t *self, uint8_t priority) {
assert(get_core_num() == self->core.core_num);
int irq = user_irq_claim_unused(false);
if (irq < 0) return false;
self->low_priority_irq_num = (uint8_t) irq;
uint index = irq - FIRST_USER_IRQ;
assert(index < count_of(async_contexts_by_user_irq));
async_contexts_by_user_irq[index] = self;
irq_set_exclusive_handler(self->low_priority_irq_num, low_priority_irq_handler);
irq_set_enabled(self->low_priority_irq_num, true);
irq_set_priority(self->low_priority_irq_num, priority);
return true;
}
static void low_prio_irq_deinit(async_context_threadsafe_background_t *self) {
if (self->low_priority_irq_num > 0) {
assert(get_core_num() == self->core.core_num);
irq_set_enabled(self->low_priority_irq_num, false);
irq_remove_handler(self->low_priority_irq_num, low_priority_irq_handler);
user_irq_unclaim(self->low_priority_irq_num);
self->low_priority_irq_num = 0;
}
}
static int64_t alarm_handler(__unused alarm_id_t id, void *user_data) {
async_context_threadsafe_background_t *self = (async_context_threadsafe_background_t*)user_data;
#if ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
self->force_alarm_id = 0;
#endif
self->alarm_pending = false;
async_context_threadsafe_background_wake_up(&self->core);
return 0;
}
bool async_context_threadsafe_background_init(async_context_threadsafe_background_t *self, async_context_threadsafe_background_config_t *config) {
memset(self, 0, sizeof(*self));
self->core.type = &template;
self->core.flags = ASYNC_CONTEXT_FLAG_CALLBACK_FROM_IRQ | ASYNC_CONTEXT_FLAG_CALLBACK_FROM_NON_IRQ;
self->core.core_num = get_core_num();
if (config->custom_alarm_pool) {
self->alarm_pool = config->custom_alarm_pool;
} else {
#if PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
self->alarm_pool = alarm_pool_create_with_unused_hardware_alarm(ASYNC_CONTEXT_THREADSAFE_BACKGROUND_ALARM_POOL_MAX_ALARMS);
self->alarm_pool_owned = true;
#else
self->alarm_pool = alarm_pool_get_default();
#if ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
if (self->core.core_num != alarm_pool_core_num(self->alarm_pool)) {
self->alarm_pool = alarm_pool_create_with_unused_hardware_alarm(ASYNC_CONTEXT_THREADSAFE_BACKGROUND_ALARM_POOL_MAX_ALARMS);
self->alarm_pool_owned = true;
}
#endif
#endif
}
assert(self->core.core_num == alarm_pool_core_num(self->alarm_pool));
sem_init(&self->work_needed_sem, 1, 1);
recursive_mutex_init(&self->lock_mutex);
bool ok = low_prio_irq_init(self, config->low_priority_irq_handler_priority);
return ok;
}
static void async_context_threadsafe_background_set_work_pending(async_context_t *self_base, async_when_pending_worker_t *worker) {
worker->work_pending = true;
async_context_threadsafe_background_wake_up(self_base);
}
static void async_context_threadsafe_background_deinit(async_context_t *self_base) {
async_context_threadsafe_background_t *self = (async_context_threadsafe_background_t *)self_base;
// todo we do not currently handle this correctly; we could, but seems like a rare case
assert(get_core_num() == self_base->core_num);
low_prio_irq_deinit(self);
if (self->alarm_id > 0) alarm_pool_cancel_alarm(self->alarm_pool, self->alarm_id);
#if ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
if (self->alarm_pool_owned) {
alarm_pool_destroy(self->alarm_pool);
}
#endif
// acquire the lock to make sure the callback is not running (we have already disabled the IRQ
recursive_mutex_enter_blocking(&self->lock_mutex);
recursive_mutex_exit(&self->lock_mutex);
memset(self, 0, sizeof(*self));
}
static void process_under_lock(async_context_threadsafe_background_t *self) {
#ifndef NDEBUG
async_context_threadsafe_background_lock_check(&self->core);
assert(self->core.core_num == get_core_num());
#endif
do {
absolute_time_t next_time = async_context_base_execute_once(&self->core);
// if the next wakeup time is in the past then loop
if (absolute_time_diff_us(get_absolute_time(), next_time) <= 0) continue;
// if there is no next wakeup time, we're done
if (is_at_the_end_of_time(next_time)) {
// cancel the alarm early (we will have been called soon after an alarm wakeup), so that
// we don't risk alarm_id collision.
if (self->alarm_id > 0) {
alarm_pool_cancel_alarm(self->alarm_pool, self->alarm_id);
self->alarm_id = 0;
}
break;
}
// the following is an optimization; we are often called much more frequently than timeouts actually change,
// and removing and re-adding the timers has some non-trivial overhead (10s of microseconds), we choose
// to allow the existing timeout to run to completion, and then re-asses from there, unless the new wakeup
// time is before the last one set.
//
// note that alarm_pending is not protected, however, if it is wrong, it is wrong in the sense that it is
// false when it should be true, so if it is wrong due to a race, we will cancel and re-add the alarm which is safe.
if (self->alarm_pending && absolute_time_diff_us(self->last_set_alarm_time, next_time) > 0) break;
// cancel the existing alarm (it may no longer exist)
if (self->alarm_id > 0) alarm_pool_cancel_alarm(self->alarm_pool, self->alarm_id);
self->last_set_alarm_time = next_time;
self->alarm_pending = true;
self->alarm_id = alarm_pool_add_alarm_at(self->alarm_pool, next_time, alarm_handler, self, false);
if (self->alarm_id > 0) break;
self->alarm_pending = false;
} while (true);
}
// Low priority interrupt handler to perform background processing
static void low_priority_irq_handler(void) {
uint index = __get_current_exception() - VTABLE_FIRST_IRQ - FIRST_USER_IRQ;
assert(index < count_of(async_contexts_by_user_irq));
async_context_threadsafe_background_t *self = async_contexts_by_user_irq[index];
if (!self) return;
assert(self->core.core_num == get_core_num());
if (recursive_mutex_try_enter(&self->lock_mutex, NULL)) {
// if the recurse count is not 1 then we have pre-empted something which held the lock on the same core,
// so we cannot do processing here (however processing will be done when that lock is released)
if (recursive_mutex_enter_count(&self->lock_mutex) == 1) {
process_under_lock(self);
}
recursive_mutex_exit(&self->lock_mutex);
}
}
static void async_context_threadsafe_background_wait_until(__unused async_context_t *self_base, absolute_time_t until) {
// can be called in IRQs, in which case we just have to wait
if (__get_current_exception()) {
busy_wait_until(until);
} else {
sleep_until(until);
}
}
static void async_context_threadsafe_background_wait_for_work_until(async_context_t *self_base, absolute_time_t until) {
async_context_threadsafe_background_t *self = (async_context_threadsafe_background_t *)self_base;
sem_acquire_block_until(&self->work_needed_sem, until);
}
static bool async_context_threadsafe_background_add_at_time_worker(async_context_t *self_base, async_at_time_worker_t *worker) {
async_context_threadsafe_background_t *self = (async_context_threadsafe_background_t *)self_base;
lock_acquire(self);
bool rc = async_context_base_add_at_time_worker(self_base, worker);
lock_release(self);
return rc;
}
static bool async_context_threadsafe_background_remove_at_time_worker(async_context_t *self_base, async_at_time_worker_t *worker) {
async_context_threadsafe_background_t *self = (async_context_threadsafe_background_t *)self_base;
lock_acquire(self);
bool rc = async_context_base_remove_at_time_worker(self_base, worker);
lock_release(self);
return rc;
}
static bool async_context_threadsafe_background_add_when_pending_worker(async_context_t *self_base, async_when_pending_worker_t *worker) {
async_context_threadsafe_background_t *self = (async_context_threadsafe_background_t *)self_base;
lock_acquire(self);
bool rc = async_context_base_add_when_pending_worker(self_base, worker);
lock_release(self);
return rc;
}
static bool async_context_threadsafe_background_when_pending_worker(async_context_t *self_base, async_when_pending_worker_t *worker) {
async_context_threadsafe_background_t *self = (async_context_threadsafe_background_t *)self_base;
lock_acquire(self);
bool rc = async_context_base_remove_when_pending_worker(self_base, worker);
lock_release(self);
return rc;
}
static void async_context_threadsafe_background_acquire_lock_blocking(async_context_t *self_base) {
lock_acquire((async_context_threadsafe_background_t *) self_base);
}
static void async_context_threadsafe_background_release_lock(async_context_t *self_base) {
lock_release((async_context_threadsafe_background_t *)self_base);
}
static const async_context_type_t template = {
.type = ASYNC_CONTEXT_THREADSAFE_BACKGROUND,
.acquire_lock_blocking = async_context_threadsafe_background_acquire_lock_blocking,
.release_lock = async_context_threadsafe_background_release_lock,
.lock_check = async_context_threadsafe_background_lock_check,
.execute_sync = async_context_threadsafe_background_execute_sync,
.add_at_time_worker = async_context_threadsafe_background_add_at_time_worker,
.remove_at_time_worker = async_context_threadsafe_background_remove_at_time_worker,
.add_when_pending_worker = async_context_threadsafe_background_add_when_pending_worker,
.remove_when_pending_worker = async_context_threadsafe_background_when_pending_worker,
.set_work_pending = async_context_threadsafe_background_set_work_pending,
.poll = 0,
.wait_until = async_context_threadsafe_background_wait_until,
.wait_for_work_until = async_context_threadsafe_background_wait_for_work_until,
.deinit = async_context_threadsafe_background_deinit,
};
================================================
FILE: pico-sdk/src/rp2_common/pico_async_context/include/pico/async_context.h
================================================
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/** \file pico/async_context.h
* \defgroup pico_async_context pico_async_context
*
* An \ref async_context provides a logically single-threaded context for performing work, and responding
* to asynchronous events. Thus an async_context instance is suitable for servicing third-party libraries
* that are not re-entrant.
*
* The "context" in async_context refers to the fact that when calling workers or timeouts within the
* async_context various pre-conditions hold:
*
*
* - That there is a single logical thread of execution; i.e. that the context does not call any worker
* functions concurrently.
*
- That the context always calls workers from the same processor core, as most uses of async_context rely on interaction
* with IRQs which are themselves core-specific.
*
*
* THe async_context provides two mechanisms for asynchronous work:
*
* * when_pending workers, which are processed whenever they have work pending. See \ref async_context_add_when_pending_worker,
* \ref async_context_remove_when_pending_worker, and \ref async_context_set_work_pending, the latter of which can be used from an interrupt handler
* to signal that servicing work is required to be performed by the worker from the regular async_context.
* * at_time workers, that are executed after at a specific time.
*
* Note: "when pending" workers with work pending are executed before "at time" workers.
*
* The async_context provides locking mechanisms, see \ref async_context_acquire_lock_blocking,
* \ref async_context_release_lock and \ref async_context_lock_check which can be used by
* external code to ensure execution of external code does not happen concurrently with worker code.
* Locked code runs on the calling core, however \ref async_context_execute_sync is provided to
* synchronously run a function from the core of the async_context.
*
* The SDK ships with the following default async_contexts:
*
* async_context_poll - this context is not thread-safe, and the user is responsible for calling
* \ref async_context_poll() periodically, and can use async_context_wait_for_work_until() to sleep
* between calls until work is needed if the user has nothing else to do.
*
* async_context_threadsafe_background - in order to work in the background, a low priority IRQ is used
* to handle callbacks. Code is usually invoked from this IRQ context, but may be invoked after any other code
* that uses the async context in another (non-IRQ) context on the same core. Calling \ref async_context_poll() is
* not required, and is a no-op. This context implements async_context locking and is thus safe to call
* from either core, according to the specific notes on each API.
*
* async_context_freertos - Work is performed from a separate "async_context" task, however once again, code may
* also be invoked after a direct use of the async_context on the same core that the async_context belongs to. Calling
* \ref async_context_poll() is not required, and is a no-op. This context implements async_context locking and is thus
* safe to call from any task, and from either core, according to the specific notes on each API.
*
* Each async_context provides bespoke methods of instantiation which are provided in the corresponding headers (e.g.
* async_context_poll.h, async_context_threadsafe_background.h, asycn_context_freertos.h).
* async_contexts are de-initialized by the common async_context_deint() method.
*
* Multiple async_context instances can be used by a single application, and they will operate independently.
*/
#ifndef _PICO_ASYNC_CONTEXT_H
#define _PICO_ASYNC_CONTEXT_H
#include "pico.h"
#include "pico/time.h"
#ifdef __cplusplus
extern "C" {
#endif
enum {
ASYNC_CONTEXT_POLL = 1,
ASYNC_CONTEXT_THREADSAFE_BACKGROUND = 2,
ASYNC_CONTEXT_FREERTOS = 3,
};
typedef struct async_context async_context_t;
/*! \brief A "timeout" instance used by an async_context
* \ingroup pico_async_context
*
* A "timeout" represents some future action that must be taken at a specific time.
* Its methods are called from the async_context under lock at the given time
*
* \see async_context_add_worker_at
* \see async_context_add_worker_in_ms
*/
typedef struct async_work_on_timeout {
/*!
* private link list pointer
*/
struct async_work_on_timeout *next;
/*!
* Method called when the timeout is reached; may not be NULL
*
* Note, that when this method is called, the timeout has been removed from the async_context, so
* if you want the timeout to repeat, you should re-add it during this callback
* @param context
* @param timeout
*/
void (*do_work)(async_context_t *context, struct async_work_on_timeout *timeout);
/*!
* The next timeout time; this should only be modified during the above methods
* or via async_context methods
*/
absolute_time_t next_time;
/*!
* User data associated with the timeout instance
*/
void *user_data;
} async_at_time_worker_t;
/*! \brief A "worker" instance used by an async_context
* \ingroup pico_async_context
*
* A "worker" represents some external entity that must do work in response
* to some external stimulus (usually an IRQ).
* Its methods are called from the async_context under lock at the given time
*
* \see async_context_add_worker_at
* \see async_context_add_worker_in_ms
*/
typedef struct async_when_pending_worker {
/*!
* private link list pointer
*/
struct async_when_pending_worker *next;
/*!
* Called by the async_context when the worker has been marked as having "work pending"
*
* @param context the async_context
* @param worker the function to be called when work is pending
*/
void (*do_work)(async_context_t *context, struct async_when_pending_worker *worker);
/**
* True if the worker need do_work called
*/
bool work_pending;
/*!
* User data associated with the worker instance
*/
void *user_data;
} async_when_pending_worker_t;
#define ASYNC_CONTEXT_FLAG_CALLBACK_FROM_NON_IRQ 0x1
#define ASYNC_CONTEXT_FLAG_CALLBACK_FROM_IRQ 0x2
#define ASYNC_CONTEXT_FLAG_POLLED 0x4
/*!
* \brief Implementation of an async_context type, providing methods common to that type
* \ingroup pico_async_context
*/
typedef struct async_context_type {
uint16_t type;
// see wrapper functions for documentation
void (*acquire_lock_blocking)(async_context_t *self);
void (*release_lock)(async_context_t *self);
void (*lock_check)(async_context_t *self);
uint32_t (*execute_sync)(async_context_t *context, uint32_t (*func)(void *param), void *param);
bool (*add_at_time_worker)(async_context_t *self, async_at_time_worker_t *worker);
bool (*remove_at_time_worker)(async_context_t *self, async_at_time_worker_t *worker);
bool (*add_when_pending_worker)(async_context_t *self, async_when_pending_worker_t *worker);
bool (*remove_when_pending_worker)(async_context_t *self, async_when_pending_worker_t *worker);
void (*set_work_pending)(async_context_t *self, async_when_pending_worker_t *worker);
void (*poll)(async_context_t *self); // may be NULL
void (*wait_until)(async_context_t *self, absolute_time_t until);
void (*wait_for_work_until)(async_context_t *self, absolute_time_t until);
void (*deinit)(async_context_t *self);
} async_context_type_t;
/*!
* \brief Base structure type of all async_contexts. For details about its use, see \ref pico_async_context.
* \ingroup pico_async_context
*
* Individual async_context_types with additional state, should contain this structure at the start.
*/
struct async_context {
const async_context_type_t *type;
async_when_pending_worker_t *when_pending_list;
async_at_time_worker_t *at_time_list;
absolute_time_t next_time;
uint16_t flags;
uint8_t core_num;
};
/*!
* \brief Acquire the async_context lock
* \ingroup pico_async_context
*
* The owner of the async_context lock is the logic owner of the async_context
* and other work related to this async_context will not happen concurrently.
*
* This method may be called in a nested fashion by the the lock owner.
*
* \note the async_context lock is nestable by the same caller, so an internal count is maintained
*
* \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
* worker method called by the async_context or from any other non-IRQ context.
*
* \param context the async_context
*
* \see async_context_release_lock
*/
static inline void async_context_acquire_lock_blocking(async_context_t *context) {
context->type->acquire_lock_blocking(context);
}
/*!
* \brief Release the async_context lock
* \ingroup pico_async_context
*
* \note the async_context lock may be called in a nested fashion, so an internal count is maintained. On the outermost
* release, When the outermost lock is released, a check is made for work which might have been skipped while the lock was held,
* and any such work may be performed during this call IF the call is made from the same core that the async_context belongs to.
*
* \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
* worker method called by the async_context or from any other non-IRQ context.
*
* \param context the async_context
*
* \see async_context_acquire_lock_blocking
*/
static inline void async_context_release_lock(async_context_t *context) {
context->type->release_lock(context);
}
/*!
* \brief Assert if the caller does not own the lock for the async_context
* \ingroup pico_async_context
* \note this method is thread-safe
*
* \param context the async_context
*/
static inline void async_context_lock_check(async_context_t *context) {
context->type->lock_check(context);
}
/*!
* \brief Execute work synchronously on the core the async_context belongs to.
* \ingroup pico_async_context
*
* This method is intended for code external to the async_context (e.g. another thread/task) to
* execute a function with the same guarantees (single core, logical thread of execution) that
* async_context workers are called with.
*
* \note you should NOT call this method while holding the async_context's lock
*
* \param context the async_context
* \param func the function to call
* \param param the paramter to pass to the function
* \return the return value from func
*/
static inline uint32_t async_context_execute_sync(async_context_t *context, uint32_t (*func)(void *param), void *param) {
return context->type->execute_sync(context, func, param);
}
/*!
* \brief Add an "at time" worker to a context
* \ingroup pico_async_context
*
* An "at time" worker will run at or after a specific point in time, and is automatically when (just before) it runs.
*
* The time to fire is specified in the next_time field of the worker.
*
* \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
* worker method called by the async_context or from any other non-IRQ context.
*
* \param context the async_context
* \param worker the "at time" worker to add
* \return true if the worker was added, false if the worker was already present.
*/
static inline bool async_context_add_at_time_worker(async_context_t *context, async_at_time_worker_t *worker) {
return context->type->add_at_time_worker(context, worker);
}
/*!
* \brief Add an "at time" worker to a context
* \ingroup pico_async_context
*
* An "at time" worker will run at or after a specific point in time, and is automatically when (just before) it runs.
*
* The time to fire is specified by the at parameter.
*
* \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
* worker method called by the async_context or from any other non-IRQ context.
*
* \param context the async_context
* \param worker the "at time" worker to add
* \param at the time to fire at
* \return true if the worker was added, false if the worker was already present.
*/
static inline bool async_context_add_at_time_worker_at(async_context_t *context, async_at_time_worker_t *worker, absolute_time_t at) {
worker->next_time = at;
return context->type->add_at_time_worker(context, worker);
}
/*!
* \brief Add an "at time" worker to a context
* \ingroup pico_async_context
*
* An "at time" worker will run at or after a specific point in time, and is automatically when (just before) it runs.
*
* The time to fire is specified by a delay via the ms parameter
*
* \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
* worker method called by the async_context or from any other non-IRQ context.
*
* \param context the async_context
* \param worker the "at time" worker to add
* \param ms the number of milliseconds from now to fire after
* \return true if the worker was added, false if the worker was already present.
*/
static inline bool async_context_add_at_time_worker_in_ms(async_context_t *context, async_at_time_worker_t *worker, uint32_t ms) {
worker->next_time = make_timeout_time_ms(ms);
return context->type->add_at_time_worker(context, worker);
}
/*!
* \brief Remove an "at time" worker from a context
* \ingroup pico_async_context
*
* \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
* worker method called by the async_context or from any other non-IRQ context.
*
* \param context the async_context
* \param worker the "at time" worker to remove
* \return true if the worker was removed, false if the instance not present.
*/
static inline bool async_context_remove_at_time_worker(async_context_t *context, async_at_time_worker_t *worker) {
return context->type->remove_at_time_worker(context, worker);
}
/*!
* \brief Add a "when pending" worker to a context
* \ingroup pico_async_context
*
* An "when pending" worker will run when it is pending (can be set via \ref async_context_set_work_pending), and
* is NOT automatically removed when it runs.
*
* The time to fire is specified by a delay via the ms parameter
*
* \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
* worker method called by the async_context or from any other non-IRQ context.
*
* \param context the async_context
* \param worker the "when pending" worker to add
* \return true if the worker was added, false if the worker was already present.
*/
static inline bool async_context_add_when_pending_worker(async_context_t *context, async_when_pending_worker_t *worker) {
return context->type->add_when_pending_worker(context, worker);
}
/*!
* \brief Remove a "when pending" worker from a context
* \ingroup pico_async_context
*
* \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
* worker method called by the async_context or from any other non-IRQ context.
*
* \param context the async_context
* \param worker the "when pending" worker to remove
* \return true if the worker was removed, false if the instance not present.
*/
static inline bool async_context_remove_when_pending_worker(async_context_t *context, async_when_pending_worker_t *worker) {
return context->type->remove_when_pending_worker(context, worker);
}
/*!
* \brief Mark a "when pending" worker as having work pending
* \ingroup pico_async_context
*
* The worker will be run from the async_context at a later time.
*
* \note this method may be called from any context including IRQs
*
* \param context the async_context
* \param worker the "when pending" worker to mark as pending.
*/
static inline void async_context_set_work_pending(async_context_t *context, async_when_pending_worker_t *worker) {
context->type->set_work_pending(context, worker);
}
/*!
* \brief Perform any pending work for polling style async_context
* \ingroup pico_async_context
*
* For a polled async_context (e.g. \ref async_context_poll) the user is responsible for calling this method
* periodically to perform any required work.
*
* This method may immediately perform outstanding work on other context types, but is not required to.
*
* \param context the async_context
*/
static inline void async_context_poll(async_context_t *context) {
if (context->type->poll) context->type->poll(context);
}
/*!
* \brief sleep until the specified time in an async_context callback safe way
* \ingroup pico_async_context
*
* \note for async_contexts that provide locking (not async_context_poll), this method is threadsafe. and may be called from within any
* worker method called by the async_context or from any other non-IRQ context.
*
* \param context the async_context
* \param until the time to sleep until
*/
static inline void async_context_wait_until(async_context_t *context, absolute_time_t until) {
context->type->wait_until(context, until);
}
/*!
* \brief Block until work needs to be done or the specified time has been reached
* \ingroup pico_async_context
*
* \note this method should not be called from a worker callback
*
* \param context the async_context
* \param until the time to return at if no work is required
*/
static inline void async_context_wait_for_work_until(async_context_t *context, absolute_time_t until) {
context->type->wait_for_work_until(context, until);
}
/*!
* \brief Block until work needs to be done or the specified number of milliseconds have passed
* \ingroup pico_async_context
*
* \note this method should not be called from a worker callback
*
* \param context the async_context
* \param ms the number of milliseconds to return after if no work is required
*/
static inline void async_context_wait_for_work_ms(async_context_t *context, uint32_t ms) {
async_context_wait_for_work_until(context, make_timeout_time_ms(ms));
}
/*!
* \brief Return the processor core this async_context belongs to
* \ingroup pico_async_context
*
* \param context the async_context
* \return the physical core number
*/
static inline uint async_context_core_num(const async_context_t *context) {
return context->core_num;
}
/*!
* \brief End async_context processing, and free any resources
* \ingroup pico_async_context
*
* Note the user should clean up any resources associated with workers
* in the async_context themselves.
*
* Asynchronous (non-polled) async_contexts guarantee that no
* callback is being called once this method returns.
*
* \param context the async_context
*/
static inline void async_context_deinit(async_context_t *context) {
context->type->deinit(context);
}
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_async_context/include/pico/async_context_base.h
================================================
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_ASYNC_CONTEXT_BASE_H
#define _PICO_ASYNC_CONTEXT_BASE_H
#include "pico/async_context.h"
#ifdef __cplusplus
extern "C" {
#endif
// common functions for async_context implementations to use
bool async_context_base_add_at_time_worker(async_context_t *self, async_at_time_worker_t *worker);
bool async_context_base_remove_at_time_worker(async_context_t *self, async_at_time_worker_t *worker);
bool async_context_base_add_when_pending_worker(async_context_t *self, async_when_pending_worker_t *worker);
bool async_context_base_remove_when_pending_worker(async_context_t *self, async_when_pending_worker_t *worker);
async_at_time_worker_t *async_context_base_remove_ready_at_time_worker(async_context_t *self);
void async_context_base_refresh_next_timeout(async_context_t *self);
absolute_time_t async_context_base_execute_once(async_context_t *self);
bool async_context_base_needs_servicing(async_context_t *self);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_async_context/include/pico/async_context_freertos.h
================================================
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_ASYNC_CONTEXT_FREERTOS_H
#define _PICO_ASYNC_CONTEXT_FREERTOS_H
/** \file pico/async_context.h
* \defgroup async_context_freertos async_context_freertos
* \ingroup pico_async_context
*
* async_context_freertos provides an implementation of \ref async_context that handles asynchronous
* work in a separate FreeRTOS task.
*/
#include "pico/async_context.h"
// FreeRTOS includes
#include "FreeRTOS.h"
#include "semphr.h"
#include "timers.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef ASYNC_CONTEXT_DEFAULT_FREERTOS_TASK_PRIORITY
#define ASYNC_CONTEXT_DEFAULT_FREERTOS_TASK_PRIORITY ( tskIDLE_PRIORITY + 4)
#endif
#ifndef ASYNC_CONTEXT_DEFAULT_FREERTOS_TASK_STACK_SIZE
#define ASYNC_CONTEXT_DEFAULT_FREERTOS_TASK_STACK_SIZE configMINIMAL_STACK_SIZE
#endif
typedef struct async_context_freertos async_context_freertos_t;
/**
* \brief Configuration object for async_context_freertos instances.
*/
typedef struct async_context_freertos_config {
/**
* Task priority for the async_context task
*/
UBaseType_t task_priority;
/**
* Stack size for the async_context task
*/
configSTACK_DEPTH_TYPE task_stack_size;
/**
* the core ID (see \ref portGET_CORE_ID()) to pin the task to.
* This is only relevant in SMP mode.
*/
#if configUSE_CORE_AFFINITY && configNUM_CORES > 1
UBaseType_t task_core_id;
#endif
} async_context_freertos_config_t;
struct async_context_freertos {
async_context_t core;
SemaphoreHandle_t lock_mutex;
SemaphoreHandle_t work_needed_sem;
TimerHandle_t timer_handle;
TaskHandle_t task_handle;
uint8_t nesting;
volatile bool task_should_exit;
};
/*!
* \brief Initialize an async_context_freertos instance using the specified configuration
* \ingroup async_context_freertos
*
* If this method succeeds (returns true), then the async_context is available for use
* and can be de-initialized by calling async_context_deinit().
*
* \param self a pointer to async_context_freertos structure to initialize
* \param config the configuration object specifying characteristics for the async_context
* \return true if initialization is successful, false otherwise
*/
bool async_context_freertos_init(async_context_freertos_t *self, async_context_freertos_config_t *config);
/*!
* \brief Return a copy of the default configuration object used by \ref async_context_freertos_init_with_defaults()
* \ingroup async_context_freertos
*
* The caller can then modify just the settings it cares about, and call \ref async_context_freertos_init()
* \return the default configuration object
*/
static inline async_context_freertos_config_t async_context_freertos_default_config(void) {
async_context_freertos_config_t config = {
.task_priority = ASYNC_CONTEXT_DEFAULT_FREERTOS_TASK_PRIORITY,
.task_stack_size = ASYNC_CONTEXT_DEFAULT_FREERTOS_TASK_STACK_SIZE,
#if configUSE_CORE_AFFINITY && configNUM_CORES > 1
.task_core_id = (UBaseType_t)-1, // none
#endif
};
return config;
}
/*!
* \brief Initialize an async_context_freertos instance with default values
* \ingroup async_context_freertos
*
* If this method succeeds (returns true), then the async_context is available for use
* and can be de-initialized by calling async_context_deinit().
*
* \param self a pointer to async_context_freertos structure to initialize
* \return true if initialization is successful, false otherwise
*/
static inline bool async_context_freertos_init_with_defaults(async_context_freertos_t *self) {
async_context_freertos_config_t config = async_context_freertos_default_config();
return async_context_freertos_init(self, &config);
}
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_async_context/include/pico/async_context_poll.h
================================================
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_ASYNC_CONTEXT_POLL_H
#define _PICO_ASYNC_CONTEXT_POLL_H
/** \file pico/async_context.h
* \defgroup async_context_poll async_context_poll
* \ingroup pico_async_context
*
* async_context_poll provides an implementation of \ref async_context that is intended for use with a simple
* polling loop on one core. It is not thread safe.
*
* The \ref async_context_poll() method must be called periodically to handle asynchronous work that may now be
* pending. \ref async_context_wait_for_work_until() may be used to block a polling loop until there is work to do,
* and prevent tight spinning.
*/
#include "pico/async_context.h"
#include "pico/sem.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct async_context_poll {
async_context_t core;
semaphore_t sem;
} async_context_poll_t;
/*!
* \brief Initialize an async_context_poll instance with default values
* \ingroup async_context_poll
*
* If this method succeeds (returns true), then the async_context is available for use
* and can be de-initialized by calling async_context_deinit().
*
* \param self a pointer to async_context_poll structure to initialize
* \return true if initialization is successful, false otherwise
*/
bool async_context_poll_init_with_defaults(async_context_poll_t *self);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_async_context/include/pico/async_context_threadsafe_background.h
================================================
/*
* Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_ASYNC_CONTEXT_THREADSAFE_BACKGROUND_H
#define _PICO_ASYNC_CONTEXT_THREADSAFE_BACKGROUND_H
/** \file pico/async_context.h
* \defgroup async_context_threadsafe_background async_context_threadsafe_background
* \ingroup pico_async_context
*
* async_context_threadsafe_background provides an implementation of \ref async_context that handles asynchronous
* work in a low priority IRQ, and there is no need for the user to poll for work.
*
* \note The workers used with this async_context MUST be safe to call from an IRQ.
*/
#include "pico/async_context.h"
#include "pico/sem.h"
#include "pico/mutex.h"
#include "hardware/irq.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
#define ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE LIB_PICO_MULTICORE
#endif
typedef struct async_context_threadsafe_background async_context_threadsafe_background_t;
/**
* \brief Configuration object for async_context_threadsafe_background instances.
*/
typedef struct async_context_threadsafe_background_config {
/**
* the priority of the low priority IRQ
*/
uint8_t low_priority_irq_handler_priority;
/**
* a specific alarm pool to use (or NULL to use ta default)
*
* \note this alarm pool MUST be on the same core as the async_context
*
* The default alarm pool used is the "default alarm pool" (see
* \ref alarm_pool_get_default()) if available, and if that is on the same
* core, otherwise a private alarm_pool instance created during
* initialization.
*/
alarm_pool_t *custom_alarm_pool;
} async_context_threadsafe_background_config_t;
struct async_context_threadsafe_background {
async_context_t core;
alarm_pool_t *alarm_pool; // this must be on the same core as core_num
absolute_time_t last_set_alarm_time;
recursive_mutex_t lock_mutex;
semaphore_t work_needed_sem;
volatile alarm_id_t alarm_id;
#if ASYNC_CONTEXT_THREADSAFE_BACKGROUND_MULTI_CORE
volatile alarm_id_t force_alarm_id;
bool alarm_pool_owned;
#endif
uint8_t low_priority_irq_num;
volatile bool alarm_pending;
};
/*!
* \brief Initialize an async_context_threadsafe_background instance using the specified configuration
* \ingroup async_context_threadsafe_background
*
* If this method succeeds (returns true), then the async_context is available for use
* and can be de-initialized by calling async_context_deinit().
*
* \param self a pointer to async_context_threadsafe_background structure to initialize
* \param config the configuration object specifying characteristics for the async_context
* \return true if initialization is successful, false otherwise
*/
bool async_context_threadsafe_background_init(async_context_threadsafe_background_t *self, async_context_threadsafe_background_config_t *config);
/*!
* \brief Return a copy of the default configuration object used by \ref async_context_threadsafe_background_init_with_defaults()
* \ingroup async_context_threadsafe_background
*
* The caller can then modify just the settings it cares about, and call \ref async_context_threadsafe_background_init()
* \return the default configuration object
*/
async_context_threadsafe_background_config_t async_context_threadsafe_background_default_config(void);
/*!
* \brief Initialize an async_context_threadsafe_background instance with default values
* \ingroup async_context_threadsafe_background
*
* If this method succeeds (returns true), then the async_context is available for use
* and can be de-initialized by calling async_context_deinit().
*
* \param self a pointer to async_context_threadsafe_background structure to initialize
* \return true if initialization is successful, false otherwise
*/
static inline bool async_context_threadsafe_background_init_with_defaults(async_context_threadsafe_background_t *self) {
async_context_threadsafe_background_config_t config = async_context_threadsafe_background_default_config();
return async_context_threadsafe_background_init(self, &config);
}
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_bit_ops/CMakeLists.txt
================================================
if (NOT TARGET pico_bit_ops)
#shims for ROM functions for -lgcc functions (listed below)
pico_add_impl_library(pico_bit_ops)
# no custom implementation; falls thru to compiler
pico_add_library(pico_bit_ops_compiler)
# add alias "default" which is just pico.
add_library(pico_bit_ops_default INTERFACE)
target_link_libraries(pico_bit_ops_default INTERFACE pico_bit_ops_pico)
set(PICO_DEFAULT_BIT_OPS_IMPL pico_bit_ops_default)
pico_add_library(pico_bit_ops_pico)
target_link_libraries(pico_bit_ops INTERFACE
$>,$,${PICO_DEFAULT_BIT_OPS_IMPL}>)
target_sources(pico_bit_ops_pico INTERFACE
${CMAKE_CURRENT_LIST_DIR}/bit_ops_aeabi.S
)
target_link_libraries(pico_bit_ops_pico INTERFACE pico_bootrom pico_bit_ops_headers)
# gcc
pico_wrap_function(pico_bit_ops_pico __clzsi2)
pico_wrap_function(pico_bit_ops_pico __clzsi2)
pico_wrap_function(pico_bit_ops_pico __clzdi2)
pico_wrap_function(pico_bit_ops_pico __ctzsi2)
pico_wrap_function(pico_bit_ops_pico __ctzdi2)
pico_wrap_function(pico_bit_ops_pico __popcountsi2)
pico_wrap_function(pico_bit_ops_pico __popcountdi2)
# armclang
pico_wrap_function(pico_bit_ops_pico __clz)
pico_wrap_function(pico_bit_ops_pico __clzl)
pico_wrap_function(pico_bit_ops_pico __clzsi2)
pico_wrap_function(pico_bit_ops_pico __clzll)
macro(pico_set_bit_ops_implementation TARGET IMPL)
get_target_property(target_type ${TARGET} TYPE)
if ("EXECUTABLE" STREQUAL "${target_type}")
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_BIT_OPS_IMPL "pico_bit_ops_${IMPL}")
else()
message(FATAL_ERROR "bit_ops implementation must be set on executable not library")
endif()
endmacro()
endif()
================================================
FILE: pico-sdk/src/rp2_common/pico_bit_ops/bit_ops_aeabi.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/asm_helper.S"
#include "pico/bootrom.h"
pico_default_asm_setup
.macro bits_section name
#if PICO_BITS_IN_RAM
.section RAM_SECTION_NAME(\name), "ax"
#else
.section SECTION_NAME(\name), "ax"
#endif
.endm
__pre_init __aeabi_bits_init, 00010
.section .data.aeabi_bits_funcs
.global aeabi_bits_funcs, aeabi_bits_funcs_end
.equ BITS_FUNC_COUNT, 4
.align 4
aeabi_bits_funcs:
.word ROM_FUNC_POPCOUNT32
.word ROM_FUNC_CLZ32
.word ROM_FUNC_CTZ32
.word ROM_FUNC_REVERSE32
aeabi_bits_funcs_end:
.section .text
.thumb_func
__aeabi_bits_init:
ldr r0, =aeabi_bits_funcs
movs r1, #BITS_FUNC_COUNT
ldr r3, =rom_funcs_lookup
bx r3
.equ POPCOUNT32, 0
.equ CLZ32, 4
.equ CTZ32, 8
.equ REVERSE32, 12
bits_section clzsi
wrapper_func __clz
wrapper_func __clzl
wrapper_func __clzsi2
ldr r3, =aeabi_bits_funcs
ldr r3, [r3, #CLZ32]
bx r3
bits_section ctzsi
wrapper_func __ctzsi2
ldr r3, =aeabi_bits_funcs
ldr r3, [r3, #CTZ32]
bx r3
bits_section popcountsi
wrapper_func __popcountsi2
ldr r3, =aeabi_bits_funcs
ldr r3, [r3, #POPCOUNT32]
bx r3
bits_section clzdi
wrapper_func __clzll
wrapper_func __clzdi2
ldr r3, =aeabi_bits_funcs
ldr r3, [r3, #CLZ32]
cmp r1, #0
bne 1f
push {lr}
blx r3
adds r0, #32
pop {pc}
1:
mov r0, r1
bx r3
bits_section ctzdi
wrapper_func __ctzdi2
ldr r3, =aeabi_bits_funcs
ldr r3, [r3, #CTZ32]
cmp r0, #0
beq 1f
bx r3
1:
push {lr}
mov r0, r1
blx r3
adds r0, #32
pop {pc}
bits_section popcountdi
wrapper_func __popcountdi2
ldr r3, =aeabi_bits_funcs
ldr r3, [r3, #POPCOUNT32]
push {r1, r3, lr}
blx r3
mov ip, r0
pop {r0, r3}
blx r3
mov r1, ip
add r0, r1
pop {pc}
bits_section reverse32
regular_func reverse32
ldr r3, =aeabi_bits_funcs
ldr r3, [r3, #REVERSE32]
bx r3
bits_section __rev
regular_func __rev
regular_func __revl
ldr r3, =aeabi_bits_funcs
ldr r3, [r3, #REVERSE32]
bx r3
bits_section __revll
regular_func __revll
push {lr}
ldr r3, =aeabi_bits_funcs
ldr r3, [r3, #REVERSE32]
push {r1, r3}
blx r3
mov ip, r0 // reverse32 preserves ip
pop {r0, r3}
blx r3
mov r1, ip
pop {pc}
================================================
FILE: pico-sdk/src/rp2_common/pico_bootrom/CMakeLists.txt
================================================
pico_add_library(pico_bootrom_headers NOFLAG)
target_include_directories(pico_bootrom_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
add_library(pico_bootrom INTERFACE)
target_sources(pico_bootrom INTERFACE
${CMAKE_CURRENT_LIST_DIR}/bootrom.c
)
pico_mirrored_target_link_libraries(pico_bootrom INTERFACE pico_base)
================================================
FILE: pico-sdk/src/rp2_common/pico_bootrom/bootrom.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/bootrom.h"
/// \tag::table_lookup[]
void *rom_func_lookup(uint32_t code) {
return rom_func_lookup_inline(code);
}
void *rom_data_lookup(uint32_t code) {
rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) rom_hword_as_ptr(0x18);
uint16_t *data_table = (uint16_t *) rom_hword_as_ptr(0x16);
return rom_table_lookup(data_table, code);
}
/// \end::table_lookup[]
bool rom_funcs_lookup(uint32_t *table, unsigned int count) {
bool ok = true;
for (unsigned int i = 0; i < count; i++) {
table[i] = (uintptr_t) rom_func_lookup(table[i]);
if (!table[i]) ok = false;
}
return ok;
}
================================================
FILE: pico-sdk/src/rp2_common/pico_bootrom/include/pico/bootrom/sf_table.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_BOOTROM_SF_TABLE_H
#define _PICO_BOOTROM_SF_TABLE_H
// NOTE THESE FUNCTION IMPLEMENTATIONS MATCH THE BEHAVIOR DESCRIBED IN THE BOOTROM SECTION OF THE RP2040 DATASHEET
#define SF_TABLE_FADD 0x00
#define SF_TABLE_FSUB 0x04
#define SF_TABLE_FMUL 0x08
#define SF_TABLE_FDIV 0x0c
#define SF_TABLE_FCMP_FAST 0x10
#define SF_TABLE_FCMP_FAST_FLAGS 0x14
#define SF_TABLE_FSQRT 0x18
#define SF_TABLE_FLOAT2INT 0x1c
#define SF_TABLE_FLOAT2FIX 0x20
#define SF_TABLE_FLOAT2UINT 0x24
#define SF_TABLE_FLOAT2UFIX 0x28
#define SF_TABLE_INT2FLOAT 0x2c
#define SF_TABLE_FIX2FLOAT 0x30
#define SF_TABLE_UINT2FLOAT 0x34
#define SF_TABLE_UFIX2FLOAT 0x38
#define SF_TABLE_FCOS 0x3c
#define SF_TABLE_FSIN 0x40
#define SF_TABLE_FTAN 0x44
#define SF_TABLE_V3_FSINCOS 0x48
#define SF_TABLE_FEXP 0x4c
#define SF_TABLE_FLN 0x50
#define SF_TABLE_V1_SIZE 0x54
#define SF_TABLE_FCMP_BASIC 0x54
#define SF_TABLE_FATAN2 0x58
#define SF_TABLE_INT642FLOAT 0x5c
#define SF_TABLE_FIX642FLOAT 0x60
#define SF_TABLE_UINT642FLOAT 0x64
#define SF_TABLE_UFIX642FLOAT 0x68
#define SF_TABLE_FLOAT2INT64 0x6c
#define SF_TABLE_FLOAT2FIX64 0x70
#define SF_TABLE_FLOAT2UINT64 0x74
#define SF_TABLE_FLOAT2UFIX64 0x78
#define SF_TABLE_FLOAT2DOUBLE 0x7c
#define SF_TABLE_V2_SIZE 0x80
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_bootrom/include/pico/bootrom.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_BOOTROM_H
#define _PICO_BOOTROM_H
#include "pico.h"
/** \file bootrom.h
* \defgroup pico_bootrom pico_bootrom
* Access to functions and data in the RP2040 bootrom
*
* This header may be included by assembly code
*/
// ROM FUNCTIONS
#define ROM_FUNC_POPCOUNT32 ROM_TABLE_CODE('P', '3')
#define ROM_FUNC_REVERSE32 ROM_TABLE_CODE('R', '3')
#define ROM_FUNC_CLZ32 ROM_TABLE_CODE('L', '3')
#define ROM_FUNC_CTZ32 ROM_TABLE_CODE('T', '3')
#define ROM_FUNC_MEMSET ROM_TABLE_CODE('M', 'S')
#define ROM_FUNC_MEMSET4 ROM_TABLE_CODE('S', '4')
#define ROM_FUNC_MEMCPY ROM_TABLE_CODE('M', 'C')
#define ROM_FUNC_MEMCPY44 ROM_TABLE_CODE('C', '4')
#define ROM_FUNC_RESET_USB_BOOT ROM_TABLE_CODE('U', 'B')
#define ROM_FUNC_CONNECT_INTERNAL_FLASH ROM_TABLE_CODE('I', 'F')
#define ROM_FUNC_FLASH_EXIT_XIP ROM_TABLE_CODE('E', 'X')
#define ROM_FUNC_FLASH_RANGE_ERASE ROM_TABLE_CODE('R', 'E')
#define ROM_FUNC_FLASH_RANGE_PROGRAM ROM_TABLE_CODE('R', 'P')
#define ROM_FUNC_FLASH_FLUSH_CACHE ROM_TABLE_CODE('F', 'C')
#define ROM_FUNC_FLASH_ENTER_CMD_XIP ROM_TABLE_CODE('C', 'X')
/*! \brief Return a bootrom lookup code based on two ASCII characters
* \ingroup pico_bootrom
*
* These codes are uses to lookup data or function addresses in the bootrom
*
* \param c1 the first character
* \param c2 the second character
* \return the 'code' to use in rom_func_lookup() or rom_data_lookup()
*/
#define ROM_TABLE_CODE(c1, c2) ((c1) | ((c2) << 8))
#ifndef __ASSEMBLER__
// ROM FUNCTION SIGNATURES
typedef uint32_t (*rom_popcount32_fn)(uint32_t);
typedef uint32_t (*rom_reverse32_fn)(uint32_t);
typedef uint32_t (*rom_clz32_fn)(uint32_t);
typedef uint32_t (*rom_ctz32_fn)(uint32_t);
typedef uint8_t *(*rom_memset_fn)(uint8_t *, uint8_t, uint32_t);
typedef uint32_t *(*rom_memset4_fn)(uint32_t *, uint8_t, uint32_t);
typedef uint32_t *(*rom_memcpy_fn)(uint8_t *, const uint8_t *, uint32_t);
typedef uint32_t *(*rom_memcpy44_fn)(uint32_t *, const uint32_t *, uint32_t);
typedef void __attribute__((noreturn)) (*rom_reset_usb_boot_fn)(uint32_t, uint32_t);
typedef rom_reset_usb_boot_fn reset_usb_boot_fn; // kept for backwards compatibility
typedef void (*rom_connect_internal_flash_fn)(void);
typedef void (*rom_flash_exit_xip_fn)(void);
typedef void (*rom_flash_range_erase_fn)(uint32_t, size_t, uint32_t, uint8_t);
typedef void (*rom_flash_range_program_fn)(uint32_t, const uint8_t*, size_t);
typedef void (*rom_flash_flush_cache_fn)(void);
typedef void (*rom_flash_enter_cmd_xip_fn)(void);
#ifdef __cplusplus
extern "C" {
#endif
/*! \brief Return a bootrom lookup code based on two ASCII characters
* \ingroup pico_bootrom
*
* These codes are uses to lookup data or function addresses in the bootrom
*
* \param c1 the first character
* \param c2 the second character
* \return the 'code' to use in rom_func_lookup() or rom_data_lookup()
*/
static inline uint32_t rom_table_code(uint8_t c1, uint8_t c2) {
return ROM_TABLE_CODE((uint32_t) c1, (uint32_t) c2);
}
/*!
* \brief Lookup a bootrom function by code
* \ingroup pico_bootrom
* \param code the code
* \return a pointer to the function, or NULL if the code does not match any bootrom function
*/
void *rom_func_lookup(uint32_t code);
/*!
* \brief Lookup a bootrom address by code
* \ingroup pico_bootrom
* \param code the code
* \return a pointer to the data, or NULL if the code does not match any bootrom function
*/
void *rom_data_lookup(uint32_t code);
/*!
* \brief Helper function to lookup the addresses of multiple bootrom functions
* \ingroup pico_bootrom
*
* This method looks up the 'codes' in the table, and convert each table entry to the looked up
* function pointer, if there is a function for that code in the bootrom.
*
* \param table an IN/OUT array, elements are codes on input, function pointers on success.
* \param count the number of elements in the table
* \return true if all the codes were found, and converted to function pointers, false otherwise
*/
bool rom_funcs_lookup(uint32_t *table, unsigned int count);
// Bootrom function: rom_table_lookup
// Returns the 32 bit pointer into the ROM if found or NULL otherwise.
typedef void *(*rom_table_lookup_fn)(uint16_t *table, uint32_t code);
#if PICO_C_COMPILER_IS_GNU && (__GNUC__ >= 12)
// Convert a 16 bit pointer stored at the given rom address into a 32 bit pointer
static inline void *rom_hword_as_ptr(uint16_t rom_address) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
return (void *)(uintptr_t)*(uint16_t *)(uintptr_t)rom_address;
#pragma GCC diagnostic pop
}
#else
// Convert a 16 bit pointer stored at the given rom address into a 32 bit pointer
#define rom_hword_as_ptr(rom_address) (void *)(uintptr_t)(*(uint16_t *)(uintptr_t)(rom_address))
#endif
/*!
* \brief Lookup a bootrom function by code. This method is forcibly inlined into the caller for FLASH/RAM sensitive code usage
* \ingroup pico_bootrom
* \param code the code
* \return a pointer to the function, or NULL if the code does not match any bootrom function
*/
static __force_inline void *rom_func_lookup_inline(uint32_t code) {
rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) rom_hword_as_ptr(0x18);
uint16_t *func_table = (uint16_t *) rom_hword_as_ptr(0x14);
return rom_table_lookup(func_table, code);
}
/*!
* \brief Reboot the device into BOOTSEL mode
* \ingroup pico_bootrom
*
* This function reboots the device into the BOOTSEL mode ('usb boot").
*
* Facilities are provided to enable an "activity light" via GPIO attached LED for the USB Mass Storage Device,
* and to limit the USB interfaces exposed.
*
* \param usb_activity_gpio_pin_mask 0 No pins are used as per a cold boot. Otherwise a single bit set indicating which
* GPIO pin should be set to output and raised whenever there is mass storage activity
* from the host.
* \param disable_interface_mask value to control exposed interfaces
* - 0 To enable both interfaces (as per a cold boot)
* - 1 To disable the USB Mass Storage Interface
* - 2 To disable the USB PICOBOOT Interface
*/
static inline void __attribute__((noreturn)) reset_usb_boot(uint32_t usb_activity_gpio_pin_mask,
uint32_t disable_interface_mask) {
rom_reset_usb_boot_fn func = (rom_reset_usb_boot_fn) rom_func_lookup(ROM_FUNC_RESET_USB_BOOT);
func(usb_activity_gpio_pin_mask, disable_interface_mask);
}
#ifdef __cplusplus
}
#endif
#endif // !__ASSEMBLER__
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_bootsel_via_double_reset/CMakeLists.txt
================================================
pico_add_library(pico_bootsel_via_double_reset)
target_sources(pico_bootsel_via_double_reset INTERFACE
${CMAKE_CURRENT_LIST_DIR}/pico_bootsel_via_double_reset.c
)
pico_mirrored_target_link_libraries(pico_bootsel_via_double_reset INTERFACE
pico_bootrom
pico_time
pico_binary_info
)
================================================
FILE: pico-sdk/src/rp2_common/pico_bootsel_via_double_reset/pico_bootsel_via_double_reset.c
================================================
/*
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
#include "pico/time.h"
#include "pico/bootrom.h"
#include "pico/binary_info.h"
// PICO_CONFIG: PICO_BOOTSEL_VIA_DOUBLE_RESET_TIMEOUT_MS, Window of opportunity for a second press of a reset button to enter BOOTSEL mode (milliseconds), type=int, default=200, group=pico_bootsel_via_double_reset
#ifndef PICO_BOOTSEL_VIA_DOUBLE_RESET_TIMEOUT_MS
#define PICO_BOOTSEL_VIA_DOUBLE_RESET_TIMEOUT_MS 200
#endif
// PICO_CONFIG: PICO_BOOTSEL_VIA_DOUBLE_RESET_ACTIVITY_LED, Optionally define a pin to use as bootloader activity LED when BOOTSEL mode is entered via reset double tap, type=int, min=0, max=29, group=pico_bootsel_via_double_reset
// PICO_CONFIG: PICO_BOOTSEL_VIA_DOUBLE_RESET_INTERFACE_DISABLE_MASK, Optionally disable either the mass storage interface (bit 0) or the PICOBOOT interface (bit 1) when entering BOOTSEL mode via double reset, type=int, min=0, max=3, default=0, group=pico_bootsel_via_double_reset
#ifndef PICO_BOOTSEL_VIA_DOUBLE_RESET_INTERFACE_DISABLE_MASK
#define PICO_BOOTSEL_VIA_DOUBLE_RESET_INTERFACE_DISABLE_MASK 0u
#endif
/** \defgroup pico_bootsel_via_double_reset pico_bootsel_via_double_reset
*
* When the 'pico_bootsel_via_double_reset' library is linked, a function is
* injected before main() which will detect when the system has been reset
* twice in quick succession, and enter the USB ROM bootloader (BOOTSEL mode)
* when this happens. This allows a double tap of a reset button on a
* development board to be used to enter the ROM bootloader, provided this
* library is always linked.
*/
#if !PICO_NO_BI_BOOTSEL_VIA_DOUBLE_RESET
bi_decl(bi_program_feature("double reset -> BOOTSEL"));
#endif
// Doesn't make any sense for a RAM only binary
#if !PICO_NO_FLASH
static const uint32_t magic_token[] = {
0xf01681de, 0xbd729b29, 0xd359be7a,
};
static uint32_t __uninitialized_ram(magic_location)[count_of(magic_token)];
/* Check for double reset and enter BOOTSEL mode if detected
*
* This function is registered to run automatically before main(). The
* algorithm is:
*
* 1. Check for magic token in memory; enter BOOTSEL mode if found.
* 2. Initialise that memory with that magic token.
* 3. Do nothing for a short while (few hundred ms).
* 4. Clear the magic token.
* 5. Continue with normal boot.
*
* Resetting the device twice quickly will interrupt step 3, leaving the token
* in place so that the second boot will go to the bootloader.
*/
static void __attribute__((constructor)) boot_double_tap_check(void) {
for (uint i = 0; i < count_of(magic_token); i++) {
if (magic_location[i] != magic_token[i]) {
// Arm, wait, then disarm and continue booting
for (i = 0; i < count_of(magic_token); i++) {
magic_location[i] = magic_token[i];
}
busy_wait_us(PICO_BOOTSEL_VIA_DOUBLE_RESET_TIMEOUT_MS * 1000);
magic_location[0] = 0;
return;
}
}
// Detected a double reset, so enter USB bootloader
magic_location[0] = 0;
#ifdef PICO_BOOTSEL_VIA_DOUBLE_RESET_ACTIVITY_LED
const uint32_t led_mask = 1u << PICO_BOOTSEL_VIA_DOUBLE_RESET_ACTIVITY_LED;
#else
const uint32_t led_mask = 0u;
#endif
reset_usb_boot(
led_mask,
PICO_BOOTSEL_VIA_DOUBLE_RESET_INTERFACE_DISABLE_MASK
);
}
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_cxx_options/CMakeLists.txt
================================================
if (NOT TARGET pico_cxx_options)
add_library(pico_cxx_options INTERFACE)
# PICO_CMAKE_CONFIG: PICO_CXX_ENABLE_EXCEPTIONS, Enabled CXX exception handling, type=bool, default=0, group=pico_cxx_options
# PICO_BUILD_DEFINE: PICO_CXX_ENABLE_EXCEPTIONS, value of CMake var PICO_CXX_ENABLE_EXCEPTIONS, type=string, default=0, group=pico_cxx_options
if (NOT PICO_CXX_ENABLE_EXCEPTIONS)
target_compile_definitions( pico_cxx_options INTERFACE PICO_CXX_ENABLE_EXCEPTIONS=0)
target_compile_options( pico_cxx_options INTERFACE $<$:-fno-exceptions>)
target_compile_options( pico_cxx_options INTERFACE $<$:-fno-unwind-tables>)
else()
target_compile_definitions( pico_cxx_options INTERFACE PICO_CXX_ENABLE_EXCEPTIONS=1)
endif()
# PICO_CMAKE_CONFIG: PICO_CXX_ENABLE_RTTI, Enabled CXX rtti, type=bool, default=0, group=pico_cxx_options
if (NOT PICO_CXX_ENABLE_RTTI)
target_compile_options( pico_cxx_options INTERFACE $<$:-fno-rtti>)
endif()
# PICO_CMAKE_CONFIG: PICO_CXX_ENABLE_CXA_ATEXIT, Enabled cxa-atexit, type=bool, default=0, group=pico_cxx_options
if (NOT PICO_CXX_ENABLE_CXA_ATEXIT)
target_compile_options( pico_cxx_options INTERFACE $<$:-fno-use-cxa-atexit>)
endif()
endif()
================================================
FILE: pico-sdk/src/rp2_common/pico_cxx_options/doc.h
================================================
/**
* \defgroup pico_cxx_options pico_cxx_options
* \brief non-code library controlling C++ related compile options
*/
================================================
FILE: pico-sdk/src/rp2_common/pico_divider/CMakeLists.txt
================================================
if (NOT TARGET pico_divider)
# library to be depended on - we make this depend on particular implementations using per target generator expressions
pico_add_impl_library(pico_divider)
# no custom implementation; falls thru to compiler
pico_add_library(pico_divider_compiler)
# add alias "default" which is just hardware.
add_library(pico_divider_default INTERFACE)
target_link_libraries(pico_divider_default INTERFACE pico_divider_hardware)
set(PICO_DEFAULT_DIVIDER_IMPL pico_divider_default)
target_link_libraries(pico_divider INTERFACE
$>,$,${PICO_DEFAULT_DIVIDER_IMPL}>)
add_library(pico_divider_hardware_explicit INTERFACE)
target_sources(pico_divider_hardware_explicit INTERFACE
${CMAKE_CURRENT_LIST_DIR}/divider.S
)
target_link_libraries(pico_divider_hardware_explicit INTERFACE
pico_divider_headers
hardware_divider
hardware_regs
)
pico_add_library(pico_divider_hardware)
target_link_libraries(pico_divider_hardware INTERFACE pico_divider_hardware_explicit)
pico_wrap_function(pico_divider_hardware __aeabi_idiv)
pico_wrap_function(pico_divider_hardware __aeabi_idivmod)
pico_wrap_function(pico_divider_hardware __aeabi_ldivmod)
pico_wrap_function(pico_divider_hardware __aeabi_uidiv)
pico_wrap_function(pico_divider_hardware __aeabi_uidivmod)
pico_wrap_function(pico_divider_hardware __aeabi_uldivmod)
macro(pico_set_divider_implementation TARGET IMPL)
get_target_property(target_type ${TARGET} TYPE)
if ("EXECUTABLE" STREQUAL "${target_type}")
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_DIVIDER_IMPL "pico_divider_${IMPL}")
else()
message(FATAL_ERROR "divider implementation must be set on executable not library")
endif()
endmacro()
endif()
================================================
FILE: pico-sdk/src/rp2_common/pico_divider/divider.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/asm_helper.S"
#include "hardware/regs/addressmap.h"
#include "hardware/divider_helper.S"
// PICO_CONFIG: PICO_DIVIDER_DISABLE_INTERRUPTS, Disable interrupts around division such that divider state need not be saved/restored in exception handlers, default=0, group=pico_divider
// PICO_CONFIG: PICO_DIVIDER_CALL_IDIV0, Whether 32 bit division by zero should call __aeabi_idiv0, default=1, group=pico_divider
#ifndef PICO_DIVIDER_CALL_IDIV0
#define PICO_DIVIDER_CALL_IDIV0 1
#endif
// PICO_CONFIG: PICO_DIVIDER_CALL_IDIV0, Whether 64 bit division by zero should call __aeabi_ldiv0, default=1, group=pico_divider
#ifndef PICO_DIVIDER_CALL_LDIV0
#define PICO_DIVIDER_CALL_LDIV0 1
#endif
pico_default_asm_setup
// PICO_CONFIG: PICO_DIVIDER_IN_RAM, Whether divider functions should be placed in RAM, default=0, group=pico_divider
.macro div_section name
#if PICO_DIVIDER_IN_RAM
.section RAM_SECTION_NAME(\name), "ax"
#else
.section SECTION_NAME(\name), "ax"
#endif
.endm
@ wait 8-n cycles for the hardware divider
.macro wait_div n
.rept (8-\n) / 2
b 9f
9:
.endr
.if (8-\n) % 2
nop
.endif
.endm
#if (SIO_DIV_SDIVISOR_OFFSET != SIO_DIV_SDIVIDEND_OFFSET + 4) || (SIO_DIV_QUOTIENT_OFFSET != SIO_DIV_SDIVISOR_OFFSET + 4) || (SIO_DIV_REMAINDER_OFFSET != SIO_DIV_QUOTIENT_OFFSET + 4)
#error register layout has changed - we rely on this order to make sure we save/restore in the right order
#endif
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
.macro save_div_state_and_lr_64
push {r4, r5, r6, r7, lr}
ldr r6, =SIO_BASE
// note we must read quotient last, and since it isn't the last reg, we'll not use ldmia!
ldr r4, [r6, #SIO_DIV_UDIVIDEND_OFFSET]
ldr r5, [r6, #SIO_DIV_UDIVISOR_OFFSET]
// No need to wait before reading result as long as preceeding code takes more than 8 cycles
ldr r7, [r6, #SIO_DIV_REMAINDER_OFFSET]
ldr r6, [r6, #SIO_DIV_QUOTIENT_OFFSET]
.endm
.macro restore_div_state_and_return_64
// writing sdividend (r4), sdivisor (r5), quotient (r6), remainder (r7) in that order
//
// it is worth considering what happens if we are interrupted
//
// after writing r4: we are DIRTY and !READY
// ... interruptor using div will complete based on incorrect inputs, but dividend at least will be
// saved/restored correctly and we'll restore the rest ourselves
// after writing r4, r5: we are DIRTY and !READY
// ... interruptor using div will complete based on possibly wrongly signed inputs, but dividend, divisor
// at least will be saved/restored correctly and and we'll restore the rest ourselves
// after writing r4, r5, r6: we are DIRTY and READY
// ... interruptor using div will dividend, divisor, quotient registers as is (what we just restored ourselves),
// and we'll restore the remainder after the fact
mov ip, r2
ldr r2, =SIO_BASE
// note we are not use STM not because it can be restarted due to interrupt which is harmless, more because this is 1 cycle IO space
// and so 4 reads is cheaper (and we don't have to adjust r2)
str r4, [r2, #SIO_DIV_UDIVIDEND_OFFSET]
str r5, [r2, #SIO_DIV_UDIVISOR_OFFSET]
str r7, [r2, #SIO_DIV_REMAINDER_OFFSET]
str r6, [r2, #SIO_DIV_QUOTIENT_OFFSET]
mov r2, ip
pop {r4, r5, r6, r7, pc}
.endm
#endif /* !PICO_DIVIDER_DISABLE_INTERRUPTS */
// since idiv and idivmod only differ by a cycle, we'll make them the same!
div_section WRAPPER_FUNC_NAME(__aeabi_idiv)
.align 2
wrapper_func __aeabi_idiv
wrapper_func __aeabi_idivmod
regular_func div_s32s32
regular_func divmod_s32s32
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
// to support IRQ usage (or context switch) we must save/restore divider state around call if state is dirty
ldr r2, =SIO_BASE
ldr r3, [r2, #SIO_DIV_CSR_OFFSET]
lsrs r3, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
bcs divmod_s32s32_savestate
regular_func divmod_s32s32_unsafe
#else
// to avoid too much source code spaghetti with restoring interrupts, we make this the same as the other funcs
// in the PICO_DIVIDER_DISABLE_INTERRUPTS case; i.e. it is not a faster function; this seems reasonable as there
// are the hardware_divider functions that can be used instead anyway
regular_func divmod_s32s32_unsafe
// to avoid worrying about IRQs (or context switches), simply disable interrupts around call
ldr r2, =SIO_BASE
mrs r3, PRIMASK
cpsid i
#endif /* !PICO_DIVIDER_DISABLE_INTERRUPTS */
str r0, [r2, #SIO_DIV_SDIVIDEND_OFFSET]
str r1, [r2, #SIO_DIV_SDIVISOR_OFFSET]
cmp r1, #0
beq 1f
wait_div 2
// return 64 bit value so we can efficiently return both (note read order is important since QUOTIENT must be read last)
ldr r1, [r2, #SIO_DIV_REMAINDER_OFFSET]
ldr r0, [r2, #SIO_DIV_QUOTIENT_OFFSET]
#if PICO_DIVIDER_DISABLE_INTERRUPTS
msr PRIMASK, r3
#endif /* PICO_DIVIDER_DISABLE_INTERRUPTS */
bx lr
1:
#if PICO_DIVIDER_DISABLE_INTERRUPTS
msr PRIMASK, r3
#endif /* PICO_DIVIDER_DISABLE_INTERRUPTS */
push {r2, lr}
movs r1, #0x80
lsls r1, #24
asrs r2, r0, #31
eors r1, r2
cmp r0, #0
beq 1f
mvns r0, r1
1:
#if PICO_DIVIDER_CALL_IDIV0
bl __aeabi_idiv0
#endif
movs r1, #0 // remainder 0
// need to restore saved r2 as it hold SIO ptr
pop {r2, pc}
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
.align 2
regular_func divmod_s32s32_savestate
save_div_state_and_lr
bl divmod_s32s32_unsafe
restore_div_state_and_return
#endif /* !PICO_DIVIDER_DISABLE_INTERRUPTS */
// since uidiv and uidivmod only differ by a cycle, we'll make them the same!
div_section WRAPPER_FUNC_NAME(__aeabi_uidiv)
regular_func div_u32u32
regular_func divmod_u32u32
wrapper_func __aeabi_uidiv
wrapper_func __aeabi_uidivmod
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
// to support IRQ usage (or context switch) we must save/restore divider state around call if state is dirty
ldr r2, =SIO_BASE
ldr r3, [r2, #SIO_DIV_CSR_OFFSET]
lsrs r3, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
bcs divmod_u32u32_savestate
regular_func divmod_u32u32_unsafe
#else
// to avoid too much source code spaghetti with restoring interrupts, we make this the same as the other funcs
// in the PICO_DIVIDER_DISABLE_INTERRUPTS case; i.e. it is not a faster function; this seems reasonable as there
// are the hardware_divider functions that can be used instead anyway
regular_func divmod_u32u32_unsafe
// to avoid worrying about IRQs (or context switches), simply disable interrupts around call
ldr r2, =SIO_BASE
mrs r3, PRIMASK
cpsid i
#endif /* !PICO_DIVIDER_DISABLE_INTERRUPTS */
str r0, [r2, #SIO_DIV_UDIVIDEND_OFFSET]
str r1, [r2, #SIO_DIV_UDIVISOR_OFFSET]
cmp r1, #0
beq 1f
wait_div 2
// return 64 bit value so we can efficiently return both (note read order is important since QUOTIENT must be read last)
ldr r1, [r2, #SIO_DIV_REMAINDER_OFFSET]
ldr r0, [r2, #SIO_DIV_QUOTIENT_OFFSET]
#if PICO_DIVIDER_DISABLE_INTERRUPTS
msr PRIMASK, r3
#endif /* PICO_DIVIDER_DISABLE_INTERRUPTS */
bx lr
1:
#if PICO_DIVIDER_DISABLE_INTERRUPTS
msr PRIMASK, r3
#endif /* PICO_DIVIDER_DISABLE_INTERRUPTS */
push {r2, lr}
cmp r0, #0
beq 1f
movs r0, #0
mvns r0, r0
1:
#if PICO_DIVIDER_CALL_IDIV0
bl __aeabi_idiv0
#endif
movs r1, #0 // remainder 0
// need to restore saved r2 as it hold SIO ptr
pop {r2, pc}
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
.align 2
regular_func divmod_u32u32_savestate
save_div_state_and_lr
bl divmod_u32u32_unsafe
restore_div_state_and_return
#endif /* !PICO_DIVIDER_DISABLE_INTERRUPTS */
div_section WRAPPER_FUNC_NAME(__aeabi_ldiv)
.align 2
wrapper_func __aeabi_ldivmod
regular_func div_s64s64
regular_func divmod_s64s64
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
// to support IRQ usage (or context switch) we must save/restore divider state around call if state is dirty
mov ip, r2
ldr r2, =SIO_BASE
ldr r2, [r2, #SIO_DIV_CSR_OFFSET]
lsrs r2, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
mov r2, ip
bcs divmod_s64s64_savestate
b divmod_s64s64_unsafe
.align 2
divmod_s64s64_savestate:
save_div_state_and_lr_64
bl divmod_s64s64_unsafe
restore_div_state_and_return_64
#else
// to avoid worrying about IRQs (or context switches), simply disable interrupts around call
push {r4, lr}
mrs r4, PRIMASK
cpsid i
bl divmod_s64s64_unsafe
msr PRIMASK, r4
pop {r4, pc}
#endif /* !PICO_DIVIDER_DISABLE_INTERRUPTS */
.align 2
wrapper_func __aeabi_uldivmod
regular_func div_u64u64
regular_func divmod_u64u64
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
// to support IRQ usage (or context switch) we must save/restore divider state around call if state is dirty
mov ip, r2
ldr r2, =SIO_BASE
ldr r2, [r2, #SIO_DIV_CSR_OFFSET]
lsrs r2, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
mov r2, ip
bcs divmod_u64u64_savestate
b divmod_u64u64_unsafe
.align 2
regular_func divmod_u64u64_savestate
save_div_state_and_lr_64
bl divmod_u64u64_unsafe
restore_div_state_and_return_64
#else
// to avoid worrying about IRQs (or context switches), simply disable interrupts around call
push {r4, lr}
mrs r4, PRIMASK
cpsid i
bl divmod_u64u64_unsafe
msr PRIMASK, r4
pop {r4, pc}
#endif /* !PICO_DIVIDER_DISABLE_INTERRUPTS */
.macro dneg lo,hi
mvns \hi,\hi
negs \lo,\lo
bne l\@_1
adds \hi,#1
l\@_1:
.endm
.align 2
regular_func divmod_s64s64_unsafe
cmp r3,#0
blt 1f
@ here x +ve
beq 2f @ could x be zero?
3:
cmp r1,#0
bge divmod_u64u64_unsafe @ both positive
@ y -ve, x +ve
push {r14}
dneg r0,r1
bl divmod_u64u64_unsafe
dneg r0,r1
dneg r2,r3
pop {r15}
2:
cmp r2,#0
bne 3b @ back if x not zero
cmp r0,#0 @ y==0?
bne 4f
cmp r1,#0
beq 5f @ then pass 0 to __aeabi_ldiv0
4:
movs r0,#0
lsrs r1,#31
lsls r1,#31 @ get sign bit
bne 5f @ y -ve? pass -2^63 to __aeabi_ldiv0
mvns r0,r0
lsrs r1,r0,#1 @ y +ve: pass 2^63-1 to __aeabi_ldiv0
5:
push {r14}
#if PICO_DIVIDER_CALL_LDIV0
bl __aeabi_ldiv0
#endif
movs r2,#0 @ and return 0 for the remainder
movs r3,#0
pop {r15}
1:
@ here x -ve
push {r14}
cmp r1,#0
blt 1f
@ y +ve, x -ve
dneg r2,r3
bl divmod_u64u64_unsafe
dneg r0,r1
pop {r15}
1:
@ y -ve, x -ve
dneg r0,r1
dneg r2,r3
bl divmod_u64u64_unsafe
dneg r2,r3
pop {r15}
regular_func divmod_u64u64_unsafe
cmp r1,#0
bne y64 @ y fits in 32 bits?
cmp r3,#0 @ yes; and x?
bne 1f
cmp r2,#0
beq 2f @ x==0?
mov r12,r7
ldr r7,=SIO_BASE
str r0,[r7,#SIO_DIV_UDIVIDEND_OFFSET]
str r2,[r7,#SIO_DIV_UDIVISOR_OFFSET]
movs r1,#0
movs r3,#0
wait_div 2
ldr r2,[r7,#SIO_DIV_REMAINDER_OFFSET]
ldr r0,[r7,#SIO_DIV_QUOTIENT_OFFSET]
mov r7,r12
bx r14
2: @ divide by 0 with y<2^32
cmp r0,#0 @ y==0?
beq 3f @ then pass 0 to __aeabi_ldiv0
udiv0:
ldr r0,=0xffffffff
movs r1,r0 @ pass 2^64-1 to __aeabi_ldiv0
3:
push {r14}
#if PICO_DIVIDER_CALL_LDIV0
bl __aeabi_ldiv0
#endif
movs r2,#0 @ and return 0 for the remainder
movs r3,#0
pop {r15}
1:
movs r2,r0 @ x>y, so result is 0 remainder y
movs r3,r1
movs r0,#0
movs r1,#0
bx r14
.ltorg
@ here y occupies more than 32 bits
@ split into cases acccording to the size of x
y64:
cmp r3,#0
beq 1f
b y64_x48 @ if x does not fit in 32 bits, go to 48- and 64-bit cases
1:
lsrs r3,r2,#16
bne y64_x32 @ jump if x is 17..32 bits
@ here x is at most 16 bits
cmp r2,#0
beq udiv0 @ x==0? exit as with y!=0 case above
push {r7}
ldr r7,=SIO_BASE
str r1,[r7,#SIO_DIV_UDIVIDEND_OFFSET]
str r2,[r7,#SIO_DIV_UDIVISOR_OFFSET]
wait_div 4
push {r4, r5}
lsrs r4,r0,#16
ldr r3,[r7,#SIO_DIV_REMAINDER_OFFSET] @ r0=y0-q0*x; 0<=r0>16);
str r2,[r7,#SIO_DIV_UDIVISOR_OFFSET] @ must set divisor again, as we do not save/restore regs at all in IRQs if not dirty
wait_div 1
uxth r4,r0
ldr r3,[r7,#SIO_DIV_REMAINDER_OFFSET] @ r1=y1-q1*x; 0<=r1>16);
str r2,[r7,#SIO_DIV_UDIVISOR_OFFSET] @ must set divisor again, as we do not save/restore regs at all in IRQs if not dirty
wait_div 3
movs r3,#0
lsls r4,r5,#16 @ quotient=(q0<<32)+(q1<<16)+q2
lsrs r5,#16
ldr r2,[r7,#SIO_DIV_REMAINDER_OFFSET] @ r2=y2-q2*x; 0<=r2>15)+1; 2^16>48)*r)>>16;
lsls r7,r6,#13
mov r14,r7 @ quh=q0<<13
muls r3,r6 @ x0l*q
lsrs r7,r3,#15
lsls r3,#17 @ r3:r7 is (x0l*q)<<17
subs r0,r3
sbcs r1,r7 @ y-=(x0l*q)<<17
lsrs r3,r2,#16 @ x0h
muls r3,r6 @ q*x0h
adds r3,r3
subs r1,r3 @ y-=(x0h*q)<<17
lsrs r6,r1,#3
muls r6,r4
lsrs r6,#16 @ q=((ui32)(y>>35)*r)>>16;
add r14,r6 @ quh+=q1
uxth r3,r2 @ x0l
muls r3,r6 @ x0l*q
lsrs r7,r3,#28
lsls r3,#4 @ r3:r7 is (x0l*q)<<4
subs r0,r3
sbcs r1,r7 @ y-=(x0l*q)<<4
lsrs r3,r2,#16 @ x0h
muls r3,r6 @ x0h*q
lsrs r7,r3,#12
lsls r3,#20 @ r3:r7 is (x0h*q)<<4
subs r0,r3
sbcs r1,r7 @ y-=(x0h*q)<<4
lsrs r6,r0,#22
lsls r7,r1,#10
orrs r6,r7 @ y>>22
muls r6,r4
lsrs r6,#16 @ q=((ui32)(y>>22)*r)>>16;
cmp r5,#9
blt last0 @ if(xsh<9) goto last0;
@ on this path xsh>=9, which means x<2^23
lsrs r2,#9 @ x0>>9: this shift loses no bits
@ the remainder y-x0*q is guaranteed less than a very small multiple of the remaining quotient
@ bits (at most 6 bits) times x, and so fits in one word
muls r2,r6 @ x0*q
subs r0,r2 @ y-x0*q
lsls r7,r6,#13 @ qul=q<<13
1:
lsrs r6,r0,#9
muls r6,r4
lsrs r6,#16 @ q=((ui32)(y>>9)*r)>>16;
@ here
@ r0 y
@ r2 x0>>9
@ r5 xsh
@ r6 q
@ r7 qul
@ r12 x
@ r14 quh
movs r3,#22
subs r3,r5 @ 22-xsh
lsrs r6,r3 @ q>>=22-xsh
lsrs r7,r3 @ qul>>=22-xsh
adds r7,r6 @ qul+=q
mov r4,r12
muls r6,r4 @ x*q
subs r2,r0,r6 @ y-=x*q
mov r0,r14 @ quh
adds r5,#4 @ xsh+4
adds r3,#6 @ 28-xsh
movs r1,r0
lsrs r1,r3
lsls r0,r5 @ r0:r1 is quh<<(4+xsh)
adds r0,r7
bcc 1f
2:
adds r1,#1
1: @ qu=((ui64)quh<<(4+xsh))+qul
cmp r2,r4
bhs 3f
movs r3,#0
pop {r4-r7,r15}
.ltorg
3:
subs r2,r4
adds r0,#1
bcc 1b
b 2b @ while(y>=x) y-=x,qu++;
@ here:
@ r0:r1 y
@ r2 x0
@ r4 r
@ r5 xsh; xsh<9
@ r6 q
last0:
movs r7,#9
subs r7,r5 @ 9-xsh
lsrs r6,r7
mov r4,r12 @ x
uxth r2,r4
muls r2,r6 @ q*xlo
subs r0,r2
bcs 1f
subs r1,#1 @ y-=q*xlo
1:
lsrs r2,r4,#16 @ xhi
muls r2,r6 @ q*xhi
lsrs r3,r2,#16
lsls r2,#16
subs r2,r0,r2
sbcs r1,r3 @ y-q*xhi
movs r3,r1 @ y now in r2:r3
mov r0,r14 @ quh
adds r5,#4 @ xsh+4
adds r7,#19 @ 28-xsh
movs r1,r0
lsrs r1,r7
lsls r0,r5 @ r0:r1 is quh<<(4+xsh)
adds r0,r6
bcc 1f
adds r1,#1 @ quh<<(xsh+4))+q
1:
cmp r3,#0 @ y>=2^32?
bne 3f
cmp r2,r4 @ y>=x?
bhs 4f
pop {r4-r7,r15}
3:
adds r0,#1 @ qu++
bcc 2f
adds r1,#1
2:
subs r2,r4 @ y-=x
bcs 3b
subs r3,#1
bne 3b
1:
cmp r2,r4
bhs 4f
pop {r4-r7,r15}
4:
adds r0,#1 @ qu++
bcc 2f
adds r1,#1
2:
subs r2,r4 @ y-=x
b 1b
y64_x48:
@ here x is 33..64 bits
push {r4-r7,r14} @ save a copy of x
lsrs r4,r3,#16
beq 1f
b y64_x64 @ jump if x is 49..64 bits
1:
push {r2-r3} @ save a copy of x
@ here x is 33..48 bits
movs r5,#0 @ xsh=0
lsrs r4,r3,#8
bne 1f
lsls r3,#8
lsrs r6,r2,#24
orrs r3,r6
lsls r2,#8 @ if(x0<1U<<40) x0<<=8,xsh =8;
adds r5,#8
1:
lsrs r4,r3,#12
bne 1f
lsls r3,#4
lsrs r6,r2,#28
orrs r3,r6
lsls r2,#4 @ if(x0<1U<<44) x0<<=4,xsh+=4;
adds r5,#4
1:
lsrs r4,r3,#14
bne 1f
lsls r3,#2
lsrs r6,r2,#30
orrs r3,r6
lsls r2,#2 @ if(x0<1U<<46) x0<<=2,xsh+=2;
adds r5,#2
1:
lsrs r4,r3,#15
bne 1f
adds r2,r2
adcs r3,r3 @ if(x0<1U<<47) x0<<=1,xsh+=1;
adds r5,#1
1:
@ now 2^47<=x0<2^48, 0<=xsh<16 (amount x is shifted in x0); number of quotient bits to be calculated qb=xsh+17 17<=qb<33
movs r4,r3
adds r7,r2,r2
adcs r4,r4
adds r4,#1 @ x1=(ui32)(x0>>31)+1; // 2^16>48)*r)>>16;
lsls r7,r6,#13
mov r14,r7 @ save q<<13
uxth r7,r2 @ x0l
muls r7,r6
subs r0,r7
bcs 1f
subs r1,#1
1:
subs r0,r7
bcs 1f
subs r1,#1
1:
uxth r7,r3 @ x0h
muls r7,r6
subs r1,r7
subs r1,r7
lsrs r7,r2,#16 @ x0m
muls r7,r6
lsls r6,r7,#17
lsrs r7,#15
subs r0,r6
sbcs r1,r7 @ y-=((ui64)q*x0)<<1;
lsrs r6,r1,#3 @ y>>35
muls r6,r4
lsrs r6,#16 @ q=((ui32)(y>>35)*r)>>16;
cmp r5,#12
blt last1 @ if(xsh<12) goto last1;
add r14,r6 @ qu<<13+q
lsrs r2,#12
lsls r7,r3,#20
orrs r2,r7
lsrs r3,#12 @ x0>>12
uxth r7,r2 @ x0l
muls r7,r6
subs r0,r7
bcs 1f
subs r1,#1
1:
uxth r7,r3 @ x0h
muls r7,r6
subs r1,r7
lsrs r7,r2,#16 @ x0m
muls r7,r6
lsls r6,r7,#16
lsrs r7,#16
subs r0,r6
sbcs r1,r7 @ y-=((ui64)q*x0)>>12
lsrs r6,r0,#22
lsls r7,r1,#10
orrs r6,r7 @ y>>22
muls r6,r4
movs r7,#41
subs r7,r5
lsrs r6,r7 @ q=((ui32)(y>>22)*r)>>(16+25-xsh)
subs r5,#12
mov r7,r14
lsls r7,r5
2:
adds r7,r6 @ qu=(qu<<(xsh-12))+q
pop {r4,r5} @ recall x
@ here
@ r0:r1 y
@ r4:r5 x
@ r6 q
@ r7 qu
uxth r2,r4
uxth r3,r5
muls r2,r6 @ xlo*q
muls r3,r6 @ xhi*q
subs r0,r2
sbcs r1,r3
lsrs r2,r4,#16
muls r2,r6
lsrs r3,r2,#16
lsls r2,#16 @ xm*q
subs r0,r2
sbcs r1,r3 @ y-=(ui64)q*x
1:
movs r2,r0
movs r3,r1
adds r7,#1
subs r0,r4
sbcs r1,r5 @ while(y>=x) y-=x,qu++;
bhs 1b
subs r0,r7,#1 @ correction to qu
movs r1,#0
pop {r4-r7,r15}
last1:
@ r0:r1 y
@ r2:r3 x0
@ r5 xsh
@ r6 q
movs r7,#12
subs r7,r5
lsrs r6,r7 @ q>>=12-xsh
mov r7,r14
lsrs r7,#13
lsls r7,r5
adds r7,r7 @ qu<<(xsh+1)
b 2b
y64_x64:
@ here x is 49..64 bits
movs r4,#0 @ q=0 if x>>32==0xffffffff
adds r5,r3,#1
beq 1f
ldr r7,=SIO_BASE
str r5,[r7,#SIO_DIV_UDIVISOR_OFFSET]
str r1,[r7,#SIO_DIV_UDIVIDEND_OFFSET]
wait_div 0
ldr r4,[r7,#SIO_DIV_QUOTIENT_OFFSET] @ q=(ui32)(y>>32)/((x>>32)+1)
1:
uxth r5,r2
uxth r6,r3
muls r5,r4
muls r6,r4
subs r0,r5
sbcs r1,r6
lsrs r5,r2,#16
lsrs r6,r3,#16
muls r5,r4
muls r6,r4
lsls r6,#16
lsrs r7,r5,#16
orrs r6,r7
lsls r5,#16
subs r0,r5
sbcs r1,r6 @ y-=(ui64)q*x
cmp r1,r3 @ while(y>=x) y-=x,q++
bhs 1f
3:
movs r2,r0
movs r3,r1
movs r0,r4
movs r1,#0
pop {r4-r7,r15}
1:
bne 2f
cmp r0,r2
blo 3b
2:
subs r0,r2
sbcs r1,r3
adds r4,#1
cmp r1,r3
blo 3b
b 1b
div_section divmod_s64s64_rem
regular_func divmod_s64s64_rem
push {r4, lr}
bl divmod_s64s64
ldr r4, [sp, #8]
stmia r4!, {r2,r3}
pop {r4, pc}
div_section divmod_u64u64_rem
regular_func divmod_u64u64_rem
push {r4, lr}
bl divmod_u64u64
ldr r4, [sp, #8]
stmia r4!, {r2,r3}
pop {r4, pc}
================================================
FILE: pico-sdk/src/rp2_common/pico_double/CMakeLists.txt
================================================
if (NOT TARGET pico_double)
# library to be depended on - we make this depend on particular implementations using per target generator expressions
pico_add_library(pico_double)
# no custom implementation; falls thru to compiler
pico_add_library(pico_double_compiler)
target_include_directories(pico_double_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
# add alias "default" which is just pico.
add_library(pico_double_default INTERFACE)
target_link_libraries(pico_double_default INTERFACE pico_double_pico)
set(PICO_DEFAULT_DOUBLE_IMPL pico_double_default)
target_link_libraries(pico_double INTERFACE
$>,$,${PICO_DEFAULT_DOUBLE_IMPL}>)
pico_add_library(pico_double_pico)
target_sources(pico_double_pico INTERFACE
${CMAKE_CURRENT_LIST_DIR}/double_aeabi.S
${CMAKE_CURRENT_LIST_DIR}/double_init_rom.c
${CMAKE_CURRENT_LIST_DIR}/double_math.c
${CMAKE_CURRENT_LIST_DIR}/double_v1_rom_shim.S
)
target_link_libraries(pico_double_pico INTERFACE pico_bootrom pico_double_headers hardware_divider)
pico_add_library(pico_double_none)
target_sources(pico_double_none INTERFACE
${CMAKE_CURRENT_LIST_DIR}/double_none.S
)
target_link_libraries(pico_double_none INTERFACE pico_double_headers)
target_compile_definitions(pico_double_none_headers INTERFACE
PICO_PRINTF_SUPPORT_FLOAT=0 # printing floats/doubles won't work, so we can save space by removing it
)
function(wrap_double_functions TARGET)
pico_wrap_function(${TARGET} __aeabi_dadd)
pico_wrap_function(${TARGET} __aeabi_ddiv)
pico_wrap_function(${TARGET} __aeabi_dmul)
pico_wrap_function(${TARGET} __aeabi_drsub)
pico_wrap_function(${TARGET} __aeabi_dsub)
pico_wrap_function(${TARGET} __aeabi_cdcmpeq)
pico_wrap_function(${TARGET} __aeabi_cdrcmple)
pico_wrap_function(${TARGET} __aeabi_cdcmple)
pico_wrap_function(${TARGET} __aeabi_dcmpeq)
pico_wrap_function(${TARGET} __aeabi_dcmplt)
pico_wrap_function(${TARGET} __aeabi_dcmple)
pico_wrap_function(${TARGET} __aeabi_dcmpge)
pico_wrap_function(${TARGET} __aeabi_dcmpgt)
pico_wrap_function(${TARGET} __aeabi_dcmpun)
pico_wrap_function(${TARGET} __aeabi_i2d)
pico_wrap_function(${TARGET} __aeabi_l2d)
pico_wrap_function(${TARGET} __aeabi_ui2d)
pico_wrap_function(${TARGET} __aeabi_ul2d)
pico_wrap_function(${TARGET} __aeabi_d2iz)
pico_wrap_function(${TARGET} __aeabi_d2lz)
pico_wrap_function(${TARGET} __aeabi_d2uiz)
pico_wrap_function(${TARGET} __aeabi_d2ulz)
pico_wrap_function(${TARGET} __aeabi_d2f)
pico_wrap_function(${TARGET} sqrt)
pico_wrap_function(${TARGET} cos)
pico_wrap_function(${TARGET} sin)
pico_wrap_function(${TARGET} tan)
pico_wrap_function(${TARGET} atan2)
pico_wrap_function(${TARGET} exp)
pico_wrap_function(${TARGET} log)
pico_wrap_function(${TARGET} ldexp)
pico_wrap_function(${TARGET} copysign)
pico_wrap_function(${TARGET} trunc)
pico_wrap_function(${TARGET} floor)
pico_wrap_function(${TARGET} ceil)
pico_wrap_function(${TARGET} round)
pico_wrap_function(${TARGET} sincos) # gnu
pico_wrap_function(${TARGET} asin)
pico_wrap_function(${TARGET} acos)
pico_wrap_function(${TARGET} atan)
pico_wrap_function(${TARGET} sinh)
pico_wrap_function(${TARGET} cosh)
pico_wrap_function(${TARGET} tanh)
pico_wrap_function(${TARGET} asinh)
pico_wrap_function(${TARGET} acosh)
pico_wrap_function(${TARGET} atanh)
pico_wrap_function(${TARGET} exp2)
pico_wrap_function(${TARGET} log2)
pico_wrap_function(${TARGET} exp10)
pico_wrap_function(${TARGET} log10)
pico_wrap_function(${TARGET} pow)
pico_wrap_function(${TARGET} powint) #gnu
pico_wrap_function(${TARGET} hypot)
pico_wrap_function(${TARGET} cbrt)
pico_wrap_function(${TARGET} fmod)
pico_wrap_function(${TARGET} drem)
pico_wrap_function(${TARGET} remainder)
pico_wrap_function(${TARGET} remquo)
pico_wrap_function(${TARGET} expm1)
pico_wrap_function(${TARGET} log1p)
pico_wrap_function(${TARGET} fma)
endfunction()
wrap_double_functions(pico_double_pico)
wrap_double_functions(pico_double_none)
macro(pico_set_double_implementation TARGET IMPL)
get_target_property(target_type ${TARGET} TYPE)
if ("EXECUTABLE" STREQUAL "${target_type}")
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_DOUBLE_IMPL "pico_double_${IMPL}")
else()
message(FATAL_ERROR "double implementation must be set on executable not library")
endif()
endmacro()
endif()
================================================
FILE: pico-sdk/src/rp2_common/pico_double/double_aeabi.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/asm_helper.S"
#include "pico/bootrom/sf_table.h"
#include "hardware/divider_helper.S"
pico_default_asm_setup
__pre_init __aeabi_double_init, 00020
.macro double_section name
#if PICO_DOUBLE_IN_RAM
.section RAM_SECTION_NAME(\name), "ax"
#else
.section SECTION_NAME(\name), "ax"
#endif
.endm
.macro _double_wrapper_func x
wrapper_func \x
.endm
.macro wrapper_func_d1 x
_double_wrapper_func \x
#if PICO_DOUBLE_PROPAGATE_NANS
mov ip, lr
bl __check_nan_d1
mov lr, ip
#endif
.endm
.macro wrapper_func_d2 x
_double_wrapper_func \x
#if PICO_DOUBLE_PROPAGATE_NANS
mov ip, lr
bl __check_nan_d2
mov lr, ip
#endif
.endm
.section .text
#if PICO_DOUBLE_PROPAGATE_NANS
.thumb_func
__check_nan_d1:
movs r3, #1
lsls r3, #21
lsls r2, r1, #1
adds r2, r3
bhi 1f
bx lr
1:
bx ip
.thumb_func
__check_nan_d2:
push {r0, r2}
movs r2, #1
lsls r2, #21
lsls r0, r1, #1
adds r0, r2
bhi 1f
lsls r0, r3, #1
adds r0, r2
bhi 2f
pop {r0, r2}
bx lr
2:
pop {r0, r2}
mov r0, r2
mov r1, r3
bx ip
1:
pop {r0, r2}
bx ip
#endif
.macro table_tail_call SF_TABLE_OFFSET
push {r3, r4}
#if PICO_DOUBLE_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
#ifndef NDEBUG
movs r3, #0
mov ip, r3
#endif
#endif
ldr r3, =sd_table
ldr r3, [r3, #\SF_TABLE_OFFSET]
str r3, [sp, #4]
pop {r3, pc}
.endm
.macro shimmable_table_tail_call SF_TABLE_OFFSET shim
push {r3, r4}
ldr r3, =sd_table
ldr r3, [r3, #\SF_TABLE_OFFSET]
#if PICO_DOUBLE_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
mov ip, pc
#endif
str r3, [sp, #4]
pop {r3, pc}
#if PICO_DOUBLE_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
.byte \SF_TABLE_OFFSET, 0xdf
.word \shim
#endif
.endm
.macro double_wrapper_section func
double_section WRAPPER_FUNC_NAME(\func)
.endm
double_section push_r8_r11
regular_func push_r8_r11
mov r4,r8
mov r5,r9
mov r6,r10
mov r7,r11
push {r4-r7}
bx r14
double_section pop_r8_r11
regular_func pop_r8_r11
pop {r4-r7}
mov r8,r4
mov r9,r5
mov r10,r6
mov r11,r7
bx r14
// note generally each function is in a separate section unless there is fall thru or branching between them
// note fadd, fsub, fmul, fdiv are so tiny and just defer to rom so are lumped together so they can share constant pool
// note functions are word aligned except where they are an odd number of linear instructions
// double FUNC_NAME(__aeabi_dadd)(double, double) double-precision addition
double_wrapper_section __aeabi_darithmetic
// double FUNC_NAME(__aeabi_drsub)(double x, double y) double-precision reverse subtraction, y - x
// frsub first because it is the only one that needs alignment
.align 2
wrapper_func __aeabi_drsub
eors r0, r1
eors r1, r0
eors r0, r1
// fall thru
// double FUNC_NAME(__aeabi_dsub)(double x, double y) double-precision subtraction, x - y
wrapper_func_d2 __aeabi_dsub
#if PICO_DOUBLE_PROPAGATE_NANS
// we want to return nan for inf-inf or -inf - -inf, but without too much upfront cost
mov ip, r0
mov r0, r1
eors r0, r3
bmi 1f // different signs
mov r0, ip
push {r0-r3, lr}
bl 2f
b ddiv_dsub_nan_helper
1:
mov r0, ip
2:
#endif
shimmable_table_tail_call SF_TABLE_FSUB dsub_shim
wrapper_func_d2 __aeabi_dadd
shimmable_table_tail_call SF_TABLE_FADD dadd_shim
// double FUNC_NAME(__aeabi_ddiv)(double n, double d) double-precision division, n / d
wrapper_func_d2 __aeabi_ddiv
#if PICO_DOUBLE_PROPAGATE_NANS
push {r0-r3, lr}
bl 1f
b ddiv_dsub_nan_helper
1:
#endif
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
// to support IRQ usage (or context switch) we must save/restore divider state around call if state is dirty
mov ip, r2
ldr r2, =(SIO_BASE)
ldr r2, [r2, #SIO_DIV_CSR_OFFSET]
lsrs r2, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
bcs ddiv_save_state
mov r2, ip
#else
// to avoid worrying about IRQs (or context switches), simply disable interrupts around call
push {r4, lr}
mrs r4, PRIMASK
cpsid i
bl ddiv_shim_call
msr PRIMASK, r4
pop {r4, pc}
#endif
ddiv_shim_call:
shimmable_table_tail_call SF_TABLE_FDIV ddiv_shim
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
ddiv_save_state:
ldr r2, =(SIO_BASE)
save_div_state_and_lr
mov r2, ip
bl ddiv_shim_call
ldr r2, =(SIO_BASE)
restore_div_state_and_return
#endif
ddiv_dsub_nan_helper:
#if PICO_DOUBLE_PROPAGATE_NANS
// check for infinite op infinite (or rather check for infinite result with both
// operands being infinite)
lsls r2, r1, #1
asrs r2, r2, #21
adds r2, #1
beq 2f
add sp, #16
pop {pc}
2:
ldr r2, [sp, #4]
ldr r3, [sp, #12]
lsls r2, #1
asrs r2, r2, #21
lsls r3, #1
asrs r3, r3, #24
ands r2, r3
adds r2, #1
bne 3f
// infinite to nan
movs r2, #1
lsls r2, #19
orrs r1, r2
3:
add sp, #16
pop {pc}
#endif
// double FUNC_NAME(__aeabi_dmul)(double, double) double-precision multiplication
wrapper_func_d2 __aeabi_dmul
#if PICO_DOUBLE_PROPAGATE_NANS
push {r0-r3, lr}
bl 1f
// check for multiplication of infinite by zero (or rather check for infinite result with either
// operand 0)
lsls r3, r1, #1
asrs r3, r3, #21
adds r3, #1
beq 2f
add sp, #16
pop {pc}
2:
ldr r2, [sp, #4]
ldr r3, [sp, #12]
ands r2, r3
bne 3f
// infinite to nan
movs r2, #1
lsls r2, #19
orrs r1, r2
3:
add sp, #16
pop {pc}
1:
#endif
shimmable_table_tail_call SF_TABLE_FMUL dmul_shim
// void FUNC_NAME(__aeabi_cdrcmple)(double, double) reversed 3-way (<, =, ?>) compare [1], result in PSR ZC flags
double_wrapper_section __aeabi_cdcmple
wrapper_func __aeabi_cdrcmple
push {r0-r7,r14}
eors r0, r2
eors r2, r0
eors r0, r2
eors r1, r3
eors r3, r1
eors r1, r3
b __aeabi_dfcmple_guts
// NOTE these share an implementation as we have no excepting NaNs.
// void FUNC_NAME(__aeabi_cdcmple)(double, double) 3-way (<, =, ?>) compare [1], result in PSR ZC flags
// void FUNC_NAME(__aeabi_cdcmpeq)(double, double) non-excepting equality comparison [1], result in PSR ZC flags
@ compare r0:r1 against r2:r3, returning -1/0/1 for <, =, >
@ also set flags accordingly
.align 2
wrapper_func __aeabi_cdcmple
wrapper_func __aeabi_cdcmpeq
push {r0-r7,r14}
__aeabi_dfcmple_guts:
ldr r7,=0x7ff @ flush NaNs and denormals
lsls r4,r1,#1
lsrs r4,#21
beq 1f
cmp r4,r7
bne 2f
lsls r4, r1, #12
bhi 7f
1:
movs r0,#0
lsrs r1,#20
lsls r1,#20
2:
lsls r4,r3,#1
lsrs r4,#21
beq 1f
cmp r4,r7
bne 2f
lsls r4, r3, #12
bhi 7f
1:
movs r2,#0
lsrs r3,#20
lsls r3,#20
2:
movs r6,#1
eors r3,r1
bmi 4f @ opposite signs? then can proceed on basis of sign of x
eors r3,r1 @ restore r3
bpl 2f
cmp r3,r1
bne 7f
1:
cmp r2,r0
7:
pop {r0-r7,r15}
2:
cmp r1,r3
bne 7b
1:
cmp r0,r2
pop {r0-r7,r15}
4:
orrs r3,r1 @ make -0==+0
adds r3,r3
orrs r3,r0
orrs r3,r2
beq 7b
mvns r1, r1 @ carry inverse of r1 sign
adds r1, r1
pop {r0-r7,r15}
// int FUNC_NAME(__aeabi_dcmpeq)(double, double) result (1, 0) denotes (=, ?<>) [2], use for C == and !=
double_wrapper_section __aeabi_dcmpeq
.align 2
wrapper_func __aeabi_dcmpeq
push {lr}
bl __aeabi_cdcmpeq
beq 1f
movs r0, #0
pop {pc}
1:
movs r0, #1
pop {pc}
// int FUNC_NAME(__aeabi_dcmplt)(double, double) result (1, 0) denotes (<, ?>=) [2], use for C <
double_wrapper_section __aeabi_dcmplt
.align 2
wrapper_func __aeabi_dcmplt
push {lr}
bl __aeabi_cdcmple
sbcs r0, r0
pop {pc}
// int FUNC_NAME(__aeabi_dcmple)(double, double) result (1, 0) denotes (<=, ?>) [2], use for C <=
double_wrapper_section __aeabi_dcmple
.align 2
wrapper_func __aeabi_dcmple
push {lr}
bl __aeabi_cdcmple
bls 1f
movs r0, #0
pop {pc}
1:
movs r0, #1
pop {pc}
// int FUNC_NAME(__aeabi_dcmpge)(double, double) result (1, 0) denotes (>=, ?<) [2], use for C >=
double_wrapper_section __aeabi_dcmpge
.align 2
wrapper_func __aeabi_dcmpge
push {lr}
// because of NaNs it is better to reverse the args than the result
bl __aeabi_cdrcmple
bls 1f
movs r0, #0
pop {pc}
1:
movs r0, #1
pop {pc}
// int FUNC_NAME(__aeabi_dcmpgt)(double, double) result (1, 0) denotes (>, ?<=) [2], use for C >
double_wrapper_section __aeabi_dcmpgt
wrapper_func __aeabi_dcmpgt
push {lr}
// because of NaNs it is better to reverse the args than the result
bl __aeabi_cdrcmple
sbcs r0, r0
pop {pc}
// int FUNC_NAME(__aeabi_dcmpun)(double, double) result (1, 0) denotes (?, <=>) [2], use for C99 isunordered()
double_wrapper_section __aeabi_dcmpun
wrapper_func __aeabi_dcmpun
movs r0, #1
lsls r0, #21
lsls r2, r1, #1
adds r2, r0
bhi 1f
lsls r2, r3, #1
adds r2, r0
bhi 1f
movs r0, #0
bx lr
1:
movs r0, #1
bx lr
movs r0, #0
bx lr
// double FUNC_NAME(__aeabi_ui2d)(unsigned) unsigned to double (double precision) conversion
double_wrapper_section __aeabi_ui2d
shimmable_table_tail_call SF_TABLE_UINT2FLOAT uint2double_shim
double_wrapper_section __aeabi_i2d
wrapper_func __aeabi_ui2d
movs r1, #0
cmp r0, #0
bne 2f
1:
bx lr
// double FUNC_NAME(__aeabi_i2d)(int) integer to double (double precision) conversion
wrapper_func __aeabi_i2d
asrs r1, r0, #31
eors r0, r1
subs r0, r1
beq 1b
lsls r1, #31
2:
push {r0, r1, r4, lr}
ldr r3, =sf_clz_func
ldr r3, [r3]
blx r3
pop {r2, r3}
adds r4, r0, #1
lsls r2, r4
lsls r0, r2, #20
lsrs r2, #12
ldr r1,=1055
subs r1, r4
lsls r1, #20
orrs r1, r3
orrs r1, r2
pop {r4, pc}
// int FUNC_NAME(__aeabi_d2iz)(double) double (double precision) to integer C-style conversion [3]
double_wrapper_section __aeabi_d2iz
wrapper_func __aeabi_d2iz
regular_func double2int_z
push {r4, lr}
lsls r4, r1, #1
lsrs r2, r4, #21
movs r3, #0x80
adds r2, r3
lsls r3, #3
subs r2, r3
lsls r3, #21
cmp r2, #126
ble 1f
subs r2, #158
bge 2f
asrs r4, r1, #31
lsls r1, #12
lsrs r1, #1
orrs r1, r3
negs r2, r2
lsrs r1, r2
lsls r4, #1
adds r4, #1
adds r2, #21
cmp r2, #32
bge 3f
lsrs r0, r2
orrs r0, r1
muls r0, r4
pop {r4, pc}
1:
movs r0, #0
pop {r4, pc}
3:
mov r0, r1
muls r0, r4
pop {r4, pc}
2:
// overflow
lsrs r0, r1, #31
adds r0, r3
subs r0, #1
pop {r4, pc}
double_section double2int
regular_func double2int
shimmable_table_tail_call SF_TABLE_FLOAT2INT double2int_shim
// unsigned FUNC_NAME(__aeabi_d2uiz)(double) double (double precision) to unsigned C-style conversion [3]
double_wrapper_section __aeabi_d2uiz
wrapper_func __aeabi_d2uiz
regular_func double2uint
shimmable_table_tail_call SF_TABLE_FLOAT2UINT double2uint_shim
double_section fix2double
regular_func fix2double
shimmable_table_tail_call SF_TABLE_FIX2FLOAT fix2double_shim
double_section ufix2double
regular_func ufix2double
shimmable_table_tail_call SF_TABLE_UFIX2FLOAT ufix2double_shim
double_section fix642double
regular_func fix642double
shimmable_table_tail_call SF_TABLE_FIX642FLOAT fix642double_shim
double_section ufix2double
regular_func ufix642double
shimmable_table_tail_call SF_TABLE_UFIX642FLOAT ufix642double_shim
// double FUNC_NAME(__aeabi_l2d)(long long) long long to double (double precision) conversion
double_wrapper_section __aeabi_l2d
wrapper_func __aeabi_l2d
shimmable_table_tail_call SF_TABLE_INT642FLOAT int642double_shim
// double FUNC_NAME(__aeabi_l2f)(long long) long long to double (double precision) conversion
double_wrapper_section __aeabi_ul2d
wrapper_func __aeabi_ul2d
shimmable_table_tail_call SF_TABLE_UINT642FLOAT uint642double_shim
// long long FUNC_NAME(__aeabi_d2lz)(double) double (double precision) to long long C-style conversion [3]
double_wrapper_section __aeabi_d2lz
wrapper_func __aeabi_d2lz
regular_func double2int64_z
cmn r1, r1
bcc double2int64
push {lr}
lsls r1, #1
lsrs r1, #1
movs r2, #0
bl double2ufix64
cmp r1, #0
bmi 1f
movs r2, #0
negs r0, r0
sbcs r2, r1
mov r1, r2
pop {pc}
1:
movs r1, #128
lsls r1, #24
movs r0, #0
pop {pc}
double_section double2int64
regular_func double2int64
shimmable_table_tail_call SF_TABLE_FLOAT2INT64 double2int64_shim
// unsigned long long FUNC_NAME(__aeabi_d2ulz)(double) double to unsigned long long C-style conversion [3]
double_wrapper_section __aeabi_d2ulz
wrapper_func __aeabi_d2ulz
shimmable_table_tail_call SF_TABLE_FLOAT2UINT64 double2uint64_shim
double_section double2fix64
regular_func double2fix64
shimmable_table_tail_call SF_TABLE_FLOAT2FIX64 double2fix64_shim
double_section double2ufix64
regular_func double2ufix64
shimmable_table_tail_call SF_TABLE_FLOAT2UFIX64 double2ufix64_shim
double_section double2fix
regular_func double2fix
shimmable_table_tail_call SF_TABLE_FLOAT2FIX double2fix_shim
double_section double2ufix
regular_func double2ufix
shimmable_table_tail_call SF_TABLE_FLOAT2UFIX double2ufix_shim
double_wrapper_section __aeabi_d2f
1:
#if PICO_DOUBLE_PROPAGATE_NANS
// copy sign bit and 23 NAN id bits into sign bit and significant id bits, also set high id bit
lsrs r0, #30
lsls r2, r1, #12
lsrs r2, #9
asrs r1, #22
lsls r1, #22
orrs r0, r1
orrs r0, r2
bx lr
#endif
wrapper_func __aeabi_d2f
#if PICO_DOUBLE_PROPAGATE_NANS
movs r3, #1
lsls r3, #21
lsls r2, r1, #1
adds r2, r3
bhi 1b
#endif
// note double->float in double table at same index as float->double in double table
shimmable_table_tail_call SF_TABLE_FLOAT2DOUBLE double2float_shim
double_wrapper_section srqt
wrapper_func_d1 sqrt
shimmable_table_tail_call SF_TABLE_FSQRT dsqrt_shim
double_wrapper_section sincostan_remainder
regular_func sincostan_remainder
ldr r2, =0x54442D18 // 2 * M_PI
ldr r3, =0x401921FB
push {lr}
// note remainder only uses the divider thru integer divider functions
// which save and restore themselves
bl remainder
pop {pc}
double_wrapper_section cos
#don't use _d1 as we're doing a range check anyway and infinites/nans are bigger than 1024
wrapper_func cos
// rom version only works for -1024 < angle < 1024
lsls r2, r1, #2
bcc 1f
lsrs r2, #22
cmp r2, #9
bge 2f
1:
shimmable_table_tail_call SF_TABLE_FCOS dcos_shim
2:
#if PICO_DOUBLE_PROPAGATE_NANS
lsls r2, r1, #1
asrs r2, #21
adds r2, #1
bne 3f
// infinite to nan
movs r2, #1
lsls r2, #19
orrs r1, r2
bx lr
3:
#endif
push {lr}
bl sincostan_remainder
pop {r2}
mov lr, r2
b 1b
double_wrapper_section sin
#don't use _d1 as we're doing a range check anyway and infinites/nans are bigger than 1024
wrapper_func sin
// rom version only works for -1024 < angle < 1024
lsls r2, r1, #2
bcc 1f
lsrs r2, #22
cmp r2, #9
bge 2f
1:
shimmable_table_tail_call SF_TABLE_FSIN dsin_shim
2:
#if PICO_DOUBLE_PROPAGATE_NANS
lsls r2, r1, #1
asrs r2, #21
adds r2, #1
bne 3f
// infinite to nan
movs r2, #1
lsls r2, #19
orrs r1, r2
bx lr
3:
#endif
push {lr}
bl sincostan_remainder
pop {r2}
mov lr, r2
b 1b
double_wrapper_section sincos
// out of line remainder code for abs(angle)>=1024
2:
#if PICO_DOUBLE_PROPAGATE_NANS
lsls r2, r1, #1
asrs r2, #21
adds r2, #1
bne 3f
// infinite to nan
movs r2, #1
lsls r2, #19
orrs r1, r2
pop {r4-r5}
stmia r4!, {r0, r1}
stmia r5!, {r0, r1}
pop {r4, r5, pc}
3:
#endif
push {lr}
bl sincostan_remainder
pop {r2}
mov lr, r2
b 1f // continue with sincos
wrapper_func sincos
push {r2-r5, lr}
// rom version only works for -1024 < angle < 1024
lsls r2, r1, #2
bcc 1f
lsrs r2, #22
cmp r2, #9
bge 2b
1:
bl 2f // call the shim
pop {r4-r5}
stmia r4!, {r0, r1}
stmia r5!, {r2, r3}
pop {r4, r5, pc}
2:
shimmable_table_tail_call SF_TABLE_V3_FSINCOS sincos_shim_bootstrap
.thumb_func
sincos_shim_bootstrap:
push {r2, r3, r4}
movs r3, #0x13
ldrb r3, [r3]
#if PICO_DOUBLE_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
cmp r3, #1
bne 1f
ldr r3, =dsincos_shim
b 2f
#endif
1:
ldr r3, =dsincos_shim_v2
2:
ldr r2, =sd_table
str r3, [r2, #SF_TABLE_V3_FSINCOS]
str r3, [sp, #8]
pop {r2, r3, pc}
.thumb_func
dsincos_shim_v2:
push {r4-r7,r14}
bl push_r8_r11
bl v2_rom_dsincos_internal
mov r12,r0 @ save ε
bl v2_rom_dcos_finish
push {r0,r1}
mov r0,r12
bl v2_rom_dsin_finish
pop {r2,r3}
bl pop_r8_r11
pop {r4-r7,r15}
.thumb_func
v2_rom_dsincos_internal:
push {r0, lr}
ldr r0, =0x3855
str r0, [sp, #4]
pop {r0, pc}
.thumb_func
v2_rom_dcos_finish:
push {r0, r1}
ldr r0, =0x389d
str r0, [sp, #4]
pop {r0, pc}
.thumb_func
v2_rom_dsin_finish:
push {r0, r1}
ldr r0, =0x38d9
str r0, [sp, #4]
pop {r0, pc}
double_wrapper_section tan
#don't use _d1 as we're doing a range check anyway and infinites/nans are bigger than 1024
wrapper_func tan
// rom version only works for -1024 < angle < 1024
lsls r2, r1, #2
bcc dtan_in_range
lsrs r2, #22
cmp r2, #9
bge dtan_angle_out_of_range
dtan_in_range:
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
// to support IRQ usage (or context switch) we must save/restore divider state around call if state is dirty
mov ip, r2
ldr r2, =(SIO_BASE)
ldr r2, [r2, #SIO_DIV_CSR_OFFSET]
lsrs r2, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
bcs dtan_save_state
mov r2, ip
#else
// to avoid worrying about IRQs (or context switches), simply disable interrupts around call
push {r4, lr}
mrs r4, PRIMASK
cpsid i
bl dtan_shim_call
msr PRIMASK, r4
pop {r4, pc}
#endif
dtan_shim_call:
shimmable_table_tail_call SF_TABLE_FTAN dtan_shim
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
dtan_save_state:
ldr r2, =(SIO_BASE)
save_div_state_and_lr
mov r2, ip
bl dtan_shim_call
ldr r2, =(SIO_BASE)
restore_div_state_and_return
#endif
dtan_angle_out_of_range:
#if PICO_DOUBLE_PROPAGATE_NANS
lsls r2, r1, #1
asrs r2, #21
adds r2, #1
bne 3f
// infinite to nan
movs r2, #1
lsls r2, #19
orrs r1, r2
bx lr
3:
#endif
push {lr}
bl sincostan_remainder
pop {r2}
mov lr, r2
b dtan_in_range
double_wrapper_section atan2
wrapper_func_d2 atan2
shimmable_table_tail_call SF_TABLE_FATAN2 datan2_shim
double_wrapper_section exp
wrapper_func_d1 exp
shimmable_table_tail_call SF_TABLE_FEXP dexp_shim
double_wrapper_section log
wrapper_func_d1 log
shimmable_table_tail_call SF_TABLE_FLN dln_shim
================================================
FILE: pico-sdk/src/rp2_common/pico_double/double_init_rom.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
#include "pico/bootrom.h"
#include "pico/bootrom/sf_table.h"
// NOTE THIS FUNCTION TABLE IS NOT PUBLIC OR NECESSARILY COMPLETE...
// IT IS ***NOT*** SAFE TO CALL THESE FUNCTION POINTERS FROM ARBITRARY CODE
uint32_t sd_table[SF_TABLE_V2_SIZE / 2];
#if !(PICO_DOUBLE_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED)
static __attribute__((noreturn)) void missing_double_func_shim(void) {
panic("missing double function");
}
#endif
extern void double_table_shim_on_use_helper(void);
void __attribute__((weak)) *sf_clz_func;
void __aeabi_double_init(void) {
int rom_version = rp2040_rom_version();
#if PICO_DOUBLE_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
if (rom_version == 1) {
// this is a little tricky.. we only want to pull in a shim if the corresponding function
// is called. to that end we include a SVC instruction with the table offset as the call number
// followed by the shim function pointer inside the actual wrapper function. that way if the wrapper
// function is garbage collected, so is the shim function.
//
// double_table_shim_on_use_helper expects this SVC instruction in the calling code soon after the address
// pointed to by IP and patches the double_table entry with the real shim the first time the function is called.
for(uint i=0; i= 2) {
void *rom_table = rom_data_lookup(rom_table_code('S', 'D'));
assert(*((uint8_t *)rom_data_lookup(rom_table_code('S', 'F'))-2) * 4 >= SF_TABLE_V2_SIZE);
memcpy(&sd_table, rom_table, SF_TABLE_V2_SIZE);
if (rom_version == 2) {
#ifndef NDEBUG
if (*(uint16_t *)0x3854 != 0xb500 || // this is dsincos(_internal)
*(uint16_t *)0x38d8 != 0x4649 || // this is dsin_finish
*(uint16_t *)0x389c != 0x4659 // this is dcos_finish
) {
panic(NULL);
}
#endif
}
}
if (rom_version < 3) {
// we use the unused entry for SINCOS
sd_table[SF_TABLE_V3_FSINCOS / 4] = (uintptr_t) double_table_shim_on_use_helper;
}
sf_clz_func = rom_func_lookup(ROM_FUNC_CLZ32);
}
================================================
FILE: pico-sdk/src/rp2_common/pico_double/double_math.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
#include "pico/double.h"
// opened a separate issue https://github.com/raspberrypi/pico-sdk/issues/166 to deal with these warnings if at all
GCC_Pragma("GCC diagnostic push")
GCC_Pragma("GCC diagnostic ignored \"-Wconversion\"")
GCC_Pragma("GCC diagnostic ignored \"-Wsign-conversion\"")
typedef uint64_t ui64;
typedef uint32_t ui32;
typedef int64_t i64;
#define PINF ( HUGE_VAL)
#define MINF (-HUGE_VAL)
#define PZERO (+0.0)
#define MZERO (-0.0)
#define PI 3.14159265358979323846
#define LOG2 0.69314718055994530941
// Unfortunately in double precision ln(10) is very close to half-way between to representable numbers
#define LOG10 2.30258509299404568401
#define LOG2E 1.44269504088896340737
#define LOG10E 0.43429448190325182765
#define ONETHIRD 0.33333333333333333333
#define PIf 3.14159265358979323846f
#define LOG2f 0.69314718055994530941f
#define LOG2Ef 1.44269504088896340737f
#define LOG10Ef 0.43429448190325182765f
#define ONETHIRDf 0.33333333333333333333f
#define DUNPACK(x,e,m) e=((x)>>52)&0x7ff,m=((x)&0x000fffffffffffffULL)|0x0010000000000000ULL
#define DUNPACKS(x,s,e,m) s=((x)>>63),DUNPACK((x),(e),(m))
typedef union {
double d;
ui64 ix;
} double_ui64;
static inline double ui642double(ui64 ix) {
double_ui64 tmp;
tmp.ix = ix;
return tmp.d;
}
static inline ui64 double2ui64(double d) {
double_ui64 tmp;
tmp.d = d;
return tmp.ix;
}
#if PICO_DOUBLE_PROPAGATE_NANS
static inline bool disnan(double x) {
ui64 ix= double2ui64(x);
// checks the top bit of the low 32 bit of the NAN, but it I think that is ok
return ((uint32_t)(ix >> 31)) > 0xffe00000u;
}
#define check_nan_d1(x) if (disnan((x))) return (x)
#define check_nan_d2(x,y) if (disnan((x))) return (x); else if (disnan((y))) return (y);
#else
#define check_nan_d1(x) ((void)0)
#define check_nan_d2(x,y) ((void)0)
#endif
static inline int dgetsignexp(double x) {
ui64 ix=double2ui64(x);
return (ix>>52)&0xfff;
}
static inline int dgetexp(double x) {
ui64 ix=double2ui64(x);
return (ix>>52)&0x7ff;
}
static inline double dldexp(double x,int de) {
ui64 ix=double2ui64(x),iy;
int e;
e=dgetexp(x);
if(e==0||e==0x7ff) return x;
e+=de;
if(e<=0) iy=ix&0x8000000000000000ULL; // signed zero for underflow
else if(e>=0x7ff) iy=(ix&0x8000000000000000ULL)|0x7ff0000000000000ULL; // signed infinity on overflow
else iy=ix+((ui64)de<<52);
return ui642double(iy);
}
double WRAPPER_FUNC(ldexp)(double x, int de) {
check_nan_d1(x);
return dldexp(x, de);
}
static inline double dcopysign(double x,double y) {
ui64 ix=double2ui64(x),iy=double2ui64(y);
ix=((ix&0x7fffffffffffffffULL)|(iy&0x8000000000000000ULL));
return ui642double(ix);
}
double WRAPPER_FUNC(copysign)(double x, double y) {
check_nan_d2(x,y);
return dcopysign(x, y);
}
static inline int diszero(double x) { return dgetexp (x)==0; }
//static inline int dispzero(double x) { return dgetsignexp(x)==0; }
//static inline int dismzero(double x) { return dgetsignexp(x)==0x800; }
static inline int disinf(double x) { return dgetexp (x)==0x7ff; }
static inline int dispinf(double x) { return dgetsignexp(x)==0x7ff; }
static inline int disminf(double x) { return dgetsignexp(x)==0xfff; }
static inline int disint(double x) {
ui64 ix=double2ui64(x),m;
int e=dgetexp(x);
if(e==0) return 1; // 0 is an integer
e-=0x3ff; // remove exponent bias
if(e<0) return 0; // |x|<1
e=52-e; // bit position in mantissa with significance 1
if(e<=0) return 1; // |x| large, so must be an integer
m=(1ULL<>e)&1;
}
static inline int disstrictneg(double x) {
ui64 ix=double2ui64(x);
if(diszero(x)) return 0;
return ix>>63;
}
static inline int disneg(double x) {
ui64 ix=double2ui64(x);
return ix>>63;
}
static inline double dneg(double x) {
ui64 ix=double2ui64(x);
ix^=0x8000000000000000ULL;
return ui642double(ix);
}
static inline int dispo2(double x) {
ui64 ix=double2ui64(x);
if(diszero(x)) return 0;
if(disinf(x)) return 0;
ix&=0x000fffffffffffffULL;
return ix==0;
}
static inline double dnan_or(double x) {
#if PICO_DOUBLE_PROPAGATE_NANS
return NAN;
#else
return x;
#endif
}
double WRAPPER_FUNC(trunc)(double x) {
check_nan_d1(x);
ui64 ix=double2ui64(x),m;
int e=dgetexp(x);
e-=0x3ff; // remove exponent bias
if(e<0) { // |x|<1
ix&=0x8000000000000000ULL;
return ui642double(ix);
}
e=52-e; // bit position in mantissa with significance 1
if(e<=0) return x; // |x| large, so must be an integer
m=(1ULL<=5+0x3ff) { // |x|>=32?
if(!disneg(x)) return 1; // 1 << exp 2x; avoid generating infinities later
else return -1; // 1 >> exp 2x
}
u=exp(dldexp(x,1));
return (u-1)/(u+1);
}
double WRAPPER_FUNC(asinh)(double x) {
check_nan_d1(x);
int e;
e=dgetexp(x);
if(e>=32+0x3ff) { // |x|>=2^32?
if(!disneg(x)) return log( x )+LOG2; // 1/x^2 << 1
else return dneg(log(dneg(x))+LOG2); // 1/x^2 << 1
}
if(x>0) return log(sqrt(x*x+1)+x);
else return dneg(log(sqrt(x*x+1)-x));
}
double WRAPPER_FUNC(acosh)(double x) {
check_nan_d1(x);
int e;
if(disneg(x)) x=dneg(x);
e=dgetexp(x);
if(e>=32+0x3ff) return log(x)+LOG2; // |x|>=2^32?
return log(sqrt((x-1)*(x+1))+x);
}
double WRAPPER_FUNC(atanh)(double x) {
check_nan_d1(x);
return dldexp(log((1+x)/(1-x)),-1);
}
double WRAPPER_FUNC(exp2)(double x) {
check_nan_d1(x);
int e;
// extra check for disminf as this catches -Nan, and x<=-4096 doesn't.
if (disminf(x) || x<=-4096) return 0; // easily underflows
else if (x>=4096) return PINF; // easily overflows
e=(int)round(x);
x-=e;
return dldexp(exp(x*LOG2),e);
}
double WRAPPER_FUNC(log2)(double x) { check_nan_d1(x); return log(x)*LOG2E; }
double WRAPPER_FUNC(exp10)(double x) { check_nan_d1(x); return pow(10,x); }
double WRAPPER_FUNC(log10)(double x) { check_nan_d1(x); return log(x)*LOG10E; }
// todo these are marked as lofi
double WRAPPER_FUNC(expm1(double x) { check_nan_d1(x); return exp)(x)-1; }
double WRAPPER_FUNC(log1p(double x) { check_nan_d1(x); return log)(1+x); }
double WRAPPER_FUNC(fma)(double x,double y,double z) { check_nan_d1(x); return x*y+z; }
// general power, x>0, finite
static double dpow_1(double x,double y) {
int a,b,c;
double t,rt,u,v,v0,v1,w,ry;
a=dgetexp(x)-0x3ff;
u=log2(dldexp(x,-a)); // now log_2 x = a+u
if(u>0.5) u-=1,a++; // |u|<=~0.5
if(a==0) return exp2(u*y);
// here |log_2 x| >~0.5
if(y>= 4096) { // then easily over/underflows
if(a<0) return 0;
return PINF;
}
if(y<=-4096) { // then easily over/underflows
if(a<0) return PINF;
return 0;
}
ry=round(y);
v=y-ry;
v0=dldexp(round(ldexp(v,26)),-26);
v1=v-v0;
b=(int)ry; // guaranteed to fit in an int; y=b+v0+v1
// now the result is exp2( (a+u) * (b+v0+v1) )
c=a*b; // integer
t=a*v0;
rt=round(t);
c+=(int)rt;
w=t-rt;
t=a*v1;
w+=t;
t=u*b;
rt=round(t);
c+=(int)rt;
w+=t-rt;
w+=u*v;
return dldexp(exp2(w),c);
}
static double dpow_int2(double x,int y) {
double u;
if(y==1) return x;
u=dpow_int2(x,y/2);
u*=u;
if(y&1) u*=x;
return u;
}
// for the case where x not zero or infinity, y small and not zero
static inline double dpowint_1(double x,int y) {
if(y<0) x=1/x,y=-y;
return dpow_int2(x,y);
}
// for the case where x not zero or infinity
static double dpowint_0(double x,int y) {
int e;
if(disneg(x)) {
if(disoddint(y)) return dneg(dpowint_0(dneg(x),y));
else return dpowint_0(dneg(x),y);
}
if(dispo2(x)) {
e=dgetexp(x)-0x3ff;
if(y>=2048) y= 2047; // avoid overflow
if(y<-2048) y=-2048;
y*=e;
return dldexp(1,y);
}
if(y==0) return 1;
if(y>=-32&&y<=32) return dpowint_1(x,y);
return dpow_1(x,y);
}
double WRAPPER_FUNC(powint)(double x,int y) {
GCC_Like_Pragma("GCC diagnostic push")
GCC_Like_Pragma("GCC diagnostic ignored \"-Wfloat-equal\"")
if(x==1.0||y==0) return 1;
GCC_Like_Pragma("GCC diagnostic pop")
check_nan_d1(x);
if(diszero(x)) {
if(y>0) {
if(y&1) return x;
else return 0;
}
if((y&1)) return dcopysign(PINF,x);
return PINF;
}
if(dispinf(x)) {
if(y<0) return 0;
else return PINF;
}
if(disminf(x)) {
if(y>0) {
if((y&1)) return MINF;
else return PINF;
}
if((y&1)) return MZERO;
else return PZERO;
}
return dpowint_0(x,y);
}
// for the case where y is guaranteed a finite integer, x not zero or infinity
static double dpow_0(double x,double y) {
int e,p;
if(disneg(x)) {
if(disoddint(y)) return dneg(dpow_0(dneg(x),y));
else return dpow_0(dneg(x),y);
}
p=(int)y;
if(dispo2(x)) {
e=dgetexp(x)-0x3ff;
if(p>=2048) p= 2047; // avoid overflow
if(p<-2048) p=-2048;
p*=e;
return dldexp(1,p);
}
if(p==0) return 1;
if(p>=-32&&p<=32) return dpowint_1(x,p);
return dpow_1(x,y);
}
double WRAPPER_FUNC(pow)(double x,double y) {
GCC_Like_Pragma("GCC diagnostic push")
GCC_Like_Pragma("GCC diagnostic ignored \"-Wfloat-equal\"")
if(x==1.0||diszero(y)) return 1;
check_nan_d2(x, y);
if(x==-1.0&&disinf(y)) return 1;
GCC_Like_Pragma("GCC diagnostic pop")
if(diszero(x)) {
if(!disneg(y)) {
if(disoddint(y)) return x;
else return 0;
}
if(disoddint(y)) return dcopysign(PINF,x);
return PINF;
}
if(dispinf(x)) {
if(disneg(y)) return 0;
else return PINF;
}
if(disminf(x)) {
if(!disneg(y)) {
if(disoddint(y)) return MINF;
else return PINF;
}
if(disoddint(y)) return MZERO;
else return PZERO;
}
if(dispinf(y)) {
if(dgetexp(x)<0x3ff) return PZERO;
else return PINF;
}
if(disminf(y)) {
if(dgetexp(x)<0x3ff) return PINF;
else return PZERO;
}
if(disint(y)) return dpow_0(x,y);
if(disneg(x)) return PINF;
return dpow_1(x,y);
}
double WRAPPER_FUNC(hypot)(double x,double y) {
check_nan_d2(x, y);
int ex,ey;
ex=dgetexp(x); ey=dgetexp(y);
if(ex>=0x3ff+400||ey>=0x3ff+400) { // overflow, or nearly so
x=dldexp(x,-600),y=dldexp(y,-600);
return dldexp(sqrt(x*x+y*y), 600);
}
else if(ex<=0x3ff-400&&ey<=0x3ff-400) { // underflow, or nearly so
x=dldexp(x, 600),y=dldexp(y, 600);
return dldexp(sqrt(x*x+y*y),-600);
}
return sqrt(x*x+y*y);
}
double WRAPPER_FUNC(cbrt)(double x) {
check_nan_d1(x);
int e;
if(disneg(x)) return dneg(cbrt(dneg(x)));
if(diszero(x)) return dcopysign(PZERO,x);
e=dgetexp(x)-0x3ff;
e=(e*0x5555+0x8000)>>16; // ~e/3, rounded
x=dldexp(x,-e*3);
x=exp(log(x)*ONETHIRD);
return dldexp(x,e);
}
// reduces mx*2^e modulo my, returning bottom bits of quotient at *pquo
// 2^52<=|mx|,my<2^53, e>=0; 0<=result0) {
r=0xffffffffU/(ui32)(my>>36); // reciprocal estimate Q16
}
while(e>0) {
s=e; if(s>12) s=12; // gain up to 12 bits on each iteration
q=(mx>>38)*r; // Q30
q=((q>>(29-s))+1)>>1; // Q(s), rounded
mx=(mx<=my) mx-=my,quo++; // when e==0 mx can be nearly as big as 2my
if(mx>=my) mx-=my,quo++;
if(mx<0) mx+=my,quo--;
if(mx<0) mx+=my,quo--;
if(pquo) *pquo=quo;
return mx;
}
double WRAPPER_FUNC(fmod)(double x,double y) {
check_nan_d2(x, y);
ui64 ix=double2ui64(x),iy=double2ui64(y);
int sx,ex,ey;
i64 mx,my;
DUNPACKS(ix,sx,ex,mx);
DUNPACK(iy,ey,my);
if(ex==0x7ff) return dnan_or(PINF);
if(ey==0) return PINF;
if(ex==0) {
if(!disneg(x)) return PZERO;
return MZERO;
}
if(ex|y|/2
mx-=my+my;
ey--;
q=1;
} else { // x<-|y|/2
mx=my+my-mx;
ey--;
q=-1;
}
}
else {
if(sx) mx=-mx;
mx=drem_0(mx,my,ex-ey,&q);
if(mx+mx>my || (mx+mx==my&&(q&1)) ) { // |x|>|y|/2, or equality and an odd quotient?
mx-=my;
q++;
}
}
if(sy) q=-q;
if(quo) *quo=q;
return fix642double(mx,0x3ff-ey+52);
}
double WRAPPER_FUNC(drem)(double x,double y) { check_nan_d2(x, y); return remquo(x,y,0); }
double WRAPPER_FUNC(remainder)(double x,double y) { check_nan_d2(x, y); return remquo(x,y,0); }
GCC_Pragma("GCC diagnostic pop") // conversion
================================================
FILE: pico-sdk/src/rp2_common/pico_double/double_none.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/asm_helper.S"
#include "pico/bootrom/sf_table.h"
pico_default_asm_setup
wrapper_func __aeabi_dadd
wrapper_func __aeabi_ddiv
wrapper_func __aeabi_dmul
wrapper_func __aeabi_drsub
wrapper_func __aeabi_dsub
wrapper_func __aeabi_cdcmpeq
wrapper_func __aeabi_cdrcmple
wrapper_func __aeabi_cdcmple
wrapper_func __aeabi_dcmpeq
wrapper_func __aeabi_dcmplt
wrapper_func __aeabi_dcmple
wrapper_func __aeabi_dcmpge
wrapper_func __aeabi_dcmpgt
wrapper_func __aeabi_dcmpun
wrapper_func __aeabi_i2d
wrapper_func __aeabi_l2d
wrapper_func __aeabi_ui2d
wrapper_func __aeabi_ul2d
wrapper_func __aeabi_d2iz
wrapper_func __aeabi_d2lz
wrapper_func __aeabi_d2uiz
wrapper_func __aeabi_d2ulz
wrapper_func __aeabi_d2f
wrapper_func sqrt
wrapper_func cos
wrapper_func sin
wrapper_func tan
wrapper_func atan2
wrapper_func exp
wrapper_func log
wrapper_func ldexp
wrapper_func copysign
wrapper_func trunc
wrapper_func floor
wrapper_func ceil
wrapper_func round
wrapper_func sincos
wrapper_func asin
wrapper_func acos
wrapper_func atan
wrapper_func sinh
wrapper_func cosh
wrapper_func tanh
wrapper_func asinh
wrapper_func acosh
wrapper_func atanh
wrapper_func exp2
wrapper_func log2
wrapper_func exp10
wrapper_func log10
wrapper_func pow
wrapper_func powint
wrapper_func hypot
wrapper_func cbrt
wrapper_func fmod
wrapper_func drem
wrapper_func remainder
wrapper_func remquo
wrapper_func expm1
wrapper_func log1p
wrapper_func fma
push {lr} // keep stack trace sane
ldr r0, =str
bl panic
str:
.asciz "double support is disabled"
================================================
FILE: pico-sdk/src/rp2_common/pico_double/double_v1_rom_shim.S
================================================
/**
* Copyright (c) 2020 Mark Owen https://www.quinapalus.com .
*
* Raspberry Pi (Trading) Ltd (Licensor) hereby grants to you a non-exclusive license to use the software solely on a
* Raspberry Pi Pico device. No other use is permitted under the terms of this license.
*
* This software is also available from the copyright owner under GPLv2 licence.
*
* THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "pico/asm_helper.S"
pico_default_asm_setup
.macro double_section name
// todo separate flag for shims?
#if PICO_DOUBLE_IN_RAM
.section RAM_SECTION_NAME(\name), "ax"
#else
.section SECTION_NAME(\name), "ax"
#endif
.endm
double_section double_table_shim_on_use_helper
regular_func double_table_shim_on_use_helper
push {r0-r2, lr}
mov r0, ip
#ifndef NDEBUG
// sanity check to make sure we weren't called by non (shimmable_) table_tail_call macro
cmp r0, #0
bne 1f
bkpt #0
#endif
1:
ldrh r1, [r0]
lsrs r2, r1, #8
adds r0, #2
cmp r2, #0xdf
bne 1b
uxtb r1, r1 // r1 holds table offset
lsrs r2, r0, #2
bcc 1f
// unaligned
ldrh r2, [r0, #0]
ldrh r0, [r0, #2]
lsls r0, #16
orrs r0, r2
b 2f
1:
ldr r0, [r0]
2:
ldr r2, =sd_table
str r0, [r2, r1]
str r0, [sp, #12]
pop {r0-r2, pc}
#if PICO_DOUBLE_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
// Note that the V1 ROM has no double support, so this is basically the identical
// library, and shim inter-function calls do not bother to redirect back thru the
// wrapper functions
.equ use_hw_div,1
.equ IOPORT ,0xd0000000
.equ DIV_UDIVIDEND,0x00000060
.equ DIV_UDIVISOR ,0x00000064
.equ DIV_QUOTIENT ,0x00000070
.equ DIV_CSR ,0x00000078
@ Notation:
@ rx:ry means the concatenation of rx and ry with rx having the less significant bits
.equ debug,0
.macro mdump k
.if debug
push {r0-r3}
push {r14}
push {r0-r3}
bl osp
movs r0,#\k
bl o1ch
pop {r0-r3}
bl dump
bl osp
bl osp
ldr r0,[r13]
bl o8hex @ r14
bl onl
pop {r0}
mov r14,r0
pop {r0-r3}
.endif
.endm
@ IEEE double in ra:rb ->
@ mantissa in ra:rb 12Q52 (53 significant bits) with implied 1 set
@ exponent in re
@ sign in rs
@ trashes rt
.macro mdunpack ra,rb,re,rs,rt
lsrs \re,\rb,#20 @ extract sign and exponent
subs \rs,\re,#1
lsls \rs,#20
subs \rb,\rs @ clear sign and exponent in mantissa; insert implied 1
lsrs \rs,\re,#11 @ sign
lsls \re,#21
lsrs \re,#21 @ exponent
beq l\@_1 @ zero exponent?
adds \rt,\re,#1
lsrs \rt,#11
beq l\@_2 @ exponent != 0x7ff? then done
l\@_1:
movs \ra,#0
movs \rb,#1
lsls \rb,#20
subs \re,#128
lsls \re,#12
l\@_2:
.endm
@ IEEE double in ra:rb ->
@ signed mantissa in ra:rb 12Q52 (53 significant bits) with implied 1
@ exponent in re
@ trashes rt0 and rt1
@ +zero, +denormal -> exponent=-0x80000
@ -zero, -denormal -> exponent=-0x80000
@ +Inf, +NaN -> exponent=+0x77f000
@ -Inf, -NaN -> exponent=+0x77e000
.macro mdunpacks ra,rb,re,rt0,rt1
lsrs \re,\rb,#20 @ extract sign and exponent
lsrs \rt1,\rb,#31 @ sign only
subs \rt0,\re,#1
lsls \rt0,#20
subs \rb,\rt0 @ clear sign and exponent in mantissa; insert implied 1
lsls \re,#21
bcc l\@_1 @ skip on positive
mvns \rb,\rb @ negate mantissa
negs \ra,\ra
bcc l\@_1
adds \rb,#1
l\@_1:
lsrs \re,#21
beq l\@_2 @ zero exponent?
adds \rt0,\re,#1
lsrs \rt0,#11
beq l\@_3 @ exponent != 0x7ff? then done
subs \re,\rt1
l\@_2:
movs \ra,#0
lsls \rt1,#1 @ +ve: 0 -ve: 2
adds \rb,\rt1,#1 @ +ve: 1 -ve: 3
lsls \rb,#30 @ create +/-1 mantissa
asrs \rb,#10
subs \re,#128
lsls \re,#12
l\@_3:
.endm
double_section WRAPPER_FUNC_NAME(__aeabi_dsub)
# frsub first because it is the only one that needs alignment
regular_func drsub_shim
push {r0-r3}
pop {r0-r1}
pop {r2-r3}
// fall thru
regular_func dsub_shim
push {r4-r7,r14}
movs r4,#1
lsls r4,#31
eors r3,r4 @ flip sign on second argument
b da_entry @ continue in dadd
.align 2
double_section dadd_shim
regular_func dadd_shim
push {r4-r7,r14}
da_entry:
mdunpacks r0,r1,r4,r6,r7
mdunpacks r2,r3,r5,r6,r7
subs r7,r5,r4 @ ye-xe
subs r6,r4,r5 @ xe-ye
bmi da_ygtx
@ here xe>=ye: need to shift y down r6 places
mov r12,r4 @ save exponent
cmp r6,#32
bge da_xrgty @ xe rather greater than ye?
adds r7,#32
movs r4,r2
lsls r4,r4,r7 @ rounding bit + sticky bits
da_xgty0:
movs r5,r3
lsls r5,r5,r7
lsrs r2,r6
asrs r3,r6
orrs r2,r5
da_add:
adds r0,r2
adcs r1,r3
da_pack:
@ here unnormalised signed result (possibly 0) is in r0:r1 with exponent r12, rounding + sticky bits in r4
@ Note that if a large normalisation shift is required then the arguments were close in magnitude and so we
@ cannot have not gone via the xrgty/yrgtx paths. There will therefore always be enough high bits in r4
@ to provide a correct continuation of the exact result.
@ now pack result back up
lsrs r3,r1,#31 @ get sign bit
beq 1f @ skip on positive
mvns r1,r1 @ negate mantissa
mvns r0,r0
movs r2,#0
negs r4,r4
adcs r0,r2
adcs r1,r2
1:
mov r2,r12 @ get exponent
lsrs r5,r1,#21
bne da_0 @ shift down required?
lsrs r5,r1,#20
bne da_1 @ normalised?
cmp r0,#0
beq da_5 @ could mantissa be zero?
da_2:
adds r4,r4
adcs r0,r0
adcs r1,r1
subs r2,#1 @ adjust exponent
lsrs r5,r1,#20
beq da_2
da_1:
lsls r4,#1 @ check rounding bit
bcc da_3
da_4:
adds r0,#1 @ round up
bcc 2f
adds r1,#1
2:
cmp r4,#0 @ sticky bits zero?
bne da_3
lsrs r0,#1 @ round to even
lsls r0,#1
da_3:
subs r2,#1
bmi da_6
adds r4,r2,#2 @ check if exponent is overflowing
lsrs r4,#11
bne da_7
lsls r2,#20 @ pack exponent and sign
add r1,r2
lsls r3,#31
add r1,r3
pop {r4-r7,r15}
da_7:
@ here exponent overflow: return signed infinity
lsls r1,r3,#31
ldr r3,=0x7ff00000
orrs r1,r3
b 1f
da_6:
@ here exponent underflow: return signed zero
lsls r1,r3,#31
1:
movs r0,#0
pop {r4-r7,r15}
da_5:
@ here mantissa could be zero
cmp r1,#0
bne da_2
cmp r4,#0
bne da_2
@ inputs must have been of identical magnitude and opposite sign, so return +0
pop {r4-r7,r15}
da_0:
@ here a shift down by one place is required for normalisation
adds r2,#1 @ adjust exponent
lsls r6,r0,#31 @ save rounding bit
lsrs r0,#1
lsls r5,r1,#31
orrs r0,r5
lsrs r1,#1
cmp r6,#0
beq da_3
b da_4
da_xrgty: @ xe>ye and shift>=32 places
cmp r6,#60
bge da_xmgty @ xe much greater than ye?
subs r6,#32
adds r7,#64
movs r4,r2
lsls r4,r4,r7 @ these would be shifted off the bottom of the sticky bits
beq 1f
movs r4,#1
1:
lsrs r2,r2,r6
orrs r4,r2
movs r2,r3
lsls r3,r3,r7
orrs r4,r3
asrs r3,r2,#31 @ propagate sign bit
b da_xgty0
da_ygtx:
@ here ye>xe: need to shift x down r7 places
mov r12,r5 @ save exponent
cmp r7,#32
bge da_yrgtx @ ye rather greater than xe?
adds r6,#32
movs r4,r0
lsls r4,r4,r6 @ rounding bit + sticky bits
da_ygtx0:
movs r5,r1
lsls r5,r5,r6
lsrs r0,r7
asrs r1,r7
orrs r0,r5
b da_add
da_yrgtx:
cmp r7,#60
bge da_ymgtx @ ye much greater than xe?
subs r7,#32
adds r6,#64
movs r4,r0
lsls r4,r4,r6 @ these would be shifted off the bottom of the sticky bits
beq 1f
movs r4,#1
1:
lsrs r0,r0,r7
orrs r4,r0
movs r0,r1
lsls r1,r1,r6
orrs r4,r1
asrs r1,r0,#31 @ propagate sign bit
b da_ygtx0
da_ymgtx: @ result is just y
movs r0,r2
movs r1,r3
da_xmgty: @ result is just x
movs r4,#0 @ clear sticky bits
b da_pack
.ltorg
@ equivalent of UMULL
@ needs five temporary registers
@ can have rt3==rx, in which case rx trashed
@ can have rt4==ry, in which case ry trashed
@ can have rzl==rx
@ can have rzh==ry
@ can have rzl,rzh==rt3,rt4
.macro mul32_32_64 rx,ry,rzl,rzh,rt0,rt1,rt2,rt3,rt4
@ t0 t1 t2 t3 t4
@ (x) (y)
uxth \rt0,\rx @ xl
uxth \rt1,\ry @ yl
muls \rt0,\rt1 @ xlyl=L
lsrs \rt2,\rx,#16 @ xh
muls \rt1,\rt2 @ xhyl=M0
lsrs \rt4,\ry,#16 @ yh
muls \rt2,\rt4 @ xhyh=H
uxth \rt3,\rx @ xl
muls \rt3,\rt4 @ xlyh=M1
adds \rt1,\rt3 @ M0+M1=M
bcc l\@_1 @ addition of the two cross terms can overflow, so add carry into H
movs \rt3,#1 @ 1
lsls \rt3,#16 @ 0x10000
adds \rt2,\rt3 @ H'
l\@_1:
@ t0 t1 t2 t3 t4
@ (zl) (zh)
lsls \rzl,\rt1,#16 @ ML
lsrs \rzh,\rt1,#16 @ MH
adds \rzl,\rt0 @ ZL
adcs \rzh,\rt2 @ ZH
.endm
@ SUMULL: x signed, y unsigned
@ in table below ¯ means signed variable
@ needs five temporary registers
@ can have rt3==rx, in which case rx trashed
@ can have rt4==ry, in which case ry trashed
@ can have rzl==rx
@ can have rzh==ry
@ can have rzl,rzh==rt3,rt4
.macro muls32_32_64 rx,ry,rzl,rzh,rt0,rt1,rt2,rt3,rt4
@ t0 t1 t2 t3 t4
@ ¯(x) (y)
uxth \rt0,\rx @ xl
uxth \rt1,\ry @ yl
muls \rt0,\rt1 @ xlyl=L
asrs \rt2,\rx,#16 @ ¯xh
muls \rt1,\rt2 @ ¯xhyl=M0
lsrs \rt4,\ry,#16 @ yh
muls \rt2,\rt4 @ ¯xhyh=H
uxth \rt3,\rx @ xl
muls \rt3,\rt4 @ xlyh=M1
asrs \rt4,\rt1,#31 @ M0sx (M1 sign extension is zero)
adds \rt1,\rt3 @ M0+M1=M
movs \rt3,#0 @ 0
adcs \rt4,\rt3 @ ¯Msx
lsls \rt4,#16 @ ¯Msx<<16
adds \rt2,\rt4 @ H'
@ t0 t1 t2 t3 t4
@ (zl) (zh)
lsls \rzl,\rt1,#16 @ M~
lsrs \rzh,\rt1,#16 @ M~
adds \rzl,\rt0 @ ZL
adcs \rzh,\rt2 @ ¯ZH
.endm
@ SSMULL: x signed, y signed
@ in table below ¯ means signed variable
@ needs five temporary registers
@ can have rt3==rx, in which case rx trashed
@ can have rt4==ry, in which case ry trashed
@ can have rzl==rx
@ can have rzh==ry
@ can have rzl,rzh==rt3,rt4
.macro muls32_s32_64 rx,ry,rzl,rzh,rt0,rt1,rt2,rt3,rt4
@ t0 t1 t2 t3 t4
@ ¯(x) (y)
uxth \rt0,\rx @ xl
uxth \rt1,\ry @ yl
muls \rt0,\rt1 @ xlyl=L
asrs \rt2,\rx,#16 @ ¯xh
muls \rt1,\rt2 @ ¯xhyl=M0
asrs \rt4,\ry,#16 @ ¯yh
muls \rt2,\rt4 @ ¯xhyh=H
uxth \rt3,\rx @ xl
muls \rt3,\rt4 @ ¯xlyh=M1
adds \rt1,\rt3 @ ¯M0+M1=M
asrs \rt3,\rt1,#31 @ Msx
bvc l\@_1 @
mvns \rt3,\rt3 @ ¯Msx flip sign extension bits if overflow
l\@_1:
lsls \rt3,#16 @ ¯Msx<<16
adds \rt2,\rt3 @ H'
@ t0 t1 t2 t3 t4
@ (zl) (zh)
lsls \rzl,\rt1,#16 @ M~
lsrs \rzh,\rt1,#16 @ M~
adds \rzl,\rt0 @ ZL
adcs \rzh,\rt2 @ ¯ZH
.endm
@ can have rt2==rx, in which case rx trashed
@ can have rzl==rx
@ can have rzh==rt1
.macro square32_64 rx,rzl,rzh,rt0,rt1,rt2
@ t0 t1 t2 zl zh
uxth \rt0,\rx @ xl
muls \rt0,\rt0 @ xlxl=L
uxth \rt1,\rx @ xl
lsrs \rt2,\rx,#16 @ xh
muls \rt1,\rt2 @ xlxh=M
muls \rt2,\rt2 @ xhxh=H
lsls \rzl,\rt1,#17 @ ML
lsrs \rzh,\rt1,#15 @ MH
adds \rzl,\rt0 @ ZL
adcs \rzh,\rt2 @ ZH
.endm
double_section dmul_shim
regular_func dmul_shim
push {r4-r7,r14}
mdunpack r0,r1,r4,r6,r5
mov r12,r4
mdunpack r2,r3,r4,r7,r5
eors r7,r6 @ sign of result
add r4,r12 @ exponent of result
push {r0-r2,r4,r7}
@ accumulate full product in r12:r5:r6:r7
mul32_32_64 r0,r2, r0,r5, r4,r6,r7,r0,r5 @ XL*YL
mov r12,r0 @ save LL bits
mul32_32_64 r1,r3, r6,r7, r0,r2,r4,r6,r7 @ XH*YH
pop {r0} @ XL
mul32_32_64 r0,r3, r0,r3, r1,r2,r4,r0,r3 @ XL*YH
adds r5,r0
adcs r6,r3
movs r0,#0
adcs r7,r0
pop {r1,r2} @ XH,YL
mul32_32_64 r1,r2, r1,r2, r0,r3,r4, r1,r2 @ XH*YL
adds r5,r1
adcs r6,r2
movs r0,#0
adcs r7,r0
@ here r5:r6:r7 holds the product [1..4) in Q(104-32)=Q72, with extra LSBs in r12
pop {r3,r4} @ exponent in r3, sign in r4
lsls r1,r7,#11
lsrs r2,r6,#21
orrs r1,r2
lsls r0,r6,#11
lsrs r2,r5,#21
orrs r0,r2
lsls r5,#11 @ now r5:r0:r1 Q83=Q(51+32), extra LSBs in r12
lsrs r2,r1,#20
bne 1f @ skip if in range [2..4)
adds r5,r5 @ shift up so always [2..4) Q83, i.e. [1..2) Q84=Q(52+32)
adcs r0,r0
adcs r1,r1
subs r3,#1 @ correct exponent
1:
ldr r6,=0x3ff
subs r3,r6 @ correct for exponent bias
lsls r6,#1 @ 0x7fe
cmp r3,r6
bhs dm_0 @ exponent over- or underflow
lsls r5,#1 @ rounding bit to carry
bcc 1f @ result is correctly rounded
adds r0,#1
movs r6,#0
adcs r1,r6 @ round up
mov r6,r12 @ remaining sticky bits
orrs r5,r6
bne 1f @ some sticky bits set?
lsrs r0,#1
lsls r0,#1 @ round to even
1:
lsls r3,#20
adds r1,r3
dm_2:
lsls r4,#31
add r1,r4
pop {r4-r7,r15}
@ here for exponent over- or underflow
dm_0:
bge dm_1 @ overflow?
adds r3,#1 @ would-be zero exponent?
bne 1f
adds r0,#1
bne 1f @ all-ones mantissa?
adds r1,#1
lsrs r7,r1,#21
beq 1f
lsrs r1,#1
b dm_2
1:
lsls r1,r4,#31
movs r0,#0
pop {r4-r7,r15}
@ here for exponent overflow
dm_1:
adds r6,#1 @ 0x7ff
lsls r1,r6,#20
movs r0,#0
b dm_2
.ltorg
@ Approach to division y/x is as follows.
@
@ First generate u1, an approximation to 1/x to about 29 bits. Multiply this by the top
@ 32 bits of y to generate a0, a first approximation to the result (good to 28 bits or so).
@ Calculate the exact remainder r0=y-a0*x, which will be about 0. Calculate a correction
@ d0=r0*u1, and then write a1=a0+d0. If near a rounding boundary, compute the exact
@ remainder r1=y-a1*x (which can be done using r0 as a basis) to determine whether to
@ round up or down.
@
@ The calculation of 1/x is as given in dreciptest.c. That code verifies exhaustively
@ that | u1*x-1 | < 10*2^-32.
@
@ More precisely:
@
@ x0=(q16)x;
@ x1=(q30)x;
@ y0=(q31)y;
@ u0=(q15~)"(0xffffffffU/(unsigned int)roundq(x/x_ulp))/powq(2,16)"(x0); // q15 approximation to 1/x; "~" denotes rounding rather than truncation
@ v=(q30)(u0*x1-1);
@ u1=(q30)u0-(q30~)(u0*v);
@
@ a0=(q30)(u1*y0);
@ r0=(q82)y-a0*x;
@ r0x=(q57)r0;
@ d0=r0x*u1;
@ a1=d0+a0;
@
@ Error analysis
@
@ Use Greek letters to represent the errors introduced by rounding and truncation.
@
@ r₀ = y - a₀x
@ = y - [ u₁ ( y - α ) - β ] x where 0 ≤ α < 2^-31, 0 ≤ β < 2^-30
@ = y ( 1 - u₁x ) + ( u₁α + β ) x
@
@ Hence
@
@ | r₀ / x | < 2 * 10*2^-32 + 2^-31 + 2^-30
@ = 26*2^-32
@
@ r₁ = y - a₁x
@ = y - a₀x - d₀x
@ = r₀ - d₀x
@ = r₀ - u₁ ( r₀ - γ ) x where 0 ≤ γ < 2^-57
@ = r₀ ( 1 - u₁x ) + u₁γx
@
@ Hence
@
@ | r₁ / x | < 26*2^-32 * 10*2^-32 + 2^-57
@ = (260+128)*2^-64
@ < 2^-55
@
@ Empirically it seems to be nearly twice as good as this.
@
@ To determine correctly whether the exact remainder calculation can be skipped we need a result
@ accurate to < 0.25ulp. In the case where x>y the quotient will be shifted up one place for normalisation
@ and so 1ulp is 2^-53 and so the calculation above suffices.
double_section ddiv_shim
regular_func ddiv_shim
push {r4-r7,r14}
ddiv0: @ entry point from dtan
mdunpack r2,r3,r4,r7,r6 @ unpack divisor
.if use_hw_div
movs r5,#IOPORT>>24
lsls r5,#24
movs r6,#0
mvns r6,r6
str r6,[r5,#DIV_UDIVIDEND]
lsrs r6,r3,#4 @ x0=(q16)x
str r6,[r5,#DIV_UDIVISOR]
@ if there are not enough cycles from now to the read of the quotient for
@ the divider to do its stuff we need a busy-wait here
.endif
@ unpack dividend by hand to save on register use
lsrs r6,r1,#31
adds r6,r7
mov r12,r6 @ result sign in r12b0; r12b1 trashed
lsls r1,#1
lsrs r7,r1,#21 @ exponent
beq 1f @ zero exponent?
adds r6,r7,#1
lsrs r6,#11
beq 2f @ exponent != 0x7ff? then done
1:
movs r0,#0
movs r1,#0
subs r7,#64 @ less drastic fiddling of exponents to get 0/0, Inf/Inf correct
lsls r7,#12
2:
subs r6,r7,r4
lsls r6,#2
add r12,r12,r6 @ (signed) exponent in r12[31..8]
subs r7,#1 @ implied 1
lsls r7,#21
subs r1,r7
lsrs r1,#1
.if use_hw_div
ldr r6,[r5,#DIV_QUOTIENT]
adds r6,#1
lsrs r6,#1
.else
@ this is not beautiful; could be replaced by better code that uses knowledge of divisor range
push {r0-r3}
movs r0,#0
mvns r0,r0
lsrs r1,r3,#4 @ x0=(q16)x
bl __aeabi_uidiv @ !!! this could (but apparently does not) trash R12
adds r6,r0,#1
lsrs r6,#1
pop {r0-r3}
.endif
@ here
@ r0:r1 y mantissa
@ r2:r3 x mantissa
@ r6 u0, first approximation to 1/x Q15
@ r12: result sign, exponent
lsls r4,r3,#10
lsrs r5,r2,#22
orrs r5,r4 @ x1=(q30)x
muls r5,r6 @ u0*x1 Q45
asrs r5,#15 @ v=u0*x1-1 Q30
muls r5,r6 @ u0*v Q45
asrs r5,#14
adds r5,#1
asrs r5,#1 @ round u0*v to Q30
lsls r6,#15
subs r6,r5 @ u1 Q30
@ here
@ r0:r1 y mantissa
@ r2:r3 x mantissa
@ r6 u1, second approximation to 1/x Q30
@ r12: result sign, exponent
push {r2,r3}
lsls r4,r1,#11
lsrs r5,r0,#21
orrs r4,r5 @ y0=(q31)y
mul32_32_64 r4,r6, r4,r5, r2,r3,r7,r4,r5 @ y0*u1 Q61
adds r4,r4
adcs r5,r5 @ a0=(q30)(y0*u1)
@ here
@ r0:r1 y mantissa
@ r5 a0, first approximation to y/x Q30
@ r6 u1, second approximation to 1/x Q30
@ r12 result sign, exponent
ldr r2,[r13,#0] @ xL
mul32_32_64 r2,r5, r2,r3, r1,r4,r7,r2,r3 @ xL*a0
ldr r4,[r13,#4] @ xH
muls r4,r5 @ xH*a0
adds r3,r4 @ r2:r3 now x*a0 Q82
lsrs r2,#25
lsls r1,r3,#7
orrs r2,r1 @ r2 now x*a0 Q57; r7:r2 is x*a0 Q89
lsls r4,r0,#5 @ y Q57
subs r0,r4,r2 @ r0x=y-x*a0 Q57 (signed)
@ here
@ r0 r0x Q57
@ r5 a0, first approximation to y/x Q30
@ r4 yL Q57
@ r6 u1 Q30
@ r12 result sign, exponent
muls32_32_64 r0,r6, r7,r6, r1,r2,r3, r7,r6 @ r7:r6 r0x*u1 Q87
asrs r3,r6,#25
adds r5,r3
lsls r3,r6,#7 @ r3:r5 a1 Q62 (but bottom 7 bits are zero so 55 bits of precision after binary point)
@ here we could recover another 7 bits of precision (but not accuracy) from the top of r7
@ but these bits are thrown away in the rounding and conversion to Q52 below
@ here
@ r3:r5 a1 Q62 candidate quotient [0.5,2) or so
@ r4 yL Q57
@ r12 result sign, exponent
movs r6,#0
adds r3,#128 @ for initial rounding to Q53
adcs r5,r5,r6
lsrs r1,r5,#30
bne dd_0
@ here candidate quotient a1 is in range [0.5,1)
@ so 30 significant bits in r5
lsls r4,#1 @ y now Q58
lsrs r1,r5,#9 @ to Q52
lsls r0,r5,#23
lsrs r3,#9 @ 0.5ulp-significance bit in carry: if this is 1 we may need to correct result
orrs r0,r3
bcs dd_1
b dd_2
dd_0:
@ here candidate quotient a1 is in range [1,2)
@ so 31 significant bits in r5
movs r2,#4
add r12,r12,r2 @ fix exponent; r3:r5 now effectively Q61
adds r3,#128 @ complete rounding to Q53
adcs r5,r5,r6
lsrs r1,r5,#10
lsls r0,r5,#22
lsrs r3,#10 @ 0.5ulp-significance bit in carry: if this is 1 we may need to correct result
orrs r0,r3
bcc dd_2
dd_1:
@ here
@ r0:r1 rounded result Q53 [0.5,1) or Q52 [1,2), but may not be correctly rounded-to-nearest
@ r4 yL Q58 or Q57
@ r12 result sign, exponent
@ carry set
adcs r0,r0,r0
adcs r1,r1,r1 @ z Q53 with 1 in LSB
lsls r4,#16 @ Q105-32=Q73
ldr r2,[r13,#0] @ xL Q52
ldr r3,[r13,#4] @ xH Q20
movs r5,r1 @ zH Q21
muls r5,r2 @ zH*xL Q73
subs r4,r5
muls r3,r0 @ zL*xH Q73
subs r4,r3
mul32_32_64 r2,r0, r2,r3, r5,r6,r7,r2,r3 @ xL*zL
negs r2,r2 @ borrow from low half?
sbcs r4,r3 @ y-xz Q73 (remainder bits 52..73)
cmp r4,#0
bmi 1f
movs r2,#0 @ round up
adds r0,#1
adcs r1,r2
1:
lsrs r0,#1 @ shift back down to Q52
lsls r2,r1,#31
orrs r0,r2
lsrs r1,#1
dd_2:
add r13,#8
mov r2,r12
lsls r7,r2,#31 @ result sign
asrs r2,#2 @ result exponent
ldr r3,=0x3fd
adds r2,r3
ldr r3,=0x7fe
cmp r2,r3
bhs dd_3 @ over- or underflow?
lsls r2,#20
adds r1,r2 @ pack exponent
dd_5:
adds r1,r7 @ pack sign
pop {r4-r7,r15}
dd_3:
movs r0,#0
cmp r2,#0
bgt dd_4 @ overflow?
movs r1,r7
pop {r4-r7,r15}
dd_4:
adds r3,#1 @ 0x7ff
lsls r1,r3,#20
b dd_5
.section SECTION_NAME(dsqrt_shim)
/*
Approach to square root x=sqrt(y) is as follows.
First generate a3, an approximation to 1/sqrt(y) to about 30 bits. Multiply this by y
to give a4~sqrt(y) to about 28 bits and a remainder r4=y-a4^2. Then, because
d sqrt(y) / dy = 1 / (2 sqrt(y)) let d4=r4*a3/2 and then the value a5=a4+d4 is
a better approximation to sqrt(y). If this is near a rounding boundary we
compute an exact remainder y-a5*a5 to decide whether to round up or down.
The calculation of a3 and a4 is as given in dsqrttest.c. That code verifies exhaustively
that | 1 - a3a4 | < 10*2^-32, | r4 | < 40*2^-32 and | r4/y | < 20*2^-32.
More precisely, with "y" representing y truncated to 30 binary places:
u=(q3)y; // 24-entry table
a0=(q8~)"1/sqrtq(x+x_ulp/2)"(u); // first approximation from table
p0=(q16)(a0*a0) * (q16)y;
r0=(q20)(p0-1);
dy0=(q15)(r0*a0); // Newton-Raphson correction term
a1=(q16)a0-dy0/2; // good to ~9 bits
p1=(q19)(a1*a1)*(q19)y;
r1=(q23)(p1-1);
dy1=(q15~)(r1*a1); // second Newton-Raphson correction
a2x=(q16)a1-dy1/2; // good to ~16 bits
a2=a2x-a2x/1t16; // prevent overflow of a2*a2 in 32 bits
p2=(a2*a2)*(q30)y; // Q62
r2=(q36)(p2-1+1t-31);
dy2=(q30)(r2*a2); // Q52->Q30
a3=(q31)a2-dy2/2; // good to about 30 bits
a4=(q30)(a3*(q30)y+1t-31); // good to about 28 bits
Error analysis
r₄ = y - a₄²
d₄ = 1/2 a₃r₄
a₅ = a₄ + d₄
r₅ = y - a₅²
= y - ( a₄ + d₄ )²
= y - a₄² - a₃a₄r₄ - 1/4 a₃²r₄²
= r₄ - a₃a₄r₄ - 1/4 a₃²r₄²
| r₅ | < | r₄ | | 1 - a₃a₄ | + 1/4 r₄²
a₅ = √y √( 1 - r₅/y )
= √y ( 1 - 1/2 r₅/y + ... )
So to first order (second order being very tiny)
√y - a₅ = 1/2 r₅/y
and
| √y - a₅ | < 1/2 ( | r₄/y | | 1 - a₃a₄ | + 1/4 r₄²/y )
From dsqrttest.c (conservatively):
< 1/2 ( 20*2^-32 * 10*2^-32 + 1/4 * 40*2^-32*20*2^-32 )
= 1/2 ( 200 + 200 ) * 2^-64
< 2^-56
Empirically we see about 1ulp worst-case error including rounding at Q57.
To determine correctly whether the exact remainder calculation can be skipped we need a result
accurate to < 0.25ulp at Q52, or 2^-54.
*/
dq_2:
bge dq_3 @ +Inf?
movs r1,#0
b dq_4
dq_0:
lsrs r1,#31
lsls r1,#31 @ preserve sign bit
lsrs r2,#21 @ extract exponent
beq dq_4 @ -0? return it
asrs r1,#11 @ make -Inf
b dq_4
dq_3:
ldr r1,=0x7ff
lsls r1,#20 @ return +Inf
dq_4:
movs r0,#0
dq_1:
bx r14
.align 2
regular_func dsqrt_shim
lsls r2,r1,#1
bcs dq_0 @ negative?
lsrs r2,#21 @ extract exponent
subs r2,#1
ldr r3,=0x7fe
cmp r2,r3
bhs dq_2 @ catches 0 and +Inf
push {r4-r7,r14}
lsls r4,r2,#20
subs r1,r4 @ insert implied 1
lsrs r2,#1
bcc 1f @ even exponent? skip
adds r0,r0,r0 @ odd exponent: shift up mantissa
adcs r1,r1,r1
1:
lsrs r3,#2
adds r2,r3
lsls r2,#20
mov r12,r2 @ save result exponent
@ here
@ r0:r1 y mantissa Q52 [1,4)
@ r12 result exponent
.equ drsqrtapp_minus_8, (drsqrtapp-8)
adr r4,drsqrtapp_minus_8 @ first eight table entries are never accessed because of the mantissa's leading 1
lsrs r2,r1,#17 @ y Q3
ldrb r2,[r4,r2] @ initial approximation to reciprocal square root a0 Q8
lsrs r3,r1,#4 @ first Newton-Raphson iteration
muls r3,r2
muls r3,r2 @ i32 p0=a0*a0*(y>>14); // Q32
asrs r3,r3,#12 @ i32 r0=p0>>12; // Q20
muls r3,r2
asrs r3,#13 @ i32 dy0=(r0*a0)>>13; // Q15
lsls r2,#8
subs r2,r3 @ i32 a1=(a0<<8)-dy0; // Q16
movs r3,r2
muls r3,r3
lsrs r3,#13
lsrs r4,r1,#1
muls r3,r4 @ i32 p1=((a1*a1)>>11)*(y>>11); // Q19*Q19=Q38
asrs r3,#15 @ i32 r1=p1>>15; // Q23
muls r3,r2
asrs r3,#23
adds r3,#1
asrs r3,#1 @ i32 dy1=(r1*a1+(1<<23))>>24; // Q23*Q16=Q39; Q15
subs r2,r3 @ i32 a2=a1-dy1; // Q16
lsrs r3,r2,#16
subs r2,r3 @ if(a2>=0x10000) a2=0xffff; to prevent overflow of a2*a2
@ here
@ r0:r1 y mantissa
@ r2 a2 ~ 1/sqrt(y) Q16
@ r12 result exponent
movs r3,r2
muls r3,r3
lsls r1,#10
lsrs r4,r0,#22
orrs r1,r4 @ y Q30
mul32_32_64 r1,r3, r4,r3, r5,r6,r7,r4,r3 @ i64 p2=(ui64)(a2*a2)*(ui64)y; // Q62 r4:r3
lsls r5,r3,#6
lsrs r4,#26
orrs r4,r5
adds r4,#0x20 @ i32 r2=(p2>>26)+0x20; // Q36 r4
uxth r5,r4
muls r5,r2
asrs r4,#16
muls r4,r2
lsrs r5,#16
adds r4,r5
asrs r4,#6 @ i32 dy2=((i64)r2*(i64)a2)>>22; // Q36*Q16=Q52; Q30
lsls r2,#15
subs r2,r4
@ here
@ r0 y low bits
@ r1 y Q30
@ r2 a3 ~ 1/sqrt(y) Q31
@ r12 result exponent
mul32_32_64 r2,r1, r3,r4, r5,r6,r7,r3,r4
adds r3,r3,r3
adcs r4,r4,r4
adds r3,r3,r3
movs r3,#0
adcs r3,r4 @ ui32 a4=((ui64)a3*(ui64)y+(1U<<31))>>31; // Q30
@ here
@ r0 y low bits
@ r1 y Q30
@ r2 a3 Q31 ~ 1/sqrt(y)
@ r3 a4 Q30 ~ sqrt(y)
@ r12 result exponent
square32_64 r3, r4,r5, r6,r5,r7
lsls r6,r0,#8
lsrs r7,r1,#2
subs r6,r4
sbcs r7,r5 @ r4=(q60)y-a4*a4
@ by exhaustive testing, r4 = fffffffc0e134fdc .. 00000003c2bf539c Q60
lsls r5,r7,#29
lsrs r6,#3
adcs r6,r5 @ r4 Q57 with rounding
muls32_32_64 r6,r2, r6,r2, r4,r5,r7,r6,r2 @ d4=a3*r4/2 Q89
@ r4+d4 is correct to 1ULP at Q57, tested on ~9bn cases including all extreme values of r4 for each possible y Q30
adds r2,#8
asrs r2,#5 @ d4 Q52, rounded to Q53 with spare bit in carry
@ here
@ r0 y low bits
@ r1 y Q30
@ r2 d4 Q52, rounded to Q53
@ C flag contains d4_b53
@ r3 a4 Q30
bcs dq_5
lsrs r5,r3,#10 @ a4 Q52
lsls r4,r3,#22
asrs r1,r2,#31
adds r0,r2,r4
adcs r1,r5 @ a4+d4
add r1,r12 @ pack exponent
pop {r4-r7,r15}
.ltorg
@ round(sqrt(2^22./[68:8:252]))
drsqrtapp:
.byte 0xf8,0xeb,0xdf,0xd6,0xcd,0xc5,0xbe,0xb8
.byte 0xb2,0xad,0xa8,0xa4,0xa0,0x9c,0x99,0x95
.byte 0x92,0x8f,0x8d,0x8a,0x88,0x85,0x83,0x81
dq_5:
@ here we are near a rounding boundary, C is set
adcs r2,r2,r2 @ d4 Q53+1ulp
lsrs r5,r3,#9
lsls r4,r3,#23 @ r4:r5 a4 Q53
asrs r1,r2,#31
adds r4,r2,r4
adcs r5,r1 @ r4:r5 a5=a4+d4 Q53+1ulp
movs r3,r5
muls r3,r4
square32_64 r4,r1,r2,r6,r2,r7
adds r2,r3
adds r2,r3 @ r1:r2 a5^2 Q106
lsls r0,#22 @ y Q84
negs r1,r1
sbcs r0,r2 @ remainder y-a5^2
bmi 1f @ y=0
@ ω+=dω
@ x+=y>>i, y-=x>>i
adds r0,r3
adcs r1,r4
mov r3,r11
asrs r3,r7
mov r4,r11
lsls r4,r6
mov r2,r10
lsrs r2,r7
orrs r2,r4 @ r2:r3 y>>i, rounding in carry
mov r4,r8
mov r5,r9 @ r4:r5 x
adcs r2,r4
adcs r3,r5 @ r2:r3 x+(y>>i)
mov r8,r2
mov r9,r3
mov r3,r5
lsls r3,r6
asrs r5,r7
lsrs r4,r7
orrs r4,r3 @ r4:r5 x>>i, rounding in carry
mov r2,r10
mov r3,r11
sbcs r2,r4
sbcs r3,r5 @ r2:r3 y-(x>>i)
mov r10,r2
mov r11,r3
bx r14
@ ω>0 / y<0
@ ω-=dω
@ x-=y>>i, y+=x>>i
1:
subs r0,r3
sbcs r1,r4
mov r3,r9
asrs r3,r7
mov r4,r9
lsls r4,r6
mov r2,r8
lsrs r2,r7
orrs r2,r4 @ r2:r3 x>>i, rounding in carry
mov r4,r10
mov r5,r11 @ r4:r5 y
adcs r2,r4
adcs r3,r5 @ r2:r3 y+(x>>i)
mov r10,r2
mov r11,r3
mov r3,r5
lsls r3,r6
asrs r5,r7
lsrs r4,r7
orrs r4,r3 @ r4:r5 y>>i, rounding in carry
mov r2,r8
mov r3,r9
sbcs r2,r4
sbcs r3,r5 @ r2:r3 x-(y>>i)
mov r8,r2
mov r9,r3
bx r14
@ convert packed double in r0:r1 to signed/unsigned 32/64-bit integer/fixed-point value in r0:r1 [with r2 places after point], with rounding towards -Inf
@ fixed-point versions only work with reasonable values in r2 because of the way dunpacks works
double_section double2int_shim
regular_func double2int_shim
movs r2,#0 @ and fall through
regular_func double2fix_shim
push {r14}
adds r2,#32
bl double2fix64_shim
movs r0,r1
pop {r15}
double_section double2uint_shim
regular_func double2uint_shim
movs r2,#0 @ and fall through
regular_func double2ufix_shim
push {r14}
adds r2,#32
bl double2ufix64_shim
movs r0,r1
pop {r15}
double_section double2int64_shim
regular_func double2int64_shim
movs r2,#0 @ and fall through
regular_func double2fix64_shim
push {r14}
bl d2fix
asrs r2,r1,#31
cmp r2,r3
bne 1f @ sign extension bits fail to match sign of result?
pop {r15}
1:
mvns r0,r3
movs r1,#1
lsls r1,#31
eors r1,r1,r0 @ generate extreme fixed-point values
pop {r15}
double_section double2uint64_shim
regular_func double2uint64_shim
movs r2,#0 @ and fall through
regular_func double2ufix64_shim
asrs r3,r1,#20 @ negative? return 0
bmi ret_dzero
@ and fall through
@ convert double in r0:r1 to signed fixed point in r0:r1:r3, r2 places after point, rounding towards -Inf
@ result clamped so that r3 can only be 0 or -1
@ trashes r12
.thumb_func
d2fix:
push {r4,r14}
mov r12,r2
bl dunpacks
asrs r4,r2,#16
adds r4,#1
bge 1f
movs r1,#0 @ -0 -> +0
1:
asrs r3,r1,#31
ldr r4, =d2fix_a
bx r4
ret_dzero:
movs r0,#0
movs r1,#0
bx r14
.weak d2fix_a // weak because it exists in float code too
.thumb_func
d2fix_a:
@ here
@ r0:r1 two's complement mantissa
@ r2 unbaised exponent
@ r3 mantissa sign extension bits
add r2,r12 @ exponent plus offset for required binary point position
subs r2,#52 @ required shift
bmi 1f @ shift down?
@ here a shift up by r2 places
cmp r2,#12 @ will clamp?
bge 2f
movs r4,r0
lsls r1,r2
lsls r0,r2
negs r2,r2
adds r2,#32 @ complementary shift
lsrs r4,r2
orrs r1,r4
pop {r4,r15}
2:
mvns r0,r3
mvns r1,r3 @ overflow: clamp to extreme fixed-point values
pop {r4,r15}
1:
@ here a shift down by -r2 places
adds r2,#32
bmi 1f @ long shift?
mov r4,r1
lsls r4,r2
negs r2,r2
adds r2,#32 @ complementary shift
asrs r1,r2
lsrs r0,r2
orrs r0,r4
pop {r4,r15}
1:
@ here a long shift down
movs r0,r1
asrs r1,#31 @ shift down 32 places
adds r2,#32
bmi 1f @ very long shift?
negs r2,r2
adds r2,#32
asrs r0,r2
pop {r4,r15}
1:
movs r0,r3 @ result very near zero: use sign extension bits
movs r1,r3
pop {r4,r15}
double_section double2float_shim
regular_func double2float_shim
lsls r2,r1,#1
lsrs r2,#21 @ exponent
ldr r3,=0x3ff-0x7f
subs r2,r3 @ fix exponent bias
ble 1f @ underflow or zero
cmp r2,#0xff
bge 2f @ overflow or infinity
lsls r2,#23 @ position exponent of result
lsrs r3,r1,#31
lsls r3,#31
orrs r2,r3 @ insert sign
lsls r3,r0,#3 @ rounding bits
lsrs r0,#29
lsls r1,#12
lsrs r1,#9
orrs r0,r1 @ assemble mantissa
orrs r0,r2 @ insert exponent and sign
lsls r3,#1
bcc 3f @ no rounding
beq 4f @ all sticky bits 0?
5:
adds r0,#1
3:
bx r14
4:
lsrs r3,r0,#1 @ odd? then round up
bcs 5b
bx r14
1:
beq 6f @ check case where value is just less than smallest normal
7:
lsrs r0,r1,#31
lsls r0,#31
bx r14
6:
lsls r2,r1,#12 @ 20 1:s at top of mantissa?
asrs r2,#12
adds r2,#1
bne 7b
lsrs r2,r0,#29 @ and 3 more 1:s?
cmp r2,#7
bne 7b
movs r2,#1 @ return smallest normal with correct sign
b 8f
2:
movs r2,#0xff
8:
lsrs r0,r1,#31 @ return signed infinity
lsls r0,#8
adds r0,r2
lsls r0,#23
bx r14
double_section x2double_shims
@ convert signed/unsigned 32/64-bit integer/fixed-point value in r0:r1 [with r2 places after point] to packed double in r0:r1, with rounding
.align 2
regular_func uint2double_shim
movs r1,#0 @ and fall through
regular_func ufix2double_shim
movs r2,r1
movs r1,#0
b ufix642double_shim
.align 2
regular_func int2double_shim
movs r1,#0 @ and fall through
regular_func fix2double_shim
movs r2,r1
asrs r1,r0,#31 @ sign extend
b fix642double_shim
.align 2
regular_func uint642double_shim
movs r2,#0 @ and fall through
regular_func ufix642double_shim
movs r3,#0
b uf2d
.align 2
regular_func int642double_shim
movs r2,#0 @ and fall through
regular_func fix642double_shim
asrs r3,r1,#31 @ sign bit across all bits
eors r0,r3
eors r1,r3
subs r0,r3
sbcs r1,r3
uf2d:
push {r4,r5,r14}
ldr r4,=0x432
subs r2,r4,r2 @ form biased exponent
@ here
@ r0:r1 unnormalised mantissa
@ r2 -Q (will become exponent)
@ r3 sign across all bits
cmp r1,#0
bne 1f @ short normalising shift?
movs r1,r0
beq 2f @ zero? return it
movs r0,#0
subs r2,#32 @ fix exponent
1:
asrs r4,r1,#21
bne 3f @ will need shift down (and rounding?)
bcs 4f @ normalised already?
5:
subs r2,#1
adds r0,r0 @ shift up
adcs r1,r1
lsrs r4,r1,#21
bcc 5b
4:
ldr r4,=0x7fe
cmp r2,r4
bhs 6f @ over/underflow? return signed zero/infinity
7:
lsls r2,#20 @ pack and return
adds r1,r2
lsls r3,#31
adds r1,r3
2:
pop {r4,r5,r15}
6: @ return signed zero/infinity according to unclamped exponent in r2
mvns r2,r2
lsrs r2,#21
movs r0,#0
movs r1,#0
b 7b
3:
@ here we need to shift down to normalise and possibly round
bmi 1f @ already normalised to Q63?
2:
subs r2,#1
adds r0,r0 @ shift up
adcs r1,r1
bpl 2b
1:
@ here we have a 1 in b63 of r0:r1
adds r2,#11 @ correct exponent for subsequent shift down
lsls r4,r0,#21 @ save bits for rounding
lsrs r0,#11
lsls r5,r1,#21
orrs r0,r5
lsrs r1,#11
lsls r4,#1
beq 1f @ sticky bits are zero?
8:
movs r4,#0
adcs r0,r4
adcs r1,r4
b 4b
1:
bcc 4b @ sticky bits are zero but not on rounding boundary
lsrs r4,r0,#1 @ increment if odd (force round to even)
b 8b
.ltorg
double_section dunpacks
regular_func dunpacks
mdunpacks r0,r1,r2,r3,r4
ldr r3,=0x3ff
subs r2,r3 @ exponent without offset
bx r14
@ r0:r1 signed mantissa Q52
@ r2 unbiased exponent < 10 (i.e., |x|<2^10)
@ r4 pointer to:
@ - divisor reciprocal approximation r=1/d Q15
@ - divisor d Q62 0..20
@ - divisor d Q62 21..41
@ - divisor d Q62 42..62
@ returns:
@ r0:r1 reduced result y Q62, -0.6 d < y < 0.6 d (better in practice)
@ r2 quotient q (number of reductions)
@ if exponent >=10, returns r0:r1=0, r2=1024*mantissa sign
@ designed to work for 0.5=0: in quadrant 0
cmp r1,r3
ble 2f @ y<~x so 0≤θ<~π/4: skip
adds r6,#1
eors r1,r5 @ negate x
b 3f @ and exchange x and y = rotate by -π/2
1:
cmp r3,r7
bge 2f @ -y<~x so -π/4<~θ≤0: skip
subs r6,#1
eors r3,r5 @ negate y and ...
3:
movs r7,r0 @ exchange x and y
movs r0,r2
movs r2,r7
movs r7,r1
movs r1,r3
movs r3,r7
2:
@ here -π/4<~θ<~π/4
@ r6 has quadrant offset
push {r6}
cmp r2,#0
bne 1f
cmp r3,#0
beq 10f @ x==0 going into division?
lsls r4,r3,#1
asrs r4,#21
adds r4,#1
bne 1f @ x==Inf going into division?
lsls r4,r1,#1
asrs r4,#21
adds r4,#1 @ y also ±Inf?
bne 10f
subs r1,#1 @ make them both just finite
subs r3,#1
b 1f
10:
movs r0,#0
movs r1,#0
b 12f
1:
bl ddiv_shim
movs r2,#62
bl double2fix64_shim
@ r0:r1 y/x
mov r10,r0
mov r11,r1
movs r0,#0 @ ω=0
movs r1,#0
mov r8,r0
movs r2,#1
lsls r2,#30
mov r9,r2 @ x=1
adr r4,dtab_cc
mov r12,r4
movs r7,#1
movs r6,#31
1:
bl dcordic_vec_step
adds r7,#1
subs r6,#1
cmp r7,#33
bne 1b
@ r0:r1 atan(y/x) Q62
@ r8:r9 x residual Q62
@ r10:r11 y residual Q62
mov r2,r9
mov r3,r10
subs r2,#12 @ this makes atan(0)==0
@ the following is basically a division residual y/x ~ atan(residual y/x)
movs r4,#1
lsls r4,#29
movs r7,#0
2:
lsrs r2,#1
movs r3,r3 @ preserve carry
bmi 1f
sbcs r3,r2
adds r0,r4
adcs r1,r7
lsrs r4,#1
bne 2b
b 3f
1:
adcs r3,r2
subs r0,r4
sbcs r1,r7
lsrs r4,#1
bne 2b
3:
lsls r6,r1,#31
asrs r1,#1
lsrs r0,#1
orrs r0,r6 @ Q61
12:
pop {r6}
cmp r6,#0
beq 1f
ldr r4,=0x885A308D @ π/2 Q61
ldr r5,=0x3243F6A8
bpl 2f
mvns r4,r4 @ negative quadrant offset
mvns r5,r5
2:
lsls r6,#31
bne 2f @ skip if quadrant offset is ±1
adds r0,r4
adcs r1,r5
2:
adds r0,r4
adcs r1,r5
1:
movs r2,#61
bl fix642double_shim
bl pop_r8_r11
pop {r4-r7,r15}
.ltorg
dtab_cc:
.word 0x61bb4f69, 0x1dac6705 @ atan 2^-1 Q62
.word 0x96406eb1, 0x0fadbafc @ atan 2^-2 Q62
.word 0xab0bdb72, 0x07f56ea6 @ atan 2^-3 Q62
.word 0xe59fbd39, 0x03feab76 @ atan 2^-4 Q62
.word 0xba97624b, 0x01ffd55b @ atan 2^-5 Q62
.word 0xdddb94d6, 0x00fffaaa @ atan 2^-6 Q62
.word 0x56eeea5d, 0x007fff55 @ atan 2^-7 Q62
.word 0xaab7776e, 0x003fffea @ atan 2^-8 Q62
.word 0x5555bbbc, 0x001ffffd @ atan 2^-9 Q62
.word 0xaaaaadde, 0x000fffff @ atan 2^-10 Q62
.word 0xf555556f, 0x0007ffff @ atan 2^-11 Q62
.word 0xfeaaaaab, 0x0003ffff @ atan 2^-12 Q62
.word 0xffd55555, 0x0001ffff @ atan 2^-13 Q62
.word 0xfffaaaab, 0x0000ffff @ atan 2^-14 Q62
.word 0xffff5555, 0x00007fff @ atan 2^-15 Q62
.word 0xffffeaab, 0x00003fff @ atan 2^-16 Q62
.word 0xfffffd55, 0x00001fff @ atan 2^-17 Q62
.word 0xffffffab, 0x00000fff @ atan 2^-18 Q62
.word 0xfffffff5, 0x000007ff @ atan 2^-19 Q62
.word 0xffffffff, 0x000003ff @ atan 2^-20 Q62
.word 0x00000000, 0x00000200 @ atan 2^-21 Q62 @ consider optimising these
.word 0x00000000, 0x00000100 @ atan 2^-22 Q62
.word 0x00000000, 0x00000080 @ atan 2^-23 Q62
.word 0x00000000, 0x00000040 @ atan 2^-24 Q62
.word 0x00000000, 0x00000020 @ atan 2^-25 Q62
.word 0x00000000, 0x00000010 @ atan 2^-26 Q62
.word 0x00000000, 0x00000008 @ atan 2^-27 Q62
.word 0x00000000, 0x00000004 @ atan 2^-28 Q62
.word 0x00000000, 0x00000002 @ atan 2^-29 Q62
.word 0x00000000, 0x00000001 @ atan 2^-30 Q62
.word 0x80000000, 0x00000000 @ atan 2^-31 Q62
.word 0x40000000, 0x00000000 @ atan 2^-32 Q62
double_section dexp_guts
regular_func dexp_shim
push {r4-r7,r14}
bl dunpacks
adr r4,dreddata1
bl dreduce
cmp r1,#0
bge 1f
ldr r4,=0xF473DE6B
ldr r5,=0x2C5C85FD @ ln2 Q62
adds r0,r4
adcs r1,r5
subs r2,#1
1:
push {r2}
movs r7,#1 @ shift
adr r6,dtab_exp
movs r2,#0
movs r3,#1
lsls r3,#30 @ x=1 Q62
3:
ldmia r6!,{r4,r5}
mov r12,r6
subs r0,r4
sbcs r1,r5
bmi 1f
negs r6,r7
adds r6,#32 @ complementary shift
movs r5,r3
asrs r5,r7
movs r4,r3
lsls r4,r6
movs r6,r2
lsrs r6,r7 @ rounding bit in carry
orrs r4,r6
adcs r2,r4
adcs r3,r5 @ x+=x>>i
b 2f
1:
adds r0,r4 @ restore argument
adcs r1,r5
2:
mov r6,r12
adds r7,#1
cmp r7,#33
bne 3b
@ here
@ r0:r1 ε (residual x, where x=a+ε) Q62, |ε|≤2^-32 (so fits in r0)
@ r2:r3 exp a Q62
@ and we wish to calculate exp x=exp a exp ε~(exp a)(1+ε)
muls32_32_64 r0,r3, r4,r1, r5,r6,r7,r4,r1
@ r4:r1 ε exp a Q(62+62-32)=Q92
lsrs r4,#30
lsls r0,r1,#2
orrs r0,r4
asrs r1,#30
adds r0,r2
adcs r1,r3
pop {r2}
negs r2,r2
adds r2,#62
bl fix642double_shim @ in principle we can pack faster than this because we know the exponent
pop {r4-r7,r15}
.ltorg
.align 2
regular_func dln_shim
push {r4-r7,r14}
lsls r7,r1,#1
bcs 5f @ <0 ...
asrs r7,#21
beq 5f @ ... or =0? return -Inf
adds r7,#1
beq 6f @ Inf/NaN? return +Inf
bl dunpacks
push {r2}
lsls r1,#9
lsrs r2,r0,#23
orrs r1,r2
lsls r0,#9
@ r0:r1 m Q61 = m/2 Q62 0.5≤m/2<1
movs r7,#1 @ shift
adr r6,dtab_exp
mov r12,r6
movs r2,#0
movs r3,#0 @ y=0 Q62
3:
negs r6,r7
adds r6,#32 @ complementary shift
movs r5,r1
asrs r5,r7
movs r4,r1
lsls r4,r6
movs r6,r0
lsrs r6,r7
orrs r4,r6 @ x>>i, rounding bit in carry
adcs r4,r0
adcs r5,r1 @ x+(x>>i)
lsrs r6,r5,#30
bne 1f @ x+(x>>i)>1?
movs r0,r4
movs r1,r5 @ x+=x>>i
mov r6,r12
ldmia r6!,{r4,r5}
subs r2,r4
sbcs r3,r5
1:
movs r4,#8
add r12,r4
adds r7,#1
cmp r7,#33
bne 3b
@ here:
@ r0:r1 residual x, nearly 1 Q62
@ r2:r3 y ~ ln m/2 = ln m - ln2 Q62
@ result is y + ln2 + ln x ~ y + ln2 + (x-1)
lsls r1,#2
asrs r1,#2 @ x-1
adds r2,r0
adcs r3,r1
pop {r7}
@ here:
@ r2:r3 ln m/2 = ln m - ln2 Q62
@ r7 unbiased exponent
.equ dreddata1_plus_4, (dreddata1+4)
adr r4,dreddata1_plus_4
ldmia r4,{r0,r1,r4}
adds r7,#1
muls r0,r7 @ Q62
muls r1,r7 @ Q41
muls r4,r7 @ Q20
lsls r7,r1,#21
asrs r1,#11
asrs r5,r1,#31
adds r0,r7
adcs r1,r5
lsls r7,r4,#10
asrs r4,#22
asrs r5,r1,#31
adds r1,r7
adcs r4,r5
@ r0:r1:r4 exponent*ln2 Q62
asrs r5,r3,#31
adds r0,r2
adcs r1,r3
adcs r4,r5
@ r0:r1:r4 result Q62
movs r2,#62
1:
asrs r5,r1,#31
cmp r4,r5
beq 2f @ r4 a sign extension of r1?
lsrs r0,#4 @ no: shift down 4 places and try again
lsls r6,r1,#28
orrs r0,r6
lsrs r1,#4
lsls r6,r4,#28
orrs r1,r6
asrs r4,#4
subs r2,#4
b 1b
2:
bl fix642double_shim
pop {r4-r7,r15}
5:
ldr r1,=0xfff00000
movs r0,#0
pop {r4-r7,r15}
6:
ldr r1,=0x7ff00000
movs r0,#0
pop {r4-r7,r15}
.ltorg
.align 2
dreddata1:
.word 0x0000B8AA @ 1/ln2 Q15
.word 0x0013DE6B @ ln2 Q62 Q62=2C5C85FDF473DE6B split into 21-bit pieces
.word 0x000FEFA3
.word 0x000B1721
dtab_exp:
.word 0xbf984bf3, 0x19f323ec @ log 1+2^-1 Q62
.word 0xcd4d10d6, 0x0e47fbe3 @ log 1+2^-2 Q62
.word 0x8abcb97a, 0x0789c1db @ log 1+2^-3 Q62
.word 0x022c54cc, 0x03e14618 @ log 1+2^-4 Q62
.word 0xe7833005, 0x01f829b0 @ log 1+2^-5 Q62
.word 0x87e01f1e, 0x00fe0545 @ log 1+2^-6 Q62
.word 0xac419e24, 0x007f80a9 @ log 1+2^-7 Q62
.word 0x45621781, 0x003fe015 @ log 1+2^-8 Q62
.word 0xa9ab10e6, 0x001ff802 @ log 1+2^-9 Q62
.word 0x55455888, 0x000ffe00 @ log 1+2^-10 Q62
.word 0x0aa9aac4, 0x0007ff80 @ log 1+2^-11 Q62
.word 0x01554556, 0x0003ffe0 @ log 1+2^-12 Q62
.word 0x002aa9ab, 0x0001fff8 @ log 1+2^-13 Q62
.word 0x00055545, 0x0000fffe @ log 1+2^-14 Q62
.word 0x8000aaaa, 0x00007fff @ log 1+2^-15 Q62
.word 0xe0001555, 0x00003fff @ log 1+2^-16 Q62
.word 0xf80002ab, 0x00001fff @ log 1+2^-17 Q62
.word 0xfe000055, 0x00000fff @ log 1+2^-18 Q62
.word 0xff80000b, 0x000007ff @ log 1+2^-19 Q62
.word 0xffe00001, 0x000003ff @ log 1+2^-20 Q62
.word 0xfff80000, 0x000001ff @ log 1+2^-21 Q62
.word 0xfffe0000, 0x000000ff @ log 1+2^-22 Q62
.word 0xffff8000, 0x0000007f @ log 1+2^-23 Q62
.word 0xffffe000, 0x0000003f @ log 1+2^-24 Q62
.word 0xfffff800, 0x0000001f @ log 1+2^-25 Q62
.word 0xfffffe00, 0x0000000f @ log 1+2^-26 Q62
.word 0xffffff80, 0x00000007 @ log 1+2^-27 Q62
.word 0xffffffe0, 0x00000003 @ log 1+2^-28 Q62
.word 0xfffffff8, 0x00000001 @ log 1+2^-29 Q62
.word 0xfffffffe, 0x00000000 @ log 1+2^-30 Q62
.word 0x80000000, 0x00000000 @ log 1+2^-31 Q62
.word 0x40000000, 0x00000000 @ log 1+2^-32 Q62
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_double/include/pico/double.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_DOUBLE_H
#define _PICO_DOUBLE_H
#include
#include "pico.h"
#include "pico/bootrom/sf_table.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file double.h
* \defgroup pico_double pico_double
*
* Optimized double-precision floating point functions
*
* (Replacement) optimized implementations are provided of the following compiler built-ins
* and math library functions:
*
* - __aeabi_dadd, __aeabi_ddiv, __aeabi_dmul, __aeabi_drsub, __aeabi_dsub, __aeabi_cdcmpeq, __aeabi_cdrcmple, __aeabi_cdcmple, __aeabi_dcmpeq, __aeabi_dcmplt, __aeabi_dcmple, __aeabi_dcmpge, __aeabi_dcmpgt, __aeabi_dcmpun, __aeabi_i2d, __aeabi_l2d, __aeabi_ui2d, __aeabi_ul2d, __aeabi_d2iz, __aeabi_d2lz, __aeabi_d2uiz, __aeabi_d2ulz, __aeabi_d2f
* - sqrt, cos, sin, tan, atan2, exp, log, ldexp, copysign, trunc, floor, ceil, round, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh, exp2, log2, exp10, log10, pow,, hypot, cbrt, fmod, drem, remainder, remquo, expm1, log1p, fma
* - powint, sincos (GNU extensions)
*
* The following additional optimized functions are also provided:
*
* - fix2double, ufix2double, fix642double, ufix642double, double2fix, double2ufix, double2fix64, double2ufix64, double2int, double2int64, double2int_z, double2int64_z
*/
double fix2double(int32_t m, int e);
double ufix2double(uint32_t m, int e);
double fix642double(int64_t m, int e);
double ufix642double(uint64_t m, int e);
// These methods round towards -Infinity.
int32_t double2fix(double f, int e);
uint32_t double2ufix(double f, int e);
int64_t double2fix64(double f, int e);
uint64_t double2ufix64(double f, int e);
int32_t double2int(double f);
int64_t double2int64(double f);
// These methods round towards 0.
int32_t double2int_z(double f);
int64_t double2int64_z(double f);
double exp10(double x);
void sincos(double x, double *sinx, double *cosx);
double powint(double x, int y);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_fix/CMakeLists.txt
================================================
pico_add_subdirectory(rp2040_usb_device_enumeration)
================================================
FILE: pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/CMakeLists.txt
================================================
pico_add_library(pico_fix_rp2040_usb_device_enumeration)
target_sources(pico_fix_rp2040_usb_device_enumeration INTERFACE
${CMAKE_CURRENT_LIST_DIR}/rp2040_usb_device_enumeration.c
)
target_include_directories(pico_fix_rp2040_usb_device_enumeration_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
pico_mirrored_target_link_libraries(pico_fix_rp2040_usb_device_enumeration INTERFACE hardware_structs hardware_gpio pico_time)
================================================
FILE: pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include/pico/fix/rp2040_usb_device_enumeration.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_FIX_RP2040_USB_DEVICE_ENUMERATION_H
#define _PICO_FIX_RP2040_USB_DEVICE_ENUMERATION_H
/*! \brief Perform a brute force workaround for USB device enumeration issue
* \ingroup pico_fix
*
* This method should be called during the IRQ handler for a bus reset
*/
void rp2040_usb_device_enumeration_fix(void);
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/rp2040_usb_device_enumeration.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
#include "pico/time.h"
#include "hardware/structs/usb.h"
#include "hardware/gpio.h"
#include "hardware/structs/iobank0.h"
#include "hardware/structs/padsbank0.h"
#include "pico/fix/rp2040_usb_device_enumeration.h"
#define LS_SE0 0b00
#define LS_J 0b01
#define LS_K 0b10
#define LS_SE1 0b11
#if PICO_RP2040_B0_SUPPORTED || PICO_RP2040_B1_SUPPORTED
static void hw_enumeration_fix_wait_se0(void);
static void hw_enumeration_fix_force_ls_j(void);
static void hw_enumeration_fix_finish(void);
void rp2040_usb_device_enumeration_fix(void) {
// Actually check for B0/B1 h/w
if (rp2040_chip_version() == 1) {
// After coming out of reset, the hardware expects 800us of LS_J (linestate J) time
// before it will move to the connected state. However on a hub that broadcasts packets
// for other devices this isn't the case. The plan here is to wait for the end of the bus
// reset, force an LS_J for 1ms and then switch control back to the USB phy. Unfortunately
// this requires us to use GPIO15 as there is no other way to force the input path.
// We only need to force DP as DM can be left at zero. It will be gated off by GPIO
// logic if it isn't func selected.
// Wait SE0 phase will call force ls_j phase which will call finish phase
hw_enumeration_fix_wait_se0();
}
}
static inline uint8_t hw_line_state(void) {
return (usb_hw->sie_status & USB_SIE_STATUS_LINE_STATE_BITS) >> USB_SIE_STATUS_LINE_STATE_LSB;
}
int64_t hw_enumeration_fix_wait_se0_callback(__unused alarm_id_t id, __unused void *user_data) {
if (hw_line_state() == LS_SE0) {
// Come back in 1ms and check again
return 1000;
} else {
// Now force LS_J (next stage of fix)
hw_enumeration_fix_force_ls_j();
// No more repeats
return 0;
}
}
static inline void hw_enumeration_fix_busy_wait_se0(void) {
while (hw_line_state() == LS_SE0) tight_loop_contents();
// Now force LS_J (next stage of fix)
hw_enumeration_fix_force_ls_j();
}
static void hw_enumeration_fix_wait_se0(void) {
// Wait for SE0 to end (i.e. the host to stop resetting). This reset can last quite long.
// 10-15ms so we are going to set a timer callback.
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
if (add_alarm_in_ms(1, hw_enumeration_fix_wait_se0_callback, NULL, true) >= 0) {
// hw_enumeration_fix_wait_se0_callback will be called in 1ms to check if se0 has finished
// (and will poll every 1ms from there)
return;
}
#endif
// if timer pool disabled, or no timer available, have to busy wait.
hw_enumeration_fix_busy_wait_se0();
}
int64_t hw_enumeration_fix_force_ls_j_done(__unused alarm_id_t id, __unused void *user_data) {
hw_enumeration_fix_finish();
return 0;
}
static uint32_t gpio_ctrl_prev = 0;
static uint32_t pad_ctrl_prev = 0;
static const uint dp = 15;
static const uint dm = 16;
static void hw_enumeration_fix_force_ls_j(void) {
// DM must be 0 for this to work. This is true if it is selected
// to any other function. fn 8 on this pin is only for debug so shouldn't
// be selected
hard_assert(gpio_get_function(dm) != 8);
// Before changing any pin state, take a copy of the current gpio control register
gpio_ctrl_prev = iobank0_hw->io[dp].ctrl;
// Also take a copy of the pads register
pad_ctrl_prev = padsbank0_hw->io[dp];
// Enable bus keep and force pin to tristate, so USB DP muxing doesn't affect
// pin state
gpio_set_pulls(dp, true, true);
gpio_set_oeover(dp, GPIO_OVERRIDE_LOW);
// Select function 8 (USB debug muxing) without disturbing other controls
hw_write_masked(&iobank0_hw->io[dp].ctrl,
8 << IO_BANK0_GPIO15_CTRL_FUNCSEL_LSB, IO_BANK0_GPIO15_CTRL_FUNCSEL_BITS);
// J state is a differential 1 for a full speed device so
// DP = 1 and DM = 0. Don't actually need to set DM low as it
// is already gated assuming it isn't funcseld.
gpio_set_inover(dp, GPIO_OVERRIDE_HIGH);
// Force PHY pull up to stay before switching away from the phy
usb_hw_t *usb_hw_set = (usb_hw_t *)hw_set_alias_untyped(usb_hw);
usb_hw_set->phy_direct = USB_USBPHY_DIRECT_DP_PULLUP_EN_BITS;
usb_hw_set->phy_direct_override = USB_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_EN_OVERRIDE_EN_BITS;
// Switch to GPIO phy with LS_J forced
usb_hw->muxing = USB_USB_MUXING_TO_DIGITAL_PAD_BITS | USB_USB_MUXING_SOFTCON_BITS;
// LS_J is now forced but while loop here just to check
hard_assert(hw_line_state() == LS_J); // "LS_J not forced!"
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
if (add_alarm_in_ms(1, hw_enumeration_fix_force_ls_j_done, NULL, true) >= 0) {
// hw_enumeration_fix_force_ls_j_done will be called in 1ms
return;
}
#endif
// if timer pool disabled, or no timer available, have to busy wait.
busy_wait_us(1000);
hw_enumeration_fix_finish();
}
static void hw_enumeration_fix_finish(void) {
// Should think we are connected now
while (!(usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS)) tight_loop_contents();
// Switch back to USB phy
usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS;
// Get rid of DP pullup override
hw_clear_bits(&usb_hw->phy_direct_override, USB_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_EN_OVERRIDE_EN_BITS);
// Finally, restore the gpio ctrl value back to GPIO15
iobank0_hw->io[dp].ctrl = gpio_ctrl_prev;
// Restore the pad ctrl value
padsbank0_hw->io[dp] = pad_ctrl_prev;
}
#else
void rp2040_usb_device_enumeration_fix(void) {
// nothing to do
}
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_flash/CMakeLists.txt
================================================
pico_add_library(pico_flash)
target_sources(pico_flash INTERFACE
${CMAKE_CURRENT_LIST_DIR}/flash.c
)
target_include_directories(pico_flash_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
# just include multicore headers, as we don't want to pull in the lib if it isn't pulled in already
target_link_libraries(pico_flash INTERFACE pico_multicore_headers)
pico_mirrored_target_link_libraries(pico_flash INTERFACE pico_time hardware_sync)
================================================
FILE: pico-sdk/src/rp2_common/pico_flash/flash.c
================================================
/*
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/flash.h"
#include "hardware/exception.h"
#include "hardware/sync.h"
#if PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
#include "pico/multicore.h"
#endif
#if PICO_FLASH_SAFE_EXECUTE_SUPPORT_FREERTOS_SMP
#include "FreeRTOS.h"
#include "task.h"
// now we have FreeRTOS header we can check core count... we can only use FreeRTOS SMP mechanism
// with two cores
#if configNUM_CORES == 2
#if configUSE_CORE_AFFINITY
#define PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP 1
#else
#error configUSE_CORE_AFFINITY is required for PICO_FLASH_SAFE_EXECUTE_SUPPORT_FREERTOS_SMP
#endif
#endif
#endif
// There are multiple scenarios:
//
// 1. No use of core 1 - we just want to disable IRQs and not wait on core 1 to acquiesce
// 2. Regular pico_multicore - we need to use multicore lockout.
// 3. FreeRTOS on core 0, no use of core 1 - we just want to disable IRQs
// 4. FreeRTOS SMP on both cores - we need to schedule a high priority task on the other core to disable IRQs.
// 5. FreeRTOS on one core, but application is using the other core. ** WE CANNOT SUPPORT THIS TODAY ** without
// the equivalent PICO_FLASH_ASSUME_COREx_SAFE (i.e. the user mkaing sure the other core is fine)
static bool default_core_init_deinit(bool init);
static int default_enter_safe_zone_timeout_ms(uint32_t timeout_ms);
static int default_exit_safe_zone_timeout_ms(uint32_t timeout_ms);
// note the default methods are combined, rather than having a separate helper for
// FreeRTOS, as we may support mixed multicore and non SMP FreeRTOS in the future
static flash_safety_helper_t default_flash_safety_helper = {
.core_init_deinit = default_core_init_deinit,
.enter_safe_zone_timeout_ms = default_enter_safe_zone_timeout_ms,
.exit_safe_zone_timeout_ms = default_exit_safe_zone_timeout_ms
};
#if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
enum {
FREERTOS_LOCKOUT_NONE = 0,
FREERTOS_LOCKOUT_LOCKER_WAITING,
FREERTOS_LOCKOUT_LOCKEE_READY,
FREERTOS_LOCKOUT_LOCKER_DONE,
FREERTOS_LOCKOUT_LOCKEE_DONE,
};
// state for the lockout operation launched from the corresponding core
static volatile uint8_t lockout_state[NUM_CORES];
#endif
__attribute__((weak)) flash_safety_helper_t *get_flash_safety_helper(void) {
return &default_flash_safety_helper;
}
bool flash_safe_execute_core_init(void) {
flash_safety_helper_t *helper = get_flash_safety_helper();
return helper ? helper->core_init_deinit(true) : false;
}
bool flash_safe_execute_core_deinit(void) {
flash_safety_helper_t *helper = get_flash_safety_helper();
return helper ? helper->core_init_deinit(false) : false;
}
int flash_safe_execute(void (*func)(void *), void *param, uint32_t enter_exit_timeout_ms) {
flash_safety_helper_t *helper = get_flash_safety_helper();
if (!helper) return PICO_ERROR_NOT_PERMITTED;
int rc = helper->enter_safe_zone_timeout_ms(enter_exit_timeout_ms);
if (!rc) {
func(param);
rc = helper->exit_safe_zone_timeout_ms(enter_exit_timeout_ms);
}
return rc;
}
static bool default_core_init_deinit(__unused bool init) {
#if PICO_FLASH_ASSUME_CORE0_SAFE
if (!get_core_num()) return true;
#endif
#if PICO_FLASH_ASSUME_CORE1_SAFE
if (get_core_num()) return true;
#endif
#if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
return true;
#endif
#if PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
if (!init) {
return false;
}
multicore_lockout_victim_init();
#endif
return true;
}
// irq_state for the lockout operation launched from the corresponding core
static uint32_t irq_state[NUM_CORES];
static bool use_irq_only(void) {
#if PICO_FLASH_ASSUME_CORE0_SAFE
if (get_core_num()) return true;
#endif
#if PICO_FLASH_ASSUME_CORE1_SAFE
if (!get_core_num()) return true;
#endif
return false;
}
#if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
static void __not_in_flash_func(flash_lockout_task)(__unused void *vother_core_num) {
uint other_core_num = (uintptr_t)vother_core_num;
while (lockout_state[other_core_num] != FREERTOS_LOCKOUT_LOCKER_WAITING) {
__wfe(); // we don't bother to try to let lower priority tasks run
}
uint32_t save = save_and_disable_interrupts();
lockout_state[other_core_num] = FREERTOS_LOCKOUT_LOCKEE_READY;
__sev();
while (lockout_state[other_core_num] == FREERTOS_LOCKOUT_LOCKEE_READY) {
__wfe(); // we don't bother to try to let lower priority tasks run
}
restore_interrupts(save);
lockout_state[other_core_num] = FREERTOS_LOCKOUT_LOCKEE_DONE;
__sev();
// bye bye
vTaskDelete(NULL);
}
#endif
static int default_enter_safe_zone_timeout_ms(__unused uint32_t timeout_ms) {
int rc = PICO_OK;
if (!use_irq_only()) {
#if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
// Note that whilst taskENTER_CRITICAL sounds promising (and on non SMP it disabled IRQs), on SMP
// it only prevents the other core from also entering a critical section.
// Therefore, we must do our own handshake which starts a task on the other core and have it disable interrupts
uint core_num = get_core_num();
// create at low priority
TaskHandle_t task_handle;
if (pdPASS != xTaskCreate(flash_lockout_task, "flash lockout", configMINIMAL_STACK_SIZE, (void *)core_num, 0, &task_handle)) {
return PICO_ERROR_INSUFFICIENT_RESOURCES;
}
lockout_state[core_num] = FREERTOS_LOCKOUT_LOCKER_WAITING;
__sev();
// bind to other core
vTaskCoreAffinitySet(task_handle, 1u << (core_num ^ 1));
// and make it super high priority
vTaskPrioritySet(task_handle, configMAX_PRIORITIES -1);
absolute_time_t until = make_timeout_time_ms(timeout_ms);
while (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_READY && !time_reached(until)) {
__wfe(); // we don't bother to try to let lower priority tasks run
}
if (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_READY) {
lockout_state[core_num] = FREERTOS_LOCKOUT_LOCKER_DONE;
rc = PICO_ERROR_TIMEOUT;
}
// todo we may get preempted here, but I think that is OK unless what is pre-empts requires
// the other core to be running.
#elif PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
// we cannot mix multicore_lockout and FreeRTOS as they both use the multicore FIFO...
// the user, will have to roll their own mechanism in this case.
#if LIB_FREERTOS_KERNEL
#if PICO_FLASH_ASSERT_ON_UNSAFE
assert(false); // we expect the other core to have been initialized via flash_safe_execute_core_init()
// unless PICO_FLASH_ASSUME_COREX_SAFE is set
#endif
rc = PICO_ERROR_NOT_PERMITTED;
#else // !LIB_FREERTOS_KERNEL
if (multicore_lockout_victim_is_initialized(get_core_num()^1)) {
if (!multicore_lockout_start_timeout_us(timeout_ms * 1000ull)) {
rc = PICO_ERROR_TIMEOUT;
}
} else {
#if PICO_FLASH_ASSERT_ON_UNSAFE
assert(false); // we expect the other core to have been initialized via flash_safe_execute_core_init()
// unless PICO_FLASH_ASSUME_COREX_SAFE is set
#endif
rc = PICO_ERROR_NOT_PERMITTED;
}
#endif // !LIB_FREERTOS_KERNEL
#else
// no support for making other core safe provided, so fall through to irq
// note this is the case for a regular single core program
#endif
}
if (rc == PICO_OK) {
// we always want to disable IRQs on our core
irq_state[get_core_num()] = save_and_disable_interrupts();
}
return rc;
}
static int default_exit_safe_zone_timeout_ms(__unused uint32_t timeout_ms) {
// assume if we're exiting we're called then entry happened successfully
restore_interrupts(irq_state[get_core_num()]);
if (!use_irq_only()) {
#if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
uint core_num = get_core_num();
lockout_state[core_num] = FREERTOS_LOCKOUT_LOCKER_DONE;
__sev();
absolute_time_t until = make_timeout_time_ms(timeout_ms);
while (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_DONE && !time_reached(until)) {
__wfe(); // we don't bother to try to let lower priority tasks run
}
if (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_DONE) {
return PICO_ERROR_TIMEOUT;
}
#elif PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
return multicore_lockout_end_timeout_us(timeout_ms * 1000ull) ? PICO_OK : PICO_ERROR_TIMEOUT;
#endif
}
return PICO_OK;
}
================================================
FILE: pico-sdk/src/rp2_common/pico_flash/include/pico/flash.h
================================================
/*
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_FLASH_H
#define _PICO_FLASH_H
#include "pico.h"
#include "hardware/flash.h"
#include "pico/time.h"
/** \file pico/flash.h
* \defgroup pico_flash pico_flash
*
* High level flash API
*
* Flash cannot be erased or written to when in XIP mode. However the system cannot directly access memory in the flash
* address space when not in XIP mode.
*
* It is therefore critical that no code or data is being read from flash while flash is been written or erased.
*
* If only one core is being used, then the problem is simple - just disable interrupts; however if code is running on
* the other core, then it has to be asked, nicely, to avoid flash for a bit. This is hard to do if you don't have
* complete control of the code running on that core at all times.
*
* This library provides a \ref flash_safe_execute method which calls a function back having sucessfully gotten
* into a state where interrupts are disabled, and the other core is not executing or reading from flash.
*
* How it does this is dependent on the supported environment (Free RTOS SMP or pico_multicore). Additionally
* the user can provide their own mechanism by providing a strong definition of \ref get_flash_safety_helper().
*
* Using the default settings, flash_safe_execute will only call the callback function if the state is safe
* otherwise returning an error (or an assert depending on \ref PICO_FLASH_ASSERT_ON_UNSAFE).
*
* There are conditions where safety would not be guaranteed:
*
* 1. FreeRTOS smp with `configNUM_CORES=1` - FreeRTOS still uses pico_multicore in this case, so \ref flash_safe_execute
* cannot know what the other core is doing, and there is no way to force code execution between a FreeRTOS core
* and a non FreeRTOS core.
* 2. FreeRTOS non SMP with pico_multicore - Again, there is no way to force code execution between a FreeRTOS core and
* a non FreeRTOS core.
* 3. pico_multicore without \ref flash_safe_execute_core_init() having been called on the other core - The
* \ref flash_safe_execute method does not know if code is executing on the other core, so it has to assume it is. Either
* way, it is not able to intervene if \ref flash_safe_execute_core_init() has not been called on the other core.
*
* Fortunately, all is not lost in this situation, you may:
*
* * Set \ref PICO_FLASH_ASSUME_CORE0_SAFE=1 to explicitly say that core 0 is never using flash.
* * Set \ref PICO_FLASH_ASSUME_CORE1_SAFE=1 to explicitly say that core 1 is never using flash.
*/
#ifdef __cplusplus
extern "C" {
#endif
/**
* Initialize a core such that the other core can lock it out during \ref flash_safe_execute.
* \ingroup pico_flash
*
* \note This is not necessary for FreeRTOS SMP, but should be used when launching via \ref multicore_launch_core1
* \return true on success; there is no need to call \ref flash_safe_execute_core_deinit() on failure.
*/
bool flash_safe_execute_core_init(void);
/**
* De-initialize work done by \ref flash_safe_execute_core_init
* \ingroup pico_flash
* \return true on success
*/
bool flash_safe_execute_core_deinit(void);
/**
* Execute a function with IRQs disabled and with the other core also not executing/reading flash
* \ingroup pico_flash
*
* \param func the function to call
* \param param the parameter to pass to the function
* \param enter_exit_timeout_ms the timeout for each of the enter/exit phases when coordinating with the other core
*
* \return PICO_OK on success (the function will have been called).
* PICO_TIMEOUT on timeout (the function may have been called).
* PICO_ERROR_NOT_PERMITTED if safe execution is not possible (the function will not have been called).
* PICO_ERROR_INSUFFICIENT_RESOURCES if the method fails due to dynamic resource exhaustion (the function will not have been called)
* \note if \ref PICO_FLASH_ASSERT_ON_UNSAFE is 1, this function will assert in debug mode vs returning
* PICO_ERROR_NOT_PERMITTED
*/
int flash_safe_execute(void (*func)(void *), void *param, uint32_t enter_exit_timeout_ms);
// PICO_CONFIG: PICO_FLASH_ASSERT_ON_UNSAFE, Assert in debug mode rather than returning an error if flash_safe_execute cannot guarantee safety to catch bugs early, type=bool, default=1, group=pico_flash
#ifndef PICO_FLASH_ASSERT_ON_UNSAFE
#define PICO_FLASH_ASSERT_ON_UNSAFE 1
#endif
// PICO_CONFIG: PICO_FLASH_ASSUME_CORE0_SAFE, Assume that core 0 will never be accessing flash and so doesn't need to be considered during flash_safe_execute, type=bool, default=0, group=pico_flash
#ifndef PICO_FLASH_ASSUME_CORE0_SAFE
#define PICO_FLASH_ASSUME_CORE0_SAFE 0
#endif
// PICO_CONFIG: PICO_FLASH_ASSUME_CORE1_SAFE, Assume that core 1 will never be accessing flash and so doesn't need to be considered during flash_safe_execute, type=bool, default=0, group=pico_flash
#ifndef PICO_FLASH_ASSUME_CORE1_SAFE
#define PICO_FLASH_ASSUME_CORE1_SAFE 0
#endif
// PICO_CONFIG: PICO_FLASH_SAFE_EXECUTE_SUPPORT_FREERTOS_SMP, Support using FreeRTOS SMP to make the other core safe during flash_safe_execute, type=bool, default=1 when using FreeRTOS SMP, group=pico_flash
#ifndef PICO_FLASH_SAFE_EXECUTE_SUPPORT_FREERTOS_SMP
#if LIB_FREERTOS_KERNEL && FREE_RTOS_KERNEL_SMP // set by RP2040 SMP port
#define PICO_FLASH_SAFE_EXECUTE_SUPPORT_FREERTOS_SMP 1
#endif
#endif
// PICO_CONFIG: PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT, Support using multicore_lockout functions to make the other core safe during flash_safe_execute, type=bool, default=1 when using pico_multicore, group=pico_flash
#ifndef PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
#if LIB_PICO_MULTICORE
#define PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT 1
#endif
#endif
typedef struct {
bool (*core_init_deinit)(bool init);
int (*enter_safe_zone_timeout_ms)(uint32_t timeout_ms);
int (*exit_safe_zone_timeout_ms)(uint32_t timeout_ms);
} flash_safety_helper_t;
/**
* Internal method to return the flash safety helper implementation.
* \ingroup pico_flash
*
* Advanced users can provide their own implementation of this function to perform
* different inter-core coordination before disabling XIP mode.
*
* @return the \ref flash_safety_helper_t
*/
flash_safety_helper_t *get_flash_safety_helper(void);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_float/CMakeLists.txt
================================================
if (NOT TARGET pico_float)
# library to be depended on - we make this depend on particular implementations using per target generator expressions
pico_add_library(pico_float)
# no custom implementation; falls thru to compiler
pico_add_library(pico_float_compiler)
target_include_directories(pico_float_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
# add alias "default" which is just rom.
add_library(pico_float_default INTERFACE)
target_link_libraries(pico_float_default INTERFACE pico_float_pico)
set(PICO_DEFAULT_FLOAT_IMPL pico_float_default)
target_link_libraries(pico_float INTERFACE
$>,$,${PICO_DEFAULT_FLOAT_IMPL}>)
pico_add_library(pico_float_pico)
target_sources(pico_float_pico INTERFACE
${CMAKE_CURRENT_LIST_DIR}/float_aeabi.S
${CMAKE_CURRENT_LIST_DIR}/float_init_rom.c
${CMAKE_CURRENT_LIST_DIR}/float_math.c
${CMAKE_CURRENT_LIST_DIR}/float_v1_rom_shim.S
)
target_link_libraries(pico_float_pico INTERFACE pico_bootrom pico_float_headers hardware_divider)
pico_add_library(pico_float_none)
target_sources(pico_float_none INTERFACE
${CMAKE_CURRENT_LIST_DIR}/float_none.S
)
target_link_libraries(pico_float_none INTERFACE pico_float_headers)
function(wrap_float_functions TARGET)
pico_wrap_function(${TARGET} __aeabi_fadd)
pico_wrap_function(${TARGET} __aeabi_fdiv)
pico_wrap_function(${TARGET} __aeabi_fmul)
pico_wrap_function(${TARGET} __aeabi_frsub)
pico_wrap_function(${TARGET} __aeabi_fsub)
pico_wrap_function(${TARGET} __aeabi_cfcmpeq)
pico_wrap_function(${TARGET} __aeabi_cfrcmple)
pico_wrap_function(${TARGET} __aeabi_cfcmple)
pico_wrap_function(${TARGET} __aeabi_fcmpeq)
pico_wrap_function(${TARGET} __aeabi_fcmplt)
pico_wrap_function(${TARGET} __aeabi_fcmple)
pico_wrap_function(${TARGET} __aeabi_fcmpge)
pico_wrap_function(${TARGET} __aeabi_fcmpgt)
pico_wrap_function(${TARGET} __aeabi_fcmpun)
pico_wrap_function(${TARGET} __aeabi_i2f)
pico_wrap_function(${TARGET} __aeabi_l2f)
pico_wrap_function(${TARGET} __aeabi_ui2f)
pico_wrap_function(${TARGET} __aeabi_ul2f)
pico_wrap_function(${TARGET} __aeabi_f2iz)
pico_wrap_function(${TARGET} __aeabi_f2lz)
pico_wrap_function(${TARGET} __aeabi_f2uiz)
pico_wrap_function(${TARGET} __aeabi_f2ulz)
pico_wrap_function(${TARGET} __aeabi_f2d)
pico_wrap_function(${TARGET} sqrtf)
pico_wrap_function(${TARGET} cosf)
pico_wrap_function(${TARGET} sinf)
pico_wrap_function(${TARGET} tanf)
pico_wrap_function(${TARGET} atan2f)
pico_wrap_function(${TARGET} expf)
pico_wrap_function(${TARGET} logf)
pico_wrap_function(${TARGET} ldexpf)
pico_wrap_function(${TARGET} copysignf)
pico_wrap_function(${TARGET} truncf)
pico_wrap_function(${TARGET} floorf)
pico_wrap_function(${TARGET} ceilf)
pico_wrap_function(${TARGET} roundf)
pico_wrap_function(${TARGET} sincosf) # gnu
pico_wrap_function(${TARGET} asinf)
pico_wrap_function(${TARGET} acosf)
pico_wrap_function(${TARGET} atanf)
pico_wrap_function(${TARGET} sinhf)
pico_wrap_function(${TARGET} coshf)
pico_wrap_function(${TARGET} tanhf)
pico_wrap_function(${TARGET} asinhf)
pico_wrap_function(${TARGET} acoshf)
pico_wrap_function(${TARGET} atanhf)
pico_wrap_function(${TARGET} exp2f)
pico_wrap_function(${TARGET} log2f)
pico_wrap_function(${TARGET} exp10f)
pico_wrap_function(${TARGET} log10f)
pico_wrap_function(${TARGET} powf)
pico_wrap_function(${TARGET} powintf) #gnu
pico_wrap_function(${TARGET} hypotf)
pico_wrap_function(${TARGET} cbrtf)
pico_wrap_function(${TARGET} fmodf)
pico_wrap_function(${TARGET} dremf)
pico_wrap_function(${TARGET} remainderf)
pico_wrap_function(${TARGET} remquof)
pico_wrap_function(${TARGET} expm1f)
pico_wrap_function(${TARGET} log1pf)
pico_wrap_function(${TARGET} fmaf)
endfunction()
wrap_float_functions(pico_float_pico)
wrap_float_functions(pico_float_none)
macro(pico_set_float_implementation TARGET IMPL)
get_target_property(target_type ${TARGET} TYPE)
if ("EXECUTABLE" STREQUAL "${target_type}")
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_FLOAT_IMPL "pico_float_${IMPL}")
else()
message(FATAL_ERROR "float implementation must be set on executable not library")
endif()
endmacro()
endif()
================================================
FILE: pico-sdk/src/rp2_common/pico_float/float_aeabi.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/asm_helper.S"
#include "pico/bootrom/sf_table.h"
#include "hardware/divider_helper.S"
__pre_init __aeabi_float_init, 00020
pico_default_asm_setup
.macro float_section name
#if PICO_FLOAT_IN_RAM
.section RAM_SECTION_NAME(\name), "ax"
#else
.section SECTION_NAME(\name), "ax"
#endif
.endm
.macro float_wrapper_section func
float_section WRAPPER_FUNC_NAME(\func)
.endm
.macro _float_wrapper_func x
wrapper_func \x
.endm
.macro wrapper_func_f1 x
_float_wrapper_func \x
#if PICO_FLOAT_PROPAGATE_NANS
mov ip, lr
bl __check_nan_f1
mov lr, ip
#endif
.endm
.macro wrapper_func_f2 x
_float_wrapper_func \x
#if PICO_FLOAT_PROPAGATE_NANS
mov ip, lr
bl __check_nan_f2
mov lr, ip
#endif
.endm
.section .text
#if PICO_FLOAT_PROPAGATE_NANS
.thumb_func
__check_nan_f1:
movs r3, #1
lsls r3, #24
lsls r2, r0, #1
adds r2, r3
bhi 1f
bx lr
1:
bx ip
.thumb_func
__check_nan_f2:
movs r3, #1
lsls r3, #24
lsls r2, r0, #1
adds r2, r3
bhi 1f
lsls r2, r1, #1
adds r2, r3
bhi 2f
bx lr
2:
mov r0, r1
1:
bx ip
#endif
.macro table_tail_call SF_TABLE_OFFSET
#if PICO_FLOAT_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
#ifndef NDEBUG
movs r3, #0
mov ip, r3
#endif
#endif
ldr r3, =sf_table
ldr r3, [r3, #\SF_TABLE_OFFSET]
bx r3
.endm
.macro shimmable_table_tail_call SF_TABLE_OFFSET shim
ldr r3, =sf_table
ldr r3, [r3, #\SF_TABLE_OFFSET]
#if PICO_FLOAT_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
mov ip, pc
#endif
bx r3
#if PICO_FLOAT_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
.byte \SF_TABLE_OFFSET, 0xdf
.word \shim
#endif
.endm
// note generally each function is in a separate section unless there is fall thru or branching between them
// note fadd, fsub, fmul, fdiv are so tiny and just defer to rom so are lumped together so they can share constant pool
// note functions are word aligned except where they are an odd number of linear instructions
// float FUNC_NAME(__aeabi_fadd)(float, float) single-precision addition
float_wrapper_section __aeabi_farithmetic
// float FUNC_NAME(__aeabi_frsub)(float x, float y) single-precision reverse subtraction, y - x
// frsub first because it is the only one that needs alignment
.align 2
wrapper_func __aeabi_frsub
eors r0, r1
eors r1, r0
eors r0, r1
// fall thru
// float FUNC_NAME(__aeabi_fsub)(float x, float y) single-precision subtraction, x - y
wrapper_func_f2 __aeabi_fsub
#if PICO_FLOAT_PROPAGATE_NANS
// we want to return nan for inf-inf or -inf - -inf, but without too much upfront cost
mov r2, r0
eors r2, r1
bmi 1f // different signs
push {r0, r1, lr}
bl 1f
b fdiv_fsub_nan_helper
1:
#endif
table_tail_call SF_TABLE_FSUB
wrapper_func_f2 __aeabi_fadd
table_tail_call SF_TABLE_FADD
// float FUNC_NAME(__aeabi_fdiv)(float n, float d) single-precision division, n / d
wrapper_func_f2 __aeabi_fdiv
#if PICO_FLOAT_PROPAGATE_NANS
push {r0, r1, lr}
bl 1f
b fdiv_fsub_nan_helper
1:
#endif
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
// to support IRQ usage (or context switch) we must save/restore divider state around call if state is dirty
ldr r2, =(SIO_BASE)
ldr r3, [r2, #SIO_DIV_CSR_OFFSET]
lsrs r3, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
bcs fdiv_save_state
#else
// to avoid worrying about IRQs (or context switches), simply disable interrupts around call
push {r4, lr}
mrs r4, PRIMASK
cpsid i
bl fdiv_shim_call
msr PRIMASK, r4
pop {r4, pc}
#endif
fdiv_shim_call:
table_tail_call SF_TABLE_FDIV
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
fdiv_save_state:
save_div_state_and_lr
bl fdiv_shim_call
ldr r2, =(SIO_BASE)
restore_div_state_and_return
#endif
fdiv_fsub_nan_helper:
#if PICO_FLOAT_PROPAGATE_NANS
pop {r1, r2}
// check for infinite op infinite (or rather check for infinite result with both
// operands being infinite)
lsls r3, r0, #1
asrs r3, r3, #24
adds r3, #1
beq 2f
pop {pc}
2:
lsls r1, #1
asrs r1, r1, #24
lsls r2, #1
asrs r2, r2, #24
ands r1, r2
adds r1, #1
bne 3f
// infinite to nan
movs r1, #1
lsls r1, #22
orrs r0, r1
3:
pop {pc}
#endif
// float FUNC_NAME(__aeabi_fmul)(float, float) single-precision multiplication
wrapper_func_f2 __aeabi_fmul
#if PICO_FLOAT_PROPAGATE_NANS
push {r0, r1, lr}
bl 1f
pop {r1, r2}
// check for multiplication of infinite by zero (or rather check for infinite result with either
// operand 0)
lsls r3, r0, #1
asrs r3, r3, #24
adds r3, #1
beq 2f
pop {pc}
2:
ands r1, r2
bne 3f
// infinite to nan
movs r1, #1
lsls r1, #22
orrs r0, r1
3:
pop {pc}
1:
#endif
table_tail_call SF_TABLE_FMUL
// void FUNC_NAME(__aeabi_cfrcmple)(float, float) reversed 3-way (<, =, ?>) compare [1], result in PSR ZC flags
float_wrapper_section __aeabi_cfcmple
.align 2
wrapper_func __aeabi_cfrcmple
push {r0-r2, lr}
eors r0, r1
eors r1, r0
eors r0, r1
b __aeabi_cfcmple_guts
// NOTE these share an implementation as we have no excepting NaNs.
// void FUNC_NAME(__aeabi_cfcmple)(float, float) 3-way (<, =, ?>) compare [1], result in PSR ZC flags
// void FUNC_NAME(__aeabi_cfcmpeq)(float, float) non-excepting equality comparison [1], result in PSR ZC flags
.align 2
wrapper_func __aeabi_cfcmple
wrapper_func __aeabi_cfcmpeq
push {r0-r2, lr}
__aeabi_cfcmple_guts:
lsls r2,r0,#1
lsrs r2,#24
beq 1f
cmp r2,#0xff
bne 2f
lsls r2, r0, #9
bhi 3f
1:
lsrs r0,#23 @ clear mantissa if denormal or infinite
lsls r0,#23
2:
lsls r2,r1,#1
lsrs r2,#24
beq 1f
cmp r2,#0xff
bne 2f
lsls r2, r1, #9
bhi 3f
1:
lsrs r1,#23 @ clear mantissa if denormal or infinite
lsls r1,#23
2:
movs r2,#1 @ initialise result
eors r1,r0
bmi 2f @ opposite signs? then can proceed on basis of sign of x
eors r1,r0 @ restore y
bpl 1f
cmp r1,r0
pop {r0-r2, pc}
1:
cmp r0,r1
pop {r0-r2, pc}
2:
orrs r1, r0 @ handle 0/-0
adds r1, r1 @ note this always sets C
beq 3f
mvns r0, r0 @ carry inverse of r0 sign
adds r0, r0
3:
pop {r0-r2, pc}
// int FUNC_NAME(__aeabi_fcmpeq)(float, float) result (1, 0) denotes (=, ?<>) [2], use for C == and !=
float_wrapper_section __aeabi_fcmpeq
.align 2
wrapper_func __aeabi_fcmpeq
push {lr}
bl __aeabi_cfcmpeq
beq 1f
movs r0, #0
pop {pc}
1:
movs r0, #1
pop {pc}
// int FUNC_NAME(__aeabi_fcmplt)(float, float) result (1, 0) denotes (<, ?>=) [2], use for C <
float_wrapper_section __aeabi_fcmplt
.align 2
wrapper_func __aeabi_fcmplt
push {lr}
bl __aeabi_cfcmple
sbcs r0, r0
pop {pc}
// int FUNC_NAME(__aeabi_fcmple)(float, float) result (1, 0) denotes (<=, ?>) [2], use for C <=
float_wrapper_section __aeabi_fcmple
.align 2
wrapper_func __aeabi_fcmple
push {lr}
bl __aeabi_cfcmple
bls 1f
movs r0, #0
pop {pc}
1:
movs r0, #1
pop {pc}
// int FUNC_NAME(__aeabi_fcmpge)(float, float) result (1, 0) denotes (>=, ?<) [2], use for C >=
float_wrapper_section __aeabi_fcmpge
.align 2
wrapper_func __aeabi_fcmpge
push {lr}
// because of NaNs it is better to reverse the args than the result
bl __aeabi_cfrcmple
bls 1f
movs r0, #0
pop {pc}
1:
movs r0, #1
pop {pc}
// int FUNC_NAME(__aeabi_fcmpgt)(float, float) result (1, 0) denotes (>, ?<=) [2], use for C >
float_wrapper_section __aeabi_fcmpgt
wrapper_func __aeabi_fcmpgt
push {lr}
// because of NaNs it is better to reverse the args than the result
bl __aeabi_cfrcmple
sbcs r0, r0
pop {pc}
// int FUNC_NAME(__aeabi_fcmpun)(float, float) result (1, 0) denotes (?, <=>) [2], use for C99 isunordered()
float_wrapper_section __aeabi_fcmpun
wrapper_func __aeabi_fcmpun
movs r3, #1
lsls r3, #24
lsls r2, r0, #1
adds r2, r3
bhi 1f
lsls r2, r1, #1
adds r2, r3
bhi 1f
movs r0, #0
bx lr
1:
movs r0, #1
bx lr
// float FUNC_NAME(__aeabi_ui2f)(unsigned) unsigned to float (single precision) conversion
float_wrapper_section __aeabi_ui2f
wrapper_func __aeabi_ui2f
subs r1, r1
cmp r0, #0
bne __aeabi_i2f_main
mov r0, r1
bx lr
float_wrapper_section __aeabi_i2f
// float FUNC_NAME(__aeabi_i2f)(int) integer to float (single precision) conversion
wrapper_func __aeabi_i2f
lsrs r1, r0, #31
lsls r1, #31
bpl 1f
negs r0, r0
1:
cmp r0, #0
beq 7f
__aeabi_i2f_main:
mov ip, lr
push {r0, r1}
ldr r3, =sf_clz_func
ldr r3, [r3]
blx r3
pop {r1, r2}
lsls r1, r0
subs r0, #158
negs r0, r0
adds r1,#0x80 @ rounding
bcs 5f @ tripped carry? then have leading 1 in C as required (and result is even so can ignore sticky bits)
lsls r3,r1,#24 @ check bottom 8 bits of r1
beq 6f @ in rounding-tie case?
lsls r1,#1 @ remove leading 1
3:
lsrs r1,#9 @ align mantissa
lsls r0,#23 @ align exponent
orrs r0,r2 @ assemble exponent and mantissa
4:
orrs r0,r1 @ apply sign
1:
bx ip
5:
adds r0,#1 @ correct exponent offset
b 3b
6:
lsrs r1,#9 @ ensure even result
lsls r1,#10
b 3b
7:
bx lr
// int FUNC_NAME(__aeabi_f2iz)(float) float (single precision) to integer C-style conversion [3]
float_wrapper_section __aeabi_f2iz
wrapper_func __aeabi_f2iz
regular_func float2int_z
lsls r1, r0, #1
lsrs r2, r1, #24
movs r3, #0x80
lsls r3, #24
cmp r2, #126
ble 1f
subs r2, #158
bge 2f
asrs r1, r0, #31
lsls r0, #9
lsrs r0, #1
orrs r0, r3
negs r2, r2
lsrs r0, r2
lsls r1, #1
adds r1, #1
muls r0, r1
bx lr
1:
movs r0, #0
bx lr
2:
lsrs r0, #31
adds r0, r3
subs r0, #1
bx lr
cmn r0, r0
bcc float2int
push {lr}
lsls r0, #1
lsrs r0, #1
movs r1, #0
bl __aeabi_f2uiz
cmp r0, #0
bmi 1f
negs r0, r0
pop {pc}
1:
movs r0, #128
lsls r0, #24
pop {pc}
float_section float2int
regular_func float2int
shimmable_table_tail_call SF_TABLE_FLOAT2INT float2int_shim
float_section float2fix
regular_func float2fix
shimmable_table_tail_call SF_TABLE_FLOAT2FIX float2fix_shim
float_section float2ufix
regular_func float2ufix
table_tail_call SF_TABLE_FLOAT2UFIX
// unsigned FUNC_NAME(__aeabi_f2uiz)(float) float (single precision) to unsigned C-style conversion [3]
float_wrapper_section __aeabi_f2uiz
wrapper_func __aeabi_f2uiz
table_tail_call SF_TABLE_FLOAT2UINT
float_section fix2float
regular_func fix2float
table_tail_call SF_TABLE_FIX2FLOAT
float_section ufix2float
regular_func ufix2float
table_tail_call SF_TABLE_UFIX2FLOAT
float_section fix642float
regular_func fix642float
shimmable_table_tail_call SF_TABLE_FIX642FLOAT fix642float_shim
float_section ufix642float
regular_func ufix642float
shimmable_table_tail_call SF_TABLE_UFIX642FLOAT ufix642float_shim
// float FUNC_NAME(__aeabi_l2f)(long long) long long to float (single precision) conversion
float_wrapper_section __aeabi_l2f
1:
ldr r2, =__aeabi_i2f
bx r2
wrapper_func __aeabi_l2f
asrs r2, r0, #31
cmp r1, r2
beq 1b
shimmable_table_tail_call SF_TABLE_INT642FLOAT int642float_shim
// float FUNC_NAME(__aeabi_l2f)(long long) long long to float (single precision) conversion
float_wrapper_section __aeabi_ul2f
1:
ldr r2, =__aeabi_ui2f
bx r2
wrapper_func __aeabi_ul2f
cmp r1, #0
beq 1b
shimmable_table_tail_call SF_TABLE_UINT642FLOAT uint642float_shim
// long long FUNC_NAME(__aeabi_f2lz)(float) float (single precision) to long long C-style conversion [3]
float_wrapper_section __aeabi_f2lz
wrapper_func __aeabi_f2lz
regular_func float2int64_z
cmn r0, r0
bcc float2int64
push {lr}
lsls r0, #1
lsrs r0, #1
movs r1, #0
bl float2ufix64
cmp r1, #0
bmi 1f
movs r2, #0
negs r0, r0
sbcs r2, r1
mov r1, r2
pop {pc}
1:
movs r1, #128
lsls r1, #24
movs r0, #0
pop {pc}
float_section float2int64
regular_func float2int64
shimmable_table_tail_call SF_TABLE_FLOAT2INT64 float2int64_shim
float_section float2fix64
regular_func float2fix64
shimmable_table_tail_call SF_TABLE_FLOAT2FIX64 float2fix64_shim
// unsigned long long FUNC_NAME(__aeabi_f2ulz)(float) float to unsigned long long C-style conversion [3]
float_wrapper_section __aeabi_f2ulz
wrapper_func __aeabi_f2ulz
shimmable_table_tail_call SF_TABLE_FLOAT2UINT64 float2uint64_shim
float_section float2ufix64
regular_func float2ufix64
shimmable_table_tail_call SF_TABLE_FLOAT2UFIX64 float2ufix64_shim
float_wrapper_section __aeabi_f2d
1:
#if PICO_FLOAT_PROPAGATE_NANS
// copy sign bit and 25 NAN id bits into sign bit and significant ID bits, also setting the high id bit
asrs r1, r0, #3
movs r2, #0xf
lsls r2, #27
orrs r1, r2
lsls r0, #25
bx lr
#endif
wrapper_func __aeabi_f2d
#if PICO_FLOAT_PROPAGATE_NANS
movs r3, #1
lsls r3, #24
lsls r2, r0, #1
adds r2, r3
bhi 1b
#endif
shimmable_table_tail_call SF_TABLE_FLOAT2DOUBLE float2double_shim
float_wrapper_section sqrtf
wrapper_func_f1 sqrtf
#if PICO_FLOAT_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
// check for negative
asrs r1, r0, #23
bmi 1f
#endif
table_tail_call SF_TABLE_FSQRT
#if PICO_FLOAT_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
1:
mvns r0, r1
cmp r0, #255
bne 2f
// -0 or -Denormal return -0 (0x80000000)
lsls r0, #31
bx lr
2:
// return -Inf (0xff800000)
asrs r0, r1, #31
lsls r0, #23
bx lr
#endif
float_wrapper_section cosf
// note we don't use _f1 since we do an infinity/nan check for outside of range
wrapper_func cosf
// rom version only works for -128 < angle < 128
lsls r1, r0, #1
lsrs r1, #24
cmp r1, #127 + 7
bge 1f
2:
table_tail_call SF_TABLE_FCOS
1:
#if PICO_FLOAT_PROPAGATE_NANS
// also check for infinites
cmp r1, #255
bne 3f
// infinite to nan
movs r1, #1
lsls r1, #22
orrs r0, r1
bx lr
3:
#endif
ldr r1, =0x40c90fdb // 2 * M_PI
push {lr}
bl remainderf
pop {r1}
mov lr, r1
b 2b
float_wrapper_section sinf
// note we don't use _f1 since we do an infinity/nan check for outside of range
wrapper_func sinf
// rom version only works for -128 < angle < 128
lsls r1, r0, #1
lsrs r1, #24
cmp r1, #127 + 7
bge 1f
2:
table_tail_call SF_TABLE_FSIN
1:
#if PICO_FLOAT_PROPAGATE_NANS
// also check for infinites
cmp r1, #255
bne 3f
// infinite to nan
movs r1, #1
lsls r1, #22
orrs r0, r1
bx lr
3:
#endif
ldr r1, =0x40c90fdb // 2 * M_PI
push {lr}
bl remainderf
pop {r1}
mov lr, r1
b 2b
float_wrapper_section sincosf
// note we don't use _f1 since we do an infinity/nan check for outside of range
wrapper_func sincosf
push {r1, r2, lr}
// rom version only works for -128 < angle < 128
lsls r3, r0, #1
lsrs r3, #24
cmp r3, #127 + 7
bge 3f
2:
ldr r3, =sf_table
ldr r3, [r3, #SF_TABLE_FSIN]
blx r3
pop {r2, r3}
str r0, [r2]
str r1, [r3]
pop {pc}
#if PICO_FLOAT_PROPAGATE_NANS
.align 2
pop {pc}
#endif
3:
#if PICO_FLOAT_PROPAGATE_NANS
// also check for infinites
cmp r3, #255
bne 4f
// infinite to nan
movs r3, #1
lsls r3, #22
orrs r0, r3
str r0, [r1]
str r0, [r2]
add sp, #12
bx lr
4:
#endif
ldr r1, =0x40c90fdb // 2 * M_PI
push {lr}
bl remainderf
pop {r1}
mov lr, r1
b 2b
float_wrapper_section tanf
// note we don't use _f1 since we do an infinity/nan check for outside of range
wrapper_func tanf
// rom version only works for -128 < angle < 128
lsls r1, r0, #1
lsrs r1, #24
cmp r1, #127 + 7
bge ftan_out_of_range
ftan_in_range:
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
// to support IRQ usage (or context switch) we must save/restore divider state around call if state is dirty
ldr r2, =(SIO_BASE)
ldr r3, [r2, #SIO_DIV_CSR_OFFSET]
lsrs r3, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
bcs ftan_save_state
#else
// to avoid worrying about IRQs (or context switches), simply disable interrupts around call
push {r4, lr}
mrs r4, PRIMASK
cpsid i
bl ftan_shim_call
msr PRIMASK, r4
pop {r4, pc}
#endif
ftan_shim_call:
table_tail_call SF_TABLE_FTAN
#if !PICO_DIVIDER_DISABLE_INTERRUPTS
ftan_save_state:
save_div_state_and_lr
bl ftan_shim_call
ldr r2, =(SIO_BASE)
restore_div_state_and_return
#endif
ftan_out_of_range:
#if PICO_FLOAT_PROPAGATE_NANS
// also check for infinites
cmp r1, #255
bne 3f
// infinite to nan
movs r1, #1
lsls r1, #22
orrs r0, r1
bx lr
3:
#endif
ldr r1, =0x40c90fdb // 2 * M_PI
push {lr}
bl remainderf
pop {r1}
mov lr, r1
b ftan_in_range
float_wrapper_section atan2f
wrapper_func_f2 atan2f
shimmable_table_tail_call SF_TABLE_FATAN2 fatan2_shim
float_wrapper_section expf
wrapper_func_f1 expf
table_tail_call SF_TABLE_FEXP
float_wrapper_section logf
wrapper_func_f1 logf
table_tail_call SF_TABLE_FLN
================================================
FILE: pico-sdk/src/rp2_common/pico_float/float_init_rom.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
#include "pico/bootrom.h"
#include "pico/bootrom/sf_table.h"
// NOTE THIS FUNCTION TABLE IS NOT PUBLIC OR NECESSARILY COMPLETE...
// IT IS ***NOT*** SAFE TO CALL THESE FUNCTION POINTERS FROM ARBITRARY CODE
uint32_t sf_table[SF_TABLE_V2_SIZE / 2];
void __attribute__((weak)) *sf_clz_func;
#if !(PICO_FLOAT_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED)
static __attribute__((noreturn)) void missing_float_func_shim(void) {
panic("");
}
#endif
void __aeabi_float_init(void) {
int rom_version = rp2040_rom_version();
void *rom_table = rom_data_lookup(rom_table_code('S', 'F'));
#if PICO_FLOAT_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
if (rom_version == 1) {
memcpy(&sf_table, rom_table, SF_TABLE_V1_SIZE);
extern void float_table_shim_on_use_helper(void);
// todo replace NDEBUG with a more exclusive assertion guard
#ifndef NDEBUG
if (*(uint16_t *)0x29ee != 0x0fc4 || // this is packx
*(uint16_t *)0x29c0 != 0x0dc2 || // this is upackx
*(uint16_t *)0x2b96 != 0xb5c0 || // this is cordic_vec
*(uint16_t *)0x2b18 != 0x2500 || // this is packretns
*(uint16_t *)0x2acc != 0xb510 || // this is float2fix
*(uint32_t *)0x2cfc != 0x6487ed51 // pi_q29
) {
panic("");
}
#endif
// this is a little tricky.. we only want to pull in a shim if the corresponding function
// is called. to that end we include a SVC instruction with the table offset as the call number
// followed by the shim function pointer inside the actual wrapper function. that way if the wrapper
// function is garbage collected, so is the shim function.
//
// float_table_shim_on_use_helper expects this SVC instruction in the calling code soon after the address
// pointed to by IP and patches the float_table entry with the real shim the first time the function is called.
for(uint i=SF_TABLE_V1_SIZE/4; i= 2) {
assert(*((uint8_t *)rom_table-2) * 4 >= SF_TABLE_V2_SIZE);
memcpy(&sf_table, rom_table, SF_TABLE_V2_SIZE);
}
sf_clz_func = rom_func_lookup(ROM_FUNC_CLZ32);
}
================================================
FILE: pico-sdk/src/rp2_common/pico_float/float_math.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/float.h"
// opened a separate issue https://github.com/raspberrypi/pico-sdk/issues/166 to deal with these warnings if at all
GCC_Pragma("GCC diagnostic push")
GCC_Pragma("GCC diagnostic ignored \"-Wconversion\"")
GCC_Pragma("GCC diagnostic ignored \"-Wsign-conversion\"")
typedef uint32_t ui32;
typedef int32_t i32;
#define FPINF ( HUGE_VALF)
#define FMINF (-HUGE_VALF)
#define NANF ((float)NAN)
#define PZERO (+0.0)
#define MZERO (-0.0)
#define PI 3.14159265358979323846
#define LOG2 0.69314718055994530941
// Unfortunately in double precision ln(10) is very close to half-way between to representable numbers
#define LOG10 2.30258509299404568401
#define LOG2E 1.44269504088896340737
#define LOG10E 0.43429448190325182765
#define ONETHIRD 0.33333333333333333333
#define PIf 3.14159265358979323846f
#define LOG2f 0.69314718055994530941f
#define LOG2Ef 1.44269504088896340737f
#define LOG10Ef 0.43429448190325182765f
#define ONETHIRDf 0.33333333333333333333f
#define FUNPACK(x,e,m) e=((x)>>23)&0xff,m=((x)&0x007fffff)|0x00800000
#define FUNPACKS(x,s,e,m) s=((x)>>31),FUNPACK((x),(e),(m))
typedef union {
float f;
ui32 ix;
} float_ui32;
static inline float ui322float(ui32 ix) {
float_ui32 tmp;
tmp.ix = ix;
return tmp.f;
}
static inline ui32 float2ui32(float f) {
float_ui32 tmp;
tmp.f = f;
return tmp.ix;
}
#if PICO_FLOAT_PROPAGATE_NANS
static inline bool fisnan(float x) {
ui32 ix=float2ui32(x);
return ix * 2 > 0xff000000u;
}
#define check_nan_f1(x) if (fisnan((x))) return (x)
#define check_nan_f2(x,y) if (fisnan((x))) return (x); else if (fisnan((y))) return (y);
#else
#define check_nan_f1(x) ((void)0)
#define check_nan_f2(x,y) ((void)0)
#endif
static inline int fgetsignexp(float x) {
ui32 ix=float2ui32(x);
return (ix>>23)&0x1ff;
}
static inline int fgetexp(float x) {
ui32 ix=float2ui32(x);
return (ix>>23)&0xff;
}
static inline float fldexp(float x,int de) {
ui32 ix=float2ui32(x),iy;
int e;
e=fgetexp(x);
if(e==0||e==0xff) return x;
e+=de;
if(e<=0) iy=ix&0x80000000; // signed zero for underflow
else if(e>=0xff) iy=(ix&0x80000000)|0x7f800000ULL; // signed infinity on overflow
else iy=ix+((ui32)de<<23);
return ui322float(iy);
}
float WRAPPER_FUNC(ldexpf)(float x, int de) {
check_nan_f1(x);
return fldexp(x, de);
}
static inline float fcopysign(float x,float y) {
ui32 ix=float2ui32(x),iy=float2ui32(y);
ix=((ix&0x7fffffff)|(iy&0x80000000));
return ui322float(ix);
}
float WRAPPER_FUNC(copysignf)(float x, float y) {
check_nan_f2(x,y);
return fcopysign(x, y);
}
static inline int fiszero(float x) { return fgetexp (x)==0; }
//static inline int fispzero(float x) { return fgetsignexp(x)==0; }
//static inline int fismzero(float x) { return fgetsignexp(x)==0x100; }
static inline int fisinf(float x) { return fgetexp (x)==0xff; }
static inline int fispinf(float x) { return fgetsignexp(x)==0xff; }
static inline int fisminf(float x) { return fgetsignexp(x)==0x1ff; }
static inline int fisint(float x) {
ui32 ix=float2ui32(x),m;
int e=fgetexp(x);
if(e==0) return 1; // 0 is an integer
e-=0x7f; // remove exponent bias
if(e<0) return 0; // |x|<1
e=23-e; // bit position in mantissa with significance 1
if(e<=0) return 1; // |x| large, so must be an integer
m=(1<>e)&1;
}
static inline int fisstrictneg(float x) {
ui32 ix=float2ui32(x);
if(fiszero(x)) return 0;
return ix>>31;
}
static inline int fisneg(float x) {
ui32 ix=float2ui32(x);
return ix>>31;
}
static inline float fneg(float x) {
ui32 ix=float2ui32(x);
ix^=0x80000000;
return ui322float(ix);
}
static inline int fispo2(float x) {
ui32 ix=float2ui32(x);
if(fiszero(x)) return 0;
if(fisinf(x)) return 0;
ix&=0x007fffff;
return ix==0;
}
static inline float fnan_or(float x) {
#if PICO_FLOAT_PROPAGATE_NANS
return NANF;
#else
return x;
#endif
}
float WRAPPER_FUNC(truncf)(float x) {
check_nan_f1(x);
ui32 ix=float2ui32(x),m;
int e=fgetexp(x);
e-=0x7f; // remove exponent bias
if(e<0) { // |x|<1
ix&=0x80000000;
return ui322float(ix);
}
e=23-e; // bit position in mantissa with significance 1
if(e<=0) return x; // |x| large, so must be an integer
m=(1<=4+0x7f) { // |x|>=16?
if(!fisneg(x)) return 1; // 1 << exp 2x; avoid generating infinities later
else return -1; // 1 >> exp 2x
}
u=expf(fldexp(x,1));
return (u-1.0f)/(u+1.0f);
}
float WRAPPER_FUNC(asinhf)(float x) {
check_nan_f1(x);
int e;
e=fgetexp(x);
if(e>=16+0x7f) { // |x|>=2^16?
if(!fisneg(x)) return logf( x )+LOG2f; // 1/x^2 << 1
else return fneg(logf(fneg(x))+LOG2f); // 1/x^2 << 1
}
if(x>0) return (float)log(sqrt((double)x*(double)x+1.0)+(double)x);
else return fneg((float)log(sqrt((double)x*(double)x+1.0)-(double)x));
}
float WRAPPER_FUNC(acoshf)(float x) {
check_nan_f1(x);
int e;
if(fisneg(x)) x=fneg(x);
e=fgetexp(x);
if(e>=16+0x7f) return logf(x)+LOG2f; // |x|>=2^16?
return (float)log(sqrt(((double)x+1.0)*((double)x-1.0))+(double)x);
}
float WRAPPER_FUNC(atanhf)(float x) {
check_nan_f1(x);
return fldexp(logf((1.0f+x)/(1.0f-x)),-1);
}
float WRAPPER_FUNC(exp2f)(float x) { check_nan_f1(x); return (float)exp((double)x*LOG2); }
float WRAPPER_FUNC(log2f)(float x) { check_nan_f1(x); return logf(x)*LOG2Ef; }
float WRAPPER_FUNC(exp10f)(float x) { check_nan_f1(x); return (float)exp((double)x*LOG10); }
float WRAPPER_FUNC(log10f)(float x) { check_nan_f1(x); return logf(x)*LOG10Ef; }
float WRAPPER_FUNC(expm1f)(float x) { check_nan_f1(x); return (float)(exp((double)x)-1); }
float WRAPPER_FUNC(log1pf)(float x) { check_nan_f1(x); return (float)(log(1+(double)x)); }
float WRAPPER_FUNC(fmaf)(float x,float y,float z) {
check_nan_f2(x,y);
check_nan_f1(z);
return (float)((double)x*(double)y+(double)z);
} // has double rounding so not exact
// general power, x>0
static inline float fpow_1(float x,float y) {
return (float)exp(log((double)x)*(double)y); // using double-precision intermediates for better accuracy
}
static float fpow_int2(float x,int y) {
float u;
if(y==1) return x;
u=fpow_int2(x,y/2);
u*=u;
if(y&1) u*=x;
return u;
}
// for the case where x not zero or infinity, y small and not zero
static inline float fpowint_1(float x,int y) {
if(y<0) x=1.0f/x,y=-y;
return fpow_int2(x,y);
}
// for the case where x not zero or infinity
static float fpowint_0(float x,int y) {
int e;
if(fisneg(x)) {
if(fisoddint(y)) return fneg(fpowint_0(fneg(x),y));
else return fpowint_0(fneg(x),y);
}
if(fispo2(x)) {
e=fgetexp(x)-0x7f;
if(y>=256) y= 255; // avoid overflow
if(y<-256) y=-256;
y*=e;
return fldexp(1,y);
}
if(y==0) return 1;
if(y>=-32&&y<=32) return fpowint_1(x,y);
return fpow_1(x,y);
}
float WRAPPER_FUNC(powintf)(float x,int y) {
GCC_Pragma("GCC diagnostic push")
GCC_Pragma("GCC diagnostic ignored \"-Wfloat-equal\"")
if(x==1.0f||y==0) return 1;
if(x==0.0f) {
if(y>0) {
if(y&1) return x;
else return 0;
}
if((y&1)) return fcopysign(FPINF,x);
return FPINF;
}
GCC_Pragma("GCC diagnostic pop")
check_nan_f1(x);
if(fispinf(x)) {
if(y<0) return 0;
else return FPINF;
}
if(fisminf(x)) {
if(y>0) {
if((y&1)) return FMINF;
else return FPINF;
}
if((y&1)) return MZERO;
else return PZERO;
}
return fpowint_0(x,y);
}
// for the case where y is guaranteed a finite integer, x not zero or infinity
static float fpow_0(float x,float y) {
int e,p;
if(fisneg(x)) {
if(fisoddint(y)) return fneg(fpow_0(fneg(x),y));
else return fpow_0(fneg(x),y);
}
p=(int)y;
if(fispo2(x)) {
e=fgetexp(x)-0x7f;
if(p>=256) p= 255; // avoid overflow
if(p<-256) p=-256;
p*=e;
return fldexp(1,p);
}
if(p==0) return 1;
if(p>=-32&&p<=32) return fpowint_1(x,p);
return fpow_1(x,y);
}
float WRAPPER_FUNC(powf)(float x,float y) {
GCC_Like_Pragma("GCC diagnostic push")
GCC_Like_Pragma("GCC diagnostic ignored \"-Wfloat-equal\"")
if(x==1.0f||fiszero(y)) return 1;
check_nan_f2(x,y);
if(x==-1.0f&&fisinf(y)) return 1;
GCC_Like_Pragma("GCC diagnostic pop")
if(fiszero(x)) {
if(!fisneg(y)) {
if(fisoddint(y)) return x;
else return 0;
}
if(fisoddint(y)) return fcopysign(FPINF,x);
return FPINF;
}
if(fispinf(x)) {
if(fisneg(y)) return 0;
else return FPINF;
}
if(fisminf(x)) {
if(!fisneg(y)) {
if(fisoddint(y)) return FMINF;
else return FPINF;
}
if(fisoddint(y)) return MZERO;
else return PZERO;
}
if(fispinf(y)) {
if(fgetexp(x)<0x7f) return PZERO;
else return FPINF;
}
if(fisminf(y)) {
if(fgetexp(x)<0x7f) return FPINF;
else return PZERO;
}
if(fisint(y)) return fpow_0(x,y);
if(fisneg(x)) return FPINF;
return fpow_1(x,y);
}
float WRAPPER_FUNC(hypotf)(float x,float y) {
check_nan_f2(x,y);
int ex,ey;
ex=fgetexp(x); ey=fgetexp(y);
if(ex>=0x7f+50||ey>=0x7f+50) { // overflow, or nearly so
x=fldexp(x,-70),y=fldexp(y,-70);
return fldexp(sqrtf(x*x+y*y), 70);
}
else if(ex<=0x7f-50&&ey<=0x7f-50) { // underflow, or nearly so
x=fldexp(x, 70),y=fldexp(y, 70);
return fldexp(sqrtf(x*x+y*y),-70);
}
return sqrtf(x*x+y*y);
}
float WRAPPER_FUNC(cbrtf)(float x) {
check_nan_f1(x);
int e;
if(fisneg(x)) return fneg(cbrtf(fneg(x)));
if(fiszero(x)) return fcopysign(PZERO,x);
e=fgetexp(x)-0x7f;
e=(e*0x5555+0x8000)>>16; // ~e/3, rounded
x=fldexp(x,-e*3);
x=expf(logf(x)*ONETHIRDf);
return fldexp(x,e);
}
// reduces mx*2^e modulo my, returning bottom bits of quotient at *pquo
// 2^23<=|mx|,my<2^24, e>=0; 0<=result0) {
r=0xffffffffU/(ui32)(my>>7); // reciprocal estimate Q16
}
while(e>0) {
s=e; if(s>12) s=12; // gain up to 12 bits on each iteration
q=(mx>>9)*r; // Q30
q=((q>>(29-s))+1)>>1; // Q(s), rounded
mx=(mx<=my) mx-=my,quo++; // when e==0 mx can be nearly as big as 2my
if(mx>=my) mx-=my,quo++;
if(mx<0) mx+=my,quo--;
if(mx<0) mx+=my,quo--;
if(pquo) *pquo=quo;
return mx;
}
float WRAPPER_FUNC(fmodf)(float x,float y) {
check_nan_f2(x,y);
ui32 ix=float2ui32(x),iy=float2ui32(y);
int sx,ex,ey;
i32 mx,my;
FUNPACKS(ix,sx,ex,mx);
FUNPACK(iy,ey,my);
if(ex==0xff) {
return fnan_or(FPINF);
}
if(ey==0) return FPINF;
if(ex==0) {
if(!fisneg(x)) return PZERO;
return MZERO;
}
if(ex|y|/2
mx-=my+my;
ey--;
q=1;
} else { // x<-|y|/2
mx=my+my-mx;
ey--;
q=-1;
}
}
else {
if(sx) mx=-mx;
mx=frem_0(mx,my,ex-ey,&q);
if(mx+mx>my || (mx+mx==my&&(q&1)) ) { // |x|>|y|/2, or equality and an odd quotient?
mx-=my;
q++;
}
}
if(sy) q=-q;
if(quo) *quo=q;
return fix2float(mx,0x7f-ey+23);
}
float WRAPPER_FUNC(dremf)(float x,float y) { check_nan_f2(x,y); return remquof(x,y,0); }
float WRAPPER_FUNC(remainderf)(float x,float y) { check_nan_f2(x,y); return remquof(x,y,0); }
GCC_Pragma("GCC diagnostic pop") // conversion
================================================
FILE: pico-sdk/src/rp2_common/pico_float/float_none.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/asm_helper.S"
#include "pico/bootrom/sf_table.h"
pico_default_asm_setup
wrapper_func __aeabi_fadd
wrapper_func __aeabi_fdiv
wrapper_func __aeabi_fmul
wrapper_func __aeabi_frsub
wrapper_func __aeabi_fsub
wrapper_func __aeabi_cfcmpeq
wrapper_func __aeabi_cfrcmple
wrapper_func __aeabi_cfcmple
wrapper_func __aeabi_fcmpeq
wrapper_func __aeabi_fcmplt
wrapper_func __aeabi_fcmple
wrapper_func __aeabi_fcmpge
wrapper_func __aeabi_fcmpgt
wrapper_func __aeabi_fcmpun
wrapper_func __aeabi_i2f
wrapper_func __aeabi_l2f
wrapper_func __aeabi_ui2f
wrapper_func __aeabi_ul2f
wrapper_func __aeabi_f2iz
wrapper_func __aeabi_f2lz
wrapper_func __aeabi_f2uiz
wrapper_func __aeabi_f2ulz
wrapper_func __aeabi_f2d
wrapper_func sqrtf
wrapper_func cosf
wrapper_func sinf
wrapper_func tanf
wrapper_func atan2f
wrapper_func expf
wrapper_func logf
wrapper_func ldexpf
wrapper_func copysignf
wrapper_func truncf
wrapper_func floorf
wrapper_func ceilf
wrapper_func roundf
wrapper_func sincosf
wrapper_func asinf
wrapper_func acosf
wrapper_func atanf
wrapper_func sinhf
wrapper_func coshf
wrapper_func tanhf
wrapper_func asinhf
wrapper_func acoshf
wrapper_func atanhf
wrapper_func exp2f
wrapper_func log2f
wrapper_func exp10f
wrapper_func log10f
wrapper_func powf
wrapper_func powintf
wrapper_func hypotf
wrapper_func cbrtf
wrapper_func fmodf
wrapper_func dremf
wrapper_func remainderf
wrapper_func remquof
wrapper_func expm1f
wrapper_func log1pf
wrapper_func fmaf
push {lr} // keep stack trace sane
ldr r0, =str
bl panic
str:
.asciz "float support is disabled"
================================================
FILE: pico-sdk/src/rp2_common/pico_float/float_v1_rom_shim.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/asm_helper.S"
#if PICO_FLOAT_SUPPORT_ROM_V1 && PICO_RP2040_B0_SUPPORTED
#ifndef PICO_FLOAT_IN_RAM
#define PICO_FLOAT_IN_RAM 0
#endif
pico_default_asm_setup
.macro float_section name
// todo separate flag for shims?
#if PICO_FLOAT_IN_RAM
.section RAM_SECTION_NAME(\name), "ax"
#else
.section SECTION_NAME(\name), "ax"
#endif
.endm
float_section float_table_shim_on_use_helper
regular_func float_table_shim_on_use_helper
push {r0-r2, lr}
mov r0, ip
#ifndef NDEBUG
// sanity check to make sure we weren't called by non (shimmable_) table_tail_call macro
cmp r0, #0
bne 1f
bkpt #0
#endif
1:
ldrh r1, [r0]
lsrs r2, r1, #8
adds r0, #2
cmp r2, #0xdf
bne 1b
uxtb r1, r1 // r1 holds table offset
lsrs r2, r0, #2
bcc 1f
// unaligned
ldrh r2, [r0, #0]
ldrh r0, [r0, #2]
lsls r0, #16
orrs r0, r2
b 2f
1:
ldr r0, [r0]
2:
ldr r2, =sf_table
str r0, [r2, r1]
str r0, [sp, #12]
pop {r0-r2, pc}
float_section 642float_shims
@ convert uint64 to float, rounding
regular_func uint642float_shim
movs r2,#0 @ fall through
@ convert unsigned 64-bit fix to float, rounding; number of r0:r1 bits after point in r2
regular_func ufix642float_shim
push {r4,r5,r14}
cmp r1,#0
bpl 3f @ positive? we can use signed code
lsls r5,r1,#31 @ contribution to sticky bits
orrs r5,r0
lsrs r0,r1,#1
subs r2,#1
b 4f
@ convert int64 to float, rounding
regular_func int642float_shim
movs r2,#0 @ fall through
@ convert signed 64-bit fix to float, rounding; number of r0:r1 bits after point in r2
regular_func fix642float_shim
push {r4,r5,r14}
3:
movs r5,r0
orrs r5,r1
beq ret_pop45 @ zero? return +0
asrs r5,r1,#31 @ sign bits
2:
asrs r4,r1,#24 @ try shifting 7 bits at a time
cmp r4,r5
bne 1f @ next shift will overflow?
lsls r1,#7
lsrs r4,r0,#25
orrs r1,r4
lsls r0,#7
adds r2,#7
b 2b
1:
movs r5,r0
movs r0,r1
4:
negs r2,r2
adds r2,#32+29
// bl packx
ldr r1, =0x29ef // packx
blx r1
ret_pop45:
pop {r4,r5,r15}
float_section fatan2_shim
regular_func fatan2_shim
push {r4,r5,r14}
ldr r4, =0x29c1 // unpackx
mov ip, r4
@ unpack arguments and shift one down to have common exponent
blx ip
mov r4,r0
mov r0,r1
mov r1,r4
mov r4,r2
mov r2,r3
mov r3,r4
blx ip
lsls r0,r0,#5 @ Q28
lsls r1,r1,#5 @ Q28
adds r4,r2,r3 @ this is -760 if both arguments are 0 and at least -380-126=-506 otherwise
asrs r4,#9
adds r4,#1
bmi 2f @ force y to 0 proper, so result will be zero
subs r4,r2,r3 @ calculate shift
bge 1f @ ex>=ey?
negs r4,r4 @ make shift positive
asrs r0,r4
cmp r4,#28
blo 3f
asrs r0,#31
b 3f
1:
asrs r1,r4
cmp r4,#28
blo 3f
2:
@ here |x|>>|y| or both x and y are ±0
cmp r0,#0
bge 4f @ x positive, return signed 0
ldr r3, =0x2cfc @ &pi_q29, circular coefficients
ldr r0,[r3] @ x negative, return +/- pi
asrs r1,#31
eors r0,r1
b 7f
4:
asrs r0,r1,#31
b 7f
3:
movs r2,#0 @ initial angle
ldr r3, =0x2cfc @ &pi_q29, circular coefficients
cmp r0,#0 @ x negative
bge 5f
negs r0,r0 @ rotate to 1st/4th quadrants
negs r1,r1
ldr r2,[r3] @ pi Q29
5:
movs r4,#1 @ m=1
ldr r5, =0x2b97 @ cordic_vec
blx r5 @ also produces magnitude (with scaling factor 1.646760119), which is discarded
mov r0,r2 @ result here is -pi/2..3pi/2 Q29
@ asrs r2,#29
@ subs r0,r2
ldr r3, =0x2cfc @ &pi_q29, circular coefficients
ldr r2,[r3] @ pi Q29
adds r4,r0,r2 @ attempt to fix -3pi/2..-pi case
bcs 6f @ -pi/2..0? leave result as is
subs r4,r0,r2 @ pi: take off 2pi
6:
subs r0,#1 @ fiddle factor so atan2(0,1)==0
7:
movs r2,#0 @ exponent for pack
ldr r3, =0x2b19
bx r3
float_section float232_shims
regular_func float2int_shim
movs r1,#0 @ fall through
regular_func float2fix_shim
// check for -0 or -denormal upfront
asrs r2, r0, #23
adds r2, #128
adds r2, #128
beq 1f
// call original
ldr r2, =0x2acd
bx r2
1:
movs r0, #0
bx lr
float_section float264_shims
regular_func float2int64_shim
movs r1,#0 @ and fall through
regular_func float2fix64_shim
push {r14}
bl f2fix
b d2f64_a
regular_func float2uint64_shim
movs r1,#0 @ and fall through
regular_func float2ufix64_shim
asrs r3,r0,#23 @ negative? return 0
bmi ret_dzero
@ and fall through
@ convert float in r0 to signed fixed point in r0:r1:r3, r1 places after point, rounding towards -Inf
@ result clamped so that r3 can only be 0 or -1
@ trashes r12
.thumb_func
f2fix:
push {r4,r14}
mov r12,r1
asrs r3,r0,#31
lsls r0,#1
lsrs r2,r0,#24
beq 1f @ zero?
cmp r2,#0xff @ Inf?
beq 2f
subs r1,r2,#1
subs r2,#0x7f @ remove exponent bias
lsls r1,#24
subs r0,r1 @ insert implied 1
eors r0,r3
subs r0,r3 @ top two's complement
asrs r1,r0,#4 @ convert to double format
lsls r0,#28
ldr r4, =d2fix_a
bx r4
1:
movs r0,#0
movs r1,r0
movs r3,r0
pop {r4,r15}
2:
mvns r0,r3 @ return max/min value
mvns r1,r3
pop {r4,r15}
ret_dzero:
movs r0,#0
movs r1,#0
bx r14
float_section d2fix_a_float
.weak d2fix_a // weak because it exists in float shims too
.thumb_func
d2fix_a:
@ here
@ r0:r1 two's complement mantissa
@ r2 unbaised exponent
@ r3 mantissa sign extension bits
add r2,r12 @ exponent plus offset for required binary point position
subs r2,#52 @ required shift
bmi 1f @ shift down?
@ here a shift up by r2 places
cmp r2,#12 @ will clamp?
bge 2f
movs r4,r0
lsls r1,r2
lsls r0,r2
negs r2,r2
adds r2,#32 @ complementary shift
lsrs r4,r2
orrs r1,r4
pop {r4,r15}
2:
mvns r0,r3
mvns r1,r3 @ overflow: clamp to extreme fixed-point values
pop {r4,r15}
1:
@ here a shift down by -r2 places
adds r2,#32
bmi 1f @ long shift?
mov r4,r1
lsls r4,r2
negs r2,r2
adds r2,#32 @ complementary shift
asrs r1,r2
lsrs r0,r2
orrs r0,r4
pop {r4,r15}
1:
@ here a long shift down
movs r0,r1
asrs r1,#31 @ shift down 32 places
adds r2,#32
bmi 1f @ very long shift?
negs r2,r2
adds r2,#32
asrs r0,r2
pop {r4,r15}
1:
movs r0,r3 @ result very near zero: use sign extension bits
movs r1,r3
pop {r4,r15}
d2f64_a:
asrs r2,r1,#31
cmp r2,r3
bne 1f @ sign extension bits fail to match sign of result?
pop {r15}
1:
mvns r0,r3
movs r1,#1
lsls r1,#31
eors r1,r1,r0 @ generate extreme fixed-point values
pop {r15}
float_section float2double_shim
regular_func float2double_shim
lsrs r3,r0,#31 @ sign bit
lsls r3,#31
lsls r1,r0,#1
lsrs r2,r1,#24 @ exponent
beq 1f @ zero?
cmp r2,#0xff @ Inf?
beq 2f
lsrs r1,#4 @ exponent and top 20 bits of mantissa
ldr r2,=(0x3ff-0x7f)<<20 @ difference in exponent offsets
adds r1,r2
orrs r1,r3
lsls r0,#29 @ bottom 3 bits of mantissa
bx r14
1:
movs r1,r3 @ return signed zero
3:
movs r0,#0
bx r14
2:
ldr r1,=0x7ff00000 @ return signed infinity
adds r1,r3
b 3b
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_float/include/pico/float.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_FLOAT_H
#define _PICO_FLOAT_H
#include
#include
#include "pico.h"
#include "pico/bootrom/sf_table.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file float.h
* \defgroup pico_float pico_float
*
* Optimized single-precision floating point functions
*
* (Replacement) optimized implementations are provided of the following compiler built-ins
* and math library functions:
*
* - __aeabi_fadd, __aeabi_fdiv, __aeabi_fmul, __aeabi_frsub, __aeabi_fsub, __aeabi_cfcmpeq, __aeabi_cfrcmple, __aeabi_cfcmple, __aeabi_fcmpeq, __aeabi_fcmplt, __aeabi_fcmple, __aeabi_fcmpge, __aeabi_fcmpgt, __aeabi_fcmpun, __aeabi_i2f, __aeabi_l2f, __aeabi_ui2f, __aeabi_ul2f, __aeabi_f2iz, __aeabi_f2lz, __aeabi_f2uiz, __aeabi_f2ulz, __aeabi_f2d, sqrtf, cosf, sinf, tanf, atan2f, expf, logf
* - ldexpf, copysignf, truncf, floorf, ceilf, roundf, asinf, acosf, atanf, sinhf, coshf, tanhf, asinhf, acoshf, atanhf, exp2f, log2f, exp10f, log10f, powf, hypotf, cbrtf, fmodf, dremf, remainderf, remquof, expm1f, log1pf, fmaf
* - powintf, sincosf (GNU extensions)
*
* The following additional optimized functions are also provided:
*
* - fix2float, ufix2float, fix642float, ufix642float, float2fix, float2ufix, float2fix64, float2ufix64, float2int, float2int64, float2int_z, float2int64_z
*/
float fix2float(int32_t m, int e);
float ufix2float(uint32_t m, int e);
float fix642float(int64_t m, int e);
float ufix642float(uint64_t m, int e);
// These methods round towards -Infinity.
int32_t float2fix(float f, int e);
uint32_t float2ufix(float f, int e);
int64_t float2fix64(float f, int e);
uint64_t float2ufix64(float f, int e);
int32_t float2int(float f);
int64_t float2int64(float f);
// These methods round towards 0.
int32_t float2int_z(float f);
int64_t float2int64_z(float f);
float exp10f(float x);
void sincosf(float x, float *sinx, float *cosx);
float powintf(float x, int y);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_i2c_slave/CMakeLists.txt
================================================
if (NOT TARGET pico_i2c_slave)
pico_add_library(pico_i2c_slave)
target_sources(pico_i2c_slave INTERFACE
${CMAKE_CURRENT_LIST_DIR}/i2c_slave.c)
target_include_directories(pico_i2c_slave_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
pico_mirrored_target_link_libraries(pico_i2c_slave INTERFACE hardware_i2c hardware_irq)
endif()
================================================
FILE: pico-sdk/src/rp2_common/pico_i2c_slave/i2c_slave.c
================================================
/*
* Copyright (c) 2021 Valentin Milea
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/i2c_slave.h"
#include "hardware/irq.h"
typedef struct i2c_slave {
i2c_slave_handler_t handler;
bool transfer_in_progress;
} i2c_slave_t;
static i2c_slave_t i2c_slaves[2];
static void __isr __not_in_flash_func(i2c_slave_irq_handler)(void) {
uint i2c_index = __get_current_exception() - VTABLE_FIRST_IRQ - I2C0_IRQ;
i2c_slave_t *slave = &i2c_slaves[i2c_index];
i2c_inst_t *i2c = i2c_get_instance(i2c_index);
i2c_hw_t *hw = i2c_get_hw(i2c);
uint32_t intr_stat = hw->intr_stat;
if (intr_stat == 0) {
return;
}
bool do_finish_transfer = false;
if (intr_stat & I2C_IC_INTR_STAT_R_TX_ABRT_BITS) {
hw->clr_tx_abrt;
do_finish_transfer = true;
}
if (intr_stat & I2C_IC_INTR_STAT_R_START_DET_BITS) {
hw->clr_start_det;
do_finish_transfer = true;
}
if (intr_stat & I2C_IC_INTR_STAT_R_STOP_DET_BITS) {
hw->clr_stop_det;
do_finish_transfer = true;
}
if (do_finish_transfer && slave->transfer_in_progress) {
slave->handler(i2c, I2C_SLAVE_FINISH);
slave->transfer_in_progress = false;
}
if (intr_stat & I2C_IC_INTR_STAT_R_RX_FULL_BITS) {
slave->transfer_in_progress = true;
slave->handler(i2c, I2C_SLAVE_RECEIVE);
}
if (intr_stat & I2C_IC_INTR_STAT_R_RD_REQ_BITS) {
hw->clr_rd_req;
slave->transfer_in_progress = true;
slave->handler(i2c, I2C_SLAVE_REQUEST);
}
}
void i2c_slave_init(i2c_inst_t *i2c, uint8_t address, i2c_slave_handler_t handler) {
assert(i2c == i2c0 || i2c == i2c1);
assert(handler != NULL);
uint i2c_index = i2c_hw_index(i2c);
i2c_slave_t *slave = &i2c_slaves[i2c_index];
slave->handler = handler;
// Note: The I2C slave does clock stretching implicitly after a RD_REQ, while the Tx FIFO is empty.
// There is also an option to enable clock stretching while the Rx FIFO is full, but we leave it
// disabled since the Rx FIFO should never fill up (unless slave->handler() is way too slow).
i2c_set_slave_mode(i2c, true, address);
i2c_hw_t *hw = i2c_get_hw(i2c);
// unmask necessary interrupts
hw->intr_mask =
I2C_IC_INTR_MASK_M_RX_FULL_BITS | I2C_IC_INTR_MASK_M_RD_REQ_BITS | I2C_IC_RAW_INTR_STAT_TX_ABRT_BITS |
I2C_IC_INTR_MASK_M_STOP_DET_BITS | I2C_IC_INTR_MASK_M_START_DET_BITS;
// enable interrupt for current core
uint num = I2C0_IRQ + i2c_index;
irq_set_exclusive_handler(num, i2c_slave_irq_handler);
irq_set_enabled(num, true);
}
void i2c_slave_deinit(i2c_inst_t *i2c) {
assert(i2c == i2c0 || i2c == i2c1);
uint i2c_index = i2c_hw_index(i2c);
i2c_slave_t *slave = &i2c_slaves[i2c_index];
assert(slave->handler); // should be called after i2c_slave_init()
slave->handler = NULL;
slave->transfer_in_progress = false;
uint num = I2C0_IRQ + i2c_index;
irq_set_enabled(num, false);
irq_remove_handler(num, i2c_slave_irq_handler);
i2c_hw_t *hw = i2c_get_hw(i2c);
hw->intr_mask = I2C_IC_INTR_MASK_RESET;
i2c_set_slave_mode(i2c, false, 0);
}
================================================
FILE: pico-sdk/src/rp2_common/pico_i2c_slave/include/pico/i2c_slave.h
================================================
/*
* Copyright (c) 2021 Valentin Milea
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_I2C_SLAVE_H
#define _PICO_I2C_SLAVE_H
#include "hardware/i2c.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file pico/i2c_slave.h
* \defgroup pico_i2c_slave pico_i2c_slave
*
* Functions providing an interrupt driven I2C slave interface.
*
* This I2C slave helper library configures slave mode and hooks the relevant I2C IRQ
* so that a user supplied handler is called with enumerated I2C events.
*
* An example application \c slave_mem_i2c, which makes use of this library, can be found in
* pico_examples.
*/
/**
* \brief I2C slave event types.
* \ingroup pico_i2c_slave
*/
typedef enum i2c_slave_event_t
{
I2C_SLAVE_RECEIVE, /**< Data from master is available for reading. Slave must read from Rx FIFO. */
I2C_SLAVE_REQUEST, /**< Master is requesting data. Slave must write into Tx FIFO. */
I2C_SLAVE_FINISH, /**< Master has sent a Stop or Restart signal. Slave may prepare for the next transfer. */
} i2c_slave_event_t;
/**
* \brief I2C slave event handler
* \ingroup pico_i2c_slave
*
* The event handler will run from the I2C ISR, so it should return quickly (under 25 us at 400 kb/s).
* Avoid blocking inside the handler and split large data transfers across multiple calls for best results.
* When sending data to master, up to \ref i2c_get_write_available() bytes can be written without blocking.
* When receiving data from master, up to \ref i2c_get_read_available() bytes can be read without blocking.
*
* \param i2c Either \ref i2c0 or \ref i2c1
* \param event Event type.
*/
typedef void (*i2c_slave_handler_t)(i2c_inst_t *i2c, i2c_slave_event_t event);
/**
* \brief Configure an I2C instance for slave mode.
* \ingroup pico_i2c_slave
* \param i2c I2C instance.
* \param address 7-bit slave address.
* \param handler Callback for events from I2C master. It will run from the I2C ISR, on the CPU core
* where the slave was initialised.
*/
void i2c_slave_init(i2c_inst_t *i2c, uint8_t address, i2c_slave_handler_t handler);
/**
* \brief Restore an I2C instance to master mode.
* \ingroup pico_i2c_slave
* \param i2c Either \ref i2c0 or \ref i2c1
*/
void i2c_slave_deinit(i2c_inst_t *i2c);
#ifdef __cplusplus
}
#endif
#endif // _PICO_I2C_SLAVE_H_
================================================
FILE: pico-sdk/src/rp2_common/pico_int64_ops/CMakeLists.txt
================================================
if (NOT TARGET pico_int64_ops)
#shims for ROM functions for -lgcc functions (listed below)
pico_add_library(pico_int64_ops)
# no custom implementation; falls thru to compiler
pico_add_library(pico_int64_ops_compiler)
# add alias "default" which is just pico.
add_library(pico_int64_ops_default INTERFACE)
target_link_libraries(pico_int64_ops_default INTERFACE pico_int64_ops_pico)
set(PICO_DEFAULT_INT64_OPS_IMPL pico_int64_ops_default)
target_link_libraries(pico_int64_ops INTERFACE
$>,$,${PICO_DEFAULT_INT64_OPS_IMPL}>)
pico_add_library(pico_int64_ops_pico)
target_include_directories(pico_int64_ops_pico INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
pico_mirrored_target_link_libraries(pico_int64_ops_pico INTERFACE pico_base)
target_sources(pico_int64_ops_pico INTERFACE
${CMAKE_CURRENT_LIST_DIR}/pico_int64_ops_aeabi.S
)
pico_wrap_function(pico_int64_ops_pico __aeabi_lmul)
macro(pico_set_int64_ops_implementation TARGET IMPL)
get_target_property(target_type ${TARGET} TYPE)
if ("EXECUTABLE" STREQUAL "${target_type}")
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_INT64_OPS_IMPL "pico_int64_ops_${IMPL}")
else()
message(FATAL_ERROR "int64_ops implementation must be set on executable not library")
endif()
endmacro()
endif()
================================================
FILE: pico-sdk/src/rp2_common/pico_int64_ops/include/pico/int64_ops.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_INT64_OPS_H
#define _PICO_INT64_OPS_H
#include "pico/types.h"
/** \file int64_ops.h
* \defgroup pico_int64_ops pico_int64_ops
*
* Optimized replacement implementations of the compiler built-in 64 bit multiplication
*
* This library does not provide any additional functions
*/
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_int64_ops/pico_int64_ops_aeabi.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/asm_helper.S"
pico_default_asm_setup
.macro int64_section name
#if PICO_INT64_OPS_IN_RAM
.section RAM_SECTION_NAME(\name), "ax"
#else
.section SECTION_NAME(\name), "ax"
#endif
.endm
int64_section __aeabi_lmul
wrapper_func __aeabi_lmul
muls r1, r2
muls r3, r0
adds r1, r3
mov r12, r1
lsrs r1, r2, #16
uxth r3, r0
muls r3, r1
push {r4}
lsrs r4, r0, #16
muls r1, r4
uxth r2, r2
uxth r0, r0
muls r0, r2
muls r2, r4
lsls r4, r3, #16
lsrs r3, #16
adds r0, r4
pop {r4}
adcs r1, r3
lsls r3, r2, #16
lsrs r2, #16
adds r0, r3
adcs r1, r2
add r1, r12
bx lr
================================================
FILE: pico-sdk/src/rp2_common/pico_malloc/CMakeLists.txt
================================================
if (NOT TARGET pico_malloc)
#shims for ROM functions for -lgcc functions (listed below)
pico_add_library(pico_malloc)
target_sources(pico_malloc INTERFACE
${CMAKE_CURRENT_LIST_DIR}/pico_malloc.c
)
target_include_directories(pico_malloc_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
pico_wrap_function(pico_malloc malloc)
pico_wrap_function(pico_malloc calloc)
pico_wrap_function(pico_malloc realloc)
pico_wrap_function(pico_malloc free)
target_link_libraries(pico_malloc INTERFACE pico_sync)
endif()
================================================
FILE: pico-sdk/src/rp2_common/pico_malloc/include/pico/malloc.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_MALLOC_H
#define _PICO_MALLOC_H
/** \file malloc.h
* \defgroup pico_malloc pico_malloc
*
* Multi-core safety for malloc, calloc and free
*
* This library does not provide any additional functions
*/
// PICO_CONFIG: PICO_USE_MALLOC_MUTEX, Whether to protect malloc etc with a mutex, type=bool, default=1 with pico_multicore, 0 otherwise, group=pico_malloc
#if LIB_PICO_MULTICORE && !defined(PICO_USE_MALLOC_MUTEX)
#define PICO_USE_MALLOC_MUTEX 1
#endif
// PICO_CONFIG: PICO_MALLOC_PANIC, Enable/disable panic when an allocation failure occurs, type=bool, default=1, group=pico_malloc
#ifndef PICO_MALLOC_PANIC
#define PICO_MALLOC_PANIC 1
#endif
// PICO_CONFIG: PICO_DEBUG_MALLOC, Enable/disable debug printf from malloc, type=bool, default=0, group=pico_malloc
#ifndef PICO_DEBUG_MALLOC
#define PICO_DEBUG_MALLOC 0
#endif
// PICO_CONFIG: PICO_DEBUG_MALLOC_LOW_WATER, Define the lower bound for allocation addresses to be printed by PICO_DEBUG_MALLOC, min=0, default=0, group=pico_malloc
#ifndef PICO_DEBUG_MALLOC_LOW_WATER
#define PICO_DEBUG_MALLOC_LOW_WATER 0
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_malloc/pico_malloc.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
#include "pico.h"
#include "pico/malloc.h"
#if PICO_USE_MALLOC_MUTEX
#include "pico/mutex.h"
auto_init_mutex(malloc_mutex);
#endif
extern void *REAL_FUNC(malloc)(size_t size);
extern void *REAL_FUNC(calloc)(size_t count, size_t size);
extern void *REAL_FUNC(realloc)(void *mem, size_t size);
extern void REAL_FUNC(free)(void *mem);
extern char __StackLimit; /* Set by linker. */
static inline void check_alloc(__unused void *mem, __unused uint size) {
#if PICO_MALLOC_PANIC
if (!mem || (((char *)mem) + size) > &__StackLimit) {
panic("Out of memory");
}
#endif
}
void *WRAPPER_FUNC(malloc)(size_t size) {
#if PICO_USE_MALLOC_MUTEX
mutex_enter_blocking(&malloc_mutex);
#endif
void *rc = REAL_FUNC(malloc)(size);
#if PICO_USE_MALLOC_MUTEX
mutex_exit(&malloc_mutex);
#endif
#if PICO_DEBUG_MALLOC
if (!rc || ((uint8_t *)rc) + size > (uint8_t*)PICO_DEBUG_MALLOC_LOW_WATER) {
printf("malloc %d %p->%p\n", (uint) size, rc, ((uint8_t *) rc) + size);
}
#endif
check_alloc(rc, size);
return rc;
}
void *WRAPPER_FUNC(calloc)(size_t count, size_t size) {
#if PICO_USE_MALLOC_MUTEX
mutex_enter_blocking(&malloc_mutex);
#endif
void *rc = REAL_FUNC(calloc)(count, size);
#if PICO_USE_MALLOC_MUTEX
mutex_exit(&malloc_mutex);
#endif
#if PICO_DEBUG_MALLOC
if (!rc || ((uint8_t *)rc) + size > (uint8_t*)PICO_DEBUG_MALLOC_LOW_WATER) {
printf("calloc %d %p->%p\n", (uint) (count * size), rc, ((uint8_t *) rc) + size);
}
#endif
check_alloc(rc, size);
return rc;
}
void *WRAPPER_FUNC(realloc)(void *mem, size_t size) {
#if PICO_USE_MALLOC_MUTEX
mutex_enter_blocking(&malloc_mutex);
#endif
void *rc = REAL_FUNC(realloc)(mem, size);
#if PICO_USE_MALLOC_MUTEX
mutex_exit(&malloc_mutex);
#endif
#if PICO_DEBUG_MALLOC
if (!rc || ((uint8_t *)rc) + size > (uint8_t*)PICO_DEBUG_MALLOC_LOW_WATER) {
printf("realloc %p %d->%p\n", mem, (uint) size, rc);
}
#endif
check_alloc(rc, size);
return rc;
}
void WRAPPER_FUNC(free)(void *mem) {
#if PICO_USE_MALLOC_MUTEX
mutex_enter_blocking(&malloc_mutex);
#endif
REAL_FUNC(free)(mem);
#if PICO_USE_MALLOC_MUTEX
mutex_exit(&malloc_mutex);
#endif
}
================================================
FILE: pico-sdk/src/rp2_common/pico_mem_ops/CMakeLists.txt
================================================
if (NOT TARGET pico_mem_ops)
#shims for ROM functions for -lgcc functions (listed below)
pico_add_library(pico_mem_ops)
# no custom implementation; falls thru to compiler
pico_add_library(pico_mem_ops_compiler)
# add alias "default" which is just pico.
add_library(pico_mem_ops_default INTERFACE)
target_link_libraries(pico_mem_ops_default INTERFACE pico_mem_ops_pico)
set(PICO_DEFAULT_MEM_OPS_IMPL pico_mem_ops_default)
pico_add_library(pico_mem_ops_pico)
target_link_libraries(pico_mem_ops INTERFACE
$>,$,${PICO_DEFAULT_MEM_OPS_IMPL}>)
target_sources(pico_mem_ops_pico INTERFACE
${CMAKE_CURRENT_LIST_DIR}/mem_ops_aeabi.S
)
target_include_directories(pico_mem_ops_pico_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
pico_mirrored_target_link_libraries(pico_mem_ops_pico INTERFACE pico_base)
target_link_libraries(pico_mem_ops INTERFACE pico_bootrom)
pico_wrap_function(pico_mem_ops_pico memcpy)
pico_wrap_function(pico_mem_ops_pico memset)
pico_wrap_function(pico_mem_ops_pico __aeabi_memcpy)
pico_wrap_function(pico_mem_ops_pico __aeabi_memset)
pico_wrap_function(pico_mem_ops_pico __aeabi_memcpy4)
pico_wrap_function(pico_mem_ops_pico __aeabi_memset4)
pico_wrap_function(pico_mem_ops_pico __aeabi_memcpy8)
pico_wrap_function(pico_mem_ops_pico __aeabi_memset8)
macro(pico_set_mem_ops_implementation TARGET IMPL)
get_target_property(target_type ${TARGET} TYPE)
if ("EXECUTABLE" STREQUAL "${target_type}")
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_MEM_OPS_IMPL "pico_mem_ops_${IMPL}")
else()
message(FATAL_ERROR "mem_ops implementation must be set on executable not library")
endif()
endmacro()
endif()
================================================
FILE: pico-sdk/src/rp2_common/pico_mem_ops/include/pico/mem_ops.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_MEM_OPS_H
#define _PICO_MEM_OPS_H
#include "pico/types.h"
/** \file mem_ops.h
* \defgroup pico_mem_ops pico_mem_ops
*
* Provides optimized replacement implementations of the compiler built-in memcpy, memset and related functions:
*
* - memset, memcpy
* - __aeabi_memset, __aeabi_memset4, __aeabi_memset8, __aeabi_memcpy, __aeabi_memcpy4, __aeabi_memcpy8
*
* This library does not provide any additional functions
*/
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_mem_ops/mem_ops.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/mem_ops.h"
================================================
FILE: pico-sdk/src/rp2_common/pico_mem_ops/mem_ops_aeabi.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/asm_helper.S"
#include "pico/bootrom.h"
pico_default_asm_setup
__pre_init __aeabi_mem_init, 00001
.macro mem_section name
#if PICO_MEM_IN_RAM
.section RAM_SECTION_NAME(\name), "ax"
#else
.section SECTION_NAME(\name), "ax"
#endif
.endm
.equ MEMSET, 0
.equ MEMCPY, 4
.equ MEMSET4, 8
.equ MEMCPY4, 12
.equ MEM_FUNC_COUNT, 4
# NOTE: All code sections are placed in RAM (at the expense of some veneer cost for calls from flash) because
# otherwise code using basic c division operators will require XIP flash access.
.section .data.aeabi_mem_funcs
.global aeabi_mem_funcs, aeabi_mem_funcs_end
.align 2
aeabi_mem_funcs:
.word ROM_FUNC_MEMSET
.word ROM_FUNC_MEMCPY
.word ROM_FUNC_MEMSET4
.word ROM_FUNC_MEMCPY44
aeabi_mem_funcs_end:
.section .text
regular_func __aeabi_mem_init
ldr r0, =aeabi_mem_funcs
movs r1, #MEM_FUNC_COUNT
ldr r3, =rom_funcs_lookup
bx r3
# lump them both together because likely both to be used, in which case doing so saves 1 word
# and it only costs 1 word if not
// Note from Run-time ABI for the ARM architecture 4.3.4:
// If there is an attached device with efficient memory copying or clearing operations
// (such as a DMA engine), its device supplement specifies whether it may be used in
// implementations of these functions and what effect such use has on the device’s state.
mem_section aeabi_memset_memcpy
wrapper_func __aeabi_memset
// 2nd/3rd args are reversed
eors r2, r1
eors r1, r2
eors r2, r1
ldr r3, =aeabi_mem_funcs
ldr r3, [r3, #MEMSET]
bx r3
wrapper_func __aeabi_memset4
wrapper_func __aeabi_memset8
// 2nd/3rd args are reversed
eors r2, r1
eors r1, r2
eors r2, r1
ldr r3, =aeabi_mem_funcs
ldr r3, [r3, #MEMSET4]
bx r3
wrapper_func __aeabi_memcpy4
wrapper_func __aeabi_memcpy8
ldr r3, =aeabi_mem_funcs
ldr r3, [r3, #MEMCPY4]
bx r3
mem_section memset
wrapper_func memset
ldr r3, =aeabi_mem_funcs
ldr r3, [r3, #MEMSET]
bx r3
mem_section memcpy
wrapper_func __aeabi_memcpy
wrapper_func memcpy
ldr r3, =aeabi_mem_funcs
ldr r3, [r3, #MEMCPY]
bx r3
================================================
FILE: pico-sdk/src/rp2_common/pico_multicore/CMakeLists.txt
================================================
if (NOT TARGET pico_multicore)
pico_add_library(pico_multicore)
target_include_directories(pico_multicore_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_sources(pico_multicore INTERFACE
${CMAKE_CURRENT_LIST_DIR}/multicore.c)
pico_mirrored_target_link_libraries(pico_multicore INTERFACE
pico_sync
hardware_irq)
endif()
================================================
FILE: pico-sdk/src/rp2_common/pico_multicore/include/pico/multicore.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_MULTICORE_H
#define _PICO_MULTICORE_H
#include "pico/types.h"
#include "pico/sync.h"
#include "hardware/structs/sio.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file multicore.h
* \defgroup pico_multicore pico_multicore
* Adds support for running code on the second processor core (core 1)
*
* \subsection multicore_example Example
* \addtogroup pico_multicore
* \include multicore.c
*/
// PICO_CONFIG: PICO_CORE1_STACK_SIZE, Stack size for core 1, min=0x100, max=0x10000, default=PICO_STACK_SIZE (0x800), group=pico_multicore
#ifndef PICO_CORE1_STACK_SIZE
#ifdef PICO_STACK_SIZE
#define PICO_CORE1_STACK_SIZE PICO_STACK_SIZE
#else
#define PICO_CORE1_STACK_SIZE 0x800
#endif
#endif
/*! \brief Reset core 1
* \ingroup pico_multicore
*
* This function can be used to reset core 1 into its initial state (ready for launching code against via \ref multicore_launch_core1 and similar methods)
*
* \note this function should only be called from core 0
*/
void multicore_reset_core1(void);
/*! \brief Run code on core 1
* \ingroup pico_multicore
*
* Wake up (a previously reset) core 1 and enter the given function on core 1 using the default core 1 stack (below core 0 stack).
*
* core 1 must previously have been reset either as a result of a system reset or by calling \ref multicore_reset_core1
*
* core 1 will use the same vector table as core 0
*
* \param entry Function entry point
* \see multicore_reset_core1
*/
void multicore_launch_core1(void (*entry)(void));
/*! \brief Launch code on core 1 with stack
* \ingroup pico_multicore
*
* Wake up (a previously reset) core 1 and enter the given function on core 1 using the passed stack for core 1
*
* core 1 must previously have been reset either as a result of a system reset or by calling \ref multicore_reset_core1
*
* core 1 will use the same vector table as core 0
*
* \param entry Function entry point
* \param stack_bottom The bottom (lowest address) of the stack
* \param stack_size_bytes The size of the stack in bytes (must be a multiple of 4)
* \see multicore_reset_core1
*/
void multicore_launch_core1_with_stack(void (*entry)(void), uint32_t *stack_bottom, size_t stack_size_bytes);
/*! \brief Launch code on core 1 with no stack protection
* \ingroup pico_multicore
*
* Wake up (a previously reset) core 1 and start it executing with a specific entry point, stack pointer
* and vector table.
*
* This is a low level function that does not provide a stack guard even if USE_STACK_GUARDS is defined
*
* core 1 must previously have been reset either as a result of a system reset or by calling \ref multicore_reset_core1
*
* \param entry Function entry point
* \param sp Pointer to the top of the core 1 stack
* \param vector_table address of the vector table to use for core 1
* \see multicore_reset_core1
*/
void multicore_launch_core1_raw(void (*entry)(void), uint32_t *sp, uint32_t vector_table);
/*!
* \defgroup multicore_fifo fifo
* \ingroup pico_multicore
* \brief Functions for the inter-core FIFOs
*
* The RP2040 contains two FIFOs for passing data, messages or ordered events between the two cores. Each FIFO is 32 bits
* wide, and 8 entries deep. One of the FIFOs can only be written by core 0, and read by core 1. The other can only be written
* by core 1, and read by core 0.
*
* \note The inter-core FIFOs are a very precious resource and are frequently used for SDK functionality (e.g. during
* core 1 launch or by the \ref multicore_lockout functions). Additionally they are often required for the exclusive use
* of an RTOS (e.g. FreeRTOS SMP). For these reasons it is suggested that you do not use the FIFO for your own purposes
* unless none of the above concerns apply; the majority of cases for transferring data between cores can be eqaully
* well handled by using a \ref queue
*/
/*! \brief Check the read FIFO to see if there is data available (sent by the other core)
* \ingroup multicore_fifo
*
* See the note in the \ref multicore_fifo section for considerations regarding use of the inter-core FIFOs
*
* \return true if the FIFO has data in it, false otherwise
*/
static inline bool multicore_fifo_rvalid(void) {
return !!(sio_hw->fifo_st & SIO_FIFO_ST_VLD_BITS);
}
/*! \brief Check the write FIFO to see if it has space for more data
* \ingroup multicore_fifo
*
* See the note in the \ref multicore_fifo section for considerations regarding use of the inter-core FIFOs
*
* @return true if the FIFO has room for more data, false otherwise
*/
static inline bool multicore_fifo_wready(void) {
return !!(sio_hw->fifo_st & SIO_FIFO_ST_RDY_BITS);
}
/*! \brief Push data on to the write FIFO (data to the other core).
* \ingroup multicore_fifo
*
* This function will block until there is space for the data to be sent.
* Use multicore_fifo_wready() to check if it is possible to write to the
* FIFO if you don't want to block.
*
* See the note in the \ref multicore_fifo section for considerations regarding use of the inter-core FIFOs
*
* \param data A 32 bit value to push on to the FIFO
*/
void multicore_fifo_push_blocking(uint32_t data);
/*! \brief Push data on to the write FIFO (data to the other core) with timeout.
* \ingroup multicore_fifo
*
* This function will block until there is space for the data to be sent
* or the timeout is reached
*
* \param data A 32 bit value to push on to the FIFO
* \param timeout_us the timeout in microseconds
* \return true if the data was pushed, false if the timeout occurred before data could be pushed
*/
bool multicore_fifo_push_timeout_us(uint32_t data, uint64_t timeout_us);
/*! \brief Pop data from the read FIFO (data from the other core).
* \ingroup multicore_fifo
*
* This function will block until there is data ready to be read
* Use multicore_fifo_rvalid() to check if data is ready to be read if you don't
* want to block.
*
* See the note in the \ref multicore_fifo section for considerations regarding use of the inter-core FIFOs
*
* \return 32 bit data from the read FIFO.
*/
uint32_t multicore_fifo_pop_blocking(void);
/*! \brief Pop data from the read FIFO (data from the other core) with timeout.
* \ingroup multicore_fifo
*
* This function will block until there is data ready to be read or the timeout is reached
*
* See the note in the \ref multicore_fifo section for considerations regarding use of the inter-core FIFOs
*
* \param timeout_us the timeout in microseconds
* \param out the location to store the popped data if available
* \return true if the data was popped and a value copied into `out`, false if the timeout occurred before data could be popped
*/
bool multicore_fifo_pop_timeout_us(uint64_t timeout_us, uint32_t *out);
/*! \brief Discard any data in the read FIFO
* \ingroup multicore_fifo
*
* See the note in the \ref multicore_fifo section for considerations regarding use of the inter-core FIFOs
*/
static inline void multicore_fifo_drain(void) {
while (multicore_fifo_rvalid())
(void) sio_hw->fifo_rd;
}
/*! \brief Clear FIFO interrupt
* \ingroup multicore_fifo
*
* Note that this only clears an interrupt that was caused by the ROE or WOF flags.
* To clear the VLD flag you need to use one of the 'pop' or 'drain' functions.
*
* See the note in the \ref multicore_fifo section for considerations regarding use of the inter-core FIFOs
*
* \see multicore_fifo_get_status
*/
static inline void multicore_fifo_clear_irq(void) {
// Write any value to clear the error flags
sio_hw->fifo_st = 0xff;
}
/*! \brief Get FIFO statuses
* \ingroup multicore_fifo
*
* \return The statuses as a bitfield
*
* Bit | Description
* ----|------------
* 3 | Sticky flag indicating the RX FIFO was read when empty (ROE). This read was ignored by the FIFO.
* 2 | Sticky flag indicating the TX FIFO was written when full (WOF). This write was ignored by the FIFO.
* 1 | Value is 1 if this core’s TX FIFO is not full (i.e. if FIFO_WR is ready for more data)
* 0 | Value is 1 if this core’s RX FIFO is not empty (i.e. if FIFO_RD is valid)
*
* See the note in the \ref multicore_fifo section for considerations regarding use of the inter-core FIFOs
*
*/
static inline uint32_t multicore_fifo_get_status(void) {
return sio_hw->fifo_st;
}
/*!
* \defgroup multicore_lockout lockout
* \ingroup pico_multicore
* \brief Functions to enable one core to force the other core to pause execution in a known state.
*
* Sometimes it is useful to enter a critical section on both cores at once. On a single
* core system a critical section can trivially be entered by disabling interrupts, however on a multi-core
* system that is not sufficient, and unless the other core is polling in some way, then it will need to be interrupted
* in order to cooperatively enter a blocked state.
*
* These "lockout" functions use the inter core FIFOs to cause an interrupt on one core from the other, and manage
* waiting for the other core to enter the "locked out" state.
*
* The usage is that the "victim" core ... i.e the core that can be "locked out" by the other core calls
* \ref multicore_lockout_victim_init to hook the FIFO interrupt. Note that either or both cores may do this.
*
* \note When "locked out" the victim core is paused (it is actually executing a tight loop with code in RAM) and has interrupts disabled.
* This makes the lockout functions suitable for use by code that wants to write to flash (at which point no code may be executing
* from flash)
*
* The core which wishes to lockout the other core calls \ref multicore_lockout_start_blocking or
* \ref multicore_lockout_start_timeout_us to interrupt the other "victim" core and wait for it to be in a
* "locked out" state. Once the lockout is no longer needed it calls \ref multicore_lockout_end_blocking or
* \ref multicore_lockout_end_timeout_us to release the lockout and wait for confirmation.
*
* \note Because multicore lockout uses the intercore FIFOs, the FIFOs cannot be used for any other purpose
*/
/*! \brief Initialize the current core such that it can be a "victim" of lockout (i.e. forced to pause in a known state by the other core)
* \ingroup multicore_lockout
*
* This code hooks the intercore FIFO IRQ, and the FIFO may not be used for any other purpose after this.
*/
void multicore_lockout_victim_init(void);
/*! \brief Determine if \ref multicore_victim_init() has been called on the specified core.
* \ingroup multicore_lockout
*
* \note this state persists even if the core is subsequently reset; therefore you are advised to
* always call \ref multicore_lockout_victim_init() again after resetting a core, which had previously
* been initialized.
*
* \param core_num the core number (0 or 1)
* \return true if \ref multicore_victim_init() has been called on the specified core, false otherwise.
*/
bool multicore_lockout_victim_is_initialized(uint core_num);
/*! \brief Request the other core to pause in a known state and wait for it to do so
* \ingroup multicore_lockout
*
* The other (victim) core must have previously executed \ref multicore_lockout_victim_init()
*
* \note multicore_lockout_start_ functions are not nestable, and must be paired with a call to a corresponding
* \ref multicore_lockout_end_blocking
*/
void multicore_lockout_start_blocking(void);
/*! \brief Request the other core to pause in a known state and wait up to a time limit for it to do so
* \ingroup multicore_lockout
*
* The other core must have previously executed \ref multicore_lockout_victim_init()
*
* \note multicore_lockout_start_ functions are not nestable, and must be paired with a call to a corresponding
* \ref multicore_lockout_end_blocking
*
* \param timeout_us the timeout in microseconds
* \return true if the other core entered the locked out state within the timeout, false otherwise
*/
bool multicore_lockout_start_timeout_us(uint64_t timeout_us);
/*! \brief Release the other core from a locked out state amd wait for it to acknowledge
* \ingroup multicore_lockout
*
* \note The other core must previously have been "locked out" by calling a `multicore_lockout_start_` function
* from this core
*/
void multicore_lockout_end_blocking(void);
/*! \brief Release the other core from a locked out state amd wait up to a time limit for it to acknowledge
* \ingroup multicore_lockout
*
* The other core must previously have been "locked out" by calling a `multicore_lockout_start_` function
* from this core
*
* \note be very careful using small timeout values, as a timeout here will leave the "lockout" functionality
* in a bad state. It is probably preferable to use \ref multicore_lockout_end_blocking anyway as if you have
* already waited for the victim core to enter the lockout state, then the victim core will be ready to exit
* the lockout state very quickly.
*
* \param timeout_us the timeout in microseconds
* \return true if the other core successfully exited locked out state within the timeout, false otherwise
*/
bool multicore_lockout_end_timeout_us(uint64_t timeout_us);
#ifdef __cplusplus
}
#endif
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_multicore/multicore.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/multicore.h"
#include "hardware/sync.h"
#include "hardware/irq.h"
#include "hardware/structs/scb.h"
#include "hardware/structs/sio.h"
#include "hardware/regs/psm.h"
#include "hardware/claim.h"
#if PICO_USE_STACK_GUARDS
#include "pico/runtime.h"
#endif
// note that these are not reset by core reset, however for now, I think people resetting cores
// and then relying on multicore_lockout for that core without re-initializing, is probably
// something we can live with breaking.
//
// whilst we could clear this in core 1 reset path, that doesn't necessarily catch all,
// and means pulling in this array even if multicore_lockout is not used.
static bool lockout_victim_initialized[NUM_CORES];
static inline void multicore_fifo_push_blocking_inline(uint32_t data) {
// We wait for the fifo to have some space
while (!multicore_fifo_wready())
tight_loop_contents();
sio_hw->fifo_wr = data;
// Fire off an event to the other core
__sev();
}
void multicore_fifo_push_blocking(uint32_t data) {
multicore_fifo_push_blocking_inline(data);
}
bool multicore_fifo_push_timeout_us(uint32_t data, uint64_t timeout_us) {
absolute_time_t end_time = make_timeout_time_us(timeout_us);
// We wait for the fifo to have some space
while (!multicore_fifo_wready()) {
tight_loop_contents();
if (time_reached(end_time)) return false;
}
sio_hw->fifo_wr = data;
// Fire off an event to the other core
__sev();
return true;
}
static inline uint32_t multicore_fifo_pop_blocking_inline(void) {
// If nothing there yet, we wait for an event first,
// to try and avoid too much busy waiting
while (!multicore_fifo_rvalid())
__wfe();
return sio_hw->fifo_rd;
}
uint32_t multicore_fifo_pop_blocking() {
return multicore_fifo_pop_blocking_inline();
}
bool multicore_fifo_pop_timeout_us(uint64_t timeout_us, uint32_t *out) {
absolute_time_t end_time = make_timeout_time_us(timeout_us);
// If nothing there yet, we wait for an event first,
// to try and avoid too much busy waiting
while (!multicore_fifo_rvalid()) {
__wfe();
if (time_reached(end_time)) return false;
}
*out = sio_hw->fifo_rd;
return true;
}
// Default stack for core1 ... if multicore_launch_core1 is not included then .stack1 section will be garbage collected
static uint32_t __attribute__((section(".stack1"))) core1_stack[PICO_CORE1_STACK_SIZE / sizeof(uint32_t)];
static void __attribute__ ((naked)) core1_trampoline(void) {
pico_default_asm ("pop {r0, r1, pc}");
}
int core1_wrapper(int (*entry)(void), void *stack_base) {
#if PICO_USE_STACK_GUARDS
// install core1 stack guard
runtime_install_stack_guard(stack_base);
#else
__unused void *ignore = stack_base;
#endif
irq_init_priorities();
return (*entry)();
}
void multicore_reset_core1() {
// Use atomic aliases just in case core 1 is also manipulating some PSM state
io_rw_32 *power_off = (io_rw_32 *) (PSM_BASE + PSM_FRCE_OFF_OFFSET);
io_rw_32 *power_off_set = hw_set_alias(power_off);
io_rw_32 *power_off_clr = hw_clear_alias(power_off);
// Hard-reset core 1.
// Reading back confirms the core 1 reset is in the correct state, but also
// forces APB IO bridges to fence on any internal store buffering
*power_off_set = PSM_FRCE_OFF_PROC1_BITS;
while (!(*power_off & PSM_FRCE_OFF_PROC1_BITS)) tight_loop_contents();
// Bring core 1 back out of reset. It will drain its own mailbox FIFO, then push
// a 0 to our mailbox to tell us it has done this.
*power_off_clr = PSM_FRCE_OFF_PROC1_BITS;
}
void multicore_launch_core1_with_stack(void (*entry)(void), uint32_t *stack_bottom, size_t stack_size_bytes) {
assert(!(stack_size_bytes & 3u));
uint32_t *stack_ptr = stack_bottom + stack_size_bytes / sizeof(uint32_t);
// push 2 values onto top of stack for core1_trampoline
stack_ptr -= 3;
stack_ptr[0] = (uintptr_t) entry;
stack_ptr[1] = (uintptr_t) stack_bottom;
stack_ptr[2] = (uintptr_t) core1_wrapper;
#if PICO_VTABLE_PER_CORE
#warning PICO_VTABLE_PER_CORE==1 is not currently supported in pico_multicore
panic_unsupported();
#endif
multicore_launch_core1_raw(core1_trampoline, stack_ptr, scb_hw->vtor);
}
void multicore_launch_core1(void (*entry)(void)) {
extern uint32_t __StackOneBottom;
uint32_t *stack_limit = (uint32_t *) &__StackOneBottom;
// hack to reference core1_stack although that pointer is wrong.... core1_stack should always be <= stack_limit, if not boom!
uint32_t *stack = core1_stack <= stack_limit ? stack_limit : (uint32_t *) -1;
multicore_launch_core1_with_stack(entry, stack, sizeof(core1_stack));
}
void multicore_launch_core1_raw(void (*entry)(void), uint32_t *sp, uint32_t vector_table) {
// Allow for the fact that the caller may have already enabled the FIFO IRQ for their
// own purposes (expecting FIFO content after core 1 is launched). We must disable
// the IRQ during the handshake, then restore afterwards.
bool enabled = irq_is_enabled(SIO_IRQ_PROC0);
irq_set_enabled(SIO_IRQ_PROC0, false);
// Values to be sent in order over the FIFO from core 0 to core 1
//
// vector_table is value for VTOR register
// sp is initial stack pointer (SP)
// entry is the initial program counter (PC) (don't forget to set the thumb bit!)
const uint32_t cmd_sequence[] =
{0, 0, 1, (uintptr_t) vector_table, (uintptr_t) sp, (uintptr_t) entry};
uint seq = 0;
do {
uint cmd = cmd_sequence[seq];
// Always drain the READ FIFO (from core 1) before sending a 0
if (!cmd) {
multicore_fifo_drain();
// Execute a SEV as core 1 may be waiting for FIFO space via WFE
__sev();
}
multicore_fifo_push_blocking(cmd);
uint32_t response = multicore_fifo_pop_blocking();
// Move to next state on correct response (echo-d value) otherwise start over
seq = cmd == response ? seq + 1 : 0;
} while (seq < count_of(cmd_sequence));
irq_set_enabled(SIO_IRQ_PROC0, enabled);
}
#define LOCKOUT_MAGIC_START 0x73a8831eu
#define LOCKOUT_MAGIC_END (~LOCKOUT_MAGIC_START)
static_assert(SIO_IRQ_PROC1 == SIO_IRQ_PROC0 + 1, "");
static mutex_t lockout_mutex;
static bool lockout_in_progress;
// note this method is in RAM because lockout is used when writing to flash
// it only makes inline calls
static void __isr __not_in_flash_func(multicore_lockout_handler)(void) {
multicore_fifo_clear_irq();
while (multicore_fifo_rvalid()) {
if (sio_hw->fifo_rd == LOCKOUT_MAGIC_START) {
uint32_t save = save_and_disable_interrupts();
multicore_fifo_push_blocking_inline(LOCKOUT_MAGIC_START);
while (multicore_fifo_pop_blocking_inline() != LOCKOUT_MAGIC_END) {
tight_loop_contents(); // not tight but endless potentially
}
restore_interrupts(save);
multicore_fifo_push_blocking_inline(LOCKOUT_MAGIC_END);
}
}
}
static void check_lockout_mutex_init(void) {
// use known available lock - we only need it briefly
uint32_t save = hw_claim_lock();
if (!mutex_is_initialized(&lockout_mutex)) {
mutex_init(&lockout_mutex);
}
hw_claim_unlock(save);
}
void multicore_lockout_victim_init(void) {
check_lockout_mutex_init();
uint core_num = get_core_num();
irq_set_exclusive_handler(SIO_IRQ_PROC0 + core_num, multicore_lockout_handler);
irq_set_enabled(SIO_IRQ_PROC0 + core_num, true);
lockout_victim_initialized[core_num] = true;
}
static bool multicore_lockout_handshake(uint32_t magic, absolute_time_t until) {
uint irq_num = SIO_IRQ_PROC0 + get_core_num();
bool enabled = irq_is_enabled(irq_num);
if (enabled) irq_set_enabled(irq_num, false);
bool rc = false;
do {
int64_t next_timeout_us = absolute_time_diff_us(get_absolute_time(), until);
if (next_timeout_us < 0) {
break;
}
multicore_fifo_push_timeout_us(magic, (uint64_t)next_timeout_us);
next_timeout_us = absolute_time_diff_us(get_absolute_time(), until);
if (next_timeout_us < 0) {
break;
}
uint32_t word = 0;
if (!multicore_fifo_pop_timeout_us((uint64_t)next_timeout_us, &word)) {
break;
}
if (word == magic) {
rc = true;
}
} while (!rc);
if (enabled) irq_set_enabled(irq_num, true);
return rc;
}
static bool multicore_lockout_start_block_until(absolute_time_t until) {
check_lockout_mutex_init();
if (!mutex_enter_block_until(&lockout_mutex, until)) {
return false;
}
hard_assert(!lockout_in_progress);
bool rc = multicore_lockout_handshake(LOCKOUT_MAGIC_START, until);
lockout_in_progress = rc;
mutex_exit(&lockout_mutex);
return rc;
}
bool multicore_lockout_start_timeout_us(uint64_t timeout_us) {
return multicore_lockout_start_block_until(make_timeout_time_us(timeout_us));
}
void multicore_lockout_start_blocking(void) {
multicore_lockout_start_block_until(at_the_end_of_time);
}
static bool multicore_lockout_end_block_until(absolute_time_t until) {
assert(mutex_is_initialized(&lockout_mutex));
if (!mutex_enter_block_until(&lockout_mutex, until)) {
return false;
}
assert(lockout_in_progress);
bool rc = multicore_lockout_handshake(LOCKOUT_MAGIC_END, until);
if (rc) {
lockout_in_progress = false;
}
mutex_exit(&lockout_mutex);
return rc;
}
bool multicore_lockout_end_timeout_us(uint64_t timeout_us) {
return multicore_lockout_end_block_until(make_timeout_time_us(timeout_us));
}
void multicore_lockout_end_blocking(void) {
multicore_lockout_end_block_until(at_the_end_of_time);
}
bool multicore_lockout_victim_is_initialized(uint core_num) {
return lockout_victim_initialized[core_num];
}
================================================
FILE: pico-sdk/src/rp2_common/pico_platform/CMakeLists.txt
================================================
if (NOT TARGET pico_platform_headers)
add_library(pico_platform_headers INTERFACE)
target_compile_definitions(pico_platform_headers INTERFACE
PICO_NO_HARDWARE=0
PICO_ON_DEVICE=1
PICO_BUILD=1
)
target_include_directories(pico_platform_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(pico_platform_headers INTERFACE hardware_regs)
endif()
if (NOT TARGET pico_platform)
pico_add_impl_library(pico_platform)
target_sources(pico_platform INTERFACE
${CMAKE_CURRENT_LIST_DIR}/platform.c)
target_link_libraries(pico_platform INTERFACE pico_platform_headers)
endif()
function(pico_add_platform_library TARGET)
target_link_libraries(pico_platform INTERFACE ${TARGET})
endfunction()
================================================
FILE: pico-sdk/src/rp2_common/pico_platform/include/pico/asm_helper.S
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
# note we don't do this by default in this file for backwards comaptibility with user code
# that may include this file, but not use unified syntax. Note that this macro does equivalent
# setup to the pico_default_asm macro for inline assembly in C code.
.macro pico_default_asm_setup
.syntax unified
.cpu cortex-m0plus
.thumb
.endm
// do not put align in here as it is used mid function sometimes
.macro regular_func x
.global \x
.type \x,%function
.thumb_func
\x:
.endm
.macro regular_func_with_section x
.section .text.\x
regular_func \x
.endm
// do not put align in here as it is used mid function sometimes
.macro wrapper_func x
regular_func WRAPPER_FUNC_NAME(\x)
.endm
.macro __pre_init func, priority_string
.section .preinit_array.\priority_string
.align 2
.word \func
.endm
================================================
FILE: pico-sdk/src/rp2_common/pico_platform/include/pico/platform.h
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_PLATFORM_H
#define _PICO_PLATFORM_H
/** \file platform.h
* \defgroup pico_platform pico_platform
*
* Macros and definitions (and functions when included by non assembly code) for the RP2 family device / architecture
* to provide a common abstraction over low level compiler / platform specifics.
*
* This header may be included by assembly code
*/
#include "hardware/platform_defs.h"
#include "hardware/regs/addressmap.h"
#include "hardware/regs/sio.h"
// Marker for builds targeting the RP2040
#define PICO_RP2040 1
// PICO_CONFIG: PICO_STACK_SIZE, Stack Size, min=0x100, default=0x800, advanced=true, group=pico_platform
#ifndef PICO_STACK_SIZE
#define PICO_STACK_SIZE _u(0x800)
#endif
// PICO_CONFIG: PICO_HEAP_SIZE, Heap size to reserve, min=0x100, default=0x800, advanced=true, group=pico_platform
#ifndef PICO_HEAP_SIZE
#define PICO_HEAP_SIZE _u(0x800)
#endif
// PICO_CONFIG: PICO_NO_RAM_VECTOR_TABLE, Enable/disable the RAM vector table, type=bool, default=0, advanced=true, group=pico_platform
#ifndef PICO_NO_RAM_VECTOR_TABLE
#define PICO_NO_RAM_VECTOR_TABLE 0
#endif
// PICO_CONFIG: PICO_RP2040_B0_SUPPORTED, Whether to include any specific software support for RP2040 B0 revision, type=bool, default=1, advanced=true, group=pico_platform
#ifndef PICO_RP2040_B0_SUPPORTED
#define PICO_RP2040_B0_SUPPORTED 1
#endif
// PICO_CONFIG: PICO_FLOAT_SUPPORT_ROM_V1, Include float support code for RP2040 B0 when that chip revision is supported , type=bool, default=1, advanced=true, group=pico_platform
#ifndef PICO_FLOAT_SUPPORT_ROM_V1
#define PICO_FLOAT_SUPPORT_ROM_V1 1
#endif
// PICO_CONFIG: PICO_DOUBLE_SUPPORT_ROM_V1, Include double support code for RP2040 B0 when that chip revision is supported , type=bool, default=1, advanced=true, group=pico_platform
#ifndef PICO_DOUBLE_SUPPORT_ROM_V1
#define PICO_DOUBLE_SUPPORT_ROM_V1 1
#endif
// PICO_CONFIG: PICO_RP2040_B1_SUPPORTED, Whether to include any specific software support for RP2040 B1 revision, type=bool, default=1, advanced=true, group=pico_platform
#ifndef PICO_RP2040_B1_SUPPORTED
#define PICO_RP2040_B1_SUPPORTED 1
#endif
// PICO_CONFIG: PICO_RP2040_B2_SUPPORTED, Whether to include any specific software support for RP2040 B2 revision, type=bool, default=1, advanced=true, group=pico_platform
#ifndef PICO_RP2040_B2_SUPPORTED
#define PICO_RP2040_B2_SUPPORTED 1
#endif
// --- remainder of file is not included by assembly code ---
#ifndef __ASSEMBLER__
#if defined __GNUC__
#include
// note LLVM defines __GNUC__
#ifdef __clang__
#define PICO_C_COMPILER_IS_CLANG 1
#else
#define PICO_C_COMPILER_IS_GNU 1
#endif
#elif defined __ICCARM__
#ifndef __aligned
#define __aligned(x) __attribute__((__aligned__(x)))
#endif
#ifndef __always_inline
#define __always_inline __attribute__((__always_inline__))
#endif
#ifndef __noinline
#define __noinline __attribute__((__noinline__))
#endif
#ifndef __packed
#define __packed __attribute__((__packed__))
#endif
#ifndef __printflike
#define __printflike(a, b)
#endif
#ifndef __unused
#define __unused __attribute__((__unused__))
#endif
#ifndef __used
#define __used __attribute__((__used__))
#endif
#ifndef __CONCAT1
#define __CONCAT1(a, b) a ## b
#endif
#ifndef __CONCAT
#define __CONCAT(a, b) __CONCAT1(a, b)
#endif
#ifndef __STRING
#define __STRING(a) #a
#endif
/* Compatible definitions of GCC builtins */
static inline uint __builtin_ctz(uint x) {
extern uint32_t __ctzsi2(uint32_t);
return __ctzsi2(x);
}
#define __builtin_expect(x, y) (x)
#define __builtin_isnan(x) __iar_isnan(x)
#else
#error Unsupported toolchain
#endif
#include "pico/types.h"
// GCC_Like_Pragma(x) is a pragma on GNUC compatible compilers
#ifdef __GNUC__
#define GCC_Like_Pragma _Pragma
#else
#define GCC_Like_Pragma(x)
#endif
// Clang_Pragma(x) is a pragma on Clang only
#ifdef __clang__
#define Clang_Pragma _Pragma
#else
#define Clang_Pragma(x)
#endif
// GCC_Pragma(x) is a pragma on GCC only
#if PICO_C_COMPILER_IS_GNU
#define GCC_Pragma _Pragma
#else
#define GCC_Pragma(x)
#endif
#ifdef __cplusplus
extern "C" {
#endif
/*! \brief Marker for an interrupt handler
* \ingroup pico_platform
*
* For example an IRQ handler function called my_interrupt_handler:
*
* void __isr my_interrupt_handler(void) {
*/
#define __isr
/*! \brief Section attribute macro for placement in RAM after the `.data` section
* \ingroup pico_platform
*
* For example a 400 element `uint32_t` array placed after the .data section
*
* uint32_t __after_data("my_group_name") a_big_array[400];
*
* The section attribute is `.after_data.`
*
* \param group a string suffix to use in the section name to distinguish groups that can be linker
* garbage-collected independently
*/
#define __after_data(group) __attribute__((section(".after_data." group)))
/*! \brief Section attribute macro for placement not in flash (i.e in RAM)
* \ingroup pico_platform
*
* For example a 3 element `uint32_t` array placed in RAM (even though it is `static const`)
*
* static const uint32_t __not_in_flash("my_group_name") an_array[3];
*
* The section attribute is `.time_critical.`
*
* \param group a string suffix to use in the section name to distinguish groups that can be linker
* garbage-collected independently
*/
#define __not_in_flash(group) __attribute__((section(".time_critical." group)))
/*! \brief Section attribute macro for placement in the SRAM bank 4 (known as "scratch X")
* \ingroup pico_platform
*
* Scratch X is commonly used for critical data and functions accessed only by one core (when only
* one core is accessing the RAM bank, there is no opportunity for stalls)
*
* For example a `uint32_t` variable placed in "scratch X"
*
* uint32_t __scratch_x("my_group_name") foo = 23;
*
* The section attribute is `.scratch_x.`
*
* \param group a string suffix to use in the section name to distinguish groups that can be linker
* garbage-collected independently
*/
#define __scratch_x(group) __attribute__((section(".scratch_x." group)))
/*! \brief Section attribute macro for placement in the SRAM bank 5 (known as "scratch Y")
* \ingroup pico_platform
*
* Scratch Y is commonly used for critical data and functions accessed only by one core (when only
* one core is accessing the RAM bank, there is no opportunity for stalls)
*
* For example a `uint32_t` variable placed in "scratch Y"
*
* uint32_t __scratch_y("my_group_name") foo = 23;
*
* The section attribute is `.scratch_y.`
*
* \param group a string suffix to use in the section name to distinguish groups that can be linker
* garbage-collected independently
*/
#define __scratch_y(group) __attribute__((section(".scratch_y." group)))
/*! \brief Section attribute macro for data that is to be left uninitialized
* \ingroup pico_platform
*
* Data marked this way will retain its value across a reset (normally uninitialized data - in the .bss
* section) is initialized to zero during runtime initialization
*
* For example a `uint32_t` foo that will retain its value if the program is restarted by reset.
*
* uint32_t __uninitialized_ram(foo);
*
* The section attribute is `.uninitialized_data.`
*
* \param group a string suffix to use in the section name to distinguish groups that can be linker
* garbage-collected independently
*/
#define __uninitialized_ram(group) __attribute__((section(".uninitialized_data." #group))) group
/*! \brief Section attribute macro for placement in flash even in a COPY_TO_RAM binary
* \ingroup pico_platform
*
* For example a `uint32_t` variable explicitly placed in flash (it will hard fault if you attempt to write it!)
*
* uint32_t __in_flash("my_group_name") foo = 23;
*
* The section attribute is `.flashdata.`
*
* \param group a string suffix to use in the section name to distinguish groups that can be linker
* garbage-collected independently
*/
#define __in_flash(group) __attribute__((section(".flashdata." group)))
/*! \brief Indicates a function should not be stored in flash
* \ingroup pico_platform
*
* Decorates a function name, such that the function will execute from RAM (assuming it is not inlined
* into a flash function by the compiler)
*
* For example a function called my_func taking an int parameter:
*
* void __not_in_flash_func(my_func)(int some_arg) {
*
* The function is placed in the `.time_critical.` linker section
*
* \see __no_inline_not_in_flash_func
*/
#define __not_in_flash_func(func_name) __not_in_flash(__STRING(func_name)) func_name
/*! \brief Indicates a function is time/latency critical and should not run from flash
* \ingroup pico_platform
*
* Decorates a function name, such that the function will execute from RAM (assuming it is not inlined
* into a flash function by the compiler) to avoid possible flash latency. Currently this macro is identical
* in implementation to `__not_in_flash_func`, however the semantics are distinct and a `__time_critical_func`
* may in the future be treated more specially to reduce the overhead when calling such function from a flash
* function.
*
* For example a function called my_func taking an int parameter:
*
* void __time_critical(my_func)(int some_arg) {
*
* The function is placed in the `.time_critical.` linker section
*
* \see __not_in_flash_func
*/
#define __time_critical_func(func_name) __not_in_flash_func(func_name)
/*! \brief Indicate a function should not be stored in flash and should not be inlined
* \ingroup pico_platform
*
* Decorates a function name, such that the function will execute from RAM, explicitly marking it as
* noinline to prevent it being inlined into a flash function by the compiler
*
* For example a function called my_func taking an int parameter:
*
* void __no_inline_not_in_flash_func(my_func)(int some_arg) {
*
* The function is placed in the `.time_critical.` linker section
*/
#define __no_inline_not_in_flash_func(func_name) __noinline __not_in_flash_func(func_name)
#define __packed_aligned __packed __aligned(4)
/*! \brief Attribute to force inlining of a function regardless of optimization level
* \ingroup pico_platform
*
* For example my_function here will always be inlined:
*
* int __force_inline my_function(int x) {
*
*/
#if PICO_C_COMPILER_IS_GNU && (__GNUC__ <= 6 || (__GNUC__ == 7 && (__GNUC_MINOR__ < 3 || !defined(__cplusplus))))
#define __force_inline inline __always_inline
#else
#define __force_inline __always_inline
#endif
/*! \brief Macro to determine the number of elements in an array
* \ingroup pico_platform
*/
#ifndef count_of
#define count_of(a) (sizeof(a)/sizeof((a)[0]))
#endif
/*! \brief Macro to return the maximum of two comparable values
* \ingroup pico_platform
*/
#ifndef MAX
#define MAX(a, b) ((a)>(b)?(a):(b))
#endif
/*! \brief Macro to return the minimum of two comparable values
* \ingroup pico_platform
*/
#ifndef MIN
#define MIN(a, b) ((b)>(a)?(a):(b))
#endif
#define pico_default_asm(...) __asm (".syntax unified\n" __VA_ARGS__)
#define pico_default_asm_volatile(...) __asm volatile (".syntax unified\n" __VA_ARGS__)
/*! \brief Execute a breakpoint instruction
* \ingroup pico_platform
*/
static inline void __breakpoint(void) {
pico_default_asm ("bkpt #0");
}
/*! \brief Ensure that the compiler does not move memory access across this method call
* \ingroup pico_platform
*
* For example in the following code:
*
* *some_memory_location = var_a;
* __compiler_memory_barrier();
* uint32_t var_b = *some_other_memory_location
*
* The compiler will not move the load from `some_other_memory_location` above the memory barrier (which it otherwise
* might - even above the memory store!)
*/
__force_inline static void __compiler_memory_barrier(void) {
pico_default_asm_volatile ("" : : : "memory");
}
/*! \brief Macro for converting memory addresses to 32 bit addresses suitable for DMA
* \ingroup pico_platform
*
* This is just a cast to `uintptr_t` on the RP2040, however you may want to use this when developing code
* that also runs in "host" mode. If the host mode is 64 bit and you are embedding data pointers
* in other data (e.g. DMA chaining), then there is a need in "host" mode to convert a 64 bit native
* pointer to a 32 bit value for storage, which can be done using this macro.
*/
#define host_safe_hw_ptr(x) ((uintptr_t)(x))
#define native_safe_hw_ptr(x) host_safe_hw_ptr(x)
/*! \brief Panics with the message "Unsupported"
* \ingroup pico_platform
* \see panic
*/
void __attribute__((noreturn)) panic_unsupported(void);
/*! \brief Displays a panic message and halts execution
* \ingroup pico_platform
*
* An attempt is made to output the message to all registered STDOUT drivers
* after which this method executes a BKPT instruction.
*
* @param fmt format string (printf-like)
* @param ... printf-like arguments
*/
void __attribute__((noreturn)) panic(const char *fmt, ...);
#ifdef NDEBUG
#define panic_compact(...) panic(__VA_ARGS__)
#else
#define panic_compact(...) panic("")
#endif
// PICO_CONFIG: PICO_NO_FPGA_CHECK, Remove the FPGA platform check for small code size reduction, type=bool, default=0, advanced=true, group=pico_runtime
#ifndef PICO_NO_FPGA_CHECK
#define PICO_NO_FPGA_CHECK 0
#endif
#if PICO_NO_FPGA_CHECK
static inline bool running_on_fpga(void) {return false;}
#else
bool running_on_fpga(void);
#endif
/*! \brief Returns the RP2040 chip revision number
* \ingroup pico_platform
* @return the RP2040 chip revision number (1 for B0/B1, 2 for B2)
*/
uint8_t rp2040_chip_version(void);
/*! \brief Returns the RP2040 rom version number
* \ingroup pico_platform
* @return the RP2040 rom version number (1 for RP2040-B0, 2 for RP2040-B1, 3 for RP2040-B2)
*/
static inline uint8_t rp2040_rom_version(void) {
GCC_Pragma("GCC diagnostic push")
GCC_Pragma("GCC diagnostic ignored \"-Warray-bounds\"")
return *(uint8_t*)0x13;
GCC_Pragma("GCC diagnostic pop")
}
/*! \brief No-op function for the body of tight loops
* \ingroup pico_platform
*
* No-op function intended to be called by any tight hardware polling loop. Using this ubiquitously
* makes it much easier to find tight loops, but also in the future \#ifdef-ed support for lockup
* debugging might be added
*/
static __force_inline void tight_loop_contents(void) {}
/*! \brief Multiply two integers using an assembly `MUL` instruction
* \ingroup pico_platform
*
* This multiplies a by b using multiply instruction using the ARM mul instruction regardless of values (the compiler
* might otherwise choose to perform shifts/adds), i.e. this is a 1 cycle operation.
*
* \param a the first operand
* \param b the second operand
* \return a * b
*/
__force_inline static int32_t __mul_instruction(int32_t a, int32_t b) {
pico_default_asm ("muls %0, %1" : "+l" (a) : "l" (b) : );
return a;
}
/*! \brief multiply two integer values using the fastest method possible
* \ingroup pico_platform
*
* Efficiently multiplies value a by possibly constant value b.
*
* If b is known to be constant and not zero or a power of 2, then a mul instruction is used rather than gcc's default
* which is often a slow combination of shifts and adds. If b is a power of 2 then a single shift is of course preferable
* and will be used
*
* \param a the first operand
* \param b the second operand
* \return a * b
*/
#define __fast_mul(a, b) __builtin_choose_expr(__builtin_constant_p(b) && !__builtin_constant_p(a), \
(__builtin_popcount(b) >= 2 ? __mul_instruction(a,b) : (a)*(b)), \
(a)*(b))
/*! \brief Utility macro to assert two types are equivalent.
* \ingroup pico_platform
*
* This macro can be useful in other macros along with `typeof` to assert that two parameters are of equivalent type
* (or that a single parameter is of an expected type)
*/
#define __check_type_compatible(type_a, type_b) static_assert(__builtin_types_compatible_p(type_a, type_b), __STRING(type_a) " is not compatible with " __STRING(type_b));
/*! \brief Get the current exception level on this core
* \ingroup pico_platform
*
* \return the exception number if the CPU is handling an exception, or 0 otherwise
*/
static __force_inline uint __get_current_exception(void) {
uint exception;
pico_default_asm( "mrs %0, ipsr" : "=l" (exception));
return exception;
}
#define WRAPPER_FUNC(x) __wrap_ ## x
#define REAL_FUNC(x) __real_ ## x
/*! \brief Helper method to busy-wait for at least the given number of cycles
* \ingroup pico_platform
*
* This method is useful for introducing very short delays.
*
* This method busy-waits in a tight loop for the given number of system clock cycles. The total wait time is only accurate to within 2 cycles,
* and this method uses a loop counter rather than a hardware timer, so the method will always take longer than expected if an
* interrupt is handled on the calling core during the busy-wait; you can of course disable interrupts to prevent this.
*
* You can use \ref clock_get_hz(clk_sys) to determine the number of clock cycles per second if you want to convert an actual
* time duration to a number of cycles.
*
* \param minimum_cycles the minimum number of system clock cycles to delay for
*/
static inline void busy_wait_at_least_cycles(uint32_t minimum_cycles) {
pico_default_asm_volatile(
"1: subs %0, #3\n"
"bcs 1b\n"
: "+l" (minimum_cycles) : : "memory"
);
}
/*! \brief Get the current core number
* \ingroup pico_platform
*
* \return The core number the call was made from
*/
__force_inline static uint get_core_num(void) {
return (*(uint32_t *) (SIO_BASE + SIO_CPUID_OFFSET));
}
#ifdef __cplusplus
}
#endif
#else // __ASSEMBLER__
#if defined __GNUC__
// note LLVM defines __GNUC__
#ifdef __clang__
#define PICO_ASSEMBLER_IS_CLANG 1
#else
#define PICO_ASSEMBLER_IS_GNU 1
#endif
#elif defined __ICCARM__
#else
#error Unsupported toolchain
#endif
#define WRAPPER_FUNC_NAME(x) __wrap_##x
#define SECTION_NAME(x) .text.##x
#define RAM_SECTION_NAME(x) .time_critical.##x
#endif // !__ASSEMBLER__
#endif
================================================
FILE: pico-sdk/src/rp2_common/pico_platform/platform.c
================================================
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
#include "hardware/address_mapped.h"
#include "hardware/regs/tbman.h"
#include "hardware/regs/sysinfo.h"
// Note we leave the FPGA check in by default so that we can run bug repro
// binaries coming in from the wild on the FPGA platform. It takes up around
// 48 bytes if you include all the calls, so you can pass PICO_NO_FPGA_CHECK=1
// to remove it. The FPGA check is used to skip initialisation of hardware
// (mainly clock generators and oscillators) that aren't present on FPGA.
#if !PICO_NO_FPGA_CHECK
// Inline stub provided in header if this code is unused (so folding can be
// done in each TU instead of relying on LTO)
bool running_on_fpga(void) {
return !!((*(io_ro_32 *)TBMAN_BASE) & TBMAN_PLATFORM_FPGA_BITS);
}
#endif
#define MANUFACTURER_RPI 0x927
#define PART_RP2 0x2
uint8_t rp2040_chip_version(void) {
// First register of sysinfo is chip id
uint32_t chip_id = *((io_ro_32*)(SYSINFO_BASE + SYSINFO_CHIP_ID_OFFSET));
uint32_t __unused manufacturer = chip_id & SYSINFO_CHIP_ID_MANUFACTURER_BITS;
uint32_t __unused part = (chip_id & SYSINFO_CHIP_ID_PART_BITS) >> SYSINFO_CHIP_ID_PART_LSB;
assert(manufacturer == MANUFACTURER_RPI);
assert(part == PART_RP2);
// Version 1 == B0/B1
uint version = (chip_id & SYSINFO_CHIP_ID_REVISION_BITS) >> SYSINFO_CHIP_ID_REVISION_LSB;
return (uint8_t)version;
}
================================================
FILE: pico-sdk/src/rp2_common/pico_printf/CMakeLists.txt
================================================
if (NOT TARGET pico_printf)
# library to be depended on - we make this depend on particular implementations using per target generator expressions
pico_add_library(pico_printf)
# no custom implementation; falls thru to compiler
pico_add_library(pico_printf_compiler)
target_include_directories(pico_printf_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
# add alias "default" which is just pico.
add_library(pico_printf_default INTERFACE)
target_link_libraries(pico_printf_default INTERFACE pico_printf_pico)
set(PICO_DEFAULT_PRINTF_IMPL pico_printf_default)
target_link_libraries(pico_printf INTERFACE
$