5。STM32裸机开发(2)

嵌入式软件开发学习过程记录,本部分结合本人的学习经验撰写,系统描述各类基础例程的程序撰写逻辑。构建裸机开发的思维,为RTOS做铺垫(本部分基于库函数版实现),如有不足之处,敬请批评指正。

(2)中还是IO的一些基本操作和扩展,包括系统定时器、蜂鸣器、数码管、按键

一 SysTick系统定时器

        SysTick系统定时器是ARM Cortex-M微控制器中的一个定时器,可以用于实现一些基本的计时和调度功能。它可以作为系统时钟的一个计数器,以固定的时间间隔来触发中断,从而实现一些定时任务的调度,例如周期性的数据采集、任务轮询等。作为一个 24 位向下递减的定时器,每计数一次所需时间为 1/SYSTICK,SYSTICK为系统定时器时钟

SysTick系统定时器的主要功能包括:

  1. 提供了一个硬件定时器,可以以固定的时间间隔触发中断,从而实现一些定时操作;
  2. 可以作为系统时钟的计数器,方便进行一些时间相关的操作和计算;
  3. 可以与其他外设如串口、定时器等配合使用,实现更复杂的系统控制功能;
  4. 可以用于软件延时或者忙等待的实现,避免在一些特定场景下使用while循环等方式造成CPU浪费等问题。

        总之,SysTick系统定时器可以作为一个非常方便的系统时钟计数器和定时器,为嵌入式系统的各种应用提供了基础的时间和定时相关功能。

        注意:STM32F1的库函数中,没有提供SysTick定时器配置函数,因此需要我们根据芯片寄存器进行撰写。SysTick 定时器寄存器分别是 CTRL、LOAD、VAL、CALIB。此处需要查看Cortex M3手册(以下摘自文档内容)

CTRL寄存器
注:CLKSOUTCE 位是用于选择 SysTick 定时器时钟来源,如果该位为 1,表示其时钟是由系统时钟直接提供即 72M。如果为 0,表示其时钟是由系统时钟八分频后提供即 72/8=9M

LOAD寄存器

因为 STM32F1 的 SysTick 定时器是一个 24 位递减计数器,因此重装载寄存器中只使用到了低 24 位,即 bit0-bit23。当系统复位时,其值为 0。

VAL寄存器

同样只有 bit0-bit23 有效,复位时值为 0。

CALIB 寄存器

CALIB寄存器是SysTick系统定时器中的一个寄存器,用于存储SysTick定时器的校准值。该寄存器是一个只读寄存器,长度为32位,包含了3个字段:

  • TENMS:表示每个时钟滴答所对应的时间,单位为微秒;
  • SKEW:标志位,表示当前SysTick定时器的实现是否存在失调(skew);
  • NOREF:标志位,表示SysTick定时器是否支持外部参考时钟。

TENMS字段是CALIB寄存器中最重要的一个字段,它可以告诉我们,当前系统时钟频率下,一次SysTick定时器中断所需要的时间间隔。这个信息在实际应用中非常有用,例如在进行延时操作时,可以使用该值作为延时参数,在不同的系统时钟频率下保证延时的准确性。

SKEW和NOREF字段则分别表示SysTick定时器的实现是否存在时钟失调和是否支持外部参考时钟。如果SKEW字段为1,则表示当前的SysTick定时器实现可能存在时钟失调,需要特别处理;如果NOREF字段为1,则表示SysTick定时器不支持外部参考时钟输入,只能使用内部的系统时钟进行计数。

需要注意的是,CALIB寄存器只能在SysTick定时器使能之前进行读取,并且在操作SysTick定时器之前需要根据实际所使用的时钟频率进行校准。

程序设计

