How to replace the pinb 1. Ports of the AVR microcontroller. Port A Input Data Register - PINA

Let's consider examples of port settings in CodeVision AVR
For example DDRB = 0x02; this entry means that the second leg of port B is configured as an output, but where did this number come from ????

To begin with, let's translate this entry into a more understandable form for us:
the prefix 0x means that the number is written in hexadecimal notation, in order to easily understand its essence, you need to translate it into the binary system. The Windows calculator from a set of standard programs will help us in this, we immediately transfer it to the programmer mode.

We switch it to the hexadecimal system mode (tick HEX), and enter our number 0x02 just as 2.

now we do not press any equals, etc. just switch to the binary number system (tick Bin)

We got the number 10. What does it mean? Our ATmega8 has 8 legs on port B (circled in the picture)

so if you represent the number 10 as 00000010, it will mean that only the second leg is configured as an output, the rest as an input.

PORTB.7 PORTB.6 PORTB.5 PORTB.4 PORTB.3 PORTB.2 PORTB.1 PORTB.0
0 0 0 0 0 0 1 0

It should be noted here that if instead of
DDRB = 0x02;
write down
DDRB = 0b00000010;
then it will work too, i.e. these are equivalent records of the same number, in different number systems.

With DDRB sorted out 1-output, 0-input, and what does it mean
PORTB = 0x01;
here the principle is the same, but:
if a leg is configured as an output and the PORTB value is equal to one on the same leg, then the leg after firmware will be turned on by default (i.e. there will be voltage on it), if 0, then the leg will be turned off. In the first lesson, we could replace the PORTB.0 = 1; by writing PORTB = 0x01; and got the same result.

In fact, the following constructions are the correct solution to enable / disable the leg from the point of view of the C language:

PORTD | = (1<<3); //включить ножку 3 порта D PORTD |= (1<<2)|(1<<5)|(1<<6); //включить ножки 2, 5, 6 порта D PORTD &= ~(1<<2); //выключить ножку 2 PORTD &= (~((1<<0)|(1<<2)|(1<<4))); //выключить ножки 0, 2, 4

If a leg is configured as an input and the PORTB value is equal to one on the same leg, then a pull-up resistor will be connected to the leg to eliminate noise. If you don't need an internal resistor, just set 0 on this leg.

I hope everything is clear, if you write anything, ask questions.

When programming microcontrollers, you constantly have to work with bits. Set them, reset, check their presence in a particular register. There are a number of commands in AVR assembler for this purpose. Firstly, this is a group of instructions for operations with bits - they are designed to set or clear bits in various registers of the microcontroller, and secondly, a group of control transfer commands - they are designed to organize program branches. Naturally, there are no such commands in the C language, so novice programmers often have a question, how to work with bits in C. We are going to analyze this topic now.

There are 6 operators in C for manipulating bits. They can be applied to any signed or unsigned integer variable type.

<< - сдвиг влево
>> - shift right
~ - bitwise inversion
| - bitwise OR
& - bitwise AND
^ - bitwise exclusive OR

_______________ shift left<< _______________

Shifts a number n places to the left. In this case, the most significant n bits disappear, and the least significant n bits are filled with zeros.


unsigned char tmp = 3; // 0b00000011
tmp = tmp<< 1;
// now the tmp variable contains the number 6 or 0b00000110

Tmp = tmp<< 3;
// now the tmp variable contains the number 48 or 0b00110000

Expressions in which an operation is performed on a variable, and then the result of the operation is assigned to the same variable, can be written shorter, using compound operators.

Tmp = 7; // 0b00000111
tmp<<= 2; // shorthand version
// now the tmp variable contains the number 28 or 0b00011100

The operation of shifting to the left by n bits is equivalent to multiplying the variable by 2 n.

_______________ shift right >> _______________

Shifts a number n digits to the right. The least significant n bits are lost in this case. The filling of the most significant n bits depends on the type of the variable and its value. The most significant n bits are filled with zeros in two cases - if the variable is of unsigned type or if the variable is signed and its current value is positive. When the variable is signed and its value is negative, the most significant bits are filled with ones.

An example for an unsigned variable

unsigned char tmp = 255; // 0b11111111
tmp = tmp >> 1;
// now the tmp variable contains the number 127 or 0b01111111

Tmp >> = 3; // shorthand version
// now the tmp variable contains the number 15 or 0b00001111

Example for a signed variable

int tmp = 3400; // 0b0000110101001000
tmp >> = 2;
// now the variable contains the number 850 or 0b0000001101010010

Tmp = -1200; // 0b1111101101010000
tmp >> = 2;
// now the tmp number is -300 or 0b1111111011010100
// you see - the two most significant digits are filled with ones

Shift to the right by n bits is equivalent to dividing by 2 n. However, there are some nuances. If the lost low-order bits contained ones, then the result of such a “division” is rough.

For example 9/4 = 2.5 a 9 >> 2 (1001 >> 2) equals 2
11/4 = 2.75 and 11 >> 2 (1011 >> 2) equals 2
28/4 = 7 and 28 >> ​​2 (11100 >> 2) equals 7

In the second case, the error is larger, because both low-order bits are ones. In the third case, there is no error, because the lost bits are zero.

_______________ bitwise inversion ~ _______________

Inverts the number bit by bit. The digits in which there were zeros are filled with ones. The digits in which there were ones are filled with zeros. The bitwise inversion operator is a unary operator, that is, it is used with one operand.

unsigned char tmp = 94; // 0b01011110
tmp = ~ tmp;
// now the tmp variable contains the number 161 or 0b10100001

Tmp = ~ tmp;
// now in tmp again the number 94 or 0b01011110

_______________ bitwise OR | ______________

Operator | performs a logical OR operation between the corresponding bits of the two operands. The result of a logical OR operation between two bits will be 0 only if both bits are equal to 0. In all other cases, the result will be 1. This is illustrated in the truth table.

Operator | usually used to set the given bits of a variable to one.

Tmp = 155
tmp = tmp | 4; // set the second bit of the tmp variable to one

155 0b100110 11
4 0b00000 1 00
159 0b100111 11

Using decimal numbers to set the bits is pretty awkward. It is much more convenient to do this using the left shift operation.<<.


tmp = tmp | (1<<4); // set the fourth bit of the tmp variable to one

We read from right to left - shift one four digits to the left, OR between the received number and the value of the tmp variable, assign the result to the tmp variable.


You can set several bits to one like this

Tmp = tmp | (1<<7)|(1<<5)|(1<<0);
// set the seventh, fifth and zero bits of the tmp variable to one

With the compound assignment operator | =, you can make the notation more compact.

Tmp | = (1<<4);
tmp | = (1<<7)|(1<<5)|(1<<0);

_______________ bitwise AND & _______________

The & operator performs a logical AND operation between the corresponding bits of the two operands. The result of a logical AND operation between two bits will be 1 only if both bits are equal to 1. In all other cases, the result will be 0. This is illustrated in the truth table.

The & operator is commonly used to zero out one or more bits.

Tmp = 155;
tmp = tmp & 247; // zero the third bit of the tmp variable

155 0b10011 011
&
247 0b1111 0 111
147 0b10010 011

You see, the third bit has become 0, and the rest of the bits have not changed.

Zeroing bits using decimal digits is inconvenient. But you can make your life easier by using operators<< и ~

Tmp = 155;
tmp = tmp & (~ (1<<3)); // zero the third bit

1<<3 0b00001 000
~(1<<3) 0b11110 111
tmp & (~ (1<<3)) 0b10011 011 & 0b11110 111
result 0b10010 011

We read from right to left - shift one three digits to the left, invert the resulting number, perform the & operation between the value of the tmp variable and the inverted number, assign the result to the tmp variable.


You can zero a few bits like this

tmp = tmp & (~ ((1<<3)|(1<<5)|(1<<6))); // zero out the third, fifth and sixth bits



Using the compound assignment operator & =, you can write the expression more compactly

Tmp & = (~ ((1<<3)|(1<<5)|(1<<6)));

How to check if a bit is set in a variable?
You need to zero all the bits, except for the check, and then compare the resulting value with zero

if((tmp & (1<<2)) != 0){
// block will be executed only if set

}

if((tmp & (1<<2)) == 0){
// block will be executed only if not set
// second bit of tmp variable

}

_______________ bitwise exclusive OR ^ _______________


The ^ operator performs a logical exclusive OR operation between the corresponding bits of the two operands. The logical exclusive OR will result in 0 if the bits are equal. In all other cases, the result will be 1. This is illustrated in the table of truth.

The ^ operator is not used as often as the other bitwise operators, but there is work to be done for it too. For example, it can be used to invert one or more bits of a variable.


tmp = 155;
tmp = tmp ^ 8; // invert the fourth bit with tmp

155 0b10011 011
^
8 0b0000 1 000
147 0b10010 011

The fourth bit changed its meaning to the opposite, and the rest of the bits remained unchanged.

Tmp = tmp ^ 8; // again invert the fourth bit with tmp

147 0b10010 011
^
8 0b000 0 1 000
155 0b10011 011

You see, the fourth bit has changed its meaning to the opposite again.

It is much more convenient to write an expression this way.

Tmp = tmp ^ (1<<3); // invert the third bit with tmp

And so it is convenient and compact

Tmp ^ = (1<<4); // invert the fourth bit

Can invert multiple bits at the same time

Tmp ^ = ((1<<4)|(1<<2)|(1<<1)); // invert 4,2 and 1 bits

Bitwise exclusive OR has another interesting property... It can be used to swap the values ​​of two variables. This usually requires a third variable.


tmp = var1;
var1 = var2;
var2 = tmp;

But using the ^ operator, you can rearrange the values ​​like this:

var1 ^ = var 2;
var 2 ^ = var 1;
var 1 ^ = var 2;

Pure magic, although, to be honest, I have never used such a technique.

________________ Directive #define__________________


Now we know how to set, zero and invert bits, we know how to check if a bit is set or not. The above expressions are quite cumbersome, but with the help of the #define preprocessor directive, they can be made more pleasant.

The #define directive is used to assign symbolic names to constants and macros. The use of symbolic names makes the program more modifiable and portable.

For example, you use a constant in the program text, and suddenly you need to change its value. If it occurs in only three places, then you can correct it manually, but what if it occurs in fifty lines? Not only will the fix take a lot of time, but it is also easy to make a mistake in this case. This is where the #define directive comes in handy. At the beginning of the program, the symbolic name of the constant is specified, which is used throughout the program. If we need to change this value, this is done in just one place. And before compilation, the preprocessor will substitute its value in all expressions instead of the name of the constant.

Microcontroller programming is inextricably linked with its hardware and most often with the external harness. Take, for example, buttons - by polling them in our program, we refer to the real outputs of the microcontroller. What if we suddenly needed to use the button polling program in a different circuit, where the buttons are connected to other pins? We'll have to fix the program. Again, by specifying a symbolic name for the corresponding pins using the #define directive, modifying the program will be as easy as shelling pears


Example:

#include "iom8535.h"

// port to which the buttons are connected
#define PORT_BUTTON PORTA
#define PIN_BUTTON PINA
#define DDRX_BUTTON DDRA

// pins to which the buttons are connected
#define DOWN 3
#define CANCEL 4
#define UP 5
#define ENTER 6

int main ()
{
// configure the port for input,
// and turn on the pull-up resistors

DDRX_BUTTON = 0;
PORT_BUTTON = 0xff;

When specifying a symbolic name, you can also use the expressions

#define MASK_BUTTONS ((1<

example of use :
tmp = PORTB & MASK_BUTTONS;

When using #define, don't spare parentheses to clearly define the sequence in which expressions are evaluated!

Some expressions can be disguised as "functions".

#define ADC_OFF () ADCSRA = 0

example of use:
ADC_OFF ();

You can use multi-line definitions by using the \ character at the end of each line.

#define INIT_Timer () TIMSK = (1<TCCR0 = (1<TCNT0 = 0; \
OCR0 = 0x7d

example of use:
INIT_Timer ();

Well, the most powerful use of the #define directive is setting macros (or just macros). Here's how you can use #define to set macros for the bit operations discussed earlier.

#define SetBit (reg, bit) reg | = (1<#define ClearBit (reg, bit) reg & = (~ (1<#define InvBit (reg, bit) reg ^ = (1<#define BitIsSet (reg, bit) ((reg & (1<#define BitIsClear (reg, bit) ((reg & (1<

example of use:

SetBit (PORTB, 0); // set port B to zero
InvBit (tmp, 6); // invert the sixth bit of the tmp variable


if(BitIsClear (PIND, 0)) ( // if the zero bit in the PIND register is cleared
… .. // execute the block
}

Before compilation, the preprocessor will replace these lines with the previously declared expressions, substituting the appropriate arguments in them.

Macros are very powerful, but you need to use them carefully. Here are the most common rakes that are written about in all programming textbooks.

Let's define a macro that calculates the square of a number:

#define SQUARE (x) x * x

expression
tmp = SQUARE (my_var);
will give the correct result.

What happens if you use the expression my_var + 1 as an argument to the macro definition

tmp = SQUARE (my_var +1);

The preprocessor will replace this line with

tmp = my_var + 1 * my_var +1;

and this is not at all the result that we expect.

To avoid such errors, do not skimp on parentheses when declaring macros!

If you declare a macro like this

#define SQUARE (x) ((x) * (x))

expression
tmp = SQUARE (my_var +1);
will give the correct result, because the preprocessor will replace this line with
tmp = ((my_var + 1) * (my_var +1));

we write them to the project folder, and at the beginning of the main.c file we write #include "bits_macros.h"

Bit

Read / Write

Initial value

· Bit 7 - Enable all interrupts. To enable interrupts, this bit must be set to 1. The enablement of a specific interrupt is controlled by the interrupt mask registers EIMSK and TIMSK. If this bit is cleared (= 0), then none of the interrupts are processed. The bit is cleared by hardware after an interrupt occurs and is set to enable the interrupt later with the RETI instruction.
· Bit 6 - Save copy bit. Bit copy commands BLD and BST use this bit as source and destination for bit operations. The BST instruction copies the bit of the general register to the T bit, and the BLD instruction copies the T bit to the bit of the general register.
· Bit 5 - Half-carry flag. It indicates the transfer between tetrads when performing a number of arithmetic operations.
· Bit 4 - Sign bit. Bit S has the value of the result of the exclusive OR (N (+) V) operation on the negative (N) and two's complement overflow flags (V).

· Bit 3 - Two's complement of the overflow flag. It supports two's complement arithmetic.
· Bit 2 - Negative value flag. This flag indicates a negative result of a number of arithmetic and logical operations.
· Bit 1 - Flag of zero value. This flag indicates the zero result of a number of arithmetic and logical operations.
· Bit 0 - Carry flag. This flag indicates carry for arithmetic and logical operations.

The AT90S8535 microcontroller has 4 parallel I / O ports A, B, C, and D.
Port A is an 8-bit bidirectional port. Interaction with port A is carried out through three registers in the data memory input / output space: data register - PORTA, $ 1B ($ 3B), data direction register - DDRA, $ 1A ($ 3A), input data register - PINA, $ 19 ($ 39 ). The PINA register is read-only, while the PORTA and DDRA registers are read-write. The PINA register is not a register in the full sense of the word. Accessing it provides a read of the physical state of each port pin. Port A also serves to input analog A / D signals.

Port A Data Register -PORTA

Bit

Read / Write

Initial value

Port A Data Direction Register -DDRA

Bit

Read / Write

Initial value

Port A Input Data Register -PINA

Bit

Read / Write

Initial value

Port B is an 8-bit bidirectional I / O port. As well as port A, interaction with port B is carried out through three registers in the data memory input / output space: data register - PORTB, $ 18 ($ 38), data direction register - DDRB, $ 17 ($ 37) and input data register - PINB, $ 16 ($ 36). The PINB register is read-only. The PINB register is not a register in the full sense of the word. Accessing it provides a read of the physical state of each port pin. Port B pins can perform alternative functions as shown in Table 1. 2.1.

Table 2.1. Port B pinout alternatives

Port pin

Alternative function

T0 - Timer / Counter 0 clock input

T1 - Timer / Counter 1 clock input

AIN0 - positive terminal of the comparator

AIN1 - negative pin of the comparator

- slave SPI selection input

MOSI - set master output / slave SPI input

MISO - setting the master input / slave SPI output

SCK - SPI clock signal

When using pins for alternative functions, the PORTB, DDRB registers must be set appropriately.

Port data registerBPORTB

Bit

Read / Write

Initial value

Port B Data Direction Register -DDRB

Bit

Read / Write

Initial value

Port B Input Data Register -PINB

Bit

Read / Write

Initial value

Port C is an 8-bit bidirectional I / O port. As with ports A and B, interaction with port C is carried out through three registers in the data memory input / output space: data register - PORTC, $ 15 ($ 35), data direction register - DDRC, $ 14 ($ 34) and input data register - PINC, $ 13 ($ 33). The PINC register is read-only, and the PORTC and DDRC registers are read and write. The PINC register is not a register in the full sense of the word. Accessing it provides a read of the physical state of each port pin.
Port C has only two pins that can perform alternative functions: the PC6 and PC7 pins perform the TOSC1 and TOSC2 functions of Timer / Counter 2.

Port data registerCPORTC

Bit

Read / Write

Initial value

Port C Data Direction Register -DDRC

Bit

Read / Write

Initial value

Port C Input Data Register -PINC

Bit

Read / Write

Initial value

Port D is an 8-bit bidirectional I / O port. As with ports A, B and C, interaction with port D is carried out through three registers in the data memory input / output space: data register - PORTD, $ 12 ($ 32), data direction register - DDRD, $ 11 ($ 31) and input data register - PIND, $ 10 ($ 30). The PIND register is readable, and the PORTD and DDRD registers are read and write. The PIND register is not a register in the full sense of the word. Accessing it provides a read of the physical state of each port pin.
Port D pins can perform alternative functions, as shown in table. 2.2.

Table 2.2. Port D Pin Alternate Functions

Port pin

Alternative function

RxD - UART receiver input

TxD - UART transmitter output

INT0 - external interrupt 0 input

INT1 - external interrupt 1 input

OC1B - output of comparison of output B of timer / counter 1

OC1A - comparison pin of output A of timer / counter 1

ICP - Timer / Counter Capture Trigger Input 1

OC2 - output comparison of timer / counter 2 output

When using pins for alternative functions, the PORTD, DDRD registers must be set appropriately.

Port data registerDPORTD

Bit

Read / Write

Initial value

Port Data Direction RegisterDDDRD

Bit

Read / Write

Initial value

Port Input Data RegisterDPIND

Bit

Read / Write

Initial value

Since the work in question is the first one, in order to acquire the skills of working with the laboratory complex by students, all students first do the same work. From their workplaces, they enter into the PC the same problem of subtracting the number 3 from the number 5, given in paragraph 1.5.3.1. After compiling the program, it is written into the microcontroller of the workplace and its work is demonstrated to the teacher.
After such an acquaintance with the complex, the student proceeds to the individual assignment. If there is time, the teacher can complicate the individual task.

So you are reading this now and you think - memory, registers, stack and so on are good. But you can't feel it, you can't see it. Is that in the simulator, but I can do it on the dolphi with the same condition. Where is the meat !!!

In other courses there, almost from the first lines, they do something significant - they blink a diode and say that this is our Hello World. And here? Where ???

Yes, yes, yes, I understand you. Moreover, you probably already ran to competitors and blinked a diode at them;)))) Nothing, forgivable.

I just didn’t want to stop at the same blinking of didodiks, but progress requires a clear understanding of the foundations and principles - a powerful theoretical base. But now it was the turn of practice.

We've already covered the ports, you already have a program template, so let's start right away.

Tools
Working with ports usually means working with bits. It is to set a bit, clear a bit, invert a bit. Yes, of course, there are convenient commands in assembler

cbi / sbi, but they work exclusively in a small address range (from 0 to 1F, so let's first write universal macros in order to use them in the future and not worry about the address space.

Macros will be called:

  • SETB byte, bit, temp
  • CLRB byte, bit, temp
  • INVB byte, bit, temp, temp2

Moreover, when working with the bits of the lower PBB (0-1F address), the value of the TEMP parameter can be omitted - it will not be substituted anyway. Except for the inversion commands - there intermediate registers will be needed by anyone.

It is also useful to have a group of case-free macros. More precisely, they will use registers, but first save them on the stack. They can be shoved thoughtlessly like ordinary commands. But they will take longer to run and will require RAM.

  • SETBM byte, bit
  • CLRBM byte, bit
  • INVBM byte, bit

Here is their source code. As you can see, the conditions of the macro language are actively used, which makes it possible to create universal macros. The compiler will figure out which version to stick it with :)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 < 0x20 SBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ORI R17,1<<@1 OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ORI R17,1<<@1 STS @0,R17 POP R17 .endif .endif .ENDM ;SET BIT with REG .MACRO SETB .if @0 < 0x20 ; Low IO SBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ORI @2,1<<@1 OUT @0,@2 .else ; Memory LDS @2,@0 ORI @2,1<<@1 STS @0,@2 .endif .endif .ENDM ;............................................................. ;Clear BIT with REG .MACRO CLRB .if @0 < 0x20 ; Low IO CBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ANDI @2,~(1<<@1) OUT @0,@2 .else ; Memory LDS @2,@0 ANDI @2,~(1<<@1) STS @0,@2 .endif .endif .ENDM ;Clear BIT with STACK .MACRO CLRBM .if @0 < 0x20 CBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ANDI R17,~(1<<@1) OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ANDI R17,~(1<<@1) STS @0,R17 POP R17 .endif .endif .ENDM ;............................................................. .MACRO INVB .if @0 < 0x40 IN @2,@0 LDI @3,1<<@1 EOR @3,@2 OUT @0,@3 .else LDS @2,@0 LDI @3,1<<@1 EOR @2,@3 STS @0,@2 .endif .ENDM .MACRO INVBM .if @0 < 0x40 PUSH R16 PUSH R17 IN R16,@0 LDI R17,1<<@1 EOR R17,R16 OUT @0,R17 POP R17 POP R16 .else PUSH R16 PUSH R17 LDS R16,@0 LDI R17,1<<@1 EOR R17,R16 STS @0,R17 POP R17 POP R16 .endif .ENDM ;= End macro.inc ========================================

