项目

  • 毕业设计

    简单记录制作毕业设计的过程

    • 电赛

      简单记录参加电赛的过程。

      • 唛盟杯比赛详记

        这个项目是一个知识产权流程管理系统,主要功能包括客户下单、付款、订单查询、订单进度消息推送、文档查阅下载等。还介绍了安装git和nodejs,安装前需要先安装nvm,这是一个管理node的工具,然后安装node和vue,最后安装mvn。

        • 基于C++的职工管理系统

          描述文章内容

          2024年7月7日

          项目 的子部分

          毕业设计

          目录

          运行环境

          【ESP32学习】联网模块

          ESP8266-01S上传数据到OneNet

          参考视频

          1.进入OneNet创建产品和设备(具体步骤待补充)

          2.复制产品和设备的详细信息(备份)

          目的是为了方便使用AT指令以及代码编写。

          设备名称(clientlD) : test
          产品ID (username) : F1AVyfXw7l
          
          password: version=2018-10-31&res=products%2FF1AVyfXw71%2Fdevices%
          2Ftest&et=2810313321&method=md5&sign=EfJtPPxHwMX6iWWf3YksbA%3D%3D.urI:mqtts.heclouds.com
          
          端口号:1883
          
          订阅: $sys/F1AVyfXw7I/test/thing/property/post/reply发布: $sys/F1AVyfXw7l/test/thing/property/post
          
          设备密钥: cjFZSWRwUGNrazdRR2d6eTJ4Tm9YNXN5bTNuMGpMTIE=access key: vclTl19loOn7mcfdtT39l1eRfVffM6FfH8U/U3HhUQc=
          
          res:products/F1AVyfXw7l/devices/test
          et:时间戳

          收藏的一些资料

          OneNet云平台 OneNet云平台 OneNet云平台

          2.时间戳 时间戳 时间戳

          OneNet文档中心 OneNet文档中心 OneNet文档中心

          最终的操作结果

          结果 结果

          ESP32&&ESP8266-12E连接ONeNet参考资料

          代码部分唯一不同的是引用的库函数不同,

          ESP8266使用的是

          #include <ESP8266WiFi.h>

          ESP32使用的是

          #include <WiFi.h>

          在使用时需要注意引用的库函数,如果库函数不匹配,可能会导致编译失败,这部分的代码以放在附件中。

          • ESP-12EConnectONenet是ESP8266-12E连接ONeNet的代码;
          • ESP32ConnectONenet 是ESP8266-12E连接ONeNet的代码。

          **注意:**我提供的代码是以onejson格式上传的,而不是数据流的格式。在云平台创建产品时需要注意这点,当然代码里博主写的就是以数据流格式上传,如果你用原来博主写的,那么创建产品时就选数据流。


          上面的代码只能实现数据上传,并不能实现命令控制,下面就介绍如何实现命令控制。 主要修改的地方是callback函数,当拿到topic的时候要解析拿到一段ID,具体可以看官方的资料,里面有提到命令下发的操作。 alt text alt text

          ONeNet云平台报错: {“protocol”:“MQTT”,“offline_time”:“2024-07-15 16:01:49.977”,“offline_reason”:“CloseDueToProtoError”}怎么解决?

          这个错误是协议格式不对,也就是用的topic不正确,通过下面的案例对这个作说明。

          当我们在云平台下发命令的时候,在callback函数里使用Serial.println(topic);会打印订阅的topic,格式为:

          $sys/584938/onenet_mqtt_dev1/cmd/request/c5a7e27e-05bb-41eb-972b-abce424ade8e

          callback函数如下:

          /********************************************************************************
          函数:callback
          功能:接收ONeNet传回的信息
          返回值:无
          参数:
              topic是消息的主题,
              payload是消息的有效载荷(数据部分),
              length是payload的长度
          *********************************************************************************/
          void callback(char *topic, byte *payload, unsigned int length)
          {
            Serial.println("message rev:");
            Serial.println(topic);                    // 打印订阅的topic
            const char* lastSlash = strrchr(topic, '/');
            if (lastSlash != NULL) {
              
              const char* cmdId = lastSlash + 1;      // 命令ID位于最后一个'/'之后
              msgid = String(cmdId);                  //将命令ID复制给msgid,msgid定义为全局变量
            }
            Serial.println(msgid);                    // 打印拿到的ID
          
            if (strstr(topic, ONENET_GET_CAM_REQUEST)) //ONENET_GET_CAM_REQUEST="$sys/{产品ID}/{设备名}/cmd/request/"
            {
              DynamicJsonDocument doc(100);
              DeserializationError error = deserializeJson(doc, payload);
              if (error)
              {
                Serial.println("parse json failed");
                return;
              }
              JsonObject setAlinkMsgObj = doc.as<JsonObject>();
              serializeJsonPretty(setAlinkMsgObj, Serial);
              String cam = setAlinkMsgObj["cam"].as<String>();
              Serial.println("@"+cam);                //取出下发的命令并在前面加@
          
              char RESPONSE_TOPIC[100];
              sprintf(RESPONSE_TOPIC, "$sys/{产品ID}/{设备名}/cmd/response/%s",msgid.c_str());
              client.publish(RESPONSE_TOPIC, "OK");   // 向平台响应OK
            }else {
              Serial.println("GET_CAM faile!");
            }
          }

          这份代码是根据一位博主的代码改的(博主文章链接),取出后面的ID用如下代码即可

           const char* lastSlash = strrchr(topic, '/');
            if (lastSlash != NULL) {
              
              const char* cmdId = lastSlash + 1;      // 命令ID位于最后一个'/'之后
              msgid = String(cmdId);                  //将命令ID复制给msgid,msgid定义为全局变量
            }
            Serial.println(msgid);                    // 打印拿到的ID
          

          而这个时候我们需要响应这个命令用的是

          $sys/584938/onenet_mqtt_dev1/cmd/response/c5a7e27e-05bb-41eb-972b-abce424ade8e

          一个是request,一个是response,所以在拿到下发命令的topic后要取出后面跟着的ID,然后再重新拼接在$sys/584938/onenet_mqtt_dev1/cmd/response/后面,然后用这个拼接的topic发送响应数据给云平台。

          这个时候就能在云平台那看到类似如下信息:

          alt text alt text

          一般格式错误返回的错误码是15,原因就是格式有问题。使用如下的格式上传响应都可能有问题:

          1.$sys/584938/onenet_mqtt_dev1/cmd/response/

          2.$sys/584938/onenet_mqtt_dev1/cmd/response/+

          3.$sys/584938/onenet_mqtt_dev1/cmd/response/+/+

          以上内容仅供参考,如果你有更好的方法,请在评论区告诉我。

          APP连接ONeNet

          在B站上学习OKHTTP3,我把主要的核心代码放在附件里,分别是get和post的同步和异步请求,返回的结果和视频地址2里讲的一样。

          在编写HTTPS网络请求(post)时需要上传一些数据,这些数据有一定的上传格式,下面的网址汇总了上传格式content参数


          2024.8.19

          json数据解析步骤

          方法一:

          1 1 2 2

          方法二:

          使用第三方库Gson,在使用前需要创建一个Jsonbear,就是接收Jsons数据的格式。

          总结:

          在Android Studio中,如果你想要生成代码,例如getter和setter方法,可以使用快捷键Alt + Insert(Windows/Linux系统)或Command + N(Mac系统) 。这个快捷操作会引导你通过一个菜单来选择想要生成的代码类型,比如构造函数、toString方法、以及各种重写方法等。

          此外,如果你需要查看某个方法的参数信息,可以使用快捷键Ctrl + P(Windows/Linux系统)或Command + P(Mac系统),这将展示出当前方法的参数列表及其类型。

          STM32+电脑(模拟ESP32)+ESP8266通信

          STM32————电脑:串口1(PA 9 PA10),手机连接指定热点,连接成功后等待一段时间把摄像头的网址发给单片机,单片机通过串口2发给ESP8266.ESP8266再传到OneNet上。

          注意:在用ESP8266最小系统通信的时,需要给板子单独供电,否则通信可能失败,原因我想可能是因为电压不够。

          设备热点配网技术——WIFIManager

          这个配网技术是让开发板设置热点,然后让手机连接,连接好后配置要让开发板连接的WIFI和密码,这个技术叫设备热点技术。相比于以往的一键配网技术,我个人觉得这个设备热点配网技术成功率要要一些。

          缺点是这个技术不能用AT指令来实现,所以目前AT指令在不配合其他设备的情况下,通常都使用一键配网技术。

          ESP32和ESP8266使用设备热点配网技术的区别

          • 区别一:使用的WiFi库不同,ESP32使用WiFi.h,而ESP8266使用ESP8266WiFi.h
          • 区别二:使用的WiFiManager.h版本不同,ESP32使用2.0.17(目前这个版本是最新的),而ESP8266使用0.16或0.15 在使用ESP32时,请使用目前最新版的wifimanage库,而使用ESP8266的话请使用0.15或0.16版本的WIFIManager,如果ESP8266使用最新版的可能会报错。报什么错呢?

          报错:error: ‘wifi_country_t’ does not name a type const wifi_country_t WM_COUNTRY_US{“US”,1,11,WIFI_COUNTRY_POLICY_AUTO};

          完整报错如下:

          d:\Program Files (x86)\arduino\libraries\WiFiManager/wm_consts_en.h:162:7: error: 'wifi_country_t' does not name a type
           const wifi_country_t WM_COUNTRY_US{"US",1,11,WIFI_COUNTRY_POLICY_AUTO};
                 ^
          d:\Program Files (x86)\arduino\libraries\WiFiManager/wm_consts_en.h:163:7: error: 'wifi_country_t' does not name a type
           const wifi_country_t WM_COUNTRY_CN{"CN",1,13,WIFI_COUNTRY_POLICY_AUTO};
                 ^
          d:\Program Files (x86)\arduino\libraries\WiFiManager/wm_consts_en.h:164:7: error: 'wifi_country_t' does not name a type
           const wifi_country_t WM_COUNTRY_JP{"JP",1,14,WIFI_COUNTRY_POLICY_AUTO};
                 ^

          这个错误我目前没有解决,如果你解决了还请评论区或邮件(3256149770@qq.con)告诉我。

          通信过程中遇到的问题

          • keil5中一点击下载就闪退或点击debug中的setting就闪退。参照文章

          • Keil5编程之warning: #223-D: function “xxx“ declared implicitly.参考链接

          这一部分所用到的资料下载地址


          2024.8.12(更新).

          【实验】SPIFFS文件系统的使用

          参考地址

          【实验】TCP/UDP通信实验

          参考地址

          【STM32F4学习】摄像头

          参考资料

          STM32F4工程代码

          使用三个ADC通道要注意按顺序去初始化,否则可能会出现问题,运营我还不知道.

          【服务器】NGINX

          在Linux中搭建NGINX服务器

          TCP/IP协议

          alt text alt text alt text alt text alt text alt text alt text alt text alt text alt text alt text alt text


          [知识储备]

          在线自动生成nginx配置文件的网站

          • 可以自由选择所需的应用,生成nginx配置作为参考。
          • 根据你的业务需求,自动生成复杂的nginx配置文件,提供你作为参考,非常好用

          NGINX架构

          NGINX中master的工作原理

          alt text alt text

          nginx处理HTTP请求的过程

          过程 过程

          安装NGINX

          任务 任务

          安装路径:https://blog.csdn.net/u011715638/article/details/138670319

          server {
              listen 80;
              listen [::]:80;
          
              server_name example.com www.example.com;
          
              root /var/www/html;
              index index.html index.htm;
          
              location / {
                  try_files $uri $uri/ =404;
              }
          }

          上面是配置文件的内容,下面说明一下配置时的一些注意事项:

          • 把配置文件的example.com www.example.com换成172.0.0.1,这样方便在本地测试;
          • 在Ubuntu中可以下载ifconfig来查看Ubuntu的IP地址(ip addr也可以)
          • 每次修改配置文件记sudo systemctl reload nginx

          【服务器】Tomcat

          1.安装Tomcat要使用哪个版本的java?

          使用jdk8或更高版本,详细参考官方文档

          2.如何在Ubuntu安装Tomcat?

          • 安装jdk8或更高版本
          • 安装

          【服务器】ZLMedaikit流媒体服务器

          需要掌握的知识点: 知识点 知识点

          【第三方库】FFmpeg媒体流转码工具

          【深度学习】飞桨paddlepaddle实战


          2024.8.18

          1.从入门到实际运用,请参考B站up主视频:点击链接

          2.学完上面的视频后学习paddlepaddle的语音识别课程:

          3.接着参考STM32cudeXM.AI的相关知识,把模型转换成onnx格式然后在但【单片机上运行:

          3.1先用一个项目练练手:https://shequ.stmicroelectronics.cn/thread-632736-1-1.html

          意外收获,这是一个学习论坛:https://www.waveshare.net/study/portal.php?mod=list&catid=38


          2024.8.25

          参考文献:

          • 禹鑫鹏,贺庆,王世昕,.基于STM32CubeMX AI和NanoEdge AI的眼动信号分类效果对比研究【J】.传感器世界,2024,(04):6-10.

          2024.8.26

          X-CUDE—AI在线课程

          参考地址

          当模型验证结束后,需要修改以下内容: 修改文件 修改文件

          相关论文参考

          【机器学习】HMM模型(隐马尔科夫列)

          隐马尔科夫列模型定义: 定义 定义

          相关论文参考

          -【1.基于声学的婴儿哭声识别细分市场模型】(Baby Cry Recognition Based on Acoustic) -【2.使用隐马尔可夫模型自动分割婴儿哭闹信号】(Automatic segmentation of infant cry signals using hidden Markov models)

          导入库

          【音视频学习】

          H.264编码

          所谓视频编码就是指通过特定的压缩技术,将某个视频格式文件转换成另一种视频格式文件的方式。

          视频流传输中最重要的编解码标准有国际电联(TU-T,国际电信联盟)的H.261、H.263、H.264等,运动静止图像专家组(由ISO国际标准化组织与IEC国际电子委员会于1988年联合成立)的MPEG系列标准MPEG1、MPEG2、MPEG4 AVC等。

          其中ITU-TH.264/MPEG-4 AVC是ITU-T与ISO/IEC连手合作制订的新标准。ITU-T方面称之为H.264。但ISOIEC的则将这个新标准归纳于MPEG系列,称为MPEG-4 AVC。

          而H.265则被视为是ITU-TH.264/MPEG-4AVC标准的继任者,又称为高效率视频编码(High Efficiency VideoCoding,简称HEVC)

          h264 h264

          格式: h264 h264

          安卓音视频实战编程

          构建预览布局-使用SurfaceView或者TextureView

          打开相机- Camera.open

          设置参数- Camera.Parameters

          设置预览数据回调- PreviewCallback

          设置预览画布并启动- setPreviewTexture/startPreview

          释放相机- stopPreview/release

          YUV

          YUV主要应用于优化彩色视频信号的传输,与RGB相比,YUV只需要占用极少的频宽(RGB需要三个独立的视频信号同时传输)。YUV中Y代表明亮度,也称灰阶值;U与V表示的则是色度(色调和饱和度)也可以记作:YCbCr。如果只有Y数据,那么表示的图像就是黑白的。

          使用YUV的原因:

          使用YUV格式才能极大地去除冗余信息,人眼对亮点信息更敏感,对色度敏感度不高。也就是说,可以压缩UV数据,而人眼难以发现。所以压缩算法的第一步,往往先把RGB数据转换成YUV数据。对Y少压缩一点,对UV多压缩一点,以平衡图像效果和压缩率。这也是为什么编码选择使用YUV而不是RGB。

          推算公式: alt text alt text

          YUV是一组数据的压缩格式,其下还有因为排列不同而衍生的不同格式名称

          alt text alt text

          NV21转I420 alt text alt text

          MediaCode组件: alt text alt text

          RTMP协议所在所在的网络层结构

          alt text alt text

          【第三方库】LIBRTMP

          【工具】类似安卓虚拟机:Total Control

          采集音频数据工具

          alt text alt text

          视频采集工具—MediaProjection

          alt text alt text


          安卓P18


          2024.8.27

          方法一:

          安卓使用VLC库实内网播放ESPCam摄像头数据

          方法二:

          Mediacode编解码

          【安卓APP制作】

          1.安卓登录开发


          2024.8.21

          安卓的两种登录方式

          登录可以进去就强登录,或进去后在某个页面触发登录。

          对于第一个只需要在第一个页面做登录处理即可,而第二种需要Hook AMS + APT框架实现,具体看这篇博文:文章连接

          安卓中集成第三方库进行登录

          可以使用目前比较流行的第三方库实现:

          JustAuth开源组件JustAuth是一个整合了国内外数十家知名平台的OAuth登录的开源组件,它提供了丰富的OAuth平台支持,自定义State、自定义OAuth、自定义Http接口和自定义Scope等功能,极大地简化了开发者在第三方登录功能上的工作量。


          2024.8.23

          2.数据显示

          安卓显示ONENet提供的可视化链接

          参考链接


          2024.8.29

          硬件系统

          一、设备选型

          1.空气质量模块设备选型:

          (1)MQ135测量空气质量和氨气PPM的计算

          电赛


          以下笔记皆基于HAL库。

          新建STM32CubeMX工程步骤

          步骤 步骤

          创建工程中如果勾选MCO表示向外部输出时钟, 打钩 打钩 输出的是哪一个引脚可以查看,如下图。 PA8 PA8

          外部时钟设置 时钟 时钟

          工程设置 项目设置 项目设置

          如果Application Structure设置为Basic,那么会把下面的两个文件分开放 xx xx 否则项目会把两个文件放在一个文件夹里,如Core

          最后还需要设置Code Generator,如下: Code Generator Code Generator

          时钟

          1.初识时钟周期

          2.时钟树 时钟树 时钟树 时钟树1 时钟树1 时钟树2 时钟树2

          3.时钟配置步骤 步骤 步骤

          4.外设时钟使能和失能

          5.sys_stm32_cloc_init()函数

          • HAL_RCC_OscConfig()

            • HSE高速外设振荡器
            • LSE
            • RC振荡器,随着电压的变化而变化,所以需要一个检验值
            • PLL锁相环
          • HAL_RCC_ClockConfig() alt text alt text alt text alt text

            • 第一个参数
              • 结构体中的第一个参数里的HCLK是指AHB总线、PCLK1是指APB1总线、PCLK2是指APB2总线,这个配置表示要配那条总线的时钟。
            • 第二个形参 alt text alt text
              • F1系统时钟72MHz,如果使用72MHz访问闪存(Flash)那么是需要等待的,因为闪存的允许最大时钟频率是42MHz,所以需要等待,等待多少个周期需要下图 alt text alt text

          6.SYSTEN文件夹

          • sys文件夹 sys表 sys表

          NVIC介绍

          中断基本概念

          概念 概念 中断表 中断表

          中断向量表

          alt text alt text

          文件里的中断在哪里呢?

          在文件startup_stm32f10x_hd.s中这里列出内部中断,外部中断就在内部中断之下,这里不再列出。

          __Vectors       DCD     __initial_sp               ; Top of Stack
                          DCD     Reset_Handler              ; Reset Handler
                          DCD     NMI_Handler                ; NMI Handler
                          DCD     HardFault_Handler          ; Hard Fault Handler
                          DCD     MemManage_Handler          ; MPU Fault Handler
                          DCD     BusFault_Handler           ; Bus Fault Handler
                          DCD     UsageFault_Handler         ; Usage Fault Handler
                          DCD     0                          ; Reserved
                          DCD     0                          ; Reserved
                          DCD     0                          ; Reserved
                          DCD     0                          ; Reserved
                          DCD     SVC_Handler                ; SVCall Handler
                          DCD     DebugMon_Handler           ; Debug Monitor Handler
                          DCD     0                          ; Reserved
                          DCD     PendSV_Handler             ; PendSV Handler
                          DCD     SysTick_Handler            ; SysTick Handler

          中断寄存器

          中断寄存器 中断寄存器

          中断原理

          原理 原理

          中断优先级

          优先级 优先级 优先级 优先级 响应优先级又叫子优先级

          中断的抢占优先级由AICR寄存器的后三位控制,而响应优先级由IPRx寄存器的后四位控制,最终分配结果参考下面的表格 表 表

          中断一般只设置一次,设置多次可能会导致中断紊乱,一般以最后一次的中断设置为准,详细说明参考手册4.4.5

          中断执行的顺序:抢占优先级(先执行数值小的)————响应优先级(先执行数值小的)————自然优先级(先执行数值大的)

          NVIC 的使用


          以下笔记皆基于标准库。

          标准库中额中断

          alt text alt text

          EXTI(Extern Interrupt)外部中断

          EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序

          简而言之,gpio电平发生变化时,申请外部中断。

          • 支持的触发方式:上升沿/下降沿/双边沿/软件触发(引脚没有发生变化,执行一段代码就申请中断
          • 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(如PA1,PB1,PC1不能同时触发中断
          • 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
          • 触发响应方式:中断响应/事件响应(事件响应不会触发中断。而是触发别的外设操作。属于外设之间的联合工作

          AFIO复用IO口

          主要用于引脚复用功能的选择和重定义

          在STM32中,AFIO主要完成两个任务:

          • 复用功能引脚重映射
          • 中断引脚选择 GPIO口中断申请图 GPIO口中断申请图

          EXTI实现代码

          这段代码的中断端口是GPIOB_Pin14,所以需要将相关的传感器接到PB14这个端口

          void CountSensor_Init(){
          	//配置RCC
          	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
          	
          	//AFIO、
          	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
          	
          	//EXTI、一直打开着不用配置打开时钟
          	//NVIC  一直打开着不用配置打开时钟
          	
          	GPIO_InitTypeDef GPIO_InitStructure;
          	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
          	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;
          	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
          	GPIO_Init(GPIOB,&GPIO_InitStructure);
          	
          	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
          	
          	//EXTI
          	EXTI_InitTypeDef EXTI_InitStructure;
          	EXTI_InitStructure.EXTI_Line=EXTI_Line14;
          	EXTI_InitStructure.EXTI_LineCmd=ENABLE;
          	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
          	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
          	
          	EXTI_Init(&EXTI_InitStructure);
          	
          	//NVIC
          	
          	NVIC_InitTypeDef NVIC_InitStructure;
          	NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;
          	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
          	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
          	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
          	NVIC_Init(&NVIC_InitStructure);
          }
          void EXTI15_10_IRQHandler(void){//中断函数
          	if(EXTI_GetITStatus(EXTI_Line14)== SET){
          		EXTI_ClearITPendingBit(EXTI_Line14);
          	}
          }

          定时器

          TIM(Timer)定时器

          定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断

          16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

          使用内部时钟源实现中断(标准库中)

          在编写代码前先看看定时器的实现原理图

          • 基本定时器 基本定时器 基本定时器
          • 通用定时器 通用定时器 通用定时器
          • 高级定时器(红色框出来的部分是高级定时器独有的) 高级定时器 高级定时器

          此处主要参考通用计时器的结构图,下面是基于内部时钟RCC的定时器。

          stm32f103xxx_rcc.c 文件对 RCC_APB1PeriphClockCmd 这样的介绍:

          /**
            * @brief  Enables or disables the High Speed APB (APB2) peripheral clock.
            * @param  RCC_APB2Periph: specifies the APB2 peripheral to gates its clock.
            *   This parameter can be any combination of the following values:
            *     @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, 
            ……
            ……
            ……+
            */

          specifies the APB2 peripheral to gates its clock.即:指定APB 2外围设备到其时钟门。而这里的时钟门代码里选择了TIM2。

          void Timer_Init(void){
          	//1.RCC 
          	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
          	
          	//2.选择时基单元时钟源,
          	TIM_InternalClockConfig(TIM2);
          	
          	//3.时基单元
          	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
          	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;		//时钟分频,这里是1分频
          	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;	//计数方式
          	TIM_TimeBaseInitStructure.TIM_Period=10000-1;					//自动重装值,计数达到这个值后触发中断,然后重新开始计数
          	TIM_TimeBaseInitStructure.TIM_Prescaler=7200 -1;				//预分频,计数频率,
          																	//假设计数为1s则自动重装值可设置为10000,预分频可设置为7200,公式为秒数=27MHz/PSC/ARR
          	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;				//重复计数数器高级定时器才有,这里设置为0
          	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);				//时基单元设置
          	
          	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
          	
          	//4.中断输出控制 
          	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);					//抢占优先级,0-3, 响应优先级0-3,一个工程只设置一次
          	
          	//5.NVIC
          	NVIC_InitTypeDef  NVIC_InitStructure;
          	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
          	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
          	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
          	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
          	NVIC_Init(&NVIC_InitStructure);
          	
          	TIM_Cmd(TIM2,ENABLE);
          }
          
          void TIM2_IRQHandler(void){
          	 if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){
          		Num++;
          		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
          	 }
          }

          实现输出比较

          OC(Output Compare)输出比较

          • 输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形

          • 每个高级定时器和通用定时器都拥有4个输出比较通道

          • 高级定时器的前3个通道额外拥有死区生成和互补输出的功能

          • OC:输出比较

          • IC:输入比较

          • CC:输入/输出比较

          PWM波形

          • PWM(Pulse Width Modulation)脉冲宽度调制,广泛应用于各种电子和电气系统中,用于控制功率转换、电机速度、信号传输等
          • 在具有^1中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域 ^1
          • PWM参数:
            • 频率 = 1 / TS
            • 占空比 = TON / TS
            • 分辨率 = 占空比变化步距 PWM PWM 一些电平变化图可以等效为图中的虚线。 信息 信息

          通用定时器结构图 通用定时器结构图 上图框出来的部分具体如下图: 捕获/比较通道的输出部分 捕获/比较通道的输出部分

          在信号通过比较后输出到CC1P,然后再输出到输出使用电路,输出使用电路的OC1可以查看引脚定义表

          在上图中需要注意的一些引文缩写:

          • REF 是reference的缩写。意思是 参考信号

          输出比较的设置

          设置的函数为

          模式 模式

          PWM基本结构

          配置PWM的时候可以参考下面的图, 结构图 结构图 图中的参考坐标系图红色线表示CCR的值,蓝色线表示CNT的值,黄色线表示自动重装载值即ARR。 了解了这点后我们再看看PWM的是如何计算的,如下公式

          PWM频率: [ \text{Freq} = \frac{CKPSC}{(PSC + 1) \times (ARR + 1)} ] 可以看出PWM的频率等于CNT的更新频率

          PWM占空比: [ \text{Duty} = \frac{CCR}{(ARR + 1)} ]

          PWM分辨率: [ \text{Reso} = \frac{1}{(ARR + 1)} ]

          代码编写

          高级定时器 高级定时器

          附件

          唛盟杯比赛详记

          比赛题目

          一、项目主题

          知识产权流程管理系统

          二、项目描述

          该系统用于知识产权项目申报的全过程跟踪管理,其功能包括:

          • 客户下单、
          • 提交申请、
          • 申请反馈、
          • 订单查询、
          • 订单详情、
          • 文档查阅下载等六大部分。

          该系统分为客户端和管理端两个子系统。客户端对客户开放,主要功能包括:

          • 客户下单、
          • 付款、
          • 订单查询、
          • 订单进度消息推送(推送到公众号、网站内部消息)、
          • 文档查阅下载等。

          管理端对管理人员开放,不对客户开放,负责订单管理、文档上传(批量上传)等。

          三、项目需求

          (一)、用户端

          个人中心模块:

          1、订单查询

          分为我的订单(综合查询)及商标、专利、版权、科技项目四大子类查询,订单查询按全部业务下单时间顺列。

          订单查询功能大致效果图: 订单查询 订单查询

          2、订单详细

          可根据订单详情按钮跳转到对应的订单详情。 订单详细 订单详细 订单详情页展示图: 订单详情页 订单详情页

          (二)、管理端

          订单管理模块:

          1、订单查询

          查询订单,展示列表。

          2、订单详细

          在订单列表中点击某个订单,进入订单详情。

          3、文档上传

          在订单详情页,点击文档上传,选中本地文件,上传本地文件到服务器,支持批量、单文件上传。

          注册唛盟账号

          队长和队员注册账号

          一、扫描一级部门的二维码

          这一部分只建议参加了比赛的网友查看。本次是组队参加,一队3-5人,队长要在他们的官方网页端注册号团队(二级部门),方便后面的项目管理,然后再通过一级部门添加自己的团队,首先是扫描官方给的二维码,扫这个二维码后注册就会在一级部门下,这也是要注册在一级部门之下的办法,

          二、授权登录成功后,跳转到提示页面,点击“首页”

          alt text alt text alt text alt text

          三、进入低代码平台首页,按照提示,点击“账户明细”

          四、按照提示,输入姓名,并提交

          五、点击唛盟低代码平台官网,选择微信扫码登录

          唛盟低代码平台官网:https://maimengcloud.com/lcode/m1/#/login

          六、看到用户名称是自己的即报名成功

          队长注册团队

          1要求

          1.构建每个参赛团队的独立部门
          2.将参赛队员拉入部门
          3.给每个参赛队员设置好岗位
          

          2相关环境

          唛盟账户系统

          3操作步骤

          3.1 在一级部门,即总部下建立子部门

          进入【部门管理】,点击【添加下级】

          3.2输入部门信息

          • 部门名称要求:以参赛团队名称作为部门名称
          • 上级领导选:一级部门
          • 部门负责人:选参赛团队组长
          • 部门性质:技术部门
          • 协作类型:内部组织

          其它字段不填

          (如果你不是第一届比赛的同学,那这个不需要关注,可能每年都不一样)

          3.3保存提交

          3.4把岗位挂接到部门上

          【部门管理】中找到刚才建立的部门,点击【岗位】按钮弹出以下岗位选择框:

          选择左边需要绑定的岗位,移动到右边,点击【提交】按钮即完成岗位绑定(如果没有显示岗位,多次刷新,或者退出重新登录,这个知识第一届遇到此问题解决的方案)

          3.5把用户拉入部门

          打开【用户管理】菜单,先把自己的搜索出来,如果不是通过一级部门的二维码注册的,会搜不到的哦。找到需要拉入的队友,点击【部门/岗位】

          弹出以下部门岗位选择界面:

          在弹出的界面中左边选中归属的部门,右边选中岗位,再【提交】即可

          这部分本来也不想记了,毕竟没什么用了,可能下一届又不一样了,但想想自己记一下当作回忆也可以。

          安装git和nodejs

          在安装前我们通过这个链接https://gitee.com/maimengcloud/mdp-lcode-ui-web去看看这个项目的介绍,

          alt text alt text

          大概浏览一下,然后往下拉根据导航直接跳到 快速开始

          alt text alt text

          这里并不是说其他的不重要,只是说对于一个项目而言如果不是涉及本地启动的话不必花费时间去纠结,等你能让项目在本地启动后再回来看看也不迟,当然这也有弊端,可能会错一些细节哈。

          在快速开始的地方提到:


          注意:该项目使用 element-ui@2.3.0+ 版本,所以最低兼容 vue@2.5.0+ nodejs版本v14.16.1


          所以我们安装的时候要注意版本的问题尽量不要选太高的,如果你已经安装了一部分或全部,而且版本很高或很低,这样也不必太担心(大不了删掉重新安装,哈哈),下面是我的一些经验,希望帮到你。

          git的安装

          这部分没有太多可讲的,网上有很多的教程,讲的也很好,这里需要你记住的一条命令是:

           git clone <远程仓库地址>  <本地存放目录>

          这里举个例子,就比如这次比赛用到的这个项目,我们建一个文件夹装要下载的项目,比如是mdp文件夹,然后右键,win11选显示更多,在弹出的选项选择: git bash here

          alt text alt text

          然后复制下面的内容回车就可以了。


          git clone https://gitee.com/maimengcloud/mdp-lcode-ui-web.git mdp-lcode-ui-web/


          但是有的时候也会因为网速的问题,导致无法下载,比如回车后报错,如下:


          alt text alt text Cloning into ‘mdp-lcode-ui-web’… fatal: unable to access ‘https://gitee.com/maimengcloud/mdp-lcode-ui-web.git/': Failed to connect to 127.0.0.1 port 26501 after 2017 ms: Couldn’t connect to server


          简单翻译理解就是这个远程仓库无法访问,其实这也是使用让我比较烦恼的问题,这里的原因我可能没办法给你讲清楚(你有时间可以去琢磨哈),但有个软件可以分享给你,帮你解决这个问题,即 watt toolkit ,而且这个软件在微软软件商城就有。

          alt text alt text

          下载后选github,然后点击一键加速。

          alt text alt text alt text alt text

          然我们再回到git base here,再次输入刚才的命令回车,

          alt text alt text

          发现刚才的代码仓库已经下载到本地了。

          alt text alt text

          如果你使用上面的软件还是无法下载远程的仓库,那你需要根据报错信息在网上查考相关的信息,这里提供上面报错的可能解决方案,只是针对上面的问题哦!参照文章

          安装node

          网上可能称为nodejs都是一个工具,这个工具的简介还请参照官方,这里我们有做过多的了解哦。在安装node之前我们要先安装nvm。这是一个管理node的工具,这里安装的主要目的是 升高或降低node版本,简单就是想安装哪个版本(已发布且可下载的版本)的可以用这个工具来实现。

          nvm是否要安装取决于你的情况,除了nvm,此次项目还需要安装工具node,vue,mvn。

          方案一:为防止版本过高过低影响运行,以后可能还需要,最好是下载nvm管理node,然后也要了解升级或降低vue版本的方法,最后下载mvn;

          方案二:自己不管安装还是没安装,直接安装node,vue,mvn,本次项目用到的node,vue,mvn都在package.json文件里定义写好了,运行后会根据里面去下载,似乎对项目没太大影响(因为我工具处于最高版本,或是相对低一点的版本,使用相同的命令,报错都是一个,基于这点你可以尝试。)

          方案一

          安装nvm

          参照文档 关于文章的几点说明:

          • 登录github的时候记得开watt toolket;
          • 如果nvm下载不了要刷新或者退出重新进入,多试几次;
          • 基于本次的项目我们只看文章的一——四,五往后有时间就了解一下就好了;

          里面的 三、配置路径和下载源我们需要把淘宝镜像改为如下:

              node_mirror: https://cdn.npmmirror.com/binaries/node/
              npm_mirror: https://cdn.npmmirror.com/binaries/npm/

          这是因为淘宝的镜像换了。

          接着参照文章,目的是node还要做一些配置,主要node的环境变量设置,还有目录node_cache和目录node_global的一些设置。从文章的下图开始看,关于node的下载就不看了。 alt text alt text

          注意在设置目录node_cache和目录node_global时,


          npm config set prefix “<自己的文件目录>\node_global” npm config set cache “<自己的文件目录>\node_cache”


          这两个路径时要记得把它改成自己安装的路径:

          安装指定的vue版本

          情况一:你没安装过vue

          首先你要先安装好node,然后用node的npm工具安装vue。

          使用命令查看可安装版本:


          要查看的vue版本是1.x和2.x的


              npm view vue-cli versions --json

          要查看的vue版本是3.x的


              npm view @vue/cli versions --json

          安装vue指定的版本


          安装的vue版本是1.x和2.x的


              npm install -g @vue/cli@版本号
          
              yarn global add @vue/cli@版本号

          要查看的vue版本是3.x的


              npm install -g vue-cli@版本号
          
              yarn global add vue-cli@版本号

          第一次安装要去配环境变量。

          情况二:你安装过vue

          卸载已经安装的Vue Cli


          卸载 3.x 或 4.x 版本的Vue Cli


          npm uninstall -g @vue/cli
          
          yarn global remove @vue/cli

          卸载 1.x 或 2.x 版本的Vue Cli


          npm uninstall vue-cli -g
          
          yarn global remove vue-cli

          重复情况一的步骤,然后打开cmd,输入:

          vue -V

          显示安装的版本则说明安装完成。

          原文章链接

          安装mvn(Maven)工具

          直接参照这个文章就好了。

          方案二

          安装node和vue

          参照文章,同样的里面涉及到镜像文件的设置要注意换掉:

              node_mirror: https://cdn.npmmirror.com/binaries/node/
              npm_mirror: https://cdn.npmmirror.com/binaries/npm/

          除此之外在设置目录node_cache和目录node_global这两个路径时要记得把它改成自己安装的路径:


          npm config set prefix “<自己的文件目录>\node_global” npm config set cache “<自己的文件目录>\node_cache”


          安装mvn

          直接参照这个文章就好了。

          至此工具就就安装完了,接着我们就要去启动项目了。

          本地启动项目

          本次使用的系统是win11和code应用程序,如果你用的不是这个系统和应用程序情况可能不一样哈。

          根据项目的简介开始运行项目

          这里我使用的是code应用程序(你如果用的是别的应用程序另说咯),点击文件夹用code打开后,根据项目文档执行命令:

              npm install

          结果…………………………报错啦(我就知道没这么简单)!!!错误如下:

          alt text alt text

          上面的报错意思是:在尝试安装项目依赖时,npm遇到了依赖冲突问题。具体来说,babel-loader需要webpack的版本在2、3或4之间,但是你的项目中已经安装了webpack的5.90.1版本。这导致了peer依赖冲突,因为babel-loader期望的webpack版本与实际安装的版本不匹配,

          解决办法如下:

          1.查看自己的node版本;

          ```
          node -v
          ```
          

          alt text alt text

          2.修改"package.json",在"devDependencies"中增加"node": “^21.6.1"依赖;

          ```
          "node":"^21.6.1",
          ```
          

          alt text alt text

          3.npm install -s node-sass@4.14.1;

          4.npm install –save –legacy-peer-deps;

          结果……………………报错啦!!!!!
          

          alt text alt text

          修改"package.json",原来的改为"node": "^13.14.0"依赖;
          
          再次运行结果没报错。
          

          5.npm install –registry=https://cdn.npmmirror.com

          接着我们运行如下代码 `npm install --registry=https://cdn.npmmirror.com`
          

          6.npm run dev

          运行npm run dev,结果还是报错了……
          

          alt text alt text

          这里的错误说vue-template-compiler的版本为2.7.16,那我们就去"package.json"找到vue,
          
          原来的vue版本:
          

          alt text alt text

          把上面的修改为`"vue":"^2.7.16",`
          
          这时候要重新运行 `npm install --save --legacy-peer-deps`,然后再次运行 `npm run dev`;
          
          结果还是报错,如下:
          

          alt text alt text

          大概意思是没有找到ajv这个包,那么我们在"package.json"里的 **"dependencies"** 添加`"ajv": "^8.12.0",`
          然后重复`npm install --save --legacy-peer-deps`, `npm run dev`操作就可以了。
          
          显示如下:
          

          alt text alt text

          我们再看看页面,页面如下:
          

          alt text alt text

          到这里说明没问题了。
          

          从上面我们可以了解到一点,自己安装的版本似乎并不会影响项目的运行,所以选择方案二来进行安装似乎更简单,但从往长远的看方案一更更好,他可以随时换版本,更为方便。

          原文章链接

          基于C++的职工管理系统

          项目需求

          alt text alt text

          项目功能

          alt text alt text

          效果图

          界面

          alt text alt text

          增加

          alt text alt text

          显示

          alt text alt text

          ……

          开始项目开发

          1.创建管理类

          管理类功能

          管理类负责内容 alt text alt text

          实现代码

          添加头文件:

          #pragma once //防止头文件重复包含
          #include <iostream>
          
          using namespace std;
          
          class WorkerManager
          {
          public:
          	WorkerManager();
          	~WorkerManager();
          };

          添加源文件:

          #include "WorkerManager.h"
          
          WorkerManager::WorkerManager()
          {
          }
          
          WorkerManager::~WorkerManager()
          {
          }

          2.添加菜单功能

          添加菜单显示函数

          class WorkerManager
          {
          public:
          	WorkerManager();
            void Show_Menu();
          	~WorkerManager();
          };

          显示菜单界面

          void WorkerManager::Show_Menu()
          {
          	cout << "*************************" << endl;
            cout << "***欢迎使用职工管理系统******" << endl;
            cout << "*****  0-退出管理程序  *****" << endl;
          	cout << "*****  1-增加职工信息  *****" << endl;
          	cout << "*****  2-显示职工信息  *****" << endl;
          	cout << "*****  3-删除职工信息  *****" << endl;
          	cout << "*****  4-修改职工信息  *****" << endl;
          	cout << "*****  5-查找职工信息  *****" << endl;
          	cout << "*****  6-按照编号排序  *****" << endl;
            cout << "*****  7-清空所有文档  *****" << endl;
          	cout << "*************************" << endl;
          }

          测试菜单界面

          #include <iostream>
          using namespace std;
          #include "workerManager.h"
          
          int main()
          {
          	WorkerManager wm;
          	wm.Show_Menu();
          	system("pause");
          	return 0;
          }

          3.退出功能

          添加退出功能

          class WorkerManager
          {
          public:
          	WorkerManager();
            void Show_Menu();
            void ExitSystem();
          	~WorkerManager();
          };

          实现退出功能

          void WorkerManager::ExitSystem()
          {
          	cout << "欢迎下次使用" << endl;
          	system("pause");
          	exit(0);
          }

          测试退出功能

          int main()
          {
          	WorkerManager wm;
          	int choice = 0;
          	while (true)
          	{
          		wm.Show_Menu(); //显示菜单
          		cout << "请输入您的选择: " << endl;
          		cin >> choice;
          		switch (choice)
          		{
          		case 0: //退出系统
          			wm.ExitSystem();
          			break;
          		case 1: //增加职工
          			break;
          		case 2: //显示职工
          			break;
          		case 3: //删除职工
          			break;
          		case 4: //修改职工
          			break;
          		case 5: //查找职工
          			break;
          		case 6: //按照编号排序
          			break;
          		case 7: //清空文件
          			break;
          		default:
          			cout << "输入有误,请重新输入" << endl;
          			system("pause");
          			system("cls"); //清屏操作
          			break;
          		}
          	}
          	system("pause");
          	return 0;
          }

          4.编写职工类

          将三个职工抽象为一个类,利用多态管理不同职工; 职工属性行为: 职工行为:

          写worker.h头文件

          #pragma once
          #include <iostream>
          using namespace std;
          #include <string>
          //职工抽象类
          class Worker
          {
          public:
          	//显示个人性息
          	virtual void showInfo() = 0;
          	//获取岗位个人性息
          	virtual string GetDeptName() = 0;
          	//职工编号
          	int m_Id;
          	//职工姓名
          	string m_Name;
          	//部门编号
          	int m_DeptId;
          
          };

          写employee.h、manager.h、boss.h头文件

          #pragma once
          #include <iostream>
          using namespace std;
          #include <string>
          #include "worker.h"
          //职工类
          class Employee:public Worker
          {
          public:
          	Employee(int id,string name,int did);
          	//显示个人性息
          	void showInfo();
          	//获取岗位个人性息
          	string GetDeptName();
          };

          写employee.cpp等源文件

          #include "employee.h"//不同岗位此处不同
          
          Employee::Employee(int id, string name, int did)
          {
          	this->m_Id = id;
          	this->m_Name = name;
          	this->m_DeptId = did;
          }
          //显示个人性息
          void Employee::showInfo()
          {
          	cout << "职工编号:" << this->m_Id
          		<< "\t职工姓名:" << this->m_Name
          		<< "\t岗位:" << this->GetDeptName()
          		<< "\t岗位职责:完成经理交给的任务" << endl;
          
          }
          //获取岗位个人性息
          string Employee::GetDeptName()
          {
          	return string("员工");//不同岗位此处不同
          }

          在主函数中测试

          Worker* worker = new Employee(20, "张三", 3);
          worker->showInfo();
          delete worker;
          
          worker = new Manager(10, "李四", 2);
          worker->showInfo();
          delete worker;
          
          worker = new Boss(1, "王五", 1);
          worker->showInfo();
          delete worker;

          5.添加职工

          添加成员函数

          class WorkerManager
          {
          public:
          	//构造函数
          	WorkerManager();
          	//展示菜单
          	void Show_Menu();
          	//退出系统
          	void ExitSystem();
          
          	//添加职工函数
          	void Add_Emp();
          
          	//记录数组中有多少个职工人数
          	int m_EmpNum;
          
          	//职工数组指针,使用双重指针管理worker数组指针,数组类型是*Worker
          	Worker** m_EmpArray;
          	~WorkerManager();
          };

          实现添加职工

          void WorkerManager::Add_Emp()
          {
          	//提示
          	cout << "请输入要添加的人数" << endl;
          
          	//保存输入的人数
          	int addNum = 0;
          	cin >> addNum;
          	if (addNum > 0)
          	{
          		//添加
          		//计算添加空间大小
          		int newSize = this->m_EmpNum + addNum;
          
          		//开辟空间,使用二级指针指向一个指针数组,这个指针数组会因为多态而指向不同职工
          		Worker **newSpace = new Worker *[newSize];
          
          		//如果原来有数据要复制到新空间下
          		if (this->m_EmpArray != NULL)
          		{
          			for (int i = 0; i < this->m_EmpNum; i++)
          			{
          				newSpace[i] = this->m_EmpArray[i];
          			}
          		}
          
          		//添加新成员
          		for (int i = 0; i < addNum; i++)
          		{
          			int id;
          			string name;
          			int dSelect;
          
          			cout << "输入第"<<i+1<<"个职工的编号" << endl;
          			cin >> id;
          
          			cout << "输入第" << i + 1 << "个职工的姓名" << endl;
          			cin >> name;
          
          			cout << "选择岗位职称" << endl;
          			cout << "1:表示普通员工" << endl;
          			cout << "2:表示经理" << endl;
          			cout << "3:表示老板" << endl;
          			cin >> dSelect;
          
          			Worker* worker = NULL;
          			switch (dSelect)
          			{
          			case 1:
          				worker = new Employee(id, name, 1);
          				break;
          			case 2:
          				worker = new Manager(id, name, 2);
          				break;
          			case 3:
          				worker = new Boss(id, name, 3);
          				break;
          			default:
          				break;
          			}
          
          			//将职工信息添加到数组中
          			newSpace[this->m_EmpNum + i] = worker;
          		}
          
          		//释放堆区数据
          		delete[] m_EmpArray;
          
          		//更新双重指针的指向
          		this->m_EmpArray = newSpace;
          
          		//更新人数
          		this->m_EmpNum = newSize;
          
          		//文件不为空
          		this——>m_FileIsEmpty = false;
          
          		//成功添加后保存到文件中
          		Save();
          
          		//提示添加成功
          		cout << "添加成功,添加了"<<addNum<<"人" << endl;
          		system("pause");
          		system("cls");
          	}
          	else
          	{
          		cout << "输入错误" << endl;
          	}
          
          }

          测试添加职工

          int main()
          {
          	WorkerManager wm;
          	int choice = 0;//用于存储用户选择
          	
          	while (true)
          	{
          		wm.Show_Menu(); //显示菜单
          		cout << "请输入您的选择: " << endl;
          		cin >> choice;
          		switch (choice)
          		{
          		case 0: //退出系统
          			wm.ExitSystem();
          			break;
          		case 1: //增加职工
          			wm.Add_Emp();
          			break;
          		case 2: //显示职工
          			break;
          		case 3: //删除职工
          			break;
          		case 4: //修改职工
          			break;
          		case 5: //查找职工
          			break;
          		case 6: //按照编号排序
          			break;
          		case 7: //清空文件
          			break;
          		default:
          			cout << "输入有误,请重新输入" << endl;
          			system("pause");
          			system("cls"); //清屏操作
          			break;
          		}
          	}
          	system("pause");
          	return 0;
          }

          6.职工数据保存

          因程序运行结束后添加的数据会被清除,再次打开创建的数据就没有了,所以需要将数据保存到文件中,下次打开时可以读取文件中的数据。

          创建保存数据函数

          在WorkerManager.h中添加保存数据函数Save();并在WorkerManager.cpp中实现,实现代码如下:

          void WorkerManager::Save()
          {
          	//创建写入文件对象
          	ofstream ofs;
          	//打开文件
          	ofs.open(FILENAME, ios::out);
          	//开始写入
          	for (int i = 0; i < this->m_EmpNum; i++)
          	{
          		ofs << this->m_EmpArray[i]->m_Id << " "
          			<< this->m_EmpArray[i]->m_Name << " "
          			<< this->m_EmpArray[i]->m_DeptId << endl;
          	}
          	//关闭文件
          	ofs.close();
          }

          接着在WorkerManager.cpp中Add_Emp()函数中添加保存数据函数,详细查看代码

          读取文件中的数据

          在读取文件的过程中,文件可能有一下三种情况:

          • 文件不存在
          • 文件存在,但是文件中无数据
          • 文件存在,并且文件中有已写入的数据

          文件为空的情况

          在worrkerManager.h中添加一个bool类型的m_FileIsEmpty成员变量用于记录文件是否为空。

          在WorkerManager.cpp中构造函数中添加代码,当文件为空时如何操作:

          WorkerManager::WorkerManager()
          {
          	//1,判断文件是否存在
          	ifstream ifs;
          	ifs.open(FILENAME, ios::in);
          	if (!ifs.is_open())
          	{
          		cout << "文件不存在" << endl;
          		//初始化属性
          		this->m_EmpArray = NULL;
          		this->m_EmpNum = 0;
          		this->m_FileIsEmpty = true;
          
          		ifs.close();
          		return;
          	}
          	//文件存在,但是没有记录
          	char ch;
          	ifs >> ch;
          	if (ifs.eof())
          	{
          		//文件为空
          		cout << "文件为空" << endl;
          		//初始化属性
          		this->m_EmpArray = NULL;
          		this->m_EmpNum = 0;
          		this->m_FileIsEmpty = true;
          
          		ifs.close();
          		return;
          	}
          }

          因为每次添加人数后文件就不是空的,所以需要在函数Add_Emp()中添加代码,当文件不为空时,将m_FileIsEmpty = false,详细查看代码

          文件不为空的情况

          在为文件中WorkerManager.h类添加如下成员函数:

          //统计文件中的人数
          int Get_EmpNUm();
          
          //如果文件存在且有记录,读出数据并存储在数组中,初始化员工人数
          void initEmp();

          在WorkerManager.cpp中实现Get_EmpNUm()、initEmp()函数,实现代码如下:

          int WorkerManager::Get_EmpNUm()//用于记录人数个数
          {
          	ifstream ifs;
          	ifs.open(FILENAME, ios::in);
          
          	int id;
          	string name;
          	int did;
          
          	int empNum = 0;
          
          	while (ifs >> id && ifs >> name && ifs >> did)
          	{
          		empNum++;
          	}
          	return empNum;
          }
          void WorkerManager::initEmp()
          {
          	//打开文件
          	ifstream ifs;
          	ifs.open(FILENAME, ios::in);
          
          	int id;
          	string name;
          	int dId;
          
          	int index = 0;
          
          	while (ifs >> id && ifs >> name && ifs >> dId)
          	{
          		Worker* worker = NULL;
          		if (dId == 1)
          		{
          			worker = new Employee(id, name, dId);
          		}
          		else if (dId == 2)
          		{
          			worker = new Manager(id, name, dId);
          		}
          		else
          		{
          			worker = new Boss(id, name, dId);
          		}
          		this->m_EmpArray[index] = worker;//将数据存储在指针数组中
          		index++;//记录人数
          	}
          	//关闭文件
          	ifs.clear();
          }

          此时回到构造函数中添加如下代码

          //文件存在,并有记录
          int num = this->Get_EmpNUm();
          cout << "职工人数为:" << num<<endl;
          this->m_EmpNum = num;
          //开辟空间
          this->m_EmpArray = new Worker * [this->m_EmpNum];
          //将数据存到数组中
          this->initEmp();
          
          //测试代码,可以删除
          for (int i = 0; i < this->m_EmpNum; i++)
          {
          	cout << "职工编号" << this->m_EmpArray[i]->m_Id << " "
          		<< "职工姓名:" << this->m_EmpArray[i]->m_Name << " "
          		<< "职工职称:" << this->m_EmpArray[i]->GetDeptName() << endl;
          
          }

          7.显示职工

          在WorkerManager.h中添加如下成员函数:

          //显示职工
          void Show_Emp();

          在WorkerManager.cpp中实现Show_Emp()函数,实现代码如下:

          //判断文件是否为空
          if (this->m_FileIsEmpty)
          {
          	cout << "文件不存在或为空" << endl;
          
          }
          else
          {
          	for (int i = 0; i < this->m_EmpNum; i++)
          	{
          		this->m_EmpArray[i]->showInfo();
          	}
          	system("pause");
          	system("cls");
          }

          测试显示职工

          在main()函数中添加如下代码:

          case 2: //显示职工
          	wm.Show_Emp();
          	break;

          8.删除职工

          实现思路

          • 先判断文件是否存在
          • 如果文件不存在,则提示文件不存在,删除失败。
          • 如果文件存在,则提示用户输入要删除的职工编号
          • 在文件中查找该编号的职工,如果找到了,则进行删除,如果没找到,则提示用户输入的职工编号错误

          实现删除职工

          在WorkerManager.h中添加如下成员函数:

          // 删除职工
          void Del_Emp();
          //判断职工是否存在
          int IsExist(int id);

          在WorkerManager.cpp中实现Del_Emp()函数,实现代码如下:

          // 删除职工
          void WorkerManager::Del_Emp()
          {
          	if (this->m_FileIsEmpty)
          	{
          		cout << "文件不存在" << endl;
          	}
          	else
          	{
          		cout << "请输入要删除的职工编号" << endl;
          		int id = 0;
          		cin >> id;
          
          		int index = this->IsExist(id);
          
          		if (index != -1 ) {
          			for (int i = index; i < this->m_EmpNum - 1; i++)
          			{
          				//数据前移
          				this->m_EmpArray[i] = this->m_EmpArray[i + 1];
          			}
          			//更新人数
          			this->m_EmpNum--;
          
          			//同步到文件中
          			this->Save();
          
          			cout << "删除成功" << endl;
          			system("pause");
          			system("cls");
          		}
          		else
          		{
          			cout << "职工编号不存在" << endl;
          			system("pause");
          			system("cls");
          		}
          	}
          }
          //判断职工是否存在
          int WorkerManager::IsExist(int id)
          {
          	int index = -1;
          	for (int i = 0; i < this->m_EmpNum; i++)
          	{
          		if (this->m_EmpArray[i]->m_Id == id)
          		{
          			//按照编号,找到职工
          			index = i;
          			break;
          		}
          	}
          	return index;
          }

          9.修改职工

          实现思路

          • 先判断文件是否存在
          • 如果文件不存在,则提示文件不存在,修改失败。
          • 如果文件存在,则提示用户输入要修改的职工编号
          • 在文件中查找该编号的职工,如果找到了,则提示用户输入新的职工信息,并且更新到文件中

          实现修改职工

          在WorkerManager.h中添加如下成员函数:

          //修改职工
          void Mod_Emp();

          在WorkerManager.cpp中实现Mod_Emp()函数,实现代码如下:

          //修改职工
          void WorkerManager::Mod_Emp()
          {
          	if (this->m_FileIsEmpty)
          	{
          		cout << "文件不存在" << endl;
          	}
          	else
          	{
          		cout << "请输入要修改的员工编号" << endl;
          		int oldId = 0;
          		cin >> oldId;
          
          		int ret = this->IsExist(oldId);
          		if (ret != -1)
          		{
          			//先清空原有数据
          			delete  this->m_EmpArray[ret];
          
          			//提示输入,修改职工信息
          			int id;
          			string name;
          			int dSelect;
          
          			cout << "查到:" << oldId << "号职工,请输入职工新编号" << endl;
          			cin >> id;
          
          			cout << "输入职工的姓名" << endl;
          			cin >> name;
          
          			cout << "选择岗位职称" << endl;
          			cout << "1:表示普通员工" << endl;
          			cout << "2:表示经理" << endl;
          			cout << "3:表示老板" << endl;
          			cin >> dSelect;
          
          			Worker* worker = NULL;
          			switch (dSelect)
          			{
          			case 1:
          				worker = new Employee(id, name, 1);
          				break;
          			case 2:
          				worker = new Manager(id, name, 2);
          				break;
          			case 3:
          				worker = new Boss(id, name, 3);
          				break;
          			default:
          				break;
          			}
          
          			//将职工信息添加到数组中
          			this->m_EmpArray[ret] = worker;
          			cout << "修改成功" << endl;
          			//同步到文件
          			this->Save();
          		}
          		else
          		{
          			cout << "修改失败,员工编号不存在" << endl;
          		}
          	}
          	system("pause");
          	system("cls");
          }

          10.查找职工

          详细请查看代码:

          int WorkerManager::IsNameExist(string name) {
          	bool flag = -1;
          	for (int i = 0; i < this->m_EmpNum; i++)
          	{
          		if (this->m_EmpArray[i]->m_Name == name)
          		{
          			flag = i;
          			break;
          		}
          	}
          	return flag;
          }
          void WorkerManager::Find_Emp()
          {
          	if (this->m_FileIsEmpty)
          	{
          		cout << "文件不存在" << endl;
          	}
          	else
          	{
          		cout << "请输入查找方式" << endl;
          		cout << "1:按照编号查询" << endl;
          		cout << "2:按照姓名查询" << endl;
          		int select = 0;
          		cin >> select;
          
          		switch (select)
          		{
          		case 1:
          		{
          			//按照编号查询
          			int id;
          			cout << "请输入查找的编号:" << endl;
          			cin >> id;
          			int index = 0;
          			index = this->IsExist(id);
          
          			if(index != -1)
          			{
          				cout << "查询到编号为"<<id<<"的职工,信息如下:" << endl;
          				this->m_EmpArray[index]->showInfo();
          			}
          			else
          			{
          				cout << "查无此人!" << endl;
          			}
          		}
          			break;
          		case 2:
          		{
          			//按照姓名查找
          			string name;
          			cout << "请输出要查找的职工姓名:" << endl;
          			cin >> name;
          
          			int index = this->IsNameExist(name);
          			if (index != -1)
          			{
          				cout << "查找成功," << name << "职工的信息如下:" << endl;
          				this->m_EmpArray[index]->showInfo();
          			}
          			else
          			{
          				cout << "查无此人" << endl;
          			}
          		}
          			break;
          		default:
          			cout << "输入错误请重新输入!!" << endl;
          			break;
          		}
          	}
          	this->Cls_coutContents();
          }

          11.按照编号排序

          详细查看代码:

          void WorkerManager::Sort_Emp()
          {
          	if (this->m_FileIsEmpty)
          	{
          		cout << "文件不存在或为空" << endl;
          		this->Cls_coutContents();
          	}
          	else
          	{
          		//文件存在输入编号,设置升序,降序
          		cout << "输入数字设置排序" << endl;
          		cout << "1:升序" << endl;
          		cout << "2:降序" << endl;
          
          		int select = 0;
          		cin >> select;
          		for (int i = 0; i < this->m_EmpNum; i++)
          		{
          			//假设i下标对象中的编号就是最大的
          			int minOrMan = i;
          			for (int j = 0; j < this->m_EmpNum; j++)
          			{
          				if (select == 1)//升序
          				{
          					if (this->m_EmpArray[minOrMan]->m_Id > this->m_EmpArray[j]->m_Id)//如果是大于,把下标赋值给minOrman
          					{
          						minOrMan = j;
          					}
          				}
          				else 
          				{
          					if (this->m_EmpArray[minOrMan]->m_Id < this->m_EmpArray[j]->m_Id)//如果是小于,把下标赋值给minOrman
          					{
          						minOrMan = j;
          					}
          				}
          			}
          
          			//以升序为例,当假设的值最大值minOrMan与j中对象比较完后,
          			// 就能得到第一轮真正最大的下标,这个是时候交换数据即可
          			if (minOrMan != i)
          			{
          				Worker* temp = this->m_EmpArray[i];
          				this->m_EmpArray[i] = this->m_EmpArray[minOrMan];
          				this->m_EmpArray[minOrMan] = temp;
          			}
          		}
          		cout << "排序成功" << endl;
          		//同步数据到文件
          		this->Save();
          		this->Show_Emp();
          	}
          }

          12.清空文件

          详细查看代码:

          void WorkerManager::Clean_File()
          {
          	cout << "确定清空文件" << endl;
          	cout << "1:确定" << endl;
          	cout << "2:取消" << endl;
          
          	int select = 0;
          	cin >> select;
          
          	if(select == 1)
          	{
          		//清空文件
          		ofstream ofs(FILENAME,ios::trunc);
          		ofs.close();
          
          		//判断存储指针对象的数组是否为空,为空则需要一一清楚堆区的每个职工
          		if (this->m_EmpArray != NULL)
          		{
          			for (int i = 0; i < this->m_EmpNum; i++)
          			{
          				delete this->m_EmpArray[i];
          				this->m_EmpArray[i] = NULL;//避免野指针
          			}
          
          			//释放指针数组
          			delete[] this->m_EmpArray;
          			this->m_EmpArray = NULL;
          			this->m_EmpNum = 0;
          			this->m_FileIsEmpty = true;
          
          			cout << "文件清除成功。" << endl;
          		}
          	}
          	this->Cls_coutContents();
          }
          void WorkerManager::Cls_coutContents() 
          {
          	system("pause");
          	system("cls");
          }

          结束语

          以上就是一个基于C++的职工管理系统,希望对大家有所帮助。