Browse Source

feat: add Arduino firmware files

master
Dirk Grappendorf 2 years ago
parent
commit
b738a4990d
21 changed files with 2208 additions and 0 deletions
  1. +3
    -0
      smarkant-arduino/.gitignore
  2. +127
    -0
      smarkant-arduino/lib/Bounce2/Bounce2.cpp
  3. +88
    -0
      smarkant-arduino/lib/Bounce2/Bounce2.h
  4. +32
    -0
      smarkant-arduino/lib/LinProcessor/avr_util.cpp
  5. +45
    -0
      smarkant-arduino/lib/LinProcessor/avr_util.h
  6. +34
    -0
      smarkant-arduino/lib/LinProcessor/custom_defs.h
  7. +41
    -0
      smarkant-arduino/lib/LinProcessor/hardware_clock.cpp
  8. +55
    -0
      smarkant-arduino/lib/LinProcessor/hardware_clock.h
  9. +108
    -0
      smarkant-arduino/lib/LinProcessor/io_pins.h
  10. +107
    -0
      smarkant-arduino/lib/LinProcessor/lin_frame.cpp
  11. +78
    -0
      smarkant-arduino/lib/LinProcessor/lin_frame.h
  12. +618
    -0
      smarkant-arduino/lib/LinProcessor/lin_processor.cpp
  13. +55
    -0
      smarkant-arduino/lib/LinProcessor/lin_processor.h
  14. +44
    -0
      smarkant-arduino/lib/LinProcessor/passive_timer.h
  15. +165
    -0
      smarkant-arduino/lib/LinProcessor/sio.cpp
  16. +50
    -0
      smarkant-arduino/lib/LinProcessor/sio.h
  17. +53
    -0
      smarkant-arduino/lib/LinProcessor/system_clock.cpp
  18. +33
    -0
      smarkant-arduino/lib/LinProcessor/system_clock.h
  19. +38
    -0
      smarkant-arduino/lib/readme.txt
  20. +24
    -0
      smarkant-arduino/platformio.ini
  21. +410
    -0
      smarkant-arduino/src/main.cpp

+ 3
- 0
smarkant-arduino/.gitignore View File

@@ -0,0 +1,3 @@
.pioenvs
.clang_complete
.gcc-flags.json

+ 127
- 0
smarkant-arduino/lib/Bounce2/Bounce2.cpp View File

@@ -0,0 +1,127 @@
// Please read Bounce2.h for information about the liscence and authors

#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include "Bounce2.h"

#define DEBOUNCED_STATE 0
#define UNSTABLE_STATE 1
#define STATE_CHANGED 3


Bounce::Bounce()
: previous_millis(0)
, interval_millis(10)
, state(0)
, pin(0)
{}

void Bounce::attach(int pin) {
this->pin = pin;
state = 0;
if (digitalRead(pin)) {
state = _BV(DEBOUNCED_STATE) | _BV(UNSTABLE_STATE);
}
#ifdef BOUNCE_LOCK_OUT
previous_millis = 0;
#else
previous_millis = millis();
#endif
}

void Bounce::attach(int pin, int mode){
pinMode(pin, mode);
this->attach(pin);
}

void Bounce::interval(uint16_t interval_millis)
{
this->interval_millis = interval_millis;
}

bool Bounce::update()
{
#ifdef BOUNCE_LOCK_OUT
state &= ~_BV(STATE_CHANGED);
// Ignore everything if we are locked out
if (millis() - previous_millis >= interval_millis) {
bool currentState = digitalRead(pin);
if ((bool)(state & _BV(DEBOUNCED_STATE)) != currentState) {
previous_millis = millis();
state ^= _BV(DEBOUNCED_STATE);
state |= _BV(STATE_CHANGED);
}
}
return state & _BV(STATE_CHANGED);

#elif defined BOUNCE_WITH_PROMPT_DETECTION
// Read the state of the switch port into a temporary variable.
bool readState = digitalRead(pin);

// Clear Changed State Flag - will be reset if we confirm a button state change.
state &= ~_BV(STATE_CHANGED);

if ( readState != (bool)(state & _BV(DEBOUNCED_STATE))) {
// We have seen a change from the current button state.

if ( millis() - previous_millis >= interval_millis ) {
// We have passed the time threshold, so a new change of state is allowed.
// set the STATE_CHANGED flag and the new DEBOUNCED_STATE.
// This will be prompt as long as there has been greater than interval_misllis ms since last change of input.
// Otherwise debounced state will not change again until bouncing is stable for the timeout period.
state ^= _BV(DEBOUNCED_STATE);
state |= _BV(STATE_CHANGED);
}
}

// If the readState is different from previous readState, reset the debounce timer - as input is still unstable
// and we want to prevent new button state changes until the previous one has remained stable for the timeout.
if ( readState != (bool)(state & _BV(UNSTABLE_STATE)) ) {
// Update Unstable Bit to macth readState
state ^= _BV(UNSTABLE_STATE);
previous_millis = millis();
}
// return just the sate changed bit
return state & _BV(STATE_CHANGED);
#else
// Read the state of the switch in a temporary variable.
bool currentState = digitalRead(pin);
state &= ~_BV(STATE_CHANGED);

// If the reading is different from last reading, reset the debounce counter
if ( currentState != (bool)(state & _BV(UNSTABLE_STATE)) ) {
previous_millis = millis();
state ^= _BV(UNSTABLE_STATE);
} else
if ( millis() - previous_millis >= interval_millis ) {
// We have passed the threshold time, so the input is now stable
// If it is different from last state, set the STATE_CHANGED flag
if ((bool)(state & _BV(DEBOUNCED_STATE)) != currentState) {
previous_millis = millis();
state ^= _BV(DEBOUNCED_STATE);
state |= _BV(STATE_CHANGED);
}
}

return state & _BV(STATE_CHANGED);
#endif
}

bool Bounce::read()
{
return state & _BV(DEBOUNCED_STATE);
}

bool Bounce::rose()
{
return ( state & _BV(DEBOUNCED_STATE) ) && ( state & _BV(STATE_CHANGED));
}

bool Bounce::fell()
{
return !( state & _BV(DEBOUNCED_STATE) ) && ( state & _BV(STATE_CHANGED));
}

+ 88
- 0
smarkant-arduino/lib/Bounce2/Bounce2.h View File

