This commit is contained in:
Jonathan Roth 2026-03-10 16:02:53 +01:00
parent a6584012bb
commit 39cce4bbde
12 changed files with 742 additions and 36 deletions

View File

@ -1,8 +1,8 @@
# fdr1-modbus-slave # fdr1-modbus-slave
Firmware Modbus RTU esclave pour encodeur incrémental, conçu pour tourner sur la carte **FDR1 v1.00**. Firmware Modbus RTU esclave pour trancanneur industriel, conçu pour tourner sur la carte **FDR1 v1.00**.
Le PIC lit la position du codeur QEI et la met à disposition d'un maître Modbus RTU (OpenPLC ou autre) via liaison série RS232/RS485. Le PIC lit la vitesse du fil via le périphérique QEI, pilote le moteur de bobine (SPOOL) et synchronise le guide-fil (ARM) via un virtual gear configurable. La communication avec le maître (OpenPLC ou autre) se fait en Modbus RTU RS232/RS485.
--- ---
@ -12,24 +12,73 @@ Le PIC lit la position du codeur QEI et la met à disposition d'un maître Modbu
- Microcontrôleur **PIC18F4431** - Microcontrôleur **PIC18F4431**
- Quartz **8 MHz** (HSPLL x4 → Fosc = 32 MHz) - Quartz **8 MHz** (HSPLL x4 → Fosc = 32 MHz)
- Encodeur incrémental raccordé au périphérique QEI - Encodeur incrémental raccordé au périphérique QEI
- 2 drivers pas à pas Step/Dir (SPOOL + ARM)
- Liaison série RS232 (RS485 à venir) - Liaison série RS232 (RS485 à venir)
--- ---
## Configuration Modbus RTU ## Configuration Modbus RTU
| Paramètre | Valeur | | Paramètre | Valeur |
|------------------|------------| |-----------------|------------|
| Adresse esclave | 183 | | Adresse esclave | 183 |
| Vitesse | 115200 bps | | Vitesse | 115200 bps |
| Format | 8N1 | | Format | 8N1 |
| Function code | FC03 | | Function codes | FC03, FC06, FC16 |
### Registres ---
| Adresse | Nom | Type | Accès | Description | ## Registres Modbus
|----------|-----------|--------|-------|-----------------|
| `0x1700` | HR0 | UINT16 | R | Position QEI | ### Lecture (FC03)
| Adresse | Nom | Type | Description |
|----------|-------------|--------|--------------------------|
| `0x1700` | QEI_POS | UINT16 | Vitesse fil (position QEI) |
### Écriture (FC06 / FC16)
| Adresse | Nom | Type | Accès | Description |
|----------|-------------|--------|-------|----------------------------------------------------|
| `0x0800` | REG_CTRL | UINT16 | R/W | Registre de contrôle (voir bits ci-dessous) |
| `0x0801` | SPOOL_FREQ | UINT16 | R/W | Fréquence moteur SPOOL (Hz, 0=arrêt) |
| `0x0802` | ARM_GEAR | UINT16 | R/W | Virtual gear ARM (µm par tour SPOOL, 0=arrêt) |
| `0x0803` | ARM_OFFSET | UINT16 | R/W | Distance capteur home → origine (µm) — EEPROM |
| `0x0804` | ARM_LEFT | UINT16 | R/W | Position limite gauche (1/10 mm, défaut: 500) |
| `0x0805` | ARM_RIGHT | UINT16 | R/W | Position limite droite (1/10 mm, défaut: 4500) |
| `0x0806` | SPOOL_RATIO | UINT16 | R/W | Démultiplication SPOOL (millièmes, 1000=1:1) — EEPROM |
### Bits REG_CTRL (0x0800)
| Bit | Nom | Description |
|------|-------------------|------------------------------------------------------|
| 15 | SPOOL_ENABLE | Active le moteur SPOOL |
| 14 | SPOOL_DIR | Sens de rotation SPOOL |
| 13 | ARM_HOMING | Lance la procédure de prise d'origine (one-shot) |
| 12 | ARM_GO_TO_ZERO | Retour ARM à LEFT, démarre nouveau cycle (one-shot) |
| 1 | ARM_FREE | Libère le moteur ARM (désactive vgear + ENABLE=0) |
### EEPROM interne
| Adresse | Contenu |
|---------|---------------------|
| `0x10` | ARM_OFFSET high |
| `0x11` | ARM_OFFSET low |
| `0x12` | SPOOL_RATIO high |
| `0x13` | SPOOL_RATIO low |
---
## Fonctionnement normal
En production, seuls deux registres sont utilisés en continu :
```
FC03 0x1700 → lecture vitesse fil
FC06 0x0801 → consigne vitesse SPOOL
```
Les registres de configuration (0x0802~0x0806) sont écrits une fois à la mise en service.
--- ---
@ -73,4 +122,4 @@ Redistribution and use in source and binary forms, with or without modification,
3. All advertising materials mentioning features or use of this software must display the following acknowledgement: *This product includes software developed by the FDR1 Project.* 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: *This product includes software developed by the FDR1 Project.*
4. Neither the name of the FDR1 Project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 4. Neither the name of the FDR1 Project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.

59
includes/arm.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef ARM_H
#define ARM_H
#include <xc.h>
#include <stdint.h>
#include <macros.h>
// ─────────────────────────────────────────────
// Registres Modbus
// ─────────────────────────────────────────────
#define ARM_REG_GEAR 0x0802 // déplacement en µm par tour SPOOL
#define ARM_REG_OFFSET 0x0803 // distance capteur home → origine (µm)
#define ARM_REG_LEFT 0x0804 // position limite gauche (1/10 mm)
#define ARM_REG_RIGHT 0x0805 // position limite droite (1/10 mm)
// ─────────────────────────────────────────────
// Mécanique
// 1 tour ARM = 40mm = 1600 micropas
// 1 micropas ARM = 25µm
// ─────────────────────────────────────────────
#define ARM_UM_PER_USTEP 25U // µm par micropas
#define ARM_USTEP_PER_REV 1600U // micropas par tour
#define ARM_UM_PER_REV ((uint32_t)ARM_UM_PER_USTEP * ARM_USTEP_PER_REV) // 40000µm
// ─────────────────────────────────────────────
// Limites gear
// ─────────────────────────────────────────────
#define ARM_GEAR_MIN 0 // µm (arrêt vgear)
#define ARM_GEAR_MAX 65535 // µm = 65.535mm par tour SPOOL
// ─────────────────────────────────────────────
// Valeurs par défaut positions (1/10 mm)
// ─────────────────────────────────────────────
#define ARM_LEFT_DEFAULT 500 // 50.0mm
#define ARM_RIGHT_DEFAULT 4500 // 450.0mm
// ─────────────────────────────────────────────
// API publique
// ─────────────────────────────────────────────
void ARM_Init(void);
void ARM_Step(void); // appelé depuis l'ISR SPOOL à chaque step
void ARM_Enable(uint8_t state); // ENABLE moteur ARM
void ARM_SetDir(uint8_t dir); // sens de rotation ARM
void ARM_SetFree(uint8_t state); // free ARM + désactive vgear (REG_CTRL bit1)
void ARM_GoToZero(void); // retour à LEFT, nouvelle bobine (REG_CTRL bit12)
void ARM_SetGear(uint16_t um); // écriture registre 0x0802
void ARM_SetOffset(uint16_t um); // écriture registre 0x0803 + sauvegarde EEPROM
uint16_t ARM_GetOffset(void); // lecture offset courant
void ARM_SetLeft(uint16_t dmm); // écriture registre 0x0804 (1/10 mm)
void ARM_SetRight(uint16_t dmm); // écriture registre 0x0805 (1/10 mm)
uint16_t ARM_GetLeft(void);
uint16_t ARM_GetRight(void);
#endif // ARM_H

23
includes/eeprom.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef EEPROM_H
#define EEPROM_H
#include <xc.h>
#include <stdint.h>
// ─────────────────────────────────────────────
// Adresses EEPROM
// ─────────────────────────────────────────────
#define EE_ARM_OFFSET_H 0x10 // ARM offset high byte
#define EE_ARM_OFFSET_L 0x11 // ARM offset low byte
// ─────────────────────────────────────────────
// API publique
// ─────────────────────────────────────────────
uint8_t EE_ReadByte(uint8_t addr);
void EE_WriteByte(uint8_t addr, uint8_t data);
uint16_t EE_ReadWord(uint8_t addr_h, uint8_t addr_l);
void EE_WriteWord(uint8_t addr_h, uint8_t addr_l, uint16_t data);
#endif // EEPROM_H

View File

@ -3,6 +3,16 @@
#ifndef __MACROS_H__ #ifndef __MACROS_H__
#define __MACROS_H__ #define __MACROS_H__
#define MOTOR_EN(STATE) {LATBbits.LB4 = !STATE;}
#define MOTOR_STATE() (!LATBbits.LB4)
#define MOTOR_REV(STATE) {LATBbits.LB2 = STATE;}
#define MOTOR_HSTEP() {LATBbits.LB3 = !LATBbits.LB3;}
#define ARM_EN(STATE) {LATEbits.LE0 = !STATE;}
#define ARM_STATE() (!LATEbits.LE0)
#define ARM_REV(STATE) {LATEbits.LE2 = STATE;}
#define ARM_HSTEP() {LATEbits.LE1 = !LATEbits.LE1;}
#define RED(STATE) {LATDbits.LD0 = STATE;} #define RED(STATE) {LATDbits.LD0 = STATE;}
#define GREEN(STATE) {LATDbits.LD2 = STATE;} #define GREEN(STATE) {LATDbits.LD2 = STATE;}
#define BLUE(STATE) {LATDbits.LD1 = STATE;} #define BLUE(STATE) {LATDbits.LD1 = STATE;}

View File

@ -10,6 +10,8 @@
#define RTU_SLAVE_ADDR 0xB7 #define RTU_SLAVE_ADDR 0xB7
#define RTU_FC03 0x03 #define RTU_FC03 0x03
#define RTU_FC06 0x06
#define RTU_FC16 0x10
#define RTU_HR0_ADDR 0x1700 // Position QEI #define RTU_HR0_ADDR 0x1700 // Position QEI
@ -35,6 +37,6 @@
void RTU_Init(void); void RTU_Init(void);
void RTU_RxISR(void); // ISR UART RX haute priorité void RTU_RxISR(void); // ISR UART RX haute priorité
void RTU_TimerISR(void); // ISR Timer2 basse priorité void RTU_TimerISR(void); // ISR Timer2 basse priorité
void RTU_Task(void); // boucle principale void RTU_Task(void); // boucle principale
#endif // RTU_H #endif // RTU_H

46
includes/spool.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef SPOOL_H
#define SPOOL_H
#include <xc.h>
#include <stdint.h>
#include <macros.h>
// ─────────────────────────────────────────────
// Registres Modbus
// ─────────────────────────────────────────────
#define REG_CTRL 0x0800 // registre de contrôle commun
#define SPOOL_REG_FREQ 0x0801 // fréquence Step (Hz)
// bits REG_CTRL
#define CTRL_SPOOL_ENABLE ((uint16_t)(1U << 15)) // bit15 : SPOOL enable
#define CTRL_SPOOL_DIR ((uint16_t)(1U << 14)) // bit14 : SPOOL dir
#define CTRL_ARM_HOMING ((uint16_t)(1U << 13)) // bit13 : ARM homing start
#define CTRL_ARM_GO_TO_ZERO ((uint16_t)(1U << 12)) // bit12 : ARM retour à LEFT
#define CTRL_ARM_FREE ((uint16_t)(1U << 1)) // bit1 : ARM libre
// ─────────────────────────────────────────────
// Limites
// ─────────────────────────────────────────────
#define SPOOL_FREQ_MIN 1 // Hz
#define SPOOL_FREQ_MAX 16000 // Hz (600tr/min, 200pas, x8 microstepping)
// ─────────────────────────────────────────────
// Timer5
// Fosc/4 = 8MHz, prescaler 1:1
// reload = 65536 - (8000000 / (freq × 2))
// ─────────────────────────────────────────────
#define SPOOL_TIMER_PRESCALER 1
// ─────────────────────────────────────────────
// API publique
// ─────────────────────────────────────────────
void SPOOL_Init(void);
void SPOOL_TimerISR(void); // ISR Timer5 basse priorité
void SPOOL_SetCtrl(uint16_t ctrl); // écriture registre 0x0800
void SPOOL_SetFreq(uint16_t hz); // écriture registre 0x0801
#endif // SPOOL_H

