[MDK] 介绍STM32使用C和C++混合编程的方法

目录

  • [MDK] 介绍STM32使用C和C++混合编程的方法
    • 前言
    • 业务场景
    • 步骤1基础工程
    • 步骤2写代码
    • 步骤3添加cpp文件
    • 步骤4配置与编译
    • 上机现象
    • 后记

[MDK] 介绍STM32使用C和C++混合编程的方法

前言

搞单片机编程大多数还是使用MDK编程,自己对MDK这个软件也比较熟悉,在网络寻找资料时,发现有一些大佬会用c++来写单片机程序,很是高大上,于是笔者也想研究一下,于是有了这篇文章,使用stm32的内部flash进行编程,在芯片内部flash的最后一个page上进行存储一些数据。

业务场景

假设公司有一个项目是专门做智能家居的主板,这些主板上智能的功能比较多,可以语音控制某个灯,智能控制某个场景,开光空调等一系列的高级功能,现在这些主板用来供给酒店客房,酒店的这些客房都是装同一套板子,但是每个客房中一些配置又有一些不同,比如客房A比较大装有18个灯,15个按键;客房B比较小,只装有5个灯,3个按键。现在的需求则是根据客户每个房间的配置来对智能主板上进行部分编程。

步骤1基础工程

找一个基础工程,使用cubemx生成一个最基本的项目,时钟和SWD配置好就行,可以参考hal库教程。

步骤2写代码

移植现有代码

在这里插入图片描述

random_flash_interface.h内容

#ifndef FlashStorage_STM32_h
#define FlashStorage_STM32_h#include "random_flash_utils.h"class EEPROM
{
public:EEPROM()= default;uint8_t Read(int _address){if (!isInitialized)init();return EEPROMReadBufferedByte(_address);}void Update(int _address, uint8_t _value){if (!isInitialized)init();if (EEPROMReadBufferedByte(_address) != _value){dirtyBuffer = true;EEPROMWriteBufferedByte(_address, _value);}}void Write(int _address, uint8_t _value){Update(_address, _value);}template<typename T>T &Pull(int _offset, T &_t){// Copy the data from the flash to the buffer if not yetif (!isInitialized)init();uint16_t offset = _offset;auto* _pointer = (uint8_t*) &_t;for (uint16_t count = sizeof(T); count; --count, ++offset){*_pointer++ = EEPROMReadBufferedByte(offset);}return _t;}template<typename T>const T &Push(int _idx, const T &_t){// Copy the data from the flash to the buffer if not yetif (!isInitialized) init();uint16_t offset = _idx;const auto* _pointer = (const uint8_t*) &_t;for (uint16_t count = sizeof(T); count; --count, ++offset){EEPROMWriteBufferedByte(offset, *_pointer++);}if (commitASAP){// Save the data from the buffer to the flash right awayEEPROMBufferFlush();dirtyBuffer = false;isValid = true;} else{// Delay saving the data from the buffer to the flash. Just flag and wait for commit() laterdirtyBuffer = true;}return _t;}void Commit(){if (!isInitialized)init();if (dirtyBuffer){// Save the data from the buffer to the flashEEPROMBufferFlush();dirtyBuffer = false;isValid = true;}}static uint16_t TotalSize(){return EEPROM_SIZE + 1;}void SetCommitASAP(bool value = true){commitASAP = value;}bool isValid = true;private:void init(){// Copy the data from the flash to the bufferEEPROMFillBuffer();isInitialized = true;}bool isInitialized = false;bool dirtyBuffer = false;bool commitASAP = true;
};#endif

random_flash_utils.cpp内容

#ifndef __STM32_EEPROM_HPP
#define __STM32_EEPROM_HPP#ifdef __cplusplus
extern "C" {
#endif#include <cstring>
//#include <string.h>
#include "random_flash_utils.h"#define FLASH_BANK_NUMBER       FLASH_BANK_1
#define FLASH_END               FLASH_BANK1_END
#define FLASH_BASE_ADDRESS      ((uint32_t)((FLASH_END + 1) - EEPROM_SIZE))static uint8_t eepromBuffer[EEPROM_SIZE] __attribute__((aligned(8))) = {0};static inline uint32_t GetFlashEndAddress()
{uint32_t size;switch ((*((uint16_t*) FLASH_SIZE_DATA_REGISTER))){case 0x200U:size = 0x0807FFFFU;break;case 0x100U:size = 0x0803FFFFU;break;case 0x80U:size = 0x0801FFFFU;break;case 0x40U:size = 0x0800FFFFU;break;case 0x20U:size = 0x08007FFFU;break;default:size = 0x08003FFFU;break;}return size;
}uint8_t EEPROMReadByte(const uint32_t _pos)
{EEPROMFillBuffer();return EEPROMReadBufferedByte(_pos);
}void EEPROMWriteByte(uint32_t _pos, uint8_t _value)
{EEPROMWriteBufferedByte(_pos, _value);EEPROMBufferFlush();
}uint8_t EEPROMReadBufferedByte(const uint32_t _pos)
{return eepromBuffer[_pos];
}void EEPROMWriteBufferedByte(uint32_t _pos, uint8_t _value)
{eepromBuffer[_pos] = _value;
}void EEPROMFillBuffer(void)
{memcpy(eepromBuffer, (uint8_t*) (FLASH_BASE_ADDRESS), EEPROM_SIZE);
}void EEPROMBufferFlush(void)
{FLASH_EraseInitTypeDef eraseInitStruct;uint32_t offset = 0;uint32_t address = FLASH_BASE_ADDRESS;uint32_t address_end = FLASH_BASE_ADDRESS + EEPROM_SIZE - 1;uint32_t pageError = 0;uint64_t data = 0;/* ERASING page */eraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;eraseInitStruct.Banks = FLASH_BANK_NUMBER;eraseInitStruct.PageAddress = FLASH_BASE_ADDRESS;eraseInitStruct.NbPages = 1;if (HAL_FLASH_Unlock() == HAL_OK){__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR);if (HAL_FLASHEx_Erase(&eraseInitStruct, &pageError) == HAL_OK){while (address <= address_end){data = *((uint64_t*) ((uint8_t*) eepromBuffer + offset));if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data) == HAL_OK){address += 8;offset += 8;} elseaddress = address_end + 1;}}HAL_FLASH_Lock();}
}#ifdef __cplusplus
}
#endif
#endif

