调试
OLED:方便,试试更新,但是显示框小。
串口:数据全,但是带电脑不方便。
MDK 自带 debug
OLED 调试
4个引脚的:3.3~5V 电压,地,SCL SDA 的 IIC。
我们把 GND-VCC-SCL-SDA 接到 B6-B9 上,B6 B7接高低电平。代码和51非常像,先使用老师的代码调试即可。
DeBug 调试
编译后点击放大镜d的符号。会显示汇编代码寄存器等信息,这些C编程都不用太管。
debug 从左到右的按钮意思:复位,全速运行,停止,向下一行,跳出当前函数,跳转到光标指定位置。
窗口部分:命令窗口,反汇编窗口,符号窗口(里面可以查看一些寄存器的结构,右键添加到 watch 窗口还可以实时查看值)。
Peripherals-System View 里面可以查看外设实时的值。
中断
可以使程序先停止运行去处理一些事情。
中断优先级:有一定的优先级。
中断嵌套:低优先级中断处理时,可以被高优先级中断中断。
看门狗中断:程序定期喂狗,一段时间不喂触发,可能是卡死了,可以加一些校验是否卡死的逻辑。
PVD 中断:电量低触发,可以提醒充电。
NVIC 可以对中断的优先级分组,他接收多个中断,按优先级顺序分配给 CPU。0最高,15最低。优先级相同的按中断号排队。
有两种调度方法,一种是插队的,需要把当前中断处理完再调度下一个;另一种是不插队的,可以打断当前中断(抢占)。
EXTI
检测 GPIO 电平变化触发中断(PVD RTC USB Ethernet 也包含在内),可以上升 下降 双边沿 或软件触发。
所有 GPIO 引脚都可以,但是同 Pin 的不可以同时做中断引脚(PA1 PB1)。
32有两种触发响应的方式,一种是中断响应,发生响应时去 CPU 触发中断。一种是事件响应,发生响应时转而去触发其他的事件。
外部中断只有 16 个Pin,所以所有 GPIO 通过 AFIO 中断引脚选择,挑选一个 Pin 到 EXTI 边沿检测中。
AFIO 还有一个功能,让有复用功能的引脚重映射的功能。
为了节省线路,9-5, 15-10 会触发同一个中断函数,因此需要通过标志位区分。
边沿检测电路和软件中断时间寄存器只要有一个是1,就让或门结果=1.
触发事件就走下路,触发中断就走中路。
中断屏蔽寄存器:是否开启中断。
请求挂起寄存器:是否开启了当前引脚的中断。
什么时候用外部中断?
比如突发的需要读取的转瞬即逝的信号。比如旋转编码器。
按键需要处理消抖,而且也不是转瞬即逝,建议持续循环读取或者定时器中断读取。
旋转编码器
光栅式内部有红外光,旋转时会遮挡-接收-遮挡-接收,进而测定旋转速度。但是这种无法测定方向。
机械触点式旋转时依次接通触点,且左右旋转不同向会产生有相位差的方波。不适合高速旋转的电机测速。
其内部还有一个按键。
霍尔传感器式感应内部磁铁的旋转。
对射式红外传感器
很简单,内部有一个发送和接收红外线的部分,挡住时信号就会变化。
代码:对射式红外传感器
首先介绍一下 GPIO 里和中断相关的函数。
我们设置中断源就需要最后一个函数,这个函数操作 AFIO 选脚。
//1. RCC 2. GPIO Mode 3. AFIO choose a pin 4. EXTI set trigger mode and IT response/Event response 5. set NVIC priority
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//EXTI NVIC don't need open RCC.
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
然后看一下 stm32f10x_exti.h 的内容:
复位,初始化,结构体赋初值,软件中断,查看和清除标志位状态,查看和清除中断标志位状态。
EXTI_Initstruct 包含的参数:中断线(决定引脚),线命令(启用或禁用),中断模式(事件,中断),触发(上升,下降,双边沿)。
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
最后配置 NVIC 中断。中断函数信息在 misc.h 中:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//each chip can only set this group once. 2 means 2 bits for pre-emption priority, 2 bits for subpriority.
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;//choose a bit to open it
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//可以去查看一下设置的 EXTI_Mode 允许的范围
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
这样中断初始化函数就定义好了,
接下来写中断函数,中断函数名字固定,在启动文件中,以 IRQHandler 结尾的函数。找到对应端口的。
我们要先判断一下是不是当前的中断线(因为10-15通用),再清除标志位。
void EXTI15_10_IRQHandler(void){
if(EXTI_GetITStatus(EXTI_Line14)==SET){
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
然后在 main.c 中 include 该文件,并打开调试,在中断函数里打一个断点,然后全速运行,试一下挡住红外线会不会在中断处中断。
成功停下了~
然后我们写一个示例,触发一次中断计数器++,并在 OLED 上打印计数器值。
则我们在 CountSensor 里定义一个全局变量,且写一个 Get 函数。
uint16_t Count_Get(void){
return count;
}
main 里获取这个值并展现在 OLED 上就能记录了有几次挡住。
这里不建议在中断函数里对 OLED 进行操作,因为我们的中断本来就是暂停程序去执行的,如果程序正处理着 OLED 函数,被我们中断了,我们又在中断里操作了 OLED,就会出问题。因此我们这里采取的是设定一个变量,主函数可以访问其值,然后在主函数中再操作 OLED。
代码:旋转编码器
机械旋转编码器左拧右拧时触发触点接通。我们设置左拧-1,右拧+1,根据 OLED 上的数值判断现在拧到什么地步了。
数据处理:检测到左拧(A下降沿,B低电平),返回-1,右拧(B下降沿,A低电平),返回+1,主函数 count+返回值。
下图为右拧(顺时针),先检测B下降沿,再检测A低电平,即可确认确实是顺时针,count++。
#include "stm32f10x.h" // Device header
int16_t count;
void Encoder_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line0 | EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel=EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
NVIC_Init(&NVIC_InitStructure);
}
int16_t Counter_Get(void){
int16_t temp=count;
count=0;
return temp;
}
void EXTI0_IRQHandler(void){
if(EXTI_GetITStatus(EXTI_Line0)==SET){
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0){count--;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
void EXTI1_IRQHandler(void){
if(EXTI_GetITStatus(EXTI_Line1)==SET){
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0)==0){count++;
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
#include "stm32f10x.h"
#include "Delay.h"
#include "Encoder.h"
#include "OLED.h"
int main(void){
int16_t count;
OLED_Init();
OLED_ShowChar(1,1,'A');
OLED_ShowString(1,3,"Hello World!");
Encoder_Init();
while(1){
count+=Counter_Get();
OLED_ShowNum(2,1,count,5);
}
}