В выражении "tm.hex : m1.o m2.o" та же ошибка: если объектные файлы лежат в obj, то нужно писать "tm.hex: obj/m1.o obj/m2.o"... Ты хочешь от make странного: если m1.o изначально вообще везде отсутствует, то как make догадается, что нужен именно obj/m1.o? VPATH об этом не говорит. VPATH говорит только о том, что если вдруг понадобится "m1.o", то искать его в таком-то каталоге. Это не то же самое, что сделать логический вывод, что мол tm.hex получается именно из obj/m1.o. Нет -- правила интерпретируются непосредственно как есть. Т.е. будет пытаться создать в текущем каталоге, а правила по созданию .o в текущем каталоге -- нет. В итоге vpath осмысленно применять можно только к всегда существующим исходным текстам, но не к промежуточным файлам (которые ещё где-то создать нужно).
Вообще правильно было бы написать что-то вроде того:
src = file1.c file2.c
obj/%.o: src/%.c # можно без src -- тогда нужен vpath
gcc -o$@ -c $<
tm.hex: $(src:%.c=obj/%.o)
gcc -o$@ $^