agrobot_base/Python/oidlec/controle.py

304 lines
11 KiB
Python
Raw Normal View History

2025-01-10 18:52:38 +00:00
import serial
import struct
import threading
import time
# Configuração da porta serial
SERIAL_PORT = "COM114" # Substitua pela sua porta correta
BAUD_RATE = 115200
DEVICE_ID = 1 # Endereço do dispositivo Modbus
Heartbeat = False
ModoControle = 0
N_Polos = 15
# Mapeamento de registradores
REG_HEARTBEAT = 0x1770
REG_MODOCONTROLE = 0x1771
REG_TORQUEMAX = 0x178C
REGISTERS = {
"velocidade": 0x1389, # Velocidade em ERPM 9
"corrente": 0x138E, # Corrente em escala de 0.01 A 7
"tensao": 0x138D, # Tensão em V (corrigido) 7
"potencia": 0x138C, # Potência em W (corrigido) 7
"codigo_erro": 0x1388, # Código de erro do motor 7
"ciclo_trabalho": 0x138B, # Ciclo de trabalho (% em escala de 0.1) 7
"temperatura": 0x1390 # Temperatura em °C 7
}
# Função para calcular CRC16 Modbus
def calculate_crc(data):
crc = 0xFFFF
for pos in data:
crc ^= pos
for _ in range(8):
if crc & 0x0001:
crc >>= 1
crc ^= 0xA001
else:
crc >>= 1
return crc.to_bytes(2, byteorder='little')
def build_command(slave_address, function_code, register, value=None, num_regs=1):
"""
Constrói um comando Modbus RTU válido, suportando:
- 0x03 (Leitura de Holding Registers)
- 0x04 (Leitura de Input Registers)
- 0x06 (Escrita em um único registrador)
- 0x10 (Escrita em múltiplos registradores)
"""
frame = struct.pack(">BBH", slave_address, function_code, register)
if function_code in (0x03, 0x04): # Leitura de registradores
frame += struct.pack(">H", num_regs) # Número de registradores a ler
elif function_code == 0x06: # Escrita em único registrador
frame += struct.pack(">h", value) # Suporta valores negativos
elif function_code == 0x10: # Escrita em múltiplos registradores
if isinstance(value, int) and num_regs == 2:
# Conversão de um inteiro para dois registradores (32 bits)
value_high = (value >> 16) & 0xFFFF # Parte alta
value_low = value & 0xFFFF # Parte baixa
frame += struct.pack(">H", num_regs) # Número de registradores
frame += struct.pack("B", num_regs * 2) # Número de bytes de dados
frame += struct.pack(">H", value_high) # Registrador alto
frame += struct.pack(">H", value_low) # Registrador baixo
elif isinstance(value, list) and len(value) == num_regs:
frame += struct.pack(">H", num_regs) # Número de registradores
frame += struct.pack("B", num_regs * 2) # Número de bytes de dados
for v in value:
frame += struct.pack(">H", v) # Adiciona cada valor
else:
raise ValueError("Para função 0x10, 'value' deve ser um inteiro (32 bits) ou uma lista com valores correspondentes a 'num_regs'.")
# Adiciona CRC e retorna comando
frame += calculate_crc(frame)
return frame
def send_command(ser, command):
"""
Envia um comando Modbus RTU e aguarda a resposta correta dinamicamente.
"""
ser.write(command)
time.sleep(0.05) # Pequeno delay inicial
# Lendo os primeiros 3 bytes (ID, Função, Nº de bytes de resposta)
response = ser.read(3)
if len(response) < 3:
print(f"Erro: Resposta Modbus incompleta (Recebido: {response.hex()})")
return None
# Extraindo código de função recebido
function_code = response[1]
if function_code in (0x03, 0x04):
# Se for leitura (0x03 ou 0x04), usar o byte 3 para determinar o número de bytes de dados
num_bytes = response[2]
total_bytes = 3 + num_bytes + 2 # 3 iniciais + dados + 2 CRC
else:
# Se for outro comando (ex: 0x06, 0x10), assume resposta fixa de 8 bytes
total_bytes = 8
# Ler o restante dos bytes esperados
remaining_data = ser.read(total_bytes - 3)
response += remaining_data
# Se a resposta não tiver o tamanho esperado, exibir erro
if len(response) != total_bytes:
print(f"Erro: Resposta Modbus incompleta ({len(response)} de {total_bytes} bytes recebidos).")
print(f"Resposta parcial: {response.hex()}")
return None
# Retornar resposta completa
return response
# Função para controle de velocidade
def definir_velocidade(ser, valor):
"""
Define a velocidade do motor:
- Positivo: sentido horário.
- Negativo: sentido anti-horário.
- Zero: desliga o motor.
"""
global ModoControle
funcao = 0x06
velocidade = 0
registro = 0x1772
n_reg = 1
if ModoControle == 0: # 100 = 1A
registro = 0x1772
velocidade = valor * 100
if ModoControle == 1: # RPM = ERPM / N_POLOS
registro = 0x1773
velocidade = valor * N_Polos
funcao = 0x10
n_reg = 2
if ModoControle == 2: # 100 = 10%
registro = 0x1775
velocidade = valor * 10
if ModoControle == 6: # 100 = 1A
registro = 0x177E
velocidade = valor * 100
command = build_command(DEVICE_ID, funcao, registro, value=int(velocidade), num_regs=n_reg)
response = send_command(ser, command)
print(f"Velocidade definida para {velocidade}. Comando: {command.hex()} - Resposta: {response.hex()}")
# Função para thread do heartbeat
def heartbeat_thread(ser):
global Heartbeat
global heartbeat_ativo
while heartbeat_ativo:
try:
idxHeartbeat = 1 if Heartbeat else 0
command = build_command(DEVICE_ID, 0x06, REG_HEARTBEAT, value=idxHeartbeat)
send_command(ser, command)
time.sleep(0.2) # Envia heartbeat a cada 200ms
Heartbeat = not Heartbeat
except Exception as e:
print(f"Erro no heartbeat: {e}")
break
# Função para ler parâmetros e corrigir valores
def ler_parametros(ser):
print("\n📊 **Parâmetros do Motor**")
for nome, reg in REGISTERS.items():
try:
# Criando comando de leitura Modbus (2 registradores = 4 bytes)
command = build_command(DEVICE_ID, 0x04, reg, None, 2)
response = send_command(ser, command)
if len(response) >= 7: # Garantir que há pelo menos 7 bytes na resposta (ID + Função + Byte Count + Dados + CRC)
num_bytes = response[2] # Número de bytes de dados na resposta
num_regs = num_bytes // 2 # Cada registrador Modbus tem 2 bytes
valor = 0.0
if num_regs == 1:
valor = struct.unpack(">H", response[3:5])[0] # 16 bits (unsigned)
elif num_regs == 2:
# Separar os dois registradores
parte_inteira = struct.unpack(">H", response[3:5])[0] # Primeiros 2 bytes
parte_decimal = struct.unpack(">H", response[5:7])[0] # Últimos 2 bytes
# Construir valor real
valor = float(parte_inteira) + (float(parte_decimal) / 10000) # Decimal com precisão de 4 casas
mensagem = f" - {command.hex()} -> {response.hex()}"
# Conversões conforme a escala de cada registro
if nome == "velocidade":
if num_regs == 2:
parte_inteira = struct.unpack(">h", response[3:5])[0] # 16 bits signed
parte_decimal = struct.unpack(">H", response[5:7])[0] # 16 bits unsigned
valor = float(parte_inteira) + (float(parte_decimal))
rpm = int(valor) // N_Polos # Ajustando para polos do motor
print(f"{nome.capitalize()}: {valor:.4f} ERPM ({rpm} RPM)")
elif nome == "corrente":
print(f"{nome.capitalize()}: {valor * 0.01:.4f} A")
elif nome == "tensao":
print(f"{nome.capitalize()}: {valor:.4f} V")
elif nome == "potencia":
print(f"{nome.capitalize()}: {valor:.4f} W")
elif nome == "ciclo_trabalho":
print(f"{nome.capitalize()}: {valor * 0.1:.4f}%")
elif nome == "temperatura":
print(f"{nome.capitalize()}: {valor:.4f} °C")
else:
print(f"{nome.capitalize()}: {valor:.4f}")
else:
print(f"{nome.capitalize()}: Erro na leitura!")
#print(f"{mensagem}")
except Exception as e:
print(f"{nome.capitalize()}: Erro ({e})")
def definir_modo_controle(ser, modo):
"""
Define o modo de controle do motor (valor entre 0 e 10).
"""
global ModoControle
try:
if 0 <= modo <= 10:
command = build_command(DEVICE_ID, 0x06, REG_MODOCONTROLE, value=modo)
response = send_command(ser, command)
print(f"Modo de controle definido para {modo}. Resposta: {response.hex()}")
ModoControle = modo
else:
print("Modo inválido! Digite um número entre 0 e 10.")
except ValueError:
print("Entrada inválida! Digite um número entre 0 e 10.")
def definir_torque_maximo(ser, corrente):
valor = corrente * 100
command = build_command(DEVICE_ID, 0x06, REG_TORQUEMAX, value=valor)
response = send_command(ser, command)
print(f"Torque maximo definido para {corrente}A")
print(f"Comando: {command.hex()} - Resposta: {response.hex()}")
def menu():
global ModoControle
with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) as ser:
# Inicia a thread do heartbeat
threading.Thread(target=heartbeat_thread, args=(ser,), daemon=True).start()
while True:
print("\nMenu de Controle do Motor:")
print("1 - Definir Modo de Controle")
print("2 - Definir Velocidade (RPM)")
print("3 - Acionar Freio")
print("4 - Ler Parâmetros do Motor")
if (ModoControle == 1):
print("5 - Definir torque maximo")
print("0 - Sair")
opcao = input("Escolha uma opção: ")
if opcao == "1":
modo = int(input("\nDigite o modo de controle (0-10): "))
definir_modo_controle(ser, modo)
elif opcao == "2":
descricao = ""
if ModoControle == 0: # 100 = 1A
descricao = "corrente (A)"
if ModoControle == 1: # RPM
descricao = "RPM"
if ModoControle == 2: # 100 = 10%
descricao = "porcentagem"
velocidade = int(input(f"Digite a Velocidade em {descricao} (positivo/negativo ou 0 para desligar): "))
definir_velocidade(ser, velocidade)
if opcao == "3":
definir_velocidade(ser, 0)
modo_anterior = ModoControle
definir_modo_controle(ser, 6)
definir_velocidade(ser, 1)
time.sleep(1.0)
definir_modo_controle(ser, modo_anterior)
elif opcao == "4":
ler_parametros(ser)
if opcao == "5":
corrente = int(input("\nDigite a corrente de torque maximo em A: "))
definir_torque_maximo(ser, corrente)
elif opcao == "0":
print("Encerrando...")
break
else:
print("Opção inválida! Tente novamente.")
# Executar o menu interativo
if __name__ == "__main__":
heartbeat_ativo = True # Inicializa a flag do heartbeat
menu()