Yellow Rabbit

Hardware PWM

PWM and LED

Last time we got a garland of four LEDs to turn on and off wonderfully. Let’s try to use the hardware PWM that Raspberry Pi has. Of course, there is nothing to prevent the implementation of PWM in software, but I prefer to use the hardware in the first place - it was also paid for:smiley:

The circuit is very simple: I chose a green 5mm LED with a voltage drop of at a current of 8mA and a limiting resistor : Single-LED circuit

For PWM use GPIO18.

Breadboard

Breadboard

Code


package io.github.yrabbit.kotlin

import io.github.yrabbit.java.util.AddDir
import jpigpio.JPigpio.*
import jpigpio.Pigpio
import jpigpio.Utils

fun main(args: Array<String>) {
    AddDir.addDir("/home/rabbit/local/lib")
    println("*** Raspberry Pi Kotlin ***")
    val pigpio = Pigpio()
    pigpio.gpioInitialize()
    Utils.addShutdown(pigpio)
    normOutputDriveStrength(pigpio)

    println("PWM0 freq:$PWM_FREQ, max duty:$PI_HW_PWM_RANGE")
    // test pwm0: change duty from 0 to MAX
    repeat (3) {
	    for (vol in 0..PI_HW_PWM_RANGE step PWM_STEP) {
		pigpio.gpioHardwarePWM(PWM0_GPIO, PWM_FREQ, vol)
		pigpio.gpioDelay(100 * 1000)
	    }
	    for (vol in PI_HW_PWM_RANGE downTo 0 step PWM_STEP) {
		pigpio.gpioHardwarePWM(PWM0_GPIO, PWM_FREQ, vol)
		pigpio.gpioDelay(100 * 1000)
	    }
    }
    pigpio.gpioSetMode(PWM0_GPIO, PI_INPUT)
}


fun normOutputDriveStrength(pigpio: JPigpio) {
    // Check drive strength on GPIO
    val strength = pigpio.gpioGetPad(0)

    if (strength != CURRENT) {
        pigpio.gpioSetPad(0, CURRENT)
    }
}

const val PWM0_GPIO = 18

const val PWM_FREQ = 20000

const val PWM_STEP = PI_HW_PWM_RANGE / 30 // 30 seconds for full

const val CURRENT = 8 // mA

Breath

Well, isn’t it amazing?:smiley:

Second hardware PWM

Add one more LED (red 5mm voltage drop , a limiting resistor at a current of 8mA) and connect it to the second PWM on GPIO13: Circuit with two PWMs

Breadboard

Breadboard

When there are more details, I try to check the installation on a small power supply in order not to lose Rasperry Pi due to errors: Circuit check

Code

To manage two PWMs, it’s time to take advantage of Kotlin’s interesting features like coroutines. Each of the coroutines will reprogram its PWM and fall asleep for a while giving the opportunity to work another coroutine.


package io.github.yrabbit.kotlin

import io.github.yrabbit.java.util.AddDir
import jpigpio.JPigpio
import jpigpio.JPigpio.PI_HW_PWM_RANGE
import jpigpio.JPigpio.PI_INPUT
import jpigpio.Pigpio
import jpigpio.PigpioSocket
import jpigpio.Utils
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking
import kotlin.system.exitProcess

fun main(args: Array<String>) {
    AddDir.addDir("/home/rabbit/local/lib")
    println("*** Raspberry Pi Kotlin ***")
    val pigpio = Pigpio()
    pigpio.gpioInitialize()
    Utils.addShutdown(pigpio)
    normOutputDriveStrength(pigpio)

    println("PWM0 freq:$PWM_FREQ, max duty:$PI_HW_PWM_RANGE")
    runBlocking {
        val slowBreath = launch { runPWM(pigpio, UsedGPIO.Pwm0.pin, 30, 3, PWM_STEP_30) }
        val fastBreath = launch { runPWM(pigpio, UsedGPIO.Pwm1.pin, 10, 20, PWM_STEP_10) }
        slowBreath.join()
        fastBreath.join()
    }
    gpioSwitchToInput(pigpio)
    println("done.")
    exitProcess(0)
}

suspend fun runPWM(pigpio: JPigpio, pwmPin: Int, msDelay: Int, cnt: Int, st: Int) {
    repeat (cnt) {
        println("cnt:$cnt, ${Thread.currentThread().name}")
        for (vol in 0..PI_HW_PWM_RANGE step st) {
            pigpio.gpioHardwarePWM(pwmPin, PWM_FREQ, vol)
            delay(msDelay)
        }
        for (vol in PI_HW_PWM_RANGE downTo 0 step st) {
            pigpio.gpioHardwarePWM(pwmPin, PWM_FREQ, vol)
            delay(msDelay)
        }
    }
}

fun normOutputDriveStrength(pigpio: JPigpio) {
    // Check drive strength on GPIO
    val strength = pigpio.gpioGetPad(0)

    if (strength != CURRENT) {
        pigpio.gpioSetPad(0, CURRENT)
    }
}

/*
 * Clean up
 */
fun gpioSwitchToInput(pigpio: JPigpio) {
    UsedGPIO.values().forEach {
        pigpio.gpioSetMode(it.pin, PI_INPUT)
    }
}

enum class UsedGPIO(val pin: Int) {
    Pwm0(18),   // GPIO18
    Pwm1(13)    // GPIO13
}
val LEDS = arrayOf(UsedGPIO.Pwm0, UsedGPIO.Pwm1)

const val PWM_FREQ = 20000

const val PWM_STEP_30 = PI_HW_PWM_RANGE / 30
const val PWM_STEP_10 = PI_HW_PWM_RANGE / 10

const val CURRENT = 8 // mA<Paste>

Unfortunately it turned out that the pigpio library does not work very well in a multithreaded environment: I always received signal 11 at trial runs. So I had to temporarily use the option pigpio as a daemon.

Correction: as it turned out there is a way to make pigpio work without a daemon: you need to run coroutines in CoroutineScope. Then they work in the same thread as the library:


fun main(args: Array<String>) {
    AddDir.addDir("/home/rabbit/local/lib")
    println("*** Raspberry Pi Kotlin ***")
    val pigpio = Pigpio()
    pigpio.gpioInitialize()
    Utils.addShutdown(pigpio)
    normOutputDriveStrength(pigpio)

    println("PWM0 freq:$PWM_FREQ, max duty:$PI_HW_PWM_RANGE")
    runBlocking {
        val slowBreath = launch { runPWM(pigpio, UsedGPIO.Pwm0, 30, 3, PWM_STEP_30) }
        val fastBreath = launch { runPWM(pigpio, UsedGPIO.Pwm1, 10, 20, PWM_STEP_10) }
        slowBreath.join()
        fastBreath.join()
    }
    gpioSwitchToInput(pigpio)
    println("done.")
    exitProcess(0)
}

Running

It’s a miracle!:smiley: