При реализации работы с любой периферией часто требуется обеспечить некоторую задержку между выполняемыми операциями. Для реализации задержки можно использовать следующие ресурсы:
Системный таймер используется системами реального времени, и использовать его для реализации задержки в библиотеке уже нельзя. Периферийные таймеры обычно используются для захвата внешних фронтов и генерации ШИМ в рабочих проектах. Отбирать ради задержки целый таймер не разумно, их и так зачастую не хватает. Блок DWT есть только в Cortex-M3 (1986ВЕ9х) и Cortex-M4 (1986ВЕ8Т, "Электросила"). Поэтому самым доступным решением является реализация задержки программным путем.
При реализации задержки удобно делать проверку на передачу нулевого количества циклов. Если в вызываемом коде количество циклов задержки высчитывается по какой-то формуле, то может случиться, что результат расчета будет нулевой, и на вход функции придет 0, что приведет к зацикливанию функции на очень долгий период - пересчитать придется 0хFFFFFFFF циклов.
Ниже представлены два варианта реализации задержки. Volatile использован для того, чтобы компилятор не решил, что декремент локальной переменной не имеет смысла и стоит от него избавиться.
void MDR_DelayA(volatile uint32_t Ticks) { if (Ticks) while(--Ticks); } // =============== Compiler V6 -O0 ============ // =============== Compiler V5 -O0 ================ 39: void MDR_DelayA(volatile uint32_t Ticks) 39: void MDR_DelayA(volatile uint32_t Ticks) 0x20000A6A B003 ADD sp,sp,#0x0C 40: { 0x20000A6C 4770 BX lr 0x2000069E B501 PUSH {r0,lr} 0x20000A6E 0000 MOVS r0,r0 41: if (Ticks) 40: { 0x200006A0 9800 LDR r0,[sp,#0x00] 0x20000A70 B082 SUB sp,sp,#0x08 0x200006A2 B128 CBZ r0,0x200006B0 0x20000A72 4601 MOV r1,r0 42: while(--Ticks); 0x20000A74 9001 STR r0,[sp,#0x04] 0x200006A4 BF00 NOP 41: if (Ticks) 0x200006A6 9800 LDR r0,[sp,#0x00] 0x20000A76 9801 LDR r0,[sp,#0x04] 0x200006A8 1E40 SUBS r0,r0,#1 0x20000A78 2800 CMP r0,#0x00 0x200006AA 9000 STR r0,[sp,#0x00] 0x20000A7A 9100 STR r1,[sp,#0x00] 0x200006AC 2800 CMP r0,#0x00 0x20000A7C D009 BEQ 0x20000A92 0x200006AE D1FA BNE 0x200006A6 0x20000A7E E7FF B 0x20000A80 43: } 42: while(--Ticks); 0x200006B0 BD08 POP {r0,pc} 0x20000A80 E7FF B 0x20000A82 0x20000A82 9801 LDR r0,[sp,#0x04] 0x20000A84 3801 SUBS r0,r0,#0x01 0x20000A86 9001 STR r0,[sp,#0x04] 0x20000A88 2800 CMP r0,#0x00 0x20000A8A D001 BEQ 0x20000A90 0x20000A8C E7FF B 0x20000A8E 0x20000A8E E7F8 B 0x20000A82 0x20000A90 E7FF B 0x20000A92 43: } 0x20000A92 B002 ADD sp,sp,#0x08 0x20000A94 4770 BX lr // =============== Compiler V6 -Oz =========== // =============== Compiler V5 -O2 ================ 39: void MDR_DelayA(volatile uint32_t Ticks) 39: void MDR_DelayA(volatile uint32_t Ticks) 0x200005E8 B001 ADD sp,sp,#0x04 40: { 0x200005EA 4770 BX lr 0x2000061C B501 PUSH {r0,lr} 40: { 41: if (Ticks) 0x200005EC B081 SUB sp,sp,#0x04 0x2000061E 9800 LDR r0,[sp,#0x00] 0x200005EE 9000 STR r0,[sp,#0x00] 0x20000620 2800 CMP r0,#0x00 41: if (Ticks) 0x20000622 D002 BEQ 0x2000062A 0x200005F0 9800 LDR r0,[sp,#0x00] 0x20000624 1E40 SUBS r0,r0,#1 0x200005F2 B118 CBZ r0,0x200005FC 42: while(--Ticks); 42: while(--Ticks); 0x20000626 9000 STR r0,[sp,#0x00] 0x200005F4 9800 LDR r0,[sp,#0x00] 0x20000628 E7FA B 0x20000620 0x200005F6 3801 SUBS r0,r0,#0x01 43: } 0x200005F8 9000 STR r0,[sp,#0x00] 0x2000062A BD08 POP {r0,pc} 0x200005FA D1FB BNE 0x200005F4 43: } 0x200005FC B001 ADD sp,sp,#0x04 0x200005FE 4770 BX lr
Второй распространенный вариант реализации цикла, делает тоже самое чуть по-другому.
static void MDR_DelayB(uint32_t Ticks) { for (; Ticks > 0; Ticks--); } // =============== Compiler V6 -O0 =========== // =============== Compiler V5 -O0 ================ 104: void MDR_DelayB(uint32_t Ticks) 45: void MDR_DelayB(volatile uint32_t Ticks) 0x20000A98 2800 CMP r0,#0x00 46: { 0x20000A9A D002 BEQ 0x20000AA2 0x200006B2 B501 PUSH {r0,lr} 0x20000A9C BF00 NOP 47: for (; Ticks > 0; Ticks--); 0x20000A9E 1E40 SUBS r0,r0,#1 0x200006B4 E002 B 0x200006BC 0x20000AA0 D1FD BNE 0x20000A9E 0x200006B6 9800 LDR r0,[sp,#0x00] 0x20000AA2 4770 BX lr 0x200006B8 1E40 SUBS r0,r0,#1 46: { 0x200006BA 9000 STR r0,[sp,#0x00] 0x20000AA4 B082 SUB sp,sp,#0x08 0x200006BC 9800 LDR r0,[sp,#0x00] 0x20000AA6 4601 MOV r1,r0 0x200006BE 2800 CMP r0,#0x00 0x20000AA8 9001 STR r0,[sp,#0x04] 0x200006C0 D1F9 BNE 0x200006B6 47: for (; Ticks > 0; Ticks--); 48: } 0x20000AAA 9100 STR r1,[sp,#0x00] 0x200006C2 BD08 POP {r0,pc} 0x20000AAC E7FF B 0x20000AAE 0x20000AAE 9801 LDR r0,[sp,#0x04] 0x20000AB0 2800 CMP r0,#0x00 0x20000AB2 D005 BEQ 0x20000AC0 0x20000AB4 E7FF B 0x20000AB6 0x20000AB6 E7FF B 0x20000AB8 0x20000AB8 9801 LDR r0,[sp,#0x04] 0x20000ABA 3801 SUBS r0,r0,#0x01 0x20000ABC 9001 STR r0,[sp,#0x04] 0x20000ABE E7F6 B 0x20000AAE 48: } 0x20000AC0 B002 ADD sp,sp,#0x08 0x20000AC2 4770 BX lr // =============== Compiler V6 -Oz =========== // =============== Compiler V5 -O2 ================ 113: void MDR_DelayB(volatile uint32_t Ticks) 45: void MDR_DelayB(volatile uint32_t Ticks) 0x20000600 2800 CMP r0,#0x00 46: { 0x20000602 D002 BEQ 0x2000060A 0x2000062C B501 PUSH {r0,lr} 0x20000604 BF00 NOP 47: for (; Ticks > 0; Ticks--); 0x20000606 1E40 SUBS r0,r0,#1 0x2000062E 9800 LDR r0,[sp,#0x00] 0x20000608 D1FD BNE 0x20000606 0x20000630 E001 B 0x20000636 0x2000060A 4770 BX lr 0x20000632 1E40 SUBS r0,r0,#1 46: { 0x20000634 9000 STR r0,[sp,#0x00] 0x2000060C B081 SUB sp,sp,#0x04 0x20000636 2800 CMP r0,#0x00 0x2000060E E001 B 0x20000614 0x20000638 D1FB BNE 0x20000632 47: for (; Ticks > 0; Ticks--); 48: } 0x20000610 9800 LDR r0,[sp,#0x00] 0x2000063A BD08 POP {r0,pc} 0x20000612 3801 SUBS r0,r0,#0x01 0x20000614 9000 STR r0,[sp,#0x00] 0x20000616 9800 LDR r0,[sp,#0x00] 0x20000618 2800 CMP r0,#0x00 0x2000061A D1F9 BNE 0x20000610 48: } 0x2000061C B001 ADD sp,sp,#0x04 0x2000061E 4770 BX lr
(Сборка происходила под 1986ВЕ8Т, т.е. Cortex-M4. С вытаскиванием кода мог и накосячить, но общий вид понятен.)
Как видно по ассемблерному коду, длительность исполнения функций сильно зависит от опций компилятора и от самого компилятора. Складывается впечатление, что компилятор Compiler V6, который LVVM, мог бы быть оптимальней. Ведь код у Compiler V5 получается оптимальнее даже без оптимизации.
Полагаю, что есть различные возможности, чтобы дать понять компилятору Си, что внутренние переменные не стоит читать и писать из памяти, что всего лишь необходимо постоять немного в цикле, для чего достаточно использовать лишь регистры. Но показалось разумным написать функцию напрямую на ассемблере, чтобы не устраивать объяснения с компилятором. Есть надежда, что ассемблерный код компилятор Си не возьмется оптимизировать, и все-таки получится получить ожидаемую задержку в любом варианте сборки.
За основу ассемблерного варианта взят код от Compiler V5, потому что он короче и понятнее. Убрано обращение к памяти с загрузкой значения в регистр (LDR) и выгрузкой значения в память (STR). Учтено, что входной параметр функции, согласно соглашению языка Си, передан через регистр R0. Т.е. в R0 в функцию пришло количество циклов, которые необходимо исполнить. Исполнение крутится на двух инструкциях в теле __NextLoop в количестве Ticks раз, декрементируя регистр R0, пока он не станет нулем. Код такой:
__asm void MDR_DelayASM_RAM(uint32_t Ticks) { CMP r0,#0x00 BEQ __Exit NOP __NextLoop SUBS r0,r0,#1 BNE __NextLoop __Exit BX lr }
Важно, чтобы название меток начиналось с начала строки, иначе компилятор не понимает, что это метка! Данный код собирается без проблем Compiler V5. Но Compiler V6 собирать данный код отказался. Собрать код удалось в таком варианте:
__attribute__((naked)) void MDR_DelayASM_RAM(uint32_t Ticks) { __asm("CMP r0,#0x00"); __asm("BEQ __Exit"); __asm("NOP"); __asm("__NextLoop: SUBS r0,r0,#1"); __asm("BNE __NextLoop"); __asm("__Exit: BX lr"); }
Код аналогичен версии для Compiler V5, только метки выделены двоеточием и каждая строка включена в кавычки. Кроме этого Compiler V6 не понимает атрибут __asm, для него необходимо указывать __attribute__((naked)), чтобы компилятор не вставлял Push и Pop для данной функции. Про атрибут написано тут __attribute__((naked)) function attribute
Данный код для Compiler V6 можно записать еще так:
__attribute__((naked)) void MDR_DelayASM(uint32_t Ticks) { __asm( " CMP r0,#0x00 ;\n" " BEQ 1f ;\n" " NOP ;\n" "0: ;\n" " SUBS r0,r0,#1 ;\n" " BNE 0b ;\n" "1: ;\n" " BX lr ;\n" ); }
Здесь выявились свои особенности. Метки должны находиться на отдельной строке и они не могут быть заданы словами, только цифрами. Для прыжка на метку необходимо указывать, в какую сторону будет переход, нашел здесь. Например:
Есть прочие способы писать ассемблерный код, но разбираться в них пока не возникло необходимости. Оставлю ссылки, которые могут быть полезны при задании input/output операндов - ARM Description, Forum example.
Блок Data Watchpoint and Trace Unit входит в состав отладочного модуля ядер Cortex-M3 и Cortex-M4. В данном блоке есть счетчик, который считает такты ядра и который используется отладчиком при трассировке. Перед работой данный блок необходимо включить, и после этого можно работать со счетчиком данного блока. Код:
#define regDWT_CYCCNT *(volatile unsigned long *)0xE0001004 #define regDWT_CONTROL *(volatile unsigned long *)0xE0001000 #define regSCB_DEMCR *(volatile unsigned long *)0xE000EDFC #define CoreDebug_DEMCR_TRCENA_Msk 0x01000000UL #define DWT_CTRL_CYCCNTENA_Msk 0x1UL void MDR_DelayDWT_Init(void) { //разрешаем использовать счётчик regSCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; //обнуляем значение счётного регистра regDWT_CYCCNT = 0; //запускаем счётчик regDWT_CONTROL |= DWT_CTRL_CYCCNTENA_Msk; } void MDR_DelayDWT(uint32_t clockCountCPU) { uint32_t t0 = regDWT_CYCCNT; while ((regDWT_CYCCNT - t0) < (clockCountCPU)); }
Для проверки работы функций задержки был реализован проект, который доступен на GitHub в рамках разрабатываемой нами библиотеки.
В проекте переключается пин светодиода микроконтроллера с задержкой в 3мс. Каждая функция реализует один период, т.е. формируется длительность нуля и единицы.
while (1) { // Asm Delay MDRB_LED_Set(MDRB_LED_1, 0); MDR_DelayASM(asmDelay); MDRB_LED_Set(MDRB_LED_1, 1); MDR_DelayASM(asmDelay); // Asm Delay from RAM MDRB_LED_Set(MDRB_LED_1, 0); MDR_DelayASM_RAM(asmDelayRAM); MDRB_LED_Set(MDRB_LED_1, 1); MDR_DelayASM_RAM(asmDelayRAM); // C Delay MDRB_LED_Set(MDRB_LED_1, 0); MDR_DelayC(cDelay); MDRB_LED_Set(MDRB_LED_1, 1); MDR_DelayC(cDelay); #if DWT_AVAILABLE // WDT Delay MDRB_LED_Set(MDRB_LED_1, 0); MDR_DelayDWT(dwtDelay); MDRB_LED_Set(MDRB_LED_1, 1); MDR_DelayDWT(dwtDelay); #endif // New Cycle MDRB_LED_Set(MDRB_LED_1, 0); MDR_DelayC(cDelay); MDR_DelayC(cDelay); MDRB_LED_Set(MDRB_LED_1, 1); MDR_DelayC(cDelay); MDR_DelayC(cDelay); }
Функции MDR_DelayASM и MDR_DelayASM_RAM - это уже упомянутые функции на ассемблере, но MDR_DelayASM_RAM исполняется из ОЗУ. MDR_DelayC - это функция с циклом While, также упомянутая выше. MDR_DelayDWT - функция с использованием DWT.
На вход этих функций подается количество циклов, которые функция должна исполнить, чтобы выдержать необходимую задержку. Количество циклов считается по данной формуле:
#define LOOP_CYCLES_ASM 3 #define LOOP_CYCLES_ASM_RAM 9 #define LOOP_CYCLES_C 8 #define LOOP_CYCLES_DWT 1 #define DELAY_US 3000 #define US_TO_LOOPS(uSec, CPU_FregHz, LoopCycles) ((uint32_t)((double)(uSec) * (CPU_FregHz) / LoopCycles / 1000000 )) uint32_t asmDelay = US_TO_LOOPS(DELAY_US, freqCPU_Hz, LOOP_CYCLES_ASM); uint32_t asmDelayRAM = US_TO_LOOPS(DELAY_US, freqCPU_Hz, LOOP_CYCLES_ASM_RAM); uint32_t cDelay = US_TO_LOOPS(DELAY_US, freqCPU_Hz, LOOP_CYCLES_C); uint32_t dwtDelay = US_TO_LOOPS(DELAY_US, freqCPU_Hz, LOOP_CYCLES_DWT);
Параметры LOOP_CYCLES_ASM и прочие задают, сколько тактов CPU занимает исполнение одного цикла задержки. Действительно, если задержку в секундах умножить на частоту CPU, что есть количество тактов в секунду, то это будет количество тактов за секунду. Если задержка задается в микросекундах, то необходимо добавить деление на 106. Но каждый цикл занимает несколько тактов, поэтому, поделив все еще на и количество тактов в цикле (LOOP_CYCLES_ASM), получим необходимое количество циклов.
Значения LOOP_CYCLES_ASM, LOOP_CYCLES_ASM_RAM, LOOP_CYCLES_C, LOOP_CYCLES_DWT подобраны так, чтобы на осциллографе наблюдались импульсы с полупериодом, равным задержке, т.е. 3мс. Далее менялись компиляторы и настройки компиляторов, чтобы посмотреть, как будет при этом изменяться наблюдаемый на осциллографе сигнал. Стоит отметить, что вариант с DWT не исполняется программно, поэтому LOOP_CYCLES_DWT всегда равен 1.
На каждой осциллограмме ниже представлен один и тот же сигнал, только маркерами выделен тот участок, для которого осциллограф указывает период. Период, соответственно, состоит из двух задержек, т.е. период должен получаться по 6 мс.
Осциллограммы показывают, что ассемблерная реализация задержки не зависит от настроек компилятора, в то время как программная реализация на языке Си может различаться значительно. Но реализация на ассемблере зависит от того, из какой памяти исполняется код - из ОЗУ или EEPROM (об этом ниже). Реализация на DWT ожидаемо не зависит ни от чего.
Чтобы проверить, как будут отрабатывать функции на разных платформах проект был запущен на 1986ВЕ4У, 1986ВЕ1Т, 1986ВЕ93У, 1986ВЕ8Т. Запуск проводился только под Compiler V5, но зато от двух тактовых частот - от HSI и на максимальной частоте, получаемой для каждого МК на демоплате через PLL от HSE.
Задержка в 3мс взята достаточно большая, чтобы на ее фоне код переключения пина терялся по значимости. Для этого сначала измерялась задержка на пару порядков меньше, и затем измерялось еще несколько значений задержки, чтобы убедиться что на осциллографе импульсы меняются линейно, т.е. вклад задержки от переключения пина мал. Но там, где код компилируется без оптимизации, некоторое влияние от кода переключения пина может быть.
Микроконтроллер 1986ВЕ9х имеет ядро Cortex-M3. Задержки LOOP_CYCLES подбирались так, чтобы в режиме MAX_CLK(-O2) на осциллографе получались заданные задержки в ~3мс. При смене настроек компилятора "ARM Compiler V5" задержки изменяются так:
LOOP_CYCLES | HSI(-O0) | HSI(-O2) | MAX_CLK(-O2) | MAX_CLK(-O0) | |
---|---|---|---|---|---|
ASM | 3 | 3.09ms | 3.09ms | 3.00ms | 3.03ms |
ASM_RAM | 9 | 3.09ms | 3.09ms | 3.00ms | 3.03ms |
C | 6 | 4.08ms | 3.06ms | 3.00ms | 4.01ms |
DWT | 1 | 3.09ms | 3.09ms | 3.00ms | 3.03ms |
Если сравнить строки ASM и ASM_RAM то видно, что для получения задержки в ~3мс необходимо рассчитывать количество циклов исходя из того, что исполнение одного цикла из памяти EEPROM занимает 3 такта ядра, в то время как исполнение из ОЗУ занимает 9 тактов. Это вызывает недоумение, ведь ассемблерный цикл выражен всего двумя инструкциями, как они могут занимать 9 тактов? И почему из более медленной EEPROM исполнение происходит быстрее!?
Это объясняется, скорее всего, так:
Действительно, ассемблерный код не обращается за памятью данных, значит шина данных не работает. Но выборка инструкций должна происходить, значит шина инструкций должна работать. Но флеш память имеет ускоритель, в который считывается сразу строка памяти, т.е. 4-ре 32-разрядных значения. Каждая инструкция THUMB занимает 2 байта, т.е. потенциально за одно чтение из флеш памяти выбирается сразу 8 инструкций. Если все инструкции цикла поместились в буфер ускорителя, т.е. уже считаны из памяти, то шина не обращается к памяти, а, следовательно, такты тратятся только на исполнение инструкций. Вот и получается, что цикл исполняется за 3 инструкции. 1 такт на декремент и 2 такта на прыжок на новый цикл.
В случае с ОЗУ шина инструкций не простаивает, а вычитывает инструкции. Кроме этого, есть еще трехступенчатый конвейер, который осуществляет предвыборку инструкций. В целом, видимо, это приводит к тому, что цикл занимает 9 тактов.
(Не факт что данное объяснение правильное, но других предположений пока нет.)
Выяснилось, что если в код добавить 4-ре инструкции NOP, то исполнение из флеш памяти также начинает занимать 9 тактов, как и при запуске из ОЗУ.
// При значении 8 на осциллографе длительность начинает соответствовать необходимым 3мс. #define LOOP_CYCLES_ASM 8 __attribute__((naked)) void MDR_DelayASM(uint32_t Ticks) { __asm( " CMP r0,#0x00 ;\n" " BEQ 1f ;\n" " NOP ;\n" "0: ;\n" " NOP ;\n" " NOP ;\n" " SUBS r0,r0,#1 ;\n" " NOP ;\n" " NOP ;\n" " BNE 0b ;\n" "1: ;\n" " BX lr ;\n" ); }
Итого, инструкций в цикле 6-сть, плюс две инструкции в конвейере предвыборки. Что сопоставимо со строкой выборки из флеш памяти на 8-мь инструкций. Т.е. скорее всего ускорение действительно возникает благодаря флеш-ускорителю.
Микроконтроллер 1986ВЕ1Т - это ядро-аналог Cortex-M1, блока DWT в данном ядре нет. Задержки на осциллографе при LOOP_CYCLES получились следующие (Compiler V5):
LOOP_CYCLES | HSI(-O0) | HSI(-O2) | MAX_CLK(-O2) | MAX_CLK(-O0) | |
---|---|---|---|---|---|
ASM | 4 | 3.03ms | 3.03ms | 3.03ms | 3.03ms |
ASM_RAM | 8 | 3.03ms | 3.03ms | 3.03ms | 3.03ms |
C | 9 | 4.33ms | 3.03ms | 3.03ms | 4.33ms |
Код на СИ сильно зависит от оптимизации. Код на ассемблере более стабилен. Код из флеш памяти исполняется быстрее, вероятно, здесь тот же эффект - проявляется влияние ускорителя флеш. Но ассемблерный код занимает уже не 3 инструкции, а 4-ре.
Микроконтроллер 1986ВЕ4У - это ядро Cortex-M0. Здесь результаты получились следующие (Compiler V5):
LOOP_CYCLES | HSI(-O0) | HSI(-O2) | MAX_CLK(-O2) | MAX_CLK(-O0) | |
---|---|---|---|---|---|
ASM | 8 | 3.21ms | 3.21ms | 2.94ms | 2.94ms |
ASM_RAM | 4 | 3.21ms | 3.21ms | 2.94ms | 2.94ms |
C | 14 | 3.96ms | 3.21ms | 2.94ms | 3.52ms |
Выводы такие же - код на Си сильно зависит от опций компилятора. Здесь также видно, что HSI в данном МК был не совсем 8МГц, потому что результаты при тактировании от HSI и HSE больше различаются, чем в ранее расcмотренных микроконтроллерах.
Самое интересное, что в 1986ВЕ4У исполнение из флеш памяти занимает больше времени чем из ОЗУ. Это скорее всего говорит о том, что флеш не имеет ускорителя. Действительно, в 1986ВЕ4У строка памяти не состоит из 4-х значений по 32 бита. Читается за раз всего одно 32-битное слово, видимо, потому что частота данного МК небольшая, всего до 36МГц, и поэтому ускоритель тут не нужен. Вообще организации памяти в этом МК значительно отличается от описанных выше микроконтроллеров, как и архитектура ядра. Почему исполнение из ОЗУ происходит быстрее, чем уже привычные 8 тактов, не понятно.
Микроконтроллер 1986ВЕ8Т - это ядро Cortex-M4. Задержки на осциллографе (Compiler V5):
LOOP_CYCLES | HSI(-O0) | HSI(-O2) | MAX_CLK(-O2) | MAX_CLK(-O0) | |
---|---|---|---|---|---|
ASM | 9 | 3.34ms | 3.34ms | 3.00ms | 3.03ms |
ASM_RAM | 9 | 3.34ms | 3.34ms | 3.00ms | 3.03ms |
C | 14 | 3.58ms | 2.34ms | 3.03ms | 3.21ms |
DWT | 1 | 3.34ms | 3.34ms | 3.00ms | 3.00ms |
Здесь тоже значительно различаются результаты при тактировании от HSI и HSE0. Видимо внутренний HSI далек от 8МГц. Код запускался только из ОЗУ, поэтому строчки ASM и ASM_RAM ожидаемо совпадают. Запускать код из ОТР накладно.
В текущий момент уже написано достаточно много примеров под "Pack V6", многие из которых используют функцию MDR_Delay, написанную на СИ. Но функция на СИ оказалась сильно не точна, поэтому будет заменена на ассемблерный вариант. Перепроверить все примеры достаточно кропотливая задача, уже сейчас количество сборок превышает 260 штук, т.к. каждый проект собирается под несколько микроконтроллеров. Возможно, в каких-то проектах это приведет к утере работоспособности, будем их исправлять по мере выявления.
В файлы MDR_ConfigXX.h добавлены значения LOOP_CYCLES для каждого МК, с которыми функция MDR_Delay будет работать по умолчанию в новых проектах. На примере MDR_ConfigVE9х.h это сделано так:
// ========================= MDR_Delay =========================== // Выбор реализации для MDR_Delay: // По умолчанию используется ассемблерный вариант, универсальный // Вариант на Си сильно зависит от опций компилятора // Вариант на DWT есть только в Cortex-M3/M4 и требует предварительное включение вызовом MDR_Delay_Init(). #define USE_MDR_DELAY_ASM //#define USE_MDR_DELAY_C //#define USE_MDR_DELAY_DWT // Исполнение функции задержки из ОЗУ / EEPROM происходит за разное количество тактов CPU. // Данными параметрами можно уточнить сколько тактов CPU занимает один цикл задержки в MDR_Funcs, для повышения точности. #define DELAY_LOOP_CYCLES_ASM 3 #define DELAY_LOOP_CYCLES_ASM_RAM 9 #define DELAY_LOOP_CYCLES_C 6 #define DELAY_LOOP_CYCLES_C_RAM 12 #define DELAY_LOOP_CYCLES_DWT 1 #ifdef USE_MDR_DELAY_C #define DELAY_LOOP_CYCLES DELAY_LOOP_CYCLES_C #define DELAY_LOOP_CYCLES_RAM DELAY_LOOP_CYCLES_C_RAM #elif defined USE_MDR_DELAY_DWT #define DELAY_LOOP_CYCLES DELAY_LOOP_CYCLES_DWT #define DELAY_LOOP_CYCLES_RAM DELAY_LOOP_CYCLES_WDT_RAM #else #define DELAY_LOOP_CYCLES DELAY_LOOP_CYCLES_ASM #define DELAY_LOOP_CYCLES_RAM DELAY_LOOP_CYCLES_ASM #endif
Активное значение USE_MDR_DELAY_ASM / USE_MDR_DELAY_C / USE_MDR_DELAY_DWT выбирает, какая реализация будет у MDR_Delay(). Сами реализации находятся в MDR_Func.c:
// MDR_ConfigVEx.h: Выбор текущего варианта MDR_Delay - задержка в DELAY_LOOPS #if defined (USE_MDR_DELAY_C) #define MDR_Delay_Init() UNUSED(0) #define MDR_Delay() MDR_DelayС #elif defined (USE_MDR_DELAY_DWT) #define MDR_Delay_Init MDR_DelayDWT_Init #define MDR_Delay MDR_DelayDWT #else #define MDR_Delay_Init() UNUSED(0) #define MDR_Delay MDR_DelayASM #endif
Функция MDR_Delay_Init() необходима лишь для реализации с DWT, но для взаимозаменяемости реализаций эту функцию необходимо вставлять куда-нибудь в начало кода, до первого использования MDR_Delay().