SpenceKonde / megaTinyCore

Arduino core for the tinyAVR 0/1/2-series - Ones's digit 2,4,5,7 (pincount, 8,14,20,24), tens digit 0, 1, or 2 (featureset), preceded by flash in kb. Library maintainers: porting help available!
Other
551 stars 142 forks source link

T3216 won't shutdown/restart if PB2/3 connected to other UART #486

Closed mrWheel closed 3 years ago

mrWheel commented 3 years ago

Hi,

I have a T3216 that gets commands from an other T3216. Both are connected by PB2/PB3 (crossed).

If I switch off the power of one of them and turn the power back on the T3216 won't restart/reboot.

The voltage over Vcc with "power off" is about 2.2v.

The same problem if I connect an USB2Serial adapter to PB2/PB3.

I have tried all kinds of (BOD) settings but it won't help.

Is this a known problem or am I doing the impossible (again)? Is there a solution?

SpenceKonde commented 3 years ago

The cause of the problem, as you probably recognize, is that UART serial is HIGH when idle, so with that and ground connected, you're "backpowering it" through the protection diode. Thus the voltage isn't allowed to fall below the power on reset rearm threshold, but is falling below the voltage required for it to function, leaving the chip in a bad state when power is restored.

Backpowering a part by connecting power to an input pin while the chip is powered is a recipe for problems and misery. That's how you burn out pins. You must ensure that the voltages applied to the pins of the "off" device have current limited to a low enough level they will not backpower the device.

What clock speed are they running at? What BOD voltages have you tried? What BOD mode? proper BOD configuration should be able to prevent the hung state , though you will still be operating the chip outside it's specifications when it's backpowered, and cannot expect it to survive long term. What is the nominal operating voltage? What else is connected to them?

