Teensy register manipulation

BitSeeker

Member
Can someone help me understand Teensy registers please?

Lets start with a simple question. I am referencing the manual on PJRC here:


Sectrion 12.6.1 describes the GPIO registers. 12.6.1.1 says that there are 8 32-bit registers and a table below lists them showing registers GPIO1 through to GPIO9. So I count 9 registers, not 8?

The next question is, how do these map to the actual physical pins on the board? I can't find a reference that shows this anywhere?
 
Take a look at <this> thread. It includes info from @PaulStoffregen on where to find the info that you seek in the Teensy core files, as well as a link to an extremely valuable spreadsheet maintained by @KurtE that may help as well.

Hope that helps . . .

Mark J Culross
KD5RXT
 
Each GPIO actually has 11 registers. Early on NXP designed it with only 8 (the ones at offset 0 to 1C). They added 3 more (the ones at offset 84 to 8C) shortly before finalizing the chip design, but they never edited the word "eight" to "eleven".

There are 9 GPIO peripherals, each GPIO peripheral with 11 registers, for a total of 99 registers.

However, many of the IOMUX registers also configure important properties of the I/O pins, so there's a lot more than only 99.

Might also be worth mentioning 4 pairs control the same pins. Again it was a late addition, where NXP's earlier chip has only 5 GPIO registers. GPIO1-GPIO5 are on the slow peripheral bus. NXP added GPIO6-GPIO9 which duplicate the function of GPIO1-GPIO4 but they are connected to a special low latency bus for much faster access. By default we use the fast GPIO6-GPIO9 registers with Teensy.


The next question is, how do these map to the actual physical pins on the board? I can't find a reference that shows this anywhere?

Use this annotated copy of the reference manual. :)


You'll find the GPIO pin mapping on page 300-303, and also duplicated in most other places the pins are mentioned throughout the manual.

You can also find the NXP pad name to Teensy pin number mapping documented on the schematic on the Teensy 4.1 page. Scroll down to "Schematic", near the end of that very long page.
 
@kd5rxt-mark , thank you for the helpful links to further material. I am still reading the other thread. Its slowly starting to make a little sense - if only to re-enforce the idea that its not meant to make any sense! Curious that its been designed this way almost as if designations have been randomly and deliberately thrown together so as not to form any kind of consistent pattern. I have messed with registers on other platforms but this one seems to be by far the most convoluted! Hopefully things will become clearer in due course!

@PaulStroffregen, thanks for the information. It seems that I need to get away from the notion that a bit in a register corresponds to a pin number in some sort of consistent way.

Your first comment at least explains why it says '8 registers' whereas the list below that shows 9 and the table below that lists 11!

Now I need to understand how the term "GPIO peripheral" relates to GPIO pins and how the pins themselves relate to the resiters. Some of the information from @kd5rxt-mark (once I have read and absorbed it) should help. However, why does one need 11 registers to control a GPIO pin? I know that pins can support ADC, DAC, PWM, GPIO, SPI, I2C and UART/Serial functions although obviously not all pins suport each of those. The board also has ethernet so I guess that accounts for some of those peripheral registers and the registers somehow map functions to pins. I suspect that is where IOMUX comes in?

In my application I need only GPIO functions - i.e. logic input/output high/low and the datasheet states that the IOMUX register must be set to GPIO mode. So far I have not been able to make sense of how to do that.

Interestingly, I also see no mention of input_pullup, which will be essenital for my project and I am hoping that it is supported.

Thanks for the annotated version of the manual and the Teensy development board page. That diagram, but also the annotations in the manual may yet prove to be very helpful. Its early days yet and this is all still looking rather blurry but hopefully things will get clearer eventually.
 
Last edited:
@BitSeeker: Taking one step back in an attempt to understand the bigger picture, as viewed specifically from your perspective, what ultimate goal are you working towards (and all answers are automatically considered correct !!) ?? Are you looking to learn & better understand how the GPIO pins are controlled at the register level ?? Are you looking to set and/or read the GPIO pins as fast as possible ?? Or something else entirely that no one has guessed so far ??

If you're looking to set/read the pins quickly, then calls to digitalWriteFast(<OUTPIN>, <LEVEL>) & digitalReadFast(<INPIN>) in your loop() function are almost certainly the best way to get there. These functions are already optimized for quick GPIO access & don't require any additional detailed understanding of what's going on under the covers at the regsiter level.

Also, calls to pinMode(<PIN>, <MODE>) in your setup() function will initialize all of the registers correctly for your chosen pin in your chosen mode.

If your ultimate goal is something else entirely, maybe a quick description of what you're looking to do would allow anyone atempting to help to provide more detailed assistance, targeted to your specific goal.

Thanks !!

Mark J Culross
KD5RXT
 
The next question is, how do these map to the actual physical pins on the board? I can't find a reference that shows this anywhere?
Reference manual p.399
11.6 IOMUXC Memory Map/Register Definition

Example:
Code:
  // SPDIF_IN
  CORE_PIN15_CONFIG = 3; // mux config registers control which peripheral uses the pin
  //    or
  //
  // Select one of iomux modes to be used for pad: GPIO_AD_B1_03
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_03 = (0b011);
  // 0b000 ALT0 — Select mux mode: ALT0 mux port: USB_OTG1_OC of instance: usb
  // 0b001 ALT1 — Select mux mode: ALT1 mux port: QTIMER3_TIMER3 of instance: qtimer3
  // 0b010 ALT2 — Select mux mode: ALT2 mux port: LPUART2_RX of instance: lpuart2
  // 0b011 ALT3 — Select mux mode: ALT3 mux port: SPDIF_IN of instance: spdif             <---
  // 0b100 ALT4 — Select mux mode: ALT4 mux port: ENET_1588_EVENT2_IN of instance: enet
  // 0b101 ALT5 — Select mux mode: ALT5 mux port: GPIO1_IO19 of instance: gpio1
  // 0b110 ALT6 — Select mux mode: ALT6 mux port: USDHC2_CD_B of instance: usdhc2
  // 0b111 ALT7 — Select mux mode: ALT7 mux port: KPP_COL06 of instance: kpp
  // 0b1000 ALT8 — Select mux mode: ALT8 mux port: GPT2_CAPTURE1 of instance: gpt2
  // 0b1001 ALT9 — Select mux mode: ALT9 mux port: FLEXIO3_FLEXIO03 of instance: flexio3

Example: SPDIF passthrough SPDIF IN to SPDIF OUT - no Teensy Audio Library.
Code:
// SPDIF passthrough SPDIF IN to SPDIF OUT

void setup() {
  CCM_CCGR5 |= CCM_CCGR5_SPDIF(CCM_CCGR_ON); //Clock gate on (15–14 spdif clock (spdif_clk_enable))

  // SPDIF_IN
  // CORE_PIN15_CONFIG = 3; // mux config registers control which peripheral uses the pin
  //  or
  //
  // Select one of iomux modes to be used for pad: GPIO_AD_B1_03
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_03 = (0b011);
  // 0b000 ALT0 — Select mux mode: ALT0 mux port: USB_OTG1_OC of instance: usb
  // 0b001 ALT1 — Select mux mode: ALT1 mux port: QTIMER3_TIMER3 of instance: qtimer3
  // 0b010 ALT2 — Select mux mode: ALT2 mux port: LPUART2_RX of instance: lpuart2
  // 0b011 ALT3 — Select mux mode: ALT3 mux port: SPDIF_IN of instance: spdif             <---
  // 0b100 ALT4 — Select mux mode: ALT4 mux port: ENET_1588_EVENT2_IN of instance: enet
  // 0b101 ALT5 — Select mux mode: ALT5 mux port: GPIO1_IO19 of instance: gpio1
  // 0b110 ALT6 — Select mux mode: ALT6 mux port: USDHC2_CD_B of instance: usdhc2
  // 0b111 ALT7 — Select mux mode: ALT7 mux port: KPP_COL06 of instance: kpp
  // 0b1000 ALT8 — Select mux mode: ALT8 mux port: GPT2_CAPTURE1 of instance: gpt2
  // 0b1001 ALT9 — Select mux mode: ALT9 mux port: FLEXIO3_FLEXIO03 of instance: flexio3
  //
  // SPDIF_IN_SELECT_INPUT DAISY Register
  // (IOMUXC_SPDIF_IN_SELECT_INPUT)
  IOMUXC_SPDIF_IN_SELECT_INPUT = 0; // GPIO_AD_B1_03_ALT3
  // Selecting Pads Involved in Daisy Chain.
  // 1 GPIO_EMC_16_ALT3 — Selecting Pad: GPIO_EMC_16 for Mode: ALT3
  // 0 GPIO_AD_B1_03_ALT3 — Selecting Pad: GPIO_AD_B1_03 for Mode: ALT3 - Teensy 4.x pin 15

  // SPDIF_OUT
  SPDIF_SCR =
    SPDIF_SCR_TXSEL(0b001) |    // TxSel
    // 0b000 - off and output 0
    // 0b001 - Feed-though SPDIFIN
    // 0b101 - Tx Normal operation
    SPDIF_SCR_USRC_SEL(0b01);  // USrc_Sel
    // U channel source select
    // 0b00 - No embedded U channel
    // 0b01 - U channel from SPDIF receive block (CD mode)
    // 0b10 - Reserved
    // 0b11 - U channel from on chip transmitter
   
  // CORE_PIN14_CONFIG = 3;  //3:SPDIF_OUT
  //  or
  //
  // Select one of iomux modes to be used for pad: GPIO_AD_B1_02
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = (0b011);
  // 0b000 ALT0 — Select mux mode: ALT0 mux port: USB_OTG1_ID of instance: anatop
  // 0b001 ALT1 — Select mux mode: ALT1 mux port: QTIMER3_TIMER2 of instance: qtimer3
  // 0b010 ALT2 — Select mux mode: ALT2 mux port: LPUART2_TX of instance: lpuart2
  // 0b011 ALT3 — Select mux mode: ALT3 mux port: SPDIF_OUT of instance: spdif
  // 0b100 ALT4 — Select mux mode: ALT4 mux port: ENET_1588_EVENT2_OUT of instance: enet
  // 0b101 ALT5 — Select mux mode: ALT5 mux port: GPIO1_IO18 of instance: gpio1
  // 0b110 ALT6 — Select mux mode: ALT6 mux port: USDHC1_CD_B of instance: usdhc1
  // 0b111 ALT7 — Select mux mode: ALT7 mux port: KPP_ROW06 of instance: kpp
  // 0b1000 ALT8 — Select mux mode: ALT8 mux port: GPT2_CLK of instance: gpt2
  // 0b1001 ALT9 — Select mux mode: ALT9 mux port: FLEXIO3_FLEXIO02 of instance: flexio3
 
}

void loop() {
  // put your main code here, to run repeatedly:

}
 
Last edited:
However, why does one need 11 registers to control a GPIO pin?

5 of those registers are related to the pin's ability to trigger an interrupt. You don't need these unless you're using the feature where change on the pin causes an interrupt. If you do use the interrupt capability, hopefully it's easy to imagine how these 5 registers are useful.

3 of the registers (the 3 added beyond the original 8) allow modifying bits in the data output (DR) register without having to read it. You don't "need" these, because you could just read the DR register (giving you whatever output value you've previously written), change the bit(s) for the specific pins you want, and write the changed value back. But the ability to change specific bits by only writing is faster and avoids conflicts if you also write to DR from any interrupts.

So that leaves only the first 3 registers, which you can use to write the pins (DR), read the pins (PSR), and configure input vs output mode (GDIR). Those 3 are pretty essential. But perhaps you don't "need" to directly use the DR register because the last 3 registers are used to access it.
 
Last edited:
@BitSeeker: Taking one step back in an attempt to understand the bigger picture, as viewed specifically from your perspective, what ultimate goal are you working towards (and all answers are automatically considered correct !!) ?? Are you looking to learn & better understand how the GPIO pins are controlled at the register level ?? Are you looking to set and/or read the GPIO pins as fast as possible ?? Or something else entirely that no one has guessed so far ??

If you're looking to set/read the pins quickly, then calls to digitalWriteFast(<OUTPIN>, <LEVEL>) & digitalReadFast(<INPIN>) in your loop() function are almost certainly the best way to get there. These functions are already optimized for quick GPIO access & don't require any additional detailed understanding of what's going on under the covers at the regsiter level.

Also, calls to pinMode(<PIN>, <MODE>) in your setup() function will initialize all of the registers correctly for your chosen pin in your chosen mode.

If your ultimate goal is something else entirely, maybe a quick description of what you're looking to do would allow anyone atempting to help to provide more detailed assistance, targeted to your specific goal.

Thanks !!

Mark J Culross
KD5RXT
I am emulating an 8-bit bus. Pinmode (or equivalent) needs be used outside of setup() because the bus is bi-directional being controlled by a second 8-bit control bus, so pins are continually switched between input and output modes on both buses during transmission. On the databus, the output logic levels (0 or 1) need to be switched quite fast and all pins near simultaneously, which is why I was looking at direct register manipulation. On the control bus a max of only two pins need to be switched simultaneously, so its not as critical.

I have actually written functions using digitalReadFast() and digitalWriteFast() as well as using pinMode() to set direction and this does actually work as expected so it may be sufficient although I haven't stress tested it yet, although I do appear to have a working solution. When you look at the output on a logic analyzer, its apparent that the 8 pins on the databus are switched in a stepped fashion as the loop processes each pin in turn. This may or may not be a problem given that the Teensy runs much faster than the ancient technology this is meant to emulate and it may ultimately not be worth the time and effort required to tidy that up so that all pins switch simultaneously using direct register manipulation, but I was trying to see whether that could be accomplished. I was able to do that on the AVR platform and the Raspberry Pico, but the Teensy looks far more complex. That's the difficulty with direct register manipulation. Its different for every platfoirm annd you have to delve into the datasheets and learn how it works, which takes time. Its also usually not very portable and not flexible if one then wants to change the pin configuration.
 
@BitSeeker: Thanks for your very clear explanation. We can now understand exactly what your goal is. I don't have any specific experience with register manipulation, but hopefully, between the spreadsheet that @KurtE maintains & the annotated reference manual, you'll be able to accomplish your end goal.

Good luck !!

Mark J Culross
KD5RXT
 
I did one 8-bit bus back in 2021 for A t a r i ST/e ACSI (Atari Computer Systems Interfac) hard drive Emulator + Media Transfer Protocol (MTP)

