AVR's assembly - Page 1
Forum section: electronics / beginners

author: 10101
author_rank: Contributor
timestamp: June 18, 2015, 05:32:37 pm
content: I know there's some guides about this but from what i've seen they don't seem to start from the very basic, they seem to be oriented to people that already know assembly and want to program AVR's.Yes, i know assembly isn't much used anymore but it's for learning purposesSo i wanted to learn how to program some attiny's 85 and atmega's 328 in assembly, were should i start the learning process ?Sorry for any grammar error and thanks in advance, 10101

author: Thor-Arne
author_rank: Unknown
timestamp: June 18, 2015, 05:58:04 pm
content: You could try to use assembly to make some small dos/windows apps, that approach could be faster when learning than to try directly on a avr.PerhapsFASMis of interest.

author: helius
author_rank: Super Contributor
timestamp: June 18, 2015, 06:00:48 pm
content: For a complete newbie to assembly languages I would recommend starting with a simulator, not a physical chip.It is easier to see the effect of your code at each step.The basic idea behind assemblers is that each line constitutes an instruction to the assembler program that changes its state. Some lines are declarations that do not emit machine code, (those start with a period), and the rest emit one machine instruction per line. Then there are macros that invoke more complex behavior inside the assembler.A processor's programmer reference contains information like the formats of its instructions, its register scheme, and particular features you need to know such as condition codes or function linkage. You should be familiar with those before you begin writing. Then you can assemble some code and begin to single-step it inside your simulator (or on your chip with a debug interface).

author: LA7SJA
author_rank: Regular Contributor
timestamp: June 18, 2015, 07:48:03 pm
content: This is one of my favorites!http://www.avr-asm-download.de/beginner_en.pdfJohan-Fredrik

author: 10101
author_rank: Contributor
timestamp: June 18, 2015, 09:32:07 pm
content: Quote from: Thor-Arne on June 18, 2015, 05:58:04 pmMaybe ill try this first i think ....Quote from: helius on June 18, 2015, 06:00:48 pmwhich one ?Quote from: helius on June 18, 2015, 06:00:48 pmwhat ?Quote from: LA7SJA on June 18, 2015, 07:48:03 pmI've seen this before, this is what i talked when i said that this seems oriented to people that know already how to do it in another arquitectures, maybe it's just my noobness talking but i can't understand most of it

author: Thor-Arne
author_rank: Unknown
timestamp: June 18, 2015, 09:50:15 pm
content: If you go for FASM, check out the tutorial section on the forum.Grab a dos emulator and a dos reference book.Thismight be useful to.A evening or two you should be able to write simple dos applications.Also, there are several threads about AVR development.Best of all, it's all free

author: LA7SJA
author_rank: Regular Contributor
timestamp: June 18, 2015, 09:55:30 pm
content: Have you read everything from the beginning? In that case, what was it you did not understand? I've translated this text to my mother tongue and taught this for very many without prior knowledge and so far none of them ended without understanding the curriculum. If you have questions along the way please send me an email (PM) and I'll try to help you as best I can.Johan Fredrik

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 18, 2015, 11:38:10 pm
content: You can do ASM in the authentic AVR Studio, though there's basically no features in a plain assembly project (such as autocomplete, macros, functions..). You're better off doing it in a C project, with .s files or asm blocks. C preprocessor macros are a huge can of worms in their own, but worth learning at least the simpler ones.Offhand, I don't know of an MASM equivalent for AVR.Tim

author: G0HZU
author_rank: Super Contributor
timestamp: June 19, 2015, 12:19:50 am
content: I think a lot depends on what your ultimate expectations are... I like writing assembly code for AVRs but I tend to just do fairly simple stuff.A typical use for me would be to decode stuff or to communicate with or to program or to control other devices . These devices could be other ICs or they could be a complicated system that has a data IO port that I can tap into with the AVR.Also, it's easy to write efficient assembly code to control LCD or LED displays and to work with 2D or 3D lookup tables (with interpolation). Within these boundaries I think assembly code is ideal but go much beyond this and it quickly becomes difficult, especially if you want to start dealing with complex equations and floating point numbers.Most people seem happier with higher level languages because you can do all the above with a few lines of code and let the compiler do the rest. So if you want to go beyond simple tasks like the above then maybe consider learning to program in c or basic and skip over the assembly language option.

