Tutorial de desarrollo de aplicaciones Portapack (diecisiete) nrf24l01 lanzamiento C

A continuación, mire el código de varios proyectos relacionados. Concéntrese en cómo se implementa la parte de modulación.

En orden de dificultad, creo que es mejor mirar primero el proyecto send_simplified, luego los proyectos send y recv, y finalmente el proyecto BTLE (lanzamiento de HackRF).

enviar_elemento simplificado:

btle_nrf24l01/send_simpified.ino en principal · jamesshao8/btle_nrf24l01 · GitHub

Este proyecto es muy simple, solo contiene un archivo ino y no se llama a la biblioteca RF24, pero la codificación y las llamadas subyacentes se completan directamente.

Las primeras funciones btLeCrc, swapbits, btLeWhitten, btLeWhitenStart, btLePacketEncode se utilizan para la codificación de paquetes Bluetooth. Entre ellos, btLePacketEncode llama a varias otras funciones, y la función realizada es similar a codificar el bit de información en el bit phy mencionado en el artículo anterior para una mayor modulación.

Además, spi_byte, nrf_cmd, nrf_simplebyte, nrf_manybytes, estas funciones se utilizan para la comunicación subyacente, y las siguientes funciones llaman a spi_byte, que es la interfaz que finalmente envía datos al bus SPI.

El principal proceso de operación es completar el trabajo de una sola vez en la configuración primero, como configurar el modo de hardware nrf24l01, incluida la bandera del paquete de transmisión (similar a la dirección mac) también se realiza en la configuración.

Otra función de bucle es el contenido principal de la operación de bucle repetido más adelante. Al principio, está empaquetando, y la dirección mac, el nombre para mostrar, los datos y su encabezado y longitud se almacenan en buf, lo que significa que buf se almacena primero en el bit de información y luego se usa btLePacketEncode para convertir el contenido en buf en phy bit, y finalmente Use spi_byte para enviar al bus SPI, y el trabajo posterior debe ser la modulación, pero esta parte no está implementada en el código, porque esta parte del trabajo está completamente a cargo del chip nrf24l01, y el microcontrolador arduino no se preocupa por esta parte del trabajo. (Además, también está la operación del pin CSN CE, pero esta parte del código es relativamente simple, solo use mi código para ejecutarlo, no es necesario estudiarlo en profundidad).

De esta forma, se finaliza send_simplified. Aunque los otros dos proyectos de envío/recepción tienen más códigos, en realidad son similares en principio. Arduino solo es responsable de la codificación o decodificación, no de la modulación y demodulación. El bit físico se escribe y lee desde el bus SPI. En lugar de la muestra. en la radiocomunicación.

A continuación, mira el proyecto de envío:

btle_nrf24l01/send en principal · jamesshao8/btle_nrf24l01 · GitHub

La función de este proyecto es similar a la del proyecto anterior, pero la estructura es mucho más complicada. send.ino es el programa principal, llamará a BTLE.cpp (esta es la biblioteca compartida al enviar y recibir paquetes de datos de baja potencia de Bluetooth), llama a la biblioteca RF24 y encapsula el controlador subyacente de nrf24l01, y no hay necesidad de RF24 Leer y escribir datos manualmente en el bus SPI.

El trabajo en send.ino también es relativamente simple. Primero, se inicializa la radio de RF24, se define el pin de CSN CE y la radio se pasa a la clase BTLE. Esto es muy importante. Las siguientes operaciones de hardware interesantes son todas alrededor de esta radio Expandida, y las clases de estas dos bibliotecas están conectadas aquí.

Luego, en la función de configuración, la función btle.begin establece el nombre SHARF que se enviará. El btle.advertise en el bucle posterior es una función para transmitir paquetes de datos de forma cíclica y repetida.

El inicio en btle incluye la primera inicialización de radio, es decir, radio.begin (si mira hacia abajo, todo está en RF24.cpp, y finalmente está enviando comandos al bus SPI para establecer el modo de hardware). Luego setAutoAck disabledCRC, etc. También se debe considerar la comunicación dúplex, por lo que debe desactivar ack, de lo contrario no podrá comunicarse con hackrf.

Luego mire el anuncio en btle, llamará a prepare_packet y transmit_packet. El primero es establecer la dirección mac y el nombre del extremo de envío, el segundo es responsable de llamar a las funciones whiten y swapbuf para la codificación y, finalmente, llamar a la función radio->write para enviar el bit phy en la salida. Y radio->write, y esta escritura está en la biblioteca RF24, llama a startFastWrite, startFastWrite llama a write_payload y write_payload finalmente llama a la función de envío de datos en el bus SPI. De esta forma, el bit phy se envía al nrf24l01 a través del bus spi, y es modulado por el hardware y enviado al aire.

artículo recibido:

https://github.com/jamesshao8/btle_nrf24l01/tree/main/recv

De hecho, es muy similar a la estructura de envío.También es un recv.ino, y luego llama a BTLE y RF24.Estas dos bibliotecas son exactamente iguales, pero las funciones utilizadas para recibir pueden ser diferentes. Después de que recv.ino inicializa la radio, sigue leyendo datos de btle.buffer.payload[i] y selecciona partes importantes para mostrar. Tenga en cuenta que btle.buffer.payload[i] corresponde al bit de información demodulado + decodificado. También hay un btle.listen que también es muy importante, el bucle sigue llamándolo para seguir recibiendo datos. Abra BTLE.cpp y busque la función de escucha. Lo que hace es almacenar los datos leídos por radio->leer en inbuf, y luego usar swapbuf y whitten para decodificar Bluetooth. Significa que el inbuf es un bit phy al principio, y se convierte en un bit de información después de la decodificación. Finalmente, si no hay problema con la verificación de crc, son los datos finalmente decodificados. Este inbuf es el mismo espacio de memoria que el btle.buffer mencionado anteriormente, y puedes pensar que es lo mismo.

Luego, veamos cómo radio->read lee el bit phy. En RF24.cpp, read llama a read_payload y read_payload también manipula el bus SPI para leer datos del bus. Muestra que nrf24l01 ha completado la demodulación y envía directamente el bit físico de la salida demodulada a la función de lectura.

De esta manera, incluso si se leen todos estos tres proyectos simples. Solo hacen códec, no modulación y demodulación. Para ver el módem, aún debe mirar el proyecto BTLE utilizado por HackRF.

Aunque este proyecto tiene mucho código, lo que nos interesa es solo btle_tx, que solo corresponde a un archivo:

BTLE/btle_tx.c en maestro · jamesshao8/BTLE · GitHub

Después de abrir btle_tx.c, encontramos que el código también es muy largo, pero de hecho solo nos importa una parte, porque solo se usa un tipo de paquete de datos. 

Primero encuentre la función principal, que internamente llama a dos funciones principales parse_input e init_board, init_board es para inicializar el hardware HackRF (llame a API y similares, estamos familiarizados con otros proyectos), también es compatible con otro BladeRF, pero ahora Ya no se usa mucho .

Luego, el foco está en la función parse_input, que se usa para leer los parámetros de comando, y también incluye operaciones de codificación y modulación basadas en parámetros de comando.

Encuentre la función parse_input. Es más importante calcular_pkt_info y la última parte de printf. La parte de printf corresponde a los datos de bits antes y después de la codificación que vemos en la ventana del terminal. Significa que en la función de cálculo_pkt_info, la lectura de parámetros se ha completado. Obtención, operaciones de codificación y otras operaciones (en realidad, incluso la operación de modulación también se completa).

Hay get_next_field en la función compute_pkt_info, buscando el carácter "-", porque la posición de este carácter es seguida por el tipo de paquete de datos, como ADV_IND. Después de encontrar esta posición clave, use compute_sample_from_pkt_type para leer el paquete de datos y juzgar su tipo.

En calcule_sample_from_pkt_type, si el tipo es ADV_IND, seguirá llamando a la función compute_sample_for_ADV_IND para la codificación y la modulación. Vale la pena señalar que los parámetros originales se transmiten con el parámetro pkt_str en las funciones que comienzan con compute_sample, lo que significa que los parámetros que ingreso en la línea de comando se transmiten capa por capa.

