-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🔀 Merge branch 'mmyster/feature/quad-digital-to-analog-converter' int…
…o develop
- Loading branch information
Showing
9 changed files
with
553 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,26 @@ | ||
# Leka - LekaOS | ||
# Copyright 2022 APF France handicap | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
add_library(CoreQDAC STATIC) | ||
|
||
target_include_directories(CoreQDAC | ||
PUBLIC | ||
include | ||
) | ||
|
||
target_sources(CoreQDAC | ||
PRIVATE | ||
source/CoreQDAC.cpp | ||
) | ||
|
||
target_link_libraries(CoreQDAC | ||
mbed-os | ||
CoreI2C | ||
) | ||
|
||
if(${CMAKE_PROJECT_NAME} STREQUAL "LekaOSUnitTests") | ||
leka_unit_tests_sources( | ||
tests/CoreQDAC_test.cpp | ||
) | ||
endif() |
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,51 @@ | ||
// Leka - LekaOS | ||
// Copyright 2022 APF France handicap | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#pragma once | ||
|
||
#include <array> | ||
#include <span> | ||
|
||
#include "external/MCP4728.h" | ||
#include "interface/drivers/I2C.h" | ||
#include "interface/drivers/QDAC.h" | ||
|
||
namespace leka { | ||
|
||
class CoreQDACMCP4728 : public interface::QDAC | ||
{ | ||
public: | ||
CoreQDACMCP4728(interface::I2C &i2c, uint8_t address) : _i2c(i2c), _address(address) {}; | ||
|
||
void init() final; | ||
|
||
void write(uint8_t channel, uint16_t data) final; | ||
auto read(uint8_t channel) -> uint16_t final; | ||
|
||
private: | ||
void writeInputRegisters(); | ||
void readInputRegisters(); | ||
|
||
void setVoltageReference(uint8_t data); | ||
void setPowerDown(uint8_t data); | ||
void setGain(uint8_t data); | ||
|
||
interface::I2C &_i2c; | ||
uint8_t _address; | ||
|
||
struct QDACInputData { | ||
uint8_t vref = 0x00; | ||
uint8_t pd = 0x00; | ||
uint8_t gain = 0x00; | ||
uint16_t data = 0x0000; | ||
}; | ||
|
||
std::array<QDACInputData, 4> _tx_registers {}; | ||
std::array<QDACInputData, 4> _rx_registers {}; | ||
|
||
static constexpr std::array<uint8_t, 4> _channels {mcp4728::channel::A, mcp4728::channel::B, mcp4728::channel::C, | ||
mcp4728::channel::D}; | ||
}; | ||
|
||
} // namespace leka |
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,138 @@ | ||
// Leka - LekaOS | ||
// Copyright 2022 APF France handicap | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Source: https://www.dropbox.com/home/Development/hardware/electronics/datasheets?preview=Aceltis-Flex-DAC-MCP4728.pdf | ||
|
||
#pragma once | ||
|
||
#include <cstddef> | ||
|
||
#include "cstdint" | ||
|
||
namespace leka::mcp4728 { | ||
|
||
namespace command { | ||
|
||
inline constexpr auto fast_write = uint8_t {0x00}; | ||
inline constexpr auto multi_write = uint8_t {0x40}; | ||
inline constexpr auto sequential_write = uint8_t {0x50}; | ||
inline constexpr auto single_write = uint8_t {0x58}; | ||
|
||
inline constexpr auto set_vref = uint8_t {0x80}; | ||
inline constexpr auto set_power_down = uint8_t {0xA0}; | ||
inline constexpr auto set_gain = uint8_t {0xC0}; | ||
|
||
namespace read { | ||
|
||
inline constexpr auto buffer_size = std::size_t {24}; | ||
|
||
} // namespace read | ||
|
||
} // namespace command | ||
|
||
namespace channel { | ||
|
||
inline constexpr auto A = uint8_t {0x00}; | ||
inline constexpr auto B = uint8_t {0x01}; | ||
inline constexpr auto C = uint8_t {0x02}; | ||
inline constexpr auto D = uint8_t {0x03}; | ||
|
||
} // namespace channel | ||
|
||
namespace data { | ||
|
||
namespace voltage_reference { | ||
|
||
inline constexpr auto Vdd = uint8_t {0x00}; | ||
|
||
namespace internal { | ||
|
||
namespace channel { | ||
|
||
inline constexpr auto A = uint8_t {0x08}; | ||
inline constexpr auto B = uint8_t {0x04}; | ||
inline constexpr auto C = uint8_t {0x02}; | ||
inline constexpr auto D = uint8_t {0x01}; | ||
|
||
} // namespace channel | ||
|
||
inline constexpr auto all = uint8_t {0x0f}; | ||
|
||
} // namespace internal | ||
|
||
} // namespace voltage_reference | ||
|
||
namespace power_down { | ||
|
||
inline constexpr auto normal = uint8_t {0x00}; | ||
|
||
namespace channel { | ||
|
||
namespace A { | ||
|
||
inline constexpr auto normal = uint8_t {0x00}; | ||
inline constexpr auto powerDown1K = uint8_t {0x40}; | ||
inline constexpr auto powerDown100K = uint8_t {0x80}; | ||
inline constexpr auto powerDown500K = uint8_t {0xC0}; | ||
|
||
} // namespace A | ||
|
||
namespace B { | ||
|
||
inline constexpr auto normal = uint8_t {0x00}; | ||
inline constexpr auto powerDown1K = uint8_t {0x10}; | ||
inline constexpr auto powerDown100K = uint8_t {0x20}; | ||
inline constexpr auto powerDown500K = uint8_t {0x30}; | ||
|
||
} // namespace B | ||
|
||
namespace C { | ||
|
||
inline constexpr auto normal = uint8_t {0x00}; | ||
inline constexpr auto powerDown1K = uint8_t {0x04}; | ||
inline constexpr auto powerDown100K = uint8_t {0x08}; | ||
inline constexpr auto powerDown500K = uint8_t {0x0C}; | ||
|
||
} // namespace C | ||
|
||
namespace D { | ||
|
||
inline constexpr auto normal = uint8_t {0x00}; | ||
inline constexpr auto powerDown1K = uint8_t {0x01}; | ||
inline constexpr auto powerDown100K = uint8_t {0x02}; | ||
inline constexpr auto powerDown500K = uint8_t {0x03}; | ||
|
||
} // namespace D | ||
|
||
} // namespace channel | ||
} // namespace power_down | ||
|
||
namespace gain { | ||
|
||
namespace x1 { | ||
|
||
inline constexpr auto all = uint8_t {0x00}; | ||
|
||
} | ||
|
||
namespace x2 { | ||
|
||
namespace channel { | ||
|
||
inline constexpr auto A = uint8_t {0x08}; | ||
inline constexpr auto B = uint8_t {0x04}; | ||
inline constexpr auto C = uint8_t {0x02}; | ||
inline constexpr auto D = uint8_t {0x01}; | ||
|
||
} // namespace channel | ||
|
||
inline constexpr auto all = uint8_t {0x0f}; | ||
|
||
} // namespace x2 | ||
|
||
} // namespace gain | ||
|
||
} // namespace data | ||
|
||
} // namespace leka::mcp4728 |
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,168 @@ | ||
// Leka - LekaOS | ||
// Copyright 2022 APF France handicap | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#include "CoreQDAC.h" | ||
#include <algorithm> | ||
#include <array> | ||
|
||
#include "MemoryUtils.h" | ||
|
||
using namespace leka; | ||
|
||
void CoreQDACMCP4728::init() | ||
{ | ||
setVoltageReference(mcp4728::data::voltage_reference::Vdd); | ||
setPowerDown(mcp4728::data::power_down::normal); | ||
setGain(mcp4728::data::gain::x1::all); | ||
} | ||
|
||
void CoreQDACMCP4728::write(uint8_t channel, uint16_t data) | ||
{ | ||
const auto *it = std::find(std::begin(_channels), std::end(_channels), channel); | ||
|
||
if (it != std::end(_channels)) { | ||
_tx_registers.at(channel).data = data; | ||
writeInputRegisters(); | ||
} | ||
} | ||
|
||
auto CoreQDACMCP4728::read(uint8_t channel) -> uint16_t | ||
{ | ||
if (const auto *it = std::find(std::begin(_channels), std::end(_channels), channel); it != std::end(_channels)) { | ||
readInputRegisters(); | ||
return _rx_registers.at(channel).data; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
void CoreQDACMCP4728::writeInputRegisters() | ||
{ | ||
const auto number_of_bytes_per_channel = uint8_t {3}; | ||
const auto command_size = number_of_bytes_per_channel * _channels.size(); | ||
auto command = std::array<uint8_t, command_size> {}; | ||
|
||
auto compute_first_byte_for_channel = [&command, this](auto channel) { | ||
command.at(channel * 3) = static_cast<uint8_t>(mcp4728::command::multi_write | ((0x03 & channel) << 1)); | ||
}; | ||
|
||
auto compute_second_byte_for_channel = [&command, this](auto channel) { | ||
command.at(channel * 3 + 1) = static_cast<uint8_t>( | ||
_tx_registers.at(channel).vref << 7 | _tx_registers.at(channel).pd << 5 | | ||
_tx_registers.at(channel).gain << 4 | (0x0F & utils::memory::getHighByte(_tx_registers.at(channel).data))); | ||
}; | ||
|
||
auto compute_third_byte_for_channel = [&command, this](auto channel) { | ||
command.at(channel * 3 + 2) = utils::memory::getLowByte(_tx_registers.at(channel).data); | ||
}; | ||
|
||
for (auto channel: _channels) { | ||
compute_first_byte_for_channel(channel); | ||
compute_second_byte_for_channel(channel); | ||
compute_third_byte_for_channel(channel); | ||
} | ||
|
||
_i2c.write(_address, command.data(), command.size(), false); | ||
} | ||
|
||
void CoreQDACMCP4728::setVoltageReference(uint8_t data) | ||
{ | ||
auto compute_vref_for_channel = [data](auto channel) { | ||
auto vref = 1 & (data >> (3 - channel)); | ||
return vref; | ||
}; | ||
|
||
auto set_vref_for_all_channels = [&] { | ||
for (auto channel: _channels) { | ||
auto vref = compute_vref_for_channel(channel); | ||
_tx_registers.at(channel).vref = static_cast<uint8_t>(vref); | ||
} | ||
}; | ||
|
||
set_vref_for_all_channels(); | ||
|
||
auto command = static_cast<uint8_t>(mcp4728::command::set_vref | (0x0F & data)); | ||
|
||
_i2c.write(_address, &command, 1, false); | ||
} | ||
|
||
void CoreQDACMCP4728::setPowerDown(uint8_t data) | ||
{ | ||
auto compute_power_down_for_channel = [data](auto channel) { | ||
auto power_down = 3 & (data >> (6 - 2 * channel)); | ||
return power_down; | ||
}; | ||
|
||
auto set_power_down_for_all_channels = [&] { | ||
for (auto channel: _channels) { | ||
auto power_down = compute_power_down_for_channel(channel); | ||
_tx_registers.at(channel).pd = static_cast<uint8_t>(power_down); | ||
} | ||
}; | ||
|
||
set_power_down_for_all_channels(); | ||
|
||
auto command = std::array<uint8_t, 2> {}; | ||
|
||
command.at(0) = static_cast<uint8_t>(mcp4728::command::set_power_down | (0xF0 & data) >> 4); | ||
command.at(1) = static_cast<uint8_t>((0x0F & data) << 4); | ||
|
||
_i2c.write(_address, command.data(), command.size(), false); | ||
} | ||
|
||
void CoreQDACMCP4728::setGain(uint8_t data) | ||
{ | ||
auto compute_gain_for_channel = [data](auto channel) { | ||
auto gain = 1 & (data >> (3 - channel)); | ||
return gain; | ||
}; | ||
|
||
auto set_gain_for_all_channels = [&] { | ||
for (auto channel: _channels) { | ||
auto gain = compute_gain_for_channel(channel); | ||
_tx_registers.at(channel).gain = static_cast<uint8_t>(gain); | ||
} | ||
}; | ||
set_gain_for_all_channels(); | ||
auto command = static_cast<uint8_t>(mcp4728::command::set_gain | (0x0F & data)); | ||
_i2c.write(_address, &command, 1, false); | ||
} | ||
|
||
void CoreQDACMCP4728::readInputRegisters() | ||
{ | ||
auto buffer = std::array<uint8_t, mcp4728::command::read::buffer_size> {}; | ||
|
||
_i2c.read(_address, buffer.data(), buffer.size(), false); | ||
|
||
auto compute_vref_for_channel = [&buffer](auto channel) { | ||
auto vref = (buffer.at(channel * 6 + 1) & 0x80) >> 7; | ||
return vref; | ||
}; | ||
|
||
auto compute_power_down_for_channel = [&buffer](auto channel) { | ||
auto power_down = (buffer.at(channel * 6 + 1) & 0x60) >> 5; | ||
return power_down; | ||
}; | ||
|
||
auto compute_gain_for_channel = [&buffer](auto channel) { | ||
auto gain = (buffer.at(channel * 6 + 1) & 0x10) >> 4; | ||
return gain; | ||
}; | ||
|
||
auto compute_data_for_channel = [&buffer](auto channel) { | ||
auto data = ((buffer.at(channel * 6 + 1) & 0x0F) << 8) | buffer.at(channel * 6 + 2); | ||
return data; | ||
}; | ||
|
||
auto set_input_data_for_all_channels = [&] { | ||
for (auto channel: _channels) { | ||
_rx_registers.at(channel).vref = static_cast<uint8_t>(compute_vref_for_channel(channel)); | ||
_rx_registers.at(channel).pd = static_cast<uint8_t>(compute_power_down_for_channel(channel)); | ||
_rx_registers.at(channel).gain = static_cast<uint8_t>(compute_gain_for_channel(channel)); | ||
_rx_registers.at(channel).data = static_cast<uint16_t>(compute_data_for_channel(channel)); | ||
} | ||
}; | ||
|
||
set_input_data_for_all_channels(); | ||
} |
Oops, something went wrong.