1. 流水灯模拟多线程
用一个流水灯小实验学习systick,模拟多线程

1.1 main.c
先看main.c文件,main函数中实现两个灯进行不同的任务
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "tasks.h"
 
int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    led_init();                         /* 初始化LED灯 */
 
    while(1)
    { 
        task1();
        task2();
//        led1_on();
//        led2_off();
//        delay_ms(500);
//        led1_off();
//        led2_on();
//        delay_ms(500);
    }
}
 
AI写代码
cpp
运行

HAL_Init()中:使用systick作为时基源,并默认配置1ms滴答(重置后的默认时钟为HSI)

即1ms中断一次

然后就只需要在中断服务函数中实现功能就好了

但是中断服务函数中最好不要写很长,最好把线程写在一个文件中,于是有了task.c

1.2 重点task.c
 重点是task.c,要在这里实现多线程,main主函数里的task1()和task2()正是在这里实现

systick模拟多线程实验时,为什么两个小灯不同频率闪烁的代码写在中断服务函数里,就能实现功能呢?

答:SysTick 通常配置为每 1ms 产生一次中断

       每次中断时,这个函数自动被调用,执行完中断程序再回到断点

       函数内部维护两个任务的计时器,实现:

                任务1:每秒执行一次(1000ms)
                任务2:每0.5秒执行一次(500ms)
在main函数的HAL_Init()函数中默认设置的是每过1ms调用一下中断函数systick_isr(),相当于每过1ms都会检测task1和task2的cnt计数情况。就是每过1ms调用一次中断函数systick_isr(),这1ms后就实现systick_isr()中1000ms的led1和500ms的led2闪烁

#include "tasks.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_isr(void)
{
    if (task1_cnt < 1000)
        task1_cnt++;
    else
    {
        task1_flag = 1;
        task1_cnt = 0;
    }
 
    if (task2_cnt < 500)
        task2_cnt++;
    else
    {
        task2_flag = 1;
        task2_cnt = 0;
    }
}
 
void task1(void)
{
    if(task1_flag == 0)
        return;
    
    task1_flag = 0;
    
    led1_toggle();
}
 
void task2(void)
{
    if(task2_flag == 0)
        return;
    
    task2_flag = 0;
    
    led2_toggle();
}
AI写代码
cpp
运行

在main函数中一直调用task1和task2,如果task1_flag == 0,即task1还没记完数,就return继续运行;否则,跳过return执行task1_flag = 0(没记完数时都为0,现在否则了,说明task1_flag = 1,所以需要重新置零,不能让它后面一直都是1进不去循环了); led1_toggle()(翻转灯);

别忘了中断函数再调用void systick_isr(void)
别忘了在.h文件中补充函数声明
1.3 老演员led.c
#include "led.h"
#include "sys.h"
 
//初始化GPIO函数
void led_init(void)
{
    GPIO_InitTypeDef gpio_initstruct;
    //打开时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();                           // 使能GPIOB时钟
    
    //调用GPIO初始化函数
    gpio_initstruct.Pin = GPIO_PIN_8 | GPIO_PIN_9;          // 两个LED对应的引脚
    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);
    //关闭LED
    led1_off();
    led2_off();
}
 
//点亮LED1的函数
void led1_on(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);   // 拉低LED1引脚,点亮LED1
}
 
//熄灭LED1的函数
void led1_off(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);     // 拉高LED1引脚,熄灭LED1
}
 
//翻转LED1状态的函数
void led1_toggle(void)
{
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
}
 
//点亮LED2的函数
void led2_on(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);   // 拉低LED2引脚,点亮LED2
}
 
//熄灭LED2的函数
void led2_off(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);     // 拉高LED2引脚,熄灭LED2
}
 
//翻转LED2状态的函数
void led2_toggle(void)
{
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
}
AI写代码
cpp
运行

实现的是led1和led2不一样频率的闪烁,led1以1000ms闪烁,led2以500ms闪烁。

2. 什么是SysTick?
Systick,即滴答定时器,是内核中的一个特殊定时器,用于提供系统级的定时服务。该定时器是一个24位的递减计数器,具有自动重载值寄存器的功能。当计数器到达自动重载值时,它会自动重新加载并开始新的计数周期。
在使用Systick定时器进行延时操作时,可以设定初值并使能后,每经过一个系统时钟周期,计数值就减1。当计数到0时,Systick计数器自动重装初值并继续计数,同时内部的COUNTFLAG标志会置位,触发中断 (如果中断使能)。这样,可以在中断处理函数中实现特定的延时逻辑。

3. 手撸带操作系统的延时函数


根据流程图看懂以下代码不难

void delay_us(uint32_t nus)
{
    uint32_t ticks;
    uint32_t tcnt = 0, told, tnow;
    uint32_t reload = SysTick->LOAD;                //重装载值
    
    ticks = nus * 72;                               //需要计的节拍数
    told = SysTick->VAL;                            //刚进入while循环时计数器的值
    
    while(1)
    {
        tnow = SysTick->VAL;
        if(tnow != told)
        {
            if(tnow < told)
                tcnt += told - tnow;
            else
                tcnt += reload - (tnow -told);
            
            told = tnow;                            //下次进入while循环时,当前VAL的值作为told
            
            if(tcnt >= ticks)                       //已计的数超过/等于需要计的数时,退出循环
                break;
        }
    }
}
AI写代码
cpp
运行

写在delay.c

学过C语言的都知道->是用指针访问结构体成员,systick是结构体指针,VAL是它访问的结构体成员,其实它是一个寄存器

4. systick寄存器
SysTick控制及状态寄存器(CTRL)

SysTick重装载数值寄存器(LOAD)

SysTick当前数值寄存器(VAL)

————————————————
版权声明:本文为CSDN博主「逆小舟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2301_76153977/article/details/154233968

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