Questions about Interrupts and loop()

Status
Not open for further replies.

calphool

Active member
Hi all,

I need to understand how the loop() function gets invoked under the covers for Teensy 3.6. I'm working with interrupts and I don't know if my interrupt enabling/disabling will screw up the loop() function, or if the loop() function is the main thread of the processor.

What's the best way to see how your code is actually being generated under the covers?


Code:
#include <stdarg.h>

#define L1_RED()    digitalWriteFast(21,HIGH);digitalWriteFast(22,LOW); digitalWriteFast(23,LOW);
#define L1_GREEN()  digitalWriteFast(21,LOW); digitalWriteFast(22,LOW); digitalWriteFast(23,HIGH);
#define L1_BLUE()   digitalWriteFast(21,LOW); digitalWriteFast(22,HIGH);digitalWriteFast(23,LOW);
#define L1_YELLOW() digitalWriteFast(21,HIGH);digitalWriteFast(22,LOW); digitalWriteFast(23,HIGH);
#define L1_CYAN()   digitalWriteFast(21,LOW); digitalWriteFast(22,HIGH);digitalWriteFast(23,HIGH);
#define L1_VIOLET() digitalWriteFast(21,HIGH);digitalWriteFast(22,HIGH);digitalWriteFast(23,LOW);
#define L1_WHITE()  digitalWriteFast(21,HIGH);digitalWriteFast(22,HIGH);digitalWriteFast(23,HIGH);
#define L1_BLACK()  digitalWriteFast(21,LOW); digitalWriteFast(22,LOW); digitalWriteFast(23,LOW);

#define L2_RED()    digitalWriteFast(18,HIGH);digitalWriteFast(19,LOW); digitalWriteFast(20,LOW);
#define L2_BLUE()   digitalWriteFast(18,LOW); digitalWriteFast(19,HIGH);digitalWriteFast(20,LOW);
#define L2_GREEN()  digitalWriteFast(18,LOW); digitalWriteFast(19,LOW); digitalWriteFast(20,HIGH);
#define L2_CYAN()   digitalWriteFast(18,LOW); digitalWriteFast(19,HIGH);digitalWriteFast(20,HIGH);
#define L2_VIOLET() digitalWriteFast(18,HIGH);digitalWriteFast(19,HIGH);digitalWriteFast(20,LOW);
#define L2_YELLOW() digitalWriteFast(18,HIGH);digitalWriteFast(19,LOW); digitalWriteFast(20,HIGH);
#define L2_WHITE()  digitalWriteFast(18,HIGH);digitalWriteFast(19,HIGH);digitalWriteFast(20,HIGH);
#define L2_BLACK()  digitalWriteFast(18,LOW); digitalWriteFast(19,LOW); digitalWriteFast(20,LOW);

#define A1 37
#define A0 36
#define INTERUPT_TO_TRS80 35
#define _37ECWR 34
#define _37E8WR 33
#define FF_PRE 29
#define _37E4WR 28
#define _37E0WR 27
#define _37ECRD 26
#define FF_CLR 25
#define _37E8RD 12
#define _37E4RD 11
#define _37E0RD 10
#define D7 9
#define D6 8
#define D5 7
#define D4 6
#define D3 5
#define D2 4
#define D1 3
#define D0 2
#define NOTHING 0

volatile byte activeInterrupt = NOTHING;



/* printf() to serial output */
void p(char *fmt, ... ){
    char buf[80];
        
    va_list args;
    va_start (args, fmt );
    vsnprintf(buf, 128, fmt, args);
    va_end (args);
    Serial.print(buf);
}


/* direct data bus pins outward */
void dataOutMode() {
  pinMode(D7,OUTPUT);
  pinMode(D6,OUTPUT);
  pinMode(D5,OUTPUT);
  pinMode(D4,OUTPUT);
  pinMode(D3,OUTPUT);
  pinMode(D2,OUTPUT);
  pinMode(D1,OUTPUT);
  pinMode(D0,OUTPUT);
}


/* direct data bus pins inward */
void dataInMode() {
  pinMode(D7,INPUT);
  pinMode(D6,INPUT);
  pinMode(D5,INPUT);
  pinMode(D4,INPUT);
  pinMode(D3,INPUT);
  pinMode(D2,INPUT);
  pinMode(D1,INPUT);
  pinMode(D0,INPUT);
}


/* set up pin directionality */
void pinSetup() {
  pinMode(18,OUTPUT);                 // L2 LEDs (leg pins may be rearranged depending on what RGB LED used -- just change the macro names around)
  pinMode(19,OUTPUT);                 // L2 LEDs
  pinMode(20,OUTPUT);                 // L2 LEDs
  pinMode(21,OUTPUT);                 // L1 LEDs
  pinMode(22,OUTPUT);                 // L1 LEDs
  pinMode(23,OUTPUT);                 // L1 LEDs
  pinMode(39,OUTPUT);                 // nothing, unused pin
  pinMode(38,OUTPUT);                 // nothing, unused pin
  pinMode(A1, INPUT);                 // A1 from TRS-80
  pinMode(A0, INPUT);                 // A0 from TRS-80
  pinMode(INTERUPT_TO_TRS80, OUTPUT); // pin to trigger interrupts on the TRS-80
  pinMode(_37ECWR, INPUT);            // pin to detect when write to address 37EC has been triggered (low)
  pinMode(_37E8WR, INPUT);            // pin to detect when write to address 37E8 has been triggered (low)
  pinMode(30,OUTPUT);                 // nothing, unused pin
  pinMode(FF_PRE,OUTPUT);             // flip flop PRE 
  pinMode(_37E4WR,INPUT);             // pin to detect when write to address 37E4 has been triggered (low)
  pinMode(_37E0WR,INPUT);             // pin to detect when write to address 37E0 has been triggered (low)
  pinMode(_37ECRD,INPUT);             // pin to detect when read from address 37E0 has been triggered (low)
  pinMode(FF_CLR,OUTPUT);             // flip flop CLR 
  pinMode(_37E8RD,INPUT);             // pin to detect when read from address 37E8 has been triggered (low)
  pinMode(_37E4RD,INPUT);             // pin to detect when read from address 37E4 has been triggered (low)
  pinMode(_37E0RD,INPUT);             // pin to detect when read from address 37E0 has been triggered (low)
  dataInMode();
}


/* jigger the flip flop that's tied to the WAIT* line on the TRS-80 to make sure it's in a known state */
void initFlipFlop() {
  digitalWrite(FF_PRE,LOW);
  digitalWrite(FF_CLR,LOW);

  digitalWrite(FF_PRE,HIGH);
  digitalWrite(FF_CLR,LOW);

  digitalWrite(FF_PRE,LOW);
  digitalWrite(FF_CLR,HIGH);

  digitalWrite(FF_PRE,HIGH);
  digitalWrite(FF_CLR,LOW);

  digitalWrite(FF_PRE,HIGH);
  digitalWrite(FF_CLR,HIGH);
}



/* initialize output pins */
void initialPinState() {
  digitalWriteFast(INTERUPT_TO_TRS80, HIGH); // turn off TRS-80 interrupt line (it's active low)
  initFlipFlop();                            // jigger flip flop into a known state (loaded, ready for trigger)
}


/* reset the latch that's tied to TRS-80 WAIT* line */
inline void resetWaitLatch() {
  digitalWriteFast(FF_PRE,HIGH);
  digitalWriteFast(FF_CLR,HIGH);
}


#define DISABLE_INTR_AND_WAIT()   cli(); while(activeInterrupt != NOTHING);

void _37E0WRInterrupt() {
  DISABLE_INTR_AND_WAIT()
  activeInterrupt = _37E0WR;
}

void _37E8WRInterrupt() {
  DISABLE_INTR_AND_WAIT()
  activeInterrupt = _37E8WR;
}

void _37E4WRInterrupt() {
  DISABLE_INTR_AND_WAIT()
  activeInterrupt = _37E4WR;  
}

void _37ECWRInterrupt() {
  DISABLE_INTR_AND_WAIT()
  activeInterrupt = _37ECWR;
}

void _37ECRDInterrupt() {
  DISABLE_INTR_AND_WAIT()
  activeInterrupt = _37ECRD;
}

void _37E8RDInterrupt() {
  DISABLE_INTR_AND_WAIT()
  activeInterrupt = _37E8RD;
}

void _37E4RDInterrupt() {
  DISABLE_INTR_AND_WAIT()
  activeInterrupt = _37E4RD;
}