235
src/arm.c Normal file
View File

@ -0,0 +1,235 @@
#include <arm.h>
#include <eeprom.h>
// ─────────────────────────────────────────────
// Machine d'état traverse
// ─────────────────────────────────────────────
typedef enum {
ARM_IDLE,
ARM_GO_TO_ZERO, // retour vers LEFT
ARM_WAIT_LEFT, // attente 3/4 tour à LEFT
ARM_TRAVERSE, // déplacement vers RIGHT
ARM_WAIT_RIGHT, // attente 3/4 tour à RIGHT
ARM_RETURN // déplacement vers LEFT
} arm_state_t;
// ─────────────────────────────────────────────
// Constantes
// ─────────────────────────────────────────────
#define ARM_WAIT_STEPS 1200U // 3/4 tour SPOOL (200pas × 8µstep × 3/4)
#define ARM_DIR_RIGHT 1
#define ARM_DIR_LEFT 0
// ─────────────────────────────────────────────
// État interne
// ─────────────────────────────────────────────
static volatile uint16_t gear_um; // déplacement µm par tour SPOOL
static volatile uint32_t accumulator; // accumulateur µm
static uint16_t arm_offset; // distance capteur home → origine (µm)
static volatile uint8_t free_arm; // 1 = vgear désactivé, moteur libre
static uint16_t pos_left; // limite gauche (1/10 mm)
static uint16_t pos_right; // limite droite (1/10 mm)
static volatile arm_state_t state; // état courant de la machine
static volatile uint16_t arm_pos; // position courante en micropas depuis LEFT
static volatile uint16_t wait_ctr; // compteur steps SPOOL pour les pauses
static uint16_t travel_steps; // micropas pour aller de LEFT à RIGHT
// ─────────────────────────────────────────────
// Calcul du trajet LEFT→RIGHT en micropas
// (pos_right - pos_left) en 1/10mm → µm → micropas
// ─────────────────────────────────────────────
static uint16_t ARM_CalcTravelSteps(void)
{
uint32_t um = (uint32_t)(pos_right - pos_left) * 100UL; // 1/10mm → µm
return (uint16_t)(um / ARM_UM_PER_USTEP);
}
// ─────────────────────────────────────────────
// Init — lecture offset EEPROM au démarrage
// ─────────────────────────────────────────────
void ARM_Init(void)
{
gear_um = 0;
accumulator = 0;
free_arm = 0;
pos_left = ARM_LEFT_DEFAULT;
pos_right = ARM_RIGHT_DEFAULT;
state = ARM_IDLE;
arm_pos = 0;
wait_ctr = 0;
travel_steps = ARM_CalcTravelSteps();
ARM_EN(0);
ARM_REV(0);
arm_offset = EE_ReadWord(EE_ARM_OFFSET_H, EE_ARM_OFFSET_L);
}
// ─────────────────────────────────────────────
// Enable / Dir — appelés depuis homing ou PLC
// ─────────────────────────────────────────────
void ARM_Enable(uint8_t state_en)
{
ARM_EN(state_en);
}
void ARM_SetDir(uint8_t dir)
{
ARM_REV(dir);
}
// ─────────────────────────────────────────────
// Step — appelé depuis l'ISR SPOOL
// ─────────────────────────────────────────────
void ARM_Step(void)
{
if (free_arm || gear_um == 0)
return;
switch (state) {
case ARM_IDLE:
break;
case ARM_GO_TO_ZERO:
// Déplacement vers LEFT — on décrémente arm_pos
accumulator += gear_um;
while (accumulator >= ARM_UM_PER_REV) {
accumulator -= ARM_UM_PER_REV;
if (arm_pos > 0) {
ARM_HSTEP();
arm_pos--;
} else {
// Arrivée à LEFT
accumulator = 0;
wait_ctr = 0;
state = ARM_WAIT_LEFT;
ARM_REV(ARM_DIR_RIGHT);
}
}
break;
case ARM_WAIT_LEFT:
wait_ctr++;
if (wait_ctr >= ARM_WAIT_STEPS) {
wait_ctr = 0;
state = ARM_TRAVERSE;
}
break;
case ARM_TRAVERSE:
// Déplacement vers RIGHT
accumulator += gear_um;
while (accumulator >= ARM_UM_PER_REV) {
accumulator -= ARM_UM_PER_REV;
if (arm_pos < travel_steps) {
ARM_HSTEP();
arm_pos++;
} else {
// Arrivée à RIGHT
accumulator = 0;
wait_ctr = 0;
state = ARM_WAIT_RIGHT;
ARM_REV(ARM_DIR_LEFT);
}
}
break;
case ARM_WAIT_RIGHT:
wait_ctr++;
if (wait_ctr >= ARM_WAIT_STEPS) {
wait_ctr = 0;
state = ARM_RETURN;
}
break;
case ARM_RETURN:
// Déplacement vers LEFT
accumulator += gear_um;
while (accumulator >= ARM_UM_PER_REV) {
accumulator -= ARM_UM_PER_REV;
if (arm_pos > 0) {
ARM_HSTEP();
arm_pos--;
} else {
// Arrivée à LEFT
accumulator = 0;
wait_ctr = 0;
state = ARM_WAIT_LEFT;
ARM_REV(ARM_DIR_RIGHT);
}
}
break;
}
}
// ─────────────────────────────────────────────
// GO_TO_ZERO — retour à LEFT, nouvelle bobine
// appelé depuis REG_CTRL bit12
// ─────────────────────────────────────────────
void ARM_GoToZero(void)
{
accumulator = 0;
wait_ctr = 0;
ARM_REV(ARM_DIR_LEFT);
state = ARM_GO_TO_ZERO;
}
// ─────────────────────────────────────────────
// Écriture registre GEAR (0x0802)
// ─────────────────────────────────────────────
void ARM_SetGear(uint16_t um)
{
gear_um = um;
accumulator = 0;
}
// ─────────────────────────────────────────────
// Écriture registre OFFSET (0x0803) + EEPROM
// ─────────────────────────────────────────────
void ARM_SetOffset(uint16_t um)
{
arm_offset = um;
EE_WriteWord(EE_ARM_OFFSET_H, EE_ARM_OFFSET_L, um);
}
uint16_t ARM_GetOffset(void)
{
return arm_offset;
}
// ─────────────────────────────────────────────
// Free ARM — appelé depuis REG_CTRL bit1
// ─────────────────────────────────────────────
void ARM_SetFree(uint8_t s)
{
free_arm = s;
ARM_EN(s ? 0 : 1);
}
// ─────────────────────────────────────────────
// Positions limites gauche/droite (1/10 mm)
// ─────────────────────────────────────────────
void ARM_SetLeft(uint16_t dmm)
{
pos_left = dmm;
travel_steps = ARM_CalcTravelSteps();
}
void ARM_SetRight(uint16_t dmm)
{
pos_right = dmm;
travel_steps = ARM_CalcTravelSteps();
}

53
src/eeprom.c Normal file
View File

@ -0,0 +1,53 @@
#include <eeprom.h>
// ─────────────────────────────────────────────
// Lecture d'un octet
// ─────────────────────────────────────────────
uint8_t EE_ReadByte(uint8_t addr)
{
EEADR = addr;
EECON1bits.EEPGD = 0;
EECON1bits.CFGS = 0;
EECON1bits.RD = 1;
return EEDATA;
}
// ─────────────────────────────────────────────
// Écriture d'un octet
// ─────────────────────────────────────────────
void EE_WriteByte(uint8_t addr, uint8_t data)
{
EEADR = addr;
EEDATA = data;
EECON1bits.EEPGD = 0;
EECON1bits.CFGS = 0;
EECON1bits.WREN = 1;
INTCONbits.GIE = 0;
EECON2 = 0x55;
EECON2 = 0xAA;
EECON1bits.WR = 1;
while (EECON1bits.WR);
EECON1bits.WREN = 0;
INTCONbits.GIE = 1;
}
// ─────────────────────────────────────────────
// Lecture d'un mot 16 bits (big endian)
// ─────────────────────────────────────────────
uint16_t EE_ReadWord(uint8_t addr_h, uint8_t addr_l)
{
return ((uint16_t)EE_ReadByte(addr_h) << 8) | EE_ReadByte(addr_l);
}
// ─────────────────────────────────────────────
// Écriture d'un mot 16 bits (big endian)
// ─────────────────────────────────────────────
void EE_WriteWord(uint8_t addr_h, uint8_t addr_l, uint16_t data)
{
EE_WriteByte(addr_h, data >> 8);
EE_WriteByte(addr_l, data & 0xFF);
}