@@ -0,0 +1,88 @@
/*
The MIT License (MIT)

Copyright (c) 2013 thomasfredericks

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
Main code by Thomas O Fredericks (tof@t-o-f.info)
Previous contributions by Eric Lowry, Jim Schimpf and Tom Harkaway
* * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifndef Bounce2_h
#define Bounce2_h

// Uncomment the following line for "LOCK-OUT" debounce method
//#define BOUNCE_LOCK_OUT

// Uncomment the following line for "BOUNCE_WITH_PROMPT_DETECTION" debounce method
//#define BOUNCE_WITH_PROMPT_DETECTION

#include <inttypes.h>

#ifndef _BV
#define _BV(n) (1<<(n))
#endif

class Bounce
{
public:
// Create an instance of the bounce library
Bounce();

// Attach to a pin (and also sets initial state)
void attach(int pin);
// Attach to a pin (and also sets initial state) and sets pin to mode (INPUT/INPUT_PULLUP/OUTPUT)
void attach(int pin, int mode);

// Sets the debounce interval
void interval(uint16_t interval_millis);

// Updates the pin
// Returns 1 if the state changed
// Returns 0 if the state did not change
bool update();

// Returns the updated pin state
bool read();

// Returns the falling pin state
bool fell();

// Returns the rising pin state
bool rose();

// Partial compatibility for programs written with Bounce version 1
bool risingEdge() { return rose(); }
bool fallingEdge() { return fell(); }
Bounce(uint8_t pin, unsigned long interval_millis ) : Bounce() {
attach(pin);
interval(interval_millis);
}

protected:
unsigned long previous_millis;
uint16_t interval_millis;
uint8_t state;
uint8_t pin;
};

#endif

+ 32
- 0
smarkant-arduino/lib/LinProcessor/avr_util.cpp View File

@@ -0,0 +1,32 @@
// 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.

#include "avr_util.h"

namespace avr_util_private {

// Using lookup to avoid individual bit shifting. This is faster
// than (1 << n) or a switch/case statement.
const byte kBitMaskArray[] = {
H(0),
H(1),
H(2),
H(3),
H(4),
H(5),
H(6),
H(7),
};

} // namespace avr_util_private



+ 45
- 0
smarkant-arduino/lib/LinProcessor/avr_util.h View File

@@ -0,0 +1,45 @@
// 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.

#ifndef AVR_UTIL_H
#define AVR_UTIL_H

#include <Arduino.h>

// Get rid of the _t type suffix.
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;

typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;

// Bit index to bit mask.
// AVR registers bit indices are defined in iom328p.h.
#define H(x) (1 << (x))
#define L(x) (0 << (x))

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

// Private data. Do not use from other modules.
namespace avr_util_private {
extern const byte kBitMaskArray[];
}

// Similar to (1 << bit_index) but more efficient for non consts. For
// const masks use H(n). Undefined result if it_index not in [0, 7].
inline byte bitMask(byte bit_index) {
return *(avr_util_private::kBitMaskArray + bit_index);
}

#endif

+ 34
- 0
smarkant-arduino/lib/LinProcessor/custom_defs.h View File

@@ -0,0 +1,34 @@
// 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.

#ifndef CUSTOM_DEFS_H
#define CUSTOM_DEFS_H

#include "avr_util.h"

// Custom application specific parameters.
//
// Like all the other custom_* files, this file should be adapted to the specific application.
// The example provided is for a Sport Mode button press injector for 981/Cayman.
namespace custom_defs {

// True for LIN checksum V2 (enahanced). False for LIN checksum version 1.
const boolean kUseLinChecksumVersion2 = false;

// LIN bus bits per second rate.
// Supported baud range is 1000 to 20000. If out of range, using silently default
// baud of 9600.
const uint16 kLinSpeed = 19200;

} // namepsace custom_defs

#endif

+ 41
- 0
smarkant-arduino/lib/LinProcessor/hardware_clock.cpp View File

@@ -0,0 +1,41 @@
// 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.

#include "hardware_clock.h"

#include <Arduino.h>
#include "avr_util.h"

namespace hardware_clock {

#if F_CPU != 16000000
#error "The existing code assumes 16Mhz CPU clk."
#endif

void setup() {
// Normal mode (free running [0, ffff]).
TCCR1A = L(COM1A1) | L(COM1A0) | L(COM1B1) | L(COM1B0) | L(WGM11) | L(WGM10);
// Prescaler: X64 (250 clocks per ms @ 16MHz). 2^16 clock cycle every ~260ms.
// This also defines the max update() interval to avoid missing a counter overflow.
TCCR1B = L(ICNC1) | L(ICES1) | L(WGM13) | L(WGM12) | L(CS12) | H(CS11) | H(CS10);
// Clear counter.
TCNT1 = 0;
// Compare A. Not used.
OCR1A = 0;
// Compare B. Used to output cycle pulses, for debugging.
OCR1B = 0;
// Diabled interrupts.
TIMSK1 = L(ICIE1) | L(OCIE1B) | L(OCIE1A) | L(TOIE1);
TIFR1 = L(ICF1) | L(OCF1B) | L(OCF1A) | L(TOV1);
}

} // namespace hardware_clock

+ 55
- 0
smarkant-arduino/lib/LinProcessor/hardware_clock.h View File

@@ -0,0 +1,55 @@
// 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.

#ifndef HARDWARE_CLOCK_H
#define HARDWARE_CLOCK_H

#include <Arduino.h>
#include "avr_util.h"

// Provides a free running 16 bit counter with 250 ticks per millisecond and
// about 280 millis cycle time. Assuming 16Mhz clock.
//
// USES: timer 1, no interrupts.
namespace hardware_clock {
// Call once from main setup(). Tick count starts at 0.
extern void setup();

// Free running 16 bit counter. Starts counting from zero and wraps around
// every ~280ms.
// Assumes interrupts are enabled upon entry.
// DO NOT CALL THIS FROM AN ISR.
inline uint16 ticksForNonIsr() {
// We disable interrupt to avoid corruption of the AVR temp byte buffer
// that is used to read 16 bit values.
// TODO: can we avoid disabling interrupts (motivation: improve LIN ISR jitter).
cli();
const uint16 result TCNT1;
sei();
return result;
}

// Similar to ticksNonIsr but does not enable interrupts.
// CALL THIS FROM ISR ONLY.
inline uint16 ticksForIsr() {
return TCNT1;
}

#if F_CPU != 16000000
#error "The existing code assumes 16Mhz CPU clk."
#endif

// @ 16Mhz / x64 prescaler. Number of ticks per a millisecond.
const uint32 kTicksPerMilli = 250;
} // namespace hardware_clock

#endif

+ 108
- 0
smarkant-arduino/lib/LinProcessor/io_pins.h View File

@@ -0,0 +1,108 @@
// 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.

#ifndef IO_PINS_H
#define IO_PINS_H

#include "avr_util.h"

namespace io_pins {
// A class to abstract an output pin that is not necesarily an arduino
// digital pin. Also optimized for fast setOn/Off.
//
// Assumes that interrupts are enabled and thus should not be called
// from ISRs.
class OutputPin {
public:
// port is either PORTB, PORTC, or PORTD.
// bit index is one of [7, 6, 5, 4, 3, 2, 1, 0] (lsb).
// NOTE: ddr port is is always one address below portx.
// NOTE: pin port is is always two addresses below portx.
OutputPin(volatile uint8& port, uint8 bitIndex) :
port_(port),
pin_(*((&port)-2)),
bit_mask_(1 << bitIndex) {
// NOTE: ddr port is is always one address below port.
volatile uint8& ddr = *((&port)-1);
ddr |= bit_mask_;
low(); // default state.
}

inline void high() {
// Wrapping in cli/sei in case any pin of this port is changed
// from an ISR. Keeping this as short as possible to avoid jitter
// in the ISR invocation.
cli();
port_ |= bit_mask_;
sei();
}

inline void low() {
// Wrapping in cli/sei in case any pin of this port is changed
// from an ISR. Keeping this as short as possible to avoid jitter
// in the ISR invocation.
cli();
port_ &= ~bit_mask_;
sei();
}

inline void set(boolean v) {
if (v) {
high();
}
else {
low();
}
}

inline void toggle() {
set(!isHigh());
}

inline boolean isHigh() {
return pin_ & bit_mask_;
}

private:
volatile uint8& port_;
volatile uint8& pin_;
const uint8 bit_mask_;
};

// A class to abstract an input pin that is not necesarily an arduino
// digital pin. Also optimized for quick access.
class InputPin {
public:
// See OutputPin() for description of the args.
InputPin(volatile uint8& port, uint8 bitIndex)
:
pin_(*((&port)-2)),
bit_mask_(1 << bitIndex) {
volatile uint8& ddr = *((&port)-1);
ddr &= ~bit_mask_; // input
port |= bit_mask_; // pullup
}

inline boolean isHigh() {
return pin_ & bit_mask_;
}

private:
volatile uint8& pin_;
const uint8 bit_mask_;
};
} // namespace io_pins

#endif



+ 107
- 0
smarkant-arduino/lib/LinProcessor/lin_frame.cpp View File

@@ -0,0 +1,107 @@
// 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.

#include "lin_frame.h"

#include "custom_defs.h"

// Compute the checksum of the frame. For now using LIN checksum V2 only.
uint8 LinFrame::computeChecksum() const {
// LIN V2 checksum includes the ID byte, V1 does not.
const uint8 startByteIndex = custom_defs::kUseLinChecksumVersion2 ? 0 : 1;
const uint8* p = &bytes_[startByteIndex];
// Exclude the checksum byte at the end of the frame.
uint8 numBytesToChecksum = num_bytes_ - startByteIndex - 1;

// Sum bytes. We should not have 16 bit overflow here since the frame has a limited size.
uint16 sum = 0;
while (numBytesToChecksum-- > 0) {
sum += *(p++);
}

// Keep adding the high and low bytes until no carry.
for (;;) {
const uint8 highByte = (uint8)(sum >> 8);
if (!highByte) {
break;
}
// NOTE: this can add additional carry.
sum = (sum & 0xff) + highByte;
}

return (uint8)(~sum);
}


uint8 LinFrame::setLinIdChecksumBits(uint8 id) {
// The algorithm is optimized for CPU time (avoiding individual shifts per id
// bit). Using registers for the two checksum bits. P1 is computed in bit 7 of
// p1_at_b7 and p0 is comptuted in bit 6 of p0_at_b6.
uint8 p1_at_b7 = ~0;
uint8 p0_at_b6 = 0;

// P1: id5, P0: id4
uint8 shifter = id << 2;
p1_at_b7 ^= shifter;
p0_at_b6 ^= shifter;

// P1: id4, P0: id3
shifter += shifter;
p1_at_b7 ^= shifter;

// P1: id3, P0: id2
shifter += shifter;
p1_at_b7 ^= shifter;
p0_at_b6 ^= shifter;

// P1: id2, P0: id1
shifter += shifter;
p0_at_b6 ^= shifter;

// P1: id1, P0: id0
shifter += shifter;
p1_at_b7 ^= shifter;
p0_at_b6 ^= shifter;

return (p1_at_b7 & 0b10000000) | (p0_at_b6 & 0b01000000) | (id & 0b00111111);
}

boolean LinFrame::isValid() const {
const uint8 n = num_bytes_;

// Check frame size.
// One ID byte with optional 1-8 data bytes and 1 checksum byte.
// TODO: should we enforce only 1, 2, 4, or 8 data bytes? (total size
// 1, 3, 4, 6, or 10)
//
// TODO: should we pass through frames with ID only (n == 1, no response from slave).
if (n != 1 && (n < 3 || n > 10)) {
return false;
}

// Check ID byte checksum bits.
const uint8 id_byte = bytes_[0];
if (id_byte != setLinIdChecksumBits(id_byte)) {
return false;
}

// If not an ID only frame, check also the overall checksum.
if (n > 1) {
if (bytes_[n - 1] != computeChecksum()) {
return false;
}
}
// TODO: check protected id.
return true;
}


+ 78
- 0
smarkant-arduino/lib/LinProcessor/lin_frame.h View File

@@ -0,0 +1,78 @@
// 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.

#ifndef LIN_FRAME_H
#define LIN_FRAME_H

#include "avr_util.h"

// A buffer for a single frame.
class LinFrame {
public:
LinFrame() {
reset();
}
// Number of bytes in a shotest frame. This is a frame with ID byte and no
// slave response (and thus no data or checksum).
static const uint8 kMinBytes = 1;

// Number of bytes in the longest frame. One ID byte, 8 data bytes, one checksum byte.
static const uint8 kMaxBytes = 1 + 8 + 1;

// Compute the to checkum bits [P1,P0] of the lin id in bits [5:0] and return
// [P1,P0][5:0] which is the wire representation of this id.
static uint8 setLinIdChecksumBits(uint8 id);
boolean isValid() const;
// Compute LIN frame checksum. Assuming buffer has at least one byte. A valid
// frame should contain one byte for id, 1-8 bytes for data, one byte for checksum.
uint8 computeChecksum() const;

inline void reset() {
num_bytes_ = 0;
}

inline uint8 num_bytes() const {
return num_bytes_;
}
// Get a the frame byte of given inde.
// Byte index 0 is the frame id byte, followed by 1-8 data bytes
// followed by 1 checksum byte.
//
// Caller should verify that index < num_bytes.
inline uint8 get_byte(uint8 index) const {
return bytes_[index];
}
// Caller should check that num_bytes < kMaxBytes;
inline void append_byte(uint8 value) {
bytes_[num_bytes_++] = value;
}
// TODO: make this stuff private without sacrifying performance.
private:
// Number of bytes in bytes_ buffer. At most kMaxBytes.
uint8 num_bytes_;

// Recieved frame bytes. Includes id, data and checksum. Does not
// include the 0x55 sync byte.
uint8 bytes_[kMaxBytes];
};

#endif




+ 618
- 0
smarkant-arduino/lib/LinProcessor/lin_processor.cpp View File

@@ -0,0 +1,618 @@
// 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.

// TODO: file is too long. Refactor.

#include "lin_processor.h"

#include "avr_util.h"
#include "custom_defs.h"
#include "hardware_clock.h"

// TODO: for debugging. Remove.
#include "sio.h"

// ----- Baud rate related parameters. ---

// If an out of range speed is specified, using this one.
static const uint16 kDefaultBaud = 9600;

// Wait at most N bits from the end of the stop bit of previous byte
// to the start bit of next byte.
//
// TODO: seperate values for pre response space (longer, e.g. 8) and pre
// regular byte space (shorter, e.g. 4).
//
static const uint8 kMaxSpaceBits = 8;

// Define an input pin with fast access. Using the macro does
// not increase the pin access time compared to direct bit manipulation.
// Pin is setup with active pullup.
#define DEFINE_INPUT_PIN(name, port_letter, bit_index) \
namespace name { \
static const uint8 kPinMask = H(bit_index); \
static inline void setup() { \
DDR ## port_letter &= ~kPinMask; \
PORT ## port_letter |= kPinMask; \
} \
static inline uint8 isHigh() { \
return PIN##port_letter & kPinMask; \
} \
}

// Define an output pin with fast access. Using the macro does
// not increase the pin access time compared to direct bit manipulation.
#define DEFINE_OUTPUT_PIN(name, port_letter, bit_index, initial_value) \
namespace name { \
static const uint8 kPinMask = H(bit_index); \
static inline void setHigh() { \
PORT ## port_letter |= kPinMask; \
} \
static inline void setLow() { \
PORT ## port_letter &= ~kPinMask; \
} \
static inline void setup() { \
DDR ## port_letter |= kPinMask; \
(initial_value) ? setHigh() : setLow(); \
} \
}


namespace lin_processor {

class Config {
public:
#if F_CPU != 16000000
#error "The existing code assumes 16Mhz CPU clk."
#endif
// Initialized to given baud rate.
void setup() {
// If baud rate out of range use default speed.
uint16 baud = custom_defs::kLinSpeed;
if (baud < 1000 || baud > 20000) {
// sio::println(F("ERROR: kLinSpeed out of range"));
Serial.println(F("ERROR: kLinSpeed out of range"));
baud = kDefaultBaud;
}
baud_ = baud;
prescaler_x64_ = baud < 8000;
const uint8 prescaling = prescaler_x64_ ? 64 : 8;
counts_per_bit_ = (((16000000L / prescaling) / baud));
// Adding two counts to compensate for software delay.
counts_per_half_bit_ = (counts_per_bit_ / 2) + 2;
clock_ticks_per_bit_ = (hardware_clock::kTicksPerMilli * 1000) / baud;
clock_ticks_per_half_bit_ = clock_ticks_per_bit_ / 2;
clock_ticks_per_until_start_bit_ = clock_ticks_per_bit_ * kMaxSpaceBits;
}

inline uint16 baud() const {
return baud_;
}

inline boolean prescaler_x64() {
return prescaler_x64_;
}

inline uint8 counts_per_bit() const {
return counts_per_bit_;
}
inline uint8 counts_per_half_bit() const {
return counts_per_half_bit_;
}
inline uint8 clock_ticks_per_bit() const {
return clock_ticks_per_bit_;
}
inline uint8 clock_ticks_per_half_bit() const {
return clock_ticks_per_half_bit_;
}
inline uint8 clock_ticks_per_until_start_bit() const {
return clock_ticks_per_until_start_bit_;
}
private:
uint16 baud_;
// False -> x8, true -> x64.
// TODO: timer2 also have x32 scalingl Could use it for better
// accuracy in the mid baude range.
boolean prescaler_x64_;
uint8 counts_per_bit_;
uint8 counts_per_half_bit_;
uint8 clock_ticks_per_bit_;
uint8 clock_ticks_per_half_bit_;
uint8 clock_ticks_per_until_start_bit_;
};

// The actual configurtion. Initialized in setup() based on baud rate.
Config config;

// ----- Digital I/O pins
//
// NOTE: we use direct register access instead the abstractions in io_pins.h.
// This way we shave a few cycles from the ISR.

// LIN interface.
DEFINE_INPUT_PIN(rx_pin, D, 2);
// TODO: Not use, as of Apr 2014.
DEFINE_OUTPUT_PIN(tx1_pin, C, 2, 1);

// Debugging signals.
DEFINE_OUTPUT_PIN(break_pin, C, 0, 0);
DEFINE_OUTPUT_PIN(sample_pin, B, 4, 0);
DEFINE_OUTPUT_PIN(error_pin, B, 3, 0);
DEFINE_OUTPUT_PIN(isr_pin, C, 3, 0);
DEFINE_OUTPUT_PIN(gp_pin, D, 6, 0);

// Called one during initialization.
static inline void setupPins() {
rx_pin::setup();
break_pin::setup();
sample_pin::setup();
error_pin::setup();
isr_pin::setup();
gp_pin::setup();
}

// ----- ISR RX Ring Buffers -----

// Frame buffer queue size.
static const uint8 kMaxFrameBuffers = 8;

// RX Frame buffers queue. Read/Writen by ISR only.
static LinFrame rx_frame_buffers[kMaxFrameBuffers];

// Index [0, kMaxFrameBuffers) of the current frame buffer being
// written (newest). Read/Written by ISR only.
static uint8 head_frame_buffer;

// Index [0, kMaxFrameBuffers) of the next frame to be read (oldest).
// If equals head_frame_buffer then there is no available frame.
// Read/Written by ISR only.
static uint8 tail_frame_buffer;

// Called once from main.
static inline void setupBuffers() {
head_frame_buffer = 0;
tail_frame_buffer = 0;
}

// Called from ISR or from main with interrupts disabled.
static inline void incrementTailFrameBuffer() {
if (++tail_frame_buffer >= kMaxFrameBuffers) {
tail_frame_buffer = 0;
}
}

// Called from ISR. If stepping on tail buffer, caller needs to
// increment raise frame overrun error.
static inline void incrementHeadFrameBuffer() {
if (++head_frame_buffer >= kMaxFrameBuffers) {
head_frame_buffer = 0;
}
}

// ----- ISR To Main Data Transfer -----

// Increment by the ISR to indicates to the main program when the ISR returned.
// This is used to defered disabling interrupts until the ISR completes to
// reduce ISR jitter time.
static volatile uint8 isr_marker;

// Should be called from main only.
static inline void waitForIsrEnd() {
const uint8 value = isr_marker;
// Wait until the next ISR ends.
while (value == isr_marker) {
}
}

// Public. Called from main. See .h for description.
boolean readNextFrame(LinFrame* buffer) {
boolean result = false;
waitForIsrEnd();
cli();
if (tail_frame_buffer != head_frame_buffer) {
//led::setHigh();
// This copies the request buffer struct.
*buffer = rx_frame_buffers[tail_frame_buffer];
incrementTailFrameBuffer();
result = true;
//led::setLow();
}
sei();
return result;
}

// ----- State Machine Declaration -----

// Like enum but 8 bits only.
namespace states {
static const uint8 DETECT_BREAK = 1;
static const uint8 READ_DATA = 2;
}
static uint8 state;

class StateDetectBreak {
public:
static inline void enter() ;
static inline void handleIsr();

private:
static uint8 low_bits_counter_;
};

class StateReadData {
public:
// Should be called after the break stop bit was detected.
static inline void enter();
static inline void handleIsr();

private:
// Number of complete bytes read so far. Includes all bytes, even
// sync, id and checksum.
static uint8 bytes_read_;

// Number of bits read so far in the current byte. Includes start bit,
// 8 data bits and one stop bits.
static uint8 bits_read_in_byte_;

// Buffer for the current byte we collect.
static uint8 byte_buffer_;

// When collecting the data bits, this goes (1 << 0) to (1 << 7). Could
// be computed as (1 << (bits_read_in_byte_ - 1)). We use this cached value
// recude ISR computation.
static uint8 byte_buffer_bit_mask_;
};

// ----- Error Flag. -----

// Written from ISR. Read/Write from main. Bit mask of pending errors.
static volatile uint8 error_flags;

// Private. Called from ISR and from setup (beofe starting the ISR).
static inline void setErrorFlags(uint8 flags) {
error_pin::setHigh();
// Non atomic when called from setup() but should be fine since ISR is not running yet.
error_flags |= flags;
error_pin::setLow();
}

// Called from main. Public. Assumed interrupts are enabled.
// Do not call from ISR.
uint8 getAndClearErrorFlags() {
// Disabling interrupts for a brief for atomicity. Need to pay attention to
// ISR jitter due to disabled interrupts.
cli();
const uint8 result = error_flags;
error_flags = 0;
sei();
return result;
}

struct BitName {
const uint8 mask;
const char* const name;
};

static const BitName kErrorBitNames[] PROGMEM = {
{ errors::FRAME_TOO_SHORT, "SHRT" },
{ errors::FRAME_TOO_LONG, "LONG" },
{ errors::START_BIT, "STRT" },
{ errors::STOP_BIT, "STOP" },
{ errors::SYNC_BYTE, "SYNC" },
{ errors::BUFFER_OVERRUN, "OVRN" },
{ errors::OTHER, "OTHR" },
};

// Given a byte with lin processor error bitset, print the list
// of set errors.
void printErrorFlags(uint8 lin_errors) {
const uint8 n = ARRAY_SIZE(kErrorBitNames);
boolean any_printed = false;
for (uint8 i = 0; i < n; i++) {
const uint8 mask = pgm_read_byte(&kErrorBitNames[i].mask);
if (lin_errors & mask) {
if (any_printed) {
// sio::printchar(' ');
Serial.print(' ');
}
const char* const name = (const char*)pgm_read_word(&kErrorBitNames[i].name);
// sio::print(name);
Serial.print(name);
any_printed = true;
}
}
}

// ----- Initialization -----

static void setupTimer() {
// OC2B cycle pulse (Arduino digital pin 3, PD3). For debugging.
DDRD |= H(DDD3);
// Fast PWM mode, OC2B output active high.
TCCR2A = L(COM2A1) | L(COM2A0) | H(COM2B1) | H(COM2B0) | H(WGM21) | H(WGM20);
const uint8 prescaler = config.prescaler_x64()
? (H(CS22) | L(CS21) | L(CS20)) // x64
: (L(CS22) | H(CS21) | L(CS20)); // x8
// Prescaler: X8. Should match the definition of kPreScaler;
TCCR2B = L(FOC2A) | L(FOC2B) | H(WGM22) | prescaler;
// Clear counter.
TCNT2 = 0;
// Determines baud rate.
OCR2A = config.counts_per_bit() - 1;
// A short 8 clocks pulse on OC2B at the end of each cycle,
// just before triggering the ISR.
OCR2B = config.counts_per_bit() - 2;
// Interrupt on A match.
TIMSK2 = L(OCIE2B) | H(OCIE2A) | L(TOIE2);
// Clear pending Compare A interrupts.
TIFR2 = L(OCF2B) | H(OCF2A) | L(TOV2);
}

// Call once from main at the begining of the program.
void setup() {
// Should be done first since some of the steps below depends on it.
config.setup();

setupPins();
setupBuffers();
StateDetectBreak::enter();
setupTimer();
error_flags = 0;

}

// ----- ISR Utility Functions -----

// Set timer value to zero.
static inline void resetTickTimer() {
// TODO: also clear timer2 prescaler.
TCNT2 = 0;
}

// Set timer value to half a tick. Called at the begining of the
// start bit to generate sampling ticks at the middle of the next
// 10 bits (start, 8 * data, stop).
static inline void setTimerToHalfTick() {
// Adding 2 to compensate for pre calling delay. The goal is
// to have the next ISR data sampling at the middle of the start
// bit.
TCNT2 = config.counts_per_half_bit();
}

// Perform a tight busy loop until RX is low or the given number
// of clock ticks passed (timeout). Retuns true if ok,
// false if timeout. Keeps timer reset during the wait.
// Called from ISR only.
static inline boolean waitForRxLow(uint16 max_clock_ticks) {
const uint16 base_clock = hardware_clock::ticksForIsr();
for(;;) {
// Keep the tick timer not ticking (no ISR).
resetTickTimer();

// If rx is low we are done.
if (!rx_pin::isHigh()) {
return true;
}

// Test for timeout.
// Should work also in case of 16 bit clock overflow.
const uint16 clock_diff = hardware_clock::ticksForIsr() - base_clock;
if (clock_diff >= max_clock_ticks) {
return false;
}
}
}

// Same as waitForRxLow but with reversed polarity.
// We clone to code for time optimization.
// Called from ISR only.
static inline boolean waitForRxHigh(uint16 max_clock_ticks) {
const uint16 base_clock = hardware_clock::ticksForIsr();
for(;;) {
resetTickTimer();
if (rx_pin::isHigh()) {
return true;
}
// Should work also in case of an clock overflow.
const uint16 clock_diff = hardware_clock::ticksForIsr() - base_clock;
if (clock_diff >= max_clock_ticks) {
return false;
}
}
}

// ----- Detect-Break State Implementation -----

uint8 StateDetectBreak::low_bits_counter_;

inline void StateDetectBreak::enter() {
state = states::DETECT_BREAK;
low_bits_counter_ = 0;
}

// Return true if enough time to service rx request.
inline void StateDetectBreak::handleIsr() {
if (rx_pin::isHigh()) {
low_bits_counter_ = 0;
return;
}

// Here RX is low (active)

if (++low_bits_counter_ < 10) {
return;
}

// Detected a break. Wait for rx high and enter data reading.
break_pin::setHigh();

// TODO: set actual max count
waitForRxHigh(255);
break_pin::setLow();

// Go process the data
StateReadData::enter();
}

// ----- Read-Data State Implementation -----

uint8 StateReadData::bytes_read_;
uint8 StateReadData::bits_read_in_byte_;
uint8 StateReadData::byte_buffer_;
uint8 StateReadData::byte_buffer_bit_mask_;

// Called on the low to high transition at the end of the break.
inline void StateReadData::enter() {
state = states::READ_DATA;
bytes_read_ = 0;
bits_read_in_byte_ = 0;
rx_frame_buffers[head_frame_buffer].reset();

// TODO: handle post break timeout errors.
// TODO: set a reasonable time limit.
waitForRxLow(255);
setTimerToHalfTick();
}

inline void StateReadData::handleIsr() {
// Sample data bit ASAP to avoid jitter.
sample_pin::setHigh();
const uint8 is_rx_high = rx_pin::isHigh();
sample_pin::setLow();

// Handle start bit.
if (bits_read_in_byte_ == 0) {
// Start bit error.
if (is_rx_high) {
// If in sync byte, report as a sync error.
setErrorFlags(bytes_read_ == 0 ? errors::SYNC_BYTE : errors::START_BIT);
StateDetectBreak::enter();
return;
}
// Start bit ok.
bits_read_in_byte_++;
// Prepare buffer and mask for data bit collection.
byte_buffer_ = 0;
byte_buffer_bit_mask_ = (1 << 0);
return;
}

// Handle next data bit, 1 out of total of 8.
// Collect the current bit into byte_buffer_, lsb first.
if (bits_read_in_byte_ <= 8) {
if (is_rx_high) {
byte_buffer_ |= byte_buffer_bit_mask_;
}
byte_buffer_bit_mask_ = byte_buffer_bit_mask_ << 1;
bits_read_in_byte_++;
return;
}

// Here when in a stop bit.
bytes_read_++;
bits_read_in_byte_ = 0;

// Error if stop bit is not high.
if (!is_rx_high) {
// If in sync byte, report as sync error.
setErrorFlags(bytes_read_ == 0 ? errors::SYNC_BYTE : errors::STOP_BIT);
StateDetectBreak::enter();
return;
}

// Here when we just finished reading a byte.
// bytes_read is already incremented for this byte.

// If this is the sync byte, verify that it has the expected value.
if (bytes_read_ == 1) {
// Should be exactly 0x55. We don't append this byte to the buffer.
if (byte_buffer_ != 0x55) {
setErrorFlags(errors::SYNC_BYTE);
StateDetectBreak::enter();
return;
}
} else {
// If this is the id, data or checksum bytes, append it to the frame buffer.
// NOTE: the byte limit count is enforeced somewhere else so we can assume safely here that this
// will not cause a buffer overlow.
rx_frame_buffers[head_frame_buffer].append_byte(byte_buffer_);
}

// Wait for the high to low transition of start bit of next byte.
const boolean has_more_bytes = waitForRxLow(config.clock_ticks_per_until_start_bit());

// Handle the case of no more bytes in this frame.
if (!has_more_bytes) {
// Verify min byte count.
if (bytes_read_ < LinFrame::kMinBytes) {
setErrorFlags(errors::FRAME_TOO_SHORT);
StateDetectBreak::enter();
return;
}

// Frame looks ok so far. Move to next frame in the ring buffer.
// NOTE: we will reset the byte_count of the new frame buffer next time we will enter data detect state.
// NOTE: verification of sync byte, id, checksum, etc is done latter by the main code, not the ISR.
incrementHeadFrameBuffer();
if (tail_frame_buffer == head_frame_buffer) {
// Frame buffer overrun. We drop the oldest frame and continue with this one.
setErrorFlags(errors::BUFFER_OVERRUN);
incrementTailFrameBuffer();
}

StateDetectBreak::enter();
return;
}

// Here when there is at least one more byte in this frame. Error if we already had
// the max number of bytes.
if (rx_frame_buffers[head_frame_buffer].num_bytes() >= LinFrame::kMaxBytes) {
setErrorFlags(errors::FRAME_TOO_LONG);
StateDetectBreak::enter();
return;
}

// Everything is ready for the next byte. Have a tick in the middle of its
// start bit.
//
// TODO: move this to above the num_bytes check above for more accurate
// timing of mid bit tick?
setTimerToHalfTick();
}

// ----- ISR Handler -----

// Interrupt on Timer 2 A-match.
ISR(TIMER2_COMPA_vect)
{
isr_pin::setHigh();
// TODO: make this state a boolean instead of enum? (efficency).
switch (state) {
case states::DETECT_BREAK:
StateDetectBreak::handleIsr();
break;
case states::READ_DATA:
StateReadData::handleIsr();
break;
default:
setErrorFlags(errors::OTHER);
StateDetectBreak::enter();
}

// Increment the isr flag to indicate to the main that the ISR just
// exited and interrupts can be temporarily disabled without causes ISR
// jitter.
isr_marker++;

isr_pin::setLow();
}
} // namespace lin_processor

+ 55
- 0
smarkant-arduino/lib/LinProcessor/lin_processor.h View File

@@ -0,0 +1,55 @@
// 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.

#ifndef LIN_PROCESSOR_H
#define LIN_PROCESSOR_H

#include "avr_util.h"
#include "lin_frame.h"

// Uses
// * Timer2 - used to generate the bit ticks.
// * OC2B (PD3) - timer output ticks. For debugging. If needed, can be changed
// to not using this pin.
// * PD2 - LIN RX input.
// * PC0, PC1, PC2, PC3 - debugging outputs. See .cpp file for details.
namespace lin_processor {
// Call once in program setup.
extern void setup();

// Try to read next available rx frame. If available, return true and set
// given buffer. Otherwise, return false and leave *buffer unmodified.
// The sync, id and checksum bytes of the frame as well as the total byte
// count are not verified.
extern boolean readNextFrame(LinFrame* buffer);

// Errors byte masks for the individual error bits.
namespace errors {
static const uint8 FRAME_TOO_SHORT = (1 << 0);
static const uint8 FRAME_TOO_LONG = (1 << 1);
static const uint8 START_BIT = (1 << 2);
static const uint8 STOP_BIT = (1 << 3);
static const uint8 SYNC_BYTE = (1 << 4);
static const uint8 BUFFER_OVERRUN = (1 << 5);
static const uint8 OTHER = (1 << 6);
}

// Get current error flag and clear it.
extern uint8 getAndClearErrorFlags();
// Print to sio a list of error flags.
extern void printErrorFlags(uint8 lin_errors);
}

#endif



+ 44
- 0
smarkant-arduino/lib/LinProcessor/passive_timer.h View File

@@ -0,0 +1,44 @@
// 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.

#ifndef PASSIVE_TIMER_H
#define PASSIVE_TIMER_H

#include <arduino.h>
#include "system_clock.h"

// A wrapper around system clock that provides millisecond based time measurments.
class PassiveTimer {
public:
PassiveTimer() {
restart();
}

inline void restart() {
start_time_millis_ = system_clock::timeMillis();
}

void copy(const PassiveTimer &other) {
start_time_millis_ = other.start_time_millis_;
}

inline uint32 timeMillis() const {
return system_clock::timeMillis() - start_time_millis_;
}

private:
uint32 start_time_millis_;
};

#endif



+ 165
- 0
smarkant-arduino/lib/LinProcessor/sio.cpp View File

@@ -0,0 +1,165 @@
// 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.

#include "sio.h"

#include <stdarg.h>

namespace sio {
// TODO: do we need to set the i/o pins (PD0, PD1)? Do we rely on setting by
// the bootloader?
// Size of output bytes queue. Shuold be <= 128 to avoid overflow.
// TODO: reduce buffer size? Do we have enough RAM?
// TODO: increase index size to 16 bit and increase buffer size to 200?
static const uint8 kQueueSize = 120;
static uint8 buffer[kQueueSize];
// Index of the oldest entry in buffer.
static uint8 start;
// Number of bytes in queue.
static uint8 count;

// Caller need to verify that count < kQueueSize before calling this.
static void unsafe_enqueue(byte b) {
// kQueueSize is small enough that this will not overflow.
uint8 next = start + count;
if (next >= kQueueSize) {
next -= kQueueSize;
}
buffer[next] = b;
count++;
}

// Caller need to verify that count > 1 before calling this.
static byte unsafe_dequeue() {
const uint8 b = buffer[start];
if (++start >= kQueueSize) {
start = 0;
}
count--;
return b;
}

void setup() {
start = 0;
count = 0;
#if F_CPU != 16000000
#error "The existing code assumes 16Mhz CPU clk."
#endif
// For devisors see table 19-12 in the atmega328p datasheet.
// U2X0, 16 -> 115.2k baud @ 16MHz.
// U2X0, 207 -> 9600 baud @ 16Mhz.
UBRR0H = 0;
UBRR0L = 16;
UCSR0A = H(U2X0);
// Enable the transmitter. Reciever is disabled.
UCSR0B = H(TXEN0);
UCSR0C = H(UDORD0) | H(UCPHA0); //(3 << UCSZ00);
}

void printchar(uint8 c) {
// If buffer is full, drop this char.
// TODO: drop last byte to make room for the new byte?
if (count >= kQueueSize) {
return;
}
unsafe_enqueue(c);
}

void loop() {
if (count && (UCSR0A & H(UDRE0))) {
UDR0 = unsafe_dequeue();
}
}

uint8 capacity() {
return kQueueSize - count;
}

void waitUntilFlushed() {
// Busy loop until all flushed to UART.
while (count) {
loop();
}
}

// Assuming n is in [0, 15].
static void printHexDigit(uint8 n) {
if (n < 10) {
printchar((char)('0' + n));
}
else {
printchar((char)(('a' - 10) + n));
}
}

void printhex2(uint8 b) {
printHexDigit(b >> 4);
printHexDigit(b & 0xf);
}

void println() {
printchar('\n');
}

void print(const __FlashStringHelper *str) {
const char* PROGMEM p = (const char PROGMEM *)str;
for(;;) {
const unsigned char c = pgm_read_byte(p++);
if (!c) {
return;
}
printchar(c);
}
}

void println(const __FlashStringHelper *str) {
print(str);
println();
}


void print(const char* str) {
for(;;) {
const char c = *(str++);
if (!c) {
return;
}
printchar(c);
}
}

void println(const char* str) {
print(str);
println();
}


void printf(const __FlashStringHelper *format, ...)
{
// Assuming single thread, using static buffer.
static char buf[80];
va_list ap;
va_start(ap, format);
vsnprintf_P(buf, sizeof(buf), (const char *)format, ap); // progmem for AVR
for(char *p = &buf[0]; *p; p++) // emulate cooked mode for newlines
{
printchar(*p);
}
va_end(ap);
}
} // namespace sio





+ 50
- 0
smarkant-arduino/lib/LinProcessor/sio.h View File

@@ -0,0 +1,50 @@
// 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.

#ifndef SIO_H
#define SIO_H

#include <Arduino.h>
#include "avr_util.h"

// A serial output that uses hardware UART0 and no interrupts (for lower
// interrupt jitter). Requires periodic calls to update() to send buffered
// bytes to the uart.
//
// TX Output - TXD (PD1) - pin 31
// TX Input - TXD (PD0) - pin 30 (currently not used).
namespace sio {

// Call from main setup() and loop() respectivly.
extern void setup();
extern void loop();

// Momentary size of free space in the output buffer. Sending at most this number
// of characters will not loose any byte.
extern uint8 capacity();

extern void printchar(uint8 b);
extern void print(const __FlashStringHelper *str);
extern void println(const __FlashStringHelper *str);
extern void print(const char* str);
extern void println(const char* str);
extern void println();
extern void printf(const __FlashStringHelper *format, ...);
extern void printhex2(uint8 b);

// Wait in a busy loop until all bytes were flushed to the UART.
// Avoid using this when possible. Useful when needing to print
// during setup() more than the output buffer can contain.
void waitUntilFlushed();
} // namespace sio

#endif

+ 53
- 0
smarkant-arduino/lib/LinProcessor/system_clock.cpp View File

@@ -0,0 +1,53 @@
// 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.

#include "system_clock.h"

#include <Arduino.h>
#include "avr_util.h"
#include "hardware_clock.h"

namespace system_clock {
static const uint16 kTicksPerMilli = hardware_clock::kTicksPerMilli;
static const uint16 kTicksPer10Millis = 10 * kTicksPerMilli;

static uint16 accounted_ticks = 0;
static uint32 time_millis = 0;

void loop() {
const uint16 current_ticks = hardware_clock::ticksForNonIsr();

// This 16 bit unsigned arithmetic works well also in case of a timer overflow.
// Assuming at least two loops per timer cycle.
uint16 delta_ticks = current_ticks - accounted_ticks;

// A course increment loop in case we have a large update interval. Improves
// runtime over the single milli update loop below.
while (delta_ticks >= kTicksPer10Millis) {
delta_ticks -= kTicksPer10Millis;
accounted_ticks += kTicksPer10Millis;
time_millis += 10;
}

// Single millis update loop.
while (delta_ticks >= kTicksPerMilli) {
delta_ticks -= kTicksPerMilli;
accounted_ticks += kTicksPerMilli;
time_millis++;
}
}

uint32 timeMillis() {
return time_millis;
}

} // namespace system_clock

+ 33
- 0
smarkant-arduino/lib/LinProcessor/system_clock.h View File

@@ -0,0 +1,33 @@
// 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.

#ifndef SYS_CLOCK_H
#define SYS_CLOCK_H

#include <Arduino.h>
#include "hardware_clock.h"

// Uses the hardware clock to provide a 32 bit milliseconds time since program start.
// The 32 milliseconds time has about 54 days cycle time.
namespace system_clock {
// Call once per main loop(). Updates the internal millis clock based on the hardware
// clock. A calling interval of larger than 280ms will result in loosing time due
// to hardware clock overflow.
extern void loop();

// Return time of last update() in millis since program start. Returns zero if update() was
// never called.
extern uint32 timeMillis();

} // namespace system_clock

#endif

+ 38
- 0
smarkant-arduino/lib/readme.txt View File

@@ -0,0 +1,38 @@

This directory is intended for the project specific (private) libraries.
PlatformIO will compile them to static libraries and link to executable file.

The source code of each library should be placed in separate directory, like
"lib/private_lib/[here are source files]".

For example, see how can be organized `Foo` and `Bar` libraries:

|--lib
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |- readme.txt --> THIS FILE
|- platformio.ini
|--src
|- main.c

Then in `src/main.c` you should use:

#include <Foo.h>
#include <Bar.h>

// rest H/C/CPP code

PlatformIO will find your libraries automatically, configure preprocessor's
include paths and build them.

See additional options for PlatformIO Library Dependency Finder `lib_*`:

http://docs.platformio.org/en/latest/projectconf.html#lib-install


+ 24
- 0
smarkant-arduino/platformio.ini View File

@@ -0,0 +1,24 @@
#
# Project Configuration File
#
# A detailed documentation with the EXAMPLES is located here:
# http://docs.platformio.org/en/latest/projectconf.html
#

# A sign `#` at the beginning of the line indicates a comment
# Comment lines are ignored.

# Simple and base environment
# [env:mybaseenv]
# platform = %INSTALLED_PLATFORM_NAME_HERE%
# framework =
# board =
#
# Automatic targets - enable auto-uploading
# targets = upload

[env:dragon_isp_diecimilaatmega328]
platform = atmelavr
framework = arduino
board = dragon_isp_diecimilaatmega328
upload_flags = -e

+ 410
- 0
smarkant-arduino/src/main.cpp View File

@@ -0,0 +1,410 @@
/*
* This file is part of Smarkant project
*
* (C) 2017 Dirk Grappendorf, www.grappendorf.net
*
* Based on the Hackant project from Robin Reiter
* https://github.com/robin7331/IKEA-Hackant
*/

