To come in
Sewerage and drainpipes portal
  • Connecting a rotary encoder to a computer via USB
  • Homemade TV backlight from USB
  • Evaluation of methods for measuring low frequencies on the Arduino Brief description of the frequency meter FC1100-M2
  • Photoresistor and LEDs on Arduino
  • Line transformer tvs
  • Complementary FET Hi-Fi Amplifier
  • Encoder activation. Connecting a rotary encoder to a computer via USB

    Encoder activation.  Connecting a rotary encoder to a computer via USB

    Surely, everyone has come across, in everyday life, with an encoder. For example, in car stereos, they are used to control the volume. Or in computer mice, a scroll wheel.

    With their help it is very convenient to organize the menu. I know of cases when, on a very serious and expensive device, all control is organized using just one encoder. Similarly, in ancient times, there was a phone model where all control was also organized with just one wheel.

    First of all, there are several types of encoders, considered in this article - mechanical incremental. As a test subject, pec12-4220f-s0024 was used. Outwardly, it looks like a variable resistor, but, unlike a resistor, it has no limiters, i.e. can spin endlessly in any direction.

    The result of the operation of such a device is a binary Gray code. You can get it by analyzing the state of the legs, which receive pulses from the encoder.

    Now let's look at everything in more detail. Electrically, it represents 2 buttons without latching, when we start twisting they work in turn - first one, then the second. Depending on which direction we rotate, one of the buttons is triggered earlier or later. In order to find out what state these buttons are in, the pins of the port (to which the encoder is connected) must be pulled up to the "+" power supply.

    On a disassembled encoder, 1/3 of the pad refers to 1 contact, 1/3 to 2 contacts, the solid area is common. When the sliding contacts hit the insulated areas (black), a clicking sound is heard. At this point, the encoder is in a steady state when both buttons are open. On the port legs there will be a log unit (state 11).

    As soon as we start to rotate in any direction, one of the contacts closes to ground. Log 0 will appear on this leg, log1 will still be on the second leg (state 01). If we continue to rotate, log0 will appear on the second leg (state 00). Further, the contact on the first leg disappears (state 10), in the end the encoder returns to a stable state (11). Those. there are 4 state changes per click. The timing diagram looks like this:

    When rotating in the opposite direction, the idea remains the same, only at first it will close, the second leg.

    If we write out these states in the binary system and convert them to decimal, then we get the following order (for rotation in one direction):
    11=3
    01=1
    00=0
    10=2

    When rotating in the opposite direction:
    11=3
    10=2
    00=0
    01=1

    Now it remains to understand how to process these values. Let's say the encoder is connected to the pins of ports B0 and B1. We need to read these legs. There is a pretty tricky way, but first we need to understand the boolean and (&) operation.

    The result will be equal to one only if both numbers are equal to 1, i.e. the result of operation 1 & 1, will be equal to 1. Therefore, 1 & 0 = 0, 0 & 0 = 0, 0 & 1 = 0.

    The logical & will help us to isolate from the whole port, only the legs of interest to us. Those. operation x = 0b00000011 & PINB; will allow us to read into the variable "x" the state of the first two legs, regardless of what is on the other legs. The number 0b00000011 can be converted to 0x3 hexadecimal system.

    Now we have all the necessary knowledge to write the firmware. Task: increase / decrease the variable Vol, using the encoder, display the result on the lcd display.

    #include int NewState, OldState, Vol, upState, downState; #asm .equ __lcd_port = 0x12; PORTD #endasm #include #include interrupt [TIM1_COMPA] void timer1_compa_isr (void) (NewState = PINB & 0b00000011; if (NewState! = OldState) (switch (OldState) (case 2: (if (NewState == 3) upState ++; if (NewState == 0) downState ++ ; break;) case 0: (if (NewState == 2) upState ++; if (NewState == 1) downState ++; break;) case 1: (if (NewState == 0) upState ++; if (NewState == 3) downState ++ ; break;) case 3: (if (NewState == 1) upState ++; if (NewState == 2) downState ++; break;)) OldState = NewState;) TCNT1H = 0x00; TCNT1L = 0x00;) void main (void) ( char lcd_buf [17]; // Input / Output Ports initialization// Port B initialization // Func7 = In Func6 = In Func5 = In Func4 = In Func3 = In Func2 = In Func1 = In Func0 = In // State7 = T State6 = T State5 = T State4 = T State3 = T State2 = T State1 = P State0 = P PORTB = 0x03; DDRB = 0x00; // Timer / Counter 1 initialization// Clock source: System Clock // Clock value: 1000,000 kHz // Mode: CTC top = OCR1A // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer 1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: On // Compare B Match Interrupt: Off TCCR1A = 0x00; TCCR1B = 0x0A; TCNT1H = 0x00; TCNT1L = 0x00; ICR1H = 0x00; ICR1L = 0x00; OCR1AH ​​= 0x03; OCR1AL = 0xE8; OCR1BH = 0x00; OCR1BL = 0x00; // Timer (s) / Counter (s) Interrupt (s) initialization TIMSK = 0x10; // Global enable interrupts #asm ("sei") lcd_init (8); while (1) (if (upState> = 4) (Vol ++; upState = 0;) if (downState> = 4) (Vol--; downState = 0;) sprintf (lcd_buf, "vol =% d", Vol) ; lcd_gotoxy (0, 0); lcd_clear (); lcd_puts (lcd_buf);); )

    #include int NewState, OldState, Vol, upState, downState; #asm .equ __lcd_port = 0x12; PORTD #endasm #include #include interrupt void timer1_compa_isr (void) (NewState = PINB & 0b00000011; if (NewState! = OldState) (switch (OldState) (case 2: (if (NewState == 3) upState ++; if (NewState == 0) downState ++; break; ) case 0: (if (NewState == 2) upState ++; if (NewState == 1) downState ++; break;) case 1: (if (NewState == 0) upState ++; if (NewState == 3) downState ++; break; ) case 3: (if (NewState == 1) upState ++; if (NewState == 2) downState ++; break;)) OldState = NewState;) TCNT1H = 0x00; TCNT1L = 0x00;) void main (void) (char lcd_buf; // Input / Output Ports initialization // Port B initialization // Func7 = In Func6 = In Func5 = In Func4 = In Func3 = In Func2 = In Func1 = In Func0 = In // State7 = T State6 = T State5 = T State4 = T State3 = T State2 = T State1 = P State0 = P PORTB = 0x03; DDRB = 0x00; // Timer / Counter 1 initialization // Clock source: System Clock // Clock value: 1000,000 kHz // Mode: CTC top = OCR1A // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Time r 1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: On // Compare B Match Interrupt: Off TCCR1A = 0x00; TCCR1B = 0x0A; TCNT1H = 0x00; TCNT1L = 0x00; ICR1H = 0x00; ICR1L = 0x00; OCR1AH ​​= 0x03; OCR1AL = 0xE8; OCR1BH = 0x00; OCR1BL = 0x00; // Timer (s) / Counter (s) Interrupt (s) initialization TIMSK = 0x10; // Global enable interrupts #asm ("sei") lcd_init (8); while (1) (if (upState> = 4) (Vol ++; upState = 0;) if (downState> = 4) (Vol--; downState = 0;) sprintf (lcd_buf, "vol =% d", Vol) ; lcd_gotoxy (0,0); lcd_clear (); lcd_puts (lcd_buf);); )

    For clarification: timer 1 is configured to fire 1000 times per second, with the string NewState = PINB & 0b00000011; read the status of legs 0 and 1 of port B. if (NewState! = OldState) if the state has not changed, then there is no rotation.
    If the state has changed, the switch construction determines in which direction the rotation was performed, depending on this, the value of the variable downState (to the left) or upState (to the right) increases.

    From click to next click, there are 4 changes of state, so 1 time in 4 pulses we change the variable Vol. We also display it. Firmware available

    Often in devices based on microcontrollers, you need to organize the management of menu items or implement some kind of adjustment. There are many ways: using buttons, variable resistors or encoders. An incremental encoder allows you to control anything by turning the knob endlessly. In this article, we'll take a look at how to get an incremental encoder and an Arduino to work.

    Incremental encoder features

    An incremental encoder, like any other type of encoder, is a rotating arm device. It remotely resembles a potentiometer. The main difference from a potentiometer is that the encoder handle rotates 360 degrees. He has no extreme positions.

    Encoders are of different types. The incremental one differs in that with its help it is impossible to find out the position of the handle, but only the very fact of rotation in some direction - to the left or to the right. By the number of signal pulses, you can already calculate at what angle it turned.

    This way you can send a command, control a menu, volume level, for example, and so on. In everyday life, you could see them in car radios and other equipment. It is used as a multifunctional level control, equalizer and menu navigation.

    Inside the incremental encoder there is a disc with labels and sliders that touch them. Its structure is similar to a potentiometer.

    In the figure above, you see a disc with marks, they are needed to interrupt the electrical connection with the moving contact, as a result, you get data on the direction of rotation. The design of the product is not so important, let's figure out how it works.

    The encoder has three information pins, one common, the other two are usually called "A" and "B", in the figure above you see the pinout of the encoder with a button - you can receive a signal by pressing its shaft.

    What signal will we receive? Depending on the direction of rotation, a logical one will first appear on pin A or B, so we get a phase-shifted signal, and this shift allows us to determine which direction. The signal is obtained in the form of a rectangular shape, and the microcontroller is controlled after processing the data of the direction of rotation and the number of pulses.

    The figure shows a conventional designation of a disk with contacts, in the middle is a graph of output signals, and on the right is a table of states. This device is often drawn as two keys, which is logical, because in fact we receive a signal "forward" or "backward", "up" or "down", and the number of impacts.

    Here is an example of a real encoder pinout:

    Interesting:

    A faulty encoder can be replaced with two buttons without latching, and vice versa: a homemade product that is controlled by two such buttons can be modified by installing an encoder.

    In the video below, you can see the alternation of the signal at the outputs - with smooth rotations, the LEDs light up in the sequence shown in the previous graph.

    This is illustrated no less clearly in the following animation (click on the picture):

    The encoder can be both optical (the signal is generated by emitters by photodetectors, see the figure below) and magnetic (operates on the Hall effect). In this case, he has no contacts and a longer service life.

    As already mentioned, the direction of rotation can be determined by which of the output signals changed earlier, but this is how it looks in practice!

    The control accuracy depends on the encoder resolution - the number of pulses per revolution. The number of pulses can be from units to thousands of pieces. Since the encoder can act as a position sensor, the more pulses, the more accurate the determination will be. This parameter is referred to as PPR - pulse per revolution.

    But there is a small nuance, namely the similar designation LPR - this is the number of labels on the disk.

    And the number of processed pulses. Each label on the disc produces 1 square wave on each of the two outputs. The pulse has two edges - trailing and leading. Since there are two outputs, from each of them we get a total of 4 pulses, the values ​​of which you can process.

    Connect to Arduino

    We figured out what you need to know about the incremental encoder, now let's find out how to connect it to the Arduino. Consider the connection diagram:

    An encoder module is a board that contains an incremental encoder and pull-up resistors. Any pins can be used.

    If you do not have a module, but a separate encoder, you just need to add these resistors, the circuit will not be any different in principle. To check the direction of rotation and its operability, we can read information from the serial port.

    Let's analyze the code in more detail, in order. In void setup () we declared that we would use serial communication and then set pins 2 and 8 to login mode. You choose the pin numbers yourself based on your connection diagram. The INPUT_PULLUP constant sets the input mode, arduino has two options:

      INPUT - input without pull-up resistors;

      INPUT_PULLUP - connection to the input of pull-up resistors. There are already resistors inside the microcontroller through which the input is connected to the plus of the power supply (pullup).

    If you use resistors to pull up to the positive power supply as shown in the diagrams above or use an encoder module - use the INPUT command, and if for some reason you cannot or do not want to use external resistors - INPUT_PULLUP.

    The logic of the main program is as follows: if at the input "2" we have one - it outputs to the monitor of port H, if not - L. Thus, when rotating in one direction on the monitor of the serial port, something like this will turn out: LL HL HH LH LL. And vice versa: LL LH HH HL LL.

    If you read the lines carefully, you probably noticed that in one case the first character acquired a meaning, and in another case the second character changed first.

    Conclusion

    Incremental encoders have found wide practical application in amplifiers for acoustic systems - they were used as a control for the volume control, in car radios - to adjust sound parameters and navigate through menus, in computer mice you use it to scroll through the pages every day (a wheel is installed on its shaft) ... And also in measuring tools, CNC machines, robots, in selsyns, not only as controls, but also for measuring values ​​and determining position.

    I was ordered to develop a program for a device in which an incremental encoder is used as a control element. Therefore, I decided to write an unscheduled lesson about working with an encoder in the Arduino system.

    Quite briefly, what is at stake, i.e. about the classification of encoders.

    Encoders are digital rotary encoders. In other words, angle-to-code converters.

    I emphasize that these are digital sensors because there are a large number of sensors with analog outputs. A simple variable resistor can be considered an angle sensor. Encoders generate discrete signals at the output.

    Encoders are absolute and cumulative (incremental).

    Absolute encoders generate a code at the output that corresponds to the current angle of the shaft position. They have no memory. You can turn off the device, turn the encoder shaft, turn it on and the output will be a new code showing the new position of the shaft. Such encoders are complex, expensive, and often use standard digital interfaces RS-485 and the like for connection.

    Incremental encoders generate pulses at the output that appear when the shaft is turned. The receiving device can determine the current angle of the encoder shaft by counting the number of pulses at its output. After power-up, the incremental encoders are unable to detect the shaft position. It is necessary to tie it to the origin.

    But in most cases it is not necessary to know the absolute value of the current angle. If we use the encoder, for example, to adjust the volume level, then we need to increase it by several steps or decrease it. We are not looking at the encoder knob, there is no scale on it. We need to determine the change in angle relative to the current position. The same goes for setting parameters on the display. We turn the encoder knob and watch how the parameter value changes on the display screen.

    In such cases, incremental encoders become ideal devices for control, parameter setting, menu selection. They are much more convenient than the "+" and "-" buttons.

    In these cases, the simplest mechanical incremental encoders can be used, which are notable for their low cost.

    The principle of operation, the connection diagram and the source code of the library for working with an incremental encoder have already been considered by me in one of the articles. Today we will talk about the practical application of the encoder. As an example, I chose a square wave generator program with an operating frequency range of 1 - 100 Hz. The original idea was for a range of 1 - 1000 Hz, but in practice it turned out that going through a thousand values ​​is tedious even with an encoder.

    Preparation

    Create a new project in an empty workspace

    Project> Create New Project ...

    Template type C> main

    Copy the library source files to the project folder for working with the encoder
    encoder.h and encoder.c

    We connect the encoder.c file to our project
    Right mouse button in the workspace window and in the Add> Add Files ... menu that opens.

    Copy the bits_macros.h file to the project folder.


    We include header files

    At the beginning of the main.c file, we hammer in the following lines
    #include
    #include
    #include "encoder.h"
    #include "bits_macros.h"

    Setting project settings

    Project> Options

    Microcontroller type
    General Options> Target> Processor Configuration> ATMega8535

    Allowing the use of bit names defined in header files
    General Options> System> Enable bit defenitions ...

    Optimizing code for size
    C / C ++ Compiler> Optimisations> Size High

    Output file type
    Linker> Output File checkbox Override default and change extension to hex
    Linker> Format> Other select Intel Standart

    Click OK. We save the project and workspace.
    Now we have an empty project with the connected lib and the specified settings.

    Task

    Force the microcontroller to generate a square wave with a frequency of 1 to 100 Hz. The frequency value must be set using an encoder. Rotation of the encoder one position should correspond to a change in the generator frequency by 1 Hz.

    Scheme for our example

    An LED is connected to the output on which the meander will be generated, in order to somehow see the result of the program. It is unlikely that many have an oscilloscope at hand.

    Algorithm of the program

    The square wave is generated by a 16-bit T1 timer that operates in CTC - Reset On Match. An array is stored in the flash memory of the microcontroller, containing a constant for each value of the required frequency. The pTimerValue variable is used to access the elements of the array. In timer T1 interrupts, the value of the constant is read and written to the compare register.

    The PD5 pin (OC1A) is used to generate the signal. It has alternative functions - it can change its state to the opposite when the counting register and the comparison register are equal.

    In the main program, in an endless while loop, the microcontroller polls the encoder buffer and, depending on its value, decreases or increases the pTimerValue variable.

    At the very beginning of main, there is the code for initializing the periphery and the necessary variables.

    Program structure

    For clarity, I have depicted the structure of the program in the form of a diagram.

    This is a typical structure for building simple programs. Interruptions naturally occur at any point in the loop.

    • Initialization.
    • An infinite loop (called a superloop), in which an event is waited for, usually in the form of polling flags or some kind of buffer.
    • Parallel operation of peripheral devices causing interrupts. They execute some code (preferably short) and set flags.

    For simple tasks, this approach is enough for the eyes. For complex programs, there are other ways of organizing programs. Be patient, soon it will come to them.

    Calculation of constants for timer T1

    Let's calculate the value of the constant for a frequency of 1 Hz. I have already given a similar calculation, but it will not be superfluous to remember it.

    The clock frequency of the microcontroller is 16 MHz (see diagram). The timer prescaler factor is 256. It allows you to get interrupts with any frequency from our range.

    The period of one tick of the timer will be equal to 1 / (16 MHz / 256) = 16 μs

    At pin PD5, we need to receive a 1 Hz signal. The output changes its state for each timer interrupt, which means that the interrupt frequency should be 2 times higher. For our case - 2 Hz.

    How many timer ticks will fit in 2 Hertz? (1/2 Hz) / 16 μs = 31250
    This is the desired constant.

    The rest of the values ​​are calculated in the same way. I usually use Exel for this.


    We put the resulting values ​​into an array

    __flash unsigned int timerValue =
    {

    save it in a separate file - timer_value.h and connect it to the main.c file

    #include "timer_value.h"

    Yes, you also need to add a couple of constants to this file.

    #define MAX_TIM_VALUE 99
    #define MIN_TIM_VALUE 0

    Let's make sure that we correctly calculated the constants for the timer. Let's launch it. The program code will be like this.

    // programming AVR in C

    // site 17.10.09
    #include
    #include
    #include "encoder.h"
    #include "bits_macros.h"
    #include "timer_value.h"

    // index for accessing array elements
    volatile unsigned char pTimerValue = 0;

    int main ( void )
    {
    // initialization of timer T1
    TCNT1 = 0;
    TCCR1A = (0<TCCR1B = (0<

    // configure PD5 pin to output
    SetBit (PORTD, PD5);
    SetBit (DDRD, PD5);

    // do nothing in an infinite loop
    while(1);
    return 0;
    }

    I think that only a piece of timer initialization requires an explanation.

    Resetting the counting register
    TCNT1 = 0;

    Initialization of configuration registers of timer T1.
    TCCR1A = (0<TCCR1B = (0<

    Where the bits WGM13, WGM12, WGM11, WGM10 set the timer operation mode - CTC,
    CS12, CS11, CS10 - define the timer prescaler factor -256,

    COM1A1, COM1A0 - determine the behavior of the PD5 pin (OC1F) - in this case, according to the timer signal, it will change its state to the opposite


    Initialize the match register with the initial value.
    OCR1A = timerValue;

    We compile the program and load it into the microcontroller. The LED should blink at a frequency of 1 Hz.
    There are no interrupts in the program. There is no manipulation of the PD5 pin. However, the LED is blinking!

    Program

    Now you need to "screw" the encoder to this program. Let's set the settings in the header file encoder.h - the port and pins to which the encoder is connected, the values ​​of the constants.


    #define PORT_Enc PORTA
    #define PIN_Enc PINA
    #define DDR_Enc DDRA
    #define Pin1_Enc 2
    #define Pin2_Enc 1

    #define RIGHT_SPIN 0x01
    #define LEFT_SPIN 0xff

    The header contains prototypes for three functions. Let's remember their purpose.

    void ENC_InitEncoder (void) configures the microcontroller pins to which the encoder is connected to the input. This function must be called at the beginning of main.


    void ENC_PollEncoder (void)- polls the encoder once, analyzes the current and previous states and writes the corresponding constants (RIGHT_SPIN and LEFT_SPIN) to the buffer. This function will sit in the interrupt timer T0.


    unsigned char ENC_GetStateEncoder (void)- returns the contents of the encoder buffer. If the rotation by one position was not fixed, the function will return 0, if the rotation was fixed, the function will return the value of the corresponding constant. This clears the buffer value. This function will be called in the main program - in the while loop.


    We supplement our program. You can try doing it yourself.

    // programming AVR in C
    // example of using the encoder
    // site 17.10.09

    #include
    #include
    #include "encoder.h"
    #include "bits_macros.h"
    #include "timer_value.h"

    #define TCNT0_const 253
    #define TCCR0_const 5

    volatile unsigned char pTimerValue = 0;

    int main ( void )
    {
    ENC_InitEncoder ();

    // initialization of the timer t0
    TCNT0 = TCNT0_const;
    TCCR0 = TCCR0_const;

    // initialization of timer t1
    TCNT1 = 0;
    TCCR1A = (0<TCCR1B = (0<OCR1A = timerValue;

    // enable timer interrupts
    // t0 - by overflow, t1 - by coincidence

    TIMSK = (1<

    // set up PD5 to exit
    SetBit (PORTD, PD5);
    SetBit (DDRD, PD5);

    __enable_interrupt ();
    while (1){
    // read the contents of the encoder buffer
    // after reading it is cleared

    unsigned char stateEnc = ENC_GetStateEncoder ();

    // if not empty
    if(stateEnc! = 0) (
    // determine the direction of rotation and change the timerValue variable
    if(stateEnc == RIGHT_SPIN) (
    if(pTimerValue == MAX_TIM_VALUE) pTimerValue = MIN_TIM_VALUE;
    else pTimerValue ++;
    }
    if(stateEnc == LEFT_SPIN) (
    if(pTimerValue == MIN_TIM_VALUE) pTimerValue = MAX_TIM_VALUE;
    else pTimerValue--;
    }
    }
    }
    return 0;
    }

    // poll the encoder
    #pragma vector = TIMER0_OVF_vect
    __interruptvoid timer0_ovf_my ( void)
    {
    TCNT0 = TCNT0_const;
    ENC_PollEncoder ();
    }

    #pragma vector = TIMER1_COMPA_vect
    __interrupt void timer1_compa_my ( void)
    {
    // update the value of the compare register
    OCR1A = timerValue;
    }

    It seems that everything should be clear.
    A piece of code in which the value of pTimerValue is changed could have been written else like this:

    if(stateEnc! = 0) (
    pTimerValue = pTimerValue + stateEnc;
    if(pTimerValue == (MAX_TIM_VALUE + 1)) pTimerValue = MIN_TIM_VALUE;
    else if(pTimerValue == (MIN_TIM_VALUE - 1)) pTimerValue = MAX_TIM_VALUE;
    }

    When the encoder is rotated to the right, pTimerValue is added to 1, that is, it is incremented.

    When the encoder is rotated to the left, pTimerValue is added to 0xff, which is equivalent to subtracting 1. The same operation, but the result is exactly the opposite.