This commit is contained in:
2026-04-08 18:08:13 +02:00
commit 9504255571
15 changed files with 1754 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]
+19
View File
@@ -0,0 +1,19 @@
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}
+78
View File
@@ -0,0 +1,78 @@
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Directory metadata
.directory
# Temporary files
*~
*.swp
*.swo
*.bak
*.tmp
# Log files
*.log
# Build artifacts and directories
**/build/
build/
*.o
*.a
*.out
*.exe # For any host-side utilities compiled on Windows
# ESP-IDF specific build outputs
*.bin
*.elf
*.map
flasher_args.json # Generated in build directory
sdkconfig.old
sdkconfig
# ESP-IDF dependencies
# For older versions or manual component management
/components/.idf/
**/components/.idf/
# For modern ESP-IDF component manager
managed_components/
# If ESP-IDF tools are installed/referenced locally to the project
.espressif/
# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
# Python environment files
*.pyc
*.pyo
*.pyd
__pycache__/
*.egg-info/
dist/
# Virtual environment folders
venv/
.venv/
env/
# Language Servers
.clangd/
.ccls-cache/
compile_commands.json
# Windows specific
Thumbs.db
ehthumbs.db
Desktop.ini
# User-specific configuration files
*.user
*.workspace # General workspace files, can be from various tools
*.suo # Visual Studio Solution User Options
*.sln.docstates # Visual Studio
+19
View File
@@ -0,0 +1,19 @@
{
"configurations": [
{
"name": "ESP-IDF",
"compilerPath": "${default}",
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
"includePath": [
"${workspaceFolder}/**"
],
"browse": {
"path": [
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}
+10
View File
@@ -0,0 +1,10 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
}
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"C_Cpp.intelliSenseEngine": "default",
"idf.currentSetup": "/home/bruno/.espressif/v6.0/esp-idf",
"idf.flashType": "UART",
"idf.port": "/dev/ttyUSB0"
}
+8
View File
@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(rfkamera)
+7
View File
@@ -0,0 +1,7 @@
set(component_srcs "mirf.c")
idf_component_register(
SRCS "${component_srcs}"
PRIV_REQUIRES driver esp_driver_spi esp_driver_gpio
INCLUDE_DIRS "."
)
+130
View File
@@ -0,0 +1,130 @@
menu "nRF24L01 Configuration"
config GPIO_RANGE_MAX
int
default 33 if IDF_TARGET_ESP32
default 46 if IDF_TARGET_ESP32S2
default 48 if IDF_TARGET_ESP32S3
default 18 if IDF_TARGET_ESP32C2
default 19 if IDF_TARGET_ESP32C3
default 30 if IDF_TARGET_ESP32C6
config RADIO_CHANNEL
int "Channel number"
range 0 127
default 90
help
Channel number.
config MISO_GPIO
int "MISO GPIO number"
range 0 GPIO_RANGE_MAX
default 19 if IDF_TARGET_ESP32
default 37 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 4 # C3 and others
help
GPIO number (IOxx) to SPI MISO.
Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to MISO.
On the ESP32, GPIOs 35-39 are input-only so cannot be used as outputs.
On the ESP32-S2, GPIO 46 is input-only so cannot be used as outputs.
config SCLK_GPIO
int "SCLK GPIO number"
range 0 GPIO_RANGE_MAX
default 18 if IDF_TARGET_ESP32
default 36 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 3 # C3 and others
help
GPIO number (IOxx) to SPI SCLK.
Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to SCLK.
On the ESP32, GPIOs 35-39 are input-only so cannot be used as outputs.
On the ESP32-S2, GPIO 46 is input-only so cannot be used as outputs.
config MOSI_GPIO
int "MOSI GPIO number"
range 0 GPIO_RANGE_MAX
default 23 if IDF_TARGET_ESP32
default 35 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 2 # C3 and others
help
GPIO number (IOxx) to SPI MOSI.
Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to MOSI.
On the ESP32, GPIOs 35-39 are input-only so cannot be used as outputs.
On the ESP32-S2, GPIO 46 is input-only so cannot be used as outputs.
config CE_GPIO
int "CE GPIO number"
range 0 GPIO_RANGE_MAX
default 16 if IDF_TARGET_ESP32
default 34 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 1 # C3 and others
help
GPIO number (IOxx) to CE.
Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to CE.
On the ESP32, GPIOs 35-39 are input-only so cannot be used as outputs.
On the ESP32-S2, GPIO 46 is input-only so cannot be used as outputs.
config CSN_GPIO
int "CSN GPIO number"
range 0 GPIO_RANGE_MAX
default 17 if IDF_TARGET_ESP32
default 33 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 0 # C3 and others
help
GPIO number (IOxx) to CSN.
Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to CSN.
On the ESP32, GPIOs 35-39 are input-only so cannot be used as outputs.
On the ESP32-S2, GPIO 46 is input-only so cannot be used as outputs.
choice SPI_HOST
prompt "SPI peripheral that controls this bus"
default SPI2_HOST
help
Select SPI peripheral that controls this bus.
config SPI2_HOST
bool "SPI2_HOST"
help
Use SPI2_HOST. This is also called HSPI_HOST.
config SPI3_HOST
depends on IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
bool "SPI3_HOST"
help
USE SPI3_HOST. This is also called VSPI_HOST
endchoice
config ADVANCED
bool "Enable Advanced Setting"
default false
help
Enable Advanced Setting.
choice RF_RATIO
depends on ADVANCED
prompt "RF Data Ratio"
default RF_RATIO_2M
help
Select RF Data Ratio.
config RF_RATIO_2M
bool "2Mbps"
help
RF Data Ratio is 2Mbps.
config RF_RATIO_1M
bool "1Mbps"
help
RF Data Ratio is 1Mbps.
config RF_RATIO_250K
bool "250Kbps"
help
RF Data Ratio is 250Kbps.
endchoice
config RETRANSMIT_DELAY
depends on ADVANCED
int "Auto Retransmit Delay"
range 0 15
default 0
help
Set Auto Retransmit Delay.
Delay = value * 250us.
endmenu
+677
View File
@@ -0,0 +1,677 @@
#include <stdint.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include <driver/gpio.h>
#include <driver/spi_master.h>
#include "mirf.h"
#define TAG "NRF24"
#define HOST_ID SPI2_HOST
// static const int SPI_Frequency = 4000000; // Stable even with a long jumper
// cable static const int SPI_Frequency = 6000000; static const int
// SPI_Frequency = 8000000; // Requires a short jumper cable
static const int SPI_Frequency =
10000000; // Unstable even with a short jumper cable
// const char rf24_datarates[][8] = {"1Mbps", "2Mbps", "250Kbps"};
char rf24_datarates[][8] = {"1Mbps", "2Mbps", "250Kbps"};
const char rf24_crclength[][10] = {"Disabled", "8 bits", "16 bits"};
// const char rf24_pa_dbm[][8] = {"PA_MIN", "PA_LOW", "PA_HIGH", "PA_MAX"};
char rf24_pa_dbm[][8] = {"PA_MIN", "PA_LOW", "PA_HIGH", "PA_MAX"};
void Nrf24_init(NRF24_t *dev) {
esp_err_t ret;
ESP_LOGI(TAG, "CONFIG_MISO_GPIO=%d", CONFIG_MISO_GPIO);
ESP_LOGI(TAG, "CONFIG_MOSI_GPIO=%d", CONFIG_MOSI_GPIO);
ESP_LOGI(TAG, "CONFIG_SCLK_GPIO=%d", CONFIG_SCLK_GPIO);
ESP_LOGI(TAG, "CONFIG_CE_GPIO=%d", CONFIG_CE_GPIO);
ESP_LOGI(TAG, "CONFIG_CSN_GPIO=%d", CONFIG_CSN_GPIO);
// gpio_pad_select_gpio(CONFIG_CE_GPIO);
gpio_reset_pin(CONFIG_CE_GPIO);
gpio_set_direction(CONFIG_CE_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(CONFIG_CE_GPIO, 0);
// gpio_pad_select_gpio(CONFIG_CSN_GPIO);
gpio_reset_pin(CONFIG_CSN_GPIO);
gpio_set_direction(CONFIG_CSN_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(CONFIG_CSN_GPIO, 1);
spi_bus_config_t spi_bus_config = {.sclk_io_num = CONFIG_SCLK_GPIO,
.mosi_io_num = CONFIG_MOSI_GPIO,
.miso_io_num = CONFIG_MISO_GPIO,
.quadwp_io_num = -1,
.quadhd_io_num = -1};
ret = spi_bus_initialize(HOST_ID, &spi_bus_config, SPI_DMA_CH_AUTO);
ESP_LOGI(TAG, "spi_bus_initialize=%d", ret);
assert(ret == ESP_OK);
spi_device_interface_config_t devcfg;
memset(&devcfg, 0, sizeof(spi_device_interface_config_t));
devcfg.clock_speed_hz = SPI_Frequency;
// It does not work with hardware CS control.
// devcfg.spics_io_num = csn_pin;
// It does work with software CS control.
devcfg.spics_io_num = -1;
devcfg.queue_size = 7;
devcfg.mode = 0;
devcfg.flags = SPI_DEVICE_NO_DUMMY;
spi_device_handle_t handle;
ret = spi_bus_add_device(HOST_ID, &devcfg, &handle);
ESP_LOGI(TAG, "spi_bus_add_device=%d", ret);
assert(ret == ESP_OK);
dev->cePin = CONFIG_CE_GPIO;
dev->csnPin = CONFIG_CSN_GPIO;
dev->channel = 1;
dev->payload = 16;
dev->_SPIHandle = handle;
spi_csnLow(dev); // Pull down chip select
uint8_t bTmp[2] = {ACTIVATE, 0x73};
spi_write_byte(dev, bTmp, 2); // Write cmd to flush tx fifo
spi_csnHi(dev); // Pull up chip select
Nrf24_enableAckPayloadFeature(dev);
}
void Nrf24_deinit(NRF24_t *dev) {
memset(dev, 0, sizeof(NRF24_t));
spi_bus_free(HOST_ID);
}
bool spi_write_byte(NRF24_t *dev, uint8_t *Dataout, size_t DataLength) {
spi_transaction_t SPITransaction;
if (DataLength > 0) {
memset(&SPITransaction, 0, sizeof(spi_transaction_t));
SPITransaction.length = DataLength * 8;
SPITransaction.tx_buffer = Dataout;
SPITransaction.rx_buffer = NULL;
spi_device_transmit(dev->_SPIHandle, &SPITransaction);
}
return true;
}
bool spi_read_byte(NRF24_t *dev, uint8_t *Datain, uint8_t *Dataout,
size_t DataLength) {
spi_transaction_t SPITransaction;
if (DataLength > 0) {
memset(&SPITransaction, 0, sizeof(spi_transaction_t));
SPITransaction.length = DataLength * 8;
SPITransaction.tx_buffer = Dataout;
SPITransaction.rx_buffer = Datain;
spi_device_transmit(dev->_SPIHandle, &SPITransaction);
}
return true;
}
uint8_t spi_transfer(NRF24_t *dev, uint8_t address) {
uint8_t datain[1];
uint8_t dataout[1];
dataout[0] = address;
// spi_write_byte(dev, dataout, 1 );
spi_read_byte(dev, datain, dataout, 1);
return datain[0];
}
void spi_csnHi(NRF24_t *dev) { gpio_set_level(dev->csnPin, 1); }
void spi_csnLow(NRF24_t *dev) { gpio_set_level(dev->csnPin, 0); }
// Sets the important registers in the MiRF module and powers the module
// in receiving mode
// NB: channel and payload must be set now.
void Nrf24_config(NRF24_t *dev, uint8_t channel, uint8_t payload) {
dev->channel = channel;
dev->payload = payload;
Nrf24_configRegister(dev, RF_CH, dev->channel); // Set RF channel
Nrf24_configRegister(dev, RX_PW_P0,
dev->payload); // Set length of incoming payload
Nrf24_configRegister(dev, RX_PW_P1, dev->payload);
Nrf24_configRegister(dev, SETUP_AW, 0x01);
Nrf24_powerUpRx(dev); // Start receiver
Nrf24_flushRx(dev);
}
// Sets the receiving device address
// void Nrf24_setRADDR(NRF24_t * dev, uint8_t * adr)
esp_err_t Nrf24_setRADDR(NRF24_t *dev, uint8_t *adr) {
esp_err_t ret = ESP_OK;
Nrf24_writeRegister(dev, RX_ADDR_P1, adr, mirf_ADDR_LEN);
uint8_t buffer[5];
Nrf24_readRegister(dev, RX_ADDR_P1, buffer, sizeof(buffer));
for (int i = 0; i < 5; i++) {
ESP_LOGD(TAG, "adr[%d]=0x%x buffer[%d]=0x%x", i, adr[i], i, buffer[i]);
if (adr[i] != buffer[i])
ret = ESP_FAIL;
}
return ret;
}
// Sets the transmitting device address
// void Nrf24_setTADDR(NRF24_t * dev, uint8_t * adr)
esp_err_t Nrf24_setTADDR(NRF24_t *dev, uint8_t *adr) {
esp_err_t ret = ESP_OK;
Nrf24_writeRegister(dev, RX_ADDR_P0, adr,
mirf_ADDR_LEN); // RX_ADDR_P0 must be set to the sending
// addr for auto ack to work.
Nrf24_writeRegister(dev, TX_ADDR, adr, mirf_ADDR_LEN);
uint8_t buffer[3];
Nrf24_readRegister(dev, RX_ADDR_P0, buffer, sizeof(buffer));
for (int i = 0; i < 3; i++) {
ESP_LOGD(TAG, "adr[%d]=0x%x buffer[%d]=0x%x", i, adr[i], i, buffer[i]);
if (adr[i] != buffer[i])
ret = ESP_FAIL;
}
return ret;
}
// Add the receiving device address
void Nrf24_addRADDR(NRF24_t *dev, uint8_t pipe, uint8_t adr) {
uint8_t value;
Nrf24_readRegister(dev, EN_RXADDR, &value, 1);
if (pipe == 2) {
Nrf24_configRegister(dev, RX_PW_P2, dev->payload);
Nrf24_configRegister(dev, RX_ADDR_P2, adr);
value = value | 0x04;
Nrf24_configRegister(dev, EN_RXADDR, value);
} else if (pipe == 3) {
Nrf24_configRegister(dev, RX_PW_P3, dev->payload);
Nrf24_configRegister(dev, RX_ADDR_P3, adr);
value = value | 0x08;
Nrf24_configRegister(dev, EN_RXADDR, value);
} else if (pipe == 4) {
Nrf24_configRegister(dev, RX_PW_P4, dev->payload);
Nrf24_configRegister(dev, RX_ADDR_P4, adr);
value = value | 0x10;
Nrf24_configRegister(dev, EN_RXADDR, value);
} else if (pipe == 5) {
Nrf24_configRegister(dev, RX_PW_P5, dev->payload);
Nrf24_configRegister(dev, RX_ADDR_P5, adr);
value = value | 0x20;
Nrf24_configRegister(dev, EN_RXADDR, value);
}
}
// Checks if data is available for reading
extern bool Nrf24_dataReady(NRF24_t *dev) {
// See note in getData() function - just checking RX_DR isn't good enough
uint8_t status = Nrf24_getStatus(dev);
// printf("Nrf24_dataReady status=0x%x\n", status);
if (status & (1 << RX_DR)) {
// Save status
dev->status = status;
return 1;
}
// We can short circuit on RX_DR, but if it's not set, we still need
// to check the FIFO for any pending packets
// return !Nrf24_rxFifoEmpty(dev);
return 0;
}
// Get pipe number for reading
uint8_t Nrf24_getDataPipe(NRF24_t *dev) {
// uint8_t status = Nrf24_getStatus(dev);
// printf("dev->status=0x%x\n",dev->status);
return ((dev->status & 0x0E) >> 1);
}
extern bool Nrf24_rxFifoEmpty(NRF24_t *dev) {
uint8_t fifoStatus;
Nrf24_readRegister(dev, FIFO_STATUS, &fifoStatus, sizeof(fifoStatus));
return (fifoStatus & (1 << RX_EMPTY));
}
// Reads payload bytes into data array
extern void Nrf24_getData(NRF24_t *dev, uint8_t *data) {
spi_csnLow(dev); // Pull down chip select
spi_transfer(dev, R_RX_PAYLOAD); // Send cmd to read rx payload
spi_read_byte(dev, data, data, dev->payload); // Read payload
spi_csnHi(dev); // Pull up chip select
// NVI: per product spec, p 67, note c:
// "The RX_DR IRQ is asserted by a new packet arrival event. The procedure
// for handling this interrupt should be: 1) read payload through SPI,
// 2) clear RX_DR IRQ, 3) read FIFO_STATUS to check if there are more
// payloads available in RX FIFO, 4) if there are more data in RX FIFO,
// repeat from step 1)."
// So if we're going to clear RX_DR here, we need to check the RX FIFO
// in the dataReady() function
Nrf24_configRegister(dev, STATUS, (1 << RX_DR)); // Reset status register
}
// Clocks only one byte into the given MiRF register
void Nrf24_configRegister(NRF24_t *dev, uint8_t reg, uint8_t value) {
spi_csnLow(dev);
spi_transfer(dev, W_REGISTER | (REGISTER_MASK & reg));
spi_transfer(dev, value);
spi_csnHi(dev);
}
// Reads an array of bytes from the given start position in the MiRF registers
void Nrf24_readRegister(NRF24_t *dev, uint8_t reg, uint8_t *value,
uint8_t len) {
spi_csnLow(dev);
spi_transfer(dev, R_REGISTER | (REGISTER_MASK & reg));
spi_read_byte(dev, value, value, len);
spi_csnHi(dev);
}
// Writes an array of bytes into inte the MiRF registers
void Nrf24_writeRegister(NRF24_t *dev, uint8_t reg, uint8_t *value,
uint8_t len) {
spi_csnLow(dev);
spi_transfer(dev, W_REGISTER | (REGISTER_MASK & reg));
spi_write_byte(dev, value, len);
spi_csnHi(dev);
}
// Sends a data package to the default address. Be sure to send the correct
// amount of bytes as configured as payload on the receiver.
void Nrf24_send(NRF24_t *dev, uint8_t *value) {
uint8_t status;
status = Nrf24_getStatus(dev);
while (dev->PTX) // Wait until last paket is send
{
status = Nrf24_getStatus(dev);
if ((status & ((1 << TX_DS) | (1 << MAX_RT)))) {
dev->PTX = 0;
break;
}
}
Nrf24_ceLow(dev);
Nrf24_powerUpTx(dev); // Set to transmitter mode , Power up
spi_csnLow(dev); // Pull down chip select
spi_transfer(dev, FLUSH_TX); // Write cmd to flush tx fifo
spi_csnHi(dev); // Pull up chip select
spi_csnLow(dev); // Pull down chip select
spi_transfer(dev, W_TX_PAYLOAD); // Write cmd to write payload
spi_write_byte(dev, value, dev->payload); // Write payload
spi_csnHi(dev); // Pull up chip select
Nrf24_ceHi(dev); // Start transmission
}
void Nrf24_send_in_ack(NRF24_t *dev, uint8_t *value) {
uint8_t status;
status = Nrf24_getStatus(dev);
while (dev->PTX) // Wait until last paket is send
{
status = Nrf24_getStatus(dev);
if ((status & ((1 << TX_DS) | (1 << MAX_RT)))) {
dev->PTX = 0;
break;
}
}
spi_csnLow(dev); // Pull down chip select
spi_transfer(dev, W_ACK_PAYLOAD); // Write cmd to write payload
spi_write_byte(dev, value, dev->payload); // Write payload
spi_csnHi(dev); // Pull up chip select
}
// Sends payload without expecting an ACK from the receiver effectively turning
// off retransmission of failed payloads. See Nrf24l01 "PTX Operation" flowchart
// in the datasheet. NOTE: Make sure to call Nrf24_enableNoAckFeature() before
// calling this function. Is useful when achieving maximum throughput without
// caring much about losses.
void Nrf24_sendNoAck(NRF24_t *dev, uint8_t *value) {
uint8_t status;
status = Nrf24_getStatus(dev);
while (dev->PTX) // Wait until last paket is sent
{
status = Nrf24_getStatus(dev);
if ((status & ((1 << TX_DS) | (1 << MAX_RT)))) {
dev->PTX = 0;
break;
}
}
Nrf24_ceLow(dev);
Nrf24_powerUpTx(dev); // Set to transmitter mode , Power up
spi_csnLow(dev); // Pull down chip select
spi_transfer(dev, FLUSH_TX); // Write cmd to flush tx fifo
spi_csnHi(dev); // Pull up chip select
spi_csnLow(dev); // Pull down chip select
spi_transfer(dev, W_TX_PAYLOAD_NO_ACK); // Write cmd to write payload
spi_write_byte(dev, value, dev->payload); // Write payload
spi_csnHi(dev); // Pull up chip select
Nrf24_ceHi(dev); // Start transmission
}
// Test if chip is still sending.
// When sending has finished return chip to listening.
bool Nrf24_isSending(NRF24_t *dev) {
uint8_t status;
if (dev->PTX) {
status = Nrf24_getStatus(dev);
if ((status &
((1 << TX_DS) | (1 << MAX_RT)))) { // if sending successful (TX_DS) or
// max retries exceded (MAX_RT).
Nrf24_powerUpRx(dev);
return false;
}
return true;
}
return false;
}
// Test if Sending has finished or retry is over.
// When sending has finished return trur.
// When reach maximum number of TX retries return false.
bool Nrf24_isSend(NRF24_t *dev, int timeout) {
uint8_t status;
TickType_t startTick = xTaskGetTickCount();
if (dev->PTX) {
while (1) {
status = Nrf24_getStatus(dev);
/*
if sending successful (TX_DS) or max retries exceded (MAX_RT).
*/
if (status & (1 << TX_DS)) { // Data Sent TX FIFO interrup
Nrf24_powerUpRx(dev);
return true;
}
if (status & (1 << MAX_RT)) { // Maximum number of TX retries interrupt
// ESP_LOGW(TAG, "Maximum number of TX retries interrupt");
Nrf24_powerUpRx(dev);
return false;
}
// I believe either TX_DS or MAX_RT will always be notified.
// Therefore, it is unusual for neither to be notified for a period of
// time. I don't know exactly how to respond.
TickType_t diffTick = xTaskGetTickCount() - startTick;
if ((diffTick * portTICK_PERIOD_MS) > timeout) {
ESP_LOGE(TAG, "Status register timeout. status=0x%x", status);
return false;
}
vTaskDelay(1);
}
}
return false;
}
// Enables the W_TX_PAYLOAD command
// NOTE: Make sure to call this before using Nrf24_sendNoAck().
// Can be called anytime after the call to Nrf24_init() and preferably only
// once.
void Nrf24_enableNoAckFeature(NRF24_t *dev) {
uint8_t value;
Nrf24_readRegister(dev, FEATURE, &value, 1);
value = value | 1;
Nrf24_configRegister(dev, FEATURE, value);
}
void Nrf24_enableAckPayloadFeature(NRF24_t *dev) {
uint8_t value;
Nrf24_readRegister(dev, FEATURE, &value, 1);
value = value | 2;
Nrf24_configRegister(dev, FEATURE, value);
}
uint8_t Nrf24_getStatus(NRF24_t *dev) {
uint8_t rv;
Nrf24_readRegister(dev, STATUS, &rv, 1);
return rv;
}
void Nrf24_powerUpRx(NRF24_t *dev) {
dev->PTX = 0;
Nrf24_ceLow(dev);
Nrf24_configRegister(
dev, CONFIG,
mirf_CONFIG | ((1 << PWR_UP) | (1 << PRIM_RX))); // set device as RX mode
Nrf24_ceHi(dev);
Nrf24_configRegister(
dev, STATUS,
(1 << TX_DS) |
(1 << MAX_RT)); // Clear seeded interrupt and max tx number interrupt
}
void Nrf24_flushRx(NRF24_t *dev) {
spi_csnLow(dev);
spi_transfer(dev, FLUSH_RX);
spi_csnHi(dev);
}
void Nrf24_powerUpTx(NRF24_t *dev) {
dev->PTX = 1;
Nrf24_configRegister(
dev, CONFIG,
mirf_CONFIG | ((1 << PWR_UP) | (0 << PRIM_RX))); // set device as TX mode
Nrf24_configRegister(
dev, STATUS,
(1 << TX_DS) |
(1 << MAX_RT)); // Clear seeded interrupt and max tx number interrupt
}
void Nrf24_ceHi(NRF24_t *dev) { gpio_set_level(dev->cePin, 1); }
void Nrf24_ceLow(NRF24_t *dev) { gpio_set_level(dev->cePin, 0); }
void Nrf24_powerDown(NRF24_t *dev) {
Nrf24_ceLow(dev);
Nrf24_configRegister(dev, CONFIG, mirf_CONFIG);
}
// Set tx power : 0=-18dBm,1=-12dBm,2=-6dBm,3=0dBm
void Nrf24_SetOutputRF_PWR(NRF24_t *dev, uint8_t val) {
if (val > 3)
return;
uint8_t value;
Nrf24_readRegister(dev, RF_SETUP, &value, 1);
value = value & 0xF9;
value = value | (val << RF_PWR);
// Nrf24_configRegister(dev, RF_SETUP, (val<< RF_PWR) );
Nrf24_configRegister(dev, RF_SETUP, value);
}
// Select between the high speed data rates:0=1Mbps, 1=2Mbps, 2=250Kbps
void Nrf24_SetSpeedDataRates(NRF24_t *dev, uint8_t val) {
if (val > 2)
return;
uint8_t value;
Nrf24_readRegister(dev, RF_SETUP, &value, 1);
if (val == 2) {
value = value | 0x20;
value = value & 0xF7;
// Nrf24_configRegister(dev, RF_SETUP, (1 << RF_DR_LOW) );
Nrf24_configRegister(dev, RF_SETUP, value);
} else {
value = value & 0xD7;
value = value | (val << RF_DR_HIGH);
// Nrf24_configRegister(dev, RF_SETUP, (val << RF_DR_HIGH) );
Nrf24_configRegister(dev, RF_SETUP, value);
}
}
// Set Auto Retransmit Delay 0=250us, 1=500us, ... 15=4000us
void Nrf24_setRetransmitDelay(NRF24_t *dev, uint8_t val) {
uint8_t value;
Nrf24_readRegister(dev, SETUP_RETR, &value, 1);
value = value & 0x0F;
value = value | (val << ARD);
Nrf24_configRegister(dev, SETUP_RETR, value);
}
void Nrf24_setRetransmitCount(NRF24_t *dev, uint8_t val) {
uint8_t value;
Nrf24_readRegister(dev, SETUP_RETR, &value, 1);
value = value & 0xF0;
value = value | val;
Nrf24_configRegister(dev, SETUP_RETR, value);
}
void Nrf24_printDetails(NRF24_t *dev) {
printf("================ SPI Configuration ================\n");
printf("CSN Pin \t = GPIO%d\n", dev->csnPin);
printf("CE Pin \t = GPIO%d\n", dev->cePin);
printf("Clock Speed\t = %d\n", SPI_Frequency);
printf("================ NRF Configuration ================\n");
Nrf24_print_status(Nrf24_getStatus(dev));
Nrf24_print_byte_register(dev, "CONFIG\t", CONFIG, 1);
Nrf24_print_byte_register(dev, "EN_AA\t", EN_AA, 1);
Nrf24_print_byte_register(dev, "EN_RXADDR", EN_RXADDR, 1);
Nrf24_print_byte_register(dev, "SETUP_AW", SETUP_AW, 1);
Nrf24_print_byte_register(dev, "SETUP_RETR", SETUP_RETR, 1);
Nrf24_print_byte_register(dev, "RF_CH\t", RF_CH, 1);
Nrf24_print_byte_register(dev, "RF_SETUP", RF_SETUP, 1);
Nrf24_print_byte_register(dev, "STATUS", STATUS, 1);
Nrf24_print_byte_register(dev, "OBSERVE_TX", OBSERVE_TX, 1);
Nrf24_print_byte_register(dev, "CD", CD, 1);
Nrf24_print_address_register(dev, "RX_ADDR_P0-1", RX_ADDR_P0, 2);
Nrf24_print_byte_register(dev, "RX_ADDR_P2-5", RX_ADDR_P2, 4);
Nrf24_print_address_register(dev, "TX_ADDR\t", TX_ADDR, 1);
Nrf24_print_byte_register(dev, "RX_PW_P0-6", RX_PW_P0, 6);
Nrf24_print_byte_register(dev, "FIFO_STATUS", FIFO_STATUS, 1);
Nrf24_print_byte_register(dev, "DYNPD/FEATURE", DYNPD, 2);
// printf("getDataRate()=%d\n",Nrf24_getDataRate(dev));
printf("Data Rate\t = %s\n", rf24_datarates[Nrf24_getDataRate(dev)]);
printf("CRC Length\t = %s\n", rf24_crclength[Nrf24_getCRCLength(dev)]);
printf("PA Power\t = %s\n", rf24_pa_dbm[Nrf24_getPALevel(dev)]);
uint8_t retransmit = Nrf24_getRetransmitDelay(dev);
int16_t delay = (retransmit + 1) * 250;
printf("Retransmit\t = %d us\n", delay);
}
#define _BV(x) (1 << (x))
void Nrf24_print_status(uint8_t status) {
printf("STATUS\t\t = 0x%02x RX_DR=%x TX_DS=%x MAX_RT=%x RX_P_NO=%x "
"TX_FULL=%x\r\n",
status, (status & _BV(RX_DR)) ? 1 : 0, (status & _BV(TX_DS)) ? 1 : 0,
(status & _BV(MAX_RT)) ? 1 : 0, ((status >> RX_P_NO) & 0x07),
(status & _BV(TX_FULL)) ? 1 : 0);
}
void Nrf24_print_address_register(NRF24_t *dev, const char *name, uint8_t reg,
uint8_t qty) {
printf("%s\t =", name);
while (qty--) {
// uint8_t buffer[addr_width];
uint8_t buffer[5];
Nrf24_readRegister(dev, reg++, buffer, sizeof(buffer));
printf(" 0x");
#if 0
uint8_t* bufptr = buffer + sizeof buffer;
while (--bufptr >= buffer) {
printf("%02x", *bufptr);
}
#endif
for (int i = 0; i < 5; i++) {
printf("%02x", buffer[i]);
}
}
printf("\r\n");
}
void Nrf24_print_byte_register(NRF24_t *dev, const char *name, uint8_t reg,
uint8_t qty) {
printf("%s\t =", name);
while (qty--) {
uint8_t buffer[1];
Nrf24_readRegister(dev, reg++, buffer, 1);
printf(" 0x%02x", buffer[0]);
}
printf("\r\n");
}
uint8_t Nrf24_getDataRate(NRF24_t *dev) {
rf24_datarate_e result;
uint8_t dr;
Nrf24_readRegister(dev, RF_SETUP, &dr, sizeof(dr));
// printf("RF_SETUP=%x\n",dr);
dr = dr & (_BV(RF_DR_LOW) | _BV(RF_DR_HIGH));
// switch uses RAM (evil!)
// Order matters in our case below
if (dr == _BV(RF_DR_LOW)) {
// '10' = 250KBPS
result = RF24_250KBPS;
} else if (dr == _BV(RF_DR_HIGH)) {
// '01' = 2MBPS
result = RF24_2MBPS;
} else {
// '00' = 1MBPS
result = RF24_1MBPS;
}
return result;
}
char *Nrf24_getDataRateString(NRF24_t *dev) {
return rf24_datarates[Nrf24_getDataRate(dev)];
}
uint8_t Nrf24_getCRCLength(NRF24_t *dev) {
rf24_crclength_e result = RF24_CRC_DISABLED;
uint8_t config;
Nrf24_readRegister(dev, CONFIG, &config, sizeof(config));
// printf("CONFIG=%x\n",config);
config = config & (_BV(CRCO) | _BV(EN_CRC));
uint8_t AA;
Nrf24_readRegister(dev, EN_AA, &AA, sizeof(AA));
if (config & _BV(EN_CRC) || AA) {
if (config & _BV(CRCO)) {
result = RF24_CRC_16;
} else {
result = RF24_CRC_8;
}
}
return result;
}
uint8_t Nrf24_getPALevel(NRF24_t *dev) {
uint8_t level;
Nrf24_readRegister(dev, RF_SETUP, &level, sizeof(level));
// printf("RF_SETUP=%x\n",level);
level = (level & (_BV(RF_PWR_LOW) | _BV(RF_PWR_HIGH))) >> 1;
return (level);
}
char *Nrf24_getPALevelString(NRF24_t *dev) {
return rf24_pa_dbm[Nrf24_getPALevel(dev)];
}
uint8_t Nrf24_getRetransmitDelay(NRF24_t *dev) {
uint8_t value;
Nrf24_readRegister(dev, SETUP_RETR, &value, 1);
return (value >> 4);
}
uint8_t Nrf24_getRetransmitCount(NRF24_t *dev) {
uint8_t value;
Nrf24_readRegister(dev, SETUP_RETR, &value, 1);
return (value & 0x0F);
}
uint8_t Nrf24_getChannle(NRF24_t *dev) { return dev->channel; }
uint8_t Nrf24_getPayload(NRF24_t *dev) { return dev->payload; }
+227
View File
@@ -0,0 +1,227 @@
#ifndef MAIN_MIRF_H_
#define MAIN_MIRF_H_
#include "driver/spi_master.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint8_t PTX; // In sending mode.
uint8_t cePin; // CE Pin controls RX / TX, default 8.
uint8_t csnPin; // CSN Pin Chip Select Not, default 7.
uint8_t channel; // Channel 0 - 127 or 0 - 84 in the US.
uint8_t payload; // Payload width in bytes default 16 max 32.
spi_device_handle_t _SPIHandle;
uint8_t status; // Receive status
} NRF24_t;
/* Memory Map */
#define CONFIG 0x00
#define EN_AA 0x01
#define EN_RXADDR 0x02
#define SETUP_AW 0x03
#define SETUP_RETR 0x04
#define RF_CH 0x05
#define RF_SETUP 0x06
#define STATUS 0x07
#define OBSERVE_TX 0x08
#define CD 0x09
#define RX_ADDR_P0 0x0A
#define RX_ADDR_P1 0x0B
#define RX_ADDR_P2 0x0C
#define RX_ADDR_P3 0x0D
#define RX_ADDR_P4 0x0E
#define RX_ADDR_P5 0x0F
#define TX_ADDR 0x10
#define RX_PW_P0 0x11
#define RX_PW_P1 0x12
#define RX_PW_P2 0x13
#define RX_PW_P3 0x14
#define RX_PW_P4 0x15
#define RX_PW_P5 0x16
#define FIFO_STATUS 0x17
#define DYNPD 0x1C
#define FEATURE 0x1D
/* Bit Mnemonics */
#define MASK_RX_DR 6
#define MASK_TX_DS 5
#define MASK_MAX_RT 4
#define EN_CRC 3
#define CRCO 2
#define PWR_UP 1
#define PRIM_RX 0
#define ENAA_P5 5
#define ENAA_P4 4
#define ENAA_P3 3
#define ENAA_P2 2
#define ENAA_P1 1
#define ENAA_P0 0
#define ERX_P5 5
#define ERX_P4 4
#define ERX_P3 3
#define ERX_P2 2
#define ERX_P1 1
#define ERX_P0 0
#define AW 0
#define ARD 4
#define ARC 0
#define RF_DR_LOW 5
#define PLL_LOCK 4
#define RF_DR_HIGH 3
#define RF_PWR 1
#define LNA_HCURR 0
#define RX_DR 6
#define TX_DS 5
#define MAX_RT 4
#define RX_P_NO 1
#define TX_FULL 0
#define PLOS_CNT 4
#define ARC_CNT 0
#define TX_REUSE 6
#define FIFO_FULL 5
#define TX_EMPTY 4
#define RX_FULL 1
#define RX_EMPTY 0
/* Instruction Mnemonics */
#define R_REGISTER 0x00
#define W_REGISTER 0x20
#define REGISTER_MASK 0x1F
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3
#define ACTIVATE 0x50
#define R_RX_PL_WID 0x60
#define W_ACK_PAYLOAD 0xA8
#define NOP 0xFF
/* Non-P omissions */
#define LNA_HCURR 0
/* P model memory Map */
#define RPD 0x09
#define W_TX_PAYLOAD_NO_ACK 0xB0
/* P model bit Mnemonics */
#define RF_DR_LOW 5
#define RF_DR_HIGH 3
#define RF_PWR_LOW 1
#define RF_PWR_HIGH 2
/* Device addrees length:3~5 bytes */
#define mirf_ADDR_LEN 5
/*
enable interrupt caused by RX_DR.
enable interrupt caused by TX_DS.
enable interrupt caused by MAX_RT.
enable CRC and CRC data len=1
mirf_CONFIG = 00001000B
*/
// #define mirf_CONFIG ((1<<EN_CRC) | (0<<CRCO) )
/*
enable interrupt caused by RX_DR.
disable interrupt caused by TX_DS.
enable interrupt caused by MAX_RT.
enable CRC and CRC data len=2
mirf_CONFIG == 00101000B
*/
#define mirf_CONFIG ((1 << MASK_TX_DS) | (1 << EN_CRC) | (1 << CRCO))
/**
* Power Amplifier level.
*
* For use with setPALevel()
*/
typedef enum {
RF24_PA_MIN = 0,
RF24_PA_LOW,
RF24_PA_HIGH,
RF24_PA_MAX,
RF24_PA_ERROR
} rf24_pa_dbm_e;
/**
* Data rate. How fast data moves through the air.
*
* For use with setDataRate()
*/
typedef enum { RF24_1MBPS = 0, RF24_2MBPS, RF24_250KBPS } rf24_datarate_e;
/**
* CRC Length. How big (if any) of a CRC is included.
*
* For use with setCRCLength()
*/
typedef enum {
RF24_CRC_DISABLED = 0,
RF24_CRC_8,
RF24_CRC_16
} rf24_crclength_e;
void Nrf24_init(NRF24_t *dev);
void Nrf24_deinit(NRF24_t *dev);
bool spi_write_byte(NRF24_t *dev, uint8_t *Dataout, size_t DataLength);
bool spi_read_byte(NRF24_t *dev, uint8_t *Datain, uint8_t *Dataout,
size_t DataLength);
uint8_t spi_transfer(NRF24_t *dev, uint8_t address);
void spi_csnLow(NRF24_t *dev);
void spi_csnHi(NRF24_t *dev);
void Nrf24_config(NRF24_t *dev, uint8_t channel, uint8_t payload);
void Nrf24_send(NRF24_t *dev, uint8_t *value);
void Nrf24_send_in_ack(NRF24_t *dev, uint8_t *value);
void Nrf24_enableNoAckFeature(NRF24_t *dev);
void Nrf24_enableAckPayloadFeature(NRF24_t *dev);
void Nrf24_sendNoAck(NRF24_t *dev, uint8_t *value);
esp_err_t Nrf24_setRADDR(NRF24_t *dev, uint8_t *adr);
esp_err_t Nrf24_setTADDR(NRF24_t *dev, uint8_t *adr);
void Nrf24_addRADDR(NRF24_t *dev, uint8_t pipe, uint8_t adr);
bool Nrf24_dataReady(NRF24_t *dev);
uint8_t Nrf24_getDataPipe(NRF24_t *dev);
bool Nrf24_isSending(NRF24_t *dev);
bool Nrf24_isSend(NRF24_t *dev, int timeout);
bool Nrf24_rxFifoEmpty(NRF24_t *dev);
bool Nrf24_txFifoEmpty(NRF24_t *dev);
void Nrf24_getData(NRF24_t *dev, uint8_t *data);
uint8_t Nrf24_getStatus(NRF24_t *dev);
void Nrf24_configRegister(NRF24_t *dev, uint8_t reg, uint8_t value);
void Nrf24_readRegister(NRF24_t *dev, uint8_t reg, uint8_t *value, uint8_t len);
void Nrf24_writeRegister(NRF24_t *dev, uint8_t reg, uint8_t *value,
uint8_t len);
void Nrf24_powerUpRx(NRF24_t *dev);
void Nrf24_powerUpTx(NRF24_t *dev);
void Nrf24_powerDown(NRF24_t *dev);
void Nrf24_SetOutputRF_PWR(NRF24_t *dev, uint8_t val);
void Nrf24_SetSpeedDataRates(NRF24_t *dev, uint8_t val);
void Nrf24_setRetransmitDelay(NRF24_t *dev, uint8_t val);
void Nrf24_setRetransmitCount(NRF24_t *dev, uint8_t val);
void Nrf24_ceHi(NRF24_t *dev);
void Nrf24_ceLow(NRF24_t *dev);
void Nrf24_flushRx(NRF24_t *dev);
void Nrf24_printDetails(NRF24_t *dev);
void Nrf24_print_status(uint8_t status);
void Nrf24_print_address_register(NRF24_t *dev, const char *name, uint8_t reg,
uint8_t qty);
void Nrf24_print_byte_register(NRF24_t *dev, const char *name, uint8_t reg,
uint8_t qty);
uint8_t Nrf24_getDataRate(NRF24_t *dev);
char *Nrf24_getDataRateString(NRF24_t *dev);
uint8_t Nrf24_getCRCLength(NRF24_t *dev);
uint8_t Nrf24_getPALevel(NRF24_t *dev);
char *Nrf24_getPALevelString(NRF24_t *dev);
uint8_t Nrf24_getRetransmitDelay(NRF24_t *dev);
uint8_t Nrf24_getRetransmitCount(NRF24_t *dev);
uint8_t Nrf24_getChannle(NRF24_t *dev);
uint8_t Nrf24_getPayload(NRF24_t *dev);
#ifdef __cplusplus
}
#endif
#endif /* MAIN_MIRF_H_ */
+35
View File
@@ -0,0 +1,35 @@
dependencies:
espressif/esp32-camera:
component_hash: 2b75f6f0c987fb0e3687ccbd464cd00c163ea8cb05d2470cfb05adf6535bc488
dependencies:
- name: espressif/esp_jpeg
registry_url: https://components.espressif.com
require: public
version: ^1.3.1
- name: idf
require: private
version: '>=5.1'
source:
registry_url: https://components.espressif.com/
type: service
version: 2.1.6
espressif/esp_jpeg:
component_hash: defb83669293cbf86d0fa86b475ba5517aceed04ed70db435388c151ab37b5d7
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com
type: service
version: 1.3.1
idf:
source:
type: idf
version: 6.0.0
direct_dependencies:
- espressif/esp32-camera
- idf
manifest_hash: bb2e04f03fc22fe03978f48d3f4f74ead5a698f8aba43ac46cd3353f373a000c
target: esp32
version: 3.0.0
+4
View File
@@ -0,0 +1,4 @@
idf_component_register(SRCS "main.c"
"../components/mirf/mirf.c"
"../components/mirf/mirf.h"
INCLUDE_DIRS ".")
+17
View File
@@ -0,0 +1,17 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=4.1.0'
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true
espressif/esp32-camera: '*'
+504
View File
@@ -0,0 +1,504 @@
#include "driver/gpio.h"
#include "hal/gpio_types.h"
#include "sensor.h"
#include <stdint.h>
#include <stdio.h>
#define TAG "CAM"
#include "esp_camera.h"
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y2_GPIO_NUM 5
#define Y3_GPIO_NUM 18
#define Y4_GPIO_NUM 19
#define Y5_GPIO_NUM 21
#define Y6_GPIO_NUM 36
#define Y7_GPIO_NUM 39
#define Y8_GPIO_NUM 34
#define Y9_GPIO_NUM 35
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "esp_log.h"
#include "esp_timer.h"
#include "nvs_flash.h"
#include "string.h"
#include "../components/mirf/mirf.h"
#include <driver/uart.h>
#define FEC_INTERVAL 3
#define BUF_SIZE 20000
typedef struct {
int x, y, width, height;
} Rectangle;
#define MAX_CHUNK_DATA 28
typedef struct __attribute__((packed)) {
uint8_t header[4];
uint8_t data[28];
} RadioStruct;
typedef enum {
CMD_SET_CHANNEL = 1,
CMD_SET_RESOLUTION = 2,
CMD_RESTART_FRAME = 3
} RadioCommandType;
uint8_t abort_sending = 0;
typedef struct __attribute__((packed)) {
uint8_t cmd; // CMD_SET_CHANNEL or CMD_SET_RESOLUTION
uint16_t param1; // new channel or width
uint16_t param2; // height (only used for resolution)
uint16_t param3;
} RadioCommand;
framesize_t width_height_to_framesize(uint16_t w, uint16_t h) {
if (w <= 96 && h <= 96) return FRAMESIZE_96X96;
if (w <= 128 && h <= 128) return FRAMESIZE_128X128;
if (w <= 160 && h <= 120) return FRAMESIZE_QQVGA;
if (w <= 176 && h <= 144) return FRAMESIZE_QCIF;
if (w <= 240 && h <= 176) return FRAMESIZE_HQVGA;
if (w <= 240 && h <= 240) return FRAMESIZE_240X240;
if (w <= 320 && h <= 240) return FRAMESIZE_QVGA;
if (w <= 320 && h <= 320) return FRAMESIZE_320X320;
if (w <= 400 && h <= 296) return FRAMESIZE_CIF;
if (w <= 480 && h <= 320) return FRAMESIZE_HVGA;
if (w <= 640 && h <= 480) return FRAMESIZE_VGA;
if (w <= 800 && h <= 600) return FRAMESIZE_SVGA;
if (w <= 1024 && h <= 768) return FRAMESIZE_XGA;
if (w <= 1280 && h <= 720) return FRAMESIZE_HD;
if (w <= 1280 && h <= 1024) return FRAMESIZE_SXGA;
if (w <= 1600 && h <= 1200) return FRAMESIZE_UXGA;
return FRAMESIZE_QVGA; // fallback if unknown
}
NRF24_t dev;
static void packHeader4_15bit(const uint8_t flags, const uint16_t chunkIndex,
const uint16_t totalChunks, uint8_t header[4]) {
uint32_t bits = ((flags & 0x3) << 30) | // 2 bits flags
((chunkIndex & 0x7FFF) << 15) | // 15 bits chunkIndex
(totalChunks & 0x7FFF); // 15 bits totalChunks
header[0] = (bits >> 24) & 0xFF;
header[1] = (bits >> 16) & 0xFF;
header[2] = (bits >> 8) & 0xFF;
header[3] = bits & 0xFF;
}
static void unpackHeader4_15bit(const uint8_t header[4], uint8_t *flags,
uint16_t *chunkIndex, uint16_t *totalChunks) {
uint32_t bits =
(header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3];
*flags = (bits >> 30) & 0x3;
*chunkIndex = (bits >> 15) & 0x7FFF;
*totalChunks = bits & 0x7FFF;
}
//
// Code start
//
int vsync_count = 0;
int sync_fail_count = 0;
int reset_fail_count = 0;
int64_t vsyncDetectedUsec;
static camera_config_t camera_config = {
.pin_pwdn = PWDN_GPIO_NUM,
.pin_reset = RESET_GPIO_NUM,
.pin_xclk = XCLK_GPIO_NUM,
.pin_sccb_sda = SIOD_GPIO_NUM,
.pin_sccb_scl = SIOC_GPIO_NUM,
.pin_d7 = Y9_GPIO_NUM,
.pin_d6 = Y8_GPIO_NUM,
.pin_d5 = Y7_GPIO_NUM,
.pin_d4 = Y6_GPIO_NUM,
.pin_d3 = Y5_GPIO_NUM,
.pin_d2 = Y4_GPIO_NUM,
.pin_d1 = Y3_GPIO_NUM,
.pin_d0 = Y2_GPIO_NUM,
.pin_vsync = VSYNC_GPIO_NUM,
.pin_href = HREF_GPIO_NUM,
.pin_pclk = PCLK_GPIO_NUM,
.xclk_freq_hz = 10000000, // EXPERIMENTAL: Set to 16MHz on ESP32-S2 or
// ESP32-S3 to enable EDMA mode
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG, // YUV422,GRAYSCALE,RGB565,JPEG
.frame_size =
FRAMESIZE_UXGA, // QQVGA-UXGA, For ESP32, do not use sizes above QVGA
// when not JPEG. The performance of the ESP32-S series
// has improved a lot, but JPEG mode always gives better
// frame rates.+
//.frame_size = FRAMESIZE_128X128, // QQVGA-UXGA, For ESP32, do not use
// sizes above QVGA when not JPEG. The performance of the ESP32-S series has
// improved a lot, but JPEG mode always gives better frame rates.
.jpeg_quality = 63, // 0-63, for OV series camera sensors, lower number
// means higher quality
.fb_count = 2, // When jpeg mode is used, if fb_count more than one, the
// driver will work in continuous mode.
//.fb_location = CAMERA_FB_IN_PSRAM,
.fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_LATEST // CAMERA_GRAB_LATEST. Sets when buffers
// should be filled
};
void handle_radio_commands() {
uint8_t buf[32];
if (Nrf24_dataReady(&dev)) {
Nrf24_getData(&dev, buf);
RadioCommand *cmd = (RadioCommand*)buf;
switch (cmd->cmd) {
case CMD_SET_CHANNEL:
ESP_LOGI(TAG, "Changing RF channel to %d", cmd->param1);
Nrf24_config(&dev, cmd->param1, 32); // reconfigure channel
break;
case CMD_SET_RESOLUTION:
ESP_LOGI(TAG, "Changing resolution to %dx%d", cmd->param1, cmd->param2);
camera_config.frame_size = width_height_to_framesize(cmd->param1, cmd->param2);
uint8_t quality = cmd->param3;
if (quality > 63) {
quality = 63;
}
camera_config.jpeg_quality = quality;
esp_camera_deinit();
esp_camera_init(&camera_config);
break;
case CMD_RESTART_FRAME:
abort_sending = 1;
break;
default:
ESP_LOGW(TAG, "Unknown command %d", cmd->cmd);
}
}
}
// frames = array of RadioStruct data frames (not FEC frames)
// fecFrame = RadioStruct to write FEC frame into
// numFrames = number of frames to XOR
void createFECFrame(RadioStruct *frames, uint16_t numFrames,
RadioStruct *fecFrame) {
uint8_t flags;
uint16_t chunkIndex;
uint16_t totalChunks;
unpackHeader4_15bit(frames[0].header, &flags, &chunkIndex, &totalChunks);
flags = 0x3; // mark as FEC
// uint16_t totalChunks = numFrames; // optional
packHeader4_15bit(flags, chunkIndex, totalChunks, fecFrame->header);
memset(fecFrame->data, 0, MAX_CHUNK_DATA);
for (uint16_t i = 0; i < numFrames; i++) {
for (uint8_t j = 0; j < MAX_CHUNK_DATA; j++) {
fecFrame->data[j] ^= frames[i].data[j];
}
}
}
// dataPtr = pointer to JPEG data
// chunkIndex = index of this chunk
// chunkCount = total chunks
// flags = 0, START_OF_FRAME, END_OF_FRAME, or FEC
// outFrame = pointer to RadioStruct to fill
void createFrame(const uint8_t *dataPtr, size_t jpegLen, uint16_t chunkIndex,
uint16_t totalChunks, uint8_t flags, RadioStruct *outFrame) {
packHeader4_15bit(flags, chunkIndex, totalChunks, outFrame->header);
size_t remaining = jpegLen - chunkIndex * MAX_CHUNK_DATA;
size_t copyLen = remaining > MAX_CHUNK_DATA ? MAX_CHUNK_DATA : remaining;
memcpy(outFrame->data, dataPtr + chunkIndex * MAX_CHUNK_DATA, copyLen);
if (copyLen < MAX_CHUNK_DATA) {
memset(outFrame->data + copyLen, 0, MAX_CHUNK_DATA - copyLen); // pad last
}
/*
ESP_LOGI(TAG, "Flags: 0x%02x, chunk index: %04d, chunk count: %04d", flags,
chunkIndex, totalChunks);
uint8_t *frmPTR = (uint8_t *)outFrame;
ESP_LOGI(TAG, "Bytes: 0x%02x 0x%02x 0x%02x", frmPTR[0], frmPTR[1], frmPTR[2]);
*/
}
void sendPacket(const RadioStruct * packet) {
uint8_t packetData[32];
memcpy(packetData, packet->header, 4);
memcpy(&(packetData[4]), packet->data, 28);
Nrf24_send(&dev, (uint8_t *)&packetData);
if (Nrf24_isSend(&dev, 1000)) {
// ESP_LOGI(pcTaskGetName(NULL), "Send success.");
} else {
// ESP_LOGW(pcTaskGetName(NULL), "Send fail.");
}
handle_radio_commands();
}
void send_jpeg_nrf(const uint8_t *jpegData, size_t jpegLen) {
// calculate total chunks
uint16_t totalChunks = (jpegLen + MAX_CHUNK_DATA - 1) / MAX_CHUNK_DATA;
RadioStruct frameBuffer[FEC_INTERVAL]; // buffer to store frames for FEC
uint16_t fecCount = 0;
for (uint16_t chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
if (abort_sending == 1) {
abort_sending = 0;
break;
}
uint8_t flags = 0;
if (chunkIndex == 0) {
flags |= 0x1;
ESP_LOGI(TAG, "Flag start");
}
if (chunkIndex == totalChunks - 1) {
flags |= 0x2;
ESP_LOGI(TAG, "Flag end");
}
RadioStruct packet;
createFrame(jpegData, jpegLen, chunkIndex, totalChunks, flags, &packet);
frameBuffer[fecCount++] = packet;
sendPacket(&packet);
if (fecCount == FEC_INTERVAL) {
RadioStruct fecPacket;
createFECFrame(frameBuffer, FEC_INTERVAL, &fecPacket);
sendPacket(&fecPacket);
fecCount = 0;
}
}
// send FEC for leftover frames
if (fecCount > 0) {
RadioStruct fecPacket;
createFECFrame(frameBuffer, fecCount, &fecPacket);
sendPacket(&fecPacket);
}
}
esp_err_t camera_init() {
// initialize the camera
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera Init Failed");
return err;
}
return ESP_OK;
}
typedef struct __attribute__((packed)) {
uint32_t len;
uint16_t width;
uint16_t height;
pixformat_t format;
struct timeval timestamp;
} PreData;
uint8_t *create_frame(const uint8_t *data, size_t *outSize, PreData pre) {
const size_t totalSize = pre.len + 23;
ESP_LOGI(TAG, "DataWithFrame: %d\n", totalSize);
uint8_t *dataOut = malloc(totalSize);
if (!dataOut) {
ESP_LOGE(TAG, "Failed to allocate memory for frame");
return NULL;
}
size_t bytePos = 0;
int64_t time_us =
(int64_t)pre.timestamp.tv_sec * 1000000L + (int64_t)pre.timestamp.tv_usec;
dataOut[bytePos++] = 'B';
dataOut[bytePos++] = 'R';
dataOut[bytePos++] = 'N';
dataOut[bytePos++] = 'F';
dataOut[bytePos++] = 'R';
dataOut[bytePos++] = 'M';
dataOut[bytePos++] = (pre.len >> 24) & 0xFF;
dataOut[bytePos++] = (pre.len >> 16) & 0xFF;
dataOut[bytePos++] = (pre.len >> 8) & 0xFF;
dataOut[bytePos++] = (pre.len) & 0xFF;
dataOut[bytePos++] = (pre.width >> 8) & 0xFF;
dataOut[bytePos++] = (pre.width) & 0xFF;
dataOut[bytePos++] = (pre.height >> 8) & 0xFF;
dataOut[bytePos++] = (pre.height) & 0xFF;
dataOut[bytePos++] = (pre.format) & 0xFF;
dataOut[bytePos++] = (time_us >> 56) & 0xFF;
dataOut[bytePos++] = (time_us >> 48) & 0xFF;
dataOut[bytePos++] = (time_us >> 40) & 0xFF;
dataOut[bytePos++] = (time_us >> 32) & 0xFF;
dataOut[bytePos++] = (time_us >> 24) & 0xFF;
dataOut[bytePos++] = (time_us >> 16) & 0xFF;
dataOut[bytePos++] = (time_us >> 8) & 0xFF;
dataOut[bytePos++] = (time_us) & 0xFF;
ESP_LOGI(TAG,
"Len: %ld, width:%d, height:%d, format:%d, timestamp: %lld",
pre.len, pre.width, pre.height, pre.format, time_us);
// Copy image data after PreData
memcpy(dataOut + bytePos, data, pre.len);
*outSize = totalSize;
return dataOut;
}
esp_err_t camera_capture() {
gpio_set_level(GPIO_NUM_4, 1);
ESP_LOGI(TAG, "Pregrab");
camera_fb_t *fb = esp_camera_fb_get();
ESP_LOGI(TAG, "Postgrab");
if (!fb) {
ESP_LOGE(TAG, "Camera Capture Failed");
return ESP_FAIL;
}
PreData pre;
pre.len = fb->len;
pre.width = fb->width;
pre.height = fb->height;
pre.format = fb->format;
pre.timestamp = fb->timestamp;
size_t totalSize;
uint8_t *dataOut = create_frame(fb->buf, &totalSize, pre);
if (!dataOut) {
esp_camera_fb_return(fb);
return ESP_ERR_NO_MEM;
}
esp_camera_fb_return(fb);
gpio_set_level(GPIO_NUM_4, 0);
send_jpeg_nrf(dataOut, totalSize);
free(dataOut);
/*
uint8_t testData[1000];
memcpy(testData, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ut felis vitae magna laoreet interdum dictum auctor risus. Quisque quis enim sagittis, venenatis dui sed, fermentum tortor. Fusce euismod vulputate felis at commodo. Ut ac fermentum metus. Proin in nunc ac libero accumsan vehicula. Praesent posuere nulla sit amet odio blandit, in pharetra odio dapibus. Nulla id ultrices erat. Cras porta sed nunc vitae ultricies. Duis luctus ipsum quis mauris maximus, in venenatis dui venenatis. Integer nec tellus turpis. Nulla erat sapien, tempus at velit sit amet, maximus euismod ipsum. Vivamus ac orci mi. Sed elementum tortor ac nisi lobortis, in sodales est lacinia.\n\nVestibulum a eros ut metus imperdiet finibus. Vivamus vel lacus blandit, accumsan massa ut, elementum lorem. Maecenas ac neque nibh. Integer et nisi pellentesque, iaculis augue vitae, lobortis urna. Nullam venenatis viverra odio, sed molestie orci sagittis in. Curabitur iaculis sem finibus arcu viverra, quis imperdiet libe", 1000);
PreData pre;
pre.len = sizeof(testData);
pre.width = fb->width;
pre.height = fb->height;
pre.format = fb->format;
pre.timestamp = fb->timestamp;
size_t totalSize;
uint8_t *dataOut = create_frame(testData, &totalSize, pre);
if (!dataOut) {
esp_camera_fb_return(fb);
return ESP_ERR_NO_MEM;
}
esp_camera_fb_return(fb);
send_jpeg_nrf(dataOut, totalSize);
free(dataOut);
*/
return ESP_OK;
}
void init_nrf() {
Nrf24_init(&dev);
uint8_t payload = 32;
uint8_t channel = CONFIG_RADIO_CHANNEL;
Nrf24_config(&dev, channel, payload);
// Set destination address using 5 characters
//esp_err_t ret = Nrf24_setTADDR(&dev, (uint8_t *)"CAMRX");
esp_err_t ret = Nrf24_setTADDR(&dev, (uint8_t *)"CAM");
if (ret != ESP_OK) {
ESP_LOGE(pcTaskGetName(NULL), "nrf24l01 not installed");
while (1) {
vTaskDelay(1);
}
}
ret = Nrf24_setRADDR(&dev, (uint8_t *)"CAM");
if (ret != ESP_OK) {
ESP_LOGE(pcTaskGetName(NULL), "nrf24l01 not installed");
while (1) {
vTaskDelay(1);
}
}
ESP_LOGW(pcTaskGetName(NULL), "Set RF Data Ratio to 250kBps");
Nrf24_SetSpeedDataRates(&dev, 2);
/*
ESP_LOGW(pcTaskGetName(NULL), "Set RF Data Ratio to 2MBps");
Nrf24_SetSpeedDataRates(&dev, 1);
*/
ESP_LOGW(pcTaskGetName(NULL), "Set RF max retransmits to 4 tries");
Nrf24_setRetransmitCount(&dev, 15);
ESP_LOGW(pcTaskGetName(NULL), "CONFIG_RETRANSMIT_DELAY=2mS");
Nrf24_setRetransmitDelay(&dev, 8);
// Print settings
Nrf24_printDetails(&dev);
}
void main_task(void *arg) {
init_nrf();
gpio_set_direction(4, GPIO_MODE_DEF_OUTPUT);
ESP_ERROR_CHECK(camera_init());
while (1) {
int64_t start = esp_timer_get_time();
camera_capture();
handle_radio_commands();
int64_t end = esp_timer_get_time();
int64_t duration = end - start;
printf("%lf FPS, %lf SPF\n", 1 / (duration / 1000000.0),
duration / 1000000.0);
}
}
void app_main(void) {
const uart_port_t uart_num = UART_NUM_0;
uart_config_t uart_config = {.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE};
uart_param_config(uart_num, &uart_config);
uart_driver_install(uart_num, BUF_SIZE * 2, 0, 0, NULL, 0);
nvs_flash_init();
xTaskCreate(main_task, "main_task", 8192, NULL, 5, NULL); // 8 KB stack
}