Pic18f4455 adc problem

Thread Starter

micro_noob

Joined Aug 6, 2012
10
Good day!

Hi, I'm a newbie in microcontroller interfacing and I would like to seek help.

Our project was to make an RC-meter using PIC18F4455 and right now, I'm working on the resistance-meter of our project.
The simple schematic I used is attached here.
The code with comments is shown below.

Rich (BB code):
#include<P18F4455.INC>
;203008 - pickit number

	CONFIG	FOSC = INTOSC_EC
	CONFIG	WDT = OFF
	CONFIG	PBADEN = OFF
	CONFIG	MCLRE = ON
	CONFIG	DEBUG = ON
	CONFIG	ICPRT = ON
	CONFIG	LVP = OFF
	CONFIG	PWRT = ON

	ORG 	0x000
	GOTO	MAIN

MAIN
; configure clock	
	MOVLW	b'01110010'		; set internal oscillator to 8MHz
	MOVWF	OSCCON		
; configure serial communication
	MOVLW	b'10000000'		; to access Tx and Rx pins
	MOVWF	SPEN
	MOVLW	b'11111111'		; use alternate function
	MOVWF	TRISC
	MOVLW	b'00100100'		; transmit enabled; high speed baud rate
	MOVWF	TXSTA
	MOVLW	b'10010000'		; serial port enabled; receiver enabled
	MOVWF	RCSTA
	CLRF	BAUDCON
	MOVLW	d'25'			; for 19.2 kbps baud rate
	MOVWF	SPBRG
; configure ADC
	MOVLW	b'00000001'		; use AN0 for analog input, ADC enabled
	MOVWF	ADCON0
	MOVLW	b'00001110'		; Vdd=5, Vss=GND, only AN0 is analog
	MOVWF	ADCON1
	MOVLW	b'00010001'		; left-justified, 4 TAD, Fosc/8
	MOVWF	ADCON2
	
MAIN_LOOP
; pseudocode:
; wait 10us acq time
; begin conversion
; wait until completed (polling GO/DONE bit)
; begin transmission
; repeat

	BSF		ADCON0,1	; begin conversion
WAIT_ADC
	BTFSC	ADCON0,1
	GOTO	WAIT_ADC	; wait til finished
	
; begin transmission
TRANSMIT1
	BTFSS	PIR1,TXIF
	GOTO	TRANSMIT1
	MOVLW	ADRESH
	MOVWF	TXREG
	CLRF	TXREG

TRANSMIT2
	BTFSS	PIR1,TXIF
	GOTO	TRANSMIT2
	MOVLW	ADRESL
	MOVWF	TXREG

	GOTO	MAIN_LOOP

END
Currently I am testing if I have set up the ADC configuration correctly, so I am sending the contents of ADRESH and ADRESL registers to the hyperteminal of my PC using serial communication (RS-232).

I am using VDD (5V) for Vref+ and VSS (GND) for Vref-. I also initially set the resistors to be equal so I can expect a VDD/2 (2.5V) on pin AN0. I based my calculations for setting the acquisition time and conversion clock from the ADC tutorial of Gooligum electronics.

However, the problem is that no matter what values of resistors I used, I am sending the same characters to the hyperterminal.

Does anyone know why? Is it because I configured the ADC wrong? Am I missing something in the hardware? Or I have wrong computations?

Any help, comments, suggestions, links, references would really help!
Thank you so much!
 

Attachments

ErnieM

Joined Apr 24, 2011
8,377
The A2D code looks OK... do check ADCON2.ADFM, I believe you want a 1 not a zero to make it right justified. That puts the 10 digits into the 2 registers in the most useful way. (Typically left justified is only used when you wish an 8 bit conversion.)

Well the first thing to do in cases like this is to ignore the A2D data and try to send some known data to debug the transmit code.

In Transmit1, why are you clearing TXREG? That will send another zero byte, did you intend that? You don't wait for it to complete if you want that. Other then that it's been a while since I used the USART and I'm not back up to speed on it right now.
 

ErnieM

Joined Apr 24, 2011
8,377
The ADC returns binary values that have to be converted to ASCII to make printable characters.
Yeah I noticed that too figuring he'd have to fix that one day, but his symptom right now is it is always the SAME characters. While they should appear almost random without the conversion they should change, should they not?