author: G0HZU
author_rank: Super Contributor
timestamp: June 19, 2015, 12:46:09 am
content: QuoteI'm out of touch with the whole AVR scene but the old Atmel STK-500 programmer/eval board was a popular platform for beginners many years ago. I'm not sure if it is still available. It programs via RS-232 so I assume there will be a USB based alternative available.However, I found the STK-500 support documentation from AVR to be fairly appalling and also their datasheets do not present information well (or even reliably) but once you get over these initial hurdles the AVR devices are really nice to work with if you want to program in assembly language. In this respect, I much prefer AVR to PIC.I've got an STK-500 here somewhere, but I started out with AVRs over 10 years ago by making my own programmer and I used simple (and free) command line tools like AVRSP and GAVRASM to program with it. So I still use this in preference to the Atmel STK-500.

author: BennVenn
author_rank: Regular Contributor
timestamp: June 19, 2015, 01:55:30 am
content: The basic Arduino boards can be programmed with asm using the native bootloader. This makes it a very good platform to start coding on, it is very hard to brick and you don't need to touch fuses. You also get an LED on the board to blink :-)

author: mfratus2001
author_rank: Contributor
timestamp: June 20, 2015, 06:51:42 am
content: I agree with BennVenn. I bought some Arduino Nano boards and they came with a Blink LED program. You just download and install the Arduino IDE on a Linux, Windows or MAC PC, and in no time you can be loading and modifying the demos provided. Yes, it is C more or less, but WHY in the world learn assembly language? Once compiled, the program becomes machine language.

author: helius
author_rank: Super Contributor
timestamp: June 20, 2015, 07:59:09 am
content: There are many things you can do with assembly that are not possible in C. Any programming technique that depends on changing the return address, for example. One of the most useful is called "threaded code", as used by the TOPS-10 FORTRAN compiler and most Forth systems. This is code in which functions are made up of a sequence of addresses, each address being a piece of code performing some function. It has the nice property that it is interpreted at runtime, and the code may be generated at runtime or modify itself, and yet it executes at nearly full speed, as the interpreter is just a jump instruction.

