STM32的SysTick

SysTick介紹

定義:Systick,即滴答定時器,是內核中的一個特殊定時器,用于提供系統級的定時服務。該定時器是一個24位的遞減計數器,具有自動重載值寄存器的功能。當計數器到達自動重載值時,它會自動重新加載并開始新的計數周期。

滴答定時器的主要功能

實現簡單的 延時
操作系統的時基(如 FreeRTOS
生成定時中斷以及進行精確定時周期定時操作
軟件看門狗等系統調度操作
  • STM32中,Systick通常以HCLKAHB時鐘)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; } }
}
  • ?毫秒和秒的函數和不帶操作系統時的函數相同。

項目:智能排隊控制系統?

項目需求

  1. ?紅外傳感器檢測有人通過并計數;
  2. ?計數值顯示在LCD1602;
  3. ?允許通過時,LED1閃爍,蜂鳴器不響,繼電器不閉合;
  4. ?不允許通過時,LED2閃爍,蜂鳴器響,繼電器閉合;
  5. ?每次允許通過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模擬多線程存在時間差,不是完全的實時操作系統,只能實現簡單的任務。

?模塊:紅外跟隨避障

模塊介紹

????????在智能車、機器人和自動化等領域避障技術是確保安全和高效運行的關鍵。紅外避障模塊作為一種常見的避障解決方案,因其非接觸、響應速度快和抗干擾能力強等優點而備受青睞。本文將詳細介紹紅外避障模塊的特點、工作原理、以及應用案例,幫助您更好地了解這一技術。

工作原理:

紅外避障模塊不斷發射紅外信號,當紅外信號

  1. 有反射回來,OUT 輸出低電平,輸出指示燈(綠燈)亮。
  2. 沒反射回來,OUT 輸出高電平,輸出指示燈(綠燈)滅。

? ? ? ? 紅外避障模塊上的一對紅外線發射與接收管,發射管發射出一定頻率的紅外線,當檢測方向遇到障礙物時,紅外線反射回來被接收管接收,經過比較器(LM393)電路處理之后,信號輸出接口輸出低電平信號,同時綠色指示燈會亮起。

?????????因為黑色能夠吸收紅外線(紅外線不反射),而白色不行(紅外線反射),所以除了避障外可用作黑白線循跡、光電開關等等。

常見的用途:

  1. 機器人避障;
  2. 小車避障、跟隨;
  3. 流水線計數;
  4. 黑白線循跡。

工作參數及引腳介紹

  • 工作參數:
工作電壓DC 3.3-5V
工作溫度-10°C ~ +50°C
檢測角度35°
檢測距離2 ~ 30 CM可調(不同廠家略有差異),距離越近性能越穩定。
靈敏度模塊中藍色的電位器用于調節靈敏度,順時針旋轉,靈敏度變高,檢測距離變長;逆時針越小,靈敏度變低,檢測距離變短。
  • 接線如下:
紅外避障模塊STM32備注
VCC3.3/5V電源正極
GNDGND電源負極
OUT任意GPIO口數字輸出

?小實驗:紅外點燈

  • 實驗目的:用手遮擋紅外傳感器,LED1點亮2s,之后熄滅。
  • 硬件清單:

紅外傳感器

上官二號

ST-Link

  • 硬件接線:
STM32紅外避障模塊
5vVCC
GGND
GPIO口:PB4OUT
  • 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

工作參數和引腳介紹?

  • 工作參數
    工作電壓工作電流工作溫度設備壽命
    5V2.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地址設置。
指令8DDRAM地址設置。
指令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顯示內容

實驗目的 :

  1. 使用LCD1602顯示一個字符;
  2. 使用LCD1602顯示一個字符串。

硬件清單:

LCD1602上官二號ST-Link電阻(可以不用)

硬件接線:

LCD1602STM32
VSSGND
VDD5V
V0GND
RSB1
RWB2
EB10
D0A0
D1A1
D2A2
D3A3
D4A4
D5A5
D6A6
D7A7
A3.3V
KGND
  • 實驗1:LCD1602顯示一個字符?

  • lcd1602.c文件代碼:

步驟:

  1. 初始化lcd的函數初始化GPIO口函數 初始化啟動過程函數;
  2. 根據 時序圖 編寫 寫指令寫數據 的函數;(使用宏定義更加的方便,簡潔)
  3. 根據 寫地址 和 寫數據 的原理:編寫一個寫字符的函數。
#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)是一種用于描述系統行為或功能行為的數學模型。它通常包含一組狀態、一組轉換條件以及動作執行。狀態機通過在不同狀態之間進行轉換來模擬系統的行為。

