Using the Open Drain capabilities of the Teensy 3.1 processor

Status
Not open for further replies.

MikeWood

New member
Being new to the Teensy/Arduino environment I'm not sure if this is doable. There doesn't seem to be an option in the pin setup modes for this, perhaps I'm looking in the wrong place. My product requires this capability so if I need to be able to twiddle the register bits to make it happen. Any suggestions on how to do this will be helpful.
Thanks in advance.
 
You will need to write directly to the registers.

Teensyduino defines names CORE_PINx_CONFIG, where "x" is the pin number, which makes it a little simpler.

For example, if you want to put pin 5 into open drain mode, you'd use:

Code:
CORE_PIN5_CONFIG = PORT_PCR_ODE | PORT_PCR_MUX(1);

I haven't personally tested this mode, but my belief is digitalWrite() ought to work once it's in this mode, but writing HIGH will make the pin disconnected instead of driven high.
 
I didn't realize there was an open-drain mode. The datasheet does not contain the phrase "open drain". Is there a better datasheet I should be using?

I understand that (unlike T3.0) the Teensy 3.1 I/O pins are +5V-tolerant, but I presume that is in "input" mode only. Or can they actually accept a 5V load in open-drain mode as well ?
 
Last edited:
This one: http://www.pjrc.com/teensy/K20P64M72SF1RM.pdf

Specifically Ch. 10 (muxing) and Ch. 11 (port config). Open drain is Sect 11.14.1, pg 227.

Has anybody had some luck working with "open drain"?

I need to communicate with the MGC3130 from Microchip and it asks for an 'open drain' connection not only on the GPIO's but also on the SCL and SDA pins of the I2C port....

In addition, MGC3130 requires a dedicated transfer status line (TS), which features a
data transfer status function. It is used by both I2C Master and Slave to control the data
flow. I2C SCL, I2C SDA and TS lines require an open-drain connection on MGC3130
and the connected host controller. To function properly, I2C SCL and I2C SDA need to
be pulled up to VCC with 1.8 kΩ resistors and the TS line needs to be pulled up to VCC
with a 10 kΩ resistor

The code that Paul provided does not work. If I use digitalWrite alternating between HIGH and LOW on PIN 5 after using that command in the setup I see the voltage moving between 3 and 3.3 volts. They get both pulled down by a 1.8 kΩ resistor....

Where can I see the teensyduino text on this? Pauls code is not part of the Freescale manual either it seems...

Thanks in advance.
 
I am only guessing, and someone like Paul probably has a lot more knowledge here.
The T3 I2C may already be open Drain or at least compatible as the I2C section of the manual(46) talks about multimaster support as well as clients being able to control the clock (clock stretching)... Again only guessing.

But one thing jumped out at me in the last post. Your quote talks about which PU resisters to use on the SCL(1.8K) as well as the SDA(1.8K) and the other pin 10K, but your comments talk about Pull Down resistors, which does not make sense to me. Maybe it was a typo...

Kurt

Edit: Note you did not post your code, but If I were to try to do this, I would make sure to also first do:

I would also set the other config this that are set by pinMode. Something like:
Code:
pinMode(5, OUTPUT);   // This would configure some of the stuff...
CORE_PIN5_CONFIG = PORT_PCR_ODE | PORT_PCR_MUX(1) | PORT_PCR_SRE | PORT_PCR_DSE ;

Then I believe that digitalWrite will leave the settings alone, but probably should test to see...
 
Last edited:
I2C is already open drain per definition.

Note, if you have never setup I2C on a Teensy before, you do have to use external pull-up resistors between each of A4/A5 and the 3.3v power. I use 4.7K resistors, which is what Paul originally recommended when I was first getting into i2c. There might be situations where different power resistors are called for (switching to higher speeds, or dealing with longer connections). If you've used an Uno before, you didn't have to do this on an Uno, since the Uno library enables the internal resistors that the Uno provides.
 
The internal pull-up resistors on most AVR boards are in the range of 20kΩ - 50kΩ. While that may work for close range (stacked shield) low frequency (100KHz) I2C communication it is better practice to use an external pull-up resistor.

IIRC the internal pull-ups on the Teensy 3 are 33kΩ.
 
Last edited:
Dear all,

Thanks for all the (quick) reply's. I made some progress but I am not there yet. I succeeded in writing to the MGC3130 on an irregular basis.... (at least now I sometimes see a positive error message when writing to the MGC3130 and when that happens I can see on my scope that the Bytes are written to the MGC as well). It seems that there were (are) some problems in the wiring though since it sometimes works (either just ones between a lot of unsuccesful attempts or a number of succesful attempts in a row (weird....)). My guess is that there is one parameter just slightly wrong for the MGC3130. In the initialization code from microchip I saw something about the I2C slewrate that they enable in the main program on the I2C bridge(that is between the MGC and the computer that they use and that I want to replace with the teensy in order to work standalone)....

With the I2C library from NOX771 it is easy to work with external pullups. So that is what I implemented in the code now:
#include <i2c_t3.h>

byte request_FW_version_info[] = {
0x0C, 0x00, 0x00, 0x06, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // this is the I2C payload message to make the MGC3130 respond with its firmware version


void setup()
{
Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, I2C_RATE_300); // MGC3130 works up to 400 Khz
Serial.begin(115200); // start serial for output
}

void loop()
{
Wire.beginTransmission(0x42);
Wire.write(request_FW_version_info, 12);
int TransmitSucces = Wire.endTransmission(); // diagnostics
Serial.print ("TransmitSucces = ");
Serial.println (TransmitSucces);
delay(2000);
}

I tried the pullup resisters of 1.8k and 4.7k but there seems to be no difference in the behavior. The error message that I get (when getting it) is 2 (the address is not recognized). I guess that the signal cannot be interpreted right because sometimes there is success so the code in itself seems to work there is sometimes even a longer period where it keeps transmitting succesful (few minutes). Does anybody recognize this?



The TS pin:

Thanks KurtE. I used your code and it works flawlessly. Without an external pull-up resister the PIN stays low and with the resistor it jumps between 0 and 5. I will use another PIN as an input to check on the TS line. Where can I find this syntax?

void setup(){
//configure pin0 as an input and enable the internal pull-up resistor
pinMode(5, OUTPUT); // This would configure some of the stuff...
CORE_PIN5_CONFIG = PORT_PCR_ODE | PORT_PCR_MUX(1) | PORT_PCR_SRE | PORT_PCR_DSE ;

}

void loop(){
digitalWrite(5, LOW);
delay (2000);
digitalWrite(5, HIGH);
delay (2000);
}
 
You will need to write directly to the registers.

Teensyduino defines names CORE_PINx_CONFIG, where "x" is the pin number, which makes it a little simpler.

For example, if you want to put pin 5 into open drain mode, you'd use:

Code:
CORE_PIN5_CONFIG = PORT_PCR_ODE | PORT_PCR_MUX(1);

I haven't personally tested this mode, but my belief is digitalWrite() ought to work once it's in this mode, but writing HIGH will make the pin disconnected instead of driven high.