View File

@ -5,6 +5,7 @@
#include <qei.h> #include <qei.h>
#include <uart.h> #include <uart.h>
#include <rtu.h> #include <rtu.h>
#include <spool.h>
void main(void) { void main(void) {
setup(); setup();
@ -15,16 +16,15 @@ void main(void) {
} }
void __interrupt(low_priority) LOWprio_interrupt(void) { void __interrupt(low_priority) LOWprio_interrupt(void) {
if( PIR1bits.TMR2IF ) if (PIR1bits.TMR2IF)
RTU_TimerISR(); RTU_TimerISR();
return; if (PIR3bits.TMR5IF)
SPOOL_TimerISR();
} }
void __interrupt(high_priority) HIGHprio_interrupt(void) { void __interrupt(high_priority) HIGHprio_interrupt(void) {
if( PIR1bits.RCIF ) if (PIR1bits.RCIF)
RTU_RxISR(); RTU_RxISR();
return;
} }

155
src/rtu.c
View File

@ -1,6 +1,7 @@
#include "rtu.h" #include <rtu.h>
#include "qei.h" #include <qei.h>
#include <macros.h> #include <spool.h>
#include <arm.h>
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
// Buffers & état interne // Buffers & état interne
@ -68,9 +69,65 @@ static void RTU_SendFC03(uint16_t value)
} }
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
// Init // Réponse FC06 — Write Single Register
// Echo de la requête (adresse + valeur)
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
static void RTU_SendFC06(uint16_t reg_addr, uint16_t value)
{
uint8_t resp[6];
uint16_t crc;
resp[0] = RTU_SLAVE_ADDR;
resp[1] = RTU_FC06;
resp[2] = reg_addr >> 8;
resp[3] = reg_addr & 0xFF;
resp[4] = value >> 8;
resp[5] = value & 0xFF;
crc = RTU_CRC16(resp, 6);
RTU_SendByte(resp[0]);
RTU_SendByte(resp[1]);
RTU_SendByte(resp[2]);
RTU_SendByte(resp[3]);
RTU_SendByte(resp[4]);
RTU_SendByte(resp[5]);
RTU_SendByte(crc & 0xFF);
RTU_SendByte(crc >> 8);
}
// ─────────────────────────────────────────────
// Réponse FC16 — Write Multiple Registers
// Echo adresse + nombre de registres écrits
// ─────────────────────────────────────────────
static void RTU_SendFC16(uint16_t reg_addr, uint16_t reg_count)
{
uint8_t resp[6];
uint16_t crc;
resp[0] = RTU_SLAVE_ADDR;
resp[1] = RTU_FC16;
resp[2] = reg_addr >> 8;
resp[3] = reg_addr & 0xFF;
resp[4] = reg_count >> 8;
resp[5] = reg_count & 0xFF;
crc = RTU_CRC16(resp, 6);
RTU_SendByte(resp[0]);
RTU_SendByte(resp[1]);
RTU_SendByte(resp[2]);
RTU_SendByte(resp[3]);
RTU_SendByte(resp[4]);
RTU_SendByte(resp[5]);
RTU_SendByte(crc & 0xFF);
RTU_SendByte(crc >> 8);
}
void RTU_Init(void) void RTU_Init(void)
{ {
rx_len = 0; rx_len = 0;
@ -138,13 +195,14 @@ void RTU_Task(void)
{ {
uint16_t crc_rx, crc_calc; uint16_t crc_rx, crc_calc;
uint16_t reg_addr, reg_count; uint16_t reg_addr, reg_count;
uint16_t value;
if (!frame_ready) if (!frame_ready)
return; return;
frame_ready = 0; frame_ready = 0;
// Trame FC03 minimum : 8 octets // Trame minimum : 8 octets
if (rx_len < 8) if (rx_len < 8)
goto done; goto done;
@ -152,10 +210,6 @@ void RTU_Task(void)
if (rx_buf[0] != RTU_SLAVE_ADDR) if (rx_buf[0] != RTU_SLAVE_ADDR)
goto done; goto done;
// Function code
if (rx_buf[1] != RTU_FC03)
goto done;
// Vérification CRC // Vérification CRC
crc_rx = (uint16_t)rx_buf[rx_len - 1] << 8 | rx_buf[rx_len - 2]; crc_rx = (uint16_t)rx_buf[rx_len - 1] << 8 | rx_buf[rx_len - 2];
crc_calc = RTU_CRC16(rx_buf, rx_len - 2); crc_calc = RTU_CRC16(rx_buf, rx_len - 2);
@ -163,16 +217,87 @@ void RTU_Task(void)
if (crc_rx != crc_calc) if (crc_rx != crc_calc)
goto done; goto done;
// Adresse et nombre de registres
reg_addr = (uint16_t)rx_buf[2] << 8 | rx_buf[3]; reg_addr = (uint16_t)rx_buf[2] << 8 | rx_buf[3];
reg_count = (uint16_t)rx_buf[4] << 8 | rx_buf[5]; reg_count = (uint16_t)rx_buf[4] << 8 | rx_buf[5];
// On ne supporte que HR0, 1 registre
if (reg_addr != RTU_HR0_ADDR || reg_count != 1)
goto done;
GREEN(1); GREEN(1);
RTU_SendFC03(QEI_ReadPosition());
switch (rx_buf[1]) {
// ── FC03 — Read Holding Registers ────────
case RTU_FC03:
if (reg_addr != RTU_HR0_ADDR || reg_count != 1)
goto done;
RTU_SendFC03(QEI_ReadPosition());
break;
// ── FC06 — Write Single Register ─────────
case RTU_FC06:
value = reg_count; // dans FC06, octets 4-5 = valeur
if (reg_addr == REG_CTRL) {
SPOOL_SetCtrl(value);
if (value & CTRL_ARM_HOMING) {
// TODO: lancer homing
}
if (value & CTRL_ARM_GO_TO_ZERO)
ARM_GoToZero();
} else if (reg_addr == SPOOL_REG_FREQ)
SPOOL_SetFreq(value);
else if (reg_addr == ARM_REG_GEAR)
ARM_SetGear(value);
else if (reg_addr == ARM_REG_OFFSET)
ARM_SetOffset(value);
else if (reg_addr == ARM_REG_LEFT)
ARM_SetLeft(value);
else if (reg_addr == ARM_REG_RIGHT)
ARM_SetRight(value);
else
goto done;
RTU_SendFC06(reg_addr, value);
break;
// ── FC16 — Write Multiple Registers ──────
case RTU_FC16: {
uint8_t byte_count = rx_buf[6];
uint8_t i;
// On supporte 0x0800~0x0805, registres contigus
if (reg_addr < REG_CTRL || reg_addr + reg_count - 1 > ARM_REG_RIGHT)
goto done;
if (byte_count != reg_count * 2)
goto done;
// Vérification longueur trame : 9 + byte_count + 2 CRC
if (rx_len < (uint8_t)(9 + byte_count))
goto done;
for (i = 0; i < reg_count; i++) {
value = (uint16_t)rx_buf[7 + i * 2] << 8 | rx_buf[8 + i * 2];
switch (reg_addr + i) {
case REG_CTRL:
SPOOL_SetCtrl(value);
if (value & CTRL_ARM_HOMING) {
// TODO: lancer homing
}
if (value & CTRL_ARM_GO_TO_ZERO)
ARM_GoToZero();
break;
case SPOOL_REG_FREQ: SPOOL_SetFreq(value); break;
case ARM_REG_GEAR: ARM_SetGear(value); break;
case ARM_REG_OFFSET: ARM_SetOffset(value); break;
case ARM_REG_LEFT: ARM_SetLeft(value); break;
case ARM_REG_RIGHT: ARM_SetRight(value); break;
default: goto done;
}
}
RTU_SendFC16(reg_addr, reg_count);
break;
}
default:
goto done;
}
GREEN(0); GREEN(0);
done: done:

View File

@ -6,6 +6,8 @@
#include <uart.h> #include <uart.h>
#include <rtu.h> #include <rtu.h>
#include <macros.h> #include <macros.h>
#include <spool.h>
#include <arm.h>
void setup(void) { void setup(void) {
setup_gpio(); setup_gpio();
@ -15,6 +17,8 @@ void setup(void) {
QEI_Init(); QEI_Init();
UART_Init(); UART_Init();
RTU_Init(); RTU_Init();
SPOOL_Init();
ARM_Init();
INTCON = 0b01010000; // GIE disabled for now, PEIE enable, INT0 enable (always highprio), not int on portB, TMR0 INTCON = 0b01010000; // GIE disabled for now, PEIE enable, INT0 enable (always highprio), not int on portB, TMR0
INTCON2 = 0b00000101; // port B pull-up enabled, INT0/1/2 on falling edge INTCON2 = 0b00000101; // port B pull-up enabled, INT0/1/2 on falling edge

100
src/spool.c Normal file
View File

@ -0,0 +1,100 @@
#include <spool.h>
#include <arm.h>
// ─────────────────────────────────────────────
// État interne
// ─────────────────────────────────────────────
static volatile uint16_t reload;
static volatile uint8_t running;
// ─────────────────────────────────────────────
// Calcul du reload Timer5
// reload = 65536 - (Fosc/4 / (freq × 2))
// Fosc/4 = 8 000 000, prescaler 1:1
// ─────────────────────────────────────────────
static uint16_t SPOOL_CalcReload(uint16_t hz)
{
return (uint16_t)(65536UL - (8000000UL / ((uint32_t)hz * 2)));
}
// ─────────────────────────────────────────────
// Init
// ─────────────────────────────────────────────
void SPOOL_Init(void)
{
reload = 0;
running = 0;
MOTOR_EN(0);
MOTOR_REV(0);
// Timer5 off, prescaler 1:1, 16bit, Fosc/4
T5CON = 0b00000000;
TMR5H = 0;
TMR5L = 0;
PIR3bits.TMR5IF = 0;
IPR3bits.TMR5IP = 0; // basse priorité
PIE3bits.TMR5IE = 1;
}
// ─────────────────────────────────────────────
// ISR Timer5 — basse priorité
// ─────────────────────────────────────────────
void SPOOL_TimerISR(void)
{
PIR3bits.TMR5IF = 0;
TMR5H = reload >> 8;
TMR5L = reload & 0xFF;
MOTOR_HSTEP();
ARM_Step();
}
// ─────────────────────────────────────────────
// Écriture registre CTRL (0x0800)
// bit15 = ENABLE, bit14 = DIR
// ─────────────────────────────────────────────
void SPOOL_SetCtrl(uint16_t ctrl)
{
MOTOR_EN((ctrl & CTRL_SPOOL_ENABLE) ? 1 : 0);
MOTOR_REV((ctrl & CTRL_SPOOL_DIR) ? 1 : 0);
ARM_SetFree((ctrl & CTRL_ARM_FREE) ? 1 : 0);
// CTRL_ARM_HOMING : one-shot, traité dans RTU_Task
}
// ─────────────────────────────────────────────
// Écriture registre FREQ (0x0801)
// ─────────────────────────────────────────────
void SPOOL_SetFreq(uint16_t hz)
{
if (hz == 0) {
// Arrêt — timer off, position maintenue par ENABLE
T5CONbits.TMR5ON = 0;
running = 0;
return;
}
// Clamping
if (hz < SPOOL_FREQ_MIN) hz = SPOOL_FREQ_MIN;
if (hz > SPOOL_FREQ_MAX) hz = SPOOL_FREQ_MAX;
reload = SPOOL_CalcReload(hz);
if (!running) {
// Premier démarrage — charger le timer et démarrer
TMR5H = reload >> 8;
TMR5L = reload & 0xFF;
PIR3bits.TMR5IF = 0;
T5CONbits.TMR5ON = 1;
running = 1;
}
// Si déjà running, le nouveau reload sera pris en compte
// au prochain débordement dans l'ISR
}