Code:
// 19 pin D-SUB male connector at the cable.
//  ---------------------------------
//  \ 01 02 03 04 05 06 07 08 09 10 /
//   \ 11 12 13 14 15 16 17 18 19  /
//     ---------------------------
//  T4.1       bit pin
//01 <-> GPIO6-16 19/A5  AD_B1_00  Data   BIDIR                   Teensy Data BUS 0-7 INPUT & OUTPUT
//02 <-> GPIO6-17 18/A4  AD_B1_01
//03 <-> GPIO6-18 14/A0  AD_B1_02
//04 <-> GPIO6-19 15/A1  AD_B1_03
//05 <-> GPIO6-20 40/A16 AD_B1_04
//06 <-> GPIO6-21 41/A17 AD_B1_05
//07 <-> GPIO6-22 17/A3  AD_B1_06
//08 <-> GPIO6-23 16/A2  AD_B1_07  Data   BIDIR                   Teensy Data BUS 0-7 INPUT & OUTPUT
//
//09 --> GPIO6-25 23/A9  AD_B1_09  _CS    Chip Select             Teensy INPUT ONLY
//10 <-- GPIO7-28 35     B1_12     _IRQ   Interrupt               Teensy OUTPUT_OPENDRAIN,   (1K pullup to 5V on ATARI - Device OPEN collector output) USE SN74LS07 Hex Buffers With Open-Collector
//11 --- GND      GND    GND                                      Ground
//12 --> GPIO7-19 37     B1_03     Reset                          Teensy INPUT ONLY  / Reset T4.1 37
//13 --- GND      GND    GND                                      Ground
//14 --> GPIO7-18 36     B1_02     ACK                            Teensy INPUT ONLY
//15 --- GND      GND    GND                                      Ground
//16 --> GPIO6-24 22/A8  AD_B1_08  A1_st  Address bit             Teensy INPUT ONLY
//17 --- GND      GND    GND                                      Ground
//18 --> GPIO9?   33     EMC_07    R/W    Read/Write              Teensy INPUT ONLY  / TODO pin T4.1--33 ,   T3.5--19     T4, CRAP THIS IS MCLK2 FOR AUDIO OUT PIN
//19 <-- GPIO7-29 34     B1_13     _DRQ   Data request            Teensy OUTPUT_OPENDRAIN,  (1K pullup to 5V on ATARI - Device OPEN collector output) USE SN74LS07
Because of the voltage v5+ vs v3.3+ difference I did use few ICs:
Code:
// _______________________________
// |  INPUTS   |  SN74LVC4245A   |   https://www.ti.com/lit/ds/symlink/sn74lvc4245a.pdf?HQS=dis-dk-null-digikeymode-dsf-pf-null-wwe&ts=1616938602079
// | OE | DIR  |  OPERATION      |   DIR NOTE: Need inverting buffer which produces the state opposite to the R_W on ATARI DB19 pin !!!
// |-----------------------------|   DIR NOTE: So when its High on Atari its Low on DIR pin; From Teensy to Atari, use SN7406 Hex inverter Buffers With Open-Collector ?
// | L  |  L   | B data to A bus |   DIR - Inverted HIGH from ATARI A input to Device B input side, ATARI original signal is LOW from ATARI to Device    COOMMAND write R/_W low
// | L  |  H   | A data to B bus |   DIR - Inverted LOW from Device B input to ATARI A input side, ATARI original signal is HIGH from Device to ATARI    STATUS read R/_W    high ( Device is on B input side )
// | H  |  X   |   Isolation     |   OE - tied to GND  LOW -Enabled, TODO: NOTE 1 -OE
// -------------------------------
// SN74LVC4245A Pin Configuration and Functions pinout.
// ATARI     ----_----     Teensy
// (5V) VCCA-|1    24|-VCCB (3.3V)    , DIR NOTE: Need inverting buffer which produces the state opposite the input for ATARI DIR DB19 pin !!!  ( ATARI DIR = LOW from ATARI to Device - HIGH from Device to ATARI )
//       DIR-|2    23|-VCCB (3.3V)    , ATARI DIR DB19 ACSI connector --> R/W  Read/Write   - Inverted HIGH from ATARI to Device (A data to B bus), Inverted LOW from Device to ATARI (B data to A bus)
//D0(LSB) A1-|3    22|-OE             , OE - tied to GND  LOW -Enabled, TODO: NOTE 1 -OE
//        A2-|4    21|-B1             , A1 ~ A8 (5V)   From ATARI DB19 ACSI connector, 01 to 08 <-> Data BUS 0-7 INPUT & OUTPUT
//        A3-|5    20|-B2             , B1 ~ B8 (3.3V) From Device to ATARI (B data to A bus)
//        A4-|6    19|-B3
//        A5-|7    18|-B4
//        A6-|8    17|-B5
//        A7-|9    16|-B6
//D7(MSB) A8-|10   15|-B7
//       GND-|11   14|-B8
//       GND-|12   13|-GND
//           ---------
// NOTE 1: TODO -OE pin MCU control when (A1 Address bit LOW) & (_CS Chip Select LOW) enabled & read the data bus 1st byte of new command if correct Controller# leave it enabled else disable.
//         TODO -OE issue the control circuitry (DIR, OE) is powered by VCCA 5V.

