Mircochip PIC PIC12F683 test setup.
Fig. 1 Mircochip PIC PIC12F683 test setup.

Introduction PIC12F683 Programming Circuits Tutorial

by Lewis Loflin

In Pulse Width Modulation Power Control for Microcontrollers we explored the use of a PIC12F683 to control power output to a 12-volt LED lamp. Here we look at the PIC12F683 itself and some programming hints.

YouTube video: My Home Built PIC Dev. Board

For a video demo of this page see PIC12F683 Microcontroller and Circuits on YouTube.

This test circuit (Fig. 1) employs a Mircochip PIC PIC12F683 using its analog-to-digital converter (ADC) taking the analog value of a potentiometer converting the 0-5V reading into a 10-bit value from 0-1023. That 10-bit value is sent to pulse-width-modulation module at pin GP2.

That value sets the duty cycle (on time vs. period) of a 250Hz square wave generated by an internal timer. Period = 1/F = 1/250 = 4mSec.

K150 PIC Programmer
K150 PIC Programmer

Downloads - there is no warranty use at your own risk:
Driver for K150 PIC programmer: pl1303dtiver.exe
Programming software for K150: K150.rar

I have included a pre-made template with the basic setup and configuration already done with a couple of delay routines pre-written. (We will go over those.) I'll be programming in assembly for a number of reasons in particular cost and flexibility. Also because what few assembly examples available are just terrible.

Features and pin connections of the PIC12F683.
Fig. 2 Features and pin connections of the PIC12F683.

Fig. 2 shows just some of the features of the PIC12F683 which includes 6 programmable I/O pins, 4-channel analog-to-digital converters, and 1 each PWM and comparator modules. It has far more features than a PIC16F84A and has ADCs the PIC16F628 lacks. It has 2048 bytes of flash ram more than enough for most applications.

As a bonus its programmable internal "clock" requires no external parts just a 3-5 volt power supply. (See Fig. 1) I designed this with a 4mHz clock (default then internally divided by 4) which provides a clock cycle time of 1uSec - the 35 instructions are mostly 1 cycle except branches such as GOTO.

Register banks of the PIC12F683.
Fig. 3 Register banks of the PIC12F683.

New October 2016:

PIC12F683 registers are accessed in two banks. They control the hardware and other functions. The I/O pins are labeled GP0 - GP5. they can be programmed as shown in fig. 2 by setting and clearing bits in the associated register. The p12f683.inc file defines names based on the register/bank labels. Look at this code that will setup the internal clock in the template. Note that I'm using MPLAB v8.92 compiler which changes labels and code one types into machine code (a HEX file) programmed into the PIC.

BANKSEL OSCCON          ; Switch to Bank 1.
MOVLW   b'01100001'     ; 4MHz Clk, IntOsc, SysClk via IntOsc

Note 1: anything after ";" is a comment and ignored. Use lots of comments to make notes to yourself!

Note 2: commands such MOVLW, MOVWF, and BANKSEL are NOT case sensitive. One could use say "movlw" or "MOVlw", etc. Be consistent!

Note 3: the PIC series uses mainly 8-bit registers that can hold a number form 0-255 decimal. The compiler doesn't care what number base one uses so 255 can be d'255' or .255 for decimal; b'11111111' or 0b11111111 for binary, or 0xff or 0xFF for hexadecimal. I don't know about OCTAL - the compiler doesn't seem to care when I tried 0777.

Note 4: the include file in the template defines names to a number. Look at the bank table in figure 3 and notice "OSCON" (or oscillator control register) is in Bank 1 location Ox8F. Otherwise one would have to define the label themselves (OSCON EQU 0x8F) or simply use "MOVWF 0x8F".

Note 5: defined names from the include file ARE case sensitive! So using "oscon" etc. will cause a compiler error.

Note 6: one must be in the correct bank to change register data, bits, etc. The STATUS register (0x03 and 0x83) is shared by BOTH Bank0 and Bank1 respectively. Bit 5 (labeled RP0) is the bank select bit. Clearing the bit (0) puts one in bank 0 and setting the bit (1) puts one in bank 1:

BSF STATUS, RP0 ; selects bank 1 by setting bit 5 - Rp0 is defined as 5
; or 
BCF STATUS, RP0 ; select bank 0

BSF is "bit-set file" while BCF is "bit clear file". For more on the STATUS register see P11 in the data sheet - make sure you have that data sheet!

