How it was?
When I had a desire to develop under the Arduino, I ran into several problems:
- Select a model from the list of available
- Attempts to understand what I need besides the platform itself
- Installing and configuring the development environment
- Search and analysis of test cases
- "Disassembly" with a 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 review the solutions I have found and the methods for finding them.
Platform Selection
Before you start programming, you need to buy it at the beginning. And then I ran into the first problem: it turned out that there were quite a few * dooins. Here there is a wide range of Arduino and about the same wide Freeduino and other analogues. As it turned out, there is not much difference what to take. That is, some of these devices are a little faster, others are a little slower, some are cheaper, others are more expensive, but the basic principles of operation do not differ much. Differences appear almost only when working with processor registers, and then I will explain how to avoid problems as much as possible.
')
I chose the Arduino Leonardo platform as the most affordable and available at that time in the online store where I ordered everything. It differs from the rest of the line in that it has only one controller installed on board, which also deals with working with a USB port and performing the tasks we will hang on our device. This has its pros and cons, but it will not work on them during the initial study, so let's forget about them for now. It turned out that it connects to the computer via micro-USB, and not USB-B, as most others seem to be. This surprised me somewhat, but it also made me happy, because I, as the owner of a modern Android device, do not leave home without this cable.
Yes, almost any * duo-compatible piece of iron is fed in several ways, including from the same cable through which it is programmed. Also, one LED on almost all boards is placed directly on the controller board, which allows you to start working with the device immediately after purchase, even without having anything in your hands at all, except a compatible cable.
Range of tasks
I think it’s interesting to understand what can be done on it before writing down something as a piece of iron as such. With Arduino, you’ll realize almost anything. Automation systems, ideas for “smart home”, controllers for controlling something useful, “brains” of robots ... There are a lot of options. And a rather wide range of expansion modules that are extremely easy to connect 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 for myself the most useful LCD screen with a basic set of buttons, without which, in my humble opinion, it would be completely uninteresting to engage in any kind of training projects. The screen was taken
from here , there is also a description of it, and from the given page are links to the manufacturer’s official website.
Formulation of the problem
Once I got used to getting a new tool in my hands, I immediately put myself in some rather complicated and absolutely unnecessary task, bravely solve it, then put the source aside and only then take on something truly complicated and useful. Now I had a very similar screen with a part of a mine from some Hollywood movie screen with all the necessary buttons that I had to learn to work with, and I also wanted to learn how to work with interrupts (otherwise what’s the point of using a controller?) So the first what came to mind was to write a clock. And since the screen size allowed, it is also with the date.
The first steps
So I finally got all the purchased components and I collected them. The screen connector connected to the controller board as a native one, the board was connected to the computer ... And here
this article helped me a lot. I will not repeat the same thing.
Hidden textThe only thing I can say is that recalling youth (or rather the first “project” collected during the study of radio electronics in the Palace of Pioneers — a multivibrator with two LEDs), I found 2 LEDs and corrected the example given in the article and began to blink by them :).
“Second Steps”
The next logical question for me was “how to work with an 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. Just did not say what to do with all this. It turned out that the contents just need to be unpacked into the libraries folder of the development environment.
After that, you can open the example of GuessTheNumber.pde and fill in the board in a manner similar to the example with a flashing LED. However, personally after the firmware, the screen remained evenly luminous and without a single letter. After a brief search for the problem, it turned out that it was necessary to simply turn the potentiometer on the screen board with a screwdriver to set the normal contrast value.
The command set used in the example is in principle sufficient 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 time. And they must do this for sure. Naturally, without the use of the
interrupt mechanism
, no one can guarantee that time is considered with sufficient accuracy. Therefore, the calculation of time just need to leave them. Everything else can be brought into the body of the main program. And we have quite a lot of this “rest” - all the work with the interface. It would be possible to do otherwise, to create a stack of events created including the interrupt handling mechanism, and processed inside the main application, for example, would allow to update the screen no more than once every half a second (or by pressing a button), but I considered it superfluous for such a simple task, since besides redrawing the screen, the processor still has nothing to do. Therefore, all the free time the program re-reads 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 they burned for half a second, like in classic watches, but not sex. But since the screen is being redrawn all the time, it was necessary to somehow determine in which half of a second they should be drawn, and in which - not. The easiest thing was to do 120 seconds per minute and draw colons every odd second.
Flicker
When the screen is constantly redrawn, flickering becomes noticeable. To prevent this, it makes sense not to clear the screen, but to draw a new text over the old one. If the text itself does not change, then there will be no flickering on the screen. Then the time reprint 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); }
Work with buttons
A similar situation with the buttons. The pressed button is listed pressed for each run of the program, so for one press it can be processed any number of times. We have to force the program to 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, that all work with the timer consists of two important components:
- Initialization of the interrupt mechanism from the timer in a convenient mode
- Actually, interrupt handling
Timer initialization
In order to start getting the interrupts we need, we need to configure the processor so that it starts generating them. To do this, we need to set the registers we need to the required values. What kind of registers and exactly which values ​​need to be set, you need to look at the
datasheet on the processor :(. Frankly, I strongly hoped that this information could be found in the documentation for the Arduino itself, but no, that would be too easy. Not only that. , for different processors, the bit numbers may differ, and I personally came across the fact that the attempt to set the bits in accordance with the datasheet on the neighboring processor led to disastrous results ... But still, everything is not as sad as it may seem, since of these bits to have more names, they are more or less common to different processors. Therefore, use numerical values, we will not only names.
First, remember that AVR microcontrollers have several timers. Zero is used to calculate the values ​​of delay () and similar things, so we will not use it. Accordingly, we use the first. Therefore, further in the designation of registers will often skip unit, to configure, say the 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 the values ​​in 4 registers, TCCR1A, TCCR1B, TIMSK1, OCR1A. The first 2 of them are called “timer-counter 1 control registers A and B”. The third one is “timer / counter 1 interrupt mask register”, and the last one is “comparison register A of counter 1”.
Commands for setting bits are usually used as follows (of course, there are many options, but these are most often used):
BITE |= (1 << POSITION)
that is, slide “1” on the POSITION bit right to the 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 the zeros. Thus, after executing the following code
A=0; A |= (1 << 3)
A value will become 8.
The options for setting the timer are lots, but we need to get the following from the timer:
- In order for the timer to switch to CTC operation mode (that is, to account mode with reset after a match, “Clear Timer on Compare match”), judging by the datasheet, this is achieved by setting the WGM12 bits: 0 = 2, which in itself means setting the bits from second to zero to “2”, that is, “010”, command
TCCR1B |= (1 << WGM12)
; - Since 16 MHz (this is the frequency of the 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
- To make so that interruption came at coincidence with register A, for this counter
TIMSK1 |= (1 << OCIE1A)
- When this value is reached, to specify the interrupt processing, this value is placed in the same register A of counter 1 (its whole name is OCR1A), the interruption, which we included in the previous paragraph.
How to calculate how much we need to calculate? - It is easy, if the clock frequency of the quartz resonator is 16 MHz, then when the counter reaches 16000, a second would have passed if the division factor was 1. Since it is 1024, then we get 16000000/1024 = 15625 per second. And everything would be fine, but we need to get the values ​​every half second, and 15625 is not divided by 2. So we made a mistake before and we will have to take a smaller division factor. And the next one is 256, which gives 62500 ticks per second or 31250 in half a second. We have a 16-bit counter, so it can count to 65,536. 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 then
OCR1A = 31249;
(as I understand it, one clock goes either to a reset, or somewhere else, so there are tips to reset one more time from the received figure).
Interrupt handling
The syntax of the interrupt handling function has changed somewhat, now it looks like in the example below. So don't be surprised if you see a slightly different description of the function name somewhere.
Actually, it now consists of the reserved word ISR and an indication of a specific interrupt, which this function handles in brackets. And inside this function, as you can see, there is nothing fantastic. Even the mandatory RETI as you can see for us automatically inserts 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 how to work with the timer interruptions in Russian.