[活动专区]

【N32G430开发板试用】可视呼叫门铃演示样机

[复制链接]
829|11
手机看帖
扫描二维码
随时随地手机跟帖
xld0932|  楼主 | 2022-7-25 19:57 | 显示全部楼层 |阅读模式
本帖最后由 xld0932 于 2023-7-19 13:24 编辑

@安小芯    @21小跑堂


概述
基于N32G430C8L7-STB开发板搭配外围功能模块实现可视呼叫门铃的功能演示。通过板载的按键检测实现门铃呼叫、开锁功能;当按键呼叫按键后,通过摄像头将门口景象以静态图片的形式显示在TFT显示屏上,同时播放门铃音乐;当按键开锁按键后,在TFT显示屏上显示开锁提示。功能描述虽然简单,但使用的MCU外设技术点不少哦,涉及到了GPIO、SPI、I2S、DMA、UART、FLASH、RTC等,主要是想通过实现这个演示样机的功能来熟悉国民MCU产品,最后为了完成上述功能还需要在标准板的基础上再画一块实现上述功能的扩展板。


软件功能
  • 通过板载的按键实现模拟门铃呼叫按键、开锁的功能;
  • 通过USART2与RS485模块连接,RS485模块连接到远端的门口摄像头,通过发送指令的形式抓拍图片,再通过指令回传图片数据;
  • 通过SPI接口连接SPI接口的TFT显示屏,显示相关信息(**、开锁提示、摄像头图片、音频文件等);
  • 通过SPI接口连接SPI FLASH存储模块,在存储模块中存放GBK中文字库、**图片数据等信息;通过PC端的SecureCRT软件与N32G430C8L7-STB板载的USART1进行通讯,将资源文件通过Xmodem协议下载到SPI FLASH存储模块中;
  • 通过I2S接口连接CS4344音频模块,通过音频线将CS4344模块与音箱相连接;


系统框图
由于N32G430C8L7芯片资源只带有64KB的FLASH和16KB的SRAM空间,所以需要扩展SPI FLASH来存放一些资源文件;在系统中使用I2S来播放WAV文件,为了达到播放音频流畅的效果,WAV文件的读取使用了双缓存结合DMA的实现方式,所以占用了部分的SRAM空间;对于RS485摄像头回传的图片数据是JPEG格式的,所以在实现方案中我们使用了TJPGD3软件解码库,将JPEG格式的图片数据进行解析,并显示到TFT显示屏上,对于软件解码JPEG图片需要占用不少的SRAM的空间,这样留给摄像头图片读回存放的空间就已经不够用了,所以为了解决这个问题,我们想到将摄像头读回的JPEG图片数据暂存在MCU内部FLASH空间,这样就解决了SRAM空间不足的问题了!
系统框图.jpg


硬件准备
  • N32G430C8L7-STB开发板
  • N32G430C8L7-STB_EBK功能扩展板(RS485模块、CS4344模块、SPI FLASH模块、TFT显示屏)
  • RS485接口摄像头
  • 音箱
  • 相关数据连接线
硬件环境.jpg


N32G430C8L7-STB_EBK功能扩展板
原理图
扩展板原理图.png

PCB-2D正面图
扩展板PCB.png

焊接成品
扩展板成品图.jpg


资源文件
GBK中文字库存放在SPI FLASH起始地址为0的位置、音频文件存放在SPI FLASH起始地址为0x100000的位置、**等图片信息存放在SPI FLASH起始地址为0x200000的位置;在下载资源到SPI FLASH时,需要将config.h文件中宏定义修改为如下所示:
下载配置.png

然后打开PC端的SecureCRT软件下载资源文件,输入的命令为XMODEM 1 XXXXX,其中1表示PC端将数据下传到MCU,经MCU将数据写入到SPI FLASH,第二个参数是指令下载的起始地址;XMODEM协议在传输时带有校验和重传机制,所以在出现ERROR后不用担心数据丢失或者数据不正确,如下图所示:
下载资源.png

在TFT初始显示界面上有两个**显示,如果这些图片存放在MCU FLASH空间会导致后面的功能代码空间不足,所以就存放在SPI FLASH存储了;在取模时需要结合代码的实现来设定,如下所示:
取模1.jpg 取模2.jpg

