IAP实验流水账(2026.07.16-2026.07.23)
警告:本文会有大量剧透!如果你想全程靠自己钻研,请不要往下看。本文只是图一乐,记录我做测试的过程中的思考以及遇到的问题。如果你在学习的过程中总觉得很困惑,或者想看看其他人做项目时的思路,可以看看我是如何完成这个项目的。
我的基础情况:双非硕,本科计科,研究生软件工程,2024 年 6 月研究生毕业,2025 年 12 月找到勃勃,浅浅(没写过代码)接触过 STM32F103、ESP32-S3、RK3399Pro、RK3588、I.MX6ULL,会写 C、Python、Java、JavaScript/TypeScript,会用 Linux,会用 PyQT5、Electron,会用 Vue,会用 YOLO 以及使用 RKNN-toolkit 部署模型。其实会的都很浅,很杂,找不到工作,所以找勃勃提升来了,所有东西相当于重新学。研究生毕业找不到工作,被导师拉着一起创业做人工智能教学,奈何本人导师只是二把手没有话语权,工作一年半我对一把手攒满了怨气,最后选择在 2026 年 2 月春节前裸辞,开启了待业学习的日子。选择搞嵌入式是因为我希望能进入机器人行业。
学了大半年,总算来到 IAP 的测试了,勃哥说我进度有点太慢了。没办法,前几个月真的一点都不想学,每天刷 8 小时短视频,玩 8 小时游戏,吃了睡,睡醒玩,玩了吃了睡,乐不思蜀,挂壁半年。STM32 第一层从 2025 年 12 月 20 号学到 2026 年 6 月 13 号,第二层 2026 年 6 月 13 号到 7 月 8 号,FreeRTOS 是 2026 年 7 月 8 号-7 月 15 号。因为我有 PyQT 基础,用 C++ 做个 QT 的上位机没啥问题,所以我说服了勃勃让我跳过 QT 直接进入 IAP 测试。下面将记录完成 IAP 测试的过程中,我的思路、遇到的问题以及解决的办法。
初步思路
测试要求:我做的是 IAP 的简化路线,只需要支持在 boot 中用串口升级程序,在 APP 里支持通过以太网、WiFi 与上位机的通信。flash 只需要划分 3 个区:boot、运行区、存储区即可。
初步思路与计划:因为学 STM32 的时候没弄过 ETH 和 WiFi,这部分知识需要补一下。先从 WiFi 入手,简单看了一下文档,貌似只是使用串口发送一下 AT 指令就好了。那先在 RTOS 上把 WiFi 调通。后面学习一下 lwip,完成在 RTOS 上的 ETH 的通信。都弄完以后再去复习一下 MDK 相关的内容,写一个裸机程序能够支持将程序跳转到新的内存空间执行,然后编写一个能够通过串口接收二进制文件的裸机程序,并将文件存放到程序空间中。以上功能都能使用串口调试工具调通的情况下,再通过 QT 编写一个上位机软件,满足选择文件、打开文件、通过串口发送文件、收发以太网和 WiFi 数据的功能即可。
STM32 程序部分
WiFi 功能测试
简单看了一下,就是通过串口给 ESP8266 芯片发送 AT 指令就好了,应该不难。
利用开发板的 USB 转串口,直接用 PC 连接 WiFi 模块进行测试【成功了】
先试试看能不能直接用开发板上面的 USB 转串口直接接到 ESP8266 的串口引脚上,跳过 STM32 直接让 ESP8266 和 PC 通信,这样可以看看 WiFi 模组正不正常,节省中间很多步骤。
-
查看开发板原理图,WIFI_TXD 和 WIFI_RXD 是通过跳线帽连接到 STM32 的 PB11 和 PB10 引脚。而 USB 转串口也是用跳线帽将 TXD、RXD 与 PA9 和 PA10 连接。因此理论上如果将跳线帽拿掉,用杜邦线直接飞线将 WIFI_TXD 与 RXD 连接,WIFI_RXD 与 TXD 连接,这样应该就可以直接通过 USB 将 PC 与 ESP8266 连接,通过串口调试工具使用 AT 指令了。

-
测试了一下,发现不行,奇怪。再看一下原理图,看看有没有接错或者漏掉的东西。噢噢噢噢噢!原来还需要给 WIFI_EN 引脚来个高电平才能使能,开发板的 PE2 引脚连接了 WIFI_EN 引脚,PE2 引脚从来没有配置过,默认应该悬空吧,用万用表测试一下电压。

-
破案了,现在肯定就是 WIFI_EN 引脚没有给高电平,所以 ESP8266 没有启动,有两种办法,一个给 STM32 写个程序,控制 PE2 引脚输出高电平,但是这也太麻烦了吧。还有一个,就是直接给 WIFI_EN 引脚接上 3V3 电源。懂得都懂,肯定选第二种办法嘛。

-
66666,真的可以了,但是很奇怪,为什么发什么数据,就收到什么数据?难道是芯片有问题?还是说我的板子的 ESP8266 没有固件?

-
看看手册吧 3. ESP8266 模块 — [野火]STM32 模块例程介绍 文档。噢噢噢噢噢!我指令发的有问题,醉了,所有 AT 指令后面要跟一个换行。
AT+指令[换行]这才是真指令啊,成功了。
让 PC 能够通过串口调试工具控制 STM32 并用上 WiFi 模块
没想到 PC 直连 WiFi 模组一下就通了,说明 WiFi 模组固件肯定是没啥问题了。那现在就考虑 STM32 与 WiFi 模组通信的问题了。把跳线帽全部插回去,让 STM32 可以和 WiFi 模块通信。目前思路是这样:让 STM32 当个中介,原封不动将 PC 发来的 AT 指令转发给 WiFi 模块,WiFi 模块返回的数据也是同样的原封不动返回给 PC,这样就能实现 PC 对 WiFi 模块的控制了,先这么干。
-
用 STM32CubeMX 生成一个工程,先把整个 IAP 项目可能用到的引脚都配置一下。3 个 LED、2 个按键、WiFi 有 4 个、ETH 有 9 个、UART 转 USB 有 2 个,还有 NVIC 配置优先级组为 4(要用 FreeRTOS)、RCC 配置 HSE 用外部晶振。USART1、USART3,考虑到以后可能要发文件,内容有点多,把 DMA 也打开,FIFO 忘了干嘛用的,先不开了,DMA 具体怎么配置忘了,先默认吧,遇到问题再说。ETH 不会配置不管了先放着。时钟配置成 168MHz。项目管理把 IDE 配置成 MDK-ARM。这样应该就行了。靠,点到 Tools 的 CAD 里面,卡了 1 分钟!生成代码!
-
OK 代码拿到手,用 keil 打开,先编译一下,完美,没问题。
-
现在移植 FreeRTOS,我直接用的最新版本的 FreeRTOSv202604.00-LTS,解压出来。在工程根目录下创建
FreeRTOS/Source和FreeRTOS/Include两个文件夹,把FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel目录下的所有.c文件、FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\portable\MemMang\heap_4.c、FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\portable\RVDS\ARM_CM4F\port.c拷贝到FreeRTOS/Source,把FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\include里的所有文件文件、FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\portable\RVDS\ARM_CM4F\portmacro.h、FreeRTOS-LTS\FreeRTOS\FreeRTOS-Kernel\examples\template_configuration\FreeRTOSConfig.h文件拷贝到FreeRTOS/Include文件夹下。最终目录结构如下:+─FreeRTOS │ ├─Include │ │ atomic.h │ │ CMakeLists.txt │ │ croutine.h │ │ deprecated_definitions.h │ │ event_groups.h │ │ FreeRTOS.h │ │ FreeRTOSConfig.h │ │ list.h │ │ message_buffer.h │ │ mpu_prototypes.h │ │ mpu_syscall_numbers.h │ │ mpu_wrappers.h │ │ newlib-freertos.h │ │ picolibc-freertos.h │ │ portable.h │ │ portmacro.h │ │ projdefs.h │ │ queue.h │ │ semphr.h │ │ StackMacros.h │ │ stack_macros.h │ │ stdint.readme │ │ stream_buffer.h │ │ task.h │ │ timers.h │ │ │ └─Source │ croutine.c │ event_groups.c │ heap_4.c │ list.c │ port.c │ queue.c │ stream_buffer.c │ tasks.c │ timers.c然后去 Keil 里面创建一个
FreeRTOS/Source的Group把FreeRTOS/Source里的文件都加上,在魔术棒里面把 include 文件加上FreeRTOS/Include目录。先编译一下,不过这时候肯定要报错了,因为 freertos 还有很多东西没有配置。凭借本人 7 天入门的 freertos,我总结出了以下需要修改的内容:-
FreeRTOSConfig.h 文件
宏 旧值 新值 configCPU_CLOCK_HZ ( ( unsigned long ) 20000000 ) ( ( unsigned long ) 168000000 ) configTICK_RATE_HZ 100 1000 configUSE_TIME_SLICING 0 1 configUSE_PORT_OPTIMISED_TASK_SELECTION 0 1 configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_64_BITS TICK_TYPE_WIDTH_32_BITS configQUEUE_REGISTRY_SIZE 0 10 configENABLE_BACKWARD_COMPATIBILITY 0 1 configTOTAL_HEAP_SIZE 4096 ((size_t)(36*1024)) configMAX_SYSCALL_INTERRUPT_PRIORITY 0 ( 5 << 4 ) configCHECK_FOR_STACK_OVERFLOW 2 0 configUSE_QUEUE_SETS 0 1 xPortPendSVHandler 新增 PendSV_Handler vPortSVCHandler 新增 SVC_Handler -
stm32f4xx_it.c 文件
把
PendSV_Handler和SVC_Handler两个函数都注释掉。
-
-
OK,完成了上述操作以后,编译一下,没有报错。。。怎么会没有报错呢?我靠,不对啊,怎么会没有报错啊?心里慌慌的,按理说应该还得报错才对啊。用 FreeRTOS 点个灯看看。
-
先裸机点个灯看看 HAL 库是不是正常,创建 BSP 的 Group 并添加
bsp_led.c和bsp_led.h文件,编写代码。// =============================== // bsp_led.h // =============================== #ifndef __BSP_LED_H #define __BSP_LED_H #include "stm32f4xx.h" #include "main.h" #include "stm32f4xx_hal_gpio.h" #define LED_ON GPIO_PIN_RESET #define LED_OFF GPIO_PIN_SET void LED_Turn_On(GPIO_TypeDef* LED_Color, uint16_t LED_Pin); void LED_Turn_Off(GPIO_TypeDef* LED_Color, uint16_t LED_Pin); void LED_Toggle(GPIO_TypeDef* LED_Color, uint16_t LED_Pin); #endif // =============================== // bsp_led.c // =============================== #include "bsp_led.h" // 点亮LED void LED_Turn_On(GPIO_TypeDef* LED_Color, uint16_t LED_Pin){ HAL_GPIO_WritePin(LED_Color, LED_Pin, LED_ON); } // 熄灭LED void LED_Turn_Off(GPIO_TypeDef* LED_Color, uint16_t LED_Pin){ HAL_GPIO_WritePin(LED_Color, LED_Pin, LED_OFF); } // 翻转LED状态 void LED_Toggle(GPIO_TypeDef* LED_Color, uint16_t LED_Pin){ HAL_GPIO_TogglePin(LED_Color, LED_Pin); } // =============================== // main.c // =============================== #include "bsp_led.h" ...中间内容省略 while (1) { /* USER CODE END WHILE */ LED_Toggle(LED_R_GPIO_Port, LED_R_Pin); for(i = 0; i < 0x7FFFFF; i++); LED_Toggle(LED_R_GPIO_Port, LED_R_Pin); for(i = 0; i < 0x7FFFFF; i++); LED_Toggle(LED_G_GPIO_Port, LED_G_Pin); for(i = 0; i < 0x7FFFFF; i++); LED_Toggle(LED_G_GPIO_Port, LED_G_Pin); for(i = 0; i < 0x7FFFFF; i++); LED_Toggle(LED_B_GPIO_Port, LED_B_Pin); for(i = 0; i < 0x7FFFFF; i++); LED_Toggle(LED_B_GPIO_Port, LED_B_Pin); for(i = 0; i < 0x7FFFFF; i++); /* USER CODE BEGIN 3 */ } ...剩余内容省略编译一把过,烧录看看!尴尬啊,烧录烧录不了,报错
No ST-LINK detected
这个问题好解决,就是魔术棒里面
Debug下的Debugger换成CMSIS-DAP Debugger,然后Settings里面CMSIS-DAP-JTAG/SW Adapter选择Fire CMSIS-DAP即可。烧录!奇怪,LED 灯怎么不亮?不可能啊?复位一下看看。噢噢噢噢可以了,原来如此啊,我没有配置Reset and Run,去魔术棒里面的Debug->右上settings->Flash Download下把Reset and Run勾选就好了。 -

-
既然裸机没问题,那试试用 freertos 运行这个任务看看行不行。修改一下代码。我靠,有点忘了 FreeRTOS 怎么写了。。。回忆一下,反正就是用
xTaskCreate创建一个任务,然后启动调度器就行了吧,启动调度器的函数我也忘了,看一下tasks.c文件,搜索一下schedule。。。有点大海捞针啊,记得好像是什么startScheduler,搜这个看看。找到了,应该就是这个函数vTaskStartScheduler启动调度器,看一下代码.........xPortStartScheduler.......那包不会有错了,就是这个。OK,准备好了,创建一下任务。。。为什么我xTaskCreate函数没有代码提示啊?我明明引入了task.h了,奇怪啊?task.h里面明明声明了xTaskCreate了,为啥啊?难道是没有包含FreeRTOS.h?不应该把,这应该没影响吧?引入一下试试...我靠,还真是!666666 什么奇奇怪怪的问题。。。任务名称“LED_Red_Blink_Task”。。名字会不会有点长啊?18 个字母。。这算几个字节啊?最大任务长度是 16。。这 16 是 bit 还是 byte 还是 word 啊。。注释是说 in characters,那按字母算啊?那 18 字母超了吧?真超了吗?看看代码里面怎么处理这个。。。看到了for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )OKOK,感情我给的名称超出长度的部分会被自动裁剪掉,而且没有任何报错,那行那我就要用这么长的任务名字。不过还是有风险,以后尽量不写这么长。。。任务优先级 emmm 现在默认是 5 个优先级,是从 0 开始算还是 1 啊?看一下代码,搜索一下configMAX_PRIORITIES,找到了for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ ),好家伙,从 0 开始是吧。OK,那我给创建任务的任务最高优先级 0 吧。。。唉有点怀疑啊,还是翻一下野火的文档吧。还真是,弄错了,优先级数字越大优先级越高,跟中断服务号是相反的,行,记住了,然后因为从 0 开始,所以最高的优先级应该是configMAX_PRIORITIES - 1。总算写好了。搞个两个文件专门放自己创建的任务有关的代码,
user_task.c和user_task.h// =============================== // user_task.h // =============================== #ifndef __USER_TASK_H #define __USER_TASK_H void User_Start_Tasks(void); void APP_Create_Task(void); void LED_Red_Blink_Task(void); #endif // =============================== // user_task.c // =============================== #include "bsp_led.h" #include "user_task.h" #include "FreeRTOS.h" #include "task.h" TaskHandle_t APP_Create_Task_Handler = NULL; TaskHandle_t LED_Red_Blink_Task_Handler = NULL; void User_Start_Tasks(){ BaseType_t xReturn; xReturn = xTaskCreate( (TaskFunction_t) APP_Create_Task, "APP_Create_Task", configMINIMAL_STACK_SIZE, NULL, 4, &APP_Create_Task_Handler); vTaskStartScheduler(); } static void APP_Create_Task(){ BaseType_t xReturn; xReturn = xTaskCreate( (TaskFunction_t) LED_Red_Blink_Task, "LED_Red_Blink_Task", configMINIMAL_STACK_SIZE, NULL, 2, &LED_Red_Blink_Task_Handler); vTaskDelete(APP_Create_Task_Handler); } static void LED_Red_Blink_Task(){ uint32_t i = 0; while(1){ LED_Toggle(LED_R_GPIO_Port, LED_R_Pin); for(i = 0; i < 0x7FFFFF; i++); LED_Toggle(LED_R_GPIO_Port, LED_R_Pin); for(i = 0; i < 0x7FFFFF; i++); LED_Toggle(LED_G_GPIO_Port, LED_G_Pin); for(i = 0; i < 0x7FFFFF; i++); LED_Toggle(LED_G_GPIO_Port, LED_G_Pin); for(i = 0; i < 0x7FFFFF; i++); LED_Toggle(LED_B_GPIO_Port, LED_B_Pin); for(i = 0; i < 0x7FFFFF; i++); LED_Toggle(LED_B_GPIO_Port, LED_B_Pin); for(i = 0; i < 0x7FFFFF; i++); } } // =============================== // main.c 做了一点点修改,部分内容省略。 // 我靠,我回头一看发现怎么写到while里面了,这个要写在while外面,不过没影响,有点抽象了 // =============================== while (1) { /* USER CODE END WHILE */ User_Start_Tasks(); /* USER CODE BEGIN 3 */ }编译一下看看。emmm 有警告
..\Core\Src\user_task.c(10): warning: #550-D: variable "xReturn" was set but never used,拉倒吧,我就要定义了不用。。不管了烧录一下看看。。。又报错?!
OKOK 我知道了,刚刚灯太闪了板子电源被我关了。重新上电烧录看一下。我擦,怎么不行?emmmm,噢噢噢噢,启动任务调度器写错地方了,改一下。完美!!!LED 一样在闪哈哈哈哈。那我就纳闷了,那我这 FreeRTOS 移植没问题啊?这次移植就这么顺利吗?算了,能用就行。
-
-
忘了要做什么了,看一下前面写的内容。。。噢噢噢,这次是要能让 STM32 成为一个中介啊。那行,那我先把 STM32 和 PC 的通信调通吧。这怎么说?有手就行吧。
-
emmm 其实 HAL 库我不熟,看看
stm32f4xx_hal_uart.h文件里面都定义了什么函数吧。反正就是一个调包侠,别人有什么我就用什么。
-
懂了,那我就是用
HAL_UART_Transmit吧,简单。不过我开了 DMA 啊,得用带_DMA的函数吧?DMA 怎么用啊。。。STM32CubeMX 应该都有帮我准备好吧?看一下代码。。还真是啊,等等,怎么有这么多中断?
-
懂了,就是 DMA 满了就中断呗,那我就是发现有中断以后快进快出,然后给任务发消息应该就能拿到串口发送的数据了。看看中断服务函数是不是都写好了。

-
果真啊,太爱 STM32CubeMX 了,我看看,emmm 怎么中间都有一个
HAL_DMA_IRQHandler或者HAL_UART_IRQHandler。。不会是我想的那样吧?看看源码。。。啊?不会吧?这是。。。我记得标准库里面没有这个待遇啊。。。让我试试HAL_UART_Transmit_DMA先。随便先搞个任务创建一下// =============================== // user_task.c // =============================== static void UART1_Test_Task(){ while(1){ UART1_Test(); vTaskDelay(500); } } static void APP_Create_Task(){ BaseType_t xReturn; xReturn = xTaskCreate( (TaskFunction_t) LED_Red_Blink_Task, "LED_Red_Blink_Task", configMINIMAL_STACK_SIZE, NULL, 2, &LED_Red_Blink_Task_Handler); xReturn = xTaskCreate( (TaskFunction_t) UART1_Test_Task, "UART1_Test_Task", configMINIMAL_STACK_SIZE, NULL, 2, &UART1_Test_Task_Handler); vTaskDelete(APP_Create_Task_Handler); } // =============================== // bsp_uart.c // =============================== #include "bsp_uart.h" extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart3; extern DMA_HandleTypeDef hdma_usart1_tx; extern DMA_HandleTypeDef hdma_usart1_rx; extern DMA_HandleTypeDef hdma_usart3_rx; extern DMA_HandleTypeDef hdma_usart3_tx; void UART1_Test(){ HAL_UART_Transmit_DMA(&huart1, (uint8_t *) "Hello World.\n", 12); } -
编译烧录运行!emmm 灯闪的很勤。。但是没有串口内容输出。。难道这玩意儿不是这样用的?调试一下,断点直接放在
UART1_Test()上。。。奇怪,不会走到断点的位置。。。难道任务一直在LED_Red_Blink_Task任务里面?这个 freertos 不是说一个任务分配一点时间片吗?两个任务优先级是一样的啊,不得来回切换吗。。。把UART1_Test_Task优先级调高一级看看情况。。。我靠真是啊,会输出一次,然后就在 LED 的任务死循环了。。。我是已经把FreeRTOSConfig.h文件里面的configUSE_PREEMPTION配置的是 1 啊,抢占式的啊。然后configUSE_TIME_SLICING配置成 1 了,所以说这个时间片到底是啥意思?看看手册。。。奇怪,写的确实是相同优先级的任务会轮流运行啊。。。总不能说因为我UART1_Test_Task任务调用了vTaskDelay进入了阻塞列表了?然后一直没机会进入就绪列表???再捋一下思路,这个 freertos 是会配置 SysTick 的,让每个 Tick 都来一次中断,然后进入SysTick_Handler函数,在这个函数中去重新给判断应该给哪个任务运行。。。所以说现在应该看看SysTick_Handler在干嘛。。。
-
我靠!我就说 FreeRTOS 没配置完嘛,原来 BUG 在这里!!!SysTick 在溢出的时候确实会来到
SysTick_Handler的中断处理函数中,但是这个函数没有去调用port.c里面的xPortSysTickHandler函数!没有xPortSysTickHandler就不会进入PendSV的中断,所以就不会跳转到vTaskSwitchContext里面去切换任务!!!终于破案了!那把xPortSysTickHandler塞到SysTick_Handler里面吧!!
-
这个红色波浪线很影响观感啊妈的,这个函数明明就是定义了,非得在这报警。。。在上面加一个
extern void xPortSysTickHandler( void );试试。。。我去真的可以消掉!舒服了。。。编译!烧录!!成功了!!就是发送的文本长度算错了,应该是 13 个字符,写成 12 了。给他改成 13 就行了。牛逼牛逼! -
现在既然能发了,那能不能收呢?收要怎么弄?再搞个相同优先级的任务来,然后也是轮流看接收数据?先随便试试这个
HAL_UART_Receive_DMA函数吧。。。这个函数怎么要我提前给要接收多少的 size。。。这我哪知道啊。。不管了先随便给个数字 12,然后看看怎么个情况吧。。。有点抽象了兄弟们。没弄懂这个接收数据怎么搞的,而且这个 freertos 搞的调试起来挺不方便的啊。这样吧,先给这个输出日志的功能做好,用 freertos 的任务通知的功能,搞个任务专门用来输出日志,优先级高一些,一旦接收到打印输出日志的任务通知,就马上执行输出的任务。先这么干,磨刀不误砍柴工。-
。。。。。。有时候挺无语的,就是会突然想搞一些没意义的事。。现在好了,花了 3 个小时搞了个没啥用的日志任务。。可以根据配置的全局变量的参数,通过串口发送一般、警告、错误三个等级的日志。。。确实没啥意义。唉,写代码就经常时不时跑去搞一些奇奇怪怪的东西。不过有了这东西可以保证日志输出肯定没问题了。
// =============================== //bsp_console.h // =============================== #ifndef __BSP_CONSOLE_H #define __BSP_CONSOLE_H #include "stm32f4xx.h" #include "main.h" #include "stm32f4xx_hal_uart.h" void Console_Log(uint8_t *pdata, char* head); void Console_Info(uint8_t *pdata); void Console_Warning(uint8_t *pdata); void Console_Error(uint8_t *pdata); #endif // =============================== // bsp_console.c // =============================== #include "bsp_console.h" extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart3; extern DMA_HandleTypeDef hdma_usart1_tx; extern DMA_HandleTypeDef hdma_usart1_rx; extern DMA_HandleTypeDef hdma_usart3_rx; extern DMA_HandleTypeDef hdma_usart3_tx; void Console_Info(uint8_t *pdata){ Console_Log(pdata, "[INFO] "); } void Console_Warning(uint8_t *pdata){ Console_Log(pdata, "[WARNING] "); } void Console_Error(uint8_t *pdata){ Console_Log(pdata, "[ERROR] "); } void Console_Log(uint8_t *pdata, char* head){ uint8_t phead = 0; uint8_t ppdata = 0; char tmp_buffer[0xff]; while(phead < 0xff && *(head + phead) != '\0'){ tmp_buffer[phead] = *(head + phead); phead += 1; } while(phead < 0xff && *(pdata + ppdata) != '\0'){ tmp_buffer[phead] = *(pdata + ppdata); ppdata++; phead++; } HAL_UART_Transmit_DMA(&huart1, (uint8_t *)tmp_buffer, phead); } // =============================== // user_task.c // =============================== // 宏定义 #define CONSOLE_LEVEL_INFO (0x01 << 0) #define CONSOLE_LEVEL_WARNING (0x01 << 1) #define CONSOLE_LEVEL_ERROR (0x01 << 2) // ... // 日志输出等级 uint32_t Console_Level_Param = CONSOLE_LEVEL_INFO; // ... // 创建任务 xReturn = xTaskCreate( (TaskFunction_t) Console_Log_Task, "Console_Log_Task", configMINIMAL_STACK_SIZE, &Console_Level_Param, 4, &Console_Log_Task_Handler); // ... // 任务入口函数 static void Console_Log_Task(uint32_t* CONSOLE_LEVEL){ BaseType_t xReturn = pdTRUE; char *r_char; while(1){ xReturn = xTaskNotifyWait(0x00000000, 0xFFFFFFFF, (uint32_t *)&r_char, portMAX_DELAY); if(xReturn == pdTRUE){ if((*CONSOLE_LEVEL & CONSOLE_LEVEL_INFO) == CONSOLE_LEVEL_INFO){ Console_Info((uint8_t *)r_char); }else if((*CONSOLE_LEVEL & CONSOLE_LEVEL_WARNING) == CONSOLE_LEVEL_WARNING){ Console_Warning((uint8_t *)r_char); }else{ Console_Error((uint8_t *)r_char); } } } } // 使用方式:在其他任务中调用xTaskNotify函数,可以向日志任务发送字符串,最多不要超过245个字符。 // char *pstr = "Hello World.\n"; // xTaskNotify(Console_Log_Task_Handler, (uint32_t)pstr, eSetValueWithOverwrite);
-
-
OK 日志输出的任务完成了,刀磨好了,下面还得实现串口输入的功能啊。这个接收我其实不是很能理解啊,首先什么时候可以接收?肯定是上位机发送消息的时候才去接收吧。那就是需要有一个任务去检查串口有没有接收到数据。如果只看寄存器那就是看 USART_SR 寄存器的 RXNE 位的值。可是在 HAL 库中要怎么看呢?加上了 DMA 又怎么看?既然配置了 DMA,那么就当接收到数据以后 DMA 自动就会帮我先暂存一下数据吧。那我就是在每次 DMA 满了的时候去取一下数据就好了吧。
-
我靠见鬼了,想要自己摸索 HAL 库的串口接收函数到底怎么用,摸索一晚上了,还是没弄明白,眼看着天快亮了。。。先睡觉吧。。。
-
好了,睡了 5 个小时,醒来是中午 12 点了,没啥胃口,继续研究这个 HAL 库的串口吧。这回还是不头铁了。直接百度查一下 HAL 库的串口接收怎么用吧(参考了这篇文章 STM32HAL 库---串口 DMA 发送接收详解_stm32 串口 dma 接收-CSDN 博客)。。。emmm 把
HAL_UART_Receive_DMA换成HAL_UARTEx_ReceiveToIdle_DMA就能用了。就这问题困扰老夫这么久?这说明什么?说明基础薄弱,核心知识掌握得较差,还是重新捋一下使用 DMA 接收串口信号的流程吧。。再回顾一下串口接收数据和怎么结合 DMA 使用:-
串口接收数据:裸机并且不用库实现串口接收的时候,主要就是看 USART_SR 寄存器的 RXNE 位,要么轮询要么中断,而我们根本不用考虑这个数据是如何到 USART_DR,只要无脑等 RXNE 就行。等到了 RXNE 以后,只要从 DR 寄存器读取数据,RXNE 就会自动被硬件清空。因此手写寄存器的时候读数据只有两步:等 RXNE,读 DR。
-
使用 DMA 搬运串口数据:加入了 DMA 以后,那就是连等 RXNE 和读 DR 都省了。把 DR 在哪、数据放哪告诉 DMA,其他就不用管了。接下来要么轮询 DMA 的 TCIFx 要么等 DMA 通知。那使用 DMA 就变成了等 DMA 消息了啊。。。这我就纳闷了,没啥毛病啊。那 HAL 库到底是怎么实现的
HAL_UART_Receive_DMA功能啊??不对,前面说等 DMA 标志位就好了,那如果等到了标志位以后,我是不是得手动清除这个标志位啊?不会吧?看一下 UART 或者 DMA 代码里面有没有清除 TCIFx 标志位的函数。。。看了 HAL 库提供的中断服务函数,里面会清除中断标志位啊。。。那这就怪了,是我代码有问题吗??重新调整一下代码。。。还是用HAL_UART_Receive_DMA来试试。。static void Console_Read_Task(){ HAL_StatusTypeDef sTatus; char rData[128] = {0}; char *error; while(1){ if(HAL_UART_GetState(&huart1) == HAL_UART_STATE_READY){ Console_Level_Param = CONSOLE_LEVEL_ERROR; // sTatus = HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rData, 128); sTatus = HAL_UART_Receive_DMA(&huart1, (uint8_t*)rData, 128); if(sTatus == HAL_OK){ Console_Level_Param = CONSOLE_LEVEL_INFO; xTaskNotify(Console_Log_Task_Handler, (uint32_t)rData, eSetValueWithOverwrite); }else if(sTatus == HAL_ERROR){ error = "HAL_ERROR\n"; xTaskNotify(Console_Log_Task_Handler, (uint32_t)error, eSetValueWithOverwrite); }else if(sTatus == HAL_BUSY){ error = "HAL BUSY\n"; xTaskNotify(Console_Log_Task_Handler, (uint32_t)error, eSetValueWithOverwrite); }else if(sTatus == HAL_TIMEOUT){ error = "HAL TIMEOUT\n"; xTaskNotify(Console_Log_Task_Handler, (uint32_t)error, eSetValueWithOverwrite); }else{ error = "未知错误\n"; xTaskNotify(Console_Log_Task_Handler, (uint32_t)error, eSetValueWithOverwrite); } }else{ vTaskDelay(50); } } }测试一下。。这就怪了,现在能用了。。难道是因为我之前是吧
vTaskDelay(50);写在 if 外面?改一下试试。。。还是能用啊?我靠为啥啊?把外面一层 if 扒掉。。
调试结果虽然很抽象,得发到足够数量的数据才会显示,但是起码还是能接收到数据啊。。。(突然反应过来)难道是因为
char rData[128] = {0};这个?我一开始初始化变量用的是char *rData;或者char *rData = "Hello";这样的。。莫非真是因为这个,导致提供给 DMA 的内存存储器地址有问题?改成char *rData = "Hello";试试。。还是能用啊?只不过输出的都是Hello,但是我只要有发够内容就会返回啊。。见鬼了啊,睡一觉醒来。。还有一个可能,我在另一个任务中也是不停调用Console_Log_Task这个任务,会一只占用 DMA,但是这个代码上午被我删了,我加回去试试。。。还是能用。。。啊啊啊啊啊不管了,现在能够怀疑的可能就是我之前测试的时候给板子发送的数据没够,所以一直都没显示。。。找不到问题了,不管了,能用就行。。。太抽象了真的。不过现在既然串口正常可以用了,把代码整理一下,顺便完成对 WiFi 的控制吧
-
-
优化了一下接收上位机发送数据的任务,现在功能简单,就是判断是不是有接收到数据,接收到了就原封不动返回给 PC 就好了。
static void Console_Read_Task(){ HAL_StatusTypeDef sTatus; char rx_buffer[128] = {0}; char *error_msg; while(1){ sTatus = HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t*)rx_buffer, 128); if(sTatus == HAL_OK){ Console_Level_Param = CONSOLE_LEVEL_INFO; xTaskNotify(Console_Log_Task_Handler, (uint32_t)rx_buffer, eSetValueWithOverwrite); }else{ vTaskDelay(100); } } }
-
-
可算是把串口读取的任务实现了,那现在就是判断上位机发过来的到底是什么玩意了,根据上位机发送的不同的指令来执行不同的操作。我准备这样规定一下,因为功能不多只要能给 WIFI 发信号就行,那就判断如果接收到上位机发送的字符串的前 2 位是不是 AT,如果是那就无脑把收到的指令转发给 WiFi 模块,不是就返回
无效指令。开干!-
先吃个饭。。。吃完饭有点犯困,躺了一会儿没睡着,起来继续干了。。。
-
改造
Console_Read_Task任务,使其能够识别 AT 指令并执行转发。如果不是 AT 指令就先原封不动输出吧,这个以后也可以改造成别的指令。这部分代码可以写好一些,后期裸机写 boot 程序的时候也能直接复制过去用。。。。 -
emmmm 代码写着写着写偏了,跑去搞一些奇奇怪怪的事。。。先重新规划一下。按照最初心里想的架构,freertos 任务不应该直接去使用 HAL 库,应该通过使用 bsp 里提供的代码来调用串口输出功能的(我自己规定的)。那现在这个任务就不符合我心中的规范了,应该先重构一下。那重构一下就感觉
Console_Read名字有点不够屌不够精准啊,准备改成Console_Wait_Command,这样指向性明确,很清楚就是等待控制台指令的意思。。。。唉感觉这个名字也不太对,等待的操作应该是由任务来执行的吧,和这个没关系,还是叫做Console_Get_Command吧。。。一通操作可算是写好代码了,烧录一下试试。。。

怎么乱码啊??? 噢噢噢应该是编码格式问题,代码文件是 GB2312 格式,里面的中文也是 GB2312 编码的,而野火串口调试助手的接收区编码格式现在在 UTF-8,所以中文显示有问题。
右上角设置->编码设置改成GB18030就好了。。。。emm 那乱发指令显示未知是正常的,可是我发个 AT 出去,给我返回 AT 是没错,可是怎么后面还跟着456789012345678???啥毛病啊??不过这个感觉一看就是发送的数据缓冲区有脏数据,给他改改。。。不对,发送缓冲区的数据是我直接给的接收缓冲区的地址,那就是接收有问题啊。。。可是接收的代码接收完字符串肯定是有'\0'才对啊,这不应该是接收有问题啊。。。噢噢噢噢噢噢!!发送的代码的时候计算字符串长度的时候出错了!因为是用while(xxx != '\0')来判断是不是到字符串的尾部,完了就跳出循环,长度忘记算上'\0'了。。。。不对。。。还是有问题。。那还是接收缓冲区有问题啊,真就奇了怪了。。。。噢噢噢噢噢,因为发消息的时候我会拼上一个头部,所以申请了一个新的地址来装拼接好的消息,而这个拼好的消息没有用'\0'收尾!破案了。。。吗??还是有问题啊!!!!直接 DEBUG 模式吧,我真受不了了。。。 -
破案了!实锤就是接收的函数有问题了!接收完成以后,并没有这堆字符串加上
'\0'作为结尾,而是直接把我发送的内容直接写到缓冲区了,还写了\r\n难绷啊,那这个算 1 个还是 2 个字符啊?
那找到问题了,该怎么修复啊?我哪知道要接收多长的数据啊?为了保证接收到的数据是原汁原味的,我决定还是在
Console_Readlines函数内部创建一个新的缓冲区来存数据,完事来个偷梁换柱,把传进来的指针的地址改成新地址。试试看。。。我靠怎么内容全变成 0 了?我靠我数据呢???。。。。 -
崩溃了,打断点一个一个去试吧。。。。。。哎,现在的问题好像是我通过串口发送数据,并不会发个
'\0'给 STM32 啊,所以 DMA 也不会往内存空间里面写'\0',因此我在打印输出的时候没有办通过判断是不是到了'\0'来结束输出。。。问了一下豆包,还真是。。事到如今。。还是看看豆包怎么建议吧。 -
有了豆师傅的指点,现在简直醍醐灌顶啊,豆包建议我用
HAL_UARTEx_RxEventCallback去判断接收到数据的长度。我看行,就这么办。唉,吭哧吭哧干了两三个小时,可算是弄好了,有点无语了,一个串口调试了 2 天,总算写出了让人满意的代码了。现在基本上可以保证 PC 和 STM32 正确通信了。已经晚上 11 点半了,先睡觉。明天去玩,后天再做了。
-
昨天 18 号玩了一天。。今天激情褪去,有点不想干了。害,不想挂逼了,先硬着头皮继续整吧。。目前进度是串口和 PC 通信是正常了,那可以把同样一套代码用到 UART3 上,这样就可以让 STM32 和 WIFI 模块通信了。先 CV 一下。同样还是创建两个任务,一个任务用于发送 WiFi 指令,另一个任务用于接收 WiFi 发送的消息,同时为了支持这两个任务,给 BSP 也配置上 WiFi 功能。STM32 接收到上位机发过来的命令,解析后发现是 AT 指令就直接发给 WiFi 模块并等待返回结果。
//bsp_wifi.c #include "bsp_wifi.h" extern UART_HandleTypeDef huart3; uint8_t* WIFI_Send_Command(uint8_t *pdata, uint16_t size); static uint8_t* WIFI_Receive_Message(void); uint8_t wifi_rx_buffer[WIFI_MAX_BUFFER_SIZE] = {0}; // WIFI指令发送,向WiFi模块发送指令,目前最大仅支持接收256个字符,即512字节。 uint8_t* WIFI_Send_Command(uint8_t *pdata, uint16_t size){ HAL_UART_Transmit_DMA(&huart3, (uint8_t *)pdata, size); return WIFI_Receive_Message(); } // WiFi指令接收,接收WiFi模块发回的数据 static uint8_t* WIFI_Receive_Message(){ HAL_StatusTypeDef sTatus = HAL_UARTEx_ReceiveToIdle_DMA(&huart3, (uint8_t*)wifi_rx_buffer, WIFI_MAX_BUFFER_SIZE); if(sTatus == HAL_OK){ return wifi_rx_buffer; }else{ return NULL; } } // 修改了user_task.c static void Console_Get_Command_Task(){ Console_Command_Type cmdType; // 收到的指令类型 char *msg; // 通过控制台输出的消息 while(1){ cmdType = Console_Get_Command(); // 调用 if(cmdType == Console_Command_WiFi){ // 检测到接收的是WiFi指令的任务,那就发送WiFi指令给WiFi模块并等待结果。 msg = (char *)WIFI_Send_Command(console_rx_buffer, console_rx_buf_size); if(msg != NULL){ xTaskNotify(Console_Log_Task_Handler, (uint32_t)msg, eSetValueWithOverwrite); }else{ Console_Log_Task_Param.Console_Level = CONSOLE_LEVEL_ERROR; msg = "WiFi通信异常,接收数据为空!\n"; xTaskNotify(Console_Log_Task_Handler, (uint32_t)msg, eSetValueWithOverwrite); } }else if(cmdType == Console_Command_Unknown){ Console_Log_Task_Param.Console_Level = CONSOLE_LEVEL_WARNING; msg = "未知指令\n"; xTaskNotify(Console_Log_Task_Handler, (uint32_t)msg, eSetValueWithOverwrite); } vTaskDelay(100); // 100ms应该不会太久吧,反正也不是电竞应该不需要高响应。。。 } }烧录测试一下,发现不行。。。emmm 噢噢噢噢忘记给 WiFi 初始化了,应该是要给 WiFi 先使能才对,在 BSP 里面加个一个 wifi_init 的函数并在 main 刚开始的时候调用。。。

还是不行。。。怪了。。开 debug 瞧瞧。。。怪了,指令都正常发送并且数据都正确,但是没收到任何内容。。。这又是哪里出问题了呢?给 WiFi 的 EN 引脚测一下电压看看,3.3V 左右。。。怪了。。对了,WiFi 模块还有一个 RST 也测试一下。。PG15。。怎么是低电压?看看 WiFi 模组的手册。。低电压有效。。我靠那 WiFi 模组是处于复位状态啊,这不行,耶得给他使能了。。。哈哈哈哈完美,成功了...不对,高兴太早了,怎么第一次收到 WiFi 模组返回的消息是空的啊?怎么上一次的指令这一次才返回给我啊?。。怎么。。噢噢噢噢噢,应该是忘记去回调函数里面配置串口 3 返回的数据内容的长度了,嗯,修改一下。。。我靠我拿到了长度以后,怎么好像在别处的代码都没有用啊?感觉代码变成一坨狗屎了。。。
经过了一番修改,输出的文本长度混乱的 bug 解决了,,现在还有接收的指令是上一次发送的返回。。这个问题应该是要加个等待来解决,我目前判断 DMA 完成接收的方式是判断
HAL_UARTEx_ReceiveToIdle_DMA是不是给我返回了HAL_OK,但实际上这个 HAL_OK 具体可能在哪些情况下出现,这不太清楚。要么问豆包要么看源码。。我觉得还是问问豆包吧。。果然,豆包说HAL_UARTEx_ReceiveToIdle_DMA返回HAL_OK只是说明这个函数调用成功,不代表 DMA 接收到有效数据。不过其实我也知道有这个问题,一直装作没看见,因为前面测试的时候已经发现了,如果发送的内容长度超出定义接收的长度就会触发中断,我没有进行任何处理,接收的数据会被截断,我就是偷懒不想改一直无视罢了。。。现在既然问题已经摆在眼前,那也只好先解决了吧。。。。。。。。。。。。我感觉我好像还是对
HAL_UARTEx_ReceiveToIdle_DMA函数的理解不够深刻,所以现在会遇到这么多问题。。。OK,重新研究了一下HAL_UARTEx_ReceiveToIdle_DMA函数的逻辑:这个函数第一次调用的时候,就会切换到等待串口接收数据的模式,只要正确切换模式,就会返回HAL_OK,但是这个时候其实可能还没有接收到数据,只有在HAL_UARTEx_RxEventCallback回调函数被触发的时候,通过判断当前是空闲、溢出还是半满的状态,这时候才知道到底是什么情况。而如果一直没有接收到数据,那么后续HAL_UARTEx_ReceiveToIdle_DMA被调用都会返回一个HAL_BUSY的值。。。总之我原有的判断逻辑实际上有很严重的错误,现在推倒重来,从最简单的开始:// user_hal_callback.c // 接收数据完成的回调函数 void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){ // 接受完的回调函数,判断是空闲 HAL_UART_RxEventTypeTypeDef rxStatue = HAL_UARTEx_GetRxEventType(huart); if(rxStatue == HAL_UART_RXEVENT_IDLE){ if(Size > 0){ if(huart == &huart1){ console_rx_buf_size = Size; console_rx_status = 1; }else if(huart == &huart3){ wifi_rx_buf_size = Size; wifi_rx_status = 1; } } }else if(rxStatue == HAL_UART_RXEVENT_TC){ // 如果是接收溢出了的处理方案 }else if(rxStatue == HAL_UART_RXEVENT_HT){ // 如果是半满了的处理办法 } } // user_task.c static void Test_Uart_Task(){ HAL_StatusTypeDef rx_state; HAL_StatusTypeDef tx_state; while(1){ rx_state = HAL_UARTEx_ReceiveToIdle_DMA(&huart1, console_rx_buffer, CONSOLE_MAX_BUFFER_SIZE); while(console_rx_status == 0){ vTaskDelay(10); } console_rx_status = 0; tx_state = HAL_UART_Transmit_DMA(&huart1, console_rx_buffer, console_rx_buf_size); } }这个代码可以说是相当的简单了,刨去了所有毫无意义的判断,数据的就是就是等待接收到数据并来到空闲的状态。这次代码经过了测试已经完全没有任何问题了!代码还是应该要简单,现在就把这个测试的代码替换到原代码中。
-
试来试去还是不得劲呐,突然想干脆放弃这堆狗屎代码重构一下吧,因为突然发现自己又犯了以前常常会犯的错误,就是学了点什么东西就硬要全部都用上,不用就感觉有点亏了。。比如我真的有必要去专门为串口输出创建一个任务,并通过通知的方式来输出吗?似乎真的毫无必要,在需要输出的时候输出就好了吧。。。这狗屎代码就是让人不舒服,还是重构一下吧,毕竟现在相比三天前还是提升了不少,,应该能写出比三天前更好的代码吧。。先把狗屎代码都清除掉,重新规划好代码应该怎么写。开始优化代码!
功夫不负有心人啊。。在简化了代码以后,现在是彻底能用了,笑死我了。每次写代码都会犯同样的错误,就是为了“炫技”去写一些复杂代码,最终都会因为自己太菜了被自己坑了。。以后还是不要这样了,代码能正常使用才是真理!!
// 现在只保留LED闪烁和控制台输入的任务,这个是等待控制台输入的任务 static void Console_Wait_Command_Task(){ Console_Command_Type cmdType; // 收到的指令类型 Console_Status_Type cStatus; char *msg; // 通过控制台输出的消息 while(1){ cStatus = Console_Readlines(); if(cStatus == CONSOLE_OK){ Wait_Tx_Rx(&console_rx_complete, 100); cmdType = Console_Parse_Command(); if(cmdType == Console_Command_WiFi){ WIFI_Send_Command(console_rx_buffer, console_rx_buf_size); Wait_Tx_Rx_Forever(&wifi_tx_complete); // 和WiFi有关的操作,不知道为什么不能让任务调度,一调度就不能用 WIFI_Receive_Message(); Wait_Tx_Rx_Forever(&wifi_rx_complete); // 和WiFi有关的操作,不知道为什么不能让任务调度,一调度就不能用 Console_Send_Data(wifi_rx_buffer, wifi_rx_buf_size); Wait_Tx_Rx(&console_tx_complete, 100); }else if(cmdType == Console_Command_Unknown){ msg = "==> error: 未知指令,无操作\n"; Console_Send_Data((uint8_t*)msg, 0); Wait_Tx_Rx(&console_tx_complete, 100); } } } } // 等待发送或者接收,完事会清除标志位 void Wait_Tx_Rx(uint8_t *flag, uint32_t delayTime){ while(*flag != 1){ vTaskDelay(delayTime); } *flag = 0; } void Wait_Tx_Rx_Forever(uint8_t *flag){ while(*flag != 1); *flag = 0; }而且这个任务拿到了指令以后就直接处理了,不去调用各种乱七八糟的函数了 hhhhh,先能用,优化的事以后再说了,相信未来自己的智慧。😅
-
-
这下串口数据转发的工作也完成了,真没想到一个串口搞了三天。。。不过也好,起码裸机程序到时候应该是不会踩坑了。,啊接下来就是试试给 WiFi 做个配置,然后通过串口调试助手中的网络调试助手来看看能不能正常通信了。
-
配置 WiFi:作为 AP 模式吧,然后让上位机作为客户端去发送数据。。。

测试的过程中发现功能是能用,但是又有新问题了。。。还不止一个。。
- 第一个问题:现在我的代码是给 WiFi 发送一次数据才能接收一次。但是如果 WiFi 作为 AP 模式,那 PC 如果持续要发送消息,就没法持续接收了。。这个 bug 得修复啊。
- 第二个问题:如果作为 Station 模式,那在要请求发送消息以后,如果发送的消息开头不带 AT,这个消息就不会转发给 WiFi 设备。这个问题太抽象了,也得修复啊。。。算了,先放着,串口搞了这么久真的有点崩溃了,先弄一下别的吧。这个代码先提交到 git 上,不然到时候代码丢了就难受了。。
- 优化点:开启 AP 模式或者开启 station 模式要通过串口发送很多内容,要是能直接写成一个函数直接一键调用就好了。
-
让 PC 能够通过网口进行通信
-
这部分好像是要先学会 LwIP,不懂,没学过,先花点时间学一下。。先不学了,看世界杯,明天再开始学。。
-
OK 今天也是学起来了,已经 20 号了,算上 16 号开始,18 号休息,IAP 这是进行到第 4 天了,希望今天可以把 LwIP 移植并且运行起来。为了能够快点用上,我选择跳过大部分内容,只了解一些基本概念,然后把最基础的功能满足了就行了。
-
LwIP 是常于嵌入式的开源轻量化 TCP/IP 协议栈,支持的协议比较完整。支持 ARP、ICMP、IGMP、UDP、TCP、PPP、DNS、DHCP、IP、SNMP、AUTOIP 等。目前常见的网络通信功能都比较能够支持。
-
3 种编程接口:RAW、NETCONN、SOCKET。
-
我现在只需要移植过来,然后能用就行了,所以让 AI 帮我总结了一下重点关注哪些内容:
-
基本能力
- 完成裸机 / RTOS(FreeRTOS/RT-Thread)双环境 lwIP 完整移植;
- 网卡驱动适配(ETH/USB 网卡 / WiFi SPI 网卡),能获取 DHCP 动态 IP、静态 IP 配置;
- 基础协议跑通:ARP、ICMP(ping 通 PC)、UDP/TCP 单连接收发;
- 上层 API 使用:RAW API / NETCONN API 二选一实现和 PC 双向数据收发;
- 可稳定连接路由器,局域网内和 PC 网络调试、数据互传。
-
核心文件
src/core/init.c // lwIP初始化总入口,移植第一步必看 src/core/mem.c // 内存堆管理,移植内存参数配置依据 src/core/memp.c // 内存池,TCP/UDP数据包缓冲池 src/core/pbuf.c // 数据包载体pbuf,网卡收发底层依赖 src/core/ip.c // IPv4基础收发、路由 src/core/arp.c // 地址解析,局域网通信必备 src/core/icmp.c // Ping实现,验证链路通断 src/core/udp.c // UDP协议栈实现 src/core/tcp.c // TCP基础收发逻辑(入门只看连接、收发部分) src/core/netif.c // 网卡抽象层,移植网卡驱动核心 src/api/netconn.c // 推荐入门API,封装好、易上手 src/api/api_msg.c // RTOS下netconn消息通信 -
调用层看懂接口
src/include/lwip/xxx.h // 全部头文件,看懂宏、结构体、函数声明 src/netif/ethernet.c // 以太网通用封装,看懂输入输出接口 ports/xxx/arch.c // 平台移植层(临界区、系统延时、内存拷贝) ports/xxx/sys_arch.c // RTOS适配层:信号量、消息队列、线程封装
-
-
。。。算了,还是认认真真读一读野火的《LwIP 应用开发实战指南》吧。
- hhhhh 看了一天看是没看完,PDF 软件还有笔记软件倒是研究了半天。本人平常比较浮躁,不喜欢看视频,都直接看的书,所以经常用到 PDF 阅读,目前用的是 Sumatra PDF,非常轻量化,非常好用,但是功能很简陋,给 PDF 高亮的话还可以,但如果要写一些批注就麻烦了。所以就想着搞个好点的 PDF 阅读器,这阅读器要是真好用,付钱也可以啊。所以就去找,一开始用的是思源笔记里面的思阅插件,开一个 PDF 文档内存直接飙到 4GB,但是是真的好用啊,而且本人是思源笔记重度用户,这个文档也是思源笔记记录的,但是内存占用实在太大了,所以考虑用下别的。。。然后又是必应搜索又是豆包的,试了 Zotero、Koodo、Okular 感觉都差点意思,Koodo 是真花哨啊。。。最后发现没啥可用的,还是回头去看这个思源笔记的思阅插件,结果发现我目前的插件版本有点旧了 hhhhh 上个月装的,没想到作者一个月内更新了三四次,直接从 1.3.7 升级到了 2.1.4 了。不得不说,这个升级直接解决了这个内存占用的问题,而且还变得更好用了,我服了。强推思源笔记,真的,如果有需要我可以写一篇思源笔记的使用心得。
- 不行,这个思阅插件的 bug 还是有点多,这会儿打不开 PDF 了。。。还是先回归古法 Sumatra PDF 阅读了,加了思阅的 QQ 群反馈反馈,,,
-
回归正题。。还是 LwIP,野火的书我是看到了有操作系统的移植了,自己花了一晚上移植,五六个小时,都天亮了,最后都没有移植成功。不知道该怎么调试,有点累了。首先我因为是 CubeMX 生成的代码,HAL 库等级比野火提供的 HAL 库等级更高。。对照着看有挺多地方不太一样,没法照搬,但是硬看又看不懂。。而且我不知道我的板子现在是哪个部分没有初始化好,到底是硬件没初始化还是 LwIP 没有初始化还是底层驱动我没有写清楚还是 LwIP 的配置有问题还是基于 FreeRTOS 的情况下有问题。。头疼。。事实证明还是应该老老实实先用野火的示例代码来学习吧。。今天 21 号了,起床已经中午 12 点半了。。我真的不知道该怎么调试了,总不能把 HAL 换成野火一样的版本然后把代码全部复制过来吧。迷茫了。。。我决定了,还是先自己硬着头皮调试吧,今天要是还是调不通就直接照搬野火的代码吧。。。不能太浮躁,规划一下如何排查错误,下面是记录一下调试的思路:先试试无操作系统无 lwip 纯硬件初始化 ETH 看看能不能成功;再裸机移植 lwip 测试一下能不能通;最后再把 freertos 加进来。这样控制变量一步步排查看看到底是哪一步出了问题。同时现在的代码也先在 git 上创建一个分支来保存修改的过程,做好版本控制。
-
纯裸机,无 freertos 无 lwip 测试
-
因为 git 版本已经做好了控制,所以代码可以肆无忌惮地修改了,现在 main 函数里面把 freertos 和 lwip 有关的内容全部注释掉,仅保留 BSP 的一些初始化代码,然后初始化 ETH 看看。。。。。
-
啊啊啊啊啊没有精神气了,不想搞了,直接 copy 野火的 HAL 库过来改吧。。崩溃了。。总而言之会走到这一步还是因为自己太浮躁了,没有一步一个脚印耐心看完 LwIP,然后一步一步做实验。害,太急了,但是我不想慢下来。还是快点先把 lwip 调通再说吧。不管了,先抄代码了,调通以后再琢磨,不然被 HAL 版本卡住有点脱离主线。嗯。。。核心还是要学习 LwIP,而不是研究 HAL 库不同版本的特性,当务之急还是先把 LwIP 调通了。。
-
OK,HAL 库被我从 1.8.5 暴力改成 1.7.3 了,先和野火的案例对齐了。。然后细节部分的代码一个一个 copy 过来。。。果然问题还是出现了,1.7.3 版本的 HAL 库没有
HAL_UARTEx_ReceiveToIdle_DMA这个函数。。。这意味着我前面调串口都白干了额。。。还是不能头铁钻牛角尖非得去用这个跟教程不一样的版本啊。。。先学会走,再学跑。。吃一堑长一智,以后不这样了。。。现在得把以前的代码都删了去适应这个 HAL 库了唉。😅
调整 HAL 库的报应来了。。。1.7.3 的 HAL 有好多函数都没有啊,要崩溃了啊啊啊啊啊,怎么
HAL_UART_RxEventTypeTypeDef也能undefined,,,无语了,呼,冷静,先吃一顿好的缓解一下情绪。。。66666 移植的过程中发现 FreeRTOS 的版本不一样,也出现了一些问题,比如QueueHandle_t这个用在 lwip 兼容层做创建信号量的时候返回的数据类型,在野火的版本的 freertos 中是void *的类型,而我用的版本是struct QueueDefinition *类型。。我不知道说啥了。。看了一下,我用的 freertos 版本是 11.3.0,野火的是 9.0.0。emmm 事实证明初学没有特殊情况最好还是别用最新版本,会出现一些无法掌控的问题。。。现在咋办?我想发是先做个适配吧,如果问题更多了再改一下版本,毕竟 freertos 是正经学过啊。 -
我靠我服了,我都已经把 HAL 的代码全部复制过来,而且还把 sys_arch、ethernetif、lwipopts 之类的移植有关的文件全部都照抄过来了,还是没法用啊,奇怪了。。。现在有区别的地方是 freertos 版本和 lwip 版本了,freertos 目前没问题,lwip 版本看了一下,都是 2.1.2。。怪了。。沃趣,野火的案例也不能用了。。更怪了我的天啊。。我网络肯定没有配置错啊,静态 IP 配置 192.168.31.200,网关 192.168.31.1,子网掩码不动,没啥问题啊,而且 IP 也没有被占用啊。。怪了,之前试过明明可以啊。。。重新复制一份野火的代码出来。。。还是不行。。。我目前用的是开发板作为服务器的模式,用的是 lwip_tcpecho 里面的代码,但是配置静态 IP 没法用啊。。。试试改成 DHCP?emmm 板子跟我说拿到了 IP 地址,但是路由器管理页面上面明明没有板子啊。。而且服务我也建立不了。。有点抽象了,真不知道该怎么办了。。。但是让板子作为 client 又可以用。为啥啊?要是我自己写的代码没法用那都合理,可是为什么野火给的例程也不能用啊?而且更匪夷所思的是昨天明明可以用。。。总不能我路由器有问题吧???那我也没条件换路由器啊。。。
-
卧槽,可以 ping 得通!

-
之前我一直都在用野火的那个串口调试助手里面的网络调试助手,昨天能连上,但是后面软件打不开了,我把配置文件改了一下可以打开了,现在就一直连不上,我怀疑是野火的串口调试助手有严重的 BUG。。。为了验证是不是因为这个问题,我重新下载一下野火的调试助手。。。emmm 还是不行,那就说明不是野火的串口调试助手有问题??

-
我真的是无语了,野火的串口调试工具有问题啊,我换成正点原子的网络调试助手就能用了,666666。避雷了兄弟们,搞 LwIP 要调试的时候记得用不要用野火的网络调试助手(v1.0.3)。截止目前 2026-07-21,野火最新版本的多功能调试助手依然有这个 bug,触发的条件应该是第一次使用 TCP Client 模式连接开发板以后,如果没有停止监听就直接关掉软件,就会触发这个 bug,这个 bug 会导致第二次无法打开多功能调试助手。如果删除软件重新下载,则会导致网络调试助手功能无法使用。
-
而且这部分踩坑点还挺多的,就是路由器实际上也会有问题。我后面改成 DHCP 来动态获取 IP 地址了,如果配置成静态 IP,我没试过行不行,因为卡在野火调试工具的那个 bug 了。我的路由器管理页面下,如果开发板没有数据包的传输,在路由器设备状态查看中会看不到设备。。。可能是路由器固件问题吧,我是用的红米 AX6 路由器,官方固件。。所以根据这个经验可以得出,板子的网络到底能不能通,还得看 ping 能不能 ping 通了。
-
烧录进自己的代码,绝了,可以用了。果然天无绝人之路啊。那 LwIP 就先到此为止了,能用就行,TCP 回显就够了。
-
-
-
boot 程序:等待若干秒,等待过程中如果收到上位机指令,跳转到永久等待模式,等待接收上位机下发的 app.bin
-
这部分内容印象中好像就是 STM32 大师篇里面介绍的,但是具体怎么操作全忘了。。这会儿得先复习总结一遍以后再来弄了,重点还是如何将 APP 应用搞到别的 ROM 地址,然后 boot 程序去跳转了(凭感觉应该是要有个分区表啥的,然后通过函数入口来直接跳转)。然后好像也就这些内容了,其他的应该没啥困难了吧。。
我靠,看了一下我的
.map文件,我的这个程序已经占用了这么多 123.34kB 内存空间了,太牛了。。看了一晚上这个 MDK 部分的内容,感觉就.map、elf输出的文件比较重要,其他的好像不怎么用得上啊。然后还有一个什么.bss不知在哪,感觉也挺重要的,但是还没看到。然后程序二进制文件的大小就是最底下这个 ROM Size,占用了 96.45KB。而 STM32 的 ROM,好像有 7 个扇区都是 128KB 的啊,这空间妥妥够的啊。
现在看到了
.sct文件了,不得不说啊这个文件才是控制程序存放在什么位置的关键吧?我现在已经有思路了,就是指定 boot 程序在地址的开头,剩余的全部都一股脑丢到另一个节区里面。不知道能不能行,等下搞个简单的项目试试。。。OK,看完了
.sct文件,感觉自己现在强的可怕,先搞个小实验试试。比如就按 IAP 简化版的要求,给 FLASH 划分 3 个区。先搞一个项目,这个项目的目的是能够将 FLASH 空间分为 3 块,然后主要是 boot 程序,支持通过串口将 BIN 文件写入到 APP 区域,然后将 APP 复制到运行区,每次开机会自动跳转到运行区来执行代码。然后在搞一个项目,这个项目专门就是只写 APP 程序并生成 BIN 文件,别的不管。。。不知道这个思路对不对,我也没去网上查 IAP 到底是咋实现的。先按自己想法来吧,后面如果不行再改。(感觉我的这个思路就是类似 Linux 里面的 bootloader、rootfs 的思想了)笑死我了,搞了几个小时,验证了自己的想法不对,哈哈哈哈哈。我的思路是这样,boot 就是主程序,通过主程序可以将 bin 文件发送到指定的 ROM 区域,然后主程序在运行了之后会跳到对应 ROM 的区域然后去执行。这个 bin 文件怎么生成?很明显就是整个项目编译以后通过
fromelf指令生成的。因此我搞了一个电容触摸的实验的 bin 文件作为烧录的测试固件。然后编写 boot 代码,将 bin 文件通过串口写入到 08040000 起始的地址。我将 FLASH 分成 3 个空间,boot 程序占用 0x08000000-0x0803FFFF 一共 0x00040000 的空间,然后运行区和 APP 区平均分剩下的空间。然后编写一个入口函数,入口就是这个 08040000。这样非常完美了,当 boot 程序正常运行的时候,就会执行入口函数进入 08040000 这个地址空间然后去执行这里的代码。理论上好像很完美啊 hhhhh,函数确实跳转过去了,但是最后会跳到Hard_Fault的中断去哈哈哈哈哈。。。卧槽,问了一下豆包,发现我这个思路没问题啊,问题出在我是通过函数跳过去(我把某个函数定义到 0x08040000),这样很抽象,中断向量表啥的不匹配。要在 APP 程序里面把中断向量表配置好,然后在 boot 程序里面写好 SP 和 reset_entry 的指针,通过汇编代码给切过去,,然后真的就成了啊。我当时是想过这个问题,但是没想到真的是这样啊。还是豆包师傅教得好啊。这就是 boot 程序里面跳转到 APP 代码的关键代码了。
#define APP_ADDR 0x08040000UL typedef void (*AppResetHandler)(void); __asm void IAP_JumpToApp(uint32_t ulAppMSP, uint32_t ulAppResetEntry) { /* *INDENT-OFF* */ PRESERVE8 ; R0 = ulAppMSP ; R1 = ulAppResetEntry MSR MSP, R0 BX R1 /* *INDENT-ON* */ } void jumpAPP(){ uint32_t app_sp = *(volatile uint32_t *)APP_ADDR; uint32_t app_reset_entry = *(volatile uint32_t *)(APP_ADDR + 4); // ========== 校验APP镜像合法性 ========== // F4 SRAM范围 0x20000000 ~ 0x2002FFFF(192KB) if ((app_sp < 0x20000000UL) || (app_sp > 0x20030000UL)) { // APP无效,直接返回,不跳转 return; } if (app_reset_entry < APP_ADDR || app_reset_entry > 0x080FFFFFUL) { return; } // ========== 4. 载入APP主栈指针 MSP ========== IAP_JumpToApp(app_sp, app_reset_entry); // ========== 5. 跳转至APP复位服务函数 ========== ((AppResetHandler)app_reset_entry)(); // 理论永远执行不到这里 while(1); } -
完美,把可以用 LwIP 和 FreeRTOS 的固件烧进去看看。。。emmm 不行,为啥啊。。。调试一下,,怎么时钟初始化出问题了?噢噢噢噢懂了,就是代码切换走之前先把时钟切回 HSI,不然 HAL 初始化时钟的时候会报错。。行,那也得把 FLASH 的访问时间也弄短了,不然 FLASH 访问乱飞。。。。中断向量表配置一下。。。哈哈哈哈哈成功了。。功夫不负有心人啊


居然真的成功了,就自己胡思乱想一下,然后验证,最后就成功了。牛逼啊。不过真的花了好多时间啊。。通宵了,现在都早上 8 点了。真怕猝死啊。。先睡了,睡醒来补一补笔记。
对了判断烧录进度这个,我是直接每接收 1000 个字节就输出一次,这样有点反馈,会知道到了什么进度。
-
好了这会儿已经睡醒了,睡了 5 个小时左右,最近今天真的是每一天好觉,也不是不想睡,就是做项目有点废寝忘食了,我这人要么就彻底摆烂不学,要么就全部时间投入。。但凡有点三心二意都坚持不了。这会儿也是有点起色了,就借着这股劲干个 40 天,争取 9 月能够找到满意的工作入职。
收尾
-
有点高兴的太早了。。发现还有一些问题没有解决
-
APP 程序中的串口因为替换了 HAL 库,串口收发这块内容需要重新实现一下。
也是难搞,弄了一个多小时也是调通了,1.7.3 版本的 HAL 库没有
HAL_UARTEx_ReceiveToIdle_DMA所以就是得自己实现的,原理就是正常调用HAL_UART_Receive_DMA去发送数据,但是要使能IDLE中断。在 UART1 的中断服务函数中判断,如果是IDLE中断,就停止 DMA,然后计算一下收到了多少数据就可以了。 -
重新看了一下勃勃的需求,要求 boot 程序发送数据包需要使用一些传输协议比如 Modbus。。主要是要校验,本想着用 MD5 来校验就行了,发现 MD5 计算对于单片机来说还是太复杂了。。问了一下勃勃,说只要串口能发送能烧录就行了,知道怎么弄协议栈就行,不弄也 OK。那正好,直接不弄了。
-
WiFi 功能恢复。之前因为替换了 HAL 库,所以和 WiFi 通信的功能也被暂时阉割了,这下也是成功恢复了。
-
之前还提到 WiFi 功能这里,如果要和 WiFi 通信,就会出现发送数据的时候如果开头不是 AT 就会无法识别的问题。
- 这个问题的解决办法我是加入了一个判断指令模式的语句,判断当前指令是控制台模式还是 WiFi 模式,如果是 WiFi 模式那随便发送,如果是控制台模式那还是需要一点点判断什么指令。
- 调试的时候 WiFi 不太能用,最后发现是 WiFi 没有使能。。。这点一定要注意。
-
-
现在还有一个问题点,我的 boot 程序实际上不会自动跳转到 APP 程序中,需要通过串口发送启动的命令。。这个其实无关紧要,搞个延时然后进去就好了。问题是我的 boot 程序实际上是纯手搓寄存器的代码,我实在是懒得去配置 NVIC 中断服务还有 systick 了,但是直接无脑延时又挺不准的。。。反正这个问题现在挺纠结的,干脆将就先用,后面再说了。。
上位机
思路总结
- 现在万事俱备只欠东风了,只要完成上位机,这个简化版的 IAP 基本上就暂告一段落了。标准版的 IAP 测试,还有 OTA,想想麻烦。。不过感觉应该也差不多,就是划一块 ROM 的空间,接收 TCP 发来的
binary数据,写入到 APP 区。我目前的 ROM 区划分成 3 块。如果要弄标准版,划分成 5 个 ROM 区,第一块空间放 boot 程序,第二块放一些标志位(存放一些系统启动信息啥的,引导 boot 程序是加载 APP1 还是加载 APP2 的代码,其实也就第一次加载需要搬到运行区去,后续判断加载过了应该直接跳到运行区就好了),第三块运行区,第四块 APP1,第五块 APP2。其实都还好,比简化版需要多花点时间来处理 lwip 的数据收发,还有 WiFi 的数据收发,其他也没了。 - 接下来就是完成上位机的开发了,说实话我其实不会 C++,但是 QT Designer 以前用得很熟。。实际开发起来应该还好吧,就是先把界面都画好了,后面把信号槽弄好,相关函数代码配置清楚就行了。
- 那首先就是先画好原型界面,然后主要的几个功能:串口通信、网络通信、WiFi 配置,这 3 个功能配置好就行了。
原型界面
-
不知道为啥。。这 win11 的 UI 好丑啊。。。当然我自己画的也丑哈哈哈哈,就这样了,能用就行。
这个 QT Designer 画界面其实很难用,以前用的经验就是先把控件都摆到对应位置上,然后每个空间去配队做布局,最后整个窗口布局做起来就好了。


-
界面画好了,接下来给每个会用到的控件都重新命名一下。
串口功能
-
OK 完事了,现在先实现读取串口的功能。。。这真不知道怎么弄,C++ 有什么库可以直接读取串口?
-
看了一下官方给的文档。。在项目的
.pro文件中添加QT += serialport,结果编译失败了。。。查了一下发现没有安装相关的组件,从工具 -> QT Maintenance tool -> Start Maintenance tool,去安装一下需要的组件,还有其他一些需要的组件也都加上。。。今天就先干到这了,睡觉! -
OK,时间也是来到了 23 号了,今天应该是可以弄好了,像这种 QT 之类工具很多都被封装好了,只要调包就好了,管他底层怎么实现的,能用就行。接下来就是化身调包侠来调包了。串口好像只要去实例化这两个工具就行了?
QSerialPort:提供访问串行端口的函数
QSerialPortInfo:提供有关现有串行端口的详细信息
这 QT 的文档里面都不怎么给示例啊,API 有给出列表但是都是一些函数的形参,没说功能。。。不过我看 QT Creator 里面有不少示例,我直接拿示例看看吧。

-
OK 找到需要的东西了
-
获取系统当前的串口列表:
QSerialPortInfo::availablePorts() -
....就获取了一个串口,其他的还是不会,样例代码没看懂。。他收到了数据是怎么放上去的啊?再看不懂我要去看视频了。。。我懂了,serial 也是有一个信号槽,只要把读取完数据的信号绑定到对应的槽就好了,简单。

-
很潦草,但是可以接收到数据了,就是输出的这玩意儿。。。得转换成 GBK 的编码格式吧。。在别的语言里面一般都有个 encoding 之类的选项。。这个 C++ 怎么改?
-
累了。。刚去床上躺了一个小时。。现在回来继续干。
-
QT 怎么连这个文本编码的功能都这么难用啊。。问了豆包说用
QTStringConverter::decoder,但是我的版本里面没有这玩意儿。。最后网上找了说用String::fromLocal8Bit来转换,但是这么转换是基于本机的语言来转的,如果切成别的语言会有问题。。不管了。。。反正现在接收是正常了。然后就是把按钮状态做个修改。然后搞个关闭串口的功能。。。。 -
OK 完成了,花了 5 分钟。。。接下来是啥?发送消息是吧。。先给发送消息的按钮加个反馈,当串口打开的时候,发送消息的按钮才 enable。
-
现在卡在编码问题了,服了。单片机发过来的是 GB2312 编码格式的字符串,我本地解码以后拿到的是不知道是什么垃圾,如果是数字就会出错。。。问了豆包,结果:

现在也不懂出现了什么,串口接收的数据回显的时候会有:
绻恍枰蛟趌这样子的东西,豆包说可能是发送的汉字可能被截断了。我觉得也有可能,通常这个乱码都是出现在中文突然变到非中文的情况。。看看怎么解决吧。。。OK 解决了,现在可以正确读取代码了:
void MainWindow::readData() { QByteArray chunk = my_serial->readAll(); m_recvBuf += chunk; int pos; while ((pos = m_recvBuf.indexOf('\n')) != -1) { QByteArray line = m_recvBuf.left(pos + 1); m_recvBuf = m_recvBuf.mid(pos + 1); QString text = QTextCodec::codecForName("GBK")->toUnicode(line); ui->serialRec_textBrowser->insertPlainText(text); } } -
接下来完成一下打开文件然后发送文件的功能。因为我的 boot 程序里面关于升级的部分,在设备开机的时候需要先发送一个 1 进入烧录模式,然后需要发送整个文件的大小(4 字节),然后才是发送文件。如果单靠串口发送也可以,但是挺麻烦的。
-
先完成打开文件部分。还是调包,QFile
-
调了两个小时,现在终于是可以了,但是发现以后很离谱的事情。就是我感觉我的 boot 程序挺糟糕的。。所以就想修改一下。。但是惊人的一幕出现了。。只要我对我的 boot 程序做一点修改,boot 就没法烧录。。惊呆了。。。我现在都不敢动我的 boot 程序了,否则功亏一篑。。。
-
我知道 boot 程序出什么问题了。。。因为我的上位机程序接收的时候有问题。。。笑死了,如果单片机没有发
\n就接不到数据了。。算了,不管了。。现在出问题的时候不止是单片机可能有问题,也可能是我的上位机 APP 有问题哈哈哈哈。
-
其实有很多功能我都偷工减料了。。。比如上面那些数据位选择,奇偶校验什么的哈哈哈哈哈,只能看不能用。
-
-
-
网络模式
ETH 以太网模式
-
现在来到网络的通信模式了,先把 ETH 的给做了,ETH 比较稳,毕竟直接偷的野火的代码。QT 的网络通信方式应该就是 socket 啥的吧,用的
QTcpSocket库,估计也就是什么创建一个对象,然后配置一下 ip 和端口还有连接模式,建立连接以后调方法发数据就好了。还是调包侠。- 果然很简单,就是把
QTcpSocket引入一下,然后实例化一个对象,调用connectToHost方法去连接就可以。。简单啊。 - 那 WiFi 也是一样了。无聊。
- 果然很简单,就是把
-
卧槽,出现一个惊天大 bug!!!!对于 WiFi 这一块,我的输出逻辑是,设备接收到 WiFi 指令就马上转发给 WiFi,WiFi 模块收到以后就马上转发回串口。理论上没有任何问题是吧?我用正点原子的串口调试工具也是完全 OK 的。但是!用我自己的工具,就会导致本来应该接收到的数据是:
41 54 0D0D 0A 0D 0A 4F 4B 0D 0A3D 3D 3E 20 57 69 46 69 D6 B8 C1 EE C4 A3 CA BD A3 BA 0A,而变成了两段41 54 0D和3D 3D 3E 20 57 69 46 69 D6 B8 C1 EE C4 A3 CA BD A3 BA 0A,很明显,中间的0D 0A 0D 0A 4F 4B 0D 0A丢失了。。。我用了各种办法来弄,比如等待串口一直接收,还有定时器不停轮询。。都不行。不知道为啥0D 0A 0D 0A 4F 4B 0D 0A这段会丢失。这个数据我都没有进行任何处理,我怀疑是 QT 串口的函数库有 bug。说不定换个版本的 QT 就可以用?具体我也不懂了。。。这个问题算是 WiFi 数据串口回显的问题,问题不大。 -
不管了。现在配置一下 WiFi,然后启动 WiFi 的 TCP 服务,然后上位机再测试一下。
- 完美,完事了,找勃勃汇报去。
问题总结
有很多地方都是一笔带过的,不用怀疑,就是没有卡到我很长时间,都是比较顺利的。一些 3-5 分钟就解决的问题我都没写上,不代表没遇到。问题太多都写出来一个是耗费我的时间,一个也是会显得我太啰嗦了。。。下面是一些印象比较深的问题的总结。。
MDK 相关问题
-
Keil 编译问题 L6050U:The code size of this image (33016 bytes) exceeds the maximum allowed for this version of the linker
-
有时候明明正常编译成功,但是烧录进去程序没法正常运行,调试的时候程序总是莫名跑飞,各种疑难杂症
- 看看是不是
Use MicroLIB没有勾选,咱这也不知道什么原理,反正勾上了以后如果出了问题就不用往这边考虑了
- 看看是不是
-
右键点击函数以后没有办法跳转到函数的定义
- 小问题,魔术棒->
Output->Browse Infomation选上。
- 小问题,魔术棒->
-
小技巧:可以通过修改
IROM1地址,直接把程序烧录到对应的 ROM 区,这样就不用总是去 boot 程序烧录 BIN 文件来写入 APP。
FreeRTOS 相关问题
- 在 FreeRTOS 下,和串口输出有关的任务最好用阻塞的方式输出,不然可能会出现串口输出不完整或者无法输出的问题。我怀疑是任务之间调度的问题,但我不敢确定,暂定是这个问题。在任务中如果是阻塞式输出串口信号就没任何问题。总之这个项目只是一个测试,不要在串口输入输出的实时性上面死磕,能实现就行。
- 串口通信的优先级一定要高一点,如果和其他任务相同优先级,串口常常会丢失数据,具体原因不明。
WiFi 模组相关问题
- 要使用 WiFi 模组,一定要记得 WiFi 的使能引脚和复位引脚都拉高了。
LwIP 相关问题
- 这玩意儿很难,要改的文件不少,直接抄野火了。。。而且项目做完,要问任何和 LwIP 有关的问题,我其实都答不上来。
- 如果一定要自己移植,重点关注的就这几个文件:
arch_sys.c、ethernetif.c、lwipopts.h、bsp_eth.c;初始化的过程中主要出问题的也就是这几个文件,调通了基本上就可以驱动了。最主要的还是ethernetif.c里面的 3 个low_level_xxxx了。
汇报内容
-
单片机 FLASH 分区:
- 0x08000000 - 0x0803FFFF:BOOT 程序空间
- 0x08040000 - 0x080A0000:运行区
- 0x080A0000 - 0x080FFFFF:APP 区
-
BOOT 程序运行逻辑:程序启动进入 BOOT 程序,等待 5s 检测用户输入。
- 用户输入 1,进入烧录模式,无限循环等待,用户先发送 BIN 文件大小,然后发送 BIN 文件。BOOT 程序在收到文件大小以后,擦除扇区 9 10 11 也就是 APP 区
0x080A0000 - 0x080FFFFF的空间。完成擦除后开始接收系统发来的 BIN 文件,逐个字节直接写入到 APP 区。待全部写完以后,重复对比校验一次数据,如果全部一致表示校验通过。然后自动将 APP 区的所有数据都搬到运行区,并调低 FLASH 的 FLATENCY、切换 HSI 时钟、关闭中断等,然后把 RUN 区地址压入 MSP 跳转到0x08040000运行程序,这个是收到 FreeRTOS 的启发的。 - 用户输入 2,恢复出厂设置。将 APP 区的代码全部拷贝到运行区。
- 用户输入 3 或等待超过 5s,跳转到
0x08040000执行运行区代码。
- 用户输入 1,进入烧录模式,无限循环等待,用户先发送 BIN 文件大小,然后发送 BIN 文件。BOOT 程序在收到文件大小以后,擦除扇区 9 10 11 也就是 APP 区
-
APP 程序:移植了 LwIP 实现 ETH 以太网通信还有 FreeRTOS。
-
3 个任务
- 第 1 个用来闪烁 LED,只要 LED 闪烁就说明正常进入系统。
- 第 2 个任务用来监听串口和 WiFi 输入,其实 WiFi 得单独再弄一个任务的,我是收尾的时候才发现的问题,有点不想改了。
- 第 3 个任务就是监听以太网输入了,配置了 DHCP 和 TCP Server 模式比较方便调试,直接进行 TCP 数据回显。
-
-
上位机使用 QSerialPort 实现串口通信、QTcpSocket 实现网络通信。这个很简单都是调包,没什么好说的。
项目源码:z1178902213/IAP_test: 基于 STM32 的 IAP 升级项目,包含上位机
评论