traditionally arm allows for 8 or 9 bits and a shift, so 12000 = 0x2EE0, as Stephen pointed out you can then do this:
mov r0, #0x2E00
orr r0, #0x00E0
Another way, with a quick shortcut is:
ldr r0,=0x2EE0
which means the assembler will find a place to put that value then do a pc relative load or you can do that yourself:
ldr r0,mynumber
...
mynumber: .word 12000
The newer extensions to the instruction set allow for more bits in the immediate. I normally use the
ldr r0,=0x2EE0
solution, and make sure I have unconditional branches, basically pools for the assembler to place variables. The compilers normally do the same thing, if they cant fit the immediate in a single instruction they tend to use a load pc relative rather than multiple immediate instructions.