PD_ODR_ODR4 = 1;
编程STM8 GPIO,但是stm32f10x.h
没有这个函数。是否有.h
文件定义位?抱歉,我不知道如何更好地解释这个问题。
我尝试了多个GPIO库。
"Original Answer"翻译成"最初的回答"
PD_ODR_ODR4 = 1;
编程STM8 GPIO,但是stm32f10x.h
没有这个函数。是否有.h
文件定义位?stm32f10x.h
,所以我假设它是关于STM32F1系列控制器的。其他系列有一些差异,但一般过程是相同的。GPIOA
,GPIOB
等。它们被定义为指向GPIO_TypeDef
结构体的指针。有3个控制寄存器影响引脚输出。ODR
会一次性设置所有16个引脚,例如GPIOB->ODR = 0xF00F
会将引脚B0
到B3
和B12
到B15
设置为1,并将B4
到B11
设置为0,而不考虑它们之前的状态。可以写GPIOD->ODR |= (1<<4)
将引脚GPIOD4
设置为1,或者写GPIOD->ODR &= ~(1<<4)
将其重置。BSRR
把写入的值视为两个位掩码。低半字是设置掩码,值为1的位将在ODR
中对应的位设置为1。高半字是重置掩码,值为1的位将在ODR
中对应的位设置为0。GPIOC->BSRR = 0x000701E0
会将引脚C5
到C8
设置为1,将C0
到C2
重置为0,并保持所有其他端口位不变。当在写入BSRR
时尝试同时设置和重置相同的位时,它将被设置为1。BRR
与在BSRR
中写入重置掩码相同,即GPIOx->BRR = x
等同于GPIOx->BSRR = (x << 16)
。#define GPIOD_OUT(pin, value) GPIOD->BSRR = ((0x100 + value) << pin)
#define GPIOD4_OUT(value) GPIOD_SET(4, value)
要更改单个引脚,但它并不像它本来应该那么灵活,例如,您无法获取单个引脚的地址并将其传递给变量。
位带技术
Cortex-M控制器(不是所有控制器,但STM32F1
系列有)具有此功能,可使内部RAM和硬件寄存器中的每个位可寻址。 0x40000000-0x400FFFFF
范围内的每个位映射到0x42000000-0x43FFFFFF
范围内的完整32位字。 它不能与位于此地址范围之外的外设一起使用,例如USB或NVIC。
可以使用此宏计算外设寄存器的位带地址:
#define BB(reg) ((uint32_t *)(PERIPH_BB_BASE + ((uint32_t)&(reg) - PERIPH_BASE) * 32U))
你可以将得到的指针视为一个数组的基础,该数组包含32个字,每个字对应外设寄存器中的一个位。现在,就可以
#define PD_ODR_ODR4 (BB(GPIOD->ODR)[4])
并在作业中使用它。读取它将以0或1作为其值,写入它的值将复制写入值的最低有效位到外设寄存器位。您甚至可以获取它的地址,并将其传递给执行某些操作的函数。
比特带是在PM0056 Cortex®-M3编程手册中记录的。
请注意,底部的“裸机,无头文件”的示例仅供教育目的:我建议仅使用由CMSIS和STM32提供的头文件,而不是编写自己的头文件。但是,在某些情况下,您可能需要快速访问寄存器,就是这样。
// Be sure to add `UL` after ADDRESS_TO_MY_REGISTER to make it "unsigned long".
#define MY_REGISTER (*(volatile uint32_t *)(ADDRESS_TO_MY_REGISTER))
// Be sure to add `UL` after ADDRESS_TO_MY_REGISTER to make it "unsigned long".
#define MY_REGISTER (*(volatile const uint32_t *)(ADDRESS_TO_MY_REGISTER))
C语言中访问任何内存地址位置的标准(实际上也是唯一)方式是使用以下基于#define
的易失性指针结构:
#define MY_REGISTER (*(volatile uint32_t *)(ADDRESS_TO_MY_REGISTER)) // Be sure to add `UL` after ADDRESS_TO_MY_REGISTER to make it "unsigned long".
如何读取此内容:
(基本上从右到左阅读):“将ADDRESS_TO_MY_REGISTER转换为指向4个字节的易失性组(即4个易失性字节的组)的指针,然后获取该组4个字节的内容,并使其成为MY_REGISTER的含义。” 即:现在MY_REGISTER修改了这个地址位置处内存的内容。
必须进行指针转换才能将地址位置转换为实际内存地址(指针),而最左边的解引用(*)是为了使我们修改该寄存器或该地址处内存的内容,而不仅仅是修改地址本身。关键字“volatile”是必需的,以防止编译器优化,否则可能会尝试假设该寄存器中的内容并优化掉读取或写入该寄存器的代码。访问寄存器时始终需要使用“volatile”,因为必须假定它们可以从其他进程、外部事件或引脚更改或来自mcu本身的硬件和/或外设发生更改。
尽管此构造在C中可在所有设备上运行(不仅限于STM32),但请注意您所转换的类型的大小(如uint8_t
,uint32_t
等)对于您的架构非常重要。即:不要尝试在8位微控制器上使用uint32_t
类型,因为即使它似乎可以工作,但在8位微控制器上对32位内存块的原子访问是不被保证的。然而,在8位AVR微控制器上进行8位访问实际上是保证自动原子性的(相关阅读:C++ decrementing an element of a single-byte (volatile) array is not atomic! WHY? (Also: how do I force atomicity in Atmel AVR mcus/Arduino))。但是对于STM32 mcu,32位或更小的内存访问是自动原子的,正如我在这里研究和描述的一样:https://dev59.com/o1QJ5IYBdhLWcg3w8ajH#52785864。#define
的结构在所有微控制器中都被使用,你可以使用它任意访问任何你认为合适的内存位置,字面上,在任何微控制器上,除非数据手册和/或参考手册另有规定(例如:某些寄存器需要先进行特殊解锁指令)。例如,如果你追溯AVRLibc上的寄存器(由Arduino使用--在此下载:https://www.nongnu.org/avr-libc/-->“下载”部分),并展开所有宏定义,你会发现所有寄存器都是8位,归结为以下内容:#define TCCR2A (*(volatile uint8_t *)(0xB0))
在这里,注册TCCR2A
,或者称为"定时器2的定时器计数控制寄存器A",被设置为一个1字节大小的地址为0xB0的寄存器。
在STM32中同样适用,只不过寄存器通常是32位,因此你可以使用uint32_t
代替uint8_t
(虽然uint8_t
在STM32上也可以使用),并且它们经常使用基于结构体的构造。例如来自"stm32f767xx.h":
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
其中GPIO_TypeDef
是一个结构体:
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
而__IO
被简单地定义为volatile
。由于该结构体的每个成员都是4字节,并且您具有4字节对齐,因此该结构体会自动打包,因此您最终会得到结构体的每个新元素只是指向离基地址更远的“地址偏移”位置(如右侧的注释所示),因此一切都能正常工作!
例如,使用STM32定义的GPIOD->BSRR
类型结构的替代方法是手动执行以下操作:
#define GPIOD_BSRR (*(volatile uint32_t *)(GPIOD_BASE + 0x18UL)) // Don't forget the `U` or `UL` at the end of 0x18 here!
*
的左侧任何位置添加const
即可:#define MY_REGISTER (*(volatile const uint32_t *)(ADDRESS_TO_MY_REGISTER)) // Be sure to add `UL` after ADDRESS_TO_MY_REGISTER to make it "unsigned long".
现在,您可以使用位移、位掩码和位操作或使用您定义的一些宏来设置或获取寄存器中的任何位。
例如:
// get bit30 from the address location you just described above
bool bit30 = (MY_REGISTER >> 30UL) & 0x1UL;
// or (relies on the fact that anything NOT 0 in C is automatically `true`):
bool bit30 = MY_REGISTER & (1UL << 30UL);
// set bit 30 to 1
MY_REGISTER |= (1UL << 30UL);
// clear bit 30 to 0
MY_REGISTER &= ~(1UL << 30UL);
或者: (例如,就像Arduino在这里所做的一样:https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Arduino.h)
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))
然后:
// get bit 30
bool bit30 = bitRead(MY_REGISTER, 30);
// set bit 30 to 1
bitSet(MY_REGISTER, 30);
// or
bitWrite(MY_REGISTER, 30, 1);
// clear bit 30 to 0
bitClear(MY_REGISTER, 30);
// or
bitWrite(MY_REGISTER, 30, 0);
我们需要:
我不会详细介绍(请参阅上文),但是要读取引脚,您需要使用GPIOx_IDR
(GPIO输入数据寄存器)。要将引脚写入0或1,您需要GPIOx_ODR
(GPIO输出数据寄存器)。根据上面显示的RM0008中的措辞,显然对GPIOx_ODR
的写入不是原子的,因此,如果您想要在一个端口上以原子方式写入一组引脚(同时),则需要使用GPIOx_BSRR
(GPIO位设置/复位寄存器)或GPIOx_BRR
(GPIO位复位寄存器--仅能将位清除为0)。
假设我们只需要处理A端口,则需要以下寄存器的定义:
GPIOA_IDR // Input Data Register (for reading pins on Port A)
GPIOA_ODR // Output Data Register (for *nonatomically* writing 0 or 1 to pins on Port A)
GPIOA_BSRR // Bit Set/Reset Register (for *atomically* setting (writing 1) or resetting (writing 0) pins on Port A)
GPIOA_BRR // Bit Reset Register (for *atomically* resetting (writing 0) pins on Port A)
让我们去寻找这些寄存器的地址!
参见RM0008 p172至174。
我们可以看到偏移量和数据方向如下:| Register | "Address offset"| direction
|------------|-----------------|---------------
| GPIOA_IDR | 0x08 | r (read only)
| GPIOA_ODR | 0x0C | rw (read/write)
| GPIOA_BSRR | 0x10 | w (write only)
| GPIOA_BRR | 0x14 | w (write only)
#define GPIOA_IDR (*(volatile const uint32_t *)(0x40010800UL + 0x08UL)) // use `const` since this register is read-only
#define GPIOA_ODR (*(volatile uint32_t *)(0x40010800UL + 0x0CUL))
#define GPIOA_BSRR (*(volatile uint32_t *)(0x40010800UL + 0x10UL))
#define GPIOA_BRR (*(volatile uint32_t *)(0x40010800UL + 0x14UL))
// Choose a pin number from 0 to 15
uint8_t pin_i = 0; // pin index
// Read it
bool pin_state = (GPIOA_IDR >> (uint32_t)pin_i) & 0x1;
// Write it to 1
GPIOA_ODR |= 1UL << (uint32_t)pin_i; // not to be used for writing to more than 1 pin at a time since apparently its operation is not atomic?
// OR
GPIOA_BSRR |= 1UL << (uint32_t)pin_i; // GOOD! RECOMMENDED approach
// Write it to 0
GPIOA_ODR &= ~(1UL << (uint32_t)pin_i); // not to be used for writing to more than 1 pin at a time since apparently its operation is not atomic?
// OR
GPIOA_BSRR |= (1UL << (uint32_t)pin_i) << 16UL; // GOOD! RECOMMENDED approach, but a bit confusing obviously
// OR
GPIOA_BRR |= 1UL << (uint32_t)pin_i; // GOOD! RECOMMENDED approach
例如:从“STM32Cube_FW_F1_V1.6.0/Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_gpio.c”中:
HAL_GPIO_ReadPin()
(请注意,他们使用GPIOx->IDR
寄存器进行读取):/**
* @brief Reads the specified input port pin.
* @param GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
* @param GPIO_Pin: specifies the port bit to read.
* This parameter can be GPIO_PIN_x where x can be (0..15).
* @retval The input port pin value.
*/
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
GPIO_PinState bitstatus;
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)GPIO_PIN_RESET)
{
bitstatus = GPIO_PIN_SET;
}
else
{
bitstatus = GPIO_PIN_RESET;
}
return bitstatus;
}
HAL_GPIO_WritePin()
(请注意,他们使用GPIOx->BSRR
寄存器将引脚写入0和1):/**
* @brief Sets or clears the selected data port bit.
*
* @note This function uses GPIOx_BSRR register to allow atomic read/modify
* accesses. In this way, there is no risk of an IRQ occurring between
* the read and the modify access.
*
* @param GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
* @param GPIO_Pin: specifies the port bit to be written.
* This parameter can be one of GPIO_PIN_x where x can be (0..15).
* @param PinState: specifies the value to be written to the selected bit.
* This parameter can be one of the GPIO_PinState enum values:
* @arg GPIO_BIT_RESET: to clear the port pin
* @arg GPIO_BIT_SET: to set the port pin
* @retval None
*/
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
if(PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U;
}
}
结束
|=
或&=
)。 - Gabriel Staples
GPIOD -> BSRR = (1 << pin);
设置为复位端口D
的GPIOD -> BRR = (1 << pin);
。请注意,此操作仅适用于fir端口D
。 - 0___________