-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
mmyster/feature/quad digital to analog converter #933
mmyster/feature/quad digital to analog converter #933
Conversation
MMyster
commented
Jul 10, 2022
- ✨ (interface): Add QDAC
- ✨ (drivers): Add CoreQDAC
Codecov Report
@@ Coverage Diff @@
## develop #933 +/- ##
===========================================
+ Coverage 95.75% 95.91% +0.15%
===========================================
Files 123 125 +2
Lines 2826 2936 +110
===========================================
+ Hits 2706 2816 +110
Misses 120 120
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
File comparision analysis report🔖 Info
Click to show memory sections
📝 SummaryClick to show summary
🗺️ Map files diff outputClick to show diff listNo differenes where found in map files. |
File comparision analysis report🔖 Info
Click to show memory sections
📝 SummaryClick to show summary
🗺️ Map files diff outputClick to show diff listNo differenes where found in map files. |
fe28ff7
to
13ba488
Compare
13ba488
to
ac18117
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
premier passage 👍
drivers/CoreQDAC/source/CoreQDAC.cpp
Outdated
selectGain(mcp4728::data::gain::x1::all); | ||
} | ||
|
||
void CoreQDACMCP4728::write(uint8_t channel, uint16_t data, bool b_eep) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ça veut dire quoi le bool b_eep
? ça correspond à une condition de quoi? dans quels cas on le veut true
? dans quels cas on le veut false
?
channel c'est A, B, C ou D
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true
: écriture dans les inputs registers + sauvegarde dans l'EEPROM Memory
false
: écriture dans les inputs registers seulement
Channel c'est bien ça
drivers/CoreQDAC/include/CoreQDAC.h
Outdated
void fastWrite(); | ||
void multiWrite(); | ||
void sequentialWrite(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
je trouve ça bizarre d'avoir des write
sans argument de data
et quelles sont les différences entre fast, multi et sequential?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fastWrite : écriture séquentielle vers les inputs registers
multiWrite : comme fastWrite mais avec possibilité de définir le ou les channels
sequential : écriture séquentielle vers les inputs registers + EEPROM Memory à partir d'un channel défini
single : écriture "unique" vers un input register + EEPROM Memory
Voir https://www.dropbox.com/home/Development/hardware/electronics/datasheets?preview=Aceltis-Flex-DAC-MCP4728.pdf
Page 34
Les data
, ce sont les tableaux en variables privées
00972cd
to
e6ca578
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C'est bon pour moi en l'état, Bravo! ✅
Il me reste le spike à valider et tout ce qui sera QDAC sera bon selon moi :)
include/interface/drivers/QDAC.h
Outdated
virtual void write(Channel channel, uint16_t data, bool eeprom = false) = 0; | ||
virtual auto read(Channel channel, bool eeprom = false) -> uint16_t = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merci de confirmer si c'est bien ces actions qui en découle selon la valeur booléenne~
virtual void write(Channel channel, uint16_t data, bool eeprom = false) = 0; | |
virtual auto read(Channel channel, bool eeprom = false) -> uint16_t = 0; | |
virtual void write(Channel channel, uint16_t data, bool save_in_eeprom = false) = 0; | |
virtual auto read(Channel channel, bool load_from_eeprom = false) -> uint16_t = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MMyster tu n'as pas répondu ici
include/interface/drivers/QDAC.h
Outdated
virtual ~QDAC() = default; | ||
|
||
virtual void init() = 0; | ||
virtual void write(Channel channel, uint16_t data, bool eeprom = false) = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Un commentaire aussi pour la notion d'EEPROM pour tous les QDAC: est-ce qu'ils en possèdent tous un?
Mon opinion est que non, et que celui qu'on a cet avantage de posséder une mémoire en plus.
Dans l'immédiat, ça ne me gêne pas de le laisser, on pourra y revenir une fois que tout le TouchKit sera fonctionnel~
e6ca578
to
ed3cfbb
Compare
ed3cfbb
to
c8e19a2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
merci @MMyster! C'est pas mal du tout!
quelques retours sur la forme et changements pour rendre les choses plus lisibles et compréhensible pour les gens qui ne savent pas ce qui se passe
include/interface/drivers/QDAC.h
Outdated
enum class Channel : uint8_t | ||
{ | ||
A, | ||
B, | ||
C, | ||
D | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ici c'est bizarre d'avoir un enum class Channel
dans namespace leka
directement.
faut plutôt que tu la mettes dans l'interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
et comme je dis dans l'autre commentaire, en fait pas besoin, donc ça peut même aller dans l'implémentation directement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
et d'autre part, c'est bien de donner la première valeur, voir même toutes, pour ne pas avoir de UB ou de bug difficile à déceler dans le futur:
enum class Channel : uint8_t | |
{ | |
A, | |
B, | |
C, | |
D | |
}; | |
enum class Channel : uint8_t | |
{ | |
A = 0, | |
B = 1, | |
C = 2, | |
D = 3 | |
}; |
include/interface/drivers/QDAC.h
Outdated
virtual void write(Channel channel, uint16_t data, bool eeprom = false) = 0; | ||
virtual auto read(Channel channel, bool eeprom = false) -> uint16_t = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MMyster tu n'as pas répondu ici
drivers/CoreQDAC/source/CoreQDAC.cpp
Outdated
command.at(0) = static_cast<uint8_t>(mcp4728::command::multi_write | ((0x03 & channel) << 1)); | ||
command.at(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))); | ||
command.at(2) = utils::memory::getLowByte(_tx_registers.at(channel).data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pareil ici, les lambdas vont simplifier la compréhension pour comprendre:
- ce qu'on calcule pour
0
- ce qu'on calcule pour
1
- ce qu'on calcule pour
2
drivers/CoreQDAC/source/CoreQDAC.cpp
Outdated
auto command = std::array<uint8_t, 9> {}; | ||
command.at(0) = static_cast<uint8_t>(mcp4728::command::sequential_write | ((0x03 & starting_channel) << 1)); | ||
for (uint8_t ch = starting_channel; ch <= mcp4728::channel::D; ch++) { | ||
command.at((ch - starting_channel) * 2 + 1) = | ||
static_cast<uint8_t>(_tx_eeprom.at(ch).vref << 7 | _tx_eeprom.at(ch).pd << 5 | _tx_eeprom.at(ch).gain << 4 | | ||
(0x0F & utils::memory::getHighByte(_tx_eeprom.at(ch).data))); | ||
command.at((ch - starting_channel) * 2 + 2) = utils::memory::getLowByte(_tx_eeprom.at(ch).data); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pareil ici
même le loop il pourrait être dans un lambda:
auto set_all_channels_values = []{
// ...
};
par exemple
drivers/CoreQDAC/source/CoreQDAC.cpp
Outdated
command.at(0) = static_cast<uint8_t>(mcp4728::command::single_write | ((0x03 & channel) << 1)); | ||
command.at(1) = static_cast<uint8_t>(_tx_eeprom.at(channel).vref << 7 | _tx_eeprom.at(channel).pd << 5 | | ||
_tx_eeprom.at(channel).gain << 4 | | ||
(0x0F & utils::memory::getHighByte(_tx_eeprom.at(channel).data))); | ||
command.at(2) = utils::memory::getLowByte(_tx_eeprom.at(channel).data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pareil ici
drivers/CoreQDAC/source/CoreQDAC.cpp
Outdated
for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) { | ||
_tx_registers.at(ch).vref = 1 & (data >> (3 - ch)); | ||
_tx_eeprom.at(ch).vref = 1 & (data >> (3 - ch)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ici un peu pareil, un petit lambda c'est plus clair pour comprendre la magie
for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) { | |
_tx_registers.at(ch).vref = 1 & (data >> (3 - ch)); | |
_tx_eeprom.at(ch).vref = 1 & (data >> (3 - ch)); | |
} | |
auto compute_vref_for_channel = [data](auto ch) { | |
auto vref = 1 & (data >> (3 - ch)); | |
return vref; | |
}; | |
auto set_vref_for_all_channels = [data] { | |
for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) { | |
auto vref = compute_vref_for_channel(ch); | |
_tx_registers.at(ch).vref = vref; | |
_tx_eeprom.at(ch).vref = vref; | |
} | |
}; |
et là ça rend les choses plus claires parce que ça cache les détails d'implémentation dans un lambda qui me dit ce qu'il fait.
drivers/CoreQDAC/source/CoreQDAC.cpp
Outdated
for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) { | ||
_tx_registers.at(ch).pd = 3 & (data >> (6 - 2 * ch)); | ||
_tx_eeprom.at(ch).pd = 3 & (data >> (6 - 2 * ch)); | ||
} | ||
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pareil ici
aff29a3
to
eb90226
Compare
Patch containing 2022_08_18-QDAC-Patch.diff.zip diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt
index 26e1b4db..f3154b56 100644
--- a/drivers/CMakeLists.txt
+++ b/drivers/CMakeLists.txt
@@ -39,3 +39,6 @@ add_subdirectory(${DRIVERS_DIR}/CoreMotor)
# Touch drivers
add_subdirectory(${DRIVERS_DIR}/CoreIOExpander)
+add_subdirectory(${DRIVERS_DIR}/CoreQDAC)
+
+
diff --git a/drivers/CoreQDAC/CMakeLists.txt b/drivers/CoreQDAC/CMakeLists.txt
new file mode 100644
index 00000000..a2aa8949
--- /dev/null
+++ b/drivers/CoreQDAC/CMakeLists.txt
@@ -0,0 +1,28 @@
+# 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()
diff --git a/drivers/CoreQDAC/include/CoreQDAC.h b/drivers/CoreQDAC/include/CoreQDAC.h
new file mode 100644
index 00000000..b4046d07
--- /dev/null
+++ b/drivers/CoreQDAC/include/CoreQDAC.h
@@ -0,0 +1,55 @@
+// 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(Channel channel, uint16_t data, bool eeprom = false) final;
+ void writeAllChannels(uint16_t data, bool eeprom = false);
+
+ auto read(Channel channel, bool eeprom = false) -> uint16_t final;
+
+ private:
+ void fastWrite();
+ void multiWrite(uint8_t channel);
+ void sequentialWrite(uint8_t starting_channel);
+ void singleWrite(uint8_t channel);
+
+ void setVoltageReference(uint8_t data);
+ void setPowerDown(uint8_t data);
+ void setGain(uint8_t data);
+
+ void readInputRegistersAndMemory();
+
+ 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> _tx_eeprom {};
+ std::array<QDACInputData, 4> _rx_registers {};
+ std::array<QDACInputData, 4> _rx_eeprom {};
+};
+
+} // namespace leka
diff --git a/drivers/CoreQDAC/include/external/MCP4728.h b/drivers/CoreQDAC/include/external/MCP4728.h
new file mode 100644
index 00000000..493fd4ea
--- /dev/null
+++ b/drivers/CoreQDAC/include/external/MCP4728.h
@@ -0,0 +1,114 @@
+// 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
diff --git a/drivers/CoreQDAC/source/CoreQDAC.cpp b/drivers/CoreQDAC/source/CoreQDAC.cpp
new file mode 100644
index 00000000..2547d602
--- /dev/null
+++ b/drivers/CoreQDAC/source/CoreQDAC.cpp
@@ -0,0 +1,153 @@
+// Leka - LekaOS
+// Copyright 2022 APF France handicap
+// SPDX-License-Identifier: Apache-2.0
+
+#include "CoreQDAC.h"
+#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(Channel channel, uint16_t data, bool eeprom)
+{
+ auto ch = static_cast<uint8_t>(channel);
+ if (eeprom) {
+ _tx_registers.at(ch).data = data;
+ _tx_eeprom.at(ch).data = data;
+ singleWrite(ch);
+ } else {
+ _tx_registers.at(ch).data = data;
+ multiWrite(ch);
+ }
+}
+
+void CoreQDACMCP4728::writeAllChannels(uint16_t data, bool eeprom)
+{
+ if (eeprom) {
+ for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) {
+ _tx_registers.at(ch).data = data;
+ _tx_eeprom.at(ch).data = data;
+ }
+ sequentialWrite(mcp4728::channel::A);
+ } else {
+ for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) {
+ _tx_registers.at(ch).data = data;
+ }
+ fastWrite();
+ }
+}
+
+auto CoreQDACMCP4728::read(Channel channel, bool eeprom) -> uint16_t
+{
+ auto ch = static_cast<uint8_t>(channel);
+ readInputRegistersAndMemory();
+ return eeprom ? _rx_eeprom.at(ch).data : _rx_registers.at(ch).data;
+}
+
+void CoreQDACMCP4728::fastWrite()
+{
+ auto command = std::array<uint8_t, 8> {};
+ for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) {
+ command.at(ch * 2) = static_cast<uint8_t>(mcp4728::command::fast_write | _tx_registers.at(ch).pd << 4 |
+ (0x0F & utils::memory::getHighByte(_tx_registers.at(ch).data)));
+ command.at(ch * 2 + 1) = utils::memory::getLowByte(_tx_registers.at(ch).data);
+ }
+ _i2c.write(_address, command.data(), command.size(), false);
+}
+
+void CoreQDACMCP4728::multiWrite(uint8_t channel)
+{
+ auto command = std::array<uint8_t, 3> {};
+ command.at(0) = static_cast<uint8_t>(mcp4728::command::multi_write | ((0x03 & channel) << 1));
+ command.at(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)));
+ command.at(2) = utils::memory::getLowByte(_tx_registers.at(channel).data);
+ _i2c.write(_address, command.data(), command.size(), false);
+}
+
+void CoreQDACMCP4728::sequentialWrite(uint8_t starting_channel)
+{
+ auto command = std::array<uint8_t, 9> {};
+ command.at(0) = static_cast<uint8_t>(mcp4728::command::sequential_write | ((0x03 & starting_channel) << 1));
+ for (uint8_t ch = starting_channel; ch <= mcp4728::channel::D; ch++) {
+ command.at((ch - starting_channel) * 2 + 1) =
+ static_cast<uint8_t>(_tx_eeprom.at(ch).vref << 7 | _tx_eeprom.at(ch).pd << 5 | _tx_eeprom.at(ch).gain << 4 |
+ (0x0F & utils::memory::getHighByte(_tx_eeprom.at(ch).data)));
+ command.at((ch - starting_channel) * 2 + 2) = utils::memory::getLowByte(_tx_eeprom.at(ch).data);
+ }
+ _i2c.write(_address, command.data(), command.size(), false);
+}
+
+void CoreQDACMCP4728::singleWrite(uint8_t channel)
+{
+ auto command = std::array<uint8_t, 3> {};
+ command.at(0) = static_cast<uint8_t>(mcp4728::command::single_write | ((0x03 & channel) << 1));
+ command.at(1) = static_cast<uint8_t>(_tx_eeprom.at(channel).vref << 7 | _tx_eeprom.at(channel).pd << 5 |
+ _tx_eeprom.at(channel).gain << 4 |
+ (0x0F & utils::memory::getHighByte(_tx_eeprom.at(channel).data)));
+ command.at(2) = utils::memory::getLowByte(_tx_eeprom.at(channel).data);
+ _i2c.write(_address, command.data(), command.size(), false);
+}
+
+void CoreQDACMCP4728::setVoltageReference(uint8_t data)
+{
+ for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) {
+ _tx_registers.at(ch).vref = 1 & (data >> (3 - ch));
+ _tx_eeprom.at(ch).vref = 1 & (data >> (3 - ch));
+ }
+ auto command = static_cast<uint8_t>(mcp4728::command::set_vref | (0x0F & data));
+ _i2c.write(_address, &command, 1, false);
+}
+
+void CoreQDACMCP4728::setPowerDown(uint8_t data)
+{
+ for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) {
+ _tx_registers.at(ch).pd = 3 & (data >> (6 - 2 * ch));
+ _tx_eeprom.at(ch).pd = 3 & (data >> (6 - 2 * ch));
+ }
+ 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)
+{
+ for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) {
+ _tx_registers.at(ch).gain = 1 & (data >> (3 - ch));
+ _tx_eeprom.at(ch).gain = 1 & (data >> (3 - ch));
+ }
+ auto command = static_cast<uint8_t>(mcp4728::command::set_gain | (0x0F & data));
+ _i2c.write(_address, &command, 1, false);
+}
+
+void CoreQDACMCP4728::readInputRegistersAndMemory()
+{
+ auto buffer = std::array<uint8_t, mcp4728::command::read::buffer_size> {};
+
+ _i2c.read(_address, buffer.data(), buffer.size(), false);
+
+ for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) {
+ _rx_registers.at(ch).vref = static_cast<uint8_t>((buffer.at(ch * 6 + 1) & 0b10000000) >> 7);
+ _rx_registers.at(ch).pd = static_cast<uint8_t>((buffer.at(ch * 6 + 1) & 0b01100000) >> 5);
+ _rx_registers.at(ch).gain = static_cast<uint8_t>((buffer.at(ch * 6 + 1) & 0b00010000) >> 4);
+ _rx_registers.at(ch).data =
+ static_cast<uint16_t>(((buffer.at(ch * 6 + 1) & 0b00001111) << 8) | buffer.at(ch * 6 + 2));
+
+ _rx_eeprom.at(ch).vref = static_cast<uint8_t>((buffer.at(ch * 6 + 4) & 0b10000000) >> 7);
+ _rx_eeprom.at(ch).pd = static_cast<uint8_t>((buffer.at(ch * 6 + 4) & 0b01100000) >> 5);
+ _rx_eeprom.at(ch).gain = static_cast<uint8_t>((buffer.at(ch * 6 + 4) & 0b00010000) >> 4);
+ _rx_eeprom.at(ch).data =
+ static_cast<uint16_t>(((buffer.at(ch * 6 + 4) & 0b00001111) << 8) | buffer.at(ch * 6 + 5));
+ }
+}
diff --git a/drivers/CoreQDAC/tests/CoreQDAC_test.cpp b/drivers/CoreQDAC/tests/CoreQDAC_test.cpp
new file mode 100644
index 00000000..c3d895be
--- /dev/null
+++ b/drivers/CoreQDAC/tests/CoreQDAC_test.cpp
@@ -0,0 +1,154 @@
+// Leka - LekaOS
+// Copyright 2022 APF France handicap
+// SPDX-License-Identifier: Apache-2.0
+
+#include "CoreQDAC.h"
+
+#include "MemoryUtils.h"
+#include "external/MCP4728.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "mocks/leka/CoreI2C.h"
+
+using namespace leka;
+
+using ::testing::Args;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::ElementsAreArray;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::SetArrayArgument;
+
+class CoreQDACTest : public ::testing::Test
+
+{
+ protected:
+ // void SetUp() override {}
+ // void TearDown() override {}
+
+ const uint8_t i2c_address {0xC0};
+ mock::CoreI2C mocki2c;
+ CoreQDACMCP4728 dac {mocki2c, i2c_address};
+};
+
+TEST_F(CoreQDACTest, initializationDefault)
+{
+ auto new_dac = CoreQDACMCP4728 {mocki2c, i2c_address};
+ ASSERT_NE(&new_dac, nullptr);
+}
+
+TEST_F(CoreQDACTest, init)
+{
+ const auto expected_vref = static_cast<uint8_t>(mcp4728::command::set_vref | 0x00);
+ const auto expected_buffer_vref = ElementsAre(expected_vref);
+ EXPECT_CALL(mocki2c, write).With(Args<1, 2>(expected_buffer_vref));
+
+ const auto expected_pd_first_byte = static_cast<uint8_t>(mcp4728::command::set_power_down | 0x00);
+ const auto expected_pd_second_byte = uint8_t {0x00};
+ const auto expected_buffer_power_down = ElementsAre(expected_pd_first_byte, expected_pd_second_byte);
+ EXPECT_CALL(mocki2c, write).With(Args<1, 2>(expected_buffer_power_down));
+
+ const auto expected_gain = static_cast<uint8_t>(mcp4728::command::set_gain | 0x00);
+ const auto expected_buffer_gain = ElementsAre(expected_gain);
+ EXPECT_CALL(mocki2c, write).With(Args<1, 2>(expected_buffer_gain));
+
+ dac.init();
+}
+
+TEST_F(CoreQDACTest, write)
+{
+ auto value_to_write = uint16_t {0x0ABC};
+
+ auto command = std::array<uint8_t, 3> {};
+
+ command.at(0) = static_cast<uint8_t>(mcp4728::command::multi_write | mcp4728::channel::B << 1);
+ command.at(1) = 0x00 | (0x0F & utils::memory::getHighByte(value_to_write));
+ command.at(2) = utils::memory::getLowByte(value_to_write);
+ const auto expected_buffer = ElementsAreArray(command);
+
+ EXPECT_CALL(mocki2c, write).With(Args<1, 2>(expected_buffer)).Times(1);
+
+ dac.write(leka::Channel::B, value_to_write);
+}
+
+TEST_F(CoreQDACTest, writeMemory)
+{
+ auto value_to_write = uint16_t {0x0ABC};
+
+ auto command = std::array<uint8_t, 3> {};
+
+ command.at(0) = static_cast<uint8_t>(mcp4728::command::single_write | mcp4728::channel::B << 1);
+ command.at(1) = 0x00 | (0x0F & utils::memory::getHighByte(value_to_write));
+ command.at(2) = utils::memory::getLowByte(value_to_write);
+ const auto expected_buffer = ElementsAreArray(command);
+
+ EXPECT_CALL(mocki2c, write).With(Args<1, 2>(expected_buffer));
+
+ dac.write(leka::Channel::B, value_to_write, true);
+}
+
+TEST_F(CoreQDACTest, writeAllChannels)
+{
+ auto value_to_write = uint16_t {0x0ABC};
+
+ auto command = std::array<uint8_t, 8> {};
+
+ for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) {
+ command.at(ch * 2) =
+ static_cast<uint8_t>(mcp4728::command::fast_write | (0x0F & utils::memory::getHighByte(value_to_write)));
+ command.at(ch * 2 + 1) = utils::memory::getLowByte(value_to_write);
+ }
+
+ const auto expected_buffer = ElementsAreArray(command);
+
+ EXPECT_CALL(mocki2c, write).With(Args<1, 2>(expected_buffer)).Times(1);
+
+ dac.writeAllChannels(value_to_write);
+}
+
+TEST_F(CoreQDACTest, writeAllChannelsMemory)
+{
+ auto value_to_write = uint16_t {0x0ABC};
+
+ auto command = std::array<uint8_t, 9> {};
+
+ command.at(0) = static_cast<uint8_t>(mcp4728::command::sequential_write | mcp4728::channel::A << 1);
+
+ for (uint8_t ch = mcp4728::channel::A; ch <= mcp4728::channel::D; ch++) {
+ command.at((ch - mcp4728::channel::A) * 2 + 1) = 0x00 | (0x0F & utils::memory::getHighByte(value_to_write));
+ command.at((ch - mcp4728::channel::A) * 2 + 2) = utils::memory::getLowByte(value_to_write);
+ }
+
+ const auto expected_buffer = ElementsAreArray(command);
+
+ EXPECT_CALL(mocki2c, write).With(Args<1, 2>(expected_buffer)).Times(1);
+
+ dac.writeAllChannels(value_to_write, true);
+}
+
+TEST_F(CoreQDACTest, read)
+{
+ auto expected_buffer = std::array<uint8_t, 24> {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
+ auto expected_data = static_cast<uint16_t>(((expected_buffer.at(1) & 0x0F) << 8) | expected_buffer.at(2));
+
+ EXPECT_CALL(mocki2c, read)
+ .WillOnce(DoAll(SetArrayArgument<1>(begin(expected_buffer), end(expected_buffer)), Return(0)));
+
+ auto data = dac.read(leka::Channel::A);
+
+ ASSERT_EQ(expected_data, data);
+}
+
+TEST_F(CoreQDACTest, readMemory)
+{
+ auto expected_buffer = std::array<uint8_t, 24> {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
+ auto expected_data = static_cast<uint16_t>(((expected_buffer.at(4) & 0x0F) << 8) | expected_buffer.at(5));
+
+ EXPECT_CALL(mocki2c, read)
+ .WillOnce(DoAll(SetArrayArgument<1>(begin(expected_buffer), end(expected_buffer)), Return(0)));
+
+ auto data = dac.read(leka::Channel::A, true);
+
+ ASSERT_EQ(expected_data, data);
+}
diff --git a/include/interface/drivers/QDAC.h b/include/interface/drivers/QDAC.h
new file mode 100644
index 00000000..dcbfb881
--- /dev/null
+++ b/include/interface/drivers/QDAC.h
@@ -0,0 +1,22 @@
+// Leka - LekaOS
+// Copyright 2022 APF France handicap
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <span>
+
+namespace leka::interface {
+class QDAC
+{
+ public:
+ virtual ~QDAC() = default;
+
+ virtual void init() = 0;
+ virtual void write(uint8_t channel, uint16_t data) = 0;
+ virtual auto read(uint8_t channel) -> uint16_t = 0;
+};
+
+} // namespace leka::interface
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index e8447fbf..413fafea 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -244,6 +244,7 @@ add_subdirectory(template)
# Register drivers
leka_register_unit_tests_for_driver(CoreBattery)
leka_register_unit_tests_for_driver(CoreBufferedSerial)
+leka_register_unit_tests_for_driver(CoreQDAC)
leka_register_unit_tests_for_driver(CoreEventFlags)
leka_register_unit_tests_for_driver(CoreEventQueue)
leka_register_unit_tests_for_driver(CoreFlashMemory)
diff --git a/tests/unit/mocks/mocks/leka/CoreQDAC.h b/tests/unit/mocks/mocks/leka/CoreQDAC.h
new file mode 100644
index 00000000..280894b3
--- /dev/null
+++ b/tests/unit/mocks/mocks/leka/CoreQDAC.h
@@ -0,0 +1,20 @@
+// Leka - LekaOS
+// Copyright 2022 APF France handicap
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include "gmock/gmock.h"
+#include "interface/drivers/QDAC.h"
+
+namespace leka::mock {
+
+class CoreQDAC : public interface::QDAC
+{
+ public:
+ MOCK_METHOD(void, init, (), (override));
+ MOCK_METHOD(void, write, (Channel, uint16_t, bool), (override));
+ MOCK_METHOD(uint16_t, read, (Channel, bool), (override));
+};
+
+} // namespace leka::mock |
adfa308
to
cfeffdf
Compare
cfeffdf
to
0109d91
Compare
Kudos, SonarCloud Quality Gate passed! |