Work with Arduino

How it was?


When I had a desire to lead development for Arduino, I ran into several problems:
  • Select a model from the list of available
  • Attempts to understand what I will need besides the platform itself
  • Install and configure development environment
  • Search and analysis of test cases
  • "Showdown" with the screen
  • "Disassembly" with a processor


To solve these problems, I looked through and read quite a lot of different sources, and in this article I will try to give an overview of the solutions I found and the methods for finding them.

Platform selection


Before starting programming for a piece of iron, you need to buy it at the beginning. And then I ran into the first problem: it turned out that there were a lot of different * duins. There is a wide range of Arduino and about the same wide Freeduino and other analogues. As it turned out, there is no big difference what exactly to take. That is, some of these devices are slightly faster, others are slightly slower, some are cheaper, others are more expensive, but the basic principles of operation are practically the same. Differences appear almost exclusively when working with processor registers, and I will further explain how to avoid problems if possible.


I chose the Arduino Leonardo platform as the most affordable and available at that time in the online store in which I ordered everything. It differs from the rest of the line in that it has only one controller installed on board, which deals with working with a USB port and performing the very tasks that we hang on our device. This has its pros and cons, but you can’t run into them during the initial study, so forget about them for now. It turned out that it connects to the computer via micro-USB, and not USB-B, as it seems to be the majority of others. This somewhat surprised me, but also made me happy, because I, as the owner of a modern Android device, do not leave my house without this cable.
Yes, almost any * duino-compatible piece of iron is fed in several ways, including from the same cable through which it is programmed. Also, almost all motherboards have one LED located directly on the controller board, which allows you to start working with the device immediately after purchase, even without anything in your hands except a compatible cable.

Range of tasks


I think that before you start writing something like a piece of iron as such, it is interesting to understand what can be implemented on it. With Arduino, you can implement almost anything. Automation systems, ideas for a "smart home", controllers for controlling something useful, the "brains" of robots ... There are a lot of options. And a rather wide range of expansion modules, extremely simply connected to the controller board, helps a lot in this direction. Their list is quite long and promising, and they are searched on the Internet for the word shield. Of all these devices, I considered the most useful LCD screen with a basic set of buttons, without which, in my humble opinion, it is completely uninteresting to engage in any kind of training projects. The screen was taken from here, there is also a description there, as well as links to the manufacturer’s official website from the page provided.

Formulation of the problem


I somehow got used to getting a new tool into my hands to immediately set myself some moderately difficult and absolutely unnecessary task, solve it bravely, then put the source aside and only then take on something really complicated and useful. Now at my fingertips was a screen very similar to the component of a mine from some Hollywood movie with all the necessary buttons, with which you had to learn how to work, and also really wanted to master working with interrupts (otherwise what's the point of using a controller?) So the first what came to mind turned out to write a watch. And since the screen size allowed, it’s also with the date.

First steps


So I finally got all the purchased components and I assembled them. The screen connector was connected to the controller board like a native one, the board was connected to a computer ... And here this article helped me a lot . I will not repeat the same thing.
Hidden text
The only thing I will say is that recalling youth (or rather the first “project”, assembled while studying electronics in the Pioneer Palace - a multivibrator with two LEDs), I found 2 LEDs and corrected the example given in the article and started blinking them :).


Second Steps


The next logical question for me was "how to work with the LCD screen?". The official page of the device kindly provided me with links to the archive, which turned out to be 2 libraries with wonderful examples. I just didn’t say what to do with this. It turned out that you just need to unpack the contents into the libraries folder of the development environment.

After that, you can open the GuessTheNumber.pde example and fill it into the board similarly to the example with the flashing LED. However, personally after the firmware, the screen remained uniformly luminous and without a single letter. After a brief search for the problem, it turned out that you just had to screw up the only potentiometer on the screen board with a screwdriver to set the normal contrast value.

The set of commands used in the example is basically enough for simple work with the screen, but if you want something more, you can open the source code of the LCDKeypad and LiquidCrystal libraries and see what else is there.

Program architecture


