如何使用 STM32 中的寄存器配置 SWD 引脚?

Posted

技术标签:

【中文标题】如何使用 STM32 中的寄存器配置 SWD 引脚?【英文标题】:How to configure SWD pins using registers in STM32? 【发布时间】:2020-09-19 12:17:30 【问题描述】:

信息:我使用 STM32 中的寄存器创建 Blink。这有效,除了固件后 SWD 连接器停止为我工作。最可能的原因是 SWD 与 LED 位于同一端口上。在配置端口和 LED 引脚时,我可能会重置 SWD 设置。

代码:

#include "main.h"

void SystemClock_Config(void);

int main(void)

  HAL_Init();
  SystemClock_Config();

  __HAL_RCC_GPIOA_CLK_ENABLE();
  // Set PA8 to OUTPUT mode
  GPIOA->MODER = 0x00010000;
  // Set PUSH-PULL mode
  GPIOA->OTYPER = 0x00000000;
  // Set pin speed
  GPIOA->OSPEEDR = 0x64010000;
  // Set not pull
  GPIOA->PUPDR = 0x64000000;
  // Set pin bit
  GPIOA->BSRR = 0x00000100;

  static int pin_state = 0;

  while (1)
  
      // LED blink
      if(pin_state == 0) 
        // Bit set
        GPIOA->BSRR = 0x00000100;
        pin_state = 1;
       else 
        // Bit clear
        GPIOA->BSRR = 0x01000000;
        pin_state = 0;
      

      HAL_Delay(3000);

  


void SystemClock_Config(void)

  RCC_OscInitTypeDef RCC_OscInitStruct = 0;
  RCC_ClkInitTypeDef RCC_ClkInitStruct = 0;

  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  
    Error_Handler();
  
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  
    Error_Handler();
  


void Error_Handler(void) 

#ifdef  USE_FULL_ASSERT
void assert_failed(char *file, uint32_t line) 
#endif

在STM32F334K8Tx 数据表中,我没有找到太多关于设置 SWD 触点寄存器的信息(哪种模式、设置时钟、上拉等)。

问题:如何做到这一点?或者我在哪里可以找到信息?

【问题讨论】:

【参考方案1】:

如何避免此类问题

首先,我同意其他答案,因为您确实应该使用某种函数或宏来执行您经常需要的基本寄存器操作(例如设置 GPIO 引脚)。那么您的代码就不太容易出现相当简单的编程错误,例如问题示例代码中的错误。


需要什么

示例代码的实际问题在于它不仅修改了寄存器的某些位(例如,启用 LED 引脚作为输出),而且还修改了整个 GPIO 组寄存器,它控制另外 15 或 7 个引脚(加上程序真正想要处理的 LED 引脚)。

在 STM32 中(就像在任何 ARM 中一样),几乎所有的寄存器和内存位置都被寻址为 32 位变量。 大多数端口寄存器控制不止一个资源(或关于资源的多个方面,resp.)。 相反,一堆类似的资源(或参数)被收集在一个 32 位的寄存器中。

为了修改寄存器的一部分,同时保留其他部分的内容,控制器软件必须

    将整个端口寄存器值(32 位)读出到 CPU 寄存器(或 RAM)中, 操作此寄存器,通常是通过ANDing/OR将位或位组(“掩码”)从中取出/放入其中, 将整个 CPU 寄存器值(及其修改)写回端口寄存器。

怎么做

如果你拿@P__J__的样本这样的函数,

#define BITMASK(nbits)   ((1 << nbits) - 1)

void GPIO_SetMode(GPIO_TypeDef *gpio, unsigned pin, unsigned mode)

    gpio -> MODER &= ~(BITMASK(2) << (2 * pin));
    gpio -> MODER |= ((mode & BITMASK(2)) << (2 * pin));

编译器会将&amp;= 翻译成一些汇编指令,这些指令执行步骤 (1.)-(3.) - 然后以相同的方式执行 |=

@YvonBlais 的样本,

// Set PA8 to OUTPUT mode
GPIOA->MODER |= GPIO_MODER_MODER8_1; // Ref.: 9.4.1

对单个寄存器和修改执行相同的操作(通用性较差!)。 它不包含&amp;= 部分,因为它不关心首先重置目标位,而是使用(不太通用...)MCU 的初始化状态。

这两个命题都应该在您的情况下或多或少地优雅/简单。

如何不这样做/你的控制器实际发生了什么

相比之下,原始样本中的bug是

// Set PA8 to OUTPUT mode
GPIOA->MODER = 0x00010000;

这会将所需的“01”模式分配给寄存器的位 [16:17](将引脚 8 配置为输出 - 请参阅 reference manual, 秒。 9.4.1)。 但是,它同时将 '00' 分配给 [0:1]、[2:3]、...、[14:15]、[18:19]、...和 ​​[30:31] ,它将所有其他 PAxy 引脚配置为输入模式。 正如您可以在 datasheet, (表 14 / 第 42 页),这也会影响引脚 PA13/PA14,它们曾经处于备用功能 (AF0) 模式以支持 SWD 接口。

如何刷写修正后的程序

有一种方法可以覆盖(错误的)软件设置以刷新另一个二进制文件: SWD 适配器在进行 SWD 编程之前/期间必须拉动控制器的复位引脚。

所有 SWD 编程工具都允许这样做,但通常必须选择正确的工具配置(“重置下连接”或类似的)才能工作。

【讨论】:

感谢您的回答!现在我将使用辅助功能,这将真正方便扩展代码时的工作。至于禁用 SWD 时的固件,将 BOOT0 拉到 VCC 的方法对我有帮助。【参考方案2】:

不要生气,但这是程序员懒惰的一个例子。如果您以这种方式编程,您将永远无法成功使用复杂的 uC,例如 STM32。

您应该编写一些辅助函数(是的,我知道它多了 20 行)。

#define GPIO_AFRL_AFRL0_Msk     (GPIO_AFRL_AFRL0_0 | GPIO_AFRL_AFRL0_1 | GPIO_AFRL_AFRL0_2 | GPIO_AFRL_AFRL0_3)

void GPIO_SetAF(GPIO_TypeDef *gpio, unsigned pin, unsigned AF)

    volatile uint32_t *AFreg = &gpio -> AFR[pin >= 8];

    if(AF <= 15)
    
        if(pin > 7) pin -= 8;

        *AFreg &= ~(GPIO_AFRL_AFRL0_Msk << (4 * pin));
        *AFreg |= (AF << (4 * pin));
    


#define BITMASK(nbits)   ((1 << nbits) - 1)

void GPIO_SetMode(GPIO_TypeDef *gpio, unsigned pin, unsigned mode)

    gpio -> MODER &= ~(BITMASK(2) << (2 * pin));
    gpio -> MODER |= ((mode & BITMASK(2)) << (2 * pin));


void GPIO_SetPUPDR(GPIO_TypeDef *gpio, unsigned pin, unsigned pupdr)

    gpio -> PUPDR &= ~(BITMASK(2) << (2 * pin));
    gpio -> PUPDR |= ((pupdr & BITMASK(2)) << (2 * pin));


void GPIO_SetOSPEEDR(GPIO_TypeDef *gpio, unsigned pin, unsigned speed)

    gpio -> OSPEEDR &= ~(BITMASK(2) << (2 * pin));
    gpio -> OSPEEDR |= ((speed & BITMASK(2)) << (2 * pin));


void GPIO_SetOTYPER(GPIO_TypeDef *gpio, unsigned pin, unsigned type)

    gpio -> OTYPER &= ~(BITMASK(1) << (1 * pin));
    gpio -> OTYPER|= ((type & BITMASK(1)) << (1 * pin));

正如你看到的很多工作。然后设置您的引脚,不影响 SWD 引脚。

在 STM32F334K8Tx 数据表中,我没有找到关于 设置 SWD 触点的寄存器(哪种模式,设置时钟, 引体向上等)。

问题:如何做到这一点?或者我在哪里可以找到信息?

一切都在那里。就读吧。例如MODER寄存器。

【讨论】:

感谢您的回答!我尊重反馈,因为这是一种变得更好的方法。实际上,辅助函数可以在扩展代码时促进工作,并将出错的可能性降到最低。我会用它。至于MODER寄存器等,我看到了这个,没看到对SWD管脚的要求,比如什么类型的输出,什么时钟速度等等。但是我修复了SWD,之后我开始在逻辑加法后配置PA8(LED),其中只更改了所需的位,而不是整个寄存器。 @АлексГарисон 所有其他寄存器都有默认值。拿 Datasheet 看看哪些端口是 SWD 然后从默认值你会知道 SWD 在复位后是如何配置的 我正在研究这段代码,我有一个问题。为什么我们在 BITMASK (2) 中使用 2? BITMASK(2) 返回 3,然后我们将在寄存器中选择两个必要的位。 我们需要两个位的掩码 - 0b11 即 3。你知道如何重置位以及为什么我们需要这个掩码吗? 据我了解,需要一个掩码来准确选择我们将使用的那些位。就我而言,这些是 16 位和 17 位 8 针 (8 * 2)。接下来,我们向左移位以达到 16 位。在我们反转接收到的位后,16 和 17 变为 0,其他所有变为 1。接下来,我们将执行位加法,其中包含的位(1 & 1 = 1 和 0 & 1 或 1 & 0 = 0)将被清除,其他一切都将不可替代。【参考方案3】:

您应该使用类似的东西,而不是使用幻数:

// Set PA8 to OUTPUT mode
GPIOA->MODER |= GPIO_MODER_MODER8_1; // Ref.: 9.4.1
// Set PUSH-PULL mode
//GPIOA->OTYPER = 0x00000000; // Reset value is 0x0000 0000
// Set pin speed
//GPIOA->OSPEEDR = 0x6401 0000;
GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEEDR8_0; // 9.4.3

// Set not pull
//GPIOA->PUPDR  // Reset value is: 0x6400 0000 9.4.4
// Set pin bit
GPIOA->BSRR |= GPIO_BSRR_BS_8; // 9.4.7

在循环中,您可以使用:

// The BSRR register is read only
GPIOA->BSRR |= GPIO_BSRR_BS_8; // set ON
GPIOA->BSRR |= GPIO_BSRR_BR_8; // reset
// You could also use:
GPIOA->BRR |= GPIO_BSRR_BS_8; // set ON 9.4.11

如果你想重置一个寄存器,使用类似的东西:

GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR8_1; // reset the value

【讨论】:

感谢您的回答!我尝试深入研究基础知识,所以我发现这项使用寄存器的工作很有用,也许我错了。 GPIOA-> 现代 | = GPIO_MODER_MODER8_1;这条线会完全清除端口 A 寄存器还是只是设置 PA8 位?据我了解,SWD连接器停止工作,因为在配置MODER,OTYPER等时,我完全清除了A端口设置,其中包括SWD引脚。

以上是关于如何使用 STM32 中的寄存器配置 SWD 引脚?的主要内容,如果未能解决你的问题,请参考以下文章

请教STM32用JLINK V8 SWD输出调试信息到ITM Viewer的问题

stm32用jlinkv8在swd模式下使用哪些引脚就可以了

stm32c8t6 swd调试时选择芯片选哪个(mdk5)

求助stm32程序小问题

STM32中管脚利用

如何通过jtag或swd连接stm32f205进行读/写?