void _37E0RDInterrupt() {
  DISABLE_INTR_AND_WAIT()
  activeInterrupt = _37E0RD;
}


/* retrieve content of data bus */
byte getDataBus() {
  byte x = 0;

  if(digitalReadFast(D7) == HIGH) x+=128;
  if(digitalReadFast(D6) == HIGH) x+=64;
  if(digitalReadFast(D5) == HIGH) x+=32;
  if(digitalReadFast(D4) == HIGH) x+=16;
  if(digitalReadFast(D3) == HIGH) x+=8;
  if(digitalReadFast(D2) == HIGH) x+=4;
  if(digitalReadFast(D1) == HIGH) x+=2;
  if(digitalReadFast(D0) == HIGH) x+=1;

  return x;
}


/* set the value of the data bus */
void setDataBus(byte b) {
  byte x = b;

  if(x >= 128) {digitalWriteFast(D7,HIGH); x-=128;} else {digitalWriteFast(D7,LOW);}
  if(x >= 64)  {digitalWriteFast(D6,HIGH); x-=64; } else {digitalWriteFast(D6,LOW);}
  if(x >= 32)  {digitalWriteFast(D5,HIGH); x-=32; } else {digitalWriteFast(D5,LOW);}
  if(x >= 16)  {digitalWriteFast(D4,HIGH); x-=16; } else {digitalWriteFast(D4,LOW);}
  if(x >= 8)   {digitalWriteFast(D3,HIGH); x-=8;  } else {digitalWriteFast(D3,LOW);}
  if(x >= 4)   {digitalWriteFast(D2,HIGH); x-=4;  } else {digitalWriteFast(D2,LOW);}
  if(x >= 2)   {digitalWriteFast(D1,HIGH); x-=2;  } else {digitalWriteFast(D1,LOW);}
  if(x >= 1)   {digitalWriteFast(D0,HIGH);        } else {digitalWriteFast(D0,LOW);}
}


/* wire up the interrupts */
void configureInterrupts() {
  attachInterrupt(digitalPinToInterrupt(_37ECWR), _37ECWRInterrupt, LOW);
  attachInterrupt(digitalPinToInterrupt(_37E8WR), _37E8WRInterrupt, LOW);
  attachInterrupt(digitalPinToInterrupt(_37E4WR), _37E4WRInterrupt, LOW);
  attachInterrupt(digitalPinToInterrupt(_37E0WR), _37E0WRInterrupt, LOW);
  attachInterrupt(digitalPinToInterrupt(_37ECRD), _37ECRDInterrupt, LOW);
  attachInterrupt(digitalPinToInterrupt(_37E8RD), _37E8RDInterrupt, LOW);
  attachInterrupt(digitalPinToInterrupt(_37E4RD), _37E4RDInterrupt, LOW);
  attachInterrupt(digitalPinToInterrupt(_37E0RD), _37E0RDInterrupt, LOW);
}


/* initialize everything */
void setup() {
  int i = 0;
  cli();
  Serial.begin(2000000); // high speed serial over USB
  pinSetup();            // set pin modes
  initialPinState();     // put pins in initial configuration

  L1_RED();
  L2_RED();
  while(!Serial && i < 10) {
    i++;
    delay(100);
    L2_YELLOW();
    delay(100);
    L2_RED();
  }
  L2_GREEN();
  configureInterrupts(); // tie interrupt lines to code blocks
  L1_GREEN();  

  sei();
}



/* Main Loop                                                               */
/* ---------                                                               */
/* When activeInterrupt changes, split it into a "read" or "write" request */
/* For read requests, set data bus direction to outward (from the Teensy   */
/* to the TRS-80).  For write requests, set the data bus direction to      */
/* inward (from the TRS-80 to the Teensy).                                 */
/*                                                                         */
/* Then switch into each address's intended function                       */

