用Vs调试STM32记事

it2022-05-05  75

直接导入keil的工程文件,会有各种问题,如关联不上头文件,即使加载了头文件,也不能访问里面的定义??一头雾水,由于对GCC编译器不熟悉,并且用这种方式导入的文件,.h文件的颜色也不对,按F12可以关联到定义处,但颜色 却显示的是没有被编译的………………只能是另外找方法。

一步一步建立个MS_Build文件,按照keil的目录结构重建文件夹,将头文件包含设置好如图:

很简单的一个程序,但发现还是不能关联头文件,由于这次的GDB的完全由自己定义一个一个找原因,找不到GPIOInit等的定义。

最终发现:

全局宏设置部分Preprocessor macros,各项是用逗号分隔开的(keil的方式),这样就没办法加载后面的,所以就关联不起来,原来后面的“+”是可以一项项添加的,并且是用分号来隔开的。

第2个问题:重定义了#define EnableIrq()             __enable_irq()//开关中断,但就是提示没有非预期的函数声名,造成编译不通过,试过了各种办法,都不行,参考https://stackoverflow.com/questions/8024440/what-causes-the-error-undefined-reference-to-some-function,前面加上了void 通过#define EnableIrq()            void __enable_irq()。

修改后,就可以正常关联头文件了,可以正常编译,但串口printf还是不能正常打印?怎么回事呢???

在GCC下printf的重映射不再是putch了,下是是从网上查的资料,改写—write后,串口可以正常打印了。

在大家使用keil或是iar开发stm32等arm芯片的时候,想来最不陌生的就是使用print通过串口输出一些数据,用来调试或是其他作用。但是要明确的是由于keil iar gcc 他们使用的标准C语言库虽然都遵循一个标准,但他们底层的函数实现方式都是不同的,那么在GCC中我们能否像在keil中一样重映射print的输出流到串口上呢?答案是肯定的。

keil下重映射:

#include //include "stm32f10x.h" #pragma import(__use_no_semihosting) //标准库需要的支持函数 struct __FILE { int handle; }; FILE __stdout; //定义_sys_exit()以避免使用半主机模式 _sys_exit(int x) { x = x; } //重映射fputc函数,此函数为多个输出函数的基础函数 int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); USART_SendData(USART1, (uint8_t) ch); return ch; }

在keil中的C库中,printf、scanf等输入输出流函数是通过fputc、fgetc来实现最底层操作的,所以我们只需要在我们的工程中重定义这两个函数的功能就可以实现printf、scanf等流函数的重映射。

GUN下重映射:

#include #include #include int _write(int fd, char *ptr, int len); int _read(int fd, char *ptr, int len); void get_buffered_line(void); #define BUFLEN 127 static uint16_t start_ndx; static uint16_t end_ndx; static char buf[BUFLEN + 1]; #define buf_len ((end_ndx - start_ndx) % BUFLEN) static inline int inc_ndx(int n) { return ((n + 1) % BUFLEN); } static inline int dec_ndx(int n) { return (((n + BUFLEN) - 1) % BUFLEN); } static inline void back_up(void) { end_ndx = dec_ndx(end_ndx); usart_send_blocking(USART1, '\010'); usart_send_blocking(USART1, ' '); usart_send_blocking(USART1, '\010'); } void get_buffered_line(void) { char c; if (start_ndx != end_ndx) { return; } while (1) { c = usart_recv_blocking(USART1); if (c == '\r') { buf[end_ndx] = '\n'; end_ndx = inc_ndx(end_ndx); buf[end_ndx] = '\0'; usart_send_blocking(USART1, '\r'); usart_send_blocking(USART1, '\n'); return; } if ((c == '\010') || (c == '\177')) { if (buf_len == 0) { usart_send_blocking(USART1, '\a'); } else { back_up(); } } else if (c == 0x17) { while ((buf_len > 0) && (!(isspace((int) buf[end_ndx])))) { back_up(); } } else if (c == 0x15) { while (buf_len > 0) { back_up(); } } else { if (buf_len == (BUFLEN - 1)) { usart_send_blocking(USART1, '\a'); } else { buf[end_ndx] = c; end_ndx = inc_ndx(end_ndx); usart_send_blocking(USART1, c); } } } } int _write(int fd, char *ptr, int len) { int i = 0; if (fd > 2) { return -1; } while (*ptr && (i < len)) { usart_send_blocking(USART1, *ptr); if (*ptr == '\n') { usart_send_blocking(USART1, '\r'); } i++; ptr++; } return i; } int _read(int fd, char *ptr, int len) { int my_len; if (fd > 2) { return -1; } get_buffered_line(); my_len = 0; while ((buf_len > 0) && (len > 0)) { *ptr++ = buf[start_ndx]; start_ndx = inc_ndx(start_ndx); my_len++; len--; } return my_len; }

这个文件因为实现了scanf的功能同时还带有在串口上终端回显并支持backspace键所以显得有些长,我们来将其中的实现printf重映射的片段取出:

#include #include int _write(int fd, char *ptr, int len) { int i = 0; if (fd > 2) { return -1; } while (*ptr && (i < len)) { usart_send_blocking(USART1, *ptr); if (*ptr == '\n') { usart_send_blocking(USART1, '\r'); } i++; ptr++; } return i; }

差点忘了最重要的。我们在使用GNU的printf时,一定要记住在发送的内容后添加 \n或者在printf后使用fflush(stdout),来立即刷新输出流。否则printf不会输出任何数据,而且会被后来的正确发送的printf数据覆盖。这是由于printf的数据流在扫描到 \n以前会被保存在缓存中,直到 \n出现或是fflush(stdout)强制刷新才会输出数据,如果我们在printf数据的末尾不加入\n或fflush(stdout),这个printf数据就不会被发送出去,而且在新的printf语句也会重写printf的缓存内容,使得新的printf语句不会附带之前的内容一起输出,从而造成上一条错误的printf内容不显示且丢失。

如下所示: printf("Enter the delay(ms) constant for blink : "); fflush(stdout); printf("Error: expected a delay > 0\n");

总结:

     这里需要大家明白的是,GNU C 与 KEIL C 下的标准库函数实际上都是各个不同的机构组织编写的,虽然他们符合不同时期的C标准,如C89、C99等,那也只是用户层的API相同(同时要明白他们这些标准库是属于编译器的一部分的,就储存在编译器路径下的lib文件夹中)。虽然上层被调用的标准C函数相同,但是他们各有各的实现方式,他们在底层实现是可能完全不同的样子。所以在我们更换工具链后,一定要注意自己工程中的代码不一定会适应新的工具链开发环境。


    对于只能输出*.bin文件的问题,也是折腾了差不多一天才真正找到答案,下载最新的GUN_ARM最新版的是2019-7-10日,再编译,可以正常输出的*.hex文件了。但也发现其实GCC这种开源的编译器编出的代码体积还是挺大的,与MDK实际对比,要大出很多,这个问题,看了很多文章,都没有提到这回事,所以用VS开发像单片机这种项目还是不很合适的。除非是不在乎成本,选个FLASH超出项目预估很多的芯片,这样还可以勉强用用,有时再研究吧,基本的lVS下的程序已经搭建好了。就是项目是低成本的,不可能再换芯片了。


最新回复(0)