Часть 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 в репозитории.
Спасибо за уделенное время! Желаю всем осилившим эту серию статей побольше интересных проектов. Как хоббийных, так и коммерческих.