void loop() {
    if(activeInterrupt != NOTHING) {
        if(activeInterrupt == _37ECWR || activeInterrupt == _37E8WR || activeInterrupt == _37E4WR || activeInterrupt == _37E0WR) {
          dataInMode();
          L1_CYAN();
          byte b = getDataBus();
          p("write - ");
          if(activeInterrupt == _37ECWR) {
            L2_RED();
            p("Disk command register   - 0x37EC - %2X\n",b);
          }
          else
          if(activeInterrupt == _37E8WR) {
            L2_BLUE();
            p("Line printer data port  - 0x37E8 - %2X\n",b);
          }
          else
          if(activeInterrupt == _37E4WR) {
            L2_YELLOW();
            p("Cassette drive latch    - 0x37E4 - %2X\n",b);
          }
          else {
            L2_VIOLET();
            p("Disk drive select latch - 0x37E0 - %2X\n",b);
          }
        }
        else {
          dataOutMode();
          L1_VIOLET();
          p("read  - ");
          if(activeInterrupt == _37ECRD) {
            L2_RED();
            p("Disk status register    - 0x37EC\n");
            setDataBus(0);
          }
          else
          if(activeInterrupt == _37E8RD) {
            L2_BLUE();
            p("Line printer status port- 0x37E8\n");
            setDataBus(0);
          }
          else
          if(activeInterrupt == _37E4RD) {
            L2_YELLOW();
            p("Cassette drive latch    - 0x37E4\n");
            setDataBus(0);
          }
          else {
            L2_VIOLET(); 
            p("Disk drive select latch - 0x37E0\n");
            setDataBus(0);           
          }
        
        }

        L1_GREEN();
        L2_GREEN();
        activeInterrupt = NOTHING;
        resetWaitLatch();
        sei();  
    }
}
 
Last edited:
loop() is the sole and main thread. your ISR’s interrupt it and it returns back to loop afterwards where it left off

the best way to find out if your ISR’s are firing is to run other code or no code at all in loop, maybe just toggle the led every sec in loop, then see if your ISR code is running at runtime.
 
Here's the mechanics of loop()

Code:
#include <Arduino.h>

extern "C" int main(void)
{
	// Arduino's main() function just calls setup() and loop()....
	setup();
	while (1) {
		loop();
		yield();
	}
}

At a glance this doesn't look right? :: #define DISABLE_INTR_AND_WAIT() cli(); while(activeInterrupt != NOTHING);

Turning off interrupts and waiting in a while() for something to change?
 
with the nested interrupts in ARM, enabling/disabling global interrupts may not be needed depending on your usage. Also I would recommend using int instead of byte for your volatile as the mcu is 32bits native, faster and less instructions
 
Here's the mechanics of loop()

Code:
#include <Arduino.h>

extern "C" int main(void)
{
	// Arduino's main() function just calls setup() and loop()....
	setup();
	while (1) {
		loop();
		yield();
	}
}

At a glance this doesn't look right? :: #define DISABLE_INTR_AND_WAIT() cli(); while(activeInterrupt != NOTHING);

Turning off interrupts and waiting in a while() for something to change?


Okay. That's what I was hoping.

Regarding the #define, I took the cli() out after I read somewhere that that happens automatically anyway when the interrupt is triggered (so you don't have to disable interrupts at the top of an interrupt service routine like you do on some processors). The while() loop is just making sure that the main loop is finished before trying to change the activeInterrupt value, so the activeInterrupt global doesn't get stomped in the middle of handling.
 
Help me understand what you mean by "nested interrupts in ARM". The only way I could think that I would be able to get away with not turning off all interrupts would be if I did my actual processing *inside* the interrupt handler, but that seems like a bad idea... generally you want to do the least amount of code you can in an interrupt handler, right?

Or is that whole design (set a variable and have your main loop handle it) unique to the Atmel chips?
 
loop() is the sole and main thread. your ISR’s interrupt it and it returns back to loop afterwards where it left off

That's good to know. I was unclear if the interrupt was happening on the same thread or not. Do we even have the *ability* to do multithreaded apps then?
 
What's the best way to see how your code is actually being generated under the covers?

Arduino compiles your code in a temporary directory. Find that folder. On Mac and Windows, it's often in a hidden location. Turning on verbose output while compiling in File > Preference causes Arduino to print a ton of info, and buried in there are the full pathnames.

Once you find that folder, look for the .LST file. It has a full assembly language listing of the code the compiler generated. The compiler tries to put the matching source code into that file, but due to heavy optimization things don't always line up perfectly. Selecting "Debug" in Tools > Optimize will tell the compiler to avoid the optimizations which make reading the file confusing.

