一、stm32的C库
cubeIDE针对STM32芯片开发,提供个了两大库,HLA库和C库(集成GNU Tools for STM32工具链时提供,该工具链同样是意法半导体提供),前者帮助开发这简化与硬件交互,后者帮助开发者简化底层软逻辑实现。CubeIDE已经通过插件形式将GNU Tools for STM32工具链内嵌在该开发平台的插件集中,它主要包含两大内容,一部分涉及编译相关的make、gcc及平台特性等,另一部分就是开发应用软件密切相关的newlib库(即c库)。
因为在符合POSIX.1标准(也称为IEEE 1003.1)的系统上使用C库,C库依赖于操作系统服务的少量必要函数调用,这些必要函数中的大多数都是随操作系统提供的。若开发者基于cubeIDE开发STM32应用时,如果不加入第三方嵌入式系统时,相当于在为“裸板”系统开发软件,没有操作系统,就必须自行提供一些必须的依赖函数(子例程)。因此,当基于cubeIDE创建STM32工程时,开发平台就直接帮开发者把newlib库的调用加入输出代码中,其中syscalls.c就是已经最小化地提供不做任何操作的必要函数定义或具有最小功能的子例程。
同时在程序启动文件startup_stm32*.s汇编文件中,调用了c库。
CubeIDE通过集成arm-none-eabi交叉编译工具包将 C库加入STM32工程,该c库包含:
as(assembler ,汇编器)子库,用于汇编编译;
BFD(Binary File Descriptor Library)子库,用于处理目标文件,像as、ld、binutils等都依赖它间接出来文件;
libc(standard ANSI C library,基于Red Hat newlib C Library)子库;
gdb子库,用于调试。
stabs(symbol table strings)子库;
ld(GNU linker)子库,用于链接功能。
libm(C Math Library)子库,用于计算,主要是通用计算。
refcard 子库,涉及GDB相关的启停、断点监测、信号捕获、标识列表、调试脚本、调试输出、调试控制等。
ibiberty(library of free software)子库,主要是实现了一些函数的接口或者宏,用于补充、替换、优化、扩展c实现。
gprof子库,用于性能检测分析工具;
binutils( gnu Binary Utilities)子库,用于各种命令工具集;
gcc子库,用于gcc编译、部署、运行相关。
其中,在STM32应用开发编程中涉及对c库调整的主要是libc库,该库主要包含 stdlib、stdio、ctype、string、wchar、 signal、time、locale、arglist、Protection以及前面例程组件的重写部分及其他特定例程。
二、stm32的c库重载函数调整
CubeIDE在创建工程是输出syscalls.c源码,已经将c库在stm32MCU上运行的依赖的必要函数例程做了定义,但是都是没有具体内柔的伪实现。
正如本专栏前面提到的将printf函数输出映射到lpuart1串口上,就是改写了syscalls.c中_write函数来实现的。在libc的printf源码中,printf接受一系列参数,从*format向每个参数应用一个格式说明符,并将格式化数据写入stdout。在不修改libc库基础上,调整_write写入到“stdout”替换成lpuart1,就可以实现printf输出内容到串口上。
syscalls.c中的_write如下,该函数还是弱定义(_weak):
例如可以调整为- __attribute__((weak)) int _write(int file, char *ptr, int len)
- {
- int DataIdx;
- for (DataIdx = 0; DataIdx < len; DataIdx++)
- {
- //__io_putchar(*ptr++);
- HAL_UART_Transmit(&lpuart1, ptr++, 1, HAL_MAX_DELAY);
- }
- return len;
- }
复制代码
更近一步,如前面博文提到的,可以程序开始前先指定printf需要映射的实例句柄,将整个写入内容直接转发到串口实例上。
- UART_HandleTypeDef *gHuart;
- void ResetPrintInit(UART_HandleTypeDef *huart) {
- gHuart = huart;
- /* Disable I/O buffering for STDOUT stream, so that
- * chars are sent out as soon as they are printed. */
- setvbuf(stdout, NULL, _IONBF, 0);
- }
- int _write(int fd, char* ptr, int len) {
- HAL_StatusTypeDef hstatus;
- if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
- hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
- if (hstatus == HAL_OK)
- return len;
- else
- return EIO;
- }
- errno = EBADF;
- return -1;
- }
复制代码
依据libc的移植说明,printf需要这些函数:close、fstat、isatty、lseek、read、sbrk、write支持,因此在STM32上实现printf函数,需要提供这些函数的定义(可以是伪定义、空定义或实际内容)。在实际应用中,先将映射指向串口,在调用printf函数就可以实现打印输出到指向串口,而非libc原来的stdout。
- ResetPrintInit(&hlpuart1);
- ...
- printf("123\r\n");
复制代码
类似printf,以下功能函数也可以根据实际项目需要调整其输出指向来实现:
fprintf与printf类似,只是输出指向流fd而不是stdout。
sprintf与printf类似,只是输出指向缓冲区str,并输出终止NUL,注意生成的输出不要超过缓冲区所能容纳的输出。
snprintf与sprintf类似,不同的是输出最多限制为字节大小,包括终止的NUL。
asprintf与sprintf类似,只是输出存储在动态分配的缓冲区pstr中,稍后应使用free释放该缓冲区。
asnprintf类似于sprintf,不同的是返回类型是原始str(如果它足够大),或者是动态分配的字符串(注意输出超过*size情况)。
又如syscalls.c中和时间相关的_times函数,该函数是clock计时函数所需要,通过改写该函数,将其与STM32的systick关联,实现到STM32的迁移使用。
在STM32工程中,调用clock函数时,就会进入子例程调用_times函数,通过改写_times函数,就间接地调整了clock函数,例如:
- int _times(struct tms *buf)
- {
- uint32_t ct = HAL_GetTick();
- return (int)ct/1000;
- }
复制代码
更多关于libc库里各个功能函数依赖那些子例程函数,可以通过查找自己CubeIDE开发平台的GNU Tools for STM32工具包目录,
进入该目录,找到pdf/libc.pdf说明文档查看,该文档会具体指出各个功能函数依赖那些子例程函数以及使用注意事项和迁移注意事项。
|