diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 7fcfc73..b2ee920 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -63,6 +63,10 @@ // playing sounds with IO_I2CDFPlayer #define PLAYSOUND ANOUT +// SEG7 is a helper to create ANOUT from a 7-segment requets +#define SEG7(vpin,value,format) \ + ANOUT(vpin,(value & 0xFFFF),TM1638::DF_##format,((uint32_t)value)>>16) + // helper macro to strip leading zeros off time inputs // (10#mins)%100) #define STRIP_ZERO(value) 10##value%100 diff --git a/IODevice.h b/IODevice.h index 05df4df..09fd08d 100644 --- a/IODevice.h +++ b/IODevice.h @@ -570,6 +570,6 @@ protected: #include "IO_EncoderThrottle.h" #include "IO_TCA8418.h" #include "IO_NeoPixel.h" - +#include "IO_TM1638.h" #endif // iodevice_h diff --git a/IO_TM1638.cpp b/IO_TM1638.cpp new file mode 100644 index 0000000..45614f6 --- /dev/null +++ b/IO_TM1638.cpp @@ -0,0 +1,217 @@ +/* + * © 2024, Chris Harlow. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* Credit to https://github.com/dvarrel/TM1638 for the basic formulae.*/ + + +#include +#include "IODevice.h" +#include "DIAG.h" + + +const uint8_t HIGHFLASH _digits[16]={ + 0b00111111,0b00000110,0b01011011,0b01001111, + 0b01100110,0b01101101,0b01111101,0b00000111, + 0b01111111,0b01101111,0b01110111,0b01111100, + 0b00111001,0b01011110,0b01111001,0b01110001 + }; + + // Constructor + TM1638::TM1638(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin){ + _firstVpin = firstVpin; + _nPins = 8; + _clk_pin = clk_pin; + _stb_pin = stb_pin; + _dio_pin = dio_pin; + pinMode(clk_pin,OUTPUT); + pinMode(stb_pin,OUTPUT); + pinMode(dio_pin,OUTPUT); + _pulse = PULSE1_16; + + _buttons=0; + _leds=0; + _lastLoop=micros(); + addDevice(this); + } + + + void TM1638::create(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin) { + if (checkNoOverlap(firstVpin,8)) + new TM1638(firstVpin, clk_pin,dio_pin,stb_pin); + } + + void TM1638::_begin() { + displayClear(); + test(); + _display(); + } + + + void TM1638::_loop(unsigned long currentMicros) { + if (currentMicros - _lastLoop > (1000000UL/LoopHz)) { + _buttons=getButtons();// Read the buttons + _lastLoop=currentMicros; + } + } + + void TM1638::_display() { + DIAG(F("TM1638 Configured on Vpins:%u-%u"), _firstVpin, _firstVpin+_nPins-1); + } + +// digital read gets button state +int TM1638::_read(VPIN vpin) { + byte pin=vpin - _firstVpin; + bool result=bitRead(_buttons,pin); + // DIAG(F("TM1638 read (%d) buttons %x = %d"),pin,_buttons,result); + return result; +} + +// digital write sets led state +void TM1638::_write(VPIN vpin, int value) { + // TODO.. skip if no state change + writeLed(vpin - _firstVpin + 1,value!=0); + } + +// Analog write sets digit displays + +void TM1638::_writeAnalogue(VPIN vpin, int lowBytes, uint8_t mode, uint16_t highBytes) { + // mode is in DataFormat defined above. + byte formatLength=mode & 0x0F; // last 4 bits + byte formatType=mode & 0xF0; // + int8_t leftDigit=vpin-_firstVpin; // 0..7 from left + int8_t rightDigit=leftDigit+formatLength-1; // 0..7 from left + + // loading is done right to left startDigit first + int8_t startDigit=7-rightDigit; // reverse as 7 on left + int8_t lastDigit=7-leftDigit; // reverse as 7 on left + uint32_t value=highBytes; + value<<=16; + value |= (uint16_t)lowBytes; + + //DIAG(F("TM1638 fl=%d ft=%x sd=%d ld=%d v=%l vx=%X"), + // formatLength,formatType,startDigit,lastDigit,value,value); + while(startDigit<=lastDigit) { + switch (formatType) { + case _DF_DECIMAL:// decimal (leading zeros) + displayDig(startDigit,GETHIGHFLASH(_digits,(value%10))); + value=value/10; + break; + case _DF_HEX:// HEX (leading zeros) + displayDig(startDigit,GETHIGHFLASH(_digits,(value & 0x0F))); + value>>=4; + break; + case _DF_RAW:// Raw 7-segment pattern + displayDig(startDigit,value & 0xFF); + value>>=8; + break; + default: + DIAG(F("TM1368 invalid mode 0x%x"),mode); + return; + } + startDigit++; + } +} + +uint8_t TM1638::getButtons(){ + ArduinoPins::fastWriteDigital(_stb_pin, LOW); + writeData(INSTRUCTION_READ_KEY); + pinMode(_dio_pin, INPUT); + ArduinoPins::fastWriteDigital(_clk_pin, LOW); + uint8_t buttons=0; + for (uint8_t eachByte=0; eachByte<4;eachByte++) { + uint8_t value = 0; + for (uint8_t eachBit = 0; eachBit < 8; eachBit++) { + ArduinoPins::fastWriteDigital(_clk_pin, HIGH); + value |= ArduinoPins::fastReadDigital(_dio_pin) << eachBit; + ArduinoPins::fastWriteDigital(_clk_pin, LOW); + } + buttons |= value << eachByte; + delayMicroseconds(1); + } + pinMode(_dio_pin, OUTPUT); + ArduinoPins::fastWriteDigital(_stb_pin, HIGH); + return buttons; +} + + +void TM1638::displayDig(uint8_t digitId, uint8_t pgfedcba){ + if (digitId>7) return; + setDataInstruction(DISPLAY_TURN_ON | _pulse); + setDataInstruction(INSTRUCTION_WRITE_DATA| INSTRUCTION_ADDRESS_FIXED); + writeDataAt(FIRST_DISPLAY_ADDRESS+14-(digitId*2), pgfedcba); +} + +void TM1638::displayClear(){ + setDataInstruction(DISPLAY_TURN_ON | _pulse); + setDataInstruction(INSTRUCTION_WRITE_DATA | INSTRUCTION_ADDRESS_FIXED); + for (uint8_t i=0;i<15;i+=2){ + writeDataAt(FIRST_DISPLAY_ADDRESS+i,0x00); + } +} + +void TM1638::writeLed(uint8_t num,bool state){ + if ((num<1) | (num>8)) return; + setDataInstruction(DISPLAY_TURN_ON | _pulse); + setDataInstruction(INSTRUCTION_WRITE_DATA | INSTRUCTION_ADDRESS_FIXED); + writeDataAt(FIRST_DISPLAY_ADDRESS + (num*2-1), state); +} + + +void TM1638::writeData(uint8_t data){ + for (uint8_t i = 0; i < 8; i++) { + ArduinoPins::fastWriteDigital(_dio_pin, data & 1); + data >>= 1; + ArduinoPins::fastWriteDigital(_clk_pin, HIGH); + ArduinoPins::fastWriteDigital(_clk_pin, LOW); + } +} + +void TM1638::writeDataAt(uint8_t displayAddress, uint8_t data){ + ArduinoPins::fastWriteDigital(_stb_pin, LOW); + writeData(displayAddress); + writeData(data); + ArduinoPins::fastWriteDigital(_stb_pin, HIGH); + delayMicroseconds(1); +} + +void TM1638::setDataInstruction(uint8_t dataInstruction){ + ArduinoPins::fastWriteDigital(_stb_pin, LOW); + writeData(dataInstruction); + ArduinoPins::fastWriteDigital(_stb_pin, HIGH); + delayMicroseconds(1); +} + +void TM1638::test(){ + DIAG(F("TM1638 test")); + uint8_t val=0; + for(uint8_t i=0;i<5;i++){ + setDataInstruction(DISPLAY_TURN_ON | _pulse); + setDataInstruction(INSTRUCTION_WRITE_DATA| INSTRUCTION_ADDRESS_AUTO); + ArduinoPins::fastWriteDigital(_stb_pin, LOW); + writeData(FIRST_DISPLAY_ADDRESS); + for(uint8_t i=0;i<16;i++) + writeData(val); + ArduinoPins::fastWriteDigital(_stb_pin, HIGH); + delay(1000); + val = ~val; + } + +} + + diff --git a/IO_TM1638.h b/IO_TM1638.h new file mode 100644 index 0000000..9907e6a --- /dev/null +++ b/IO_TM1638.h @@ -0,0 +1,134 @@ + /* + * © 2024, Chris Harlow. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +#ifndef IO_TM1638_h +#define IO_TM1638_h +#include +#include "IODevice.h" +#include "DIAG.h" + +class TM1638 : public IODevice { +private: + + uint8_t _buttons; + uint8_t _leds; + unsigned long _lastLoop; + static const int LoopHz=20; + + static const byte + INSTRUCTION_WRITE_DATA=0x40, + INSTRUCTION_READ_KEY=0x42, + INSTRUCTION_ADDRESS_AUTO=0x40, + INSTRUCTION_ADDRESS_FIXED=0x44, + INSTRUCTION_NORMAL_MODE=0x40, + INSTRUCTION_TEST_MODE=0x48, + + FIRST_DISPLAY_ADDRESS=0xC0, + + DISPLAY_TURN_OFF=0x80, + DISPLAY_TURN_ON=0x88; + + + uint8_t _clk_pin; + uint8_t _stb_pin; + uint8_t _dio_pin; + uint8_t _pulse; + bool _isOn; + + + // Constructor + TM1638(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin); + +public: + enum DigitFormat : byte { + // last 4 bits are length. + // DF_1.. DF_8 decimal + DF_1=0x01,DF_2=0x02,DF_3=0x03,DF_4=0x04, + DF_5=0x05,DF_6=0x06,DF_7=0x07,DF_8=0x08, + // DF_1X.. DF_8X HEX + DF_1X=0x11,DF_2X=0x12,DF_3X=0x13,DF_4X=0x14, + DF_5X=0x15,DF_6X=0x16,DF_7X=0x17,DF_8X=0x18, + // DF_1R .. DF_4R raw 7 segmnent data + // only 4 because HAL analogWrite only passes 4 bytes + DF_1R=0x21,DF_2R=0x22,DF_3R=0x23,DF_4R=0x24, + + // bits of data conversion type (ored with length) + _DF_DECIMAL=0x00,// right adjusted decimal unsigned leading zeros + _DF_HEX=0x10, // right adjusted hex leading zeros + _DF_RAW=0x20 // bytes are raw 7-segment pattern (max length 4) + }; + + static void create(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin); + + // Functions overridden in IODevice + void _begin(); + void _loop(unsigned long currentMicros) override ; + void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override; + void _display() override ; + int _read(VPIN pin) override; + void _write(VPIN pin,int value) override; + + // Device driving functions + private: + enum pulse_t { + PULSE1_16, + PULSE2_16, + PULSE4_16, + PULSE10_16, + PULSE11_16, + PULSE12_16, + PULSE13_16, + PULSE14_16 + }; + + /** + * @fn getButtons + * @return state of 8 buttons + */ + uint8_t getButtons(); + + /** + * @fn writeLed + * @brief put led ON or OFF + * @param num num of led(1-8) + * @param state (true or false) + */ + void writeLed(uint8_t num, bool state); + + + /** + * @fn displayDig + * @brief set 7 segment display + dot + * @param digitId num of digit(0-7) + * @param val value 8 bits + */ + void displayDig(uint8_t digitId, uint8_t pgfedcba); + + /** + * @fn displayClear + * @brief switch off all leds and segment display + */ + void displayClear(); + void test(); + void writeData(uint8_t data); + void writeDataAt(uint8_t displayAddress, uint8_t data); + void setDisplayMode(uint8_t displayMode); + void setDataInstruction(uint8_t dataInstruction); +}; +#endif diff --git a/KeywordHasher.h b/KeywordHasher.h index 4b1e0fb..fe85eb4 100644 --- a/KeywordHasher.h +++ b/KeywordHasher.h @@ -54,4 +54,43 @@ static_assert("MAIN"_hk == 11339,"Keyword hasher error"); static_assert("SLOW"_hk == -17209,"Keyword hasher error"); static_assert("SPEED28"_hk == -17064,"Keyword hasher error"); static_assert("SPEED128"_hk == 25816,"Keyword hasher error"); + +// Compile time converter from "abcd"_s7 to the 7 segment nearest equivalent + +constexpr uint8_t seg7Digits[]={ + 0b00111111,0b00000110,0b01011011,0b01001111, // 0..3 + 0b01100110,0b01101101,0b01111101,0b00000111, // 4..7 + 0b01111111,0b01101111 // 8..9 + }; + +constexpr uint8_t seg7Letters[]={ + 0b01110111,0b01111100,0b00111001,0b01011110, // ABCD + 0b01111001,0b01110001,0b00111101,0b01110110, // EFGH + 0b00000100,0b00011110,0b01110010,0b00111000, //IJKL + 0b01010101,0b01010100,0b01011100,0b01110011, // MNOP + 0b10111111,0b01010000,0b01101101,0b01111000, // QRST + 0b00111110,0b00011100,0b01101010,0b01001001, //UVWX + 0b01100110,0b01011011 //YZ + }; +constexpr uint8_t seg7Space=0b00000000; +constexpr uint8_t seg7Minus=0b01000000; +constexpr uint8_t seg7Equals=0b01001000; + + +constexpr uint32_t CompiletimeSeg7(const char * sv, uint32_t running, size_t rlen) { + return (*sv==0 || rlen==0) ? running << (8*rlen) : CompiletimeSeg7(sv+1, + (*sv >= '0' && *sv <= '9') ? (running<<8) | seg7Digits[*sv-'0'] : + (*sv >= 'A' && *sv <= 'Z') ? (running<<8) | seg7Letters[*sv-'A'] : + (*sv >= 'a' && *sv <= 'z') ? (running<<8) | seg7Letters[*sv-'a'] : + (*sv == '-') ? (running<<8) | seg7Minus : + (*sv == '=') ? (running<<8) | seg7Equals : + (running<<8) | seg7Space, + rlen-1 + ); // +} + +constexpr uint32_t operator""_s7(const char * keyword, size_t len) +{ + return CompiletimeSeg7(keyword,0*len,4); +} #endif \ No newline at end of file diff --git a/Release_Notes/TM1638.md b/Release_Notes/TM1638.md new file mode 100644 index 0000000..adec7ae --- /dev/null +++ b/Release_Notes/TM1638.md @@ -0,0 +1,84 @@ +## TM1638 ## + +The TM1638 board provides a very cheap way of implementing 8 buttons, 8 leds and an 8 digit 7segment display in a package requiring just 5 Dupont wires (vcc, gnd + 3 GPIO pins) from the command station without soldering. + + +This is ideal for prototyping and testing, simulating sensors and signals, displaying states etc. For a built layout, this could provide a control for things that are not particularly suited to throttle 'route' buttons, perhaps lineside automations or fiddle yard lane selection. + +By adding a simple HAL statement to myAutomation.h it creates 8 buttons/sensors and 8 leds. + +`HAL(TM1638,500,29,31,33)` +Creates VPINs 500-507 And desscribes the GPIO pins used to connect the clk,dio,stb pins on the TM1638 board. + +Setting each of the VPINs will control the associated LED (using for example SET, RESET or BLINK in Exrail or ` from a command). + +Unlike most pins, you can also read the same pin number and get the button state, using Exrail IF/AT/ONBUTTON etc. + +For example: +` +HAL(TM1638,500,29,31,33) +` +All the folowing examples assume you are using VPIN 500 as the first, leftmost, led/button on the TM1638 board. + + +`ONBUTTON(500) + SET(500) // light the first led + BLINK(501,500,500) // blink the second led + SETLOCO(3) FWD(50) // set a loco going + AT(501) STOP // press second button to stop + RESET(500) RESET(501) // turn leds off + DONE +` + +Buttons behave like any other sensor, so using `` will cause the command station to issue `` and `` messages when the first button is pressed or released. + +Exrail `JMRI_SENSOR(500,8)` will create `