STM32l433 板从 C 转换到 C++ 时 Printf 停止工作(发送垃圾数据)

问题描述 投票:0回答:1

我一直在开发 Nucleo STM32L433RCP,并进行了很好的设置,其中我有一个自定义 _write 函数,该函数获取由

printf
缓冲的字符并通过 UART 传输它们。所有这些都是用 C 语言编写的,并且运行得非常好。从那以后,为了与我正在参与的项目组兼容,我已经过渡到 C++,但它不再起作用了。
printf
仍然会在刷新时调用 write 函数,但据我所知,缓冲区充满了垃圾。我正在裸机工作,没有使用 STM32CubeIDE 或其附带的 HAL 工具。

我在 C 和 C++ 之间切换时所做的唯一更改是使用

extern "C"
将 _write 和我的重置处理程序作为 C 代码运行(没有它,什么也不会发生)。

下面是我的main.cpp,请忽略一些丑陋的格式


#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define GPIOBS 0x48000400

#define RCC_APB2ENR 0x4002104C
// #define ODR 0x14
// #define OTR 0x04

#define SERIAL_PORT "/dev/ttyACM0"
#include "../bsp/stm32l433xx.h"

uint32_t SystemCoreClock = 0;
extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss;

uint32_t arr[2000];

extern "C" {
// Reset handler: set the stack pointer and branch to main().
__attribute__((naked)) void reset_handler(void) {
  // Set the stack pointer to the 'end of stack' value.
  __asm__("LDR r0, =_estack\n\t"
          "MOV sp, r0");
  // Branch to main().
  __asm__("B main");
}
}



extern "C" {

int __io_putchar(int ch){
  while (!(USART2->ISR & USART_ISR_TXE)) {};
  USART2->TDR = ch;
  return ch;
}

int _write(int handle, const char* data, int size) {
  int count = size;
  while (count--) {
    __io_putchar(*data++);
  }
  while (!(USART2->ISR & USART_ISR_TXE)) {};
  USART2->TDR = 74;
  while (!(USART2->ISR & USART_ISR_TXE)) {};
  USART2->TDR = 10;
  while (!(USART2->ISR & USART_ISR_TXE)) {};
  USART2->TDR = 13;
  return size;
} 




void init(){
  // copy the flash varibales into ram
  memcpy(&_sdata, &_sidata, 4*(&_edata - &_sdata));
  // Clear the .bss section in RAM.
  memset(&_sbss, 0x00, 4* (&_ebss -&_sbss));
  
  for (int i = 0; i < 10000; ++i){
    arr[i] = i;
  }
  // Enable floating-point unit.
  SCB->CPACR |= (0xF << 20);
  RCC->AHB2ENR |= 7;

  // Enable floating-point unit.
  SCB->CPACR |= (0xF << 20);
  // Default clock source is the "multi-speed" internal oscillator.
  // Switch to the 16MHz HSI oscillator.
  RCC->CR |= (RCC_CR_HSION);
  while (!(RCC->CR & RCC_CR_HSIRDY)) {
  };
  RCC->CFGR &= ~(RCC_CFGR_SW);
  RCC->CFGR |= (RCC_CFGR_SW_HSI);
  while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI) {
  };
  SystemCoreClock = 16000000;

  RCC->APB1ENR1 |= (RCC_APB1ENR1_USART2EN);
  RCC->AHB2ENR |= (RCC_AHB2ENR_GPIOAEN);
  // Configure pins A2, A15 for USART2 (AF7, AF3).
  GPIOA->MODER &= ~((0x3 << (2 * 2)) | (0x3 << (15 * 2)));
  GPIOA->MODER |= ((0x2 << (2 * 2)) | (0x2 << (15 * 2)));
  GPIOA->OTYPER &= ~((0x1 << 2) | (0x1 << 15));
  GPIOA->OSPEEDR &= ~((0x3 << (2 * 2)) | (0x3 << (15 * 2)));
  GPIOA->OSPEEDR |= ((0x2 << (2 * 2)) | (0x2 << (15 * 2)));
  GPIOA->AFR[0] &= ~((0xF << (2 * 4)));
  GPIOA->AFR[0] |= ((0x7 << (2 * 4)));
  GPIOA->AFR[1] &= ~((0xF << ((15 - 8) * 4)));
  GPIOA->AFR[1] |= ((0x3 << ((15 - 8) * 4)));

  uint16_t uartdiv = SystemCoreClock / 9600;

  USART2->BRR = uartdiv;
  USART2->CR1 |= (USART_CR1_RE | USART_CR1_TE | USART_CR1_UE);
  
  //printf("starting, Clock speed is: %lu \r\n", SystemCoreClock);

  //  LED Test

  GPIOB->MODER &= (~(1 << 27));
  GPIOB->MODER |= (1 << 26);
  GPIOB->ODR |= (1 << 13);
  printf("Test\n");
}
}


int main() {
  init();
  int x = 0;
  ;
  while (1) {
    GPIOB->ODR |= (1 << 13);
    for (int i = 0; i < 500000; i++) {
    };
    GPIOB->ODR &= (~(1 << 13));
    for (int i = 0; i < 500000; i++) {
    };
    x++;
    printf("APPLE\n");// %d\r\n", x);
    _write(0, "APE TOGETHER",12);
    fflush(stdout);
  }
}

目前我的 _write 函数还输出换行符和回车符,因此通过 uart 查看输出时看起来不那么糟糕。我已经用 C 测试了所有内容并且它有效。

我正在使用arm-none-eabi-g++/gcc进行编译,具体取决于它是否是C文件。

谢谢你

c++ embedded stm32 bare-metal
1个回答
0
投票

您的C/C++运行时启动不充分。 它只是建立一个堆栈并跳转到

main()
。 它必须做其他事情来建立 C 和 C++ 运行时所期望的完整运行时环境,并且通常应该在
main()
之前完成,期望在
main()
运行时环境完成。

正在

init()
做的一些事情,尽管是来自而不是之前
main()
。 例如,您似乎正在执行
.data
.bss
段的静态初始化,但对于 C++ 来说这是不够的。 您还需要迭代静态 constructors,在此之前可能需要调用一些 C 库初始化来 extablish
stdin
stdout
stderr
和堆等。

一些应该做的事情(通常从

__start
main
之前):

  • 初始化全局和静态内存(您已经在
    main()
    中完成了该部分)。
  • 必须构造C++全局对象。链接器将这些构造函数放入图像的 .init、.init_array 或 .ctors 部分。
  • 准备用于调用 main 的 argc 和 argv 变量(即使只是将它们设置为 0/NULL)。
  • 执行 C/C++ 标准库实现所需的任何其他设置步骤。

根据应用程序和运行时环境,可能需要额外的运行时初始化步骤,例如:

  • 堆初始化
  • 初始化stdio(即stdin、stdout、stderr)
  • 初始化异常支持(如果使用 C++)
  • 注册退出程序时将运行的析构函数和其他清理函数(使用atexit和__cxa_atexit)
  • 准备环境变量。
  • RTOS 初始化。

您最好采用提供的 C/C++ 运行时启动并使其适应您平台的需求,而不是采用相当简约的方法,这种方法显然无法为完整的 C 或 C++ 运行时建立合适的环境。

© www.soinside.com 2019 - 2024. All rights reserved.