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