Pic18f4455 adc problem

Discussion in 'Embedded Systems and Microcontrollers' started by micro_noob, Aug 6, 2012.

  1. micro_noob

    Thread Starter New Member

    Aug 6, 2012
    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.

    Code ( (Unknown Language)):
    1. #include<P18F4455.INC>
    2. ;203008 - pickit number
    5.     CONFIG  WDT = OFF
    6.     CONFIG  PBADEN = OFF
    7.     CONFIG  MCLRE = ON
    8.     CONFIG  DEBUG = ON
    9.     CONFIG  ICPRT = ON
    10.     CONFIG  LVP = OFF
    11.     CONFIG  PWRT = ON
    13.     ORG     0x000
    14.     GOTO    MAIN
    16. MAIN
    17. ; configure clock  
    18.     MOVLW   b'01110010'     ; set internal oscillator to 8MHz
    19.     MOVWF   OSCCON     
    20. ; configure serial communication
    21.     MOVLW   b'10000000'     ; to access Tx and Rx pins
    22.     MOVWF   SPEN
    23.     MOVLW   b'11111111'     ; use alternate function
    24.     MOVWF   TRISC
    25.     MOVLW   b'00100100'     ; transmit enabled; high speed baud rate
    26.     MOVWF   TXSTA
    27.     MOVLW   b'10010000'     ; serial port enabled; receiver enabled
    28.     MOVWF   RCSTA
    29.     CLRF    BAUDCON
    30.     MOVLW   d'25'           ; for 19.2 kbps baud rate
    31.     MOVWF   SPBRG
    32. ; configure ADC
    33.     MOVLW   b'00000001'     ; use AN0 for analog input, ADC enabled
    34.     MOVWF   ADCON0
    35.     MOVLW   b'00001110'     ; Vdd=5, Vss=GND, only AN0 is analog
    36.     MOVWF   ADCON1
    37.     MOVLW   b'00010001'     ; left-justified, 4 TAD, Fosc/8
    38.     MOVWF   ADCON2
    40. MAIN_LOOP
    41. ; pseudocode:
    42. ; wait 10us acq time
    43. ; begin conversion
    44. ; wait until completed (polling GO/DONE bit)
    45. ; begin transmission
    46. ; repeat
    48.     BSF     ADCON0,1    ; begin conversion
    49. WAIT_ADC
    50.     BTFSC   ADCON0,1
    51.     GOTO    WAIT_ADC    ; wait til finished
    53. ; begin transmission
    54. TRANSMIT1
    55.     BTFSS   PIR1,TXIF
    56.     GOTO    TRANSMIT1
    57.     MOVLW   ADRESH
    58.     MOVWF   TXREG
    59.     CLRF    TXREG
    61. TRANSMIT2
    62.     BTFSS   PIR1,TXIF
    63.     GOTO    TRANSMIT2
    64.     MOVLW   ADRESL
    65.     MOVWF   TXREG
    67.     GOTO    MAIN_LOOP
    69. 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!
  2. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    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.
  3. JohnInTX


    Jun 26, 2012
    The ADC returns binary values that have to be converted to ASCII to make printable characters.
  4. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    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.
  5. micro_noob

    Thread Starter New Member

    Aug 6, 2012
    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! :)
  6. micro_noob

    Thread Starter New Member

    Aug 6, 2012
    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! :)
  7. micro_noob

    Thread Starter New Member

    Aug 6, 2012
    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
  8. JohnInTX


    Jun 26, 2012
    Code ( (Unknown Language)):
    2. MOVLW    ADRESH    
    3. MOVWF    TXREG
    5. should be:
    6. MOVF    ADRESH,W    
    7. MOVWF    TXREG
    9. you could also use:
    10. movff   ADRESH,TXREG
    You are loading W with the address of ADRESx, not its content.
    The assembler should be griping about it:
    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..

    Code ( (Unknown Language)):
    1. cblock (tempram) ; put this somewhere accessable
    2. HtoAtemp:    1    ; temp storage for hex to ascii
    3. sbtemp:        1    ; temp storage for sendbyte
    4.     endc
    6.         ;******************* SEND CHARACTER IN W TO SIO  *********************
    8. TXw:
    9.         ; fill in the blanks: copy W to TXREG, put in a buffer etc.
    11.         return
    13.         ;******************** CONVERT HEX TO ASCII  ***************************
    14.           ;CONVERT LS Nibble OF W TO ASCII IN W
    15. hxtoas:
    16.         andlw        0fh                ; strip to lsn
    17.         movwf        HtoAtemp        ; save in temp reg
    19.         movlw        0ah                ; if 0ah+, add 37h, else add 30h
    20.         subwf        HtoAtemp,W        ; C set if 0ah+
    21.         movlw        37h                ; add 37h if so
    22.         btfss STATUS,C                ; skip on carry
    23.         movlw        30h                ; else add 30h
    24.         addwf        HtoAtemp,W        ; return result in W
    25.         return
    27.         ;**************  SEND HEX BYTE TO SIO  ***********************************        
    28.         ; SPsendbyte: sends hex byte in W to sio as 2 ASCII chars with leading space
    29.         ; sendbyte: sends hex byte in W to sio as 2 ASCII chars
    31. SPsendbyte:
    32.         movwf        sbtemp            ; save in temp
    33.         movlw        ' '                ; send leading space
    34.         call        TXw
    35.         goto        _sbw            ; continue with data
    37. sendbyte:
    38.         movwf        sbtemp            ; save in temp
    40. _sbw:
    41.         swapf        sbtemp,W        ; get msn to wreg lsn
    42.         call        hxtoas
    43.         call        TXw
    45.         movf        sbtemp,W        ;get lsn
    46.         call        hxtoas
    47.         call        TXw
    48.         return
    50.           ;*********************  CRLF  *************************************
    51. crlf:
    52.         movlw   0dh
    53.         call    TXw
    54.         movlw   0ah
    55.         call    TXw
    56.         return
    Last edited: Aug 6, 2012
  9. micro_noob

    Thread Starter New Member

    Aug 6, 2012
    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. :)
  10. JohnInTX


    Jun 26, 2012
    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!
  11. micro_noob

    Thread Starter New Member

    Aug 6, 2012
    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.
  12. JohnInTX


    Jun 26, 2012
    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.
  13. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    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: Aug 7, 2012
  14. micro_noob

    Thread Starter New Member

    Aug 6, 2012
    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.
  15. osx-addict


    Feb 9, 2012
    Read this post.. there are others out there too like this one..
  16. JohnInTX


    Jun 26, 2012
    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.
  17. micro_noob

    Thread Starter New Member

    Aug 6, 2012
    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!
  18. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    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.
  19. micro_noob

    Thread Starter New Member

    Aug 6, 2012
    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!
  20. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    Nah, we just gave you a few nudges. YOU did all the grunt work.


    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).