Библиотека libopencm3: Быстрый старт (Часть 3). Работа с USART, прерываниями, I2C и таймерами

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Часть 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 series, F4 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 в репозитории.

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

Источник: https://habr.com/ru/companies/auriga/articles/761612/


Интересные статьи

Интересные статьи

XGBoost и другие методы на основе дерева решений, обучающие модели при помощи градиентного подъема, принимают решение через сравнение, тогда как определить оператор сравнения категорий математически —...
Всем привет, меня зовут Дарья Чернышева, я инженер по обеспечению качества команды RuScanner.В этом посте я расскажу про процессы на проекте и о том, как мы при помощи специальных отладочных инструмен...
Столкнувшись с проблемой не работающего звука при установке Windows на свой MacBook 11го года обнаружил, что материала по данной теме в русскоязычном сегменте интернета крайне мало, можно сказать что ...
SELinux, как и все средства безопасности, снижает производительность системы. И хотя для большинства рабочих нагрузок такое влияние невелико (см., например, проведенное  порталом Phoronix тестиро...
Источник Вот уже несколько лет идет развитие рынка доставки товаров беспилотниками. Медленно, но уверенно эта сфера движется вперед. На днях стало известно, что немецкая компания разр...