LORA-B1S——Ping通信

Lora DK45

lora ping通信例程名为:lora-Ping,基于LADK进行创建。

ping通信和p2p通信类似,也是实现两块LORA-B1S之间进行通信的。不过p2p是实现单向通信,而Ping则实现了双向通信,可以用来测试lora的传输距离或者丢包情况。

如下图所示,客户端发送一包数据给服务端,服务端收到以后会再返回给客户端。客户端按照顺序发送数据包1,数据包2……数据包10,共连续发送10包数据为一个完整过程,之后再次循环。通过串口输出就可以查看传输的数据丢包率。

例程代码共用一个MDK工程,但分为两个不同的target

  • ping-server服务端
  • ping-client客户端

通过MDK这个地方可以选择编译为client还是server

分别编译烧录到两块Lora-B1s开发板中,按下复位按键,正常如果通信成功两块板子的LED都会闪烁,串口tx可以接到电脑,串口助手可以观察到通信数据包的信息以及信号强度。

需要开发环境

硬件:

  • 两块Lora-B1S开发板
  • 调试器

软件:

  • MDK5
  • stm32Cubemx

基于LADK工程创建实现步骤

  • 拷贝lora-ADK并重新命名为lora-Ping
  • ./user/app文件夹中新建三个文件ping-client.c ping-server.c ping.h
  • ping-client.c ping-server.c添加到MDK工程分组App中
  • ping-client.c ping-server.c中添加应用代码,分别实现客户端和服务端功能
  • 在MDK中添加两个target:client和server
  • client 排除ping-server.c参与编译
  • server 排除ping-client.c参与编译
  • 修改main.c 调用ping.h 接口
  • 编译烧录到开发板中

Ping-client代码分析

client的逻辑会稍微多一些,所以client我们会使用app_fsm状态机组件来简化逻辑部分。

初始化部分

初始化部分的代码和p2p例程中的几乎一样,区别在于这里多初始化了一个app_fsm,并且在最后给fsm状态机put进去一个事件让这个状态机运转起来。

APP_FSM_DEF(ping_fsm);		//定义app_fsm对象
APP_TIMER_DEF(ping_timer);	//定义app_timer对象

/* lora 回调函数结构体 */
static RadioEvents_t events = {
    .TxDone = lora_tx_done_callback,
    .TxTimeout = lora_tx_timeout_callback,
    .RxDone = lora_rx_done_callback,
    .RxTimeout = lora_rx_timeout_callback,
};

void ping_init()
{
    /* 初始化app_scheduler, app_timer 和 app_fsm 都要依赖于他运行 */
    APP_SCHED_INIT(12,20);
    /* 创建一个app_fsm*/
    app_fsm_create( &ping_fsm,ping_fsm_list,APP_FSM_LIST_LEN(ping_fsm_list),
                    PING_STATE_IDLE,ping_fsm_handler);
    /* 初始化app_timer*/
    app_timer_init();
    app_timer_create(&ping_timer,1000,APP_TIMER_ONESHOT,ping_timer_handler,NULL);

    /* lora 参数配置 */
    Radio.Init(&events);
    Radio.SetChannel(LORA_FREQUENCY);
    Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
                      LORA_SPREADING_FACTOR, LORA_CODINGRATE,
                      LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
                      true, 0, 0, LORA_IQ_INVERSION_ON, 2000);
    Radio.SetRxConfig(MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
                      LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
                      LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
                      0, true, 0, 0, LORA_IQ_INVERSION_ON, false);
    Radio.SetPublicNetwork(false);
    Radio.Rx(5000);

    /* fsm状态机发送一个事件 */
    app_fsm_event_put(&ping_fsm,PING_EVENT_NEXT);
}

FSM状态机组件使用

该例子的核心是通过app_fsm状态机组件来实现的,所以要明白搞懂这个例子的代码,关键就是要看明白app_fsm 的使用。

有限状态机在单片机开发中是非常常用的一个工具,用的好能大大简化代码的开发逻辑。有限状态机可以通过状态和事件组成,事件可以触发从一个状态调转到另外一个状态。

先来看下client客户端的核心逻辑状态图:

图中的圆形就是一个个状态,圆圈与圆圈之间的线代表的就是事件触发转移。client初始化以后进行IDLE状态,当产生NEXT时间的时候,状态机切换到开始状态。这样通过上图的5个状态和4种时间就把client的逻辑描述清楚了。

如下代码就用ping_state_e 定义出来所有的状态,ping_event_e定义出来所有的事件,ping_fsm_list 数组描述了状态转移的逻辑。

/* 定义所有状态 */
typedef enum
{
    PING_STATE_IDLE,
    PING_STATE_START,
    PING_STATE_SEND,
    PING_STATE_RECV,
    PING_STATE_END,
} ping_state_e;

/* 定义所有事件 */
typedef enum
{
    PING_EVENT_NEXT,
    PING_EVENT_TIMEOUT,
    PING_EVENT_LESS_10,
    PING_EVENT_MORE_10,
}ping_event_e;

/* 定义一个状态机转移的列表,控制着状态转移的逻辑 */
fsm_list_t ping_fsm_list[]=
{
	{PING_STATE_IDLE,	    PING_EVENT_NEXT,		PING_STATE_START},
    {PING_STATE_START,      PING_EVENT_NEXT,        PING_STATE_SEND},
    {PING_STATE_SEND,       PING_EVENT_TIMEOUT,     PING_STATE_RECV},
    {PING_STATE_RECV,       PING_EVENT_LESS_10,     PING_STATE_SEND},
    {PING_STATE_RECV,       PING_EVENT_MORE_10,     PING_STATE_END},
    {PING_STATE_END,        PING_EVENT_NEXT,        PING_STATE_START},
};

在文件开头定义一个fsm对象:

APP_FSM_DEF(ping_fsm);		//定义app_fsm对象

在初始化代码中针对fsm有两句:

app_fsm_create( &ping_fsm,ping_fsm_list,APP_FSM_LIST_LEN(ping_fsm_list),
                    PING_STATE_IDLE,ping_fsm_handler);

app_fsm_event_put(&ping_fsm,PING_EVENT_NEXT);
  • app_fsm_create 创建了ping_fsm这个状态机,ping_fsm 是创建的fsm对象;ping_fsm_list 定义的状态转移列表;APP_FSM_LIST_LEN(ping_fsm_list) 求列表的长度,PING_STATE_IDLE 初始状态,ping_fsm_handler 状态机处理函数。
  • app_fsm_event_put 触发一个事件,初始化完以后触发一个PING_EVENT_NEXT事件,根据状态转移列表描述就会自动跳转到下一个状态,并交给ping_fsm_handler去做处理。其他地方需要触发事件的就调用这个接口函数。

所以ping_fsm_handler这个函数实现的就是针对各个状态去做处理:

static void ping_fsm_handler(uint8_t state)
{
    static uint8_t send_counter = 0;
    static uint8_t success_counter = 0;
    uint8_t buf[4] = {0};

    switch (state)
    {
        case PING_STATE_START:
            printf("\r\n**********ping start********\r\n");
            send_counter = 0;
            success_counter = 0;
            app_fsm_event_put(&ping_fsm,PING_EVENT_NEXT);
            break;
        case PING_STATE_SEND:
            send_counter++;
            now_num = send_counter;
            memset(buf, send_counter, 4);
            Radio.Send(buf, 4);
            app_timer_start(&ping_timer);
            break;
        case PING_STATE_RECV:
            if (recv_flag)
            {
                success_counter++;
                printf("client ping pack [%d],result:1, rssi:%d, snr:%d\r\n", send_counter, recv_rssi, recv_snr);
            }
            else
            {
                printf("client ping pack [%d],result:0\r\n", send_counter);
            }
            if (send_counter >= 10)
                app_fsm_event_put(&ping_fsm,PING_EVENT_MORE_10);
            else
                app_fsm_event_put(&ping_fsm,PING_EVENT_LESS_10);
            break;
        case PING_STATE_END:
            printf("ping all pack=10,success=%d\r\n", success_counter);
            app_fsm_event_put(&ping_fsm,PING_EVENT_NEXT);
            break;
        default:
            break;
    }
}

Lora回调函数

static void lora_tx_done_callback()
{
    Radio.Rx(5000);
}
static void lora_tx_timeout_callback()
{
}
static void lora_rx_done_callback(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr)
{
    recv_rssi = rssi;
    recv_snr = snr;
    if ((size == 4) &&
        (payload[0] == now_num) &&
        (payload[1] == now_num) &&
        (payload[2] == now_num) &&
        (payload[3] == now_num))
    {
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        recv_flag=true;
    }
    Radio.Sleep();
}
static void lora_rx_timeout_callback()
{
}

重点的就是lora_rx_done_callback,当client发送一包数据以后就会进入接收状态,等待server端收到数据以后重新返回,这时候client端接收到就会和发出去的数据做对比,如果对比一样就认为通信一包数据成功了!

Ping-server代码分析

初始化部分:

和前面初始化一样,不再过多描述。

    APP_SCHED_INIT(MAX_SCHED_EVENT_SIZE,10);

    Radio.Init(&events);
    Radio.SetChannel(LORA_FREQUENCY);
    Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
                      LORA_SPREADING_FACTOR, LORA_CODINGRATE,
                      LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
                      true, 0, 0, LORA_IQ_INVERSION_ON, 2000);
    Radio.SetRxConfig(MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
                      LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
                      LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
                      0, true, 0, 0, LORA_IQ_INVERSION_ON, false);
    Radio.SetPublicNetwork(false);
    Radio.Rx(5000);

Lora回调函数

server的逻辑比较简单,当接收到数据以后还把这个数据再发送出去就可以了。那重点我们就来看接收完成中断函数lora_rx_done_callback。收到数据以后就调用了app_sched_event_put 函数,这个是app_scheduler的常用接口:把需要执行的函数压入调度器去运行,这样ping_shceduler_handler处理最终会在调度器中执行,而不会在lora_rx_done_callback 里面运行。

这个也是app_scheduler的关键作用,在一些中断中比较耗时的函数就把他压入调度器中运行(main的while循环中)。

static void ping_shceduler_handler(void * p_event_data, uint16_t event_size)
{
    printf("server receive pack:%d, rssi:%d, snr:%d\r\n", ((uint8_t *)p_event_data)[0], recv_rssi, recv_snr);
    Radio.Send((uint8_t *)p_event_data, event_size);
}
static void lora_rx_done_callback(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr)
{
    recv_rssi = rssi;	//更新rssi
    recv_snr = snr;		//更新snr

    if(size>MAX_SCHED_EVENT_SIZE)return ;
    app_sched_event_put(payload,size,ping_shceduler_handler);
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}

main.c修改

client和server 留出来接口函数是一样的,在ping.h中定义

#ifndef __PING_H
#define __PING_H

void ping_init(void);
void ping_process(void);

#endif

在main 的while之前调用ping_init,while 里面调用ping_process

MDK中如何创建不同的target

在ping例程中,通过targe来选择不同的代码,在MDK中可以通过这里添加targe:

添加完成以后,选择文件右键->Options 设置,就可以选择这个文件是否参与编译:

LORA-B1S支持

淘宝购买地址:

https://item.taobao.com/item.htm?&id=657480900713


Lora技术支持群:

QQ群:603253865


LORA-B1S专栏

源码下载地址,最新文档都会更新在专栏内,欢迎大家订阅收藏

https://www.yuque.com/eemaker/lora-b1s

喜欢 (1)