#include <Arduino.h>
#include <hardware_clock.h>
#include <system_clock.h>
#include <Wire.h>
#include <EEPROM.h>
#include <Bounce2.h>
#include <lin_processor.h>

const uint8_t BUTTON_UP_PIN = 6;
const uint8_t BUTTON_DOWN_PIN = 5;
const uint8_t BUTTON_POSITION_1_PIN = 4;
const uint8_t BUTTON_POSITION_2_PIN = 3;
const uint8_t BUTTON_POSITION_3_PIN = 10;
const uint8_t BUTTON_POSITION_4_PIN = 9;
const uint8_t TABLE_UP_PIN = 8;
const uint8_t TABLE_DOWN_PIN = 7;
const int NUM_POSITION_BUTTONS = 4;
const uint16_t BUTTON_DEBOUNCE_INTERVAL_MS = 5;
const unsigned long POSITION_BUTTON_STORE_DELAY_MS = 1 * 1000;
const uint16_t HEIGHT_READ_MIN = 100;
const uint16_t HEIGHT_READ_MAX = 8000;
const uint16_t HEIGHT_MIN = 500;
const uint16_t HEIGHT_MAX = 6000;
const uint16_t HEIGHT_DEFAULT = 1200;
const uint16_t HEIGHT_THRESHOLD_MIN = 10;
const uint16_t HEIGHT_THRESHOLD_MAX = 200;
const uint16_t HEIGHT_THRESHOLD_DEFAULT = 50;
const uint8_t I2C_ADDRESS = 0x10;
const unsigned long SERIAL_BAUD_RATE = 115200;
const uint16_t EEPROM_ADDR_HEIGHT_THRESHOLD = 0;
const uint16_t EEPROM_ADDR_POSITIONS = sizeof(uint16_t);
const unsigned long WATCHDOG_INTERVAL_MS = 20 * 1000;
const uint8_t LIN_HEIGHT_FRAME_ID = 0x92;

enum Movement {
STOP,
UP,
DOWN,
TARGET
};

enum I2CCommand {
I2C_CMD_NOOP,
I2C_CMD_MOVE_STOP,
I2C_CMD_MOVE_UP,
I2C_CMD_MOVE_DOWN,
I2C_CMD_MOVE_HEIGHT,
I2C_CMD_MOVE_POSITION,
I2C_CMD_STORE_POSITION,
I2C_CMD_STORE_CURRENT_POSITION,
I2C_CMD_STORE_THRESHOLD,
I2C_CMD_READ_HEIGHT,
I2C_CMD_READ_HEIGHT_THRESHOLD,
I2C_CMD_READ_POSITIONS
};

Bounce buttonUp = Bounce();
Bounce buttonDown = Bounce();
uint8_t buttonPositionPin[] = {BUTTON_POSITION_1_PIN, BUTTON_POSITION_2_PIN, BUTTON_POSITION_3_PIN, BUTTON_POSITION_4_PIN};
Bounce buttonPosition[] = {Bounce(), Bounce(), Bounce(), Bounce()};
uint16_t positions[] = {0, 0, 0, 0};
uint16_t currentHeight = 0;
uint16_t targetHeight = 0;
uint16_t heightThreshold = 0;
Movement currentMovement = STOP;
unsigned long watchdogTimeout = 0;
unsigned long positionButtonPressTime = 0;
uint8_t i2cReadCommand = I2C_CMD_NOOP;

