本篇分享:
对安信可A9G模组二次开发,使其能满足智能救生衣的功能,如,检测车辆是否浸水、获取地理坐标并向家属号码发送短信提醒、通过短信修改修改通讯号码等功能。
环境介绍:
系统:Windows
SDK:CSDTK4.2
IDE :VSCode
硬件:安信可A9G开发板
类似Linux多线程机制,每个任务中都可以使用while(1)循环检测。在该项目中包括主任务在内共有四个任务,分别是:主任务(循环检测处理各种事件,如信息发送、接收事件)、副任务(获取地理坐标、发送短信)、ADC检测任务(检测车辆是否浸水)、激光灯闪烁任务(控制激光灯周期性闪烁)
但有一点要注意:似乎不能再一个任务里面创建多个任务(发现这样会导致程序卡死,原因不详,),所以本项目代码中都是在一个任务中去创建另一个任务。
函数
函数原型:
HANDLE OS_CreateTask(
PTASK_FUNC_T pTaskEntry,
PVOID pParameter,
PVOID pStackAddr,
UINT16 nStackSize,
UINT8 nPriority,
UINT16 nCreationFlags,
UINT16 nTimeSlice,
PCSTR pTaskName);
参数:
pTaskEntry -- 任务执行函数,PTASK_FUNC_T 类型
pParameter -- 需要传递给执行函数的参数
pStackAddr -- 自定义栈的地址,暂时不支持自定义,即值只能为`NULL`
nStackSize -- 栈大小
nPriority -- 任务优先级,>=0,每个任务优先级不同,值越小任务优先级越大
nCreationFlags -- OS_CREATE_DEFAULT/0:默认,创建任务后开始执行任务;OS_CREATE_SUSPENDED -- 创建任务后不执行任务,需要手动调用start函数开启任务运行
nTimeSlice -- 保留,值为0
pTaskName -- 任务名称
返回值:
创建的任务的指针(句柄),如果创建任务失败,则返回`NULL`
作用:
创建一个新的任务
代码
/*系统任务句柄定义*/
HANDLE mainTaskHandle = NULL;
HANDLE secondTaskHandle = NULL;
HANDLE adcTaskHandle = NULL;
HANDLE laserTaskHandle = NULL;
void Laser_Task(void *pData)//激光灯闪烁任务
{
...
}
void ADC_Task(void *pData)//ADC检测任务
{
/*创建激光灯闪烁任务*/
laserTaskHandle = OS_CreateTask(Laser_Task ,
NULL, NULL, Laser_TASK_STACK_SIZE, Laser_TASK_PRIORITY, 0, 0, Laser_TASK_NAME);
}
void Second_Task(void *pData)//副任务
{
/*创建ADC检测任务*/
adcTaskHandle = OS_CreateTask(ADC_Task ,
NULL, NULL, ADC_TASK_STACK_SIZE, ADC_TASK_PRIORITY, 0, 0, ADC_TASK_NAME);
}
void SaveMainTask(VOID *pData)//主任务
{
/*创建副任务*/
secondTaskHandle = OS_CreateTask(Second_Task ,
NULL, NULL, Second_TASK_STACK_SIZE, Second_TASK_PRIORITY, 0, 0, Second_TASK_NAME);
}
void save_Main(void)//入口函数
{
/*创建主任务*/
mainTaskHandle = OS_CreateTask(SaveMainTask ,
NULL, NULL, SaveMain_TASK_STACK_SIZE, SaveMain_TASK_PRIORITY, 0, 0, SaveMain_TASK_NAME);
/*设置主任务,主任务用来接收来自底层的消息,这个函数必须在开机的时候创建主任务后立即调用,否则可能导致底层无法向应用层发布消息*/
OS_SetUserMainHandle(&mainTaskHandle);
}
要保证引脚功能可以正常使用,在配置IO之前必须 将对应IO口的电源打开 ,使用下面函数即可实现
函数原型:
bool PM_PowerEnable(Power_Type_t powerType, bool isOn);
参数:
powerType -- IO选择,POWER_TYPE_VPAD(GPIO0~GPIO17 & GPIO25~GPIO36),POWER_TYPE_MMC(GPIO8~GPIO13) & POWER_TYPE_LCD(GPIO14~GPIO18) & POWER_TYPE_CAM(GPIO19~24)
isOn -- true or flase
代码(打开GPIO26电源)
/*打开GPIO0 ~ GPIO7 and GPIO25 ~ GPIO36电源*/
PM_PowerEnable(POWER_TYPE_VPAD,true);
函数
函数原型:
bool GPIO_Init(GPIO_config_t config);
参数:
config -- 初始化配置结构体
代码
/*宏定义*/
#define LASER_PIN GPIO_PIN26//激光灯控制引脚
/*配置*/
GPIO_config_t gpio_config_t;
gpio_config_t.mode = GPIO_MODE_OUTPUT;//输出模式
gpio_config_t.pin = LASER_PIN;//激光灯控制引脚
gpio_config_t.defaultLevel = GPIO_LEVEL_LOW;//默认低电平->激光灯灭
/*初始化*/
GPIO_Init(gpio_config_t);
/*引脚输出高电平*/
GPIO_Set(LASER_PIN,GPIO_LEVEL_HIGH);//控制灯亮
/*引脚输出低电平*/
GPIO_Set(LASER_PIN,GPIO_LEVEL_LOW);//控制灯灭
/*参数单位:毫秒*/
OS_Sleep(500);//500ms
ADC_Config_t config = {
.channel = ADC_CHANNEL_0,//ADC通道0
.samplePeriod = ADC_SAMPLE_PERIOD_100MS
};
ADC_Init(config);
/*变量定义*/
uint16_t value = 0, mV = 0;//记录电压值
uint8_t adc_flag=0;//记录adc检测值低于阈值次数
/*ADC任务循环体*/
while(1)
{
/*读取电压值*/
if(ADC_Read(ADC_CHANNEL_0, &value, &mV))
{
/*电压值<1300mV 车辆可能遇水*/
if(mV<1300)
{
adc_flag++;
if(adc_flag>=5)//连续五次低于1300为遇水 防止误报
{
adc_flag=5;//设上限为5
saveFlag_new = 1;//标志位置1,表示遇水
}
}
else
{
if(adc_flag>0)adc_flag--;//计数值减少 不直接清零的目的是防止遇水后电压值波动过大
saveFlag_new = 0; //标志位清零,表示非落水
ModeChange();//查看是否需要改变电源工作模式
}
}
else
{
/*读取ADC出错*/
Error();
}
/*1s读取一次adc数据*/
OS_Sleep(1000);
}
根据例程代码,将初始化和数据获取封装成 两个函数。
函数
/* GPS相关初始化 */
GPSInit();
/*获取GPS数据*/
GetGPS();
在 GetGPS() 函数中,提取坐标并保存至数组,后续发送短信使用
/*编辑短信发送内容*/
snprintf(send_buf,sizeof(send_buf),"驾驶员落水警告!所在WGS84坐标:%f,%f",longitude,latitude);
参考例程中的 三个函数 实现。
/*SMS相关初始化*/
SMSInit();
/*发送短信*/
SendSMS();
/*Utf8格式*/
SendUtf8();
代码
/*SMS相关全局变量定义*/
uint8_t PHONE_NUMBER[12];//联系人号码
uint8_t send_buf[300];//短信字符串
/*SMS相关初始化*/
SMSInit();
/*发送短信*/
SendSMS();//内部调用SendUtf8()
void SendUtf8(void)
{
uint8_t* unicode = NULL;
uint32_t unicodeLen;
Trace(1,"sms start send UTF-8 message");
/*本地语言转unicode编码 UTF_8->unicode*/
if(!SMS_LocalLanguage2Unicode(send_buf,strlen(send_buf),CHARSET_UTF_8,&unicode,&unicodeLen))
{
Trace(1,"local to unicode fail!");
Error();
return;
}
/*发送短信*/
if(!SMS_SendMessage(PHONE_NUMBER,unicode,unicodeLen,SIM0))
{
Trace(1,"sms send message fail");
Error();
}
OS_Free(unicode);
}
接受短信的目的是使客户可以通过发送短信的方式**更改通讯号码(接收驾驶员坐标的号码)。该系统接收到短信后系统会触发API_EVENT_ID_SMS_RECEIVED
事件,并在主任务**进行处理。
/*事件处理函数*/
void EventDispatch(API_Event_t* pEvent)
{
/*信息接收 设定联系号码 短信格式为 change@号码*/
case API_EVENT_ID_SMS_RECEIVED:
PM_SleepMode(false);//退出低功耗模式
SMS_Encode_Type_t encodeType = pEvent->param1;
uint32_t contentLength = pEvent->param2;
uint8_t* header = pEvent->pParam1;
uint8_t* content = pEvent->pParam2;
if(encodeType == SMS_ENCODE_TYPE_ASCII)
{
ChangePhoneNumber(content,contentLength);//修改通讯号码
}
PM_SleepMode(true);//进入低功耗模式
break;
}
步骤:
使用:
代码
/*修改通讯号码*/
void ChangePhoneNumber(uint8_t* content,uint32_t contentLength)
{
uint8_t receive_buf[30];
uint8_t change_flag = 0;
uint8_t check_buf[100];
uint8_t Read_buf[30];
uint8_t ret;
/*指针转数组 strtok拆分字符串是直接在原串上操作,所以要求参1必须,可读可写(char *str = "xxx" 不行!!!)*/
strcpy(receive_buf,content);
/*确认是否为修改号码的短信 change@号码 strtok函数用于分割字符串 第一次调用strtok返回@前的字符串*/
strcpy(check_buf,strtok(receive_buf,"@"));
/*检测短信长度是否为17 @前的内容是否为change*/
if(contentLength != 17 && strcmp("change",check_buf) != 0)
{
change_flag = 1;//其他短信或短信格式有误 表示第一种出错情况
goto error;
}
/*再次以@字符分割*/
strcpy(check_buf,strtok(NULL,"@"));
/*判断号码长短是否为11位数*/
if(strlen(check_buf) != 11)
{
change_flag = 2;
goto error;
}
/*向原号码发送短信 先查看文件下是否有号码信息 判断是否为初次使用 初次使用的话PHONE_NUMBER中无号码 需要先修改才能发送*/
if(ReadData(Read_buf)==false)//文件为空 初次使用
{
snprintf(send_buf,sizeof(send_buf),"初始化成功!设置的联系号码为:%s!",check_buf);
strcpy(PHONE_NUMBER,check_buf);//修改号码
}
else//非初次使用
{
snprintf(send_buf,sizeof(send_buf),"您已成功修改联系号码为:%s!",check_buf);
}
SendSMS();
/*修改号码*/
strcpy(PHONE_NUMBER,check_buf);
/*修改flash中存储的号码*/
ret = SaveData(PHONE_NUMBER);
if(ret != true)
{
change_flag = 3;
Error();
}
/*错误处理 正常运行change_flag为0*/
error:
if(change_flag != 0)
{
switch(change_flag)
{
case 1:
sprintf(send_buf,"修改格式有误,格式为change@号码,请您重新发送!");
break;
case 2:
sprintf(send_buf,"号码有误,请您更正号码后重新发送!");
break;
case 3:
sprintf(send_buf,"储存号码失败!");
break;
}
SendSMS();
}
}
函数
函数原型:
int32_t API_FS_Open(
PCSTR fileName,
uint32_t operationFlag,
uint32_t mode
);
参数:
fileName -- 文件名
operationFlag -- 操作标记,比如只读、只写等
mode -- 模式,文件属性
返回值:
如果成功返回大于等于零的文件描述符,否则返回错误代码
作用:
打开文件
函数原型:
int32_t API_FS_Close(
int32_t fd
);
参数:
fd -- 文件描述符
返回值:
成功返回0,否则返回错误代码
作用:
关闭文件
函数原型:
int32_t API_FS_Write(
int32_t fd,
uint8_t* pBuffer,
uint32_t length
);
参数:
fd -- 文件描述符
pBuffer -- 待写入数据存放的位置
length -- 写入数据的长度
返回值:
成功写入的数据字节,错误则返回错误代码(负数)
作用:
向文件写入数据
代码
/*修改flash中存储的号码*/
ret = SaveData(PHONE_NUMBER);
if(ret != true)
{
change_flag = 3;
Error();
}
bool SaveData(uint8_t* data)
{
int32_t fd;
int32_t ret;
uint8_t *path = CONFIG_FILE_NAME;
/*以只写方式打开文件 若无则创建 若有则清空原内容*/
fd = API_FS_Open(path, FS_O_WRONLY | FS_O_CREAT | FS_O_TRUNC, 0);
if ( fd < 0)
{
Trace(1,"Open file failed:%d",fd);
return false;
}
/*向文件写入长度为11字节的数据*/
ret = API_FS_Write(fd,data,11);
if(ret <= 0)
{
API_FS_Close(fd);
return false;
}
Trace(1,"write ret:%d",ret);
/*关闭文件*/
API_FS_Close(fd);
return true;
}
函数
函数原型:
int32_t API_FS_Read(
int32_t fd,
uint8_t* pBuffer,
uint32_t length
);
参数:
fd -- 文件描述符
pBuffer -- 读取到的数据存放的位置
length -- 读取数据的长度
返回值:
成功写入的数据字节,错误则返回错误代码(负数)
作用:
向文件写入数据
代码
/* 初次使用作用为创建文件 之后为读取flash号码 */
ret = ReadData(PHONE_NUMBER);
if(ret != true)
{
Error();
UART_Write(UART1,"read flash faild\r\n",strlen("read flash faild\r\n"));
}
若ADC检测当前为非浸水状态,并且未触发一键报警,则系统进入低功耗模式,否则退出低功耗模式,进行地理坐标的获取以及短信的发送等操作。
函数
/*进入低功耗模式*/
PM_SleepMode(true);
/*退出低功耗模式*/
PM_SleepMode(flase);
代码
void ModeChange(void)
{
/*状态更新*/
if(saveFlag_new != saveFlag_old)
{
saveFlag_old = saveFlag_new;
if(saveFlag_new==1)//落水
{
PM_SleepMode(false);//退出低功耗模式
}
else//无
{
PM_SleepMode(true);//进入低功耗模式 降低主频到32kHz 程序依然会运行
}
}
}
为了检测到一键报警按键按下后(按键需外接),系统能迅速响应,本系统使用 外部中断 的方式检测按键是否按下。当检测到按键按下时,停止ADC的采集,并周期性闪烁激光灯,向家属手机号码发送位置信息。
函数
函数原型:
void OS_StopTask(
HANDLE pHTask);
参数:
pHTask -- 任务句柄
作用:
停止任务执行
/*宏定义*/
#define KEY_PIN GPIO_PIN6//连接报警按键引脚(可触发中断的引脚有GPIO0~GPIO7)
/*配置*/
GPIO_config_t gpio_config_t;
gpio_config_t.pin = KEY_PIN;//一键报警
gpio_config_t.mode = GPIO_MODE_INPUT_INT;//配置为中断方式
gpio_config_t.intConfig.type = GPIO_INT_TYPE_FALLING_EDGE;//下降沿捕获
gpio_config_t.intConfig.debounce = 15;//15ms消抖
gpio_config_t.intConfig.callback = OnPinFalling;//指定回调函数
/*初始化*/
GPIO_Init(gpio_config_t);
void OnPinFalling(GPIO_INT_callback_param_t* param)
{
switch(param->pin)
{
case GPIO_PIN6:
OS_StopTask(adcTaskHandle);//停止ADC检测任务
saveFlag_new = 1;
break;
default:
break;
}
}
(该内容源于安信可官方文档,仅用于记录方便查看)
SDK下载:GPRS_C_SDK_V2112.7z
新建的工程结构如下图所示:
注意Makefile只是首字母大写,或者全是小写,填入以下内容:
# Name of the module
LOCAL_NAME := demo/first
# List of submodules which contain code we need to include in the final lib
LOCAL_API_DEPENDS := \
LOCAL_ADD_INCLUDE := include\
include/std_inc \
include/api_inc \
# Set this to any non-null string to signal a module which
# generates a binary (must contain a "main" entry point).
# If left null, only a library will be generated.
IS_ENTRY_POINT := no
## ------------------------------------ ##
## Add your custom flags here ##
## ------------------------------------ ##
MYCFLAGS +=
## ------------------------------------- ##
## List all your sources here ##
## ------------------------------------- ##
C_SRC := ${notdir ${wildcard src/*.c}}
## ------------------------------------- ##
## Do Not touch below this line ##
## ------------------------------------- ##
include ${SOFT_WORKDIR}/platform/compilation/cust_rules.mk
这里LOCAL_NAME := demo/first
填**文件夹路径,LOCAL_ADD_INCLUDE
是包含的头文件路径**
HelloWorld示例代码可以在工程目录demo/first/src/demo_first.c
中找到,主要要做的事情是:编写程序入口,并创建主任务,用来接收来自底层的事件,同时建立一个任务,不停打印信息
代码入口及主任务:
#include "stdbool.h"
#include "stdint.h"
#include "api_os.h"
#include "api_debug.h"
#include "api_event.h"
#define MAIN_TASK_STACK_SIZE (2048 * 2)
#define MAIN_TASK_PRIORITY 0
#define MAIN_TASK_NAME "Main Test Task"
static HANDLE mainTaskHandle = NULL;
void first_Main(void)
{
mainTaskHandle = OS_CreateTask(MainTask,
NULL, NULL, MAIN_TASK_STACK_SIZE, MAIN_TASK_PRIORITY, 0, 0, MAIN_TASK_NAME);
OS_SetUserMainHandle(&mainTaskHandle);
}
每个程序都有一个入口,这里的入口是一个函数: void first_Main(void)
,返回值和参数都是void,下划线左边 必须 是文件夹的名字,这里是first,下划线右半边 必须 是Main,首字母大写。
调用OS_CreateTask
来创建一个任务,具体参数含义可以见OS部分,这里先不阐述。调用OS_SetUserMainHandle
将刚刚创建的任务指针(句柄)传给底层,让底层知道给那个任务发送事件,这个是必须的。
#define SECOND_TASK_STACK_SIZE (2048 * 2)
#define SECOND_TASK_PRIORITY 1
#define SECOND_TASK_NAME "Second Test Task"
static HANDLE secondTaskHandle = NULL;
void SecondTask(void *pData)
{
while(1)
{
Trace(1,"Hello GPRS ");
OS_Sleep(3000);
}
}
void EventDispatch(API_Event_t* pEvent)
{
switch(pEvent->id)
{
default:
break;
}
}
void MainTask(void *pData)
{
API_Event_t* event=NULL;
secondTaskHandle = OS_CreateTask(SecondTask,
NULL, NULL, SECOND_TASK_STACK_SIZE, SECOND_TASK_PRIORITY, 0, 0, SECOND_TASK_NAME);
while(1)
{
if(OS_WaitEvent(mainTaskHandle, (void**)&event, OS_TIME_OUT_WAIT_FOREVER))
{
EventDispatch(event);
OS_Free(event->pParam1);
OS_Free(event->pParam2);
OS_Free(event);
}
}
}
主任务MainTask
中又新建了一个任务SecondTask
,这个任务中做的事情就是每间隔3秒钟打印一句Hello GPRS
,打印信息可以在coolwatcher的tracer工具中看到;
然后主任务阻塞等待来自底层的事件,并在EventDispatch
函数中处理,这里暂时没有处理任何事件,所有事件可以在api_event.h
中找到,每个事件的参数也有注释
保证环境可以使用后,在GPRS_C_SDC目录下,Ctrl+Shift+鼠标右键打开powershell窗口,在命令行中使用build.bat
脚本来编译工程,有以下参数:
./build.bat $PROJ
来编译你的应用模块,如 ./build.batsh app
则是编译app目录下的源码./build.bat demo $PROJ
来编译demo目录下的特定例程,比如./build.bat demo gpio
./build.bat clean $PROJ
清除$PROJ
目录的中间文件./build.bat clean all
清除所有中间文件./build.bat demo $PROJ release
来生成release版本,比如./build.bat demo gpio release
, 如果最后一个参数不是release
,则默认是debug
版本, debug
版本在死机后会停止运行并可以使用GDB调试,而release版本加入了看门狗功能,在死机时会自动重启系统,所以实际投入使用时请使用release版本以防止出现bug时死机,测试时使用debug版本比如:
./build.bat demo gpio
进行编译,编译会生成一个build
目录,编译完成会在hex
目录下生成两个格式为lod
的文件,这就是我们用来下载到开发板的目标文件
目标文件说明:
两个文件一大一小,在第一次下载时需要下载大文件(*_B*.lod
)即可,第二次和以后为了减少下载时间都只需要下载小文件(*_flash.lod
)即可,更新SDK后需要重新下载大文件。
烧入:
8955 ramrun spi32m .lod
的文件(在工程platform/ramrun
下或者coolwatcher cooltools\chipgen\Modem2G\toolpool\plugins\fastpf\flash_programmers
目录下)。 只需要设置一次,后面都会记住的.lod
格式的文件,首次下载下载文件体积较大的.lod
格式文件,后面为了加快下载速度只需要下载体积较小的固件文件即可统一的代码风格方便阅读,对于任何一份代码来说都十分重要,每个人的代码风格不尽相同,但是多人在开发同一份代码时一定要统一风格,一份简洁干净的代码会让编码更加顺利舒畅~
CSDK使用统一的代码风格,简要概括为以下几个点:
模块API使用大写功能模块_大驼峰命名
,比如AUDIO_MicOpen
,SSL_Init
;普通函数使用大驼峰命名,比如:EventDispatch
入口函数特殊,使用文件夹名字_Main
,比如imei_Main
小驼峰命名,比如:int ballNumber;
大写+下划线,比如:#define BUFFER_MAX_LENGTH 10
文件编码统一为UTF-8
无BOM格式
不使用tab,编辑器设置tab为4空格替换
src
、tmp
等,比如: NetworkLocation
,如果缩写成NL
、NetLo
,相信就算是对这个单词非常熟悉的人也得去看代码的上下文才能慢慢才到其中的含义!如果去网上搜索这几个缩写,也不可能得到NetworkLocation
!
所以,为了让代码更简洁清晰易读,不一定是要让代码单词缩写为更简单的形式,相反的是全称的单词往往更易读。
/**/
或者//
进行注释,失效的代码尽量使用/**/
注释//
///这里写注释
//
或者
/**
*这里写注释
*
*/
/**
*@file header.h
*@author nick name
*/
或函数
/**
*@brief 这里写简要说明,@brief可以省略,省略则将简要说明写在注释开头
*@param a 求和参数1
*@param b 求和参数2
*@return 返回和
*/
函数原型:
void OS_SetUserMainHandle(HANDLE* appMainHandle);
参数:
appMainHandle -- 创建的主任务返回的指针(句柄)
返回值:
无
作用:
设置主任务,主任务用来接收来自底层的消息,这个函数必须在开机的时候创建主任务后立即调用,否则可能导致底层无法向应用层发布消息
函数原型:
HANDLE OS_CreateTask(
PTASK_FUNC_T pTaskEntry,
PVOID pParameter,
PVOID pStackAddr,
UINT16 nStackSize,
UINT8 nPriority,
UINT16 nCreationFlags,
UINT16 nTimeSlice,
PCSTR pTaskName);
参数:
pTaskEntry -- 任务执行函数,PTASK_FUNC_T 类型
pParameter -- 需要传递给执行函数的参数
pStackAddr -- 自定义栈的地址,暂时不支持自定义,即值只能为`NULL`
nStackSize -- 栈大小
nPriority -- 任务优先级,>=0,每个任务优先级不同,值越小任务优先级越大
nCreationFlags -- OS_CREATE_DEFAULT/0:默认,创建任务后开始执行任务;OS_CREATE_SUSPENDED -- 创建任务后不执行任务,需要手动调用start函数开启任务运行
nTimeSlice -- 保留,值为0
pTaskName -- 任务名称
返回值:
创建的任务的指针(句柄),如果创建任务失败,则返回`NULL`
作用:
创建一个新的任务
函数原型:
void OS_StartTask(
HANDLE pHTask,
PVOID pParameter);
参数:
pHTask -- 任务句柄,OS_CreateTask的返回值
pParameter -- 需要传给任务函数的参数
返回值:
无
作用:
开始执行任务
函数原型:
void OS_StopTask(
HANDLE pHTask);
参数:
pHTask -- 任务句柄,OS_CreateTask的返回值
返回值:
无
作用:
停止任务执行
函数原型:
bool OS_Free (PVOID pMemBlock);
参数:
pMemBlock -- 动态分配的内存块首地址
返回值:
是否释放成功
作用:
释放动态分配的内存
官方AT指令:
附着网络:
AT+CGATT=1
设置PDP参数:
AT+CGDCONT=1,"IP","CMNET"
激活PDP,正确激活以后就可以上网了:
AT+CGACT=1,1
报错:
+CME ERROR: 3
解决:
用下列AT 指令
设置自动选择APN:
AT+CSTT="","","" \r
激活移动场景,激活后能获取到IP:
AT+CIICR\r
查询IP,只有获取到IP后才能上网:
AT+CIFSR\r
AT+CMGS=20
\> 0001000D91685110365210F800080660A8597DFF01
+CMS ERROR: 500
原因:
未设置为PDU模式
AT+CMGF=1 //设置为Text模式
AT+CMGF=0 //设置为PDU模式
解决:
AT+CMGF=0 //设置为PDU模式
AT+CMGS=20 //发送指令,更改为对应PDU编码的长度
>0011000D91685150800576F70008C4044F60597D(+^z,十六进制的1A结束)//返回字符 串中有OK 发送成功
PDU在线编码工具:
原因:
解决:
常规操作:
原因:
解决:
原因:
解决:
原因:
解决:
坐标系在线转换:https://tool.lu/coordinate
转换后4G基站定位(误差极小):
(无法解决)
尝试:
使用C语音代码转换(需要math库,无法解决)
调用API接口?(非盈利项目可以随意调用)
使用云平台解决?(未解决)
尝试:
解决:
(未解决,原本中文也正常)
尝试:
极少出现,疑似供电问题:
现象:
解决:
猜测:
和烧入的文件版本有关?有debug版本和release版本。
官方文档中提到的相关描述:
(注释掉的串口打印,编译后烧入依然存在。并且和debug版本一样,while循环中不添加睡眠函数无法正常串口打印,执行程序)
烧入release版本现象:
同样的代码debug版本现象:
安信可官方开发资料
安信可A9G模块资料
A9G使用常见问题
GSM/GPRS AT指令操作示例
https://github.com/Octopus1633/A9G_SmartLifeJacket.git