A continuación, busque la función compute_sample_for_ADV_IND.

Hace 4 trabajos.

1. Lea los parámetros restantes, como TXADD RXADD ADVA ADVDATA

2. Mientras lee los parámetros, empaquételos, almacene los datos en pkt->info_bit y almacene la longitud del paquete temporal en pkt->num_info_bit

3. Utilice fill_adv_pdu_header y crc24_and_scramble_to_gen_phy_bit para completar la codificación de info_bit y obtener pkt->phy_bit, que es phy bit.

4. Finalmente, use la función gen_sample_from_phy_bit para modular el bit phy recién obtenido. Esta es también la parte que más nos preocupa, esta función completa el trabajo implementado por el hardware nrf24l01 en los proyectos anteriores de arduino.

Así que ahora veamos la función gen_sample_from_phy_bit:

De hecho, el autor original ha realizado varias implementaciones para este trabajo. El código real al que realmente debemos prestar atención es de 1022 a 1060 líneas. Su bit de parámetro es el bit phy pasado, muestra es el punto de muestreo calculado después de que se completa la modulación y num_bit es el número de bits.

Para hacer la modulación GFSK, en realidad se divide en dos pasos: primero, hacer el filtrado gaussiano en el bit phy, y luego hacer la modulación fsk en los datos 1010, es decir, por ejemplo, 1 en un momento determinado corresponde a una frecuencia, y 0 en el siguiente momento corresponde a otra frecuencia.

Al principio, se hizo un remuestreo, lo único que debe importar es este párrafo:

for (i=0; i<(num_bit*SAMPLE_PER_SYMBOL); i++) {
    if (i%SAMPLE_PER_SYMBOL == 0) {
      tmp_phy_bit_over_sampling_int8[i+(LEN_GAUSS_FILTER*SAMPLE_PER_SYMBOL-1)] = ( bit[i/SAMPLE_PER_SYMBOL] ) * 2 - 1;
    } else {
      tmp_phy_bit_over_sampling_int8[i+(LEN_GAUSS_FILTER*SAMPLE_PER_SYMBOL-1)] = 0;
    }
  }

Transfiere los datos de bits originales a la matriz tmp_phy_bit_over_sampling_int8, lo que significa que el trabajo de modulación posterior es todo para los datos en tmp_phy_bit_over_sampling_int8.

Luego viene el algoritmo de modulación real:

  int16_t tmp = 0;
  sample[0] = cos_table_int8[tmp];
  sample[1] = sin_table_int8[tmp];

  int len_conv_result = num_sample - 1;
  for (i=0; i<len_conv_result; i++) {
    int16_t acc = 0;
    for (j=3; j<(LEN_GAUSS_FILTER*SAMPLE_PER_SYMBOL-4); j++) {
      acc = acc + gauss_coef_int8[(LEN_GAUSS_FILTER*SAMPLE_PER_SYMBOL)-j-1]*tmp_phy_bit_over_sampling_int8[i+j];
    }

    tmp = (tmp + acc)&1023;
    sample[(i+1)*2 + 0] = cos_table_int8[tmp];
    sample[(i+1)*2 + 1] = sin_table_int8[tmp];
  }

La muestra[0] y la muestra[1] al principio solo están calculando el valor inicial, que no es tan importante. Es importante comenzar con el primer bucle for.

El primer bucle for (externo) está haciendo modulación, el segundo bucle for (interior) solo está haciendo filtrado gaussiano

En realidad, no me importa mucho el filtrado gaussiano, porque la experiencia previa de demodulación es que no me importa este filtro gaussiano, y puedo usar directamente el algoritmo de demodulación FSK (FM) para obtener los datos correctos. Mírelo, significa que la siguiente fórmula debe usarse para aumentar acc

acc = acc + tmp_phy_bit_over_sampling_int8[i];

Ahora, hacer el filtrado gaussiano es multiplicar un coeficiente frente al bit físico y luego hacer un pequeño bucle. Para simplificar, podemos establecer el coeficiente en 1 y eliminar el pequeño bucle interior. 

Acc se inicializa a 0 cada vez antes de entrar en el pequeño bucle interior.Si no hay un pequeño bucle interior, el valor de acc es igual a tmp_phy_bit_over_sampling_int8, es decir, acc corresponde directamente a 0 y 1 de phy bit.

    tmp = (tmp + acc)&1023;
    sample[(i+1)*2 + 0] = cos_table_int8[tmp];
    sample[(i+1)*2 + 1] = sin_table_int8[tmp];

Combinado con el código anterior para mirar, básicamente puede entender.

tmp es en realidad la fase de la onda sinusoidal, y sample es el punto de muestreo final. Dado que son datos iq, los dos adyacentes son cos y el otro es sin. Solo observamos cos, y su parámetro es tmp, y tmp se acumula constantemente acc, acc es la diferencia de fase. El procesamiento de estos puntos de muestreo se realiza a intervalos de tiempo fijos. Entonces acc es la diferencia de fase por unidad de tiempo, que es la frecuencia.

Si el bit phy es 1, acc = 1, para que la tabla cos pueda generar una onda sinusoidal de alta frecuencia.

Y si el bit phy es 0, entonces acc = 0, entonces la salida de frecuencia de onda sinusoidal de la tabla cos es 0. Si usa el dominio de la frecuencia para comprender las señales correspondientes a estos dos bits físicos, uno está a 0 Hz en fft y el otro está más a la derecha. Este es el principio de 2FSK.

De esta manera, todos estos proyectos han sido leídos. Si está más interesado o no tiene un conocimiento profundo de la modulación FSK, también puede consultar el código correspondiente de gnuradio. Estoy acostumbrado a mirar la versión 3.7 de gnuradio, que contiene gfsk.py, si eres 3.9, es posible que no lo tengas.

Aquí está el link para esto:

gnuradio/gfsk.py en maint-3.7 · gnuradio/gnuradio · GitHub

Haga clic en el código, tiene partes de modulación y demodulación. Si solo miras la parte de la modulación, también verás el filtro gaussiano filter.firdes.gaussian implementado en gnuradio, y frequency_modulator_fc, que es el código central de FSK.

Busque esta función, hay un archivo frequency_modulator_fc_impl.cc en gr-analog/lib/ gnuradio/frequency_modulator_fc_impl.cc en maint-3.7 gnuradio/gnuradio GitHub

int frequency_modulator_fc_impl::work(int noutput_items,
                                      gr_vector_const_void_star& input_items,
                                      gr_vector_void_star& output_items)
{
    const float* in = (const float*)input_items[0];
    gr_complex* out = (gr_complex*)output_items[0];

    for (int i = 0; i < noutput_items; i++) {
        d_phase = d_phase + d_sensitivity * in[i];

// place phase in [-pi, +pi[
#define F_PI ((float)(M_PI))
        d_phase = std::fmod(d_phase + F_PI, 2.0f * F_PI) - F_PI;

        float oi, oq;

        int32_t angle = gr::fxpt::float_to_fixed(d_phase);
        gr::fxpt::sincos(angle, &oq, &oi);
        out[i] = gr_complex(oi, oq);
    }

    return noutput_items;
}

Arriba está su implementación principal. in es entrada, que es equivalente a phy bit, y out es salida, que es muestra. Está utilizando el valor de in para determinar d_phase. Correspondiente a la fase (la d al comienzo de la variable solo significa el tipo de dato doble, no el significado de diferencial), si in es 1, este valor aumentará más rápido, si in es 0, no aumentará (fase rápida el crecimiento en realidad significa alta frecuencia)). Luego convierta la fase, primero limite la amplitud al período de -pi ~ pi, y luego conviértalo a un ángulo de forma angular, luego use el ángulo como parámetro y use la función sincos para generar directamente una onda sinusoidal compleja.

