domenica 23 novembre 2025

Cinematica Inversa su braccio robotico

Mi sono messo a leggiucchiare il libro Robotica: Modellistica, Pianificazione e Controllo (Bruno Siciliano,Lorenzo Sciavicco,Luigi Villani, Giuseppe Oriolo McGraw-Hill Libri Italia srl) ...leggiucchiare perche' e' un libro di teoria di robot pieno di matematica

 

Partendo dalle misure e dalla forma di un robot reale (braccio robotico antropomorfo tinkerkit a 5 gradi di liberta' piu' presa) con l'idea di tracciare una traiettoria circolare sul pavimento

In questo tipo si esercizio si definisce l'obbiettivo e si lascia all'algoritmo il calcolo della posa del robot


 

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation

# --- 1. MOTORE CINEMATICO (IK) ---
def cinematica_inversa_3R_planare(r, z, phi_deg, L1, L2, L3):
phi_rad = np.deg2rad(phi_deg)
# Calcolo posizione polso
# Con phi = -90, cos(-90)=0 e sin(-90)=-1
# Quindi w_r = r e w_z = z + L3
w_r = r - L3 * np.cos(phi_rad)
w_z = z - L3 * np.sin(phi_rad)
dist_sq = w_r**2 + w_z**2
# Teorema Coseno per Gomito
cos_theta2 = (dist_sq - L1**2 - L2**2) / (2 * L1 * L2)
if abs(cos_theta2) > 1: return []
soluzioni = []
for sigma in [1, -1]:
sin_theta2 = sigma * np.sqrt(1 - cos_theta2**2)
theta2 = np.arctan2(sin_theta2, cos_theta2)
alpha = np.arctan2(w_z, w_r)
beta = np.arctan2(L2 * np.sin(theta2), L1 + L2 * np.cos(theta2))
theta1 = alpha - beta
theta3 = phi_rad - theta1 - theta2
soluzioni.append((theta1, theta2, theta3))
return soluzioni

def ik_braccio(x, y, z, phi_pitch_deg, L_base, L_omero, L_ulna, L_mano):
theta_base = np.arctan2(y, x)
r_planare = np.sqrt(x**2 + y**2)
z_relativa = z - L_base
sols_2d = cinematica_inversa_3R_planare(r_planare, z_relativa, phi_pitch_deg, L_omero, L_ulna, L_mano)
return [(theta_base, t1, t2, t3) for (t1, t2, t3) in sols_2d]

def calcola_punti_robot(angles, L0, L1, L2, L3):
th_base, th_spalla, th_gomito, th_polso = angles
p0 = np.array([0, 0, 0]); p1 = np.array([0, 0, L0])
cos_b, sin_b = np.cos(th_base), np.sin(th_base)
r2 = L1 * np.cos(th_spalla); z2 = L0 + L1 * np.sin(th_spalla)
p2 = np.array([r2 * cos_b, r2 * sin_b, z2])
angle_cum = th_spalla + th_gomito
r3 = r2 + L2 * np.cos(angle_cum); z3 = z2 + L2 * np.sin(angle_cum)
p3 = np.array([r3 * cos_b, r3 * sin_b, z3])
angle_cum_tot = angle_cum + th_polso
r4 = r3 + L3 * np.cos(angle_cum_tot); z4 = z3 + L3 * np.sin(angle_cum_tot)
p4 = np.array([r4 * cos_b, r4 * sin_b, z4])
return np.array([p0, p1, p2, p3, p4])

# --- 2. LOGICA DI SELEZIONE OTTIMIZZATA ---
def scegli_soluzione_migliore(soluzioni, angoli_precedenti):
if not soluzioni: return None
migliore_sol = None
miglior_punteggio = -np.inf
for sol in soluzioni:
deviazione_gomito_rad = abs(sol[2])
# Criterio: Angolo aperto (>90 gradi interni)
score = 0
if deviazione_gomito_rad < np.pi/2:
score += 1000
else:
score -= 1000
if angoli_precedenti is not None:
diff = np.linalg.norm(np.array(sol) - np.array(angoli_precedenti))
score -= diff * 100
if score > miglior_punteggio:
miglior_punteggio = score
migliore_sol = sol
return migliore_sol

# --- 3. CONFIGURAZIONE PARAMETRI ---
L_BASE, L_OMERO, L_ULNA, L_MANO = 0.0715, 0.125, 0.125, 0.060

# >>> MODIFICA CHIAVE QUI <<<
# Impostiamo -90 gradi. Il polso punta verso il basso (asse -Z)
# Ortogonale al piano XY.
PHI_PITCH = -90

CENTER = np.array([0.15, 0.15, 0.0]) # Spostato un po' più avanti per facilitare la presa verticale
RADIUS = 0.03

# Home Position
HOME_POS = np.array([0.0, 0.15, 0.25])
START_CIRCLE = np.array([CENTER[0] + RADIUS, CENTER[1], CENTER[2]])

# Generazione Traiettoria
frames_approach = 40
traj_approach_x = np.linspace(HOME_POS[0], START_CIRCLE[0], frames_approach)
traj_approach_y = np.linspace(HOME_POS[1], START_CIRCLE[1], frames_approach)
traj_approach_z = np.linspace(HOME_POS[2], START_CIRCLE[2], frames_approach)

frames_circle = 80
angles = np.linspace(0, 2*np.pi, frames_circle)
traj_circle_x = CENTER[0] + RADIUS * np.cos(angles)
traj_circle_y = CENTER[1] + RADIUS * np.sin(angles)
traj_circle_z = np.full_like(angles, CENTER[2])

full_traj_x = np.concatenate([traj_approach_x, traj_circle_x])
full_traj_y = np.concatenate([traj_approach_y, traj_circle_y])
full_traj_z = np.concatenate([traj_approach_z, traj_circle_z])
TOTAL_FRAMES = len(full_traj_x)

# --- 4. ANIMAZIONE ---
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
ax.set_xlim(-0.1, 0.3); ax.set_ylim(-0.1, 0.3); ax.set_zlim(0, 0.4)
ax.set_xlabel('X'); ax.set_ylabel('Y'); ax.set_zlabel('Z')
ax.set_title(f'Tinkerkit Braccio - Polso Verticale ({PHI_PITCH}°)')

line_robot, = ax.plot([], [], [], 'o-', lw=4, color='tab:blue', label='Robot')
point_target, = ax.plot([], [], [], 'X', color='red', label='Target')
line_trace, = ax.plot([], [], [], '-', lw=1, color='red', alpha=0.5)

# Disegniamo un piccolo piano XY per riferimento visivo
xx, yy = np.meshgrid(np.linspace(-0.1, 0.3, 2), np.linspace(-0.1, 0.3, 2))
ax.plot_surface(xx, yy, np.zeros_like(xx), alpha=0.2, color='gray')

ax.legend()

prev_angles = None
history_x, history_y, history_z = [], [], []

def update(frame_idx):
global prev_angles
tx = full_traj_x[frame_idx]
ty = full_traj_y[frame_idx]
tz = full_traj_z[frame_idx]
sols = ik_braccio(tx, ty, tz, PHI_PITCH, L_BASE, L_OMERO, L_ULNA, L_MANO)
best_sol = scegli_soluzione_migliore(sols, prev_angles)
if best_sol:
prev_angles = best_sol
points = calcola_punti_robot(best_sol, L_BASE, L_OMERO, L_ULNA, L_MANO)
line_robot.set_data(points[:,0], points[:,1])
line_robot.set_3d_properties(points[:,2])
point_target.set_data([tx], [ty])
point_target.set_3d_properties([tz])
history_x.append(tx); history_y.append(ty); history_z.append(tz)
line_trace.set_data(history_x, history_y)
line_trace.set_3d_properties(history_z)
return line_robot, point_target, line_trace

ani = animation.FuncAnimation(fig, update, frames=TOTAL_FRAMES, interval=40, blit=False, repeat=True)
plt.show()


 

 il braccio segue la traiettoria circolare ma muovendosi in uno spazio reale sbatterebbe il gomito sul pavimento...Mettiamo la condizione che i giunti debbano avere coordinata Z sempre positiva


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation

# --- 1. PARAMETRI E COSTANTI GLOBALI ---
L_BASE, L_OMERO, L_ULNA, L_MANO = 0.0715, 0.125, 0.125, 0.060
PHI_PITCH = -90 # Polso verticale
CENTER = np.array([0.15, 0.0, 0.0]) # Z target = 0.0, come richiesto
RADIUS = 0.03

# --- 2. FUNZIONI CINEMATICHE (Invariate) ---
def cinematica_inversa_3R_planare(r, z, phi_deg, L1, L2, L3):
phi_rad = np.deg2rad(phi_deg)
w_r = r - L3 * np.cos(phi_rad)
w_z = z - L3 * np.sin(phi_rad)
dist_sq = w_r**2 + w_z**2
cos_theta2 = (dist_sq - L1**2 - L2**2) / (2 * L1 * L2)
if abs(cos_theta2) > 1: return []
soluzioni = []
for sigma in [1, -1]:
sin_theta2 = sigma * np.sqrt(1 - cos_theta2**2)
theta2 = np.arctan2(sin_theta2, cos_theta2)
alpha = np.arctan2(w_z, w_r)
beta = np.arctan2(L2 * np.sin(theta2), L1 + L2 * np.cos(theta2))
theta1 = alpha - beta
theta3 = phi_rad - theta1 - theta2
soluzioni.append((theta1, theta2, theta3))
return soluzioni

def ik_braccio(x, y, z, phi_pitch_deg, L_base, L_omero, L_ulna, L_mano):
theta_base = np.arctan2(y, x)
r_planare = np.sqrt(x**2 + y**2)
z_relativa = z - L_base
sols_2d = cinematica_inversa_3R_planare(r_planare, z_relativa, phi_pitch_deg, L_omero, L_ulna, L_mano)
return [(theta_base, t1, t2, t3) for (t1, t2, t3) in sols_2d]

def calcola_punti_robot(angles):
# Uso le costanti globali L_BASE, L_OMERO, etc.
th_base, th_spalla, th_gomito, th_polso = angles
# P0 e P1 (Base e Spalla)
p0 = np.array([0, 0, 0]); p1 = np.array([0, 0, L_BASE])
# P2 (Gomito)
cos_b, sin_b = np.cos(th_base), np.sin(th_base)
r2 = L_OMERO * np.cos(th_spalla); z2 = L_BASE + L_OMERO * np.sin(th_spalla)
p2 = np.array([r2 * cos_b, r2 * sin_b, z2])
# P3 (Polso)
angle_cum = th_spalla + th_gomito
r3 = r2 + L_ULNA * np.cos(angle_cum); z3 = z2 + L_ULNA * np.sin(angle_cum)
p3 = np.array([r3 * cos_b, r3 * sin_b, z3])
# P4 (Pinza)
angle_cum_tot = angle_cum + th_polso
r4 = r3 + L_MANO * np.cos(angle_cum_tot); z4 = z3 + L_MANO * np.sin(angle_cum_tot)
p4 = np.array([r4 * cos_b, r4 * sin_b, z4])
return np.array([p0, p1, p2, p3, p4])

# --- 3. LOGICA DI SELEZIONE MODIFICATA ---
def scegli_soluzione_migliore(soluzioni, angoli_precedenti):
if not soluzioni: return None
migliore_sol = None
miglior_punteggio = -np.inf
for sol in soluzioni:
th_spalla = sol[1] # Angolo M2 (Spalla)
deviazione_gomito_rad = abs(sol[2])
# >>> NUOVO VINCOLO FORTE: Z DEL GOMITO POSITIVA <<<
# Z_gomito = L_BASE + L_OMERO * sin(theta_spalla)
z_gomito = L_BASE + L_OMERO * np.sin(th_spalla)
if z_gomito <= 0.01: # Uso 0.01 come margine di sicurezza
# Scarta la soluzione: il gomito è sotto il tavolo o troppo vicino
continue
# Vincolo di flessibilità (Angolo > 90 gradi interni)
score = 0
if deviazione_gomito_rad < np.pi/2:
score += 1000
else:
score -= 1000
# Vincolo di continuità
if angoli_precedenti is not None:
diff = np.linalg.norm(np.array(sol) - np.array(angoli_precedenti))
score -= diff * 100
if score > miglior_punteggio:
miglior_punteggio = score
migliore_sol = sol
return migliore_sol

# --- 4. CONFIGURAZIONE TRAIETTORIA ---
HOME_POS = np.array([0.0, 0.15, 0.25])
START_CIRCLE = np.array([CENTER[0] + RADIUS, CENTER[1], CENTER[2]])

# Generazione Traiettoria (Avvicinamento + Cerchio)
frames_approach = 40
traj_approach_x = np.linspace(HOME_POS[0], START_CIRCLE[0], frames_approach)
traj_approach_y = np.linspace(HOME_POS[1], START_CIRCLE[1], frames_approach)
traj_approach_z = np.linspace(HOME_POS[2], START_CIRCLE[2], frames_approach)

frames_circle = 80
angles = np.linspace(0, 2*np.pi, frames_circle)
traj_circle_x = CENTER[0] + RADIUS * np.cos(angles)
traj_circle_y = CENTER[1] + RADIUS * np.sin(angles)
traj_circle_z = np.full_like(angles, CENTER[2])

full_traj_x = np.concatenate([traj_approach_x, traj_circle_x])
full_traj_y = np.concatenate([traj_approach_y, traj_circle_y])
full_traj_z = np.concatenate([traj_approach_z, traj_circle_z])
TOTAL_FRAMES = len(full_traj_x)

# --- 5. ANIMAZIONE ---
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
ax.set_xlim(-0.1, 0.3); ax.set_ylim(-0.2, 0.2); ax.set_zlim(0, 0.4) # Aggiornato Y per vedere meglio
ax.set_xlabel('X'); ax.set_ylabel('Y'); ax.set_zlabel('Z')
ax.set_title(f'Tinkerkit Braccio - Z Target={CENTER[2]:.2f}m con Z Gomito > 0')

line_robot, = ax.plot([], [], [], 'o-', lw=4, color='tab:blue', label='Robot')
point_target, = ax.plot([], [], [], 'X', color='red', label='Target')
line_trace, = ax.plot([], [], [], '-', lw=1, color='red', alpha=0.5)

# Aggiunge il piano Z=0
xx, yy = np.meshgrid(np.linspace(-0.1, 0.3, 2), np.linspace(-0.2, 0.2, 2))
ax.plot_surface(xx, yy, np.zeros_like(xx), alpha=0.2, color='gray')

ax.legend()

prev_angles = None
history_x, history_y, history_z = [], [], []

def update(frame_idx):
global prev_angles
tx = full_traj_x[frame_idx]
ty = full_traj_y[frame_idx]
tz = full_traj_z[frame_idx]
sols = ik_braccio(tx, ty, tz, PHI_PITCH, L_BASE, L_OMERO, L_ULNA, L_MANO)
best_sol = scegli_soluzione_migliore(sols, prev_angles)
if best_sol:
prev_angles = best_sol
points = calcola_punti_robot(best_sol)
line_robot.set_data(points[:,0], points[:,1])
line_robot.set_3d_properties(points[:,2])
point_target.set_data([tx], [ty])
point_target.set_3d_properties([tz])
history_x.append(tx); history_y.append(ty); history_z.append(tz)
line_trace.set_data(history_x, history_y)
line_trace.set_3d_properties(history_z)
return line_robot, point_target, line_trace

ani = animation.FuncAnimation(fig, update, frames=TOTAL_FRAMES, interval=40, blit=False, repeat=True)
plt.show()


 

 

sabato 22 novembre 2025

HU-069 CW Morse Decoder

Mi sono comprato questo kit denominato HU-069 (ne esiste una versione successiva alimentata via Usb) per imparare a saldare

 


Le istruzioni non sono granche' anche usando il link https://mp.weixin.qq.com/s/7lH6Fs58QgmLsrjEl6atdQ 


Nonostante istruzioni in alcuni casi lacunose sono riuscito a farlo funzionare. (non ho capito ancora a cosa serve il pulsante rosso).L'alimentazione e' 9V GND sull'esterno del jack). Il potenziometro sotto al di display regola la luminosita' di quest'ultimo

Non so quanto possa essere utile per il morse ma per imparare a saldare e correggere gli errori va piu' che bene visto anche il costo 

 


 

venerdì 21 novembre 2025

Aggiornare il BIOS da Linux

A causa di questi errori in fase di boot legati al Bios ho provato ad aggiornare 

[    0.917988] thermal LNXTHERM:00: registered as thermal_zone0
[    0.917992] ACPI: thermal: Thermal Zone [THM] (25 C)
[    0.918117] ACPI BIOS Error (bug): Could not resolve symbol [\_SB.PCI0.LPCB.HEC.ECAV], AE_NOT_FOUND (20250404/psargs-332)
[    0.918258] ACPI Error: Aborting method \_TZ.TZ00._TMP due to previous error (AE_NOT_FOUND) (20250404/psparse-529)
[    0.918511] ACPI BIOS Error (bug): Could not resolve symbol [\_SB.PCI0.LPCB.HEC.ECAV], AE_NOT_FOUND (20250404/psargs-332)
[    0.918648] ACPI Error: Aborting method \_TZ.TZ00._TMP due to previous error (AE_NOT_FOUND) (20250404/psparse-529)
[    0.918832] ACPI BIOS Error (bug): Could not resolve symbol [\_SB.PCI0.LPCB.HEC.ECAV], AE_NOT_FOUND (20250404/psargs-332)
[    0.918970] ACPI Error: Aborting method \_TZ.TZ01._TMP due to previous error (AE_NOT_FOUND) (20250404/psparse-529)
[    0.919151] ACPI BIOS Error (bug): Could not resolve symbol [\_SB.PCI0.LPCB.HEC.ECAV], AE_NOT_FOUND (20250404/psargs-332)
[    0.919288] ACPI Error: Aborting method \_TZ.TZ01._TMP due to previous error (AE_NOT_FOUND) (20250404/psparse-529)

Su Linux per i computer Dell ci sono due strade

Se si usa UEFI si puo' seguire questa strada che scarica in automatico il necessario e da l'update 

sudo apt install fwupd
sudo fwupdmgr refresh
fwupdmgr get-updates
sudo fwupdmgr update
 

Se si usa il Legacy Bios ci sono due strade: la prima e' scaricare il file .exe dell'aggiornamento del BIOS da Dell, metterlo su una chiavetta, fare il boot, premere F12 e selezionare Bios Flash Update 


 
 



 
sudo apt install libsmbios-bin
sudo dellBiosUpdate --platform --reboot Latitude_5414_1.53.1.exe

In tutto cio' alla fine non ho risolto il mio problema

Chiavetta CW BH8DZE

Seguendo questo post ho provato la chiavetta BH8DZE (si vedra' che i due sono parenti prossimi)


 

 

Nulla di nuovo ...la cinesata si presenta come un mouse USB HID 

La cosa curiosa e' che l'identificativo 413d:2107  che sembra poter essere ricondotto a WinKeyer (oltre che ad un sensore di temperatura)

sembra che il processore sia della famiglia degli ATTiny (sicuramente non ATTiny8 perche' il numero di piedini e' 16)

 

luca@Dell:~$ lsusb
Bus 001 Device 013: ID 413d:2107  

Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x413d product 0x2107 version 0x110
Input device name: "HID 413d:2107"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 272 (BTN_LEFT)
    Event code 273 (BTN_RIGHT)
    Event code 274 (BTN_MIDDLE)
  Event type 2 (EV_REL)
    Event code 0 (REL_X)
    Event code 1 (REL_Y)
    Event code 8 (REL_WHEEL)
    Event code 11 (REL_WHEEL_HI_RES)
  Event type 4 (EV_MSC)
    Event code 4 (MSC_SCAN)
Properties:
Testing ... (interrupt to exit)

Testing ... (interrupt to exit)
Event: time 1763723445.111938, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90001
Event: time 1763723445.111938, type 1 (EV_KEY), code 272 (BTN_LEFT), value 1
Event: time 1763723445.111938, -------------- SYN_REPORT ------------
Event: time 1763723445.231861, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90001
Event: time 1763723445.231861, type 1 (EV_KEY), code 272 (BTN_LEFT), value 0
Event: time 1763723445.231861, -------------- SYN_REPORT ------------
Event: time 1763723446.184010, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90001
Event: time 1763723446.184010, type 1 (EV_KEY), code 272 (BTN_LEFT), value 1
Event: time 1763723446.184010, -------------- SYN_REPORT ------------
Event: time 1763723446.319872, type 4 (EV_MSC), code 4 (MSC_SCAN), value 90001
Event: time 1763723446.319872, type 1 (EV_KEY), code 272 (BTN_LEFT), value 0
Event: time 1763723446.319872, -------------- SYN_REPORT ------------

Analisi MNF su spettri di riflettanza di plastica

Devo cerca di lavorare su spettri di riflettanza di plastica e la prima domanda e': quale sono le bande significative? Sono partito dal ...