Sitemap
Oleksandr Neiko (Robotics/Senior Android Engineer)

Oleksandr Neiko (Robotics/Senior Android Engineer)

Robotics Engineer , Senior Android Engineer, Kotlin / Java developer.

Kotlin Gpio project. Servo Motor SG90. Actuator controlling: (Working with IO lines on Raspberry Pi using Pi4J Kotlin/Java langs and remote compiling / debugging to any ARM GPIO compatible hardware. Advanced AI features): Lesson 10

LESSON 10: Servo Motor SG90. Actuator controlling

https://github.com/RoboticsLife/Lesson10-Gpio-Kotlin-Pi4J

Step 1: Add SG90 Servo motor (Actuator) type of device’s part to Json configuration

   "servos": [
{
"name": "Simple servo example SG-90",
"hardwareModel": "SG-90",
"hardwareVersion": "",
"pin": 18,
"installedServoPosition": 0
}
]

Step 2: Add servo motor type to local data class

package brain.data

import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@JsonIgnoreProperties(ignoreUnknown = true)
data class Configuration(
val configName: String? = null,
val configDescription: String? = null,
val configVersion: String? = null,
val hardwareModel: String? = null,
val hardwareModelCode: Int? = null,
val hardwareType: String? = null,
val hardwareTypeCode: Int? = null,
val leds: List<LedConfig?>? = null,
val buttons: List<ButtonConfig?>? = null,
val buzzers: List<BuzzerConfig?>? = null,
val distanceSensors: List<DistanceSensorConfig?>? = null,
val displays: List<DisplayConfig?>? = null,
val servos: List<ServoConfig?>? = null,
) {
@JsonIgnoreProperties(ignoreUnknown = true)
data class ButtonConfig(
val name: String?,
val pin: Int?,
val pullResistance: Int?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class LedConfig(
val name: String?,
val pin: Int?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class BuzzerConfig(
val name: String?,
val pin: Int?
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class DistanceSensorConfig(
val name: String?,
val hardwareModel: String?,
val hardwareVersion: String?,
val pinTrigger: Int?,
val pinEcho: Int?,
val installedSensorPosition: Int?,
val movingAngle: Int? = 0
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class DisplayConfig(
val name: String?,
val hardwareModel: String?,
val hardwareVersion: String?,
val pin01: Int?,
val pin02: Int?,
val pin03: Int?,
val pin04: Int?,
val pin05: Int?,
val pin06: Int?,
val pin07: Int?,
val pin08: Int?,
val pin09: Int?,
val pin10: Int?,
val pin11: Int?,
val pin12: Int?,
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class ServoConfig(
val name: String?,
val hardwareModel: String?,
val hardwareVersion: String?,
val pin: Int?,
val installedServoPosition: Int?
)
}

Step 3: Create Servo Motor Interface and implement a specific type of it for SG90

package avatar.hardware.parts

import brain.data.Configuration

interface Servo {

fun actuatorServoGetCurrentAngle(): Float

fun actuatorServoGetAngleRangeLimit(): Float

fun actuatorServoMoveToAngle(angle: Float = 0f, customMovingTimeInMillis: Int? = 0): Boolean



companion object {
const val NAME_HARDWARE_MODEL_SG90 = "SG90"


fun isConfigurationValid(servoConfig: Configuration.ServoConfig): String {

if (servoConfig.hardwareModel
?.filterNot { it == ' ' || it == '-' || it == '_' || it == ',' || it == '.' }?.lowercase()
?.contains(NAME_HARDWARE_MODEL_SG90.filterNot { it == ' ' || it == '-' || it == '_' || it == ',' || it == '.' }
.lowercase()) == true) {
return NAME_HARDWARE_MODEL_SG90
} else if (false) {
//TODO add other types implementations
return ""
} else return ""
}
}
}
package avatar.hardware.parts

import brain.data.Configuration
import com.pi4j.context.Context
import com.pi4j.io.pwm.Pwm
import com.pi4j.io.pwm.PwmType
import kotlinx.coroutines.*
import kotlin.math.abs

class ServoSG90(pi4j: Context, servoConfig: Configuration.ServoConfig): Servo {

lateinit var pwm: Pwm
private var threadScope: Job? = null
private var customTimeMoveThreadScope: Job? = null
private var currentPositionInDegrees: Float = 0f

init {
buildServo(pi4j, servoConfig)
moveToDefaultAngle()
}

private fun buildServo(pi4j: Context, servoConfig: Configuration.ServoConfig) {
val params = Pwm.newConfigBuilder(pi4j)
.id("BCM${servoConfig.pin}")
.name(servoConfig.name)
.address(servoConfig.pin)
.pwmType(PwmType.HARDWARE)
.initial(0)
.frequency(DEFAULT_FREQUENCY)
.shutdown(0)
.build()

pwm = pi4j.create(params)
}

private fun moveToDefaultAngle() {
actuatorServoMoveToAngle(angle = 0f)
}

private fun moveToAngleForCustomTime(angle: Float, customMovingTimeInMillis: Int) {
if (customMovingTimeInMillis == 0) return
val filteredAngle =
if (angle > DEFAULT_MAX_ANGLE) DEFAULT_MAX_ANGLE else if (angle < DEFAULT_MIN_ANGLE) DEFAULT_MIN_ANGLE else angle

val angleMovementRange = currentPositionInDegrees - filteredAngle
val range = abs(if (filteredAngle % 1 > 0) angleMovementRange.toInt() + 1 else angleMovementRange.toInt())
customTimeMoveThreadScope?.cancel()
customTimeMoveThreadScope = CoroutineScope(Job() + Dispatchers.IO).launch {
for (i in 1..range) {
delay((customMovingTimeInMillis / range).toLong())
actuatorServoMoveToAngle(
angle = if (currentPositionInDegrees < filteredAngle) currentPositionInDegrees + 1.0f else currentPositionInDegrees - 1.0f
)
}
}
}


override fun actuatorServoGetCurrentAngle(): Float {
return currentPositionInDegrees
}

override fun actuatorServoGetAngleRangeLimit(): Float {
return DEFAULT_ANGLE_RANGE
}

override fun actuatorServoMoveToAngle(angle: Float, customMovingTimeInMillis: Int?): Boolean {
if (customMovingTimeInMillis != null && customMovingTimeInMillis > 0) {
moveToAngleForCustomTime(angle, customMovingTimeInMillis)
} else {
val filteredAngle =
if (angle > DEFAULT_MAX_ANGLE) DEFAULT_MAX_ANGLE else if (angle < DEFAULT_MIN_ANGLE) DEFAULT_MIN_ANGLE else angle

threadScope?.cancel()
threadScope = CoroutineScope(Job() + Dispatchers.IO).launch {
currentPositionInDegrees = filteredAngle
val dutyCycleByOnDegree = (DEFAULT_MAX_DUTY_CYCLE - DEFAULT_MIN_DUTY_CYCLE) / DEFAULT_ANGLE_RANGE
pwm.on(DEFAULT_START_POSITION_DUTY_CYCLE + filteredAngle * dutyCycleByOnDegree)
delay(20)
}
return true
}
return false
}

companion object {
//from SG90 datasheet
const val DEFAULT_FREQUENCY: Int = 50
const val DEFAULT_MIN_ANGLE: Float = -90.0f
const val DEFAULT_MAX_ANGLE: Float = 90.0f
const val DEFAULT_ANGLE_RANGE: Float = 180f
const val DEFAULT_MIN_DUTY_CYCLE: Float = 2.0f
const val DEFAULT_MAX_DUTY_CYCLE: Float = 12.0f
const val DEFAULT_START_POSITION_DUTY_CYCLE: Float =
(DEFAULT_MAX_DUTY_CYCLE - DEFAULT_MIN_DUTY_CYCLE) / (DEFAULT_ANGLE_RANGE / DEFAULT_MAX_ANGLE) + DEFAULT_MIN_DUTY_CYCLE
const val SPEED_PER_DEGREE_IN_MILLIS: Float = 100.0f / 60.0f
}
}

Get Oleksandr Neiko (Robotics/Senior Android Engineer)’s stories in your inbox

Join Medium for free to get updates from this writer.

Step 4: Add functions to Circuit body type Interface and implement it

package avatar.hardware.types.circuitboard.data

import avatar.body.BodyPrototype
import avatar.hardware.HardwareTypes
import avatar.hardware.parts.Button
import avatar.hardware.parts.Buzzer
import avatar.hardware.parts.Led

data class BodyCircuitBoard(
override var type: HardwareTypes.Type = HardwareTypes.Type.CIRCUIT_BOARD,
val leds: MutableList<Led> = mutableListOf(),
val buttons: MutableList<Button> = mutableListOf(),
val buzzers: MutableList<Buzzer> = mutableListOf(),
val distanceSensors: MutableList<DistanceSensor> = mutableListOf(),
val displays: MutableList<Display> = mutableListOf(),
val servos: MutableList<Servo> = mutableListOf(),

): BodyPrototype()
package avatar.hardware.types.circuitboard

import avatar.hardware.Body

interface CircuitBoard: Body {

fun getLedsCount(): Int

fun ledOn(ledPosition: Int = 0, durationInMillis: Long = 0L): Boolean

fun ledOff(ledPosition: Int = 0): Boolean

fun addButtonListeners(buttonPosition: Int = 0, actionHigh: () -> Unit, actionLow: () -> Unit): Boolean

fun buzzerSoundOn(buzzerPosition: Int = 0, durationInMillis: Long = 0L): Boolean

fun buzzerSoundOff(buzzerPosition: Int = 0): Boolean

fun stopDistanceMeasuring(sensorPosition: Int = 0): Boolean

fun getDistanceMeasuringState(sensorPosition: Int = 0): Boolean

fun displayPrint(displayPosition: Int = 0, outFloat: Float? = null, string: String? = null, printTimeInMillis: Int? = 0): Boolean

fun actuatorServoGetCurrentAngle(servoPosition: Int = 0): Float

fun actuatorServoGetAngleRangeLimit(servoPosition: Int = 0): Float

fun actuatorServoMoveToAngle(servoPosition: Int = 0, angle: Float = 0f, customMovingTimeInMillis: Int? = 0): Boolean

}
    private fun initHardware() {

//PARSE CONFIG

/** init Servos */
configuration.servos?.forEach {
if (it?.pin != null) {
when (Servo.isConfigurationValid(it)) {
Servo.NAME_HARDWARE_MODEL_SG90 ->
body.servos.add(ServoSG90(pi4J, it))
}
}
}

////////////////

override fun actuatorServoGetCurrentAngle(servoPosition: Int): Float {
if (servoPosition < 0) return Float.POSITIVE_INFINITY

if (servoPosition < body.servos.size) {
return body.servos[servoPosition].actuatorServoGetCurrentAngle()
}
return Float.POSITIVE_INFINITY
}

override fun actuatorServoGetAngleRangeLimit(servoPosition: Int): Float {
if (servoPosition < 0) return Float.POSITIVE_INFINITY

if (servoPosition < body.servos.size) {
return body.servos[servoPosition].actuatorServoGetAngleRangeLimit()
}
return Float.POSITIVE_INFINITY
}

override fun actuatorServoMoveToAngle(servoPosition: Int, angle: Float, customMovingTimeInMillis: Int?): Boolean {
if (servoPosition < 0) return false

if (servoPosition < body.servos.size) {
return body.servos[servoPosition].actuatorServoMoveToAngle(angle, customMovingTimeInMillis)
}
return false
}

Step 5: Main thread logic. Control Servo Motor movements and using custom time settings movements for slower pivots.


(avatar.body as CircuitBoard).actuatorServoMoveToAngle(0, 90f)
delay(2000)
(avatar.body as CircuitBoard).actuatorServoMoveToAngle(0, -90f, 4000)

* Additional settings: remote compiling / debugging setup

Add new launch configuration to IntelliJ IDEA

fill IP / port adress to Raspberry PI. Username & password as sudo connection. Add Main Kotlin class and project module.

--

--

Oleksandr Neiko (Robotics/Senior Android Engineer)
Oleksandr Neiko (Robotics/Senior Android Engineer)

Robotics Engineer , Senior Android Engineer, Kotlin / Java developer.