PIC16F57 LCD display counting in binary and decimal on prototyping board.
PIC16F57 LCD display counting in binary and decimal.

PIC Bitwise Operations and Delay Macros in XC8 – Practical Guide for Beginners

by Lewis Loflin

Common register control using C-style bitwise logic:

Operation Purpose Example
|= Set bit n to 1 (make high) PORTB |= (1 << 2);
&= ~(1 << n) Clear bit n to 0 (make low) TRISB &= ~(1 << 3);
^= Toggle bit n (flip state) PORTB ^= (1 << 0);

These are especially useful when working with individual I/O pins without affecting other bits in a register.

#pragma config Overview

#pragma is used to send special instructions to the compiler. In XC8, it's mainly used to set configuration bits (fuses) directly from your C source code.

Directive Meaning
#pragma config FOSC = XT Use external crystal oscillator
#pragma config WDTE = OFF Disable watchdog timer
#pragma config MCLRE = OFF Disable MCLR (RA5 becomes I/O)
#pragma config LVP = OFF Disable low-voltage programming

These lines are essential for ensuring your microcontroller starts up with the correct hardware settings. Without them, you'll rely on manual IDE configuration — and that can lead to mismatched builds.

Bit Manipulation Macros:

#define bitset(var, bitno)   ((var) |=  (1 << (bitno)))   // Set bit
#define bitclr(var, bitno)   ((var) &= ~(1 << (bitno)))   // Clear bit
#define bitflip(var, bitno)  ((var) ^=  (1 << (bitno)))   // Toggle bit
#define bitcheck(var, bitno) (((var) &   (1 << (bitno))) != 0)  // Check bit
  

🕒 Using _XTAL_FREQ and Delay Macros in XC8

To use __delay_ms() and __delay_us() in MPLAB XC8 projects, you must define the system clock frequency using _XTAL_FREQ as an unsigned long.

✅ Example Setup

#include <xc.h>
  
// Configuration bits
#pragma config WDT = OFF   // Watchdog Timer disabled
#pragma config CP = OFF     // Code protection off
#pragma config OSC = XT    // XT Oscillator (use 4 MHz crystal)

#define _XTAL_FREQ 4000000UL  // Required: 4 MHz crystal, note the UL!

void main(void) {
    TRISB = 0x00;      // PORTB output
    PORTB = 0x00;

    while (1) {
        PORTB ^= 0x01;  // Toggle RB0
        __delay_ms(500);  // 500ms delay
    }
}

📌 Key Notes

🚫 Common Pitfalls


🔧 Flash Usage Comparison – Toggling RB1 on PIC16F57

Compiled with XC8 for PIC16F57, these code snippets toggle LED on RB1. Measured flash usage illustrates macro efficiency:

Code Flash Used Notes
PORTBbits.RB1 ^= 1; 67 bytes Standard bit toggle, safe and readable
PORTBbits.RB1 = PORTBbits.RB1 ^ 1; 66 bytes Explicit XOR form; minor gain
LED = LED ^ 1; 66 bytes Macro hides bit expression
bitflip(PORTB, 1); 58 bytes ✅ Most efficient — direct XOR via macro

Macro used:

#define bitflip(var, bitno) ((var) ^= (1 << (bitno)))

Conclusion: For size-critical code, direct macros like bitflip() save valuable flash on baseline PICs such as the 16F57.


🔍 Understanding int8_t and int16_t in Embedded C

The types int8_t and int16_t are part of the <stdint.h> header and define fixed-width signed integers. These are essential in embedded systems where memory and data width must be tightly controlled.

Type Size Range Description
int8_t 8 bits -128 to +127 Signed 8-bit integer (1 byte)
uint8_t 8 bits 0 to 255 Unsigned 8-bit integer
int16_t 16 bits -32,768 to +32,767 Signed 16-bit integer (2 bytes)
uint16_t 16 bits 0 to 65,535 Unsigned 16-bit integer

📘 Why Use These Types?

🌡️ Example: DS18B20 Temperature Reading

The DS18B20 sensor returns a 16-bit signed value that must be interpreted correctly. This is why we use:

int16_t temp;     // Holds raw 16-bit signed temperature from DS18B20
int8_t whole;     // Stores the integer (whole °C) part of the temperature
uint8_t frac;     // Stores the fractional part (in tenths of a degree)

For example, if the sensor returns 0x00A2, that equals 162 / 16 = 10.125°C. The code separates this into:

Using these types ensures accurate, portable temperature display on an LCD or serial output.


🔁 Ladder Logic Is Just a While Loop

Many beginners think PLCs run some kind of magic instruction set, but in reality, ladder logic is just a continuous scan loop. The CPU in a PLC like the Allen-Bradley MicroLogix 1000 simply executes a series of steps repeatedly, similar to this:

while (1) {
    read_inputs();        // Sample all digital and analog inputs
    evaluate_rungs();     // Process ladder logic rungs (Boolean logic)
    update_outputs();     // Set or clear outputs based on logic results
}

Each "rung" of the ladder is nothing more than a Boolean expression — a combination of ANDs, ORs, and NOTs acting on internal bits and I/O states. Timers and counters are updated during each scan.

🧠 Why This Fits the PIC16F57

Simple microcontrollers like the PIC16F57 are ideal for teaching this structure. If you've programmed ladder logic before, you'll recognize that you're really writing a loop that checks conditions and sets outputs — and that's exactly what a microcontroller does at its core.

Once you understand the loop, you understand the logic.


⏱️ Watch Crystal Frequency Summary

Conclusion: The 32.768 kHz crystal is precise, low-power, and easily divided in binary systems for accurate timekeeping.



XC8 Blink Example - PIC16F628A

/* These are example ways to configure pic IDE*/

#include <xc.h>


// CONFIGURATION BITS use this
#pragma config FOSC = XT        // Crystal oscillator
#pragma config WDTE = OFF       // Watchdog Timer disabled
#pragma config PWRTE = ON       // Power-up Timer enabled
#pragma config MCLRE = OFF      // RA5 is digital I/O, not MCLR
#pragma config BOREN = OFF      // Brown-out Reset disabled
#pragma config LVP = OFF        // Low-Voltage Programming disabled
#pragma config CPD = OFF        // Data EEPROM code protection off
#pragma config CP = OFF         // Flash code protection off



#define _XTAL_FREQ 4000000UL      // 4 MHz crystal

void main(void) {
    TRISBbits.TRISB1 = 0;  // Set RB0 as output
    PORTBbits.RB1 = 0;     // Turn off LED initially
    CMCON = 0x07; // comparators off
    
    
    while (1) {
        PORTBbits.RB1 = 1;  // Turn on
        __delay_ms(500);
        PORTBbits.RB1 = 0;  // Turn off
        __delay_ms(500);
    }
}

This example from the web using obsolete Hi Tech C:


#include <htc.h>
#include <pic.h>
#include <pic16f628a.h>

// Config word
__CONFIG(FOSC_XT & WDTE_OFF & PWRTE_ON & CP_OFF);

#define LED  RA0
#define _XTAL_FREQ   4000000

void main()
{   
        CMCON = 0x07; // comparators off
        // Make RA0 low
        TRISA0 = 0;                // Make RA0 pin output
        LED    = 0;

    while(1)
    {
        __delay_ms(500);       // Half sec delay
        LED = 0;               // LED off
        __delay_ms(500);       // Half sec delay
        LED = 1;               // LED on
    }
}


📥 CD4021B Parallel-to-Serial Input – PIC16F57 (XC8)

This example reads 8 switch inputs through a CD4021B shift register connected to RB0–RB2. The result can be output to LEDs, an LCD, or stored for logic decisions. Pin naming follows *_PIN format for clarity.

🔌 Connections

#include <xc.h>
#include <stdint.h>
#define _XTAL_FREQ 4000000UL

// ----------- I/O Pin Definitions -------------
#define DATA_PIN     PORTBbits.RB0   // Q7 output from CD4021B
#define CLK_PIN      PORTBbits.RB1   // Clock input to CD4021B
#define LATCH_PIN    PORTBbits.RB2   // Parallel Load (active HIGH)

// ----------- I/O Direction Setup -------------
void cd4021_init(void) {
    TRISBbits.TRISB0 = 1;  // RB0 = input (DATA)
    TRISBbits.TRISB1 = 0;  // RB1 = output (CLK)
    TRISBbits.TRISB2 = 0;  // RB2 = output (LATCH)

    CLK_PIN = 0;
    LATCH_PIN = 0;
}

