今天只刷了每日一题,主要还是在做EXTI中断的实验,题解析放到后面,先总结一下今天学到的知识
stm32的EXTI中断最重要的就是搞清楚下面这张图,再按照这张图的顺序来初始化外设即可
首先AFIO会从GPIO口选择16个端口传输给EXTI,GPIO口的选择的限制是不能够选Pin口一样的端口,比如说选择了GPIOA,GPIO_Pin_0,就不能够选择GPIOB, GPIO_Pin_0以及其他的Pin端口。AFIO将选好的16条线路引加上PVD,RTC,USB,ETH总共20条线路引到EXTI中
EXTI再来选择是要进行中断响应还是事件响应,要注意这里EXTI将端口5到端口9合到了一个端口,端口10到端口15也是。
之后再由NVIC对中断事件进行优先级排序,最后再交给CPU处理。
在我们使用EXTI的时候就可依照着这张图的顺序来进行初始化了。
最最开始的还是我们最熟悉的时钟控制使能:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
这里我们只需要使能GPIO口和AFIO的时钟,像EXTI并没有使能时钟的操作,因为它们使用微控制器内部的专用时钟源。而NVIC本来就是内设部件直接使用内部的专用时钟。
接下来就是初始化各个部件了,首先就是我们的GPIO口,还是需要创建一个GPIO_InitTypeDef类型的结构体来完成初始化:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;//这里选择了PB14端口来jieshou接受中断信息
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
接下来就是初始化AFIO的操作,这个初始化就比较简单,只要选择好需要使用的那个Pin口就好了
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//注意这个函数并没有AFIO的字眼
再接下来就是EXTI的初始化,同样也是需要一个结构体:EXTI_InitTypeDef,但也只需要这个结构体,和NVIC比起来还是比较简单,需要选择中断信号所在的Pin口,还有选择中断响应还是事件响应(中断响应就是需要CPU中断然后来操作,事件响应就是直接给出信号让外设来做出操作),最后要选择是中断触发条件要下降沿触发,上升沿触发,上升下降沿都触发还是软件触发(下降沿即高电平转换成低电平触发,上升沿反之)
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的初始化了,首先要选择我们的抢占优先级和响应优先级的分组情况,抢占优先级就是可以直接中断当前执行的操作来执行中断函数中的操作,即使当前执行的也是一个中断条件触发的中断函数中的操作,只要现在这个中断的抢占优先级高,那么就可以抢占CPU使其中断当前的操作来执行这个抢占优先级高的中断的操作。而响应优先级高的是可以优先排队等当前操作结束后再来进行这个中断的操作。抢占优先级和响应优先级的分配情况是存储在一个4位的空间中,我们可以自定义抢占优先级所占有的位数和响应优先级的位数,以下是函数定义中的解释,分组0时抢占优先级分配0位,响应优先级分配4位,到分组4,抢占优先级分配4位,响应优先级分配0位。
这里我们只有一个中断的操作,对优先级的分配没有要求,这里选择使用分组2的方式。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
接着还是需要创建一个结构体来进行初始化:NVIC_InitTypeDef。
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//因为我们选择的是Pin14,在10到15中间所以要选择EXTI15_10_IRQn
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//这个中断操作的抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//这个中断操作的响应优先级
NVIC_Init(&NVIC_InitStructure);
至此EXTI中断的初始化就完成了,对比之前做的GPIO口的初始化还是复杂不少的,但其实这些函数参数的填写还是比较简单的,遇到不会的右键查找函数定义,基本上也能够解决的七七八八。
在EXTI的头文件中我们能找到使用EXTI需要用到的函数。
void EXTI_DeInit(void);//恢复到复位后的默认状态
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);//EXTI的初始化操作
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);//用于软件中断的触发
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);//获取指定外部中断线的标志状态,主要用在主函数中
void EXTI_ClearFlag(uint32_t EXTI_Line);//清除标志位,主要在主函数中使用
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);//也是获取标志状态,但主要是在中断函数中使用
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);//清除标志位,主要在中断函数中使用
接下来的中断函数中就会用到上面的函数。
这次做的是一个红外对射装置的计数器,红外对射装置被遮挡一次就会计数。
uint16_t CountSensor_Count;
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line14) == SET)//中断信号源置高电平触发中断函数
{
CountSensor_Count++;
EXTI_ClearITPendingBit(EXTI_Line14);//需要将表示有中断触发的标志位置零否则将一直中断
}
}
最后还需要一个获取数值的函数
uint16_t CountSensor_Get(void)
{
return CountSensor_Count;
}
这样我们就可以在主函数中使用中断操作了,
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Countsensor.h"
int main(void)
{
OLED_Init();
CountSensor_Init();
OLED_ShowString(1, 1, "Count:");
while (1)
{
OLED_ShowNum(1, 7, CountSensor_Get(), 5);
}
}
接下来是使用旋转编码器来控制计数器
大体的代码差不太多,就是这个旋转编码器需要连接两个pin端口,以实现分辨出正旋还是反旋。
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;//选中了PB0和PB1
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//这里AFIO端口的选择需要分出两段分别选出两个端口
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;//EXTI的初始化也没有什么区别,只是这里选择了两个端口
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这里就不太一样了,需要分别初始化两个端口,这个IRQ通道一次只能选择一个端口
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);
中断函数这边也比较不一样,需要分别写出正旋和反旋的函数
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) == SET)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
Encoder_Count --;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
Encoder_Count ++;
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
int16_t Encoder_Count;
uint16_t Encoder_Get(void)
{
uint16_t temp;
temp = Encoder_Count;
Encoder_Count = 0;
return temp;
}
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "encoder.h"
int16_t Num;
int main(void)
{
OLED_Init();
Encoder_Init();
OLED_ShowString(1, 1, "Num:");
while (1)
{
Num += Encoder_Get();
OLED_ShowSignedNum(1, 5, Num, 5);
}
}
今天的每日一题:
题目描述:
-
- 给你一个下标从 0 开始、大小为
m x n
的矩阵grid
,矩阵由若干 正 整数组成。你可以从矩阵第一列中的 任一 单元格出发,按以下方式遍历grid
:- 从单元格
(row, col)
可以移动到(row - 1, col + 1)
、(row, col + 1)
和(row + 1, col + 1)
三个单元格中任一满足值 严格 大于当前单元格的单元格。
返回你在矩阵中能够 移动 的 最大 次数。
- 从单元格
- 给你一个下标从 0 开始、大小为
示例:
输入:grid = [[2,4,3,5],[5,4,9,3],[3,4,2,11],[10,9,13,15]] 输出:3 解释:可以从单元格 (0, 0) 开始并且按下面的路径移动: - (0, 0) -> (0, 1). - (0, 1) -> (1, 2). - (1, 2) -> (2, 3). 可以证明这是能够移动的最大次数。
看到本题第一个想到的就是用图论的深度优先搜索来遍历,不过需要注意剪枝,尽量减少遍历的次数
这里我们可以明确的知道,这个能够得到的最大移动次数就是这个矩阵的宽度,所以如果我们已经遍历计数达到矩阵的宽度时就可以return了
另外就是我们可以将遍历过的地点,标记成0防止后面没有必要的比较浪费时间。
class Solution {
public:
int result;
int dir[3][2] = {-1, 1, 0, 1, 1, 1};
void dfs(vector<vector<int>>& grid, int& count, int x, int y){
result = max(result, count);
if (result == grid[0].size()) return;
for (int i = 0; i < 3; i++){
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
if (nextx >= 0 && nextx < grid.size() && nexty >= 0 && nexty < grid[0].size() && grid[nextx][nexty] > grid[x][y]) {
count++;
dfs(grid, count, nextx, nexty);
count--;
}
}
grid[x][y] = 0;
}
int maxMoves(vector<vector<int>>& grid) {
for (int i = 0; i < grid.size(); i++){
int count = 0;
dfs(grid, count, i, 0);
}
return result;
}
};
写完这道题感觉自己对这个深度优先搜索的理解也更加深刻了,其中具体的操作也更加熟悉了。
今天出了刷了每日一题也没多刷别的题了,感觉中心还是要放在单片机的学习上比较好,今天大部分时间也是在学这个EXTI中断。
就是这个WordPress写文章里代码的复制粘贴经常会把缩进都删掉,Tab键又不能给缩进,只能用空格点就很难受,到现在我也只发现力扣提交记录里的代码复制过来会带缩进并且复制过来就直接是代码的格式不用再调。也不知道什么原理。本来还有实验做的视频,但不知道怎么弄上来,改天学学怎么搞。
今天下午还久违地去跑了跑步,时隔3个月再次踏上操场,感觉自己跑的像个僵尸,跑得又慢还累,四五圈就不行了,还是运动的太少了,又胖了回来,感觉自己好容易胖,身边同学回去过年感觉都没什么变化,我自己明显感觉得到自己变胖了,以后还是要坚持跑步才行,跑步后真的睡得香吃的也香,听他们说跑步能健脾胃,寒假的时候去看了中医说是脾虚,感觉就是因为寒假没怎么跑步,现在也总感觉胃经常有一点不舒服,希望多跑跑步能够改善改善吧。
今日推荐歌曲:アルクアラウンド——sakanaction(点击左下角倒数第九首即可收听)