The goal of these macros of course is to get better control of code by making it much more clear what you're doing, and getting rid of the mass of labels and jumps that commonly constitute assembly code. The macros let you keep full control of every speck of code laid down by the assembler, but you don't have to keep looking at the internal details. In most cases there is absolutely zero penalty in program memory taken or in execution speed. You get the performance of assembly with a lot of the benefits of higher-level languages. Code becomes quicker to develop, more bug-free, and easier to maintain, meaning that it's easier to come back later and figure out what you did when you decide to add a feature or change something.
For an example, take this short piece of code from my I²C sample code, actual code I used on a PIC12CE673 which had no SSP (even though the I²C serial EEPROM was onboard, with CLK and SDA on bits 6 & 7 of the GPIO) so the I²C had to be bit-banged:
Code: Select all
RCV_I2C_BYTE: I2C_DATA_UP CLRF EEPROM_DATA ; Init EEPROM_DATA as 0. MOVLW 8 MOVWF LOOP_COUNT1 rib1: I2C_DATA_UP I2C_CLK_UP CALL RD_I2C_BIT I2C_CLK_DN DECFSZ LOOP_COUNT1,F GOTO rib1 RETURN ; The ACK or NAK bit must be sent separately. ;----------------------------
(Even there, you can see the use of macros, as for example I2C_DATA_UP is more clear than BSF GPIO,6 but assembles exactly the same thing.) The loop is to gather the 8 bits of data to read for one byte. If we do a FOR...NEXT loop with the macros, we get:
Code: Select all
RCV_I2C_BYTE: I2C_DATA_UP CLRF EEPROM_DATA ; Init EEPROM_DATA as 0. FOR LOOP_COUNT1, 8, DOWN_TO, 0 I2C_DATA_UP I2C_CLK_UP CALL RD_I2C_BIT I2C_CLK_DN NEXT LOOP_COUNT1 RETURN ; The ACK or NAK bit must be sent separately. ;----------------------------
Looking at the .hex file output, you would have no way of telling which source code it came from, because the assembled code is exactly the same for the two versions. In BASIC you would probably have the FOR...NEXT count up instead of down, but here it's more efficient to count down and end at zero since it allows branching on the Z flag without doing another comparison first. This way it just assembles the DECFSZ LOOP_COUNT1,F and then the GOTO like the more-manual version above.
Here's an example of using an "IF...END_IF" (and as you can see, it's also in part of a larger CASE statement). This is part of a project I'm working on.
Code: Select all
CASE_OF 4 ; We were waiting for the low address byte to be shifted out to the flash memory. RAM_BANK 1 ; Is that finished yet? (Check buffer-full bit in SSP-status register.) IF_BIT SSPSTAT, BF, IS_SET ; If so, RAM_BANK 0 CLRF SSPBUF ; send out a dummy byte in order to get the first data byte to read, INCF FLASH_RD_STATE,F ; then increment the state. END_IF END_OF
In this case, the serial-port shifting for the given hardware takes time, and there's too much to do to twiddle our thumbs, so I have kind of a multitasking system where every time a task is called up, it looks to see if there's anything it can or should do at the moment, and if not, lets the computer move on to do other necessary things while it's waiting. (Don't worry about it being left in RAM bank 1 if the buffer-full bit is not set. There's a RAM_BANK 0 instruction right after the END_CASE.)
As you get a lot of decision-making, branching, looping, and exceptions to unevetful straight-line code, the macros become more and more valuable. It's nice going a hundred lines or more without a single label, and being able to more easily see the structure and the conditions under which the program carries out various operations. See the article for more info.
Brad, I don't know if I'm posting this in the best place. If there's a more appropriate place, go ahead and move it.