当前位置: 首页 > news >正文

使用STM32实现一个线性代数计算器

文章目录

  • 背景
  • 挑战与困难
    • 如何整合编译?
      • error: non-ASM statement in naked function is not supported
      • error: '#pragma import' is an ARM Compiler 5 extension, and is not supported by ARM Compiler 6
      • error: redefinition of '__FILE'
    • 改造demo中的cout
    • 改造delete运算符
  • 总结与展望

背景

在文章使用C/C++实现线性代数计算——环境bringup 中,围绕Eigen介绍了使用在各种场合下的环境bringup和编译问题,本文接着上文的内容,详细记录一下基于MDK5+Eigen+STM32来实现一个线性代数计算器的过程,里面还是有很多问题可以学习分享一下的。

挑战与困难

首先,Eigen是一个C++框架的开源项目,虽说不依赖什么OS之类的东西,但是若要完全跑在STM32裸机中,还是有一些问题和适配难点需要解决的,本文先详细介绍一下如果规避这些问题,给大家一个参考,解决思路可能不是最优的,如果你有更好的解决思路,不妨评论一下,我们一起相互学习交流一下~~

本文使用的环境时MDK5.35 + eigen3.4.0 + STM32F103ZE系列开发板

如何整合编译?

在 使用C/C++实现线性代数计算——环境bringup 一文的文末,简单提了一下在Keil中的编译环境配置,借助MDK官方提供的启动文件,能编译链接生成一个bin文件。如果要想真的在STM32里面跑起来是远远不够的,首先里面的printf函数、std::cout输出流在stm32上就没有,所以为了验证编译出的bin文件到底能不能直接烧写到stm32里面直接跑,笔者找了一个stm32串口demo程序,在里面接入Eigen,然后调用Eigen提供的矩阵运算API,通过串口发出来(本文末会将改造后的demo发出来供大家参考)。具体实现的效果如下:
串口输出
项目中关键文件目录树如下:
目录树结构
添加官方提供的example.c文件和cpp文件(详见 使用C/C++实现线性代数计算——环境bringup ,后面不再赘述),不过需要修改一下,具体如下:

  1. 考虑到在main.c里已经有一个main函数了,并且后面会主要借助main.c里的main函数初始化外设,所以需要将example.c里面有一个int main函数改个名字,void test_eigen(),然后在main函数里加上如下语句:main函数改造
    (PS:我这里的改法仅仅是为了方便验证Eigen的功能,一个完整、规范的项目最好不要瞎几把跨文件用extern声明函数,项目复杂之后可读性会非常差,这是一个反例,大家不要学我!)

然后,按照之前的文章,修改Target里面的ARM Compiler选择版本6,C/C++里面版本和之前文章一样即可。然后点击编译,会有很多错误,我们一个一个来解决。

error: non-ASM statement in naked function is not supported

完整的报错是:…/CORE/core_cm3.c(445): error: non-ASM statement in naked function is not supported
这个报错的根因是ARM Ver6 Compiler不支持旧版本的core_cm3.c里面的C中的汇编指令,解决的思路有三个:

  1. 编译器换回V5(换回V5不支持C++编译,行不通);
  2. 把core_cm3.c和core_cm3.h升级到新版本(这个没试过,不过应该可以,用新版的stcCubeMX生成一个demo,看看里面是不是新的,然后替换掉旧的好不好使,笔者没试这个路子);
  3. 把core_cm3.c从项目中删了(试了,可以,为啥可以删掉?可能是用V5编译生成过.o文件,即便从项目里把core_cm3.c删掉了,链接时仍能用缓存的目标文件);

error: ‘#pragma import’ is an ARM Compiler 5 extension, and is not supported by ARM Compiler 6

具体报错信息是:
…/SYSTEM/usart/usart.c(39): error: ‘#pragma import’ is an ARM Compiler 5 extension, and is not supported by ARM Compiler 6 [-Warmcc-pragma-import]
#pragma import(__use_no_semihosting)
^
这个错误说的很明白,V6版本的arm编译器不支持’#pragma import’ 语法,那为啥要用到 ‘#pragma import’ 语句呢?回到代码里看一下,这一行具体是#pragma import(__use_no_semihosting) ,它的作用是关掉arm的半主机模式。

所谓半主机模式:是用于ARM目标的一种机制;可将来自STM32单片机应用程序的输入输出请求传送至运行仿真器的PC主机。使用此机制可以启用C库中的函数,如printf()和scanf(),来使用PC主机的屏幕和键盘。禁掉后方可使用重定向手段,将printf函数的标准输出重定向到串口上,这样就可以用printf函数打印字符串,然后在串口里读到数据,这种骚操作非常适合调试,具体细节可以参考:【stm32串口打印】printf函数的使用方法,注意事项,原理以及拓展,个人学习理解总结。

