.. note::
¡Hola! Bienvenido a la comunidad de entusiastas de SunFounder Raspberry Pi, Arduino y ESP32 en Facebook. Únete a otros entusiastas y profundiza en el mundo de Raspberry Pi, Arduino y ESP32.
**¿Por qué unirse?**
- **Soporte de expertos**: Resuelve problemas postventa y desafíos técnicos con la ayuda de nuestra comunidad y equipo.
- **Aprende y comparte**: Intercambia consejos y tutoriales para mejorar tus habilidades.
- **Vistas previas exclusivas**: Obtén acceso anticipado a anuncios de nuevos productos y adelantos.
- **Descuentos especiales**: Disfruta de descuentos exclusivos en nuestros productos más recientes.
- **Promociones festivas y sorteos**: Participa en sorteos y promociones de temporada.
👉 ¿Listo para explorar y crear con nosotros? Haz clic en [|link_sf_facebook|] y únete hoy mismo.
.. _3.1.6_py_pi5:
3.1.6 Control de Movimiento
=============================
Introducción
---------------
En esta lección, crearemos un dispositivo simple de detección y control
de movimiento. Utilizaremos el MPU6050 como sensor y un motor paso a paso
como dispositivo controlado. Al montar el MPU6050 en un guante, podrás
controlar el motor paso a paso rotando tu muñeca.
Componentes Necesarios
-------------------------
En este proyecto, necesitaremos los siguientes componentes.
.. image:: ../python_pi5/img/3.1.6_motion_list.png
:width: 800
:align: center
Diagrama Esquemático
-----------------------
============ ======== ======== ===
T-Board Name physical wiringPi BCM
GPIO18 Pin 12 1 18
GPIO23 Pin 16 4 23
GPIO24 Pin 18 5 24
GPIO25 Pin 22 6 25
SDA1 Pin 3
SCL1 Pin 5
============ ======== ======== ===
.. image:: ../python_pi5/img/3.1.6_motion_schematic.png
:align: center
Procedimientos Experimentales
---------------------------------
**Paso 1:** Construir el circuito.
.. image:: ../python_pi5/img/3.1.6_motion_control_circuit.png
**Paso 2:** Abre el archivo de código.
.. raw:: html
.. code-block::
cd ~/davinci-kit-for-raspberry-pi/python-pi5
**Paso 3:** Ejecuta el código.
.. raw:: html
.. code-block::
sudo python3 3.1.6_MotionControl.py
Al ejecutar el código, si el ángulo de inclinación de **mpu6050** en el eje
`Y `__\ **-**\ `axis `__
es mayor a **45°**, el motor paso a paso girará en sentido antihorario; si es menor a **-45°**, el motor girará en sentido horario.
.. warning::
Si aparece el error ``RuntimeError: Cannot determine SOC peripheral base address``, consulta :ref:`faq_soc`
**Código**
.. note::
Puedes **Modificar/Restablecer/Copiar/Ejecutar/Detener** el código a continuación. Antes de eso, debes acceder a la ruta del código fuente como ``davinci-kit-for-raspberry-pi/python-pi5``. Después de modificar el código, puedes ejecutarlo directamente para ver el efecto.
.. raw:: html
.. code-block:: python
#!/usr/bin/env python3
from gpiozero import OutputDevice
import smbus
import math
import time
# Inicializa los registros de gestión de energía para el MPU6050
power_mgmt_1 = 0x6b
power_mgmt_2 = 0x6c
# Configura la comunicación I2C con el MPU6050
bus = smbus.SMBus(1) # Inicializa SMBus
address = 0x68 # Dirección I2C de MPU6050
bus.write_byte_data(address, power_mgmt_1, 0) # Activa el MPU6050
# Inicializa los pines del motor a GPIO 18, 23, 24, 25
motorPin = [OutputDevice(pin) for pin in (18, 23, 24, 25)]
# Define parámetros de velocidad de rotación del motor
rolePerMinute = 15
stepsPerRevolution = 2048
# Calcula el retraso entre pasos para la RPM deseada
stepSpeed = (60 / rolePerMinute) / stepsPerRevolution
# Lee un solo byte desde la dirección I2C especificada
def read_byte(adr):
return bus.read_byte_data(address, adr)
# Lee una palabra (2 bytes) desde la dirección I2C especificada
def read_word(adr):
high = bus.read_byte_data(address, adr)
low = bus.read_byte_data(address, adr + 1)
val = (high << 8) + low
return val
# Lee una palabra en formato de complemento a 2
def read_word_2c(adr):
val = read_word(adr)
if val >= 0x8000:
return -((65535 - val) + 1)
else:
return val
# Calcula la distancia euclidiana entre dos puntos
def dist(a, b):
return math.sqrt((a * a) + (b * b))
# Calcula la rotación en el eje Y
def get_y_rotation(x, y, z):
radians = math.atan2(x, dist(y, z))
return -math.degrees(radians)
# Calcula la rotación en el eje X
def get_x_rotation(x, y, z):
radians = math.atan2(y, dist(x, z))
return math.degrees(radians)
# Obtiene el ángulo de inclinación del MPU6050
def mpu6050():
accel_xout = read_word_2c(0x3b)
accel_yout = read_word_2c(0x3d)
accel_zout = read_word_2c(0x3f)
accel_xout_scaled = accel_xout / 16384.0
accel_yout_scaled = accel_yout / 16384.0
accel_zout_scaled = accel_zout / 16384.0
angle = get_y_rotation(accel_xout_scaled, accel_yout_scaled, accel_zout_scaled)
return angle
# Controla la rotación del motor paso a paso
def rotary(direction):
if direction == 'c':
# Secuencia de rotación en sentido horario
for j in range(4):
for i in range(4):
if 0x99 >> j & (0x08 >> i):
motorPin[i].on()
else:
motorPin[i].off()
time.sleep(stepSpeed)
elif direction == 'a':
# Secuencia de rotación en sentido antihorario
for j in range(4):
for i in range(4):
if 0x99 << j & (0x08 >> i):
motorPin[i].on()
else:
motorPin[i].off()
time.sleep(stepSpeed)
# Bucle principal para leer continuamente el ángulo de inclinación y controlar el motor
try:
while True:
angle = mpu6050()
if angle >= 45:
rotary('a') # Gira en sentido antihorario para inclinación positiva
elif angle <= -45:
rotary('c') # Gira en sentido horario para inclinación negativa
except KeyboardInterrupt:
# Apaga todos los pines del motor al interrumpir con teclado
for pin in motorPin:
pin.off()
**Explicación del Código**
#. El script comienza importando las bibliotecas necesarias. ``gpiozero`` para controlar los pines GPIO, ``smbus`` para la comunicación I2C, ``math`` para operaciones matemáticas y ``time`` para introducir retardos.
.. code-block:: python
#!/usr/bin/env python3
from gpiozero import OutputDevice
import smbus
import math
import time
#. Configura la comunicación I2C con el sensor MPU6050. ``power_mgmt_1`` y ``power_mgmt_2`` son registros para gestionar la energía del sensor. El sensor se "despierta" escribiendo en ``power_mgmt_1``.
.. code-block:: python
# Inicializa los registros de gestión de energía para el MPU6050
power_mgmt_1 = 0x6b
power_mgmt_2 = 0x6c
# Configura la comunicación I2C con el MPU6050
bus = smbus.SMBus(1) # Inicializa SMBus
address = 0x68 # Dirección I2C del MPU6050
bus.write_byte_data(address, power_mgmt_1, 0) # Activa el MPU6050
#. Inicializa los pines GPIO (18, 23, 24, 25) en la Raspberry Pi para controlar el motor paso a paso. Cada pin está asociado con una bobina en el motor.
.. code-block:: python
# Inicializa los pines del motor a GPIO 18, 23, 24, 25
motorPin = [OutputDevice(pin) for pin in (18, 23, 24, 25)]
#. Define las revoluciones por minuto (RPM) del motor y el número de pasos por revolución. ``stepSpeed`` calcula el retraso entre pasos para alcanzar las RPM deseadas, asegurando un funcionamiento suave del motor.
.. code-block:: python
# Define parámetros de velocidad de rotación del motor
rolePerMinute = 15
stepsPerRevolution = 2048
# Calcula el retraso entre pasos para la RPM deseada
stepSpeed = (60 / rolePerMinute) / stepsPerRevolution
#. Estas funciones se utilizan para la comunicación I2C. ``read_byte`` lee un solo byte de una dirección dada, mientras que ``read_word`` lee dos bytes (una palabra) combinándolos en un solo valor mediante operaciones de bits (``<<`` y ``+``).
.. code-block:: python
# Lee un byte desde la dirección I2C especificada
def read_byte(adr):
return bus.read_byte_data(address, adr)
# Lee una palabra (2 bytes) desde la dirección I2C especificada
def read_word(adr):
high = bus.read_byte_data(address, adr)
low = bus.read_byte_data(address, adr + 1)
val = (high << 8) + low
return val
#. Esta función convierte la palabra leída en formato de complemento a 2, lo cual es útil para interpretar valores con signo de los datos del sensor. Esta conversión es necesaria para manejar lecturas negativas.
.. code-block:: python
# Lee una palabra en formato de complemento a 2
def read_word_2c(adr):
val = read_word(adr)
if val >= 0x8000:
return -((65535 - val) + 1)
else:
return val
#. ``dist`` calcula la distancia euclidiana entre dos puntos, utilizada en los cálculos de rotación. ``get_y_rotation`` y ``get_x_rotation`` calculan los ángulos de rotación a lo largo de los ejes Y y X, respectivamente, usando la función ``atan2`` de la biblioteca ``math`` y convirtiendo el resultado a grados.
.. code-block:: python
# Calcula la distancia euclidiana entre dos puntos
def dist(a, b):
return math.sqrt((a * a) + (b * b))
# Calcula la rotación en el eje Y
def get_y_rotation(x, y, z):
radians = math.atan2(x, dist(y, z))
return -math.degrees(radians)
# Calcula la rotación en el eje X
def get_x_rotation(x, y, z):
radians = math.atan2(y, dist(x, z))
return math.degrees(radians)
#. Esta función lee los datos del acelerómetro del sensor MPU6050, escala las lecturas y calcula el ángulo de inclinación usando la función ``get_y_rotation``. La función ``read_word_2c`` lee datos del sensor en formato de complemento a 2 para manejar valores negativos.
.. code-block:: python
# Obtiene el ángulo de inclinación del MPU6050
def mpu6050():
accel_xout = read_word_2c(0x3b)
accel_yout = read_word_2c(0x3d)
accel_zout = read_word_2c(0x3f)
accel_xout_scaled = accel_xout / 16384.0
accel_yout_scaled = accel_yout / 16384.0
accel_zout_scaled = accel_zout / 16384.0
angle = get_y_rotation(accel_xout_scaled, accel_yout_scaled, accel_zout_scaled)
return angle
#. La función ``rotary`` controla la rotación del motor paso a paso. Ejecuta una secuencia de pasos para la rotación en sentido horario o antihorario, según el parámetro ``direction``. La secuencia involucra activar o desactivar pines específicos del motor en un patrón.
.. code-block:: python
# Controla la rotación del motor paso a paso
def rotary(direction):
if direction == 'c':
# Secuencia de rotación en sentido horario
for j in range(4):
for i in range(4):
if 0x99 >> j & (0x08 >> i):
motorPin[i].on()
else:
motorPin[i].off()
time.sleep(stepSpeed)
elif direction == 'a':
# Secuencia de rotación en sentido antihorario
for j in range(4):
for i in range(4):
if 0x99 << j & (0x08 >> i):
motorPin[i].on()
else:
motorPin[i].off()
time.sleep(stepSpeed)
#. El bucle principal lee continuamente el ángulo de inclinación del sensor MPU6050 y controla la dirección de rotación del motor según el ángulo. Si el programa se interrumpe (por ejemplo, mediante una interrupción de teclado), apaga todos los pines del motor por seguridad.
.. code-block:: python
# Bucle principal para leer continuamente el ángulo de inclinación y controlar el motor
try:
while True:
angle = mpu6050()
if angle >= 45:
rotary('a') # Gira en sentido antihorario para inclinación positiva
elif angle <= -45:
rotary('c') # Gira en sentido horario para inclinación negativa
except KeyboardInterrupt:
# Apaga todos los pines del motor al interrumpir con el teclado
for pin in motorPin:
pin.off()