[经验分享] 【基于人脸识别的自动打卡健走计时系统】节点通信——LoRa技术

alanlan86   2022-9-17 18:25 楼主

之前提及的LoRa调试过程分享,一直由于工作出差耽误,没来得及写帖子分享一下,今天它终于来啦~~~~

 

调试任务

  • 采用nRF52832(蓝牙SoC)作为主控,通过SPI驱动Semtech公司的SX1262(兼容的有LLCC68芯片),实现2个板子之间点对点的LoRa通信
  • 至于为何使用52832呢?是考虑后面在蓝牙芯片上还有其他功能。。。此处,请允许我先卖个关子先~~
  • 最后用nRF52832透过UART和MAIXBIT K210板子的UART进行连接通信

开发板.png

 

fc38fafc3accfdbc27016fb8fd2aa64.jpg   

芯片规格书

  • 先通过semtech原厂,查询到LLCC68的规格书

 

image.png  

  • 关于nRF52832的开发资料就非常多了,可以关注网络资料(例如,https://www.cnblogs.com/iini/ 这个博客有很多入门的文章)
  • 到官方的链接下载sdk:https://developer.nordicsemi.com/
  • 我调试时候采用的SDK是基于V17.1.0版本的SDK代码包

引脚连接情况

  • 明确SX1262/LLCC68的电路,并将起连接到主控的SPI、DIO引脚都找出来
  • nRF52832主控采用了一块模组,可以找到其引脚连接关系

引脚连接2.PNG

  • 引脚连接主要是SPI、RESET和DIO


引脚连接3.PNG

 

驱动接口部分代码

代码部分,

  • 需要先在semtech的GitHub仓库下载SX126X的驱动代码:https://github.com/Lora-net/sx126x_driver
  • 或者选择下载LLCC68的驱动代码:https://github.com/Lora-net/llcc68_driver
  • 接着需要调试SPI部分驱动,由于nRF52832的SDK里面在nrfx SPI驱动,有比较多的API是现成的,所以无需关注寄存器,调用API即可。
#include "board.h"
#include "spi-board.h"


#define SPI_INSTANCE           0 /**< SPI instance index. */
static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);  /**< SPI instance. */

void SpiInit( Spi_t *obj, PinNames mosi, PinNames miso, PinNames sclk, PinNames nss )
{
   //SPI Gpio_t
   obj->Mosi.mode = PIN_OUTPUT;
   obj->Mosi.pull = PIN_PULL_UP;
   obj->Mosi.pin  = mosi;
   
   obj->Miso.mode = PIN_INPUT;
   obj->Miso.pull = PIN_PULL_UP;
   obj->Miso.pin  = miso;
  
   obj->Sclk.mode = PIN_OUTPUT;
   obj->Sclk.pull = PIN_PULL_UP;
   obj->Sclk.pin  = sclk;
  
   obj->Nss.mode = PIN_OUTPUT;
   obj->Nss.pull = PIN_PULL_UP;
   obj->Nss.pin = nss;
  
   //spi interface config
   obj->config.irq_priority = SPI_DEFAULT_CONFIG_IRQ_PRIORITY;
   obj->config.orc          = 0xFF;
   obj->config.frequency    = NRF_DRV_SPI_FREQ_1M;
   obj->config.mode         = NRF_DRV_SPI_MODE_0;
   obj->config.bit_order    = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST;
  
   obj->config.mosi_pin = mosi;
   obj->config.miso_pin = miso;
   obj->config.sck_pin  = sclk;
#if (HW_SPI == 1)  
   obj->config.ss_pin   = nss;
#else
   obj->config.ss_pin   = NC;
#endif
  
#if (SPI_INSTANCE == 0)  
   obj->spi = NRF_SPI0;
#elif (SPI_INSTANCE == 1)
   obj->spi = NRF_SPI1;
#elif (SPI_INSTANCE == 2)
   obj->spi = NRF_SPI2;
#else
  #error "no spi interface"
#endif
   
   APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &obj->config, NULL, NULL));
	 
   //sw gpio => nss config
   GpioInit( &obj->Nss, obj->Nss.pin, obj->Nss.mode, PIN_PUSH_PULL, obj->Nss.pull, 1 );

	 nrf_spi_int_disable(obj->spi, NRF_SPI_INT_READY_MASK);
   
	 nrf_spi_enable(obj->spi);
}