; = Start macro.inc =======================================; SET BIT with stack .MACRO SETBM .if @ 0< 0x20 SBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ORI R17,1<<@1 OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ORI R17,1<<@1 STS @0,R17 POP R17 .endif .endif .ENDM ;SET BIT with REG .MACRO SETB .if @0 < 0x20 ; Low IO SBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ORI @2,1<<@1 OUT @0,@2 .else ; Memory LDS @2,@0 ORI @2,1<<@1 STS @0,@2 .endif .endif .ENDM ;............................................................. ;Clear BIT with REG .MACRO CLRB .if @0 < 0x20 ; Low IO CBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ANDI @2,~(1<<@1) OUT @0,@2 .else ; Memory LDS @2,@0 ANDI @2,~(1<<@1) STS @0,@2 .endif .endif .ENDM ;Clear BIT with STACK .MACRO CLRBM .if @0 < 0x20 CBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ANDI R17,~(1<<@1) OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ANDI R17,~(1<<@1) STS @0,R17 POP R17 .endif .endif .ENDM ;............................................................. .MACRO INVB .if @0 < 0x40 IN @2,@0 LDI @3,1<<@1 EOR @3,@2 OUT @0,@3 .else LDS @2,@0 LDI @3,1<<@1 EOR @2,@3 STS @0,@2 .endif .ENDM .MACRO INVBM .if @0 < 0x40 PUSH R16 PUSH R17 IN R16,@0 LDI R17,1<<@1 EOR R17,R16 OUT @0,R17 POP R17 POP R16 .else PUSH R16 PUSH R17 LDS R16,@0 LDI R17,1<<@1 EOR R17,R16 STS @0,R17 POP R17 POP R16 .endif .ENDM ;= End macro.inc ========================================

Over time, when you write in assembler, there are a lot of such macros. They are placed in a separate file and simply connected to any of your projects, and writing code becomes easy and enjoyable.

But back to the code,
Let's blink the LED light, then finally?

Sure, not a problem. LEDs are already mounted on the demo board, why not use them? They hang on the pins of the PD4, PD5, PD7 port. You just need to put on jumpers.

; Internal Hardware Init ====================================== SETB DDRD, 4, R16; DDRD.4 = 1 SETB DDRD, 5, R16; DDRD.5 = 1 SETB DDRD, 7, R16; DDRD.7 = 1; End Internal Hardware Init ===================================

It remains to light our diodes. They are lit by writing bits to the PORT register. We already do this in the main section of the program.

; Main ================================================ ======== Main: SETB PORTD, 4, R16; Lit up LED1 SETB PORTD, 7, R16; Light up LED3 JMP Main; End Main =============================================== =====

We compile, you can run it in the tracer, you will immediately see how the bits change. We are flashing ... and after clicking on RESET and unloading the bootloader (if of course you have) you will see the following picture:


And for version II


In! Current is boring. Let's blink them.

Let's replace just our macros.

; Main ================================================= ======== Main: SETB PORTD, 4, R16; Lit LED1 INVB PORTD, 7, R16, R17; Inverted LED3 JMP Main; End Main =============================================== =====

They lit it up, flashed it ...

But figs - both are on, but one is a little dimmer. It actually flickers, but very very fast. If you poke an oscilloscope into the PD7 pin, you will see that the level changes there with a breakneck frequency:


What to do? Obviously slow down. How? The easiest way, which is practiced in the vast majority of tutorials and quick starts, is dull delay. Those. get a code like:

; Main ================================================= ======== Main: SETB PORTD, 4, R16; Lit LED1 INVB PORTD, 7, R16, R17; Inverted LED3 RCALL Delay JMP Main; End Main =============================================== =====; Procedure ================================================= === .equ LowByte = 255 .equ MedByte = 255 .equ HighByte = 255 Delay: LDI R16, LowByte; We load three bytes LDI R17, MedByte; Our excerpt LDI R18, HighByte loop: SUBI R16,1; Subtract 1 SBCI R17.0; Subtract only C SBCI R18.0; Subtract from BRCC Loop only; If there is no carry - branch RET; End Procedure ===============================================

We asked for it, launched it ... Oh yes, now the blinking will be noticeable.

With the parameters 255.255.255, the exposure time at 8MHz will be about 2.1 seconds. You can increase the bit delay by a few more bytes. Then you can charge at least an hour.

But this method is flawed, now I will show you why.

Let's add a button. Let LED3 flash in reverse. And we will make it so that when the button is pressed, LED1 is on for us, and when it is released, LED2 is on.

Take clock A as a button and connect it to the PD6 port.


For version II, the same is true. Just take the button from the group of buttons and connect the PD6 pin of the controller to the COL1 pin on the button panel.

Pay only attention to the jumpers that are on the jumpers of the button field. Blue are like that. And also one inconspicuous black jumper, which is indicated by the right arrow. It connects the far left column of buttons to ground. Pounces on pins GND and ROW1. Everything is signed on the board.

Checking the button is done by the SBIC command, but first it must be initialized. Make DDR = 0, PORT = 1 - pullup input.

Add these lines to the Internal Hardware Init section:

