5。STM32裸机开发(1)

news2025/1/11 2:30:21

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

(1)中包括led点灯、stm32的各类时钟和简化程序的位带操作

一 点LED灯

实现点灯功能的程序逻辑为改变指定GPIO口的高/低电平(就要根据硬件电路具体判断),作为入门例程,要养成良好的代码习惯,即功能裸机实现代码在main.c中尽可能少的出现。因此此处新建APP文件夹,并在APP文件夹中新建LED文件夹,创建led.c和led.h文件

1)编写led.c文件 ,两步核心,而为了进一步解耦(解耦是贯穿你程序设计的始终思想),可以在.c文件中定义一些宏,这些宏在.h文件中赋值,比如LED_PIN。最后,别忘了将写好的ledinit()函数在.h文件中声明

1.初始化结构体变量并给内部各类变量赋值,

2.使能时钟

GPIO_InitTypeDef GPIO_InitStructure;//初始化结构体变量
RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE);//使能GPIOC外设时钟,此处因为GPIOC挂载在APB2总线上
GPIO_InitStructure.GPIO_Pin=LED_PIN; //设置 IO 口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //设置推挽输出模式(众多模式中的一种)
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率

/* 初始化 GPIO,这个很重要,第一个形参是 GPIO_TypeDef 类型的指针变
量,而 GPIO_TypeDef 又是一个结构体类型,封装了 GPIO 外设的所有寄存器,
所以给它传送 GPIO 外设基地址即可通过指针操作寄存器内容 */
GPIO_Init(LED_PORT,&GPIO_InitStructure); 

GPIO_SetBits(LED_PORT,LED_PIN); //将 LED 端口拉高,熄灭所有 LED
//GPIO_ResetBits(GPIOC,GPIO_Pin_0); //输出低电平
// #define 可以定义各类宏,减少.c文件中的代码量
#define LED_PORT GPIOC
#define LED_PIN
(GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
#define LED_PORT_RCC RCC_APB2Periph_GPIOC
//写好的ledinit函数别忘了声明
void LED_Init(void);

此处,由GPIO_SetBits(LED_PORT,LED_PIN);GPIO_ResetBits(GPIOC,GPIO_Pin_0); ,可以拓展一些其他的函数

(1)读取输入引脚
        uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
功能:读取端口中的某个管脚输入电平。底层是通过读取 IDR 寄存器。
        uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
功能:读取某组端口的输入电平。底层是通过读取 IDR 寄存器。
(2)读取输出引脚
        uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
功能:读取端口中的某个管脚输出电平。底层是通过读取 ODR 寄存器。
        uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
功能:读取某组端口的输出电平。底层是通过读取 ODR 寄存器。

二 STM32时钟系统(RCC配置)

由led点灯可以发现,使能时钟是GPIO初始化所必须的。因此必须对单片机的时钟树和系统时钟频率进行了解。

在stm32中,不同的外设需要不同的时钟频率,即高速时钟源与低速时钟源,以满足不同的传输速度需求

在STM32系列单片机中,时钟源是系统运行的基础,可以说是系统工作的“心脏”之一。STM32单片机提供了多个时钟源以适应不同应用场景的需求。下面介绍STM32时钟系统中的5个重要时钟源。

  1. LSI(Low-Speed Internal):低速内部时钟源,RC振荡器,频率为40KHz左右。它比LSE和HSI更加节能,适合对功耗要求较高的场合,例如实时时钟(RTC)模块。LSI时钟可以通过使能RCC_APB1PeriphClockCmd函数中的RCC_APB1Periph_LSI来启用。

  2. LSE(Low-Speed External):低速外部时钟源,晶体振荡器,频率为32.768KHz。由于LSE时钟稳定、精度高,主要用于STM32单片机的RTC模块和WWDG(独立看门狗)计时器。LSE时钟可以通过使能RCC_APB1PeriphClockCmd函数中的RCC_APB1Periph_PWR和RCC_BackupResetCmd函数来启用。

  3. HSI(High-Speed Internal):高速内部时钟源,RC振荡器,频率为8MHz。它是STM32单片机出厂时默认的时钟源,并且使用最为广泛,可以提供高达72Mhz的系统时钟频率。HSI时钟可以通过使能RCC_HSICmd函数来启用。

  4. HSE(High-Speed External):高速外部时钟源,晶体振荡器,频率可以选择4MHz、8MHz、12MHz、16MHz等不同频率,最大支持25MHz。通常在需要更高精度、更高稳定性的场合使用,例如USB模块、CAN模块等需要高速通信的场合。HSE时钟可以通过使能RCC_HSECmd函数来启用。

  5. PLL(Phase-Locked Loop):锁相环时钟源,它是基于输入的参考时钟频率(通常是HSI或HSE),通过倍频、分频等数学运算得到更高频率的时钟输出。PLL的输出时钟频率可以高达72MHz,是STM32单片机中最快的时钟源。PLL时钟可以通过使能RCC_PLLCmd函数来启用。

需要注意的是,时钟源的选择要根据具体的应用场景和需求进行选择,不同的时钟源会对系统的性能、功耗等方面产生影响。因此,需要根据实际情况进行选择和配置。

注意:STM32的SystemInit函数执行完,默认的各时钟大小设置如下所示

SYSCLK(系统时钟) =72MHz
AHB 总线时钟(HCLK=SYSCLK) =72MHz
APB1 总线时钟(PCLK1=SYSCLK/2) =36MHz
APB2 总线时钟(PCLK2=SYSCLK/1) =72MHz
PLL 主时钟 =72MHz
stm32中的时钟使能函数包括外设时钟使能和时钟源使能。首先介绍外设时钟使能相关函数,对应了STM32的3条总线,若想使能各类外设,必须找到他是挂载在哪个总线上的
这点我们可以通过STM32参考手册查找,或通过固件库stm32f10x_rcc.h 文件中查找
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

其次介绍时钟源使能函数, STM32 有 5 大类时钟源,这里只挑几个重要的时钟源使能函数介绍,这些函数都是用来使能相应的时钟源,比如我们要使能 PLL 时钟,那么就调用 RCC_PLLCmd 函数,ENABLE 表示使能,DISABLE 表示失能。

void RCC_HSICmd(FunctionalState NewState);
void RCC_LSICmd(FunctionalState NewState);
void RCC_PLLCmd(FunctionalState NewState);
void RCC_RTCCLKCmd(FunctionalState NewState);

时钟源和倍频因子配置函数

用于选择相应的时钟源和配置时钟倍频因子,比如系统时钟,它可以由 HSE、HSI 或者 PLLCLK 作为它的时钟源,具体选择哪个,就是通过时钟源配置函数实现。比如设置 HSE 作为系统时钟源,那么调用的函数就是:

RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE);//配置时钟源为 HSE

