本文介绍了瑞萨 RA-Eco-RA6E2-64PIN-V1.0 开发板通过串口读取 GPS 模块数据,解析获得时间、经纬度等数据,串口转发至 ATK-D20 模块,由 MQTT 透传实现物联网 GPS 定位时钟的项目设计。
瑞萨 RA6E2 Eco 开发板串口接收 GPS 模块 NMEA 数据并解析,通过 ATK-D20 的 MQTT 透传功能,实现时钟、经纬度数据上传至 Home Assistant 平台的项目设计。

| GPS module | RA6E2 | Note |
|---|---|---|
| RXD | TXD0 (P101) | Receive data |
| TXD | RXD0 (P100) | Transmit data |
| VCC | 3V3 | Power |
| GND | GND | Ground |
| RA6E2 | ATK-D20 | Note |
|---|---|---|
| RXD9 (P110) | TX | Receive data |
| TXD9 (P109) | RX | Transmit data |
| GND | GND | Ground |
| RA6E2 | J-Link | Note |
|---|---|---|
| SWCLK | SCL | Serial Clock |
| SWDIO | SDA | Serial Data |
| GND | GND | Ground |

动态效果见顶部视频。
文件 - 新建 - 瑞萨 C/C++ 项目 - Renesas RA ;R7FA6E2BB3CFM ,工具链选择 GNU ARM Embedded ,调试器选择 J-Link ;
New Stack - Connectivity - UART (r_sci_uart) ;

New Stack - Connectivity - UART (r_sci_uart) ;General 标签下的 Channel 为 0,名称为 g_uart0,中断回调函数命名为 user_uart0_callback,注意波特率需要修改为 9600 并与 GPS 模块匹配;Generate Project Content 按钮,生成工程代码;
在 .../src 目录下新建源文件 gps_parser.c 和头文件 gps_parser.h 用于配置 GPS 相关解析函数。
#include "hal_data.h"
#include "gps_parser.h"
#include <stdio.h>
#include <string.h>
fsp_err_t err = FSP_SUCCESS;
volatile bool uart_send_complete_flag = false;
void user_uart9_callback (uart_callback_args_t * p_args)
{
if(p_args->event == UART_EVENT_TX_COMPLETE)
{
uart_send_complete_flag = true;
}
}
/*------------- 串口重定向 -------------*/
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#endif
PUTCHAR_PROTOTYPE
{
err = R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t *)&ch, 1);
if(FSP_SUCCESS != err) __BKPT();
while(uart_send_complete_flag == false){}
uart_send_complete_flag = false;
return ch;
}
int _write(int fd,char *pBuffer,int size)
{
for(int i=0;i<size;i++)
{
__io_putchar(*pBuffer++);
}
return size;
}
// 全局变量
volatile uint8_t gps_rx_buffer[256];
volatile uint16_t gps_rx_index = 0;
volatile bool gps_line_ready = false;
gps_data_t current_gps_data;
// SCI0回调函数(GPS数据接收)
void user_uart0_callback(uart_callback_args_t * p_args)
{
if (p_args->event == UART_EVENT_RX_CHAR)
{
uint8_t rx_char = (uint8_t)p_args->data;
// 存储接收到的字符
if (gps_rx_index < (sizeof(gps_rx_buffer) - 1))
{
gps_rx_buffer[gps_rx_index++] = rx_char;
// 检查是否收到换行符(一行结束)
if (rx_char == '\\n')
{
gps_rx_buffer[gps_rx_index] = '\\0';
gps_line_ready = true;
gps_rx_index = 0;
}
}
else
{
// 缓冲区溢出,重置
gps_rx_index = 0;
}
}
}
// 初始化函数
void init_system(void)
{
fsp_err_t err;
// 初始化GPS解析器
gps_parser_init();
// 打开SCI9(printf输出)
err = R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg);
if (FSP_SUCCESS != err)
{
while(1); // 初始化失败
}
//printf("SCI9 (printf) initialized at 115200 baud\\r\\n");
// 打开SCI0(GPS数据接收)
err = R_SCI_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);
if (FSP_SUCCESS != err)
{
printf("Error: Failed to initialize SCI0 for GPS\\r\\n");
while(1);
}
//printf("SCI0 (GPS) initialized at 9600 baud\\r\\n");
//printf("Waiting for GPS data...\\r\\n\\r\\n");
}
// 处理GPS数据
void process_gps_data(void)
{
if (gps_line_ready)
{
// 禁用中断以防数据被修改
__disable_irq();
// 复制数据到本地缓冲区
char nmea_line[256];
strncpy(nmea_line, (char *)gps_rx_buffer, sizeof(nmea_line));
nmea_line[sizeof(nmea_line) - 1] = '\\0';
gps_line_ready = false;
// 使能中断
__enable_irq();
// 打印原始NMEA数据
printf("%s", nmea_line);
// 解析GPGGA语句
if (strstr(nmea_line, "$GPGGA"))
{
if (parse_gpgga(nmea_line, ¤t_gps_data))
{
print_gps_data(¤t_gps_data);
}
else
{
printf("Failed to parse GPGGA data\\r\\n");
}
}
// 可以添加其他NMEA语句的解析
else if (strstr(nmea_line, "$GPRMC"))
{
gps_time_t rmc_time;
if (parse_gprmc(nmea_line, ¤t_gps_data, &rmc_time))
{
// 如果GPGGA没有提供时间,使用GPRMC的时间
if (current_gps_data.time.hour == 0 &&
current_gps_data.time.minute == 0 &&
current_gps_data.time.second == 0)
{
current_gps_data.time = rmc_time;
}
}
//printf("GPRMC sentence received\\r\\n");
}
//printf("\\r\\n");
}
}
// 打印解析后的GPS数据
void print_parsed_gps_data(void)
{
static uint32_t last_print_time = 0;
static uint32_t current_time = 0;
current_time++;
// 每2秒打印一次解析结果
if (current_time - last_print_time >= 200)
{
last_print_time = current_time;
printf("\\r\\n=== PARSED GPS DATA ===\\r\\n");
if (current_gps_data.is_valid)
{
// 打印时间信息
print_gps_time(¤t_gps_data.time);
printf("\\r\\n");
// 打印位置信息
printf("Latitude: %.6f°\\r\\n", current_gps_data.latitude);
printf("Longitude: %.6f°\\r\\n", current_gps_data.longitude);
printf("Altitude: %.1f m\\r\\n", current_gps_data.altitude);
printf("Satellites: %d\\r\\n", current_gps_data.satellites);
printf("Fix Quality: %d\\r\\n", current_gps_data.fix_quality);
// 根据定位质量显示状态
switch (current_gps_data.fix_quality)
{
case 0: printf("Status: Invalid fix\\r\\n"); break;
case 1: printf("Status: GPS fix\\r\\n"); break;
case 2: printf("Status: DGPS fix\\r\\n"); break;
default: printf("Status: Other fix (%d)\\r\\n", current_gps_data.fix_quality); break;
}
}
else
{
// 即使没有有效定位,也显示时间和卫星信息
if (current_gps_data.time.hour != 0 ||
current_gps_data.time.minute != 0 ||
current_gps_data.time.second != 0)
{
print_gps_time(¤t_gps_data.time);
printf("\\r\\n");
}
printf("No valid GPS fix\\r\\n");
printf("Satellites in view: %d\\r\\n", current_gps_data.satellites);
printf("Waiting for satellite lock...\\r\\n");
}
printf("==========================\\r\\n");
}
}
//fsp_err_t err = FSP_SUCCESS;
int timeout_ms = 100;
void hal_entry(void)
{
/* TODO: add your own code here */
err = R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg);
assert(FSP_SUCCESS == err);
printf("RA4M2 GPS Reader Started\\r\\n");
// 系统初始化
init_system();
uint32_t status_counter = 0; // 延时计数
while(1){
process_gps_data(); // 原始GPS数据
print_parsed_gps_data(); // 解析GPS数据
// 每隔一段时间打印状态
status_counter++;
if (status_counter >= 1000) // 约每10秒
{
printf("[Status] System running...\\r\\n");
if (current_gps_data.is_valid)
{
printf("[Status] GPS fix acquired\\r\\n");
}
else
{
printf("[Status] Waiting for GPS fix\\r\\n");
}
status_counter = 0;
}
R_BSP_SoftwareDelay(10, BSP_DELAY_UNITS_MILLISECONDS); // 延时
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
GPS 模块解析代码 gps_parser.c
#include "gps_parser.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
// 初始化GPS解析器
void gps_parser_init(void)
{
// 可以在这里初始化任何需要的变量
}
// 解析GPGGA语句
bool parse_gpgga(const char *nmea_sentence, gps_data_t *gps_data)
{
if (!nmea_sentence || !gps_data || strncmp(nmea_sentence, "$GPGGA", 6) != 0)
{
return false;
}
// 初始化数据结构
memset(gps_data, 0, sizeof(gps_data_t));
gps_data->is_valid = false;
char sentence_copy[256];
strncpy(sentence_copy, nmea_sentence, sizeof(sentence_copy) - 1);
sentence_copy[sizeof(sentence_copy) - 1] = '\\0';
char *token = strtok(sentence_copy, ",");
int field_index = 0;
double latitude = 0.0, longitude = 0.0;
char lat_dir = 'N', lon_dir = 'E';
while (token != NULL)
{
switch (field_index)
{
case 1: // UTC时间
// 可以在这里解析时间
break;
case 2: // 纬度
latitude = atof(token);
break;
case 3: // 纬度方向
lat_dir = token[0];
break;
case 4: // 经度
longitude = atof(token);
break;
case 5: // 经度方向
lon_dir = token[0];
break;
case 6: // 定位质量
gps_data->fix_quality = atoi(token);
break;
case 7: // 卫星数量
gps_data->satellites = atoi(token);
break;
case 9: // 海拔高度
gps_data->altitude = atof(token);
break;
default:
break;
}
token = strtok(NULL, ",");
field_index++;
}
// 转换度分格式为十进制度
if (latitude > 0)
{
double lat_deg = (int)(latitude / 100);
double lat_min = latitude - (lat_deg * 100);
gps_data->latitude = lat_deg + (lat_min / 60.0);
if (lat_dir == 'S') gps_data->latitude = -gps_data->latitude;
}
if (longitude > 0)
{
double lon_deg = (int)(longitude / 100);
double lon_min = longitude - (lon_deg * 100);
gps_data->longitude = lon_deg + (lon_min / 60.0);
if (lon_dir == 'W') gps_data->longitude = -gps_data->longitude;
}
// 检查数据有效性
gps_data->is_valid = (gps_data->fix_quality > 0);
return gps_data->is_valid;
}
// 解析GPRMC语句
bool parse_gprmc(const char *nmea_sentence, gps_data_t *gps_data, gps_time_t *time_data)
{
// 类似的解析逻辑,可以根据需要实现
return false;
}
// 打印GPS数据
void print_gps_data(const gps_data_t *gps_data)
{
if (!gps_data || !gps_data->is_valid)
{
printf("GPS Data: No valid fix\\r\\n");
return;
}
printf("=== GPS Information ===\\r\\n");
printf("Latitude: %.6f°\\r\\n", gps_data->latitude);
printf("Longitude: %.6f°\\r\\n", gps_data->longitude);
printf("Altitude: %.1f m\\r\\n", gps_data->altitude);
printf("Satellites: %d\\r\\n", gps_data->satellites);
printf("Fix Quality: %d\\r\\n", gps_data->fix_quality);
printf("========================\\r\\n");
}
// 打印时间数据
void print_gps_time(const gps_time_t *time_data)
{
if (time_data)
{
printf("Time: %02d:%02d:%02d UTC\\r\\n",
time_data->hour, time_data->minute, time_data->second);
}
}
GPS 数据解析头文件 gps_parser.h
#ifndef GPS_PARSER_H_
#define GPS_PARSER_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
// GPS位置数据结构
typedef struct {
double latitude; // 纬度(十进制度)
double longitude; // 经度(十进制度)
float altitude; // 海拔高度(米)
uint8_t satellites; // 卫星数量
uint8_t fix_quality; // 定位质量
bool is_valid; // 数据是否有效
} gps_data_t;
// 时间数据结构
typedef struct {
uint8_t hour;
uint8_t minute;
uint8_t second;
} gps_time_t;
// 函数声明
void gps_parser_init(void);
bool parse_gpgga(const char *nmea_sentence, gps_data_t *gps_data);
bool parse_gprmc(const char *nmea_sentence, gps_data_t *gps_data, gps_time_t *time_data);
void print_gps_data(const gps_data_t *gps_data);
void print_gps_time(const gps_time_t *time_data);
#ifdef __cplusplus
}
#endif
#endif /* GPS_PARSER_H_ */
保存代码,构建工程、调试工程。

解析数据包括时间、日期、经纬度、速度等信息。
动态效果见底部视频。
进一步发送解析数据的 JSON 格式消息,便于 Home Assistant 与 MQTT 解析。
在消息输出函数 print_parsed_gps_data(void) 中添加 JSON 格式语句输出
if (current_gps_data.is_valid)
{
// 打印时间信息
//print_gps_time(¤t_gps_data.time);
// 打印位置信息
//printf("Latitude: %.6f°\\r\\n", current_gps_data.latitude); // 纬度
//printf("Longitude: %.6f°\\r\\n", current_gps_data.longitude); //经度
//printf("Altitude: %.1f m\\r\\n", current_gps_data.altitude);
//printf("Satellites: %d\\r\\n", current_gps_data.satellites);
//printf("Fix Quality: %d\\r\\n", current_gps_data.fix_quality);
printf("{\\"lat\\": %.6f, \\"lon\\": %.6f, \\"sat\\": %d, \\"time\\": %02d%02d%02d}\\r\\n",
current_gps_data.latitude,
current_gps_data.longitude,
current_gps_data.satellites,
current_gps_data.time.hour,
current_gps_data.time.minute,
(int)current_gps_data.time.second);
}
串口打印效果

这里使用 ATK-D20 WiFi DTU 模块实现串口 MQTT 透传。
ATK-D20 是由正点原子团队(ALIENTEK)自主研发的一款高性能 2.4GHz WiFi DTU 模块,主要用于实现串口设备通过 WiFi 无线方式接入网络。
详见:D20 WiFi DTU | 文档 .
模式配置 界面,工作模式选择 MQTT;保存所有参数 按钮,此时上位机自动发送 AT 指令,完成 MQTT 配置并自动重启;
详见:正点原子 D20 WiFi 模块 .
使用 MQTTX 软件测试串口透传与服务器转发。
homeassistant/gps/state;
通过 MQTT 服务器将 JSON 格式的 GPS 定位信息发送至 HA 平台,实现 GPS 物联网定位时钟。
配置 HA 平台的 YAML 文件,根据主题添加相关参数;
在 HA 安装目录下的 .../config/configuration.yaml 文件添加如下代码
mqtt:
device_tracker:
- name: "GPS Tracker"
unique_id: "atk_d20_gps_001"
state_topic: "homeassistant/sensor/aht10/state"
json_attributes_topic: "homeassistant/sensor/aht10/state"
value_template: "{{ value_json.lat }},{{ value_json.lon }}"
source_type: gps
sensor:
- name: "GPS 纬度"
unique_id: "gps_lat"
state_topic: "homeassistant/sensor/aht10/state"
value_template: "{{ value_json.lat }}"
unit_of_measurement: "°"
- name: "GPS 经度"
unique_id: "gps_lon"
state_topic: "homeassistant/sensor/aht10/state"
value_template: "{{ value_json.lon }}"
unit_of_measurement: "°"
- name: "GPS 时间"
unique_id: "gps_time"
state_topic: "homeassistant/sensor/aht10/state"
value_template: >
{% set t=value_json.time|string %}
{{ t[0:2] }}:{{ t[2:4] }}:{{ t[4:6] }} UTC
configuration.yaml 文件;开发者工具 界面 - YAML配置 标签页,点击 所有 YAML 配置 ,出现绿色背景的对号表明更新并应用配置完成。进入 HA 概览页面,编辑仪表盘,添加对应的卡片即可;


动态效果见底部视频。
瑞萨 RA6E2 开发板串口接收 GPS 模块 NMEA 数据并解析,通过 ATK-D20 的 MQTT 透传功能,实现时钟、经纬度数据上传至 Home Assistant 平台的项目设计,为该产品在物联网领域的开发设计和快速应用提供了参考。
举报
更多回帖