forked from libhal-google/libhal-soft
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* ✨ Add bit_bang_spi * Remove unnecessary include Resolves #17
- Loading branch information
1 parent
f1c93bd
commit 534fa4e
Showing
3 changed files
with
253 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |