RISC-V MCU 导盲手套
关于本文
本项目采用沁恒RISC-V板卡/芯片,参加了2022年(第五届)全国大学生嵌入式芯片与系统设计竞赛,应用赛道为中部赛区。秉承RISC-V开源精神,本队伍选择对作品开源。
队伍名称
一零二八队
第一部分 设计概述
1.1 设计目的
为帮助视障人群更好出行,本作品设计为一款便携可穿戴式设备——导盲手套,该设备基于沁恒赤菟平台和RT-thread物联网技术,根据北斗定位模块定位,当位置处于电子围栏内部,手套下方气囊充气产生触感,处于电子围栏外部气囊放气,使用者据此提示调整路线。
1.2 应用领域
可应用于盲人导航,未来可扩展用于VR技术。
1.3 主要技术特点
基于沁恒赤菟平台和RT-thread物联网技术,并用北斗数据地图定位和超声波检测加以辅助。使用时,该项目APP先通过定位与避障,规划出电子护栏的位置和方向,使用者到达附近后会得到语音提示,气囊随之变化,使用者可根据触感调整路线从而达到导盲效果。
1.4 关键性能指标
可通过手机蓝牙连接手套进行经纬度设定,划定区域,在到达指定经纬度区域内,手套可实现充气,提醒用户到达区域,离开区域后,手套可实现放气。
1.5 主要创新点
改变了传统的拐杖避障,采用更人性化更轻松的模式,给予盲人虚拟现实的体验和与人交互的可能。
结合北斗定位模块,提供防丢保障和科学的路径规划。
第二部分 系统组成及功能说明
2.1 整体介绍
本作品主要由四部分组成:沁恒赤菟开发板、Air530北斗定位模块、手套充放气控制模块、手套主体。沁恒开发板为主要信息处理模块,负责各模块间的信息交流和数据处理。Air530模块主要负责实时定位,向赤菟开发板提供经纬度数据。手套充放气控制模块通过电磁阀和气泵,实现手套的充放气功能。
2.2 各模块介绍
开发板采用沁恒官方提供的赤菟开发板;
北斗定位模块采用合宙的Air530模块,精度高,连接快;
手套充放气模块为自主设计电路,采用了12V供压的电磁阀和气泵;
手套主体采用了可以实现充放气的医疗康复手套。
第三部分 完成情况及性能参数
3.1 可通过手机蓝牙实现经纬度设定:
- 当进入划定区域时,手套充气,LED1亮起。
- 当离开划定区域时,手套放气,LED2亮起。
第四部分 总结
4.1 可扩展之处
- 手套充放气模块需12V供压,本作品使用3节18650电池实现,过于臃肿,可通过DC-DC升压模块,用一节电池完成12V供压。
- 手套充放气模块现阶段使用洞洞板简单实现,未来可使用PCB,缩小模块体积。
- 可通过增加超声波模块达到更精确实时的避障功能。
- 增加姿态传感功能,使得手套上方屏幕可根据手部姿态显示信息来提示路人。
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);