News:

;) This forum is the property of Proton software developers

Main Menu

Analog

Started by joesaliba, Feb 06, 2021, 05:43 PM

Previous topic - Next topic

joesaliba

Hi,

I am using a 12F675 and would like to sample an analog measurement.

I would like that this be quick, so was thinking about using the Go_Done.

How I can do this please? IS it possible to use this method in an interrupt or will it slow the interrupt?

Thank you

Joseph

top204

I don't think it gets any faster or smaller that the code snippet below:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Read the ADC on a PIC12F675 with the least amount of time wasted
'
' Written by Les Johnson for the Proton8 BASIC compiler.
' https://sites.google.com/view/rosetta-tech/home
'
    Device = 12F675                             ' Tell the compiler what device is being compiled for
    Declare Xtal = 8                            ' Tell the compiler what speed the device will be operating at
'
' Setup RSout
'
    Declare Serial_Baud = 9600                  ' Set the Baud rate to 9600 for RSout
    Declare RsOut_Pin = GPIO.1                  ' Set pin GPIO.1 as the output for RSOut
    Declare RsOut_Mode = True                   ' Set the mode to true for RSout
'
' Create a variable for the demo
'
    Dim MyWord As Word
'
' Create a 16-bit SFR for the ADC meta-macro's result
'   
    Dim wADRES As ADRESL.Word                   ' Create a 16-bit SFR from ADRESL/ADRESH

'------------------------------------------------------------------
' Setup the ADC for a PIC12F675 device
' Input     : None
' Output    : None
' Notes     : Sets the ADC for 10-bit operation, and makes pin GPIO.2 an analogue input
'           : If not using a PIC12F675 device, the datasheet will need reading to make sure the peripheral's mechanism is the same
'   
Proc ADC_Setup()
    ANSELbits_ADCS0 = 1                         ' \
    ANSELbits_ADCS1 = 1                         ' |  Setup the ADC's clock for FRC
    ANSELbits_ADCS2 = 0                         ' /
  
    'ANSELbits_ANS0 = 1                         ' Set AN0 (GPIO.0) as Analogue input
    'ANSELbits_ANS1 = 1                         ' Set AN1 (GPIO.1) as Analogue input
    ANSELbits_ANS2 = 1                          ' Set AN2 (GPIO.2) as Analogue input
    'ANSELbits_ANS3 = 1                         ' Set AN3 (GPIO.3) as Analogue input

    ADCON0bits_ADFM = 1                         ' Right justify the ADC for a 10-bit result
    ADCON0bits_ADON = 1                         ' Enable the ADC
EndProc

'------------------------------------------------------------------
' Set the channel to read from with the ADC_Read() meta-macro for a PIC12F675 device
' Input     : pChan holds the channel that is going to be read
' Output    : None
' Notes     : If not using a PIC12F675 device, the datasheet will need reading to make sure the peripheral's mechanism is the same
'
$define ADC_Channel(pChan)        '
    ADCON0 = ADCON0 & %11110011   '
    ADCON0 = ADCON0 | (pChan << 2)

'------------------------------------------------------------------
' Read the ADC as fast as possible from a PIC12F675 device
' Input     : None
' Output    : pResult holds the values held in ADRESL/ADRESH
' Notes     : Implemented inline for the fastest operation
'           : But may need a delay of a few uS before the ADCON0bits_GO_DONE bit is initially set
'           : If not using a PIC12F675 device, the datasheet will need reading to make sure the peripheral's mechanism is the same
'   
$define ADC_Read(pResult)     ' 
    Set ADCON0bits_GO_DONE    '
    Btfsc ADCON0bits_GO_DONE  '
    GoTo($ - 1)               '
    pResult = wADRES

'------------------------------------------------------------------
' Read the ADC on a PIC12F675 device using the simplest and fastest method 
'
Main:   
    ADC_Setup()                     ' Setup the ADC
    ADC_Channel(2)                  ' Set the channel that the ADC_Read meta-macro will read from
    Do                              ' Create a loop
        ADC_Read(MyWord)            ' Read the ADC into MyWord
        RsOut Dec MyWord, 13        ' Transmit the ASCII ADC value to a serial terminal
        DelayMS 100                 ' A small delay so we can see what is happening
    Loop                            ' Close the loop

Making ADC_Read an inline meta-macro makes the code slightly larger if implemented more than once in the program, but makes it faster than a procedure call by a few clock cycles. Also, setting the channel to use seperately, saves time. If using a faster fOSC for the microcontroller and using a tight loop to read the ADC, you may need a small delay of a few uS before the GO_DONE bit is set in the ADC_Read meta-macro to give the ADC's internal capacitor time to discharge/charge. However, in normal program loops, this is often not required because the program around it takes up enough clock cycles.

The time it takes for the ADC to get a sample varies with the differnt settings given to it, so it will slow an interrupt slightly, but not a great deal. In fact, if the interrupt is a timed one, you can sometimes get rid of the Btfsc and Goto $ -1 because the time of each interrupt will allow the ADC to get a sample ready. You can also fire an interrupt when the ADC is finished sampling.

Amod

Do Proton Basic has ADC oversampling techniques for getting higher resolution Adc by a simple 10 bit adc like Picbasic pro have.Darrel Taylor made some library for oversampling in Picbasic pro.

keytapper

I always worried when the sampling get stuck. Shouldn't be good to add a timeout?
Perhaps the WDT should be considered an option, but it shouldn't reset the MCU.
Ignorance comes with a cost

joesaliba

@top204 Thank you for your code.

So to makes things clear, I do not need to place your A/D sampling in an interrupt, and I do not need to disable interrupts while sampling A/D to get stable readings.

Regards

Joseph

FiremanTR

When I read the two channels adc, both get the same value, I wonder how to reset the ADRESL for each reading?
The best thing I know is that I know nothing
__SOCRATES__

FiremanTR

Kesme:
Context Save
If TMR1IF =1 Then
Toggle PORTA.2
Inc X1
If PORTA.2 =0 Then
    If X1 =30 Then
        PORTA.5 =1
        ADC_Channel(0)
        ADC_Read(adc)
        PORTA.5 =0
        X1 =0
    EndIf
EndIf
If PORTA.2 =1 Then

    If X1 =15 Then
        PORTA.5 =1
        ADC_Channel(1)
        ADC_Read(adc1)
        PORTA.5 =0
    EndIf
EndIf
TMR1L =fr.LowByte
TMR1H =fr.HighByte
TMR1IF =0
EndIf


Context Restore
When I read the two channels adc, both get the same value, I wonder how to reset the ADRESL for each reading?
The best thing I know is that I know nothing
__SOCRATES__

Stephen Moss

Quote from: joesaliba on Feb 07, 2021, 12:02 PM@top204 Thank you for your code.

So to makes things clear, I do not need to place your A/D sampling in an interrupt, and I do not need to disable interrupts while sampling A/D to get stable readings.

Regards

Joseph
What I do is write to the ADC registers to set up the sampling period, enable the ADC and select the channel to read. I write to the appropriate interrupt registers to enable the appropriate ADC interrupt, there is usually one for the Go_Done or one that otherwise indicates a completed conversion.
I then the set/clear (as applicable) the Go_Done bit to start a conversion, the PIC the then gets on with other things until the ADC interrupt is trigged, then inside the interrupt handler I copy the ADC result to a variable, clear the interrupt and if I need to do it there and then start the next ADC sample, otherwise it start it elsewhere.

You can trigger a new ADC sample within an interrupt handler but generally your ADC code would be placed in a subroutine or procedure that you would call every time you need it, generally the ISR code should be short and so only used to clear the interrupt and move the ADC result into a variable. As I recall using the context save command automatically changes the GIE bit to stop other interrupts occurring while you are in the handler and context restore re-enables it but you might want to do that manually.
As far as I know you should not have to disable interrupts to get a stable reading, once the ADC conversion is started it is entirely hardware, only a power down or brown out may affect it, otherwise the ADC should complete the conversion on its own and the result should remain in the ADC result registers until another conversion updates it.

Using the Go_Done bit would only slow a non ADC interrupt if the interrupt occurred while your code was inside the ADC interrupt handler and the GIE bit disabled, but that is true of all interrupts as you do not really want one interrupt handler routine interrupted but another interrupt.

There is no way of not using the Go_Done bit, even the compilers ADC commands have to set it appropriately, I may be wrong but I believe the difference is that the command then just constantly polls the Go-Done bit to identify the end of the ADC conversion, while that would not slow down any other interrupts it does hold up the rest of the program until the conversion ends.
An alternative method is to work out how many commands can be executed during the ADC conversion time, set the Go_Done and add as many command as can be executed in the time and then constantly poll the Go_Done bit until it indicates the conversion has ended, that way you can potentially maximise the number of instructions executed during the ADC conversion time and the frequency with the you can take ADC reading without potentially affecting any other interrupt. However, this method only really works in you need to execute the same code every time an ADC sample is being taken.

Typically though using the ADC interrupt is the best method.           

Quote from: FiremanTR on Mar 04, 2021, 09:14 AMWhen I read the two channels adc, both get the same value, I wonder how to reset the ADRESL for each reading?
You would have to check the device datasheet but I think the ADC result register is likely to be read only, therefore you cannot clear it, you could only clear the variable you are copying the ADC result register value to between reads. If the register can be written then the code would simply be ADRESL = $00
However, the problem is likely to be that either...
1) You are not changing ADC channels and so reading the same channel twice
2) You are reading the result of the second channel before the conversion has finished and so re-reading the result of the first channel or
3) You input pins used for your ADC channels are set to digital not analogue (the compiler does that automatically) which may be why both channels are reading the same, you would need to set them back to Analogue inputs by writing to the appropriate register.
4) The mostly likely cause of your problem is that you are toggling PORTA.2 and then trying to read its states to determine which ADC channel to read. That is a bad idea as toggling it sets the port pin to output mode and then changes its state, then you are trying to test that state. You cannot read a pin that is set to an output, even if the complier takes care of that for you and changes the pin back to an input you are reading a pin with no input attached. Therefore it is likely you will always get the same result and thus always read the same ADC channel. Instead of toggling a PORT bit create a variable, i.e. Dim ADC_Select as bit and toggle that instead or if that does not work try...
If ADC_Select = 0 then ADC_Select = 1 Else ADC_Select = 0 which is effectively the same thing. 

If none of the above solve your problem you may need to provided more of your code for anyone to effectively identify the problem as we cannot see how you have set up the ADCs. I may be wrong but the code you have provided appear to be an interrupt handler in which it seems to me that the commands you are using for changing the ADC channel and obtaining the reading appear to be calling a subroutine or procedure external to the interrupt handler, if that is the case then it is probably not a good idea. The manual has a couple of section on a typical BASIC and Procedural program layout, if you have not read them you might find them useful.

See_Mos

or try using a different variable for each channel to make it clearer which channel results in which reading.

tumbleweed

After changing the channel you need to wait for the acquisition time before calling ADC_read

FiremanTR

@Stephen Moss
Quote1) You are not changing ADC channels and so reading the same channel twice

I am using 12F1822,
$define ADC_Channel(pChan)  ADCON0 = ADCON0 | (pChan << 2)
fixed the code above, it worked.
thank you
The best thing I know is that I know nothing
__SOCRATES__

OG

A moment, I got the idea that it will support Positron STM mcu's.   ;D

tumbleweed

$define ADC_Channel(pChan)  ADCON0 = ADCON0 | (pChan << 2)
You will need to clear the current ADCON0 channel select bits before OR ing in the new setting

top204

#13
Tumbleweed is correct. Clearing the bits of ADCON0 used for channel select will be required before the ORing of the shifted channel value, otherwise the OR will not clear bits that are already set from a previous use of the ADC_Channel meta-macro.

This will do it:

$define ADC_Channel(pChan)         '
    ADCON0 = ADCON0 & %10000011    '
    ADCON0 = ADCON0 | (pChan << 2)

The channel select bits on the PIC12F1822 are bits 2 to 6 of the ADCON0 SFR, So ANDing with %10000011 will clear them, and leave the other bits in the SFR as they are.

FiremanTR

#14
Quote from: top204 on Mar 05, 2021, 05:29 PMClearing the 5-bits of ADCON0 used for channel select would be better before the shift of the channel required, otherwise the OR will not clear bits that are already set.

This will do it:

$define ADC_Channel(pChan)         '
    ADCON0 = ADCON0 & %10000011    '
    ADCON0 = ADCON0 | (pChan << 2)

The channel select bits on the PIC12F1822 are bits 2 to 6 of the ADCON0 SFR, So ANDing with %10000011 will clear them, and leave the other bits in the SFR as they are.


perfect !!!!!
The best thing I know is that I know nothing
__SOCRATES__