Прошлый раз мы заставили гирлянду из четырёх светодиодов замечательно включаться и выключаться. Попробуем использовать аппаратный ШИМ, который есть у Raspberry Pi. Конечно, ничто не мешает реализовать ШИМ программно, однако я предпочитаю в первую очередь использовать аппаратные возможности - за них так же было уплачено
Схема очень проста: я выбрал зелёный 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
Ну не изумительно ли?
Добавим ещё один светодиод (красный 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)
}
Это чудо!