5。STM32裸机开发(2)

news2024/11/24 5:22:34

嵌入式软件开发学习过程记录,本部分结合本人的学习经验撰写,系统描述各类基础例程的程序撰写逻辑。构建裸机开发的思维,为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);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/518892.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全景描绘云原生技术图谱,首个《云原生应用引擎技术发展白皮书》发布

5月12日&#xff0c;由神州数码主办、北京经开区国家信创园、中关村云计算产业联盟协办的2023通明湖论坛-云原生分论坛在京召开。论坛期间&#xff0c;神州数码联合北京通明湖信息技术应用创新中心、中国信通院和通明智云正式发布了《云原生应用引擎技术发展白皮书》&#xff0…

干货 | 心理学人电脑选购指南来了!

Hello&#xff0c;大家好&#xff01; 这里是壹脑云科研圈&#xff0c;我是喵君姐姐&#xff5e; 当我们在选择电脑时经常会无从下手&#xff0c;不知道该如何才能选择一款既能满足我们的科研需要又具有良好性价比的电脑。 本期我们邀请到了唐仙和梦马来为我们详细解答心理学…

我的Makefile模板

OBJxxxxCFLAGS -g -Wall${OBJ}:${OBJ}.o main.o%*.o:%*.c.PHONY:clean clean:${RM} *.o ${OBJ} core xxxx → xxxx.c xxxx.h main.c 比如&#xff1a; 包含了&#xff1a;

PhotoScan拼接无人机航拍RGB照片

目录 背景 拼接步骤 1.新建并保存项目 2.添加照片 3.对齐照片 4.添加标记&#xff08;Markers&#xff09; 5.添加地面控制点 6.建立批处理任务 7.使用批处理文件进行批处理 8.导出DEM 9.导出DOM 背景 本文介绍使用地面控制点&#xff08;GCPs&#xff09;拼接​​…

Java面试知识点(全)- Java面试基础部分一

Java基础 语法基础 面向对象 封装 利用抽象数据类型将数据和基于数据的操作封装在一起&#xff0c;使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部&#xff0c;尽可能地隐藏内部的细节&#xff0c;只保留一些对外接口使之与外部发生联系。用户无需知道对…

如何解决ChatGPT网络错误的问题,让AI对话更丝滑~

前言 在当今人工智能技术的飞速发展中&#xff0c;ChatGPT 作为一款大型语言模型备受瞩目。近期&#xff0c;其在各大社交媒体平台上的表现更是引来了一片关注之声。无论是与用户进行有趣的对话&#xff0c;还是帮助人们解决实际问题&#xff0c;ChatGPT 展现出了其强大的自然…

谷歌慌了!想发论文得审批,优先开发产品,让OpenAI没得看

来源 | 机器之心 ID | almosthuman2014 众所周知&#xff0c;谷歌就像人工智能领域的「黄埔军校」&#xff0c;自深度学习兴起后培养出了整整一代机器学习研究人员和工程师。很长一段时间里&#xff0c;谷歌就是领先 AI 技术的代名词。 人们已经习惯跟随谷歌的脚步&#xff0c…

操作符(算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符)

目录 算术操作符 移位操作符 移位规则 位操作符 交换两个整形变量的写法 赋值操作符 单目操作符 sizeof和数组的纠缠 和--运算符 多组输入的方案 关系操作符 逻辑操作符 算术操作符 -- 加法操作符&#xff08;&#xff09;&#xff1a;用于将两个值相加。 -- 减法操…

算法修炼之练气篇——练气八层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

cv2BGR转化为RGB

import cv2 import matplotlib.pyplot as plt img cv2.imread(1.png,1)#1加载彩图 0加载灰度图 img2 cv2.cvtColor(img,cv2.COLOR_BGR2RGB)#cv2读取是BGR 如果使用plt包要转换为RGB plt.subplot(1,2,1) plt.imshow(img2) plt.subplot(1,2,2) plt.imshow(img) plt.savefig(&qu…

