最近,我用STM32F107RCT6设计了一款LED灯板控制板,现在板子已经完成嘉立创PCB制板+贴片了,现在需要编写对应测试代码程序用于测试。现在需要用到STM32CubeMX这个工具生成基于keil MDK的模板工程文件,现在遇到了一些知识点(以前学习过),现在记录一下希望对自己有所帮助。
(1)外部时钟晶振HSE的选择
首先如果使用的是外部无源晶振,那么RCC的High Speed Clock(HSE)需要选择:Crystal/Ceramic Resonator这个选项。
现在大家需要注意的是,如果HSE选项选择了Bypass Clock Source,会发现STM32cubeMX软件也分配了2个引脚,这个与我以前记忆中的不一致了,毕竟有源时钟就需要一个OSC_IN就可以了。后来我查询了好多资料,发现F1、F2、F4都存在这样的问题了,后来查询了一下资料,并问了以前的同事,发现并不是stm32cubeMX的bug,而是stm32为了避险,防止OSC_OUT引脚用作其他功能(毕竟已经被占用了)。而且在生成的HAL库中Bypass是兼容Cystal/Ceramic Resonator的,如下图所示:

cpp
#define RCC_HSE_BYPASS (uint32_t)(RCC_CR_HSEBYP | RCC_CR_HSEON)
(2)HAL库中位操作如何找到对应参考手册中的寄存器以及寄存器操作
首先,HAL库环境中使用宏定义的方式对寄存器的地址进行了封装,如下图所示:
cpp
#define RCC ((RCC_TypeDef *)RCC_BASE)
#define CRC ((CRC_TypeDef *)CRC_BASE)
#define FLASH ((FLASH_TypeDef *)FLASH_R_BASE)
#define OB ((OB_TypeDef *)OB_BASE)
#define ETH ((ETH_TypeDef *) ETH_BASE)
#define DBGMCU ((DBGMCU_TypeDef *)DBGMCU_BASE)
#define USB_OTG_FS ((USB_OTG_GlobalTypeDef *)USB_OTG_FS_PERIPH_BASE)
RCC为STM32的RCC模块对应寄存器组的首地址,我们都知道ARM为32位单片机,寄存器为32位的占4个字节,如果继续查询可以知道RCC对应的地址为寄存器组的首元素的地址。
通过追踪RCC宏定义的值,得到RCC的值为/**0x4002_1000 */可以查询参考手册中RCC字样得到内存映射(Memory map)相关章节:

两者起始地址都是0x4002 1000。
另外,RCC这个地址值0x4000_1000被强制转换为"(RCC_TypeDef *)",这个RCC_TypeDef为自定义结构体类型:
cpp
typedef struct
{
__IO uint32_t CR;
__IO uint32_t CFGR;
__IO uint32_t CIR;
__IO uint32_t APB2RSTR;
__IO uint32_t APB1RSTR;
__IO uint32_t AHBENR;
__IO uint32_t APB2ENR;
__IO uint32_t APB1ENR;
__IO uint32_t BDCR;
__IO uint32_t CSR;
__IO uint32_t AHBRSTR;
__IO uint32_t CFGR2;
} RCC_TypeDef;
这个结构体类型内部成员是各种uint32_t类型的变量,而且有一定顺序,通过查询参考手册,发现成员变量为各个寄存器(相当于内存空间),如下所示。

cpp
#define __HAL_RCC_GET_FLAG(__FLAG__) (((((__FLAG__) >> 5U) == CR_REG_INDEX)? RCC->CR : \
((((__FLAG__) >> 5U) == BDCR_REG_INDEX)? RCC->BDCR : \
RCC->CSR)) & (1U << ((__FLAG__) & RCC_FLAG_MASK)))
再回到HAL代码中,通过RCC->xxx寄存器来读写访问这些寄存器,如果RCC->xxx寄存器作为赋值运算符的左值,那么会进行写操作,如果RCC->xxx寄存器作为右值则进行读操作。可以将c语言中"->"使用"_"替代,再参考手册中查询得到对应寄存的值。例如:搜索RCC_CR可以搜索找到CR寄存器。
其中,Address offset:0x00表示距离首元素首地址偏移量为0x00。Reset Value为复位上电后的默认值。
表格中的31~0表示CR寄存器的bit位,其中有一些是作为一组共同作用的,例如bit7~bit3一共4为一共有2^4=16种可能,每一种可能都对应某些操作,我们只需要使用c语言的相关位操作来操作那个这些寄存器位即可实现硬件功能控制。表格中间层表示bit位或者bit位组表示的功能,表格最下层r:只读,rw:可读可写,w:只写。
那么,现在遇到一个问题:c语言代码软件是如何表示这个bit位的呢。
答案:还是宏定义,我们可以通过搜索RCC_xxx寄存器_yyy功能来找到对应寄存器的bit位或者比特位组。
例如:搜索RCC_CR_HSION,找到如下:
cpp
#define RCC_CR_HSEON_Msk (0x1U << RCC_CR_HSEON_Pos) /*!< 0x00010000 */
#define RCC_CR_HSEON RCC_CR_HSEON_Msk
经过计算:RCC_CR_HSEON:0x1U<<16,这个与硬件参考手册RCC_CR寄存器的bit16相对应。
使用了含参宏SET_BIT()、CLEAR_BIT()对这些bit位进行读1或者写0操作。
cpp
else if ((__STATE__) == RCC_HSE_BYPASS) \
{ \
SET_BIT(RCC->CR, RCC_CR_HSEBYP); \
SET_BIT(RCC->CR, RCC_CR_HSEON); \
} \
else \
{ \
CLEAR_BIT(RCC->CR, RCC_CR_HSEON); \
CLEAR_BIT(RCC->CR, RCC_CR_HSEBYP);
cpp
#define SET_BIT(REG, BIT) ((REG) |= (BIT))
#define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT))
#define READ_BIT(REG, BIT) ((REG) & (BIT))
#define CLEAR_REG(REG) ((REG) = (0x0))
#define WRITE_REG(REG, VAL) ((REG) = (VAL))
#define READ_REG(REG) ((REG))
#define MODIFY_REG(REG, CLEARMASK, SETMASK) WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
#define POSITION_VAL(VAL) (__CLZ(__RBIT(VAL)))
因为,这个bit位对应功能是可读可写的,所以位与运算(&)实现了对应bit位中数值的读取工作
cpp
else if((RCC->CR &RCC_CR_HSEON) == RCC_CR_HSEON)
{
RCC_OscInitStruct->HSEState = RCC_HSE_ON;
}
最后,再次理解"硬件软件一盘棋"这句话的含义:硬件就是对应数据手册中相关寄存器以及寄存器功能,软件指的是用c语言编写实现的代码。硬件有的功能都可以通过软件编写实现出来。另外,c语言为什么是最适合操作硬件的语言,这句话也得到充分论证。