void watchdogCheck();
void moveTable(Movement move);
void moveTableToHeight(uint16_t height);
void processLINFrame(LinFrame frame);
void storePosition(int index, uint16_t height);
uint16_t recallPosition(int index);
void storeHeightThreshold(uint16_t threshold);
void handleI2CRequest();
void handleI2CReceive(int numBytes);
void loop();
void setup();
void log(const char *str, ...);

void setup() {
Serial.begin(SERIAL_BAUD_RATE);
log("Smarkant ready...");

hardware_clock::setup();
lin_processor::setup();

pinMode(BUTTON_UP_PIN, INPUT_PULLUP);
pinMode(BUTTON_DOWN_PIN, INPUT_PULLUP);
buttonUp.attach(BUTTON_UP_PIN);
buttonUp.interval(BUTTON_DEBOUNCE_INTERVAL_MS);
buttonDown.attach(BUTTON_DOWN_PIN);
buttonDown.interval(BUTTON_DEBOUNCE_INTERVAL_MS);

for (int i = 0; i < NUM_POSITION_BUTTONS; ++i) {
pinMode(buttonPositionPin[i], INPUT_PULLUP);
buttonPosition[i].attach(buttonPositionPin[i]);
buttonPosition[i].interval(BUTTON_DEBOUNCE_INTERVAL_MS);
EEPROM.get(EEPROM_ADDR_POSITIONS + (i * sizeof(uint16_t)), positions[i]);
if (positions[i] < HEIGHT_MIN || positions[i] > HEIGHT_MAX) {
log("Storing default position %d", i);
storePosition(i, HEIGHT_DEFAULT);
}
}

EEPROM.get(EEPROM_ADDR_HEIGHT_THRESHOLD, heightThreshold);
if (heightThreshold < HEIGHT_THRESHOLD_MIN || heightThreshold > HEIGHT_THRESHOLD_MAX) {
log("Storing default height threshold");
storeHeightThreshold(HEIGHT_THRESHOLD_DEFAULT);
}

pinMode(TABLE_UP_PIN, INPUT);
pinMode(TABLE_DOWN_PIN, INPUT);
digitalWrite(TABLE_UP_PIN, LOW);
digitalWrite(TABLE_DOWN_PIN, LOW);

Wire.begin(I2C_ADDRESS);
Wire.onRequest(handleI2CRequest);
Wire.onReceive(handleI2CReceive);
}

