ajout BLE
This commit is contained in:
1 parent
66bb319423
commit
3757975e51
20 files changed
+77318
-68028
No files matched your search
@@ -159,6 +159,21 @@ STM32CubeIDE alternative: debug Boot, terminate (Ctrl+F2), debug Appli, Resume (
|
||||
- **SPI** - Touch interface
|
||||
- **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
|
||||
|
||||
### Effects Chain (in order)
|
||||
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "audio_effects.h"
|
||||
#include "audio_analysis.h"
|
||||
#include "audio_looper.h"
|
||||
#include "remote_ble.h"
|
||||
#include <string.h>
|
||||
/* USER CODE END Includes */
|
||||
|
||||
@@ -264,6 +265,7 @@ int main(void)
|
||||
|
||||
/* USER CODE BEGIN RTOS_THREADS */
|
||||
/* add threads, ... */
|
||||
RemoteBle_Start(); /* controle a distance BLE (module ST67W611M1) */
|
||||
/* USER CODE END RTOS_THREADS */
|
||||
|
||||
/* USER CODE BEGIN RTOS_EVENTS */
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,30 @@
|
||||
#include "main.h"
|
||||
|
||||
/* 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 */
|
||||
|
||||
/* Global variables ----------------------------------------------------------*/
|
||||
/* 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 */
|
||||
|
||||
/* 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)
|
||||
{
|
||||
/* 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 */
|
||||
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);
|
||||
}
|
||||
/* USER CODE BEGIN spi_port_transfer_2 */
|
||||
|
||||
/* USER CODE END spi_port_transfer_2 */
|
||||
|
||||
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);
|
||||
#endif /* __DCACHE_PRESENT */
|
||||
/* 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 */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 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 BEGIN spi_port_transfer_dma_2 */
|
||||
@@ -314,7 +372,15 @@ int32_t spi_port_set_cs(int32_t state)
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
|
||||
/* Weak functions redefinition -----------------------------------------------*/
|
||||
@@ -335,7 +401,7 @@ void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
|
||||
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
|
||||
{
|
||||
/* USER CODE BEGIN HAL_SPI_RxCpltCallback_1 */
|
||||
|
||||
spi_port_dma_rx_copyback(hspi);
|
||||
/* USER CODE END HAL_SPI_RxCpltCallback_1 */
|
||||
if (hspi == &NCP_SPI_HANDLE)
|
||||
{
|
||||
@@ -349,7 +415,7 @@ void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
|
||||
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
|
||||
{
|
||||
/* USER CODE BEGIN HAL_SPI_TxRxCpltCallback_1 */
|
||||
|
||||
spi_port_dma_rx_copyback(hspi);
|
||||
/* USER CODE END HAL_SPI_TxRxCpltCallback_1 */
|
||||
if (hspi == &NCP_SPI_HANDLE)
|
||||
{
|
||||
|
||||
+10493
-6137
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+32765
-30941
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+32765
-30941
File diff suppressed because it is too large.
Load diff
@@ -29,6 +29,7 @@
|
||||
volatile bool doSampleTouch = false;
|
||||
|
||||
extern "C" I2C_HandleTypeDef hi2c1;
|
||||
extern "C" void ST67_ExtiCallback(uint16_t gpio_pin); /* Core/Src/st67_glue.c */
|
||||
|
||||
using namespace touchgfx;
|
||||
extern "C"
|
||||
@@ -52,6 +53,9 @@ extern "C"
|
||||
doSampleTouch = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/* SPI_RDY du module Wi-Fi/BLE ST67W611M1 : une seule fonction HAL_GPIO_EXTI_Callback possible */
|
||||
ST67_ExtiCallback(GPIO_Pin);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+30
-1
@@ -208,6 +208,9 @@ cpp_compiler_options += $(float_options) -mcpu=cortex-m7 -DCORE_CM7
|
||||
linker_options += $(float_options) -mcpu=cortex-m7
|
||||
|
||||
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
|
||||
|
||||
# include everything + specific vendor folders
|
||||
@@ -339,6 +342,12 @@ board_include_paths := \
|
||||
Appli/Core/Inc \
|
||||
Appli/FATFS/App \
|
||||
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/drivers/sd \
|
||||
$(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/diskio.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 := \
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
Executable
+123
@@ -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."
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user