Skip to content

Repository files navigation

SecurPic32Cam

Leer en español

License: MIT MCU Compiler Camera

A pan-and-tilt security camera served over Ethernet by a PIC32MX230F064D with 16 KB of RAM, no RTOS, no heap, and a hand-written TCP/IP stack — ARP, ICMP and TCP/80 are parsed byte by byte from raw Ethernet frames. Video comes from an ESP32-CAM acting as a dumb serial peripheral, and reaches the browser as an MJPEG stream.

Enclosure with the camera on its pan servo

Demo

The browser on 192.168.60.240 showing the live stream, the servo panning on command, the alarm going off, and the flash LED kicking in as night vision.

demo.mp4

Features

  • Web server written from scratch — no lwIP, no Microchip TCP/IP stack. tcpip.c handles ARP replies, ICMP echo, and the TCP handshake/segmentation for port 80 directly on top of the ENC28J60 driver.
  • MJPEG live stream (GET /stream) over a persistent multipart/x-mixed-replace connection, plus single-shot JPEG capture (GET /photo).
  • Servo pan with manual steps and a continuous auto-sweep, driven entirely from the Timer2 ISR.
  • Automatic night vision — an LDR on the ADC triggers the ESP32-CAM's on-board flash LED with hysteresis, with no involvement from the main loop.
  • Alarm — an ambulance-style two-tone siren generated by bit-banging a GPIO from the Timer4 ISR, with a synchronised blinking LED.
  • Serial command console on UART2 — move the servo and fire the alarm without a browser.
  • Single-page dark UI compiled into flash as one C macro (webpage.h).
Inside the box Lid open
Breadboard, ENC28J60 and PIC32 board Camera assembly above, electronics below

Left: the breadboard, the ENC28J60 module and the PIC32 board, powered from a bench supply. Right: the lid carries the camera and its servo; everything else lives underneath.

Architecture

flowchart LR
    B["Browser"]
    E["ENC28J60<br/>10 Mbps Ethernet"]
    P["PIC32MX230F064D<br/>40 MHz · 16 KB RAM"]
    C["ESP32-CAM<br/>OV2640 · QQVGA"]

    B <-->|"HTTP :80 · MJPEG"| E
    E <-->|"SPI1 @ 500 kHz"| P
    P <-->|"UART1 @ 250000 baud"| C

    P --- S["Servo<br/>OC1 · Timer2 @ 50 Hz"]
    P --- L["LDR<br/>AN4 · Timer3 @ 100 Hz"]
    P --- Z["Buzzer + LED<br/>Timer4 siren"]
    P --- D["Debug console<br/>UART2 @ 9600"]
Loading

main.c initialises every peripheral and then does exactly one thing forever: poll the ENC28J60 for packets and hand them to tcpip.c. The servo, the light sensor and the siren all run from their own timer interrupts and never touch the main loop.

Source map

File Responsibility
main.c Peripheral init, network polling loop, periodic ENC28J60 diagnostics
tcpip.c / .h Ethernet frame parsing, ARP, ICMP, TCP/80, MJPEG stream state, 1400-byte segmentation
http_server.c / .h Routes the control endpoints and /status; serves the HTML page
webpage.h The whole UI as one WEBPAGE_HTML macro (HTTP headers included)
enc28j60.c / .h ENC28J60 register/PHY access, bank switching, packet TX/RX, errata B7 workaround
spi.c / .h SPI1 master (mode 0,0) and manual chip-select helpers
camera.c / .h Blocking UART1 request/response driver for the ESP32-CAM
ADC.c / .h Timer3-triggered LDR sampling; hysteresis drives the camera's flash LED
PWM.c / .h OC1 servo positioning and Timer2-driven auto-sweep
buzzer.c / .h Timer4 two-tone siren plus the alarm LED
UART1.c / .h UART1 to the ESP32-CAM, 256-byte circular TX/RX queues
UART2.c / .h UART2 debug console; its RX ISR is also the command interpreter
Pic32Ini.c / .h Device configuration pragmas (oscillator, PLL, watchdog)
esp32-cam/code/code.ino ESP32-CAM sketch — serial only, no WiFi

Hardware

Bill of materials

  • PIC32MX230F064D board
  • ENC28J60 Ethernet module (SPI, 3.3 V)
  • ESP32-CAM, AI Thinker variant with OV2640
  • Micro servo (SG90 class)
  • LDR in a voltage divider
  • Buzzer, LED + resistor
  • USB–serial adapter for the debug console
  • PICkit 3 (or equivalent) to program the PIC32

Pinout

Taken from the source, not from the original design sketch.

Function PIC32 pin Notes
ENC28J60 CS RC8 Plain GPIO, toggled by hand (spi.c)
ENC28J60 SDO1 (MOSI) RC6 PPS RPC6R = 3
ENC28J60 SCK1 RB14 Fixed pin
ENC28J60 SDI1 (MISO) RA1 PPS SDI1R = 0
Servo signal RB15 PPS RPB15R = 5 → OC1
LDR RB2 / AN4 Analog input
UART1 TX → ESP32 RX RB7 PPS RPB7R = 1
UART1 RX ← ESP32 TX RB13 PPS U1RXR = 3
UART2 TX → PC RC9 PPS RPC9R = 2
UART2 RX ← PC RB8 PPS U2RXR = 4
Buzzer RC5 Toggled from the Timer4 ISR
Alarm LED RB3
Night-vision LED GPIO 4 On the ESP32-CAM board

