01:(寄存器开发)点亮一个LED灯

news2024/10/5 0:04:09

寄存器开发

  • 1、单片机的简介
    • 1.1、什么是单片机
    • 1.2、F1系列内核和芯片的系统架构
    • 1.3、存储器映像
    • 1.4、什么是寄存器
  • 2、寄存器开发模板工程
  • 3、使用寄存器点亮一个LED
  • 4、代码改进1
  • 5、代码改进2

本教程使用的是STM32F103C8T6最小系统板,教程来源B站up“嵌入式那些事”。

1、单片机的简介

1.1、什么是单片机

单片机(Single-Chip Microcomputer)单片机微型计算机,是一种集成电路芯片,把具有数据处理能力的中央处理器CPU、随机存储器RAM、闪存flash、多种l/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。
在这里插入图片描述
STM32F103C8T6又被称为32位单片机,那么这个32代表着什么意思喃?
    ——其中32代表着地址总线有32位,即最小存储单元(1字节B)的地址编号是32位二进制构成的。所以stm32的存储空间位2^32B = 4GB(地址:0x0000 0000~0xFFFF FFFF)

①FLASH:FLAS又称闪存,它是ROM的一部分(只读存储器,现在往里面写数据),用于存放向单片机烧录的代码。而ROM的另外一部分为系统存储器和字节选项。一般情况下单片机读取FLASH里面的数据要通过FLASH接口,所以读取FLASH的数据比较慢。
在这里插入图片描述

②RAM:临时存储器,掉电里面的数据会丢失,读写速度比ROM快。主要存储代码运行时的临时变量,内核和片上外设寄存器的配置参数。
在这里插入图片描述

③总线:就像是桥梁,用于CPU与外设之间的连接,DMA与外设之间的连接等,进行数据从传输
④时钟:由内部晶振或者外部晶振产生的时钟频率脉冲信号,时钟频率决定了CPU处理0/1的快慢程度(单片机执行代码的快慢程度),除了CPU需要时钟频率脉冲信号,片上外设也需要时钟信号时钟频率脉冲信号,其作用是启动边沿触发器。而像定时器这样的片上外设需要时钟频率脉冲信号来进行计数。
⑤外设:单片机除了CPU还有很多的片上外设,比如GPIO,定时器TIM等,只有CPU和这些片上外设共同作用才能完成我们想要的功能

1.2、F1系列内核和芯片的系统架构

在这里插入图片描述
如上图为F1系列单片机内核和芯片的系统架构,其中分为4个主动单元和4个被动单元。

  1. 四个主动单元
        ——能够主动的发起请求,主动的访问数据。
    ①Dcode总线,②System总线,③DMA1,④DMA2(stm32f103c8t6没有DMA2)

  2. 四个被动单元
        ——不能够主动的发起请求,被动的访问数据。
    ①FLASH,②SRAM,③FSMC,④桥接1和桥接2上面连接的片上外设

1.3、存储器映像

由前面得知stm32单片机里面有4GB的存储空间,人们将这4GB的空间进行了地址编号就叫存储器映像。划分为8个区域,不同区域代表存储不同功能的信息。如下图所示:

在这里插入图片描述
由上图存储器映像得出如下结论:

在这里插入图片描述

1.4、什么是寄存器

寄存器的本质就是内存,若通过给某个内存a里面写入数据来控制外设A,那么这个内存a就是外设A的控制寄存器。若某个内存b里面的数据来表示此时外设B的状态,那么这个内存b就是外设B的状态寄存器。我们知晓stm32里面由很多的外设,那么这些外设的寄存器怎么寻找喃?答案:通过地址,我们由前面得知外设寄存器的起始地址为0x4000 0000,即之后的地址就代表着寄存器地址,不同的寄存器的地址不同。 如下图为部分外设的寄存器地址
在这里插入图片描述而寄存器开发就是找到外设的寄存器,通过给这些寄存器里面写入数据来进行对外设配置,进而完成我们想要实现的功能,一般情况下单片机使用寄存器开发的执行效率比使用库函数开发的执行效率更高,但是人们的寄存器代码开发效率会比使用库函数的代码开发效率低。

2、寄存器开发模板工程

开发环境安装和创建工程请参考stm32标准库入门教程的第1章和第2章。寄存器开发必要文件如下图所示:
在这里插入图片描述
模板资料链接: link

3、使用寄存器点亮一个LED

实物按照如下图所示连接好。创建好工程模板后,我们在模板中的main()函数里面按照如下步骤编写代码:
在这里插入图片描述

1、开启时钟
如图LED引脚连接着A0,则开启片上外设GPIOA的时钟——RCC寄存器(与外设的时钟有关)

① 打开参考手册找到存储器映像如下图所示:

在这里插入图片描述
如图所示:地址0x4002 1000——0x4002 13FF这段的内存是与RCC有关的寄存器。

②打开参考手册找到RCC寄存器描述如下图所示:

在这里插入图片描述如图RCC的第一个寄存器为RCC_CR(时钟控制寄存器),它的地址偏移为0x00,则这个寄存器的地址就是0x4002 1000。RCC的第二个寄存器为RCC_CFGR(时钟配置寄存器),它的地址偏移为0x04,那为什么第二个寄存器的地址偏移是0x04喃?
在这里插入图片描述
如图:寄存器的地址其实是所包含4个字节中的首字节的地址,一个寄存器中包含4个字节,所以每增加一个寄存器,地址就偏移0x04。

③GPIOA挂载在APPB2时钟总线上面,我们找到APB2使能寄存器如下图所示:

在这里插入图片描述
如图RCC_APB2ENR寄存器的地址偏移为0x18,而与GPIOA时钟的位在32位中的第3位,只需要在这一位写入1,即开启了片上外设GPIOA的时钟。那如何用代码表示喃?

*(uint32_t *)(0x40021000 + 0x18) = 0x04;//指针代表地址
将十六进制的数值通过类型转换为指针类型,然后通过*取内容给其赋值。

2、配置IO口的输出模式

①打开参考手册找到存储器映像如下图所示:
在这里插入图片描述
如图:地址0x4001 0800——0x4001 0BFF这段的内存是与GPIOA引脚有关的寄存器。

②打开参考手册找到GPIO寄存器描述如下图所示:
在这里插入图片描述
如上图:配置低引脚IO(IO0~IO7)的寄存器的地址偏移为0x00,那么配置高引脚IO(IO8 ~ IO15)的寄存器的地址偏移为0x04。而我们的LED负极连接着A0,所以我们需要对低寄存器进行配置。那如何用代码配置喃?

*(uint32_t *)(0x40010800 + 0x00) = 0x03;//MODE0 = 11,CNF0 = 00
MODE0 = 11,表示配置为输出模式,且输出速度为50MHz
CNF0 = 00,表示配置为通用推挽输出模式(能输出低电平0和高电平1)
地址为0x4001 0800如何进行地址偏移,若需要配置GPIOB0则地址为0x4001 0C00进行地址偏移

3、IO输出逻辑电平
①打开参考手册找到GPIO寄存器描述如下图所示:
在这里插入图片描述
如图:给ODR0写入1则对应的IO0引脚则输出高电平1,若写入0,则引脚输出低电平0

由实物连接图可知,LED的正极连接着VCC,负极连接着IO引脚,所以引脚输出低电平,LED点亮,输出高电平,LED熄灭。代码如下:

*(uint32_t *)(0x40010800 + 0x0C) = 0xFFFE;//1111 1111 1111 1110
IO1引脚~IO15引脚输出高电平,IO0引脚输出低电平

综上:使用寄存器编程点亮一个LED灯完整的代码如下:

int main(void)
{
	//1、开启对应的GPIOA的时钟
	*(uint32_t *)(0x40021000 + 0x18) = 0x04;
	
	//2、给IO口设置工作模式:PA0配置为通用推挽输出模式
	*(uint32_t *)(0x40010800 + 0x00) = 0x03;
	
	//3、对应的IO口设置值:1/0,PA0输出0,点亮LED
	*(uint32_t *)(0x40010800 + 0x0C) = 0xFFFE;
	
	while(1)
	{

	}
}

在这里插入图片描述

4、代码改进1

从上面的代码开发步骤中,我们每次需要配置某个片上外设的寄存器时,我们都需要打开存储器映像来找到这个片上外设的寄存器初始地址(基地址),然后在通过寄存器的地址偏移,然后通过基地址+偏移地址最终定位到我们需要的那个寄存器的地址。使用这种开发方式大大降低了我们开发的效率,而st公司也想到了这一点。

所示它在stm32f10x.h这个文件里面已经将每个片上外设的寄存器基地址用#define定义好了,如下图所示:RCC的基地址就用RCC这个字符串来表示了(即RCC = (uint32_t *)(0x40021000))

在这里插入图片描述我们以外设RCC为例,查看它的代码是如何定义的,如下图所示:
在这里插入图片描述
如图:RCC的基地址 =RCC_BASE = 0x4000 0000 + 0x20000 +0x1000 = 0x4002 1000,正好和存储器映像里面的一样。在这里插入图片描述
而RCC_TypeDef *(RCC_BASE)将基地址转换为结构体的指针类型,我们转到这个结构体的定义看看,如下图所示:
在这里插入图片描述
如上图所示:这个结构体里面的变量全是RCC的寄存器,且都是按照参考手册的顺序一比一排列的,且都是定义的和参考手册一样的32位(方便地址偏移)。则由C语言结构体的如下的基础知识
在这里插入图片描述P->a :结构体的基地址偏移了1个字节,P->b:结构体的基地址偏移了5个字节。

既然RCC就是结构体的地址,那么通过地址表示结构体的变量如下:

RCC->CR:RCC寄存器的基地址偏移了0位
RCC->CFGR:RCC寄存器的绝对值偏移了32位
RCC->CIR:RCC寄存器的绝对值偏移了64位…

综上:通过上面的基础知识,我们进行如下的代码改进

int main(void)
{

	//1、开启对应的GPIOA的时钟
	//*(uint32_t *)(0x40021000 + 0x18) = 0x04;
	
	//2、给IO口设置工作模式:PA0配置为通用推挽输出模式
	//*(uint32_t *)(0x40010800 + 0x00) = 0x03;
	
	//3、对应的IO口设置值:1/0,PA0输出0,点亮LED
	//*(uint32_t *)(0x40010800 + 0x0C) = 0xFFFE;
	
/*
	改进1:地址换为st公司定义好的宏
*/
	
	//1、开启对应的GPIOA的时钟
	RCC->APB2ENR = 0x04;
	
	//2、给IO口设置工作模式:PA0配置为通用推挽输出模式
	GPIOA->CRL = 0x03;
	
	//3、对应的IO口设置值:1/0,PA0输出0,点亮LED
	GPIOA->ODR = 0xFFFE;//1111 1111 1111 1110

	while(1)
	{

	}
}

5、代码改进2

1、为了提高程序员开发的效率,st公司早已经将需要写入寄存器里面的值使用宏定义好了
例如:开启对应的GPIOA的时钟,我们需要给RCC_APB2ENR寄存器里面写入数值0x04,而我们则可以通过RCC_APB2ENR_IOPAEN这段字符串来代替0x40这个数值。在这里插入图片描述

RCC->APB2ENR = 0x04;//开启对应的GPIOA的时钟
改进为:
RCC->APB2ENR = RCC_APB2ENR_IOPAEN; //开启对应的GPIOA的时钟

2、前面给寄存器写入数据的时候,都是通过某某寄存器 = 数值。这样写入数据必然会影响寄存器中的其他位置的数据,例如GPIOA->ODR = 0xFFFE;//1111 1111 1111 1110这段代码,我只需要个IO0口输出低电平0,虽然我们成功写入了0,但是其他的IO引脚却因为我们写入的数据写入了高电平1。为了解决这种现象,我们可以通过位运算<<, >>, |, &, ~来解决我们的问题

RCC->APB2ENR = RCC_APB2ENR_IOPAEN; //开启对应的GPIOA的时钟
改进为:
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //开启对应的GPIOA的时钟

综上:改进的代码如下:

int main(void)
{
/*
	改进2:地址换为st公司定义好的宏,且使用位运算
*/
	
	//1、开启对应的GPIOA的时钟
	RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
	
	//2、给IO口设置工作模式:PA0配置为通用推挽输出模式,即MODE0 = 11,CNF0 = 00
	GPIOA->CRL |= GPIO_CRL_MODE0;
	GPIOA->CRL &= ~GPIO_CRL_CNF0;
	
	//3、对应的IO口设置值:1/0,PA0输出0,点亮LED
	GPIOA->ODR &= ~GPIO_ODR_ODR0;//PA0引脚输出0

	while(1)
	{

	}
}

改进代码总结:
①寄存器使用结构体指针来进行表示
②写入寄存器的值使用通过定义好的宏
③为了不影响寄存器的其他位的值最好使用位运算
④位运算时:若需要写入寄存器的值为1则用 |=,若需要写入寄存器的值为0则用 &=(其中数值则需要取反)

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

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

相关文章

前缀和(6)_和可被k整除的子数组_蓝桥杯

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 前缀和(6)_和可被k整除的子数组 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 …

kubeadm部署k8s

1.1 安装Docker [rootk8s-all ~]# wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.huaweicloud.com/docker-ce/linux/centos/docker-ce.repo [rootk8s-all ~]# sed -i sdownload.docker.commirrors.huaweicloud.com/docker-ce /etc/yum.repos.d/docker-ce.repo [ro…

基于Keras的U-Net模型在图像分割与计数中的应用

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色&a…

Yocto - 使用Yocto开发嵌入式Linux系统_07 构建使用的临时文件夹

Detailing the Temporary Build Directory 在本章中&#xff0c;我们将尝试了解映像生成后临时构建目录的内容&#xff0c;并了解 BitBake 如何在烘焙过程中使用它。此外&#xff0c;我们还将了解这些目录中的某些内容如何在出现问题时作为有价值的信息来源来帮助我们。 In thi…

前缀和——从LeetCode题海中总结常见套路

目录 前缀和定义 截断前缀和DP&#xff1a;LeetCode53.最大子序和 经典左右指针&#xff1a;LeetCode209.长度最小的子数组 暴力求解&#xff1a;超时 优雅的双指针写法一&#xff1a; 优雅的双指针写法二&#xff1a; LeetCode.1588.所有奇数长度子数组的和 手速题&am…

springboot系列--web相关知识探索三

一、前言 web相关知识探索二中研究了请求是如何映射到具体接口&#xff08;方法&#xff09;中的&#xff0c;本次文章主要研究请求中所带的参数是如何映射到接口参数中的&#xff0c;也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、…

[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解

[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解 前言: 古人云: 得卧龙者&#xff0c;得天下。 然在当今大语言模型流行的时代&#xff0c;同样有一句普世之言: 会微调技术者&#xff0c;得私域大模型部署之道&#xff01; 在众多微调技术中&#xff0c;LoRA (…

单细胞scDist细胞扰动差异分析学习

scDist通过分析不同状态下细胞的距离来找到差异最大的细胞亚群(见下图的A)&#xff0c;然后再分析每一个细胞亚群的PCA通过线性的混合模型并结合最终的系数去预估不同干预方式下细胞群之间的距离。 Augur是通过对每一个细胞进行AUC评分并排序最终找到扰动最佳的细胞群&#xf…

等额本金和等额本息是什么意思?

等额本金和等额本息是两种常见的贷款还款方式&#xff0c;它们各自有着不同的特点和适用场景。下面我将用通俗易懂的语言来解释这两种还款方式&#xff1a; 等额本金 定义&#xff1a;等额本金指的是在贷款期限内&#xff0c;每月偿还相同数额的本金&#xff0c;而利息则随着剩…

FPGA远程烧录bit流

FPGA远程烧录bit流 Vivado支持远程编译并下载bit流到本地xilinx开发板。具体操作就是在连接JTAG的远程电脑上安装hw_server.exe。比如硬件板在实验室或者是其他地方&#xff0c;开发代码与工程在本地计算机&#xff0c;如何将bit流烧录到实验室或者远程开发板&#xff1f; vi…

Socket套接字(客户端,服务端)和IO多路复用

Socket套接字&#xff08;客户端&#xff0c;服务端&#xff09; 目录 socket是什么一、在客户端1. 创建套接字2. 设置服务器地址3. 连接到服务器4. 发送数据5. 接收数据6. 关闭连接 二、内核态与用户态切换三、系统调用与上下文切换的关系四、在服务端1. 创建 Socket (用户态…

【Linux】进程地址空间(初步了解)

文章目录 1. 奇怪的现象2. 虚拟地址空间3. 关于页表4. 为什么要有虚拟地址 1. 奇怪的现象 我们先看一个现象&#xff1a; 为什么父子进程从“同一块地址中”读取到的值不一样呢&#xff1f; 因为这个地址不是物理内存的地址 &#xff0c;如果是物理内存的地址是绝对不可能出…

C++【类和对象】(友元、内部类与匿名对象)

文章目录 1.友元2.内部类3.匿名对象结语 1.友元 友元提供了⼀种突破类访问限定符封装的方式&#xff0c;友元分为&#xff1a;友元函数和友元类&#xff0c;在函数声明或者类声明的前面加friend&#xff0c;并且把友元声明放到⼀个类的里面。外部友元函数可访问类的私有和保护…

【安全科普】从“微信文件助手隐私泄漏”看社交平台网络安全

随着互联网技术的飞速发展&#xff0c;社交平台已经成为了人们日常生活中不可或缺的一部分。人们通过社交平台与亲朋好友保持联系&#xff0c;分享生活点滴&#xff0c;获取资讯信息。然而&#xff0c;与此同时&#xff0c;社交平台上的网络安全风险也日益凸显。近期&#xff0…

简单的a+b-C语言

1.问题&#xff1a; 输入两个整数a和b&#xff0c;计算ab的和。 2.解答&#xff1a; scanf()函数是通用终端格式化输入函数&#xff0c;它从标准输入设备(键盘) 读取输入的信息。可以读入任何固有类型的数据并自动把数值变换成适当的机内格式。 scanf()函数返回值分为3种&…

分布式学习02-CAP理论

文章目录 CAP三指标一致性可用性分区容错性 CAP不可能三角P存在的必要性CP理论AP理论 CAP理论对分布式系统的特性做了高度抽象&#xff0c;将其抽象为一致性、可用性、分区容错性。 并对特征间的冲突做了总结&#xff1a;CAP不可能三角。 CAP三指标 一致性&#xff08;Consis…

【NIO基础】基于 NIO 中的组件实现对文件的操作(文件编程),FileChannel 详解

目录 1、FileChannel (1&#xff09;获取 FileChannel (2&#xff09;读取文件 (3&#xff09;写入文件 (4&#xff09;关闭通道 (5&#xff09;当前位置与文件大小 (6&#xff09;强制写入磁盘 2、两个 FileChannel 之间的数据传输 (1&#xff09;使用 transferTo()…

HTML的修饰(CSS) -- 第三课

文章目录 前言一、CSS是什么&#xff1f;二、使用方式1. 基本语法2. 引入方式1.行内式2.内嵌式3. 链入式 3. 选择器1. 标签选择器2.类选择器3. id选择器4. 通配符选择器 4. css属性1. 文本样式属性2. 文本外观属性 5. 元素类型及其转换1. 元素的类型2. 元素的转换 6.css高级特性…

isinstance()学习

aa {} if isinstance(aa,dict):print("是")aa 2 if isinstance(aa,dict):print("是")aa 2 if isinstance(aa,int):print("是")aa [] if isinstance(aa,list):print("list")aa [1,2,3] if isinstance(aa,list):print("list"…

模拟算法(4)_外观数列

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 模拟算法(4)_外观数列 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 题目链…