# Pic Lab, PIC16, Experiment #20, The encoder

I needed to get my hands on the encoder, so I started to explore things around it.

First of all, what is the encoder? This is a thing that helps to convert the rotation angle and a direction of rotation to some numbers we can use for our further advance.

Usually, the encoder has three terminals, to connect it I have used the following circuit:

All resistors have a nominal equal to 4.7KOhm.

I had an extra button in my encoder, which is quite convenient for my application since I wanted my future project to have fewer as possible buttons on the enclosure.

So, how would we know the direction and the angle?

I made some simple drawings to help me apprehend the deal. Imagine, we are rotating the knob in one direction, and during this, we connected the scope probes:

And now, we’ve changed the direction:

In this approach, I was analyzing the port C state in dependence on port A. What I did is: I started an interrupt handler and when the interrupt happened, just checked the second port state.

All is good, but there is one bad thing in this approach – the bouncing. There are different ways to make de-bouncing, among them I picked to check the state of both ports in the main cycle (outside of the interrupt handler). The program did not work super well, I added some caps to make debouncing

volatile static bit encoder_flag = 0;    //global flag

if (encoder_flag) {

GIE = 0;
if ((!right) && (left)) up = 1;          //clockwise
if ((right) && (!left)) up = 1;          //clockwise

if ((right) && (left)) down = 1;         //anti clock-wise
if ((!right) && (!left)) down = 1;       //anti clock-wise

encoder_flag = 0;                        //drop the flag
GIE = 1;                                 //
}

Variables up and down just to represent the rotation direction.

Right and left – pins 5 and 2 of port B, don’t know why I called them this way, but it seemed logical to me at this point in time, so live with it.

This code was generating a lot of false events, so I added some software delay, which has been determined in an experimental way, the code is shown here (think about it in an application for the audio amplifier which is my home project):

volatile static bit encoder_flag = 0;

if (encoder_flag) {

GIE = 0;
__delay_us(20);
if ((!right) && (left)) up = 1;          //clockwise
if ((right) && (!left)) up = 1;          //clockwise

if ((right) && (left)) down = 1;         //anti-clockwise
if ((!right) && (!left)) down = 1;       //anti-clockwise

encoder_flag = 0;                        //dropping the flag
GIE = 1;
}

//interrupt handler
interrupt isr() {

if (RBIF) {

encoder_flag = 1;
RBIF = 0;

The code is fairly simple and still generate false event like once per 15 tests, I decided that this is fine for a moment (not ideal, I understand).

Now a bit of a more narrowed application for my audio amplifier. What is the most often used function in the audio amplifier? It is volume control, isn’t it? Let me try to draft the logic of future applications:

• Rotate left/right – change the volume level
• Press the encoder button in the waiting state – enter the main menu
• Rotate left/right after the button has been pressed – scroll the menu items
• The button is pressed after we are in the main menu – choose the certain menu item
• The button is pressed after we are already in sub-item – exit back to the main menu

Alright, now time to add the interrupt on the button pressing.

I hung the button on the pin RB0 and enabled INTF interrupt.

interrupt isr() {

if (INTF) {

volume_flag = 1;    //1 - flag to go to the main menu
INTF = 0;
}

Time to check, as usual, I use the UART for debugging::

if ((up)&&(!volume_flag)) {                  //увеличение громкости
if (volume<100) volume++;
printf("\r\n volume [%d]", volume);
up = 0;
}

if ((down)&&(!volume_flag)) {                //уменьшение громкости
if (volume>0) volume--;
printf("\r\n volume [%d]", volume);
down = 0;
}

if ((up)&&(volume_flag)) {
if (itemp < 10) {
if (itemp<9) {
itemp++;
exit_status = 0; //остаемся в меню
}
else {
itemp = 10;
exit_status = 1;        //если в десятом пункте меню, то можем выходить
}

}
else {
itemp = 1;
exit_status = 0;
}

printf("\r\n itemp [%d]", itemp);
printf("\r\n exit status - %d", exit_status);
up = 0;
}

if ((down)&&(volume_flag)) {
if (itemp > 1 ) {
itemp--;
exit_status = 0;
}
else {
itemp = 10;
exit_status = 1;
}
printf("\r\n itemp [%d]", itemp);
printf("\r\n exit status - %d", exit_status);
down = 0;
}

Some explanations are coming…

As I told you earlier, the encoder state changing will lead to a couple of actions, among them there are two main:

• Volume level changing

Those two are under the control of the volume_flag variable, if it is in 0 states we are in the volume, if in 1 state – go to the menu.

To save a state of the volume level I have the volume variable (so far it is local, but later on I plan to save it in the EEPROM, together with other setups). Similar to that there is also itemp variable for the menu state.

To organize the exit from the menu I made the condition equal to 10, so 10 is run away from the menu.

interrupt isr() {

if (INTF) {                                   //нажата кнопка
printf("\r\n\EButtoN");
if (!volume_flag) volume_flag = 1;            //если было изменение громкости, то входим в меню
if (exit_status) {                            //если выход, то выходим в изменение громкости
exit_status = 0;
volume_flag = 0;
}
INTF = 0;

}

if (RBIF) {                            //индикатор крутелки

encoder_flag = 1;
RBIF = 0;

}

That’s it, time to test.

I need to warn everybody who will want to use this procedure – this is not a good approach, I rebuilt it from ashes in another article, and I strongly advise using this alternative approach.