random_flash_utils.h内容

#ifndef __STM32_EEPROM_H
#define __STM32_EEPROM_H#ifdef __cplusplus
extern "C" {
#include <stm32f103xb.h>
#include <stm32f1xx_hal.h>
#endif#define EEPROM_SIZE  FLASH_PAGE_SIZE // 1K Byteuint8_t EEPROMReadByte(uint32_t _pos);
void EEPROMWriteByte(uint32_t _pos, uint8_t _value);void EEPROMFillBuffer();
void EEPROMBufferFlush();
uint8_t EEPROMReadBufferedByte(uint32_t _pos);
void EEPROMWriteBufferedByte(uint32_t _pos, uint8_t _value);#ifdef __cplusplus
}
#endif
#endif

讲上述的源码添加进工程中,并配置头文件路径

在这里插入图片描述

步骤3添加cpp文件

在main.c中添加一个C函数,用于C++代码的入口

#include "main.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "common_inc.h"int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();/* USER CODE BEGIN 2 */printf("%d\r\n",SystemCoreClock);Main();/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

这个Main函数在一个头文件中进行声明,在main_app.cpp中进行实现

common_inc.h的内容

#ifndef LOOP_H
#define LOOP_H#ifdef __cplusplus
extern "C" {
#endif
/*---------------------------- C Scope ---------------------------*/
//#include "stdint-gcc.h"
#include "stm32f1xx_hal.h"
#include "main.h"
//#include "tim.h"
//#include "usbd_customhid.h"
//#include "usb_device.h"void Main();#ifdef __cplusplus
}/*---------------------------- C++ Scope ---------------------------*/
#include "random_flash_interface.h"#endif
#endif

main_app.cpp的内容

