1. Unboxing Contents:
- AiPi-R2 development board ×1
- Camera module: Coovision CV031C50 ×1
- 4-inch touch display: Uye UE040WV ×1
- 65dB microphone ×1
- 8Ω2W speakers ×2
- Connection cables ×2
Issue: Internal packaging is very detailed, but the outer carton is too soft, risking hardware damage.
2. Environment Setup
Using VScode for compilation. Refer to: “Beginner Guide: Setting up AiPi-R2 Windows Environment” on Ai-Thinker Forum.
Encountered issue during compilation:
Fix: Open aithinker_Ai-M6X_SDK\project.build, comment out lines 75 and 76 by adding # at the beginning and save.
# cp $(BL_SDK_BASE)/bsp/board/bl616dk/config/edata.bin build/build_out
# cp $(BL_SDK_BASE)/bsp/board/bl616dk/config/Rx_boot2_m61.bin build/build_out
3. Example Testing
No onboard LED (recommended to add one), making it hard to verify board operation without a display.
LVGL Demo: Reflashed official LVGL example using make for compilation and make flash COMX=COM3 for flashing (COM3 is USB-TTL port).
Re-burn the LVGL official routines. Use make to compile the code and make flash COMX=COM3 to burn it. COM3 is the port currently connected to the development board. Use USB to TTL. The connection diagram is as follows:
4. LVGL Integration & Testing
LVGL was customized based on the AiPi-Eyes-Rx example. After GUI design using GuiGuider, generate and replace the generated folder under demos\ai_lvgl\src\generated.
5. SPI TF Card Reading & FATFS Filesystem
Wired the onboard SPI to a TF card module (or solder a TF slot). Adjust pin definitions accordingly.
Key SPI code: Refer to
aithinker_Ai-M6X_SDK\examples\peripherals\spi\spi_poll,SPI configuration and TF card initialization code included in the document.
void spi_isr(int irq, void *arg)
{
uint32_t intstatus = bflb_spi_get_intstatus(spi0);
if (intstatus & SPI_INTSTS_TC) {
bflb_spi_int_clear(spi0, SPI_INTCLR_TC);
//printf("tc done\r\n");
spi_tc_done_count++;
}
if (intstatus & SPI_INTSTS_TX_FIFO) {
//printf("tx fifo\r\n");
}
if (intstatus & SPI_INTSTS_RX_FIFO) {
//printf("rx fifo\r\n");
}
}
void SPI_init(u32 freqm)
{
struct bflb_spi_config_s spi_cfg =
{
#if (SPI_CASE_SELECT == SPI_MASTER_CASE)
.freq = freqm,
.role = SPI_ROLE_MASTER,
#else
.freq = 32 * 1000 * 1000,
.role = SPI_ROLE_SLAVE,
#endif
.mode = SPI_MODE3,
.data_width = SPI_DATA_WIDTH_8BIT,
.bit_order = SPI_BIT_MSB,
.byte_order = SPI_BYTE_LSB,
.tx_fifo_threshold = 0,
.rx_fifo_threshold = 0,
};
spi0 = bflb_device_get_by_name("spi0");
bflb_spi_init(spi0, &spi_cfg);
bflb_spi_tcint_mask(spi0, false);
bflb_irq_attach(spi0->irq_num, spi_isr, NULL);
bflb_irq_enable(spi0->irq_num);
bflb_spi_feature_control(spi0, SPI_CMD_SET_CS_INTERVAL, 0);
}// Modify board.h and add the following:
void board_spi0_gpio_init()
{
struct bflb_device_s* gpio;
gpio = bflb_device_get_by_name("gpio");
// spi cs
bflb_gpio_init(gpio, GPIO_PIN_20, GPIO_FUNC_SPI0 | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);
// spi clk
bflb_gpio_init(gpio, GPIO_PIN_17, GPIO_FUNC_SPI0 | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);
// spi miso
bflb_gpio_init(gpio, GPIO_PIN_22, GPIO_FUNC_SPI0 | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);
// spi mosi
bflb_gpio_init(gpio, GPIO_PIN_15, GPIO_FUNC_SPI0 | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);
}// Below is the SPI initialization and SD card read code:
u8 SD_Initialize(void)
{
u8 r1; // Response from SD card
u16 retry; // Retry counter
u8 buf[4];
u16 i;
SPI_init(281000); // Set to low speed
SD_DisSelect();
for(i=0;i<10;i++)
{
SD_SPI_ReadWriteByte(0XFF); // Send at least 74 clock cycles
}
retry=20;
do
{
r1=SD_SendCmd(CMD0,0,0x95); // Enter IDLE state
} while((r1!=0X01) && retry--);
SD_DisSelect();
SD_Type=0; // Default: no card detected
if(r1==0X01)
{
r1=SD_SendCmd(CMD8,0x1AA,0x87);
if(r1==1)
{
for(i=0;i<4;i++)
{
buf[i]=SD_SPI_ReadWriteByte(0xFF);
}
SD_DisSelect();
if(buf[2]==0X01&&buf[3]==0XAA)
{
retry=0XFFFE;
do
{
r1=SD_SendCmd(CMD55,0,0x01); // Send CMD55
r1=SD_SendCmd(0x69,0x40000000,0x01); // Send CMD41
SD_DisSelect();
} while(r1&&retry--);
if(retry && SD_SendCmd(CMD58,0,0X01)==0)
{
for(i=0;i<4;i++)
{
buf[i]=SD_SPI_ReadWriteByte(0xFF);
}
SD_DisSelect();
if(buf[0]&0x40)
SD_Type=SD_TYPE_V2HC;
else
SD_Type=SD_TYPE_V2;
}
}
}
else // SD V1.x / MMC V3
{
SD_SendCmd(CMD55,0,0X01);
r1=SD_SendCmd(CMD41,0,0X01);
SD_DisSelect();
if(r1<=1)
{
SD_Type=SD_TYPE_V1;
retry=0XFFFE;
do
{
SD_SendCmd(CMD55,0,0X01);
r1=SD_SendCmd(CMD41,0,0X01);
SD_DisSelect();
} while(r1&&retry--);
}
else // MMC card does not support CMD55+CMD41
{
SD_Type=SD_TYPE_MMC;
retry=0XFFFE;
do
{
r1=SD_SendCmd(CMD1,0,0X01);
SD_DisSelect();
} while(r1&&retry--);
}
r1=SD_SendCmd(CMD16,512,0X01);
SD_DisSelect();
if(retry==0 || r1!=0)
{
SD_Type=SD_TYPE_ERR;
}
}
}
SD_DisSelect();
SPI_init(16000000); // Switch to high speed
if(SD_Type)
return 0;
else if(r1)
return r1;
return 0xaa; // Other error
}
u8 SD_SPI_ReadWriteByte(u8 data)
{
u8 retnum;
Spi_Send_Read_Data_Arr(&data, &retnum, 1);
return retnum;
}
void SD_DisSelect(void)
{
SD_SPI_ReadWriteByte(0XFF); // Send 8 extra clock cycles
}
u8 SD_WaitReady(void)
{
u32 t = 0;
u8 rStr = 0;
do
{
rStr = SD_SPI_ReadWriteByte(0xFF);
if(rStr == 0XFF)
return 0; // Ready
t++;
} while(t < 0XFFFFFF);
return 1; // Timeout
}
u8 SD_Select(void)
{
if(SD_WaitReady() == 0)
return 0; // Ready
SD_DisSelect();
return 1; // Failed
}
u8 SD_SendCmd(u8 cmd, u32 arg, u8 crc)
{
u8 r1;
u8 Retry = 0;
SD_DisSelect();
if(SD_Select()) return 0XFF; // Select failed
SD_SPI_ReadWriteByte(cmd | 0x40);
SD_SPI_ReadWriteByte((u8)(arg >> 24));
SD_SPI_ReadWriteByte((u8)(arg >> 16));
SD_SPI_ReadWriteByte((u8)(arg >> 8));
SD_SPI_ReadWriteByte((u8)(arg));
SD_SPI_ReadWriteByte(crc);
if(cmd == 12)
SD_SPI_ReadWriteByte(0xff); // Dummy byte for CMD12
Retry = 0X1F;
do
{
r1 = SD_SPI_ReadWriteByte(0xFF);
} while((r1 & 0X80) && Retry--);
return r1;
}
If you have the system time, you can enter it. If not, just use a random one.
Other files in the file system can be copied directly to the folder and used. The specific interface is as follows:
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
FRESULT f_truncate (FIL* fp); /* Truncate the file */
FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
FRESULT f_closedir (DIR* dp); /* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
FRESULT f_chdir (const TCHAR* path); /* Change current directory */
FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */
FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */
FRESULT f_setcp (WORD cp); /* Set current code page */
int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
6. Wi-Fi Connection and TCP/IP Communication
For Wi-Fi connection initialization and connection code, refer to: aithinker_Ai-M6X_SDK\examples\wifi\sta\wifi_tcp. Key code to note:
uint8_t wifi_connect(char* ssid, char* passwd)
{
int ret = 255;
// struct fhost_vif_ip_addr_cfg ip_cfg = { 0 };
uint32_t ipv4_addr = 0;
scan_item_cb_t resd;
char we1;
char we2;
if (NULL==ssid || 0==strlen(ssid)) {
return 1;
}
if (wifi_mgmr_sta_state_get() == 1) {
wifi_sta_disconnect();
}
printf("wait000");
//wifi_mgmr_scan_ap_all(&we1,&we2,resd);
if (wifi_sta_connect(ssid, passwd, NULL, NULL, 0, 0, 0, 1))
//if (wifi_mgmr_sta_quickconnect(ssid, passwd, 0, 0))
{
return 4;
}
LOG_I("Wating wifi connet");
//Wait for the connection to succeed
sta_ConnectStatus = 0;
for (int i = 0;i<10*30;i++) {
vTaskDelay(100/portTICK_PERIOD_MS);
switch (sta_ConnectStatus) {
case CODE_WIFI_ON_MGMR_DONE:
return 3;
case CODE_WIFI_ON_SCAN_DONE:
return 2;
case CODE_WIFI_ON_DISCONNECT: //Connection failed (the number of retries has exceeded but the connection is not successful)
return 4;
case CODE_WIFI_ON_CONNECTED: //)Connection successful (when the Wi-Fi status is STA, it means that the IP (DHCP) is successfully obtained, or a static IP is used)
LOG_I("Wating wifi connet OK");
break;
case CODE_WIFI_ON_GOT_IP:
return 0;
default:
//Wait for the connection to succeed
break;
}
}
return 14; //Connection timeout
}
int wifi_start_firmware_task(void)
{
LOG_I("Starting wifi ...");
/* enable wifi clock */
GLB_PER_Clock_UnGate(GLB_AHB_CLOCK_IP_WIFI_PHY | GLB_AHB_CLOCK_IP_WIFI_MAC_PHY | GLB_AHB_CLOCK_IP_WIFI_PLATFORM);
GLB_AHB_MCU_Software_Reset(GLB_AHB_MCU_SW_WIFI);
/* set ble controller EM Size */
GLB_Set_EM_Sel(GLB_WRAM160KB_EM0KB);
if (0 != rfparam_init(0, NULL, 0)) {
LOG_I("PHY RF init failed!");
return 0;
}
LOG_I("PHY RF init success!");
/* Enable wifi irq */
extern void interrupt0_handler(void);
bflb_irq_attach(WIFI_IRQn, (irq_callback)interrupt0_handler, NULL);
bflb_irq_enable(WIFI_IRQn);
xTaskCreate(wifi_main, (char*)"fw", WIFI_STACK_SIZE, NULL, TASK_PRIORITY_FW, &wifi_fw_task);
return 0;
}
/**
* @brief wifi event handler
* WiFi Event callback
*
* @param code
*/
void wifi_event_handler(uint32_t code)
{
sta_ConnectStatus = code;
BaseType_t xHigherPriorityTaskWoken;
switch (code) {
case CODE_WIFI_ON_INIT_DONE:
{
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_INIT_DONE", __func__);
wifi_mgmr_init(&conf);
}
break;
case CODE_WIFI_ON_MGMR_DONE:
{
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_MGMR_DONE", __func__);
}
break;
case CODE_WIFI_ON_SCAN_DONE:
{
wifi_mgmr_sta_scanlist();
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_SCAN_DONE SSID numbles:%d", __func__, wifi_mgmr_sta_scanlist_nums_get());
}
break;
case CODE_WIFI_ON_CONNECTED:
{
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_CONNECTED", __func__);
void mm_sec_keydump();
mm_sec_keydump();
}
break;
case CODE_WIFI_ON_GOT_IP:
{
ui_load_scr_animation(&guider_ui, &guider_ui.screen_1, guider_ui.screen_1_del, &guider_ui.screen_del, setup_scr_screen_1, LV_SCR_LOAD_ANIM_OVER_LEFT, 0, 0, true, true);
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP", __func__);
}
break;
case CODE_WIFI_ON_DISCONNECT:
{
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_DISCONNECT", __func__);
}
break;
case CODE_WIFI_ON_AP_STARTED:
{
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STARTED", __func__);
}
break;
case CODE_WIFI_ON_AP_STOPPED:
{
LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STOPPED", __func__);
}
break;
case CODE_WIFI_ON_AP_STA_ADD:
{
LOG_I("[APP] [EVT] [AP] [ADD] %lld", xTaskGetTickCount());
}
break;
case CODE_WIFI_ON_AP_STA_DEL:
{
LOG_I("[APP] [EVT] [AP] [DEL] %lld", xTaskGetTickCount());
}
break;
default:
{
LOG_I("[APP] [EVT] Unknown code %u ", code);
}
}
}
The calling method is as follows:
Start the thread in the main function
tcpip_init(NULL, NULL); wifi_start_firmware_task();
char * wifi_code;
char * wifi_name;
wifi_name = lv_textarea_get_text(guider_ui.screen_ta_1);
wifi_code = lv_textarea_get_text(guider_ui.screen_ta_2);
wifi_connect(wifi_name,wifi_code);
Here, we retrieve the Wi-Fi name and password from the text boxes on the LVGL screen.
The TCP/IP reference code is located in the same location as the Wi-Fi code. The key code is as follows:
void wifi_test_tcp_client_init(int argc, char **argv)
{
abort_exec = shell_signal(SHELL_SIGINT, test_close_client);
printf("tcp client task start ...\r\n");
char *addr;
char *port;
struct sockaddr_in remote_addr;
if (argc < 3) {
printf("%s", PING_USAGE);
return;
}
printf("111...\r\n");
/* get address (argv[1] if present) */
addr = "192.168.31.236";//argv[1];
/* get port number (argv[2] if present) */
port = "8888";//argv[2];
while (1) {
printf("222...\r\n");
if ((sock_client = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("TCP Client create socket error\r\n");
return;
}
printf("333...\r\n");
remote_addr.sin_family = AF_INET;
remote_addr.sin_port = htons(atoi(port));
remote_addr.sin_addr.s_addr = inet_addr(addr);
memset(&(remote_addr.sin_zero), 0, sizeof(remote_addr.sin_zero));
printf("444...\r\n");
printf("Server ip Address : %s:%s\r\n", addr, port);
if (connect(sock_client, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) != 0) {
printf("TCP client connect server falied!\r\n");
closesocket(sock_client);
return;
}
printf("TCP client connect server success!\r\n");
printf("Press CTRL-C to exit.\r\n");
total_cnt = 0;
while (1) {
if (write(sock_client, send_buf, sizeof(send_buf)) < 0) {
printf("write falied!\r\n");
break;
}
total_cnt += sizeof(send_buf);
vTaskDelay(500);
}
closesocket(sock_client);
return;
}
}
The calling method is as follows:
- char *ip_addr="192.168.31.62";
- char *ip_port="333";
- *ip_port = lv_textarea_get_text(guider_ui.screen_1_ta_1);
- wifi_test_tcp_server_init(ip_addr,ip_port);
Invocation:
- Get IP and port from LVGL screen input.
- Call wifi_test_tcp_server_init(ip_addr, ip_port) on button click.
7. Summary & Development Ideas
Current test results:
- AiPi-R2 is suitable for smart home endpoints.
- Display and network speed are acceptable.
- TF card access is working.
Future plans:
- Use AiPi-R2 as a smart hub integrated with Mi Home.
- Play MP3 files from TF card. MP4 not supported due to decoding speed limits.
- Network bandwidth is currently limited to key data (image/video transfer not yet feasible).
Ai-Thinker