Битовые операции
Операция сдвига
Для числа 1 (Dec, в десятичной системе 1) — 00000001 (Bin, в двоичной системе 0b00000001):
1 << 0 = 1 (00000001); 1 << 1 = 2 (00000010); 1 << 2 = 4 (00000100) 1 << 5 = 32 (00100000); 1 >> 2 = 0 (00000000).
Операция конъюнкция «&» (логическое И, AND) :
1101 0001 (209)
&
0000 0111 (7)
----------------
0000 0001 (1)
Операция дизъюнкция «|» (логическое ИЛИ, OR):
1101 0001 (209)
|
0000 0111 (7)
----------------
1101 0111 (215)
Операция инверсия «~» (логическое НЕ):
1101 0001(209)
~
----------------
0010 1110(46)
Применение битовых операций.
- Установка бита в регистре.
Например для включения тактирования на порту А нужно установить 3 бит в регистр APB2ENR можно использовать любую из следующих конструкций операторов, все инструкции выполняют идентичную задачу:
RCC->APB2ENR = RCC->APB2ENR | 4; RCC->APB2ENR = RCC->APB2ENR | (1 << 2); RCC->APB2ENR |= 0x000004; RCC->APB2ENR |= (1 << 2);
Чтобы установить несколько бит в регистре нужно использовать битовую операцию «или» например установка 3 и 4 бита одной инструкцией:
RCC->APB2ENR |= ( 1 << 2 ) | ( 1 << 3 );
2. Сброс битов в регистре.
Для сброса разрядов в регистре необходимо использовать битовую операцию «&» (логическое «И»), которая применяется к двум битам (бинарная операция) и даёт единицу только в том случае если оба исходных бита имеют единичное значение, также необходима битовая операция «~» (логическое «НЕ», инверсия).
Для того чтобы сбросить например 5-й бит (10011101) в регистре нужно подготовить маску (как при установке битов), инвертировать ее биты «~», а потом выполним битовую операцию «&» над текущим значением регистра и полученной инвертированной маской.
Для подготовки маски выполним сдвиг битов на 4 разрядов в числе 1 (00000001).
0000 0001 (1)
<< 4
----------------
0001 0000 (16)
Маска готова, получили число 16 (00010000), 2 в 4-й степени. Выполним инверсию битов:
0001 0000 (16)
~
----------------
1110 1111 (239)
Готово, осталось применить маску к содержимому регистра используя битовую операцию «&»:
1001 1101 (157)
&
1110 1111 (239)
----------------
1000 1101 (141)
В языке Си данные операции можно выполнить используя любую из приведенных ниже, идентичных по результату команд, например ниже приведена команда сброса флага окончания преобразования АЦП end of conversion:
ADC1->SR = ADC1->SR &~ 0x02; ADC1->SR = ADC1->SR & ~( 1 << 1 ); ADC1->SR &= ~( 1 << 1 );
Для одновременного сброса нескольких битов регистра нужно использовать битовую операцию «или» . Сброс 2 и 3 бита:
ADC1->SR &= ~(1 << 1)|(1 << 2);
Чтобы, записать или считать какое значение в, из ячейки памяти по адресу 0x40010800 в языке СИ,используется указатель «*».
*((uint32_t*)0x40010800)
Теперь напишем программу pwm на портах
Прежде чем приступить к написанию программы для микроконтроллера, нужно подключить тактирование нужных блоков, за это отвечают регистры RCC — reset and clock control. Описание работы и их расположение можно узнать из reference manual к данной серии контроллеров https://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf. Затем нужно найти регистры которые включают тактирование.
Нам нужно включить тактирование на таймере 2 и на порту GPIOA.
Как видим для этого нужно установить 2 бит в регистре APB2ENR и 0 бит в регистре APB1ENR. Теперь осталось определить адреса этих регистров чтобы обратиться к ним. В refernce manual заходим в раздел memory map
Там находим адрес RCC Reset and clock control регистров, 0x40021000 теперь осталось определить смещение APB2ENR и APB1ENR регистров.
Соответственно регистр APB2ENR имеет адрес 0x40021018, а APB1ENR по адресу 0x4002101C. Функция установки битов будет иметь следующий вид.
void RCC_ENABLE(void) { *((uint32_t*)0x40021018)|= 1 << 2; *((uint32_t*)0x4002101C)|= 1 << 0; }
При запуске в режиме отладки в keil мы введя в поле memory адрес регистров увидеть как он изменяется 0x04=b0100
Каждый порт GPIO (GPIOA, GPIOB и т. д.) имеет 7 регистров:
2 используются для индивидуальной настройки шестнадцати битов порта,
2 — для параллельного чтения / записи шестнадцати битов порта,
2 — для индивидуальной установки / сброса шестнадцати битов порта,
1 — для реализации «последовательности блокировки», которая предназначена для предотвращения случайного изменения мошенническим кодом конфигурации порта. Эта последняя функция может помочь минимизировать вероятность того, что программные ошибки приводят к аппаратным сбоям; например, случайно вызвав короткое замыкание.
Структура регистров портов GPIO:
typedef struct
{
volatile uint32_t CRL;
volatile uint32_t CRH;
volatile uint32_t IDR;
volatile uint32_t ODR;
volatile uint32_t BSRR;
volatile uint32_t BRR;
volatile uint32_t LCKR;
} GPIO_TypeDef;
Адреса регистров различных портов в библиотеке stm32f10x.h:
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
Подмножество процедур, доступных для управления портами GPIO:
void GPIO_Init(GPIO_TypeDef* GPIOx ,
GPIO_InitTypeDef* GPIO_InitStruct);
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx ,
uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx ,
uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx , uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx , uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx , uint16_t GPIO_Pin ,
BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx , uint16_t PortVal);
Адреса периферийных устройств:
0x40013800 - 0x40013BFF USART1
0x40013000 - 0x400133FF SPI1
0x40012C00 - 0x40012FFF TIM1 timer
0x40012400 - 0x400127FF ADC1
Основные этапы инициализации, необходимые для использования любого из периферийных устройств STM32:
- Включить тактирование на периферию
- Настроить контакты, необходимые для периферийных устройств
- Настроить периферийное оборудование
#include <stm32f10x.h> #include <stm32f10x_rcc.h> #include <stm32f10x_gpio.h> void Delay(uint32_t nTime); int main(void) { GPIO_InitTypeDef GPIO_InitStructure; // Enable Peripheral Clocks ... (1) ... // Configure Pins ... (2) ... // Configure SysTick Timer ... (3) ... while (1) { static int ledval = 0; // toggle led ... (4) ... Delay(250); // wait 250ms } } // Timer code ... (5) ... #ifdef USE_FULL_ASSERT void assert_failed(uint8_t* file, uint32_t line) { /* Infinite loop */ /* Use GDB to find out why we're here */ while (1); } #endif