This commit is contained in:
2025-06-18 09:13:39 +08:00
commit d5f6744fc8
12 changed files with 752 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

37
include/README Normal file
View File

@ -0,0 +1,37 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

26
platformio.ini Normal file
View File

@ -0,0 +1,26 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:nucleo_l476rg]
platform = ststm32
board = nucleo_l476rg
; change microcontroller
board_build.mcu = stm32l476rgt6
; change MCU frequency
board_build.f_cpu = 80000000L
upload_protocol = stlink
lib_deps = mikalhart/TinyGPSPlus@^1.1.0
framework = arduino

237
src/STP-23L.c Normal file
View File

@ -0,0 +1,237 @@
#include "STP-23L.h"
#include <Arduino.h>
#include <stdio.h>
LidarPointTypedef Pack_Data[12]; /* 雷达接收的数据储存在这个变量之中 */
LidarPointTypedef Pack_sum; /* 输出结果储存 */
extern uint16_t receive_cnt;
extern uint8_t confidence;
extern uint16_t distance,noise,reftof;
extern uint32_t peak,intg;
void parseData(uint8_t temp_data) {
static uint8_t state = 0; // 状态位
static uint8_t crc = 0; // 校验和
static uint8_t cnt = 0; // 用于一帧12个点的计数
static uint8_t PACK_FLAG = 0; // 命令标志位
static uint8_t data_len = 0; // 数据长度
static uint32_t timestamp = 0; // 时间戳
static uint8_t state_flag = 1; // 转入数据接收标志位
if(state < 4) { // 起始符验证 前4个数据均为0xAA
if(temp_data == HEADER) state++;
else state = 0;
}
else if(state < 10 && state > 3) {
switch(state) {
case 4:
if(temp_data == device_address) { // 设备地址验证
state++;
crc += temp_data;
break;
}
else state = 0, crc = 0;
case 5:
if(temp_data == PACK_GET_DISTANCE) { // 获取测量数据命令
PACK_FLAG = PACK_GET_DISTANCE;
state++;
crc += temp_data;
break;
}
else if(temp_data == PACK_RESET_SYSTEM) { // 复位命令
PACK_FLAG = PACK_RESET_SYSTEM;
state++;
crc += temp_data;
break;
}
else if(temp_data == PACK_STOP) { // 停止测量数据传输命令
PACK_FLAG = PACK_STOP;
state++;
crc += temp_data;
break;
}
else if(temp_data == PACK_ACK) { // 应答码命令
PACK_FLAG = PACK_ACK;
state++;
crc += temp_data;
break;
}
else if(temp_data == PACK_VERSION) { // 获取传感器信息命令
PACK_FLAG = PACK_VERSION;
state++;
crc += temp_data;
break;
}
else state = 0, crc = 0;
case 6:
if(temp_data == chunk_offset) { // 偏移地址
state++;
crc += temp_data;
break;
}
else state = 0, crc = 0;
case 7:
if(temp_data == chunk_offset) {
state++;
crc += temp_data;
break;
}
else state = 0, crc = 0;
case 8:
data_len = (uint16_t)temp_data; // 数据长度低八位
state++;
crc += temp_data;
break;
case 9:
data_len = data_len + ((uint16_t)temp_data << 8); // 数据长度高八位
state++;
crc += temp_data;
break;
default: break;
}
}
else if(state == 10) state_flag = 0; // 由switch跳出来时state为10但temp_data仍为距离长度高八位数据需跳过一次中断
if(PACK_FLAG == PACK_GET_DISTANCE && state_flag == 0) { // 获取一帧数据并校验
if(state > 9) {
if(state < 190) {
static uint8_t state_num;
state_num = (state - 10) % 15;
switch(state_num) {
case 0:
Pack_Data[cnt].distance = (uint16_t)temp_data; // 距离数据低八位
crc += temp_data;
state++;
break;
case 1:
Pack_Data[cnt].distance = ((uint16_t)temp_data << 8) + Pack_Data[cnt].distance; // 距离数据
crc += temp_data;
state++;
break;
case 2:
Pack_Data[cnt].noise = (uint16_t)temp_data; // 环境噪音低八位
crc += temp_data;
state++;
break;
case 3:
Pack_Data[cnt].noise = ((uint16_t)temp_data << 8) + Pack_Data[cnt].noise; // 环境噪音
crc += temp_data;
state++;
break;
case 4:
Pack_Data[cnt].peak = (uint32_t)temp_data; // 接受强度信息低八位
crc += temp_data;
state++;
break;
case 5:
Pack_Data[cnt].peak = ((uint32_t)temp_data << 8) + Pack_Data[cnt].peak;
crc += temp_data;
state++;
break;
case 6:
Pack_Data[cnt].peak = ((uint32_t)temp_data << 16) + Pack_Data[cnt].peak;
crc += temp_data;
state++;
break;
case 7:
Pack_Data[cnt].peak = ((uint32_t)temp_data << 24) + Pack_Data[cnt].peak; // 接受强度信息
crc += temp_data;
state++;
break;
case 8:
Pack_Data[cnt].confidence = temp_data; // 置信度
crc += temp_data;
state++;
break;
case 9:
Pack_Data[cnt].intg = (uint32_t)temp_data; // 积分次数低八位
crc += temp_data;
state++;
break;
case 10:
Pack_Data[cnt].intg = ((uint32_t)temp_data << 8) + Pack_Data[cnt].intg;
crc += temp_data;
state++;
break;
case 11:
Pack_Data[cnt].intg = ((uint32_t)temp_data << 16) + Pack_Data[cnt].intg;
crc += temp_data;
state++;
break;
case 12:
Pack_Data[cnt].intg = ((uint32_t)temp_data << 24) + Pack_Data[cnt].intg; // 积分次数
crc += temp_data;
state++;
break;
case 13:
Pack_Data[cnt].reftof = (int16_t)temp_data; // 温度表征值低八位
crc += temp_data;
state++;
break;
case 14:
Pack_Data[cnt].reftof = ((int16_t)temp_data << 8) + Pack_Data[cnt].reftof; // 温度表征值
crc += temp_data;
state++;
cnt++; // 进入下一个测量点
break;
default: break;
}
}
// 时间戳
if(state == 191) timestamp = temp_data, state++, crc += temp_data;
else if(state == 192) timestamp = ((uint32_t)temp_data << 8) + timestamp, state++, crc += temp_data;
else if(state == 193) timestamp = ((uint32_t)temp_data << 16) + timestamp, state++, crc += temp_data;
else if(state == 194) timestamp = ((uint32_t)temp_data << 24) + timestamp, state++, crc += temp_data;
else if(state == 195) {
if(temp_data == crc) { // 校验成功
data_process(); // 数据处理函数,完成一帧之后可进行数据处理
receive_cnt++; // 输出接收到正确数据的次数
}
distance = Pack_Data[0].distance;
crc = 0;
state = 0;
state_flag = 1;
cnt = 0; // 复位
}
if(state == 190) state++; // state等于190的数据实际是第12个测距点的最后一位数据数据已经取走使用
}
}
}
void data_process(void) {
static uint8_t cnt = 0;
uint8_t i;
static uint16_t count = 0;
static uint32_t sum = 0;
LidarPointTypedef Pack_sum;
for(i = 0; i < 12; i++) { // 12个点取平均
if(Pack_Data[i].distance != 0) { // 去除0的点
count++;
Pack_sum.distance += Pack_Data[i].distance;
Pack_sum.noise += Pack_Data[i].noise;
Pack_sum.peak += Pack_Data[i].peak;
Pack_sum.confidence += Pack_Data[i].confidence;
Pack_sum.intg += Pack_Data[i].intg;
Pack_sum.reftof += Pack_Data[i].reftof;
}
}
if(count != 0) {
distance = Pack_sum.distance / count;
noise = Pack_sum.noise / count;
peak = Pack_sum.peak / count;
confidence = Pack_sum.confidence / count;
intg = Pack_sum.intg / count;
reftof = Pack_sum.reftof / count;
Pack_sum.distance = 0;
Pack_sum.noise = 0;
Pack_sum.peak = 0;
Pack_sum.confidence = 0;
Pack_sum.intg = 0;
Pack_sum.reftof = 0;
count = 0;
}
}

43
src/STP-23L.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef __STP23L_H
#define __STP23L_H
#include <stdint.h>
// 如果是 C++ 编译器,使用 extern "C"
#ifdef __cplusplus
extern "C" {
#endif
#define HEADER 0xAA /* 起始符 */
#define device_address 0x00 /* 设备地址 */
#define chunk_offset 0x00 /* 偏移地址命令 */
#define PACK_GET_DISTANCE 0x02 /* 获取测量数据命令 */
#define PACK_RESET_SYSTEM 0x0D /* 复位命令 */
#define PACK_STOP 0x0F /* 停止测量数据传输命令 */
#define PACK_ACK 0x10 /* 应答码命令 */
#define PACK_VERSION 0x14 /* 获取传感器信息命令 */
typedef struct {
int16_t distance; /* 距离数据:测量目标距离单位 mm */
uint16_t noise; /* 环境噪声:当前测量环境下的外部环境噪声,越大说明噪声越大 */
uint32_t peak; /* 接收强度信息:测量目标反射回的光强度 */
uint8_t confidence; /* 置信度:由环境噪声和接收强度信息融合后的测量点的可信度 */
uint32_t intg; /* 积分次数:当前传感器测量的积分次数 */
int16_t reftof; /* 温度表征值:测量芯片内部温度变化表征值,只是一个温度变化量无法与真实温度对应 */
} LidarPointTypedef;
extern uint16_t receive_cnt;
extern uint8_t confidence;
extern uint16_t distance, noise, reftof;
extern uint32_t peak, intg;
extern LidarPointTypedef Pack_sum;
void parseData(uint8_t temp_data);
void data_process(void);
#ifdef __cplusplus
}
#endif
#endif /* __STP_23L_H */

116
src/i2c_communication.cpp Normal file
View File

@ -0,0 +1,116 @@
#include "i2c_communication.h"
#include <TinyGPS++.h>
#include "main.h"
extern uint16_t receive_cnt;
extern TinyGPSPlus gps;
extern uint16_t distance;
// 缓冲区用于发送结构体数据
uint8_t i2c_tx_buffer[sizeof(hh3_slave_data)];
// 初始化 I2C 从机
void init_i2c_slave() {
Wire.setSDA(I2C_SDA_PIN);
Wire.setSCL(I2C_SCL_PIN);
//Wire.Pins(16,17);
Wire.begin(SLAVE_ADDRESS); // 设置从机地址为 0x55
Wire.onRequest(onRequestEvent); // 注册请求回调 ***改用主机命令0x01
Wire.onReceive(onReceiveEvent); // 注册接收回调
}
// 当主机请求数据时调用
void onRequestEvent() {
update_sensor_data(); // 确保数据是最新的
memcpy(i2c_tx_buffer, &sensor_data, sizeof(sensor_data));
Wire.write(i2c_tx_buffer, sizeof(sensor_data));
}
void power_init(){
pinMode(RANGING_POWER_PIN, OUTPUT);
pinMode(GPS_POWER_PIN, OUTPUT);
}
// 当主机发送命令时调用
void onReceiveEvent(int bytes) {
while (Wire.available()) {
uint8_t cmd = Wire.read();
switch (cmd) {
case CMD_GPS_POWER_OFF:
turn_off_gps();
break;
case CMD_RANGING_POWER_OFF:
turn_off_ranging();
break;
case CMD_GPS_POWER_ON:
turn_on_gps();
break;
case CMD_RANGING_POWER_ON:
turn_on_ranging();
break;
case CMD_MASTER_GET_SLAVE_DATA:
onRequestEvent(); // 主动调用一次数据发送
break;
default:
Serial.printf("Unknown command received: 0x%02X\n", cmd);
break;
}
}
}
// 更新传感器状态和数据
static uint8_t last_receive_cnt = 0;
void update_sensor_data() {
//gps
if (millis() > 5000 && gps.charsProcessed() < 10 && digitalRead(GPS_POWER_PIN) == HIGH) {
sensor_data.gps_sta = 0x00; // 无设备
sensor_data.gps_lat = 0.0f;
sensor_data.gps_lon = 0.0f;
} else if (digitalRead(GPS_POWER_PIN) == LOW){
sensor_data.gps_sta = 0x01; // 关机
sensor_data.gps_lat = 0.0f;
sensor_data.gps_lon = 0.0f;
}else if (!gps.location.isUpdated()) {
sensor_data.gps_sta = 0x02; // 未搜到信号
sensor_data.gps_lat = 0.0f;
sensor_data.gps_lon = 0.0f;
} else {
sensor_data.gps_sta = 0x03; // 正常工作
sensor_data.gps_lat = gps.location.lat();
sensor_data.gps_lon = gps.location.lng();
}
//测距
if (receive_cnt == last_receive_cnt && digitalRead(RANGING_POWER_PIN) == HIGH) {
sensor_data.height_sta = 0x00; // 无设备
sensor_data.height = 0.0f;
}else if (digitalRead(RANGING_POWER_PIN) == LOW){
sensor_data.height_sta = 0x01; // 关机
sensor_data.height = 0.0f;
}else {
last_receive_cnt = receive_cnt;
sensor_data.height_sta = 0x02; // 有效高度
sensor_data.height = (float)distance / 1000.0f; // mm -> m
}
}
// 电源控制
void turn_off_gps() {
digitalWrite(GPS_POWER_PIN, LOW);
//Serial.println("Turning OFF GPS Power");
}
void turn_on_gps() {
digitalWrite(GPS_POWER_PIN, HIGH);
//Serial.println("Turning ON GPS Power");
}
void turn_off_ranging() {
digitalWrite(RANGING_POWER_PIN, LOW);
//Serial.println("Turning OFF Ranging Module");
}
void turn_on_ranging() {
digitalWrite(RANGING_POWER_PIN, HIGH);
//Serial.println("Turning ON Ranging Module");
}

36
src/i2c_communication.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <Arduino.h>
#include <Wire.h>
#include <stdint.h>
// 命令定义
#define CMD_MASTER_GET_SLAVE_DATA 0x01
#define CMD_GPS_POWER_OFF 0x02
#define CMD_RANGING_POWER_OFF 0x03
#define CMD_GPS_POWER_ON 0x04
#define CMD_RANGING_POWER_ON 0x05
// 数据结构定义
typedef struct {
uint8_t gps_sta; // 0x00 ~ 0x03
uint8_t height_sta; // 0x00 ~ 0x01
float gps_lat;
float gps_lon;
float height;
} hh3_slave_data;
extern hh3_slave_data sensor_data;
// 函数声明
void power_init();
void update_sensor_data();
void turn_off_gps();
void turn_on_gps();
void turn_off_ranging();
void turn_on_ranging();
// I²C 从机接口
void init_i2c_slave();
void onRequestEvent();
void onReceiveEvent(int bytes);

148
src/main.cpp Normal file
View File

@ -0,0 +1,148 @@
#include "main.h"
HardwareSerial rangingSerial(RANGING_RX_PIN, RANGING_TX_PIN);
HardwareSerial gpsSerial(GPS_RX_PIN, GPS_TX_PIN);
HardwareSerial printSerial(PRINT_RX_PIN,PRINT_TX_PIN);
TinyGPSPlus gps;
// 全局变量
uint16_t receive_cnt = 0; // 计算成功接收数据帧次数
uint8_t confidence = 0;
uint16_t distance = 0, noise = 0, reftof = 0;
uint32_t peak = 0, intg = 0;
extern LidarPointTypedef Pack_sum; // 输出结果储存
// 实例化共享数据结构体
hh3_slave_data sensor_data = {0};
// //每当主机请求数据时,该函数便会执行
// //在setup()中,该函数被注册为一个事件
// void requestEvent() {
// Wire.write("hello "); // 用6B的信息回应主机的请求hello后带一个空格
// }
void setup() {
// Wire.begin(8); // Wire初始化, 并以从设备地址#8的身份加入i2c总线
// Wire.onRequest(requestEvent); // 注册一个IIC事件用于响应主机的数据请求
// 初始化 I2C 从机通信
init_i2c_slave();
rangingSerial.begin(RANGING_BAUDRATE); // 激光雷达
gpsSerial.begin(GPS_BAUDRATE); //RX引脚、TX引脚
printSerial.begin(PRINT_BAUDRATE); // 调试输出串口RX, TX
power_init();
// pinMode(PD14, OUTPUT);
// pinMode(PD15, OUTPUT);
digitalWrite(RANGING_POWER_PIN, HIGH);
digitalWrite(GPS_POWER_PIN, HIGH);
printSerial.println("Initialized...");
}
void loop() {
// 处理主串口(激光雷达数据)
while (rangingSerial.available()) {
parseData(rangingSerial.read());
//ranging_displayInfo();
}
// 处理 GPS 数据
while (gpsSerial.available() > 0)
gps.encode(gpsSerial.read());
//gps_displayInfo();
// If 5000 milliseconds pass and there are no characters coming in
// over the software serial port, show a "No GPS detected" error
// if (millis() > 5000 && gps.charsProcessed() < 10)
// {
// Serial.println("No GPS detected");
// while(true);
// }
}
void ranging_displayInfo()
{
// 定期打印数据
static unsigned long lastPrintTime = 0;
if (millis() - lastPrintTime > 100) {
lastPrintTime = millis();
printSerial.printf("Successfully received: %d times\n", receive_cnt);
printSerial.printf("Distance: %d mm\n", distance);
printSerial.printf("Environment noise: %d\n", noise);
printSerial.printf("Received signal strength: %d\n", peak);
printSerial.printf("Confidence: %d\n", confidence);
printSerial.printf("Integration count: %d\n", intg);
printSerial.printf("Temperature characterization: %d\n", reftof);
printSerial.println("----------------------------");
}
}
void gps_displayInfo()
{
if (gps.location.isValid())
{
printSerial.print("Latitude: ");
printSerial.println(gps.location.lat(), 6);
printSerial.print("Longitude: ");
printSerial.println(gps.location.lng(), 6);
printSerial.print("Altitude: ");
printSerial.println(gps.altitude.meters());
}
else
{
printSerial.println("Location: Not Available");
}
printSerial.print("Date: ");
if (gps.date.isValid())
{
printSerial.print(gps.date.month());
printSerial.print("/");
printSerial.print(gps.date.day());
printSerial.print("/");
printSerial.println(gps.date.year());
}
else
{
printSerial.println("Not Available");
}
printSerial.print("Time: ");
if (gps.time.isValid())
{
if (gps.time.hour() < 10) Serial.print(F("0"));
printSerial.print(gps.time.hour());
printSerial.print(":");
if (gps.time.minute() < 10) Serial.print(F("0"));
printSerial.print(gps.time.minute());
printSerial.print(":");
if (gps.time.second() < 10) Serial.print(F("0"));
printSerial.print(gps.time.second());
printSerial.print(".");
if (gps.time.centisecond() < 10) Serial.print(F("0"));
printSerial.println(gps.time.centisecond());
}
else
{
printSerial.println("Not Available");
}
printSerial.println();
printSerial.println();
delay(1000);
}

37
src/main.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef MAIN_H
#define MAIN_H
#include <Arduino.h>
#include <Wire.h>
#include "STP-23L.h"
#include <HardwareSerial.h>
#include <TinyGPS++.h>
#include "i2c_communication.h"
// 全局引脚常量,// 使用 constexpr 定义编译时常量
constexpr int RANGING_BAUDRATE = 230400;
constexpr int GPS_BAUDRATE = 9600;
constexpr int PRINT_BAUDRATE = 115200;
//测距
constexpr int RANGING_POWER_PIN = PB0;
constexpr int RANGING_RX_PIN = PC5; //USART3_RX
constexpr int RANGING_TX_PIN = PC4; //USART3_TX
//gps
constexpr int GPS_POWER_PIN = PB1;
constexpr int GPS_RX_PIN = PA3; //USART2_RX
constexpr int GPS_TX_PIN = PA2; //USART2_TX
//串口
constexpr int PRINT_RX_PIN = PA1; //UART4_RX
constexpr int PRINT_TX_PIN = PA0; //UART4_TX
//IIC
constexpr int I2C_SDA_PIN = PB7; // I2C1_SDA
constexpr int I2C_SCL_PIN = PB6; // I2C1_SCL
constexpr int SLAVE_ADDRESS = 0x55;
// 函数声明
void ranging_displayInfo();
void gps_displayInfo();
#endif // MAIN_H

11
test/README Normal file
View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html