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()