Aber der Ausdruck wird/sollte doch vom Compiler als Konstante gesehen werden und auch so gehändelt werden. Wird aber offensichtlich nicht.
Doch, wird er, aber auch diese Konstante muss ja erst vom Compiler berechnet werden. Und diese Berechnung geschieht natürlich nach den C-Regeln, damit das gleiche dabei rauskommt, wie wenn die Berechnung erst zur Laufzeit passieren würde. Im Gegensatz zur Berechnung zur Laufzeit kann der Compiler hier aber direkt sehen, dass es zu einem Überlauf kommt, und dich warnen.


Des im Ausdruck mit uint16_t gerechnet wird, ist ebenfalls klar.
Ne, ist nicht klar, und auch falsch. Der Teil in der Klammer wird in int gerechnet (und da passiert auch gleich der Überlauf). Das (bereits falsche) Zwischenergebnis wird dann nach long promotet, und der Rest in long weiter gerechnet (außer natürlich dein F_CPU ist ungewöhnlich klein).


Die Wirkung von 2UL * habe ich zwar verstanden, die Hintergründe aber nicht wirklich.
Und auch in deinem Fall hier würde ein UL (oder auch nur L) hinter einer der Zahlen in der Klammer weiterhelfen. Dadurch, dass du einen der Operanden in einen größeren Typ zwingst, erzwingst du, dass die Rechnung in der Klammer insgesamt in einem größeren Typ stattfindet.