Никогда не показывай дураку сделанное наполовину.

Меню навигации для мобильных

Как засветить светодиоды от AVR через сдвиговый регистр?

Автор Shaman, 23 Сен., 2020, 19:16

« предыдущая - следующая »

Shaman

Захотелось мне сделать свой космический модуль... Задний фонарь для велосипеда с указанием поворотов и стоп-сигналом. Поскольку лепить такое на ардуино будет слишком громоздким и есть потребность поглубже разобраться в написании программ на Си решено было использовать голый микроконтроллер. Потому как отдельных светодиодов зажечь нужно много (27) решено было использовать сдвиговые регистры 74HC595 (4шт.). Микроконтроллер был взят Atmega328P как самый доступный.
Поскольку в AVR я новичок то решил разбить задачу на этапы и первый из них это написать процедуру, которая будет из некоего буфера экрана брать данные и вносить их последовательно в регистры.
Немного побившись головой об стол получил вот такой код:
Немного побившись головой об стол получил вот такой код:
#define F_CPU 8000000UL
#include <avr/io.h>
#define _data 3  // устанавливаем пин вывода данных в сдвиговые регистры
#define _clock 4 // устанавливаем пин clock
#define _latch 5 // устанавливаем пин защёлки
#define SIZE_SCREEN 4
unsigned char buffer [SIZE_SCREEN]; //создаем буфер буфер экрана (выяснить какой тип данных применить) 


int record (); //прототип функции для записи в сдвиговые регистры

int main (void) 
{

    DDRC = 0b00111000;
    PORTC |= (1<<_clock); // устанавливаем clock в 1 для предотвращения ложного срабатывания
    PORTC |= (1<<_latch); // поднимаем защелку
    PORTC |= (1<<_data); // устанавливаем на выходе дата 1       
    
}
int record ()
{
    PORTC &= ~(1<<_latch); // опускаем защелку
  for (int x = 0; x<SIZE_SCREEN; x++) //цикл для переборки байтов в массиве (уточнить как храниться число)
  {
       for (int y = 0; y<8; y++) // цикл для перебора битов в байте и подачи их на выход
        {
            if ((buffer[x] & (1<<y)) == 0) // накладываем побитовую маску для считывания нужного бита
            {
                PORTC &= ~(1<<_data); // если ответ 0, выставляем на входе регистра 0
            }
            else 
            {
                PORTC |= (1<<_data); //иначе, выставляем на входе регистра 1
            }
            PORTC &= ~(1<<_clock); // устанавливаем clock в 0, переводим регистр в режим ожидания данных
            PORTC |= (1<<_clock);  // устанавливаем clock в 1, записываем данные в регистр
        }
  }
    PORTC |= (1<<_latch); // поднимаем защелку включаем светодиоды
}
[свернуть]
Он даже скомпилировался значит явных ошибок нет. Осталось выяснить как его можно проверить на железе или в эмуляторе.
А пока уважаемые гуру не могли бы вы проверить меня на ошибки?

Slabovik

#1
Насколько я понимаю, схема подключения регистров выглядит примерно так
Подсоединение каскада регистров
Схема-регистров-595.png
[свернуть]

Для начала надо точно понимать, что собой представляет 74HC595 (она же ИР52 в отечественной системе обозначений). Думаю, с этим проблем нет, но я всё-таки нарисую, чтобы было куда пальцем тыкать (это важно т.к. устраняет неопределённости, когда один думает об одном, а другой о другом и в итоге друг друга не понимают).

ИР52 по внутреннему строению состоит как бы из двух микросхем. Вот как-то так
Внутри регистра
Схема-регистров-595-внутри.png
[свернуть]
Т.е. внутри у неё фактически два регистра, первый регистр сдвига, куда данные можно задвигать при помощи сигналов D (Data) и C (Clock). При этом процесс записи и сдвига (вообще, сдвиг - это тоже запись) происходит строго по фронту (переходу из 0 в 1) сигнала Clock. Чисто логически это можно изобразить так
Работа регистра
Схема-регистров-595-сдвиг.png
[свернуть]

Такая связка двух микросхем в одном корпусе весьма удобна - поскольку выходной регистр "непрозрачный", то на его выходах всегда хранится информация, которая там запомнилась при переходе сигнала OC (по схеме Ready) из 0 в 1. Получается, что пока мы "двигаем" Data в регистре сдвига, на выходах Q0~Q7 информация "стоит как вкопанная" с прошлого раза. И только когда мы "дёрнем" OC, на выходах появится то, что мы только что записывали в регистр сдвига. Очень, очень удобно  :)

Диаграмма записи всех 8 бит в регистр типа 595 рекомендую делать такого вида
Диаграмма записи
Схема-регистров-595-процедура.png
[свернуть]

Что тут получается. Самое главное: опускаем сигнал Clock -> выставляем сигнал Data (очередной бит, который мы ходим задвинуть) -> поднимаем сигнал Clock. Делаем так столько раз, сколько бит нам нужно записать в регистр. В случае 4-х последовательно включенных регистров 595, сделать так надо 32 раза (есть исключения, но они связаны уже со схемотехникой. Вдруг, например, выводы Q4~Q7 самого правого (по схеме выше) регистра никуда не подключены - тогда их и заполнять не надо, а значит, сдвигов можно делать меньше).

Сигнал Ready рекомендую (именно рекомендую, т.к. фактически, опустить, т.е. сделать 0, его можно в любой момент) опускать ровно перед тем, как соберётесь записывать Data в регистр сдвига. А как всё запишете - поднимайте. Дело в том, что такое поведение сигнала Ready даёт возможность контролировать физически (осциллографом), когда у вас началась процедура записи, а когда закончилась, что облегчает ловлю багов (не хвалитесь - баги всегда есть, даже если вы их не видите).

Посмотрим код. Вот честно... каждый раз говорю, что Си, а тем более ++ я знаю весьма фигово. То ли дело - ассемблер :)
Цитата: Shaman#define _data 3  // устанавливаем пин
Наверное, не устанавливаем пин, а назначаем разряд порта.
Да, тут надо аккуратно, потому что здесь есть одно место, порождающее глобальную путаницу при малейшей неосторожности (я тоже постоянно путаюсь).
Биты порта нумеруются с 1 по 8. А вот разряды с 0 по 7. При этом 1-й бит порта - это 0-й разряд, второй бит - первый разряд и т.д. Я тоже буду путать(ся) в показаниях - имейте это в виду и следите за руками  :)

про дефайны
При таком способе назначений (define) для работы с портами используется записи типа
Цитата: ShamanPORTC |= (1<<_clock)
Честно говоря, мне совсем не нравятся эти записи "1<<Clock". Считаю, что в этом месте надо чётко писать "Clock" без всяких 1<<

Для этого надо... всего лишь привести в порядок дефайн. Например, для Data это должно выглядеть так
#define _data 8  // назначаем разряд 3 порта как сигнал Data
Это потому что на порт посмотреть как на биты, слева старший, справа младший, то он выглядит как бинарное число '00001000', т.е. '8' в нашем обиходе.

Да, '8' надо записать просто потому, что говорят, в Си, нет возможности записывать бинарные числа. Или врут? Я в коде вижу бинарную запись. Удивительно...
Впрочем, можно (и может даже нагляднее) использовать такое определение
#define _data 1<<3  // назначаем разряд 3 порта как сигнал Data
Здесь записано, что 1 сдвинута логически влево три раза, т.е. на три разряда. Бинарно это выглядит так
00000001(bin) << 3 получаем 00001000(bin)Точно также определим "_clock"
#define _clock 1<<4 // назначаем разряд порта для сигнала clock
Если сделать так, то процедура изменения значения порта станет выглядеть так
PORTC |= _clock; // устанавливаем clock в "1"
PORTC &= ~_clock; // устанавливаем clock в "0"
[свернуть]
Алгоритм вроде верен, но на 100% ручаться не могу. Единственное, проход битов производится от 0 до 7, что означает, что первым будет задвигаться в регистр младший бит, затем старший, а на микросхемах выходы маркированы наоборот. Это совершенно ничего не значит, просто надо иметь ввиду, что Q7 микросхемы в этом случае соответствует младшему биту буфера (в проектах даже можно переименовывать имена выводов микросхем, чтобы они соответствовали их реальной логической роли).
Да, разве что установку _clock в 0 я бы я бы делал не после установки _data, а перед. Но можно и так оставить, если между установкой _clock в "0" и установкой в "1" вставить какой-нибудь nop.

Для проверки работоспособности можно в CharBuffer записать какие-нибудь значения и поморгать (в цикле, естественно, не быстром)

зы: долго вспоминал, как выглядят логические опреаторы. В итоге Вики (https://ru.wikipedia.org/wiki/%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80%D1%8B_%D0%B2_C_%D0%B8_C%2B%2B) сдала все  :)

зы:зы: Просвещаюсь дальше: оказывается, в Си нет типа "byte" (https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1%82%D0%B8%D0%BF%D0%BE%D0%B2_%D0%A1%D0%B8). Похоже, что тип "char" - вполне нормально для данного места. Хотя если определить его как "unsigned char", то он видимо и будет полностью равноценен типу "byte", который я точно где-то использовал...

зы:зы:зы: С вашего позволения продолжу.
Могу показать свой ассемблерный код, которым я делаю то же самое на этом же (точнее, на 168, но это те же штаны) процессоре.
↓ спойлер ↓
AsmCode.png
[свернуть]
Это один файл, просто я показал нужные места. Если чуть помедитировать, то станет видна та же картина: имеется буфер под выводимые на индикатор символы и процедура вывода этих символов, чуть украшенная ещё парой входов на "гасим всё" и "зажигаем всё".

И не стесняйтесь на комментарии того, что вы делаете в своём коде. Обильное комментирование - залог понимания кода, когда к нему вдруг придётся вернуться.

И вот как чисто физически, на осциллографе (весьма хреновеньком, надо сказать) выглядит бурст обновления "экрана".
AsmCode_Burst_8_bytes.png AsmCode_Burst.png
Синий луч - сигнал "Clock", жёлтый - сигнал "Rdy" (Ready, Latch, OC...). Data не показана - там не так явно.
Частота процессора здесь 14,3 МГц, в пределах одного байта частота следования битов 1 МГц, а между байтами видна небольшая задержка в передаче - всё согласно программы: между байтами ведь выполняется несколько больше действий, нежели между битами.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Благодарю за столь подробный ответ!  :o  :)

С вашей помощью немного оптимизировал код.
Сейчас он выглядит так:
#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#define _data_1 PORTC |= (1<<3)      // Выбираем пин для вывода данных в сдвиговые регистры и устанавливаем на нём 1 путём установки порта и назначения разряда соответствующего пину
#define _data_0 PORTC &= ~(1<<3)     // -.- устанавливаем на нём 0 -.-
#define _clock_high PORTC |= (1<<4)  // Выбираем пин clock и устанавливаем на нём высокий уровень
#define _clock_low PORTC &= ~(1<<4)  // Выбираем пин clock и устанавливаем на нём низкий уровень
#define _latch_up PORTC |= (1<<5)    // Выбираем пин защёлки и поднимаем её
#define _latch_down PORTC &= ~(1<<5) // Выбираем пин защёлки и опускаем её
#define SIZE_SCREEN 4                //размер буфера экрана
unsigned char buffer [SIZE_SCREEN];  //создаем буфер буфер экрана


int record (); //прототип функции для записи в сдвиговые регистры

int main (void) 
{

    DDRC = 0b00111000; //устанавливаем необходимые пины на вывод
    _clock_high;       // для предотвращения ложного срабатывания
    _latch_up;         // поднимаем защелку    
    while (1)
    {
        buffer[0] = 0b01010101;
        buffer[1] = 0b01010101;
        buffer[2] = 0b01010101;
        buffer[3] = 0b01010101;
         record (); 
        _delay_ms(50);
        buffer[0] = 0b10101010;
        buffer[1] = 0b10101010;
        buffer[2] = 0b10101010;
        buffer[3] = 0b10101010;
        record (); 
        _delay_ms(50);               
    }
   
    
}
int record ()
{
    _latch_down;                           // опускаем защелку
    
  for (int x = 0; x<SIZE_SCREEN; x++)      //цикл для переборки байтов в массиве (уточнить как храниться число)
  {
       for (int y = 7; y>=0; y--)          // цикл для перебора битов в байте и подачи их на выход
        {   
            _clock_low;                    // переводим регистры в режим ожидания данных   
            
            if ((buffer[x] & (1<<y)) == 0) // накладываем побитовую маску для считывания нужного бита
            {
                _data_0;                   // если ответ 0, выставляем на входе регистра 0
            }
            else 
            {
                _data_1;                   //иначе, выставляем на входе регистра 1
            }
            
            _clock_high;                   // записываем данные в регистр
            
        }
  }
    _latch_up;                            // поднимаем защелку, включаем светодиоды
}
[свернуть]
И работает
[свернуть]

Команды дерганья пинов решил полностью вывести в дефайны и сделать их 2-х видов выставление 1 и 0.
В противном случае, даже при использовании ваших примеров, при использовании других выводов и портов либо я не смогу назначить им уникальные имена поскольку все эти варианты
#define _data 8  // назначаем разряд 3 порта как сигнал Data
#define _data 1<<3  // назначаем разряд 3 порта как сигнал Data
#define _clock 1<<4 // назначаем разряд порта для сигнала clock
PORTC |= _clock; // устанавливаем clock в "1"
PORTC &= ~_clock; // устанавливаем clock в "0"
[/quote]
означают что я всего лишь заменяю операцию сдвига бита или вообще номер бита уникальным именем, а эта операция используется по отношению ко всем портам.
А вот мой вариант
#define _data_1 PORTC |= (1<<3)      // Выбираем пин для вывода данных в сдвиговые регистры и устанавливаем на нём 1 путём установки порта и назначения разряда соответствующего пину
#define _data_0 PORTC &= ~(1<<3)     // -.- устанавливаем на нём 0 -.-
#define _clock_high PORTC |= (1<<4)  // Выбираем пин clock и устанавливаем на нём высокий уровень
#define _clock_low PORTC &= ~(1<<4)  // Выбираем пин clock и устанавливаем на нём низкий уровень
#define _latch_up PORTC |= (1<<5)    // Выбираем пин защёлки и поднимаем её
#define _latch_down PORTC &= ~(1<<5) // Выбираем пин защёлки и опускаем её
это уже использование конкретного бита на конкретном порту и буде возникнет необходимость поменять порт и бит нужно всего лишь исправить несколько строчек кода, а не шарахаться по всему листингу.

И да простят меня прожженные AVR-щики  :), но я буду комментировать что выбираю я именно пины просто так привык со среды Arduino. Там оперирование идет именно пинами, а не абстрактными регистрами и битами. Просто подкорректирую формулировку дабы было понятно, что оперирование идет именно через регистры памяти привязанные к пинам и биты в них.

Сейчас буду думать в каком виде хранить данные выводимые на экран и как их передавать в буфер. Если подскажут идею не обижусь.  ;)   ;D

Slabovik

Ну вот, а то всё "мама, мама"!  ;)
Вполне нормально, разве что потакание привычке называть разряд порта пином в будущем приведёт к непоняткам (видимо, ещё не сталкивались с другим клаcсом чипов, где вывод порта можно назначать на определённый пин).

Для раскладки экрана могу порекомендовать изобрести "шрифт". Подобно тому, как изобретают (назначают) "шрифт" всяким семисегментным индикаторам. Пусть, например, там будут "буквы": 1) габарит 2) стоп 3) влево 4) вправо, и ещё какие-нибудь (я слабо представляю, что там в реальности творится). По событиям контроллер будет выводить "буквы" в экранную область памяти и вызывать вот эту процедурку, которая заработала чуть выше. Я так думаю...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Slabovik

Интересно, что есть у КМОП-серии аналогично работающая микросхема типа CD4094.
Традиционная КМОП (не 74HC) серий 561, 1561 хотя и более медленная, но умеет работать в гораздо более широком диапазоне питающих напряжений, о трёх до 15 вольт. Если полистать старые журналы, то быстро найдётся устройства, где питание таких микросхем (всяких часов, автоматики и т.п.) производится батарейкой "Крона" или напрямую от бортсети автомобиля (12 вольт).

Кстати, фишка. Некоторые, вроде аналогично работающие микросхемы, отличаются типом защёлки на OC (Output Clock). У одних там именно защёлка (Latch), работающая по уровню сигнала (низкий - хранение, высокий - прозрачно), а у других по фронту (flip-flop).
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Доброго времени суток продолжаю клепать свой космический модуль. И сейчас уткнулся в проблему. Гуру помогите разобраться  :)
Для того чтобы сделать  оранжевый свет на RGB светодиоде, ток на зелёном был просто ограничен (этот пиксель нужен только для оранжевого), а красный регулируется через шим. Поскольку его нужно светить ещё и м полную яркость.
Шим подаётся на 13 ногу U2 и U4 каждая из которых отвечает за группу красных и синих светодиодов.

Fonar.jpg

Поясню, на рисунке 1 светододы сверху в низ синие, зелёные, красные.
Зелёные подключены к U3
Синие слева и красные справа по 4 шт. к U2
Красные слева и синие справа по 4 шт. к U4
И 3 светодиода в центре к U5

Мне нужно получить вот такой эффект
[свернуть]

И вод здесь у меня возникли трудности
Сейчас мигание после бегущих огней организовано просто перезаписью буфера экрана и задержкой через delay

кусок кода
int left_stop ()
{
    _scvagnost;
    _pwm_on
    _pwm_open_BrRl;
    _Null;
    _Green = ABC[4];
    _Blue_right_Red_left = ABC[4];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[5];
    _Blue_right_Red_left = ABC[5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[6];
    _Blue_right_Red_left = ABC[6];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7];
    _Blue_right_Red_left = ABC[7];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7] | ABC [4];
    _Blue_right_Red_left = ABC[7] | ABC [4];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7] | ABC [6];
    _Blue_right_Red_left = ABC[7] | ABC [6];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [4];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [4];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Red_right_Blue_left =  ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _pwm_close_BrRl;
    _pwm_off;
}     
[свернуть]

Но мне хотелось бы реализовать это через тот же 13 пин

И собственно вот. Не могу реализовать это с помощью простейших логических элементов.

Имеем.
1 вывод ШИМ
2 максимум 3 вывода микроконтроллера для управления коммутацией из.

Задача.
Коммутировать
Шим только на правую или левую часть.
Возможность вместо шим или при её отсутствии подавать на вывод 13 0 или 1.

Дополнение. Если выключить шим командой TCCR1B &= ~(1<<CS10), то на выходе останется тот уровень, который был последним и управлять им через выставления бита порта нельзя.

Постарался написать как можно внятнее если расписывать дальше получается поток мыслей. Если что-то не понятно спрашивайте, дополню.

Slabovik

Что-то мне подсказывает (могу быть не прав т.к. до железа сейчас не дотянуться), что состояние пина, на который выводится ШИМ сигнал, можно контролировать, записывая в регистр счётчика таймера так называемое начальное значение (т.е. просто значение), с которого счётчик начнёт счёт, если вновь запустить его. Потому что то, что выводится на пин, зависит от содержимого этого счётчика, которое внутри сравнивается с содержимым регистра сравнения (цифровым компаратором), по результату чего и формируется состояние пина. Если в регистр счётчика записать нужное значение, то можно предопределить состояние пина.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Не помогает либо я не правильно пишу код
пробую записать во весь регистр целиком
TCCR1B &= ~(1<<CS10);    //выключаем шим
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
[свернуть]

и в каждый по отдельности
_TCCR1B &= ~(1<<CS10);    //выключаем шим 
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
[свернуть]

 Регистр сравнения устанавливаю командой OCR1A = 256 На 10-ти битном счетчике это середина
На выходе после отключения рандомное состояние (читай последнее установленное до выключения)

Slabovik

Честно говоря, в последние дни был пригружен по работе, поэтому в код не вникал. Но вот тебе картинка, может поможет

Включить-ШИМ.png

Для управления "жёстким" включить-выключить нужно две управляющие ноги.
Нога "Б" включает 100% яркость.
Нога "А" выключает. Но преимущество за ногой "Б", нога "А" не может выключить, если включена нога "Б".

Микросхема КР1564ЛЕ1, она же 74HC02. Одной микросхемы достаточно для управления двумя каналами (потому что в ней четыре логических элемента).

Да, ШИМ получается инвертированный, 100% заполнение на выходе контроллера соответствует минимальной яркости.

Нет ЛЕ1 - возьми универсальную микросхему всех времён и народов ЛА3

Включить-ШИМ-2.png

Немного изменится логика работы. Но может так даже удобнее, тут уж по вкусу...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Прошло и полтора года, но проект пока не умер и на данный момент находиться вот в такой стадии:
Fonar.jpg

Код U1 (отвечает за управление индикацией)
#define F_CPU 1000000UL                   //Скорость контроллера
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include <avr/interrupt.h>
// настраиваем скорость прердачи UART
#define BAUD 9600
#define MYUBRR F_CPU/16/BAUD-1
//
#define _data_1 PORTC |= (1<<3)           // Выбираем пин для вывода данных в сдвиговые регистры и устанавливаем на нём 1 путём установки порта и назначения разряда соответствующего пину
#define _data_0 PORTC &= ~(1<<3)          // -.- устанавливаем на нём 0 -.-
#define _clock_high PORTC |= (1<<4)       // Выбираем пин clock и устанавливаем на нём высокий уровень
#define _clock_low PORTC &= ~(1<<4)       // Выбираем пин clock и устанавливаем на нём низкий уровень
#define _latch_up PORTC |= (1<<5)         // Выбираем пин защёлки и поднимаем её
#define _latch_down PORTC &= ~(1<<5)      // Выбираем пин защёлки и опускаем её

//Выбор портов для управления морганием и управлением PWM
#define _flash_G_hight PORTD |= (1<<2)    //Выбираем пин out enable для Green и устанавливаем на нём высокий уровень
#define _flash_G_low PORTD &= ~(1<<2)     //Выбираем пин out enable для Green и устанавливаем на нём низкий уровень
#define _flash_RGB_hight PORTD |= (1<<3)  //Выбираем пин out enable для _RGB и устанавливаем на нём высокий уровень
#define _flash_RGB_low PORTD &= ~(1<<3)   //Выбираем пин out enable для _RGB и устанавливаем на нём низкий уровень
#define _pwm_close_RrBl PORTC |= (1<<1)   //устанавливаем на пине 1 порта C 1  
#define _pwm_open_RrBl PORTC &= ~(1<<1)   //устанавливаем на пине 1 порта C 0 тем самым пропуская PWM на выход
#define _pwm_close_BrRl PORTC |= (1<<2)   //устанавливаем на пине 2 порта C 1 
#define _pwm_open_BrRl PORTC &= ~(1<<2)   //устанавливаем на пине 2 порта C 0 тем самым пропуская PWM на выход
#define _pwm_pass PORTC &= ~(1<<0)        //пропускаем PWM на вход элемента 2ИЛИ-НЕ Б
#define _pwm_drop PORTC |= (1<<0)         //не пропускаем PWM на вход элемента 2ИЛИ-НЕ Б
#define _pwm_on TCCR1B |= (1<<CS10)       //включаем шим
#define _pwm_off TCCR1B &= ~(1<<CS10)     //выключаем шим

//Блок регулировки задержек
#define _turn_signal_spd _delay_ms(100)   //скорость бегущей строки поворотника
#define _turn_signal_fsdp _delay_ms(200)  //скорость моргания поворотника
#define _blink_spd _delay_ms(200)          //скорость моргания моргалки ))

#define _scvagnost OCR1A = 700            //устанавливаем регистр сравнения рагулируя яркость краcных светодиодов по отношению к зелёным

#define SIZE_ABC 13                       //размер алфавита
#define SIZE_STATUS 4                     //размер буфера статуса
#define SIZE_SCREEN 4                     //размер буфера экрана
unsigned char buffer [SIZE_SCREEN];       //создаем буфер буфер экрана
uint8_t step = 1;                         //создаем глобальную переменную подсчёта шагов


// присваиваем имя значению в буфере экрана оно означает цвет светодиода
#define _Red_right_Blue_left buffer[3]              
#define _Green buffer[2]
#define _Blue_right_Red_left buffer[1]
#define _RGB buffer[0]

#define _Null memset (buffer, 0, 4)  //обнуление буфера экрана


//Алфавит
unsigned char ABC [SIZE_ABC] =       
{						
        0b00001000,  //эффект						
        0b00000100,							
        0b00000010,							
        0b00000001,							
        0b00001001, // для						
        0b00000101,
        0b00000011,					
        0b00001011,							
        0b00000111, // поворотника 
        0b11110000, // разновидности
        0b00001111, // стопов
        0b00000000, // нет сигнала
}; 
// конец алфавита

//Массив состояния
unsigned char status [SIZE_STATUS] =
{
      0b00001000,   /*Состояние эффекта начиная с младшего бита по порядку:
                        0 Правый поворот
                        1 Левый поворот
                        2 Стоп
                        3 Моргалка
                        7 Позволяет включить или выключить стоп во время моргания поворотником (взводится при изменении состояния сигнала стоп)*/
      1,            // Шаг эффекта поворот
      1,            // Шаг Эффекта моргалка
      0b00000000,   //Флаги
};
//Массив буфера приёма
unsigned char rx_buf [3] =
{
        //Буфер приёма
        0b00000000, 
        0b00000000,
        
};
int8_t num_rx_buf = 0;


