心心恋恋的基于移远M26模块设计的用于开发OpenCPU功能的板子终于完成,废话少说,直接上图:
对于板子外观这里不作讨论,毕竟不是我设计的,期望能用吧。
可能是由于设计者经验不足的原因吧。我刚拿到板子的时候一看,心想坏了。貌似串口没有进行电平匹配,找来原理图纸一看,还真的没有进行电平匹配。呵呵,这个时候我还能说啥,自己搭电平匹配电路吧。我又打开了M26 OpenCPU硬件参考手册,找到串口设计章节,如下图:
手册下载地址参见:M26 OpenCPU硬件设计参考手册 。
找来了需要的元器件,手工焊接,有点丑陋,不喜勿喷哈。成品如下图:
由于使用的是笔记本电脑开发,所以找来一根以前工程使用剩下来的USB转TTL线,与自制电平匹配电路板连接起来,如下图:
接下来我就将M26电路板上的串口引脚引出来连接至自制电平匹配电路板上,效果如下:
好了,到此OpenCPU开发所需的硬件已经准备好了。我们就通电连上电脑试一下效果吧。
呵呵,果然,不可能那么顺利的。
连上后打开串口工具,给其发送AT指令,串口没有返回OK,而是直接返回AT,而且串口还会时不时的返回00。不过至少可以证明M26模块没有问题,我想着可能是供应商知道我们是要使用OpenCPU功能,直接烧录了测试代码吧。毕竟一上电板子上的指示灯不停的闪烁、蜂鸣器不停的响。那行,我就直接下载一次M26内核试一下。
打开QFlash 4.8烧录软件,选择好串口号和烧录文件,如下:
操作方法参见qflash用户指导手册:Quectel_QFlash_OpenCPU_User_Guide_V1.0
工具下载地址:qFlash v4.8
点击Start按钮后,重新给电路板上电,看到下面的进度条在前进,很是激动,心想就要成功了。
呵呵,没错,又出问题了,此时一万只草泥马从心中经过。错误如下图:
通过查看qflash用户指导手册中找到引起这个错误的原因,原来是下载时电压不稳定或者是下载线缆连接不稳定,如下图:
好了,知道问题了就来解决问题吧。下载线缆连接肯定不会出错,毕竟串口调试助手一直收发都没有出问题,那就只能是电路板供电问题了,继续查看原理图。看到供电电路貌似也没有问题,这时我又犯愁了,到底怎么回事呢?这时,一个地方引起了我的注意,那就是电路板上的复位电路,如下图:
使用外部自带看门狗功能的芯片进行复位的,必须给其喂狗,这个芯片才会正常工作,不输出复位信号。说明如下:
从上图可知喂狗的引脚连接的是M26的16号引脚,通过引脚图可知是NETLIGHT引脚。如下图:
所以就需要M26的NETLIGHT引脚不停的给复位芯片喂狗才行,即必须在1.6s内有一个电平变化。我想就是这个导致,此时M26模块内应该没有这个信号,通过测量,NETLIGHT引脚的确没有电平变化的信号。这就导致M26一直在复位,无法提供稳定的电源给其下载flash。
问题找到了,我就纳闷了,为啥要这么设计,问一下设计人员,得到的回复竟然是到时候M26芯片会固化程序进去,装上就可以正常使用了。暂时移远提供的芯片没有固化这个功能,我也是无语了,控制程序不是还得我们来设计吗,这样搞的话,测试得多麻烦。
啥也不说了,解决问题吧。既然是复位引起的,那就在下载时跳过它吧。通过分析原理图,发现这个芯片的复位引脚是通过控制一个三极管驱动两路MOS管来断M26模块供电电源来实现复位的,那就直接跳过这个三极管,让MOS管一直导通,也就是让Q3的G极一直为低电平即可。电路如下:
我想那就直接焊接一根杜邦线,下载时短接GND应该就可以了吧。焊接好后立马测试一下,果然可以了。就是下载时比较麻烦,而且速度老慢了,下载内核耗时400多秒,不知是不是就是这样,还是电路的问题。
到此,重新下载内核后,就需要下载运行程序(含喂狗功能)进去看一下效果了,原来第一次上电时的指示灯和蜂鸣器工作是M26不停复位导致,我还以为是M26自带的测试代码,大写的尴尬。下载测试代码方法参考如下文档,里面有详细操作步骤:Quectel_QFlash_OpenCPU_User_Guide_V1.0。
至于如何编写程序,以及如何编程环境搭建,参考如下文档:
编程环境安装与配置:Quectel_OpenCPU_GCC_Installation_Guide_V1.1
编写程序快速入门:Quectel_OpenCPU_Quick_Start_Application_Note_V1.1
开发工具包:M26_OpenCPU_GS3_SDK_V2.0
编程参考文档:M26 OpenCPU用户指导手册
测试代码主要实现如下功能:
1.给外部复位芯片喂狗;
2.控制板子上的WORK指示灯闪烁,频率为1S;
3.串口打印相关信息。
下面我们来实现这些功能,需要给外部复位芯片喂狗,就得配置相关引脚为输出功能,初始化代码如下:
由于喂狗是周期性的,所以需要定时器功能,实现500ms定时器中断,方法如下:
实现work指示灯闪烁,图纸如下:
32号引脚是PCM_IN,初始化代码如下:
需要实现闪烁,正好可以再定时器回调函数里实现。如下:
因为板子上只使用了M26的主串口,所以此功能实现如下:
编程详请参考相关手册,完整代码如下:
/*****************************************************************************
* Copyright Statement:
* --------------------
* This software is protected by Copyright and the information contained
* herein is confidential. The software may not be copied and the information
* contained herein may not be used or disclosed except with the written
* permission of Quectel Co., Ltd. 2013
*
*****************************************************************************/
/*****************************************************************************
*
* Filename:
* ---------
* main.c
*
* Project:
* --------
* OpenCPU
*
* Description:
* ------------
*
* 测试代码
*
* Usage:
* ------
* Compile & Run:
*
* Set "C_PREDEF=-D __CUSTOMER_CODE__" in gcc_makefile file. And compile the
* app using "make clean/new".
* Download image bin to module to run.
*
* Author: 顾小豆
*
*
*============================================================================
* HISTORY
*----------------------------------------------------------------------------
*
****************************************************************************/
#ifdef __CUSTOMER_CODE__
#include "ql_trace.h"
#include "ql_system.h"
#include "ql_gpio.h"
#include "ql_stdlib.h"
#include "ql_error.h"
#include "ql_uart.h"
#include "ql_timer.h"
#include "ql_type.h"
#include "ql_error.h"
// 串口輸出配置,串口1
#define DEBUG_ENABLE 1
#if DEBUG_ENABLE > 0
#define DEBUG_PORT UART_PORT1
#define DBG_BUF_LEN 512
static char DBG_BUFFER[DBG_BUF_LEN];
#define APP_DEBUG(FORMAT,...) {\
Ql_memset(DBG_BUFFER, 0, DBG_BUF_LEN);\
Ql_sprintf(DBG_BUFFER,FORMAT,##__VA_ARGS__); \
if (UART_PORT2 == (DEBUG_PORT)) \
{\
Ql_Debug_Trace(DBG_BUFFER);\
} else {\
Ql_UART_Write((Enum_SerialPort)(DEBUG_PORT), (u8*)(DBG_BUFFER), Ql_strlen((const char *)(DBG_BUFFER)));\
}\
}
#else
#define APP_DEBUG(FORMAT,...)
#endif
// 定义控制引脚相关变量
static Enum_PinName g_wtd_gpioPin = PINNAME_NETLIGHT; // 外部看门狗控制引脚
static Enum_PinName g_work_gpioPin = PINNAME_PCM_IN; // WORK指示灯控制引脚
// 定义定时器相关变量
static u32 m_myTimerId = 2014; // 定时器ID
static u32 m_nInterval = 500; // 500ms中断
static void Callback_OnTimer(u32 timerId, void* param); // 定时器回调函数
// 定义串口使用变量
static u8 m_Read_Buffer[1024]; // 串口接收缓存
// 串口回调函数
static void CallBack_UART_Hdlr(Enum_SerialPort port, Enum_UARTEventType msg, bool level, void* customizedPara);
// GPIO引脚初始化函数
static void GPIO_Program(void)
{
// 初始化NET指示灯引脚,低电平,无上下拉配置
Ql_GPIO_Init(g_net_gpioPin, PINDIRECTION_OUT, PINLEVEL_LOW, PINPULLSEL_DISABLE);
Ql_GPIO_SetLevel(g_net_gpioPin, PINLEVEL_LOW); // 设定NET引脚为低电平
// 初始化外部看门狗引脚,低电平,无上下拉配置
Ql_GPIO_Init(g_wtd_gpioPin, PINDIRECTION_OUT, PINLEVEL_LOW, PINPULLSEL_DISABLE);
// 初始化外WORK指示灯引脚,低电平,无上下拉配置
Ql_GPIO_Init(g_work_gpioPin, PINDIRECTION_OUT, PINLEVEL_LOW, PINPULLSEL_DISABLE);
Ql_GPIO_SetLevel(g_work_gpioPin, PINLEVEL_LOW); // 设定WORK引脚为低电平
// 初始化蜂鸣器引脚,低电平,无上下拉配置
Ql_GPIO_Init(g_beep_gpioPin, PINDIRECTION_OUT, PINLEVEL_LOW, PINPULLSEL_DISABLE);
Ql_GPIO_SetLevel(g_beep_gpioPin, PINLEVEL_LOW); // 设定蜂鸣器引脚为低电平
// 初始化输出端口引脚,低电平,无上下拉配置
Ql_GPIO_Init(g_out_gpioPin, PINDIRECTION_OUT, PINLEVEL_LOW, PINPULLSEL_DISABLE);
Ql_GPIO_SetLevel(g_out_gpioPin, PINLEVEL_LOW); // 设定输出端口引脚为低电平
// 睡眠 500ms,点亮NET指示灯.通过串口打印相关信息
Ql_Sleep(500);
Ql_GPIO_SetLevel(g_net_gpioPin, PINLEVEL_HIGH);
APP_DEBUG("<-- 获取NET控制引脚电平: %d -->\r\n", Ql_GPIO_GetLevel(g_net_gpioPin));
APP_DEBUG("<-- 点亮NET指示灯 -->\r\n");
}
// 定时器配置函数
static void TIMER_Program(void)
{
// 注册定时器2014及定时器回调函数
Ql_Timer_Register(m_myTimerId, Callback_OnTimer, NULL);
// 开始定时器2014
Ql_Timer_Start(m_myTimerId, m_nInterval, TRUE);
}
// 串口初始化函数
static void UART_Program(void)
{
s32 ret;
// 注册串口1和串口回调函数
ret = Ql_UART_Register(UART_PORT1, CallBack_UART_Hdlr, NULL);
// 注册失败打印相关信息
if (ret < QL_RET_OK)
{
APP_DEBUG("Fail to register serial port[%d], ret=%d\r\n", UART_PORT1, ret);
}
// 打开串口1 波特率115200 无流控
ret = Ql_UART_Open(UART_PORT1, 115200, FC_NONE);
// 打开失败打印相关信息
if (ret < QL_RET_OK)
{
APP_DEBUG("Fail to open serial port[%d], ret=%d\r\n", UART_PORT1, ret);
}
}
// 喂狗函数
static void feed_wtd(void)
{
s32 gpioLvl = Ql_GPIO_GetLevel(g_wtd_gpioPin);
if (PINLEVEL_LOW == gpioLvl)
{
// 置位看门狗控制引脚
Ql_GPIO_SetLevel(g_wtd_gpioPin, PINLEVEL_HIGH);
APP_DEBUG("<-- 外部看门狗喂狗 -->\r\n");
}
else
{
// 复位看门狗控制引脚
Ql_GPIO_SetLevel(g_wtd_gpioPin, PINLEVEL_LOW);
APP_DEBUG("<-- 外部看门狗喂狗 -->\r\n");
}
}
// 蜂鸣器控制函数
static void beep_ctrl(void)
{
}
/************************************************************************/
/* The entrance for this example application */
/************************************************************************/
void proc_main_task(s32 taskId)
{
ST_MSG msg;
float g_f;
UART_Program(); // 调用串口初始化函数
GPIO_Program(); // 调用GPIO初始化函数
TIMER_Program(); // 调用定时器配置函数
// Start message loop of this task
while (TRUE)
{
Ql_OS_GetMessage(&msg);
switch(msg.message)
{
case MSG_ID_USER_START:
break;
default:
break;
}
}
}
static void Callback_OnTimer(u32 timerId, void* param)
{
static u16 timerCnt[10] = {0};
feed_wtd();
if(timerCnt[0]++ >= 2)
{
s32 gpioLvl = Ql_GPIO_GetLevel(g_work_gpioPin);
if (PINLEVEL_LOW == gpioLvl)
{
// 点亮work指示灯
Ql_GPIO_SetLevel(g_work_gpioPin, PINLEVEL_HIGH);
APP_DEBUG("<-- 点亮work指示灯 -->\r\n");
}
else
{
// 熄灭work指示灯
Ql_GPIO_SetLevel(g_work_gpioPin, PINLEVEL_LOW);
APP_DEBUG("<-- 关闭work指示灯 -->\r\n");
}
timerCnt[0] = 0;
}
}
#endif //__EXAMPLE_GPIO__
欢迎读者提出疑问,可以加群讨论:838839442 微信公众号:物联网技术交流与学习