CRC32: на STM32 как на компе или на компе как на STM32.

И он, в общем-то, работает. Но только контрольная сумма почему-то не совпадает с таковой, рассчитанной софтварно.
В гугле обычно 2 типа вопросов:
- Как хардварно посчитать на STM32 побайтовую CRC
- Как посчитать софтово CRC так, чтоб она совпала с хардовой на STM32
Софтварно CRC32 обычно считается побайтово и (как в эзернете) — младшим битом вперёд, сдвиг LSFR — вправо, в сторону младшего бита, поэтому используется реверсированный полином 0xEDB88320.
Регистр данных же в CRC блоке STM32 — 32х-битный и сдвигается в сторону старшего бита.
Чтоб понять, почему так, немного иллюстрации:

- Во-первых, CRC — побитовая функция. параллельный подсчёт CRC — плюшки из следствий математики двоичных полиномов.
- порядок загрузки бит в LSFR не должен нарушаться в зависимости от разрядности и остроконечности архитектуры
Т.е., CRC32 на STM32 можно посчитать так же, как и принято в ethernet. Для этого надо реверсировать входные слова и реверсировать в итоге контрольную сумму. Это работает только для длины, кратной 4.
Сперва совтварная реализация.
Инициализируем таблицы остатков для быстрого вычисления CRC:
static uint32_t crc32_table[256];
static uint32_t crc32r_table[256];
#define CRC32_POLY 0x04C11DB7
#define CRC32_POLY_R 0xEDB88320
static void crc32_init(void)
{
int i, j;
uint32_t c, cr;
for (i = 0; i < 256; ++i) {
cr = i;
c = i << 24;
for (j = 8; j > 0; --j) {
c = c & 0x80000000 ? (c << 1) ^ CRC32_POLY : (c << 1);
cr = cr & 0x00000001 ? (cr >> 1) ^ CRC32_POLY_R : (cr >> 1);
}
crc32_table[i] = c;
crc32r_table[i] = cr;
//printf("f[%02X]=%08X\t", i, crc32_table[i]);
//printf("r[%02X]=%08X\t", i, crc32r_table[i]);
}
//printf("\n");
}
Побайтовое вычисление «нормальной CRC»:
uint32_t crc32_byte(uint32_t init_crc, uint8_t *buf, int len)
{
uint32_t v;
uint32_t crc;
crc = ~init_crc;
while(len > 0) {
v = *buf++;
crc = ( crc >> 8 ) ^ crc32r_table[( crc ^ (v ) ) & 0xff];
len --;
}
return ~crc;
}
Вычисление CRC как на CRC блоке STM32:
uint32_t crc32_stm32(uint32_t init_crc, uint32_t *buf, int len)
{
uint32_t v;
uint32_t crc;
crc = ~init_crc;
while(len >= 4) {
v = htonl(*buf++);
crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v ) )];
crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 8) )];
crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 16) )];
crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 24) )];
len -= 4;
}
if(len) {
switch(len) {
case 1: v = 0xFF000000 & htonl(*buf++); break;
case 2: v = 0xFFFF0000 & htonl(*buf++); break;
case 3: v = 0xFFFFFF00 & htonl(*buf++); break;
}
crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v ) )];
crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 8) )];
crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 16) )];
crc = ( crc << 8 ) ^ crc32_table[0xFF & ( (crc >> 24) ^ (v >> 24) )];
}
return ~crc;
}
Тут я применил htonl чтоб байты в слове были в определённом порядке независимо от LE/BE: сначала в LSFR заправляется байт, который лежит в памяти по смещению 3 (как на рисунке). Ещё, остаток сообщения, не влезающий в 4х-байтовое слово дополняется нулями справа и CRC досчитвается дальше.
Можно писать конструкцию типа такой (вычислять CRC кусками):
printf("crc32_byte = %08X\n", crc32_byte(crc32_byte(0, "12345", 5), "6789", 4));
printf("crc32_stm32 = %08X\n", crc32_stm32(crc32_stm32(0, "1234", 4), "56789", 5));
Вот то, что получается:
crc32_byte(«123456789») = CBF43926
crc32_stm32(«123456789») = 500E6FA8
crc32_byte(«12345678») = 9AE0DAAF
crc32_stm32(«12345678») = 0103AB06
Теперь код для STM32:
Сперва чисто хардварная CRC (ей соответствует софтовая crc32_stm32):
uint32_t crc32_native(char *bfr, int len, int clear) {
int l;
uint32_t *p, x, crc;
l = len / 4;
p = (uint32_t*)bfr;
x = p[l];
if(clear) CRC_ResetDR();
while(l--) {
crc = CRC_CalcCRC(*p++);
}
switch(len & 3) {
case 1: crc = CRC_CalcCRC(x & 0x000000FF); break;
case 2: crc = CRC_CalcCRC(x & 0x0000FFFF); break;
case 3: crc = CRC_CalcCRC(x & 0x00FFFFFF); break;
}
return 0xFFFFFFFF ^ crc;
}
Далее сделаем «как на софте», или «как в ethernet». Благо, в ARM есть инструкция для реверсирования бит.
Но это не всё. Ведь если пораскинуть мозгами, то можно добавить вкусную плюшку — сосчитать-таки побайтовую CRC на аппаратном блоке.
Нужно просто вычислить полином-остаток от оставшегося куска и добавить его к уже посчитанной пословно CRC. Остаток — это по сути та же CRC, но с начальным состоянием LSFR=0 (см инициализацию таблицы остатков). Но вот беда — CRC_ResetDR может установить регистр CRC только в 0xFFFFFFFF. Слава яйцам, что нам надо именно 0, а не что-то ещё. Одно из свойств CRC состоит в том, что если к сообщению приписать его CRC, то CRC нового сообщения будет равна 0. Другими словами, если мы подадим в регистр CRC то, что мы из него считали, то в результате получится 0. Теперь осталось заправить в регистр кусочек из одного, двух или трёх оставшихся байт — et voila, забираем наш полином-остаток и добавляем его к CRC.
Ниже код:
uint32_t reverse_32(uint32_t data)
{
asm("rbit r0,r0");
return data;
};
uint32_t crc32_ether(char *buf, int len, int clear)
{
uint32_t *p = (uint32_t*) buf;
uint32_t crc, crc_reg;
if(clear) CRC_ResetDR();
if(len >=4) {
while(len >= 4) {
crc_reg = CRC_CalcCRC(reverse_32(*p++));
len -= 4;
}
} else {
crc = 0xFFFFFFFF;
crc_reg = CRC_CalcCRC(0xEBABAB);
}
crc = reverse_32(crc_reg);
if(len) {
CRC_CalcCRC(crc_reg);
switch(len) {
case 1:
crc_reg = CRC_CalcCRC(reverse_32((*p & 0xFF) ^ crc) >> 24);
crc = ( crc >> 8 ) ^ reverse_32(crc_reg);
break;
case 2:
crc_reg = CRC_CalcCRC(reverse_32((*p & 0xFFFF) ^ crc) >> 16);
crc = ( crc >> 16 ) ^ reverse_32(crc_reg);
break;
case 3:
crc_reg = CRC_CalcCRC(reverse_32((*p & 0xFFFFFF) ^ crc) >> 8);
crc = ( crc >> 24 ) ^ reverse_32(crc_reg);
break;
}
}
return ~crc;
}
Вот, как-то так, думаю, многим пригодится.
P.S. бонус:
uint8_t pkt_alt[] = {
0x00, 0x10, 0xA4, 0x7B, 0xEA, 0x80, 0x00, 0x12,
0x34, 0x56, 0x78, 0x90, 0x08, 0x00, 0x45, 0x00,
0x00, 0x2E, 0xB3, 0xFE, 0x00, 0x00, 0x80, 0x11,
0x05, 0x40, 0xC0, 0xA8, 0x00, 0x2C, 0xC0, 0xA8,
0x00, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, 0x1A,
0x2D, 0xE8, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
0x0E, 0x0F, 0x10, 0x11, 0xB3, 0x31, 0x88, 0x1B
};
uint8_t pkt_alt_d[] = {
0x00, 0xC0, 0x02, 0x37, 0x57, 0x28, 0x00, 0x10,
0xA4, 0x7B, 0xEA, 0x80, 0x08, 0x00, 0x45, 0x00,
0x00, 0x3C, 0x02, 0x24, 0x00, 0x00, 0x80, 0x01,
0xB7, 0x47, 0xC0, 0xA8, 0x00, 0x04, 0xC0, 0xA8,
0x00, 0x01, 0x08, 0x00, 0x42, 0x5C, 0x02, 0x00,
0x09, 0x00, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
0x68, 0x69, 0x62, 0x31, 0xC5, 0x4E
};
uint8_t pkt_xil[] = {
0x00, 0x0A, 0xE6, 0xF0, 0x05, 0xA3, 0x00, 0x12,
0x34, 0x56, 0x78, 0x90, 0x08, 0x00, 0x45, 0x00,
0x00, 0x30, 0xB3, 0xFE, 0x00, 0x00, 0x80, 0x11,
0x72, 0xBA, 0x0A, 0x00, 0x00, 0x03, 0x0A, 0x00,
0x00, 0x02, 0x04, 0x00, 0x04, 0x00, 0x00, 0x1C,
0x89, 0x4D, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
0x7A, 0xD5, 0x6B, 0xB3
};
это, если кому надо, реальные ethernet-фреймы с CRC32 FCS
- +13
- 09 октября 2013, 00:43
- scaldov
да конечно, я знаю правила. просто про постоянно забываю.
а вот вам, думаю, надо освежить свою память, т.к., например, оскорблять участников форума — запрещено.
а вот вам, думаю, надо освежить свою память, т.к., например, оскорблять участников форума — запрещено.
а вот вам, думаю, надо освежить свою память, т.к., например, оскорблять участников форума — запрещеноБанить таких надо вообще с сайта, если участников оскорбляют.
- Papandopala
- 13 октября 2013, 12:08
- ↑
- ↓
А кроме этернета, контрольная сумма с таким полиномом еще в каком-то протоколе используется?
- count_enable
- 09 октября 2013, 11:29
- ↓
У вас в crc32_ether ошибка.
Если длинна буфера будет кратна 4 то тогда у вас будет возвращатся 0.
Так-как так-как переменная crc в while не изменяется.
И еще зачем вам дополнительная переменная p(uint32_t *p = (uint32_t*) buf;)
Если длинна буфера будет кратна 4 то тогда у вас будет возвращатся 0.
Так-как так-как переменная crc в while не изменяется.
И еще зачем вам дополнительная переменная p(uint32_t *p = (uint32_t*) buf;)
верно, забыл перепастить из финального исходника.
надо поднять crc = reverse_32(crc_reg); на скобку выше (поправил в статье).
так buf это char*, а мы тащим словами. поэтому.
кстати, компиляторы идеально утилизируют регистры. так что, с лишними переменными щас можно не париться. для этого используются RTL-паттерны.
надо поднять crc = reverse_32(crc_reg); на скобку выше (поправил в статье).
так buf это char*, а мы тащим словами. поэтому.
кстати, компиляторы идеально утилизируют регистры. так что, с лишними переменными щас можно не париться. для этого используются RTL-паттерны.
Да понятно что компилятор их соптимизирует.
Кстати вопрос
if(clear) CRC_ResetDR();
Это кокраз и есть запись в CRC_DR 0xFFFFFFFF?
Да и спасибо за статью.
Кстати вопрос
if(clear) CRC_ResetDR();
Это кокраз и есть запись в CRC_DR 0xFFFFFFFF?
Да и спасибо за статью.
да, это из STM32f1xx-STL, эта ф-я записывает бит0 в регистре CRC->CR, и блок CRC инициализирует CRC->DATA значением 0xFFFFFFFF
+1
До хардварного расчета пока еще ручки не добирались, все как-то софтово считается, ибо копипастится их разных других мест, где раньше аппаратного CRC никак небыло. Прозреваю, что потратил бы какое-то количество времени, чтобы вкурить этот момент при необходимости (а она, как водится, рано или поздно появится), так что спасибо за аттеншн.
До хардварного расчета пока еще ручки не добирались, все как-то софтово считается, ибо копипастится их разных других мест, где раньше аппаратного CRC никак небыло. Прозреваю, что потратил бы какое-то количество времени, чтобы вкурить этот момент при необходимости (а она, как водится, рано или поздно появится), так что спасибо за аттеншн.
- darksimpson
- 12 октября 2013, 22:58
- ↓
public class CRC32
{
static UInt32[] Crc32Table = new UInt32[] {
0x00000000,0x04C11DB7,0x09823B6E,0x0D4326D9,
0x130476DC,0x17C56B6B,0x1A864DB2,0x1E475005,
0x2608EDB8,0x22C9F00F,0x2F8AD6D6,0x2B4BCB61,
0x350C9B64,0x31CD86D3,0x3C8EA00A,0x384FBDBD
};
static UInt32 DR;
public static void Reset()
{
DR = 0xFFFFFFFF;
}
public static void Write(UInt32 data)
{
DR = DR ^ data;
DR = (DR << 4) ^ Crc32Table[DR >> 28];
DR = (DR << 4) ^ Crc32Table[DR >> 28];
DR = (DR << 4) ^ Crc32Table[DR >> 28];
DR = (DR << 4) ^ Crc32Table[DR >> 28];
DR = (DR << 4) ^ Crc32Table[DR >> 28];
DR = (DR << 4) ^ Crc32Table[DR >> 28];
DR = (DR << 4) ^ Crc32Table[DR >> 28];
DR = (DR << 4) ^ Crc32Table[DR >> 28];
}
public static UInt32 Read()
{
return DR;
}
}
Проблемы несовпадения CRC при использовании аппаратного CRC-юнита описаны на сайте ST и на electronix
Писал в своё время
Писал в своё время
inline unsigned int revbit(unsigned int Data)
{
asm("rbit r0,r0"); //Аргумент передаётся через r0. Над ним и выполнить команду rbit
return Data;
}
unsigned int CalcCRC32(unsigned char *Buf, unsigned int Len)
{
unsigned int i;
unsigned int Temp;
RCC->AHBENR |= RCC_AHBENR_CRCEN; //Разрешить тактирование CRC-юнита
CRC->CR = 1;
asm("nop"); //Аппаратная готовность за 4 такта, жду...
asm("nop");
asm("nop");
// Аппаратный CRC-расчёт работает с 32-битными словами. Т.е. сразу по 4 байта из входной последовательности
i = Len >> 2;
while(i--)
{
Temp = *((unsigned int*)Buf);
Temp = revbit(Temp); //Переставить биты во входных данных
CRC->DR = Temp;
Buf += 4;
}
Temp = CRC->DR;
Temp = revbit(Temp); //Переставить биты в выходных данных
// Обработать оставшиеся байты (классическим не аппаратным методом), если их число не было кратно 4
i = Len & 3;
while(i--)
{
Temp ^= (unsigned int)*Buf++;
for(int j=0; j<8; j++)
if (Temp & 1)
Temp = (Temp >> 1) ^ 0xEDB88320;
else
Temp >>= 1;
}
Temp ^= 0xFFFFFFFFul;
return Temp;
}
Спасибо за статью. Не один день убил разбираясь почему crc не совпадают. Чаниковский вопрос — keil Не принимает
asm(«rbit r0,r0»); И в гугле ответ как-то не нашел-(
asm(«rbit r0,r0»); И в гугле ответ как-то не нашел-(
- pistoletov
- 20 октября 2015, 19:34
- ↓
вы его даже не запускали, даю 146%.
а я тестировал конешно, но на последовательностях >=4, он уж 2 года работает во многих местах.
Вобщем, пропатчил, пользуйтесь наздоровье :)
проверка
на длине 3 CRC(«012»)=D5A06AB0
на длине 2 CRC(«01»)=CF412436
на длине 1 CRC(«0»)=F4DBDF21
а я тестировал конешно, но на последовательностях >=4, он уж 2 года работает во многих местах.
Вобщем, пропатчил, пользуйтесь наздоровье :)
проверка
на длине 3 CRC(«012»)=D5A06AB0
на длине 2 CRC(«01»)=CF412436
на длине 1 CRC(«0»)=F4DBDF21
Запускал, и даже переделал с претензией на большую скорость
uint32_t HW_CRC32(const uint8_t* pData, size_t count, uint32_t init) {
uint32_t crc;
uint32_t *p32 = (uint32_t*) pData;
size_t count32 = count >> 2;
if (0xFFFFFFFF == init)
CRC->CR |= CRC_CR_RESET;
while (count32--) {
CRC->DR = __RBIT(*p32++);
}
crc = __RBIT(CRC->DR);
count = count % 4;
if (count) {
CRC->DR = __RBIT(crc);
switch (count) {
case 1:
CRC->DR = __RBIT((*p32 & 0x000000FF) ^ crc) >> 24;
crc = (crc >> 8) ^ __RBIT(CRC->DR);
break;
case 2:
CRC->DR = (__RBIT((*p32 & 0x0000FFFF) ^ crc) >> 16);
crc = (crc >> 16) ^ __RBIT(CRC->DR);
break;
case 3:
CRC->DR = __RBIT((*p32 & 0x00FFFFFF) ^ crc) >> 8;
crc = (crc >> 24) ^ __RBIT(CRC->DR);
break;
}
}
return ~crc;
}
Сброс делается при init = 0xFFFFFFFF
для совместимости с софтовой версией
Во-первых, ясное дело, что компилятор оперирует регистрами и прочими низкоуровневыми сущностями.
Во-вторых, я избавился от вызова функции
В третьих, imho,
В четвёртых, счётчик цикла у меня изменяется простым декрементом.
В пятых, цикл у меня короче, а значит и быстрее.
Вывод, на компилятор надейся, а сам не плошай.
Во-вторых, я избавился от вызова функции
reverse_32
применив __RBIT
. В третьих, imho,
CRC_CalcCRC
делает лишние телодвижения, которые компилятор не будет игнорировать, а эта функция у Вас в цикле вызывается. В четвёртых, счётчик цикла у меня изменяется простым декрементом.
В пятых, цикл у меня короче, а значит и быстрее.
Вывод, на компилятор надейся, а сам не плошай.
цикл короче != быстрее
декремент или нет — всё равно. компилятор это норм оптимизирует. откройте получившийся код.
единственное что оптимизируется — это вызов
в вашем случае она инлайнится, в моём — нет. но по сравнению со скоростью вычисления в блоке CRC это неактуально
декремент или нет — всё равно. компилятор это норм оптимизирует. откройте получившийся код.
единственное что оптимизируется — это вызов
<br />
uint32_t CRC_CalcCRC(uint32_t Data)
{
CRC->DR = Data;
return (CRC->DR);
}
в вашем случае она инлайнится, в моём — нет. но по сравнению со скоростью вычисления в блоке CRC это неактуально
цикл короче != быстрееУ Вас что-то с мировоззрением. Разжёвываю, чем меньше команд выполняется в цикле, тем быстрее он заканчивается, и это постулат, который и ежу понятен.
Я вот не поленился и посчитал при дефолтных настройках компилятора у Вас цикл в 17 команд(вызовы учёл)
41 uint32_t CRC_CalcCRC(uint32_t Data)
42 {
\ _Z11CRC_CalcCRCj:
\ 00000000 0x0001 MOVS R1,R0
43 CRC->DR = Data;
\ 00000002 0x.... LDR.N R0,??DataTable3_1 ;; 0x40023000
\ 00000004 0x6001 STR R1,[R0, #+0]
44
45 return (CRC->DR);
\ 00000006 0x.... LDR.N R0,??DataTable3_1 ;; 0x40023000
\ 00000008 0x6800 LDR R0,[R0, #+0]
\ 0000000A 0x4770 BX LR ;; return
46 }
...53 uint32_t reverse_32(uint32_t data)
54 {
55 asm("rbit r0,r0");
\ _Z10reverse_32j:
\ 00000000 0xFA90 0xF0A0 rbit r0,r0
56 return data;
\ 00000004 0x4770 BX LR ;; return
57 };
65 while(len >= 4) {
\ ??crc32_ether_2:
\ 0000001A 0xF1B9 0x0F04 CMP R9,#+4
\ 0000001E 0xDB12 BLT.N ??crc32_ether_3
66 crc_reg = CRC_CalcCRC(reverse_32(*p++));
\ 00000020 0xF8D8 0x0000 LDR R0,[R8, #+0]
\ 00000024 0x.... 0x.... BL _Z10reverse_32j
\ 00000028 0xF118 0x0804 ADDS R8,R8,#+4
\ 0000002C 0x.... 0x.... BL _Z11CRC_CalcCRCj
\ 00000030 0x0007 MOVS R7,R0
67 len -= 4;
\ 00000032 0xF1B9 0x0904 SUBS R9,R9,#+4
\ 00000036 0xE7F0 B.N ??crc32_ether_2
68 }
, а у меня 10 15 while (count32--) {
\ ??HW_CRC32_0:
\ 0000001A 0x0028 MOVS R0,R5
\ 0000001C 0x1E45 SUBS R5,R0,#+1
\ 0000001E 0x2800 CMP R0,#+0
\ 00000020 0xD006 BEQ.N ??HW_CRC32_1
16 CRC->DR = __RBIT(*p32++);
\ 00000022 0x6820 LDR R0,[R4, #+0]
\ 00000024 0xFA90 0xF0A0 RBIT R0,R0
\ 00000028 0x1D24 ADDS R4,R4,#+4
\ 0000002A 0x.... LDR.N R7,??DataTable3_1 ;; 0x40023000
\ 0000002C 0x6038 STR R0,[R7, #+0]
\ 0000002E 0xE7F4 B.N ??HW_CRC32_0
17 }
Предлагаю закончить холивар, всё-равно никому он не интересен.
Весьма приглянулся этот код, что позволил себе немного шлифануть его еще:
Чтобы избежать проблем с выравниванием, входной параметр задан, как указатель на 32-битное целое. Однако, при вызове можно передавать хоть строку:
uint32_t crc32_zlib(const uint32_t *data, size_t cnt) {
uint32_t i;
CRC->CR = CRC_CR_RESET;
for (i = 0; i < (cnt / 4); i++) CRC->DR = __RBIT(data[i]);
uint32_t result = __RBIT(CRC->DR);
cnt = (cnt % 4) * 8;
if (cnt) {
CRC->DR = CRC->DR;
CRC->DR = __RBIT((data[i] & (0xFFFFFFFF >> (32 - cnt))) ^ result) >> (32 - cnt);
result = (result >> cnt) ^ __RBIT(CRC->DR);
}
return ~result;
}
Чтобы избежать проблем с выравниванием, входной параметр задан, как указатель на 32-битное целое. Однако, при вызове можно передавать хоть строку:
crc32_zlib("just a test", 11)
Очень полезная статья. Спасибо большое.
Но для того, чтобы заставить работать под Atollic True Studio, код пришлось поправить(возможно, из за отключенной оптимизации).
Если конкретно, то ф-ция reverse_32(uint32_t data) в том виде, как в статье, у меня скомпилилась так:
Содержимое перевернутого r0 никуда не сохранялось, а потом затиралось содержимым r3 и поэтому переменная data выходила нетронутой :)
Если использовать cmsis, то переписать ф-цию можно так:
И скомпилированный код стал выглядеть так:
Оптимизация, повторюсь, отключена :), но даже так видно, что перевернутое содержимое регистра r3 сохраняется и в стек и в r0 и, конечно же, ф-ция расчета crc32_ether начинает работать правильно.
Вывод: прямое использование конструкций asm volatile(… чревато и требует контроля после каждой компиляции. Поэтому, по возможности, желательно использовать макросы обертки, вроде __RBIT(uint32_t).
Еще раз, статья золотая, спасибо.
Но для того, чтобы заставить работать под Atollic True Studio, код пришлось поправить(возможно, из за отключенной оптимизации).
Если конкретно, то ф-ция reverse_32(uint32_t data) в том виде, как в статье, у меня скомпилилась так:
.........
08001920: mov r0, r3
08001922: adds r7, #32
08001924: mov sp, r7
08001926: pop {r7, pc}
155 {
reverse_32:
08001928: push {r7}
0800192a: sub sp, #12
0800192c: add r7, sp, #0
0800192e: str r0, [r7, #4]
156 asm volatile("rbit r0,r0""\n\t");
08001930: rbit r0, r0
157 return data;
08001934: ldr r3, [r7, #4]
159 };
08001936: mov r0, r3
08001938: adds r7, #12
0800193a: mov sp, r7
0800193c: pop {r7}
0800193e: bx lr
...............
Содержимое перевернутого r0 никуда не сохранялось, а потом затиралось содержимым r3 и поэтому переменная data выходила нетронутой :)
Если использовать cmsis, то переписать ф-цию можно так:
uint32_t reverse_32(uint32_t data)
{
return(__RBIT(data));
};
И скомпилированный код стал выглядеть так:
mov r0, r3
08001922: adds r7, #32
08001924: mov sp, r7
08001926: pop {r7, pc}
155 {
reverse_32:
08001928: push {r7}
0800192a: sub sp, #20
0800192c: add r7, sp, #0
0800192e: str r0, [r7, #4]
08001930: ldr r3, [r7, #4]
08001932: str r3, [r7, #12]
531 __ASM volatile ("rbit %0, %1" : "=r" (result) : "r" (value) );
08001934: ldr r3, [r7, #12]
08001936: rbit r3, r3
0800193a: str r3, [r7, #8]
544 return(result);
0800193c: ldr r3, [r7, #8]
158 return(__RBIT(data));
0800193e: nop
159 };
08001940: mov r0, r3
08001942: adds r7, #20
08001944: mov sp, r7
08001946: pop {r7}
08001948: bx lr
0800194a: movs r0, r0
......
Оптимизация, повторюсь, отключена :), но даже так видно, что перевернутое содержимое регистра r3 сохраняется и в стек и в r0 и, конечно же, ф-ция расчета crc32_ether начинает работать правильно.
Вывод: прямое использование конструкций asm volatile(… чревато и требует контроля после каждой компиляции. Поэтому, по возможности, желательно использовать макросы обертки, вроде __RBIT(uint32_t).
Еще раз, статья золотая, спасибо.
Комментарии (46)
RSS свернуть / развернуть