Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RMT primitives #543

Merged
merged 88 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from 87 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
2370109
WIP
Feb 17, 2022
6343c01
WIP
Feb 21, 2022
f17391f
WIP
Feb 23, 2022
77800f1
WIP
Feb 23, 2022
c464665
First stab at primtiives
Feb 25, 2022
e7173e8
Add lib file
Feb 25, 2022
facddf1
Update primitive parameter amount
Feb 25, 2022
b594a5f
Add primitives
Feb 25, 2022
bf0f0c6
Add resource group
Feb 25, 2022
ddb7f99
WIP on toit level rmt lib
Feb 25, 2022
059f50f
Add config to controller
Mar 1, 2022
57e2ab1
Add transfer and read to controller
Mar 1, 2022
5d62fe0
Add == operator to item
Mar 1, 2022
8d94d13
SWap Item from bytes constructor argument
Mar 1, 2022
00c6d23
Add tests
Mar 1, 2022
37815f7
Update config primitives
Mar 2, 2022
c6020e5
WIP
Mar 3, 2022
e7ca700
Merge remote-tracking branch 'origin/master' into lask-rmt-primitives
Mar 3, 2022
7ca4dc4
Fix RMT item serialization
Mar 4, 2022
c03fd2c
Add Item Toit doc
Mar 4, 2022
f3ad028
Check for error when starting rx
Mar 4, 2022
aaaa4bf
Add TODO
Mar 4, 2022
56d7467
Fix value initialization in Item construction from bytes
Mar 4, 2022
2a34f29
Add item serialization test
Mar 7, 2022
4ee9959
This seems to work
Mar 7, 2022
72e16ae
Remove val to item test primitive
Mar 7, 2022
a7e4491
Update comment with reason for external allocation
Mar 7, 2022
6aea331
Remove debug printing
Mar 7, 2022
0465f9e
Reset RX memory
Mar 7, 2022
84af9c8
Add reset mem comment
Mar 7, 2022
317d302
Remove debug printing
Mar 7, 2022
98f1c29
Preallocate result list
Mar 7, 2022
12277f1
Swap Item constructor parameters
Mar 7, 2022
ebad10b
Add ringbuffer flush
Mar 7, 2022
cd3f05a
Reduce receive timeout
Mar 7, 2022
591b9fb
Restructure rmt toit lib
Mar 7, 2022
d2d2f4c
Remove newlines
Mar 7, 2022
6dd2f96
Remove more newlines
Mar 7, 2022
bb97354
Rename value -> level
Mar 7, 2022
b21af00
Rewrite Item Toit doc
Mar 7, 2022
72bf74f
Make Item's byte access primitive
Mar 7, 2022
0664a7f
Remove unused channel config pin number parameter
Mar 7, 2022
a418238
Remove unused channel config channel number parameter
Mar 7, 2022
d796193
Add more docs
Mar 7, 2022
51c0a8d
Fix typo
Mar 7, 2022
265fb29
value -> level
Mar 7, 2022
481aed1
Apply suggestions from code review
lask Mar 8, 2022
ae825ff
Remove unnecessary reset
Mar 8, 2022
fe99d86
Remember to stop reading if we fail to retrieve an item
Mar 8, 2022
e49791d
Check for null rather than length when checking element received from…
Mar 8, 2022
fd19cc8
NULL -> null
Mar 8, 2022
07d7bb2
Only uninstall driver when applicable
Mar 8, 2022
94be811
Add a bit more Toit docs
Mar 8, 2022
9f88c21
Merge branch 'lask-rmt-primitives' of https://github.com/toitlang/toi…
Mar 8, 2022
3d8aae3
Make RX buffer size configurable
Mar 9, 2022
ae9a402
Merge remote-tracking branch 'origin/master' into lask-rmt-primitives
Mar 9, 2022
2f6038b
Update rtm API (WIP)
Mar 9, 2022
deebade
Add rmt items tests (WIP)
Mar 9, 2022
badd755
Add types to RMT Items methods
Mar 9, 2022
2ac1911
Fix incorrect Items indexing into backing bytes
Mar 9, 2022
7ac2355
Finish Items tests
Mar 9, 2022
a992dd2
Remove obsolete Item class from RMT library
Mar 9, 2022
6ef9607
Correct reference in docs
Mar 10, 2022
501dcea
Add more docs
Mar 10, 2022
94139b5
Check size of received data before copying
Mar 10, 2022
74332d4
Fix renamed bytes -> bytes_ in test
Mar 10, 2022
6b566c1
read -> receive
Mar 10, 2022
8678f39
Add limitations on the channel number to Toit doc
Mar 10, 2022
7f7238c
Update lib/rmt.toit
lask Mar 11, 2022
77a89de
Rename Items -> Signals
Mar 11, 2022
e1f4e37
amount -> number
Mar 11, 2022
4a7d54c
Add alternating constructors
Mar 11, 2022
00179b6
Make set throw when level or period out of range
Mar 11, 2022
75ed26f
Expand docs
Mar 11, 2022
98ee3b8
Use LITTLE_ENDIAN to get period
lask Mar 11, 2022
69d40fb
Update mem block docs
Mar 11, 2022
24a4253
Merge branch 'lask-rmt-primitives' of https://github.com/toitlang/toi…
Mar 11, 2022
445b6aa
24 comes after 23
Mar 11, 2022
eb4fb1c
Add missing import
Mar 11, 2022
e8fc90e
merge ifs
Mar 11, 2022
2af2dc0
Use macro to initialize rmt configs
Mar 11, 2022
ce45369
Remove unnecessary const cast
Mar 11, 2022
6ed7048
Change cast style
Mar 11, 2022
7b08e25
Fix test
Mar 14, 2022
b6fc277
Add test of alternating constructor
Mar 14, 2022
2fe9ada
Naming and spelling
Mar 14, 2022
2ccd99c
Fix Erik's comment
Mar 14, 2022
2d9a4ba
Apply suggestions from code review
lask Mar 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 consits of 16 bits: 15 bits for the period and 1
lask marked this conversation as resolved.
Show resolved Hide resolved
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 patted with two bytes to make the $bytes_ usable by the RMT
lask marked this conversation as resolved.
Show resolved Hide resolved
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 [block]:
lask marked this conversation as resolved.
Show resolved Hide resolved
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 = level ^ 1
lask marked this conversation as resolved.
Show resolved Hide resolved

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might make sense to keep the configuration (and not apply it immediately), so that a channel can be used for read and writing.
The configuration could then be applied every time the direction switches.

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