There are many methods for converting pure binary to an ASCII string, but perhaps the simplest thing for a new coder to try is add each nibble (4 bit piece of the 10 bit number) to a binary 01000000. This results in ASCII characters @, A, B, up to letter O.

Of course, the nibbles have to be shifted and/or AND'ed with 00001111 to get just the lowest 4 bits.

micro_noob: By the way, do you have a debugger? PICkit or such? You should be able to step thru the code that way (without needed the serial interface) and see the registers and variables directly.
 

Thread Starter

micro_noob

Joined Aug 6, 2012
10
The A2D code looks OK... do check ADCON2.ADFM, I believe you want a 1 not a zero to make it right justified. That puts the 10 digits into the 2 registers in the most useful way. (Typically left justified is only used when you wish an 8 bit conversion.)

Well the first thing to do in cases like this is to ignore the A2D data and try to send some known data to debug the transmit code.

In Transmit1, why are you clearing TXREG? That will send another zero byte, did you intend that? You don't wait for it to complete if you want that. Other then that it's been a while since I used the USART and I'm not back up to speed on it right now.
Thank you so much for your reply! :)

So I changed the ADCON2 setting to right-justified, but still I am getting the same characters as shown in the attached image. >_<

Also, I tried sending known data (letters) and the serial communication worked perfectly. So I assume the problem lies on the ADC operation alone? :|

About clearing TXREG, I did that because I was assuming it was necessary before sending another data (i.e, storing new data in TXREG). I removed that line and no significant change happened.

I still can't figure out why the output of the conversion is set constant. Is it about the calculation or something? Should I change my clock? Tacq? Tconv? Thanks for your help! :)
 

Attachments

Thread Starter

micro_noob

Joined Aug 6, 2012
10
The ADC returns binary values that have to be converted to ASCII to make printable characters.
Yeah, I know. I was initially planning to look at the displayed characters and look at its binary equivalent, before subjecting to analysis. However, the ADC output is always the same for all cases - no matter what the values of the resistors are.

Thank you so much for your reply! :)
 

Thread Starter

micro_noob

Joined Aug 6, 2012
10
micro_noob: By the way, do you have a debugger? PICkit or such? You should be able to step thru the code that way (without needed the serial interface) and see the registers and variables directly.
I have a PicKit2 but I don't know how to do that. :|

Thanks for the tips in converting data to displayable characters. I'll be needing that after I solved this ADC mystery. LOL
 

JohnInTX

Joined Jun 26, 2012
4,787
Rich (BB code):
MOVLW    ADRESH     
MOVWF    TXREG 

should be:
MOVF    ADRESH,W     
MOVWF    TXREG

you could also use:
movff   ADRESH,TXREG
You are loading W with the address of ADRESx, not its content.
The assembler should be griping about it:
202 Argument out of range. Least significant bits used.

Argument did not fit in the allocated space. For example, literals must be 8 bits.
Is it?

Here's something lifted from an old midrange project but should work for the 18F.. You can use it to see your data in hex anyway..

Rich (BB code):
cblock (tempram) ; put this somewhere accessable
HtoAtemp:    1    ; temp storage for hex to ascii
sbtemp:        1    ; temp storage for sendbyte
    endc

        ;******************* SEND CHARACTER IN W TO SIO  *********************

TXw:
        ; fill in the blanks: copy W to TXREG, put in a buffer etc.

        return

        ;******************** CONVERT HEX TO ASCII  ***************************
          ;CONVERT LS Nibble OF W TO ASCII IN W
hxtoas:
        andlw        0fh                ; strip to lsn
        movwf        HtoAtemp        ; save in temp reg
        
        movlw        0ah                ; if 0ah+, add 37h, else add 30h
        subwf        HtoAtemp,W        ; C set if 0ah+
        movlw        37h                ; add 37h if so
        btfss STATUS,C                ; skip on carry 
        movlw        30h                ; else add 30h
        addwf        HtoAtemp,W        ; return result in W
        return
        
        ;**************  SEND HEX BYTE TO SIO  ***********************************        
        ; SPsendbyte: sends hex byte in W to sio as 2 ASCII chars with leading space
        ; sendbyte: sends hex byte in W to sio as 2 ASCII chars

SPsendbyte:
        movwf        sbtemp            ; save in temp
        movlw        ' '                ; send leading space
        call        TXw
        goto        _sbw            ; continue with data

sendbyte:
        movwf        sbtemp            ; save in temp

_sbw:
        swapf        sbtemp,W        ; get msn to wreg lsn
        call        hxtoas
        call        TXw
        
        movf        sbtemp,W        ;get lsn
        call        hxtoas
        call        TXw
        return

          ;*********************  CRLF  *************************************
crlf:
        movlw   0dh
        call    TXw
        movlw   0ah
        call    TXw
        return
 
Last edited:

Thread Starter

micro_noob

Joined Aug 6, 2012
10
Wow! Thank you so much! I am finally getting different characters now! I admit, I did overlook that move instruction right there. Heh.

I will try to study the code you just shared and see if it can be used on my project. :)
 

JohnInTX

Joined Jun 26, 2012
4,787
What about the assembler warning? You should get into the habit of not ignoring those, ever.

To amplify what ErnieM said, stop what you are doing and get the PicKit set up for debugging. It won't take much and it will pay back big time.

In MPLAB 8.x, just select PicKit2 as the debugger, select DEBUG on the toolbar and build away. Use View->Watch and add some variables to watch. Step and watch things not work. Then fix them.

In MPLAB X the procedure is similar but I'm too lazy to look it up.

Have fun!
 

Thread Starter

micro_noob

Joined Aug 6, 2012
10
Thank you JohnInTX for your tips!

So I inserted your hex-to-ascii conveter in my code, and I found out that I am finally getting the correct values. Yey!

So my new problem is to actually compute for the unknown resistance of the circuit. My pseudocode is to:
1. Get the decimal equivalent of the ADC output: Vout=(Vin/1024)*analogvalue
2. Solve for Runk = Rk[(Vout/Vin)-1] where Runk is the unknown resistance and Rk is the known resistance.

I can convert the analog value to its decimal equivalent. My problem starts with the division and multiplication operation. Any idea how to do this in PIC? Is there an easier way to solve for Runk? Thank you in advance.
 

JohnInTX

Joined Jun 26, 2012
4,787
AN544 has lots of fixed point routines for the 17C family but you can port it easily enough to the 18F or use it as a guide to write your own.
AN575 has floating point, also for the 17C.
I'm not aware of gp math appnotes for the 18. If you find a reference, post it.

You should be able to do what you need in 16bit fixed point. When you do it, multiply first then divide to keep precision. If you have multiple variables, repeat in this order. Remember that the product and dividend sizes are twice the integer size. One of the challenges with fixed point arithmetic is keeping the intermediate results within the size of the variables.

You can make things easier if you rearrange terms and pre-compute any constants at your desk to minimize calculations.

Try to keep divisors even powers of 2 by scaling your computations. This makes the division a simple shift. Sometimes, you can scale such that the intermediate result is 256x the final result. In this case, the division can be done by simple byte-extraction.

Finally, while its good to develop a gp math library for .asm work (to understand the process if nothing else), using C is way easier if you are starting out.
 

ErnieM

Joined Apr 24, 2011
8,377
...My problem starts with the division and multiplication operation. Any idea how to do this in PIC? Is there an easier way to solve for Runk? Thank you in advance.
Now you know why I use C. :D

Those routines John suggested will work.

You can also download free C compilers from Microchip for this device. It will make larger code then your assembler but it will be easier to write that code.

One thing to keep in mind is YOU pick where the binary point (like the decimal point) is, or what units you are using. I will often use a simple integer to hold fractional numbers, I just scale them up so they are again integers.

Example: I want to read how many micro amps my unit takes. I have a current to voltage converter with a gain of 41,000 volts per amp. (This and all number that follow are decimal.) I read 402 from my A2D and convert to milliamps.

Now 402 / 41,000 = 0.0098 which is the correct answer, but will give me zero as an integer. So instead I scale my units. The 402 gets multiplied by 10, and I divide by just 41. Now my answer is:

(402 * 10) / 41 = 98

I have 0.1 mA resolution inside an integer (my integers here are 32 bit quantities so they can get pretty big).

Something to keep in mind as you code this.
 
Last edited:

Thread Starter

micro_noob