My first thought for solution would be to reconfigure the UART on the device that remains powered (or both, if either of them can lose power without the other one losing power) to run in Open Drain Mode (note that as described in the Silicon Errata, and contrary to the datasheet itself,the 3216 and every other modern AVR, open drain mode requires that the TX pin be set as an input, not an output - Serial.begin() sets TX to output, so you'd want to immediately set it back to input and set open drain mode after Serial.begin is called), and have each device enable the pullup on it's RX line. That way, the extent of any backpowering would be limited to the current through the internal pullup on the still-powered part's RX pin....

mrWheel commented 3 years ago

Spence, thanks for your quick reply!

I have scanned the internet and the closes I could find regarding a backfeeding solution is this:

  Serial.begin(9600);
  //-- set OpenDrain mode
  pinMode(PIN_PB2, INPUT);
  digitalWrite(PIN_PB2, LOW); 

But than there is no communication at all so abandoned that.

Also tried placing a resistor in series with the TX lines (tried 500 Ohm to 10k) which did something but I'm not sure that it is a good solution.

Chip: ATTiny3216
Clock: "20MHz internal"
millis()/micros() Timer: "Enabled"
Startup Time: "64ms"
BOD Voltage Level: tried them all
BOD mode when Active/Sleeping: "Enabled/Enabled"
UPDI/Reset: "UPDI (no reset pin)"

Sad to say: I have no idea what the implications of "Startup Time" or the BOD levels are.

I do hope you can elaborate a bit more and maybe post some code?

SpenceKonde commented 3 years ago

If your code, you do not enable open drain mode. you just say //enable open drain mode You then set the pin to input as the open drain mode which you never enabled requires, and yeah in that case I wouldn't expect you to get any output!

SpenceKonde commented 3 years ago
Serial.begin(9600); //easily slow enough to expect to work. 
USART0.CTRLB |= USART_ODME_bm;  //enable open drain mode
VPORTB.DIR &= ~(1<<2);  // pinMode(PIN_PB2, INPUT) only small and fast. 
VPORTB.OUT &= ~(1<<2); //digitalWrite(PIN_PB2,LOW) //I forget whether Serial.begin sets the  output state or just relies on the peripheral overriding output value, or whether we even care in in open drain mode.. This line is probably unneeded. 
// note that digitalWriteFast(PIN_PB2,LOW) is exactly equivalent to VPORTB.OUT &= ~(1<<2)
PORTB.PIN3CTRL |= PORT_PULLUPEN_bm;   // pinMode(PIN_PB3, INPUT)-assumiung-the-pin-is-input.  We definitely need the pullup on RX enabled. I don't know whether Serial does this, so again this may be unnecessary

I do direct port manipulation habitually >.> digitalWrite/pinMode are stunningly slow (and also bloat the size of the binary, though you get the same amount of bloat if you use it more than once anywhere in the sketch - after that, each additional call to it is just a couple of instructions to call the bloated blob of a function.

SpenceKonde commented 3 years ago

RE BOD level: This is a voltage that the power supply voltage is contstantly compared to. In the event that the supply voltage is found to be less than the "BOD" (brown out detect" voltage the chip is held in reset until the voltage is higher than that This should absolutely ensure that the part resets cleanly. It's a terrible bandaid for this specific problem, but it absolutely should guarantee clean resets. You did do "Burn bootloader" after each time you changed one of the bootloader settings, like it says you need to do in the menu right?

(note that that includes reprogramming the chip to use a differenmt BOD level - for that reason, the BOD settings (as described in the documentartion) are considered "unsafe" so they are only set when you do "burn bootloader", as opposed to a few "safe" fuses that we set whenever uploading via UPDI.

Safe fuses are written whenever a sketch is uploaded via UPDI. for example. OSCCFG which sets whether the oscillator speed is derived from 16 or 20 MHz (before I made it set "safe" fuses on all UPDI yuoploads,, more "bug reports" than any other cause, indeed possibly more bugs than all other causes combined - resulted from people changing the clock speed from the menu and not doing burn bootloader, even when the menu was - welll - like it is now, only without the word "NOT", and even though every AVR supported by Arduino by any core and released prior to these tinies in 2016, has required burn bootloader to apply changes to clock speed..... But on those older parts, setting clock source is not a "safe" setting (no fuse is "safe" on a classic AVR - all have at least one bit that would brick the chip in a way that's hard to recover from without special equipment!)) , and the BOOTEND and APPEND fuses, (which set the length of the bootloader and application section, respectively - code has to be compiled with the knowledge of whether it is to run on something with or without a bootloader on these parts).

Startup time (the delay between the release of power on reset, and when it actually starts executing code is kinda rarely used - for example. if you have a "slow rising" power supply, and the BOD is not enabled to save power, you can tell it to wait up to 64 milliseconds before it tries to execute code, in the hopes that by then, the power supply voltage will be high enough that it will work. The statup time is controlled by one of the SYSCFG fuses,. that one is safe (I think it just does SUT - it may control CRC scan too, but we have no option to turn on CRC scan, because the function of CRC scan is to guarantee that any code without a valid checksum doesnt get allowed to run. Having no facility to generate such a CRC value, it would just guarantee that no code we uploaded would be allowed to run. Whichever fuse thats on,never turn on the CRC scan :-P

The other SYSCFG is "unsafe" because it lets you turn the UPDI pin into a reset pin,. and you need a high voltage updi programmer to undo that; those are still exotic. The EEPROM saving is controlled by a bit on the same fuse as the reset pin thing, which is why that one needs burn bootloader to apply.

And the risk with the BODCFG (both level and mode set different bits in the BODCFG fuse) is that imagine you have the chip and some other parts soldered down to a circuit board. And some of the things it's connected to are only 3.3v maximum. If you set the BOD voltage to 4.2v and BOD mode to enabled, the chip will always be held in reset,even if you try to connect a programmer to reprogram it, until you supply more than 4.2v. Which you can't do without destroying other components on the circuit board, so you'd have to unsolder stuff in order to supply a high enough voltage to get it into programming mode and tell it not to do that.

And so, the rule with megaTinyCore and DxCore is - if the fuse can make it impossible to reprogramthe part if set wrong ( without either semi-exotic HV programming tools or a soldering iron), or if there are no options that would change that fuse , it will only be set when you do tools -> burn bootloader (whether or not selected board definition has a bootloader - often it doesn't,. but there's no way that I as a core developer can change that menu option to say "Set fuses and burn bootloader (if any)" or something that would be more accurate). The options like this are marked as such in the menu. That ensures that someone will at least pause and think, and not just casually click upload, forgetting that they were just programming something with different fuse settings. All safe fuses that won't render the chip unprogrammable are always set upon UPDI upload unless our core has no options for changing them (ex: WDTCFG or TCDCFG. which control the startup configuration of the watchdog timer and timer D respectively - if those got changed, we assume the user did it on purpose, and don't undo their work - we do reset those when doing "burrn bootloader" since that traditionally restores the chip to a fully known state.

mrWheel commented 3 years ago

Thank you so much!


Serial.begin(9600); //easily slow enough to expect to work. 
USART0.CTRLB |= USART_ODME_bm;  //enable open drain mode
VPORTB.DIR &= ~(1<<2);  // pinMode(PIN_PB2, INPUT) only small and fast. 
VPORTB.OUT &= ~(1<<2); //digitalWrite(PIN_PB2,LOW) //I forget whether Serial.begin sets the  output state or 

I néver would have figured that out!

I tried to read the datasheet on the GPIO registers but could not find information on how to "open drain" other GPIO pins (I need SoftwareSerial to also be open drained). So I assume this is not possible (but if it is a code example would again be very welcome)?

This information will probably get buried in all the other information in the "closed issues". Maybe it's an idea to make a kind of "tricks" information page. A code and circuit example of "how to make reset on PB4 work" would definitly need to be in there as well!

SpenceKonde commented 3 years ago

One could adapt SoftwareSerial to act as opendrain, but that library is an absolute abomination. I couldn't do work on that without rewriting it, it's not fun to gold plate cow pies. A better software serial implementation is something that I do not have time to write (or rather, I do not expect to ever get to it - too many other higher priority action items). SoftwareSerial.h is implemented in C... it is a BITBANG IMPLEMENTATION OF A TIMING CRITICAL PROTOCOL WRITTEN IN C. the timing critical parts of it need to be implemented in inline assembly. I looked into writing something like that once.... it gets unpleasant dealing with the RX. Frankly I think it's a miracle that it ever works at all, especially using attachInterrupt!

I'd also also need to have the ISRs placed in separate files for each port, or need to use some dirty trick like this (this is a problem with attachInterrupt() which is another abomination)

#define KONDE_SOFTWARE_SERIAL_RX_PORT PA
#include <KondeSoftSerial.h> // This in turn will #ifdef KONDE_SOFTWARE_SERIAL_RX_PORT
// and if it finds one, then - and only then - will it create an RX ISR for that port. I'd also need to make it error out if that wasn't defined
// but ONLY when included from the main sketch. When included by any other file, I'd need to detect that define and #error if it *is* defined, because that can only be defined in one place otherwise it's a duplicate vector error. 
// like I said, this is a "dirty trick" approach, not a generally applicable one, and I don't like it.

PB4 reset is only supported on the 2-series parts (eg, the ATtiny1626 and upcoming 3226), and only on parts that have a PB4. There is no code. You just select PB4 as reset, and do "burn bootloader" and PB4 acts as a hardware reset pin instead of an I/O pin. The standard DTR autoreset circuit can be used. The fact that you can;'t get a hardware reset pin on the 0/1-series if you don't have an HV UPDI programmer is terribly disappointing. Honestly, the quality of the 0/1-series was kinda slapdash. some of the peripherals come off as "beta test" quality - ex, the way the event system was done there, vs everywhere else, the key missing features of the TCBs, the whacko random reference voltages.... but AVRs had been languishing for quite a while. When you go from like... the featureset of the classic AVR tinies, to what you have on the modern ones, it's unrealistic to expect there to not be a fair number of bugs. (though I'd say that the 1-series came with an unfair number of bugs) The 2-series is much better in that regard.

The closest you can come on a 1-series like the 3216 is "ersatz reset": putting a low level interrupt on a pin that requests a software reset. There is an example of it included with the "megaTinyCore" library that the core comes with (which isn't so much a library as a container for examples of how to do stuff.

The 2-series parts also have a second hardware serial port. It sounds to me like you're using the wrong 20-pin tinyAVR.

SpenceKonde commented 3 years ago

As for documenting open drain mode, I suspect a trivial wrapper could/should be made, it will come along with the half-duplex single wire UART feature request - #441