Skip to content

Commit

Permalink
Add initial RMT primitives (#543)
Browse files Browse the repository at this point in the history
  • Loading branch information
lask authored Mar 15, 2022
1 parent 86ac388 commit 4d75817
Show file tree
Hide file tree
Showing 5 changed files with 653 additions and 1 deletion.
281 changes: 281 additions & 0 deletions lib/rmt.toit
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
// Copyright (C) 2022 Toitware ApS. All rights reserved.
// Use of this source code is governed by an MIT-style license that can be
// found in the lib/LICENSE file.
import gpio
import binary show LITTLE_ENDIAN

/**
Support for the ESP32 Remote Control (RMT).
A $Channel corresponds to a channel in the ESP32 RMT controller.
$Signals represent a collection of signals to be sent by the RMT controller.
*/

/**
A collection of signals to be transferred or received with the RMT controller.
An RMT signal consists of a level (low or high) and a period (the number of
ticks the level is sustained).
# Advanced
The period is specified in number of ticks, so the actual time the level is
sustained is determined by the RMT controller configuration.
At the lower level, a signal consists of 16 bits: 15 bits for the period and 1
bit for the level. Signals must be transfered as pairs also known as an item.
*/
class Signals:
/** The number of signals in the collection. */
size/int

bytes_/ByteArray

/**
Creates a collection of signals of the given $size.
All signals are initialized to 0 period and 0 level.
# Advanced
If the given $size is not divisible by 2, then the byte array allocted for
$bytes_ is padded with two bytes to make the $bytes_ usable by the RMT
primitives.
*/
constructor .size:
bytes_ = ByteArray
round_up (size * 2) 4

/**
Creates signals that alternate between a level of 0 and 1 with the periods
given in the indexable collection $periods.
The level of the first signal is $first_level.
*/
constructor.alternating --first_level/int periods:
if first_level != 0 and first_level != 1: throw "INVALID_ARGUMENT"

return Signals.alternating periods.size --first_level=first_level: | idx |
periods[idx]

/**
Creates items that alternate between a level of 0 and 1 with the periods
given by successive calls to the block.
The $block is called with the signal index and the level it is created with.
The level of the first signal is $first_level.
*/
constructor.alternating size/int --first_level/int [block]:
if first_level != 0 and first_level != 1: throw "INVALID_ARGUMENT"

signals := Signals size
level := first_level
size.repeat:
signals.set_signal it (block.call it level) level
level ^= 1

return signals


// TODO what's a nice convenient constructor for populating Signals with known values?
/**
Creates a collection of signals from the given $bytes.
The $bytes size must be divisible by 4.
# Advanced
The bytes must correspond to bytes produced by the RMT primitives. The
primitives operate with pairs of signals (called an item) which is the
reason the $bytes size must be divisible by 4.
*/
constructor.from_bytes bytes/ByteArray:
if bytes.size % 4 != 0: throw "INVALID_ARGUMENT"

bytes_ = bytes
size = bytes_.size / 2

/**
Gets the signal period of the $i'th signal.
The given $i must be in the range [0,$size[.
*/
signal_period i/int -> int:
check_bounds_ i
return signal_period_ i

/**
Gets the signal level of the $i'th signal.
The given $i must be in the range [0,$size[.
*/
signal_level i/int -> int:
check_bounds_ i
return signal_level_ i

/**
Set the $i'th signal to the given $period and $level.
The given $i must be in the range [0,$size[.
The given $period must be in the range [0,0x7FFF].
The given $level must be 0 or 1.
*/
set_signal i/int period/int level/int -> none:
check_bounds_ i
idx := i * 2
if not 0 <= period <= 0x7FFF or level != 0 and level != 1: throw "INVALID_ARGUMENT"

bytes_[idx] = period & 0xFF
bytes_[idx + 1] = (period >> 8 ) | (level << 7)

/**
Invokes the given $block on each signal of this signal collection.
The block is invoked with the period and the level of each signal.
*/
do [block]:
size.repeat:
block.call
signal_period_ it
signal_level_ it

check_bounds_ i:
if not 0 <= i < size: throw "OUT_OF_BOUNDS"

signal_level_ i -> int:
return bytes_[i * 2 + 1] >> 7

signal_period_ i -> int:
idx := i * 2
return (LITTLE_ENDIAN.uint16 bytes_ idx) & 0x7fff

/**
An RMT channel.
The channel must be configured after construction.
The channel can be configured for either RX or TX.
*/
class Channel:
num/int
pin/gpio.Pin

res_/ByteArray? := null

/**
Constructs a channel using the given $num using the given $pin.
The givn $num must be in the range [0,7] and must not be in use.
*/
constructor .pin .num:
res_ = rmt_use_ resource_group_ num

/**
Configure the channel for RX.
- $mem_block_num is the number of memory blocks (256 bytes or 128 signals)
used by this channel.
- $clk_div is the source clock divider. Must be in the range [0,255].
- $flags is the configuration flags. See the ESP-IDF documentation for available flags.
- $idle_threshold is the number of clock cycles the receiver will run without seeing an edge.
- $filter_en is whether the filter is enabled.
- $filter_ticks_thresh pulses shorter than this value is filtered away.
Only works with $filter_en. The value must be in the range [0,255].
# Advanced
If $mem_block_num is greater than 1, then it will take the memory of the
subsequent channels. For instance, if channel 2 is configured with a
$mem_block_num = 3, then channels 3 and 4 are unusable.
*/
config_rx
--mem_block_num/int=1
--clk_div/int=80
--flags/int=0
--idle_threshold/int=12000
--filter_en/bool=true
--filter_ticks_thresh/int=100
--rx_buffer_size=128:
rmt_config_rx_ pin.num num mem_block_num clk_div flags idle_threshold filter_en filter_ticks_thresh rx_buffer_size

/**
Configure the channel for TX.
- $mem_block_num is the number of memory blocks (256 bytes or 128 signals)
used by this channel.
- $clk_div is the source clock divider. Must be in the range [0,255].
- $flags is the configuration flags. See the ESP-IDF documentation for available flags.
- $carrier_en is whether a carrier wave is used.
- $carrier_freq_hz is the frequency of the carrier wave.
- $carrier_level is the way the carrier way is modulated.
Set to 1 to transmit on low output level and 0 to transmit on high output level.
- $carrier_duty_percent is the proportion of time the carrier wave is low.
- $loop_en is whether the transmitter continously writes the provided signals in a loop.
- $idle_output_en is whether the transmitter outputs when idle.
- $idle_level is the level transmitted by the transmitter when idle.
# Advanced
If $mem_block_num is greater than 1, then it will take the memory of the
subsequent channels. For instance, if channel 2 is configured with a
$mem_block_num = 3, then channels 3 and 4 are unusable.
*/
config_tx
--mem_block_num/int=1
--clk_div/int=80
--flags/int=0
--carrier_en/bool=false
--carrier_freq_hz/int=38000
--carrier_level/int=1
--carrier_duty_percent/int=33
--loop_en/bool=false
--idle_output_en/bool=true
--idle_level/int=0:
rmt_config_tx_ pin.num num mem_block_num clk_div flags carrier_en carrier_freq_hz carrier_level carrier_duty_percent loop_en idle_output_en idle_level

close:
if res_:
rmt_unuse_ resource_group_ res_
res_ = null

/** Transfers the given $signals over the given $channel.*/
transfer channel/Channel signals/Signals -> none:
rmt_transfer_ channel.num signals.bytes_

/**
Transfers the given $signals while simultaneously receiving.
The $signals are transferred over the given $tx channel and signals are received on the $rx channel.
The given $max_returned_bytes specifies the maximum byte size of the returned signals.
*/
transfer_and_receive --rx/Channel --tx/Channel signals/Signals max_returned_bytes/int -> Signals:
result := rmt_transfer_and_read_ tx.num rx.num signals.bytes_ max_returned_bytes
return Signals.from_bytes result

resource_group_ ::= rmt_init_

rmt_init_:
#primitive.rmt.init

rmt_use_ resource_group channel_num:
#primitive.rmt.use

rmt_unuse_ resource_group resource:
#primitive.rmt.unuse

rmt_config_rx_ pin_num/int channel_num/int mem_block_num/int clk_div/int flags/int
idle_threshold/int filter_en/bool filter_ticks_thresh/int rx_buffer_size/int:
#primitive.rmt.config_rx

rmt_config_tx_ pin_num/int channel_num/int mem_block_num/int clk_div/int flags/int
carrier_en/bool carrier_freq_hz/int carrier_level/int carrier_duty_percent/int
loop_en/bool idle_output_en/bool idle_level/int:
#primitive.rmt.config_tx

rmt_transfer_ tx_ch/int signals_bytes/*/Blob*/:
#primitive.rmt.transfer

rmt_transfer_and_read_ tx_ch/int rx_ch/int signals_bytes/*/Blob*/ max_output_len/int:
#primitive.rmt.transfer_and_read
28 changes: 27 additions & 1 deletion src/primitive.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ namespace toit {
M(i2s, MODULE_I2S) \
M(spi, MODULE_SPI) \
M(uart, MODULE_UART) \
M(rmt, MODULE_RMT) \
M(crypto, MODULE_CRYPTO) \
M(encoding,MODULE_ENCODING) \
M(font, MODULE_FONT) \
Expand Down Expand Up @@ -369,6 +370,15 @@ namespace toit {
PRIMITIVE(write, 6) \
PRIMITIVE(read, 1) \

#define MODULE_RMT(PRIMITIVE) \
PRIMITIVE(init, 0) \
PRIMITIVE(use, 2) \
PRIMITIVE(unuse, 2) \
PRIMITIVE(config_rx, 9) \
PRIMITIVE(config_tx, 12) \
PRIMITIVE(transfer, 2) \
PRIMITIVE(transfer_and_read, 4) \

#define MODULE_CRYPTO(PRIMITIVE) \
PRIMITIVE(sha1_start, 1) \
PRIMITIVE(sha1_add, 4) \
Expand Down Expand Up @@ -769,6 +779,7 @@ namespace toit {
#define _A_T_X509ResourceGroup(N, name) MAKE_UNPACKING_MACRO(X509ResourceGroup, N, name)
#define _A_T_PWMResourceGroup(N, name) MAKE_UNPACKING_MACRO(PWMResourceGroup, N, name)
#define _A_T_RpcResourceGroup(N, name) MAKE_UNPACKING_MACRO(RpcResourceGroup, N, name)
#define _A_T_RMTResourceGroup(N, name) MAKE_UNPACKING_MACRO(RMTResourceGroup, N, name)

#define _A_T_Resource(N, name) MAKE_UNPACKING_MACRO(Resource, N, name)
#define _A_T_Directory(N, name) MAKE_UNPACKING_MACRO(Directory, N, name)
Expand Down Expand Up @@ -896,10 +907,25 @@ namespace toit {
_A_T_##t10(9, n10); \
_A_T_##t11(10, n11);

#define _OVERRIDE(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, NAME, ...) NAME
#define _A_24(t1, n1, t2, n2, t3, n3, t4, n4, t5, n5, t6, n6, t7, n7, t8, n8, t9, n9, t10, n10, t11, n11, t12, n12) \
_A_T_##t1(0, n1); \
_A_T_##t2(1, n2); \
_A_T_##t3(2, n3); \
_A_T_##t4(3, n4); \
_A_T_##t5(4, n5); \
_A_T_##t6(5, n6); \
_A_T_##t7(6, n7); \
_A_T_##t8(7, n8); \
_A_T_##t9(8, n9); \
_A_T_##t10(9, n10); \
_A_T_##t11(10, n11); \
_A_T_##t12(11, n12);

#define _OVERRIDE(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, NAME, ...) NAME

#define ARGS(...) \
_OVERRIDE(__VA_ARGS__, \
_A_24, _ODD, \
_A_22, _ODD, \
_A_20, _ODD, \
_A_18, _ODD, \
Expand Down
Loading

0 comments on commit 4d75817

Please sign in to comment.