Skip to content

Commit

Permalink
✨ Add bit_bang_spi (#39)
Browse files Browse the repository at this point in the history
* ✨ Add bit_bang_spi
* Remove unnecessary include

Resolves #17
  • Loading branch information
MaliaLabor authored Aug 27, 2024
1 parent f1c93bd commit 534fa4e
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
127 changes: 127 additions & 0 deletions include/libhal-soft/bit_bang_spi.hpp
Original file line number Diff line number Diff line change
@@ -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 <libhal/input_pin.hpp>
#include <libhal/output_pin.hpp>
#include <libhal/spi.hpp>
#include <libhal/steady_clock.hpp>

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<hal::byte const> p_data_out,
std::span<hal::byte> 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
125 changes: 125 additions & 0 deletions src/bit_bang_spi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#include <chrono>

#include <libhal-soft/bit_bang_spi.hpp>
#include <libhal-util/bit.hpp>
#include <libhal-util/steady_clock.hpp>
#include <libhal/steady_clock.hpp>
#include <libhal/units.hpp>

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<period>(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<hal::byte const> p_data_out,
std::span<hal::byte> 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

0 comments on commit 534fa4e

Please sign in to comment.