I have to test a hash function and I want to change only a single bit of a specific file.
I tried with the dd command. That works, but I can only change a whole byte and not just a bit.
sudo dd if=/dev/zero of=/file.bin bs=1 seek=10 count=1 conv=notrunc
I also tried the sed command with a regex, but as I don’t know the content of the file, I can’t just change an «a» to a «b».
Does anyone know a command for doing this?
dhag
15.1k4 gold badges55 silver badges64 bronze badges
asked Apr 14, 2015 at 20:17
7
Since the file may contain nulls, text-oriented filters like sed are going to fail. But you can use a programming language that can handle nulls, like perl or python. Here’s a solution for Python 3. It’s a few lines longer than strictly necessary, for readability.
#!/usr/bin/env python3
"""Toggle the bit at the specified offset.
Syntax: <cmdname> filename bit-offset"""
import sys
fname = sys.argv[1]
# Convert bit offset to bytes + leftover bits
bitpos = int(sys.argv[2])
nbytes, nbits = divmod(bitpos, 8)
# Open in read+write, binary mode; read 1 byte
fp = open(fname, "r+b")
fp.seek(nbytes, 0)
c = fp.read(1)
# Toggle bit at byte position `nbits`
toggled = bytes( [ ord(c)^(1<<nbits) ] )
# print(toggled) # diagnostic output
# Back up one byte, write out the modified byte
fp.seek(-1, 1) # or absolute: fp.seek(nbytes, 0)
fp.write(toggled)
fp.close()
Save it in a file (e.g., bitflip), make it executable, and run it with the filename to modify and the offset in bits. Note that it modifies the file in place. Run it twice with the same offset and you’ll get your file restored.
answered Apr 14, 2015 at 21:49
alexisalexis
5,6593 gold badges21 silver badges28 bronze badges
3
I don’t think there’s a single command.
Here’s a simple script, save it as «flipbit«:
#!/usr/bin/perl
# Arguments: byte (starting from 0), bit (0-7), filename (otherwise stdin)
$byte = shift(@ARGV);
$bit = shift(@ARGV);
undef $/;
$file=<>;
substr($file,$byte,1) = substr($file,$byte,1) ^ chr(1<<$bit);
print $file;
test:
$ echo abb | ~/bin/flip-bit.pl 2 0 | od -xa
0000000 6261 0a63
a b c nl
this flipped the low-order bit (0) of the third character, changing the ‘b’ to ‘c’.
As a single line command:
perl -e '$byte=shift(@ARGV);$bit=shift(@ARGV);undef $/; $file=<>; substr($file,$byte,1) = substr($file,$byte,1) ^ chr(1<<$bit); print $file'
answered Apr 14, 2015 at 21:40
1
Finally I found a solution with xxd and dd.
a=$(xxd -b -l 1 -seek 3 -p a.bin);b=1;echo -e "x$((${a}^${b}))" | dd of=a.bin bs=1 seek=3 count=1 conv=notrunc
hexdump a.bin v
0000000 61 39 73 36 36 64 66 38 61 39 73 64 35 36 66 35
0000010 37 61 73 64 37 66 74 75 61 67 73 0a 61 73 64 66
hexdump b.bin v
0000000 61 39 73 37 36 64 66 38 61 39 73 64 35 36 66 35
0000010 37 61 73 64 37 66 74 75 61 67 73 0a 61 73 64 66
But this is ugly.
answered Apr 14, 2015 at 22:12
KantiumKantium
2531 gold badge2 silver badges9 bronze badges
2
If you really want to use dd, here is an abomination that will do the trick by flipping the highest bit in the given byte. Adjust the settings for the tr command to change the selected bit.
# Preparation
finger > original.txt
BYTE=3
# Here we go...
dd if=original.txt bs=1c 2>/dev/null | ( dd bs=1c count=$((BYTE-1)) ; dd bs=1c count=1 | tr '00-377' '200-37700-177' ; dd bs=1c ) 2>/dev/null > flipped.txt
# Demonstrate the difference (byte 3: 67 → e7)
hexdump -C original.txt | head -1
00000000 4c 6f 67 69 6e 20 20 20 20 20 4e 61 6d 65 20 20 |Login Name |
hexdump -C flipped.txt | head -1
00000000 4c 6f e7 69 6e 20 20 20 20 20 4e 61 6d 65 20 20 |Lo.in Name |
answered Apr 14, 2015 at 21:42
roaimaroaima
99.2k14 gold badges126 silver badges239 bronze badges
Simple solution using head, tail, xxd. Example below flips least significant bit in last byte of file.bin.
head -c -1 file.bin > flipped.bin
LAST=`tail -c 1 file.bin | xxd -ps`
printf "%02X" $(( $((16#$LAST)) ^ 1 )) | xxd -r -ps >> flipped.bin
answered Jan 12, 2017 at 12:37
Here are two perl one-liners. The first changes the file bar in-place:
perl -p -0777 -i -e 'substr($_,2,1)^=chr(1<<5)' bar
The second reads file foo and writes bar
perl -p -0777 -e 'substr($_,2,1)^=chr(1<<5)' < foo > bar
To adapt to your application: the 2 selects which byte: 0..file_length-1, i.e. 2 is the 3rd byte. The 5 selects which bit to flip: 0-7, i.e 5 is the 6th bit. This will only work for files that fit in memory.
Explanation
-p iterate through the file, print after each iteration
-0777 read the whole file into memory on each iteration (so there will only be one iteration)
-e run the following perl code inside the loop
substr select a single character in the file starting at index 2
^=chr XOR that character with 1 shifted 5 times i.e. 2^5
This answer is a simplified version of toddkaufmann’s
answered Sep 26, 2017 at 14:55
I have to test a hash function and I want to change only a single bit of a specific file.
I tried with the dd command. That works, but I can only change a whole byte and not just a bit.
sudo dd if=/dev/zero of=/file.bin bs=1 seek=10 count=1 conv=notrunc
I also tried the sed command with a regex, but as I don’t know the content of the file, I can’t just change an «a» to a «b».
Does anyone know a command for doing this?
dhag
15.1k4 gold badges55 silver badges64 bronze badges
asked Apr 14, 2015 at 20:17
7
Since the file may contain nulls, text-oriented filters like sed are going to fail. But you can use a programming language that can handle nulls, like perl or python. Here’s a solution for Python 3. It’s a few lines longer than strictly necessary, for readability.
#!/usr/bin/env python3
"""Toggle the bit at the specified offset.
Syntax: <cmdname> filename bit-offset"""
import sys
fname = sys.argv[1]
# Convert bit offset to bytes + leftover bits
bitpos = int(sys.argv[2])
nbytes, nbits = divmod(bitpos, 8)
# Open in read+write, binary mode; read 1 byte
fp = open(fname, "r+b")
fp.seek(nbytes, 0)
c = fp.read(1)
# Toggle bit at byte position `nbits`
toggled = bytes( [ ord(c)^(1<<nbits) ] )
# print(toggled) # diagnostic output
# Back up one byte, write out the modified byte
fp.seek(-1, 1) # or absolute: fp.seek(nbytes, 0)
fp.write(toggled)
fp.close()
Save it in a file (e.g., bitflip), make it executable, and run it with the filename to modify and the offset in bits. Note that it modifies the file in place. Run it twice with the same offset and you’ll get your file restored.
answered Apr 14, 2015 at 21:49
alexisalexis
5,6593 gold badges21 silver badges28 bronze badges
3
I don’t think there’s a single command.
Here’s a simple script, save it as «flipbit«:
#!/usr/bin/perl
# Arguments: byte (starting from 0), bit (0-7), filename (otherwise stdin)
$byte = shift(@ARGV);
$bit = shift(@ARGV);
undef $/;
$file=<>;
substr($file,$byte,1) = substr($file,$byte,1) ^ chr(1<<$bit);
print $file;
test:
$ echo abb | ~/bin/flip-bit.pl 2 0 | od -xa
0000000 6261 0a63
a b c nl
this flipped the low-order bit (0) of the third character, changing the ‘b’ to ‘c’.
As a single line command:
perl -e '$byte=shift(@ARGV);$bit=shift(@ARGV);undef $/; $file=<>; substr($file,$byte,1) = substr($file,$byte,1) ^ chr(1<<$bit); print $file'
answered Apr 14, 2015 at 21:40
1
Finally I found a solution with xxd and dd.
a=$(xxd -b -l 1 -seek 3 -p a.bin);b=1;echo -e "x$((${a}^${b}))" | dd of=a.bin bs=1 seek=3 count=1 conv=notrunc
hexdump a.bin v
0000000 61 39 73 36 36 64 66 38 61 39 73 64 35 36 66 35
0000010 37 61 73 64 37 66 74 75 61 67 73 0a 61 73 64 66
hexdump b.bin v
0000000 61 39 73 37 36 64 66 38 61 39 73 64 35 36 66 35
0000010 37 61 73 64 37 66 74 75 61 67 73 0a 61 73 64 66
But this is ugly.
answered Apr 14, 2015 at 22:12
KantiumKantium
2531 gold badge2 silver badges9 bronze badges
2
If you really want to use dd, here is an abomination that will do the trick by flipping the highest bit in the given byte. Adjust the settings for the tr command to change the selected bit.
# Preparation
finger > original.txt
BYTE=3
# Here we go...
dd if=original.txt bs=1c 2>/dev/null | ( dd bs=1c count=$((BYTE-1)) ; dd bs=1c count=1 | tr '00-377' '200-37700-177' ; dd bs=1c ) 2>/dev/null > flipped.txt
# Demonstrate the difference (byte 3: 67 → e7)
hexdump -C original.txt | head -1
00000000 4c 6f 67 69 6e 20 20 20 20 20 4e 61 6d 65 20 20 |Login Name |
hexdump -C flipped.txt | head -1
00000000 4c 6f e7 69 6e 20 20 20 20 20 4e 61 6d 65 20 20 |Lo.in Name |
answered Apr 14, 2015 at 21:42
roaimaroaima
99.2k14 gold badges126 silver badges239 bronze badges
Simple solution using head, tail, xxd. Example below flips least significant bit in last byte of file.bin.
head -c -1 file.bin > flipped.bin
LAST=`tail -c 1 file.bin | xxd -ps`
printf "%02X" $(( $((16#$LAST)) ^ 1 )) | xxd -r -ps >> flipped.bin
answered Jan 12, 2017 at 12:37
Here are two perl one-liners. The first changes the file bar in-place:
perl -p -0777 -i -e 'substr($_,2,1)^=chr(1<<5)' bar
The second reads file foo and writes bar
perl -p -0777 -e 'substr($_,2,1)^=chr(1<<5)' < foo > bar
To adapt to your application: the 2 selects which byte: 0..file_length-1, i.e. 2 is the 3rd byte. The 5 selects which bit to flip: 0-7, i.e 5 is the 6th bit. This will only work for files that fit in memory.
Explanation
-p iterate through the file, print after each iteration
-0777 read the whole file into memory on each iteration (so there will only be one iteration)
-e run the following perl code inside the loop
substr select a single character in the file starting at index 2
^=chr XOR that character with 1 shifted 5 times i.e. 2^5
This answer is a simplified version of toddkaufmann’s
answered Sep 26, 2017 at 14:55
Си: как изменить отдельный бит в байте
Вопрос:
Добрый вечер, подскажите как изменить бит в байте?
Мой ответ:
Побитовым исключающим или (операция ^) со степенью двойки от номера нужного бита (справа налево, младший бит имеет номер 0)
Пример на консольном Си:
#include <stdio.h>
#include <math.h>
void main () {
char b=0xFF; //Байт
int n=3; //Номер бита, который нужно изменить - считаем справа налево с 0
printf ("nБыло: %02X",b);
b^=(int)pow(2,n); //Переключение нужного бита - 0 в 1 или 1 в 0
printf ("nПосле переключения: %02X",b);
b^=(int)pow(2,n);
printf ("nПереключили еще раз: %02X",b);
}
На самом деле вычислять степени двойки глупо, раз есть побитовый сдвиг, так что окончательно получаем такую версию:
#include <stdio.h>
void main () {
char b=0xFF; //Байт
int n=3; //Номер бита, который нужно изменить - считаем справа налево с 0
printf ("nБыло: %02X",b);
b^=1<<n; //Переключение нужного бита - 0 в 1 или 1 в 0
printf ("nПосле переключения: %02X",b);
b^=1<<n;
printf ("nПереключили еще раз: %02X",b);
}
Если нужно просто проверить, установлен ли в байте тот или иной бит, в тех же обозначениях получится следующий код:
if (b&1<<n) {
//Бит с номером n установлен
}
else {
//Бит не установлен
}
Здесь нам помогла операция побитового «и» (бинарная операция &)
Наконец, бывает нужно принудительно включить или выключить некоторый бит независимо от его начального состояния. Для принудительного включения пригодится операция побитового «или» (|), ведь обычное «или» с единицей всегда даёт единицу, а с нулём оставляет состояние бита неизменным:
b|=1<<n; //Принудительно включаем бит номер n
Для принудительного выключения бита применим к байту побитовое «и» с числом, обратным к нужной степени двойки, то есть, таким, где все единичные биты заменены нулевыми и наоборот. Взять такое число можно побитовым отрицанием (операция ~), в итоге получится вот что:
b&=~(1<<n); //Принудительно выключаем бит номер n
Побитовое «и» с единицей оставит неизменными все биты кроме того, который сброшен нами в ноль.
Чтобы менять биты не «вручную», используйте <bitset>.
26.04.2012, 14:53 [34693 просмотра]
Для тех кому надо освежить знания оставлю тут памятку, более подробно эти операции будут рассмотрены в статье.
установить нулевой бит
PORTB | = 0x01;
сбростить нулевой бит
PORTB &= ~0x01;
проверить установлен ли бит
if(PORTB & 0x01)
{
}
инвертировать значение нулевого бита
#define LED 0x01
PORTB ^= LED;
Независимо от того какие микроконтроллеры Вы собираетесь программировать, первое что придётся освоить — это битовые операции.
Битовых операций в языке Си всего 6.
& ( AND )
| ( OR )
^ ( XOR )
~ ( NOT )
<<(сдвиг влево)
>>(сдвиг вправо)
Начнем с того, что выводы микроконтроллера условно разделены на порты, у Atmega16 порт состоит из 8 выводов, у STM32f103 из 16 выводов.
Отмеченные ножки, как раз и составляют порт А.
Побитовое ИЛИ — результат операции равен 1, если один из соответствующих битов равен 1, иначе 0.
Установить в 1 нулевой бит порта B можно следующим образом.
PORTB = 0x01; //шестнадцатеричная запись
или
PORTB = 0b00000001; //двоичная запись
или
PORTB = 1; //десятичная запись
Таким образом, мы установили нулевой бит в 1, а все остальные в 0, то есть мы переопределили все биты порта. А что если мы хотим установить в 1 только нулевой бит и не задеть остальные? В таком случае нужно воспользоваться побитовым ИЛИ.
установить нулевой бит в единицу
PORTB = PORTB | 0x01;
или воспользовавшись составным присваиванием
PORTB | = 0x01;
В результате мы изменили только нулевой бит порта.
Надо отметить, что в микроконтроллерах счёт начинается с нуля, то есть первый бит будет иметь нулевой порядковый номер, а порядковый номер восьмого бита будет 7.
Битовая операция НЕ — изменяет значение бита на противоположное.
Побитовое И — если соответствующие биты равны 1, результирующий бит равен 1. Если один из соответствующих битов равен 0, то результирующий бит равен 0.
Эта операция совместно с битовым НЕ может использоваться для сброса конкретного бита в ноль.
сростить нулевой бит
PORTB &= 0xFE;
или
PORTB &= ~0x01;
При такой записи, мы выставляем в единицу бит, который хотим обнулить, затем инвертируем получившееся число
Теперь накладываем получившуюся маску
В итоге мы выставили в 0 только первый бит.
Также эту операцию можно использовать для проверки чему равен бит. Например, нам надо проверить чему равен нулевой бит порта B, это можно сделать с помощью следующей конструкции.
if(PORTB & 0x01)
Если бит равен единице, выражение в скобках будет правда, иначе — ложь.
Побитовое исключающее ИЛИ — если сумма соответствующих битов число чётное, результирующий бит 0, иначе 1.
С помощью этой операции можно инвертировать состояние выбранного бита. Например, к нулевому выводу порта подключен светодиод и при выполнении одного и того же фрагмента кода, мы хотим чтобы он погас если горит и наоборот, зажёгся если не горит.
#define LED 0x01
PORTB ^= LED;
Также с помощью этой операции можно определить равенство регистров. Например, мы хотим сравнить в одинаковом ли состоянии находятся порты B и D.
if(PORTB ^ PORTD)
Если результат равен нулю, то содержимое регистров равно.
Логический сдвиг влево — все разряды при этом сдвигаются на одну позицию влево, самый левый бит теряется, а в самый правый бит записывается 0.
Операция логического сдвига влево эквивалентна умножению на 2.
0b0000 1011 = 11
0b0001 0110 = 22
Логический сдвиг вправо — все разряды при этом сдвигаются на одну позицию вправо, самый правый бит теряется, а в самый левый бит записывается 0.
Операция логического сдвига влево эквивалентна делению на 2.
0b1000 1011 = 147
0b0100 0101 = 73
Видно, что при делении на 2 результат округляется в меньшую сторону, на этом всё.
From snip-c.zip’s bitops.h:
/*
** Bit set, clear, and test operations
**
** public domain snippet by Bob Stout
*/
typedef enum {ERROR = -1, FALSE, TRUE} LOGICAL;
#define BOOL(x) (!(!(x)))
#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))
OK, let’s analyze things…
The common expression that you seem to be having problems with in all of these is «(1L << (posn))». All this does is create a mask with a single bit on
and which will work with any integer type. The «posn» argument specifies the
position where you want the bit. If posn==0, then this expression will
evaluate to:
0000 0000 0000 0000 0000 0000 0000 0001 binary.
If posn==8, it will evaluate to:
0000 0000 0000 0000 0000 0001 0000 0000 binary.
In other words, it simply creates a field of 0’s with a 1 at the specified
position. The only tricky part is in the BitClr() macro where we need to set
a single 0 bit in a field of 1’s. This is accomplished by using the 1’s
complement of the same expression as denoted by the tilde (~) operator.
Once the mask is created it’s applied to the argument just as you suggest,
by use of the bitwise and (&), or (|), and xor (^) operators. Since the mask
is of type long, the macros will work just as well on char’s, short’s, int’s,
or long’s.
The bottom line is that this is a general solution to an entire class of
problems. It is, of course, possible and even appropriate to rewrite the
equivalent of any of these macros with explicit mask values every time you
need one, but why do it? Remember, the macro substitution occurs in the
preprocessor and so the generated code will reflect the fact that the values
are considered constant by the compiler — i.e. it’s just as efficient to use
the generalized macros as to «reinvent the wheel» every time you need to do
bit manipulation.
Unconvinced? Here’s some test code — I used Watcom C with full optimization
and without using _cdecl so the resulting disassembly would be as clean as
possible:
—-[ TEST.C ]—————————————————————-
#define BOOL(x) (!(!(x)))
#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))
int bitmanip(int word)
{
word = BitSet(word, 2);
word = BitSet(word, 7);
word = BitClr(word, 3);
word = BitFlp(word, 9);
return word;
}
—-[ TEST.OUT (disassembled) ]————————————————
Module: C:BINKtst.c
Group: 'DGROUP' CONST,CONST2,_DATA,_BSS
Segment: _TEXT BYTE 00000008 bytes
0000 0c 84 bitmanip_ or al,84H ; set bits 2 and 7
0002 80 f4 02 xor ah,02H ; flip bit 9 of EAX (bit 1 of AH)
0005 24 f7 and al,0f7H
0007 c3 ret
No disassembly errors
—-[ finis ]——————————————————————
From snip-c.zip’s bitops.h:
/*
** Bit set, clear, and test operations
**
** public domain snippet by Bob Stout
*/
typedef enum {ERROR = -1, FALSE, TRUE} LOGICAL;
#define BOOL(x) (!(!(x)))
#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))
OK, let’s analyze things…
The common expression that you seem to be having problems with in all of these is «(1L << (posn))». All this does is create a mask with a single bit on
and which will work with any integer type. The «posn» argument specifies the
position where you want the bit. If posn==0, then this expression will
evaluate to:
0000 0000 0000 0000 0000 0000 0000 0001 binary.
If posn==8, it will evaluate to:
0000 0000 0000 0000 0000 0001 0000 0000 binary.
In other words, it simply creates a field of 0’s with a 1 at the specified
position. The only tricky part is in the BitClr() macro where we need to set
a single 0 bit in a field of 1’s. This is accomplished by using the 1’s
complement of the same expression as denoted by the tilde (~) operator.
Once the mask is created it’s applied to the argument just as you suggest,
by use of the bitwise and (&), or (|), and xor (^) operators. Since the mask
is of type long, the macros will work just as well on char’s, short’s, int’s,
or long’s.
The bottom line is that this is a general solution to an entire class of
problems. It is, of course, possible and even appropriate to rewrite the
equivalent of any of these macros with explicit mask values every time you
need one, but why do it? Remember, the macro substitution occurs in the
preprocessor and so the generated code will reflect the fact that the values
are considered constant by the compiler — i.e. it’s just as efficient to use
the generalized macros as to «reinvent the wheel» every time you need to do
bit manipulation.
Unconvinced? Here’s some test code — I used Watcom C with full optimization
and without using _cdecl so the resulting disassembly would be as clean as
possible:
—-[ TEST.C ]—————————————————————-
#define BOOL(x) (!(!(x)))
#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))
int bitmanip(int word)
{
word = BitSet(word, 2);
word = BitSet(word, 7);
word = BitClr(word, 3);
word = BitFlp(word, 9);
return word;
}
—-[ TEST.OUT (disassembled) ]————————————————
Module: C:BINKtst.c
Group: 'DGROUP' CONST,CONST2,_DATA,_BSS
Segment: _TEXT BYTE 00000008 bytes
0000 0c 84 bitmanip_ or al,84H ; set bits 2 and 7
0002 80 f4 02 xor ah,02H ; flip bit 9 of EAX (bit 1 of AH)
0005 24 f7 and al,0f7H
0007 c3 ret
No disassembly errors
—-[ finis ]——————————————————————
|
0 / 0 / 0 Регистрация: 29.01.2010 Сообщений: 68 |
|
|
1 |
|
|
24.04.2010, 22:55. Показов 13086. Ответов 19
Пишу на ASM для ATtiny2313.
__________________
0 |
|
0 / 0 / 0 Регистрация: 23.01.2010 Сообщений: 966 |
|
|
24.04.2010, 23:05 |
2 |
|
sbi PORTD,0 ;Нулевой бит порта B становится единицой
0 |
|
0 / 0 / 0 Регистрация: 29.01.2010 Сообщений: 68 |
|
|
24.04.2010, 23:25 |
3 |
|
Увы, номера битов в командах sbi и cbi можно задать только как константы.
0 |
|
0 / 0 / 0 Регистрация: 06.04.2010 Сообщений: 1,088 |
|
|
24.04.2010, 23:45 |
4 |
|
Не хочется писать 4 одинаковых куска кода для каждого индикатора, вот и думал что это можно как-то оптимизировать. IN PORTD, Temp — это ты так порт в регистре сохраняешь? Как-то не очень понятно. В Temp у тебя сдвигается номер разряда и хранится состояние порта?
0 |
|
0 / 0 / 0 Регистрация: 23.01.2010 Сообщений: 966 |
|
|
25.04.2010, 00:09 |
5 |
|
Например можно так еще
0 |
|
0 / 0 / 0 Регистрация: 06.04.2010 Сообщений: 1,088 |
|
|
25.04.2010, 00:13 |
6 |
|
Например можно так еще Если мне не изменяет память, то порт читается несколько по иному:
0 |
|
0 / 0 / 0 Регистрация: 29.01.2010 Сообщений: 68 |
|
|
25.04.2010, 00:25 |
7 |
|
Из helpa по ASM: Не катит
0 |
|
0 / 0 / 0 Регистрация: 23.01.2010 Сообщений: 966 |
|
|
25.04.2010, 00:28 |
8 |
|
Например можно так еще Если мне не изменяет память, то порт читается несколько по иному: Ну это смотря что тебе надо прочитать.. у аффтара топика написано что другие биты порта используются в других целях. Я предположил что они тоже настроены на выход как и первые четыре.
0 |
|
0 / 0 / 0 Регистрация: 24.01.2010 Сообщений: 727 |
|
|
25.04.2010, 00:36 |
9 |
|
как-то так, может Код in temp,PORTD ;считываем значение из порта. тут будем хранить биты не относящие к индикатору mov temp1,temp ;тут биты индикатора omdi temp,0xF0 ;обнуляем ненужные биты omdi temp1,0x0F ;аналогично swap temp1 ;меняем местами нибблы, чтобы получить перенос при сдвиге rol temp1 ;сдвигаем на следующий разряд brcs label1 ;если вышли за 4 разряд идем зажигаем первый разряд swap temp1 ;опять меняем местами rjmp label2 label1: ldi temp1,1 ; если вышли, зажигаем первый label2: or temp,temp1 ;собираем значение остальных бит с новым значением out PORTD,temp ;выводим хотя вместо двух свапов можно поставить cpi, может меньше памяти займет
Если мне не изменяет память, то порт читается несколько по иному: В данном случае не имеет значения. Когда порт на выход настроен, то его значение читается и оттуда и оттуда, а остальные биты не меняются. но эта операция должна быть атомарной. Если она выполняется не в прерывании, значение PORTD может измениться в процессе выполнения этого кода. По-этому может быть лучше Код in temp,PORTD omdi temp,0x0F swap temp rol temp brcs label1 swap temp rjmp label2 label1: ldi temp,1 label2: cli in temp1,PORTD omdi temp1,0xF0 or temp1,temp out PORTD,temp1 sei но опять-таки… sei и cli… если прерывания были выключены до этого кода, то он их включит. по-этому в идеале надо не cli, sei. а сохранять состояние флага I и восстанавливать его. Но это уже если совсем докапываться…
0 |
|
0 / 0 / 0 Регистрация: 06.04.2010 Сообщений: 1,088 |
|
|
25.04.2010, 00:52 |
10 |
|
В данном случае не имеет значения. Когда порт на выход настроен, то его значение читается и оттуда и оттуда, а остальные биты не меняются. Спасибо, не знал. Запомню — выход можно читать, как PORT.
0 |
|
0 / 0 / 0 Регистрация: 24.01.2010 Сообщений: 727 |
|
|
25.04.2010, 00:57 |
11 |
|
Плюс, в данном конкретном случае лучше читать PORT. Потому что, например на других битах висит кнопка, бит настроен на вход с внутренней подтяжкой, т.е. ddr=0, port=1. А прочитав pin при нажатой кнопке, мы получим в том разряде 0 и запишем его в port, переключив тот бит в другой режим.
0 |
|
0 / 0 / 0 Регистрация: 23.01.2010 Сообщений: 156 |
|
|
25.04.2010, 01:35 |
12 |
|
Если биты нужно именно изменить, то запиши в PINxx маску.
0 |
|
0 / 0 / 0 Регистрация: 24.01.2010 Сообщений: 727 |
|
|
25.04.2010, 01:39 |
13 |
|
Если биты нужно именно изменить, то запиши в PINxx маску. биты нужно изменить, а не инвертировать
0 |
|
0 / 0 / 0 Регистрация: 23.01.2010 Сообщений: 156 |
|
|
25.04.2010, 02:04 |
14 |
|
Хз. Менять биты можно по-разному. Например «0» поменять на «1». Или «1» на «0».
0 |
|
0 / 0 / 0 Регистрация: 24.01.2010 Сообщений: 727 |
|
|
25.04.2010, 02:36 |
15 |
|
Отличается тем, что рассматривая каждый бит по-отдельности его значение можно изменить только инвертированием, а если рассматривать все 4 нужных бита — вариантов изменения больше. Плюс к этому на маску при сдвиге надо на одно сравнение больше, так как циклически сдвигать надо два единичных бита. Хотя можно и использовать маску 00110011…
0 |
|
0 / 0 / 0 Регистрация: 06.04.2010 Сообщений: 1,088 |
|
|
25.04.2010, 02:44 |
16 |
|
У меня была подобная проблема — на одном порту висит ЖКИ и кнопки. Для кнопок сдвигается нуль. Решил это так, может с академической точки зрения и не правильно, но понятно и работает. Только у меня состояние не считывается с порта, а хранится и сдвигается в отдельном регистре. sbi PORTD, 0 ; все нужные пины установить в 1
0 |
|
0 / 0 / 0 Регистрация: 23.01.2010 Сообщений: 156 |
|
|
25.04.2010, 03:03 |
17 |
|
Отличается тем, что рассматривая каждый бит по-отдельности его значение можно изменить только инвертированием, а если рассматривать все 4 нужных бита — вариантов изменения больше…. С единичными ногоами, согласен, проще через sbi, cbi. А если нужно одновременно
0 |
|
0 / 0 / 0 Регистрация: 06.04.2010 Сообщений: 1,088 |
|
|
25.04.2010, 03:09 |
18 |
|
С единичными ногоами, согласен, проще через sbi, cbi. А если нужно одновременно А нельзя ли в виде кода это посмотреть? Полезная вещь.
0 |
|
0 / 0 / 0 Регистрация: 24.01.2010 Сообщений: 727 |
|
|
25.04.2010, 03:14 |
19 |
|
я так понял, что-то типа такого (если применительно к данному случаю): Код ldi nextVal,0x02 ;начальное присвоение ... in temp,PIND omdi temp,0x0F ;чтобы не похерить ненужные биты eor temp,nextVal out PIND,temp ...;тут циклический сдвиг nextVal например PIND=xxxx0001, nextVal=00000010, получим temp=00000011. при записи в pind оно обнулит младший бит и выставит в 1 следующий. p.s. но прокатит не на всех мк, ЕМНИП такое инвертирование битов появилось в более-менее новых моделях, типа тини2313, мега48-328. В меге8-16 такого нету
0 |
|
0 / 0 / 0 Регистрация: 23.01.2010 Сообщений: 156 |
|
|
25.04.2010, 03:22 |
20 |
|
Ага. Гдето-так. Только для выходных портов я все-таки читаю не
0 |
От snip-c.zip bitops.h:
/*
** Bit set, clear, and test operations
**
** public domain snippet by Bob Stout
*/
typedef enum {ERROR = -1, FALSE, TRUE} LOGICAL;
#define BOOL(x) (!(!(x)))
#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))
Хорошо, пусть анализирует вещи…
Общее выражение, с которым у вас возникли проблемы во всех этих случаях, — «(1L << (posn))». Все это создает маску с одним битом и которая будет работать с любым целым типом. Аргумент «posn» указывает позицию, в которой вы хотите бит. Если posn == 0, то это выражение будет оцениваться так:
0000 0000 0000 0000 0000 0000 0000 0001 binary.
Если posn == 8, он будет оценивать
0000 0000 0000 0000 0000 0001 0000 0000 binary.
Другими словами, он просто создает поле 0 с 1 в указанной позиции. Единственная сложная часть — в макросе BitClr(), где нам нужно установить один бит 0 бит в поле 1. Это достигается с помощью дополнения 1 того же выражения, которое обозначается оператором тильды (~).
Как только маска создается, она применяется к аргументу так же, как вы предполагаете, с помощью побитовых и (&) или (|) и xor (^) операторов. Поскольку маска имеет тип long, макросы будут работать так же хорошо, как на char, short, int, или long.
Суть в том, что это общее решение всего класса проблем. Разумеется, возможно и даже целесообразно переписать эквивалент любого из этих макросов с явными значениями маски каждый раз, когда вам это нужно, но зачем это делать? Помните, что макроподстановка происходит в препроцессоре, и поэтому сгенерированный код будет отражать тот факт, что значения считаются постоянными компилятором, т.е. Так же эффективно использовать обобщенные макросы, чтобы «изобретать колесо» каждый раз, когда вам нужно сделайте бит-манипуляцию.
Убежденный? Здесь некоторый тестовый код — я использовал Watcom C с полной оптимизацией и без использования _cdecl, поэтому результирующая разборка была бы настолько чистой, насколько это возможно:
—- [TEST.C] —————————————— ————————
#define BOOL(x) (!(!(x)))
#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))
int bitmanip(int word)
{
word = BitSet(word, 2);
word = BitSet(word, 7);
word = BitClr(word, 3);
word = BitFlp(word, 9);
return word;
}
—- [TEST.OUT (разобрано)] ————————————— ———
Module: C:BINKtst.c
Group: 'DGROUP' CONST,CONST2,_DATA,_BSS
Segment: _TEXT BYTE 00000008 bytes
0000 0c 84 bitmanip_ or al,84H ; set bits 2 and 7
0002 80 f4 02 xor ah,02H ; flip bit 9 of EAX (bit 1 of AH)
0005 24 f7 and al,0f7H
0007 c3 ret
No disassembly errors
—- [finis] ——————————————- ———————-

Данная тема является одной из самых сложных для понимания в рамках данного курса уроков, так что давайте разберёмся, зачем вообще нужно уметь работать с битами:
- Гибкая и быстрая работа напрямую с регистрами микроконтроллера.
- Работа напрямую с внешними микросхемами (датчики и прочее), управление которыми состоит из записи и чтения регистров, данные в которых могут быть запакованы в байты самым причудливым образом.
- Более эффективное хранение данных: упаковка нескольких значений в одну переменную и распаковка обратно.
- Создание символов и другой информации для матричных дисплеев.
- Максимально быстрые вычисления.
- Разбор чужого кода.
Данный урок основан на оригинальном уроке по битовым операциям от Arduino, можете почитать его здесь – там всё описано чуть более подробно.
Двоичная система
В цифровом мире, к которому относится также микроконтроллер, информация хранится, преобразуется и передается в цифровом виде, то есть в виде нулей и единиц. Соответственно элементарная ячейка памяти, которая может запомнить 0 или 1, называется бит (bit).
Минимальная ячейка памяти, которую мы можем изменить – 1 бит, а ячейка памяти, которая которая имеет адрес в памяти и мы можем к ней обратиться – байт, который состоит из 8-ми бит, каждый занимает своё место (примечание: в других архитектурах в байте может быть больше или меньше бит, в данном уроке речь идёт об AVR и 8-ми битном байте). 
| Двоичная | Десятичная |
| 0000 | 0 |
| 0001 | 1 |
| 0010 | 2 |
| 0011 | 3 |
| 0100 | 4 |
| 0101 | 5 |
| 0110 | 6 |
| 0111 | 7 |
| 1000 | 8 |
| 1001 | 9 |
| … | … |
| 10000 | 16 |
Заметили последовательность?
Здесь также нужно увидеть важность степени двойки – на ней в битовых операциях завязано абсолютно всё. Давайте посмотрим на первые 8 степеней двойки в разных системах счисления:
| 2 в степени | DEC | BIN |
| 0 | 1 | 0b00000001 |
| 1 | 2 | 0b00000010 |
| 2 | 4 | 0b00000100 |
| 3 | 8 | 0b00001000 |
| 4 | 16 | 0b00010000 |
| 5 | 32 | 0b00100000 |
| 6 | 64 | 0b01000000 |
| 7 | 128 | 0b10000000 |
Таким образом, степень двойки явно “указывает” на номер бита в байте, считая справа налево (примечание: в других архитектурах может быть иначе). Напомню, что абсолютно неважно, в какой системе исчисления вы работаете – микроконтроллеру всё равно и он во всём видит единицы и нули. Если мы “включим” все биты в байте, то получится число 0b11111111 в двоичной системе или 255 в десятичной.
Если “сложить” полный байт в десятичном представлении каждого бита: 128+64+32+16+8+4+2+1 – получится 255. Нетрудно догадаться, что число 0b11000000 равно 128+64, то есть 192. Именно таким образом и получается весь диапазон от 0 до 255, который умещается в один байт. Если взять два байта – будет всё то же самое, просто ячеек будет 16, то же самое для 4 байт – 32 ячейки с единицами и нулями, каждая имеет свой номер согласно степени двойки.
Другие системы счисления
Данные в памяти микроконтроллера хранятся в двоичном представлении, но помимо него существуют и другие системы счисления, в которых мы можем работать. Переводить числа из одной системы счисления в другую не нужно: программе абсолютно всё равно, в каком формате вы скармливаете значение переменной, они автоматически будут интерпретированы в двоичный вид. Разные системы счисления введены в первую очередь для удобства программиста.
Теперь по сути: Arduino поддерживает четыре классических системы счисления: двоичную, восьмеричную, десятичную и шестнадцатеричную.
- Двоичная (Binary) имеет префикс 0b (ноль бэ) или B, то есть двоичное число 101 запишется как
0b101илиB101. - С десятичной (DEC) всё просто, пишем числа так, как они выглядят.
10это десять,25это двадцать пять и так далее. - Восьмеричная (Octal) может содержать числа от 0 до 7 и имеет префикс 0 (ноль), например
012. - 16-ричная (hexademical) система имеет 16 значений на один разряд, первые 10 как у десятичной, остальные – первые буквы латинского алфавита: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f. При записи имеет префикс 0x (ноль икс), число FF19 запишется как
0xFF19.
| Базис | Префикс | Пример | Особенности |
| 2 (двоичная) | B или 0b (ноль бэ) | B1101001 | цифры 0 и 1 |
| 8 (восьмеричная) | 0 (ноль) | 0175 | цифры 0 – 7 |
| 10 (десятичная) | нет | 100500 | цифры 0 – 9 |
| 16 (шестнадцатеричная) | 0x (ноль икс) | 0xFF21A | цифры 0-9, буквы A-F |
Основная фишка 16-ричной системы в том, что она позволяет записывать длинные десятичные числа короче, например один байт (255) запишется как 0xFF, два байта (65 535) как 0xFFFF, а жуткие три байта (16 777 215) как 0xFFFFFF.
Двоичная система обычно используется для наглядного представления данных и низкоуровневых конфигураций различного железа. Например конфиг кодируется одним байтом, каждый бит в нём отвечает за отдельную настройку (вкл/выкл), и передав один байт вида 0b10110100 можно сразу кучу всего настроить, к этому мы вернёмся в уроке работа с регистрами из раздела продвинутых уроков. В документации по этому поводу пишут в стиле “первый бит отвечает за это, второй за то” и так далее. Перейдём к изменению состояний битов.
Макросы для манипуляций с битами
В библиотеке Arduino.h есть несколько удобных макросов, которые позволяют включать и выключать биты в байте:
| Макрос | Действие |
bitRead(value, bit) |
Читает бит под номером bit в числе value |
bitSet(value, bit) |
Включает (ставит 1) бит под номером bit в числе value |
bitClear(value, bit) |
Выключает (ставит 0) бит под номером bit в числе value |
bitWrite(value, bit, bitvalue) |
Ставит бит под номером bit в состояние bitvalue (0 или 1) в числе value |
bit(bit) |
Возвращает 2 в степени bit |
| Другие встроенные макросы | |
_BV(bit) |
Возвращает 2 в степени bit |
bit_is_set(value, bit) |
Проверка на включенность (1) бита bit в числе value |
bit_is_clear(value, bit) |
Проверка на выключенность (0) бита bit в числе value |
Простой пример:
// тут myByte == 0 byte myByte = 0; // тут myByte станет 128 или 0b10000000 bitSet(myByte, 7); // тут myByte станет 192 или 0b11000000 bitWrite(myByte, 6, 1);
Этого уже достаточно для полноценной работы с регистрами. Так как это именно макросы, работают они максимально быстро и ничуть не хуже написанных вручную элементарных битовых операций. Чуть ниже мы разберём содержимое этих макросов и увидим, как они работают, а пока познакомимся с элементарными логическими операциями.
Примечание: число value из таблицы выше может иметь любой целочисленный тип, 1 байт (byte), 2 байта (int), 4 байта (long)
Битовые операции
Битовое И
И (AND), оно же “логическое умножение”, выполняется оператором & или and и возвращает следующее:
0 & 0 == 0 0 & 1 == 0 1 & 0 == 0 1 & 1 == 1
Основное применение операции И – битовая маска. Позволяет “взять” из байта только указанные биты:
myByte = 0b11001100; myBits = myByte & 0b10000111; // myBits теперь равен 0b10000100
То есть при помощи & мы взяли из байта 0b11001100 только биты 10000111, а именно – 0b11001100, и получили 0b10000100 Также можно использовать составной оператор &=
myByte = 0b11001100; myByte &= 0b10000000; // берём старший бит // myByte теперь 10000000
Битовое ИЛИ
ИЛИ (OR), оно же “логическое сложение”, выполняется оператором | или or и возвращает следующее:
0 | 0 == 0 0 | 1 == 1 1 | 0 == 1 1 | 1 == 1
Основное применение операции ИЛИ – установка бита в байте:
myByte = 0b11001100; myBits = myByte | 0b00000001; // ставим бит №0 // myBits теперь равен 0b11001101 myBits = myBits | bit(1); // ставим бит №1 // myBits теперь равен 0b11001111
Также можно использовать составной оператор |=
myByte = 0b11001100; myByte |= 16; // 16 - 2 в 4, включаем бит №4 // myByte теперь 0b11011100
Вы уже поняли, что указывать на нужные биты можно любым удобным способом: в бинарном виде (0b00000001 – нулевой бит), в десятичном виде (16 – четвёртый бит) или при помощи макросов bit() или _BV() (bit(7) даёт 128 или 0b10000000, _BV(7) делает то же самое)
Битовое НЕ
Битовая операция НЕ (NOT) выполняется оператором ~и просто инвертирует бит:
~0 == 1 ~1 == 0
Также она может инвертировать байт:
myByte = 0b11001100; myByte = ~myByte; // инвертируем // myByte теперь 00110011
Битовое исключающее ИЛИ
Битовая операция исключающее ИЛИ (XOR) выполняется оператором ^ или xor и делает следующее:
0 ^ 0 == 0 0 ^ 1 == 1 1 ^ 0 == 1 1 ^ 1 == 0
Данная операция обычно используется для инвертирования состояния отдельного бита:
myByte = 0b11001100; myByte ^= 0b10000000; // инвертируем 7-ой бит // myByte теперь 01001100
То есть мы взяли бит №7 в байте 0b11001100 и перевернули его в 0, получилось 0b01001100, остальные биты не трогали.
Битовый сдвиг
Битовый сдвиг – очень мощный оператор, позволяет буквально “двигать” биты в байте вправо и влево при помощи операторов >> и <<, и соответственно составных >>= и <<=. Если биты выходят за границы блока (8 бит, 16 бит или 32 бита) – они теряются.
myByte = 0b00011100; myByte = myByte << 3; // двигаем на 3 влево // myByte теперь 0b11100000 myByte >>= 5; // myByte теперь 0b00000111 myByte >>= 2; // myByte теперь 0b00000001 // остальные биты потеряны!
Битовый сдвиг делает не что иное, как умножает или делит байт на 2 в степени. Да, это операция деления, выполняющаяся за один такт процессора! К этому мы ещё вернёмся ниже. Посмотрите на работу оператора сдвига и сравните её с макросами bit() и _BV():
1 << 0 == 1 1 << 1 == 2 1 << 2 == 4 1 << 3 == 8 ... 1 << 8 == 256 1 << 9 == 512 1 << 10 == 1024
Возведение двойки в степень! Важный момент: при сдвиге дальше, чем на 15, нужно преобразовывать тип данных, например в ul: 1 << 17 даст результат 0, потому что сдвиг выполняется в ячейке int (как при умножении, помните?). Но если мы напишем 1ul << 17 – результат будет верный.
Включаем-выключаем
Вспомним пример из пункта про битовое ИЛИ, про установку нужного бита. Вот эти варианты кода делают одно и то же:
myByte = 0b11000011; // ставим бит №3 разными способами // по сути - одно и то же myByte |= (1 << 3); myByte |= bit(3); myByte |= _BV(3); bitSet(myByte, 3); // myByte равен 0b11001011
Как насчёт установки нескольких бит сразу?
myByte = 0b11000011; // ставим бит №3 и 4 разными способами // по сути - одно и то же myByte |= (1 << 3) | (1 << 4); myByte |= bit(3) | bit(4); myByte |= _BV(3) | _BV(4); // myByte равен 0b11011011
Или прицельного выключения бит? Тут чуть по-другому, используя &= и ~
myByte = 0b11000011; // выключаем бит №1 разными способами // по сути - одно и то же myByte &= ~(1 << 1); myByte &= ~_BV(1); bitClear(myByte, 1); // myByte равен 0b11000001
Выключить несколько бит сразу? Пожалуйста!
myByte = 0b11000011; // выключаем биты №0 и 1 разными способами myByte &= ~( (1 << 0) | (1 << 1) ); myByte &= ~( _BV(0) | _BV(1) ); // myByte равен 0b11000000
Именно такие конструкции встречаются в коде высокого уровня и библиотеках, именно так производится работа с регистрами микроконтроллера. Вернёмся к устройству Ардуиновских макросов:
#define bitRead(value, bit) (((value) >> (bit)) & 0x01) #define bitSet(value, bit) ((value) |= (1UL << (bit))) #define bitClear(value, bit) ((value) &= ~(1UL << (bit))) #define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit)) #define bit(b) (1UL << (b))
Я думаю, комментарии излишни: макросы состоят из тех же элементарных битовых операций и сдвигов!
Быстрые вычисления
Как я уже говорил, битовые операции – самые быстрые. Если требуется максимальная скорость вычислений – их можно оптимизировать и подогнать под “степени двойки”, но иногда компилятор делает это сам, подробнее смотри в уроке про оптимизацию кода. Рассмотрим базовые операции:
- Деление на
2^n– сдвиг вправо наn. Например,val / 8можно записать какval >> 3. Компилятор не оптимизирует деление самостоятельно, что позволяет ускорить данную операцию приблизительно в 15 раз при ручной оптимизации. - Умножение на
2^n– сдвиг влево наn. Например,val * 8можно записать какval << 3. Компилятор оптимизирует умножение самостоятельно, поэтому в ручной оптимизации нет смысла. Но можно встретить в чужих исходниках. - Остаток от деления на
2^n– битовая маска наnмладших битов. Операции остатка от деленияval % (2^n)можно вычислить через битовую маску:val & (2^n - 1). Например,val % 8можно записать какval & 7, аval % 32можно записать какval & 31. Компилятор оптимизирует такие операции самостоятельно, поэтому в ручной оптимизации нет смысла. Но можно встретить в чужих исходниках.
Примечание: рассмотренные выше операции работают только с целочисленными типами данных!
Экономия памяти
При помощи битовых операций можно экономить немного памяти, пакуя данные в блоки. Например, переменная типа boolean занимает в памяти 8 бит, хотя принимает только 0 и 1. В один байт можно запаковать 8 логических переменных, например вот так:
Пакуем биты в байт, макро
// храним флаги как 1 бит
// макросы
#define B_TRUE(bp,bb) (bp) |= (bb)
#define B_FALSE(bp,bb) (bp) &= ~(bb)
#define B_READ(bp,bb) bool((bp) & (bb))
// вот так храним наши флаги, значения обязательно как степени двойки!
#define B_FLAG_1 1
#define B_FLAG_2 2
#define B_LED_STATE 4
#define B_BUTTON_STATE 8
#define B_BUTTON_FLAG 16
#define B_SUCCESS 32
#define B_END_FLAG 64
#define B_START_FLAG 128
// этот байт будет хранить 8 бит
byte boolPack1 = 0;
void setup() {
// суть такая: макрос функциями мы ставим/читаем бит в байте boolPack1
// записать true во флаг B_BUTTON_STATE
B_TRUE(boolPack1, B_BUTTON_STATE);
// записать false во флаг B_FLAG_1
B_FALSE(boolPack1, B_FLAG_1);
// прочитать флаг B_SUCCESS (для примера читаем в булин переменную)
boolean successFlag = B_READ(boolPack1, B_SUCCESS);
// либо используем в условии
if (B_READ(boolPack1, B_SUCCESS)) {
// выполнить при выполнении условия
}
}
void loop() { }
Вариант с Ардуино-функциями
// пример упаковки битовых флагов в байт
// при помощи ардуино-функций
byte myFlags = 0; // все флаги в false
// можно задефайнить названия
// цифры по порядку 0-7
#define FLAG1 0
#define FLAG2 1
#define FLAG3 2
#define FLAG4 3
#define FLAG5 4
#define FLAG6 5
#define FLAG7 6
#define FLAG8 7
void setup() {
// установить FLAG5 в true
bitSet(myFlags, FLAG5);
// установить FLAG1 в true
bitSet(myFlags, FLAG1);
// установить FLAG1 в false
bitClear(myFlags, FLAG1);
// считать FLAG5
bitRead(myFlags, FLAG5);
// условие с флагом 7
if (bitRead(myFlags, FLAG7)) {
// если FLAG7 == true
}
}
void loop() {}
Очень удобно храним целую пачку флагов (NEW!)
// вариант упаковки флагов в массив. ЛУЧШЕ И УДОБНЕЕ ПРЕДЫДУЩИХ ПРИМЕРОВ!
#define NUM_FLAGS 30 // количество флагов
byte flags[NUM_FLAGS / 8 + 1]; // массив сжатых флагов
// ============== МАКРОСЫ ДЛЯ РАБОТЫ С ПАЧКОЙ ФЛАГОВ ==============
// поднять флаг (пачка, номер)
#define setFlag(flag, num) bitSet(flag[(num) >> 3], (num) & 0b111)
// опустить флаг (пачка, номер)
#define clearFlag(flag, num) bitClear(flag[(num) >> 3], (num) & 0b111)
// записать флаг (пачка, номер, значение)
#define writeFlag(flag, num, state) ((state) ? setFlag(flag, num) : clearFlag(flag, num))
// прочитать флаг (пачка, номер)
#define readFlag(flag, num) bitRead(flag[(num) >> 3], (num) & 0b111)
// опустить все флаги (пачка)
#define clearAllFlags(flag) memset(flag, 0, sizeof(flag))
// поднять все флаги (пачка)
#define setAllFlags(flag) memset(flag, 255, sizeof(flag))
// ============== МАКРОСЫ ДЛЯ РАБОТЫ С ПАЧКОЙ ФЛАГОВ ==============
void setup() {
Serial.begin(9600);
clearAllFlags(flags);
writeFlag(flags, 0, 1);
writeFlag(flags, 10, 1);
writeFlag(flags, 12, 1);
writeFlag(flags, 15, 1);
writeFlag(flags, 15, 0);
writeFlag(flags, 29, 1);
// выводим все
for (byte i = 0; i < NUM_FLAGS; i++)
Serial.print(readFlag(flags, i));
}
void loop() {
}
Хороший трюк, может пригодиться! Я сделал удобную библиотеку для хранения битовых флагов, документация и примеры есть здесь.
Пример сжатия 1
Таким же способом можно паковать любые другие данные других размеров для удобного хранения или сжатия. Как пример – моя библиотека microLED, в которой используется следующий алгоритм: изначально необходимо хранить в памяти три цвета для каждого светодиода, каждый цвет имеет глубину 8 бит, т.е. в общей сложности тратится 3 байта на один светодиод RRRRRRRR GGGGGGGG BBBBBBBB. Для экономии места и удобства хранения можно сжать эти три байта в два (тип данных int), потеряв несколько оттенков результирующего цвета. Например вот так: RRRRRGGG GGGBBBBB. Сожмём и упакуем: есть три переменные каждого цвета, r, g, b:
int rgb = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | ((b & 0b11111000) >> 3);
Таким образом мы отбросили у красного и синего младшие (правые) биты, в этом и заключается сжатие. Чем больше битов отброшено – тем менее точно получится “разжать” число. Например сжимали число 0b10101010 (170 в десятичной) на три бита, при сжатии получили 0b10101000, т.е. потеряли три младших бита, и в десятичной уже получится 168. Для упаковки используется битовый сдвиг и маска, таким образом мы берём первые пять битов красного, шесть зелёного и пять синего, и задвигаем на нужные места в результирующей 16-битной переменной. Всё, цвет сжат и его можно хранить. Для распаковки используется обратная операция: выбираем при помощи маски нужные биты и сдвигаем их обратно в байт:
byte r = (data & 0b1111100000000000) >> 8; byte g = (data & 0b0000011111100000) >> 3; byte b = (data & 0b0000000000011111) << 3;
Таким образом можно сжимать, разжимать и просто хранить маленькие данные в стандартных типах данных.
Пример сжатия 2
Давайте ещё пример: нужно максимально компактно хранить несколько чисел в диапазоне от 0 до 3, то есть в бинарном представлении это 0b00, 0b01, 0b10 и 0b11. Видим, что в один байт можно запихнуть 4 таких числа (максимальное занимает два бита). Запихиваем:
// числа для примера byte val_0 = 2; // 0b10 byte val_1 = 0; // 0b00 byte val_2 = 1; // 0b01 byte val_3 = 3; // 0b11 byte val_pack = ((val_0 & 0b11) << 6) | ((val_1 & 0b11) << 4) | ((val_2 & 0b11) << 2) | (val_3 & 0b11); // получили 0b10000111
Как и в примере со светодиодами, мы просто брали нужные биты ( в этом случае младшие два, 0b11) и сдвигали их на нужное расстояние. Для распаковки делаем в обратном порядке:
byte unpack_1 = (val_pack & 0b11000000) >> 6; byte unpack_2 = (val_pack & 0b00110000) >> 4; byte unpack_3 = (val_pack & 0b00001100) >> 2; byte unpack_4 = (val_pack & 0b00000011) >> 0;
И получим обратно наши байты. Также маску можно заменить на более удобную для работы запись, задвинув 0b11 на нужное расстояние:
byte unpack_1 = (val_pack & 0b11 << 6) >> 6; byte unpack_2 = (val_pack & 0b11 << 4) >> 4; byte unpack_3 = (val_pack & 0b11 << 2) >> 2; byte unpack_4 = (val_pack & 0b11 << 0) >> 0;
Теперь, проследив закономерность, можно сделать для себя функцию или макрос чтения пакета:
#define UNPACK(x, y) ( ((x) & 0b11 << ((y) * 2)) >> ((y) * 2) )
Где x это пакет, а y – порядковый номер запакованного значения. Выведем посмотрим:
Serial.println(UNPACK(val_pack, 3)); Serial.println(UNPACK(val_pack, 2)); Serial.println(UNPACK(val_pack, 1)); Serial.println(UNPACK(val_pack, 0));
“Трюки” с битами
На битовых операциях можно сделать очень много всего интересного, и работать оно будет очень быстро и занимать мало места. Огромный список битовых трюков и хаков можно посмотреть в этой статье, их там очень много и все с примерами. Есть ещё один небольшой сборник самых простых и полезных хаков вот здесь (английский). Его я перевёл, смотрите ниже под спойлером. Другой вариант перевода (могут быть не все трюки) можно посмотреть здесь.
Целые числа
Перемотка бита
Перематывает один бит слева направо, то есть формирует последовательность 0b10000000, 0b01000000, 0b00100000, 0b00010000, 0b00001000, 0b00000100, 0b00000010, 0b00000001, 0b10000000, или 128, 64, 32, 16, 8, 4, 2, 1, 128
Вызывать циклично: x = ((x >> 1) | (x << 7));
Установка nго бита
x | (1<<n);
Выключение nго бита
x & ~(1<<n);
Инверсия nго бита
x ^ (1<<n);
Округление до ближайшей степени двойки
unsigned int v; // работает только с 32 битными числами v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++;
Получение максимального целого
int maxInt = ~(1 << 31); int maxInt = (1 << 31) - 1; int maxInt = (1 << -1) - 1; int maxInt = -1u >> 1;
Получение минимального целого
int minInt = 1 << 31; int minInt = 1 << -1;
Умножение на 2
n << 1;
Деление на 2
n >> 1;
Умножение на mую степень двойки
n << m;
Деление на mую степень двойки
n >> m;
Остаток от деления
n & 0b1; // на 2 n & 0b11; // на 4 n & 0b111; // на 8 // ...
Проверка равенства
(a ^ b) == 0; // a == b !(a ^ b) // использовать внутри if()
Проверка на чётность (кратность 2)
(n & 1) == 1;
Обмен значениями
a = a ^ b ^ (b = a);
Проверка на одинаковый знак
(x ^ y) >= 0;
Смена знака
i = ~i + 1; // or i = (i ^ -1) + 1; // i = -i
Вернёт 2n
1 << n;
Является ли число степенью 2
n > 0 && !(n & (n - 1));
Остаток от деления на 2n на m
m & ((1 << n) - 1);
Среднее арифметическое
(x + y) >> 1; ((x ^ y) >> 1) + (x & y);
Получить mый бит из n (от младшего к старшему)
(n >> (m-1)) & 1;
Получить mый бит из n (от старшего к младшему)
n & ~(1 << (m-1));
Проверить включен ли nый бит
x & (1<<n)
Выделение самого правого включенного бита
x & (-x);
Выделение самого правого выключенного бита
~x & (x+1);
Выделение правого включенного бита
x | (x+1);
Выделение правого выключенного бита
x & (x-1);
Получение отрицательного значения
~n + 1; (n ^ -1) + 1;
if (x == a) x = b; if (x == b) x = a;
x = a ^ b ^ x;
Поменять смежные биты
((n & 10101010) >> 1) | ((n & 01010101) << 1);
Быстрый обратный квадратный корень
float Q_rsqrt( float number ) {
union {
float f;
uint32_t i;
} conv = {number};
conv.i = 0x5f3759df - ( conv.i >> 1 );
conv.f *= 1.5 - number * 0.5 * conv.f * conv.f;
return conv.f;
}
Быстрый квадратный корень (2 байта)
uint16_t root2(uint16_t x) {
uint16_t m, y, b;
m = 0x4000;
y = 0;
while (m != 0) {
b = y | m;
y >>= 1;
if (x >= b) {
x -= b;
y |= m;
}
m >>= 2;
}
return y;
}
Быстрый квадратный корень (4 байта)
uint32_t root4(uint32_t x) {
uint32_t m, y, b;
m = 0x4000ul << 16;
y = 0;
while (m != 0) {
b = y | m;
y >>= 1ul;
if (x >= b) {
x -= b;
y |= m;
}
m >>= 2ul;
}
return y;
}
float числа
Разбить float в массив бит (unsigned uint32_t)
typedef union {float flt; uint32_t bits} lens_t;
uint32_t f2i(float x) {
return ((lens_t) {.flt = x}).bits;
}
Вернуть массив бит обратно в float
float i2f(uint32_t x) {
return ((lens_t) {.bits = x}).flt;
}
Быстрый обратный квадратный корень
return i2f(0x5f3759df - f2i(x) / 2);
Быстрый nый корень из целого числа
float root(float x, int n) {
#DEFINE MAN_MASK 0x7fffff
#DEFINE EXP_MASK 0x7f800000
#DEFINE EXP_BIAS 0x3f800000
uint32_t bits = f2i(x);
uint32_t man = bits & MAN_MASK;
uint32_t exp = (bits & EXP_MASK) - EXP_BIAS;
return i2f((man + man / n) | ((EXP_BIAS + exp / n) & EXP_MASK));
}
Быстрая степень
return i2f((1 - exp) * (0x3f800000 - 0x5c416) + f2i(x) * exp);
Быстрый натуральный логарифм
#DEFINE EPSILON 1.1920928955078125e-07 #DEFINE LOG2 0.6931471805599453 return (f2i(x) - (0x3f800000 - 0x66774)) * EPSILON * LOG2;
Быстрая экспонента
return i2f(0x3f800000 + (uint32_t)(x * (0x800000 + 0x38aa22)));
Строки
Конвертировать в нижний регистр
(x | ' ');
('a' | ' ') => 'a'; // пример
('A' | ' ') => 'a'; // пример
Конвертировать в верхний регистр
(x & '_');
('a' & '_') => 'A'; // пример
('A' & '_') => 'A'; // пример
Инвертировать регистр
(x ^ ' ');
('a' ^ ' ') => 'A'; // пример
('A' ^ ' ') => 'a'; // пример
Позиция буквы в алфавите (англ)
(x & "x1F");
('a' & "x1F") => 1; // пример
('B' & "x1F") => 2; // пример
Позиция большой буквы в алфавите (англ)
(x & '?') или (x ^ '@');
('C' & '?') => 3; // пример
('Z' ^ '@') => 26; // пример
Позиция строчной буквы в алфавите (англ)
(x ^ '`');
('d' ^ '`') => 4; // пример
('x' ^ '`') => 24; // пример
Цвет
Быстрая конвертация цвета R5G5B5 в R8G8B8
R8 = (R5 << 3) | (R5 >> 2); G8 = (R5 << 3) | (R5 >> 2); B8 = (R5 << 3) | (R5 >> 2);
Приоритет операций
Чтобы не плодить скобки, нужно знать приоритет операций. В C++ он такой:
Приоритет операций
::++--()[].->++--+-!~(type)*&sizeofnew, new[]delete, delete[].*->**/%+-<<>><<=>>===!=&^|&&||?:=+=-=*=/=%=<<=>>=&=^=|=
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])













