STM8L-Универсальный асинхронный приёмопередатчик (UART)

21 Февраля 2011 К комментариям

К сожалению обещанный второй компаратор я решил пропустить, да и с индикатором от Nokia 1100 вышла неувязка, решил передавать данные с АЦП в компьютер, поэтому сегодня кратенько об универсальном приёмопередатчике (USART).

Модуль универсального приемопередатчика (USART)

Возможности модуля:

  • двухпроводный полно-дуплексный асинхронный обмен
  • однопроводной полудуплексный обмен
  • IrDA кодер-декодер
  • максимальная скорость обмена SYSCLK/16
  • два вектора прерываний (прием, передача), восемь источников прерываний
  • мульти-процессорное взаимодействие (возможность адресации)
  • и как обычно ещё много полезного :)

Функциональная схема модуля:

image

Я думаю начать нужно с самого простого. Попробуем сконфигурировать модуль на передачу данных в ПК и передать пару байт.

Разрешаем тактирование модуля:

PCKENR1 |= CLK_PCKENR1_USART1;

Вы не поверите, но для “стандартного” режима обмена (восемь бит данных, один стоп бит и т.д.) достаточно разрешить работу передатчика и задать скорость обмена:

USART1->CR1 = 0;
USART1->CR3 = 0;
USART1->CR4 = 0;
USART1->CR5 = 0;
USART1->CR2 = USART_CR2_TEN;

Скорость обмена задается очень просто, нужно знать лишь частоту тактирования:

image

пример из документации:

image

Примечание: не понял, за чем такие хитрые перестановки Улыбка

В моем случае тактовая частота равна 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