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

от автора

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

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


ссылка на оригинал статьи https://habr.com/ru/articles/761612/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *