前言:引入C++面向对象的编程方式会让写单片机程序看起来更加板正。祝我顺利!

人如果要进步,就要用于去接受新鲜事物,新鲜方法,新鲜思想。这种新鲜对你来说是新鲜的,可是在客观的世界,它却比你想象的更加成熟,这就是你的无知,善于向优秀的人去学习,善于向不同领域的人学习,倾听他们的思想,可能当时你觉得没用,那是因为你们还不在一个LEVEL。回到单片机,仅仅以实现功能为目的,那可能单片机你能走的路也就这么长了,甚至一年以后,五年以后,你还是这个水平,写的代码拼凑,没有层次,没有美感。这不是我想要的,所以从0到1,每天努力一点,不要永远做底层的那帮人。——米杰的声音

1 基础准备:一块电路板

程序主要是在这个板子上跑,硬件电路板如下(原理图就不贴了,主要学习思想):

2 基础准备:CobeMX基础功能配置

2.1 CobeMX项目设置

ADC采用多通道和DMA传输:

扫描转换模式开启

再把ADC中断加上,再加上DMA。

2.2 定时器TIM2的PWM输出设置

产生了1ms周期可调的占空比信号

加入中断,在中断里面调整占空比:

2.3 按键外部中断设置

检测低电平按下

2.4 添加FREERTOS

3 Keil环境配置

我们的文件夹按照这个格式配置看起来相对工整一些,之后只需要将自己配置的文放在对应的文件夹里面就可以了。

Port用于存放连接硬件和应用层的底层驱动。 今后上述文件夹中的Scr和Inc再去调用GPIO的时候,不必直接去调用HAL库文件,而是直接调用Port中的GPIO类,因此给新建的GPIO类起一个好听的名字就显得格外重要。

4 GPIO类创建

开发环境IDE基于Keil5

类的基本思想是数据的抽象和封装,数据抽象是一个依赖于接口和实现分离的编程。

封装实现了类的接口和实现的分离,要想实现数据抽象和封装,首先需要定义一个抽象的数据类型。

比如GPIO这个功能,我们想要调用一个端口比如GPIOA的PN1,GPIOA是端口的基地址,也就是:

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOA_BASE            (D3_AHB1PERIPH_BASE + 0x0000UL)

引脚号是基于端口基地址的偏移量:

#define GPIO_PIN_1                 ((uint16_t)0x0002)  /* Pin 1 selected    */

因此我们操作具体某个引脚的时候,这两个输入实参就会作为数据的输入:

那么输入知道了,就需要对这个功能进行封装了。

我们常用的功能有输出、输入、反转;

我们建立便携GPIO类的这个文件。

并将其导入工程中。

4.1 创建MCUGPIO类——构造函数

这个类主要是实现GPIO的一些操作,比如:

HAL_GPIO_WritePin的GPIO_PIN_SET和GPIO_PIN_RESET

输入量是GPIO_TypeDef *GPIOx和uint16_t GPIO_Pin

那么首先要进行构造函数来初始化这两个输入值

首先类要有成员定义:

void *_GPIOx;
    uint16_t _GPIO_Pin;

构造函数没有返回值且函数名和类名相同,同时构造函数也可以重载;

MCUGPIO(void *GPIOx, uint16_t GPIO_Pin);
    MCUGPIO();

那么如何调用构造函数?

构造函数和普通函数不同,一般不显式的去调用;但在创建一个对象时构造函数被自动调用;

也就是说我定义了一个对象:

在生成这个对象的过程中:

MCUGPIO::MCUGPIO(void *GPIOx, uint16_t GPIO_Pin)
{
    _GPIOx = GPIOx;
    _GPIO_Pin = GPIO_Pin;
}

在.cpp中定义的构造函数的具体实现会被编译器自动调用。

在C中引用C++语言中的函数和变量时,C++的函数或变量要声明在extern "C"{}里,

在main.h中定义了引脚的宏定义

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#ifdef __cplusplus
extern "C" {
#endif

#define SCL_Pin GPIO_PIN_6
#define SCL_GPIO_Port GPIOB
#define SDA_Pin GPIO_PIN_7
#define SDA_GPIO_Port GPIOB

/* USER CODE BEGIN Private defines */
#ifdef __cplusplus
}
#endif
/* USER CODE END Private defines */

#endif /* __MAIN_H */

4.1.1 析构函数

构造函数用来完成对象的初始化

析构函数用来清理

注意:析构函数和构造函数没有来行说明符,程序不能直接调用,创建和撤销由系统调用自动完成。

4.2 FREE RTOS任务函数创建——this指针

GPIO类创建完成之后

this指针:

1 this指针指向的是类的对象;答:this指针不占用类的大小,是编译器帮助传递的。

2 this指针是一个地址,地址里面存放的是什么?答:this实际存放对象的首地址。

3.类的成员函数有静态static和非静态,为什么this指针不能操作静态成员函数?

答:因为静态成员函数是先于对象存在的,是对于所有对象共享的。如果没有对象this是不能实例化对象首地址的。因此也就解释了为什么在static静态成员函数中不能直接调用this指针。

4 为什么要设计this指针?c++对象模型。

4.2.1 创建osInterface文件放在视图层

用于对下面的模型信息进行操作,任务的调度。

头文件的框架如下:

便于C的调用

#ifndef __OSINTERFACE_H__
#define __OSINTERFACE_H__#ifdef __cplusplus
extern "C"{
#endif#ifdef __cplusplus
};
#endif#endif/* __OSINTERFACE_H__ */

我们在OS文件中主要将CUBEMX生成的任务虚函数拿过来。

这些虚函数在freertos.c/.h文件里面

我们将它提取到视图文件里;

#ifndef __OSINTERFACE_H__
#define __OSINTERFACE_H__#ifdef __cplusplus
extern "C"{
#endifvoid StartDefaultTask(void const * argument);void StartTask02(void const * argument);void StartTask03(void const * argument);void StartTask04(void const * argument);void StartTask05(void const * argument);void StartTask06(void const * argument);#ifdef __cplusplus
};
#endif#endif  /* __OSINTERFACE_H__ */

5 创建IO对象——单例模式(23种设计模式之一)

单例模式:确保一个类有且只有一个实例,且自行实例化并向整个系提供这个实例。

注定了它的构造方法不能是public,而是private。

且这个实例是当前类的成员变量,即静态变量,即用static修饰。

故单例模式要求构造方法是private,并且拥有当前类的静态成员变量。还需要提供一个静态方法,向外界提供当前类的实例。

单例模式的作用是确保一个类只有一个实例存在;

特点是:类构造器私有;持有自己类型的属性;对外界提供获取实例的静态方法;

注意事项:构造函数是私有的;析构函数是共有的;提供获取实例的函数;自己类型的属性需要在外部初始化;最后的垃圾回收。

实现顺序是:定义类——定义构造函数——添加自身属性——添加获取实例函数——使用

首先判断这个对象是否为空,不为空就返回这个对象,为空就创建一个新的对象;为这个对象开辟内存。

class TurnTable
{
private:static TurnTable *_instance; //私有静态对象属性TurnTable() {};禁止外部实例化对象,应当私有化这个类的构造函数public:公有静态方法实例化对象static TurnTable *Instance();}TurnTable *TurnTable::_instance = nullptr;TurnTable *TurnTable::Instance()
{if (nullptr == _instance){_instance = new TurnTable;//如果时空创建这个对象}return _instance;//如果不为空,返回这个对象
}

这就引出一个问题C++的空如何表示?

5.1 C++中NULL和nullptr的区别

空指针不指向任何对象,在试图使用一个指针之前可以首先检查它是否为空。

得到一个空指针最直接的办法就是直接使用nullprt初始化指针,这种类型的字面值可以被转化成任意其他的指针类型,也可以通过将指针初始化为0来生成空指针。

在C++中使用NULL来初始化指针需要引入头函数#include <cstdlib>

在新的标准下,最好使用nullpr,避免使用NULL。

因此应当初始化所有的指针,且在定义了对象之后再定义指向它的指针,如果不清楚指针的具体位置,可以使用0或者nullprt进行初始化

5.2 懒汉式单例模式

//懒汉式:
pbulic class SinglentonDemo{private static SingletonDemo instance;private SingletonDemo(){ } //构造函数public static SingletonDemo getinstance(){if(instance == nullprt){instance = new SingletonDemo();return instance;}}
}//线程安全加锁,增加synchronized关键字,效率低
pbulic class SinglentonDemo{private static SingletonDemo instance;private SingletonDemo(){ } //构造函数public static synchronized SingletonDemo getinstance(){if(instance == nullprt){instance = new SingletonDemo();return instance;}}
}//饿汉式:直接初始化
pbulic class SinglentonDemo{private static SingletonDemo instance = new SingletonDemo;private SingletonDemo(){ } //构造函数public static synchronized SingletonDemo getinstance(){if(instance == nullprt){instance = new SingletonDemo();return instance;}}
}

6 创建IO操作函数;

对于这个项目,IO口主要实现的功能主要包括LED小灯的点亮工作。

我们建立一个deviceconfigure.cpp函数用于实例MCUGPIO类。

在这里我们创建了实例化构造函数:

MCUGPIO LED1(LED1_GPIO_Port,LED1_Pin);
MCUGPIO LED2(LED2_GPIO_Port,LED2_Pin);
MCUGPIO LED3(LED3_GPIO_Port,LED3_Pin);
MCUGPIO LED4(LED4_GPIO_Port,LED4_Pin);

并在头文件中声明了其调用性。

extern MCUGPIO LED1;
extern MCUGPIO LED2;
extern MCUGPIO LED3;
extern MCUGPIO LED4;

同时为了延时的实现,又声明了延时函数用于系统调用。

/*** @description: 延时函数* @param {uint32_t} ms* @return {*}*/
void delay_ms(uint32_t nms)
{
#if (INCLUDE_xTaskGetSchedulerState  == 1 )   if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)     {        osDelay(nms);  //OS延时   }else delay_us((uint32_t)(nms*1000));              //普通方式延时
#elseHAL_Delay(nms);
#endif
} /*** @description: 微秒级延时* @param {__IO uint32_t} delay* @return {*}*/
void delay_us(uint32_t us)
{__IO uint32_t currentTicks = SysTick->VAL;/* Number of ticks per millisecond */const uint32_t tickPerMs = SysTick->LOAD + 1;/* Number of ticks to count */const uint32_t nbTicks = ((us - ((us > 0) ? 1 : 0)) * tickPerMs) / 1000;/* Number of elapsed ticks */uint32_t elapsedTicks = 0;__IO uint32_t oldTicks = currentTicks;do{currentTicks = SysTick->VAL;elapsedTicks += (oldTicks < currentTicks) ? tickPerMs + oldTicks - currentTicks : oldTicks - currentTicks;oldTicks = currentTicks;} while (nbTicks > elapsedTicks);
}

然后再在视图控制器中去进行调用,也就是让其再任务中执行。例如:

void StartTask04(void const * argument)
{for(;;){LED4.setpin();delay_ms(100); LED4.resetpin();delay_ms(100); osDelay(1);}
}

效果如下:

我们可以看到LED不是同时闪亮,主要原因就是任务的优先级设置的问题。

因为优先级高的任务可以打断优先级低的任务,这就导致了优先级低的LED灯闪亮的时间不是自己设置的,更改的方法很简单。

7. IIC驱动液晶屏

在编写程序之前需要将源程序添加到工程里面。

【STM32】在Keil上使用C++编程相关推荐

  1. 单片机搭建环境烧录方法_万物互联-stm32单片机简介、烧录、编程及其项目环境搭建...

    万物互联-stm32单片机简介.烧录.编程 前言:stm32单片机这里给出简单介绍,给不了解的朋友普及下硬件端的基本知识,叙述的较为简单,想深入研究的朋友可以去一些官方网站.论坛.博客汲取知识.最下端 ...

  2. 【嵌入式基础】STM32中断及DMA通信原理编程

    本文主要学习stm32中断.DMA通信原理和编程方法.使用stm32tubemx和HAL库分别完成中断模式编程和串口通信中断实验. 目录 一.STM32中断,DMA通信原理编程 1.STM32中断 ( ...

  3. stm32 app 连上阿里云

    stm32 app 连上阿里云 阿里云篇 1.首先得先注册账号,注册账号后选择物联网平台 2.点击公共实例 3.创建产品产品(我以智能窗户为例) 4添加设备 我们需要添三个设备 app pc stm3 ...

  4. STM32 使用Keil下载仿真时,报错 JLink Info: STM32Fxxxx: Cannot attach to CPU. Trying connect under reset.

    STM32 使用Keil下载仿真时,报错 JLink Info: STM32Fxxxx: Cannot attach to CPU. Trying connect under reset. 解决方式 ...

  5. python解释器在语法上不支持 编程方式-python解释器在语法上不支持什么编程方式_后端开发...

    python程序的两种运行方式是什么_后端开发 python程序的两种运行方式是:1.使用REPL模式运行,REPL模式即读取-计算-打印-循环的模式,借助的工具是IDLE(python集成开发环境) ...

  6. STM32开发 -- Keil使用(2)

    继续参看:ybhuangfugui 博客专家 STM32开发 – Keil使用(1) 用了很长的篇幅,主要介绍了keil的菜单栏.工具栏.工程配置等信息.但是很多时候拿到的工程示例,这些可是都配好了的 ...

  7. Java黑皮书课后题第8章:*8.15(几何:在一条直线上吗)编程练习题6.39给出了一个方法,用于测试三个点是否在一条直线上。编写下面的方法,检测points数组中所有的点是否都在同一条直线上

    *8.15(几何:在一条直线上吗)编程练习题6.39给出了一个方法,用于测试三个点是否在一条直线上.编写下面的方法,检测points数组中所有的点是否都在同一条直线上 题目 题目描述与运行示例 破题 ...

  8. python ipad pro_离开 PC,在 iPad Pro 上也能编程了?

    论基于 iPad Pro 平台编码的可行性. 作者 | Andrew Brookins 译者 |弯月 责编 | 屠敏 出品 | CSDN(ID:CSDNNews) 2017年我曾提出过一个问题,&qu ...

  9. 运行时:Linux 和 Windows 2000上的高性能编程技术

    运行时:Linux 和 Windows 2000上的高性能编程技术 建立计时例程       级别: 初级 Edward G. Bradford, 高级程序员, IBM 2001 年 4 月 01 日 ...

最新文章

  1. 保持dropdownlist选中值
  2. Nginx upstream的几种分配方式
  3. python之pygame安装教程_Python中pygame安装方法图文详解
  4. 数据结构和算法 —— 谈谈算法
  5. ORA-06502:PL/SQL :numberic or value error: character string buffer too small
  6. oracle的function的语法,Oracle function语法
  7. Redis运维和开发学习笔记-全书思维导图
  8. JDK 8的一些新特性
  9. 女人选择安逸一点还是拼搏一点
  10. springcloud之ribbon负载均衡
  11. 信息系统项目管理师考试相关介绍
  12. 红帽linux中文语言包,英文 RedHat AS5 中文语言包安装
  13. Google DFP广告管理系统简介:开始与您的网站进行广告集成
  14. excel 第15讲:条件格式与公式
  15. 小卡要民主(卡雷尔机器人)
  16. Word 之 清除页眉下划线
  17. b、B、KB、MB、GB 的关系?
  18. python身份证号码共18位_18位身份证校验
  19. grasscutter 使用指南——Android/Windows/IOS端均已支持
  20. mybatis拦截器实现update之前根据pk字段校验数据有效性

热门文章

  1. Prometheus和它的xdm
  2. c语言指数部分尾数部分,C语言中 float double在内存中的存储
  3. 一款不愿透露姓名的绿色小说软件
  4. 单片机的调试接口 JTAG SWD
  5. 双屏 3840 * 1080 如何装逼?不同的屏幕显示不同的壁纸。php 定时脚本下载必应每日壁纸。
  6. Amazon DynamoDB详解
  7. echarts 中国地图 世界地图
  8. 计算机技术电子出版参考文献,参考文献规范
  9. php保留两位小叔_PHP价格格式化,保留两位小数
  10. vue-cli的webpack模板项目配置文件分析[转]