345 lines
11 KiB
C
345 lines
11 KiB
C
/*
|
|
Copyright 2022, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
|
|
|
This file is part of Savanni's AVR library.
|
|
|
|
Lumeto is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
|
|
Lumeto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include "rfm.h"
|
|
|
|
#define REG_FIFO 0x00
|
|
#define REG_OP_MODE 0x01
|
|
#define REG_DATA_MODUL 0x02
|
|
#define REG_BITRATE_MSB 0x03
|
|
#define REG_BITRATE_LSB 0x04
|
|
#define REG_FDEV_MSB 0x05
|
|
#define REG_FDEV_LSB 0x06
|
|
#define REG_VERSION 0x10
|
|
#define REG_PA_LEVEL 0x11
|
|
#define REG_RXBW 0x19
|
|
#define REG_AFCBW 0x1a
|
|
#define REG_RSSI_CONFIG 0x23
|
|
#define REG_RSSI_VALUE 0x24
|
|
#define REG_DIO_MAPPING1 0x25
|
|
#define REG_IRQ_FLAGS1 0x27
|
|
#define REG_IRQ_FLAGS2 0x28
|
|
#define REG_RSSI_THRESH 0x29
|
|
#define REG_PREAMBLE_MSB 0x2c
|
|
#define REG_PREAMBLE_LSB 0x2d
|
|
#define REG_SYNC_CONFIG 0x2e
|
|
#define REG_SYNC_VALUE1 0x2f
|
|
#define REG_PACKET_CONFIG1 0x37
|
|
#define REG_FIFO_THRESH 0x3c
|
|
#define REG_TEST_PA1 0x5a
|
|
#define REG_TEST_PA2 0x5c
|
|
#define REG_TEST_DAGC 0x6f
|
|
|
|
#define PA1_LOW_POWER 0x55
|
|
#define PA1_HIGH_POWER 0x5D
|
|
#define PA2_LOW_POWER 0x70
|
|
#define PA2_HIGH_POWER 0x7C
|
|
|
|
void _rfm_write(rfm_t *rfm, uint8_t reg, uint8_t *data, size_t length) {
|
|
spi_acquire(&rfm->spi);
|
|
spi_transceive(&rfm->spi, _BV(7) | reg);
|
|
for (size_t i = 0; i < length; i++) {
|
|
spi_transceive(&rfm->spi, data[i]);
|
|
}
|
|
spi_release(&rfm->spi);
|
|
}
|
|
|
|
void _rfm_read(rfm_t *rfm, uint8_t reg, uint8_t *data, size_t length) {
|
|
spi_acquire(&rfm->spi);
|
|
spi_transceive(&rfm->spi, reg);
|
|
for (size_t i = 0; i < length; i++) {
|
|
data[i] = spi_transceive(&rfm->spi, 0);
|
|
}
|
|
spi_release(&rfm->spi);
|
|
}
|
|
|
|
void _rfm_set_low_power(rfm_t *rfm) {
|
|
_rfm_write(rfm, REG_TEST_PA1, (uint8_t [1]){ PA1_LOW_POWER }, 1);
|
|
_rfm_write(rfm, REG_TEST_PA2, (uint8_t [1]){ PA2_LOW_POWER }, 1);
|
|
}
|
|
|
|
void _rfm_set_high_power(rfm_t *rfm) {
|
|
_rfm_write(rfm, REG_TEST_PA1, (uint8_t [1]){ PA1_HIGH_POWER }, 1);
|
|
_rfm_write(rfm, REG_TEST_PA2, (uint8_t [1]){ PA2_HIGH_POWER }, 1);
|
|
}
|
|
|
|
void rfm_init(rfm_t *rfm, uint8_t *sync_word, size_t length, rfm_error_e *error) {
|
|
if (!error) return;
|
|
|
|
spi_initialize(&rfm->spi);
|
|
dio_set_direction(&rfm->irq, LINE_IN);
|
|
dio_set_direction(&rfm->reset, LINE_OUT);
|
|
|
|
rfm_reset(rfm);
|
|
rfm_sleep(rfm);
|
|
|
|
uint8_t version;
|
|
_rfm_read(rfm, REG_VERSION, &version, 1);
|
|
if (version != 0x24) {
|
|
*error = radio_not_found;
|
|
return;
|
|
}
|
|
|
|
// RadioHead packet format
|
|
// 4 bytes preamble (done automatically)
|
|
// 2 Sync words 2d, d4 (configured here, but done automatically)
|
|
// 2 CRC CCITT octets computed on the header, length, and data (wat?)
|
|
// 0 to 60 bytes of data
|
|
// RSSI Threshhold -114dbm
|
|
// No RFM69HCW adress filtering. Payload has custom headers.
|
|
|
|
_rfm_write(rfm, REG_FIFO_THRESH, (uint8_t [1]){ 0x8f }, 1);
|
|
_rfm_write(rfm, REG_RSSI_THRESH, (uint8_t [1]){ 0xe4 }, 1);
|
|
|
|
// Fading margin improvement (3.4.4). Improved Margin, LowBeta off
|
|
_rfm_write(rfm, REG_TEST_DAGC, (uint8_t [1]){ 0x30 }, 1);
|
|
|
|
_rfm_set_low_power(rfm);
|
|
|
|
uint8_t syncconfig;
|
|
_rfm_read(rfm, REG_SYNC_CONFIG, &syncconfig, 1);
|
|
uint8_t word_base = REG_SYNC_VALUE1;
|
|
for (int i = 0; i < length; i++) {
|
|
_rfm_write(rfm, word_base + i, &sync_word[i], 1);
|
|
}
|
|
syncconfig &= ~0b00111000;
|
|
syncconfig |= (length - 1) << 3;
|
|
_rfm_write(rfm, REG_SYNC_CONFIG, &syncconfig, 1);
|
|
|
|
_rfm_write(rfm, REG_PREAMBLE_MSB, (uint8_t [1]){ 0 }, 1);
|
|
_rfm_write(rfm, REG_PREAMBLE_LSB, (uint8_t [1]){ 4 }, 1);
|
|
|
|
// Datamodul: CONFIG_FSK (Packet mode, FSK, no shaping: 0b00000000
|
|
// BitRateMSB: 0x00
|
|
// BitRateLSB: 0x80 (oscillator 32,000,000 / bitrate 250kbps = 128 (0x80))
|
|
// FDEVMSB: 0x10
|
|
// FDEVLSB: 0x00
|
|
// RXBW: 0xe0
|
|
// AFCBW: 0xe0
|
|
// PacketConfig1: CONFIG_WHITE (variable packet format, whitening on, crc on, no address filtering)
|
|
|
|
_rfm_write(rfm, REG_DATA_MODUL, (uint8_t [1]){ 0 }, 1);
|
|
_rfm_write(rfm, REG_BITRATE_MSB, (uint8_t [1]){ 0 }, 1);
|
|
_rfm_write(rfm, REG_BITRATE_LSB, (uint8_t [1]){ 0x80 }, 1);
|
|
_rfm_write(rfm, REG_FDEV_MSB, (uint8_t [1]){ 0x10 }, 1);
|
|
_rfm_write(rfm, REG_FDEV_LSB, (uint8_t [1]){ 0x00 }, 1);
|
|
_rfm_write(rfm, REG_RXBW, (uint8_t [1]){ 0xe0 }, 1);
|
|
_rfm_write(rfm, REG_AFCBW, (uint8_t [1]){ 0xe0 }, 1);
|
|
_rfm_write(rfm, REG_PACKET_CONFIG1, (uint8_t [1]){ 0b10110000 }, 1);
|
|
|
|
// set power level 13dbm
|
|
// This is super complicated. Dig through the RadioHead library and the documentation and make things better.
|
|
_rfm_write(rfm, REG_PA_LEVEL, (uint8_t [1]){ 0b11000000 | 31 }, 1);
|
|
}
|
|
|
|
uint8_t sync_word(rfm_t *rfm, uint8_t sync_word[8]) {
|
|
uint8_t length;
|
|
|
|
_rfm_read(rfm, REG_SYNC_CONFIG, &length, 1);
|
|
length = (length >> 2) & 0b00000111;
|
|
uint8_t word_base = REG_SYNC_VALUE1;
|
|
for (int i = 0; i < length; i++) {
|
|
_rfm_read(rfm, word_base + i, &sync_word[i], 1);
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
void rfm_reset(rfm_t *rfm) {
|
|
dio_set(&rfm->reset, 1);
|
|
_delay_us(100);
|
|
dio_set(&rfm->reset, 0);
|
|
_delay_us(5);
|
|
}
|
|
|
|
void rfm_packet_format(rfm_t *rfm, packet_format_e format, size_t length) {
|
|
switch (format) {
|
|
case unlimited:
|
|
_rfm_write(rfm, 0x37, (uint8_t [1]){ 0 }, 1);
|
|
_rfm_write(rfm, 0x38, (uint8_t [1]){ 0 }, 1);
|
|
break;
|
|
case fixed:
|
|
_rfm_write(rfm, 0x37, (uint8_t [1]){ 0 }, 1);
|
|
_rfm_write(rfm, 0x38, (uint8_t [1]){ length }, 1);
|
|
break;
|
|
case variable:
|
|
_rfm_write(rfm, 0x37, (uint8_t [1]){ _BV(7) }, 1);
|
|
_rfm_write(rfm, 0x38, (uint8_t [1]){ 0 }, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void rfm_sleep(rfm_t *rfm) {
|
|
rfm_set_mode(rfm, (op_mode_t){ .listen_on = false, .mode = sleep });
|
|
}
|
|
|
|
void rfm_standby(rfm_t *rfm) {
|
|
rfm_set_mode(rfm, (op_mode_t){ .listen_on = false, .mode = standby });
|
|
}
|
|
|
|
void rfm_listen(rfm_t *rfm) {
|
|
rfm_set_mode(rfm, (op_mode_t){ .listen_on = true, .mode = standby });
|
|
}
|
|
|
|
void rfm_transmit(rfm_t *rfm, uint8_t *data, uint8_t length) {
|
|
rfm_set_mode(rfm, (op_mode_t){ .listen_on = false, .mode = standby });
|
|
_rfm_set_low_power(rfm);
|
|
_rfm_write(rfm, REG_FIFO, &length, 1);
|
|
_rfm_write(rfm, REG_FIFO, data, length);
|
|
_rfm_write(rfm, REG_DIO_MAPPING1, (uint8_t [1]){ 0x00 }, 1);
|
|
rfm_set_mode(rfm, (op_mode_t){ .listen_on = false, .mode = tx });
|
|
}
|
|
|
|
void rfm_receive_mode(rfm_t *rfm) {
|
|
_rfm_write(rfm, REG_DIO_MAPPING1, (uint8_t [1]){ _BV(6) }, 1);
|
|
rfm_set_mode(rfm, (op_mode_t){ .listen_on = false, .mode = rx });
|
|
}
|
|
|
|
void rfm_receive(rfm_t *rfm, uint8_t data[66], uint8_t *length) {
|
|
_rfm_write(rfm, REG_DIO_MAPPING1, (uint8_t [1]){ _BV(6) }, 1);
|
|
rfm_set_mode(rfm, (op_mode_t){ .listen_on = false, .mode = rx });
|
|
_rfm_read(rfm, REG_FIFO, length, 1);
|
|
|
|
if (*length > 0) {
|
|
_rfm_read(rfm, 0x00, data, *length);
|
|
}
|
|
}
|
|
|
|
uint8_t rfm_temperature(rfm_t *rfm) {
|
|
uint8_t ready = 0;
|
|
uint8_t temp;
|
|
_rfm_write(rfm, 0x4e, (uint8_t [1]){ _BV(3) }, 1);
|
|
_delay_ms(1);
|
|
|
|
uint8_t i = 0;
|
|
while (!(ready & _BV(2)) && i < 10) {
|
|
_rfm_read(rfm, 0x4e, &ready, 1);
|
|
i++;
|
|
_delay_us(100);
|
|
}
|
|
|
|
_rfm_read(rfm, 0x4f, &temp, 1);
|
|
|
|
return temp;
|
|
}
|
|
|
|
op_mode_t rfm_mode(rfm_t *rfm) {
|
|
op_mode_t op_mode = {
|
|
.listen_on = false,
|
|
.mode = sleep,
|
|
};
|
|
uint8_t flags;
|
|
|
|
_rfm_read(rfm, 0x01, &flags, 1);
|
|
if (flags & _BV(6)) op_mode.listen_on = 1;
|
|
op_mode.mode = (flags & 0b00011100) >> 2;
|
|
|
|
return op_mode;
|
|
}
|
|
|
|
void rfm_set_mode(rfm_t *rfm, op_mode_t mode) {
|
|
op_mode_t current = rfm_mode(rfm);
|
|
uint8_t flags = mode.mode << 2;
|
|
|
|
if (mode.listen_on) {
|
|
flags |= _BV(6);
|
|
} else if (current.listen_on) {
|
|
flags |= _BV(5);
|
|
_rfm_write(rfm, REG_OP_MODE, (uint8_t [1]){ flags }, 1);
|
|
|
|
flags = flags & ~_BV(5);
|
|
}
|
|
|
|
_rfm_write(rfm, REG_OP_MODE, (uint8_t [1]){ flags }, 1);
|
|
}
|
|
|
|
uint8_t rfm_status(rfm_t *rfm) {
|
|
uint8_t status_flag;
|
|
_rfm_read(rfm, 0x28, &status_flag, 1);
|
|
return status_flag;
|
|
}
|
|
|
|
uint32_t rfm_frequency(rfm_t *rfm) {
|
|
uint8_t frequency_bytes[3];
|
|
uint32_t frequency;
|
|
_rfm_read(rfm, 0x07, &frequency_bytes[2], 1);
|
|
_rfm_read(rfm, 0x08, &frequency_bytes[1], 1);
|
|
_rfm_read(rfm, 0x09, &frequency_bytes[0], 1);
|
|
|
|
frequency = frequency_bytes[2];
|
|
frequency = frequency << 8;
|
|
frequency += frequency_bytes[1];
|
|
frequency = frequency << 8;
|
|
frequency += frequency_bytes[0];
|
|
return frequency * 61;
|
|
}
|
|
|
|
interrupt_flags_t rfm_interrupts(rfm_t *rfm) {
|
|
uint8_t irq_1;
|
|
uint8_t irq_2;
|
|
interrupt_flags_t flags = {
|
|
.mode_ready = false,
|
|
.rx_ready = false,
|
|
.tx_ready = false,
|
|
.timeout = false,
|
|
.auto_mode = false,
|
|
.sync_addr_match = false,
|
|
|
|
.fifo_full = false,
|
|
.fifo_not_empty = false,
|
|
.fifo_level = false,
|
|
.fifo_overrun = false,
|
|
.packet_sent = false,
|
|
.payload_ready = false,
|
|
.crc_ok = false,
|
|
};
|
|
|
|
_rfm_read(rfm, REG_IRQ_FLAGS1, &irq_1, 1);
|
|
_rfm_read(rfm, REG_IRQ_FLAGS2, &irq_2, 1);
|
|
|
|
if (irq_1 & _BV(7)) flags.mode_ready = true;
|
|
if (irq_1 & _BV(6)) flags.rx_ready = true;
|
|
if (irq_1 & _BV(5)) flags.tx_ready = true;
|
|
if (irq_1 & _BV(2)) flags.timeout = true;
|
|
if (irq_1 & _BV(1)) flags.auto_mode = true;
|
|
if (irq_1 & _BV(0)) flags.sync_addr_match = true;
|
|
|
|
if (irq_2 & _BV(7)) flags.fifo_full = true;
|
|
if (irq_2 & _BV(6)) flags.fifo_not_empty = true;
|
|
if (irq_2 & _BV(5)) flags.fifo_level = true;
|
|
if (irq_2 & _BV(4)) flags.fifo_overrun = true;
|
|
if (irq_2 & _BV(3)) flags.packet_sent = true;
|
|
if (irq_2 & _BV(2)) flags.payload_ready = true;
|
|
if (irq_2 & _BV(1)) flags.crc_ok = true;
|
|
|
|
return flags;
|
|
}
|
|
|
|
uint8_t rfm_rssi(rfm_t *rfm) {
|
|
uint8_t rssi_reg;
|
|
uint8_t rssi_value;
|
|
|
|
_rfm_write(rfm, REG_RSSI_CONFIG, (uint8_t [1]){ _BV(0) }, 1);
|
|
|
|
while(!(rssi_reg & _BV(1))) {
|
|
_rfm_read(rfm, REG_RSSI_CONFIG, &rssi_reg, 1);
|
|
}
|
|
|
|
_rfm_read(rfm, REG_RSSI_VALUE, &rssi_value, 1);
|
|
|
|
return rssi_value;
|
|
}
|
|
|
|
|