Creating a PWM in Assembly - Part 7 Microcontroller Basics (PIC10F200)
Published
Note: Microchip recently made some changes and their newer IDE versions use the XC8 compiler for Assembly, whereas all sample code here is created for the MPASM compiler. However, the sample code has been ported to XC8 by user tinyelect on our Discord channel (to whom we are extremely grateful!) and the XC8 version of the code is at the bottom of this tutorial for your reference. To see the changes needed to switch from MPASM to XC8, please check out the process that he shared.
Hi there and welcome back! In tutorial 6, we made an LED blink and this time, we’re going to control the LED again but, unlike last time, we will not just blink the LED but smoothly change its brightness.
First, at the end of the last chapter, I challenged you to figure out how to make both LED’s blink on and off, opposite to each other. I’ll show you my solution as there are some tricks you should know, and maybe you already figured them out when you tried to implement this by yourself. So, here we go:
#include "p10f200.inc"
; CONFIG
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
ORG 0x0000
INIT
MOVLW ~(1<<T0CS) ;Enable GPIO2
OPTION
MOVLW ~((1 << GP2) | (1 << GP1));Set GP1 and GP2 as outputs
TRIS GPIO
LOOP
BSF GPIO, GP2 ;Set GP2
NOP ;No operation
BCF GPIO, GP1 ;Reset GP1
CALL DELAY ;Call DELAY subroutine
BCF GPIO, GP2 ;Reset GP2
NOP ;No operation
BSF GPIO, GP1 ;Set GP1
CALL DELAY ;Call DELAY subroutine
GOTO LOOP ;loop forever
DELAY ;Start DELAY subroutine here
MOVLW 5 ;Load initial value for the delay
MOVWF 10 ;Copy the value to the register 0x10
MOVWF 11 ;Copy the value to the register 0x11
MOVWF 12 ;Copy the value to the register 0x12
DELAY_LOOP ;Start delay loop
DECFSZ 10, F ;Decrement the register 0x10 and check if not zero
GOTO DELAY_LOOP ;If not then go to the DELAY_LOOP label
DECFSZ 11, F ;Else decrement the register 0x11, check if it is not 0
GOTO DELAY_LOOP ;If not then go to the DELAY_LOOP label
DECFSZ 12, F ;Else decrement the register 0x12, check if it is not 0
GOTO DELAY_LOOP ;If not then go to the DELAY_LOOP label
RETLW 0 ;Else return from the subroutine
END
As you probably guessed, to blink the LEDs in opposite you need set GP2 and clear GP1 simultaneously, and then clear GP2 and set GP1 also simultaneously. The first case is implemented in lines 11 and 13, and the second case is done in lines 15 and 17.
You may notice that between these pairs of lines I inserted an unknown instruction “NOP” in lines 12 and 16. What is that and why is it needed here?
As for the first question, the NOP instruction means “No OPeration”, so the microcontroller does nothing while performing it. Why is it needed, then? Sometimes we need to make very short delays, for 1 or 2 cycles. In this case we don’t need to invent something random for the microcontroller to do in this period, we can just use the NOP instruction.
Now, why would I have these NOPs in the code? BCF and BSF instructions belong to the so-called “read-modify-write” type. That means that to change the bit value the microprocessor reads the entire register, modifies a single bit in it (set or clears) and writes it back. It is normal to use two such sequential operations in almost all cases except one - when it is applied to the GPIO register. What is wrong with it?
In line 11 with the instruction BSF GPIO, GP2 we set the GP2 bit as 1. But, in fact the GP2 output is latched *after* this instruction is implemented, we just gave the command to set the output pin, it hasn’t actually happened yet. Let’s imagine we invoke the BCF GPIO, GP1 instruction right after the previous one. The BCF instruction reads the actual value of the pins, and that means that GP2 output may be still in the low state, not high. Thus, this instruction will discard the previous one, and GP2 will remain low. But it may also be OK, so the behavior is undefined - you don’t actually know what is going to happen. To make the behavior more predictable, we use the NOP instruction, which gives it enough time to consistently latch the output pin before reading it for the next instruction. You can read more about it in chapter 5.4 of the data sheet of the PIC10F200 microcontroller.
I also made a change in the DELAY subroutine. You may notice that I use three registers now, because we need to make 1 second delay and, with the two registers, the maximum delay is just 200ms. In line 22, we load the value 5 into all the registers as the initial value.
According to the equation I provided last time, the approximate delay will be 5 x 256 x 256 x 3 = 983040us, which is quite close to 1s.
That’s it about the challenge from Tutorial 6, let’s now switch to the current task, fading the brightness of LED1 in.
PIC10F200 Pulse Width Modulation (PWM)
As you know (and if you don’t please see this tutorial) the brightness of an LED depends on the amount of current flowing through it. The higher the current, the brighter LED shines. But it’s quite hard to change the current directly with the PIC10F200 microcontroller as it doesn’t have a DAC. So we will use another way, which is extremely widespread and used in almost all dimming systems. This is, of course, pulse width modulation (PWM). I won’t go over it in detail as there are tons of articles about PWM in the internet, including here on CircuitBread.com, so if this is the first time you’ve ever heard about it, go check it out.
Briefly, we can control brightness by means of the PWM by switching the LED on and off many times a second. The switching is so fast that our eyes can’t see the flicker, and considers the LED to be always on. With the PWM, we change the LED on/off ratio - the duty cycle - and this changes the brightness of the LED.
So, by changing the duty cycle of the PWM, we will smoothly adjust the brightness from 0 to full brightness before it drops immediately back to darkness before coming up to full brightness again. Let’s now consider the code that implements the given task:
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
CLRF limit ;Clear the PWM limit
LOOP
MOVLW 0xFF ;Set the initial value of i
MOVWF i ;as 0xFF
BSF GPIO, GP1 ;Set GP1 pin
INT_LOOP ;Beginning of the internal PWM loop
MOVF limit, W ;Copy the PWM limit value of the W register
SUBWF i, W ;Subtract the W from i
BTFSS STATUS, Z ;If the result is not 0
GOTO $ + 2 ;then go to the line 22
BCF GPIO, GP1 ;else reset the GP1 pin
CALL DELAY ;and call the DELAY subroutine
DECFSZ i, F ;Decrement the i, and check if the result is 0
GOTO INT_LOOP ;If not, return to the internal PWM loop start
DECF limit, F ;otherwise change the PWM limit
GOTO LOOP ;and return to the main loop
DELAY ;Start DELAY subroutine here
MOVLW 10 ;Load initial value for the delay
MOVWF j ;Copy the value to j
DELAY_LOOP ;Start delay loop
DECFSZ j, F ;Decrement j and check if it is not zero
GOTO DELAY_LOOP ;If not, then go to the DELAY_LOOP label
RETLW 0 ;Else return from the subroutine
END
Our programs are becoming longer and longer, but, at the same time, there are fewer new instructions each time.
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
In lines 4, 5, 6 there is new Assembly directive EQU. It sounds like the beginning of the word “EQUivalent” which, basically, is what it means. This directive defines the constant and sets its value. Actually, you could use the #define directive like in C language but EQU looks more “assemblish”. “Assembly-ish”? Either way. So we define three constants - “i”, “limit”, and “j”, and set their values as 0x10, 0x11, and 0x12 correspondingly. We do this to give the names to specific parts of the unnamed general purpose register. It’s difficult to remember what is stored in register 0x10 if the program is big, but, by this directive, we know that 0x10 is called “i” which sounds like a loop variable (which it is). The same with “j”. The “limit” register will consist of the PWM limit value, the target value at which point the pulse will end.
Please note that the name given by the EQU directive should be written in the first column like a label.
Ideally, now lines from 1 to 10 should be clear to you.
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
CLRF limit ;Clear the PWM limit
LOOP
MOVLW 0xFF ;Set the initial value of i
MOVWF i ;as 0xFF
In line 11 there is new instruction CLRF (CLeaR File register). As follows from its name, it clears the mentioned file register, which means the register value after implementing this instruction is zero. In line 11 we clear the “limit” register to start with the LED at full brightness. Give me a moment and I’ll explain how this works soon.
In lines 13 and 14 we load the value 0xFF into the “i” register.
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
CLRF limit ;Clear the PWM limit
LOOP
MOVLW 0xFF ;Set the initial value of i
MOVWF i ;as 0xFF
BSF GPIO, GP1 ;Set GP1 pin
In line 15 we set GP1 “high”.
Let’s first consider the whole operation of the PWM before jumping into the internal loop. So we preloaded the “i” register with the value 0xFF, or decimal 255. In the internal loop, we will decrement the “i” register on each iteration and compare it with the “limit” register. If “limit” is equal to the “i” then we clear the GP1 pin and keep decrementing “i” until it becomes 0. As soon as this happens we exit the internal loop, preload the “i” register with 0xFF and set the GP1 pin again.
Clearing a pin is a "0" and sets the voltage low while setting a pin is a "1" and sets the voltage high.
Thus, the smaller the value in the “limit” register is, the longer the LED will stay on during the period, and the brighter the LED will be. That’s why the value “0” of the “limit” corresponds to the maximum brightness and 255 corresponds to completely off.
Also, we decrement the value of “limit” each iteration of the main loop to make the brightness consistently higher. When “limit” becomes 0, on the next decrement it will become 255, which means that after it is fully bright, it will immediately start again from darkness. So let’s figure out how this is done in our program.
As mentioned in Tutorial 6, in an 8-bit microcontroller, 255 + 1 = 0 and 0 - 1 = 255.
Unfortunately, in Assembly language you can’t simply write “if (i == limit)”, there is no instruction for it, so we need to perform the sequence of operations to implement this simple comparison.
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
CLRF limit ;Clear the PWM limit
LOOP
MOVLW 0xFF ;Set the initial value of i
MOVWF i ;as 0xFF
BSF GPIO, GP1 ;Set GP1 pin
INT_LOOP ;Beginning of the internal PWM loop
MOVF limit, W ;Copy the PWM limit value of the W register
Line 16 is the start of the internal loop, in which we will decrement the “i” value from 255 to 0.
In line 17 there is another new instruction MOVF (MOVe File register). Don’t mix this up with MOVWF, they both move things and the “W” register is involved but they’re very different. The MOVF instruction moves the file register mentioned as operand 1 to the W register if operand 2 is W or back to the same file register if operand 2 is F. So by writing MOVF limit, W we copy the value of the “limit” register to the W register. We have to do this because we can’t perform arithmetic operations between two file registers, only between one file register and W register.
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
CLRF limit ;Clear the PWM limit
LOOP
MOVLW 0xFF ;Set the initial value of i
MOVWF i ;as 0xFF
BSF GPIO, GP1 ;Set GP1 pin
INT_LOOP ;Beginning of the internal PWM loop
MOVF limit, W ;Copy the PWM limit value of the W register
SUBWF i, W ;Subtract the W from i
Line 18 consists of another new instruction SUBWF (SUBtract W from File register). This should be easier to understand from its name. It subtracts the value in the W register from the of the file register mentioned as operand 1 and saves the result into the file register or W register depending on the operand 2. So, by invoking SUBWF i, W we subtract the W register from “i” (and we remember that it is equal to the “limit” because of the previous line) and save the result back to the W register. Actually we are not interested in the result itself, we just need to know if it is 0 (which means that “i” = “limit”). That’s why we store the result in the auxiliary register W. The SUBWF instruction also affects certain bits of the STATUS register (C and Z in particular). But now we are interested just in the Z bit. As I mentioned previously, it is set to 1 when the result of the previous operation was zero, otherwise it is 0.
In the status register, C is the "Carry bit" and Z is the "Zero bit".
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
CLRF limit ;Clear the PWM limit
LOOP
MOVLW 0xFF ;Set the initial value of i
MOVWF i ;as 0xFF
BSF GPIO, GP1 ;Set GP1 pin
INT_LOOP ;Beginning of the internal PWM loop
MOVF limit, W ;Copy the PWM limit value of the W register
SUBWF i, W ;Subtract the W from i
BTFSS STATUS, Z ;If the result is not 0
In line 19 we find another new instruction BTFSS (Bit Test in File register, Skip if Set). It tests the bit given as operand 2 in the file register given as the operand 1, and if the bit is set, the next line is skipped. So this is another conditional branch instruction, in addition to DECFSZ we already know.
By calling BTFSS STATUS, Z we check if the Z bit of the STATUS register is set (basically if “i” = “limit”). And if it is, we skip line 20 and go to line 21. Otherwise, it runs line 20.
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
CLRF limit ;Clear the PWM limit
LOOP
MOVLW 0xFF ;Set the initial value of i
MOVWF i ;as 0xFF
BSF GPIO, GP1 ;Set GP1 pin
INT_LOOP ;Beginning of the internal PWM loop
MOVF limit, W ;Copy the PWM limit value of the W register
SUBWF i, W ;Subtract the W from i
BTFSS STATUS, Z ;If the result is not 0
GOTO $ + 2 ;then go to the line 22
In line 20 we see a familiar instruction (finally!) - GOTO. But with an unknown operand (ugh!). The $ sign means the current PC value.
Reminder: I know we keep reminding you of this, but PC is the program counter.
If you write “GOTO $” this means that you load the current address into the PC register, and this line is invoked again and again, implementing endless loop (generally considered bad). If you write “GOTO $ + 1” this will load the PC with the address of the next line.
And finally, the expression “GOTO $ + 2” will load the PC with the address of the line after the next, in our case, it is line 22.
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
CLRF limit ;Clear the PWM limit
LOOP
MOVLW 0xFF ;Set the initial value of i
MOVWF i ;as 0xFF
BSF GPIO, GP1 ;Set GP1 pin
INT_LOOP ;Beginning of the internal PWM loop
MOVF limit, W ;Copy the PWM limit value of the W register
SUBWF i, W ;Subtract the W from i
BTFSS STATUS, Z ;If the result is not 0
GOTO $ + 2 ;then go to the line 22
BCF GPIO, GP1 ;else reset the GP1 pin
CALL DELAY ;and call the DELAY subroutine
DECFSZ i, F ;Decrement the i, and check if the result is 0
In line 21 (and remember that it is implemented only when “i” = “limit”) we turn off the LED by the well-known instruction BCF.
In line 22 we call the DELAY subroutine. We need the delay to slow down changing the brightness. If we don’t use it, it will change very fast. On the other hand, the delay shouldn’t be very big, otherwise you will notice the LED flicker.
In line 23 we use the operator DECFSZ to decrement the “i” value and check if it is now 0. If it isn’t, then line 24 is implemented.
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
CLRF limit ;Clear the PWM limit
LOOP
MOVLW 0xFF ;Set the initial value of i
MOVWF i ;as 0xFF
BSF GPIO, GP1 ;Set GP1 pin
INT_LOOP ;Beginning of the internal PWM loop
MOVF limit, W ;Copy the PWM limit value of the W register
SUBWF i, W ;Subtract the W from i
BTFSS STATUS, Z ;If the result is not 0
GOTO $ + 2 ;then go to the line 22
BCF GPIO, GP1 ;else reset the GP1 pin
CALL DELAY ;and call the DELAY subroutine
DECFSZ i, F ;Decrement the i, and check if the result is 0
GOTO INT_LOOP ;If not, return to the internal PWM loop start
DECF limit, F ;otherwise change the PWM limit
GOTO LOOP ;and return to the main loop
At line 24 the PC returns to the INT_LOOP label with the GOTO instruction. If the value of “i” is 0, then line 24 is skipped.
If “i” is not 0, line 25 is implemented where we see another new instruction DECF (DECrement File register). This one is similar to DECFSZ but is simpler - it doesn’t test the result, it just decrements the value of the file register.
In line 26, the value of the “limit” register is decremented to increase the brightness in the next iteration. Please note that line 25 doesn’t belong to the internal loop, it is outside it, and belongs to the main loop.
Line 26 loads the PC with the value under the LOOP label with the GOTO instruction, and this line ends both the main loop and the main program.
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10 ;define 0x10 register as the PWM loop variable
limit EQU 11 ;define 0x11 register as the PWM limit
j EQU 12 ;define 0x12 register as the delay variable
ORG 0x0000
INIT
MOVLW ~(1 << GP1) ;set GP1 as an output
TRIS GPIO
CLRF limit ;Clear the PWM limit
LOOP
MOVLW 0xFF ;Set the initial value of i
MOVWF i ;as 0xFF
BSF GPIO, GP1 ;Set GP1 pin
INT_LOOP ;Beginning of the internal PWM loop
MOVF limit, W ;Copy the PWM limit value of the W register
SUBWF i, W ;Subtract the W from i
BTFSS STATUS, Z ;If the result is not 0
GOTO $ + 2 ;then go to the line 22
BCF GPIO, GP1 ;else reset the GP1 pin
CALL DELAY ;and call the DELAY subroutine
DECFSZ i, F ;Decrement the i, and check if the result is 0
GOTO INT_LOOP ;If not, return to the internal PWM loop start
DECF limit, F ;otherwise change the PWM limit
GOTO LOOP ;and return to the main loop
DELAY ;Start DELAY subroutine here
MOVLW 10 ;Load initial value for the delay
MOVWF j ;Copy the value to j
DELAY_LOOP ;Start delay loop
DECFSZ j, F ;Decrement j and check if it is not zero
GOTO DELAY_LOOP ;If not, then go to the DELAY_LOOP label
RETLW 0 ;Else return from the subroutine
END
Lines 28 to 36 implement the DELAY subroutine. Unlike the previous program, here we use only one register to decrement - “j”. This is because we don’t need big delays. In line 29 we load the initial value 0x10 (which is 16 in decimal system) thus, total delay is 16 x 3*(DELAY LOOP) + 1*(MOVLW) + 1*(MOVWF) + 2*(GOTO) + 2*(RETLW) + 2*(CALL) = 56us.
I recommend you change the “j” value and see how this will affect program execution.
And that’s all, folks! Now let’s assemble the program and load it into the microcontroller to enjoy LED1 flashing. As I mentioned, you may try to change the delay ‘j’ to see how it will affect the flashing. Also, you can change the “i” and “limit” values to see their influence and get a more intuitive understanding of how this works.
I have a very useful device called a Logic Analyzer. If you are going to seriously work with microcontrollers this tool is absolutely a must-have. I bought the clone of the Saleae Logic logic analyzer on Aliexpress for about $5, and I still consider it as my best buy.
Anyway, I’m sharing this not to boast but to show you what’s going on with the GP1 pin when the program is executed (see figure 1).

Looks a bit boring, huh? That’s because the step is too small and the delay is relatively big.
I will change the program to fix these two flaws by removing the extra delay and reducing the “limit” by 16 for each iteration, not 1 (Take a moment to think about how this works). And after that, the results are much more obvious (see figure 2).

In figure 2 you can clearly see the changing pulse width, which means that our program works as desired.
And, as I like to do, let’s go over some statistics from this tutorial. The code size that implements this task is 21 words, so still less than 1/10th of our full memory space. We’ve learned another 6 new instructions - NOP, CLRF, MOVF, SUBWF, BTFSS, and DECF, so with the previous 10 we know already 16 instructions, almost half! Also, we’ve learned the new Assembly directive EQU which can help make the code more readable.
To practice and see if you have a good grasp of what I’ve gone over in this tutorial, I recommend you change the code so the brightness of the LED will smoothly change from the minimum to maximum brightness, and then smoothly from max back down to minimum. I think to make this task you might need the instruction INCF (INCrement File register) which is the same concept as DECF but the opposite direction. It increments the value of the given file register. As usual I will provide my solution in the beginning of the next tutorial. Good luck!
More Microcontroller Tutorials here.
Homework Solution. However don't read it until you're done with your code!
#include "p10f200.inc"
__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF
i EQU 10
limit EQU 11
j EQU 12
dir EQU 13
ORG 0x0000
INIT
MOVLW ~(1 << GP1)
TRIS GPIO
CLRF limit
CLRF dir
LOOP
MOVLW 0xFF
MOVWF i
BSF GPIO, GP1
INT_LOOP
MOVF limit, W
SUBWF i, W
BTFSS STATUS, Z
GOTO $ + 2
BCF GPIO, GP1
CALL DELAY
DECFSZ i, F
GOTO INT_LOOP
BTFSS dir, 0
GOTO DEC_BRIGHTNESS
DECFSZ limit, F
GOTO LOOP
GOTO TOGGLE_DIR
DEC_BRIGHTNESS
INCF limit, F
MOVLW 0xFF
SUBWF limit, W
BTFSS STATUS, Z
GOTO LOOP
TOGGLE_DIR
BTFSS dir, 0
GOTO SET_DIR
BCF dir, 0
GOTO LOOP
SET_DIR
BSF dir, 0
GOTO LOOP
DELAY
MOVLW 10
MOVWF j
DELAY_LOOP
DECFSZ j, F
GOTO DELAY_LOOP
RETLW 0
END
The comments and explanations are at the beginning of the next tutorial.
Get the latest tools and tutorials, fresh from the toaster.