其中勾选了包含图像头数据为了获取图片的宽度、高度信息,方便程序中排版显示。


代码实现
代码部分仅摘录了部分,完整的功能代码可以下载文末的软件工程源代码附件。

摄像头相关部分
void CAMERA_RxHandler(void)
{
    static uint8_t  RxStart   = 0;
    static uint16_t RxIndex   = 0;
    static uint8_t  RxBuffer[600];
    static uint32_t RxLength  = 0;

    if(QUEUE_EMPTY(QUEUE_CAMERA_IDX) == 0)
    {
        uint8_t RxData = QUEUE_READ(QUEUE_CAMERA_IDX);

        if(RxStart == 0)
        {
            if(RxData == CAMERA_START)
            {
                RxStart = 1;
                RxIndex = 0;

                memset(RxBuffer, 0, sizeof(RxBuffer));

                RxBuffer[RxIndex++] = RxData;
            }
        }
        else
        {
            if(RxIndex == 1)
            {
                RxBuffer[RxIndex++] = RxData;
            }
            else
            {
                RxBuffer[RxIndex++] = RxData;

                switch(RxBuffer[1])
                {
                    case CAMERA_CMD_H:
                        if((RxIndex ==  4) && (RxBuffer[RxIndex-1] == CAMERA_END))
                        {
                            RxStart = 0;
                        }
                        break;

                    case CAMERA_CMD_R:
                        if((RxIndex == 10) && (RxBuffer[RxIndex-1] == CAMERA_END))
                        {
                            RxStart = 0;

                            CAMERA_PictureSize   = RxBuffer[6];
                            CAMERA_PictureSize <<= 8;
                            CAMERA_PictureSize  |= RxBuffer[5];
                            CAMERA_PictureSize <<= 8;
                            CAMERA_PictureSize  |= RxBuffer[4];
                            CAMERA_PictureSize <<= 8;
                            CAMERA_PictureSize  |= RxBuffer[3];

                            CAMERA_PacketTotal   = RxBuffer[8];
                            CAMERA_PacketTotal <<= 8;
                            CAMERA_PacketTotal  |= RxBuffer[7];

                            CAMERA_CMD_Running = 0;
                        }
                        break;

                    case CAMERA_CMD_E:
                        if((RxIndex ==  4) && (RxBuffer[RxIndex-1] == CAMERA_END))
                        {
                            RxStart = 0;
                        }
                        break;

                    case CAMERA_CMD_F:
                        if(CAMERA_PacketIndex < CAMERA_PacketTotal)
                        {
                            RxLength = CAMERA_PACKET_SIZE;
                        }
                        else
                        {
                            RxLength = CAMERA_PictureSize % CAMERA_PACKET_SIZE;
                        }

                        if(RxIndex == (RxLength + 9))
                        {
                            RxStart = 0;

                            uint16_t PacketIndex = 0;
                            uint16_t PictureSize = 0;
                            uint32_t SRAM_Offset = 0;

                            PacketIndex   = RxBuffer[4];
                            PacketIndex <<= 8;
                            PacketIndex  |= RxBuffer[3];

                            PictureSize   = RxBuffer[6];
                            PictureSize <<= 8;
                            PictureSize  |= RxBuffer[5];

                            SRAM_Offset = (PacketIndex - 1) * CAMERA_PACKET_SIZE;

                            if(((PacketIndex - 1) % 4) == 0)
                            {
                                FLASH_Unlock();
                                FLASH_One_Page_Erase(0x0800A000 + SRAM_Offset);
                                FLASH_Lock();
                            }

                            FLASH_Unlock();

                            for(uint16_t i = 0; i < PictureSize; i+=4)
                            {
                                FLASH_Word_Program(i+SRAM_Offset+0x0800A000, *((uint32_t *)(&RxBuffer[i+7])));
                            }

                            FLASH_Lock();

                            CAMERA_CMD_Running = 0;
                        }
                        break;

                    case CAMERA_CMD_D: break;
                    case CAMERA_CMD_I: break;
                    case CAMERA_CMD_Q: break;
                    default:
                        break;
                }
            }
        }
    }
}


TFT相关部分
void LCD_ShowEN(uint16_t X, uint16_t Y, char ch)
{  
    uint8_t  Data  = 0;
    uint16_t Color = 0;

    for(uint8_t i = 0; i < 16; i++)
    {
        Data = ASCII_1608[ch - 32][i];

        for(uint8_t j = 0; j < 8; j++)
        {
            if((Data >> j) & 0x01) Color = LCD_Forecolor;
            else                   Color = BACKCOLOR;

            LCD_DrawPoint(X+j, Y+i, Color);
        }
    }
}

void LCD_ShowCN(uint16_t StartX, uint16_t StartY, const char *str)
{
    uint8_t Buffer[32];
    uint8_t Array[16][16];
    uint8_t Point[16][16];

    memset(Buffer, 0xFF, sizeof(Buffer));

    SPI_FLASH_GetGBK(str, Buffer);

    for(uint8_t i = 0; i < 8; i++)
    {
        for(uint8_t j = 0; j < 4; j++)
        {
            uint8_t Data = Buffer[i*4+j];

            for(uint8_t k = 0; k < 4; k++)
            {
                if(Data & (0x08 >> (k-0)))  Array[i*2+0][j*4+k-0] = 1;
                else                        Array[i*2+0][j*4+k-0] = 0;
            }

            for(uint8_t k = 4; k < 8; k++)
            {
                if(Data & (0x80 >> (k-4)))  Array[i*2+1][j*4+k-4] = 1;
                else                        Array[i*2+1][j*4+k-4] = 0;
            }
        }
    }

    for(uint8_t i = 0; i < 16; i++)
    {
        for(uint8_t j = 0; j < 16; j++)
        {
            Point[i][j] = Array[j][15-i];
        }
    }

    for(uint8_t i = 0; i < 16; i++)
    {
        for(uint8_t j = 0; j < 16; j++)
        {
            if(Point[15-i][j])
            {
                LCD_DrawPoint(StartX+i, StartY+j, LCD_Forecolor);
            }
            else
            {
                LCD_DrawPoint(StartX+i, StartY+j, BACKCOLOR);
            }
        }
    }
}

void LCD_ShowLOG(uint16_t StartX, uint16_t StartY, const char *str)
{
    while(*str != '\0')
    {
        if(*str < 0x7F)
        {
            if(StartX > (LCD_WIDTH  - 8))
            {
                StartX = 0; StartY += 16;
            }

            if(StartY > (LCD_HEIGHT - 16))
            {
                StartX = 0; StartY = 0;

                LCD_ClearScreen(BACKCOLOR);
            }

            LCD_ShowEN(StartX, StartY, *str);

            StartX += 0x08;
            str    += 0x01;
        }
        else
        {
            if(StartX > (LCD_WIDTH  - 16))
            {
                StartX = 0; StartY += 16;
            }

            if(StartY > (LCD_HEIGHT - 16))
            {
                StartX = 0; StartY = 0;

                LCD_ClearScreen(BACKCOLOR);
            }

            LCD_ShowCN(StartX, StartY,  str);

            StartX += 0x10;
            str    += 0x02;
        }
    }
}

void LCD_DrawImage(void)
{
    uint16_t StartX = 0, StartY = 0;
    uint16_t Width  = 0, Height = 0, Index = 0;
    uint32_t Address = 0, Offset = 0, Length = 0;

    uint8_t  Buffer[200];


    Address = 0x20204C;
    Offset  = 0;
    Length  = 0x001A84;
    Index   = 8;

    SPI_FLASH_FastRead(Address + Offset, Buffer, 8);

    Width    = Buffer[2];
    Width  <<= 8;
    Width   |= Buffer[3];

    Height   = Buffer[4];
    Height <<= 8;
    Height  |= Buffer[5];

    StartX = (240 - Width ) / 2;
    StartY = 30;

    LCD_SetAddress(StartX, StartY, StartX + Width - 1, StartY + Height - 1);

    GPIO_Pins_Set(GPIOB, GPIO_PIN_11);

    do
    {
        SPI_FLASH_FastRead(Address + Offset + Index, Buffer, sizeof(Buffer));

        for(uint8_t i = 0; i < sizeof(Buffer); i++)
        {
            if(Index++ < Length)
            {
                LCD_SPI_WriteByte(Buffer[i]);
            }
        }
    }while(Index < Length);


    Address = 0x200000;
    Offset  = 0;
    Length  = 0x00204C;
    Index   = 8;

    SPI_FLASH_FastRead(Address + Offset, Buffer, 8);

    Width    = Buffer[2];
    Width  <<= 8;
    Width   |= Buffer[3];

    Height   = Buffer[4];
    Height <<= 8;
    Height  |= Buffer[5];

    StartX = (240 - Width ) / 2;
    StartY = 255;

    LCD_SetAddress(StartX, StartY, StartX + Width - 1, StartY + Height - 1);

    GPIO_Pins_Set(GPIOB, GPIO_PIN_11);

    do
    {
        SPI_FLASH_FastRead(Address + Offset + Index, Buffer, sizeof(Buffer));

        for(uint8_t i = 0; i < sizeof(Buffer); i++)
        {
            if(Index++ < Length)
            {
                LCD_SPI_WriteByte(Buffer[i]);
            }
        }
    }while(Index < Length);
}


WAV相关部分
uint8_t WAV_DecodeFile(WAV_TypeDef *pWav, uint32_t Address)
{
    ChunkRIFF_TypeDef *WAV_RIFF;
    ChunkFMT_TypeDef  *WAV_FMT ;
    ChunkFACT_TypeDef *WAV_FACT;
    ChunkDATA_TypeDef *WAV_DATA;

    uint8_t WAV_HeadBuffer[512];

    SPI_FLASH_FastRead(Address, WAV_HeadBuffer, 512);

    /* 获取RIFF块 */
    WAV_RIFF = (ChunkRIFF_TypeDef *)WAV_HeadBuffer;

    /* 是WAV格式文件 */
    if(WAV_RIFF->Format == 0x45564157)
    {
        /* 获取FMT块 */
        WAV_FMT  = (ChunkFMT_TypeDef *)(WAV_HeadBuffer+12);

        /* 读取FACT块 */
        WAV_FACT = (ChunkFACT_TypeDef *)(WAV_HeadBuffer+12+8+WAV_FMT->ChunkSize);

        if((WAV_FACT->ChunkID == 0x74636166) || (WAV_FACT->ChunkID == 0x5453494C))
        {
            /* 具有FACT/LIST块的时候(未测试) */
            pWav->DataStart=12+8+WAV_FMT->ChunkSize+8+WAV_FACT->ChunkSize;
        }
        else
        {
            pWav->DataStart=12+8+WAV_FMT->ChunkSize;
        }

        /* 读取DATA块 */
        WAV_DATA = (ChunkDATA_TypeDef *)(WAV_HeadBuffer+pWav->DataStart);

        /* 解析成功 */
        if(WAV_DATA->ChunkID == 0x61746164)
        {
            pWav->AudioFormat   = WAV_FMT->AudioFormat;     /* 音频格式 */
            pWav->nChannels     = WAV_FMT->NumOfChannels;   /* 通道数 */
            pWav->SampleRate    = WAV_FMT->SampleRate;      /* 采样率 */
            pWav->BitRate       = WAV_FMT->ByteRate*8;      /* 得到位速 */
            pWav->BlockAlign    = WAV_FMT->BlockAlign;      /* 块对齐 */
            pWav->BitsPerSample = WAV_FMT->BitsPerSample;   /* 位数,16/24/32位 */

            pWav->DataSize      = WAV_DATA->ChunkSize;      /* 数据块大小 */
            pWav->DataStart     = pWav->DataStart+8;        /* 数据流开始的地方 */

            printf("\r\npWav->AudioFormat   : %d", pWav->AudioFormat);
            printf("\r\npWav->nChannels     : %d", pWav->nChannels);
            printf("\r\npWav->SampleRate    : %d", pWav->SampleRate);
            printf("\r\npWav->BitRate       : %d", pWav->BitRate);
            printf("\r\npWav->BlockAlign    : %d", pWav->BlockAlign);
            printf("\r\npWav->BitsPerSample : %d", pWav->BitsPerSample);
            printf("\r\npWav->DataSize      : %d", pWav->DataSize);
            printf("\r\npWav->DataStart     : %d", pWav->DataStart);

            WAV_Offset   = pWav->DataStart + Address;
            WAV_DataSize = pWav->DataSize;
        }
    }

    return 0;
}

void WAV_PrepareData(void)
{
    if(WAV_NextIndex == 0)
    {
        SPI_FLASH_FastRead(WAV_Offset + WAV_TxLength, WAV_DataBuffer[0], WAV_BUFFER_SIZE);
    }
    else
    {
        SPI_FLASH_FastRead(WAV_Offset + WAV_TxLength, WAV_DataBuffer[1], WAV_BUFFER_SIZE);
    }

    WAV_TxLength += WAV_BUFFER_SIZE;

    if(WAV_TxLength > WAV_DataSize) WAV_PlayEnded = 1;
}

void WAV_PlayHandler(void)
{
    if(WAV_PlayEnded == 0)
    {
        CS4344_I2S_DMA_Transfer((uint16_t *)&WAV_DataBuffer[WAV_NextIndex][0], (WAV_BUFFER_SIZE / 2));
    }
    else
    {
        DMA_Channel_Disable(DMA_CH1);

        printf("\r\nWAV Play Finish!\r\n");     WAV_PlayState = 0;
    }

    if(WAV_NextIndex == 0) WAV_NextIndex = 1;
    else                   WAV_NextIndex = 0;
}

void WAV_PlaySong(void)
{
    WAV_TypeDef WaveFile;

    if(WAV_PlayState == 0)
    {
        if(WAV_DecodeFile(&WaveFile, 0x100000) == 0)
        {
            if((WaveFile.BitsPerSample == 16) && (WaveFile.nChannels == 2) &&
               (WaveFile.SampleRate  > 44000) && (WaveFile.SampleRate < 48100))
            {
                WAV_NextIndex = 0;
                WAV_PlayEnded = 0;
                WAV_TxLength  = 0;
                WAV_PlayState = 1;

                printf("\r\n");
                printf("\r\nWAV Data Size : %d, Data Start : 0x%08x", WAV_DataSize, WAV_Offset);
                printf("\r\n");

                WAV_PrepareData();
                WAV_PlayHandler();
            }
            else
            {
                printf("\r\nWAV File Format Error!\r\n"); return;
            }
        }
        else
        {
            printf("\r\nNo WAV File!\r\n"); return;
        }
    }
    else
    {
        printf("\r\nThe Song Is Not Over Yet!\r\n");
    }
}


KEY相关部分
void KEY_Init(void)
{
    GPIO_InitType GPIO_InitStructure;

    RCC_AHB_Peripheral_Clock_Enable(RCC_AHB_PERIPH_GPIOA);

    GPIO_Structure_Initialize(&GPIO_InitStructure);
    GPIO_InitStructure.Pin       = GPIO_PIN_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_MODE_INPUT;
    GPIO_InitStructure.GPIO_Pull = GPIO_NO_PULL;
    GPIO_Peripheral_Initialize(GPIOA, &GPIO_InitStructure);

    GPIO_Structure_Initialize(&GPIO_InitStructure);
    GPIO_InitStructure.Pin       = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_MODE_INPUT;
    GPIO_InitStructure.GPIO_Pull = GPIO_PULL_UP;
    GPIO_Peripheral_Initialize(GPIOA, &GPIO_InitStructure);

    TASK_Append(TASK_ID_KEY, KEY_Scan, 10);
}

void KEY_Handler(char *Name, uint8_t State, uint8_t Value)
{
    if(strcmp(Name, "WAKEUP") == 0)
    {
        if(State == 0)
        {
            CAMERA_TakePhoto();

            for(uint16_t i = 1; i <= CAMERA_PacketTotal; i++)
            {
                CAMERA_GetPacket(i);
            }

            CAMERA_DrawImage();

#if ENABLE_WAV
            WAV_PlaySong();
#endif
        }
    }

    if(strcmp(Name, "KEY3") == 0)
    {
        if(State == 1)
        {
            LCD_SetForecolor(FORECOLOR);
            LCD_ShowLOG(76, 230, "正在开门...");
        }
        else
        {
            LCD_SetForecolor(FORECOLOR);
            LCD_ShowLOG(76, 230, "           ");
        }
    }
}

void KEY_SubScan(uint8_t *State, uint8_t *Count, uint8_t Value, uint8_t Active, char *Name)
{
    if(*State == 0)
    {
        if(Value == Active) *Count += 1;
        else                *Count  = 0;

        if(*Count > 5)
        {
            *Count = 0; *State = 1;
            printf("\r\n%s Pressed", Name); KEY_Handler(Name, 1, false);
        }
    }
    else
    {
        if(Value != Active) *Count += 1;
        else                *Count  = 0;

        if(*Count > 5)
        {
            *Count = 0; *State = 0;
            printf("\r\n%s Release", Name); KEY_Handler(Name, 0, true);
        }
    }
}

void KEY_Scan(void)
{
    static uint8_t KeyState[4] = {0, 0, 0, 0};
    static uint8_t KeyCount[4] = {0, 0, 0, 0};

    KEY_SubScan(&KeyState[0], &KeyCount[0], GPIO_Input_Pin_Data_Get(GPIOA, GPIO_PIN_0), PIN_SET,   "WAKEUP");
    KEY_SubScan(&KeyState[1], &KeyCount[1], GPIO_Input_Pin_Data_Get(GPIOA, GPIO_PIN_4), PIN_RESET, "KEY1");
    KEY_SubScan(&KeyState[2], &KeyCount[2], GPIO_Input_Pin_Data_Get(GPIOA, GPIO_PIN_5), PIN_RESET, "KEY2");
    KEY_SubScan(&KeyState[3], &KeyCount[3], GPIO_Input_Pin_Data_Get(GPIOA, GPIO_PIN_6), PIN_RESET, "KEY3");
}


运行效果 & 演示视频
https://www.bilibili.com/video/BV1XG4y1i7wr?vd_source=c2d3413b74687345dfc8205a8d735598
待机显示界面.jpg


附件
软件工程源代码: VideoDoorPhone_20220724.zip (473.49 KB)

使用特权

评论回复
maqianqu| | 2022-8-16 22:14 | 显示全部楼层
这个没有功能代码吗   

使用特权

评论回复
xld0932|  楼主 | 2022-8-17 08:54 | 显示全部楼层
maqianqu 发表于 2022-8-16 22:14
这个没有功能代码吗

不是有上传附件嘛……完整的软件工程代码呀,怎么说没有呢?

使用特权

评论回复
xld0932|  楼主 | 2022-8-24 15:12 | 显示全部楼层

使用特权

评论回复
uptown| | 2022-9-3 17:23 | 显示全部楼层
可以实现图片的传输吗

使用特权

评论回复
adolphcocker| | 2022-9-3 20:33 | 显示全部楼层
这个看着比较高端了。   

使用特权

评论回复
timfordlare| | 2022-9-3 20:52 | 显示全部楼层
语音的传输使用压缩算法了吗   

使用特权

评论回复
xld0932|  楼主 | 2022-9-4 20:14 | 显示全部楼层
timfordlare 发表于 2022-9-3 20:52
语音的传输使用压缩算法了吗

语音是通过I2S接口实现的,音频文件(WAV文件)存放在SPI FLASH中,通过MCU的SPI接口读取SPI FLASH中的音频数据进行软件解码,然后通过I2S接口传输音频数据,进行播放的……

使用特权

评论回复
janewood| | 2022-12-5 21:16 | 显示全部楼层
这个图像是怎么传输呢?              

使用特权

评论回复
claretttt| | 2022-12-5 21:56 | 显示全部楼层
楼主做的这个可视化门铃确实很棒。

使用特权

评论回复
alvpeg| | 2022-12-6 12:50 | 显示全部楼层
使用的是什么音频模块?              

使用特权

评论回复
wwppd| | 2022-12-6 15:41 | 显示全部楼层
能不能实现远程wifi通信呢              

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:上海灵动微电子股份有限公司资深现场应用工程师
简介:诚信·承诺·创新·合作

67

主题

2992

帖子

29

粉丝