Original post - 12/2022
I'm currently trying to transmit audio via nRF24 with an ESP32 development board using a driver library from nopnop2002. I'm using the i2s-adc lib functions to get data from an analog microphone and save it into a buffer after scaling it down to 8-bit so that the other esp32 can play it directly with its DAC converter.
At first, I had a large i2s_read_len and later I suspected that this could be an issue so I decided to reduce it to 12-bit (the ADC bit width of the esp32) for testing purposes. What is the ideal size for my audio packets?
I'm also not sure how to handle the sampling rate of i2s_read and the rate at which I attempt to send data to the nRF24 via SPI. As far as I know the internal ADC-I2S already has a queue implemented, should I make another one and use vTaskDelay(??? / portTICK_PERIOD_MS); to avoid sending data too fast?
Here's what I currently have ignoring the includes:
#define V_REF 1100
#define I2S_COMM_MODE 0 // ADC/DAC Mode
#define I2S_SAMPLE_RATE 44100
#define I2S_SAMPLE_BITS 16
#define I2S_BUF_DEBUG 0 // enable display buffer for debug
#define I2S_READ_LEN 16 * 1024 // I2S read buffer length
#define I2S_FORMAT (I2S_CHANNEL_FMT_ONLY_RIGHT)
#define I2S_CHANNEL_NUM 0 // I2S channel number
#define I2S_ADC_UNIT ADC_UNIT_1 // I2S built-in ADC unit
#define I2S_ADC_CHANNEL ADC1_CHANNEL_0 // I2S built-in ADC channel GPIO36
#define BIT_SAMPLE 16
#define SPI_DMA_CHAN SPI_DMA_CH_AUTO
#define NUM_CHANNELS 1 // For mono recording only!
#define SAMPLE_SIZE (BIT_SAMPLE * 1024)
#define BYTE_RATE (I2S_SAMPLE_RATE * (BIT_SAMPLE / 8)) * NUM_CHANNELS
/**
* #brief I2S ADC mode init.
*/
void init_microphone(void)
{
int i2s_num = I2S_COMM_MODE;
i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN | I2S_MODE_ADC_BUILT_IN,
.sample_rate = I2S_SAMPLE_RATE,
.bits_per_sample = I2S_SAMPLE_BITS,
.communication_format = I2S_COMM_FORMAT_STAND_MSB,
.channel_format = I2S_FORMAT,
.intr_alloc_flags = 0,
.dma_buf_count = 6,
.dma_buf_len = 256,
.use_apll = 1,
};
// Call driver installation function and adc pad.
ESP_ERROR_CHECK(i2s_driver_install(i2s_num, &i2s_config, 0, NULL));
ESP_ERROR_CHECK(i2s_set_adc_mode(I2S_ADC_UNIT, I2S_ADC_CHANNEL));
}
/**
* #brief Scale data to 8bit for data from ADC.
* Data from ADC are 12bit width by default.
* #param d_buff: destination buffer
* #param s_buff: source buffer
* #param len: length of source buffer
*/
void i2s_adc_data_scale(uint8_t *d_buff, uint8_t *s_buff, uint32_t len)
{
uint32_t j = 0;
uint32_t dac_value = 0;
#if (EXAMPLE_I2S_SAMPLE_BITS == 16)
for (int i = 0; i < len; i += 2)
{
dac_value = ((((uint16_t)(s_buff[i + 1] & 0xf) << 8) | ((s_buff[i + 0]))));
d_buff[j++] = 0;
d_buff[j++] = dac_value * 256 / 4096;
}
#else
for (int i = 0; i < len; i += 4)
{
dac_value = ((((uint16_t)(s_buff[i + 3] & 0xf) << 8) | ((s_buff[i + 2]))));
d_buff[j++] = 0;
d_buff[j++] = 0;
d_buff[j++] = 0;
d_buff[j++] = dac_value * 256 / 4096;
}
#endif
}
#if CONFIG_TRANSMITTER
void transmitter(void *pvParameters)
{
size_t bytes_read;
ESP_LOGI(pcTaskGetName(0), "Start");
int i2s_read_len = (12);
char *i2s_read_buff = (char *)calloc(i2s_read_len, sizeof(char));
uint8_t *i2s_write_buff = (uint8_t *)calloc(i2s_read_len, sizeof(char));
i2s_adc_enable(I2S_CHANNEL_NUM);
NRF24_t dev;
Nrf24_init(&dev);
uint8_t payload = sizeof(i2s_read_buff);
uint8_t channel = 90;
Nrf24_config(&dev, channel, payload);
// Set the receiver address using 5 characters
esp_err_t ret = Nrf24_setTADDR(&dev, (uint8_t *)"FGHIJ");
if (ret != ESP_OK)
{
ESP_LOGE(pcTaskGetName(0), "nrf24l01 not installed");
while (1)
{
vTaskDelay(1);
}
}
#if CONFIG_ADVANCED
AdvancedSettings(&dev);
#endif // CONFIG_ADVANCED
// Print settings
Nrf24_printDetails(&dev);
// Start ADC
while (1)
{
// Read data from I2S bus, in this case, from ADC. //
i2s_read(I2S_CHANNEL_NUM, (void *)i2s_read_buff, i2s_read_len, &bytes_read, portMAX_DELAY);
// process data and scale to 8bit for I2S DAC.
i2s_adc_data_scale(i2s_write_buff, (uint8_t *)i2s_read_buff, i2s_read_len);
// i2s_write_buff needs to be the buffer that is sent via nr24l01.
Nrf24_send(&dev, i2s_write_buff);
if (Nrf24_isSend(&dev, 1000))
{
ESP_LOGI(pcTaskGetName(0), "sending audio data ...");
}
else
{
ESP_LOGW(pcTaskGetName(0), "sending failed ...");
}
}
}
#endif // CONFIG_TRANSMITTER
void app_main(void)
{
// I2S ADC mode microphone init.
init_microphone();
#if CONFIG_TRANSMITTER
xTaskCreate(transmitter, "TRANSMITTER", 1024 * 3, NULL, 2, NULL);
#endif
// Stop I2S driver and destroy
// ESP_ERROR_CHECK(i2s_driver_uninstall(I2S_COMM_MODE));
}
Here is the code where Nrf24_send is defined:
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <driver/spi_master.h>
#include <driver/gpio.h>
#include "esp_log.h"
#include "mirf.h"
#define TAG "NRF24"
// SPI Stuff
#if CONFIG_SPI2_HOST
#define HOST_ID SPI2_HOST
#elif CONFIG_SPI3_HOST
#define HOST_ID SPI3_HOST
#endif
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"};
const char rf24_crclength[][10] = {"Disabled", "8 bits", "16 bits"};
const 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;
}
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_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[5];
Nrf24_readRegister(dev, RX_ADDR_P0, 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;
}
// 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);
if ( status & (1 << RX_DR) ) 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);
return ((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
}
// 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;
}
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 TX 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) ) );
}
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_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_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, "EN_AA\t", EN_AA, 1);
Nrf24_print_byte_register(dev, "EN_RXADDR", EN_RXADDR, 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, "CONFIG\t", CONFIG, 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)]);
#if 0
printf_P(PSTR("Model\t\t = "
PRIPSTR
"\r\n"),pgm_read_ptr(&rf24_model_e_str_P[isPVariant()]));
#endif
//printf("getCRCLength()=%d\n",Nrf24_getCRCLength(dev));
printf("CRC Length\t = %s\n", rf24_crclength[Nrf24_getCRCLength(dev)]);
//printf("getPALevel()=%d\n",Nrf24_getPALevel(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;
}
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);
}
uint8_t Nrf24_getRetransmitDelay(NRF24_t * dev)
{
uint8_t value;
Nrf24_readRegister(dev, SETUP_RETR, &value, 1);
return (value >> 4);
}
I'm getting the following output:
...
W (19498) NRF24: Maximum number of TX retries interrupt
W (19498) TRANSMITTER: sending failed ...
...
Update: Transmission succeeds but audio is not inteligible - 02/2023
I found out that I'm using Si24R1 (Chinese clone of nRF24L01) and I'm still using the i2s-adc esp-idf lib functions to get data from an analog microphone and I need to get this I2S stream through the SPI interface between the ESP32 and the nRF24L01 breakout board clone. Is this possible?
I've seen some people mention on the internet that Nordic Semiconductors has nRF chips that support I2S but I would rather stick to the hardware I have available.
Here's the link I've consulted.
I've also found this library that supports audio transmission using the nRF24L01/Si24R1 chip using the nrf24.h lib but it is writeen in c++ and it is not clear to me how I could integrate it into my project.
Let me know what you think the problem is or/and if you have good recommendations for learning material about this subject.
Thanks for the help in advance!
Do your hardware works with a simple sender/receiver from sample codes you can find in adruino ide? If you didn't checked, have a try so you can validate the hardware is ok.
If simple sender/receiver doesn't work, check your power supply lines. Sometimes not enough current from the voltage regulator.
Add some caps on the rf24 module power-supply pins to filter some noise can also helps depending on the quality of the power supply.
./X
I'm trying to interface vl53l0x sensor with my atSamE51A19 controller. I can send the data to the sensor. As it has established connection I send identification code and it responds. But further initialization and ranging is not working.
I'm using stm library implementation on my sam board. [stmArduinoLib][1]
The only changes I made is in the read write functions. It was using stm base functions but I'm using different functions.
/****************** Write and read functions from I2C *************************/
VL53L0X_Error VL53L0X::VL53L0X_WriteMulti(VL53L0X_DEV Dev, uint8_t index, uint8_t *pdata, uint32_t count)
{
int status;
status = VL53L0X_I2CWrite(index, pdata, (uint16_t) count);
if (status == 0)
{
status = VL53L0X_ERROR_CONTROL_INTERFACE;
}
else
{
status = VL53L0X_ERROR_NONE;
}
return status;
}
VL53L0X_Error VL53L0X::VL53L0X_ReadMulti(VL53L0X_DEV Dev, uint8_t index, uint8_t *pdata, uint32_t count)
{
int status;
if (count >= VL53L0X_MAX_I2C_XFER_SIZE)
{
status = VL53L0X_ERROR_INVALID_PARAMS;
}
status = VL53L0X_I2CRead(index, pdata, (uint16_t) count);
return status;
}
VL53L0X_Error VL53L0X::VL53L0X_WrByte(VL53L0X_DEV Dev, uint8_t index, uint8_t data)
{
int status;
status = WriteRegister(index, data); //VL53L0X_I2CWrite(index, &data, 1);
if (status == 0)
{
status = VL53L0X_ERROR_CONTROL_INTERFACE;
}
else
{
status = VL53L0X_ERROR_NONE;
}
return status;
}
VL53L0X_Error VL53L0X::VL53L0X_WrWord(VL53L0X_DEV Dev, uint8_t index, uint16_t data)
{
int status;
int32_t status_int;
alignas(2) uint8_t buffer[2];
buffer[0] = data >> 8;
buffer[1] = data & 0x00FF;
status_int = VL53L0X_I2CWrite(index, (uint8_t*) buffer, 2);
// buffer[0] = MSB
// buffer[1]
// buffer[2]
// buffer[3] = LSB
if (status_int == 0)
{
status = VL53L0X_ERROR_CONTROL_INTERFACE;
}
else
{
status = VL53L0X_ERROR_NONE;
}
return status;
}
VL53L0X_Error VL53L0X::VL53L0X_WrDWord(VL53L0X_DEV Dev, uint8_t index, uint32_t data)
{
int status;
int32_t status_int;
alignas(2) uint8_t buffer[4];
buffer[0] = (data >> 24) & 0xFF; // MSB
buffer[1] = (data >> 16) & 0xFF;
buffer[2] = (data >> 8) & 0xFF;
buffer[3] = (data >> 0) & 0xFF; // LSB
status_int = VL53L0X_I2CWrite(index, buffer, 4);
if (status_int == 0)
{
status = VL53L0X_ERROR_CONTROL_INTERFACE;
}
else
{
status = VL53L0X_ERROR_NONE;
}
return status;
}
VL53L0X_Error VL53L0X::VL53L0X_RdByte(VL53L0X_DEV Dev, uint8_t index, uint8_t *data)
{
int status = VL53L0X_ERROR_NONE;
status = ReadRegister(index, *data); //
if (status)
{
status = VL53L0X_ERROR_NONE;
return status;
}
else
{
status = VL53L0X_ERROR_CONTROL_INTERFACE;
}
return status;
}
VL53L0X_Error VL53L0X::VL53L0X_RdWord(VL53L0X_DEV Dev, uint8_t index, uint16_t *data)
{
int status = 0;
uint8_t buffer[2] =
{ 0, 0 };
status = VL53L0X_I2CRead(index, buffer, 2);
if (status == 0)
{
*data = (uint16_t) (buffer[0] << 8) | (buffer[1] & 0xFF); // | ? +
}
return status;
}
VL53L0X_Error VL53L0X::VL53L0X_RdDWord(VL53L0X_DEV Dev, uint8_t index, uint32_t *data)
{
int status;
uint8_t buffer[4] =
{ 0, 0, 0, 0 };
status = VL53L0X_I2CRead(index, buffer, 4);
if (!status) // use OR
{
*data = ((uint32_t) buffer[0] << 24) | ((uint32_t) buffer[1] << 16) | ((uint32_t) buffer[2] << 8) | (uint32_t) buffer[3];
}
return status;
}
VL53L0X_Error VL53L0X::VL53L0X_UpdateByte(VL53L0X_DEV Dev, uint8_t index, uint8_t AndData, uint8_t OrData)
{
int status;
uint8_t buffer = 0;
/* read data direct onto buffer */
status = VL53L0X_I2CRead(index, &buffer, 1);
if (!status)
{
buffer = (buffer & AndData) | OrData;
status = VL53L0X_I2CWrite(index, &buffer, (uint8_t) 1);
}
if (status == 0)
{
status = VL53L0X_ERROR_CONTROL_INTERFACE;
}
else
{
status = VL53L0X_ERROR_NONE;
}
return status;
}
VL53L0X_Error VL53L0X::VL53L0X_I2CWrite(uint8_t RegisterAddr, uint8_t *pBuffer, uint16_t NumByteToWrite)
{
return WriteRegisters(RegisterAddr, pBuffer, NumByteToWrite);
}
VL53L0X_Error VL53L0X::VL53L0X_I2CRead(uint8_t RegisterAddr, uint8_t *pBuffer, uint16_t NumByteToRead)
{
alignas(2) uint8_t vpBuffer[5];
/* dev_i2c->beginTransmission(((uint8_t) (((DeviceAddr) >> 1) & 0x7F)));
*/
if (ReadRegisters(RegisterAddr, vpBuffer, NumByteToRead))
{
pBuffer = vpBuffer;
return 0;
}
return 1;
}
In this functions I'm not sending device address everytime just sending the index and data. In constructor only I've set the address once. The only error I'm getting when I use Status = VL53L0X_PerformRefSpadManagement(Device, &refSpadCount, &isApertureSpads); This function. In initialization. Don't know how to solve this issue. Tried to change the mode to continous that didn't help. Note: the sensor is working fine with arduino.
[1]: https://github.com/stm32duino/VL53L0X
I found a new possible solution to this problem, as I spent 2 days to solve it, I feel that I need to share it with you guys. Here is the summary of solutions:
Powering problem 1: add a 50ms delay in the setup function (before the sensor init, obviously). This way, the Vin will have time enough to reach the level needed by the sensor, as explained in a previous message (ocrdu)
Powering problem 2: This sensor demands more than others, normally the 5V pin of your board will do it better than the 3v3. If there are many sensors connected at the same time, disconnect all of them to check whether they are using the power our vl53l0x needs.
Powering problem 3: If you feed it with an external source, unify grounds with the board, don't forget it
SCL - SDA weak signals: add a pullup (2k2 - 4k7 from each channel to vin), this way the signals will perform better, that could be the problem
Soldering problem: Make sure that each header pin is well soldered. I've found many cases of poor soldering. The tiny pads don't help. It is advisable to check continuity with a multimeter between the headers and the pads where they should be connected
XSHUT not pulled up: My problem, this was that AZDelivery board says that it has a pull up in XSHUT (which works like an enable). But it could either be false, or badly implemented. Xshut needs to be high, 3v3/5v, otherwise, the sensor won't work. Bridge the XSHUT to Vin or implement a pull up and the problem will be solved.
I am using adafruit's library 1.1.2, and it works, with pullups, without pullups, connected to 5V and also connected to 3V3, with the
50ms delay, and connecting XSHUT to high (3V3 / Vin)
My board is vl53l0x from AZDelivery (5€ amazon)
I have tested the sensor with arduino nano and with ESP32, both work fine
Hope you guys find this useful <3
I am using LwIP stack with FreeRTOS in STM32F407Discovery board as TCP Client and I have a Linux computer as TCP Server. I faced a problem during transmission of a struct array, say struct EncoderData Encoder[2], through netconn API.
The thing is, when I print the elements of the struct array to Serial Terminal (with UART) the data stored in the struct array is correctly shown. However when I send the structs through netconn socket, the elements of the first struct, i.e. Encoder[0] is received correctly, whereas Encoder[1] is received as all zeroes.
Expected output in the Server side,
Encoder[0] ---> 0xAA 0x07 0x00 0x52 0x12 0xDC 0xAB 0xFA
Encoder[1] ---> 0xAA 0x07 0x01 0x52 0x42 0xBF 0xAA 0xFA
Resulting problematic output in the Server side
Encoder[0] ---> 0xAA 0x07 0x00 0x52 0x12 0xDC 0xAB 0xFA
Encoder[1] ---> 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Below, I am sharing both the server and the client side code, I would appreciate if you could help me out identifying the problem.
Server.c -- Linux Computer
#define MAX 9
#define PORT 8080
#define SA struct sockaddr
#define NUM_OF_ENCODERS 2
#define PREAMBLE 0xAA
#define LENGTH 0x07
#define CRC 0xFA
struct EncoderData {
volatile uint8_t encoderID;
volatile uint8_t byte0;
volatile uint8_t byte1;
volatile uint8_t byte2;
volatile uint8_t byte3;
volatile uint8_t byte4;
volatile uint32_t singleTurnValue;
volatile uint16_t multiTurnValue;
volatile uint16_t prevMultiTurnValue;
volatile uint8_t direction;
volatile float angle;
};
struct EncoderData Encoder[NUM_OF_ENCODERS];
// This function is called inside int main()
void func(int sockfd)
{
uint8_t buff[MAX];
uint32_t singleTurnValue = 0;
float angle = 0.0;
uint8_t channel;
uint8_t encoderID;
// infinite loop to read data continuously
for (;;) {
bzero(buff, MAX);
// read the message from client and copy it in buffer
read(sockfd, buff, sizeof(buff));
if(buff[0] == PREAMBLE && buff[1] == LENGTH && buff[8] == CRC)
{
encoderID = buff[2];
Encoder[encoderID].encoderID = encoderID;
Encoder[encoderID].byte0 = (uint8_t)buff[3];
Encoder[encoderID].byte1 = (uint8_t)buff[4];
Encoder[encoderID].byte2 = (uint8_t)buff[5];
Encoder[encoderID].byte3 = (uint8_t)buff[6];
Encoder[encoderID].byte4 = (uint8_t)buff[7];
Encoder[encoderID].singleTurnValue = (uint32_t)((buff[5] << 16)|(buff[6] << 8)|(buff[7]));
Encoder[encoderID].multiTurnValue = (uint16_t)((buff[3] << 8)|(buff[4]));
//printf("%d\n", encoderID);
bzero(buff, MAX);
}
// Here I print the struct elements, below printf shows data correctly
printf("0x%X 0x%X 0x%X 0x%X 0x%X 0x%X\t\t",
Encoder[0].encoderID, Encoder[0].byte0, Encoder[0].byte1,
Encoder[0].byte2, Encoder[0].byte3, Encoder[0].byte4);
// However below printf results in only zeroes
printf("0x%X 0x%X 0x%X 0x%X 0x%X 0x%X\n",
Encoder[1].encoderID, Encoder[1].byte0, Encoder[1].byte1,
Encoder[1].byte2, Encoder[1].byte3, Encoder[1].byte4);
}
}
Client.c -- STM32F407Discovery
#define NUM_OF_ENCODERS 2
struct EncoderData Encoder[NUM_OF_ENCODERS];
void Encoder_Process(void)
{
//float angle = 0.0;
uint64_t encoderRawValue = 0;
uint32_t singleTurnValue = 0;
uint8_t channel = 0;
for(channel = 0; channel < NUM_OF_ENCODERS; channel++)
{
selectChannel(channel);
encoderRawValue = readEncoder();
singleTurnValue = getSingleTurn(encoderRawValue);
angle = calculateMotorAngle(singleTurnValue);
Encoder[channel].preamble = 0xAA;
Encoder[channel].length = 0x07;
Encoder[channel].encoderID = channel;
Encoder[channel].byte0 = (uint8_t)((encoderRawValue >> 32) & 0xFF);
Encoder[channel].byte1 = (uint8_t)((encoderRawValue >> 24) & 0xFF);
Encoder[channel].byte2 = (uint8_t)((encoderRawValue >> 16) & 0xFF);
Encoder[channel].byte3 = (uint8_t)((encoderRawValue >> 8) & 0xFF);
Encoder[channel].byte4 = (uint8_t)((encoderRawValue ) & 0xFF);
Encoder[channel].crc = 0xFA;
}
}
// Convert Encoder struct into buffer
uint8_t* prepareEncoderData(struct EncoderData enc)
{
static uint8_t buffer[9];
memset(buffer, '\0', 9);
buffer[0] = enc.preamble;
buffer[1] = enc.length;
buffer[2] = enc.encoderID;
buffer[3] = enc.byte0;
buffer[4] = enc.byte1;
buffer[5] = enc.byte2;
buffer[6] = enc.byte3;
buffer[7] = enc.byte4;
buffer[8] = enc.crc;
return buffer;
}
static void tcp_client_netconn_thread(void *arg)
{
struct netconn *conn; //, *newconn;
struct ip_addr remoteipaddr, localipaddr;
err_t err1, err2;
// portCHAR pagehits[100] = {0};
uint8_t channels;
uint8_t *transmitBuffer;
IP4_ADDR(&remoteipaddr, DEST_IP_ADDR0, DEST_IP_ADDR1, DEST_IP_ADDR2, DEST_IP_ADDR3);
IP4_ADDR(&localipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
/* Create a new TCP connection handle */
conn = netconn_new(NETCONN_TCP);
if (conn!= NULL)
{
err1 = netconn_bind(conn, &localipaddr, PORT_NUMBER);
err2 = netconn_connect(conn, &remoteipaddr, PORT_NUMBER);
if (err1 == ERR_OK && err2 == ERR_OK)
{
printf("STM32F4 connected to the server!\n");
while(1)
{
for(channels = 0; channels < NUM_OF_ENCODERS; channels++)
{
Encoder_Process();
transmitBuffer = prepareEncoderData(Encoder[channels]);
netconn_write(conn, transmitBuffer, strlen((char*)transmitBuffer), NETCONN_COPY);
}
vTaskDelay(10);
//netconn_delete(newconn);
}
}
else
{
printf("can not bind netconn");
printf("\terr code: %d", err1);
printf("\terr code: %d\n", err2);
}
}
else
{
printf("can not create netconn");
}
}
I want to write a simple test program to receive Ethernet frames using the ibverbs API.
The code below compiles and runs but never receives any packets. I'm using Mellanox ConnectX-3 hardware on Ubuntu 18.
Questions:
If, while running this RX program, I ping the Inifiniband interface from another machine, then ping receives responses. I would not expect that because the ping requests should be grabbed by the RX program and the Linux IP stack should not see them and therefore should not respond. What should happen?
Is there anything obvious wrong with my code?
Do I need a steering rule? If I remove the call of ibv_create_flow() should I just receive all packets the interface sees?
#include <infiniband/verbs.h>
#include <stdio.h>
#include <stdlib.h>
#define PORT_NUM 1
#define MAX_MSG_SIZE 1500 // The maximum size of each received packet.
#define RQ_NUM_DESC 512 // Max packets that can be received without processing.
// The MAC of the interface we are listening on.
#define DEST_MAC { 0x00, 0x0d, 0x3a, 0x47, 0x1c, 0x2e }
#define FATAL_ERROR(msg, ...) { fprintf(stderr, "ERROR: " msg "\n", ##__VA_ARGS__); exit(-1); }
int main() {
// Get the list of devices.
int num_devices = 0;
struct ibv_device **dev_list = ibv_get_device_list(&num_devices);
if (!dev_list)
FATAL_ERROR("Failed to get IB devices list.");
// Choose the first device.
struct ibv_device *ib_dev = dev_list[0];
if (!ib_dev)
FATAL_ERROR("IB device not found.");
printf("Found %i Infiniband device(s).\n", num_devices);
printf("Using device '%s'.\n", ibv_get_device_name(ib_dev));
// Get the device context.
struct ibv_context *context = ibv_open_device(ib_dev);
if (!context)
FATAL_ERROR("Couldn't get context for device.");
// Allocate a protection domain (PD) that will group memory
// regions (MR) and rings.
struct ibv_pd *pd = ibv_alloc_pd(context);
if (!pd)
FATAL_ERROR("Couldn't allocate protection domain.");
// Create Complition Queue (CQ).
struct ibv_cq *cq = ibv_create_cq(context, RQ_NUM_DESC, NULL, NULL, 0);
if (!cq)
FATAL_ERROR("Couldn't create completion queue. errno = %d.", errno);
// Create Queue Pair (QP).
struct ibv_qp_init_attr qp_init_attr = {
.qp_context = NULL,
.send_cq = cq, // Report receive completion to CQ.
.recv_cq = cq,
.cap = {
.max_send_wr = 0, // No send ring.
.max_recv_wr = RQ_NUM_DESC, // Max num packets in ring.
.max_recv_sge = 1, // Only one pointer per descriptor.
},
.qp_type = IBV_QPT_RAW_PACKET, // Use Ethernet packets.
};
struct ibv_qp *qp = ibv_create_qp(pd, &qp_init_attr);
if (!qp)
FATAL_ERROR("Couldn't create queue pair.");
// Initialize the QP (receive ring) and assign a port.
struct ibv_qp_attr qp_attr = { 0 };
qp_attr.qp_state = IBV_QPS_INIT;
qp_attr.port_num = PORT_NUM;
int qp_flags = IBV_QP_STATE | IBV_QP_PORT;
if (ibv_modify_qp(qp, &qp_attr, qp_flags) < 0)
FATAL_ERROR("Failed to initialize queue pair.");
// Move ring state to ready-to-receive. This is needed in
// order to be able to receive packets.
memset(&qp_attr, 0, sizeof(qp_attr));
qp_flags = IBV_QP_STATE;
qp_attr.qp_state = IBV_QPS_RTR;
if (ibv_modify_qp(qp, &qp_attr, qp_flags) < 0)
FATAL_ERROR("Failed to put queue pair into ready-to-receive state.");
// Allocate memory for packet buffer.
int buf_size = MAX_MSG_SIZE * RQ_NUM_DESC; // Maximum size of data to be accessed by hardware.
void *buf = malloc(buf_size);
if (!buf)
FATAL_ERROR("Couldn't allocate memory.");
// Register the user memory so it can be accessed by the HW directly.
struct ibv_mr *mr = ibv_reg_mr(pd, buf, buf_size, IBV_ACCESS_LOCAL_WRITE);
if (!mr)
FATAL_ERROR("Couldn't register memory region.");
// Create a scatter/gather entry.
struct ibv_sge sg_entry;
sg_entry.length = MAX_MSG_SIZE;
sg_entry.lkey = mr->lkey;
// Create a receive work request.
struct ibv_recv_wr wr;
wr.num_sge = 1;
wr.sg_list = &sg_entry;
wr.next = NULL;
// Post a load of receive work requests onto the receive queue.
struct ibv_recv_wr *bad_wr;
for (int n = 0; n < RQ_NUM_DESC; n++) {
// Each descriptor points to max MTU size buffer.
sg_entry.addr = (uint64_t)buf + MAX_MSG_SIZE * n;
// When a packet is received, a work completion will be created
// corresponding to this work request. It will contain this field.
wr.wr_id = n;
// Post the receive buffer to the ring.
int rv = ibv_post_recv(qp, &wr, &bad_wr);
if (rv != 0) {
FATAL_ERROR("Posting recv failed with error code %i.", rv);
}
}
// Create steering rule.
struct raw_eth_flow_attr {
struct ibv_flow_attr attr;
struct ibv_flow_spec_eth spec_eth;
} __attribute__((packed)) flow_attr = {
.attr = {
.comp_mask = 0,
.type = IBV_FLOW_ATTR_NORMAL,
.size = sizeof(flow_attr),
.priority = 0,
.num_of_specs = 1,
.port = PORT_NUM,
.flags = 0,
},
.spec_eth = {
.type = IBV_FLOW_SPEC_ETH,
.size = sizeof(struct ibv_flow_spec_eth),
.val = {
.dst_mac = DEST_MAC,
.src_mac = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
.ether_type = 0,
.vlan_tag = 0,
},
.mask = {
.dst_mac = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
.src_mac = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
.ether_type = 0,
.vlan_tag = 0,
}
}
};
// Register steering rule to intercept packet to DEST_MAC and place packet in
// ring pointed by qp.
struct ibv_flow *eth_flow = ibv_create_flow(qp, &flow_attr.attr);
if (!eth_flow)
FATAL_ERROR("Couldn't attach steering flow. Does DEST_MAC match that of the local NIC?");
printf("Receiving.\n");
while (1) {
// Wait for CQ event upon message received, and print a message
struct ibv_wc wc;
int msgs_completed = ibv_poll_cq(cq, 1, &wc);
if (msgs_completed > 0) {
printf("Message %ld received size %d\n", wc.wr_id, wc.byte_len);
sg_entry.addr = (uint64_t)buf + wc.wr_id * MAX_MSG_SIZE;
wr.wr_id = wc.wr_id;
// After processed need to post back the buffer.
int rv = ibv_post_recv(qp, &wr, &bad_wr);
if (rv != 0) {
FATAL_ERROR("Re-posting recv failed with error code %i.", rv);
}
}
else if (msgs_completed < 0) {
FATAL_ERROR("Polling error.");
}
}
}
Take a look at this example from Mellanox: https://community.mellanox.com/s/article/raw-ethernet-programming--basic-introduction---code-example
To receive everything the interface sees, you can use the experimental api #include <infiniband/verbs_exp.h>, then when creating the steering rule, use ibv_exp_flow_attr and set the type to IBV_EXP_FLOW_ATTR_SNIFFER.
Please refer to https://github.com/Mellanox/libvma/wiki/Architecture
VMA implements native RDMA verbs API. The native RDMA verbs have been extended into the Ethernet RDMA-capable NICs, enabling the packets to pass directly between the user application and the InfiniBand HCA or Ethernet NIC, bypassing the kernel and its TCP/UDP handling network stack.
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
For some odd reason, i'm unable to properly verify the TCP checksum. I have code to check IP and UDP checksum, and it works perfectly fine, but for TCP something in my logic is amiss.
My struct definitions for these headers are fine as i can read the data perfectly fine (verified from wireshark). The only problem i'm having is that for TCP checksum, i'm unable to verify whether the checksum is actually correct. Any thoughts as to where i'm doing this wrong?
Very much appreciated.
checksum function
unsigned short in_cksum(unsigned short *addr,int len)
{
register int sum = 0;
u_short answer = 0;
register u_short *w = addr;
register int nleft = len;
/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(u_char *)(&answer) = *(u_char *)w ;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return(answer);
}
read TCP function (old, check edited version)
/* packets are read using the pcap libraries */
void readTCP(const u_char *packets) {
struct TCP_Header *tcp = (struct TCP_Header*) (packets + sizeof(struct Ethernet_Header) + sizeof(struct IP_Header));
struct IP_Header *ip = (struct IP_Header*) (packets + sizeof(struct Ethernet_Header));
struct TCP_Pseudo tcpPseudo;
char tcpcsumblock[sizeof(struct TCP_Pseudo) + sizeof(struct TCP_Header)];
/* tcp pseudo header */
memset(&tcpPseudo, 0, sizeof(struct TCP_Pseudo));
tcpPseudo.source_ip = ip->source_ip.s_addr;
tcpPseudo.destination_ip = ip->destination_ip.s_addr;
tcpPseudo.zero = 0;
tcpPseudo.protocol = 6;
tcpPseudo.length = htons(sizeof(struct TCP_Header));
/* grab tcp checksum and reset it */
int tcpCheckSum = htons(tcp->tcp_checksum);
tcp->tcp_checksum = 0;
/* place the data from the tcp pseudo infront of the tcp header */
memcpy(tcpcsumblock, &tcpPseudo, sizeof(TCPPseudoHeader));
memcpy(tcpcsumblock+sizeof(TCPPseudoHeader),tcp, sizeof(TCPHeader));
/* here is the issue, the checksum that i'm calculating isn't the correct checksum (i checked this by examing the packets from wireshark */
u_short checksum = in_cksum((unsigned short *)tcpcsumblock, sizeof(tcpcsumblock));
}
==EDIT==
new tcp function
/* packets are read using the pcap libraries */
void readTCP(const u_char *packets) {
struct TCP_Header *tcp = (struct TCP_Header*) (packets + sizeof(struct Ethernet_Header) + sizeof(struct IP_Header));
struct IP_Header *ip = (struct IP_Header*) (packets + sizeof(struct Ethernet_Header));
struct TCP_Pseudo tcpPseudo;
/* tcp pseudo header */
memset(&tcpPseudo, 0, sizeof(struct TCP_Pseudo));
tcpPseudo.source_ip = ip->source_ip;
tcpPseudo.destination_ip = ip->destination_ip;
tcpPseudo.zero = 0;
tcpPseudo.protocol = 6;
tcpPseudo.len = htons(ip->ip_len - (ip->ip_hdr_len * 4));
int len = sizeof(struct TCP_Pseudo) + tcpPseudo.len;
u_char tcpcsumblock[len];
memcpy(tcpcsumblock, &tcpPseudo, sizeof(struct TCP_Pseudo));
memcpy(tcpcsumblock + sizeof(struct TCP_Pseudo), (packets + sizeof(struct Ethernet_Header) + sizeof(struct IP_Header)), tcpPseudo.len);
/* here is the issue, the checksum that i'm calculating isn't the correct checksum (i checked this by examing the packets from wireshark */
u_short checksum = in_cksum((unsigned short *)ps_tcp, len);
char *cs = checksum ? "Invalid Checksum!" : "Valid!";
}
ip header
typedef struct IP_Header {
#if __BYTE_ORDER__ == __LITTLE_ENDIAN__
uint8_t ip_hdr_len:4; /* header length */
uint8_t ip_version:4; /* ip version */
#else
uint8_t ip_version:4; /* ip version */
uint8_t ip_hdr_len:4; /* The IP header length */
#endif
uint8_t ip_tos; /* type of service */
uint16_t ip_len; /* total length */
uint16_t ip_id; /* identification */
uint16_t ip_off; /* fragment offset field */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
uint8_t ip_ttl; /* time to live */
uint8_t ip_p; /* protocol */
uint16_t ip_sum; /* checksum */
struct in_addr ip_src, ip_dst; /* source and dest address */
} __attribute__ ((packed));
tcp header
typedef struct TCP_Header {
uint16_t tcp_source_port; /* source port */
uint16_t tcp_dest_port; /* destination port */
uint32_t tcp_seq; /* sequence */
uint32_t tcp_ack; /* acknowledgement number */
uint8_t tcp_offest; /* data offset */
#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4)
uint8_t tcp_flags; /* flags */
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_NS 0x100
#define TH_RS 0xE00
uint16_t tcp_window; /* window */
uint16_t tcp_sum; /* checksum */
uint16_t tcp_urp; /* urgent pointer */
} __attribute__ ((packed));
tcp pseudo header
typedef struct TCP_Pseudo {
struct in_addr src_ip; /* source ip */
struct in_addr dest_ip; /* destination ip */
uint8_t zeroes; /* = 0 */
uint8_t protocol; /* = 6 */
uint16_t len; /* length of TCPHeader */
} __attribute__ ((packed));
The problem is this line:
tcpPseudo.length = htons(sizeof(struct TCP_Header));
According to RFC 793:
The TCP Length is the TCP header length plus the data length in
octets (this is not an explicitly transmitted quantity, but is
computed), and it does not count the 12 octets of the pseudo
header.
You only set the TCP header length, but it should be TCP header length + data length.
The data length is the Total Length reported by the IP header minus the IP header length (field is named IHL in the IP header and must be multiplied by 4 to get the length in bytes) minus the size of the TCP header.
Yet since you want to add the length of the TCP header to the data length, you just have to subtract the IP header length form the total packet length and left over is the sum of TCP header and data length.
tcpPseudo.length = htons(ntohs(ip->total_length) - (ip->ihl * 4));
Also according to the RFC:
The checksum field is the 16 bit one's complement of the one's
complement sum of all 16 bit words in the header and text.
"And text" means all the data following the TCP header is also checksummed. Otherwise TCP could not guarantee that the data was transmitted correctly. Remember, TCP is a reliable protocol, that will retransmit corrupted data, but therefor it must also recognize when data got corrupted.
So the whole packet minus the IP header must be added to tcpcsumblock. Therefor tcpcsumblock must be big enough for whole packets to fit (in case of TCP, 1500 bytes are usually enough, though in theory an IP packet may be as big as 64 KB and will be fragmented if needed) and then you must add the pseudo header, the tcp header and everything to the end of the packet.
I wrote a working piece of code for you. I verified that this works correctly by feeding in some real-live data. As a bonus, this implementation performs a couple of sanity checks on the IP header, including verifying its checksum (since if that doesn't match, all header fields may contain bogus as the header got most likely corrupted), and it also doesn't need to allocate any dynamic memory or copy any data around, so it should be pretty fast. Especially for the last feature, I had to change the in_cksum function to accept a third parameter. If this parameter is zero, it will behave exactly as the version in your code, but by feeding the correct value, you can use this function to update an already calculated checksum as if the data you are going to checksum had directly followed the data you already did checksum before (pretty nifty, huh?)
uint16_t in_cksum (const void * addr, unsigned len, uint16_t init) {
uint32_t sum;
const uint16_t * word;
sum = init;
word = addr;
/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
while (len >= 2) {
sum += *(word++);
len -= 2;
}
if (len > 0) {
uint16_t tmp;
*(uint8_t *)(&tmp) = *(uint8_t *)word;
sum += tmp;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return ((uint16_t)~sum);
}
void readTCP (const u_char *packets) {
uint16_t csum;
unsigned ipHdrLen;
unsigned ipPacketLen;
unsigned ipPayloadLen;
struct TCP_Pseudo pseudo;
const struct IP_Header * ip;
const struct TCP_Header * tcp;
// Verify IP header and calculate IP payload length
ip = (const struct IP_Header *)(packets + sizeof(struct Ethernet_Header));
ipHdrLen = ip->ip_hdr_len * 4;
if (ipHdrLen < sizeof(struct IP_Header)) {
// Packet is broken!
// IP packets must not be smaller than the mandatory IP header.
return;
}
if (in_cksum(ip, ipHdrLen, 0) != 0) {
// Packet is broken!
// Checksum of IP header does not verify, thus header is corrupt.
return;
}
ipPacketLen = ntohs(ip->ip_len);
if (ipPacketLen < ipHdrLen) {
// Packet is broken!
// The overall packet cannot be smaller than the header.
return;
}
ipPayloadLen = ipPacketLen - ipHdrLen;
// Verify that there really is a TCP header following the IP header
if (ip->ip_p != 6) {
// No TCP Packet!
return;
}
if (ipPayloadLen < sizeof(struct TCP_Header)) {
// Packet is broken!
// A TCP header doesn't even fit into the data that follows the IP header.
return;
}
// TCP header starts directly after IP header
tcp = (const struct TCP_Header *)((const u_char *)ip + ipHdrLen);
// Build the pseudo header and checksum it
pseudo.src_ip = ip->ip_src;
pseudo.dest_ip = ip->ip_dst;
pseudo.zeroes = 0;
pseudo.protocol = 6;
pseudo.len = htons(ipPayloadLen);
csum = in_cksum(&pseudo, (unsigned)sizeof(pseudo), 0);
// Update the checksum by checksumming the TCP header
// and data as if those had directly followed the pseudo header
csum = in_cksum(tcp, ipPayloadLen, (uint16_t)~csum);
char * cs = csum ? "Invalid Checksum!" : "Valid!";
printf("%s\n", cs);
}