So to change data in the OSCON register (P19 in data sheet) I must be in bank 1. BANKSEL saves a lot of trouble because the compiler knows what's in what bank and does the work for you. So if I needed to change a bit, etc. in say TMRO (bank 0 0x01) use "BANKSEL TMRO" and forget this RP0 business.

Note 7: the W or working register. Think of the W register as a box that can hold any combination of 8 zeros and ones. I load a number from 0-255 into the box and deliver it to the correct register (ex. OSCON) and deposit it there. Most instructions the programmer must specify if the result is to be stored in the W register or a file: Ex. DECFSZ filename, 0 (or W) stores the result in W; DECFSZ filename, 1 (or F) stores the result in the file register.

So what did my three lines of code in 3uSec. do? I selected bank 0, moved a literal number in binary into the W register, then the PIC W register moved that byte to the OSCON register from the W register.

Now I'm sure some are quaking in their boots - I have to do all this stuff??? That's why I'm here to help others avoid the nightmares I went through when I taught myself PIC assembly. The basic compilers are EXPENSIVE and the MPLAB C compiler has some copyright and licensing issues. It makes no sense to use anything other than assembly for these small controllers - larger ones yes.

Once one learns assembly on one PIC it works on the others. See my section PIC16F628A in Assembly.

Test setup to demonstrate pulse width modulation.
Fig. 4 Test setup using a Microchip 12F683 to demonstrate pulse width modulation.

Programming the PIC12F683 and Others

Hardware operation and pulse-width modulation is covered in Pulse Width Modulation Power Control for Micro-controllers. As shown in figure 1 switches are connected to GP3 and GP4 programmed as digital inputs. The pins are pulled to +5V (HIGH) through two 10K resistors. When a switch is pressed the inputs go to 0V (LOW).

An opto-coupler is connected to GP2 programmed as an pulse-width modulation output. Output is "toggled" ON/OFF whenever SW1 and/or SW2 is pressed. GP1 and GP5 are also "toggled" ON/OFF respectively. That was used to control a H-bridge motor control. The opto-coupler isolates the 12-volt circuit from the 5-volt PIC.

A potentiometer is connected to GP0 programmed as an analog input using the 10-bit ADC module. This 10-bit value is read and used to set the duty cycle for the 10-bit PWM module at GP2.

Think of the PIC12F683 or any other PIC as a collection of programmable electronic modules along with the micro-controller. PIC like the ATMEGA168, etc used by Arduino are known as RISC processors - reduced instruction set computer. There are only 35 instructions in this case.

Let's dive deeper into programming. Refer to template.asm and study the specification sheet.

CBLOCK  0x20 ; Assign each reg. from Bank 0 RAM area.
ENDC        ; Conclude Bank 0 RAM assignments.

Referring to figure 3 I'm using static ram locations starting at 0x20 for variables. I always declare several in case I need them. The function "CBLOCK" saves one from declaring each variable individually using EQU or using the hexadecimal number itself.

ORG     0x000     ; processor reset vector
GOTO    setup     ; go to beginning of program
ORG     0x004     ; interrupt vector location
; isr code can go here or be located as a call subroutine elsewhere
RETRIE           ; return from interrupt

When the controller resets the address counter starts at 0x000. The label "setup" points to a flash ram location for the setup routine defining mainly hardware functions. That location is done by the compiler.

ORG 0x004 is the interrupt vector pointing to what is known as an ISR or interrupt service routine. The PIC12F683 has a number of programmable hardware interrupts. Once an interrupt occurs the PIC stops what it's doing, saves the present location-count address on the STACK, then services the ISR.

One can call another subroutine or perform the function here. One must have RETIE that pulls the original address from the STACK and allows PIC to return to what it was doing.


BANKSEL OSCCON          ; Switch to Bank 1.
MOVLW   b'01100001'     ; 4MHz Clk, IntOsc, SysClk via IntOsc

BANKSEL CMCON0          ; Switch to Bank 0.
MOVLW   b'00000111'     ; Turn off Comparator.
BANKSEL ANSEL           ; Switch to Bank 1.
CLRF    ANSEL           ; Set I/O pins to Digital.

; Define inputs & outputs.
CLRF   TRISIO ; all output
BSF	   TRISIO, GP3 ; GP3 input

BANKSEL GPIO    ; Switch to Bank 0.
CLRF    GPIO            

; This code block configures the ADC for polling, 
; Vdd reference, Frc clock and GP0 input.

BSF     TRISIO, 0 ; Set GP0 for ADC input
; ANSEL ; 0x9F P32 
MOVLW   0b01110001 ; ADC Frc clock,
IORWF   ANSEL ; and GP0 as analog 
MOVLW   0b00000001 ; Left justify,
MOVWF   ADCON0 ; Vdd Vref, An0, On
CALL    delay_1ms ; Acquisition delay