(1)设置 SysTick 定时器的时钟源。
(2)设置 SysTick 定时器的重装初始值(如果要使用中断的话,就将中断
使能打开)。
(3)清零 SysTick 定时器当前计数器的值。
(4)打开 SysTick 定时器。

        同样的,作为一个单片机程序中的公用部分,我们在Public文件夹中创建SysTick.c和SysTick.h文件(想了解为什么这么做可以去看STM32裸机开发(1)

        首先对SysTick定时器初始化,SysTick_Init 函数形参 SYSCLK 表示的系统时钟大小,默认配置使用的系统时钟是 72M,所以调用这个函数时,形参值即为 72。函数内部调用了一个库函数 SysTick_CLKSourceConfig,此函数用来对 SysTick 定时器时钟的选择,使 用 的 SysTick 定 时 器 时 钟 是 系 统 时 钟 的 8 分 频 , 所 以 参 数 是 SysTick_CLKSource_HCLK_Div8。如果使用源系统时钟作为 SysTick 定时器时钟,那么参数即为 SysTick_CLKSource_HCLK。

void SysTick_Init(u8 SYSCLK)
{
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
    fac_us=SYSCLK/8; //SYSCLK的 8分频 保存 1us所需的计数次数
    fac_ms=(u16)fac_us*1000; //每个 ms 需要的 systick 时钟数
}

设计基于SysTick定时器的1us级函数

①将需要延时多少 us 的计数值加载到 SysTick 的 LOAD 寄存器中,fac_us 值是延时 1us 所需的计数值。

②清空当前计数值寄存器 VAL。

③打开 SysTick 定时器,定时器开始向下递减计数。

④CTRL 寄存器的第 16 位是 SysTick 递减到 0 的标志位,如果递减到 0,此为置 1,通过读取该位来判断延时是否完成,从而退出 while 循环。

⑤关闭 SysTick 定时器。

⑥清空当前计数值寄存器 VAL。

void delay_us(u32 nus)
{
    u32 temp;
    SysTick->LOAD=nus*fac_us; //时间加载
    SysTick->VAL=0x00; //清空计数器
    SysTick->CTRL|=0x01 ; //开始倒数
    do
    {
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
    SysTick->CTRL&=~0x01; //关闭计数器
    SysTick->VAL =0X00; //清空计数器
}

设计基于SysTick定时器的1ms级函数,SYSCLK 是系统时钟为 72M,所以最大延时为1864ms。如果需要延时大于 1.864S,可以调用多个 delay_ms 函数即可。(通过引入.h,即可在main.c中直接调用delay_ms(500))

void delay_ms(u16 nms)
{
    u32 temp;
    SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD 为 24bit)
    SysTick->VAL =0x00; //清空计数器
    SysTick->CTRL|=0x01 ; //开始倒数
    do
    {
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
    SysTick->CTRL&=~0x01; //关闭计数器
    SysTick->VAL =0X00; //清空计数器
}

二 蜂鸣器实验及数码管实验

蜂鸣器实验

作为常用的蜂鸣器部件,可以给设备出现故障时进行报警工作。

无源蜂鸣器需提供一定频率的脉冲信号才能发声,频率大小通常在 1.5-5KHz 之间。

有源蜂鸣器加一个 1.5-5KHz 的脉冲信号,同样也会发声,而且改变这个频率,就可以调节蜂鸣器音调,产生各种不同音色、音调的声音。如果改变输出电平的高低电平占空比,则可以改变蜂鸣器的声音大小。

基于前面led点灯和位带实验的基础,此处不多进行解释,直接给出deep.c和deep.h的代码(创建在APP文件夹中)

#include "beep.h"
void BEEP_Init() //端口初始化
{
    GPIO_InitTypeDef GPIO_InitStructure; //声明一个结构体变量,用来初始化 GPIO
    RCC_APB2PeriphClockCmd(BEEP_PORT_RCC,ENABLE); /* 开启 GPIO 时钟 */
    /* 配置 GPIO 的模式和 IO 口 */
    GPIO_InitStructure.GPIO_Pin=BEEP_PIN; //选择你要设置的 IO口
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; // 设置推挽输出模式
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率
    GPIO_Init(BEEP_PORT,&GPIO_InitStructure); /* 初 始 化 GPIO*/
}
#ifndef _beep_H
#define _beep_H
#include "system.h"
/* 蜂鸣器时钟端口、引脚定义 */
#define BEEP_PORT GPIOB
#define BEEP_PIN GPIO_Pin_5
#define BEEP_PORT_RCC RCC_APB2Periph_GPIOB
#define beep PBout(5)
void BEEP_Init(void);
#endif

数码管

        数码管可以看做是多个led灯管组成,可分为共阴数码管,共阳数码管,比如数码管的 8 个段选口(A-DP)是连接在STM32 的 PC 0-7 管脚上。通过输出GPIO口的高低电平,即可控制输出不同的数字,

        具体实现则可以视为创建一个数组,以共阳数码管为例:

u8 smgduan[16]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71};//0~F

三 按键控制实验

        按键作为一个后续常用的一个硬件外设,通过判断输入电平的高低来触发相应的函数任务,通过与中断配合,完成各项功能,此处仅简单介绍不含中断的按键任务。

        通常按键所用开关为机械弹性开关,但会出现抖动,一般为5ms至10ms,因此往往需要采用硬件消抖或软件消抖。

        以图为例:K_UP 按键另一端是接在 3.3V 上,按下时输入到芯片管脚即为高电平;K_LEFT、K_DOWN、K_RIGHT 按键另一端是全部接在 GND 上,按下时输入到芯片管脚即为低电平。在APP文件夹内创建key文件夹,创建key.c和key.h。

        因为硬件电路中K_UP与K_LEFT、K_DOWN、K_RIGHT不同,因此需要设置两种GPIO结构体的参数。其中K_UP是下拉输入模式K_LEFT、K_DOWN、K_RIGHT是上拉输入模式

void KEY_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);

    GPIO_InitStructure.GPIO_Pin=KEY_UP_Pin; //选择你要设置的IO 口
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;//下拉输入
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率
    GPIO_Init(KEY_UP_Port,&GPIO_InitStructure); /* 初始化GPIO */

    GPIO_InitStructure.GPIO_Pin=KEY_DOWN_Pin|KEY_LEFT_Pin|KEY_RIGHT_Pin;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(KEY_Port,&GPIO_InitStructure);
}

        要知道哪个按键被按下,就需要编写按键检测函数,KEY_Scan 函数带一个形参 mode,该参数用来设定是否连续扫描按键,如果 mode 为 0,只能操作一次按键,只有当按键松开后才能触发下次的扫描,这样做的好处是可以防止按下一次出现多次触发的情况。如果 mode 为 1,函数是支持连续扫描的,即使按键未松开,在函数内部有 if(mode==1)这条判断语句,因此 key 始终是等于 1 的,所以可以连续扫描按键,当按下某个按键,会一直返回这个按键的键值,这样做的好处是可以很方便实现连按操作。(注意:此函数中各个按键的扫描优先级是不相同的,如需优先级一致,则需全部设置if语句

u8 KEY_Scan(u8 mode)
{
    static u8 key=1;
    if(key==1&&(K_UP==1||K_DOWN==0||K_LEFT==0||K_RIGHT==0)) //任意一个按键按下
    {
        delay_ms(10); //消抖
        key=0;
        if(K_UP==1)
        {
            return KEY_UP;
        }
        else if(K_DOWN==0)
        {
            return KEY_DOWN;
        }    
        else if(K_LEFT==0)
        {
            return KEY_LEFT;
        }
        else
        {
            return KEY_RIGHT;
        }
    }
    else if(K_UP==0&&K_DOWN==1&&K_LEFT==1&&K_RIGHT==1) //无按键按下
    {
        key=1;
    }
    if(mode==1) //连续按键按下
    {
        key=1;
    }
    return 0;
}

key.h文件中则依旧借用位带操作命令。 

#ifndef _key_H
#define _key_H

#include "system.h"

#define KEY_LEFT_Pin GPIO_Pin_2 //定义 K_LEFT 管脚
#define KEY_DOWN_Pin GPIO_Pin_3 //定义 K_DOWN 管脚
#define KEY_RIGHT_Pin GPIO_Pin_4 //定义 K_RIGHT 管脚
#define KEY_UP_Pin GPIO_Pin_0 //定义 KEY_UP 管脚

#define KEY_Port (GPIOE) //定义端口
#define KEY_UP_Port (GPIOA) //定义端口

//使用位操作定义
#define K_UP PAin(0)
#define K_DOWN PEin(3)
#define K_LEFT PEin(4)
#define K_RIGHT PEin(2)

//定义各个按键值
#define KEY_UP 1
#define KEY_DOWN 2
#define KEY_LEFT 3
#define KEY_RIGHT 4

void KEY_Init(void);
u8 KEY_Scan(u8 mode);
#endif

main.c

int main()
{
    u8 key,i;
    SysTick_Init(72);
    LED_Init();
    KEY_Init();

    while(1)
    {
        key=KEY_Scan(0); //扫描按键
        switch(key)
        {
            case KEY_UP: 
            led2=0;
            break; //按下 K_UP 按键 点亮D2 指示灯

            case KEY_DOWN: 
            led2=1;
            break; //按下 K_DOWN 按键 熄灭 D2 指示灯

            case KEY_LEFT: 
            led3=1;
            break; //按下 K_LEFT 按键 点亮 D3 指示灯

            case KEY_RIGHT: 
            led3=0;
            break; //按下 K_RIGHT 按键 熄灭 D3 指示灯
        }
        i++;
        if(i%20==0)
        {
            led1=!led1; //LED1 状态取反
        }
        delay_ms(10);
    }
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值