author: janoc
author_rank: Super Contributor
timestamp: June 20, 2015, 10:35:46 am
content: Quote from: helius on June 20, 2015, 07:59:09 amUh, there is a very good reason that these techniques are not really widely used anymore. And it isn't the use of higher level languages. What you are describing are basically jump tables and those are possible just fine in C (arrays of function pointers ...), without resorting to self modifying code.Self modifying code is a nightmare to debug and maintain. Also, good luck doing that on Harvard architecture where the code is in FLASH/ROM and the machine doesn't execute code from RAM as on many embedded systems (including the AVR the OP was asking about), or where the higher level OS protects the code pages from writing and/or doesn't allow execution from data pages (most modern OSes do that today).There are many reasons why one may need or want to use assembler today (e.g. special hw instructions not having a C equivalent - like the event out on ARM, enable/disable interrupts, etc.), but this example certainly wouldn't be it.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 20, 2015, 10:57:07 am
content: Well, you can do that in C with an array of voids, too.Tail calls don't exist (i.e., return by way of a different function), but that's usually only a special case for recursion semantics and you can call it from the head at the cost of only one additional stack level. (Of course if you're doing recursion, it adds up, but you probably shouldn't be doing semantic recursion on the AVR with what little RAM it has, anyway; iterate over an array instead. There's no recursion problem that can't be solved in this way.)Dynamic linking comes to mind as another one of those "high tech" approaches (so to speak; not that it's anything new at all). But, since you're limited to what's loaded in PROGMEM anyway, it doesn't make much difference. The best example of loading external functions would simply be a bootloader.Other reasons:Even if you don't have a single care in the world to ever write a single opcode,You should still know assembly so that you understand the compiler output. Understand that, on -O0, it simply translates statements into blocks of instructions. Nothing in common is saved between blocks, they fully stand alone. On -O3 or so, many extraneous instructions are culled, and others are grouped for time/size savings. But the translation process is still pretty straightforward, and it's easy to see what's going on. Or not. Because inevitably, you will find a problem, where what you thought you were writing is not what you get.Which leads to a deeper truth: you are not writing C code for any high level purpose. You are ultimately writing it to produce assembly (well, machine code). There are some semantics that C simply does not simplify. Example: on XMEGA, registers are specified in structs; so you get statements likePORTA.REGISTER = REGISTER_BIT_N;The compiler knows the offset to the PORTA struct, and it knows the offset of the REGISTER within it. It will not add them; instead, it puts PORTA in an index (the X, Y or Z registers) and uses memory offset indirect (e.g. LD (X+4), r16), even if you're only doing one single access (for which an immediate address would be faster). The compiler sets that up in case you'll be accessing a whole bunch of items in the struct, but it doesn't understand scale here.Speaking of pointers, understand that C functions are always in stack frames, so whenever you access local variables, you'll be pulling the address calculations and offsets through the X register (pointed at the stack) as well. That takes a lot of time to set up and use.Recent threads have mentioned shortcomings in GCC's parsing of logic operations, like sign extend. The intermediate stages of an expression might be computed implicitly in int (32 bits?) then not optimized out later (e.g., clearing a register, then not reading it).Perhaps the most valuable assembly has to do with bit and logical manipulations of data. There is simply no way to express logical rotates, shifts into and out of carry, calculation of parity, etc. You can write most of these as preposterously complex expressions, but more than likely, the compiler will take the easy way out, and translate them to a series of primitive operations it knows about!Consider taking the XOR of a partial set of bits. This might be used in a CRC or LFSR function. Computing an LFSR requires shifting the full length word (e.g., 32 bits), shifting in the new bit as the XOR from specific positions from the previous step. This expression might be formed by masking off each desired bit, shifting it into the "new bit" position, and XORing all the new bits:uint32_t lfsr = <some initial state>;... (in the iteration)lfsr = ((lfsr & 1<< LFSR_BIT0) >> LFSR_BIT0) ^ ((lfsr & 1<< LFSR_BIT1) >> LFSR_BIT1) ^ ((lfsr & 1<< LFSR_BIT2) >> LFSR_BIT2) ^ lfsr << 1;There are better ways to compute an LFSR in C, but the XOR sum can also be computedAND lfsr, LFSR_BIT_MASKBLD SREG, parityBST SREG, carrySHL lfsr, 1A logical operation on (lfsr) sets the parity flag, which is simply the XOR sum of all bits in the result. This is especially handy if LFSR_BIT_MASK fits in one byte, so you don't have to combine multiple parity bits. The shift only reads from the carry flag, so Bit Load and Bit Store operations have to be used to move it around.Tim

author: G0HZU
author_rank: Super Contributor
timestamp: June 20, 2015, 01:33:39 pm
content: I found from my limited experience, that you can do some neat tricks if you dip below assembly level and start looking at the machine codes.I've written code for some older MCU types (and seen others do the same) where you can write instructions that deliberately jump into the middle of an existing two or three byte instruction. So a 'new' and hidden instruction sequence is born starting from the remaining byte fragment(s) of the existing 3 byte instruction and if you get it right, nothing is illegal about it.It can save on program size and can also seriously confuse a simple disassembler program as it will see the above jump as illegal.I've also written code where I hide lookup tables immediately after a call to a common subroutine. So no pointer is needed to find the lookup table and the subroutine returns at the instruction after the lookup table. This saves on program size and also has the effect/benefit? of confusing a simple disassembler because it will usually get derailed the moment it tries to return from the subroutine as it will treat the lookup table bytes as valid instructions.Note that I've only done this with 680x MCUs and it was a long time ago. Also, stuff like this is effectively obsolete today but I enjoyed programming tight code like this

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 20, 2015, 05:07:46 pm
content: Inlined data/tables/const isn't actually as uncommon as it might seem. It's pretty apparent that either code or data can follow a RET, so it's not very confusing for disassemblers. (At worst, the opcodes might get out of sync when the content switches back from data to code, but it doesn't usually take long to re-align after a few short instructions, then to check backwards for the start.)In the case of AVR, it's pretty straightforward: instructions are 1-2 WORDs, and there is no segmentation or protection (program memory is either code, or const data that gets read with LPM instructions).On the more advanced RISCs (with memory management and protection), you might be better off loading const into RAM with a bootloader, and it won't matter where (usually off in some .data segment or whatever). Though those usually go fast enough that you still don't care about assembly... and the instructions tend to suck (like the dest, src1, src2 syntax of ARM ISA, which is very flexible given its number of bits..), while still being just as verbose in the effect of instructions (load, store, load again..).Tim

