Jump to content
 
  • entries
    31
  • comments
    46
  • views
    22,062

Contributors to this blog

Introduction to Assembly Language on a PIC16F18446


N9WXU

2,162 views

 Share

Quote

Real programmers can write assembly code in any language. - Larry Wall (Author of Perl)

Assembly language may no longer be the mainstream way to write code for embedded systems, however it is the best way to learn how a specific CPU works without actually building one.    Assembly language is simply the raw instruction set of a specific CPU broken into easy to remember pneumonics with a very basic syntax.  This enables you full control of everything the CPU does without any translation provided by a compiler.  Sometimes this is the only reasonable way to do something that cannot be represented by a higher level language.  Here is an example from a project I was working on today.

Today I wanted to create a 128-bit integer (16 bytes).  That means I will need to add, subtract, multiply, etc. on my new 128-bit datatype.  I was writing for a 32-bit CPU so this would require 4 32-bit values concatenated together to form the 128-bit value.  If we consider the trivial problem of adding two of these numbers together, lets consider the following imaginary code.

int128_t foo = 432123421234;
int128_t bar = 9873827438282;

int128_t sum = foo + bar;

But my 32-bit CPU does not understand int128_t so I must fake it.  How about this idea.

int32_t foo[] = {0x00112233, 0x44556677, 0x8899AABB, 0xCCDDEEFF};
int32_t bar[] = {0xFFEEDDCC, 0xBBAA9988, 0x77665544, 0x33221100};

int32_t sum[4];

sum[0] = foo[0] + bar[0];
sum[1] = foo[1] + bar[1];
sum[2] = foo[2] + bar[2];
sum[3] = foo[3] + bar[3];

But back in grade school I learned about the 10's place and how I needed to carry a 1 when the sum of the one's place exceeded 10.  It seems that it is possible that FOO[0] + BAR[0] could exceed the maximum value that can be stored in an int32_t so there will be a carry from that add.  How do I add carry into the next digit?  In C I would need to rely upon some math tricks to determine if there was a carry.  But the hardware already has a carry flag and there are instructions to use it.  We could easily incorporate some assembly language and do this function in the most efficient way possible.

So enough rambling.  Let us see some code.  First, we need to configure MPLAB to create an ASM project.

Create a project in the normal way, but when you get to select a compiler you will select MPASM.

image.png

Now you are ready to get the basic source file up and running.  Here is a template to cut/paste.

#include "p16f18446.inc"

; CONFIG1
; __config 0xFFFF
 __CONFIG _CONFIG1, _FEXTOSC_ECH & _RSTOSC_EXT1X & _CLKOUTEN_OFF & _CSWEN_ON & _FCMEN_ON
; CONFIG2
; __config 0xFFFF
 __CONFIG _CONFIG2, _MCLRE_ON & _PWRTS_OFF & _LPBOREN_OFF & _BOREN_ON & _BORV_LO & _ZCD_OFF & _PPS1WAY_ON & _STVREN_ON
; CONFIG3
; __config 0xFF9F
 __CONFIG _CONFIG3, _WDTCPS_WDTCPS_31 & _WDTE_OFF & _WDTCWS_WDTCWS_7 & _WDTCCS_SC
; CONFIG4
; __config 0xFFFF
 __CONFIG _CONFIG4, _BBSIZE_BB512 & _BBEN_OFF & _SAFEN_OFF & _WRTAPP_OFF & _WRTB_OFF & _WRTC_OFF & _WRTD_OFF & _WRTSAF_OFF & _LVP_ON
; CONFIG5
; __config 0xFFFF
 __CONFIG _CONFIG5, _CP_OFF
;
GPR_VAR        UDATA
Variable        RES     1

SHR_VAR			UDATA_SHR
Variable2		RES     1
	 
;*******************************************************************************
; Reset Vector
;*******************************************************************************

RES_VECT  CODE    0x0000            ; processor reset vector
    pagesel START		    		; the location of START could go beyond 2k
    GOTO    START                   ; go to beginning of program

ISR       CODE    0x0004           ; interrupt vector location
	; add Interrupt code here
    RETFIE

;*******************************************************************************
; MAIN PROGRAM
;*******************************************************************************

MAIN_PROG CODE                      ; let linker place main program

START
	; initialize the CPU
LOOP
	; do the work
    GOTO LOOP

    END
    

The first thing you will notice is the formatting is very different than C.  In assembly language programs the first column in your file is for a label, the second column is for instructions and the third column is for the parameters for the instructions.  In this code RES_VECT, ISR, MAIN_PROG, START and LOOP are all labels.  In fact, Variable and Variable2 are also simply labels.  The keyword CODE tells the compiler to place code at the address following the keyword.  So the RES_VECT (reset vector) is at address zero.  We informed the compiler to place the instructions pagesel and GOTO at address 0.  Now when the CPU comes out of reset it will be at the reset vector (address 0) and start executing these instructions.  Pagesel is a macro that creates a MOVLP instruction with the bits <15:11> of the address of START.  Goto is a CPU instruction for an unconditional branch that will direct the program to the address provided.  The original PIC16 had 35 instructions plus another 50 or so special keywords for the assembler.  The PIC16F1xxx family (like the PIC16F18446) raises that number to about 49 instructions.  You can find the instructions in the instruction set portion of the data sheet documented like this:

image.png

The documentation shows the syntax, the valid range of each operand, the status bits that are affected and the work performed by the instruction.  In order to make full use of this information, you need one more piece of information.  That is the Programmers Model.  Even C has a programmers model but it does not always match the underlying CPU.  In ASM programming the programmers model is even more critical.  You can also find this information in the data sheet.  In the case of the PIC16F18446 it can be found in chapter 7 labeled Memory Organization.  This chapter is required reading for any aspiring ASM programmers.

Before I wrap up we shall modify the program template above to have a real program.

START
    banksel TRISA
    clrf    TRISA
    banksel LATA
loop
    bsf LATA,2
    nop
    bcf LATA,2
    GOTO loop                          ; loop forever

	END

This program changes to the memory bank that contains TRISA and clears TRISA making all of PORT A an output.

Next is changes to the memory bank that contains the LATCH register for PORT A and enters the loop.

BSF is the pneumonic for Bit Set File and it allows us to set bit 2 of the LATA register.  NOP is for No OPeration and just lets the bit set settle.  BCF is for Bit Clear File and allows us to clear bit 2 and finally we have a branch to loop to do this all over again.  Because this is in assembly we can easily count up the instruction cycles for each instruction and determine how fast this will run.  Here is the neat thing about PIC's.  EVERY instruction that does not branch takes 1 instruction cycle (4 clock cycles) to execute.  So this loop is 5 cycles long.  We can easily add instructions if we need to produce EXACTLY a specific waveform.

I hope this has provided some basic getting started information for assembly language programming.  It can be rewarding and will definitely provide a deeper understanding on how these machines work.

Good Luck

  • Helpful 2
 Share

0 Comments


Recommended Comments

There are no comments to display.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...