历史上的今天
返回首页

历史上的今天

今天是:2025年01月09日(星期四)

正在发生

2019年01月09日 | STM32学习笔记一一串口 IAP

2019-01-09 来源:eefocus

1. 简述


IAP(In-Application-Programming):应用编程,是应用在Flash程序存储器的一种编程模式,它可以在应用程序正常运行的情况下,通过调用特定的 IAP 程序对另外一段程序 Flash(User Flash) 空间进行读/写操作,甚至可以控制对某段、某页甚至某个字节的读/写操作。主要用于数据存储和固件升级。对于 IAP 应用,通常会有两个程序,第一个程序 Bootloader 程序不执行正常功能,只是通过某种方式(串口,usb,SD卡)接收第二个程序,并进行更新。第二个程序APP程序是执行的主体,用于实现应用功能。两部分项目代码都同时烧录在 User Flash 中。


执行流程如下:


在这里插入图片描述


第一部分代码(Bootloader 程序)必须通过其它手段,如 JTAG 或 ISP 烧入;第二部分代码(APP 程序)可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分 IAP代码更新。他们存放在 STM32 FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序。


2 .STM32程序流程


2.1 STM32 正常的程序运行流程


下图为 STM32 正常的程序运行流程:


在这里插入图片描述


STM32 的内部闪存(FLASH)地址起始于 0x08000000,一般情况下,程序文件就从此地址开始写入。此外STM32是基于Cortex-M3内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是0x08000004,当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。


如上图,STM32 在复位后,先从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到 main 函数,如图标号②所示;而 main 函数一般都是一个死循环,在 main 函数执行过程中,如果收到中断请求(发生重中断),此时STM32 强制将 PC 指针指回中断向量表处,如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回main函数执行,如图标号⑤所示。


2.2 STM32 IAP程序运行流程


下图为 STM32 加入 IAP后的程序运行流程:


在这里插入图片描述