Joined Aug 6, 2012
10
Now you know why I use C. :D

Those routines John suggested will work.

You can also download free C compilers from Microchip for this device. It will make larger code then your assembler but it will be easier to write that code.
As much as I would like to use C, I can't. The course is restricted to using assembly language. :((

Anyways, thanks for your tips! :)

Actually my problem is how to actually divide and multiply 10bitsx10bits. As far as I know, there is no division operation in PIC18F4455 using assembly language. :|

Do you guys have any code, tutorial, e-book that can actually help me? Thank you in advance.
 

JohnInTX

Joined Jun 26, 2012
4,787
In the databook sec 8.3 gives a 16x16 multiply. Right justify your 10 bit values and have at it. The result will be right justified in the 32 bit product (and will fit into 20 bits max).

You are correct. No divide instructions. You have to roll your own but take a look at the apnotes and the other references. Its not too bad.

Again, if you can scale one of the multiplication factors to make the division a power of 2, its just shifts to the right to do the divide. Faster too.
 

Thread Starter

micro_noob

Joined Aug 6, 2012
10
In the databook sec 8.3 gives a 16x16 multiply. Right justify your 10 bit values and have at it. The result will be right justified in the 32 bit product (and will fit into 20 bits max).

You are correct. No divide instructions. You have to roll your own but take a look at the apnotes and the other references. Its not too bad.

Again, if you can scale one of the multiplication factors to make the division a power of 2, its just shifts to the right to do the divide. Faster too.
Nice! The multiply routine in Sec 8.3 worked perfectly in my code! Very simple algorithm, and easy to understand, too! Thank you so much! :D

Now, I am only left with the division routine for 16x16 (actually, 10x10 only). Do you happen to know a simple algorithm for this, just like the one you just showed me? Thanks!
 

ErnieM

Joined Apr 24, 2011
8,377
Once I had trouble fitting a division routine into 1 or 2K of code, so I took a peek of how a C compiler was doing an unsigned 16 bit / 16 bit divide.

I never did dig thru to see how it was working but I could see it was working.

So see if this is of any help. It's written for a 12F core so you will have to update it for the 18F core.
 

Attachments

Thread Starter

micro_noob

Joined Aug 6, 2012
10
Once I had trouble fitting a division routine into 1 or 2K of code, so I took a peek of how a C compiler was doing an unsigned 16 bit / 16 bit divide.

I never did dig thru to see how it was working but I could see it was working.

So see if this is of any help. It's written for a 12F core so you will have to update it for the 18F core.
I tested your code and it works fine; however, I changed my computation of Runk a little bit which made me need a 24bit-by-16bit division routine instead. I went to piclist.com and found a wide range of sample codes. I tweaked them a little bit since most of them are not suitable for the 18f series. But still, thank you for your help.

Right now, my code can solve for the unknown resistance! (Thanks to you guys!) I am sending the result of my equation to the hyperterminal through RS-232 using a hex-to-ascii converter and display. It's actually tiring to compare the computed value with the actual value, well because I'm working with hex values.

..which brings me to my last issue! I have to display the resistance value in the right format (decimal). I know the double dabble algorithm to convert binary to BCD. However, it becomes cumbersome since I need to work on a 3-byte data. Do you happen to know a simpler algorithm for this? Thanks!
 

ErnieM

Joined Apr 24, 2011
8,377
Right now, my code can solve for the unknown resistance! (Thanks to you guys!)
Nah, we just gave you a few nudges. YOU did all the grunt work.

Congratulations!

..which brings me to my last issue! I have to display the resistance value in the right format (decimal). I know the double dabble algorithm to convert binary to BCD. However, it becomes cumbersome since I need to work on a 3-byte data. Do you happen to know a simpler algorithm for this? Thanks!
BCD? So your display is a 7-segment or similar? Got that division routine handy? The display has several digits: say for example 4 digits displaying 0 to 9999.

If you divide your result by 1,000 you get the number of 1000's in it, or the BCD of the top digit.

Multiply the number of 1000's by 1,000, subtract that from the initial result, divide by 100 and you get the number of 100's... and so on.

No big tricks, just some dog work grinding.

If you actually needed AASCII instead it's really very similar. Just take each BCD terms and add 0x30 to each digit (that's the ASCII for zero).
 
Top