智能硬件赛项
前言
网站
[平台(]https://dev.bj-jc.com:20002/RemoteExp/#/stu-exp-panel)
参考文章/代码
- https://github.com/XiaomanChu/AGV-Driver
- 语音识别模块官网
- STM32定时与计数器总结 第一部分
- stm32之MG995舵机+原理+程序+详解
- 基于STM32实现MQTT
- ST-Link usb communication error 解决方法
- 【STM32标准库】【基础知识】外部中断
在线工具
官方资料
- 2021-2022 智能硬件应用开发赛项竞赛规程
- 智能硬件应用开发平台(使用说明书)型号:JC-IHAP-V1.0
- 智能硬件应用开发平台(安装说明书)型号:JC-IHAP-V1.0
- 智能硬件应用开发平台(AGV小车安装说明书)型号:JC-IHAP-V1.0
- 2020-2021年度广东省职业院校技能大赛高职组智能硬件应用开发赛项样题
- 智能硬件应用平台高职-系统方案介绍
- 沙盘启动说明文档
- 垃圾分类沙盘总体说明
- 垃圾桶控制说明
所用手册资料+思维导图XMind 阿里云网盘:https://www.aliyundrive.com/s/Xj1tqTZn9je
图片
智能硬件小车总体流程图
需要安装的软件
- Keil uVision5 MDK + stm32F4芯片包
- Altium Designer 20(看原理图用)
- 待补充
小车信息(JC-SC)
组成 | 具体参数 |
---|---|
底盘 | ① 尺寸 400×307×123mm; ② 颜色:蓝; ③ 负载能力:10kg; ④ 底盘高度:21.5mm |
麦克纳姆轮 | ① 直径:100mm; ② 轴向宽度:50mm; ③ 板数:2; ④ 滚轮数:9; ⑤ 材质:铝合金;⑥ 净重:0.4kg;⑦ 负载能力:15kg。 |
联轴器 | ① 规格:6mm 内径; ② 材质:铝合金。 |
电机 | ① 工作电压:12v;② 额定功率:17w; ③ 空载转速:8100rpm;④ 减速比:64:1; ⑤ 输出轴:6mm D 型 |
编码器 | ① 类型:A/B 相增量式磁编码器; ② 线数:12; ③ 供电电压:5V。 |
机械臂/机械手 | ① 机械臂:三自由度高精度步进驱动机械臂,前伸距离:442mm,缩回距离:254mm,最高 点: 322mm,最低点:-159mm; ② 配备高精密 42 行星减速步进电机,减速比:1:10; ③ 电机驱动电压:12V;④ 工作最大电流:4.5A; ⑤ 机械手最大尺寸:53mm,5V 舵机驱动 |
电机驱动 | ① 控制板:额定输入电压:DC 12V/24V;② 输出通道数:4 路;③ 每路额定输出电流:7A;④ 额定输出功率:84W(12V 供电);⑤ 控制信号电压:3~6.5V;⑥ PWM 频率范围:0~10kHz |
超声波模块 | ① 工作电压:DC5V;② 工作电流:15mA; ③ 工作频率:40kHz;④ 最远射程:4m;最近射程:2cm; ⑤ 测量角度:15 度。 |
磁导航传感模块 | ① 检测极性:SorN; ② 输入电压:9~28V; ③ 输出方式:NPN-OC&RS232。 |
串口无线通信模块 | 芯片 SI4438,工作频率:425~525MHZ; |
锂 离 子聚 合物 电 池 组 | 电 池 组 :12.8V 8000mAh; |
机械臂组成 | 作用 | 采用芯片 |
---|---|---|
底盘 | 用于机械臂的左右转动,在底盘的前方有一个位置传感器,用于底盘初始位置的校准定位 | 采用42步进电机加减速装置运动的,控制方式是给一个脉冲转动一定的角度,所以只要不丢步,运行的位置是确定的(前提是上电后执行位置初始化) |
大臂 | 用于前后转动,在大臂上有一个位置传感器,用于大臂的初始位置的定位 | 同上 |
小臂 | 用于上下转动,在小臂上有一个位置传感器,用于小臂的初始位置的定位 | 同上 |
舵机夹子 | 夹取物品 | 使用舵机控制的,舵机采用PWM占空比驱动,PWM占空比决定了夹子的位置,所以不需要上电校准,只要上电后保持最大张开角就可以 |
激光测距板 | 能够探测前面物体的距离,用于检测夹子前面是否有物块,左右移动机械臂来找准物块 | / |
STM32口袋机信息(JC-SPZII)
组成 | 具体参数 |
---|---|
MCU | STM32F407ZG |
LED | 8 个单色 LED 灯,5 个LED 电源指示灯 |
按键 | 8 个方向圆盘电容式按键,具有震动 体感反馈;1 个电子物理按键,具有复位和开 机功能 |
拨码开关 | 5 位拨码开关,控制 BOOT、体感 震动等 |
LCD 液晶屏 | 65535 真彩,2.4 寸,分辨率: 240*320,4 线 SPI 接口 |
高速模拟输入 | / |
SRAM 静态内存 | 512K*16bit/1MB |
以太网口 | / |
DAC 模拟输出 | / |
ADC 模拟输入 | 1 路 12bit-ADC 模拟信号采 集 |
TF 卡 | 标准的 SD 卡协议 |
TYPE-C | 板载 TYPE-C 接口,具有烧写、调 试、仿真等功能 |
USB | USB 主从接口 |
供电电压 | 7~24V、1A 电源适配器;5V、 3A 的 TYPE-C |
GPIO 接口 | 可用 GPIO 共60pin |
音频 | 集有高质量的立体双声道 |
智能分拣线信息(JC-IGSP)
组成 | 具体参数 |
---|---|
环形输送线 | ① 柔性链板输送线参数;长 1500 宽 450 高 ② 250 12V 直流电机驱动。 ③ 柔性链板输送线组成;由主动轮、被动轮、铝合金支架、铝合金导轨、衬条及护栏组成。 |
图像采集 | ① 工业相机:CMOS 光学传感器尺寸 1/2.5、分辨率 2592X1944、帧率 8FPS; 柔性链板输送线参数;长 1500 宽 450 高 ② 工业镜头焦距 8mm; ③ LED 环形白光光源; ④ 专业可调光源控制器; ⑤ 可调式相机和光源金属支架 |
推杆装置 | 最大尺寸:53mm,5V 舵机驱动(MG995型号) |
处理器 | ① GPU:128 核心 Maxwell ② CPU:四核 ARM A57@1.43GHZ |
集中站信息(JC-ICS)
组成 | 具体参数 |
---|---|
整体结构 | 整体架构采用铝合金框架式模块化结构,面板采用理化板。 |
集中回收区 | ① LED 显示屏参数:供电电压 5V、LED 点阵屏,可以显示 16 个汉字(16 x 16 分辨率) ② 仓库尺寸:1550 x 300 x 450(mm) ③ 含四个门、能够自动开启 ④ 每个仓库门采用模拟舵机控制 ⑤ 地图尺寸: 2000mm x 3000mm,采用 PVC 地板加工业打印技术,构造回收系统展示情景。 ⑥ 台面贴有 10mm 宽黑色磁引导带,采用 N 极向上方式,以便小车巡线行驶 ⑦ 4 路舵机控制接口 ⑧ 一个 LED 点阵屏(分辨率 256 x 16)驱动接口(8080 接口) ⑨ 4 路 SW2812 彩灯控制接口,可以控制每个集中站垃圾箱灯的颜色和亮度 ⑩ 板载 433MHz 的无线通讯模块,可以与其沙盘上的其他设备无线通讯 |
智能回收垃圾桶信息(JC-SRD)
组成 | 具体参数 |
---|---|
整体 | ① 尺寸:240 x 310 x 402mm; ② 重量:3.5Kg; ③ 材料:高强度冲击 ABS; ④ 红 外 感 应 器 : 高 灵 敏 度 , 延 时 时 间0.5-200S,可调 ⑤ 涡轮式真空压缩机:高速涡轮式真空压缩机,转数:12v5000 ⑥ 高温自动封袋器:(横向纵向双向熔接,功率 10W) ⑦ 称重传感器:精度 1 克 ⑧ 内置铅酸电池:12V 2.2AH ⑨ 适配器规格:13.8V 0.65A ⑩ 4 路直流电机 H 桥控制输出:12V 输出,最大电流 1A,分别控制小盖的开合,大盖的开合,自动封口机械 X 轴和 Y 轴的行程,限位开关接口 ⑪ 1 路使用 PWM 控制的电热丝加热输出接口,用于垃圾袋的封口和熔断 ⑫ 1 路红外反射传感器接口,用于手势识别 ⑬ 1 路红外对射传感器接口,用于检测垃圾桶中是否有垃圾袋 ⑭ 1 路称重传感器接口,最大量程 5kg ⑮ 板载 433MHz 的无线通讯模块,可以与其沙盘上的其他设备无线通讯 |
比赛须知(来自官方手册)
- 比赛提供 STM32
初始化及各类传感、控制、驱动和通信组件
的基本驱动程序,提供标准函数调用接口和详细说明。提供各网络终端数据交互所需 MQTT 协议代码及接口调用说明 - 根据参赛队竞赛成绩排名分别设立一、二、三等奖。以各赛项实际参赛队数量为基数,一、 二、三等奖获奖比例分别为
15%、25%、40%
(小数点后四舍五入)
比赛步骤
比赛步骤
:
裁判将写有数据的射频卡给选手,选手刷卡,口袋机经过读卡器读取射频卡中的任务参数。
比如:识别几个垃圾,全部垃圾全部扔到垃圾桶后,将全部垃圾打包然后扔到回收站中。
运行步骤
:
1.将垃圾放在传送带上,然后刷卡,传送带开始转动,同时摄像头开始识别;识别后的垃圾按照分类分别推送到托盘上
2.小车开始抓取托盘上的垃圾,然后扔到指定的垃圾桶中
3.将全部垃圾扔到垃圾桶后,全部垃圾桶开始打包垃圾
4.小车将打包好的垃圾扔到指定的集中站中
5.全部任务完成
STM32F407ZG介绍
口袋机部分
口袋机调试
用仿真器下载程序调试,USB线插 TYPE-C 口(需要下载驱动 dpinst_amd64
),串口调试助手需要先安装驱动(CP210xVCPInstaller_x64
),如果串口接收没反应可把USB重新插一下
口袋机基础模块学习
延时函数
SysTick_Config
函数的参数是systick重装定时器的值,RCC_Clocks.HCLK_Frequency
可以通过调试知道是168000000
,168000000/1000 = 168000;168000/168000000 = 0.001s=1ms
/*************************delay.h*************************/
# include "stm32f4xx.h"
extern __IO uint32_t LocalTime;
void TimingDelay_Decrement(void);
void delay_Init(void);
void delay_ms(__IO uint32_t nTime);
/*************************delay.c*************************/
static __IO uint32_t TimingDelay;
__IO uint32_t LocalTime = 0;
void delay_Init(void)
{
RCC_ClocksTypeDef RCC_Clocks;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); //配置SysTick定时器时钟源为HCLK
RCC_GetClocksFreq(&RCC_Clocks);
if (SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000))//SysTick定时器每1ms中断一次
{
/* Capture error */
while (1);
}
NVIC_SetPriority (SysTick_IRQn, 0); //配置SysTick定时器的优先级
}
void delay_ms(__IO uint32_t nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
LocalTime++;
}
查看时钟源
在main函数添加下面两行代码:
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq(&RCC_Clocks);
用ST-Link连接,打开keil调试即可看到
点阵
所用模块 | 连接注意 |
---|---|
STM32模块扩展板 | 插口袋机右边 |
点阵屏模块 | 背面印有P1的那边插GND,不能插反否则会烧坏模块 |
硬件连接:
-
点阵模块
行驱动是两片74HC138
,列驱动是两片74Hc595芯片
-
74HC595芯片需要懂:
是一个 移位寄存器
,它是 8位串行输入,并排输出
8位串行输入
:所谓8位串行输入,就是由8个数字组成,每一个数字占据一个位,共有8个位,像串在一起一样以一行的方式输入到蓝色方框内(蓝色框就是我们的芯片)
移位寄存
:它不是直接进去的,而是以 “移位” 的方式进去的,每次只移动一个数字进去,蓝色方框内最多可以移进去8个数字,也就说 74HC595移位寄存器 最多可存储8个数字,称为 “寄存”
绿色方框 = SH_CP
(CLK),移位寄存器的输入条件就是SH_CP这个引脚,当它处于 高电平
时,数字才能被送进74HC595;
蓝色圆球 = ST_CP
(SCK),当 “并排输出开关” 被触发时,74HC595移位寄存器里面存储的数字就会被输出出来,它不是一个一个的出来的,而是并排同时出来,称为 “并排输出”
- 74HC138芯片要懂
程序
由图可知P1是输入,P2是输出,故只需编程P1管脚即可,点阵是高电平亮
P1管脚号 | 对应扩展板管脚 |
---|---|
GND | GND |
D | PA4 |
C | PD7 |
B | PA5 |
A | PF1 |
悬空(可不管) | PD12 |
DINT | PD14 |
SCK | PD15 |
CLK | PF14 |
3.3V | 3.3V |
- 由原理图可知
D
是用来区分哪一块138芯片的,当D
低电平时连接u1
;当D
高电平时连接u4
- 一个8*8点阵从左到右是
低位-->高位
(对应1248码就是 1–2–4–8–16–32–64–128) - 取模软件生成四个角点亮步骤:选择
16*16
— 描点(黑色表示1
,高电平) —图像左右调换
— 参数设置(横向取模,任何时候都加0
) —C51格式
— 复制粘贴即可
/*************************STM32F40x_ GPIO Init.h*************************/
# define CLK_L (GPIOF->BSRRH = GPIO_Pin_14) //控制PF14输出低电平
# define CLK_H (GPIOF->BSRRL = GPIO_Pin_14) //控制PF14输出高电平
# define LAT_L (GPIOD->BSRRH = GPIO_Pin_15) //SCLK
# define LAT_H (GPIOD->BSRRL = GPIO_Pin_15) //SCLK
# define DIN_L (GPIOD->BSRRH = GPIO_Pin_14)
# define DIN_H (GPIOD->BSRRL = GPIO_Pin_14)
# define A_L (GPIOF->BSRRH = GPIO_Pin_1)
# define A_H (GPIOF->BSRRL = GPIO_Pin_1)
# define B_L (GPIOA->BSRRH = GPIO_Pin_5)
# define B_H (GPIOA->BSRRL = GPIO_Pin_5)
# define C_L (GPIOD->BSRRH = GPIO_Pin_7)
# define C_H (GPIOD->BSRRL = GPIO_Pin_7)
# define D_L (GPIOA->BSRRH = GPIO_Pin_4)
# define D_H (GPIOA->BSRRL = GPIO_Pin_4)
/*************************STM32F40x_ GPIO Init.c*************************/
//端口初始化
void GPIO_init_all(void)
{
GPIO_init(GPIO_F,GPIO_Pin_14,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//CLK
GPIO_init(GPIO_D,GPIO_Pin_15,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//SCK
GPIO_init(GPIO_D,GPIO_Pin_14,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//DIN
GPIO_init(GPIO_D,GPIO_Pin_12,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//悬空
GPIO_init(GPIO_F,GPIO_Pin_1,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//A
GPIO_init(GPIO_A,GPIO_Pin_5,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//B
GPIO_init(GPIO_D,GPIO_Pin_7,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//C
GPIO_init(GPIO_A,GPIO_Pin_4,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//D
}
/*************************main.c*************************/
u8 dis_buf_test[32] =
{0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}; //测试数据,全亮
u32 dis_count = 0;
void dis_flash_fun(u8 *p);
void decode_138_fun(u8 row);
void hc595_out(unsigned char data);
int main(void)
{
//...
GPIO_init_all();//初始化所有要使用的端口
for(dis_count=0;dis_count<1;dis_count++)//循环显示1次测试数据,测试数据是把全部LED点亮,用于测试模块,看有无坏点
{
dis_flash_fun(dis_buf_test);
}
while(1)
{
}
}
//扫描16*16 点阵 输入参数是 一个32字节的数组 数据依次是第一行的右边 第一行的左边
void dis_flash_fun(u8 *p)
{
u8 i;
u8 count;
count = 0;
for(i=0;i<16;i++)
{
decode_138_fun(i);//行扫描
hc595_out(*(p+count));//串行输出第一个字节,给右边的8*8 LED
count++;
hc595_out(*(p+count));//串行输出第二个字节,给左边的8*8 LED
count++;
delay_ms(1);//延时1ms 使得图像停留1ms的时间 不然会扫描的太快
}
}
//控制两片138译码器 输入0-15 控制端口ABCD 依次高电平,实现行扫描
void decode_138_fun(u8 row)
{
switch(row)
{
case 0:A_L;B_L;C_L;D_L;break; //000 0
case 1:A_H;B_L;C_L;D_L;break; //100 0
case 2:A_L;B_H;C_L;D_L;break; //010 0
case 3:A_H;B_H;C_L;D_L;break; //110 0
case 4:A_L;B_L;C_H;D_L;break; //001 0
case 5:A_H;B_L;C_H;D_L;break; //101 0
case 6:A_L;B_H;C_H;D_L;break; //011 0
case 7:A_H;B_H;C_H;D_L;break; //111 0
case 8:A_L;B_L;C_L;D_H;break; //000 1
case 9:A_H;B_L;C_L;D_H;break; //100 1
case 10:A_L;B_H;C_L;D_H;break; //010 1
case 11:A_H;B_H;C_L;D_H;break; //110 1
case 12:A_L;B_L;C_H;D_H;break; //001 1
case 13:A_H;B_L;C_H;D_H;break; //101 1
case 14:A_L;B_H;C_H;D_H;break; //011 1
case 15:A_H;B_H;C_H;D_H;break; //111 1
}
}
//74hc595时序函数 data是要并行输出的8位数据
void hc595_out(unsigned char data)
{
unsigned char i,temp;
for(i=0;i<8;i++)
{
if((temp&0x80)==0x80)
{
DIN_H;
}
else
{
DIN_L;
}
temp = temp<<1;
CLK_H;
CLK_L;
LAT_H;
LAT_L;
}
}
WIFI
所用模块 | 连接注意 |
---|---|
STM32模块扩展板 | 插口袋机左边 |
WIFI模块 | 模块有按键的朝右边 |
硬件连接:
管脚号 | 对应扩展板管脚 |
---|---|
RST | PG13 |
RX | PD2(USART5) |
TX | PC12(USART5) |
wifi模块的使用步骤是:(都是使用AT命令实现的)
① 关闭回显
② 设置模式
③ 连接路由器
④ 连接服务器
⑤ 发送数据
- 首先在手机开热点,热点名和密码记住,在代码里填写(注意数组大小也要改)
- 打开网络适配器查看IP,代码里电脑端那填
IPv4地址
- 注意命令里""内容是用反斜杠
\
隔着的(数组大小不包括它) - 单片机给wifi模组发送AT指令后,
需要稍微延时
等待wifi模组应答,读的太快会读不到wifi应答的AT指令,会达不到研发要求 - 回显的含义是:发送什么指令,返回给你结果的时候,会把发送的指令再显示一遍
- LCD显示的自己定义的数组时需要
len-2
(把/r/n去掉),显示Wifi返回的数据时不需要
程序
ESP8266.c
/*************************ESP8266.c*************************/
# define CW_LEN 35 // CWJAP 数组的字符串长度(不包括\)
char CWJAP[CW_LEN] = {"AT+CWJAP=\"yang5201314\",\"00000000\"\r\n"};//yang5201314是WIFI的ssid 00000000是密码(手机热点)
# define CIPSTART_LEN 39 // CIPSTART数组的字符串长度(不包括\)
char CIPSTART[CIPSTART_LEN] = {"AT+CIPSTART=\"TCP\",\"172.20.10.7\",12345\r\n"};//172.20.10.7是电脑端的IP地址 12345是端口号
char CWMODE[13] = {"AT+CWMODE=1\r\n"};//设置模式 不用改变
char CIPMUX[13] = {"AT+CIPMUX=0\r\n"};// 设置模式 不用改变
char CIPSEND_5[14] = {"AT+CIPSEND=5\r\n"};//连接上服务器后 需要发送数据的命令 5是要发送数据的长度
char CIPSEND_9[14] = {"AT+CIPSEND=9\r\n"};// 连接上服务器后 需要发送数据的命令 5是要发送数据的长度
char send_buf[5] = {"12345"};//要发送的数据
char GOT_IP[6] = {"GOT IP"};//定义一个数组
char OK_buf[2] = {"OK"};//定义一个数组
char http_race_buf[20];//收到服务器返回的数据
char get_time_cmd_buf[9] = {"get time;"};//向服务器发送 获取时间命令
extern char usart5_race_buf[100];//串口5 接收数组
extern u16 usart5_race_count; //串口5接收数据计数
//关闭回显
u8 ATE_fun(void)
{
u8 temp;
//串口5 向WIFI模块 发送ATE0 命令
Uart5_send_data("ATE0\r\n",6); //串口1 给电脑也发送
Usart1_send_data("ATE0\r\n",6); //口袋机屏幕显示
LCD_ShowString_stm32_send("ATE0\r\n",4);
//延时
delay_ms(500);
//串口1 发送 串口5收到的数据(wifi模块返回的数据)
Usart1_send_data(usart5_race_buf,usart5_race_count);
//屏幕显示 wifi模块返回的数据
LCD_ShowString_stm32_race(usart5_race_buf);
//串口5再发送一次ATE0 有时候第一次发送回有问题
Uart5_send_data("ATE0\r\n",6); //串口1给电脑发送ATE0
Usart1_send_data("ATE0\r\n",6); //延时
delay_ms(500);
//串口1发送 wifi模块返回的数据
Usart1_send_data(usart5_race_buf,usart5_race_count);
//屏幕显示
LCD_ShowString_stm32_race(usart5_race_buf);
//检查usart5_race_buf数组中 是否有OK 字符
temp = chack_have_fun(OK_buf,2,usart5_race_buf,30);
if(temp==1)//有 说明ATE0 设置成功
{
printf("ATE-ok\r\n");
return 1;
}
else
{
printf("ATE-error\r\n");
return 0;
}
}
//连接WIFI
u8 CWJAP_fun(void)
{
u8 temp;
//发送连接路由器命令 CWJAP 是要发送的数组
Uart5_send_data(CWJAP,CW_LEN);
//同时也给电脑串口发送
Usart1_send_data(CWJAP,CW_LEN);
//屏幕显示
LCD_ShowString_stm32_send(CWJAP,CW_LEN-2);
//延时10秒 有时候时间比较长
delay_ms(20000);
//串口1 向电脑发送 wifi模块返回的数据
Usart1_send_data(usart5_race_buf,usart5_race_count);
//屏幕显示wifi模块返回的数据
LCD_ShowString_stm32_race(usart5_race_buf);
//判断 usart5_race_buf 收到的数据中 是否有GOT_IP 字符串
temp = chack_have_fun(GOT_IP,6,usart5_race_buf,30);
if(temp==1)//连接路由器成功
{
printf("WIFI_link_ok\r\n");
return 1;
}
else
{
printf("WIFI_link_error\r\n");
return 0;
}
}
//设置模式 wifi 模块初始化
u8 CWMODE_fun(void)
{
u8 temp;
Uart5_send_data(CWMODE,13);
Usart1_send_data(CWMODE,13);
LCD_ShowString_stm32_send(CWMODE,11);
delay_ms(1000);
Usart1_send_data(usart5_race_buf,usart5_race_count);
LCD_ShowString_stm32_race(usart5_race_buf);
temp = chack_have_fun(OK_buf,2,usart5_race_buf,30);
if(temp==1)
{
return 1;
}
else
{
return 0;
}
}
//设置模式 wifi模块初始化 一般不用修改
u8 CIPMUX_fun(void)
{
u8 temp;
Uart5_send_data(CIPMUX,13);
Usart1_send_data(CIPMUX,13);
LCD_ShowString_stm32_send(CIPMUX,11);
delay_ms(1000);
Usart1_send_data(usart5_race_buf,usart5_race_count);
LCD_ShowString_stm32_race(usart5_race_buf);
temp = chack_have_fun(OK_buf,2,usart5_race_buf,30);
if(temp==1)
{
return 1;
}
else
{
return 0;
}
}
//发送域名和端口 连接服务器
u8 CIPSTART_fun(void)
{
u8 temp;
// 连接的域名和端口在CIPSTART数组中 上面有定义
Uart5_send_data(CIPSTART,CIPSTART_LEN);
Usart1_send_data(CIPSTART,CIPSTART_LEN);
//屏幕显示
LCD_ShowString_stm32_send(CIPSTART,CIPSTART_LEN-2);
delay_ms(5000);
//串口1 发送 wifi模块返回的数据
Usart1_send_data(usart5_race_buf,usart5_race_count);
//屏幕显示
LCD_ShowString_stm32_race(usart5_race_buf);
//检查usart5_race_buf wifi模块返回的数据中 是否有 OK 字符串
temp = chack_have_fun(OK_buf,2,usart5_race_buf,30);
if(temp==1)//连接成功
{
return 1;
}
else
{
return 0;
}
}
//向服务器发送数据
u8 CIPSEND_fun(void)
{
u8 temp;
//发送CIPSEND_5 命令 定义在最上面
Uart5_send_data(CIPSEND_5,14);
//串口1也发送 用于串口监测
Usart1_send_data(CIPSEND_5,14);
//屏幕显示
LCD_ShowString_stm32_send(CIPSEND_5,12);
//延时5秒
delay_ms(5000); //串口1 显示 wifi模块返回的数据
Usart1_send_data(usart5_race_buf,usart5_race_count);
//屏幕显示wifi 返回的数据
LCD_ShowString_stm32_race(usart5_race_buf);
//监测usart5_race_buf 数组(wifi模块返回的数据)中 是否有OK 字符串
temp = chack_have_fun(OK_buf,2,usart5_race_buf,30);
//向服务器发送数据 send_buf就是要发送的数组 发送5个字节 send_buf在上面有定义
Uart5_send_data(send_buf,5); //延时1秒
delay_ms(1000); //串口打印 返回的数据
Usart1_send_data(usart5_race_buf,usart5_race_count);
//屏幕显示返回的数据
LCD_ShowString_stm32_race(usart5_race_buf);
//监测usart5_race_buf数组 wifi模块返回的数据中有无 OK 字符串
temp = chack_have_fun(OK_buf,2,usart5_race_buf,30);
if(temp==1)
{
return 1;
}
else
{
return 0;
}
}
//检查数组中有无字符串 没有返回0 有就返回位置数据
u8 chack_have_fun(char *flr, u8 len, char *buf, u16 mun)
{
u8 i;
u16 j;
u8 temp;
for(j=0;j<mun-len+1;j++)
{
temp = 0;
for(i=0;i<len;i++)
{
if(*flr==*buf)
{
temp++;
}
flr++;
buf++;
}
flr = flr - len;
buf = buf - len;
buf++;
if(temp==len)
{
return 1;
}
}
return 0;
}
/*************************main.c*************************/
int main(void)
{
//...
RST_L; //Wifi复位键拉低
delay_ms(10);
RST_H; //Wifi复位键拉高
delay_ms(3000); //延时3s
Uart5_Init();//串口5 与wifi模块连接
ATE_fun();//关闭串口回显
CWJAP_fun();//连接WIFI
CWMODE_fun();//设置模式
CIPMUX_fun();//设置模式
CIPSTART_fun();//连接服务器
while(1)
{
CIPSEND_fun();//发送数据给服务器
delay_ms(5000);
}
}
语音识别
所用模块 | 连接注意 |
---|---|
STM32模块扩展板 | 插口袋机右边 |
语音识别模块 | 模块话筒在左上角边 |
硬件连接:
管脚号 | 对应扩展板管脚 |
---|---|
CLK(SPI时钟) | PA4 |
MOSI(SPI数据输入) | PD7 |
MISO(SPI数据输出) | PA5 |
CS(SPI片选) | PF1 |
RST(复位) | PD12 |
INT(中断输出) | PD14 |
- 语音识别芯片采用LD3320,与口袋机SPI接口通讯,外接麦克风,外接晶振;通过SPI接口设置LD3320进行初始化和语音识别库的设置,完成特定语音识别
- 每次识别最多可以设置
50项候选识别句
,每个识别句可以是单字,词组或短句
,长度为不超过10个汉字或者79个字节的拼音串
语音播放MP3文件,需要把MP3文件16进制做成数组(MP3分有ID3头跟无ID3头,这两者不会有影响)
可以用16进制阅读器(比如 UltraEdit
(软件+破解在阿里云盘) )打开mp3文件和数组相对照
程序
/*************************main.c*************************/
u8 INT_flag = 1; //上电触发中断
extern u8 nAsrStatus;
extern u8 nLD_Mode;
//所有中断线都在这,这里只用到14
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line10) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line10);
}
if(EXTI_GetITStatus(EXTI_Line11) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line11);
}
if(EXTI_GetITStatus(EXTI_Line12) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line12);
}
if(EXTI_GetITStatus(EXTI_Line13) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line13);
}
if(EXTI_GetITStatus(EXTI_Line14) != RESET)
{
//printf("INT\r\n");
EXTI_ClearITPendingBit(EXTI_Line14);
INT_flag = 1;
}
if(EXTI_GetITStatus(EXTI_Line15) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line15);
}
}
u8 test_u8;
int main(void)
{
LCD_ShowString(0, 0, "speech recognition:", 32, TYPEFACE);
GPIO_init_all();//初始化用到的IO端口
EXTIX_Init();//外部中断初始化
ld3320_reset();//模块复位
delay_ms(100);//延时
while(ld3320_check())//检查模块通讯是否正常
{
printf("LD3320 Error!!\r\n");//串口打印错误
delay_ms(500);
}
printf("LD3320 OK!!\r\n");//串口打印正常
ld3320_init_asr();//模块设置ASR寄存器
ld3320_asr_addFixed();//添加识别关键词语
test_u8 = ld3320_read_reg(0xBF);//看是否是31
test_u8 = ld3320_asrun();//
nAsrStatus = LD_ASR_NONE; // 初始状态:没有在作ASR
nLD_Mode = LD_MODE_ASR_RUN;//
while(1)
{
if(INT_flag)//判断是否有中断
{
INT_flag = 0;//中断标志清零
ld3320_process_init();//初始化
test_u8 = LD_GetResult();//查看识别结果
printf("test_u8 = %02x\r\n",test_u8);//串口打印
LCD_Draw_Rect_Win(0,32,32,32,BACKGROND);//在对应的位置清屏变成蓝色
LCD_ShowNum(0,32,test_u8,1,32,TYPEFACE);//显示识别的结果 显示0-5之间的数字
ld3320_init_asr();//初始化
ld3320_asr_addFixed();//添加识别关键词语
test_u8 = ld3320_read_reg(0xBF);//看是否是31
test_u8 = ld3320_asrun();//
nAsrStatus = LD_ASR_NONE; // 初始状态:没有在作ASR
nLD_Mode = LD_MODE_ASR_RUN;//
}
}
}
/*************************STM32F40x_GPIO_Init.c*************************/
void GPIO_init_all(void)
{
GPIO_init(GPIO_D,GPIO_Pin_14,GPIO_Mode_IN,GPIO_OType_PP,GPIO_PuPd_NOPULL);//D14
GPIO_init(GPIO_D,GPIO_Pin_12,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//D12
GPIO_init(GPIO_F,GPIO_Pin_1,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//F1
GPIO_init(GPIO_A,GPIO_Pin_5,GPIO_Mode_IN,GPIO_OType_PP,GPIO_PuPd_NOPULL);//A5
GPIO_init(GPIO_D,GPIO_Pin_7,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//D7
GPIO_init(GPIO_A,GPIO_Pin_4,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//A4
}
LD3320.h
// 以下三个状态定义用来记录程序是在运行ASR识别还是在运行MP3播放
# define LD_MODE_IDLE 0x00
# define LD_MODE_ASR_RUN 0x08
# define LD_MODE_MP3 0x40
// 以下五个状态定义用来记录程序是在运行ASR识别过程中的哪个状态
# define LD_ASR_NONE 0x00 // 表示没有在作ASR识别
# define LD_ASR_RUNING 0x01 // 表示LD3320正在作ASR识别中
# define LD_ASR_FOUNDOK 0x10 // 表示一次识别流程结束后,有一个识别结果
# define LD_ASR_FOUNDZERO 0x11 // 表示一次识别流程结束后,没有识别结果
# define LD_ASR_ERROR 0x31 // 表示一次识别流程中LD3320芯片内部出现不正确的状态
# define CLK_IN 24 //用户需要根据时钟修改这个值
# define LD_PLL_11 (u8)((CLK_IN/2.0)-1)
# define LD_PLL_MP3_19 0x0f
# define LD_PLL_MP3_1B 0x18
# define LD_PLL_MP3_1D (u8)(((90.0*((LD_PLL_11)+1))/(CLK_IN))-1)
# define LD_PLL_ASR_19 (u8)(CLK_IN*32.0/(LD_PLL_11+1) - 0.51)
# define LD_PLL_ASR_1B 0x48
# define LD_PLL_ASR_1D 0x1f
// LD芯片固定值
# define RESUM_OF_MUSIC 0x01
# define CAUSE_MP3_SONG_END 0x20
# define MASK_INT_SYNC 0x10
# define MASK_INT_FIFO 0x04
# define MASK_AFIFO_INT 0x01
# define MASK_FIFO_STATUS_AFULL 0x08
# define MIC_VOL 0x55 //值越小代表MIC音量越小
# define YUYIN_MAX 5 //语音语句(条)
void EXTIX_Init(void);
void ld3320_reset(void);
u8 ld3320_check(void);
u8 SPI_RW(u8 data);
u8 ld3320_read_reg(u8 add);
void ld3320_write_reg(u8 add, u8 dat);
u8 ld3320_check_asrbusyflag_b2(void);
u8 ld3320_asrun(void);
u8 ld3320_get_result(void);
u8 LD_Check_ASRBusyFlag_b2(void);
void ld3320_init_common(void);
u8 LD_GetResult(void);
void LD_Init_Common(void);
void ld3320_init_asr(void);
u8 ld3320_asr_addFixed(void);
void ld3320_process_init(void);
void LD_AsrStart(void);
u8 ld3320_run_asr(void);
LD3320.c
/*************************LD3320.c*************************/
//枚举
typedef enum
{
nihao = 1,
tongxue = 2,
beijing = 3,
shanghai = 4,
bisai = 5
} Order;
u8 nLD_Mode; //当前模式
u32 nMp3StartPos = 0;
u32 nMp3Pos = 0;
u8 ucStatus;
u32 nMp3Size = 0;
u8 ucRegVal;
u8 nAsrStatus;
u8 ucHighInt;
u8 ucLowInt;
u8 ucSPVol = 15;
u8 bMp3Play;
//D14外部中断初始化
void EXTIX_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能SYSCFG时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOD, EXTI_PinSource14);//D14
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断请求
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//中断线使能
EXTI_Init(&EXTI_InitStructure);//配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//外部中断10-15
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;//抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;//子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//配置
}
//LD3320芯片复位
void ld3320_reset(void)
{
RST_H; //复位引脚拉高
delay_us(10); //延时
RST_L; //复位引脚拉低
delay_us(10);
RST_H; //复位引脚拉高
delay_us(10);
CS_L; //片选拉低
delay_us(10); //延时
CS_H; //片选拉高
delay_us(10);
}
//模拟SPI时序 发送一个字节并 同时读取一个字节
u8 SPI_RW(u8 data)
{
u8 i;
u8 temp;
temp = 0;
for(i = 0; i < 8; i++)
{
if((data & 0x80) == 0x80) //要发送的最高位是否是1
{
MO_H; //mosi输出高
}
else
{
MO_L; //mosi输出底
}
data <<= 1; //数据左移一位
delay_us(10);
SCK_L;
temp = temp << 1;
if(READ_MI) //读取MISO 如果是高
{
temp |= 0x01; //记录数据
}
delay_us(10);
SCK_H; //时钟变高
}
return temp; //返回收到的一个字节
}
//读取寄存器的值,0x00是数据可随意,固定是:操作命令---寄存器地址---数据
u8 ld3320_read_reg(u8 add)
{
u8 temp;
CS_L; //必须要在CS低电平时才有效
SPI_RW(0x05); //发送读操作命令
SPI_RW(add); //发送寄存器地址
temp = SPI_RW(0x00);
CS_H;
return temp;
}
//写入寄存器的值
void ld3320_write_reg(u8 add, u8 dat)
{
CS_L;
SPI_RW(0x04);
SPI_RW(add);
SPI_RW(dat);
CS_H;
}
//检查芯片
u8 ld3320_check(void)
{
u8 a[3];
memset(a, 0x00, sizeof(a));
ld3320_reset();
delay_ms(10);
ld3320_read_reg(0x06); //0x06:(只读)FIFO状态
delay_ms(10);
a[0] = ld3320_read_reg(0x06);
delay_ms(10);
a[1] = ld3320_read_reg(0x35); //0x35:ADC增益,或可以理解为麦克风(MIC)音量
delay_ms(10);
a[2] = ld3320_read_reg(0xb3); //0xb3:打开或关闭 “语音端点检测”功能
printf("%02x ", a[0]); //读取值存储显示
printf("%02x ", a[1]);
printf("%02x ", a[2]);
printf("\r\n");
ld3320_reset();
delay_ms(10);
ld3320_read_reg(0x06);
delay_ms(10);
a[0] = ld3320_read_reg(0x06);
delay_ms(10);
a[1] = ld3320_read_reg(0x35);
delay_ms(10);
a[2] = ld3320_read_reg(0xb3);
printf("%02x ", a[0]);
printf("%02x ", a[1]);
printf("%02x ", a[2]);
printf("\r\n");
ld3320_write_reg(0x35, 0x33); //写入0x33到寄存器地址0x35里
delay_ms(10);
ld3320_write_reg(0x1b, 0x55); //写入0x55到寄存器地址0x1b里 0x1b:时钟频率设置3
delay_ms(10);
ld3320_write_reg(0xb3, 0x40); //写入0xaa到寄存器地址0xb3里,推荐10~40,越小灵敏度越高容易误判
a[0] = ld3320_read_reg(0x35); //读取刚刚写入的值
delay_ms(10);
a[1] = ld3320_read_reg(0x1b); //读取刚刚写入的值
delay_ms(10);
a[2] = ld3320_read_reg(0xb3); //读取刚刚写入的值
printf("%02x ", a[0]);
printf("%02x ", a[1]);
printf("%02x ", a[2]);
printf("\r\n");
if(a[0] != 0x33 || a[1] != 0x55 || a[2] != 0x20) //检查写入的值是否一样
return 1;
return 0;
}
//通用初始化
void LD_Init_Common(void)
{
bMp3Play = 0;
ld3320_read_reg(0x06);
ld3320_write_reg(0x17, 0x35);
delay_ms(10);
ld3320_read_reg(0x06);
ld3320_write_reg(0x89, 0x03); //模拟电路控制 初始化时写 03H MP3播放时写 FFH
delay_ms(5);
ld3320_write_reg(0xCF, 0x43); //内部省电模式设置 初始化时写入 43H MP3初始化和ASR初始化时写入 4FH
delay_ms(5);
ld3320_write_reg(0xCB, 0x02); //ASR:读取ASR结果
ld3320_write_reg(0x11, LD_PLL_11); //时钟频率设置1
if (nLD_Mode == LD_MODE_MP3)
{
ld3320_write_reg(0x1E, 0x00); //ADC专用控制,应初始化为00H
ld3320_write_reg(0x19, LD_PLL_MP3_19); //时钟频率设置2
ld3320_write_reg(0x1B, LD_PLL_MP3_1B); //时钟频率设置3
ld3320_write_reg(0x1D, LD_PLL_MP3_1D); //时钟频率设置4
}
else
{
ld3320_write_reg(0x1E, 0x00);
ld3320_write_reg(0x19, LD_PLL_ASR_19);
ld3320_write_reg(0x1B, LD_PLL_ASR_1B);
ld3320_write_reg(0x1D, LD_PLL_ASR_1D);
}
delay_ms(10);
ld3320_write_reg(0xCD, 0x04); //初始化时写入04H 允许DSP休眠
ld3320_write_reg(0x17, 0x4c);
delay_ms(5);
ld3320_write_reg(0xB9, 0x00); //当前添加识别句的字符串长度(拼音字符串)初始化时写入00H 每添加一条识别句后要设定一次。
ld3320_write_reg(0xCF, 0x4F);
ld3320_write_reg(0x6F, 0xFF); //对芯片进行初始化时设置为0xFF
}
void ld3320_init_asr(void)
{
nLD_Mode = LD_MODE_ASR_RUN; //运行ASR识别
//ld3320_init_common();
LD_Init_Common();
ld3320_write_reg(0xBD, 0x00); //初始化控制寄存器 02H:启动MP3模块 00H:语音识别模块
ld3320_write_reg(0x17, 0x48); //48H:激活DSP 4CH:使DSP休眠,比较省电 35H:对LD3320进行软复位
delay_ms(10);
ld3320_write_reg(0x3C, 0x80); //FIFO_EXT下限低8位
ld3320_write_reg(0x3E, 0x07); //FIFO_EXT下限高8位
ld3320_write_reg(0x38, 0xff); //FIFO_EXT上限低8位
ld3320_write_reg(0x3A, 0x07); //FIFO_EXT上限高8位
ld3320_write_reg(0x40, 0); //FIFO_EXT MCU水线低8位
ld3320_write_reg(0x42, 8); //FIFO_EXT MCU水线高8位
ld3320_write_reg(0x44, 0); //FIFO_EXT DSP水线低8位
ld3320_write_reg(0x46, 8); //FIFO_EXT DSP水线高8位
delay_ms(10);
}
//检查是否处于闲状态
u8 ld3320_check_asrbusyflag_b2(void)
{
u8 j;
u8 flag = 0;
for (j = 0; j < 10; j++)
{
if (ld3320_read_reg(0xb2) == 0x21) //ASR:DSP忙闲状态,0x21 表示闲,查询到为闲状态可以进行下一步ASR动作
{
flag = 1;
break;
}
delay_ms(10);
}
return flag;
}
// Return 1: success.
// 添加识别关键词语,开发者可以学习"语音识别芯片LD3320高阶秘籍.pdf"中关于垃圾词语吸收错误的用法
u8 ld3320_asr_addFixed(void)
{
u8 k, flag;
u8 nAsrAddLength;
const char sRecog[YUYIN_MAX][30] =
{
"ni hao",
"tong xue",
"bei jing",
"shang hai",
"bi sai"
}; //需要识别的拼音
//编号可以相同,可以不连续 例子中的“北京”和“首都”对应同一编号,说这两个词会有相同的结果返回
const Order pCode[YUYIN_MAX] =
{
nihao,
tongxue,
beijing,
shanghai,
bisai
}; //编号
flag = 1;
for (k = 0; k < YUYIN_MAX; k++)
{
if(ld3320_check_asrbusyflag_b2() == 0)
{
//printf("busy------------\r\n");
flag = 0;
break;
}
else
{
//printf("not busy------------\r\n");
}
ld3320_write_reg(0xc1, pCode[k] ); //ASR:识别字Index(00H—FFH)
ld3320_write_reg(0xc3, 0 ); //ASR:识别字添加时写入00
ld3320_write_reg(0x08, 0x04); //清除FIFO内容(清除指定FIFO后再写入一次00H) 第0位:写入1→清除FIFO_DATA 第2位:写入1→清除FIFO_EXT
delay_ms(10);
ld3320_write_reg(0x08, 0x00); //清除指定FIFO后再写入一次00H
delay_ms(10);
for (nAsrAddLength = 0; nAsrAddLength < 20; nAsrAddLength++)
{
if (sRecog[k][nAsrAddLength] == 0)
{
break;
}
ld3320_write_reg(0x5, sRecog[k][nAsrAddLength]); //FIFO_EXT数据口
}
ld3320_write_reg(0xb9, nAsrAddLength); //当前添加识别句的字符串长度
ld3320_write_reg(0xb2, 0xff); //写入忙状态
ld3320_write_reg(0x37, 0x04); //语音识别控制命令下发寄存器 写04H:通知DSP要添加一项识别句。 写06H:通知DSP开始识别语音。 在下发命令前,需要检查B2寄存器的状态
}
return flag;
}
u8 ld3320_asrun(void)
{
u8 temp;
temp = temp;
ld3320_write_reg(0x35, MIC_VOL); //ADC增益,或可以理解为麦克风(MIC)音量。
ld3320_write_reg(0x1C, 0x09); //ADC开关控制写09H Reserve保留命令字
ld3320_write_reg(0xBD, 0x20); //初始化控制寄存器
ld3320_write_reg(0x08, 0x01); //写入20H;Reserve保留命令字
delay_ms(10);
ld3320_write_reg(0x08, 0x00); //清除FIFO内容(清除指定FIFO后再写入一次00H)
delay_ms(10);
if(ld3320_check_asrbusyflag_b2() == 0)
{
return 0;
}
ld3320_write_reg(0xB2, 0xff); //DSP忙闲状态
ld3320_write_reg(0x37, 0x06); //写06H:通知DSP开始识别语音
delay_ms(10);
temp = ld3320_read_reg(0xBF);//看是否是32-3a 读到数值为0x35,可以确定是一次语音识别流程正常结束
ld3320_write_reg(0x1C, 0x0b); //写0BH 麦克风输入ADC通道可用
ld3320_write_reg(0x29, 0x10); //中断允许
ld3320_write_reg(0xBD, 0x00); //写入00H;然后启动;为ASR模块;
return 1;
}
//获取识别最佳结果
u8 LD_GetResult(void)
{
return ld3320_read_reg(0xc5); //读取ASR结果(最佳)
}
void ld3320_process_init(void)
{
u8 nAsrResCount = 0;
u8 temp;
ucRegVal = ld3320_read_reg(0x2B); //中断请求编号
//printf("ucRegVal = %02x\r\n",ucRegVal);
ucHighInt = ld3320_read_reg(0x29); //中断允许
//printf("ucHighInt = %02x\r\n",ucHighInt);
ld3320_write_reg(0x29, 0) ;
ucLowInt = ld3320_read_reg(0x02); //FIFO中断允许
//printf("ucLowInt = %02x\r\n",ucLowInt);
ld3320_write_reg(0x02, 0) ;
if(nLD_Mode == LD_MODE_ASR_RUN)
{
//ucRegVal==0x10表示语音识别有结果产生且芯片内部FIFO中断发生
//读到数值为0x35,可以确定是一次语音识别流程正常结束
//0x21表示空闲
if( (ucRegVal & 0x10) && ld3320_read_reg(0xbf) == 0x35 && ld3320_read_reg(0xb2) == 0x21)
{
//中断辅助信息
//ASR:中断时,是语音识别有几个识别候选 Value: 1 – 4: N个识别候选 0或者大于4:没有识别候选
nAsrResCount = ld3320_read_reg(0xba);
//printf("nAsrResCount = %02x\r\n",nAsrResCount);
if(nAsrResCount > 0 && nAsrResCount < 4)
{
nAsrStatus = LD_ASR_FOUNDOK; //表示一次识别流程结束后,有一个识别结果
temp = LD_GetResult();
printf("temp = %02x\r\n", temp);
}
else
{
nAsrStatus = LD_ASR_FOUNDZERO; //表示一次识别流程结束后,没有识别结果
}
}
else
{
nAsrStatus = LD_ASR_FOUNDZERO; ////表示一次识别流程结束后,没有识别结果
}
ld3320_write_reg(0x2b, 0); //清除中断标志
ld3320_write_reg(0x1C, 0); //ADC开关控制 写00H ADC不可用
return;
}
}
摄像头
摄像头型号OV2640,I2C接口对摄像头进行设置
所用模块 | 连接注意 |
---|---|
STM32模块扩展板 | 插口袋机右边 |
语音识别模块 | 模块有灯的在右下角 |
硬件连接:
管脚号 | 对应扩展板管脚 |
---|---|
Y0(8位数据输出) | PE13 |
Y1 | PF15 |
Y2 | PE15 |
Y3 | PE14 |
Y4 | PB13 |
Y5 | PA4 |
Y6 | PD7 |
Y7 | PA5 |
SDA(I2C数据) | PE14 |
SCL(I2C时钟) | PD15 |
VSYNC(帧同步输出) | PD14 |
PWND(掉电模式使能) | PD12 |
PCLK(像素时钟) | PE1 |
/*************************STM32F40x_GPIO_Init.h*************************/
# define READ_SDA GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_14)
# define READ_VSYNC GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_14)
# define READ_HREF GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_1)
# define READ_PCLK GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_1)
# define READ_Y0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_13)
# define READ_Y1 GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_15)
# define READ_Y2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_15)
# define READ_Y3 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_14)
# define READ_Y4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)
# define READ_Y5 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)
# define READ_Y6 GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_7)
# define READ_Y7 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)
# define SCL_L (GPIOD->BSRRH = GPIO_Pin_15)
# define SCL_H (GPIOD->BSRRL = GPIO_Pin_15)
# define SDA_L (GPIOF->BSRRH = GPIO_Pin_14)
# define SDA_H (GPIOF->BSRRL = GPIO_Pin_14)
# define PWDN_L (GPIOD->BSRRH = GPIO_Pin_12)
# define PWDN_H (GPIOD->BSRRL = GPIO_Pin_12)
# define OV2640_DATA1 GPIOA->IDR
# define OV2640_DATA2 GPIOB->IDR
# define OV2640_DATA3 GPIOD->IDR
# define OV2640_DATA4 GPIOE->IDR
# define OV2640_DATA5 GPIOF->IDR
//IO方向设置
# define SCCB_SDA_IN() {GPIOF->MODER&=(u32)(~(3<<(14*2)));GPIOF->MODER|=(u32)(0<<14*2);} //PF14 输入
# define SCCB_SDA_OUT() {GPIOF->MODER&=(u32)(~(3<<(14*2)));GPIOF->MODER|=(u32)(1<<14*2);} //PF14 输出
/*************************STM32F40x_GPIO_Init.c*************************/
void GPIO_init_all(void)
{
GPIO_init(GPIO_E,GPIO_Pin_14,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //SDA
GPIO_init(GPIO_D,GPIO_Pin_15,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL); //SCL
GPIO_init(GPIO_D,GPIO_Pin_14,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //VSYNC
GPIO_init(GPIO_D,GPIO_Pin_12,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL); //PWND
GPIO_init(GPIO_F,GPIO_Pin_1,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //HREF
GPIO_init(GPIO_A,GPIO_Pin_5,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //Y7
GPIO_init(GPIO_D,GPIO_Pin_7,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //Y6
GPIO_init(GPIO_A,GPIO_Pin_4,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //Y5
GPIO_init(GPIO_E,GPIO_Pin_15,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //Y2
GPIO_init(GPIO_F,GPIO_Pin_15,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //Y1
GPIO_init(GPIO_E,GPIO_Pin_14,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //Y3
GPIO_init(GPIO_E,GPIO_Pin_13,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //Y0
GPIO_init(GPIO_B,GPIO_Pin_13,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //Y4
GPIO_init(GPIO_E,GPIO_Pin_1,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL); //PCLK
}
main.c
/*************************main.c(I2C部分)*************************/
//SCCB起始信号
//当时钟为高的时候,数据线的高到低,为SCCB起始信号
//在激活状态下,SDA和SCL均为低电平
void SCCB_Start(void)
{
SDA_H; //数据线高电平
SCL_H; //在时钟线高的时候数据线由高至低
delay_us(50);
SDA_L;
delay_us(50);
SCL_L; //数据线恢复低电平,单操作函数必要
}
//SCCB停止信号
//当时钟为高的时候,数据线的低到高,为SCCB停止信号
//空闲状况下,SDA,SCL均为高电平
void SCCB_Stop(void)
{
SDA_L;
delay_us(50);
SCL_L;
delay_us(50);
SDA_H;
delay_us(50);
}
//产生NA信号
void SCCB_No_Ack(void)
{
delay_us(50);
SDA_H;
SCL_H;
delay_us(50);
SCL_L;
delay_us(50);
SDA_L;
delay_us(50);
}
//SCCB,写入一个字节
//返回值:0,成功;1,失败.
u8 SCCB_WR_Byte(u8 dat)
{
u8 j,res;
for(j=0;j<8;j++) //循环8次发送数据
{
if(dat&0x80)SDA_H;
else SDA_L;
dat<<=1;
delay_us(50);
SCL_H;
delay_us(50);
SCL_L;
}
SCCB_SDA_IN(); //设置SDA为输入
delay_us(50);
SCL_H; //接收第九位,以判断是否发送成功
delay_us(50);
if(READ_SDA)res=1; //SDA=1发送失败,返回1
else res=0; //SDA=0发送成功,返回0
SCL_L;
SCCB_SDA_OUT(); //设置SDA为输出
return res;
}
//SCCB 读取一个字节
//在SCL的上升沿,数据锁存
//返回值:读到的数据
u8 SCCB_RD_Byte(void)
{
u8 temp=0,j;
SCCB_SDA_IN(); //设置SDA为输入
for(j=8;j>0;j--) //循环8次接收数据
{
delay_us(50);
SCL_H;
temp=temp<<1;
if(READ_SDA)temp++;
delay_us(50);
SCL_L;
}
SCCB_SDA_OUT(); //设置SDA为输出
return temp;
}
其他代码略(源码在阿里云网盘)
超声波测距
硬件连接:
所用模块 | 连接注意 |
---|---|
STM32模块扩展板 | 插口袋机右边 |
超声波模块 | 模块有灯的在左上角 |
使用两个超声波探头,一个发送,一个接收; CS100A
是超声波测距控制芯片
/*************************STM32F40x_Timer_eval.c*************************/
__IO uint16_t Tim3_Cont_val = 0;
extern u32 t_count;
//定时器3
void Timer3_Config(uint16_t period, uint16_t prescaler)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* TIM3 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = period;
TIM_TimeBaseStructure.TIM_Prescaler = prescaler;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* TIM3 enable */
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
/* TIM3 enable counter */
TIM_Cmd(TIM3, ENABLE);
// /* Configure and enable TIM3 interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//定时器中断函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
/* Clear interrupt pending bit */
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
t_count ++;
}
}
/*************************main.c*************************/
//外部中断服务程序
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line14) != RESET)
{
read_bit = READ_ECHO;//读端口电平
if(read_bit==1)//如果是1 计数清零
{
t_count = 0;
}
else //如果是0 停止计数 保存计数值
{
race_count = t_count;
race_ok_bit = 1;
}
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
//外部中断初始化函数
void EXTIX_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能SYSCFG时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource14);//E14
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断事件
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //上升沿下降沿 都触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//中断线使能
EXTI_Init(&EXTI_InitStructure);//配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//外部中断10-15
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;//抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;//子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//配置
}
int main(void)
{
Timer3_Config(20,19);//5us 21*20/42000000=10us
while (1)
{
TRIG_H; //发射管教变高
delay_us(10); //延时
TRIG_L; //发射管教变低
DIS_mm_f = (float)race_count;//读取计数
DIS_mm_f = DIS_mm_f * 5.0f * 0.34f / 2.0f;//计算
DIS_mm_u16 = DIS_mm_f/10;//变成整形
LCD_Draw_Rect_Win(144,0,80,32,BACKGROND);//刷新显示
LCD_ShowNum(144,0,DIS_mm_u16,5,32,TYPEFACE);//显示数据
if(race_ok_bit==1)//是否收到返回的脉冲
{
race_ok_bit = 0;
printf("%.1f\r\n",DIS_mm_u16);//串口打印
}
else
{
printf("no_race\r\n");//串口打印
}
delay_ms(50);//间隔50ms采集一次
}
}
/*************************STM32F40x_GPIO_Init.c*************************/
void GPIO_init_all(void)
{
GPIO_init(GPIO_E,GPIO_Pin_14,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_NOPULL);//E14
GPIO_init(GPIO_E,GPIO_Pin_13,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//E13
}
提供的函数详解
代码集合
/*************************STM32F40x_GPIO_Init.c*************************/
void GPIO_init(u16 GPIOx, u16 GPIO_Pin, GPIOMode_TypeDef mode, GPIOOType_TypeDef type, GPIOPuPd_TypeDef pupd)
{
//宏定义端口组
# define GPIO_A 0x0000
# define GPIO_B 0x0001
# define GPIO_C 0x0002
# define GPIO_D 0x0003
# define GPIO_E 0x0004
# define GPIO_F 0x0005
# define GPIO_G 0x0006
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin; //引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度
GPIO_InitStructure.GPIO_Mode = mode; //操作模式
GPIO_InitStructure.GPIO_OType = type; //输出类型
GPIO_InitStructure.GPIO_PuPd = pupd; //是否上拉/下拉
switch(GPIOx)
{
case GPIO_A:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_Init(GPIOA,&GPIO_InitStructure);
break;
case GPIO_B:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_Init(GPIOB,&GPIO_InitStructure);
break;
case GPIO_C:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
GPIO_Init(GPIOC,&GPIO_InitStructure);
break;
case GPIO_D:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
GPIO_Init(GPIOD,&GPIO_InitStructure);
break;
case GPIO_E:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
GPIO_Init(GPIOE,&GPIO_InitStructure);
break;
case GPIO_F:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
GPIO_Init(GPIOF,&GPIO_InitStructure);
break;
case GPIO_G:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);
GPIO_Init(GPIOG,&GPIO_InitStructure);
break;
}
}
/*************************STM32F40x_LCD_SPI.c*************************/
一个字符占16*32(大字体),16*16(小字体)
//显示字符串
void LCD_ShowString(uint16_t x, uint16_t y, char *p, uint8_t size, uint16_t Color)
● 参数1:x横坐标,0~240
● 参数2:y纵坐标才,0~320
● 参数3:要显示的字符串
● 参数4:显示大小(16是小字体,32是大字体)
● 参数5:颜色
/*************************STM32F40x_Usart_eval.c(USART5部分)*************************/
char usart5_race_buf[100];
u16 usart5_race_count;
u8 usart5_race_over_bit;
void uart5_race_buf_init(void)
{
u16 i;
for(i=0;i<100;i++)
{
usart5_race_buf[i] = 0;
}
}
//串口5初始化
void Uart5_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable GPIO clock */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
/* Connect PXx to USARTx_Tx*/
GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_UART5);
/* Connect PXx to USARTx_Rx*/
GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_UART5);
/* Configure USART Tx as alternate function */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PC12
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Configure USART Rx as alternate function */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PD2
GPIO_Init(GPIOD, &GPIO_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = UART5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable USART clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5, ENABLE);//串口5的时钟使能
//USART5 初始化设置
USART_InitStructure.USART_BaudRate = 115200;//一般设置为115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8bit
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位1位
USART_InitStructure.USART_Parity = USART_Parity_No;//不使用奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //不使用硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(UART5, &USART_InitStructure); //初始化USART5
USART_ITConfig(UART5, USART_IT_RXNE, ENABLE);//开启接收中断
USART_Cmd(UART5, ENABLE); //使能USART5
USART_ClearFlag(UART5, USART_FLAG_TC); //清除发送完成标志位
}
//串口5发送数组 dat是数组 len是长度
void Uart5_send_data(char *dat, unsigned short len)
{
usart5_race_count = 0;
uart5_race_buf_init();//接收数组清零
while(len--)
{
//stm32中DR寄存器只有低9位有效,其余高位是保留的,为了保证正确性和将来的兼容性,只取Data的低9位数据,所以&(uint16_t)0x01FF
UART5->DR = (*dat & (uint16_t)0x01FF);
while(USART_GetFlagStatus(UART5, USART_FLAG_TC) == RESET);//传输完成
dat++;
}
}
//串口5接收中断
void UART5_IRQHandler(void)
{
unsigned char Res;
if(USART_GetITStatus(UART5, USART_IT_RXNE) != RESET) //接收中断
{
Res = USART_ReceiveData(UART5);//(UART5->DR); //读取接收到的数据
usart5_race_buf[usart5_race_count] = Res;
usart5_race_count++;
if(usart5_race_count==100)
{
usart5_race_count = 0;
usart5_race_over_bit = 1;
}
}
}
//重定向printf到USART1
int fputc(int ch, FILE *f)
{
USART1->DR = (ch & (uint16_t)0x01FF);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return ch;
}
口袋机端口
-
PD7
接口 连接了内部RAM的使能,外部是不可以使用的,必须输出高电平
,否则会影响其他端口的电平 -
左侧是口袋机原理图的 P3,00 ~ 29 对应网络标号
GPIO_30 ~ GPIO_59
;右 侧是口袋机原理图的 P1,00 ~ 29 对应网络标号GPIO_00 ~ GPIO_29
-
口袋机按键(初始化为输入,开漏,上拉),低电平时表示按下
按键(管脚) | 对应程序 |
---|---|
A(PA^0) | BUTTON_A READ_BUTTON4 |
B(PG^6) | BUTTON_B READ_BUTTON3 |
C(PG^7) | BUTTON_C READ_BUTTON2 |
D(PG^8) | BUTTON_D READ_BUTTON1 |
上(PG^11) | BUTTON_UP READ_BUTTON7 |
下(PD^6) | BUTTON_DOWN READ_BUTTON6 |
左(PG^10) | BUTTON_LEFT READ_BUTTON8 |
右(PD^3) | BUTTON_RIGHT READ_BUTTON5 |
- 串口1(波特率9600),拿来单片机与电脑通信
USART1(管脚) |
---|
PA^9(USB_UART_Rx) |
PA^10(USB_UART_Tx) |
其他注意问题
- 在使用串口中断函数处理数据时,不用调用函数
USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG)
清除发送完成标志位,否则程序可能会发生异常混乱 - STM32F4系列 —
TIM1,TIM8,TIM11
时钟频率是APB2两分频的2倍时钟频率(即168MHz);TIM2~TIM7
,TIM12~TIM14
时钟频率是APB1四分频的2倍频率(即84MHz);所以定时时需要注意预分频值取167还是83
BSRRH
表示BSRR寄存器高16位(BRy),哪一个BRy置1,引脚输出低电平
;BSRRL
表示BSRR寄存器低16位(BSy),哪一个BRy置1,引脚输出高电平
硬件注意问题
- 小车 12V 充电器;垃圾桶 13.8V 充电器
- 如果小车夹子没反应查看一下线是否松了(如果代码正常的话)
函数问题(第一次用的)
函数 | 函数作用 | 注意 |
---|---|---|
GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF) | 更改指定引脚的映射 | 不要用 “或” 一次多选引脚;要分开写 第二个参数是 GPIO_PinSourcex ,不要写成 GPIO_Pin_x ,它们是有区别的参数3是选择用作备用功能的引脚 |
- 外部中断的一般配置步骤
1、使能SYSCFG时钟:RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYACFG,ENABLE);
2、初始化IO口为输入:GPIO_Init();(中断与IO口电平无关)
3、设置IO口与中断线的映射关系:void SYSCFG_EXTILineConfig();
4、初始化线上中断,设置触发条件:EXTI_init();
5、配置中断分组(NVIC),并使能中断:NVIC_Init();
6、编写中断服务函数:EXTIx_IRQHandler();
7、清除中断标志位:EXTI_ClearITPendingBit();
- 使用定时器的外部时钟
ETR
的方式实现,从ETR输入方波作为外部时钟,输出PWM波触发ADC采集(用来对引脚上的输入信号进行统计)。定时时间计算与内部时钟时一样计算,只是总时钟频率变化了
,在检测验证时要注意计算输出的频率,避免乱设置导致频率太小,计数时用外部时钟模式2 GPIOG->ODR
是直接操作寄存器(对ODR赋值是一次操作16位的,也就是同时设置了16个引脚的输出电平);IDR
是查看引脚电平状态用的寄存器(只读),ODR
是引脚电平输出的寄存器- 清除空闲中断方法:USART的状态位TC是置位的,TC位的置零则是通过软件序列来清除的,具体的步骤是“
先读USART_SR,然后写入USART_DR
”,只有这样才能够清除标志位TC - TIM1做计数时的中断通道是:
TIM1_UP_TIM10_IRQn
(TIM1 更新中断和 TIM10 全局中断)
各设备通信全局定义
单元 | ID |
---|---|
转盘 | 1 |
小车 | 2 |
集中站 | 3 |
垃圾桶1 | 4 |
垃圾桶2 | 5 |
垃圾桶3 | 6 |
垃圾桶4 | 7 |
沙盘存在的问题汇总
轮子到第2个转弯会打滑几秒夹子夹不准(现在夹到的几率有80%)- 轮子的PWM看不懂频率是初始化为多少,根据计算结果不太准(待解决)
在小车没回到原点时要是有垃圾进入托盘小车回到原点后不会去夹,需要在小车在原点再去放垃圾到转盘才行- 转盘识别有时候傻傻的,纸皮被识别成厨余垃圾也是醉了
<1>小车部分
小车代码详解(主要代码)
TIM5/车轮编码器
- 编码器管脚
管脚 | 对应 | 模式 | 哪个轮 | 类型 |
---|---|---|---|---|
PA^5 | CODE-F-L-1 | 输入,开漏,上拉 | 前左 | 编码器 |
PB^1(TIM3_CH4) | CODE-F-L-2 | / | / | / |
PD^14 | CODE-F-R-1 | 同上 | 前右 | 编码器 |
PD^15(TIM4_CH4) | CODE-F-R-2 | / | / | / |
PC^8 | CODE-B-L-1 | 同上 | 后左 | 编码器 |
PC^10(UART4_Tx) | CODE-B-L-2 | / | / | / |
PC^12(UART5_Tx) | CODE-B-R-1 | 同上 | 后右 | 编码器 |
PC^11(UART4_Rx) | CODE-B-R-2 | / | / | / |
- 轮子用到的PWM(使用TIM1的通道)
管脚 | 对应 | 频率 | 哪个轮 | 类型 |
---|---|---|---|---|
PE^9 | TIM1_CH1 | 前左 | PWM | |
PE^11 | TIM1_CH2 | 前右 | PWM | |
PE^14 | TIM1_CH4 | 后左 | PWM | |
PE^13 | TIM1_CH3 | 后右 | PWM |
- 方向控制端口
管脚 | 哪个轮 | 模式 | 类型 |
---|---|---|---|
PB^15 | 前左 | 输出,推挽,无上下拉 | 方向控制 |
PB^8 | 前右 | 同上 | 方向控制 |
PG^13 | 后左 | 同上 | 方向控制 |
PG^12 | 后右 | 同上 | 方向控制 |
- B表示向后走,F表示向前
/*************************CAR.h*************************/
# define MOTO_0_B (GPIOB->BSRRH = GPIO_Pin_15) // 复位 置0 前左
# define MOTO_0_F (GPIOB->BSRRL = GPIO_Pin_15) //置1 默认向前转
# define MOTO_1_B (GPIOB->BSRRH = GPIO_Pin_8) //前右
# define MOTO_1_F (GPIOB->BSRRL = GPIO_Pin_8)
# define MOTO_2_B (GPIOG->BSRRH = GPIO_Pin_13) //后左
# define MOTO_2_F (GPIOG->BSRRL = GPIO_Pin_13)
# define MOTO_3_B (GPIOG->BSRRH = GPIO_Pin_12) //后右
# define MOTO_3_F (GPIOG->BSRRL = GPIO_Pin_12)
/*************************car_init.c*************************/
void encoder_gpio_init(void)
{
EXTIX_Init_A5(); //前左
EXTIX_Init_D14(); //前右
EXTIX_Init_C8(); //后左
EXTIX_Init_C12(); //后右
}
//PA5 中断初始化 用于四个电机的编码器 前左(其余3个配置一样只是中断线不一样)
void EXTIX_Init_A5(void)
{
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); //使用外部中断必须要使能的时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource5); //把A组引脚5设置为外部中断5
EXTI_InitStructure.EXTI_Line = EXTI_Line5; //5号线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能中断
EXTI_Init(&EXTI_InitStructure); //初始化
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; //中断通道选择
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能通道
NVIC_Init(&NVIC_InitStructure); //初始化
GPIO_init(GPIO_A, GPIO_Pin_5, GPIO_Mode_IN, GPIO_OType_OD, GPIO_PuPd_UP); //输入,开漏,上拉
}
//底盘电机 初始化
void moto_car_init(void)
{
TIM1_CH1_PWM_Init_PE9(1000, 8); //前左轮的 PWM初始化
TIM1_CH2_PWM_Init_PE11(1000, 8); //前右的 PWM初始化
TIM1_CH4_PWM_Init_PE14(1000, 8); //后左的 PWM初始化
TIM1_CH3_PWM_Init_PE13(1000, 8); //后右的 PWM初始化
GPIO_init(GPIO_B, GPIO_Pin_15, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_NOPULL); //前左轮的转动方向控制端口初始化
GPIO_init(GPIO_B, GPIO_Pin_8, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_NOPULL); //前右
GPIO_init(GPIO_G, GPIO_Pin_13, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_NOPULL); //后左
GPIO_init(GPIO_G, GPIO_Pin_12, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_NOPULL); //后右
MOTO_0_F;//前左轮 默认向前转
MOTO_1_F;//前右
MOTO_2_F;//后左
MOTO_3_F;//后右
TIM_SetCompare1(TIM1, 0); //0-100 前左轮 PWM输出设置为0 先不转
TIM_SetCompare2(TIM1, 0); //0-100 前右轮
TIM_SetCompare4(TIM1, 0); //0-100 后左轮
TIM_SetCompare3(TIM1, 0); //0-100 后右轮
}
//TIM1_CH3 PE13 PWM 前左轮
//参数:1000, 8
//剩下3个配置也一样管脚不一样而已
void TIM1_CH3_PWM_Init_PE13(u32 arr, u32 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义结构体
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //使能TIM1时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); //使能E组时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //PE^13
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource13, GPIO_AF_TIM1); //把PE^13复用为TIM1
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //分频因子
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseStructure.TIM_Prescaler = psc; //预分频系数
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //默认
TIM_TimeBaseStructure.TIM_Period = arr; //重装载值
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //初始化
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM1模式 CNT < CCR1为有效电平
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能输出比较模式
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;//使能互补输出
TIM_OCInitStructure.TIM_Pulse = (arr + 1) / 2;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出比较极性高
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High;// 互补输出极性高
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;//MOE=0 设置TIM1输出比较空闲
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;//MOE=0 重置TIM1输出比较空闲状态
TIM_OC3Init(TIM1, &TIM_OCInitStructure);
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE); //高级定时器必须要加
}
/*************************STM32F40x_Timer_eval.c*************************/
//TIM5中断服务函数
void TIM5_IRQHandler(void)
{
if(TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) //当触发中断
{
TIM_ClearITPendingBit(TIM5, TIM_IT_Update); //清除标志位
//PG9_L;
timer_interrupt_fun();
//PG9_H;
}
}
/*************************Car.c*************************/
//定时器5中断要执行的函数
void timer_interrupt_fun(void)//定时器中需要运行的函数
{
timer5_speed_calculation_fun();//计算4个轮子的速度 和 求平均后 计算小车走的距离
if(Tracing_dis_bit)//是否需要寻迹走
{
timer_Tracing_fun();//寻迹走函数
chack_MAG_fun(); //检查是否有十字磁条或者左转磁条
}
if(speed_PID_bit)//是否需要4个轮子的速度PID
{
timer5_speed_0_pid_fun(speed_set_mun_L);
timer5_speed_1_pid_fun(speed_set_mun_R);
timer5_speed_2_pid_fun(speed_set_mun_L);
timer5_speed_3_pid_fun(speed_set_mun_R);
}
}
//在定时器5中断中执行的函数 记录转速 记录路程
void timer5_speed_calculation_fun(void)
{
u8 i;
speed_avg = 0;
for(i = 0; i < 4; i++)
{
speed_actual[i] = speed_count[i];//记录速度 speed_count是在编码器中断中累加的
speed_count[i] = 0;//一个计数周期清零
speed_avg = speed_avg + speed_actual[i];//速度求和
}
speed_avg = speed_avg / 4;//速度求平均
distance_now_u32 = distance_now_u32 + speed_avg;//累计走形距离 distance_now_u32是小车走的距离计数
}
磁条传感器/走+夹过程
管脚 | 对应 | 模式 |
---|---|---|
PF^2 | MAG_1 | 输入,推挽,无上下拉 |
PD^9 | MAG_2 | 同上 |
PF^4 | MAG_3 | 同上 |
PF^3 | MAG_4 | 同上 |
PB^13 | MAG_5 | 同上 |
PF^1 | MAG_6 | 同上 |
PE^1 | MAG_7 | 同上 |
PD^12 | MAG_8 | 同上 |
/*************************Car_init.c*************************/
//磁条传感器端口初始化
void gpio_MAG_init_all(void)
{
GPIO_init(GPIO_F, GPIO_Pin_2, GPIO_Mode_IN, GPIO_OType_PP, GPIO_PuPd_NOPULL); //F2
GPIO_init(GPIO_D, GPIO_Pin_9, GPIO_Mode_IN, GPIO_OType_PP, GPIO_PuPd_NOPULL); //D9
GPIO_init(GPIO_F, GPIO_Pin_4, GPIO_Mode_IN, GPIO_OType_PP, GPIO_PuPd_NOPULL); //F4
GPIO_init(GPIO_F, GPIO_Pin_3, GPIO_Mode_IN, GPIO_OType_PP, GPIO_PuPd_NOPULL); //F3
GPIO_init(GPIO_B, GPIO_Pin_13, GPIO_Mode_IN, GPIO_OType_PP, GPIO_PuPd_NOPULL); //B13
GPIO_init(GPIO_F, GPIO_Pin_1, GPIO_Mode_IN, GPIO_OType_PP, GPIO_PuPd_NOPULL); //F1
GPIO_init(GPIO_E, GPIO_Pin_1, GPIO_Mode_IN, GPIO_OType_PP, GPIO_PuPd_NOPULL); //E1
GPIO_init(GPIO_D, GPIO_Pin_12, GPIO_Mode_IN, GPIO_OType_PP, GPIO_PuPd_NOPULL); //D12
}
- 4个托盘的距离+4个集中站距离
Tracing_to_cross_and_dis_fun
函数:走过十字磁条后再走一段距离,参数tuopan_dis[x]
是第几个托盘的距离(x分别是0,1,2,3);前提必须是在起点,不能在任意一个托盘前再执行走到另一个托盘car_move_back_fun
函数:小车后退多少个距离Tracing_to_L_fun
函数:走到第一个左转(前3个转弯的参数都是1,第4个转弯参数是3,这样会回到原点)car_turn_L_fun
函数:左转90度find_MAG_fun
函数:转弯后车身修正(防止偏离路线)arm_0_L
函数:整个机械臂左转多少脉冲(参数是数字,一个个试);arm_0_R
函数:整个机械臂右转多少脉冲(参数是数字,一个个试)arm_1_F
函数:大臂向前多少脉冲;arm_1_B
函数:大臂向后多少脉冲arm_2_DN
函数:小臂向前多少脉冲;arm_2_UP
函数:小臂向后多少脉冲- 关于夹子夹托盘的东西需要考虑
走到托盘的距离
+底盘转的角度
+大小臂的角度
+激光扫描后的向右偏移多少的问题
(有可能第一个托盘可以但是到第4个托盘参数就会不适用就得又改,改到通用4个托盘为止,也可以用判断语句每个托盘的参数都不一样用if进入)
测试代码:
//测试走到设定托盘夹完东西走一圈到原点(不丢到垃圾桶)
Tracing_to_cross_and_dis_fun(tuopan_dis[3]);
arm_pick_up_block_fun();
car_move_back_fun(500);//小车后退300个距离
Tracing_to_L_fun(1);//走到第一个左转
car_turn_L_fun();//自己左转90度
find_MAG_fun();//小车车身修正磁条
Tracing_to_L_fun(1);//一直走到左转磁条
car_turn_L_fun();//左转90度
find_MAG_fun();//摆正车身
Tracing_to_L_fun(1);//走到左转位置
car_turn_L_fun();//左转90度
find_MAG_fun();//修正车身
Tracing_to_L_fun(3);//走到第三个左转磁条
car_turn_L_fun();//左转90度
find_MAG_fun();//修正车身
/*************************main.h*************************/
//小车走 托盘的4个距离
# define TUOPAN_DIS_0 20 //210
# define TUOPAN_DIS_1 1000 //1100
# define TUOPAN_DIS_2 1970 //1900
# define TUOPAN_DIS_3 2870 //2700
//小车走 集中站的4个距离
# define JIZHONGZHAN_DIS_0 0
# define JIZHONGZHAN_DIS_1 90
# define JIZHONGZHAN_DIS_2 1920
# define JIZHONGZHAN_DIS_3 3645
/*************************main.c*************************/
u32 tuopan_dis[4]; //托盘4个距离
u32 jizhongzhan_dis[4]; //集中站4个距离
void ram_init(void)//小车在转盘路 走的4个距离 集中站路 走的4个距离 赋值
{
tuopan_dis[0] = TUOPAN_DIS_0;//
tuopan_dis[1] = TUOPAN_DIS_1;//
tuopan_dis[2] = TUOPAN_DIS_2;//
tuopan_dis[3] = TUOPAN_DIS_3;//
jizhongzhan_dis[0] = JIZHONGZHAN_DIS_0;//
jizhongzhan_dis[1] = JIZHONGZHAN_DIS_1;//
jizhongzhan_dis[2] = JIZHONGZHAN_DIS_2;//
jizhongzhan_dis[3] = JIZHONGZHAN_DIS_3;//
}
//执行从托盘拿物块 送到集中站的全部过程
void Grab_garbage_fun(u8 tuopan,u8 jizhongzhan)//小车从起始点开始 抓取物块 走到集中站 tuopan是托盘编号 jizhongzhan 是集中站编号 走到起点
{
//转盘路
Tracing_to_cross_and_dis_fun(tuopan_dis[tuopan]);//走过十字磁条后再走一段距离 tuopan_dis[1]是第一个距离
delay_ms(1000);
arm_pick_up_block_fun();//机械臂抓物块 全部动作
car_move_back_fun(500);//小车后退300个距离
Tracing_to_L_fun(1);//走到第一个左转
car_turn_L_fun();//自己左转90度
find_MAG_fun();//小车车身修正磁条
//垃圾桶路
Tracing_to_L_fun(1);//一直走到左转磁条
car_turn_L_fun();//左转90度
find_MAG_fun();//摆正车身
//集中站路
if(jizhongzhan==0)//如果是集中站第一个门
{
car_move_back_fun(100);//小车后退100个距离
send_cmd_to_Concentration_station_fun(DOOR_OPEN,jizhongzhan);//通过串口2给集中站发送开门命令 DOOR_OPEN是开门的命令 jizhongzhan是集中站第几个门
jizhongzhan_fun();//机械臂扔集中站全部动作
send_cmd_to_Concentration_station_fun(DOOR_CLOSE,jizhongzhan);//通过串口2发送关门命令
car_move_forward_fun(100);//小车向前移动100个距离
Tracing_to_L_fun(1);//小车走到左转位置
car_turn_L_fun();//左转90度
find_MAG_fun();//依据修正车身
}
if((jizhongzhan==1)||(jizhongzhan==2))//如果是集中站的2门和3门
{
Tracing_to_cross_and_dis_fun(jizhongzhan_dis[jizhongzhan]);//小车走过十字磁条后再走一段距离
send_cmd_to_Concentration_station_fun(DOOR_OPEN,jizhongzhan);//发送开门命令
jizhongzhan_fun();//机械臂扔集中站全部动作
send_cmd_to_Concentration_station_fun(DOOR_CLOSE,jizhongzhan);//小车发送关门命令
Tracing_to_L_fun(1);//走到左转位置
car_turn_L_fun();//左转90度
find_MAG_fun();//修正车身
}
if(jizhongzhan==3)//如果是第四个门
{
//Tracing_to_cross_and_dis_fun(jizhongzhan_dis[jizhongzhan]);//走过十字磁条后 再走一段距离
Tracing_to_L_fun(1);//走到左转位置
send_cmd_to_Concentration_station_fun(DOOR_OPEN,jizhongzhan);//发送开门命令
jizhongzhan_fun();//扔集中站
send_cmd_to_Concentration_station_fun(DOOR_CLOSE,jizhongzhan);//发送关门命令
car_turn_L_fun();//左转90度
find_MAG_fun();//修正车身
}
//车库路
Tracing_to_L_fun(3);//走到第三个左转磁条
car_turn_L_fun();//左转90度
find_MAG_fun();//修正车身
}
/*************************car.c*************************/
void Tracing_to_cross_and_dis_fun(u32 dis)//寻迹走到十字后再走一定距离
{
u8 i;
MOTO_0_F; //轮子方向控制(默认向前)
MOTO_1_F;
MOTO_2_F;
MOTO_3_F;
speed_PID_data_init(); //PID清0
distance_now_u32 = 0;
speed_set_mun_L = 0;
speed_set_mun_R = 0;
Tracing_dis_bit = 1;
ok_bit = 1;
shizi_bit = 0;
//缓启动
for(i = 0; i < 20; i++)
{
speed_set_mun_L++;
speed_set_mun_R++;
delay_ms(25);
//if(chack_MAG_fun()==1)
if(shizi_bit == 1)
{
ok_bit = 0;
distance_now_u32 = 0;
}
}
if(ok_bit == 1)
{
while(ok_bit)
{
//if(chack_MAG_fun()==1)
if(shizi_bit == 1)
{
ok_bit = 0;
distance_now_u32 = 0;
}
}
}
ok_bit = 1;
while(ok_bit)
{
if(distance_now_u32 >= dis)
{
ok_bit = 0;
}
}
speed_set_mun_L = 20;
speed_set_mun_R = 20;
for(i = 0; i < 20; i++)
{
speed_set_mun_L--;
speed_set_mun_R--;
delay_ms(50);
}
car_pwm_stop();
}
void speed_PID_data_init(void)//4个车轮PID参数 清零 在运行PID程序之前 调用
{
u8 i;
for(i = 0; i < 4; i++)
{
e[i] = 0;
num[i] = 0;
duk[i] = 0;
e1[i] = 0;
uk[i] = 0;
uk1[i] = 0;
out[i] = 0;
e2[i] = 0;
PWMTime[i] = 0;
}
}
void timer_Tracing_fun(void)////寻迹走 定时器5中断函数中 运行
{
u16 speed_Tracing_L;
u16 speed_Tracing_R;
MAG_dat = mag_test();//读磁条
//printf("%d\r\n",MAG_dat);
if(MAG_dat == 0)
{
speed_Tracing_L = speed_set_mun_L;
speed_Tracing_R = speed_set_mun_R;
}
else if((MAG_dat > 0) && (MAG_dat <= 4)) //1到4之间 磁条在右边 小车偏左 左边加速
{
speed_Tracing_L = gain_fun(speed_set_mun_L, 0.2);
speed_Tracing_R = gain_fun(speed_set_mun_R, -0.2);
}
else if((MAG_dat >= 5) && (MAG_dat <= 7)) //5到7之间 磁条在右边 小车偏左 左边加速
{
speed_Tracing_L = gain_fun(speed_set_mun_L, 0.4);
speed_Tracing_R = gain_fun(speed_set_mun_R, -0.4);
}
else if((MAG_dat < 0) && (MAG_dat >= -4)) //-1到-4 磁条在左边 小车偏右 右边加速
{
speed_Tracing_L = gain_fun(speed_set_mun_L, -0.2);
speed_Tracing_R = gain_fun(speed_set_mun_R, 0.2);
}
else if((MAG_dat <= -5) && (MAG_dat >= -7)) //-5到-7 磁条在左边 小车偏右 右边加速
{
speed_Tracing_L = gain_fun(speed_set_mun_L, -0.4);
speed_Tracing_R = gain_fun(speed_set_mun_R, 0.4);
}
else if(MAG_dat == 200) //跑出磁条了
{
car_pwm_stop();//小车停止
bell_fun(3);//蜂鸣器响3下
}
else
{
speed_Tracing_L = speed_set_mun_L;//设置左边轮子速度
speed_Tracing_R = speed_set_mun_R;//设置右边轮子速度
}
timer5_speed_0_pid_fun(speed_Tracing_L);//设置4个轮字的PID转速
timer5_speed_1_pid_fun(speed_Tracing_R);//
timer5_speed_2_pid_fun(speed_Tracing_L);//
timer5_speed_3_pid_fun(speed_Tracing_R);//
}
机械臂/舵机
管脚 | 对应 | 模式 |
---|---|---|
PF^14 | JXB01 | 输出,推挽,无上下拉 |
PE^8 | JXB02 | 同上 |
PE^10 | JXB11 | 同上 |
PF^13 | JXB12 | 同上 |
PF^12 | JXB21 | 同上 |
PF^11 | JXB22 | 同上 |
PF^15 | TJ1(机械臂复位) | 输入,推挽,无上下拉 |
PG^0 | TJ2(机械臂复位) | 同上 |
PG^1 | TJ3(机械臂复位) | 同上 |
管脚 | 对应 | 频率 |
---|---|---|
PB^0(TIM3_CH3) | DJ | 50Hz(20ms) |
/*************************main.h*************************/
# define STEERING_ENGINE_OPEN 335 //爪子张开时舵机的参数 舵机张开不能太大 会卡住 卡住会发热
/*************************Mechanical_arm.h*************************/
# define JXB01_L (GPIOF->BSRRH = GPIO_Pin_14)//小臂脉冲
# define JXB01_H (GPIOF->BSRRL = GPIO_Pin_14)//
# define JXB01_PP GPIOF->ODR^=GPIO_Pin_14
# define JXB02_UP (GPIOE->BSRRH = GPIO_Pin_8)//小臂上
# define JXB02_DN (GPIOE->BSRRL = GPIO_Pin_8)//小臂下
# define JXB11_L (GPIOE->BSRRH = GPIO_Pin_10)//底盘脉冲
# define JXB11_H (GPIOE->BSRRL = GPIO_Pin_10)//
# define JXB12_R (GPIOF->BSRRH = GPIO_Pin_13)//底盘右
# define JXB12_L (GPIOF->BSRRL = GPIO_Pin_13)//底盘左
# define JXB21_L (GPIOF->BSRRH = GPIO_Pin_12)//大臂脉冲
# define JXB21_H (GPIOF->BSRRL = GPIO_Pin_12)//
# define JXB22_F (GPIOF->BSRRH = GPIO_Pin_11)//大臂前
# define JXB22_B (GPIOF->BSRRL = GPIO_Pin_11)//大臂后
//机械臂复位
# define JSB_W1_Input GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_15)//F15
# define JSB_W2_Input GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_0)//G0
# define JSB_W3_Input GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_1)//G1
/*************************Mechanical_arm.c*************************/
//机械臂全部端口初始化
void gpio_arm_init(void)
{
//机械臂控制输出
GPIO_init(GPIO_F,GPIO_Pin_11,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//F11
GPIO_init(GPIO_F,GPIO_Pin_12,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//F12
GPIO_init(GPIO_F,GPIO_Pin_13,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//F13
GPIO_init(GPIO_E,GPIO_Pin_10,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//E10
GPIO_init(GPIO_E,GPIO_Pin_8,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//E8
GPIO_init(GPIO_F,GPIO_Pin_14,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//F14
//机械臂归位信号输入
GPIO_init(GPIO_F,GPIO_Pin_15,GPIO_Mode_IN,GPIO_OType_PP,GPIO_PuPd_NOPULL);//F15
GPIO_init(GPIO_G,GPIO_Pin_14,GPIO_Mode_IN,GPIO_OType_PP,GPIO_PuPd_NOPULL);//G0
GPIO_init(GPIO_G,GPIO_Pin_14,GPIO_Mode_IN,GPIO_OType_PP,GPIO_PuPd_NOPULL);//G1
TIM3_CH3_PWM_Init_PB0(5000-1,336-1);//夹子舵机初始化调用 50HZ 20MS
steering_engine(STEERING_ENGINE_OPEN); //夹子舵机 设置位置
}
//TIM3_CH3 PB0 PWM 舵机 0.5ms 2.5ms
//参数:5000-1,336-1
void TIM3_CH3_PWM_Init_PB0(u32 arr,u32 psc)// 小车机械臂 夹子的 舵机初始化 舵机是PWM控制的
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义结构体
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能TIM3时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); //使能B组时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PB^0
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没上下拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度
GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化
GPIO_PinAFConfig(GPIOB,GPIO_PinSource0,GPIO_AF_TIM3); //把管脚PB^0复用为TIM3
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //分频因子
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseStructure.TIM_Prescaler = psc; //预分频值
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //默认
TIM_TimeBaseStructure.TIM_Period = arr; //重装载值
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure); //初始化
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM1模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //开启OC输出到对应引脚
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; //互补输出使能
TIM_OCInitStructure.TIM_Pulse = (arr+1)/2; //占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //极性高
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High; //互补通道极性高
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; //空闲状态输出高电平
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset; //互补通道的空闲时输出低电平
TIM_OC3Init(TIM3,&TIM_OCInitStructure); //初始化
TIM_Cmd(TIM3,ENABLE); //使能TIM3
TIM_CtrlPWMOutputs(TIM3,ENABLE); //可有可无
//TIM_SetCompare3(TIM3,350);//620=2.5ms 125=0.5ms 370=1.5ms 620是夹住 125是放开
}
//夹子舵机 设置位置
void steering_engine(u16 dat)
{
TIM_SetCompare3(TIM3,dat);//620=2.5ms 125=0.5ms 370=1.5ms 620是夹住 125是放开
}
//机械臂 底盘 大臂 小臂 依次归位
void arm_position_init_all(void)
{
arm0_position_init();
delay_ms(500);
arm1_position_init();
delay_ms(500);
arm2_position_init();
delay_ms(500);
//可以在这里添加下面的 机械臂控制函数 达到调整的目的
arm_2_UP(90);//小臂向上
arm_1_B(80);//大臂向后
/*
//下面是控制 大臂 和 小臂的 函数 参数是脉冲数 根据需要修改大小 20只是示意 需要哪个就复制哪个到上面
arm_1_F(20);//大臂向前
arm_1_B(20);//大臂向后
arm_2_UP(20);//小臂向上
arm_2_DN(20);//小臂向下
*/
}
//机械臂大臂 归位
void arm1_position_init(void)
{
u8 ok_bit=1;
u8 read_bit;
u16 count;
count = 0;
read_bit = JSB_W2_Input;
if(read_bit==0)//在原位
{
//需要伸出 直到出去
while(ok_bit)
{
JXB22_F;//向前
JXB21_H;//给脉冲
delay_ms(TIME_arm);
JXB21_L;//给脉冲
delay_ms(TIME_arm);
read_bit = JSB_W2_Input;
if(read_bit==1)
{
ok_bit = 0;
}
count++;
if(count>=200)
{
ok_bit = 0;
bell_fun(3);
}
}
arm_1_F(20);
ok_bit = 1;
while(ok_bit)
{
JXB22_B;//向后
JXB21_H;//给脉冲
delay_ms(TIME_arm);
JXB21_L;//给脉冲
delay_ms(TIME_arm);
read_bit = JSB_W2_Input;
if(read_bit==0)
{
ok_bit = 0;
}
}
}
else
{
arm_1_F(120);
read_bit = JSB_W2_Input;
if(read_bit==0)//在原位
{
//需要伸出 直到出去
while(ok_bit)
{
JXB22_F;//向前
JXB21_H;//给脉冲
delay_ms(TIME_arm);
JXB21_L;//给脉冲
delay_ms(TIME_arm);
read_bit = JSB_W2_Input;
if(read_bit==1)
{
ok_bit = 0;
}
count++;
if(count>=200)
{
ok_bit = 0;
bell_fun(3);
}
}
arm_1_F(20);
ok_bit = 1;
while(ok_bit)
{
JXB22_B;//向后
JXB21_H;//给脉冲
delay_ms(TIME_arm);
JXB21_L;//给脉冲
delay_ms(TIME_arm);
read_bit = JSB_W2_Input;
if(read_bit==0)
{
ok_bit = 0;
}
}
}
else
{
while(ok_bit)
{
JXB22_B;//向后
JXB21_H;//给脉冲
delay_ms(TIME_arm);
JXB21_L;//给脉冲
delay_ms(TIME_arm);
read_bit = JSB_W2_Input;
if(read_bit==0)
{
ok_bit = 0;
}
}
}
}
arm1_position_now = 0;
}
//机械臂小臂 归位
void arm2_position_init(void)
{
u8 ok_bit=1;
u8 read_bit;
u16 count;
read_bit = JSB_W3_Input;
if(read_bit==0)//在原位
{
//需要伸出 直到出去
while(ok_bit)
{
JXB02_UP;//向上
JXB01_H;//给脉冲
delay_ms(TIME_arm);
JXB01_L;//给脉冲
delay_ms(TIME_arm);
read_bit = JSB_W3_Input;
if(read_bit==1)
{
ok_bit = 0;
}
count++;
if(count>=300)
{
ok_bit = 0;
bell_fun(3);
}
}
arm_2_UP(20);
ok_bit = 1;
while(ok_bit)
{
JXB02_DN;//向下
JXB01_H;//给脉冲
delay_ms(TIME_arm);
JXB01_L;//给脉冲
delay_ms(TIME_arm);
read_bit = JSB_W3_Input;
if(read_bit==0)
{
ok_bit = 0;
}
}
}
else
{
while(ok_bit)
{
JXB02_DN;//向下
JXB01_H;//给脉冲
delay_ms(TIME_arm);
JXB01_L;//给脉冲
delay_ms(TIME_arm);
read_bit = JSB_W3_Input;
if(read_bit==0)
{
ok_bit = 0;
}
}
}
arm2_position_now = 0;
}
//机械臂底座左右 归位
void arm0_position_init(void)
{
u8 ok_bit=1;
u8 read_bit;
u8 read_bit_new;
u8 read_bit_old;
u16 recode_1 = 0;
u16 recode_2 = 0;
u16 middle;
u16 i;
read_bit = JSB_W1_Input;//转盘有洞的位置是1 没有洞的位置时0
if(read_bit==1)//在中点
{
//需要伸出 直到出去
while(ok_bit)
{
JXB12_L;//向左
JXB11_H;//给脉冲
delay_ms(TIME_arm);
JXB11_L;//给脉冲
delay_ms(TIME_arm);
read_bit = JSB_W1_Input;
if(read_bit==0)//到了原点边缘
{
ok_bit = 0;
}
}
delay_ms(100);
arm_0_R(26);//26个脉冲可以回到洞的 正中心 如果不是 就可以调26这个数据
}
else
{
arm_0_R(100);
for(i=0;i<200;i++)
{
read_bit_new = JSB_W1_Input;
if((read_bit_new==1)&&(read_bit_old==0)) // 第一次在有洞的地方
{
recode_1 = i;
}
if((read_bit_new==0)&&(read_bit_old==1)) // 第一次在没有洞的地方
{
recode_2 = i;
}
read_bit_old = read_bit_new;
JXB12_L;//向左
JXB11_H;//给脉冲
delay_ms(TIME_arm);
JXB11_L;//给脉冲
delay_ms(TIME_arm);
}
delay_ms(100);
if((recode_1!=0)&&(recode_2!=0))
{
middle = (200 - recode_2) + (recode_2 - recode_1)/2;
arm_0_R(middle);
}
else
{
bell_fun(3);
}
//再按照第一个步骤 重新校准一下
while(ok_bit)
{
JXB12_L;//向左
JXB11_H;//给脉冲
delay_ms(TIME_arm);
JXB11_L;//给脉冲
delay_ms(TIME_arm);
read_bit = JSB_W1_Input;
if(read_bit==0)//到了原点边缘
{
ok_bit = 0;
}
}
delay_ms(100);
arm_0_R(26);//26个脉冲可以回到洞的 正中心 如果不是 就可以调26这个数据
}
arm0_position_now = 0;
}
//机械臂大臂 向后
void arm_1_B(u32 mun)
{
u32 i;
JXB22_B;
for(i=0;i<mun;i++)
{
JXB21_H;//给脉冲
delay_ms(TIME_arm);
JXB21_L;
delay_ms(TIME_arm);
}
}
//小臂 向上
void arm_2_UP(u32 mun)
{
u32 i;
JXB02_UP;
for(i=0;i<mun;i++)
{
JXB01_H;
delay_ms(TIME_arm);
JXB01_L;
delay_ms(TIME_arm);
}
}
蜂鸣器
管脚 | 对应 | 模式 |
---|---|---|
PB^9 | FMQ | 输出,推挽,无上下拉 |
/*************************CAR.h*************************/
# define BELL_OFF (GPIOB->BSRRH = GPIO_Pin_9) //低电平不响
# define BELL_ON (GPIOB->BSRRL = GPIO_Pin_9) //高电平响(NPN管)
/*************************Car_init.c*************************/
//蜂鸣器端口 初始化
void gpio_bell_init(void)
{
GPIO_init(GPIO_B, GPIO_Pin_9, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_NOPULL); //B9
BELL_ON; //蜂鸣器响
delay_ms(200); //
BELL_OFF; //蜂鸣器灭
delay_ms(500); //
}
激光测距
管脚 | 对应 | 模式 |
---|---|---|
PB^6 | SDA | 输出,开漏,无上下拉 |
PD^2 | SCL | 输出,推挽,无上下拉 |
/*************************Car_init.c*************************/
//激光测距端口初始化
void laser_gpio_init(void)
{
GPIO_init(GPIO_B, GPIO_Pin_6, GPIO_Mode_OUT, GPIO_OType_OD, GPIO_PuPd_NOPULL); //B6 SDA
GPIO_init(GPIO_D, GPIO_Pin_2, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_NOPULL); //D2 SCL
}
串口2无线通信
/*************************STM32F40x_Usart_eval.c*************************/
void uart2_race_chack(void)//检测串口2 是否收到一帧数据
{
if(usart2_race_over_bit)
{
LCD_ShowString_fun("433race");//屏幕显示
if(usart2_race_buf[0] == 1) //判断是否是小车ID
LCD_ShowString_fun("ID=1");
if(usart2_race_buf[0] == 2)
LCD_ShowString_fun("ID=2");
if(usart2_race_buf[0] == 3)
LCD_ShowString_fun("ID=3");
copy_fun(usart2_race_buf, cmd_race_buf, usart2_race_count); //拷贝usart2_race_buf 串口2接收数组 到 cmd_race_buf usart2_race_count是拷贝字节数
usart2_race_over_bit = 0;//收到一帧数据标志清零
usart2_race_count = 0;//接收字节数清零
delay_ms(1000);//转盘发命令会响一声 这里也响一声 延时半秒 能听到发送的嘀和 小车接收的嘀 便于判断 不延时 两个声音会混在一起
bell_fun(1);//蜂鸣器响一声
cmd_explan_fun();//命令解释函数 判断是不是自己的ID 提取里面的参数
}
}
void copy_fun(u8 *p_in, u8 *p_out, u8 mun) //拷贝一个数组 到 另一个数组
{
u8 i;
for(i = 0; i < mun; i++)
{
*(p_out + i) = *(p_in + i);
}
}
/*************************main.c*************************/
/*
第一字节是 ID 小车的ID是2
第二字节是命令类型 小车拿送物块 1:轮盘-->集中站
第三字节是托盘号 0-3
第四字节是集中站号 0-3
*/
void cmd_explan_fun(void)//无线模块串口2发过来的数据 解析
{
if(cmd_race_buf[0]==ID)//如果是自己的ID
{
switch(cmd_race_buf[1])//判断第二个字节
{
case 1://转盘到集中站命令
tuopan_number = cmd_race_buf[2]; //托盘编号 第三字节
lajitong_number = cmd_race_buf[3]; //集中站编号 第四字节
//判断托盘号然后显示在屏上
if(tuopan_number==0)
LCD_ShowString_fun("tuopan=0");
if(tuopan_number==1)
LCD_ShowString_fun("tuopan=1");
if(tuopan_number==2)
LCD_ShowString_fun("tuopan=2");
if(tuopan_number==3)
LCD_ShowString_fun("tuopan=3");
//判断集中站号然后显示在屏上
if(lajitong_number==0)
LCD_ShowString_fun("jizhongzhan=0");
if(lajitong_number==1)
LCD_ShowString_fun("jizhongzhan=1");
if(lajitong_number==2)
LCD_ShowString_fun("jizhongzhan=2");
if(lajitong_number==3)
LCD_ShowString_fun("jizhongzhan=3");
//小车开始执行命令
Grab_garbage_fun(tuopan_number,lajitong_number); //执行从托盘拿物块 送到集中站的全部过程
send_to_zhuanpan_cmd_fun();//给转盘发送一个数据 让转盘知道 小车的任务已经完成 转盘就可以再发送下一个任务
delay_ms(2000);//延时
send_to_zhuanpan_cmd_fun();
//delay_ms(1000);//延时
//send_to_zhuanpan_cmd_fun();
break;
}
}
}
代码修改/添加记录
11/15
- TIM5定时20ms改成别的计算方式
/*************************STM32F40x_Timer_eval.c*************************/
//定时器5初始化 定时20ms
//参数:10000, 168 计算公式:10000*168/84000000 = 0.02s = 20ms
void Timer5_Config(uint16_t period, uint16_t prescaler)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义结构体
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //使能TIM5时钟
TIM_TimeBaseStructure.TIM_Period = period - 1; //重装载值
TIM_TimeBaseStructure.TIM_Prescaler = prescaler; //预分频系数
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //分频因子
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //初始化
TIM_ITConfig(TIM5, TIM_IT_Update, ENABLE); //TIM5中断使能
TIM_Cmd(TIM5, ENABLE); //使能TIM5
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //中断通道为TIM5
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能通道
NVIC_Init(&NVIC_InitStructure); //中断初始化
}
11/16
- 修改了宏定义
TUOPAN_DIS_3
的值,原本是2870
# define TUOPAN_DIS_3 2670 //2700第4个托盘的距离(已弃用)
# define TUOPAN_DIS_3 2900 //2022.11.17
- 修改了
find_MAG_fun
函数
//找磁条
void find_MAG_fun(void)// 小车左转后 可能磁条不在中间 这个函数是让小车磁条 修正到中间
{
u8 arr[10];
speed_set_mun_L = 0;//设置左右速度为0
speed_set_mun_R = 0;
ok_bit = 1;
while(ok_bit)
{
MAG_dat = mag_test();//读磁条
sprintf((char *)arr, "num:%d", MAG_dat);
LCD_ShowString_fun((char *)arr);
speed_PID_bit = 1;//开始执行PID函数
if(speed_set_mun_L < 10) //缓启动 限制速度在10以内
{
speed_set_mun_L++;
speed_set_mun_R++;
}
if((MAG_dat >= 0 && MAG_dat <= 5) || (MAG_dat <= -4 && MAG_dat >= -1) ) //这个范围就不用管
{
ok_bit = 0;// 表示清零
delay_ms(100);
car_pwm_stop();//小车停止
}
else if(MAG_dat >= -5) //如果小车偏左或者右 就是设置车轮的方向 达到车身修正的目的
{
MOTO_0_B;
MOTO_1_F;
MOTO_2_B;
MOTO_3_F;
}
else if(MAG_dat > 5) //如果小车偏左或者右 就是设置车轮的方向 达到车身修正的目的
{
MOTO_0_F;
MOTO_1_B;
MOTO_2_F;
MOTO_3_B;
}
delay_ms(50);
}
}
11/17
- 修改了
arm_pick_up_block_fun
函数
void arm_pick_up_block_fun(void)//机械臂 在 托盘处 向右抓取全部动作
{
arm_0_R(490);//右转490脉冲(原本500)
delay_ms(500);
arm_1_F(100);//大臂向前
delay_ms(500);
arm_2_DN(70);//小臂向下
delay_ms(500);
arm_1_F(115);//大臂向前
delay_ms(500);
arm_2_DN(30);//小臂向下(原本55) 这个太大会导致扫描前碰到物品所以调小点
delay_ms(500);
acoustic_wave_find_box_fun();//用激光找找盒子动作
delay_ms(500);
//添加1
arm_0_R(4); //底盘右转
delay_ms(500);
arm_2_DN(60);//小臂向下
delay_ms(500);
//添加2
arm_1_F(5); //大臂向前
arm_2_UP(10); //小臂抬起
arm3_clip_fun();//夹子夹物块
delay_ms(500);
arm_2_UP(150);//小臂抬起
delay_ms(500);
arm_0_L(500);//底盘向左500
delay_ms(500);
arm_position_init_all();//三臂归位
}
- 在
uart2_race_chack
函数里添加
void uart2_race_chack(void)//检测串口2 是否收到一帧数据
{
if(usart2_race_over_bit)
{
LCD_ShowString_fun("433race");//屏幕显示
if(usart2_race_buf[0]==1)
LCD_ShowString_fun("ID=1");
if(usart2_race_buf[0]==1)
LCD_ShowString_fun("ID=2");
if(usart2_race_buf[0]==1)
LCD_ShowString_fun("ID=3");
copy_fun(usart2_race_buf, cmd_race_buf, usart2_race_count); //拷贝usart2_race_buf 串口2接收数组 到 cmd_race_buf usart2_race_count是拷贝字节数
usart2_race_over_bit = 0;//收到一帧数据标志清零
usart2_race_count = 0;//接收字节数清零
uart2_buf_init(); //添加这个清空数组
delay_ms(1000);//转盘发命令会响一声 这里也响一声 延时半秒 能听到发送的嘀和 小车接收的嘀 便于判断 不延时 两个声音会混在一起
bell_fun(1);//蜂鸣器响一声
cmd_explan_fun();//命令解释函数 判断是不是自己的ID 提取里面的参数
}
}
- 把
cmd_explan_fun
函数里的两行屏蔽掉,只留下一行(这个bug搞了我几个小时一直只能接收一次任务…),这样就可以接收完一次任务回到原点又接收下一次任务了
send_to_zhuanpan_cmd_fun();
//delay_ms(2000);//延时
//send_to_zhuanpan_cmd_fun();
串口测试函数
- 此函数是拿来测试底盘和机械臂的方便调参 ``
void uart1_race_chack(void)//检查串口1 是否接收完毕
{
/*底盘调试*/
if(usart1_race_over_bit)
{
usart1_race_over_bit = 0;
if(usart1_race_buf[0] == '0')
{
bell_fun(1);
arm_0_L(maichong);//左转
printf("左转底盘:%d@@\r\n", maichong);
}
if(usart1_race_buf[0] == '1') //执行步骤1底盘
{
bell_fun(1);
arm_0_R(maichong);//右转
printf("右转底盘:%d@@\r\n", maichong);
}
if(usart1_race_buf[0] == '+') //脉冲+
{
bell_fun(1);
maichong += 1;
printf("底盘脉冲加:%d@@\r\n", maichong);
}
if(usart1_race_buf[0] == '-') //脉冲-
{
bell_fun(1);
maichong -= 1;
printf("底盘脉冲减:%d@@\r\n", maichong);
}
/*第一次大臂调试*/
if((usart1_race_buf[0] == 'x') && (usart1_race_buf[1] == '1')) //大臂前 //执行步骤2
{
bell_fun(1);
arm_1_F(x1);
printf("第一次大臂前:%d@@\r\n", x1);
}
if((usart1_race_buf[0] == 'x') && (usart1_race_buf[1] == '0')) //大臂后
{
bell_fun(1);
arm_1_B(x1);
printf("第一次大臂后:%d@@\r\n", x1);
}
if((usart1_race_buf[0] == 'x') && (usart1_race_buf[1] == '+')) //脉冲+
{
bell_fun(1);
x1 += 1;
printf("第一次大臂加:%d@@\r\n", x1);
}
if((usart1_race_buf[0] == 'x') && (usart1_race_buf[1] == '-')) //脉冲-
{
bell_fun(1);
x1 -= 1;
printf("第一次大臂减:%d@@\r\n", x1);
}
/*第一次小臂调试*/
if((usart1_race_buf[0] == 'd') && (usart1_race_buf[1] == '1')) //小臂前 //执行步骤3
{
bell_fun(1);
arm_2_DN(d1);
printf("第一次小臂前:%d@@\r\n", d1);
}
if((usart1_race_buf[0] == 'd') && (usart1_race_buf[1] == '0')) //小臂后
{
bell_fun(1);
arm_2_UP(d1);
printf("第一次小臂后:%d@@\r\n", d1);
}
if((usart1_race_buf[0] == 'd') && (usart1_race_buf[1] == '+')) //脉冲+
{
bell_fun(1);
d1 += 1;
printf("第一次小臂加:%d@@\r\n", d1);
}
if((usart1_race_buf[0] == 'd') && (usart1_race_buf[1] == '-')) //脉冲-
{
bell_fun(1);
d1 -= 1;
printf("第一次小臂减:%d@@\r\n", a1);
}
/*第二次小臂调试*/
if((usart1_race_buf[0] == 'a') && (usart1_race_buf[1] == '1')) //小臂前 //执行步骤4
{
bell_fun(1);
arm_1_F(a1);
printf("第二次小臂前:%d@@\r\n", a1);
}
if((usart1_race_buf[0] == 'a') && (usart1_race_buf[1] == '0')) //小臂后
{
bell_fun(1);
arm_1_B(a1);
printf("第二次小臂后:%d@@\r\n", a1);
}
if((usart1_race_buf[0] == 'a') && (usart1_race_buf[1] == '+')) //脉冲+
{
bell_fun(1);
a1 += 1;
printf("第二次小臂加:%d@@\r\n", a1);
}
if((usart1_race_buf[0] == 'a') && (usart1_race_buf[1] == '-')) //脉冲-
{
bell_fun(1);
a1 -= 1;
printf("第二次小臂减:%d@@\r\n", a1);
}
/*第二次大臂调试*/
if((usart1_race_buf[0] == 'b') && (usart1_race_buf[1] == '1')) //大臂前 //执行步骤5
{
bell_fun(1);
arm_2_DN(b1);
printf("第二次大臂前:%d@@\r\n", b1);
}
if((usart1_race_buf[0] == 'b') && (usart1_race_buf[1] == '0')) //大臂后
{
bell_fun(1);
arm_2_UP(b1);
printf("第二次大臂后:%d@@\r\n", b1);
}
if((usart1_race_buf[0] == 'b') && (usart1_race_buf[1] == '+')) //脉冲+
{
bell_fun(1);
b1 += 1;
printf("第二次大臂加:%d@@\r\n", b1);
}
if((usart1_race_buf[0] == 'b') && (usart1_race_buf[1] == '-')) //脉冲-
{
bell_fun(1);
b1 -= 1;
printf("第二次大臂减:%d@@\r\n", b1);
}
uart1_buf_init(); //清空数组
}
}
<2>集中站
问题 | 答 |
---|---|
组成 | 口袋机,集中站控制板,LED点阵屏(可以用于显示信息),有害垃圾箱,其他垃圾箱,厨余垃圾箱,可回收垃圾箱;4个舵机输出接口,分别控制4个箱门 |
集中站功能 | 接收小车或者其他单元发过来的命令,开启或者关闭箱门 接收其他单元发过来的命令,开启或者关闭LED灯条 接收控制转盘的口袋机发送过来的文字显示在LED点阵屏上 |
点阵 | GT30L32S4W为字库芯片,通过SPI接口与口袋机通讯,可实现读取字库数据,显示在LED点阵屏上 |
LED | 每个垃圾箱都安装了SW2812灯条,可以独立控制每个灯条的颜色 |
代码修改/添加记录
集中站代码详解(主要部分)
-
首先把管脚
D7
初始化(没有用到所以要输出高电平,防止SRAM干扰其他端口) -
定时器1定时功能代码(暂没用上)
//定时器1初始化
void Timer1_Config(uint16_t period, uint16_t prescaler)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义结构体
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //使能TIM5时钟
TIM_TimeBaseStructure.TIM_Period = period - 1; //自动重装载值(溢出值)
TIM_TimeBaseStructure.TIM_Prescaler = prescaler - 1; //预分频系数
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //分频因子,不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_TIM10_IRQn; //定时器1通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能通道
NVIC_Init(&NVIC_InitStructure); //中断初始化
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);//清空中断标志位
TIM_ITConfig(TIM1, TIM_IT_Update|TIM_IT_Trigger, ENABLE); //定时器中断源为 更新中断源/触发中断源
TIM_Cmd(TIM1, ENABLE); //使能定时器1
}
void TIM1_UP_TIM10_IRQHandler(void)
{
static uint16_t count = 0;
if(TIM_GetITStatus(TIM1,TIM_IT_Update) != RESET)
{
count++;
if(count >= 20)
{
//执行动作
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);//清空中断
}
}
Timer1_Config(1000,168); //定时1ms
舵机
- 4个舵机(顺序是从有害垃圾那开始1—2—3—4)
- 管脚定义等跟转盘一样
串口2无线通信
- 管脚定义等跟转盘一样
灯条
- SPI1引脚
管脚 | 对应 | 模式 |
---|---|---|
PB^3 | LCD_CLK | 复用,推挽,无上下拉 |
PB^4 | LCD_MISO(输入) | 同上 |
PB^5 | LCD_MOSI(输出) | 同上 |
- 灯的控制引脚
管脚 | 对应 | 模式 | 对应集中站 |
---|---|---|---|
PD^4 | 2812_EN1 | 输出,推挽,无上下拉 | 有害垃圾 |
PA^8 | 2812_EN2 | 同上 | 其他垃圾 |
PE^7 | 2812_EN3 | 同上 | 厨余垃圾 |
PE^0 | 2812_EN4 | 同上 | 可回收物 |
- WS2812芯片
高位在前低位在后(和一般十六进制颜色码相反)
正常十六进制(16位) | 此程序里十六进制(32位) | 表示颜色 |
---|---|---|
0xFFFFFF | 0xFFFFFFFF | 白色 |
0x000000 | 0x00000000 | 全灭 |
0xFF0000 | 0x000000FF | 红色 |
0x00FF00 | 0x0000FF00 | 绿色 |
0x0000FF | 0x00FF0000 | 蓝色 |
0xFFFF00 | 0x0000FFFF | 黄色 |
0xCE09FB | 0x00BF90EC | 紫色 |
0xFB0083 | 0x003800BF | 粉红色 |
… | … | … |
74HC32芯片
:四组2输入端或
门
/*************************STM32F40x_GPIO_Init.c*************************/
# define WS2812_EN1_L (GPIOD->BSRRH = GPIO_Pin_4)
# define WS2812_EN1_H (GPIOD->BSRRL = GPIO_Pin_4) //
# define WS2812_EN2_L (GPIOA->BSRRH = GPIO_Pin_8)
# define WS2812_EN2_H (GPIOA->BSRRL = GPIO_Pin_8)
# define WS2812_EN3_L (GPIOE->BSRRH = GPIO_Pin_7)
# define WS2812_EN3_H (GPIOE->BSRRL = GPIO_Pin_7)
# define WS2812_EN4_L (GPIOE->BSRRH = GPIO_Pin_0)
# define WS2812_EN4_H (GPIOE->BSRRL = GPIO_Pin_0)
/*************************WS2812.c*************************/
//灯带使用的是SPI的 mosi 这个端口 所以要初始化SPI1
void SPI1_WS2812_Init(void)
{
SPI_InitTypeDef SPI_InitStructure; //定义结构体
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC, ENABLE); //使能对应时钟
GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1); //将管脚PB^3复用为SPI1
GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI1); //将管脚PB^4复用为SPI1
GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1); //将管脚PB^5复用为SPI1
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PB^3
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //PB^4
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //PB^5
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //使能SPI时钟
SPI_I2S_DeInit(SPI1); //将外设SPI1寄存器重设为缺省值
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟悬空低
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //数据捕获于第一个时钟沿
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //波特率预分频值为16
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB位开始(高位)
SPI_InitStructure.SPI_CRCPolynomial = 7; //不使用校验
SPI_Init(SPI1, &SPI_InitStructure); //初始化
SPI_Cmd(SPI1, ENABLE);//使能SPI1
}
//灯带用到了门电路 有4个控制端口
void SW2812_init_fun(void)
{
SPI1_WS2812_Init();
GPIO_init(GPIO_D, GPIO_Pin_4, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_NOPULL); //EN1
GPIO_init(GPIO_A, GPIO_Pin_8, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_NOPULL); //EN2
GPIO_init(GPIO_E, GPIO_Pin_7, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_NOPULL); //EN3
GPIO_init(GPIO_E, GPIO_Pin_0, GPIO_Mode_OUT, GPIO_OType_PP, GPIO_PuPd_NOPULL); //EN4
WS2812_EN1_H;
WS2812_EN2_H;
WS2812_EN3_H;
WS2812_EN4_H;
light_change_color_1(0x00000000);
light_change_color_2(0x00000000);
light_change_color_3(0x00000000);
light_change_color_4(0x00000000);
}
/*************************main.c*************************/
//设置颜色
light_change_color_1(0x000000FF);//第一个灯红
light_change_color_2(0x00FFFFFF);//第二个灯白
light_change_color_3(0x0000FF00);//第三个灯绿
light_change_color_4(0x00FF0000);//第四个灯蓝
点阵
- 字库芯片GT30L32S4W
管脚 | 对应 | 模式 |
---|---|---|
PG^1 | SIP2 SCK | 输出,推挽,无上下拉 |
PE^13 | SIP2 NSS | 同上 |
PE^15 | SIP2 MOSI | 同上 |
PE^14 | SIP2 MISO | 输入,开漏,上拉 |
- LED点阵屏
管脚 | 对应 | 模式 |
---|---|---|
PG^9 | LA | 输出,推挽,无上下拉 |
PG^15 | LB | 同上 |
PD^2 | LC | 同上 |
PG^13 | LD | 同上 |
PD^1 | CLK | 同上 |
PA^15 | LAT | 同上 |
PC^11 | R1 | 同上 |
PC^10 | R2 | 同上 |
PG^12 | G1 | 同上 |
PB^15 | G2 | 同上 |
PC^12 | EN | 同上 |
- 点阵是通过
74hc595
芯片控制行列
/*************************LED8080.c*************************/
//字库芯片的GPIO初始化
void GT30L32S4W_init(void)
{
/*
SIP2 SCK G1
SIP2 MOSI E15
SIP2 MISO E14
SIP2 NSS E13
*/
GPIO_init(GPIO_G,GPIO_Pin_1,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//SCK
GPIO_init(GPIO_E,GPIO_Pin_15,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//MOSI
GPIO_init(GPIO_E,GPIO_Pin_14,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_UP);//MISO
GPIO_init(GPIO_E,GPIO_Pin_13,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//NSS
SPI_SCK_H;//SCK 输出高
SPI_NSS_H;//NSS 输出高
}
//LED点阵屏的端口初始化
void LED8080_gpio_init(void)
{
/*
LA G9
LB G15
LC D2
LD G13
CLK D1
LAT A15
R1 C11
R2 C10
G1 G12
G2 B15
EN C12
*/
GPIO_init(GPIO_G,GPIO_Pin_9,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//LA
GPIO_init(GPIO_G,GPIO_Pin_15,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//LB
GPIO_init(GPIO_D,GPIO_Pin_2,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//LC
GPIO_init(GPIO_G,GPIO_Pin_13,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//LD
GPIO_init(GPIO_D,GPIO_Pin_1,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//CLK
GPIO_init(GPIO_A,GPIO_Pin_15,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//LAT
GPIO_init(GPIO_C,GPIO_Pin_11,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//R1
GPIO_init(GPIO_C,GPIO_Pin_10,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//R2
GPIO_init(GPIO_G,GPIO_Pin_12,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//G1
GPIO_init(GPIO_B,GPIO_Pin_15,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//G2
GPIO_init(GPIO_C,GPIO_Pin_12,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//EN
EN_L;
LA_H;
LB_H;
LC_H;
LD_H;
}
串口2无线通信
- 管脚定义等跟转盘一样
/*************************main.h*************************/
# define ID 0X03 //集中站的ID
/*************************STM32F40x_Usart_eval.c*************************/
u8 usart2_send_buf[100]; //串口2发送数据数组
# define UART2_RACE_MUN 200 //定义接收的命令最大不能超过这个数
u8 usart2_race_buf[UART2_RACE_MUN]; //接收数组
u8 usart2_race_over_bit; //接收完成标志位
u8 usart2_race_count; //接收的命令长度(如果超过UART2_RACE_MUN变回0)
//命令分析
void cmd_explan(void)
{
u8 door_mun;
u8 cmd_data;
if(usart2_race_buf[0]==ID) //判断是不是自己的ID
{
LCD_ShowString_fun("ID=3"); //显示自己的ID
cmd_data = usart2_race_buf[1];
door_mun = usart2_race_buf[2];
if(cmd_data==0x01)
LCD_ShowString_fun("kaimen"); //开门
if(cmd_data==0x02)
LCD_ShowString_fun("guanmen"); //关门
if(door_mun==0)
LCD_ShowString_fun("0");
if(door_mun==1)
LCD_ShowString_fun("1");
if(door_mun==2)
LCD_ShowString_fun("2");
if(door_mun==3)
LCD_ShowString_fun("3");
steering_engine_ctl(cmd_data,door_mun);//开门或者关门函数 data 1是开门 2是关门 door_mun是门编号 0-3
}
}
//判断是否接收到命令
void uart2_race_chack(void)
{
if(usart2_race_over_bit)
{
usart2_race_over_bit= 0;
Uart2_send_data(usart2_race_buf,usart2_race_count);
usart2_race_count = 0;
LCD_ShowString_fun("433_race");//屏上显示收到数据 但是不一定是给自己的
cmd_explan(); //命令分析
}
}
/*************************MOTO.c*************************/
//舵机控制 dat 1是开 2是关 mun是 门编号
void steering_engine_ctl(u8 cmd,u8 mun)
{
switch(mun)
{
case 0:
if(cmd==1)
steering_engine_1_open();
if(cmd==2)
steering_engine_1_close();
break;
case 1:
if(cmd==1)
steering_engine_2_open();
if(cmd==2)
steering_engine_2_close();
break;
case 2:
if(cmd==1)
steering_engine_3_open();
if(cmd==2)
steering_engine_3_close();
break;
case 3:
if(cmd==1)
steering_engine_4_open();
if(cmd==2)
steering_engine_4_close();
break;
}
}
<3>转盘
问题 | 答 |
---|---|
组成 | AI智能图像识别部分(英伟达NANO主机,工业摄像头) 转盘(减速电机,光电码盘) 4个舵机 4个光电反射传感器 2个红外对射传感器 转盘控制板 口袋机 |
整个一个单独步骤运行流程
① 将沙盘上的所有单元上电,NANO主机开机并运行垃圾识别软件
,在屏幕上能都看到图像,并 点击识别按钮
② 将要识别的模拟垃圾物块放置到转盘上(一般为1-4个),RFID刷卡
(转盘控制板上有RFID读卡模块),启动转盘开始转动,当物块经过第一红外对射光电管后
,口袋机知道物块已经进入识别区域,等待NANO主机通过MQTT通讯
将垃圾分类的结果(0是可回收垃圾,1是有害垃圾,2是厨余垃圾,3是其他垃圾) 发送过来,当物块经过第二个红外对射光电管后,口袋机知道物块已经通过识别区域,将这个物块的类别和位置计数清零
(通过编码器可以知道物块走了多少个脉冲,第二个红外对射管是物块移动多少脉冲的记录的起始位置
)
③ 口袋机得到NANO发来的0-3的数据后,当垃圾走到对应的托盘(0-3),口袋机控制舵机将垃圾推入托盘
④ 推入托盘后,口袋机通过无线模块
(转盘控制板上),发送命令给小车
(夹几号托盘的物块扔到几号集中站),小车收到命令后从起始位置出发,经过十字磁条,开始计数
(走多少距离到一号托盘),停下,夹物块
⑤ 托盘的下面有反射光电开关
,当小车拿走物块后,转盘的口袋机知道小车已经把物块夹走了
⑥ 小车夹到物块后,通过磁条寻迹
,走到指定的集中站
(转盘发给小车的命令中有),小车发送命令给集中站
(开几号门),集中站通过舵机控制几号门开门
⑦ 小车执行扔垃圾程序,将物块扔进集中站,给集中站发送关门命令
⑧ 小车回到起始位置
,给转盘发送命令(动作全部完成,集中站收到命令后,可以再给小车分配新的任务)
转盘上有 3个光电对射管
,用于检测垃圾的通过, 1和2是用来检测垃圾在1和2之间的时候是否收到摄像发过来的分类数据
(通过网线,使用MQTT协议), 3号光电对射是用来记录垃圾走到这个位置后开始编码器计数
(转盘的电机上装有光电编码器,通过程序可以知道每个垃圾经过光电对射管3后走了多少距离),垃圾到达对应的托盘后,通过舵机,将垃圾推到托盘上
假设任务
小车收到转盘发过来的命令(例如:将1号托盘的垃圾运送到1号垃圾桶)
小车开始寻迹向前走,走过一个横着的十字磁条后,再走若干的脉冲,达到1号托盘;拿到垃圾后,小车继续寻迹向前,直到遇到左转磁条后停止,然后小车原地左转90度;直走若干脉冲直到遇到左转磁条,然后小车原地左转90度;将垃圾扔到1号集中站中;继续走到左转磁条,然后小车原地左转90度;连续通过3个左转磁条后停止;小车原地左转90度,小车回到起始位置。
沙盘各个单元之间的通讯
- 图像识别的NANO主机与控制转盘的口袋机通过
MQTT协议
通讯,NANO通过网线连接路由器,转盘口袋机通过网线连接路由器,NANO运行图像识别分类程序后,建立了一个MQTT服务器,转盘口袋机作为客户端,订阅特定的主题,NANO每识别完一次垃圾后都会将分类结果向转盘口袋机发送一个消息 - 转盘,集中站,小车,垃圾桶,每个单元都有一个
无线433模块
,分别在各自的控制板上,并有天线,无线模块采用透传广播
的方式传输数据(透传广播意思是一个口袋机的串口发送数据给模块,所有上电的模块都能收到,并通过串口发送给口袋机) - 口袋机和无线模块是通过
串口
收发数据的,波特率115200
- 如何做到单独通讯?
用 设置ID
的方式(传输数据的第一个字节是ID)
单元 | ID |
---|---|
转盘 | 1 |
小车 | 2 |
集中站 | 3 |
垃圾桶1 | 4 |
垃圾桶2 | 5 |
垃圾桶3 | 6 |
垃圾桶4 | 7 |
举例:转盘给小车发送命令(串口发送的数据)
这个命令其他的单元也会受到,通过编写命令判断程序,可以判断第一字节是否是自己的ID, 是就执行命令,不是就不执行命令
字节1 | 字节2 | 字节3 | 字节4 |
---|---|---|---|
2 (小车的ID) | 要执行的命令 | 参数1 | 参数2 |
- 口袋机给无线模块发送的数据要求
① 波特率115200
② 数据长度可变(最大64字节)
③ 一条命令的全部数据必须是连续的(模块会判断一帧连续的数据,如果数据不连续了,就认为一帧完成,打包通过无线发送出去),所以给无线模块发送数据时,要一帧全部发完,不要分开发
启动转盘步骤
打开沙盘电脑(密码:123456) — 右上角设置 — 系统设置 — 网络 — 点击左边那几个有线,看看哪个有显示IP地址的
下载程序到口袋机,然后打开桌面的 垃圾分拣
— 打开摄像头 — 开始识别
代码修改/添加记录
11/12
修改的文件 | 修改行号开始 | 修改了某处 |
---|---|---|
main.c STM32F40x_Timer_eval.c |
330 22 |
Timer5_Config(74, 5);改成Timer5_Config(20000, 84); 把溢出值/预分频值计算公式改变 |
11/15
测试轮盘发命令给集中站开门关门–成功
void send_to_jzz_cmd(uint8_t oc)
{
u8 cmd_buf[3];
cmd_buf[0] = 3;//发送集中站的ID
cmd_buf[1] = oc; //开门
cmd_buf[2] = 0; //几号门
Uart2_send_data(cmd_buf,3);//发送函数
bell_fun(1); //蜂鸣器响
}
//在按键里执行即可
轮盘代码详解(主要部分)
- 清除数组大小
//数组初始化为0([x][0]:存储垃圾状态,[x][1]:垃圾类型,[x][2]转盘计数,[x][3]:暂没用到)
void task_buf_init(void)
{
u8 i,j;
for(i=0;i<TASK_MUN;i++)
{
for(j=0;j<4;j++)
{
task_buf[i][j] = 0;
}
}
}
- 按键执行的内容
//判断哪个按键按下
u8 button_scan_fun(void)
{
u8 i;
u8 temp[8];
u8 return_data;
# define BUTTON_A 4
# define BUTTON_B 3
# define BUTTON_C 2
# define BUTTON_D 1
# define BUTTON_UP 7
# define BUTTON_DOWN 6
# define BUTTON_LEFT 8
# define BUTTON_RIGHT 5
return_data = 0;
temp[0] = READ_BUTTON1; //D
temp[1] = READ_BUTTON2; //C
temp[2] = READ_BUTTON3; //B
temp[3] = READ_BUTTON4; //A
temp[4] = READ_BUTTON5; //右
temp[5] = READ_BUTTON6; //下
temp[6] = READ_BUTTON7; //上
temp[7] = READ_BUTTON8; //左
for(i=0;i<8;i++)
{
if(temp[i]==0) //按下是低电平
{
return_data = i+1; //返回对应的按键编码
break;
}
}
return return_data; //没有按键按下则返回0
}
//根据返回的按键编码执行对应动作
void test_fun(void)
{
u8 button_dat;
button_dat = button_scan_fun(); //接收按键编码
switch(button_dat)
{
case BUTTON_A:
bell_fun(1); //响一下
steering_engine_1_push(); //舵机1推
delay_ms(1000); //延时1s
steering_engine_1_shrink(); //舵机1收
printf("%d\r\n",button_dat); //串口显示对应按键编码
break;
case BUTTON_B:
bell_fun(1); //响一下
steering_engine_2_push(); //舵机2推
delay_ms(1000); //延时1s
steering_engine_2_shrink(); //舵机2收
printf("%d\r\n",button_dat); //串口显示对应按键编码
break;
case BUTTON_C:
bell_fun(1); //响一下
steering_engine_3_push(); //舵机3推
delay_ms(1000); //延时1s
steering_engine_3_shrink(); //舵机3收
printf("%d\r\n",button_dat); //串口显示对应按键编码
break;
case BUTTON_D:
bell_fun(1); //响一下
steering_engine_4_push(); //舵机4推
delay_ms(1000);
steering_engine_4_shrink(); //舵机4收
printf("%d\r\n",button_dat); //串口显示对应按键编码
break;
case BUTTON_UP:
bell_fun(1); //响一下
//开始测试
MOTO_run_fun(); //转盘转动
printf("%d\r\n",button_dat); //串口显示对应按键编码
break;
case BUTTON_DOWN:
bell_fun(1); //响一下
MOTO_stop_fun(); //转盘停止
printf("%d\r\n",button_dat); //串口显示对应按键编码
break;
case BUTTON_LEFT:
bell_fun(1); //响一下
printf("%d\r\n",button_dat); //串口显示对应按键编码
break;
case BUTTON_RIGHT:
bell_fun(1); //响一下
printf("%d\r\n",button_dat); //串口显示对应按键编码
break;
}
}
网口/TCP/MQTT部分
管脚 |
---|
PB^0/TIM3_CH3/网口 |
某些代码意思
英文(打印信息) | 含义 |
---|---|
10M,100M | PHY速度(与下面一起的) |
PHY Speed is: | PHY 速度为: |
PHY Init Successful! | PHY 初始化成功! |
Network cable is not connected! | 网线未连接! |
Please connect the network cable. | 请连接网线。 |
Network cable is now connected. | 网络电缆现已连接。 |
Network cable is unplugged! | 网线被拔掉了! |
DHCP IP address: | DHCP IP地址:(地址一般是Nano机有线网络IPv4地址) |
DHCP IP GOT | IP已经得到 |
DHCP IP address wait timeout | DHCP IP 地址等待超时 |
Static IP address: | 静态 IP 地址: |
Connect to Server … | 连接到服务器… |
Connect to Server Error ! | 连接服务器错误! |
can not create tcp pcb | 无法创建 tcp pcb |
Connected to Server Successful | 连接服务器成功 |
connected server flag set: 1 | 连接的服务器标志集:1 |
Memory allocate structure “s_es” Error ! | 内存分配结构"s_es" 错误 ! |
Server not found | 找不到服务器 |
have one tcp client error ! | 有一个 TCP 客户端错误! |
close connection | 紧密联系 |
connected server flag set: 0 | 关闭连接已连接的服务器标志集:0 |
sign_in | 登入 |
MQTT_sign_in | MQTT登入 |
topic_in | 服务器登入 |
MQTT_topic_in | MQTT服务器登入 |
Connent command send OK | 连接命令发送OK |
netconf.c/stm32f4x7_ eth_ bsp.c/tcp_ echoclient.c
这3个主要文件是关于服务器/MQTT等
/*************************MQTT.c*************************/
u8 MQTT_race_TOPIC_bit; //NAn0主机是否识别到结果标志位
u8 MQTT_race_TOPIC_dat; //MQTT_race_TOPIC_dat是识别物块的结果 0x30 0x31 0x32 0x33
//发布消息
//buff:数据包数组
//dup :重发标志
//qos :服务质量等级
//retain:保留标志
//topic:主题如“a/c”
//msg:消息
物品名 | 对应数据(前面13字节一样) |
---|---|
白菜 | 30 0C 00 09 63 6F 6C 6C 65 63 74 30 36 32 |
芬达 | 30 |
打火机 | 33 |
纸箱 | 30 |
完整的鱼 | 32 |
药片 | 31 |
鱼骨头 | 32 |
西红柿 | 32 |
可乐易拉罐 | 30 |
香蕉 | 32 |
- 托盘是从小车出发点开始: 0—1—2—3 集中箱是从有害垃圾箱开始:0—1—2—3
最后一个数据表示 | 对应托盘号(命令) | 类别 | 对应集中箱号()(命令) |
---|---|---|---|
0x30(第4个托盘) | 3 | 可回收物 | 3 |
0x31(第1个托盘) | 0 | 有害垃圾 | 0 |
0x32(第3个托盘) | 2 | 厨余垃圾 | 2 |
0x33(第2个托盘) | 1 | 其他垃圾 | 1 |
- 转盘宏定义全局
宏定义 | 含义 |
---|---|
# define TASK_MUN 10 | 控制数组行的大小 |
# define GARBAGE_STATE 0 | 垃圾状态(即数组第1列) |
# define GARBAGE_TYPE 1 | 垃圾类型(即数组第2列) |
# define TURNTABLE_COUNT 2 | 转盘计数(即数组第3列) |
# define EMPTY 0 | 空 |
# define WAIT_CAMERA 1 | 等待识别 |
# define CAMERA_OK 2 | 已经识别 |
# define CAMERA_TIME_OUT 3 | 识别超时 |
# define WAIT_ENGINE 4 | 等待舵机推 |
# define PUSH 5 | 推的过程中 |
# define ARRIVE_OK 6 | 已经到达托盘 |
# define SEND_OUT 7 | 已经发送命令给小车 |
# define TRANSPORT 8 | 被小车拿走运送中 |
/*************************task.c*************************/
//确认垃圾状态与类别
void MQTT_race_topic_data_fun(u8 type)
{
u8 i;
u8 type_data;
// type_data = type - 0x30;//因为MQTT 发过来的数据是 0x30 0x31 0x32 0x33 所以这个数据要变成0123 就要减去0x30
if(type-0x30==0){type_data=3;} //可回收垃圾
if(type-0x30==1){type_data=0;} //有害垃圾
if(type-0x30==2){type_data=2;} //厨余垃圾
if(type-0x30==3){type_data=1;} //其他垃圾
for(i=0;i<TASK_MUN;i++)//从0开始查看 垃圾的状态 如果是等待识别的,就记录数据
{
if(task_buf[i][GARBAGE_STATE]==WAIT_CAMERA)//如果是等待识别
{
task_buf[i][GARBAGE_TYPE] = (u32)type_data;//确定垃圾类别
task_buf[i][GARBAGE_STATE] = CAMERA_OK; //是识别后的状态
//printf("MQTT RACE\r\n"); //串口打印
if(type_data==0)
LCD_ShowString_fun("2:MQTT_race = 0");//屏上显示(有害垃圾)
if(type_data==1)
LCD_ShowString_fun("2:MQTT_race = 1");//屏上显示(其他垃圾)
if(type_data==2)
LCD_ShowString_fun("2:MQTT_race = 2");//屏上显示(厨余垃圾)
if(type_data==3)
LCD_ShowString_fun("2:MQTT_race = 3");//屏上显示(可回收垃圾)
break;
}
else //如果没有物在第一个光电管和第二个光电管之间 屏幕显示错误
{
LCD_ShowString_fun("MQTT_race_late"); //屏上显示表示MQTT_race_late 说明给消息的时间不对了 已经超过了第二个光电管
}
}
}
转盘电机
管脚 | 对应 | 模式 |
---|---|---|
PD^4 | RUN | 输出,推挽,无上下拉 |
PD^13(TIM4_CH2) | BACK | 同上 |
PA^8(TIM1_CH1) | PWM_STM32(产生PWM) | 复用,推挽,无上下拉 |
PD^2(D2/UART5 RX/TIM3 ETR)外部时钟做时钟源 | CODE1 | 复用,开漏,无上下拉 |
由于电机那接了一个 74HC02D
与非门芯片(即输入1输出0,输入0输出1),电机低电平才会动,故在配置 PWM时需要配成 PWM1模式+高电平极性
设置占空比100(即一直输出高电平,然后经过芯片就变成一直输出低电平驱动电机转动)
尝试
:把极性设为低电平会发现波形是一直低电平,经过与非门也就是一直高电平
转动是通过PID算法计算的(看不懂)
/*************************task.c*************************/
//==========speed_PID==========
u16 speed_new;
float SpeedSet_dat; //设置的速度
float Kp = 10;//pi控制系数
float Ki = 1;//pi控制系数(没有用到d)
float e; //pid偏差
float e1;
float e2;
float duk; //pid输出值
float uk;
float uk1;
float out;
float num; //实际速度
u16 PWMTime; //脉冲宽度
void timer5_speed_pid_fun(u16 speed)
{
u8 i;
speed_new = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0);
MOTO_count = MOTO_count + speed_new;
for(i=0;i<TASK_MUN;i++)
{
if(task_buf[i][GARBAGE_STATE]==WAIT_ENGINE)
{
task_buf[i][TURNTABLE_COUNT] = task_buf[i][TURNTABLE_COUNT] + speed_new;
}
}
num = (float)speed_new;
SpeedSet_dat = speed;
e=SpeedSet_dat-num;//设置速度-实际速度,两者的差值
duk=(Kp*(e-e1)+Ki*e)/100;//只调节PI
uk=uk1+duk;//uk=u(k-1)+Δuk
out=(int)uk;//取整后输出
if(out>90) //设置最大限制
out=90;
else if(out<0)//设置最小限制
out=0;
uk1=uk; //为下一次增量做准备
e2=e1;
e1=e;
PWMTime=(u16)out; //out对应于PWM高电平的时间
MOTO_pwm_set(PWMTime);
}
void PID_ram_init(void)
{
//pid偏差
e = 0;
e1 = 0;
e2 = 0;
//pid输出值
uk = 0;
uk1 = 0;
duk = 0;
num = 0;
out = 0;
}
/*************************STM32F40x_ GPIO_ Init.h*************************/
//电机
# define RUN_OFF (GPIOD->BSRRH = GPIO_Pin_4) //管脚设为低电平(相当于停止)
# define RUN_ON (GPIOD->BSRRL = GPIO_Pin_4) //管脚设为高电平(相当于启动)
/*************************main.c*************************/
//轮盘速度(越大速度越快)
# define MOTO_SPEED_SET 30
/*************************MOTO.c*************************/
//转盘电机PWM初始化(这里没设置互补通道故相当于普通PWM模式)
void TIM1_PWM_Init_PA8(u32 arr,u32 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义结构体
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE); //使能TIM1时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能A组时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //PA^8
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化
GPIO_PinAFConfig(GPIOA,GPIO_PinSource8,GPIO_AF_TIM1); //PA^8复用为TIM1_CH1
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //分频因子为1
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseStructure.TIM_Prescaler = psc;//Timer clock = sysclock /(TIM_Prescaler+1) = 168M
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //此参数不用需配置为0(只有高级定时器才有)
TIM_TimeBaseStructure.TIM_Period = arr; //Period = (TIM counter clock / TIM output clock) - 1 = 20K
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM1模式(CNT<CCR有效,反之无效)
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //互补输出使能。开启OCN输出到对应的管脚
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; //互补输出使能。开启OCN输出到对应的管脚
TIM_OCInitStructure.TIM_Pulse = (arr+1)/2; //重装载值的一半
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //主通道的输出极性
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High; //互补通道的输出极性
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; //空闲状态输出高电平
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset; //空闲状态输出低电平
TIM_OC1Init(TIM1,&TIM_OCInitStructure); //使能
TIM_Cmd(TIM1,ENABLE);
TIM_CtrlPWMOutputs(TIM1,ENABLE); //高级定时器才有 必须打开
}
//转盘电机控制初始化 PWM:PA8 RUN:PD4 BACK:PD13
void MOTO_init(void)
{
GPIO_init(GPIO_D,GPIO_Pin_4,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//D4 RUN
GPIO_init(GPIO_D,GPIO_Pin_13,GPIO_Mode_OUT,GPIO_OType_PP,GPIO_PuPd_NOPULL);//D13 BACK
RUN_OFF;
BACK_OFF;
//计算:100*84/168000000 = 0.00005s = 0.05ms (1KHz = 1ms --> 20KHz = 0.05ms)
//右边/20,左边*20
TIM1_PWM_Init_PA8(100-1,84-1); //频率 20kHz 占空比 0-100
TIM_SetCompare1(TIM1,100);//最大0 最小100 PWM输出经过了数字电路 低电平有效
}
//识别到RFID后开始设置速度,pid初始化,准备启动
void MOTO_run_fun(void)
{
PID_ram_init();
moto_speed = MOTO_SPEED_SET; //速度
moto_run_bit = 1;
}
//轮盘停止(暂时没用到)
void MOTO_stop_fun(void)
{
moto_run_bit = 0;
RUN_OFF;
MOTO_pwm_set(0);
}
//转盘电机PWM输出
void MOTO_pwm_set(u16 speed)
{
RUN_ON;
TIM_SetCompare1(TIM1,100-speed);//因为电平有一个 74HC02 所以 电平是反的 低电平 开始转
}
//编码器 计数 TIM3 PD2 计数程序初始化 用于转盘电机编码器
void TIM3_PD2_encoder_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
GPIO_init(GPIO_D,GPIO_Pin_2,GPIO_Mode_AF,GPIO_OType_OD,GPIO_PuPd_NOPULL); //复用,开漏,无上下拉
GPIO_PinAFConfig(GPIOD,GPIO_PinSource2,GPIO_AF_TIM3); //复用PD^2为TIM3
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能TIM3时钟
TIM_TimeBaseStructure.TIM_Prescaler = 0x00; //预分频系数
TIM_TimeBaseStructure.TIM_Period = 0xFFFF; //重装载值
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 定时器3初始化
TIM_ETRClockMode2Config(TIM3,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted, 0); //配置ETR外部输入触发模式,外部输入模式2
TIM_SetCounter(TIM3, 0); //清0
TIM_Cmd(TIM3, ENABLE);
}
/*************************STM32F40x_Timer_eval.c*************************/
//定时器中断服务函数
//定时20ms
void TIM5_IRQHandler(void)
{
if(TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) //==1表示发生溢出中断
{
TIM_ClearITPendingBit(TIM5, TIM_IT_Update); //清除标志位
timer5_int_bit = 1;
if(moto_run_bit == 1)
{
timer5_speed_pid_fun(moto_speed); //在这轮盘一直动
}
PG9_F; //暂不知道这个什么用处
}
}
舵机
管脚 | 对应 | 模式 | 对应舵机(小车出发点起) |
---|---|---|---|
PA^6(TIM13_CH1) | DOUJI1 | 复用,推挽,无上下拉 | 舵机1 |
PC^8(TIM8_CH3) | DOUJI2 | 同上 | 舵机2 |
PD^12(TIM4_CH1) | DOUJI3 | 同上 | 舵机3 |
PC^9(TIM8_CH4) | DOUJI4 | 同上 | 舵机4 |
- 对于180°的舵机,一般来说周期为
20ms
(频率为50Hz),频率越高,舵机反应速度越快,但是力的输出却越小,脉宽(一个周期内高电平持续时间)为500–2500us
。其中为1500us使得舵机转轮处于中间位置,可以理解为90°的位置,为500-1500us和1500–2500us之间分别会朝着0–90°和90–180°的方向旋转(慢慢增大占空比的时间延长点可以把舵机旋转速度降下来) - 本轮盘4个舵机型号是
MG995
,下面是它的一些需要注意的地方
t占空比高电平持续时间(频率20ms) | 角度 |
---|---|
0.5ms | 舵机会转动 0 ° |
1.0ms | 舵机会转动 45° |
1.5ms | 舵机会转动 90° |
2.0ms | 舵机会转动 135° |
2.5ms | 舵机会转动180° |
/*************************main.c*************************/
//舵机推和收的PWM参数
# define PULL1 275//290
# define SHRINK1 100//120
# define PULL2 280//265
# define SHRINK2 100//105
# define PULL3 280//280
# define SHRINK3 100//115
# define PULL4 285//255
# define SHRINK4 100//95
/*************************MOTO.c*************************/
//舵机1(剩下3个舵机配置一样只是引脚不一样)
void TIM13_PWM_Init_PA6(u32 arr,u32 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义结构体
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM13,ENABLE); //使能TIM13时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能A组时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //PA^6
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_TIM13); //复用引脚为TIM13
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //分频因子
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseStructure.TIM_Prescaler = psc; //预分频系数
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseStructure.TIM_Period = arr; //重装载值
TIM_TimeBaseInit(TIM13,&TIM_TimeBaseStructure);
//互补通道也就是含N的结构体成员可写可不写没影响(如果不需要互补通道的话)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM1模式(cnt<rcc电平有效;反之无效)
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
TIM_OCInitStructure.TIM_Pulse = (arr+1)/2; //重装载的一半
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //有效电平为高
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
TIM_OC1Init(TIM13,&TIM_OCInitStructure);
TIM_Cmd(TIM13,ENABLE); //使能TIM13
TIM_CtrlPWMOutputs(TIM13,ENABLE); //此函数仅高级定时器才需要加(其他定时器可省略)
}
//4个舵机初始化
void pwm_init(void)
{
//PA6 ok
//PC8 ok
//D12 OK
//C9 OK
//下面如果想更加精准点可以通过公式计算即可
TIM13_PWM_Init_PA6(5000-1,335-1); // 5000*335/84000000 ≈ 0.0199s ≈ 20ms(50Hz)
TIM8_PWM_Init_PC8(5000-1,670-1); // 5000*670/168000000 ≈ 0.0199s ≈ 20ms(50Hz) 为什么是670? 因为可以通过分母扩大2倍分子也扩大2倍来保持结果不变
TIM4_PWM_Init_PD12(5000-1,335-1);
TIM8_PWM_Init_PC9(5000-1,670-1);
//750=2.5ms 125=0.5ms
steering_engine_1_shrink();
steering_engine_2_shrink();
steering_engine_3_shrink();
steering_engine_4_shrink();
}
//舵机1推---收(剩下3个舵机配置一样占空比也是一样)
void steering_engine_1_push(void)
{
u16 pwm_data;
//printf("PWM1_open\r\n");
pwm_data = SHRINK1;
while(pwm_data<=PULL1) //pwm_data:100 PULL1:275(占空比范围:2%~5.5%)
{
//最小占空比:100/5000 = 0.02 = 2%
//最大占空比:275/5000 = 0.055 = 5.5%
TIM_SetCompare1(TIM13,pwm_data);
pwm_data++;
delay_ms(2);//决定了舵机推出去的速度(延时越大速度越慢)
}
}
void steering_engine_1_shrink(void)
{
TIM_SetCompare1(TIM13,SHRINK1);//占空比:100/5000 = 0.02 = 2%
//printf("PWM1_close\r\n");
}
蜂鸣器
管脚 | 对应 | 模式 |
---|---|---|
PG^1 | BELL | 输出,推挽,无上下拉 |
/*************************STM32F40x_ GPIO _Init.h*************************/
//蜂鸣器
# define BELL_OFF (GPIOG->BSRRH = GPIO_Pin_1) //管脚设为低电平(不响)
# define BELL_ON (GPIOG->BSRRL = GPIO_Pin_1) //管脚设为高电平(响),因为原理图那个是NPN三极管需要高电平导通
//没用到(蜂鸣器的)
# define BELL_F GPIOG->ODR^=GPIO_Pin_2
光电管
管脚 | 对应 | 模式 |
---|---|---|
PF^1 | WEIZHI1(托盘的反射管) | 输入,开漏,上拉(因为距离太近,有光是1,无光是0) |
PF^2 | WEIZHI2(托盘的反射管) | 同上 |
PF^3 | WEIZHI3(托盘的反射管) | 同上 |
PF^4 | WEIZHI4(托盘的反射管) | 同上 |
PA5 | SW1(第一个光电对射) | 输出,开漏,无上下拉 |
PD7 | SW2(第二个光电对射) | 同上 |
PE8 | SW3 | 同上 |
PE10 | SW4 | 同上 |
PF12 | SW6 | 同上 |
PF11 | SW5 | 同上 |
-
轮盘有
2
个对射管 ,4
个反射管(分别在4个托盘上用来识别是否有物体在托盘);有光是1 无光是0
-
第一个光电对射管
是判断物块是否已经进入识别区域(需要MQTT把识别结果发送回来第二个光电对射管才会进行对应操作);
第二个红外对射光电管` 判断物块是否已经通过识别区域(如果识别通过则将这个物块的类别和位置计数清零)
/*************************STM32F40x_ GPIO _Init.h*************************/
//光电对射
# define READ_DUIGUAN1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5) //读取第一个光电对射的电平状态
# define READ_DUIGUAN2 GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_7) //读取第二个光电对射的电平状态
# define READ_DUIGUAN3 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_8) //这4个没用到暂时不用管
# define READ_DUIGUAN4 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_10)
# define READ_DUIGUAN5 GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_12)
# define READ_DUIGUAN6 GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_11)
//托盘位置的反射管
# define READ_WEIZHI1 GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_1)
# define READ_WEIZHI2 GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_2)
# define READ_WEIZHI3 GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_3)
# define READ_WEIZHI4 GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_4)
/*************************STM32F40x_ GPIO _Init.c*************************/
//光电对射管
void Photoelectric_switch_gpio_init(void)
{
//1:PF1
//2:PF2
//3:PF3
//4:PF4
//反射管脚
GPIO_init(GPIO_F,GPIO_Pin_1,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_UP);//PF1 //有光是1 无光是0 必须是上拉模式 不然距离有太近 不到10cm
GPIO_init(GPIO_F,GPIO_Pin_2,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_UP);//PF2
GPIO_init(GPIO_F,GPIO_Pin_3,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_UP);//PF3
GPIO_init(GPIO_F,GPIO_Pin_4,GPIO_Mode_IN,GPIO_OType_OD,GPIO_PuPd_UP);//PF4
//第一个对射管脚
GPIO_init(GPIO_A,GPIO_Pin_5,GPIO_Mode_OUT,GPIO_OType_OD,GPIO_PuPd_NOPULL);//PA5
(GPIOA->BSRRL = GPIO_Pin_5); //设为高电平
//第二个对射管脚
GPIO_init(GPIO_D,GPIO_Pin_7,GPIO_Mode_OUT,GPIO_OType_OD,GPIO_PuPd_NOPULL);//PD7
(GPIOD->BSRRL = GPIO_Pin_7);
GPIO_init(GPIO_E,GPIO_Pin_8,GPIO_Mode_OUT,GPIO_OType_OD,GPIO_PuPd_NOPULL);//PE8
(GPIOE->BSRRL = GPIO_Pin_8);
GPIO_init(GPIO_E,GPIO_Pin_10,GPIO_Mode_OUT,GPIO_OType_OD,GPIO_PuPd_NOPULL);//PE10
(GPIOE->BSRRL = GPIO_Pin_10);
GPIO_init(GPIO_F,GPIO_Pin_12,GPIO_Mode_OUT,GPIO_OType_OD,GPIO_PuPd_NOPULL);//PF12
(GPIOF->BSRRL = GPIO_Pin_12);
GPIO_init(GPIO_F,GPIO_Pin_11,GPIO_Mode_OUT,GPIO_OType_OD,GPIO_PuPd_NOPULL);//PF11
(GPIOF->BSRRL = GPIO_Pin_11);
}
/*************************task.c*************************/
//两个对射管电平状态存储数组
u8 Photoelectric_switch_new[2];//两个光电对射
u8 Photoelectric_switch_old[2];
//托盘的反射管电平状态存储数组
u8 position_switch_new[4];//四个光电反射
u8 position_switch_old[4];
u8 interrupt1; //经过第一个光电对射标志位
u8 interrupt2; //经过第二个光电对射标志位
u8 weizhi1_interrupt_in; //托盘有物品
u8 weizhi2_interrupt_in;
u8 weizhi3_interrupt_in;
u8 weizhi4_interrupt_in;
u8 weizhi1_interrupt_out; //托盘没有物品
u8 weizhi2_interrupt_out;
u8 weizhi3_interrupt_out;
u8 weizhi4_interrupt_out;
//读取光电管的变化
void read_interrupt_fun(void)
{
//第一个光电对射
Photoelectric_switch_new[0] = READ_DUIGUAN1; //读取最新的电平状态(第一次读取时是没有物体的所以一定是1,当有物体经过时变0)
if((Photoelectric_switch_new[0]==0)&&(Photoelectric_switch_old[0]==1))
{
interrupt1 = 1; //标志位置1表示经过了第一个对射
//printf("interrupt1\r\n");
}
Photoelectric_switch_old[0] = Photoelectric_switch_new[0]; //把当前状态存储起来
//第二个光电对射
Photoelectric_switch_new[1] = READ_DUIGUAN2;
if((Photoelectric_switch_new[1]==0)&&(Photoelectric_switch_old[1]==1))
{
interrupt2 = 1;
//printf("interrupt2\r\n");
}
Photoelectric_switch_old[1] = Photoelectric_switch_new[1];
//第一个位置
position_switch_new[0] = READ_WEIZHI1;
if((position_switch_new[0]==0)&&(position_switch_old[0]==1))
{
weizhi1_interrupt_in = 1;
//printf("weizhi1_in\r\n");
}
if((position_switch_new[0]==1)&&(position_switch_old[0]==0))
{
weizhi1_interrupt_out = 1;
//printf("weizhi1_out\r\n");
}
position_switch_old[0] = position_switch_new[0];
//第二个位置
position_switch_new[1] = READ_WEIZHI2;
if((position_switch_new[1]==0)&&(position_switch_old[1]==1))
{
weizhi2_interrupt_in = 1;
//printf("weizhi2_in\r\n");
}
if((position_switch_new[1]==1)&&(position_switch_old[1]==0))
{
weizhi2_interrupt_out = 1;
//printf("weizhi2_out\r\n");
}
position_switch_old[1] = position_switch_new[1];
//第三个位置
position_switch_new[2] = READ_WEIZHI3;
if((position_switch_new[2]==0)&&(position_switch_old[2]==1))
{
weizhi3_interrupt_in = 1;
//printf("weizhi3_in\r\n");
}
if((position_switch_new[2]==1)&&(position_switch_old[2]==0))
{
weizhi3_interrupt_out = 1;
//printf("weizhi3_out\r\n");
}
position_switch_old[2] = position_switch_new[2];
//第四个位置
position_switch_new[3] = READ_WEIZHI4;
if((position_switch_new[3]==0)&&(position_switch_old[3]==1))
{
weizhi4_interrupt_in = 1;
//printf("weizhi4_in\r\n");
}
if((position_switch_new[3]==1)&&(position_switch_old[3]==0))
{
weizhi4_interrupt_out = 1;
//printf("weizhi4_out\r\n");
}
position_switch_old[3] = position_switch_new[3];
}
//经过第一个光电对射时执行动作
void Photoelectric_switch_1_fun(void)//第一个光电开关信号处理
{
u8 i;
for(i=0;i<TASK_MUN;i++)//从0开始查看 垃圾的状态 为空的 设置成下一个状态 就是等待摄像头识别
{
if(task_buf[i][GARBAGE_STATE]==EMPTY)//如果是空
{
task_buf[i][GARBAGE_STATE] = WAIT_CAMERA;//垃圾状态变成等待摄像头识别
//printf("position 1 int\r\n");
break;
}
}
}
//经过第二个光电对射时执行动作
void Photoelectric_switch_2_fun(void)//第2个光电开关信号处理
{
u8 i;
for(i=0;i<TASK_MUN;i++)
{
if(task_buf[i][GARBAGE_STATE]==CAMERA_OK)
{
task_buf[i][GARBAGE_STATE] = WAIT_ENGINE;//等待舵机推
task_buf[i][TURNTABLE_COUNT] = 0;//计数清零(准备开始计数脉冲)
//printf("position 2 int\r\n");
break;
}
}
}
//位置有东西
void position_in_fun(void)
{
u8 i;
for(i=0;i<TASK_MUN;i++)
{
if(task_buf[i][GARBAGE_STATE]==PUSH) //判断当前垃圾状态是否为推的过程,是则把状态改为到达托盘
{
task_buf[i][GARBAGE_STATE]=ARRIVE_OK; //垃圾状态为到达托盘
break;
}
}
}
//位置没有东西
void position_out_fun(void)
{
u8 i;
for(i=0;i<TASK_MUN;i++)
{
if(task_buf[i][GARBAGE_STATE]==ARRIVE_OK) //当垃圾状态为到达托盘且被车取走时
{
task_buf[i][GARBAGE_STATE]=TRANSPORT;//状态为被小车拿走运送中
break;
}
}
}
RFID
管脚 | 对应 | 模式 |
---|---|---|
PG^15 | MISO(SPI数据输入) | 输入,推挽,无上下拉 |
PB^9 | SCK(SPI时钟) | 输出,推挽,无上下拉 |
PG^13 | MOSI(SPI数据输出) | 同上 |
PG^12 | NSS(SPI片选) | 同上 |
PB^8 | RST(复位) | 同上 |
- RC522是13.56MHz非接触通讯高集成度的芯片,通过SPI接口与S50卡通讯,完成卡的识别,读卡,写卡等操作
串口2无线通信
管脚 | 对应 | 模式 |
---|---|---|
PA^3 | USART2_RX | 复用,推挽,上拉 |
PD^5 | USART2_TX | 复用,推挽,上拉 |
- 目前只有小车会给转盘发命令 就一种命令 就是小车的任务完成
/*************************task.c*************************/
u8 car_busy_bit = 0; //小车忙的标志位 0:空闲 1:忙
/*************************STM32H40x_ Usart eval.h*************************/
# define UART2_RACE_MUN 100 //串口2数组大小
u8 uart2_cmd_buf[100]; //命令数组(存储usart2_race_buf的数据)
u8 uart2_cmd_count; //数组索引
u8 usart2_send_buf[100]; //USART2发送数据数组
u8 usart2_race_buf[UART2_RACE_MUN]; //接收USART2接收到的数据
u8 usart2_race_over_bit; //接收一帧完成标志位
u8 usart2_race_count; //数组索引
/*************************STM32H40x_ Usart eval.c*************************/
//USART2初始化
void Uart2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable GPIO clock */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能对应时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
/* Connect PXx to USARTx_Tx*/
GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_USART2); //将对应管脚复用为USART2
/* Connect PXx to USARTx_Rx*/
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
/* Configure USART Tx as alternate function */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //PD^5
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/* Configure USART Rx as alternate function */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PA^3
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure and enable I2C DMA TX Stream interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //串口中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能通道
NVIC_Init(&NVIC_InitStructure); //初始化
/* Enable USART clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);//串口2的时钟使能
//USART2 初始化设置
USART_InitStructure.USART_BaudRate = 115200;//一般设置为115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8bit
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位1位
USART_InitStructure.USART_Parity = USART_Parity_No;//无流控
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收/发送
/* USART2 configuration */
USART_Init(USART2, &USART_InitStructure);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启接收中断
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);//开启空闲总线中断
/* Enable USART2 */
USART_Cmd(USART2, ENABLE);
USART_ClearFlag(USART2, USART_FLAG_TC); //清除发送完成标志位
}
//USART2发送多个字节
void Uart2_send_data(unsigned char *dat, unsigned short len)
{
uart2_race_init();
while(len--)
{
while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);//传输完成
USART2->DR = (*dat & (uint16_t)0x01FF);
dat++;
}
}
//USART2串口中断服务函数
void USART2_IRQHandler(void)
{
u8 Res;
u8 temp;
temp = temp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断
{
Res = USART_ReceiveData(USART2);//(UART4->DR); //读取接收到的数据
usart2_race_buf[usart2_race_count] = Res; //将接收的数据保存到数组
usart2_race_count++;
if(usart2_race_count==UART2_RACE_MUN) //如果接收的数量超过定义的最大大小则为0
{
usart2_race_count = 0;
}
}
if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET) //空闲中断
{
//清除标志位
temp = USART2->SR; //先读USART_SR,然后写入USART_DR
temp = USART2->DR;
usart2_race_over_bit = 1;
}
}
//串口2接收标志位/数组索引清零
void uart2_race_init(void)
{
usart2_race_over_bit = 0;
usart2_race_count = 0;
}
//串口2 接收数据数组+接收标志位/数组索引 清零
void uart2_buf_init(void)
{
u8 i;
for(i=0;i<UART2_RACE_MUN;i++)
{
usart2_race_buf[i] = 0;
}
usart2_race_over_bit = 0;
usart2_race_count = 0;
}
//拷贝串口2的数据到命令数组
void uart2_copy_data(void)
{
u8 i;
for(i=0;i<usart2_race_count;i++)
{
uart2_cmd_buf[i] = usart2_race_buf[i+3];
}
uart2_cmd_count = usart2_race_count; //命令数组的索引等于接收数组的索引
uart2_buf_init(); //接收数组初始化
}
//处理小车发来的命令
void uart2_race_explan(void)
{
//后期补充
if(usart2_race_buf[0]==0x01) //判断数组第一个元素是不是自己的ID 1:转盘
{
if(usart2_race_buf[1]==0xff) //判断数组第二个元素是不是 0xff;这个在小车的代码里有写 0xff表示已完成任务
{
car_busy_bit = 0;//小车忙标志清除
}
}
}
/*
轮盘ID:01
托盘编号 1234
集中站标号 1234
*/
//发送命令给小车(当托盘上有物品时才会通知小车)
void send_to_car_cmd(u8 tuopan,u8 jizhongzhan)
{
u8 cmd_buf[5];
cmd_buf[0] = 2;//发送小车的ID
cmd_buf[1] = 0x01; //第二个字节是需要小车干嘛(暂时定义了:0x01---轮盘->集中站;后期可以添加:0x02---轮盘->垃圾桶等等)
cmd_buf[2] = tuopan; //几号托盘
cmd_buf[3] = jizhongzhan; //到哪个集中站
Uart2_send_data(cmd_buf,4);//发送函数
bell_fun(1); //蜂鸣器响
}
/*************************task.c*************************/
u32 coder_mun_buf[4] = {DIS_MUN1,DIS_MUN2,DIS_MUN3,DIS_MUN4}; //存储走到4个舵机位置脉冲的计数值
//检查小车是否在运送还是在起点
void check_car_fun(void)
{
u8 i;
if(car_busy_bit==0) //当小车处于闲的状态
{
for(i=0;i<TASK_MUN;i++)
{
if(task_buf[i][GARBAGE_STATE]==ARRIVE_OK)//物品在托盘位置上
{
send_to_car_cmd(task_buf[i][GARBAGE_TYPE],task_buf[i][GARBAGE_TYPE]);//发送命令给小车 第一个参数是托盘 第二个参数是集中站
car_busy_bit = 1;//小车忙标志位置1
task_buf[i][GARBAGE_STATE] = SEND_OUT; //已经发送命令给小车
if(task_buf[i][GARBAGE_TYPE]==0)
LCD_ShowString_fun("6:send_car 0,0");//屏上显示(即第一个托盘->到第一个集中箱)
if(task_buf[i][GARBAGE_TYPE]==1)
LCD_ShowString_fun("6:send_car 1,1");//屏上显示(即第二个托盘->到第二个集中箱)
if(task_buf[i][GARBAGE_TYPE]==2)
LCD_ShowString_fun("6:send_car 2,2");//屏上显示(即第三个托盘->到第三个集中箱)
if(task_buf[i][GARBAGE_TYPE]==3)
LCD_ShowString_fun("6:send_car 3,3");//屏上显示(即第四个托盘->到第四个集中箱)
break;
}
}
}
}
//检查物品是否已经走到对应的舵机前(如果是则推)
void coder_count_chack_fun(void)
{
u8 i;
u8 type;
for(i=0;i<TASK_MUN;i++)
{
if(task_buf[i][GARBAGE_STATE]==WAIT_ENGINE) //判断状态是不是等待舵机推
{
type = (u8)task_buf[i][GARBAGE_TYPE]; //读取物品类型然后对应的舵机推(0就第一个舵机,1是第二个舵机,以此类推)
if(task_buf[i][TURNTABLE_COUNT] >= coder_mun_buf[type]) //判断脉冲值是不是>=规定脉冲
{
//printf_fun();
task_buf[i][GARBAGE_STATE] = PUSH; //垃圾状态为推的过程中
switch(type)
{
case 0:
LCD_ShowString_fun("4:tui0");//屏上显示
steering_engine_1_push();//舵机1推
delay_ms(500);
steering_engine_1_shrink();//舵机1收
break;
case 1:
LCD_ShowString_fun("4:tui1");//屏上显示
steering_engine_2_push();//舵机2推
delay_ms(500);
steering_engine_2_shrink();//舵机1收
break;
case 2:
LCD_ShowString_fun("4:tui2");//屏上显示
steering_engine_3_push();//舵机3推
delay_ms(500);
steering_engine_3_shrink();//舵机1收
break;
case 3:
LCD_ShowString_fun("4:tui3");//屏上显示
steering_engine_4_push();//舵机4推
delay_ms(500);
steering_engine_4_shrink();//舵机1收
break;
}
}
}
}
}
/*************************main.c*************************/
//转盘推杆位置(计数值)
# define DIS_MUN1 29500//33046 //走到舵机1位置所需脉冲
# define DIS_MUN2 36500//39132 //走到舵机2位置所需脉冲
# define DIS_MUN3 44000//46091 //走到舵机3位置所需脉冲
# define DIS_MUN4 50500//52348 //走到舵机4位置所需脉冲
转盘注意问题
- STM32F40x_Timer_eval.c 定时器5计算20ms定时步骤
- 这个数组是代码的主要部分
- 注意转盘有时候会接收不了MQTT发送回来的数据,初步怀疑可能服务器不稳定,重启然后等待10秒左右再放物品(需要没有收到心跳时才可以,如果上电后一直收到心跳则继续重启直到没收到心跳再滴卡),如果不行还得顺便把Nano主机那个识别垃圾程序重启一下(反正两个东西多重启即可)