单片机

市面上很多基于单片机的产品都具有在线或离线升级功能,为了防止升级过程出现意外,一般我们都会对Flash程序数据进行校验,常见的就是添加 CRC 校验信息。

本文给大家讲述一下Keil和IAR中计算CRC值的方法。

Flash自检的流程

Flash的自检一般分为启动时自检和程序运行时自检两个阶段。不管是哪种自检,其思路都是:

在程序编译完成后,计算整个程序的CRC值,然后将这个CRC值添加到可执行文件末尾,再将带有CRC校验值的可执行文件烧录到MCU中。程序启动后,由程序中的自检代码重新根据当前Flash内容(不包括预存的CRC校验值)计算一次CRC值,再与之前预先计算并烧录到Flash中的CRC校验值进行比较,如果一致就通过检测。

这两个自检阶段的区别就是:

程序启动自检是一次性对整个实际Flash代码范围计算出最终的CRC值;而运行时的自检,为了不影响其他程序模块的运行,计算CRC的过程是分步进行的,每次计算一部分,分多次计算出最终的CRC值。围绕Flash的自检所发生的问题可以归为两大类,一类是预先计算CRC值时和上电后计算CRC值的Flash范围设置是否一致;第二类就是预先计算CRC时和上电后计算CRC采用的CRC算法是否一致。

如何添加CRC值

下面我们主要介绍如何添加CRC校验值到可执行文件。

1、基于IAR环境

如果你使用IAR,那么添加CRC值的配置相对比较简单。通过配置IAR的CRC计算参数,自动对整个FLASH空间进行CRC计算,并将计算结果放到FLASH的末尾。

1)修改Link文件,指定CRC值的存放位置

在Link文件中增加下面语句,指定checksum在FLASH中的存储位置。

1.jpg

该语句指定将CRC的值放在FLASH的末尾位置,是整个FLASH空间的末尾,不是应用程序的代码末尾。这样,CRC值的位置就是固定的。不会随代码大小而变化。

在自检代码中,可以通过__checksum读取Flash中保存的CRC校验值来与重新计算的CRC值进行比较。

2)配置Checksum页面的参数

在link文件中指定了checksum的存储位置后,还要在工程配置菜单中,配置计算CRC值的范围和参数。见下图:

2.jpg

IAR的checksum页面分为两个部分。

第一部分,也就是红线圈出的部分。定义了FLASH中需要计算CRC的范围和空闲字节填充值。这里注意要留出flash末尾存储CRC值的位置。

剩下的部分,就是对checksum计算参数的设定部分。

Checksum size:选择checksum的大小(字节数)Alignment:指定checksum的对齐方式。一般,处理器不支持非对齐访问时有用,不填的话默认1字节对齐。

Algorithm:选择checksum的算法。

Complement:是否需要进行补码计算。选择“As is”就是不进行补码计算。

Bit order:位输出的顺序。

MSB first,每个字节的高位在前。LSB first,每个字节的低位在前。

Reverse byte order within word: 对于输入数据,在一个字内反转各个字节的顺序。

Initial value: checksum计算的初始化值。

Checksum unit size:选择进行迭代的单元大小,按8-bit,16-bit还是32-bit进行迭代。

3)STM32CRC外设的配置

与上图IARchecksum的配置对应,STM32 CRC外设可以按以下配置:

POLY= 0x4C11DB7(CRC32)

Initial_Crc = 0Xffffffff

输入/输出数据不反转

输入数据:根据实际Flash范围设定,留出CRC校验值的位置

CRC外设初始化及计算代码:

3.jpg

2、基于Keil环境

KEIL没有提供直接生成CRC值的功能,所以需要借助外部的工具计算CRC值,然后添加到可执行文件的末尾。在X-CUBE-CLASSB软件中提供了bat文件,它会利用外部工具Srecord来生成整个Flash的CRC校验码并放在文件末尾。这个工具同样也可以和标准外设库的ClassB库一起用。下面我们就来看看如何在KEIL工程中利用Srecord工具来添加CRC值。

1)安装Srecord工具

下载Srecord 工具(http://srecord.sourceforge.net )。将srec_cat.exe,srec_cmp.exe,srec_info.exe拷贝到C:\SREC(自己新建)目录下。

2)添加crc_gen_keil.bat及crc_load.ini文件到KEIL工程同级目录下

打开X-CUBE-CLASSB软件包中的任意KEIL工程目录,将其中crc_gen_keil.bat及crc_load.ini文件拷贝到自己的KEIL工程目录下。

crc_gen_keil.bat:利用外部工具Srecord来生成整个Flash的CRC校验码并放在文件末尾。

crc_load.ini:这个文件调试时有用,用来配置调试时导入带CRC校验码的HEX,避免对FLASH检测失败导致程序无法正常运行。

4.jpg

这两个文件中的内容也需要根据新工程路径进行修改:

  • 将crc_gen_keil.bat中的TARGET_NAME和TARGET_PATH改成跟新工程一致。否则不能成功的自动生成带CRC校验值的HEX文件。

5.jpg

  • Crc_load.ini文件中的路径和文件也要和实际的一致

6.jpg


3)添加定义CRC校验码存储区域

7.jpg

在分散加载文件中将CHECKSUM指定在代码的末尾。和IAR不同的是,通过在分散加载文件中+last指定checksum的位置,它不是将其固定放在整个flash地址的末尾,而是放在实际代码的末尾。

8.jpg

4)添加外部命令让KEIL在编译结束后,自动生成一个带CRC校验值的HEX文件

9.jpg

定义debug和flash download使用的HEX文件路径,使用带CRC校验值的HEX文件。

10.jpg

11.jpg

5)STM32CRC外设的配置

这里需要注意,从X-CUBE-CLASSB的软件包里拷贝出的crc_gen_keil.bat文件,里面的BYTE_SWAP设为1,也就是它在计算CRC值的时候,输入的数据,在一个字内按字节颠倒顺序。

12.jpg

所以直接用HAL_CRC_Calculate函数进行计算结果是不对的。可以参考下面的代码来初始化及进行计算:

13.jpg

或者,将crc_gen_keil.bat文件,里面的BYTE_SWAP改为0, 就可以直接调用HAL_CRC_Calculate函数进行计算了。

总结

本文介绍了基于IAR及ARM KEIL中如何添加CRC校验值的过程。在X-CUBE-CLASSB软件包中,也都可以找到对应的例程。如果在调试中,遇到FLASH CRC校验出错,也不用急。

可以从计算CRC值的范围设置是否一致和采用的CRC算法是否一致这两个方面去找原因。一定要调试看看代码实际执行的情况,比如要测试的地址范围和实际代码执行时计算的地址范围是否一样,防止因为coding错误造成检测不通过。

直接来源:strongerHuang

素材来源 | 网络

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

围观 312

Holtek着眼于中高阶应用市场,宣布新推出HT32F49365/HT32F49395高性能32-bit单片机。采用高效能Arm® Cortex®-M4核心,提供单精度浮点运算单元(FPU),支持所有Arm®单精度数据处理指令和数据类型,内置完整DSP指令和内存保护单元(MPU),增强数值运算效能与应用安全性。高集成度与高运算效能并提供多种节能模式,可满足各种应用场景,例如智能家居、工业控制、嵌入式系统、电机控制等。

1.jpg

HT32F49365/HT32F49395系列最高运行速度可达240MHz,工作电压范围为2.6V~3.6V,符合-40℃~105℃工业温度范围。Flash容量为256KB/1024KB,SRAM容量最高为224KB,可支持外部SPI Flash扩展 (最高16 MBytes寻址能力),并提供耐受5V电压输入的GPIO。丰富的外围资源包括:12-bit 2 Msps ADC ×3,12-bit DAC ×2,16-bit通用定时器 ×8,32-bit通用定时器 ×2,高级控制定时器 ×2,I²C ×3,SPI/I²S ×4,SDIO ×2,USART/UART ×8,USB FS ×1和CAN ×2。提供封装48QFN、48/64/100LQFP,依据封装类型GPIO引脚可达37~80个。

HT32F49365/HT32F49395系列支持业界主流Keil MDK及IAR EWARM开发环境,并提供硬件开发工具包、周边驱动函数库(Firmware Library)及应用范例等,操作系统支持FreeRTOS及RT-Thread OS。

来源:Holtek

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

围观 28

GD32F30x 和 GD32F403 系列硬件为参考。

一. 硬件设计 

1.电源

1.png




2.png

3.png

2.复位

4.png


注意:

1. 内部上拉电阻40kΩ,建议外部上拉电阻建议10kΩ,以使得电压干扰不会导致芯片工作异常;

2. 若考虑静电等影响,可在NRST管脚处放置ESD保护二极管;

3. 尽管MCU内部有硬件POR电路,仍推荐外部加NRST复位阻容电路;

4. 如果MCU启动异常(由于电压波动等),可适当增加NRST对地电容值,拉长MCU复位完成时间,避开上电异常时序区。

3.时钟

GD32F30x/GD32F403系列内部有完备的时钟系统,可以根据不同的应用场合,选择合适的时钟源,时钟主要特征:

4-32MHz外部高速晶体振荡器(HXTAL);

8MHz内部高速RC振荡器(IRC8M);

32.768KHz外部低速晶体振荡器(LXTAL);

48 MHz内部高速RC振荡器(IRC48M);

40kHz内部低速RC振荡器(IRC40K);

PLL时钟源可选HXTAL、IRC8M或IRC48M;

HXTAL时钟可监控;

时钟树如下:

5.png







6.png

7.png

8.png

9.png

4.启动配置

GD32F30x/GD32F403系列提供三种启动方式,可以通过BOOT0和BOOT1来进行相关的配置。

用户可以配置BOOT0和BOOT1,进行上电复位或系统复位,从而确定启动选项。电路设计时,运行用户程序,BOOT0不能悬空,建议通过一个10kΩ电阻到GND;运行System Memory进行程序更新,需要将BOOT0接高,BOOT1接低,更新完成后,再将BOOT0接低上电才能运行用户程序;SRAM执行程序多用于调试状态下。

嵌入式的 Bootloader 存放在系统存储空间,用于对 FLASH 存储器进行重新编程。在GD32F305xx/ GD32F307xx/ GD32F403xx设备中,Bootloader可以通过USART0 (PA9 and PA10),USART1 (PD5 and PD6),USBFS (PA9, PA11 and PA12)和外界交互。在GD32F303xx(Flash<512kB)设备中,Bootloader可以通过USART0 (PA9 and PA10) 和外界交互, 在GD32F303xx(Flash>512kB)设备中,Bootloader可以通过USART0 (PA9 and PA10) USART1 (PA2 and PA3)和外界交互。




10.png

11.png

5.下载调试

GD32F30x/GD32F403系列内核支持JTAG调试接口和SWD接口。JTAG接口标准为20针接口,其中5根信号接口,SWD接口标准为5针接口,其中2根信号接口。

注意:复位后,调试相关端口为输入PU/PD模式,其中:

PA15:JTDI为上拉模式;

PA14:JTCK / SWCLK为下拉模式;

PA13:JTMS / SWDIO为上拉模式;

PB4:NJTRST为上拉模式;

PB3:JTDO为浮空模式。

12.png





13.png

14.png

15.png

有以下几种方式可以提高SWD下载调试通信的可靠性,增强下载调试的抗干扰能力。

1. 缩短SWD两个信号线长度,最好15cm以内;

2. 将SWD两根线和GND线编个麻花,缠在一起;

3. 在SWD两根信号线对地各并几十pF小电容;

4. SWD两根信号线任意IO串入100Ω~1KΩ电阻。

6.典型外设

ubs外设电路

16.png




17.png

18.png

二. PCB Layout

1.电源去耦电容

GD32F30x/GD32F403系列电源有VDD、VDDA、VREF+和VBAT四个供电脚,100nF去耦电容采用陶瓷即可,且需要保证位置尽可能地靠近电源引脚。电源走线要尽量使得经过电容后再到达MCU电源引脚,建议可通过靠近电容PAD处打Via的形式Layout。

19.png


2.时钟电路

GD32F30x/GD32F403系列时钟有HXTAL和LXTAL,要求时钟电路(包括晶体或晶振及电容等)靠近MCU时钟引脚放置,且尽量时钟走线由GND包裹起来。

20.png


注意:

1. 晶体尽量靠近MCU时钟Pin,匹配电容等尽量靠近晶体;

2. 整个电路尽量与MCU在同层,走线尽量不要穿层;

3. 时钟电路PCB区域尽量禁空,不走任何与时钟无关走线;

4. 大功率、强干扰风险器件及高速走线尽量远离时钟晶体电路;

5. 时钟线进行包地处理,以起到屏蔽效果。

3.复位电路

21.png


注意:复位电路阻容等尽可能地靠近MCU NRST引脚,且NRST走线尽量远离强干扰风险器件及高速走线等,条件允许的话,最好将NRST走线做包地处理,以起到更好的屏蔽效果。

4.USB 电路

USB模块有DM、DP两根差分信号线,建议PCB走线要求做特性阻抗90ohm,差分走线严格按照等长等距规则来走,且尽量使走线最短,如果两条差分线不等长,可在终端用蛇形线补偿短线。

由于阻抗匹配考虑,串联匹配电阻建议50Ω左右即可。当USB终端接口离MCU较远的时候,需要适当增大该串联电阻值。

22.png


注意:

1. 布局时摆放合理,以缩短差分走线距离;

2. 优先绘制差分线,一对差分线上尽量不要超过两对过孔,且需要对称放置;

3. 对称平行走线,保证两根线紧密耦合,避免90°、弧形或45°走线方式;

4. 差分走线上所接阻容、EMC等器件,或测试点,也要做到对称原则。

对于USB HS模块,MCU与外部HS PHY之间的数据线与信号控制线也尽量走短,需要用蛇形线做等长处理,注意事项如下:

1. 布局时摆放合理,USB HS-PHY芯片与MCU之间尽量紧凑;

2. 布线时,以信号线中最长的一根线长度为目标,将其他信号线通过蛇形走线补偿即可。

5.BGA 走线

GD32F403x 系列中包含 BGA100 的封装,对应的型号为 GD32F403VxH6,该芯片走线和其它 BGA 芯片类似,先对各个球型焊盘进行扇出,再进行布线操作。对于 0.5 mm Pitch 的 BGA封装,若将 BGA 焊盘大小设置为 0.25/0.35,过孔距焊盘以及线宽线距为 3 mil 时,可以使用Dog bone 型扇出,扇出后如图 3-5. BGA100 封装的扇出方式所示,过孔距焊盘距离为 4.5mil;但此种布线对 PCB 制造商工艺要求较高,需与 PCB 制造商沟通后再进行布线,若制造商工艺达不到要求,可对此 BGA 封装打盘中孔以及盲埋孔

23.png



参考:

https://gd32mcu.com/cn/download/10?kw=

来源: 嵌入式学习与实践

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

围观 106

CW32的LVD低电压检测器适用于监测VDDA电源电压或外部引脚输入电压,当被监测电压与LVD阈值的比较结果满足触发的条件时,LVD将会产生中断或者复位信号,通常用来处理一些紧急任务。LVD产生的中断或复位标志,只能通过软件程序清零,并且只有当中断或复位标志被清零后,在再次达到触发条件时,LVD才能再次产生中断或复位信号。在本文中以CW32L083系列为例,介绍LVD的基本功能和使用例程。

LVD的基本功能介绍:

1、4路监测电压源

VDDA电源电压,PA00引脚输入,PB00引脚输入,PB11引脚输入

2、16阶阈值电压,范围2.02V-3.76V

3、3种触发条件,可以组合使用

电平触发:电压低于阈值

下降沿触发:电压跌落到阈值以下的下降沿

上升沿触发:电压回升到阈值以上的上升沿

4、可触发产生中断或复位信号,二者不能同时产生

5、8阶滤波可配置

6、支持迟滞功能

7、支持低功耗模式下运行,中断唤醒MCU

1.png

通过LVD的控制寄存器LVD_CR0的SOURCE位域来选择LVD模块监控的电压(VDDA电源/ PA00引脚/PB00引脚/PB11引脚),在监测外部引脚电压时,需将对应的GPIO端口配置为模拟输入模式(GPIOx_ANALOG.PINy = 1)。

LVD的比较结果可以从PA01/PA08/PC12/PE02/PF02脚输出,在此之前,需将对应的GPIO口配置为数字输出模式,同时选择端口位LVDOUT复用功能。

LVD 内置的电压比较器具有迟滞功能,只有当被监测电压高于或低于阈值电压达到 20mV 时,比较器输出信号才会发生翻转,可避免当 LVD 的监测电压在阈值电压附近时,电压比较器的输出结果发生频繁翻转,增强系统抗干扰能力。具体波形如下图所示:

2.png

LVD的阈值电压根据LVD控制寄存器LVD_CR0的VTH位控制。

3.png

LVD支持数字滤波功能,可以增强系统的鲁棒性(系统在一定的参数抖动下,维持起某些性能的特性),可以将LVD电压比较的输出结果信号进行数字滤波,小于滤波宽度的信号被滤除,不会被触发中断或复位,如下图所示,图中两处噪音或其他信号就被滤除了。

4.png

通过设置控制寄存器LVD_CR1的FLTEN位域,可以使能数字滤波模块,当将该位设置为1的时候,会使能数字滤波模块。

通过设置控制寄存器 LVD_CR1 的 FLTCLK 位域可以选择数字滤波的时钟:

• FLTCLK 位为 1,选择 HSIOSC 作为滤波时钟 

• FLTCLK 位为 0,选择内置 RC 振荡器时钟作为滤波时钟,其频率约 150kHz

控制寄存器 LVD_CR1 的 FLTTIME 位域用于选择数字滤波的时钟个数,如下表所示:

5.png

从 LVD 状态寄存器 LVD_SR 的 FLTV 位域,可以读出经 LVD 数字滤波后的信号电平;当 GPIO 的功能复用为 LVD_OUT 时,数字滤波后的信号就可以从 GPIO 输出,以方便观察测量。

LVD 支持在低功耗模式下工作,中断输出可将芯片从低功耗模式下唤醒。当被监测电压与 LVD 阈值的比较结果满足触发条件时,可产生中断或复位信号。产生中断还是复位信号由控制寄存器 LVD_CR0 的 ACTION 位域控制:

 • ACTION 为 1,LVD 触发产生复位 #define LVD_Action_Reset  ((uint32_t)0x00000002)

 • ACTION 为 0,LVD 触发产生中断 #define LVD_Action_Irq   ((uint32_t)0x00000000)

LVD可以通过设置控制寄存器 LVD_CR0 的 IE 位域为 1,使能 LVD 中断,满足触发条件时将产生 LVD 中断,中断标志位 LVD_SR.INTF 会被硬件置 1,用户可以向 INTF 位写 0,清除中断标志。设置控制寄存器 LVD_CR1 的 LEVEL、FALL、RISE 位域,可选择不同的中断或复位触发方式,三者可组合使用:

• LEVEL 为 1,被监测电压低于阈值时触发中断或产生复位 

• FALL 为 1,被监测电压跌落到阈值以下的下降沿触发中断或产生复位 

• RISE 为 1,被监测电压回升到阈值以上的上升沿触发中断或产生复位

LVD使用例程介绍:

根据上述内容,可以配置一个关于CW32L083的电压监测例程,LVD的输入通道设置为PA00,输出端口为PA08,门限电压为2.02V,利用LVD的中断实现当LVD输入通道电压低于或者高于门限电压时刻(利用上升沿和下降沿),PC03输出电平翻转一次。

void LVD_PortInit(void) 
{ 
    GPIO_InitTypeDef GPIO_InitStructure = {0}; 
    
    //打开GPIOA时钟 
    __RCC_GPIOA_CLK_ENABLE(); 
    
    //将PA08设置为LVD比较结果输出 
    GPIO_InitStructure.Pins = GPIO_PIN_8; 
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; 
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure); 
    
    //将PA08复用为LVD比较结果输出     
    PA08_AFx_LVDOUT();
     //将PA00设置为LVD的输入口 
    PA00_ANALOG_ENABLE(); 
} 

int main(void) 
{ 
    LVD_InitTypeDef LVD_InitStruct = {0}; 
    
    //LED初始化 
    LED_Init(); 
    
    //配置测试IO口 
    LVD_PortInit(); 
    
    LVD_InitStruct.LVD_Action = LVD_Action_Irq;  //配置中断功能 
    LVD_InitStruct.LVD_Source = LVD_Source_PA00; //配置LVD输入口为PA00 
    LVD_InitStruct.LVD_Threshold = LVD_Threshold_2p02V; //配置LVD基准电压为2.02v 
    LVD_InitStruct.LVD_FilterEn = LVD_Filter_Enable;//LVD滤波模块开启 
    LVD_InitStruct.LVD_FilterClk = LVD_FilterClk_RC150K;//LVD滤波时钟为150KHz 
    LVD_InitStruct.LVD_FilterTime = LVD_FilterTime_4095Clk; 
    LVD_Init(&LVD_InitStruct);
    
    LVD_TrigConfig(LVD_TRIG_FALL | LVD_TRIG_RISE, ENABLE);//LVD中断为上升沿和下降沿触发 
    LVD_EnableIrq(LVD_INT_PRIORITY); 
    LVD_ClearIrq(); 
    FirmwareDelay(4800); 
    LVD_Enable(); //LVD使能 
    
    while (1) 
    { 
        if (gFlagIrq) 
        { 
            PC03_TOG(); 
            gFlagIrq = FALSE; 
        } 
    } 
} 

/** 
* @brief LED I/O初始化 
* 
*/ 
void LED_Init(void) 
{ 
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    
    //打开GPIOC时钟 
    REGBITS_SET(CW_SYSCTRL->AHBEN, SYSCTRL_AHBEN_GPIOC_Msk); 
    
    /* Configure the GPIO_LED pin */ 
    GPIO_InitStructure.Pins = GPIO_PIN_2 | GPIO_PIN_3; 
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; 
    GPIO_Init(CW_GPIOC, &GPIO_InitStructure); 
    
    //LEDs are off. 
    PC02_SETLOW(); 
    PC03_SETLOW(); 
} 

//LVD中断服务函数 
void LVD_IRQHandler(void) 
{ 
    LVD_ClearIrq();      //清除中断标志 
    gFlagIrq = TRUE;     //将gFlagIrq赋值为TURE 
}

根据上述例程可以得到在PA00的输入电压值低于2.02v或高于2.02v的瞬间时刻,LVD会产生中断,PC03的输出电平会产生翻转,可利用CW32L083的开发板和一根杜邦线,将PA00和DVCC连接,在连接上的时刻以及拔掉杜邦线的时刻,LED1的状态会发生翻转。

来源:武汉芯源半导体

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

围观 169

程序架构重要性

很多人尤其是初学者在写代码的时候往往都是想一点写一点,最开始没有一个整体的规划,导致后面代码越写越乱,bug不断。

最终代码跑起来看似没有问题(有可能也真的没有问题),但是系统的可扩展性很差,添加一个功能的时候会浪费大量的时间,甚至导致整个代码的崩溃。

所以,在一个项目开始的时候多花一些时间在代码的架构设计上是十分有必要的。代码架构确定好了之后你会发现敲代码的时候会特别快,并且在后期调试的时候也不会像无头苍蝇一样胡乱找问题。当然,调试也是一门技术。

在学习实时操作系统的过程中,发现实时操作系统框架与个人的业务代码之间的耦合性就非常低,都是只需要将业务代码通过一定的接口函数注册好后就交给操作系统托管了,十分方便。

但是操作系统的调度过于复杂,这里就使用操作系统的思维方式来重构这个时间片轮询框架。实现该框架的完全解耦,用户只需要包含头文件,并且在使用过程中不需要改动已经写好的库文件。

Demo

首先来个demo,该demo是使用电脑开两个线程:一个线程模拟单片机的定时器中断产生时间片轮询个时钟,另一个线程则模拟主函数中一直运行的时间片轮询调度程序。

#include <thread>
#include <stdio.h>
#include <windows.h>
#include "timeslice.h"

// 创建5个任务对象
TimesilceTaskObj task_1, task_2, task_3, task_4, task_5;

// 具体的任务函数
void task1_hdl()
{        
    printf(">> task 1 is running ...\n");
}

void task2_hdl()
{    
    printf(">> task 2 is running ...\n");
}

void task3_hdl()
{    
    printf(">> task 3 is running ...\n");
}

void task4_hdl()
{    
    printf(">> task 4 is running ...\n");
}

void task5_hdl()
{    
    printf(">> task 5 is running ...\n");
}

// 初始化任务对象,并且将任务添加到时间片轮询调度中
void task_init()
{    
    timeslice_task_init(&task_1, task1_hdl, 1, 10);    
    timeslice_task_init(&task_2, task2_hdl, 2, 20);    
    timeslice_task_init(&task_3, task3_hdl, 3, 30);    
    timeslice_task_init(&task_4, task4_hdl, 4, 40);    
    timeslice_task_init(&task_5, task5_hdl, 5, 50);    
    timeslice_task_add(&task_1);    
    timeslice_task_add(&task_2);    
    timeslice_task_add(&task_3);    
    timeslice_task_add(&task_4);    
    timeslice_task_add(&task_5);
}

// 开两个线程模拟在单片机上的运行过程
void timeslice_exec_thread()
{    
    while (true)    
    {        
        timeslice_exec();    
    }
}

void timeslice_tick_thread()
{    
    while (true)    
    {        
        timeslice_tick();        
        Sleep(10);    
    }
}

int main()
{    
    task_init();
    
   printf(">> task num: %d\n", timeslice_get_task_num());    
   printf(">> task len: %d\n", timeslice_get_task_timeslice_len(&task_3));
   
   timeslice_task_del(&task_2);    
   printf(">> delet task 2\n");    
   printf(">> task 2 is exist: %d\n", timeslice_task_isexist(&task_2));
    
   printf(">> task num: %d\n", timeslice_get_task_num());    
   timeslice_task_del(&task_5);    printf(">> delet task 5\n");
   printf(">> task num: %d\n", timeslice_get_task_num());    
   printf(">> task 3 is exist: %d\n", timeslice_task_isexist(&task_3));   
   
   timeslice_task_add(&task_2);    printf(">> add task 2\n");    
   printf(">> task 2 is exist: %d\n", timeslice_task_isexist(&task_2));
    
   timeslice_task_add(&task_5);    printf(">> add task 5\n");   
   printf(">> task num: %d\n", timeslice_get_task_num());
    
   printf("\n\n========timeslice running===========\n");
    
   std::thread thread_1(timeslice_exec_thread);    
   std::thread thread_2(timeslice_tick_thread);
    
   thread_1.join();    
   thread_2.join();

   return 0;
}

运行结果如下:

1.jpg

由以上例子可见,这个框架使用十分方便,甚至可以完全不知道其原理,仅仅通过几个简单的接口就可以迅速创建任务并加入到时间片轮询的框架中,十分好用。

时间片轮询架构

其实该部分主要使用了面向对象的思维,使用结构体作为对象,并使用结构体指针作为参数传递,这样作可以节省资源,并且有着极高的运行效率。

其中最难的部分是侵入式链表的使用,这种链表在一些操作系统内核中使用十分广泛,这里是参考RT-Thread实时操作系统中的侵入式链表实现。

h文件:

#ifndef _TIMESLICE_H
#define _TIMESLICE_H

#include "./list.h"

typedef enum 
{    
    TASK_STOP,    
    TASK_RUN
} IsTaskRun;

typedef struct timesilce
{    
    unsigned int id;    
    void (*task_hdl)(void);    
    IsTaskRun is_run;    
    unsigned int timer;    
    unsigned int timeslice_len;    
    ListObj timeslice_task_list;
} TimesilceTaskObj;

void timeslice_exec(void);
void timeslice_tick(void);
void timeslice_task_init(TimesilceTaskObj* obj, void (*task_hdl)(void), unsigned int id, unsigned int timeslice_len);
void timeslice_task_add(TimesilceTaskObj* obj);
void timeslice_task_del(TimesilceTaskObj* obj);
unsigned int timeslice_get_task_timeslice_len(TimesilceTaskObj* obj);
unsigned int timeslice_get_task_num(void);
unsigned char timeslice_task_isexist(TimesilceTaskObj* obj);

#endifc文件:
#include "./timeslice.h"

static LIST_HEAD(timeslice_task_list);

void timeslice_exec()
{    
    ListObj* node;    
    TimesilceTaskObj* task;
   
   list_for_each(node, &timeslice_task_list)    
   {
        
        task = list_entry(node, TimesilceTaskObj, timeslice_task_list);        
        if (task->is_run == TASK_RUN)        
        {            
            task->task_hdl();            
            task->is_run = TASK_STOP;        
        }    
    }
}

void timeslice_tick()
{    
    ListObj* node;    
    TimesilceTaskObj* task;
   
   list_for_each(node, &timeslice_task_list)    
   {        
       task = list_entry(node, TimesilceTaskObj, timeslice_task_list);        
       if (task->timer != 0)        
       {            
           task->timer--;            
           if (task->timer == 0)            
           {                
               task->is_run = TASK_RUN;                
               task->timer = task->timeslice_len;            
           }        
       }    
   }
}

unsigned int timeslice_get_task_num()
{    
    return list_len(&timeslice_task_list);
}

void timeslice_task_init(TimesilceTaskObj* obj, void (*task_hdl)(void), unsigned int id, unsigned int timeslice_len)
{    
    obj->id = id;    
    obj->is_run = TASK_STOP;    
    obj->task_hdl = task_hdl;    
    obj->timer = timeslice_len;    
    obj->timeslice_len = timeslice_len;
}

void timeslice_task_add(TimesilceTaskObj* obj)
{    
    list_insert_before(&timeslice_task_list, &obj->timeslice_task_list);
}

void timeslice_task_del(TimesilceTaskObj* obj)
{    
    if (timeslice_task_isexist(obj))        
        list_remove(&obj->timeslice_task_list);    
    else        
        return;
}

unsigned char timeslice_task_isexist(TimesilceTaskObj* obj)
{    
    unsigned char isexist = 0;    
    ListObj* node;    
    TimesilceTaskObj* task;
    
   list_for_each(node, &timeslice_task_list)    
   {        
       task = list_entry(node, TimesilceTaskObj, timeslice_task_list);        
       if (obj->id == task->id)            
           isexist = 1;    
   }
    
   return isexist;
}

unsigned int timeslice_get_task_timeslice_len(TimesilceTaskObj* obj)
{    
    return obj->timeslice_len;
}

底层侵入式双向链表

该链表是linux内核中使用十分广泛,也十分经典,其原理具体可以参考文章:

https://www.cnblogs.com/skywang12345/p/3562146.html

h文件:

#ifndef _LIST_H
#define _LIST_H    
#define offset_of(type, member)             (unsigned long) &((type*)0)->member
#define container_of(ptr, type, member)     ((type *)((char *)(ptr) - offset_of(type, member)))

typedef struct list_structure
{    
    struct list_structure* next;    
    struct list_structure* prev;
} ListObj;

#define LIST_HEAD_INIT(name)    {&(name), &(name)}
#define LIST_HEAD(name)         ListObj name = LIST_HEAD_INIT(name)

void list_init(ListObj* list);
void list_insert_after(ListObj* list, ListObj* node);
void list_insert_before(ListObj* list, ListObj* node);
void list_remove(ListObj* node);
int list_isempty(const ListObj* list);
unsigned int list_len(const ListObj* list);

#define list_entry(node, type, member) \    
    container_of(node, type, member)
    
#define list_for_each(pos, head) \    
    for (pos = (head)->next; pos != (head); pos = pos->next)

#define list_for_each_safe(pos, n, head) \  
    for (pos = (head)->next, n = pos->next; pos != (head); \    
        pos = n, n = pos->next)
        
#endif

c文件:

#include "list.h"

void list_init(ListObj* list)
{    
    list->next = list->prev = list;
}

void list_insert_after(ListObj* list, ListObj* node)
{    
    list->next->prev = node;    
    node->next = list->next;
   
   list->next = node;    
   node->prev = list;
}

void list_insert_before(ListObj* list, ListObj* node)
{    
    list->prev->next = node;    
    node->prev = list->prev;
    
   list->prev = node;    
   node->next = list;
}

void list_remove(ListObj* node)
{    
    node->next->prev = node->prev;    
    node->prev->next = node->next;
   
   node->next = node->prev = node;
}

int list_isempty(const ListObj* list)
{    
    return list->next == list;
}

unsigned int list_len(const ListObj* list)
{    
    unsigned int len = 0;    
    const ListObj* p = list;    
    while (p->next != list)    
    {        
        p = p->next;        
        len++;    
    }
    
    return len;
}

到此,一个全新的,完全解耦的,十分方便易用时间片轮询框架完成。

来源:小麦大叔

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

围观 80

就8051 MCU(单片机)而言,烧录器的种类繁多,兹此我们为您介绍烧录工具的”在线升级”。过去有普通烧写器,这个工具大多用并口烧录,使用引脚较多。以笙泉科技的8051 MCU为例:MPC82系列、MPC89系列几乎为20或20多个引脚。这些带来诸多不便,假如程序有BUG,软件需要升级等,就会很麻烦,得要先把IC从系统上卸下来,然后烧录新的程序,最后再焊回去,……於是痛苦就这么产生了。

基于解决上述的痛点,方便的ISP和ICP方式应运而生。

方式1: ISP(在系统编程)

所谓的ISP即在系统编程: In-System Programming, 缩写为ISP), 参见下图之范例:

1.png

这样,使用者可以在系统上预留3个点:VCC、DTA(P3.1)、GND,通过笙泉专利默认的应用程序软件重新配置在线升级,由于烧录接口减少,第一段所述之痛苦也可逐步减轻。

2.png

8051 ISP烧录器(TH079)特色:

  • USB即插即用

  • 支持联机及脱机二种烧录模式

  • 支持滚动码烧录,此滚动码可当序列号或唯一ID号使用

  • 可设置最大烧录次数限制

  • 自动软件在线更新

  • 3线传输模式,接口简单好连接

3.png

虽然以ISP方式,其使用的接口少(VCC, P3.1, GND) ,但它必须在IC内先植入ISP CODE,也就是说必须用掉部分程序空间,且只能修改部分硬件选项。

方式2: 专业再升级-ICP(在电路编程)

1)  ICP (在电路编程)为一种电路,具有5线烧录模式,完全可代替前述的ISP方式之在线烧录工具,这个在笙泉科技的对应规格书上都有强烈建议,常搭配采用的仿真、烧录工具包括有OCD_ICE和M-Link。

4.png

2) ICP (在电路编程)特色:

  • 无须在目标芯片上预编程一个引导程序

  • 专用串行接口: 不占用 IO口

  • 目标芯片无须在运行状态: 仅需电源

  • 便携、独立的工作,而无须主机的干预

采ICP方式的普通烧写器,其关键是只要用到5个接口,一般来讲4个就足够 ( OCD_SCL, VDD, OCD_SDA, GND )。亦即该烧写器通过ICP方式(电路)来实现在线升级/下载更新程序的功能。

最后归纳: 在您设计的系统中,一定要预留ICP的接口(4个点 OCD_SCL, VDD, OCD_SDA, GND ),为您的产品保驾护航,从此前述之”痛苦”将消失,轻松实现在线升级更新。

来源:megawin笙泉科技

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

围观 91

作者: Bill Hawkins、 Zhang Feng、 Xavier Bignalet(Microchip Technology Inc.)

简介

在许多熟悉的系统中,通常需要对消耗品组件进行身份验证。例如,打印机墨盒、车辆中的电池、化妆品盒和送药设备中的药物等。本文档探讨了在现有设计中包含Microchip的CryptoAuthentication™系列ATSHA204A安全元件以实现对消耗品进行安全身份验证的优势。请参见图1。

1.png

图1:安全身份验证的雾化器框图

在本示例中,我们将从Microchip的AN2265“Vibrating Mesh Nebulizer Reference Design”(DS00002265) 开始,其中介绍了振动式网孔雾化器及其如何使用Microchip独立于内核的外设实现。本文档介绍了如何更新雾化器参考设计以包含可拆卸振动网和药盒的安全身份验证来增强设计。

雾化器参考设计具有用于固定振动网(将随时间逐渐磨损)和供应药物(一种耗材供应)的可拆卸药盒。这种储存药物的可拆卸药盒会极大地影响设备的性能。

出于以下几个原因,制造商可能希望保护这些药盒应用不受第三方供应商的影响:

  • 客户对设备的质量和功能的印象在很大程度上受药盒内物质的影响。当更换的第三方药盒不满足制造商的标准,并且提供的性能低于客户的预期时,制造商而不是第三方药盒厂商会接收到投诉。

  • 尤其是在医疗行业中,允许不受控地访问设备可能会导致制造商因用药剂量错误、药物受到污染甚至是消耗品药盒中的药物出错而承担责任。

  • 另一个原因是为了保护收入来源。典型的业务模式包括消耗品销售将提供支撑公司主要收入来源的预期。如果第三方可以在同一个市场上销售,那么制造商的收入会降低,而这部分收入本应补偿基本单元和研发(Research and development,R&D)的成本。

本文档介绍了对雾化器进行的安全增强,包括对可拆卸 药盒进行身份验证和跟踪其使用情况,并且可能限制药 盒的使用以避免出现硬件磨损、未授权重填和重用旧药 盒等问题。 

引入ATSHA204A

雾化器的药盒中使用ATSHA204A芯片向基本单元对药盒进行身份验证。ATSHA204A具有用于使能身份验证的多种功能,例如SHA-256哈希算法、安全EEPROM、惟一序列号、高质量随机数发生器和I 2C接口或单线接口。

SHA-256加密引擎

ATSHA204A在硬件中集成了SHA-256哈希算法。该哈希函数使用输入流(质询)并产生256位(32字节)输出(响应)。哈希函数的两个主要特性如下:

• 无法从哈希输出重新生成输入流。

• 无法改变输入流并获得相同的哈希输出。

ATSHA204A将使用主机单片机提供的质询字符串生成哈希密钥。单片机还可以使用该质询字符串和密钥来执行哈希函数。如果哈希输出与主机单片机预期的输出一致,则证明ATSHA204A器件知道相同的密钥。

安全EEPROM/密钥存储

ATSHA204A具有512字节的EEPROM,按十六个32字节槽排列。其中任何一个槽都可以配置为要在哈希函数中使用的密钥值。当配置ATSHA204A时,槽中的值可以受到写保护、读保护、用其他槽作为密钥进行加密或者保留为可读/可写的EEPROM存储单元。提供物理防篡改和边信道保护,以防止有人解密和探测器件芯片来读取存储器。

惟一序列号

保证每个ATSHA204A都有惟一的72位(9字节)序列号,从而允许制造商通过连接的ATSHA204A惟一识别任何器件。

高质量随机数发生器

ATSHA204A具有基于噪声源的高质量随机数发生器。这可以用来生成将只使用一次的惟一质询,并防止攻击设备记录质询和响应以在主机上回放来冒充有效器件。

I2C/单线接口

ATSHA204A提供I2C接口或单线接口,允许药盒在接口中具有最少的触点。经过身份验证的雾化器使用单线接口,这种接口采用具有三个大型焊盘的芯片封装。ATSHA204A芯片采用可粘到药盒上的设计,主机只提供滑动触点来连接到药盒(就像电池连接器一样),这样药盒就无需电路板或配对连接器。

如何使用ATSHA204A进行身份验证?

安全雾化器演示是单芯片系统设计。PIC16F1718运行整个操作,包括使用ATSHA204A进行身份验证所需的质询/响应。请参见图2

2.png

图2:ATSHA204A身份验证框图

PIC16具有16个存储在存储器中的质询/响应对。每次使用器件时,都会对药盒进行身份验证。当用户按下按钮来启动雾化器时,PIC16主机会向药盒上的ATSHA204A发送32字节质询。然后,ATSHA204A使用密钥对质询进行哈希运算,并将值返回到PIC16。返回的值与预期的响应进行比较,如果匹配,则允许雾化器运行。如果不匹配,雾化器的红色LED指示灯会点亮几秒钟,然后再熄灭。雾化器通过使用自由运行定时器中的低4位从16个质询中进行选择,但具体数量无法预测。

由于PIC16存储有限数量的质询/响应对,这就意味着克隆器件只需处理几个用例即可破坏该系统的安全性。为了防止出现这种情况,可以为每个PIC16主机编程惟一的质询/响应对,这样一来,通过这种方式创建的克隆器件便无法在任何其他器件上工作。

身份验证方法可保护配件/一次性用品,但无法防止克隆主机(在新系统中复制质询/响应对)。要实现更高级别的安全性,需要在主机MCU旁边添加独立安全元件(ATSHA204A)。请参见图3。该主机ATSHA204A会为药盒ATSHA204A产生随机质询,并提供响应以与来自药盒ATSHA204A的响应进行比较。此外,密钥派生实现将进一步提高解决方案的稳健性。

3.png

图3:对称身份验证

作为ATSHA204A的替代产品,ATSHA206A是外形受限应用的理想解决方案,在此类应用中,由于药盒尺寸受限而无法容纳任何PCB,因此只能采用2引脚封装。2引脚ATSHA206A或3引脚ATSHA204A可以塑封到一次性药盒中。ATSHA206A的内部添加了集成电容,可在电源引脚上提供寄生电源能力。本质上,数据和电源共用同一个引脚。

限制药盒的使用

经过身份验证的雾化器保存每个药盒的使用计数。此数据可用于限制药盒的使用。有多种机制可实现此功能。ATSHA204A器件包含EEPROM和SRAM两个存储器块。EEPROM分为三个区域:数据区域、配置区域和 一次性编程(One-Time-Programmable,OTP)区域。

数据区域分为16个通用存储器槽。EEPROM数据区域中的任何槽都可用于存储密钥。

配置区域中有128位分配为LastKeyUse,可用于限制存储在Slot 15中的密钥编号15的使用。每次使用Slot15作为密钥时,这128位都会清零。没有复位机制。在 使用128次之后,将永久禁止Slot/Key 15。因此,每个药盒的使用计数可以限制为128次或更少。

此外,还有512位的OTP存储器可用于存储只读数据或单向熔丝型消耗记录信息。在消耗模式下配置时,这些位可以写入0,但不能写回1。因此,在OTP存储器为全零之前,可以将0移入OTP存储器512次。在OTP存 储器为全零之后,可以禁止使用药盒。

如果药盒的使用次数小于这些值(Slot 15为128次, OTP为512次),则可以将其中一些位预编程为零,以便根据需要提供所需的次数。

关于安全增强功能的深入探讨

存储在主机MCU、ATSHA204A或公司计算机系统中的机密信息密钥必须受到绝对保护。如果密钥泄露,则安全功能将不再起作用,产品也很容易遭到克隆。

Microchip的一些32位单片机具有稳健的安全性,能够提供可信执行环境(Trusted Execution Environment,TEE)、加密加速器、安全引导和安全自举程序等。例如,图4显示了一个SAM L11作为配件身份验证应用中 (其中SAM L11增加了稳健性)的主机MCU正在托管TEE,即从MCU到安全元件的CryptoAuthlib API调用。TEE将Cryptoauthlib API回调会离开的关键代码与系统的其余部分隔离,并避免固件更改。

4.png

图4:一次性药盒身份验证

存储在ATSHA204A中的一次性药盒的密钥不会遭到窥探。ATSHA204A只是Microchip提供的其中一种安全芯片。Microchip还提供其他使用公钥加密(用于网络身份验证)的芯片来防止单一密钥泄露。每个芯片都有自己的密钥,其真实性由证书确定,该证书提供了器件到根证书颁发机构的可追溯性。这些芯片的说明超出了本文档的范围,可通过Microchip网站上的安全设计中心查看更多信息,网址如下:https://www.microchip.com/design-centers/security-ics

附录A:警告、限制和免责声明

医疗参考设计和演示仅用作评估和开发目的。如果将Microchip器件用于生命维持和/或生命安全应用,一切风险由买方自负。买方同意在由此引发任何一切损害、索赔、诉讼或费用时,会维护和保障Microchip免于承担法律责任。

附录B:参考资料

 • AN2265,“Vibrating Mesh Nebulizer Reference Design”(DS00002265),Bill Hawkins和 Zhang Feng,Microchip Technology Inc.

- AN2265 位于 Microchip 产品页面,网址为https://www.microchip.com/en-us/solutions/medical/drug-delivery-devices/nebulizer

• ATSHA206A一次性药盒安全身份验证

- 请参见ATSHA206A产品页面,网址为https://www.microchip.com/en-us/product/ATSHA206A

来源:Microchip 工程师社区

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

围观 162

页面

订阅 RSS - 单片机