Skip to content

Commit

Permalink
Changed behaviour on unsupported or misconfigured Arduino core. Inste…
Browse files Browse the repository at this point in the history
…ad of blocking build with error, now it issues warning and build with old Livolo code without HW Timer usage.

Added an option to DLTransmitter library to set transmitter pin at compile-time to produce shorter interrupt routines. Use this method by default in the firmware, "hardcoding" PIN_B5.
Improved some code comments.
Fixed minor errors in README.md.
  • Loading branch information
N-Storm committed Jun 6, 2024
1 parent 5c025bc commit 19f0d08
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 89 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ Can be built from ArduinoIDE as well, but it will take few more steps to prepare

Firmware code has been developed for
[Digispark module by Digistump](https://en.wikipedia.org/wiki/List_of_Arduino_boards_and_compatible_systems#Digispark).
This module already has everything for running [V-USB](https://www.obdev.at/products/vusb/index.html).
This module already has everything for running [V-USB](https://www.obdev.at/products/vusb/index.html).
This firmware should also work on any other ATTiny85 based Arduino module or bare AVR hardware. Depending on the
board/pin configuration, it might be required to adjust USB-related config headers.
For other AVR-based boards firmware will require some changes in the code, mostly changing V-USB config header to
adapt it to hardware. Partial support for other AVR hardware are in the code, but it's not complete and not tested
adapt it to hardware. Partial support for other AVR hardware are in the code, but it's not complete and not tested
at all as for now. Feel free to open an issue on Github project page to drop a support request for other
Arduino board.

Expand All @@ -38,10 +38,11 @@ into flash even with default 2Kb bootloader, so it should work along with it as
Because PlatformIO doesn't have the latest [DigistumpArduino](https://github.com/ArminJo/DigistumpArduino)
framework, I've included one in `firmware/packages/framework-arduino-avr-digistump`. RF transmitter code
uses hardware Timer 1 to generate accurate waveforms, so I've changed framework configuration to use Timer 0
for millis() instead of defaults using Timer 1 for that. See build section below for details.
for millis() instead of defaults using Timer 1 for that. Thankfully that required to change 1 line in the
`core_build_options.h` file of the framework (set TIMER_TO_USE_FOR_MILLIS to 0 for ATtiny85 build options there).

Library `DLUSB` for the HID USB communications are based on DigiUSB library, included with
`DigistumpArduino`. Compared to original library it was improved slightly, mostly notable is to allow more than
`DigistumpArduino`. Compared to original library it was improved slightly, mostly notable is to allow more than
1 byte bidirectional USB communication via USB HID Reports.

## Usage
Expand Down Expand Up @@ -152,7 +153,7 @@ it from there. Requires PlatformIO plugin installed.

Clone git repo with submodules (these are the build requirements for software part):

```
```shell
git clone --recurse-submodules https://github.com/N-Storm/DigiLivolo
```

Expand Down
169 changes: 94 additions & 75 deletions firmware/lib/DLTransmitter/DLTransmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "DLTransmitter.h"

volatile dl_buffer_u dl_buf = { 0 };

uint8_t txPin_g = 0;

#ifndef __AVR_ATtinyX5__
Expand All @@ -29,94 +30,108 @@ uint8_t txPin_g = 0;

DLTransmitter::DLTransmitter(uint8_t pin) : Livolo(pin)
{
pinMode(pin, OUTPUT);
txPin = pin;
#ifdef DL_STATIC_PIN
pinMode(DL_STATIC_PIN, OUTPUT);
txPin = DL_STATIC_PIN;
#else
pinMode(pin, OUTPUT);
txPin = pin;
#endif
}

// Sequence remoteID, 16 bits followed by keycode, 7 bits
// seq = (remoteID << 7) + (keycode & 0x7F)
/* Sequence begins with remoteID (16 bits), followed by a keycode (7 bits).
* I.e. one code sequence are aired as "(remoteID << 7) + (keycode & 0x7F)".
* We always set remaining MSB in keycode to 1, it's used to find an end of sequence when we're shifting bits on transmit. */

void DLTransmitter::sendButton(uint16_t remoteID, uint8_t keycode, bool use_timer, void (*idleCallback_ptr)(void)) {
// Use new Timer function if available & not used right now
if (use_timer == true && txPin_g == 0) {
txPin_g = txPin;
// Use new Timer function if available & not used right now.
#ifdef DL_TIMER
if (use_timer == true && txPin_g == 0) {
txPin_g = txPin;

// Clear transmit buffer struct
memset((void *)&dl_buf, 0, sizeof(dl_buf));
// Clear transmit buffer struct
memset((void *)&dl_buf, 0, sizeof(dl_buf));

// Set MSB to 1 (it's not transmitted as keycode are 7-bit, but we use it as a end-of-transmit mark)
keycode |= (1 << 7);
// Set MSB to 1 (it's not transmitted as keycode are 7-bit, but we use it as a "end of sequence" mark).
keycode |= (1 << 7);

// Prepare data bits in transmit order (reversed as they are right shifted during transmit).
// Volatile transmit buffer dl_buf will be set from here each repeat.
uint8_t dl_buf_origin[3];
dl_buf_origin[2] = __builtin_avr_insert_bits(0x70123456, keycode, 0);
dl_buf_origin[1] = __builtin_avr_insert_bits(0x01234567, (uint8_t)(remoteID & 0xFF), 0);
dl_buf_origin[0] = __builtin_avr_insert_bits(0x01234567, (uint8_t)(remoteID >> 8), 0);

// Save timer settings if not using native core
#ifndef DL_NATIVE_CORE
uint8_t tccr1_saved, gtccr_saved, tifr_saved, ocr1a_saved, ocr1c_saved;
tccr1_saved = TCCR1;
gtccr_saved = GTCCR;
tifr_saved = TIFR;
ocr1a_saved = OCR1A;
ocr1c_saved = OCR1C;
#endif

// Transmit loop. Packet with one button press are transmitted 128 times, as the original remote does.
for (uint8_t z = 0; z <= DLTRANSMIT_REPEATS; z++) {
memcpy((void *)dl_buf.bytes, dl_buf_origin, 3);

#ifdef __AVR_ATtinyX5__
PORTB |= 1 << txPin;
#else
digitalWrite(txPin, HIGH);
// Prepare data bits in transmit order (reversed as they are right shifted during transmit).
// Volatile transmit buffer dl_buf will be set from here each repeat.
uint8_t dl_buf_origin[3];
dl_buf_origin[2] = __builtin_avr_insert_bits(0x70123456, keycode, 0);
dl_buf_origin[1] = __builtin_avr_insert_bits(0x01234567, (uint8_t)(remoteID & 0xFF), 0);
dl_buf_origin[0] = __builtin_avr_insert_bits(0x01234567, (uint8_t)(remoteID >> 8), 0);

// Save timer registers if not on a native core
#ifndef DL_NATIVE_CORE
uint8_t tccr1_saved, gtccr_saved, tifr_saved, ocr1a_saved, ocr1c_saved;
tccr1_saved = TCCR1;
gtccr_saved = GTCCR;
tifr_saved = TIFR;
ocr1a_saved = OCR1A;
ocr1c_saved = OCR1C;
#endif

timer1_start();
// while (dl_buf.buf > 1);
// TODO: Make non-blocking function
while (dl_buf.buf != 0)
if (idleCallback_ptr != NULL)
idleCallback_ptr();
// Transmit loop. Packet with one button press are transmitted 128 times, as the original remote does.
for (uint8_t z = 0; z <= DLTRANSMIT_REPEATS; z++) {
memcpy((void *)dl_buf.bytes, dl_buf_origin, 3);

#if defined(__AVR_ATtinyX5__) && defined (DL_STATIC_PIN) // ATTiny 25/45/85 has only one IO PORT - B.
PORTB |= 1 << DL_STATIC_PIN;
#elif defined(__AVR_ATtinyX5__)
PORTB |= 1 << txPin;
#else
digitalWrite(txPin, HIGH);
#endif

timer1_start();
// while (dl_buf.buf > 1);
// TODO: Make non-blocking function
while (dl_buf.buf != 0)
if (idleCallback_ptr != NULL)
idleCallback_ptr();

timer1_stop();
}

#if defined(__AVR_ATtinyX5__) && defined (DL_STATIC_PIN)
PORTB &= ~(1 << DL_STATIC_PIN);
#elif defined(__AVR_ATtinyX5__)
PORTB &= ~(1 << txPin);
#else
digitalWrite(txPin, LOW);
#endif

timer1_stop();
}
#if DL_TIMER == DL_TIMER_PLL
PLLCSR &= ~(1 << PCKE);
#endif
#ifndef DL_NATIVE_CORE
#endif

#ifdef __AVR_ATtinyX5__
PORTB &= ~(1 << txPin);
#else
digitalWrite(txPin, LOW);
#endif

timer1_stop();
#if DL_TIMER == DL_TIMER_PLL
PLLCSR &= ~(1 << PCKE);
#endif
#ifndef DL_NATIVE_CORE
#endif

OCR1C = 0xFF;

// Reset interrupt flags
TIFR = (1 << OCF1A | 1 << OCF1B | 1 << TOV1);

// Restore timer-related registers content
#if not defined(DL_NATIVE_CORE) && DL_TIMER == 1
TCCR1 = tccr1_saved;
GTCCR = gtccr_saved;
TIFR = tifr_saved;
OCR1A = ocr1a_saved;
OCR1C = ocr1c_saved;
#endif

txPin_g = 0;
}
else // Use original function
Livolo::sendButton(remoteID, keycode);
OCR1C = 0xFF;

// Reset interrupt flags
TIFR = (1 << OCF1A | 1 << OCF1B | 1 << TOV1);

// Restore timer-related registers content
#if not defined(DL_NATIVE_CORE) && DL_TIMER == 1
TCCR1 = tccr1_saved;
GTCCR = gtccr_saved;
TIFR = tifr_saved;
OCR1A = ocr1a_saved;
OCR1C = ocr1c_saved;
#endif

txPin_g = 0;
}
else // Use original function
#endif
Livolo::sendButton(remoteID, keycode);
}

#ifdef DL_TIMER

/// @brief Initializes and starts Timer 1
void DLTransmitter::timer1_start() {
cli();
Expand Down Expand Up @@ -186,7 +201,9 @@ inline void timer1_update(uint8_t ocr, uint8_t ocr_aux = 0) {
}

void inline switch_txPin() {
#ifdef __AVR_ATtinyX5__
#if defined(__AVR_ATtinyX5__) && defined (DL_STATIC_PIN)
PINB = 1 << DL_STATIC_PIN;
#elif defined(__AVR_ATtinyX5__)
PINB = 1 << txPin_g;
#else
state_buf = !state_buf;
Expand All @@ -213,3 +230,5 @@ ISR(TIMER1_COMPA_vect) {
ISR(TIMER1_COMPB_vect) {
switch_txPin();
}

#endif // ifdef DL_TIMER
28 changes: 21 additions & 7 deletions firmware/lib/DLTransmitter/DLTransmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
// How many times packet are repeated on transmit for one button code
#define DLTRANSMIT_REPEATS 128

/* Uncomment line below to make transmit pin set at compile time ("hardcoded").
* Results in smaller interrupt routines -> more USB stability & RF accuracy.
* If set, pin setting from the constructor call will be ignored. */
#define DL_STATIC_PIN PIN_B5

class DLTransmitter : public Livolo
{
public:
Expand All @@ -51,26 +56,35 @@ class DLTransmitter : public Livolo
#ifdef __cplusplus
} // extern "C"
#endif
#if defined(TIMER_TO_USE_FOR_USER) && TIMER_TO_USE_FOR_USER == 1
#if defined(TIMER_TO_USE_FOR_USER) && (TIMER_TO_USE_FOR_USER == 1)
/* Attiny x5, DigiStump core defines are set & Timer 1 are available for user.
* Looks like we're using bundled reconfigured core ("native") or it was reconfigured by user.
*/
#define DL_NATIVE_CORE
#define DL_TIMER DL_TIMER_PLL
// Timer OCR values for PLL mode on a native core have are precalculated.
#define OCR_HALFBIT 82
#define OCR_FULLBIT 164
#define OCR_START 255
#else
#if defined(TIMER_TO_USE_FOR_USER) && TIMER_TO_USE_FOR_USER == 0
#define DL_TIMER 0
#error "Timer 0 on DigiStump core are not supported yet. Please change TIMER_TO_USE_FOR_MILLIS to 0 in Digistump core to make Timer 1 available."
// Assuming we're on the default DigiStump core.
// #define DL_TIMER 0
#undef DL_TIMER
#warning "Digistump core are configured to use Timer 1 for millis(). Will use old RF routines as fallback. Please change TIMER_TO_USE_FOR_MILLIS to 0 in Digistump core to make Timer 1 available."
#else
#error "Unsupported/unknnown ATTiny85 core or unknown configuration."
#undef DL_TIMER
#warning "Unsupported/unknown ATTiny85 core or unknown configuration. Will use old RF routines as fallback."
#endif
#endif
#else
// Most likely we're on a "classic" Arduino part/core with Timer 1 available for user.
#define DL_TIMER 1
#warn "Not ATTiny85 or unsupported core. Using Timer1, may break things if it's used by this core."
#warning "Not an ATTiny85 or unsupported core. Using Timer1, may break things if it's used by this core."
#endif

#if DL_TIMER != DL_TIMER_PLL
#if defined(DL_TIMER) && (DL_TIMER != DL_TIMER_PLL)
// For a non-PLL mode of Timer operation, we calculate OCR values at a compile time
#define OCR_START_US 530ULL
#define OCR_BIT_US 320ULL
#define DL_TIMER_PRESCALER 64
Expand All @@ -96,4 +110,4 @@ typedef union {

inline void timer1_update(uint8_t ocr, uint8_t ocr_aux);

#endif // __DLTRansmitter_h__
#endif // __DLTRansmitter_h__
2 changes: 1 addition & 1 deletion firmware/lib/DLUSB/usbconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ section at the end of this file).
* with libusb: 0x16c0/0x5dc. Use this VID/PID pair ONLY if you understand
* the implications!
*/
#define USB_CFG_DEVICE_VERSION 0x01, 0x02
#define USB_CFG_DEVICE_VERSION 0x02, 0x02
/* Version number of the device: Minor number first, then major number.
*/
#define USB_CFG_VENDOR_NAME 'd','i','g','i','l','i','v','o','l','o','@','y','a','n','d','e','x','.','c','o','m'
Expand Down
6 changes: 5 additions & 1 deletion firmware/src/DigiLivolo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
#include <DLTransmitter.h>
#include <stdint.h>

DLTransmitter dltransmitter(PIN_B5); // Transmitter connected to pin #5
/* Pin currently set at compile time in DLTransmitter.h. When it defined there, pin from the constructor
* parameter are ignored. To change pin number either edit #define DL_STATIC_PIN line in DLTransmitter.h
* as well or comment out that define and set pin from here. */
DLTransmitter dltransmitter(PIN_B5);

dlusb_packet_t in_buf, out_buf; // Input & outpus USB packet buffers

/// @brief Populates dlusb_packet_t struct with RDY packet which are sent
Expand Down

0 comments on commit 19f0d08

Please sign in to comment.