基于RK3399&ESP8285自动售货柜项目—②MP08开发板端代码详解
本系列文章将详细讲解该基于RK3399及ESP8285自动售货柜的完整实现方法,从硬件连接到网络通信再到软件实现,本产品所用开发板为RK3399以及MP08_2019/11/03 , 如有疑问与见解,可随时留言或评论
本系列以往文章列表:
文章目录
上一篇我们讲解了如何搭建AiThinkIDE开发环境以及编写和烧录程序,那么这一篇我们来详细讲解本项目在MP08开发板上的代码实现原理,如果你手上也有这块板子,可以完全按照本系列文章来进行项目的实现
一、ESP开发代码结构
由于本项目不用过多深入学习ESP_SDK,故只用知道如何利用结构框架去编写想要的功能即可
我们先来看看官方例程代码:
/*跟其他C代码一样,这里放头文件,注意:AiThinkIDE没有标准C库函数,故没有如stdio.h 或者 string。h等,不要包含他们*/
#include "driver/uart.h" //串口0需要的头文件
#include "osapi.h" //串口1需要的头文件
#include "user_interface.h" //WIFI连接需要的头文件
#include "gpio.h" //端口控制需要的头文件
#include "user_config.h"
/*这一长段条件编译不用管他是干嘛的,写程序放进来就行*/
#if ((SPI_FLASH_SIZE_MAP == 0) || (SPI_FLASH_SIZE_MAP == 1))
#error "The flash map is not supported"
#elif (SPI_FLASH_SIZE_MAP == 2)
#define SYSTEM_PARTITION_OTA_SIZE 0x6A000
#define SYSTEM_PARTITION_OTA_2_ADDR 0x81000
#define SYSTEM_PARTITION_RF_CAL_ADDR 0xfb000
#define SYSTEM_PARTITION_PHY_DATA_ADDR 0xfc000
#define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR 0xfd000
#elif (SPI_FLASH_SIZE_MAP == 3)
#define SYSTEM_PARTITION_OTA_SIZE 0x6A000
#define SYSTEM_PARTITION_OTA_2_ADDR 0x81000
#define SYSTEM_PARTITION_RF_CAL_ADDR 0x1fb000
#define SYSTEM_PARTITION_PHY_DATA_ADDR 0x1fc000
#define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR 0x1fd000
#elif (SPI_FLASH_SIZE_MAP == 4)
#define SYSTEM_PARTITION_OTA_SIZE 0x6A000
#define SYSTEM_PARTITION_OTA_2_ADDR 0x81000
#define SYSTEM_PARTITION_RF_CAL_ADDR 0x3fb000
#define SYSTEM_PARTITION_PHY_DATA_ADDR 0x3fc000
#define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR 0x3fd000
#elif (SPI_FLASH_SIZE_MAP == 5)
#define SYSTEM_PARTITION_OTA_SIZE 0x6A000
#define SYSTEM_PARTITION_OTA_2_ADDR 0x101000
#define SYSTEM_PARTITION_RF_CAL_ADDR 0x1fb000
#define SYSTEM_PARTITION_PHY_DATA_ADDR 0x1fc000
#define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR 0x1fd000
#elif (SPI_FLASH_SIZE_MAP == 6)
#define SYSTEM_PARTITION_OTA_SIZE 0x6A000
#define SYSTEM_PARTITION_OTA_2_ADDR 0x101000
#define SYSTEM_PARTITION_RF_CAL_ADDR 0x3fb000
#define SYSTEM_PARTITION_PHY_DATA_ADDR 0x3fc000
#define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR 0x3fd000
#else
#error "The flash map is not supported"
#endif
void ICACHE_FLASH_ATTR user_init(void)
{
//相当于main函数,一般放各类软硬件的初始化代码,为程序入口
}
//这个结构体定义也必须要,不用管他是干嘛的,SDK3.0规定必须要这一段
static const partition_item_t at_partition_table[] = {
{ SYSTEM_PARTITION_BOOTLOADER, 0x0, 0x1000},
{ SYSTEM_PARTITION_OTA_1, 0x1000, SYSTEM_PARTITION_OTA_SIZE},
{ SYSTEM_PARTITION_OTA_2, SYSTEM_PARTITION_OTA_2_ADDR, SYSTEM_PARTITION_OTA_SIZE},
{ SYSTEM_PARTITION_RF_CAL, SYSTEM_PARTITION_RF_CAL_ADDR, 0x1000},
{ SYSTEM_PARTITION_PHY_DATA, SYSTEM_PARTITION_PHY_DATA_ADDR, 0x1000},
{ SYSTEM_PARTITION_SYSTEM_PARAMETER, SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR, 0x3000},
};
//这一个函数也要放进来,直接复制就行
void ICACHE_FLASH_ATTR user_pre_init(void)
{
if(!system_partition_table_regist(at_partition_table, sizeof(at_partition_table)/sizeof(at_partition_table[0]),SPI_FLASH_SIZE_MAP)) {
os_printf("system_partition_table_regist fail\r\n");
while(1);
}
}
可以看到,整个代码结构就由这几部分构成,我们主要编写user_init函数里的内容,并且也可以自己定义想要的函数,函数名前的ICACHE_FLASH_ATTR表示的是函数地址存储位置在flash里,最好都加上
编写代码我们还需要去下载几个官方文档,里面有各种API函数用法介绍,本质跟STM32库函数差不多
ESP8266文档中心 | 安信可科技 (ai-thinker.com)
二、本项目MP09开发板端代码实现
2.1MP08开发板原理图
该原理图为我用万用表徒手测并绘制,仅供引脚走线参考使用,电阻电容等均未调整数值
通过原理图我们可以看到
- 每个减速电机得三个引脚分别接在了L、C、H线上
- 其中L引脚由原理图中间的74HC595D控制器的Q0-Q7控制
- C引脚由ESP_IO12控制
- H引脚由ESP_IO2控制
知道了电机由哪几个引脚控制,我们就可以往上进行推理,可知要想选择开启哪个电机就得控制74HC595D的Q0-Q7哪个引脚,所以我们需要找到74HC595D芯片手册来查看如何控制
2.274HC595D驱动方式
通过数据手册,我们可以知道,要想给Q0-Q7输出想要的信号值,就得在SER串行数据输入端输入相应的值并锁存到锁存寄存器里
所以,驱动方式总结如下:
- 设置SER串行数据输入引脚电平
- 拉高SCK,使SER数据被移位进入八位移位寄存器
- 重复以上两个步骤共8次,发送8位数据(1Byte)
- 拉高RCK,使得八位移位寄存器中的数据被送到八位锁存器里
- 锁存器里的数据将会自动发送到Q0-Q7各个引脚
提示:当我们想驱动Q3和Q5引脚输出高电平,我们的8位数据就应该为00101000,也就是0x28
根据以上信息,我们可以写出以下代码:
在看代码前,我们需要了解几个官方API函数:
- os_printf:跟标准C库函数printf一样,但这里必须用os_printf
- os_delay_us:官方给的专用微秒延时API
- system_soft_wdt_feed:看门狗喂狗函数,不喂好像也没事,最好加上,安信可官方说默认开启了看门狗
- GPIO_OUTPUT_SET(GPIO_ID_PIN(x),1):GPIO口输出电平控制宏,x为引脚编号,后面的1为高电平,0则为低电平
- PIN_FUNC_SELECT:引脚功能选择宏函数
- API函数及其具体用法可参照ESP8266_SDK_API参考指南
void delay_ms(uint32_t ms)
{
while(ms--)
{
os_delay_us(1000);//os_delay_us为官方API
system_soft_wdt_feed();//喂狗
}
}
//发送数据函数
void hc595d_send_byte(uint8_t data)
{
uint8_t i;
for(i = 0;i < 8;i++)
{
if(data & (0x80>>i))
{
GPIO_OUTPUT_SET(GPIO_ID_PIN(4),1);//IO4高电平
}
else
{
GPIO_OUTPUT_SET(GPIO_ID_PIN(4),0);//IO4低电平
}
GPIO_OUTPUT_SET(GPIO_ID_PIN(13),0);//先拉低
os_delay_us(5);//适当延时
GPIO_OUTPUT_SET(GPIO_ID_PIN(13),1);//上升沿将一位数据移入移位寄存器
os_delay_us(5);//适当延时
}
}
//锁存函数
void hc595d_latch_data(void)
{
GPIO_OUTPUT_SET(GPIO_ID_PIN(15),0);//先拉低
os_delay_us(5);//适当延时
GPIO_OUTPUT_SET(GPIO_ID_PIN(15),1);//上升沿将数据送入锁存寄存器
os_delay_us(5);//适当延时
}
void driver_motor(uint8_t j)
{
/*
* HC595D ESP_IO12 ESP_IO2
* MOTOR_L MOTOR_C MOTOR_H 转向
* 1 1 0 逆时针
* 1 0 0 x
* 1 0 1 x
* 1 1 1 逆时针
* */
hc595d_send_byte(j);//发送1byte数据
hc595d_latch_data();//锁存
GPIO_OUTPUT_SET(GPIO_ID_PIN(12),1);
GPIO_OUTPUT_SET(GPIO_ID_PIN(2),0);
GPIO_OUTPUT_SET(GPIO_ID_PIN(5),j%2);//led监视
os_printf("j:%2d on\r\n",j);
//售货机转1秒就停止
delay_ms(1000);
GPIO_OUTPUT_SET(GPIO_ID_PIN(12),0);
}
void user_init(void)
{
system_soft_wdt_feed();//喂狗
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO4_U,FUNC_GPIO4);//IO4-SER
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U,FUNC_GPIO15);//IO15-RCK
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U,FUNC_GPIO13);//IO13-SCK
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U,FUNC_GPIO12);//IO12-MOTOR_C
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U,FUNC_GPIO2);//IO2-MOTOR_H
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO5_U,FUNC_GPIO5);//IO5-LED
}
以上就是如何去驱动74HC595D来控制MP08开发板上8个电机的方法
现在,我们来考虑售货机通信方面的问题,从这里开始呢,就可以根据不同的方式来去控制MP08售货机板子
-
第一种方法:利用RK3399和MP08进行TCP连接,这样我们可以在RK3399板子上对MP08进行可视化操作
-
第二种方法:无需其他外设,只需一部手机,我们可以在手机端连接MP08上的ESP8285,进行操控
-
第三种方法:MP08上的ESP8285连接WiFi,然后连接上云服务器,手机端通过云服务器来进行远程发送数据去控制MP08
……方法非常多,远不止这几种,我们这里采用第一种方法
2.3MP08—TCP_Client代码实现
这里我们将MP08上的ESP8285作为TCP客户端连接RK3399的TCP服务端、
tcp_client.c
#include "user_config.h"
#include "driver/uart.h" //串口0需要的头文件
#include "osapi.h" //串口1需要的头文件
#include "user_interface.h" //WIFI连接需要的头文件
#include "espconn.h"//TCP连接需要的头文件
#include "mem.h" //系统操作需要的头文件
#include "gpio.h"
os_timer_t checkTimer_wifistate;
struct espconn user_tcp_conn;
uint8_t my_atoi(char *pdata,unsigned short len)
{
uint8_t ret = 0;
os_printf("len = %d\r\n",len);
while(len--)
{
ret *= 10;
ret += *pdata++ - '0';
}
os_printf("ret = %d\r\n",ret);
return ret;
}
//将输入的点击编号转化为二进制
//如驱动1号电机Q0,则为00000001->0x01
//如驱动8号电机Q7,则为10000000->0x80
uint8_t to_binaray(uint8_t num)
{
uint8_t ret = 0;
switch(num)
{
case 1:ret = 1;break;
case 2:ret = 2;break;
case 3:ret = 4;break;
case 4:ret = 8;break;
case 5:ret = 16;break;
case 6:ret = 32;break;
case 7:ret = 64;break;
case 8:ret = 128;break;
default:break;
}
return ret;
}
//tcp发送数据回调函数
void ICACHE_FLASH_ATTR user_tcp_sent_cb(void *arg) //发送
{
os_printf("发送数据成功!");
}
//tcp断开后的回调函数
void ICACHE_FLASH_ATTR user_tcp_discon_cb(void *arg) //断开
{
os_printf("断开连接成功!");
}
//tcp收到数据后的回调函数
void ICACHE_FLASH_ATTR user_tcp_recv_cb(void *arg, //接收
char *pdata, unsigned short len) {
os_printf("收到数据:%s\r\n", pdata);
driver_motor(to_binaray(my_atoi(pdata,len)));//电机驱动
espconn_sent((struct espconn *) arg, "0", strlen("0"));
}
//注册 TCP 连接发生异常断开时的回调函数,可以在回调函数中进行重连
void ICACHE_FLASH_ATTR user_tcp_recon_cb(void *arg, sint8 err)
{
os_printf("连接错误,错误代码为%d\r\n", err);
espconn_connect((struct espconn *) arg);
}
//注册 TCP 连接成功建立后的回调函数
void ICACHE_FLASH_ATTR user_tcp_connect_cb(void *arg)
{
struct espconn *pespconn = arg;
espconn_regist_recvcb(pespconn, user_tcp_recv_cb); //接收
espconn_regist_sentcb(pespconn, user_tcp_sent_cb); //发送
espconn_regist_disconcb(pespconn, user_tcp_discon_cb); //断开
espconn_sent(pespconn, "8226", strlen("8226"));
os_printf("连接服务器成功!\r\n");
}
void ICACHE_FLASH_ATTR my_station_init(struct ip_addr *remote_ip,
struct ip_addr *local_ip, int remote_port) {
user_tcp_conn.proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp)); //分配空间
user_tcp_conn.type = ESPCONN_TCP; //设置类型为TCP协议
os_memcpy(user_tcp_conn.proto.tcp->local_ip, local_ip, 4);
os_memcpy(user_tcp_conn.proto.tcp->remote_ip, remote_ip, 4);
user_tcp_conn.proto.tcp->local_port = espconn_port(); //本地端口
user_tcp_conn.proto.tcp->remote_port = remote_port; //目标端口
//注册连接成功回调函数和重新连接回调函数
espconn_regist_connectcb(&user_tcp_conn, user_tcp_connect_cb);//注册 TCP 连接成功建立后的回调函数
espconn_regist_reconcb(&user_tcp_conn, user_tcp_recon_cb);//注册 TCP 连接发生异常断开时的回调函数,可以在回调函数中进行重连
//启用连接
espconn_connect(&user_tcp_conn);
}
//连接局域网wifi,连接tcp服务器
void Check_WifiState(void) {
uint8 getState;
getState = wifi_station_get_connect_status();
//查询 ESP8266 WiFi station 接口连接 AP 的状态
if (getState == STATION_GOT_IP) {
os_printf("WIFI连接成功!\r\n");
os_timer_disarm(&checkTimer_wifistate);
struct ip_info info;
const char remote_ip[4] = { 192, 168, 31, 116 };//目标IP地址
wifi_get_ip_info(STATION_IF, &info); //查询 WiFi模块的 IP 地址
my_station_init((struct ip_addr *) remote_ip, &info.ip, 6000);//连接到目标服务器的6000端口
}
}
void tcp_client_init() //初始化
{
wifi_set_opmode(0x01); //设置为STATION模式
struct station_config stationConf;
os_strcpy(stationConf.ssid, "Xiaomi_9908"); //改成你自己的 路由器的用户名
os_strcpy(stationConf.password, "20010624w"); //改成你自己的 路由器的密码
wifi_station_set_config(&stationConf); //设置WiFi station接口配置,并保存到 flash
wifi_station_connect(); //连接路由器
os_timer_disarm(&checkTimer_wifistate); //取消定时器定时
os_timer_setfn(&checkTimer_wifistate, (os_timer_func_t *) Check_WifiState,
NULL); //设置定时器回调函数
os_timer_arm(&checkTimer_wifistate, 500, 1); //启动定时器,单位:毫秒
}
在此代码中,如有不了解的API函数,可随时查阅ESP8266_SDK_API参考指南,里面对每个API都有用法讲解,但不够清晰,可参照该代码进行理解
注意:
- WiFi名和密码需要更改成和RK3399同局域网的WiFi
- 目标IP地址需要更改为RK3399的ip地址,可输入ipconfig查询
2.4MP08完整代码
将以下代码替换到上一篇文章所创建工程的user_main.c
#include "driver/uart.h" //串口0需要的头文件
#include "osapi.h" //串口1需要的头文件
#include "user_interface.h" //WIFI连接需要的头文件
#include "gpio.h" //端口控制需要的头文件
#include "user_config.h"
#if ((SPI_FLASH_SIZE_MAP == 0) || (SPI_FLASH_SIZE_MAP == 1))
#error "The flash map is not supported"
#elif (SPI_FLASH_SIZE_MAP == 2)
#define SYSTEM_PARTITION_OTA_SIZE 0x6A000
#define SYSTEM_PARTITION_OTA_2_ADDR 0x81000
#define SYSTEM_PARTITION_RF_CAL_ADDR 0xfb000
#define SYSTEM_PARTITION_PHY_DATA_ADDR 0xfc000
#define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR 0xfd000
#elif (SPI_FLASH_SIZE_MAP == 3)
#define SYSTEM_PARTITION_OTA_SIZE 0x6A000
#define SYSTEM_PARTITION_OTA_2_ADDR 0x81000
#define SYSTEM_PARTITION_RF_CAL_ADDR 0x1fb000
#define SYSTEM_PARTITION_PHY_DATA_ADDR 0x1fc000
#define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR 0x1fd000
#elif (SPI_FLASH_SIZE_MAP == 4)
#define SYSTEM_PARTITION_OTA_SIZE 0x6A000
#define SYSTEM_PARTITION_OTA_2_ADDR 0x81000
#define SYSTEM_PARTITION_RF_CAL_ADDR 0x3fb000
#define SYSTEM_PARTITION_PHY_DATA_ADDR 0x3fc000
#define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR 0x3fd000
#elif (SPI_FLASH_SIZE_MAP == 5)
#define SYSTEM_PARTITION_OTA_SIZE 0x6A000
#define SYSTEM_PARTITION_OTA_2_ADDR 0x101000
#define SYSTEM_PARTITION_RF_CAL_ADDR 0x1fb000
#define SYSTEM_PARTITION_PHY_DATA_ADDR 0x1fc000
#define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR 0x1fd000
#elif (SPI_FLASH_SIZE_MAP == 6)
#define SYSTEM_PARTITION_OTA_SIZE 0x6A000
#define SYSTEM_PARTITION_OTA_2_ADDR 0x101000
#define SYSTEM_PARTITION_RF_CAL_ADDR 0x3fb000
#define SYSTEM_PARTITION_PHY_DATA_ADDR 0x3fc000
#define SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR 0x3fd000
#else
#error "The flash map is not supported"
#endif
void delay_ms(uint32_t ms)
{
while(ms--)
{
os_delay_us(1000);
system_soft_wdt_feed();//喂狗
}
}
//发送数据函数
void hc595d_send_byte(uint8_t data)
{
uint8_t i;
for(i = 0;i < 8;i++)
{
if(data & (0x80>>i))
{
GPIO_OUTPUT_SET(GPIO_ID_PIN(4),1);//IO4高电平
}
else
{
GPIO_OUTPUT_SET(GPIO_ID_PIN(4),0);//IO4低电平
}
GPIO_OUTPUT_SET(GPIO_ID_PIN(13),0);//先拉低
os_delay_us(5);//适当延时
GPIO_OUTPUT_SET(GPIO_ID_PIN(13),1);//上升沿将一位数据移入移位寄存器
os_delay_us(5);//适当延时
}
}
//锁存函数
void hc595d_latch_data(void)
{
GPIO_OUTPUT_SET(GPIO_ID_PIN(15),0);//先拉低
os_delay_us(5);//适当延时
GPIO_OUTPUT_SET(GPIO_ID_PIN(15),1);//上升沿将数据送入锁存寄存器
os_delay_us(5);//适当延时
}
void driver_motor(uint8_t j)
{
/*
* 译码器 ESP_IO12 ESP_IO2
* MOTOR_L MOTOR_C MOTOR_H 转向
* 1 1 0 逆时针
* 1 0 0 x
* 1 0 1 x
* 1 1 1 逆时针
* */
hc595d_send_byte(j);//发送1byte数据
hc595d_latch_data();//锁存
GPIO_OUTPUT_SET(GPIO_ID_PIN(12),1);
GPIO_OUTPUT_SET(GPIO_ID_PIN(2),0);
GPIO_OUTPUT_SET(GPIO_ID_PIN(5),j%2);//led监视
os_printf("j:%2d on\r\n",j);
delay_ms(1000);
GPIO_OUTPUT_SET(GPIO_ID_PIN(12),0);
}
void user_init(void)
{
system_soft_wdt_feed();//喂狗
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO4_U,FUNC_GPIO4);//IO4-SER
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U,FUNC_GPIO15);//IO15-RCK
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U,FUNC_GPIO13);//IO13-SCK
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U,FUNC_GPIO12);//IO12-MOTOR_C
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U,FUNC_GPIO2);//IO2-MOTOR_H
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO5_U,FUNC_GPIO5);//IO5-LED
//8266作为TCP客户端,目标端口为6000,IP地址见串口打印,此要先获取Tcp服务端的ip地址
tcp_client_init();
}
static const partition_item_t at_partition_table[] = {
{ SYSTEM_PARTITION_BOOTLOADER, 0x0, 0x1000},
{ SYSTEM_PARTITION_OTA_1, 0x1000, SYSTEM_PARTITION_OTA_SIZE},
{ SYSTEM_PARTITION_OTA_2, SYSTEM_PARTITION_OTA_2_ADDR, SYSTEM_PARTITION_OTA_SIZE},
{ SYSTEM_PARTITION_RF_CAL, SYSTEM_PARTITION_RF_CAL_ADDR, 0x1000},
{ SYSTEM_PARTITION_PHY_DATA, SYSTEM_PARTITION_PHY_DATA_ADDR, 0x1000},
{ SYSTEM_PARTITION_SYSTEM_PARAMETER, SYSTEM_PARTITION_SYSTEM_PARAMETER_ADDR, 0x3000},
};
void ICACHE_FLASH_ATTR user_pre_init(void)
{
if(!system_partition_table_regist(at_partition_table, sizeof(at_partition_table)/sizeof(at_partition_table[0]),SPI_FLASH_SIZE_MAP)) {
os_printf("system_partition_table_regist fail\r\n");
while(1);
}
}
将以下代码加入到smart_config->user中
更改头文件user_config.h
#ifndef __USER_CONFIG_H__
#define __USER_CONFIG_H__
void delay_ms(unsigned int ms);
void hc595d_send_byte(unsigned char data);
void hc595d_latch_data(void);
void driver_motor(unsigned char j);
void tcp_client_init();
#endif
2.5编译烧录及运行结果
编译方法:保存(一定要先保存)-> Clean -> Build
编译结果
烧录方式见上一篇文章讲解基于RK3399&ESP8285自动售货柜项目—ESP8266(8285)程序编写与烧录 (zsxq.com)
运行结果
开启RK3399-TCP服务器后
参照本篇只需实现能够连接上WiFi即可,如要连接TCP服务器,下篇将会详细讲解RK3399服务器端代码,以及交互界面的制作,最终将完整实现本产品的制作