; Main ================================================= ======== Main: SBIS PIND, 6; If the button is pressed - transition RJMP BT_Push SETB PORTD, 5; Light up LED2 CLRB PORTD, 4; Extinguish LED1 Next: INVB PORTD, 7, R16, R17; Inverted LED3 RCALL Delay JMP Main BT_Push: SETB PORTD, 4; Light up LED1 CLRB PORTD, 5; Extinguish LED2 RJMP Next; End Main =============================================== =====

Well cho, it works. The button is pressed - the diodes are changing. The third winks cheerfully. But there is a zapadlo:

The program is slowing down! I pressed the button, but the picture did not change, I have to wait, hold it ... Why? And this is because of our redlocking.

Do you remember I told you that stupid delays in which MK does nothing is hellish evil? Here! Now you are convinced of this yourself. Well, evil must be fought. How? Well, at that I also said - to do a continuous loop with flags. At least add useful work to our delay.

Hurdy-gurdy
Now I will show you how you can make a digital organ. Do you remember how it works?

There is a drum with protruding nails and springs in different tones. The nails rotate, the springs pull - they clink. It turns out raskolbasny Mouzon. And what if we unfold our hurdy-gurdy into a tape. Do not nails look like 1s? ;))))

The tape will be a counter that counts from zero, say, to FF.FF.FF.FF and then back to zero or whatever value, as much as necessary and we will do it. And our subroutines will play the role of springs, clinging to the right places - comparing their constant with the current value of the counter.

Coincided? We make "DRYN!"

It remains only to register on the time cycle of our organ where and what should be launched. And here there is one very convenient feature - to build cyclic sequences, we just need to catch one bit.

Let's say the organ counts from 0 to 1000, and we need to blink the diode 10 times. It is not necessary to stick in 10 handlers with different values. One is enough, but for it to catch the value ** 10. Everything else is not important to us. And it will work on 0010, 0110, 0210, 0310, 0410, 0510, 0610, 0710, 0810, 0910. More frequent intervals are also divided as we need, it is enough to get into another category. Here you just need to remember to cut off the high-order bits so that they don't get in the way.

Let's get started. First, let's create our counter in the data segment:

LDS R16, CCNT LDS R17, CCNT + 1 LDS R18, CCNT + 2 LDS R19, CCNT + 3

That's it, now in R16 the least significant byte of our counter, and in R19 the oldest.

The registers can be pushed onto the stack beforehand, but I'd rather give you another piece of advice - when you write a program, think over the algorithm so that registers are used as a solid TEMP data of which is relevant only here and now. And what will happen to them in the next procedure is no longer important - everything that needs to be saved in the RAM.

You can do it like this:

LDI R20.1; We need one CLR R15; And also zero. ADD R16, R20; Add 1 if the register is 255, then it will be C ADC R17, R15; Add 0 + C ADC R18, R15; Add 0 + C ADC R19, R15; Add 0 + C

I had to spend two more registers to store the constants of our addition. All from the fact that the AVR is not able to add registers with an immediate number. But he knows how to subtract.

I have already shown that R - (- 1) = R + 1, but no one forbids us to arrange the same technique here - to make addition through subtraction.

1 2 3 4 SUBI R16, (- 1) SBCI R17, (- 1) SBCI R18, (- 1) SBCI R19, (- 1)

SUBI R16, (- 1) SBCI R17, (- 1) SBCI R18, (- 1) SBCI R19, (- 1)

Will give us the increment of the four-byte number R19: R18: R17: R16

Now I'll show you some integer magic
Why will this work? Take a look yourself:

SUBI R16, (- 1) is, in fact, R16 - 255, and in almost all cases, it will give us a loan from the next category - C. And in the register the number will remain for which is greater.

Those. see how this math works, remember the number in additional codes. I'll show you with a four-row decimal example. We have ONLY FOUR DISCHARGES, no more, no less. A negative number is 0-1 right? OK.

1 C 0000-1 = 9999 + C

Those. we sort of took away 1 from the five-digit 1 С 0000, but we have only four digits! Received additional code 9999 and loan flag C (signaling that there was a loan)

Those. in our integer math 9999 = -1 :) It's easy to check -1 + 1 = 0 Right?

9999 + 1 = 1 C 0000 Correct! :))) And 1 of the most significant bit did not fit into the bit capacity and went into the carry flag C, which also signals an overflow.

Okay, now let's take and make R - (- 1). Let R = 4

1 C 0004-9999 = 0005 + C

So they took it and added it through subtraction. Just magic, right? ;)

The joke of the assembler is that these are just commands, not a doctrine or a rule. And commands that imply sign calculations can be used anywhere, as long as they give the result we need!

Here and here - our counter is unsigned, but we use the features of the signed calculus because it is more convenient for us.

Flag C will not pop up only when we hit 255 (9999 in decimal), then 255-255 = 0 and only Z will jump, but we don't need it.

STS CCNT, R16 STS CCNT + 1, R17 STS CCNT + 2, R18 STS CCNT + 3, R19

The increment code of a four-byte constant in memory can be folded into a macro so as not to clutter the code

; Main ================================================ ======== Main: SETB PORTD, 4; Lit LED1 INVB PORTD, 7, R16, R17; Inverted LED3 Next: INCM CCNT JMP Main

Start debug mode and put a breakpoint (F9) on the Main label and move the cursor to the first breakpoint.

0xB6 (CCNT) 0x9F (CCNT + 1) 0x04 (CCNT + 2) 0x00 (CCNT + 3)

It remains now only to compare the number with this cast.

