Часть 1. Настройка окружения для работы с libopencm3

Часть 2. Работа с GPIO, SPI, отладка проекта при помощи GDB

Часть 3. Работа с USART, прерываниями, I2C и таймерами

Данная статья является заключительной в цикле, посвященном быстрому старту разработки под STM32 при помощи libopencm3. Без лишних слов приступим к рассмотрению оставшейся периферии, наиболее часто используемой в микроконтроллерах.

Третий проект: Передача данных через USART & использование прерываний

Пора рассмотреть взаимодействие микроконтроллера с окружающим миром с помощью USART – интерфейса, позволяющего организовать взаимодействие с человеком при помощи текста.

Цель: Отправить в USART строчку «Hello from LibOpenCM3!» Параметры подключения должны быть: скорость 9600, 8 бит данных, один стоп-бит, проверка четности выключена.

Генерируем наш проект:

./make_project.sh ex3_1_usart

Начальные шаги не отличаются от предыдущих случаев: подключение заголовочных файлов, ответственных за периферию, и включение тактирования порта и периферии.

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>
#include "ex3_1_usart.h"
static void clock_setup(void)
{
rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
rcc_periph_clock_enable(RCC_GPIOA);
rcc_periph_clock_enable(RCC_AFIO);
rcc_periph_clock_enable(RCC_USART2);
}
int main(void)
{
clock_setup();
while(1);
}

STM32F4
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>
#include "ex3_1_usart.h"
static void clock_setup(void)
{
rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_96MHZ]);
rcc_periph_clock_enable(RCC_GPIOA);
rcc_periph_clock_enable(RCC_USART2);
}
int main(void)
{
clock_setup();
while(1);
}

Отконфигурируем USART с требуемыми параметрами. Функции libopencm3 интуитивно понятны. Так же настроим ножки микроконтроллера для выполнения функций RX и TX, активизировав альтернативные функции.

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>
#include "ex3_1_usart.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_AFIO);
        rcc_periph_clock_enable(RCC_USART2);
}
 
int main(void)
{
        clock_setup();
 
        gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO2 | GPIO3 );
 
        usart_set_baudrate(USART2, 9600);
        usart_set_databits(USART2, 8);
        usart_set_stopbits(USART2, USART_STOPBITS_1);
        usart_set_parity(USART2, USART_PARITY_NONE);
        usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
        usart_set_mode(USART2, USART_MODE_TX_RX);
        usart_enable(USART2);
 
        while(1);
}

STM32F4

USART готов к приему и передаче данных. Так давайте отправим что-нибудь в сторону компьютера. Напишем вспомогательную функцию, отправляющую посимвольно в USART строку:

static void usart_transmit(const char *str)
{
	while (*str != '\000') {
		usart_send_blocking(USART2, *str);
		str++;
	}
}

Для реализации задержки между отправками воспользуемся подсистемой DWT, описанной ранее:

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/cm3/dwt.h>
#include "ex3_1_usart.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_AFIO);
        rcc_periph_clock_enable(RCC_USART2);
}
 
static uint32_t dwt_setup(void)
{
        dwt_enable_cycle_counter();
        __asm volatile ("nop");
        __asm volatile ("nop");
        __asm volatile ("nop");
 
     if(dwt_read_cycle_counter())
     {
       return 0;
     }
     else
  {
    return 1;
  }
}
 
static void dwt_delay_ms(uint32_t milliseconds)
{
        uint32_t initial_ticks = dwt_read_cycle_counter();
        uint32_t ms_count_tics = milliseconds * (rcc_ahb_frequency / 1000);
        while ((dwt_read_cycle_counter() - initial_ticks) < ms_count_tics);
}
 
static void usart_transmit(const char *str)
{
        while (*str != '\000') {
                usart_send_blocking(USART2, *str);
                str++;
        }
}
 
int main(void)
{
        clock_setup();
        dwt_setup();
 
        gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO2 | GPIO3 );
 
        usart_set_baudrate(USART2, 9600);
        usart_set_databits(USART2, 8);
        usart_set_stopbits(USART2, USART_STOPBITS_1);
        usart_set_parity(USART2, USART_PARITY_NONE);
        usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
        usart_set_mode(USART2, USART_MODE_TX_RX);
        usart_enable(USART2);
 
        while(1){
                dwt_delay_ms(1000);
                usart_transmit("Hello from LibOpenCM3!\r\n");
        }
}

STM32F4
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/cm3/dwt.h>
#include "ex3_1_usart.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_96MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_USART2);
}
 
static uint32_t dwt_setup(void)
{
        dwt_enable_cycle_counter();
        __asm volatile ("nop");
        __asm volatile ("nop");
        __asm volatile ("nop");
 
     if(dwt_read_cycle_counter())
     {
       return 0;
     }
     else
  {
    return 1;
  }
}
 
static void dwt_delay_ms(uint32_t milliseconds)
{
        uint32_t initial_ticks = dwt_read_cycle_counter();
        uint32_t ms_count_tics = milliseconds * (rcc_ahb_frequency / 1000);
        while ((dwt_read_cycle_counter() - initial_ticks) < ms_count_tics);
}
 
static void usart_transmit(const char *str)
{
        while (*str != '\000') {
                usart_send_blocking(USART2, *str);
                str++;
        }
}
 
int main(void)
{
        clock_setup();
        dwt_setup();
        gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2 | GPIO3);
        gpio_set_af(GPIOA, GPIO_AF7, GPIO2 | GPIO3);
 
        usart_set_baudrate(USART2, 9600);
        usart_set_databits(USART2, 8);
        usart_set_stopbits(USART2, USART_STOPBITS_1);
        usart_set_parity(USART2, USART_PARITY_NONE);
        usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
        usart_set_mode(USART2, USART_MODE_TX_RX);
 
        usart_enable(USART2);
        while(1){
                dwt_delay_ms(1000);
                usart_transmit("Hello from LibOpenCM3!\r\n");
        }
}

Выполняем сборку и прошивку, и подключаем переходник Serial2Usb к микроконтроллеру: PA3 к TX, PA2 к RX.

Подключение переходника USB-2-Serial  к микроконтроллеру на примере BluePill
Подключение переходника USB-2-Serial к микроконтроллеру на примере BluePill

Открываем терминальную программу, и подключаемся к символьному устройству переходника.

$ cu -l /dev/ttyUSB0 -s 9600
Connected.
Hello from LibOpenCM3!
Hello from LibOpenCM3!
Hello from LibOpenCM3!
Hello from LibOpenCM3!
Hello from LibOpenCM3!
Hello from LibOpenCM3!

Следующий наш шаг – передача в сторону микроконтроллера. Поскольку постоянно опрашивать периферию на предмет входящих данных – дурной тон, следующая наша цель будет включать работу с прерываниями STM32.

Цель: при старте микроконтроллер должен выдать в терминал «Hello», ожидать введения команд. При введений команды «toggle_led» переключать состояние светодиода на ножке PC13, а при команде «say_hello» – отправлять строку «Hello from LibOpenCM3!».

Работа прерываний в STM32 опирается на систему NVIC (Nested Vector Interrupt Controller) и ничем особо не отличается от других микроконтроллеров (к примеру, Atmega). Единственное, у прерываний в STM32 имеются приоритеты.

Начнем реализовывать работу USART с поддержкой прерываний.

Генерируем шаблон нового проекта:

$ ./make_project.sh ex3_2_usart_irq

Первым делом подключим и активизируем NVIC:

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
#include "ex3_2_usart_irq.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
}
 
int main(void)
{
        clock_setup();
        nvic_enable_irq(NVIC_USART2_IRQ); //Включаем контроллер прерываний для USART2
        nvic_set_priority(NVIC_USART2_IRQ, 1); //Опционально, устанавливем приоритет прерывания.
        while(1);
}

STM32F4
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
#include "ex3_2_usart_irq.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_96MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_USART2);
}
 
int main(void)
{
        clock_setup();
        nvic_enable_irq(NVIC_USART2_IRQ); //Включаем контроллер прерываний для USART
        nvic_set_priority(NVIC_USART2_IRQ, 1); //Опционально, устанавливем приоритет прерывания.
        while(1);
}

Далее мы конфигурируем USART почти так же, как в предыдущем примере. Единственное, что мы добавим – активизацию прерываний по приему данных. Вызываем функцию usart_enable_rx_interrupt() и имплементируем обработчик прерываний usart2_isr(). Полный список имен обработчиков прерываний можно найти в документации (F1 seriesF4 series).

Еще не забываем сконфигурировать ножку PC13, подсоединенную к светодиоду.

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/gpio.h>
 
#include "ex3_2_usart_irq.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_GPIOC);
        rcc_periph_clock_enable(RCC_AFIO);
        rcc_periph_clock_enable(RCC_USART2);
}
 
int main(void)
{
        clock_setup();
        nvic_enable_irq(NVIC_USART2_IRQ); //Включаем контроллер прерываний для USART2
        nvic_set_priority(NVIC_USART2_IRQ, 1); //Опционально, устанавливем приоритет прерывания.
        gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO2);
        gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO3);
 
        gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_10_MHZ,  GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
        gpio_set(GPIOC, GPIO13);
 
 
        usart_set_baudrate(USART2, 9600);
        usart_set_databits(USART2, 8);
        usart_set_stopbits(USART2, USART_STOPBITS_1);
        usart_set_parity(USART2, USART_PARITY_NONE);
        usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
        usart_set_mode(USART2, USART_MODE_TX_RX);
        usart_enable_rx_interrupt(USART2);
        usart_enable(USART2);
 
        while(1);
}
 
void usart2_isr(void){
}

STM32F4
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/gpio.h>
#include "ex3_2_usart_irq.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_96MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_GPIOC);
        rcc_periph_clock_enable(RCC_USART2);
}
 
int main(void)
{
        clock_setup();
        nvic_enable_irq(NVIC_USART2_IRQ); //Включаем контроллер прерываний для USART
        nvic_set_priority(NVIC_USART2_IRQ, 1); //Опционально, устанавливем приоритет прерывания.
        gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2 | GPIO3);
        gpio_set_af(GPIOA, GPIO_AF7, GPIO2 | GPIO3);
 
        gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO13);
        gpio_set(GPIOC, GPIO13);
 
        usart_set_baudrate(USART2, 9600);
        usart_set_databits(USART2, 8);
        usart_set_stopbits(USART2, USART_STOPBITS_1);
        usart_set_parity(USART2, USART_PARITY_NONE);
        usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
        usart_set_mode(USART2, USART_MODE_TX_RX);
        usart_enable_rx_interrupt(USART2);
        usart_enable(USART2);
        while(1);
}
 
void usart2_isr(void){
}

Микроконтроллер готов обрабатывать поступающие данные.

Теперь можно переключиться на реализацию вспомогательных функций для приема/передачи. Добавим два кольцевых буфера на прием данных и на передачу, а также функции для взаимодействия с ними.

Работа с кольцевыми буферами
#define BUF_SIZE 128
 
uint8_t tx_buffer[BUF_SIZE] = {};
uint8_t rx_buffer[BUF_SIZE] = {};
 
typedef struct {
  uint8_t *data;
  uint32_t size;
  uint32_t head;
  uint32_t tail;
  uint32_t length;
} rb;
 
typedef struct {
        uint8_t cmd_code;
        uint8_t assembling;
} cmd;
 
volatile rb tx_rb = {
  .data = tx_buffer,
  .size = sizeof(tx_buffer),
  .head = 0,
  .tail = 0,
  .length = 0
};
 
volatile rb rx_rb = {
  .data = rx_buffer,
  .size = sizeof(rx_buffer),
  .head = 0,
  .tail = 0,
  .length = 0
};
 
cmd cli_cmd = {
  .cmd_code = 0,
  .assembling =0
};
 
 
static uint8_t rb_push(volatile rb *ring, const uint8_t data)
{
  if (((ring->tail + 1) % ring->size) != ring->head)
  {
    ring->data[ring->tail++] = data;
    ring->tail %= ring->size;
    ring->length++;
    return 1;
  }
  return 0;
}
 
static uint8_t rb_pop(volatile b *ring, uint8_t *data)
{
  if (ring->head != ring->tail)
  {
    *data = ring->data[ring->head++];
    ring->head %= ring->size;
    ring->length--;
    return 1;
  }
  return 0;
}

Парсинг пользовательских команд мы будет выполняться следующим образом: побайтно будем выполнять операцию XOR над входящим байтом и кодом команды. При обнаружении символа конца строки мы интерпретируем это как конец пользовательской команды и анализируем получившийся код команды. В случае небольшого набора пользовательских команд подобный метод позволит избежать посимвольных сравнений.

Парсинг команд
static void parse_cmd(volatile rb *ring, cmd *cli){
        uint8_t data = 0;
        while(rb_pop(ring, &data)){
                if(data == 0x0D){ // \r char
                        cli->assembling = 0;
                        break;
                } else {
                                cli->assembling = 1;
                                cli->cmd_code ^= data;
                }
        }
        if (!cli->assembling){
                switch(cli->cmd_code){
                        case 0x20: // "toggle_led" cmd
                                gpio_toggle(GPIOC, GPIO13);
                                break;
                        case 0x56: // "say_hello" cmd
                                usart_transmit(&tx_rb, "Hello from LibOpenCm3!\n");
                                break;
                        default:
                                break;
                }
                cli->cmd_code = 0;
                cli->assembling = 1;
        }
}

Поместим строку в кольцевой буфер, а затем установим флаг прерывания при передаче.

Функция передачи
static void usart_transmit(volatile rb *ring, const char *str){
        while (*str != '\000') {
                rb_push(ring, (uint8_t)*str);
                str++;
        }
        usart_enable_tx_interrupt(USART2);
}

Теперь наполним обработчик прерываний. В нем сначала определим, по какой причине было вызвано прерывание: по приему или передаче. Также при приеме байта немедленно отправим его обратно, реализуя режим эхо. В случае приема символа \r, генерируемого моей терминальной программой, в ответ отдадим символ новой строки.

Обработчик прерываний
void usart2_isr(void)
{
        uint8_t data = 0;
        if (usart_get_flag(USART2, USART_SR_TXE)) {
                if(rb_pop(&tx_rb, &data)){
                        usart_send(USART2, data);
                } else {
                        usart_disable_tx_interrupt(USART2);
                }
        }
 
        if (usart_get_flag(USART2, USART_SR_RXNE)) {
                data = (uint8_t)usart_recv(USART2);
                rb_push(&rx_rb, data);
                if (data == 0x0D){
                        usart_send(USART2, 0x0A);
                } else {
                        usart_send(USART2, data);
                }
        }
 }

В функции main в цикле мы будем каждую миллисекунду проверять, имеются ли у нас какие-нибудь новые данные в приемном кольцевом буфере. Для задания миллисекундной задержки вновь воспользуемся DWT.

Объединим все написанные элементы вместе, и загрузим код в микроконтроллер.

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/cm3/dwt.h>
 
#include "ex3_2_usart_irq.h"
#define BUF_SIZE 128
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_GPIOC);
        rcc_periph_clock_enable(RCC_AFIO);
        rcc_periph_clock_enable(RCC_USART2);
}
 
 
uint8_t tx_buffer[BUF_SIZE] = {};
uint8_t rx_buffer[BUF_SIZE] = {};
 
typedef struct {
  uint8_t *data;
  uint32_t size;
  uint32_t head;
  uint32_t tail;
  uint32_t length;
} rb;
 
typedef struct {
        uint8_t cmd_code;
        uint8_t assembling;
} cmd;
 
volatile rb tx_rb = {
  .data = tx_buffer,
  .size = sizeof(tx_buffer),
  .head = 0,
  .tail = 0,
  .length = 0
};
 
volatile rb rx_rb = {
  .data = rx_buffer,
  .size = sizeof(rx_buffer),
  .head = 0,
  .tail = 0,
  .length = 0
};
 
cmd cli_cmd = {
  .cmd_code = 0,
  .assembling =0
};
static uint8_t rb_push(volatile rb *ring, const uint8_t data)
{
  if (((ring->tail + 1) % ring->size) != ring->head)
  {
    ring->data[ring->tail++] = data;
    ring->tail %= ring->size;
    ring->length++;
    return 1;
  }
  return 0;
}
 
static uint8_t rb_pop(volatile rb *ring, uint8_t *data)
{
  if (ring->head != ring->tail)
  {
    *data = ring->data[ring->head++];
    ring->head %= ring->size;
    ring->length--;
    return 1;
  }
  return 0;
}
 
static void usart_transmit(volatile rb *ring, const char *str){
        while (*str != '\000') {
                rb_push(ring, (uint8_t)*str);
                str++;
        }
        usart_enable_tx_interrupt(USART2);
}
 
 
static void parse_cmd(volatile rb *ring, cmd *cli){
        uint8_t data = 0;
        while(rb_pop(ring, &data)){
                if(data == 0x0D){ // \r char
                        cli->assembling = 0;
                        break;
                } else {
                                cli->assembling = 1;
                                cli->cmd_code ^= data;
                }
        }
        if (!cli->assembling){
                switch(cli->cmd_code){
                        case 0x20: // "toggle_led" cmd
                                gpio_toggle(GPIOC, GPIO13);
                                break;
                        case 0x56: // "say_hello" cmd
                                usart_transmit(&tx_rb, "Hello from LibOpenCm3!\n");
                                break;
                        default:
                                break;
                }
                cli->cmd_code = 0;
                cli->assembling = 1;
        }
}
 
static uint32_t  dwt_setup(void)
{
        dwt_enable_cycle_counter();
        __asm volatile ("nop");
        __asm volatile ("nop");
        __asm volatile ("nop");
 
     if(dwt_read_cycle_counter())
     {
       return 0;
     }
     else
  {
    return 1;
  }
}
 
static void dwt_delay_us(uint32_t microseconds)
{
        uint32_t initial_ticks = dwt_read_cycle_counter();
        uint32_t us_count_tics = microseconds * (rcc_ahb_frequency / 1000000);
        while ((dwt_read_cycle_counter() - initial_ticks) < us_count_tics);
}
 
 
int main(void)
{
        clock_setup();
        dwt_setup();
        nvic_enable_irq(NVIC_USART2_IRQ); //Включаем контроллер прерываний для USART2
        nvic_set_priority(NVIC_USART2_IRQ, 1); //Опционально, устанавливем приоритет прерывания.
        gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO2);
        gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO3);
 
        gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_10_MHZ,  GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
        gpio_set(GPIOC, GPIO13);
 
 
        usart_set_baudrate(USART2, 9600);
        usart_set_databits(USART2, 8);
        usart_set_stopbits(USART2, USART_STOPBITS_1);
        usart_set_parity(USART2, USART_PARITY_NONE);
        usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
        usart_set_mode(USART2, USART_MODE_TX_RX);
        usart_enable_rx_interrupt(USART2);
        usart_enable(USART2);
 
        usart_transmit(&tx_rb, "Startup\r\n");
 
        while(1){
                dwt_delay_us(1000);
                if(rx_rb.length > 0){
                        parse_cmd(&rx_rb, &cli_cmd);
                }
        }
}
void usart2_isr(void){
                uint8_t data = 0;
        if (usart_get_flag(USART2, USART_SR_TXE)) {
                if(rb_pop(&tx_rb, &data)){
                        usart_send(USART2, data);
                } else {
                        usart_disable_tx_interrupt(USART2);
                }
        }
 
        if (usart_get_flag(USART2, USART_SR_RXNE)) {
                data = (uint8_t)usart_recv(USART2);
                rb_push(&rx_rb, data);
                if (data == 0x0D){
                        usart_send(USART2, 0x0A);
                } else {
                        usart_send(USART2, data);
                }
        }
}

STM32F4
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/cm3/dwt.h>
#include "ex3_2_usart_irq.h"
#define BUF_SIZE 128
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_96MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_GPIOC);
        rcc_periph_clock_enable(RCC_USART2);
}
 
uint8_t tx_buffer[BUF_SIZE] = {};
uint8_t rx_buffer[BUF_SIZE] = {};
 
typedef struct {
  uint8_t *data;
  uint32_t size;
  uint32_t head;
  uint32_t tail;
  uint32_t length;
} rb;
 
typedef struct {
        uint8_t cmd_code;
        uint8_t assembling;
} cmd;
 
volatile rb tx_rb = {
  .data = tx_buffer,
  .size = sizeof(tx_buffer),
  .head = 0,
  .tail = 0,
  .length = 0
};
 
volatile rb rx_rb = {
  .data = rx_buffer,
  .size = sizeof(rx_buffer),
  .head = 0,
  .tail = 0,
  .length = 0
};
 
cmd cli_cmd = {
  .cmd_code = 0,
  .assembling =0
};
 
static uint8_t rb_push(volatile rb *ring, const uint8_t data)
{
  if (((ring->tail + 1) % ring->size) != ring->head)
  {
    ring->data[ring->tail++] = data;
    ring->tail %= ring->size;
    ring->length++;
    return 1;
  }
  return 0;
}
static uint8_t rb_pop(volatile rb *ring, uint8_t *data)
{
  if (ring->head != ring->tail)
  {
    *data = ring->data[ring->head++];
    ring->head %= ring->size;
    ring->length--;
    return 1;
  }
  return 0;
}
 
static void usart_transmit(volatile rb *ring, const char *str){
        while (*str != '\000') {
                rb_push(ring, (uint8_t)*str);
                str++;
        }
        usart_enable_tx_interrupt(USART2);
}
 
 
static void parse_cmd(volatile rb *ring, cmd *cli){
        uint8_t data = 0;
        while(rb_pop(ring, &data)){
                if(data == 0x0D){ // \r char
                        cli->assembling = 0;
                        break;
                } else {
                                cli->assembling = 1;
                                cli->cmd_code ^= data;
                }
        }
        if (!cli->assembling){
                switch(cli->cmd_code){
                        case 0x20: // "toggle_led" cmd
                                gpio_toggle(GPIOC, GPIO13);
                                break;
                        case 0x56: // "say_hello" cmd
                                usart_transmit(&tx_rb, "Hello from LibOpenCm3!\n");
                                break;
                        default:
                                break;
                }
                cli->cmd_code = 0;
                cli->assembling = 1;
        }
}
 
static uint32_t  dwt_setup(void)
{
        dwt_enable_cycle_counter();
        __asm volatile ("nop");
        __asm volatile ("nop");
        __asm volatile ("nop");
 
     if(dwt_read_cycle_counter())
     {
       return 0;
     }
     else
  {
    return 1;
  }
}
static void dwt_delay_us(uint32_t microseconds)
{
        uint32_t initial_ticks = dwt_read_cycle_counter();
        uint32_t us_count_tics = microseconds * (rcc_ahb_frequency / 1000000);
        while ((dwt_read_cycle_counter() - initial_ticks) < us_count_tics);
}
 
int main(void)
{
        clock_setup();
        dwt_setup();
        nvic_enable_irq(NVIC_USART2_IRQ); //Включаем контроллер прерываний для USART
        nvic_set_priority(NVIC_USART2_IRQ, 1); //Опционально, устанавливем приоритет прерывания.
        gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2 | GPIO3);
        gpio_set_af(GPIOA, GPIO_AF7, GPIO2 | GPIO3);
 
        gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO13);
        gpio_set(GPIOC, GPIO13);
 
        usart_set_baudrate(USART2, 9600);
        usart_set_databits(USART2, 8);
        usart_set_stopbits(USART2, USART_STOPBITS_1);
        usart_set_parity(USART2, USART_PARITY_NONE);
        usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
        usart_set_mode(USART2, USART_MODE_TX_RX);
        usart_enable_rx_interrupt(USART2);
        usart_enable(USART2);
 
        usart_transmit(&tx_rb, "Startup\r\n");
 
        while(1){
                dwt_delay_us(1000);
                if(rx_rb.length > 0){
                        parse_cmd(&rx_rb, &cli_cmd);
                }
        }
}
 
void usart2_isr(void){
        uint8_t data = 0;
        if (usart_get_flag(USART2, USART_SR_TXE)) {
                if(rb_pop(&tx_rb, &data)){
                        usart_send(USART2, data);
                } else {
                        usart_disable_tx_interrupt(USART2);
                }
        }
 
        if (usart_get_flag(USART2, USART_SR_RXNE)) {
                data = (uint8_t)usart_recv(USART2);
                rb_push(&rx_rb, data);
                if (data == 0x0D){
                        usart_send(USART2, 0x0A);
                } else {
                        usart_send(USART2, data);
                }
        }
}

Подключаем и проверяем

$ cu -l /dev/ttyUSB0 -s 9600
Connected.
Startup
say_hello
Hello from LibOpenCm3!
toggle_led

