کار با ماژول تمام عیار mc60 – قسمت چهارم – OpenCPU و تکمیل ردیاب

0
413
کار با ماژول تمام عیار mc60 – قسمت چهارم – OpenCPU و تکمیل ردیاب

در قسمت‌های قبل، بر اساس برد  پیش رفتیم و چند برنامه مختلف رو برای اون نوشتیم. حالا توی این قسمت میخوایم برنامه ردیاب mc60 که نوشته بودیم رو کامل‌تر بکنیم و چند قابلیت دیگه از جمله ارسال مکان به صورت آفلاین توسط پیامک و ذخیره مسیرهای پیموده شده به صورت فایل متنی در کارت حافظه رو بهش اضافه کنیم. برای اینکار میخوایم از هدربرد جدیدی استفاده کنیم که توی اون میتونیم از تمام قابلیت‌های ماژول mc60 استفاده کنیم. این امکانات شامل کارت حافظه، ADC،I2C، بلوتوث، GPS، هدفون (میکروفن و اسپیکر)، دو سیم کارت و چند LED هست که کار با ماژول رو راحت‌تر میکنه. تصویر اون رو هم در پایین می‌بینید.

mc60-header_board

اول از همه کدهای برنامه ردیاب mc60 رو قرار می‌دم و بعد به توضیح هر قسمت می‌پردازیم:

#define __CUSTOMER_CODE__
#ifdef __CUSTOMER_CODE__

#include "custom_feature_def.h"
#include "ril.h"
#include "ril_util.h"
#include "ril_telephony.h"
#include "ql_stdlib.h"
#include "ql_error.h"
#include "ql_uart.h"
#include "ql_system.h"
#include "ril_network.h"
#include "ril_http.h"
#include "ql_time.h"
#include "ql_fs.h"
#include "ril_sms.h"

///for GPRS////////////////////////////////////////////////////////////////
char HTTP_URL_ADDR[100] = "http://mahdi2001h.ir/utils/location.php\0";
static u32 m_rcvDataLen = 0;
u8 SENDING_TO_SERVER = 0;
u8 postMsg[300] = "";
u8 arrHttpRcvBuf[10 * 1024];

#define APN_NAME "mcinet\0"
#define APN_USERID ""
#define APN_PASSWD ""
///////////////////////////////////////////////////////////////////////////

///for use SD ////////////////////////////////
u8 LOG_TO_SD = 1;
#define DATA_LOG_DIR "SD:\\location_log"
//////////////////////////////////////////////

///for other use////////////////////////
char PhNum[] = "+9891XXXXXXXX\0";
u8 RMC_BUFFER[100];
u8 LAST_FIX[100];
u8 stable = 0;
u8 RIL_STATUS = 0;
Enum_PinName LED_1 = PINNAME_RTS;
Enum_PinName LED_2 = PINNAME_CTS;
Enum_PinName LED_3 = PINNAME_DTR;
////////////////////////////////////////

///for DEBUG///////////////////////////////////////////////////////////////////////////////////////////////////////////
#define SERIAL_RX_BUFFER_LEN 2048
#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__); \
Ql_UART_Write((Enum_SerialPort)(DEBUG_PORT), (u8 *)(DBG_BUFFER), Ql_strlen((const char *)(DBG_BUFFER))); \
}
char m_RxBuf_Uart[SERIAL_RX_BUFFER_LEN];
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void CallBack_UART_Hdlr(Enum_SerialPort port, Enum_UARTEventType msg, bool level, void *customizedPara);
static void HTTP_Program();
static void Hdlr_RecvNewSMS(u32 nIndex, bool bAutoReply);
void SMS_TextMode_Send(char strPhNum[], char strTextMsg[]);

void proc_main_task(s32 taskId)
{
s32 ret;
ST_MSG msg;

Ql_GPIO_Init(LED_1, PINDIRECTION_OUT, PINLEVEL_LOW, PINPULLSEL_PULLUP);
Ql_GPIO_Init(LED_2, PINDIRECTION_OUT, PINLEVEL_LOW, PINPULLSEL_PULLUP);
Ql_GPIO_Init(LED_3, PINDIRECTION_OUT, PINLEVEL_LOW, PINPULLSEL_PULLUP);

while (TRUE)
{
Ql_OS_GetMessage(&msg);
switch (msg.message)
{
case MSG_ID_RIL_READY:
APP_DEBUG("LOAD LEVEL 1 (RIL READY)\r\n");
Ql_RIL_Initialize();
RIL_STATUS = 1;
GPSPower(1);
ret = Ql_RIL_SendATCmd("AT+QNITZ=1", Ql_strlen("AT+QNITZ=1"), NULL, NULL, 0);

break;
case MSG_ID_URC_INDICATION:
APP_DEBUG("Received URC: type: %d\r\n", msg.param1);
switch (msg.param1)
{
case URC_GSM_NW_STATE_IND:
APP_DEBUG("GSM Network Status:%d\r\n", msg.param2);
break;

case URC_GPRS_NW_STATE_IND:
APP_DEBUG("GPRS Network Status:%d\r\n", msg.param2);
if (NW_STAT_REGISTERED == msg.param2 || NW_STAT_REGISTERED_ROAMING == msg.param2)
{
APP_DEBUG("LOAD LEVEL 2 (NETWORK REGISTERED)\r\n");
stable = 1;
}
break;
case URC_NEW_SMS_IND:
{
Hdlr_RecvNewSMS((msg.param2), TRUE);
break;
}
}

break;
}
}
}

//Read serial port
void proc_subtask1(s32 TaskId)
{
s32 ret;
ST_MSG msg;
ST_UARTDCB dcb;
Enum_SerialPort mySerialPort = UART_PORT1;
dcb.baudrate = 115200;
dcb.dataBits = DB_8BIT;
dcb.stopBits = SB_ONE;
dcb.parity = PB_NONE;
dcb.flowCtrl = FC_NONE;
Ql_UART_Register(mySerialPort, CallBack_UART_Hdlr, NULL);
Ql_UART_OpenEx(mySerialPort, &dcb);
Ql_UART_ClrRxBuffer(mySerialPort);
APP_DEBUG("START PROGRAM (SISOOG.COM)\r\n");
while (TRUE)
{
Ql_OS_GetMessage(&msg);
switch (msg.message)
{
case MSG_ID_USER_START:
break;
default:
break;
}
}
}

//Update location data
proc_subtask2(s32 TaskId)
{
ST_Time SysTime;
static s32 handle = -1;
char filePath[50] = {0};
u32 writenLen = 0;
s32 ret;

while (RIL_STATUS != 1)
Ql_Sleep(1000);

while (TRUE)
{
Ql_Sleep(1000);

Ql_GetLocalTime(&SysTime);
APP_DEBUG("\r\nCurrent Time :\r\n%02d/%02d/%02d %02d:%02d:%02d\r\n", SysTime.year, SysTime.month, SysTime.day, SysTime.hour, SysTime.minute, SysTime.second);

ret = RIL_GPS_Read("RMC", RMC_BUFFER);
if (RIL_AT_SUCCESS != ret)
{
APP_DEBUG("Read %s information failed.\r\n", "RMC");
}
else
{
if (RMC_BUFFER[30] == 'A')
{
Ql_GPIO_SetLevel(LED_1, PINLEVEL_HIGH);
Ql_Sleep(50);
Ql_GPIO_SetLevel(LED_1, PINLEVEL_LOW);
Ql_Sleep(50);
Ql_GPIO_SetLevel(LED_1, PINLEVEL_HIGH);
Ql_Sleep(50);
Ql_GPIO_SetLevel(LED_1, PINLEVEL_LOW);

Ql_strcpy(LAST_FIX, RMC_BUFFER);
}
else if (RMC_BUFFER[30] == 'V')
{
Ql_GPIO_SetLevel(LED_1, PINLEVEL_HIGH);
Ql_Sleep(50);
Ql_GPIO_SetLevel(LED_1, PINLEVEL_LOW);
}

if (LOG_TO_SD == 1 && SysTime.year > 2020 && (SysTime.second % 2 == 0))
{
Ql_GPIO_SetLevel(LED_2, PINLEVEL_HIGH);
ret = Ql_FS_CheckDir(DATA_LOG_DIR);
if (ret == QL_RET_ERR_FILENOTFOUND)
{
ret = Ql_FS_CreateDir(DATA_LOG_DIR);
if (ret == QL_RET_OK)
{
APP_DEBUG("Dir Created: %s !\r\n", DATA_LOG_DIR);
}
}
else if (ret == QL_RET_OK)
{
Ql_sprintf(filePath, "%s\\%d_%d.txt\0", DATA_LOG_DIR, SysTime.month, SysTime.day);
handle = Ql_FS_Open(filePath, QL_FS_CREATE);
if (handle > 0)
{
Ql_FS_Seek(handle, 0, QL_FS_FILE_END);
Ql_FS_Write(handle, RMC_BUFFER, Ql_strlen(RMC_BUFFER), &writenLen);
Ql_FS_Flush(handle);
Ql_FS_Close(handle);
}
else
{
APP_DEBUG("file does not exist \r\n");
}
}
else
{
APP_DEBUG("ERROR READ SD \r\n", ret);
}
Ql_GPIO_SetLevel(LED_2, PINLEVEL_LOW);
}
}
}
}

// Send location data to server
proc_subtask3(s32 TaskId)
{
while (1)
{
Ql_Sleep(5000);
if (stable == 1)
{
Ql_sprintf(postMsg, "location=%s\0", RMC_BUFFER);
HTTP_Program();
}
}
}

void SMS_TextMode_Send(char strPhNum[], char strTextMsg[])
{
u32 nMsgRef;
ST_RIL_SMS_SendExt sExt;
Ql_memset(&sExt, 0x00, sizeof(sExt));
APP_DEBUG("< Send Normal Text SMS begin... >\r\n");
RIL_SMS_SendSMS_Text(strPhNum, Ql_strlen(strPhNum), LIB_SMS_CHARSET_GSM, strTextMsg, Ql_strlen(strTextMsg), &nMsgRef);
}

static void Hdlr_RecvNewSMS(u32 nIndex, bool bAutoReply)
{
ST_RIL_SMS_TextInfo *pTextInfo = NULL;
ST_RIL_SMS_DeliverParam *pDeliverTextInfo = NULL;
char aPhNum[RIL_SMS_PHONE_NUMBER_MAX_LEN] = {
0,
};
pTextInfo = Ql_MEM_Alloc(sizeof(ST_RIL_SMS_TextInfo));
Ql_memset(pTextInfo, 0x00, sizeof(ST_RIL_SMS_TextInfo));
RIL_SMS_ReadSMS_Text(nIndex, LIB_SMS_CHARSET_GSM, pTextInfo);
pDeliverTextInfo = &((pTextInfo->param).deliverParam);
Ql_strcpy(aPhNum, pDeliverTextInfo->oa);
APP_DEBUG("data = %s\r\n", (pDeliverTextInfo->data));
char text[350];
Ql_strcpy(text, (pDeliverTextInfo->data));

if (Ql_strcmp("send loc", text))
{
if (LAST_FIX[30] = 'A')
{
char lat[10], lon[10];
float flat, flon;
int c = 0;

while (c < 9)
{
lat[c] = LAST_FIX[32 + c];
c++;
}
lat[c] = '\0';
c = 0;
while (c < 10)
{
lon[c] = LAST_FIX[44 + c];
c++;
}
lon[c] = '\0';
flat = Ql_atof(lat) / 100;
flon = Ql_atof(lon) / 100;

Ql_sprintf(text, "lat= %f , lon= %f\0", flat, flon);
SMS_TextMode_Send(aPhNum, text);
}
else
{
SMS_TextMode_Send(aPhNum, "GPS NOT FIXED YET");
}
}
Ql_MEM_Free(pTextInfo);

return;
}

static void HTTP_RcvData(u8 *ptrData, u32 dataLen, void *reserved)
{
APP_DEBUG("<-- Data coming on http, total len:%d -->\r\n", m_rcvDataLen + dataLen);
if ((m_rcvDataLen + dataLen) <= sizeof(arrHttpRcvBuf))
{
Ql_memcpy((void *)(arrHttpRcvBuf + m_rcvDataLen), (const void *)ptrData, dataLen);
}
else
{
if (m_rcvDataLen < sizeof(arrHttpRcvBuf))
{
u32 realAcceptLen = sizeof(arrHttpRcvBuf) - m_rcvDataLen;
Ql_memcpy((void *)(arrHttpRcvBuf + m_rcvDataLen), (const void *)ptrData, realAcceptLen);
APP_DEBUG("<-- Rcv-buffer is not enough, discard part of data (len:%d/%d) -->\r\n", dataLen - realAcceptLen, dataLen);
}
else
{
APP_DEBUG("<-- No more buffer, discard data (len:%d) -->\r\n", dataLen);
}
}
m_rcvDataLen += dataLen;
}

static void HTTP_Program()
{
s32 ret;
m_rcvDataLen = 0;
ret = RIL_NW_SetGPRSContext(Ql_GPRS_GetPDPContextId());
APP_DEBUG("START SEND TO SERVER\r\n");
ret = RIL_NW_SetAPN(1, APN_NAME, APN_USERID, APN_PASSWD);
APP_DEBUG("<-- Set GPRS APN, ret=%d -->\r\n", ret);
ret = RIL_NW_OpenPDPContext();
APP_DEBUG("<-- Open PDP context, ret=%d -->\r\n", ret);
ret = RIL_HTTP_SetServerURL(HTTP_URL_ADDR, Ql_strlen(HTTP_URL_ADDR));
APP_DEBUG("<-- Set http server URL, ret=%d -->\r\n", ret);
ret = RIL_HTTP_RequestToPost(postMsg, Ql_strlen((char *)postMsg));
APP_DEBUG("<-- Send post-request, ret=%d -->\r\n", ret);
ret = RIL_HTTP_ReadResponse(120, HTTP_RcvData);
APP_DEBUG("READ SERVER RESPONSE (dataLen=%d) \r\n", m_rcvDataLen);
ret = RIL_NW_ClosePDPContext();
APP_DEBUG("<-- Close PDP context, ret=%d -->\r\n", ret);
}

static s32 ReadSerialPort(Enum_SerialPort port, /*[out]*/ u8 *pBuffer, /*[in]*/ u32 bufLen)
{
s32 rdLen = 0;
s32 rdTotalLen = 0;
if (NULL == pBuffer || 0 == bufLen)
{
return -1;
}
Ql_memset(pBuffer, 0x0, bufLen);
while (1)
{
rdLen = Ql_UART_Read(port, pBuffer + rdTotalLen, bufLen - rdTotalLen);
if (rdLen <= 0)
{
break;
}
rdTotalLen += rdLen;
}
return rdTotalLen;
}

void GPSPower(int status)
{
u16 ret;
if (status == 1)
{
ret = RIL_GPS_Open(1);
if (RIL_AT_SUCCESS != ret)
{
APP_DEBUG("GPS is on \r\n");
}
else
{
APP_DEBUG("Power on GPS Successful.\r\n");
}
}
else if (status == 0)
{
ret = RIL_GPS_Open(0);
if (RIL_AT_SUCCESS != ret)
{
APP_DEBUG("GPS is off \r\n");
}
else
{
APP_DEBUG("Power off GPS Successful.\r\n");
}
}
}

static void CallBack_UART_Hdlr(Enum_SerialPort port, Enum_UARTEventType msg, bool level, void *customizedPara)
{
switch (msg)
{
case EVENT_UART_READY_TO_READ:
{
char *p = NULL;
s32 totalBytes = ReadSerialPort(port, m_RxBuf_Uart, sizeof(m_RxBuf_Uart));
if (totalBytes <= 0)
{
break;
}
if (Ql_strstr(m_RxBuf_Uart, "GPSOn"))
{
APP_DEBUG("ok\r\n");
GPSPower(1);
break;
}
if (Ql_strstr(m_RxBuf_Uart, "GPSOff"))
{
APP_DEBUG("ok\r\n");
GPSPower(0);
break;
}
if (Ql_strstr(m_RxBuf_Uart, "location"))
{
APP_DEBUG("ok\r\n");
APP_DEBUG("%s \r\n", RMC_BUFFER);
break;
}
break;
}
}
}
#endif // __CUSTOMER_CODE__

توی قسمت دوم طریقه ارسال و دریافت پیامک رو توضیح دادیم و توی قسمت سوم هم دریافت اطلاعات از gps و نحوه ارسال اون توسط GPRS رو، برای همین از توضیح این قسمت‌ها صرف نظر می‌کنیم.

توجه داشته باشید که توی این کد هم مثل قسمت قبل از قابلیت RTOS استفاده کردیم پس این سه خط رو به فایل custom_task_cfg.h اضافه می‌کنیم.

TASK_ITEM(proc_subtask1, subtask1_id, 5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)
TASK_ITEM(proc_subtask2, subtask2_id, 10*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)
TASK_ITEM(proc_subtask3, subtask3_id, 5*1024, DEFAULT_VALUE1, DEFAULT_VALUE2)

 

تنظیم کردن ساعت ماژول :

ما میخوایم در ردیاب mc60 اطلاعات مکان رو به صورت روزانه توی یک فایل ذخیره کنیم که اسم هر فایل برابر با روز و ماه جاری باشه، برای این کار میتونیم از دو راه استفاده کنیم، یکی اینکه زمان دقیق رو از GPS دریافت کنیم که خیلی دقیق و خوب هست و برای اینکار میتونیم زمان دقیق رو که توی پارامتر دوم دیتای RMC جی پی اس هست به خونیم و از اون استفاده کنیم، اما اگر توی پروژمون GPS نداشتیم یا هر چیز دیگه، میتونیم از زمان شبکه GSM استفاده کنیم. برای اینکار از دستور AT+QNITZ=1  استفاده میکنیم که یکی از AT COMMAND های ماژول هست. من تابع RIL اون رو توی example هایی که داده بود، پیدا نکردم. برای همین به صورت مستقیم اون رو اجرا کردم و توی خط 84 هست.

 ret = Ql_RIL_SendATCmd("AT+QNITZ=1", Ql_strlen("AT+QNITZ=1"), NULL, NULL, 0);

همون طور که از اسمش پیداست این تابع کلاً برای ارسال دستورات AT COMMAND به کار میره و میتونید هر دستوری رو باهاش بفرستید.

دستور AT+QNITZ=1  میاد و توی اولین اتصال ماژول به شبکه ساعت داخلی ماژول رو با زمان شبکه تنظیم میکنه. البته این دستور توی حافظه ماژول میمونه و لازم نیست هر بار که ماژول روشن شد اون رو فراخوانی کنیم. (یه نکته ای هم توی پرانتز بگم، اونم اینکه بعضی از اپراتورها از این قابلیت پشتیبانی نمی‌کنند. مثلاً من با سیم کارت رایتل نتونستم زمان رو از شبکه بگیرم، اما همراه اول مشکلی نداشت.)

 

ذخیره اطلاعات در کارت حافظه :

برای استفاده از کارت حافظه اول باید هدر ql_fs.h  رو اضافه کنیم تا توابعش برامون قابل دسترسی بشه.

برای وارد کردن اطلاعات توی یک فایل چند مرحله رو باید طی کنیم اول از همه باید با دستور Ql_FS_Open یک فایل رو باز کنیم، بعد با دستور Ql_FS_Seek انتخاب کنیم که کجای فایل میخوایم بنویسیم (مثلاً اول یا آخر فایل) و بعد از اون با دستور Ql_FS_Write متنی رو به فایل اضافه می‌کنیم و بعد با دستور Ql_FS_Flush تغییراتمون رو توی فایل اعمال می‌کنیم و بعد با دستور Ql_FS_Close فایل رو می‌بندیم و آزاد می‌کنیم و تمام?. البته هر کدوم از این دستورها آپشن های مختلفی دارند که پیشنهاد می‌کنم طریقه استفاده هر کدوم از اونها رو به طور کامل توی داکیومنت Quectel_MC60-OpenCPU_User_Guide بخونید که توی فایل‌هایی که توی قسمت اول قرار دادیم هست.

حالا توی خط 189 تا 222 اومدیم و از این دستورات استفاده کردیم، البته اول از همه بررسی کردیم که سال سیستم بزرگ‌تر از 2020 باشه تا مطمئن بشیم که تاریخ ماژول درست هست بعد از اون هم اومدیم و خروجی تقسیم ثانیه رو بر 2 بررسی کردیم که برابر با 0 باشه، این باعث میشه تا این شرط هر 2 ثانیه یکبار اجرا به شه، مثلاً میتونید عدد 15 رو قرار بدید تا هر 15 ثانیه یکبار اجرا بشه.

بعد از اون با دستور Ql_FS_CheckDir وجود پوشه رو بررسی کردیم و اگه نبود یه دایرکتوری می‌سازیم و بعد هم اطلاعات رو توی فایلی توی اون پوشه ذخیره می‌کنیم.

 if (LOG_TO_SD == 1 && SysTime.year > 2020 && (SysTime.second % 2 == 0))
{
Ql_GPIO_SetLevel(LED_2, PINLEVEL_HIGH);
ret = Ql_FS_CheckDir(DATA_LOG_DIR);
if (ret == QL_RET_ERR_FILENOTFOUND)
{
ret = Ql_FS_CreateDir(DATA_LOG_DIR);
if (ret = QL_RET_ERR_FILENOTFOUND)
{
APP_DEBUG("failed Create Dir: %s !\r\n", DATA_LOG_DIR);
}
}
else if (ret == QL_RET_OK)
{
Ql_sprintf(filePath, "%s\\%d_%d.txt\0", DATA_LOG_DIR, SysTime.month, SysTime.day);
handle = Ql_FS_Open(filePath, QL_FS_CREATE);
if (handle > 0)
{
Ql_FS_Seek(handle, 0, QL_FS_FILE_END);
Ql_FS_Write(handle, RMC_BUFFER, Ql_strlen(RMC_BUFFER), &writenLen);
Ql_FS_Flush(handle);
Ql_FS_Close(handle);
}
else
{
APP_DEBUG("file does not exist \r\n");
}
}
else
{
APP_DEBUG("ERROR READ SD \r\n", ret);
}
Ql_GPIO_SetLevel(LED_2, PINLEVEL_LOW);
}

ارسال مکان آفلاین با SMS :

توی تابع زیر هم اومدیم و بررسی کردیم اگر پیامکی با محتوای “send loc” برای ردیاب mc60 اومد، اول طول و عرض جغرافیایی رو استخراج کنیم و بعد توی پیامک برای شماره‌ای که توی خط 36 تعریف کردیم بفرسته.

static void Hdlr_RecvNewSMS(u32 nIndex, bool bAutoReply)
{
ST_RIL_SMS_TextInfo *pTextInfo = NULL;
ST_RIL_SMS_DeliverParam *pDeliverTextInfo = NULL;
char aPhNum[RIL_SMS_PHONE_NUMBER_MAX_LEN] = {
0,
};
pTextInfo = Ql_MEM_Alloc(sizeof(ST_RIL_SMS_TextInfo));
Ql_memset(pTextInfo, 0x00, sizeof(ST_RIL_SMS_TextInfo));
RIL_SMS_ReadSMS_Text(nIndex, LIB_SMS_CHARSET_GSM, pTextInfo);
pDeliverTextInfo = &((pTextInfo->param).deliverParam);
Ql_strcpy(aPhNum, pDeliverTextInfo->oa);
APP_DEBUG("data = %s\r\n", (pDeliverTextInfo->data));
char text[350];
Ql_strcpy(text, (pDeliverTextInfo->data));

if (Ql_strcmp(“send loc”, text) == 0)
{
if (LAST_FIX[30] = 'A')
{
char lat[10], lon[10];
float flat, flon;
int c = 0;

while (c < 9)
{
lat[c] = LAST_FIX[32 + c];
c++;
}
lat[c] = '\0';
c = 0;
while (c < 10)
{
lon[c] = LAST_FIX[44 + c];
c++;
}
lon[c] = '\0';
flat = Ql_atof(lat) / 100;
flon = Ql_atof(lon) / 100;

Ql_sprintf(text, "lat= %f , lon= %f\0", flat, flon);
SMS_TextMode_Send(aPhNum, text);
}
else
{
SMS_TextMode_Send(aPhNum, "GPS NOT FIXED YET");
}
}
Ql_MEM_Free(pTextInfo);

return;
}

توی قسمت بعد سراغ ساخت یک MP3 پلیر با ماژول MC60 و همین برد جدید میریم و موزیک‌ها رو از روی کارت حافظه خوانده و با چند تا دکمه وضعیت پخش اون رو کنترل می‌کنیم.

 

 

 

منبع:سیسوگ

مطلب قبلیآموزش STM32 با توابع LL قسمت دهم: مبدل آنالوگ به دیجیتال (ADC)
مطلب بعدیSTM یا مشابه های چینی

پاسخ دهید

لطفا نظر خود را وارد کنید!
لطفا نام خود را در اینجا وارد کنید