主要特點:?

  1. ?有限狀態:狀態機通常具有有限數量的狀態。這些狀態可以是有序的、離散的或層次化的。
  2. 轉換條件:狀態之間的轉換是基于特定條件觸發的。當滿足某個條件時,狀態機會從當前狀態轉換到下一個狀態。
  3. ?動作執行:在狀態轉換過程中,狀態機可能會執行某些動作或操作。這些動作可以包括計算、數據更新、輸出信號等。
  4. 確定性和非確定性:狀態機可以是確定性的(每個條件唯一對應一個轉換)或非確定性的(一個條件可能導致多個可能的轉換)。

實現方式:狀態機的實現方式多種多樣,可以使用編程語言中的條件語句、循環結構或專門的狀態機庫來實現。此外,硬件設計領域中的有限狀態機(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燈的代碼寫好;
  • 利用枚舉來定義狀態機的狀態,相比宏函數更加的方便;
  • 利用 &&運算符,進行兩個條件的限制;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/904703.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/904703.shtml
英文地址,請注明出處:http://en.pswp.cn/news/904703.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Java項目腳手架系列】第一篇:Maven基礎項目腳手架

【Java項目腳手架系列】第一篇:Maven基礎項目腳手架 前言 在Java開發中,一個好的項目腳手架可以大大提高開發效率,減少重復工作。本系列文章將介紹各種常用的Java項目腳手架,幫助開發者快速搭建項目。今天,我們先從最基礎的Maven項目腳手架開始。 什么是項目腳手架? …

Kafka的消息保留策略是怎樣的? (基于時間log.retention.hours或大小log.retention.bytes,可配置刪除或壓縮策略)

Kafka 消息保留策略詳解 1. 核心保留機制 # Broker 基礎配置示例&#xff08;server.properties&#xff09; log.retention.hours168 # 默認7天保留時間 log.retention.bytes1073741824 # 1GB 大小限制2. 策略類型對比 策略類型配置參數執行邏輯適用場景時間刪除log.re…

五一の自言自語 2025/5/5

今天開學了&#xff0c;感覺還沒玩夠。 假期做了很多事&#xff0c;弄了好幾天的路由器、監控、錄像機&#xff0c;然后不停的出現問題&#xff0c;然后問ai&#xff0c;然后解決問題。這次假期的實踐&#xff0c;更像是計算機網絡的實驗&#xff0c;把那些交換機&#xff0c;…

安卓基礎(靜態方法)

靜態方法的特點?? ??無需實例化??&#xff1a;直接用 類名.方法名() 調用。 ??不能訪問實例成員??&#xff1a;只能訪問類的靜態變量或靜態方法。 ??內存中只有一份??&#xff1a;隨類加載而初始化&#xff0c;生命周期與類相同。 // 工具類 MathUtils publi…

EasyRTC嵌入式音視頻通話SDK驅動智能硬件音視頻應用新發展

一、引言 在數字化浪潮下&#xff0c;智能硬件蓬勃發展&#xff0c;從智能家居到工業物聯網&#xff0c;深刻改變人們的生活與工作。音視頻通訊作為智能硬件交互與協同的核心&#xff0c;重要性不言而喻。但嵌入式設備硬件資源受限&#xff0c;傳統音視頻方案集成困難。EasyRT…

《數字圖像處理(面向新工科的電工電子信息基礎課程系列教材)》封面顏色空間一圖的選圖歷程

禹晶、肖創柏、廖慶敏《數字圖像處理&#xff08;面向新工科的電工電子信息基礎課程系列教材&#xff09;》 學圖像處理的都知道&#xff0c;彩色圖像的顏色空間很多&#xff0c;而且又是三維&#xff0c;不同的角度有不同的視覺效果&#xff0c;MATLAB的圖又有有box和沒有box。…

Flutter 異步原理-Zone

前言 Zone 是 Dart 異步模型中的核心機制&#xff0c;主要用于&#xff1a; 隔離異步上下文&#xff0c;形成邏輯上的執行環境。捕獲未處理的異步異常&#xff0c;保證系統穩定。自定義異步任務的調度行為&#xff08;比如微任務、Timer&#xff09;。 什么是 Zone&#xff1…

聊一聊自然語言處理在人工智能領域中的應用

目錄 一、智能交互與對話系統 二、 信息提取與文本分析 三、機器翻譯與跨語言應用 四、內容生成與創作輔助 五、 搜索與推薦系統 六、垂直領域的專業應用 七、關鍵技術支撐 自然語言處理NLP屬于AI的一個子領域&#xff0c;專注于讓機器理解和生成人類語言&#xff0c;比…

Redis的過期設置和策略

Redis設置過期時間主要有以下幾個配置方式 expire key seconds 設置key在多少秒之后過期pexpire key milliseconds 設置key在多少毫秒之后過期expireat key timestamp 設置key在具體某個時間戳&#xff08;timestamp:時間戳 精確到秒&#xff09;過期pexpireat key millisecon…

vite:npm 安裝 pdfjs-dist , PDF.js View 預覽功能示例

pdfjs-dist 是 Mozilla 的 PDF.js 庫的預構建版本&#xff0c;能讓你在項目里展示 PDF 文件。下面為你介紹如何用 npm 安裝 pdfjs-dist 并應用 pdf.js 和 pdf.worker.js。 為了方便&#xff0c;我將使用 vite 搭建一個原生 js 項目。 1.創建項目 npm create vitelatest pdf-v…

【Android】動畫原理解析

一,基礎動畫 基礎動畫,有四種,分別是平移(Translate)、縮放(Scale)、Rorate(旋轉)、Alpha(透明度),對應Android中以下四種。 1,Animation基類 1,基本概念 1,插值器 插值器的作用,是控制動畫過程的參數,可以理解為 時間(t)與動畫進程(d)的函數,動畫僅…

手撕基于AMQP協議的簡易消息隊列-2(所用第三方庫的介紹與簡單使用)

第三方庫的介紹 Protobuf 什么是Protobuf&#xff08;Protocol Buffer&#xff09;&#xff1f; Protobuf是數據結構序列化和反序列化框架 Protobuf的特點有哪些&#xff1f; 通用性&#xff1a;語??關、平臺?關。即 ProtoBuf ?持 Java、C、Python 等多種語?&#xf…

Altera系列FPGA實現圖像視頻采集轉HDMI/LCD輸出,提供4套Quartus工程源碼和技術支持

目錄 1、前言工程概述免責聲明 2、相關方案推薦我已有的所有工程源碼總目錄----方便你快速找到自己喜歡的項目Altera系列FPGA相關方案推薦 3、設計思路框架工程設計原理框圖輸入Sensor之-->OV7725攝像頭輸入Sensor之-->OV5640攝像頭輸入Sensor之-->串口傳圖輸入圖像緩…

ABP vNext 集成 CAP + RabbitMQ 實現可靠事件總線

&#x1f680; ABP vNext 集成 CAP RabbitMQ 實現可靠事件總線 在分布式系統中&#xff0c;事件總線是實現服務解耦與最終一致性的核心手段。本文將以 ABP vNext 8.1 為基礎&#xff0c;手把手教你如何集成 CAP RabbitMQ 構建可靠的事件驅動架構。 &#x1f3af; 本文適用于…

Linux 服務器靜態 IP 配置初始化指南

? 第一步&#xff1a;確認網絡管理方式 運行以下命令判斷系統使用的網絡管理服務&#xff1a; # 檢查 NetworkManager 是否活躍 systemctl is-active NetworkManager# 檢查 network&#xff08;舊服務&#xff09;是否活躍 systemctl is-active network或者檢查配置路徑&…

C++ 工具鏈與開發實踐:構建安全、高效與創新的開發生態

引言 在 C 的技術演進中&#xff0c;工具鏈的革新與開發實踐的迭代始終是推動語言生命力的核心動力。從內存安全的攻防體系到嵌入式設備的能效優化&#xff0c;從跨平臺開發的降本增效到開發者社區的生態構建&#xff0c;C 正通過工具鏈與方法論的雙重升級&#xff0c;應對復雜…

跨瀏覽器自動化測試的智能生成方法

一、背景與挑戰&#xff1a;跨瀏覽器測試為什么“難”&#xff1f; 在現代Web應用開發中&#xff0c;跨瀏覽器兼容性是用戶體驗的底線保障。面對Chrome、Firefox、Safari、Edge乃至IE、移動瀏覽器等多種運行環境&#xff0c;開發者與測試人員常面臨&#xff1a; 相同DOM在不同…

【Hive入門】Hive安全管理與權限控制:用戶認證與權限管理深度解析

目錄 引言 1 Hive安全管理體系概述 2 Hive用戶認證機制 2.1 Kerberos集成認證 2.1.1 Kerberos基本原理 2.1.2 Hive集成Kerberos配置步驟 2.1.3 Kerberos認證常見問題排查 2.2 LDAP用戶同步 2.2.1 LDAP協議概述 2.2.2 Hive集成LDAP配置 2.2.3 LDAP與Hive用戶同步架構…

0X. Linux嵌入式系統(課堂筆記)

目錄 一. 開發板橋接 二. 開發板白屏 三. 0324-MPU6050開發 3.1 函數詳解 3.2 常用 ioctl 請求碼&#xff08;request&#xff09; 3.3 頭文件詳解 四. 獲取鼠標信息 4.1 獲取鼠標信息 4.2 內核修改并編譯 五. QT基礎使用 六. 內核打印Hello world 七. 內核GPIO …

qml中的TextArea使用QSyntaxHighlighter顯示高亮語法

效果圖&#xff0c;左側顯示行號&#xff0c;右側用TextArea顯示文本內容&#xff0c;并且語法高亮。 2025年5月8號更新 1、多行文本注釋 多行文本注釋跟普通的高亮規則代碼不太一樣&#xff0c;代碼需要修改&#xff0c;這里以JavaScript舉例。 先制定多行文本注釋規則&…