Table of Contents

Controlling the jPC's Electro-Mechanical Buzzer

The jPC has an electro-mechanical buzzer attached to one of the SoC's PWMs.

Generating Audible Feedback from The Terminal

Use the beep program to generate audible feedback for a specific frequency and duration: e.g. beep -f 2000 -l 100.

Generating Audible Feedback Programmatically

Write EV_SND events to device /dev/input/by-path/platform-buzzer-event to generate audible feedback in your programs. The following example generates a siren sound on the buzzer. See the Linux input subsystem for more information.

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
 
class Siren
{
    static void Main(string[] args)
    {
        Console.CancelKeyPress += (sender, e) =>
        {
            e.Cancel = true;
            exiting = true;
            Console.WriteLine("\nCtrl+C received. Stopping siren...");
        };
 
        const string eventPath = "/dev/input/by-path/platform-buzzer-event";
 
        Console.WriteLine("Siren generator running. Press Ctrl+C to stop.");
 
        double time = 0;
        double step = 0.02; // 50 Hz update rate (20ms)
 
        FileStream? fs = null;
 
        try
        {
            fs = new FileStream(eventPath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
 
            while (!exiting)
            {
                // Siren: 500 Hz to 1500 Hz and back in 2 seconds
                double cycleTime = time % 2.0;
                double frequency;
 
                if (cycleTime < 1.0)
                    frequency = 500 + (cycleTime * 1000);          // Rise
                else
                    frequency = 1500 - ((cycleTime - 1.0) * 1000); // Fall
 
                int hz = (int)Math.Round(frequency);
 
                SendTone(fs, hz);
 
                Thread.Sleep((int)(step * 1000));
                time += step;
            }
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex.Message);
        }
        finally
        {
            // Ensure buzzer is silenced
            try
            {
                if (fs != null)
                {
                    SendTone(fs, 0);
                    fs.Dispose();
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.Message);
            }
 
            Console.WriteLine("Siren stopped.");
        }
    }
 
    // Linux input event constants
    const ushort EV_SYN = 0x00;
    const ushort EV_SND = 0x12;
    const ushort SYN_REPORT = 0x00;
    const ushort SND_BELL = 0x01;
    const ushort SND_TONE = 0x02;
 
    static bool exiting = false;
 
    // input_event struct from <linux/input.h>
    // struct timeval { long tv_sec; long tv_usec; };
    // struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value; };
    [StructLayout(LayoutKind.Sequential)]
    struct TimeVal
    {
        public long tv_sec;
        public long tv_usec;
    }
 
    [StructLayout(LayoutKind.Sequential)]
    struct InputEvent
    {
        public TimeVal time;
        public ushort type;
        public ushort code;
        public int value;
    }
 
    // Compute once to avoid repeated reflection-ish cost
    static readonly int InputEventSize = Marshal.SizeOf<InputEvent>();
 
    static void SendTone(FileStream fs, int hz)
    {
        // hz > 0 => play tone at hz
        // hz == 0 => stop tone
        WriteEvent(fs, EV_SND, SND_TONE, hz);
        WriteEvent(fs, EV_SYN, SYN_REPORT, 0);
        fs.Flush();
    }
 
    static void WriteEvent(FileStream fs, ushort type, ushort code, int value)
    {
        // Local struct lives on the stack (no heap allocation)
        InputEvent ev = new InputEvent
        {
            time = default, // kernel will timestamp; zeroed is fine
            type = type,
            code = code,
            value = value
        };
 
        // Stack-allocated byte buffer
        Span<byte> buffer = stackalloc byte[InputEventSize];
 
        // Copy struct bytes into the stack buffer
        MemoryMarshal.Write(buffer, in ev);
 
        // Write without allocating
        fs.Write(buffer);
    }
}