This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C123GH6PM: Tivaware GPIO functions seem to mess with things they shouldn't

Part Number: TM4C123GH6PM
Other Parts Discussed in Thread: TM4C1231H6PGE

I am trying to implement effectively an open drain GPIO with the internal pull up enabled but every time I change the GPIO mode the library resets the pin state.

How do I change JUST the pin direction without the library changing other parameters of the GPIO?

I expect the below code to ultimately toggle the GPIO high/low but the "GPIODirModeSet(SPI_CSZ, GPIO_DIR_MODE_OUT);" seems to set the output state high too, not just the direction.

void TestGPIO()
{
    #define SPI_CSZ GPIO_PORTA_BASE, GPIO_PIN_3
    
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    
    GPIOPadConfigSet(SPI_CSZ, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);

    GPIOPinWrite(SPI_CSZ, 0);

    while(1)
    {
        GPIODirModeSet(SPI_CSZ, GPIO_DIR_MODE_OUT);//Set GPIO low
        GPIODirModeSet(SPI_CSZ, GPIO_DIR_MODE_IN);//Let GPIO go high
    }
}

It is not possible to simply add "GPIOPinWrite(SPI_CSZ, 0);" after then set mode out since if there is anything connected to the GPIO which is pulling it low then the moment between setting output mode and then setting the GPIO low will drive high, causing a contention and also a glitch on the GPIO.

How to I change the direction of a GPIO without touching anything else about the GPIO?

BR,

