Автор желает прочитать значение бита в исходном байте и перенести это значение в байт результата. То есть, бит-ориентированные действия желает провести.
На языке Си (поскольку он байт-ориентированный) чтение бита выполняется опять же через логическую операцию AND с байтом, в котором содержится бит, и маской, в которой записана 1 в позиции читаемого бита. Запись бита в байт производится логической операцией OR, если записывается 1, или операцией AND, если записывается 0. Однако, если записываемом бейте в позиции этого бита находится 0, то операцию AND можно не применять. Операция 0 OR 0 = 0.
, где Rbit - номер позиции читаемого бита в байте DDRB,
Wbit - номер позиции записываемого бита в байт result.
(1<<Rbit) выдает маску из 0 во всех битах, кроме бита номер Rbit, который будет = 1. Эта маска будет наложена на байт DDRB, при этом результат операции будет содержать значение читаемого бита в позиции номер Rbit. Чтобы записать его в байт result в позиции Wbit, нужно провести сдвиги, сначала вправо на число позиций Rbit, затем влево на число позиций Wbit.
Второй вариант с несколько иным алгоритмом, используя проверку true/false при чтении бита.
result = (DDRB & (1 << Rbit)) ? result | (1 << Wbit) : result & ~(1 << Wbit);
Тут сложность в понимании того, что используется так называемый тренарный оператор условия. Суть в чем. То, что записано после знака "=" и перед знаком "?" является условием сравнения и аналогично записи if(DDRB & Rbit).
Следующие после "?" два выражения, разделенные знаком ":" являются действиями, исполняемыми по условию предыдущего сравнения. Первое выражение будет выполняется, если результат сравнения "истина" (true), второе выражение выполнится если результат сравнения "ложь" (false), и результат выражений запишется в первую переменную до знака "=", то есть в result. Напомню, что результат сравнения "истина" получается при любом ненулевом значении выражения, а результат "ложь" - при нулевом. То есть, если при выполнении побитовой операции AND получились все нули, то выражение ложно. Если хоть в какой-то позиции есть 1, то результат "истина".
Таким образом, если при чтении бита с номером Rbit там стоит 1, то в result она перенесется в позиции Wbit, а если прочитанный бит = 0, то в result запишется 0 в позиции Wbit.
И третий вариант записи - через структуры с восемью однобитовыми полями. В этом случае запись на Си будет более понятной, типа result.bit2 = DDRB.bit5;
//ВЫВОДЫ ATMEGA328 НА СЕГМЕНТЫ
#define A 1
#define B 2
#define C 4
#define D 8
#define E 16
#define F 32
#define G 64
int h1,h0,m1,m0; // h1 - десятки часов, h0 - еденицы часов и так далее
unsigned char digit_out[4], // буфер экрана (нумерация от 0 до 3 плюс четвертый невидимый разряд)
cur_dig; // тек.высвечиваемый символ (позиция)
byte mask = 0xFF;
void setup() {
DDRD = 0xFF; // порт D на OUTPUT
DDRB = 0xFF; // порт B на OUTPUT
cli();
TCCR1A = 0b00000000; // OC1A Отключен
TCCR1B = 0b00001011; // делитель 64, сброс при совпадении 1A (режим СТС)
OCR1A = 0x041; // 5ms
TIMSK1 = 0b0000010; // запусе прерывания А таймера 1
sei();
}
void loop() {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
h1=2; //тут будем читать потом время
h0=1;
m1=5;
m0=8;
for (int i =0; i<7;++i){ // это сколько кадров
if (i==0) mask=F; // первый эффект
if (i==1) mask=F+A;
if (i==2) mask=F+A+B;
if (i==3) mask=F+A+B+G;
if (i==4) mask=F+A+B+G+E;
if (i==5) mask=F+A+B+G+E+D;
if (i==6) mask=F+A+B+G+E+D+C;
if (i==7) mask=F+A+B+G+E+D+C+D;
delay(40);
}
delay(100);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ISR(TIMER1_COMPA_vect) { //обработчик прерывания по совпадению А таймера 1, счетчик сбрасывается в 0
PORTD&=0b11110000; // потушить все (разряды - off)
PORTB=digits[digit_out[cur_dig]]& mask; // символ на экран
PORTD |= (1<<cur_dig); // засветить нужный разряд (бит знакоместа - on)
cur_dig++; if (cur_dig > 3) {
cur_dig = 0;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
правильно я Вас понял или нет, НО получилось !!!
спасибо Вам за учебу, терпение и доброту
А это так называемая "бегущая строка". И есть два пути. Второй из них заключается в том, чтобы при сдвиге цифр влево просто переносить байты цифр в буфере дисплея левее, начиная слева. Допустим, есть буфер
buf[4], в котором buf[0] - самый левый разряд, а buf[3] - самый правый. Так вот, каждый шаг анимации бегущей строки будет представлен шагами:
- копируем из buf[1] в buf[0],
- копируем из buf[2] в buf[1],
- копируем из buf[3] в buf[2],
- в buf[3] записываем "пустой символ".
И так - 4 раза. Всё число "уйдет" с дисплея влево. А чтобы новое число "вошло" справа налево, нужно в последнем пункте вместо "пустого" символа записать символ "вдвигаемого" числа, начиная с левого его края.
Первый же способ делается на указателях. В этом случае ничего не копируется, инкрементируется только указатель на начало отображения строки в дисплей, сдвигаясь по строке. То есть, допустим, есть строка chat txt[4+4], которую нужно сдвинуть по дисплею влево. Эта строка должна быть вдвое длиннее количества разрядов на дисплее, и в последних четырех байтах содержать "пустой" символ. Функция, принимающая строку для отображения в дисплей, будет брать строку txt[], смещаясь по ней на одну позицию вправо, от начала к концу.
Второй из них заключается в том, чтобы при сдвиге цифр влево просто переносить байты цифр в буфере дисплея левее, начиная слева. Допустим, есть буфер
buf[4], в котором buf[0] - самый левый разряд, а buf[3] - самый правый. Так вот, каждый шаг анимации бегущей строки будет представлен шагами:
да, этот способ я опробовал и он работает, но если бы выводить на экран два показания, скажем часы и температуру. а если выводить несколько показаний часы, несколько температур, день недели...
я не соображу как это написать вместо h1(часы) ,t1(температуру) d1(день )... Спойлерfor (byte i =0; i<4;++i){ // это сколько кадров
unsigned int AA[] = { 0, 0, 0,h1}; // h1- десятки часов
unsigned int BB[] = { 0, 0,h1,h0}; // h0- еденицы часов
unsigned int CC[] = { 0,h1,h0,m1}; // m1- десятки минут
unsigned int DD[] = {h1,h0,m1,m0}; // m0- еденицы минут
digit_out[0] = AA; //высветить первый разряд
digit_out[1] = BB; //высветить второй разряд
digit_out[2] = CC; //высветить третий разряд
digit_out[3] = DD; //высветить четвертый разряд
delay(100);
}
Первый же способ делается на указателях. В этом случае ничего не копируется, инкрементируется только указатель на начало отображения строки в дисплей, сдвигаясь по строке.
Для разрядов индикатора нужно создать отдельный буфер, не привязанный к часам-минутам или температуре. Вот так: byte buf[4]; Из этого буфера динамичская индикация берет очередное значение для зажигания сегментов в очередном разряде. И в этот же буфер бубут ложиться значения сегментов другой функцией, которая называется LED_Print и выполняет преобразование входного числа в комбинацию зажигаемых сегментов.
Вот так:
функция sprintf является стандартной и выполняет форматированное преобразование чисел в ANSI-кодированный текст. В принципе, для нужд часов можно обойтись простым преобразованием числа в ANSI-строку любым доступным способом.
Добавлено after 10 minutes 42 seconds:
Да... Если всё написанное выше слишком сложно для понимания, то можно существенно упростить функции, сделав их по-топорному, вот так:
А смысл как бы вот в том, что... Вот определили мы комбинации битов для зажигания сегментов в цифрах #define CH_0 (SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F )
теперь, чтобы попроще сделать выбор этих комбинаций, создаем массив из десяти однобайтных переменных и сразу в этот массив кладем вышеопределенные комбинации: const uint8_t font[10] = {CH_0, CH_1, CH_2, CH_3, CH_4, CH_5, CH_6, CH_7, CH_8, CH_9};
Таким образом, получается, что например 5-й элемент массива font[5] содержит комбинацию сегментов для цифры 5. И теперь получается прямая зависимость между числом единиц или десятков с комбинациями для зажигания сегментов в разрядах.
То есть, если например число десятков часов dHour = 5, то отобразить цифру 5 можно просто выбрав 5-й индекс в массиве, вот так: Port = font[dHour];
В Port будет передано значение из пятого элемента массива font[].
Функция void DND_Print(byte d0, byte d1, byte d2, byte d3) принимает числовые значения d0, d1, d2, d3 в диапазоне 0-9 для каждого разряда (цифры, которые нужно на дисплей вывести), преобразует эти числа показанным выше методом в комбинации сегментов и кладет в массив переменных buf[4], из которого потом в нужные моменты переключения разрядов функция DND_DynaScan() забирает уже готовые комбинации для передачи в выходной порт для зажигания сегментов.
Такой алгоритм через промежуточный буфер buf[] сделан для того, чтобы разделить работу динамической индикации, непрерывно через интервалы читающей значения зажигаемых сегментов из buf[], и функции, преобразующей любые числа в комбинации сегментов. То есть, для этих функций не имеет значения, что именно отображается - часы с минутами, температура, дата или еще что.
Задачу разложения целого числа из переменной в поразрядное представление от 0 до 9 - это будет выполнять другая функция.
Тоже так делаю, но ради автоматизации знакогенератора - если окажется удобнее развести контакты порта по сегментам не по порядку, пожалуйста, разводи как удобно, но в коде, вот в этом месте, пропиши цифры в соответствии с разводкой, например:
и весь знакогенератор,
(а у меня он обычно гораздо шире, чем 10 цифр... цифры лежат в массиве, остальное вызывается по дефайну, в т.ч. и сегменты по отдельности)
вобщем всё будет автоматически пересчитано, перерисовано и приведено в соответствие со схемой!
Добавлено after 1 minute 23 seconds:
поэтому я этот код выделил в отдельный файл и целиком его из проекта в проект таскаю...
Добавлено after 33 minutes 24 seconds:
Вот примерное содержание файлов:
SETUP.CСпойлер
//Setup
#define DisablePWM //отключение ШИМ выходов (мне для протеуса)
#define EnDebug //включение порта отладки (reset) мне для протеуса
//полярность дисплеев
#define DISPLAY_U_CC //#define DISPLAY_U_CA или #define DISPLAY_U_CC при отсутствии - автоопределение
#define DISPLAY_I_CC //#define DISPLAY_I_CA или #define DISPLAY_I_CC при отсутствии - автоопределение
//соответствие сегмента и номера ноги порта D
#define Seg_A 5 //0
#define Seg_B 0 //1
#define Seg_C 1 //2
#define Seg_D 6 //3
#define Seg_E 7 //4
#define Seg_F 2 //5
#define Seg_G 3 //6
#define Seg_P 4 //7
//#define ShowLogo //показать лого
..........
..........
в этом файле служебные и отладочные настройки проекта (файл показан не весь, там дальше прописаны длительности задержек, количество импульсов и прочее... закомментированные номера - в соответствии с отладочной платой, подставленые - с прототипом)
Если проект делается для кого-то, то этот кто-то допускается только в этот файл.
начало файла, работающего с 7 сегментным индикатором... далее идут процедуры гашения и зажигания разряда дисплея, они работают только с буфером дисплея и портами выводящими информацию на индикатор
остальной код, в т.ч. и код, реализующий спецэффекты, работает только с буфером.
Я тоже тут не полный код привожу, потому как в полном виде он будет слишком сложен для понимания автором вопроса. Я уже и без того упростил до минимально разумного варианта.
Есть два способа адаптации под распиновку: либо менять цифры (положение битов) в знакогенераторе, либо записать функцию вывода таким образом, чтобы она преобразовывала входную комбинацию из знакогенератора в выходную комбинацию на порты. Годится любой способ, в зависимости от того, нужно ли сделать более оптимальную работу кода или более универсальную библиотеку. Для случая посегментной анимации пересчитывать положение битов в знакогерераторе и в картах сегментной анимации - это уж не шибко кошерно, потому что есть еще одно, частное ограничение - сегменты могут быть разведены не на одном порту, а на разных. На том же STM32 у вас не шибко то большой выбор последовательно расположенных портов на 8 выходов. Да и то, часть из нужных выходов могут быть занятыми другими не менее важными функциями. Например, SPI1 как назло вклинивается в PA5-PA7. Так что полюбасу понадобится функция "перепиновки", так сказать. Посему, знакогенератор желателен независимый от распиновки индикатора. Но это на вкус и цвет. Комуто хватает и просто заменить цифры в дефайнах знакогенератора.
Буквы на семисегментнике отображать не особо весело. Я работал с 16-сегментными ЖКД, вот там замечательно буквы показываются. Но тем не менее, на 7-сегментном тоже можно. Лично я использую массив const с символами цифр и букв, а за основу беру парсер ANSI-строки:
} //----------------------------------------------------------------------------------------
загоняем в буфер и ждем когда понадобится Спойлер//---------- экранный буфер --- (индикация времени )--------
void view_time(void) {
if (indicate == IND_CLOCK) { // час : мин
if (hour0) { digit_out[0] = (myhour >> 4) ? (myhour >> 4) : (PROBEL); }
else { digit_out[0] = myhour >> 4; }
if (indicate == IND_CLOCK) {
zpt_BLINK=1; // включить мигающую запятую
for (j=0;j<tc1;j++){ // сколько секунд показывать "время"
read_time(); // читаем время
view_time(); // в буфере
set_ind(); // дает задержку в 0,2 сек
set_yarkost(); // яркось 0-10
if (indicate != IND_CLOCK) break; // если был выход из индикации времени
}
} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^с датой и температурой также читаем- буфер- понадобилось выводим.
Функция void DND_Print(byte d0, byte d1, byte d2, byte d3) принимает числовые значения d0, d1, d2, d3
d0, d1, d2, d3 мне нужно воткнуть в буфер на пример так
----------------------------------------------------------------------------------
if (indicate == IND_CLOCK) { // час : мин
if (hour0) { d0 = (myhour >> 4) ? (myhour >> 4) : (PROBEL); }
else { d0 = myhour >> 4; }
d1 = myhour & 0x0F;
d2 = mymin >> 4;
d3 = mymin & 0x0F;
------------------------------------------------------------------------------------
// --------- что у нас будет в экранном буфере ? ------- (индикация даты) ------------------
void view_date(void) {
if (indicate == IND_DATE) { // день - месяц
d0 = mydate >> 4;
d1 = mydate & 0x0F;
d2 = mymonth >> 4;
d3 = mymonth & 0x0F;
}
} //----------------------------------------------------------------------------------------
а эффект туда-суда по экрану выводить так???
void effect_down (void) {
int i;
for (i =0; i<4;++i){
unsigned int AA[] = {PROBEL,PROBEL,PROBEL,d0};
unsigned int BB[] = {PROBEL,PROBEL,d0,d1};
unsigned int CC[] = {PROBEL,d0,d1,d2};
unsigned int DD[] = {d0,d1,d2,d3};
только CodeVisionAVR ругается и не хочет принимать byte d0, byte d1, byte d2, byte d3
а Функцию void DND_Print(byte d0, byte d1, byte d2, byte d3) я не знаю куда приделать
а зачем тебе всякие "минусы" в массив загонять? оставь их просто дефайнами...
1) если дефайн не вызывается, то он и места не занимает (а архив занимает)
2) получение числа из дефайна происходит в несколько раз быстрее, чем из массива
3) число типа чар подставляемое непосредственно занимает во флеше во много раз меньше места, чем код, позволяющий достать значение из массива.
Но! в массиве удобно и экономично алфавит хранить, чтобы к символам (цифрам) добираться без лишних условий.
Сорян, надо было char написать. Просто я тыщщу лет уже как не работаю с AVR.
Функция DND_Print() - это собственно функция, преобразующая четыре числа в комбинации сегментов. И ставить её туда, где требуется в этот момент вывести на дисплей новую информацию.
У нас просто разный стиль написания. Я пишу в таком стиле, для меня он удобен.
Опять же, почему я говорю за таблицу символов в виде массива const char font[], так потому, что так обеспечивается наиболее быстрое сопоставление входной строки и комбинации сегментов для её отображение. Нет перебора условий сравнения, есть прямая зависимость между входным числом и индексом массива. Табличный метод - нормальная практика. Представьте, что у вас 16-сегментный индикатор, полноценно отображающий почти полторы сотни символов. И перебором условий через if или switch вы сделаете ужос полный. А табличный метод - другое дело. Навряд ли 10 или даже 120 байт в программной или даже оперативной памяти отнимут последний кусок памяти. Впрочем, я уже давно не пишу для мелких AVR/PIC с ограниченными ресурсами.
По поводу бегущей строки. Опять же, нужна отдельная функция, которая будет вызываться в нужный момент и принимать в себя 4 числа для текущего отображения и внутри этой функции сдвигать их на одну позицию через равные промежутки времени. Можно их реализовать хоть через Delay. Потому что с прерываниями будет сложнее.
Вообще, все эти штуки делаются на так называемых конечных автоматах.