Here is my pin setup Arduino way:
Code:
  // --- pinMode setup --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // GPIO6_GDIR &= ~0x00FF0000;   //Set the 8 Data pins of GPIO6 as input (note the invert ~)
  pinMode(19, INPUT);        //00  Data GPIO6-16
  pinMode(18, INPUT);        //01  Data
  pinMode(14, INPUT);        //02  Data
  pinMode(15, INPUT);        //03  Data
  pinMode(40, INPUT);        //04  Data
  pinMode(41, INPUT);        //05  Data
  pinMode(17, INPUT);        //06  Data
  pinMode(16, INPUT);        //07  Data GPIO6-23

  pinMode(23, INPUT);        // _CS     GPIO6-25
  pinMode(35, OUTPUT), digitalWriteFast(35, HIGH); // _IRQ   Interrupt    GPIO7-28 pin35
  pinMode(RESET_pin, INPUT); // T3.5 35, T4.1 GPIO7-19 pin37
  pinMode(36, INPUT);        // ACK    B1_02
  pinMode(22, INPUT);        // _A1_st  GPIO6-24
  pinMode(33, INPUT);        // R/W    GPIO9-07 EMC_07                                                                                 I NEED THIS PIN FOR AUDIO OUT
  pinMode(34, OUTPUT), digitalWriteFast(34, HIGH); // _DRQ   Data request GPIO7-29 pin34

  pinMode(OE_SN74LVC4245A, OUTPUT_OPENDRAIN), digitalWriteFast(OE_SN74LVC4245A, HIGH); // T4.1 pin 31 SN74LVC4245A -OE PIN, Active LOW.
  pinMode(OE_SN74LS245N, OUTPUT_OPENDRAIN), digitalWriteFast(OE_SN74LS245N, HIGH); // T4.1 pin 32 -- ACSI OUT -- SN74LS245N -OE PIN (optional), Active LOW-Enabled.
  pinMode(Led, OUTPUT); // LED_BUILTIN, This LED_BUILTIN (pin 13) will change soon, need this pin for SPI SCK.

Low level control:
Code:
// --------------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------
//
//  Low level pin control
//
// --------------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------
// Teensy 4.1 https://forum.pjrc.com/threads/60005-Teensy-4-0-ADC-keeper-issue
// In order to disable the keeper you have to clear the pke bit of the corresponding pin. For the Teensy 4.0 pin 14=A0 it is
// IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_02 &= ~ (1<<12) ; // disable keeper
// AD_B1_02 is the internal name of the pin14=A0, bit 12 is the pke bit in the IOMUXC_SW_PAD_CTL_PAD_GPIO register (see datasheet).

// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Release IRQ and _DRQ pins by putting them back to HIGH, Note: we are using the SN74LS07 Hex Buffer - Driver With Open-Collector High-Voltage Outputs for _IRQ & _DRQ control.
static inline void releaseRq() {
  // The Teensy 4.1 GPIO registers support only 32-bit accesses.
  // well just use digitalWriteFast(IRQ, HIGH);  (inactive)
  digitalWriteFast(_IRQ, HIGH);
  digitalWriteFast(_DRQ, HIGH);
  // GPIO7-28 35     B1_12     _IRQ   Interrupt
  // GPIO7-29 34     B1_13     _DRQ   Data request
  // GPIO7_DR &= ~0x18000000;  //Clear to 0, 0b0001 1000 0000 0000 0000 0000 0000 0000
  // GPIO7_DR |= 0x18000000;   //Write new value
}

// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Release data pins by putting them back to input
static inline void releaseData() {
  GPIO6_GDIR &= ~0x00FF0000;   //Set the 8 Data pins of GPIO6-16 to GPIO6-23 as input (note the invert ~)
}

// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Set data pins as output
static inline void acquireDataBus() {
  GPIO6_GDIR |= 0x00FF0000;  //Set GPIO6-16 to GPIO6-23 as outputs  0B0000 0000 1111 1111 0000 0000 0000 0000
}

// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Release the bus completely
static inline void releaseBus() {
  releaseRq();   // digitalWriteFast(_IRQ, HIGH), digitalWriteFast(_DRQ, HIGH)
  releaseData(); // GPIO6_GDIR &= ~0x00FF0000;   //Set the 8 Data pins of GPIO6-16 to GPIO6-23 as input (note the invert ~)
}

// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Write a byte to the data pins
static inline void WriteData(uint32_t byte_) {
  // t.3x GPIOD_PDOR = byte_ & 0xFF;                       // write all 8 bits in parallel

  GPIO6_DR &= ~0x00FF0000;  //Clear to 0
  GPIO6_DR |= byte_ << 16;  //Write new value to GPIO6-bit16 to GPIO6-bit23
}
 
Now I don't remember if I use these but just in case I am including it here:
Code:
// Pin masks for direct port access Teensy 4.1
#define CS_MASK  0b00000010000000000000000000000000 // 0b0000 0010 0000 0000 0000 0000 0000 0000 -GPIO6-25  pin 23/A9
#define A1_MASK  0b00000001000000000000000000000000 // 0b0000 0001 0000 0000 0000 0000 0000 0000 -GPIO6-24 22/A8  AD_B1_08  A1
#define ACK_MASK 0b00000000000001000000000000000000 // 0b0000 0000 0000 0100 0000 0000 0000 0000 -GPIO7-18 Pin 36 B1_02     ACK                            Teensy INPUT ONLY
// #define DRQ_MASK 0b000000000000000000001000 // 0b0000 0000 0000 0000 0000 1000 -PTB 03  port B pin 18
// #define R_W_MASK 0b000000000000000000000100 // 0b0000 0000 0000 0000 0000 0100 -PTB 02  port B pin 19

One of the interrupt request handlers:
Code:
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void FASTRUN CS_IRQhandler() { // _CS  Chip Select - ATARI is writing command or data to device, or reading handshake or status byte from device.
  // we are inside CS (LOW) interrupt handler
  // CS_IRQhandler() used only for command reading and then use nointerrupt() & bit bang the STATUS byte, but re-enable the interrupts() in STATUS sendStatus(uint8_t s).
  // ToDo _CS _IRQ handshake under cpu control ( use nointerrupt() & bit bang )
  // _______________________________
  // |  INPUTS   |  SN74LVC4245A   |   https://www.ti.com/lit/ds/symlink/sn74lvc4245a.pdf?HQS=dis-dk-null-digikeymode-dsf-pf-null-wwe&ts=1616938602079
  // | OE | DIR  |  OPERATION      |   DIR NOTE: Need inverting buffer which produces the state opposite to the R_W on ATARI DB19 pin !!!
  // |-----------------------------|   DIR NOTE: So when its High on Atari its Low on DIR pin; From Teensy to Atari, use SN7406 Hex inverter Buffers With Open-Collector ?
  // | L  |  L   | B data to A bus |   DIR - Inverted HIGH from ATARI A input to Device B input side, ATARI original signal is LOW from ATARI to Device    COOMMAND write R/_W low
  // | L  |  H   | A data to B bus |   DIR - Inverted LOW from Device B input to ATARI A input side, ATARI original signal is HIGH from Device to ATARI    STATUS read R/_W    high ( Device is on B input side )
  // | H  |  X   |   Isolation     |   OE - tied to GND  LOW -Enabled, TODO: NOTE 1 -OE
  // -------------------------------
  // SN74LVC4245A Pin Configuration and Functions pinout.
  // ATARI     ----_----     Teensy
  // (5V) VCCA-|1    24|-VCCB (3.3V)    , DIR NOTE: Need inverting buffer which produces the state opposite the input for ATARI DIR DB19 pin !!!  ( ATARI DIR = LOW from ATARI to Device - HIGH from Device to ATARI )
  //       DIR-|2    23|-VCCB (3.3V)    , ATARI DIR DB19 ACSI connector --> R/W  Read/Write   - Inverted HIGH from ATARI to Device (A data to B bus), Inverted LOW from Device to ATARI (B data to A bus)
  //D0(LSB) A1-|3    22|-OE             , OE - tied to GND  LOW -Enabled, TODO: NOTE 1 -OE
  //        A2-|4    21|-B1             , A1 ~ A8 (5V)   From ATARI DB19 ACSI connector, 01 to 08 <-> Data BUS 0-7 INPUT & OUTPUT
  //        A3-|5    20|-B2             , B1 ~ B8 (3.3V) From Device to ATARI (B data to A bus)
  //        A4-|6    19|-B3
  //        A5-|7    18|-B4
  //        A6-|8    17|-B5
  //        A7-|9    16|-B6
  //D7(MSB) A8-|10   15|-B7
  //       GND-|11   14|-B8
  //       GND-|12   13|-GND
  //           ---------
  // NOTE 1: TODO -OE pin MCU control when (A1 Address bit LOW) & (_CS Chip Select LOW) enabled & read the data bus 1st byte of new command if correct Controller# leave it enabled else disable.
  //         TODO -OE issue the control circuitry (DIR, OE) is powered by VCCA 5V.
  //
  // ----- ACSI Command Descriptor Block ---------------
  // Byte 0 |xxxxxxxx|
  //         ||||||||
  //         |||------ Operation Code
  //         --------- Controller Number

  /* T 4.1
    //For Reading:
                     31        23        15        7       0
                ~    1111 1111 1111 1111 0000 0000 0000 0000
                   0B0000 0011 1111 1111 0000 0000 0000 0000) >> 16;  // BIT DATA 16,17,18,19,20,21,22,23,_A1 24,_CS 25.  HEX 3FF0000
    GPIO6_GDIR &= ~0x03FF0000;   //Set the 8 Data and _A1 - _CS pins of GPIO6 as input (note the invert ~)
    uint16_t DATA_IN = GPIO6_PSR >> 16;  //sample and shift the bits to a 16bit value

    //For writing
    // only write to 8 bit data port not _A1 and _CS pins
    GPIO6_GDIR |= 0x00FF0000;  //Set as outputs  0B0000 0000 1111 1111 0000 0000 0000 0000
    GPIO6_DR &= ~0x00FF0000;  //Clear to 0
    GPIO6_DR |= DATA_OUT << 16  //Write new value
    //Alternatively there is also GPIO6_DR_SET, GPIO6_DR_CLEAR and GPIO6_DR_Toggle.
  */
  GPIO_cs = GPIO6_PSR >> 16; //sample and shift the bits.
  digitalWriteFast(_IRQ, HIGH);  // high    <--    (CS active low), when CS is low allways set irq high inactive

  // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // ----- Command Phase -----------------------------------Alt+0175¯ ------
  // DATA direction: From Atari to Target Device
  // A1    ¯¯¯¯¯¯¯¯\___________________________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ LOW indicates 1st byte of new command.
  // IRQ   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\___________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\_____ Active low driven by target device to indicate (A) readness to accept another command byte (B) the availability of a byte to be read.
  // _CS   ¯¯¯¯¯¯¯¯¯¯¯¯¯\__________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\__________/¯¯¯¯¯¯¯¯¯¯
  //                     |        |                  |        |
  // R/_W  ¯¯¯¯¯¯\______________________/¯¯¯¯¯¯\____________________/¯¯¯¯¯ LOW from ATARI to Device (Write), HIGH from Device to ATARI (Read)
  //               |     |        |     |      |     |        |     |
  // DATA  =======><-------VALID--------><====><-------VALID--------><==== 8bit DATA Bus
  //               |     |        |     |      |     |        |     |
  //               |<-a->|<--b--->|<-c->|      |<-a->|<--b--->|<-c->|
  //                Byte 0                      Byte 1                     Byte n
  // Timing
  // a)  60 ns  (max)
  // b)  250 ns (max)
  // c)  20 ns  (max)
  // IRQ Active LOW (open-collector) 1K Pullup on ATARI.
  //

  // T4.x GPIO_cs  has its bits >> 16 shifted
  if ((GPIO_cs & 0b000000000000000100000000) == 0) { // Check the A1 Address bit - When LOW during a _CS write, indicates 1st byte of new command
    //                           1001010010
    // ID 6 ICD TIME CLOCK https://github.com/emutos/emutos/blob/2a55652646c3cc2b03cf3397e5168a10a428308e/bios/clock.c#L91
    uint32_t D_ID = (GPIO_cs & 0b0000000011100000) >> (5);
    //if ((D_ID == readerMask0) || (D_ID == readerMask1) || (D_ID == 6)) {  // Check the device ID
    if ((D_ID == readerMask0) || (D_ID == readerMask1)) {  // Check the device ID, (NO ID 6 ICD CLOCK EMULATION)
      // 3-State
      digitalWriteFast(OE_SN74LS245N, HIGH); // -- ACSI OUT -- SN74LS245N -OE PIN (optional), Active LOW-Enabled.
      /// if (((((GPIO_cs & 0b0000000011100000) >> (5)) == readerMask0) || (((GPIO_cs & 0b0000000011100000) >> (5)) == readerMask1)) || (((GPIO_cs & 0b0000000011100000) >> (5)) == 6)) {  // Check the device ID
      // if (((GPIO_cs & 0b0000000011100000) >> (5)) == readerMask0) {

      CorrectDeviceIdFlag = true;
      ACSI_ID_Flag = (GPIO_cs & 0b0000000011100000) >> 5;
      // Serial.print("ACSI_ID "), Serial.println(ACSI_ID_Flag);
      counter = 0;
      if ((GPIO_cs & 0b00011111) == 0x1F) { // ICD extended command
        //cmdLen_I = 11; // well this should set flag for icd CMD. then chek next byte for cmdLen(the first 3 bit) GROUP CODE.
        isICD = true;                   // then it's a ICD command
        cmdLen_I = 7; // ICD extended 6 byte commands
      } else {
        isICD = false;
        cmdLen_I = 6;
      }
      //Serial.print("ID CORRECT: 0b"), Serial.print( GPIO_cs, BIN ), Serial.print(" HEX "), Serial.println( GPIO_cs, HEX );
    } else {
      CorrectDeviceIdFlag = false;
      // 3-State
      digitalWriteFast(OE_SN74LVC4245A, HIGH);  // (active low) SN74LVC4245A -OE, Teensy 3.5 pin 34, Teensy 4.1 31, 3-State SN74LVC4245A Octal Bus Transceiver Outputs

      Serial.print("A1 Device ID "), Serial.println((GPIO_cs & 0b0000000011100000) >> (5));
      Serial.print("GPIO_cs ID INCORRECT: BIN "), Serial.print( GPIO_cs & 0b0000000011111111, BIN ), Serial.print("  HEX "), Serial.println( GPIO_cs & 0b0000000011111111, HEX );
      Serial.print("Show all bits GPIO_cs: BIN "), Serial.println( GPIO_cs, BIN );
    }
  }

  // Read the next bytes of the command
  if (CorrectDeviceIdFlag == true) {
    if (counter < cmdLen_I) {
      if (counter == 0) {
        cmdBuf_I[0] = GPIO_cs & 0b00011111;
      } else if (counter == 1) {
        if (isICD == true) {
          cmdBuf_I[counter] = GPIO_cs & 0xff; //
          // get the GROUP CODE see SCSI Commands Reference Manual, Rev. J (page 42)
          switch ((GPIO_cs & 0xe0) >> 5)   // get the length of the command, Common CDB fields
          {
            case  0: cmdLen_I =  7; break; // 6 byte commands
            case  1: cmdLen_I = 11; break; // 10 byte commands,  1F (0x25 00100101) READ CAPACITY (10)
            case  2: cmdLen_I = 11; break; // 10 byte commands
            case  3: cmdLen_I =  7; break; // Reserved [a]
            case  4: cmdLen_I = 17; break; // 16 byte commands
            case  5: cmdLen_I = 13; break; // 12 byte commands
            case  6: cmdLen_I =  7; break; // TODO FIX, C0h to FFh vendor specific.
            default: cmdLen_I =  7; break;
              // [a] The format of the commands using operation code 7Fh is described in 2.1.3. With
              // the exception of operation code 7Fh, all group code 011b operation codes are reserved.
              // 7Eh variable (more than 16 bytes)
              // 7Fh extended (variable length; may contain one or more CDBs)
          }
        } else {
          cmdBuf_I[counter] = GPIO_cs & 0xff;
        }
      } else {
        cmdBuf_I[counter] = GPIO_cs & 0xff;
      }
      counter ++;

      if (counter < cmdLen_I) {
        delayNanoseconds(250); // not sure if this is needed?
        digitalWriteFast(_IRQ, LOW);  // low enabled  <--   (active low)
      }
    }

    if (counter >= cmdLen_I) {
      digitalWriteFast(_IRQ, HIGH);  // high disable  <--   (active low)
      CorrectDeviceIdFlag = false;
      counter = 0;
      CommandBool = true; //
    }
  }
}
 
