面试stm32基础知识

本文详细介绍了STM32的启动模式、存储映射、GPIO工作原理、volatile关键字的使用、位段操作、启动文件解析以及Keil预处理宏,还探讨了中断处理、无限循环的创建、I2C和SPI通信、Freertos移植注意事项、堆栈溢出和内存泄漏问题,以及单片机浮点数传输和调试技巧。
摘要由CSDN通过智能技术生成

1.ISP

第一步进入bootloader模式:先置BOOT0为高,BOOT1为低,再复位单片机进入bootloader模式,之后通过上位机下载程序;
第二步配置启动代码的地方:代码下载完毕后,置BOOT0为低,BOOT1为低,再复位单片机即可启动用户代码。

STM32三种启动模式

下好程序后,重启芯片时,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存,这就是所谓的启动过程。
1.STM32上电或者复位后,代码区始终从0x00000000开始,其实就是将存储空间的地址映射到0x00000000中。三种启动模式如下:
从主闪存存储器启动,将主Flash地址0x08000000映射到0x00000000,这样代码启动之后就相当于从0x08000000开始。主闪存存储器是STM32内置的Flash,作为芯片内置的Flash,是正常的工作模式。一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。
2.从系统存储器启动。首先控制BOOT0、BOOT1管脚,复位后,STM32与上述两种方式类似,从系统存储器地址0x1FFF F000开始执行代码。系统存储器是芯片内部一块特定的区域,芯片出厂时在这个区域预置了一段Bootloader,就是通常说的ISP程序。这个区域的内容在芯片出厂后没有人能够修改或擦除,即它是一个ROM区。启动的程序功能由厂家设置。系统存储器存储的其实就是STM32自带的bootloader代码。
3.从内置SRAM启动,将SRAM地址0x20000000映射到0x00000000,这样代码启动之后就相当于从0x20000000开始。内置SRAM,也就是STM32的内存,既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。假如我只修改了代码中一个小小的地方,然后就需要重新擦除整个Flash,比较的费时,可以考虑从这个模式启动代码,用于快速的程序调试,等程序调试完成后,在将程序下载到SRAM中。

2.储存映射:

4G的地址空间:8块,每块512MB;
在这里插入图片描述
第0块的内容:
在这里插入图片描述
第1块的内容:
在这里插入图片描述

3.GPIO

推挽输出:P管负责灌电流,N管负责拉电流
开漏输出:而在开漏输出模式时,上方的 P-MOS 管完全不工作。如果我们控制输出为 0,低电平,则 P-MOS管关闭,N-MOS 管导通,使输出接地,若控制输出为 1 (它无法直接输出高电平) 时,则 P-MOS管和 N-MOS 管都关闭,所以引脚既不输出高电平,也不输出低电平,为高阻态。为正常使用时必须外部接上拉电阻,参考图开漏电路 中等效电路。它具有“线与”特性,也就是说,若有很多个开漏模式引脚连接到一起时,只有当所有引脚都输出高阻态,才由上拉电阻提供高电平,此高电平的电压为外部上拉电阻所接的电源的电压。若其中一个引脚为低电平,那线路就相当于短路接地,使得整条线路都为低电平,0 伏。

GPIO的工作模式:
在这里插入图片描述

4.volatile

C 语言中的关键字“volatile”,在 C 语言中该关键字用于表示变量是易变的,要求编译器不要优化。若没有这个关键字修饰,在某些情况下,编译器认为没有代码修改该变量,就直接从 CPU 的某个缓存获取该变量值,这时可以加快执行速度,但该缓存中的是陈旧数据,与我们要求的寄存器最新状态可能会有出入。加上这个值我们都要求 CPU 每次去该变量的地址重新访问。

用法举例:

  • 中断服务程序中修改的变量需要加 volatile;
  • 全局变量;
  • 多任务环境下各任务间共享的标志应该加 volatile

volatile const int a;
从结果中我们可以看到,编译器然后认为 a 的值为一开始定义的 7,所以对 const a 的操作就会产生上面的情况。所以千万不要轻易对const 变量设法赋值,这会产生意想不到的行为。如果不想让编译器察觉到上面到对 const 的操作,我们可以在 const 前面加上 volatile 关键字。Volatile 关键字跟 const 对应相反,是易变的,容易改变的意思。所以不会被编译器优化,编译器也就不会改变对 a 变量的操作。

5.位段操作

在 STM32 中,有两个地方实现了位带,一个是 SRAM 区的最低 1MB 空间,令一个是外设区最低 1MB 空间。比特位经过膨胀后得到位带别名。

6.启动文件的解析

1. 初始化堆栈指针 SP=_initial_sp
2. 初始化 PC 指针 =Reset_Handler
3. 初始化中断向量表
4. 配置系统时钟
5. 调用 C 库函数 _main 初始化用户堆栈,从而最终调用 main 函数去到 C 的世界

https://baijiahao.baidu.com/s?id=1716363537839969540&wfr=spider&for=pc
https://cloud.tencent.com/developer/article/1599577

7.keil的预处理宏:

STM32F10X_HD 宏:为了告诉 STM32 标准库,我们使用的芯片类型是 STM32 型号是大容
量的,使 STM32 标准库根据我们选定的芯片型号来配置。
USE_STDPERIPH_DRIVER 宏:为了让 stm32f10x.h 包含 stm32f10x_conf.h 这个头文件。

8.断言的实现

#ifdef USE_FULL_ASSERT
	#define assert_param(expr)  ((expr)?(void)0:assert_failed((uint8_t *)__FILE__,__LINE___))
	void assert_failed(uint8_t *file,uint32_t line);
#else
	#define assert_param(expr) ((void)0)
#endif

void assert_failed(uint8_t *file,uint32_t line)
{
	printf("\r\n 输入参数错误,错误文件名=%s,行号=%d",file,line);
}

9.如何让程序陷入无限循环(C语言)

// for循环
for(;;);
// while 循环
while(1);
// do while
do{}while(1);
// goto方式
state:
goto state;

2.查看can总线入门
3.stm32面试合集
5.第一期。题目开源地址
6.c语言
7.内存泄漏
8.stm32和GD32的区别

10.同步通信和异步通信

数据信号所传输的内容绝大部分就是有效数据,而异步通讯中会包含有帧的各种标识符,所以同步通讯的效率更高,但是同步通讯双方的时钟允许误差较小,而异步通讯双方的时钟允许误差较大。
TTL 逻辑1:2.4-5V
逻辑0:0-0.5V
RS_232 逻辑1:-15~-3V
逻辑0:+3V~+15V
而为了增加串口通讯的远距离传输及抗干扰能力,它使用-15V 表示逻辑 1,+15V 表示逻辑 0。
协议层:
起始位为0 数据位0-位7 校验位 停止位为高
串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑 0 的数据位表示,而数据包的停止信号可由0.5、1、1.5 或 2 个逻辑 1 的数据位表示,只要双方约定
一致即可。

通用同步异步收发器 (Universal Synchronous Asynchronous Receiver and Transmitter) 是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。有别于 USART 还有一个 UART(Universal Asynchronous Receiver and Transmitter),它是在 USART 基础上裁剪掉了同步通信功能,只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。

11.存储器种类

易失性:静态随机存储器SRAM
SSRAM
SRAM
动态随机存储器DRAM:
SDRAM
DDR SDRAM

非易失性:
ROM
EPROM
EEPROM
NORFLASH
NANDFLASH

12.I2C读EEPROM

I2C的时钟和数据线都由主机控制,SCL高采样数据,低时数据切换
当有两个主机就会出现同步和仲裁的问题
同步,低电平由最长低电平周期决定,高电平由最短高电平决定。
仲裁,当SCL和SDA都为高电平时,处于空闲状态,主机们才能进行仲裁,最先输出低电平的仲裁成功。
地址为7位和10位。

13.SPI读写串行FLASH

SPI 通讯使用 3 条总线及片选线,3 条总线分别为 SCK、MOSI、MISO,片选线。
MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出,在 SCK 的下降沿时被采样。
[SPI比IIC快]:(https://blog.csdn.net/z1026544682/article/details/90578313)

4.freertos的移植过程

(内核源码,头文件,内存,接口)
1.下载源码
2.拉取FreeRTOSv9.0.0\FreeRTOS\Source的所有.c文件=>>内核源码
3.拉取FreeRTOSv9.0.0\FreeRTOS\Source\portable{\MemMang,\RVDS\ARM_CM3}
4.拉取FreeRTOSv9.0.0\ FreeRTOS\Source\include==》头文件
5.工程中C++配置目录、
6.修改FreeRTOSConfig.h文件,堆栈空间问题,时间节拍
7.修改服务中断函数,stm32f10x_it.c,SysTick_Handler(void)
8.注释掉PendSV_Handler()、SVC_Handler()函数

1.为什么要在中断中进行任务切换

pendsv

freertos的几种状态

就绪 运行 阻塞 挂起

2.中断服务函数的写要求

  1. 中断处理函数的返回值和形参
    中断处理函数不能有返回值和形参,因为中断处理函数都是硬件调用(或者叫触发),没有程序给它传递参数,也没有程序接收它的返回值,其参数的传递通过全局变量的方式。

但是要注意,如果在中断服务函数中改变了供其他函数检测的全局变量的值,要使用volatile关键字定义该全局变量。因为主程序可能将该变量读取到寄存器中,以后每次只使用寄存器中的变量副本,这时候吐过不使用volatile关键字,会导致中断服务函数中修改该变量的操作被短路。

  1. 中断处理函数中进行浮点数运算
    由于浮点运算一般都是由专门的硬件来完成的,硬件设备会牵扯到一些类似全局变量的东西(比如硬件端口,或者硬件设备本身存放的数据),如果浮点运算的过程被中断,而其他函数也可能使用浮点数运算,这就会破坏当前硬件设备中的数据。可以理解为浮点运算一般是不可重入的,因此不能在中断服务函数中使用浮点运算。

可以在满足精度的前提下,将浮点运算扩大若干倍,变成整型运算。

  1. 中断处理函数中使用printf函数
    这个原理跟上面的在中断服务函数中使用浮点数类似,因为printf函数使用硬件资源,而这些资源本身就应该互斥访问(在多线程和多进程中),而这些导致printf函数不可重入,不能在中断中使用。

另外像malloc,free这些函数会使用全局的内存分配表,因此也是不可重入的,不能在中断中使用。

要注意,标准库函数中中很多都是不可重入的,在中断服务函数中要慎重使用它们。

中断服务函数应该是短而有效的。

参考资料:

中断服务函数能不能带形参和返回值?

如何理解这句话:“浮点一般都是不可重入的,printf()经常有重入和性能上的问题”
重入一般可以理解为一个函数在同时多次调用,例如操作系统在进程调度过程中,或者单片机、处理器等的中断的时候会发生重入的现象。
一般浮点运算都是由专门的硬件来完成,举个例子假设有个硬件寄存器名字叫做FLOAT,用来计算和存放浮点数的中间运算结果
假设有这么个函数
void fun()
{
//…这个函数对FLOAT寄存器进行操作
}
假如第一次执行,有个对浮点数操作运算的结果临时存在FLOAT寄存器中,而就在这时被中断了,而中断函数或者另一个进程也调用fun函数,这时第二次调用的fun函数在执行的过程中就会破坏第一次FLOAT寄存器中的结果,这样当返回到第一次fun函数的时候,结果就不正确了。

可以把fun函数理解为printf()()函数。

不可重入的问题
把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写他。

其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。

第一,不要使用全局变量。因为别的代码很可能覆盖这些变量值。

第二,在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL来描述。

第三,不能调用任何不可重入的函数。

第四,谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。

还有一些规则,都是很好理解的,总之,时刻记住一句话:保证中断是安全的!

满足下列条件的函数多数是不可重入的:

(1)函数体内使用了静态的数据结构;

(2)函数体内调用了malloc()或者free()函数;

(3)函数体内调用了标准I/O函数。

下面举例加以说明。

可重入函数

void strcpy(char* lpszDest, char* lpszSrc)

{undefined

while(*lpszDest++ = *lpszSrc++);

*dest=0;

}

非可重入函数1

char cTemp; // 全局变量

void SwapChar1(char* lpcX, char* lpcY)

{undefined

cTemp = *lpcX;

*lpcX = *lpcY;

lpcY = cTemp; // 访问了全局变量,在分享内存的多个线程中可能造成问题

}

非可重入函数2

void SwapChar2(char* lpcX, char* lpcY)

{undefined

static char cTemp; // 静态局部变量

cTemp = *lpcX;

*lpcX = *lpcY;

lpcY = cTemp; // 使用了静态局部变量,在分享内存的多个线程中可能造成问题

}

如何写出可重入的函数?在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用局部变量,写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。

https://blog.csdn.net/maochengtao/article/details/40373039?spm=1001.2101.3001.6650.4&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-4.pc_relevant_paycolumn_v3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-4.pc_relevant_paycolumn_v3&utm_relevant_index=7

Modbus RTU和Modbus ASCII协议应用于串口链接(RS232、RS485、RS422)

FOC

程序跑飞的原因

1、意外中断:是否打开了某个中断,但是没有响应和清除中端标志,导致程序一直进入中断,造成死机假象。
2、中断变量处理不妥:若定义某些会在中断中修改的全局变量,这时要注意两个问题:首先为了防止编译器优化中断变量,要在这些变量定义时前加volatile;其次在主循环中读取中断变量前应该首先关闭全局中断,防止读到一半被中断给修改了,读完之后再打开全局中断,否则出现造成数据乱套。
3、地址溢出,常见错误为指针操作错误:着重说的是数组下标使用循环函数中循环变量,如果循环变量没控制好则会出现数组下标越界,意外修改系统的寄存器造成死机,这种情况下如果死机说明运气好,否则后面不知道发生什么头疼的事。
4、无条件的死循环:比如使用while(x),等待电平变化,正常情况下x都会变成0,就怕万一,因此最好加上时间限制。
5、看门狗没有关闭:有的单片机即使没使用看门狗开机时也有可能意外自动开启了最小周期的看门狗,导致软件不断复位,造成死机。这个要看芯片手册,最好在程序复位后首先应该显式清除看门狗再关闭看门狗。
6、堆栈溢出:最难查找的问题,对于容量小的单片机,尽量减少函数调用层级,减少局部变量,从而减少压栈的时候所需的空间。
当你把以上几条都试过不能解决问题,试一试把你的被调用少函数直接内置到调用的地方并且把占用RAM大的局部变量改成全局变量,试一试说不定就可以了。

堆栈溢出:

内存溢出

[STM32如何解决堆栈溢出问题及详细分析溢出原因](http://www.openedv.com/forum.php?mod=viewthread&tid=90431)

内存泄漏

两个单片机传输一个浮点数

我们都知道,单片机串口传输的单位是字节,而浮点型数是占四个字节,简单思路是用一个char型指针指向浮点型数据,利用指针寻址即可以将浮点数拆成四个char数据。接收端接收到四个char型数,为了还原成float型数据,采用共用体是一不错的方式。

调试异常

优先级

  • 16
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值