时钟倍频因子配置函数主要用来修改系统的时钟频率,比如设置APB1 的时钟频率是 HCLK 的 2 分频。那么可以调用下面这个函数来实现:

RCC_PCLK1Config(RCC_HCLK_Div2);//设置低速 APB1 时钟(PCLK1)
外设复位函数
void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);

自定义系统时钟

一个系统时钟初始化函数

void RCC_HSE_Config(u32 div,u32 pllm) //自定义系统时间(可以修改时钟)
{
RCC_DeInit(); //将外设 RCC 寄存器重设为缺省值
RCC_HSEConfig(RCC_HSE_ON);//设置外部高速晶振(HSE)
if(RCC_WaitForHSEStartUp()==SUCCESS) //等待 HSE 起振
{
RCC_HCLKConfig(RCC_SYSCLK_Div1);//设置 AHB 时钟(HCLK)
RCC_PCLK1Config(RCC_HCLK_Div2);//设置低速 AHB 时钟(PCLK1)
RCC_PCLK2Config(RCC_HCLK_Div1);//设置高速 AHB 时钟(PCLK2)
RCC_PLLConfig(div,pllm);//设置 PLL 时钟源及倍频系数
RCC_PLLCmd(ENABLE); //使能或者失能 PLL
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET);//检查指定的 RCC
标志位设置与否,PLL 就绪
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);// 设 置 系 统 时 钟
(SYSCLK)
while(RCC_GetSYSCLKSource()!=0x08);//返回用作系统时钟的时钟源,0x08:
PLL 作为系统时钟
}
}
系统初始化后的时钟是 72M
RCC_HSE_Config(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
系统初始化后的时钟是 36M
RCC_HSE_Config(RCC_PLLSource_HSE_Div2,RCC_PLLMul_9);

三 STM32位带操作(与库函数配合)

        STM32中的位带操作是一种基于寄存器单个位的操作方式,可以直接对特定的位进行读写操作,而不需要像常规操作那样需要用掩码等方式来进行操作。它可以提高代码的执行效率,同时也方便了程序员对寄存器某个特定位的控制。

        在STM32中,每个GPIO口都有一个32位的寄存器,称为数据寄存器(Data Register),同时每个GPIO口的每个IO口位都有一个独立的标志位Band,这些标志位被组织成一个独立的地址空间。通过利用这些标志位的地址,我们就可以使用位带操作方式对GPIO的某个具体的IO口进行读写控制。

        例如,对于PA3这个IO口,我们可以使用如下位带操作方式:

#define PA3_IN  *(volatile unsigned long *) 0x40020010
#define PA3_OUT *(volatile unsigned long *) 0x40020014

#define PA3_OFF PA3_OUT &= ~(1<<3)
#define PA3_ON  PA3_OUT |= 1<<3
#define PA3_RD  ((PA3_IN >> 3) & 0x01)

        其中,PA3_IN和PA3_OUT是该IO口的数据输入输出寄存器的地址,PA3_OFF和PA3_ON分别是将该IO口输出设置为低电平和高电平的函数,PA3_RD是读取该IO口当前状态的函数。这样,我们就可以通过对PA3_OFF和PA3_ON函数的调用实现对该IO口输出状态的控制,同时也可以通过PA3_RD函数读取其当前状态。

        以GPIO中的IDR和ODR两个寄存器的位操作为例,其中基地址的偏移量分别是8和12,(通过stm32参考手册可以查到),这种对位带操作的定义,根据程序解耦的思想,可以新建与APP同级的Public文件夹进行存放,并新建system.c和system.h文件

//IO 口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08

其中,BIT_ADDR()函数是stm32中的一个宏定义,用于将位带区域的地址转换为内存中的标准地址。在stm32中,位带区域是指每个32位寄存器在内存中对应的一块区域,该区域中的每个比特位都可以被单独访问。 

//IO 口操作,只对单一的 IO 口
//确保 n 的值小于 16
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入

注意程序解耦设计的思路:在led.h中调用Public文件夹中的system.h文件,随后进行宏定义

#define led1 PCout(0) //D1 指示灯连接的是 PC0 管脚
#define led2 PCout(1) //D2 指示灯连接的是 PC1 管脚
#define led3 PCout(2) //D3 指示灯连接的是 PC2 管脚

此时在main.c文件中,我们只需~~~

LED_Init();
led1=!led1; //D1 状态取反

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

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

相关文章

ssm框架之SpringMVC:域共享数据

本篇主要聊的是在springmvc中的共享域传递数据的使用。如果对共享域可能不了解的话&#xff0c;可以看下前面聊servlet的时候&#xff0c;对共享域的详细描述&#xff0c;以及其作用和方法。传送阵 至于如何构建SpringMVC的环境&#xff0c;以及如何构建一个项目&#xff0c;可…

一款基于 Python+flask 的态势感知系统(附完整源码)

一、开发 一个基于linux的态势感知系统&#xff0c;基于python和flask框架开发&#xff0c;项目文件目录如下&#xff1a; admin -核心算法 charts -图表生成 model -类 app.py -主文件 config.py -配置文件 install.py -安装文件 项目文件在文章结尾处~ 二、安装 1、…

矩池云搭建DeepLabV3Plus网络,预测无人机遥感语义分割数据集

先上效果图&#xff0c;效果是真不错呀&#xff01; 带大家复现这个过程 一、下载源码 代码下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1MkW7DOgNHD5h5sfXQ6L1HA 提取码&#xff1a;ynev 权重下载地址&#xff1a; 链接&#xff1a;https://pan.bai…

Java - 欢迎使用spring中的Base64Utils!

&#x1f335;如果项目技术栈中包含spring&#xff0c;同时又有Base64编码的需求&#xff0c;那么Base64Utils工具类将会是你的最好选择&#xff01;⤵️ 什么是Base64编码&#xff1f;⤵️ 基本转换针对URL的转换 &#xff08;/替换为-_&#xff09; Base64Utils公开的AP…

工业软件上云:有“数据之根”方能向阳生长

有人说&#xff0c;数字化时代&#xff0c;所有的事情都值得用云的方式重新做一遍。 深以为然。作为拥有全球工业门类最为齐全的国家&#xff0c;中国近年来正在从制造大国向制造强国迈进。随着《中国制造2025》国家战略的稳步推进&#xff0c;制造业的数字化转型和智能化升级…

【倒计时2天】CCIG文档图像智能分析与处理论坛开启直播预约,共探智能文档处理前沿技术

文档是人们在日常生活、工作中产生的信息的重要载体&#xff0c;各领域从业者几乎每天都要与金融票据、商业规划、财务报表、会议记录、合同、简历、采购订单等文档“打交道”。让计算机具备阅读、理解和解释这些文档图像的能力&#xff0c;在智能金融、智能办公、电子商务等许…

9:00面试,9:03就出来了 ,问的实在是太变态了···

从外包出来&#xff0c;没想到竟然死在了另一家厂子 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以我也就忍了。没想到12月一纸通知&#xff0c;所有人都不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个…

BRC20懂Web3?探寻宗教式社区建设- Yuga Labs「猿」宇宙案例解析

前言 早前&#xff0c;NFT 领域最强IP 缔造者Yuga Labs官宣&#xff1a;将基于Ordinal 协议在比特币区块链上推出NFT 系列「TwelveFold 」&#xff0c;为比特币NFT 生态添加了催化剂。所以以太坊失宠了吗&#xff1f;事实上&#xff0c;据欧科云链OKLink多链浏览器数据显示&am…

单精度浮点数与十进制数据相互转换

一、float基础&#xff1a; Float类型占4个字节,也就是32bit,其中最高位是符号位,2~9位是指数位,后边的23bit是数值位.如下所示 大部分数据的二进制形式都可以用科学计数法表示,即1.m*2^n这种形式,只要知道m和n,就能确定一个数值 二、小数位如何转变为二进制&#xff1a; 下面…

工业主板定制选型的要点都有哪些呢?

工业主板是工控机的核心部件。工控机通过工业主板将CPU等各种器件和外部设备有机地结合起来&#xff0c;形成一套完整的系统&#xff0c;因此工控机的整体运行速度和稳定性在相当程度上取决于工业主板的性能。工业主板应用范围广泛&#xff0c;使用环境复杂&#xff0c;因此用户…

键树_Trie树_介绍和C语言实现_20230511

键树_Trie树形式_树介绍及C语言实现 前言 上一篇提到键树有两种不同的表示方法&#xff0c;它们分别是双链树和Trie树&#xff0c;在上文中对双链树的数据结构以及在键树上的C语言实现做了详细的分析与讨论。如若键树中的结点的度较大&#xff0c;则采用Trie树结构较双链结构…

【软件工程】期末复习总结(通俗易懂,学不会来打我)

【软件工程】期末复习总结&#xff08;通俗易懂&#xff0c;学不会来打我&#xff09; 第一章 1.1 软件工程的发展历程 1.1.1 软件危机&#xff08;日子没法过了&#xff09; 软件危机&#xff08;Software Crisis&#xff09;是指在计算机软件开发、运行、维护和管理过程中…

126-Linux_git安装及使用

文章目录 一.git基本概念1.什么是git2.git的特点3.git工作流程4.文件的四种状态 二.git的安装1.在ubuntu上测试有没有安装2.使用命令 sudo apt install git 进行安装3.安装后查看版本,检查是否安装成功 三.git的使用1.git常用命令(1)创建一个目录(2)使用git init 命令将其变为一…

Netty编程入门超级详细,有这篇就足够了

目录 前言一、简介二、为什么使用Netty2.1 NIO的缺点2.2 Netty的优点 三、架构图四、永远的Hello Word4.1引入Maven依赖4.2 创建服务端启动类4.3 创建服务端处理器4.4 创建客户端启动类4.5 创建客户端处理器4.6 测试 五、Netty的特性与重要组件5.1 taskQueue任务队列5.2 schedu…

MyBatis的CRUD

0-基础知识 id&#xff1a;唯一标识 type&#xff1a;映射的类型&#xff0c;支持别名 java里的命名规则是驼峰&#xff0c;而sql里面是下划线&#xff0c;如何对数据库表的字段起别名&#xff1f; 数据库表的字段名称和实体类的属性名称 不一样&#xff0c;则不能自动封装数据…

算法套路十五——最长公共子序列LCS

算法套路十五——最长公共子序列LCS 算法示例&#xff1a;LeetCode1143. 最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&am…

Windows系统配置Anaconda虚拟环境,并安装Numpy、Scipy和Matplotlib等模块方法

有些项目不是必须在Ubuntu系统下进行的&#xff0c;对大部分人来说更熟悉Window系统&#xff0c;且查阅电脑中相关文件和使用微信更方便&#xff0c;因此记录一下Windows系统配置Anaconda虚拟环境步骤和安装Numpy、Scipy及Matplotlib等模块方法。 一、Anaconda安装 Anaconda可以…

异步电机速度估计-模型参考自适应MRAS法(补充)

导读&#xff1a;前期文章已经介绍过模型参考自适应MRAS进行速度估计的方法&#xff0c;本期文章主要是对MRAS实现的细节做一下补充。 若需要文章的仿真模型&#xff0c;关注微信公众号&#xff1a;浅谈电机控制&#xff0c;获取。 一、MRAS知识点回顾 1.1 基本原理 MRAS 模…

Calico的BGP打通Kubernetes网络和局域网

1、项目背景 随着云原生技术的不断发展&#xff0c;容器化应用已成为企业构建云原生架构的重要方式。而随着集群规模不断扩大&#xff0c;跨主机通信的需求也越来越重要。在 Kubernetes 集群中&#xff0c;Pod 是最小的调度和管理单位&#xff0c;而网络也是 Kubernetes 中最重…

双链表——“数据结构与算法”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰又回来了&#xff0c;到了好久没有更新的数据结构与算法专栏&#xff0c;最近确实发现自己有很多不足&#xff0c;需要学习的内容也有很多&#xff0c;所以之后更新文章可能不会像之前那种一天一篇或者一天两篇啦&…