Просмотр полной версии : AVR
Здрасти.
Собственно, нужна помощь в создании прошивки для микроконтроллера.
В Atmel AVR написал на С++ программку вращения энкодера.
Сама программка небольшая, и вроде бы ошибок нет, но созданный hex-файл не работает ни на живом контроллере, ни в эмуляции на Proteus.
Если есть кто в состоянии помочь, то вышлю файлы, и буду безмерно благодарен.
Высылайте, только для это есть свой раздел- Компютеры.
Вы эмулируете энкодер?
Подробнее давайте.
Куда слать?
Эмуляция энкодера - это 4 выключателя, которые нажимаются вручную (мышкой) в нужные положения.
суть затеи - вышел из строя блок управления поворотом вала.
Вал вращается в пределах 180 градусов (приблизительно).
Для отслеживания положения вала имеется 3 концевика, отслеживающих сектор, и еще один концевик, отслеживающий точное позиционирование.
Задатчик - еще 4 кнопки, которыми в зависимости от комбинации выбирается необходимое положение вала.
Программа сравнивает текущие значения задатчика и энкодера, и в зависимости от их состояния доворачиваем электромотор в том или ином направлении, или выдает информацию об ошибке.
Программа написана, ошибок по дебаггеру нет.
Но вот в протеусе и изменений на нажатие кнопок тоже нет...
можете и сюда файл прикрепить (наверное), или ссылку на него.
http://www.ex.ua/878246416461
Наверняка будут вопросы. Если надо - могу перезвонить.
есть подозрение, что по GOTO программа уходит в ошибку и никогда не возвращается.
я бы избавился от них путем оформления всё в процедуры. ладно, еще посижу-посмотрю.
Возможно, я не силен в программировании.
Но хотел бы набраться опыта.
мда. у меня этой версии студии нет. могу разве что переписать на иаре.
частота внутрення рц-цепочка на 1Мгц? правильно я понял?
кстати, ресет у вас на массе. должен быть к питанию. возможно у вас всё работает. перепроверьте.
С резетом действительно лажанулся. Спасибо.
НО, программа выдает неправильную индикацию и не реагирует ни на какие действия.
Сегодня-завтра перепроверю текст, может что-то найду.
кстати, диоды в протеусе поменяйте, эти не светятся, можно судить только по уровню на резисторах.
и второе, если кнопки коммутируют питание на пины, то для нормальной симуляции их мк, надо подтянуть через pull-down к массе, иначе, после отпускания кнопки, на входе мк останется лог. 1.
короче, правильно оформи схему, и возможно программа отработает правильно.
Так вроде ж в самом начале программы указал подтяжку всех портов через резисторы на массу.
Работу программы проверяю не только в симуляции, но и на живом МК. Благо на работе есть паяльник.
там нет резисторов на массу, есть только на питание. на вход с нулями это третье состояние.
ладно, если есть живой макет, то начните с простого, отрабатывает ли диод нажатие кнопки.
типа
for(;;)
{
if (PIND0==0)
PORTA = 0xFF
else
PORTA = 0x00;
}
потом дальше бум думать.
Я вот как раз и подумал сделать что-то наподобие этого - разбить программу на составляющие, и смотреть как оно работает. Завтра и займусь.
стою на асфальте я в лыжи обутый....
текст программы
#include <avr/io.h>
int main()
{
PORTA=0x00; //порт А
DDRA=0xFF;
PORTD=0x00;
DDRD=0x00;
for(;;)
{
{
if (PIND0==0)
PORTA=0xFF;
else
PORTA=0x00;
}
}
}
и та не работает...
первый цикл - диоды погашены, все последующие - диоды включены, независимо от состояния кнопки.
Я даже установил подтягивающий резистор на землю - не помогло.
Может у меня руки кривые, а извилины - прямые?
а так реакция на пинд0 в реальном железе есть?
http://exfile.ru/441048
Есть и в реальном, и в симуляции...
тогда я хз. что-то не правильно настроено в студии.
скомпилил у себя без изменений.
пробуй.
http://exfile.ru/441062
то есть компиляция неправильно идет?
тогда я хз. что-то не правильно настроено в студии.
скомпилил у себя без изменений.
пробуй.
http://exfile.ru/441062
если это моя программа - то не фурычит.
после временной задержки выдает не запрограммированную индикацию (portA == 0b00110110),при том даже не отрабатывая положенные шаги.
Моя компиляция работала так-же (после подвешивания резет на +)
А предыдущий файл - это компиляция какого кода?
а, ну если так-же, то по идее файл собирает правильно.
предыдущая просто цикл.
if (PIND0==0)
PORTA=0xFF;
else
PORTA=0x00;
А ты открывал мой проект? Или создавал свой и писал туда мою программу?
Вопрос в том, что компиляция цикла у меня раза в полтора меньше, чем у тебя.
А компиляция программы по размеру такая-же. Может (если открывал мой проект) не перезаписался hex-файл?
почему?
вот мой.
:100000000C945800189518951895189518951895EA
:100010001895189518951895189518951895189578
:100020001895189518951895189518951895189568
:100030001895189518951895189518951895189558
:100040001895189518951895189518951895189548
:100050001895189500E00BBB0FEF0ABB00E008BB3A
:1000600000E007BB00E005BB07E004BB00E002BB0B
:1000700000E001BB0FEF0BBB0FE71AE126E00150D8
:1000800010402040E1F700C0000000E00BBB00E2A0
:100090000BBBAA98A89AA998A898A998DA9ADC9A6A
:1000A000DD9AD898D99AAA98F7CF00008895FECF04
:1000B0000FE90DBF00E00EBFC0E8D0E00E9466006F
:1000C0000E942A000E9455000C94550001E00895FA
:00000001FF
:00000001FF
а вот твой.
:100000000C942A000C9434000C9434000C943400AA
:100010000C9434000C9434000C9434000C94340090
:100020000C9434000C9434000C9434000C94340080
:100030000C9434000C9434000C9434000C94340070
:100040000C9434000C9434000C9434000C94340060
:100050000C94340011241FBECFE5D4E0DEBFCDBF29
:100060000E9436000C9450000C9400001BBA8FEFD5
:100070008ABB18BA17BA15BA97E094BB12BA11BA6C
:100080008BBB1BBA80E28BBBAA98A89AA998A898A8
:10009000A998DA9ADC9ADD9AD898D99AAA98F7CFD3
:0400A000F894FFCF02
:00000001FF
опкоды разные, но размер подходит.
да, похоже что ошибся с объемом кода.
Возможно действительно по разному компилирует.
Поставил avr studio 6 (в связи с тем, что на основном ПК она поломалась) на виртуалку, и там мучаюсь.
пошел другим путем - смотрю в дебаггере шаги.
Взял из программы кусок кода, и немного укоротил - сделал чисто дешифратор:
#include <avr/io.h>
int main(void)
{
DDRA = 0xFF;
PORTA = 0b00000000;
DDRD = 0x00;
PORTD = 0b00000000;
for (;;)
{
if ((PIND0==1)&&(PIND1==0)&&(PIND2==0))
PORTA = 0b00000100;
else if ((PIND0==1)&&(PIND1==1)&&(PIND2==0))
PORTA = 0b00110100;
else if ((PIND0==0)&&(PIND1==1)&&(PIND2==0))
PORTA = 0b00100100;
else if ((PIND0==0)&&(PIND1==1)&&(PIND2==1))
PORTA = 0b00000010;
else if ((PIND0==0)&&(PIND1==0)&&(PIND2==1))
PORTA = 0b00000010;
else if ((PIND0==1)&&(PIND1==0)&&(PIND2==1))
PORTA = 0b00111100;
else
PORTA = 0b00000001;
}
}
По шагам, первые 4 (назначение портов) дебаггер отрабатывает, а потом сразу прыгает в строку
else
PORTA = 0b00000001;
не отрабатывая никакие другие. и оттуда его ничем не выудить...
ошибок, предупреждений нет.
значит не отрабатывают PIND0. попробуй прочитать пины по другому.
в еще лучше сделать это через switch.
не помню как в студии, в ирае это объявлено как
#define PIND0 0
естественно никогда единице равняться не будет.
вот. так как-то.
if ((PIND & 1 ==1) &&(PIND & 2==0) && (PIND &4 ==0))
Ничего, я пять копеек вставлю?
Полгодика uC не ковырял, но вот так удобно:
#define cw_pressed !(PIND & 0b00000100)
#define ccw_pressed !(PIND & 0b00001000)
Потом что-то типа:
ISR(INT0_vect){
cli();
_delay_ms(1);
ClrBit(PORTD,PS);
if(cw_pressed) t = ((t << 7) | (t >> 1));
if(t & 1) SetBit(PORTD,DIR1); else ClrBit(PORTD,DIR1);
if(t & 2) SetBit(PORTD,DIR2); else ClrBit(PORTD,DIR2);
_delay_ms(1);
SetBit(PORTD,PS);
sei();
}
Только при куче датчиков может прерываний не хватить, но схемотехнически, через диоды, можно все кнопки на одно внешнее прерывание посадить.
5 копеек - это хорошо, но мое понимание еще не дошло до данного кода...
проще говоря - я из этого ничего не понял.
Т.к. мое изучение програмирования МК и вообще С++ ограничивается на данный момент только вот этим одним устройством.
Но за код все равно спасибо - найду на работе час-полтора свободного времени - попробую разобраться с данной записью
Я тогда пока накидаю недостающих для понимания строк и картинку.
Также добавлю, что у меня биполярный шаговик.
#define PS PD6
#define DIR1 PD4
#define DIR2 PD5
#define SetBit(Port,bit) Port |= _BV(bit)
#define ClrBit(Port,bit) Port &= ~_BV(bit)
#define Bit(bit) (1 << (bit))
volatile unsigned char t;
void init() {
cli();
DDRD=0b01110000;
PORTD=0b00001110;
ClrBit(MCUCR,ISC01);
ClrBit(MCUCR,ISC00);
ClrBit(MCUCR,ISC11);
ClrBit(MCUCR,ISC10);
SetBit(GICR,INT0);
SetBit(GICR,INT1);
set_sleep_mode(SLEEP_MODE_IDLE);
t = 0b11001100;
sei();
}
int main(void) {
init();
while(1);
return 0;
}
http://radiohlam.ru/control/images/bipolar_step_motor1.jpg
К картинке необходимо добавить, что я использовал МС управления ШД, который при подаче единиц на DIR1 и DIR2 устанавливает одну полярность на обмотках 1 и 2 соответственно, а при "нулях" - противоположную.
PS управляет режимом с пониженным потреблением - обеспечивает удержание ротора в паузах.
Начинаю все с начала, в связи с некоторыми изменениями в логике устройства (введение АЦП)
первым делом разбиваю всю программу на подпрограммы, и смотрю как работает.
Первая программа - дешифратор задатчика (порт B), для наглядности выводит информацию в унитарном виде на порту D
(данные на порту В - третий столбец таблицы, диод на порту D - первый столбец таблицы)
прочерки означают незадаваемое положение, кресты - что при указанном состоянии прочих ног, состояние данной не учитывается.
#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
PORTA=0b11111111;
DDRA=0b00000000;
PORTB=0b11111111;
DDRB=0b00000000;
PORTC=0b00000000;
DDRC=0b11111111;
PORTD=0b00000000;
DDRD=0b11111111;
PORTD=0b11111111;
_delay_ms(2000);
PORTD=0b00000000;
while(1)
{
//Задатчик;
//Таблица соответствия состояния энкодера / задатчика положения / индикации положения;
// № | PA4 PA5 PA6 | PB0 PB1 PB2 PB3 | PD2-6;
// --------------------------------------------------;
// 1 | 0 1 1 | 1 1 x 1 | 00010;
// 2 | 0 0 1 | 0 1 0 1 | 11010;
// 3 | 1 0 1 | 0 1 1 1 | 10010;
// 4 | 1 0 0 | x x x 0 | 00001;
// 5 | 1 1 0 | - - - - | 00001;
// 6 | 0 1 0 | 0 0 x 1 | 11110;
// 7 | 0 1 0 | 1 0 x 1 | 00110;
com: if (PORTB3==0)
{
PORTD=0b00001000;
}
else if(PORTB3==1 && PORTB0==1 && PORTB1==1)
{
PORTD=0b00000001;
}
else if(PORTB3==1 && PORTB0==1 && PORTB1==0)
{
PORTD=0b01000000;
}
else if(PORTB3==1 && PORTB0==0 && PORTB1==0)
{
PORTD=0b00100000;
}
else if(PORTB3==1 && PORTB0==0 && PORTB1==1 && PORTB2==0)
{
PORTD=0b00000010;
}
else if(PORTB3==1 && PORTB0==0 && PORTB1==1 && PORTB2==1)
{
PORTD=0b00000100;
}
else
{
goto com;
}
}
}
При проверке дебаггером ошибок и замечаний нет, но когда провести пошаговыую проверку, тоон просматривает назначение портов, и замирает.
до тела программы не доходит.
При проверке в эмуляторе (протеус), ругается на то, что в hex-файле найдена какая-то ошибка.
ps: to Глобус
1 - к сожалению с твоим кодом не разобрался
2 - двигатель у меня не шаговый, а коллекторный.
pps: может кто посоветует более адекватное ПО для написания, отладки и компиляции программы?
pps: может кто посоветует более адекватное ПО для написания, отладки и компиляции программы?
Для первых шагов (да и не только) я бы очень посоветовал ИмиджКрафт ICC AVR. Сейчас ломаная седьмая версия есть в инете. Совместим с симулятором АВР Студио. Там есть очень удобный так называемый аппликейшн билдер, для быстрой и безошибочной автоматической генерации кода настройки портов, прерываний, таймеров и остальной периферии. Довольно удобная интегрированная среда, есть даже встроенное окно для программирования чипа не отходя от кассы (поддерживает железо Понипрога). Поддерживает также бинарное представление чисел (иногда очень повышает читаемость кода), есть документация на русском языке.
Есть еще Кодевижн, но мне больше нравится ICC, там работающий проект можно создать буквально за несколько кликов мышкой. Ну и конечно IAR, но это для профессионалов, можно позже на него переползти.
Блин. Надо найти время и запилить вам этот проект. Осталось узнать как.
Вы на функции программу не перевели, у Вас остался goto. Это не принципиально, но и не функция.
Немного теории (совсем чуть-чуть). Регистры портов в авр делятся на три типа, направление, выход и вход, называються соответственно DDRx, PORTx и PINx.
Т.е. чтобы выдать на порт значение используем PORT, чтобы считать - используем PIN.
Давайте так.
Создайте функцию. Типа char GetEncoder(void);
И в ней, вместо этого, вызывайте её:
m: if (PORTB3==0)
{
PORTD=0b00001000;
}
else if(PORTB3==1 && PORTB0==1 && PORTB1==1)
{
PORTD=0b00000001;
}
else if(PORTB3==1 && PORTB0==1 && PORTB1==0)
{
PORTD=0b01000000;
}
else if(PORTB3==1 && PORTB0==0 && PORTB1==0)
{
PORTD=0b00100000;
}
else if(PORTB3==1 && PORTB0==0 && PORTB1==1 && PORTB2==0)
{
PORTD=0b00000010;
}
else if(PORTB3==1 && PORTB0==0 && PORTB1==1 && PORTB2==1)
{
PORTD=0b00000100;
}
else
{
goto com;
}
}
}
char GetEncoder(void)
{
if (PINB & 0x80 == 0)
{
PORTD=0b00001000;
return 1; // выход с флагом 1, что сработал по "if (PINB & 0x80 == 0) "
}
// иначе там единица, продолжаем
switch (PINB & 0x0F)
{
case 0x0B:
PORTD=0b00000001;
break;
case 0x09:
PORTD=0b01000000;
break;
case 0x08:
PORTD=0b00100000;
break;
case 0x0A:
PORTD=0b00000010;
break;
case 0x0E:
PORTD=0b00000100;
break;
default: // если ничего не совпало - возвращаем 2.
return 2;
break;
}
в цикле
while(1)
{
char x;
x = GetEncoder();
if (x == 1) знач вернулся вернулся по "if (PINB & 0x80 == 0) "
или так
while (GetEncoder()) == 2) {} // т.е. вызываем GetEncoder, пока не вернется что-то отличное от двойки.
} // скобка конца while()
Pilot, а что в таличке состояний означают "x" и "-"?
прочерки означают незадаваемое положение (задатчиком его нельзя выбрать, но в энкодере оно есть)
кресты - что при указанном состоянии прочих ног, состояние данной не учитывается.
Pilot
Отмечучь и я, чисто в рекомендательном плане.
Уберите goto в коде поста №31, оно там ни к чему вообще, while(1) и так выполнится с первой строчки, если не сработает ни одно условие, при этом желательно в конце while поставить небольшую задержку (~100мс), если планируете использовать какие-то дополнительные процедуры.
И я б, честно, подобные строки if(PORTB3==1 && PORTB0==1 && PORTB1==1)
заменил бы на if (PORTB==0b00001011)
Почему? Да потому что оператор if выполнится всего один раз вместо 5 :)
Ну или для полноты понимания, при помощи define создайте варианты необходимых условий. Вы хоть код понимать будете ну и помочь будет легче вновь читающим.
К примеру
#define VAR0 0b00001011
if (PORTB==VAR0)...
или
if (PORTB & VAR0)...
что полностью заменяет if(PORTB3==1 && PORTB0==1 && PORTB1==1) и облегчает чтение.
Этим разгрузите код и вероятно облегчите жизнь дебугеру.
Конечно, если так можно, а пятой точкой ощущаю, что можно наврняка.
Kino, к стати, правильно подсказывает, насчет отдельной процедуры, но по опыту работы с контроллерами, я б нестал использовать такие тяжелые формы как case, от этого легче не станет, а код усложнит и займет на порядок больше тактов, описанный выше мною вариант более применим к контроллерам, со статическмими условиями регулятора.
длинное сообщение
вот здесь вроде немного понятно:
char GetEncoder(void)
while (GetEncoder()) == 2)
{
if (PINB & 0x80 == 0)
{
PORTD=0b00001000;
return 1; // выход с флагом 1, что сработал по "if (PINB & 0x80 == 0) "
// иначе там единица, продолжаем
}
// я так понимаю, что если 8-ая нога порта В равна 0, то на выходе порта D будет 00001000, в противном случае идем дальше.
switch (PINB & 0x0F)
{
case 0x0B: PORTD=0b00000001; break;
case 0x09: PORTD=0b01000000; break;
case 0x08: PORTD=0b00100000; break;
case 0x0A: PORTD=0b00000010; break;
case 0x0E: PORTD=0b00000100; break;
default: return 2; break;
// если ничего не совпало - возвращаем 2.
}
// Здесь, как я понял, определение выражения на 4х ногах порта В (PINB & 0x0F) - (в двоичной =00001111), тех, на которых кнопки задатчика
// соответствие входа выхода вроде бы такое:
// 1011 - 00000001
// 1001 - 01000000
// 1000 - 00100000
// 1010 - 00000010
// 1110 - 00000100
}
// До тех пор, пока будет возвращатся 2, будет выполнятся оператор while. в противном случае опреатор прекращается, я имею на порту D нужный сигнал и могу выполнять следующую процедуру.
Правильно понял?
Условие if (PINB & 0x80 == 0) как по мне не корректно. В данном случае всегда будет ложь, т.к. 0x80 не равно 0 :)
Для успокоения совести напишите:
if ((PINB & 0x80) == 0)
или просто
if !(PINB & 0x80)
опять большое письмо
т.е. ты предлагаешь сделать следующим образом:
#define com1 0b00001011
#define com2 0b00001010
#define com3 0b00001110
#define com4 0b00000000
#define com6 0b00001000
#define com7 0b00001001
char GetEncoder(void)
while (GetEncoder())==2)
if PORTB==com1
{PORTD=0b00000001}
else if PORTB==com2
{PORTD=0b00000010}
else if PORTB==com3
{PORTD=0b00000100}
else if PORTB==com4
{PORTD=0b00001000}
else if PORTB==com6
{PORTD=0b00100000}
else if PORTB==com7
{PORTD=0b01000000}
else
{return 2}
Но в таком виде код не пройдет компиляцию, ибо ошибок тьма.
И, если GetEncoder разовая функция, то ее нет смысла оформлять отдельной процедурой.
В остальном направление мысли верно.
Ну вот как-то так:
#define com1 0b00001011
#define com2 0b00001010
#define com3 0b00001110
#define com4 0b00000000
#define com6 0b00001000
#define com7 0b00001001
char GetEncoder(void)
{
if (PORTB==com1) PORTD=0b00000001;
else if PORTB==com2 PORTD=0b00000010;
else if (PORTB==com3) PORTD=0b00000100;
else if (PORTB==com4) PORTD=0b00001000;
else if (PORTB==com6) PORTD=0b00100000;
else if (PORTB==com7) PORTD=0b01000000;
else return 2;
return 0;
}
int main(void) // главная рутина
{
//.........какой-то код............
while (GetEncoder()==0) _delay_ms(100);
//или
//while (GetEncoder()>0) _delay_ms(100);
//зависит от желаемого результата
//.........какой-то код............
}
Вот алгоритм работы устройства, чтоб было понятно о чем речь:
http://savepic.su/3712486m.jpg (http://savepic.su/3712486.htm)
Так у Вас все есть, вот четко по алгоритму и действуйте. Просто код старайтесь дедать максимально простым и учитывайте, что необходимы задержки при отработке команд до чтения состояния, причем величина задержек прямопропорциональна инертности исполнительных элементов.
И, если GetEncoder разовая функция, то ее нет смысла оформлять отдельной процедурой.
Тут функция должна быть (чтоб не было недоразумения в коде - getcomander) несколько раз, поэтому наверное, все-таки лучше функцией.
И остальные так-же.
#define com1 0b00001011
#define com2 0b00001010
#define com3 0b00001110
#define com4 0b00000000
#define com6 0b00001000
#define com7 0b00001001
char GetComander(void)
{
if (PORTB==com1) com=0b00000001;
else if PORTB==com2 com=0b00000010;
else if (PORTB==com3) com=0b00000100;
else if (PORTB==com4) com=0b00001000;
else if (PORTB==com6) com=0b00100000;
else if (PORTB==com7) com=0b00100000;
else return 2;
return 0;
}
While (GetComander()==2)_delay_ms(100);
// если какое-то условие верно, то на выходе получим соответствующее значение, а функция GetComander будет равна 0. Ну и соответственно, пойдет дальнейшее выполнение программы.
// если никакое условие не совпадет, то функция GetComander будет равна 2 и повторится через 0,1с.
Теперь вторая часть дешифратора, в которой не может быть неоднозначного положения, т.е. если возникает неопознанное значение, то программа должна "выпасть" в ошибку:
#define enc1 0bx110xxxx
#define enc2 0bx100xxxx
#define enc3 0bx101xxxx
#define enc4 0bx001xxxx
#define enc5 0bx011xxxx
#define enc6 0bx010xxxx // где "х" - может быть любым значением (используется отдельно от дешифратора, но как это прописать - я не знаю вроде бы как то так: )
char GetEncoder(void)
{
if (PORTA & 0b01110000 ==enc1) enc=0b00000001;
else if (PORTA & 0b01110000 == enc2) enc=0b00000010;
else if (PORTA & 0b01110000 == enc3) enc=0b00000100;
else if (PORTA & 0b01110000 == enc4) enc=0b00001000;
else if (PORTA & 0b01110000 == enc5) enc=0b00100000;
else if (PORTA & 0b01110000 == enc6) enc=0b00100000;
else return 2;
return 0;
}
if (GetEncoder()==2)
ERROR \\ переход к подпрограмме ошибки
else
\\ продолжаем выполнять основную программу
Так у Вас все есть, вот четко по алгоритму и действуйте. Просто код старайтесь дедать максимально простым и учитывайте, что необходимы задержки при отработке команд до чтения состояния, причем величина задержек прямопропорциональна инертности исполнительных элементов.
Да вот я и стараюсь действовать.
Проблема в том, что я не изучал ранее языки программирования (окромя паскаля в 9м классе).
И вот собственно мне стал вопрос изготовления блока управления.
На нем я и решил поучиться столь интересному занятию.
Конечно, как для первой программы у меня возникло дохренадостаточно много проблем:
- программа на операторе IF/ELSE написана вроде как правильно, замечаний в компиляторе нет, но она не работает в контроллере. Вот и обратился за помощью (и заодно - получу новые знания :D, надеюсь)
Ну, судя по представленному Вами алгоритму, вам, кроме if/else, врядли потребуются более изощренные компоненты языка.....
только он у меня нихрена не заработал... к сожаленью
Хотя 2 человека, которые пишут на С++, но не программируют контроллеры, сказали, что код правильный
Значит Вы просто где-то промахиваетесь либо с начальной конфигурацией контроллера либо в схеме.
не фьюзах дело. настройки где-то кривые. он пишет под avrstudio + winavr.
Сорри что вклиниваюсь в интеллектуальную беседу, а, извиняюсь, - более простые программы писали?
Светодиодиком получалось моргать?
К сожалению - из топика не вижу ни полную схему, ни программу. Но пробовали собирать на макете, и вместо датчиков использовать кнопки, а вместо исполнительных инструментов те же светодиоды?
Проблему дребезга контактов концевиков решили?
Все входы и выходы проинициализировали правильно? Ничего у вас в "воздухе" не осталось висеть?
попробую переписать программу с внесенными предложениями, и выложу на обозрение.
так-же и схему устройства предоставлю
По поводу АВР, подскажите среду ... а то я в ардуине, но хотелось бы устройство перенести в железку. Чтобы было меньше и универсальней.
Среду чего? Программирования на С? Разные есть. IAR, но он навороченный слишком, новичка может отпугнуть. Codevision демократичнее. Я использую ImageCraft ICC7, очень нравится своей простотой и наличием аппликейшн билдера, автоматически генерирующего правильный код настройки периферии буквально за несколько кликов мышкой.
Для своих поделок CVAVR самое оно.
А IAR он не навороченный, он как-раз тупо голый. Но у него с оптимизацией более правильно сделано.
Не знаю, меня оптимизатор ИмиджКрафта очень даже устраивает. Компилил как-то один и тот же код ИАРом и Крафтом, у Крафта код получился меньше. К тому же там есть очень полезная фича "Code Compression", уменьшает размер кода примерно на 12% за счет небольшого снижения скорости работы. Незаменимо, когда проект чуть-чуть не влазит в выбранный чип, а менять чип уже не хочется.
Вопросы ТС, уточнение по энкодеру (точнее по табличке):
в случае "х" (нельзя задать) на вход МК таки будет приходить 0 или 1?
Также интересен вопрос по "-". Ясно, что не учитывается, какое оно будет реально?
Я вижу в кусках кода включение резисторов подтяжки.. Вероятно, кнопки коротят входы МК на землю, а на разомкнутых контактах будет "1"?
Еще один важный вопрос - в табличке PA5-7 и PB0-3 поданы как исходные данные, а PD2-6 как результат. Да и в программе порты сконфигурированы соответствующим образом.
Однако, я посмотрел внимательно на табличку и увидел закономерность:
кроме строк 6 и 7, PA5-7 однозначно описывают состояния выходов. А для того, чтобы отличить строки 6 и 7, достаточно будет посмотреть состояние PB0.
Выходит, на задатчике достаточно одной кнопки?
Или я что-то не так понял?
Ну и это - там правильно подсказали: не работают почему-то простейшие вещи, так что попробуйте для начала проверить состояние одного пина и в ответ давать 1 или 0 на другой.
И прошу прощения у ТС, раз заговорили об оптимизации, задам свой вопрос:
Есть поверье, что t = ((t << 7) | (t >> 1)) некоторые компиляторы компилируют в ROR. У меня не вышло такого добиться. Кто-то с этим сталкивался? На чем-то получалось?
всмысле? всё выражение в один ROR? Такого же не бывает. )
Ну так по сути это ROR и есть.
Просто для SHL и SHR в C есть << и >>.
А для ROL, ROR нет ничего.
Их можно заменить:
int rol(int value, int places)
{
return (value<<places)|(value>>(WORD_LENGTH-places);
}
int ror(int value, int places)
{
return (value>>places)|(value<<WORD_LENGTH-places);
}
Но напрямую это скомпилируется в SHR и SHL для каждой функции.
Вот в каких-то интернетах чувак утверждал, что с включенной оптимизацией компилер понял, что это именно ROL и ROR и соптимизировал.
Мой gcc не удалось заставить это понять.
ну как только ROR если команду ИЛИ тоже надо выполнить.
и в авр нет SHL и SHR, есть LSL и LSR, отличие от ROL и ROR только в переносе старшего бита в младший. В SHL и SHR этого переноса нет.
Только никак не применю к вашему выражению, о котором ходит поверье... )
На любой восьмибитной переменной результат такой функции:
__asm
{
mov eax,MyVar;
rol eax,1;
mov MyVar,eax;
}
абсолютно эквивалентен MyVar = (MyVar << 1 | MyVar >> 7) - (сдвиг влево на один бит) или (сдвиг право на семь бит //перенос старшего бита в младший).
Ну или для наглядности:
rol 10110110 на 1 даст 01101101
вторая функция даст
00000001 or 01101100, т.е. тот же 01101101
По поводу LSL, LSR - да. Это я уже по привычке.
это наверное сильно надо раздуть прогу, чтобы реально места не хватало. а так, что включена оптимизация, что выключена - по шарабану.
Вопросы ТС, уточнение по энкодеру (точнее по табличке):
в случае "х" (нельзя задать) на вход МК таки будет приходить 0 или 1?
Также интересен вопрос по "-". Ясно, что не учитывается, какое оно будет реально?
в случае "х" - состояние на входе может быть как "1", так и "0", но в случае необходимости можно упростить, задав четкие положения кнопок, и тогда "х" будет равен "0"
в строке, где "-" возможно только определить положение энкодера (и индицировать его), но выбрать задатчиком это положение нельзя.
Это конструктивная особенность энкодера, там ввели лишнюю позицию, которая не используется.
Я вижу в кусках кода включение резисторов подтяжки.. Вероятно, кнопки коротят входы МК на землю, а на разомкнутых контактах будет "1"?
Именно так
Еще один важный вопрос - в табличке PA5-7 и PB0-3 поданы как исходные данные, а PD2-6 как результат. Да и в программе порты сконфигурированы соответствующим образом.
Однако, я посмотрел внимательно на табличку и увидел закономерность:
кроме строк 6 и 7, PA5-7 однозначно описывают состояния выходов. А для того, чтобы отличить строки 6 и 7, достаточно будет посмотреть состояние PB0.
Выходит, на задатчике достаточно одной кнопки?
Или я что-то не так понял?
одной кнопки не достаточно для использования всех позиций.
Ее (в данном случае) достаточно только чтоб разделить 6ю и 7ю позиции задатчика, а для остальных позиций используются остальные кнопки тоже.
Ну и это - там правильно подсказали: не работают почему-то простейшие вещи, так что попробуйте для начала проверить состояние одного пина и в ответ давать 1 или 0 на другой.
перепаяю живой макет и буду проверять.
но мне тоже кажется, что проблема в настройках ПО для компиляции.
ну в общем, есть вариант (недопиленный пока что) программы без GoTO (на функциях):
/*
* encoder.cpp
*
* Created: 28.10.2013 16:50:50
* Author: PVD
*/
#include <avr/io.h>
#include <delay/io.h>
char com;
char comander;
char enc;
//Задатчик;
//Таблица соответствия состояния энкодера / задатчика положения / индикации положения;
// № | PA4 PA5 PA6 | PB0 PB1 PB2 PB3 | PD2-6;
// --------------------------------------------------;
// 1 | 0 1 1 | 1 1 x 1 | 00010;
// 2 | 0 0 1 | 0 1 0 1 | 11010;
// 3 | 1 0 1 | 0 1 1 1 | 10010;
// 4 | 1 0 0 | x x x 0 | 00001;
// 5 | 1 1 0 | - - - - | 00001;
// 6 | 0 1 0 | 0 0 x 1 | 11110;
// 7 | 0 1 0 | 1 0 x 1 | 00110;
int GetEncoder(int)
{
enc=(PORTA & 0b0111000);
if (enc==0b01100000) {(PORTD=0b00010000); return 1;}
else if (enc==0b01000000) {(PORTD=0b00010110); return 2;}
else if (enc==0b01010000) {(PORTD=0b00010010); return 3;}
else if (enc==0b00010000) {(PORTD=0b00100000); return 4;}
else if (enc==0b00110000) {(PORTD=0b00100000); return 5;}
else if (enc==0b00100000) {(PORTD=0b00011110); return 6;}
else
{
PORTD=0b10000000;
if (PORTB0==1) {ErrorHi();}
else {ErrorLo();}
}
}
int ErrorHi(void)
{
PORTC=0b00000000;
while (PORTA0==0) {PORTC0=1;};
PORTC=0b00000000;
PORTD=0b10011110;
while (PORTB0==1)
{
PORTC2=PINB0;
if (PINA3==0 && PINC2==0) {(PIND0=0); (PIND1=0);}
else if (PINA3==0 && PINC2==1) {(PIND0=0); (PIND1=1);}
else if (PINA3==1 && PINC2==0) {(PIND0=0); (PIND1=1);}
else (PINA3==1 && PINC2==1) {(PIND0=1); (PIND1=1);}
while (PINB7=0) {Manual();}
}
ErrorLo();
}
int ErrorLo(void)
{
PORTC=0b00000000;
while (PORTA0==0) {PORTC1=1};
PORTC=0b00000000;
PORTD=0b10000010;
while (PORTB0==0)
{
while (PINB7=0) {Manual();}
}
ErrorHi();
}
int Manual(int)
{
PORTC2=PINB0;
if (PINA3==0 && PINC2==0) {(PIND0=0); (PIND1=0);}
else if (PINA3==0 && PINC2==1) {(PIND0=0); (PIND1=1);}
else if (PINA3==1 && PINC2==0) {(PIND0=0); (PIND1=1);}
else (PINA3==1 && PINC2==1) {(PIND0=1); (PIND1=1);}
enc=(PORTA & 0b0111000);
if (enc==0b01100000) {PORTD=0b00010000;}
else if (enc==0b01000000) {PORTD=0b00010110;}
else if (enc==0b01010000) {PORTD=0b00010010;}
else if (enc==0b00010000) {PORTD=0b00100000;}
else if (enc==0b00110000) {PORTD=0b00100000;}
else if (enc==0b00100000) {PORTD=0b00011110;}
else PORTD=0b00011110;
}
int Critical(void)
{
while (1)
{
PORTD=0b10000000;
_delay_ms(1000);
PORTD=0b00000000;
_delay_ms(1000);
}
}
while(1)
{
int main(void)
PORTA=0b11111111;
DDRA=0b00000000;
PORTB=0b11111111;
DDRB=0b00000000;
PORTC=0b00000000;
DDRC=0b11111111;
PORTD=0b00000000;
DDRD=0b11111111;
PORTD=0b11111111;
_delay_ms(2000);
PORTD=0b00000000;
while (1)
{
{
while (PINB7=0) {Manual()};
{
com=(PORTB & 0b00001111)
while (comander=0)
{
_delay_ms(100)
if (com==0b00001011) comander=1
else if (com==0b00001010) comander=2
else if (com==0b00001110) comander=3
else if (com==0b00000000) comander=4
else if (com==0b00001000) comander=6
else if (com==0b00001001) comander=6
else comander=0;
}
}
if (GetEncoder() < comander)
{
PORTC=0b00000000;
while (GetEncoder() < comander) {PORTC=0b00000001};
while ((GetEncoder() = comander) && (PINA7 = 1)) {PORTC=0b00001001};
PORTC=0b00000000;
if (GetEncoder() = comander) {}
else {ErrorHi()};
}
else if (GetEncoder() > comander)
{
PORTC=0b00000000;
while (GetEncoder() > comander) {PORTC=0b00000010};
while ((GetEncoder() = comander) && (PINA7 = 1)) {PORTC=0b00001010}
PORTC=0b00000000;
if (GetEncoder() = comander) {}
else {ErrorLo()}
}
else
{
PORTC2=PINB01;
if (PINA3==0 && PINC2==0) {(PIND0=0) (PIND1=0)}
else if (PINA3==0 && PINC2==1) {(PIND0=0) (PIND1=1)}
else if (PINA3==1 && PINC2==0) {(PIND0=0) (PIND1=1)}
else (PINA3==1 && PINC2==1) {(PIND0=1) (PIND1=1)};
}
}
}
А вот и старый вариант
Порты другие, но сути не меняет
/*
* TCCM.cpp
*
* Created: 16.07.2013 11:20:09
* Author: dombrugov_pv
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
unsigned char E,C;
while(1)
{
// PA.0 - out - display 4 |green;
// PA.1 - out - display 4 |red;
// PA.2 - out - display lock |green;
// PA.3 - out - display lock |red;
// PA.4 - out - display low yellow;
// PA.5 - out - display 2 green;
// PA.6 - out - display N |green;
// PA.7 - out - display N,error |red;
// PB.0 - in - commander 2/4;
// PB.1 - in - commander H/L;
// PB.2 - in - commander Lc;
// PB.3 - in - commander N;
// PB.4 -;
// PB.5 -;
// PB.6 -;
// PB.7 -;
// PC.0 - out - motor UP;
// PC.1 - out - motor DOWN;
// PC.2 - out - front axle control;
// PC.3 -;
// PC.4 -;
// PC.5 -;
// PC.6 -;
// PC.7 -;
// PD.0 - in - encoder A;
// PD.1 - in - encoder B;
// PD.2 - in - encoder C;
// PD.3 - in - encoder P;
// PD.4 -;
// PD.5 -;
// PD.6 - in - front axle state;
// PD.7 - in - motor overload;
PORTA=0x00;
DDRA=0b11111111;
PORTB=0x00;
DDRB=0b00000000;
PORTC=0x00;
DDRC=0b00000111;
PORTD=0x00;
DDRD=0b00000000;
PORTA=0b11111111;
_delay_ms(2000);
PORTA=0b00000000;
START: if ((PIND0==1)&&(PIND1==0)&&(PIND2==0)) // Чтение данных с энкодера;
{
E=0b000001; // преобразование в унитарный код;
PORTA&= ~(1<<2);
PORTA&= ~(1<<3); // Вывод информации;
PORTA&= ~(1<<4); // о состоянии энкодера;
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else if ((PIND0==1)&&(PIND1==1)&&(PIND2==0))
{
E=0b000010;
PORTA|=1<<2;
PORTA|=1<<3;
PORTA&= ~(1<<4);
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else if ((PIND0==0)&&(PIND1==1)&&(PIND2==0))
{
E=0b000100;
PORTA|=1<<2;
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else if ((PIND0==0)&&(PIND1==1)&&(PIND2==1))
{
E=0b001000;
PORTA&= ~(1<<2);
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA&= ~(1<<5);
PORTA|=1<<6;
PORTA|=1<<7;
}
else if ((PIND0==0)&&(PIND1==0)&&(PIND2==1))
{
E=0b010000;
PORTA&= ~(1<<2);
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA&= ~(1<<5);
PORTA|=1<<6;
PORTA|=1<<7;
}
else if ((PIND0==1)&&(PIND1==0)&&(PIND2==1))
{
E=0b100000;
PORTA|=1<<2;
PORTA|=1<<3;
PORTA|=1<<4;
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else
{
goto ERROR;
}
if ((PIND6==0)&&(PINC2==0)) // Вывод информации;
{ // о состоянии;
PORTA&= ~(1<<0); // переднего моста;
PORTA&= ~(1<<1);
}
else if ((PIND6==1)&&(PINC2==1))
{
PORTA|=1<<0;
PORTA|=1<<1;
}
else
{
PORTA&= ~(1<<0);
PORTA|=1<<1;
}
READ_C: if (PINB3==1) // Чтение данных с задатчика;
{ // и преобразование в унитарный код;
C=0b001000;
}
else if ((PINB3==0)&&(PINB1==1))
{
C=0b100000;
}
else if ((PINB3==0)&&(PINB1==0)&&(PINB0==0))
{
C=0b000001;
}
else if ((PINB3==0)&&(PINB1==0)&&(PINB0==1)&&(PINB2==1))
{
C=0b000010;
}
else if ((PINB3==0)&&(PINB1==0)&&(PINB0==1)&&(PINB2==0))
{
C=0b000100;
}
else
{
goto READ_C;
}
// Сравнение состояний энкодера и задатчика;
if (E<C) // Если состояние энкодера ниже задатчика;
UP: {
PORTC&= ~(1<<2);
PORTC|=1<<0; // Вращение энкодера;
PORTC&= ~(1<<1);
if ((PIND0==1)&&(PIND1==0)&&(PIND2==0)) // Чтение данных с энкодера;
{
E=0b000001; // преобразование в унитарный код;
PORTA&= ~(1<<2); // Вывод информации о положении энкодера;
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else if ((PIND0==1)&&(PIND1==1)&&(PIND2==0))
{
E=0b000010;
PORTA|=1<<2;
PORTA|=1<<3;
PORTA&= ~(1<<4);
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else if ((PIND0==0)&&(PIND1==1)&&(PIND2==0))
{
E=0b000100;
PORTA&= ~(1<<2);
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else if ((PIND0==0)&&(PIND1==1)&&(PIND2==1))
{
E=0b001000;
PORTA&= ~(1<<2);
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA&= ~(1<<5);
PORTA|=1<<6;
PORTA|=1<<7;
}
else if ((PIND0==0)&&(PIND1==0)&&(PIND2==1))
{
E=0b010000;
PORTA&= ~(1<<2);
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA&= ~(1<<5);
PORTA|=1<<6;
PORTA|=1<<7;
}
else if ((PIND0==1)&&(PIND1==0)&&(PIND2==1))
{
E=0b100000;
PORTA|=1<<2;
PORTA|=1<<3;
PORTA|=1<<4;
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else
{
goto ERROR;
}
if ((PIND6==0)&&(PINC2==0)) // Вывод информации о состоянии переднего моста;
{
PORTA&= ~(1<<0);
PORTA&= ~(1<<1);
}
else if ((PIND6==1)&&(PINC2==1))
{
PORTA|=1<<0;
PORTA|=1<<1;
}
else
{
PORTA&= ~(1<<0);
PORTA|=1<<1;
} // чтение энкодера;
if ((E==C)&&(PINA3==1)) // Остановка мотора по достижении заданной поз-ии;
{
PORTC&= ~(1<<0);
PORTC&= ~(1<<1);
goto START;
}
else if ((E>C)||(PIND7==1)) // переход к программе ошибки при пропуске заданного положения;
{ // или срабатывании токового реле мотора;
goto ERROR;
}
else
{
goto UP;
}
}
else if (E>C) // Если состояние энкодера выше задатчика;
DOWN: {
PORTC&= ~(1<<2);
PORTC&= ~(1<<0); // Вращение энкодера;
PORTC|=1<<1;
if ((PIND0==1)&&(PIND1==0)&&(PIND2==0)) // Чтение данных с энкодера;
{
E=0b000001; // преобразование в унитарный код;
PORTA&= ~(1<<2); // Вывод информации о положении энкодера;
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else if ((PIND0==1)&&(PIND1==1)&&(PIND2==0))
{
E=0b000010;
PORTA|=1<<2;
PORTA|=1<<3;
PORTA&= ~(1<<4);
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else if ((PIND0==0)&&(PIND1==1)&&(PIND2==0))
{
E=0b000100;
PORTA&= ~(1<<2);
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else if ((PIND0==0)&&(PIND1==1)&&(PIND2==1))
{
E=0b001000;
PORTA&= ~(1<<2);
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA&= ~(1<<5);
PORTA|=1<<6;
PORTA|=1<<7;
}
else if ((PIND0==0)&&(PIND1==0)&&(PIND2==1))
{
E=0b010000;
PORTA&= ~(1<<2);
PORTA&= ~(1<<3);
PORTA&= ~(1<<4);
PORTA&= ~(1<<5);
PORTA|=1<<6;
PORTA|=1<<7;
}
else if ((PIND0==1)&&(PIND1==0)&&(PIND2==1))
{
E=0b100000;
PORTA|=1<<2;
PORTA|=1<<3;
PORTA|=1<<4;
PORTA|=1<<5;
PORTA&= ~(1<<6);
PORTA&= ~(1<<7);
}
else
{
goto ERROR;
}
if ((PIND6==0)&&(PINC2==0)) // Вывод информации о состоянии переднего моста;
{
PORTA&= ~(1<<0);
PORTA&= ~(1<<1);
}
else if ((PIND6==1)&&(PINC2==1))
{
PORTA|=1<<0;
PORTA|=1<<1;
}
else
{
PORTA&= ~(1<<0);
PORTA|=1<<1;
} // чтение энкодера;
if ((E==C)&&(PIND3==1)) // Остановка мотора по достижении заданной поз-ии;
{
PORTC&= ~(1<<0);
PORTC&= ~(1<<1);
goto START;
}
else if ((E<C)||(PIND7==1)) // переход к программе ошибки при пропуске заданного положения;
{ // или срабатывании токового реле мотора;
goto ERROR;
}
else
{
goto DOWN;
}
}
else // Если состояние энкодера соответствует задатчику;
{
if ((PINB0==1)&&(PINB3==0)) // Управление состоянием переднего моста;
{ // в установленном состоянии энкодера;
PORTC|=1<<2;
}
}
goto START;
ERROR: PORTA=0b100000; // Подпрограмма ошибки;
if (PINB1==0) // при некорректной работе энкодера;
{
goto ERR_H;
}
else
{
goto ERR_L;
}
ERR_H: PORTC&= ~(1<<2); // Переключение энкодера;
if (PIND7==1) // в состояние РК 2H по сигналу токового реле;
{
goto ERR_H;
}
else
{
while (PIND7==0)
{
PORTC&= ~(1<<0);
PORTC|=1<<1;
}
}
ERR_H2: PORTC&= ~(1<<0); // Вывод информации о положении энкодера на индикатор;
PORTC&= ~(1<<1);
PORTA&= ~(1<<2);
PORTA&= ~(1<<4);
PORTA|=1<<5;
if ((PIND6==0)&&(PINC2==0)) // Вывод информации о состоянии переднего моста;
{
PORTA&= ~(1<<0);
PORTA&= ~(1<<1);
}
else if ((PIND6==1)&&(PINC2==1))
{
PORTA|=1<<0;
PORTA|=1<<1;
}
else
{
PORTA&= ~(1<<0);
PORTA|=1<<1;
}
if (PINB1==0)
{
goto ERR_H2;
}
else
{
goto ERR_L;
}
ERR_L: PORTC&= ~(1<<2); // Переключение энкодера;
if (PIND7==1) // в состояние РК 4L по сигналу токового реле;
{
goto ERR_L;
}
else
{
while (PIND7==0);
{
PORTC|=1<<0;
PORTC&= ~(1<<1);
}
}
ERR_L2: PORTC&= ~(1<<0); // Вывод информации о положении энкодера на индикатор;
PORTC&= ~(1<<1);
PORTA|=1<<2;
PORTA|=1<<4;
PORTA|=1<<5;
if ((PIND6==0)&&(PINC2==0)) // Вывод информации о состоянии переднего моста;
{
PORTA&= ~(1<<0);
PORTA&= ~(1<<1);
}
else if ((PIND6==1)&&(PINC2==1))
{
PORTA|=1<<0;
PORTA|=1<<1;
}
else
{
PORTA&= ~(1<<0);
PORTA|=1<<1;
}
if (PINB1==1) // Управление состоянием переднего моста;
{
if (PINB0==1)
{
PORTC|=1<<2;
}
else
{
PORTC&= ~(1<<2);
}
goto ERR_L2;
}
else
{
goto ERR_H;
}
}
}
vBulletin® v3.8.6, Copyright ©2000-2025, Jelsoft Enterprises Ltd. Перевод: zCarot