Dec 17

PIC18F2550 Assembly Tutorial 3

In this tutorial we’ll continue with the serial communication between 2 microcontrollers. You should always configure one PIC as master while all the others are to be configured as slaves. For more information check this SPI explanation.

SPI Master-Slave Configuration

SPI Master-Slave Configuration

Check the datasheet to see which pins are SDO (output), SDI (input) and SCK (SPI Clock). Once you have connected the PICs there are a couple of registers that need to be set.

This is for the master:

MOVLW	0x00	; Datasample middle, SCK act-idle.
MOVLW	0x32	; SSP enabled, Idle at high, Fosc/64

As you can see we use the internal timing components for the SPI clock, however you can opt to use TMR2 as well. Don’t forget to enable TMR2 and set its T2CON register if you do this.

This is for the slave:

MOVLW	0x00	; Datasample middle, SCK act-idle.
MOVLW	0x35	; SSP enabled, Idle at high, SPI SLAVEMODE

Make sure you don’t make a mistake here, if you have your CLK idle at high, see to it that the transition is configured correctly else you’ll miss the first pulse. For example, when the CLK is idle at high, its first pulse will be from high to low. When you configure the slave that it should do something when the CLK goes from low to high you will have missed that first pulse resulting in missed or incorrect data.

Now you have configured both master and slave you can send any data you wish. Let’s take a look on how to transmit data from master to slave:

   goto	Main                    ; Infinite loop
   bcf		PIR1,ADIF	; Clear AD converter flag
   MOVFF	ADRESH,SSPBUF	; move ADRESH to send buffer
   bcf		PIR1,SSPIF      ; Clear the flag
   MOVFF	SSPBUF,PORTB    ; Move received data to PORTB
inter				; using single interrupt priority
   BTFSC	PIR1,SSPIF	; if set-> transmission finished
   CALL	SPIread
   BTFSC	PIR1,ADIF	; wait until AD finished -> send
   CALL	SPIsend

In this example I’m sending values sampled by the AD converter to the slave. The code is pretty self explanatory. The ADC will generate an interrupt when it’s finished with converting and the SPI will generate one when it’s done transmitting the data that was in SSPBUF, which is the send buffer.

The slave has even simpler code:

   goto main
   bcf 	  PIR1,SSPIF
   movff  SSPBUF,PORTA
   CALL   SPIread			

The slave will generate an interrupt when the data byte is received, you just have to move the data in the buffer to another register such as PORTA if you’d like to display the data with LEDs for example.

Dec 15

PIC18F2550 Assembly Tutorial 2

This time we’ll take a closer look at using the flash memory and grabbing data from the program memory. This is used for a wide variety of things such as sine lookup tables. In this example we’ll display some letters (for example your name) on a seven segment display attached to PORTB. I’ve used a Kingbright one if you’d like to do the same, check out the datasheet for the SC56-11GWA.

We start by adding a software flag variable and putting the letter data in the program memory. I’ve put in “FEZ” in the following code bit:

  intFlag equ 0x29
  org 0x820
  DATA 0x71, 0x79, 0x5B

The org directive will tell the compiler to put whatever is coming next in the program memory starting at the address given, 0x820 in this case. The DATA command says which data should be put there. Make sure your other org parameters are correct else you will overwrite other bits of data (the compiler should warn you about this). If you have no idea how 0x71 corresponds to the letter F, I suggest you check out the datasheet of the 7segment display and the following part:

  MOVLW 	0x00 		; Value used to initialize data direction
  MOVWF 	TRISB 		; Set RB as outputs for 7-segment
  ;	RB0 segment 0				0
  ;	RB1 segment 1			5		1    
  ;	...				    	6
  ;	RB6 segment 6			4		2
  ;					      	3   	  7

We configure PORTB as output (do the same for PORTA and C because you should never have floating inputs!). The numbers correspond to the pin of PORTB I connected.

We’d like to be able to see the letters on the display so we need to add some delay, unlike last time where we use software and waste cpu cycles we’re using Timer 0. Time to configure its registers!

  movlw  0x20  ; Disable peripheral - enable TRM0 interrupts
  movwf  INTCON
  MOVLW  0x87   ; 0b10000111;   Presscaler 1:256    
  MOVLW 0x80 ; 0b10000000

