Read Working Effectively with Legacy Code. http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052
Even if it's your project, and not a mystery to you, it's still a kind of legacy. It helps to step back from the code a moment and think about what you're going to do.
Recognize TDD doesn't mean literally "all tests before all code." Having a bunch of code with no test doesn't utterly break the TDD model. It makes things a little more difficult, but it can still be TDD even with zero unit tests available at the current moment.
Start designing with testing in mind. This is actually harder than it appears. But you can do test-driven design and still not quite write all the tests. It requires some experience to design a testable API.
Write one test for one thing that's under development.
Get that one test to work.
Iterate steps 3, 4 and 5 for every deliverable you've got. You're doing TDD for new development only. Legacy remains as it was.
Sometimes, you're rewriting or revising or expanding legacy code.
Also, when you're doing maintenance and bug-fixing, you'll do a slightly different thing.
For legacy code that needs to be touched, try to write a test. Sometimes this is hard because the code is not designed for testability. In which case, you need to refactor the legacy code so you can test it in the first place.
Now that the code you're about to change is structured so it can be tested. You can now (a) run tests on the legacy, (b) revise the tests, (c) revise the code, (d) run tests on the revised version.
You don't try to test everything. Just the absolute minimum to keep moving forward with deliverables.
When you're diagnosing a problem with legacy code, you can also use tests to help with the diagnostics.
Rewrite the bug report into a test case.
If necessary, refactor the likely culprit modules for testing. They may not be designed for unit testing, making it difficult to use TDD.
After refactoring, you have a unit test that demonstrates the bug. Run it. It should fail. Now you're doing TDD. You have a test and code which fails the test.
In the maintenance and diagnostic cases, you're often forced to refactor legacy code. This is sometimes hard because the legacy code may not be designed with unit testing in mind.
The goal is to isolate functionality through "Dependency Injection" (Delegation or Strategy design patterns) and Mock Objects.
To achieve dependency injection, you will often decompose legacy code into smaller methods or classes which do less, and and knit together during the main program initialization. Your unit test will knit them together for testing, your main program will knit them together for production work. The important thing is that you often need to decompose.
Some programmers object to this because a testable API often "exposes too much" or requires "too many lines of code from the client" or some other whining. Those were not measures of quality. Testing has more value than code golf. A few extra lines of initialization in the production application won't kill anybody. A few extra lines of initialization to aid testing may save mountains of diagnostic and maintenance effort in the long run.
Building mocks -- in some languages -- is an art form. There are libraries and frameworks for this. Languages with static type declarations (Java, C#, C++, etc.) require sophisticated mocks. Languages with duck typing (Python, Ruby, etc.) don't require fancy mock libraries.