【Java零基础入门篇】第 ⑥ 期 - 异常处理

博主&#xff1a;命运之光 专栏&#xff1a;Java零基础入门 学习目标 掌握异常的概念&#xff0c;Java中的常见异常类&#xff1b; 掌握Java中如何捕获和处理异常&#xff1b; 掌握自定义异常类及其使用&#xff1b; 目录 异常概述 异常体系 常见的异常 Java的异常处理机制…

【数学】通俗理解泰勒公式(牛顿迭代法有用到)

【数学】通俗理解泰勒公式&#xff08;牛顿迭代法有用到&#xff09; 文章目录 【数学】通俗理解泰勒公式&#xff08;牛顿迭代法有用到&#xff09;1. 介绍2. 通俗理解2.1 近似计算 3. 泰勒公式的推导4. 泰勒公式的定义5. 扩展 — 麦克劳林公式参考 1. 介绍 最近在看一些机器…

java异常的分类(常见的异常类型)

异常的分类 1. 编译时异常 在程序编译期间发生的异常&#xff0c;称为编译时异常&#xff0c;也称为受检查异常(Checked Exception) public class Person {int age;private String name;private String gender;// 想要让该类支持深拷贝&#xff0c;覆写Object类的clone方法即…

C语言—字符函数和字符串函数

字符函数和字符串函数 strlenstrcpystrcatstrcmpstrncpystrncatstrncmpstrstrstrtokstrerrorperror字符分类函数字符转换函数memcpymemmovememmcmpmemset C语言中对字符和字符串的处理很是频繁&#xff0c;但是C语言本身是没有字符串类型的&#xff0c;字符串通常放在 常量字符…

ChatGPT最强对手Claude如何无门槛使用?

Claude&#xff0c;一个冉冉升起的新星&#xff0c;由 chatgpt 团队出来的员工开发的&#xff0c;由于他们对模型的一些发展理念不同&#xff0c;单独融资创建了 Claude&#xff0c;总体来说表现可圈可点&#xff0c;但整体看可能还不如 chatgpt4.0。 ChatGPT 眼中的 Claude C…

【产品应用】一体化电机在卡盘设备中的应用

在现代工业生产中&#xff0c;自动化程度的提高和生产效率的提升对于生产设备的要求也越来越高。卡盘设备作为自动化生产线中的重要组成部分&#xff0c;其设计和制造也必须适应现代工业的需求。一体化电机在卡盘设备中的应用&#xff0c;不仅可以提高生产效率和精度&#xff0…

线程状态是五种还是六种

从操作系统层面上描述线程状态 初始状态&#xff1a;仅仅是语言层面创建了线程对象,还没有与操作系统相关联.比如new 了一个Thread对象还没有调用start方法可运行状态&#xff1a;仅仅是语言层面创建了线程对象,还没有与操作系统相关联.比如new 了一个Thread对象还没有调用s…

微服架构基础设施环境平台搭建 -(二)Docker私有仓库Harbor服务搭建

微服架构基础设施环境平台搭建 -&#xff08;二&#xff09;Docker私有仓库Harbor服务搭建 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动构建、编译、…

【数学杂记】表达式中的 s.t. 是什么意思

今天写题的时候遇见了这个记号&#xff1a;s.t.&#xff0c;查了一下百度。 s.t.&#xff0c;全称 subject to&#xff0c;意思是“使得……满足”。 比如这个&#xff1a; 意思是存在 i i i&#xff0c;使得 i i i 满足 A i ≠ B i A_i\neq B_i Ai​Bi​. 运用这个记号…

MySQL面试题1(索引)

目录 1.MySQL如何实现索引机制 1.1InnoDb与MyISAM索引实现区别是是什么&#xff1f; 1.2一个表中如果没有创建索引&#xff0c;还会创建B树么&#xff1f; 2.说一下B树索引实现原理&#xff08;数据结构&#xff09; 2.1聚簇索引和非聚簇索引在B树实现区别是什么&#xff1…