目录
- [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 yet
if (!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 yet
if (!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 away
EEPROMBufferFlush();
dirtyBuffer = false;
isValid = true;
} else
{
// Delay saving the data from the buffer to the flash. Just flag and wait for commit() later
dirtyBuffer = true;
}
return _t;
}
void Commit()
{
if (!isInitialized)
init();
if (dirtyBuffer)
{
// Save the data from the buffer to the flash
EEPROMBufferFlush();
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 buffer
EEPROMFillBuffer();
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;
} else
address = 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 Byte
uint8_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 settings
roomConfigs = 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,没啥人愿意折腾新东西,自己是比较能接收新鲜事物的,所以做了这篇的实战记录。
本文记录到此,算是自己工程的一次实践,本文完!!感谢阅读,感谢关注。