Saturday, July 18, 2026

CUDA Hello World - Gain Experience of 10 Years

If you already know the fundamentals from your earlier work with Nsight Eclipse. A lot has changed since then, and your current setup is far better going by current standards.

The IDE situation in 2026

About 10 years back, you needed Nsight Eclipse because it was the only CUDA-aware IDE. That's no longer true. VS Code is now the go-to for CUDA development — and you already have it installed 

Nsight Eclipse is Linux-only and essentially deprecated for Windows. On Windows, NVIDIA moved to Nsight Visual Studio Edition (integrates into Visual Studio) or the standalone Nsight Systems/Compute profilers — which you already have Nsight Compute installed.

Open a terminal and run:

cmd
nvcc --version

Already confirmed ✓. Now try compiling a tiny .cu file:

cuda
// hello.cu
#include <stdio.h>

__global__ void hello() {
    printf("Hello from thread %d, block %d\n", threadIdx.x, blockIdx.x);
}

int main() {
    hello<<<2, 4>>>();
    cudaDeviceSynchronize();
    return 0;
}

Save it anywhere and compile with:

cmd
    nvcc hello.cu -o hello.exe
    hello.exe

If you see 8 lines printing (2 blocks × 4 threads), your entire chain — driver, CUDA toolkit, nvcc, and GPU — is working end to end. That's your "Hello World" moment, much more meaningful than the bit-reverse template because you'll understand exactly why 8 lines print. BIT REVERSE TEMPLATE DEMO


What's different from 2016

2016 (your 410M)2026 (your GTX 1650)
CUDA 5.5, CC 2.1        CUDA 12.2, CC 7.5
Nsight Eclipse on Ubuntu        VS Code + nvcc on Windows
No Tensor Cores        Tensor Cores available
~192 CUDA cores        896 CUDA cores
No printf in kernels easily     printf from device works natively

The printf inside the kernel above would not have worked cleanly on your old 410M — it's a nice demonstration that your new hardware is a proper leap forward.

C:\Users\usrname\OneDrive\Documents\GitHub\LearningC>nvcc helloWorld.cu -o helloWorld.exe

helloWorld.cu

tmpxft_00001d74_00000000-10_helloWorld.cudafe1.cpp

   Creating library helloWorld.lib and object helloWorld.exp


C:\Users\usrname\OneDrive\Documents\GitHub\LearningC>helloWorld.exe

Hello from thread 0, block 0

Hello from thread 1, block 0

Hello from thread 2, block 0

Hello from thread 3, block 0

Hello from thread 0, block 1

Hello from thread 1, block 1

Hello from thread 2, block 1

Hello from thread 3, block 1


Let's Go!!

got 8 hello messages

That's your GPU running code you wrote. 8 lines = 2 blocks × 4 threads, all executing in parallel on the GTX 1650.

Notice what just happened in that launch syntax:

cuda
hello<<<2, 4>>>();

Those angle brackets are CUDA's extension to C++ — not standard C, not anything your MinGW or MSVC understands alone. Only nvcc parses them. The two numbers are:

  • 2 → number of blocks in the grid
  • 4 → number of threads per block

So total threads = 2 × 4 = 8. Each thread ran the same hello() function, but with a different threadIdx.x and blockIdx.x — that's how every thread knows who it is and what data to work on.

Now let's make it real — Lesson 2: Vector Addition

This is the "Hello World" of parallel computing that actually means something. You'll add two arrays of 1 million numbers simultaneously on the GPU.

This exercise teaches you the four fundamental steps every CUDA program ever written follows:

1. Allocate memory on GPU        (cudaMalloc)
2. Copy data CPU → GPU           (cudaMemcpy)
3. Launch kernel                 (kernel<<<blocks, threads>>>)
4. Copy result GPU → CPU         (cudaMemcpy back)

