Microcontroller: PIC16F886 @ 20 MHz
Project: Sistema de Monitorización & Actuación - Lámpara de Mesa Diseñada para el Estudio
Version: 1.0
Date: December 2025
Language: C (XC8 Compiler)
Institution: Universidad Politécnica de Madrid
- System Overview
- Hardware Architecture
- Software Architecture
- Project Structure
- Quick Start
- Communication Protocol
- Building & Deployment
- Testing
- Troubleshooting
- Documentation
SMA-LAMP is an embedded monitoring and control system that:
- Monitors environmental conditions (temperature, humidity, noise, light, CO₂)
- Controls RGB LED lighting and fan speed
- Communicates with SMA-COMP host via UART protocol
- Persists configuration in EEPROM
- Operates autonomously with timed sensor readings
- Real-time noise monitoring (10ms sampling, 1s window)
- Environmental sensors (5s periodic readings)
- CO₂ sensor with 5-minute warmup handling
- UART protocol with timeout protection
- Automatic configuration persistence (every 5s)
- LED activity indicator on command reception
- Brown-out reset protection
MCU: PIC16F886
Frequency: 20 MHz (HS crystal)
Voltage: 5V
Flash: 8 KB
RAM: 368 bytes
EEPROM: 256 bytes
| Pin | Function | Type | Device | Notes |
|---|---|---|---|---|
| RA0 | AN0 | Analog Input | LM35 Temperature | 0-5V → 0-500°C |
| RA1 | AN1 | Analog Input | HIH4000 Humidity | 0-5V → 0-100% RH |
| RA2 | AN2 | Analog Input | Microphone Noise | Sampled every 10ms |
| Pin | Function | Type | Device | Notes |
|---|---|---|---|---|
| RC0 | SPI_CLK | Digital Output | SK9822 LEDs | Bit-bang SPI clock |
| RC1 | SPI_SDO | Digital Output | SK9822 LEDs | Bit-bang SPI data |
| RC2 | CCP1/PWM | PWM Output | Fan Motor | 20 kHz, 10-bit resolution |
| RC3 | SCL | I2C | VEML7700, iAQ-Core | Requires 4.7kΩ pull-up |
| RC4 | SDA | I2C | VEML7700, iAQ-Core | Requires 4.7kΩ pull-up |
| RC6 | TX | UART Output | SMA-COMP | 9600 baud |
| RC7 | RX | UART Input | SMA-COMP | 9600 baud |
| Peripheral | Usage | Configuration |
|---|---|---|
| Timer0 | 10ms tick | Prescaler 1:256, preload 61 |
| Timer1 | ~105ms tick | Prescaler 1:8, 16-bit overflow |
| Timer2 | PWM base | PR2=249 → 20 kHz |
| ADC | 3 channels | 10-bit, VDD/VSS ref, 10µs acq time |
| MSSP | I2C Master | 100 kHz, SSPADD=49 |
| EUSART | UART | 9600 8N1, SPBRG=129 |
| CCP1 | PWM | Fan control, duty 0-100% |
| Sensor | Interface | Address/Channel | Range | Update Rate |
|---|---|---|---|---|
| LM35 | ADC | AN0 | 0-255°C | 5 seconds |
| HIH4000 | ADC | AN1 | 0-100% RH | 5 seconds |
| Microphone | ADC | AN2 | 3 categories | 1 second (max of 100 samples) |
| VEML7700 | I2C | 0x10 | 0-65535 lux | 5 seconds |
| iAQ-Core | I2C | 0x5A | 0-65535 ppm CO₂ | 5 seconds (warmup 5 min) |
| Actuator | Interface | Control | Range | Notes |
|---|---|---|---|---|
| SK9822 RGB LED | SPI (bit-bang) | R, G, B, Brightness | 0-255 RGB, 0-31 brightness | N_LEDS configurable |
| Fan | PWM (CCP1) | Speed percentage | 0-100% | 20 kHz frequency |
┌─────────────────────────────────────────┐
│ main.c │ ← Application Logic
│ • Initialization │
│ • ISR (Timer0/Timer1) │
│ • Main Loop (scheduler-driven) │
└───────────────┬─────────────────────────┘
│
┌───────────┴───────────┬─────────────┬──────────────┐
▼ ▼ ▼ ▼
┌─────────┐ ┌──────────────┐ ┌──────────┐ ┌────────────┐
│ sensor/ │ │ actuador/ │ │protocol/ │ │ hal/ │
│ │ │ │ │ │ │ │
│ • temp │ │ • led │ │• uart │ │• adc │
│ • hum │ │ • fan │ │• sched │ │• i2c │
│ • noise │ │ │ │ │ │• spi │
│ • lux │ │ │ │ │ │• pwm │
│ • co2 │ │ │ │ │ │• uart │
│ │ │ │ │ │ │• eeprom │
└────┬────┘ └──────┬───────┘ └────┬─────┘ └─────┬──────┘
│ │ │ │
│ │ │ │
└─────────────────────┴───────────────┴───────────────┘
│
┌──────┴──────┐
│ lib/ │ ← Official Libraries
│ │
│ • i2c-v2 │ (Read-only)
│ • spi-v1 │
└─────────────┘
- Layered Architecture: Clear separation of concerns
- Single Responsibility: Each module has one job
- HAL Abstraction: Hardware access only through HAL layer
- No Re-initialization: Peripherals initialized once in main()
- Event-Driven: Scheduler triggers periodic operations
- Non-Blocking: All operations use timeouts or polling
SMA-LAMP/
├── main.c # Application entry point and main loop
├── README.md # This file - project overview
├── REGISTERS_MAP.md # PIC16F886 register reference
│
├── lib/ # Official vendor libraries (read-only)
│ ├── README.md # Library documentation
│ ├── i2c-v2.c/h # Hardware I2C driver
│ └── spi-master-v1.c/h # Bit-bang SPI driver
│
├── hal/ # Hardware Abstraction Layer
│ ├── README.md # HAL architecture and API reference
│ ├── hal_adc.c/h # ADC peripheral (3 channels, 10-bit)
│ ├── hal_uart.c/h # UART communication (9600 baud)
│ ├── hal_i2c.c/h # I2C master (100 kHz)
│ ├── hal_spi.c/h # SPI bit-bang implementation
│ ├── hal_pwm.c/h # PWM generation (20 kHz)
│ └── hal_eeprom.c/h # EEPROM read/write
│
├── sensor/ # Sensor driver implementations
│ ├── README.md # Sensor specifications and usage
│ ├── sensor_temp.c/h # LM35 temperature sensor (ADC)
│ ├── sensor_hum.c/h # HIH4000 humidity sensor (ADC)
│ ├── sensor_noise.c/h # Microphone noise level (ADC)
│ ├── sensor_lux.c/h # VEML7700 light sensor (I2C)
│ └── sensor_co2.c/h # iAQ-Core CO₂ sensor (I2C)
│
├── actuador/ # Actuator driver implementations
│ ├── README.md # Actuator specifications and usage
│ ├── actuator_led.c/h # SK9822 RGB LED strip (SPI)
│ └── actuator_fan.c/h # PWM fan control
│
├── protocol/ # Communication and timing
│ ├── README.md # Protocol specification
│ ├── protocol_uart.c/h # UART command protocol with CRC-8
│ └── scheduler.c/h # Time-based event scheduler
│
└── test/ # Individual component tests
├── README.md # Testing documentation and procedures
├── test_fan.c # Fan PWM ramp test
├── test_led.c # LED color cycling test
├── test_command.c # UART command reception test
├── test_co2.c # CO₂ sensor I2C test
├── test_lux.c # Light sensor I2C test
├── test_temperature.c # Temperature sensor ADC test
├── test_noise.c # Noise sensor ADC test
├── test_humidity.c # Humidity sensor ADC test
└── test_eeprom.c # EEPROM read/write test
Each module folder contains detailed documentation:
- lib/README.md - Vendor library API and integration notes
- hal/README.md - HAL design principles, peripheral configurations, API reference
- sensor/README.md - Sensor datasheets summary, calibration, wiring diagrams
- actuador/README.md - Actuator specifications, control algorithms, safety limits
- protocol/README.md - Complete protocol specification, frame format, CRC details
- test/README.md - Test procedures, expected results, troubleshooting
- Hardware: PIC16F886 microcontroller @ 20 MHz crystal
- Software: MPLAB X IDE v6.0+, XC8 Compiler v2.40+
- Programmer: PICkit 3/4 or compatible
- Clone/Download this project
- Open
main.cproject in MPLAB X - Configure device as PIC16F886
- Build the project (Production mode)
- Program the device using PICkit
- Connect UART terminal @ 9600 baud to RC6 (TX)
- Observe periodic sensor data transmission
After programming, the system will:
- Initialize all peripherals (200ms)
- Restore saved configuration from EEPROM
- Send diagnostic frames:
CMD_NOISE = 0(noise sensor baseline)CMD_TEMP = 25°C(temperature test)
- Begin periodic operation:
- Every 1s: Noise level report
- Every 5s: Temperature, humidity, light, CO₂ reports
Before first power-on:
- 20 MHz crystal + 15-33pF capacitors on OSC1/OSC2
- 10kΩ pull-up resistor on MCLR pin
- 100nF decoupling capacitor near VDD
- 4.7kΩ pull-up resistors on RC3 (SCL) and RC4 (SDA)
- 5V power supply (stable, ±5%)
- UART connection: TX(RC6)→RX, RX(RC7)→TX, GND common
The HAL provides a clean interface to PIC16F886 peripherals:
| Module | Purpose | Key Functions |
|---|---|---|
| hal_adc | Analog-to-Digital conversion | adc_init(), adc_read(ch) |
| hal_uart | Serial communication | uart_init(baud), uart_send_byte() |
| hal_i2c | I2C bus communication | hal_i2c_init(), hal_i2c_read/write() |
| hal_spi | SPI communication | spi_init(), spi_send_byte() |
| hal_pwm | PWM signal generation | pwm_init(), pwm_set_percent() |
| hal_eeprom | Non-volatile storage | eeprom_read/write(addr, val) |
Design Principle: Upper layers never access registers directly - all hardware interaction goes through HAL.
See hal/README.md for detailed API reference and configuration details.
| Sensor | Interface | Range | Update Rate | Special Notes |
|---|---|---|---|---|
| LM35 | ADC AN0 | 0-255°C | 5s | 10mV/°C, direct read |
| HIH4000 | ADC AN1 | 0-100% RH | 5s | Requires temperature compensation |
| Microphone | ADC AN2 | 3 categories | 1s | Maximum of 100 samples |
| VEML7700 | I2C 0x10 | 0-65535 lux | 5s | 16-bit light sensor |
| iAQ-Core | I2C 0x5A | 0-65535 ppm | 5s | 5-minute warmup period |
See sensor/README.md for calibration procedures, wiring diagrams, and troubleshooting.
| Actuator | Interface | Control Range | Notes |
|---|---|---|---|
| SK9822 LED | SPI (RC0/RC1) | RGB: 0-255, Brightness: 0-31 | Supports multiple LEDs (N_LEDS) |
| Fan | PWM (RC2) | 0-100% speed | 20 kHz frequency, 10-bit resolution |
See actuador/README.md for control algorithms and safety considerations.
UART Protocol: Custom binary protocol with CRC-8 error detection
- Frame Format:
[HEADER][LEN][CMD][DATA...][CRC_L][CRC_H] - Baudrate: 9600 bps (8N1)
- Commands: 7 bidirectional commands (see protocol documentation)
- Error Detection: CRC-8-CCITT (polynomial 0x07)
Scheduler: Time-based event system driven by Timer0 (10ms) and Timer1 (~105ms)
See protocol/README.md for complete protocol specification and examples.
Every 1 second:
└─→ [AA 04 00 {cat} 00 00] CMD_NOISE
Every 5 seconds:
├─→ [AA 04 06 {temp} 00 00] CMD_TEMP
├─→ [AA 05 03 {hh} {hl} 00 00] CMD_HUM
├─→ [AA 05 01 {lh} {ll} 00 00] CMD_LUX
└─→ [AA 05 02 {ch} {cl} 00 00] CMD_CO2
Asynchronous (any time):
├─→ [AA 04 04 {speed} 00 00] CMD_FAN
└─→ [AA 07 05 {r} {g} {b} {br} 00 00] CMD_LEDS
Send Temperature 25°C:
AA 04 06 19 00 00
│ │ │ │ └─ CRC
│ │ │ └──── Data: 25 (0x19)
│ │ └─────── Command: TEMP (0x06)
│ └────────── Length: 4
└───────────── Header: 0xAA
Receive LED Command (Red, max brightness):
AA 07 05 FF 00 00 1F 00 00
│ │ │ │ │ │ │ └─ CRC
│ │ │ │ │ │ └──── Brightness: 31
│ │ │ │ │ └─────── Blue: 0
│ │ │ │ └──────────Green: 0
│ │ │ └───────────── Red: 255
│ │ └──────────────── Command: LEDS (0x05)
│ └─────────────────── Length: 7
└────────────────────── Header: 0xAA
Fosc/4 = 20MHz / 4 = 5 MHz
Period = 0.2 µs
Prescaler 1:256 → tick every 51.2 µs
10ms / 51.2µs = 195.3 ticks
Preload = 256 - 195 = 61
OPTION_REG = 0b10000111 // nRBPU=1 (PORTB pull-ups OFF), PSA=0, PS=111 (1:256), T0CS=0 (Fosc/4)
TMR0 += 61 // Compensated reload
Fosc/4 = 5 MHz
Prescaler 1:8 → tick every 1.6 µs
Overflow = 65536 × 1.6µs = 104.8576 ms
T1CON = 0b00110001 // TMR1ON=1, TMR1CS=0 (Fosc/4), T1CKPS=11 (1:8), TMR1GE=0 (always count)
void __interrupt() isr(void) {
// Every 10ms
if(TMR0IF && TMR0IE) {
TMR0IF = 0;
TMR0 += 61; // Compensated reload (avoids drift)
scheduler_tick_10ms();
// Sample noise and keep maximum over 1s window
noise_cat_t cat = noise_read_category();
if(cat > noise_max_cat) noise_max_cat = cat;
}
// Every ~105ms
if(TMR1IF && TMR1IE) {
TMR1IF = 0;
scheduler_tick_100ms();
}
}IMPORTANT: TMR0 += 61 compensates for ISR latency, preventing cumulative drift.
The project includes 9 standalone test programs in the test/ folder:
| Test | Purpose | Hardware Required |
|---|---|---|
| test_fan | PWM fan ramp (0→80%→0%) | Fan on RC2 |
| test_led | LED color cycling | SK9822 on RC0/RC1 |
| test_command | UART command reception | UART terminal |
| test_temperature | Temperature reading | LM35 on RA0 |
| test_humidity | Humidity reading | HIH4000 on RA1 |
| test_noise | Noise level sampling | Microphone on RA2 |
| test_lux | Light sensor I2C | VEML7700 on I2C |
| test_co2 | CO₂ sensor I2C | iAQ-Core on I2C |
| test_eeprom | EEPROM read/write | None (internal) |
Usage: Each test is a standalone .c file with its own main(). Compile and program individually to verify hardware functionality before deploying the full system.
See test/README.md for detailed test procedures and expected outputs.
1. Power-On Reset → PWRTE waits 72ms
2. main() starts
3. init_pins_min() Configure TRIS registers
4. init_timers() Start Timer0, Timer1, enable GIE
5. scheduler_init() Reset tick counters
6. protocol_init(9600) Initialize UART
7. Peripheral Init (once):
├─ adc_init()
├─ hal_i2c_init()
├─ spi_init()
└─ pwm_init()
8. Sensor Init:
├─ temp_init() (No-op)
├─ hum_init() (No-op)
├─ noise_init() (No-op)
├─ lux_init() (No-op)
└─ co2_init() Reset warmup counter
9. Actuator Init:
├─ led_init() Set default white color
└─ fan_init() Set speed to 0%
10. Restore from EEPROM:
├─ Read addresses 0x00-0x04
├─ Validate (check for 0xFF = empty)
└─ Apply configuration
11. Diagnostic Test:
├─ Send CMD_NOISE = 0
└─ Send CMD_TEMP = 25°C
12. Enter main loop
while(1) {
// Event 1: Noise report (every 1s)
if(scheduler_noise_ready()) {
protocol_send(CMD_NOISE, {noise_max_cat}, 1);
noise_max_cat = NOISE_LOW; // Reset window
}
// Event 2: Environmental sensors (every 5s)
if(scheduler_env_ready()) {
save_actuators_state(); // Persist to EEPROM
co2_update_warmup(); // Increment warmup counter
temp = temp_read_degC();
hum = hum_read_percent();
lux = lux_read();
co2 = co2_read_ppm(); // Returns 0xFFFF if not ready
protocol_send(CMD_TEMP, ...);
protocol_send(CMD_HUM, ...);
protocol_send(CMD_LUX, ...);
protocol_send(CMD_CO2, ...);
}
// Event 3: Command reception (asynchronous)
if(protocol_receive(&cmd, buf, &len)) {
switch(cmd) {
case CMD_FAN:
current_fan_speed = buf[0];
fan_set_speed(current_fan_speed);
break;
case CMD_LEDS:
current_led_r = buf[0];
current_led_g = buf[1];
current_led_b = buf[2];
current_led_brightness = buf[3];
led_set_color(...);
break;
}
// Activity indicator: flash green LED
led_set_color(0, 255, 0, 10);
__delay_ms(50);
led_set_color(current_led_r, current_led_g, current_led_b, current_led_brightness);
}
}
Non-blocking: All operations use polling or timeouts (no infinite loops).
| Address | Content | Range | Description |
|---|---|---|---|
| 0x00 | LED Brightness | 0-31 | Masked with 0x1F |
| 0x01 | LED Red | 0-255 | RGB component |
| 0x02 | LED Green | 0-255 | RGB component |
| 0x03 | LED Blue | 0-255 | RGB component |
| 0x04 | Fan Speed | 0-100 | Percentage |
When: Every 5 seconds (called from scheduler_env_ready())
Why:
- Protects against power loss
- Reduces EEPROM wear (~1M cycle limit)
- Max 5s data loss window
Function:
static void save_actuators_state(void) {
eeprom_write(0x00, current_led_brightness);
eeprom_write(0x01, current_led_r);
eeprom_write(0x02, current_led_g);
eeprom_write(0x03, current_led_b);
eeprom_write(0x04, current_fan_speed);
}- Read all 5 addresses
- Check if empty (
0xFFindicates first boot) - If empty: use defaults (white LED, fan off)
- If valid: apply saved configuration
- IDE: MPLAB X v6.0 or later
- Compiler: XC8 v2.40 or later
- Programmer: PICkit 3/4 or compatible
- Open project in MPLAB X
- Select device: PIC16F886
- Configure toolchain: XC8
- Build (Production):
Ctrl+Shift+F11 - Verify configuration bits match
main.cpragmas
#pragma config FOSC = HS // Crystal 20 MHz
#pragma config WDTE = OFF // Watchdog disabled
#pragma config PWRTE = ON // Power-up timer enabled
#pragma config MCLRE = ON // MCLR enabled
#pragma config BOREN = ON // Brown-out reset enabled
#pragma config LVP = OFF // Low-voltage programming disabledBefore powering on:
- ☐ 20 MHz crystal between OSC1/OSC2 with 15-33pF capacitors
- ☐ MCLR pulled up to VDD with 10kΩ
- ☐ VDD = 5V stable (100nF decoupling capacitor)
- ☐ I2C pull-ups (4.7kΩ on RC3/RC4)
- ☐ UART connected to SMA-COMP (TX→RX, RX→TX, GND common)
| Symptom | Cause | Solution |
|---|---|---|
| No UART output | Crystal not oscillating | Verify crystal, capacitors, FOSC=HS |
| UART sends garbage | Baudrate mismatch | Verify SPBRG=129, _XTAL_FREQ=20MHz |
| I2C sensors don't respond | Missing pull-ups | Add 4.7kΩ on RC3 (SCL) and RC4 (SDA) |
| CO₂ always reads 0xFFFF | Sensor in warmup | Wait 5 minutes after power-on |
| Fan doesn't run | Pin not configured | Verify TRISC2=0, CCP1CON=0x0C |
| LEDs don't light | Wrong SPI pins | Verify RC0 (CLK), RC1 (SDO) connections |
| Random resets | Brown-out | Check 5V supply stable, BOREN=ON |
| EEPROM data lost | Not saving | Verify save_actuators_state() called every 5s |
Startup Test Frames (100ms after boot):
AA 04 00 00 00 00 → CMD_NOISE = 0
AA 04 06 19 00 00 → CMD_TEMP = 25°C
Purpose: Verify UART TX works immediately after boot.
LED Activity Indicator:
- Green flash (50ms) when valid command received
- Indicates: UART RX works + protocol parsing OK
- Connect terminal @ 9600 8N1 to RC6 (TX)
- Power on PIC
- Should see 2 frames within 200ms
- Every 1s: CMD_NOISE frame
- Every 5s: 4 sensor frames (TEMP, HUM, LUX, CO2)
If no output → hardware issue (crystal, power, connections).
| Metric | Value | Notes |
|---|---|---|
| CPU Usage | ~15% | ISR: 2%, Main loop: 13% |
| RAM Usage | ~200 bytes | 54% of 368 bytes |
| Flash Usage | ~5-8 KB | 60-100% of 8KB (depends on optimization) |
| EEPROM Writes | 5 writes/5s | ~100k cycles = 139 hours continuous |
| ISR Frequency | 110 Hz | Timer0: 100 Hz, Timer1: 10 Hz |
| Worst ISR Latency | <50 µs | Including noise sampling |
- Brown-Out Reset: Detects low voltage, resets safely
- UART Timeout: Prevents infinite blocking (100ms)
- EEPROM Wear Leveling: Saves every 5s (not every command)
- Watchdog: Disabled (system is deterministic)
- Interrupt Priority: Timers only (no nesting issues)
For detailed technical information, refer to the README in each module folder:
- lib/README.md - Official library API reference and integration guidelines
- hal/README.md - Complete HAL architecture, register configurations, timing analysis
- sensor/README.md - Sensor datasheets, calibration procedures, wiring diagrams
- actuador/README.md - Actuator control algorithms, safety limits, performance specs
- protocol/README.md - Protocol specification, frame format, CRC implementation, examples
- test/README.md - Test procedures, expected outputs, debugging guide
- REGISTERS_MAP.md - Complete PIC16F886 register reference with bit definitions
- PIC16F886 Datasheet - DS41291E (official Microchip documentation)
- XC8 Compiler Guide - Microchip XC8 C Compiler User's Guide
- I2C Specification - I²C-bus specification and user manual (NXP)
- SPI Protocol - Serial Peripheral Interface documentation
| Version | Date | Changes |
|---|---|---|
| 1.0 | Dec 2025 | Initial production release |
| - Modular layered architecture (lib/hal/sensor/actuador/protocol) | ||
| - CO₂ sensor warmup handling (5-minute automatic) | ||
| - EEPROM persistence (auto-save every 5s) | ||
| - CRC-8 error detection in UART protocol | ||
| - Complete test suite (9 standalone tests) | ||
| - Comprehensive documentation structure |
Project: SMA-LAMP (LMDE-MA)
Institution: Universidad Politécnica de Madrid
Course: Sistemas Empotrados (Embedded Systems)
Academic Year: 2024-2025
Semester: 1st Half - 4th Year
For issues or questions:
- Check the Troubleshooting section in this README
- Consult module-specific README files for detailed information
- Refer to PIC16F886 datasheet (DS41291E) for hardware details
- Review test programs in
test/folder for working examples
This is an academic project. If you're working on modifications:
- Maintain the layered architecture (lib → hal → sensor/actuador → protocol → main)
- Never modify
lib/folder (vendor code) - Document all changes in module-specific README files
- Create test programs for new functionality
- Update REGISTERS_MAP.md if adding new peripheral usage
This project is licensed under the MIT License. See the LICENSE file for details.
For issues, questions, or contributions, please open an issue on the GitHub repository.
Version: 0.1.0
Last Updated: December 2025