第一次在CSDN分享自己的课设作业,课设实现的内容为使用MSP432和TI的机器人套装利用红外传感器实时检测距离,然后将距离数据通过串口传给ESP8266模块,再使用8266通过WIFI将数据传送给电脑端,同时小车能根据实时距离控制自己的移动,使自己保持在一个安全的范围内。
软硬件准备
硬件准备
-
主板:MSP432
-
WIFI模块:ESP8266
-
红外传感器模块:SHARP 2Y0A21
(注:TI的机器人套装内以包含MSP432和红外传感器模块。)
软件准备
-
Code Compose Studio 9.1(MSP432的开发环境)
-
AiThinkerIDE_V0.5(ESP8266的开发环境)
(注:这两款软件的安装网上都可以找到很多的教程。)
MSP432编程
- 头文件
#include <ti/devices/msp432p4xx/driverlib/driverlib.h>
导入该头文件需要安装432的SDK,可以在TI的官网上下载到:SDK
- 红外传感器的状态定义
// IR Sensor Wall Classification
enum scenario {
Error = 0, //错误状态
CenterRight = 1, //正确状态
Straight = 2, //前进状态
Back = 3 //后退状态
};
- 其他参数定义
#define CENTERMAX 250 // max distance to wall in the front
#define CENTERMIN 150 // min distance to wall in the front
uint8_t TXData1 = 0; //uart send data1
uint8_t TXData2 = 0; //uart send data2
uint8_t RXData = 0; //uart receive data
设置安全区范围为15cm-25cm,并定义2个8位的串口发送数据和1个8位的串口接收数据。
- 串口的参数配置
const eUSCI_UART_ConfigV1 uartConfig =
{
EUSCI_A_UART_CLOCKSOURCE_SMCLK, // SMCLK Clock Source
26, // BRDIV = 26
0, // UCxBRF = 0
111, // UCxBRS = 111
EUSCI_A_UART_NO_PARITY, // No Parity
EUSCI_A_UART_LSB_FIRST, // LSB First
EUSCI_A_UART_ONE_STOP_BIT, // One stop bit
EUSCI_A_UART_MODE, // UART mode
EUSCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION, // Oversampling
EUSCI_A_UART_8_BIT_LEN // 8 bit data length
};
这里设置波特率为115200,使用48MHz的时钟频率,低位先行,1个停止位,0个校验位,1个数据的长度为8bit。(应该和ESP8266的串口参数匹配)
BRDIV、UCxBRF、UCxBRS这三个参数是根据设置的波特率和时钟频率来计算的,官方提供了一个网页版的计算工具: 计算工具
- 串口初始化
// initialize clock
//clock_init_48MHz();
/* Selecting P1.2 and P1.3 in UART mode*/
MAP_GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,
GPIO_PIN2 | GPIO_PIN3, GPIO_PRIMARY_MODULE_FUNCTION);
/* Setting DCO to 48MHz (upping Vcore) */
FlashCtl_setWaitState(FLASH_BANK0, 1);
FlashCtl_setWaitState(FLASH_BANK1, 1);
MAP_PCM_setCoreVoltageLevel(PCM_VCORE1);
CS_setDCOCenteredFrequency(CS_DCO_FREQUENCY_48);
/* Configuring UART Module */
MAP_UART_initModule(EUSCI_A0_BASE, &uartConfig);
/* Enable UART module */
MAP_UART_enableModule(EUSCI_A0_BASE);
/* Enabling interrupts */
MAP_UART_enableInterrupt(EUSCI_A0_BASE, EUSCI_A_UART_RECEIVE_INTERRUPT);
MAP_Interrupt_enableInterrupt(INT_EUSCIA0);
MAP_Interrupt_enableSleepOnIsrExit();
设置P1.2引脚为TXD和P1.3引脚为RXD,DCO=时钟频率,并为串口分配寄存器,然后启动这个串口和中断。
- 读取红外传感器的数据并用串口发送给ESP8266
// the sending data is 8bit or 16bit
if(ir_data.center<255)
{
TXData1 = ir_data.center&0xffffff;
TXData2 = 0;
MAP_UART_transmitData(EUSCI_A0_BASE, TXData1);
MAP_UART_transmitData(EUSCI_A0_BASE, TXData2);
}
else
{
TXData1 = ir_data.center>>8&0xffff;
TXData2 = ir_data.center&0xffffff;
MAP_UART_transmitData(EUSCI_A0_BASE, TXData1);
MAP_UART_transmitData(EUSCI_A0_BASE, TXData2);
}
MAP_Interrupt_enableSleepOnIsrExit();
MAP_PCM_gotoLPM0InterruptSafe();
如果距离小于255mm,则只需1个8bit的TXData,因为我设置最高只能检测到800mm,所以最多只需要2个8bit的TXData,发送完数据就进入了等待中断的状态中。
- 中断函数
/* EUSCI A0 UART ISR - receive data from ESP8266 */
void EUSCIA0_IRQHandler(void)
{
uint32_t status = MAP_UART_getEnabledInterruptStatus(EUSCI_A0_BASE);
if(status & EUSCI_A_UART_RECEIVE_INTERRUPT_FLAG)
{
RXData = MAP_UART_receiveData(EUSCI_A0_BASE);
MAP_Interrupt_disableSleepOnIsrExit();
}
}
当ESP8266发送来应答数据时,就进入到了串口中断函数中,接收完应答数据后,程序退出中断,开始控制小车运动。
- 根据距离判断小车状态
scenario_t classify(uint32_t Left, uint32_t Center, uint32_t Right){
if((Center<0)||(Center>800)) return Error;
if(Center>=CENTERMAX){
return Straight;
}
if(Center<=CENTERMIN){
return Back;
}
if(Center<CENTERMAX && Center>CENTERMIN){
return CenterRight;
}
return Error; // should happen
}
IR_class = classify(ir_data.left, ir_data.center, ir_data.right);
- 根据小车的状态控制小车运动
switch(IR_class)
{
case Straight:
// Move forward with both motors equal
launchpad_output(LP_GREEN);
drive_straight();
break;
case Back:
launchpad_output(LP_BLUE);
drive_back();
break;
case CenterRight:
launchpad_output(LP_RED);
motor_stop();
default:
// This case should not occur, but is included to indicate errors
launchpad_output(LP_PINK);
motor_stop();
break;
}
小车根据对应的4个状态作出反应:①前进状态:亮绿灯,小车前进;②后退状态:亮蓝灯,小车后退;③正确状态:亮红灯,小车静止;④错误状态:亮粉灯,小车静止。
- 以上步骤的6、8、9均包含在循环中,小车可以不停地判断距离,然后移动,使自己处于安全范围中。
ESP8266编程
- 相关定义
// 头文件引用
//==================================================================================
#include "user_config.h" // 用户配置
#include "c_types.h" // 变量类型
#include "eagle_soc.h" // GPIO函数、宏定义
#include "ets_sys.h" // 回调函数
#include "os_type.h" // os_XXX
#include "osapi.h" // os_XXX、软件定时器
#include "ip_addr.h" // 被"espconn.h"使用。在"espconn.h"开头#include"ip_addr.h"或#include"ip_addr.h"放在"espconn.h"之前
#include "espconn.h" // TCP/UDP接口
#include "user_interface.h" // 系统接口、system_param_xxx接口、WIFI、RateContro
#include "mem.h" // 内存申请等函数
#include "driver/uart.h" //串口
#include "driver/oled.h" //OLED
//==================================================================================
// 宏定义
//==================================================================================
#define ProjectName "MSP432_ESP8266_WIFI_TCP" // 工程名宏定义
#define ESP8266_AP_SSID "ESP8266_WHX" // 创建的WIFI名
#define ESP8266_AP_PASS "123456" // 创建的WIFI密码
#define LED_ON GPIO_OUTPUT_SET(GPIO_ID_PIN(4),0) // LED亮
#define LED_OFF GPIO_OUTPUT_SET(GPIO_ID_PIN(4),1) // LED灭
//==================================================================================
// 全局变量
//==================================================================================
uint8_t TCPSendData[4]={0}; // TCP发送的数据
os_timer_t OS_Timer_1; // 定义软件定时器,用于等待WIFI连接
os_timer_t OS_Timer_2; // 定义软件定时器,用于定时发送数据
struct espconn ST_NetCon; // 网络连接结构体
struct espconn * dataarg; // TCP发送数据的指针
//==================================================================================
以下编程涉及到SDK编程,需要提前到乐鑫官网下载esp8266的SDK,否则无法导入头文件"driver/uart.h"和使用串口相关函数。
ESP8266_NONOS_SDK-3.0【提取码:b4y6】
- 初始化设置
void ICACHE_FLASH_ATTR user_init(void)
{
partition_item_t partition_item;
if (!system_partition_get_item(SYSTEM_PARTITION_CUSTOMER_PRIV_PARAM, &partition_item)) {
os_printf("Get partition information fail\n");
}
uart_init(115200,115200); //初始化串口波特率为115200
os_delay_us(10000); // 等待串口稳定
// OLED显示初始化
//------------------------------------------------------------
OLED_Init(); // OLED初始化
OLED_ShowString(0,0,"ESP8266 = Client"); // ESP8266模式
OLED_ShowString(0,2,"IP:"); // ESP8266_IP地址
OLED_ShowString(0,4,"Remote = Server"); // 远端主机模式
OLED_ShowString(0,6,"IP:"); // 远端主机IP地址
//------------------------------------------------------------
LED_Init_JX(); // LED初始化
ESP8266_AP_Init_JX(); // 初始化ESP8266_AP模式
OS_Timer_1_Init_JX(10000,0); // 10秒定时(一次)
}
- AP模式的初始化
void ICACHE_FLASH_ATTR ESP8266_AP_Init_JX()
{
struct softap_config AP_Config; // AP参数结构体
wifi_set_opmode(0x02); // 设置为AP模式,并保存到Flash
// 结构体赋值(注意:【服务集标识符/密码】须设为字符串形式)
//--------------------------------------------------------------------------------------
os_memset(&AP_Config, 0, sizeof(struct softap_config)); // AP参数结构体 = 0
os_strcpy(AP_Config.ssid,ESP8266_AP_SSID); // 设置SSID(将字符串复制到ssid数组)
os_strcpy(AP_Config.password,ESP8266_AP_PASS); // 设置密码(将字符串复制到password数组)
AP_Config.ssid_len=os_strlen(ESP8266_AP_SSID); // 设置ssid长度(和SSID的长度一致)
AP_Config.channel=1; // 通道号1~13
AP_Config.authmode=AUTH_WPA2_PSK; // 设置加密模式
AP_Config.ssid_hidden=0; // 不隐藏SSID
AP_Config.max_connection=4; // 最大连接数
AP_Config.beacon_interval=100; // 信标间隔时槽100~60000 ms
wifi_softap_set_config(&AP_Config); // 设置soft-AP,并保存到Flash
}
在AP模式初始化函数中,对AP模式的各项参数进行配置,包括SSID,密码,加密模式和最大连接数等。初始化完成后,启动一个10秒的定时器,用于等待WIFI连接。
- 定时器1的回调函数
void ICACHE_FLASH_ATTR OS_Timer_1_cb(void)
{
struct ip_info ST_ESP8266_IP; // IP信息结构体
u8 ESP8266_IP[4]; // 点分十进制形式保存IP
wifi_get_ip_info(SOFTAP_IF,&ST_ESP8266_IP); // 查询AP模式下ESP8266的IP地址
if(ST_ESP8266_IP.ip.addr!=0 ) // ESP8266成功获取到IP地址
{
ESP8266_IP[0] = ST_ESP8266_IP.ip.addr; // 点分十进制IP的第一个数 <==> addr低八位
ESP8266_IP[1] = ST_ESP8266_IP.ip.addr>>8; // 点分十进制IP的第二个数 <==> addr次低八位
ESP8266_IP[2] = ST_ESP8266_IP.ip.addr>>16; // 点分十进制IP的第三个数 <==> addr次高八位
ESP8266_IP[3] = ST_ESP8266_IP.ip.addr>>24; // 点分十进制IP的第四个数 <==> addr高八位
// 显示ESP8266的IP地址
//-----------------------------------------------------------------------------------------------
//os_printf("ESP8266_IP = %d.%d.%d.%d\n",ESP8266_IP[0],ESP8266_IP[1],ESP8266_IP[2],ESP8266_IP[3]);
OLED_ShowIP(24,2,ESP8266_IP); // OLED显示ESP8266的IP地址
//-----------------------------------------------------------------------------------------------
os_timer_disarm(&OS_Timer_1); // 关闭定时器
ESP8266_NetCon_Init_JX(); // 初始化网络连接(TCP通信)
}
}
定时器1的10s定时一到就进入到该回调函数中。
- 初始化网络连接(TCP通信)
void ICACHE_FLASH_ATTR ESP8266_NetCon_Init_JX()
{
// 结构体赋值
//--------------------------------------------------------------------------
ST_NetCon.type = ESPCONN_TCP ; // 设置为TCP协议
ST_NetCon.proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); // 开辟内存
// 此处需要设置目标IP/端口(ESP8266作为Client,需要预先知道Server的IP/端口)
//-------------------------------------------------------------------------
ST_NetCon.proto.tcp->local_port = 8266 ; // 设置本地端口
ST_NetCon.proto.tcp->remote_port = 8888; // 设置目标端口
ST_NetCon.proto.tcp->remote_ip[0] = 192; // 设置目标IP地址
ST_NetCon.proto.tcp->remote_ip[1] = 168;
ST_NetCon.proto.tcp->remote_ip[2] = 4;
ST_NetCon.proto.tcp->remote_ip[3] = 2;
// 注册连接成功回调函数、异常断开回调函数
//--------------------------------------------------------------------------------------------------
espconn_regist_connectcb(&ST_NetCon, ESP8266_TCP_Connect_Cb_JX); // 注册TCP连接成功建立的回调函数
espconn_regist_reconcb(&ST_NetCon, ESP8266_TCP_Break_Cb_JX); // 注册TCP连接异常断开的回调函数
// 连接 TCP server
//----------------------------------------------------------
espconn_connect(&ST_NetCon); // 连接TCP-server
}
在该函数中,设置网络通信协议方式为TCP,同时设置本地端口号和目标服务器的端口号、IP地址,设置完成后,注册TCP连接成功和异常断开的回调函数,然后就可以启动TCP连接服务。
- TCP连接成功
void ICACHE_FLASH_ATTR ESP8266_TCP_Connect_Cb_JX(void *arg)
{
dataarg = (struct espconn *)arg;
espconn_regist_sentcb((struct espconn *)arg, ESP8266_WIFI_Send_Cb_JX); // 注册网络数据发送成功的回调函数
espconn_regist_recvcb((struct espconn *)arg, ESP8266_WIFI_Recv_Cb_JX); // 注册网络数据接收成功的回调函数
espconn_regist_disconcb((struct espconn *)arg,ESP8266_TCP_Disconnect_Cb_JX); // 注册成功断开TCP连接的回调函数
OS_Timer_2_Init_JX(1000,1);
}
如果TCP连接成功,就启动定时器2,这是一个重复的1s定时。
- 定时器2的回调函数
void ICACHE_FLASH_ATTR OS_Timer_2_cb(void)
{
uint8 idx=0;
//数据处理
if(uartRxBuffer[1]==0x00)
{
change_to_dec0(0); //十六进制转十进制(红外数据只有8位时)
}
else
{
change_to_dec1(1); //十六进制转十进制(红外数据有16位时)
}
espconn_send(dataarg,&TCPSendData[0],os_strlen(TCPSendData));//发送TCP数据
//清空TCP发送数据缓存
for(idx=0;idx<4;idx++)
{
TCPSendData[idx]=0;
idx++;
}
//清空串口接收数据缓存
for(idx=0;idx<32;idx++)
{
uartRxBuffer[idx]=0;
idx++;
}
//向MSP432回复1,表示已成功接收到数据
os_printf("1");
}
在WIFI发送数据前,要先对串口接收的数据进行处理,否则服务器端接收到的数据无法正常阅读。
- 距离数据类型的转换
void change_to_dec0(uint8 idx)
{
int a=0;
if((uartRxBuffer[idx]&0x01)==0x01)
a+=1;
if((uartRxBuffer[idx]&0x02)==0x02)
a+=2;
if((uartRxBuffer[idx]&0x04)==0x04)
a+=4;
if((uartRxBuffer[idx]&0x08)==0x08)
a+=8;
if((uartRxBuffer[idx]&0x10)==0x10)
a+=16;
if((uartRxBuffer[idx]&0x20)==0x20)
a+=32;
if((uartRxBuffer[idx]&0x40)==0x40)
a+=64;
if((uartRxBuffer[idx]&0x80)==0x80)
a+=128;
int a1 = a%10;
int a2 = ((a-a1)%100)/10;
int a3 = a/100;
change_to_char(0,a3); //十进制ASCII编码
change_to_char(1,a2);
change_to_char(2,a1);
}
void change_to_char(uint8 idx,int a)
{
if(a==0)
TCPSendData[idx]=0x30;
if(a==1)
TCPSendData[idx]=0x31;
if(a==2)
TCPSendData[idx]=0x32;
if(a==3)
TCPSendData[idx]=0x33;
if(a==4)
TCPSendData[idx]=0x34;
if(a==5)
TCPSendData[idx]=0x35;
if(a==6)
TCPSendData[idx]=0x36;
if(a==7)
TCPSendData[idx]=0x37;
if(a==8)
TCPSendData[idx]=0x38;
if(a==9)
TCPSendData[idx]=0x39;
}
红外传感器测到的数据,将以十六进制的形式通过串口发送给ESP8266,如果不进行数据类型的转换,最后在服务器端看到的将是十六进制的数据,不能直观展示距离大小。
注:16位数据的转换与8位的类似,这里不再贴出。
- 串口接收函数的修改
for(idx=0;idx<fifo_len;idx++) {
d_tmp = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF;
uartRxBuffer[idx] = d_tmp;
}
原本的串口接收函数uart_recvTask,一次接收一位值,并且接收一个就把该值再通过串口发送出去,我这里使用一个数组将接收到值储存起来,方便后续使用。
调试
MSP432和ESP8266的连接
结尾
这个课设由本人独立完成,大部分参考了官方例程和一些网络教程,如果有问题,欢迎大家来交流!因为具体代码略长不能全部贴出,完整代码我已上传至下方连接:使用MSP432-ESP8266实现小车红外测距和数据传输