无线定位之一 SX1302 网关源码 thread_up 线程详解
创始人
2025-05-31 11:55:46
0

前言

笔者从实践出发、本篇直接走读无线定位系统关键节点、网关 SX1302 源码框架,并在源码走读过程中、着重分析与无线定位相关的PPS时间的来龙去脉、并在后期文章中以实际代码讲解 TDOA 无线定位实现过程及多网关综合定位内容,敬请期待。

semtech 公司在 2020年06月份推出 LR1110\LR1120 两款GNSS、WIFI和Lora(LR-HFSS)混合定位芯片、并提供’定位云服务’的接入、国内与腾讯云合作,腾讯云也提供定位云服务接入,这是笔者对混合无线定位技术背景简单描述、此用意看官自行审度。

闲言少叙直奔主体、sx1302 网关数据处理逻辑在 lora_pkt_fwd.c 文件,看官阅读下面内容时、请打开源码对照阅读下、否则可能不知道笔者所云是何物。

本篇文章目标是快速建立起SX1302网关框架认识。

第1节 主程序代码走读

主线程基本功能:
<1>. 读取 *.conf.json 文件内容、并解析内容把变量赋值到相关全局变量中;
<2>. 启动各子线程、子线程清单如下所述;
<3>. 固定周期定时检测gps的时间戳、并上报网关的状态信息;
<4>. 等待退出信号量、网络断开信号量和各子线程退出.

子线程清单.

/* threads */
void thread_up(void);               //> 上行线程:负责接收lora模块的数据、并把数据通过网络上传至网络服务器;
void thread_down(void);             //> 下行线程:负责接收服务器的数据,并把数据通过lora无线下方给终端模块;
void thread_jit(void);              //> jit 时间线程:
void thread_gps(void);              //> gps 线程: 
void thread_valid(void);            //> 时钟校正线程
void thread_spectral_scan(void);    //> SCAN扫描线程:

主程序源码基本功能就这么多,笔者就不贴出源码对照了。

第2节 线程 thread_up 代码走读

此线程负责实时读取 Lora 模块的数据、并打包数据内容上传至网络服务器。

此线程代码逻辑如下:
1>. 配置网关网络上线通讯参数、并建立上行传输socket信道和下行传输的socket信道;
2>. 读取Lora sx1302 模块数据内容、并解码接收数据包内容;
3>. 配置上报数据帧内容和状态、发送数据包内容;
4>. 延时等待接收网络服务器回复 ACK 内容。

我们根据上面总体功能框架、逐步走读此部分代码;先看一下 lgw_pkt_rx_s 结构体内容定义:

2.1 通讯协议数据格式

/**
@struct lgw_pkt_rx_s
@brief Structure containing the metadata of a packet that was received and a pointer to the payload
*/
struct lgw_pkt_rx_s {uint32_t    freq_hz;        /*!> central frequency of the IF chain */int32_t     freq_offset;uint8_t     if_chain;       /*!> by which IF chain was packet received */uint8_t     status;         /*!> status of the received packet */uint32_t    count_us;       /*!> internal concentrator counter for timestamping, 1 microsecond resolution, 网关时间戳计数器 */uint8_t     rf_chain;       /*!> through which RF chain the packet was received, 数据包来源链路 */uint8_t     modem_id;       //>  模块ID号uint8_t     modulation;     /*!> modulation used by the packet, 本包调制模式 */uint8_t     bandwidth;      /*!> modulation bandwidth (LoRa only) */uint32_t    datarate;       /*!> RX datarate of the packet (SF for LoRa) */uint8_t     coderate;       /*!> error-correcting code of the packet (LoRa only) */float       rssic;          /*!> average RSSI of the channel in dB, 接收信息RSSI平均值 */float       rssis;          /*!> average RSSI of the signal in dB, 本包接收信息的RSSI 值 */float       snr;            /*!> average packet SNR, in dB (LoRa only) */float       snr_min;        /*!> minimum packet SNR, in dB (LoRa only) */float       snr_max;        /*!> maximum packet SNR, in dB (LoRa only) */uint16_t    crc;            /*!> CRC that was received in the payload */uint16_t    size;           /*!> payload size in bytes */uint8_t     payload[256];   /*!> buffer containing the payload */bool        ftime_received; /*!> a fine timestamp has been received ,接收到精准时间戳标志位 */uint32_t    ftime;          /*!> packet fine timestamp (nanoseconds since last PPS) 该报数据接收时间,TDOA 同步时间 */
};

根据此格式我们基本上得出、网关的通讯基本逻辑:
<1>. 数据通讯采用 json 格式、采用 key:value 模式组织数据;
<2>. 网关与Lora模块之间的数据帧、存放在 payload【】 数组中,此部分可以扩展私有协议内容;
<3>. 其他字段内容根据注释和字段名望文生义即可;
<4>. 与网络服务器之间采用异步通讯结构、上报数据后就接收ACK内容;网络服务器会根据上报内容
下发命令、是在下行线程中进行、与上行线程间无业务关联。

2.2 thread_up 线程网络通讯建立


/* prepare hints to open network sockets */memset(&hints, 0, sizeof hints);hints.ai_family = AF_INET; /* WA: Forcing IPv4 as AF_UNSPEC makes connection on localhost to fail */hints.ai_socktype = SOCK_DGRAM;  //> UDP 通讯方式/* look for server address w/ upstream port */i = getaddrinfo(serv_addr, serv_port_up, &hints, &result);if (i != 0) {MSG("ERROR: [up] getaddrinfo on address %s (PORT %s) returned %s\n", serv_addr, serv_port_up, gai_strerror(i));exit(EXIT_FAILURE);}/* try to open socket for upstream traffic */for (q=result; q!=NULL; q=q->ai_next) {sock_up = socket(q->ai_family, q->ai_socktype,q->ai_protocol);if (sock_up == -1) continue; /* try next field */else break; /* success, get out of loop */}
/* connect so we can send/receive packet with the server only */i = connect(sock_up, q->ai_addr, q->ai_addrlen);if (i != 0) {MSG("ERROR: [up] connect returned %s\n", strerror(errno));exit(EXIT_FAILURE);}
/* look for server address w/ downstream port */i = getaddrinfo(serv_addr, serv_port_down, &hints, &result);if (i != 0) {MSG("ERROR: [down] getaddrinfo on address %s (port %s) returned %s\n", serv_addr, serv_port_down, gai_strerror(i));exit(EXIT_FAILURE);}/* set upstream socket RX timeout, 此部分代码在 thread_up 线程中 */i = setsockopt(sock_up, SOL_SOCKET, SO_RCVTIMEO, (void *)&push_timeout_half, sizeof push_timeout_half);if (i != 0) {MSG("ERROR: [up] setsockopt returned %s\n", strerror(errno));exit(EXIT_FAILURE);}    

网络服务器的ip地址是在 *.conf.json 文件中,如下:

 "gateway_conf": {"gateway_ID": "AA555A0000000000",/* change with default server address/ports */"server_address": "localhost","serv_port_up": 1730,"serv_port_down": 1730,/* adjust the following parameters for your network */"keepalive_interval": 10,"stat_interval": 30,"push_timeout_ms": 100,/* forward only valid packets */"forward_crc_valid": true,"forward_crc_error": false,"forward_crc_disabled": false,/* GPS configuration */"gps_tty_path": "/dev/ttyS0",/* GPS reference coordinates */"ref_latitude": 0.0,"ref_longitude": 0.0,"ref_altitude": 0,/* Beaconing parameters */"beacon_period": 0,"beacon_freq_hz": 869525000,"beacon_datarate": 9,"beacon_bw_hz": 125000,"beacon_power": 14,"beacon_infodesc": 0},

此部分是网关的配置内容,其中 server_address 和 serv_port_up 是上行线程中的网络通讯参数,
此部分配置内容我们就望文生义呗、基本上可以理解差不多。

网关地址上报至网络服务器

/* gateway unique identifier (aka MAC address) (optional) */str = json_object_get_string(conf_obj, "gateway_ID");if (str != NULL) {sscanf(str, "%llx", &ull);lgwm = ull;MSG("INFO: gateway MAC address is configured to %016llX\n", ull);}/* process some of the configuration variables */net_mac_h = htonl((uint32_t)(0xFFFFFFFF & (lgwm>>32)));net_mac_l = htonl((uint32_t)(0xFFFFFFFF &  lgwm  ));/* pre-fill the data buffer with fixed fields */buff_up[0] = PROTOCOL_VERSION;                //> 协议版本 buff_up[3] = PKT_PUSH_DATA;                   //> 上行数据标识*(uint32_t *)(buff_up + 4) = net_mac_h;       //> 网关 ID*(uint32_t *)(buff_up + 8) = net_mac_l;/* start composing datagram with the header */token_h = (uint8_t)rand(); /* random token */token_l = (uint8_t)rand(); /* random token */buff_up[1] = token_h;                   //> tokenbuff_up[2] = token_l;    

上行信道通讯协议格式定义如下:
/--------------------------------------------------------------
| 12 bytes 帧头 | JSON 内容 {“rxpk”:[],“stat”:{}} |
--------------------------------------------------------------/

帧头格式: 协议版本、token、上行数据标识 和网关 ID 地址,总共 12 bytes ;

json 串中存放两个数据集,1>. rxpk : [{"jver":10, "tmst": , "time": , "tmms": , "ftime": , "chan": , "rfch": , "freq": , "mid": , "stat":-1, "modu":"FSK || LORA", "datr":"SF5", "BW125":"", "codr":"4/5", "rssis":0.1, "lsnr":1.0, "foff":123, "rssi":0.123, "size": 5, "data":"base64_code"}] 是无线通讯物理参数,如频率、带宽、码速率和载荷数据等;2>. "stat":{"time":"2022-10-08 01:50:20 GMT","rxnb":0,"rxok":0,"rxfw":0,"ackr":0.0,"dwnb":0,"txnb":0,"temp":0.0}存放的是统计数据内容。

至此我们大致了解网关与网络服务器间的通讯协议格式, 网关 id 在帧头上传输、Lora 模块 id 在
json 串的 modem_id 中,基本通讯框架算是建立起来喽。

接下来看thread_up程序框架功能。

2.3>. thread_up 框架功能

下面代码已删减、只保留主体功能.

void thread_up(void) 
{while (!exit_sig && !quit_sig) {//> 接收 Lora 模块数据内容nb_pkt = lgw_receive(NB_PKT_MAX, rxpkt);/* start composing datagram with the header,补充帧头 token 内容 */token_h = (uint8_t)rand(); /* random token */token_l = (uint8_t)rand(); /* random token */buff_up[1] = token_h;buff_up[2] = token_l;/* start of JSON structure , 下面就是json串数据打包过程  */buff_index = 12; /* 12-byte header */memcpy((void *)(buff_up + buff_index), (void *)"{\"rxpk\":[", 9);buff_index += 9;for (i = 0; i < nb_pkt; ++i) {p = &rxpkt[i];/* Get mote information from current packet (addr, fcnt),数据区 payload 内容,lora模块上报过来的数据 *//* FHDR - DevAddr */if (p->size >= 8) {mote_addr  = p->payload[1];mote_addr |= p->payload[2] << 8;mote_addr |= p->payload[3] << 16;mote_addr |= p->payload[4] << 24;/* FHDR - FCnt */mote_fcnt  = p->payload[6];mote_fcnt |= p->payload[7] << 8;} else {mote_addr = 0;mote_fcnt = 0;}meas_nb_rx_rcv += 1;switch(p->status) {case STAT_CRC_OK:meas_nb_rx_ok += 1;if (!fwd_valid_pkt) {pthread_mutex_unlock(&mx_meas_up);continue; /* skip that packet */}break;case STAT_CRC_BAD:meas_nb_rx_bad += 1;if (!fwd_error_pkt) {pthread_mutex_unlock(&mx_meas_up);continue; /* skip that packet */}break;case STAT_NO_CRC:meas_nb_rx_nocrc += 1;if (!fwd_nocrc_pkt) {pthread_mutex_unlock(&mx_meas_up);continue; /* skip that packet */}break;default:MSG("WARNING: [up] received packet with unknown status %u (size %u, modulation %u, BW %u, DR %u, RSSI %.1f)\n", p->status, p->size, p->modulation, p->bandwidth, p->datarate, p->rssic);pthread_mutex_unlock(&mx_meas_up);continue; /* skip that packet */// exit(EXIT_FAILURE);}meas_up_pkt_fwd += 1;meas_up_payload_byte += p->size;}/* JSON rxpk frame format version, 8 useful chars,帧格式类型 */j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, "\"jver\":%d", PROTOCOL_JSON_RXPK_FRAME_FORMAT );if (j > 0) {buff_index += j;} else {MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));exit(EXIT_FAILURE);}/* RAW timestamp, 8-17 useful chars, 网关内部时钟 */j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"tmst\":%u", p->count_us);if (j > 0) {buff_index += j;} else {MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));exit(EXIT_FAILURE);}/* Packet RX time (GPS based), 37 useful chars, 接收Lora数据帧时间 */if (ref_ok == true) {/* convert packet timestamp to UTC absolute time */j = lgw_cnt2utc(local_ref, p->count_us, &pkt_utc_time);if (j == LGW_GPS_SUCCESS) {/* split the UNIX timestamp to its calendar components */x = gmtime(&(pkt_utc_time.tv_sec));j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"time\":\"%04i-%02i-%02iT%02i:%02i:%02i.%06liZ\"", (x->tm_year)+1900, (x->tm_mon)+1, x->tm_mday, x->tm_hour, x->tm_min, x->tm_sec, (pkt_utc_time.tv_nsec)/1000); /* ISO 8601 format */if (j > 0) {buff_index += j;} else {MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));exit(EXIT_FAILURE);}}/* convert packet timestamp to GPS absolute time */j = lgw_cnt2gps(local_ref, p->count_us, &pkt_gps_time);if (j == LGW_GPS_SUCCESS) {pkt_gps_time_ms = pkt_gps_time.tv_sec * 1E3 + pkt_gps_time.tv_nsec / 1E6;j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"tmms\":%" PRIu64 "", pkt_gps_time_ms); /* GPS time in milliseconds since 06.Jan.1980 */if (j > 0) {buff_index += j;} else {MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));exit(EXIT_FAILURE);}}}/* Fine timestamp */if (p->ftime_received == true) {j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"ftime\":%u", p->ftime);if (j > 0) {buff_index += j;} else {MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));exit(EXIT_FAILURE);}}//> 省略打包内容项开始p->if_chainp->rf_chain((double)p->freq_hz / 1e6)p->modem_idp->statusp->dataratep->bandwidthp->rssisp->snrp->freq_offsetstatus_report//> 省略打包内容项结束/* send datagram to server,打包数据上报到网络服务器 */send(sock_up, (void *)buff_up, buff_index, 0);clock_gettime(CLOCK_MONOTONIC, &send_time);pthread_mutex_lock(&mx_meas_up);meas_up_dgram_sent += 1;meas_up_network_byte += buff_index;/* wait for acknowledge (in 2 times, to catch extra packets),等待网络服务器器ACK内容 */for (i=0; i<2; ++i) {j = recv(sock_up, (void *)buff_ack, sizeof buff_ack, 0);clock_gettime(CLOCK_MONOTONIC, &recv_time);if (j == -1) {if (errno == EAGAIN) { /* timeout */continue;} else { /* server connection error */break;}} else if ((j < 4) || (buff_ack[0] != PROTOCOL_VERSION) || (buff_ack[3] != PKT_PUSH_ACK)) {//MSG("WARNING: [up] ignored invalid non-ACL packet\n");continue;} else if ((buff_ack[1] != token_h) || (buff_ack[2] != token_l)) {//MSG("WARNING: [up] ignored out-of sync ACK packet\n");continue;} else {MSG("INFO: [up] PUSH_ACK received in %i ms\n", (int)(1000 * difftimespec(recv_time, send_time)));meas_up_ack_rcv += 1;break;}}}
}

此线程主体内容就这么多,我们只了解程序基本框架功能,关于定位时间的描述会有专门文章来分析。

2.4 函数 lgw_receive() 函数实现功能:

源码路径@libloragw/loragw_hal.c

int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data) {/* Get packets from SX1302, if any */res = sx1302_fetch(&nb_pkt_fetched);/* WARNING: this needs to be called regularly by the upper layer */res = sx1302_update();/* Apply RSSI temperature compensation */res = lgw_get_temperature(¤t_temperature);res = sx1302_parse(&lgw_context, &pkt_data[nb_pkt_found]);/* Appli RSSI offset calibrated for the board */pkt_data[nb_pkt_found].rssic += CONTEXT_RF_CHAIN[pkt_data[nb_pkt_found].rf_chain].rssi_offset;pkt_data[nb_pkt_found].rssis += CONTEXT_RF_CHAIN[pkt_data[nb_pkt_found].rf_chain].rssi_offset;rssi_temperature_offset = sx1302_rssi_get_temperature_offset(&CONTEXT_RF_CHAIN[pkt_data[nb_pkt_found].rf_chain].rssi_tcomp, current_temperature);pkt_data[nb_pkt_found].rssic += rssi_temperature_offset;pkt_data[nb_pkt_found].rssis += rssi_temperature_offset;res = merge_packets(pkt_data, &nb_pkt_found);
}

此代码删减后内容、如上,基本功能总结如下:
1>. 获取 sx1302 的数据报状态, sx1302_fetch();
2>. 更新接收数据包的时间戳, sx1302_update();
3>. 获取电路板温度值,用以RSSI 温度补偿使用;
4>. 解析lora数据包内容,sx1302_parse();
5>. 双包数据合并优化接收时间戳, merge_packets().

2.5 payload[] 数据帧格式

在 sx1302_fetch() 函数中、读取Lora模块上报数据帧内容,其程序调用过程如下:

sx1302_fetch(uint8_t * nb_pkt)==> err = rx_buffer_fetch(&rx_buffer);

源码路径@libloragw/loragw_sx1302_rx.c

int rx_buffer_fetch(rx_buffer_t * self) {int i, res;uint8_t buff[2];uint8_t payload_len;uint16_t next_pkt_idx;int idx;uint16_t nb_bytes_1, nb_bytes_2;/* Check input params */CHECK_NULL(self);/* Check if there is data in the FIFO */lgw_reg_rb(SX1302_REG_RX_TOP_RX_BUFFER_NB_BYTES_MSB_RX_BUFFER_NB_BYTES, buff, sizeof buff);nb_bytes_1 = (buff[0] << 8) | (buff[1] << 0);/* Workaround for multi-byte read issue: read again and ensure new read is not lower than the previous one */lgw_reg_rb(SX1302_REG_RX_TOP_RX_BUFFER_NB_BYTES_MSB_RX_BUFFER_NB_BYTES, buff, sizeof buff);nb_bytes_2 = (buff[0] << 8) | (buff[1] << 0);self->buffer_size = (nb_bytes_2 > nb_bytes_1) ? nb_bytes_2 : nb_bytes_1;/* Fetch bytes from fifo if any */if (self->buffer_size > 0) {DEBUG_MSG   ("-----------------\n");DEBUG_PRINTF("%s: nb_bytes to be fetched: %u (%u %u)\n", __FUNCTION__, self->buffer_size, buff[1], buff[0]);memset(self->buffer, 0, sizeof self->buffer);res = lgw_mem_rb(0x4000, self->buffer, self->buffer_size, true);     //> 读取数据内容if (res != LGW_REG_SUCCESS) {printf("ERROR: Failed to read RX buffer, SPI error\n");return LGW_REG_ERROR;}/* print debug info */DEBUG_MSG("RX_BUFFER: ");for (i = 0; i < self->buffer_size; i++) {DEBUG_PRINTF("%02X ", self->buffer[i]);}DEBUG_MSG("\n");/* Sanity check: is there at least 1 complete packet in the buffer */if (self->buffer_size < (SX1302_PKT_HEAD_METADATA + SX1302_PKT_TAIL_METADATA)) {   //> 判断数据长度合法性printf("WARNING: not enough data to have a complete packet, discard rx_buffer\n");return rx_buffer_del(self);}/* Sanity check: is there a syncword at 0 ? If not, move to the first syncword found */idx = 0;while (idx <= (self->buffer_size - 2)) {                                             //> 数据同步头合法性if ((self->buffer[idx] == SX1302_PKT_SYNCWORD_BYTE_0) && (self->buffer[idx + 1] == SX1302_PKT_SYNCWORD_BYTE_1)) {DEBUG_PRINTF("INFO: syncword found at idx %d\n", idx);break;} else {printf("INFO: syncword not found at idx %d\n", idx);idx += 1;}}if (idx > self->buffer_size - 2) {printf("WARNING: no syncword found, discard rx_buffer\n");return rx_buffer_del(self);}if (idx != 0) {printf("INFO: re-sync rx_buffer at idx %d\n", idx);memmove((void *)(self->buffer), (void *)(self->buffer + idx), self->buffer_size - idx);self->buffer_size -= idx;}/* Rewind and parse buffer to get the number of packet fetched */idx = 0;while (idx < self->buffer_size) {if ((self->buffer[idx] != SX1302_PKT_SYNCWORD_BYTE_0) || (self->buffer[idx + 1] != SX1302_PKT_SYNCWORD_BYTE_1)) {printf("WARNING: syncword not found at idx %d, discard the rx_buffer\n", idx);return rx_buffer_del(self);}/* One packet found in the buffer */self->buffer_pkt_nb += 1;/* Compute the number of bytes for this packet, 获取数据包内容 */payload_len = SX1302_PKT_PAYLOAD_LENGTH(self->buffer, idx);next_pkt_idx =  SX1302_PKT_HEAD_METADATA +payload_len +SX1302_PKT_TAIL_METADATA +(2 * SX1302_PKT_NUM_TS_METRICS(self->buffer, idx + payload_len));/* Move to next packet */idx += (int)next_pkt_idx;}}/* Initialize the current buffer index to iterate on */self->buffer_index = 0;return LGW_REG_SUCCESS;
}

从 SX1302 中读取 Lora 无线接收到的数据内容。

第3节 总结

本篇内容就分析线程 thread_up 部分内容、下一篇介绍 thread_down 线程内容;然后笔者将参考loraWan的协议,进一步梳理对照此两块内容。
如果本篇文章对您有所启发或帮助、请给笔者点赞助力、鼓励笔者坚持把此系列内容尽快梳理、分享出来。谢谢。

后言

今天 csdn 给发了一篇纪念开博三周年的短文,有点小感动,继续耕耘.

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...