LoRA Connect 基本收发应用

——LLCC68,基本编程方法

作者:fujian

基本电路

使用 Ra-01SC 模块+AVR单片机 搭建基本应用电路,实现串口(RS422)透传功能。

(对于Ra-01SC 模块,淘宝商家给的示例代码,是用结构体+指针模仿C++的类对象来实现,太绕了,不直观,比较难理解。)

原本想用两块Lora模块实现全双工,但由于两个模块在电路板上相距不够远,会互相产生干扰,所以还是半双工吧,只是一块用于发送,另一块用于接收。

Lora 模块 对接 AVR 硬件SPI口,AVR通过串口转成RS422,也预留了RS485接口。 为啥不用STM32,而用AVR呢,因为手头正好还有好几块这个芯片。

用Kicad7.0 画了板

嘉立创打样,自己焊了两块板。

LLCC68 官方驱动:GitHub - Lora-net/llcc68_driver: Driver for LLCC68 radio

驱动说明原文

llcc68.c 驱动函数,包含一些说明。

llcc68.h 是llcc68.c的头文件,一些宏定义,枚举类型,驱动函数声明等。

llcc68_regs.h 芯片所有的寄存器声明。

llcc68_hal.h 硬件层函数的头文件,里面声明好了需要我们编写的硬件层函数。

我们需要新建个llcc68_hal.c,把  llcc68_hal_reset(),llcc68_hal_wakeup(),llcc68_hal_write(),llcc68_hal_read() 4个硬件层函数完成。

llcc68_hal_reset() —— llcc68硬件复位函数

llcc68_hal_wakeup() —— llcc68唤醒函数

llcc68_hal_write() ——MCU SPI写llcc68函数

llcc68_hal_read()——MCU SPI读llcc68函数

完成以上几个硬件层函数后,便可以直接调用llcc68.c里的函数对llcc68进行操作。

我把IO口初始化也写在llcc68_hal.c里了,

以下是我的llcc68_hal.h和llcc68_hal.c代码,水平有限~ 多多包含~

#ifndef LLCC68_HAL_H
#define LLCC68_HAL_H

#ifdef __cplusplus
extern "C" {
#endif

/*
 * -----------------------------------------------------------------------------
 * --- DEPENDENCIES ------------------------------------------------------------
 */

#include <stdint.h>
#include <stdbool.h>
#include <avr/io.h>

/*
 * -----------------------------------------------------------------------------
 * --- PUBLIC MACROS -----------------------------------------------------------
 */

/*
 * -----------------------------------------------------------------------------
 * --- PUBLIC CONSTANTS --------------------------------------------------------
 */

/**
 * @brief Write this to SPI bus while reading data, or as a dummy/placeholder
 */
#define LLCC68_NOP ( 0x00 )

#define SET_LLCC68_0_nRESET() PORTA|=(1<<PORTA0)
#define CLR_LLCC68_0_nRESET() PORTA&=~(1<<PORTA0)

#define SET_LLCC68_1_nRESET() PORTA|=(1<<PORTA2)
#define CLR_LLCC68_1_nRESET() PORTA&=~(1<<PORTA2)

#define SET_LLCC68_0_NSS() PORTB|=(1<<PORTB4)
#define CLR_LLCC68_0_NSS() PORTB&=~(1<<PORTB4)

#define SET_LLCC68_1_NSS() PORTA|=(1<<PORTA4)
#define CLR_LLCC68_1_NSS() PORTA&=~(1<<PORTA4)

#define LLCC68_0_command_busy PINA&0x02
#define LLCC68_1_command_busy PINA&0x08


/*
 * -----------------------------------------------------------------------------
 * --- PUBLIC TYPES ------------------------------------------------------------
 */

typedef enum llcc68_hal_status_e
{
    LLCC68_HAL_STATUS_OK    = 0,
    LLCC68_HAL_STATUS_ERROR = 3,
} llcc68_hal_status_t;


/*
 * -----------------------------------------------------------------------------
 * --- PUBLIC FUNCTIONS PROTOTYPES ---------------------------------------------
 */

llcc68_hal_status_t llcc68_interface_init();


/**
 * Radio data transfer - write
 *
 * @remark Shall be implemented by the user
 *
 * @param [in] context          Radio implementation parameters
 * @param [in] command          Pointer to the buffer to be transmitted
 * @param [in] command_length   Buffer size to be transmitted
 * @param [in] data             Pointer to the buffer to be transmitted
 * @param [in] data_length      Buffer size to be transmitted
 *
 * @returns Operation status
 */
llcc68_hal_status_t llcc68_hal_write( const void* context, const uint8_t* command, const uint16_t command_length,
                                      const uint8_t* data, const uint16_t data_length );

/**
 * Radio data transfer - read
 *
 * @remark Shall be implemented by the user
 *
 * @param [in] context          Radio implementation parameters
 * @param [in] command          Pointer to the buffer to be transmitted
 * @param [in] command_length   Buffer size to be transmitted
 * @param [in] data             Pointer to the buffer to be received
 * @param [in] data_length      Buffer size to be received
 *
 * @returns Operation status
 */
llcc68_hal_status_t llcc68_hal_read( const void* context, const uint8_t* command, const uint16_t command_length,
                                     uint8_t* data, const uint16_t data_length );

/**
 * Reset the radio
 *
 * @remark Shall be implemented by the user
 *
 * @param [in] context Radio implementation parameters
 *
 * @returns Operation status
 */
llcc68_hal_status_t llcc68_hal_reset( const void* context );

/**
 * Wake the radio up.
 *
 * @remark Shall be implemented by the user
 *
 * @param [in] context Radio implementation parameters
 *
 * @returns Operation status
 */
llcc68_hal_status_t llcc68_hal_wakeup( const void* context );

#ifdef __cplusplus
}
#endif

#endif  // LLCC68_HAL_H

/* --- EOF ------------------------------------------------------------------ */

#include "llcc68_hal.h"
#include <util/delay.h>
#include "avr_spi.h"

llcc68_hal_status_t llcc68_interface_init()       //IO口初始化
{
	DDRA=0x15;
	PORTA=0x1F;
	DDRB=0xB0;
	PORTB=0x10;
	DDRC=0x30;
	PORTC=0x20;
	DDRD=0x00;
	PORTD=0xff;
	
	return LLCC68_HAL_STATUS_OK;	
	
}

llcc68_hal_status_t llcc68_hal_reset( const void* context )  //LLCC68 硬件复位函数
{
	uint8_t which_one_llcc68=0;
	const uint8_t *p;
	
	p=context;
	which_one_llcc68 = *p;
	
	if (which_one_llcc68==0) {CLR_LLCC68_0_nRESET();}
	else {CLR_LLCC68_1_nRESET();}	
	_delay_us(150);
	SET_LLCC68_0_nRESET();
	SET_LLCC68_1_nRESET();
	_delay_us(3500);
	return LLCC68_HAL_STATUS_OK;
}


llcc68_hal_status_t llcc68_hal_write( const void* context, const uint8_t* command, const uint16_t command_length,
const uint8_t* data, const uint16_t data_length )    // 写LLCC68函数
{
		uint8_t i=0;
		uint16_t counter=0;
		uint8_t which_one_llcc68=0;
		const uint8_t *p;
			
		p=context;
		which_one_llcc68 = *p;
		
		
		if(which_one_llcc68==0)
		{
		   while (LLCC68_0_command_busy)
		  {
			  counter++;
			  if (counter>2000)
			 {
		       return LLCC68_HAL_STATUS_ERROR;
			 }
		   }	
		}
		else
		{
		   while (LLCC68_1_command_busy)
		   {
			   counter++;
			   if (counter>2000)
			   {
				   return LLCC68_HAL_STATUS_ERROR;
			   }
		   }			
		}
		
		if (which_one_llcc68==0){CLR_LLCC68_0_NSS();}
		else {CLR_LLCC68_1_NSS();}
		
		_delay_us(10);
		for (i=0;i<command_length;i++)
		{
			SPI_Write_Read_Byte(command[i]);
		}
		
		for (i=0;i<data_length;i++)
		{
			SPI_Write_Read_Byte(data[i]);
		}
		if (which_one_llcc68==0){SET_LLCC68_0_NSS();}
		else {SET_LLCC68_1_NSS();}
		
		return LLCC68_HAL_STATUS_OK;
}


llcc68_hal_status_t llcc68_hal_read( const void* context, const uint8_t* command, const uint16_t command_length,
uint8_t* data, const uint16_t data_length )  //读取LLCC68
{
	
	uint8_t i=0;
	uint16_t counter=0;
		uint8_t which_one_llcc68=0;
		const uint8_t *p;
		
		p=context;
		which_one_llcc68 = *p;
		
		
		if(which_one_llcc68==0)
		{
			while (LLCC68_0_command_busy)
			{
				counter++;
				if (counter>2000)
				{
					return LLCC68_HAL_STATUS_ERROR;
				}
			}
		}
		else
		{
			while (LLCC68_1_command_busy)
			{
				counter++;
				if (counter>2000)
				{
					return LLCC68_HAL_STATUS_ERROR;
				}
			}
		}
		
		if (which_one_llcc68==0){CLR_LLCC68_0_NSS();}
		else {CLR_LLCC68_1_NSS();}
	_delay_us(10);
	for (i=0;i<command_length;i++)
	{
		SPI_Write_Read_Byte(command[i]);
	}
	
	for (i=0;i<data_length;i++)
	{
		data[i]=SPI_Write_Read_Byte(0);
	}
		if (which_one_llcc68==0){SET_LLCC68_0_NSS();}
		else {SET_LLCC68_1_NSS();}
	
	return LLCC68_HAL_STATUS_OK;	
	
}


llcc68_hal_status_t llcc68_hal_wakeup( const void* context ) //唤醒LLCC68
{
	uint16_t counter=0;
    uint8_t which_one_llcc68=0;
	const uint8_t *p;
			
	p=context;
	which_one_llcc68 = *p;
			
	if (which_one_llcc68==0){CLR_LLCC68_0_NSS();}
	else {CLR_LLCC68_1_NSS();}
	_delay_us(10);
	if (which_one_llcc68==0){SET_LLCC68_0_NSS();}
	else {SET_LLCC68_1_NSS();}
	_delay_us(4000);
	
		if(which_one_llcc68==0)
		{
			while (LLCC68_0_command_busy)
			{
				counter++;
				if (counter>2000)
				{
					return LLCC68_HAL_STATUS_ERROR;
				}
			}
		}
		else
		{
			while (LLCC68_1_command_busy)
			{
				counter++;
				if (counter>2000)
				{
					return LLCC68_HAL_STATUS_ERROR;
				}
			}
		}
	
	return LLCC68_HAL_STATUS_OK;	
}

写完硬件层代码后,根据数据手册基本发送操作写代码。

截取数据手册基本发送操作步骤:

但要注意,这里缺少了 设置DIO2为自动切换天线的代码,编程的时候注意写上,不然发送的时候天线连接的还是接收电路。

截取数据手册基本接收操作步骤:

参照这些基本步骤,稍作修改,

编程思路为:

写个接收初始化函数,让接收机一直处于接收状态,这里基本收发操作,先不考虑功耗。当有数据收到产生中断后,通知接收函数处理数据。

写个发送初始化函数,初始化大部分参数,另写一个发送函数,用于发送数据。

写个中断处理函数,用于处理收发数据事宜 。

代码如下(radio.h和radio.c):

#ifndef RADIO_H_
#define RADIO_H_

#include "llcc68.h"
#include "llcc68_hal.h"
#include "llcc68_regs.h"
#include <stdio.h>


extern uint8_t llcc68_which_0;
extern uint8_t llcc68_which_1;

extern uint8_t radio_irq_flag;
extern llcc68_pkt_status_lora_t last_pkt_status;
extern llcc68_rx_buffer_status_t last_rxbuff_status;
extern uint8_t rx_buff[256];


void radiorx_init(const void* context);
void radiotx_init(const void* context);
void radiosend(const void* context, uint8_t *playload,uint8_t length);
void radio_irq_processing(const void *context);
void radio_0_irq(void);
void radio_1_irq(void);
void radioreceive(const void* context, uint8_t *rxbuff);
void clear_rxbuff(void);


#endif /* RADIO_H_ */

#include "radio.h" 


#define N0                                                                                       //条件编译,N0是第一块板,N1是第二块板 两块板配对使用。

uint8_t llcc68_which_0 =0;
uint8_t llcc68_which_1 =1;

uint8_t buff[256]={0};

#ifdef N0
llcc68_mod_params_lora_t rx_lora_par={LLCC68_LORA_SF9,LLCC68_LORA_BW_125,LLCC68_LORA_CR_4_5,0};   //定义接收Lora调制器相关参数,扩频因子(SF),带宽(BW),编码率(CR),低速优化(LDRO)
llcc68_mod_params_lora_t tx_lora_par={LLCC68_LORA_SF9,LLCC68_LORA_BW_125,LLCC68_LORA_CR_4_5,0};   //定义发送Lora调制器相关参数,扩频因子(SF),带宽(BW),编码率(CR),低速优化(LDRO)
#endif

#ifdef N1
llcc68_mod_params_lora_t rx_lora_par={LLCC68_LORA_SF9,LLCC68_LORA_BW_125,LLCC68_LORA_CR_4_5,0};  //同上
llcc68_mod_params_lora_t tx_lora_par={LLCC68_LORA_SF9,LLCC68_LORA_BW_125,LLCC68_LORA_CR_4_5,0};  //同上
#endif

	
llcc68_pkt_params_lora_t rx_pkt_par={8,LLCC68_LORA_PKT_EXPLICIT,255,true,false};   //定义接收Lora包参数,前导码长度,头类型,PayLoad长度,CRC校验是否开启,IRQ极性是否反转
llcc68_pkt_params_lora_t tx_pkt_par={8,LLCC68_LORA_PKT_EXPLICIT,255,true,false};   //定义发送Lora包参数,前导码长度,头类型,PayLoad长度,CRC校验是否开启,IRQ极性是否反转
llcc68_pa_cfg_params_t pa_cfg={0x04,0x07,0x0,0x01};	 //定义功率放大器参数,分别为:paDutyCycle,hpMax, deviceSel, paLut 可参考数据手册71页末的表格

#ifdef N0
uint32_t rx_freq = 470300000;  //定义发送频率
uint32_t tx_freq = 509700000;  //定义接收频率
#endif	

#ifdef N1
uint32_t rx_freq = 509700000; //同上
uint32_t tx_freq = 470300000; //同上
#endif

uint8_t radio_irq_flag=0; //定义一个标志变量,当INT0产生中断时,置bit0为1,当INT1产生中断时,置bit1为1。
llcc68_pkt_status_lora_t last_pkt_status; //定义PKT状态变量,用于保存上一包数据的RSSI,Snr,和解扩后的RSSI。
llcc68_rx_buffer_status_t last_rxbuff_status;  //定义接收缓存状态,用于保存上一次收到数据的状态,包含接收到的字节数量和缓存起始指针
uint8_t rx_buff[256]={0}; //定义接收缓存数组


void radiorx_init(const void *context)        //接收初始化函数,参照数据手册基本接收操作编写
{
#ifdef N0
uint8_t sync_word[2]={0x14,0x24};             //定义同步字
#endif

#ifdef N1
uint8_t sync_word[2]={0x14,0x24};             //定义同步字
#endif
		
	llcc68_hal_reset(context);	                                           //复位接收机
	llcc68_set_standby(context, LLCC68_STANDBY_CFG_RC);                    //接收机转到待机状态
	llcc68_set_pkt_type(context, LLCC68_PKT_TYPE_LORA);                    //设置接收机数据包类型为LoRA
	llcc68_set_rf_freq(context, rx_freq);                                  //设置接收机频率
	llcc68_set_reg_mode(context,LLCC68_REG_MODE_DCDC);                     //设置启用接收机的DC-DC
	llcc68_set_buffer_base_address(context,0xff,0x00);                     //设置接收机发送与接收缓存指针
	llcc68_set_lora_mod_params(context, &rx_lora_par);                     //设置Lora调制参数
	llcc68_set_lora_pkt_params(context, &rx_pkt_par);                      //设置Lora数据包参数
	llcc68_set_dio_irq_params(context,0x42,0x42,0x00,0x00);                //设置接收机中断相关参数,参见数据手册74页。
	llcc68_write_register(context,0x0740,sync_word,2);                     //设置同步字符
	llcc68_set_rx_with_timeout_in_rtc_step( context,LLCC68_RX_CONTINUOUS );//接收机转换到连续接收状态
	
}