Create a new file vector_add.cu and type this out (don't copy-paste — typing it builds muscle memory):

#include <stdio.h>

#include <stdlib.h>


// DEVICE function — runs on GPU

// Each thread adds ONE pair of elements

__global__ void vectorAdd(float *a, float *b, float *c, int n) {

    // Calculate this thread's unique index

    int i = blockIdx.x * blockDim.x + threadIdx.x;


    // Guard: don't go out of bounds

    if (i < n) {

        c[i] = a[i] + b[i];

    }

}


int main() {

    int n = 1 << 20;          // 1,048,576 elements (1M)

    size_t bytes = n * sizeof(float);


    // --- HOST memory (CPU RAM) ---

    float *h_a = (float*)malloc(bytes);

    float *h_b = (float*)malloc(bytes);

    float *h_c = (float*)malloc(bytes);


    // Fill input arrays

    for (int i = 0; i < n; i++) {

        h_a[i] = (float)i;

        h_b[i] = (float)(i * 2);

    }


    // --- DEVICE memory (GPU VRAM) ---

    float *d_a, *d_b, *d_c;

    cudaMalloc(&d_a, bytes);

    cudaMalloc(&d_b, bytes);

    cudaMalloc(&d_c, bytes);


    // Step 2: Copy CPU → GPU

    cudaMemcpy(d_a, h_a, bytes, cudaMemcpyHostToDevice);

    cudaMemcpy(d_b, h_b, bytes, cudaMemcpyHostToDevice);


    // Step 3: Launch kernel

    int threads = 256;

    int blocks  = (n + threads - 1) / threads;   // ceiling division

    vectorAdd<<<blocks, threads>>>(d_a, d_b, d_c, n);


    // Step 4: Copy GPU → CPU

    cudaMemcpy(h_c, d_c, bytes, cudaMemcpyDeviceToHost);


    // Verify first 5 and last 5 results

    printf("First 5 results:\n");

    for (int i = 0; i < 5; i++)

        printf("  c[%d] = %.0f  (expected %.0f)\n", i, h_c[i], h_a[i] + h_b[i]);


    printf("Last 5 results:\n");

    for (int i = n-5; i < n; i++)

        printf("  c[%d] = %.0f  (expected %.0f)\n", i, h_c[i], h_a[i] + h_b[i]);


    // Cleanup

    cudaFree(d_a); cudaFree(d_b); cudaFree(d_c);

    free(h_a); free(h_b); free(h_c);


    printf("\nDone. %d elements added in parallel.\n", n);

    return 0;

}

Compile and run:

cmd
nvcc vector_add.cu -o vector_add.exe
vector_add.exe

The one line to understand deeply

cuda
int i = blockIdx.x * blockDim.x + threadIdx.x;

This is the most important line in all of CUDA. Every thread computes its own unique global index from three built-in variables:

VariableMeaningIn your launch
blockIdx.xWhich block am I in?0 to 4095
blockDim.xHow many threads per block?256
threadIdx.xWhich thread within my block?0 to 255

So thread 0 of block 3 gets index 3 * 256 + 0 = 768 — it adds a[768] + b[768]. Thread 1 of block 3 gets index 769. And so on. Every thread handles exactly one element, all simultaneously.

Run it and share the output — then we'll talk about why blocks = (n + threads - 1) / threads and what happens if you skip the if (i < n) guard.

Output:

C:\Users\usrname\OneDrive\Documents\GitHub\LearningC>nvcc vector_add.cu -o vector_add.exe

vector_add.cu

tmpxft_00003078_00000000-10_vector_add.cudafe1.cpp

   Creating library vector_add.lib and object vector_add.exp

C:\Users\usrname\OneDrive\Documents\GitHub\LearningC>vector_add.exe

First 5 results:

  c[0] = 0  (expected 0)

  c[1] = 3  (expected 3)

  c[2] = 6  (expected 6)

  c[3] = 9  (expected 9)

  c[4] = 12  (expected 12)

Last 5 results:

  c[1048571] = 3145713  (expected 3145713)

  c[1048572] = 3145716  (expected 3145716)

  c[1048573] = 3145719  (expected 3145719)

  c[1048574] = 3145722  (expected 3145722)

  c[1048575] = 3145725  (expected 3145725)

Done. 1048576 elements added in parallel.

Friday, June 12, 2026

Building an IoT Bus Passenger Counter in Public Transport

 

Building an IoT Passenger Counter

ESP32 · MQTT · Express.js · React

A complete step-by-step guide for Windows

Overview

This guide walks you through building a real-time passenger counter using a single IR sensor connected to an ESP32 microcontroller. Sensor events are published over WiFi using the MQTT protocol to a Mosquitto broker running on Windows. An Express.js backend subscribes to the broker and pushes live updates to a React dashboard via WebSocket.

 

Component

Role

ESP32 + IR sensor

Detects beam breaks, publishes count

Mosquitto broker

MQTT message router (Windows service)

Express.js server

Subscribes to MQTT, serves REST + WebSocket

React (Vite)

Live dashboard — updates without page refresh

 

Part 1 — USB Driver Installation

The ESP32 communicates with your PC over USB via a USB-to-UART bridge chip. You must install the correct driver before Windows can detect the board.

 

Step 1 — Identify the bridge chip

Look at your ESP32 board near the USB connector. The chip will be labelled with one of the following:

 

Chip marking

Driver needed

CP2102 or CP2104

Silicon Labs CP210x VCP

CH340 or CH341

WCH CH340 driver

FT232 or FT231

FTDI VCP driver

 

Step 2 — Download and install the Silicon Labs CP210x driver

1.     Open your browser and go to: www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers

2.     Under Downloads, select CP210x Windows Drivers and download the ZIP file.

3.     Extract the ZIP. Right-click CP210xVCPInstaller_x64.exe and choose Run as administrator.

4.     Follow the installer prompts and click Finish.

5.     Plug in your ESP32 via USB.

6.     Open Device Manager (Win + X → Device Manager). You should see Silicon Labs CP210x USB to UART Bridge (COMx) under Ports (COM & LPT).

7.     Note the COM port number — you will need it in Arduino IDE.

 

  If your board uses a CH340 chip instead, download the driver from: www.wch-ic.com/downloads/CH341SER_EXE.html — the installation steps are identical.

 

Part 2 — Arduino IDE Setup

Step 1 — Download and install Arduino IDE

8.     Go to www.arduino.cc/en/software

9.     Download the Windows installer (.exe).

10.  Run the installer. Accept the license, keep default options, click Install.

11.  Launch Arduino IDE once installation is complete.

 

Step 2 — Add ESP32 board support

12.  In Arduino IDE go to File → Preferences.

13.  Find the Additional Board Manager URLs field and paste:

 

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

 

14.  Click OK.

15.  Go to Tools → Board → Boards Manager.

16.  Search for esp32 and install the package by Espressif Systems.

17.  Once installed, go to Tools → Board → ESP32 Arduino → ESP32 Dev Module.

18.  Set Tools → Port to the COM port you noted during driver installation.

19.  Set Tools → Upload Speed to 115200 for reliable uploads.

 

ℹ  If upload fails, hold the BOOT button on the ESP32 as soon as you see Connecting... in the Arduino IDE console, then release once uploading begins.

 

Step 3 — Install required libraries

Go to Sketch → Include Library → Manage Libraries and install each of the following:

 

Library

Purpose

PubSubClient by Nick O'Leary

MQTT publish and subscribe for ESP32

ArduinoJson by Benoit Blanchon

JSON serialization for MQTT payloads

 

Search the library name, click the result, then click Install.

 

Part 3 — IR Sensor Wiring

Components

        ESP32 Dev Module

        1× IR obstacle sensor module (with OUT, VCC, GND pins)

        Breadboard and male-to-male jumper wires

 

Connections

IR sensor pin

Connect to ESP32

VCC

3.3V or 5V (check your module — most accept both)

GND

GND

OUT

GPIO 34

 

ℹ  GPIO 34 is input-only on the ESP32 — it has no internal pull-up. If your readings are noisy, wire a 10kΩ resistor between GPIO 34 and 3.3V.

 

Part 4 — ESP32 Firmware

Create a new sketch in Arduino IDE (File → New), paste the code below, update your WiFi credentials and broker IP, then upload.

 

Find your PC IP address

Open Command Prompt and run:

 

ipconfig

 

Look for IPv4 Address under your WiFi adapter. Example: 192.168.1.105. Use this as mqttServer in the sketch.

 

sketch.ino — paste into Arduino IDE

#include <WiFi.h>

#include <PubSubClient.h>

 

#define IR_PIN 34

 

const char* ssid       = "YOUR_WIFI_SSID";

const char* password   = "YOUR_WIFI_PASSWORD";

const char* mqttServer = "192.168.1.105";  // your PC IPv4

const int   mqttPort   = 1883;

const char* topic      = "bus/passengers";

 

WiFiClient   wifiClient;

PubSubClient mqtt(wifiClient);

 

int  obstacleCount = 0;

bool lastState     = false;

 

void setup() {

  Serial.begin(115200);

  pinMode(IR_PIN, INPUT);

 

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) delay(500);

  Serial.println("WiFi connected");

 

  mqtt.setServer(mqttServer, mqttPort);

  while (!mqtt.connected()) {

    mqtt.connect("esp32-bus");

    delay(500);

  }

  Serial.println("MQTT connected");

}

 

