Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 4 of 4

Thread: 74 hundred Series logic IC emulation - Teensy 4.x

  1. #1
    Member
    Join Date
    Nov 2020
    Location
    Montana, USA
    Posts
    29

    74 hundred Series logic IC emulation - Teensy 4.x

    I've been working on this project again, this time for the Teensy 4.x. I am emulating the 74 hundred series of Logic IC's starting with the 74HC00 (NAND) and this is my code - working -. The roll section to read an actual IC is not completed yet, I think it will be much simpler than the emulation part.

    I connected 8 switches and 4 LED's to the Teensy. The 8 switches represent the NAND IC inputs and the LED's are connected to emulate the outputs of the NAND.

    Main question is: Is ther an easier method to code this? Not realy necessary because it works. I intend on emulating more logic and want to make corrections now before I get too much more involved.


    Code:
    //  Copyright 2023 Jorge Joaquin Pareja
    //
    //  FileName: HC00.ino
    //  Purpose:  Emulate OR Communicate with 74HC00 Quad 2 Input NAND Gate
    //
    //  NOTE: Keep in mind that teensy 4.x are limited to 3.3v IO, the 74 series of ic's are higher so care should be taken.
    //        The 7400 series device datasheet will indicate (in Recomended Operating Conditions) if the real IC can operate
    //        at Teensy 4.x's voltage levels
    //
    //  Mocrocontroller: Teensy 4.1
    //
    ////////////
    //  Document formatting symbols (forums may mangle this)
    ////////////
    //
    //  ─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼ ═ ║ ╒ ╓ ╔ ╕╖ ╗ ╘ ╙ ╚ ╛╜╝╞ ╟ ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬
    //
    ////////////
    // Documentation
    ////////////
    //
    //  DocumentationSource : Nexperia 74HC00, This IC can operate at 3.3v of Teensy 4.x, other manufacturers may differ.
    //
    //
    //  Pinout DIP 14
    //
    //                    ┌──────┬──┬──────┐
    //                    │      └──┘      │
    //                 1A ┤1   74HC00    14├ VCC
    //                 1B ┤2             13├ 4B
    //                 1Y ┤3             12├ 4A
    //                 2A ┤4             11├ 4Y
    //                 2B ┤5             10├ 3B
    //                 2Y ┤6              9├ 3A
    //                GND ┤7              8├ 3Y
    //                    │                │
    //                    └────────────────┘
    //
    // Truth Table (NAND)
    //
    //                    ┌───────────╥────────┐
    //                    │   INPUTS  ║ OUTPUT │
    //                    ├─────┬─────╫────────┤
    //                    │  A  │  B  ║   Y    │
    //                    ╞═════╪═════╬════════╡
    //                    │  L  │  L  ║   H    │
    //                    ├─────┼─────╫────────┤
    //                    │  L  │  H  ║   H    │
    //                    ├─────┼─────╫────────┤
    //                    │  H  │  L  ║   H    │
    //                    ├─────┼─────╫────────┤
    //                    │  H  │  H  ║   L    │
    //                    └─────┴─────╨────────┘
    //
    //
    ////////////////////
    
    // define the some pins for teensy to act as a 7400 IC (Respecting Teensy's voltage limits)
    #define PIN_1A  0
    #define PIN_1B  1
    #define PIN_1Y  2
    #define PIN_2A  3
    #define PIN_2B  4
    #define PIN_2Y  5
    #define PIN_3A  6
    #define PIN_3B  7
    #define PIN_3Y  8
    #define PIN_4A  9
    #define PIN_4B 10
    #define PIN_4Y 11
    
    #define PIN_ROLL 12 // teensy pin 12 will serve as the input to change the roll of the teensy at runtime
    
    #define BIT0 0
    #define BIT1 1
    #define BIT2 2
    #define BIT3 3
    #define BIT4 4
    #define BIT5 5
    #define BIT6 6
    #define BIT7 7
    
    int lastRoll;
    int currentRoll;
    
    uint8_t inputs;
    uint8_t outputs;
    
    
    // constants can NOT be changed by setting them in code after programmed to the micro
    const int TEENSY_ROLL_READ_IC   = 0; // set the microcontroller to read an actual 74HC00 IC
    const int TEENSY_ROLL_ACT_AS_IC = 1; // act as an 74HC00 IC for testing and learning
    
    void setup() {
      // set direction of roll pin
      pinMode(PIN_ROLL, INPUT_PULLUP);  // defaults to act as IC
    
      // initialize serial communication:
      Serial.begin(115200);
    
      //wait 0.1 second
      delayMicroseconds(100);
    
      lastRoll = 1;  // set to a known value, eg. default state
      getROLL();
    
    }
    
    void loop() {
      //getROLL();
    
      
    
      // print out some data :TODO: check if changes are visible and set lastRole to currentRoll outside of the same function.
      Serial.print("Current roll: ");
      Serial.print(currentRoll, DEC);
      Serial.print(" Last roll: ");
      Serial.print(lastRoll, DEC);
    
      readPins();
      Serial.print(" --- Port Input bits: ");
      printBin(inputs);
    
      setOutputs();
      Serial.print(" --- Port Output bits: ");
      printBin(outputs);
    
      Serial.println();
      delayMicroseconds(250);
    }
    
    void setOutputs(){
      outputs = 0b00000000;
      //////////////////////////////////
      // Port 1
      if( (bitRead(inputs,BIT0) == 1) && (bitRead(inputs,BIT1) ==1) ){
        digitalWrite(PIN_1Y, LOW);
        bitWrite(outputs,BIT0,LOW);
      }
      else{
        digitalWrite(PIN_1Y, HIGH);
        bitWrite(outputs,BIT0,HIGH);
      }
    
      //////////////////////////////////
      // Port 2
      if( (bitRead(inputs,BIT2) == 1) && (bitRead(inputs,BIT3) ==1) ){
        digitalWrite(PIN_2Y, LOW);
        bitWrite(outputs,BIT1,LOW);
      }
      else{
        digitalWrite(PIN_2Y, HIGH);
        bitWrite(outputs,BIT1,HIGH);
      }
    
      //////////////////////////////////
      // Port 3
      if( (bitRead(inputs,BIT4) == 1) && (bitRead(inputs,BIT5) ==1) ){
        digitalWrite(PIN_3Y, LOW);
        bitWrite(outputs,BIT2,LOW);
      }
      else{
        digitalWrite(PIN_3Y, HIGH);
        bitWrite(outputs,BIT2,HIGH);
      }
    
      //////////////////////////////////
      // Port 4
      if( (bitRead(inputs,BIT6) == 1) && (bitRead(inputs,BIT7) ==1) ){
        digitalWrite(PIN_4Y, LOW);
        bitWrite(outputs,BIT3,LOW);
      }
      else{
        digitalWrite(PIN_4Y, HIGH);
        bitWrite(outputs,BIT3,HIGH);
      }
    }
    
    void readPins(){
      inputs = 0b00000000;
    
      inputs |= digitalRead(PIN_1A) << BIT0;
      delayMicroseconds(10);
      inputs |= digitalRead(PIN_1B) << BIT1;
      delayMicroseconds(10);
    
      inputs |= digitalRead(PIN_2A) << BIT2;
      delayMicroseconds(10);
      inputs |= digitalRead(PIN_2B) << BIT3;
      delayMicroseconds(10);
      
      inputs |= digitalRead(PIN_3A) << BIT4;
      delayMicroseconds(10);
      inputs |= digitalRead(PIN_3B) << BIT5;
      delayMicroseconds(10);
      
      inputs |= digitalRead(PIN_4A) << BIT6;
      delayMicroseconds(10);
      inputs |= digitalRead(PIN_4B) << BIT7;
      delayMicroseconds(10);
    }
    
    // Alters IO direction of the respective pins but does not read them
    void setROLL_READ_IC(){
      // we are reading an actual 7400, set pins appropriately
      pinMode(PIN_1A, OUTPUT);  // by default, digital pin 0 is used to send signals to 1A
      pinMode(PIN_1B, OUTPUT);  // by default, digital pin 1 is used to send signals to 1B
      pinMode(PIN_1Y, INPUT);   // read the result from the 7400 1Y output from digital pin 2
    
      pinMode(PIN_2A, OUTPUT);  // same as above with respect to pins for the remaining 7400 ports
      pinMode(PIN_2B, OUTPUT);
      pinMode(PIN_2Y, INPUT);
      
      pinMode(PIN_3A, OUTPUT);
      pinMode(PIN_3B, OUTPUT);
      pinMode(PIN_3Y, INPUT);
      
      pinMode(PIN_4A, OUTPUT);
      pinMode(PIN_4B, OUTPUT);
      pinMode(PIN_4Y, INPUT);
    
      currentRoll = TEENSY_ROLL_READ_IC;
    } // end setROLL_READ_IC
    
    // Alters IO direction of the respective pins but does not read them
    void setROLL_ACT_AS_IC(){
      pinMode(PIN_1A, INPUT_PULLUP);
      pinMode(PIN_1B, INPUT_PULLUP);
      pinMode(PIN_1Y, OUTPUT);
    
      pinMode(PIN_2A, INPUT_PULLUP);
      pinMode(PIN_2B, INPUT_PULLUP);
      pinMode(PIN_2Y, OUTPUT);
      
      pinMode(PIN_3A, INPUT_PULLUP);
      pinMode(PIN_3B, INPUT_PULLUP);
      pinMode(PIN_3Y, OUTPUT);
      
      pinMode(PIN_4A, INPUT_PULLUP);
      pinMode(PIN_4B, INPUT_PULLUP);
      pinMode(PIN_4Y, OUTPUT);
    
      currentRoll = TEENSY_ROLL_ACT_AS_IC;
    } // end setROLL_ACT_AS_IC
    
    // checks to see if the controll pin (PIN_ROLL) has changed
    void getROLL(){
      //read ROLL pin
      currentRoll = digitalRead(PIN_ROLL);
      delayMicroseconds(10);
    
      if(currentRoll != lastRoll)
      {
        switch(currentRoll){
          case TEENSY_ROLL_READ_IC:
            setROLL_READ_IC();
            break;
          case TEENSY_ROLL_ACT_AS_IC:
            setROLL_ACT_AS_IC();
            break;
          default:
            break;
        }//end switch
    
        lastRoll = currentRoll;
      } // end if
    } // end getROLL
    
    // to print leading zero's in binary numbers.
    // from https://forum.arduino.cc/t/how-can-i-serial-println-a-binary-output-that-will-give-all-the-leading-zeros-of-a-variable/962247
    void printBin(byte aByte) {
      for (int8_t aBit = 7; aBit >= 0; aBit--)
        Serial.write(bitRead(aByte, aBit) ? '1' : '0');
    }
    

  2. #2
    Senior Member
    Join Date
    May 2017
    Posts
    365
    At first glance this project suggests one would use the C bitwise operators, &-AND, |-OR, ^-XOR but the way you have stored your data prevents this. But if all your inputs were kept in the BIT 0 position you could write functions like:

    uint8_t fAND( uint8_t A, uint8_t B ){

    return A & B;
    }

    uint8_t fNAND( uint8_t A, uint8_t B ){

    return !( A & B );
    }

    And this would just simply work rather than implementing the truth table with IF ELSE type of statements.


    Edit to add:

    And if you wanted to cascade gates in the program for example the output of an AND gate feeding an XOR gate:

    Code:
    uint8_t in0 = digitalRead( 0 );
    uint8_t in1 = digitalRead( 1 );
    uint8_t in2 = digitalRead( 2 );
    
    uint8_t node1 = fAND( in0, in1 );
    uint8_t out0 = fXOR( node1, in2 );
    Last edited by rcarr; 01-20-2023 at 03:03 PM.

  3. #3
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,782
    Table lookup is one approach, combine the inputs into a single int and do the lookup, then expand for the outputs - the advantage is each logic function just needs a different table, not more coding, the disavantage is the amount of Flash memory consumed for the tables.

  4. #4
    Junior Member
    Join Date
    Jan 2023
    Posts
    3
    Personally I'd read all the values into one value (may as well use a uint32_t, it's a 32 bit processor) and then use bitwise operations to calculate all the outputs at the same time.
    If you define the pin numbers as an array then it becomes simple to expand the logic to any number of gates (well up to 32 but that would be 96 IO pins which isn't going to happen anyway).

    Code:
    const int gates = 4;
    const int InputA[] = {PIN_1A ,PIN_2A,PIN_3A,PIN_4A};
    const int InputB[] = {PIN_1B ,PIN_2B,PIN_3B,PIN_4B};
    const int Output[] = {PIN_1Y ,PIN_2Y,PIN_3Y,PIN_4Y};
    
    uint32_t InputAAll = 0;
    uint32_t InputBAll = 0;
    
    // read inputs into single value
    for (int i=0;i<gates;i++) {
      InputAAll |= digitalRead(InputA[i])<<i;
      InputBAll |= digitalRead(InputB[i])<<i;
    }
    
    //NAND
    uint32_t OutputAll = ~(InputAAll & InputBAll);  // Output = Not (A and B)
    
    //AND
    // uint32_t OutputAll = InputAAll & InputBAll;
    //NOR
    // uint32_t OutputAll = ~(InputAAll | InputBAll);
    //OR
    // uint32_t OutputAll = InputAAll | InputBAll;
    //XOR
    // uint32_t OutputAll = InputAAll ^ InputBAll;
    
    // set outputs
    for (int i=0;i<gates;i++) {
      digitalWrite (Output[i], (OutputAll >> i)&0x01 );
    }
    Now if you wanted to get clever, if you defined the input and output pins correctly (1A, 1B and 1Y were all the same pin number on different logical GPIO ports) and used direct register reads and writes you could read all the inputs in 2 CPU instructions, 1 or 2 instructions for the logic operation and finally 1 instruction to write all the outputs at the same time. That's 32 gates of logic in 5 clock cycles.
    But that's probably too restricting on pin choices and not worth the effort.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •