【硬件IIC】stm32单片机利用硬件IIC驱动OLED屏幕

之前操作OLED屏幕都是用GPIO模拟IIC去驱动,最近打算用硬件IIC去驱动,于是写下这个demo,在这个过程中遇到一点小坑,记录一下,本文章非小白教程,所以只突出踩到的坑点,文章中涉及到的OLED也是网上资料写烂的,所以不懂的同学可以万能的百度。话不多说开始。

目标

使用硬件IIC驱动OLED屏,显示英文字符串“I am Rio”
在这里插入图片描述

完整项目工程链接: stm32F103C8T6驱动OLED屏显示字符

同时附上一篇网上找的介绍这个OLED屏的文章
0.96寸OLED(SSD1306)屏幕显示(一)——基础功能介绍

硬件

MCU: stm32f103c8t6

屏幕: 0.96寸OLED(SSD1306)

本次使用的是 gpio 的PB6,PB7脚,这两个脚位可以复用硬件I2C1。

驱动程序

代码中已加入详细注释,放心食用

GPIO和IIC初始化配置

这里遇到第一个坑,就是在配置IO的模式时,一开始设置成推挽输出GPIO_MODE_OUTPUT_PP,因为想着就接一个IIC设备,推挽输出或者开漏输出,都影响不大,结果还说遇到坑了,配置成推挽输出之后,程序会出现卡死现象,debug之后发现程序卡死在函数HAL_I2C_Init(&hi2c1)里; 然后进入到错误HardFault_Handler()中,怀疑是HAL_I2C_Init中的某些设置与设置成推挽输出GPIO_MODE_OUTPUT_PP出现冲突导致硬件错误,具体还未深入研究,欢迎大佬补充。

将GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;后不再出现卡死现象。

I2C_HandleTypeDef hi2c1;
uint16_t slaveAddr = 0x78;	//OLED显示屏的IIC设备地址,改地址为写地址
void oled_gpio_init(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};__HAL_RCC_GPIOB_CLK_ENABLE();	//使能GPIOB口时钟/**I2C1 GPIO ConfigurationPB6     ------> I2C1_SCLPB7     ------> I2C1_SDA*/GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;//当时就是这句导致程序卡死,用HAL库实现硬件IIC时还是乖乖用开漏模式好
//    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;		//配置为开漏模式GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* I2C1 clock enable */__HAL_RCC_I2C1_CLK_ENABLE();hi2c1.Instance = I2C1;hi2c1.Init.ClockSpeed = 100000; // 设置I2C时钟速度为100kHz(可以根据需要调整)hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // 占空比(通常不需要修改)hi2c1.Init.OwnAddress1 = 0; // 主设备通常不需要设置自己的地址hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // 7位地址模式hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; // 双地址模式禁用hi2c1.Init.OwnAddress2 = 0; // 不使用第二个地址hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; // 通用调用模式禁用hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; // 时钟延伸模式禁用HAL_I2C_Init(&hi2c1);}
IIC的读和写

这里由于没有理解透HAL库的函数踩到第二个坑,用的这一款OLED在写入命令或者数据时,时序是与从机建立IIC通讯开始信号后,就连续写入control byte + data byte; 比如写数据为 0x40 + data; 写命令是0x00 + cmd;

而HAL库提供的IIC读写函数有好几种,如:

HAL_I2C_Master_Receive(&hi2c1, DevAddress, pData, Size, Timeout);
HAL_I2C_Master_Transmit(&hi2c1, DevAddress, pData, Size, Timeout);

这一组是用来作为主机时,对从机进行读写操作,而我一开始在往从机写数据的时候,使用的就是HAL_I2C_Master_Transmit函数,将control byte + data byte;分开两次来发送,结果就写失败了。原因是分开两次写的话,HAL_I2C_Master_Transmit每次写完一个byte数据之后,就结束该次通讯,这就相当于没有发送完整的control byte + data byte时序;而是

第一次发送control byte结束;第二次发送data byte结束;所以两次没有一次是完整的组合时序。正确的是应该在一次通讯中发送两个byte数据才行;

所以我将要发送的时序进行组合,然后一次发送两个数据即可

uint8_t dataArr[2] = {0x40, data};
I2C_SendData(slaveAddr, dataArr, 2, 1000);

//对HAL_I2C_Master_Transmit函数进行封装
HAL_StatusTypeDef I2C_SendData(uint16_t DevAddress, uint8_t* pData, uint16_t Size, uint32_t Timeout)
{return HAL_I2C_Master_Transmit(&hi2c1, DevAddress, pData, Size, Timeout);
}//对HAL_I2C_Master_Receive函数进行封装
HAL_StatusTypeDef I2C_ReceiveData(uint16_t DevAddress, uint8_t* pData, uint16_t Size, uint32_t Timeout)
{return HAL_I2C_Master_Receive(&hi2c1, DevAddress, pData, Size, Timeout);
}//封装函数,实现对往OLED中写入一个字节数据
void oled_write_data(uint8_t data)
{    /*错误的一次只写一个数据,时序不完整*/
//    uint8_t dataOptionByte = 0x00;   
//    I2C_SendData(slaveAddr, &dataOptionByte, 1, 1000);
//    I2C_SendData(slaveAddr, &dataData, 1, 1000);/*一次只写入完整的时序即可*/uint8_t dataArr[2] = {0x40, data};I2C_SendData(slaveAddr, dataArr, 2, 1000);/*使用HAL_I2C_Mem_Write函数,也可实现相同效果*/
//    uint8_t tmpData = data;
//    HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,
//										&tmpData, 1, 0xff);
}//封装函数,实现对往OLED中写入一个字节命令
void oled_write_cmd(uint8_t cmd)
{/*错误的一次只写一个数据,时序不完整*/
//    uint8_t cmdOptionByte = 0x00;
//    I2C_SendData(slaveAddr, &cmdOptionByte, 1, 1000);
//    I2C_SendData(slaveAddr, &dataCmd, 1, 1000);/*一次只写入完整的时序即可*/uint8_t cmdArr[2] = {0x00, cmd};I2C_SendData(slaveAddr, cmdArr, 2, 1000);/*使用HAL_I2C_Mem_Write函数,也可实现相同效果*/
//    uint8_t tmpCmd = cmd;
//    HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,
//										&tmpCmd, 1, 0xff);}

完整代码

oled.c

#include "oled.h"
#include "delay.h"
#include "font.h"I2C_HandleTypeDef hi2c1;
uint16_t slaveAddr = 0x78;
void oled_gpio_init(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};__HAL_RCC_GPIOB_CLK_ENABLE();/**I2C1 GPIO ConfigurationPB6     ------> I2C1_SCLPB7     ------> I2C1_SDA*/GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;//    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  //GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/* I2C1 clock enable */__HAL_RCC_I2C1_CLK_ENABLE();hi2c1.Instance = I2C1;hi2c1.Init.ClockSpeed = 100000; // 设置I2C时钟速度为100kHz(可以根据需要调整)hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // 占空比(通常不需要修改)hi2c1.Init.OwnAddress1 = 0; // 主设备通常不需要设置自己的地址hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // 7位地址模式hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; // 双地址模式禁用hi2c1.Init.OwnAddress2 = 0; // 不使用第二个地址hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; // 通用调用模式禁用hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; // 时钟延伸模式禁用HAL_I2C_Init(&hi2c1);}
/*oled_gpio_init()函数中不配置GPIO口和使能时钟,在MSP函数中配置也可以,因为
执行HAL_I2C_Init(&hi2c1);时,会执行HAL_I2C_MspInit函数
*/
//void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
//{//  GPIO_InitTypeDef GPIO_InitStruct = {0};
//  if(i2cHandle->Instance==I2C1)
//  {
//  /* USER CODE BEGIN I2C1_MspInit 0 *///  /* USER CODE END I2C1_MspInit 0 *///    __HAL_RCC_GPIOB_CLK_ENABLE();
//    /**I2C1 GPIO Configuration
//    PB6     ------> I2C1_SCL
//    PB7     ------> I2C1_SDA
//    */
//    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
//    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
//    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
//    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);//    /* I2C1 clock enable */
//    __HAL_RCC_I2C1_CLK_ENABLE();
//  /* USER CODE BEGIN I2C1_MspInit 1 *///  /* USER CODE END I2C1_MspInit 1 */
//  }
//}HAL_StatusTypeDef I2C_SendData(uint16_t DevAddress, uint8_t* pData, uint16_t Size, uint32_t Timeout)
{return HAL_I2C_Master_Transmit(&hi2c1, DevAddress, pData, Size, Timeout);
}HAL_StatusTypeDef I2C_ReceiveData(uint16_t DevAddress, uint8_t* pData, uint16_t Size, uint32_t Timeout)
{return HAL_I2C_Master_Receive(&hi2c1, DevAddress, pData, Size, Timeout);
}void oled_write_data(uint8_t data)
{    
//    uint8_t dataOptionByte = 0x00;   
//    I2C_SendData(slaveAddr, &dataOptionByte, 1, 1000);
//    I2C_SendData(slaveAddr, &dataData, 1, 1000);uint8_t dataArr[2] = {0x40, data};I2C_SendData(slaveAddr, dataArr, 2, 1000);
//    uint8_t tmpData = data;
//    HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT,
//										&tmpData, 1, 0xff);
}void oled_write_cmd(uint8_t cmd)
{
//    uint8_t cmdOptionByte = 0x00;
//    I2C_SendData(slaveAddr, &cmdOptionByte, 1, 1000);
//    I2C_SendData(slaveAddr, &dataCmd, 1, 1000);uint8_t cmdArr[2] = {0x00, cmd};I2C_SendData(slaveAddr, cmdArr, 2, 1000);//    uint8_t tmpCmd = cmd;
//    HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT,
//										&tmpCmd, 1, 0xff);}void oled_init(void)
{oled_gpio_init();delay_ms(100);oled_write_cmd(0xAE);    //设置显示开启/关闭,0xAE关闭,0xAF开启oled_write_cmd(0xD5);    //设置显示时钟分频比/振荡器频率oled_write_cmd(0x80);    //0x00~0xFFoled_write_cmd(0xA8);    //设置多路复用率oled_write_cmd(0x3F);    //0x0E~0x3Foled_write_cmd(0xD3);    //设置显示偏移oled_write_cmd(0x00);    //0x00~0x7Foled_write_cmd(0x40);    //设置显示开始行,0x40~0x7Foled_write_cmd(0xA1);    //设置左右方向,0xA1正常,0xA0左右反置oled_write_cmd(0xC8);    //设置上下方向,0xC8正常,0xC0上下反置oled_write_cmd(0xDA);    //设置COM引脚硬件配置oled_write_cmd(0x12);oled_write_cmd(0x81);    //设置对比度oled_write_cmd(0xCF);    //0x00~0xFFoled_write_cmd(0xD9);    //设置预充电周期oled_write_cmd(0xF1);oled_write_cmd(0xDB);    //设置VCOMH取消选择级别oled_write_cmd(0x30);oled_write_cmd(0xA4);    //设置整个显示打开/关闭oled_write_cmd(0xA6);    //设置正常/反色显示,0xA6正常,0xA7反色oled_write_cmd(0x8D);    //设置充电泵oled_write_cmd(0x14);oled_write_cmd(0xAF);    //开启显示}
// y的值为page,屏幕总共有8个page
void oled_set_cursor(uint8_t x, uint8_t y)
{oled_write_cmd(0xB0 + y);   //确定在哪一个page, 第一个page地址是B0oled_write_cmd((x & 0x0F) | 0x00);  //取字节的低位oled_write_cmd(((x & 0xF0) >> 4) | 0x10);   //取字节的高位,|0x10是因为oled芯片的要求}//清屏函数,每次刷新画面时,需要清屏,防止上一帧数据残留
void oled_fill(uint8_t data)
{uint8_t i, j;for( i = 0; i <8 ; i++){oled_set_cursor(0, i);for(j = 0; j < 128; j++){   //page模式,地址每次都自动偏移oled_write_data(data);}}}//输入字符的坐标、ASCII码、大小; size一般是宽为高的1/2;
void oled_show_char(uint8_t x, uint8_t y, uint8_t num, uint8_t size)
{uint8_t i, j, page;num = num - ' ';page = size/8;if (size % 8)page++;for(j = 0; j < page; j++ ){oled_set_cursor(x, y + j);for(i = size/2 * j; i < size/2 *(j + 1); i++)   //即每一个page写size/2宽的数据,{if (size == 12)oled_write_data(ascii_6X12[num][i]);else if (size == 16)oled_write_data(ascii_8X16[num][i]);else if (size == 24)oled_write_data(ascii_12X24[num][i]);}}
}//输入字符的坐标、字符指针、大小; size一般是宽为高的1/2;
void oled_show_string(uint8_t x, uint8_t y, char *p, uint8_t size)
{while( *p !='\0'){oled_show_char(x, y, *p, size);x += size/2;p++;}
}

oled.h

#ifndef __OLED_H__
#define __OLED_H__#include "sys.h"void oled_gpio_init(void);
void oled_init(void);
void oled_write_cmd(uint8_t cmd);
void oled_write_data(uint8_t data);void oled_set_cursor(uint8_t x, uint8_t y);
void oled_fill(uint8_t data);
void oled_show_char(uint8_t x, uint8_t y, uint8_t num, uint8_t size);
void oled_show_string(uint8_t x, uint8_t y, char *p, uint8_t size);
#endif

main.c

#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "oled.h"int main(void)
{HAL_Init();                         /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */led_init();                         /* 初始化LED灯 */uart1_init(115200);printf("hello world!\r\n");oled_init();oled_fill(0x00);oled_set_cursor(0, 0);//划线
//    oled_write_data(0x80);
//    oled_write_data(0x80);
//    oled_write_data(0x80);
//    oled_write_data(0x80);
//    oled_write_data(0x80);
//    oled_write_data(0x80);//    oled_show_char(0, 0, 'x', 24);oled_show_string(0, 2, "I am Rio", 24);while(1){ led1_on();led2_off();delay_ms(500);led1_off();led2_on();delay_ms(500);}
}

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

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

相关文章

python如何自动加空格

首先&#xff0c;需要进行打开的一个pycharm的软件&#xff0c;可进行双击的打开该软件。 可以看到的是在当前的打开的文件中&#xff0c;格式相对较乱一下。格式不对会格式错误。 然后点击菜单栏中的“code”。 在弹出的下拉菜单中选择“reformat code”选项。 可以看到的是在…

【开源免费】基于SpringBoot+Vue.JS网上订餐系统(JAVA毕业设计)

本文项目编号 T 018 &#xff0c;文末自助获取源码 \color{red}{T018&#xff0c;文末自助获取源码} T018&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 新…

答题考试系统v1.6.1高级版源码分享+uniapp+搭建测试环境

一.系统介绍 一款基于FastAdminThinkPHPUniapp开发的小程序答题考试系统&#xff0c;支持多种试题类型、多种试题难度、练题、考试、补考模式&#xff0c;提供全部前后台无加密源代码&#xff0c;支持私有化部署 二.测试环境 系统环境&#xff1a;CentOS、 运行环境&#x…

经典电荷泵/Charge pump——1998.JSSC

电路结构 工作原理 M3 and M4 are the series switches, and M5, M6 switch to the highest voltage. If M5 and M6 are missing, having a large capacitor is of absolute necessity, because must always stay between 2 Vin and 2Vin - Uj to avoid switching on the vert…

Swin transformer 论文阅读记录 代码分析

该篇文章&#xff0c;是我解析 Swin transformer 论文原理&#xff08;结合pytorch版本代码&#xff09;所记&#xff0c;图片来源于源paper或其他相应博客。 代码也非原始代码&#xff0c;而是从代码里摘出来的片段&#xff0c;配上简单数据&#xff0c;以便理解。 当然&…

GPT-Omni 与 Mini-Omni2:创新与性能的结合

近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;各种模型和平台应运而生&#xff0c;以满足从个人用户到企业级应用的多样化需求。在这一领域&#xff0c;GPT-Omni 和 Mini-Omni2 是两款备受瞩目的技术产品&#xff0c;它们凭借独特的设计和强大的功能&#xff0c;在…

龙迅#LT7911E适用于EDP/DP/TPYE-C转MIPIDSI应用,支持图像处理功能,内置I2C,主应用副屏显示,投屏领域!

1. 描述 LT7911E 是一款高性能 eDP 转 MIPI D-PHY 转换器&#xff0c;旨在将 eDP 源连接到 MIPI 显示面板。 LT7911E 集成了一个符合 eDP1.4 标准的接收器&#xff0c;支持 1.62Gbps 至 5.67Gbps 的输入数据&#xff0c;以 270Mbps 的递增步长&#xff0c;以及一个 2 端口 D…

C语言——实现求出最大值

问题描述&#xff1a;利用C语言自定义函数求出一维数组里边最大的数字 //利用函数找最大数#include<stdio.h>int search(int s[9]) //查找函数 {int i , max s[0] , max_xia 0;for(i0;i<9;i){if(s[i] > max){max_xia i;max s[max_xia];}}return max; } in…

解锁 draw.io 流程图制作工具Docker私有化部署(2/2)

一、draw.io 流程图制作工具简介 &#xff08;一&#xff09;基础介绍 draw.io 是一款备受青睐的开源流程图软件&#xff0c;它有着诸多优点。首先&#xff0c;其界面十分整洁有序&#xff0c;完全没有广告的干扰&#xff0c;并且所有功能都是免费向用户开放的&#xff0c;这一…

[HNCTF 2022 Week1]baby_rsa

源代码&#xff1a; from Crypto.Util.number import bytes_to_long, getPrime from gmpy2 import * from secret import flag m bytes_to_long(flag) p getPrime(128) q getPrime(128) n p * q e 65537 c pow(m,e,n) print(n,c) # 62193160459999883112594854240161159…

docker run命令大全

docker run命令大全 基本语法常用选项基础选项资源限制网络配置存储卷和挂载环境变量重启策略其他高级选项示例总结docker run 命令是 Docker 中最常用和强大的命令之一,用于创建并启动一个新的容器。该命令支持多种选项和参数,可以满足各种使用场景的需求。以下是 docker ru…

Flink2.0未来趋势中需要注意的一些问题

手机打字&#xff0c;篇幅不长&#xff0c;主要讲一下FFA中关于Flink2.0的未来趋势&#xff0c;直接看重点。 Flink Forward Asia 2024主会场有一场关于Flink2.0的演讲&#xff0c;很精彩&#xff0c;官方也发布了一些关于Flink2.0的展望和要解决的问题。 1.0时代和2.0时代避免…

智能座舱进阶-应用框架层-Jetpack主要组件

Jetpack的分类 1. DataBinding&#xff1a;以声明方式将可观察数据绑定到界面元素&#xff0c;通常和ViewModel配合使用。 2. Lifecycle&#xff1a;用于管理Activity和Fragment的生命周期&#xff0c;可帮助开发者生成更易于维护的轻量级代码。 3. LiveData: 在底层数据库更…

PowerMILL 客制化宏 - 变量

从PowerMILL2012起&#xff0c;命令起始支持变量。支持变量将使宏命令更加灵活和功能强大。可以对变量做一些运算而不依赖其它语言。 当前支持有变量类型为&#xff1a; INT&#xff1b; REAL&#xff1b; STRING&#xff1b; ENTITY&#xff1b; ARRAY LIST; OBJECT; 以下就…

arcgis for js实现地图截图、地图打印

地图截图 效果 实现 复制运行即可 要实现复杂的截图保存可以参考 官网案例 <!DOCTYPE html> <html lang"zn"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" />…

【BUG】记一次context canceled的报错

文章目录 案例分析gorm源码解读gin context 生命周期context什么时候cancel的什么时候context会被动cancel掉呢&#xff1f; 野生协程如何处理 案例分析 报错信息 {"L":"ERROR","T":"2024-12-17T11:11:33.0050800","file"…

信号槽【QT】

文章目录 对象树字符集信号槽QT坐标系信号与槽connect自定义槽自定义信号disconnect 对象树 #ifndef MYLABEL_H #define MYLABEL_H#include<QLabel> class MyLabel : public QLabel { public:// 构造函数使用带 QWidget* 版本的.// 确保对象能够加到对象树上MyLabel(QWi…

写SQL太麻烦?免费搭建 Text2SQL 应用,智能写 SQL | OceanBase AI 实践

自OceanBase 4.3.3版本推出以来&#xff0c;向量检索的能力受到了很多客户的关注&#xff0c;也纷纷表达希望OB能拓展更多 多模数据库大模型 的AI应用实践。 在上篇文章 &#x1f449; OceanBase LLM&#xff0c;免费构建你的专属 AI 助手 &#xff0c;我们介绍了如何去搭建一…

Dalsa线阵CCD相机使用开发手册

要使用Dalsa工业相机进行二次开发&#xff0c;看用户开发手册顺便做下笔记&#xff1a;&#xff08;欢迎加QQ讨论&#xff1a;77248031&#xff0c; 或QQ群&#xff1a;585068192&#xff09; 由于“本公主”用的.NET开发&#xff0c;软件支持只翻译了手册中.NET部分&#xff0…

C++特殊类设计(单例模式等)

目录 引言 1.请设计一个类&#xff0c;不能被拷贝 2. 请设计一个类&#xff0c;只能在堆上创建对象 为什么设置实例的方法为静态成员呢 3. 请设计一个类&#xff0c;只能在栈上创建对象 4. 请设计一个类&#xff0c;不能被继承 5. 请设计一个类&#xff0c;只能创建一个对…