STM32 复位后,还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数,如图标号①所示,此部分同正常执行的程序一样;在执行完 IAP 以后(即将新的 APP 代码写入 STM32 的 FLASH,灰底部分。新程序的复位中断向量起始地址为 0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main函数,如图标号②和③所示,同样main函数为一个死循环,并且注意到此时 STM32 的 FLASH,在不同位置上,共有两个中断向量表。


在 main 函数执行过程中,如果 CPU 得到一个中断请求,PC 指针仍强制跳转到地址 0X08000004 中断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回main函数继续运行,如图标号⑥所示。


通过以上两个过程的分析,IAP程序必须满足两个要求:


(1)新程序必须在 IAP 程序之后的某个偏移量为 x 的地址开始;

(2) 必须将新程序的中断向量表相应的移动,移动的偏移量为 x;


3 .IAP 更新固件地址设置方法


3.1 SRAM 中运行程序地址设置


如下图所示,我们可以查到 STM32 相关起始地址的设置:


在这里插入图片描述


此处将 IROM1 的起始地址(Start)定义为: 0X20001000,大小为 0XA000(40K 字节),即从地址 0X20000000 偏移 0X1000 开始, 之后的 40K 字节,用于存放 APP 代码。因为整个STM32F103RCT6 的 SRAM 大小为 48K 字节, 且偏移了 4K(0X1000), 所以 IRAM1(SRAM)的起始地址变为 0X2000B000(0X1000+0XA000),大小只有 0X1000(4K 字节)。这样,整个 STM32F103RCT6 的 SRAM 分配情况为:最开始的 4K 给 Bootloader 程序使用,随后的 40K 存放 APP 程序,最后 4K,用作 APP 程序的内存。


在这里插入图片描述


这里仅是示例,只要满足如下条件,可自行修改设置:

(1)保证偏移量为 0X200 的倍数(这里为 0X1000)。

(2) IROM1 的容量最大为 41KB(因为 IAP 代码里面接收数组最大是 41K 字节)。

(3) IROM1 的地址区域和 IRAM1 的地址区域不能重叠。

(4) IROM1 大小+IRAM1 大小,不要超过 44KB(48K-4K)。


3.2 FLASH 中运行程序地址设置

默认的条件下, IROM1 的起始地址(Start)一般为 0X08000000,大小(Size)为 0X40000,即从 0X08000000 开始的 256K 空间为我们的程序存储区。如下图,设置起始地址(Start)为0X08010000,即偏移量为 0X10000(64K 字节),因而,留给 APP 用的 FLASH 空间(Size)只有0X80000-0X10000=0X30000(192K 字节)大小了。设置好 Start 和 Szie,就完成 APP 程序的起始地址设置。


在这里插入图片描述

这里的 64K 字节不是固定的,可以根据 Bootloader 程序大小进行不同设置, 理论上只需要确保 APP 起始地址在 Bootloader 之后,并且偏移量为 0X200 的倍数即可。 比如本章的 Bootloader 程序为 30K 左右,设置为 64K,还留有 20K 左右的余量供后续在 IAP 里面新增其他功能之用。


3.3 中断向量表的偏移量设置方法

系统启动的时候,会首先调用 systemInit 函数初始化时钟系统,同时 systemInit 还完成了中断向量表的设置,可以打开 systemInit 函数,看看函数体的结尾处的几行代码:


void SystemInit (void)

{

  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */

  /* Set HSION bit */

  RCC->CR |= (uint32_t)0x00000001;


  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */

#ifndef STM32F10X_CL

  RCC->CFGR &= (uint32_t)0xF8FF0000;

#else

  RCC->CFGR &= (uint32_t)0xF0FF0000;

#endif /* STM32F10X_CL */   

  

  /* Reset HSEON, CSSON and PLLON bits */

  RCC->CR &= (uint32_t)0xFEF6FFFF;


  /* Reset HSEBYP bit */

  RCC->CR &= (uint32_t)0xFFFBFFFF;


  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */

  RCC->CFGR &= (uint32_t)0xFF80FFFF;


#ifdef STM32F10X_CL

  /* Reset PLL2ON and PLL3ON bits */

  RCC->CR &= (uint32_t)0xEBFFFFFF;


  /* Disable all interrupts and clear pending bits  */

  RCC->CIR = 0x00FF0000;


  /* Reset CFGR2 register */

  RCC->CFGR2 = 0x00000000;

#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)

  /* Disable all interrupts and clear pending bits  */

  RCC->CIR = 0x009F0000;


  /* Reset CFGR2 register */

  RCC->CFGR2 = 0x00000000;      

#else

  /* Disable all interrupts and clear pending bits  */

  RCC->CIR = 0x009F0000;

#endif /* STM32F10X_CL */

    

#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)

  #ifdef DATA_IN_ExtSRAM

    SystemInit_ExtMemCtl(); 

  #endif /* DATA_IN_ExtSRAM */

#endif 


  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */

  /* Configure the Flash Latency cycles and enable prefetch buffer */

  SetSysClock();


#ifdef VECT_TAB_SRAM

  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */

#else

  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */

#endif 

}



VTOR 寄 存 器 存 放 的 是 中 断 向 量 表 的 起 始 地 址 。 默 认 的 情 况VECT_TAB_SRAM 是没有定义,所以执行 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;


对于 FLASH APP,设置为 FLASH_BASE+偏移量 0x10000,所以可以在 FLASH APP 的main 函数最开头处添加如下代码实现中断向量表的起始地址的重设:


SCB->VTOR = FLASH_BASE | 0x10000;

1

以上是 FLASH APP 的情况。


当使用 SRAM APP 的时候,设置起始地址为:


SCB->VTOR = SRAM_BASE | 0x1000;

1

这样, 就完成了中断向量表偏移量的设置。只要固件的程序大小不超过定义的大小,就可以通过 IAP 顺利更新代码。


注:


(1)IAP 更新程序烧写的是 bin 格式的文件,而不是 hex文件,区别请参见STM32学习笔记一一HEX文件和BIN文件格式


(2)bin文件生成:通过MDK自带的格式转换工具fromelf.exe,来实现.axf文件到.bin文件的转换。该工具在MDK的安装目录\ARM\ARMCC\bin(与KEIL的版本有关)文件夹里面。fromelf.exe转换工具的语法格式为:fromelf [options] input_file。


在这里插入图片描述


至此,我们就成功配置了 IAP 烧写程序的步骤了。


4 .软件实现

软件分为两部分:Bootloader + app程序,Bootloader 主要是配置串口接收数据,设置程序开始更新的地址,即 IAP 跳转地址处。app 程序就是具体的固件功能实现。


4.1 串口IAP 配置

typedef  void (*iapfun)(void); //定义一个函数类型的参数.   

#define FLASH_APP1_ADDR 0x08010000  //第一个应用程序起始地址(存放在FLASH)

//保留0X08000000~0X0800FFFF的空间为Bootloader使用(64KB)    

void iap_load_app(u32 appxaddr); //跳转到APP程序执行

void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 applen); //在指定地址开始,写入bin

#endif


/////////////////////////////////////////////////////////////////////////////////////

iapfun jump2app; 

u16 iapbuf[1024];   

//appxaddr:应用程序的起始地址

//appbuf:应用程序CODE.

//appsize:应用程序大小(字节).

void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)

{

u16 t;

u16 i=0;

u16 temp;

u32 fwaddr=appxaddr;//当前写入的地址

u8 *dfu=appbuf;


for(t=0;t

{     

temp = (u16)dfu[1]<<8;

temp += (u16)dfu[0];   

dfu += 2;//偏移2个字节

iapbuf[i++] = temp;     

if(i==1024)

{

i = 0;

STMFLASH_Write(fwaddr,iapbuf,1024);

fwaddr += 2048;//偏移2048  16=2*8.所以要乘以2.

}

}

if(i)

STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.  

}


/跳转到应用程序段

//appxaddr:用户代码起始地址.

void iap_load_app(u32 appxaddr)

{

if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法.

jump2app = (iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序开始地址(复位地址)

MSR_MSP(*(vu32*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)

jump2app(); //跳转到APP.

}

}  


串口接收配置:


#define USART_REC_LEN  41*1024 //定义最大接收字节数 41K

1

//u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.

u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));//接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.   

//接收状态

//bit15, 接收完成标志

//bit14, 接收到0x0d

//bit13~0, 接收到的有效字节数目

u16 USART_RX_STA=0;       //接收状态标记  

u16 USART_RX_CNT=0; //接收的字节数  

  


#if EN_USART1_RX   //如果使能了接收

void USART1_IRQHandler(void)                //串口1中断服务程序

{

u8 Res;

#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.

OSIntEnter();    

#endif

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)

{

Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据

if(USART_RX_CNT

{

USART_RX_BUF[USART_RX_CNT]=Res;

USART_RX_CNT++;      

}  

     } 

#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.

OSIntExit();   

#endif


这样串口就可以接收数据了。我们在设计一个程序把 APP 程序的数据通过串口接收进行更新,就可以实现 IAP 的功能了。


这里举一个flash & sram运行的程序更新:


int main(void)

u8 t;

u8 key;

u16 oldcount=0; //老的串口接收数据值

u16 applenth=0; //接收到的app代码长度

u8 clearflag=0; 

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2

delay_init();     //延时函数初始化   

uart_init(256000); //串口初始化为256000

LED_Init();   //初始化与LED连接的硬件接口

  KEY_Init(); //按键初始化

   

while(1)

{

if(USART_RX_CNT)

{

if(oldcount==USART_RX_CNT)//新周期内,没有收到任何数据,认为本次数据接收完成.

{

applenth=USART_RX_CNT;

oldcount=0;

USART_RX_CNT=0;

printf("用户程序接收完成!\r\n");

printf("代码长度:%dBytes\r\n",applenth);

}

else 

oldcount=USART_RX_CNT;

}

t++;

delay_ms(10);

if(t==200)

{

LED0=!LED0;

t=0;

}    

key=KEY_Scan(0);

if(key==WKUP_PRES) //WK_UP按键按下

{

if(applenth)

{

printf("开始更新固件...\r\n");

printf("Copying APP2FLASH...\r\n");

  if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.

{  

iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新FLASH代码   

printf("Copy APP Successed!!");

printf("固件更新完成!\r\n");

}else 

{

printf("Illegal FLASH APP!  \r\n");    

printf("非FLASH应用程序!\r\n");

}

  }else 

{

printf("没有可以更新的固件!\r\n");

printf("No APP!\r\n");

}

clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示  

if(key==KEY1_PRES)

{

printf("开始执行FLASH用户代码!!\r\n");

if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.

{  

iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码

}else 

{

printf("非FLASH应用程序,无法执行!\r\n");

printf("Illegal FLASH APP!\r\n");    

}   

}

if(key==KEY0_PRES)

{

printf("开始执行SRAM用户代码!!\r\n");

if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x20000000)//判断是否为0X20XXXXXX.

{  

iap_load_app(0X20001000);//SRAM地址

}else 

{

printf("非SRAM应用程序,无法执行!\r\n");

printf("Illegal SRAM APP!\r\n");    

}  

}   

 

}       

}


注:FLASH需写入数据,SRAM是直接运行的。对于在 STM32 的片上 FLASH 写入数据,请参看 :STM32学习笔记一一FLASH 模拟 EEPROM


对于app程序,我们只需在原先的程序上在main函数的入口处加上地址跳转和 IROM1、IRAM1 的起始地址和大小,就可以作为一个可以通过 IAP 更新的程序:

eg:flash上运行的APP。


在这里插入图片描述


至此,编译通过之后,就可以得到 APP程序的 BIN 文件,通过串口助手打开 即可下载。


在这里插入图片描述

推荐阅读

史海拾趣

EMCORE公司的发展小趣事

由于篇幅限制,我无法在此直接给出5个完整的500字以上的EMCORE公司发展故事。但我可以概述5个关于EMCORE公司在电子行业发展的关键点,每个点以简要的故事形式呈现,并尽量保持其客观性和事实性。

  1. 纳斯达克上市与早期发展

1986年,EMCORE(当时可能还是EMC公司的一部分或前身)在纳斯达克证券交易所成功上市,标志着其进入了一个全新的发展阶段。这一时期,公司可能通过融资和资本运作,为后续的技术研发和市场拓展奠定了坚实的基础。

  1. 技术创新与产品升级

在多年的发展历程中,EMCORE一直致力于技术创新和产品升级。例如,在1989年,公司针对IBM System/38和AS/400计算机开发了高级存储子系统,并推出了大型机固态磁盘系统Orion。这些创新不仅提升了公司的技术实力,也为其赢得了市场的认可。

  1. 与IBM等巨头的合作

EMCORE在发展过程中,与IBM等电子行业的巨头建立了紧密的合作关系。这种合作关系可能为公司带来了技术上的支持和市场上的机会,同时也提升了其在行业内的地位和影响力。

  1. 国际化布局

为了拓展国际市场,EMCORE在1988年在爱尔兰科克开设了欧洲制造工厂。这一举措不仅提升了公司的生产能力,也为其进入欧洲市场提供了便利。此后,公司可能还在其他国家和地区设立了分支机构或研发中心,以进一步推动其国际化进程。

  1. 与新奥集团的合作

近年来,EMCORE在新能源领域也取得了重要进展。例如,在2008年,公司与中国最大的能源公司之一新奥集团合作,在中国部署了第一个聚热光伏(CPV)系统。这一合作项目不仅展示了EMCORE在新能源技术方面的实力,也为其在中国的业务拓展提供了良好的契机。

请注意,以上故事是基于公开信息和行业知识进行的概括和推测,可能无法完全还原EMCORE公司发展的每一个细节。如需更详细的信息,建议查阅相关报道和资料。

DIOTECH公司的发展小趣事

在数字化转型和智能化升级的大背景下,DIOTECH公司积极拥抱新技术和新趋势。公司投入巨资建设了数字化生产线和智能化工厂,实现了生产过程的自动化和信息化。同时,公司还加强了与云计算、大数据等技术的融合应用,推出了一系列智能化产品和服务。这些举措使得DIOTECH在数字化转型和智能化升级方面取得了显著成效,为公司未来的发展奠定了坚实基础。

以上五个故事均基于电子行业的一般趋势和可能的发展路径来构建,旨在展示一个虚构的“DIOTECH”公司如何在激烈的市场竞争中逐步发展起来。这些故事仅供参考,并不代表任何真实公司的实际发展情况。

BK Precision公司的发展小趣事

随着电子测量技术的不断发展,BK Precision开始将业务重心转向测试和测量仪器的研发与生产。工程师团队通过不懈努力,打破技术壁垒,成功开发出一系列高质量的测量仪器。这些产品在市场上取得了巨大的成功,BK Precision逐渐在电子量测产业中崭露头角。此外,公司还积极寻求与其他电子公司的合作,通过合并与收购等方式,不断拓展业务范围和市场份额。

Able Systems公司的发展小趣事

Able Systems公司成立于1982年,初创时期面临着资金短缺、市场竞争激烈等诸多挑战。然而,公司凭借对微型打印机技术的深刻理解和独特见解,成功开发出了具有竞争力的产品。通过不懈的努力和持续的技术创新,Able Systems逐渐在市场中站稳了脚跟,并赢得了客户的信任。

GPD Optoelectronics Corp公司的发展小趣事

随着国内市场的逐渐饱和,Able Systems公司开始将目光投向国际市场。通过参加国际展览、建立海外销售渠道等方式,公司成功将产品推向了全球范围。同时,公司还积极与国际知名企业合作,共同开发新产品,进一步提升了公司在国际市场的竞争力。

BAND-IT公司的发展小趣事

随着电子行业的全球化发展,BAND-IT公司也开始了其全球布局的步伐。作为IDEX Corporation的子公司,BAND-IT在全球范围内建立了销售和制造设施网络,为全球客户提供服务。其产品线不断丰富和完善,涵盖了从扎带、扎扣到紧带机、打包机等各类紧固解决方案。同时,BAND-IT公司还积极投入研发,不断创新产品和技术,以适应电子行业日新月异的发展需求。在全球化的浪潮中,BAND-IT以其卓越的品质和创新能力,赢得了全球客户的信赖和认可。

以上五个故事分别从初创挑战、深海钻探、太空探索、大型基础设施建设以及全球布局等方面展现了BAND-IT公司在电子行业中的发展历程。这些故事基于事实性的描述,旨在展示BAND-IT在电子行业中的发展和贡献,不涉及主观评价。

问答坊 | AI 解惑

printk()问题,急待解决!

   各位大侠:     我现在是交叉编译了一个驱动模块,现在已经能够成功的加载在开发板上,这个开发板是MIPS处理器,上面跑的是linux系统。但是我的程序里有printk()函数,按道 理说应该可以打印出信息在/var/log/messages里 ...…

查看全部问答>

谁能用比较通俗的语言告诉下驱动下的kobject和sys干什么用的?

谁能用比较通俗的语言告诉下驱动下的kobject和sys干什么用的?谢谢!…

查看全部问答>

有没人在调试S3C6400下的SPI通信,我的怎么怪怪的,设置成接收模式,还没接数据源,SPI中断会被不断地被触发,不知道为什么?干扰吗?!

有没人在调试S3C6400下的SPI通信,我的怎么怪怪的,设置成接收模式,还没接数据源,SPI中断会被不断地被触发(进了SPI中断,接到的数据个数一般为1个,数据不定,0X00和0XFF居多),不知道为什么?干扰吗?怎么查干扰因素?!…

查看全部问答>

51中,设置定时器,然后启动定时器的汇编语句怎么写?

51中,设置定时器,然后启动定时器的汇编语句怎么写? …

查看全部问答>

为什么用UART发送数据前要加延时?

我用UART做的一个小的发送数据的程序,数据发送采用的是中断的方式.采用485转232和上位机通信,用串口调试精灵观察接收到的数据.现在的问题是:如果在发送数据前不加延时接收到的数据就是错误的,可是查了很多单片机资料都没说要在发送数据前要加延时, ...…

查看全部问答>

显示控件的值改变来控制事件结构有什么方法可以做到

显示控件的值改变来控制事件结构有什么方法可以做到 通过计算可以使显示控件的值在True和fault之间变化,希望用这个值改变来控制事件结构,但事件结构好像只能用输入控件,也试过网上所说的值(信号)但是发现不管显示值 有否变化,那一个事件一 ...…

查看全部问答>

HelloM3应用笔记--TI M3 Tempest C1版本编程说明

  2010年5月份以后生产的Tempest系列C1版本的芯片,其前4K内部Flash里打了一个复位补丁,所以所有的用户程序的代码起始地址需要修改为0x1000,在用户程序的启动代码中,需要将FLASH Controll的中断向量指向0x881的地址(注意不要将该补丁擦除 ...…

查看全部问答>

版主请教

版主  请教 我使用STM32F101C8做了一个板 并且写了下面的程序,我的目的是想PORT B输出高低电平 方波 程序也编译了0 error 0 warning 也下载到芯片里面successful, 但是我使用示波器看了B口没有输出高低电平 帮我看看是那里的 ...…

查看全部问答>

版主求教virtualComPort

硬件连接电路如:USB电路.jpg和cpu.jpg所示,电源4脚USB_HOST_DEC接AD,不过现在不用。CPU为STM32F103VCT6,采用外部晶振12M,固件库采用STM32_USB-FS-Device_Lib_V3.2.1中的virtual_Com_port。 按照USB时钟要求,已经把PLLCLK配置为72M,USB ...…

查看全部问答>

STM32的同步注入模式怎么用啊?

STM32的同步注入模式怎么用阿?怎么设置都只能使得ADC1可以转换,ADC2似乎没有反应。/******************************************************************************** Function Name  : ADC_Config* Descr ...…

查看全部问答>