咸宁市网站建设_网站建设公司_测试工程师_seo优化
2026/1/7 17:22:11 网站建设 项目流程

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询