LORA-B1S——Ping通信
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.cping-server.cping.h
- 把ping-client.cping-server.c添加到MDK工程分组App中
- 在ping-client.cping-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进去一个事件让这个状态机运转起来。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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 数组描述了状态转移的逻辑。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | /* 定义所有状态 */ 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对象:
| 1 | APP_FSM_DEF(ping_fsm); //定义app_fsm对象 | 
在初始化代码中针对fsm有两句:
| 1 2 3 4 | 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这个函数实现的就是针对各个状态去做处理:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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回调函数
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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代码分析
初始化部分:
和前面初始化一样,不再过多描述。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |     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循环中)。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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中定义
| 1 2 3 4 5 6 7 | #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专栏
源码下载地址,最新文档都会更新在专栏内,欢迎大家订阅收藏

 
 
