STM32 基础知识入门 (C语言基础巩固)

news2024/11/26 13:50:22

1、在不改变其他位的值的状况下,对某几个位进行设值

这个场景在单片机开发中经常使用,方法就是先对需要设置的位用&操作符进行清零操作, 然后用|操作符设值。

比如我要改变 GPIOA 的 CRL 寄存器 bit6(第 6 位)的值为 1,可以先对寄 存器的值进行&清零操作:

GPIOA->CRL &= 0XFFFFFFBF; /* 将第 bit6 清 0 */ 

 然后再与需要设置的值进行|或运算:

GPIOA->CRL |= 0X00000040; /* 设置 bit6 的值为 1,不改变其他位的值 */ 

 2、移位操作提高代码的可读性

 移位操作在单片机开发中非常重要,下面是 delay_init 函数的一行代码:

SysTick->CTRL |= 1 << 1;

 这个操作就是将 CTRL 寄存器的第 1 位(从 0 开始算起)设置为 1,为什么要通过左移而 不是直接设置一个固定的值呢?其实这是为了提高代码的可读性以及可重用性。这行代码可以 很直观明了的知道,是将第 1 位设置为 1。如果写成:

SysTick->CTRL |= 0X0002;

 这个虽然也能实现同样的效果,但是可读性稍差,而且修改也比较麻烦。

3、~按位取反操作使用技巧  

按位取反在设置寄存器的时候经常被使用,常用于清除某一个 /某几个位。下面是 delay_us

函数的一行代码:

SysTick->CTRL &= ~(1 << 0) ; /* 关闭 SYSTICK */ 

该代码可以解读为 仅设置 CTRL 寄存器的第 0 位(最低位)为 0,其他位的值保持不变。 同样我们也不使用按位取反,将代码写成:

SysTick->CTRL &= 0XFFFFFFFE; /* 关闭 SYSTICK */ 

 可见前者的可读性,及可维护性都要比后者好很多。

4、^按位异或操作使用技巧 

该功能非常适合用于控制某个位翻转,常见的应用场景就是控制 LED 闪烁,如:

GPIOB->ODR ^= 1 << 5;

执行一次该代码,就会使 PB5 的输出状态翻转一次,如果我们的 LED 接在 PB5 上,就可 以看到 LED 闪烁了。 

5、ifdef 条件编译

单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而 当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:

#ifdef 标识符 

    程序段 1 

#else 

    程序段 2 

#endif

它的作用是:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译, 否则编译程序段 2。 其中#else 部分也可以没有,即:

#ifdef 

     程序段 1 

#endif 

 条件编译在 MDK 里面是用得很多,在 stm32f10x.h 这个头文件中经常会看到这样的语句:

#if !defined (STM32F1)     //在这里也可以换成是#ifndef   
#define STM32F1 
#endif 

 如果没有定义 STM32F1 这个宏,则定义 STM32F1 宏。条件编译也是 c 语言的基础知识, 这里也就点到为止吧。

6、extern 外部申明

C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编 译器遇到此变量和函数时在其他模块中寻找其定义。这里面要注意,对于 extern 申明变量可以多 次,但定义只有一次。在我们的代码中你会看到这样的语句:  

extern uint16_t g_usart_rx_sta; 

 这个语句是申明 g_usart_rx_sta 变量在其他文件中已经定义了,在这里要使用到。所以,你 肯定可以找到在某个地方有变量定义的语句:

uint16_t g_usart_rx_sta; 

 在这里大家可以觉得有点绕,但是其思想就是:
例如我在filec文件当中定义了uint16_t g_usart_rx_sta;

然后这个时候我要在filec文件当中使用这个变量,就是说我想直接从filec文件当中将uint16_t g_usart_rx_sta取过来,那么我可以在filec文件当中在变量前面加上

extern uint16_t g_usart_rx_sta; 

7、typedef 类型别名

typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。typedef

在 MDK 用得最多的就是定义结构体的类型别名和枚举类型了。

struct _GPIO 

{ 
     __IO uint32_t CRL; 
     __IO uint32_t CRH; 
     … 
    
}; 

 定义了一个结构体 GPIO,这样我们定义结构体变量的方式为:

struct _GPIO gpiox; /* 定义结构体变量 gpiox */ 

 但是这样很繁琐,MDK 中有很多这样的结构体变量需要定义。这里我们可以为结体定义一 个别名 GPIO_TypeDef,这样我们就可以在其他地方通过别名 GPIO_TypeDef 来定义结构体变 量了,方法如下:

typedef struct 

{ 
     __IO uint32_t CRL; 
     __IO uint32_t CRH; 
     … 

} GPIO_TypeDef; 

Typedef 为结构体定义一个别名 GPIO_TypeDef,这样我们可以通过 GPIO_TypeDef 来定义 结构体变量:

GPIO_TypeDef gpiox;

这里的 GPIO_TypeDef 就跟 struct _GPIO 是等同的作用了,但是 GPIO_TypeDef 使用起来 方便很多。

8、结构体

经常很多用户提到,他们对结构体使用不是很熟悉,但是 MDK 中太多地方使用结构体以及 结构体指针,这让他们一下子摸不着头脑,学习 STM32 的积极性大大降低,其实结构体并不是 那么复杂,这里我们稍微提一下结构体的一些知识,还有一些知识我们会在下面的“寄存器映 射”中讲到一些。

声明结构体类型:

struct 结构体名 

{ 
     成员列表; 

}变量名列表;

例如:

struct U_TYPE 

{ 
 int BaudRate 
 int WordLength; 

}usart1, usart2; 

在结构体申明的时候可以定义变量,也可以申明之后定义,方法是:

struct 结构体名字 结构体变量列表 ;

例如:

struct U_TYPE usart1,usart2; 

结构体成员变量的引用方法是:

结构体变量名字.成员名 

比如要引用 usart1 的成员 BaudRate,方法是:usart1.BaudRate;结构体指针变量定义也是 一样的,跟其他变量没有啥区别。

例如:

struct U_TYPE *usart3; /* 定义结构体指针变量 usart3 */ 

结构体指针成员变量引用方法是通过“->”符号实现,比如要访问 usart3 结构体指针指向 的结构体的成员变量 BaudRate,方法是:

usart3->BaudRate;

上面讲解了结构体和结构体指针的一些知识,其他的什么初始化这里就不多讲解了。 讲到 这里,有人会问,结构体到底有什么作用呢?为什么要使用结构体呢?下面我们将简单的通过 一个实例回答一下这个问题。

在我们单片机程序开发过程中,经常会遇到要初始化一个外设比如串口,它的初始化状态 是由几个属性来决定的,比如串口号,波特率,极性,以及模式。对于这种情况,在我们没有 学习结构体的时候,我们一般的方法是:

void usart_init(uint8_t usartx, uiut32_t BaudRate, uint32_t Parity, 
uint32_t Mode); 

这种方式是有效的同时在一定场合是可取的。但是试想,如果有一天,我们希望往这个函 数里面再传入一个/几个参数,那么势必我们需要修改这个函数的定义,重新加入新的入口参数, 随着开发不断的增多,那么是不是我们就要不断的修改函数的定义呢?这是不是给我们开发带 来很多的麻烦?那又怎样解决这种情况呢?

我们使用结构体参数,就可以在不改变入口参数的情况下,只需要改变结构体的成员变量, 就可以达到改变入口参数的目的。

结构体就是将多个变量组合为一个有机的整体,上面的函数,usartx,BaudRate,Parity, Mode

等这些参数,他们对于串口而言,是一个有机整体,都是来设置串口参数的,所以我们可以将 他们通过定义一个结构体来组合在一个。MDK 中是这样定义的:

typedef struct 

{ 
     uint32_t BaudRate; 
     uint32_t WordLength; 
     uint32_t StopBits; 
     uint32_t Parity; 
     uint32_t Mode; 
     uint32_t HwFlowCtl; 
     uint32_t OverSampling; 

} UART_InitTypeDef; 

 这样,我们在初始化串口的时候入口参数就可以是 USART_InitTypeDef 类型的变量或者指 针变量了,于是我们可以改为:

void usart_init(UART_InitTypeDef *huart); 

这样,任何时候,我们只需要修改结构体成员变量,往结构体中间加入新的成员变量,而 不需要修改函数定义就可以达到修改入口参数同样的目的了。这样的好处是不用修改任何函数 定义就可以达到增加变量的目的。 理解了结构体在这个例子中间的作用吗?在以后的开发过程中,如果你的变量定义过多, 如果某几个变量是用来描述某一个对象,你可以考虑将这些变量定义在结构体中,这样也许可 以提高你的代码的可读性。

使用结构体组合参数,可以提高代码的可读性,不会觉得变量定义混乱。当然结构体的作 用就远远不止这个了,同时,MDK 中用结构体来定义外设也不仅仅只是这个作用,这里我们只 是举一个例子,通过最常用的场景,让大家理解结构体的一个作用而已。后面一节我们还会讲 解结构体的一些其他知识。

9、指针

指针是一个值指向地址的变量(或常量),其本质是指向一个地址,从而可以访问一片内 存区域。在编写 STM32 代码的时候,或多或少都要用到指针,它可以使不同代码共享同一片内 存数据,也可以用作复杂的链接性的数据结构的构建,比如链表,链式二叉树等,而且,有些 地方必须使用指针才能实现,比如内存管理等。

申明指针我们一般以 p 开头,如:

char * p_str = “This is a test!”;

这样,我们就申明了一个 p_str 的指针,它指向 This is a test!这个字符串的首地址。我们 编写如下代码:

int main(void) 

{ 
     uint8_t temp = 0X88; /* 定义变量 temp */ 
     uint8_t *p_num = &temp; /* 定义指针 p_num,指向 temp 的地址 */
     HAL_Init(); /* 初始化 HAL 库 */ 
     sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟,72M */ 

     delay_init(72); /* 延时初始化 */ 
     usart_init(115200); /* 初始化串口 */ 
     printf("temp:0X%X\r\n", temp); /* 打印 temp 的值 */ 
     printf("*p_num: 0X %X\r\n", *p_num); /* 打印*p_num 的值 */ 
     printf("p_num: 0X %X\r\n", (uint32_t)p_num); /* 打印 p_num 的值 */ 
     printf("&p_num: 0X %X\r\n", (uint32_t)&p_num); /* 打印&p_num 的值 */ 
     while (1); 

} 

此代码的输出结果为:

p_num:是uint8_t类型指针,指向temp变量的地址,其值等于temp变量的地址。

*p_num:取p_num指向的地址所存储的值,即temp的值。

&p_num:取p_num指针的地址,即指针自身的地址。

以上,就是指针的简单使用和基本概念说明,指针的详细知识和使用范例大家可以百度学习,网上有非常多的资料可供参考。指针是C语言的精髓,在后续的代码中我们将会大量用到各种指针,大家务必好好学习和了解指针的使用。

 

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

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

相关文章

MiNiGPT4安装记录

装conda wget https://repo.anaconda.com/archive/Anaconda3-5.3.0-Linux-x86_64.sh chmod x Anaconda3-5.3.0-Linux-x86_64.sh ./Anaconda3-5.3.0-Linux-x86_64.sh export PATH~/anaconda3/bin:$PATH # 或者写到环境保护变量 # 不会弄看这吧 https://blog.csdn.net/wyf2017/a…

fork()创建进程原理

目录 一、写时复制技术写时复制的优点&#xff1a;vfork()和fork() 二、fork()原理初步再理解下页表与多进程在内存中的图像创建进程和创建线程的区别 三、fork()的具体过程 一、写时复制技术 fork()生成子进程时&#xff0c;只是把虚拟地址拷贝给子进程&#xff0c;也就是父进…

( 字符串) 205. 同构字符串 ——【Leetcode每日一题】

❓205. 同构字符串 难度&#xff1a;简单 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符&#xff0c;同时不改变字符的顺序。不同…

网络基础:socket套接字

文章目录 1. 前导知识1.1 源MAC地址和目的MAC地址1.2 源IP地址和目的IP地址1.3 MAC地址和IP地址的配合1.4 源端口号和目的端口号1.5 Socket1.6 UCP协议和TCP协议1.7 网络字节序高低位高低地址大端和小端网络字节序常用转换函数 2. socket 网络编程2.1 socket 常见接口创建套接字…

ChatGPT最好用的连接-自动写文案-代码算法最佳选择

ChatGPT最好用的连接-自动写文案-代码算法最佳选择 最近测试了很多国内分享的ChatGPT&#xff0c;很多都是限制最多写200文字&#xff0c;超过200个文字就不显示了。或者有的写出的文章逻辑性不对&#xff0c;写的算法不能正常运行。 经过多天的搜索测试&#xff0c;最终确定…

某电商客户数据价值分析项目

目录 一、项目意义 二、项目流程 三、项目内容 1、导入数据 2、数据预处理 3、单变量分析 4、聚类分析—Kmeans算法 一、项目意义 客户价值分析就是一个客户分群问题&#xff0c;以客户为中心&#xff0c;从客户需求出发&#xff0c;搞清楚客户需要什么&#xff0c;他们…

Linux进程通信:进程组 会话

1. 进程组 &#xff08;1&#xff09;概念&#xff1a;一个或多个进程的集合&#xff0c;也称为“作业”。 &#xff08;2&#xff09;父进程创建子进程时&#xff0c;默认属于同一个进程组。进程组ID为组长进程ID。 &#xff08;3&#xff09;进程组中只要有一个进程存在&a…

unity中的Line Renderer

介绍 unity中的Line Renderer 方法 首先&#xff0c;Line Renderer 是 Unity 引擎中的一个组件&#xff0c;它可以生成直线、曲线等形状&#xff0c;并且在场景中呈现。通常情况下&#xff0c;Line Renderer 被用来实现轨迹、路径、线框渲染以及射线可视化等功能。 在使用 …

imx6ull开发板环境配置 - libusb、libudev、eudev交叉编译

目录 零、前言 一、libusb交叉编译 1.0 前言 1.1 交叉编译 二、usbutils交叉编译 2.0 前言 2.1 交叉编译 三、libudev交叉编译 3.0 前言 3.1 交叉编译 3.2 错误处理-没找到usbutils 3.3 错误处理-没找到pci.ids &#xff08;pci.ids not found&#xff09; 3.3.0 前…

【数据库】索引与事务

目录 1、索引 1.1、概念 1.2、索引的作用 1.3、 索引的缺点 1.4、数据库中实现索引的数据结构 1.4.1、B树/B-树 1.4.2、B树 1.4.3、回表 1.5、使用场景 1.6、索引的使用 1.6.1、查看索引 1.6.2、创建索引 1.6.3、 删除索引 1.7、索引的分类 2、事务 2.1、为什…

Arduino ESP8266基于ESPAsyncWebServer 网页GPIO控制

Arduino ESP8266基于ESPAsyncWebServer 网页GPIO控制 📍相关篇《Arduino ESP8266利用AJAX局部动态更新网页内容》 📺控制页面演示: 🌿在手机上可以通过接入ESP8266的WIFI,通过浏览器方位192.168.4.1进行网页页面操控引脚以及查看esp8266信息。 ✨本项目是基于github上…

[oeasy]python0143_主控程序_main

主控程序 回忆上次内容 上次把 apple.py 拆分成了 输入主函数 引用模块中变量的时候 要带上包(module)名 get_fruits.aget_fruits.b 最终 拆分代码 成功&#xff01; 可以将程序 再拆分成 输入输出 然后 再由主函数调用吗&#xff1f;&#x1f914; 建立主控 新建一个 ma…

【Java笔试强训 10】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;井字棋 …

大数据技术之大数据概论

第1章 大数据概念 大数据(Big Data): 指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合&#xff0c;是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产 大数据主要解决&#xff0c;海量数据的采集、存…

【吴恩达推荐】《ChatGPT Prompt Engineering for Developers》- 知识点目录

《ChatGPT Prompt Engineering for Developers》 1 Introduction 2 Guidelines Principle 1: Write clear and specific instructions Tactic 1: Use delimiters Tactic 3: “If-statement” Check whether conditions are satisfiedCheck assumptions required to do the …

RDD的Stage划分原理

1. 什么是RDD RDD&#xff08;Resilient Distributed Dataset&#xff09;叫做分布式数据集&#xff0c;是Spark 中最基本的数据抽象&#xff0c;它代表一个不可变、可分区、里面的元素可并行计算的集合。在Spark 中&#xff0c;对数据的所有操作不外乎创建RDD、转化已有RDD 以…

JavaBeaneljstl

1.JavaBean 1.1 什么是JavaBean JavaBean 是一种JAVA语言写成的可重用组件。为写成JavaBean&#xff0c;类必须是具体的和公共的&#xff0c;并且具有无参数的构造器 简单一点&#xff1a;建一个类,给一个无参的构造方法. 它就是JavaBean&#xff0c;对应JavaBean来说&#x…

【C++】程序员的屠龙母鸡:二叉树进阶OJ题详解

不会自动生成&#xff0c;还是我自己写目录吧 -.- 文章目录 前言一、稍微简单一点的二叉树OJ题二、相对困难一点的二叉树OJ题总结 前言 在看这篇文章前希望大家是学过二叉树的&#xff0c;不然理解起来可能会比较费劲&#xff0c;但我会尽自己的努力让大家学会这些题&#xf…

TensorFlow会被JAX代替吗,使用JAX训练第一个机器学习模型

上期文章我们分享了JAX的概念&#xff0c;Jax 是来自 Google 的一个相对较新的机器学习库。它更像是一个 autograd 库&#xff0c;可以区分每个本机 python 和 NumPy 代码。 “PythonNumPy 程序的可组合转换&#xff1a;微分、向量化、JIT 到 GPU/TPU 等等”。该库利用 grad 函…

vue 视频播放插件vue-video-player自定义样式

1、背景 项目中有涉及视频播放的需求&#xff0c;并且UI设计了样式&#xff0c;与原生的视频video组件有差异&#xff0c;所以使用了vue-video-player插件&#xff0c;并对vue-video-player进行样式改造&#xff0c;自定义播放暂停按钮、全屏按钮、时间进度条样式等 2、效果图…