adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.02k stars 1.19k forks source link

PulseOut inverted (idle_state = carrier active) #5153

Open s-light opened 3 years ago

s-light commented 3 years ago

iam designing a light barrier for speed measuring - to be immune against environment-light i use a VISHAY TSSP96038 IR Receiver. my ItsyBitsy M4 generates a 38kHz stream to pulse the sending IR-LED - that is working fine with

# 50% duty cycle at 38kHz.
pwm = pwmio.PWMOut(
    board.D7,
    frequency=38000,
    duty_cycle=2 ** 15,
)

for speed & respons-tests i want to setup a test case where i pause this stream for some us. as fare as i can tell currently the PulseOut only works the other way around: default state is off and the pulse-length the carrier-frequence is on. i need it the other way around:

i would like to have a option like idle_state=True or similar (named after the PulseIn argument):

pulse = pulseio.PulseOut(
    board.D7,
    frequency=38000,
    duty_cycle=2 ** 15,
    idle_state=True
)
dmcomm commented 3 years ago

I was previously thinking that idle_state=True would mean the line is high when it's not pulsing (useful for driving active-low circuits, and noting that some of these microcontrollers can sink more current than they can source).

This request is another meaning of inverting PulseOut that hadn't occurred to me.

HelenFoster commented 1 year ago

I noticed the esp-idf RMT API has something about this. https://docs.espressif.com/projects/esp-idf/en/v4.4/esp32s2/api-reference/peripherals/rmt.html#transmit-mode

Level of the RMT output, when the carrier is applied - carrier_level Enable the RMT output if idle - idle_output_en Set the signal level on the RMT output if idle - idle_level

It's not very clear. Looking at the S2 TRM, RMT_IDLE_OUT_EN_CHn selects between RMT_IDLE_OUT_LV_CHn and the most recent level fetched from RAM. Seems like idle_output_en probably contributes to that but isn't the same thing. And I think what we're calling the "RMT output" is pre-modulation, so the OP's request is supported, but active-low circuits with carrier are not. Edit: Actually, the latter might be what carrier_level is for.

n0xa commented 1 year ago

Bumping this because I'm eunning into it on a new (to me, and CircuitPython) esp32 dev kit, the M5Stick C Plus, and it affects both the LED and the IR, both for PulseIO and DigitalIo, for example, this will turn the LED on:

led = DigitalInOut(board.LED)
led.direction = Direction.OUTPUT
led.value = False

For things like digitalIO it's mostly just annoying and easy to work around. For trying to control IR devices, it makes the IR transmitter unusable. When I use pulseio on either the IR transmitter or the LED, it stays on, and flickers to dark when bursting pulses (vs being dark and sending IR pulses). This issue was the only thing I found when trying to figure out how to invert the state of pulseio.PulseOut. Not sure if this is something we can tweak per-board at a lower level, or if we really do need an arg for PulseOut

dhalbert commented 1 year ago

Just to be clear, the visible and IR LEDs on the M5Stick C Plus are active low: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/schematic/Core/M5StickC_Plus/StickC_Plus_20200616.pdf image

n0xa commented 1 year ago

I have verified that if I go in to esp-idf/components/driver/include/driver/rmt.h and do the following:

the infrared LED transmitter looks to be behaving properly, visually (through an IR sensitive camera on my phone) however the code that works to control my TV through my Adafruit CPX isn't working to control my TV through my M5StickC-Plus quite yet. I'll keep experimenting with it. I don't know a good way to make a change like this on a per-board level, or surface it through a boolean option to Pulseio, though.

HelenFoster commented 1 year ago

For the esp-idf RMT, I tried a bunch of combinations via Arduino. (I had a voltage divider on the output to see if it went high-impedence for any of them, but it didn't.)

idle_output_en - "Enable the RMT output if idle" - I still have no idea what false is doing. Assume it's true for the rest.

idle_level - "Set the signal level on the RMT output if idle" - as with CircuitPython's PulseIn, the output level when we aren't sending any pulses.

carrier_en - "Enable the RMT carrier signal" - basically what you would expect.

carrier_level - "Level of the RMT output, when the carrier is applied" - ignored if carrier_en is false. Otherwise, the level which gets turned into the carrier while sending pulses. Meaning if it's HIGH, the 0-pulses are output as LOW and the 1-pulses are output as carrier. If it's LOW, the 0-pulses are output as carrier and the 1-pulses are output as HIGH. This gives strange and useless results for carrier_level==idle_level.

It would make sense for carrier_level==idle_level to do what s-light requested, but anyway it doesn't, and I don't see anything else in the docs that would help with that.

The combination that can handle n0xa's request is idle HIGH and carrier LOW, but note that it's not actually inverting the pulse levels, so it would be necessary to invert the pulse levels in the array before sending it. Duty cycle is also not inverted, so if you're not using 50%, you'll need to change that too.

Also note #6799 where the carrier will need to be disabled for the 100% duty cycle case.

esp-idf 5.1 has a completely different API. Sounds like rmt_carrier_config_t::always_on does s-light's request and rmt_tx_channel_config_t::invert_out does n0xa's more simply.

https://docs.espressif.com/projects/esp-idf/en/v4.4/esp32s2/api-reference/peripherals/rmt.html

https://docs.espressif.com/projects/esp-idf/en/v5.1/esp32s2/api-reference/peripherals/rmt.html

rmt4