ATTiny85 - 带 Timer1 的软件 UART
ATTiny85 - Software UART with Timer1
所以最近我尝试为 ATTiny85 实现软件 UART(仅限 TX)。我想用内部 Timer1.
驱动它
计时器应以波特率的频率中断。每个 ISR 都会发送一位,直到没有任何内容可发送,中断将再次被禁用。
(注:F_CPU=1000000 ; 保险丝为出厂默认值(E:FF, H:DF, L:62))
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>
#define SET(reg, pos) (reg |= 1<<(pos))
#define FLP(reg, pos) (reg ^= 1<<(pos))
#define CLR(reg, pos) (reg &= ~(1<<(pos)))
#define GET(reg, pos) (reg & 1<<(pos))
#define UART_TX_BIT PB1
#define UART_BAUDRATE 600
static volatile uint16_t txframe;
/* Timer1A interrupt at BAUDRATE */
ISR(TIM1_COMPA_vect)
{
/* Write current bit */
if(txframe & 1) SET(PORTB, UART_TX_BIT);
else CLR(PORTB, UART_TX_BIT);
/*
* If the 1 mark at the end of txframe is reached,
* disable interrupts (stop transmitting)
*/
if(txframe == 1) CLR(TIMSK, OCIE1A);
txframe >>= 1;
}
static void uart_putc(const char c)
{
/* Wait until pending txframe is transmitted */
do {
sei();
__asm__ __volatile__ ("nop");
cli();
}while(txframe);
/* MARK . STOP | DATA | START */
txframe = (0b11<<9) | ((uint16_t) c<<1) | 0;
/* Enable timer interrupt and clear flag */
SET(TIMSK, OCIE1A);
SET(TIFR, OCF1A);
sei();
}
static void uart_init()
{
uint8_t sreg = SREG;
cli();
/* Set timer1 (CK) to CTC with divisor of 8 */
TCCR1 = _BV(CTC1) | _BV(CS12);
/* Set BAUDRATE clock divisor */
OCR1A = (uint8_t) ((uint32_t) (F_CPU/8)/UART_BAUDRATE)-1;
/* Enable and pull TX Pin to HIGH */
SET(DDRB, UART_TX_BIT);
SET(PORTB, UART_TX_BIT);
txframe = 0;
SET(TIFR, OCF1A);
sreg = SREG;
}
int main()
{
uart_init();
for(;;) {
uart_putc('A');
_delay_ms(2000);
}
}
使用此设置,接收器仅会收到 0x00 或 0xFF,偶尔还会收到一些其他垃圾(取决于波特率)
最终我试图在不中断的情况下实现同样的事情:
#define UART_FALLBACK_DELAY() _delay_us(1000000UL/UART_BAUDRATE)
static void uart_putc_fallback(uint8_t c)
{
uint8_t sreg = SREG;
cli();
/* Start */
CLR(PORTB, UART_TX_BIT);
UART_FALLBACK_DELAY();
/* Data */
for(int i = 0; i < 8; i++, c>>=1) {
if(c&1) SET(PORTB, UART_TX_BIT);
else CLR(PORTB, UART_TX_BIT);
UART_FALLBACK_DELAY();
}
/* Stop */
SET(PORTB, UART_TX_BIT);
UART_FALLBACK_DELAY();
SREG = sreg;
}
static void uart_putc_fallback2(const char c)
{
uint8_t sreg = SREG;
cli();
txframe = (0b11<<9) | ((uint16_t) c<<1) | 0;
while(txframe) {
if(txframe & 1) SET(PORTB,UART_TX_BIT);
else CLR(PORTB,UART_TX_BIT);
txframe >>= 1;
UART_FALLBACK_DELAY();
}
SREG = sreg;
}
令人惊讶的是,这两个函数都按预期工作,所以我想我搞砸了 Timer1。遗憾的是我没有示波器,所以我无法用手检查信号。但一般来说,使用 Timer1 时信号似乎有点慢。该设置应每 1664µs 中断一次:
- 波特率 = 600Hz
- CK = 1MHz
- Timer1DIV = 8
- Timer1CK = CK/Timer1DIV = 125kHz
- OCR1A = Timer1CK/Baud = 208
- 延迟 = (Timer1DIV * OCR1A)/CK = (8*208)/1MHz = 1664µs
谁能告诉我为什么中断方法没有按预期工作?
更多信息:
- Vcc = ~4.52V (~25°C) Oscillator 应该足够准确
- Timer1 Prescale, Table 12-5
- Timer1 Overview
- 默认情况下 Timer1 使用 CK (1MHz) 而不是 PCK(我已经尝试手动将其设置为 CK)
- 工具链:avr-gcc
我想我发现了一个可能导致您所看到的问题。
在 CTC 模式下,此计时器在达到 OCR1C
后清除计数器...
您似乎没有在您的代码中设置 OCR1C
,因此计时器的周期将基于 OCR1C
中发生的任何情况(即 0
在电源向上)。
这很容易被忽略(OCR1C
对比 OCR1A
)!
我认为您可以通过在此处添加一行来设置此寄存器来使您的代码正常工作...
/* Set BAUDRATE clock divisor */
uint8_t match = (uint8_t) ((uint32_t) (F_CPU/8)/UART_BAUDRATE)-1;
OCR1A = match; // Generate interrupt on match to drive ISR
OCR1C = match; // Clear counter on match to define period
如果是,请反馈 - 如果不是,我们会继续寻找!
所以最近我尝试为 ATTiny85 实现软件 UART(仅限 TX)。我想用内部 Timer1.
驱动它计时器应以波特率的频率中断。每个 ISR 都会发送一位,直到没有任何内容可发送,中断将再次被禁用。
(注:F_CPU=1000000 ; 保险丝为出厂默认值(E:FF, H:DF, L:62))
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>
#define SET(reg, pos) (reg |= 1<<(pos))
#define FLP(reg, pos) (reg ^= 1<<(pos))
#define CLR(reg, pos) (reg &= ~(1<<(pos)))
#define GET(reg, pos) (reg & 1<<(pos))
#define UART_TX_BIT PB1
#define UART_BAUDRATE 600
static volatile uint16_t txframe;
/* Timer1A interrupt at BAUDRATE */
ISR(TIM1_COMPA_vect)
{
/* Write current bit */
if(txframe & 1) SET(PORTB, UART_TX_BIT);
else CLR(PORTB, UART_TX_BIT);
/*
* If the 1 mark at the end of txframe is reached,
* disable interrupts (stop transmitting)
*/
if(txframe == 1) CLR(TIMSK, OCIE1A);
txframe >>= 1;
}
static void uart_putc(const char c)
{
/* Wait until pending txframe is transmitted */
do {
sei();
__asm__ __volatile__ ("nop");
cli();
}while(txframe);
/* MARK . STOP | DATA | START */
txframe = (0b11<<9) | ((uint16_t) c<<1) | 0;
/* Enable timer interrupt and clear flag */
SET(TIMSK, OCIE1A);
SET(TIFR, OCF1A);
sei();
}
static void uart_init()
{
uint8_t sreg = SREG;
cli();
/* Set timer1 (CK) to CTC with divisor of 8 */
TCCR1 = _BV(CTC1) | _BV(CS12);
/* Set BAUDRATE clock divisor */
OCR1A = (uint8_t) ((uint32_t) (F_CPU/8)/UART_BAUDRATE)-1;
/* Enable and pull TX Pin to HIGH */
SET(DDRB, UART_TX_BIT);
SET(PORTB, UART_TX_BIT);
txframe = 0;
SET(TIFR, OCF1A);
sreg = SREG;
}
int main()
{
uart_init();
for(;;) {
uart_putc('A');
_delay_ms(2000);
}
}
使用此设置,接收器仅会收到 0x00 或 0xFF,偶尔还会收到一些其他垃圾(取决于波特率)
最终我试图在不中断的情况下实现同样的事情:
#define UART_FALLBACK_DELAY() _delay_us(1000000UL/UART_BAUDRATE)
static void uart_putc_fallback(uint8_t c)
{
uint8_t sreg = SREG;
cli();
/* Start */
CLR(PORTB, UART_TX_BIT);
UART_FALLBACK_DELAY();
/* Data */
for(int i = 0; i < 8; i++, c>>=1) {
if(c&1) SET(PORTB, UART_TX_BIT);
else CLR(PORTB, UART_TX_BIT);
UART_FALLBACK_DELAY();
}
/* Stop */
SET(PORTB, UART_TX_BIT);
UART_FALLBACK_DELAY();
SREG = sreg;
}
static void uart_putc_fallback2(const char c)
{
uint8_t sreg = SREG;
cli();
txframe = (0b11<<9) | ((uint16_t) c<<1) | 0;
while(txframe) {
if(txframe & 1) SET(PORTB,UART_TX_BIT);
else CLR(PORTB,UART_TX_BIT);
txframe >>= 1;
UART_FALLBACK_DELAY();
}
SREG = sreg;
}
令人惊讶的是,这两个函数都按预期工作,所以我想我搞砸了 Timer1。遗憾的是我没有示波器,所以我无法用手检查信号。但一般来说,使用 Timer1 时信号似乎有点慢。该设置应每 1664µs 中断一次:
- 波特率 = 600Hz
- CK = 1MHz
- Timer1DIV = 8
- Timer1CK = CK/Timer1DIV = 125kHz
- OCR1A = Timer1CK/Baud = 208
- 延迟 = (Timer1DIV * OCR1A)/CK = (8*208)/1MHz = 1664µs
谁能告诉我为什么中断方法没有按预期工作?
更多信息:
- Vcc = ~4.52V (~25°C) Oscillator 应该足够准确
- Timer1 Prescale, Table 12-5
- Timer1 Overview
- 默认情况下 Timer1 使用 CK (1MHz) 而不是 PCK(我已经尝试手动将其设置为 CK)
- 工具链:avr-gcc
我想我发现了一个可能导致您所看到的问题。
在 CTC 模式下,此计时器在达到 OCR1C
后清除计数器...
您似乎没有在您的代码中设置 OCR1C
,因此计时器的周期将基于 OCR1C
中发生的任何情况(即 0
在电源向上)。
这很容易被忽略(OCR1C
对比 OCR1A
)!
我认为您可以通过在此处添加一行来设置此寄存器来使您的代码正常工作...
/* Set BAUDRATE clock divisor */
uint8_t match = (uint8_t) ((uint32_t) (F_CPU/8)/UART_BAUDRATE)-1;
OCR1A = match; // Generate interrupt on match to drive ISR
OCR1C = match; // Clear counter on match to define period
如果是,请反馈 - 如果不是,我们会继续寻找!