void loop() {

  mqtt.loop();

 

  bool beamBroken = digitalRead(IR_PIN) == LOW;

 

  // Rising edge detection — count only on the moment beam first breaks

  if (beamBroken && !lastState) {

    obstacleCount++;

    Serial.print("Obstacle count: ");

    Serial.println(obstacleCount);

 

    String payload = "{\"count\":" + String(obstacleCount)

                   + ",\"busId\":\"BUS_01\"}";

    mqtt.publish(topic, payload.c_str());

  }

 

  lastState = beamBroken;

  delay(50);  // debounce

}

 

After uploading, open Tools → Serial Monitor at 115200 baud. You should see WiFi connected followed by MQTT connected. Wave your hand in front of the IR sensor — the count increments and is published to bus/passengers.

 

Part 5 — Mosquitto MQTT Broker on Windows

Step 1 — Install Mosquitto

20.  Go to mosquitto.org/download

21.  Download the Windows installer (.exe).

22.  Run the installer with default options.

 

Step 2 — Create a config file

Create the following folder and config file:

 

C:\mosquitto\mosquitto.conf

 

Paste this content into the file:

 

# Network

listener 1883

protocol mqtt

 

# Allow anonymous connections (fine for local dev)

allow_anonymous true

 

# Logging

log_type all

log_dest file C:\mosquitto\mosquitto.log

log_timestamp true

 

# Persistence

persistence true

persistence_location C:\mosquitto\data\

 

Create the data folder:

 

md C:\mosquitto\data

 

Step 3 — Open port 1883 in Windows Firewall

netsh advfirewall firewall add rule name="Mosquitto MQTT" ^

  dir=in action=allow protocol=TCP localport=1883

 

Step 4 — Stop any existing Mosquitto service

net stop mosquitto

 

Step 5 — Run Mosquitto with your config

cd "C:\Program Files\mosquitto"

mosquitto.exe -c C:\mosquitto\mosquitto.conf -v

 

You should see: Opening ipv4 listen socket on port 1883.

 

Step 6 — Verify the broker

Open a second Command Prompt:

 

mosquitto_sub.exe -h localhost -t "bus/passengers" -v

 

Open a third Command Prompt and publish a test message:

 

mosquitto_pub.exe -h localhost -t "bus/passengers" -m "{\"count\":1}"

 

The first terminal should display the message. Broker is confirmed working.

 

ℹ  MQTT Explorer (free GUI at mqtt-explorer.com) is highly recommended — connect it to localhost:1883 to visually inspect all topics and messages without using the terminal.

 

Part 6 — Express.js Backend