Четвёртый проект: Использование I2C

Идем далее, и у нас на очереди следующий протокол для взаимодействия с оборудованием: I2C.

Цель: Вывести на популярный LCD экран 1602, подключенным через модуль расширитель GPIO портов PCF8574.

Генерируем проект:

./make_project.sh ex4_i2c
  • Подключаем заголовочные файлы относящиеся в I2C

  • Включаем тактирование порта, на котором расположены пины, тактирование блока AFIO и блока I2C

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/i2c.h>
#include "ex4_i2c.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
        rcc_periph_clock_enable(RCC_GPIOB);
        rcc_periph_clock_enable(RCC_AFIO);
        rcc_periph_clock_enable(RCC_I2C1);
}
 
int main(void)
{
        clock_setup();
        gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ , GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN , GPIO8 | GPIO9);
        gpio_primary_remap(AFIO_MAPR_SWJ_CFG_FULL_SWJ, AFIO_MAPR_I2C1_REMAP);
 
        i2c_peripheral_disable(I2C1);
        i2c_set_own_7bit_slave_address(I2C1, 0x00);
        i2c_set_speed(I2C1, i2c_speed_sm_100k, rcc_apb1_frequency/1000000);
        i2c_peripheral_enable(I2C1);
        while(1){
 
        }
}

STM32F4
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/i2c.h>
#include "ex4_i2c.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_96MHZ]);
        rcc_periph_clock_enable(RCC_GPIOB);
        rcc_periph_clock_enable(RCC_I2C1);
}
int main(void)
{
        clock_setup();
        gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO8 | GPIO9);
        gpio_set_output_options(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO8 | GPIO9);
        gpio_set_af(GPIOB, GPIO_AF4, GPIO8 | GPIO9);
 
        i2c_peripheral_disable(I2C1);
        i2c_set_own_7bit_slave_address(I2C1, 0x00);
        i2c_set_speed(I2C1, i2c_speed_sm_100k, rcc_apb1_frequency/1000000);
        i2c_peripheral_enable(I2C1);
        while(1){
 
        }
}

Наш микроконтроллер готов к передаче данных черех I2C. Для передачи данных воспользуемся библиотечной функцией i2c_transfer7(). Реализуем служебные функции для работы с дисплеем 1602, используя её.

#define LCD1602_ADDR 0x3F
#define LCD1602_BACKLIGHT 0x08
#define LCD1602_ENABLE 0x04
#define LCD1602_WIDTH 20   // Maximum characters per line
#define LCD1602_CHR  1 // Mode - Sending data
#define LCD1602_CMD  0 // Mode - Sending command
#define LCD1602_LINE_1  0x80 // LCD RAM address for the 1st line
#define LCD1602_LINE_2  0xC0 // LCD RAM address for the 2nd lin
 
static void lcd1602_write_byte(uint32_t i2c, uint8_t byte, uint8_t flag){
        uint8_t transaction[6] = {0};
        transaction[0] = (flag | (byte & 0xF0) | LCD1602_BACKLIGHT);
        transaction[1] = ((flag | (byte & 0xF0) | LCD1602_BACKLIGHT) | LCD1602_ENABLE);
        transaction[2] = ((flag | (byte & 0xF0) | LCD1602_BACKLIGHT) & ~LCD1602_ENABLE);
 
        transaction[3] = (flag | ((byte<<4) & 0xF0) | LCD1602_BACKLIGHT);
        transaction[4] = ((flag | ((byte<<4) & 0xF0) | LCD1602_BACKLIGHT) | LCD1602_ENABLE );
        transaction[5] = ((flag | ((byte<<4) & 0xF0) | LCD1602_BACKLIGHT) & ~LCD1602_ENABLE);
 
        for(uint8_t i = 0; i < 6; i++){
                i2c_transfer7(i2c, LCD1602_ADDR, &transaction[i], 1, NULL, 0);
        }
}
 
static void lcd1602_init(uint32_t i2c){
        lcd1602_write_byte(i2c, 0x33, LCD1602_CMD); // 110011 Initialise
        lcd1602_write_byte(i2c, 0x32, LCD1602_CMD); // 110010 Initialise
        lcd1602_write_byte(i2c, 0x06, LCD1602_CMD); // 000110 Cursor move direction
        lcd1602_write_byte(i2c, 0x0C, LCD1602_CMD); // 001100 Display On,Cursor Off, Blink Off
        lcd1602_write_byte(i2c, 0x28, LCD1602_CMD); // 101000 Data length, number of lines, font size
        lcd1602_write_byte(i2c, 0x01, LCD1602_CMD); // 000001 Clear display
}

Теперь комбинируем все части в единое целое.

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/i2c.h>
#include "ex4_i2c.h"
 
#define LCD1602_ADDR 0x3F
#define LCD1602_BACKLIGHT 0x08
#define LCD1602_ENABLE 0x04
#define LCD1602_WIDTH 20   // 20 символов на линию
#define LCD1602_CHR  1 // Режим отправки данных
#define LCD1602_CMD  0 // Режим отправки команд
#define LCD1602_LINE_1  0x80 // LCD RAM адрес первой строки
#define LCD1602_LINE_2  0xC0 // LCD RAM адрес второй строки
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
        rcc_periph_clock_enable(RCC_GPIOB);
        rcc_periph_clock_enable(RCC_AFIO);
        rcc_periph_clock_enable(RCC_I2C1);
}
 
static void lcd1602_write_byte(uint32_t i2c, uint8_t byte, uint8_t flag){
        uint8_t transaction[6] = {0};
        transaction[0] = (flag | (byte & 0xF0) | LCD1602_BACKLIGHT);
        transaction[1] = ((flag | (byte & 0xF0) | LCD1602_BACKLIGHT) | LCD1602_ENABLE);
        transaction[2] = ((flag | (byte & 0xF0) | LCD1602_BACKLIGHT) & ~LCD1602_ENABLE);
 
        transaction[3] = (flag | ((byte<<4) & 0xF0) | LCD1602_BACKLIGHT);
        transaction[4] = ((flag | ((byte<<4) & 0xF0) | LCD1602_BACKLIGHT) | LCD1602_ENABLE );
        transaction[5] = ((flag | ((byte<<4) & 0xF0) | LCD1602_BACKLIGHT) & ~LCD1602_ENABLE);
 
        for(uint8_t i = 0; i < 6; i++){
                i2c_transfer7(i2c, LCD1602_ADDR, &transaction[i], 1, NULL, 0);
        }
}
 
static void lcd1602_init(uint32_t i2c){
        lcd1602_write_byte(i2c, 0x33, LCD1602_CMD); // 110011 Initialise
        lcd1602_write_byte(i2c, 0x32, LCD1602_CMD); // 110010 Initialise
        lcd1602_write_byte(i2c, 0x06, LCD1602_CMD); // 000110 Cursor move direction
        lcd1602_write_byte(i2c, 0x0C, LCD1602_CMD); // 001100 Display On,Cursor Off, Blink Off
        lcd1602_write_byte(i2c, 0x28, LCD1602_CMD); // 101000 Data length, number of lines, font size
        lcd1602_write_byte(i2c, 0x01, LCD1602_CMD); // 000001 Clear display
}
 
int main(void)
{
        clock_setup();
        gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ , GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN , GPIO8 | GPIO9);
        gpio_primary_remap(AFIO_MAPR_SWJ_CFG_FULL_SWJ, AFIO_MAPR_I2C1_REMAP);
 
        i2c_peripheral_disable(I2C1);
        i2c_set_own_7bit_slave_address(I2C1, 0x00);
        i2c_set_speed(I2C1, i2c_speed_sm_100k, rcc_apb1_frequency/1000000);
        i2c_peripheral_enable(I2C1);
 
        lcd1602_init(I2C1);
        lcd1602_write_byte(I2C1, ((LCD1602_LINE_1) | ((0x00) & 0x0f)), LCD1602_CMD);
 
        const char msg[15] = "Hi!";
        lcd1602_write_byte(I2C1, (uint8_t)msg[0], LCD1602_CHR);
        lcd1602_write_byte(I2C1, (uint8_t)msg[1], LCD1602_CHR);
        lcd1602_write_byte(I2C1, (uint8_t)msg[2], LCD1602_CHR);
        while(1){
 
        }
}

STM32F4
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/i2c.h>
#include "ex4_i2c.h"
 
#define LCD1602_ADDR 0x3F
#define LCD1602_BACKLIGHT 0x08
#define LCD1602_ENABLE 0x04
#define LCD1602_WIDTH 20   // 20 символов на линию
#define LCD1602_CHR  1 // Режим отправки данных
#define LCD1602_CMD  0 // Режим отправки команд
#define LCD1602_LINE_1  0x80 // LCD RAM адрес первой строки
#define LCD1602_LINE_2  0xC0 // LCD RAM адрес второй строки
 
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_96MHZ]);
        rcc_periph_clock_enable(RCC_GPIOB);
        rcc_periph_clock_enable(RCC_I2C1);
}
 
static void lcd1602_write_byte(uint32_t i2c, uint8_t byte, uint8_t flag){
        uint8_t transaction[6] = {0};
        transaction[0] = (flag | (byte & 0xF0) | LCD1602_BACKLIGHT);
        transaction[1] = ((flag | (byte & 0xF0) | LCD1602_BACKLIGHT) | LCD1602_ENABLE);
        transaction[2] = ((flag | (byte & 0xF0) | LCD1602_BACKLIGHT) & ~LCD1602_ENABLE);
 
        transaction[3] = (flag | ((byte<<4) & 0xF0) | LCD1602_BACKLIGHT);
        transaction[4] = ((flag | ((byte<<4) & 0xF0) | LCD1602_BACKLIGHT) | LCD1602_ENABLE );
        transaction[5] = ((flag | ((byte<<4) & 0xF0) | LCD1602_BACKLIGHT) & ~LCD1602_ENABLE);
 
        for(uint8_t i = 0; i < 6; i++){
                i2c_transfer7(i2c, LCD1602_ADDR, &transaction[i], 1, NULL, 0);
        }
}
 
static void lcd1602_init(uint32_t i2c){
        lcd1602_write_byte(i2c, 0x33, LCD1602_CMD); // 110011 Initialise
        lcd1602_write_byte(i2c, 0x32, LCD1602_CMD); // 110010 Initialise
        lcd1602_write_byte(i2c, 0x06, LCD1602_CMD); // 000110 Cursor move direction
        lcd1602_write_byte(i2c, 0x0C, LCD1602_CMD); // 001100 Display On,Cursor Off, Blink Off
        lcd1602_write_byte(i2c, 0x28, LCD1602_CMD); // 101000 Data length, number of lines, font size
        lcd1602_write_byte(i2c, 0x01, LCD1602_CMD); // 000001 Clear display
}
 
int main(void)
{
        clock_setup();
        gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO8 | GPIO9);
        gpio_set_output_options(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO8 | GPIO9);
        gpio_set_af(GPIOB, GPIO_AF4, GPIO8 | GPIO9);
 
        i2c_peripheral_disable(I2C1);
        i2c_set_own_7bit_slave_address(I2C1, 0x00);
        i2c_set_speed(I2C1, i2c_speed_sm_100k, rcc_apb1_frequency/1000000);
        i2c_peripheral_enable(I2C1);
 
        lcd1602_init(I2C1);
        lcd1602_write_byte(I2C1, ((LCD1602_LINE_1) | ((0x00) & 0x0f)), LCD1602_CMD);
 
        const char msg[15] = "Hi!";
        lcd1602_write_byte(I2C1, (uint8_t)msg[0], LCD1602_CHR);
        lcd1602_write_byte(I2C1, (uint8_t)msg[1], LCD1602_CHR);
        lcd1602_write_byte(I2C1, (uint8_t)msg[2], LCD1602_CHR);
        while(1){
 
        }
}

Прошиваем и подключаем экранчик.

Подключение расширителя GPIO PCF8574 к микроконтроллеру на примере BluePill. Обвязка PCF8574 и подключенный к ней индикатор опущена
Подключение расширителя GPIO PCF8574 к микроконтроллеру на примере BluePill. Обвязка PCF8574 и подключенный к ней индикатор опущена

На нем мы должны будем увидеть сообщение «Hi!».

Пятый проект: Таймеры

Любому, кто имел дело с программированием микроконтроллеров, известно, что таймеры – наиболее часто используемое оборудование. Их используют для создания временных задержек, генерации PWM сигналов, вызова регулярных процедур, взаимодействия с энкодерами и многого другого.

В этой части мы рассмотрим три примера использования таймера:

  • Таймер для вызова внешних процедур

  • Генерация PWM сигнала

  • Считывание состояния энкодера

Таймер для регулярного вызова функций

Цель: настроить таймер так, чтобы он при помощи прерывания моргал светодиодом на плате с частотой 100 Герц.

  • Подключаем заголовочные файлы для работы с таймером, GPIO, и NVIC для прерываний;

  • Настраиваем ножку PC13 на выход;

  • Настраиваем делитель тактового сигнала (который в нашем случае равен 72 Мгц для STM32F1 и 96 МГц для STM32F4) для таймера TIM3, чтобы получить частоту счета 2 Мгц;

  • Далее задаем период в 10000, что вызовет переполнение таймера с частотой 200 Герц;

  • Активируем вызов прерывания по переполнению;

  • В обработчике прерывания переключим состояние пина PC13, подключенного к светодиоду.

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/timer.h>
#include <libopencm3/cm3/nvic.h>
#include "ex5_1_timer.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
        rcc_periph_clock_enable(RCC_GPIOC);
        rcc_periph_clock_enable(RCC_TIM3);
}
 
int main(void)
{
        clock_setup();
 
        gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
 
        rcc_periph_reset_pulse(RST_TIM3);
        timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
        timer_continuous_mode(TIM3);
        timer_set_prescaler(TIM3, 36-1); //72 Mhz / 36 = 2 Mhz
        timer_set_period(TIM3, 10000-1); // 2 Mhz / 10000 = 200 Hz
        timer_disable_preload(TIM3);
        timer_enable_irq(TIM3, TIM_DIER_UIE);
        timer_enable_counter(TIM3);
        nvic_enable_irq(NVIC_TIM3_IRQ);
 
        while(1){
        }
}
 
void tim3_isr(){
        if (timer_interrupt_source(TIM3, TIM_SR_UIF)) {
                timer_clear_flag(TIM3, TIM_SR_UIF);
                gpio_toggle(GPIOC, GPIO13);
        }
}

STM32F4
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/timer.h>
#include <libopencm3/cm3/nvic.h>
#include "ex5_1_timer.h"

static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_96MHZ]);
        rcc_periph_clock_enable(RCC_GPIOC);
        rcc_periph_clock_enable(RCC_TIM3);
}

int main(void)
{
        clock_setup();

        gpio_mode_setup(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO13);

        rcc_periph_reset_pulse(RST_TIM3);
        timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
        timer_continuous_mode(TIM3);
        timer_set_prescaler(TIM3, 48-1); //96 Mhz / 48 = 2 Mhz
        timer_set_period(TIM3, 10000-1); // 2 Mhz / 10000 = 200 Hz
        timer_disable_preload(TIM3);
        timer_enable_irq(TIM3, TIM_DIER_UIE);
        timer_enable_counter(TIM3);
        nvic_enable_irq(NVIC_TIM3_IRQ);

        while(1){
        }
}

void tim3_isr(){
        if (timer_interrupt_source(TIM3, TIM_SR_UIF)) {
                timer_clear_flag(TIM3, TIM_SR_UIF);
                gpio_toggle(GPIOC, GPIO13);
        }
}
Логический анализатор подключенный к ножке PC13
Логический анализатор подключенный к ножке PC13

Генерация PWM сигнала

Цель: настроить таймер так, чтобы он переключал вывод микроконтроллера с частотой 100 Герц и скважностью 0.25.

Действия:

  • Подключаем заголовочные файлы для работы с таймером, GPIO;

  • Настраиваем ножку PA6 как альтернативную функцию;

  • Настраиваем делитель тактового сигнала (который в нашем случае равен 72 Мгц для STM32F1 и 96 МГц для STM32F4) для таймера TIM3, чтобы получить частоту счета 2 Мгц;

  • Задаем период в 20000, что вызовет переполнение таймера с частотой 100 Герц;

  • Активируем режим PWM1;

  • Задаем значение, при достижении которого произойдет событие. В нашем случае 5000;

  • Включаем генерацию событий по сравнению с регистром OC1;

  • Включаем сам счетчик.

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/timer.h>
#include "ex5_2_timer.h"
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_TIM3);
}
 
int main(void)
{
        clock_setup();
 
        gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO6);
 
        rcc_periph_reset_pulse(RST_TIM3);
        timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
        timer_continuous_mode(TIM3);
        timer_set_prescaler(TIM3, 36-1); //72 Mhz / 36 = 2 Mhz
        timer_set_period(TIM3, 20000-1); // 2 Mhz / 20000 = 100 Hz
 
        timer_set_oc_mode(TIM3, TIM_OC1, TIM_OCM_PWM1);
        timer_set_oc_value(TIM3, TIM_OC1, 5000-1);
        timer_enable_oc_output(TIM3, TIM_OC1);
 
        timer_set_counter(TIM3, 0);
        timer_enable_preload(TIM3);
        timer_enable_counter(TIM3);
 
        while(1){
        }
}

STM32F4
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/timer.h>
#include "ex5_2_timer.h"

static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_96MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_TIM3);
}

int main(void)
{
        clock_setup();

        gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO6);
        gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO6);
        gpio_set_af(GPIOA, GPIO_AF2, GPIO6);

        rcc_periph_reset_pulse(RST_TIM3);
        timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
        timer_continuous_mode(TIM3);
        timer_set_prescaler(TIM3, 48-1); //96 Mhz / 48 = 2 Mhz
        timer_set_period(TIM3, 20000-1); // 2 Mhz / 20000 = 100 Hz

        timer_set_oc_mode(TIM3, TIM_OC1, TIM_OCM_PWM1);
        timer_set_oc_value(TIM3, TIM_OC1, 5000-1);
        timer_enable_oc_output(TIM3, TIM_OC1);

        timer_set_counter(TIM3, 0);
        timer_enable_preload(TIM3);
        timer_enable_counter(TIM3);
        while(1);
}
Логический анализатор подключенный к пину PA6
Логический анализатор подключенный к пину PA6

Считывание состояния энкодера

Цель: Подключить энкодер к микроконтроллеру. Энкодер должен изменять свое значение от 0 до 2000, при изменении своего значения он должен вывести свое состояние в USART.