The main task of the watch is to count the time. And they must do it for sure. Naturally, without using the interrupt mechanismno one can guarantee that time is considered with sufficient accuracy. Therefore, the timing should definitely be left to them. Everything else can be carried into the body of the main program. And we have quite a lot of this “rest” - all the work with the interface. One could do otherwise, create an event stack, created interrupt by the mechanism, and processed inside the main application, this would allow, for example, updating the screen no more than once every half a second (or by pressing a button), but I figured it out redundant for such a simple task, because in addition to redrawing the screen, the processor still has nothing to do. Therefore, all the free time the program rereads the state of the buttons and redraws the screen.

Problems with this approach

Periodic screen changes

I really wanted to make flashing colons between hours, minutes and seconds, so that, like in a classic watch, they burn for half a second, but the floor does not. But since the screen is redrawn all the time, it was necessary to somehow determine in which half a second they should be drawn, and in which - not. The easiest one was to make 120 seconds per minute and draw colons every odd second.

Flickering

With a constant redrawing of the screen, flickers become noticeable. To avoid this, it makes sense not to clear the screen, but to draw new text on top of the old one. If the text itself does not change, then there will be no flickering on the screen. Then the time redraw function will look like this:
LCDKeypad lcd;
void showTime(){
  lcd.home();
  if (hour<10) lcd.print("0"); // Случай разной длины приходится обрабатывать руками
  lcd.print(hour,DEC); // английские буквы и цифры ОНО пишет само, русские буквы нужно определять программисту
  if (second %2) lcd.print(" "); else lcd.print(":"); // вот они где используются, мои 120 секунд в минуте
  if (minute<10) lcd.print("0");
  lcd.print(minute,DEC);
  if (second %2) lcd.print(" "); else lcd.print(":");
  if (second<20) lcd.print("0");
  lcd.print(second / 2,DEC);
  lcd.print(" "); 
  lcd.setCursor(0,1); // переходим в координаты x=0, y=1 то есть в начало второй строки
  lcd.print("    ");
  lcd.print(day,DEC);
  lcd.print(months[month-1]); // месяцы мне было приятнее нумеровать от 1 до 12, а массив с названиями от 0 до 11
  lcd.print(year,DEC);
}


Button operation

A similar situation with the buttons. The pressed button is considered to be pressed during each run of the program, therefore, it can be processed any number of times in one click. We have to make the program wait for "push-ups" separately. Let's start the main program like this:
int lb=0; // переменная хранит старое значение кнопки
void loop(){
 // main program
  int cb,rb; // определим 2 переменные, для реально нажатой кнопки и для той, которую будет считать нажатой программа
  cb=rb=lcd.button(); // в начале можно считать, что это одна и та же кнопка
  if (rb!=KEYPAD_NONE) showval=1; // переменная указывает, что пока нажата кнопка не надо мигать тому, что настраивается
  if (cb!=lb) lb=cb; // если состояние кнопки изменилось, запоминаем новое, 
    else cb=KEYPAD_NONE; // иначе говорим программе, что все кнопки давно отпущены.


Work with timer


Actually, all the work with a timer consists of two important components:
  • Initialize the timer interrupt mechanism in a mode convenient for us
  • Actually, interrupt handling

Timer initialization

In order to start getting the interrupts we need, we need to configure the processor so that it starts to generate them. To do this, set the registers we need to the desired values. Which registers and which values ​​you need to set, you need to look at ... datasheeton the processor :(. Honestly, I really hoped that this information could be found in the documentation for the Arduino itself, but no, that would be too simple. Moreover, for different processors the series of bit numbers may differ. And I personally came across the fact that an attempt to set the bits in accordance with the datasheet on the neighboring processor led to disastrous results ... But nevertheless, everything is not as sad as it might seem, because for these bits there are also names, they are more or less common for different processors. Therefore use digital s beginnings we will not, only names.

First, let's recall that there are several AVR timers in microcontrollers. Zero is used to calculate delay () values ​​and the like, so we will not use it. Accordingly, we use the first. Therefore, further in the designation of registers a unit will often skip, for setting, say, a second timer, you need to put a deuce in the same place.

All timer initialization must occur in the setup () procedure. It consists of placing values ​​in 4 registers, TCCR1A, TCCR1B, TIMSK1, OCR1A. The first 2 of them are called “control registers A and B of timer-counter 1”. The third is “timer / counter 1 interrupt mask register”, and the last is “counter register A counter 1”.

It is customary to use the following commands for setting bits (it’s clear that there are many options, but these are most often used):
BITE |= (1 << POSITION)
that is, we push “1” on the POSITION bit from right to left and draw a logical “or” between the target and received bytes. When the controller is turned on, the values ​​of all these registers contain 0, so we just forget about zeros. So after executing the following code

A=0;
A |= (1 << 3)


the value of A will be 8.

A lot of timer settings, but we need to get the following from the timer:
  • In order for the timer to switch to CTC operation mode (that is, to the counting mode with reset after coincidence, “Clear Timer on Compare match”), judging by the datasheet, this is achieved by setting the WGM12 bits: 0 = 2, which in itself means setting bits from the second to zero in the value "2", that is, "010", the command TCCR1B |= (1 << WGM12);
  • Since 16 MHz (namely, such a frequency for a quartz resonator on my board) is a lot, choose the maximum possible divider, 1024 (that is, only every 1024th clock will reach our counter), CS12: 0 = 5
  • Make the interrupt arrive when it matches the register A, for this counter TIMSK1 |= (1 << OCIE1A)
  • To indicate, upon reaching which particular value, to cause interrupt processing, this value is placed in the same register A of counter 1 (its entire name is OCR1A), the interrupt by coincidence with which we included in the previous paragraph.


How to calculate how much we need to do the calculations? - It’s easy, if the clock frequency of the quartz resonator is 16 MHz, then when the counter reaches 16000, a second would pass if the division coefficient were 1. Since it is 1024, we get 16000000/1024 = 15625 per second. And everything would be fine, but we need to get values ​​every half second, and 15625 is not divided by 2. So we were wrong before that and we have to take a smaller division coefficient. And the next reduction we have is 256, which gives 62,500 ticks per second or 31,250 in half a second. We have a 16-bit counter, so it can count up to 65536. In other words, we have enough for half a second and a second. We climb into the datasheet, then into the source and fix it on CS12:0=4, and after thatOCR1A = 31249; (as I understand it, one measure goes either to a reset, or else where, so there are tips to reset another one from the received number).

Interrupt handling

The syntax of the interrupt function has changed a bit, now it looks like in the example below. So do not be surprised if somewhere you see a slightly different description of the function name.

Actually, now it consists of the reserved word ISR and an indication of the specific interrupt that this function processes in parentheses. And inside, as you see, this function has nothing fantastic. Even the compulsory RETI, as you see, is automatically inserted by the compiler.

ISR(TIMER1_COMPA_vect) {
  digitalWrite(LEDPIN, !digitalRead(LEDPIN)); // LEDPIN=13. Эта строка мигает светодиодом на плате. Удобно и прикольно :)
  second++;
  if ((second %2) && lastshowval) { // эта и следующие 7 строк нужны только для того, 
    lastshowval = 0;            // чтобы можно было добиться этого забавного эффекта, как на аппаратных часах,
    showval = 0;       // когда в режиме настройки скажем минут, значение настраиваемого параметра мигает
  }
  if (!(second %2) && !lastshowval){   // только при отпущенных кнопках, а пока кнопки нажаты, оно просто горит.
    lastshowval = 1;
    showval = 1;
  }
  if (second>=120) {  // опять мои 120 секунд в минуте. Ну а кому сейчас легко?
    second-=120;
    minute++;
    if (minute>=60){
      minute-=60;
      hour++;
      if (hour>=24){
        hour-=24;
        day++;
        if ( 
          daylarge(day,month,year) // возвращает true если значение дня 
  // больше максимально возможного для этого месяца этого года. 
        ) {
          day=1;
          month++;
          if (month>12){
            month = 1;
            year++;
          }
        }        
      }
    }
  }
}


I hope this article will be useful to someone, because there are not enough detailed instructions on the topic of working with timer interrupts in Russian.

Also popular now: