One of the first projects I ever worked on was a massive graphics project written in assembly language. I later found out that the only reason it had been done in assembly language is that the lead programmer could not figure out how to get the linker to cooperate with the compiler. The project went down in flames and it left a bad taste in my mouth, but I did learn a great deal about assembly language.
Since then my mantra has been that there are places were assembly language is useful and/or necessary, but otherwise it is better to use a high level language. Assembly language may be required for faster execution, smaller code size, or to do things that are not supported by a high level language. High level languages are better from the standpoint of clarity. However, it is not enough to just use a high level language. It is very easy to write incomprehensible code using 'C'. I have written routines that are only a dozen lines of code, but required four pages of explanation to make it clear.
Clarity is paramount. If someone else cannot understand the code, your project is doomed. Sometimes the code itself is very clear, but it still leaves the question of why do we even have this procedure? I can see what it does, but why would anyone want to do that? If the code works perfectly and no one is every going to look at it again, then I supposed you can get away without an explanation. An unlikely scenario at best.
However much code I write in high level languages, I never get too far from assembly language. Sometimes it is just a matter of using the disassembler in the debugger to understand why the code is not doing what I expect it to do. Sometimes it is an error on my part, sometimes it is a bug in the compiler. Sometimes it is a matter of counting instruction cycles to see that we are executing within the system's parameters. Sometimes it is debugging somebody else's assembly language.
Then there is the case where something needs to be written assembly language. There are two recent cases. One was writing a task switcher for an Atmel AVR. All the registers had to be saved, the stack pointer changed, and then the registers restored. Along with some higher level routines written in 'C', it worked very well.
The other was composing an elementary timing program for a PIC microcontroller. The program was simple enough. The tricky part was loading the chip into the programming fixture. It was one of these tiny six legged surface mount parts. I had to use tweezers to hold it and a magnifying glass to see the orientation marks. To spare manufacturing this ordeal, we devised a way to program this secondary microcontroller from the main processor. This required another assembly language routine to load the timing program into the PIC.
It seems that every time I have dealt with assembly language I have had to learn about a new chip with a new set of registers and a new set of instructions. Some chips use VonNeuman architecture, some use Harvard. Some have only a couple of registers, some have a whole bank. Some can be made to reprogram themselves, some require external programming. It is usually not enough to just glom on to basic features. It usually requires some in depth study of just how the chip works. Sometimes even something as elementary as loading a register behaves differently on different chips.