Step 1 — Create the project (nodejs needs to be installed in the system)

md bus-counter && cd bus-counter

md server && cd server

npm init -y

npm install express mqtt socket.io cors

 

Step 2 — Create server.js

server/server.js

const express    = require('express');

const http       = require('http');

const { Server } = require('socket.io');

const mqtt       = require('mqtt');

const cors       = require('cors');

 

const app    = express();

const server = http.createServer(app);

const io     = new Server(server, { cors: { origin: '*' } });

 

app.use(cors());

app.use(express.json());

 

let passengerCount = 0;

let history = [];

 

const mqttClient = mqtt.connect('mqtt://localhost:1883');

 

mqttClient.on('connect', () => {

  console.log('MQTT connected');

  mqttClient.subscribe('bus/passengers');

});

 

mqttClient.on('message', (topic, message) => {

  const data = JSON.parse(message.toString());

  passengerCount = data.count;

 

  const entry = { ...data, timestamp: new Date().toISOString() };

  history.push(entry);

  if (history.length > 100) history.shift();

 

  console.log('Received:', entry);

  io.emit('passengerUpdate', entry);

});

 

app.get('/passengers', (req, res) => {

  res.json({ count: passengerCount, busId: 'BUS_01' });

});

 

app.get('/history', (req, res) => {

  res.json(history);

});

 

server.listen(3001, () =>

  console.log('Server running on http://localhost:3001')

);

 

Step 3 — Run the server

node server.js

 

You should see: MQTT connected followed by Server running on http://localhost:3001.

 

Test the REST endpoint by opening http://localhost:3001/passengers in your browser.

 

Part 7 — React Dashboard

Step 1 — Create the Vite + React project

cd ..

npm create vite@latest client -- --template react

cd client

npm install

npm install socket.io-client

 

Step 2 — Replace src/App.jsx

client/src/App.jsx

import { useEffect, useState } from "react";

import { io } from "socket.io-client";

 

const socket = io("http://localhost:3001");

const MAX_CAPACITY = 40;

 

