Rewrote devices on Beaglebone. Passes precompile, but needs proper testing.

parent 4a2ded68
Pipeline #576 canceled with stage
in 0 seconds
# AbstractDevice should define a type `T` with methods:
# write!(::T, identifier, val)
# read(::T, identifier)
include("IO_Object.jl")
include("Debug.jl")
include("SysLED.jl")
include("GPIO.jl")
include("PWM.jl")
#List of available devices and their constructors
const DEVICES = Dict("debug" => Debug(), "sysled" => SysLED(), "gpio" => GPIO(), "pwm" => PWM())
const DEVICES = Dict("debug" => Debug, "sysled" => SysLED, "gpio" => GPIO, "pwm" => PWM)
#Currently active devices on the BeagleBone
active_devices = Dict{String,Dict{Int32,IO_Object}}("debug" => Dict{Int32,Debug}(), "sysled" => Dict{Int32,SysLED}(),
"gpio" => Dict{Int32,GPIO}(), "pwm" => Dict{Int32,PWM}())
"""
active_device = initdev(dev_name::String, i:Int32)
Initializes a new device of type 'dev_name' at index 'i' on the BeagleBone,
and adds it to the dict of currently active devices. Returns the initialized
device 'active_device'.
"""
function initdev(dev_name::String, i::Int32)
#Check if the type of device is valid
dev_constr = try
DEVICES[dev_name]
catch
error("Device $dev_name does not exist")
end
#Check if the device index is already in use
haskey(active_devices[dev_name],i) && error("Index $i is already in use for device $dev_name")
#Construct a new device and add it to dict of currently active devices
active_device = dev_constr(i)
active_devices[dev_name][i] = active_device
return active_device
end
"""
closedev(dev_name::String, i::Int32)
Closes down a currently active device of type 'dev_name' at index 'i' on the BeagleBone,
and removes it from the dict of currently active devices.
"""
function closedev(dev_name::String, i::Int32)
active_device = try
active_devices[dev_name][i]
catch
error("No device of type $dev_name at index $i is currently active")
end
#Call the teardown method on the device, to close all file-streams and
#unexport the device from the BeagleBone
teardown(active_device)
#Remove the device from the dict of active devices
delete!(active_devices[device_name], i)
end
"""
dev = getdev(devname)
Gets the device corresponding to the name `devname`
dev = getdev(dev_name::String, i::Int32)
Retrieves the active device of type `dev_name` at index 'i'
"""
function getdev(devname)
function getdev(dev_name::String, i::Int32)
dev = try
DEVICES[devname]
active_devices[dev_name][i]
catch
error("Device $devname does not exist")
error("No device of type $dev_name at index $i is currently active")
end
return dev
end
......@@ -50,8 +90,8 @@ function bbparse(l::Tuple, sock)
if iswrite
for i = 1:ndev
command = l[2+i]::Tuple
dev = getdev(command[1])
write!(dev, command[2], command[3])
dev = getdev(command[1], command[2])
write!(dev, command[3])
end
return
else
......@@ -60,8 +100,8 @@ function bbparse(l::Tuple, sock)
timestamps = Array{UInt64,1}(ndev)
for i = 1:ndev
command = l[2+i]::Tuple
dev = getdev(command[1])
vals[i] = read(dev, command[2])
dev = getdev(command[1], command[2])
vals[i] = read(dev)
timestamps[i] = UInt64(0)#time_ns()
end
bbsend(sock, (vals, timestamps))
......@@ -71,7 +111,7 @@ end
global __waiting_first_connection__ = false
"""
run_server(port=2001)
run_server(port=2001; debug=false)
Run a server on `port` that listens for commands from computer
Optional debug keyword disables blinking system leds
"""
......@@ -82,14 +122,14 @@ function run_server(port=2001; debug=false)
try
@async while __waiting_first_connection__ && !debug
#Blink SysLED 2 when waiting for first connection to signal availability
led = SysLED()
write!(led, 2, true)
led = initdev("sysled",2)
write!(led, true)
sleep(0.4)
write!(led, 2, false)
write!(led, false)
sleep(0.2)
write!(led, 2, true)
write!(led, true)
sleep(0.4)
write!(led, 2, false)
write!(led, false)
sleep(0.8)
end
sock = accept(server)
......
# Devices should define a type `T` with methods:
# write!(::T, identifier, val)
# read(::T, identifier)
include("Debug.jl")
include("SysLED.jl")
include("GPIO.jl")
#List of available devices and their constructors
const DEVICES = Dict("debug" => Debug(), "sysled" => SysLED(), "gpio" => GPIO())
"""
dev = getdev(devname)
Gets the device corresponding to the name `devname`
"""
function getdev(devname)
dev = try
DEVICES[devname]
catch
error("Device $devname does not exist")
end
return dev
end
"""
bbparse(cmd)
Parse and execute the command `cmd`
"""
bbparse(any) = error("Unexpected input: $any")
"""
bbparse(l::Tuple)
Parse input on the form `l=(iswrite, ndev, cmd1, cmd2, ..., cmdn)``
where if `iswrite`
`cmdi = (devname, id, val)`
and if not `iswrite`
`cmdi = (devname, id)`
"""
function bbparse(l::Tuple)
iswrite = l[1]::Bool #True if write command, false if read
ndev = l[2]::Int32 #Number of devices/commands
for i = 1:ndev
command = l[2+i]::Tuple
dev = getdev(command[1])
if iswrite
write!(dev, command[2], command[3])
else
val = read(dev, command[2])
println("$val")
#TODO return somewhere
end
end
end
"""
run_server(port=2001)
Run a server on `port` that listens for commands from computer
"""
function run_server(port=2001)
server = listen(port)
@async while isopen(server)
try
sock = accept(server)
@async while isopen(sock)
try
l = deserialize(sock);
println("deserialize: $l")
bbparse(l)
catch err
if !isopen(sock) && isa(err, Base.EOFError)
println("Connection to server closed")
else
throw(err)
end
end
end
catch err
if isa(err,Base.UVError) && err.prefix == "accept"
println("Server closed successfully")
else
throw(err)
end
end
end
return server
end
"""
Debug
Debug()
Type for debugging and precompile
"""
type Debug
type Debug <: IO_Object
i::Int32
end
write!(::Debug, ind::Int32, val, debug::Bool=false) = nothing
read(::Debug, ind::Int32, debug::Bool=false) = -1
write!(::Debug, val, debug::Bool=false) = nothing
read(::Debug, debug::Bool=false) = -1
teardown(::Debug, debug::Bool=false) = nothing
"""
Lowest form of communication with the GPIO pins. The available pins are
listed in the "channel" parameter, and appear as directories in /sys/class.
listed in the "channel" parameter, and appear as directories in /sys/class/gpio.
Any of these GPIOs can be run in various ways, with any operation specified
by a sequence of three entries
Any of these GPIOs can be run in various ways, with any operation specified by an
Int32 value.
(channel, (operation, entry)) = (int32, (String, String))
For instance, to setup a GPIO on "gpio112", configure it as an output pin and set
it to high, the following code would be used.
For instance, if the GPIO "gpio30" is to be configured as a output pin and set
to high, the following two writes would be done.
gpio = GPIO(1)
write!(gpio, (2,"out"))
write!(gpio, (1, "1"))
write!(GPIO, 14, ("direction", "out"))
write!(GPIO, 14, ("value", "1"))
The operation of reading the current output value of the GPIO is done by
The operation of reading the current cofiguration of the GPIO is specified by
(channel, operation) = (int32, String)
For instance, to read the current value of the GPIO "gpio30" the following read
would be done
read(GPIO, 14, "value")
read(gpio, 1)
"""
struct GPIO
type GPIO <: IO_Object
i::Int32
filestreams::Array{IOStream,1} #1 = value, 2 = direction, 3 = edge
function GPIO(i::Int32)
(i <= 0 || i > length(channels)) && error("Invalid GPIO index: $i")
#TODO, in the future we should interface to config and let it setup gpio-folder and streams.
#TODO: Add a write to export-file
value_filestream = open("/sys/class/gpio/$(channels[i])/value","r+")
direction_filestream = open("/sys/class/gpio/$(channels[i])/direction","r+")
edge_filestream = open("/sys/class/gpio/$(channels[i])/edge","r")
return new(i, [value_filestream, direction_filestream, edge_filestream])
end
end
writeOperations = Dict("value" => ["1", "0"], "direction" => ["in", "out"])
readOperations = [
"value"
"direction"
"edge"
]
writeOperations = [["1", "0"],["in", "out"],["none","rising","falling","both"]]
channels =[
"gpio112"
"gpio114"
"gpio115"
"gpio116"
"gpio14"
"gpio15"
"gpio2"
"gpio20"
"gpio22"
"gpio23"
"gpio26"
"gpio27"
"gpio3"
"gpio30"
"gpio31"
"gpio4"
"gpio44"
"gpio45"
"gpio46"
"gpio47"
"gpio48"
"gpio49"
"gpio5"
"gpio50"
"gpio51"
"gpio60"
"gpio61"
"gpio65"
"gpio66"
"gpio67"
"gpio68"
"gpio69"
"gpio7"
"gpio112"
"gpio114"
"gpio115"
"gpio116"
"gpio14"
"gpio15"
"gpio2"
"gpio20"
"gpio22"
"gpio23"
"gpio26"
"gpio27"
"gpio3"
"gpio30"
"gpio31"
"gpio4"
"gpio44"
"gpio45"
"gpio46"
"gpio47"
"gpio48"
"gpio49"
"gpio5"
"gpio50"
"gpio51"
"gpio60"
"gpio61"
"gpio65"
"gpio66"
"gpio67"
"gpio68"
"gpio69"
"gpio7"
]
function write!(::GPIO, index::Int32, args::Tuple{String,String}, debug::Bool=false)
debug && return
operation, entry = args[1], args[2]
(index <= 0 || index > length(channels)) && error("Invalid GPIO index: $index")
!haskey(writeOperations,operation) && error("Invalid GPIO operation: $operation")
filename = "/sys/class/gpio/$(channels[index])/$operation"
if entry in writeOperations[operation]
file = open(filename, "r+")
write(file, "$(entry)")
close(file)
else
error("Cannot write $(entry) to operation: $operation")
end
return
"""
write!(gpio::GPIO, args::Tuple{Int32,String}, debug::Bool=false)
Writes an entry to an operation on a GPIO, of the form args = (operation, entry).
"""
function write!(gpio::GPIO, args::Tuple{Int32,String}, debug::Bool=false)
debug && return
operation, entry = args[1], args[2]
(operation < 1 || operation > length(gpio.filestreams)) && error("Invalid GPIO operation: $operation")
if entry in writeOperations[operation]
write(gpio.filestreams[operation], entry)
seekstart(gpio.filestreams[operation])
else
error("Invalid entry for GPIO operation $operation: $entry")
end
end
function read(::GPIO, index::Int32, operation::String, debug::Bool=false)
debug && return
(index <= 0 || index > length(channels)) && error("Invalid GPIO index: $index")
"""
l = read(gpio::GPIO, operation::Int32, debug::Bool=false)
Reads the current value from an operation on a GPIO.
"""
function read(gpio::GPIO, operation::Int32, debug::Bool=false)
debug && return
(operation < 1 || operation > length(gpio.filestreams)) && error("Invalid GPIO operation: $operation")
l = readline(gpio.filestreams[operation])
seekstart(gpio.filestreams[operation])
return l
end
if operation in readOperations
filename = "/sys/class/gpio/$(channels[index])/$operation"
file = open(filename, "r")
l = readline(file)
close(file)
l readOperations[operation] && error("Invalid entry read : $l")
return l
else
error("Cannot read from: $operation")
end
"""
teardown!(gpio::GPIO)
Closes all open streams on the GPIO, and unexports it from the file system.
"""
function teardown!(gpio::GPIO, debug::Bool=false)
debug && return
#Close all IOStreams
for stream in gpio.filestreams
close(stream)
end
#Unexport filestructure
filename = "/sys/class/gpio/unexport"
#TODO Verify if this is the correct command to send to unexport...
write(filename, channels[gpio.i])
end
"""Define abstract type for pins/LEDS on the BeagleBone"""
abstract type IO_Object end
......@@ -5,8 +5,34 @@ AM3359 chip, see p.182 in
www.ti.com/product/AM3359/technicaldocuments
"""
type PWM <: IO_Object
i::Int32
pin::String
chip::String
filestreams::Array{IOStream,1} #1 = enable, 2 = period, 3 = duty_cycle, 4 = polarity
function PWM(i::Int32)
pins = collect(keys(validPins))
(i < 1 || i > length(pins)) && error("Invalid PWM index: $i")
pin = pins[i]
struct PWM
# Configure the pin to run PWM if possible
Base.run(`config-pin $(pin) pwm`)
# Find chip and export number
chip = validPins[pin][2]
filename = "/sys/class/pwm/$(chip)/export"
exportNumber = validPins[pin][3]
# Export the filestructure of the corresponding chip
write(filename, exportNumber)
# Setup filestreams
enable_filestream = open("/sys/class/pwm/$(validPins[pin][2])/pwm$(validPins[pin][3])/enable","r+")
period_filestream = open("/sys/class/pwm/$(validPins[pin][2])/pwm$(validPins[pin][3])/period","r+")
duty_cycle_filestream = open("/sys/class/pwm/$(validPins[pin][2])/pwm$(validPins[pin][3])/duty_cycle","r+")
polarity_filestream = open("/sys/class/pwm/$(validPins[pin][2])/pwm$(validPins[pin][3])/polarity","r+")
return new(i, pin, chip, [enable_filestream, period_filestream, duty_cycle_filestream, polarity_filestream])
end
end
# These pins are exported with the Device Tree Overlay cape-universaln (default)
......@@ -27,65 +53,48 @@ validPins = Dict(
# "P8.34" => ("PWM1B", "pwmchip1", "1"),
# "P9.31" => ("PWM0A", "pwmchip0", "0"),
function setup(::PWM, pin::String)
if pin in keys(validPins)
"""
write!(pwm::PWM, args::Tuple{Int32,String}, debug::Bool=false)
Writes an entry to an operation on the PWM, of the form args = (operation, entry).
"""
function write!(pwm::PWM, args::Tuple{Int32,String}, debug::Bool=false)
debug && return
# Configure the pin to run PWM if possible
Base.run(`config-pin $(pin) pwm`)
operation, entry = args[1], args[2]
(operation < 1 || operation > length(pwm.filestreams)) && error("Invalid PWM operation: $operation")
# Find chip and export number
chip = validPins[pin][2]
filename = "/sys/class/pwm/$(chip)/export"
exportNumber = validPins[pin][3]
# Export the filestructure of the corresponding chip
write(filename, exportNumber)
else
error("The pin write $(pin) does not support PWM")
end
return
#TODO: Add a list of allowed entries for error checking
write(pwm.filestreams[operation], entry)
seekstart(pwm.filestreams[operation])
end
function teardown(::PWM, pin::String)
if pin in keys(validPins)
# Find chip and export number
chip = validPins[pin][2]
filename = "/sys/class/pwm/$(chip)/unexport"
exportNumber = validPins[pin][3]
# Export the filestructure of the corresponding chip
write(filename, exportNumber)
else
error("The pin write $(pin) does not support PWM")
end
return
"""
l = read(pwm::PWM, operation::Int32, debug::Bool=false)
Reads the current value from an operation on a PWM.
"""
function read(pwm::PWM, operation::Int32, debug::Bool=false)
debug && return
(operation < 1 || operation > length(pwm.filestreams)) && error("Invalid PWM operation: $operation")
l = readline(pwm.filestreams[operation])
seekstart(pwm.filestreams[operation])
return l
end
function write!(::PWM, pin::String, operation::Int32, entry::String)
!(pin in keys(validPins)) && error("Invalid PWM pin: $(pin))")
(operation <= 0 || operation > 4) && error("Invalid GPIO operation: $operation")
# TODO: Error checks
if operation == 1
directory = "enable"
end
if operation == 2
directory = "period"
end
if operation == 3
directory = "duty_cycle"
end
if operation == 4
directory = "polarity"
end
filename = "/sys/class/pwm/$(validPins[pin][2])/pwm$(validPins[pin][3])/$(directory)"
file = open(filename, "r+")
write(file, "$(entry)")
close(file)
"""
teardown!(pwd::PWM)
Closes all open streams on the PWM, and unexports it from the file system
"""
function teardown!(pwm::PWM, debug::Bool=false)
debug && return
#Close all IOStreams
for stream in pwm.filestreams
close(stream)
end
#Unexport filestructure
filename = "/sys/class/pwm/$(pwm.chip)/unexport"
export_number = validPins[pwm.pin][3]
write(filename, export_number)
return
end
"""
This script allows for low level PWM control of selected pins
The valid pins dictionary relates to memory adresses in of the
AM3359 chip, see p.182 in
www.ti.com/product/AM3359/technicaldocuments
"""
struct PWM
end
# These pins are exported with the Device Tree Overlay cape-universaln (default)
validPins = Dict(
"P9.22" => ("PWM0A", "pwmchip0", "0"),
"P9.21" => ("PWM0B", "pwmchip0", "1"),
"P9.14" => ("PWM1A", "pwmchip2", "0"),
"P9.16" => ("PWM1B", "pwmchip2", "1"),
"P8.19" => ("PWM2A", "pwmchip4", "0"),
"P8.13" => ("PWM2B", "pwmchip4", "1"),
)
# These pins are exported with the Device Tree Overlay cape-universala
# "P8.36" => ("PWM1A", "pwmchip1", "0"),
# "P9.29" => ("PWM0B", "pwmchip0", "1"),
# "P8.46" => ("PWM2B", "pwmchip2", "1")
# "P8.45" => ("PWM2A", "pwmchip2", "0"),
# "P8.34" => ("PWM1B", "pwmchip1", "1"),
# "P9.31" => ("PWM0A", "pwmchip0", "0"),
function setup(::PWM, pin::String)
if pin in keys(validPins)
# Configure the pin to run PWM if possible
Base.run(`config-pin $(pin) pwm`)
# Find chip and export number
chip = validPins[pin][2]
filename = "/sys/class/pwm/$(chip)/export"
exportNumber = validPins[pin][3]
# Export the filestructure of the corresponding chip
write(filename, exportNumber)
else
error("The pin write $(pin) does not support PWM")
end
return
end
function teardown(::PWM, pin::String)
if pin in keys(validPins)
# Find chip and export number
chip = validPins[pin][2]
filename = "/sys/class/pwm/$(chip)/unexport"
exportNumber = validPins[pin][3]
# Export the filestructure of the corresponding chip
write(filename, exportNumber)
else
error("The pin write $(pin) does not support PWM")
end
return
end
function write!(::PWM, pin::String, operation::Int32, entry::String)
!(pin in keys(validPins)) && error("Invalid PWM pin: $(pin))")
(operation <= 0 || operation > 4) && error("Invalid GPIO operation: $operation")
# TODO: Error checks
if operation == 1
directory = "enable"
end
if operation == 2
directory = "period"
end
if operation == 3
directory = "duty_cycle"
end
if operation == 4
directory = "polarity"
end
filename = "/sys/class/pwm/$(validPins[pin][2])/pwm$(validPins[pin][3])/$(directory)"
file = open(filename, "r+")
write(file, "$(entry)")
close(file)
return
end
"""
The on-board leds with id ∈ [1,2,3,4]
SysLED
Type representing the system LEDs on the BeagleBone. The LEDs are indexed by
i ∈ [1,2,3,4].
"""
struct SysLED
type SysLED <: IO_Object
i::Int32
brightness_filestream::IOStream
function SysLED(i::Int32)
i [1,2,3,4] && error("Invalid SysLED index: $i")
#Note, in the future we should interface to config and retrieve IOStream from there
brightness_filestream = open("/sys/class/leds/beaglebone:green:usr$(i-1)/brightness","r+")
return new(i, brightness_filestream)
end
end
function write!(::SysLED, ind::Int32, val::Bool, debug::Bool=false)
"""
write!(led::SysLED, val::Bool, debug::Bool=false)
Turns the LED 'SysLed' on/off for val = true/false respectively.
"""
function write!(led::SysLED, val::Bool, debug::Bool=false)
debug && return
ind [1,2,3,4] && error("Invalid SysLEND ind: $ind")
filename = "/sys/class/leds/beaglebone:green:usr$(ind-1)/brightness"
file = open(filename, "r+")
write(file, val ? "1" : "0")
close(file)
return
write(led.brightness_filestream, val ? "1" : "0")
seekstart(led.brightness_filestream)
end
function read(::SysLED, ind::Int32, debug::Bool=false)
"""
l = read(led::SysLED, debug::Bool=false)
Reads the current brightness value from the LED 'SysLED'.
"""
function read(led::SysLED, debug::Bool=false)
debug && return
ind [1,2,3,4] && error("Invalid SysLEND ind: $ind")
filename = "/sys/class/leds/beaglebone:green:usr$(ind-1)/brightness"
file = open(filename, "r")
l = read(file,Char)
l = read(led.brightness_filestream, Char)
(l != '1' && l != '0') && error("Invalid value \"$l\" read from SysLed")
close(file)
return l == '1'
seekstart(led.brightness_filestream)
return l
end
"""
teardown(led::SysLED, debug::Bool=false)
Closes all open filestreams for the SysLED 'led'.
"""
function teardown(led::SysLED, debug::Bool=false)
debug && return
close(led.brightness_filestream)
end
......@@ -6,6 +6,7 @@ function precompile_bb()
clientside = connect(3001)
#Precompile serialize
initdev("debug", Int32(1))
serialize(clientside, (true, Int32(2), ("debug", Int32(1), true), ("debug", Int32(1), (1,2.0,"asd"))))
serialize(clientside, (true, Int32(2), ("debug", Int32(1), Int32(1)),
("debug", Int32(1), 1.0)))
......@@ -22,18 +23,22 @@ function precompile_bb()
debug = true
#Precompile SysLED
led = SysLED()
write!(led, Int32(1), true, debug)
read(led, Int32(1), debug)
led = initdev("sysled",Int32(1))
write!(led, true, debug)
read(led, debug)