ajout BLE

This commit is contained in:
thomas committed 2026-10-24 00:14:53 +02:00
1 parent 66bb319423
commit 3757975e51
20 files changed
+77318 -68028

No files matched your search

+15
View File
@@ -159,6 +159,21 @@ STM32CubeIDE alternative: debug Boot, terminate (Ctrl+F2), debug Appli, Resume (
- **SPI** - Touch interface - **SPI** - Touch interface
- **GPIO** - Touch interrupt (TP_IRQ) - **GPIO** - Touch interrupt (TP_IRQ)
## Remote Control (BLE, X-NUCLEO-67W61M1)
The effects can be driven from a phone/PC over BLE through the ST67W611M1 shield (SPI4, Arduino header). Wi-Fi (MQTT/HTTP) is the next step; **audio streaming to Bluetooth speakers is not possible** with this module (BLE GATT only, no A2DP/Classic, no LE Audio) - use an external A2DP transmitter on the headphone jack if needed (adds 100-250 ms of latency).
Layers (all hand-written, regeneration-safe):
- `Appli/Core/Src/remote_params.c` + `Inc/remote_params.h`: transport-independent table of 34 parameters (id byte -> field of `AudioEffects_GetParams()`, scale, range). Unit-testable on PC.
- `Appli/Core/Src/remote_ble.c` + `Inc/remote_ble.h`: FreeRTOS task `remoteBle` (BelowNormal, 3 KB stack) started by `RemoteBle_Start()` from the `RTOS_THREADS` user section of `main.c`. GATT service `c3110000-7e8a-4b1c-9d52-f0a1e4b7c600`, `Control` char (`...0001`, write) and `State` char (`...0002`, notify). Records are `[id u8][value i16 LE]`; id `0xFF` requests a snapshot (also sent when the client subscribes). Full protocol in the header comment.
- `Appli/Core/Src/st67_glue.c`: `vLoggingPrintf` (the driver's Logging component is not selected, there is no UART) writes INFO/WARN/ERROR into the RAM ring `st67_log_ring` (4 KB, read it in the debugger; `st67_log_head` = next write position), and `ST67_ExtiCallback()` forwards the `SPI_RDY` edges (hooked in `STM32TouchController.cpp`, the only `HAL_GPIO_EXTI_Callback`).
- `Appli/ST67W6X_Network_Driver/Target/spi_port.c` (generated by the pack): only `USER CODE` sections were edited (pin-name aliases `CHIP_EN_*`/`SPI_CS_*`, and **CS driven low at init**: the driver treats CS as active-high, ST's example idles it low; CubeMX leaves it high).
- Build: `gcc/makefile_appli` compiles the driver (`Middlewares/ST/ST67W6X_Network_Driver`, Core + W61_at + W61_bus) with `-DST67_ARCH=W6X_ARCH_T01`.
**NCP firmware**: the ST67W611M1 must run the "mission profile T01" firmware of the pack (UM3449 step 4), otherwise the SPI header is rejected (symptom: `Invalid magic 0x5501`, logged in `st67_log_ring`; the module answers `01 55 00 00 00 00 01 00` to `AA 55 00 ...`). ST's scripts only support a few NUCLEO hosts, so the DK acts as the UART bridge: `gcc/ncp_loader/main.c` (port of ST's NCP_Loader: bit-level copy VCP UART4 PD0/PD1 <-> module UART PE7/PE8, BOOT PD15 / CHIP_EN PD13 to enter the ROM download mode) built by `gcc/makefile_ncp_loader`, driven by `gcc/ncp_update.sh [version] [--yes]` (compiles the bridge, loads it in place of Boot, flashes the module with the pack's `QConn_Flash_Cmd-ubuntu` at 2 Mbaud, then restores Boot with `gcc/flash.sh boot`; Appli in external flash is untouched). Bridge validated on the board with the read-only `QConn_Eflash -r` (handshake + eFuse read OK). The module is already locked (eFuse public key filled, JTAG disabled). Needs a free ST-Link (no debug session) and the X-NUCLEO on the Arduino header.
Not validated on hardware yet. Things to check first: the FreeRTOS heap (50 KB in `DTCM_RTOS`, region is 52 KB, ~94 % used at link time: the driver allocates its tasks/queues there - `st67_log_ring` records the free heap after start-up), solder bridges SB3/SB6/SB12/SB20 closed on the shield (UM3449 lists them OFF by default, which would leave the Arduino SPI pins unconnected), JP1/JP2 fitted. Pairing is "Just Works": anyone in range can control the effects.
## Audio Processing ## Audio Processing
### Effects Chain (in order) ### Effects Chain (in order)
+34
View File
@@ -0,0 +1,34 @@
#ifndef REMOTE_BLE_H
#define REMOTE_BLE_H
#ifdef __cplusplus
extern "C" {
#endif
/* Controle a distance par BLE (module ST67W611M1 de la X-NUCLEO-67W61M1, liaison SPI4).
* Une tache FreeRTOS initialise le module, annonce un serveur GATT "CelloFX" et applique les
* commandes recues aux parametres d'effets (voir remote_params.h).
*
* Service c3110000-7e8a-4b1c-9d52-f0a1e4b7c600
* Control c3110001-... ecriture (avec ou sans reponse)
* State c3110002-... notification
*
* Les deux caracteristiques echangent des enregistrements de 3 octets :
* [id : uint8] [valeur : int16 little-endian]
* Plusieurs enregistrements peuvent tenir dans une ecriture (jusqu'a la taille du MTU).
* Control : le client envoie des commandes. L'identifiant 0xFF demande un instantane de l'etat.
* State : le serveur notifie les valeurs courantes (a l'abonnement du client, et sur 0xFF).
*
* Le module est appaire en "Just Works" : n'importe quel appareil a portee peut piloter les
* effets. A durcir (code PIN) avant tout usage hors banc d'essai.
*/
/* Cree la tache de controle. A appeler une fois, avant osKernelStart(). Sans effet sur l'audio :
* si le module est absent ou ne repond pas, la tache s'arrete et le reste tourne normalement. */
void RemoteBle_Start(void);
#ifdef __cplusplus
}
#endif
#endif /* REMOTE_BLE_H */
+45
View File
@@ -0,0 +1,45 @@
#ifndef REMOTE_PARAMS_H
#define REMOTE_PARAMS_H
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Couche commune du controle a distance (BLE maintenant, Wi-Fi ensuite) : elle expose les
* parametres de AudioEffects_GetParams() sous forme d'identifiants d'un octet avec une valeur
* entiere signee 16 bits. Aucune dependance au transport.
*
* Valeur sur le fil (int16) = valeur reelle * echelle, bornee a la plage du parametre :
* booleen (actif/inactif) 0 ou 1
* niveaux 0.0..1.0 echelle 1000 (500 = 0.5)
* gain d'une bande d'egaliseur echelle 10 (-60 = -6.0 dB)
* delai d'echo echelle 1 (250 = 250 ms)
*
* Identifiants :
* 0x00 audio_on_off 0x01 master_volume
* 0x10 octaver.enabled 0x11 down2 0x12 down1 0x13 dry 0x14 up1 0x15 up2
* 0x20 equalizer.enabled 0x30..0x39 gain des bandes 0 a 9
* 0x50 overdrive.enabled 0x51 drive 0x52 tone 0x53 level
* 0x60 saturation.enabled 0x61 drive 0x62 level
* 0x70 echo.enabled 0x71 delay_ms 0x72 feedback 0x73 mix
* 0x80 reverb.enabled 0x81 room_size 0x82 damping 0x83 mix
*/
/* Applique une valeur recue. Retourne false si l'identifiant est inconnu. */
bool RemoteParams_Set(uint8_t id, int16_t value);
/* Lit la valeur courante d'un parametre. Retourne false si l'identifiant est inconnu. */
bool RemoteParams_Get(uint8_t id, int16_t *value);
/* Parcours de tous les parametres (pour envoyer un instantane de l'etat). */
uint8_t RemoteParams_Count(void);
uint8_t RemoteParams_IdAt(uint8_t index);
#ifdef __cplusplus
}
#endif
#endif /* REMOTE_PARAMS_H */
+24
View File
@@ -0,0 +1,24 @@
#ifndef ST67_GLUE_H
#define ST67_GLUE_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* A appeler depuis HAL_GPIO_EXTI_Callback() pour toute broche : ne fait rien sauf pour SPI_RDY,
* dont les fronts cadencent la liaison SPI avec le module ST67W611M1. */
void ST67_ExtiCallback(uint16_t gpio_pin);
/* Journal du driver ST67 : tampon circulaire en RAM, lisible au debogueur (variables
* st67_log_ring / st67_log_head). Pas de liaison serie disponible pour les traces. */
#define ST67_LOG_RING_SIZE 4096U
extern char st67_log_ring[ST67_LOG_RING_SIZE];
extern volatile uint32_t st67_log_head; /* prochain octet a ecrire (modulo la taille) */
#ifdef __cplusplus
}
#endif
#endif /* ST67_GLUE_H */
+2
View File
@@ -29,6 +29,7 @@
#include "audio_effects.h" #include "audio_effects.h"
#include "audio_analysis.h" #include "audio_analysis.h"
#include "audio_looper.h" #include "audio_looper.h"
#include "remote_ble.h"
#include <string.h> #include <string.h>
/* USER CODE END Includes */ /* USER CODE END Includes */
@@ -264,6 +265,7 @@ int main(void)
/* USER CODE BEGIN RTOS_THREADS */ /* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */ /* add threads, ... */
RemoteBle_Start(); /* controle a distance BLE (module ST67W611M1) */
/* USER CODE END RTOS_THREADS */ /* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */ /* USER CODE BEGIN RTOS_EVENTS */
+292
View File
@@ -0,0 +1,292 @@
#include "remote_ble.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "FreeRTOS.h"
#include "event_groups.h"
#include "cmsis_os2.h"
#include "w6x_api.h"
#include "logging.h"
#include "remote_params.h"
/* Identifiants GATT : UUID 128 bits en notation canonique, sans tirets (format du module). */
#define SERVICE_UUID "c31100007e8a4b1c9d52f0a1e4b7c600"
#define CHAR_CONTROL_UUID "c31100017e8a4b1c9d52f0a1e4b7c600"
#define CHAR_STATE_UUID "c31100027e8a4b1c9d52f0a1e4b7c600"
#define SERVICE_INDEX 0U
#define CHAR_CONTROL_INDEX 0U
#define CHAR_STATE_INDEX 1U
#define DEVICE_NAME "CelloFX"
/* Donnees d'annonce BLE brutes (hexadecimal) :
* annonce : flags (LE General Discoverable, BR/EDR non supporte) + nom complet "CelloFX"
* reponse au scan : liste complete de l'UUID 128 bits du service, en little-endian */
#define ADV_DATA "020106" "0809" "43656C6C6F4658"
#define SCAN_RESP_DATA "1107" "00C6B7E4A1F0529D1C4B8A7E000011C3"
#define RECORD_SIZE 3U
#define CMD_SNAPSHOT 0xFFU
/* 6 enregistrements = 18 octets : tient dans une notification avec le MTU ATT par defaut (23). */
#define NOTIFY_MAX_RECORDS 6U
#define RX_BUFFER_SIZE 247U
#define INIT_RETRIES 3U
#define INIT_RETRY_DELAY_MS 3000U
#define EVT_CONNECTED (1UL << 0)
#define EVT_DISCONNECTED (1UL << 1)
#define EVT_SNAPSHOT (1UL << 2)
#define EVT_ALL (EVT_CONNECTED | EVT_DISCONNECTED | EVT_SNAPSHOT)
static uint8_t rx_buffer[RX_BUFFER_SIZE]; /* le module y copie les donnees ecrites par le client */
static EventGroupHandle_t events;
static volatile uint32_t conn_handle = 0xFFU;
static volatile bool connected;
static bool started;
static const osThreadAttr_t task_attr = {
.name = "remoteBle",
.stack_size = 3072,
.priority = (osPriority_t)osPriorityBelowNormal,
};
/* Les callbacks du driver s'executent dans son propre contexte : ils doivent rester courts et
* n'appellent aucune fonction W6X_* (l'envoi des notifications est fait par la tache). */
static void on_ble_data(const W6X_Ble_CbParamData_t *p)
{
uint32_t len = p->available_data_length;
if (len > RX_BUFFER_SIZE)
{
len = RX_BUFFER_SIZE;
}
for (uint32_t i = 0; i + RECORD_SIZE <= len; i += RECORD_SIZE)
{
uint8_t id = rx_buffer[i];
int16_t value = (int16_t)((uint16_t)rx_buffer[i + 1] | ((uint16_t)rx_buffer[i + 2] << 8));
if (id == CMD_SNAPSHOT)
{
(void)xEventGroupSetBits(events, EVT_SNAPSHOT);
}
else if (!RemoteParams_Set(id, value))
{
LogWarn("BLE: identifiant de parametre inconnu 0x%02X\n", (unsigned)id);
}
}
}
static void app_ble_cb(W6X_event_id_t event_id, void *event_args)
{
const W6X_Ble_CbParamData_t *p = (const W6X_Ble_CbParamData_t *)event_args;
switch (event_id)
{
case W6X_BLE_EVT_CONNECTED_ID:
conn_handle = p->remote_ble_device.conn_handle;
connected = true;
LogInfo("BLE: client connecte (handle %" PRIu32 ")\n", conn_handle);
(void)xEventGroupSetBits(events, EVT_CONNECTED);
break;
case W6X_BLE_EVT_DISCONNECTED_ID:
connected = false;
conn_handle = 0xFFU;
LogInfo("BLE: client deconnecte\n");
(void)xEventGroupSetBits(events, EVT_DISCONNECTED);
break;
case W6X_BLE_EVT_WRITE_ID:
if (p->service_idx == SERVICE_INDEX && p->charac_idx == CHAR_CONTROL_INDEX)
{
on_ble_data(p);
}
break;
case W6X_BLE_EVT_NOTIFICATION_STATUS_ENABLED_ID:
/* le client s'abonne : on lui envoie l'etat complet pour qu'il se synchronise */
(void)xEventGroupSetBits(events, EVT_SNAPSHOT);
break;
default:
break;
}
}
/* Le driver exige des callbacks valides pour toutes les familles d'evenements. */
static void app_unused_cb(W6X_event_id_t event_id, void *event_args)
{
(void)event_id;
(void)event_args;
}
static void app_error_cb(W6X_Status_t status, char const *func_name)
{
LogError("ST67: erreur %" PRIi32 " dans %s\n", (int32_t)status, func_name);
}
static W6X_Status_t configure_server(void)
{
W6X_Status_t ret;
char name[W6X_BLE_DEVICE_NAME_SIZE] = DEVICE_NAME; /* l'API lit un tableau de taille fixe */
ret = W6X_Ble_SetDeviceName(name);
if (ret != W6X_STATUS_OK) { LogError("BLE: nom\n"); return ret; }
ret = W6X_Ble_SetTxPower(0);
if (ret != W6X_STATUS_OK) { LogError("BLE: puissance d'emission\n"); return ret; }
ret = W6X_Ble_SetAdvData(ADV_DATA);
if (ret != W6X_STATUS_OK) { LogError("BLE: donnees d'annonce\n"); return ret; }
ret = W6X_Ble_SetScanRespData(SCAN_RESP_DATA);
if (ret != W6X_STATUS_OK) { LogError("BLE: reponse au scan\n"); return ret; }
ret = W6X_Ble_CreateService(SERVICE_INDEX, SERVICE_UUID, W6X_BLE_UUID_TYPE_128);
if (ret != W6X_STATUS_OK) { LogError("BLE: creation du service\n"); return ret; }
ret = W6X_Ble_CreateCharacteristic(SERVICE_INDEX, CHAR_CONTROL_INDEX, CHAR_CONTROL_UUID,
W6X_BLE_UUID_TYPE_128,
W6X_BLE_CHAR_PROP_WRITE_WITHOUT_RESP | W6X_BLE_CHAR_PROP_WRITE_WITH_RESP,
W6X_BLE_CHAR_PERM_WRITE);
if (ret != W6X_STATUS_OK) { LogError("BLE: caracteristique Control\n"); return ret; }
ret = W6X_Ble_CreateCharacteristic(SERVICE_INDEX, CHAR_STATE_INDEX, CHAR_STATE_UUID,
W6X_BLE_UUID_TYPE_128,
W6X_BLE_CHAR_PROP_NOTIFY,
W6X_BLE_CHAR_PERM_READ);
if (ret != W6X_STATUS_OK) { LogError("BLE: caracteristique State\n"); return ret; }
ret = W6X_Ble_RegisterCharacteristics();
if (ret != W6X_STATUS_OK) { LogError("BLE: enregistrement des caracteristiques\n"); return ret; }
ret = W6X_Ble_SetSecurityParam(W6X_BLE_SEC_IO_NO_INPUT_OUTPUT);
if (ret != W6X_STATUS_OK) { LogWarn("BLE: parametres de securite\n"); }
return W6X_Ble_AdvStart();
}
static void send_snapshot(void)
{
if (!connected)
{
return;
}
uint8_t buffer[NOTIFY_MAX_RECORDS * RECORD_SIZE];
uint8_t count = RemoteParams_Count();
uint8_t index = 0;
while (index < count && connected)
{
uint32_t used = 0;
while (index < count && used < sizeof(buffer))
{
uint8_t id = RemoteParams_IdAt(index);
int16_t value = 0;
(void)RemoteParams_Get(id, &value);
buffer[used++] = id;
buffer[used++] = (uint8_t)((uint16_t)value & 0xFFU);
buffer[used++] = (uint8_t)(((uint16_t)value >> 8) & 0xFFU);
index++;
}
uint32_t sent = 0;
W6X_Status_t ret = W6X_Ble_ServerNotify((uint8_t)conn_handle, SERVICE_INDEX, CHAR_STATE_INDEX,
buffer, used, &sent, 1000);
if (ret != W6X_STATUS_OK)
{
LogError("BLE: echec de notification (%" PRIi32 ")\n", (int32_t)ret);
return;
}
osDelay(20); /* laisse le temps au lien radio d'ecouler la notification */
}
}
static bool start_module(void)
{
W6X_App_Cb_t cb = {0};
cb.APP_wifi_cb = app_unused_cb;
cb.APP_net_cb = app_unused_cb;
cb.APP_mqtt_cb = app_unused_cb;
cb.APP_ble_cb = app_ble_cb;
cb.APP_error_cb = app_error_cb;
(void)W6X_RegisterAppCb(&cb);
for (uint32_t attempt = 1; attempt <= INIT_RETRIES; attempt++)
{
W6X_Status_t ret = W6X_Init();
if (ret == W6X_STATUS_OK)
{
ret = W6X_Ble_Init(W6X_BLE_MODE_SERVER, rx_buffer, sizeof(rx_buffer) - 1U);
if (ret == W6X_STATUS_OK)
{
ret = configure_server();
if (ret == W6X_STATUS_OK)
{
return true;
}
}
}
LogError("ST67: initialisation impossible (essai %" PRIu32 "/%u, code %" PRIi32 ")\n",
attempt, (unsigned)INIT_RETRIES, (int32_t)ret);
W6X_DeInit();
osDelay(INIT_RETRY_DELAY_MS);
}
return false;
}
static void remote_ble_task(void *argument)
{
(void)argument;
if (!start_module())
{
LogError("ST67: module absent ou muet, controle a distance desactive\n");
osThreadExit();
}
LogInfo("BLE: annonce \"" DEVICE_NAME "\" demarree\n");
LogInfo("tas FreeRTOS libre: %u octets (minimum atteint: %u)\n",
(unsigned)xPortGetFreeHeapSize(), (unsigned)xPortGetMinimumEverFreeHeapSize());
for (;;)
{
EventBits_t bits = xEventGroupWaitBits(events, EVT_ALL, pdTRUE, pdFALSE, portMAX_DELAY);
if ((bits & EVT_DISCONNECTED) != 0U)
{
W6X_Status_t ret = W6X_Ble_AdvStart(); /* redevient visible apres une deconnexion */
if (ret != W6X_STATUS_OK)
{
LogWarn("BLE: relance de l'annonce (%" PRIi32 ")\n", (int32_t)ret);
}
}
if ((bits & EVT_SNAPSHOT) != 0U)
{
send_snapshot();
}
}
}
void RemoteBle_Start(void)
{
if (started)
{
return;
}
started = true;
events = xEventGroupCreate();
if (events == NULL || osThreadNew(remote_ble_task, NULL, &task_attr) == NULL)
{
LogError("ST67: creation de la tache de controle impossible\n");
}
}
+136
View File
@@ -0,0 +1,136 @@
#include "remote_params.h"
#include <stddef.h>
#include <math.h>
#include "audio_effects.h"
typedef enum
{
PARAM_BOOL,
PARAM_FLOAT
} ParamKind;
typedef struct
{
uint8_t id;
ParamKind kind;
uint16_t offset; /* position du champ dans AudioEffectsParams */
float scale; /* valeur sur le fil = reel * scale */
float min;
float max;
} ParamDesc;
#define P_BOOL(id, field) { (id), PARAM_BOOL, offsetof(AudioEffectsParams, field), 1.0f, 0.0f, 1.0f }
#define P_UNIT(id, field) { (id), PARAM_FLOAT, offsetof(AudioEffectsParams, field), 1000.0f, 0.0f, 1.0f }
#define P_UNIT_MAX(id, field, vmax) { (id), PARAM_FLOAT, offsetof(AudioEffectsParams, field), 1000.0f, 0.0f, (vmax) }
#define P_EQ_GAIN(i) { (uint8_t)(0x30 + (i)), PARAM_FLOAT, \
offsetof(AudioEffectsParams, equalizer.bands[i].gain_db), 10.0f, -24.0f, 24.0f }
static const ParamDesc k_params[] =
{
P_BOOL(0x00, audio_on_off),
P_UNIT(0x01, master_volume),
P_BOOL(0x10, octaver.enabled),
P_UNIT(0x11, octaver.level_down2),
P_UNIT(0x12, octaver.level_down1),
P_UNIT(0x13, octaver.level_dry),
P_UNIT(0x14, octaver.level_up1),
P_UNIT(0x15, octaver.level_up2),
P_BOOL(0x20, equalizer.enabled),
P_EQ_GAIN(0), P_EQ_GAIN(1), P_EQ_GAIN(2), P_EQ_GAIN(3), P_EQ_GAIN(4),
P_EQ_GAIN(5), P_EQ_GAIN(6), P_EQ_GAIN(7), P_EQ_GAIN(8), P_EQ_GAIN(9),
P_BOOL(0x50, overdrive.enabled),
P_UNIT(0x51, overdrive.drive),
P_UNIT(0x52, overdrive.tone),
P_UNIT(0x53, overdrive.level),
P_BOOL(0x60, saturation.enabled),
P_UNIT(0x61, saturation.drive),
P_UNIT(0x62, saturation.level),
P_BOOL(0x70, echo.enabled),
{ 0x71, PARAM_FLOAT, offsetof(AudioEffectsParams, echo.delay_ms), 1.0f, 0.0f, (float)FX_ECHO_MAX_DELAY_MS },
P_UNIT_MAX(0x72, echo.feedback, 0.95f), /* < 1.0 pour eviter l'emballement */
P_UNIT(0x73, echo.mix),
P_BOOL(0x80, reverb.enabled),
P_UNIT(0x81, reverb.room_size),
P_UNIT(0x82, reverb.damping),
P_UNIT(0x83, reverb.mix),
};
#define PARAM_COUNT (sizeof(k_params) / sizeof(k_params[0]))
_Static_assert(FX_EQ_NUM_BANDS == 10U, "la table des bandes d'egaliseur suppose 10 bandes");
_Static_assert(PARAM_COUNT < 256U, "RemoteParams_Count() tient sur un octet");
static const ParamDesc *find(uint8_t id)
{
for (size_t i = 0; i < PARAM_COUNT; i++)
{
if (k_params[i].id == id)
{
return &k_params[i];
}
}
return NULL;
}
bool RemoteParams_Set(uint8_t id, int16_t value)
{
const ParamDesc *d = find(id);
if (d == NULL)
{
return false;
}
uint8_t *base = (uint8_t *)AudioEffects_GetParams();
if (d->kind == PARAM_BOOL)
{
*(bool *)(base + d->offset) = (value != 0);
}
else
{
float v = (float)value / d->scale;
if (v < d->min) { v = d->min; }
if (v > d->max) { v = d->max; }
*(float *)(base + d->offset) = v;
}
return true;
}
bool RemoteParams_Get(uint8_t id, int16_t *value)
{
const ParamDesc *d = find(id);
if (d == NULL || value == NULL)
{
return false;
}
const uint8_t *base = (const uint8_t *)AudioEffects_GetParams();
if (d->kind == PARAM_BOOL)
{
*value = *(const bool *)(base + d->offset) ? 1 : 0;
}
else
{
float raw = roundf(*(const float *)(base + d->offset) * d->scale);
if (raw > 32767.0f) { raw = 32767.0f; }
if (raw < -32768.0f) { raw = -32768.0f; }
*value = (int16_t)raw;
}
return true;
}
uint8_t RemoteParams_Count(void)
{
return (uint8_t)PARAM_COUNT;
}
uint8_t RemoteParams_IdAt(uint8_t index)
{
return (index < PARAM_COUNT) ? k_params[index].id : 0xFFU;
}
+98
View File
@@ -0,0 +1,98 @@
#include "st67_glue.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "main.h"
#include "logging.h"
#include "spi_iface.h"
char st67_log_ring[ST67_LOG_RING_SIZE];
volatile uint32_t st67_log_head;
/* Niveau le plus verbeux conserve dans le tampon (LOG_ERROR < LOG_WARN < LOG_INFO < LOG_DEBUG). */
#define ST67_LOG_MAX_LEVEL LOG_INFO
static const char *level_tag(uint32_t level)
{
switch (level)
{
case LOG_ERROR: return "E ";
case LOG_WARN: return "W ";
case LOG_INFO: return "I ";
default: return "D ";
}
}
static void ring_write(const char *text, uint32_t len)
{
uint32_t primask = __get_PRIMASK();
__disable_irq();
uint32_t head = st67_log_head;
for (uint32_t i = 0; i < len; i++)
{
st67_log_ring[head] = text[i];
head = (head + 1U) % ST67_LOG_RING_SIZE;
}
st67_log_head = head;
__set_PRIMASK(primask);
}
/* Implementation attendue par les macros LogInfo/LogError/... du driver (composant "Logging" non
* retenu, et pas de UART : les traces vont dans le tampon circulaire st67_log_ring). */
void vLoggingPrintf(uint32_t log_level,
const uint8_t metadata_print,
const uint32_t line_number,
const char *const p_file_name,
const char *const p_format,
...)
{
char line[128];
int n = 0;
if (log_level > ST67_LOG_MAX_LEVEL)
{
return; /* les traces DEBUG du driver saturent le tampon circulaire */
}
n += snprintf(line + n, sizeof(line) - (size_t)n, "%s", level_tag(log_level));
if (metadata_print != 0U && p_file_name != NULL)
{
n += snprintf(line + n, sizeof(line) - (size_t)n, "%s:%lu ", p_file_name, (unsigned long)line_number);
}
va_list args;
va_start(args, p_format);
int m = vsnprintf(line + n, sizeof(line) - (size_t)n, p_format, args);
va_end(args);
if (m < 0)
{
return;
}
n += m;
if ((size_t)n >= sizeof(line))
{
n = (int)sizeof(line) - 1; /* ligne tronquee */
}
ring_write(line, (uint32_t)n);
}
void ST67_ExtiCallback(uint16_t gpio_pin)
{
if (gpio_pin != SPI_RDY_Pin)
{
return;
}
/* front montant : le module a des donnees a transmettre ; front descendant : il a acquitte l'en-tete */
if (HAL_GPIO_ReadPin(SPI_RDY_GPIO_Port, SPI_RDY_Pin) == GPIO_PIN_SET)
{
(void)spi_on_txn_data_ready();
}
else
{
(void)spi_on_header_ack();
}
}
+74 -8
View File
@@ -37,12 +37,30 @@
#include "main.h" #include "main.h"
/* USER CODE BEGIN Includes */ /* USER CODE BEGIN Includes */
/* Noms de broches : les etiquettes CubeMX du projet sont ST67_CHIP_EN, SPI4_CS ; le driver attend
* CHIP_EN et SPI_CS (SPI_RDY est deja identique). Ces alias survivent a la regeneration. */
#ifndef CHIP_EN_Pin
#define CHIP_EN_Pin ST67_CHIP_EN_Pin
#define CHIP_EN_GPIO_Port ST67_CHIP_EN_GPIO_Port
#endif
#ifndef SPI_CS_Pin
#define SPI_CS_Pin SPI4_CS_Pin
#define SPI_CS_GPIO_Port SPI4_CS_GPIO_Port
#endif
/* USER CODE END Includes */ /* USER CODE END Includes */
/* Global variables ----------------------------------------------------------*/ /* Global variables ----------------------------------------------------------*/
/* USER CODE BEGIN GV */ /* USER CODE BEGIN GV */
/* Le DMA du SPI ne sait ni lire ni ecrire la DTCM, or les tampons du driver viennent du tas FreeRTOS
* place en DTCM (cf. Appli/stm32h7rsxx_RAMxspi1_ROMxspi2.ld) : le canal DMA sortait en erreur de
* transfert (HAL_DMA_ERROR_TE) des que le module envoyait plus que l'en-tete. Les transferts DMA
* passent donc par deux tampons de rebond en RAM_CMD (non cachee, region MPU 6), recopies a la
* volee. Taille : W61_MAX_SPI_XFER + en-tete SPI + alignement. */
#define DMA_BOUNCE_SIZE 1536U
static uint8_t dma_tx_bounce[DMA_BOUNCE_SIZE] __attribute__((section("noncacheable_buffer"), aligned(32)));
static uint8_t dma_rx_bounce[DMA_BOUNCE_SIZE] __attribute__((section("noncacheable_buffer"), aligned(32)));
static void * volatile dma_rx_dest; /* tampon final du transfert de reception en cours */
static volatile uint16_t dma_rx_len;
/* USER CODE END GV */ /* USER CODE END GV */
/* Private typedef -----------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/
@@ -145,7 +163,35 @@ void *spi_port_memcpy(void *dest, const void *src, unsigned int len)
int32_t spi_port_init(spi_transaction_complete_t transaction_complete_cb) int32_t spi_port_init(spi_transaction_complete_t transaction_complete_cb)
{ {
/* USER CODE BEGIN spi_port_init_1 */ /* USER CODE BEGIN spi_port_init_1 */
/* Le driver traite CS comme actif a l'etat haut (spi_port_set_cs(1) = niveau haut). Au repos il
* doit donc etre bas, avant meme d'alimenter le module (CubeMX le laisse haut par defaut). */
HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_RESET);
/* CubeMX configure SCK/MOSI du SPI4 en vitesse LOW (front lent) alors que le bus tourne a
* ~19 Mbit/s : les fronts deformes corrompent l'en-tete (magic 0x5501 au lieu de 0x55AA).
* Vitesse tres haute pour PE12 (SCK), PE13 (MISO) et PE14 (MOSI). */
GPIO_InitTypeDef spi_gpio = {0};
spi_gpio.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14;
spi_gpio.Mode = GPIO_MODE_AF_PP;
spi_gpio.Pull = GPIO_NOPULL;
spi_gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
spi_gpio.Alternate = GPIO_AF5_SPI4;
HAL_GPIO_Init(GPIOE, &spi_gpio);
/* CubeMX genere les DMA de SPI4 avec adresse memoire FIXE (RX: DestInc, TX: SrcInc) : tous les
* octets recus sont ecrits au meme endroit. A cocher dans CubeMX (SPI4 > DMA Settings >
* "Increment Address" cote memoire) ; en attendant on corrige ici. */
extern DMA_HandleTypeDef handle_GPDMA1_Channel4, handle_GPDMA1_Channel5;
handle_GPDMA1_Channel4.Init.DestInc = DMA_DINC_INCREMENTED; /* SPI4_RX */
handle_GPDMA1_Channel5.Init.SrcInc = DMA_SINC_INCREMENTED; /* SPI4_TX */
if ((HAL_DMA_Init(&handle_GPDMA1_Channel4) != HAL_OK) || (HAL_DMA_Init(&handle_GPDMA1_Channel5) != HAL_OK))
{
Error_Handler();
}
/* Le bus est instable a ~19 Mbit/s (en-tetes decales d'un bit, donnees nulles) : on ralentit.
* Reglage equivalent dans CubeMX : SPI4 > Baud Rate Prescaler (regenere ensuite sans effet ici). */
__HAL_SPI_DISABLE(&NCP_SPI_HANDLE);
MODIFY_REG(NCP_SPI_HANDLE.Instance->CFG1, SPI_CFG1_MBR, SPI_BAUDRATEPRESCALER_16);
/* USER CODE END spi_port_init_1 */ /* USER CODE END spi_port_init_1 */
if (NCP_SPI_HANDLE.State == HAL_SPI_STATE_RESET) if (NCP_SPI_HANDLE.State == HAL_SPI_STATE_RESET)
{ {
@@ -221,7 +267,6 @@ int32_t spi_port_transfer(void *tx_buf, void *rx_buf, uint16_t len, uint32_t tim
status = HAL_SPI_TransmitReceive(&NCP_SPI_HANDLE, tx_dummy, rx_buf, len, timeout); status = HAL_SPI_TransmitReceive(&NCP_SPI_HANDLE, tx_dummy, rx_buf, len, timeout);
} }
/* USER CODE BEGIN spi_port_transfer_2 */ /* USER CODE BEGIN spi_port_transfer_2 */
/* USER CODE END spi_port_transfer_2 */ /* USER CODE END spi_port_transfer_2 */
return ((status == HAL_OK) ? 0 : -1); return ((status == HAL_OK) ? 0 : -1);
@@ -254,13 +299,26 @@ int32_t spi_port_transfer_dma(void *tx_buf, void *rx_buf, uint16_t len)
SCB_CleanDCache_by_Addr(tx_buf, len); SCB_CleanDCache_by_Addr(tx_buf, len);
#endif /* __DCACHE_PRESENT */ #endif /* __DCACHE_PRESENT */
/* USER CODE BEGIN spi_port_transfer_dma_trx */ /* USER CODE BEGIN spi_port_transfer_dma_trx */
status = HAL_SPI_TransmitReceive_DMA(&NCP_SPI_HANDLE, tx_buf, rx_buf, len); if (len > DMA_BOUNCE_SIZE)
{
return -1;
}
(void)memcpy(dma_tx_bounce, tx_buf, len);
dma_rx_dest = rx_buf;
dma_rx_len = len;
status = HAL_SPI_TransmitReceive_DMA(&NCP_SPI_HANDLE, dma_tx_bounce, dma_rx_bounce, len);
/* USER CODE END spi_port_transfer_dma_trx */ /* USER CODE END spi_port_transfer_dma_trx */
} }
else else
{ {
/* USER CODE BEGIN spi_port_transfer_dma_rx */ /* USER CODE BEGIN spi_port_transfer_dma_rx */
status = HAL_SPI_Receive_DMA(&NCP_SPI_HANDLE, rx_buf, len); if (len > DMA_BOUNCE_SIZE)
{
return -1;
}
dma_rx_dest = rx_buf;
dma_rx_len = len;
status = HAL_SPI_Receive_DMA(&NCP_SPI_HANDLE, dma_rx_bounce, len);
/* USER CODE END spi_port_transfer_dma_rx */ /* USER CODE END spi_port_transfer_dma_rx */
} }
/* USER CODE BEGIN spi_port_transfer_dma_2 */ /* USER CODE BEGIN spi_port_transfer_dma_2 */
@@ -314,7 +372,15 @@ int32_t spi_port_set_cs(int32_t state)
} }
/* USER CODE BEGIN FD */ /* USER CODE BEGIN FD */
/* Recopie la reception DMA du tampon de rebond vers le tampon du driver, avant de signaler la fin. */
static void spi_port_dma_rx_copyback(SPI_HandleTypeDef *hspi)
{
if (hspi == &NCP_SPI_HANDLE && dma_rx_dest != NULL)
{
(void)memcpy(dma_rx_dest, dma_rx_bounce, dma_rx_len);
dma_rx_dest = NULL;
}
}
/* USER CODE END FD */ /* USER CODE END FD */
/* Weak functions redefinition -----------------------------------------------*/ /* Weak functions redefinition -----------------------------------------------*/
@@ -335,7 +401,7 @@ void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{ {
/* USER CODE BEGIN HAL_SPI_RxCpltCallback_1 */ /* USER CODE BEGIN HAL_SPI_RxCpltCallback_1 */
spi_port_dma_rx_copyback(hspi);
/* USER CODE END HAL_SPI_RxCpltCallback_1 */ /* USER CODE END HAL_SPI_RxCpltCallback_1 */
if (hspi == &NCP_SPI_HANDLE) if (hspi == &NCP_SPI_HANDLE)
{ {
@@ -349,7 +415,7 @@ void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{ {
/* USER CODE BEGIN HAL_SPI_TxRxCpltCallback_1 */ /* USER CODE BEGIN HAL_SPI_TxRxCpltCallback_1 */
spi_port_dma_rx_copyback(hspi);
/* USER CODE END HAL_SPI_TxRxCpltCallback_1 */ /* USER CODE END HAL_SPI_TxRxCpltCallback_1 */
if (hspi == &NCP_SPI_HANDLE) if (hspi == &NCP_SPI_HANDLE)
{ {
File diff suppressed because it is too large. Load diff
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
File diff suppressed because it is too large. Load diff
@@ -29,6 +29,7 @@
volatile bool doSampleTouch = false; volatile bool doSampleTouch = false;
extern "C" I2C_HandleTypeDef hi2c1; extern "C" I2C_HandleTypeDef hi2c1;
extern "C" void ST67_ExtiCallback(uint16_t gpio_pin); /* Core/Src/st67_glue.c */
using namespace touchgfx; using namespace touchgfx;
extern "C" extern "C"
@@ -52,6 +53,9 @@ extern "C"
doSampleTouch = true; doSampleTouch = true;
return; return;
} }
/* SPI_RDY du module Wi-Fi/BLE ST67W611M1 : une seule fonction HAL_GPIO_EXTI_Callback possible */
ST67_ExtiCallback(GPIO_Pin);
} }
} }
+30 -1
View File
@@ -208,6 +208,9 @@ cpp_compiler_options += $(float_options) -mcpu=cortex-m7 -DCORE_CM7
linker_options += $(float_options) -mcpu=cortex-m7 linker_options += $(float_options) -mcpu=cortex-m7
c_compiler_options += -DSTM32H7S7xx -DUSE_HAL_DRIVER c_compiler_options += -DSTM32H7S7xx -DUSE_HAL_DRIVER
# architecture du driver ST67W6X (T01 = pile TCP/IP interne du module, sans LwIP)
c_compiler_options += -DST67_ARCH=W6X_ARCH_T01
cpp_compiler_options += -DST67_ARCH=W6X_ARCH_T01
cpp_compiler_options += -DSTM32H7S7xx -DUSE_HAL_DRIVER cpp_compiler_options += -DSTM32H7S7xx -DUSE_HAL_DRIVER
# include everything + specific vendor folders # include everything + specific vendor folders
@@ -339,6 +342,12 @@ board_include_paths := \
Appli/Core/Inc \ Appli/Core/Inc \
Appli/FATFS/App \ Appli/FATFS/App \
Appli/FATFS/Target \ Appli/FATFS/Target \
Appli/ST67W6X_Network_Driver/Target \
Middlewares/ST/ST67W6X_Network_Driver/Api \
Middlewares/ST/ST67W6X_Network_Driver/Core \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_at \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_bus \
Middlewares/ST/ST67W6X_Network_Driver/Utils/Misc \
Middlewares/Third_Party/FatFs/source \ Middlewares/Third_Party/FatFs/source \
Middlewares/Third_Party/FatFs/source/drivers/sd \ Middlewares/Third_Party/FatFs/source/drivers/sd \
$(gpu2d_path)/TouchGFXNema/include \ $(gpu2d_path)/TouchGFXNema/include \
@@ -364,7 +373,27 @@ board_c_files += \
Middlewares/Third_Party/FatFs/source/ff_gen_drv.c \ Middlewares/Third_Party/FatFs/source/ff_gen_drv.c \
Middlewares/Third_Party/FatFs/source/diskio.c \ Middlewares/Third_Party/FatFs/source/diskio.c \
Middlewares/Third_Party/FatFs/source/ffsystem_cmsis_os.c \ Middlewares/Third_Party/FatFs/source/ffsystem_cmsis_os.c \
Middlewares/Third_Party/FatFs/source/drivers/sd/sd_diskio_dma_rtos.c Middlewares/Third_Party/FatFs/source/drivers/sd/sd_diskio_dma_rtos.c \
Appli/Core/Src/remote_params.c \
Appli/Core/Src/remote_ble.c \
Appli/Core/Src/st67_glue.c \
Appli/ST67W6X_Network_Driver/Target/spi_port.c \
Middlewares/ST/ST67W6X_Network_Driver/Core/w6x_sys.c \
Middlewares/ST/ST67W6X_Network_Driver/Core/w6x_ble.c \
Middlewares/ST/ST67W6X_Network_Driver/Core/w6x_wifi.c \
Middlewares/ST/ST67W6X_Network_Driver/Core/w6x_net.c \
Middlewares/ST/ST67W6X_Network_Driver/Core/w6x_http.c \
Middlewares/ST/ST67W6X_Network_Driver/Core/w6x_mqtt.c \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_at/modem_cmd_handler.c \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_at/w61_at_ble.c \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_at/w61_at_common.c \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_at/w61_at_mqtt.c \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_at/w61_at_net.c \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_at/w61_at_sys.c \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_at/w61_at_wifi.c \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_bus/spi_iface.c \
Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_bus/w61_io.c \
Middlewares/ST/ST67W6X_Network_Driver/Utils/Misc/common_parser.c
asm_source_files := \ asm_source_files := \
+65
View File
@@ -0,0 +1,65 @@
# Passerelle UART pour mettre a jour le firmware du module ST67W611M1 (voir gcc/ncp_loader/main.c).
# Makefile autonome : lance depuis la racine avec make -f gcc/makefile_ncp_loader
# Produit gcc/ncp_loader/build/ncp_loader.{elf,hex}.
CC := arm-none-eabi-gcc
OBJCOPY := arm-none-eabi-objcopy
SIZE := arm-none-eabi-size
OUT := gcc/ncp_loader/build
HAL := Drivers/STM32H7RSxx_HAL_Driver
# Memes options que le Boot (gcc/makefile_boot) pour le coeur et les definitions de la puce.
ARCH := -mcpu=cortex-m7 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard
DEFS := -DCORE_CM7 -DST -DSTM32H7S7xx -DUSE_HAL_DRIVER -DUSE_STM32H7S78_DK -DUSE_PWR_LDO_SUPPLY__N -DUSE_FLOATING_POINT
INCS := -IBoot/Core/Inc \
-I$(HAL)/Inc \
-IDrivers/CMSIS/Include \
-IDrivers/CMSIS/Device/ST/STM32H7RSxx/Include
CFLAGS := $(ARCH) $(DEFS) $(INCS) -Os -g -Wall -std=gnu11 -ffunction-sections -fdata-sections -fno-exceptions
LDFLAGS := $(ARCH) -Os -g --specs=nosys.specs --specs=nano.specs -Wl,--gc-sections -static \
-T Boot/stm32h7s7xx_flash.ld -Wl,-Map=$(OUT)/ncp_loader.map
SRCS := gcc/ncp_loader/main.c \
Boot/Core/Src/system_stm32h7rsxx.c \
$(HAL)/Src/stm32h7rsxx_hal.c \
$(HAL)/Src/stm32h7rsxx_hal_cortex.c \
$(HAL)/Src/stm32h7rsxx_hal_gpio.c \
$(HAL)/Src/stm32h7rsxx_hal_pwr.c \
$(HAL)/Src/stm32h7rsxx_hal_pwr_ex.c \
$(HAL)/Src/stm32h7rsxx_hal_rcc.c \
$(HAL)/Src/stm32h7rsxx_hal_rcc_ex.c \
$(HAL)/Src/stm32h7rsxx_hal_dma.c \
$(HAL)/Src/stm32h7rsxx_hal_dma_ex.c \
$(HAL)/Src/stm32h7rsxx_hal_exti.c \
$(HAL)/Src/stm32h7rsxx_hal_flash.c \
$(HAL)/Src/stm32h7rsxx_hal_flash_ex.c
ASRC := gcc/startup_stm32h7s7l8hxh_boot.s
OBJS := $(SRCS:%.c=$(OUT)/%.o) $(ASRC:%.s=$(OUT)/%.o)
.PHONY: all clean
all: $(OUT)/ncp_loader.hex
@$(SIZE) $(OUT)/ncp_loader.elf
$(OUT)/%.o: %.c
@echo Compiling $<
@mkdir -p $(@D)
@$(CC) $(CFLAGS) -MMD -MP -c $< -o $@
$(OUT)/%.o: %.s
@echo Assembling $<
@mkdir -p $(@D)
@$(CC) $(ARCH) -g -x assembler-with-cpp -c $< -o $@
$(OUT)/ncp_loader.elf: $(OBJS)
@echo Linking $@
@$(CC) $(LDFLAGS) $(OBJS) -o $@
$(OUT)/ncp_loader.hex: $(OUT)/ncp_loader.elf
@$(OBJCOPY) -O ihex $< $@
clean:
rm -rf $(OUT)
-include $(OBJS:.o=.d)
+182
View File
@@ -0,0 +1,182 @@
/*
* Passerelle de mise a jour du firmware du module ST67W611M1 (X-NUCLEO-67W61M1) pour la
* STM32H7S78-DK. Adaptation du "NCP_Loader" de ST (fourni pour les NUCLEO uniquement).
*
* Elle met le module en mode telechargement (BOOT haut + impulsion sur CHIP_EN), puis recopie en
* boucle l'etat des broches entre le port serie virtuel du ST-LINK (PC) et l'UART du module. Aucune
* UART materielle : la recopie bit a bit supporte les 2 Mbauds de l'outil QConn_Flash.
*
* Usage : gcc/ncp_update.sh (compile, charge cette passerelle, flashe le module, restaure le Boot).
* Ce programme remplace temporairement le Boot en flash interne : il ne lance jamais l'Appli.
*
* Broches (UM3289 / UM3449) :
* PE7 <- module UART_TXD (Arduino D0) PE8 -> module UART_RXD (Arduino D1)
* PD0 <- PC via ST-LINK (VCP UART4 RX) PD1 -> PC via ST-LINK (VCP UART4 TX)
* PD13 -> CHIP_EN (Arduino D5) PD15 -> BOOT (Arduino D6)
* PO1 -> LD1 verte (allumee : passerelle active)
*/
#include "stm32h7rsxx_hal.h"
#define MOD_TX_PORT GPIOE /* sortie du module, lue par le MCU */
#define MOD_TX_PIN GPIO_PIN_7
#define MOD_RX_PORT GPIOE /* entree du module, pilotee par le MCU */
#define MOD_RX_PIN GPIO_PIN_8
#define VCP_RX_PORT GPIOD /* venant du PC, lue par le MCU */
#define VCP_RX_PIN GPIO_PIN_0
#define VCP_TX_PORT GPIOD /* vers le PC, pilotee par le MCU */
#define VCP_TX_PIN GPIO_PIN_1
#define NCP_EN_PORT GPIOD
#define NCP_EN_PIN GPIO_PIN_13
#define NCP_BOOT_PORT GPIOD
#define NCP_BOOT_PIN GPIO_PIN_15
#define LED_PORT GPIOO
#define LED_PIN GPIO_PIN_1
void Error_Handler(void)
{
__disable_irq();
for (;;)
{
}
}
void SysTick_Handler(void)
{
HAL_IncTick();
}
/* Meme configuration que le Boot de la DK (HSE 24 MHz en bypass, PLL1 -> 600 MHz) : la boucle de
* recopie doit etre bien plus rapide qu'un bit a 2 Mbauds (500 ns). */
static void SystemClock_Config(void)
{
RCC_OscInitTypeDef osc = {0};
RCC_ClkInitTypeDef clk = {0};
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE0) != HAL_OK)
{
Error_Handler();
}
HAL_PWR_EnableBkUpAccess();
osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;
osc.HSEState = RCC_HSE_BYPASS;
osc.PLL1.PLLState = RCC_PLL_ON;
osc.PLL1.PLLSource = RCC_PLLSOURCE_HSE;
osc.PLL1.PLLM = 4;
osc.PLL1.PLLN = 200;
osc.PLL1.PLLP = 2;
osc.PLL1.PLLQ = 2;
osc.PLL1.PLLR = 2;
osc.PLL1.PLLS = 2;
osc.PLL1.PLLT = 2;
osc.PLL1.PLLFractional = 0;
osc.PLL2.PLLState = RCC_PLL_NONE;
osc.PLL3.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&osc) != HAL_OK)
{
Error_Handler();
}
clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 |
RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_PCLK4 | RCC_CLOCKTYPE_PCLK5;
clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
clk.SYSCLKDivider = RCC_SYSCLK_DIV1;
clk.AHBCLKDivider = RCC_HCLK_DIV2;
clk.APB1CLKDivider = RCC_APB1_DIV2;
clk.APB2CLKDivider = RCC_APB2_DIV2;
clk.APB4CLKDivider = RCC_APB4_DIV2;
clk.APB5CLKDivider = RCC_APB5_DIV2;
if (HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_7) != HAL_OK)
{
Error_Handler();
}
}
static void gpio_init(void)
{
GPIO_InitTypeDef g = {0};
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOO_CLK_ENABLE();
/* Lignes UART au repos a l'etat haut, BOOT et CHIP_EN bas, LED eteinte, avant de les configurer */
HAL_GPIO_WritePin(MOD_RX_PORT, MOD_RX_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(VCP_TX_PORT, VCP_TX_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(NCP_EN_PORT, NCP_EN_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(NCP_BOOT_PORT, NCP_BOOT_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET);
g.Mode = GPIO_MODE_OUTPUT_PP;
g.Pull = GPIO_NOPULL;
g.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
g.Pin = MOD_RX_PIN; HAL_GPIO_Init(MOD_RX_PORT, &g);
g.Pin = VCP_TX_PIN; HAL_GPIO_Init(VCP_TX_PORT, &g);
g.Pin = NCP_EN_PIN; HAL_GPIO_Init(NCP_EN_PORT, &g);
g.Pin = NCP_BOOT_PIN; HAL_GPIO_Init(NCP_BOOT_PORT, &g);
g.Pin = LED_PIN; HAL_GPIO_Init(LED_PORT, &g);
g.Mode = GPIO_MODE_INPUT;
g.Pull = GPIO_PULLUP; /* ligne UART au repos = haut, meme si l'autre cote est deconnecte */
g.Pin = MOD_TX_PIN; HAL_GPIO_Init(MOD_TX_PORT, &g);
g.Pin = VCP_RX_PIN; HAL_GPIO_Init(VCP_RX_PORT, &g);
}
/* Recopie bit a bit, dans les deux sens, tant que la carte est alimentee. */
static void uart_pass_through(void)
{
SysTick->CTRL = 0; /* pas d'interruption : la recopie ne doit jamais etre retardee */
__disable_irq();
for (;;)
{
if ((MOD_TX_PORT->IDR & MOD_TX_PIN) != 0U)
{
VCP_TX_PORT->BSRR = VCP_TX_PIN;
}
else
{
VCP_TX_PORT->BSRR = (uint32_t)VCP_TX_PIN << 16;
}
if ((VCP_RX_PORT->IDR & VCP_RX_PIN) != 0U)
{
MOD_RX_PORT->BSRR = MOD_RX_PIN;
}
else
{
MOD_RX_PORT->BSRR = (uint32_t)MOD_RX_PIN << 16;
}
}
}
int main(void)
{
SCB_EnableICache();
SCB_EnableDCache();
HAL_Init();
SystemClock_Config();
gpio_init();
/* clignotement : la passerelle demarre */
for (int i = 0; i < 3; i++)
{
HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET);
HAL_Delay(100);
}
/* Entree du module en mode telechargement : BOOT haut pendant le reset (CHIP_EN bas puis haut),
* relache ensuite. Meme sequence que le NCP_Loader de ST. */
HAL_GPIO_WritePin(NCP_BOOT_PORT, NCP_BOOT_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(NCP_EN_PORT, NCP_EN_PIN, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(NCP_EN_PORT, NCP_EN_PIN, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(NCP_BOOT_PORT, NCP_BOOT_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET); /* allumee : passerelle active */
uart_pass_through();
return 0;
}
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env bash
# Met a jour le firmware du module ST67W611M1 de la X-NUCLEO-67W61M1 depuis la STM32H7S78-DK.
#
# Les scripts de X-CUBE-ST67W61 ne supportent que quelques cartes NUCLEO. Ici la DK joue le role
# de passerelle : gcc/ncp_loader recopie le port serie virtuel du ST-LINK vers l'UART du module
# (et le met en mode telechargement), puis l'outil QConn_Flash du pack ecrit le firmware.
#
# Usage: gcc/ncp_update.sh [version] [--yes]
# version version du firmware "mission profile T01" (defaut : la plus recente du pack)
# --yes ne pas demander de confirmation
#
# ATTENTION : le binaire du module est signe ; son chargement VERROUILLE le module s'il ne l'est
# pas deja. Operation irreversible.
#
# Deroulement : compile la passerelle -> la charge dans la flash interne de la DK (a la place du Boot)
# -> flashe le module -> RESTAURE le Boot (gcc/flash.sh boot). L'Appli en flash externe n'est pas touchee.
# Prerequis : la X-NUCLEO montee sur l'Arduino, DK branchee en USB (CN7), aucun debug ouvert.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
# shellcheck source=toolchain-env.sh
source "$ROOT/gcc/toolchain-env.sh"
RED=$'\033[0;31m'; NC=$'\033[0m'
VERSION=""
ASSUME_YES=0
for arg in "$@"; do
case "$arg" in
--yes|-y) ASSUME_YES=1 ;;
*) VERSION="$arg" ;;
esac
done
die() { echo "${RED}erreur: $*${NC}" >&2; exit 1; }
# --- outils -----------------------------------------------------------------------------------
PACK="$(ls -d "$HOME"/STM32Cube/Repository/Packs/STMicroelectronics/X-CUBE-ST67W61/* 2>/dev/null | tail -1)"
[[ -n "$PACK" ]] || die "pack X-CUBE-ST67W61 introuvable (~/STM32Cube/Repository/Packs/STMicroelectronics)"
BIN_DIR="$PACK/Projects/ST67W6X_Scripts/Binaries"
NCP_DIR="$BIN_DIR/NCP_Binaries"
QCONN="$BIN_DIR/QConn_Flash/QConn_Flash_Cmd-ubuntu"
CLI="${ST_PROGRAMMER_BIN:-}/STM32_Programmer_CLI"
[[ -x "$CLI" ]] || die "STM32_Programmer_CLI introuvable (STM32CubeIDE dans ~/st ?)"
[[ -f "$QCONN" ]] || die "outil QConn_Flash introuvable : $QCONN"
if [[ ! -x "$QCONN" ]]; then
echo "QConn_Flash n'est pas executable : chmod +x $QCONN"
chmod +x "$QCONN"
fi
# --- version du firmware ----------------------------------------------------------------------
PREFIX="st67w611m_mission_t01"
if [[ -z "$VERSION" ]]; then
VERSION="$(ls "$NCP_DIR"/${PREFIX}_v*.bin 2>/dev/null | sed -n "s#^.*/${PREFIX}_v\([0-9][0-9.]*\)\.bin\$#\1#p" | sort -V | tail -n 1)"
fi
FW="${PREFIX}_v${VERSION}.bin"
[[ -f "$NCP_DIR/$FW" ]] || die "firmware introuvable : $NCP_DIR/$FW"
echo "Firmware du module : $FW"
echo "Pack : $PACK"
# --- confirmation -----------------------------------------------------------------------------
echo "${RED}##########################################################${NC}"
echo "${RED}# Un binaire SIGNE va etre charge dans le module ST67W611M. #${NC}"
echo "${RED}# Il sera VERROUILLE s'il ne l'est pas deja (irreversible). #${NC}"
echo "${RED}##########################################################${NC}"
if [[ "$ASSUME_YES" -ne 1 ]]; then
read -r -p "Continuer ? (Y/N) " answer
[[ "$answer" == "Y" || "$answer" == "y" ]] || { echo "Abandon."; exit 1; }
fi
# --- 1. passerelle ----------------------------------------------------------------------------
echo ">>> Compilation de la passerelle"
make -f gcc/makefile_ncp_loader
LOADER_HEX="$ROOT/gcc/ncp_loader/build/ncp_loader.hex"
# Quoi qu'il arrive ensuite, la DK ne doit pas rester avec la passerelle a la place du Boot.
restore_boot() {
echo ">>> Restauration du Boot de la DK"
if [[ -f "$ROOT/Boot/TouchGFX/build/bin/target.hex" ]]; then
"$ROOT/gcc/flash.sh" boot || echo "${RED}Boot non restaure : lance 'gcc/flash.sh boot' a la main${NC}"
else
echo "${RED}Boot non compile : lance 'make -C gcc -f makefile_boot all' puis 'gcc/flash.sh boot'${NC}"
fi
}
GENERATED_CFG=""
cleanup() {
[[ -n "$GENERATED_CFG" && -f "$GENERATED_CFG" ]] && rm -f "$GENERATED_CFG"
restore_boot
}
trap cleanup EXIT
echo ">>> Chargement de la passerelle dans la DK"
"$CLI" -c port=SWD ap=1 mode=UR -d "$LOADER_HEX" -v -hardRst
# --- 2. port serie virtuel du ST-LINK ---------------------------------------------------------
sleep 1
COMNAME="$(ls /dev/serial/by-id/ 2>/dev/null | grep -i "STLINK-V3" | head -n 1 || true)"
[[ -n "$COMNAME" ]] || die "port serie du ST-LINK non detecte (/dev/serial/by-id)"
COMPORT="$(readlink -f "/dev/serial/by-id/$COMNAME")"
echo "Port serie : $COMPORT"
stty -F "$COMPORT" 2000000 cs8 -cstopb -parenb || die "port serie deja utilise (ferme tout terminal ou debug)"
sleep 1
stty -F "$COMPORT" 2000000 cs8 -cstopb -parenb
# --- 3. configuration de flash (meme principe que le script ST) -------------------------------
GENERATED_CFG="$NCP_DIR/mission_t01_flash_prog_cfg_auto.ini"
sed "s#^filedir[[:space:]]*=[[:space:]]*\./${PREFIX}_v.*\.bin[[:space:]]*\$#filedir = ./${FW}#" \
"$NCP_DIR/mission_t01_flash_prog_cfg.ini" > "$GENERATED_CFG"
# --- 4. flash du module -----------------------------------------------------------------------
echo ">>> Flash du module en cours ..."
attempt=0
until "$QCONN" --port "$COMPORT" --config "$GENERATED_CFG" --efuse="$NCP_DIR/efusedata.bin"; do
attempt=$((attempt + 1))
[[ "$attempt" -lt 2 ]] || die "echec du flash du module"
echo "QConn_Flash a echoue, nouvel essai ..."
sleep 1
done
echo "${NC}>>> Firmware $FW installe."
+171
View File
@@ -0,0 +1,171 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>CelloFX Remote</title>
<meta name="description" content="Télécommande BLE des effets CelloFX (STM32H7S78-DK)">
<meta name="theme-color" content="#14161c">
<meta name="webapp:icon" content="🎻">
<meta name="webapp:orientation" content="portrait">
<style>
:root{--bg:#14161c;--card:#1e222b;--fg:#e8eaf0;--mut:#8b92a3;--acc:#ff9f43;--ok:#3ddc97;--ko:#ff5d5d;--r:14px}
@media (prefers-color-scheme:light){:root{--bg:#f2f3f7;--card:#fff;--fg:#1b1e26;--mut:#6b7285}}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
body{margin:0;background:var(--bg);color:var(--fg);font:16px system-ui,sans-serif;
padding:env(safe-area-inset-top) 16px calc(env(safe-area-inset-bottom) + 24px)}
header{position:sticky;top:0;z-index:5;background:var(--bg);padding:12px 0;display:flex;gap:10px;align-items:center}
header h1{font-size:20px;margin:0;flex:1}
.dot{width:10px;height:10px;border-radius:50%;background:var(--ko);display:inline-block;margin-right:6px}
.dot.on{background:var(--ok)}
button.main{min-height:44px;padding:0 16px;border:0;border-radius:22px;background:var(--acc);color:#1b1200;font-weight:700;font-size:15px}
.card{background:var(--card);border-radius:var(--r);padding:14px 16px;margin:12px 0}
.card h2{font-size:17px;margin:0;display:flex;align-items:center;justify-content:space-between;min-height:44px}
.row{margin:10px 0}
.row label{display:flex;justify-content:space-between;color:var(--mut);font-size:14px}
.row b{color:var(--fg);font-variant-numeric:tabular-nums}
input[type=range]{width:100%;height:36px;accent-color:var(--acc)}
.sw{position:relative;width:52px;height:30px;flex:none}
.sw input{opacity:0;position:absolute;inset:0;width:100%;height:100%;margin:0;z-index:2}
.sw i{position:absolute;inset:0;border-radius:15px;background:#555;transition:.15s}
.sw i::after{content:"";position:absolute;top:3px;left:3px;width:24px;height:24px;border-radius:50%;background:#fff;transition:.15s}
.sw input:checked+i{background:var(--acc)}
.sw input:checked+i::after{transform:translateX(22px)}
.eq{display:grid;grid-template-columns:repeat(10,1fr);gap:2px;height:210px;margin-top:8px}
.eq div{display:flex;flex-direction:column;align-items:center;font-size:11px;color:var(--mut)}
.eq input{writing-mode:vertical-lr;direction:rtl;flex:1;width:100%;min-width:0;margin:0}
.eq span{margin-top:4px}
#msg{color:var(--mut);font-size:13px;margin:4px 0 0;min-height:1.2em}
</style>
</head>
<body>
<header>
<h1><span class="dot" id="dot"></span>CelloFX</h1>
<button class="main" id="btn">Connecter</button>
</header>
<p id="msg"></p>
<main id="app"></main>
<script>
"use strict";
const SERVICE = "c3110000-7e8a-4b1c-9d52-f0a1e4b7c600";
const CHAR_CTRL = "c3110001-7e8a-4b1c-9d52-f0a1e4b7c600";
const CHAR_STATE = "c3110002-7e8a-4b1c-9d52-f0a1e4b7c600";
const SNAPSHOT = 0xFF, MAX_RECORDS = 6; /* 18 octets : tient dans un MTU par defaut de 23 */
/* kind: b = interrupteur, u = 0..1 (fil 0..1000), g = dB (fil x10), ms = millisecondes */
const U = (id, name) => ({id, name, kind: "u", min: 0, max: 1000});
const B = id => ({id, kind: "b"});
const EQ = ["31","62","125","250","500","1k","2k","4k","8k","16k"];
const SECTIONS = [
{title: "Général", on: B(0x00), params: [U(0x01, "Volume")]},
{title: "Octaver", on: B(0x10), params: [U(0x11,"−2 oct"),U(0x12,"−1 oct"),U(0x13,"Sec"),U(0x14,"+1 oct"),U(0x15,"+2 oct")]},
{title: "Égaliseur", on: B(0x20), eq: true},
{title: "Overdrive", on: B(0x50), params: [U(0x51,"Drive"),U(0x52,"Tone"),U(0x53,"Niveau")]},
{title: "Saturation", on: B(0x60), params: [U(0x61,"Drive"),U(0x62,"Niveau")]},
{title: "Echo", on: B(0x70), params: [{id:0x71,name:"Délai",kind:"ms",min:0,max:1000},{id:0x72,name:"Feedback",kind:"u",min:0,max:950},U(0x73,"Mix")]},
{title: "Reverb", on: B(0x80), params: [U(0x81,"Taille"),U(0x82,"Amortissement"),U(0x83,"Mix")]},
];
const state = new Map(); /* id -> valeur fil */
const widgets = new Map(); /* id -> fonction de mise a jour de l'UI */
let ctrl = null, pending = new Map(), timer = 0, demo = false;
const $ = s => document.querySelector(s);
const msg = t => { $("#msg").textContent = t || ""; };
function fmt(p, v) {
if (p.kind === "g") return (v / 10).toFixed(1) + " dB";
if (p.kind === "ms") return v + " ms";
return Math.round(v / 10) + " %";
}
function slider(p, label, extraClass) {
const wrap = document.createElement("div");
wrap.className = extraClass || "row";
const input = document.createElement("input");
input.type = "range"; input.min = p.min; input.max = p.max; input.step = p.kind === "g" ? 5 : (p.kind === "ms" ? 5 : 5);
input.value = p.min;
const val = document.createElement("b");
const upd = v => { input.value = v; val.textContent = fmt(p, v); };
input.addEventListener("input", () => { val.textContent = fmt(p, +input.value); send(p.id, +input.value); });
widgets.set(p.id, upd);
if (extraClass) { wrap.append(input); wrap.dataset.val = ""; const s = document.createElement("span"); s.textContent = label; wrap.append(s); }
else { const l = document.createElement("label"); l.append(label, val); wrap.append(l, input); }
upd(p.min);
return wrap;
}
function build() {
const app = $("#app");
for (const s of SECTIONS) {
const card = document.createElement("section"); card.className = "card";
const h = document.createElement("h2"); h.append(s.title);
const sw = document.createElement("label"); sw.className = "sw";
const cb = document.createElement("input"); cb.type = "checkbox";
cb.addEventListener("change", () => { navigator.vibrate?.(15); send(s.on.id, cb.checked ? 1 : 0); });
sw.append(cb, document.createElement("i")); h.append(sw); card.append(h);
widgets.set(s.on.id, v => { cb.checked = !!v; });
if (s.eq) {
const eq = document.createElement("div"); eq.className = "eq";
EQ.forEach((f, i) => eq.append(slider({id: 0x30 + i, kind: "g", min: -240, max: 240}, f, "col")));
card.append(eq);
eq.querySelectorAll(".col").forEach(c => c.style.cssText = "display:flex;flex-direction:column;align-items:center;font-size:11px;color:var(--mut)");
} else for (const p of s.params) card.append(slider(p, p.name));
app.append(card);
}
}
/* ---- envoi : on garde la derniere valeur par id et on groupe ~40 ms ---- */
function send(id, value) {
state.set(id, value);
pending.set(id, value);
if (!timer) timer = setTimeout(flush, 40);
}
async function flush() {
timer = 0;
const items = [...pending]; pending = new Map();
if (!ctrl) return;
try {
for (let i = 0; i < items.length; i += MAX_RECORDS) {
const chunk = items.slice(i, i + MAX_RECORDS);
const buf = new DataView(new ArrayBuffer(chunk.length * 3));
chunk.forEach(([id, v], k) => { buf.setUint8(k * 3, id); buf.setInt16(k * 3 + 1, v, true); });
await ctrl.writeValueWithoutResponse(buf);
}
} catch (e) { msg("Envoi impossible : " + e.message); }
}
function onState(ev) {
const dv = ev.target.value;
for (let o = 0; o + 3 <= dv.byteLength; o += 3) {
const id = dv.getUint8(o), v = dv.getInt16(o + 1, true);
state.set(id, v); widgets.get(id)?.(v);
}
}
/* ---- connexion ---- */
function setConnected(on) { $("#dot").classList.toggle("on", on); $("#btn").textContent = on ? "Déconnecter" : "Connecter"; }
let device = null;
async function connect() {
if (!navigator.bluetooth) { demo = true; setConnected(true); msg("Web Bluetooth indisponible ici : mode démo (rien n'est envoyé). Ouvre la page dans Chrome Android en HTTPS."); return; }
try {
msg("Recherche de CelloFX…");
device = await navigator.bluetooth.requestDevice({filters: [{name: "CelloFX"}], optionalServices: [SERVICE]});
device.addEventListener("gattserverdisconnected", () => { ctrl = null; setConnected(false); msg("Déconnecté."); });
const server = await device.gatt.connect();
const svc = await server.getPrimaryService(SERVICE);
ctrl = await svc.getCharacteristic(CHAR_CTRL);
const st = await svc.getCharacteristic(CHAR_STATE);
st.addEventListener("characteristicvaluechanged", onState);
await st.startNotifications(); /* le module envoie alors un instantane */
await ctrl.writeValueWithResponse(new Uint8Array([SNAPSHOT, 0, 0])); /* au cas ou */
setConnected(true); msg("Connecté.");
} catch (e) { setConnected(false); msg(e.name === "NotFoundError" ? "Aucun appareil choisi." : "Connexion échouée : " + e.message); }
}
function disconnect() { device?.gatt?.connected && device.gatt.disconnect(); ctrl = null; demo = false; setConnected(false); msg(""); }
$("#btn").addEventListener("click", () => ($("#dot").classList.contains("on") ? disconnect() : connect()));
build();
</script>
</body>
</html>