#include #include #include #include #include #include #include "board_profile.h" static_assert(F_CPU == meCoffee::hardware::board::kClockFrequency, "Clock frequency does not match the board profile"); static_assert(meCoffee::hardware::board::kAdcPrescaler == 128, "ADC register configuration requires prescaler 128"); // Optional start-up padding that lines this image up with the original's start-up time in // differential runs. Zero in the default build. #ifndef MECOFFEE_CONSTRUCTION_ALIGNMENT_MICROSECONDS #define MECOFFEE_CONSTRUCTION_ALIGNMENT_MICROSECONDS 0 #endif #ifndef MECOFFEE_SETUP_ALIGNMENT_MICROSECONDS #define MECOFFEE_SETUP_ALIGNMENT_MICROSECONDS 0 #endif #include #include namespace meCoffee::hardware { void beginSerialLink(uint32_t baudRate) { #if MECOFFEE_SETUP_ALIGNMENT_MICROSECONDS > 0 static bool isFirstCall = true; if (isFirstCall) { isFirstCall = false; delayMicroseconds(MECOFFEE_SETUP_ALIGNMENT_MICROSECONDS); } #endif Serial.begin(baudRate); } void writeSerialByte(uint8_t byteToTransmit) { Serial.write(byteToTransmit); } int readSerialByte() { return Serial.read(); } void waitForSerialTransmitCompletion() { Serial.flush(); } uint32_t readMillisecondCounter() { return millis(); } uint32_t readMicrosecondCounter() { return micros(); } void blockForMilliseconds(uint32_t durationInMilliseconds) { delay(durationInMilliseconds); } uint8_t readPersistentByte(uint16_t persistentAddress) { return eeprom_read_byte(reinterpret_cast(persistentAddress)); } void writePersistentByte(uint16_t persistentAddress, uint8_t byteToStore) { eeprom_write_byte(reinterpret_cast(persistentAddress), byteToStore); } uint8_t readProgramMemoryByte(const void* programMemoryAddress) { return pgm_read_byte(programMemoryAddress); } uint8_t readDeviceSignatureByte(uint8_t signatureByteIndex) { // The signature read must complete within a few cycles of the control write. const uint8_t savedStatusRegister = SREG; cli(); const uint8_t signatureByte = boot_signature_byte_get(signatureByteIndex); SREG = savedStatusRegister; return signatureByte; } void configureLogicalPin(uint8_t logicalPin, PinConfiguration configuration) { const uint8_t physicalPin = board::physicalPin(logicalPin); switch (configuration) { case PinConfiguration::Input: pinMode(physicalPin, INPUT); break; case PinConfiguration::InputWithPullUp: pinMode(physicalPin, INPUT_PULLUP); break; case PinConfiguration::Output: pinMode(physicalPin, OUTPUT); break; } } void writeLogicalPin(uint8_t logicalPin, bool isHigh) { digitalWrite(board::physicalPin(logicalPin), isHigh ? HIGH : LOW); } bool readLogicalPin(uint8_t logicalPin) { return digitalRead(board::physicalPin(logicalPin)) != LOW; } void selectAnalogChannel(uint8_t channelNumber) { ADMUX = static_cast(0xc0 | (board::analogChannel(channelNumber) & 0x0f)); } void startAnalogConversion() { ADCSRA |= _BV(ADSC); } bool isAnalogConversionRunning() { return (ADCSRA & _BV(ADSC)) != 0; } uint16_t readAnalogConversionResult() { const uint8_t lowByte = ADCL; const uint8_t highByte = ADCH; return static_cast(lowByte | (highByte << 8)); } void clearPulseOutputPins() { #if MECOFFEE_ARDUINO_16MHZ PORTD &= static_cast(~(_BV(PD3) | _BV(PD4) | _BV(PD5))); #else PORTB &= static_cast(~(_BV(PB1) | _BV(PB2))); PORTD &= static_cast(~_BV(PD3)); #endif } void writeRangeIndicatorPin(uint8_t rangeSelector, bool isHigh) { #if MECOFFEE_ARDUINO_16MHZ const uint8_t rangeMask = rangeSelector == 0 ? _BV(PC2) : _BV(PC3); if (isHigh) { PORTC |= rangeMask; } else { PORTC &= static_cast(~rangeMask); } #else if (rangeSelector == 0) { if (isHigh) { PORTB |= _BV(PB7); } else { PORTB &= static_cast(~_BV(PB7)); } } else if (isHigh) { PORTD |= _BV(PD7); } else { PORTD &= static_cast(~_BV(PD7)); } #endif } void writeGenerationParityPin(bool isHigh) { #if MECOFFEE_ARDUINO_16MHZ if (isHigh) { PORTB |= _BV(PB3); } else { PORTB &= static_cast(~_BV(PB3)); } #else if (isHigh) { PORTC |= _BV(PC0); } else { PORTC &= static_cast(~_BV(PC0)); } #endif } void startPulseTimerAndMainsInterrupt() { // Mains edge: logical pin 2 (INT0), no pull-up, both edges. pinMode(2, INPUT); EICRA = static_cast((EICRA & ~(_BV(ISC01) | _BV(ISC00))) | _BV(ISC00)); EIMSK |= _BV(INT0); // Output pins that follow the sensor range flags and generation parity. #if MECOFFEE_ARDUINO_16MHZ DDRC |= _BV(PC2) | _BV(PC3); PORTC &= static_cast(~(_BV(PC2) | _BV(PC3))); DDRB |= _BV(PB3); PORTB |= _BV(PB3); #else DDRB |= _BV(PB7); PORTB &= static_cast(~_BV(PB7)); DDRC |= _BV(PC0); PORTC |= _BV(PC0); #endif // Pulse timer: Timer 1, CTC, no prescaling, compare interrupt every 16.125 microseconds. TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; OCR1A = board::kPulseTimerCompare; TCCR1B = _BV(WGM12) | _BV(CS10); TIMSK1 = _BV(OCIE1A); // Analog converter: internal reference, first temperature channel, prescaler 128. selectAnalogChannel(3); ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); } void disableInterrupts() { cli(); } void enableInterrupts() { sei(); } uint8_t readOscillatorCalibrationRegister() { return OSCCAL; } void writeOscillatorCalibrationRegister(uint8_t calibrationValue) { if (!board::kUsesExternalClock) { OSCCAL = calibrationValue; } } } // namespace meCoffee::hardware ISR(TIMER1_COMPA_vect) { const meCoffee::PulseTickResult tickResult = meCoffee::advancePulseTick(&meCoffee::firmwareStateStorage); if ((tickResult.toggleMask & 0x01) != 0) { PIND = _BV(PD3); } if ((tickResult.toggleMask & 0x02) != 0) { #if MECOFFEE_ARDUINO_16MHZ PIND = _BV(PD4); #else PINB = _BV(PB2); #endif } if ((tickResult.toggleMask & 0x04) != 0) { #if MECOFFEE_ARDUINO_16MHZ PIND = _BV(PD5); #else PINB = _BV(PB1); #endif } if (tickResult.shouldStartConversion) { ADCSRA |= _BV(ADSC); } } ISR(INT0_vect) { meCoffee::processMainsEdge(&meCoffee::firmwareStateStorage); } namespace { // Mirrors the original's static constructors: settings are restored before the // Arduino runtime starts its millisecond timer. struct ConstructionTimeInitializer { ConstructionTimeInitializer() { meCoffee::initializeAtConstruction(); #if MECOFFEE_CONSTRUCTION_ALIGNMENT_MICROSECONDS > 0 delayMicroseconds(MECOFFEE_CONSTRUCTION_ALIGNMENT_MICROSECONDS); #endif } }; const ConstructionTimeInitializer constructionTimeInitializer; } // namespace