资讯动态

STM32毕设项目分享:基于RFID的生产流水线工件分拣系统设计

发布时间:2026/9/15 8:32:30 来源:尧图企业网站定制
STM32F103C8T6 MFRC-522 SG90舵机 红外定位 继电器传送带 低成本分拣系统。一、成品简介1. 实物成品成品展示整体装置整套东西搭在一块木制传送带。左边是上料位位点一中间是传送带右端是舵机分拣工位位点二两个工位各挂一个红外检测头工件的 RFID 标签在起始位置刷卡识别。OLED 屏显示状态。显示屏界面屏幕四行信息从左往右、从上往下是传送带开 / 关检测物货一 / 货二就是这次识别到的 RFID 标签类型位点一是 / 否位点二是 / 否二、项目简介1. 功能详解基于RFID的生产流水线工件分拣系统工件识别MFRC-522 射频模块读 Mifare One 卡卡里第 1 扇区第 0 块写一个字节值为 1 代表货一为 2 代表货二。STM32 用SPI 跟 MFRC-522 通信完成寻卡 → 防冲突 → 选卡 → 密钥校验 → 读卡。识别成功后 LED 亮一下OLED 同步刷新货物类型。到位检测两路红外光电检测模块分别装在位点一上料位置、控制启动和位点二分拣位、控制停止。没有工件的时候模块 输出高电平有工件挡住就拉低。程序在 1ms 的定时器中断里写到对应变量代表这个位置是否有货。传送带自动启停位点一检测到工件 → 继电器吸合 → 传送带启动把工件往分拣工位送。同时置 标志位防止工件还没走远就被重复触发。等工件离开位点一、又到位点二 → 继电器断开 → 传送带停。舵机自动分拣传送带停下时根据前面 RFID 读到的卡号决定哪个舵机动。卡 1 转舵机 1卡 2 转舵机 2把工件推进对应的分拣槽停 500ms 后自动回 0° 复位。手动调试四个独立按键。KEY1 手动开关传送带应急停机用KEY2 / KEY3 手动拨一下舵机 1 / 舵机 2KEY4 手动切换货一 / 货二。可视化显示0.96 寸 OLED显示当前的四项状态。硬件框图程序流程图RFID 识别部分2. 主要器件器件型号 / 说明主控STM32F103C8T6 最小系统板射频识别MFRC-522 模块 Mifare One(S50) 白卡 ×2执行机构SG90 舵机 ×2用于分拣推杆位点检测红外光电检测模块 ×2传送带控制5V 继电器模块 木制传送带模型带直流减速电机显示0.96 寸 OLED交互独立按键 ×4电源AMS1117 降压稳压芯片3. 原理图设计单片机引脚分配:所属模块硬件信号单片机端口功能说明MFRC-522SCK时钟PA5软件 SPI 同步时钟MFRC-522MISO数据输入PA6软件 SPI 数据接收MFRC-522MOSI数据输出PA7软件 SPI 数据发送MFRC-522NSS片选PB0拉低开始通信MFRC-522RST复位PB1拉低实现硬件复位SG90 舵机舵机 1PA0TIM2_CH1 PWM 输出SG90 舵机舵机 2PA1TIM2_CH2 PWM 输出继电器INPB10传送带电机启停控制红外检测位点一 DOPB3工件到位检测启停触发红外检测位点二 DOPB4工件分拣触发OLEDIIC_SCLPB6I2C1 时钟OLEDIIC_SDAPB7I2C1 数据独立按键KEY1~KEY4PB12~PB15手动调试输入内部上拉状态指示灯LEDPC13读卡成功提示三、主要模块1. MFRC-522 射频识别模块MFRC522 是 NXP 出的13.56MHz 非接触读卡芯片支持 ISO14443A 协议也就是我们平时说的 M1 卡S50。它把 13.56MHz 下的调制解调、帧处理、CRC 校验、防冲突这些都集成在片内了单片机只要通过 SPI 读写它的寄存器就能跑起来。选它主要两个原因便宜十几块、资料多。但是读卡距离就 2~3cm 左右。2. SG90 舵机分拣SG90 是 9g 微型舵机三根线VCC、GND、PWM。控制方式就是给它一个 50Hz 的 PWM靠高电平脉宽决定转角——0.5ms 对应 0°1.5ms 对应 90°2.5ms 对应 180°。TIM2 的时钟是 72MHz要出 50HzPWM频率 定时器时钟 / [(PSC1) × (ARR1)]取 PSC 7199、ARR 1999得到 72000000 / 7200 / 2000 50Hz。这时候计数器走一格是 1us所以0° → CCR 5000.5ms90° → CCR 15001.5ms180° → CCR 25002.5ms代码里MG90_Set_Angle()就是把 0~180 线性映射到 50~250 这个区间。3. 红外光电检测位点检测用的是反射式红外模块。板子上一对红外发射管和接收管朝同一个方向前面有物体就把红外反射回来接收管导通DO 输出拉低。输出是数字量直接接 GPIO 读高低电平就行。4. 继电器 传送带STM32 的 IO 口输出电流就 20mA 左右直接驱动传送带的直流电机是不现实的。所以中间加一个 5V 继电器模块做隔离单片机给 IN 脚一个电平继电器线圈吸合电机那头接通电源传送带转起来。用继电器还有额外好处一是光耦隔离电机换向那一下的尖峰干扰不会串回单片机二是接线简单VCC/GND/IN 三根线搞定。传送带本体淘宝买的木制拼装套件自己配了个直流减速电机。传送带本身不参与控制就是被动地被继电器开关电源。四、写作内容参考1. 摘要参考内容随着工业自动化的不断深入制造业正加速向智能化、数字化转型。当前多数中小企业仍采用人工分拣或半自动化分拣模式存在效率低、误差高、人工成本高的弊端难以满足轻量化、低成本的分拣需求。 本文针对这一问题结合 RFID 射频识别技术与嵌入式控制技术设计了一套基于 STM32F103C8T6 单片机的生产流水线工件分拣系统。系统以 STM32 单片机为核心整合 MFRC-522 射频模块、SG90 舵机、红外光电检测模块、继电器、0.96 寸 OLED 显示模块和独立按键实现了工件自动识别、精准定位、传送带自动启停与舵机自动分拣功能。有效解决了传统分拣环节的痛点降低了人工成本与管理成本适配中小企业的基本生产需求。 本文设计的系统虽实现了核心分拣功能但仍存在 RFID 识别距离有限、舵机分拣精度有待提升、缺乏智能化管理功能等不足。关键词RFID射频识别技术生产流水线工件分拣2. 目录参考内容1 引言 1.1 研究背景 1.2 选题的目的和意义 2 系统方案设计 2.1 系统硬件结构图设计 2.2 系统硬件选取 2.2.1 STM32F103C8T6 单片机 2.2.2 MFRC-522 射频模块 2.2.3 SG90 舵机模块 2.2.4 红外光电检测模块 2.2.5 继电器模块 2.2.6 传送带模块 2.2.7 0.96 寸 OLED 模块 3 系统硬件电路设计 3.1 STM32 的 GPIO 电路 3.2 MFRC-522 射频模块电路 3.3 SG90 舵机电路 3.4 红外检测模块电路 3.5 继电器模块电路 3.6 0.96 寸 OLED 显示模块电路 4 系统软件设计 4.1 RFID 识别系统程序 4.2 分拣控制系统程序 4.2.1 SG90 舵机程序设计 4.2.2 红外检测模块程序设计 4.2.3 0.96 寸 OLED 显示模块程序设计 4.2.4 独立按键模块程序设计 4.2.5 STM32 分拣主程序设计 5 系统测试 5.1 RFID 识别模块测试 5.2 红外检测模块测试 5.3 传送带控制模块测试 5.4 系统综合功能测试 6 结论和展望 参考文献 附录Ⅰ 电气原理图 附录Ⅱ 主要程序五、项目主要源码讲解5.1 RFID 识别RFID 是整个系统的眼睛读得准不准直接决定分拣对不对。核心函数是rfid_task()走的是 Mifare 卡的标准五步流程void rfid_task() { status PcdRequest(REQ_ALL, TagType); // 1. 寻卡 if(!status) { status PcdAnticoll(SelectedSnr); // 2. 防冲突拿卡号 if(!status) { status PcdSelect(SelectedSnr); // 3. 选卡 if(!status) { snr 1; // 第 1 扇区 status PcdAuthState(KEYA, (snr*43), DefaultKey, SelectedSnr); // 4. 密钥校验 if(!status) { status PcdRead((snr*40), buf); // 5. 读第 1 扇区第 0 块 car buf[0]; if(car1){ things0; } // 卡 1 → 货一 if(car2){ things1; } // 卡 2 → 货二 if(!status) { LED_ON; // 读卡成功点亮指示灯 WaitCardOff(); memset(buf, 0, 16); } } } } } LED_OFF; }扇区地址怎么算的。M1 卡一共 16 个扇区每个扇区 4 个块。第snr扇区的第 n 块地址就是snr*4 n。密码存在每扇区的第 3 块所以校验用snr*43读数据用snr*40。默认密钥。DefaultKey[6] {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}这是 M1 卡出厂密钥新买的白卡都是这个。改成自定义密钥的话这里要跟着改不然PcdAuthState会一直返回错误。为什么要 WaitCardOff()。这个函数会一直循环寻卡直到连续三次寻不到卡才返回。卡片一拿走主循环才继续往下跑。不加这一句的话卡留在感应区rfid_task()每轮都在重复读同一张卡car会一直刷新赶上工件正好到位就会重复触发分拣。当然这个写法也有代价后面踩坑里说。底层是软件 SPI。PcdRequest这些函数最终都调到PcdComMF522()它负责往 FIFO 里塞命令、启动收发、轮询中断寄存器、把结果读回来char PcdComMF522(unsigned char Command, unsigned char *pInData, unsigned char InLenByte, unsigned char *pOutData, unsigned int *pOutLenBit) { char status MI_ERR; unsigned char irqEn 0x00, waitFor 0x00; /* ... 省略变量声明 ... */ switch (Command) { case PCD_AUTHENT: irqEn 0x12; waitFor 0x10; break; case PCD_TRANSCEIVE: irqEn 0x77; waitFor 0x30; break; default: break; } WriteRawRC(ComIEnReg, irqEn|0x80); ClearBitMask(ComIrqReg, 0x80); WriteRawRC(CommandReg, PCD_IDLE); SetBitMask(FIFOLevelReg, 0x80); // 清空 FIFO for (i0; iInLenByte; i) WriteRawRC(FIFODataReg, pInData[i]); // 把要发的数据推进 FIFO WriteRawRC(CommandReg, Command); // 启动命令 if (Command PCD_TRANSCEIVE) SetBitMask(BitFramingReg, 0x80); // 启动发送 i 2000; // 超时计数见踩坑记录 do { n ReadRawRC(ComIrqReg); i--; } while ((i!0) !(n0x01) !(nwaitFor)); ClearBitMask(BitFramingReg, 0x80); if (i ! 0) { if (!(ReadRawRC(ErrorReg) 0x1B)) { status MI_OK; if (n irqEn 0x01) status MI_NOTAGERR; // 没卡 if (Command PCD_TRANSCEIVE) { n ReadRawRC(FIFOLevelReg); lastBits ReadRawRC(ControlReg) 0x07; if (lastBits) *pOutLenBit (n-1)*8 lastBits; else *pOutLenBit n*8; if (n 0) n 1; if (n MAXRLEN) n MAXRLEN; for (i0; in; i) pOutData[i] ReadRawRC(FIFODataReg); } } else status MI_ERR; } SetBitMask(ControlReg, 0x80); // 停定时器 WriteRawRC(CommandReg, PCD_IDLE); return status; }而再往下ReadRawRC/WriteRawRC就是纯粹的 GPIO 翻转了unsigned char ReadRawRC(unsigned char Address) { unsigned char i, ucAddr; unsigned char ucResult 0; NSS_L; // 片选拉低开始通信 ucAddr ((Address1)0x7E)|0x80; // 地址 读标志位 for(i8;i0;i--) // 先发 8 位地址 { SCK_L; if(ucAddr 0x80) MOSI_H; else MOSI_L; SCK_H; ucAddr 1; } for(i8;i0;i--) // 再收 8 位数据 { SCK_L; ucResult 1; SCK_H; if(READ_MISO 1) ucResult | 1; } NSS_H; SCK_H; return ucResult; }怎么验证读卡到底成功了没有。最土也最有效的办法是打断点。在rfid_task()的入口和PcdRead之后各下一个断点烧进去跑第一次进断点说明有卡靠近了全速跑之后第二次进断点看car这个变量的值car 0x01就是卡一0x02就是卡二同时 OLED 上的检测物也会跟着变。两个都对上说明读卡链路是通的剩下的问题就只可能出在分拣逻辑上——排查范围一下就缩小了。5.2 红外定位 传送带启停 舵机分拣这是整个系统的主逻辑全在loop()里就是一个很朴素的状态机(hd1_value1) // 位点一检测到工件 { hd1_value_up 1; // 记一个标志防止重复触发 RELAY_ON; motor_state 1; // 传送带启动 } rfid_task(); // 每轮都去寻一次卡随时更新货物类型 if(hd1_value_up1 hd1_value0 hd2_value1) { // 工件已经离开位点一并且到位点二 → 停机分拣 hd1_value_up 0; RELAY_OFF; motor_state 0; // 传送带停 if(car1){ MG90_Set_Angle(90,1); mg1_state1; things0; } // 舵机1 推货一 if(car2){ MG90_Set_Angle(90,2); mg2_state1; things1; } // 舵机2 推货二 HAL_Delay(500); // 等工件滑进分拣槽 MG90_Set_Angle(0,1); mg1_state0; // 两个舵机都归位 MG90_Set_Angle(0,2); mg2_state0; }三个条件缺一不可。只看hd2_value1是不够的——万一有人手贱在位点二放了东西hd1_value_up是 0不会误触发分拣。必须曾经从位点一走了 现在离开位点一了 到位点二了才认。红外检测的采集放在定时器中断里主循环只读结果void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM1) { key_dly; // 按键计时20ms 一次防抖 hd1_value !HD1; hd2_value !HD2; // 取反模块有遮挡输出低 } }5.3 舵机 PWMvoid MG90_Set_Angle(uint8_t angle, uint8_t channel) { /* 角度范围保护防止舵机堵转烧掉 */ if(angle 180) angle 180; if(angle 0) angle 0; /* 0°→500.5ms 180°→2502.5ms线性映射 */ uint16_t pwm_val 50.0 (angle * 200.0 / 180.0); if(channel 1) __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, pwm_val); else __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, pwm_val); HAL_Delay(20); // 等舵机转到位 }__HAL_TIM_SET_COMPARE这个宏直接改的是 TIM2 的 CCR 寄存器改完下一个 PWM 周期就生效不用重启定时器。5.4 OLED 显示OLED_NewFrame(); // 清显存 OLED_PrintString(0, 0, 传送带, font16x16, OLED_COLOR_NORMAL); OLED_PrintString(64, 0, 检测物, font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffers, %s, motor_state0 ? 关 : 开); OLED_PrintString(16, 16, oled_buffers, font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffers, %s, things0 ? 货一 : 货二); OLED_PrintString(648, 16, oled_buffers, font16x16, OLED_COLOR_NORMAL); OLED_PrintString(0, 32, 位点一, font16x16, OLED_COLOR_NORMAL); OLED_PrintString(64, 32, 位点二, font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffers, %s, hd1_value0 ? 否 : 是); OLED_PrintString(16, 48, oled_buffers, font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffers, %s, hd2_value0 ? 否 : 是); OLED_PrintString(80, 48, oled_buffers, font16x16, OLED_COLOR_NORMAL); OLED_ShowFrame(); // 整帧推上去5.5 按键扫描uint8_t key_scan() { static uint8_t key_up 1; if(key_up1 (KEY10||KEY20||KEY30||KEY40)) { key_up 0; // 标记已经按下了不重复触发 if(KEY10) return 1; if(KEY20) return 2; if(KEY30) return 3; if(KEY40) return 4; } else if(KEY11 KEY21 KEY31 KEY41) { key_up 1; // 全松开了允许下一次 } return 0; }配合中断里的key_dly做 20ms 的软件防抖void key_task() { if(key_dly 20) { key_dly 0; key key_scan(); switch (key) { case 1: /* KEY1 手动开关传送带 */ if(motor_state0){ RELAY_ON; motor_state1; } else { RELAY_OFF; motor_state0; } break; case 2: /* KEY2 手动拨舵机1 */ if(mg1_state0){ mg1_state1; MG90_Set_Angle(90,1); } else { mg1_state0; MG90_Set_Angle(0,1); } break; case 3: /* KEY3 手动拨舵机2 */ if(mg2_state0){ mg2_state1; MG90_Set_Angle(90,2); } else { mg2_state0; MG90_Set_Angle(0,2); } break; case 4: /* KEY4 手动切换货物类型不刷卡也能验证分拣 */ if(things1) things0; else things1; break; } } }六、项目主要源码6.1 main.cCubeMX 生成的骨架用户代码就加了三行。int main(void) { HAL_Init(); SystemClock_Config(); // HSE PLL×9 → 72MHz MX_GPIO_Init(); MX_I2C1_Init(); MX_TIM1_Init(); // 1ms 定时中断 MX_TIM2_Init(); // 50Hz 双通道 PWM /* USER CODE BEGIN 2 */ bsp_init(); // 所有模块初始化都塞这儿了 /* USER CODE END 2 */ while (1) { loop(); // 主逻辑 } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; RCC_PeriphCLKInitTypeDef PeriphClkInit {0}; RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL9; // 8MHz × 9 72MHz HAL_RCC_OscConfig(RCC_OscInitStruct); RCC_ClkInitStruct.ClockType RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; // AHB 72MHz RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV2; // APB1 36MHz RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; // APB2 72MHz HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_2); }6.2 bsp.c —— 主循环全在这/* RFID-SPI1 0.96OLED-IIC1 MG90:TIM2-CH1 PA0 TIM2-CH2 PA1 RELAY:PB10 RED-READ: PB3-HD1 PB4-HD2 KEY:KEY1 2 3 4 位点1 检测到一次货物 → 传送带开 位点2 检测到一次货物 → 传送带关根据 RFID 扫到的卡 1/卡 2 决定哪个舵机动作 KEY1: 开启关闭 传送带 KEY2: 开启关闭 舵机1 KEY3: 开启关闭 舵机2 KEY4: 模拟卡1卡2 */ #include bsp.h uint8_t hd1_value, hd2_value; //RFID char status; unsigned char snr, buf[16], TagType[2], SelectedSnr[4]; unsigned char DefaultKey[6] {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; char oled_buffers[25]; uint8_t key; uint8_t key_dly; #define RELAY_ON HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_SET); #define RELAY_OFF HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_RESET); uint8_t things; // 0-货物1 1-货物2 uint8_t motor_state; uint8_t mg1_state; uint8_t mg2_state; void bsp_init() { OLED_Init(); MG90_Init(); LED_OFF; //RFID 初始化 rfid_init(); PcdReset(); PcdAntennaOff(); PcdAntennaOn(); M500PcdConfigISOType(A); // 配置成 ISO14443A RELAY_OFF; motor_state 0; //1ms 定时中断 HAL_TIM_Base_Start_IT(htim1); MG90_Set_Angle(0,1); mg1_state 0; MG90_Set_Angle(0,2); mg2_state 0; } uint8_t hd1_value_up; uint8_t car 1; void loop() { /* ---------------- OLED 刷新 ---------------- */ OLED_NewFrame(); OLED_PrintString(0, 0, 传送带, font16x16, OLED_COLOR_NORMAL); OLED_PrintString(64, 0, 检测物, font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffers, %s, motor_state0 ? 关 : 开); OLED_PrintString(16, 16, oled_buffers, font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffers, %s, things0 ? 货一 : 货二); OLED_PrintString(648, 16, oled_buffers, font16x16, OLED_COLOR_NORMAL); OLED_PrintString(0, 32, 位点一, font16x16, OLED_COLOR_NORMAL); OLED_PrintString(64, 32, 位点二, font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffers, %s, hd1_value0 ? 否 : 是); OLED_PrintString(16, 48, oled_buffers, font16x16, OLED_COLOR_NORMAL); sprintf((char *)oled_buffers, %s, hd2_value0 ? 否 : 是); OLED_PrintString(80, 48, oled_buffers, font16x16, OLED_COLOR_NORMAL); OLED_ShowFrame(); /* ------------- 位点一启传送带 ------------ */ if(hd1_value 1) { hd1_value_up 1; RELAY_ON; motor_state 1; } /* ------------- 循环寻卡 ------------- */ rfid_task(); /* -------- 位点二停机 舵机分拣 -------- */ if(hd1_value_up1 hd1_value0 hd2_value1) { hd1_value_up 0; RELAY_OFF; motor_state 0; if(car1){ MG90_Set_Angle(90,1); mg1_state1; things0; } if(car2){ MG90_Set_Angle(90,2); mg2_state1; things1; } HAL_Delay(500); MG90_Set_Angle(0,1); mg1_state 0; MG90_Set_Angle(0,2); mg2_state 0; } key_task(); } void rfid_task() { status PcdRequest(REQ_ALL, TagType); if(!status) { status PcdAnticoll(SelectedSnr); if(!status) { status PcdSelect(SelectedSnr); if(!status) { snr 1; // 扇区号 1 // 校验 1 扇区密码密码位于每一扇区第 3 块 status PcdAuthState(KEYA, (snr*43), DefaultKey, SelectedSnr); if(!status) { // 读卡读取 1 扇区 0 块数据到 buf[0]-buf[15] status PcdRead((snr*40), buf); car buf[0]; if(car1){ things0; } if(car2){ things1; } if(!status) { LED_ON; // 读卡成功点亮 LED WaitCardOff(); memset(buf, 0, 16); } } } } } LED_OFF; } void key_task() { if(key_dly 20) // 20ms 防抖 { key_dly 0; key key_scan(); switch (key) { case 1: if(motor_state0){ RELAY_ON; motor_state1; } else { RELAY_OFF; motor_state0; } break; case 2: if(mg1_state0){ mg1_state1; MG90_Set_Angle(90,1); } else { mg1_state0; MG90_Set_Angle(0,1); } break; case 3: if(mg2_state0){ mg2_state1; MG90_Set_Angle(90,2); } else { mg2_state0; MG90_Set_Angle(0,2); } break; case 4: if(things1){ things0; } else if(things0){ things1; } break; } } }6.3 bsp_it.c —— 定时中断#include bsp_it.h uint8_t rx1, rx2; extern uint8_t key_dly; extern uint8_t hd1_value; extern uint8_t hd2_value; #define HD1 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) #define HD2 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) HAL_UART_Receive_IT(huart1, rx1, 1); if (huart-Instance USART2) HAL_UART_Receive_IT(huart2, rx2, 1); } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM1) { key_dly; // 按键计时 hd1_value !HD1; hd2_value !HD2; } }6.4 mg90.c#include mg90.h /* 舵机角度控制把 0~180 度映射成对应的 PWM 占空比 */ void MG90_Set_Angle(uint8_t angle, uint8_t channel) { if(angle 180) angle 180; // 角度保护防止舵机损坏 if(angle 0) angle 0; /* 0°→500.5ms180°→2502.5ms线性计算 */ uint16_t pwm_val 50.0 (angle * 200.0 / 180.0); if(channel 1) __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, pwm_val); else __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, pwm_val); HAL_Delay(20); // 等舵机到位 } /* 舵机初始化启动 PWM 输出需在 MX_TIM2_Init 之后调用 */ void MG90_Init(void) { HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_2); MG90_Set_Angle(0,1); MG90_Set_Angle(0,2); }6.5 key.c#include key.h #define KEY1 HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) #define KEY2 HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) #define KEY3 HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) #define KEY4 HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) uint8_t key_scan() { static uint8_t key_up 1; if(key_up1 (KEY10||KEY20||KEY30||KEY40)) { key_up 0; if(KEY10) return 1; if(KEY20) return 2; if(KEY30) return 3; if(KEY40) return 4; } else if(KEY11 KEY21 KEY31 KEY41) { key_up 1; } return 0;七、工程主要内容工程的具体内容包括但不限于电气接线图、原理图、流程图、STM32 完整源码CubeMX 工程 Keil 工程等等。需要的同学可以评论区留言或者私信010-基于RFID的生产流水线工件分拣系统设计

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价