How to compare? Quite simple. It all depends on what we want to get. If there is ONE event for the ENTIRE period of the global counter of our organ, then it is stupid, byte byte. In this case, your diode will blink a second after the start, and then you will wait half an hour until the entire four-byte counter overflows.

To make it blink every second, you need to mask the high-order bits of the global counter when comparing, as if it has less than 32 bits (and it overflows more often).

The lower bytes are compared as they are, and the oldest bytes only up to its maximum bit, the rest must be cut off.

Those. the most significant bit for this case is CCNT + 2 = 0x04 if in binary representation then 0x04 = 00000 100 and so, we have a four-digit counter, which means an event with a mask

00 04 9F B6 ( 00000000 00000 100 10011111 10110110)

before overflow, there will be a number of times. You see, I have highlighted the zeros in bold. We will not compare the older one at all, but before the older one we need to push through the AND using the mask 00000111 in order to cut off the most significant bits.

They still need to be filled before the counter overflows and zeroes. But if we disguise them, then their further fate does not bother us. Even if it ticks until the second coming, we don't care.

LDS R16, CCNT; We load numbers into registers LDS R17, CCNT + 1 LDS R18, CCNT + 2 ANDI R18,0x07; Apply a CPI R16.0xB6 mask; Let's compare BRNE NoMatch CPI R17,0x9F BRNE NoMatch CPI R18,0x04 BRNE NoMatch by byte; If it matches, then we do the action Match: INVB PORTD, 7, R16, R17; Inverted LED3; Didn't match - don't do it :) NoMatch: Next: INCM CCNT; Turning the barrel organ JMP Main

In, loaded is now flashing. There are no blunts, nothing hangs anywhere, and the main cycle flies with a whistle, just have time to turn the barrel organ drum :)

It just flashes noticeably slower than we wanted. Not 1 second, but 8. Well, what did you want - by adding a comparison procedure, we lengthened the cycle by a few more commands. And now it is performed not 25 bars, but 36. Recalculate all the numbers again :)))))

But this is not yet the most tsimes! The trick is that part of your code is being executed, and part is not - the comparison and transition commands. Therefore, it is easier to accurately calculate the delay in clock cycles - it’s easier to strangle ourselves right away - you need to calculate for each iteration when and how many transitions you will have, how many clock cycles they will take ...

And if the code is even larger, then finally the pipe and the error accumulates with each iteration!

But if you add the button handling code:

; Main ================================================ ======== Main: SBIS PIND, 6; If the button is pressed - transition RJMP BT_Push SETB PORTD, 5; Light up LED2 CLRB PORTD, 4; Turn off LED1 Next: LDS R16, CCNT; Load numbers into registers LDS R17, CCNT + 1 LDS R18, CCNT + 2 ANDI R18,0x07 CPI R16,0xB6; Let's compare BRNE NoMatch CPI R17,0x9F BRNE NoMatch CPI R18,0x04 BRNE NoMatch by byte; If it matches, then we do the action Match: INVB PORTD, 7, R16, R17; Inverted LED3; Didn't match - don't do it :) NoMatch: NOP INCM CCNT JMP Main BT_Push: SETB PORTD, 4; Light up LED1 CLRB PORTD, 5; Extinguish LED2 RJMP Next; End Main =============================================== =====

Then we will see that there are no traces left of the brakes. The buttons instantly respond to pressing, and the diode blinks by itself. Multitasking! :)

In general, the hurdy-gurdy is not suitable where precise calculations are needed. But if the task is from the series “you have to jerk around from time to time and it’s not fundamentally how accurate”, then that’s it. Because does not take up hardware resources like a timer.

For example, scan the keyboard periodically, say every 2048 main loop revolutions. Calculate for yourself what number you need to load for comparison and what mask to impose :)

You can download and take a look, trace it to see how everything turns there.

And for accurate time calculations, there are timers. But they are a separate topic.

Port control in AVR GCC. DDRx and PORTx registers.
Representation of numbers. Bitwise operations.
Delay function. Unconditional jump in the program.

Microcontroller ports are input / output devices that allow the microcontroller to send or receive data. The standard port of the AVR microcontroller has eight data bits that can be transmitted or received in parallel. Each bit (or bit) corresponds to a pin (pin) of the microcontroller. Microcontroller feet are also called pins. To designate ports, the Latin letters A, B, C, etc. are used. The number of I / O ports varies depending on the microcontroller model.

Any port of the microcontroller can be configured as input or output. In order to do this, you should write the register corresponding to the port DDRx required value. In addition, any output (pin) of the port can be configured separately as an input or output. In any case, whether you want to configure an entire port or a single pin, you will need to work with DDRx registers.

DDRx - data transfer direction register. This register determines whether a particular port pin is an input or an output. If some bit of the DDRx register contains a logical unit, then the corresponding port pin is configured as an output, otherwise - as an input. The letter x in this case should represent the name of the port you are working with. Thus, for port A it will be the DDRA register, for port B it will be the DDRB register, and so on.

Using AVR GCC, you can write one or another value to the required register in one of the following ways.

For the entire port at once.

DDRD = 0xff;

All port D pins will be configured as outputs.

0xff- hexadecimal representation of the number ff, where 0x is the prefix used to write hexadecimal numbers. In decimal representation it will be the number 255, and in binary it will look like 11111111. That is, logical units will be written in all bits of the DDRD register.

V AVR GCC prefix 0b is used to represent binary numbers. Thus, the number 11111111 should be represented in the program as 0b11111111. We can write the previous command in a more readable way.

DDRD = 0b11111111;

Although this notation looks more descriptive, it is common practice to use hexadecimal notation when configuring ports.

