基于串口环形队列的BootLoader实现

一起学嵌入式 2023-08-25 07:50

扫描关注一起学嵌入式,一起学习,一起成长

大家好,之前分享过很多软件版本升级相关的文章:#软件版本升级

今天分享一篇MCU端基于环形队列接收升级数据包的实现文章,希望能够帮助到大家。以下为原文:


本文所用的型号是STM32F103C8T6,这个IC有64KFlash和20K的RAM,也有小道消息说有后置隐藏的64K,也就是说其实是有128K,我一直也没有测试,有空测测,有大神这样说,估计是可以的。

这里重点记录一下我写的IAP思路和代码以及细节和遇到坑的地方。先大体的概述一下,最后贴上我认为重点的代码。

在概述之前先要解决一个问题,那就是sram空间和flash空间的问题,sram只有20K,flash有64k。

解决的办法有很多:

1)最常见的就是自己写上位机软件,通过分包发送,期间还可以加入加密算法,校验等等。

2)使用环形队列,简单点说就是个环形数组,一边接收上位机数据,一边往flash里面写。

这里条件限制就采用第二种方法。所以即使是分给A和B的25K空间的flash空间,sram只有20K也是不能一次接收完所有的bin数据的,这里我只开辟了一个1K的BUF,使用尾插法写入,我的测试应用程序都在5-6K,用这样的方法可以在9600波特率下测试稳定,也试过57600的勉强可以的,115200就不行了。

环形队列代码如下:

C文件:

#include "fy_looplist.h"
 
#include "fy_includes.h"
 
 
#ifndef NULL
#define NULL 0
#endif
 
#ifndef min
#define min(a, b) (a)<(b)?(a):(b) //< 获取最小值
#endif
 
#define DEBUG_LOOP 1
 
static int Create(_loopList_s* p,unsigned char *buf,unsigned int len);
static void Delete(_loopList_s* p);
static int Get_Capacity(_loopList_s *p);
static int Get_CanRead(_loopList_s *p);
static int Get_CanWrite(_loopList_s *p);
static int Read(_loopList_s *p, void *buf, unsigned int len);
static int Write(_loopList_s *p, const void *buf, unsigned int len);
 
struct _typdef_LoopList  _list=
{
    Create,
    Delete,
    Get_Capacity,
    Get_CanRead,
    Get_CanWrite,
    Read,
    Write
};
 
 
//初始化环形缓冲区
static int Create(_loopList_s* p,unsigned char *buf,unsigned int len)
{
    if(NULL == p)
    {
#if DEBUG_LOOP
        printf("ERROR: input list is NULL\n");
#endif
        return 0;
    }
    p->capacity = len;
    p->buf = buf;
    p->head = p->buf;//头指向数组首地址
    p->tail = p->buf;//尾指向数组首地址
 
    return 1;
}
 
//删除一个环形缓冲区
static void Delete(_loopList_s* p)
{
    if(NULL == p)
    {
#if DEBUG_LOOP
        printf("ERROR: input list is NULL\n");
#endif
        return;
    }
 
    p->buf  = NULL;//地址赋值为空
    p->head = NULL;//头地址为空
    p->tail = NULL;//尾地址尾空
    p->capacity = 0;//长度为空
}
 
//获取链表的长度
static int Get_Capacity(_loopList_s *p)
{
    if(NULL == p)
    {
#if DEBUG_LOOP
        printf("ERROR: input list is NULL\n");
#endif
        return -1;
    }
    return p->capacity;
}
 
//返回能读的空间
static int Get_CanRead(_loopList_s *p)
{
    if(NULL == p)
    {
#if DEBUG_LOOP
        printf("ERROR: input list is NULL\n");
#endif
        return -1;
    }
 
    if(p->head == p->tail)//头与尾相遇
    {
        return 0;
    }
 
    if(p->head < p->tail)//尾大于头
    {
        return p->tail - p->head;
    }
 
    return Get_Capacity(p) - (p->head - p->tail);//头大于尾
}
 
//返回能写入的空间
static int Get_CanWrite(_loopList_s *p)
{
    if(NULL == p)
    {
#if DEBUG_LOOP
        printf("ERROR: input list is NULL\n");
#endif
        return -1;
    }
 
    return Get_Capacity(p) - Get_CanRead(p);//总的减去已经写入的空间
}
 
 
//  p--要读的环形链表
//  buf--读出的数据
//  count--读的个数
static int Read(_loopList_s *p, void *buf, unsigned int len)
{
    int copySz = 0;
 
    if(NULL == p)
    {
#if DEBUG_LOOP
        printf("ERROR: input list is NULL\n");
#endif
        return -1;
    }
 
    if(NULL == buf)
    {
#if DEBUG_LOOP
        printf("ERROR: input buf is NULL\n");
#endif
        return -2;
    }
 
    if(p->head < p->tail)//尾大于头
    {
        copySz = min(len, Get_CanRead(p)); //比较能读的个数
        memcpy(buf, p->head, copySz);  //读出数据
        p->head += copySz;     //头指针加上读取的个数
        return copySz;      //返回读取的个数
    }
    else //头大于等于了尾
    {
        if (len < Get_Capacity(p)-(p->head - p->buf))//读的个数小于头上面的数据量
        {
            copySz = len;//读出的个数
            memcpy(buf, p->head, copySz);
            p->head += copySz;
            return copySz;
        }
        else//读的个数大于头上面的数据量
        {
            copySz = Get_Capacity(p) - (p->head - p->buf);//先读出来头上面的数据
            memcpy(buf, p->head, copySz);
            p->head = p->buf;//头指针指向数组的首地址
            //还要读的个数
            copySz += Read(p,(char*)buf+copySz, len-copySz);//接着读剩余要读的个数
            return copySz;
        }
    }
}
//  p--要写的环形链表
//  buf--写出的数据
//  len--写的个数
static int Write(_loopList_s *p, const void *buf, unsigned int len)
{
    int tailAvailSz = 0;//尾部剩余空间
 
    if(NULL == p)
    {
#if DEBUG_LOOP
        printf("ERROR: list is empty \n");
#endif
        return -1;
    }
 
    if(NULL == buf)
    {
#if DEBUG_LOOP
        printf("ERROR: buf is empty \n");
#endif
        return -2;
    }
 
    if (len >= Get_CanWrite(p))//如果剩余的空间不够
    {
#if DEBUG_LOOP
        printf("ERROR: no memory \n");
#endif
        return -3;
    }
 
    if (p->head <= p->tail)//头小于等于尾
    {
        tailAvailSz = Get_Capacity(p) - (p->tail - p->buf); //查看尾上面剩余的空间
        if (len <= tailAvailSz)//个数小于等于尾上面剩余的空间
        {
            memcpy(p->tail, buf, len);//拷贝数据到环形数组
            p->tail += len;//尾指针加上数据个数
            if (p->tail == p->buf+Get_Capacity(p))//正好写到最后
            {
                p->tail = p->buf;//尾指向数组的首地址
            }
            return len;//返回写入的数据个数
        }
        else
        {
            memcpy(p->tail, buf, tailAvailSz); //填入尾上面剩余的空间
            p->tail = p->buf;     //尾指针指向数组首地址
            //剩余空间                   剩余数据的首地址       剩余数据的个数
            return tailAvailSz + Write(p, (char*)buf+tailAvailSz, len-tailAvailSz);//接着写剩余的数据
        }
    }
    else //头大于尾
    {
        memcpy(p->tail, buf, len);
        p->tail += len;
        return len;
    }
}
 
 
/*********************************************END OF FILE********************************************/

1、整体思路

把64K的flash空间分成了4个部分,第一部分是BootLoader,第二部分是程序A(APP1),第三部分是程序B(APP2),第四部分是用来存储一些变量和标记的。下面是空间的分配情况。BootLoader程序可以用来更新程序A,而程序A又更新程序B,程序B可以更新程序A。

最开始的时候想的是程序A、B都带更新了,干嘛还多此一举,其实这个Bootloader还是需要的。如果之后程序A、B和FLAG三部分,假设一种情况,在程序B中更新程序A中遇到问题,复位后直接成砖,因为程序A在起始地址,上电直接运行程序A,而程序A现在出问题了,那就没招了。

所以加上BootLoader情况下,不管怎么样BootLoader的程序是不会错的,因为更新不会更新BootLoader,即使更新出错了,还可以进入BootLoader重新更新应用程序。

我见也有另外一种设计方法的,就是应用程序只有一个程序A,把程序B区域的flash当作缓存用,重启的时候判断B区域有没有更新程序,有的话就把B拷贝到A,然后擦除B,我感觉这样其实也一样,反正不管怎么样这部分空间是必须要预留出来的。

这里在keil中配置的只有起始地址和大小,并没有结束地址,我这里也就不详细计算了。总体就是这样的。

2、Bootloader部分

BootLoader的任务有两个,一是在串口中断接收BIN的数据和主循环内判断以及更新APP1的程序,二是在在程序开始的时候判断有没有可用的用户程序进而跳转到用户程序(程序A或者程序B)。

简单介绍下执行流程:

系统上电首先肯定是执行BootLoader程序的,因为它的起始地址就是0x08000000,首先是初始化,然后判断按键是否手动升级程序,按键按下了就把FLAG部分的APP标记写成0xFFFF(这里用的宏定义方式),再执行执行App_Check(),否则就直接执行App_Check()。

App_Check函数是来判断程序A和程序B的,最开始BootLoader是用swd方式下载的,下载的时候全片擦除,所以会执行主循环的Update_Check函数。此时串口打印出“等待接收APP1的BIN”,这个时候发送APP1的BIN过去,等接受完了,会写在FLAG区域写个0xAAAA,代表程序A写入了,下次启动可以执行程序A。

主要代码部分

#include "fy_includes.h"
 
/*
晶振使用的是16M 其他频率在system_stm32f10x.c中修改
使用printf需要在fy_includes.h修改串口重定向为#define PRINTF_USART USART1 
*/

 
 
/*
Bootloader程序
 
完成三个任务
 
步骤1.检查是否有程序更新,如果有就擦写flash进行更新,如果没有进入步骤2
步骤2.判断app1有没有可执行程序,如果有就执行,如果没有进入步骤3
步骤3.串口等待接收程序固件
 
*/

 
#define FLAG_UPDATE_APP1 0xBBAA
#define FLAG_UPDATE_APP2 0xAABB
#define FLAG_APP1 0xAAAA
#define FLAG_APP2 0xBBBB
#define FLAG_NONE 0xFFFF
 
_loopList_s list1;
u8 rxbuf[1024];
u8 temp8[2]; 
u16 temp16;
u32 rxlen=0;
u32 applen=0;
u32 write_addr;
u8 overflow=0;
u32 now_tick=0;
u8 _cnt_10ms=0;
 
static void App_Check(void)
{
    //获取程序标号
    STMFLASH_Read(FLASH_PARAM_ADDR,&temp16,1);
 
    if(temp16 == FLAG_APP1)//执行程序A
    {
        if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//可执行?
        {
            printf(" 执行程序A...\r\n");   
            IAP_RunApp(FLASH_APP1_ADDR);
        }
        else
        {
            printf(" 程序A不可执行,擦除APP1程序所在空间...\r\n");
            for(u8 i=10;i<35;i++)
            {
                STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512);
            }
            printf(" 程序A所在空间擦除完成... \r\n");
            printf(" 将执行程序B... \r\n");
            if(((*(vu32*)(FLASH_APP2_ADDR+4))&0xFF000000)==0x08000000)//可执行?
            {
                printf(" 执行程序B...\r\n");    
                IAP_RunApp(FLASH_APP2_ADDR);
            }
            else
            {
                printf(" 程序B不可执行,擦除APP2程序所在空间...\r\n");
                for(u8 i=35;i<60;i++)
                {
                    STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512);
                }
                printf(" 程序B所在空间擦除完成...\r\n");
            }
        }
    }
    
    if(temp16 == FLAG_APP2)//执行程序B
    {
        if(((*(vu32*)(FLASH_APP2_ADDR+4))&0xFF000000)==0x08000000)//可执行?
        {
            printf(" 执行程序B...\r\n");   
            IAP_RunApp(FLASH_APP2_ADDR);
        }
        else
        {
            printf(" 程序B不可执行,擦除APP2程序所在空间... \r\n");
            for(u8 i=35;i<60;i++)
            {
                STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512);
            }
            printf(" 程序B所在空间擦除完成... \r\n");
            printf(" 将执行程序A... \r\n");
            if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//可执行?
            {
                printf(" 执行程序A...\r\n");    
                IAP_RunApp(FLASH_APP1_ADDR);
            }
            else
            {
                printf(" 程序A不可执行,擦除APP1程序所在空间...\r\n");
                for(u8 i=10;i<35;i++)
                {
                    STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512);
                }
                printf(" 程序A所在空间擦除完成...\r\n");
            }
        }
    }
    
    if(temp16 == FLAG_NONE)
    {
        printf(" 擦除App1程序所在空间...\r\n");
        for(u8 i=10;i<35;i++)
        {
            STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512);
        }
        printf(" 程序A所在空间擦除完成...\r\n");
    }
}
 
 
static void Update_Check(void)
{
    if(_list.Get_CanRead(&list1)>1)
    {
        _list.Read(&list1,&temp8,2);//读取两个数据
         
        temp16 = (u16)(temp8[1]<<8) | temp8[0];
                    
        STMFLASH_Write(write_addr,&temp16,1);
        write_addr+=2;
    }
 
    if(GetSystick_ms() - now_tick >10)//10ms
    {
        now_tick = GetSystick_ms();
        _cnt_10ms++;
        if(applen == rxlen && rxlen)//接收完成
        {
            if(overflow)
            {
                printf("接收溢出,无法更新,请重试 \r\n");
                SoftReset();//软件复位
            }
            else
            {
                printf(" \r\n 接收BIN文件完成,长度为 %d \r\n",applen);
                
                temp16 = FLAG_APP1;
                STMFLASH_Write(FLASH_PARAM_ADDR,&temp16,1);//写入标记
                temp16 = (u16)(applen>>16);
                STMFLASH_Write(FLASH_PARAM_ADDR+2,&temp16,1);
                temp16 = (u16)(applen);
                STMFLASH_Write(FLASH_PARAM_ADDR+4,&temp16,1);
                
                SoftReset();//软件复位
            }
        }else applen = rxlen;//更新长度
    }
    if(_cnt_10ms>=50)
    {
        _cnt_10ms=0;
        Led_Tog();
        if(!rxlen)
        {
            printf(" 等待接收App1的BIN文件 \r\n");
        }
    }
}
int main(void)
{
    NVIC_SetPriorityGrouping( NVIC_PriorityGroup_2);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);  //开启AFIO时钟
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //禁止JTAG保留SWD
    
    Systick_Configuration();
    Led_Configuration();
    Key_Configuration();
    Usart1_Configuration(9600);
    USART_ITConfig(USART1, USART_IT_IDLE, DISABLE);//关闭串口空闲中断
 
    printf(" this is bootloader!\r\n\r\n");
    if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == SET)
    {
        Delay_ms(100);
        if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == SET)//开机按下keyup进行更新
        {
            printf(" 主动更新,");
            temp16 = FLAG_NONE;
            STMFLASH_Write(FLASH_PARAM_ADDR,&temp16,1);
        }
        else
        {
            
        }
    }
 
    App_Check();
    
    printf(" 执行BootLoader程序... \r\n");
    _list.Create(&list1,rxbuf,sizeof(rxbuf));
 
    write_addr = FLASH_APP1_ADDR;
    
    while(1)
    {  
        Update_Check();
    }
}
 
//USART1串口中断函数
void USART1_IRQHandler(void)
{
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        u8 temp = USART1->DR;
        if(_list.Write(&list1,&temp,1)<=0)
        {
            overflow=1;
        }
        rxlen++;
    }
}

其中的宏:

//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000     //STM32 FLASH的起始地址
#define FLASH_APP1_ADDR    STM32_FLASH_BASE+0x2800    //偏移10K
#define FLASH_APP2_ADDR    STM32_FLASH_BASE+0x8c00    //偏移35K
#define FLASH_PARAM_ADDR    STM32_FLASH_BASE+0xF000    //偏移60K

3、程序A和程序B部分

这两个都是用户程序,这两个程序都带有更新程序功能,我这里用作测试的A和B程序大体都差不多,不同的地方就是程序A接收的BIN用来更新程序B,程序B接收的BIN用来更新A,还有就是中断向量表便宜不同以及打印输出不同。

应用程序部分没什么说的,程序A和B很类似,这里贴上A的代码

#include "fy_includes.h"
 
/*
晶振使用的是16M 其他频率在system_stm32f10x.c中修改
使用printf需要在fy_includes.h修改串口重定向为#define PRINTF_USART USART1 
*/

 
 
/*
APP1程序
 
完成两个任务
 
1.执行本身的app任务,同时监听程序更新,监听到停止本身的任务进入到状态2
2.等待接收完成,完成后复位重启
 
*/

 
#define FLAG_UPDATE_APP1 0xBBAA
#define FLAG_UPDATE_APP2 0xAABB
#define FLAG_APP1 0xAAAA
#define FLAG_APP2 0xBBBB
#define FLAG_NONE 0xFFFF
 
_loopList_s list1;
u8 rxbuf[1024];
u8 temp8[2]; 
u16 temp16;
u32 rxlen=0;
u32 applen=0;
u32 write_flsh_addr;
u8 update=0;
u8 overflow=0;
u32 now_tick;
u8 _cnt_10ms=0;
 
static void Update_Check(void)
{
    if(update)//监听到有更新程序
    {
        write_flsh_addr = FLASH_APP2_ADDR;//App1更新App2的程序
        overflow=0;
        rxlen=0;
        _list.Create(&list1,rxbuf,sizeof(rxbuf));
        
        printf(" 擦除APP2程序所在空间...\r\n");
        for(u8 i=35;i<60;i++)//擦除APP2所在空间程序
        {
            STMFLASH_Erase(FLASH_BASE + i*STM_SECTOR_SIZE,512);
        }
        printf(" 程序B所在空间擦除完成...\r\n");
        
        while(1)
        {
            if(_list.Get_CanRead(&list1)>1)
            {
                _list.Read(&list1,&temp8,2);//读取两个数据
                 
                temp16 = (u16)(temp8[1]<<8) | temp8[0];
                            
                STMFLASH_Write(write_flsh_addr,&temp16,1);
                write_flsh_addr+=2;
            }
            
            if(GetSystick_ms() - now_tick >10)//10ms
            {
                now_tick = GetSystick_ms();
                _cnt_10ms++;
                if(applen == rxlen && rxlen)//接收完成
                {
                    if(overflow)
                    {
                        printf(" \r\n 接收溢出,请重新尝试 \r\n");
                        SoftReset();//软件复位
                    }
                    
                    printf(" \r\n 接收BIN文件完成,长度为 %d \r\n",applen);
                    
                    temp16 = FLAG_APP2;
                    STMFLASH_Write(FLASH_PARAM_ADDR,&temp16,1);//写入标记
                    temp16 = (u16)(applen>>16);
                    STMFLASH_Write(FLASH_PARAM_ADDR+2,&temp16,1);
                    temp16 = (u16)(applen);
                    STMFLASH_Write(FLASH_PARAM_ADDR+4,&temp16,1);
                    
                    printf(" 系统将重启....\r\n");
                    SoftReset();//软件复位
                }else applen = rxlen;//更新长度    
            }
            
            if(_cnt_10ms>=50)
            {
                _cnt_10ms=0;
                Led_Tog();
                if(!rxlen)
                {
                    printf(" 等待接收App2的BIN文件 \r\n");
                }
            }
        }//while(1)
    }
}
 
 
static void App_Task(void)
{
    if(GetSystick_ms() - now_tick >500)     
    {
        now_tick = GetSystick_ms();
        printf(" 正在运行APP1 \r\n");
        Led_Tog();
    }
}
 
int main(void)
{
    SCB->VTOR = FLASH_APP1_ADDR;   
 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);  //开启AFIO时钟
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //禁止JTAG保留SWD
    
    Systick_Configuration();
    Led_Configuration();
    Usart1_Configuration(9600);
    printf(" this is APP1!\r\n");
    
    Delay_ms(500);
    
    while(1)
    {
        Update_Check();
        App_Task();
    }
}
 
//USART1串口中断函数
void USART1_IRQHandler(void)
{
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        u8 temp = USART1->DR;
        if(update)
        {   
            if(_list.Write(&list1,&temp,1) <= 0 )
            {
                overflow = 1;
            }
        }
        else
        {
            rxbuf[rxlen] = temp;
        }
        rxlen++;
    }
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
    {
        u8 temp = USART1->DR;
        temp = USART1->SR;
        
        if(strstr((char *)rxbuf,"App Update") && rxlen)
        {
            update=1;
            USART_ITConfig(USART1, USART_IT_IDLE, DISABLE);//关闭串口空闲中断
        }
        else
        {
            Usart1_SendBuf(rxbuf,rxlen);
        }
        rxlen=0;
    }
    
}

这里如果要移植需要注意的就是向量表的偏移以及更新擦写的区域。

4、剩余的4Kflash空间部分

这里其实只是用来存储2个变量,一个是程序运行标记,一个是接收到的程序长度,程序标记还有点把子用,程序长度其实要不要都无所谓。

5、遇到的坑

最值得一说的就是更新部分,最开始程序没有加入擦除flash,遇到的情况就是下载完BootLoader后发送app1没问题,在app1中更新App2也没问题,然后app2再更新app1就出问题了。

直观的结果就是循环队列溢出,原因就是app2在更新app1前没有去擦除app1所在的flash,所以在写的时候就要去擦除,这样就写的很慢,然而串口接收是不停的收,所以就是写不过来。

原文:https://blog.csdn.net/qq997758497/article/details/90211868

文章来源于网络,版权归原作者所有,如有侵权,请联系删除。



扫码,拉你进高质量嵌入式交流群


关注我【一起学嵌入式】,一起学习,一起成长。


觉得文章不错,点击“分享”、“”、“在看” 呗!

一起学嵌入式 公众号【一起学嵌入式】,RTOS、Linux编程、C/C++,以及经验分享、行业资讯、物联网等技术知
评论
  • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
    丙丁先生 2025-01-06 09:23 82浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 113浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 87浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 36浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 102浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 58浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 74浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 57浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 163浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 122浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 141浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