@Chris O. , thank you for posting those details. Looks very interesting although I have not analyzed this in detail yet as I am a bit rushed off my feet at the moment. However I will have a look in detail when I get the chance. There seems to be a difference in that you can use open collector, whereas I need to use pull-ups, but for the first timer I see a correlation between pin references and internal designations. Although I believe I read somewhere that the Teensy is 5V tolerant, I note your use of buffer chips. Probably much safer. I did try buffer chips with the Raspberry Pico but couldn't get them to work correctly. I think I did try SN74LS245's among others. Ironically, it did work OK without buffers but obviously it is better if things are designed for the logic levels involved on each side of the bus. Anyway, thank you for taking the time to post these details.
 
Teensy 4.x I/O are NOT 5V tolerant. Use a buffer. Max input voltage is VDD+0.3V, which is typically 3.6V.
 
ote that a 74LVC245 run at 3.3V may have 5V tolerant inputs, but it cannot drive both directions with a voltage shift, that takes a proper bidirectional level shifter.

5V will destroy a T4, you have been warned.
 
Just few notes: Teensy 4.x is only 3.3v max.
I only use Open-Collector for control lines and SN74LVC4245A push pull for data lines.
NOTE the LVS not LS (too slow)
Code:
// SN74LS07 Hex Buffers and Drivers With Open-Collector High-Voltage Outputs
// Power the ic with 5v, Teensy OUTPUT_OPENDRAIN -- USE 10K PULLUP TO 3.3V ON ALL TEENSY PINS GOING TO SN74LS07
//                                                    ____________________________
//                                                    |    SN74LS07 OPERATION    |
//                                                    |  INPUT A  |   OUTPUT Y   |
//                                                    |--------------------------|
//                                                    |     H     |     Hi-Z     |
//                                                    |     L     |      L       |
//                                                    ----------------------------
//                                                             ----_----
// From Teensy pin 34 _DRQ                                  1A-|1    14|-VCC   , 5V+
// To ATARI DB19 ACSI connector pin 19 _DRQ  Data request   1Y-|2    13|-6A    , USE THIS FOR _DRQ LED ?
// From Teensy pin 35 _IRQ                                  2A-|3    12|-6Y    , _DRQ LED OUTPUT
// To ATARI DB19 ACSI connector pin 10 _IRQ  Interrupt      2Y-|4    11|-5A    , USE THIS FOR _IRQ LED ?
// From Teensy pin 31 U3 SN74LVC4245A _EN Bus Transceiver   3A-|5    10|-5Y    , _IRQ LED OUTPUT
// To U3 SN74LVC4245A _EN pin 22                            3Y-|6     9|-4A    , From Teensy pin 32 U5 SN74LS245N _EN Bus Transceiver
//                                                         GND-|7     8|-4Y    , To U5 SN74LS245N _EN pin 19
//                                                             --------
 
Back
Top