In order to configure all pins of port D as inputs, it is necessary to write logical zeros to all bits of the DDRD register.

DDRD = 0x00;

Other numbers can be written to the DDRD register. For example:

DDRD = 0xb3;

0xb3- hexadecimal representation of the number 179. In binary it will look like 10110011. That is, some of the pins of port D will be configured as outputs, and some as inputs.

PD0 - 1 (output)
PD1 - 1 (output)
PD2 - 0 (input)
PD3 - 0 (input)
PD4 - 1 (output)
PD5 - 1 (output)
PD6 - 0 (input)
PD7 - 1 (output)

To configure separately the PD2 pin as an input, we need to write 0 to the corresponding bit of the DDRD register. For this, the following construction is used.

DDRD & = ~ (1
In this case, the result of shifting one two positions to the left is inverted by a bitwise inverting operation, indicated by " ~ ".

With the inversion, we get ones instead of zeros, and zeros instead of ones. This logical operation is otherwise called operation NOT(English name NOT).

Thus, with a bitwise inversion of 00000100, we get 11111011. (For more information on working with numbers in the microcontroller, see the sidebar below.)

The resulting number is multiplied by the number stored in the DDRD register using the bitwise logical multiplication operation &, and the result is written to the DDRD register.

With logical multiplication 0*0=0, 0*1=0, 1*1=1 ... The operation of logical multiplication is otherwise called the operation AND(English name AND).

That is, the one shifted by us two positions to the left turns into zero when inverted and multiplied by the corresponding bit stored in the DDRD register. When multiplied by zero, we get zero. Thus, the PD2 bit becomes zero.

After the direction of data transfer for the port is configured, you can assign a value to the port, which will be stored in the corresponding register. PORTx.
PORTx is a port register, where x is the port name.

If the pin is configured as an output, a one in the corresponding bit of the PORTx register generates a high-level signal on the pin, and a zero - a low-level signal.

If the pin is configured as an input, then a one in the corresponding bit of the PORTx register connects an internal pull-up resistor to the pin, which provides a high level at the input in the absence of an external signal.

You can set "1" on all pins of port D as follows.

PORTD = 0xff;

And you can set "0" on all pins of port D like this.

PORTD = 0x00;

Each bit of the PORTx registers can be accessed separately, just as in the case of the DDRx registers.

For example, the command

PORTD | = 1
will set "1" (high signal) on the PD3 pin.

PORTD & = ~ (1
will set "0" (low signal) on PD4 pin.

V AVR GCC the shift can be performed using the function _BV () which performs a bit shift and inserts the result into the compiled code.

In the case of using the _BV () function, the two previous commands will look like this.

PORTD | = _BV (PD3); // set "1" on line 3 of port D

PORTD & = ~ _BV (PD4); // set "0" on line 4 of port D

Depending on the connection method, the LED will light up either from a high level signal applied to the PD1 pin of the microcontroller, as in the first case, or from a low level signal in the case of the connection shown in the second figure.

/ ************************************************ ************************* EXAMPLE OF LED ON WITH HIGH LEVEL SIGNAL Example of connection in Figure 1 *************** ************************************************* ********* /#include $ WinAVR = ($ _GET ["avr"]); if ($ WinAVR) include ($ WinAVR);?> int main ( void) { // start of the main program DDRD = 0xff; PORTD | = _BV (PD1); // set "1" (high) on PD1 pin }

Now let's try to blink the LED connected as shown in the left figure. For this we use the delay function _delay_ms ().

The _delay_ms () function forms the delay depending on the argument passed to it, expressed in milliseconds (in one second 1000 milliseconds). The maximum latency can be up to 262.14 milliseconds. If the user passes a value greater than 262.14 to the function, the resolution will automatically decrease to 1/10 millisecond, which provides delays of up to 6.5535 seconds. (You can read about the formation of longer delays in the article.)

The _delay_ms () function is contained in the delay.h file, so we will need to connect this file to the program. In addition, for the normal operation of this function, you must specify the frequency at which the microcontroller operates, in hertz.

/ ************************************ EXAMPLE OF LED BLINKING Example of connection in Figure 1 **** ********************************* /#define F_CPU 1000000UL #include #include int main ( void) { // start of the main program DDRD = 0xff; // configure all pins of port D as outputs PORTD | = _BV (PD1); _delay_ms (500); // wait 0.5 sec. PORTD | = _BV (PD1); // set "1" (high level) on the PD1 pin, // light the LED _delay_ms (500); // wait 0.5 sec. PORTD & = ~ _BV (PD1); // set "0" (low level) on the PD1 pin, // turn off the LED } // closing parenthesis of the main program

The LED flashes will be very short. In order to make the blinking continuous, you can organize an endless loop using the "goto" operator. The goto statement moves to the place of the program indicated by the label. The label name must not contain spaces. The name of the label is followed by a colon. There must be no spaces between the label name and the colon.
/ *********************************************** ****** EXAMPLE OF ENDLESS LED BLINKING Example of connection in Figure 1 ********************************* ********************* /#define F_CPU 1000000UL // specify the frequency in hertz#include #include int main ( void) { // start of the main program DDRD = 0xff; // configure all pins of port D as outputs start: // label for goto start command PORTD | = _BV (PD1); // set "1" (high level) on the PD1 pin, // light the LED _delay_ms (250); // wait 0.25 sec. PORTD & = ~ _BV (PD1); // set "0" (low level) on the PD1 pin, // turn off the LED _delay_ms (250); // wait 0.25 sec. goto start; // go to label start } // closing parenthesis of the main program

Did you like the article? To share with friends: