Skip to content
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

Asynchronous analog reads #161

Draft
wants to merge 29 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
[submodule "libraries/PacketSerial"]
[submodule "lib/PacketSerial"]
active = true
path = lib/PacketSerial
url = https://github.com/bakercp/PacketSerial.git
[submodule "libraries/Adafruit_MCP23008"]
url = https://github.com/adafruit/Adafruit-MCP23008-library.git
[submodule "lib/Adafruit_MCP23008"]
active = true
path = lib/Adafruit_MCP23008
url = https://github.com/adafruit/Adafruit-MCP23008-library.git
[submodule "lib/avr-libc"]
active = true
path = lib/avr-libc
url = https://github.com/avrdudes/avr-libc.git
[submodule "lib/AnalogReadAsync"]
path = lib/AnalogReadAsync
url = https://github.com/boothinator/AnalogReadAsync
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## 1.0.0 / Unreleased

* Migrate to AYAB API v6
* Remove dependency on SerialCommand library
* Add informative error codes
* Add support for garter carriage
* Add support for KH270
* Allow carriage to start on the right-hand side moving left
* Add run-time hardware tests
* Fix intermittent patterning errors
* Change end-of-line beep to be non-blocking so that knitting can continue during beep
* Migrate to generic firmware from machine-specific versions
* Migrate to semantic versioning
* Change libraries to submodules
* Remove dependency on SerialCommand library
* Add unit tests that can run in the absence of the hardware
* Add GPLv3 license
* Add informative error codes
* Add development environment documentation to README
* Add firmware update instructions to README
* Add CHANGELOG.md
Expand Down
17 changes: 10 additions & 7 deletions doc/finite_state_machine.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
### Finite State Machine

The finite state machine is defined in the `Fsm` class.
The finite state machine is defined in the `Controller` class.

| State | Action |
--: | :--
`Idle` | Wait for information on machine type.
`Init` | Wait for carriage to be put in the correct location.
`Ready` | Wait to start operation.
`Knit` | Operate in knitting mode.
Expand All @@ -14,9 +15,11 @@ A tabular representation of state transitions follows.

