西电通院微控指南MCU部分

目录

前言

代码框架

任务1:传感器读取数据

Cubemx配置

​编辑

TCS3472颜色传感器

Vl6180距离传感器

语音传感器

传感器初始化

任务2:接收上位机数据,根据上位机指令实现功能。

Cubemx配置

代码部分

串口重定向

前言

在微控讨论贡献度时,做FPGA和MCU的同学贡献度通常会高的离谱,但是事实上这两方面工作难度是有,但是如果认真学习这方面知识,微控的MCU与FPGA难度并不高。

我认为微控作为一个小组作业,有分工合作,每个人都要完成自己分内的东西,秘书,公关,产品经理所作文书工作虽然简单,但是要做好也不容易,也并不应该因为选择了做这个,最终微控得到了很低的得分。FPGA,MCU部分的有一定门槛,但是在我看来绝大多数做该部分的同学,并没有真正去学习这方面的知识,而是使用着从各处找的工程,或者在别人的博文上抄的代码,不知其原理,每天坐在大厅里改来改去,改到凌晨三四点,看似辛苦,做的大部分都是无用功罢了。

所以写本文章的目的把我这次微控所写的工程进行讲解,搭建一个可以直接使用的微控系统设计的代码框架,帮助学弟学妹们在面对微控可以轻松完成MCU与FPGA任务,减少难度的同时不希望出现某些做这两部分工作,就觉得自己比秘书,产品经理,公关做的努力多得多,要求高出20%贡献度的情况。(可以高但是不要高的离谱,例如MCU+FPGA的人拿20%以上,剩下人只有15%甚至更低,这种情况在本人看来十分离谱!!!除非你的队友什么都不干!)

先放上工程链接zlzj12/week1_project直接下载即可。

我使用的是Kile+Cubemx的方式来编写MCU工程的,如果有同学使用CubeIDE进行工程编写,也可以参考我的工程,基础配置,驱动移植都是一样的。现阶段上传的工程是我的整个工程文件,后期会对其进行删改,写成代码框架,只需要知道在哪块编写代码即可。

具体的学习教程可以看这个给博主,我认为作为一个速通教程讲解的非常好【STM32入门教程】应该是全B站最好的STM32教程了!!_哔哩哔哩_bilibili

 关于32单片机的开发学习,大家可以结合微处理器这门课程所学的东西,理解寄存器操作是什么,也可以直接使用HAL库直接进行开发。想要深入学习这方面的同学可以学习江科大的教程里面会讲解寄存器,对于微处理器这门课程的学习也有帮助。

STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili

MCU部分微控的具体要求为:

①实现从传感器读取数据。

②接收上位机数据,根据上位机指令实现功能。

③给FPGA发送指令控制舵机转动。

代码框架

所有我的代码均在user_Drivers文件夹中,My_code.c中编写的所有需要外部调用的函数,只需要在main.c中调用即可,这样子可以使main.c文件更为简洁,工程管理,debug也更方便。

任务1:传感器读取数据

传感器需要我们来编写驱动文件,学校给的例程中将所有驱动代码放在了main.c文件中,我将其做了整理做成驱动文件放在我的工程中。

AIvoic.c是语音模块驱动,TCS3472.c是颜色传感器驱动,Vl6180.c是距离传感器驱动,Emm_V5.c是电机驱动,电机是我自己加的模块,可以不用移植。Mycode.c中是所有的函数代码。

可以使用我的工程框架,也可以直接从我的工程中移植传感器的驱动代码至自己的工程

 接下来着重讲解如何使用和配置传感器。

Cubemx配置

配置时钟,主频100M

打开Debug,注意这个一定要打开,否则会导致芯片烧录一次后自锁,不能再烧录代码。 

打开IIC1和IIC3,这里的引脚配置是我按照绿色接口板修改后的,不是IIC的默认引脚,可以自己修改一下。

 打开这两个GPIO口,如果有语音传感器做这一步,不使用语音传感器忽略即可。

生成代码时记得勾选这个选项,这样子.C.H文件分开的写法方便工程管理。

 到此我们传感器的基础配置就完成了,关于串口的配置我们放在任务2讲解。

TCS3472颜色传感器

学校给的颜色传感器驱动中,读取颜色数据的代码有问题,通过阅读数据手册可知需要读取高八位低八位数据拼起来才可以得到正确的颜色数据,而学校给出的代码只读取了低八位数据,所以正确的读取颜色数据代码如下

其中小写的c,r,g,b为读取的颜色数据,使用这段代码可以读取到正确的颜色数据。c是光强,r是红色光,g是绿色光,b是蓝色光。rgb那个数据最大就是什么颜色的物体。

 HAL_I2C_Mem_Read(&hi2c1,I2C_TCS34725_ADDR<<1|I2C_READ,TCS34725_STATUS_ADDR|TCS34725_CMD_MASK,1,buffer,1,3000); if(buffer[0]&TCS34725__STATUS_AVALID) { uint8_t R,G,B,C; uint8_t R1,G1,B1,C1; uint16_t r,g,b,c; R = 10; float r_norm, g_norm, b_norm; float h, s, v; for(int i = 0;i<10;i++) { //Get Clear RAW data C = TCS34725_Get_RawData(&hi2c1,TCS34725_CDATA_ADDR|TCS34725_CMD_MASK); C1 = TCS34725_Get_RawData(&hi2c1,TCS34725_CDATAH_ADDR|TCS34725_CMD_MASK); c = (C1<<8)|C; //Get RED RAW data R = TCS34725_Get_RawData(&hi2c1,TCS34725_RDATA_ADDR|TCS34725_CMD_MASK); R1 = TCS34725_Get_RawData(&hi2c1,TCS34725_RDATAH_ADDR|TCS34725_CMD_MASK); r = (R1<<8)|R; //Get Green RAW data G = TCS34725_Get_RawData(&hi2c1,TCS34725_GDATA_ADDR|TCS34725_CMD_MASK); G1 = TCS34725_Get_RawData(&hi2c1,TCS34725_GDATAH_ADDR|TCS34725_CMD_MASK); g = (G1<<8)|G; //Get Blue RAW data B = TCS34725_Get_RawData(&hi2c1,TCS34725_BDATA_ADDR|TCS34725_CMD_MASK); B1 = TCS34725_Get_RawData(&hi2c1,TCS34725_BDATAH_ADDR|TCS34725_CMD_MASK); b = (B1<<8)|B; }

Vl6180距离传感器

这个传感器只需要调用驱动中的函数就可以读取数据。使用如下,range是读取的距离数据。

uint32_t range; range = VL6180X_GetRange();

语音传感器

语音传感器需要写的代码较多,首先我们要先写入语料,定义为全局变量。

const char TEXT1_Buffer[]={"wo yao ting che"}; const char TEXT2_Buffer[]={"ni hao wo shi liu zhi jing"}; const char TEXT3_Buffer[]={"ni hao wo shi duan xin yu"}; const char TEXT4_Buffer[]={"ni hao wo shi yang bo xiang"}; const char TEXT5_Buffer[]={"ni hao wo shi sun fan"}; const char TEXT6_Buffer[]={"ni hao wo shi xiao shuo"}; const char TEXT7_Buffer[]={"ni hao wo shi chen jiang hao"};

 然后使用这个函数,将所有语料写入语音传感器,需要几个语料就写入几个。可以根据自己的需求更换,添加,减少语料,只要在初始化函数中使用AsrAddWords函数写入就可以。

 使用AsrSetMode()函数设置模式,注释中有解释。

/*语音模块初始化函数,加入所有的语料*/ void AIVoice_init(uint8_t flag) { if (flag) //添加的词条和识别模式是可以掉电保存的,第一次设置完成后,可以将此段注释掉,即将1改为0,然后重新下载一次程序 { AsrErase(); //擦除 HAL_Delay(60); //1:循环识别模式。状态灯常亮(默认模式) //2:口令模式,以第一个词条为口令。状态灯常灭,当识别到口令词时常亮,等待识别到新的语音,并且读取识别结果后即灭掉 //3:按键模式,按下开始识别,不按不识别。支持掉电保存。状态灯随按键按下而亮起,不按不亮 AsrAddWords(1, (uint8_t*)TEXT1_Buffer, strlen(TEXT1_Buffer)); HAL_Delay(60); AsrAddWords(2, (uint8_t*)TEXT2_Buffer, strlen(TEXT2_Buffer)); HAL_Delay(60); AsrAddWords(3, (uint8_t*)TEXT3_Buffer, strlen(TEXT3_Buffer)); HAL_Delay(60); AsrAddWords(4, (uint8_t*)TEXT4_Buffer, strlen(TEXT4_Buffer)); HAL_Delay(60); AsrAddWords(5, (uint8_t*)TEXT5_Buffer, strlen(TEXT5_Buffer)); HAL_Delay(60); AsrAddWords(6, (uint8_t*)TEXT6_Buffer, strlen(TEXT6_Buffer)); HAL_Delay(60); AsrAddWords(7, (uint8_t*)TEXT7_Buffer, strlen(TEXT7_Buffer)); HAL_Delay(60); AsrSetMode(1); //设置模式 HAL_Delay(60); } }

 由于语音传感器使用的是软件模拟IIC,我们需要在AIvoice.h文件中修改使用的引脚,你使用的引脚记得在配置是配置为输出。

传感器初始化

所有的传感器在使用前都需要初始化,在main函数的while(1)之前一定要初始化才可以正常使用这些传感器。 

这部分代码便是传感器初始化代码

HAL_Delay_us_init(84);     Aivoice_init();     if(VL6180X_GetID()==VL6180X_DEFAULT_ID)     {         VL6180X_Init();     }             TCS34725_ENABLE();     Sevo_start(Right_Close,Middle_on,Left_Close);     ParkingStatus_init();

 这里是我的main函数,可以自己看一下初始化代码写在哪里。

int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); MX_USART2_UART_Init(); MX_I2C1_Init(); MX_USART6_UART_Init(); MX_I2C3_Init(); /* USER CODE BEGIN 2 */ HAL_UARTEx_ReceiveToIdle_DMA(&huart6,rx_buffer,Rx_Buffer_size); HAL_Delay_us_init(84); Aivoice_init(); if(VL6180X_GetID()==VL6180X_DEFAULT_ID) { VL6180X_Init(); } TCS34725_ENABLE(); Sevo_start(Right_Close,Middle_on,Left_Close); ParkingStatus_init(); Car_state(1); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ //语音模块语料初始化 AIVoice_init(1); while (1) { if(Voice_Control_flag == 1) { Voice_Control(); } if(Color_Control_flag == 1) { Sevo_start(Right_Open,Middle_on,Left_Open); HAL_Delay(2000); Color_Control(); HAL_Delay(6000); Sevo_start(Right_Close,Middle_on,Left_Close); Color_Control_flag = 0; } if(TemporaryParking_Control_flag == 1) { Sevo_start(Left_Open,Right_Open,Middle_on); Uart_printf(&huart6,"Space number 6 .\r\n"); Car_state(6); HAL_Delay(6000); Sevo_start(Right_Close,Middle_on,Left_Close); TemporaryParking_Control_flag = 0; } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }

至此,我们已经完成了所有传感器数据该如何读取,接下来讲解如何通过串口吧读取的数据发送出去与接收上位机数据。

任务2:接收上位机数据,根据上位机指令实现功能。

这部分是调通设备间通信,推荐使用DMA+中断的方式接收数据。

首先给出我的串口配置以及该串口通信双方,其中MCU与FPGA的通信是单向的。MCU和步进电机部分不用管,是多加的模块。

通信双方

通信协议

通信接口

上位机\leftarrow \rightarrowMCU

UART

UART6

MCU\rightarrowFPGA

UART

UART1

MCU\leftarrow \rightarrow步进电机

UART

UART2

 上表中可知,只有上位机与MCU通信需要MCU接收数据,所以配置UART6为DMA中断方式。其他都是异步通信配置好波特率即可。

Cubemx配置

配置串口时,只需要打开异步通信,配置波特率即可。

接下来讲解如何配置DMA+中断的方式接收数据,点击DMA Settings,接下来按照下图进行配置即可。只需要打开RX的DMA。

代码部分

配置好后,接下来写代码,使用这个函数便可以开启串口DMA接收中断,其中rx_buffer,Rx_Buffer_size为接收数据缓冲区,缓冲区的大小即最大可接收数据量。这两个变量都要定义为全局变量。

这里实现的是串口不定长接收,只要接收的数据大小不超过Rx_Buffer_size,接收的数据都会存在rx_buffer中

 HAL_UARTEx_ReceiveToIdle_DMA(&huart6,rx_buffer,Rx_Buffer_size); 

使用了中断必然要有中断回调函数,在中断回调函数中处理中断事件。产生中断后代码会进入中断回调函数,首先要判断是哪个串口引起的中断,即代码中if判断huart参数。

这里我约定的通信协议只有一个八进制数,所以只判断了rx_buffer[0]第一位数据,如果你发送了一个字符串,可以使用字符串对比函数,来判断数据,应该执行什么操作。

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef * huart, uint16_t Size) { if(huart == &huart6) { /**/ if(rx_buffer[0] == 0x32)//暂时停靠 { TemporaryParking_Control_flag = 1; Color_Control_flag = 0; Voice_Control_flag = 0; } if(rx_buffer[0] == 0x31)//自助停车即颜色识别模块停车 { TemporaryParking_Control_flag = 0; Color_Control_flag = 1; Voice_Control_flag = 0; } if(rx_buffer[0] == 0x33)//语音停车 { TemporaryParking_Control_flag = 0; Color_Control_flag = 0; Voice_Control_flag = 1; } memset(rx_buffer, 0, Rx_Buffer_size); HAL_UARTEx_ReceiveToIdle_DMA(&huart6, rx_buffer, Rx_Buffer_size); // 接收完毕后重启 } }

最后在回调函数中,我们一定要有最后两代码,这两句代码的作用是清空数据缓冲区,重新打开中断,一定要有。

这样子我们就可以通过接收上位机数据,判断数据是什么,实现相对应功能了。推荐使用标志位实现功能,即在中断中只改变该操作对应的标志位,while(1)中轮询该标志位,检测到标志位改变时,实现对应功能。

如下代码,我在主函数的while(1)中,轮询标志位,判断到标志位为1,执行对应函数,不为1不执行。

 while (1) { if(Voice_Control_flag == 1) { Voice_Control(); } if(Color_Control_flag == 1) { Sevo_start(Right_Open,Middle_on,Left_Open); HAL_Delay(2000); Color_Control(); HAL_Delay(6000); Sevo_start(Right_Close,Middle_on,Left_Close); Color_Control_flag = 0; } if(TemporaryParking_Control_flag == 1) { Sevo_start(Left_Open,Right_Open,Middle_on); Uart_printf(&huart6,"Space number 6 .\r\n"); Car_state(6); HAL_Delay(6000); Sevo_start(Right_Close,Middle_on,Left_Close); TemporaryParking_Control_flag = 0; } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ }

串口重定向

关于串口重定向至Printf,给其他设备发送数据,可能大部分同学都使用这种方法,但是printf并不能指定串口发送,所以我给出可以指定串口发送的重定向方法

在Cubemx产生的usart.c文件中底部加入这段代码,注意这里定义了一个函数,需要在usart.h文件中进行声明才能被外部调用。

/* USER CODE BEGIN 1 */ void Uart_printf(UART_HandleTypeDef *huart,char *format, ...) { char buf[1024]; //定义临时数组,根据实际发送大小微调 va_list args; va_start(args, format); uint16_t len = vsnprintf((char *)buf, sizeof(buf), (char *)format, args); va_end(args); HAL_UART_Transmit(huart,(uint8_t *)buf,len,HAL_MAX_DELAY); } /* USER CODE END 1 */

 main.h中引入这些头文件。这样子就可以使用这个函数。

#include <stdlib.h> #include <stdio.h> #include <stdarg.h> #include <string.h>

这个函数的使用方法和printf相同,只是多了第一个参数为串口句柄,下面给出一个使用示例

Uart_printf(&huart6,"Please use temporary parking.\r\n"); 

任务3:给FPGA发送指令控制舵机转动

如果你使用了我上篇文章给出的FPGA代码,或者同样使用Uart的通信协议,只需要打开Uart配置对应波特率即可。这里给出我控制三个舵机的方法作为参考。但是每次上电后发送3个数据给fpga控制的舵机顺序不一样。

这个函数中degree是0~180°,只使用HAL_UART_Transmit()就可以把这个数传给FPGA来控制舵机角度。

/* 通过串口控制挂载在FPGA上的3个舵机 * degree1:控制右门 * degree2:控制中控 * degree3:控制左门 */ void Sevo_start(uint8_t degree1,uint8_t degree2,uint8_t degree3) { HAL_UART_Transmit(&huart1,&degree1,1,HAL_MAX_DELAY);//发送给fpga的角度信号 Sevo_delay(1000); HAL_UART_Transmit(&huart1,&degree2,1,HAL_MAX_DELAY);//发送给fpga的角度信号 Sevo_delay(1000); HAL_UART_Transmit(&huart1,&degree3,1,HAL_MAX_DELAY);//发送给fpga的角度信号 }

Sevo_delay()函数是一个短暂的延时,使FPGA可以区分出发送的3个数据,分配给三个舵机。

/*舵机控制小延时*/ void Sevo_delay(uint32_t cnt) { while (cnt) { cnt--; } }

总结

至此所有MCU中使用的外设配置,函数以及使用均已讲解完毕,其余部分就是使用这些代码实现你想实现的功能,我们以框图的形式做一次系统的总结,下面的框图中l610部分我未使用所以未给出教程,其余部分均做出讲解。

通信双方

通信协议

通信接口

上位机 MCU

UART

UART6

MCU FPGA

UART

UART1

MCU 步进电机

UART

UART2

MCU TCS3472

硬件IIC

IIC1

MCU VL6180

硬件IIC

IIC3

MCU 语音模块

软件IIC

SDA-PB12

SCL-PB1

Read more

GHCTF2025-WEB题解:如何用SSTI绕过WAF黑名单(附实战payload)

从GHCTF2025实战出发:深度拆解SSTI黑名单绕过策略与高阶Payload构造 最近在GHCTF2025的WEB赛道上,一道看似简单的文件上传题目,却让不少选手陷入了“知道有洞,但payload总被拦截”的困境。这道题表面上是文件上传,实际上却是一场针对SSTI(服务器端模板注入)绕过能力的深度考验。我在实际测试中发现,很多选手能够快速识别出SSTI漏洞的存在,但在面对严格的黑名单过滤时,却往往束手无策,反复尝试的payload都被WAF无情拦截。 这种情况在真实的渗透测试和CTF比赛中并不少见。WAF(Web应用防火墙)的过滤规则越来越智能,传统的{ {7*7}}测试虽然能确认漏洞,但真正要执行命令、读取文件时,那些包含os、flag、__builtins__等关键词的payload几乎都会被第一时间拦截。这道题的精妙之处在于,它模拟了一个相对真实的防御环境——不仅过滤常见敏感词,还对下划线这种在Python反射中至关重要的字符进行了拦截。 本文将从实战角度出发,不局限于GHCTF2025这一道题目,而是系统性地探讨SSTI黑名单绕过的核心思路、技术原理和进阶技巧。我会结

前端通用 Token 全流程操作指南(常见常用版)

前端通用 Token 全流程操作指南(常见常用版) 本文梳理 所有前端框架通用 的 Token 操作逻辑,剥离具体项目/技术栈细节,聚焦「获取→存储→使用→过期→清除」的核心生命周期,每个步骤均标注「通用场景+通用方案+注意事项」,适合所有前端开发场景,可直接作为开发速查表。 前置说明:Token 的核心定位 Token 是后端签发的临时访问凭证,核心作用是: 1. 证明“当前用户是谁”(身份认证); 2. 证明“当前用户有权限访问”(权限校验)。 一、第一步:登录成功获取 Token 通用场景 用户通过账号密码/验证码/第三方登录等方式,向后端发起登录请求,后端验证通过后,在响应体中返回 Token。

前端图片加载失败、 img 出现裂图的原因全解析

在前端开发过程中,我们几乎都遇到过这种情况: 页面中某张图片加载不出来,显示成一个小小的“裂图”图标。 这看似简单的问题,实际上可能由多种原因造成,尤其是在 HTTPS 环境下,混合内容机制(Mixed Content) 是最常见、也最容易被误解的根源之一。 本文将带你系统梳理裂图的各种原因、排查思路,并重点讲清楚混合内容的原理与浏览器行为。 一、什么是“裂图”? “裂图”(broken image)是指浏览器尝试加载 <img> 标签的图片资源失败时的表现形式。 常见表现: * 图片区域显示为灰底、叉号、占位符; * 控制台出现 Failed to load resource 或 Mixed Content 警告; * Network 面板中图片请求状态码为 404 / 403 / blocked。 二、常见的裂图原因汇总

WebRTC / HLS / HTTP-FLV 的本质区别与选型指南

WebRTC / HLS / HTTP-FLV 的本质区别与选型指南

在做系统级直播(而不是自己本地播放)时,很多人都会遇到一个经典问题: WebRTC、HLS、HTTP-FLV 到底有什么区别? 项目中到底该选哪个? 传输协议不同 → 延迟不同 → 兼容性 / 稳定性 / 成本不同 在系统里选哪个,核心看两点: 你要多低的延迟?你要多强的兼容和稳定? 一、简介 * WebRTC:超低延迟(0.2 ~ 1s),适合实时监控、无人机、实时指挥 * HLS(hls.js):最稳、最通用(5 ~ 15s),适合活动直播、课程、公开大并发 * HTTP-FLV(flv.js):中低延迟(1 ~ 3s),适合想比 HLS 低延迟,但不想用 WebRTC 的场景(