author: G0HZU
author_rank: Super Contributor
timestamp: June 20, 2015, 05:38:55 pm
content: Maybe I've not explained it very very well but in normal circumstances when the old Motorola 680x MCU jumps into a subroutine it (automatically)stores the return address on the stack such that it knows where to find the next instruction byte(s) after it returns from the subroutine it has just called. In my code this stored return address is actually illegal because the MCU would crash because I have parked a number of data bytes at this return address. But in the subroutine I increment a set number of corrections to this address value that is held in the stack. So when the code returns from the subroutine it returns to a legit address that has hopped over the lookup table.This way the stack pointer can be exploited as a pointer to the lookup table and once this is exploited I manipulate this value such that the MCU returns to a different return address than it originally stored in the stack.I've not found a disassembler that can cope with this easily. But that isn't the main aim with code like this. If the subroutine gets called lots of times (each with a unique lookup table) then lots of bytes can be saved in the ROM because there's no need to code up a unique pointer to each and every lookup table. The MCU does it for you for free because it stores this address on the stack as part of the subroutine process.Without giving away too many old tricks you can also put fake data in the lookup table that masquerades as code and a regular disassembler will treat it as code and you can therefore create a fake jump in the code (or fake some other sequence of code) to divert/confuse a disassembler or any beginner trying to read the disassembled code.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: June 20, 2015, 07:42:26 pm
content: Oh, so your code looks like:(...other code...)CALL do_funny_stufftabletop:.dw table0, table1, table2, ..., tableNtablebottom:.dw returnaddr(...code continues...)and you elsewhere havedo_funny_stuff:MOV count, tablebottom - tabletoploop:LD A, [SP++] ; = POP A!(and do whatever with the table entries)DEC countJNZ loopRETSo when it's done iterating through the table (using SP as a hardware auto-increment/decrement reference!), it reaches the bottom of the table (assuming POP reads in the correct direction, anyway), just in place for RET to jump back somewhere useful (the return address)?Obvious downside is, you can't be using interrupts during this time, or your table gets corrupted as stack!Maybe I don't have the scheme right, but using SP as an indexed array pointer is a neat and handy one -- at least if you don't need interrupts, and you're starved for registers (which you would be, on something like a 6800/6502).Tim

author: G0HZU
author_rank: Super Contributor
timestamp: June 20, 2015, 08:53:13 pm
content: If done correctly it can be very robust. I don't claim any originality for this method as I learned the basic idea whilst reverse engineering code in car ECUs many years ago. In those days the MCU had very limited ROM space and once the ECU manufacturer wanted to revise the (usually full) ROM image for next year's model they had to fight to create every free byte they could in ROM. Usually they wanted to add extra lookup tables to improve drivability for example.Also, they developed more efficient code every year to navigate their various 2D and 3D lookup tables (to achieve the same ROM saving goals) so I learned an awful lot from studying code like this. I also did my share of PC/dongle hacking many years ago so I learned a lot of sneaky tricks with assembly language.In my experience, the people that knock assembly programming are usually c programmers who have little time for assembly language and maybe limited experience of it for that reason. I suppose they have a strong case (especially in 2015 with so many cheap and powerful MCUs to choose from) but there are advantages to learning assembly language when it comes to debugging and reverse engineering and also when trying to get the most from limited resources in a chosen MCU

