extra \x0D appended when echo \0x0A

If you want bytes to be transmitted as-is across a serial connection both ways, you need to tell the system it's not to be used as a terminal device, either by detaching the tty line discipline from it or by issuing:

stty raw -echo < /dev/ttyS2

In your case, it was the onlcr attribute which is enabled by default on tty device that was causing the LF to CRLF conversion on output. You can disable all output processing including ocrnl with stty -opost, but that leaves all the input processing for data transmitted in the other direction.

You're not indicating which shell, echo or printf implementation you're using but note that the -n, -e options are not standard, and the behaviour of printf or echo when an argument contains \x is unspecified.

Several implementations of printf in their format argument expand \xHH to the byte of value 0xHH.

In those

printf "\xAA\xEE\x0A\x%02x" $i

Would expand \xAA to the byte 0xAA and for \x would either expand that to byte 0 or to \x literally or complain that hex digits are missing. So you'd end up with <0xaa><0xee><LF>\x01 in $a for instance.

Some echo implementations do also interpret \xHH sequences (some only when passed a -e option).

Here, if you wanted to be portable, you'd do:

a=$(printf '\\252\\356\\012\\%03o' "$i")
printf "$a" > /dev/ttyS2

Or:

a=$(printf '\\0252\\0356\\0012\\0%03o' "$i")
printf %b "$a" > /dev/ttyS2

(standard printf supports \ooo octal sequences in the format, and a %b format which is meant to emulate the kind of expansions some echo implementations do with or without -e, and where the octal sequences are expressed as \0ooo).

You also want to make sure the first printf doesn't output a NUL byte, as except for zsh, shells cannot store NUL bytes in their variable (don't do a=$(printf '\0 or \x00 or \u0000') for instance).

With zsh:

bytes=(0xaa 0xee 0x0a $i)
set +o multibyte
printf %s ${(#)bytes} > /dev/ttyS2

That data could be stored in a variable with either:

printf -v data %s ${(#)bytes}

or

data=${(#j[])bytes}

But more generally, if you want to manipulate data with arbitrary byte values, you may want to use a proper programming language, even if it's an interpreted one like perl or python.

Another approach would be to use tools like xxd that can convert back and forth between a binary form and a text hex dump.

printf aaee0a%02d "$i" | xxd -p -r > /dev/ttyS1

You need to disable newline conversion:

stty -F /dev/ttyS2 -onlcr

or, for strict POSIX stty:

stty -onlcr < /dev/ttyS2