304 lines
11 KiB
Python
304 lines
11 KiB
Python
|
|
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()
|