Introduction: every cycle counts
Programming in Assembly for PIC microcontrollers is, in essence, an exercise in economy. With a 4 MHz clock and a 1 µs instruction cycle, every wasted instruction has a real and measurable cost. In real-time systems — motor control, signal generation, software serial communication — the difference between a 5-cycle loop and an 8-cycle loop can mean the difference between a system that works and one that fails.
Scott Dattalo, in his technical articles on PIC Assembly, has repeatedly demonstrated that the most efficient way to solve a problem is rarely the most obvious. His techniques for delays, PWM, and signal generation fundamentally depend on well-constructed loops. This article extracts and deepens these patterns, presenting them systematically with practical examples and cycle analysis.
1. PIC loop instructions
O PIC midrange (PIC16Fxxx) não possui uma instrução de loop dedicada como o DJNZ do 8051. Em vez disso, os loops são construídos com duas instruções fundamentais que, combinadas, formam o núcleo de quase todo loop em Assembly PIC.
DECFSZ — Decrement and Skip if Zero
A instrução DECFSZ f, d decrementa o registrador f e pula a próxima instrução se o resultado for zero. É a instrução de loop mais importante do PIC. Sua semântica exata é:
__PROTECTED_3__
Um detalhe crítico: DECFSZ decrementa antes de testar. Isso significa que um registrador inicializado com 0 executará o loop 256 vezes (de 0 → 255 → 254 → ... → 1 → 0), não zero vezes. Inicializar com N executa o loop exatamente N vezes.
INCFSZ — Increment and Skip if Zero
A instrução complementar INCFSZ f, d incrementa e pula se o resultado for zero (overflow de 8 bits). É menos comum para loops de contagem simples, mas útil em padrões específicos de otimização que veremos adiante.
| Instruction | Operation | Skip when | Cycles (no skip) | Cycles (with skip) |
|---|---|---|---|---|
DECFSZ f, d | f = f − 1 | result = 0 | 1 | 2 |
INCFSZ f, d | f = f + 1 | result = 0 (overflow) | 1 | 2 |
BTFSC f, b | tests bit b of f | bit = 0 | 1 | 2 |
BTFSS f, b | tests bit b of f | bit = 1 | 1 | 2 |
GOTO label | PC ← label | — | 2 | — |
2. The basic loop and its real cost
The simplest loop in PIC Assembly — counting from N down to 0 — seems trivial, but already reveals the first optimization opportunity. Compare the two versions below:
__PROTECTED_11__
__PROTECTED_12__
A versão com DECFSZ elimina uma instrução por iteração. Para um loop de 256 iterações, isso representa 256 ciclos economizados — com clock de 4 MHz, são 64 µs a menos de overhead puro. Em loops internos de algoritmos críticos, essa diferença se multiplica.
3. Precise delays: Dattalo's technique
Generating precise delays without using hardware timers is one of the most classic problems in PIC Assembly. The naive approach — a simple decrement loop — works, but wastes cycles and makes exact time calculation difficult. Dattalo documented a family of techniques that allow arbitrarily precise delays with minimal code.
The fundamental delay formula
Um loop de delay com DECFSZ e GOTO tem o seguinte custo exato:
Onde N é o valor inicial do contador (1 a 256). O "−1" vem do fato de que na última iteração o DECFSZ executa em 2 ciclos (skip tomado) mas o GOTO não é executado, economizando 1 ciclo. Para N = 0 (256 iterações): T = 3 × 256 − 1 = 767 ciclos.
__PROTECTED_18__
Fine-tuning: delays not multiples of 3
O problema com o loop de 3 ciclos é que só gera delays múltiplos de 3 (menos 1). Para delays arbitrários, Dattalo usa a técnica de preenchimento com NOPs antes ou depois do loop:
__PROTECTED_19__
GOTO $+1 (salto para o endereço seguinte) é um NOP de 2 ciclos — útil quando você precisa adicionar exatamente 2 ciclos de ajuste sem usar dois NOPs separados. Dattalo usa esse padrão extensivamente nos artigos de PWM e delays para alinhar o timing sem desperdiçar memória de programa.4. Nested loops: multiplying the reach
A single 8-bit loop generates at most 767 delay cycles (N=256). For longer delays — milliseconds or seconds — nested loops are necessary. Dattalo's technique for nested loops is elegant: each outer level multiplies the reach of the inner loop.
Millisecond delay
With a 4 MHz clock (1 instruction cycle = 1 µs), a 1 ms delay requires 1000 cycles. A simple 8-bit loop reaches at most 767 cycles, so we need two levels:
__PROTECTED_21__
Parametric delay: passing N via W
A very useful variation documented by Dattalo is the parametric delay — where the number of milliseconds is passed in the W register before the call. This allows a single reusable delay sub-routine:
__PROTECTED_22__
5. The loop without GOTO: Dattalo's technique for ultra-efficient loops
A técnica mais elegante de Dattalo para loops é a eliminação completa do GOTO. Em vez de usar DECFSZ + GOTO (3 ciclos/iteração), ele usa a estrutura de skip invertido para criar um loop de apenas 2 ciclos por iteração — uma redução de 33%.
A ideia é: em vez de pular o GOTO quando o contador chega a zero, pular o DECFSZ (ou equivalente) quando o contador ainda não chegou a zero. Isso é possível quando o corpo do loop tem exatamente 1 instrução:
__PROTECTED_26__
Unrolled loop (loop unrolling)
A técnica mais poderosa de Dattalo para loops de alto desempenho é o loop desenrolado (loop unrolling): em vez de executar N iterações de um loop, duplicar o corpo do loop N vezes no código. Isso elimina completamente o overhead de controle (DECFSZ + GOTO = 3 ciclos) ao custo de mais memória de programa.
__PROTECTED_27__
6. RETLW tables: the loop that doesn't look like a loop
Uma das técnicas mais elegantes do PIC Assembly, amplamente usada por Dattalo em seus artigos de geração de sinais, é a tabela de lookup implementada com RETLW. Ela é, na essência, um "loop" de acesso indexado à memória de programa — mas sem nenhum overhead de controle.
__PROTECTED_29__
O truque ADDWF PCL, F soma o índice ao contador de programa, saltando diretamente para a entrada correta da tabela. Cada RETLW carrega o valor em W e retorna em 2 ciclos. O custo total é 3 ciclos por acesso (1 para ADDWF + 2 para RETLW) — independente do tamanho da tabela.
ADDWF PCL, F modifica apenas os 8 bits baixos do PC. Se a tabela cruzar um limite de 256 palavras (página de 256 endereços), o acesso será incorreto. Dattalo recomenda sempre alinhar tabelas ao início de uma página usando a diretiva ORG ou garantir que a tabela inteira caiba dentro de 256 endereços.7. Loops in PWM generation: applying Dattalo
O artigo de PWM de Dattalo (disponível em camposlh.com/dattalo/pwm.html) demonstra como loops bem construídos são a base da geração de PWM por software. A técnica de phase-shifted counters usa dois contadores que incrementam no mesmo loop, com o output sendo controlado pelos overflows.
__PROTECTED_34__
8. Technique comparison: cycles and memory
| Technique | Cycles/iteration | Overhead instructions | Memory | Best use |
|---|---|---|---|---|
DECF + BTFSS + GOTO |
4 | 3 | Low | Legacy code, compatibility |
DECFSZ + GOTO |
3 | 2 | Low | General loop, most common pattern |
INCFSZ + GOTO $+2 |
3–4 | 2–3 | Low | PWM, overflow counters |
| Loop desenrolado | 0 | 0 | High (N × body) | Small N, critical timing |
| Tabela RETLW | 3 (acesso) | 1 (ADDWF PCL) | N + 1 words | Value lookup, signal generation |
| Loop aninhado 2 níveis | ~3 (inner) | 4 (2 per level) | Low | Delays longos, N > 256 |
Conclusion
As técnicas de Dattalo para loops em PIC Assembly não são apenas truques de otimização — são a expressão de um entendimento profundo da arquitetura Harvard do PIC e do custo real de cada instrução. A filosofia subjacente é sempre a mesma: entender o que o hardware faz em nível de ciclo e explorar isso ao máximo.
Os padrões apresentados aqui — DECFSZ + GOTO como estrutura base, ajuste fino com NOPs e GOTO $+1, loops aninhados para delays longos, loop unrolling para seções críticas e tabelas RETLW para lookup — formam um vocabulário completo para escrever Assembly PIC eficiente. Combinados com as técnicas de PWM e geração de sinais documentadas por Dattalo, eles permitem implementar algoritmos sofisticados em microcontroladores com apenas 2K palavras de programa e 68 bytes de RAM.
Generating Sine Waves in Software — 10 métodos para geração de senoides, incluindo tabelas RETLW e o algoritmo de Goertzel.
Square Root Theory and Algorithms — Square root algorithms for 8-bit microcontrollers.