Steve

  • Hi Steve,
    Can you try GPIOPadConfigSet(SPI_CSZ, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_OD)? I also assume you have an external pullup, right?
  • Charles,
    No, there is no external pullup, hence using GPIO_PIN_TYPE_STD_WPU.
    BR,
    Steve
  • You may want to take a look at the code. I recall one of the oem libraries, either for tiva or stm8, that does this.

    If not, operate on the registers, DIR for directions for example.

    When I get some time I can show a piece of code.
  • Hi Steve,
    If you look at the GPIODirModeSet() it does not reset the pin state. It only changes the direction bit in the GPIO_O_DIR register. What do you have connected to the GPIO pin? Is your external device ever driving the GPIO pin or it is always in input mode? If the external device is in input mode then I don't understand what you meant by having driving conflict. The GPIO pin should be driven low when you call GPIODirModeSet(SPI_CSZ, GPIO_DIR_MODE_OUT). Later when you call GPIODirModeSet(SPI_CSZ, GPIO_DIR_MODE_IN) the GPIO pin becomes an input pin with the weak pullup.

    void
    GPIODirModeSet(uint32_t ui32Port, uint8_t ui8Pins, uint32_t ui32PinIO)
    {
    //
    // Check the arguments.
    //
    ASSERT(_GPIOBaseValid(ui32Port));
    ASSERT((ui32PinIO == GPIO_DIR_MODE_IN) ||
    (ui32PinIO == GPIO_DIR_MODE_OUT) ||
    (ui32PinIO == GPIO_DIR_MODE_HW));

    //
    // Set the pin direction and mode.
    //
    HWREG(ui32Port + GPIO_O_DIR) = ((ui32PinIO & 1) ?
    (HWREG(ui32Port + GPIO_O_DIR) | ui8Pins) :
    (HWREG(ui32Port + GPIO_O_DIR) & ~(ui8Pins)));
    HWREG(ui32Port + GPIO_O_AFSEL) = ((ui32PinIO & 2) ?
    (HWREG(ui32Port + GPIO_O_AFSEL) |
    ui8Pins) :
    (HWREG(ui32Port + GPIO_O_AFSEL) &
    ~(ui8Pins)));
  • Charles,

    Thanks for looking at this.

    After I posted I did look at GPIODirModeSet and agree that it doesn't look like it touches anything other than the direction.

    Further investigation does explain WHAT is happening though, but I believe it is really badly designed hardware !!!

    It looks like when you change the state of the direction register from output to input, then back to output that the DATA register latches the state of the input pins and NOT what was previously in the output register.

    This makes it completely impossible to ensure a 'safe' output condition on the pin since you can't control what it actually drives.

    Consider the following scenario (and is my real life problem)

    I have multiple drivers of the pin and want Tiva to provide the pull up resistance (my hardware is already built so can't change it easily). All the other signal drivers will only ever force the pin low (completely standard open drain behavior).

    Now, if Tiva wants to leave the pin alone (i.e. apply a weak pull up and pin in input mode) then all is OK. Other devices can safely pull down or let float. If Tiva now wants to drive low it should be able to immediately transition from weak pull up to force low, but it can't. It must switch to an output (and since its output data register sensed the high, it will DRIVE high), THEN set the pin low. In that small window of time it will be driving a high on the output. If at exactly the same time an external device also starts driving low then there will be a period of time where Tiva is driving high but the other device is driving low. Only a tiny amount of time, but still systematically not correct, and the whole reason we have open drain behavior in the first place.

    No other micro controller I have ever worked with automatically changes the value of the output data register based on the input state when switching direction!!!

    From what I can gather my only solution would be to add an external pull up resistor and make the pin type open drain.

    Here is register access code which clearly shows the issue...

    void TestGPIO()
    {
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
        while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF));

        GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_1, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU); //Pin should be input, but has weak pull up, so actually transitions high. External goes high as expected

        //Set the pin as an output
        HWREG(GPIO_PORTF_BASE + GPIO_O_DIR) = GPIO_PIN_1;
        //Wiggle high then low to check really output, and also what state it is in
        GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0xff);
        //Leave the output register in state 0
        GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0);

        //Set the pin as an input. Pin goes high as expected due to weak pull up
        HWREG(GPIO_PORTF_BASE + GPIO_O_DIR) = 0x00;
        //Set the pin back as an output. I expect the pin to go back low since that was the last output state it was in
        HWREG(GPIO_PORTF_BASE + GPIO_O_DIR) = GPIO_PIN_1;
        //Unfortunately it is necessary to 'reset' the output data register low
        GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0);
    }

  • Hi Steve,
    I understand what you are saying. I think the reason is the fact that there is only the GPIODATA register. In a different MCU I worked with there are two separate DOUT and DIN registers for each pin. As I mentioned in my first reply, the suggestion would be to put the pin in open-drain mode and have an external pullup.
  • "It looks like when you change the state of the direction register from output to input, then back to output that the DATA register latches the state of the input pins and NOT what was previously in the output register."

    to me, that would be the desirable (generally) behavior where the input/output data register is combined.

    you may want to look for a chip that has separate input / output data registers.

  • I disagree.

    The 'registers' are logical, not physical.

    A read should simply return what is seen at the pins (when configured as output reading would then return what was written, when configured as input it would return the input state).

    The registers are not really 'combined', they simply appear at the same address in memory space.

    Either way, writing to one register (the data direction register) should not affect a completely different register (the data register).

    Anyhow, religious discussions aside, the behavior is as it is, so I need to work around it.

    BR,

    Steve

  • "A read should simply return what is seen at the pins (when configured as output reading would then return what was written, when configured as input it would return the input state)."

    precisely. so when you turn the direction from output to input, the DATA register takes on the state of the "pins", rather than what you had "previously" written to those pins. that's precisely what the chip does.

    to do what you wanted to do, you will need a chip with an INPUT data register and an OUTPUT data register.
  • As poster "Danny F" well noted (earlier) - it is my recollection as well (and I've been here forever) that "Just such an issue" WAS OVERCOME!

    Via the hallowed "Forum Search Box" - should the proper keyword be chosen - I believe, "Light may arise @ tunnel's END!"

    I'm forced to "exit fun time" - and engage in (some/brief) Profit-Making - yet the answer resided in the "Sequence of  API function calls!"    (bit unexpected - indeed - as a "kid" I had "tested/proven "Photo Memory" and I recall the (past) post - answering your issue - exactly!)    Allez!

  • The data 'register ' isn't 'real'. It is simply a mux that selects the input path (or internal bus drivers). There shouldn't be any latching of the pin input state to the data register when the direction is changed.

    Even though the data out and data in 'appear' at the same address in memory space they are usually completely different hardware paths. The TM4C is the only processor I have ever encountered that latches the input pin state in the data out register when changing the direction.

    In fact, have a look here for a diagram of what it should be.
    Look at figure 6.7
    users.ece.utexas.edu/.../C6_MicrocontrollerPorts.htm

    They have a statement "Common Error: Many program errors can be traced to confusion between I/O ports and regular memory. For example, you should not write to an input port, and sometimes we cannot read from an output port", which is what I think you are referring to.

    I will say that in figure 6.7 the labels are wrong and "Write to port address direction bits" should say "Write to port data bits".

    You can see though from this diagram (regardless of label naming) that changing the port direction register does not loop back the input pin state back to the output flip flop since only one of "read from port address" or "Write to port data bits" or "Write to port direction register" can be active at the same time.

    The key points here are that a read returns the PIN state, not necessarily what was written to the register, and that the output flop (top flop) should ONLY get updated when explicitly written to. The direction flop has nothing to do with the state of the data flop.

    This is NOT how things seem to be behaving though and it is almost as if there is an OR-gate on the "Write to port data bits" clock input oring to "Read from port address"
  • Hi Steve,
    The UT diagram is probably a generic IO diagram which is true for some other TI MCUs (i.e. TMS570 MCU) . I have just confirmed with my colleague (Amit) that there is only one physical DATA register in TM4C.
  • Since I DON'T have a photographic memory, but do recall a similar issue, I tried to find the previous post but I couldn't find it after 20 minutes so decided to seek help again.

    I have just tried again to find the post, even searching through my own posts directly, but still can't find it, so if you have a link please let me know. I would genuinely like to review it since the current behavior seems incorrect, and makes me think I am doing something wrong.

    Forum search boxes are great when you can tune the terms to what you need. I couldn't. Sorry.

    BUT, that previous issue was more related to the Tivaware sequences and what is 'hidden' in the function calls (i.e. setting the direction register explicitly also changed the configuration... or visa versa... can't recall)

    THIS thread is now specifically about how the hardware behaves, even at the lowest hardware level, so whilst certainly similar, most certainly not "exactly" the same.

    BR,
    Steve
  • We must agree then to "disagree" - I stand by "exactly" - or at minimum,  "startling close."    (usually I'm sufficiently "disciplined" to "Cut/Paste such "Hi Value" info...)     Note too - new poster,  "Danny F." had "similar recall" - that's "Two against One" - is it not?    (and - we're told you've,  "Lost your keys" - that's a memory  deficiency - one must assume...)

    Most vendor staff exit on weekends - if you arrive here - I'll search tomorrow.

    Some "Profit Making Activity" (insufficiently produced here) expected - I've a client meeting - will engage tomorrow - should you arrive & signal...

  • Charles,
    (first, a clarification... I am going to start using the term flip-flop for a physical hardware 'register' and use the term 'register' for a firmware accessible memory location since the term means very different things in software worlds and physical hardware worlds.)

    I agree there is only one flip flop, and only one FW register for the data. That is all that is needed, and that is all that is in the UT diagram too.

    Reading a memory register simply enables the input pin to drive the bus. It does not require a physical hardware flip-flop and the 'register' maps to the address and the fact that it is a read enables the input buffer to drive the bus. For a write the flip-flop load enable is set based on the SAME address but the fact it is a write.

    Like I mentioned a while back, the same logical register address can (and will) map to different hardware.

    What I really would like to see is the details inside the "Data Control" block of Figure 10-1. Digital I/O Pads in the "Tiva™ TM4C1231H6PGE Microcontroller DATA SHEET". I couldn't find this anywhere in the datasheet.

    BR,
    Steve
  • "The key points here are that a read returns the PIN state, not necessarily what was written to the register, and that the output flop (top flop) should ONLY get updated when explicitly written to."

    I think that kind of behaviors can be useful but I'm not sure if it is universally desirable.

    going OD as you mentioned / using (internal) pull-up can work. Using a shadow register is another - there will be a small / quick glitch however. You can also hang a capacitor/resistor network on the output pin to alleviate that.
  • Our current solution is to live with the momentary drive high. The chance of it being contention in our system if very, very low and even then shouldn't be a problem.

    The purist in me doesn't really like it though :)

    BR,
    Steve
  • "The purist in me doesn't really like it though :)"

    I can understand that as well. if anything, I can confirm that the behaviors you described are real - as I was able to replicate it.

    I would have preferred that two data registers are used, as TI did in their msp430/432 families, or most modern MCUs.
  • Hi Steve,

    Steve Clynes said:
    Reading a memory register simply enables the input pin to drive the bus. It does not require a physical hardware flip-flop and the 'register' maps to the address and the fact that it is a read enables the input buffer to drive the bus. For a write the flip-flop load enable is set based on the SAME address but the fact it is a write.

     In my experience I rarely see the pin state directly driving the peripheral bus and then back to the CPU. It is always latched (with a flip-flop) for reasons such as deglitching, breaking timing loop during STA (static timing analysis) or in the case of GIO to detect edge transition for interrupt generation. 

      Below is an excerpt from the datasheet. 

    The GPIO Direction (GPIODIR) register (see page 663) is used to configure each individual pin as
    an input or output. When the data direction bit is cleared, the GPIO is configured as an input, and
    the corresponding data register bit captures and stores the value on the GPIO port. When the data
    direction bit is set, the GPIO is configured as an output, and the corresponding data register bit is
    driven out on the GPIO port.

  • I think we all agree (I hope?) that this behavior is datasheet-compliant.

    the debate seems to be on if such a behavior is desirable. unfortunately that's quite subjective, as shown here. Personally I think arguments can be made on both sides.
  • Datasheet compliant, well, I guess. "the GPIO is configured as an input, and the corresponding data register bit captures and stores the value on the GPIO port" does say that, but it is very common for the physical hardware to have different flip flops for input and output data which appear at the same register address.

    As you say though, mute point since this IS how it behaves, and now I understand that I have to live with the output high glitch and move on :-)

    Good discussion though.

    BR,
    Steve
  • Charles,
    Re : "I rarely see the pin state directly driving the peripheral bus", I agree and was trying to simplify the signal flow logically. From the UT presentation you can see what I was trying to convey. Sometimes there is other 'stuff' between the actual port and the system bus, but the end result is the pin state pops on the bus when requested.
    It is not common for this data to also pop into another flip-flop automatically.

    BR,
    Steve
  • "but it is very common for the physical hardware to have different flip flops for input and output data which appear at the same register address. "

    agreed. where you can flip the pin's direction register to output a pulse, for example. TI's implementation here makes that approach difficult (but not impossible).
  • After a brief search - I did succeed in finding the post to,  "come closest"  (I believe) to  meeting your objective.    

    The "Verified Answer" was authored by  past forum leader, "Mr. Amit Ashara" -  via post dated 19 Apr 2016, @ 12:30.    The originating poster, "Brett Preston" - detailed Amit's finding w/ sample code - that appearing same date (19 Apr 2016, @ 14:14 (2:14 p.m.))

    The post details,  "A method to overcome the API's code tendency to "glitch" GPIO output (low) - immediately upon ordering the GPIO away from "Input."     The method employs - as I earlier recalled & noted - an "unusual sequence" of GPIO function calls.    

    Such may, "Answer your need" directly - or  may suggest  a  "new path" for your  further - consideration/investigation.

    That past (2016 dated thread) is found (below) - the 2 VITAL posts appear w/in page 1 of that thread.    (Thread contains "2 pages" - the link  "deposits  readers" upon page 2 - you must scroll down to expose the "Page Back Arrow."  so that page 1's content reveals...)       Note too - that for,  "unexplained/unjustified reason"  -  that past thread has been,  "Locked"  -  preventing (or at minimum increasing) effort required to seek (further) suggestion and/or guidance...

    e2e.ti.com/.../1841296

  • Hi cb1,
    LIKE. Thank you for digging out this post.
  • As I mentioned previously, the post referenced talks about the order of operations from the API and whilst absolutely true and relevant, it is not actually talking about the root cause of my specific issue, and is not the complete story.

    In the thread you link to there is a comment "In this case you have to fully set the initial state of the pin before you enable the output driver.", but this is not in fact the whole story.

    The REAL cause of my specific issue is that when the DIR register is changed the output register gets updated to reflect the current state of the inputs (or is continually updated since it was an input).This completely precludes the ability to "pre-set' the output states before changing the direction register. My guess is that this behavior is designed to ensure that there is never a glitch in the state of the pin when switching from input to output, but it is not the behavior I need. I need to make sure I can never drive high, only drive low (i.e. open drain like). I wanted OD behavior but use the internal pull ups, but I don't seem to be able to configure both OD and WPU.

    So, in my case I want to 'pre-load' a zero on a pin so that if/when I enable the output it only drives low, but if the external pin state is high (due to a pull up, either internal or external) then, since the pin is an input, the data register will not accept the pre-loading, and will retain the high setting. This means that even if I try to "fully set the initial state of the pin before enabling the output driver" the state of the output when switched will STILL only output what was on the input before switching and not the value I want to force.

    I hate to labor the point but it is a subtle but critical distinction which is not clarified in any E2E thread I have seen so far. The datasheet DOES say this is the behavior, but man is it buried in there, and definitely not how every other micro-controller I have ever worked with behaves.

    I do think we have beaten this to death now, but wanted to make sure a full explanation of the hardware behavior exists here on E2E for future generations :)

    I do appreciate your time looking at this question. People like you make E2E a great resource.

    BR,

    Steve

  • I agree with Steve. This behavior is hardware driven, assuming the datasheet is accurate. When the direction is turned from input to output, the data register isn't updated to reflect the content of the output data register.

    As such I don't see how this can be fixed via a software solution.
  • Thank you for your much appreciated, kind/closing sentence.

    Have you actually attempted (i.e. exercised the hardware) in accordance w/the method prescribed - w/in that past (yet searched & found) thread?

    Firm/I so often find that, "real-world" digging/discovery may lead to, "unexplored (even) unexpected paths!" (which "on multiple occasions" have, "trumped/expanded/exceeded" (any/all) data sheet recitals...)