本文隶属于AVR单片机教程系列。

 

到目前为止,我们的开发板只能处理很小量的数据:读取几个引脚电平,输出几个LED,顶多用数码管显示一个两位数字。至于输入一个指令、输出一条调试信息,甚至用scanf和printf来输入输出,在已经接触过的这些器件上是难以想象的。而本讲“串口发送”与下一讲“串口接收”,将打开这一扇大门。

硬件

本讲的主题是UART(Universal Asynchronous Receiver-Transmitter,通用异步收发器),俗称串口。实际上串口是串行接口的统称,在单片机领域通常指UART。“串行”的意思是每次传输一个bit,而一个字节的数据被拆成8个bit传输;相比之下并行总线可以一次传输一个或多个字节(这并不意味着并行总线一定优于串行总线)。

AVR单片机提供的硬件组件不是UART,而是USART(S代表Synchronous,同步的),相比UART额外支持同步通信。所谓“同步”是指收发双方通过时钟同步,“异步”是指没有时钟来同步,但实际上双方还是由一些特殊信号同步的。

数据在UART总线上以“帧(frame)”为单位发送,如下图所示,带有方括号的位是可选的。

一帧包含一个起始位、5~9个数据位(常用8位;很多设备不支持9位)、可选的一个校验位(偶校验或奇校验,即所有数据位与0或1的异或结果)与1或2个终止位。起始位与终止位统称为同步位,用于在异步总线上起到同步的作用,这样接收方才能知道一帧何时开始。

波特率的定义是信息在通信信道上传输的速率。假如信号线上的波形允许1秒有9600个方框(方框表示高电平或低电平,实际电平是其中一个),那么波特率就是9600。常用的波特率有9600与115200(打开Serial Port Utility或类似软件,可选的波特率都是常用的)。

在开始通信之前,收发双方必须约定好波特率与帧格式。uart_init函数的配置是波特率38400,8数据位,偶校验,1停止位。相应地在电脑的串口调试软件中也要这样配置。

开发板的TX引脚发送数据,RX引脚接收数据。为了使开发板与电脑能通过UART通信,电脑上需要插一个USB转串口的工具。用杜邦线把开发板与工具的TX与RX引脚交叉连接。本讲只涉及串口发送,所以只连接开发板的TX与串口工具的RX就可以了。在串口调试软件中打开端口,就可以通信了。

软件

由于printf是变参函数,不是很安全(如果格式串和参数对应错,程序可能直接跑飞),我倾向于使用类型安全的函数,即函数通过C的句法知道实参类型(写错就编译错误,而不是通过编译器无法检测的格式串)。不过,avr-gcc的<stdio.h>中还是提供了printf等函数,你可以了解一下。

库中提供的发送函数都是同步阻塞的,即等待硬件组件把数据全部发送完,函数才返回。这里的“同步”与刚才的“异步总线”所指是不同的。关于“同步”与“异步”、“阻塞”与“非阻塞”的概念,可以参考:怎样理解阻塞非阻塞与同步异步的区别?

不难计算,总线发送一个字节的时间是几千个CPU周期,CPU会浪费大量时间在无用的等待上。这个问题直到我们讲到中断才会解决(也许我会把它封装起来放进库)。

实例

我们来写一个用串口发送按键与拨动开关信息的程序。如果你会相关的C#编程,就可以让电脑响应按键事件。

#include <ee1/delay.h>
#include <ee1/button.h>
#include <ee1/switch.h>
#include <ee1/uart.h> int main(void)
{
button_init(PIN_6, PIN_7);
switch_init(PIN_4, PIN_5);
uart_init(UART_TX);
uart_print_string("start\n");
while (1)
{
for (uint8_t i = 0; i != BUTTON_COUNT; ++i)
if (button_pressed(i))
{
uart_print_string("button ");
uart_print_int(i);
uart_print_string("\n");
}
for (uint8_t i = 0; i != SWITCH_COUNT; ++i)
if (switch_changed(i))
{
uart_print_string("switch ");
uart_print_int(i);
uart_print_string(switch_status(i) ? " on\n" : " off\n");
}
delay(1);
}
}

程序首先将UART初始化为发送模式(UART_TX),然后打印"start"。在间隔一毫秒的循环中(实际上串口发送的时间远长于一毫秒,因为是阻塞的),程序检测每一个按键与开关的动作,如果有则发送相应数据。

用printf一行就能解决的操作这里需要三行才能完成,这就是权衡吧。

作业

  1. 基于uart_print_char,实现my_print_int函数,在串口上打印一个int类型整数(在avr-gcc中,int类型默认是16位宽度;注意负号和0;你可以了解一下itoa,尽管它是非标准的)。

  2. 将旋转编码器的数据通过串口传输给电脑。将原始数据(pin_read或rotary_status)与处理后的数据(rotary_rotated)一同打印,你可以更直观地感受数据处理的过程。