; setup PWM
; CCP1CON in bank 0 bits 4-5 are LSB of 10-bit PWM

MOVLW	b'00000111'
MOVWF	T2CON ; turn on TMR2 prescale 16 - frequency
MOVLW	d'127' ; duty cycle = TMR2 = CCPR1L:CCP1CON<5:4>
MOVLW	b'00001100'
MOVWF	CCP1CON 	; turn on PWM 
GOTO loop ; jump to main program "loop"

The above does the same thing as setup() does in Arduino and is done only once unless called. I defined the 4mHz "clock", turned off the comparator module, set all the I/O to digital. With TRISIO in bank 1 I defined GP3 and GP4 (set the corresponding bits) as inputs all other pins as outputs. I clear registers such as GPIO (bank 0) and ANSEL (analog select bank 1) to make sure they are in a known state. Failing to do that can create all kinds of problems.

Note: GP3 can only be programmed as an input!

Then I setup GP0 as an ADC input, then the PWM module is setup for 250Hz through timer 2 (T2CON) while the 10-bit value to control the duty cycle is in registers CCPR1L and CCP1CON in bank 0.

Setup is the hardest thing to do so start with my general template and learn to do one thing then go onto another.

Bank 0 PIC12F683
Fig. 5 Part of Bank 0 Microchip 12F683.

; do something
GOTO loop

The above is the very same thing the Arduino loop does in C. Let's try something simple like blink a LED connected to pin GP1 connected through a resistor to ground. To make the program go we need to store a HIGH in the GPIO register at GP1 or bit 1. We wait for while, then store a 0 in bit 1 or GP1 and wait again. At the end of the sequence "GOTO loop" brings us back to the beginning. Note the register configuration for part of bank 0.

We should be in bank 0 and if not sure use "BANKSEL GPIO". When we use commands like BSF and BCF we go DIRECTLY to the register in this case GPIO or 0x05. Note figure 5.


BSF GPIO, GP1 ; same as BSF 0x05, 1 - bit 1 HIGH LED on.
; wait
BCF GPIO, GP1 ; same as BCF 0x05, 1 - bit 1 LOW LED off
; wait  

GOTO loop

As written the ON-OFF time is 2uSec. and can only be seen on an oscilloscope. Now we come to the use of subroutines. Build a collection of small debugged routines and use them to build other larger routines through the use of CALL. We need a delay routine to slow the process down so we can see something. Back in setup there was a CALL to delay_1ms.

; Calculating a 1mSec delay. 4mHz is divided by 4 internally to
; 1,000,000. Take reciprocal divide 1mSec by 1uSec = 1000.
; GOTO uses 2 cycles, DECFSZ 1 cycle.

	; 4 mHz crystal
	movlw D'2'
	movwf count1
aa	movlw D'167'
	decfsz count2, F ; inner loop
	goto $-1 ; two cycles
	decfsz count1, F ; 
	goto aa 

What we have is a subroutine delay_1ms using what is called a software loop. We have two loops one with count1 = 2 and the other count2 = 167. (Both in decimal.) These are the static RAM locations we declared earlier with CBOCK 0x20.

What DECFSZ does is decrement the count1 or count2 and check the Z flag in the STATUS_REG register (0x03) bit 2 and if set (1) means the result of the last decrement is zero and will skip the following instruction goto $-1 and exit the loop.

With DECFSZ count, F - F means store the result in the "count" register while a W will store the result in the internal working register W. Many of instructions do this.

Each DECFSZ and GOTO takes 3uSec. 167 times for about 500uSec. total. The outer loop (count = 2) goes through the 500uSec loop twice for a delay of about 1mSec - thus the name delay_1ms. (Use names that make sense.)

	movlw d'250' 
	movwf count1
aa	call delay_1ms
	decfsz count1, F
	goto aa ; aa can be left out use "goto $-2"

Now we used a 2nd delay routine that calls delay_1ms 250 times. So now we complete our program where the LED will blink ON-OFF every half-second.


BSF GPIO, GP1 ; same as BSF 0x05, 1 - bit 1 HIGH LED on.
CALL delay_250ms
BCF GPIO, GP1 ; same as BCF 0x05, 1 - bit 1 LOW LED off
CALL delay_250ms

GOTO loop

I hope the example was helpful. To see the entire program shown in the video see analog.asm for more sample code.