RISC-V MCU 导盲手套

发布于:2023-01-15 ⋅ 阅读:(576) ⋅ 点赞:(0)

关于本文

本项目采用沁恒RISC-V板卡/芯片,参加了2022年(第五届)全国大学生嵌入式芯片与系统设计竞赛,应用赛道为中部赛区。秉承RISC-V开源精神,本队伍选择对作品开源。

队伍名称

一零二八队

第一部分 设计概述

1.1 设计目的

为帮助视障人群更好出行,本作品设计为一款便携可穿戴式设备——导盲手套,该设备基于沁恒赤菟平台和RT-thread物联网技术,根据北斗定位模块定位,当位置处于电子围栏内部,手套下方气囊充气产生触感,处于电子围栏外部气囊放气,使用者据此提示调整路线。

1.2 应用领域

可应用于盲人导航,未来可扩展用于VR技术。

1.3 主要技术特点

基于沁恒赤菟平台和RT-thread物联网技术,并用北斗数据地图定位和超声波检测加以辅助。使用时,该项目APP先通过定位与避障,规划出电子护栏的位置和方向,使用者到达附近后会得到语音提示,气囊随之变化,使用者可根据触感调整路线从而达到导盲效果。

1.4 关键性能指标

可通过手机蓝牙连接手套进行经纬度设定,划定区域,在到达指定经纬度区域内,手套可实现充气,提醒用户到达区域,离开区域后,手套可实现放气。

1.5 主要创新点

  1. 改变了传统的拐杖避障,采用更人性化更轻松的模式,给予盲人虚拟现实的体验和与人交互的可能。

  2. 结合北斗定位模块,提供防丢保障和科学的路径规划。

第二部分 系统组成及功能说明

2.1 整体介绍

本作品主要由四部分组成:沁恒赤菟开发板、Air530北斗定位模块、手套充放气控制模块、手套主体。沁恒开发板为主要信息处理模块,负责各模块间的信息交流和数据处理。Air530模块主要负责实时定位,向赤菟开发板提供经纬度数据。手套充放气控制模块通过电磁阀和气泵,实现手套的充放气功能。
导盲手套

2.2 各模块介绍

  1. 开发板采用沁恒官方提供的赤菟开发板;
    开发板

  2. 北斗定位模块采用合宙的Air530模块,精度高,连接快;
    合宙Air530

  3. 手套充放气模块为自主设计电路,采用了12V供压的电磁阀和气泵;
    电磁阀和气泵

  4. 手套主体采用了可以实现充放气的医疗康复手套。
    手套

第三部分 完成情况及性能参数

3.1 可通过手机蓝牙实现经纬度设定:

蓝牙发送
反馈信息

  1. 当进入划定区域时,手套充气,LED1亮起。

充气
当前位置

  1. 当离开划定区域时,手套放气,LED2亮起。
    放气
    当前位置

第四部分 总结

4.1 可扩展之处

  1. 手套充放气模块需12V供压,本作品使用3节18650电池实现,过于臃肿,可通过DC-DC升压模块,用一节电池完成12V供压。
  2. 手套充放气模块现阶段使用洞洞板简单实现,未来可使用PCB,缩小模块体积。
  3. 可通过增加超声波模块达到更精确实时的避障功能。
  4. 增加姿态传感功能,使得手套上方屏幕可根据手部姿态显示信息来提示路人。

4.2 心得体会

深入理解了北斗定位模块使用原理,学会了NMEA0183 协议及其使用,深入学习串口通信和RT-Thread操作系统使用。

第五部分 相关链接

Air530定位模块使用手册
NMEA0183 协议手册(中文)
项目gitee页面

第六部分 详细方案设计

收集导航模块数据

找到两个扩展接口D8和D9作为TX与RX,接收来自导航模块的数据。将两个端口重定向到USART3,波特率经查阅导航模块手册为9600

// USART3
rt_pin_mode(55, PIN_MODE_OUTPUT_AF_PP);  // PIND8 USART3_TX
rt_pin_mode(56, PIN_MODE_INPUT);         // PIND9 USARt3_RX
// 重定位
GPIO_PinRemapConfig(GPIO_FullRemap_USART3, ENABLE);
// 初始化USART3
USART_InitStruct.USART_BaudRate = 9600;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART3, &USART_InitStruct);
// 使能串口3
USART_Cmd(USART3, ENABLE);

导航模块定时发送数据,采用接收中断方式接收数据

// 中断初始化
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);

处理接收数据

收到的数据遵循NMEA-0831协议,经查阅知所需数据(经纬度)在#GNRMC行。
由于接收中断为每个字符产生一次,故只需检测以”#”开头的行后面是否为”GNRMC”,然后将整行用字符串变量存储。

if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
 {
      USART_ClearITPendingBit(USART3,USART_IT_RXNE);
      tmp = USART_ReceiveData(USART3);
      if(tmp == '$' && flag == 0)
      {
          flag = 1;
      }
      if(flag && tmp != '$')
      {
          buf[index++] = tmp;
      }
      if(flag && (tmp == '$' || index == 100) && index > 3)
      {
          if(buf[0] == 'G' && buf[1] == 'N' && buf[2] == 'R')
          {
              strcpy(gnrmc, buf);
              printf("%s", gnrmc);
              rt_pin_write(AIR_IN_PIN, PIN_LOW);
              rt_pin_write(AIR_OUT_PIN, PIN_LOW);
              break;
          }
          index = 0;
          memset(buf, 0, sizeof(buf));
      }
  }

根据协议,利用数逗号方法,提取出N/S前的数字作为维度,提取出E/W前的数字作为经度

// 经纬度
longt = 0; lat = 0;
u_int8_t i, c, count = 0;
for(i = 0; i < 100; i++)
{
    c = gnrmc[i];
    if(c == ',') count++;
    if(count == 3)
    {
        c = gnrmc[i + 1];
        if(c >= '0' && c <= '9') lat = lat * 10 + (u_int32_t)(c - '0');
        else if(c == '.');
        else if(c == ',')
        {
            count++;
            i++;
        }
    }
    if(count == 5)
    {
        c = gnrmc[i + 1];
        if(c >= '0' && c <= '9') longt = longt * 10 + (u_int32_t)(c - '0');
        else if(c == '.');
        else if(c == ',') break;
    }
}

实现充放气

实际上利用两个扩展接口实现充放气功能,这里用两个LED灯指示。初始状态两个LED等均灭,进入范围后LED1亮,LED2灭;出范围后均亮
实现方法,直接判断读取到的当前经纬度是否在设置范围内
如果在范围内,开始充气,到界限后停止充气;出范围后,放气。

 if(longt <= longt_right && longt >= longt_left && lat <= lat_up && lat >= lat_down)
 {
     // 亮LED1
     rt_pin_write(LED1_PIN, PIN_LOW);
     rt_pin_write(LED2_PIN, PIN_HIGH);
     // 充气
     if(has_gas <= 4)
     {
         rt_pin_write(AIR_IN_PIN, PIN_HIGH);
         has_gas++;
     }
 }
 else
 {
     if(rt_pin_read(LED1_PIN) == 0)
     {
         // 亮LED2
         rt_pin_write(LED2_PIN, PIN_LOW);
         // 放气, 无论什么状态,放气 1个时间单位, 置充气位为0;
         if(has_gas)
         {
             rt_pin_write(AIR_OUT_PIN, PIN_HIGH);
             has_gas = 0;
         }

     }
 }

实现蓝牙通信

接收导航数据相同,另找两个扩展接口C2与C3作为TX与RX端,重定位为UART7,需要另打开蓝牙模式

    rt_pin_mode(32, PIN_MODE_INPUT_PULLUP);  //PINA7 BLUETOOTH MODE

在更改边界时,通过uart7向蓝牙发送信息,进行选择,信息的接收依旧使用rx中断接收

UART_SendString(UART7, "Change boundary? 1 - Yes  2- No");

// 发送当前经纬度
// 发送当前经纬度
char *s1 = "\r\nCurrent Latitude:    ",
     *s2 = "\r\nCurrent Longitude: ";
char t[20];
UART_SendString(UART7, s1);
itoa(lat, t, 10);
UART_SendString(UART7, t);
UART_SendString(UART7, s2);
itoa(longt, t, 10);
UART_SendString(UART7, t);

UART_SendString(UART7, "\r\nYour option: ");
u_int8_t op, count, print_flag;
u_int8_t num[20], index, c;
memset(num, 0, sizeof(num));

依次输入四个边界点,以’#’为中止

while(USART_GetITStatus(UART7, USART_IT_RXNE) == RESET);
 // 接收选择
 if(USART_GetITStatus(UART7, USART_IT_RXNE) != RESET)
 {
     USART_ClearITPendingBit(UART7, USART_IT_RXNE);
     op = USART_ReceiveData(UART7);
     if(op == '2')
     {
         UART_SendString(UART7, "2\r\nBoundary unchange!\r\n");
         return;
     }
     UART_SendString(UART7, "1\r\nInput the new boundary ended with '#':\r\n");
     // 1 - 维度上边界
     count = 1;
     print_flag = 1;
     index = 0;
     while(1)
     {
         if(print_flag)
         {
             switch(count)
             {
             case 1: UART_SendString(UART7, "Latitude_Up:        "); break;
             case 2: UART_SendString(UART7, "\r\nLatitude_Down:   "); break;
             case 3: UART_SendString(UART7, "\r\nLongitude_Left:   "); break;
             case 4: UART_SendString(UART7, "\r\nLongitude_Right: "); break;
             case 5: UART_SendString(UART7, "\r\nEnd!"); return;
             }
         }
         while(USART_GetITStatus(UART7, USART_IT_RXNE) == RESET);
         if(USART_GetITStatus(UART7, USART_IT_RXNE) != RESET)
         {
             USART_ClearITPendingBit(UART7, USART_IT_RXNE);
             c = USART_ReceiveData(UART7);
             print_flag = 0;
             num[index++] = c;
             if(c == '#')
             {
                 if(count == 1)      lat_up = to_int(num);
                 else if(count == 2) lat_down = to_int(num);
                 else if(count == 3) longt_left = to_int(num);
                 else if(count == 4) longt_right = to_int(num);
                 UART_SendString(UART7, num);
                 memset(num, 0, sizeof(num));
                 count++;
                 index = 0;
                 print_flag = 1;
             }
         }
     }
 }

第七部分 总体代码

/* 该文件基于RT-thread系统,仅显示main.c内容 */
#include "ch32v30x.h"
#include <rtthread.h>
#include <rthw.h>
#include "drivers/pin.h"
#include <debug.h>
#include <string.h>
#include <stdlib.h>

#define LED1_PIN    42   //PE11
#define LED2_PIN    43   //PE12

#define AIR_IN_PIN  57   //PD10
#define AIR_OUT_PIN 58   //PD11

#define CH_EDGE_IT_INIT 5

// 经纬度限界
u_int32_t longt_left, longt_right, lat_up, lat_down;

// 经纬度
u_int32_t longt, lat;

// 显示经纬度
u_int8_t started = 0;

// 更改限界中断间隔
u_int8_t change_edge_it = CH_EDGE_IT_INIT;

// 气体容量
u_int8_t has_gas = 0;

void Init_UART()
{
    //USART1
    rt_pin_mode(68, PIN_MODE_OUTPUT_AF_PP);  //PINA9  USART1_TX
    rt_pin_mode(69, PIN_MODE_INPUT);         //PINA10 USARt1_RX

    //USART3
    rt_pin_mode(55, PIN_MODE_OUTPUT_AF_PP);  //PIND8 USART3_TX
    rt_pin_mode(56, PIN_MODE_INPUT);         //PIND9 USARt3_RX

    // UART7
    rt_pin_mode(17, PIN_MODE_OUTPUT_AF_PP);  //PINC2 USART7_TX
    rt_pin_mode(18, PIN_MODE_INPUT);         //PINC3 USART7_RX
    rt_pin_mode(32, PIN_MODE_INPUT_PULLUP);  //PINA7 BLUETOOTH MODE

    USART_InitTypeDef USART_InitStruct;

    // 开时钟(USART1 TX-A9 RX-A10)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1| RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3| RCC_APB1Periph_UART7, ENABLE);

    GPIO_PinRemapConfig(GPIO_FullRemap_USART3, ENABLE);

    // 初始化USART3
    USART_InitStruct.USART_BaudRate = 9600;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART3, &USART_InitStruct);

    // 使能串口3
    USART_Cmd(USART3, ENABLE);

    // 初始化USART1
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1, &USART_InitStruct);

    // 使能串口1
    USART_Cmd(USART1, ENABLE);

    // 初始化USART7
    USART_InitStruct.USART_BaudRate = 9600;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_Init(UART7, &USART_InitStruct);

    // 使能串口7
    USART_Cmd(UART7, ENABLE);

    // 中断初始化
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
    USART_ITConfig(UART7,  USART_IT_RXNE, ENABLE);

}

void UART_SendByte(USART_TypeDef * USARTx,uint8_t data)
{
    USART_SendData(USARTx, data);
    while(!USART_GetFlagStatus(USARTx, USART_FLAG_TXE));
}

void UART_SendString(USART_TypeDef * USARTx,char* string)
{
    char *str = string;
    while(*str)
    {
        UART_SendByte(USARTx,*str);
        str++;
    }
}

u_int32_t to_int(u_int8_t *num)
{
    u_int8_t i;
    u_int32_t result = 0;
    for(i = 0; i < 20; i++)
    {
        if(num[i] == '#')
            return result;
        else
        {
            result = result * 10 + (num[i] - '0');
        }
    }
    return 0;
}

void change_edge()
{
    UART_SendString(UART7, "Change boundary? 1 - Yes  2- No");

    // 发送当前经纬度
    // 发送当前经纬度
    char *s1 = "\r\nCurrent Latitude:    ",
         *s2 = "\r\nCurrent Longitude: ";
    char t[20];
    UART_SendString(UART7, s1);
    itoa(lat, t, 10);
    UART_SendString(UART7, t);
    UART_SendString(UART7, s2);
    itoa(longt, t, 10);
    UART_SendString(UART7, t);


    UART_SendString(UART7, "\r\nYour option: ");
    u_int8_t op, count, print_flag;
    u_int8_t num[20], index, c;
    memset(num, 0, sizeof(num));

    while(USART_GetITStatus(UART7, USART_IT_RXNE) == RESET);
    // 接收选择
    if(USART_GetITStatus(UART7, USART_IT_RXNE) != RESET)
    {
        USART_ClearITPendingBit(UART7, USART_IT_RXNE);
        op = USART_ReceiveData(UART7);
        if(op == '2')
        {
            UART_SendString(UART7, "2\r\nBoundary unchange!\r\n");
            return;
        }
        UART_SendString(UART7, "1\r\nInput the new boundary ended with '#':\r\n");
        // 1 - 维度上边界
        count = 1;
        print_flag = 1;
        index = 0;
        while(1)
        {
            if(print_flag)
            {
                switch(count)
                {
                case 1: UART_SendString(UART7, "Latitude_Up:        "); break;
                case 2: UART_SendString(UART7, "\r\nLatitude_Down:   "); break;
                case 3: UART_SendString(UART7, "\r\nLongitude_Left:   "); break;
                case 4: UART_SendString(UART7, "\r\nLongitude_Right: "); break;
                case 5: UART_SendString(UART7, "\r\nEnd!"); return;
                }
            }
            while(USART_GetITStatus(UART7, USART_IT_RXNE) == RESET);
            if(USART_GetITStatus(UART7, USART_IT_RXNE) != RESET)
            {
                USART_ClearITPendingBit(UART7, USART_IT_RXNE);
                c = USART_ReceiveData(UART7);
                print_flag = 0;
                num[index++] = c;
                if(c == '#')
                {
                    if(count == 1)      lat_up = to_int(num);
                    else if(count == 2) lat_down = to_int(num);
                    else if(count == 3) longt_left = to_int(num);
                    else if(count == 4) longt_right = to_int(num);
                    UART_SendString(UART7, num);
                    memset(num, 0, sizeof(num));
                    count++;
                    index = 0;
                    print_flag = 1;
                }
            }
        }
    }
    return;
}

void Daomang()
{
    Init_UART();
    rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);
    rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);
    rt_pin_mode(AIR_IN_PIN, PIN_MODE_OUTPUT);
    rt_pin_mode(AIR_OUT_PIN, PIN_MODE_OUTPUT);

    uint8_t capcity = 100;
    char buf[capcity], gnrmc[capcity];
    uint8_t index, tmp;
    int flag;

    rt_pin_write(LED1_PIN, PIN_HIGH);
    rt_pin_write(LED2_PIN, PIN_HIGH);

    lat_up      = 30462290;      // 上
    lat_down    = 30462250;      // 下

    longt_left  = 120182900;     // 左
    longt_right = 120183100;     // 右

//    change_edge();

    while(1)
    {
        index = 0; flag = 0;
        memset(buf, 0, sizeof(buf));
        memset(gnrmc, 0, sizeof(gnrmc));
        while(1)
        {
            if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
            {
                USART_ClearITPendingBit(USART3,USART_IT_RXNE);
                tmp = USART_ReceiveData(USART3);
                if(tmp == '$' && flag == 0)
                {
                    flag = 1;
                }
                if(flag && tmp != '$')
                {
                    buf[index++] = tmp;
                }
                if(flag && (tmp == '$' || index == 100) && index > 3)
                {
                    if(buf[0] == 'G' && buf[1] == 'N' && buf[2] == 'R')
                    {
                        strcpy(gnrmc, buf);
                        printf("%s", gnrmc);
                        rt_pin_write(AIR_IN_PIN, PIN_LOW);
                        rt_pin_write(AIR_OUT_PIN, PIN_LOW);
                        break;
                    }
                    index = 0;
                    memset(buf, 0, sizeof(buf));
                }
            }
        }
        // 经纬度
        longt = 0; lat = 0;
        u_int8_t i, c, count = 0;
        for(i = 0; i < 100; i++)
        {
            c = gnrmc[i];
            if(c == ',') count++;
            if(count == 3)
            {
                c = gnrmc[i + 1];
                if(c >= '0' && c <= '9') lat = lat * 10 + (u_int32_t)(c - '0');
                else if(c == '.');
                else if(c == ',')
                {
                    count++;
                    i++;
                }
            }
            if(count == 5)
            {
                c = gnrmc[i + 1];
                if(c >= '0' && c <= '9') longt = longt * 10 + (u_int32_t)(c - '0');
                else if(c == '.');
                else if(c == ',') break;
            }
        }


        // 更改经纬度限界
        if(started == 0){
            started ++;
        }
        else if(started == 1){
            change_edge();
            started ++;
        }
        else{
            printf("Latitude:  %d\r\nLongitude: %d\r\n", lat, longt);
            printf("lau: %d\r\nlad: %d\r\nlol: %d\r\nlor: %d\r\n",lat_up,lat_down,longt_left,longt_right);
        }
        if(longt <= longt_right && longt >= longt_left && lat <= lat_up && lat >= lat_down)
        {
            // 亮LED1
            rt_pin_write(LED1_PIN, PIN_LOW);
            rt_pin_write(LED2_PIN, PIN_HIGH);
            // 充气
            if(has_gas <= 4)
            {
                rt_pin_write(AIR_IN_PIN, PIN_HIGH);
                has_gas++;
            }

        }
        else
        {
            if(rt_pin_read(LED1_PIN) == 0)
            {
                // 亮LED2
                rt_pin_write(LED2_PIN, PIN_LOW);
                // 放气, 无论什么状态,放气 1个时间单位, 置充气位为0;
                if(has_gas)
                {
                    rt_pin_write(AIR_OUT_PIN, PIN_HIGH);
                    has_gas = 0;
                }

            }
        }
    }
}

int main()
{
    rt_kprintf("\r\n MCU: CH32V307\r\n");
    rt_kprintf(" SysClk: %dHz\r\n",SystemCoreClock);
    rt_kprintf(" www.wch.cn\r\n");

    while(1)
    {
        rt_thread_mdelay(500);
    }
}
MSH_CMD_EXPORT(Daomang,test);


本文含有隐藏内容,请 开通VIP 后查看