void SpiDeInit( Spi_t *obj )
{
    nrf_drv_spi_uninit(&spi);
  
    GpioInit( &obj->Mosi, obj->Mosi.pin, PIN_ANALOGIC, PIN_PUSH_PULL, PIN_NO_PULL, 0 );
    GpioInit( &obj->Miso, obj->Miso.pin, PIN_ANALOGIC, PIN_PUSH_PULL, PIN_NO_PULL, 0 );
    GpioInit( &obj->Sclk, obj->Sclk.pin, PIN_ANALOGIC, PIN_PUSH_PULL, PIN_NO_PULL, 0 );
    GpioInit( &obj->Nss,  obj->Nss.pin,  PIN_ANALOGIC,  PIN_PUSH_PULL, PIN_PULL_UP, 1 );
}


uint16_t SpiInOut( Spi_t *obj, uint16_t outData )
{
    uint8_t rxData = 0;

    nrf_spi_event_clear(obj->spi, NRF_SPI_EVENT_READY);

    nrf_spi_txd_set(obj->spi, outData);

	  while (!nrf_spi_event_check(obj->spi, NRF_SPI_EVENT_READY)) {}
			
		nrf_spi_event_clear(obj->spi, NRF_SPI_EVENT_READY);
       
    rxData = nrf_spi_rxd_get(obj->spi);
      
    return( rxData );
}
  • GPIO中断驱动部分,实现IO控制和IO中断配置
#include "board.h"
#include "gpio-board.h"

static GpioIrqHandler *GpioIrq[31];

void GpioMcuInit( Gpio_t *obj, PinNames pin, PinModes mode, PinConfigs config, PinTypes type, uint32_t value )
{

    obj->pin = pin;

    if( pin == NC )
    {
        return;
    }

    obj->mode = mode;
    obj->pull = type;

    if( mode == PIN_INPUT ) //gpio input
    {
        if( config == PIN_PUSH_PULL )
        {
            if (type == PIN_PULL_UP)
            {
              nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_PULLUP);
            } else if (type == PIN_PULL_DOWN)
            {
              nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_PULLDOWN);
            } else {
              nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL);
            }
        }
        else
        {
            nrf_gpio_cfg_default(pin);
        }
    }
    else if( mode == PIN_ANALOGIC ) //adc
    {
        nrf_gpio_cfg_default(pin);
    }
    else if( mode == PIN_OUTPUT ) //gpio output
    {
        nrf_gpio_cfg_output(pin);
      
        GpioMcuWrite( obj, value );
    }
    
    return;
}

void HAL_Gpio_Interrupt_Handle(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
  if (pin > 31)
  {
    return;
  }
  
  if (GpioIrq[pin] != NULL)
  {
    GpioIrq[pin]();
  }
}

void GpioMcuSetInterrupt( Gpio_t *obj, IrqModes irqMode, IrqPriorities irqPriority, GpioIrqHandler *irqHandler )
{

    uint32_t priority = 0;

    if( irqHandler == NULL )
    {
        return;
    }
 
    ret_code_t err_code;
		
		if (!nrf_drv_gpiote_is_init())
    {
				err_code = nrf_drv_gpiote_init();
				APP_ERROR_CHECK(err_code);
		}
    
    if( irqMode == IRQ_RISING_EDGE )
    {
      nrf_drv_gpiote_in_config_t rising_config = GPIOTE_CONFIG_IN_SENSE_LOTOHI(false);
      if (obj->pull == PIN_PULL_UP)
      {
        rising_config.pull = NRF_GPIO_PIN_PULLUP;
      } else if (obj->pull == PIN_PULL_DOWN) {
        rising_config.pull = NRF_GPIO_PIN_PULLDOWN;
      } else {
        rising_config.pull = NRF_GPIO_PIN_NOPULL;
      }
      
      err_code = nrf_drv_gpiote_in_init(obj->pin, &rising_config, HAL_Gpio_Interrupt_Handle);
      APP_ERROR_CHECK(err_code);
    }
    else if( irqMode == IRQ_FALLING_EDGE )
    {
      nrf_drv_gpiote_in_config_t falling_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(false);
      if (obj->pull == PIN_PULL_UP)
      {
        falling_config.pull = NRF_GPIO_PIN_PULLUP;
      } else if (obj->pull == PIN_PULL_DOWN) {
        falling_config.pull = NRF_GPIO_PIN_PULLDOWN;
      } else {
        falling_config.pull = NRF_GPIO_PIN_NOPULL;
      }
      
      err_code = nrf_drv_gpiote_in_init(obj->pin, &falling_config, HAL_Gpio_Interrupt_Handle);
      APP_ERROR_CHECK(err_code);
    }
    else
    {
      nrf_drv_gpiote_in_config_t toggle_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(false);
      if (obj->pull == PIN_PULL_UP)
      {
        toggle_config.pull = NRF_GPIO_PIN_PULLUP;
      } else if (obj->pull == PIN_PULL_DOWN) {
        toggle_config.pull = NRF_GPIO_PIN_PULLDOWN;
      } else {
        toggle_config.pull = NRF_GPIO_PIN_NOPULL;
      }
      
      err_code = nrf_drv_gpiote_in_init(obj->pin, &toggle_config, HAL_Gpio_Interrupt_Handle);
      APP_ERROR_CHECK(err_code);
    }

    switch( irqPriority )
    {
    case IRQ_VERY_LOW_PRIORITY:
    case IRQ_LOW_PRIORITY:
        priority = 3;
        break;
    case IRQ_MEDIUM_PRIORITY:
        priority = 2;
        break;
    case IRQ_HIGH_PRIORITY:
        priority = 1;
        break;
    case IRQ_VERY_HIGH_PRIORITY:
    default:
        priority = 0;
        break;
    }

    GpioIrq[obj->pin] = irqHandler;

    nrf_drv_gpiote_in_event_enable(obj->pin, true);
}

void GpioMcuRemoveInterrupt( Gpio_t *obj )
{
   nrf_drv_gpiote_in_event_enable(obj->pin, false);
}

void GpioMcuWrite( Gpio_t *obj, uint32_t value )
{
    if( obj == NULL )
    {
        assert_param( FAIL );
    }
    
    // Check if pin is not connected
    if( obj->pin == NC )
    {
        return;
    }
    
    nrf_gpio_pin_write(obj->pin, value);
}

void GpioMcuToggle( Gpio_t *obj )
{
    if( obj == NULL )
    {
        assert_param( FAIL );
    }

    // Check if pin is not connected
    if( obj->pin == NC )
    {
        return;
    }

    nrf_gpio_pin_toggle(obj->pin);
}

uint32_t GpioMcuRead( Gpio_t *obj )
{
		uint8_t pinstate = 0;
	
    if( obj == NULL )
    {
        assert_param( FAIL );
    }
    
    // Check if pin is not connected
    if( obj->pin == NC )
    {
        return 0;
    }
    
#if 1		
		if(nrf_gpio_pin_dir_get(obj->pin) == NRF_GPIO_PIN_DIR_INPUT)
		{
				pinstate = nrf_gpio_pin_read(obj->pin);
		}
		else
		{
				pinstate = nrf_gpio_pin_out_read(obj->pin);		
		}
		return pinstate;
		
#else			
    return nrf_gpio_pin_read(obj->pin);
#endif	
}
  • 另外,由于semtech写的驱动部分用到定时器,针对收发定时进行调用,引出需要实现对应timer的驱动
#include <math.h>
#include "board.h"
#include "timer-board.h"


const nrf_drv_rtc_t rtc = NRF_DRV_RTC_INSTANCE(2); /**< Declaring an instance of nrf_drv_rtc for RTC2. */

void TIMER_IRQHandler(nrf_drv_rtc_int_type_t int_type);

/*!
 * Hardware Time base in ms
 */
#define HW_TIMER_TIME_BASE 1 //ms

/*!
 * Hardware Timer tick counter
 */
volatile TimerTime_t TimerTickCounter = 1;

/*!
 * Saved value of the Tick counter at the start of the next event
 */
static TimerTime_t TimerTickCounterContext = 0;

/*!
 * Value trigging the IRQ
 */
volatile TimerTime_t TimeoutCntValue = 0;

/*!
 * Increment the Hardware Timer tick counter
 */
void TimerIncrementTickCounter(void);

/*!
 * Counter used for the Delay operations
 */
volatile uint32_t TimerDelayCounter = 0;

/*!
 * Retunr the value of the counter used for a Delay
 */
uint32_t TimerHwGetDelayValue(void);

/*!
 * Increment the value of TimerDelayCounter
 */
void TimerIncrementDelayCounter(void);

void TimerHwInit(void)
{
	  /* TIMER clock enable */
    //LFCLK Initial
  
	  /* TIMER interrupt Init */
    uint32_t err_code;

    //Initialize RTC instance
    nrf_drv_rtc_config_t config = NRF_DRV_RTC_DEFAULT_CONFIG;
#if 1  
    config.prescaler = 31;
#else
    config.prescaler = 0;
#endif
  
    err_code = nrf_drv_rtc_init(&rtc, &config, TIMER_IRQHandler);
    APP_ERROR_CHECK(err_code);
  
#if 1
    //Enable tick event & interrupt
    nrf_drv_rtc_tick_enable(&rtc, true);
#else

    //Set compare channel to trigger interrupt after COMPARE_COUNTERTIME seconds
    err_code = nrf_drv_rtc_cc_set(&rtc, 0,  RTC_DEFAULT_CONFIG_FREQUENCY/1000, true);
    APP_ERROR_CHECK(err_code);
#endif    
}

void TimerHwDeInit(void)
{
    /* TIMER clock enable */
    //Power uninit RTC instance
    nrf_drv_rtc_uninit(&rtc);
}

uint32_t TimerHwGetMinimumTimeout(void)
{
    return (ceil(2 * HW_TIMER_TIME_BASE));
}

void TimerHwStart(uint32_t val)
{
    TimerTickCounterContext = TimerHwGetTimerValue();

    if (val <= HW_TIMER_TIME_BASE + 1)
    {
        TimeoutCntValue = TimerTickCounterContext + 1;
    }
    else
    {
        TimeoutCntValue = TimerTickCounterContext + ((val - 1) / HW_TIMER_TIME_BASE);
    }
    
    //Power on RTC instance
    nrf_drv_rtc_enable(&rtc);
}

void TimerHwStop(void)
{
   //Power on RTC instance
    nrf_drv_rtc_disable(&rtc);
}

TimerTime_t TimerHwGetElapsedTime(void)
{
    return (((TimerHwGetTimerValue() - TimerTickCounterContext) + 1) * HW_TIMER_TIME_BASE);
}

TimerTime_t TimerHwGetTimerValue(void)
{
    TimerTime_t val = 0;

    __disable_irq();

    val = TimerTickCounter;

    __enable_irq();

    return (val);
}

TimerTime_t TimerHwComputeElapsedTime(TimerTime_t eventInTime)
{
    TimerTime_t elapsedTime = 0;

    // Needed at boot, cannot compute with 0 or elapsed time will be equal to current time
    if( eventInTime == 0 )
    {
        return 0;
    }

    elapsedTime = TimerHwGetTimerValue( );

    if( elapsedTime < eventInTime )
    { // roll over of the counter
        return( elapsedTime + ( 0xFFFFFFFF - eventInTime ) );
    }
    else
    {
        return( elapsedTime - eventInTime );
    }    
}

TimerTime_t TimerHwGetTime(void)
{

    return TimerHwGetTimerValue() * HW_TIMER_TIME_BASE;
}

uint32_t TimerHwGetDelayValue(void)
{
    uint32_t val = 0;

    __disable_irq();

    val = TimerDelayCounter;

    __enable_irq();

    return (val);
}

void TimerIncrementTickCounter(void)
{
    __disable_irq();

    TimerTickCounter++;

    __enable_irq();
}

void TimerIncrementDelayCounter(void)
{
    __disable_irq();

    TimerDelayCounter++;

    __enable_irq();
}

/*!
 * Timer IRQ handler
 */

void TIMER_IRQHandler(nrf_drv_rtc_int_type_t int_type)
{  
    if (int_type == NRF_DRV_RTC_INT_TICK)
    {
      TimerIncrementTickCounter();

      if (TimerTickCounter == TimeoutCntValue)
      {
          TimerIrqHandler();
      }
    }
    else if (int_type == NRF_DRV_RTC_INT_COMPARE0)
    {

      TimerIncrementTickCounter();

      if (TimerTickCounter == TimeoutCntValue)
      {
          TimerIrqHandler();
      }
    }
}

主任务部分

#include "task_lora.h"
#include "board.h"

