|
#include <avr/io.h> #include <avr/sfr_defs.h> #include <stdio.h>
// 芯片型号: ATMega16A // 晶振: 外部11.0592MHz // 熔丝位配置: CKSEL3~0=1110, CKOPT=0 // 只有使用外部晶振才能保证PC端接收到的内容不乱码
int fputc(int ch, FILE *fp) { if (fp == stdout) { if (ch == '\n') { // 自动添加\r UDR = '\r'; while ((UCSRA & _BV(UDRE)) == 0); } UDR = ch; while ((UCSRA & _BV(UDRE)) == 0); } return ch; }
int fgetc(FILE *fp) { if (fp == stdout) { while ((UCSRA & _BV(RXC)) == 0); return UDR; } return 0; }
void USART_WriteString(const char *s) { while (*s) { UDR = *s++; while ((UCSRA & _BV(UDRE)) == 0); } }
int main(void) { char str[50]; //UBRRL = 71; // 波特率: 9600 UBRRL = 5; // 波特率: 115200 UCSRB = _BV(RXEN) | _BV(TXEN);
USART_WriteString("This is a string.\r\n"); fdevopen((int (*)(char, FILE*))fputc, fgetc); // 进行类型转换的目的是为了避免警告 printf("Hello World!\nUBRRL=%d\n", UBRRL); while (1) { gets(str); puts(str); } }
|
|
这个程序有一个问题,就是如果连续发送多行,那么接收到的内容从第二行开始就会不完整。例如,发送: This is a long string. This is another paragraph.
接收到的却是: This is a long string. Thph.
这是因为串行通信是收发同时进行的,而在上面的程序中却是先接收(gets)后发送(puts),这样当gets获取完第一行内容后,puts才开始发送,并且发送需要一定的时间。等puts发送完了gets才开始接收,此时单片机端早已发送了很多字符了,中间的内容都没有收到而溢出(Overrun)了。所以第二行的内容就不完整。
|
|
上述问题的解决办法是,创建一个FIFO缓冲区(队列),并打开串口接收中断。每接收到一个字符就放入该缓冲区中,而scanf, gets, fgetc等函数则是从缓冲区中读取字符,如果没有字符可读取就一直阻塞。这样就能做到收发同时进行,且可以正常使用标准输入函数。 【改进后的程序】 #include <avr/interrupt.h> #include <avr/io.h> #include <avr/sfr_defs.h> #include <stdio.h>
// 芯片型号: ATMega16A // 晶振: 外部11.0592MHz // 熔丝位配置: CKSEL3~0=1110, CKOPT=0 // 只有使用外部晶振才能保证PC端接收到的内容不乱码
// 队列结构体 struct fifo { unsigned char buf[128]; unsigned char front; unsigned char rear; } usartbuf;
#define fifo_init(f) ((f)->front = (f)->rear = 0) // 初始化队列 #define fifo_empty(f) ((f)->front == (f)->rear) // 队空判定 #define fifo_full(f) ((f)->front == ((f)->rear + 1) % sizeof((f)->buf)) // 队满判定
// 入队 unsigned char fifo_in(struct fifo *f, unsigned char data) { if (fifo_full(f)) return 0; f->buf[f->rear] = data; f->rear = (f->rear + 1) % sizeof(f->buf); return 1; }
// 出队 unsigned char fifo_out(struct fifo *f, unsigned char *data) { if (fifo_empty(f)) return 0; *data = f->buf[f->front]; f->front = (f->front + 1) % sizeof(f->buf); return 1; }
int fputc(int ch, FILE *fp) { if (fp == stdout) { if (ch == '\n') { // 自动添加\r UDR = '\r'; while ((UCSRA & _BV(UDRE)) == 0); } UDR = ch; while ((UCSRA & _BV(UDRE)) == 0); } return ch; }
int fgetc(FILE *fp) { unsigned char value = 0; if (fp == stdout) while (!fifo_out(&usartbuf, &value)); return value; }
int main(void) { char str[101]; // 假定每一行最长不超过100个字符 //UBRRL = 71; // 波特率: 9600 UBRRL = 5; // 波特率: 115200 UCSRB = _BV(RXEN) | _BV(TXEN) | _BV(RXCIE); // 开接收中断 sei(); // 开总中断
fdevopen((int (*)(char, FILE*))fputc, fgetc); while (1) { gets(str); puts(str); } }
// 串口接收中断 ISR(USARTRXC_vect) { fifo_in(&usartbuf, UDR); }
|
|
在main函数中最好先将front和rear的值清零: int main(void) { char str[101]; // 假定每一行最长不超过100个字符 //UBRRL = 71; // 波特率: 9600 UBRRL = 5; // 波特率: 115200 UCSRB = _BV(RXEN) | _BV(TXEN) | _BV(RXCIE); // 开接收中断 sei(); // 开总中断
fifo_init(&usartbuf); // 将front和rear清零 fdevopen((int (*)(char, FILE*))fputc, fgetc); while (1) { gets(str); puts(str); } }
|
|
如果接收到的内容出现乱码或字符丢失的现象,说明FIFO缓冲区的大小不够,扩大缓冲区就行了: struct fifo { unsigned char buf[512]; // 缓冲区的大小 unsigned int front, rear; // 这两个变量要改用int类型 } usartbuf;
|
|
串口大师这个软件本身可能有问题,发送同样的内容,另一个串口调试工具就没有出现乱码。
|
|
如果接收到的内容出现乱码或字符丢失的现象,说明FIFO缓冲区的大小不够,扩大缓冲区就行了: struct fifo {...
同时还应该注意扩大main函数中str数组的大小。
|
|
在C程序里直接执行printf("// 只有使用外部晶振才能保证PC端接收到的内容不乱码");(没有换行符),串口大师接收到的内容都会乱码。。。
|