cscalingbit-shiftpic18

How to scale a 10 bit analog signal into an 8 bit PWM?


I'm using the PIC18F47-Q18 Curiosity HPC Development Board and the MPLAB X IDE v6.20.

The goal is to store the 10 bit value from the analog output into the 8 bit PWM. Depending on the justification of the analog alignment. ADRESL holds the first 8 bits of data (2^0 up to 2^8), and ADRESH holds the last two bits in bit 0 and bit 1 (2^9 and 2^10). By doing PWM4DCH = ADRESL;. The LED will get brighter when I turn the potentiometer. However, after a certain point the PWM cycle restarts and the LED turns off before getting brigher again. This repeats 4 times. I believe this is due to the fact that the microcontroller I am using is Fosc/4. The final goal is to make the LED reach max brightness when the potentiometer is turned clockwise.

I pasted the code below with as much detail and information as possible. Additionally, I currently use polling but I will recreate this using the interrupt flags when I learn how they work.

// CONFIG1L
#pragma config FEXTOSC = OFF
#pragma config RSTOSC = HFINTOSC_64MHZ

// CONFIG1H
#pragma config CLKOUTEN = OFF
#pragma config CSWEN = OFF
#pragma config FCMEN = OFF

// CONFIG3L
#pragma config WDTCPS = WDTCPS_31
#pragma config WDTE = OFF

#define clock OSCCON1           // define the timer control bits
#define freq OSCFRQ

#define enable_timer T2CONbits.T2ON         // define the timer control bits
#define interrupt_enable PIE4bits.TMR2IE 
#define PB1 PORTBbits.RB4
#define PB2 PORTCbits.RC5
#define light PORTAbits.RA7

void main(void) {   
    clock = 0x60;               // use High Mhz clock
    freq = 0x03;                // Use 8Mhz clock
    
    T2CLKCON = 0x01;            // Make Timer2 Fosc/4
    
    ANSELA = 0x01;              // set up port A,B,C D/A
    TRISA = 0x01;               // set up port A,B,C I/O
    ANSELB = 0x00;   
    TRISB = 0xFF;   
    ANSELC = 0x00;   
    TRISC = 0xFF;   
  
    T2PR = 0xC7;                // period of the PWM
    PWM4DCH = 0x95;             // set up duty cycle of PWM
    PWM4DCL = 0x10;             // set up duty cycle of PWM
    RA4PPS = 0x08;              // set RA4 to  be PWM output            
    T2CON = 0x20;               // set pre scale value of 1:4
    PWM4CONbits.PWM4EN = 1;     // enable the PWM
    PWM4CONbits.POL = 1;        // reverse Polarity of PWM

    while (1)
    {
        enable_timer=1;                 // enable the timer 2
        while(PIR4bits.TMR2IF==0);      // Monitor that the interrupt flag is zero
        enable_timer=0;                 // disable the timer
        PIR4bits.TMR2IF=0;              // clear the interrupt flag
       
        ADCON0 = 0x84;                  // ADC is enabled and ADFM is right justified
        ADPCH = 0x00;                   // Selected channel is RA0       
        ADCON0bits.GO =1;               // start ADC conversion
        while (ADCON0bits.GO ==0);      // using polling method to wait until GO bit is 0      
        PWM4DCH = ADRESL;               // display ADRESL results on PWM4DCH
        ADCON0bits.ADON = 0;            // Turn off ADC enable bit
    }
    return;
}

I tried to set ADRESL and ADRESH into a integer x and y before adding them and outputing to the PWM:

int x;
int y;
int z;

x = ADRESL;
y = ADRESH << 8;
z = (ADRESH << 8 | ADRESL);

PWM4DCH = z;

LED would not do anything when the potentiometer is used.

I tried to do the same thing above, but go directly to PWM4DCH:

PWM4DCH = (ADRESH<<8 | ADRESL);

Same issue as above.


Solution

  • (Since my comment works, and you asked for more information, I put this in an answer. I didn't want to do it at first, because I wasn't sure if the problem was just that "bits handling" problem, or a hardware problem specific to a microcontroler I don't know)

    This looks a lot like what would happen if you use the 8 least significant bits of input as PWM output: then you would have 4 "cycles" in the entire range.

    Which incidently seems to be exactly what you meant by PWM4DCH = ADRESL. So, you are using the 8 least significant bits.

    Your last attempt, likewise, is just trying to put 10 bits (in the correct order) into a 8 bits output. The 2 most significant bits are probably ignored. What you meant to do is rather PWM4DCH = (ADRESH<<6) | (ADRESL>>2);

    You have (if I follow correctly. Again, I am just citing parts of your post, surmising I understand what they mean), ADRESH containing ******98 and ADRESL containing 76543210, (using digits to designed bit number. So 6, means bit 6, the one that worth 2⁶). And you want PWM4DCH to be 98765432 (droping 1 and 0, since you can't be that accurate). So that means that bit 9 at position 1 of ADRESH (starting numbering at 0 on the right), needs to be at position 7. So <<6. And bit 2 at position 2 of ADRESL needs to be at position 0 of the result. So >>2

    Depending on the types of ADRESL (if it is a signed type), since >> operator fills the blanks not with 0, but with the sign bit, it may be safer to filter the bits you want:

    PWM4DCH = (ADRESH<<6) | ((ADRESL>>2)&0x3f);

    Your warning seems to indicate that those (ADRESH and ADRESL) are int. That's a bit strange (but I take that is not of your choosing), since clearly, they can hold only 8 bits (otherwise, why split that 10 bits value in 2 variables). You can get rid of the warning by casting.

    PWM4DCH = ((unsigned char)ADRESH<<6) | (((unsigned char)ADRESL>>2)&0x3f);

    As for why the >>2, well same reason as for the <<6: think of from where the bit start and where you want it to end up.

    If you had a galvanometer (or anything able to show a value in a range) that was controlled by a decimal input between 0 and 9, and wanted to use it to display the remaining charge of a battery, that is stored into a 2 digit decimal number from 00 to 99 (in %). Then, obviously, you will have to drop a digit and keep another. The least significant digit is the one you want to drop. If you kept only the last one, then, when battery is charged 39%, the indicator shows maximum value. Then when charge drops at 30%, indicator drops at minimum value. But immediately after, when battery is 29%, indicator jump back to max, then slowly decrease to the min again when battery is 20%, then jump back to max when battery is 19%... etc.

    This is exactly, in binary, what happened when you plugged the least significant bits of your input to your output.

    Of course, what you want to do, is to plug the most significant digit to your indicator. So that when charge is 50%, indicator is in the middle, then a bit lower when charge is 40%, etc, and reach minimum when charge is 0 to 9%. Of course, you loose some acuracy (the indicator is unable to show the difference between 50 and 59, between 40 and 49, between 30 and 39, etc. But at least, it decreases continuously. And anyway, there is not other choice: indicator has only 10 possible values, since its input is a single digit)

    Likewise, what you want to do in your 10 bits ADC -> 8 bits PWM, is to drop the 2 lowest bits, and use the 8 highest one as the 8 bits of the PWM. (I am pretty sure you understand already all this. But need to be clear)

    So, all that being said you have those bits in ADRSH and ADRSL

    value 7 6 5 4 3 2 1 0
    ADRSH ? ? ? ? ? ? b9 b8
    ADRSL b7 b6 b5 b4 b3 b2 b1 b0

    And you want then in PWM4DCH like this :

    value 7 6 5 4 3 2 1 0
    PWM4DCH b9 b8 b7 b6 b5 b4 b3 b2

    So you need to perform the following operations

    value 7 6 5 4 3 2 1 0
    ADRESH<<6 b9 b8 0 0 0 0 0 0
    ADRESL>>2 ? ? b7 b6 b5 b4 b3 b2
    (ADRSL>>2)&0x3f 0 0 b7 b6 b5 b4 b3 b2

    So that when you or (|) the two, you get

    value 7 6 5 4 3 2 1 0
    (ADRESH<<6) | (ADRSL>>2)&0x3f b9 b8 b7 b6 b5 b4 b3 b2