void USART_Init( unsigned int ubrr);                                 //Функция включения USART
void USART_transfer (int8_t a);                                      //Функция передачи по USART
void record ();                                                      //прототип функции для записи в сдвиговые регистры
void flash ();                                                       //прототип функции мигания поворотниками
void receive ();                                                     //функция обработки данных принимаемых по UART
void readmi (uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e); //прототип функции чтения алфавита и записи его в буфер экрана
/*парадигма записи в буфер а=RGB, b=Синий справа-Красный слева, c=Зелёный, d=Красный справа-Синий слева*/
int invert (int8_t b);                                               //прототип  функции для инверсии битов в байте

//Обработка прерывания по вектору USART_RX
ISR (USART_RX_vect) 
{       rx_buf[num_rx_buf]=UDR0;
        num_rx_buf++;
        if (num_rx_buf == 2)
        {
             status[3] |= (1<<0);
             num_rx_buf = 0;   
        }
}

int main (void)
{
        TCCR1A = 0b10000011;        //устанавливаем настройки таймера
        TCCR1B = 0b00001000;        //устанавливаем настройки таймера 
        DDRC = 0b00111111;          //устанавливаем необходимые пины порта C на вывод
        DDRB = 0b00000110;          //устанавливаем необходимые пины порта B на вывод
        DDRD = 0b00001100;          //устанавливаем необходимые пины порта D на вывод
        PORTC |= (1<<1) | (1<<2);   //устанавливаем на пинах порта C 1
        PORTB &= ~(1<<1) | ~(1<<2); //устанавливаем на пинах порта B 0
        PORTD &= ~(1<<2) | ~(1<<3); //устанавливаем на пинах порта D 0
        USART_Init(MYUBRR);

        _pwm_pass;
        _clock_high;                //для предотвращения ложного срабатывания
        _latch_up;                  //поднимаем защелку
        _flash_G_low;               //выключаем светодиоды
        _flash_RGB_low;             //выключаем светодиоды
        _pwm_close_BrRl;            //выключаем светодиоды
        _pwm_close_RrBl;            //выключаем светодиоды
        sei();                      //разрешаем глобальные прерывания
        readmi (11,11,11,11,0);     //гасим экран

        while (1)
        {
                uint8_t x;          
                uint8_t y;          //RGB
                uint8_t z = 0;      //включение и выключение инверсии буфера экрана, 0 справа
                uint8_t w;          //выбор порта для пропуска  PWM на выход
             //включение поворотников
             if (((status[0] & (7<<0)) == 1) || ((status[0] & (7<<0)) == 2) || ((status[0] & (7<<0)) == 5) || ((status[0] & (7<<0)) == 6))
             {
                //вкючение правого поворотника
                if  ((status[0] & (1<<0)) == 1)
                {

                        if (((status[0] & (1<<2)) == 0))
                        {
                           x = 11; 
                           y = 11; 
                           z = 0;
                           w = 1;  
                        }
                        else //со стопом
                        {
                           x = 9; 
                           y = 0;
                           z = 0;
                           w = 1; 
                        }
                }
                //вкючение левого поворотника
                else if ((status[0] & (1<<1)) == 2)
                {
                         if (((status[0] & (1<<2)) == 0)) 
                        {
                           x = 11; 
                           y = 11;
                           z = 1;
                           w = 2;  
                        }
                        else //со стопом
                        {
                           x = 9; 
                           y = 0;
                           z = 1;
                           w = 2; 
                        }
                }
                _scvagnost;
                _pwm_on;
                PORTC &= ~(1<<w);   //устанавливаем на пине w порта C 0, тем самым пропуская PWM на выход
                switch (status[1])  //бегущий огонь
                {
                   case 1:
                   {
                           readmi (y, x, 0, 0, z);
                           status[1] = 2;
                           _turn_signal_spd;
                           break;
                   }
                   case 2:
                   {
                           readmi (y, x, 1, 1, z);
                           status[1] = 3;
                           _turn_signal_spd;
                           break;
                   };
                   case 3:
                   {
                           readmi (y, x, 2, 2, z);
                           status[1] = 4;
                           _turn_signal_spd;
                           break;
                   };
                   case 4:
                   {
                           readmi (y, x, 3, 3, z);
                           status[1] = 5;
                           _turn_signal_spd;
                           break;
                   };
                   case 5:
                   {
                           readmi (y, x, 4, 4, z);
                           status[1] = 6;
                           _turn_signal_spd;
                           break;
                   };
                   case 6:
                   {
                           readmi (y, x, 5, 5, z);
                           status[1] = 7;
                           _turn_signal_spd;
                           break;
                   };
                   case 7:
                   {
                           readmi (y, x, 6, 6, z);
                           status[1] = 8;
                           _turn_signal_spd;
                           break;
                   };
                   case 8:
                   {
                           readmi (y, x, 7, 7, z);
                           status[1] = 9;
                           _turn_signal_spd;
                           break;
                   };
                   case 9:
                   {
                           readmi (y, x, 8, 8, z);
                           status[1] = 10;
                           _turn_signal_spd;
                           break;
                   };
                   case 10:
                   {
                           flash();
                           status[1] = 1;
                           _turn_signal_spd;
                           break;
                   };
                }

                           PORTC |= (1<<w);   //устанавливаем на пине w порта C 1
                           _pwm_off; 
                }
                //включение стопов
             else if ((status[0] & (7<<0)) == 4)
             {
                    readmi (3, 9, 11, 10, z);
             }
                //включение моргалки
             else if ((status[0] & (15<<0)) == 8)
             {
                    if (status[2] == 1)
                    {
                            readmi (3, 9, 11, 10, z);
                            status[2] = 2;
                            _blink_spd;
                            
                    }
                    else if (status[2] == 2)
                    {
                            readmi (1, 5, 11, 5, 2);
                            status[2] = 1;
                            _blink_spd;
                    }
                    
             }
             else if ((status[0] & (1<<6)) != 0)
             {
                     readmi (11,11,11,11,0);
                     status[0] &= ~(1<<6);
             }

                //обработка принятой инфы, если взведён бит приёма
             if (status[3] & (1<<0) != 0)  
             {
                     receive ();
             }     
        }
        
}

void record ()
{
    _latch_down;                           // опускаем защелку
    
  for (int x = 0; x<SIZE_SCREEN; x++)      // цикл для переборки байтов в массиве (уточнить как храниться число)
  {
       for (int y = 7; y>=0; y--)          // цикл для перебора битов в байте и подачи их на выход
        {   
            _clock_low;                    // переводим регистры в режим ожидания данных   
            
            if (((buffer[x] & (1<<y)) == 0)^1) // накладываем побитовую маску для считывания нужного бита, ^1 стоит для иверсии бита для отладки (потом нужно будет убрать)
            {
                _data_0;                   // если ответ 0, выставляем на входе регистра 0
            }
            else 
            {
                _data_1;                   // иначе, выставляем на входе регистра 1
            }
            
            _clock_high;                   // записываем данные в регистр
            
        }
  }
    _latch_up;                             // поднимаем защелку, включаем светодиоды
}


void flash ()                               //функция моргания поворотником
{ 
   do
   {
        if (status[3] & (1<<0) != 0)  
        {
                receive ();
        }
        
        if ((status[0] & (1<<7)) == 128)     //если было изменение состояния
        {
                if ((status[0] & (1<<2)) == 4) //если включен стоп добавляем его в эффект
                {
                        if ((status[0] & (1<<0)) == 1)
                        {
                                PORTC &= ~(1<<1);           //устанавливаем на пине 1 порта C 0, тем самым пропуская PWM на выход
                                PORTC |= (1<<2);            //устанавливаем на пине 1 порта C 1, тем самым не пропуская PWM на выход
                                readmi (0, 9, 8, 8, 0);     // rgb не горит, _Blue_right_Red_left, горят красные, _Green горят 3 правых зеленых, _Red_right_Blue_left горят 3 правых красных
                                status [0] &= ~(1<<7);
                        } 
                        else if ((status[0] & (1<<1)) == 2) 
                        {
                                PORTC &= ~(1<<2);           //устанавливаем на пине 2 порта C 0, тем самым пропуская PWM на выход
                                PORTC |= (1<<1);            //устанавливаем на пине 2 порта C 1, тем самым не пропуская PWM на выход
                                readmi (0, 9, 8, 8, 1);     // тоже что и вверху, но инверсное
                                status [0] &= ~(1<<7);
                        }    
                }
                  //если выключен стоп убираем его из эффекта
                else if ((status[0] & (7<<0)) == 2)         //если включен левый
                {
                        readmi (11, 11, 8, 8, 1);
                        status [0] &= ~(1<<7);
                }
                else if ((status[0] & (7<<0)) == 1)         //если включен правый
                {
                        readmi (11, 11, 8, 8, 0);
                        status [0] &= ~(1<<7);                        
                }
                else if ((status[0] & ((1<<7)|(7<<0))) == 128)
                {
                        status [0] &= ~(1<<7);
                        status [1] = 20;
                }
                
        }
        _pwm_drop;
        _flash_G_hight;
        _turn_signal_fsdp;
        _pwm_pass;
        _flash_G_low;
        _turn_signal_fsdp;   
        status [1]++;        
   } while (status [1]<20);
    
}

void readmi (uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e)
{
        
        if (e == 0)
        {
                buffer [0] = ABC [a];
                buffer [1] = ABC [b];
                buffer [2] = ABC [c];
                buffer [3] = ABC [d];
        }
        else if (e == 1)
        {
                buffer [0] = ABC [a];
                buffer [3] = ABC [b];
                buffer [2] = ABC [c];
                buffer [1] = ABC [d];
                invert (1);
                invert (2);
                invert (3);
        }
        else if (e == 2)
        {
                buffer [0] = ABC [a];
                buffer [3] = ABC [b];
                buffer [2] = ABC [c];
                buffer [1] = ABC [d];
                invert (3);
        }
            
        record();
}
int invert (int8_t b)
{
        int8_t tmp = buffer [b] & 0xFF;
        tmp = ((tmp >> 4) & 15) | ((tmp & 15) << 4);
        tmp = ((tmp >> 2) & 51) | ((tmp & 51) << 2);
        tmp = ((tmp >> 1) & 85) | ((tmp & 85) << 1);
        buffer [b] = buffer [b] & ~0xFF | tmp;

}
void USART_transfer (int8_t a)
{
      int8_t x = 0;
        do
        {
                while(!(UCSR0A&(1<<UDRE0)));
                if (a == 1)
                {
                        UDR0 = status[0];
                }
                else
                {
                        UDR0 = 0b00000001;
                }
                x++;
        }
        while (x < 2);
      
}
void USART_Init( unsigned int ubrr)
{
/*установка битрейта */
UBRR0H = (unsigned char)(ubrr>>8);
UBRR0L = (unsigned char)ubrr;

UCSR0A = 0;                                  //Режим двойной скорости выключен:
UCSR0B = (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);  // Установка параметров: включение портов ввода и вывода, разрешение прерывание по окончанию приёма
UCSR0C = (1<<UCSZ01)|(1<<UCSZ00)|(1<<USBS0); // Установка формата фрейма: 8data, 2stop bit
}
void receive ()
{
        if (rx_buf[1] == rx_buf[0])
        {
               status[0]=rx_buf[1];
                USART_transfer (1);
                if (status[0] == 0 ||status[0] == 128)
                {
                        status[0] |= (1<<6);
                } 
        }
        else
        {
              USART_transfer (0);  
        }
        status[3] &= ~(1<<0);
}
[свернуть]
Код U7 (отвечает за кнопки и индикацию на руле)
[свернуть]

Видео с примером
[свернуть]

Видео чуть постарее текущей версии кода. На текущий момент добавлен переход от одного сигнала поворота на другой без выключения предыдущего и проверка передаваемого байта, а также отправка подтверждения о приёме.



Shaman

Вот, собственно, передачи байта и его проверки я хочу коснуться.

На текущий момент это происходит так:
После того как байт сформирован взводится бит готовности и вызывается функция отправки оного.

↓ спойлер ↓
if ((status[0] & (1<<6)) == 64)  //
            {
                  status[0] &= ~(1<<6);
                  USART_transfer (1);
                  status[0] &= ~(1<<7);
            }

void USART_transfer (int8_t a)
{
     int8_t x = 0;
        do
        {
                while(!(UCSR0A&(1<<UDRE0)));
                if (a == 1)
                {
                        UDR0 = status[0];
                }
                else
                {
                        UDR0 = 0b00000001;
                }
                x++;
        }
        while (x < 2);

}
[свернуть]


Байт отправляется два раза и на принимающей стороне складывается в отдельные элементы массива буфера приёма, после чего проверяется на идентичность. И при совпадении записывается в массив статуса, а при не совпадении отправляется код ошибки, по приходу которого передающая сторона повторяет отправку. На передающей стороне повтор отправки пока не реализован.

↓ спойлер ↓
//Массив буфера приёма
unsigned char rx_buf [3] =
{
        //Буфер приёма
        0b00000000, 
        0b00000000,
        
};
int8_t num_rx_buf = 0;

//Обработка прерывания по вектору USART_RX
ISR (USART_RX_vect) 
{       rx_buf[num_rx_buf]=UDR0;
        num_rx_buf++;
        if (num_rx_buf == 2)
        {
             status[3] |= (1<<0);
             num_rx_buf = 0;   
        }
}

   //обработка принятой инфы, если взведён бит приёма
             if (status[3] & (1<<0) != 0)  
             {
                     receive ();
             }

void receive ()
{
        if (rx_buf[1] == rx_buf[0])
        {
               status[0]=rx_buf[1];
                USART_transfer (1);
                if (status[0] == 0 ||status[0] == 128)
                {
                        status[0] |= (1<<6);
                } 
        }
        else
        {
              USART_transfer (0);  
        }
        status[3] &= ~(1<<0);
}
[свернуть]

Собственно почему проверка сделана так просто. Здесь объём передачи весьма небольшой и даже если следом за байтом передавать контрольную сумму, то объём передачи будет так же 2 байта, а на её вычисление тратятся ресурсы. Пробовал играться с битом четности, но всё, что смог сделать это его включить. Он передаётся, но принимающая сторона на него не реагирует или я поздно считываю реакцию когда бит паритета уже сброшен. Вызвать ошибку я пытался включив на приемнике проверку по нечётности битов.

А расписал я всё это ради вопроса. Такой вариант проверки имеет право на жизнь или есть полее элегантные решения?
Просто хочу рассмотреть варианты, дабы не пришлось потом пол программы переписывать.

Slabovik

Почему нет? Вполне имеет смысл как раз в силу уже указанной причины.
Второй байт можно несколько видоизменить (с целью небольшого повышения степени обнаружения ошибки), например, поксорить с 0FFh (простая инверсия бит), а на принимающей стороне в качестве проверки поксорить между собой оба принятых байта. Если результат будет 0, то значит передача с высокой степенью вероятности верна, а если хоть где-то один битик выпал - результат будет не 0 (всё-равно будет вероятность, что два или более битиков выпадут так, что результат проверки даст "верно", но это справедливо для любых алгоритмов проверки в принципе. Вопрос только в вероятности таких ситуаций).
Возможны и другие варианты, типа перепутывания битов, сложения с константой и т.п.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Т.е. получается код будет выглядеть так?

Передающая сторона
void USART_transfer (int8_t a)
{
     int8_t x = 0;
     int8_t temp = status[0];
        do
        {
                if (x==1)
                {
                      temp = temp ^ 0xFF;
                }
                while(!(UCSR0A&(1<<UDRE0)));
                if (a == 1)
                {
                        UDR0 = temp;
                }
                else
                {
                        UDR0 = 0b00000001;
                }
                x++;
        }
        while (x < 2);

}
[свернуть]

Принимающая сторона
void receive ()
{
        int8_t temp = rx_buf[1] & rx_buf[0];
        if (temp == 0)
        {
               status[0]=rx_buf[0];
                USART_transfer (1);
                if (status[0] == 0 ||status[0] == 128)
                {
                        status[0] |= (1<<6);
                } 
        }
        else
        {
              USART_transfer (0);  
        }
        status[3] &= ~(1<<0);
}
[свернуть]

По крайней мере у меня так заработало. Но тогда на принимающей стороне нужно не XOR-ить (в СИ знак ^), а делать AND (в СИ знак &), или я путаюсь в терминах?

Slabovik

Таблица истинности XOR

AB
A XOR B
00
0
10
1
01
1
11
0

Если поксоришь байт сам на себя получишь 0
Если поксоришь байт с 0FFh - получишь инвертированный побитно байт
Если поксоришь байт на инвертированную копию - получишь 0FFh

Первый и третий варианты отличаются только тем, как смотреть на результат.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Цитата: Slabovik от 08 Нояб., 2021, 18:19Второй байт можно несколько видоизменить (с целью небольшого повышения степени обнаружения ошибки), например, поксорить с 0FFh (простая инверсия бит), а на принимающей стороне в качестве проверки поксорить между собой оба принятых байта. Если результат будет 0,

Понятно, опять пролазит математика  :) . Для меня 0 это 0 пусть и тоже информация. А 0xFF это 0b11111111, 255 что угодно только не ноль  :) . Сравнивать конечно можно и 0xFF, но если пишут 0, я и пытаюсь его добиться.  :)

Shaman

