前导:本文的目的与,意在于面向应用的学习单片机,故不会涉及太多的原理知识,例如寄存器之类的。
主要目的在于面向应用的学习单片机,学会单片机的基础用法,开发板采取野火的指南者f103。
作者大二小白,写的不好的地方轻点喷,欢迎评论区交流
全部工程代码开源在Gitee仓库
SysTick简介
这是一个24位的系统节拍定时器system tick timer,SysTick,具有自动重载和溢出中断功能,所有基于Cortex_M3处理器的微控制器都可以由这个定时器获得一定的时间间隔。
每当重装载寄存器的值递减到0之后,系统定时器就会产生一个中断,如此往复。例如从1000递减到0后,产生一个中断,执行完中断服务函数后,再回到1000,再次减少到0,再次产生中断,进入中断处理,如此往返。
简单点说,systick就是stm32内置的一个系统中断,为的就是给操作者提供一个心跳节拍吧,当然,可以通过设置重装载计数的值,用来精确定时。
它产生一次中断的计算规则是这样的
- 重装载计数次数:一般写成,SystemCoreCLock(主频)/值这个形式,也可以直接给定数值
- 产生一次中断的时间:Time = 重装载计数次数 * (1/SystemCoreCLock)单位为s 。
举个例子,例如我们写入1000到重装载寄存器中。
stm32f103的默认主频是72M,那么重装载计数次数就是 72M/1000 = 72 000 ;
产生一次中断的时间就是 72000 * (1/72M) = 0.001s = 1ms
代码编写
你无法想象到,SysTick的代码编写有多么简单,真的是一行函数完成配置。
在While(1)前面加一行
SysTick_Config(SystemCoreClock / 1000);
游戏结束,现在每1ms,程序都会进入systick的中断服务函数,这个函数在it.c里。
小实验
实验1 计算秒数
刚刚我们前面不是已经实现了配置SysTick 1ms进一次中断吗?
现在我需要计算秒数的话,是不是只需要判断进去1000次中断时,1ms*1000 = 1s,达到1s后,秒数++就可以了。
代码就是
- 声明一个int second秒变量
- 在it.c里,extern int second,然后声明一个count变量,每次++
- 当count++到1000后,second++,count=0;(清零后下次才能重新从1000)
思路分析完毕,开始写代码。
照例还是复制之前LCD_Test的工程
it.c里。
extern int second;
int count=0;
void SysTick_Handler(void)
{
if(++count==1000)
{
second++;
count=0;
}
}
main.c里
char disp[20];
int second = 0;
void LCD_Show();
int main (void)
{
//...省略初始化和其他操作
SysTick_Config(SystemCoreClock / 1000);
while (1)
{
LCD_Show();
}
}
void LCD_Show()
{
sprintf(disp,"S:%d",second);
ILI9341_DispStringLine_EN(LINE(1),disp);
}
实验2 时钟
在LCD实现一个简单的时钟的效果。如 12:12:45,要求是时分秒都能正常走和进位
上一个实验里,我们其实已经实现了秒数,所以在本次实验中。我们的秒数就可以拿来控制分钟了,也就是it.c里不要做任何改变,写出如下逻辑即可
void Time_Logic()
{
//到达60s,那就是一分钟到了
if(second==60){
second = 0;
minute++;
}
//到达60分钟,那就是1h到了
if(minute==60){
minute = 0;
hour++;
}
if(hour==24){
hour=0;
}
}
修改一下LCD的显示,注意这里的占位符%2d,还是比较重要的,如果没有这个占位符的话,在液晶上
1和10的占位个数不一样,就会出一些小问题,%2d就表示了,不管是两位数字还是一位数字,都会占两格
void LCD_Show()
{
sprintf(disp,"%2d:%2d:%2d",hour,minute,second);
ILI9341_DispStringLine_EN(LINE(1),disp);
}
主函数如下
while (1)
{
LCD_Show();
Time_Logic();
}
实验3 交通灯
默认情况下 亮3s绿灯,5s红灯,1s黄灯
当按键1按下,代表有紧急事件,红灯常亮,当按键1再次按下,代表紧急事件停止,恢复默认情况
我的思路是这样的
- 3s+5s+1s为9s,9s = 9000ms,那就是以9000ms为一个来回。
- 在it.c中,分为三段,每段对应一个颜色
- 声明一个mode变量,mode为1的时候正常情况,mode=0的时候红灯常亮
当然也有其他写法,比如it.c里计算秒数,主函数里用这个秒数在1-9的情况里亮灯,到9后清零,就看各位想怎么做了
所以我们先这样写,main.c里声明一个全局变量color
it.c中
extern int color;
extern int mode;
int count;
void SysTick_Handler(void)
{
if(mode==1)
{
count++;
if(count<=3000)
{
color = LED_GREEN;
}
else if(count>=3000 && count<=8000)
{
color = LED_RED;
}
else if(count>=8000 && count<=9000)
{
color = LED_YELLOW;
}
else{
count=0;
}
}
}
然后在主函数main.c里,我们用这个color变量控制灯的颜色,按键控制模式
void LED_Control()
{
if(mode==1)
{
LED_Color(color);//这里的color会在it.c里改变
}
if(mode==0)
{
LED_Color(LED_RED);
}
}
void KEY_Control()
{
char value = KEY_Scan();
if(value=='1')
{
mode = !mode;
}
}
最后while(1)里就变成这样了
while (1)
{
LED_Control();
KEY_Control();
}
实验4 准确延时
思考一下,我们之前的延时怎么实现的?
void Delay(int i){
for(;i>0;i--);
}
事实上这种延时是不准确的,你并不知道它到底能延时多久。所以我们可以利用systick实现准确的延时。
在delay.c里写如下代码,并在delay.h里声明
void Delay_ms(int time)
{
TimeDelay = time;
while(TimeDelay!=0);
}
这里的TimeDelay在it.c里面extern引入。
extern int TimeDelay;
void SysTick_Handler(void)
{
TimeDelay--;
}
然后主函数这样就可以尝试了
while (1)
{
LED_Color(LED_PURPLE);
Delay_ms(500);
LED_Color(LED_OFF);
Delay_ms(500);
}
然后我们可以改造我们之前的LED_Flashing闪烁函数和LED_Stream流水灯函数,让他们指定精确时间
void LED_Flashing(int color,int speed)
{
LED_Color(color);
Delay_ms(speed);
LED_Color(LED_OFF);
Delay_ms(speed);
}
void LED_Stream(int speed)
{
int i;
for(i = 0; i<8; i++)
{
LED_Color(i);
Delay_ms(speed);
}
}
然后做如下测试,
while (1)
{
LED_Flashing(LED_BLUE,3000);
LED_Stream(200);
}
同理,按键的消抖也可以这么改了
char KEY_Scan()
{
char key = '0';
if(KEY1)
{
//这里的延时需要根据每个人板子的按键自己调整,因为消抖是在处理硬件问题,每个人按键体质不一样
//但至少比你看着之前那种奇奇怪怪的数值号,这个延时你知道大概能延时多久
Delay_ms(200);
key = '1';
}
if(KEY2)
{
Delay_ms(200);
key = '2';
}
return key;
}
然后可以做这个测试
int count = 0;
void Test_Delay2()
{
char key = KEY_Scan();
if(key=='1')
{
count++;
}
sprintf(disp,"Count:%d",count);
ILI9341_DispStringLine_EN(LINE(1),disp);
}