| Transition | Function / condition |
--: | :--
`Init -> Test` | `Tester::startTest()`
`Ready -> Test` | `Tester::startTest()`
`Test -> Init` | `Tester::quitCmd()`
`Init -> Ready` | `Knitter::isReady()`
`Ready -> Knit` | `Knitter::startKnitting()`
`Knit -> Ready` | `m_workedOnLine && m_lastLineFlag`
`Idle -> Init` | `Com::h_reqInit()`
`Init -> Ready` | `OpInit::update()`
`Ready -> Knit` | `OpKnit::startKnitting()`
`Init -> Test` | `OpTest::startTest()`
`Ready -> Test` | `OpTest::startTest()`
`Knit -> Test` | `OpTest::startTest()`
`Knit -> Init` | `m_workedOnLine && m_lastLineFlag`
`Test -> Init` | `OpTest::end()`
125 changes: 125 additions & 0 deletions init
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
test/test_encoders.cpp: encoders->init(Machine_t::Kh910);
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MIN[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getDirection(), Direction_t::Right);
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), 0x01);
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::NoCarriage);
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MIN[static_cast<int8_t>(encoders->getMachineType())] - 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getDirection(), Direction_t::Right);
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), END_RIGHT_MINUS_OFFSET[static_cast<int8_t>(encoders->getMachineType())]);
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::Knit);
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MAX[static_cast<int8_t>(encoders->getMachineType())] + 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getDirection(), Direction_t::Right);
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), END_LEFT_PLUS_OFFSET[static_cast<int8_t>(encoders->getMachineType())]);
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::Knit);
test/test_encoders.cpp: ASSERT_FALSE(encoders->getMachineType() == Machine_t::Kh270);
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::NoCarriage);
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MIN[static_cast<int8_t>(encoders->getMachineType())] - 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getDirection(), Direction_t::Right);
test/test_encoders.cpp: ASSERT_EQ(encoders->getHallActive(), Direction_t::Left);
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), END_LEFT_PLUS_OFFSET[static_cast<int8_t>(encoders->getMachineType())]);
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::Lace);
test/test_encoders.cpp: ASSERT_EQ(encoders->getBeltShift(), BeltShift::Regular);
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MIN[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: encoders->m_position = 0;
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MAX[static_cast<int8_t>(encoders->getMachineType())] + 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getDirection(), Direction_t::Right);
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), END_LEFT_PLUS_OFFSET[static_cast<int8_t>(encoders->getMachineType())]);
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::Knit);
test/test_encoders.cpp: encoders->init(Machine_t::Kh270);
test/test_encoders.cpp: ASSERT_TRUE(encoders->getMachineType() == Machine_t::Kh270);
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getDirection(), Direction_t::Right);
test/test_encoders.cpp: ASSERT_EQ(encoders->getHallActive(), Direction_t::Left);
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), END_LEFT_PLUS_OFFSET[static_cast<int8_t>(Machine_t::Kh270)]);
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::Knit);
test/test_encoders.cpp: ASSERT_EQ(encoders->getBeltShift(), BeltShift::Regular);
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MIN[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: encoders->init(Machine_t::Kh270);
test/test_encoders.cpp: ASSERT_TRUE(encoders->getMachineType() == Machine_t::Kh270);
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MAX[static_cast<int8_t>(encoders->getMachineType())] + 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getDirection(), Direction_t::Right);
test/test_encoders.cpp: ASSERT_EQ(encoders->getHallActive(), Direction_t::Left);
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), END_LEFT_PLUS_OFFSET[static_cast<int8_t>(Machine_t::Kh270)] + MAGNET_DISTANCE_270);
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::Knit);
test/test_encoders.cpp: ASSERT_EQ(encoders->getBeltShift(), BeltShift::Regular);
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MAX[static_cast<int8_t>(encoders->getMachineType())] + 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::Knit);
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MAX[static_cast<int8_t>(encoders->getMachineType())] + 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MIN[static_cast<int8_t>(encoders->getMachineType())] - 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::Garter);
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MIN[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MIN[static_cast<int8_t>(encoders->getMachineType())] - 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MIN[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: encoders->m_position = END_LEFT[static_cast<uint8_t>(encoders->getMachineType())] + 1;
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MIN[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MIN[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MAX[static_cast<int8_t>(encoders->getMachineType())] + 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getDirection(), Direction_t::Right);
test/test_encoders.cpp: ASSERT_EQ(encoders->getHallActive(), Direction_t::Right);
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), END_RIGHT_MINUS_OFFSET[static_cast<int8_t>(encoders->getMachineType())]);
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::NoCarriage);
test/test_encoders.cpp: ASSERT_EQ(encoders->getBeltShift(), BeltShift::Shifted);
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MAX[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MAX[static_cast<int8_t>(encoders->getMachineType())] + 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), END_RIGHT_MINUS_OFFSET[static_cast<int8_t>(encoders->getMachineType())]);
test/test_encoders.cpp: uint16_t pos = END_RIGHT_MINUS_OFFSET[static_cast<int8_t>(encoders->getMachineType())];
test/test_encoders.cpp: while (pos < END_RIGHT[static_cast<int8_t>(encoders->getMachineType())]) {
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MAX[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), ++pos);
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MAX[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), pos);
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), pos);
test/test_encoders.cpp: .WillOnce(Return(FILTER_L_MAX[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), pos);
test/test_encoders.cpp: ASSERT_TRUE(encoders->getMachineType() == Machine_t::Kh910);
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MIN[static_cast<int8_t>(encoders->getMachineType())] - 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getCarriage(), Carriage_t::Knit);
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MAX[static_cast<int8_t>(encoders->getMachineType())] + 1));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), END_LEFT_PLUS_OFFSET[static_cast<int8_t>(encoders->getMachineType())]);
test/test_encoders.cpp: .WillOnce(Return(FILTER_R_MAX[static_cast<int8_t>(encoders->getMachineType())]));
test/test_encoders.cpp: encoders->isr();
test/test_encoders.cpp: ASSERT_EQ(encoders->getPosition(), END_LEFT_PLUS_OFFSET[static_cast<int8_t>(encoders->getMachineType())]);
test/test_encoders.cpp: uint8_t p = encoders->getPosition();
test/test_encoders.cpp: BeltShift_t b = encoders->getBeltShift();
test/test_encoders.cpp: Direction_t d = encoders->getDirection();
test/test_encoders.cpp: Direction_t d = encoders->getHallActive();
test/test_encoders.cpp: Carriage_t c = encoders->getCarriage();
test/test_encoders.cpp: Machine_t m = encoders->getMachineType();
test/test_encoders.cpp: encoders->init(Machine_t::Kh270);
test/test_encoders.cpp: Machine_t m = encoders->getMachineType();
test/test_encoders.cpp: uint16_t v = encoders->getHallValue(Direction_t::NoDirection);
test/test_encoders.cpp: v = encoders->getHallValue(Direction_t::Left);
test/test_encoders.cpp: v = encoders->getHallValue(Direction_t::Right);
test/test_encoders.cpp: v = encoders->getHallValue(Direction_t::Right);
1 change: 1 addition & 0 deletions lib/AnalogReadAsync
Submodule AnalogReadAsync added at b97e43
4 changes: 2 additions & 2 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ default_envs = uno

[env]
framework = arduino
extra_scripts =
pre:scripts/preBuild.py
extra_scripts =
pre:scripts/preBuild.py

[env:uno]
platform = atmelavr
Expand Down
2 changes: 1 addition & 1 deletion scripts/preBuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Import("env")
print("Pre build script")

