STM32

STM32是STMicroelectronics(意法半导体)推出的一系列基于ARM Cortex-M内核的32位微控制器(MCU)产品。这些微控制器提供了广泛的产品系列,覆盖了多种不同的性能和功能需求,适用于各种应用领域,包括工业控制、汽车电子、消费类电子、医疗设备等。

STM32系列微控制器以其高性能、低功耗、丰富的外设接口和灵活的开发工具而闻名。它们通常具有丰富的存储器、多种通信接口(如UART、SPI、I2C、CAN等)、模拟数字转换器(ADC)、定时器、PWM输出等功能,以满足不同应用场景下的需求。

STM32微控制器通常使用标准的ARM Cortex-M内核,包括Cortex-M0、M0+、M3、M4和M7等,这些内核具有不同的性能和功耗特性,可根据具体应用的需求进行选择。此外,STM32系列还提供了多种封装和引脚配置,以满足不同尺寸和集成度的要求。

STMicroelectronics为STM32系列提供了丰富的开发工具和支持资源,包括基于ARM开发环境的集成开发环境(IDE)、调试器、评估板和参考设计等。这些工具和资源有助于开发人员快速开发和部署他们的应用,并提供了全面的技术支持和文档资料,帮助用户充分发挥STM32微控制器的性能和功能优势。

“MCU国产替代选型合集来了,干货不容错过!"

GD32F103是GD早期的产品,GD32E103和GD32F303是对GD32F103的升级和优化,所以4者是兼容的,虽然内核不同,但是通用外设几乎很少涉及到内核部分,在时间急迫的情况下可以使用ST的库开发。

一、相同点

1)外围引脚PIN TO PIN兼容,每个引脚上的复用功能也完全相同。

2)芯片内部寄存器、外部IP寄存器地址和逻辑地址完全相同,但是有些寄存器默认值不同,有些外设模块的设计时序上和STM32有差异,这点差异主要体现在软件上修改,详情见下文。

3)编译工具:完全相同例如:KEIL 、IAR

4)型号命名方式完全相同,所以替代只需找尾缀相同的型号即可,例如:STM32F103C8T6 与 GD32E103C8T6。

5)仿真工具:JLINK GDLINK

二、外围硬件区别

“MCU国产替代选型合集来了,干货不容错过!"

三、硬件替换需要注意的地方

从上面的介绍中,我们可以看出,GD32F30/E103系列和STM32F103系列是兼容的,但也需要一些注意的地方。

1)BOOT0必须接10K下拉或接GND,ST可悬空,这点很重要。

2)RC复位电路必须要有,否则MCU可能不能正常工作,ST的有时候可以不要。

3)有时候发现用仿真器连接不上。因为GD的swd接口驱动能力比ST弱,可以有如下几种方式解决:

a、线尽可能短一些;

b、降低SWD通讯速率;

c、SWDIO接10k上拉,SWCLK接10k下拉。

4)使用电池供电等,注意GD的工作电压,例如跌落到2.0V~2.6V区间,ST还能工作,GD可能无法启动或工作异常。

四、使用ST标准库开发需要修改的地方

1)GD对时序要求严格,配置外设需要先打开时钟,在进行外设配置,否则可能导致外设无法配置成功;ST的可以先配置在开时钟。

2)修改外部晶振起振超时时间,不用外部晶振可跳过这步。

原因:GD与ST的启动时间存在差异,为了让GD MCU更准确复位。

修改:

将宏定义:

#define HSE_STARTUP_TIMEOUT ((uint16_t)0x0500)

修改为:

#define HSE_STARTUP_TIMEOUT ((uint16_t)0xFFFF)

3)GD32F10X flash取值零等待,而ST需要2个等待周期,因此,一些精确延时或者模拟IIC或SPI的代码可能需要修改。

原因:GD32采用专利技术提高了相同工作频率下的代码执行速度。

修改:如果使用for或while循环做精确定时的,定时会由于代码执行速度加快而使循环的时间变短,因此需要仿真重新计算设计延时。使用Timer定时器无影响。

4)在代码中设置读保护,如果使用外部工具读保护比如JFLASH或脱机烧录器设置,可跳过此步骤。
在写完KEY序列后,需要读该位确认key已生效,修改如下:

“MCU国产替代选型合集来了,干货不容错过!"

总共需要修改如下四个函数:

FLASH_Status FLASH_EraseOptionBytes(void);
FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);
uint32_t FLASH_GetWriteProtectionOptionByte(void);
FlagStatus FLASH_GetReadOutProtectionStatus(void);

5)GD与ST在flash的Erase和Program时间上有差异,修改如下:

“MCU国产替代选型合集来了,干货不容错过!"

6)需求flash大于256K注意,小于256K可以忽略这项。

与ST不同,GD的flash存在分区的概念,前256K,CPU执行指令零等待,称code区,此范围外称为dataZ区。两者在擦写操作上没有区别,但在读操作时间上存在较大差别,code区代码取值零等待,data区执行代码有较大延迟,代码执行效率比code区慢一个数量级,因此data区通常不建议运行对实时性要求高的代码,为解决这个问题,可以使用分散加载的方法,比如把初始化代码,图片代码等放到data区。

总结:至此,经过以上修改,在不使用USB和网络能复杂协议的代码,就可以使用ST的代码操作了。

来源:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 320

1、前言

直接存储器访问(Direct Memory Access),简称DMA。DMA是CPU一个用于数据从一个地址空间到另一地址空间“搬运”(拷贝)的组件,数据拷贝过程不需CPU干预,数据拷贝结束则通知CPU处理。因此,大量数据拷贝时,使用DMA可以释放CPU资源。DMA数据拷贝过程,典型的有:

  • 内存—>内存,内存间拷贝
  • 外设—>内存,如uart、spi、i2c等总线接收数据过程
  • 内存—>外设,如uart、spi、i2c等总线发送数据过程

2、串口有必要使用DMA吗

串口(uart)是一种低速的串行异步通信,适用于低速通信场景,通常使用的波特率小于或等于115200bps。对于小于或者等于115200bps波特率的,而且数据量不大的通信场景,一般没必要使用DMA,或者说使用DMA并未能充分发挥出DMA的作用。

对于数量大,或者波特率提高时,必须使用DMA以释放CPU资源,因为高波特率可能带来这样的问题:

  • 对于发送,使用循环发送,可能阻塞线程,需要消耗大量CPU资源“搬运”数据,浪费CPU
  • 对于发送,使用中断发送,不会阻塞线程,但需浪费大量中断资源,CPU频繁响应中断;以115200bps波特率,1s传输11520字节,大约69us需响应一次中断,如波特率再提高,将消耗更多CPU资源
  • 对于接收,如仍采用传统的中断模式接收,同样会因为频繁中断导致消耗大量CPU资源

因此,高波特率场景下,串口非常有必要使用DMA。

3、实现方式

“整体设计图"
整体设计图

4、STM32串口使用DMA

关于STM32串口使用DMA,不乏一些开发板例程及网络上一些博主的使用教程。使用步骤、流程、配置基本大同小异,正确性也没什么毛病,但都是一些基本的Demo例子,作为学习过程没问题;实际项目使用缺乏严谨性,数据量大时可能导致数据异常。

测试平台:

  • STM32F030C8T6
  • UART1/UART2
  • DMA1 Channel2—Channel5
  • ST标准库
  • 主频48MHz(外部12MHz晶振)

“一个严谨的STM32串口DMA发送&接收(1.5Mbps波特率)机制"

5、串口DMA接收

5.1 基本流程

“串口接收流程图"
串口接收流程图

5.2 相关配置

关键步骤

【1】初始化串口

【2】使能串口DMA接收模式,使能串口空闲中断

【3】配置DMA参数,使能DMA通道buf半满(传输一半数据)中断、buf溢满(传输数据完成)中断

【1】第一步,DMA先将数据搬运到buf1,搬运完成通知CPU来拷贝buf1数据
【2】第二步,DMA将数据搬运到buf2,与CPU拷贝buf1数据不会冲突
【3】第三步,buf2数据搬运完成,通知CPU来拷贝buf2数据
【4】执行完第三步,DMA返回执行第一步,一直循环

“双缓存DMA数据搬运过程"
双缓存DMA数据搬运过程

STM32F0系列DMA不支持双缓存(以具体型号为准)机制,但提供了一个buf"半满中断",即是数据搬运到buf大小的一半时,可以产生一个中断信号。基于这个机制,我们可以实现双缓存功能,只需将buf空间开辟大一点即可。

【1】第一步,DMA将数据搬运完成buf的前一半时,产生“半满中断”,CPU来拷贝buf前半部分数据
【2】第二步,DMA继续将数据搬运到buf的后半部分,与CPU拷贝buf前半部数据不会冲突
【3】第三步,buf后半部分数据搬运完成,触发“溢满中断”,CPU来拷贝buf后半部分数据
【4】执行完第三步,DMA返回执行第一步,一直循环

“使用半满中断DMA数据搬运过程"
使用半满中断DMA数据搬运过程

UART2 DMA模式接收配置代码如下,与其他外设使用DMA的配置基本一致,留意关键配置:

  • 串口接收,DMA通道工作模式设为连续模式
  • 使能DMA通道接收buf半满中断、溢满(传输完成)中断
  • 启动DMA通道前清空相关状态标识,防止首次传输错乱数据
void bsp_uart2_dmarx_config(uint8_t *mem_addr, uint32_t mem_size)
{
  	DMA_InitTypeDef DMA_InitStructure;
	
	DMA_DeInit(DMA1_Channel5); 
	DMA_Cmd(DMA1_Channel5, DISABLE);
	DMA_InitStructure.DMA_PeripheralBaseAddr 	= (uint32_t)&(USART2->RDR);/* UART2接收数据地址 */
	DMA_InitStructure.DMA_MemoryBaseAddr 		= (uint32_t)mem_addr; /* 接收buf */
	DMA_InitStructure.DMA_DIR 					= DMA_DIR_PeripheralSRC; 	/* 传输方向:外设->内存 */
	DMA_InitStructure.DMA_BufferSize 			= mem_size; /* 接收buf大小 */
	DMA_InitStructure.DMA_PeripheralInc 		= DMA_PeripheralInc_Disable; 
	DMA_InitStructure.DMA_MemoryInc 			= DMA_MemoryInc_Enable; 
	DMA_InitStructure.DMA_PeripheralDataSize 	= DMA_PeripheralDataSize_Byte; 
	DMA_InitStructure.DMA_MemoryDataSize 		= DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_Mode 					= DMA_Mode_Circular; /* 连续模式 */
	DMA_InitStructure.DMA_Priority 				= DMA_Priority_VeryHigh; 
	DMA_InitStructure.DMA_M2M 					= DMA_M2M_Disable; 
	DMA_Init(DMA1_Channel5, &DMA_InitStructure); 
	DMA_ITConfig(DMA1_Channel5, DMA_IT_TC|DMA_IT_HT|DMA_IT_TE, ENABLE);/* 使能DMA半满、溢满、错误中断 */
	DMA_ClearFlag(DMA1_IT_TC5);	/* 清除相关状态标识 */
	DMA_ClearFlag(DMA1_IT_HT5);
	DMA_Cmd(DMA1_Channel5, ENABLE); 
}

DMA 错误中断“DMA_IT_TE”,一般用于前期调试使用,用于检查DMA出现错误的次数,发布软件可以不使能该中断。

5.3 接收处理

基于上述描述机制,DMA方式接收串口数据,有三种中断场景需要CPU去将buf数据拷贝到fifo中,分别是:

  • DMA通道buf溢满(传输完成)场景
  • DMA通道buf半满场景
  • 串口空闲中断场景

前两者场景,前面文章已经描述。串口空闲中断指的是,数据传输完成后,串口监测到一段时间内没有数据进来,则触发产生的中断信号。

5.3 .1 接收数据大小

数据传输过程是随机的,数据大小也是不定的,存在几类情况:

  • 数据刚好是DMA接收buf的整数倍,这是理想的状态
  • 数据量小于DMA接收buf或者小于接收buf的一半,此时会触发串口空闲中断

因此,我们需根据“DMA通道buf大小”、“DMA通道buf剩余空间大小”、“上一次接收的总数据大小”来计算当前接收的数据大小。

/* 获取DMA通道接收buf剩余空间大小 */
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

DMA通道buf溢满场景计算

接收数据大小 = DMA通道buf大小 - 上一次接收的总数据大小

DMA通道buf溢满中断处理函数:

void uart_dmarx_done_isr(uint8_t uart_id)
{
  	uint16_t recv_size;
	
	recv_size = s_uart_dev[uart_id].dmarx_buf_size - s_uart_dev[uart_id].last_dmarx_size;

	fifo_write(&s_uart_dev[uart_id].rx_fifo, 
				   (const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);

	s_uart_dev[uart_id].last_dmarx_size = 0;
}

DMA通道buf半满场景计算

接收数据大小 = DMA通道接收总数据大小 - 上一次接收的总数据大小
DMA通道接收总数据大小 = DMA通道buf大小 - DMA通道buf剩余空间大小

DMA通道buf半满中断处理函数:

void uart_dmarx_half_done_isr(uint8_t uart_id)
{
  	uint16_t recv_total_size;
  	uint16_t recv_size;
	
	if(uart_id == 0)
	{
	  	recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart1_get_dmarx_buf_remain_size();
	}
	else if (uart_id == 1)
	{
		recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart2_get_dmarx_buf_remain_size();
	}
	recv_size = recv_total_size - s_uart_dev[uart_id].last_dmarx_size;
	
	fifo_write(&s_uart_dev[uart_id].rx_fifo, 
				   (const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);
	s_uart_dev[uart_id].last_dmarx_size = recv_total_size;/* 记录接收总数据大小 */
}

串口空闲中断场景计算

串口空闲中断场景的接收数据计算与“DMA通道buf半满场景”计算方式是一样的。

串口空闲中断处理函数:

void uart_dmarx_idle_isr(uint8_t uart_id)
{
  	uint16_t recv_total_size;
  	uint16_t recv_size;
	
	if(uart_id == 0)
	{
	  	recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart1_get_dmarx_buf_remain_size();
	}
	else if (uart_id == 1)
	{
		recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart2_get_dmarx_buf_remain_size();
	}
	recv_size = recv_total_size - s_uart_dev[uart_id].last_dmarx_size;
	s_UartTxRxCount[uart_id*2+1] += recv_size;
	fifo_write(&s_uart_dev[uart_id].rx_fifo, 
				   (const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);
	s_uart_dev[uart_id].last_dmarx_size = recv_total_size;
}

注:
串口空闲中断处理函数,除了将数据拷贝到串口接收fifo中,还可以增加特殊处理,如作为串口数据传输完成标识、不定长度数据处理等等。

5.3.2 接收数据偏移地址

将有效数据拷贝到fifo中,除了需知道有效数据大小外,还需知道数据存储于DMA 接收buf的偏移地址。有效数据偏移地址只需记录上一次接收的总大小即,可,在DMA通道buf全满中断处理函数将该值清零,因为下一次数据将从buf的开头存储。

在DMA通道buf溢满中断处理函数中将数据偏移地址清零:

void uart_dmarx_done_isr(uint8_t uart_id)
{
 	/* todo */
	s_uart_dev[uart_id].last_dmarx_size = 0;
}

5.4 应用读取串口数据方法

经过前面的处理步骤,已将串口数据拷贝至接收fifo,应用程序任务只需从fifo获取数据进行处理。前提是,处理效率必须大于DAM接收搬运数据的效率,否则导致数据丢失或者被覆盖处理。

6、串口DMA发送

6.1 基本流程

“串口发送流程图"
串口发送流程图

5.2 相关配置

关键步骤

【1】初始化串口

【2】使能串口DMA发送模式

【3】配置DMA发送通道,这一步无需在初始化设置,有数据需要发送时才配置使能DMA发送通道

UART2 DMA模式发送配置代码如下,与其他外设使用DMA的配置基本一致,留意关键配置:

  • 串口发送是,DMA通道工作模式设为单次模式(正常模式),每次需要发送数据时重新配置DMA
  • 使能DMA通道传输完成中断,利用该中断信息处理一些必要的任务,如清空发送状态、启动下一次传输
  • 启动DMA通道前清空相关状态标识,防止首次传输错乱数据
void bsp_uart2_dmatx_config(uint8_t *mem_addr, uint32_t mem_size)
{
  	DMA_InitTypeDef DMA_InitStructure;
	
	DMA_DeInit(DMA1_Channel4);
	DMA_Cmd(DMA1_Channel4, DISABLE);
	DMA_InitStructure.DMA_PeripheralBaseAddr 	= (uint32_t)&(USART2->TDR);/* UART2发送数据地址 */
	DMA_InitStructure.DMA_MemoryBaseAddr 		= (uint32_t)mem_addr; 	/* 发送数据buf */
	DMA_InitStructure.DMA_DIR 					= DMA_DIR_PeripheralDST; 	/* 传输方向:内存->外设 */
	DMA_InitStructure.DMA_BufferSize 			= mem_size; 			/* 发送数据buf大小 */
	DMA_InitStructure.DMA_PeripheralInc 		= DMA_PeripheralInc_Disable; 
	DMA_InitStructure.DMA_MemoryInc 			= DMA_MemoryInc_Enable; 
	DMA_InitStructure.DMA_PeripheralDataSize 	= DMA_PeripheralDataSize_Byte; 
	DMA_InitStructure.DMA_MemoryDataSize 		= DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_Mode 					= DMA_Mode_Normal; 		/* 单次模式 */
	DMA_InitStructure.DMA_Priority 				= DMA_Priority_High;	 
	DMA_InitStructure.DMA_M2M 					= DMA_M2M_Disable; 
	DMA_Init(DMA1_Channel4, &DMA_InitStructure);  
	DMA_ITConfig(DMA1_Channel4, DMA_IT_TC|DMA_IT_TE, ENABLE); /* 使能传输完成中断、错误中断 */
	DMA_ClearFlag(DMA1_IT_TC4);	/* 清除发送完成标识 */
	DMA_Cmd(DMA1_Channel4, ENABLE); /* 启动DMA发送 */
}

5.3 发送处理

串口待发送数据存于发送fifo中,发送处理函数需要做的的任务就是循环查询发送fifo是否存在数据,如存在则将该数据拷贝到DMA发送buf中,然后启动DMA传输。前提是需要等待上一次DMA传输完毕,即是DMA接收到DMA传输完成中断信号"DMA_IT_TC"。

串口发送处理函数:

void uart_poll_dma_tx(uint8_t uart_id)
{
  	uint16_t size = 0;
	
	if (0x01 == s_uart_dev[uart_id].status)
    {
        return;
    }
	size = fifo_read(&s_uart_dev[uart_id].tx_fifo, s_uart_dev[uart_id].dmatx_buf,
					 s_uart_dev[uart_id].dmatx_buf_size);
	if (size != 0)
	{
        s_UartTxRxCount[uart_id*2+0] += size;
	  	if (uart_id == 0)
		{
            s_uart_dev[uart_id].status = 0x01;	/* DMA发送状态 */
		  	bsp_uart1_dmatx_config(s_uart_dev[uart_id].dmatx_buf, size);
		}
		else if (uart_id == 1)
		{
            s_uart_dev[uart_id].status = 0x01;	/* DMA发送状态,必须在使能DMA传输前置位,否则有可能DMA已经传输并进入中断 */
			bsp_uart2_dmatx_config(s_uart_dev[uart_id].dmatx_buf, size);
		}
	}
}

注意发送状态标识,必须先置为“发送状态”,然后启动DMA 传输。如果步骤反过来,在传输数据量少时,DMA传输时间短,“DMA_IT_TC”中断可能比“发送状态标识置位”先执行,导致程序误判DMA一直处理发送状态(发送标识无法被清除)。

注:
关于DMA发送数据启动函数,有些博客文章描述只需改变DMA发送buf的大小即可;经过测试发现,该方法在发送数据量较小时可行,数据量大后,导致发送失败,而且不会触发DMA发送完成中断。因此,可靠办法是:每次启动DMA发送,重新配置DMA通道所有参数。该步骤只是配置寄存器过程,实质上不会占用很多CPU执行时间。

DMA传输完成中断处理函数:

void uart_dmatx_done_isr(uint8_t uart_id)
{
 	s_uart_dev[uart_id].status = 0;	/* 清空DMA发送状态标识 */
}

上述串口发送处理函数可以在几种情况调用:

主线程任务调用,前提是线程不能被其他任务阻塞,否则导致fifo溢出

void thread(void)
{
    uart_poll_dma_tx(DEV_UART1);
    uart_poll_dma_tx(DEV_UART2);
}

定时器中断中调用

void TIMx_IRQHandler(void)
{
    uart_poll_dma_tx(DEV_UART1);
    uart_poll_dma_tx(DEV_UART2);
}

DMA通道传输完成中断中调用

void DMA1_Channel4_5_IRQHandler(void)
{
	if(DMA_GetITStatus(DMA1_IT_TC4))
	{
		UartDmaSendDoneIsr(UART_2);
		DMA_ClearFlag(DMA1_FLAG_TC4);
		uart_poll_dma_tx(DEV_UART2);
	}
}

每次拷贝多少数据量到DMA发送buf:

关于这个问题,与具体应用场景有关,遵循的原则就是:只要发送fifo的数据量大于等于DMA发送buf的大小,就应该填满DMA发送buf,然后启动DMA传输,这样才能充分发挥会DMA性能。因此,需兼顾每次DMA传输的效率和串口数据流实时性,参考着几类实现:

  • 周期查询发送fifo数据,启动DMA传输,充分利用DMA发送效率,但可能降低串口数据流实时性
  • 实时查询发送fifo数据,加上超时处理,理想的方法
  • 在DMA传输完成中断中处理,保证实时连续数据流

6、串口设备

6.1 数据结构

/* 串口设备数据结构 */
typedef struct
{
	uint8_t status;			/* 发送状态 */
	_fifo_t tx_fifo;		/* 发送fifo */
	_fifo_t rx_fifo;		/* 接收fifo */
	uint8_t *dmarx_buf;		/* dma接收缓存 */
	uint16_t dmarx_buf_size;/* dma接收缓存大小*/
	uint8_t *dmatx_buf;		/* dma发送缓存 */
	uint16_t dmatx_buf_size;/* dma发送缓存大小 */
	uint16_t last_dmarx_size;/* dma上一次接收数据大小 */
}uart_device_t;

6.2 对外接口

/* 串口注册初始化函数 */
void uart_device_init(uint8_t uart_id)
{
  	if (uart_id == 1)
	{
		/* 配置串口2收发fifo */
		fifo_register(&s_uart_dev[uart_id].tx_fifo, &s_uart2_tx_buf[0], 
                      sizeof(s_uart2_tx_buf), fifo_lock, fifo_unlock);
		fifo_register(&s_uart_dev[uart_id].rx_fifo, &s_uart2_rx_buf[0], 
                      sizeof(s_uart2_rx_buf), fifo_lock, fifo_unlock);
		
		/* 配置串口2 DMA收发buf */
		s_uart_dev[uart_id].dmarx_buf = &s_uart2_dmarx_buf[0];
		s_uart_dev[uart_id].dmarx_buf_size = sizeof(s_uart2_dmarx_buf);
		s_uart_dev[uart_id].dmatx_buf = &s_uart2_dmatx_buf[0];
		s_uart_dev[uart_id].dmatx_buf_size = sizeof(s_uart2_dmatx_buf);
		bsp_uart2_dmarx_config(s_uart_dev[uart_id].dmarx_buf, 
							   sizeof(s_uart2_dmarx_buf));
		s_uart_dev[uart_id].status  = 0;
	}
}

/* 串口发送函数 */
uint16_t uart_write(uint8_t uart_id, const uint8_t *buf, uint16_t size)
{
	return fifo_write(&s_uart_dev[uart_id].tx_fifo, buf, size);
}

/* 串口读取函数 */
uint16_t uart_read(uint8_t uart_id, uint8_t *buf, uint16_t size)
{
	return fifo_read(&s_uart_dev[uart_id].rx_fifo, buf, size);
}

7、相关文章

依赖的fifo参考该文章:

【1】通用环形缓冲区模块

8、完整源码

代码仓库:https://github.com/Prry/stm32f0-uart-dma

串口&DMA底层配置:

#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "stm32f0xx.h"
#include "bsp_uart.h"

/**
 * @brief  
 * @param  
 * @retval 
 */
static void bsp_uart1_gpio_init(void)
{
    GPIO_InitTypeDef    GPIO_InitStructure;
#if 0
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
	
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_0);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_0); 
	
	GPIO_InitStructure.GPIO_Pin 	= GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType 	= GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed  	= GPIO_Speed_Level_3;
    GPIO_InitStructure.GPIO_PuPd 	= GPIO_PuPd_UP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
#else
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
	
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_1);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_1); 
	
	GPIO_InitStructure.GPIO_Pin 	= GPIO_Pin_9 | GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType 	= GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed  	= GPIO_Speed_Level_3;
    GPIO_InitStructure.GPIO_PuPd 	= GPIO_PuPd_UP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
#endif
}

/**
 * @brief  
 * @param  
 * @retval 
 */
static void bsp_uart2_gpio_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
	
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_1);
	
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}

/**
 * @brief  
 * @param  
 * @retval 
 */
void bsp_uart1_init(void)
{
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	bsp_uart1_gpio_init();
	
	/* 使能串口和DMA时钟 */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	
	USART_InitStructure.USART_BaudRate            = 57600;
	USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits            = USART_StopBits_1;
	USART_InitStructure.USART_Parity              = USART_Parity_No;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART1, &USART_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);	/* 使能空闲中断 */
	USART_OverrunDetectionConfig(USART1, USART_OVRDetection_Disable);
	
	USART_Cmd(USART1, ENABLE);
	USART_DMACmd(USART1, USART_DMAReq_Rx|USART_DMAReq_Tx, ENABLE); /* 使能DMA收发 */

	/* 串口中断 */
	NVIC_InitStructure.NVIC_IRQChannel         = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd      = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* DMA中断 */
  	NVIC_InitStructure.NVIC_IRQChannel 		   = DMA1_Channel2_3_IRQn;       
  	NVIC_InitStructure.NVIC_IRQChannelPriority = 0; 
	NVIC_InitStructure.NVIC_IRQChannelCmd      = ENABLE;
  	NVIC_Init(&NVIC_InitStructure);
}

/**
 * @brief  
 * @param  
 * @retval 
 */
void bsp_uart2_init(void)
{
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	bsp_uart2_gpio_init();
	
	/* 使能串口和DMA时钟 */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

	USART_InitStructure.USART_BaudRate            = 57600;
	USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits            = USART_StopBits_1;
	USART_InitStructure.USART_Parity              = USART_Parity_No;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART2, &USART_InitStructure);
	
	USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);	/* 使能空闲中断 */
	USART_OverrunDetectionConfig(USART2, USART_OVRDetection_Disable);
	
	USART_Cmd(USART2, ENABLE);
	USART_DMACmd(USART2, USART_DMAReq_Rx|USART_DMAReq_Tx, ENABLE); 	/* 使能DMA收发 */

	/* 串口中断 */
	NVIC_InitStructure.NVIC_IRQChannel         = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd      = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* DMA中断 */
	NVIC_InitStructure.NVIC_IRQChannel         = DMA1_Channel4_5_IRQn;       
  	NVIC_InitStructure.NVIC_IRQChannelPriority = 0; 
	NVIC_InitStructure.NVIC_IRQChannelCmd      = ENABLE;
  	NVIC_Init(&NVIC_InitStructure);
}

void bsp_uart1_dmatx_config(uint8_t *mem_addr, uint32_t mem_size)
{
  	DMA_InitTypeDef DMA_InitStructure;
	
	DMA_DeInit(DMA1_Channel2);
	DMA_Cmd(DMA1_Channel2, DISABLE);
	DMA_InitStructure.DMA_PeripheralBaseAddr 	= (uint32_t)&(USART1->TDR);
	DMA_InitStructure.DMA_MemoryBaseAddr 		= (uint32_t)mem_addr; 
	DMA_InitStructure.DMA_DIR 					= DMA_DIR_PeripheralDST; 	/* 传输方向:内存->外设 */
	DMA_InitStructure.DMA_BufferSize 			= mem_size; 
	DMA_InitStructure.DMA_PeripheralInc 		= DMA_PeripheralInc_Disable; 
	DMA_InitStructure.DMA_MemoryInc 			= DMA_MemoryInc_Enable; 
	DMA_InitStructure.DMA_PeripheralDataSize 	= DMA_PeripheralDataSize_Byte; 
	DMA_InitStructure.DMA_MemoryDataSize 		= DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_Mode 					= DMA_Mode_Normal; 
	DMA_InitStructure.DMA_Priority 				= DMA_Priority_High; 
	DMA_InitStructure.DMA_M2M 					= DMA_M2M_Disable; 
	DMA_Init(DMA1_Channel2, &DMA_InitStructure);  
	DMA_ITConfig(DMA1_Channel2, DMA_IT_TC|DMA_IT_TE, ENABLE); 
	DMA_ClearFlag(DMA1_IT_TC2);	/* 清除发送完成标识 */
	DMA_Cmd(DMA1_Channel2, ENABLE); 
}

void bsp_uart1_dmarx_config(uint8_t *mem_addr, uint32_t mem_size)
{
  	DMA_InitTypeDef DMA_InitStructure;
	
	DMA_DeInit(DMA1_Channel3); 
	DMA_Cmd(DMA1_Channel3, DISABLE);
	DMA_InitStructure.DMA_PeripheralBaseAddr 	= (uint32_t)&(USART1->RDR);
	DMA_InitStructure.DMA_MemoryBaseAddr 		= (uint32_t)mem_addr; 
	DMA_InitStructure.DMA_DIR 					= DMA_DIR_PeripheralSRC; 	/* 传输方向:外设->内存 */
	DMA_InitStructure.DMA_BufferSize 			= mem_size; 
	DMA_InitStructure.DMA_PeripheralInc 		= DMA_PeripheralInc_Disable; 
	DMA_InitStructure.DMA_MemoryInc 			= DMA_MemoryInc_Enable; 
	DMA_InitStructure.DMA_PeripheralDataSize 	= DMA_PeripheralDataSize_Byte; 
	DMA_InitStructure.DMA_MemoryDataSize 		= DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_Mode 					= DMA_Mode_Circular; 
	DMA_InitStructure.DMA_Priority 				= DMA_Priority_VeryHigh; 
	DMA_InitStructure.DMA_M2M 					= DMA_M2M_Disable; 
	DMA_Init(DMA1_Channel3, &DMA_InitStructure); 
	DMA_ITConfig(DMA1_Channel3, DMA_IT_TC|DMA_IT_HT|DMA_IT_TE, ENABLE);/* 使能DMA半满、全满、错误中断 */
	DMA_ClearFlag(DMA1_IT_TC3);
	DMA_ClearFlag(DMA1_IT_HT3);
	DMA_Cmd(DMA1_Channel3, ENABLE); 
}

uint16_t bsp_uart1_get_dmarx_buf_remain_size(void)
{
	return DMA_GetCurrDataCounter(DMA1_Channel3);	/* 获取DMA接收buf剩余空间 */
}

void bsp_uart2_dmatx_config(uint8_t *mem_addr, uint32_t mem_size)
{
  	DMA_InitTypeDef DMA_InitStructure;
	
	DMA_DeInit(DMA1_Channel4);
	DMA_Cmd(DMA1_Channel4, DISABLE);
	DMA_InitStructure.DMA_PeripheralBaseAddr 	= (uint32_t)&(USART2->TDR);
	DMA_InitStructure.DMA_MemoryBaseAddr 		= (uint32_t)mem_addr; 
	DMA_InitStructure.DMA_DIR 					= DMA_DIR_PeripheralDST; 	/* 传输方向:内存->外设 */
	DMA_InitStructure.DMA_BufferSize 			= mem_size; 
	DMA_InitStructure.DMA_PeripheralInc 		= DMA_PeripheralInc_Disable; 
	DMA_InitStructure.DMA_MemoryInc 			= DMA_MemoryInc_Enable; 
	DMA_InitStructure.DMA_PeripheralDataSize 	= DMA_PeripheralDataSize_Byte; 
	DMA_InitStructure.DMA_MemoryDataSize 		= DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_Mode 					= DMA_Mode_Normal; 
	DMA_InitStructure.DMA_Priority 				= DMA_Priority_High; 
	DMA_InitStructure.DMA_M2M 					= DMA_M2M_Disable; 
	DMA_Init(DMA1_Channel4, &DMA_InitStructure);  
	DMA_ITConfig(DMA1_Channel4, DMA_IT_TC|DMA_IT_TE, ENABLE); 
	DMA_ClearFlag(DMA1_IT_TC4);	/* 清除发送完成标识 */
	DMA_Cmd(DMA1_Channel4, ENABLE); 
}

void bsp_uart2_dmarx_config(uint8_t *mem_addr, uint32_t mem_size)
{
  	DMA_InitTypeDef DMA_InitStructure;
	
	DMA_DeInit(DMA1_Channel5); 
	DMA_Cmd(DMA1_Channel5, DISABLE);
	DMA_InitStructure.DMA_PeripheralBaseAddr 	= (uint32_t)&(USART2->RDR);
	DMA_InitStructure.DMA_MemoryBaseAddr 		= (uint32_t)mem_addr; 
	DMA_InitStructure.DMA_DIR 					= DMA_DIR_PeripheralSRC; 	/* 传输方向:外设->内存 */
	DMA_InitStructure.DMA_BufferSize 			= mem_size; 
	DMA_InitStructure.DMA_PeripheralInc 		= DMA_PeripheralInc_Disable; 
	DMA_InitStructure.DMA_MemoryInc 			= DMA_MemoryInc_Enable; 
	DMA_InitStructure.DMA_PeripheralDataSize 	= DMA_PeripheralDataSize_Byte; 
	DMA_InitStructure.DMA_MemoryDataSize 		= DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_Mode 					= DMA_Mode_Circular; 
	DMA_InitStructure.DMA_Priority 				= DMA_Priority_VeryHigh; 
	DMA_InitStructure.DMA_M2M 					= DMA_M2M_Disable; 
	DMA_Init(DMA1_Channel5, &DMA_InitStructure); 
	DMA_ITConfig(DMA1_Channel5, DMA_IT_TC|DMA_IT_HT|DMA_IT_TE, ENABLE);/* 使能DMA半满、全满、错误中断 */
	DMA_ClearFlag(DMA1_IT_TC5);
	DMA_ClearFlag(DMA1_IT_HT5);
	DMA_Cmd(DMA1_Channel5, ENABLE); 
}

uint16_t bsp_uart2_get_dmarx_buf_remain_size(void)
{
	return DMA_GetCurrDataCounter(DMA1_Channel5);	/* 获取DMA接收buf剩余空间 */
}

压力测试:

  • 1.5Mbps波特率,串口助手每毫秒发送1k字节数据,stm32f0 DMA接收数据,再通过DMA发送回串口助手,毫无压力。
  • 1.5Mbps波特率,可传输大文件测试,将接收数据保存为文件,与源文件比较。
  • 串口高波特率测试需要USB转TLL工具及串口助手都支持才可行,推荐CP2102、FT232芯片的USB转TTL工具。

“1.5Mbps串口回环压力测试"
1.5Mbps串口回环压力测试

————————————————

版权声明:本文为CSDN博主「Acuity.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_20553613/article/details/108367512
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 1220

阅读本文时,请先阅读:

臻于至善,履践致远|STM32CubeProgrammer 和 STM32CubeMonitor上新,提高STM32开发效率-上

ST 近期推出新版本的 STM32CubeProgrammer、STM32CubeMonitor、STM32CubeMonitor-RF 和 STM32CubeMonitor-UCPD。许多 STM32 开发人员通过使用它们更快地将产品推向市场。所有嵌入式系统工程师都需要面对这样的挑战,为选用的微控制器或微处理器寻找功能全面的开发平台。一个设备可能有很多特性需求,设计人员如何有效地实现这些性能非常关键。因此,泛生态软件工具在推动基于 STM32 的嵌入式系统开发至关重要。让我们一起探索STM32CubeProgrammer 和 STM32CubeMonitor的新功能。

“STM32CubeProgrammer

STM32CubeProgrammer关键词,请参见“臻于至善,履践致远|STM32CubeProgrammer 和 STM32CubeMonitor上新,提高STM32开发效率-上

STM32 Cube Monitor关键词

MCU 的 “奈飞”

STM32CubeMonitor 是一个实时变量监控和可视化工具,提供支持远程连接的 Web 界面和可以创建自定义仪表板的图形界面。它确保开发人员可以通过可视化图形界面有效地监控他们的应用程序。这种基于流的编程工具使用户无需编码即可创建复杂的数据图表,使他们能够在不破坏程序代码的情况下轻松调试软件和分析行为。此外,用户可以在 Node-RED 和 ST 社区上共享他们的仪表板,以相互构建。

为了让 STM32CubeMonitor 的首次体验更加直观,ST Wiki 详细解释了开发人员如何通过两个简单的步骤监控应用程序中的变量。用户选择他们在内存中跟踪的数据起始地址及其类型。为了协助完成这项任务,我们提供了一份指南,展示了如何从 ELF 文件中获取地址,以及CubeMonitor 如何通过 STLINK 与 STM32 连接。

基于Node-RED的实时变量监控工具

跟踪寄存器、内存中的变量以及在任何给定时刻发生的无数事件难度非常大,手动监控它们的要求非常高,以至于团队通常没有资源来进行这项工作。STM32CubeMonitor 为这个问题提供了一个解决方案,并尽可能使其简单易用。用户可将可编辑的图形化节点拖放到画布上来创建流程,即一系列事件。例如,通过设置条件可以触发通过电子邮件发送警报或使用 MQTT 将数据推送到云平台的模块。

“▲
▲ STM32CubeMonitor

用户一行代码都不用输入,就可以创建图形、图表或生成仪表板,帮助他们计数器中的数据、传感器的数据以及应用程序的许多其他方面进行可视化。此外,网络服务器的存在意味着可以在任何 PC 或移动浏览器上使用这些可视化,无论是在本地网络上还是远程。此外,得益于 Node-RED 和 ST 社区,用户可以从简单地查看其他用户的仪表板开始,并通过学习其他人的示例来系统地学习。

贯穿产品生命周期的支持工具

在原型设计阶段,工程师可能会使用 STLINK ,例如目前可用的 STLINK-V3 模块之一。它将 MCU 板连接到 PC,这有助于设置 STM32CubeMonitor 仪表板并充当 Web 界面的网关。当设计人员准备好交付他们的最终产品时,他们还可以创建一段程序,该程序将通过 UART 将数据发送到 USB 端口。因此,开发人员仍然可以通过运行在PC上的STM32CubeMonitor连接到该 USB 端口来安全地监控他们的应用程序。因此,该工具提供了有助于计划升级或未来新功能的长期分析。

STM32CubeMonitor 的新功能

支持Node-RED 1.3.7

STM32CubeMonitor 1.3.0使用NodeRED 1.3.7框架,目前最新版本是1.3版本。1.3 版本带来了新的插件框架和自动执行功能节点的能力。Change/Switch 节点的更新也提高了它们的可读性。

OpenJS 基金会将在 2022 年推出新版本的 Node-RED,ST 将继续更新 STM32CubeMonitor。

新格式和符号更改通知

最新版本的 STM32CubeMonitor 能够以 CSV 格式导出数据,而不是仅仅使用专有格式。因此,用户将能够将信息导入 Excel、MATLAB 等,从而为更多的数据优化和操作打开大门。如果符号发生变化,新软件也会发出通知。简而言之,该软件通过在文件中定义变量并将它们与符号相关联来跟踪变量。但是,重新编译代码可能会使符号文件过时,从而与 Node-RED 仪表板产生差异。如果用户忘记更新符号文件,新的 STM32CubeMonitor 会提醒用户。

STM32CubeMonitor-RF关键词

STM32CubeMonitor-RF 是一款用于测试 STM32WB 微控制器的蓝牙和 802.15.4 无线性能的工具。图形用户界面有助于实现信号强度和数据包错误的可视化,而命令行界面为宏、批处理文件和其他类型的自动化打开了大门。简而言之,它借鉴了与传统 STM32CubeMonitor 相同的理念,但专注于无线性能。因此,开发人员可以快速测试他们的设计并发现问题。该实用程序还可以探测设备之间的 802.15.4 通信。试用方法非常简单,将 STM32WB 开发板通过其 USB 或 UART 接口连接到计算机,就可以开始使用该软件了。

STM32CubeMonitor-RF的新功能

STM32CubeMonitor-RF 2.8.0 是一项重大更新,因为使用了更大的数据包,无线性能提高了一倍以上。当用户在“OTA Updater”中选择“Optimize MTU size”选项时,软件工具将OTA传输从16 kbit/s增加到41 kbit/s。因此,对于开发人员来说,这是提高生活质量的必要条件。发送文件或更新设备固件是开发过程中的日常操作。更快的速度将确保开发人员更快、更高效地工作。

“▲
▲ The OTA Updater 和 Optimize MTU Size option

STM32 CubeMonitor-UCPD关键词

STM32CubeMonitor-UCPD 监控并帮助在运行 ST USB PD 堆栈的 STM32 微控制器上设置 USB-C 和供电系统。开发人员可以使用该工具监控 USB-C 接口上的交互,使用sink或source电源配置文件,并使用供应商定义的消息 (VDM)。该工具甚至具有预定义的设置,通过处理这些新技术固有的复杂性来加速开发。STM32CubeMonitor-UCPD 于2019年推出,是意法半导体 USB-C 供电生态系统不可或缺的一部分。一直以来,我们都在不断改进软件,以帮助开发人员更快地评估性能并获得认证。

STM32CubeMonitor-UCPD的新功能

STM32CubeMonitor-UCPD 1.2.0 最重要的新增功能之一是 Java 机器的集成。与本文中的其他工具一样,该软件具有安装程序所需的一切。用户在运行应用程序之前不再需要自己安装 Java。此外,用户现在可以追踪显示电压和电流、VDM、UCSI 等轨迹。新的 STM32CubeMonitor-UCPD 还可以监控电池的电气值。因此,开发人员可以跟踪更多进程并了解连接两个 USB-C 设备或使用 Power Delivery 时发生的情况。

更多资料

围观 300

引言

客户在使用 STM32U5 时,想对外部 LSE 的起振情况和精度进行监测,于是使用 HSE 为时钟基准,对 LSE 进行测量。

Note : 本文中由于 Nucleo 开发板默认没有焊接 HSE 器件,因此,采用内部高速时钟替代 HSE作为 PLL 的时钟源。为保证测量精度,应用中可将时钟源替换为 HSE 。

测量的实现

使用 NUCLEO-U575 开发板的 Timer15 进行 LSE 的测量。Timer15 在 APB2 总线上, 配置为使用内部时钟,时钟 base 为 160MHz。使用 Timer15 对 256 个 LSE 周期进行测量,然后对比精确度。

“使用

测量的详细情况

TIM15 时钟源选择内部时钟,频率为 160MHz。设置为捕获模式。

通过配置 TIM15_SMCR.TS[4 :0]对应 bit21,20,6,5,4 位为 0x00101B,选择 Filtered Timer Input 1(tim_ti1fp1)做为启动 TIM15 开始计时的触发源,根据图 3 可以看到等待捕获的时钟源是 LSE。

“使用

“使用

通过 TIM15_CCMR1.CC1S[1:0]为 0x01,选择 CC1 通道输入信号,tim_ic1 连接到 tim_ti1,如下图黄色部分。

“使用

通过配置 TIM15_SMCR.TS[4 :0]为 0x00101B 选择 Filtered Timer Input 1(tim_ti1fp1)做为触发源。即 LSE 过来的信号为触发源。通过配置 TIM15_SMCR.SMS[3 :0]为 0x0110B 选择 Trigger Mode,在触发源的上升沿启动 TIM15。

“使用

“使用

“使用

“使用

开启 DMA 的时钟,因为 GPDMA1 是挂在 AHB1 总线上的,因此代码如下:

“使用

下面是 Timer15 的初始化代码:

“使用

“使用

对捕获和用时的计时的程序处理:

在 timer15 DMA 256 个数据存储完成的回调函数中做如下处理,并停止 Timer15 的更新中断和 DMA 中断,256 个 LSE 周期计时数据存储在 LES_buffer[256]中。

“使用

DMA 运行的同时需要使能 Timer15 溢出中断,并对计数器溢出进行计数。代码如下:

“使用

在 LES_Counter=256 时进行 LSE 频率的计算,代码如下:

“使用

标准值为:(255/32768)x10000000 = 77819 实际 255 个 LSE 周期值为:[(LSE_End_timer2-LSE_Start_timer1)+65536*(LSE_Timer15_overload_counter-1)]/16 = A, 255 LSE timer period. one LSE period average accuracy error is (A-77819)/255看 LSE 是否准确,只要比较上面的标准值和实际测量值就好。

小结

这个 LSE 的准确性测试程序采用的是 DMA 结合时钟溢出计数方式进行计算。使用 DMA 存储每个 LSE 周期的捕获值,同时配合 Timer15 溢出中断进行溢出次数计数,最后对取得的结果进行计算,对 255 个 LSE 周期累加在一起的计时结果进行判断,并同理想值进行比较得知实际的LSE 精度。

同时请留意把驱动能力调节大些,用 LOW 有时候测到的误差会偏大,代码如下:

__HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_MEDIUMHIGH);

文档中所用到的工具及版本

NUCLEO-U575 开发板

STM32CubeMX Version:6.99.12-B1

来源:STM32单片机
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 47

CAN协议基础知识

I2C.SPI总线多用于短距离传输,协议简单,数据量少,主要用于IC之间的通讯,而 CAN 总线则不同,CAN(Controller Area Network) 总线定义了更为优秀的物理层、数据链路层,并且拥有种类丰富、简繁不一的上层协议。与I2C、SPI有时钟信号的同步通讯方式不同,CAN通讯并不是以时钟信号来进行同步的,它是一种异步通讯,只具有CAN_High和CAN_Low两条信号线,共同构成一组差分信号线,以差分信号的形式进行通讯。

CAN物理层的形式主要分为闭环总线及开环总线网络两种,一个适合于高速通讯,一个适合于远距离通讯。CAN闭环通讯网络是一种遵循ISO11898标准的高速、短距离网络,它的总线最大长度为40m,通信速度最高为1Mbps,总线的两端各要求有一个
“120欧”的电阻来做阻抗匹配,以减少回波反射。

闭环总线网络

“STM32中CAN总线接口发送和接收数据"

CAN开环总线网络是遵循ISO11519-2标准的低速、远距离网络,它的最大传输距离为1km,最高通讯速率为125kbps,两根总线是独立的、不形成闭环,要求每根总线上各串联有一个“2.2千欧”的电阻。

开环总线网络

“STM32中CAN总线接口发送和接收数据"

CAN总线上可以挂载多个通讯节点,节点之间的信号经过总线传输,实现节点间通讯。由于CAN通讯协议不对节点进行地址编码,而是对数据内容进行编码,所以网络中的节点个数理论上不受限制,只要总线的负载足够即可,可以通过中继器增强负载。

CAN通讯节点由一个CAN控制器及CAN收发器组成,控制器与收发器之间通过CAN_Tx及CAN_Rx信号线相连,收发器与CAN总线之间使用CAN_High及CAN_Low信号线相连。其中CAN_Tx及CAN_Rx使用普通的类似TTL逻辑信号,而CAN_High及CAN_Low是一对差分信号线,使用比较特别的差分信号。当CAN节点需要发送数据时,控制器把要发送的二进制编码通过CAN_Tx线发送到收发器,然后由收发器把这个普通的逻辑电平信号转化成差分信号,通过差分线CAN_High和CAN_Low线输出到CAN总线网络。而通过收发器接收总线上的数据到控制器时,则是相反的过程,收发器把总线上收到的CAN_High及CAN_Low信号转化成普通的逻辑电平信号,通过CAN_Rx输出到控制器中。

差分信号

差分信号又称差模信号,与传统使用单根信号线电压表示逻辑的方式有区别,使用差分信号传输时,需要两根信号线,这两个信号线的振幅相等,相位相反,通过两根信号线的电压差值来表示逻辑0和逻辑1。

相对于单信号线传输的方式,使用差分信号传输具有如下优点:

  • 抗干扰能力强,当外界存在噪声干扰时,几乎会同时耦合到两条信号线上,而接收端只关心两个信号的差值,所以外界的共模噪声可以被完全抵消。

  • 能有效抑制它对外部的电磁干扰,同样的道理,由于两根信号的极性相反,他们对外辐射的电磁场可以相互抵消,耦合的越紧密,泄放到外界的电磁能量越少。

  • 时序定位精确,由于差分信号的开关变化是位于两个信号的交点,而不像普通单端信号依靠高低两个阈值电压判断,因而受工艺,温度的影响小,能降低时序上的误差,同时也更适合于低幅度信号的电路。

  • 由于差分信号线具有这些优点,所以在USB协议、485协议、以太网协议及CAN协议的物理层中,都使用了差分信号传输。

CAN协议中的差分信号

CAN协议中对它使用的CAN_High及CAN_Low表示的差分信号做了规定。以高速CAN协议为例,当表示逻辑1时(隐性电平),CAN_High和CAN_Low线上的电压均为2.5v,即它们的电压差V H -V L =0V;而表示逻辑0时(显性电平),CAN_High的电平为3.5V,CAN_Low线的电平为1.5V,即它们的电压差为V H -V L =2V。

“STM32中CAN总线接口发送和接收数据"

CAN 总线的特点

CAN 总线网络是一种真正的多主机网络,在总线处于空闲状态时,任何一个节点单元都可以申请成为主机,向总线发送消息。其原则是:最先访问总线的节点单元可以获得总线的控制权;多个节点单元同时尝试获取总线的控制权时,将发生仲裁事件,具有高优先级的节点单元将获得总线控制权。

CAN 协议中,所有的消息都以固定的数据格式打包发送。两个以上的节点单元同时发送信息时,根据节点标识符(常称为 ID,亦打包在固定的数据格式中)决定各自优先级关系,所以 ID 并非表示数据发送的目的地址,而是代表着各个节点访问总线的优先级。如此看来,CAN 总线并无类似其他总线“地址”的概念,在总线上增加节点单元时,连接在总线的其他节点单元的软硬件都不需要改变。

CAN 总线的通信速率和总线长度有关,在总线长度小于 40m 的场合中,数据传输速率可以达到 1Mbps,而即便总线长度上升至 1000m,数据的传输速率仍可达到 50Kbps,无论在速率还是传输距离都明显优于常见的 RS232、RS485 和 I2C 总线。

对于总线错误,CAN 总线有错误检测功能、错误通知功能、错误恢复功能三种应对措施,分别应对于下面三点表述:所有的单元节点都可以自动检测总线上的错误;检测出错误的节点单元会立刻将错误通知给其他节点单元;若正在发送消息的单元检测到当前总线发生错误,则立刻强制取消当前发送,并不断反复发送此消息至成功为止。

CAN 总线上的每个节点都可以通过判断得出,当前总线上的错误是暂时的错误(如瞬间的强干扰)还是持续的错误(如总线断裂)。当总线上发生持续错误时,引起故障的节点单元会自动脱离总线。

CAN 总线上的节点数量在理论上没有上限,但在实际上受到总线上的时间延时及电气负载的限制。降低最大通信速率,可以增加节点单元的连接数;反之,减少节点单元的连接数,则最大通信速率可以提高。

CAN总线的数据通信是以数据帧的格式进行的,而数据帧又是由位场组成的,其中每一个位又被划分为四段.即SS(SYNC SEG),PTS(PROP SEG--传播时间段),PBS1(PHASE SEG1--相位缓冲段1),PBS2(PHASE SEG1--相位缓冲段2).

数据帧的结构图:

“STM32中CAN总线接口发送和接收数据"

图中D表示显性电平,R表示隐形电平.
ID:高位在前,低位在后。
基本ID,禁止高7位都为隐性,即不能:ID=1111111XXXX。
RTR,远程请求位。0,数据帧;1, 远程帧;
SRR,替代远程请求位。设置为1(隐性电平);
IDE,标识符选择位。0,标准标识符;1,扩展标识符;
r0,r1:保留位。必须以显现电平发送,但是接收可以是隐性电平。
DLC:数据长度码。0~8,表示发送/接收的数据长度(字节)。
IDE,标识符选择位。0,标准标识符;1,扩展标识符;

位时序分解

为了实现位同步,CAN协议把每一个数据位的时序分解成SS段、PTS段、PBS1段、PBS2段,这四段的长度加起来即为一个CAN数据位的长度。分解后最小的时间单位是Tq,而一个完整的位由8~25个Tq组成。

“STM32中CAN总线接口发送和接收数据"

STM32中的CAN接口

STM32的芯片中具有bxCAN控制器 (Basic Extended CAN),它支持CAN协议2.0A和2.0B标准。该CAN控制器支持最高的通讯速率为1Mb/s;可以自动地接收和发送CAN报文,支持使用标准ID和扩展ID的报文;外设中具有3个发送邮箱,发送报文的优先级可以使用软件控制,还可以记录发送的时间;具有2个3级深度的接收FIFO,可使用过滤功能只接收或不接收某些ID号的报文;可配置成自动重发;不支持使用DMA进行数据收发。

“STM32中CAN总线接口发送和接收数据"

1. CAN控制内核
2. CAN发送邮箱
3. CAN接收FIFO
4. 验收筛选器

STM32有两组CAN控制器,其中CAN1是主设备,框图中的“存储访问控制器”是由CAN1控制的,CAN2无法直接访问存储区域,所以使用CAN2的时候必须使能CAN1外设的时钟。

STM32至少配备一个bxCAN(basic extend can )控制器,支持2.0A和2.0B协议,最高数据传输速率可达1M bps,支持11位标准帧格式和29位扩展帧格式的接收和发送,具备三个发送邮箱和两个接收FIFO,此wa此外还有三级可编程滤波器,STM32的bxCAN非常适应CAN总线网络y网络应用发展需求,其主要主要特征如下 :

  • 支持CAN协议2.0A和2.0B主动模式

  • 波特率最高可达1Mbps

  • 支持时间触发通讯功能

数据发送特性:具备三个发送邮箱;发送报文的优先级可以通过软件配置,可记录发送时间的时间戳。

数据接收特性:具备三级深度和两个接收FIFO;具备可变的过滤器组,具备可编程标识符列表,可配置FIFO溢出处理方式,记录接收时间的时间戳。

报文管理:中断可屏蔽;邮箱单独占有一块地址空间,便于提高软件效率。

本文的实验设计将利用STM32 的bxCAN控制器的环回工作模式,实现bxCAN控制器的自收发过程,并使用串口设备跟踪监视数据收发情况。

程序流程如下图

“STM32中CAN总线接口发送和接收数据"

本程序设计主要围绕bxCAN控制器的初始化初始化配置展开,其要点罗列如下:

1. 初始化RCC寄存器,配置PLL输出72MHZ时钟,APB1总线频率为36MHZ,分别打开CAN,GPIO和USART1的设备时钟。

2. 设置CAN的Tx引脚(即PA12)为复用推挽模式,并设置Rx引脚(即PA1)为上拉输入模式,其中三个重要的参数如下配置:

CAN_InitStructure.CAN_SJW配置为CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1配置为CAN_BS1_8tq;
CAN_InitStructure.CAN_BS2配置为CAN_BS2_7tq;

3. 最后分频数配置为5,配置接受接受缓冲区标识符为0x00AA0000,配置过滤器为32位屏蔽位模式,过滤器屏蔽标识符为0x00FF0000.

4. 初始化USART设备

5. 使用扩展帧shu帧数据格式,ID为0xAA,数据长度长度为8

STM32的CAN控制器设计的重点集中在CAN寄存器组的初始化过程中,而CAN初始化的重点在于波特率的设置,过滤器的设置和位时序的设置,以下作详细叙述。

(1)CAN波特率的计算

计算波特率是任何一种总线的zhon重要内容之一,CAN总线也不例外.从STM32微控制器的官方参考手册里可以查找到关于CAN波特率的计算公式如下.

通过配置位时序寄存器CAN_BTR的TS1[3:0]及TS2[2:0]寄存器位设定BS1及BS2段的长度后,就可以确定每个CAN数据位的时间:

BS1段时间:

T S1 =Tq x (TS1[3:0] + 1),

BS2段时间:

T S2 = Tq x (TS2[2:0] + 1),

一个数据位的时间:

T 1bit =1Tq+T S1 +T S2 =1+ (TS1[3:0] + 1)+ (TS2[2:0] + 1)= N Tq

其中单个时间片的长度Tq与CAN外设的所挂载的时钟总线及分频器配置有关,CAN1和CAN2外设都是挂载在APB1总线上的,而位时序寄存器CAN_BTR中的BRP[9:0]寄存器位可以设置CAN外设时钟的分频值 ,所以:

Tq = (BRP[9:0]+1) x T PCLK

其中的PCLK指APB1时钟,默认值为36MHz。

最终可以计算出CAN通讯的波特率:

BaudRate = 1/N Tq

程序设计要点中强调的三个重要参数,其实是can总线物理层中所要求的位时序。共三个阶段,分别为SJW,BS1和BS2阶段,这三个阶段的时间长度都是以长度为tq的时间单元为单位的。这样可以逐步计算出CAN总线的波特率。因此要点提示中所要求的参数,实际上将CAN的波特率设置为450kdps。

过滤器的设置

can总线没有所谓地址的概念。总线上的每个报文都可以被各个节点接收。这是一种典型的广播式网络。在实际应用中。某个节点往往只希望接收到特定类型的数据, 这就要借助过滤器来实现。顾名思义,过滤器的作用就是把节点不希望接收到的数据过滤掉。只将希望接收到的数据给予通行。

stm32的CAN控制器,提供14个过滤器。可以设置为屏蔽模式和列表模式对can总线上的报文进行过滤。当节点希望接收到一种报文时。可以用屏蔽位模式对can总线上的报文进行过滤。反之,当节点希望接受到单一类型报文时。则应该配置为列表模式。本机程序中使用了32位的屏蔽位模式。

下面仅对这种模式进行解析。can控制器的每个过滤器都具备一个寄存器。称为屏蔽寄存器。其中标识符寄存器的每一位都有屏蔽寄存器的每一位所对应。事实上,这也对应着can数据。事实上,这也对应着看标准数据帧中的标识符段。

如下图所示。

“STM32中CAN总线接口发送和接收数据"

此处重点在于屏蔽寄存器的作用。通过查阅stm32微控制器参考文档可以知道。当过滤器工作在屏蔽模式下时。屏蔽寄存器被置为1的每一位都要求can接收到的数据帧标识符段必须和对应的接收缓冲区标识位相同。否则予以滤除。以本程序为例。要点中要求将节点接收缓冲标识符配置为0x00AA0000。过滤器屏蔽标识符为0x00FF0000。

该节点接收到的数据帧的标识符段的位[23:16],必须和接收缓冲区标识符中的[23:16]匹配。否则予以滤除。但若满足了这一条件而即便如下的位不匹配。则该数据帧仍不会被滤除。正如本程序而言。即can接口仅仅接收标识符段的位[23:16]为0xAA的数据帧。

根据can总线物理层的要求。can总线的波特率和传输距离成反比关系。传输距离变化时,要根据位时序来调整can总线的波特率。

“STM32中CAN总线接口发送和接收数据"

“STM32中CAN总线接口发送和接收数据"

程序代码如下:

void RCC_Config(void)
{

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE)
 
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE)
 
}
void GPIO_for_can_and_uart_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
 

/*设置can的RX--pa.11引脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

GPIO_Init(GPIOA, &GPIO_InitStructure);

/*设置can的TX--pa.12引脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*设置usart1 的RX 脚 -PA.10为父浮空输入脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOA, &GPIO_InitStructure);
 



}
 
void Can_Config(void)
{
CAN_InitTypeDef          CAN_InitStructure;
CAN_FilterInitTypeDef  CAN_FilterInitStructure;
 
CAN_DeInit(CAN1);
 
CAN_StructInit(&CAN_InitStructure);
CAN_InitStructure.CAN_TTCM=DISABLE;
CAN_InitStructure.CAN_ABOM=DISABLE;
CAN_InitStructure.CAN_AWUM=DISABLE;
CAN_InitStructure.CAN_NART=DISABLE;
CAN_InitStructure.CAN_RFIM=DISABLE;
CAN_InitStructure.CAN_TXFP=DISABLE;
 
CAN_InitStructure.CAN_Mode=CAN_Mode_LoopBack;
CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1=CAN_BS1_8tq;
CAN_InitStructure.CAN_BS2=CAN_BS2_7tq;
 
CAN_InitStructure.CAN_Prescaler=5;
CAN_Init(CAN1,&CAN_InitStructure);
 
CAN_FilterInitStructure.CAN_FilterNumber=0;
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh=0x00AA<<3;
CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x00FF<<3;
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=0;
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
 
}
 
void main(void)
{
u8 TransmitMailbox=0;
CanTxMsg   TxMessage;
CanRxMsg   RxMessage;
RCC_Config();
GPIO_for_can_and_uart_Config();
USART_Config();
Can_Config();
 
TxMessage.ExtId=0x00aa0000;
TxMessage.RTR=CAN_RTR_Data;
TxMessage.IDE=CAN_ID_EXT;
TxMessage.DLC=8;
TxMessage.Data[0]=0x00;
TxMessage.Data[1]=0x12;
TxMessage.Data[2]=0x34;
TxMessage.Data[3]=0x56;
TxMessage.Data[4]=0x78;
TxMessage.Data[5]=0xab;
TxMessage.Data[6]=0xcd;
TxMessage.Data[7]=0xef;
 
TransmitMailbox=CAN_Transmit(CAN1,&TxMessage);
while((CAN_TransmitStatus(CAN1,TransmitMailbox))!=CANTXOK);
printf("rnThe CAN has send data :0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,rn",
TxMessage.Data[0],TxMessage.Data[1],TxMessage.Data[2],TxMessage.Data[3],
TxMessage.Data[4],TxMessage.Data[5],TxMessage.Data[6],TxMessage.Data[7],);
 
while((CAN_MessagePending(CAN1,CAN_FIFO0)==0));

RxMessage.StdId=0x00;
RxMessage.IDE=CAN_ID_EXT;
RxMessage.DLC=0;
RxMessage.Data[0]=0x00;
RxMessage.Data[1]=0x12;
RxMessage.Data[2]=0x34;
RxMessage.Data[3]=0x56;
RxMessage.Data[4]=0x78;
RxMessage.Data[5]=0xab;
RxMessage.Data[6]=0xcd;
RxMessage.Data[7]=0xef;
 
CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);
printf("rnThe CAN has received data :0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,rn",
RxMessage.Data[0],RxMessage.Data[1],RxMessage.Data[2],RxMessage.Data[3],
RxMessage.Data[4],RxMessage.Data[5],RxMessage.Data[6],RxMessage.Data[7],);
while(1);
 
 
}

来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 3248

STM32外部晶振频率设置

cathy的头像

之前申请了一个STM32免费的迷你板,调试串口通信时总是弄不好。后来发现大多数stm32采用的是8M的晶振,但是手里这块迷你版采用的是12M的晶振,其实STM32支持4—16MHz之内任何频点的晶振。于是想到了两个解决方案:

第一个是改程序,第二个是换晶振。本着不乱改硬件的原则,决定开始更改程序。

“STM32外部晶振频率设置"

经过查资料得知,其实就是把system_stm32f10x.c里面的时钟设置改一下就行了,原本8*9=72。12M的晶振72/12=6。但是把响应位置改过了之后并没有成功。后来查倒stm32f10x.h里面也有时钟的设置。再次更改,依旧失败。最终到处查询后找到了问题所在——keil自身设置。

参考链接:

https://blog.st.com/stm32cubeprogrammer-stm32cubemonitor/

“”STM32CubeProgrammer

行而不辍,履践致远。为了给嵌入式工程师带来全新的STM32开发调试体验,大幅提升开发感受,ST在生态建设和开发工具创新上的努力从未间断。

STM32Cube系列大家族,相信大家用得很顺手。该系列软件开发工具大家族,提供了从MCU配置、代码生成、编译以及调试,到程序烧录和监测整个软件开发流程需要的全部功能。它们各司其职,也有功能交叠,大家合力,共同给STM32开发者搭建了一个完整的开发平台。

ST 近期推出新版本的 STM32CubeProgrammer、STM32CubeMonitor、STM32CubeMonitor-RF 和 STM32CubeMonitor-UCPD。许多 STM32 开发人员通过使用它们更快地将产品推向市场。所有嵌入式系统工程师都需要面对这样的挑战,为选用的微控制器或微处理器寻找功能全面的开发平台。一个设备可能有很多特性需求,设计人员如何有效地实现这些性能非常关键。因此,泛生态软件工具在推动基于 STM32 的嵌入式系统开发至关重要。让我们一起探索STM32CubeProgrammer 和 STM32CubeMonitor的新功能。

STM32CubeProgrammer关键词

统一的体验

STM32CubeProgrammer 旨在统一用户体验。ST 将 ST-LINK Utility、DFU 等实用程序的所有功能引入 STM32CubeProgrammer,使其成为嵌入式系统开发人员的一站式解决方案。ST还将它设计为适用于所有主要操作系统,甚至集成 OpenJDK8-Liberica, 以方便安装。在体验 STM32CubeProgrammer 之前,用户无需自己安装 Java ,也不用为兼容性问题烦恼。该实用程序有两个关键组件:图形用户界面和命令行界面。用户既可以选择直观的图形用户界面进行工作,也可以选择使用命令行工具来编写脚本文件。

STM32 Flasher 和调试器

STM32CubeProgrammer 的核心是帮助调试和烧写 STM32 微控制器。因此,它也包括优化这两个过程的功能。例如,2.6 版引入了导出整个寄存器内容和动态编辑任何寄存器的能力。以往,更改寄存器的值意味着更改源代码、重新编译并刷新固件。如今,测试新参数或确定某个值是否导致错误要简单得多。同样,工程师现在可以使用 STM32CubeProgrammer 一次烧写所有外部存储器。但在以前,烧写外部嵌入式存储和 SD 卡需要开发人员单独启动每个进程。而STM32CubeProgrammer 可以一步完成。

开发人员面临的另一个挑战是解析通过 STM32CubeProgrammer 传递的大量信息。刷过固件的人都知道跟踪所有日志有多么困难。因此,我们带来了自定义跟踪功能,允许开发人员为不同的日志信息设置不同的颜色。它确保开发人员可以快速将特定输出与日志的其余部分区分开来,从而使调试变得更加直接和直观。此外,它可以帮助开发人员使用与 STM32CubeIDE 一致的配色方案,STM32CubeIDE 是我们独特的生态系统的另一个成员,旨在为开发者提供支持。

STM32 上的安全门户

STM32CubeProgrammer 是 STM32Cube 生态系统中安全解决方案的核心部分。该实用程序附带 Trusted Package Creator,它使开发人员能够将 OEM 密钥上传到硬件安全模块并使用相同的密钥加密他们的固件。然后,OEM 使用 STM32CubeProgrammer 将固件安全地安装到支持SFI的 STM32 微控制器上。开发人员甚至可以使用 I2C 和 SPI 接口,这为他们提供了更大的灵活性。此外,STM32L5 和 STM32U5 还支持外部安全固件安装 (SFIx),使 OEM 可以在微控制器外部的内存模块上刷新加密的二进制文件。

Sigfox 规定

使用 STM32WL 微控制器时,开发人员可以使用 STM32CubeProgrammer 提取嵌入到 MCU 中的 Sigfox 证书。首先,开发人员将这个 136 字节的字符串复制到他们的剪贴板或将其保存在二进制文件中。 其次,他们访问 my.st.com/sfxp,在那里粘贴证书并立即以 ZIP 文件的形式下载 Sigfox 凭据。第三,他们通过 STM32CubeProgrammer 将下载包的内容加载到 MCU,并使用 AT 命令获取 MCU 的 Sigfox ID 和 PAC。最后,开发者去 https://buy.sigfox.com/activate/ 进行注册。激活后两年有效,开发者可以在一年内每天免费发送 140 条消息。

“▲
▲ STM32CubeProgrammer

STM32CubeProgrammer 的新功能

双重认证

最新版本 STM32CubeProgrammer 2.9 现在支持双重身份验证系统,通过 JTAG 或引导加载程序为 STM32WB 上的蓝牙协议栈配置OEM的密钥。简而言之,该功能使制造商能够保护其蓝牙协议栈防止最终用户随意更新协议栈。事实上,在大多数情况下,如果开发人员知道自己在做什么,他们就可以使用 ST 的安全固件更机制升级新蓝牙协议栈。但是,制造商可能会希望使用特定版本的协议栈配置,希望对其进行保护。因此,双重身份验证系统会阻止最终用户随意使用更新机制。ST 已发布了AN5185 应用笔记以提供更多详细信息。

脚本模式

新软件对其命令行界面 (CLI) 进行了更新,以支持创建脚本。由于脚本管理器是应用程序的一部分,因此它不依赖于操作系统或其外壳环境。因此,脚本是高度可共享的。另一个优点是脚本管理器可以保持与目标的连接。使用传统的批处理文件时,STM32CubeProgrammer CLI 需要在每一步都重新连接。另一方面,新的脚本管理器可以在整个会话期间保持连接。它还可以处理局部变量,甚至支持对这些变量的算术或逻辑运算。因此,开发人员可以创建强大的宏更好地实现复杂流程的自动化。

PKCS#11 支持

现在,STM32CubeProgrammer 2.9在为 STM32MP1 加密固件时可支持 PKCS#11。公钥加密标准 (PKCS) 11,也称为 Cryptoki,是一种在低级别管理加密过程的标准。PCKS#11标准化的API 方便嵌入式系统开发人员使用相关机制,因而越来越受流行。在 STM32MP1 上,PKCS#11 允许工程师分离私钥的存储和安全秘密配置 (SSP) 的加密过程。

SSP 相当于 MPU 的安全固件安装SFI。开发人员在将代码发送给 OEM 之前,可以通过STM32CubeProgrammer 使用公私钥系统对其固件进行加密。第三方无法读取OEM的软件代码。在生产过程中,OEM 将使用硬件安全模块 (HSM),将解密的固件加载到 MPU 内部,HSM中包含固件加密密钥,这个密钥无法保护在HSM中无法被读出。到目前为止,加密 MPU 代码的开发人员可以访问私钥。但一些组织必须限制对此类关键信息的访问。解决这个问题的新方法是使用新版 STM32CubeProgrammer 和 PKCS#11,即使在开发人员的加密过程中,私钥仍然可隐藏在 HSM 中。

来源:STM32
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 69

页面

订阅 RSS - STM32