Skip to content

Commit

Permalink
Merge pull request #51 from jfpoilpret/remove_virtual
Browse files Browse the repository at this point in the history
Remove virtual to optimize code.
  • Loading branch information
jfpoilpret committed Jul 7, 2019
2 parents 2d7aec8 + 4d5e0ec commit 1c99f91
Show file tree
Hide file tree
Showing 10 changed files with 424 additions and 202 deletions.
2 changes: 1 addition & 1 deletion cores/fastarduino/devices/nrf24l01p.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ namespace devices::rf
*
* @sa IRQ_NRF24L01
*/
template<board::DigitalPin CSN, board::DigitalPin CE> class NRF24L01 : private spi::SPIDevice<CSN>
template<board::DigitalPin CSN, board::DigitalPin CE> class NRF24L01 : public spi::SPIDevice<CSN>
{
public:
/** Broadcast device address. */
Expand Down
64 changes: 22 additions & 42 deletions cores/fastarduino/soft_uart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ void serial::soft::AbstractUATX::compute_times(uint32_t rate, StopBits stop_bits
{
// Calculate timing for TX in number of cycles
uint16_t bit_time = uint16_t(F_CPU / rate);
// Actual timing is based on number of times to count 4 cycles, because we use _delay_loop_2()
// 11 or 12 cycles + delay counted from start bit (cbi) to first bit (sbi or cbi)
interbit_tx_time_ = (bit_time - 12) / 4;
// 11 or 12 cycles + delay counted from first bit (sbi or cbi) to second bit (sbi or cbi)
start_bit_tx_time_ = (bit_time - 12) / 4;
// 11 or 12 cycles + delay counted from first bit (sbi or cbi) to second bit (sbi or cbi)
interbit_tx_time_ = (bit_time - 12) / 4;
// For stop bit we lengthten the bit duration of 25% to guarantee alignment of RX side on stop duration
stop_bit_tx_time_ = (bit_time / 4) * 5 / 4;
if (stop_bits == StopBits::TWO) stop_bit_tx_time_ *= 2;
Expand All @@ -34,55 +33,36 @@ static constexpr uint16_t compute_delay(uint16_t total_cycles, uint16_t less_cyc
return (total_cycles > less_cycles ? (total_cycles - less_cycles + 3) / 4 : 1);
}

void serial::soft::AbstractUARX::compute_times(uint32_t rate, bool has_parity, UNUSED StopBits stop_bits)
//TODO possible code size optimizations by using a static inline constexpr to calculate all values?
void serial::soft::AbstractUARX::compute_times(uint32_t rate, UNUSED bool has_parity, UNUSED StopBits stop_bits)
{
// Calculate timing for RX in number of cycles
uint16_t bit_time = uint16_t(F_CPU / rate);

// Actual timing is based on number of times to count 4 cycles, because we use _delay_loop_2()
// 87 cycles (+4N delay) elapse until start bit is detected from PCI interrupt and 1st bit is sampled:

// Time to wait (_delay_loop_2) between detection of start bit and sampling of first bit
// For sampling of first bit we wait until middle of first bit
// We remove processing time due to ISR call and ISR code:
// - 3 cycles to generate the PCI interrupt
// - 1-4 (take 2) cycles to complete current instruction
// - 4 cycles to process the interrupt + 2 cycles rjmp in vector table
// - 50 cycles spent in PCINT vector to save context and call handler
// - 4 cycles spent in virtual handler thunk
// - 4N + 4 in delay
// - 18 cycles until first bit sample read
// For sampling of first bit we wait until middle of first bit
start_bit_rx_time_ = compute_delay(3 * bit_time / 2, 3 + 2 + 4 + 2 + 50 + 4 + 4 + 18);

// 16+4N cycles elapse between processing of each bit
interbit_rx_time_ = compute_delay(bit_time, 16);
// - 32 cycles spent in PCINT vector to save context and check stop bit (sbic)
// - 8 cycles to setup stack variables
// - (4N) + 4 in delay
// - 8 cycles until first bit sample read (sbis)
start_bit_rx_time_ = compute_delay(3 * bit_time / 2, 3 + 2 + 4 + 2 + 32 + 8 + 4 + 8);

if (has_parity)
{
// When parity must be checked, the number of cycles between last data bit sampled and parity bit sample is:
// - 20 cycles + 4n delay
parity_bit_rx_time_ = compute_delay(bit_time, 20);
// If no error (push) then the following cycles will get executed until we re-enable PCI
// - 21 cycles before delay loop
// - 41 cycles in push_
// - 4N cycles in delay loop
// - 5 additional cycles to make sure we passed the edge of stop bit
stop_bit_rx_time_push_ = compute_delay(bit_time / 2, 21 + 41 + 5);
// If an error occurred (no push) then the following cycles will get executed until we re-enable PCI
// - 26 cycles before delay loop
// - 4N cycles in delay loop
// - 5 additional cycles to make sure we passed the edge of stop bit
stop_bit_rx_time_no_push_ = compute_delay(bit_time / 2, 26 + 5);
}
else
{
// If no error (push) then the following cycles will get executed until we re-enable PCI
// - 24 cycles before delay loop
// - 41 cycles in push_
// - 4N cycles in delay loop
// - 5 additional cycles to make sure we passed the edge of stop bit
stop_bit_rx_time_push_ = compute_delay(bit_time / 2, 24 + 41 + 5);
}
// Time to wait (_delay_loop_2) between sampling of 2 consecutive data bits
// This is also use between last bit and parity bit (if checked) or stop bit
// We have to wait exactly for `bit_time` cycles
// We remove processing time due to each bit sampling and data value update
// - 10+4N cycles elapse between processing of each bit
interbit_rx_time_ = compute_delay(bit_time, 10);

// We don't care about actual stop bit time, we just ensure we are ready for PCI before the end on stop bit
// Additionally, 49 cycles elapse until next PCI can be handled
// No extra delay is used fater sampling of first stop bit (as there are already
// enough code cycles used for pushing data and restoring context on ISR leave)
// Actually, >80 cycles elapse until next PCI can be handled
}

serial::Parity serial::soft::AbstractUATX::calculate_parity(Parity parity, uint8_t value)
Expand Down
123 changes: 64 additions & 59 deletions cores/fastarduino/soft_uart.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,17 @@
* Defines API types used by software UART features.
* This API is available to all MCU, even those that do not have hardware UART,
* hence even ATtiny MCU are supported.
* IMPORTANT! Note that software-emulated UART cannot be as fast as hardware UART,
* for that reason a maximum rate of 115'200bps is supported, preferrably with 2
* stop bits (depending on the sending device, UART reception may lose bits if only
* one stop bit is used).
*
* @sa serial::hard
*/
namespace serial::soft
{
/// @cond notdocumented
class AbstractUATX : private streams::ostreambuf
class AbstractUATX
{
public:
/**
Expand All @@ -104,27 +108,36 @@ namespace serial::soft
}

protected:
template<uint8_t SIZE_TX> explicit AbstractUATX(char (&output)[SIZE_TX]) : ostreambuf{output} {}
using CALLBACK = streams::ostreambuf::CALLBACK;

template<uint8_t SIZE_TX>
explicit AbstractUATX(char (&output)[SIZE_TX], CALLBACK callback, void* arg)
: obuf_{output, callback, arg} {}

void compute_times(uint32_t rate, StopBits stop_bits);
static Parity calculate_parity(Parity parity, uint8_t value);

streams::ostreambuf& out_()
{
return (streams::ostreambuf&) *this;
return obuf_;
}

void check_overflow(Errors& errors)
{
errors.queue_overflow = overflow();
errors.queue_overflow = obuf_.overflow();
}

template<board::DigitalPin DPIN> void write(Parity parity, uint8_t value)
{
synchronized write_<DPIN>(parity, value);
}
template<board::DigitalPin DPIN> void write_(Parity parity, uint8_t value);


private:
// NOTE declaring obuf_ first instead of last optimizes code size (4 bytes)
streams::ostreambuf obuf_;

protected:
// Various timing constants based on rate
uint16_t interbit_tx_time_;
uint16_t start_bit_tx_time_;
Expand All @@ -146,11 +159,7 @@ namespace serial::soft
if (value & 0x01)
TX::set();
else
{
// Additional NOP to ensure set/clear are executed exactly at the same time (cycle)
asm volatile("NOP");
TX::clear();
}
value >>= 1;
_delay_loop_2(interbit_tx_time_);
}
Expand Down Expand Up @@ -179,6 +188,9 @@ namespace serial::soft
*/
template<board::DigitalPin TX_> class UATX : public AbstractUATX, public UARTErrors
{
private:
using THIS = UATX<TX_>;

public:
/** The `board::DigitalPin` to which transmitted signal is sent */
static constexpr const board::DigitalPin TX = TX_;
Expand All @@ -189,8 +201,8 @@ namespace serial::soft
* @param output an array of characters used by this transmitter to
* buffer output during transmission
*/
template<uint8_t SIZE_TX>
explicit UATX(char (&output)[SIZE_TX]) : AbstractUATX{output}, tx_{gpio::PinMode::OUTPUT, true} {}
template<uint8_t SIZE_TX> explicit UATX(char (&output)[SIZE_TX])
: AbstractUATX{output, THIS::on_put, this}, tx_{gpio::PinMode::OUTPUT, true} {}

/**
* Enable the transmitter.
Expand Down Expand Up @@ -222,24 +234,22 @@ namespace serial::soft
// - enable pushing again?
}

protected:
/// @cond notdocumented
void on_put() override
private:
static void on_put(void* arg)
{
THIS& target = *((THIS*) arg);
//FIXME we should write ONLY if UAT is active (begin() has been called and not end())
check_overflow(errors());
target.check_overflow(target.errors());
char value;
while (out_().queue().pull(value)) write<TX>(parity_, uint8_t(value));
while (target.out_().queue().pull(value)) target.write<TX>(target.parity_, uint8_t(value));
}
/// @endcond

private:
Parity parity_;
typename gpio::FastPinType<TX>::TYPE tx_;
};

/// @cond notdocumented
class AbstractUARX : private streams::istreambuf
class AbstractUARX
{
public:
/**
Expand All @@ -252,23 +262,25 @@ namespace serial::soft
}

protected:
template<uint8_t SIZE_RX> explicit AbstractUARX(char (&input)[SIZE_RX]) : istreambuf{input} {}
template<uint8_t SIZE_RX> explicit AbstractUARX(char (&input)[SIZE_RX]) : ibuf_{input} {}

streams::istreambuf& in_()
{
return (streams::istreambuf&) *this;
return ibuf_;
}

void compute_times(uint32_t rate, bool has_parity, StopBits stop_bits);

template<board::DigitalPin DPIN> void pin_change(Parity parity, Errors& errors);

private:
// NOTE declaring ibuf_ first instead of last optimizes code size (2 bytes)
streams::istreambuf ibuf_;

protected:
// Various timing constants based on rate
uint16_t interbit_rx_time_;
uint16_t start_bit_rx_time_;
uint16_t parity_bit_rx_time_;
uint16_t stop_bit_rx_time_push_;
uint16_t stop_bit_rx_time_no_push_;
};

template<board::DigitalPin DPIN> void AbstractUARX::pin_change(Parity parity, Errors& errors)
Expand All @@ -282,44 +294,33 @@ namespace serial::soft
// Wait for start bit to finish
_delay_loop_2(start_bit_rx_time_);
// Read first 7 bits
for (uint8_t i = 0; i < 7; ++i)
for (uint8_t i = 0; i < 8; ++i)
{
if (RX::value())
{
value |= 0x80;
odd = !odd;
}
value >>= 1;
if (i < 7)
value >>= 1;
_delay_loop_2(interbit_rx_time_);
}
// Read last bit
if (RX::value())
{
value |= 0x80;
odd = !odd;
}

if (parity != Parity::NONE)
{
// Wait for parity bit
_delay_loop_2(parity_bit_rx_time_);
// Check parity bit
bool parity_bit = (parity == Parity::ODD ? !odd : odd);
// Check parity bit
errors.parity_error = (RX::value() != parity_bit);
_delay_loop_2(interbit_rx_time_);
}

// Check we receive a stop bit
if (!RX::value())
errors.frame_error = true;
// Push value if no error
if (errors.has_errors == 0)
{
errors.queue_overflow = !in_().queue().push_(char(value));
// Wait for 1st stop bit
_delay_loop_2(stop_bit_rx_time_push_);
}
else
{
// Wait for 1st stop bit
_delay_loop_2(stop_bit_rx_time_no_push_);
}
}
/// @endcond

Expand Down Expand Up @@ -413,6 +414,9 @@ namespace serial::soft
template<board::ExternalInterruptPin RX_, board::DigitalPin TX_>
class UART<board::ExternalInterruptPin, RX_, TX_> : public AbstractUARX, public AbstractUATX, public UARTErrors
{
private:
using THIS = UART<board::ExternalInterruptPin, RX_, TX_>;

public:
/** The `board::DigitalPin` to which transmitted signal is sent */
static constexpr const board::DigitalPin TX = TX_;
Expand Down Expand Up @@ -440,7 +444,8 @@ namespace serial::soft
*/
template<uint8_t SIZE_RX, uint8_t SIZE_TX>
explicit UART(char (&input)[SIZE_RX], char (&output)[SIZE_TX])
: AbstractUARX{input}, AbstractUATX{output}, tx_{gpio::PinMode::OUTPUT, true}, rx_{gpio::PinMode::INPUT}
: AbstractUARX{input}, AbstractUATX{output, THIS::on_put, this},
tx_{gpio::PinMode::OUTPUT, true}, rx_{gpio::PinMode::INPUT}
{
interrupt::register_handler(*this);
}
Expand Down Expand Up @@ -478,18 +483,16 @@ namespace serial::soft
int_->disable();
}

protected:
/// @cond notdocumented
void on_put() override
private:
static void on_put(void* arg)
{
THIS& target = *((THIS*) arg);
//FIXME we should write ONLY if UAT is active (begin() has been called and not end())
check_overflow(errors());
target.check_overflow(target.errors());
char value;
while (out_().queue().pull(value)) write<TX>(parity_, uint8_t(value));
while (target.out_().queue().pull(value)) target.write<TX>(target.parity_, uint8_t(value));
}
/// @endcond

private:
void on_pin_change()
{
this->pin_change<RX>(parity_, errors());
Expand Down Expand Up @@ -587,6 +590,9 @@ namespace serial::soft
template<board::InterruptPin RX_, board::DigitalPin TX_>
class UART<board::InterruptPin, RX_, TX_> : public AbstractUARX, public AbstractUATX, public UARTErrors
{
private:
using THIS = UART<board::InterruptPin, RX_, TX_>;

public:
/** The `board::DigitalPin` to which transmitted signal is sent */
static constexpr const board::DigitalPin TX = TX_;
Expand Down Expand Up @@ -614,7 +620,8 @@ namespace serial::soft
*/
template<uint8_t SIZE_RX, uint8_t SIZE_TX>
explicit UART(char (&input)[SIZE_RX], char (&output)[SIZE_TX])
: AbstractUARX{input}, AbstractUATX{output}, tx_{gpio::PinMode::OUTPUT, true}, rx_{gpio::PinMode::INPUT}
: AbstractUARX{input}, AbstractUATX{output, THIS::on_put, this},
tx_{gpio::PinMode::OUTPUT, true}, rx_{gpio::PinMode::INPUT}
{
interrupt::register_handler(*this);
}
Expand Down Expand Up @@ -652,18 +659,16 @@ namespace serial::soft
pci_->template disable_pin<RX_>();
}

protected:
/// @cond notdocumented
void on_put() override
private:
static void on_put(void* arg)
{
THIS& target = *((THIS*) arg);
//FIXME we should write ONLY if UAT is active (begin() has been called and not end())
check_overflow(errors());
target.check_overflow(target.errors());
char value;
while (out_().queue().pull(value)) write<TX>(parity_, uint8_t(value));
while (target.out_().queue().pull(value)) target.write<TX>(target.parity_, uint8_t(value));
}
/// @endcond

private:
void on_pin_change()
{
this->pin_change<RX>(parity_, errors());
Expand Down
Loading

0 comments on commit 1c99f91

Please sign in to comment.