# Reads the current git tag of the repo and returns the version number
# Reads the current git tag of the repo and returns the version number
# elements
# In case there are changes since the last tag, the dirty flag is set
# In case the git tag does not match the x.y.z format, 0.0.0 is used as fallback
Expand Down
39 changes: 39 additions & 0 deletions src/ayab/analogReadAsyncWrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*!
* \file analogReadAsyncWrapper.cpp
* \brief Class containing methods to actuate a analogReadAsyncWrapper connected
* to PIEZO_PIN.
*
* This file is part of AYAB.
*
* AYAB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AYAB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AYAB. If not, see <http://www.gnu.org/licenses/>.
*
* Original Work Copyright 2013 Christian Obersteiner, Andreas Müller
* Modified Work Copyright 2020-3 Sturla Lange, Tom Price
* http://ayab-knitting.com
*/

#include "analogReadAsyncWrapper.h"

/*!
* \brief Wrapper for analogReadAsync
*/
void AnalogReadAsyncWrapper::analogReadAsyncWrapped(uint8_t pin, analogReadCompleteCallback_t cb, const void *data) {
#ifndef AYAB_TESTS
analogReadAsync(pin, cb, data);
#else
(void) pin;
(void) cb;
(void) data;
#endif
}
64 changes: 64 additions & 0 deletions src/ayab/analogReadAsyncWrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*!
* \file analogReadAsyncWrapper.h
*
* This file is part of AYAB.
*
* AYAB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AYAB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AYAB. If not, see <http://www.gnu.org/licenses/>.
*
* Original Work Copyright 2013 Christian Obersteiner, Andreas Müller
* Modified Work Copyright 2020-3 Sturla Lange, Tom Price
* http://ayab-knitting.com
*/

#ifndef ANALOGREADASYNCWRAPPER_H_
#define ANALOGREADASYNCWRAPPER_H_

#include <Arduino.h>
#include <analogReadAsync.h>

class AnalogReadAsyncWrapperInterface {
public:
virtual ~AnalogReadAsyncWrapperInterface() = default;

// any methods that need to be mocked should go here
virtual void analogReadAsyncWrapped(uint8_t pin, analogReadCompleteCallback_t cb, const void *data) = 0;
};

// Container class for the static method analogReadAsync.
// Dependency injection is enabled using a pointer to a global instance of
// either `AnalogReadAsyncWrapper` or `AnalogReadAsyncWrapperMock`,
// both of which classes implement the
// pure virtual methods of `AnalogReadAsyncWrapperInterface`.

class GlobalAnalogReadAsyncWrapper final {
private:
// singleton class so private constructor is appropriate
GlobalAnalogReadAsyncWrapper() = default;

public:
// pointer to global instance whose methods are implemented
static AnalogReadAsyncWrapperInterface *m_instance;

static void analogReadAsyncWrapped(uint8_t pin, analogReadCompleteCallback_t cb = nullptr, const void *data = nullptr);
};

/*!
* \brief Wrapper for analogReadAsync method
*/
class AnalogReadAsyncWrapper : public AnalogReadAsyncWrapperInterface {
public:
void analogReadAsyncWrapped(uint8_t pin, analogReadCompleteCallback_t cb = nullptr, const void *data = nullptr) final;
};

#endif // ANALOGREADASYNCWRAPPER_H_
58 changes: 58 additions & 0 deletions src/ayab/atomic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include <avr/io.h>
#include <avr/interrupt.h>

/* Inline assembly suggested by @jpcornil-git */

#define ENTER_CRITICAL() __asm__ __volatile__ ( \
"in __tmp_reg__, __SREG__" "\n\t" \
"cli" "\n\t" \
"push __tmp_reg__" "\n\t" \
::: "memory" \
)

#define EXIT_CRITICAL() __asm__ __volatile__ ( \
"pop __tmp_reg__" "\n\t" \
"out __SREG__, __tmp_reg__" "\n\t" \
::: "memory" \
)

/* Suggested by @jpcornil-git based on https://arduino.stackexchange.com/questions/77494/which-arduinos-support-atomic-block */

#define ATOMIC_BLOCK(type) for(type; type##_OBJECT_NAME.run(); type##_OBJECT_NAME.stop())
#define ATOMIC_RESTORESTATE_OBJECT_NAME atomicBlockRestoreState_
#define ATOMIC_RESTORESTATE AtomicBlockRestoreState ATOMIC_RESTORESTATE_OBJECT_NAME

class AtomicBlockRestoreState
{
public:
// Constructor: called when the object is created
inline AtomicBlockRestoreState()
{
sreg_save = SREG; // save status register
cli(); // turn interrupts OFF
}

// Destructor: called when the object is destroyed (ex: goes out-of-scope)
inline ~AtomicBlockRestoreState()
{
SREG = sreg_save; // restore status register
__asm__ volatile ("" ::: "memory"); // memory barrier
}

// Can we run? Returns true to run the `for` loop or
// `false` to stop it.
inline bool run()
{
return run_now;
}

// Tell the `for` loop to stop
inline void stop()
{
run_now = false;
}

private:
bool run_now = true;
uint8_t sreg_save;
};
Loading