A prescaler of 1:256 should be enough, this will make TRM0 overflow about once a second. When it overflows an overflow flag is set and an interrupt is generated, we’ll take a look at that in a minute. The EECON1 is associated with flash & EEPROM memory, take a look at the PIC’s datasheet to see how it’s configurated.

  BTFSS INTCON,TMR0IF  ; Check if TMR0 has overflowed, if not, just return
  BCF INTCON, TMR0IF   ; Mandatory clearing of the TMR0 overflow flag
  BSF  intFlag, 0      ; Set our own software flag

Every time TMR0 overflows it’ll generate an interrupt, but other things can generate one too so we need to check if it was actually the timer who caused it. You HAVE to clear the flag yourself, it isn’t done by the hardware once it is set.

Now we have our registers set and our interrupt routine finished, let’s take a look at the main program:

  CLRF TBLPTRU       ; Set the inital Tabel Pointer values
  MOVLW  0x08
  MOVLW  0x1F

Since we put our data at 0x820 we want the table pointer to point one memory address before the one we want, it’ll become clear why that is with the following part of the code:

  BTFSS intFlag, 0        ; Check if we set the software flag in the interrupt
  GOTO	Main
  BCF  intFlag, 0         ; Here too, we need to clear it if it was set
  INCF  TBLPTRL           ; increment the pointer by one more address
  TBLRD*+                 ; Read the data which TBLPTR is pointing to & increment TBLPTR
  MOVF TABLAT, w          ; Move the readout to W
  MOVLW 0x25              
  CPFSEQ TBLPTRL          ; Have we read out the 3rd letter? Jump back to the first
  GOTO Main
  MOVLW 0x1F
  MOVWF TBLPTRL           ; Set the initial value again
  GOTO Main

So what happens here? We first check to see if we have our software flag high to know TMR0 has overflowed, next we clear it, then we increment the TBLPTR once more so it’s at 0x820. TBLRD*+ will read the data the TBLPTR is pointing at, it’ll also increment the TBLPTRL’s value by one (for some reason it incremented it by one for me, although it probably should increment by 2, not sure why that’s happening). The data is read to TABLAT (table latch), which can then be moved to the working register and moved to PORTB (you can do this in one command by using MOVFF). Once we’ve had all 3 letters, we have to put TBLPTRL back to its initial value so we can start allover.

Now connect your 7-segment display to PORTB and check if it’s working! Also a quick note, I’m using 18F2550 PICs but you can just as easily use a 2553.

If you’re satisfied with the result, continue to tutorial 3.

Dec 11

PIC18F2550 Assembly Tutorial 1

Welcome to the first of a series of assembly tutorials for the Microchip PIC18F2550 (datasheet) or 2550 for short. I have been using this at university for about two years now, both programming in C and ASM. You might think “why would anyone ever write in assembly when you’ve got C, which looks a lot easier to write a program with”. The idea with writing code in assembly is full control. You directly control every operation the processor will execute which, if you’re an experienced programmer, will result in faster program execution and less overhead.

But enough of the jibber jabber, let’s get started. We’ll start simple and gradually increase complexity, ending with serial communication between 2 microcontrollers.

I’m assuming you are familiar with Microchip’s IDE called MPLab so I won’t be explaining how it works, it’s fairly straightforward anyway. The big difference between C and ASM compilation is that you do not use a linker file when compiling your ASM project (give it a try, include the linker, you’ll notice MPLab throws an error).

First of all, I’m using a bootloaded PIC so some vectors (such as the interrupt en reset vectors) have an “offset”. Let’s take a look at the following bit of code:

	title "SPI MASTER"
	list p=18F2550, r=hex, n=0
	#include ;	
	org	0x800		; Reset Vector
	goto	Start
	org	0x808		; Interrupt Vector  HIGH priority
	goto	inter			
	org	0x818		; Interrupt Vector  LOW priority
	goto 	inter
	org	0x820			

This is the very beginning of the assembly program. As said before, vector mapping several things as well as giving the program a name. The actual program begins after the “Start” label. Whenever you see such a label (which can be any word you like) you can use the GOTO instruction to jump to the code directly after the label.