There's also a .SYM file which has a list of the addresses for all global variables.


Regarding the #define, I took the cli() out after I read somewhere that that happens automatically anyway when the interrupt is triggered (so you don't have to disable interrupts at the top of an interrupt service routine like you do on some processors).

Yes, but the details vary depending on the microcontroller.

With the old 8 bit AVR chips, there is single GIE (Global Interrupt Enable) bit which controls whether interrupts are enabled. It gets automatically cleared when an interrupt starts, and turned back on when the interrupt ends. Much of the info you'll find online will be written about this old & very simple approach.

On 32 bit ARM chips like Teensy 3.6, interrupts are managed by a much more sophisticated system. ARM does have a single bit called PRIMASK which blocks all interrupts. Often people familiar with AVR mistake it for GIE, but it works differently. Unlike GIE, the PRIMASK bit does not get changed to disable all other interrupts when your interrupt function starts. Instead, ARM has a nested priority level system for that function. Each interrupt has an 8 bit priority number, where 0 is the highest and 255 is the lowest. By default, most interrupts on Teensy are assigned 128. When your main program runs, the CPU is effectively running at priority level 256. When an interrupt occurs, the priority level changes, so all interrupts at that level or lower priority (higher numerical values) are blocked. But higher priority interrupts are still able to run, interrupting your lower priority interrupt. That's why it's called "nested" priority. It's a pretty awesome system that lets us do stuff like the audio library, and "normal" interrupts still work, and libs like Servo which need higher priority can still work even when you do lots of stuff in normal interrupts, and on top of all that timing stuff involving millis() usually remains highly reliable.

On ARM you can also use PRIMASK (with the cli, or __disable_irq & __enable_irq) at any time to block all interrupts, like GIE on AVR. But the key difference is unlike GIE, the hardware never automatically changes PRIMASK. The automatic hardware mechanism that changes as interrupts begin and end is the priority level stuff. Global masking of all interrupts is a separate mechanism.
 
That's good to know. I was unclear if the interrupt was happening on the same thread or not. Do we even have the *ability* to do multithreaded apps then?

with TeensyThreads yes, you only have one core, so its just task switching and works well
 
Help me understand what you mean by "nested interrupts in ARM". The only way I could think that I would be able to get away with not turning off all interrupts would be if I did my actual processing *inside* the interrupt handler, but that seems like a bad idea... generally you want to do the least amount of code you can in an interrupt handler, right?

Or is that whole design (set a variable and have your main loop handle it) unique to the Atmel chips?


forgot about this quote, you can just set the variable and let the loop handle the other events. make sure its declared as volatile and use a 32 bit primitive you should be fine, dont need to play with global interrupts unless your doing specific ordering in your isrs that you want maintained
 
