PID进阶教程

PID精进教程; 这篇文章的主要内容是如何改进初学者的PID控制器。文章中提到,初学者的PID控制器设计的目的是不规则地调用。这会导致两个问题:您无法从PID获得一致的行为,因为有时它被频繁调用,有时则没有。您需要进行额外的数学计算,以计算导数和积分,因为它们都依赖于时间的变化。作者提出了一种新的方法,称为“Proportional on Measurement”,可以解决这些问题。 任何编写自己的 PID 算法的人都可以看看我是如何做的,并可以从中学到他们需要的东西。 这将是一个艰难的过程,但我想我找到了一种不太痛苦的方式来解释我的代码。我将从所谓的“初学者的 PID”开始。然后我将逐步改进它,直到我们得到一个高效、健壮的 pid 算法。 PID的开始 这是初学PID的人都知道的公式: Output=K_pe(t)+K_t\int{e(t)dt}+K_D{\frac{d}{dt}}e(t) Where:e=Setpoint-Input 公式(2)也就是指偏差e等于设定值和当前值的差别; 根据这个公式,大多数都能写出下面的代码: /*working variables*/ unsigned long lastTime; double Input, Output, Setpoint; double errSum, lastErr; double kp, ki, kd; void Compute() { /*How long since we last calculated*/ unsigned long now = millis(); double timeChange = (double)(now - lastTime); /*Compute all the working error variables*/ double error = Setpoint - Input; errSum += (error * timeChange); double dErr = (error - lastErr) / timeChange; /*Compute PID Output*/ Output = kp * error + ki * errSum + kd * dErr; /*Remember some variables for next time*/ lastErr = error; lastTime = now; } void SetTunings(double Kp, double Ki, double Kd) { kp = Kp; ki = Ki; kd = Kd; } Compute()被定期或不定期地调用,并且运行良好。不过,这个系列并不是关于“工作得很好”。如果我们要将这段代码变成与工业 PID 控制器相当的东西,我们必须解决一些问题: ...

April 9, 2023 · 3 min · Rancho

基于Arduino的LVGL移植

这是一篇如何将LVGL移植到Arduino的教程(基于芯片ESP32 Pico D4); 软件版本 这次实验使用的lvgl版本是8.1.1,要先配置好tft_espi,确保显示正常;如果要使用触摸屏设备,在移植之前要确保能获取到触摸数据; 工程配置 库安装 添加lvgl库 ,最好也添加lv_examples库,自带的例子虽然内容完全一样,但是并不能直接使用; 库安装 然后复制为lv_conf_template.h为lv_conf.h: lv_conf.h创建 然后复制为lv_demo_conf_template.h为lv_demo_conf.h: lv_demo_conf.h创建 配置文件 lv_conf.h 修改这几个地方; 启动lv_conf.h: 启动lv_conf 设置色深,一般都是16: 设置色深 启动自定义时钟,不设置的话只会显示第一帧不动: 启动自定义时钟 LV_DPI_DEF 注意这里,虽然LVGL的作者说这个没这么重要,但他会严重影响到LVGL的动画效果,你应该进行DPI的手动计算,例如240x280分辨率1.69英寸的屏幕,那么 DPI为: LV_DPI_DEF =\frac{\sqrt{240*280} }{1.69} ≈ 153 LV_DPI_DEF配置 也可以使能日志打印: 使能日志打印 lv_demo_conf.h 修改这几个地方; 启动lv_demo_conf.h: 启动Demo 配置要运行的Demo: Demo选择 自定义显示接口和外部输入接口 文件添加 在src文件夹下添加以下两个文件: 自定义接口 代码内容 my_lv_ports.cpp #include "my_lv_ports.h" #include "CST816T.h" // TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight); /* TFT instance */ TFT_eSPI tft = TFT_eSPI(); /* TFT instance */ CST816T touch(19, 21, -1, 22); // sda, scl, rst, irq // /*Read the touchpad*/ void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) { bool FingerNum = 0; uint8_t gesture; uint16_t touchX, touchY; FingerNum = touch.getTouch(&touchX, &touchY, &gesture); if (FingerNum) { data->state = LV_INDEV_STATE_REL; data->point.x = touchX; data->point.y = touchY; #if LV_USE_LOG != 0 Serial.printf("Touch: x=%d y=%d mode=%d\r\n", touchX, touchY, gesture); #endif FingerNum = 0; } else { data->state = LV_INDEV_STATE_PR; } } /* Display flushing */ void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { uint32_t w = (area->x2 - area->x1 + 1); uint32_t h = (area->y2 - area->y1 + 1); tft.setSwapBytes(true); // tft.pushImageDMA(area->x1, area->y1, w, h, (uint16_t *)&color_p->full); tft.pushImage(area->x1, area->y1, w, h, (uint16_t *)&color_p->full); // tft.startWrite(); // tft.setAddrWindow( area->x1, area->y1, w, h ); // tft.pushColors( ( uint16_t * )&color_p->full, w * h, true ); // tft.endWrite(); lv_disp_flush_ready(disp); } #if LV_USE_LOG != 0 void my_print(const char *buf) { Serial.printf("%s \r\n", buf); } #endif void my_disp_init(void) { // 绘图缓冲初始化 // static lv_disp_draw_buf_t draw_buf; // static lv_color_t buf[screenWidth * 10]; // lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * 10); static lv_disp_draw_buf_t draw_buf; static lv_color_t buf_2_1[screenWidth * 40]; /*A buffer for 10 rows*/ static lv_color_t buf_2_2[screenWidth * 40]; /*An other buffer for 10 rows*/ lv_disp_draw_buf_init(&draw_buf, buf_2_1, buf_2_2, screenWidth * 30); /*Initialize the display buffer*/ // TFT驱动初始化 tft.begin(); /* TFT init */ // tft.initDMA(); tft.setRotation(0); /* Landscape orientation, flipped */ // 设置LVGL显示设备 static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); /*Change the following line to your display resolution*/ disp_drv.hor_res = screenWidth; disp_drv.ver_res = screenHeight; disp_drv.flush_cb = my_disp_flush; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); touch.begin(); // 设置LVGL输入设备(电阻屏) static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_touchpad_read; lv_indev_drv_register(&indev_drv); // 设置LVGL串口输出设备(调试用) #if LV_USE_LOG != 0 lv_log_register_print_cb(my_print); #endif } my_lv_ports.h ...

April 7, 2023 · 2 min · Rancho

TODO

有空就会做的事; 记得翻译一下这篇文章 PID精进教程 完成否?否 创建于:2023年4月4日20:32:45 完成于: 备注:

April 4, 2023 · 1 min · Rancho

ESP32-PICO-D4开发记录

我这两天又翻出来了之前做的一个ESP32-Pico-D4的板子,之前测是发现会无限重启,结果现在再试,下进去一个新的程序就能用了,这里记录一下开发中遇到的问题; Pico D4介绍 这个芯片资源挺多的,最主要的是内嵌了晶振和Flash,在板子布线方面容易了许多; 芯片特性 原理图设计 原理图设计参考 原理图参考 实物图片 重启问题 ESP32 启动时会有如下打印: rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) 其中 rst 简单说明如下: PRO APP 源 复位方式 注释 0x01 0x01 芯片上电复位 系统复位 0x10 0x10 RWDT 系统复位 系统复位 详见 ESP32 技术参考手册 WDT 章节 0x0F 0x0F 欠压复位 系统复位 详见 ESP32 技术参考手册 Power Management 章节 0x03 0x03 软件系统复位 内核复位 配置 RTC_CNTL_SW_SYS_RST 寄存器 0x05 0x05 Deep Sleep Reset 内核复位 详见 ESP32 技术参考手册 Power Management 章节 0x07 0x07 MWDT0 全局复位 内核复位 详见 ESP32 技术参考手册 WDT 章节 ...

April 1, 2023 · 2 min · Rancho

docker初尝试

docker初体验; docker常用命令 #docker常用命令: 1. 查看容器: docker ps 2. 查看镜像: docker images 3. 删除容器: docker rm 容器name 4. 删除镜像: docker rmi 镜像id 5. 创建容器: docker run --name dockermysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=654321 mysql:5.7.23 6. 启动容器: docker start 容器name #docker start dockermysql 7. 重启容器: docker restart dockermysql 8. 停止容器: docker stop dockermysql 9. 容器交互: docker exec -it dockermysql bash #或 docker attach dockermysql 10.退出交互: Ctrl+P,Ctrl+Q(Ctrl键一直保持按下) 11.设置开机自启: systemctl enable docker 12.容器设置自启,update命令: docker update --restart=always aeccnginx 13.启动docker systemctl start docker 14.重启docker systemctl restart docker

March 19, 2023 · 1 min · Rancho

使用C和C++进行混合编程

在做毕设过程中,用到了八个相同的传感器,传感器的通信协议都是一样的,固然直接搞八个差不多的文件来驱动传感器是没问题的,但是有一种更简洁的方式,那就是使用C++,来复用通信程序; 参考:https://zhuanlan.zhihu.com/p/115068898 环境说明 使用的单片机芯片是STM32F103C6T6; Keil编译器版本为AC6; Keil的配置如下: 然后是将C++源文件配对的头文件改为使用C++编译器进行编译; 源码编写 注意把main文件的后缀也改为.cpp,否则会出错; 头文件: /* * @Author : fan-pengfei 2253770787@qq.com * @Date : 2023-03-07 13:43:38 * @LastEditors : fan-pengfei 2253770787@qq.com * @LastEditTime : 2023-03-07 14:41:29 * @FilePath : \Core\Inc\ds18b20.h * @Description : * Copyright (c) 2023 by ${git_name_email}, All Rights Reserved. */ #ifndef __DS18B20_H #define __DS18B20_H #include "main.h" #include "tim.h" class DS18B20_Class { private: GPIO_TypeDef *BSP_DS18B20_PORT; uint16_t BSP_DS18B20_PIN; float *Temp_Sum; uint8_t LOCATION; public: DS18B20_Class(GPIO_TypeDef *BSP_DS18B20_PORT, uint16_t BSP_DS18B20_PIN, uint8_t LOCATION, float *Temp_data); void delay_us(uint16_t us); GPIO_PinState DS18B20_IN(void); void DS18B20_OUT_1(void); void DS18B20_OUT_0(void); void DS18B20_Mode_OUT_PP(void); void DS18B20_Mode_IN_NP(void); void DS18B20_Reset(void); uint8_t DS18B20_Presence(void); uint8_t DS18B20_ReadBit(void); uint8_t DS18B20_ReadByte(void); void DS18B20_WriteByte(uint8_t dat); void DS18B20_ReadId(uint8_t *ds18b20_id); void DS18B20_SkipRom(void); void DS18B20_MatchRom(void); uint8_t Init(void); float DS18B20_GetTemp_SkipRom(void); float DS18B20_GetTemp_MatchRom(uint8_t *ds18b20_id); void Start_Convert(void); void Get_Data(uint8_t ds18b20_id[64][8]); }; extern "C" { void User_DS18B20_Init(void); void User_Start_Convert(void); void User_Get_Data(void); } #endif 源文件: /* * @Author : fan-pengfei 2253770787@qq.com * @Date : 2023-03-07 13:43:26 * @LastEditors : fan-pengfei 2253770787@qq.com * @LastEditTime : 2023-03-07 16:41:42 * @FilePath : \Core\Src\ds18b20.cpp * @Description : * Copyright (c) 2023 by ${git_name_email}, All Rights Reserved. */ #include "ds18b20.h" #include "main.h" #include "DS18B20_ID.h" extern float Temp_Sum[64]; void DS18B20_Class::delay_us(uint16_t us) { tim_delay_us(us); // uint32_t delay; // delay = (1600 * us); // while (delay--) // { // } } GPIO_PinState DS18B20_Class::DS18B20_IN(void) { return HAL_GPIO_ReadPin(BSP_DS18B20_PORT, BSP_DS18B20_PIN); } void DS18B20_Class::DS18B20_OUT_1(void) { HAL_GPIO_WritePin(BSP_DS18B20_PORT, BSP_DS18B20_PIN, GPIO_PIN_SET); } void DS18B20_Class::DS18B20_OUT_0(void) { HAL_GPIO_WritePin(BSP_DS18B20_PORT, BSP_DS18B20_PIN, GPIO_PIN_RESET); } /** * @brief DS18B20 输出模式 */ void DS18B20_Class::DS18B20_Mode_OUT_PP(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = BSP_DS18B20_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(BSP_DS18B20_PORT, &GPIO_InitStruct); } /** * @brief DS18B20 输入模式 */ void DS18B20_Class::DS18B20_Mode_IN_NP(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = BSP_DS18B20_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(BSP_DS18B20_PORT, &GPIO_InitStruct); } /** * @brief 主机给从机发送复位脉冲 */ void DS18B20_Class::DS18B20_Reset(void) { DS18B20_Mode_OUT_PP(); // 主机输出 DS18B20_OUT_0(); // 主机至少产生 480us 的低电平复位信号 delay_us(750); DS18B20_OUT_1(); // 主机在产生复位信号后,需将总线拉高 // 从机接收到主机的复位信号后,会在 15 ~ 60 us 后给主机发一个存在脉冲 delay_us(15); } /** * @brief 检测从机给主机返回的存在脉冲 * @return 0:成功 1:失败 */ uint8_t DS18B20_Class::DS18B20_Presence(void) { uint8_t pulse_time = 0; DS18B20_Mode_IN_NP(); // 主机设为输入 while (DS18B20_IN() && (pulse_time = 100) { return 1; } else { pulse_time = 0; } while (!(DS18B20_IN()) && pulse_time = 240) { return 1; } else { return 0; } } /** * @brief 从DS18B20读取一个bit */ uint8_t DS18B20_Class::DS18B20_ReadBit(void) { uint8_t dat; DS18B20_Mode_OUT_PP(); // 读 0 和读 1 的时间至少要大于 60 us DS18B20_OUT_0(); // 读时间的起始:必须由主机产生 > 1us > 1; // 写 0 和写 1 的时间至少要大于60us if (testb) // 当前位写 1 { DS18B20_OUT_0(); delay_us(5); // 拉低发送写时段信号 DS18B20_OUT_1(); // 读取电平时间保持高电平 delay_us(65); } else // 当前位写 0 { DS18B20_OUT_0(); // 拉低发送写时段信号 delay_us(70); // 读取电平时间保持低电平 DS18B20_OUT_1(); delay_us(2); // 恢复时间 } } } /** * @brief 跳过匹配 DS18B20 ROM */ void DS18B20_Class::DS18B20_SkipRom(void) { DS18B20_Reset(); DS18B20_Presence(); DS18B20_WriteByte(0XCC); /* 跳过 ROM */ } /** * @brief 执行匹配 DS18B20 ROM */ void DS18B20_Class::DS18B20_MatchRom(void) { DS18B20_Reset(); DS18B20_Presence(); DS18B20_WriteByte(0X55); /* 匹配 ROM */ } DS18B20_Class::DS18B20_Class(GPIO_TypeDef *BSP_DS18B20_PORT, uint16_t BSP_DS18B20_PIN, uint8_t LOCATION, float *Temp_data) { DS18B20_Class::BSP_DS18B20_PORT = BSP_DS18B20_PORT; DS18B20_Class::BSP_DS18B20_PIN = BSP_DS18B20_PIN; DS18B20_Class::LOCATION = LOCATION; DS18B20_Class::Temp_Sum = Temp_data; } uint8_t DS18B20_Class::Init(void) { DS18B20_Mode_OUT_PP(); DS18B20_OUT_1(); DS18B20_Reset(); return DS18B20_Presence(); } /** * 存储的温度是16 位的带符号扩展的二进制补码形式 * 当工作在12位分辨率时,其中5个符号位,7个整数位,4个小数位 * * |---------整数----------|-----小数 分辨率 1/(2^4)=0.0625----| * 低字节 | 2^3 | 2^2 | 2^1 | 2^0 | 2^(-1) | 2^(-2) | 2^(-3) | 2^(-4) | * * * |-----符号位:0->正 1->负-------|-----------整数-----------| * 高字节 | s | s | s | s | s | 2^6 | 2^5 | 2^4 | * * * 温度 = 符号位 + 整数 + 小数*0.0625 */ /** * @brief 在跳过匹配 ROM 情况下获取 DS18B20 温度值 * @param 无 * @retval 温度值 */ float DS18B20_Class::DS18B20_GetTemp_SkipRom(void) { uint8_t tpmsb, tplsb; int16_t s_tem; float f_tem; DS18B20_SkipRom(); DS18B20_WriteByte(0X44); /* 开始转换 */ DS18B20_SkipRom(); DS18B20_WriteByte(0XBE); /* 读温度值 */ tplsb = DS18B20_ReadByte(); tpmsb = DS18B20_ReadByte(); s_tem = tpmsb 要注意其中的`extern "C"`的用法; ### Main中调用: > 注意main文件也是cpp后缀; ```c++ void main(void) { User_DS18B20_Init(); while (1) { User_Start_Convert(); HAL_Delay(200); // 更新速率为200ms,等待转换结束 User_Get_Data(); int i = 0; while (i 使用C编写 ![5a011b38ca2d1e989de04f32bb8c0e5](img-5.png) 使用C++编写 ![4dbfdf38abe4f0d85cc7f98e4133007](img-6.png) **C:** Total RO Size (Code + RO Data) 14016 (13.69kB) Total RW Size (RW Data + ZI Data) 2040 (1.99kB) Total ROM Size (Code + RO Data + RW Data) 14464 ( 14.13kB) **C++:** Total RO Size (Code + RO Data) 11780 (11.50kB) Total RW Size (RW Data + ZI Data) 2864 (2.80kB) Total ROM Size (Code + RO Data + RW Data) 12220 ( 11.93kB) > 相比较而言C用的ROM比较多一些,C++用的RAM比较多一些; **可见在某些情况下,使用C++编写代码可以有效缩减代码体积,且代码更易懂;** ### microlib 使用C++编译的话,就没法再使用MicroLIB,因为MicroLIB为非标准的精简库,会与标准C++产生冲突; ### 中断服务程序 如果中断服务程序是异常的,因为stm32的中断入口矢量是按C的方式进入的,因此需要在整个文档的头部和末尾加上extern “C”{}用大括号把整个代码段扩住,这样中断就可以正常的进入了; ### Cubemx Cubemx在生成头文件中已经加入了: ```c #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif 所以在不是其生成的文件中要注意extern "C"的使用; ...

