admin管理员组文章数量:1559109
stm32系列博客:
stm32学习之旅① 开发环境搭建
stm32学习之旅② 固件库的使用及工程模板的建立
stm32学习之旅③ 从点灯到代码移植
stm32学习之旅④ usart串口和上位机通信
stm32学习之旅⑤ spi控制tft,从底层到底层的设计
目录:
一、认识其本质
(一)串口
(二)协议
(三)时序
(四)上位机
二、所需材料
三、usart的介绍
四、usart串口的配置
五、发送函数
(一)单字节发送
(二)数据流发送
六、接收函数
七、串口打印,重定向printf函数
一、认识其本质
(一)串口
串口是串行接口 (serial interface)的简称,它是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。一条信息的各位数据被逐位按顺序传送的通讯方式称为串行通讯。串行通讯的特点是:数据位的传送,按位顺序进行,最少只需一根传输线即可完成;成本低但传送速度慢。串行通讯的距离可以从几米到几千米;根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。
(二)协议
所谓协议,就是通信双方约定好的规定,通信双方只有遵守这个规定才能够完成任务。举个栗子就是周幽王烽火戏诸侯,双方约定好以烽火为信号进行通信,但是愚蠢的周幽王为博美人褒姒一笑破坏了这个规定,最后付出的代价是惨重的。可见,通信双方只有遵守协议才能够完成通信。
(三)时序
时序就是协议的实际化,它实质上是一些列的脉冲信号,通信双方将信息按照预先定好的规定(协议)转换成一系列的脉冲信号,通过总线发送给接收方,接收方再将接收到的数据按照规定进行解析,从而得到发送方发送过来的数据。
(四)上位机
上位机和下位机其实是一个相对的概念,上位机指的是可以直接发出操控命令的计算机,一般指pc机,能够显示各种信号变化(液压,水位,温度等),能够将信息直接传递给人。下位机是直接控制设备获取设备状况的计算机,一般是plc/单片机single chip microcomputer/slave computer/lower computer之类的,下位机需要pc机来对其进行控制。
二、所需材料
usb转ttl串口
串口助手, 密码:9vjy
三、usart的介绍
stm32有丰富的通讯外设,usart(universal synchronous asynchronous receiver transmitter)、spi(serial peripheral interface)、i2c(inter-integrated circuit)、can(controller area network),因为stm32有完整的且强大的固件库,这使得配置串口的难度大大降低了,和用软件io口模拟通信时序相比,硬件的支持可以大大提高通信的速率、大大降低出错的概率,从而提高了通信的质量和效率。用io口模拟usart难度较大,它对延时要求比较苛刻,且出错的概率较大,所以一般很少用io口模拟usart。io口模拟i2c比较常见,由于i2c的最高通信速度只有3.4m/s,单片机的io口速度可以完美驾驭。由于spi多用于一些较高速的通信,例如lcd、oled、tft显示器的写入,eeprom (electrically erasable programmable read only memory)的写入和读取,用io口模拟效果不是很理想,所以建议使用硬件自带接口。
关于usart,以下是官方的介绍
四、usart串口的配置
先来看一下stm32的系统结构
通过对stm32几个模块的操作,我们可以发现stm32外设配置的一些基本套路:打开相应的时钟->配置相应的引脚功能->声明对应的结构体->利用相应的init函数进行初始化
打开打开usatt1、gpioa、afio的时钟
void usart_config()
{
/*打开usatt1、gpioa、afio的时钟*/
rcc_apb2periphclockcmd( rcc_apb2periph_usart1 \
| rcc_apb2periph_gpioa | rcc_apb2periph_afio, enable);
/*配置对应的串口引脚*/
usart_release_gpio_init();
/*配置串口中断*/
usart_para_config();
usart_clearflag(usart1,usart_flag_tc); //清除发送完成标志位
nvic_config(); //初始化nvio
usart_cmd(usart1, enable); //使能串口1
}
配置相应的io口,将其设为复用推挽输出和浮空输入
void usart_release_gpio_init()
{
gpio_inittypedef gpio_initstruct;
/*配置pa9为复用推外输出*/
gpio_initstruct.gpio_pin = gpio_pin_9;
gpio_initstruct.gpio_mode = gpio_mode_af_pp;
gpio_initstruct.gpio_speed = gpio_speed_50mhz;
gpio_init(gpioa, &gpio_initstruct);
/*配置pa10为浮空输入*/
gpio_initstruct.gpio_pin = gpio_pin_10;
gpio_initstruct.gpio_mode = gpio_mode_in_floating;
gpio_init(gpioa, &gpio_initstruct);
}
配置nvic(nested vectored interrupt controller),即内嵌向量中断控制器,它是用来配置中断抢占优先级和从优先级(响应优先级)的
关于抢占优先级和响应优先级区别:
高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。
抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。
抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。
如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;
void nvic_config(void)
{
nvic_inittypedef nvic_initstructure; //nvic 初始化结构体声明
nvic_prioritygroupconfig(nvic_prioritygroup_1);
nvic_initstructure.nvic_irqchannel = usart1_irqn; //设置串口1 中断
nvic_initstructure.nvic_irqchannelpreemptionpriority = 0; //抢占优先级0
nvic_initstructure.nvic_irqchannelsubpriority = 0; //子优先级为0
nvic_initstructure.nvic_irqchannelcmd = enable; //使能
nvic_init(&nvic_initstructure);
}
配置串口协议
void usart_para_config(void)
{
usart_inittypedef usart_initstruct;
usart_initstruct.usart_baudrate = 115200;
usart_initstruct.usart_hardwareflowcontrol = usart_hardwareflowcontrol_none;
usart_initstruct.usart_mode = usart_mode_tx | usart_mode_rx;
usart_initstruct.usart_wordlength = usart_wordlength_8b;//8
usart_initstruct.usart_parity = usart_parity_no; //n
usart_initstruct.usart_stopbits = usart_stopbits_1; //1
usart_init(usart1, &usart_initstruct);
usart_itconfig(usart1, usart_it_rxne, enable); //使能接收中断
}
五、发送函数
(一)单字节发送
在main函数中调用usart_senddata(usart1, 0x08);这个函数就能够完成单字节的发送了
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "timer.h"
#include "usart.h"
int main()
{
systeminit(); //初始化系统,系统时钟设定为72mhz
systick_init(72); //配置systick,中断时间设置为72000/72000000 = 1us
usart_config();
while(1)
{
usart_senddata(usart1, 0x08);
delay_ms(100);
}
}
打开串口助手就能够看到串口发来的数据了
(二)数据流发送
数据流简单来说就是一串连续的信息序列,一串序列中有若干个字节,每个字节分别对应着通信双方预先约定好的数据含义,例如第一位代表地址、第二位代表数据流向、最后一位代表结束标志、其余位代表数据。数据流的长度可长可短,由通信双方确定,但通信的过程中不能够变化。
定义一个协议栈
typedef struct
{
u8 head;
u8 tail;
u8 direction;
u8 data[4];
}send_stack;
send_stack tx_stack;
void tx_stack_init()
{
tx_stack.head = 0xaa; //协议栈头,起始位,1010 1010b
tx_stack.direction = 0x09;//数据流方向,0x09表示从单片机发出
memset(tx_stack.data, 0, sizeof(tx_stack.data));//把tx_stack.data[]全部初始化为零
tx_stack.tail = 0xdd; //协议栈尾,结束位,1101 1101b,栈头和栈尾最好能互补
}
将协议栈内的数据依次发出
void usart_senddata()
{
u8 i;
while(usart_getflagstatus(usart1, usart_flag_txe) == reset);
usart_senddata(usart1, tx_stack.head);
while(usart_getflagstatus(usart1, usart_flag_txe) == reset);
usart_senddata(usart1, tx_stack.direction);
while(usart_getflagstatus(usart1, usart_flag_txe) == reset);
for(i = 0; i < 4; i )
{
usart_senddata(usart1, tx_stack.data[i]);
while(usart_getflagstatus(usart1, usart_flag_txe) == reset);
}
usart_senddata(usart1, tx_stack.tail);
while(usart_getflagstatus(usart1, usart_flag_txe) == reset);
}
打开串口助手可以看到串口发来的数据流
六、接收函数
接收函数和发送函数类似,先定义接收协议栈
typedef struct
{
u8 head;
u8 tail;
u8 direction;
u8 data[4];
u8 lock_flag;
u8 data_pt;
}receive_stack;
receive_stack rx_stack;
void rx_stack_init()
{
rx_stack.head = 0x00; //协议栈头,起始位
rx_stack.direction = 0x00; //数据流方向,0x09表示从单片机发出
memset(rx_stack.data, 0, sizeof(rx_stack.data));//把tx_stack.data[]全部初始化为零
rx_stack.tail = 0x00; //协议栈尾,结束位
rx_stack.data_pt = 0x00;
rx_stack.lock_flag = unlock;
}
接收数据需要借助中断来完成
void usart1_irqhandler(void)
{
u8 receive_data;
if(usart_getitstatus(usart1, usart_it_rxne) != reset) //判断读寄存器是否非空
{
receive_data = usart_receivedata(usart1); //接收单个字节的串口数据
if(rx_stack.lock_flag == unlock) //如果接收协议栈未锁柱
{
if(receive_data == 0xaa)
{
rx_stack.head = receive_data;
}
else if(receive_data == 0xf9)
{
rx_stack.direction = receive_data;
}
else if(receive_data == 0xdd)
{
rx_stack.tail = receive_data;
if(rx_stack.data_pt >= 4)// && (rx_stack.tail == 0xdd))
{
rx_stack.data_pt = 0;
rx_stack.lock_flag = lock;
}
}
else
{
rx_stack.data[rx_stack.data_pt] = receive_data;
rx_stack.data_pt ;
if(rx_stack.data_pt > 4)// && (rx_stack.tail == 0xdd))
{
rx_stack.data_pt = 0;
rx_stack.lock_flag = lock;
}
}
}
usart_clearitpendingbit(usart1, usart_it_rxne);//清除接受中断标志
}
}
将接收到的数据流进行解析,用灯的亮灭将控制命令现实化
void ptr_handle(u8 *stack)
{
u8 *stack_pt;
stack_pt = stack;
if(*stack_pt == 0xff)
{
key0.key_change_bit = chge_in;
if((key0.led_on_off % 2) == 1)
{
}
else
{
key0.led_on_off = key0.led_on_off >=3 ? 0 : key0.led_on_off 1;
}
}
else
{
key0.key_change_bit = chge_in;
if((key0.led_on_off % 2) == 1)
{
key0.led_on_off = key0.led_on_off >=3 ? 0 : key0.led_on_off 1;
}
else
{
}
}
stack_pt ;
if(*stack_pt == 0xff)
{
key1.key_change_bit = chge_in;
if((key1.led_on_off % 2) == 1)
{
}
else
{
key1.led_on_off = key1.led_on_off >=3 ? 0 : key1.led_on_off 1;
}
}
else
{
key1.key_change_bit = chge_in;
if((key1.led_on_off % 2) == 1)
{
key1.led_on_off = key1.led_on_off >=3 ? 0 : key1.led_on_off 1;
}
else
{
}
}
stack_pt ;
if(*stack_pt == 0xff)
{
key2.key_change_bit = chge_in;
if((key2.led_on_off % 2) == 1)
{
}
else
{
key2.led_on_off = key2.led_on_off >=3 ? 0 : key2.led_on_off 1;
}
}
else
{
key2.key_change_bit = chge_in;
if((key2.led_on_off % 2) == 1)
{
key2.led_on_off = key2.led_on_off >=3 ? 0 : key2.led_on_off 1;
}
else
{
}
}
stack_pt ;
if(*stack_pt == 0xff)
{
key3.key_change_bit = chge_in;
if((key3.led_on_off % 2) == 1)
{
}
else
{
key3.led_on_off = key3.led_on_off >=3 ? 0 : key3.led_on_off 1;
}
}
else
{
key3.key_change_bit = chge_in;
if((key3.led_on_off % 2) == 1)
{
key3.led_on_off = key3.led_on_off >=3 ? 0 : key3.led_on_off 1;
}
else
{
}
}
rx_stack.lock_flag = unlock;
}
主函数要做的,就是循环判断是否有灯的状态需要改变,每次接收到上位机发来的命令后把当前的状态发送到上位机
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "timer.h"
#include "gpio_config.h"
#include "usart.h"
int main()
{
systeminit(); //初始化系统,系统时钟设定为72mhz
systick_init(72); //配置systick,中断时间设置为72000/72000000 = 1us
led_gpio_init();
usart_config();
tx_stack_init();
while(1)
{
if (key0.key_change_bit == chge_in)
{
if((key0.led_on_off % 2) == 1)
{
led0_on; //打开led0
tx_stack.data[0] = 0xff;
}
else
{
led0_off; //关闭led0
tx_stack.data[0] = 0x00;
}
key0.key_change_bit = no_chge;
}
if (key1.key_change_bit == chge_in)
{
if((key1.led_on_off % 2) == 1)
{
led1_on; //打开led1
tx_stack.data[1] = 0xff;
}
else
{
led1_off; //关闭led1
tx_stack.data[1] = 0x00;
}
key1.key_change_bit = no_chge;
}
if (key2.key_change_bit == chge_in)
{
if((key2.led_on_off % 2) == 1)
{
led2_on; //打开led2
tx_stack.data[2] = 0xff;
}
else
{
led2_off; //关闭led2
tx_stack.data[2] = 0x00;
}
key2.key_change_bit = no_chge;
}
if (key3.key_change_bit == chge_in)
{
if((key3.led_on_off % 2) == 1)
{
led3_on; //打开led3
tx_stack.data[3] = 0xff;
}
else
{
led3_off; //关闭led3
tx_stack.data[3] = 0x00;
}
key3.key_change_bit = no_chge;
usart_senddata();
}
if(rx_stack.lock_flag == lock)
{
ptr_handle(rx_stack.data); //运行协议解析函数
}
}
}
七、串口打印,重定向printf函数
使用printf函数,需要包含其头文件stdio.h,即标准输入输出头文件,std是standard的缩写,是标准的意思;i是input,输入;o是output,输出;h是head,头,头文件的意思。
然后在usart,c中编写字符写入函数,将格式化后的字符串依次写入到发送总线上
int fputc(int ch, file *f)
{
while(usart_getflagstatus(usart1,usart_flag_tc)==reset)
{
}
usart_senddata(usart1, (uint8_t) ch);
return ch;
}
在主函数中调用printf函数,实现格式化输出
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "timer.h"
#include "usart.h"
int main()
{
systeminit(); //初始化系统,系统时钟设定为72mhz
systick_init(72); //配置systick,中断时间设置为72000/72000000 = 1us
led_gpio_init();
usart_config();
tx_stack_init();
while(1)
{
printf("hello world!\n%d\n\n", 12345);
delay_ms(100);
}
}
本文标签:
j9九游会老哥俱乐部交流区的版权声明:本文标题:stm32串口通信、串口调试助手 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/xitong/1727404931a1113266.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。