#15
У меня затык в коде, не могу разобраться с двоичной математикой.
Есть вот такой
кусок кода
//имена бит в массиве состояния
#define RXEND 2
#define STOP 2
#define LEFT 1
#define RIGHT 0
#define BLINK 3
#define RTFG 0
#define LTFG 0
#define STCE 7
#define OFF 6 
...
...
//Массив состояния
unsigned char status [SIZE_STATUS] =
{
      0b00001000,  /*Состояние эффекта начиная с младшего бита по порядку:
                        0 Правый поворот, имя бита RIGHT
                        1 Левый поворот, имя бита LEFT
                        2 Стоп, имя бита STOP
                        3 Моргалка, имя бита BLINK
                        7 Позволяет включить или выключить стоп во время моргания поворотником (взводится при изменении состояния сигнала стоп) STCE*/
      1,            // Шаг эффекта поворот
      1,            // Шаг Эффекта моргалка
      0b00000000,  /* Флаги :
                        0 Правый поворот вкл (нужен для сброса шага эффекта после выключения)
                        1 Левый поворот вкл
                        2 Пакет принят*/
};
...
...
if ((status[0] & (1<<STCE)) != 0)
            { 
                    if ((status[0] & 3)^(status[3] & 3) != 0) 
                    {
                            int8_t temp = (status[0] & 3);
                            status[3] &= ~3;
                            status[3] = (status[3] | temp);
                            
                            
                            status[1] = 1;
                    }


            if ((status[3]& 3) == 2) // добавлено для дебага
                            {
                              PORTD ^= (1<<4);    
                            }
[свернуть]
По задумке когда взведён бит STCE и последние 2 бита элементов 0 и 3 массива status не равны, происходит установка 2 элемента массива status в 1, а последние биты элемента 0 присваиваются элементу 3.

Это работает только для комбинации status 0 10000001
лог дебага
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./avrblink2.elf...done.
(gdb) target remote localhost:1200
Remote debugging using localhost:1200
0x00000000 in __vectors ()
(gdb) load
Loading section .text, size 0x600 lma 0x0
Loading section .data, size 0x12 lma 0x600
Start address 0x0, load size 1554
Transfer rate: 505 KB/sec, 518 bytes/write.
(gdb) break 158
Breakpoint 1 at 0x3e4: file /home/shaman/.bind/Sklad/Svalka/Фонарь/Код/avrblink2.c, line 158.
(gdb) distlay/t status
Undefined command: "distlay".  Try "help".
(gdb) display/t status
1: /t status = {10101010, 10101010, 10101010, 10101010}
(gdb) continue
Continuing.

Breakpoint 1, main () at /home/shaman/.bind/Sklad/Svalka/Фонарь/Код/avrblink2.c:158
158            if ((status[0] & (1<<STCE)) != 0)
1: /t status = {10000001, 1, 1, 0}
(gdb) step
160                    if ((status[0] & 3)^(status[3] & 3) != 0) // баг становиться равной 0 только при нажатии в право
1: /t status = {10000001, 1, 1, 0}
(gdb) step
164                            status[3] = (status[3] | temp);
1: /t status = {10000001, 1, 1, 0}
(gdb) step
167                            status[1] = 1;
1: /t status = {10000001, 1, 1, 1}
(gdb) step
171            if ((status[3]& 3) == 2)
1: /t status = {10000001, 1, 1, 1}
(gdb) continue
Continuing.

Breakpoint 1, main () at /home/shaman/.bind/Sklad/Svalka/Фонарь/Код/avrblink2.c:158
158            if ((status[0] & (1<<STCE)) != 0)
1: /t status = {10000001, 10, 1, 1}
(gdb) step
160                    if ((status[0] & 3)^(status[3] & 3) != 0) // баг становиться равной 0 только при нажатии в право
1: /t status = {10000001, 10, 1, 1}
(gdb) step
171            if ((status[3]& 3) == 2)
1: /t status = {10000001, 10, 1, 1}
(gdb)
[свернуть]
из лога видно, что на втором круге программа в if ((status[0] & 3)^(status[3] & 3) != 0) уже не проваливается т.к. последние 2 бита 1: /t status = {10000001, 10, 1, 1} равны.

Но для комбинации status 0 10000010 это не так
лог дебага
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./avrblink2.elf...done.
(gdb) target remote localhost:1200
Remote debugging using localhost:1200
0x00000000 in __vectors ()
(gdb) load
Loading section .text, size 0x600 lma 0x0
Loading section .data, size 0x12 lma 0x600
Start address 0x0, load size 1554
Transfer rate: 505 KB/sec, 518 bytes/write.
(gdb) break 158
Breakpoint 1 at 0x3e4: file /home/shaman/.bind/Sklad/Svalka/Фонарь/Код/avrblink2.c, line 158.
(gdb) display/t status
1: /t status = {10101010, 10101010, 10101010, 10101010}
(gdb) continue
Continuing.

Breakpoint 1, main ()
    at /home/shaman/.bind/Sklad/Svalka/Фонарь/Код/avrblink2.c:158
158            if ((status[0] & (1<<STCE)) != 0)
1: /t status = {10000010, 1, 1, 0}
(gdb) step
160                    if ((status[0] & 3)^(status[3] & 3) != 0) // баг становиться равной 0 только при нажатии в право
1: /t status = {10000010, 1, 1, 0}
(gdb) step
164                            status[3] = (status[3] | temp);
1: /t status = {10000010, 1, 1, 0}
(gdb) step
167                            status[1] = 1;
1: /t status = {10000010, 1, 1, 10}
(gdb) step
171            if ((status[3]& 3) == 2)
1: /t status = {10000010, 1, 1, 10}
(gdb) continue
Continuing.

Breakpoint 1, main ()
    at /home/shaman/.bind/Sklad/Svalka/Фонарь/Код/avrblink2.c:158
158            if ((status[0] & (1<<STCE)) != 0)
1: /t status = {10000010, 10, 1, 10}
(gdb) step
160                    if ((status[0] & 3)^(status[3] & 3) != 0) // баг становиться равной 0 только при нажатии в право
1: /t status = {10000010, 10, 1, 10}
(gdb) step
164                            status[3] = (status[3] | temp);
1: /t status = {10000010, 10, 1, 10}
(gdb)
[свернуть]
программа всё равно продолжает проваливаться в if ((status[0] & 3)^(status[3] & 3) != 0) даже при условии, что 1: /t status = {10000010, 10, 1, 10} равны.

Я не понимаю, где ошибка.
Когда проверяешь выражение if ((status[0] & 3)^(status[3] & 3) != 0) на калькуляторе оно работает
↓ спойлер ↓
Калькулятор_001.jpg
[свернуть]

Если кто-то видит, где ошибка ткните, пожалуйста, пальцем.
P.S. Полный листинг во вложении

Slabovik

Смотрел-смотрел и тоже не понимаю, его он хочет. Но что-то надо делать. Возможно, вопрос в плоскости интерпретации косплейятором приоритетов, а может ещё что...

Я правильно понимаю, что если два младших (не последних !) бита различаются, то нужно выполнить операции?

Для начала, я бы пробовал преобразовывать выражение в эквивалентное. Например

if ((status[0] & 3)^(status[3] & 3) != 0)

можно изменить так

if (((status[0] & 3)^(status[3] & 3)) != 0)

лучше так

if (((status[0] ^ (status[3]) & 3) != 0)

а можно и так

if (((status[0] ^ (status[3]) & 3) > 0)

Чтобы проверить одинаковость бит ксорить не обязательно

if (((status[0] & 3) - (status[3] & 3)) != 0)

и т.д.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Цитата: Slabovik от 23 Нояб., 2021, 20:18Я правильно понимаю, что если два младших (не последних !) бита различаются, то нужно выполнить операции?
Да правильно. Говорила мама учи математику... Вынес 3-ку за скобки, вот так: if (((status[0] ^ status[3]) & 3) != 0), заработало.
Благодарю.

Slabovik

Ага, ну значит дело действительно было в интерпретации компилятором приоритетов операций. Мы думаем так, а компилятор работает не так :) Расстановка скобок дала прямое указание компилятору делать в нужном нам порядке :)
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Цитата: Slabovik от 26 Нояб., 2021, 15:21Ага, ну значит дело действительно было в интерпретации компилятором приоритетов операций. Мы думаем так, а компилятор работает не так :) Расстановка скобок дала прямое указание компилятору делать в нужном нам порядке :)
Ну да, так тоже работает if (((status[0]& 3)^(status[3] & 3)) != 0)
Неочевидная вещь для новичка, я привык, что в математике сначала выполняется то, что находится с лева от знаков равенства и попался не поставив скобку.
Хотя так должно быть быстрее: if (((status[0] ^ status[3]) & 3) != 0)