export default function App() {

  const [count, setCount]     = useState(0);

  const [history, setHistory] = useState([]);

  const [status, setStatus]   = useState("connecting");

 

  useEffect(() => {

    fetch("http://localhost:3001/passengers")

      .then(r => r.json())

      .then(d => setCount(d.count));

 

    fetch("http://localhost:3001/history")

      .then(r => r.json())

      .then(setHistory);

 

    socket.on("connect",    () => setStatus("live"));

    socket.on("disconnect", () => setStatus("disconnected"));

    socket.on("passengerUpdate", (data) => {

      setCount(data.count);

      setHistory(prev => [...prev.slice(-99), data]);

    });

 

    return () => socket.off("passengerUpdate");

  }, []);

 

  const pct      = Math.round((count / MAX_CAPACITY) * 100);

  const barColor = pct > 90 ? "#E24B4A" : pct > 70 ? "#EF9F27" : "#1D9E75";

 

  return (

    <div style={{ fontFamily: "sans-serif", maxWidth: 600,

                  margin: "2rem auto", padding: "0 1rem" }}>

 

      <div style={{ display: "flex", justifyContent: "space-between",

                    alignItems: "center", marginBottom: "1.5rem" }}>

        <h1 style={{ fontSize: 20, fontWeight: 500, margin: 0 }}>

          Bus passenger counter

        </h1>

        <span style={{

          fontSize: 12, padding: "4px 10px", borderRadius: 20,

          background: status === "live" ? "#EAF3DE" : "#FCEBEB",

          color:      status === "live" ? "#3B6D11" : "#A32D2D"

        }}>

          {status === "live" ? "● Live" : "○ Disconnected"}

        </span>

      </div>

 

      <div style={{ background: "#f5f5f3", borderRadius: 12,

                    padding: "1.5rem", marginBottom: "1rem", textAlign: "center" }}>

        <p style={{ fontSize: 13, color: "#888", margin: "0 0 8px" }}>

          Passengers on board

        </p>

        <p style={{ fontSize: 64, fontWeight: 500, margin: "0 0 16px",

                    lineHeight: 1 }}>{count}</p>

        <p style={{ fontSize: 13, color: "#888", margin: "0 0 10px" }}>

          of {MAX_CAPACITY} capacity ({pct}%)

        </p>

        <div style={{ background: "#ddd", borderRadius: 6,

                      height: 10, overflow: "hidden" }}>

          <div style={{ width: `${pct}%`, height: "100%",

                        background: barColor, borderRadius: 6,

                        transition: "width 0.4s, background 0.4s" }}/>

        </div>

      </div>

 

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr",

                    gap: 10, marginBottom: "1.5rem" }}>

        {[

          { label: "Status",    value: pct > 90 ? "Full" : pct > 70 ? "Busy" : "Normal" },

          { label: "IR events", value: history.length },

          { label: "Available", value: Math.max(0, MAX_CAPACITY - count) },

        ].map(({ label, value }) => (

          <div key={label} style={{ background: "#f5f5f3",

                                    borderRadius: 8, padding: "0.75rem 1rem" }}>

            <p style={{ fontSize: 12, color: "#888", margin: "0 0 4px" }}>{label}</p>

            <p style={{ fontSize: 20, fontWeight: 500, margin: 0 }}>{value}</p>

          </div>

        ))}

      </div>

 

      <div style={{ border: "0.5px solid #ddd", borderRadius: 10, overflow: "hidden" }}>

        <p style={{ fontSize: 13, fontWeight: 500, padding: "10px 14px",

                    margin: 0, borderBottom: "0.5px solid #ddd" }}>

          Recent IR events

        </p>

        {history.length === 0 && (

          <p style={{ fontSize: 13, color: "#888",

                      padding: "12px 14px", margin: 0 }}>

            Waiting for sensor data...

          </p>

        )}

        {[...history].reverse().slice(0, 8).map((h, i) => (

          <div key={i} style={{ display: "flex", justifyContent: "space-between",

                               padding: "8px 14px",

                               borderBottom: "0.5px solid #eee", fontSize: 13 }}>

            <span>Count: <strong>{h.count}</strong></span>

            <span style={{ color: "#888" }}>

              {new Date(h.timestamp).toLocaleTimeString()}

            </span>

          </div>

        ))}

      </div>

    </div>

  );

}

 

Step 3 — Run the React app

npm run dev

 

Open http://localhost:5173 in your browser. The dashboard connects automatically and updates live whenever the IR sensor fires.

 

Part 8 — Running Everything Together

Open three separate Command Prompt windows and run each command:

 

Terminal

Command

Terminal 1 — MQTT broker

cd "C:\Program Files\mosquitto" && mosquitto.exe -c C:\mosquitto\mosquitto.conf -v

Terminal 2 — Express server

cd bus-counter\server && node server.js

Terminal 3 — React app

cd bus-counter\client && npm run dev

 

Then open http://localhost:5173 in your browser. Wave your hand in front of the IR sensor — the count increments live on the dashboard with no page refresh.

 Pics:




Troubleshooting

Problem

Fix

ESP32 not detected in Device Manager

Install the correct USB driver (CP210x or CH340). Try a different USB cable — many are charge-only with no data lines.

Upload fails with exit status 74

Hold the BOOT button on the ESP32 while clicking Upload. Release once you see progress percentages.

MQTT broker port already in use

Run: net stop mosquitto — a background service is already running on 1883.

ESP32 connects to WiFi but not MQTT

Check Windows Firewall — port 1883 must be open. Verify both devices are on the same WiFi network.

React dashboard shows Disconnected

Ensure Express server is running on port 3001. Check browser console for CORS errors.

Count increments multiple times per pass

Increase the delay(50) debounce value in the sketch to delay(150) or higher.