Clocking and timing

  • 8 MHz crystal → ÷2 → PLL ×20 → ÷2 = 40 MHz system clock; FPBDIV = DIV_85 MHz PBCLK.
  • SPI1 at 500 kHz (SPI1BRG = 4).
  • Timer2 at 50 Hz (20 ms) — servo PWM base. Pulse range 0.5–2.5 ms (OC1RS 1250–6250) ≈ 180°. A manual step is 20°; the auto-sweep advances 5° every 500 ms (≈18 s end to end).
  • Timer3 at 100 Hz — ADC auto-sampling trigger.
  • Timer4 — siren: alternates a 1201 Hz and a 700 Hz square wave every ~500 ms.
  • The ESP32-CAM needs its own 5 V supply; it draws well over what the PIC32 board can source.

Network and HTTP API

Both the MAC and the IP are compile-time constants in main.c:

uint8_t mi_mac[6] = {0x00, 0x14, 0xA5, 0x76, 0x19, 0x3F};
uint8_t mi_ip[4]  = {192, 168, 60, 240};

There is no DHCP and no gateway logic — put the board on the same subnet as your PC, or edit those two lines. The server listens on port 80.

Route Effect
GET / The HTML page from webpage.h
GET /stream MJPEG, multipart/x-mixed-replace;boundary=frame, keep-alive
GET /photo One JPEG with Content-Length; 503 if the capture fails
GET /status {"a":…,"b":…,"c":…,"d":…} — CSS class and text for the light and alarm badges
GET /left · GET /right Step the servo one position
GET /sweep Toggle the auto-sweep
GET /alarm Toggle the siren

The page polls /status every 2 s from JavaScript; the control buttons are plain fetch() calls. ARP requests are answered and ICMP echo requests are replied to, so the board pings.

Camera protocol

The ESP32-CAM never joins a WiFi network. It is a serial peripheral: it waits for a command byte and answers with a framed JPEG at 250000 baud.

PIC32 → ESP32   'C'          capture a frame
                'O' / 'F'    flash LED on / off

ESP32 → PIC32   0xFF 0xAA │ length (4 B, big-endian) │ JPEG bytes │ 0xFF 0xBB

The JPEG is sent in 128-byte chunks, and the ESP32 checks for pending O/F commands between chunks so night vision still responds while a frame is in flight. On the PIC32 side, Camera_CapturarImagen() disables the UART1 RX interrupt and polls U1RXREG directly, writing straight into cam_img_buf at offset CAM_HEADER_RESERVE, so tcpip.c can build the HTTP headers in that same buffer and just memmove the JPEG up against them — no second buffer needed.

Frames are QQVGA (160×120) at jpeg_quality = 10, which keeps them under the 4000-byte cap (CAM_IMG_MAX in camera.h).

Building and flashing

PIC32

Open the project in MPLAB X (XC32 v4.60, PICkit 3) and build. A Makefile is checked in, but it includes nbproject/Makefile-impl.mk and nbproject/Makefile-variables.mk, which are generated by the IDE and deliberately gitignored — make will not work on a fresh clone until you have opened the project in MPLAB X once.

ESP32-CAM

Arduino IDE, board AI Thinker ESP32-CAM, upload esp32-cam/code/code.ino. Requires the esp32 core (the sketch uses esp_camera.h). Remember to bridge IO0 to GND while flashing.

Verifying

There is no automated test suite. Flash both boards, then:

  1. Watch UART2 at 9600 baud — you should see Inicio MAC. Revision ENC28J60: <n>. A revision of 0 or 255 means the SPI wiring is wrong, and the firmware says so.
  2. ping 192.168.60.240
  3. Open http://192.168.60.240/ in a browser.

Configuration

What Where
MAC / IP main.cmi_mac, mi_ip
Light thresholds ADC.cLUZ_UMBRAL_ON 462, LUZ_UMBRAL_OFF 562
Max JPEG size camera.hCAM_IMG_MAX
Servo range and step PWM.cOC1_MIN, OC1_MAX, POS_STEP
Siren tones buzzer.cPR4_HIGH, PR4_LOW
The web page itself webpage.h — must fit in http_response[2900]

Debug console (UART2 @ 9600)

The UART2 RX interrupt doubles as a command interpreter, so you can drive the hardware from a serial terminal with no network at all:

Key Action
R / r Pan right
L / l Pan left
S / s Toggle auto-sweep
A / a Toggle the alarm

Anything else is ignored. The same port carries the firmware's log output.

Design notes

With 16 KB of RAM, every buffer is accounted for and there is no malloc anywhere:

Buffer Size Where
buffer_red 1500 B main.c — Ethernet RX scratch
tcp_tx_buf 1500 B tcpip.c — TX frame assembly
http_response 2900 B tcpip.c — rendered HTTP response
cam_img_buf 4150 B camera.c — 150 B header space + 4000 B JPEG

One detail worth calling out: the main loop rate-limits its SPI polling when no stream is active (main.c). Hammering the SPI bus thousands of times a second radiated enough EMI to couple audible noise into the buzzer line.

Known limitations

  • /status classifies the light with a fixed threshold of 512, while the LED itself uses hysteresis at 462/562. Inside that dead band the badge in the browser can disagree with the actual LED.
  • Camera_CapturarImagen() blocks for roughly 600 ms, so the stream frame rate is low by design.
  • One TCP client at a time. There is no retransmission, no reassembly of segmented requests, and no connection timeout — a client that disappears without FIN or RST leaves the stream flag set.
  • The Makefile cannot build a fresh clone on its own (see above).

Authors

Marcos Garijo Software, firmware and the approach to solving the problem — LinkedIn · GitHub
Jorge Abella Hardware selection and the physical build — LinkedIn · GitHub

Credits

Built as a microcontrollers coursework project. Pic32Ini.c comes from a course template. The original design document is kept at docs/wireframe.md for reference — note that the final implementation diverges from it.

License

MIT

About

Ethernet security camera on a PIC32MX230F064D with a hand-written TCP/IP stack and an ESP32-CAM

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages