DMA/eDMA Guidance

Status
Not open for further replies.

forbiddenera

Well-known member
Hi,

I was thinking of trying to use a T4.1 to try and emulate an old-school eeprom, at least just the read side, such as a 27c256/27c512

There are 15-16 address pins and 8 data pins. The address requested is asserted on the address pins and then the data is set on the output pins.

Timing is obviously important in this application. I'm not sure it can be done, though in theory, the T4.1 should be fast enough.

Doing research, it seems like this could possibly be done with the DMA/eDMA but I'm not sure where to begin..I have been reading around the manual and here a bit, so I have a general idea but..

The idea is to be able to manipulate the memory in real-time from some externally connected host. The T4.1 could boot up, read a 256kbit chunk from flash, sdcard or something into memory. That memory could be modified in real time, say over a serial connection from a PC host.

The idea is to then setup the DMA/eDMA to read the 15-16 address pins into some memory/variable, then use that to decide what byte of our in-memory chunk to put on the 8 output pins.

Some timings from the datasheet (sst27sf256/sst27sf512) for the 70ns variant (also shows 90ns chips, though, in our application we try to use 70ns ones for reliability, the actual target mcu is usually running ~10mhz)

tRC = read cycle time = minimum 70ns
tCE = chip enable access time = maximum 70ns (our target leaves the chip always enabled)
tAA = address access time = maximum 70ns
tOE = output enable access time = max 35ns (our target does seems to use this)
tOLZ = OE# low to active output = min 0 ns (no max)
tOHZ = OE# high to high-z output = max 25ns

From the timing chart tAA is the time from address pins being set to data being valid on data pins, tRC looks like the minimum amount of time the address pins need to be set for a read cycle.

So basically I need to read 15-16 bits from 15-16 address pins, use those bits as an offset for somewhere in memory where my output data lies to output the byte there to the output pins, rather quickly. :)

If I'm correct, I should be able to read input pins directly from memory with some perhaps even being contiguous? Thusly, I'd want to setup a DMA transfer to read say 16 bits/2bytes from whatever address is used for those pins state and write it out to some other address (if that's even needed? maybe as a buffer?) where it can be used as an offset for a pointer to the data for a second DMA transfer to the output pins?

I was also considering using a shift register to reduce the number of physical output pins I need, but I don't think a 595 will be just quite fast enough, maybe - but that would be another option, pushing the data out serially.

3500 pages sucks :(
 
I think I need to..

Set a TCD with the source address of the address input pins? with source data size being 16-bit.. destination address being somewhere in memory that the 2nd DMA can use as it's source address?

Not entirely sure how to feed the source address to the 2nd DMA TCD without setting it up every time the first DMA completes? 2nd dma could be setup in the completion interrupt of the first DMA? could the first dma directly update the tcd of the second dma?!

Trying to dig through this !
 
The RT106x processors are so fast that a well-written very tight interrupt service function triggered by the leading edge of OE# may be more likely to work properly than a DMA solution, and be a bit less complicated.

In a product that I work with (using Kinetis K60 and K66 parts @150 MHz), the CPU would be about 9 lines-of-code into a UART RX idle interrupt service function ahead of the DMA transfer of the final byte of a packet often enough to be a real headache until we identified this issue. We resolved it by implementing an explicit wait-for-transfer-complete indication before performing a packet-boundary buffer switch. The CPUs in the T4.1 have much higher clock rates than the Kinetis parts, improving the chances that the CPU based solution could work.
 
The RT106x processors are so fast that a well-written very tight interrupt service function triggered by the leading edge of OE# may be more likely to work properly than a DMA solution, and be a bit less complicated.

In a product that I work with (using Kinetis K60 and K66 parts @150 MHz), the CPU would be about 9 lines-of-code into a UART RX idle interrupt service function ahead of the DMA transfer of the final byte of a packet often enough to be a real headache until we identified this issue. We resolved it by implementing an explicit wait-for-transfer-complete indication before performing a packet-boundary buffer switch. The CPUs in the T4.1 have much higher clock rates than the Kinetis parts, improving the chances that the CPU based solution could work.

Possibly, I was thinking this was the case too, but I'm not sure..You're definitely right that the code would be easier, I basically have pseudo code for that already written out..although I wanted to leave CPU time open for being able to update the code in memory from host and a few other functions..

I was testing some stuff the other day, and I couldn't get solid timing using digitalWriteFast, even with a tight loop.. basically this:

Code:
void loop() {
  digitalWriteFast(DATA_OUTPUT, 1);
   delayNanoseconds(2);
   digitalWriteFast(DATA_OUTPUT, 0);
   delayNanoseconds(2);
   digitalWriteFast(DATA_OUTPUT, 1);
   delayNanoseconds(2);
   digitalWriteFast(DATA_OUTPUT, 0);
}

I'd see 4 pulses on the scope at equal timings then when the loop loops there was a longer delay.

Do you have suggestions on how to achieve specific timings otherwise? I've used timers directly on other platforms before, I suppose one could be setup to read address pins and trigger an output somehow at a constant rate.. but the output needs to be set a certain amount of time after the address lines are set, not just at a set frequency...

Ideally I suppose, the best trigger would be on any change of any address pin, though the same would have to trigger a DMA request I suppose..

Is there a way to trigger an interrupt if any of the 16 bits in a GPIO register changes?

Other functions could include driving a display over i2c or similar, receiving data over one UART, outputting data over another and possibly CANBUS, while updating internal emulated ROM memory from the host. I figured setting up the emulation with DMA would leave the CPU free for lots of other stuff like this.

I tried shifting data into a 595 in order to save some pins but it (595) doesn't read anything unless I do 4 asm("nop");s after each clock pulse..and then the output on the pins seems a bit longer than needed..dunno if that'll work but I can try that later again maybe.

Still diving in the manual here.
 
Last edited:
See here for yield() which runs after every loop, can be overridden if you supply a blank function so it doesn't do unnecessary background processing, or put a while loop within the loop and make sure the loop scope never exits should fix the extra delay issue...

https://forum.pjrc.com/threads/43241-what-is-outside-loop()?p=139759&viewfull=1#post139759

I usually run TeensyThreads as yield only affects the loop(), and runs multiple peripherals very fast
 
The RT106x processors are so fast that a well-written very tight interrupt service function triggered by the leading edge of OE# may be more likely to work properly than a DMA solution, and be a bit less complicated.

Do you happen to know if OE# is asserted every time an access is performed? The timing diagram for the sst27sf256 for examples shows OE# can be pulled low constantly through multiple accesses, it seems..

Edit: seems my willem45 eeprom reader also pulses OE on read it seems but I don't think this is needed. Chip simply outputs data within 70ns of any address changes, I need to do the same. OE# will be used to trigger high-z however.

On my target device, it seems as if this is the case; I do believe (though haven't double checked the schematic which is scanned from the 90s and someone spilled coffee on it or something) that the data pins may be shared with other things hence being driven hi-z when not in use..

It would be nice if I could reliably use OE# as a trigger.
 
Last edited:
Rt1060 gpio / isr

Edit: seems my willem45 eeprom reader also pulses OE on read it seems but I don't think this is needed. Chip simply outputs data within 70ns of any address changes, I need to do the same. OE# will be used to trigger high-z however.

I wondered whether that was the case...

edit:

If you can count on your target pulsing OE# low for each access the logic is way simpler. If your target holds OE# low for a limited time interspersed with inactive periods, the logic will be more complicated but may still be OK, simply updating the output as it sees changes of the simulated address signals.

/edit

Is there a way to trigger an interrupt if any of the 16 bits in a GPIO register changes?

I would need to study the RT manual to see if it still works the way I use it in the K series. The K series has the ability to trigger on falling/rising/both edges of GPIO pins, with a setting for each pin. Each 32-pin GPIO block connects to one interrupt trigger and corresponding vector. Which means that it could be theoretically possible to write an ISR that is triggered by a state change of any of the pins assigned as an address. Placing the OE# in a separate I/O block would allow it to be managed by a separate interrupt service function, allowing "semi-easy" control of the hi-Z functionality.

The RT has a brand new GPIO subsystem, that appears at first glance to provide what your hardware needs. It is way more complicated than the K series. (I am looking at the 12/2019 RT1060 manual, chapter 12: GPIO, page 949). Here's how I understand it, based on a very quick reading:
  • multiple GPIO blocks, some "high-speed", some "standard speed".
  • Each GPIO block supports one interrupt trigger/vector.
  • (not GPIO, but rather IOMUX) It may be possible to exercise some control over which pins are associated with each GPIO.
  • Interrupt can be triggered by rising, falling, high-level or low-level but not "both rising and falling". Depending on whether your hardware holds OE# active for extended intervals this may be important.
  • GPIO has a single direction register allowing a program to simulate hi-Z on the 8 data signals by setting them as inputs.
It may be worth thinking about whether your target device's hardware spends any significant amount of time with OE# inactive, which looks like an chance to maintain the contents of the memory. Since your system appears to be simulating an EPROM, is there a way to for your code to know when it's OK to update the contents of the virtualized EPROM?

In 1982, my coworkers and I simulated a 2764 UV EPROM with 4 2116 CMOS RAM chips driven by an external processor to "program" the "EPROM". It was comparatively easy for us because we knew when the target was not running, to allow the support/maintenance system to update the contents. It was dramatically better for our development cycles not having to keep a stock of erased EPROMs, and to not spend 5 minutes programming them. :)
 
Last edited:
I wondered whether that was the case...

edit:

If you can count on your target pulsing OE# low for each access the logic is way simpler. If your target holds OE# low for a limited time interspersed with inactive periods, the logic will be more complicated but may still be OK, simply updating the output as it sees changes of the simulated address signals.

/edit

Yeah I'm not entirely sure. It seems like my target and the Willem do pulse on read. The datasheet for the sst eeprom doesn't show that being a requirement. The Willem especially likely wouldn't be sharing its bus with anything so not sure with that. I suppose I can check other eprom/eeprom/flash datasheets, but I would prefer if it were compatible with many targets if possible.

I would need to study the RT manual to see if it still works the way I use it in the K series. The K series has the ability to trigger on falling/rising/both edges of GPIO pins, with a setting for each pin. Each 32-pin GPIO block connects to one interrupt trigger and corresponding vector. Which means that it could be theoretically possible to write an ISR that is triggered by a state change of any of the pins assigned as an address. Placing the OE# in a separate I/O block would allow it to be managed by a separate interrupt service function, allowing "semi-easy" control of the hi-Z functionality.

The RT has a brand new GPIO subsystem, that appears at first glance to provide what your hardware needs. It is way more complicated than the K series. (I am looking at the 12/2019 RT1060 manual, chapter 12: GPIO, page 949). Here's how I understand it, based on a very quick reading:
  • multiple GPIO blocks, some "high-speed", some "standard speed".
  • Each GPIO block supports one interrupt trigger/vector.
  • (not GPIO, but rather IOMUX) It may be possible to exercise some control over which pins are associated with each GPIO.
  • Interrupt can be triggered by rising, falling, high-level or low-level but not "both rising and falling". Depending on whether your hardware holds OE# active for extended intervals this may be important.
  • GPIO has a single direction register allowing a program to simulate hi-Z on the 8 data signals by setting them as inputs.

Sounds about right. I've been studying that and other parts of the manual every day for a week now, heh.

It may be worth thinking about whether your target device's hardware spends any significant amount of time with OE# inactive, which looks like an chance to maintain the contents of the memory. Since your system appears to be simulating an EPROM, is there a way to for your code to know when it's OK to update the contents of the virtualized EPROM?

In 1982, my coworkers and I simulated a 2764 UV EPROM with 4 2116 CMOS RAM chips driven by an external processor to "program" the "EPROM". It was comparatively easy for us because we knew when the target was not running, to allow the support/maintenance system to update the contents. It was dramatically better for our development cycles not having to keep a stock of erased EPROMs, and to not spend 5 minutes programming them. :)

Yeah, you get the idea. This is for a car so real-time modification of maps is the goal. Ecu probably designed late 80s early 90s (honda 'obd1')..most came without any external memory and use the otp on the mcu, but they had a spot for a 27256 and a 373, few came from the factory with a 27256 eprom already though. Jumper, resistor, latch, socket, eeprom or emu and done.

There is already quite a few on the market, even for this particular ECU but basically all use some sort of sram/fram and a buffer and/or cpld, I was curious if it could be done with just a t4.1 and some level shifters. Some guys want canbus output, was already going to use t4.1 to translate the serial stream, but ultimately could be a lot more flexible if the t4.1 was also doing the emulation instead of one of several 3rd party devices I have no real hope of interfacing with (not impossible but think end user)
 
So.. I don't know why I was thinking the mcu used the latch to sort of hold the address lines..15 address lines..8 bit latch..stupid mistake there..also couldn't come up with a good reason to do that except maybe buffering..after double checking it seems like the data pins are connected to the latch input pins, thus it seems 8 address lines latch on, then mcu changes those 8 lines to input and hits oe# for the read..? I wasn't even sure this mcu had that ability, at least for mapped memory. Docs are limited (oki 66207) as are tools.

If that's the case, I can definitely count on oe# for reads at least with this target, which while others 'would be nice', not necessarily my goal. One of the other emulators (moates ostrich) works with several targets including AFAIK NES/SNES.
 
Multiplexed address/data, makes sense

The discussion so far appeared to be related to 27c256 or 27c512 parts, which are just classical EPROMs, with nonmultiplexed address/data signals. So the discussion so far still fits the description.

Your most recent message describes a processor bus that multiplexes the low order 8 bits of address and the 8 bits of data on the same pins. This was a classical way to save hardware resources, so it's not a big surprise. It does help the rest of us understand how to support your target system. There may be another signal from the processor that could help your design; multiplexed buses have a signal that allows latching of the address pins during the data access to allow the memory (27cXXX) to drive the data lines. Intel uses a name like "address latch enable" for that signal; I'm not sure what your ECU would call it.

Depending on where you would like to attach your new hardware into the ECU, you may find an address latch enable signal to be useful to trigger an action in the T4.1.

Good luck in you project, it sounds interesting!
 
The latch enable is called latch enable. Yes 27c256/27c512 multiplexed bus as you describe.

Ideally I only want to attach to the eeprom ports. No other uses LE#. I would assume shortly after each Latch enable there would be an output enable shortly after. Seems to be the case.

Just need to figure out how to read all 15 bits from the gpio in one go. Hopefully I can do that with a rising oe trigger (technically falling oe# trigger) and use it to set outputs based on memory fast enough.

First step is trying to read address I guess.
 
so if I'm thinking right.. if I wanted to read the input straight from the register from gpio, without having to move stuff around, that I should connect a0-15 to say, GPIO 1.16-31 or something?

A0->1.16->19
A1->1.17->18
A2->1.18->14
A3->1.19->15
A4->1.20->40
A5->1.21->41
A6->1.22->17
A7->1.23->16
A8->1.24->22
A9->1.25->23
A10->1.26->20
A11->1.27->21
A12->1.28->38
A13->1.29->39
A14->1.30->26
A15->1.31->27 (edit: dont really need a15)

that way I can read the high bytes from the register or something?

would be easier if I could connect pins more consecutively on the actual teensy but..then I wont get my bits in order without changing them up or reading them individually..

this is my first time using teensy...not my first time doing embedded micro programming though :)

I suppose I could multiplex the data too and save some pins if can be done fast enough lol..maybe an experiment for if/when it works.
 
Last edited:
yay..seems to work to read...and seems like i got it pinned out right except one pin not going high (apparently one channel on one of my shifters is dead)

https://www.pjrc.com/teensy/interrupts.html .. is this only valid for earlier teensy's ? seems to target avr ?

edit..wow I didn't think doing an interrupt would be the hard part .. lol .. I'm not sure just using attachInterrupt() would be fast enough?

I have the OE# hooked to pin 37 / gpio 2.19.. need to watch for falling edge or low I guess..

Is pinMode() fast or should I just set registers directly .. ? is there a place I can see whats been #def'd already for regs n stuff?
 
Last edited:
Teensy4 GPIO controls

In case you have not found the code already... "avr" is basically a holdover from early days, but all teensy products are supported there. Two files you are likely to be interested in studying. I have found Paul's code to be informative and have had very good results in using it (again my experience is the Teensy3):

.../arduino-1.8.13/hardware/teensy/avr/cores/teensy4/core_pins.h
../arduino-1.8.13/hardware/teensy/avr/cores/teensy4/digital.c

The implementation of pinMode() is in digital.c. It does a fair amount of work to configure a pin fully, so you are likely to do direct register access to achieve the specific goal of switching pins between two well known states. (Assuming for instance transitioning the 8 data lines between hi-Z and driven). It seems logical to use pinMode() to perform initial setup, and direct register access in the ISR (or however you decide to implement it...).
 
Sorry off doing other stuff. Plus limiting time on keyboard...

My excel document for T4.x pins up on github (https://github.com/KurtE/TeensyDocuments/blob/master/Teensy4 Pins.xlsx) has lot of information about the pins.
Example 4.1 GPIO page
Code:
Pin	Name	GPIO	Serial	I2C	SPI	PWM	CAN	Audio	XBAR	FlexIO	Analog	SD/CSI/LCD
 1	AD_B0_02	1.02	Serial1(6) TX		SPI1(3) MISO	PWM1_X0	2_TX		IO-16			
 0	AD_B0_03	1.03	Serial1(6) RX		SPI1(3) CS0	PWM1_X1	2_RX		IO-17			
24/A10	AD_B0_12	1.12	Serial6(1) TX	Wire2(4) SCL		PWM1_X2					A1:1  	
25/A11	AD_B0_13	1.13	Serial6(1) RX	Wire2(4) SDA		PWM1_X3	GPT1_CLK				A1:2	
19/A5	AD_B1_00	1.16	Serial3(2) CTS	Wire(1) SCL		QT3_0				3:0	A1:5, A2:5	
18/A4	AD_B1_01	1.17	Serial3(2) RTS	Wire(1) SDA		QT3_1				3:1	A1:6, A2:6	
14/A0	AD_B1_02	1.18	Serial3(2) TX			QT3_2		SPDIF_OUT		3:2	A1:7, A2:7	
15/A1	AD_B1_03	1.19	Serial3(2) RX			QT3_3		SPDIF_IN		3:3	A1:8, A2:8	
40/A16	AD_B1_04 	1.20								3:4	A1:9,A2:9	USDHC2_DATA0
41/A17	AD_B1_05 	1.21				GPT2_1				3:5	A1:10,A2:10	USDHC2_DATA1
17/A3	AD_B1_06	1.22	Serial4(3) TX	Wire1(3) SDA				SPDIF_LOCK		3:6	A1:11, A2:11	USDHC2_DATA2
16/A2	AD_B1_07	1.23	Serial4(3) RX	Wire1(3) SCL				SPDIF_EXTCLK		3:7	A1:12, A2:12	USDHC2_DATA3
22/A8	AD_B1_08	1.24				PWM4_A0	1_TX			3:8	A1:13, A2:13	USDHC2_CMD
23/A9	AD_B1_09	1.25				PWM4_A1	1_RX	1:MCLK		3:9	A1:14, A2:14	USDHC2_CLK
20/A6	AD_B1_10	1.26	Serial5(8) TX					1:RX_SYNC		3:10	A1:15, A2:15	
21/A7	AD_B1_11	1.27	Serial5(8) RX					1:RX_BCLK		3:11	A1:0, A2:0	
38/A14	AD_B1_12 	1.28			SPI1(3) CS0 			1:rx_data 		3:12	A2:1	
39/A5	AD_B1_13 	1.29			SPI1(3) MISO			1:tx_data 		3:13	A2:2	
26/A12	AD_B1_14	1.30			SPI1(3) MOSI			1:TX_BCLK		3:14	A2:3  	
27/A13	AD_B1_15	1.31			SPI1(3) SCK			1:TX_SYNC		3:15	A2:4  	
10	B0_00	2.00			SPI(4) CS0	QT1_0		MQS_RIGHT		2:0     		
12	B0_01	2.01			SPI(4) MISO	QT1_1		MQS_LEFT		2:1     		
11	B0_02	2.02			SPI(4) MOSI	QT1_2	1_TX			2:2		
13	B0_03	2.03			SPI(4) SCK	QT2_0	1_RX			2:3		
6	B0_10	2.10				PWM2_A2, QT4_1		1:TX3_RX1		2:10		
 9	B0_11	2.11				PWM2_B2,QT4_2		1:TX2_RX2		2:11		
32	B0_12	2.12						1:TX1_RX3	IO-10	2:12		
8	B1_00	2.16	Serial2(4) TX			PWM1_A3		1:RX_DATA	IO-14	2:16, 3:16		
7	B1_01	2.17	Serial2(4) RX			PWM1_B3		1:TX_DATA	IO-15	2:17, 3:17		
36	B1_02    	2.18			SPI(4) CS2 	PWM2_A3		1:TX_BCLK 	IO-16	2:18,3:18		
37	B1_03    	2.19			SPI(4) CS1 	PWM2_B3		1:TX_SYNC 	IO-17	2:19,3:19		
35	B1_12    	2.28	Serial8(5) TX							2:28,3:28		
34	B1_13    	2.29	Serial8(5) RX							2:29,3:29		
45	SD_B0_00	3.12		Wire1(3) SCL	SPI2(1) SCK	PWM1_A0			IO-04			CMD
44	SD_B0_01	3.13		Wire1(3) SDA	SPI2(1) CS0	PWM1_B0			IO-05			CLK
43	SD_B0_02	3.14	Serial5(8) CTS		SPI2(1) MOSI	PWM1_A1			IO-06			DATA0
42	SD_B0_03	3.15	Serial5(8) RTS		SPI2(1) MISO	PWM1_B1			IO-07			DATA1
47	SD_B0_04	3.16	Serial5(8) TX		FLEXSPI B_SSO_B	PWM1_A2			IO-08			DATA2
46	SD_B0_05	3.17	Serial5(8) RX		FLEXSPI B_DQS	PWM1_B2			IO-09			DATA3
28	EMC_32	3.18	Serial7(7) RX			PWM3_B1						
31	EMC_36	3.22				GPT1_2	3_TX	3:TX_DATA	IO-22			
30	EMC_37	3.23				GPT1_3 	3_RX	3:MCLK	IO-23			
 2	EMC_04	4.04				PWM4_A2		2:TX_DATA	IO-06	1:4		
 3	EMC_05	4.05				PWM4_B2		2:TX_SYNC	IO-07	1:5		
 4	EMC_06	4.06				PWM2_A0		2:TX_BCLK	IO-08	1:6		
33	EMC_07	4.07				PWM2_B0		2:MCLK	IO-09	1:7		
5	EMC_08	4.08				PWM2_A1		2:RX_DATA	IO-17	1:8		
51	EMC_22	4.22		Wire1(3) SCL	FLEXSPI2_A_SS1_B	PWM3_B3, QT2_3						
48	EMC_24	4.24	Serial8(5) RX 		FLEXSPI2_A_SS0_B	PWM1_B0						
53	EMC_25	4.25	Serial1(6) TX 		FLEXSPI2_A_SCLK	PWM1_A1						
52	EMC_26	4.26	Serial1(6) RX 		FLEXSPI2_A_DATA00	PWM1_B1				1:12		
49	EMC_27	4.27	Serial8(5) RTS		FLEXSPI2_A_DATA01, SPI2(1) SCK  	PWM1_A2				1:13		
50	EMC_28	4.28	Serial8(5) CTS		FLEXSPI2_A_DATA02, SPI2(1) MOSI	PWM1_B2				1:14		
54	EMC_29	4.29	Serial1(6) RTS		FLEXSPI2_A_DATA03, SPI2(1) MISO	PWM3_A0				1:15		
29	EMC_31	4.31	Serial7(7) TX		SPI2(1) CS1	PWM3_A1
Is in GPIO pin number order.
Note: GPIOs are shown as 1-4 but we switch the pins to fast speed mode so they are 6-9, in startup.c using the registers
Code:
	// Use fast GPIO6, GPIO7, GPIO8, GPIO9
	IOMUXC_GPR_GPR26 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR27 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR28 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR29 = 0xFFFFFFFF;
Those bits which are 1s use high speed those with 0s use normal speed.

IMXRT.h
Interrupts for pin changes, when in normal mode, you have two interrupts per port
Code:
        IRQ_GPIO1_0_15 =        80,
        IRQ_GPIO1_16_31 =       81,
        IRQ_GPIO2_0_15 =        82,
        IRQ_GPIO2_16_31 =       83,
        IRQ_GPIO3_0_15 =        84,
        IRQ_GPIO3_16_31 =       85,
        IRQ_GPIO4_0_15 =        86,
        IRQ_GPIO4_16_31 =       87,
        IRQ_GPIO5_0_15 =        88,
        IRQ_GPIO5_16_31 =       89,
Also a few with their own interrupt... Have not done much with these.
If however in High speed, there is only one Interrupt for all of the pins in Ports 6-9
Code:
        IRQ_GPIO6789 =          157, // RT1060 only

DMA with GPIO, need to switch back to normal speed.
You normally write to GPIO Data register(DR) and read from Pad Status Register (PSR).

Sorry enough typing for now hope it helps
 
Sorry off doing other stuff. Plus limiting time on keyboard...

Understandable. Any help is appreciated. While teensy seems awesome,seems Ike documentation is maybe a bit limited.

My excel document for T4.x pins up on github (https://github.com/KurtE/TeensyDocuments/blob/master/Teensy4 Pins.xlsx) has lot of information about the pins.

Yes I found screenshots of your docs in another thread on betere: dma. Was very useful in figuring out the pins. I can now read address..just a matter of doing the rest fast enough now.
Is in GPIO pin number order.
Note: GPIOs are shown as 1-4 but we switch the pins to fast speed mode so they are 6-9, in startup.c using the registers
Code:
	// Use fast GPIO6, GPIO7, GPIO8, GPIO9
	IOMUXC_GPR_GPR26 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR27 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR28 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR29 = 0xFFFFFFFF;
Those bits which are 1s use high speed those with 0s use normal speed.

I have realized this. The manual doesn't seem super clear on this but I get it. Was a bit confused at first when I didn't see gpio 6o the screenshot of your spreadsheet.

IMXRT.h
Interrupts for pin changes, when in normal mode, you have two interrupts per port
Code:
        IRQ_GPIO1_0_15 =        80,
        IRQ_GPIO1_16_31 =       81,
        IRQ_GPIO2_0_15 =        82,
        IRQ_GPIO2_16_31 =       83,
        IRQ_GPIO3_0_15 =        84,
        IRQ_GPIO3_16_31 =       85,
        IRQ_GPIO4_0_15 =        86,
        IRQ_GPIO4_16_31 =       87,
        IRQ_GPIO5_0_15 =        88,
        IRQ_GPIO5_16_31 =       89,
Also a few with their own interrupt... Have not done much with these.
If however in High speed, there is only one Interrupt for all of the pins in Ports 6-9
Code:
        IRQ_GPIO6789 =          157, // RT1060 only

DMA with GPIO, need to switch back to normal speed.
You normally write to GPIO Data register(DR) and read from Pad Status Register (PSR).

Sorry enough typing for now hope it helps

Hmm..I was planning on watching the interrupt on a different port. Right now address lines are gpio1.16-31, oe# is on 2.19. I don't know about output yet. If I can do it quickly enough then I might be able to multiplex the data on the address lines but honestly I don't think that will end up working, but who knows - they are already multiplexed on the target so 🤔

Besides this I was hoping to use usb to a host, maybe otg for Android host, tty from target, 1 or 2 tty out, maybe canbus in, definitely canbus out, 1 or 2 pwm out at least and a few analog in.. I had just squeezed everything in a pin list, knowing certain pins had different capabilities but not knowing that certain pins go to certain gpio registers..might have to get creative..lol..

At least if I can't get the emulator working ill have plenty of pins left over for the rest *grumbles at parallel io*

I did see an application note about using flexio to run a parallel bus on a k series, so it would appear that there are more than a few ways to potentially put this together.

I'm not set on using DMA, I had just initially thought when I saw it that it might be the best option, hoping it would be plenty fast and basically handle everything in the "background"..I wasn't sure if I could reasonably even read the address lines directly and thought dma might even be the way to go to get it into a variable / memory I can work with..but that seems a bit silly.

Not a programming noob but still getting the hang of microcontrollers. While I feel like Arduino stuff has helped certain things be more approachable I also feel like its somewhat poisoned the ecosystem a bit? I see so many people trying to build things with "arduino" style code that blocks and uses delay()s instead of timers..one example is a flex fuel sketch that's popular..might be okay for flashing pretty leds but running something in your car..dug into the timers and did it right.

Want to do the same here but man this mcu is a bit complex.
 
In case you have not found the code already... "avr" is basically a holdover from early days, but all teensy products are supported there. Two files you are likely to be interested in studying. I have found Paul's code to be informative and have had very good results in using it (again my experience is the Teensy3):

.../arduino-1.8.13/hardware/teensy/avr/cores/teensy4/core_pins.h
../arduino-1.8.13/hardware/teensy/avr/cores/teensy4/digital.c

The implementation of pinMode() is in digital.c. It does a fair amount of work to configure a pin fully, so you are likely to do direct register access to achieve the specific goal of switching pins between two well known states. (Assuming for instance transitioning the 8 data lines between hi-Z and driven). It seems logical to use pinMode() to perform initial setup, and direct register access in the ISR (or however you decide to implement it...).

I was just confused on whether things like TIMER1_OVF_vect was platform specific or agnostic, if certain registers are abstracted with defines or macros or what..

I know when setting up timers on say a atmega, I'd set registers like TCCR0A, OCR0A, etc.. I am not sure if those platform specific or if something like the Arduino framework which, I think, when you include arduino loads the teensy libraries through the board file configured in your ide..?

Sorry..again not specifically new to programming (30 years) ..to microcontrollers, yes a bit (well compared to some here, total noob), stuff I've done before has either targeted something specific on a custom board or used arduino stuff.. mostly up to date, even with lower level stuff I've been able to find decent direction on what registers to set and just let the magic happen, I suppose I'm past the point where I should have a clearer idea - ie, you shouldn't have had go direct me to where those files were..

Using platformIO at least..can't stand Arduino ide. Curious about trying the debugger I saw on the forums here, sad we can't do full jtag/swd though.
 
If however in High speed, there is only one Interrupt for all of the pins in Ports 6-9

Isn't fast the default though? If fast is the default, and there's only one interrupt for all GPIO then...that seems a bit limiting?

edit: I re-checked the interrupt list. I see what you means.

I think I'll need to watch for both falling and rising edge triggers. Again, the OE# signal is already on a different port, can I just set that port to slow mode and get both my interrupts?

Still diving into the manual here. I don't really see any need or advantage to the OE# signal being on the same port as the address lines anyway, so, I should be able to put it elsewhere and get the interrupts I need off it I'm hoping?

So to get this straight I think I need to..

Set teensy pin 37, gpio 2.19, B1_03 to slow mode so I can access it as GPIO2 instead of GPIO7..

Then try to figure out how to do interrupts on low/high transitions on 2.19.. seems I need to set the interrupt control register somehow..this manual is confusing :(

Looking at the interrupt table, I see GPIO1 has "Active HIGH interrupt from INT0:7 on GPIO" on IRQ 72-79..not sure what those are for..? Then 80, 81 are "Combined interrupt indication for GPIO1 signal 0 throughout 15" and "16 through 31".. so if I'm thinking about this right, the interrupt is triggered if any bits (representing channels in this case) in some interrupt (ISR?) register are 1, which multiple could be..?

If using interrupts on GPIO2, I'd be using say, IRQ 82 for low trigger and IRQ 83 for high trigger?

I'm not sure how ICR15-0 and ICR31-16 for GPIO_ICR1 and GPIO_ICR2 (pages 965+) relate to each GPIO or what? I'm assuming that ICR0:15 refers to "interrupt signal 0:15" as referred to in the IRQ table? but not sure how that relates to which GPIO? I see the IMR which says it can mask signals..

Does it mean that, for GPIO 2.19, only GPIO2_ICR2 applies as that covers 16-31? so GPIO2_ICR2 bits 7,6..?!

Maybe that is the case? I found some random code on google, may be for a different platform (but similar?)

Code:
// from https://github.com/RT-Thread/rt-thread/blob/master/bsp/imx6sx/iMX6_Platform_SDK/sdk/drivers/gpio/src/gpio.c
if (pin < 16)
    {
        // GPIOs 0-15 use ICR1 register
    	uint32_t value = HW_GPIO_ICR1_RD(port);        // read current value
    	uint32_t field_offset = pin * 2;               // fields are 2 bits wide
        value &= ~(BM_GPIO_ICR1_ICR0 << field_offset); // clear specified field
        value |= config << field_offset;               // set specified field
        HW_GPIO_ICR1_WR(port, value);                  // write new value
    }
    else
    {
        // GPIOs 16-31 use ICR2 register
        uint32_t value = HW_GPIO_ICR2_RD(port);         // read current value
    	uint32_t field_offset = (pin - 16) * 2;         // fields are 2 bits wide
        value &= ~(BM_GPIO_ICR2_ICR16 << field_offset); // clear specified field
        value |= config << field_offset;                // set specified field
        HW_GPIO_ICR1_WR(port, value);                   // write new value
    }

do I really have to put my signal on two different pins to trigger on a high and low?!

definitely confused around here a bit... there's 32 interrupts for each gpio but we don't have that many gpio ports? there must be something I'm missing here!
 
Last edited:
You may want to consider how long OE# is expected to be active. If you are fairly confident that it is held active for limited time, then it may be OK to enter the ISR on OE# falling edge, then return from the ISR when OE# rises. Which means you may consider configuring it as an "active low" interrupt, since the ISR execution time would then be as long as the signal is low. Considering your earlier point that the CE# signal maty be held active for extended periods, it may be that the target system may try to change the address lines while OE# is held low. That would be VERY unusual behavior but not impossible.

The reason to consider connecting OE# to two inputs is due to a limitation of the RT GPIO interrupt modes: A signal can be configured to "interrupt on rising edge" or "interrupt on falling edge" but there is no setting for "interrupt on either edge". So tying the signal to two "inputs" allow one to trigger interrupt on the falling edge, and the other to trigger interrupt on the rising edge.

...You are correct, "hi-Z" is theoretically not the same as "input": Academically speaking, the high impedance state merely means "not driving the signal ignoring the signal's state", and again strictly academically speaking, an input state implies that a device is both not driving the signal and is interested in the value of the signal. Clearly your emulated EPROM would not be interested in the contents of the data lines when in hi-Z. :) It's merely a convenient way to use one operation to control whether the 8 simulated data signals are driving the bus versus not driving the bus, and your code may freely ignore the states of the signals while they are not being driven.
 
You may want to consider how long OE# is expected to be active. If you are fairly confident that it is held active for limited time, then it may be OK to enter the ISR on OE# falling edge, then return from the ISR when OE# rises. Which means you may consider configuring it as an "active low" interrupt, since the ISR execution time would then be as long as the signal is low. Considering your earlier point that the CE# signal maty be held active for extended periods, it may be that the target system may try to change the address lines while OE# is held low. That would be VERY unusual behavior but not impossible.

CE# seems to be tied low. OE# pulses on every read/address change. There is also a Latch enable pin from the mcu that latches the output of a 373, so from what I can tell, sets all address lines, latches the ones shared with data, then hits oe#, since the address lines are latched the data lines can output to the same lines. I don't think it's changing the lines while oe# is low, they are set first, lower ones latches then oe# pulsed.

As far as setting it to activate when low, I always thought if the ISR finished and the signal was still low that it would be called over and over when low with no real way to tell when its high (except that your isr isn't always being called?)

The reason to consider connecting OE# to two inputs is due to a limitation of the RT GPIO interrupt modes: A signal can be configured to "interrupt on rising edge" or "interrupt on falling edge" but there is no setting for "interrupt on either edge". So tying the signal to two "inputs" allow one to trigger interrupt on the falling edge, and the other to trigger interrupt on the rising edge.

I was hoping this wouldn't be the case or there would be a way to work around, perhaps using input capturing or something. AttachInterrupt can use "change" as an interrupt it seems but I don't know how this is implemented.

...You are correct, "hi-Z" is theoretically not the same as "input": Academically speaking, the high impedance state merely means "not driving the signal ignoring the signal's state", and again strictly academically speaking, an input state implies that a device is both not driving the signal and is interested in the value of the signal. Clearly your emulated EPROM would not be interested in the contents of the data lines when in hi-Z. :) It's merely a convenient way to use one operation to control whether the 8 simulated data signals are driving the bus versus not driving the bus, and your code may freely ignore the states of the signals while they are not being driven.

I wonder if this is any different in timing/function than specifically setting hi-z, but yeah I suppose it should work.
 
So,

Code:
attachInterrupt(digitalPinToInterrupt(37), pinChangeISR, CHANGE);

seems to work reliably, what sort of magic is this..? I supppose I should be diving into the teensy lib source stuff..

Sure, I don't know for *sure* if it's low or high at the exact moment, but if I digitalReadFast and set it on setup and track changes, should be ok?

seems like there's also teensy examples if you use the arduino ide and stuff? might be missing out on that using platformIO, wonder if they should be posted somewhere else..
 
Hmm..doesn't seem to like me..

If I set a pin change ISR and use it to update an address variable which my main loop occasionally prints, I can see the addresses count up as expected.

If I use that same ISR to set the top its of GPIO6_GDIR, ie, set 1.16-1.31 to OUTPUT when OE is high and INPUT when OE is low, it seems that I'm getting garbage on the address..

Code:
#include <Arduino.h>

inline uint32_t readAddressLinesFromGPIO6()
{
  return (GPIO6_DR >> 16);
}

volatile uint8_t OEstate=0;
volatile long unsigned ISRtime;

volatile uint16_t readAddress = 0; // (GPIO6_DR >> 16);

unsigned GDIR_MASK = 0xFFFF0000;

void pinChangeISR() {
  register long unsigned ISRstart = ARM_DWT_CYCCNT;
  if (OEstate == LOW) {
    GPIO6_GDIR &= ~GDIR_MASK; //clear (set pins to input)
    readAddress = (GPIO6_DR >> 16);
    OEstate = HIGH; 
  } else {
    OEstate = LOW;
    GPIO6_GDIR |= GDIR_MASK; //set (set pins to output)
  }

  ISRtime = ARM_DWT_CYCCNT - ISRstart;
}

void setup()
{
  // enable arm cycle counter
  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

  Serial.begin(115200);
  delay(2000);

  OEstate = digitalReadFast(37);
  Serial.printf("OEstate in setup: %d", OEstate);

  attachInterrupt(digitalPinToInterrupt(37), pinChangeISR, CHANGE);
}



void loop()
{
  Serial.printf("-- LOOP START --\nRead address (set in ISR): ");
  for (register uint16_t x=0; x<16;++x) {
    if (x % 4 == 0) { Serial.print (" "); }
    Serial.print(bitRead(readAddress,x));
  }

  Serial.printf("\nIn decimal: %d", readAddress);

  Serial.printf("\nLast ISR took %lu cycles, state set by ISR is %d\nGPIO6_GDIR: ", ISRtime, OEstate);

  for (register uint16_t x=0; x<32;++x) {
    if (x % 4 == 0) { Serial.print (" "); }
    Serial.print(bitRead(GPIO6_GDIR,x));
  }

  Serial.printf("\nIn decimal: %u\tin hex: %x\nRead address from main loop: ", GPIO6_GDIR, GPIO6_GDIR);

  for (register uint16_t x=0; x<16;++x) {
    if (x % 4 == 0) { Serial.print (" "); }
    Serial.print(bitRead(GPIO6_DR>>16,x));
  }  

  Serial.printf("\nIn decimal: %u\tin hex: %x\n -- LOOP END -- \n\n", GPIO6_DR >> 16, GPIO6_DR >> 16);
  delay(1000);
}

I just noticed, that, while it appears I'm setting the right bits of GPIO_GDIR, once I've set them, I'm not able to read all address bits..?

At the beginning, the port is configured 'default', ie, however teensy libs set it up.. seems to be it's already set as input, and the address reads fine.

After setting it to output when OE# is held low, then setting it back when OE# goes high, the ISR doesn't read anything and the main loop seems to only be able to read 16-20 and 27-31?

So, when OE# goes low, it should be set to output and my GDIR register is set to
0000 0000 0000 0000 1111 1111 1111 1111

in order to set GPIO6.16-31 to output

When OE# goes high, it should accept input and my GDIR register is set to
0000 0000 0000 0000 0000 0000 0000 0000


With the lines that change GDIR commented out, I can read fine:

Code:
-> all address pins set to high, then pull OE# high to trigger ISR..

-- LOOP START --
Read address (set in ISR):  1111 1111 1111 1110
In decimal: 32767
Last ISR took 13 cycles, state set by ISR is 0
GPIO6_GDIR:  0000 0000 0000 0000 0000 0000 0000 0000
In decimal: 0   in hex: 0
Read address from main loop:  1111 1111 1111 1110
In decimal: 32767       in hex: 7fff
 -- LOOP END --

-> then, set all address pins low

-- LOOP START --
Read address (set in ISR):  0000 0000 0000 0000
In decimal: 0
Last ISR took 18 cycles, state set by ISR is 1
GPIO6_GDIR:  0000 0000 0000 0000 0000 0000 0000 0000
In decimal: 0   in hex: 0
Read address from main loop:  0000 0000 0000 0000
In decimal: 0   in hex: 0
 -- LOOP END --

but if I let it change GDIR:

Code:
-> all address pins high, pull OE# high to trigger ISR

-- LOOP START --
Read address (set in ISR):  0000 0000 0000 0000
In decimal: 0
Last ISR took 38 cycles, state set by ISR is 1
GPIO6_GDIR:  0000 0000 0000 0000 0000 0000 0000 0000
In decimal: 0   in hex: 0
Read address from main loop:  1111 0000 1111 0000
In decimal: 3855        in hex: f0f
 -- LOOP END --

-> all address pins high, pull OE# low to set pins to output

-- LOOP START --
Read address (set in ISR):  0000 0000 0000 0000
In decimal: 0
Last ISR took 19 cycles, state set by ISR is 0
GPIO6_GDIR:  0000 0000 0000 0000 1111 1111 1111 1111
In decimal: 4294901760  in hex: ffff0000
Read address from main loop:  0000 0000 0000 0000
In decimal: 0   in hex: 0
 -- LOOP END --

not sure what I'm missing here..? It's like I can't read GPIO_DR properly right after setting GPIO_GDIR to input..? And when the main loop comes around later and reads it, bits 4-7, 12-15 are 0's when they should be 1's.?

If this 30 year old MCU can toggle between address/data (input/output) fast enough on a port, I should be able to do it on a teensy :)

I should note I've been doing some testing with my willem45 chip reader/burner; it gives me the ability to easily trigger any of the lines. It also appears to strobe OE# during read, I haven't 100% verified if the timing of such is the same as the ECU though. I'm not sure that sharing the address/data like the ECU does (assuming I could get that working on the t4.1 side) would even work with that or other targets.

Actually, thinking even more, I guess it wouldn't even work on the ECU without jumpering past the latch or using D0-7 as address inputs and latching those values on the teensy somehow, because on the eeprom socket, a0-7 come from the latch outputs which isn't connected to D0-7.. although D0-7 is connected to A0-7 before the latch, so I'd have to watch D0-7 for addresses and latch it myself somehow - either timing related or stealing the LE# signal as well.

However, I will still need to not drive D0-7 when OE# is high even if I use separate output pins. OE# high means AD0-7 is set as output, which goes to the latch and then is held there by LE#, then OE# low means AD0-7 is set as input on the target and the EEPROM should drive the data pins from the data coming from the latch a0-7 and the other address pins directly from MCU. If d0-7 is driven while address selection is happening, the signals will mix pre-latch.

So I can either read A0-7 from D0-7, latch it based on timing or steal the latch enable pin and re-use my address inputs as outputs. Or I can use extra pins as dedicated outputs but they still need to go hi-z based on OE#.

Regardless, I seem to be struggling with manually setting the GPIO_DIR register. Not sure what I'm doing wrong there, as when read out, the bits are what I thought they should be?

Another thing I can't seem to find with my google-fu is how to overclock/change clock speeds on T4.1.. I've read it can do like 1ghz but not sure.. don't know if I'll need to overclock.. if I do so be it, already got a heatsink on..if not then no biggie but was kinda curious.
 
Last edited:
looking at digital.c

in pinMode,

Code:
void pinMode(uint8_t pin, uint8_t mode)
{
	const struct digital_pin_bitband_and_config_table_struct *p;

	if (pin >= CORE_NUM_DIGITAL) return;
	p = digital_pin_to_info_PGM + pin;
	if (mode == OUTPUT || mode == OUTPUT_OPENDRAIN) {
		*(p->reg + 1) |= p->mask; // TODO: atomic
		if (mode == OUTPUT) {
			*(p->pad) = IOMUXC_PAD_DSE(7);
		} else { // OUTPUT_OPENDRAIN
			*(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_ODE;
		}
	} else {
		*(p->reg + 1) &= ~(p->mask); // TODO: atomic
		if (mode == INPUT) {
			*(p->pad) = IOMUXC_PAD_DSE(7);
		} else if (mode == INPUT_PULLUP) {
			*(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3) | IOMUXC_PAD_HYS;
		} else if (mode == INPUT_PULLDOWN) {
			*(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(0) | IOMUXC_PAD_HYS;
		} else { // INPUT_DISABLE
			*(p->pad) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_HYS;
		}
	}
	*(p->mux) = 5 | 0x10;
}

whether it's input or output, it seems that it's setting DSE->7 ? .. wouldn't you want DSE set to zero on input?

Code:
000 DSE_0_output_driver_disabled — output driver disabled;
001 DSE_1_R0_150_Ohm_3_3V_260_Ohm_1_8V — R0(150 Ohm @ 3.3V, 260 Ohm@1.8V)
010DSE_2_R0_2 — R0/2
011DSE_3_R0_3 — R0/3
100DSE_4_R0_4 — R0/4
101DSE_5_R0_5 — R0/5
110DSE_6_R0_6 — R0/6
111DSE_7_R0_7 — R0/7

just curious.. obviously someone much smrtr than I wrote the code, just trying to learn
 
Status
Not open for further replies.
Back
Top