void loop() {
watchdogCheck();

system_clock::loop();
LinFrame frame;
if (lin_processor::readNextFrame(&frame)) {
processLINFrame(frame);
}

buttonUp.update();
buttonDown.update();
if (buttonUp.rose() || buttonDown.rose()) {
moveTable(STOP);
}
if(buttonUp.fell()) {
log("Button UP");
moveTable(currentMovement == STOP ? UP : STOP);
}
if(buttonDown.fell()) {
log("Button DOWN");
moveTable(currentMovement == STOP ? DOWN : STOP);
}

for (int i = 0; i < NUM_POSITION_BUTTONS; ++i) {
buttonPosition[i].update();
if(buttonPosition[i].fell()) {
positionButtonPressTime = millis();
}
if(buttonPosition[i].rose()) {
if (millis() > positionButtonPressTime + POSITION_BUTTON_STORE_DELAY_MS) {
log("Button POSITION STORE %d", i);
storePosition(i, currentHeight);
} else {
log("Button POSITION RECALL %d", i);
moveTableToHeight(recallPosition(i));
}
}
}
}

void watchdogCheck() {
if (currentMovement != STOP && millis() > watchdogTimeout) {
log("Watchdog timeout");
moveTable(STOP);
}
}

void moveTable(Movement move) {
if (move != STOP) {
watchdogTimeout = millis() + WATCHDOG_INTERVAL_MS;
}
currentMovement = move;
switch (move) {
case STOP:
targetHeight = 0;
pinMode(TABLE_UP_PIN, INPUT);
pinMode(TABLE_DOWN_PIN, INPUT);
break;
case UP:
targetHeight = 0;
pinMode(TABLE_UP_PIN, OUTPUT);
pinMode(TABLE_DOWN_PIN, INPUT);
break;
case DOWN:
targetHeight = 0;
pinMode(TABLE_UP_PIN, INPUT);
pinMode(TABLE_DOWN_PIN, OUTPUT);
break;
case TARGET:
if (targetHeight != 0) {
if (currentHeight < targetHeight - heightThreshold) {
pinMode(TABLE_UP_PIN, OUTPUT);
pinMode(TABLE_DOWN_PIN, INPUT);
}
if (currentHeight > targetHeight + heightThreshold) {
pinMode(TABLE_UP_PIN, INPUT);
pinMode(TABLE_DOWN_PIN, OUTPUT);
}
} else {
moveTable(STOP);
}
}
}

