// ZXIF1_RCX_Communication_V4 // Version: 2021-8-2 // Program runs on Arduino Mega // 2021 7 19: Changed pins 4-9 to 2-7 // Define digital and analog pins #define pin_CTS_out 2 // goes to ZX IF1 CTS (which is an IF1 input) #define pin_RTS_in 3 // goes to ZX IF1 RTS (which is an IF1 output) #define pin_Awaiting_data 4 // drives an indicator LED as in the LEGO tower #define pin_RS232_enabled 5 // enable/disable serial port for RS232 communication #define pin_IR_enabled 6 // enable/disable serial port for RF communication #define pin_RF_enabled 7 // enable/disable serial port for IR communication #define pin_38kHz_out 10 // (timer) pin driving the IR LED. Needs to be 10! #define pin_Analog_switch A0 // one analog input for three keys // Define global variables and flags ------------------------------------------------------------------------- boolean debug = true; // text output on serial 1 (pin 0/1, USB) boolean flag_tower_enabled = false; // flushes tower buffer in first loop void setup() { // Init 38kHz pulse train on pin 11 -------------------------------------------------------------------------- pinMode (pin_38kHz_out, OUTPUT); // Pin 10: Timer 2 "A" output: OC2A // set up Timer 2 TCCR2A = _BV (COM2A0) | _BV(WGM21); // CTC, toggle OC2A on Compare Match TCCR2B = _BV (CS20); // No prescaler OCR2A = 209; // Original value: 209. // Original comment: "compare A register value (210 * clock speed) // = 13.125 nS , so frequency is 1 / (2 * 13.125) = 38095" // This results in 37.83kHz on Uno R3 clone board -> 208 better // On Mega board 209 results in 38.0X kHz -> better // Init IO pins ---------------------------------------------------------------------------------------------- pinMode (pin_CTS_out, OUTPUT); // from Arduino pin 2 to RS232 8 T2o (CTS) and from there to // ZX 4 CTS digitalWrite (pin_CTS_out, LOW); // LOW: ZX 4 CTS +V enables sending from ZX. HIGH: Sending from // ZX disabled. // CTS on ZX always enabled: LOW level not changed in this program pinMode (pin_RTS_in, INPUT); // from ZX 5 RTS to RS232 7 R2i (RTS) and from there to Arduino 5 pinMode (pin_Awaiting_data, OUTPUT); // LED indicating that the Tower awaits a reply digitalWrite (pin_Awaiting_data, LOW); // Tower LED off (LED is wired to GND) pinMode (pin_IR_enabled, OUTPUT); // Gate (LOW = on) and LED driver for IR TX/RX enable digitalWrite (pin_IR_enabled, HIGH); // IR TX/RX disabled; IR indicator LED off (LED is wired to +5V) pinMode (pin_RF_enabled, OUTPUT); // Gate (LOW = on) and LED driver for RF TX RX enable digitalWrite (pin_RF_enabled, HIGH); // RF TX/RX disabled; IR indicator LED off (LED is wired to +5V) pinMode (pin_RS232_enabled, OUTPUT); // Gate (HIGH = on) and LED driver for RS232 TX RX enable digitalWrite (pin_RS232_enabled, HIGH); // RS232 TX/RX disabled; IR indicator LED off (LED is wired to +5V) pinMode (pin_Analog_switch, INPUT_PULLUP); // Analog input A0 used for switch 1-3 detection // 1 = int IR on/off; 2 = RF on/off; 3 = RS232 on/off // Init serial ports ----------------------------------------------------------------------------------------- Serial.begin(9600); // init Mega hardware serial port for monitoring only while (!Serial) {}; // wait for serial port to connect Serial.print("Init done"); Serial1.begin(9600, SERIAL_8N1); // init hardware serial port1 connected to ZX/IF1 Serial2.begin(2400, SERIAL_8O1); // init hardware serial port3 connected to RF in/out = LINX modules // Reset serial port input buffers --------------------------------------------------------------------------- IF1_Flush(); // empty IF1 input Tower_Flush(); // empty Tower input digitalWrite(pin_IR_enabled, LOW); // set default communication line = IR } // end setup ------------------------------------------------------------------------------------------------- void loop() { //------------------------------------------------------------------------------------------------ // There are no unsolicited data arriving at Serial2 form the LEGO tower. // The LEGO protocol is: Send out data to the Tower and then RCX replies. Not the other way around! // This is also "hardwired" into the LEGO tower: It cannot not receive any IR input unless triggered by data // arriving at the tower RxD port, which then triggers a monoflop that has a dwell time of about 3 sec. // When the LEGO tower is meant to "monitor" IR traffic, then it needs to be "re-triggered" every 3 sec. // 2400 Baud (bits/s): 1 "byte" = 1 stop, 8 data, 1 parity, 1 stop = 11 bits. // 1/2400 = 417 us/bit. One "byte" = 11*417 = 4.6 ms. One message = 9*4.6 = 41.4 ms. // 1/9600 = 104 us/bit; 1.15 ms; 10.4 ms. boolean flag_time_out = false; unsigned long tower_RX_enable_time; // millis after ZX sent ID + (delayed) databyte unsigned long ZX_TX_enable_time; // millis after ZX sent first 6 bytes of ID const int max_tower_RX_enable_time = 5000; // timeout for receiving data from NXT. Within this time, the // PBrick needs to reply. 5s for NXT 0 -> 7. RCX reply is within // 1 s. This should be in sync with ZX program const int ZX_TX_timeout = 200; // timeout only way (found so far) to get the two bytes from ZX // and the critical delay between ID and databyte controlled by // ZX only. @9600(IF1) and FOR N=0 TO 7 ZX delay loop // (=> 20 ms + N*18 ms = 146) + 2x13.6 ms MSG + ZX PRINT command // execution: Measured start bit ID to stop bit databyte: 195ms //main loop ---------------------------------- // Check_Switches(); if (flag_tower_enabled == false) { // if ZX did not send any data, do not respond to IR as a lot Tower_Flush(); // of IR "garbage" may accumulate otherwise. This is done } // every main loop cycle and keeps the tower serial inbuf clean if (Serial1.available()>6){ // if(){} required to execute flush >only< after data arrived // and to set flag_tower_enabled time and LED. record time the first ZX_TX_enable_time = millis(); // >6 bytes arrived to make sure ZX 1 or two garbage does not // interfere while (millis()<(ZX_TX_enable_time + ZX_TX_timeout)){ if (Serial1.available()){ // Collect data from ZX for ZX_TX_timeout ms if (debug = false){ Serial2.write(Serial1.read()); // Same as below, without serial monitoring } else { byte s = Serial1.read(); // Read one byte Serial2.write(s); // Push bytes into Serial2 TX "buffer". Apparently there is none // This is >much< faster than Serial2 is sending out data @2400 Serial.print(s); // Debug monitoring @9600 Serial.print(" "); // formatting output } } } // --------------------------------------- //loop until ZX_TX_timeout if (debug = true) { Serial.print("EOL"); // Add "EOL" and CR/LF after time out to monitoring output Serial.println(); } Delay(30); // required (for RCX) to securely flush tower echo in the next step // without delay, last byte(s) sent is(are) read by RCX. This is // no real problem, but data verification time goes up // significantly on ZX Tower_Flush(); // not required but gets rid of Tower echo as the IR detector sends // the data from the IR LED directly into the Serial2 RX channel // ("echo") tower_RX_enable_time = millis(); // start time for tower to listen flag_tower_enabled = true; // enable tower listen loop } SetTowerLED(); // turn LED on/off depending on flag_tower_enabled status // Listen to Tower, but only when a) data were sent by IF1 (flag_tower_enabled is true) and b) something // actually arrives. The ID+data sent (echo) are flushed above. // The receiving loop is cycled until max_tower_RX_enable_time is reached. // For the RCX this time can be considerably shorter than for the NXT, as the NXT send the reply MSG only after // finishing sending out the PF command(s). This may take a long time (even >3s) when going from +7 to -7 as // the PF power is set +/- 1 at a time if (flag_tower_enabled == true){ // listen to IR/RF/RS232 (RCX reply) on Serial2 // otherwise skip to listen to ZX on Serial1 while (millis() < (tower_RX_enable_time + max_tower_RX_enable_time)){ // record and send out entire Serial2 buffer using HW hand // shake via RTS line control by the ZX if (Serial2.available()){ // wait for data - whatever they are - to arrive ... byte tower_data = Serial2.read(); // store one byte - whatever it is while (digitalRead(pin_RTS_in)==HIGH) { // HW handshake (wait loop): wait while pin_RTS_in is held // HIGH = ZX IF1 accepts no data if (millis() > tower_RX_enable_time + max_tower_RX_enable_time){ // when ZX BASIC program is set to no-handshake-mode, pin_RTS_in // does never go low. Checking for timeout again here, otherwise // this loop becomes a trap. ONLY required for non-handshake // operation selected on the ZX BASIC program Tower_Flush(); // time out has occurred; empty Serial2 input buffer; // RCX may have answered (RCX always replies by default) tower_data = 0; // optional; clear last Serial2 data byte received flag_time_out = true; // only for debug mode - notify time out break; // break out of current while wait loop } } Serial1.write(tower_data); // send out one byte to ZX - whatever it is Delay(10); // give ZX some time. Delay(1) does not work; then not all // bytes of a regular reply are transferred, regardless of HW // handshake control. Mandatory! // Delay(5) good for 2400 baud setting, Delay(10) always stable if (debug == true){ // print some debugging data on the serial monitor Serial.print(tower_data); Serial.print(" "); if (flag_time_out == true){ Serial.print("RTS time out"); flag_time_out = false; } } }//if Serial2.available }//while (millis() < ...) flag_tower_enabled = false; // reset "awaiting data LED" in next main loop via SetTowerLED }//if (flag_tower_enabled == true) }//void loop -------------------------------------------------------------------------------------------------------- void Check_Switches(){ int A0_in = analogRead(A0); // depending on key status, the voltage changes if(A0_in > 1000){ return; // no key pressed (5V = 1023); exit back to main loop } //Serial.println(A0_in); // for debugging only if(A0_in < 100){ // enable IR (default), disable all other. Enabled = LOW digitalWrite(pin_RS232_enabled, HIGH); digitalWrite(pin_RF_enabled, HIGH); digitalWrite(pin_IR_enabled, LOW); } else if(A0_in > 200 && A0_in < 300) { // enable RF, disable all other digitalWrite(pin_RS232_enabled, HIGH); digitalWrite(pin_RF_enabled, LOW); digitalWrite(pin_IR_enabled, HIGH); } else if(A0_in > 350 && A0_in < 450) { // enable RS232, disable all other digitalWrite(pin_RS232_enabled, LOW); digitalWrite(pin_RF_enabled, HIGH); digitalWrite(pin_IR_enabled, HIGH); } } void IF1_Flush(){ while(Serial1.available() > 0) { char dump = Serial1.read(); } } void Tower_Flush(){ while(Serial2.available() > 0) { char dump = Serial2.read(); } } void SetTowerLED(){ if (flag_tower_enabled == true) { digitalWrite (pin_Awaiting_data, HIGH); } else { digitalWrite (pin_Awaiting_data, LOW); } } void Delay(int msec){ unsigned long start_time = millis(); while (millis()< (start_time + msec)); }