diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7964536 --- /dev/null +++ b/.gitignore @@ -0,0 +1,189 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +#*.pubxml + +# NuGet Packages Directory +packages/* +## TODO: If the tool you use requires repositories.config +## uncomment the next line +#!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since +# NuGet packages use it for MSBuild targets. +# This line needs to be after the ignore of the build folder +# (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml \ No newline at end of file diff --git a/LowLevel.h b/LowLevel.h new file mode 100644 index 0000000..35ed948 --- /dev/null +++ b/LowLevel.h @@ -0,0 +1,114 @@ +#ifndef _LowLevel_h +#define _LowLevel_h + +#include + +#if ARDUINO >= 100 +#include "Arduino.h" // for delayMicroseconds, digitalPinToBitMask, etc +#else +#include "WProgram.h" // for delayMicroseconds +#include "pins_arduino.h" // for digitalPinToBitMask, etc +#endif + +#if defined(__AVR__) +#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM asm("r30") +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) + +#elif defined(__MK20DX128__) || defined(__MK20DX256__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (1) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (*((base)+512)) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) + +#elif defined(__SAM3X8E__) +// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. +// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 +// If you have trouble with OneWire on Arduino Due, please check the +// status of delayMicroseconds() before reporting a bug in OneWire! +#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) +#ifndef PROGMEM +#define PROGMEM +#endif +#ifndef pgm_read_byte +#define pgm_read_byte(addr) (*(const uint8_t *)(addr)) +#endif + +#elif defined(__PIC32MX__) +#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 +#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 +#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 +#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 + +#else +#error "Please define I/O register types here" +#endif + +class Pin +{ +private: + volatile IO_REG_TYPE *reg_; + IO_REG_TYPE mask_; + byte interruptNumber_; + +public: + Pin() + : mask_(0) + , reg_(0) + , interruptNumber_((byte)-1) + { } + + Pin(uint8_t pin) + { + mask_ = PIN_TO_BITMASK(pin); + reg_ = PIN_TO_BASEREG(pin); + + switch (pin) + { + case 2: interruptNumber_ = 0; break; + case 3: interruptNumber_ = 1; break; + default: interruptNumber_ = (byte)-1; + } + } + + inline void inputMode() { DIRECT_MODE_INPUT(reg_, mask_); } + inline void outputMode() { DIRECT_MODE_OUTPUT(reg_, mask_); } + + inline bool read() { return DIRECT_READ(reg_, mask_) == 1; } + inline void writeLow() { DIRECT_WRITE_LOW(reg_, mask_); } + inline void writeHigh() { DIRECT_WRITE_HIGH(reg_, mask_); } + inline void write(bool value) { if (value) writeHigh(); else writeLow(); } + + inline void attachInterrupt(void (*handler)(), int mode) + { + EIFR |= (1 << INTF0); // clear any pending interrupt (we want to call the handler only for interrupts happending after it is attached) + ::attachInterrupt(interruptNumber_, handler, mode); + } + inline void detachInterrupt() { ::detachInterrupt(interruptNumber_); } +}; + +#endif diff --git a/OneWireIO.sln b/OneWireIO.sln new file mode 100644 index 0000000..f10e9f5 --- /dev/null +++ b/OneWireIO.sln @@ -0,0 +1,32 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OneWireIO", "OneWireIO.vcxproj", "{3B500971-1570-460F-81C3-22AC3B7764B9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|Win32 = Debug|Win32 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3B500971-1570-460F-81C3-22AC3B7764B9}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {3B500971-1570-460F-81C3-22AC3B7764B9}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {3B500971-1570-460F-81C3-22AC3B7764B9}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {3B500971-1570-460F-81C3-22AC3B7764B9}.Debug|Win32.ActiveCfg = Debug|Win32 + {3B500971-1570-460F-81C3-22AC3B7764B9}.Debug|Win32.Build.0 = Debug|Win32 + {3B500971-1570-460F-81C3-22AC3B7764B9}.Release|Any CPU.ActiveCfg = Release|Win32 + {3B500971-1570-460F-81C3-22AC3B7764B9}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {3B500971-1570-460F-81C3-22AC3B7764B9}.Release|Mixed Platforms.Build.0 = Release|Win32 + {3B500971-1570-460F-81C3-22AC3B7764B9}.Release|Win32.ActiveCfg = Release|Win32 + {3B500971-1570-460F-81C3-22AC3B7764B9}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/OneWireIO.vcxproj b/OneWireIO.vcxproj new file mode 100644 index 0000000..e27473d --- /dev/null +++ b/OneWireIO.vcxproj @@ -0,0 +1,111 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {3B500971-1570-460F-81C3-22AC3B7764B9} + OneWireIO + OneWireIO_Demo + + + + Application + true + v120 + MultiByte + + + Application + false + v120 + true + MultiByte + + + + + + + + + + + + + + + Level3 + Disabled + true + D:\Outils\Arduino\hardware\arduino\avr\cores\arduino;D:\Outils\Arduino\hardware\tools\avr\avr\include + _MBCS;%(PreprocessorDefinitions);ARDUINO=160;__AVR__;UBRRH;__AVR_ATmega328__ + + + true + + + if exist $(ProjectDir)$(IntDir)$(ProjectName).cpp.elf ( +echo Elf generated +) else ( +echo no output +exit /B 1 +) + + + + if exist $(ProjectDir)$(IntDir)$(ProjectName).cpp.elf ( +echo Elf generated +) else ( +echo no output +exit /B 1 +) + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + true + + + + + Document + false + if exist $(ProjectDir)$(IntDir)$(ProjectName).cpp.elf del $(ProjectDir)$(IntDir)$(ProjectName).cpp.elf +D:\Outils\Arduino\arduino.exe --pref build.path=$(ProjectDir)$(IntDir) --upload %(FullPath) + + always_build_$(ProjectConfiguration).dummy + + + Building and uploading sketch... + + + + + true + + + + + + + + + + \ No newline at end of file diff --git a/OneWireIO.vcxproj.filters b/OneWireIO.vcxproj.filters new file mode 100644 index 0000000..fbe64d9 --- /dev/null +++ b/OneWireIO.vcxproj.filters @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OneWireIO_Demo.ino b/OneWireIO_Demo.ino new file mode 100644 index 0000000..cc2c9f6 --- /dev/null +++ b/OneWireIO_Demo.ino @@ -0,0 +1,75 @@ +#include "Arduino.h" +#include "LowLevel.h" +#include "OneWireSlave.h" + +#define LEDPin 13 + +// This is the pin that will be used for one-wire data (depending on your arduino model, you are limited to a few choices, because some pins don't have complete interrupt support) +// On Arduino Uno, you can use pin 2 or pin 3 +#define OWPin 2 + +Pin led(LEDPin); + +// This is the ROM the arduino will respond to, make sure it doesn't conflict with another device +byte owROM[7] = { 0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; + +byte acknowledge = 0x42; + +// This sample implements a simple protocol : sending match ROM, then the ROM, then 0x01 will turn the arduino light on. Sending 0x02 will turn it off. In each case, the byte 0x42 is sent as acknowledgement. +const byte CMD_TurnOn = 0x01; +const byte CMD_TurnOff = 0x02; + +// This function will be called each time the OneWire library has an event to notify (reset, error, byte received) +void owReceive(OneWireSlave::ReceiveEvent evt, byte data); + +void setup() +{ + led.outputMode(); + led.writeLow(); + + // Setup the OneWire library + OneWire.setReceiveCallback(&owReceive); + OneWire.begin(owROM, OWPin); +} + +void loop() +{ + delay(1); + + // You can do anything you want here, the OneWire library works entirely in background, using interrupts. + + cli();//disable interrupts + // Be sure to not block interrupts for too long, OneWire timing is very tight for some operations. 1 or 2 microseconds (yes, microseconds, not milliseconds) can be too much depending on your master controller, but then it's equally unlikely that you block exactly at the moment where it matters. + // This can be mitigated by using error checking and retry in your high-level communication protocol. A good thing to do anyway. + sei();//enable interrupts +} + +void owReceive(OneWireSlave::ReceiveEvent evt, byte data) +{ + switch (evt) + { + case OneWireSlave::RE_Byte: + if (data == CMD_TurnOn) + { + led.writeHigh(); + } + else if (data == CMD_TurnOff) + { + led.writeLow(); + } + else + { + break; + } + + // in this simple example we just reply with one byte to say we've processed the command + // a real application should have a CRC system to ensure messages are not corrupt, for both directions + // you can use the static OneWireSlave::crc8 method to add CRC checks in your communication protocol (it conforms to standard one-wire CRC checks, that is used to compute the ROM last byte for example) + OneWire.write(&acknowledge, 1, NULL); + + break; + + default: + ; // we could also react to reset and error notifications, but not in this sample + } +} diff --git a/OneWireSlave.cpp b/OneWireSlave.cpp new file mode 100644 index 0000000..916ae28 --- /dev/null +++ b/OneWireSlave.cpp @@ -0,0 +1,573 @@ +#include "OneWireSlave.h" + +// uncomment this line to enable sending messages along with errors (but takes more program memory) +//#define ERROR_MESSAGES + +#ifdef ERROR_MESSAGES +#define ERROR(msg) error_(msg) +#else +#define ERROR(msg) error_(0) +#endif + +namespace +{ + const unsigned long ResetMinDuration = 480; + const unsigned long ResetMaxDuration = 900; + + const unsigned long PresenceWaitDuration = 30; + const unsigned long PresenceDuration = 300; + + const unsigned long ReadBitSamplingTime = 25; + + const unsigned long SendBitDuration = 35; + + const byte ReceiveCommand = (byte)-1; + + void(*timerEvent)() = 0; +} + +OneWireSlave OneWire; + +byte OneWireSlave::rom_[8]; +byte OneWireSlave::scratchpad_[8]; +Pin OneWireSlave::pin_; +byte OneWireSlave::tccr1bEnable_; + +unsigned long OneWireSlave::resetStart_; +unsigned long OneWireSlave::lastReset_; + +void(*OneWireSlave::receiveBitCallback_)(bool bit, bool error); +void(*OneWireSlave::bitSentCallback_)(bool error); +void(*OneWireSlave::clientReceiveCallback_)(ReceiveEvent evt, byte data); + +byte OneWireSlave::receivingByte_; + +byte OneWireSlave::searchRomBytePos_; +byte OneWireSlave::searchRomBitPos_; +bool OneWireSlave::searchRomInverse_; + +byte* OneWireSlave::buffer_; +short OneWireSlave::bufferLength_; +byte OneWireSlave::bufferBitPos_; +short OneWireSlave::bufferPos_; +void(*OneWireSlave::receiveBytesCallback_)(bool error); +void(*OneWireSlave::sendBytesCallback_)(bool error); + + +ISR(TIMER1_COMPA_vect) // timer1 interrupt +{ + TCCR1B = 0; // disable clock + void(*event)() = timerEvent; + timerEvent = 0; + event(); +} + +void OneWireSlave::begin(byte* rom, byte pinNumber) +{ + pin_ = Pin(pinNumber); + resetStart_ = (unsigned long)-1; + lastReset_ = 0; + + memcpy(rom_, rom, 7); + rom_[7] = crc8(rom_, 7); + + // log("Enabling 1-wire library") + + cli(); // disable interrupts + pin_.inputMode(); + pin_.writeLow(); // make sure the internal pull-up resistor is disabled + + // prepare hardware timer + TCCR1A = 0; + TCCR1B = 0; + TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt + tccr1bEnable_ = (1 << WGM12) | (1 << CS11) | (1 << CS10); // turn on CTC mode with 64 prescaler + + // start 1-wire activity + beginWaitReset_(); + sei(); // enable interrupts +} + +void OneWireSlave::end() +{ + #ifdef DEBUG_LOG + debug.append("Disabling 1-wire library"); + #endif + + cli(); + disableTimer_(); + pin_.detachInterrupt(); + releaseBus_(); + sei(); +} + +void OneWireSlave::write(byte* bytes, short numBytes, void(*complete)(bool error)) +{ + cli(); + beginWriteBytes_(bytes, numBytes, complete == 0 ? noOpCallback_ : complete); + sei(); +} + +byte OneWireSlave::crc8(byte* data, short numBytes) +{ + byte crc = 0; + + while (numBytes--) { + byte inbyte = *data++; + for (byte i = 8; i; i--) { + byte mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) crc ^= 0x8C; + inbyte >>= 1; + } + } + return crc; +} + +void OneWireSlave::setTimerEvent_(short delayMicroSeconds, void(*handler)()) +{ + delayMicroSeconds -= 10; // remove overhead (tuned on Arduino Uno) + + short skipTicks = (delayMicroSeconds - 3) / 4; // round the micro seconds delay to a number of ticks to skip (4us per tick, so 4us must skip 0 tick, 8us must skip 1 tick, etc.) + if (skipTicks < 1) skipTicks = 1; + TCNT1 = 0; + OCR1A = skipTicks; + timerEvent = handler; + TCCR1B = tccr1bEnable_; +} + +void OneWireSlave::disableTimer_() +{ + TCCR1B = 0; +} + +void OneWireSlave::onEnterInterrupt_() +{ + #ifdef DEBUG_LOG + dbgOutput.writeLow(); + #endif +} + +void OneWireSlave::onLeaveInterrupt_() +{ + #ifdef DEBUG_LOG + dbgOutput.writeHigh(); + #endif +} + +void OneWireSlave::error_(const char* message) +{ +#ifdef DEBUG_LOG + if (message == 0) + debug.append("unspecified error"); + else + debug.append(message); +#endif + beginWaitReset_(); +} + +void OneWireSlave::pullLow_() +{ + pin_.outputMode(); + pin_.writeLow(); +} + +void OneWireSlave::releaseBus_() +{ + pin_.inputMode(); +} + +void OneWireSlave::beginResetDetection_() +{ + setTimerEvent_(ResetMinDuration - 50, &OneWireSlave::resetCheck_); + resetStart_ = micros() - 50; +} + +void OneWireSlave::cancelResetDetection_() +{ + disableTimer_(); + resetStart_ = (unsigned long)-1; +} + +void OneWireSlave::resetCheck_() +{ + onEnterInterrupt_(); + if (!pin_.read()) + { + pin_.attachInterrupt(&OneWireSlave::waitReset_, CHANGE); + // log("Reset detected during another operation"); + } + onLeaveInterrupt_(); +} + +void OneWireSlave::beginReceiveBit_(void(*completeCallback)(bool bit, bool error)) +{ + receiveBitCallback_ = completeCallback; + pin_.attachInterrupt(&OneWireSlave::receive_, FALLING); +} + +void OneWireSlave::receive_() +{ + onEnterInterrupt_(); + + pin_.detachInterrupt(); + setTimerEvent_(ReadBitSamplingTime, &OneWireSlave::readBit_); + + onLeaveInterrupt_(); +} + +void OneWireSlave::readBit_() +{ + onEnterInterrupt_(); + + bool bit = pin_.read(); + if (bit) + cancelResetDetection_(); + else + beginResetDetection_(); + receiveBitCallback_(bit, false); + //dbgOutput.writeLow(); + //dbgOutput.writeHigh(); + + onLeaveInterrupt_(); +} + +void OneWireSlave::beginSendBit_(bool bit, void(*completeCallback)(bool error)) +{ + bitSentCallback_ = completeCallback; + if (bit) + { + pin_.attachInterrupt(&OneWireSlave::sendBitOne_, FALLING); + } + else + { + pin_.attachInterrupt(&OneWireSlave::sendBitZero_, FALLING); + } +} + +void OneWireSlave::sendBitOne_() +{ + onEnterInterrupt_(); + + beginResetDetection_(); + bitSentCallback_(false); + + onLeaveInterrupt_(); +} + +void OneWireSlave::sendBitZero_() +{ + pullLow_(); // this must be executed first because the timing is very tight with some master devices + + onEnterInterrupt_(); + + pin_.detachInterrupt(); + setTimerEvent_(SendBitDuration, &OneWireSlave::endSendBitZero_); + + onLeaveInterrupt_(); +} + +void OneWireSlave::endSendBitZero_() +{ + onEnterInterrupt_(); + + releaseBus_(); + bitSentCallback_(false); + + onLeaveInterrupt_(); +} + +void OneWireSlave::beginWaitReset_() +{ + disableTimer_(); + pin_.attachInterrupt(&OneWireSlave::waitReset_, CHANGE); + resetStart_ = (unsigned int)-1; +} + +void OneWireSlave::waitReset_() +{ + onEnterInterrupt_(); + bool state = pin_.read(); + unsigned long now = micros(); + if (state) + { + if (resetStart_ == (unsigned int)-1) + { + onLeaveInterrupt_(); + return; + } + + unsigned long resetDuration = now - resetStart_; + resetStart_ = (unsigned int)-1; + if (resetDuration >= ResetMinDuration) + { + if (resetDuration > ResetMaxDuration) + { + ERROR("Reset too long"); + onLeaveInterrupt_(); + return; + } + + lastReset_ = now; + pin_.detachInterrupt(); + setTimerEvent_(PresenceWaitDuration - (micros() - now), &OneWireSlave::beginPresence_); + if (clientReceiveCallback_ != 0) + clientReceiveCallback_(RE_Reset, 0); + } + } + else + { + resetStart_ = now; + } + onLeaveInterrupt_(); +} + +void OneWireSlave::beginPresence_() +{ + pullLow_(); + setTimerEvent_(PresenceDuration, &OneWireSlave::endPresence_); +} + +void OneWireSlave::endPresence_() +{ + releaseBus_(); + + beginWaitCommand_(); +} + +void OneWireSlave::beginWaitCommand_() +{ + bufferPos_ = ReceiveCommand; + beginReceive_(); +} + +void OneWireSlave::beginReceive_() +{ + receivingByte_ = 0; + bufferBitPos_ = 0; + beginReceiveBit_(&OneWireSlave::onBitReceived_); +} + +void OneWireSlave::onBitReceived_(bool bit, bool error) +{ + if (error) + { + ERROR("Invalid bit"); + if (bufferPos_ >= 0) + receiveBytesCallback_(true); + return; + } + + receivingByte_ |= ((bit ? 1 : 0) << bufferBitPos_); + ++bufferBitPos_; + + if (bufferBitPos_ == 8) + { + // log("received byte", (long)receivingByte_); + + if (bufferPos_ == ReceiveCommand) + { + bufferPos_ = 0; + switch (receivingByte_) + { + case 0xF0: // SEARCH ROM + beginSearchRom_(); + return; + case 0x33: // READ ROM + beginWriteBytes_(rom_, 8, &OneWireSlave::noOpCallback_); + return; + case 0x55: // MATCH ROM + beginReceiveBytes_(scratchpad_, 8, &OneWireSlave::matchRomBytesReceived_); + return; + case 0xCC: // SKIP ROM + // emulate a match rom operation + memcpy(scratchpad_, rom_, 8); + matchRomBytesReceived_(false); + return; + default: + ERROR("Unknown command"); + return; + } + } + else + { + buffer_[bufferPos_++] = receivingByte_; + receivingByte_ = 0; + bufferBitPos_ = 0; + if (bufferPos_ == bufferLength_) + { + beginWaitReset_(); + receiveBytesCallback_(false); + return; + } + } + } + + beginReceiveBit_(&OneWireSlave::onBitReceived_); +} + +void OneWireSlave::beginSearchRom_() +{ + searchRomBytePos_ = 0; + searchRomBitPos_ = 0; + searchRomInverse_ = false; + + beginSearchRomSendBit_(); +} + +void OneWireSlave::beginSearchRomSendBit_() +{ + byte currentByte = rom_[searchRomBytePos_]; + bool currentBit = bitRead(currentByte, searchRomBitPos_); + bool bitToSend = searchRomInverse_ ? !currentBit : currentBit; + + beginSendBit_(bitToSend, &OneWireSlave::continueSearchRom_); +} + +void OneWireSlave::continueSearchRom_(bool error) +{ + if (error) + { + ERROR("Failed to send bit"); + return; + } + + searchRomInverse_ = !searchRomInverse_; + if (searchRomInverse_) + { + beginSearchRomSendBit_(); + } + else + { + beginReceiveBit_(&OneWireSlave::searchRomOnBitReceived_); + } +} + +void OneWireSlave::searchRomOnBitReceived_(bool bit, bool error) +{ + if (error) + { + ERROR("Bit read error during ROM search"); + return; + } + + byte currentByte = rom_[searchRomBytePos_]; + bool currentBit = bitRead(currentByte, searchRomBitPos_); + + if (bit == currentBit) + { + ++searchRomBitPos_; + if (searchRomBitPos_ == 8) + { + searchRomBitPos_ = 0; + ++searchRomBytePos_; + } + + if (searchRomBytePos_ == 8) + { + // log("ROM sent entirely"); + + beginWaitReset_(); + } + else + { + beginSearchRomSendBit_(); + } + } + else + { + // log("Leaving ROM search"); + beginWaitReset_(); + } +} + +void OneWireSlave::beginWriteBytes_(byte* data, short numBytes, void(*complete)(bool error)) +{ + buffer_ = data; + bufferLength_ = numBytes; + bufferPos_ = 0; + bufferBitPos_ = 0; + sendBytesCallback_ = complete; + + bool bit = bitRead(buffer_[0], 0); + beginSendBit_(bit, &OneWireSlave::bitSent_); +} + +void OneWireSlave::bitSent_(bool error) +{ + if (error) + { + ERROR("error sending a bit"); + sendBytesCallback_(true); + return; + } + + ++bufferBitPos_; + if (bufferBitPos_ == 8) + { + bufferBitPos_ = 0; + ++bufferPos_; + } + + if (bufferPos_ == bufferLength_) + { + beginWaitReset_(); + sendBytesCallback_(false); + return; + } + + bool bit = bitRead(buffer_[bufferPos_], bufferBitPos_); + beginSendBit_(bit, &OneWireSlave::bitSent_); +} + +void OneWireSlave::beginReceiveBytes_(byte* buffer, short numBytes, void(*complete)(bool error)) +{ + buffer_ = buffer; + bufferLength_ = numBytes; + bufferPos_ = 0; + receiveBytesCallback_ = complete; + beginReceive_(); +} + +void OneWireSlave::noOpCallback_(bool error) +{ + if (error) + ERROR("error during an internal 1-wire operation"); +} + +void OneWireSlave::matchRomBytesReceived_(bool error) +{ + if (error) + { + ERROR("error receiving match rom bytes"); + return; + } + + if (memcmp(rom_, scratchpad_, 8) == 0) + { + // log("ROM matched"); + + beginReceiveBytes_(scratchpad_, 1, &OneWireSlave::notifyClientByteReceived_); + } + else + { + // log("ROM not matched"); + + beginWaitReset_(); + } +} + +void OneWireSlave::notifyClientByteReceived_(bool error) +{ + if (error) + { + if (clientReceiveCallback_ != 0) + clientReceiveCallback_(RE_Error, 0); + ERROR("error receiving custom bytes"); + return; + } + + beginReceiveBytes_(scratchpad_, 1, &OneWireSlave::notifyClientByteReceived_); + if (clientReceiveCallback_ != 0) + clientReceiveCallback_(RE_Byte, scratchpad_[0]); +} diff --git a/OneWireSlave.h b/OneWireSlave.h new file mode 100644 index 0000000..9eb279d --- /dev/null +++ b/OneWireSlave.h @@ -0,0 +1,108 @@ +#ifndef _OneWireSlave_h_ +#define _OneWireSlave_h_ + +#include "Arduino.h" +#include "LowLevel.h" + +class OneWireSlave +{ +public: + enum ReceiveEvent + { + RE_Reset, //!< The master has sent a general reset + RE_Byte, //!< The master just sent a byte of data + RE_Error //!< A communication error happened (such as a timeout) ; the library will stop all 1-wire activities until the next reset + }; + + //! Starts listening for the 1-wire master, on the specified pin, as a virtual slave device identified by the specified ROM (7 bytes, starting from the family code, CRC will be computed internally). Reset, Presence, SearchRom and MatchRom are handled automatically. The library will use the external interrupt on the specified pin (note that this is usually not possible with all pins, depending on the board), as well as one hardware timer. Blocking interrupts (either by disabling them explicitely with sei/cli, or by spending time in another interrupt) can lead to malfunction of the library, due to tight timing for some 1-wire operations. + void begin(byte* rom, byte pinNumber); + + //! Stops all 1-wire activities, which frees hardware resources for other purposes. + void end(); + + //! Sets (or replaces) a function to be called when something is received. The callback is executed from interrupts and should be as short as possible. Failure to return quickly can prevent the library from correctly reading the next byte. + void setReceiveCallback(void(*callback)(ReceiveEvent evt, byte data)) { clientReceiveCallback_ = callback; } + + //! Enqueues the specified bytes in the send buffer. They will be sent in the background. The optional callback is used to notify when the bytes are sent, or if an error occured. Callbacks are executed from interrupts and should be as short as possible. + void write(byte* bytes, short numBytes, void(*complete)(bool error)); + + static byte crc8(byte* data, short numBytes); + +private: + static void setTimerEvent_(short delayMicroSeconds, void(*handler)()); + static void disableTimer_(); + + static void onEnterInterrupt_(); + static void onLeaveInterrupt_(); + + static void error_(const char* message); + + static void pullLow_(); + static void releaseBus_(); + + static void beginReceiveBit_(void(*completeCallback)(bool bit, bool error)); + static void beginSendBit_(bool bit, void(*completeCallback)(bool error)); + + static void beginResetDetection_(); + static void cancelResetDetection_(); + + static void beginWaitReset_(); + static void beginWaitCommand_(); + static void beginReceive_(); + static void onBitReceived_(bool bit, bool error); + + static void beginSearchRom_(); + static void beginSearchRomSendBit_(); + static void continueSearchRom_(bool error); + static void searchRomOnBitReceived_(bool bit, bool error); + + static void beginWriteBytes_(byte* data, short numBytes, void(*complete)(bool error)); + static void beginReceiveBytes_(byte* buffer, short numBytes, void(*complete)(bool error)); + + static void noOpCallback_(bool error); + static void matchRomBytesReceived_(bool error); + static void notifyClientByteReceived_(bool error); + static void bitSent_(bool error); + + // interrupt handlers + static void waitReset_(); + static void beginPresence_(); + static void endPresence_(); + static void receive_(); + static void readBit_(); + static void sendBitOne_(); + static void sendBitZero_(); + static void endSendBitZero_(); + static void resetCheck_(); + +private: + static byte rom_[8]; + static byte scratchpad_[8]; + static Pin pin_; + static byte tccr1bEnable_; + + static unsigned long resetStart_; + static unsigned long lastReset_; + + static void(*receiveBitCallback_)(bool bit, bool error); + static void(*bitSentCallback_)(bool error); + + static byte receivingByte_; + + static byte searchRomBytePos_; + static byte searchRomBitPos_; + static bool searchRomInverse_; + + static byte* buffer_; + static short bufferLength_; + static short bufferPos_; + static byte bufferBitPos_; + static void(*receiveBytesCallback_)(bool error); + static void(*sendBytesCallback_)(bool error); + + static void(*clientReceiveCallback_)(ReceiveEvent evt, byte data); +}; + +extern OneWireSlave OneWire; + +#endif