{"id":15538732,"url":"https://github.com/klaus-tockloth/pwm","last_synced_at":"2026-05-09T09:38:30.957Z","repository":{"id":224314275,"uuid":"762970461","full_name":"Klaus-Tockloth/pwm","owner":"Klaus-Tockloth","description":"Pulse Width Modulation (PWM)","archived":false,"fork":false,"pushed_at":"2024-02-25T08:50:39.000Z","size":1373,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-09T12:36:19.311Z","etag":null,"topics":["golang","linux","pulse-width-modulation","pwm","raspberry-pi"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Klaus-Tockloth.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-02-25T07:47:47.000Z","updated_at":"2024-09-14T08:52:22.000Z","dependencies_parsed_at":"2024-02-25T09:24:47.651Z","dependency_job_id":"49494e2d-0b62-4ff8-b960-7a270debe601","html_url":"https://github.com/Klaus-Tockloth/pwm","commit_stats":null,"previous_names":["klaus-tockloth/pwm"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Klaus-Tockloth%2Fpwm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Klaus-Tockloth%2Fpwm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Klaus-Tockloth%2Fpwm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Klaus-Tockloth%2Fpwm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Klaus-Tockloth","download_url":"https://codeload.github.com/Klaus-Tockloth/pwm/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246113684,"owners_count":20725508,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","linux","pulse-width-modulation","pwm","raspberry-pi"],"created_at":"2024-10-02T12:05:46.740Z","updated_at":"2026-05-09T09:38:30.922Z","avatar_url":"https://github.com/Klaus-Tockloth.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hardware generated Pulse Width Modulation\n\n## General\n\n```text\n     I--------------------I           I----- 3.3 Volt (1)\n     I                    I           I\n-----I                    I-----------I      0.0 Volt (0)\n\n     I------- pulse ------I\n     I------------- period -----------I\n```\n\n## Terminology\n\n**Pulse Width** (PW): Pin/line is on (high, 3.3V) for a specified time.\n\n**Period** (T): Sum of pulse width + space width.\n\n**Duty Cycle** (wikipedia): A duty cycle or power cycle is the fraction of one period in which a signal or system is active. Duty cycle is commonly expressed as a percentage or a ratio. A period is the time it takes for a  signal to complete an on-and-off cycle. As a formula, a duty cycle (%) may be expressed as:\n```text\nD = Duty Cycle, PW = Pulse Width, T = Period\nD = PW / T * 100\n```\n\n**Frequency** (wikipedia): Frequency most often measured in hertz (symbol: Hz), is the number of occurrences of a repeating event per unit of time.\n```text\nf = Frequency, 1 = 1 second, T = Period\nf = 1 / T\n```\n\n**Frequency versus Period:**\n```text\nFrequency  Period\n1 Hz       1 s  (second)\n1 kHz      1 ms (millisecond)\n1 MHz      1 μs (microsecond)\n1 GHz      1 ns (nanosecond)\n```\n\n## Raspberry Pi\n\nThe Raspberry Pi has the option to provide one or two hardware generated PWM channels with a granularity of 1 nanosecond (ns). This must be configured in /boot/config.txt. See /boot/overlays/README for configuration details.\n\n**One PWM channel on default pin GPIO18:**\n```text\ndtoverlay=pwm\n```\n\n**Two PWM channels on default pins GPIO18 and GPIO19:**\n```text\ndtoverlay=pwm-2chan\n```\n\n**Raspberry Pi 5 with two PWM channels configured:**\n```text\n$ pinctrl\n...\n17: no    pd | -- // PIN11/GPIO17 = none\n18: a3    pd | lo // PIN12/GPIO18 = PWM0_CHAN2\n19: a3    pd | lo // PIN35/GPIO19 = PWM0_CHAN3\n20: no    pd | -- // PIN38/GPIO20 = none\n...\n```\n\n**Raspberry Pi 4 with two PWM channels configured:**\n```text\n$ pinctrl\n...\n17: ip    pd | lo // GPIO17 = input\n18: a5    pd | lo // GPIO18 = PWM0_0\n19: a5    pd | lo // GPIO19 = PWM0_1\n20: ip    pd | lo // GPIO20 = input\n...\n```\n\n**Chip and channel naming on Raspberry Pi 4:**\n* chip = pwmchip0\n* channels = 0, 1\n* default pins = GPIO18, GPIO19\n\n**Chip and channel naming on Raspberry Pi 5:**\n* chip = pwmchip2\n* channels = 2, 3\n* default pins = GPIO18, GPIO19\n\n## Linux\n\nLinux provides a simple sysfs interface to use the PWM channels. This can be done interactive or via program. The following shell script (see blink.sh in examples) shows the general usage of the sysfs PWM interface.\n\n```shell\n#!/bin/sh\n# Raspberry Pi 4: chip=pwmchip0, channel=0 on pin=GPIO18, channel=1 on pin=GPIO19\n# Raspberry Pi 5: chip=pwmchip2, channel=2 on pin=GPIO18, channel=3 on pin=GPIO19\n# Linux kernel documentation: www.kernel.org/doc/Documentation/pwm.txt\n\nset -o verbose\n\n# set chip name and channel number\nCHANNEL=2\nCHIP=pwmchip2\nPWM=pwm$CHANNEL\n\n# export channel\necho $CHANNEL \u003e /sys/class/pwm/$CHIP/export\nsleep 1\n\n# let something blink (e.g. LED on pin GPIO18)\nDutyCycleKeptByLinux=$(cat /sys/class/pwm/$CHIP/$PWM/duty_cycle)\nif [ $DutyCycleKeptByLinux != \"0\" ]; then\n  echo 0 \u003e /sys/class/pwm/$CHIP/$PWM/duty_cycle\nfi\necho 1000000000 \u003e /sys/class/pwm/$CHIP/$PWM/period\necho 500000000 \u003e /sys/class/pwm/$CHIP/$PWM/duty_cycle\necho 1 \u003e /sys/class/pwm/$CHIP/$PWM/enable\nsleep 10\necho 0 \u003e /sys/class/pwm/$CHIP/$PWM/enable\n\n# unexport channel\necho $CHANNEL \u003e /sys/class/pwm/$CHIP/unexport\n```\n\nA full description of the sysfs interface can be found in the Linux documentation (e.g. https://www.kernel.org/doc/Documentation/pwm.txt). Excerpt:\n\n```text\nUsing PWMs with the sysfs interface\n-----------------------------------\n\nIf CONFIG_SYSFS is enabled in your kernel configuration a simple sysfs\ninterface is provided to use the PWMs from userspace. It is exposed at\n/sys/class/pwm/. Each probed PWM controller/chip will be exported as\npwmchipN, where N is the base of the PWM chip. Inside the directory you\nwill find:\n\n  npwm\n    The number of PWM channels this chip supports (read-only).\n\n  export\n    Exports a PWM channel for use with sysfs (write-only).\n\n  unexport\n   Unexports a PWM channel from sysfs (write-only).\n\nThe PWM channels are numbered using a per-chip index from 0 to npwm-1.\n\nWhen a PWM channel is exported a pwmX directory will be created in the\npwmchipN directory it is associated with, where X is the number of the\nchannel that was exported. The following properties will then be available:\n\n  period\n    The total period of the PWM signal (read/write).\n    Value is in nanoseconds and is the sum of the active and inactive\n    time of the PWM.\n\n  duty_cycle\n    The active time of the PWM signal (read/write).\n    Value is in nanoseconds and must be less than the period.\n\n  polarity\n    Changes the polarity of the PWM signal (read/write).\n    Writes to this property only work if the PWM chip supports changing\n    the polarity. The polarity can only be changed if the PWM is not\n    enabled. Value is the string \"normal\" or \"inversed\".\n\n  enable\n    Enable/disable the PWM signal (read/write).\n    - 0 - disabled\n    - 1 - enabled\n```\n\n**Remarks:**\n* Linux uses the confusing term duty_cycle instead of pulse or pulse width.\n* After system boot pulse and period are set to their default value 0.\n* Linux keeps the values for pulse and period after unexport.\n* Linux always verifies that pulse (duty_cycle) is less than period!\n* Linux needs some time to give us the permission to use an exported PWM channel.\n\n**Implications:**\n* Pulse can not be equal or greater than period.\n* Period can not be less or equal than pulse.\n* After export we must wait for channel permission. \n\n**Robust initialization strategy:**\n* get pulse value kept by Linux\n* if pulse \u003e 0: set pulse to 0\n* set period to planned value\n* set pulse to planned value\n* InitializeChannel() handles this\n\n## Package pwm\n\nThis library tries to stay as close as possible to the Linux sysfs PWM interface. This package provides the following:\n\n**Wrapper functions:**\n* Export(chip, channel string) (ChannelHandle, error)\n* Unexport(ch *ChannelHandle) error\n* SetPulse(ch *ChannelHandle, pulse time.Duration) error\n* SetPeriod(ch *ChannelHandle, period time.Duration) error\n* Enable(ch *ChannelHandle) error\n* Disable(ch *ChannelHandle) error\n\n**Convinience function:**\n* Initialize(chip, channel string, pulse, period, waitForPermission time.Duration) (ChannelHandle, error)\n\n**Helper functions:**\n* FrequencyToPeriod(frequency float64) time.Duration\n* DutyCycleToPulse(ch *ChannelHandle, dutyCycle float64) time.Duration\n\n**Special purposes:**\n* GetPulseFromOS(ch *ChannelHandle) (time.Duration, error)\n* GetPeriodFromOS(ch *ChannelHandle) (time.Duration, error)\n* GetIsEnabledFromOS(ch *ChannelHandle) (bool, error)\n\n**Channel handle:**\n\nThe channel handle keeps track of the current channel settings. After setting the values for pulse and period, this\nreflects to the current values kept by Linux. Remember: Linux keeps the values for pulse and period after an unexport\noperation.\n\n```go\ntype ChannelHandle struct {\n\tChip      string\n\tChannel   string\n\tPulse     time.Duration\n\tPeriod    time.Duration\n\tIsEnabled bool\n}\n```\n## Basic usage\n\nThe following examples shows the basic package usage.\n\n**Example 1: 'Blinking LED'**\n\n![](./images/led.jpg)\n\n```go\n// Blinking LED with hardware generated Pulse Width Modulation (PWM).\n// Raspberry Pi 4: chip=pwmchip0, channel=0 on pin=GPIO18, channel=1 on pin=GPIO19\n// Raspberry Pi 5: chip=pwmchip2, channel=2 on pin=GPIO18, channel=3 on pin=GPIO19\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/Klaus-Tockloth/pwm\"\n)\n\nfunc main() {\n\tchip := \"pwmchip2\"\n\tchannel := \"2\"\n\tpulse := time.Duration(500 * time.Millisecond)\n\tperiod := time.Duration(1000 * time.Millisecond)\n\twaitForPermission := time.Duration(500 * time.Millisecond)\n\n\tledPwmHandle, err := pwm.Initialize(chip, channel, pulse, period, waitForPermission)\n\tif err != nil {\n\t\tlog.Printf(\"error [%v] at pwm.Initialze()\", err)\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\terr = pwm.Unexport(\u0026ledPwmHandle)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"error [%v] at pwm.Unexport()\", err)\n\t\t}\n\t}()\n\n\terr = pwm.Enable(\u0026ledPwmHandle)\n\tif err != nil {\n\t\tlog.Printf(\"error [%v] at pwm.Enable()\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"LED should blink ...\\n\")\n\ttime.Sleep(10 * time.Second)\n\n\t// adjust period width for given frequency in hz\n\terr = pwm.SetPeriod(\u0026ledPwmHandle, pwm.FrequencyToPeriod(0.5))\n\tif err != nil {\n\t\tlog.Printf(\"error [%v] at pwm.SetPeriod()\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"LED should blink slower ...\\n\")\n\ttime.Sleep(10 * time.Second)\n\n\t// adjust pulse width for given duty cyle in %\n\terr = pwm.SetPulse(\u0026ledPwmHandle, pwm.DutyCycleToPulse(\u0026ledPwmHandle, 75.0))\n\tif err != nil {\n\t\tlog.Printf(\"error [%v] at pwm.SetPulse()\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"LED should blink longer ...\\n\")\n\ttime.Sleep(10 * time.Second)\n\n\terr = pwm.Disable(\u0026ledPwmHandle)\n\tif err != nil {\n\t\tlog.Printf(\"error [%v] at pwm.Disable()\", err)\n\t\treturn\n\t}\n\n\t// program exit: unexport is done by defer function\n\tfmt.Printf(\"Done\\n\")\n}\n```\n\n**Example 2: 'Servo Control'**\n\n![](./images/servo.jpg)\n\n```go\n// Servo control with hardware generated Pulse Width Modulation (PWM).\n// Raspberry Pi 4: chip=pwmchip0, channel=0 on pin=GPIO18, channel=1 on pin=GPIO19\n// Raspberry Pi 5: chip=pwmchip2, channel=2 on pin=GPIO18, channel=3 on pin=GPIO19\n// Servo parameters:\n// - period           =   20000 μs\n// - min angle        =     500 μs\n// - neutral position =    1500 μs\n// - max angle        =    2500 μs\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/Klaus-Tockloth/pwm\"\n)\n\nfunc main() {\n\tchip := \"pwmchip2\"\n\tchannel := \"2\"\n\tpulse := time.Duration(1500 * time.Microsecond) // neutral position\n\tperiod := time.Duration(20000 * time.Microsecond)\n\twaitForPermission := time.Duration(500 * time.Millisecond)\n\n\tservoPwmHandle, err := pwm.Initialize(chip, channel, pulse, period, waitForPermission)\n\tif err != nil {\n\t\tlog.Printf(\"error [%v] at pwm.Initialze()\", err)\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\terr = pwm.Unexport(\u0026servoPwmHandle)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"error [%v] at pwm.Unexport()\", err)\n\t\t}\n\t}()\n\n\tfmt.Printf(\"servo neutral position ...\\n\")\n\terr = pwm.Enable(\u0026servoPwmHandle)\n\tif err != nil {\n\t\tlog.Printf(\"error [%v] at pwm.Enable()\", err)\n\t\treturn\n\t}\n\ttime.Sleep(2 * time.Second)\n\n\tfmt.Printf(\"servo min position ...\\n\")\n\terr = pwm.SetPulse(\u0026servoPwmHandle, time.Duration(500*time.Microsecond))\n\tif err != nil {\n\t\tlog.Printf(\"error [%v] at pwm.SetPulse()\", err)\n\t\treturn\n\t}\n\ttime.Sleep(2 * time.Second)\n\n\tfmt.Printf(\"servo max position ...\\n\")\n\terr = pwm.SetPulse(\u0026servoPwmHandle, time.Duration(2500*time.Microsecond))\n\tif err != nil {\n\t\tlog.Printf(\"error [%v] at pwm.SetPulse()\", err)\n\t\treturn\n\t}\n\ttime.Sleep(2 * time.Second)\n\n\terr = pwm.Disable(\u0026servoPwmHandle)\n\tif err != nil {\n\t\tlog.Printf(\"error [%v] at pwm.Disable()\", err)\n\t\treturn\n\t}\n\n\t// program exit: unexport is done by defer function\n\tfmt.Printf(\"Done\\n\")\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fklaus-tockloth%2Fpwm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fklaus-tockloth%2Fpwm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fklaus-tockloth%2Fpwm/lists"}