diff --git a/CMakeLists.txt b/CMakeLists.txt index d6cf089..fdcb178 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ libhal_test_and_make_library( SOURCES src/bit_bang_i2c.cpp + src/bit_bang_spi.cpp src/rc_servo.cpp src/i2c_minimum_speed.cpp src/atomic_spin_lock.cpp diff --git a/include/libhal-soft/bit_bang_spi.hpp b/include/libhal-soft/bit_bang_spi.hpp new file mode 100644 index 0000000..bc2036f --- /dev/null +++ b/include/libhal-soft/bit_bang_spi.hpp @@ -0,0 +1,127 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include + +namespace hal::soft { +/** + * @brief A bit bang implementation for spi. + * + * This implementation of spi only needs 2 hal::output_pins for sck and copi, a + * hal::input_pin for cipo, and a steady_clock to work. + */ +class bit_bang_spi : public spi +{ +public: + struct pins + { + output_pin* sck; + output_pin* copi; + input_pin* cipo; + }; + + /// Adds or removes delays in the read/write cycle + enum class delay_mode + { + /// Calculates the delay time using the clock_rate from the provided + /// settings when constructing the bit_bang_spi object + with, + /// Omits delays between reading/writing to get the fastest speed possible + without + }; + + /** + * @brief Construct a new bit bang spi object + * + * @param p_pins named structure that contains pointers to the sck, copi, and + * cipo to be used inside of the driver + * @param p_steady_clock the steady clock that will be used for timing the sck + * line. + * @param p_settings the initial settings of the spi bus + * @param p_delay_mode adds or removes delays in the read/write cycle + */ + bit_bang_spi(pins const& p_pins, + steady_clock& p_steady_clock, + settings const& p_settings = {}, + delay_mode p_delay_mode = delay_mode::with); + +private: + void driver_configure(settings const& p_settings) override; + + void driver_transfer(std::span p_data_out, + std::span p_data_in, + hal::byte p_filler) override; + + /** + * @brief This function will handle writing a single byte to spi copi line + * + * @param p_byte_to_write This is the byte that will be written + */ + hal::byte transfer_byte(hal::byte p_byte_to_write); + + /** + * @brief This function will handle writing a single byte to spi copi line + * without using delays between reading and writing + * + * @param p_byte_to_write This is the byte that will be written + */ + hal::byte transfer_byte_without_delay(hal::byte p_byte_to_write); + + /** + * @brief This function will handle writing a single bit to the spi copi line + * + * @param p_bit_to_write This is the bit that will be written + */ + bool transfer_bit(bool p_bit_to_write); + + /** + * @brief This function will handle writing a single bit to the spi copi line + * without using delays between reading and writing + * + * @param p_bit_to_write This is the bit that will be written + */ + bool transfer_bit_without_delay(bool p_bit_to_write); + + /// @brief An output pin which is the spi sck pin + hal::output_pin* m_sck; + + /// @brief An output pin which is the spi copi pin + hal::output_pin* m_copi; + + /// @brief An input pin which is the spi cipo pin + hal::input_pin* m_cipo; + + /// @brief A steady_clock provides a mechanism to delay the clock pulses of + /// the sck line. + hal::steady_clock* m_clock; + + /// @brief State of the sck line when inactive + bool m_polarity; + + /// @brief Phase of the sck line dictates when to read/write bits + bool m_phase; + + /// @brief Time of a full read/write cycle, used in delay_mode::with + hal::time_duration m_cycle_duration; + + /// @brief Configuration for using delays or not + delay_mode m_delay_mode; +}; + +} // namespace hal::soft diff --git a/src/bit_bang_spi.cpp b/src/bit_bang_spi.cpp new file mode 100644 index 0000000..7363476 --- /dev/null +++ b/src/bit_bang_spi.cpp @@ -0,0 +1,125 @@ +#include + +#include +#include +#include +#include +#include + +namespace hal::soft { +// public +bit_bang_spi::bit_bang_spi(pins const& p_pins, + hal::steady_clock& p_clock, + settings const& p_settings, + delay_mode p_delay_mode) + : m_sck(p_pins.sck) + , m_copi(p_pins.copi) + , m_cipo(p_pins.cipo) + , m_clock(&p_clock) + , m_delay_mode(p_delay_mode) +{ + m_sck->configure( + { .resistor = hal::pin_resistor::none, .open_drain = false }); + m_copi->configure( + { .resistor = hal::pin_resistor::none, .open_drain = false }); + m_cipo->configure({ .resistor = hal::pin_resistor::pull_up }); + hal::soft::bit_bang_spi::driver_configure(p_settings); +} + +// private +void bit_bang_spi::driver_configure(settings const& p_settings) +{ + using period = std::chrono::nanoseconds::period; + m_cycle_duration = hal::wavelength(p_settings.clock_rate) / 2; + m_polarity = p_settings.clock_polarity; + m_phase = p_settings.clock_phase; +} + +void bit_bang_spi::driver_transfer(std::span p_data_out, + std::span p_data_in, + hal::byte p_filler) +{ + size_t max_length = std::max(p_data_in.size(), p_data_out.size()); + for (size_t i = 0; i < max_length; i++) { + hal::byte write_byte = p_filler; + if (i < p_data_out.size()) { + write_byte = p_data_out[i]; + } + hal::byte read_byte = 0x00; + if (m_delay_mode == delay_mode::with) { + read_byte = transfer_byte(write_byte); + } else { + read_byte = transfer_byte_without_delay(write_byte); + } + if (i < p_data_in.size()) { + p_data_in[i] = read_byte; + } + } + m_sck->level(m_polarity); + m_copi->level(false); +} + +hal::byte bit_bang_spi::transfer_byte(hal::byte p_byte_to_write) +{ + hal::byte read_byte = 0; + for (int i = 0; i < 8; ++i) { + bool bit = (p_byte_to_write >> (7 - i)) & 0b1; + bool read_bit = transfer_bit(bit); + read_byte = (read_byte << 1) | read_bit; + } + return read_byte; +} + +hal::byte bit_bang_spi::transfer_byte_without_delay(hal::byte p_byte_to_write) +{ + hal::byte read_byte = 0; + for (int i = 0; i < 8; ++i) { + bool bit = (p_byte_to_write >> (7 - i)) & 0b1; + bool read_bit = transfer_bit_without_delay(bit); + read_byte = (read_byte << 1) | read_bit; + } + return read_byte; +} + +bool bit_bang_spi::transfer_bit(bool p_bit_to_write) +{ + m_sck->level(m_polarity); + hal::delay(*m_clock, m_cycle_duration); + if (m_phase) { + // when phase is 1 (true), data is written on the falling edge of the clock + // and data is read in on the leading edge + bool read_bit = m_cipo->level(); + m_sck->level(!m_polarity); + hal::delay(*m_clock, m_cycle_duration); + m_copi->level(p_bit_to_write); + return read_bit; + } else { + // when phase is 0 (false), data is written on the leading edge of the clock + // and data is read in on the falling edge + m_copi->level(p_bit_to_write); + m_sck->level(!m_polarity); + hal::delay(*m_clock, m_cycle_duration); + return m_cipo->level(); + } +} + +bool bit_bang_spi::transfer_bit_without_delay(bool p_bit_to_write) +{ + m_sck->level(m_polarity); + if (m_phase) { + // when phase is 1 (true), data is written on the falling edge of the clock + // and data is read in on the leading edge + bool read_bit = m_cipo->level(); + m_sck->level(!m_polarity); + m_copi->level(p_bit_to_write); + return read_bit; + } else { + // when phase is 0 (false), data is written on the leading edge of the clock + // and data is read in on the falling edge + m_copi->level(p_bit_to_write); + m_sck->level(!m_polarity); + return m_cipo->level(); + } +} + +} // namespace hal::soft