Действия:

  • Настроить порты на нужные альтернативные функции;

  • Задать таймеру период равный "число шагов * 4". Это необходимо, поскольку у распространенных ардуино-модулей поворотного энкодера один физический "клик" при повороте порождает увеличение значения на 4. Это же мы учтем при получении значения от таймера, произведя сдвиг результата на 2 бита вправо для деления на 4;

  • Перевести таймер в "slave mode", режим энкодера;

  • Для борьбы с дребезгом контактов вы можете включить фильтрацию сигналов от энкодера при помощи функции timer_ic_set_filter();

  • Затем включаем входы TIM_IC1 и TIM_IC2.

STM32F1
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/timer.h>
#include <libopencm3/stm32/usart.h>
 
#include "ex5_3_timer.h"
 
uint32_t current_value = 0;
 
static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_AFIO);
        rcc_periph_clock_enable(RCC_USART2);
        rcc_periph_clock_enable(RCC_TIM1);
}
 
 
static uint32_t rotary_encoder_tim1_get_value(void){
    return (timer_get_counter(TIM1) >> 2);
}
 
static void usart_transmit(uint32_t number){
        uint16_t div = 1000;
        while (div > 0) {
                usart_send_blocking(USART2, ((uint8_t)(number / div) + 0x30));
                number = number % div;
                div /= 10;
        }
 
        usart_send_blocking(USART2, 0x0D);
        usart_send_blocking(USART2, 0x0A);
}
 
int main(void)
{
        uint32_t max_value = 2000;
        clock_setup();
        gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO8 | GPIO9);
        gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO2);
 
        timer_set_period(TIM1, max_value*4 );
        timer_slave_set_mode(TIM1, TIM_SMCR_SMS_EM3);
        timer_ic_set_filter(TIM1, TIM_IC1,  TIM_IC_DTF_DIV_32_N_8 );
        timer_ic_set_filter(TIM1, TIM_IC2,  TIM_IC_DTF_DIV_32_N_8 );
        timer_ic_set_input(TIM1, TIM_IC1, TIM_IC_IN_TI1);
        timer_ic_set_input(TIM1, TIM_IC2, TIM_IC_IN_TI2);
        timer_set_counter(TIM1, 0);
        timer_enable_update_event(TIM1);
        timer_enable_counter(TIM1);
 
        usart_set_baudrate(USART2, 9600);
        usart_set_databits(USART2, 8);
        usart_set_stopbits(USART2, USART_STOPBITS_1);
        usart_set_parity(USART2, USART_PARITY_NONE);
        usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
        usart_set_mode(USART2, USART_MODE_TX);
        usart_enable(USART2);
 
        while(1){
                if(rotary_encoder_tim1_get_value() != current_value){
                        current_value =  rotary_encoder_tim1_get_value();
                        usart_transmit(current_value);
                }
        }
 
}

STM32F4
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/timer.h>
#include <libopencm3/stm32/usart.h>
#include "ex5_3_timer.h"

uint32_t current_value = 0;

static void clock_setup(void)
{
        rcc_clock_setup_pll(&rcc_hse_25mhz_3v3[RCC_CLOCK_3V3_96MHZ]);
        rcc_periph_clock_enable(RCC_GPIOA);
        rcc_periph_clock_enable(RCC_USART2);
        rcc_periph_clock_enable(RCC_TIM1);
}

static uint32_t rotary_encoder_tim1_get_value(void){
    return (timer_get_counter(TIM1) >> 2);
}

static void usart_transmit(uint32_t number){
        uint16_t div = 1000;
        while (div > 0) {
                usart_send_blocking(USART2, ((uint8_t)(number / div) + 0x30));
                number = number % div;
                div /= 10;
        }

        usart_send_blocking(USART2, 0x0D);
        usart_send_blocking(USART2, 0x0A);
}



int main(void)
{
        uint32_t max_value = 2000;

        clock_setup();

        gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO8 | GPIO9);
        gpio_set_af(GPIOA, GPIO_AF1, GPIO8 | GPIO9);

        gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2);
        gpio_set_af(GPIOA, GPIO_AF7, GPIO2);

        timer_set_period(TIM1, max_value*4 );
        timer_slave_set_mode(TIM1, TIM_SMCR_SMS_EM3);
        timer_ic_set_filter(TIM1, TIM_IC1,  TIM_IC_DTF_DIV_32_N_8 );
        timer_ic_set_filter(TIM1, TIM_IC2,  TIM_IC_DTF_DIV_32_N_8 );
        timer_ic_set_input(TIM1, TIM_IC1, TIM_IC_IN_TI1);
        timer_ic_set_input(TIM1, TIM_IC2, TIM_IC_IN_TI2);
        timer_set_counter(TIM1, 0);
        timer_enable_update_event(TIM1);
        timer_enable_counter(TIM1);

        usart_set_baudrate(USART2, 9600);
        usart_set_databits(USART2, 8);
        usart_set_stopbits(USART2, USART_STOPBITS_1);
        usart_set_parity(USART2, USART_PARITY_NONE);
        usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
        usart_set_mode(USART2, USART_MODE_TX);
        usart_enable(USART2);

        while(1){
                if(rotary_encoder_tim1_get_value() != current_value){
                        current_value =  rotary_encoder_tim1_get_value();
                        usart_transmit(current_value);
                }
        }
}
Подключение BluePill платы к энкодеру с выводом результата в USART. Подтягивающие резисторы обычно уже устанвлены на модулях энкодера.
Подключение BluePill платы к энкодеру с выводом результата в USART. Подтягивающие резисторы обычно уже установлены на модулях энкодера.

$ cu -l /dev/ttyUSB0 -s 9600
Connected.
2000
1999
1998
1999
2000
0000
0001
0002

На этом мы закончим наш вводный курс в libopencm3. Надеюсь, мои старания внесли небольшой вклад в популяризацию данной библиотеки. Я буду рад, если на GitHub станет больше разнообразных проектов, которые будут использовать libopencm3.

Еще раз напомню, что исходный код всех примеров доступен на Github в репозитории.

Спасибо за уделенное время! Желаю всем осилившим эту серию статей побольше интересных проектов. Как хоббийных, так и коммерческих.

Комментарии (0)