持续断断续续更新ing;现已更新完毕~
一、迁移工程
keil建立工程,需要添加启动文件,需要修改魔术棒设置,需要添减文件和文件路径等等……,不难,倒是很麻烦;如果有现成的工程(往往都会有现成的工程),只需要稍微修改工程信息,借助这个基础工程会节省点偷懒点时间;故,说明下如何简单快速的修改工程;简单3个步骤很实用!!!
- 在工程文件目录中,将old.uvoptx和old.uvprojx名字改成new.uvoptx和new.uvprojx。并其他 .uvoptx文件统统删除。如:
- 双击打开new.uvproj,点击,在弹出的界面上,双击“Progect target”下面的工程名,修改成new
- 点击魔术棒,将Name of Executeable中的名称改为new即可,这表示生成的hex文件是new.hex,F7编译一遍,再双击new工程名字,说明形成了新的new.map文件,建立成功了
- 注意:最好先kill重编译,若编译后还有带不需要的文件,删除后重新编译即可。
二、常见驱动异常
2.1 驱动异常 & 复用时钟
AFIO时钟只是在STM32F1系列里被提及。 对于32F1系列,涉及到管脚的EXTI、 REMAP、默认复用管脚(JTAG管脚)、事件输出时就需要开启AFIO时钟。
Bug现象:刚写一个 功能(驱动),出现异常;经过多次排查后,发现是IO管脚驱动初始化的问题。经测试,部分IO位无法正常响应置复位的操作。
问题来源:
- IO时钟使能错误;
- 外设时钟未使能;
- 该管脚为默认复用管脚;
举例:STM32默认启动时PB4、PB3、PA15三个引脚不是普通IO,而是JTAG的复用功能,分别为JNTRST、JTDI、JTDO。如果作为普通IO使用,要先开启复用时钟,再关闭JTAG复用功能,才能作为普通IO口使用。
1 | RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用时钟 |
划重点:初始化中,必须使能 复用端口RCC_APB2Periph_AFIO
的时钟 才能对应关闭JTAG功能(没开启复用时钟,只关闭JTAG复用的操作是无效的)。
对于STM32F1以外的其它STM32系列,不再提AFIO,多了个SYSCFG(系统控制器)外设,其功能跟F1的AFIO 有些类似但有差异:主要管理内存空间的映射、与EXTI中断源有关的IO配置以及其它配置等事务,不同32系列间还各有细小差异。如果不打开SYSCFG时钟,有关内存空间重映射、与EXTI配置的操作就会无效。
2.2 宏定义值错误
库函数的实现,实际上就是把寄存器抽象成一个个变量,宏定义很多替换值对其进行赋值操作;由于可能不同寄存器在同一个32位bit内,因此赋值操作过程中会进行位与操作,防止改写掉其他寄存器配置。
因此,往往库函数的宏定义配置,其替换值往往都是32bit的;类型都一样,编译器是无法识别出你赋错值,例如你写错宏定义进去,编译器并不会报错。
举具体两个例子:
1 | //错误的写法 |
很明显,上面两行代码的时钟使能是错误的,但是编译是不会报错的;因为本质上就是一个赋值操作,赋错值并不会产生报错,除非类型不同、宏定义名称查询不到。不单单是IO的驱动,不管是什么驱动,大家在配置底层驱动的时候一定要细心,配置错误不会报错警,但是会卡住你项目进度。
1 | //错误的写法 |
三、STM32管脚四种输出模式对比
- 普通推挽输出( GPIO_Mode_Out_PP ) :
使用场合
:一般用在0V 和 3.3V 的场合。线路经过两个P_MOS和 N_MOS管,负责上拉和下拉电流。使用方法
:直接使用输出电平
:推挽输出的低电平是 0V,高电平是 3.3V。
- 普通开漏输出( GPIO_Mode_Out_OD ):
使用场合
:一般用在电平不匹配的场合,如需要输出 5V 的高电平。使用方法:就需要再外部接一个上拉电阻,电源为5V,把 GPIO设置为开漏模式,当输出高组态时,由上拉电阻和电源向外输出5V 的电压。输出电平
:在开漏输出模式时,如果输出为0,低电平,则使 N_MOS导通,使输出接地。若控制输出为 1(无法直接输出高电平), 则既不输出高电平也不输出低电平, 为高组态。为正常使用,必须在外部接一个上拉电阻。特性
: 它具“线与”特性,即很多个开漏模式引脚连接到一起时,只有当所有引脚都输出高阻态, 才由上拉电阻提供高电平 , 此高电平的 电压 为外部 上拉电阻所接的 电源 的电压。若其中一个引脚为低电平,那线路就相当于短路 接地 ,使得整条线路都为低电平,0 V。
- 复用推挽输出( GPIO_Mode_AF_PP ) : 用作串口的输出。
- 复用开漏输出( GPIO_Mode_AF_OD ):用在 IIC 。
Ps:所有的开漏输出都需要接上拉电阻
四、中断优先级
中断的概念具体就不赘述,这里以stm32F103RBT6芯片为例讲中断配置
4.1 抢占优先级和响应优先级
STM32的中断向量具有两个属性,一个为抢占属性,另一个为响应属性,其属性编号越小,表明它的优先级别越高。
抢占,是指打断其他中断的属性,即因为具有这个属性会出现嵌套中断(在执行中断服务函数A 的过程中被中断B打断,执行完中断服务函数B,再继续执行中断服务函数A),抢占属性由NVIC_IRQChannelPreemptionPriority 的参数配置。
响应,应用在抢占属性相同的情况下,当两个中断向量的抢占优先级相同时,如果两个中断同时到达,则先处理响应优先级高的中断, 响应属性由NVIC_IRQChannelSubPriority 参数配置。
如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行
4.2 NVIC 的优先级组
STM32的NVIC 有十六个优先级。
(一)STM32分组为:组0-4
(二)分组配置在寄存器SCB->AIRCR中:
(三)解析第二点
- 组0就是4位都用来设置成响应优先级,2^4=16位都是响应优先级
- 组1分为(2^1)两个抢占优先级,在这两个抢占优先级里面还分别有(2^3)八个响应优先级,(2^1)*(2^3) =16
- 组2分为(2^2)四个抢占优先级,在这四个抢占优先级里面还分别有(2^2)四个响应优先级,(2^2)*(2^2) =16
- 组3分为(2^3)八个抢占优先级,在这八个抢占优先级里面还分别有(2^1)两个响应优先级,(2^3)*(2^1) =16
- 组4分为(2^4)十六个都是抢占优先级(2^4) =16
(四)配置示例
1 | NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 |
(五)优先级顺序举例
假定设置中断优先级组为2,然后设置如下:
- 中断3(RTC中断)的抢占优先级为2,响应优先级为1
- 中断6(外部中断0)的抢占优先级为3,响应优先级为0
- 中断7(外部中断1)的抢占优先级为2,响应优先级为0。
那么这3个中断的优先级顺序为:中断7>中断3>中断6
五、定时器
STM32的高级定时器TIM1, TIM8以及通用定时器TIM9, TIM10, TIM11的时钟来源是APB2总线;通用的 TIM2、TIM3、TIM4和TIM5 定时器 挂载在低速的APB1总线上。
5.1 F103定时器时钟疑问
疑问: 以F103为例,APB1 提供时钟:他的最大值是 36M。有很多人不理解,为什么 TIM2 的时钟不是 36M 而是 72M呢?
解答:通用定时器(TIM2-7)的时钟不是直接来自APB1,而是通过APB1的预分频器以后才到达定时器模块
APB1 的时钟最大只能是 36M,在 RCC 时钟配置的函数,也就是程序最开始初始化系统时钟到 72M(AHB)的时候,里面有
1 | RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; |
除以了 2,也就是在我们配置 AHB 为 72M 的时候 APB1 是 72/2=36M 。根据当APB1的预分频系数为1 时,这个倍频器不起作用,定时器的时钟频率等于 APB1 的频率;当 APB1 的预分频系数为其它数值(即预分频系数为 2、4、8 或 16)时,这个倍频器起作用,定时器的时钟频率等于 APB1 的频率两倍。这里我们设置的是 2,那么到定时器就要乘以 2,那么就是 72M 了。
综上,通用定时器的时钟最大为72MHz,挂在APB1上除了通用定时器的其他外设时钟最大为36MHz。这样设计的目的就是让挂在APB1上的低速外设有合适的低速时钟,同时还可以让挂在APB1上的通用定时器能够在不影响低速外设的情况下仍然有高速的时钟。
(注:系统时钟初始化后,默认AHB是72MHz,APB1是AHB的2分频36MHz,通用定时器时钟是APB1的两倍72MHz)
5.2 定时器计时算法
定时器跟时间相关的量有:系统时钟、分频系数、重装载值。
- 系统时钟: 默认不配置的时候是72M,可以根据自己需求配置;
- 分频系数: psc,就是对系统时钟进行多少分频之后在使用,最好设置为72的倍数,方便运算;
- 重新装载值: arr,是计算这么多值,时间到了之后重新开始计算的值,每一次计数的时间为分频之后时钟的到时;
举例:假设系统时间72M,分频系数设置为7200-1,那现在定时器的时钟为10kHz,每计一个数花费1/(10000)秒,重装值设置为5000-1,那一次溢出的时间为500ms。
Time = ((period+1)*(prescaler+1))/sysclock
- time:溢出时间(MHz)
- sysclock:系统时钟(us)
- period:重装值
- prescaler:分频系数
time = ((4999+1)*(7199+1))/72 = 5000 000us = 500ms
Ps:定时器的重装值不能为0。
以上面为例:不改动分频系数的情况下,重装值设置为5000-1,那一次溢出的时间为500ms。如果让溢出的时间为1ms,那重装值要设置为10-1;但是无法设置溢出时间为0.1ms,重装值不能为1-1。该最小的定时溢出时间为2-1,即2ms触发一次定时器中断。
5.3 计时拓展
定时器的计时可以采用 任务机制回调;实现思路如下:
创建结构体数组,用于存放 多定时器任务 数据;结构体内容如下:
1
2
3
4
5
6
7
8
9/*******************************************************************************
块注释:结构体定义声明
*******************************************************************************/
typedef struct{
volatile uint32_t Time_task_name; //任务名称(组名称或上ID号码)
volatile uint32_t Time_Out; //超时时间
volatile uint32_t Time_Count; //任务启动为止到当前的时间
FUNCTION* callback_F; //回调函数
}TIMER_TASK; //定义添加执行任务所需要的数据创建任务需要:任务名称,设定时间,回调函数名;
定时器中断会循环检索任务的存在;如果任务存在则对应的Time_Count(任务计时)++;如果Time_Count和Time_Out(设定时间)相同,则重置Time_Count,并执行回调;
回调函数一般短小精悍,因为是在中断内检索处理;用于置某些标志位或者关闭自身定时器任务
实现具体细节如下(以函数为划分):
- 创建某个定时器任务
- 复位某个定时器任务
- 清除某个定时器任务(一般在执行回调函数中使用)
- 清除全体定时器任务
- 获取当前定时器计数(其值为该函数返回值)
六、串口通讯
这里讲串口常见概念和现象。
6.1 TXE 和 TC
串口的发送TX标志位 USART_FLAG_TXE
和 USART_FLAG_TC
的理解:
TXE
是指“手里要搬运的”空;TC
是指“地上要搬运的”空;
USART_FLAG_TXE
来说,只是说明数据寄存器中的数据已经被发送移位寄存器取走了USART_FLAG_TC
来说,没必要每次当发送移位寄存器中的数据发送完成后都发生中断,而应该是整个串口数据帧全部发送完毕,包括最后一个字节也发送出去之后才应该开中断,这代表的就是一个数据帧发送完成事件了。
6.2 不停进入串口中断的Bug
Bug现象:在使用stm32的时候,发现usart会莫名的卡在串口中断里,然而串口初始化只配置了RXNE
中断,打断点发现不断进入中断却发现不是RXNE
中断引起的
该Bug来源:经过查找资料发现是
ORE
的问题:开启RXNE
中断同时,ORE
也会被开启;
但是如果直接用 USART_GetITStatus
无法读取到ORE
标志位 置位的信息,这样也就无法消除中断申请(不知道什么时候置位),一直进入串口中断;因为我们没有使能ORE
标志位,所以才读不到 置位 信息;故
1 | USART_ITConfig(USART1, USART_IT_ORE, ENABLE); |
然后在串口中断内做对应处理:
1 | //注意!不能使用if(USART_GetITStatus(_UART_M, USART_IT_RXNE) != RESET)来判断 |
STM32中文参考手册541页,内容如下:
ORE:过载错误 (Overrun error)
当RXNE仍然是’1’的时候,当前被接收在移位寄存器中的数据,需要传送至RDR寄存器时,硬件将该位置位。如果USART_CR1中的RXNEIE为’1’的话,则产生中断。由软件序列将其清零(先读USART_SR,然后读USART_CR)。
- 0:没有过载错误;
- 1:检测到过载错误。
注意:该位被置位时, RDR寄存器中的值不会丢失,但是移位寄存器中的数据会被覆盖。如果设置了EIE位,在多缓冲器通信模式下,ORE标志置位会产生中断的
关于ORE的更细致处理方法主要还是参照下面几个博主的文章: