/* EMBARCATECH - INTRODUÇÃO AO PROTOCOLO LORA - CAPÍTULO 3 / PARTE 5
* BitDogLAB - Transmissor LoRa (TX)
* Comunicação com módulo LoRa (RFM95W - SX1276) via protocolo SPI para transmissão de informações.
* Prof: Ricardo Prates
*/
#include <stdio.h> // Biblioteca em C que fornece funções para realizar operações de entrada e saída (E/S) padrão
#include <string.h> // Biblioteca em C que fornece funções para manipular strings.
#include <stdlib.h> // Biblioteca em em C que fornece funções para realizar conversão de tipos de dados e outras funções gerais.
#include <math.h> // Biblioteca em C que fornece funções para realizar operações matemáticas
#include "pico/stdlib.h" // Biblioteca que fornece funções para realizar operações comuns de programação do Raspberry Pi Pico
#include "hardware/gpio.h" // Biblioteca que fornece funções para trabalhar com os pinos GPIO
#include "hardware/spi.h" // Biblioteca que fornece funções para trabalhar com a interface SPI
#include "lora.h" // Biblioteca própria com endereços do registradores do SX1276 - Módulo LoRa
// GPIO BitDogLAB - Led azul
#define PIN_LED 12
// Definição das configurações do protocolo SPI
#define PIN_MISO 16
#define PIN_MOSI 19
#define PIN_CS 17
#define PIN_SCK 18
#define PIN_RST 20
#define PIN_DIO0 8
#define SPI_PORT spi0
// Variáveis globais - configuração LoRa
typedef enum {lmIdle, lmListening, lmSending} tLoRaMode;
static tLoRaMode LoRaMode;
static int SendingRTTY=0;
static int InRTTYMode=0;
static int ImplicitOrExplicit;
static char PayloadID[32];
static char Callsign[] = "RICK";
// Valores para cálculo do ToA - Time on Air:
int payload_len = 10; // Tamanho do payload em byte
int preamble_len = 8; // Tamanho do preâmbulo
int sf = 7; // fator de espalhamento (Spreading Factor)
int crc = 0; // tamanho do CRC (Cyclic Redundancy Check) em bytes (normalmente 2 bytes)
int ih = 0; // Cabeçalho implícito (Implicit Header) (0 ou 1)
int bw = 125000; // largura de banda (Bandwidth) em Hz
int cr = 1; // Taxa de codificação (Coding Rate)
int de = 0; // LowDataRateOptimize=1, DE=0 caso contrário
// Protótipos de Funções LoRa
float lerSensor();
void setLora(float Frequency, int Mode);
static inline void cs_select();
static inline void cs_deselect();
static void writeRegister(uint8_t reg, uint8_t data);
static uint8_t readRegister(uint8_t addr);
void SetDeviceMode(uint8_t newMode);
void SetModemToLoRaMode();
void SetFrequency(double Frequency);
void SendLora();
void imprimir_binario(int num);
float calculate_toa(int payload_len, int preamble_len, int sf, int crc, int ih, int bw, int cr, int de);
// Função Principal
int main()
{
// Inicializar interfaces de entrada e saída padrão
stdio_init_all();
// Iniciando o protocolo SPI
sleep_ms(500);
printf("Iniciando o Protocolo SPI");
spi_init(SPI_PORT, 500*1000);
gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
// Chip select - configuração inicial active-low
gpio_init(PIN_CS);
gpio_set_dir(PIN_CS, GPIO_OUT);
gpio_put(PIN_CS, 1);
// Pino Reset - Desativado no nível alto
gpio_init(PIN_RST);
gpio_set_dir(PIN_RST, GPIO_OUT);
gpio_put(PIN_RST, 1);
// Led BitDogLab no pino 12
gpio_init(PIN_LED);
gpio_set_dir(PIN_LED, GPIO_OUT);
// DIO0 - input
gpio_init(PIN_DIO0);
gpio_set_dir(PIN_DIO0, GPIO_IN);
// Tempo para processamento das informações
sleep_ms(500);
// Setup do protocolo LoRa
printf("Iniciando o rádio LoRa\n");
setLora(915.000, 1);
strcpy(PayloadID, Callsign);
// Cálculo de parâmetros relacionados ao Time on Air (ToA)
calculate_toa(payload_len, preamble_len, sf, crc, ih, bw, cr, de);
//Message
char buffer_lora[40];
// Loop principal
while (true) {
//Lê os dados do sensor
float temperatura = lerSensor();
// Formata e armazena uma string
sprintf(buffer_lora, "%0.2f", temperatura);
// Exibe a mensagem no serial monitor
printf("Mensagem enviada: %s\n", buffer_lora);
// Envia a mensagem pelo protocolo LoRa
SendLora(buffer_lora, 40);
// Pisca o LED
gpio_put(PIN_LED, 1);
sleep_ms(50);
gpio_put(PIN_LED, 0);
sleep_ms(50);
// Pequeno atraso para evitar sobrecarga no loop principal
sleep_ms(2000);
}
}
// Funções LoRa
// SPI CS (Chip Select) - Nível baixo - Iniciar comunicação SPI
static inline void cs_select()
{
asm volatile("nop \n nop \n nop"); //Introduzir atraso preciso - nop (No Operation)
gpio_put(PIN_CS, 0); // Ativo em nível baixo
asm volatile("nop \n nop \n nop"); //Introduzir atraso preciso
}
// SPI CS (Chip Select) - Nível alto - Finalizar comunicação SPI
static inline void cs_deselect()
{
asm volatile("nop \n nop \n nop"); //Introduzir atraso preciso - nop (No Operation)
gpio_put(PIN_CS, 1); // Inativo em nível alto
asm volatile("nop \n nop \n nop"); //Introduzir atraso preciso
}
// Escrever no registrador do Rádio LoRa - SX1276
static void writeRegister(uint8_t reg, uint8_t data)
{
uint8_t buf[2]; // Buffer para escrita no módulo LoRa
buf[0] = reg | 0x80; // Registrador com operação de escrita
buf[1] = data; // Byte de informação inserida no registrador
cs_select(); // Iniciar comunicação SPI
spi_write_blocking(SPI_PORT, buf, 2); // Transmitir informação do Buffer pela interface SPI
cs_deselect(); // Finalizar comunicação SPI
sleep_ms(1); // Delay de 1 ms
}
// Ler conteúdo registrador do Rádio LoRa - SX1276
static uint8_t readRegister(uint8_t addr)
{
uint8_t buf[1]; // Buffer para leitura do módulo LoRa
addr &= 0x7F; // Resgistrador com operação de leitura
cs_select(); // Iniciar comunicação SPI
spi_write_blocking(SPI_PORT, &addr, 1); // Transmitir informação do Buffer pela interface SPI - Endereço de leitura
sleep_ms(1); // Delay de 1 ms
spi_read_blocking(SPI_PORT, 0, buf, 1); // Receber informação pela interface SPI e armazenar no buffer
cs_deselect(); // Finalizar comunicação SPI
sleep_ms(1); // Delay de 1 ms
printf("READ %02X\n", buf[0]); // Imprimir resultado obtido pela leitura do registrador
return buf[0]; // Retornar resultado do valor armazenado no registrador
}
// Selecionar modo de funcionamento do rádio LoRa
void SetDeviceMode(uint8_t newMode)
{
static uint8_t currentMode = 0xFF;
if (newMode == currentMode)
return;
switch (newMode)
{
case RF95_MODE_TX: // Modo Tx
writeRegister(REG_LNA, LNA_OFF_GAIN);
writeRegister(REG_PA_CONFIG, PA_MAX_UK);
writeRegister(REG_OPMODE, newMode);
currentMode = newMode;
break;
case RF95_MODE_RX_CONTINUOUS:
writeRegister(REG_PA_CONFIG, PA_OFF_BOOST); // Modo Rx
writeRegister(REG_LNA, LNA_MAX_GAIN);
writeRegister(REG_OPMODE, newMode);
currentMode = newMode;
break;
case RF95_MODE_SLEEP: // Modo SLEEP
writeRegister(REG_OPMODE, newMode);
currentMode = newMode;
break;
case RF95_MODE_STANDBY: // Modo STANDBY
writeRegister(REG_OPMODE, newMode);
currentMode = newMode;
break;
default: return;
}
if (newMode != RF95_MODE_SLEEP)
{
sleep_ms(1);
}
return;
}
// Definição do modo LoRa
void SetModemToLoRaMode()
{
// printf("Setting LoRa Mode\n");
SetDeviceMode(RF95_MODE_SLEEP); // Configura para o modo sleep
writeRegister(REG_OPMODE,0x80); // Configura para a modulação LoRa
// printf("LoRa Mode Set\n");
}
// Definir frequência de operação
void SetFrequency(double Frequency)
{
unsigned long FrequencyValue; //Frequência corrida - valor para os registradores do SX1276
printf("Frequência escolhida %.3f MHz\n", Frequency);
Frequency = Frequency * 7110656 / 434; //Fórmula para correção da frequência - considerando o padrão de 434 MHz - 6C 80 00
FrequencyValue = (unsigned long)(Frequency);
printf("Frequencia calculada para ajuste dos registradores: %lu\n", FrequencyValue);
writeRegister(REG_FRF_MSB, (FrequencyValue >> 16) & 0xFF); // Escrita no registrador RegFrMsb (0x06)
writeRegister(REG_FRF_MID, (FrequencyValue >> 8) & 0xFF); // Escrita no registrador RegFrMid (0x07)
writeRegister(REG_FRF_LSB, FrequencyValue & 0xFF); // Escrita no registrador RegFrLsb (0x08)
printf("Impressão de valor binário da frequência:\n");
imprimir_binario(FrequencyValue); // Imprimir o valor binário de 32 bits - FrequencyValue
printf("\n");
printf("Frequ. Regs. config:\n");
// Teste de leitura dos registradores RegFrMsb, RegFrMid e RegFrLsb.
readRegister(REG_FRF_MSB);
readRegister(REG_FRF_MID);
readRegister(REG_FRF_LSB);
}
// Configuração inicial do Rádio LoRa
void setLora(float Frequency, int Mode){
int ErrorCoding;
int Bandwidth;
int SpreadingFactor;
int LowDataRateOptimize;
int PayloadLength;
// Deteminar Modo Lora Inicial - modo sleep - RF95_MODE_SLEEP
SetModemToLoRaMode();
// Definir Frequência
SetFrequency(Frequency);
// Configurações para o modo LoRa.
if (Mode == 2)
{
/*
Inserir novas opções de configuração aqui!
*/
}
else if (Mode == 1) // Configuração principal
{
ImplicitOrExplicit = EXPLICIT_MODE;
ErrorCoding = ERROR_CODING_4_5;
Bandwidth = BANDWIDTH_125K;
SpreadingFactor = SPREADING_7;
LowDataRateOptimize = 0;
}
else // if (Mode == 0)
{
ImplicitOrExplicit = IMPLICIT_MODE;
ErrorCoding = ERROR_CODING_4_8;
Bandwidth = BANDWIDTH_20K8;
SpreadingFactor = SPREADING_11;
LowDataRateOptimize = 0x08;
}
/*
- Se ImplicitOrExplicit for igual a IMPLICIT_MODE, o PayloadLength será definido como 255.
- Caso contrário, o PayloadLength será definido como 0.
*/
PayloadLength = ImplicitOrExplicit == IMPLICIT_MODE ? 255 : 0;
/*
Configurações Lora: Modo implícito/explícito (I/E); CR; BW; FS; LDRO (LowDataRateOptimize).
b) REG_MODEM_CONFIG (0x1D) - I/E, CR, BW;
c) REG_MODEM_CONFIG2 (0x1E) - SF, CRC;
d) REG_MODEM_CONFIG3 (0x26) - LDRO, LNA gain;
*/
writeRegister(REG_MODEM_CONFIG, ImplicitOrExplicit | ErrorCoding | Bandwidth);
writeRegister(REG_MODEM_CONFIG2, SpreadingFactor | CRC_ON);
writeRegister(REG_MODEM_CONFIG3, 0x04 | LowDataRateOptimize);
//Verificar valor que será lido pelo comando readRegister na próxima instrução.
printf("RegDetectOp:\n");
/*
1 - Configurações do registrador RegDetectOptimize (0x31) - "LoRa Detection Optimize - 0x03".
2 - Configurações do registrador RegSeqConfig2 (0x37) - "Transmit state" e "to LowPowerSelection".
*/
writeRegister(REG_DETECT_OPT, (readRegister(REG_DETECT_OPT) & 0xF8) | ((SpreadingFactor == SPREADING_6) ? 0x05 : 0x03)); // 0x05 para SF6; 0x03 caso contrário
writeRegister(REG_DETECTION_THRESHOLD, (SpreadingFactor == SPREADING_6) ? 0x0C : 0x0A); // 0x0C para SF6, 0x0A caso contrário
/*
1 - Definição do tamanho do payload - RegPayloadLength (0x22).
2 - Número do payload do último pacote recebido - RegRxNbBytes (0x13).
*/
writeRegister(REG_PAYLOAD_LENGTH, PayloadLength);
writeRegister(REG_RX_NB_BYTES, PayloadLength);
// Alterar o mapeamento DIO para 01 para que serja possível "ouvir" o TxDone na interrupção.
writeRegister(REG_DIO_MAPPING_1,0x40);
writeRegister(REG_DIO_MAPPING_2,0x00);
// Mudar para modo standby
SetDeviceMode(RF95_MODE_STANDBY);
printf("Configuração LoRa completa\n");
}
// Função para Transmitir dados pelo Rádio LoRa
void SendLora(unsigned char *buffer, int Length){
// Buffer para escrita no registrar REG_FIFO
uint8_t buf[1];
// Modo explícito
ImplicitOrExplicit = EXPLICIT_MODE;
// Modo RTTY (Radio Teletype) = 0
InRTTYMode = 0;
// Configurações de envio
SetDeviceMode(RF95_MODE_STANDBY); // Configura para o modo Standby
writeRegister(REG_DIO_MAPPING_1, 0x40); // 01 00 00 00 mapear DIO0 para o TxDone
writeRegister(REG_FIFO_TX_BASE_AD, 0x00); // escrever o endereço base no buffer de dados FIFO para o modulador TX
writeRegister(REG_FIFO_ADDR_PTR, 0x00); // Ponteiro de endereço da interface SPI no buffer de dados FIFO
if (ImplicitOrExplicit == EXPLICIT_MODE)
{
writeRegister(REG_PAYLOAD_LENGTH, Length); // Comprimento do payload em bytes.
}
//Escrita no registrador FIFO
cs_select();
/*
"LoRaTM base-band FIFO data input/output. FIFO is cleared an not accessible when device is in SLEEP mode"
*/
buf[0] = REG_FIFO | 0x80;
cs_select();
spi_write_blocking(SPI_PORT, buf, 1);
spi_write_blocking(SPI_PORT, buffer, Length);
cs_deselect();
// Configurar para o modo de transmissão - transmitir pacote
SetDeviceMode(RF95_MODE_TX);
// Modo de envio de dados
LoRaMode = lmSending;
SendingRTTY = 0;
}
// Função para simular os dados de um sensor de temperatura
float lerSensor() {
// Gera um valor aleatório entre 200 e 300
return 20.0 + (float)(rand() % 101) / 10.0;
}
// Função imprimir número binário
void imprimir_binario(int num) {
int i;
for (i = 31; i >= 0; i--) {
(num & (1 << i)) ? printf("1") : printf("0");
}
}
// Cálculo de parâmetros de transmissão - ex: Tyme on Air (ToA)
float calculate_toa(int payload_len, int preamble_len, int sf, int crc, int ih, int bw, int cr, int de){
int n_payload; // Número de símbolos do payload
float t_payload;
float t_preamble; // Tempo de duração do preâmbulo
float toa; // Time on Air (ToA)
float symbol_rate; // Taxa de transmissão de símbolos (Rs);
float symbol_period; // Período de transmissão de símbolo (Ts);
float bit_rate; // Taxa de modulação
// Cálculo de Rs e Ts
symbol_rate = (bw)/pow(2, sf);
symbol_period = 1/symbol_rate;
printf("Rs: %.02f sim/s, Ts: %f ms \n", symbol_rate, symbol_period*1000);
// Bit Rate (Rb)
bit_rate = (sf * (4.0 / (4.0 + cr))) / (pow(2, sf) / bw);
printf("Rb: %f bps\n", bit_rate);
// Tempo do preâmbulo e do payload
t_preamble = (preamble_len + 4.25)*symbol_period;
n_payload = 8 + MAX(ceil((8.0*payload_len - 4.0*sf + 28.0 - 16.0*crc - 20.0*ih)/(4.0*(sf-2.0*de)))*(cr+4.0) , 0);
t_payload = (float)n_payload*symbol_period;
printf("Preamble length: %f ms e o payload length %f ms \n", t_preamble*1000, t_payload*1000);
// Tempo do Time on Air (ToM)
toa = t_preamble+t_payload;
printf("ToA: %f ms\n", toa*1000);
}