AVR单片机教程——串口发送的更多相关文章

  1. AVR单片机教程——串口接收

    本文隶属于AVR单片机教程系列.   上一讲中,我们实现了单片机开发板向电脑传输数据.在这一讲中,我们将通过电脑向单片机发送指令,让单片机根据指令控制LED.这一次,两端的TX与RX需要交叉连接,单片 ...

  2. AVR单片机教程——UART进阶

    本文隶属于AVR单片机教程系列.   在第一期中,我们已经开始使用UART来实现单片机开发板与计算机之间的通信,但只是简单地讲了讲一些概念和库函数的使用.在这一篇教程中,我们将从硬件与软件等各方面更深 ...

  3. AVR单片机教程——示波器

    本文隶属于AVR单片机教程系列.   在用DAC做了一个稍大的项目之后,我们来拿ADC开开刀.在本讲中,我们将了解0.96寸OLED屏,移植著名的U8g2库到我们的开发板上,学习在屏幕上画直线的算法, ...

  4. AVR单片机教程——点亮第一个LED

    做了这么多准备,我们终于可以开始用开发板做点事了. 单片机编程与计算机编程有一些不同点.程序都要有零个或多个输入.一个或多个输出,这是两者都有的,但是计算机编程的输入输出主要靠控制台,而单片机没有. ...

  5. AVR单片机教程——ADC

    ADC 计算机的世界是0和1的.单片机可以通过读取0和1来确定按键状态,也可以输出0和1来控制LED.即使是看起来不太0和1的PWM,好像可以输出0到5V之间的电压一样,达到0和1之间的效果,但本质上 ...

  6. AVR单片机教程——LCD1602

    本文隶属于AVR单片机教程系列.   显示屏 开发板套件里有两块屏幕,大的是LCD(液晶显示),小的是OLED(有机发光二极管).正与你所想的相反,短小精悍的比较贵,而本讲的主题--LCD1602-- ...

  7. AVR单片机教程——小结

    本文隶属于AVR单片机教程系列.   第一期挺让我失望的,是我太菜,没有把想讲的都讲出来.经常写了很多,然后一点一点删掉,最后就没多少了. 而且感觉难度不合适,处于很尴尬的位置.讲得简单,难的丢给库, ...

  8. AVR单片机教程——矩阵键盘

    本文隶属于AVR单片机教程系列.   开发板上有4个按键,我们可以把每一个按键连接到一个单片机引脚上,来实现按键状态的检测.但是常见的键盘有104键,是每一个键分别连接到一个引脚上的吗?我没有考证过, ...

  9. AVR单片机教程——DAC

    本文隶属于AVR单片机教程系列.   单片机的应用场景时常涉及到模拟信号.我们已经会使用ADC把模拟信号转换成数字信号,本讲中我们要学习使用DAC把数字信号转换成模拟信号.我们还将搭建一个简单的功率放 ...

随机推荐

  1. 【u233】单词化简

    Time Limit: 1 second Memory Limit: 64 MB [问题描述] 最近情报人员得到了一些经过加密的文章,每个单词都很长.破译人员想到先把单词化简一下,方法是把每个单词尽量 ...

  2. P1007 N钱M鸡问题

    题目描述 已知公鸡 \(5\) 元钱一只,母鸡 \(3\) 元钱一只,小鸡 \(3\) 只 \(1\) 元钱. 告诉你一个整数 \(n(1 \le n \le 1000)\) ,你现在要花 \(n\) ...

  3. Total Commander 显示文件包含文件名扩展

    在默认的 Total Commander 是分开文件名和文件扩展,如果想要让文件名同时显示扩展,可以通过设置合并文件名和扩展两列 点击配置,在选项的制表修改显示 How to configure To ...

  4. 【u107】数字游戏(bds)

    Time Limit: 1 second Memory Limit: 128 MB [问题描述] 有这么一个游戏: 写出一个1-N的排列a[i],然后每次将相邻两个数相加,构成新的序列,再对新序列进行 ...

  5. tensorflow在文本处理中的使用——辅助函数

    代码来源于:tensorflow机器学习实战指南(曾益强 译,2017年9月)——第七章:自然语言处理 代码地址:https://github.com/nfmcclure/tensorflow-coo ...

  6. 2018-9-14-win10-UWP-标题栏后退

    title author date CreateTime categories win10 UWP 标题栏后退 lindexi 2018-9-14 20:22:8 +0800 2018-2-13 17 ...

  7. 微信小程序 image图片组件实现宽度固定 高度自适应

    给img的mode设置值 注1:image组件默认宽度300px.高度225px 注2:image组件中二维码/小程序码图片不支持长按识别.仅在wx.previewImage中支持长按识别. mode ...

  8. JS逻辑运算操作

    非! 如果一个操作数是一个对象,返回false; 如果一个操作数是一个空字符串,返回true; 如果一个操作数是一个非空字符串,返回false; 如果一个操作数是一个数值0,返回true; 如果一个操 ...

  9. 缓存, 队列(Redis,RabbitMQ)

    Redis Redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorte ...

  10. 第二阶段:1.流程图:10.visio绘制泳道图

    选择跨智能 同时水平 设置好泳道图标题以及泳道名字 泳道位置可以拖拽更换 左侧有一些基本的形状 包括分隔符等等 添加分隔符并填上分隔符上面的阶段名称 左边选择形状界面 然后拖拽使用 同时可以用连接线链 ...