Some more research would be in order here, but I did a cursory test, and it appears to me that digitalWrite() messes with some of the port control bits. Maybe it works ok for I2C (I didn't try that), but for generic IO ports (pin 16 in my case), there is most definitely some fishy behavior.

From the K20P64M72SF1RM spec, it looks like the open drain (ODE) bit and pull enable (PE) functions are supposed to be independent, but my testing casts some doubt on this assumption.

My test circuit consisted of using an LED to see where current is flowing, and this code:
Code:
void setup()
{
    digitalWrite(16, HIGH);  //since the signal is active LOW, pre-set the pin to HIGH (inactive)
    CORE_PIN16_CONFIG = PORT_PCR_ODE | PORT_PCR_MUX(1); //turn on the Open Drain feature
    pinMode(16, OUTPUT);  //and make the pin an output
}


void loop()
{
    delay(5000);
    digitalWrite(16, LOW);  //assert
    delay(500);
    digitalWrite(16, HIGH);  //de-assert
}


First part of the test: connected pin 16 -->LED-->resistor-->Vin. This is typically how you'd hook up an LED when you have an active LOW signal. In this configuration, I expected to see the following behavior:
* When Port 16 is HIGH (not asserted) = LED off
* When Port 16 is LOW (asserted) = LED on
And indeed, this is exactly what I saw. The LED was off for 5 seconds, and blinked on for 0.5 seconds, and repeated.

However, that does not tell us whether the HIGH state is connected to Vin or just floats (disconnected). To determine that, I switched the polarity of the LED as follows: connected pin 16-->LED-->resistor-->GND. In this configuration I would expect to see the LED stay OFF all the time. Here is the behavior I observed in this configuration:
* When the sketch started, PORT 16 started out HIGH (not asserted), and as expected, the LED was OFF for 5 seconds.
* When the port went LOW (asserted) after 5 seconds, the LED remained OFF, as expected.
* When the port returned to HIGH (not asserted), however, the LED came ON (NOT EXPECTED BEHAVIOR!)
This means there is some kind of leakage going on when the port is in the HIGH state. This can be very bad in some circuits (parts can be destroyed). Some kind of pull-up became enabled when the digitalWrite() function was called.

To try and "fix" the problem, I inserted another instruction to re-enable the Open Drain feature as soon as the port was set back to HIGH, as follows:
Code:
void setup()
{
    digitalWrite(16, HIGH);  //since the signal is active LOW, pre-set the pin to HIGH (inactive)
    CORE_PIN16_CONFIG = PORT_PCR_ODE | PORT_PCR_MUX(1); //turn on the Open Drain feature
    pinMode(16, OUTPUT);  //and make the pin an output
}


void loop()
{
    delay(5000);
    digitalWrite(16, LOW);  //assert
    delay(500);
    digitalWrite(16, HIGH);  //de-assert
    CORE_PIN16_CONFIG = PORT_PCR_ODE | PORT_PCR_MUX(1); //turn on the Open Drain feature AGAIN
}

In this configuration, the LED behaved as expected, and never lit. Therefore, I conclude that digitalWrite() must destroy the ODE setting. This software fix, however, is not ideal, because there is a small but finite amount of time between the digitalWrite(16, HIGH) and re-enabling ODE where a voltage differential is present between the pin and GND.

So the takeaway here is be very careful when configuring the IO ports using the CORE_PINx_CONFIG, because your setting will probably get blown away as soon as you call digitalWrite().
 
I continued experimenting, and found my previous conclusion was not entirely correct. So I'm amending my previous post with the updated information.

I rewrote my test to not use digitalWrite() in the main loop, but use the direct port access instructions instead:
Code:
void setup()
{
    digitalWrite(16, HIGH);
    CORE_PIN16_CONFIG = PORT_PCR_ODE | PORT_PCR_MUX(1);
    pinMode(16, OUTPUT);
}


void loop()
{
    delay(5000);
    GPIOB_PCOR = 0x1;  //assert  (low)
    delay(500);
    GPIOB_PSOR = 0x1;  //de-assert (high)
}

Oddly, I had the exact same problem as before (current leakage appeared after the first de-assertion). So apparently it is actually the pinMode() function and not digitalWrite() that stomps on the ODE bit (which makes a little more sense, since one might possibly expect pinMode to modify the config register). But what is weird is that it actually takes a port transition to make the leak start.

So I think the best fix for this problem modify the DDR directly instead of using the pinMode() function. Once I removed the pinMode() function, I was able to use either digitalWrite() or the output port register and have the circuit behave correctly. Here's my final code:
Code:
void setup()
{
    digitalWrite(16, HIGH);
    CORE_PIN16_CONFIG = PORT_PCR_ODE | PORT_PCR_MUX(1);  //enable open drain
    GPIOB_PDDR = GPIOB_PDDR | 0x1;  //set pin 16 to output
}


void loop()
{
    delay(5000);  
    digitalWrite(16, LOW);  //assert
    delay(500);
    digitalWrite(16, HIGH); //de-assert
}

Both the digitalWrite() and direct output port manipulation methods work fine. As a note, although I might have been able to order the pinMode() function BEFORE the CORE_PINx_CONFIG to avoid stomping on the setting, I didn't want to do that because once again, there would have been a small but finite period of time, between the two instructions where the would be a voltage differential between the pin and GND.

Unfortunately, I don't see a way to avoid having to figure out which port and bit corresponds to the pin you want to set to output when configuring the DDR. But then again, using the output port registers is much more efficient than using DigitalWrite() anyway, and that method requires the same mapping.

So skip the previous takeaway--it is not entirely correct. New takeaway: If you want to use CORE_PINx_CONFIG settings, also directly manipulate the DDR registers instead of using pinMode() to avoid stomping on your CONFIG settings. Then go ahead and use digitalWrite() if you wish.
 
Last edited:
Late on parade here, but the simplest way to emulate open drain is by switching between pinMode OUTPUT with pin set to LOW to pull down the line, then pinMode INPUT to release it.
 
That's what I do. Combined with a gate pullup to 5V, it allows a 5V tolerant teensy to control a 5V load with a low side mosfet - even though the teensy itself can't produce 5V.
 
Status
Not open for further replies.
Back
Top