#include "common_inc.h"
#include "configurations.h"
#include <cstring>/* Component Definitions -----------------------------------------------------*/
RoomConfig_t roomConfigs;/* Main Entry ----------------------------------------------------------------*/
void Main()
{EEPROM eeprom;eeprom.Pull(0, roomConfigs);if (roomConfigs.configStatus != CONFIG_OK){// Use default settingsroomConfigs = RoomConfig_t{.configStatus = CONFIG_OK,.roomType = 1, // 默认客房类型.numLamps = 2, // 默认灯数量.numButtons = 3, // 默认按键数量.lampTypes = {LAMP_NIGHT, LAMP_READ},   // 灯带,玄武灯,床头灯,顶灯,射灯....buttonFunctions = {BUTTON_ALL_LIGHTS,BUTTON_SLEEP,BUTTON_WAKEUP} // 情景模式,全开灯,全关灯...};eeprom.Push(0, roomConfigs);}// 主循环,这里可以添加实际的功能代码while (true){// 根据客房配置处理智能客房的逻辑// 例如,根据roomConfigs[roomId].lampTypes和roomConfigs[roomId].buttonFunctions来控制灯和按键}
}

还有一个配置的头文件configurations.h

#ifndef CONFIGURATIONS_H
#define CONFIGURATIONS_H#ifdef __cplusplus
extern "C" {
#endif
/*---------------------------- C Scope ---------------------------*/
#include <stdbool.h>
#include "common_inc.h"typedef enum configStatus_t
{CONFIG_RESTORE = 0,CONFIG_OK,CONFIG_COMMIT
} configStatus_t;typedef enum lampType_t
{LAMP_NIGHT, // 夜灯LAMP_READ   // 阅读灯} lampType_t;typedef enum buttonFunction_t
{BUTTON_ALL_LIGHTS, // 全开灯BUTTON_SLEEP,BUTTON_WAKEUP
} buttonFunction_t;typedef struct RoomConfig_t
{configStatus_t configStatus;uint8_t roomType;uint8_t numLamps;uint8_t numButtons;lampType_t lampTypes[8];        // 假设最多8个灯buttonFunction_t buttonFunctions[8];  // 假设最多8个按键
} RoomConfig_t;extern RoomConfig_t roomConfigs;#ifdef __cplusplus
}
/*---------------------------- C++ Scope ---------------------------*/#endif
#endif

步骤4配置与编译

使用vscode编写完代码后,配置一下keil工程用来编译,我的配置如下

在这里插入图片描述

在这里插入图片描述

设置好之后,编译一下工程,然后进行上机实验。

上机现象

在这里插入图片描述

在这里插入图片描述

上机之后,使用调试功能进行仿真一下,发现在对应的地址上存储了自己的配置信息,跟定义的配置是一致的。

后记

我的这颗ic,最后一个page的 是从0x0801FC00开始的,所以我仿真时在内存窗口查看了这个地址。

在这里插入图片描述

使用c++来写单片机编程的是有,不过都是商业案例,也不太好找到相关的代码片段,何况搞单片机的主流还是c,没啥人愿意折腾新东西,自己是比较能接收新鲜事物的,所以做了这篇的实战记录。

本文记录到此,算是自己工程的一次实践,本文完!!感谢阅读,感谢关注。

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

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

相关文章

【通信】电子科协通信专题

数字通信 最直观的通信方式-基带通信 问题&#xff1a;①无限大的带宽②天线体积

java回调机制

目录 一、简介二、示例2.1 同步回调2.2 异步回调2.3 二者区别 三、应用场景 一、简介 在Java中&#xff0c;回调是一种常见的编程模式&#xff0c;它允许一个对象将某个方法作为参数传递给另一个对象&#xff0c;以便在适当的时候调用该方法。 以类A调用类B方法为例: 在类A中…

KMP + Compose 跨平台 Android IOS 实战入门

KMP&#xff08;Kotlin Multiplatform&#xff09;是一种面向移动端开发的跨平台框架&#xff0c;使用 Kotlin 语言编写&#xff0c;可实现在 Android 和 iOS 平台上共享代码和逻辑。通过 KMP 框架&#xff0c;我们可以编写一次代码&#xff0c;然后在不同的平台上进行部署和运…

python能够干什么?

python有哪些用途&#xff1f; Python是一种高级编程语言&#xff0c;它被广泛用于各种不同的领域。以下是Python的一些常见用途&#xff1a; 网络应用开发&#xff1a;Python可以用于编写Web应用程序、API、爬虫、网络服务器等。数据科学和机器学习&#xff1a;Python拥有许…

深究muduo网络库的Buffer类!!!

最近在学习了muduo库的Buffer类&#xff0c;因为这个编程思想&#xff0c;今后在各个需要缓冲区的项目编程中都可以用到&#xff0c;所以今天来总结一下&#xff01; Buffer的数据结构 muduo的Buffer的定义如下&#xff0c;其内部是 一个 std::vector&#xff0c;且还存在两个…

Shell编程之条件语句

条件测试 文件测试与整数测试 字符串测试与逻辑测试 if语句 if单分支语句 if双分支语句 if多分支语句 case分支语句 条件测试操作 条件测试操作 1 条件判断 test命令测试表达式是否成立&#xff0c;若成立返回0.否则返回其它数值。 格式 1 test 条件表达式 格式 2 …

【Redis7】了解Redis

1.常见数据库 1.1.键值存储数据库 如 Map 一样的key-value 对&#xff0c;典型代表就是 Redis。 1.2.列存储数据库 关系型数据库是典型的行存储数据库&#xff0c;按行存储的数据在物理层面占用的是连续存储空间&#xff0c;不适合海量数据存储。而按列存储则可实现分布式存储&…

猫不爱喝水是正常的?求求別再被洗脑了!日常可以补水的主食分享

猫不爱喝水正常吗&#xff1f;看给猫喂的什么&#xff0c;喂的罐头的话不爱喝水问题不大。喂的干粮猫还长期不喝水&#xff0c;处于缺水状态&#xff0c;可能会出现便秘、上火、尿黄、尿少等症状。在高温的夏季&#xff0c;猫还可能因脱水而中暑&#xff0c;严重时甚至可能导致…

用c++实现汉诺塔问题、归并排序

6.1.3 汉诺塔问题 【问题】 汉诺塔问题(Hanio tower problem)来自一个古老的传说&#xff1a;有一座宝塔&#xff08;塔A),其上有64个金碟&#xff0c;所有碟子按从大到小由塔底堆放至塔顶。紧挨着这座宝塔有另外两座宝塔&#xff08;塔B和塔C),要求把塔A上的碟子移动到塔C上…

01-xss基本原理

核心:攻击的是前端&#xff0c; 一、课程引入 1、开发一个简单的PHP页面&#xff0c;代码如下&#xff1a; <?php // xss 基础演示代码&#xff1a;从浏览器中接受一个URL地址参数名为content if(isset($_GET[content])){$content$_GET[content];echo "你输入的内容…

再议大模型微调之Zero策略

1. 引言 尽管关于使用Deepspeed的Zero策略的博客已经满天飞了&#xff0c;特别是有许多经典的结论都已经阐述了&#xff0c;今天仍然被问到说&#xff0c;如果我只有4块40G的A100&#xff0c;能否进行全量的7B的大模型微调呢&#xff1f; 正所谓“纸上得来终觉浅&#xff0c;…

进程状态与优先级

Linux内核源代码&#xff1a; 首先我们需要明确一点&#xff0c;Linux操作系统和操作系统的进程状态是不同的 上图大概标识了各个状态对应在操作系统的状态 普通进程 R运行状态&#xff08;running&#xff09;: 并不意味着进程一定在运行中&#xff0c;它表明进程要么是在…

ROS 2边学边练(44)-- 从头开始构建一个视觉机器人模型

前言 从此篇开始我们就开始接触URDF(Unified Robot Description Format&#xff0c;统一机器人描述格式)&#xff0c;并利用其语法格式搭建我们自己的机器人模型。 动动手 开始之前我们需要确认是否安装joint_state_publisher功能包&#xff0c;如果有安装过二进制版本的urdf_…

解密某游戏的数据加密

前言 最近有个兄弟通过我的视频号加我&#xff0c;咨询能否将这个dubo游戏游戏开始前就将数据拿到从而进行押注&#xff0c;于是通过抓包工具测试了下&#xff0c;发现数据有时候是明文&#xff0c;有时候确实密文&#xff0c;大致看了下有这几种加密&#xff1a;Md5aes、Md5&a…

网络层协议之 IP 协议

IP 协议格式 4 位版本&#xff1a;此处的取值只有两个&#xff0c;4&#xff08;IPv4&#xff09;和 6&#xff08;IPv6&#xff09;&#xff0c;即指定 IP 协议的版本。 4 位首部长度&#xff1a;描述了 IP 报头多长&#xff0c;IP 报头是变长的&#xff0c;因为报头中的选项部…

点餐小程序 点餐系统 微信点餐系统 支持微信小程序 支付公众号 可接入第三方配送 全开源uniapp

餐饮连锁v2版-体验后台&#xff08;复制粘贴以下地址到浏览器&#xff0c;打开网址即可登录&#xff0c;) 本文来自&#xff1a;点餐小程序 点餐系统 微信点餐系统 支持微信小程序 支付公众号 可接入第三方配送 全开源uniapp - 源码1688 演示后台&#xff1a;https://diancan.…

异构图神经网络——Heterogeneous Graph Neural Networks

相关代码见文末 1.回顾同构图 1.1 GNN GNN基本计算方法——邻接矩阵乘以节点,聚合相邻节点的特征,得到本节点的特征表达 1.2 Graph Attention Network 引入图注意力,实现边的权重可学习,最简单的方法是,将两个节点的特征进行拼接,使用一组可学习的权重参数映射为边的权…

华为数据之道第一部分导读

目录 导读 第一部分 序 第1章 数据驱动的企业数字化转型 非数字原生企业的数字化转型挑战 业态特征&#xff1a;产业链条长、多业态并存 运营环境&#xff1a;数据交互和共享风险高 IT建设过程&#xff1a;数据复杂、历史包袱重 数据质量&#xff1a;数据可信和一致化…

学习大数据,所需更要的shell基础(2)

文章目录 read读取控制台输入函数系统函数bashnamedirname 自定义函数Shell工具&#xff08;重点&#xff09;cutawk 正则表达式入门常规匹配常用特殊字符 read读取控制台输入 1&#xff09;基本语法 read (选项) (参数) ①选项&#xff1a; -p&#xff1a;指定读取值时的提示…

C++初识多态(1)

1.多态要解决的问题&#xff08;引入&#xff09; 任何一种机制的存在&#xff0c;必然是有其存在的意义的&#xff0c;例如我们前面学过的函数重载&#xff0c;运算符重载&#xff0c;以及引用等等&#xff0c;都是解决一些特殊问题的&#xff1b; 下面通过一些具体的例子&a…