#include <Arduino.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// USART1 -> RYLR998: PA9 TX, PA10 RX
// USART2 -> Serial Monitor: PA2 TX, PA3 RX
HardwareSerial LoRaSerial(PA10, PA9);
HardwareSerial DebugSerial(PA3, PA2);
static const uint32_t UART_BAUD = 115200;
static const uint8_t POT_PIN = PA0;
static const int ADC_THRESHOLD = 220;
static const size_t RX_LINE_SIZE = 160;
static const size_t PAYLOAD_SIZE = 64;
static const size_t COMMAND_SIZE = 96;
// -----------------------------------------------------------------------------
// RX PERMANENTE: equivalent conceptually to RxDone + packet service.
// No function waits for a reply. Every byte arriving from the RYLR998 is consumed
// as soon as loop() runs, independently of ADC/potentiometer events.
// -----------------------------------------------------------------------------
static char rxLine[RX_LINE_SIZE];
static size_t rxIndex = 0;
static bool commandPending = false;
static uint32_t commandDeadline = 0;
static char pendingCommand[COMMAND_SIZE];
static const char *configCommands[] = {
"AT",
"AT+ADDRESS=1",
"AT+NETWORKID=18",
"AT+BAND=915000000",
"AT+PARAMETER=9,7,1,12",
"AT+CRFOP=22",
"AT+ADDRESS?",
"AT+NETWORKID?",
"AT+BAND?",
"AT+PARAMETER?",
"AT+CRFOP?",
"AT+UID?",
"AT+VER?"
};
static const size_t CONFIG_COUNT = sizeof(configCommands) / sizeof(configCommands[0]);
static size_t configIndex = 0;
static bool configFinished = false;
static void processLoRaLine(const char *line) {
if (line == nullptr || line[0] == '\0') return;
DebugSerial.print("[RYLR998 -> STM32] ");
DebugSerial.println(line);
// Any complete line is proof that RX is alive. Responses to a pending AT
// command release the transmitter state so the next command can be sent.
if (commandPending) {
commandPending = false;
}
if (strncmp(line, "+RCV=", 5) == 0) {
DebugSerial.println("[RX EVENT] Paquete LoRa recibido asincronamente");
// Future drone logic belongs here: parse command, notify control task,
// then create AT+SEND ACK. RX remains independent of that processing.
} else if (strncmp(line, "+ERR=", 5) == 0) {
DebugSerial.println("[RYLR998 ERROR] El modulo reporto un error AT");
}
}
static void serviceLoRaRx() {
while (LoRaSerial.available() > 0) {
int raw = LoRaSerial.read();
if (raw < 0) continue;
char c = (char)raw;
if (c == '\r') {
continue;
}
if (c == '\n') {
if (rxIndex > 0) {
rxLine[rxIndex] = '\0';
processLoRaLine(rxLine);
rxIndex = 0;
}
continue;
}
if (rxIndex < RX_LINE_SIZE - 1) {
rxLine[rxIndex++] = c;
} else {
DebugSerial.println("[RX WARN] Trama UART demasiado larga; buffer reiniciado");
rxIndex = 0;
}
}
}
static bool transmitAT(const char *command, uint32_t timeoutMs = 1000) {
if (command == nullptr || command[0] == '\0' || commandPending) {
return false;
}
snprintf(pendingCommand, sizeof(pendingCommand), "%s", command);
DebugSerial.print("[STM32 -> RYLR998] ");
DebugSerial.println(command);
LoRaSerial.print(command);
LoRaSerial.print("\r\n");
commandPending = true;
commandDeadline = millis() + timeoutMs;
return true;
}
static void serviceCommandTimeout() {
if (!commandPending) return;
if ((int32_t)(millis() - commandDeadline) >= 0) {
DebugSerial.print("[WARN] Timeout esperando respuesta a: ");
DebugSerial.println(pendingCommand);
commandPending = false;
}
}
static void serviceConfiguration() {
if (configFinished || commandPending) return;
if (configIndex < CONFIG_COUNT) {
if (configIndex == 6) {
DebugSerial.println();
DebugSerial.println("------ CONFIGURACION DEL RYLR998 ------");
}
if (transmitAT(configCommands[configIndex], 1000)) {
configIndex++;
}
return;
}
configFinished = true;
DebugSerial.println("---------------------------------------");
DebugSerial.println();
DebugSerial.println("RX UART permanece activo continuamente.");
DebugSerial.println("Mueva el potenciometro. Un cambio >= 220 cuentas ADC enviara AT+SEND.");
}
static void servicePotentiometer() {
static int lastAdc = -1;
static uint32_t seq = 0;
// Do not start application transmissions until initial configuration has been
// sent. RX, however, has been running continuously since loop() started.
if (!configFinished || commandPending) return;
int adc = analogRead(POT_PIN);
if (lastAdc < 0) {
lastAdc = adc;
return;
}
if (abs(adc - lastAdc) < ADC_THRESHOLD) return;
lastAdc = adc;
seq++;
char payload[PAYLOAD_SIZE];
int payloadLen = snprintf(
payload,
sizeof(payload),
"SEQ=%lu;ADC=%d",
(unsigned long)seq,
adc
);
if (payloadLen <= 0 || (size_t)payloadLen >= sizeof(payload)) {
DebugSerial.println("[ERROR] No se pudo construir el payload AT+SEND");
return;
}
char command[COMMAND_SIZE];
int commandLen = snprintf(
command,
sizeof(command),
"AT+SEND=2,%d,%s",
payloadLen,
payload
);
if (commandLen <= 0 || (size_t)commandLen >= sizeof(command)) {
DebugSerial.println("[ERROR] No se pudo construir el comando AT+SEND");
return;
}
DebugSerial.println();
DebugSerial.println("[EVENT] Cambio del potenciometro detectado");
DebugSerial.print("[ADC] ");
DebugSerial.println(adc);
DebugSerial.print("[FRAME CHECK] payload='");
DebugSerial.print(payload);
DebugSerial.print("' length=");
DebugSerial.println(payloadLen);
transmitAT(command, 1200);
}
void setup() {
DebugSerial.begin(UART_BAUD);
LoRaSerial.begin(UART_BAUD, SERIAL_8N1);
analogReadResolution(12);
pinMode(POT_PIN, INPUT_ANALOG);
delay(300);
DebugSerial.println();
DebugSerial.println("============================================");
DebugSerial.println(" STM32 BLUE PILL + RYLR998 ASYNC UART TEST");
DebugSerial.println("============================================");
DebugSerial.println("[RX] Escucha UART continua habilitada desde el inicio");
}
void loop() {
// RX is always serviced first and repeatedly. It never depends on ADC activity.
serviceLoRaRx();
serviceCommandTimeout();
serviceConfiguration();
// Service RX again before application work, mirroring an event-driven receiver.
serviceLoRaRx();
servicePotentiometer();
// Final RX service before yielding the CPU.
serviceLoRaRx();
// Cooperative yield only; no long blocking wait for UART responses.
delay(1);
}