本帖最后由 wziyi 于 2021-5-24 22:33 编辑
与 AVR 的第一次相遇(三)这节要不来个串口玩玩?按以往惯例,该讲解定时器的,主要是我想试试软件仿真里的串口该怎么玩。串口是嵌入式硬件里最简单、最常见和最重要的串行通信协议。它的同类有 I2C , SPI , CAN,网口和 USB 等等。它们都是串行通信口,有的属于对等,有的属于非对等,都可以由多个节点组成网络。单片机一般可以分为内核和外设两大部分,外设里又可以把串行通信外设作为一个大类来讲。以串口的学习为例,可以分为协议层和硬件层。硬件层里有 TTL 电平,RS232 电平和 RS422/RS485 电平。主要用在 PCB 板上通信,对标的是 I2C 和 SPI。RS232 电平和 RS422/RS485 电平用在远距离通信,对标的是 USB 和 CAN。按通信协议分为同步和异步,常见的是异步,同步的我也没用过。通信特性还可分为波特率,数据位,停止位,校验位和流控制位。 下面便是直接与芯片本身相关的知识了,首先从特性中获取主要信息。半双工模式里有个单线模式,这个在 PIC 里就没有,目前我只在 ST8 和 STM32 见过。这个很有用,在很多工业应用环境下,多一条线会对工艺提出很高的要求。剩下的部分都是常规信息了,与同类芯片都是一样的。下图中有个小数波特率发生器要关注下,它可以能够基于任何系统时钟频率生成各种 USART 波特率。在很多低端单片机就没有这个东西,所以有时候为了得到合适 USART 波特率需要设置特定的系统时钟。
我初学单片机的时候有一个超级大坏习惯就是不爱看图表,数字芯片以逻辑流程图为主而模拟芯片则以参数指标图为主。从下面流程图可以看出它各个寄存器间的联系,也可以大致知道哪些寄存器是重要配置项,比如 TXDATA 和 RXDATA 就是存放数据的地方。
接下来是串行通信协议的时序图了,是每种单片机芯片都会有图表。在里面必须的搞懂一个很重的概念码元和数据帧,编码与解码。这属于通信原理里面东西,可以自己看相关网课。下面这个时序图表示就是它的数据帧是如何构成的。最开始的是起始位(火车头),然后是数据位位(乘客),随后是停止位(火车尾),最后的校验位是选配的。
紧接着便是介绍如何计算合适波特率的公式。其中寄存器的高 10 位保存整数部分,低 6 位保存小数部分。
发现了个新鲜玩意,这个 UART 貌似可以直接复用为 SPI 。这是我第一次见,还没搞清楚咋回事,先在这提一下。
一直在啰嗦软件的东西,有点腻味了,来点硬的。芯片大厂的原理图看起来就是那么赏心悦目,今天给朋友讲解复旦微单片机的时候,文档看的想吐,且不说各种错别字,东西还写的乱七八糟缺胳膊少腿的。新人啊,千万不要去碰国产单片机,没两把刷子,容易崩着牙。下面这幅图画得真的好美,把总线画成萌萌的螺旋线,外设排列的整齐画一宛若棋盘。隔壁 NXP 和 ST 的单片机开发板原理图好像大多是很传统的那种层次图。以后要把这种画法全面用在自己设计中。
写个小插曲,今天在给小师弟讲代码的时候翻车了,要对寄存器的某一个位取反,头脑第一个想到的位运算符 ^ ,就卡这了,不知道怎么写了。吃完晚饭,看看 C 语言手册,看来代码必须天天写,不然会快速倒退的。 PORTC.OUT ^= (1<<3) //PORTC.OUT 寄存器的第 3 位取反
特意找了 ST 的牛可乐板,虽然也很整齐美观,与 AVR 相比,还是差那么点点。NXP 的电路原理图是 orCAD 格式的,电脑上没装 Cadence 暂时不好展示。
从原理图看出外部晶体振荡器有 16MHz 和 32.768KHz 两个,带有一个灯 PB3 为低时亮。有一个按键却没有去耦电容,这意味着按键不能使用中断只能用轮询。要是使用中断,按键抖动会导致单片机反复进入中断易造成堆栈溢出。
接着发现有两个串口分别与调试器相连,其中 USART3 与 CDC 相连。这里解释下,现在调试器往往会集成串口通信的功能,它是利用 USB 通讯设备类(CDC)来与电脑进行数据交互的。这个开发板就是个高配的最小系统板,电路部分也就这么点点。这里主要想科普下关于原理图的基础知识,那根比较粗的线叫做总线。就是把好几条线放在一块,普通电线不能直接和它连接,得先给总线添加电气连接线,就是那些直接与总线相连的 45 度斜线。那些黄色的长方块是端口,名字一样的端口是具有电气连接的。
打工人太累了,五月加班多没时间搞,等这么久才有机会写代码。就先用串口不停地发送字节吧,一般通信里面发送都比接收简单的多。串口接收数据用软件仿真还不太好实现,等以后在说。 #include <avr/io.h>
#include <avr/cpufunc.h>
#include <avr/delay.h>
FUSES = {
.WDTCFG = 0x00, // WDTCFG {PERIOD=OFF, WINDOW=OFF}
.BODCFG = 0x00, // BODCFG {SLEEP=DISABLE, ACTIVE=DISABLE, SAMPFREQ=128Hz, LVL=BODLEVEL0}
.OSCCFG = 0xF8, // OSCCFG {CLKSEL=OSCHF}
.SYSCFG0 = 0xDA, // SYSCFG0 {EESAVE=CLEAR, RSTPINCFG=RST, CRCSEL=CRC16, CRCSRC=NOCRC}
.SYSCFG1 = 0xE8, // SYSCFG1 {SUT=0MS, MVSYSCFG=DUAL}
.CODESIZE = 0x00, // CODESIZE {CODESIZE=User range: 0x0 - 0xFF}
.BOOTSIZE = 0x00, // BOOTSIZE {BOOTSIZE=User range: 0x0 - 0xFF}
};
LOCKBITS = 0x5CC5C55C; // {KEY=NOLOCK}
#define USART1_BAUD_RATE(BAUD_RATE) (((float)3333333 * 64 / (16 * (float)BAUD_RATE)) + 0.5)
void system_init(void);
void uart_init(void);
int main(void)
{
unsigned char a=0;
system_init(); //系统初始化
uart_init(); //串口初始化
while(1)
{
_delay_ms(100); //适当延时方便后续观察
while (!(USART1.STATUS & USART_DREIF_bm)); //等待串口发送就绪
USART1.TXDATAL = a; //使串口不断发送 0 到 256 之间的数字
_delay_ms(100);
a++; //数字自加
}
return 0;
}
void system_init(void){
CLKCTRL.MCLKCTRLA = 0x80; //内部高速振荡器,时钟在对外输出
CLKCTRL.MCLKCTRLB = 0x00; //关闭预分频器分频
CLKCTRL.OSCHFCTRLA = 0xbc; //24MHz,一直开启,关闭时钟修正
}
void uart_init(void)
{
USART1.BAUD = (uint16_t)USART1_BAUD_RATE(9600); //设置波特率
USART1.CTRLA = 0x00; //关闭中断
USART1.CTRLB = 0x40; //打开发送功能
USART1.CTRLC = 0x03; //配置异步-校验位-停止位-数据位
USART1.DBGCTRL = 0x00; //关闭调试模式
USART1.EVCTRL = 0x00; //关闭 IrDA
USART1.RXPLCTRL = 0x00; //关闭 RXPLCTRL_RXPL
USART1.TXPLCTRL = 0x00; //关闭 TXPLCTRL_TXPL
}
从图中可以看出控制台输出了很多奇奇怪怪的字符,代码中是输出 0 到 256 的十六进制数,终端仿真显示的是十六进制数所对应的字符码。这是 C 语言里面的知识,自己可以翻翻书。
|