author: mfratus2001
author_rank: Contributor
timestamp: June 21, 2015, 07:32:19 am
content: I guess the main reason I would not mess with assembler or machine language now is that I don't need to. Lots of memory, fast processors, all the things we lacked in the '70s. But I cut my teeth on machine language, coding by hand from the processor instruction tables. You should at least do the standard 'Hello world' exercise once to understand how it works. But I don't need any stealthy code.

author: G0HZU
author_rank: Super Contributor
timestamp: June 21, 2015, 02:02:45 pm
content: The other issue with assembly language is that one typically has to learn numerous instruction sets and become very familiar with the inner workings of each chosen MCU. This can become a real chore. I've got experience of quite a few assembly languages and MCU types now and it isn't a lot of fun trying to learn new ones on top of this. So this can impose a mental barrier against migration to newer and more powerful MCU types. With c this is much less of an issue

author: BennVenn
author_rank: Regular Contributor
timestamp: June 21, 2015, 08:55:57 pm
content: Quote from: mfratus2001 on June 20, 2015, 06:51:42 amThink you missed the point on that one,The Arduino is shipped with the STK500 bootloader protocol which means you can plug it in to a usb port and use AVRDUDE to write hex files to it. This allows you to write ASSEMBLY in AVR studio and send it straight to the board. No ISP programmer required.Why in the world learn assembly language? It IS the language the micro understands natively. Some may say asm isn't machine code but it quite simply IS. The instructions you write are simply converted into an OPCode which the micro will execute word for word. This gives you 100% control of loop timing etc...Instruction sets are more or less identical. The mnemonics vary ever so slightly, not enough to prevent migration to a new MCU. There are only a handful of commands that get used when writing assembly with many variants to these instructions. Learning the basic set is not hard.Like others have posted, there are tricks you can do with ASM that can't be done with C. There is a thread on how Basic (Microsoft, C64, TRS-80 etc) was an abomination to programming. I think the structure of basic is much closer to assembly than C. Assembly is just logical.

author: G0HZU
author_rank: Super Contributor
timestamp: June 22, 2015, 12:21:05 am
content: QuoteIt doesn't 'prevent' migration but it's still a chore to learn a new instruction set and then work within the limits of the chosen MCU in terms of available registers, features and instructions. But that's just my opinion.In my case the most extreme case of 'learning' a new instruction set was for a MCU chip from a car ECU that turned out to have a unique and undocumented instruction set and no datasheet. That took some doing on my part. Probably the most impressive bit of reverse engineering I've ever done. It was an unknown 42pin chip with internal masked ROM. If I'd known in advance that the instruction/opcode set was unique I don't think I would have started the project but it didn't take 'that' long to reverse engineer it and get the ROM out of it and start to work out more and more instructions/opcodes and its various port features and functions.

author: BennVenn
author_rank: Regular Contributor
timestamp: June 22, 2015, 01:24:08 am
content: How did you get access to the mask ROM? Did it have a JTAG/ISP interface accessible? Modern ECU's safeguard their ROM with all kinds of creative schemes, older ones typically use external ROM

author: G0HZU
author_rank: Super Contributor
timestamp: June 22, 2015, 02:13:17 am
content: I basically made lots of assumptions about the chip and one of these was that it would be possible to remove it and reconfigure it to allow it to address external memory. It didn't take long to reverse engineer how to configure it like this. Of course, I didn't know any instructions to put in the memory once I realised it didn't support any known opcodes.But it didn't take that long to work out a few instructions from trial and error. Note that I had already hacked numerous other masked ROM ECUs before attacking this one although they all turned out to have known instruction sets and were merely custom versions of mainstream chips.Once I could write my own code for the chip it was trivial to get it to read its own ROM and dump it to NVRAM.

author: nukie
author_rank: Frequent Contributor
timestamp: June 24, 2015, 04:30:26 am
content: I use assembly everyday on memory tight ATtiny chips. I don't see it going away anytime soon.