void lora_rx_handler(uint8_t *payload)
{
		switch(payload[4])
		{
				case 'r':
						BoardLed_Flashing(RGBLED_RED);
						Radio.Send("red flashing", sizeof("red flashing"));
						//Radio.Send("led_red", sizeof("led_red"));
				break;

				case 'g':
						BoardLed_Flashing(RGBLED_GREEN);
						Radio.Send("green flashing", sizeof("green flashing"));
				break;
				
				case 'b':
						BoardLed_Flashing(RGBLED_BLUE);
						Radio.Send("blue flashing", sizeof("blue flashing"));
				break;
				
				default:
					break;
		}
}


void OnTxDone( void )
{
		printf("function: %s", __FUNCTION__);
		NRF_LOG_DEBUG("function: %s", __FUNCTION__);
		Radio.Standby();
		Radio.Rx(0xFFFFFF);
}

void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
{
	  printf("function: %s", __FUNCTION__);
		NRF_LOG_DEBUG("function: %s", __FUNCTION__);
		printf("receive:%s, rssi:%d, snr%d\r\n", payload, rssi, snr);
		lora_rx_handler(payload);
//		Radio.Standby();
//		Radio.Rx(0xFFFFFF);
}

void OnTxTimeout( void )
{
	  printf("function: %s", __FUNCTION__);
		NRF_LOG_INFO("function: %s", __FUNCTION__);
		Radio.Standby();
		Radio.Rx(0xFFFFFF);
}

void OnRxTimeout( void )
{
	  printf("function: %s", __FUNCTION__);
		NRF_LOG_INFO("function: %s", __FUNCTION__);
		Radio.Standby();
		Radio.Rx(0xFFFFFF);
}

void OnRxError( void )
{
	  printf("function: %s", __FUNCTION__);
		NRF_LOG_INFO("function: %s", __FUNCTION__);
		Radio.Standby();
		Radio.Rx(0xFFFFFF);
}


static RadioEvents_t RadioEvents;

void lora_init(void)
{
		RadioEvents.TxDone = OnTxDone;
    RadioEvents.RxDone = OnRxDone;
    RadioEvents.TxTimeout = OnTxTimeout;
    RadioEvents.RxTimeout = OnRxTimeout;
    RadioEvents.RxError = OnRxError;
	
		Radio.Init( &RadioEvents );
		
		Radio.SetMaxPayloadLength(MODEM_LORA, MAX_PAYLOADLENGTH);
	
    Radio.SetTxConfig( MODEM_LORA, LORA_TX_POWER, 0, LORA_BW, LORA_SF, LORA_CR, LORA_PREAMBLE, 
											LORA_FIX_LENGTH_PAYLOAD_ON, true, 0, 0, LORA_IQ_INVERSION_ON, TX_TIMEOUT_VALUE );
	
    Radio.SetRxConfig( MODEM_LORA, LORA_BW, LORA_SF,
                       LORA_CR, 0, LORA_PREAMBLE,
                       LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
                       0, true, 0, 0, LORA_IQ_INVERSION_ON, true );

    Radio.SetChannel( LORA_RX_FREQ );	

		Radio.Rx(0xFFFFFF);
}

void lora_process(void)
{
		//if receive from uart
		
		//if DIO1 irq
		Radio.IrqProcess();
}


/**[url=home.php?mod=space&uid=159083]@brief[/url] Application main function.
 */
int main(void)
{
    // Initialize.
    uart_init();
    log_init();
    timers_init();
    power_management_init();
    ble_stack_init();
    gap_params_init();
    gatt_init();
    services_init();
    advertising_init();
    conn_params_init();

    // Start execution.
    printf("\r\nBLE-LoRa Init...\r\n");
		NRF_LOG_INFO("Debug logging for UART over RTT started.");

#if BLE_ENABLE
		advertising_start();
#endif
	
		board_lora_init();
	
    // Enter main loop.
    for (;;)
    {
				lora_process();
					
        idle_state_handle();
    }
}

 

回复评论 (2)

接着需要调试SPI部分驱动,由于nRF52832的SDK里面在nrfx SPI驱动。

这个芯片,现在用得多吗?

 

点赞  2022-9-17 22:58
引用: lugl4313820 发表于 2022-9-17 22:58 接着需要调试SPI部分驱动,由于nRF52832的SDK里面在nrfx SPI驱动。 这个芯片,现在用得多吗?   ...

BLE应用里面挺常见的。。。。但是芯片涨价/缺货这一波也影响挺大的。。。。

点赞  2022-9-20 15:32
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复