STM8L-Универсальный асинхронный приёмопередатчик (UART)
К сожалению обещанный второй компаратор я решил пропустить, да и с индикатором от Nokia 1100 вышла неувязка, решил передавать данные с АЦП в компьютер, поэтому сегодня кратенько об универсальном приёмопередатчике (USART).
Модуль универсального приемопередатчика (USART)
Возможности модуля:
- двухпроводный полно-дуплексный асинхронный обмен
- однопроводной полудуплексный обмен
- IrDA кодер-декодер
- максимальная скорость обмена SYSCLK/16
- два вектора прерываний (прием, передача), восемь источников прерываний
- мульти-процессорное взаимодействие (возможность адресации)
- и как обычно ещё много полезного :) …
Функциональная схема модуля:
Я думаю начать нужно с самого простого. Попробуем сконфигурировать модуль на передачу данных в ПК и передать пару байт.
Разрешаем тактирование модуля:
PCKENR1 |= CLK_PCKENR1_USART1;
Вы не поверите, но для “стандартного” режима обмена (восемь бит данных, один стоп бит и т.д.) достаточно разрешить работу передатчика и задать скорость обмена:
USART1->CR1 = 0;
USART1->CR3 = 0;
USART1->CR4 = 0;
USART1->CR5 = 0;
USART1->CR2 = USART_CR2_TEN;
Скорость обмена задается очень просто, нужно знать лишь частоту тактирования:
пример из документации:
Примечание: не понял, за чем такие хитрые перестановки
В моем случае тактовая частота равна 2 МГц, а желаемая скорость обмена 38400 бод, следовательно:
2000000 / 38400 = 52,083(3)
полученное значение больше 16, следовательно данная скорость обмена реализуема, округлим до целого, при этом погрешность составит 0,16% без учета погрешности тактового RC-генератора:
52d = 0034h
USART1->BRR2 = 0x04;
USART1->BRR1 = 0x03;
или можно “автоматизировать” процесс:
//------------------------------------------------------------------------------
void mcu_usart_init(uint16_t divider)
{
CLK->PCKENR1 |= CLK_PCKENR1_USART1;
(void) USART1->SR;
(void) USART1->DR;
USART1->BRR2 = (uint8_t) (((divider >> 8) & 0xF0) | (divider & 0x0F));
USART1->BRR1 = (uint8_t) (divider >> 4);
USART1->CR1 = 0;
USART1->CR3 = 0;
USART1->CR4 = 0;
USART1->CR5 = 0;
USART1->CR2 = USART_CR2_TEN;
}
Ну вот теперь можно “инициализировать” и передать байт:
USART1->DR = 0xAA;
while(!(USART1->SR & USART_SR_TC))
{
}
Прием организуется аналогично. Однако использовать данный метод передачи не всегда удобно, иногда нужно передавать и принимать без “ожидания” для этого можно воспользоваться прерываниями.
Не хочу вдаваться в подробности реализации алгоритма FIFO буфера, поэтому просто приведу код.
Передача:
void mcu_usart_fifo_transmit(uint8_t data)
{
uint8_t tx_head;
tx_head = (uint8_t) ((usart_fifo.tx_buffer_head + 1)
& (uint8_t) (USART_TX_BUFFER_SIZE - 1));
while (tx_head == usart_fifo.tx_buffer_tail)
{
}
usart_fifo.tx_buffer[tx_head] = data;
usart_fifo.tx_buffer_head = tx_head;
USART1->CR2 |= USART_CR2_TIEN;
}
обработчик прерывания:
@far @interrupt void handler_usart1_tx(void)
{
uint8_t status;
uint8_t tx_tail;
uint8_t tx_head;
status = USART1->SR;
if (status & USART_SR_TC)
{
USART1->SR &= (uint8_t) (~USART_SR_TC);
}
if (status & USART_SR_TXE)
{
tx_head = usart_fifo.tx_buffer_head;
tx_tail = usart_fifo.tx_buffer_tail;
if (tx_head != tx_tail++)
{
tx_tail &= USART_TX_BUFFER_SIZE - 1;
usart_fifo.tx_buffer_tail = tx_tail;
USART1->DR = usart_fifo.tx_buffer[tx_tail];
}
else
{
USART1->CR2 &= (uint8_t) (~USART_CR2_TIEN);
}
}
}
Прием:
uint8_t mcu_usart_fifo_receive(uint8_t * data)
{
uint8_t rx_head;
uint8_t rx_tail;
rx_head = usart_fifo.rx_buffer_head;
if (rx_head == usart_fifo.rx_buffer_tail)
return USART_FIFO_NO_DATA;
rx_tail = (uint8_t) ((usart_fifo.rx_buffer_tail + 1)
& (uint8_t) (USART_RX_BUFFER_SIZE - 1));
usart_fifo.rx_buffer_tail = rx_tail;
*data = usart_fifo.rx_buffer[rx_tail];
return usart_fifo.rx_last_error;
}
обработчик прерывания:
@far @interrupt void handler_usart1_rx(void)
{
uint8_t status;
uint8_t rx_data;
uint8_t rx_last_error;
uint8_t rx_head;
status = USART1->SR;
rx_data = USART1->DR;
rx_last_error = 0;
if (status & USART_SR_FE)
rx_last_error |= USART_FIFO_ERROR_FRAME;
if (status & USART_SR_OR)
rx_last_error |= USART_FIFO_ERROR_OVERRUN;
if (status & USART_SR_NF)
rx_last_error |= USART_FIFO_ERROR_NOISE;
rx_head = (uint8_t) ((usart_fifo.rx_buffer_head + 1)
& (uint8_t) (USART_RX_BUFFER_SIZE - 1));
if (rx_head == usart_fifo.rx_buffer_tail)
{
rx_last_error |= USART_FIFO_ERROR_BUFFER_OVERFLOW;
}
else
{
usart_fifo.rx_buffer_head = rx_head;
usart_fifo.rx_buffer[rx_head] = rx_data;
}
usart_fifo.rx_last_error = rx_last_error;
}
Размер буферов для приема и передачи должен быть кратен двум, но при желании его можно поменять с соответствующим изменением кода.
#define USART_TX_BUFFER_SIZE 16 // кратен двум
#define USART_RX_BUFFER_SIZE 16 // кратен двум
enum usart_fifo_error
{
USART_FIFO_ERROR_FRAME = BIT(0),
USART_FIFO_ERROR_OVERRUN = BIT(1),
USART_FIFO_ERROR_NOISE = BIT(2),
USART_FIFO_ERROR_BUFFER_OVERFLOW = BIT(3),
USART_FIFO_NO_DATA = BIT(4)
};
struct
{
volatile uint8_t rx_last_error;
volatile uint8_t rx_buffer[USART_RX_BUFFER_SIZE];
volatile uint8_t rx_buffer_head;
volatile uint8_t rx_buffer_tail;
volatile uint8_t tx_buffer[USART_TX_BUFFER_SIZE];
volatile uint8_t tx_buffer_head;
volatile uint8_t tx_buffer_tail;
} usart_fifo;
//------------------------------------------------------------------------------
void mcu_usart_init(uint16_t divider)
{
usart_fifo.rx_buffer_head = 0;
usart_fifo.rx_buffer_tail = 0;
usart_fifo.tx_buffer_head = 0;
usart_fifo.tx_buffer_tail = 0;
CLK->PCKENR1 |= CLK_PCKENR1_USART1;
(void) USART1->SR;
(void) USART1->DR;
USART1->BRR2 = (uint8_t) (((divider >> 8) & 0xF0) | (divider & 0x0F));
USART1->BRR1 = (uint8_t) (divider >> 4);
USART1->CR1 = 0;
USART1->CR3 = 0;
USART1->CR4 = 0;
USART1->CR5 = 0;
USART1->CR2 = USART_CR2_TEN | USART_CR2_REN | USART_CR2_RIEN;
}
Пример
С передачей достаточно просто, а с приемом немного сложнее, нужно проверять были ли приняты данные:
uint8_t rx_data;
mcu_usart_init(2000000 / 38400);
enableInterrupts();
while (!(mcu_usart_fifo_receive(&rx) & USART_FIFO_NO_DATA))
{
// передаем один принятый байт
mcu_usart_fifo_transmit(rx_data);
}
Если нужно передавать или принимать информацию блоками, то удобнее использовать ПДП (DMA). Возможно об этом в следующий раз.
comments powered by Disqus