这个基于STM32的北斗/GPS双模定位系统,是我去年为一个农业无人机项目开发的定位子系统。核心功能是通过蓝牙主从通信,将定位模块获取的经纬度信息实时传输到VB开发的上位机界面显示。整套系统在实际田间测试中表现稳定,定位精度达到了2.5米以内,完全满足农用设备的作业需求。
主控芯片选用了经典的STM32F103C8T6,这款Cortex-M3内核的MCU在性价比和性能上达到了很好的平衡。其内置的3个USART接口正好满足本项目需求:
定位模块采用的是中科微ATGM332D,选择它主要基于三个考量:
蓝牙模块选用HC-05主从一体模块,具体型号是汇承科技的HC-05-6。这个选择经历了实际测试对比:
硬件连接关键点:定位模块的TX接STM32的USART2_RX(PA3),蓝牙模块的RX接USART3_TX(PB10)。特别注意蓝牙模块的VCC要接3.3V,5V会损坏模块。
系统采用主从式架构设计,数据流向如下:
这种架构的优势在于:
ATGM332D模块默认输出频率为1Hz,会连续发送如下语句:
code复制$GPGGA,...*hh
$GPGLL,...*hh
$GPRMC,...*hh
$GPVTG,...*hh
我们只需要关注$GPRMC语句,它包含完整的定位信息:
code复制$GPRMC,085120.307,A,2232.8946,N,11355.2353,E,0.00,0.00,220623,,,A*68
在串口中断中采用环形缓冲区存储数据:
c复制#define GPS_BUF_SIZE 256
volatile uint8_t gpsBuffer[GPS_BUF_SIZE];
volatile uint16_t rxIndex = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART2) {
gpsBuffer[rxIndex++] = rxData;
if(rxIndex >= GPS_BUF_SIZE) rxIndex = 0;
HAL_UART_Receive_IT(&huart2, &rxData, 1);
}
}
解析时采用状态机提高效率:
c复制typedef enum {
WAIT_START,
IN_MESSAGE,
CHECK_CRC
} ParserState;
ParserState state = WAIT_START;
for(int i=0; i<rxIndex; i++) {
switch(state) {
case WAIT_START:
if(gpsBuffer[i] == '$') state = IN_MESSAGE;
break;
case IN_MESSAGE:
if(gpsBuffer[i] == '*') state = CHECK_CRC;
break;
case CHECK_CRC:
// 校验计算逻辑
break;
}
}
自定义的传输协议格式如下:
code复制$POS,<纬度>,<经度>,<速度>,<方向>,<UTC时间>,<校验和>*hh\r\n
示例数据包:
code复制$POS,22.54824,113.92059,0.5,145,085120.307,73*6B\r\n
蓝牙发送前进行CRC8校验:
c复制uint8_t crc8(const uint8_t *data, size_t len) {
uint8_t crc = 0x00;
while(len--) {
crc ^= *data++;
for(uint8_t i=0; i<8; i++)
crc = (crc & 0x80) ? (crc << 1)^0x07 : crc << 1;
}
return crc;
}
通过以下措施将系统功耗从120mA降至65mA:
c复制AT+ROLE=1\r\n // 设置为主机
AT+CMODE=1\r\n // 任意地址连接
AT+SLEEP=1\r\n // 使能休眠模式
c复制__HAL_RCC_USART1_CLK_DISABLE();
__HAL_RCC_ADC1_CLK_DISABLE();
使用SerialPort控件时需要注意:
数据接收核心代码:
vb复制Private WithEvents SerialPort1 As New IO.Ports.SerialPort
Private rxBuffer As New StringBuilder
Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
Dim rawData As String = SerialPort1.ReadExisting()
Me.Invoke(Sub()
rxBuffer.Append(rawData)
Dim packets() As String = rxBuffer.ToString().Split("$"c)
For Each packet In packets
If packet.StartsWith("POS,") AndAlso packet.Contains("*") Then
ProcessPacket(packet)
End If
Next
rxBuffer.Clear()
End Sub)
End Sub
采用GMap.NET控件显示位置,关键配置步骤:
vb复制GMapProviders.GoogleMap.ApiKey = "您的API密钥"
GMapControl1.MapProvider = GMapProviders.GoogleMap
GMapControl1.Position = New PointLatLng(22.0, 113.0)
GMapControl1.MinZoom = 5
GMapControl1.MaxZoom = 18
GMapControl1.Zoom = 15
坐标系转换方法(WGS84转GCJ-02):
vb复制Private Function Wgs84ToGcj02(wgsLat As Double, wgsLng As Double) As PointLatLng
Const ee = 0.00669342162296594323
Const a = 6378245.0
If OutOfChina(wgsLat, wgsLng) Then
Return New PointLatLng(wgsLat, wgsLng)
End If
Dim dLat = TransformLat(wgsLng - 105.0, wgsLat - 35.0)
Dim dLng = TransformLng(wgsLng - 105.0, wgsLat - 35.0)
Dim radLat = wgsLat / 180.0 * Math.PI
Dim magic = Math.Sin(radLat)
magic = 1 - ee * magic * magic
Dim sqrtMagic = Math.Sqrt(magic)
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI)
dLng = (dLng * 180.0) / (a / sqrtMagic * Math.Cos(radLat) * Math.PI)
Return New PointLatLng(wgsLat + dLat, wgsLng + dLng)
End Function
采用SQLite数据库存储历史轨迹,优化措施包括:
数据库初始化脚本:
sql复制PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
CREATE TABLE IF NOT EXISTS positions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME NOT NULL,
latitude REAL NOT NULL,
longitude REAL NOT NULL,
speed REAL,
heading REAL
);
CREATE INDEX IF NOT EXISTS idx_positions_time ON positions(timestamp);
CREATE INDEX IF NOT EXISTS idx_positions_location ON positions(latitude, longitude);
在实际测试中发现以下问题及解决方案:
连接中断问题:
c复制AT+INQM=1,5,10\r\n // 查询模式:RSSI+名称
AT+INQC=1\r\n // 查询结果格式简化
AT+POLAR=1,1\r\n // 设置PIO11为连接状态指示
数据传输丢包:
c复制uint8_t resend_count = 0;
while(!bluetooth_ack && resend_count < 3) {
send_packet(data);
HAL_Delay(200);
resend_count++;
}
通过实测发现以下改进措施可提升精度:
天线摆放原则:
软件滤波算法:
c复制#define FILTER_WINDOW 5
double lat_window[FILTER_WINDOW] = {0};
double lng_window[FILTER_WINDOW] = {0};
void filter_position(double lat, double lng) {
static uint8_t index = 0;
lat_window[index] = lat;
lng_window[index] = lng;
index = (index + 1) % FILTER_WINDOW;
double sum_lat = 0, sum_lng = 0;
for(int i=0; i<FILTER_WINDOW; i++) {
sum_lat += lat_window[i];
sum_lng += lng_window[i];
}
current_lat = sum_lat / FILTER_WINDOW;
current_lng = sum_lng / FILTER_WINDOW;
}
多系统定位策略:
VB程序常见性能问题及解决方案:
界面卡顿:
vb复制Me.SetStyle(ControlStyles.OptimizedDoubleBuffer _
Or ControlStyles.AllPaintingInWmPaint _
Or ControlStyles.UserPaint, True)
内存泄漏:
vb复制Private Sub CleanupMarkers()
If GMapControl1.Overlays.Count > 10 Then
GMapControl1.Overlays.RemoveAt(0)
End If
End Sub
数据加载慢:
vb复制Const PAGE_SIZE As Integer = 500
Private currentPage As Integer = 0
Private Sub LoadHistoryData()
Using conn As New SQLiteConnection(connString)
conn.Open()
Dim cmdText = $"SELECT * FROM positions ORDER BY timestamp DESC LIMIT {PAGE_SIZE} OFFSET {currentPage * PAGE_SIZE}"
Using cmd As New SQLiteCommand(cmdText, conn)
'...执行查询...'
End Using
End Using
currentPage += 1
End Sub
在实际部署后,根据用户反馈总结了以下改进计划:
通信距离扩展:
多设备组网:
增强定位功能:
c复制// 惯性导航补偿算法
void ins_update(double dt, double accel[3], double gyro[3]) {
// 姿态解算
quaternion_update(gyro, dt);
// 速度位置更新
velocity[0] += accel[0] * dt;
velocity[1] += accel[1] * dt;
position[0] += velocity[0] * dt;
position[1] += velocity[1] * dt;
}
云端服务集成:
电源管理升级:
这个项目从原型到稳定运行历时3个月,最大的收获是认识到嵌入式系统开发中硬件可靠性的重要性。有一次因为一个劣质的USB转串口模块,我们团队花了整整两天排查通信故障。后来我们建立了严格的硬件测试流程,所有外设模块都必须通过72小时老化测试才能集成到系统中。