I don't know if someone would be willing to go down a rabbit hole with me, but it's worth asking... I'm trying to build a floppy drive emulator for a TRS-80. The Teensy 3.6 is basically doing the heavy lifting. The glue logic is detecting reads and writes to appropriate addresses and triggering interrupts on the Teensy. There's a flip-flop in the middle of the glue logic that triggers a "WAIT*" line on the TRS-80 (z80 processor), and the theory is that the appropriate address gets trapped by the address decoding logic (which came from Steve Ciarcia's "Disk-80" design back in the early 80s), that decoder triggers WAIT*, the Teensy gets notified, dumps data onto the data bus or reads data from the data bus, and then the Teensy releases the flip-flop. That's the *theory*.
Here's a video of what happens when I try to boot up my TRS-80.

http://photos.app.goo.gl/5veGqBEB5k9CsPyo8


Here's my schematic with signals annotated (you may have to download it to zoom in... probably depends on your browser):

https://drive.google.com/file/d/1IvK-K1xxhPtzxatQ8mi-zzwTABDlsff5/view?usp=sharing


Here's the current state of my code:

https://github.com/calphool/TRS80HDD/blob/master/teensy/TeensyDiskEmu/TeensyDiskEmu.ino


Here's a log file of what you can sort of see on the Mac screen:

https://github.com/calphool/TRS80HDD/blob/master/teensy/TeensyDiskEmu/LogFileOnBootUp.pdf


I'm trying to figure out if what I'm seeing is normal, given that I haven't written the disk emulation software, and so the TRS-80 is getting confused, or if I've got a hardware problem. It's hard to know without a disassembly of the TRS-80 ROM, which I don't have.
 
I never had a TRS-80 so will not be able to help but:
I would guess you have a short on your TRS-80 bus that causes all the garbage on the video screen. It could be a line shorted high or low or two lines shorted together.
I find it somewhat strange that you never decode a read command. The log file is all writes.
 
I never had a TRS-80 so will not be able to help but:
I would guess you have a short on your TRS-80 bus that causes all the garbage on the video screen. It could be a line shorted high or low or two lines shorted together.
I find it somewhat strange that you never decode a read command. The log file is all writes.

That's pretty astute. Someone pointed out today that I was using 74LS241 chips instead of 74LS244, which means that half of the data bus was pointed the wrong direction, because the enable pins are inverted on half of a 74LS241. So I'll get new 74LS244s and see where that gets me. I agree it's strange that there were only writes. I'm hoping thats an effect of having those chips screwed up, but in theory it shouldn't have mattered. The only thing I can think of is that since the writes failed the boot up routine doesn't try to do any reads.
 
Okay, well, switching out the 74LS241s for 74LS244s got me *almost* working. I can PEEK() data on the TRS-80 and get exactly what I'm trying to send from the Teensy as a value. However, the opposite direction (POKE) only *sort of* works. It's strange. Basically, if I do something like:

Code:
POKE 14304,0

on the TRS-80, the interrupt for 37E0-WR triggers as expected, but the *value* I end up receiving is some value "close to" whatever I sent. So in the case of the code above, the value received will be something between 0 and 7 or so. If I send something like 127, I'll get a value somewhere in the 90s - 130s, and so on. The value is not stable. It will hover around the value I sent, but it bounces around randomly. It *seems* like some kind of weird timing issue, but since I've got a flip-flop holding the WAIT* line on the Z80, I wasn't expecting any timing issues, and I don't experience any when I do the PEEK as mentioned.

Current code is located in here:

https://github.com/calphool/TRS80HDD/blob/master/teensy/TeensyDiskEmu/TeensyDiskEmu.ino

Anybody have any ideas what might be causing this behavior? Anybody ever interfaced an Arduino/Teensy to a Z80 without having them clocked together? Do I need to add a latch on the data bus for the WR* side (POKE) of the process? If so, why do I need one for that side, but not the RD* side (PEEK)? Any ideas would be appreciated... brainstorming here, so any and all ideas welcome... this seems kind of nonsensical to me at this point.
 
teensy is pretty fast, reading probably isnt an issue but if you are latching put a slight delay for the writes?
 
Good idea Tony. With no delay the latch signal may go on the same f_bus cycle and have a race

And on t_3.6 you might try doubling the bus speed for better response

<edit Add> ::Look about line 777 for 'overclocking caveats' in : .\hardware\teensy\avr\cores\teensy3\kinetis.h
For the F_CPU in use comment the current F_BUS and try the higher one
 
Last edited:
With the holiday over, I got a chance to get back to this. Adding delays into the code didn't work (made the getDataBus() behavior worse actually). What I noticed was that if I ignored all but one or two digitalReadFast() calls in getDataBus(), then the code generally works as it should (except of course that I'm only getting part of the bus value, and occasionally even this doesn't quite work -- I still get a spurious result every once in a while).

So I hooked up the scope to see if it showed me anything. (Horizontal time interval is 500ns).

IMG_20181227_134129544_lowres.jpg

The yellow signal is the CLK input to pin 3 of the 74LS74 flip-flop, so it's driven by the address decoder and it's what's detecting the WR* to 37E0 (the POKE). The light blue signal is WAIT* signal back to the TRS-80. It's triggered by the CLK signal, and then reset by the Teensy via pin 1 (CLR). The pink line is one data line (D0), and as you can see, it's syncing to the CLK signal, and then drifting back high. So I think my fundamental premise, that the WAIT* signal would cause the data lines to stay where they're at, is flawed. It appears that they do not stay at their current value when WAIT* is asserted.

So, I think that means I need to introduce a latch that's triggered by that CLK signal, with the output value held until the next CLK signal. I'll have to disable it as well when a RD* signal comes through. I'll still need the WAIT* in there to keep everything in sync between the Teensy and the Z80, but I can't trust the WAIT* line to hold the data values apparently.

I guess the other thing I could try is what defragster suggested, increasing the bus speed and hoping that gets me inside the 500ns window it appears I have to be in. I think I'll try that before redesigning the circuit. How do I know what F_CPU value matches my device?
 
Last edited:
How do I know what F_CPU value matches my device?

At least this question is easy, just use Serial.println(F_CPU); and see what it prints to the serial monitor.

Something like this.

Code:
int led = 13;

void setup() {
  pinMode(led, OUTPUT);     
}

void loop() {
  Serial.println(F_CPU);
  digitalWrite(led, HIGH);
  delay(1000);
  digitalWrite(led, LOW);
  delay(1000);
}
 
Well, overclocking the bus doesn't make any difference.... on to the next attempt... maybe GPIO.

It's strange... when I'm doing the PEEK (which is the Z80 RD* signal), the clock signal stays high the entire length of the WAIT* period on the flip-flop. But when doing the POKE (Z80 WR*), the clock signal is only low for 500ns.
 
On the reading of data bits, perhaps you may need use direct GPIO access to read all 8 pins at once, rather than going 8 digitalReadFast.

Hopefully this can help.

https://forum.pjrc.com/threads/1753...-GPIO_PDIR-_PDOR?p=21228&viewfull=1#post21228

I'm having a bit of trouble making sense out of the charts in that thread. I've got a Teensy 3.6, but the chart has a "Teensy 3"... I think the chart is inaccurate for a Teensy 3.6. Based on the schematic I would guess that it's like this:

PORT A = (pins) 39,28,27,26,25,4,3,40,41,42
PORT B = (pins) 19,18,17,16,32,31,30,29,1,0,43,44,45,46,49,50
PORT C = (pins) 23,22,13,38,37,36,35,12,11,10, 9
PORT D = (pins) 21,20,14,8,7,6,5,2,47,48,51,52,53,54,55
PORT E = (pins) 34,33,24,56,57
 
Last edited:
Well, GPIO, *more or less* works.

I had to write this function to work with pins 2-9:


Code:
int convertBusValue() {
  unsigned int bv = (port_d & 0x0001);
  bv = bv + ((port_a & 0x1000) >> 11);   
  bv = bv + ((port_a & 0x2000) >> 11);   
  bv = bv + ((port_d & 0x0080) >> 4);    
  bv = bv + ((port_d & 0x0010));         
  bv = bv + ((port_d & 0x0004) << 3);    
  bv = bv + ((port_d & 0x0008) << 3);    
  bv = bv + ((port_c & 0x0008) << 4);    
  
  return bv;
}

I grab the GPIOA_PDIR, GPIOC_PDIR, GPIOD_PDIR value and put them in the int variables port_a, port_c, and port_d during the interrupt routine. Then I run that conversion routine to grab the bits and reorganize them into an 8 bit value.

The only weird thing now is that the very first time everything runs, I get a nonsense value. Every subsequent invocation seems to work perfectly. I'm not quite sure what's going on there...
 
You may want to change the connections in a way that most/all bits are on the same port...it would help to make your code faster.
And I don't know what "port_a..port_d" is.. hopefully not the register, becuase it would mean you read it several times (it's "volatile"). This again would mean you sample it - bit after bit - with some ns difference.
Better is to read read the ports once, and do the bit-magic afterwards with the read variables. Again, best would be if it's all on one single port.
 
Unfortunately rearranging the pins isn't an option, at least not without a revision to the PCB.

port_a, port_c, and port_d are global ints that get their value once (as soon as the interrupt fires), and then the ints are used from that point on instead of the GPIOx_PDIR registers, as you suggested.

Like I said, it's more-or-less working, but I haven't quite figured out why I get a nonsense value on the first time the ports are accessed. I don't know if I can figure out some way to force an access to make it stop doing that or what.

I don't really like this approach, even though it's working, because it's kind of hacky. It works coincidentally because the timing is now faster with GPIOx_PDIR access. I may just revise the whole PCB to include an octal latch on the data bus that gets triggered and then held enabled until the CLR signal that's already running the WAIT* flip-flop triggers. Then I can use digitalRead() and everything just works and the code remains simple.
 
Status
Not open for further replies.
Back
Top