// ----------- Read 8 Bits from CD4021B --------
uint8_t read_cd4021(void) {
    uint8_t value = 0;

    // Latch parallel inputs
    LATCH_PIN = 1;
    __delay_us(2);
    LATCH_PIN = 0;

    // Read 8 bits MSB first
    for (uint8_t i = 0; i < 8; i++) {
        value <<= 1;
        if (DATA_PIN)
            value |= 1;

        CLK_PIN = 1;
        __delay_us(1);
        CLK_PIN = 0;
    }

    return value;
}

🧪 Example Main Loop

void main(void) {
    cd4021_init();

    while (1) {
        uint8_t switches = read_cd4021();

        // Output to PORTC for LED display
        PORTC = switches;

        __delay_ms(200);
    }
}

This approach minimizes I/O usage and avoids bloated code, perfectly suited for small devices like the PIC16F57.


⚠️ Required: Pull-Up or Pull-Down Resistors

Each of the 8 inputs to the CD4021B (D0–D7) must be connected to either:

This is essential because CMOS inputs float when unconnected, leading to unstable or unpredictable readings. You can then wire a pushbutton or switch to ground or Vcc to change the state.

🧷 Example: Pull-Up Configuration with Normally Open Switch

When the switch is open, the input reads HIGH. When pressed, it pulls the input LOW (active LOW).

Never leave CD4021B inputs floating — always tie them to a defined logic level.


🔌 Understanding “Negative Resistance” – A Technician’s Guide

❓What Is “Negative Resistance”?

Despite the name, negative resistance doesn’t mean a resistor with a negative value. Instead, it's a way to describe a device that behaves differently under certain conditions—as current increases, the voltage across it goes down.

🧠 Think of Resistance Normally:

Ohm’s Law:
V = I × R
More current → more voltage drop.
That’s positive resistance (like a resistor, heater, wire).

📉 So What’s “Negative” Resistance Then?

It’s about the slope of a voltage–current graph:

⚙️ Real-World Devices That Show This:

Device Description
Xenon Flash Tube / Neon Bulb After ignition, voltage drops as current rises
Tunnel Diode Has a “dip” in its voltage-current curve
Gunn Diode Used in microwave circuits for oscillators
Unijunction Transistor (UJT) Used for triggering in timing circuits

📈 Visual Example (simplified):

Voltage (V)
  |
  |       /\
  |      /  \
  |     /    \_________
  |    /               \
  |___/                 \____> Current (I)
       ↑
   Negative resistance region (slope ↓)
  

In this middle section, more current actually causes voltage to drop. That’s what we call negative resistance.

🧰 Why It Matters

These devices are used in oscillators, flashing lights, trigger circuits, and microwave systems. They can self-oscillate or sustain pulses, useful in timing or signal generation.

✅ Technician-Friendly Takeaway:

🛑 Common Misconception:
“Negative resistance generates power.”
No. These devices still consume power—they just have a funky curve! They're not batteries or perpetual motion machines.

🔁 Autotransformer with Adjustable Tap (Variac)

An autotransformer is a single-winding transformer where different voltage levels are accessed through tap points. One special implementation is the variac, which uses a movable contact (slider) to provide a continuously variable output voltage.

🔧 Key Features:

⚠️ Safety Note: Since the primary and secondary are not isolated, variacs should be used with caution and not assumed to be electrically safe from the mains.

Variacs are widely used in electronics labs to provide precise, adjustable AC voltage — for example, gradually powering up old tube electronics or controlling test conditions for AC-powered devices.


🔍 Using a Variac for Controlled Testing

I often used a variac in combination with a separate isolation transformer for safe, controlled power-up of electronic equipment under test.

🧰 Test Procedure:

✅ Benefits:

⚠️ Warning: Never assume a variac provides electrical isolation — always use a proper isolation transformer for safety.


74C14 Schmitt trigger based pulse generator and switch debounce circuit.

74C14 Schmitt trigger based pulse generator and switch debounce circuit.
Larger image | Visit Hobby Page

Links

PIC16F57 projects.

The next three pages focuses on Timer0 and interrupts in the PIC16F84A


Electronics and Science

Six Parts:


The following has information on ferrite materials, SCRs, Neon sign colors.