Часть 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.
Открываем терминальную программу, и подключаемся к символьному устройству переходника.
$ 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){ } }
Прошиваем и подключаем экранчик.
На нем мы должны будем увидеть сообщение «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); } }
Генерация 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); }
Считывание состояния энкодера
Цель: Подключить энкодер к микроконтроллеру. Энкодер должен изменять свое значение от 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); } } }
$ 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/
Добавить комментарий