El principio de implementación es similar al de BTLE. El bit phy de entrada es 1 y 0, lo que controla la diferencia de fase (es decir, la frecuencia). A continuación, utilice la fase como parámetro para la tabla de búsqueda de ondas sinusoidales LUT. Luego, simplemente emita el valor de muestra de onda sinusoidal como muestra.

Después de leer el principio de modulación, puede volver a btle_tx.c para ver cómo se envía la muestra modulada desde la API de hackrf.

La muestra está en pkt->phy_sample, y este paquete fue pasado originalmente por packages[i] en la función parse_input, que proviene de los parámetros de la línea de comando. En la función parse_input, num_repeat es el número después de r, que representa el número de operaciones repetidas. num_packet representa la cantidad de paquetes que se ejecutan a la vez y, por lo general, solo ejecuto 1 paquete a la vez.

Esto significa que pkt->phy_sample y packages[i].phy_sample son lo mismo. Después de llamar a parse_input en la función principal, se llamará a la función tx_one_buf.

for (j=0; j<num_repeat; j++ ) {
    for (i=0; i<num_packet; i++) {
      time_pre_pkt = time_current_pkt;
      gettimeofday(&time_current_pkt, NULL);

      if ( tx_one_buf(packets[i].phy_sample, 2*packets[i].num_phy_sample, packets[i].channel_number) == -1 ){
        close_board();
        goto main_out;
      }

Es esta función tx_one_buf la que usa paquetes[i].phy_sample como parámetro. También hay dos capas de bucles fuera.Como num_packet es 1 para mí, solo el bucle num_repeat está llamando a tx_one_buf de acuerdo con el número después del parámetro r.

Encuentra tx_one_buf, en realidad hay 2, uno para bladerf, veamos el de hackrf a continuación.

Copiará el primer parámetro de entrada buf a tx_buf. En otras palabras, pkt->phy_sample se asigna a tx_buf. Y este tx_buf finalmente se enviará al hardware hackrf en la función de devolución de llamada tx_callback.

inline int tx_one_buf(char *buf, int length, int channel_number) {
  int result;

  set_freq_by_channel_number(channel_number);

  //tx_buf = tx_zeros;
  //tx_len = HACKRF_USB_BUF_SIZE-NUM_PRE_SEND_DATA;
  tx_buf = buf;
  tx_len = length;

  // open the board-----------------------------------------
  if (open_board() == -1) {
    printf("tx_one_buf: open_board() failed\n");
    return(-1);
  }

  // first round TX---------------------------------
  stop_tx = 0;

  result = hackrf_start_tx(device, tx_callback, NULL);
  if( result != HACKRF_SUCCESS ) {
    printf("tx_one_buf: hackrf_start_tx() failed: %s (%d)\n", hackrf_error_name(result), result);
    return(-1);
  }

  while( (hackrf_is_streaming(device) == HACKRF_TRUE) &&
      (do_exit == false) )
  {
    if (stop_tx>=9) {
      break;
    }
  }

  if (do_exit)
  {
    printf("\ntx_one_buf: Exiting...\n");
    return(-1);
  }
 if (close_board() == -1) {
    printf("tx_one_buf: close_board() failed\n");
    return(-1);
  }

  do_exit = false;

  return(0);
}

Si continúa observando la segunda mitad del código anterior, encontrará que primero debe habilitar la función de devolución de llamada de configuración de hardware hackrf en tx_one_buf y luego apagar la placa de hardware.

Este método no es eficiente porque el número de num_repeat es relativamente grande, por ejemplo, cuando es 50 veces, es necesario llamar tx_one_buf 50 veces y cambiar la placa de hardware 50 veces. Esto llevará un largo tiempo. Es completamente posible inicializar la placa al principio y luego dejar que la función de devolución de llamada siga enviando los mismos puntos de muestreo y esperar 50 devoluciones de llamada. Finalmente, cierre el tablero después de enviar.

Por lo tanto, el intervalo de lanzamiento de este proyecto tiene espacio para la optimización.

Supongo que te gusta

Origin blog.csdn.net/shukebeta008/article/details/122914773
Recomendado
Clasificación