Initializing the PIC’s control registers takes a long time but it’s essential for the correct behavior of the processor. DO NOT FORGET THIS or rest assured you will spend a long time trying to figure out why your code doesn’t work. So, we start with configuring the IO pins of PORT A through C:

	CLRF 	PORTA 	; Initialize PORTA by clearing output data latches		
	MOVLW 	0x00 	; Set PORTA pins as output
	MOVWF 	TRISA 	; Value used to initialize data direction 
	CLRF 	PORTB 	; Initialize PORTB by clearing output data latches
	MOVLW 	0x00 	; Set PORTB as output
	MOVWF 	TRISB	; Value used to initialize data direction	
	CLRF 	PORTC 	; Initialize PORTC by clearing output data latches
	MOVLW 	0x00	; Value used to initialize data direction

We start by clearing anything that might be on the data latches of the ports, then we move a certain value to the WREG (working register, or its shorthand version: w). There’s an easy way to remember what defines input or output. You’ve got 1’s for Input (1 looks like an I) and 0 for output (0 looks like an O). Finally we move the value from the working register to the Tri-state register of each port. Note that it is recommended to configure any floating IO pin as output.

Next up we make sure to disable interrupts while we configure some more registers. BCF means “Bit Clear File” and BSF is the exact opposite, it sets a bit.

	bcf	INTCON,GIE	; Turn Off global interrupt
	bcf	UCON,3		; to be sure to disable USB module
	bsf	UCFG,3		; disable internal USB transceiver
	MOVLW	0x00
	MOVWF	ADCON0		; AD convertor OFF
	movlw	0x00		; No priority on interrupts
	movwf	RCON	

For this simple tutorial we do not have to set a lot of registers, let’s just make sure the AD convertor is turned off and the interrupt registers are set correctly.. After all the registers are configured we allow interrupts again.

Once this is done, we move on to the next important routine, which is the interrupt. In this example we’re not using interrupt priorities so there is just one label called “inter”.

inter			     ; using single interrupt priority
	BTFSS INTCON,TMR0IF  ; Check for TMR0 overflow

Let’s take a look at what we have here. We use the “inter” label to know the following code will be executed when an interrupt happens. We’re not actually doing anything in this example besides checking if Timer 0 has overflowed (we haven’t really configured timer 0, this is just to show you how interrupts work). When jumping into the interrupt routine, the interrupt enable bit will be cleared by the hardware, the “RETFIE” command will set the “GIE” bit again and return to wherever the program was when the interrupt happened.
I also prefer to put the interrupt routine at the end of the file, hence why there is a (mandatory) “END” at the last line of code.

Now that that’s out of our way we can create our first really simple program. We’ll start by making a simple upcounter using PORTB. If you have 8 resistors and LEDs, connect them to RB0 through 7 now. As the timer would go really, really quickly we’ll have to add a delay routine so you can actually see the counting. Start by defining 3 counter variables at the top of the file:

  count1 equ  0x30 ; Add these directly above org 0x800
  count2 equ  0x31  
  count3 equ  0x32

Then we come to the actual program, this code is located directly above “inter”:

  BSF PORTB, 0	; Set one bit so we can shift it around
  RRCF	PORTB	; Move the bit one spot to the right
  SETF	count1	; Set count1 to 0xFF
  SETF	count2
  MOVLW	0x05	; Move 0x05 to the WREG
  MOVWF	count3  ; Move the 0x05 in the WREG to count3
  DECFSZ count1	; Decrement count1, skip the next instruction if count1 = 0x00
  GOTO	 Delay
  DECFSZ count2
  GOTO	 Delay
  DECFSZ count3
  GOTO	 Delay    
  GOTO	 Main

First of all we set RB0 to 1 so we can shift it around. Next up we do an actual shift with “RRCF”, we then set the counter values and continue into the Delay routine. The logic here is the following:

1. Decrement count1
2. If count1 is not equal to 0, go to 1.
3. Decrement count2
4. If count2 is not equal to 0, go to 3.
5. Decrement count3
6. If count3 is not equal to 0, go to 5.
7. Go back to Main

I explicitly set the counter values each loop to make sure they have the correct values I want. If you would prefer an upcounter simply remove the Init part and replace “RRCF” with “INCF”. For a downcounter use “DECF”.

That concludes this tutorial, we’ll cover some more advanced things in the next one. If you have any questions or remarks feel free to post them in the comments.

Continue with the second tutorial.