那如果用了V6版本的编译器,就不能禁掉半主机模式了,下面会将平提方法,我们先接着看错误。

error: redefinition of ‘__FILE’

具体报错信息是这个:
…/SYSTEM/usart/usart.c(41): error: redefinition of ‘__FILE’
struct __FILE
对应的源码还是:

//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ int handle; }; FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   USART1->DR = (u8) ch;      return ch;
}

原因是在stdio.h里面已经有一个struct __FILE定义了,为啥这里又要重新定义一个struct __FILE?还是因为要用printf函数,因为printf函数本质上就是将格式化后的字符串打印到标准输出上,本质上标准输出就是一个FILE类型的变量(一切皆文件?以后再探究了,这里先不展开了)。

所以综合来看,我们只需要先不用printf函数打印字符串应该就能规避上述两个问题了,这里笔者提供的平提方案是:

#define USART_SEND_BUFFER_SIZE	(128)
#define USART_RECV_BUFFER_SIZE	(128)
void USART_SendString(char *str)
{uint8_t idx = 0;while (*(str+idx)){USART_SendData(USART1, *(str+idx));while(USART_GetFlagStatus(USART1, USART_FLAG_TC)!=SET);idx++;}
}void u_printf(const char *format,...)
{char String[USART_SEND_BUFFER_SIZE] = {0};__va_list arg;//定义一个参数列表变量va_list是一个类型名,arg是变量名va_start(arg,format);	 //从format位置开始接收参数表放在arg里面//sprintf打印位置是String,格式化字符串是format,参数表是arg,对于封装格式sprintf要改成vsprintfvsprintf(String,format,arg);va_end(arg);			 //释放参数表USART_SendString(String);//发送String
}

原理相当于是重写了一个接口叫u_printf,传参啥的跟printf函数是一样的,但是输出到的是串口。

改造demo中的cout

除了上面的问题,还有在binary_library.cpp中用了std::cout方法打印矩阵的值,由于stm32没有OS,所以也不存在什么标准IO流了,这里也需要改造,具体做法是:
将:

void MatrixXd_print(const C_MatrixXd *m)
{std::cout << c_to_eigen(m) << std::endl;
}

改为:

void MatrixXd_print(const C_MatrixXd *m)
{MatrixXd cpp_m = c_to_eigen(m);unsigned char r = cpp_m.rows();unsigned char c = cpp_m.cols();char val[32] = {0};while (r){while(c){sprintf(val, "%.3f \t", cpp_m(r - 1, c - 1));USART_SendString(val);c--;}r--;c = cpp_m.cols();USART_SendString("\r\n");}
}

除了void MatrixXd_print(const C_MatrixXd *m)函数,void Map_MatrixXd_print(const C_Map_MatrixXd *m)也是一样的,改造后的内容如下:

void Map_MatrixXd_print(const C_Map_MatrixXd *m)
{MatrixXd cpp_m = c_to_eigen(m);unsigned char r = cpp_m.rows();unsigned char c = cpp_m.cols();char val[32] = {0};while (r){while(c){sprintf(val, "%.3f \t", cpp_m(r - 1, c - 1));USART_SendString(val);c--;}r--;c = cpp_m.cols();USART_SendString("\r\n");}
}

本质上原理是将Eigen中MatrixBase类提供的operator<<运算符重载方法改成了C语言中能直接用的方法。

改造delete运算符

在binary_library.cpp文件中,void MatrixXd_delete(C_MatrixXd *m)void Map_MatrixXd_delete(C_Map_MatrixXd *m)函数用了delete运算符释放堆内存,本身在cpp文件中是支持的,但是放到stm32中这么操作就会导致Hardfault,这里也需要改造一下,改成free函数,如下:

void MatrixXd_delete(C_MatrixXd *m)
{
//  delete &c_to_eigen(m);if (NULL != m){free(m);m = NULL;}
}// skip .....void Map_MatrixXd_delete(C_Map_MatrixXd *m)
{
//  delete &c_to_eigen(m);if (NULL != m){free(m);m = NULL;}
}

这里还需要啰嗦一句,按理说stm32里面没跑什么os,连内存管理机制都没有,free()函数还是delete运算符都是无意义的,为啥用new或者malloc就没区别,free()换成delete就不行呢?先埋个引子,以后再去探究了,如果有懂的大哥,也帮忙解答一下,感谢。

总结与展望

本文主要介绍了将Eigen项目移植到stm32开发板上遇到的一些问题以及解决办法,完整的例程请关注VX公众号“24K纯学渣”回复关键词“stm32_eigen”获取。

当前,笔者提供的demo还是太简单,基本上就做了一个矩阵运算和打印,只是卖出了第一步,可扩展的空间着实不小,例如:

  1. 加上交互功能,可以是串口式的、CLI式的、或者复杂一些整个可触摸的LCD,做成像手机APP一样的;
  2. 除了矩阵基本运算,还可以加一些复杂的比如正交分解、求解行列式值、求特征值、特征向量;
  3. 除了线性代数运算以外,还可以求解微分方程,机器人运动学解逆、无人机视觉定位等等;

如果你也刚好对上述内容感兴趣,欢迎来学习交流噢!

相关文章:

使用STM32实现一个线性代数计算器

文章目录 背景挑战与困难如何整合编译&#xff1f;error: non-ASM statement in naked function is not supportederror: #pragma import is an ARM Compiler 5 extension, and is not supported by ARM Compiler 6error: redefinition of __FILE 改造demo中的cout改造delete运…...

我在高职教STM32——串口通信(4)

大家好,我是老耿,高职青椒一枚,一直从事单片机、嵌入式、物联网等课程的教学。对于高职的学生层次,同行应该都懂的,老师在课堂上教学几乎是没什么成就感的。正因如此,才有了借助 CSDN 平台寻求认同感和成就感的想法。在这里,我准备陆续把自己花了很多心思的教学设计分享…...

Redis 缓存中间件 缓存数据库

Redis 缓存中间件 缓存数据库 nginx web服务 PHP 转发动态请求 tomcat web页面也可以转发动态请求 springboot 自带tomcat 所有的数据库不支持高并发&#xff0c;一旦访问量激增&#xff0c;数据库很快就会崩溃。 Redis 非关系型数据库 nosql not only sql 不仅仅是sql 键值对…...

51、PHP 实现简单的快速排序

题目&#xff1a; PHP 实现简单的快速排序 描述&#xff1a; function simpleQuickSort(array $list) {$length count($list);if( $length < 1){return $list;}else{$pivot $list[0];$left_list array();$right_list array();for($i 1; $i < $length; $i){if($lis…...

如何应对机器视觉软件中时间篡改与许可绕过的挑战?

在机器视觉行业&#xff0c;软件许可绕过和时间篡改问题存在&#xff0c;这些行为对企业的正常运营和市场竞争力造成了严重威胁。机器视觉软件通常包含复杂的算法和大量的数据处理能力&#xff0c;广泛应用于制造、医疗和安防等领域。然而&#xff0c;未经授权的使用和人为篡改…...

python文件的读写

要在Python中读写文件&#xff0c;你可以使用以下方法&#xff1a; 1. 打开文件&#xff1a;使用open()函数打开文件&#xff0c;它接受两个参数&#xff1a;文件名和模式。模式可以是只读&#xff08;r&#xff09;、写入&#xff08;w&#xff09;、追加&#xff08;a&#…...

2024下《网络工程师》案例简答题,刷这些就够了!

距离2024下半年软考已经越来越近了&#xff0c;不知道今年备考软考网络工程师的同学们开始准备了吗&#xff1f; 简答题一直是网工拿分的重点区域&#xff0c;对于许多考生来说&#xff0c;也往往是最具挑战性的部分。今天我就把那些重要的案例简答题类型整理汇总给大家&#x…...

Astro 实现TodoList网页应用案例

Astro 是一个现代化的静态站点生成器和前端框架&#xff0c;它具有独特的设计理念&#xff1a;岛屿架构。它允许开发人员使用组件化的方式构建内容优先的网站&#xff0c;将各种技术栈&#xff08;如React、Vue、Svelte等&#xff09;的组件无缝集成到同一个项目中。 1、创建项…...

计算机毕业设计Hadoop+Spark旅游景点可视化 旅游景点推荐系统 景区游客满意度预测与优化 Apriori算法 景区客流量预测 旅游大数据 景点规划

### 开题报告 **论文题目&#xff1a;** 基于Spark的旅游景点可视化系统的设计与实现 **研究背景与意义&#xff1a;** 随着旅游业的快速发展&#xff0c;人们对旅游信息的获取和处理需求越来越高。传统的旅游信息系统虽然能够提供静态的数据查询和展示功能&#xff0c;但在…...

MySQL存储

目录 1. MySQL存储引擎概述 2. 存储引擎的作用 3.存储引擎类型 4. 查看支持的存储引擎 6. InnoDB存储引擎 7. MyISAM与InnoDB的区别 8. 存储引擎的选择 9. 修改默认存储引擎 1. MySQL存储引擎概述 在MySQL中&#xff0c;数据通过不同的技术存储在文件&#xff08;或内存…...

手势传感器 - 从零开始认识各种传感器【第十八期】

手势传感器|从零开始认识各种传感器 1、什么是手势传感器 手势传感器是一种能够感知人类手势或动作的传感器。它可以捕捉、识别和解释人类的手部动作或姿势&#xff0c;并将其转换成电信号或数字信号&#xff0c;通过识别人体的手势动作来实现与电子设备的交互&#xff0c;如控…...

【未来餐饮】 配送设置

一、创建门店 关键信息 1. 门店名字要有辨识度&#xff0c;尽量不和其他客户重名 2. 地址要具体到门牌号 3. 定位要和上面的地址一致 可以复制地址搜索地图&#xff0c;然后选择位置 二、创建配送模板 新建模板 填写模板 命名模板&#xff0c;勾上真省钱&#xff0c;然后保…...

移动式气象设备:灵活应对,精准监测的气象先锋

在气象监测领域&#xff0c;随着科技的进步和需求的多样化&#xff0c;移动式气象设备逐渐崭露头角&#xff0c;成为现代气象观测中不可或缺的一部分。这些设备以其灵活性高、部署迅速、监测精准的特点&#xff0c;广泛应用于应急响应、农业生产、户外探险、科研考察等多个领域…...

【AI落地应用实战】DAMODEL深度学习平台部署+本地调用ChatGLM-6B解决方案

ChatGLM-6B是由清华大学和智谱AI开源的一款对话语言模型&#xff0c;基于 General Language Model (GLM)架构&#xff0c;具有 62亿参数。该模型凭借其强大的语言理解和生成能力、轻量级的参数量以及开源的特性&#xff0c;已经成为在学术界和工业界引起了广泛关注。 本篇将介…...

英伟达开始引领下一波浪潮:物理AI

这可能会是AI技术形态的一个转折点,大模型的下一个形态&#xff0c;不再是人和模型一轮一轮的即时问答了。 当地时间 7 月 29 日&#xff0c;在美国丹佛举行的第 51 届 SIGGRAPH 计算机图形学会议上&#xff0c;英伟达创始人、CEO 黄仁勋与 Meta 创始人、CEO 马克・扎克伯格进…...

SQLServer设置端口

在SQL Server中设置端口是一个涉及多个步骤的过程&#xff0c;这些步骤旨在确保数据库服务器能够在新指定的端口上安全、高效地运行。以下是对SQL Server设置端口的详细阐述&#xff0c;包括默认端口、更改端口的步骤、验证更改以及相关的安全考虑。 一、SQL Server默认端口 …...

诊断技巧分享 | 用WPS500压力传感器测试空调压力波形?

最近收到咨询&#xff0c;问我们WPS500压力传感器能不能测汽车的空调压力波形&#xff1f;如果可以的话&#xff0c;应该怎么测&#xff1f; 是可以的。WPS500压力传感器的最大测试压力是34.5 bar&#xff0c;匹配对应的管子的接头&#xff0c;可以测试空调的动态波形。 要做这…...

W1R3S靶机全通详细教程

文章目录 w1r3s主机发现主机扫描 端口扫描tcp端口扫描UDP扫描漏洞扫描 攻击面分析FTP渗透匿名登录 web渗透目录爆破 cuppa cms文件包含漏洞getshell提权 w1r3s 引言 近些日子看红笔大佬的靶机精讲视频时&#xff0c;他的一句话让我感受颇深&#xff0c;很多视频在讲解时&…...

GitHub Revert Merge Commit的现象观察和对PR的思考

文章目录 前言Pull Request 为什么会是这样&#xff1f;Pull Request Branch的差异 ?Two Dot Diff和Three Dot Diff 老生常谈&#xff1a; Merge 和 Rebasegit mergegit rebase Revert Main分支中的一个Merge Commit现象描述解决方案: Revert Feature分支中的一个Merge Commi…...

使用JavaFx Fxml笔记

使用JavaFx Fxml实现账号密码登录 HelloApplication.java&#xff1a;package com.example.dr295cmonth7;import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.geometry.Insets; import javafx.scene.Parent; import javafx.scene.Scene; i…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行

项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战&#xff0c;克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区&#xff08;Partitioning&#xff09;是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分&#xff08;分区&#xff09;可以独立存储、管理和优化&#xff0c;…...