Tuesday, December 23, 2014

Messing around - the encoder

Well well, helo there yet again. So yeah, this time we will be messing around a simple 3 pin encoder.
It has three pins as you might have guessed, and that will be Common pin, pin A, pin B. The way it works is pretty simple. Once you turn it in any direction it sends pulse to both A and B pins. Depending on which way you turn, A will be the first, or vice versa. It works kindof like two buttons, and one gets pushed before the other. So what do we need in order to work with buttons ? Well - pull-up resistors, a little bit of debouncing and of course some magic to put it all together. Just look at the block diagram.
The PIN-A and PIN-B will be representing the pins on the microcontroller. This is actually boring, the fact that I have done this already. Oh well, the next project will be about 1 wire, so next time I will write as my progress will go. Whatever, seems I have got out of the track. Now, Let's choose two pins for this, any GPIO will work.
I chose PD2 and PD3 on my ATmega16. Of course we can use external pull-up resistors, like 10k will work just fine, but for convenience we will use the internal pull-ups of AVR. And we will need to somehow see, is it really working or what. We could technically use an LCD display, or send data back to PC with UART and other ways of overkilling it, but the for sake of simplicity, lets just connect and LED to one of AVR's PWM channels, thus letting us to control the brightness of the LED by turning the encoder. So with no further ado, let's start throwing some code in.
We begin with adding the libraries and some boring stuff as always.
#include inttypes.h
#include avr/io.h
#include util/delay.h
We have inttypes.h for all the variable types like uint8_t and uint16_t, and avr/io.h for all the register definitions. And you might have guessed it, delay.h for setting up delays.
Then we go to the unpleasant part - reading the datasheet. Well, it wasn't pleasant for me at first as well, but trust me, it will get easier once you do some messin' around. We will configure Timer counter 1 to generate PWM to control the brightness of an LED with it. So let's write a function for that..
void init_timer1(void) {
  /* 
   * Timer configuration:
   * Fast pwm mode (8 bit)
   * Freq: (divider: 1)
   * Non inverting mode
   */
  TCCR1A = _BV(COM1A1)|_BV(COM1B1)|_BV(WGM10); //|_BV(WGM11);
  TCCR1B = _BV(WGM12)| /* _BV(CS12)| */_BV(CS10);
}
IMPORTANT - keep in mind, this is the configuration for ATmega16 it might be different for your model. Just look up the datasheet, search for Timer counter register description or whatever, and try to configure it for Fast PWM mode. An oscilloscope might come handy for this kind of job, but in case you find some complications, just comment about it. It took me some time to fully understand timer/counters. In case you get the question: "what the heck will this do", it is probably a good idea to look at the datasheet (page 110 for ATmega16). Now to test if this is working, connect an LED to OC1A or OC1B (using a resistor might be a good idea) and test it with this main routine.
int main (void) {
  init_timer1();
  DDRD |= _BV(4)|_BV(5); // set PWM pins as outputs
  uint8_t ocr = 0;
  while (1) {
    ocr ++;
    OCR1A = ocr;
    _delay_ms (1);
  }
  return 0;
}
After this, the LED shold start getting all brighter, until it reaches it's criteria, then it will start over. I'll leave the explanation to you... (ocr is a uint8_t, which goes from 0-255, thus when you do (255)++, it starts over from 0). Notice that I turn both channels in init_timer1, but I turn on the OC1A channel, so be sure to connect the LED to the right pin.
Going on, if you got it working right, good for you. If however not, check this few things:
  • LED connection (anode/cathode - look twice)
  • Timer configuration
  • DDR configuration
Oh .. totally forgot, you might ask, how the heck do I get this to the microcontroller. Well, pal, you should compile it using avr-gcc, do some jiggling to get .hex file, and then using avrdude upload it to your MCU. Here's a detailed link to it: here Spoiler: I use linux, and you better do as well. But in case you don't, download win-avr and do the same steps. Also you'll need some kind of programmer. It would be practical to use some kind of makefile, but will talk about it later, so, now assuming you managed to get the LED fading in-out kindof, I will test it myself and continue ...
Yeah, sure enough, it is working for me. So lets get on with - do the jiggling for the encoder. To keep it simple, let's write it in a function: enc_init
void enc_init (void) {
  // set input pins
  DDRD = ~ ( _BV(PD2)|_BV(PD3) );
  // set inner pullups
  PORTD |= ( _BV(PD2)|_BV(PD3) );
  // ready to GO!
}
So, with this we set Data direction registers to make pins ready to be later read, and setting inner pull-ups using PORT registers. Look up the datasheet for those registers, they are the basic ones, I assume that whoever the read is, has some basic knowledge of C and AVR, but if you don't, no need to worry, you can always learn. Then we will need to make a change in the main routine, now saying it to read encoder value, and making decision based on the data read. As it was mentioned, encoder sends pules on both A and B pins, however, depending on which side you turn, A comes first, or vice versa. Now, for doing this we will be using the most common way - that is keeping track of last encoder position, reading the new one, and making decision on what to do. To avoid the noise and the bouncing that encoder brings, we will do it every 5 milliseconds. The proper way of doing it would be with another timer/counter with interrupt service routines, but this post got way complicated anyways, so I'll try to avoid making it any harder. Here is how it will go, let's first write a simple function, that returns an integer, depending on the encoder's pin's values. We'll call it get_enc_code, and it will take no arguments.
/* 
 * Returns grays code:
 * 0 - none
 * 1 - only B
 * 2 - only A
 * 3 - both
 */
uint8_t get_enc_code (void) {
  if (PIND  _BV(PD2))
    // not A
    if (PIND  _BV(PD3))
      // none
      return 0;
    else
      // B
      return 1;
  else
    // A
    if (PIND  _BV(PD3))
      // only A
      return 2;
    else
      // both
      return 3;
}
As it was mentioned, we used pull-up resistors, and judging by name, the status of the pin when nothing is moving should be pulled up, i.e. HIGH or 1 or true or whatever you call it. It will be a logical one, which can be read from the PIND register. It consists of 8 bits, responding for all the 8 pin inputs of PORTD. To get the value of that one pin (e.g PD2) we use logical and operator. However, you probably know about this, so, if both pins are 1, then encoder is not moving. In case on of the pins gets low (i.e. encoder moves) we will notice change, either A or B will come first, and then they both will turn low. You see where I'm going ?
YEss! You got it, by keeping track we can see if it was A or B turning first, and we can say if it was clockwise or counter clockwise. Let's take it to code level shall we ?
int main (void) {
  init_timer1();
  enc_init();
  DDRD |= _BV(4)|_BV(5); // set PWM pins as outputs
  uint8_t ocr = 0, enc_last_code=0, enc_code;
  uint8_t enc_rotation = 0; // 0 can be our forever..
  // 0 - no rotation
  // 1 - cw
  // 2 - ccw
  while (1) {
    enc_code = get_enc_code();
    if (enc_code == 3) {
      // we got to point where both A and B are low
      // sombody turned the darn knob
      // now checking the last position...
      if (enc_last_code == 3)
 enc_rotation = 0; // this part is for debouncing
      else if (enc_last_code == 2) // from A - both
 enc_rotation = 1;
      else if (enc_last_code == 1) // from B - both
 enc_rotation = 2;
    } else
      enc_rotation = 0;

    enc_last_code = enc_code;
    
    if (enc_rotation == 1) ocr += 10;
    if (enc_rotation == 2) ocr -= 10;

    OCR1A = ocr;
    _delay_ms (2);
  }
  return 0;
}
Ok ok, wait, I see what you might think, shit just got dark. But nope, all the code is really simple to understand. You might need to jiggle around that delay value to make it work for your encoder. Also I think there were encoders that give several pulses on one turn, however, mine is simpler, it gives only one. Back to code ... It's the realisation of the idea, and when we recognize that encoder was turned, depending on which way - we either increase or decrease the value of the output compare register, thus making the LED light brighter or darker .. However, I kind of lost you on the way here. The full file is here. Yeah .. kindof really lost the trace, what was I talking about ..hmm. Yeah, the main idea was to demonstrate my library, but my time is coming up to it's end, so I guess it will stay for later.
So this was all it was for today, hope you enjoyed. In case you don't like my style, please leave a comment, I'll try to fix it. Farewell!

No comments:

Post a Comment