目录
前些天接触到一个小项目,需要使用无线传输的功能,不仅如此还需要远距离的通信,搜索资料后最终选择了泽耀科技的LoRa(AS32—TTL-1W)无线串口模块。之前使用的是100mW的无线模块,经拉距实测在非空旷地带通信距离不到800米。因此这次我选择了1W的无线串口,一般情况下功率越高通信距离就越远。这次还未尽量拉距测试,测试完成后我会继续写一篇测评文章。今天就给大家分享一些我开发的过程以及遇到的一些bug。
无线串口简介
我使用的泽耀科技生产的AS32—TTL-1W,单价55(不含天线),天线单买10元。价格还算公道,毕竟是LoRa模块,图便宜只能买到很多虚标的产品。
1.引脚介绍
该模块一个7个引脚,引脚功能图如下
MD0,MD1引脚的作用就是修改模块工作状态的(如下图),如果刚开始学搞不懂这些工作状态是什么意思的情况下,收发数据的时候,把MD0,MD1接地即可使用。完成了基本的收发试验后,可以探索一下其它的功能。
LoRa模块的RX,TX分别接到单片机TX,RX上,如下图。
AUX引脚是用于指示模块工作状态,用户唤醒外部 MCU,配合外部中断即可开发相应的功能。但还是那句话,初学者可以不用考虑这个引脚,给它悬空即可,不影响使用的。
剩下的就是VCC和GND了,商家的参考手册明确给出电源电压小于 4.5V,输出功率会有下降,但对接收功率影响较小。所以我给它接了5V。
2.上位机简介
使用泽耀科技开发的上位机可直接修改模块的波特率,地址,信道,传输方式等参数。最好可以配上泽耀科技生产的usb转ttl一起使用。把模块直接插入上面的单排座即可使用。(配置的时候记得拔掉两个黄色的跳帽)
上位机如图所示
其实如果只是在两个MCU实现简单的单点通信,直接用厂家的出厂设置的参数就足够了。
项目简介
项目其实很简单,按下发射端精英板上的KEY_UP按键,接收端精英板的LED0闪一下。按下发射端精英板上的KEY1按键,接收端精英板的LED1闪一下。(代码是移植泽耀科技提供的demo改写的)
发送端代码
main.c
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "drv_uart.h"
#include "drv_led.h"
#include "drv_AS62.h"
#define __AS62_TX_MODE__ //模式选择,屏蔽即为接收模式
#ifdef __AS62_TX_MODE__
char *Signal_Go = "a"; //定义字符a
char *Signal_Stop = "s"; //定义字符s
#else
uint8_t go[32 ] ={ 'a'};
uint8_t stop[ 32] ={ 's'};
uint8_t Go_rx_buffer[ 100 ] = { 0 };
uint8_t Stop_rx_buffer[100] = { 0 };
uint8_t g_RxLength = 0;
#endif
int main(void)
{
int key=0;
Init_USART(); //串口初始化
ASxx_param_init( ); //模块参数初始化(定点模式,地址0x1234,信道0x17)
drv_led_init(); //LED初始化
delay_init(); //延时初始化
KEY_Init(); //按键初始化
led_green_off();
led_red_off(); //初始化LED灭
while(1)
{
key=KEY_Scan(0); //获取键值
if(key==WKUP_PRES)
{
led_red_flashing();//LED每发送一次数据闪一下
GO_On(); //发送字符a·····这里我使用了宏定义发送字符
delay_ms(500);
Send_Off; //模块复位
led_red_off();
}
if(key== KEY1_PRES)
{
led_green_flashing();//LED每发送一次数据闪一下
Stop_On; //发送字符b
delay_ms(500);
Send_Off; //模块复位
led_green_off();
}
}
}
LoRa.c(此段代码来自泽耀科技的demo)
#include "drv_AS62.h"
//模块配置参数数组
//改变模块参数,只需改变参数数组值,然后初始化即可
const uint8_t g_ASxx_Param_Config[ 6 ] = { 0xC0, 0x12, 0x34, 0x1A, 0x17, 0xC4 }; //定点模式
const uint8_t g_ASxx_Config_Status_OK[ ] = { 0x4F, 0x4B, 0x0D, 0x0A };
/**
* @brief :ASxx模块初始化
* @param :无
* @note :按照默认参数初始化,修改默认参数表即可改变模块初始化参数
* @retval:
* @ASxx_Write_OK 写入成功
* @ASxx_Write_ERROR 写入失败
*/
ASxxWriteStatusType ASxx_param_init( void )
{
uint8_t i = 0;
uint8_t Read_Buff[ 5 ] = { 0 };
drv_uart_tx_bytes((uint8_t *)g_ASxx_Param_Config, 6 );
drv_uart_rx_bytes( Read_Buff );
for( i = 0; i < 4; i++ )
{
if( Read_Buff[ i ] != g_ASxx_Config_Status_OK[ i ] )
{
break;
}
}
if( 4 == i )
{
return ASxx_Write_OK; //配置成功
}
else
{
return ASxx_Write_ERROR; //配置失败
}
}
/**
* @brief :ASxx模块读配置参数
* @param :
* @pReadBuffer:参数返回地址
* @note :无
* @retval:无
*/
void ASxx_read_param( uint8_t *pReadBuffer )
{
uint8_t Read_Cmd[ 3 ] = { 0xC1, 0xC1, 0xC1 };
drv_uart_tx_bytes( Read_Cmd, 3 );
drv_uart_rx_bytes( pReadBuffer );
}
/**
* @brief :ASxx模块读取硬件版本号
* @param :
* @pReadBuffer:硬件版本号返回地址
* @note :无
* @retval:无
*/
void ASxx_read_version( uint8_t *pReadBuffer )
{
uint8_t Read_Cmd[ 3 ] = { 0xC3, 0xC3, 0xC3 };
drv_uart_tx_bytes( Read_Cmd, 3 );
drv_uart_rx_bytes( pReadBuffer );
}
/**
* @brief :ASxx模块读取实际电压值
* @param :
* @pReadBuffer:电压值返回地址
* @note :无
* @retval:无
*/
void ASxx_read_voltage( uint8_t *pReadBuffer )
{
uint8_t Read_Cmd[ 3 ] = { 0xC5, 0xC5, 0xC5 };
drv_uart_tx_bytes( Read_Cmd, 3 );
drv_uart_rx_bytes( pReadBuffer );
}
/**
* @brief :ASxx模块复位
* @param :无
* @note :无
* @retval:无
*/
void ASxx_reset( void )
{
uint8_t Read_Cmd[ 3 ] = { 0xC4, 0xC4, 0xC4 };
drv_uart_tx_bytes( Read_Cmd, 3 );
}
/**
* @brief :ASxx模块发送数据(定点模式)
* @param :
* @Addr_H:地址高位
* @Addr_L:地址低位
* @Channel:信道
* @pTxBuff:发送数据地址
* @Length:发送数据个数
* @note :定点模式 数据个数最29个
* @retval:无
*/
void ASxx_tx_packet( uint8_t Addr_H, uint8_t Addr_L, uint8_t Channel, uint8_t *pTxBuff, uint8_t Length )
{
uint8_t Header[ 3 ] = { 0 };
Header[ 0 ] = Addr_H;
Header[ 1 ] = Addr_L;
Header[ 2 ] = Channel;
drv_uart_tx_bytes( Header, 3 );
//发送数据
drv_uart_tx_bytes( pTxBuff, Length );
}
/**
* @brief :ASxx模块接收数据(定点模式)
* @param :无
* @note :定点模式 数据个数最29个
* @retval:无
*/
uint8_t ASxx_rx_packet( uint8_t *pRxBuff )
{
uint8_t Length = 0;
Length = drv_uart_rx_bytes( pRxBuff );
return Length;
}
usart.c
#include "drv_uart.h"
void Init_USART(void)
{
GPIO_InitTypeDef GPIO_Structure;
USART_InitTypeDef USART_Structure;
NVIC_InitTypeDef NVIC_Structure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//PA9 TX
GPIO_Structure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Structure.GPIO_Pin=GPIO_Pin_9;
GPIO_Structure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Structure);
//PA10 RX
GPIO_Structure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Structure.GPIO_Pin=GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_Structure);
USART_Structure.USART_BaudRate=115200;
USART_Structure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_Structure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_Structure.USART_Parity=USART_Parity_No;
USART_Structure.USART_StopBits=USART_StopBits_1;
USART_Structure.USART_WordLength=USART_WordLength_8b;
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Init(USART1, &USART_Structure);
USART_Cmd(USART1, ENABLE);
NVIC_Structure.NVIC_IRQChannel=USART1_IRQn;
NVIC_Structure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Structure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_Structure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_Structure);
}
/**
* @brief :串口发送数据
* @param :
* @TxBuffer:发送数据首地址
* @Length:数据长度
* @note :无
* @retval:无
*/
void drv_uart_tx_bytes( uint8_t* TxBuffer, uint8_t Length )
{
while( Length-- )
{
while( RESET == USART_GetFlagStatus( UART_PORT, USART_FLAG_TXE ));
UART_PORT->DR = *TxBuffer;
TxBuffer++;
}
}
/**
* @brief :串口接收数据
* @param :
* @RxBuffer:发送数据首地址
* @note :无
* @retval:接收到的字节个数
*/
uint8_t drv_uart_rx_bytes( uint8_t* RxBuffer )
{
uint8_t l_RxLength = 0;
uint16_t l_UartRxTimOut = 0x7FFF;
while( l_UartRxTimOut-- ) //等待查询串口数据
{
if( RESET != USART_GetFlagStatus( UART_PORT, USART_FLAG_RXNE ))
{
*RxBuffer = (uint8_t)UART_PORT->DR;
RxBuffer++;
l_RxLength++;
l_UartRxTimOut = 0x7FFF; //接收到一个字符,回复等待时间
}
if( 100 == l_RxLength )
{
break; //不能超过100个字节
}
}
return l_RxLength; //等待超时,数据接收完成
}
//发送字符(想通过串口助手查看自己发送的数据可以调用这个函数)
void USART_SendByte_Init(USART_TypeDef* USARTx, uint16_t Data)
{
USART_SendData(USARTx,Data);
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET);
}
//发送字符串(想通过串口助手查看自己发送的数据可以调用这个函数)
void USART_SendStr_Init(USART_TypeDef* USARTx,char *str)
{
uint16_t i=0;
do
{
USART_SendByte_Init(USARTx,*(str+i));
i++;
}while(*(str+i)!='\0');
while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET);
}
接收端代码
main.c
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "drv_uart.h"
#include "drv_led.h"
#include "drv_AS62.h"
//#define __AS62_TX_MODE__ //接收模式开启
void delay(uint16_t time) //延时函数
{
uint16_t i=0;
while(time--)
{
i=12000;
while(i--);
}
}
#ifdef __AS62_TX_MODE__
char *Signal_Go = "a";
char *Signal_Stop = "s";
#else
uint8_t go[ 8 ] ={ 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' };
uint8_t stop[ 8 ] ={ 's', 's', 's', 's', 's', 's', 's', 's' };
uint8_t Go_rx_buffer[ 100 ] = { 0 };
uint8_t Stop_rx_buffer[100] = { 0};
uint8_t g_RxGO = 0;
uint8_t g_RxSTOP = 0;
#endif
int main( void )
{
drv_uart_init();
ASxx_param_init( );
drv_led_init( );
led_green_off();
led_red_off( );
while(1)
{
}
}
void USART1_IRQHandler (void) //串口中断服务函数
{
char temp=0;
if(USART_GetITStatus( USART1, USART_IT_RXNE)!=RESET)
{
temp = USART_ReceiveData(USART1);
if(temp=='a')
{
led_green_flashing(); //接收到字符‘a’led闪一次
delay(500);
led_green_off();
}
if(temp=='s')
{
led_red_flashing(); //接收到字符‘b’led闪一次
delay(500);
led_red_off() ;
}
}
}
由于接收端的代码与发射端除了main函数之外,其它函数几乎一致,所以我这里只上传了main函数里的内容,如果想要完整代码可以私信我。
项目中遇到的小bug
在写接收端的串口中断服务函数的时候,在实现灯的闪烁的时候,开始我的延时函数调用的是定时器中断延时。但程序编译运行后发现程序根本无法执行闪烁的效果,接收到指定的字符后led一直保持常亮的状态。我百思不得其解,根据程序执行的逻辑从表面上看没毛病呀!尝试过各种猜想,一开始还傻傻的以为是中断标志位没有清除,但后来发现都不是。然后我抱着瞎猫碰到死耗子的心理,把定时器中断延时改成了传统上的“粗延时”,编译执行后居然成功了,接收到指定字符后可以实现闪烁了。这着实让我既惊讶又惊喜,这个折磨我这么多天的bug居然就这么解决了。但我到现在都不知道是什么原理,之前用Wemos实现串口中断的时候也是在串口中断服务函数中加入delay()后,程序也出现了类似的bug。难道串口中断服务函数就是这么奇葩吗?欢迎大佬前来指点!!!
项目总结
这个项目虽然简单,但我实现的过程中还是挺坎坷的。全网适用于泽耀科技产品的例程并不多,根据泽耀科技提供的demo写了快一个星期(毕竟本人也是单片机小白),网上大多数都是关于安信可的或者NFR2401相关的例程比较多一些。尤其是NFR2401的例程多到快烂大街了,所以想在短时间内开发出项目或是单片机基础薄弱的同学选择NFR2401开发也是一个不错之选。关于LoRa无线串口这个模块我了解的还仅仅是冰山一角,还有很多功能都没有开发出来,本人也才疏学浅,文章中如有错误欢迎大佬在评论区指正,如想要源代码的小伙伴也可以私信我。