|
深入串口@[通信](這里寫自定義目錄標(biāo)題)
# 串口通信簡介
你好! 如果這是你第一次使用 **串口** ,那么了解一些串口是什么以及怎么用吧。
## 串口是什么
一般來說,單片機(jī)與上位機(jī)之間以串口通信為主,就串口通信而言。常用的幾種通信方式,包括串口自定義協(xié)議、Modbus協(xié)議、CAN總線。重點(diǎn)介紹一下串口通信。
1.png (708.81 KB, 下載次數(shù): 28)
下載附件
2024-5-24 14:52 上傳
## IDEA想法
現(xiàn)在小銘有一臺(tái)電腦和一個(gè)外設(shè),現(xiàn)在他想讓外設(shè)給電腦發(fā)送一個(gè)字母A。怎么辦呢?我們知道計(jì)算機(jī)的世界只有0和1。那么用0和1怎么發(fā)送A甚至是任意數(shù)據(jù)呢?
假設(shè)我們每次發(fā)送一個(gè)字節(jié)那么它要么是0要么是1,接下來我們一次發(fā)送8個(gè)字節(jié)則依然是每一位為0或者1。但是注意到
也就是說第一位可以代表1個(gè)數(shù)字,第二位代表2個(gè)數(shù)字......第八位可以代表128個(gè)數(shù)字,那么這八個(gè)字節(jié)可以代表1+2+4+8+16+32+64+128=255個(gè)數(shù)字。如果對照ASCII表基本上可以發(fā)送所有字符。
## 通信
好了,現(xiàn)在我們按照ASCII上對應(yīng)關(guān)系開始發(fā)送數(shù)據(jù)吧。還是以A為例。A對應(yīng)的十進(jìn)制是65。 65=1+64。那么我們可以讓第一位是1,第7位也是1,其他位為0。
現(xiàn)在我們已經(jīng)成功的發(fā)送了A。
此外還得規(guī)定通信速率也就是波特率每秒發(fā)送的位數(shù)。這里就不詳細(xì)介紹了。
## 單片機(jī)實(shí)現(xiàn)串口通信
51單片機(jī)
要想使用串口就要了解一下兩個(gè)寄存器和定時(shí)器。
(1)串口控制寄存器 SCONSCON=0x40 工作方式1;0100 0000 串口不接受數(shù)據(jù)
SCON=0x50 工作方式1;0101 0000 串口接受數(shù)據(jù)
(2)電源控制寄存器 PCON
PCON=0X80;波特率加倍
PCON=0X00;波特率不加倍
(3)TMOD 計(jì)數(shù)器這什么玩意?要不我們換一張圖看看吧!
```c
TMOD|=0X20; //設(shè)置計(jì)數(shù)器工作方式 2
TH1=baud; //計(jì)數(shù)器初始值設(shè)置
TL1=baud
```
2)
```c
TMOD|=0X01;//選擇為定時(shí)器 0 模式,工作方式1
TH0=初值
TL0=初值
```
## 硬核知識(shí)-串口發(fā)送字節(jié)
了解完以上內(nèi)容現(xiàn)在我們就可以寫一個(gè)串口發(fā)送的函數(shù)了。我們定義函數(shù)為uart_init()
在這個(gè)函數(shù)里我們做這幾件事情:
①確定 T1 的工作方式(TMOD 寄存器);
②確定串口工作方式(SCON 寄存器);
③計(jì)算 T1 的初值(設(shè)定波特率),裝載 TH1、TL1;
④啟動(dòng) T1(TCON 中的 TR1 位);
⑤如果使用中斷,需開啟串口中斷控制位(IE 寄存器)。
如果你要實(shí)現(xiàn)不同的功能那么這個(gè)初始化的函數(shù)也會(huì)不一樣。這里我們寫幾個(gè)典型的例子:
**實(shí)現(xiàn)功能**:串口把接收到的數(shù)據(jù)發(fā)送回單片機(jī)
```c
void uart_init(void)
{
TMOD|=0X20; //設(shè)置計(jì)數(shù)器工作方式 2
SCON=0X50; //設(shè)置為工作方式 1
PCON=0X80; //波特率加倍 4800倍后為9600
TH1=0XFA; //計(jì)數(shù)器初始值設(shè)置
TL1=OXFA;
ES=1; //打開接收中斷
EA=1; //打開總中斷
TR1=1; //打開計(jì)數(shù)器
}
void uart() interrupt 4//串口通信中斷函數(shù)
{
u8 rec_data;
RI = 0; //清除接收中斷標(biāo)志位
rec_data=SBUF; //存儲(chǔ)接收到的數(shù)據(jù)
SBUF=rec_data; //將接收到的數(shù)據(jù)放入到發(fā)送寄存器
while(!TI); //等待發(fā)送數(shù)據(jù)完成
TI=0; //清除發(fā)送完成標(biāo)志位
}
**串口功能**:發(fā)送傳感器數(shù)據(jù),無需中斷
void UART_Init(void)
{
SCON=0x50; //串口通信工作方式1
TMOD=0x20; //定時(shí)器1的工作方式2
PCON=0X00; //不加倍
TH1=0xfd,TL1=0xfd; //9600baud
TI=1; //這里一定要注意
TR1=1;
}
//如果你有一個(gè)返回ADC的函數(shù)adc_value()
float buff;
buff=adc_value();
printf("data is %f",buff);
//你就可以成功的把數(shù)據(jù)在串口助手中顯示了
這里我重點(diǎn)介紹一下printf函數(shù)
printf 打印調(diào)試把數(shù)據(jù)顯示在串口助手中或者是oled屏幕上。
配合上面那個(gè)串口初始化函數(shù),然后在頭文件中包含#Include “stdio.h”就可以使用了。
printf("welcome to the digatal world");
但是使用printf唯一的不好就是如果要與上位機(jī)通信它發(fā)送的數(shù)據(jù)位數(shù)不一樣;比如說你的數(shù)據(jù)是3.23;45.13;100.64;數(shù)據(jù)位數(shù)不一樣在于上位機(jī)通信時(shí)會(huì)給軟件編程造成很大的麻煩。最好是003.23;045.13;100.64;怎么做到呢?小白我編程能力有限,如果有大佬會(huì)請留言謝謝!
下面介紹用**寄存器**收發(fā)數(shù)據(jù)。
void UART_Init(void)
{
TMOD|=0X20; //設(shè)置計(jì)數(shù)器工作方式2
SCON=0X50; //設(shè)置為工作方式1
PCON=0X00; //波特率加倍
TH1=0xfd; //計(jì)數(shù)器初始值設(shè)置
TL1=0xfd;
ES=1; //打開接收中斷
EA=1; //打開總中斷
TR1=1; //打開計(jì)數(shù)器
}
void UART_Sendchar(unsigned char d) //發(fā)送一個(gè)字節(jié)的數(shù)據(jù),形參d即為待發(fā)送數(shù)據(jù)。
{
SBUF=d; //將數(shù)據(jù)寫入到串口緩沖
while(!TI);
TI=0;
}
void UART_SendString(unsigned char *String)
{
while(*String)
{
UART_Sendchar(*String);
String++;
}
}
UART_Sendchar(65); //發(fā)送A十進(jìn)制對應(yīng)的符號是A
UART_Sendchar('a');//發(fā)送a 注意只能單引號
UART_Sendchar(0x65);//發(fā)送e十六進(jìn)制65對應(yīng)的符號是e
UART_SendString("小七");//發(fā)送字符串
/*****在這里你應(yīng)該理解了吧串口發(fā)送就是按照ASCII表的規(guī)則來的。無論你發(fā)的是十進(jìn)制還是十六進(jìn)制最后都會(huì)轉(zhuǎn)換成對應(yīng)的符號
//發(fā)送浮點(diǎn)數(shù)
float b=3.2;//帶轉(zhuǎn)化數(shù)據(jù)
uchar conver_t[4];//一定要定義成**數(shù)組**類型
float b=3.2;
sprintf(conver_t,"%f",b);//通過%.1f /%.2f可以改變小數(shù)位數(shù)
UART_SendString(conver_t);
## 串口接收中斷的設(shè)置;一般用于上位機(jī)控制
**根據(jù)接受的數(shù)據(jù)做出響應(yīng),如燈的翻轉(zhuǎn),之類的**。
直接上代碼!
//串口控制led亮滅
#include "reg52.h"
#include "stdio.h"
sbit led=P1^5;
unsigned char rec;
void receive_dat( rec)
{
if(rec==0x00)
{
led=0;
}
else if(rec==0x01)
{
led=1;
}
else if(rec==0x02)
{
led=0;
}
else
{
led=1;
}
}
void UART_Sendchar(unsigned char d) //發(fā)送一個(gè)字節(jié)的數(shù)據(jù),形參d即為待發(fā)送數(shù)據(jù)。
{
SBUF=d; //將數(shù)據(jù)寫入到串口緩沖
while(!TI);
TI=0;
}
void uart_init(void)
{
TMOD|=0X20; //設(shè)置計(jì)數(shù)器工作方式 2
SCON=0X50; //設(shè)置為工作方式 1
PCON=0X00; //波特率加倍 4800倍后為9600
TH1=0XFd; //計(jì)數(shù)器初始值設(shè)置
TL1=0xfd;
TR1=1; //打開計(jì)數(shù)器;
ES=1; //打開串口中斷
EA=1; //打開總中斷
TR1=1; //打開計(jì)數(shù)器
ET1=0;
}
void main()
{
uart_init();
UART_Sendchar('E');
while(1)
{
}
}
void Uart() interrupt 4
{
if(RI==1)
{ RI=0;
rec=SBUF;
receive_dat(rec);
}
}
注意,本代碼不是萬能!如果你的程序很簡單那么這些代碼基本沒問題,但是小白我就曾經(jīng)遇到了一個(gè)問題。我在一個(gè)項(xiàng)目中用到了兩個(gè)中斷,定時(shí)器、串口中斷沖突。結(jié)果接收上位機(jī)的數(shù)據(jù)相關(guān)代碼一直不起作用。我以為是串口接收中斷函數(shù)有問題。在網(wǎng)上查了一下,可能的原因很多。解決辦法嘛主流的是增加中斷時(shí)間,然后還有一個(gè)就是改中斷號優(yōu)先級。我都嘗試過對我的程序來說沒有用。最后無意中在一個(gè)帖子看到降低串口波特率。我把9600改為4800后果然有用。遇到問題,多去查查。幾十億人,他們就做的全都對嘛?也會(huì)有類似的問題對吧。 |
|