赞
踩
前言:引入C++面向对象的编程方式会让写单片机程序看起来更加板正。祝我顺利!
人如果要进步,就要用于去接受新鲜事物,新鲜方法,新鲜思想。这种新鲜对你来说是新鲜的,可是在客观的世界,它却比你想象的更加成熟,这就是你的无知,善于向优秀的人去学习,善于向不同领域的人学习,倾听他们的思想,可能当时你觉得没用,那是因为你们还不在一个LEVEL。回到单片机,仅仅以实现功能为目的,那可能单片机你能走的路也就这么长了,甚至一年以后,五年以后,你还是这个水平,写的代码拼凑,没有层次,没有美感。这不是我想要的,所以从0到1,每天努力一点,不要永远做底层的那帮人。——米杰的声音
程序主要是在这个板子上跑,硬件电路板如下(原理图就不贴了,主要学习思想):
ADC采用多通道和DMA传输:
扫描转换模式开启
再把ADC中断加上,再加上DMA。
产生了1ms周期可调的占空比信号
加入中断,在中断里面调整占空比:
检测低电平按下
我们的文件夹按照这个格式配置看起来相对工整一些,之后只需要将自己配置的文放在对应的文件夹里面就可以了。
Port用于存放连接硬件和应用层的底层驱动。 今后上述文件夹中的Scr和Inc再去调用GPIO的时候,不必直接去调用HAL库文件,而是直接调用Port中的GPIO类,因此给新建的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类的这个文件。
并将其导入工程中。
这个类主要是实现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 */
构造函数用来完成对象的初始化
析构函数用来清理
注意:析构函数和构造函数没有来行说明符,程序不能直接调用,创建和撤销由系统调用自动完成。
GPIO类创建完成之后
this指针:
1 this指针指向的是类的对象;答:this指针不占用类的大小,是编译器帮助传递的。
2 this指针是一个地址,地址里面存放的是什么?答:this实际存放对象的首地址。
3.类的成员函数有静态static和非静态,为什么this指针不能操作静态成员函数?
答:因为静态成员函数是先于对象存在的,是对于所有对象共享的。如果没有对象this是不能实例化对象首地址的。因此也就解释了为什么在static静态成员函数中不能直接调用this指针。
4 为什么要设计this指针?c++对象模型。
用于对下面的模型信息进行操作,任务的调度。
头文件的框架如下:
便于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"{
- #endif
- void 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__ */
单例模式:确保一个类有且只有一个实例,且自行实例化并向整个系提供这个实例。
注定了它的构造方法不能是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++的空如何表示?
空指针不指向任何对象,在试图使用一个指针之前可以首先检查它是否为空。
得到一个空指针最直接的办法就是直接使用nullprt初始化指针,这种类型的字面值可以被转化成任意其他的指针类型,也可以通过将指针初始化为0来生成空指针。
在C++中使用NULL来初始化指针需要引入头函数#include <cstdlib>
在新的标准下,最好使用nullpr,避免使用NULL。
因此应当初始化所有的指针,且在定义了对象之后再定义指向它的指针,如果不清楚指针的具体位置,可以使用0或者nullprt进行初始化
- //懒汉式:
- 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;
- }
- }
- }
对于这个项目,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)); //普通方式延时
- #else
- HAL_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灯闪亮的时间不是自己设置的,更改的方法很简单。
在编写程序之前需要将源程序添加到工程里面。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。