Lvpizz 发表于 2023-8-28 16:32:56

国二,2023电赛 【G题】空地协同智能消防系

简介:
小车上和无人机都使用STC32G12K128,其中四轴使用STC32做信息采集和下位机控制板,小车采用STC32G12K128作为主控,最终获得国二。
所有硬件信息大概会开源到嘉立创上(本人考研党,也可能在考完研后整理)。
---河南科技大学
先讲故事:
大学很精彩,大一上来到实验室,跟着学长们学习51单片机,大一下32单片机,然后是21年电赛,老师把无人机交到了我们手中,我们满怀热情的扑街了(省三),但是我们不甘心啊,于是开始沉淀。大二下的17界智能车竞赛中,每天高强度备赛,但是由于已经险些无法参赛,最终在一处山村里和河南另一所学校组成赛点(我们那届特殊,疫情原因)勉强完成比赛冲进国赛,但是在外租房成本真的太高了,我们也没有得到任何学校的支持,于是无奈告别线下决赛,遗憾国二。
正巧的是当时STC赞助的比赛,所以我们使用的芯片是STC16,从此和STC32结下不解之缘~
在完成智能车竞赛后,我们得到一段难得的清闲时光,在这段事件里,我开发了一个小游戏机(也用的是STC32,有机会也会开源,图片在最后),并准备22年河南赛的电赛,虽然我们在小车题中获得过国奖,但是我们组毅然选择飞控题,不为别的,主打一个弥补遗憾!
但是事与愿违,疫情又来了。河南省电赛本来说是延迟到3月份过完年返校后举办,然后我们就把飞机留到学校回家了,突然赛委会通知要在一周内完成所有评比工作(这都没东西咋比啊),遗憾止步省二(还是凭借ppt获得的)。
真的太难受了,当时这个飞控的板子就画出来了,不过族中还是在23年电赛用上了。
然后就是最后一次比赛--23年电赛,我们这时候其实已经没有大一大二那么多经历放在电赛上了,因为都要考研。但是飞控题真的给我留下太多遗憾了,所以我们还是打算搏一把,我几乎整个暑假每天都用半天时间在弄飞机(太痛了),这对考研党来说有点致命,但是没办法,真的很想拿国一。
唉,最后我们奋战4天3夜,但是由于测试的时候没识别到火源,可能摄像头算法的问题也可能是K210没读到SD卡,总之发挥不是很理想,回来的时候我们几个心情都非常沉重,国一肯定没戏了,有点遗憾。
结果出来还是国二。
没办法,竞赛总是伴随遗憾,这大概率是我大学生涯最后一个比赛了,虽然没能达到巅峰,但总归要记录一下,一路来也踩过不少坑,写下此篇,方便大家学习入门。
有些细节方面可能会在有时间以后详细展开,现在先大致介绍一下,也算给初学者点思路。

                           
先上图吧
其中小车因为我们参加过17界智能车(也是国二),所以直接用了以前的车模。但是因为我们的车车都传给学弟了,所以搭车的时候也是慌手慌脚,最终没能达到很好的效果{:4_167:}。主板方面是抄的逐飞的板子,就不再过多展示,下面着重说明飞机STC32G12K128主控的设计和使用。

主板设计:


主板原理图并不是非常复杂,只是提供了很多接口方便连接传感器和数据采集,毕竟主要工作是做下位机嘛,所以画的并不是很复杂。


该主控板采用STC宏晶官方最新推出单片机STC32G12K128作为主控芯片,上有两个K210深度学习摄像头嵌槽,用于固定k210以及进行数据通讯,并且外引4路GPIO、3路AD、两路串口、两个可控LED灯、三个可编程按键以及一个IIC接口方便后续扩展模块等资源。其中IIC扩展部分可以直接排线相连接16路PWM舵机控制模块,对于有些需要依靠舵机转向或者抓取的任务也可以快速扩展。右侧K210直插的中间有四个孔位,可以与我们后续介绍的12v转5v稳压降压模块依靠铜柱直接相连接。
主板资源概述(来自于报告图片):






上面有两个K210的卡槽,可以插K210模块,因为近些年的无人机题都有识别任务,并且可能牵扯到前方和下方两个方向,所以设计两个卡槽来进行通讯。




电源设计:


与上述主控搭配使用,可对3C锂电池进行降压稳压处理,输出5V稳定电源,并且连接方式采用铜柱代替导线,无线化设计减少飞行扰动。



使用螺丝代替导线进行连接,这样可能会有一些问题(大佬轻喷),但是我们目前用了这么久是没遇到问题的,而且非常方便更换。

飞控方面
采用匿名飞控,懂得都懂,非常好用,方便二次开发,与飞控采用串口连接通讯,在本次设计中,重物的抛洒工作(舵机完成)、摄像头的信息反馈与控制、蜂鸣器的控制、LED的控制信号等都由飞控下达指令,然后由STC32进行传感器的控制,为了保持通讯的实时性,使用921600的波特率(之前都一直用的115200,没想到升高后数据依然稳定)。


还有就是为了方便图像的调试,自制了上位机方便观察图像,配合K210上面镶嵌的ESP32实现组网通讯(K210处理完图像直接通过ESP32发送至电脑)

通过图中信息可看的,灯点被识别后被涂满,在识别区密度满足要求后,返回数传信息给上位机,飞机通过调试信息完成对点位的矫正工作。假设目标值为 Setpoint,当前系统输出值为 ProcessVariable,定义以下变量:偏差: Error = Setpoint – ProcessVariable积分误差: Integral = Integral + Error * Δt,其中 Δt 为采样时间间隔导数: Derivative = (Error - PreviousError) / Δt 通过公式ControlOutput = Kp * Error + Ki * Integral + Kd * Derivative   (1) 计算位置式PID偏差,对位置环进行校准。
“消防车”路径规划在系统中设计每个街区每个边都一一对应一条路径,首先无人机向小车发送火情位置,小车在接受到信息后计算火情所在街区,由于小车无法驶入街区,小车会将火情坐标位置转化成离街区“火情”最近的位置、需要到达“火情”街区的边。每个路径数据包括包括数个转向操作其中包括方向dir,进行拐弯位置的四元列表,其中x、y表示需要转向的区域矩形的左下角坐标,w、h表示需要转向的区域矩形的宽和高。这样利用车载“UWB”就可以实现小车在指定位置转向。
“消防车”对无人机轨迹刻画与里程计算本系统中使用UWB进行室内定位,无人机将UWB信息每秒通过蓝牙发送给“消防车”,“消防车”在接受到数据后将实际坐标转化为屏幕中的像素坐标,之后计算本次接收到的坐标值与上次接收到的坐标值之间的距离,累加进总里程中并显示。
我们的定位采用的是UWB的方案,UWB建系PID校准,然后状态机逐点搜索,大概就是这么个流程。
以下是STC32中关键代码的局部展示(4天3夜写的有点乱~)

/**********************************
**函数名:Isr_timerInit
**作用 :定时器ISR总初始化
************************************/      
void Isr_timerInit()
{
//      //最多定时20ms左右,多了会炸!!!!!
//      pit_timer_ms(TIM_0, 5);                                                                                                                                        //使用TIMER作为系统时钟,时间5ms一次,最高优先级
//      PT0 = 1;    //定时器0优先级中断低位
//      PT0H =1;      //定时器0优先级中断高位
//      
//      pit_timer_ms(TIM_1, 20);                                                                                                                              //使用TIMER作为系统时钟,时间20ms一次,次高优先级
//      PT1 = 0;    //定时器1优先级中断低位
//      PT1H =1;      //定时器1优先级中断高位
//      
      pit_timer_ms(TIM_4, 1);                                                                                                                //使用TIMER作为系统时钟,时间1ms一次

}

/*1ms线程*/
void task_1ms()
{

               
}

/*5ms线程*/
void task_5ms()
{
      
      LED_control();      
}

/*20ms线程*/
void task_20ms()
{
      Servo_actuator_control();
      Uart1_Sendbuff();      //向两个K210传输数据
      
                        
      
      Uart4_Sendbuff(); //转发k210数据                        

      //
                        
//                        uart_putbuff(UART_1,"561",3);

//                        set_pwm(0,15);
//                        set_pwm(0,10);
//                   delay_ms(1000);               
                              

}
/*31ms线程*/
void task_31ms()
{
                Uart3_Sendbuff();      //转发k210数据
}
/*32ms线程*/
void task_32ms()
{
                Uart2_Sendbuff();      //转发k210数据
}
/*100ms线程*/
void task_100ms()
{
//      UWB_Receive();//uwb接收函数
//      UWB_X =UWB_Y =51231;
//      UWB_NewDate =1;
//      UWB_Send();
}
/*300ms线程*/
void task_300ms()
{
      
      KeyAndLedRecall();


//      BEEP==0?BEEP =1:BEEP=0;
}

/*****************任务管理**************************/

/*****************用户屏蔽**************************/

//创建任务配置
static schedule_task_t schedule_task[] =
{
//task_2ms(函数名) 2 (执行周期)2(初始化计时器) 0(任务执行标志)
      {task_1ms, 1, 1, 0}      ,
      {task_5ms, 5, 5, 0},
      {task_20ms, 20,20, 0},
      {task_31ms,31 ,31 ,0},
      {task_32ms,32,32,0},
      {task_100ms, 100,100, 0},
      {task_300ms, 300, 300, 0},
};

//根据数组长度,判断任务数量
#define TASK_NUM (sizeof(schedule_task)/sizeof(schedule_task_t))//记得改定时器值

void task_interrupt(void)
{
      uint8 index = 0;
      // 循环判断
      for (index = 0; index < TASK_NUM; index++)
      {
                // 判断计时器是否到时间
                if (schedule_task.run_timer)    //不为0
                {
                        // 计时器减1
                        schedule_task.run_timer--;
                        //判断计时器是否到时间
                        if (0 == schedule_task.run_timer)
                        {
                              // 恢复倒计时器的时间
                              schedule_task.run_timer = schedule_task.interval_time;
                              // 任务标志置1
                              schedule_task.run_sign = 1;
                        }
                }
      }
}

void task_process(void)
{
      uint8 index = 0;
      
      // 循环判断任务
      for (index = 0; index < TASK_NUM; index++)
      {
                if (schedule_task.run_sign)
                {
                        // 清除任务标志
                        schedule_task.run_sign = 0;
                        // 使用函数指针,执行任务,
                        schedule_task.task_func();
                }
      }
      
}stc32游戏机:





神农鼎 发表于 2023-8-28 17:28:32

开心,STC32自由飞翔

Lvpizz 发表于 2023-9-19 11:35:33

再来两张飞机特写,方便大家参考结构

Lvpizz 发表于 2023-10-9 14:59:15

图传如下

Lvpizz 发表于 2023-10-9 15:00:29

测试K210代码块
def ToBytes(num):
    num_H = (num >> 8) & 0xFF
    num_L = num & 0xFF
    return

def JoinBytes(Bytes):
    low_byte = Bytes
    high_byte = Bytes
    return (high_byte << 8) | low_byte

def GetMinDistance(Radar):
    Radar.ExpressGetData(10)
    DataList = Radar.ReadRadarData()
    if DataList:
      min_radar_data = min(DataList, key=lambda radar_data: radar_data.distance)
      return min_radar_data.ToBytes()
    else:
      return

def SetAngleScope(Scope,Radar):
    MinAngle = JoinBytes(Scope)
    MaxAngle = JoinBytes(Scope)
    Maxdistance = JoinBytes(Scope)
    Radar.SetScope(int(MinAngle),(MaxAngle))

def GetAngleScope(Radar):
    minangle,maxangle,maxdistance = Radar.GetScope()
    return ToBytes(minangle)+ToBytes(maxangle)+ToBytes(maxdistance)


def FindfrequentBarCode(List):
    counter = {}
    for s in List:
      if s not in counter:
            counter = 0
      counter += 1
    Frequentstr = max(counter, key=counter.get)
    return int(Frequentstr)

def IdentifyBarCode(Batch,TimeOut):
    timeout = TimeOut
    DataList = []
    while(len(DataList) < Batch and timeout > 0):
      img = sensor.snapshot()
      barcodes = img.find_barcodes()
      if barcodes:
            for barcode in barcodes:
                barcodeData = barcode.payload()
                (x, y, w, h) = barcode.rect()
                #img.draw_rectangle(barcode.rect())
                #img.draw_string(x, y, barcodeData, color=(0,255,0), scale=1)
                #lcd.display(img)
                DataList = DataList +
      else:
            timeout -= 1
    if len(DataList) > Batch//2:
      return FindfrequentBarCode(DataList)
    return -1


def BarcodeTask(Uart,Radar):
    Data =
    #IdentifyResult = IdentifyBarCode(10,5)
    #if IdentifyResult == -1:
    Data = Data +
    Result = GetMinDistance(Radar)
    Data = Data + Result
    Scope = GetAngleScope(Radar)
    Data = Data + Scope
    Uart.SendData(Data)

def CalculateCenter(rect):
    x, y, width, height = rect
    center_x = x + width // 2
    center_y = y + height // 2
    return

def AverageCoordinate(List):
    x_List, y_List = zip(*List)
    x_avg = sum(x_List) / len(x_List)
    y_avg = sum(y_List) / len(y_List)
    return

#Batch:识别Batch帧的数据平均成一帧 TimeOut:尝试TimeOut次未达到收集不齐Batch帧率,收集多少算多少
def SearchPark(YoloV2Task, Batch, TimeOut):
    ResultList = []
    timeout = TimeOut
    while(len(ResultList) < Batch and timeout > 0):
      img = sensor.snapshot()
      SearchResults = kpu.run_yolo2(YoloV2Task,img)
      if SearchResults:
            timeout = TimeOut
            for Result in SearchResults:
                img.draw_rectangle(Result.rect())
                Coordinate = CalculateCenter(Result.rect())
                ResultList.append(Coordinate)
      #lcd.display(img)
      timeout -= 1
    if ResultList:
      return ResultList
    else:
      return

def SearchParkTask(MyUart):
    Data = #发送时带模式号
    ParkingList = SearchPark(ParkingTask, 1, 10)
    if ParkingList != 0xff:
      ParkImgCoordinate = AverageCoordinate(ParkingList)
    else:
      ParkImgCoordinate = ParkingList
    #ParkCoordinate = [(int)(ParkImgCoordinate-112)//1,(int)(112-ParkImgCoordinate)//1] # 计算以最中心点为原点的坐标
    ParkCoordinate = [(int)(ParkImgCoordinate),(int)(ParkImgCoordinate)]
    Data = Data + ParkCoordinate
    MyUart.SendData(Data)

# Batch:识别多少次 TimeOut:识别不到重试次数
def JudgeColorBlobs(Batch, TimeOut):
    Color = (37, 95, -28, -6, -39, 29)
    ColorRoi = (54,54,112,112)
    timeout = TimeOut
    SuccessCount = 0
    while(SuccessCount < Batch and timeout > 0):
      Picture = sensor.snapshot()
      ColorBlobs = Picture.find_blobs(,roi=ColorRoi, x_stride = 5,y_stride = 5,pixels_threshold=50,merge=True)
      if ColorBlobs:
            SuccessCount += 1
      else:
            timeout -= 1
    if SuccessCount == Batch:
      return 1
    return 0

def JudgeColorTask(MyUart):
    Data = #发送时带模式号
    Result = JudgeColorBlobs(5,10)
    Data = Data +
    MyUart.SendData(Data)

def GetCoordinateTask(MyUart):
    Data = #发送时带模式号
    Result = CD.FindXY()
    Data = Data + [(int)(Result),(int)(Result)]
    MyUart.SendData(Data)


OldRevDatas =
Count = 0
while True:
    RevDatas = UartToStc32.ReadData()# 串口读到的模式标志为是持久性的 stc发送一次改变模式 直到下一次收到数据后
    if RevDatas:
      RevCmd = RevDatas
    if RevCmd != 0x00 and RevCmd != 0x11 and RevCmd != 0x01 and RevCmd != 0x02 and RevCmd != 0x03 and RevCmd != 0x04 and RevCmd != 0x0F and RevCmd != 0xF0:
      UartToStc32.RcvData = OldRevDatas
    if RevCmd == 0x01:
      BarcodeTask(UartToStc32,MyRadar)
    elif RevCmd == 0x11:
      if len(RevDatas)>=7:
            SetAngleScope(RevDatas,MyRadar)
      UartToStc32.RcvData =
    elif RevCmd == 0x02:
      MyRadar.PauseScan()
      SearchParkTask(UartToStc32)
    elif RevCmd == 0x03:
      MyRadar.PauseScan()
      JudgeColorTask(UartToStc32)
    elif RevCmd == 0x04:
      GetCoordinateTask(UartToStc32)
    elif RevCmd == 0x0F:
      MyData.TakePhoto()
      UartToStc32.SendData(+ToBytes(MyData.datanum-MyData.nownum+1))
      UartToStc32.RcvData = OldRevDatas
    elif RevCmd == 0x0E:
      MapImg = MyRadar.CreatMapping()
      MyDataOrigin.TakePhoto(img = MapImg)
      DataList = MyRadar.ReadRadarData()
      MyDbscan.DataList.clear()
      MyDbscan.DataList = DataList
      ClusterList, NoiseList = MyDbscan.GetCluster()
      MapImg = MyRadar.CreatDbscanMapping(ClusterList, NoiseList)
      MyData.TakePhoto(img = MapImg)
      MapImg = MyRadar.CreatAveragRadarDataMapping(ClusterList, MyDbscan)
      MyDataAfter.TakePhoto(img = MapImg)
      UartToStc32.SendData(+ToBytes(MyData.datanum-MyData.nownum+1))
      UartToStc32.RcvData = OldRevDatas
    elif RevCmd == 0x00:
      Count += 1
      MyRadar.PauseScan()
      if Count >= 50:
            Count = 0
            UartToStc32.SendData()# 停机
    OldRevDatas = RevDatas[:]
gc.collect()

Lvpizz 发表于 2023-10-9 15:01:00

激光雷达训练照片

Lvpizz 发表于 2023-10-9 15:01:31

详细照片1

Lvpizz 发表于 2023-10-9 15:02:18

详细照片2

Lvpizz 发表于 2023-10-9 15:02:39

雷达检测处理图像

Lvpizz 发表于 2023-10-9 15:03:02

详细照片1
页: [1] 2 3
查看完整版本: 国二,2023电赛 【G题】空地协同智能消防系