void radiotx_init(const void* context)    //发送初始化函数,参照数据手册基本发送操作编写
{
#ifdef N0
uint8_t sync_word[2]={0x14,0x24};
#endif

#ifdef N1
uint8_t sync_word[2]={0x14,0x24};
#endif		

	
	llcc68_hal_reset(context);                                //复位发送机
	llcc68_set_standby(context,LLCC68_STANDBY_CFG_RC);        //发送机转到待机状态
	llcc68_set_pkt_type(context,LLCC68_PKT_TYPE_LORA);		  //设置接收机数据包类型为LoRA
	llcc68_set_rf_freq(context,tx_freq);                      //设置发送机频率
	llcc68_set_dio2_as_rf_sw_ctrl(context,true);              //设置DIO2自动切换天线
	llcc68_set_reg_mode( context,LLCC68_REG_MODE_DCDC);       //设置启用发送机的DC-DC
	llcc68_set_pa_cfg(context,&pa_cfg);                       //设置功率放大器(PA)参数
	llcc68_set_tx_params(context, 0x16,LLCC68_RAMP_40_US);    //设置发射功率和RampTime
	llcc68_set_buffer_base_address(context,0x0,0xff);         //设置发送机发送与接收缓存指针
//	llcc68_write_buffer(context,0x00,playload,34);            //写有效载荷到Buffer里,这里注释掉,改在radiosend函数中进行
	llcc68_set_lora_mod_params(context,&tx_lora_par);         //设置Lora调制器参数
//	llcc68_set_lora_pkt_params(context,&pkt_par);             //设置Lorq数据包参数,这里注释掉,改在radiosend函数中进行
	llcc68_set_dio_irq_params(context,0x01,0x01,0x00,0x00);   //设置发送机中断相关参数,设置接收机中断相关参数,参见数据手册74页。
	llcc68_write_register(context,0x0740,sync_word,2);        //设置同步字符
//	llcc68_set_tx(context,0);                                 //设置发送机进入发送状态,发送完后进入待机状态,这里注释掉,改在radiosend函数中进行
}

void radiosend(const void* context,uint8_t *playload,uint8_t length)  //发送函数
{

	tx_pkt_par.pld_len_in_bytes = length;	
	llcc68_set_lora_pkt_params(context,&tx_pkt_par);             //设置Lora PKT包参数,这里主要是修改了有效载荷长度	
	llcc68_write_buffer(context,0x00,playload,length);           //写有效载荷到缓存
	llcc68_set_tx(context,0);                                    //切换至发送状态,发送完成后转为待机状态

}


void radio_0_irq(void)
{

   radio_irq_flag|=0x01;   	

}

void radio_1_irq(void)
{

	radio_irq_flag|=0x2;

}


void radio_irq_processing(const void *context)
{
	uint16_t irq_number;
		
	llcc68_get_irq_status(context,&irq_number);
	llcc68_clear_irq_status(context,LLCC68_IRQ_ALL);
	if (irq_number==1)
	{
		printf("Send success!\n");
	}
	if (irq_number==2) 
	{
	   radioreceive(context, rx_buff);
	}		
}


void radioreceive(const void* context, uint8_t *rxbuff)   //接收处理函数
{  
	 llcc68_get_lora_pkt_status(context, &last_pkt_status);			
	 llcc68_get_rx_buffer_status(context, &last_rxbuff_status);								
 //	 printf("RX Length is %d \n", last_rxbuff_status.pld_len_in_bytes);
 //	 printf("Buffer_start_pointer is %d \n", last_rxbuff_status.buffer_start_pointer);				
	 llcc68_read_buffer(context, last_rxbuff_status.buffer_start_pointer, rxbuff, last_rxbuff_status.pld_len_in_bytes );		
//	 printf("~~%d~~%d~~%d~~",last_pkt_status.rssi_pkt_in_dbm,last_pkt_status.signal_rssi_pkt_in_dbm,last_pkt_status.snr_pkt_in_db);			
	 printf("%s",rxbuff);
//	 printf("~~%d~~%d~~%d~~",last_pkt_status.rssi_pkt_in_dbm,last_pkt_status.signal_rssi_pkt_in_dbm,last_pkt_status.snr_pkt_in_db);	
     clear_rxbuff();
}
	


void clear_rxbuff(void) //清空接收缓存数组
{
	uint16_t i=0;
	for (i=0;i<256;i++)
	{
		rx_buff[i]=0;
	}
	
}

然后看看main.c里如何调用它们

#include "config.h"


#define PRINT               //启用printf 重定向功能。


/*
* printf 重定向
  初始化串口后需要执行  stdout = &mystdout;
*/
#ifdef PRINT
static int uart_putchar(char c, FILE *stream);

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,_FDEV_SETUP_WRITE);

static int uart_putchar(char c, FILE *stream)  //自定义的putchar
{
		while(!(UCSRA&0x20)); 
		UDR = c;
	   return 0;
}
#endif

void exit_init(void)  //外部中断初始化
{
	cli();
	MCUCR = 0x0f;
	GICR  = 0xc0;
	TIMSK = 0x00;
	sei();
	
}

ISR (INT0_vect)     //INT0中断服务函数
{
	radio_0_irq();
};

ISR (INT1_vect)     //INT0中断服务函数
{
	radio_1_irq();
};


uint16_t i=0;

int main(void)
{
    /* Replace with your application code */

	usart_init();                                    //串口初始化

#ifdef PRINT
stdout = &mystdout;
#endif
	
	spi_init();                                     //SPI口初始化                                
	exit_init();                                    //外部中断初始化
	llcc68_interface_init();                        //连接在Lora模块上的引脚初始化	
    radiorx_init(&llcc68_which_1);                  //模块2初始化成接收模式
    radiotx_init(&llcc68_which_0);                  //模块1初始化成发送模式
    while (1) 
    {

       if ((radio_irq_flag&0x01)==0x01)                      //当INT0中断时
       {
		   radio_irq_processing(&llcc68_which_0);           //模块1中断处理
		   radio_irq_flag &= 0xfe;                          //清除中断标志
       }
	   	   

       if ((radio_irq_flag&0x02)==0x02)                     //当INT1中断时     
       {
		   radio_irq_processing(&llcc68_which_1);           //模块2中断处理
		   radio_irq_flag &= 0xfd;                          //清除中断标志
       }
	   
        _delay_us(100);

       if(--usart_rx_timeout==0)                            //约10多个毫秒没有收到数据,认为串口接收一帧完整数据
       {
	      usart_rx_timeout=TimeOutMax;
	
	      if(usart_read_index!=usart_write_index)          //判断是否真的收到数据
	     {
 					          

			usart_read_index=0;                            //串口读指针清零,暂时没用上
			usart_write_index=0;                           //串口写指针清零
			
			radiosend(&llcc68_which_0,usart_rx_buff,usart_rx_counter);   //向Lora写入有效载荷并发送
			usart_rx_counter=0;				                             //清零串口接收计数器
			clear_rxbuff();                                         //清空串口接收缓存			

	     } 
       }	   
    }
}

另外LLCC68 扩频因子与接收带宽应遵循以下内容,超出范围不能正常收发:

太多原理性的东西这里不探讨,驱动函数对应Datasheet上提到的指令集,可以参照Datasheet来理解。我这主控用的 ATMEGA16,原理图上是ATMEGA32,它们引脚完全兼容的,16的FLASH小点而已。程序编译工具为:Microchip Studio 7.0.2594,会在最后给出全套资料(原理图,代码,打样Gerber文件),供有需要同学参考。

看看我凌乱的调试现场:

测试收发了1000包数据(4.7万个字节),暂无丢包出现,但也不能代表就不会出现丢包。。。

就写到这里吧。

全套资料下载地址:https://download.csdn.net/download/dream52/88305645

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值