https://hubstub.ru/stm32/65-stm32-gpio-ili-porty-vvoda-vyvoda.html
stm32f1xx

//Полагаем что выводы после сброса в режиме плавающего входа

//разрешаем тактирование порта A
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
	
//вход с подтяжкой к +
GPIOA->CRL &= ~GPIO_CRL_CNF0;
GPIOA->CRL |= GPIO_CRL_CNF0_1;
GPIOA->ODR |= GPIO_ODR_ODR0; 

//вход с подтяжкой к -
GPIOA->CRL &= ~GPIO_CRL_CNF1;
GPIOA->CRL |= GPIO_CRL_CNF1_1;
GPIOA->ODR &= ~GPIO_ODR_ODR1; 

//аналоговый режим
GPIOA->CRL &= ~GPIO_CRL_CNF2;

//выход с открытым стоком 2MHz
GPIOA->CRL &= ~GPIO_CRL_CNF3;
GPIOA->CRL |= GPIO_CRL_CNF3_0;
GPIOA->CRL |= GPIO_CRL_MODE3_1;

//двухтактный выход 10MHz
GPIOA->CRL &= ~GPIO_CRL_CNF4;
GPIOA->CRL |= GPIO_CRL_MODE4_0;

//альтернативная ф-ция, двухтактный выход, 50 MHz
GPIOA->CRL &= ~GPIO_CRL_CNF5;
GPIOA->CRL |= GPIO_CRL_CNF5_1;
GPIOA->CRL |= GPIO_CRL_MODE5;

//альтернативная ф-ция, выход с открытым стоком,  50 MHz
GPIOA->CRL |= GPIO_CRL_CNF6;
GPIOA->CRL |= GPIO_CRL_MODE6;


//проверяем значение нулевого вывода порта А
if (GPIOА->IDR & GPIO_IDR_IDR0)


//если вывод в режиме входа то активируется подтяжка к питанию
GPIOA->ODR |= GPIO_ODR_ODR0;
//или к земле
GPIOA->ODR &= ~GPIO_ODR_ODR0;
//если в режиме выхода, то на нём установится соответствующий лог.уровень
//например так можно установить все выходы порта в 1
GPIOA->ODR = 0xFFFF;


//сбросить нулевой бит порта А
GPIOA->BRR = GPIO_BRR_BR0;


//сбросить нулевой бит 
GPIOA->BSRR = GPIO_BSRR_BR0;
//установить нулевой бит
GPIOA->BSRR = GPIO_BSRR_BS0;

-=-=-
Как проверить состояние пина порта?
Скажем 4 пина порта С?

просто.
Можно использовать стандартную библиотеку:
if (GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_4)== Bit_RESET)....
Можно и так(само коротко и быстро:
if (GPIOС->IDR & GPIO_IDR_IDR4)...


#define pin_X  (GPIOС->IDR & GPIO_IDR_IDR4)
if (pin_X == 1) {......}

-=-=-

Код:
#define GPIO_ODR_ODR0                        ((uint16_t)0x0001)            /*!< Port output data, bit 0 */
#define GPIO_ODR_ODR1                        ((uint16_t)0x0002)            /*!< Port output data, bit 1 */
#define GPIO_ODR_ODR2                        ((uint16_t)0x0004)            /*!< Port output data, bit 2 */

GPIO_ODR_ODR0 это макросы описывающие битовую маску.
Хотите изменить сразу несколько - пишите как я в примере или укажите свою маску:

Код:
GPIOB->ODR ^=0x00ff; //переключит младшие 8 ног.

или что-б потом самому легче читалось:

Код:
/* маска для 8-ми нечетных ног */
#define ODD_0_8 0x0055
/* маска для 8-ми четных ног */
#define EVEN_0_8 0x00AA
GPIOB->ODR ^= ODD_0_8; //переключит нечетные
GPIOB->ODR ^= EVEN_0_8; //переключит четные

-=-=-=-=-=-

а как тогда событие нажатия отследить?

Ну, как-то так (одновременно и с дребезгом поборемся):

Код:
#define MAXCNT 10
char flip=0, pressed=0;

 while(1)
 {
    if((GPIOA->IDR&0x01)==0) //кнопка не нажата (или 0 читается во впемя дребезга) - готовимся к обработке
    {
      pressed=0;
      cntr=0;
    }
    else
      if(!pressed) //кнопка вроде бы нажата, но возможно это пока что только дребезг
      {
        cntr++; // сосчитаем до десяти в надежде, что за это время переходные процессы завершатся
        if(cntr>MAXCNT) // десять последних проходов кнопка в состоянии "нажато" - считаем, что событие "нажатие кнопки" свершилось
        {  // тут соб-сно ваша реакция на событие "нажатие кнопки", т.е. переход от 0 к 1
          flip=!flip;  // организуем простейший двоичный переключатель ("flip-flop")
          if(flip)      // и в зависимости от его состояния выполняем ту или иную ветвь
          {
            GPIOC->BSRR = GPIO_BSRR_BS9;
            GPIOC->BSRR = GPIO_BSRR_BR8;
          }
          else
          {
            GPIOC->BSRR = GPIO_BSRR_BS8;
            GPIOC->BSRR = GPIO_BSRR_BR9;
          }
          pressed=1;  // отработав событие "нажатие кнопки", отключаем проверку на это событие -> до следующего отпускания кнопки
        }
      }
      else //кнопка в состоянии "нажато" (после отловленного события "нажатие кнопки") - в нашем случае неинтересно 
      {
      }
 }
