Programming avr microcontrollers in C. Programming AVR microcontrollers in C. So what is a microcontroller

I decided to write a small introductory article for those who first took up programming microcontrollers and had never been familiar with the C language before. We will not go into details, but a little about everything, in order to get a general idea of ​​​​working with CodeVisionAVR.

More detailed information can be found in English in the CodeVision User Manual, and I also recommend the site http://somecode.ru with video tutorials on C for microcontrollers and the book "How to Program in C" by Deitel, this is the only good book with which I myself started.

Let's start with the fact that no matter what actions we do, in the end, it all comes down to microcontroller firmware. The firmware process itself is as follows: using a certain program, a firmware file is selected, parameters are selected, a button is pressed and the firmware is directly flashed, which, in fact, is a copy. Just like you copy music or documents from a computer to a USB flash drive, the physics of the process is the same.

The firmware itself has the .hex extension and is a set of instructions, in the form of ones and zeros, which is understandable to the microcontroller. Where can I get the firmware? You can download it from electronics sites, or write it yourself. You can write it in special programs called the development environment. Among the best known to me are AVR Studio, IAR, CodeVision, WinAVR… It is impossible to say which of these environments is better or worse, to each his own. We can say that these programs differ mainly in convenience, programming language and price. Within this site, only CodeVision is considered.

We figured out the environment, now let's deal with the process of writing the firmware. In CodeVision, you first need to create a project. It can be created using the code wizard or empty. In any case, you need to select the type of microcontroller used and specify its frequency. When using the wizard, you will be prompted to select initial settings and generate source code with settings. Next, a window will appear in which you can edit this code. Although you can write your source code in notepad and then attach it to the project in the settings.

A source code file is a set of commands in a programming language, CodeVision's task is to translate these commands into binary code, your task is to write this source code. CodeVision understands the C language, source code files have the ".c" extension. But CodeVision has some constructs that are not used in C, for which many programmers do not like it, and the language used is called C similar. However, this does not prevent writing serious projects. A lot of examples, a code generator, a large set of libraries give CodeVision a big plus. The only negative is that it is paid, although there is free versions with limited code.

The source code must contain a header with the type of microcontroller used and the main function. For example, ATtiny13 is used

#include void main(void ) ( ) ;

#include void main(void) ( );

Before the main function, you can connect the necessary libraries, declare global variables, constants, settings. A library is a separate file, usually with a ".h" extension, that already has pre-written code. In some projects, we may need this code, but in others we don’t need it. For example, in one project we use LCD, and in another we do not. You can connect the library for working with the LCD display "alcd.h" as follows:

#include #include void main(void ) ( ) ;

#include #include void main(void) ( );

Variables are areas of memory in which you can put some values. For example, we added two numbers, the result needs to be saved somewhere in order to use it in the future. First you need to declare a variable, i.e. allocate memory for it, for example:
int i=0;
those. we declared the variable i and put the value 0 in it, int is the type of the variable, or in other words, means the size of the allocated memory. Each variable type can only store a certain range of values. For example, int can be written numbers from -32768 to 32767. If you need to use numbers with a fractional part, then the variable must be declared as a float, for characters use the char type.

bit, _Bit 0 or 1 char -128 to 127 unsigned char 0 to 255 int -32768 to 32767 unsigned int 0 to 65535 long int -2147483648 to 2147483647 unsigned long int 0 to 4294967295 float ±1.17 5e- 38 to ±3.402e38

Inside the main function, the main program is already running. After the function is executed, the program will stop, so they make an infinite while loop that spins the same program all the time.

void main(void ) ( while (1 ) ( ) ; ) ;

void main(void) ( while (1) ( ); );

You can write a comment in any part of the source code, it will not affect the operation of the program in any way, but it will help to make notes to the written code. You can comment out a line with two slashes //after that, the compiler will ignore the entire line, or several lines /**/, for example:

/*Basic math operations:*/ int i= 0 ; //declaring the variable i and assigning it the value 0//Addition: i = 2 + 2 ; //Subtraction: i = 2 - 2 ; //after executing this expression, the variable i will be equal to 0// Multiplication: i = 2 * 2 ; //after executing this expression, the variable i will be equal to 4//Division: i = 2 / 2 ; //after executing this expression, the variable i will be equal to 1

/*Basic math operations:*/ int i=0; //declaring the variable i and assigning the value 0 to it //Addition: i = 2+2; //after executing this expression, the variable i will be equal to 4 //Subtraction: i = 2-2; //after executing this expression, the variable i will be equal to 0 //Multiplication: i = 2*2; //after executing this expression, the variable i will be equal to 4 //Division: i = 2/2; //after executing this expression, the variable i will be equal to 1

Often, a program needs to jump from one piece of code to another, depending on the conditions, for this there are if () conditional operations, for example:

if(i>3) //if i is greater than 3, then assign i the value 0 ( i=0; ) /*if i is less than 3, then go to the next code after the condition body, i.e. after brackets ()*/

Also, if can be used in conjunction with else - otherwise

if(i<3) //если i меньше 3, то присвоить i значение 0 { i=0; } else { i=5; //иначе, т.е. если i больше 3, присвоить значение 5 }

There is also a comparison operator "==" it should not be confused with "=" assign. The inverse operation is not equal to "!=", let's say

if(i==3)//if i is 3, set i to 0 ( i=0; ) if(i!=5) //if i is not 5, set i to 0 ( i=0; )

Let's move on to more complex things - functions. Let's say you have a piece of code that is repeated several times. Moreover, this code is quite large in size. Writing it every time is inconvenient. For example, in the program, the variable i is somehow changed, when you press the button 0 and 3 of port D, the same code is executed, which, depending on the value of variable i, turns on the pins of port B.

void main(void ) ( if (PIND.0== 0 ) // check if the button on PD0 is pressed( if (i== 0 ) //if i==0 enable PB0( PORTB.0= 1 ; ) if (i== 5 ) // if i==5 enable PB1( PORTB.1= 1 ; ) ) … if (PIND.3== 0 ) // do the same when checking the PD3 button( if (i== 0 ) ( PORTB.0= 1 ; ) if (i== 5 ) ( PORTB.1= 1 ; ) ) )

void main(void) ( if(PIND.0==0) //check if the button on PD0 is pressed ( if(i==0) //if i==0 enable PB0 ( PORTB.0=1; ) if( i==5) // if i==5 enable PB1 ( PORTB.1=1; ) ) … if(PIND.3==0)// do the same when checking the PD3 button ( if(i==0 ) ( PORTB.0=1; ) if(i==5) ( PORTB.1=1; ) ) )

In general, the code is not very large, but it could be many times larger, so it would be much more convenient to create your own function.
For example:

void i_check() ( if (i== 0 ) ( PORTB.0= 1 ; ) if (i== 5 ) ( PORTB.1= 1 ; ) )

void i_check() ( if(i==0) ( PORTB.0=1; ) if(i==5) ( PORTB.1=1; ) )

void means that the function does not return anything, more on that below i_check() - this is the name of our function, you can call it whatever you like, I called it that way - checking i. Now we can rewrite our code:

void i_check() ( if(i==0) ( PORTB.0=1; ) if(i==5) ( PORTB.1=1; ) ) void main(void) ( if(PIND.0==0 ) //check if the button is pressed on PD0 ( i_check(); ) … if(PIND.3==0) ( i_check(); ) )

When the code reaches the line i_check(); then it will jump inside the function and execute the code inside. Agree, the code is more compact and clearer, i.e. functions help to replace the same code, with just one line. Note that the function is declared outside of the main code, i.e. to the main function. You can say, yes, why do I need this, but studying the lessons you will often come across functions, for example, clearing the LCD screen lcd_clear () - the function does not take any parameters and returns nothing, but clears the screen. Sometimes this function is used almost every other line, so the code saving is obvious.

It looks much more interesting to use a function when it takes values, for example, there is a variable c and there is a sum function that takes two int values. When the main program executes this function, the arguments are already in the parentheses, so "a" will become equal to two, and "b" will become equal to 1. The function will be executed and "c" will become equal to 3.

int c= 0 ; void sum(int a, int b) ( c= a+ b; ) void main(void ) ( sum(2 , 1 ) ; )

int c=0; void sum(int a, int b) ( c=a+b; ) void main(void) ( sum(2,1); )

One of the frequently encountered similar functions is the translation of the cursor on the LCD display lcd_gotoxy(0,0); which, by the way, also takes arguments - x and y coordinates.

Another way to use the function, when it returns a value, now it will no longer be void, let's improve the previous example of the function of adding two numbers:

int c= 0 ; int sum(int a, int b) ( return a+ b; ) void main(void ) ( c= sum(2 , 1 ) ; )

int c=0; int sum(int a, int b) ( return a+b; ) void main(void) ( c=sum(2,1); )

The result will be the same as last time c=3, but note that we are assigning the value of a function to the variable “c”, which is no longer void, but returns the sum of two numbers of the int type. Thus, we are not tied to a specific variable "c", which adds flexibility in using functions. A simple example of such a function is reading ADC data, the function returns the measured value result=read_adc();. This concludes the functions.

Now let's move on to arrays. An array is bound variables. For example, you have a sine table with several points, you will not create variables int sinus1=0; int sinus2=1; etc. an array is used for this. For example, you can create an array of three elements like this:
int sinus=(0,1,5);
square brackets indicate the total number of array elements. You can assign the value of the third element to the variable "c" in this way:
c=sinus;
Please note that the numbering of the array elements starts from zero, i.e. "c" becomes five. This array has no sinus element!!!
An individual element can be assigned a value like this:
sinus=10;

You may have already noticed that there are no string variables in CodeVision. Those. you can't create a variable string hello="hello"; to do this, you will have to create an array of individual characters.

lcd_putchar(hello); lcd_putchar(hello); lcd_putchar(hello);

etc.
It turns out quite cumbersome, here cycles come to the rescue.
For example while loop

while(PINB.0!=0) ( )

While the button is not pressed, do nothing - run an empty loop.

Another version of the for loop

int i; for (i= 0 ; i< 6 ; i++ ) { lcd_putchar(hello[ i] ) ; }

int i; for(i=0;i<6;i++) { lcd_putchar(hello[i]); }

The meaning is exactly the same as for while, only the initial condition i=0 is added and the condition is executed every i++ loop. The code inside the loop is simplified as much as possible.

After you have written your program, the source code is compiled, and if there are no errors, then you will receive the coveted firmware in the project folder. Now you can flash the microcontroller and enjoy the operation of the device.

Do not immediately try to use cycles, arrays and functions in your firmware. Your main task is to make the firmware work, so do it as it is easier for you, do not pay attention to the size of the code. There will come a time when you want to not just write working code, but write it beautifully and compactly. Then it will be possible to delve into the wilds of the C language. For those who want to master everything, I immediately recommend the book "How to Program in C" once again, there are many examples and tasks. Install Visual Studio, create a win32 console application and practice there to your heart's content.

Lesson 0

So, today we are opening a series of lessons on programming microcontrollers of the AVR family.

The following questions will be discussed today:

  1. What is a microcontroller?
  2. Where are microcontrollers used?

Introduction.

Microcontrollers are everywhere. In telephones, washing machines, "smart homes", machine tools in a factory, as well as in countless technical devices. Their widespread use makes it possible to replace complex analog circuits with more compressed digital ones.

So what is a microcontroller?

microcontroller (Micro Controller Unit, MCU) - a microcircuit designed to control electronic devices. You can imagine it as a simple computer that can interact with external devices. For example, open and close transistors, receive data from temperature sensors, display data on lcd screens, etc. . In addition, the microcontroller can perform various processing of input data, just like your personal computer.

That is, microcontrollers open up almost unlimited possibilities for us to control any devices, thanks to the presence of I / 0 ports (input / output ports (output)), as well as the possibility of programming them.

Where are microcontrollers used?

  1. Household appliances (Washing machines, microwave ovens, etc.).
  2. Mobile technology (Robots, robotic systems, communications, etc.).
  3. Industrial equipment (Machine control systems).
  4. Computing equipment (motherboards, peripheral devices control systems).
  5. Entertainment equipment (Children's toys, decorations).
  6. Vehicles (Car engine management systems, security systems)

This is not a complete list of applications for microcontrollers. Often, it is very beneficial to replace the set of control chips with a single microcontroller, due to the simplification of production, lower power consumption.

Getting started with AVR

AVR- a family of Atmel microcontrollers. They have sufficient performance for most amateur devices. They are also widely used in industry.

Schematic diagram of the programmer on the LPT port is shown in the figure. Use the 74AC 244 or 74HC244 (K1564AP5), 74LS244 (K555AP5) or 74ALS244 (K1533AP5) chip as a bus driver.

LED VD1 indicates the recording mode of the microcontroller,

LED VD2 - reading,

LED VD3 - the presence of circuit power.

The circuit takes the voltage required for power supply from the ISP connector, i.e. from the programmable device. This circuit is a revised STK200/300 programmer circuit (added LEDs for ease of operation), so it is compatible with all PC programmer programs that work with the STK200/300 circuit. To work with this programmer, use the program CVAVR

The programmer can be made on a printed circuit board and placed in the LPT connector housing, as shown in the figures:




To work with the programmer, it is convenient to use an LPT port extension cable, which is easy to make yourself (for example, from a Centronix cable for a printer), the main thing is not to "spare" the conductors for the ground (18-25 feet of the connector) or buy it. The cable between the programmer and the microchip to be programmed should not exceed 20-30 cm.

Bitwise operations are based on logical operations, which we have already covered earlier. They play a key role in programming AVR microcontrollers and other types. Almost no program can do without the use of bitwise operations. So far, we have deliberately avoided them to make it easier to learn MK programming.

In all previous articles, we only programmed I / O ports and did not use additional built-in nodes, such as timers, analog-to-digital converters, interrupts, and other internal devices without which the MK loses all its power.

Before moving on to mastering the built-in MK devices, you need to learn how to control or check individual bits of the AVR MK registers. Previously, we performed a check or set the bits of the entire register at once. Let's see what the difference is, and then continue further.

Bitwise Operations

Most often, when programming AVR microcontrollers, we used it, since it has greater clarity compared to and is well understood for novice MK programmers. For example, we need to set only the 3rd bit of port D. For this, as we already know, we can use the following binary code:

PORTD = 0b00001000;

However, with this command, we set the 3rd bit to one, and we reset all the others (0, 1, 2, 4, 5, 6 and 7th) to zero. And now let's imagine the situation that the 6th and 7th digits are used as ADC inputs and at this time a signal from some device arrives at the corresponding outputs of the MK, and we reset these signals using the command above. As a result, the microcontroller does not see them and believes that the signals did not come. Therefore, instead of such a command, we should use another one that would set only the 3rd bit to one, while not affecting the rest of the bits. For this, the following bitwise operation is usually used:

PORT |= (1<<3);

We will analyze its syntax in detail below. And now another example. Let's say we need to check the status of the 3rd bit of the PIND register, thereby checking the state of the button. If this bit is reset to zero, then we know that the button is pressed and then the command code is executed, which corresponds to the state of the pressed button. Previously, we would have used the following notation:

if (pind == 0b00000000)

(any code)

However, with the help of it, we check not a single one, - the 3rd, but all the bits of the PIND register at once. Therefore, even if the button is pressed and the desired bit is reset, but at that time a signal is received on any other port D pin, the corresponding bit will be set to one, and the condition in parentheses will be false. As a result, the code in curly braces will not be executed even when the button is pressed. Therefore, to check the status of an individual 3rd bit of the PIND register, a bitwise operation should be used:

if (~PIND & (1<<3))

(any code)

To work with individual microcontroller bits, the C programming language has in its arsenal, with which you can change or check the state of one or more individual bits at once.

Setting a single bit

To set a single bit, such as port D, a bitwise OR operation is used. That is what we used at the beginning of the article.

PORTD = 0b00011100; // initial value

PORTD = PORTD | (1<<0); применяем побитовую ИЛИ

PORT |= (1<<0); // сокращенная форма записи

PORTD == 0b00011101; // result

This command sets the bit to zero and leaves the rest unchanged.

For example, let's set the 6th bit of port D.

PORTD = 0b00011100; // initial port state

PORT |= (1<<6); //

PORTD == 0b01011100; // result

To write a one to several separate bits at once, for example, zero, sixth and seventh port B the following notation applies.

PORTB = 0b00011100; // initial value

PORTB |= (1<<0) | (1<<6) | (1<<7); //

PORTB == 0b1011101; // result

Resetting (zeroing) individual bits

To reset a single bit, three previously discussed commands are used at once: .

Let's reset the 3rd bit of the PORTC register and leave the rest unchanged.

PORTC = 0b00011100;

PORTC &= ~(1<<3);

PORTC == 0b00010100;

Let's perform similar actions for the 2nd and 4th digits:

PORTC = 0b00111110;

PORTC &= ~((1<<2) | (1<<4));

PORTC == 0b00101010;

Switching the beat

In addition to setting and resetting, a useful command is also used that switches a single bit to the opposite state: one to zero and vice versa. This logical operation is widely used in the construction of various lighting effects, for example, such as a New Year's garland. Consider the example of PORTA

PORTA = 0b00011111;

PORTA ^= (1<<2);

PORTA == 0b00011011;

Change the state of the zero, second and sixth bits:

PORTA = 0b00011111;

PORTA ^= (1<<0) | (1<<2) | (1<<6);

PORTA == 0b01011010;

Checking the status of an individual bit. Let me remind you that checking (as opposed to writing) an I / O port is carried out by reading data from the PIN register.

The most common test is performed by one of two loop statements: if and while. We are already familiar with these operators earlier.

Checking the discharge for the presence of a logical zero (reset) with if

if (0==(PIND & (1<<3)))

If the third bit of port D is cleared, Code1 is executed. Otherwise, Code2 is executed.

Similar actions are performed with and in this form of recording:

if (~PIND & (1<<3))

Checking the discharge for the presence of a logical unit (setting) with if

if (0 != (PIND & (1<<3)))

if (PIND & (1<<3))

The above two loops work similarly, but due to the flexibility of the C programming language, they can be written differently. The operation != means not equal. If the third bit of the PD I/O port is set (one), then Code1 is executed, if not, Code2.

Waiting for a bit reset with while

while (PIND & (1<<5))

Code1 will be executed as long as the 5th bit of the PIND register is set. Resetting it will start executing Code2.

Waiting for the bit to be set while

Here, the syntax of the C language allows you to write code in two of the most common ways. In practice, both types of recording are used.

Microcontrollers (hereinafter referred to as MK) have firmly entered our lives, on the Internet you can find a lot of interesting circuits that are executed on MK. What can not be assembled on the MK: various indicators, voltmeters, household appliances (protection devices, switching devices, thermometers ...), metal detectors, various toys, robots, etc. list can be very long. I saw the first circuit on a microcontroller 5-6 years ago in a radio magazine, and almost immediately turned the page, thinking to myself "I still can't assemble it." Indeed, at that time MK for me was something very complicated and misunderstood device, I had no idea how they work, how to flash them, and what to do with them in case of incorrect firmware. But about a year ago, I first assembled my first circuit on an MK, it was a digital voltmeter circuit on 7 segment indicators, and an ATmega8 microcontroller. It so happened that I bought the microcontroller by accident when I was standing in the radio parts department, the guy in front of me was buying an MK, and I also decided to buy it and try to assemble something. In my articles I will tell you about AVR microcontrollers, I will teach you how to work with them, we will consider firmware programs, we will make a simple and reliable programmer, we will consider the firmware process and, most importantly, problems that may arise not only for beginners.

The main parameters of some microcontrollers of the AVR family:

microcontroller

FLASH memory

RAM memory

EEPROM memory

I/O ports

u supply

Additional parameters of MK AVR mega:

Operating temperature: -55…+125*С
Storage temperature: -65…+150*С
Voltage at the RESET pin relative to GND: max 13V
Maximum supply voltage: 6.0V
Maximum I/O line current: 40mA
Maximum current on the power line VCC and GND: 200mA

Pin assignments for ATmega 8X models

Pin assignments for ATmega48x, 88x, 168x models

Pin assignment for ATmega8515x models

Pin assignments for ATmega8535x models

Pin assignment for ATmega16, 32x models

Pin assignments for ATtiny2313 models

At the end of the article, an archive with datasheets for some microcontrollers is attached.

FUSE setting bits MK AVR

Remember, a programmed fuse is 0, an unprogrammed one is 1. Be careful when setting fuses, an erroneously programmed fuse can block the microcontroller. If you are not sure which fuse you need to program, it is better to flash the MK without fuses for the first time.

The most popular microcontrollers for radio amateurs are ATmega8, followed by ATmega48, 16, 32, ATtiny2313 and others. Microcontrollers are sold in TQFP packages and DIP, I recommend that beginners buy in DIP. If you buy TQFP, it will be more problematic to flash them, you will have to buy or solder the board. their legs are very close to each other. I advise microcontrollers in DIP packages, put them on special sockets, it's convenient and practical, you don't have to solder the MK if you want to reflash it, or use it for another design.

Almost all modern microcontrollers have the ability to in-circuit programming ISP, i.e. if your microcontroller is soldered to the board, then in order to change the firmware we do not have to unsolder it from the board.

6 pins are used for programming:
RESET- MK input
VCC- Plus supply, 3-5V, depends on the MK
GND- Common wire, minus power.
MOSI- MK input (information signal in MK)
MISO- MK output (information signal from MK)
SCK- MK input (clock signal in MK)

Sometimes they also use the XTAL 1 and XTAL2 outputs, quartz clings to these outputs if the MK will work from an external generator, in ATmega 64 and 128 the MOSI and MISO outputs are not used for ISP programming, instead the MOSI outputs are connected to the PE0 leg, and MISO to PE1. When connecting the microcontroller to the programmer, the connecting wires should be as short as possible, and the cable from the programmer to the LPT port should also not be too long.

The marking of the microcontroller may contain incomprehensible letters with numbers, for example, Atmega 8L 16PU, 8 16AU, 8A PU, etc. The letter L means that the MK operates at a lower voltage than the MK without the letter L, usually 2.7V. The numbers after the hyphen or space 16PU or 8AU indicate the internal frequency of the oscillator that is in the MK. If the fuses are set to work from an external quartz, the quartz must be set to a frequency not exceeding the maximum according to the datasheet, this is 20MHz for ATmega48/88/168, and 16MHz for other atmegas.


Top