• Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Learning C

Pommie

Well-Known Member
Most Helpful Member
Back now from my weekend in Nimbin. Good advice from Tumbleweed, especially making previous local and static - static makes it keep it's previous value which is really important. Give me a little time and I'll setup the equivalent of your circuit and have a play. I have two different encoders - one from Jaycar ($7.95) and one from Ebay ($1) and will try them both. Last week I tried the one from Ebay and couldn't get it to work reliably. Hence why I'm keen to compare the two.

Mike.
 

KeepItSimpleStupid

Well-Known Member
Most Helpful Member
I don't think so. I only wrote one program in C. Guess what I wrote? An operating system/instruction set emulator. It was part of a class. Everyone had 4-5 people in the team. We had 2. Me and my GF. I concentrated on the instruction set emulation.

I could not spend time learning the language, so I kept with simple stuff. I stuck with case instead of switch. If you don't use anything wierd, it's very readable. Lots of weird differences like A==1 and a=1. By far, LISP is the worst language I ever saw in my life. APL was just a wierd vector calculator. COBOL, SNOBOL forget it - way to wordy.

LabVIEW is pretty wierd. Everything is visual, like a schematic. The vi (like a subroutine) executes when all the data is available. Excetions didn't exist in the version I used. Parallel programming is inherent. You need to understand error handling.

Some assembly languages I knew was PDP-8, PDP-11, RCA 1802, 6800, The 1802 instruction set doesn;t have a gosub statement, but any register can be the program counter. The first thing I wrote was gosub and return. I compiled those programs by hand.

I looked at an Intel compiler and everything was "backwards" Mov A,B actually moved the value of B to A, When you move a piano from the living room (A) to the Den (B), you move it from A to B. I lost interest quickly.

So, C is pretty close as long as you don't do any wierd stuff.

The problem I have is I pretty much need a paper manual because I don't memorize well. DEC (Digital Equipment Cort) did a really noce job organizing information. It was too much information, that you could not put it all in one place.

You had like a processor handbook which showed the evolution and capabilities. An instuction set handbook, A device handbook, diagnostics, schematics, (No service manual). The MSP430 could use similar manual organization.

A computer system generally consisted of cards: The micro 11 used the Q-bus which was ABCD or ABAB.

1. Boot card
2. CPU card
3. Serial card (console interface and more)
4. memory
5. Disk drive controller
6. Terminator

1,2 and 3 were eventually combined into one card
 

Pommie

Well-Known Member
Most Helpful Member
I've had a play with this and found that contact bounce is a real problem. With this in mind I wrote the following on a 16F18346 but it should only need the ports changing - and (probably) the oscillator setup. It test the switches every 250uS and only accepts them when they match.
See how this works with your hardware.
Code:
#include <xc.h>
#include <stdint.h>

#include "config.c"

#define LED1 LATC3
#define LED2 LATC4
#define LED3 LATC5
#define SW1 RC6
#define SW2 RC7
#define TRISLED1 TRISC3
#define TRISLED2 TRISC4
#define TRISLED3 TRISC5
#define TRISSW1 TRISC6
#define TRISSW2 TRISC7
#define LED1ON LED1=0
#define LED1OFF LED1=1
#define LED2ON LED2=0
#define LED2OFF LED2=1
#define LED3ON LED3=0
#define LED3OFF LED3=1
#define _XTAL_FREQ 32000000

int8_t readEncoder(void);
       
int8_t encoder,delayLED=0;

void main(void){
    OSCCON1 = 0x60;     //HF int osc
    OSCFRQ = 0x07;      //32MHz clock
    BORCON = 0x00;
    // WDTPS 1:65536; SWDTEN OFF;
    WDTCON = 0x16;      //WDT off
    ANSELC=0;
    TRISLED1 = 0;       //only need to set
    TRISLED2 = 0;       //output pins to zero
    TRISLED3 = 0;       //as default is input
    WPUC=0b11000000;    //Week pullups on switch inputs
    //setup timer2 to generate 1mS ticks
    //clocking at 8MHz (32/4), divide by 64 gives 125kHz, postscaler = 1 and PR2 = 124
    T2CON=0b00000011;
    TMR2ON=1;
    PR2=124;
    LED1OFF;                //LED1 off
    LED2OFF;                //LED2 off
    LED3OFF;                //LED3 off
    while(1){
        while(!TMR2IF);     //ensure a ms has passed
        TMR2IF=0;           //reset flag
        encoder=readEncoder();
        if(encoder==0){     //encoder not moved
            if(delayLED>0){ //has the (100mS) delay finished
                delayLED--; //no, so count down
            }else{
                LED1OFF;    //yes, turn off LEDs
                LED3OFF;
            }
        }else if(encoder==1){   //increment?
            LED3ON;             //turn on LED
            delayLED=100;       //keep it on for 100mS
        }else if(encoder==-1){
            LED1ON;             //as above
            delayLED=100;
        }
    }
}
                        //   0  1  2 3  4 5 6  7  8 9 10 11 12 13 14 15
const signed char table[] = {0,-1,+1,0,+1,0,0,-1,-1,0,0,+1, 0,+1,-1, 0};

int8_t readEncoder(void){
    static uint8_t previous;
    uint8_t one=0,two=0;
    if(SW1)             //preread switches
        two|=1;
    if(SW2)
        two|=2;
    do{
        __delay_us(250);    //short delay
        one=two;            //keep old value
        if(SW1)             //get new value
            two|=1;
        if(SW2)
            two|=2;
    }while(one!=two);       //repeat until bouncing stops
    previous<<=2;           //make room for new bits
    previous|=one;          //OR in new bits
    previous&=15;           //get rid of higher bits
    return(table[previous]);//return value from table
}
With the above, I've never had a wrong value.

Mike.
Edit, I'm using ICSP and so needed a chip with more pins.
 

Nigel Goodwin

Super Moderator
Most Helpful Member
I've had a play with this and found that contact bounce is a real problem.
Yes, contact bounce is a real problem with encoders, particularly cheap ones - so the Jaycar one 'might' work, but the Ebay one not.

It's also a big problem in commercial gear, with contact bounce getting worse as the product ages and wears - sometimes a squirt of WD40 sorts them out, but otherwise the encoder needs changing.

There was a classic example in the UK, from a company called Pace who made satellite receivers - they made one (back in the analogue days) that also had a surround sound amplifier inside, with an encoder as the volume control on the front.

For some bizarre reason (presumably cost?) the encoder didn't have detents, instead they were moulded in the plastic front and had a plastic 'spring' on the knob that engaged with them.

Needless to say, they didn't last long - and the cure was to replace the encoder with a 'proper' one, and cut the plastic 'spring' off the knob.
 

augustinetez

Active Member
Yes, contact bounce is a real problem with encoders, particularly cheap ones - so the Jaycar one 'might' work, but the Ebay one not.
The Jaycar encoder is the Ebay one, at 4+ times the price (and green instead of blue).

I've got a couple here somewhere and they are just as bad as any of the other cheapies.

Also have 100 PPR and 600 PPR optical ones in the cupboard, but they are being saved for a particular project.
 

Pommie

Well-Known Member
Most Helpful Member
Yes, the Jaycar one seems to be just as bad as the Ebay one. I "thought" I had it working fine without a problem about 3 years ago but obviously didn't.

I've now setup a 250uS interrupt to process the rotary encoder and that works fine.
Code:
#include <xc.h>
#include <stdint.h>

#include "config.c"

#define LED1 LATC3
#define LED2 LATC4
#define LED3 LATC5
#define SW1 RC6
#define SW2 RC7
#define TRISLED1 TRISC3
#define TRISLED2 TRISC4
#define TRISLED3 TRISC5
#define TRISSW1 TRISC6
#define TRISSW2 TRISC7
#define LED1ON LED1=0
#define LED1OFF LED1=1
#define LED2ON LED2=0
#define LED2OFF LED2=1
#define LED3ON LED3=0
#define LED3OFF LED3=1
#define _XTAL_FREQ 32000000

int8_t encoder,delayLED=0,encoderCount=0;

void main(void){
    OSCCON1 = 0x60;     //HF int osc
    OSCFRQ = 0x07;      //32MHz clock
    BORCON = 0x00;
    // WDTPS 1:65536; SWDTEN OFF;
    WDTCON = 0x16;      //WDT off
    ANSELC=0;
    TRISLED1 = 0;       //only need to set
    TRISLED2 = 0;       //output pins to zero
    TRISLED3 = 0;       //as default is input
    WPUC=0b11000000;    //Week pullups on switch inputs
    //setup timer2 to generate 250uS interrupts
    //clocking at 8MHz (32/4), divide by 64 gives 125kHz, postscaler = 1 and PR2 = 31
    T2CON=0b00000011;
    TMR2ON=1;
    PR2=31;
    LED1OFF;                //LED1 off
    LED2OFF;                //LED2 off
    LED3OFF;                //LED3 off
    TMR2IE=1;
    PEIE=1;
    GIE=1;
    while(1){
        __delay_ms(1);          //stop it running too fast
        GIE=0;                  //no interrupts
        encoder=encoderCount;   //get the count
        encoderCount=0;         //and zero the interrupt variable
        GIE=1;                  //let interrupts happen again
        if(encoder==0){         //encoder not moved
            if(delayLED>0){     //has the (100mS) delay finished
                delayLED--;     //no, so count down
            }else{
                LED1OFF;        //yes, turn off LEDs
                LED3OFF;
            }
        }else if(encoder>0){    //increment?
            LED1ON;             //turn on LED
            delayLED=100;       //keep it on for 100mS
        }else if(encoder<0){
            LED3ON;             //as above
            delayLED=100;
        }
    }
}
                        //   0  1  2 3  4 5 6  7  8 9 10 11 12 13 14 15
const signed char table[] = {0,-1,+1,0,+1,0,0,-1,-1,0,0,+1, 0,+1,-1, 0};

void __interrupt() inter(void){
    volatile static uint8_t previous;
    static uint8_t one,two;
    TMR2IF=0;           //must be timer2 or we have a huge problem
    one=two;            //copy previous value
    two=0;              //and zero current
    if(SW1)             //update current from switches
        two|=1;
    if(SW2)
        two|=2;
    if(two==one){       //if bouncing stopped
        previous<<=2;   //make room for new bits
        previous|=two;  //OR in new bits
        previous&=15;   //discard higher bits
        encoderCount+=table[previous];  //get value from table - note added so encoderCount can be more than ±1
    }
}
Mike.
Edit made previous volatile.
 
Last edited:

Pommie

Well-Known Member
Most Helpful Member
I should explain the lines without curly braces,
Code:
   if(SW1)             //update current from switches
        two|=1;
    if(SW2)
        two|=2;
Whenever there is a conditional instruction, only one instruction after it is executed depending on the condition. If you want many instructions then you surround them with curly braces {}. Not using curly braces is frowned upon in academia as it's far too easy to add an instruction after the single one and assume it's part of the conditional code. So, you can write things many different ways,
Code:
while();
while(1){
}
while(1){}
while(1)
{
}
Are all the same. Academia recommends the last version.

Mike.
 

augustinetez

Active Member
Re Mike's code post #86:-

Not sure what is supposed to happen, but first things first, perhaps you can check that my translation across to the 12F1840 is correct.
Code:
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection->INTOSC oscillator: I/O function on CLKIN pin
#pragma config WDTE = SWDTEN    // Watchdog Timer Enable->WDT controlled by the SWDTEN bit in the WDTCON register
#pragma config PWRTE = ON       // Power-up Timer Enable->PWRT enabled
#pragma config MCLRE = OFF      // MCLR Pin Function Select->MCLR/VPP pin function is INPUT
#pragma config CP = OFF         // Flash Program Memory Code Protection->Program memory code protection is disabled
#pragma config CPD = OFF        // Data Memory Code Protection->Data memory code protection is disabled
#pragma config BOREN = OFF      // Brown-out Reset Enable->Brown-out Reset disabled
#pragma config CLKOUTEN = OFF   // Clock Out Enable->CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin
#pragma config IESO = ON        // Internal/External Switchover->Internal/External Switchover mode is enabled
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable->Fail-Safe Clock Monitor is enabled

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection->Write protection off
#pragma config PLLEN = ON       // PLL Enable->4x PLL enabled
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable->Stack Overflow or Underflow will cause a Reset
#pragma config BORV = LO        // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (Vbor), low trip point selected.
#pragma config LVP = OFF        // Low-Voltage Programming Enable->High-voltage on MCLR/VPP must be used for programming

#include <xc.h>
#include <stdint.h>


#define LED1 LATA0
#define LED2 LATA1
#define LED3 LATA2
#define SW1 RA3
#define SW2 RA4
#define TRISLED1 TRISA0
#define TRISLED2 TRISA1
#define TRISLED3 TRISA2
#define TRISSW1 TRISA3
#define TRISSW2 TRISA4
#define LED1ON LED1=0
#define LED1OFF LED1=1
#define LED2ON LED2=0
#define LED2OFF LED2=1
#define LED3ON LED3=0
#define LED3OFF LED3=1
#define _XTAL_FREQ 32000000

int8_t readEncoder(void);
      
int8_t encoder,delayLED=0;

void main(void){
    OSCCON = 0x70;      // set osc to 32MHz
    BORCON = 0x00;
    // WDTPS 1:65536; SWDTEN OFF;
    WDTCON = 0x16;      //WDT off
    ANSELA=0;
    TRISLED1 = 0;       //only need to set
    TRISLED2 = 0;       //output pins to zero
    TRISLED3 = 0;       //as default is input
    OPTION_REGbits.nWPUEN = 1;
    //setup timer2 to generate 1mS ticks
    //clocking at 8MHz (32/4), divide by 64 gives 125kHz, postscaler = 1 and PR2 = 124
    T2CON=0b00000011;
    TMR2ON=1;
    PR2=124;
    LED1OFF;                //LED1 off
    LED2OFF;                //LED2 off
    LED3OFF;                //LED3 off
    while(1){
        while(!TMR2IF);     //ensure a ms has passed
        TMR2IF=0;           //reset flag
        encoder=readEncoder();
        if(encoder==0){     //encoder not moved
            if(delayLED>0){ //has the (100mS) delay finished
                delayLED--; //no, so count down
            }else{
                LED1OFF;    //yes, turn off LEDs
                LED3OFF;
            }
        }else if(encoder==1){   //increment?
            LED3ON;             //turn on LED
            delayLED=100;       //keep it on for 100mS
        }else if(encoder==-1){
            LED1ON;             //as above
            delayLED=100;
        }
    }
}
                        //   0  1  2 3  4 5 6  7  8 9 10 11 12 13 14 15
const signed char table[] = {0,-1,+1,0,+1,0,0,-1,-1,0,0,+1, 0,+1,-1, 0};

int8_t readEncoder(void){
    static uint8_t previous;
    uint8_t one=0,two=0;
    if(SW1)             //preread switches
        two|=1;
    if(SW2)
        two|=2;
    do{
        __delay_us(250);    //short delay
        one=two;            //keep old value
        if(SW1)             //get new value
            two|=1;
        if(SW2)
            two|=2;
    }while(one!=two);       //repeat until bouncing stops
    previous<<=2;           //make room for new bits
    previous|=one;          //OR in new bits
    previous&=15;           //get rid of higher bits
    return(table[previous]);//return value from table
}
And what happens:-

Turn on

All LED's on

Turn encoder one way, one LED goes off, both other LED's stay on.

Turn in the opposite direction - sometimes above LED stays on and other LED (not indication 0 state) goes off, depending on how fast the encoder is turned both LED's indicating direction go off, 0 state LED stays on <-- it actually stays on all the time.

Again, depending on how fast the encoder is turned, both LED's indicating direction will oscillate back and forth between each other - I can probably set up the camera and record it if the description is not obvious as to what is happening.

I just noticed the LED on/off states are opposite what I used in my other crappy code attempt, will swap them over and see what happens.
 

Pommie

Well-Known Member
Most Helpful Member
Got to go out for a few hours (about 5) but will have a look later.

Mike.
 

augustinetez

Active Member
OK, I've got some paving to do still, so will take the chance to get some of it done.

Using the code from post #90 and inverting the LED state (ON =1), 99.9% working fine.

I can trick it in to lighting both direction LEDs at high speed rotation of the encoder or by judicious reversing direction half way through the 4 step sequence, but I was deliberatly trying things to see if I could upset it
 

Pommie

Well-Known Member
Most Helpful Member
I can trick it in to lighting both direction LEDs at high speed rotation of the encoder or by judicious reversing direction half way through the 4 step sequence, but I was deliberatly trying things to see if I could upset it
You will definitely get both LEDs lit if you reverse direction due to the delay being re-triggered every move. I can't get both lit when spinning in one direction - maybe you can spin faster than me!!!

Are you up to trying the interrupt version? It seems to work very well for me and doesn't miss anything - so far.

Any bits of the code that you're not following or needs to be better explained?

Mike.
Edit, I can occasionally get both lit in one direction but the second one lights as it stops spinning so it's maybe falling back to the previous indent.
Edit2, the interrupt version works at 4 times the speed so as not to miss states.
 

augustinetez

Active Member
Are you up to trying the interrupt version? It seems to work very well for me and doesn't miss anything - so far.

Any bits of the code that you're not following or needs to be better explained?

Mike.
I'm using your code in post #92, just dropping my config and definitions over the top of yours.

Question or three:

The variables one, two & previous - are they initialised anywhere or do they just get loaded as the encoder function runs through?

To keep this in line with my original asm, can this be done as a polling routine without the interrupts and such that it stays within the encoder function until it moves?

And does it take account of the 4 steps between detents before going off and doing things? If it does, that the bit I can't see.

Ok, four questions - this one I don't understand, yes, I know it is being set to 0, but how is it interacting with interrupts?

Code:
encoderCount=0;         //and zero the interrupt variable
 

Pommie

Well-Known Member
Most Helpful Member
Variable two is initialized where the comment "//preread switches" is. Then one is initialized from two. Previous isn't initialized and probably should be set to 0xff.
It can be done with a polling routine but ideally, is should be polled very fast and is probably best in an interrupt.
The 4 steps per detent can be accounted for by dividing the running variable by 4 before use.
The part in the interrupt version that uses the interrupt encoderCount is so that an interrupt cannot occur between reading the value and zeroing it. I've just realized that encoderCount should be marked volatile as it's changed by an interrupt - it doesn't matter in the code above but better to have it right. Note, that in asm, it would still require the same sequence to use encoderCount.

As the interrupt version stands at the moment, encoderCount can contain any number of values depending on how often it's read by the main loop. If code is executed that takes 5mS then the encoder could have moved as much as 50 (made up number) steps which is why the final code would add the number to the bigger count. Hope that makes sense.

Mike.
 

Pommie

Well-Known Member
Most Helpful Member
As it currently stands, that encoder function isn't going to work for what I want to do.
Can you elaborate on that? Why won't it work? It simply returns a value that is ± a number which you add to a larger variable and then check the larger variable is in range. The larger variable can be uses as is or divided by four to account for the detent count.

Mike.
 

Latest threads

EE World Online Articles

Loading
Top