March 7, 2023 · 4 min · Rancho

ROS相关知识

做毕设用到了ROS,这里记录ROS相关知识; 语言基于Python,因为C++短时间学不起来,Python的话还能懂一些; 工作空间构建 创建一个新的文档夹 我们创建一个新的文档夹作为新的工作空间的开始 给每个新的工作空间创建一个新的文档夹是一个好习惯,取上面名字无关紧要,但是最好能从这个名字上看出这个工作空间是干什么的,例如main_ws,(主要工作空间): mkdir main_ws 将功能包放入src也是一个比较好的习惯,我们创建一个工作空间同时创建一个src文档夹,然后进入这个文档夹内: mkdir main_ws/src cd main_ws/src 与ROS1不同,ROS2的工作空间并不需要init; 自定义包 如何在ROS2中创建一个功能包呢?我们可以使用这个指令: $ ros2 pkg create --build-type 在ros2命令中: pkg:表示功能包相关的功能; create:表示创建功能包; build-type:表示新创建的功能包是C++还是Python的,如果使用C++或者C,那这里就跟ament_cmake,如果使用Python,就跟ament_python; package_name:新建功能包的名字。 比如在终端中分别创建C++和Python版本的功能包: cd ~/main_ws/src ros2 pkg create --build-type ament_cmake learning_pkg_c # C++ ros2 pkg create --build-type ament_python learning_pkg_python # Python 对于Python包: 只需要进到构建的包文件夹中,进入与上一层文件夹名字相同的包中即可,然后写入源文件; 然后修改package.xml文件: from setuptools import setup package_name = 'serial_test' setup( name=package_name, version='0.0.0', packages=[package_name], data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), ], install_requires=['setuptools'], zip_safe=True, maintainer='xioafei', maintainer_email='2253770787@qq.com', description='TODO: Package description', license='TODO: License declaration', tests_require=['pytest'], entry_points={ 'console_scripts': [ 'interface_object_pub = serial_test.interface_object_pub:main', # 'interface_object_sub = serial_test.interface_object_sub:main', ], }, ) 编译功能包 在创建好的功能包中,我们可以继续完成代码的编写,之后需要编译和配置环境变量,才能正常运行: ...

February 28, 2023 · 3 min · Rancho

在GDB下调试STM32的记录

基于VSCode使用GDB来调试STM32,我感觉比那个Keil还好用,而且更懂底层原理; 一、调试步骤: 准备工作 已经熟悉arm gcc工具链; 已经在win中安装好mingw或者arm-none-eabi-gcc工具; 具有合适的代码工程和编译脚本,且编译输出elf文档时,已添加-g选项来生成调试信息; 安装jlink调试工具和对应驱动; 有对应的硬件电路; 1、启动Jlink GDB Server 打开Jlink诸多工具中的Jlink GDB Server并配置好: 启动: 可以看到本地端口为2331,这个一会会用到; 然后就可以把这个窗口最小化了; 2、GDB调试 启动GDB程序: arm-none-eabi-gdb.exe 然后按enter自动进入调试模式; 输入file H743_demo.elf加载调试文档: 然后输入target remote localhost:2331,连接gdb server,连接成功后,会在Jlink GDB server中显示对应的状态,如下所示: 输入monitor reset来复位MCU,从而让MCU处于确定的状态: 输入load往MCU中加载调试文档(是加载进flash,而不是ram),也就是常见的烧录过程: 输入break main设置main断点,让MCU执行到main中停止: 输入c持续运行直至运行到断点处: HAL_Init();是main函数的第一行代码,停在这里; 再次输入c会继续运行; 若要打断持续运行的状态,只需要按下Ctrl+c即可; 3、需要注意的地方 每次程序重新编译都要执行一次load以加载新的elf文档; 如果不使用命令行,而是使用VSCODE中的调试功能,则也需要在程序更新的时候重新load一次; 二、常用命令 1、p(打印) p+变量名:打印变量值: 2、s(单步运行) s:单步运行; 并且可以用 breakpoint+行号进行断点设置; 3、l(列出) 列出当前位置前后共5行程序; 4、watch(变量监视) Watchpoints 是用来告诉 GDB 停止执行某个程序的标记。Watchpoints 与数据相关联:放置监视点需要指定一个表达式来描述变量、多个变量或内存地址。 ...

February 27, 2023 · 1 min · Rancho

基于STM32的CMAKE模板

这是一个基于STM32单片机的模板; 示例中的单片机是STM32H743IIT6,调试器使用JlinkOB; 其中的各个参数可以参考使用STM32CubeMX生成的基于makefile的模板,且后续仍然可以使用STM32CubeMX进行底层代码的构建; 如果需要进行调试,可以先启动J-Link GDB Server,然后使用VSCode进行调试或者直接使用命令行进行调试; # CMAKE_SYSTEM_NAME: 即你目标机target所在的操作系统名称,比如ARM或者Linux你就需要写"Linux"; # 如果Windows平台你就写"Windows",如果你的嵌入式平台没有相关OS你即需要写成"Generic"; # 只有当CMAKE_SYSTEM_NAME这个变量被设置了,CMake才认为此时正在交叉编译; # 它会额外设置一个变量CMAKE_CROSSCOMPILING为TRUE; set(CMAKE_SYSTEM_NAME Generic) # CMAKE_SYSTEM_NAME和CMAKE_SYSTEM_PROCESSOR是交叉编译的时候必须指定的两个参数; # 如果在cmake命令行定义了CMAKE_SYSTEM_NAME,就必须也定义CMAKE_SYSTEM_PROCESSOR; set(CMAKE_SYSTEM_PROCESSOR cortex-m7) #cmake最低版本 cmake_minimum_required(VERSION 3.1.0) #编译工具 set(CROSS_COMPILE_PREFIX arm-none-eabi) # 顾名思义,即C语言编译器,这里可以将变量设置成完整路径或者文档名; # 设置成完整路径有一个好处就是CMake会去这个路径下去寻找编译相关的其他工具; # 比如linker,binutils等,如果你写的文档名带有arm-elf等等前缀; # CMake会识别到并且去寻找相关的交叉编译器; set(CMAKE_C_COMPILER ${CROSS_COMPILE_PREFIX}-gcc) set(CMAKE_CXX_COMPILER ${CROSS_COMPILE_PREFIX}-g++) set(CMAKE_ASM_COMPILER ${CROSS_COMPILE_PREFIX}-gcc) set(CMAKE_OBJCOPY ${CROSS_COMPILE_PREFIX}-objcopy) set(CMAKE_OBJDUMP ${CROSS_COMPILE_PREFIX}-objdump) set(CMAKE_SIZE ${CROSS_COMPILE_PREFIX}-size) # CMake中的命令find_program用于查找程序(program) # 会将查找到的文档路径存在CMakeCache.txt中 find_program(ARM_SIZE_EXECUTABLE ${CROSS_COMPILE_PREFIX}-size) find_program(ARM_GDB_EXECUTABLE ${CROSS_COMPILE_PREFIX}-gdb) find_program(ARM_OBJCOPY_EXECUTABLE ${CROSS_COMPILE_PREFIX}-objcopy) find_program(ARM_OBJDUMP_EXECUTABLE ${CROSS_COMPILE_PREFIX}-objdump) set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) # search for program/library/include in the build host directories # 1、CMAKE_FIND_ROOT_PATH_MODE_PROGRAM: 对FIND_PROGRAM()起作用, # 有三种取值,NEVER,ONLY,BOTH, # 第一个表示不在你CMAKE_FIND_ROOT_PATH下进行查找, # 第二个表示只在这个路径下查找, # 第三个表示先查找这个路径,再查找全局路径, # 对于这个变量来说,一般都是调用宿主机的程序,所以一般都设置成NEVER # # 2、CMAKE_FIND_ROOT_PATH_MODE_LIBRARY: 对FIND_LIBRARY()起作用, # 表示在链接的时候的库的相关选项,因此这里需要设置成ONLY来保证我们的库是在交叉环境中找的. # # 3、CMAKE_FIND_ROOT_PATH_MODE_INCLUDE: 对FIND_PATH()和FIND_FILE()起作用, # 一般来说也是ONLY,如果你想改变,一般也是在相关的FIND命令中增加option来改变局部设置 # 有NO_CMAKE_FIND_ROOT_PATH,ONLY_CMAKE_FIND_ROOT_PATH,BOTH_CMAKE_FIND_ROOT_PATH set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) #工程名称 # project命令用于指定cmake工程的名称 # 实际上,它还可以指定cmake工程的版本号(VERSION关键字)、 # 简短的描述(DESCRIPTION关键字)、 # 主页URL(HOMEPAGE_URL关键字)、 # 编译工程使用的语言(LANGUAGES关键字)。 project(H743_demo C CXX ASM) set(target "${PROJECT_NAME}") set(COMPILE_TOOLS GCC) # Target-specific flags #型号 set(MCU_FAMILY STM32H743xx) #布局文档 set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/STM32H743IITx_FLASH.ld) #内核相关 set(CPU "-mcpu=cortex-m7") set(FPU "-mfpu=fpv5-d16") set(FLOAT_ABI "-mfloat-abi=hard") #宏定义 add_definitions(-DUSE_HAL_DRIVER -DSTM32H743xx) # 构建Release或者Debug版本 if(CMAKE_BUILD_TYPE MATCHES Debug) set(DBG_FLAGS "-g3 -gdwarf-2 -O0") elseif(CMAKE_BUILD_TYPE MATCHES Release) set(DBG_FLAGS "-O3") endif() ##file语法,前一个参数是固定的 后面一个参数自己定义 ##添加文档的时候注意 相对路径和绝对路径 file(GLOB_RECURSE DRIVE_SRC Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_cortex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_rcc.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_rcc_ex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_flash.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_flash_ex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_gpio.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_hsem.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_dma.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_dma_ex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_mdma.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_pwr.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_pwr_ex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_i2c.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_i2c_ex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_exti.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_tim.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_tim_ex.c Core/Src/system_stm32h7xx.c startup_stm32h743xx.s ) file(GLOB_RECURSE USER_SRC Core/Src/main.c Core/Src/gpio.c Core/Src/stm32h7xx_it.c Core/Src/stm32h7xx_hal_msp.c ) # 添加源文件 set(SOURCE_FILES ${DRIVE_SRC} ${USER_SRC}) #添加头文件路径 include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/Core/Inc ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/STM32H7xx_HAL_Driver/Inc ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/STM32H7xx_HAL_Driver/Inc/Legacy ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/Device/ST/STM32H7xx/Include ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/Include ) #芯片特性 set(MCU_FLAGS "${CPU} -mthumb ${FPU} ${FLOAT_ABI}") # compiler: language specific flags CFLAGS set(CMAKE_C_FLAGS "${MCU_FLAGS} -std=gnu99 -Wall -fdata-sections -ffunction-sections ${DBG_FLAGS} " CACHE INTERNAL "C compiler flags") #CPP set(CMAKE_CXX_FLAGS "${MCU_FLAGS} -fno-rtti -fno-exceptions -fno-builtin -Wall -fdata-sections -ffunction-sections ${DBG_FLAGS} " CACHE INTERNAL "Cxx compiler flags") #ASFLAGS set(CMAKE_ASM_FLAGS "${MCU_FLAGS} -x assembler-with-cpp ${DBG_FLAGS} " CACHE INTERNAL "ASM compiler flags") #LDFLAGS -mcpu=cortex-m0plus -mthumb set(CMAKE_EXE_LINKER_FLAGS "${MCU_FLAGS} --specs=nosys.specs -specs=nano.specs -T${LINKER_SCRIPT} -Wl,-Map=${PROJECT_NAME}.map,--cref -Wl,--gc-sections" CACHE INTERNAL "Exe linker flags") #要链接的库 对应makefile的 LIBS set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "-lc -lm -lnosys" CACHE INTERNAL "Shared linker flags") #先定义target 才可以添加define include add_executable(${target}.elf ${SOURCE_FILES}) set(ELF_FILE ${PROJECT_BINARY_DIR}/${target}.elf) set(HEX_FILE ${PROJECT_BINARY_DIR}/${target}.hex) set(BIN_FILE ${PROJECT_BINARY_DIR}/${target}.bin) add_custom_command(TARGET "${target}.elf" POST_BUILD COMMAND ${CMAKE_OBJCOPY} -Obinary ${ELF_FILE} ${BIN_FILE} COMMAND ${CMAKE_OBJCOPY} -Oihex ${ELF_FILE} ${HEX_FILE} COMMENT "Building ${target}.bin and ${target}.hex" COMMAND ${CMAKE_COMMAND} -E copy ${HEX_FILE} "${CMAKE_CURRENT_BINARY_DIR}/${target}.hex" COMMAND ${CMAKE_COMMAND} -E copy ${BIN_FILE} "${CMAKE_CURRENT_BINARY_DIR}/${target}.bin" COMMAND ${CMAKE_SIZE} --format=berkeley ${target}.elf ${target}.hex COMMENT "Invoking: Cross ARM GNU Print Size" ) 使用方式: ...

February 27, 2023 · 3 min · Rancho

CMAKE学习记录

CMAKE学习记录; 新建两个目录: mkdir ./src mkdir ./build 源文档编写:src/main.cpp #include int main() { std::cout ![b562172f8e925c8150fa45bcbcb7b17](img-1.png) 然后输入以下命令进行编译和运行: ```bash make .\cmake_study.exe

February 25, 2023 · 1 min · Rancho