Yellow Rabbit

Аппаратный ШИМ

ШИМ и светодиод

Прошлый раз мы заставили гирлянду из четырёх светодиодов замечательно включаться и выключаться. Попробуем использовать аппаратный ШИМ, который есть у Raspberry Pi. Конечно, ничто не мешает реализовать ШИМ программно, однако я предпочитаю в первую очередь использовать аппаратные возможности - за них так же было уплачено:smiley:

Схема очень проста: я выбрал зелёный 5mm светодиод с падением напряжения при токе 8mA и ограничивающем резисторе : Схема с одним светодиодом

Для ШИМ используем GPIO18.

Макетная плата

Макетная плата

Код


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

Дыхание

Ну не изумительно ли? :smiley:

Второй аппаратный ШИМ

Добавим ещё один светодиод (красный 5mm падение напряжения , ограничивающий резистор при токе 8mA) и подключим его ко второму ШИМ на GPIO13: Принципиальная схема с двумя ШИМ

Макетная плата

Макетная плата

Когда деталей становится больше, то я стараюсь проверять монтаж на небольшом блоке питания чтобы не потерять Rasperry Pi из-за ошибок: Проверка монтажа

Код

Чтобы управлять двумя ШИМ самое время воспользоваться такой интересной фичей Kotlin как сопрограммы. Каждая из сопрограмм будет перепрограммировать свой ШИМ и засыпать на некоторое время давая возможность поработать другой сопрограмме.


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

К сожалению выяснилось, что библиотека pigpio не очень хорошо работает в многопоточной среде: я постоянно получал signal 11 при пробных запусках. Так что пришлось временно воспользоваться вариантом pigpio как демон.

Поправка: как оказалось есть способ заставить работать pigpio без демона: нужно запускать сопрограммы в CoroutineScope. Тогда они работают в том же потоке, что и библиотека:


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)
}

Запускаем

Это чудо! :smiley: