Further insight into brushed motor output control by CyberBrick core MicroPython code.
The PWM output is controlled via MicroPython.timer0 class Timer – control hardware timers — MicroPython latest documentation at 50 Hz PWM frequency and has only 20 steps (various output duty cycles) per movement direction (20 steps forward, 20 steps backwards plus off, thus 41 various steps/options per motor channel).
20 steps per direction is pretty coarse when compared with typical todays RC toys, where typically 9 to 12 bits per proportional channel are used (512 to 4096 steps over both movement directions combined). Also most todays motor controllers run at higher PWM frequencies, typically between 1 and 30 kHz, whereas indeed there are higher switching losses at higher frequencies. Higher frequencies cause less vibration, especially on smaller motors with not so much inertia weight.
The resolution in set_angle() mode is 127-25=102 steps or 1.77°, which is not great, especially for steering a model. In set_speed() mode the resolution is 201 steps (integers from -100 to +100).
This is very good work, @xrk ! Just the thing needed to improve this project!
Re Motor PWM resolution, that may be a result of having to control two bits for each motor rather than the single bit needed for the servo. The servo outputs are direct output to pins, whereas the motor requires code (and callback overhead) for each edge on the motor output. Just a thought… As you say, pretty coarse in any case.
One of the motor output pins is always low, when the motors are not at idle, so that would not be a big deal, while updating the PWM value, to change to another PWM channel.
Got initial rudimentary ESP-NOW test communication working between two CyberBrick Cores, both w/o having to erase existing code on them. Used heavily MicroPython: ESP-NOW with ESP32 (Getting Started) | Random Nerd Tutorials as basis. The only modification to stock setup is that I commented out all lines on boot.py except the last and changed the application to be started from stock ./app/rc_main.py to the ESP-NOW transmitting and receiving codes below.
This first test is uni-directional, but bi-directional communication (e.g. telemetry from the model) is also possible with ESP-NOW.
MicroPython code for the transmitting side:
import network
import aioespnow
import asyncio
# Initialize Wi-Fi in station mode
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1) # Set channel explicitly if packets are not delivered
sta.disconnect()
# Initialize AIOESPNow
e = aioespnow.AIOESPNow()
try:
e.active(True)
except OSError as err:
print("Failed to initialize AIOESPNow:", err)
raise
# Receiver's MAC address (Replace with your receiver MAC aa:bb:cc:dd:ee:ff!)
receiver_mac = b'\xaa\xbb\xcc\xdd\xee\xff'
# Add peer
try:
e.add_peer(receiver_mac)
except OSError as err:
print("Failed to add peer:", err)
raise
# Async function to send messages
async def send_messages(e, peer):
message_count = 0
while True:
try:
message = f"Ping from CyberBrick transmitter Core! Message number: #{message_count}"
if await e.asend(peer, message, sync=True):
print(f"Sent message: {message}")
else:
print("Failed to send message")
message_count += 1
await asyncio.sleep(1) # Send every 1 second
except OSError as err:
print("Error:", err)
await asyncio.sleep(5)
# Main async function
async def main(e, peer):
await send_messages(e, peer)
# Run the async program
try:
asyncio.run(main(e, receiver_mac))
except KeyboardInterrupt:
print("Stopping sender...")
e.active(False)
sta.active(False)
Receiver side code:
import network
import aioespnow
import asyncio
# Initialize Wi-Fi in station mode
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(channel=1) # Set channel explicitly if packets are not received
sta.disconnect()
# Initialize AIOESPNow
e = aioespnow.AIOESPNow()
try:
e.active(True)
except OSError as err:
print("Failed to initialize AIOESPNow:", err)
raise
# Async function to receive messages
async def receive_messages(e):
while True:
try:
async for mac, msg in e:
print(f"Received message from {mac.hex()}: {msg.decode()}")
except OSError as err:
print("Error:", err)
await asyncio.sleep(5)
# Main async function
async def main(e):
# Run receive and stats tasks concurrently
await asyncio.gather(receive_messages(e))
# Run the async program
try:
asyncio.run(main(e))
except KeyboardInterrupt:
print("Stopping receiver...")
e.active(False)
sta.active(False)
Next step would be to get the CyberBrick brushed motor driver and servo code segments merged with the receiver side ESP-NOW example above. For testing the transmission could use some proportional channel values transmitted via the ESP-NOW link and observing the two brushed motor outputs and the four servo signals from an X11 receiver shield attached to the receiver side CyberBrick core.