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微控制器的性能和功能优势。

1 前言

在进行 USB 开发的过程中,有多个客户反馈,USB 传输数据时出现卡顿现象。本文将针对这
一问题进行分析。

2 问题分析

这几个客户问题现象基本差不多,采用 STM32 作为 Device 设备,在与上位机或者 PC 端双向通讯一段时间后,从 Device 端到 Host 端的数据能够正常,而从 Host 端到 Device 端的数据异常,也就是说,STM32 在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。

这几个客户,有用 STM32F2 的,也有用 STM32F4 的,有用 CDC 类的,也有用作 HID 设备的,但都使用了 Cube 库。

下面就具体问题以其中一个客户使用 STM32F411 的 USB CDC 类的案例来分析问题,现象如
下 USB 通讯数据(CDC 类):

STM32单片机USB传输数据时出现卡顿现象

展开 Data Out 数据:

STM32单片机USB传输数据时出现卡顿现象

分析上图发现,并不是 Host 端没有向 Device 端发送 Data Out 数据,而是确实发送了,但被 Device
端 NAK 了。那么为什么会被 NAK 呢?

通过在调试下查看寄存器,我们发现当出现问题时,Data OUT 对应的端点 1 是处于关闭状态,那么为
什么端点 1 会关闭?查看 STM32 端的接收代码:

usbd_cdc_if.c:

static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
{
   /* USER CODE BEGIN 6 */
   //USBTask_ReceiveMsg(Buf, *Len); //UserRxBufferFS
   USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
   USBD_CDC_ReceivePacket(&hUsbDeviceFS);
   return (USBD_OK);
   /* USER CODE END 6 */
}

如上代码,在 MCU 端接收到赖在 Host 端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现 Data Out 端点关闭的问题,那么 OUT 端点到底是怎么关闭的呢?我们接下来看子函数:

CDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() -
>HAL_PCD_EP_Receive() ->USB_EPStartXfer()

最后在 USB_EPStartXfer 函数中有发现再次使能 OUT 端点的代码:

//…
 else /* OUT endpoint */
 {
    /* Program the transfer size and packet count as follows:
    * pktcnt = N
    * xfersize = N * maxpacket
    */
    USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);
    USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);
    if (ep->xfer_len == 0U)
    {
       USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);
       USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U << 19U));
    }
    else
    {
       pktcnt = (ep->xfer_len + ep->maxpacket -1U)/ ep->maxpacket;
       USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (pktcnt << 19U));
       USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket *
      pktcnt));
    }
    if (dma == 1U)
    {
       USBx_OUTEP(ep->num)->DOEPDMA = (uint32_t)ep->xfer_buff;
    }

    if (ep->type == EP_TYPE_ISOC)
    {
       if ((USBx_DEVICE->DSTS & ( 1U << 8U )) == 0U)
       {
          USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;
       }
       else
       {
          USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
       }
    }
     /* EP enable */
     USBx_OUTEP(ep->num)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
 }

也就是说,在调用这个函数之前这个 OUT 端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?
不应该是 OUT 端点一旦打开就一直开着的吗?带着这些疑问,我们查看 STM32F411 的参考手册,终
于在 22.17.6 Operational model 一节中找到这么一幅图(由于 CDC 类数据传输采用的是 BULK 传输):

STM32单片机USB传输数据时出现卡顿现象

如上图,MCU 对 BULK 类型的 OUT 数据处理例程大体如下:

1> Host 端试图向一个端点发送 OUT token;
2> 当 Device 端的 USB 外设接收到这么一个 OUT token 后,如果 RXFIFO 空间足够,它将数据包存
储到 RXFIFO 中;
3> 在将数据包内容存储到 RXFIFO 后,USB 外设将产生一个 RXFLVL 中断(OTG_FS_GINTSTS);
4> 在接收到 USB 数据包的个数后(PKTCNT),USB 核将内部自动将这个 OUT 端点的 NAK 为置 1,以
阻止接收更多数据包;
5> 应用程序处理 RXFLVL 中断和从 RXFIFO 读取数据;
6> 当应用读取完所有数据(等于 XFRSIZ)后,USB 核将产生一个 XFRC 中断(OTG_FS_DOEPINTx);
7> 应用处理这个 OTG_FS_DOEPINTx 中断并通过 OTG_FS_DOEPINTx 的中断为 XFRC 来判断传输
完成;

从上面步骤中的第 4 步中可以看出,当 USB 核收到来自 Host 端的数据后会自动将 OUT 端点关闭,这
也就是为什么在接收函数中在接收下一次数据时要再次使能这个 OUT 端点的原因。因此我们大体可以
判断出在 OUT 数据传输的过程中,USB 核会禁止端点->打开端点->禁止端点…如此不断循环中;那么
问题到底出现在哪里呢?会不会在 USB 核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态
逐句查看代码,最终在接收函数的子函数中发现这么一段代码:

HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t*pBuf, uint32_t len)
{
    USB_OTG_EPTypeDef *ep;

    ep = &hpcd->OUT_ep[ep_addr & 0x7FU];

    /*setup and start the Xfer */
    ep->xfer_buff = pBuf;
    ep->xfer_len = len;
    ep->xfer_count = 0U;
    ep->is_in = 0U;
    ep->num = ep_addr & 0x7FU;

    if (hpcd->Init.dma_enable == 1U)
    {
      ep->dma_addr = (uint32_t)pBuf;
    }

   __HAL_LOCK(hpcd);
  
    if ((ep_addr & 0x7FU) == 0U)
    {
       USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    }
    else
    {
       USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    }
    __HAL_UNLOCK(hpcd); 
    return HAL_OK;
}

之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据
时,发送函数的底层函数内也使用到了这个互斥锁:

CDC_Transmit_FS() -> USBD_CDC_TransmitPacket() -> USBD_LL_Transmit() ->
HAL_PCD_EP_Transmit() :
HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t*pBuf, uint32_t len)
{
    USB_OTG_EPTypeDef *ep;

    ep = &hpcd->IN_ep[ep_addr & 0x7FU];
    /*setup and start the Xfer */
    ep->xfer_buff = pBuf;
    ep->xfer_len = len;
    ep->xfer_count = 0U;
    ep->is_in = 1U;
    ep->num = ep_addr & 0x7FU;

 if (hpcd->Init.dma_enable == 1U)
 {
    ep->dma_addr = (uint32_t)pBuf;
 }

 __HAL_LOCK(hpcd);

 if ((ep_addr & 0x7FU) == 0U)
 {
    USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
 }
 else
 {
    USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
 }

 __HAL_UNLOCK(hpcd);
 return HAL_OK;
}

接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到 main 等用户函数
中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可
能导致资源冲突;

我们进一步查看__HAL_LOCK()宏定义:

 #define __HAL_LOCK(__HANDLE__)                                          \
                       do{                                                                           \
                               if((__HANDLE__)->Lock == HAL_LOCKED)  \
                               {                                                                       \
                                   return HAL_BUSY;                                      \
                               }                                                                       \
                               else                                                                  \
                               {                                                                       \
                                   (__HANDLE__)->Lock = HAL_LOCKED;    \
                               }                                                                        \
                           }while (0U)

若__HAL_LOCK(hpcd);失败则直接返回 return HAL_BUSY 的。为了验证在接收过程中是否
__HAL_LOCK 失败,我们引进全局变量 Lock_Flag,在发送函数中若成功 LOCK 则设置
Lock_Flag=1,UNLOCK 后则复位为 0:

uint8_t Lock_Flag =0;
HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t*pBuf, uint32_t len)
{
    USB_OTG_EPTypeDef *ep;

    ep = &hpcd->IN_ep[ep_addr & 0x7FU];

    /*setup and start the Xfer */
    ep->xfer_buff = pBuf;
    ep->xfer_len = len;
    ep->xfer_count = 0U;
    ep->is_in = 1U;
    ep->num = ep_addr & 0x7FU;

    if (hpcd->Init.dma_enable == 1U)
    {
       ep->dma_addr = (uint32_t)pBuf;
    }

    __HAL_LOCK(hpcd);
   Lock_Flag =1;

    if ((ep_addr & 0x7FU) == 0U)
    {
       USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    }
    else
    {
       USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    }

    __HAL_UNLOCK(hpcd);
    Lock_Flag =0;
    return HAL_OK;
}

接下来在接收函数中对全局变量 Lock_Flag 值进行判断,若为 1 则锁死程序,因为在 Lock_Flag=1 时,
则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回 HAL_BUSY;这里通
过锁死代码以便判断这种情况:

HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t*pBuf, uint32_t len)
{
    USB_OTG_EPTypeDef *ep;

    ep = &hpcd->OUT_ep[ep_addr & 0x7FU];

    /*setup and start the Xfer */
    ep->xfer_buff = pBuf;
    ep->xfer_len = len;
    ep->xfer_count = 0U;
    ep->is_in = 0U;
    ep->num = ep_addr & 0x7FU;

    if (hpcd->Init.dma_enable == 1U)
    {
       ep->dma_addr = (uint32_t)pBuf;
    }

   if(Lock_Flag ==1)
   {
      while(1);
   }
    __HAL_LOCK(hpcd);

    if ((ep_addr & 0x7FU) == 0U)
    {
       USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    }
    else
    {
       USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    }
    __HAL_UNLOCK(hpcd);

    return HAL_OK;
}

通过调试,当出现问题时,程序果然被锁死在这个 while(1)了,这也证明了正是这个互斥锁所致。因此,
我们大体可以判断出现问题时流程大致如下:

1> 在 mian 函数中发送数据 CDC_Transmit_FS()
2> USBD_CDC_TransmitPacket()
3> USBD_LL_Transmit()
4> HAL_PCD_EP_Transmit()
5> __HAL_LOCK(hpcd); 此时成功获取互斥锁
6> 恰好此时有一个接收中断,由于 USB 中断具有优先级,跳转到接收中断内执行;同时,USB 核会
自动关闭 OUT 端点;
7> HAL_PCD_DataOutStageCallback()
8> USBD_CDC_DataOut()
9> CDC_Receive_FS()
10> USBD_CDC_ReceivePacket()
11> USBD_LL_PrepareReceive()
12> HAL_PCD_EP_Receive()
13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在 OUT 端点没有再次打开就已经提前结束,导致接收循环无以为继。

3 解决方案

知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与 USB 接收中断相同的中断等级中去,例如可以利用 USB 的 EOPF 中断,在开启 EOPF 中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF 每 1ms 触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。

此外,其实此问题是出现在 Cube 库的低版本中,例如 CubeF4 V1.5.0 和 CubeF2 V1.3.0 中都存在,但是在最新本的 CubeF4 V1.16.0,CubeF2 V1.6.0 版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。

来源: 21ic.com

围观 2719

一、

STM32的AD转换,可以将转换任务组织为两个组:规则组和注入组。

在任意多个通道上以任意顺序进行的一系列转换构成成组转换。

例如,可以如下顺序完成转换:通道3、通道8、通道2、通道2、通道0、通道2、通道2、通道15。在执行规则通道组扫描转换时,如有例外处理则可启用注入通道组的转换。可以模糊的将注入组的转换理解为AD转换的中断一样,规则通道组的转换是普通转换,然而注入组的转换条件满足的情况下,注入组的转换会打断规则组的转换。

如果规则转换已经在运行,为了在注入转换后确保同步,所有的ADC(主和从)的规则转换被停止,并在注入转换结束时同步恢复。规则转换和注入转换均有外部触发选项,规则通道转换期间有DMA请求产生,而注入转换则无DMA请求,需要用查询或中断的方式保存转换的数据。

二、

规则组:此模式通过设置ADC_CR1寄存器上的DISCEN位激活。它可以用来执行一个短序列的n次转换(n<=8),此转换是ADC_SQRx寄存器所选择的转换序列的一部分。数值n由ADC_CR1寄存器的DISCNUM[2:0]位给出。

一个外部触发信号可以启动ADC_SQRx寄存器中描述的下一轮n次转换,直到此序列所有的转换完成为止。总的序列长度由ADC_SQR1寄存器的L[3:0]定义。

举例: n=3,被转换的通道 = 0、1、2、3、6、7、9、10 第一次触发:转换的序列为 0、1、2 第二次触发:转换的序列为 3、6、7 第三次触发:转换的序列为 9、10,并产生EOC事件 第四次触发:转换的序列 0、1、2,依次类推;

注入组:此模式通过设置ADC_CR1寄存器的JDISCEN位激活。在一个外部触发事件后,该模式按通道顺序逐个转换ADC_JSQR寄存器中选择的序列。

一个外部触发信号可以启动ADC_JSQR寄存器选择的下一个通道序列的转换,直到序列中所有的转换完成为止。总的序列长度由ADC_JSQR寄存器的JL[1:0]位定义。

例子: n=1,被转换的通道 = 1、2、3 第一次触发:通道1被转换 第二次触发:通道2被转换 第三次触发:通道3被转换,并且产生EOC和JEOC事件 第四次触发:通道1被转换,也是依次类推。

三、

STM32的ADC的工作模式:

单次转换模式:转换一次则停止;
连续转换模式:转换完一次后即开始下一次转换;
扫描模式:扫描一组模拟通道;
间断模式:每触发一次,转换序列中n个通道。

四、

因为规则通道转换的值储存在一个仅有的数据寄存器中,所以当转换多个规则通道时需要使用DMA,这可以避免丢失已经存储在ADC_DR寄存器中的数据。只有在规则通道的转换结束时才产生DMA请求,并将转换的数据从ADC_DR寄存器传输到用户指定的目的地址。

注: 只有ADC1和ADC3拥有DMA功能。由ADC2转化的数据可以通过双ADC模式,利用ADC1的DMA功能传输。

五、

双ADC模式

(1)同步注入模式:此模式转换一个注入通道组。外部触发来自ADC1的注入组多路开关(由ADC1_CR2寄存器的JEXTSEL[2:0]选择),它同时给ADC2提供同步触发。

注意: 不要在2个ADC上转换相同的通道(两个ADC在同一个通道上的采样时间不能重叠)。

(2)同步规则模式:此模式在规则通道组上执行。外部触发来自ADC1的规则组多路开关(由ADC1_CR2寄存器的EXTSEL[2:0]选择),它同时给ADC2提供同步触发。

(3)快速交叉模式:此模式只适用于规则通道组(通常为一个通道)。外部触发来自ADC1的规则通道多路开关。

外部触发产生后:
A.ADC2立即启动并且
B.ADC1在延迟7个ADC时钟周期后启动

注意:最大允许采样时间<7个ADCCLK周期,避免ADC1和ADC2转换相同通道时发生两个采样周期的重叠。

(4)慢速交叉模式:此模式只适用于规则通道组(只能为一个通道)。外部触发来自ADC1的规则通道多路开关。

外部触发产生后:
A.ADC2立即启动并且
B.ADC1在延迟14个ADC时钟周期后启动
C.在延迟第二次14个ADC周期后ADC2再次启动,如此循环。

(5)交替触发模式:此模式只适用于注入通道组。

外部触发源来自ADC1的注入通道多路开关。
A.当第一个触发产生时,ADC1上的所有注入组通道被转换。
B.当第二个触发到达时,ADC2上的所有注入组通道被转换。
C.如此循环……

(6)独立模式:此模式里,双ADC同步不工作,每个ADC接口独立工作。

(7)混合的规则/注入同步模式:规则组同步转换可以被中断,以启动注入组的同步转换。

(8)混合的同步规则+交替触发模式:规则组同步转换可以被中断,以启动注入组交替触发转换。 显示了一个规则同步转换被交替触发所中断。

(9)混合同步注入+ 交叉模式:一个注入事件可以中断一个交叉转换。这种情况下,交叉转换被中断,注入转换被启动,在注入序列转换结束时,交叉转换被恢复。

来源: eeworld

围观 548

STM32 防火墙(Firewall)能够构建一个与其它代码隔离的带有数据存储的可信任代码区域,结合 RDP、WRP 以及 PCROP,可用来保护安全敏感的算法。在 STM32 Cube 固件库参考代码里提供了几个不同的防火墙配置。那么问题来了,什么是STM32 防火墙的应该使用的安全配置呢?本文以 STM32 参考手册为基础,以最大化安全为目标,来探索发现 STM32 防火墙的推荐配置。

STM32 防火墙介绍

STM32 防火墙保护特定代码/数据不被保护区域之外的执行所访问。代码和数据位于 Flash 存储器中,也可以位于 SRAM1 中。

可选择配置的防火墙的三个保护段如下:

 代码段(Flash)
 数据段(Flash)
 易失数据段(SRAM1), 可被配置为可执行

防火墙配置激活后,对受保护代码的访问必须唯一的通过调用门(Call gate)进行。防火墙外设监听 AMBA 总线,任何不通过调用门的访问,将导致系统重启。

发现 STM32 防火墙的安全配置
Figure 1 防火墙的连接

不同于 STM32 上的 ARM MPU 技术,防火墙激活后,将一直保持激活状态,直至下次系统复位。

防火墙的调用门

防火墙的调用门由三个字组成,位于 Flash 中的代码段以及配置成非共享且可执行的 SRAM1 数据段,开始地址的前三个 32位。

 第 1 字: 虚设。总是处于关闭状态。用于保护指令预取造成的对调用门的访问。
 第 2 和第 3 字:总是处于打开状态。

为了打开防火墙,代码必须跳到调用门的第 2 字执行,且第 2 字和第 3 字的执行不能被中断,否则会导致系统重启。

防火墙的状态图

防火强的配置激活后,处于关闭(Close)状态。在关闭状态下,对受保护区域的访问将被禁止。跳到防火墙的调用门处执行,则防火墙打开(open)。在防火墙打开状态下,若防火墙控制寄存器(FW_CR)的 Prearm(FPA)位依然为 0,跳转至非保护代码将导致系统重启。将防火墙控制寄存器的 Prearm 位设置成 1,这时任何对非保护代码的访问将导致防火墙进入关闭(close)但激活状态。

发现 STM32 防火墙的安全配置
Figure 2 防火墙的状态转换

STM32 防火墙例程

一个 STM32 防火墙的实例可在 CubeMX 固件包里找到。例如:

STM32Cube\Repository\STM32Cube_FW_L4_V1.6.0\Projects\STM32L476RG-Nucleo\Examples\FIREWALL

工程文件支持 IAR 和 Keil 开发环境,含有两个目录:

FIREWALL_VolatileData_Executable
FIREWALL_VolatileData_Shared

读者可使用 IAR 或者 Keil 打开这两个例子进行编译以及运行。

例程防火墙配置引出的疑问

例程在激活防火墙时使用了不同的配置,演示了防火墙外设的灵活性与不同的安全性,具有很好的学习用途,但是在实际应用中,为最大化安全考虑,我们究竟应该去应用哪一种配置 呢?

 配置一: 仅配置保护 SRAM1 中的数据,且将 SRAM1 的数据配置成可执行且非共享。注意没有对 Flash 的任何地方配置保护。

 /* No protected code segment (length set to 0) */
 fw_init.CodeSegmentStartAddress = 0x0;
 fw_init.CodeSegmentLength = 0;

 /* No protected non-volatile data segment (length set to 0) */
 fw_init.NonVDataSegmentStartAddress = 0x0;
 fw_init.NonVDataSegmentLength = 0;

 /* Protected volatile data segment (in SRAM1 memory) start address and length */
 fw_init.VDataSegmentStartAddress = 0x2000F100;
 fw_init.VDataSegmentLength = 3840; /* 0xF00 bytes */

 /* The protected volatile data segment can be executed */
 fw_init.VolatileDataExecution = FIREWALL_VOLATILEDATA_EXECUTABLE;

 /* The protected volatile data segment is not shared with non-protected
 application code */
 fw_init.VolatileDataShared = FIREWALL_VOLATILEDATA_NOT_SHARED;

 配置二: 保护 Flash 里的代码和数据,以及 SRAM1 中的数据。SRAM1 中的数据被配置为不可执行,但可共享

 /* Protected code segment start address and length */
 fw_init.CodeSegmentStartAddress = 0x08010000;
 fw_init.CodeSegmentLength = 512; /* 0x200 bytes */

 /* Protected non-volatile data segment (in FLASH memory) start address and length */
 fw_init.NonVDataSegmentStartAddress = 0x080FF000;
 fw_init.NonVDataSegmentLength = 256; /* 0x100 bytes */

 /* Protected volatile data segment (in SRAM1 memory) start address and length */
 fw_init.VDataSegmentStartAddress = 0x20000000;
 fw_init.VDataSegmentLength = 576; /* 0x240 bytes */

 /* The protected volatile data segment can't be executed */
 fw_init.VolatileDataExecution = FIREWALL_VOLATILEDATA_NOT_EXECUTABLE;

 /* The protected volatile data segment is shared with non-protected
 application code */
 fw_init.VolatileDataShared = FIREWALL_VOLATILEDATA_SHARED;

防火墙配置分析

SRAM1 的共享 Vs. 非共享

用户手册 RM0351 中提到若一段 SRAM1 被防火墙设置为共享(VDS=1),则该区域总是可被访问,而不用去管防火墙是打开,关闭还是没有激活。 同时,SRAM1 中的数据可被执行, 而不需要激活防火墙更不需要打开防火墙。换言之,共享区域其实是不在防火墙的保护之下。因此,为保护 SRAM1 中的敏感数据,我们应该避免使用共享配置,也就是 VDS 应当是 0。

发现 STM32 防火墙的安全配置
Figure 3 RM0351 中关于防火强的易失数据段配置成可共享

SRAM1 中的执行 Vs. 非执行

用户手册 RM0351 中提到,若 SRAM1 被配置成非共享(VDS=0)且可执行(VDE=1), 防火墙的调用门序列需要先被执行。这里有几层含义,一是这段代码已经处于防火墙的保护之下;二是这段代码可被调用门中的代码调用;三是这段代码可以是调用门(参考调用门的描述)。前面我们提到防火墙打开后,保护区域之内的执行可以访问保护区域之内的代码以及数据。换言之, 这里的 SRAM1 执行的代码是可以访问 Flash 里受防火墙保护的代码和数据。SRAM1 中的代码是可变 的,为避免不确定性,一般情况下我们应当避免 SRAM1 运行的代码访问 受保护的 Flash 代码和数据,也就是建议将 SRAM1 中配置成非执行。

发现 STM32 防火墙的安全配置
Figure 4 RM0351 中关于防火强的易失数据段配置成可执行

是否应该配置 Flash 的数据段(Non-volatile data)

前面的例程配置一仅配置了 SRAM1。然而用户手册 RM0351 中提到,Flash 里的数据段未被定义,则 FW_CR 寄存器可在任意时刻进行修改,而不用管 防火墙是打开还是关闭。

发现 STM32 防火墙的安全配置
Figure 5 RM0351 中关于防火强控制寄存器的动态配置方式的描述

也就是说,防火墙相比较 MPU 的优势“配置有效直到系统复位”,在这种配置下不再存在了。例如在例程“配置一”里,SRAM1 的代码配置成可执行非共享,受防火墙保护。然而在防火墙设置好且处于关闭状态后,我们依然可以通过保护区域之外这样的代码将该保护去掉,例如将它配置后再次改成可执行且共享。

__HAL_FIREWALL_VOLATILEDATA_SHARED_ENABLE();

读者可轻易将该代码加入到 FIREWALL_VolatileData_Executable 进行试验。这显然不是我们想要的安全配置, 我们应该避免这种用法。 所以,我们总是配置防火墙保护 Flash 中的某一数据段,使得 NVDSL 寄存器不为 0。

推荐的防火墙配置

综合前面的例子和分析,为最大化安全考虑,我们推荐使用防火墙 时使用以下典型配置。它配置了三个保护段,Flash 中的代码和数据,SRAM1 中数据;SRAM1 中数据配置成不可共享且不可执行。同时要注意修改编译器链接文件将三块受保护的代码和数据分别放置到相应的下列定义的区域(Region)里。链接文件, 对 IAR 是.icf 文件,对 MDK 是.sct 文件。

 /* Protected code segment start address and length */
 fw_init.CodeSegmentStartAddress = FW_CODE_START_ADDRESS;
 fw_init.CodeSegmentLength = FW_CODE_LENGTH;

 /* Protected non-volatile data segment (in FLASH memory) start address and length */
 fw_init.NonVDataSegmentStartAddress = FW_DATA_START_ADDRESS;
 fw_init.NonVDataSegmentLength = FW_DATA_LENGTH;
 /* Protected volatile data segment (in SRAM1 memory) start address and length */
 fw_init.VDataSegmentStartAddress = FW_VDATA_START_ADDRESS;
 fw_init.VDataSegmentLength = FW_VDATA_LENGTH;
 /* The protected volatile data segment can't be executed */
 fw_init.VolatileDataExecution = FIREWALL_VOLATILEDATA_NOT_EXECUTABLE;

 /* The protected volatile data segment is not shared with non-protected
 application code */
 fw_init.VolatileDataShared = FIREWALL_VOLATILEDATA_NOT_SHARED; 

因为控制寄存器 FW_CR,包括 Prearm 位、共享执行、设置,只能在防火墙打开时才能修改,因此在推荐配置下,典型的调用门代码执行序列应如下:

 清除 Prearm 位 (FPA)
 执行安全算法
 清除所有中间数据
 清除 CPU 寄存器信息
 设置 Prearm 位 (FPA)
 退出保护区域

结论

本文根据 STM32 参考手册, 提出了一个 STM32 防火墙的安全配置,可在实际 STM32 防火墙的案例中直接应用。也可以利用本文加深对 STM32 防火墙功能的理解。

来源: 21ic.com

围观 470

前言

STM32 提供了丰富的音频应用外设,并得益于灵活高效的内部架构,可以支持广泛的音频应用。本文中,在简单介绍音频采集的背景知识后,从应用需求出发,确定麦克风的选用。然后,描述了 STM32 内部 DFSDM (Digital Filter for SigmaDelta Modulator)在 PDM 麦克风采集中应用。最后逐步介绍如何利用 STM32CubeMX 进行 DFSDM 设计开发,实现 PDM麦克风声音采集。

一 背景知识

声音通过声学传感器获取模拟信号,经过模数转换器,转换成二进制码 0 和 1,这些 0 和 1 便构成了音频数字信号。

PDM 麦克风能够实现上述的模拟信号获取,并输出 PDM 信号。PDM(Pulse Density Modulation)脉冲密度调制,利用脉冲密度表示模拟信号强度。

从 PDM 位流中获取数据,还需要经过如下图环节才能获得模拟信号幅度对应的数字量。

利用 DFSDM 开发 PDM 麦克风应用介绍

二 应用需求及 DFSDM 支持分析

在音频应用开发前,需要根据应用需求,对麦克风个数、支持编码类型、采样率及分辨率等进行确定。下面围绕 DFSDM在这些需求方面的支持情况进行分析。

2.1 麦克风数量

同时运行的最大麦克风数量,对于 DFSDM,由 DFSDM 中滤波器数量决定。简单理解,就是一个滤波器对应一个麦克风。注意这种简单等同并不适用于非同时采样的应用场景。

麦克风的数量不直接对应通道数量,如下图。

利用 DFSDM 开发 PDM 麦克风应用介绍

可映射任一 DFSDM 通道单元至滤波器单元。对于通道 CH(y-1),数据线可来源于 DATIN(y-1)引脚,也可来自于DATIN(y),在通道单元中可以选择获取数据的时刻(上升沿或者下降沿)。这样带来的益处是,可利用内部两个通道单元对同一个数据线上数据进行分离并处理获得采样数据。而这个应用,直接满足了双通道数据在同一条数据线上的数据采集场景。

上述描述的应用场景中,数据处理流向如下图所示:

利用 DFSDM 开发 PDM 麦克风应用介绍

注 1:图中 CLK 线硬件设计上不一定需要连接,DFSDM 可在内部关联,实现利用输出时钟作为时钟输入,具体可通过参考手册了解。

2.2 编码类型

DFSDM 支持 PDM、曼彻斯特编码,支持具有类似编码的麦克风器件,具体可通过参考手册了解。

2.3 采样率

DFSDM 通过时钟源、滤波模式、快速模式选择、过采样配置,实现不同采样率的支持。能够支持常见的8k,16k,22k,44k,48k 的采样率,也能够支持特殊应用场合所需的更高采样率,例如 192k, 384k 等。
更多关于 DFSDM 采样率介绍,以及具体计算公式可通过参考手册了解。

2.4 分辨率

DFSDM 具有 24 位数据寄存器,可通过配置实现不同分辨率的支持,有效数据最高支持到 24 位。同时,新的 HAL 库支持全硬件获取 16 位采样数据,不增加 CPU 负载。

DFSDM 分辨率由过采样率,滤波器类型和右移位器决定,更多内容可通过参考手册了解。

利用 DFSDM 开发 PDM 麦克风应用介绍

在不同处理环节数据分辨率情况如下图所示。

利用 DFSDM 开发 PDM 麦克风应用介绍

积分器最大数据输出范围如下表。例如当 FOSR 为 64,IOSR 为 1,采用 Sinc3滤波,在不考虑后续处理环节时,输出范围为±262144,分辨率能够达到 19 位。并可通过右移位器灵活获得需要的有效数据位数。

利用 DFSDM 开发 PDM 麦克风应用介绍

三 前期准备

出于将重心放在 DFSDM 应用介绍,简化其他环节考虑。本文中实现例在 ST 提供的 NUCLEO-L476RG 和 X-NUCLEOCCA02M1板展开。

考虑到利用 DFSDM 实现 PDM 麦克风采集,首先根据 UM1900 对麦克风板 X-NUCLEO-CCA02M1 进行处理,使其支持基于 DFSDM 采集的两路 PDM 麦克风。需要准备软硬件资源如下表。

利用 DFSDM 开发 PDM 麦克风应用介绍

四 实现过程

4.1 应用实现

利用 X-NUCLEO-CCA02M1 板上已有的两路 PDM 麦克风,可实现最多两路麦克风数据采集。

在本例中,先将应用需求定为两路麦克风采集,采样率为 8KHz,分辨率为 16-bit。后续介绍如何利用 STM32CubeMX生成遵循应用需求的工程,以及在获得工程后,如何启动采样,实现麦克风采集的应用。

4.2 开发流程

利用 DFSDM 开发 PDM 麦克风应用介绍

4.3 STM32CubeMX 配置实现

STM32CubeMX 操作流程如下图所示。

利用 DFSDM 开发 PDM 麦克风应用介绍

a. DFSDM 通道选择

根据 X-NUCLEO- CCA02M1 板原理图,可知在将其处理成支持 DFSDM 采集的两路麦克风时,麦克风总线引脚与STM32L476RG 连接情况如下。

利用 DFSDM 开发 PDM 麦克风应用介绍

在 STM32CubeMX 中,选择 Channel1 中“ PDM/SPI Input from ch2 and internal clock ”和 Channel2 中“ PDM/SPI Inputfrom ch2 and internal clock ”,并选择“CKOUT”,如下图所示,PC2、PB14 自动对应与 DFSDM 的 Clock out 和 Data In功能脚。

利用 DFSDM 开发 PDM 麦克风应用介绍

b. 配置通道

切换至“Configuration” 标签页,点击“Control\DFSDM”打开 DFSDM 配置界面。由于选择了通道 1 和通道 2,这里可以对这两个通道进行配置。配置情况如下图。

利用 DFSDM 开发 PDM 麦克风应用介绍

Type:配置数据读取时刻。SPI with rising edge 在时钟的上升沿读取数据;SPI with falling edge 在时钟下降沿读取数据。

Spi clock:总线时钟源。Internal SPI clock 利用内部时钟,对应为 CKOUT 时钟。
Offset:数据偏移量补偿。

Right Bit Shift : 右移位。右移位的确定,涉及到获取有效数据的位数,如“分辨率”小节中图所示。需要结合滤波器和积分器配置及分辨率需求进行确定。本文中,经过滤波器和积分器处理后输出数据分辨率为 29-bit,所以将右移位设置为 5,从而在 24 位数据寄存器中获得有效的 24-bit 数据。

Analog watchdog parameters : DFSDM 中模拟看门狗参数设置。应用例中没涉及到此功能,参数保持默认。

c. 配置滤波器

切换至“Filter0”/ “Filter1” 标签页,配置情况如下图。

利用 DFSDM 开发 PDM 麦克风应用介绍

Regular channel selection: 数据来源。选择与外部麦克风连接的通道。

Continuous mode: 转换模式。选择连续转换模式。

Trigger to start regular conversion: 启动转换的触发源。

Fast mode: 快速模式。在连续转换数据源来自于同一个通道时可启用,能够提高转换速度。本文应用例满足条件,使能Fast mode。

DMA mode : DMA 模式。如果无法使能,需参考后面“配置 DMA”小节,先完成对 DMA 参数的配置。

Inject channel selection: 注入通道选择。应用例中没涉及到此功能,参数保持默认。

Sinc Order: 滤波类型。

Fosr: 滤波器过采样率。

Iosr: 积分器过采样率。

其中,Sinc order, Fosr 和 Iosr 共同决定了积分器处理后的内部数据分辨率。根据“分辨率”小节,在如上配置时,积分器最大输出分辨率为 29 位。

d. 配置时钟

切换至“Output Clock” 标签页,配置情况如下图。

利用 DFSDM 开发 PDM 麦克风应用介绍

Selection: CKOUT 时钟源选择。

Divider: 时钟预分频因子。CKOUT = Audio clock / divider。

其中,Audio clock 来源于 SAI1 时钟,如下图配置,SAI1 时钟配置为 17.411765MHz。CKOUT 输出时钟频率为1.0242MHz。

在 Fast mode 情况下,采样率 Fs 如下:

利用 DFSDM 开发 PDM 麦克风应用介绍

利用 DFSDM 开发 PDM 麦克风应用介绍

e. 配置 DMA

切换至“DMA Settings” 标签页,配置情况如下图。

利用 DFSDM 开发 PDM 麦克风应用介绍

DMA 配置中,选择 Circular 模式,可实现循环向数据 buffer 中填充采样数据。
Data Width 设置为 Half Word,以便实现只获取数据寄存器的高 16 位数据,实现 16-bit 分辨率数据采集。

f. 生成工程

在 STM32CubeMX\Help 打开帮助文档 UM1718,参考文档,生成 IAR 工程。至此获得与硬件对应,支持两路麦克风,采样率为 8KHz,分辨率为 16-bit 的初始化软件工程。

g. 启动采样

完成上述步骤后,即可调用 HAL 库中提供的函数,启动麦克风数据采集。建议利用 DMA 方式实现麦克风采集,占用 CPU 开销最小。下述为启动采样的实现例程。在例程中通过调用 HAL_DFSDM_FilterRegularMsbStart_DMA 启动采样,这个函数实现了利用 DMA 接收 DFSDM 的数据寄存器的高 16 位数据,并传输到分配的 Buffer 空间中。

注:HAL_DFSDM_FilterRegularMsbStart_DMA()的实现需要在 DMA 配置中将 Data Width 设置为 Half Word。

由于 DMA 采用 Circular 模式,采样数据向 buffer 空间的搬运连续进行,而半传输完成和传输完成中断回调函数被执行时,意味新的数据被填充至 Buffer 空间,用户可以在其中增加处理指令,完成利用 DFSDM 的麦克风数据采集应用的开发。

#define SAMPLE_FREQ 8000
#define BYTE_PER_SAMPLE 2
#define MICROPHEN_NUMBER 2
#define FRAME_NUMBER 2
//16bit sample resolution
#define BUF_LENGTH (SAMPLE_FREQ/1000*MICROPHEN_NUMBER*FRAME_NUMBER)
…
/* Buffer 分配 */
int16_t Buf_Mic0[BUF_LENGTH];
int16_t Buf_Mic1[BUF_LENGTH];
…
main()
{
…
/*启动采样,在初始化完成后调用*/
HAL_DFSDM_FilterRegularMsbStart_DMA(&hdfsdm1_filter0,Buf_Mic0,BUF_LENGTH);
HAL_DFSDM_FilterRegularMsbStart_DMA(&hdfsdm1_filter1,Buf_Mic1,BUF_LENGTH);
…
While (1){}
…
}
…
/* I2S DMA 回调函数 */
void HAL_DFSDM_FilterRegConvHalfCpltCallback(DFSDM_Filter_HandleTypeDef *hdfsdm_filter)
{
 //半传输完成,可在此添加对采样数据的处理
}
void HAL_DFSDM_FilterRegConvCpltCallback(DFSDM_Filter_HandleTypeDef *hdfsdm_filter)
{
 //传输完成,可在此添加对采样数据的处理
}

除此之外,还可以调用 HAL_DFSDM_FilterRegularStart_DMA 启动采样,而这种实现获取的整个 32 位的数据寄存器内容,获取数据需要利用软件指令将低 8 位,非采样数据移除。同时这种实现,需要在 DMA 配置中,将 Data Width 配置为 Word。

五 小结

本文虽然尽可能详细的介绍在 PDM 麦克风采集中,DFSDM 作用及实现步骤。但并没有涉及到所有 DFSDM 支持功能的介绍,而是侧重于 DFSDM 在 PDM 麦克风采集中涉及的功能介绍,更多 DFSDM 功能介绍可以通过参考手册了解。另外不同系列 STM32 的 DFSDM 支持情况会略有差异,需以对应型号的参考手册为准。

来源:ST

围观 2176

近日有客户反映,他在在使用STM32F103C8T6的时候遇到如下问题:

I2C1使用PB6和PB7口,定时器TIM3使用PB0\PB1\PB4\PB5做4路PWM。但在使用的过程中,如果只初始化定时器就没有任何问题,但是一旦初始化I2C1,那么定时器的通道2(PB5)就不能产生PWM波,而是保持高电平。

客户查阅手册得知PB5的默认复用功能是I2C1的SMBA引脚,但是它的I2C1是初始化为I2C模式的,并不是初始化为SMBAS模式,而且同样的方式在F0上测试是可用的。它本来用的是标准库开发的,然后尝试使用STM32CubeMx进行硬件配置,使用HAL库新建工程,还是存在同样的问题。

就上面的问题,查看了其有关I2C1和TIM3d的pwm初始化的部分代码,并未发现不对的地方。首先重点怀疑I2C1的配置是否有误,担心客户在配置I2C1时配置成了SMBAS模式。借助于库代码,进一步跟踪下去查看底层的寄存器配置,相关寄存器操作也没有发现问题。

这里TIM3的PWM输出的几个管脚有涉及到重映射【REMAP】,从数据手册的管脚分配上来看,如果不开启I2C1的SMBA模式,不应该存在冲突问题。

基于STM32F1系列的重要宝典RM0008参考手册

这边再次使用STM32CubeMx基于STM32F103C8进行同样配置,结果跟客户上面反馈的一样。不开启I2C1时,TIM3的所有管脚功能正常;开启I2C1后,TIM3的部分管脚PB5功能异常。感觉问题可能出在跟TIM3的remap这个地方。打开基于STM32F1系列的重要宝典---参考手册RM0008,查看核对有关TIM3的管脚复用REMAP功能介绍的地方。

基于STM32F1系列的重要宝典RM0008参考手册

现在客户执行的是TIM3的部分管脚重映射功能【partial remap】,从上面表格来看,目前的代码配置是没有问题的。毕竟目前如果不开启I2C1的话TIM3也没什么异常,所以过来查看这个地方,心里也没怎么期望从这里找出明显错误,倒是期待从附近能否找到些额外的提示或提醒。这不,表格的下方用了小一号文字明确提示:上述REMAP操作仅适用于64脚、100脚和144脚封装的芯片.现在客户用的芯片是STM32F103C8,管脚数为48,换言之,它是不支持TIM3的复用功能脚的REMAP操作的。到此,问题应该说找到原因了。

过不了几天,客户又发邮件过来继续就该问题咨询。他问,既然说48脚芯片STM32F1不支持TIM3的REMAP操作,那为什么做了REMAP操作后,如果不开启I2C1,TIM3的4个脚的PWM功能很正常;或者说即使同时开启了I2C1,PB4的功能还是正常REMAP过来了,只是PB5功能异常。希望我这边给出进一步解释。

站在用户的角度有人会发出类似疑问很正常。其实,既然手册明确规定48脚的STM32F1芯片不支持TIM3功能脚的REMAP,它自有其原因和道理。你违背手册之规定来操作,结果的正确性就不能得到保障。有时REMAP没问题,不代表任何时候进行REMAP没问题。就像讨论某个命题,局部、个别情形成立,并不能说它恒成立。打个形象的比方,A今年10岁,B今年20岁。即B比A大10岁,B今年的年龄是A的2倍。显然,两个结论站在今年都成立,到了明年,后面的2倍论就不成立了。

在ST MCU的应用过程中,还经常出现类似违背手册规定的操作以及由此导致的疑问。比方说,有人发现使用STM32芯片内部的flash时,似乎可以使用到手册规定以外的空间。用户这样使用,芯片的功能或特性是不能得到保障的,作为厂家只能保证芯片手册规定区域的品质。又比方,我们知道ST MCU绝大部分芯片都带有UID,可有些人发些即使手册明确没有UID的芯片,他们似乎发现这类芯片还是有UID甚至加以利用,询问这样是怎么回事或者说是否可靠。同样,对于类似情形作为厂家也只能保证手册规定的特性。超出手册规定以外的应用,只能用户自己负责。

好,继续回到上面的话题。

我们从芯片应用的参考手册上应该说找到了明确的规定或答案。我们还可以查看下基于该芯片有无更为详尽的勘误表。后来在官方网站找到了相应的勘误手册【注:勘误手册往往基于芯片型号,即一个系列可能有多个勘误手册】,我们在勘误手册里也看到关于上面问题的详细描述,可应视为对参考手册的进一步补充。

基于STM32F1系列的重要宝典RM0008参考手册

到此,问题原因基本明了。或许还会有人问,上面提到使用STM32CubeMx进行过工程配置,配置过程并未发现异常,或者说配置过程中没有遇到上面阻碍。既然参考手册规定不允许STM32F103C8芯片的TIM3 remap操作,在开启i2c1时,通过cubeMx配置TIM3的REMAP功能时应该出现非法提示才对啊?

我使用的CUBEMX的版本是4.22.0,在开启I2C1的同时,并按照TIM3的部分REMAP配置时不能说没有给出提醒,只能说提醒得不够明确。该提醒可能容易被人忽视,然后可以一路配置下去。

STM32CubeMx配置如下图,在I2C1那个地方有黄色警示,鼠标放过去的时候是有文字提示的【不一定每个人会留意到】:

基于STM32F1系列的重要宝典RM0008参考手册

可以说CubeMx还是有不够严谨或者说考虑不周的地方。如果在开启I2C1情况下,当用户试图配置PB5作为输出时直接红色警告拒绝TIM3的remap就好了。但这样,可能又会影响到另外一类用户人群,他们根本不在乎PB5怎样,只关注PB4能用作PWM输出就好。有点众口难调的味道,参考手册在明确不支持STM32F1系列48脚的TIM3的REMAP操作的同时,结合勘误手册做了应用补充,以尽可能满足不同的应用需求。

毕竟STM32CubeMX工程浩大,肯定还有需要完善的地方,尤其类似的细节问题。不过,我们相信会越来越完善。不管怎样,所以,任何时候我们不能完全将芯片手册丢在一边。比如,我们知道ST官方出了基于各个STM32系列的固件库,库里各类示例工程极大方便了大家的学习和研发。不难想象,这些固件库工程也都比较庞大,难免会有bug,一直都处于不断完善中。在使用它们的过程中如果碰到疑惑的地方,不妨查看下相关数据手册或开发参考手册,做进一步比对确认。如果觉得手册还描述得不够清晰明确的话,可以去找找相应芯片的勘误手册,看看里面有无相关问题的进一步补充描述。

唠叨一堆,抛砖引玉。

转自: STM32单片机

围观 489

前言

某客户在调试 STM32L053 的比较器 1 时,使用内部 1.2V 的参考电压,没有问题.但当使用比较器 2 时,使用同样的设置,却发现比较电压无法调到 1.2V,只能设置到 0.6V 左右,到时是什么问题呢?

问题解决

问题调试

首先得到这个问题,我们先比较一下两种现象之间的设置问题,发现比较器 1 和比较器 2 的设置都是一样的.然后我们通过修改比较器 2 的内部比较电压查看现象,发现就算我们设置为二分之一的内部参考电压(二分之一的 1.2V),触发门限依旧是 0.6V.即使使用 cubeMx 重新生成代码,现象也没有得到解决.

解决方法

然后我们开始查看参考手册,我们猜想,两个比较器之间是否有不一样的设置,以为比较器 2 更为高级,有更多的设置.排除功耗和速度的不一样设置外.我们通过搜寻 comp2 发现其在 Reference control and status register (SYSCFG_CFGR3)里面有个不一样的设置, Bit 0 EN_VREFINT.具体描述如下 :

STM32L053 comp2 比较电压无效问题

如描述,我们在某些模式下,必须设置这一位.所以我们做以下修改.在使能比较器 2 之前先设置这一位.

SET_BIT (SYSCFG- > CFGR3, 1);
HAL_COMP_Start(&hcomp2);

在这修改后,比较器 2 的比较电压可以得到正常的电压值,如 1.2V.

总结

在同一个类型外设中,如果两个外设同样的设置,却得不到同样的效果.我们可以通过查询参考手册对于这两个外设的不同描述,根据不同的描述,找出与现象之间有相关的差异,进行修改与调试.往往可以得到比较好的效果,或者提示.

来源: http://stmcu.com.cn/

围观 273

前言

STM32 提供了灵活的固件加载模式,其中大部分型号支持 DFU 加载。并且在电脑端,提供了配套的演示软件 DfuSe。

包含可视化版 DfuSeDemo.exe 和命令版 DfuSeCommand.exe。本文主要介绍 DfuSeCommand.exe 的使用。

前期步骤

1. 在电脑上安装 DfuSe(可通过“相关工具 & 链接”小节中,提供的链接获取安装包)。

2. STM32 硬件正确配置,使其能够满足进入 System Bootloader 模式。更多详细介绍请参考《AN2606 STM32 microcontroller system memory boot mode》。如果应用中,利用 IAP 实现 DFU 升级,同样可以利用 DfuSe 工具。

3. 利用 USB 数据线连接电脑和 STM32 的全速 USB 接口,等待驱动自动安装完成。如果驱动无法正确安装,可指定驱动路径,重新安装驱动。驱动位于 DfuSe 安装目录\ Bin\Driver。

4. 打开 DfuSe 安装目录下的 DfuSeCommand.exe。

5. 结合下节,使用 DfuSeCommand。

在使用 DfuSeCommand 前需要完成所列步骤,以便提供运行所需的硬件和软件环境。更多关于 DfuSe 以及固件转换等介绍,可以参考《UM0412 Getting started with DfuSe USB device firmware upgrade STMicroelectronics extension》。

DfuSeCommand 命令使用介绍

DfuSecommand 提供了几个命令,分别用于实现帮助、DFU 设备连接、加载、上传功能。
为演示需要,执行上节列出的步骤,并连接两个 DFU 设备(由 STM32 的 System Bootloader 实现 DFU 功能)到电脑。

输入红框中命令,执行帮助命令,列出了DfuSecommand.exe 支持的全部命令。
注:执行命令中对应的位置信息需要与 DfuSe 安装位置一致。

STM32单片机DfuSeCommand的使用

上传命令。命令实现:连接设备 0,上传固件并保存。

上传命令。命令实现:连接设备 1,上传固件并保存。

加载命令。命令实现:连接设备 0,加载固件到设备 0.

加载命令。命令实现:连接设备 1,加载固件到设备 1.

加载命令。命令实现:连接设备 0 (默认),加载固件到设备0.

小结

本文所列出的 DfuSeCommand 命令使用,并未涵盖全部参数情况。使用者可以参考列出部分的命令实
现,并结合 Help 下列出的全部命令参数,执行需要的命令。

DfuSeCommand.exe 相对于可视化版 DfuSeDemo.exe 功能更加精简,使用者如果只是为了验证 DFU
功能,建议使用可视化版 DfuSeDemo.exe,操作方便,并且具有更多的功能。

来源: 21ic.com/stm32/

围观 679

STM32简单介绍

一、背景

如果你正为项目的处理器而进行艰难的选择:一方面抱怨16位单片机有限的指令和性能,另一方面又抱怨32位处理器的高成本和高功耗,那么,基于 ARM Cortex-M3内核的STM32系列处理器也许能帮你解决这个问题。使你不必在性能、成本、功耗等因素之间做出取舍和折衷。即使你还没有看完STM32的产品手册,但对于这样一款融合ARM和ST技术的“新生儿”相信你和我一样不会担心这款针对16位MCU应用领域 的32位处理器的性能,但是从工程的角度来讲,除了芯片本身的性能和成本之外,你或许还会考虑到开发工具的成本和广泛度;存储器的种类、规模、性能和容 量;以及各种软件获得的难易,我相信你看完本专题会得到一个满意的答案。

对于在16位MCU领域用惯专用在线仿真器(ICE)的工程师可能会担心开发工具是否能够很快的上手?开发复杂度和整体成本会不会增加?产品上 市时间会不会延长?没错,对于32位嵌入式处理器来说,随着时钟频率越来越高,加上复杂的封装形式,ICE已越来越难胜任开发工具的工作,所以在32位嵌 入式系统开发中多是采用JTAG仿真器而不是你熟悉的ICE。但是STM32采用串行单线调试和JTAG,通过JTAG调试器你可以直接从CPU获取调试 信息,从而将使你的产品设计大大简化,而且开发工具的整体价格要低于ICE,何乐而不为?

有意思的是STM32系列芯片上印有一个蝴蝶图像,据ST微控制器产品部Daniel COLONNA先生说,这是代表自由度,意在给工程师一个充分的创意空间。我则“曲解”为预示着一种蝴蝶效应,这种蝴蝶效应不仅会对方案提供商以及终端产 品供应商带来举足轻重的影响,而且会引起竞争对手策略的改变……翅膀已煽动,让我们一起静观其变!

二、STM32市面上流通的型号

截至2010年7月1日,市面流通的型号有:
基本型:STM32F101R6,STM32F101C8,STM32F101R8,STM32F101V8 ,STM32F101RB,STM32F101VB增强型:STM32F103C8STM32F103R8,STM32F103V8,STM32F103RB,STM32F103VB,STM32F103VE,STM32F103ZE

三、STM32系列的作用

ARM公司的高性能”Cortex-M3”内核
1.25DMips/MHz,而ARM7TDMI只有0.95DMips/MHz

一流的外设

1μs的双12位ADC,4兆位/秒的UART,18兆位/秒的SPI,18MHz的I/O翻转速度低功耗在72MHz时消耗36mA(所有外设处于工作状态),待机时下降到2μA

最大的集成度复位电路、低电压检测、调压器、精确的RC振荡器等简单的结构和易用的工具。

四、STM32F10x重要参数

2V-3.6V供电
容忍5V的I/O管脚
优异的安全时钟模式
带唤醒功能的低功耗模式
内部RC振荡器
内嵌复位电路
工作温度范围:-40℃至+85℃或105℃

五、性能特点

基本型STM32F101:36MHz CPU,多达16K字节SRAM,1x12位ADC温度传感器增强型STM32F103:72MHz CPU,多达20K字节SRAM,2x12位ADC 温度传感,PWM定时器,CAN,USB

六、STM32互联型系列简介:

全新STM32互连型(Connectivity)系列微控制器增加一个全速USB(OTG)接口,使终端产品在连接另一个USB设备时既可以 充当USB主机又可充当USB从机;还增加一个硬件支持IEEE1588精确时间协议(PTP)的以太网接口,用硬件实现这个协议可降低CPU开销,提高 实时应用和联网设备同步通信的响应速度。全新互连型系列还是STM32家族中首款集成两个CAN2.0B控制器的产品,让开发人员能够研制可连接两条工业标准CAN(控制器区域网)总 线的网关设备。

此外,新系列微控制器还支持以太网、USB OTG和CAN2.0B外设接口同时工作,因此,开发人员只需一颗芯片就能设计整合所有这些外设接口的网关设备。

STM32互连型系列产品强化了音频性能,采用一个先进的锁相环机制,实现音频级别的I2S通信。结合USB主机或从机功能,STM32可以从外部存储器(U盘或MP3播放器)读取、解码和输出音频信号。设计人员还可以在新系列微控制器上开发人机界面(HMI)功能,如播放和停止按键,以及显示 器界面。这个功能使其可用于各种家庭音响设备,如音响底座系统、闹钟/音乐播放器和家庭影院。

新系列产品整合先进的面向连接的外设,标准的STM32外设(包括一个PWM定时器),高性能的32位ARM Cortex-M3 CPU,这些特性使开发人员可以在设备上(如家电、楼宇或工业自动化)整合多种功能,如马达控制、用户界面控制和设备互连功能。其它目标应用包括需要联网、数据记录或USB外设扩展功能的系统,如病患监视、销售终端机、自动售货机和保安系统。包括新的互连型系列在内的STM32系列微控制器具有多种配套软件和开发工具,其中包括意法半导体免费提供的软件库以及第三方工具厂商的广泛支持。

意法半导体还将推出一个新的评估板,目前正在向大客户提供STM32F105和STM32F107互连型系列的样片。

七、STM32新系列产品的功能:

STM32互连型系列产品分为两个型号:STM32F105和STM32F107。STM32F105具有USB OTG 和CAN2.0B接口。STM32F107在USB OTG 和CAN2.0B接口基础上增加了以太网10/100 MAC模块 。片上集成的以太网MAC支持MII和RMII,因此,实现一个完整的以太网收发器只需一个外部PHY芯片。只使用一个25MHz晶振即可给整个微控制器 提供时钟频率,包括以太网和USB OTG外设接口。微控制器还能产生一个25MHz或50MHz的时钟输出,驱动外部以太网PHY层芯片,从而为客户节省了一个附加晶振。

音频功能方面,新系列微控制器提供两个I2S音频接口,支持主机和从机两种模式,既用作输入又可用作输出,分辨率为16位或32位。音频采样频 率从8kHz到96kHz。利用新系列微控制器强大的处理性能,开发人员可以用软件实现音频编解码器,从而消除了对外部组件的需求。把U盘插入微控制器的USB OTG接口,可以现场升级软件;也可以通过以太网下载代码进行软件升级。这个功能可简化大型系统网络(如远程控制器或销售终端设备)的管理和维护工作。

八、充分发挥 STM32架构的优势:

除新增的功能强化型外设接口外,STM32互连系列还提供与其它STM32微控制器相同的标准接口,这种外设共用性提升了整个产品家族的应用灵 活性,使开发人员可以在多个设计中重复使用同一个软件。新STM32的标准外设包括10个定时器、两个12位1-Msample/s 模数转换器 (交错模式下2-Msample/s)、两个12位数模转换器、两个I2C接口、五个USART接口和三个SPI端口。新产品外设共有12条DMA通道, 还有一个CRC计算单元,像其它STM32微控制器一样,支持96位唯一标识码。新系列微控制器还沿续了STM32产品家族的低电压和节能两大优点。2.0V到3.6V的工作电压范围兼容主流的电池技术,如锂电池和镍氢电 池,封装还设有一个电池工作模式专用引脚Vbat。以72MHz频率从闪存执行代码,仅消耗 27mA电流。低功耗模式共有四种,可将电流消耗降至两微安。从低功耗模式快速启动也同样节省电能;启动电路使用STM32内部生成的8MHz信号,将微 控制器从停止模式唤醒用时小于6微秒。

九、存储器和封装选项:

在STM32F105和STM32F107互连型系列微控制器之前,意法半导体已经推出STM32基本型系列、增强型系列、USB基本型系列和 增强型系列;新系列产品沿用增强型系列的72MHz处理频率。内存包括64KB到256KB闪存和 20KB到64KB嵌入式SRAM。新系列采用LQFP64、LQFP100和LFBGA100三种封装,不同的封装保持引脚排列一致性,结合STM32 平台的设计理念,开发人员通过选择产品可重新优化功能、存储器、性能和引脚数量,以最小的硬件变化来满足个性化的应用需求。

如何保证ADC精度之STM32的ADC

共有最多3个ADC模块,最多21个ADC输入通道

特性

12位分辨率
自校准
转换结束,注入转换结束和发生模拟看门狗事件时产生中断
带内嵌数据一致的数据对齐
非常丰富的操作模式
双重模式(带2个或以上ADC的器件)
ADC转换时间:
1μs:ADC时钟为14MHz时达到最快
14个时钟周期,转换周期可调:14、20、26、41、54、68、84、252
ADC供电要求:2.4V~3.6V
ADC输入范围:VREF-≤VIN≤VREF+
规则通道转换期间有DMA请求产生
模拟看门狗
ADC输入通道映射
STM32的双ADC操作模式

ADC的误差种类
(1) 理想ADC转换曲线
(2) 实际ADC转换曲线
(3) 实际ADC两终点连线

ET 总误差:实际ADC转换曲线与理想曲线间的最 大偏离
EO 偏移误差:实际转换曲 线上第一次跃迁与理想 曲线中第一次跃迁之差
EG 增益误差:实际转换曲 线上最后一次跃迁与理 想曲线中最后一次跃迁 之差
ED 微分线性误差:实际转 换曲线上步距与理想步 距(1LSB)之差
EL 积分线性误差:实际转 换曲线与终点曲线间最大偏离

消除影响ADC精度的因素

1、ADC模块自身的误差

积分线性误差(ILE)和微分线性误差(DLE)依赖于ADC模块的设计,校准它们是困难的。进行多次转换再做平均可以减小它们的影响。偏移和增益误差可以简单地使用ADC模块的自校准功能补偿。

2、电源噪声,尤其是开关电源(SMPS)的高频噪声

线性稳压器具有较好的输出。强烈建议在整流输出端连接滤波电容。如果使用开关型电源,建议使用一个线性稳压器为模拟部分供电。建议在电源线和地 线之间连接具有良好高频特性的电容,即在靠近电源一端应放置一个0.1μF和一个1~10μF的电容。每一对VDD和VSS管脚都需要使用单独的去藕电 容。VDDA管脚必须连接到2个外部的去藕电容器(10nF瓷介电容+1μF的钽电容或瓷介电容)对于100脚和144脚封装的产品,可以在VREF+上 连接一个外部的ADC的参考输入电压,从而改善对输入低电压的精度。

1、电源输出不稳,随负载变化

ADC模块使用VREF+或VDDA作为模拟参考,数字数值的输出是这个参考电压与模拟输入信号的比值,VREF+必须在各种负载情况下保持稳定。可以使用诸如LM236作为VREF+的参考电压,这是一个2.5V的电压参考二极管

2、模拟输入信号的噪声

平均值方法:适合处理不频繁变化的模拟输入信号,增加一个外部滤波器消除高频噪声。

3、将最大的信号幅度与ADC动态范围匹配

选择参考电压(仅适合于具有VREF+引脚的产品),使用一个外部的前级放大器。

4、I/O引脚间的串扰(临近数字信号的翻转)

模拟信号线的周围布置地线产生屏蔽,能有效地减小串扰干扰噪声。
消除影响ADC精度的因素
VDD与VDDA的处理

供电引脚

STM32共有7种封装规格,共有多组VDD/VSS引脚,以及一组VDDA/VSSA引脚。
尽管所有VDD和所有VSS在内部相连,在芯片外部仍然需要连接上所有的VDD和VSS。因为导线较细,内部连接负载能力较差,抗干扰的能力也较差,如果漏接VDD或VSS,容易造成内部线路损坏,同时抗干扰能力下降。

VDD与VSS的去藕电容

每对VDD与VSS都必须在尽可能靠近芯片处分别放置一个10nF~100nF的高频瓷介电容。在靠近VDD3和VSS3的地方放置一个4.7μF~10μF的钽电容或瓷介电容。
VDD与VDDA的关系

VDDA为所有的模拟电路部分供电,包括:
ADC模块,复位电路,PVD(可编程电压监测器),PLL,上电复位(POR)和掉电复位(PDR)模块,控制VBAT切换的开关等。即使不 使用ADC功能,也需要连接VDDA,强烈建议VDD和VDDA使用同一个电源供电。VDD与VDDA之间的电压差不能超过300mV,VDD与VDDA 应该同时上电或调电。

供电方案

如何达到最优功耗水准

低功耗模式

I/O引脚的处理

1、如果需要减小I/O端口的电流消耗,可以根据具体情况配置I/O端口的状态:
输入端口配置为浮空输入,带外部上拉的输出端口配置为推挽输出并输出’1’,带外部下拉的输出端口配置为推挽输出并输出’0’。

2、未用的内部外设:
保持为关闭和默认的复位状态:
不要进行重映射,复位寄存器RCC_APB1RSTR和RCC_APB2RSTR。关闭对应的时钟,时钟使能寄存器:RCC_AHBENR、RCC_APB2ENR和RCC_APB1ENR。

进入SLEEP模式的省电操作

1、为了降低系统功耗,进入SLEEP模式时,执行如下操作流程:
关闭无需等待中断或事件的外设时钟;设置进入机制(Sleep-Now或Sleep-on-Exit);设置系统进入SLEEP模式。

2、退出睡眠模式的方式:
WFI(等待中断),可由任一外设中断触发,WFE(等待事件),可由任一外设事件触发。

进入STOP省电模式的操作

为了降低系统功耗,进入STOP模式的操作流程:

关闭设置为普通IO功能的GPIO口时钟;
关闭已开启时钟的外设的使能位(尤其是ADC、DAC、USB等带模拟模块的外设);
关闭已开启时钟的外设的时钟;
关闭预取缓冲区,并将Flash等待周期置为0;
设置PWR_CR中LPDS位选择电压调节器的模式:
正常模式:电压调节器处于正常供电状态;
低功耗模式:可降低电压调节器自身的功耗,
将MCU从STOP模式唤醒的时间有所增加;
设置系统进入STOP模式。

退出STOP省电模式的操作

1、退出停止模式:

以WFI进入时:任意外部中断线的中断;
以WFE进入时:任意外部中断线的事件;
不包括PVD和USB唤醒事件。

2、从STOP模式恢复后,时钟的配置返回到复位时的状态(系统时钟为HSI),用户程序必须重新配置整个时钟系统,包括PLL。

1、ADC模块自身的误差

积分线性误差(ILE)和微分线性误差(DLE)依赖于ADC模块的设计,校准它们是困难的。进行多次转换再做平均可以减小它们的影响。偏移和增益误差可以简单地使用ADC模块的自校准功能补偿。

2、电源噪声,尤其是开关电源(SMPS)的高频噪声

线性稳压器具有较好的输出。强烈建议在整流输出端连接滤波电容。如果使用开关型电源,建议使用一个线性稳压器为模拟部分供电。建议在电源线和地 线之间连接具有良好高频特性的电容,即在靠近电源一端应放置一个0.1μF和一个1~10μF的电容。每一对VDD和VSS管脚都需要使用单独的去藕电容。

VDDA管脚必须连接到2个外部的去藕电容器(10nF瓷介电容+1μF的钽电容或瓷介电容)对于100脚和144脚封装的产品,可以在VREF+上 连接一个外部的ADC的参考输入电压,从而改善对输入低电压的精度。

消除影响ADC精度的因素

1、电源输出不稳,随负载变化

ADC模块使用VREF+或VDDA作为模拟参考,数字数值的输出是这个参考电压与模拟输入信号的比值,VREF+必须在各种负载情况下保持稳定。可以使用诸如LM236作为VREF+的参考电压,这是一个2.5V的电压参考二极管。

2、模拟输入信号的噪声

平均值方法:适合处理不频繁变化的模拟输入信号,增加一个外部滤波器消除高频噪声。
3、将最大的信号幅度与ADC动态范围匹配

选择参考电压(仅适合于具有VREF+引脚的产品),使用一个外部的前级放大器。

4、I/O引脚间的串扰(临近数字信号的翻转)模拟信号线的周围布置地线产生屏蔽,能有效地减小串扰干扰噪声。消除影响ADC精度的因素

VDD与VDDA的处理

供电引脚

STM32共有7种封装规格,共有多组VDD/VSS引脚,以及一组VDDA/VSSA引脚。

尽管所有VDD和所有VSS在内部相连,在芯片外部仍然需要连接上所有的VDD和VSS。因为导线较细,内部连接负载能力较差,抗干扰的能力也较差,如果漏接VDD或VSS,容易造成内部线路损坏,同时抗干扰能力下降。

VDD与VSS的去藕电容

每对VDD与VSS都必须在尽可能靠近芯片处分别放置一个10nF~100nF的高频瓷介电容。在靠近VDD3和VSS3的地方放置一个4.7μF~10μF的钽电容或瓷介电容。

VDD与VDDA的关系

VDDA为所有的模拟电路部分供电,包括:

ADC模块,复位电路,PVD(可编程电压监测器),PLL,上电复位(POR)和掉电复位(PDR)模块,控制VBAT切换的开关等。即使不 使用ADC功能,也需要连接VDDA,强烈建议VDD和VDDA使用同一个电源供电。VDD与VDDA之间的电压差不能超过300mV,VDD与VDDA 应该同时上电或调电。

1、如果需要减小I/O端口的电流消耗,可以根据具体情况配置I/O端口的状态:

输入端口配置为浮空输入,带外部上拉的输出端口配置为推挽输出并输出’1’,带外部下拉的输出端口配置为推挽输出并输出’0’。

2、未用的内部外设:

保持为关闭和默认的复位状态:

不要进行重映射,复位寄存器RCC_APB1RSTR和RCC_APB2RSTR。关闭对应的时钟,时钟使能寄存器:RCC_AHBENR、RCC_APB2ENR和RCC_APB1ENR。

进入SLEEP模式的省电操作

1、为了降低系统功耗,进入SLEEP模式时,执行如下操作流程:

关闭无需等待中断或事件的外设时钟;设置进入机制(Sleep-Now或Sleep-on-Exit);设置系统进入SLEEP模式。

2、退出睡眠模式的方式:

WFI(等待中断),可由任一外设中断触发,WFE(等待事件),可由任一外设事件触发。

进入STOP省电模式的操作

为了降低系统功耗,进入STOP模式的操作流程:

关闭设置为普通IO功能的GPIO口时钟;
关闭已开启时钟的外设的使能位(尤其是ADC、DAC、USB等带模拟模块的外设);
关闭已开启时钟的外设的时钟;
关闭预取缓冲区,并将Flash等待周期置为0;
设置PWR_CR中LPDS位选择电压调节器的模式:
正常模式:电压调节器处于正常供电状态;
低功耗模式:可降低电压调节器自身的功耗,
将MCU从STOP模式唤醒的时间有所增加;
设置系统进入STOP模式。

退出STOP省电模式的操作

1、退出停止模式:

以WFI进入时:任意外部中断线的中断;
以WFE进入时:任意外部中断线的事件;
不包括PVD和USB唤醒事件。

2、从STOP模式恢复后,时钟的配置返回到复位时的状态(系统时钟为HSI),用户程序必须重新配置整个时钟系统,包括PLL。

转自: 开源嵌入式

围观 410

一、原理

1、红外发射协议

红外通信的协议有很多种。这个实验使用的是NEC协议。这个协议采用PWM的方法进行调制,利用脉冲宽度来表示 0 和 1 。

NEC 遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性。因此,每帧的数据为 32 位,包括地址码,地址反码,控制码,控制反码。反码可用于解码时进行校验比对。

NEC码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要 2.25ms(560us 脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。

红外数据的波形如下图:包括一个同步头和 32 帧数据。

STM32 定时器实现红外遥控数据接收

下图可看出,同步头为 9ms 低电平加上 4.5ms 高电平,控制码为 8 个 0,控制反码为 8 个 1。

STM32 定时器实现红外遥控数据接收

2、定时器计数

定时器就是按照一个特定的频率对计数值进行加一或减一操作,当数值溢出时则产生一个标志或中断。这里是用定时器计数产生一个周期性的中断。

3、实现方法

利用定时器记录两个下降沿之间的时间,通过该时间判断是否是同步头信息、数据 1 或者数据 0。当检测到同步头,开始记录 32 个数据的时间值。

STM32 定时器实现红外遥控数据接收

二、实现

1、配置 GPIO 口下降沿触发中断

示例代码中使用 PA7 管脚,配置为上拉输入模式。

选择下降沿触发,是因为红外接收管默认情况下保持高电平,接收到数据时从高电平转变为低电平。

中断源选择为 EXTI_Line7 ,在库函数中对该中断源定义的服务函数为 EXTI9_5_IRQHandler(),也就是说外部中断 5 到 9 是 共用一个中断服务函数的。

配置代码如下:

void IR_Pin_init()
{
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    EXTI_ClearITPendingBit(EXTI_Line7);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7); 
    EXTI_InitStructure.EXTI_Line=EXTI_Line7;
    EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd=ENABLE;
    EXTI_Init(&EXTI_InitStructure); 
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 
    NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; 
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 	 
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
    NVIC_Init(&NVIC_InitStructure);
}

2、配置定时器计数值

定时器使用的是 TIM2 通用定时器,模式为向上计数。在该模式中,计数器从 0 计数到自动加载值 (TIMx_ARR计数器的内容) ,然后重新从 0 开始计数并且产生一个计数器溢出事件。

示例函数接收两个参数,分别为预分频器的值和自动加载值。通过调整这两个参数,可以灵活地改变定时器的计数周期。例如在 TIM2 的默认时钟源 PCLK1 为96MHz时,使用语句 Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1); //0.1ms 进行初始化,可以每 0.1ms 产生一次中断。

示例代码如下:

void Tim2_UPCount_Init(u16 Prescaler,u16 Period)
{
    TIM_TimeBaseInitTypeDef TIM_StructInit;
    NVIC_InitTypeDef NVIC_StructInit;
	
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
		
    TIM_StructInit.TIM_Period=Period;
    TIM_StructInit.TIM_Prescaler=Prescaler;
    TIM_StructInit.TIM_ClockDivision=TIM_CKD_DIV1;
    TIM_StructInit.TIM_CounterMode=TIM_CounterMode_Up;
    TIM_StructInit.TIM_RepetitionCounter=0;
    TIM_TimeBaseInit(TIM2, &TIM_StructInit);
	
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM2, ENABLE);	
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	
    NVIC_StructInit.NVIC_IRQChannel=TIM2_IRQn;
    NVIC_StructInit.NVIC_IRQChannelCmd=ENABLE;
    NVIC_StructInit.NVIC_IRQChannelPreemptionPriority=0;
    NVIC_StructInit.NVIC_IRQChannelSubPriority=1;
    NVIC_Init(&NVIC_StructInit);
}

3、定时器中断函数统计时间

如上所说,定时器每 0.1ms 计数完成,产生中断,在中断函数中对标志位 ucTim2Flag 加 1,意味着时间过去了 0.1ms。

时间标志位原型为 uint16_t ucTim2Flag; 。

示例代码如下:

void TIM2_IRQHandler(void)
{
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    ucTim2Flag++;
}

4、GPIO 中断函数中接收 32 位数据

在下降沿触发的 IO 口中断函数中,需要实现统计两个下降沿之间的时间,并将其记录在数组中。

下降沿第一次触发时,清除当前定时器中的计数值,以便统计时间。之后每一次下降沿触发就记录下当前计数值,然后再对其清零。如果该时间在同步头的时间区间内,对索引进行清零,表示重新开始接收数据。

完整接收同步头和 32 个数据之后,表示接收完成。

示例代码如下:

uint8_t irdata[33];   //用于记录两个下降沿之间的时间
bool receiveComplete; //接收完成标志位
uint8_t idx;          //用于索引接收到的数值
bool startflag;       //表示开始接收
void EXTI9_5_IRQHandler(void)
{
    uint16_t ir_time;
    if(startflag)
    {
        ir_time = ucTim2Flag;	
        if(ucTim2Flag < 150 && ucTim2Flag >= 50 ) // 接收到同步头
        {
            idx=0;  // 数组下标清零
        }
		
        irdata[idx] = ucTim2Flag;  // 获取计数时间
        ucTim2Flag = 0;            // 清零计数时间,以便下次统计
        idx++;                     // 接收到一个数据,索引加1
		
        if(idx==33)       // 如果接收到33个数据,包括32位数和以一个同步头
        {
            idx=0;
            ucTim2Flag = 0;
            receiveComplete = TRUE;
        }
    }
    else   // 下降沿第一次触发
    {
        idx = 0;
        ucTim2Flag = 0;
        startflag = TRUE;
    }
	
    EXTI_ClearITPendingBit(EXTI_Line7);  // 清除中断标志
}

5、判断控制码值

由于中断函数中接收并记录下的数据是两个下降沿之间的时间,并不是红外所发送的数据。因此需要根据红外协议,对 32 个时间进行判断,从而获得红外真正发送的数据。

下面这个函数需要在红外完整接收数据后执行,可通过判断接收完成标志位 receiveComplete 来实现。

示例代码如下:

uint8_t Ir_Server()
{
    uint8_t i,j,idx=1; //idx 从1 开始表示对同步头的时间不处理
    uint8_t temp;
    for(i=0; i < 4; i++)
    {
        for(j=0; j < 8; j++)
        {
            if(irdata[idx] > =8 && irdata[idx] < 15)   //表示 0
            {
                temp = 0;
            }
            else if(irdata[idx] > =18 && irdata[idx] < 25) //表示 1
            {
                temp = 1;
            }
            remote_code[i] << = 1;
            remote_code[i] |= temp;
            idx++;
        }
    }
	
    return remote_code[2];  // 该数组中记录的是控制码,每个按键不一样
    //for(idx=0; idx < 4; idx++)
    //{
    //	printf("remote_code[%d] = %#x\n",idx,remote_code[idx]);
    //}
}

6、主函数

在 main 函数中,对 IO 口和 定时器进行初始化。

主循环中,通过判断接收完成标志位,对接收完成的按键控制码进行打印。

示例代码如下:

void main()
{
    ...
	
    IR_Pin_init();
    Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1);
 	
    while(1)
    {
        if(repeatEnable)
        {
            repeatEnable = FALSE;
            Ir_Server();
            printf("key_code = %#x\n",remote_code[2]);
        }
    }
  
}

三、演示

如下图为串口打印出接收的红外按键值信息:

STM32 定时器实现红外遥控数据接收

说明1:这只是实现红外接收的其中一种方法,网上还有一种比较常见的方法是利用下降沿触发,在中断中进行延迟,判断高电平持续时间以此来判断信号类别。个人感觉这不是一种很好的方法,因为在中断中进行延时会导致主函数得不到及时的处理。

说明2:在调试时,不要在中断处理中加入过多无关语句,例如打印语句,这会导致结果出错。

本文档基于 STM32 F1 系列 MCU,固件库版本 3.5。其他 MCU 及固件库仅需要对库函数略作修改。

作者: cyang
转自:
cyang's blog

围观 547

页面

订阅 RSS - STM32