Comments 34
>>Видео, к сожалению, выложить не могу.
а схему?
а схему?
Это случайно не для России в миниатюре?
Макет шикарен!
Интересно зачем для атмеги такой суровый предохранитель?
кучу if (takt == ..) можно заменить на один switch, читается лучше
Да, действительно. Это неплохая мысль. Я просто скорее электронщик, чем программист.
НА куче if получается несколько компактней. По крайней мере на AVR. Впрочем, тут смотреть надо. Может компилятор догадается развернуть кейс через индексный переход. Не всегда, но у него бывают такие озарения. Особенно у IAR.
Если хочется поменьше условных переходов, то можно еще как-нибудь вот так можно написать.
unsigned int servo = takt / 2; // or takt >> 1
unsigned char angle = angles[servo];
unsigned char mask = 1 << servo;
if (takt & 1) {
PORTC &= ~mask;
OCR1A = cycle - angle;
} else {
PORTC |= mask;
OCR1A = angle;
}
takt = (takt + 1) & 7;
максимальная скважность получается 25%?
>Примечательно, что оба аналоговых привода могли работать нормально только пару минут, а потом начинать произвольно дергаться, зажиматься в крайних положениях и т.д.
Вот это в высшей мере странно. Т.к. электроники внутри там почти нет. Тупейшая разностная схема которая зависит ТОЛЬКО от импульсов. А импульсы часом никуда не уплыли?
Бегло глянул на алгоритм — импульсы идут друг за другом, ширина периода каждого зависит от ширины соседей получается?
Вот это в высшей мере странно. Т.к. электроники внутри там почти нет. Тупейшая разностная схема которая зависит ТОЛЬКО от импульсов. А импульсы часом никуда не уплыли?
Бегло глянул на алгоритм — импульсы идут друг за другом, ширина периода каждого зависит от ширины соседей получается?
Сервоприводы специально ездил проверял в магазин — с сервотестером любые работают отлично, но с моей электроникой аналоговые дружить отказываются. Я не смог найти этому никого разумного объяснения.
Частота фиксированная и определяется переменной cycle (хотя ее надо было прописать как константу). У меня период следования был 18000мкс и четыре привода. Я записал в cycle 4500мкс — это время которое отводится на управление одним приводом, то есть передние фронты импульсов задержаны друг относительно друга именно на это время. Когда прерывание вызывается в первый раз — в OCR1A записывается длительность импульса управления приводом. При втором срабатывании, когда импульс заканчивается, в OCR1A записывается (cycle-angle1), время которое осталось до перехода к следующему приводу. В итоге частота следования импульсов не зависит от длительности управляющих импульсов.
Частота фиксированная и определяется переменной cycle (хотя ее надо было прописать как константу). У меня период следования был 18000мкс и четыре привода. Я записал в cycle 4500мкс — это время которое отводится на управление одним приводом, то есть передние фронты импульсов задержаны друг относительно друга именно на это время. Когда прерывание вызывается в первый раз — в OCR1A записывается длительность импульса управления приводом. При втором срабатывании, когда импульс заканчивается, в OCR1A записывается (cycle-angle1), время которое осталось до перехода к следующему приводу. В итоге частота следования импульсов не зависит от длительности управляющих импульсов.
Чудес не бывает. Берите осциллограф и сравнивайте сигнал сервотестера и своей схемы. Где то там косяк. Скорей всего джиттер.
Ну, а из плюсов алгоритма. ОЧЕНЬ быстрая обработка прерывания. Это большой большой плюс.
Минус — плавающая частота импульсов. Не критично для положения, но критично для скорости вращения. Впрочем, тут не подразумевается разгон серв, так что влияние незначительное.
Минус — плавающая частота импульсов. Не критично для положения, но критично для скорости вращения. Впрочем, тут не подразумевается разгон серв, так что влияние незначительное.
Почему не подходит аппаратный ШИМ на контроллерах?
потому что их обычно 1-3, а для, скажем, гексапода нужно 12-16
у 16 максимальная скважность будет 100/16
тут лучше использовать внешний PWM, например, tlc5940
тут лучше использовать внешний PWM, например, tlc5940
Вставлю свои пять копеек.
За реализацию респект, уважение и поклон!
Но, критика ценнее и важнее похвалы. Тот кусок кода, который вы привели совершенно не переносим. Более того, если вы переставите сервопривод на другую ножку другого порта, вам придётся перепахивать всю программу.
Конкретно вот: PORTC |= 0b00000001; — это форменное безобразие и источник множества ошибок и проблем! Как это искоренить в коде я не представляю. Самое ужасное, что если вы решите перенести код на другой процессор, или о боже, другой архитектуры, то это будет весьма тривиальной задачей.
Я даже намедни писал пост по теме: dlinyj.livejournal.com/593356.html
Собтвенно говоря, у меня был случай, разные релюшки были разбросаны по разным портам и пинам, полностью хаотично. Самый лучший конечно способ был сделать структуру портов и масок, но у меня нехватило памяти контроллера и запала, и я сделал примерно так:
И далее обращался к ним вот так:
Самое ценное, что поменяв только дефайны, я могу перенести данный код даже на другой процессор, если там другой тип обращения к пинам порта, то поменять мкакросы. Мне не нужно для этого будет перепахивать весь код.
Надеюсь комментарий будет полезен.
За реализацию респект, уважение и поклон!
Но, критика ценнее и важнее похвалы. Тот кусок кода, который вы привели совершенно не переносим. Более того, если вы переставите сервопривод на другую ножку другого порта, вам придётся перепахивать всю программу.
Конкретно вот: PORTC |= 0b00000001; — это форменное безобразие и источник множества ошибок и проблем! Как это искоренить в коде я не представляю. Самое ужасное, что если вы решите перенести код на другой процессор, или о боже, другой архитектуры, то это будет весьма тривиальной задачей.
Я даже намедни писал пост по теме: dlinyj.livejournal.com/593356.html
Собтвенно говоря, у меня был случай, разные релюшки были разбросаны по разным портам и пинам, полностью хаотично. Самый лучший конечно способ был сделать структуру портов и масок, но у меня нехватило памяти контроллера и запала, и я сделал примерно так:
#define port_rele1 PORTA
#define ddr_rele1 DDRA
#define pin_rele1 PINA
#define port_rele2 PORTC
#define ddr_rele2 DDRC
#define pin_rele2 PINC
...
//pins define
//port_rele1
#define K1 5
#define K2 4
#define K3 3
#define K4 0
#define K5 1
#define K6 2
...
И далее обращался к ним вот так:
//инициализация
void hardware_init()
{
ddr_rele1 |=(1<<K1)|(1<<K2)|(1<<K3)|(1<<K4)|(1<<K5)|(1<<K6);
port_rele1 |=(1<<K1)|(1<<K2)|(1<<K3)|(1<<K4)|(1<<K5)|(1<<K6);
ddr_rele2 |=(1<<K7)|(1<<K8)|(1<<K9);
...
}
//для включения, выключения пользовался макросами
#define BIT_ON(port, bit) do {port |= (1<<bit);}while(0)
#define BIT_OFF(port, bit) do {port &=~(1<<bit);}while(0)
#define BIT_INVERCE(port, bit) do {port ^=(1<<bit);}while(0)
//пример управления:
BIT_OFF(port_rele1,K1);
Самое ценное, что поменяв только дефайны, я могу перенести данный код даже на другой процессор, если там другой тип обращения к пинам порта, то поменять мкакросы. Мне не нужно для этого будет перепахивать весь код.
Надеюсь комментарий будет полезен.
Кодеры с работы тоже долго надо мной потешались. Просто у меня была очень конкретная разовая задача и я не уделил должного внимания улучшению читаемости кода. Кроме того здорово сроки поджимали — я перепробовал кучу вариантов управления и времени на финальную реализацию почти не осталось. Написал «чтобы работало». Следующим шагом будем думать над красотой кода. Спасибо за пример.
Я так понимаю этот комментарий был адресован мне :).
Не нужно уделять внимание читаемости кода. Надо писать сразу читаемый код. Я начинаю написание программы с определения дефайнов, констант и конструкций. Ну так же флагов глобальных переменных, которые доввожу по мере необходимости.
Здесь нет места красоте. Поймите, что такой подход сокращает время разработки и уменьшает количество ошибок раз в десять!
Не нужно уделять внимание читаемости кода. Надо писать сразу читаемый код. Я начинаю написание программы с определения дефайнов, констант и конструкций. Ну так же флагов глобальных переменных, которые доввожу по мере необходимости.
Здесь нет места красоте. Поймите, что такой подход сокращает время разработки и уменьшает количество ошибок раз в десять!
> Надо писать сразу читаемый код
Ну у вас тоже неидеальный код.
К примеру, стандарт де-факто — написание дефайнов только капсом.
Или я так и не понял что за сумрачная конструкция do/while(0), её используют часто для того чтобы можно break юзать вместо goto, но в данном случае совсем мне непонятно.
В дефайнах параметры не обернуты скобки:
BIT_ON(BASE_PORT + PORT_STATUS, 2 * i) очень удивит компилятор.
Ну и всякая мелочевка типа рандомной расставки пробелов.
Ну у вас тоже неидеальный код.
К примеру, стандарт де-факто — написание дефайнов только капсом.
Или я так и не понял что за сумрачная конструкция do/while(0), её используют часто для того чтобы можно break юзать вместо goto, но в данном случае совсем мне непонятно.
В дефайнах параметры не обернуты скобки:
BIT_ON(BASE_PORT + PORT_STATUS, 2 * i) очень удивит компилятор.
Ну и всякая мелочевка типа рандомной расставки пробелов.
Ну и всякая мелочевка типа рандомной расставки пробелов.это не мой косяк, а косяк интрепретации хабром моего кода. У меня всё ок.
К примеру, стандарт де-факто — написание дефайнов только капсомвпервые слышу, быть может это конечно и оправданно.
Или я так и не понял что за сумрачная конструкция do/while(0), её используют часто для того чтобы можно break юзать вместо goto, но в данном случае совсем мне непонятно.Это макросы Аскольда Волкова, на сколько я понимаю использование ду вайл делается для переносимости кода. Не разбирался.
BIT_ON(BASE_PORT + PORT_STATUS, 2 * i) очень удивит компилятор.Силился представить, когда мне понадобится такая конструкция, но не смог… В особенности конструкция BASE_PORT + PORT_STATUS — не будет работать со сто процентной вероятностью. Но про скобки возможно замечание и стоящее.
> Это макросы Аскольда Волкова
Вспомнил еще один вариант:
Если макрос состоит из нескольких выражений (#define SOME_MACRO bla(); bla();), то нужно обернуть в {}.
Но в данном случае это не так (всего одно выражение), и разворачивать его в блок совсем не обязательно.
> впервые слышу, быть может это конечно и оправданно.
Я видел в нескольких code style стандартах.
В том числе и гугловском.
> Силился представить, когда мне понадобится такая конструкция, но не смог…
Ну к примеру есть 4 порта — 2 набора команды/данные для 2х внешних устройств.
Выясняем при конфигурации к какому набору подрублено устройство, и пишем в переменную base_port, а к самим портам уже обращаемся через base_port + PORT_DATA и base_port + PORT_COMMAND. В компьютерном железе такое не редкость. В МК думаю тоже бывает.
Вспомнил еще один вариант:
if (XXX)
SOME_MACRO();
Если макрос состоит из нескольких выражений (#define SOME_MACRO bla(); bla();), то нужно обернуть в {}.
Но в данном случае это не так (всего одно выражение), и разворачивать его в блок совсем не обязательно.
> впервые слышу, быть может это конечно и оправданно.
Я видел в нескольких code style стандартах.
В том числе и гугловском.
> Силился представить, когда мне понадобится такая конструкция, но не смог…
Ну к примеру есть 4 порта — 2 набора команды/данные для 2х внешних устройств.
Выясняем при конфигурации к какому набору подрублено устройство, и пишем в переменную base_port, а к самим портам уже обращаемся через base_port + PORT_DATA и base_port + PORT_COMMAND. В компьютерном железе такое не редкость. В МК думаю тоже бывает.
А вот и видео разрушителя с краном:
и экскаватора:
как говорил раньше — кран все-равно вибрирует, но это уже связано с конструкцией.
и экскаватора:
как говорил раньше — кран все-равно вибрирует, но это уже связано с конструкцией.
Когда начал читать, думал все привода будут задействованы в одной модели на разные движения.
ЗЫ. Плата у вас красивая получилась. ЛУТ?
ЗЫ. Плата у вас красивая получилась. ЛУТ?
Хотели изначально сделать чтобы клешня у разрушителя открывалась, но в итоге не получилось и двигается только три модели машин.
Для изготовления плат давно уже использую ЛУТ.
Для изготовления плат давно уже использую ЛУТ.
Sign up to leave a comment.
Управление несколькими сервоприводами с высокой точностью на МК ATmega16