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

news2024/9/22 5:33:12

文章目录

  • 背景
  • 挑战与困难
    • 如何整合编译?
      • 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要改成vsprintf
	vsprintf(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. 除了线性代数运算以外,还可以求解微分方程,机器人运动学解逆、无人机视觉定位等等;

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1967139.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gdb调试 查找段错误

先设置 程序崩溃时的core文件产生 ulimit -c unlimited http://t.csdnimg.cn/BBGBQ 记得改完之后重启虚拟机。 core文件&#xff1a;当程序发生异常&#xff08;如段错误&#xff09;并退出时&#xff0c;操作系统可以选择生成一个 core 文件。这个文件包含了程序崩溃时刻…

【吊打面试官系列-Dubbo面试题】Dubbo 支持服务降级吗?

大家好&#xff0c;我是锋哥。今天分享关于 【Dubbo 支持服务降级吗&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; Dubbo 支持服务降级吗&#xff1f; 以通过 dubbo:reference 中设置 mock"return null"。mock 的值也可以修改为 true&#xff0c;然…

软测面试二十问(最新面试)

1.软件测试的流程是什么 参加需求评审会&#xff0c;解决需求疑问---写测试用例---对测试用例进行评审---评审后开始执行测试---提交bug---追踪bug---关闭bug---回归测试---交叉测试---编写测试报告---冒烟测试 2.什么是黑盒测试和白盒测试&#xff1f;它们有何区别 黑盒测试…

医院HIS搭建|HIS系统开发|HIS系统源码

在现代医疗管理中&#xff0c;医院信息系统&#xff08;HIS&#xff09;扮演着至关重要的角色。它是一个综合性的信息平台&#xff0c;旨在提高医院运营效率&#xff0c;优化患者护理&#xff0c;并确保医疗数据的准确性和安全性。以下是HIS系统包含的一些核心功能&#xff1a;…

抖音短视频矩阵管理系统:短视频运营的得力助手

1. 抖音短视频矩阵管理系统介绍 随着短视频行业的迅速发展&#xff0c;越来越多的企业和自媒体人开始关注短视频运营。抖音作为国内最受欢迎的短视频平台之一&#xff0c;拥有庞大的用户群体和丰富的内容资源。为了更好地管理和运营短视频&#xff0c;抖音短视频矩阵管理系统应…

数据结构:线性表(上)

谈到线性的数据结构&#xff0c;那肯定离不开两个最基础的&#xff1a;数组和链表&#xff0c;当然有了数组和链表就会聊到栈和队列。 那么本篇我们就来介绍数组和链表 一、数组 数组&#xff08;Array&#xff09; 是一种很常见的数据结构。它由相同类型的元素&#xff08;…

Linux操作系统及gcc工具实验报告

Linux操作系统及gcc工具实验 一、 实验目的 1&#xff0e;学习在VMware虚拟机上搭建Linux操作系统。 2&#xff0e;掌握Linux操作系统的环境配置及使用。 3. 学会使用gcc工具编译。 二、 实验环境 硬件&#xff1a;PC机 软件&#xff1a;VMware虚拟机、Linux系统。 三、…

8-springboot集成nacos config

本文介绍spring boot集成nacos config&#xff0c;注意这里和spring cloud没任何关系&#xff0c;单纯基于spring boot。 0、环境 jdk 1.8nacos 2.0.3Idea 2021.1maven 3.8.1 1、springboot搭建 Idea新建maven项目&#xff0c;构建完成后&#xff0c;添加以下依赖&#xff…

新160个crackme - 018-crackme_0006

运行分析 输入Name和Serial&#xff0c;点击Check无反应 PE分析 ASM程序&#xff0c;32位&#xff0c;无壳 静态分析&动态调试 ida找到关键字符串&#xff0c;进入关键函数 通过静态分析和动态调试&#xff0c;得到以下结论 总结&#xff1a;1、String Serial2、String2 是…

JavaScript_语法_运算符_一元运算符

变量_ typeof 运算符&#xff1a; (可以使用 typeof 运算符来确定 JavaScript 变量的数据类型) 注&#xff1a; 复杂数据 typeof 运算符把对象、数组或 null 返回 object。 null被认为是对象的占位符 1.5 运算符 1.一元运算符&#xff1a;只有一个运算数的运算符 eg. A , …

电商平台服务器 IP 地址遭受攻击?

平台突然瘫痪&#xff1f;客户反映无法购物&#xff1f;订单无法查询&#xff1f;电商平台在促销活动期间往往会迎来流量高峰&#xff0c;这就给了网络攻击者可乘之机。服务器 IP 地址遭受恶意攻击&#xff0c;导致网站无法正常访问&#xff0c;就会造成以上后果&#xff0c;给…

【黑马java基础】网络通信

可以让设备中的程序与网络上其他设备中的程序进行数据交互&#xff08;实现网络通信的&#xff09;。 java提供了java.net.*包下提供了网络编程的解决方案 通信的基本架构主要有两种形式&#xff1a;一种是CS架构&#xff08;Client 客户端/Server服务端&#xff09;、一种是…

OceanBase介绍

OceanBase 是什么 OceanBase 是由蚂蚁金服、阿里巴巴完全自主研发的分布式关系型数据库&#xff0c;始创于 2010 年。 OceanBase 具有数据强一致、高可用、高性能、在线扩展、高度兼容 SQL 标准和主流关系型数据库、低成本等特点。OceanBase 至今已成功应用于支付宝全部核心业…

移动端测试如何学,超详细的APP测试攻略送上

前言 随着手机应用市场发展的逐渐成熟&#xff0c;手机APP已经渗透到人们的吃穿住行生活&#xff0c;比如手机支付APP、通讯APP、各大应用软件等&#xff0c;关于手机APP安全性能的重要性不言而喻。 鉴于此&#xff0c;做好手机APP测试对于软件开发方把控产品质量有着重要意义…

运维工作中的事件、故障排查处理思路

一、运维工作中的事件 https://www.51cto.com/article/687753.html 二、运维故障排查 一&#xff09;故障排查步骤 1、明确故障 故障现象的直接表现故障发生的时间、频率故障发生影响哪些系统故障发生是否有明确的触发条件   故障举例&#xff1a;无法通过ssh登录系统 影响…

KubeSphere 学习之路

云原生KubeSphere最佳实践&#xff1a; https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg4MzcyOTQ2NQ&actiongetalbum&album_id3022627663062056961&scene173&subscene&sessionidsvr_2156d508166&enterid1722492662&from_msgid2247484226&…

简约家居,探秘浦东TOP5装修公司的绿色魔法

在快节奏的现代生活中&#xff0c;家居装修不仅仅是为了美化居住空间&#xff0c;更是一种提升生活品质的方式。特别是对于追求环保和健康生活的业主来说&#xff0c;选择一家能够提供绿色装修服务的公司变得尤为重要。在浦东地区&#xff0c;有几家装修公司因其卓越的绿色装修…

【教程】Linux安装Redis步骤记录

下载地址 Index of /releases/ Downloads - Redis 安装redis-7.4.0.tar.gz 1.下载安装包 wget https://download.redis.io/releases/redis-7.4.0.tar.gz 2.解压 tar -zxvf redis-7.4.0.tar.gz 3.进入目录 cd redis-7.4.0/ 4.编译 make 5.安装 make install PREFIX/u…

从源码看 Redis:深入理解 redisDb 和 redisObject

Redis 是一个广泛使用的内存数据库&#xff0c;以其高性能和丰富的数据结构而闻名。不同于磁盘数据库&#xff0c;磁盘数据库将数据读取到文件中维护&#xff0c;而内存数据库将数据存储在内存中&#xff0c;意味着其想要维护数据&#xff0c;必须在代码中维护一个保存数据的结…