Skip to content

Commit

Permalink
🔀 Merge branch 'mmyster/feature/quad-digital-to-analog-converter' int…
Browse files Browse the repository at this point in the history
…o develop
  • Loading branch information
ladislas committed Aug 18, 2022
2 parents b45ac7d + 0933a53 commit 4ba2fee
Show file tree
Hide file tree
Showing 9 changed files with 553 additions and 0 deletions.
3 changes: 3 additions & 0 deletions drivers/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ add_subdirectory(${DRIVERS_DIR}/CoreMotor)

# Touch drivers
add_subdirectory(${DRIVERS_DIR}/CoreIOExpander)
add_subdirectory(${DRIVERS_DIR}/CoreQDAC)


26 changes: 26 additions & 0 deletions drivers/CoreQDAC/CMakeLists.txt
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()
51 changes: 51 additions & 0 deletions drivers/CoreQDAC/include/CoreQDAC.h
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
138 changes: 138 additions & 0 deletions drivers/CoreQDAC/include/external/MCP4728.h
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
168 changes: 168 additions & 0 deletions drivers/CoreQDAC/source/CoreQDAC.cpp
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();
}
Loading

0 comments on commit 4ba2fee

Please sign in to comment.