STM32的SysTick
SysTick介绍
定义:Systick,即滴答定时器,是内核中的一个特殊定时器,用于提供系统级的定时服务。该定时器是一个24位的递减计数器,具有自动重载值寄存器的功能。当计数器到达自动重载值时,它会自动重新加载并开始新的计数周期。
实现简单的 延时 | 操作系统的时基(如 FreeRTOS ) |
生成定时中断以及进行精确定时和周期定时操作 | 软件看门狗等系统调度操作 |
- 在STM32中,Systick通常以HCLK(AHB时钟)或HCLK/8的频率作为运行时钟。
SysTick工作原理
在使用Systick定时器进行延时操作时,可以设定初值,并使能后,每经过一个系统时钟周期,计数值就减1。当计数到0时,Systick计数器自动重装初值(nus*72)并继续计数,同时内部的COUNTFLAG标志会置1位(计数完成),触发中断(如果中断使能)。这样,可以在中断处理函数中实现特定的延时逻辑。
时钟源:HCLK
SysTick寄存器
- STK_CTRL(控制及状态寄存器)
位 | 名称 | 类 型 | 默认值 | 描述 |
16 | COUNTFLAG | R | 0 | 如果在上次读取本寄存器后, SysTick 已经数到了 0 ,则该位为 1(计数完成) 。如果读取该位后,该位自动清零。 |
2 | CLKSOURCE | R/W | 0 | 0= 外部时钟源( STCLK ), ST 公司将其应用为 8 分频; 1= 内核时钟( FCLK ), ST 公司将其应用为 1 分频。 |
1 | TICKINT | R/W | 0 | 1 = SysTick 倒数到 0 时产生 SysTick 异常请求。(产生中断); 0 = 数到 0 时无动作。 |
0 | ENABLE | R/W | 0 | SysTick 定时器的使能位。 |
- STK_LOAD(重装载数值寄存器)
位 | 名称 | 类型 | 默认值 | 描述 |
23:0 | RELOAD | R/W | 0 | 当倒数至零时,将被重装载的值 |
- STK_VAL(当前数值寄存器)
位 | 名称 | 类型 | 默认值 | 描述 |
23:0 | CURRENT | R/W | 0 | 读取时返回当前倒计数的值,写它则使之清零,同时还会清除 在 SysTick 控制及状态寄存器中的 COUNTFLAG 标志。 |
Hal_Delay函数的底层逻辑
- HAL_Delay函数中(调用的是SysTick中的中断服务函数):
- HAL_Init函数中(调用寄存器,初始化SysTick_Init()函数)
手撸延时函数 (操作寄存器)
1.声明一个微秒的延时函数
步骤:(调用寄存器)
- 给重装载寄存器设置计数的个数:72*nus;
- 清空当前寄存器;
- 给寄存器(CLR)分配系数:不分频72MHz,1us记72个数;
- 打开定时器;
- 为使定时器正常的完成一个周期循环,进行判断:定时器是否正确打开,计数完标志位是否置1;
- 关闭计数器。
void delay_us(uint32_t nus)
{uint32_t temp = 0;//滴答定时器需要配置三个寄存器//1.确定要数多少个数72SysTick->LOAD = 72 * nus;//2.清空当前计数SysTick->VAL = 0x00;//3,配置分频系数SysTick->CTRL |= (1<<2); //将1的二进制向左移2位//4,开启定时器SysTick->CTRL |=(1<<0);
// while(!(SysTick->CTRL & (1 << 16)));//再很多地方用到,定时可能被关掉,要判断定时器是否正常。enabe?1;countflag?1do{temp = SysTick->CTRL;}while((temp & 0x01) && !(temp & (1<<16)));//4.关闭定时器SysTick->CTRL &= ~(1<<0);
// temp &= ~(1<<0);
}
2.声明一个毫秒的延时函数,利用while循环
void delay_ms(uint32_t nms)
{while(nms--)delay_us(1000);
}
3.声明一个延时秒的函数,利用毫秒函数
void delay_s(uint32_t ns)
{while(ns--)delay_ms(1000);
}
手撸操作系统的延时函数
当有操作系统时SysTick是用来做时基,这时定时器不能频繁的打开和关闭。所以,延时函数要用另一种写法。
思路:看定时器的值,当前和下次的数差额,换算成时间。,ticks一共需要记得数:72*nus;利用一个参数tcnt当前已经记了多少个数。利用while循环来记录记得数量,计算总和,如何超过定义的初值停止计数。
- 参数和原理:
- 流程图:
- 微秒函数代码:
(根据流程图写代码)
void delay_us(uint32_t nus)
{//1.画流程提uint32_t ticks;uint32_t tcnt = 0,told,tnow;uint32_t relode = SysTick->LOAD;ticks = nus*72;told = SysTick->VAL;while(1){tnow = SysTick->VAL;if(tnow !=told){if(tnow < told)tcnt += told - tnow;elsetcnt += relode -(tnow - told);told = tnow;if(tcnt > ticks)break; } }
}
- 毫秒和秒的函数和不带操作系统时的函数相同。
项目:智能排队控制系统
项目需求
- 红外传感器检测有人通过并计数;
- 计数值显示在LCD1602;
- 允许通过时,LED1闪烁,蜂鸣器不响,继电器不闭合;
- 不允许通过时,LED2闪烁,蜂鸣器响,继电器闭合;
- 每次允许通过5个人,之后转为不允许通过,3秒后再转为允许通过。
硬件清单
红外避障模块 | 继电器 | 蜂鸣器 | LCD1602 | 开发板 | ST-Link |
小实验:SysTick模拟多线程
一般模拟多线程使用操作系统(FreeRots),但是本章利用SysTick定时器触发中断在中断服务函数里来实现
方法一:(在中断服务函数里直接写)
方法二: (写一个多任务的tasks.c文件)
- tasks.c文件代码:
#include "tasks.h"
#include "stm32f1xx.h"
#include "led.h"
uint32_t task1_cnt = 0;
uint32_t task2_cnt = 0;uint8_t task1_flag = 0;
uint8_t task2_flag = 0;void systick_it(void){if(task1_cnt < 1000)task1_cnt ++;else{task1_flag = 1;task1_cnt = 0; //计数清零}if(task2_cnt < 1000)task2_cnt ++;else{task2_flag = 1;task2_cnt = 0; //计数清零}
}
上面声明的函数类似于 中断服务函数里的 回调函数:写的是服务代码。void task1(void){if(task1_flag == 0)return;else{led1_toggle(); task1_flag = 0; //一定要清除标志位,否则上面标志位一直是1,灯一直亮}
}void task2(void){if(task2_flag == 0)return;else{led2_toggle();task2_flag = 0; //一定要清除标志位,否则上面标志位一直是1,灯一直亮}
}
- 将自己写的 回调函数(服务代码)声明到 void SysTick_Handler(void)里:
- tasks.h文件代码
#ifndef __TASKS_H__
#define __TASKS_H__void systick_it(void);
void task1(void);
void task2(void);#endif
- main.c文件代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "tasks.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz ,将外部HSE倍频到72Mhz*/ led_init(); /* LED初始化 */while(1){ task1();task2();}
}
注意:
利用SysTick模拟多线程存在时间差,不是完全的实时操作系统,只能实现简单的任务。
模块:红外跟随避障
模块介绍
在智能车、机器人和自动化等领域避障技术是确保安全和高效运行的关键。红外避障模块作为一种常见的避障解决方案,因其非接触、响应速度快和抗干扰能力强等优点而备受青睐。本文将详细介绍红外避障模块的特点、工作原理、以及应用案例,帮助您更好地了解这一技术。
工作原理:
红外避障模块不断发射红外信号,当红外信号:
- 有反射回来,OUT 输出低电平,输出指示灯(绿灯)亮。
- 没反射回来,OUT 输出高电平,输出指示灯(绿灯)灭。
红外避障模块上的一对红外线发射与接收管,发射管发射出一定频率的红外线,当检测方向遇到障碍物时,红外线反射回来被接收管接收,经过比较器(LM393)电路处理之后,信号输出接口输出低电平信号,同时绿色指示灯会亮起。
因为黑色能够吸收红外线(红外线不反射),而白色不行(红外线反射),所以除了避障外可用作黑白线循迹、光电开关等等。
常见的用途:
- 机器人避障;
- 小车避障、跟随;
- 流水线计数;
- 黑白线循迹。
工作参数及引脚介绍
- 工作参数:
工作电压 | DC 3.3-5V |
工作温度 | -10°C ~ +50°C |
检测角度 | 35° |
检测距离 | 2 ~ 30 CM可调(不同厂家略有差异),距离越近性能越稳定。 |
灵敏度 | 模块中蓝色的电位器用于调节灵敏度,顺时针旋转,灵敏度变高,检测距离变长;逆时针越小,灵敏度变低,检测距离变短。 |
- 接线如下:
红外避障模块 | STM32 | 备注 |
VCC | 3.3/5V | 电源正极 |
GND | GND | 电源负极 |
OUT | 任意GPIO口 | 数字输出 |
小实验:红外点灯
- 实验目的:用手遮挡红外传感器,LED1点亮2s,之后熄灭。
- 硬件清单:
红外传感器 | 上官二号 | ST-Link |
- 硬件接线:
STM32 | 红外避障模块 |
5v | VCC |
G | GND |
GPIO口:PB4 | OUT |
- exti.c文件代码:
原理:低电平触发中断,反转led灯。
步骤:(中断函数)
- 写一个初始化中断的函数:开启GPIOA的时钟,配置GPIO口;
- 写一个中断服务函数,里面调用一个GPIO口的公共服务函数;
- 调公共服务函数中的回调函数,编写服务代买;
- 获取中断标志位函数;
- 设置中断标志位的函数。
#include "exti.h"
#include "stm32f1xx.h"
#include "delay.h"
#include "led.h"//定义一个标志位
uint8_t vibrate_flag = False;void exti_init(void){//打开时钟__HAL_RCC_GPIOA_CLK_ENABLE();//初始化GPIO口GPIO_InitTypeDef GPIO_Initstruct;GPIO_Initstruct.Mode =GPIO_MODE_IT_FALLING;GPIO_Initstruct.Pin = GPIO_PIN_4;GPIO_Initstruct.Pull = GPIO_PULLUP;GPIO_Initstruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA,&GPIO_Initstruct);//在HAL_Init()函数中进行优先级分组,默认第四种,实际用二种HAL_NVIC_SetPriority(EXTI4_IRQn,2,0);HAL_NVIC_EnableIRQ(EXTI4_IRQn);
}//中断服务函数,
void EXTI4_IRQHandler(void){//调用GPIO的中断公共函数,不同外设不同HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);}
//回调函数(GPIO口只有一个),业务代码:按键;消抖
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){//消抖,震动可忽略不计: delay_ms(20);if(GPIO_Pin == GPIO_PIN_4){ //判断EXTI0是否是GPIO_PIN0if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4) == GPIO_PIN_RESET){//判断GPIO口是否是低电平
// led1_toggle();//设置中断标志位vibrate_flag = Ture;}}
}
//让主函数识别中断标志位,定义一个获取中断标志位的函数
uint8_t vibrate_flag_get(void){uint8_t temp = vibrate_flag; //第一次调用这个函数,返回:True;第二次调用,返回:Falsevibrate_flag = False;return temp;
}//设置中断标志位的值
void vibrate_flag_set(uint8_t value){vibrate_flag = value;
}
- exti.h文件代码:
#ifndef __EXTI_H__
#define __EXTI_H__
#include "stdint.h"#define Ture 1
#define False 0void exti_init(void);
uint8_t vibrate_flag_get(void);
void vibrate_flag_set(uint8_t value);#endif
- main.c文件代码:
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "exti.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */led_init(); /* LED初始化 */exti_init();while(1){ if(vibrate_flag_get() == Ture){led1_on();delay_ms(2000);led1_off();vibrate_flag_set(False); //避免在触发一次中断,LED灯亮的同时,受到外界的中断触发,标志位一直置为True,小灯一直亮。}}
}
模块:LCD1602显示模块
LCD1602介绍
定义:LCD1602( Liquid Crystal Display 1602),一种常见的字符型液晶显示模块。它能够显示16列2行,共32个字符字符,每个字符都由5x8像素点阵构成,是一种专门用来显示字母、数字、符号等的液晶显示模块。
分类:
显示字符 | 屏幕颜色 | 作电压 |
1602(16列2行)、2004(20列4行)、12864(128列64行) | 蓝屏(白字)、黄绿屏(黑字/白字)、灰屏(黑字) | 5V、3.3V |
工作参数和引脚介绍
- 工作参数
工作电压 工作电流 工作温度 设备寿命 5V 2.0mA -20°C ~ +70°C >100,000小时
- 引脚接线
(注意:在接线完成之前,不要给板子供电,避免把LCD1602显示模块烧坏)
引脚注解:
V0:液品显示器对比度调整端,接正电源时对比度最高,接地时对比度最低,对比度过高时会产 生“鬼影”,使用时可以通过电位器或电阻调整对比度。电阻小了全是黑块,电阻大了不显示。
RW:读写信号线,高电平时进行读操作,低电平时进行写操作。
当 RS (0:指令寄存器)和 RW(0: 写) 共同为低电平时可以写入指令成者显示地址;
当 RS 为低电平(0:指令寄存器)),RW 为高电平(1:读)时可以读出信号;
当 RS 为高电平(1:数据寄存器),RW 为低电平时(0: 写)可以写入数据。
E:使能端,当E端由高电平跳变成低电平时,液晶模块执行命令。
基本操作时序
- 读操作时序图
- 写操作时序图
我们以写操作为例,分析一下时序图。
- 当我们要写指令的时候,RS 置为低电平,RW 置为低电平,E 置为低电平,然后将指令数据送到数据口 D0~D7,延时 tsp1,让 LCD1602 准备接收数据,这时候将 E 拉高,产生一个上升沿,这时候指令就开始写入 LCD1602,延时一段时间,将 E 置为低电平。
- 当我们要写数据的时候,RS 置为高电平,RW 置为低电平,E 置为低电平,然后将指令数据送到数据口 D0~D7,延时 tsp1,让 LCD1602 准备接收数据,这时候将 E 拉高,产生一个上升沿,这时候数据就开始写入 LCD1602,延时一段时间,将 E 置为低电平。
对比一下可以发现,写指令和写数据在时序上只是 RS 的不同,写指令 RS=0,写数据 RS=1
- 时序时间参数如下:
LCD1602指令
指令1 | 清显示,指令码 01H,光标复位到地址 00H 位置。 |
指令2 | 光标复位,光标返回到地址00H。 |
指令3 | I/D:光标和显示模式设置,光标移动方向,高电平右移,低电平左移; S:屏幕上所有文字是否左移或者右移。高电平表示有效,低电平则无效。 |
指令4 | 显示开关控制。 D:控制整体显示的开与关,高电平表示开显示,低电平表示关显示; C:控制光标的开与关,高电平表示有光标,低电平表示无光标; B:控制光标是否闪烁,高电平闪烁,低电平不闪烁。 |
指令5 | S/C:高电平时移动显示的文字,低电平时移动光标; R/L:高电平时右移,低电平时左移。 |
指令6 | 功能设置命令 DL:高电平时为4位总线,低电平时为8位总线; N:低电平时为单行显示,高电平时双行显示; F:低电平时显示5x7的点阵字符,高电平时显示5x10的点阵字符。 |
指令7 | 字符发生器RAM地址设置。 |
指令8 | DDRAM地址设置。 |
指令9 | 读忙信号和光标地址,BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如果为低电平表示不忙。 |
指令10 | 写数据。 |
指令11 | 读数据。 |
LCD1602显存地址
- 写入地址:
写入显示地址时要求最高位 D7 为高电平(1):
- 写入第一行第一个字符的地址:
00000000B(00H)(1行1列)+ 10000000B(80H)(最高位置1) = 10000000B(80H)。
- 写入第二行第一个字符的地址:
01000000B(40H)(1行2列)+ 10000000B(80H)(最高位置1) = 11000000B(C0H)
- 写入字符
字符表如下,和 ASCII 码相似:
例:数字1 = 高八位(0011)+低八位(xxxx0001) = 00110001(对应十进制ASCII表)
LCD1602启动流程
小实验:LCD1602显示内容
实验目的 :
- 使用LCD1602显示一个字符;
- 使用LCD1602显示一个字符串。
硬件清单:
LCD1602 | 上官二号 | ST-Link | 电阻(可以不用) |
硬件接线:
LCD1602 | STM32 |
VSS | GND |
VDD | 5V |
V0 | GND |
RS | B1 |
RW | B2 |
E | B10 |
D0 | A0 |
D1 | A1 |
D2 | A2 |
D3 | A3 |
D4 | A4 |
D5 | A5 |
D6 | A6 |
D7 | A7 |
A | 3.3V |
K | GND |
-
实验1:LCD1602显示一个字符
- lcd1602.c文件代码:
步骤:
- 初始化lcd的函数:初始化GPIO口函数 和 初始化启动过程函数;
- 根据 时序图 编写 写指令 和 写数据 的函数;(使用宏定义更加的方便,简洁)
- 根据 写地址 和 写数据 的原理:编写一个写字符的函数。
#include "lcd.h"
#include "delay.h"//进行RS寄存器宏定义
#define RS_GPIO_Poter GPIOB
#define RS_GPIO_PIN GPIO_PIN_1
#define RS_LOW HAL_GPIO_WritePin(RS_GPIO_Poter,RS_GPIO_PIN,GPIO_PIN_RESET)
#define RS_HIGH HAL_GPIO_WritePin(RS_GPIO_Poter,RS_GPIO_PIN,GPIO_PIN_SET)//进行RW寄存器进行宏定义
#define RW_GPIO_Poter GPIOB
#define RW_GPIO_PIN GPIO_PIN_2
#define RW_LOW HAL_GPIO_WritePin(RW_GPIO_Poter,RW_GPIO_PIN,GPIO_PIN_RESET)
#define RW_HIGH HAL_GPIO_WritePin(RW_GPIO_Poter,RW_GPIO_PIN,GPIO_PIN_SET)//对E寄存器进行宏定义
#define E_GPIO_Poter GPIOB
#define E_GPIO_PIN GPIO_PIN_10
#define E_HIGH HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_SET)
#define E_LOW HAL_GPIO_WritePin(GPIOB,GPIO_PIN_10,GPIO_PIN_RESET)void lcd_init(void){//初始化GPIO口lcd_gpio_init();//(开机)上电的初始化lcd_start_init();
}
//上电的初始化
void lcd_gpio_init(void){__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();GPIO_InitTypeDef GPIO_Initstruct;GPIO_Initstruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_0|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;GPIO_Initstruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_Initstruct.Pull = GPIO_PULLUP;GPIO_Initstruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA,&GPIO_Initstruct);GPIO_Initstruct.Pin = RW_GPIO_PIN|RS_GPIO_PIN|E_GPIO_PIN;GPIO_Initstruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_Initstruct.Pull = GPIO_PULLUP;GPIO_Initstruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB,&GPIO_Initstruct);}
//启动初始化
void lcd_start_init(void){//延时15msdelay_ms(15);//写指令38H(不检测忙信号)lcd_write_cmd(0x38);//延时5msdelay_ms(5);//检测忙信号//写指令38H:显示模式设置lcd_write_cmd(0x38);//写指令08H:显示关闭lcd_write_cmd(0x08);//写指令01H:显示清屏lcd_write_cmd(0x01);//写指令06H:显示光标移动设置lcd_write_cmd(0x06);//写指令0CH:显示开及光标设置lcd_write_cmd(0x0C);
}//根据时序图来完成写指令的函数
void lcd_write_cmd(char cmd){RS_LOW; //写指令RW_LOW; //写E_LOW; //将E置低电平GPIOA->ODR = cmd; //将指令写入到GPIOA的输出寄存器里的的低八位中delay_ms(5); //延时tsp2E_HIGH; //E置高电平delay_ms(5); //延时tpwE_LOW; //将E置低电平
}//根据时序图来完成写数据的函数
void lcd_write_date(char data){RS_HIGH; //写数据RW_LOW; //写E_LOW; //将E置低电平GPIOA->ODR = data; //将指令写入到GPIOA的输出寄存器里的的低八位中delay_ms(5); //延时tsp2E_HIGH; //E置高电平delay_ms(5); //延时tpwE_LOW; //将E置低电平
}//显示一个字符的函数
void lcd_show_char(int colum,char data){//在哪里显示?lcd_write_cmd(0x80+colum);//显示内容?lcd_write_date(data);}
- lcd.h文件代码:
#ifndef __LCD_H__
#define __LCD_H__
#include "stm32f1xx.h"void lcd_init(void);
void lcd_gpio_init(void);
void lcd_start_init(void);
void lcd_write_cmd(char cmd);
void lcd_write_date(char dataShow);void lcd_show_char(int colum,char data);#endif
- main.c文件代码:
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "lcd.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */led_init(); /* LED初始化 */lcd_init();while(1){ lcd_show_char(0x0E,'x');}
}
实验2:lcd显示一个字符串
- 基本流程和上述显示一个字符一样;区别在lcd.c文件中显示字符函数:
- mian.c文件代码:
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "lcd.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */led_init(); /* LED初始化 */lcd_init();while(1){ lcd_show_string(1,0,"xys!!!");lcd_show_string(2,0,"good!!!");}
}
出现的问题和改正:
- LCD屏幕第一行有黑块
可能的问题:接线问题,没烧录代码。
- 利用for循环遍历字符串中的字符时,出现只打印一半的的情况
输出的结果:(不完整)
原因:
- 在于循环条件中错误地使用了动态变化的 strlen(string);
- 每次循环迭代时,string 指针递增,导致 strlen(string) 的值逐渐减少,最终提前终止循环。
- 例如,初始字符串长度为 11,但随着指针后移,后续的 strlen 会依次返回 10、9、8…当循环变量 i 超过当前剩余长度时,循环终止,导致只打印部分字符。
解决办法:预先计算字符串长度并保存在变量中,循环时使用固定的长度值。
完结项目
状态机
定义:状态机(State Machine)是一种用于描述系统行为或功能行为的数学模型。它通常包含一组状态、一组转换条件以及动作执行。状态机通过在不同状态之间进行转换来模拟系统的行为。
主要特点:
- 有限状态:状态机通常具有有限数量的状态。这些状态可以是有序的、离散的或层次化的。
- 转换条件:状态之间的转换是基于特定条件触发的。当满足某个条件时,状态机会从当前状态转换到下一个状态。
- 动作执行:在状态转换过程中,状态机可能会执行某些动作或操作。这些动作可以包括计算、数据更新、输出信号等。
- 确定性和非确定性:状态机可以是确定性的(每个条件唯一对应一个转换)或非确定性的(一个条件可能导致多个可能的转换)。
实现方式:状态机的实现方式多种多样,可以使用编程语言中的条件语句、循环结构或专门的状态机库来实现。此外,硬件设计领域中的有限状态机(Finite State Machine, FSM)也是状态机的一种重要应用。
举例:
while(1)
{if(state == 开心){KTV();撸串();if(女朋友跟人跑了)state = 郁闷;}else if(state == 郁闷){抽烟();嫩模();if(交新女朋友了)state = 开心;else if(刷到良许直播)state = 打鸡血;}else if(state == 打鸡血){写bug();改bug();if(成功入行了)state = 开心;}
}
本项目的状态机
项目框图
硬件接线
执行代码
- tasks.c文件代码:
在模拟系统中,采用多线程,来完成状态的编写,写到滴答定时器的中断服务函数中。
步骤:
- 声明一个滴答定时器的回调函数(写服务代码);
- 服务代码:状态机的状态(是否允许通行)、要执行的行为(led灯的亮灭、继电器的开闭、蜂鸣器的响、lcd显示字符串)、自动转化条件(3s之后状态的转换);
- 声明一个执行条件的函数:当通过5个人的时候,条件转化(当有人通过时计数器计次,利用lcd显示屏显示当前的计数的次数;超过5人后状态转化)。
#include "tasks.h"
#include "stm32f1xx.h"
#include "led.h"
#include "beep.h"
#include "gate.h"
#include "lcd.h"
#include "exti.h"
#include "stdio.h"//定义一个枚举来声明状态机的状态
enum{PASS_STATE, //注意:这里有个逗号WAIT_STATE
}; //这里有个分号uint32_t led1_cnt = 0;
uint32_t led2_cnt = 0;
uint32_t wait_cnt = 0;uint8_t led1_task_flag = 0;
uint8_t led2_task_flag = 0;uint8_t state = PASS_STATE; //定义一个状态:通过和不通过void systick_it(void){ //回调函数//如果处于允许通行的状态if(state == PASS_STATE){//LED1以1s的频率闪烁if(led1_cnt < 1000)led1_cnt ++;else{led1_toggle();led1_cnt = 0;}//LED2不闪led2_off();//蜂鸣器不响beep_off();//继电器打开gate_on();
// lcd_show_string(1,1,"hello");}//如果处于不允许通行的状态else if(state == WAIT_STATE){//LED1不闪,LED2以500ms的频率闪烁led1_off();if(led2_cnt < 500){led2_cnt ++;}else{led2_toggle();led2_cnt = 0;}//蜂鸣器响beep_on();//继电器关闭gate_off();//计时3s之后if(wait_cnt < 5000) // 5swait_cnt ++;else{wait_cnt = 0;//进入允许通行的状态state = PASS_STATE; //LCD显示状态lcd_show_string(1,3,"...PASS...");}}
}//在主函数中调用实现检测人数
uint32_t passager = 0;
void exti_task(void){//检测有人通过if(ia_flag_get() == True && state == PASS_STATE){passager ++;//在lcd显示屏上显示当前的计数人数char message[16]; //创建一个字符数组,存储字符串sprintf(message,"PASS...%02d/05",passager); //将字符串打印到字符数组中lcd_show_string(1,1,message);}//如果通过的人数超过5个if(passager >= 5){passager = 0;//进入不允许通行的状态state = WAIT_STATE;//lcd显示当前的状态lcd_show_string(1,1,"....WAIT....");}
}
- main.c的文件代码:
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "tasks.h"
#include "gate.h"
#include "exti.h"
#include "lcd.h"
#include "beep.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz ,将外部HSE倍频到72Mhz*/ led_init(); /* LED初始化 */gate_init();exti_init();lcd_init();beep_init();while(1){ exti_task();}
}
本项目的新知识的总结:
- 利用systick模拟操作系统,实现多线程的编程;
- 提前将避障红外模块,继电器,蜂鸣器,led灯的代码写好;
- 利用枚举来定义状态机的状态,相比宏函数更加的方便;
- 利用 &&运算符,进行两个条件的限制;
相关文章:
STM32的SysTick
SysTick介绍 定义:Systick,即滴答定时器,是内核中的一个特殊定时器,用于提供系统级的定时服务。该定时器是一个24位的递减计数器,具有自动重载值寄存器的功能。当计数器到达自动重载值时,它会自动重新加载…...
【JS事件循环机制event-loop】
目录 0、总结1、Event-Loop 概念2、宏任务-微任务3、事件循环执行机制4、调用栈5、示例 0、总结 Tasks execute in order, and the browser may render between them 【宏任务按序执行,浏览器可以在它们之间进行渲染】Microtasks execute in order, and are execut…...
对比N+1查询和关联聚合查询
通常我们管第一种模式叫 “N1 查询”,第二种叫 “关联聚合查询”。下面从几个角度来比较,帮助你做出选择。 1. 性能与资源消耗 方案SQL 语句数网络往返次数数据库负载Java 处理N1 查询(先查项目,再遍历项目查设备状态数ÿ…...
优化 Flutter 应用启动:从冷启动到就绪仅需 2 秒
冷启动序列剖析:冷启动时,Flutter 应用需经历引擎和 Dart VM 初始化、启动 Dart Isolate、渲染第一帧等步骤。Android 和 iOS 系统分别通过启动屏幕和 Storyboard 缓解启动延迟。应用大小、初始化工作、调试模式下的 JIT 编译等因素会影响冷启动时间。优…...
牟乃夏《ArcGIS Engine 地理信息系统开发教程》学习笔记 4-空间分析与高级功能开发
目录 一、核心组件与接口回顾 (一)空间分析基础架构 (二)网络分析模块 二、矢量数据空间分析实战 (一)缓冲区分析 (二)叠加分析(以裁剪为例) 三、栅格…...
UE 滚动提示条材质制作
需要两个贴图 先制作条纹屏闪 这里RGB输出连到alpha,0为白色,到1就为黑色了 因为这个图片是RGB输出代表三个图片,看贴图颜色就知道了,然后把这三个相加一下;链接自发光颜色, 这里设置速度变量 通过网盘分…...
金融业数字化转型——深入解读77页2024年中国金融体系指标大全【附全文阅读】
本文主要介绍了金融业通行宝典中国金融体系指标大全的内容,包括央行体系、商业银行体系、非银金融机构与地方金融组织的各项指标。文章详细分析了美联储资产负債表的结构,并概述了美日欧等主要经济体资产负债表状况。 重点内容: 1. 央行体系是金融分析的重点。 2. 美联储资产…...
研究:大模型输出一致性:确定性与随机性的场景化平衡
大模型在相同输入下的输出是否一致,本质上取决于其设计目标、任务性质以及技术实现方式。这一问题需要从技术原理、应用场景、用户需求三个维度进行深度分析: 一、技术实现:确定性与随机性的平衡 模型架构的确定性基础 大模型的核心参数(如权重矩阵)在训练完成后是固定的…...
数据分析1
一、常用数据处理模块Numpy Numpy常用于高性能计算,在机器学习常常作为传递数据的容器。提供了两种基本对象:ndarray、ufunc。 ndarray具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组。 ufunc提供了对数组快速运算的标准数学函数。 ndar…...
vmare pro安装报错用户在命令行上发出了EULAS_AGREED=1,表示不接受许可协议的错误解决方法
问题现状和原因 用户在命令行上发出了EULAS_AGREED1,表示不接受许可协议的错误。 以上错误主要原因是因为机器安装过了vmare 卸载时没有卸载干净导致的。 解决方法: 1、控制面板-程序和功能-卸载程序。找到vamre卸载掉。 2、打开开始菜单输入注册表 …...
《Linux篇》基础开发工具——vim详细介绍
文章目录 1.软件包管理1.1 什么是软件包1.2 Linux软件生态 2.编辑器vim2.1 vim的正常/命令模式2.2 vim的末行模式2.3 vim的插入模式 3.配置vim 1.软件包管理 我们先来看一下再Linux是那个如何安装软件? 源码安装:软件是存在相互依赖的关系的࿰…...
AI图片跳舞生成视频,animate X本地部署。
本期内容打包限时免费下载https://www.kdocs.cn/l/cnQ5lNU5DFZB 对比不同算法,使用同一组图片和舞蹈视频。animate X官网,下载项目解压。按照官方教程下载模型,项目包和命名好的模型包已上传网盘,放到解压目录下即可。 安装好cond…...
Web技术与Apache网站部署
一、Web 基础与 HTTP 协议 1.1 静态网页与动态网页 静态网页 定义:由纯 HTML、CSS、JavaScript 构成,文件扩展名为 .htm 或 .html。内容在服务器生成后固定不变,仅通过客户端脚本(如 JS)实现视觉动态效果(…...
第七章:Server/Client Communication
Chapter 7: Server/Client Communication 从工具集成到服务器通信:如何让AI“远程协作”? 在上一章的工具与LLM集成中,我们已经能让AI调用真实世界的工具。但你是否想过:如果多个用户同时请求天气查询,或者需要远程控…...
Linux调试器 - gdb使用指南
目录 一、背景知识 二、开始使用 gdb (一)查看源代码相关指令 (二)程序执行控制指令 (三)断点相关指令 (四)变量操作相关指令 (五)其他常用指令 在Li…...
C++面试常青客:LRUCache最近最少使用算法
C面试常青客:LRUCache最近最少使用算法 文章目录 C面试常青客:LRUCache最近最少使用算法1.背景🏆2.原理🚀2.1基本原理2.2核心特性 3.结构3.1为什么需要 list<pair<int,int>>(双向链表)…...
【含文档+PPT+源码】基于微信小程序的社交摄影约拍平台的设计与实现
项目介绍 本课程演示的是一款基于微信小程序的社交摄影约拍平台的设计与实现,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系…...
jetson nano上Ubuntu系统调用摄像头bug
今天在做一个比赛的时候,通过调用摄像头做检测并输出目标角度和距离。刚开始用的是 cv::VideoCapture cap; cap.open("/dev/video0");没有任何问题,使用pnp解算得到的角度和距离都是正确的,画面也是小画面。 后面加了一些功能&…...
用Python做有趣的AI项目5:AI 画画机器人(图像风格迁移)
这个项目将使用 PyTorch 实现图像风格迁移(Neural Style Transfer),让一张图片看起来具有另一张图片的“艺术风格”。 🔧 开发环境建议 Python 3.8 PyTorch(pip install torch torchvision) PIL&#x…...
一种用于从视网膜图像中识别疾病的 BERT 式自监督学习 CNN
大家读完觉得有帮助记得关注和点赞!!! 抽象 在医学成像领域,深度学习的出现,尤其是卷积神经网络 (CNN) 的应用,彻底改变了医学影像的分析和解释。然而,深度学习方法通常依…...
OpenCV 图形API(68)图像与通道拼接函数------垂直拼接两个图像/矩阵的函数concatVert()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 对给定的矩阵执行垂直拼接。该函数将两个 GMat 矩阵(列数相同)垂直连接: GMat A { 1, 7,2, 8,3, 9 }; GMat…...
重测序关系矩阵构建方式汇总
样本间亲缘关系矩阵(kinship matrix)和同源性矩阵(IBS matrix)构建的方式 1. 可以使用plink的–make-rel计算个体之间的亲缘关系(强调个体之间的遗传相似性) /opt/software/plink --bfile vcf_bfile--mak…...
OpenCV 图形API(70)图像与通道拼接函数-----创建一个图像或矩阵(GMat)的副本的操作函数copy()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 制作输入图像的一个副本。请注意,这个副本可能不是实际存在的(没有实际复制数据)。使用此函数来维护图的契约…...
30天通过软考高项-第六天
30天通过软考高项-第六天 任务:项目质量管理 思维导图阅读 知识点集锦阅读 知识点记忆 章节习题练习 知识点练习 手写回忆ITTO 听一遍喜马拉雅关于范围的内容 质量管理 -背 1. 过程定义 龟管控 要求标准规划定,计划转化看过程…...
JUC中各种锁机制的应用和原理及死锁问题定位
JUC中各种锁机制的应用和原理及死锁问题定位 在互联网大厂Java求职者的面试中,经常会被问到关于JUC(Java Util Concurrency)中的各种锁机制及其应用和原理的问题。本文通过一个故事场景来展示这些问题的实际解决方案。 第一轮提问 面试官&…...
区块链vs实体经济:一场金融、医疗、政务与物流的“效率革命”
区块链技术作为一种去中心化、不可篡改的分布式账本技术,正在重塑多个行业的运行模式。从金融交易的透明化到医疗数据的安全共享,从政务服务的效率提升到物流供应链的全程可追溯,区块链的跨行业应用展现出巨大的潜力与价值。以下是其在金融、…...
FTP-网络文件服务器
部署思路 单纯上传下载ftp系统集成间的共享 samba网络存储服务器 NFS 网络文件服务器:通过网络共享文件或文件夹,实现数据共享 NAS ( network append storage):共享的是文件夹 FTP:文件服务器samba:不同系统间的文件…...
嵌入式RTOS实战:uC/OS-III最新版移植指南(附项目源码)
文章目录 前言一、uC/OS简介二、工程移植2.1 下载ucos源码2.2 创建空白工程2.3 拷贝ucosiii源码文件2.3.1 UC-CONFIG2.3.2 UC-CPU2.3.3 UC-LIB2.3.4 UC-OS3 2.3 添加工程文件分组及路径2.4 代码首次编译2.5 源码修改2.5.1 cpu_cfg.h2.5.2 os_cpu_c.c2.5.3 lib_cfg.h2.5.4 sys.h…...
10.Excel:快速定位目标值
一 批量删除 1.如何使用 快捷键 CTRLG 补充:直接选择定位条件。 2.作用 1.批量删除工作表中的图片 补充:无法通过框选的方式选中这些图片进行删除。 这样只框选了表格,无法框选图片。因为图片在excel中被认为是一个对象,对象无法通…...
状态模式 (State Pattern)
状态模式(State Pattern)是一种行为型设计模式,它允许对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。该模式将状态封装成独立的类,并将请求委托给当前的状态对象,当对象的内部状态发生变化时,其行为也会随之改变。 一、基础部分 1. 意图 允许一个…...
【Java面试题04】MySQL 篇
文章目录 一、前言🚀🚀🚀二、MySQL 篇:☀️☀️☀️1、MySQL 是如何实现事务的? 后序还在更新中~~~三、总结:🍓🍓🍓 一、前言🚀🚀🚀 ☀️ 你每一…...
同时安装多个版本的golang
https://golang.google.cn/dl/ go install golang.org/dl/go1.20latest 这样就会将 go1.20.exe 下载到 GOPATH/bin,但是此时并没有 go1.20 的源码包,也就不能正常执行 build/run 等指令。 然后执行 go1.20 download下载源码包 > go1.20 download …...
【Web应用服务器_Tomcat】三、Tomcat 性能优化与监控诊断
在企业级 Java Web 应用的运行过程中,Apache Tomcat 作为广泛使用的 Servlet 容器和 Web 服务器,其性能表现直接影响用户体验和业务稳定性。本篇文章将深入探讨 Tomcat 性能优化的实用技巧,以及如何通过有效的监控诊断手段,及时发…...
stm32week13
stm32学习 九.stm32与HAL库 4.时钟树 stm32f103所拥有的时钟源: 外部时钟的稳定性比内部的高,但是成本高,需要在外部额外接 关于上述时钟树的简图: 右下四个是HAL库中的初始化函数 F4的时钟树简图: F7的时钟树简图…...
深入探究C++ 中的stack、queue和deque
目录 一、stack(栈) 二、queue(队列) 三、deque(双向队列) 四、容器适配器总结 在C 的标准模板库(STL)中,stack、queue和priority_queue是非常实用的容器适配器&…...
第十二节:性能优化高频题-shallowRef/shallowReactive使用场景
适用场景:大型对象/列表仅需第一层响应式变化(如JSON配置数据) Vue3 浅层响应式 API(shallowRef/shallowReactive)使用场景深度解析 一、核心使用场景与性能优化原理 大型 JSON 配置数据管理 • 场景特征:…...
openGauss DB4AI与scikit-learn模块对比探究
openGauss当前版本支持了原生DB4AI能力,引入原生AI算子,简化操作流程,充分利用数据库优化器、执行器的优化与执行能力,获得高性能的数据库内模型训练能力。 本文介绍了笔者采用鸢尾花数据集,对openGauss DB4AI功能进行…...
基于大模型的公安预审办案笔录分析的挑战与应对策略-3
引言 :在基于大模型的公安预审办案笔录分析应用过程中,虽然取得了一定的成果,但也面临着诸多挑战。本文将分析这些挑战,并提出相应的应对策略,以推动该技术在公安领域的更好地发展和应用。 引文:https://c…...
ubantu18.04(Hadoop3.1.3)之Flink安装与编程实践(Flink1.9.1)
说明:本文图片较多,耐心等待加载。(建议用电脑) 注意所有打开的文件都要记得保存。 第一步:准备工作 本文是在之前Hadoop搭建完集群环境后继续进行的,因此需要读者完成我之前教程的所有操作。 注意本次实…...
AI辅助编程-cursor开发煤矿持证上岗管理程序需求与设计篇
Cursor 是一款由人工智能驱动的智能代码编辑器,深度融合AI技术以提升开发效率。其核心功能基于GPT-4等先进模型,支持代码生成、错误修复、智能补全及自然语言编程。开发者可通过对话交互直接描述需求,AI即时生成对应代码片段,显…...
如何使用极狐GitLab 议题看板?
极狐GitLab 是 GitLab 在中国的发行版,关于中文参考文档和资料有: 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 议题看板 (BASIC ALL) 议题看板是一个软件项目管理工具,用于计划、组织和可视化功能或产品发布的工作流程。它可…...
计网分层体系结构(包括OSI,IP,两者对比和相关概念)
1. 应用层: 用户与网络的界面,FTP,SMTP, HTTP 2. 表示层(Presentation Layer): 解决用户信息的语法表示问题 数据压缩,加密解密 表示变换 3. 对话层(Session Layer): 功能:允许不同主机的各个进…...
爬虫过程中如何确保数据准确性
在爬虫过程中,确保数据的准确性是非常重要的。数据不准确可能会导致分析结果的偏差,甚至影响决策。以下是一些确保爬虫数据准确性的方法和技巧: 一、验证数据来源 确保数据来源的可靠性是确保数据准确性的第一步。选择信誉良好的网站作为数…...
Maven多模块工程版本管理:flatten-maven-plugin扁平化POM
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
数据库基础与核心操作:从概念到实战的全面解析
目录 1 基本概念2 基本操作2.1 DCL2.2 DDL2.3 DML2.4 DQL(高级查询) 3 高级功能3.1 视图(无参函数)3.2 存储过程(有参函数)3.3 触发器 4 约束4.1 主键约束4.2 UNIQUE KEY(唯一键约束)4.3 FOREIGN KEY(外键约束…...
网络原理 - 10(HTTP/HTTPS - 1)
前面的网络原理 1 - 9,按照 TCP/IP 五层协议栈,介绍了各个层次的核心协议。 应用层:自定义协议(xml,json....) 传输层:UDP/TCP 网络层:IP 数据链路层:以太网 我们这…...
UDP协议详解+代码演示
1、UDP协议基础 1. UDP是什么? UDP(User Datagram Protocol,用户数据报协议)是传输层的核心协议之一,与TCP并列。它的主要特点是: 无连接:通信前不需要建立连接(知道对端的…...
QT事件Trick
拖动 void DWidget::mousePressEvent(QMouseEvent *event) {if(event->button()Qt::LeftButton){QListWidgetItem *selItem currentItem();if(selItem! nullptr){m_startPosevent->pos(); //记录鼠标按下时的起始位置}}QListWidget::mousePressEvent(event); }void DW…...
解答UnityShader学习过程中的一些疑惑(持续更新中)
一、坐标系相关 shader中会有几种空间: 模型空间:以物体自己为中心原点 世界空间:就是unity的世界坐标 观察空间(视图空间):以相机为中心的坐标系 裁剪空间:是一个4d空间,有x,y,z,w…...
【图论 拓扑排序 bfs】P6037 Ryoku 的探索|普及+
本文涉及知识点 C图论 CBFS算法 P6037 Ryoku 的探索 题目背景 Ryoku 对自己所处的世界充满了好奇,她希望能够在她「死」之前尽可能能多地探索世界。 这一天,Ryoku 得到了一张这个世界的地图,她十分高兴。然而,Ryoku 并不知道…...