void moveTableToHeight(uint16_t height) {
if (height < HEIGHT_MIN || height > HEIGHT_MAX) {
return;
}
targetHeight = height;
moveTable(TARGET);
log("Moving table to height %d", targetHeight);
}

void processLINFrame(LinFrame frame) {
uint8_t id = frame.get_byte(0);
if (id == LIN_HEIGHT_FRAME_ID) {
uint16_t position = frame.get_byte(2);
position <<= 8;
position |= frame.get_byte(1);
if (position != currentHeight && position >= HEIGHT_READ_MIN && position <= HEIGHT_READ_MAX) {
currentHeight = position;
log("Table height: %d", position);
}

if (currentMovement == TARGET) {
if (currentHeight >= targetHeight - heightThreshold &&
currentHeight <= targetHeight + heightThreshold) {
moveTable(STOP);
}
} else if (currentMovement == UP && currentHeight >= HEIGHT_MAX) {
moveTable(STOP);
} else if (currentMovement == DOWN && currentHeight <= HEIGHT_MIN) {
moveTable(STOP);
}
}
}

void storePosition(int index, uint16_t height) {
if (height >= HEIGHT_MIN || height <= HEIGHT_MAX) {
log("Store position %d <= %d", index, height);
EEPROM.put(EEPROM_ADDR_POSITIONS + (index * sizeof(uint16_t)), height);
positions[index] = height;
}
}

uint16_t recallPosition(int index) {
uint16_t height = positions[index];
log("Recall position %d => %d", index, height);
return height;
}

void storeHeightThreshold(uint16_t threshold) {
if (heightThreshold >= HEIGHT_THRESHOLD_MIN || heightThreshold <= HEIGHT_THRESHOLD_MAX) {
log("Storing height threshold %d", threshold);
heightThreshold = threshold;
EEPROM.put(EEPROM_ADDR_HEIGHT_THRESHOLD, threshold);
}
}

void handleI2CRequest() {
switch (i2cReadCommand) {
case I2C_CMD_READ_HEIGHT:
Wire.write((const uint8_t *) & currentHeight, 2);
break;
case I2C_CMD_READ_HEIGHT_THRESHOLD:
Wire.write((const uint8_t *) & heightThreshold, 2);
break;
case I2C_CMD_READ_POSITIONS:
Wire.write((const uint8_t *) positions, NUM_POSITION_BUTTONS * 2);
break;
default:
break;
}
}

void handleI2CReceive(int numBytes) {
uint8_t command = Wire.read();
switch (command) {
case I2C_CMD_MOVE_STOP:
moveTable(STOP);
break;
case I2C_CMD_MOVE_UP:
moveTable(UP);
break;
case I2C_CMD_MOVE_DOWN:
moveTable(DOWN);
break;
case I2C_CMD_MOVE_HEIGHT:
if (numBytes == 3) {
uint16_t position = Wire.read() + (Wire.read() << 8);
moveTableToHeight(position);
}
break;
case I2C_CMD_MOVE_POSITION:
if (numBytes == 2) {
uint8_t index = Wire.read();
if (index < NUM_POSITION_BUTTONS) {
moveTableToHeight(recallPosition(index));
}
}
break;
case I2C_CMD_STORE_POSITION:
if (numBytes == 4) {
uint8_t index = Wire.read();
if (index < NUM_POSITION_BUTTONS) {
uint16_t position = Wire.read() + (Wire.read() << 8);
storePosition(index, position);
}
}
break;
case I2C_CMD_STORE_CURRENT_POSITION:
if (numBytes == 2) {
uint8_t index = Wire.read();
if (index < NUM_POSITION_BUTTONS) {
storePosition(index, currentHeight);
}