
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 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
_XTAL_FREQ and Delay Macros in XC8To use __delay_ms() and __delay_us() in MPLAB XC8 projects, you must define the system clock frequency using _XTAL_FREQ as an unsigned long.
#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
}
}
_XTAL_FREQ must be defined before any __delay_xx() macros are used.UL suffix (e.g., 4000000UL) to ensure correct arithmetic.__delay_ms(x) and __delay_us(x) are built-in macros, not functions.UL → Wrong delay or compiler errors_XTAL_FREQ after delay use → fails silentlyCompiled 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.
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 |
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:
whole = 10frac = 1 (tenths of a degree)Using these types ensures accurate, portable temperature display on an LCD or serial output.
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.
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.
Conclusion: The 32.768 kHz crystal is precise, low-power, and easily divided in binary systems for accurate timekeeping.
/* 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
}
}
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.
DATA_PINCLK_PINLATCH_PIN#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;
}
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.
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.
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.
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.
Ohm’s Law:
V = I × R
More current → more voltage drop.
That’s positive resistance (like a resistor, heater, wire).
It’s about the slope of a voltage–current graph:
ΔV / ΔI) becomes negative| 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 |
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.
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.
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.
⚠️ 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.
I often used a variac in combination with a separate isolation transformer for safe, controlled power-up of electronic equipment under test.
⚠️ 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.
Larger image |
Visit Hobby Page