L'assembly è a basso livello perchè utilizza 'vocaboli' lontani da quelli usati dell'uomo non perchè richiede di sonoscere l'architettura sottostante.
Guarda che le due cose vanno di pari passo.
Perché le istruzioni dell'assembly sono "lontane da quelle usate dall'uomo"? Perché sono di fatto gli opcode della macchina.
E se sono le istruzioni della macchina, è evidente che conoscerle implica anche conoscere (almeno in una certa misura) l'architettura della macchina. Perciò l'assembly è di basso livello in quanto programmare in assembly richiede una conoscenza della macchina.
"Alto" e "basso" livello non sono concetti separati in tenuta stagna, per cui un linguaggio sarebbe o di basso o di alto livello. Esistono invece livelli "intermedi".
Il linguaggio C non è di basso livello in senso stretto perché include istruzioni "umane", ma non è neanche di alto livello in senso stretto perché permette l'accesso ai registri, cosa che un linguaggio realmente di alto livello non dovrebbe permettere.
Non c'è nessuna contraddizione in quello che ho detto e nel considerare il C un linguaggio di "medio/alto" livello, infatti il C in una certa misura permette anche di ignorare l'architettura della macchina (PC), non obbligando però a farlo (e ciò permette di utilizzarlo in ambiente embedded, come dici, nonché di applicare delle ottimizzazioni per PC tramite gli accessi a basso livello, quando si è in grado di farlo). Più puoi (o finanche devi) ignorare l'architettura, più il linguaggio è di alto livello.