STM32入门(11-20)
USART 驱动程序
平时除了使用它进行程序下载还可以用来与电脑端进行串口通信
发送程序
本节用到的固件库函数
- RCC_APB2PeriphClockCmd(手册 15.2.22)
- USART_GetITStatus(手册 21.2.24)
- USART_ReceiveData(手册 21.2.13)
- USART_GetFlagStatus(手册 21.2.22)
- RCC_APB1PeriphClockCmd(手册 15.2.23)
- NVIC_Init(手册 13.2.4)
- NVIC_PriorityGroupConfig(手册 13.2.3)
- USART_Init(手册 21.2.2)
- USART_Cmd(手册 21.2.4)
- USART_ITConfig(手册 21.2.5)
- USART_SendData(手册 21.2.12)
注意
sys.h,sys.c,delay.c,delay.h 和 10 一样;只需增加 usart.h,usart.c 文件(Lib 文件记得添加 stm32f10x_usart.c)
usart.h
# ifndef __USART_H
# define __USART_H
# include <stdarg.h>
# include <stdlib.h>
# include <string.h>
# include "stdio.h"
# include "sys.h"
# define USART_n USART1 //定义使用printf函数的串口,其他串口要使用USART_printf专用函数发送
# define USART1_REC_LEN 200 //定义USART1最大接收字节数
# define USART2_REC_LEN 200 //定义USART2最大接收字节数
# define USART3_REC_LEN 200 //定义USART3最大接收字节数
//不使用某个串口时要禁止此串口,以减少编译量
# define EN_USART1 1 //使能(1)/禁止(0)串口1
# define EN_USART2 0 //使能(1)/禁止(0)串口2
# define EN_USART3 0 //使能(1)/禁止(0)串口3
extern u8 USART1_RX_BUF[USART1_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u8 USART2_RX_BUF[USART2_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u8 USART3_RX_BUF[USART3_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART1_RX_STA; //接收状态标记
extern u16 USART2_RX_STA; //接收状态标记
extern u16 USART3_RX_STA; //接收状态标记
//函数声明
void USART1_Init(u32 bound);//串口1初始化并启动
void USART2_Init(u32 bound);//串口2初始化并启动
void USART3_Init(u32 bound);//串口3初始化并启动
void USART1_printf(char* fmt,...); //串口1的专用printf函数
void USART2_printf(char* fmt,...); //串口2的专用printf函数
void USART3_printf(char* fmt,...); //串口3的专用printf函数
# endif
usart.c
- 本次程序只用到 EN_USART1,其他2,3的函数和1都是差不多的,只需改一下端口(注意对应的端口组)
- 从 if 起 到 endif 中 保证 “不使用/不输入/不调用 semihosting“
- USART_RX_STA:是个接收状态标记,它既指接收状态又表明接收到数据的位数
- USART_RX_STA&0x8000:判断是否已经接收到了0x0a,0x0a 是LF(line feed)换行的意思,光标到达下一行,也是判断数据接收结束的标志。
- 中断中是以是否接收到0x0d 0x0a这两个数据,判断数据是否发送结束的
- 0x0d和0x0a 代表:回车+换行
- 如果设置了接收中断,当检测到RXNE 标志位时会执行中断服务函数。读取接收数据寄存器(RDR)中的数据可以自动清除RXNE 标志位。RXNE 标志位在下一字符接收前也可以手动清除,避免溢出错误。
配置步骤:(可以查看 ARM嵌入式编程与实战应用手册 5.3.1)
# include "sys.h"
# include "usart.h"
//使UASRT串口可用printf函数发送
//在usart.h文件里可更换使用printf函数的串口号
# if 1
# pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART_n->SR & 0X40) == 0); //循环发送,直到发送完毕
USART_n->DR = (u8) ch;
return ch;
}
# endif
/*
USART1串口相关程序
*/
# if EN_USART1 //USART1使用与屏蔽选择
u8 USART1_RX_BUF[USART1_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART1_RX_STA = 0; //接收状态标记
/*
USART1专用的printf函数
当同时开启2个以上串口时,printf函数只能用于其中之一,其他串口要自创独立的printf函数
调用方法:USART1_printf("123"); //向USART2发送字符123
*/
void USART1_printf (char *fmt, ...)
{
char buffer[USART1_REC_LEN + 1]; // 数据长度
u8 i = 0;
va_list arg_ptr;
va_start(arg_ptr, fmt);
vsnprintf(buffer, USART1_REC_LEN + 1, fmt, arg_ptr);
while ((i < USART1_REC_LEN) && (i < strlen(buffer)))
{
USART_SendData(USART1, (u8) buffer[i++]);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
va_end(arg_ptr);
}
void USART1_Init(u32 bound) //串口1初始化并启动
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX PA.9(发送)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10(接收)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//选择 USART1 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//一般设置为9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启ENABLE/关闭DISABLE中断
USART_Cmd(USART1, ENABLE); //使能串口
}
void USART1_IRQHandler(void) //串口1中断服务程序(固定的函数名不能修改)
{
u8 Res;//中间变量,读取的数据进行保存
//以下是字符串接收到USART1_RX_BUF[]的程序,(USART1_RX_STA&0x3FFF)是数据的长度(不包括回车)
//当(USART1_RX_STA&0xC000)为真时表示数据接收完成,即超级终端里按下回车键。
//在主函数里写判断if(USART1_RX_STA&0xC000),然后读USART1_RX_BUF[]数组,读到0x0d 0x0a即是结束。
//注意在主函数处理完串口数据后,要将USART1_RX_STA清0
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)查看RXNE寄存器是否为空,以此判断是否有数据发送过来
{
Res = USART_ReceiveData(USART1); //(USART1->DR); //读取接收到的数据
printf("%c", Res); //把收到的数据以 a符号变量 发送回电脑
if((USART1_RX_STA & 0x8000) == 0) //判断接收是否未完成这里判断接收完的依据就是收到了0x0a
{
if(USART1_RX_STA & 0x4000) //接收到了0x0d 如果接收到了0x0d,那么再进一步执行是否接收到0x0a的判断
{
if(Res != 0x0a)USART1_RX_STA = 0; //没有接收到0x0a那么说明,数据未正确传输或者接收错误,重新开始判断;但是这里没有将接收到的数据进行清空,也没有退出接收中断,此程序只是从头开始执行接收判断
else USART1_RX_STA |= 0x8000; //接收完成了,收到了0x0a那么标志位USART_RX_STA最高位置1其他位不变,将不再进行数据检测与存储
}
else //还没收到0X0D
{
if(Res == 0x0d)USART1_RX_STA |= 0x4000; //收到了数据0x0d,标志位USART_RX_STA 次高位置1其他位不变
else //如果没有接收到数据0x0d,执行判断是否存储数组已满,已满则重新开始接收
{
USART1_RX_BUF[USART1_RX_STA & 0X3FFF] = Res ; //将收到的数据放入数组
USART1_RX_STA++; //数组地址加一,向后排
if(USART1_RX_STA > (USART1_REC_LEN - 1))USART1_RX_STA = 0; //接收数据错误,超出数组大小,重新开始接收
}
}
}
}
}
# endif
/*
USART2串口相关程序
*/
# if EN_USART2 //USART2使用与屏蔽选择
u8 USART2_RX_BUF[USART2_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART2_RX_STA = 0; //接收状态标记
/*
USART2专用的printf函数
当同时开启2个以上串口时,printf函数只能用于其中之一,其他串口要自创独立的printf函数
调用方法:USART2_printf("123"); //向USART2发送字符123
*/
void USART2_printf (char *fmt, ...)
{
char buffer[USART2_REC_LEN + 1]; // 数据长度
u8 i = 0;
va_list arg_ptr;
va_start(arg_ptr, fmt);
vsnprintf(buffer, USART2_REC_LEN + 1, fmt, arg_ptr);
while ((i < USART2_REC_LEN) && (i < strlen(buffer)))
{
USART_SendData(USART2, (u8) buffer[i++]);
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
}
va_end(arg_ptr);
}
void USART2_Init(u32 bound) //串口1初始化并启动
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能UART2所在GPIOA的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能串口的RCC时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //设置USART2的RX接口是PA3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //设置USART2的TX接口是PA2
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART2 初始化设置
USART_InitStructure.USART_BaudRate = bound;//一般设置为9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART2, &USART_InitStructure); //初始化串口
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启ENABLE/关闭DISABLE中断
USART_Cmd(USART2, ENABLE); //使能串口
//Usart2 NVIC 配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
}
void USART2_IRQHandler(void) //串口2中断服务程序(固定的函数名不能修改)
{
u8 Res;
//以下是字符串接收到USART2_RX_BUF[]的程序,(USART2_RX_STA&0x3FFF)是数据的长度(不包括回车)
//当(USART2_RX_STA&0xC000)为真时表示数据接收完成,即超级终端里按下回车键。
//在主函数里写判断if(USART2_RX_STA&0xC000),然后读USART2_RX_BUF[]数组,读到0x0d 0x0a即是结束。
//注意在主函数处理完串口数据后,要将USART2_RX_STA清0
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res = USART_ReceiveData(USART2); //(USART1->DR); //读取接收到的数据
printf("%c", Res); //把收到的数据以 a符号变量 发送回电脑
if((USART2_RX_STA & 0x8000) == 0) //接收未完成
{
if(USART2_RX_STA & 0x4000) //接收到了0x0d
{
if(Res != 0x0a)USART2_RX_STA = 0; //接收错误,重新开始
else USART2_RX_STA |= 0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res == 0x0d)USART2_RX_STA |= 0x4000;
else
{
USART2_RX_BUF[USART2_RX_STA & 0X3FFF] = Res ; //将收到的数据放入数组
USART2_RX_STA++; //数据长度计数加1
if(USART2_RX_STA > (USART2_REC_LEN - 1))USART2_RX_STA = 0; //接收数据错误,重新开始接收
}
}
}
}
}
# endif
# if EN_USART3 //如果使能了接收
u8 USART3_RX_BUF[USART3_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART3_RX_STA = 0; //接收状态标记
/*
USART3专用的printf函数
当同时开启2个以上串口时,printf函数只能用于其中之一,其他串口要自创独立的printf函数
调用方法:USART3_printf("123"); //向USART3发送字符123
*/
void USART3_printf (char *fmt, ...)
{
char buffer[USART3_REC_LEN + 1]; // 数据长度
u8 i = 0;
va_list arg_ptr;
va_start(arg_ptr, fmt);
vsnprintf(buffer, USART3_REC_LEN + 1, fmt, arg_ptr);
while ((i < USART3_REC_LEN) && (i < strlen(buffer)))
{
USART_SendData(USART3, (u8) buffer[i++]);
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
}
va_end(arg_ptr);
}
void USART3_Init(u32 BaudRate) //USART3初始化并启动
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE); //使能UART3所在GPIOB的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //使能串口的RCC时钟
//串口使用的GPIO口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//设置USART3的RX接口是PB11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//接口模式 浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//设置USART3的TX接口是PB10
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度50MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//接口模式 复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
//配置串口
USART_InitStructure.USART_BaudRate = BaudRate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART3, &USART_InitStructure);//配置串口3
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//使能串口接收中断
//USART_ITConfig(USART3, USART_IT_TXE, ENABLE);//串口发送中断在发送数据时开启
USART_Cmd(USART3, ENABLE);//使能串口3
//串口中断配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;//允许USART3中断
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//中断等级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//串口3中断服务程序(固定的函数名不能修改)
//调用方法:if(USART3_RX_STA&0xC000){ 加入数据处理程序 }//标志位是0xC000表示收到数据串完成。
void USART3_IRQHandler(void)
{
u8 Res;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) //接收中断
{
Res = USART_ReceiveData(USART3); //读取接收到的数据
if(Res == 'S') //判断数据是否是STOP(省略读取S)
{
USART3_RX_STA = 1; //如果是STOP则标志位为1
}
else if(Res == 'K') //判断数据是否是OK(省略读取K)
{
USART3_RX_STA = 2; //如果是OK则标志位为2
}
}
}
# endif
main.c
-
while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);
表示:防止前一个数据还没结束就结束发送程序,采用一个 while 循环,如果中断标志为0那么表示发送完毕了
-
串口只能以16进制显示,所以想发送字符需要查表或者把要发送的字符用 ’ ' 括起来也可以
-
也可以用C语言的 printf 函数发送数据(这个需要在设置那打钩);但是 printf 函数只能用在一个串口上,所以如果多个串口发送则需要自己写一个 printf 函数
/*********************************************************************************************
程序名: USART串口发送程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "usart.h"
int main (void){//主程序
u8 a=7,b=8;
//初始化程序
RCC_Configuration(); //时钟设置
USART1_Init(115200); //串口初始化(参数是波特率)
//主循环
while(1){
/* 发送方法1 */
//参数1:向哪个端口发送数据 参数2:发送16进制数据,必须8位
USART_SendData(USART1 , 0x55); //发送单个数值
while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET); //检查发送中断标志位
/* 发送方法2 */
// printf("STM32F103 "); //纯字符串发送数据到串口
// printf("STM32 %d %d ",a,b); //纯字符串和变量发送数据到串口,a符号变量
/* 发送方法3 */
// USART1_printf("STM32 %d %d ",a,b);
delay_ms(1000); //延时,如果不延时串口助手上发送会很快!
}
}
注:如果编译错误,则需要设置那打勾!!
实验现象
接收程序
sys.h,sys.c,delay.c,delay.h 和 10 一样; usart.h,usart.c 文件也和 11.1 差不多只需改几个函数即可(Lib 文件记得添加 stm32f10x_usart.c)
usart.h 需要改的地方:
//把这个函数所有内容
void USART1_IRQHandler(void)//串口1中断服务程序(固定的函数名不能修改)
{
...
}
//替换成
+ void USART1_IRQHandler(void)//串口1中断服务程序(固定的函数名不能修改)
+ {
+ u8 a;
+ if(USART_GetITStatus(USART1,USART_IT_RXNE)!= RESET)//接收中断(接收到的数据必须是0x0d 0x0a结尾)
+ {
+ a =USART_ReceiveData(USART1);//读取接收到的数据
+ printf("%c",a); //把收到的数据发送回电脑
+ }
+ }
//把 USART1_Init 函数里的
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启ENABLE/关闭DISABLE中断
//替换成
+ USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);//开启ENABLE/关闭DISABLE中断
main.c
查询方式:失去实时性,因为这部分串口数据的查询只是循环函数当中的一部分,如果主函数要等待查询的数据,可能时间已经过去了很久,于是最好采用中断方式(中断的方法则需要把 while 里面的代码注释掉,并且把上面的USART1_Init 函数里把中断打开)
/*********************************************************************************************
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "usart.h"
int main (void){//主程序
u8 a;//串口每次接收的数据都是8位的十六进制数
//初始化程序
RCC_Configuration(); //时钟设置
USART1_Init(115200); //串口初始化(参数是波特率)
//主循环
while(1){
//查询方式接收
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET){ //查询串口待处理标志位,标志位等于1代表有数据
a =USART_ReceiveData(USART1);//读取接收到的数据
printf("%c",a); //把收到的数据发送回电脑
}
// delay_ms(1000); //延时
}
}
实验现象
USART 控制程序
sys.h,sys.c,delay.c,delay.h 和 10 一样;led.c,led.h,key.c,key.h 和 8 相同,buzzer.c,buzzer.h 和 10.1 相同;usart.c,usart.h和 11.2 相同(用的是查询模式注意关闭中断)
main.c
/*********************************************************************************************
程序名: USART串口控制程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "key.h"
# include "buzzer.h"
# include "usart.h"
int main (void){//主程序
u8 a;
//初始化程序
RCC_Configuration(); //时钟设置
LED_Init();//LED初始化
KEY_Init();//按键初始化
BUZZER_Init();//蜂鸣器初始化
USART1_Init(115200); //串口初始化(参数是波特率)
//主循环
while(1){
//查询方式接收
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET){ //查询串口待处理标志位
a =USART_ReceiveData(USART1);//读取接收到的数据
switch (a){
case '0':
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(0)); //LED控制
printf("%c:LED1 OFF ",a); //
break;
case '1':
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1)); //LED控制
printf("%c:LED1 ON ",a); //
break;
case '2':
BUZZER_BEEP1(); //蜂鸣一声
printf("%c:BUZZER ",a); //把收到的数据发送回电脑
break;
default:
break;
}
}
//按键控制
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
delay_ms(20); //延时20ms去抖动
if(!GPIO_ReadInputDataBit(KEYPORT,KEY1)){ //读按键接口的电平
while(!GPIO_ReadInputDataBit(KEYPORT,KEY1)); //等待按键松开
printf("KEY1 "); //
}
}
if(!GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //读按键接口的电平
delay_ms(20); //延时20ms去抖动
if(!GPIO_ReadInputDataBit(KEYPORT,KEY2)){ //读按键接口的电平
while(!GPIO_ReadInputDataBit(KEYPORT,KEY2)); //等待按键松开
printf("KEY2 "); //
}
}
// delay_ms(1000); //延时
}
}
实验现象
超级终端串口控制程序
sys.h,sys.c,delay.c,delay.h 和 10 一样;led.c,led.h,key.c,key.h 和 8 相同,buzzer.c,buzzer.h 和 10.1 相同;usart.c,usart.h 和 11.2 相同(用的是查询模式注意打开中断)
main.c
/*********************************************************************************************
程序名: 超级终端串口控制程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "key.h"
# include "buzzer.h"
# include "usart.h"
int main (void){//主程序
RCC_Configuration();
LED_Init();//LED初始化
KEY_Init();//按键初始化
BUZZER_Init();//蜂鸣器初始化
USART1_Init(115200); //串口初始化,参数中写波特率
USART1_RX_STA=0xC000; //初始值设为有回车的状态,即显示一次欢迎词
while(1){
if(USART1_RX_STA&0xC000){ //如果标志位是0xC000表示收到数据串完成,可以处理。
if((USART1_RX_STA&0x3FFF)==0){ //单独的回车键再显示一次欢迎词
printf("\033[1;47;33m\r\n"); //设置颜色(参考超级终端使用)
printf(" 1y--开LED1灯 1n--关LED1灯 \r\n");
printf(" 2y--开LED2灯 2n--关LED2灯 \r\n");
printf(" 请输入控制指令,按回车键执行! \033[0m\r\n");
}else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='1' && USART1_RX_BUF[1]=='y'){ //判断数据是不是2个,第一个数据是不是“1”,第二个是不是“y”
GPIO_SetBits(LEDPORT,LED1); //LED灯都为高电平(1)
printf("1y -- LED1灯已经点亮!\r\n");
}else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='1' && USART1_RX_BUF[1]=='n'){
GPIO_ResetBits(LEDPORT,LED1); ////LED灯都为低电平(0)
printf("1n -- LED1灯已经熄灭!\r\n");
}else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='2' && USART1_RX_BUF[1]=='y'){
GPIO_SetBits(LEDPORT,LED2); //LED灯都为高电平(1)
printf("2y -- LED2灯已经点亮!\r\n");
}else if((USART1_RX_STA&0x3FFF)==2 && USART1_RX_BUF[0]=='2' && USART1_RX_BUF[1]=='n'){
GPIO_ResetBits(LEDPORT,LED2); ////LED灯都为低电平(0)
printf("2n -- LED2灯已经熄灭!\r\n");
}else{ //如果以上都不是,即是错误的指令。
printf("指令错误!\r\n");
}
USART1_RX_STA=0; //将串口数据标志位清0
}
}
}
就算是按回车一次将数据发送,一串的数据也是按照排队一个个发过去的!!!
那么每次接收到一个比如0x55这样的以后,他就会中断处理一次!!!
要是收到0x55 0x55 0x55 0x55,他就会中断处理四次!!!每一次中断的函数都会执行!!!
也就是说!!!一个状态变量来判断我这个接收的一串的数据有没有结束!!!
实验现象
需要把超级终端编码改成【GB2312】,不然中文显示不了
RTC原理与驱动程序
本节用到的固件库函数
-
PWR_BackupAccessCmd(手册 14.2.2)
-
BKP_DeInit(手册 5.2.1)
-
RCC_LSEConfig(手册 15.2.16)
-
RCC_GetFlagStatus(手册 15.2.29)
-
RCC_RTCCLKConfig(手册 15.2.18)
-
RCC_RTCCLKCmd(手册 15.2.19)
-
RTC_WaitForSynchro(手册 16.2.10)
-
RTC_WaitForLastTask(手册 16.2.9)
-
RTC_SetPrescaler(手册 16.2.6)
-
RTC_ITConfig(手册 16.2.1)
-
BKP_ReadBackupRegister(手册 5.2.8)
-
BKP_WriteBackupRegister(手册 5.2.7)
-
RCC_ClearFlag(手册 15.2.30)
-
BKP_TamperPinCmd(手册 5.2.3)
-
BKP_RTCOutputConfig(手册 5.2.5)
-
RTC_ClearITPendingBit(手册 16.2.14)
-
RTC_SetCounter(手册 16.2.5)
-
RTC_GetCounter(手册 16.2.4)
RTC 介绍
RTC和后备寄存器不会被系统或电源复位源复位;当从待机模式唤醒时,也不会被复位。实时时钟具有一组连续运行的计数器,可以通过适当的软件提供日历时钟功能,还具有闹钟中断和阶段性中断功能;RTC具有一个32位的可编程计数器,使用比较寄存器可以进行长时间的测量。有一个20位的预分频器用于时基时钟,默认情况下时钟为32.768kHz时,它将产生一个1秒长的时间基准。
- STM32的RTC只用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器(跟51的不一样)。
- 通过设置可以让这个计数器1秒加1,从0-0XFFFFFFFF大约可计时136年。
- 时间起点一般设置为1970-01-01 00:00:00(因现有函数如此定义)
如果要读当前的年月日时分秒,先读出32位RTC计数器值,然后以1970-01-01 00:00:00为起点,加上计数器中的秒数,再换算成年月日时分秒,即可得出当前时间。
其他文件不变,只需增加 rtc,c,rtc.h(Lib 文件记得添加 stm32f10x_rtc.c)
rtc.h
# ifndef __RTC_H
# define __RTC_H
# include "sys.h"
//全局变量的声明,在rtc.c文件中定义
//以下2条是使用extern语句声明全局变量
//注意:这里不能给变量赋值
extern u16 ryear;
extern u8 rmon,rday,rhour,rmin,rsec,rweek;
u8 RTC_Get(void);//读出当前时间值
void RTC_First_Config(void);//首次启用RTC的设置(如果备用电池断开过)
void RTC_Config(void);//实时时钟初始化(备用电池没断开过)
u8 Is_Leap_Year(u16 year);//判断是否是闰年函数
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//写入当前时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);//按年月日计算星期
# endif
rtc.c
- 配置步骤可以参考 ARM嵌入式编程与实战应用手册 8.3.2
/*
//时间读写与设置说明//
1,在mani函数开头放入RTC_Config();就可以使能时钟了。
在RTC_Config();函数中自带判断是不是首次使用RTC
2,使用 RTC_Get();读出时间。读出的数据存放在:
年 ryear (16位)
月 rmon (以下都是8位)
日 rday
时 rhour
分 rmin
秒 rsec
周 rweek
3,使用 RTC_Set(4位年,2位月,2位日,2位时,2位分,2位秒); 写入时间。例如:RTC_Get(2017,08,06,21,34,00);
其他函数都是帮助如上3个函数的,不需要调用。
注意要使用RTC_Get和RTC_Set的返回值,为0时表示读写正确。
*/
# include "sys.h"
# include "rtc.h"
//以下2条全局变量--用于RTC时间的读取
u16 ryear; //4位年
u8 rmon,rday,rhour,rmin,rsec,rweek;//2位月日时分秒周
void RTC_First_Config(void)
{
//<1> 当系统复位后,对后备寄存器和RTC 的访问将被禁止,这是为了防止对后备区域的意外写操作。因此在配置RTC 模块前应先设置寄存器RCC_APB1ENR 的PWREN 和BKPEN位,使能电源和后备接口时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);//启用PWR和BKP的时钟(from APB1)
//使能后备区域时钟后还要使能电源的寄存器PWR_CR 的DBP 位来取消后备区域的写保护。
PWR_BackupAccessCmd(ENABLE);//后备域解锁
BKP_DeInit();//备份寄存器模块复位
//设置RTC 时钟源为LSE 之前要先等待LSE 时钟启动,保证LSE 时钟正常起振
RCC_LSEConfig(RCC_LSE_ON);//外部32.768KHZ晶振开启
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);//等待稳定,设置后需要等待启动
//<2> LSE 时钟起振成功后才可以设置RTC 时钟源为LSE 时钟并使能RTC,由于RTC 使用的时钟源与APB1 总线时钟并非同一时钟源,因此两者的时钟信号并不是同步的,需要等待RTC和APB1 时钟同步
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//RTC时钟源配置成LSE(外部低速晶振32.768KHZ)
RCC_RTCCLKCmd(ENABLE);//RTC开启
RTC_WaitForSynchro();//等待同步,开启后需要等待APB1时钟与RTC时钟同步,才能读写寄存器
RTC_WaitForLastTask();//等待更新结束,读写寄存器前,要确定上一个操作已经结束
RTC_SetPrescaler(32767);//设置RTC分频器,使RTC时钟为1Hz,RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)
RTC_WaitForLastTask();//等待寄存器写入完成
//当不使用RTC秒中断,可以屏蔽下面2条
// RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断
// RTC_WaitForLastTask();//等待写入完成
}
void RTC_Config(void)
{
//在BKP的后备寄存器1中,存了一个特殊字符0xA5A5
//第一次上电或后备电源掉电后,该寄存器数据丢失,表明RTC数据丢失,需要重新配置
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
{
RTC_First_Config();//重新配置RTC
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5(用户自己定义的0xa5a5用来判断是否数据丢失)
}
else
{
//若后备寄存器没有掉电,则无需重新配置RTC
//这里我们可以利用RCC_GetFlagStatus()函数查看本次复位类型
if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
{
//这是上电复位
}
else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
{
//这是外部RST管脚复位
}
RCC_ClearFlag();//清除RCC中复位标志
//虽然RTC模块不需要重新配置,且掉电后依靠后备电池依然运行
//但是每次上电后,还是要使能RTCCLK
RCC_RTCCLKCmd(ENABLE);//使能RTCCLK
RTC_WaitForSynchro();//等待RTC时钟与APB1时钟同步
//当不使用RTC秒中断,可以屏蔽下面2条
// RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断
// RTC_WaitForLastTask();//等待操作完成
}
# ifdef RTCClockOutput_Enable
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);
BKP_TamperPinCmd(DISABLE);
BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
# endif
}
void RTC_IRQHandler(void)//如果启动了中断就会执行(当前没开启中断)
{
if(RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
}
RTC_ClearITPendingBit(RTC_IT_SEC);
RTC_WaitForLastTask();
}
void RTCAlarm_IRQHandler(void){ //闹钟中断处理(启用时必须调高其优先级)
if(RTC_GetITStatus(RTC_IT_ALR) != RESET)
{
}
RTC_ClearITPendingBit(RTC_IT_ALR);
RTC_WaitForLastTask();
}
//判断是否是闰年函数
//月份 1 2 3 4 5 6 7 8 9 10 11 12
//闰年 31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year)
{
if ((year % 4 == 0&& year%100!=0) || year % 400 == 0)//能被4整除并且不能被100整除 或者 能被400整除就是闰年
return 1;
else
return 0;
}
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//月份数据表
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年的月份日期表
//写入时间
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)//写入当前时间(1970~2099年有效),
{
u16 t;
u32 seccount=0;
if(syear<2000||syear>2099)//syear范围1970-2099,此处设置范围为2000-2099
return 1;
for(t=1970;t<syear;t++)
{
if(Is_Leap_Year(t))
seccount+=31622400;//闰年的秒总数
else
seccount+=31536000;//平年的秒总数
}
smon-=1;
for(t=0;t<smon;t++)//把前面月份的秒钟数相加
{
seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)
seccount+=86400;//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
seccount+=(u32)hour*3600;//小时秒钟数
seccount+=(u32)min*60; //分钟秒钟数
seccount+=sec;//最后的秒钟加上去
RTC_First_Config(); //重新初始化时钟
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
RTC_SetCounter(seccount);//把换算好的计数器值写入
RTC_WaitForLastTask(); //等待写入完成
return 0; //返回值:0,成功;其他:错误代码.
}
//读出时间
u8 RTC_Get(void)//读出当前时间值 //返回值:0,成功;其他:错误代码.
{
static u16 daycnt=0;
u32 timecount=0;
u32 temp=0;
u16 temp1=0;
timecount=RTC_GetCounter();
temp=timecount/86400; //得到天数(秒钟数对应的)
if(daycnt!=temp)//超过一天了
{
daycnt=temp;
temp1=1970; //从1970年开始
while(temp>=365)
{
if(Is_Leap_Year(temp1))//是闰年
{
if(temp>=366)
temp-=366;//闰年的秒钟数
else
{
temp1++;break;
}
}
else
temp-=365; //平年
temp1++;
}
ryear=temp1;//得到年份
temp1=0;
while(temp>=28)//超过了一个月
{
if(Is_Leap_Year(ryear)&&temp1==1)//当年是不是闰年/2月份
{
if(temp>=29)
temp-=29;//闰年的秒钟数
else
break;
}
else
{
if(temp>=mon_table[temp1])
temp-=mon_table[temp1];//平年
else
break;
}
temp1++;
}
rmon=temp1+1;//得到月份
rday=temp+1; //得到日期
}
temp=timecount%86400; //得到秒钟数
rhour=temp/3600; //小时
rmin=(temp%3600)/60; //分钟
rsec=(temp%3600)%60; //秒钟
rweek=RTC_Get_Week(ryear,rmon,rday);//获取星期
return 0;
}
u8 RTC_Get_Week(u16 year,u8 month,u8 day) //按年月日计算星期(只允许1901-2099年)//已由RTC_Get调用
{
u16 temp2;
u8 yearH,yearL;
yearH=year/100;
yearL=year%100;
// 如果为21世纪,年份数加100
if (yearH>19)yearL+=100;
// 所过闰年数只算1900年之后的
temp2=yearL+yearL/4;
temp2=temp2%7;
temp2=temp2+day+table_week[month-1];
if (yearL%4==0&&month<3)temp2--;
return(temp2%7); //返回星期值(0~6)
}
main.c
/*********************************************************************************************
程序名: LED灯显示RTC走时程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "key.h"
# include "buzzer.h"
# include "usart.h"
# include "rtc.h"
int main (void){//主程序
RCC_Configuration(); //系统时钟初始化
RTC_Config(); //实时时钟初始化
LED_Init();//LED初始化
KEY_Init();//按键初始化
BUZZER_Init();//蜂鸣器初始化
USART1_Init(115200); //串口初始化,参数中写波特率
USART1_RX_STA=0xC000; //初始值设为有回车的状态,即显示一次欢迎词
while(1){
if(RTC_Get()==0){ //读出时间值,同时判断返回值是不是0,非0时读取的值是错误的。
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(rsec%2)); //LED1接口
GPIO_WriteBit(LEDPORT,LED2,(BitAction)(rmin%2)); //LED2接口
}
}
}
实验现象
当秒数是偶数时 LED1亮,当分钟数是偶数时 LED2亮(可能有误差)
超级终端显示日历程序
sys.h,sys.c,delay.c,delay.h 和 10 一样;led.c,led.h,key.c,key.h 和 8 相同,buzzer.c,buzzer.h 和 10.1 相同;usart.c,usart.h 和 11.1 相同,rtc.c,rtc.h 和 12 相同
main.c
时间如果想要实现 00:00:00 则需要把一位拆开两位显示,则
printf(" 现在实时时间:%d-%d-%d %d:%d%d:%d%d ",ryear,rmon,rday,rhour,rmin/10,rmin%10,rsec/10,rsec%10);//显示日期时间
或者直接用 %02d 也可以
/*********************************************************************************************
程序名: 超级终端显示日历程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "key.h"
# include "buzzer.h"
# include "usart.h"
# include "rtc.h"
int main (void){//主程序
u8 bya;
RCC_Configuration(); //系统时钟初始化
RTC_Config(); //实时时钟初始化
LED_Init();//LED初始化
KEY_Init();//按键初始化
BUZZER_Init();//蜂鸣器初始化
USART1_Init(115200); //串口初始化,参数中写波特率
USART1_RX_STA=0xC000; //初始值设为有回车的状态,即显示一次欢迎词
while(1){
if(USART1_RX_STA&0xC000)//如果标志位是0xC000表示收到数据串完成,可以处理。
{
if((USART1_RX_STA&0x3FFF)==0)
{
if(RTC_Get()==0)
{
printf(" STM32实时时钟测试程序 \r\n");
printf(" 现在实时时间:%d-%d-%d %02d:%02d:%02d ",ryear,rmon,rday,rhour,rmin,rsec);//显示日期时间
if(rweek==0)printf("星期日 \r\n");//rweek值为0时表示星期日
if(rweek==1)printf("星期一 \r\n");
if(rweek==2)printf("星期二 \r\n");
if(rweek==3)printf("星期三 \r\n");
if(rweek==4)printf("星期四 \r\n");
if(rweek==5)printf("星期五 \r\n");
if(rweek==6)printf("星期六 \r\n");
printf(" 单按回车键更新时间。输入字母C初始化时钟 \r\n");
printf(" 请输入设置时间,格式20220425190000,按回车键确定! \r\n");
}
else
{
printf("读取失败!\r\n");
}
}
else if((USART1_RX_STA&0x3FFF)==1)
{
if(USART1_RX_BUF[0]=='c' || USART1_RX_BUF[0]=='C')
{
RTC_First_Config(); //键盘输入c或C,初始化时钟
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
printf("初始化成功! \r\n");//显示初始化成功
}
else
{
printf("指令错误! \r\n"); //显示指令错误!
}
}
else if((USART1_RX_STA&0x3FFF)==14)
{
//将超级终端发过来的数据换算并写入RTC
ryear = (USART1_RX_BUF[0]-0x30)*1000+(USART1_RX_BUF[1]-0x30)*100+(USART1_RX_BUF[2]-0x30)*10+USART1_RX_BUF[3]-0x30;
rmon = (USART1_RX_BUF[4]-0x30)*10+USART1_RX_BUF[5]-0x30;//串口发来的是字符,减0x30后才能得到十进制0~9的数据
rday = (USART1_RX_BUF[6]-0x30)*10+USART1_RX_BUF[7]-0x30;
rhour = (USART1_RX_BUF[8]-0x30)*10+USART1_RX_BUF[9]-0x30;
rmin = (USART1_RX_BUF[10]-0x30)*10+USART1_RX_BUF[11]-0x30;
rsec = (USART1_RX_BUF[12]-0x30)*10+USART1_RX_BUF[13]-0x30;
bya=RTC_Set(ryear,rmon,rday,rhour,rmin,rsec); //将数据写入RTC计算器的程序
if(bya==0)
printf("写入成功! \r\n");//显示写入成功
else
printf("写入失败! \r\n"); //显示写入失败
}
else
{
printf("指令错误! \r\n"); //如果不是以上正确的操作,显示指令错误!
}
USART1_RX_STA=0; //将串口数据标志位清0
}
}
}
实验现象
触摸按键的原理与驱动
sys.h,sys.c,delay.c,delay.h 和 10 一样;led.c,led.h 和 7 相同;只需添加 touch_key.c 和 touch_key.h 即可
- 需要把跳帽短接
- 注意单片机上电的时候不要把手或者其他物品放在触摸按键上(因为上电后会把触摸按键状态作为按键没有按下的初始状态)
touch_key.h
# ifndef __TOUCH_KEY_H
# define __TOUCH_KEY_H
# include "sys.h"
# define TOUCH_KEYPORT GPIOA //定义IO接口组
# define TOUCH_KEY_A GPIO_Pin_0 //定义IO接口
# define TOUCH_KEY_B GPIO_Pin_1 //定义IO接口
# define TOUCH_KEY_C GPIO_Pin_2 //定义IO接口
# define TOUCH_KEY_D GPIO_Pin_3 //定义IO接口
void TOUCH_KEY_Init(void);//初始化
# endif
touch_key.c
# include "touch_key.h"
void TOUCH_KEY_Init(void)//微动开关的接口初始化
{
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO的初始化枚举结构
GPIO_InitStructure.GPIO_Pin = TOUCH_KEY_A | TOUCH_KEY_B | TOUCH_KEY_C | TOUCH_KEY_D; //选择端口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻(低电平有效)
GPIO_Init(TOUCH_KEYPORT,&GPIO_InitStructure);
}
main.c
/*********************************************************************************************
程序名: 触摸按键驱动程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "touch_key.h"
int main (void){//主程序
RCC_Configuration(); //系统时钟初始化
LED_Init();//LED初始化
TOUCH_KEY_Init();//按键初始化
while(1)
{
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A))//读触摸按键的电平(0代表按下,取反就是1)
{
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED控制
//GPIO_SetBits(LEDPORT,LED1);//也可以用这种,我比较喜欢这种
}
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B))//读触摸按键的电平
{
GPIO_WriteBit(LEDPORT,LED2,(BitAction)(1));//LED控制
}
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C))//读触摸按键的电平
{
GPIO_WriteBit(LEDPORT,LED1|LED2,(BitAction)(0));//LED控制
//GPIO_ResetBits(LEDPORT,LED1|LED2);//也可以用这种
}
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D))//读触摸按键的电平
{
GPIO_WriteBit(LEDPORT,LED1|LED2,(BitAction)(1));//LED控制
}
}
}
实验现象
按键双击和长按
sys.h,sys.c,delay.c,delay.h,led.c,led.h,touch_key.c,touvh_key.h 和13 相同
main.c
加延时消抖是因为触摸按键跟普通按键的 I/O口是相通的,为了兼容普通按键所以加了消抖这个对触摸按键没有影响
/*********************************************************************************************
程序名: 按键双击和长按程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "touch_key.h"
# define KEYA_SPEED1 100 //长按的时间长度(单位10mS)
# define KEYA_SPEED2 10 //双击的时间长度(单位20mS)
int main (void){//主程序
u8 a=0,b,c=0;
RCC_Configuration(); //系统时钟初始化
LED_Init();//LED初始化
TOUCH_KEY_Init();//按键初始化
while(1)
{
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A))
{ //检测按键是否按下
delay_ms(20); //延时去抖动
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A))//判断长短键
{
while((!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A))&&c<KEYA_SPEED1)//循环判断长按,到时跳转
{
c++;delay_ms(10); //长按判断的计时
}
if(c>=KEYA_SPEED1)//长键处理
{
//长按后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED控制
while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A));
}
else//单击处理
{
for(b=0;b<KEYA_SPEED2;b++)//检测双击
{
delay_ms(20);
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A))
{
a=1;
//双击后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED2,(BitAction)(1));//LED控制
while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A));//等待按键放开
}
}
if(a==0)//判断单击
{
//单击后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED1|LED2,(BitAction)(0));//LED控制
}
}
a=0;c=0; //参数清0
}
} //按键判断在此结束
}
}
实验现象
触摸按键滑动程序
sys.h,sys.c,delay.c,delay.h,led.c,led.h,touch_key.c,touvh_key.h 和13 相同;usart.c,usart.h 和 11.1 相同
- 区分单击或者滑动:只需判断按下时和松开时是不是同一个按键即可,假如按下是 A 松开是 B 就可以说是从 A -> B 滑动
main.c
/*********************************************************************************************
程序名: 触摸按键滑动,单击,双击,长按程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "led.h"
# include "touch_key.h"
# include "usart.h"
# define KEYA_SPEED1 100 //长按的时间长度(单位10mS)
# define KEYA_SPEED2 10 //双击的时间长度(单位20mS)
int main (void){//主程序
u16 k=1000; //用于滑动加减计数
u8 a=0,b,c=0;
u8 s=0; //刚刚结束滑动标志
RCC_Configuration(); //系统时钟初始化
USART1_Init(115200); //串口初始化,参数中写波特率
LED_Init();//LED初始化
TOUCH_KEY_Init();//按键初始化
while(1){
//A
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){ //检测按键是否按下
delay_ms(20); //延时去抖动
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){//判断长短键
while((!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A))&&c<KEYA_SPEED1){ //循环判断长按,到时跳转
c++;delay_ms(10); //长按判断的计时
}
if(c>=KEYA_SPEED1){ //长键处理
//长按后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED控制
printf("A键长按 \r\n");
while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A));
}else{ //单击处理
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){
k++; //用于显示的计数值
printf("A键右滑 %d \r\n",k);
a=1;s=1; //a是单双击判断标志,s是刚刚结束滑动标志
}
if(a==0){
for(b=0;b<KEYA_SPEED2;b++){//检测双击
delay_ms(20);
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){
a=1;
//双击后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED2,(BitAction)(1));//LED控制
printf("A键双击 \r\n");
while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A));
}
}
if(a==0){ //判断单击
if(s==1){ //判断是不是刚执行完滑动操作
s=0; //如果是则本次不执行单击处理(因为是滑动的放开操作)
}else{ //如果不是,则正常执行单击处理
//单击后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED1|LED2,(BitAction)(0));//LED控制
printf("A键单击 \r\n");
}
}
}
}
a=0;c=0; //参数清0
}
} //按键判断在此结束
//B
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){ //检测按键是否按下
delay_ms(20); //延时去抖动
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){//判断长短键
while((!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B))&&c<KEYA_SPEED1){ //循环判断长按,到时跳转
c++;delay_ms(10); //长按判断的计时
}
if(c>=KEYA_SPEED1){ //长键处理
//长按后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED控制
printf("B键长按 \r\n");
while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B));
}else{ //单击处理
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){
k++;
printf("B键右滑 %d \r\n",k);
a=1;s=1; //a是单双击判断标志,s是刚刚结束滑动标志
}
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){
k--;
printf("B键左滑 %d \r\n",k);
a=1;s=1; //a是单双击判断标志,s是刚刚结束滑动标志
}
if(a==0){
for(b=0;b<KEYA_SPEED2;b++){//检测双击
delay_ms(20);
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){
a=1;
//双击后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED2,(BitAction)(1));//LED控制
printf("B键双击 \r\n");
while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B));
}
}
if(a==0){ //判断单击
if(s==1){ //判断是不是刚执行完滑动操作
s=0; //如果是则本次不执行单击处理(因为是滑动的放开操作)
}else{ //如果不是,则正常执行单击处理
//单击后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED1|LED2,(BitAction)(0));//LED控制
printf("B键单击 \r\n");
}
}
}
}
a=0;c=0; //参数清0
}
} //按键判断在此结束
//C
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){ //检测按键是否按下
delay_ms(20); //延时去抖动
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){//判断长短键
while((!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C))&&c<KEYA_SPEED1){ //循环判断长按,到时跳转
c++;delay_ms(10); //长按判断的计时
}
if(c>=KEYA_SPEED1){ //长键处理
//长按后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED控制
printf("C键长按 \r\n");
while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C));
}else{ //单击处理
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)){
k++;
printf("C键右滑 %d \r\n",k);
a=1;s=1; //a是单双击判断标志,s是刚刚结束滑动标志
}
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){
k--;
printf("C键左滑 %d \r\n",k);
a=1;s=1; //a是单双击判断标志,s是刚刚结束滑动标志
}
if(a==0){
for(b=0;b<KEYA_SPEED2;b++){//检测双击
delay_ms(20);
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){
a=1;
//双击后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED2,(BitAction)(1));//LED控制
printf("C键双击 \r\n");
while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C));
}
}
if(a==0){ //判断单击
if(s==1){ //判断是不是刚执行完滑动操作
s=0; //如果是则本次不执行单击处理(因为是滑动的放开操作)
}else{ //如果不是,则正常执行单击处理
//单击后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED1|LED2,(BitAction)(0));//LED控制
printf("C键单击 \r\n");
}
}
}
}
a=0;c=0; //参数清0
}
} //按键判断在此结束
//D
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)){ //检测按键是否按下
delay_ms(20); //延时去抖动
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)){//判断长短键
while((!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D))&&c<KEYA_SPEED1){ //循环判断长按,到时跳转
c++;delay_ms(10); //长按判断的计时
}
if(c>=KEYA_SPEED1){ //长键处理
//长按后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED1,(BitAction)(1));//LED控制
printf("D键长按 \r\n");
while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D));
}else{ //单击处理
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){
k--;
printf("D键左滑 %d \r\n",k);
a=1;s=1; //a是单双击判断标志,s是刚刚结束滑动标志
}
if(a==0){
for(b=0;b<KEYA_SPEED2;b++){//检测双击
delay_ms(20);
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)){
a=1;
//双击后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED2,(BitAction)(1));//LED控制
printf("D键双击 \r\n");
while(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D));
}
}
if(a==0){ //判断单击
if(s==1){ //判断是不是刚执行完滑动操作
s=0; //如果是则本次不执行单击处理(因为是滑动的放开操作)
}else{ //如果不是,则正常执行单击处理
//单击后执行的程序放到此处
GPIO_WriteBit(LEDPORT,LED1|LED2,(BitAction)(0));//LED控制
printf("D键单击 \r\n");
}
}
}
}
a=0;c=0; //参数清0
}
} //按键判断在此结束
}
}
实验现象
数码管原理与驱动程序
sys.h,sys.c,delay.c,delay.h 和 10 一样;rtc.c,rtc.h 和 12相同;只需添加 TM1640.c 和 TM1640.h 即可
首先把跳帽按下面图片接好
TM1640是一种LED(发光二极管显示器)驱动控制专用电路,内部集成有MCU数字接口、数据锁存器、LED 驱动等电路。本产品性能优良,质量可靠。主要应用于电子产品LED显示屏驱动。采用SOP28的封装形式。
- 数码管对照
TM1640.h
# ifndef __TM1640_H
# define __TM1640_H
# include "sys.h"
# define TM1640_GPIOPORT GPIOA //定义IO接口
# define TM1640_DIN GPIO_Pin_12 //定义IO接口
# define TM1640_SCLK GPIO_Pin_11 //定义IO接口
# define TM1640_LEDPORT 0xC8 //定义IO接口
void TM1640_Init(void);//初始化
void TM1640_led(u8 date);//
void TM1640_display(u8 address,u8 date);//
void TM1640_display_add(u8 address,u8 date);//
# endif
TM1640.c
- TM1640_start()和 TM1640_stop()看这个时序图
# include "TM1640.h"
# include "delay.h"
# define DEL 1 //宏定义 通信速率(默认为1,如不能通信或者数码管数据经常丢失可加大数值)
//地址模式的设置
//# define TM1640MEDO_ADD 0x40 //宏定义 自动加一模式
# define TM1640MEDO_ADD 0x44 //宏定义 固定地址模式(推荐)
//显示亮度的设置
//# define TM1640MEDO_DISPLAY 0x88 //宏定义 亮度 最小
//# define TM1640MEDO_DISPLAY 0x89 //宏定义 亮度
//# define TM1640MEDO_DISPLAY 0x8a //宏定义 亮度
//# define TM1640MEDO_DISPLAY 0x8b //宏定义 亮度
# define TM1640MEDO_DISPLAY 0x8c //宏定义 亮度(推荐)
//# define TM1640MEDO_DISPLAY 0x8d //宏定义 亮度
//# define TM1640MEDO_DISPLAY 0x8f //宏定义 亮度 最大
# define TM1640MEDO_DISPLAY_OFF 0x80 //宏定义 亮度 关
void TM1640_start() //通信时序 启始(基础GPIO操作)(低层)
{
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(1)); //接口输出高电平1
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(1)); //接口输出高电平1
delay_us(DEL);
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0)); //接口输出0
delay_us(DEL);
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0)); //接口输出0
delay_us(DEL);
}
void TM1640_stop() //通信时序 结束(基础GPIO操作)(低层)
{
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0)); //接口输出0
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(1)); //接口输出高电平1
delay_us(DEL);
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(1)); //接口输出高电平1
delay_us(DEL);
}
void TM1640_write(u8 date) //写数据(低层)
{
u8 i;
u8 aa;
aa = date;
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0)); //接口输出0
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0)); //接口输出0
for(i = 0; i < 8; i++)
{
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0)); //接口输出0
delay_us(DEL);
if(aa & 0x01)
{
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(1)); //接口输出高电平1
delay_us(DEL);
}
else
{
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0)); //接口输出0
delay_us(DEL);
}
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(1)); //接口输出高电平1
delay_us(DEL);
aa = aa >> 1;
}
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0)); //接口输出0
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0)); //接口输出0
}
void TM1640_Init(void) //TM1640接口初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = TM1640_DIN | TM1640_SCLK; //选择端口号(0~15或all)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
GPIO_Init(TM1640_GPIOPORT, &GPIO_InitStructure);
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(1)); //接口输出高电平1
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(1)); //接口输出高电平1
TM1640_start();
TM1640_write(TM1640MEDO_ADD); //设置数据,0x40,0x44分别对应地址自动加一和固定地址模式
TM1640_stop();
TM1640_start();
TM1640_write(TM1640MEDO_DISPLAY); //控制显示,开显示,0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f分别对应脉冲宽度为:
//------------------1/16, 2/16, 4/16, 10/16, 11/16, 12/16, 13/16, 14/16 //0x80关显示
TM1640_stop();
}
void TM1640_led(u8 date) //固定地址模式的显示输出8个LED控制
{
TM1640_start();
TM1640_write(TM1640_LEDPORT); //传显示数据对应的地址
TM1640_write(date); //传1BYTE显示数据
TM1640_stop();
}
void TM1640_display(u8 address, u8 date) //固定地址模式的显示输出
{
const u8 buff[21] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd, 0x87, 0xff, 0xef, 0x00}; //数字0~9及0~9加点显示段码表
//--------------- 0 1 2 3 4 5 6 7 8 9 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 无
TM1640_start();
TM1640_write(0xC0 + address); //传显示数据对应的地址
TM1640_write(buff[date]); //传1BYTE显示数据
TM1640_stop();
}
void TM1640_display_add(u8 address, u8 date) //地址自动加一模式的显示输出
{
u8 i;
const u8 buff[21] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd, 0x87, 0xff, 0xef, 0x00}; //数字0~9及0~9加点显示段码表
//--------------- 0 1 2 3 4 5 6 7 8 9 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 无
TM1640_start();
TM1640_write(0xC0 + address); //设置起始地址
for(i = 0; i < 16; i++)
{
TM1640_write(buff[date]);
}
TM1640_stop();
}
上面代码中的 TM1640_write(u8 date) 函数可以写成下面这种:
main.c
void TM1640_write(u8 date)
{
u8 mask;
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0)); //接口输出0
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0)); //接口输出0
for(mask=0x01;mask!=0;mask<<=1)//将0x01当做偏移量,date不用动每次判断date与mask的值
{
if((date & mask)!=0)
{
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(1)); //接口输出高电平1
delay_us(DEL);
}
else
{
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0)); //接口输出高电平1
delay_us(DEL);
}
delay_us(DEL);
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(1)); //接口输出高电平1
delay_us(DEL);
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0)); //接口输出0
}
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_DIN, (BitAction)(0)); //接口输出0
GPIO_WriteBit(TM1640_GPIOPORT, TM1640_SCLK, (BitAction)(0)); //接口输出0
}
main.c
- TM1640_led©; :参数是16进制想要led1点亮也就是 0000 0001 转换16进制就是 0x01
- 数码管那 +10 表示点亮小数点
/*********************************************************************************************
程序名: 数码管RTC显示程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "rtc.h"
# include "TM1640.h"
int main (void){//主程序
u8 c=0x01;
RCC_Configuration(); //系统时钟初始化
RTC_Config(); //RTC初始化
TM1640_Init(); //TM1640初始化
while(1){
if(RTC_Get()==0){ //读出RTC时间
TM1640_display(0,rday/10); //天
TM1640_display(1,rday%10+10);
TM1640_display(2,rhour/10); //时
TM1640_display(3,rhour%10+10);
TM1640_display(4,rmin/10); //分
TM1640_display(5,rmin%10+10);
TM1640_display(6,rsec/10); //秒
TM1640_display(7,rsec%10);
//TM1640_display(7,20);//灯熄灭
TM1640_led(c); //与TM1640连接的8个LED全亮
c<<=1; //数据左移 流水灯
if(c==0x00)c=0x01; //8个灯显示完后重新开始
delay_ms(125); //延时
}
}
}
实验现象
旋转编码器原理与驱动
sys.h,sys.c,delay.c,delay.h 和 10 一样;rtc.c,rtc.h 和 12相同;TM1640.c 和 TM1640.h 与14 相同;只需添加 encoder.c,encoder.h
首先把跳帽按下面图片接好
encoder.h
# ifndef __ENCODER_H
# define __ENCODER_H
# include "sys.h"
# include "delay.h"
# define ENCODER_PORT_A GPIOA //定义IO接口组
# define ENCODER_L GPIO_Pin_6 //定义IO接口(key2)
# define ENCODER_D GPIO_Pin_7 //定义IO接口(按键)
# define ENCODER_PORT_B GPIOB //定义IO接口组
# define ENCODER_R GPIO_Pin_2 //定义IO接口(key3)
void ENCODER_Init(void);//初始化
u8 ENCODER_READ(void);
# endif
encoder.c
- 这里是用第二种方法判断左右转:循环判断 key2 是否触发,再判断 key3高电平还是低电平
# include "encoder.h"
u8 KUP;//旋钮锁死标志(1为锁死)
u16 cou;
void ENCODER_Init(void)//接口初始化
{
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO的初始化枚举结构
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = ENCODER_L | ENCODER_D; //选择端口号
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻
GPIO_Init(ENCODER_PORT_A, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = ENCODER_R; //选择端口号
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 //上拉电阻
GPIO_Init(ENCODER_PORT_B, &GPIO_InitStructure);
}
u8 ENCODER_READ(void)//接口初始化
{
u8 a;//存放按键的值
u8 kt;
a = 0;
if(GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_L))KUP = 0; //判断旋钮(key2)是否解除锁死(高电平代表解除锁死),如果是1则清零
if(!GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_L) && KUP == 0) //判断是否旋转旋钮,同时判断是否有旋钮锁死(低电平有效)(单独判断key2是否触发)
{
delay_us(100);
kt = GPIO_ReadInputDataBit(ENCODER_PORT_B, ENCODER_R); //把旋钮另一端电平状态记录
delay_ms(3); //延时
if(!GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_L)) //去抖
{
if(kt == 0) //用另一端判断左或右旋转
{
a = 1; //右转(key3为低电平)
}
else
{
a = 2; //左转(key3为高电平)
}
cou = 0; //初始锁死判断计数器
while(!GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_L) && cou < 60000) //等待放开旋钮,同时累加判断锁死
{
cou++;
KUP = 1;
delay_us(20);
}
}
}
if(!GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_D) && KUP == 0) //判断旋钮是否按下
{
delay_ms(20);
if(!GPIO_ReadInputDataBit(ENCODER_PORT_A, ENCODER_D)) //去抖动
{
a = 3; //在按键按下时加上按键的状态值
//while(ENCODER_D==0); 等等旋钮放开
}
}
return a;
}
main.c
/*********************************************************************************************
程序名: 旋转编码器数码管显示程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "rtc.h"
# include "TM1640.h"
# include "encoder.h"
int main (void){//主程序
u8 a=0,b=0,c=0x01;
RCC_Configuration(); //系统时钟初始化
RTC_Config(); //RTC初始化
ENCODER_Init(); //旋转编码器初始化
TM1640_Init(); //TM1640初始化
TM1640_display(0,a/10); //显示数值
TM1640_display(1,a%10);
//其他数码管不用要关闭不然会显示乱码
TM1640_display(2,20);
TM1640_display(3,20);
TM1640_display(4,20);
TM1640_display(5,20);
TM1640_display(6,20);
TM1640_display(7,20);
while(1)
{
b = ENCODER_READ(); //读出旋转编码器值
if(b == 1)
{
a++; //分析按键值,并加减计数器值。
if(c!=0x80)c<<=1;//灯右到左
if(a > 99)a = 0;
}
if(b == 2)
{
if(a == 0)a = 100;
if(c!=0x01)c>>=1;//灯左到右
a--;
}
if(b == 3)
{
a = 0;
c=0x01;
}
if(b != 0) //如果有旋转器的操作
{
TM1640_display(0, a / 10); //显示数值
TM1640_display(1, a % 10);
TM1640_led(c);
}
}
}
实验现象
I2C总线之读取温度值
本节用到的固件库函数
- I2C_Init(手册 11.2.2)//初始化外设I2Cx
- I2C_Cmd(手册 11.2.4)//使能或者失能I2C外设
- I2C_GenerateSTART(手册 11.2.7)//产生起止信号
- I2C_CheckEvent(手册 11.2.28)//检测EVx事件
- I2C_Send7bitAddress(手册 11.2.16)//发送设备地址
- I2C_SendData(手册 11.2.14)//发送数据
- I2C_GenerateSTOP(手册 11.2.8)//停止信号
- I2C_AcknowledgeConfig(手册 11.2.9)//开启或者关闭对应 I2C 应答功能
- I2C_ReceiveData(手册 11.2.15)//返回通过 I2Cx 最近接收的数据
sys.h,sys.c,delay.c,delay.h 和 10 一样;TM1640.c 和 TM1640.h 与14 相同;只需添加 Lm75a.c,Lm75a.h,i2c.c,i2c.h(需要在 Lib文件添加 stm32f10x_i2c.c)
- 电路连接:两线总线连接,1~10K上拉电阻,复用开漏模式。
- 器件地址:每个器件都有唯一地址,最多127个器件地址。(最新版 I2C 规范中新增加了10位地址模式。最大器件地址数量可达1023个)
i2c.h
# ifndef __I2C_H
# define __I2C_H
# include "sys.h"
# define I2CPORT GPIOB //定义IO接口
# define I2C_SCL GPIO_Pin_6 //定义IO接口
# define I2C_SDA GPIO_Pin_7 //定义IO接口
# define HostAddress 0xc0 //总线主机的器件地址
# define BusSpeed 200000 //总线速度(不高于400000)
void I2C_Configuration(void);
void I2C_SAND_BUFFER(u8 SlaveAddr, u8 WriteAddr, u8* pBuffer, u16 NumByteToWrite);
void I2C_SAND_BYTE(u8 SlaveAddr,u8 writeAddr,u8 pBuffer);
void I2C_READ_BUFFER(u8 SlaveAddr,u8 readAddr,u8* pBuffer,u16 NumByteToRead);
u8 I2C_READ_BYTE(u8 SlaveAddr,u8 readAddr);
# endif
i2c.c
I2C_InitTypeDef 结构体6个成员参数介绍
- I2C_ClockSpeed:这个主要是设置SCL的时钟频率。我们知道I2C分为两种模式:标准和快速两种模式。频率为100/400khz。因此在配置这个值的时候不能超过400000。
- I2C_Mode:这个成员主要是配置I2C的工作模式。可选 I2C模式和 SMBUS模式。一般情况下我们多选I2C模式
- I2C_DutyCycle:这个成员设置的是I2C的SCL线时钟的占空比。主要有两种模式,但其实选哪种都没有多大的影响
- I2C_OwnAddress1:这个成员主要设置的是I2C设备的地址。地址可设置为7位或10位
- I2C_Ack:这个成员主要是配置I2C的应答位。分为应答和非应答,一般大多情况下设置为应答
- I2C_AcknowledgeAddress:这个成员配置选择I2C的寻址模式是7位还是10位地址。这需要根据实际连接到I2C总线上设备的地址进行选择,这个成员的配置也影响到 I2C_OwnAddress1成员,只有这里设置成 10 位模式时, I2C_OwnAddress1 才支持10位地址
主发送流程
将I2C_CR1寄存器的START位置1,就会产生起始条件,如果正常产生了一个起始信号,就会产生一个 EV5 事件,这时I2C_SR1寄存器的SB位就会变为1,所以可以通过读取状态寄存器(I2C_SR1)的SB位来判断起始条件是否产生。
主发送器发送地址后,如果从设备产生了应答信号,就会产生 EV6 和 EV8 事件,EV6 事件 I2C_SR1寄存器的ADD=1,这表示地址发送结束,EV8 事件是TxE=1,也就是数据寄存器为空,数据寄存器为空就可以继续写入新的数据,这就是判断这些EVx事件的意义(即检测这些状态位的意义就是等待I2C将要发送的数据成功发送,然后才能进行下一步工作)。当产生 EV8_2 事件时,TxE=1,BTF=1,BTF=1表示数据移位寄存器为空,这样就表示所有数据都被发送出去了,然后就可以产生停止位,即将I2C_CR1寄存器的STOP位置1,结束这次通信。
主接收流程
起始信号发送完成后就会产生一个 EV5 事件,然后发送从机地址,从机产生应答信号,产生 EV6 事件,和主发送器不一样的是,当主机读取从机的数据后,产生的应答是主机产生的,不是从机产生的,这个应答信号的产生是将I2C_CR1寄存器的ACK位置1,然后会产生一个 EV7 事件,EV7 事件 RxNE=1,表示数据寄存器非空,即数据寄存器有数据了,这时候内核就可以将数据寄存器里的值送到内存变量中以供使用。ACk应答信号为1的时候就会继续从从机中读取数据,然后再产生ACK应答,循环往复,当不在接收数据时,就会产生一个 EV7_1 事件,这个事件是RxNE=1,并设置ACK=0和STOP位(将其置1),结束数据的发送,最后产生一个 EV7 事件,表示最后一个数据读取完成。
还有要注意的就是,以上那些状态位读取后都要将其清除,每个位的清除方法都不一样,具体方法可见上面EVx事件后面的描述
# include "i2c.h"
void I2C_GPIO_Init(void){ //I2C接口初始化
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); //启动I2C功能
GPIO_InitStructure.GPIO_Pin = I2C_SCL | I2C_SDA; //选择端口号
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //选择IO接口工作方式(复用开漏输出)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
GPIO_Init(I2CPORT, &GPIO_InitStructure);
}
void I2C_Configuration(void){ //I2C初始化
I2C_InitTypeDef I2C_InitStructure;
I2C_GPIO_Init(); //先设置GPIO接口的状态
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//设置为I2C模式
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = HostAddress; //主机地址(从机不得用此地址)
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//允许应答
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //7位地址模式
I2C_InitStructure.I2C_ClockSpeed = BusSpeed; //总线速度设置
I2C_Init(I2C1,&I2C_InitStructure);
I2C_Cmd(I2C1,ENABLE);//开启I2C
}
void I2C_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr,u8* pBuffer,u16 NumByteToWrite){ //I2C发送数据串(器件地址,寄存器,内部地址,数量)
I2C_GenerateSTART(I2C1,ENABLE);//产生起始位
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //清除EV5(返回值success:1 error:0)
I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Transmitter);//发送器件地址,选择为发送模式还是接收模式
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除EV6
I2C_SendData(I2C1,WriteAddr); //内部功能地址
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));//移位寄存器非空,数据寄存器已空,产生EV8,发送数据到DR既清除该事件
while(NumByteToWrite--){ //循环发送数据
I2C_SendData(I2C1,*pBuffer); //发送数据
pBuffer++; //数据指针移位
while (!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));//清除EV8
}
I2C_GenerateSTOP(I2C1,ENABLE);//产生停止信号
}
void I2C_SAND_BYTE(u8 SlaveAddr,u8 writeAddr,u8 pBuffer){ //I2C发送一个字节(从地址,内部地址,内容)
I2C_GenerateSTART(I2C1,ENABLE); //发送开始信号
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //等待完成(清除EV5)
I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //发送从器件地址及状态(写入)
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //等待完成 (清除EV6)
I2C_SendData(I2C1,writeAddr); //发送从器件内部寄存器地址
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待完成(移位寄存器非空,数据寄存器已空,产生EV8,发送数据到DR既清除该事件)
I2C_SendData(I2C1,pBuffer); //发送要写入的内容
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待完成(清除EV8)
I2C_GenerateSTOP(I2C1,ENABLE); //发送结束信号
}
void I2C_READ_BUFFER(u8 SlaveAddr,u8 readAddr,u8* pBuffer,u16 NumByteToRead){ //I2C读取数据串(器件地址,寄存器,内部地址,数量)
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1,ENABLE);//开启信号
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //清除 EV5
I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //写入器件地址
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除 EV6
I2C_Cmd(I2C1,ENABLE);
I2C_SendData(I2C1,readAddr); //发送读的地址
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //清除 EV8
I2C_GenerateSTART(I2C1,ENABLE); //开启信号
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //清除 EV5
I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Receiver); //将器件地址传出,主机为读
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //清除EV6
while(NumByteToRead){
if(NumByteToRead == 1){ //NumByteToRead=1,表示已经接收到最后一个数据了
I2C_AcknowledgeConfig(I2C1,DISABLE); //最后有一个数据时关闭应答位
I2C_GenerateSTOP(I2C1,ENABLE); //最后一个数据时使能停止位
}
if(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED)){ //读取数据
*pBuffer = I2C_ReceiveData(I2C1);//调用库函数将数据取出到 pBuffer
pBuffer++; //指针移位
NumByteToRead--; //字节数减 1
}
}
I2C_AcknowledgeConfig(I2C1,ENABLE);//打开应答,方便下一次I2C传输
}
u8 I2C_READ_BYTE(u8 SlaveAddr,u8 readAddr){ //I2C读取一个字节
u8 a;
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1,ENABLE);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_Cmd(I2C1,ENABLE);
I2C_SendData(I2C1,readAddr);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTART(I2C1,ENABLE);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
I2C_AcknowledgeConfig(I2C1,DISABLE); //最后有一个数据时关闭应答位
I2C_GenerateSTOP(I2C1,ENABLE); //最后一个数据时使能停止位
a = I2C_ReceiveData(I2C1);
return a;
}
Lm75a.h
- 器件地址通过下面可以知道:
下面图片可以知道 A0 A1 A2 都已经接 3V 了
地址的高4位预先设置为 ‘1001’;I2C器件一共有七位地址码,还有一位是读/写(R/W)操作位,0代表读,1代表写
由于 A0 A1 A2 接的是3V,并且最后一位是读,所以器件地址是:1 0 0 1 1 1 1 0(0x9E)
如果 A0 A1 A2 接的是地线,并且最后一位是读,则器件地址是:1 0 0 1 0 0 0 0(0x90)
- 子地址(寄存器地址):
- 温度值计算
- 字节数
# ifndef __LM75A_H
# define __LM75A_H
# include "sys.h"
# include "i2c.h"
# define LM75A_ADD 0x9E //器件地址
void LM75A_GetTemp(u8 *Tempbuffer);//读温度
void LM75A_POWERDOWN(void); //掉电模式
# endif
Lm75a.c
# include "Lm75a.h"
//读出LM75A的温度值(-55~125摄氏度)
//温度正负号(0正1负),温度整数,温度小数(点后2位)依次放入*Tempbuffer(十进制)
void LM75A_GetTemp(u8 *Tempbuffer){
u8 buf[2]; //温度值储存
u8 t=0,a=0;
I2C_READ_BUFFER(LM75A_ADD,0x00,buf,2); //读出温度值(器件地址,子地址,数据储存器,字节数)
t = buf[0]; //处理温度整数部分,0~125度
*Tempbuffer = 0; //温度值为正值
if(t & 0x80){ //判断温度是否是负(MSB表示温度符号)(1 0 0 0 0 0 0 0)
*Tempbuffer = 1; //温度值为负值
t = ~t; t++; //计算补码(原码取反后加1)注:如果不行可以把小数也取反加1试试
}
if(t & 0x01){ a=a+1; } //从高到低按位加入温度积加值(0~125)
if(t & 0x02){ a=a+2; }
if(t & 0x04){ a=a+4; }
if(t & 0x08){ a=a+8; }
if(t & 0x10){ a=a+16; }
if(t & 0x20){ a=a+32; }
if(t & 0x40){ a=a+64; }
Tempbuffer++;
*Tempbuffer = a;
a = 0;
t = buf[1]; //处理小数部分,取0.125精度的前2位(12、25、37、50、62、75、87)
if(t & 0x20){ a=a+12; }
if(t & 0x40){ a=a+25; }
if(t & 0x80){ a=a+50; }
Tempbuffer++;
*Tempbuffer = a;
}
//LM75进入掉电模式,再次调用LM75A_GetTemp();即可正常工作
//建议只在需要低功耗情况下使用
void LM75A_POWERDOWN(void){//
I2C_SAND_BYTE(LM75A_ADD,0x01,1); //1就是看配置寄存器表
}
main.c
/*********************************************************************************************
程序名: 温度传感器数码管显示程序
编写时间: 2018年2月10日
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "TM1640.h"
# include "Lm75a.h"
int main (void){//主程序
u8 buffer[3];
u8 c=0x01;
RCC_Configuration(); //系统时钟初始化
I2C_Configuration();//I2C初始化
TM1640_Init(); //TM1640初始化
TM1640_display(0,20); //初始显示内容
TM1640_display(1,20);
TM1640_display(2,20);
TM1640_display(3,20);
TM1640_display(4,20);
TM1640_display(5,20);
TM1640_display(6,20);
TM1640_display(7,20);
while(1){
LM75A_GetTemp(buffer); //读取LM75A的温度数据
TM1640_display(0,buffer[1]/10); //显示数值(十位)
TM1640_display(1,buffer[1]%10+10);//显示数值(个位+小数点)
TM1640_display(2,buffer[2]/10);//小数点后的十位
TM1640_display(3,buffer[2]%10);//小数点后的个位
TM1640_led(c); //与TM1640连接的8个LED全亮
c<<=1; //数据左移 流水灯
if(c==0x00)c=0x01; //8个灯显示完后重新开始
delay_ms(150); //延时
}
}
实验现象
软件模拟I2C
软件i2c是程序员使用程序控制 SCL,SDA 线输出高低电平,模拟i2c协议的时序。一般较硬件i2c稳定,但是程序较为繁琐,但不难。有些单片机的硬件i2c不太稳定,调试问题较多。
文件跟上面 16 一样,只是把 i2c.c,i2c.h 替换成 iic.c,iic.h 然后把 Lm75a.c 改一下关于 I2C 的代码即可
iic.h
# ifndef __IIC_H
# define __IIC_H
# include "sys.h"
# define IIC_PORT GPIOB
# define SCL_PIN GPIO_Pin_6//控制线
# define SDA_PIN GPIO_Pin_7//数据线
//SCL=1
# define SCL_1() GPIO_SetBits(IIC_PORT,SCL_PIN)
//SCL=0
# define SCL_0() GPIO_ResetBits(IIC_PORT,SCL_PIN)
//SDA=1
# define SDA_1() GPIO_SetBits(IIC_PORT,SDA_PIN)
//SDA=0
# define SDA_0() GPIO_ResetBits(IIC_PORT,SDA_PIN)
//读SDA当前状态
# define SDA_READ() GPIO_ReadInputDataBit(IIC_PORT,SDA_PIN)
void IIC_Init(void);//初始化管脚
void IIC_Start(void);//起始信号
void IIC_Stop(void);//停止信号
u8 IIC_Write(u8 dat);//写操作,返回值-从机应答位的值
u8 IIC_Read(void);//读操作,返回值-读到的字节
u8 IIC_Read_Nak(void);//读操作,并且发送非应答信号,返回值-读到的字节
u8 IIC_Read_Ack(void);//读操作,并且发送应答信号,返回值-读到的字节
u8 Read_Byte(u8 device_addr,u8 addr);//向从机读取器件字节
void Write_Byte(u8 device_addr,u8 addr,u8 dat);//向从机写入器件字节
void Read_Buffer(u8 device_addr,u8 addr,u8* buf,int len);//连续读取函数
void Write_Buffer(u8 device_addr,u8 addr,u8* buf,int len);//连续写入函数
# endif
iic.c
- I ^2^ C 中也有起始信号、数据传输和停止信号;止信号。其中数据传输部分,可以一次通信过程传输很多个字节,
字节数是不受限制的
,而每个字节的数据最后也跟了一位,这一位叫做应答位
,通常用ACK
表示。 - UART 通信虽然用了
TXD
和RXD
两根线,但是实际一次通信中, 1条线就可以完成, 2条线是把发送和接收分开而已,而 I 2 C 每次通信,不管是发送还是接收,必须 2条线都参与工作才能完成
- I^2^C 通信流程解析
起始信号:
UART 通信是从一直持续的高电平出现一个低电平
标志起始位;而 I 2 C 通信的起始信号的定义是 SCL 为高电平期间, SDA 由高电平向低电平变化产生一个下降沿
,表示起始信号,如图 14-3 中的 Start 部分所示。
数据传输:
首先,UART 是低位在前,高位在后
;而 I 2 C 通信是高位在前,低位在后
。其次,UART 通信数据位是固定长度,波特率分之一,一位一位固定时间发送完毕就可以了
。而 I 2 C 没有固定波特率,但是有时序的要求
,要求当 SCL 在低电平的时 候, SDA 允许变化,也就是说,发送方必须先保持 SCL 是低电平,才可以改变数据线 SDA ,输出要发送的当前数据的一位;而当 SCL 在高电平的时候, SDA 绝对不可以变化,因为这个时候,接收方要来读取当前 SDA 的电平信号是 0 还是 1 ,因此要保证 SDA 的稳定,
如图 14-3 中的每一位数据的变化,都是在 SCL 的低电平位置
。 8 位数据位后边跟着的是一位应答位
。
停止信号:
UART 通信的停止位是一位固定的高电平信号;而 I 2 C 通信停止信号的定义是 SCL 为高电平期间, SDA 由低电平向高电平变化产生一个上升沿 ,表示结束信号,如图14-3 中的 Stop 部分所示。
I ^2^ C 寻址模式:
上面介绍的是 I^2^C 每一位信号的时序流程,而 I^2^C 通信在字节级的传输中,也有固定的时序要求。 I^2^C 通信的起始信号 (Start) 后,首先要发送一个从机的地址,这个地址一共有 7
位,紧跟着的第 8 位是数据方向位 (R/W),“0” 表示接下来要发送数据(写)“1”表示接下来是请求数据(读)。当我们发送完了这 7 位地址和 1 位方向后,如果发送的这个地址确实存在,那么这个地址的器件应该回应一个 ACK (拉低 SDA 即输出“ 0 ”),如果不存在,就没“人”回应 ACK SDA将保持高电平即“ 1 ”)。
还有一点要提一下,I ^2^ C 通信分为 低速模式 100kbit/s 、快速模式 400kbit/s 和高速模式3.4Mbit/s 。因为所有的 I ^2^ C 器件都支持低速,但却未必支持另外两种速度,所以作为通用的I ^2^ C 程序我们选择 100k 这个速率来实现,也就是说实际程序产生的时序必须小于等于 100k的时序参数,很明显也就是 要求 SCL 的高低电平持续时间都不短于 5us
# include "iic.h"
# include "delay.h"
# include "Lm75a.h"
//初始化引脚
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
GPIO_SetBits(IIC_PORT,SCL_PIN|SDA_PIN);//都初始化为高电平,避免毛刺
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能GPIOB时钟
GPIO_InitStructure.GPIO_Pin=SCL_PIN|SDA_PIN;//选择SCL和SDA I/O口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;//开漏输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz;//输出速率50
GPIO_Init(IIC_PORT,&GPIO_InitStructure);
}
//产生总线起始信号
void IIC_Start(void)
{
SDA_1();//首先确保SDA,SCL都是高电平
SCL_1();
delay_us(5);
SDA_0();//先拉低SDA
delay_us(5);
SCL_0();//再拉低SCL
}
//产生总线停止信号
void IIC_Stop(void)
{
SCL_0();//首先确保SDA,SCL都是低电平
SDA_0();
delay_us(5);
SCL_1();//先拉高SCL
delay_us(5);
SDA_1();//再拉高SDA
delay_us(5);
}
//写操作,dat-待写入的字节 返回值-从机应答位的值
u8 IIC_Write(u8 dat)
{
u8 mask;
u8 ack;//暂存应答位的值
for(mask=0x80;mask!=0;mask>>=1)//从高位到低位依次进行
{
if((mask&dat)==0)
SDA_0();
else
SDA_1();
delay_us(5);
SCL_1();//拉高SCL
delay_us(5);
SCL_0();//再拉低SCL,完成一个位周期
}
SDA_1();//8位数据发送完成,主机释放SDA,以检测从机应答
delay_us(5);
SCL_1();//拉高SCL
ack=SDA_READ();//读取此时的SDA值,即为从机的应答值
delay_us(5);
SCL_0();//再拉低SCL完成应答位,并保持住总线
delay_us(5);
return (!ack);//返回0代表不存在或者写入失败,1表示成功
}
//读取8位数据,返回值-读到的字节
u8 IIC_Read(void)
{
u16 i;
u8 dat=0;//数据接收变量赋值0
SDA_1();//释放总线
for(i=0;i<8;i++)
{
delay_us(5);
SCL_1();//拉高SCL
dat<<=1;//左移将已读的位往高位移动,实现高位在前低位在后
if(SDA_READ()!=0)
{
dat|=0x01;//SDA为1时设置dat最低位为1,SDA为0时无操作,即仍为初始值的0
}
delay_us(5);
SCL_0();//拉低SCL,使从机发送下一位
}
return dat;
}
//读操作,并且发送非应答信号,返回值-读到的字节
u8 IIC_Read_Nak(void)
{
u8 dat;
dat=IIC_Read();//读取8位数据
SDA_1();//8位数据读取完后,拉高SDA,发送非应答信号
delay_us(5);
SCL_1();//拉高SCL
delay_us(5);
SCL_0();//再拉低SCL,完成非应答位,并保持总线
delay_us(5);
return dat;
}
//读操作,并且发送应答信号,返回值-读到的字节
u8 IIC_Read_Ack(void)
{
u8 dat;
dat=IIC_Read();//读取8位数据
SDA_0();//8位数据读取完后,拉低SDA,发送应答信号
delay_us(5);
SCL_1();//拉高SCL
delay_us(5);
SCL_0();//拉低SCL
delay_us(5);
return dat;
}
//向从机读取器件字节 addr-字节地址 0:写 1:读
u8 Read_Byte(u8 device_addr,u8 addr)
{
u8 dat;//保存读取的数据
do{//用寻址操作查询当前是否可以进行读写
IIC_Start();//起始信号
if(IIC_Write(device_addr))//应答则跳出循环否则继续查询
{
break;
}
IIC_Stop();//停止信号
}while(1);
IIC_Write(addr);//写入存储地址
IIC_Start();//发送重复起始信号
IIC_Write(device_addr|0x01);//寻址器件,为读操作(0x50|0x01=0xA1)
dat=IIC_Read_Nak();//读取一个字节数据
IIC_Stop();//停止信号
return dat;//返回数据
}
//向从机写入器件字节 addr-字节地址 0:写 1:读
void Write_Byte(u8 device_addr,u8 addr,u8 dat)
{
do{//用寻址操作查询当前是否可以进行读写
IIC_Start();//起始信号
if(IIC_Write(device_addr))//应答则跳出循环否则继续查询
{
break;
}
IIC_Stop();//停止信号
}while(1);
IIC_Write(addr);//写入存储地址
IIC_Write(dat);//写入一个字节数据
IIC_Stop();//停止信号
}
//连续读取函数 addr-起始地址 buf-数据接收指针 len-读取长度
void Read_Buffer(u8 device_addr,u8 addr,u8* buf,int len)
{
do{//用寻址操作查询当前是否可以进行读写
IIC_Start();//起止信号
if(IIC_Write(device_addr))//应答则跳出循环否则继续查询
{
break;
}
IIC_Stop();//停止信号
}while(1);
IIC_Write(addr);//写入起始地址
IIC_Start();//发送重复起始信号
IIC_Write(device_addr|0x01);//读操作
while(len>1)//连续读取len-1个字节
{
*buf++=IIC_Read_Ack();//最后字节之前为读取操作+应答
len--;
}
*buf=IIC_Read_Nak();//最后一个字节为读取操作+非应答
IIC_Stop();//停止信号
}
//连续写入函数 addr-起始地址 buf-数据源指针 len-读取长度
void Write_Buffer(u8 device_addr,u8 addr,u8* buf,int len)
{
while(len>0)
{
do{//用寻址操作查询当前是否可以进行读写
IIC_Start();//起始信号
if(IIC_Write(device_addr))
{
break;
}
IIC_Stop();//停止信号
}while(1);
IIC_Write(addr);//写入起始地址
while(len>0)
{
IIC_Write(*buf++);//写入一个字节数据
len--;//待写入长度计数减一
//下面注释是EEPROM的暂时没用到
// addr++;//地址加一
// if((addr&0x07)==0)//检查地址是否到达页边界,24c02每页8字节
// { //所以检测低3位是否为0即可
// break; //到达也边界时,跳出循环,结束本次写操作
// }
}
IIC_Stop();//停止信号
}
}
OLED屏原理与驱动程序
sys.h,sys.c,delay.c,delay.h 和 10 一样;Im75a.c,Im75a.h 和 16 相同;只需添加 oled0561.c,oled0561.h 即可(需要添加 ASCII_8x16.h(字库))
主控芯片:SH1106
- 高位在下,低位在上(数据每次写入我们采用 “从左到右,从上到下,纵向 8 点下高位”)
- 英文或者数字可以显示4行每行16个;汉字可以显示4行每行8个
ASCII_8x16.h
# ifndef __ASCII_8x16_H
# define __ASCII_8x16_H
// ------------------ ASCII字模的数据表 ------------------------ //
// 码表从0x20~0x7e //
// 字库: 纵向取模下高位// (调用时要减512)
// -------------------------------------------------------------- //
const u8 ASCII_8x16[] = { // ASCII
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // - -
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x38,0xFC,0xFC,0x38,0x00,0x00, // -!-
0x00,0x00,0x00,0x0D,0x0D,0x00,0x00,0x00,
0x00,0x0E,0x1E,0x00,0x00,0x1E,0x0E,0x00, // -"-
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x20,0xF8,0xF8,0x20,0xF8,0xF8,0x20,0x00, // -# -
0x02,0x0F,0x0F,0x02,0x0F,0x0F,0x02,0x00,
0x38,0x7C,0x44,0x47,0x47,0xCC,0x98,0x00, // -$-
0x03,0x06,0x04,0x1C,0x1C,0x07,0x03,0x00,
0x30,0x30,0x00,0x80,0xC0,0x60,0x30,0x00, // -%-
0x0C,0x06,0x03,0x01,0x00,0x0C,0x0C,0x00,
0x80,0xD8,0x7C,0xE4,0xBC,0xD8,0x40,0x00, // -&-
0x07,0x0F,0x08,0x08,0x07,0x0F,0x08,0x00,
0x00,0x10,0x1E,0x0E,0x00,0x00,0x00,0x00, // -'-
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0xF0,0xF8,0x0C,0x04,0x00,0x00, // -(-
0x00,0x00,0x03,0x07,0x0C,0x08,0x00,0x00,
0x00,0x00,0x04,0x0C,0xF8,0xF0,0x00,0x00, // -)-
0x00,0x00,0x08,0x0C,0x07,0x03,0x00,0x00,
0x80,0xA0,0xE0,0xC0,0xC0,0xE0,0xA0,0x80, // -*-
0x00,0x02,0x03,0x01,0x01,0x03,0x02,0x00,
0x00,0x80,0x80,0xE0,0xE0,0x80,0x80,0x00, // -+-
0x00,0x00,0x00,0x03,0x03,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -,-
0x00,0x00,0x10,0x1E,0x0E,0x00,0x00,0x00,
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00, // ---
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -.-
0x00,0x00,0x00,0x0C,0x0C,0x00,0x00,0x00,
0x00,0x00,0x00,0x80,0xC0,0x60,0x30,0x00, // -/-
0x0C,0x06,0x03,0x01,0x00,0x00,0x00,0x00,
0xF8,0xFC,0x04,0xC4,0x24,0xFC,0xF8,0x00, // -0-
0x07,0x0F,0x09,0x08,0x08,0x0F,0x07,0x00,
0x00,0x10,0x18,0xFC,0xFC,0x00,0x00,0x00, // -1-
0x00,0x08,0x08,0x0F,0x0F,0x08,0x08,0x00,
0x08,0x0C,0x84,0xC4,0x64,0x3C,0x18,0x00, // -2-
0x0E,0x0F,0x09,0x08,0x08,0x0C,0x0C,0x00,
0x08,0x0C,0x44,0x44,0x44,0xFC,0xB8,0x00, // -3-
0x04,0x0C,0x08,0x08,0x08,0x0F,0x07,0x00,
0xC0,0xE0,0xB0,0x98,0xFC,0xFC,0x80,0x00, // -4-
0x00,0x00,0x00,0x08,0x0F,0x0F,0x08,0x00,
0x7C,0x7C,0x44,0x44,0xC4,0xC4,0x84,0x00, // -5-
0x04,0x0C,0x08,0x08,0x08,0x0F,0x07,0x00,
0xF0,0xF8,0x4C,0x44,0x44,0xC0,0x80,0x00, // -6-
0x07,0x0F,0x08,0x08,0x08,0x0F,0x07,0x00,
0x0C,0x0C,0x04,0x84,0xC4,0x7C,0x3C,0x00, // -7-
0x00,0x00,0x0F,0x0F,0x00,0x00,0x00,0x00,
0xB8,0xFC,0x44,0x44,0x44,0xFC,0xB8,0x00, // -8-
0x07,0x0F,0x08,0x08,0x08,0x0F,0x07,0x00,
0x38,0x7C,0x44,0x44,0x44,0xFC,0xF8,0x00, // -9-
0x00,0x08,0x08,0x08,0x0C,0x07,0x03,0x00,
0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00, // -:-
0x00,0x00,0x00,0x06,0x06,0x00,0x00,0x00,
0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00, // -;-
0x00,0x00,0x08,0x0E,0x06,0x00,0x00,0x00,
0x00,0x80,0xC0,0x60,0x30,0x18,0x08,0x00, // -<-
0x00,0x00,0x01,0x03,0x06,0x0C,0x08,0x00,
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00, // -=-
0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x00,
0x00,0x08,0x18,0x30,0x60,0xC0,0x80,0x00, // ->-
0x00,0x08,0x0C,0x06,0x03,0x01,0x00,0x00,
0x18,0x1C,0x04,0xC4,0xE4,0x3C,0x18,0x00, // -?-
0x00,0x00,0x00,0x0D,0x0D,0x00,0x00,0x00,
0xF0,0xF8,0x08,0xC8,0xC8,0xF8,0xF0,0x00, // -@-
0x07,0x0F,0x08,0x0B,0x0B,0x0B,0x01,0x00,
0xE0,0xF0,0x98,0x8C,0x98,0xF0,0xE0,0x00, // -A-
0x0F,0x0F,0x00,0x00,0x00,0x0F,0x0F,0x00,
0x04,0xFC,0xFC,0x44,0x44,0xFC,0xB8,0x00, // -B-
0x08,0x0F,0x0F,0x08,0x08,0x0F,0x07,0x00,
0xF0,0xF8,0x0C,0x04,0x04,0x0C,0x18,0x00, // -C-
0x03,0x07,0x0C,0x08,0x08,0x0C,0x06,0x00,
0x04,0xFC,0xFC,0x04,0x0C,0xF8,0xF0,0x00, // -D-
0x08,0x0F,0x0F,0x08,0x0C,0x07,0x03,0x00,
0x04,0xFC,0xFC,0x44,0xE4,0x0C,0x1C,0x00, // -E-
0x08,0x0F,0x0F,0x08,0x08,0x0C,0x0E,0x00,
0x04,0xFC,0xFC,0x44,0xE4,0x0C,0x1C,0x00, // -F-
0x08,0x0F,0x0F,0x08,0x00,0x00,0x00,0x00,
0xF0,0xF8,0x0C,0x84,0x84,0x8C,0x98,0x00, // -G-
0x03,0x07,0x0C,0x08,0x08,0x07,0x0F,0x00,
0xFC,0xFC,0x40,0x40,0x40,0xFC,0xFC,0x00, // -H-
0x0F,0x0F,0x00,0x00,0x00,0x0F,0x0F,0x00,
0x00,0x00,0x04,0xFC,0xFC,0x04,0x00,0x00, // -I-
0x00,0x00,0x08,0x0F,0x0F,0x08,0x00,0x00,
0x00,0x00,0x00,0x04,0xFC,0xFC,0x04,0x00, // -J-
0x07,0x0F,0x08,0x08,0x0F,0x07,0x00,0x00,
0x04,0xFC,0xFC,0xC0,0xF0,0x3C,0x0C,0x00, // -K-
0x08,0x0F,0x0F,0x00,0x01,0x0F,0x0E,0x00,
0x04,0xFC,0xFC,0x04,0x00,0x00,0x00,0x00, // -L-
0x08,0x0F,0x0F,0x08,0x08,0x0C,0x0E,0x00,
0xFC,0xFC,0x38,0x70,0x38,0xFC,0xFC,0x00, // -M-
0x0F,0x0F,0x00,0x00,0x00,0x0F,0x0F,0x00,
0xFC,0xFC,0x38,0x70,0xE0,0xFC,0xFC,0x00, // -N-
0x0F,0x0F,0x00,0x00,0x00,0x0F,0x0F,0x00,
0xF0,0xF8,0x0C,0x04,0x0C,0xF8,0xF0,0x00, // -O-
0x03,0x07,0x0C,0x08,0x0C,0x07,0x03,0x00,
0x04,0xFC,0xFC,0x44,0x44,0x7C,0x38,0x00, // -P-
0x08,0x0F,0x0F,0x08,0x00,0x00,0x00,0x00,
0xF8,0xFC,0x04,0x04,0x04,0xFC,0xF8,0x00, // -Q-
0x07,0x0F,0x08,0x0E,0x3C,0x3F,0x27,0x00,
0x04,0xFC,0xFC,0x44,0xC4,0xFC,0x38,0x00, // -R-
0x08,0x0F,0x0F,0x00,0x00,0x0F,0x0F,0x00,
0x18,0x3C,0x64,0x44,0xC4,0x9C,0x18,0x00, // -S-
0x06,0x0E,0x08,0x08,0x08,0x0F,0x07,0x00,
0x00,0x1C,0x0C,0xFC,0xFC,0x0C,0x1C,0x00, // -T-
0x00,0x00,0x08,0x0F,0x0F,0x08,0x00,0x00,
0xFC,0xFC,0x00,0x00,0x00,0xFC,0xFC,0x00, // -U-
0x07,0x0F,0x08,0x08,0x08,0x0F,0x07,0x00,
0xFC,0xFC,0x00,0x00,0x00,0xFC,0xFC,0x00, // -V-
0x01,0x03,0x06,0x0C,0x06,0x03,0x01,0x00,
0xFC,0xFC,0x00,0x80,0x00,0xFC,0xFC,0x00, // -W-
0x03,0x0F,0x0E,0x03,0x0E,0x0F,0x03,0x00,
0x0C,0x3C,0xF0,0xC0,0xF0,0x3C,0x0C,0x00, // -X-
0x0C,0x0F,0x03,0x00,0x03,0x0F,0x0C,0x00,
0x00,0x3C,0x7C,0xC0,0xC0,0x7C,0x3C,0x00, // -Y-
0x00,0x00,0x08,0x0F,0x0F,0x08,0x00,0x00,
0x1C,0x0C,0x84,0xC4,0x64,0x3C,0x1C,0x00, // -Z-
0x0E,0x0F,0x09,0x08,0x08,0x0C,0x0E,0x00,
0x00,0x00,0xFC,0xFC,0x04,0x04,0x00,0x00, // -[-
0x00,0x00,0x0F,0x0F,0x08,0x08,0x00,0x00,
0x38,0x70,0xE0,0xC0,0x80,0x00,0x00,0x00, // -\-
0x00,0x00,0x00,0x01,0x03,0x07,0x0E,0x00,
0x00,0x00,0x04,0x04,0xFC,0xFC,0x00,0x00, // -]-
0x00,0x00,0x08,0x08,0x0F,0x0F,0x00,0x00,
0x08,0x0C,0x06,0x03,0x06,0x0C,0x08,0x00, // -^-
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -_-
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
0x00,0x00,0x03,0x07,0x04,0x00,0x00,0x00, // -`-
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0xA0,0xA0,0xA0,0xE0,0xC0,0x00,0x00, // -a-
0x07,0x0F,0x08,0x08,0x07,0x0F,0x08,0x00,
0x04,0xFC,0xFC,0x20,0x60,0xC0,0x80,0x00, // -b-
0x08,0x0F,0x07,0x08,0x08,0x0F,0x07,0x00,
0xC0,0xE0,0x20,0x20,0x20,0x60,0x40,0x00, // -c-
0x07,0x0F,0x08,0x08,0x08,0x0C,0x04,0x00,
0x80,0xC0,0x60,0x24,0xFC,0xFC,0x00,0x00, // -d-
0x07,0x0F,0x08,0x08,0x07,0x0F,0x08,0x00,
0xC0,0xE0,0xA0,0xA0,0xA0,0xE0,0xC0,0x00, // -e-
0x07,0x0F,0x08,0x08,0x08,0x0C,0x04,0x00,
0x40,0xF8,0xFC,0x44,0x0C,0x18,0x00,0x00, // -f-
0x08,0x0F,0x0F,0x08,0x00,0x00,0x00,0x00,
0xC0,0xE0,0x20,0x20,0xC0,0xE0,0x20,0x00, // -g-
0x27,0x6F,0x48,0x48,0x7F,0x3F,0x00,0x00,
0x04,0xFC,0xFC,0x40,0x20,0xE0,0xC0,0x00, // -h-
0x08,0x0F,0x0F,0x00,0x00,0x0F,0x0F,0x00,
0x00,0x00,0x20,0xEC,0xEC,0x00,0x00,0x00, // -i-
0x00,0x00,0x08,0x0F,0x0F,0x08,0x00,0x00,
0x00,0x00,0x00,0x00,0x20,0xEC,0xEC,0x00, // -j-
0x00,0x30,0x70,0x40,0x40,0x7F,0x3F,0x00,
0x04,0xFC,0xFC,0x80,0xC0,0x60,0x20,0x00, // -k-
0x08,0x0F,0x0F,0x01,0x03,0x0E,0x0C,0x00,
0x00,0x00,0x04,0xFC,0xFC,0x00,0x00,0x00, // -l-
0x00,0x00,0x08,0x0F,0x0F,0x08,0x00,0x00,
0xE0,0xE0,0x60,0xC0,0x60,0xE0,0xC0,0x00, // -m-
0x0F,0x0F,0x00,0x0F,0x00,0x0F,0x0F,0x00,
0x20,0xE0,0xC0,0x20,0x20,0xE0,0xC0,0x00, // -n-
0x00,0x0F,0x0F,0x00,0x00,0x0F,0x0F,0x00,
0xC0,0xE0,0x20,0x20,0x20,0xE0,0xC0,0x00, // -o-
0x07,0x0F,0x08,0x08,0x08,0x0F,0x07,0x00,
0x20,0xE0,0xC0,0x20,0x20,0xE0,0xC0,0x00, // -p-
0x40,0x7F,0x7F,0x48,0x08,0x0F,0x07,0x00,
0xC0,0xE0,0x20,0x20,0xC0,0xE0,0x20,0x00, // -q-
0x07,0x0F,0x08,0x48,0x7F,0x7F,0x40,0x00,
0x20,0xE0,0xC0,0x60,0x20,0x60,0xC0,0x00, // -r-
0x08,0x0F,0x0F,0x08,0x00,0x00,0x00,0x00,
0x40,0xE0,0xA0,0x20,0x20,0x60,0x40,0x00, // -s-
0x04,0x0C,0x09,0x09,0x0B,0x0E,0x04,0x00,
0x20,0x20,0xF8,0xFC,0x20,0x20,0x00,0x00, // -t-
0x00,0x00,0x07,0x0F,0x08,0x0C,0x04,0x00,
0xE0,0xE0,0x00,0x00,0xE0,0xE0,0x00,0x00, // -u-
0x07,0x0F,0x08,0x08,0x07,0x0F,0x08,0x00,
0x00,0xE0,0xE0,0x00,0x00,0xE0,0xE0,0x00, // -v-
0x00,0x03,0x07,0x0C,0x0C,0x07,0x03,0x00,
0xE0,0xE0,0x00,0x00,0x00,0xE0,0xE0,0x00, // -w-
0x07,0x0F,0x0C,0x07,0x0C,0x0F,0x07,0x00,
0x20,0x60,0xC0,0x80,0xC0,0x60,0x20,0x00, // -x-
0x08,0x0C,0x07,0x03,0x07,0x0C,0x08,0x00,
0xE0,0xE0,0x00,0x00,0x00,0xE0,0xE0,0x00, // -y-
0x47,0x4F,0x48,0x48,0x68,0x3F,0x1F,0x00,
0x60,0x60,0x20,0xA0,0xE0,0x60,0x20,0x00, // -z-
0x0C,0x0E,0x0B,0x09,0x08,0x0C,0x0C,0x00,
0x00,0x40,0x40,0xF8,0xBC,0x04,0x04,0x00, // -{-
0x00,0x00,0x00,0x07,0x0F,0x08,0x08,0x00,
0x00,0x00,0x00,0xBC,0xBC,0x00,0x00,0x00, // -|-
0x00,0x00,0x00,0x0F,0x0F,0x00,0x00,0x00,
0x00,0x04,0x04,0xBC,0xF8,0x40,0x40,0x00, // -}-
0x00,0x08,0x08,0x0F,0x07,0x00,0x00,0x00,
0x08,0x0C,0x04,0x0C,0x08,0x0C,0x04,0x00, // -~-
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0xC0,0x60,0x30,0x60,0xC0,0x80,0x00, // --
0x07,0x07,0x04,0x04,0x04,0x07,0x07,0x00,
};
# endif
oled0561.c
- 清屏
- OLED_DISPLAY_8x16( 6, 7*8, ‘-’ ) :参数1:显示在哪一行(也就是一个字符占了2行) 参数2:显示在哪一列(7x8代表) 参数3:要显示的内容(只能是一个字符)
- 所以想另一行显示需要每次跳过2行因为一个字符占了B0~B1
- 还有写入字符串需要空格+显示内容一定一定是16个字符(不能多不能少,不显示的区域用空格代替,空格占2个字符)
# include "oled0561.h"
# include "ASCII_8x16.h" //引入字体 ASCII
void OLED0561_Init (void) //OLED屏开显示初始化
{
OLED_DISPLAY_OFF(); //OLED关显示(因为一上电后屏幕显示寄存器处于混乱状态,数据也是混乱的显示内容也是混乱的)
OLED_DISPLAY_CLEAR(); //清空屏幕内容(把写入寄存器全部为0)
OLED_DISPLAY_ON(); //OLED屏初始值设置并开显示
}
void OLED_DISPLAY_ON (void) //OLED屏初始值设置并开显示
{
u8 buf[28] =
{
0xae,//0xae:关显示,0xaf:开显示
0x00, 0x10, //开始地址(双字节)
0xd5, 0x80, //设置显示时钟分频比/振荡器频率
0xa8, 0x3f, //设置多路复用率
0xd3, 0x00, //显示偏移
0XB0,//写入页位置(0xB0~7)
0x40,//设置显示起始行
0x8d, 0x14, //VCC电源
0xa1,//设置段重新映射
0xc8,//设置COM输出扫描方向
0xda, 0x12, //设置COM引脚硬件配置
0x81, 0xff, //对比度,指令:0x81,数据:0~255(255最高)
0xd9, 0xf1, //充电周期?
0xdb, 0x30, //VCC电压输出
0x20, 0x00, //水平寻址设置
0xa4,//0xa4:正常显示,0xa5:整体点亮
0xa6,//0xa6:正常显示,0xa7:反色显示
0xaf//0xae:关显示,0xaf:开显示
}; //
I2C_SAND_BUFFER(OLED0561_ADD, COM, buf, 28);
}
void OLED_DISPLAY_OFF (void) //OLED屏关显示
{
u8 buf[3] =
{
0xae,//0xae:关显示,0xaf:开显示
0x8d, 0x10 //关闭VCC电源
};
I2C_SAND_BUFFER(OLED0561_ADD, COM, buf, 3); //3:字节数看上面数组大小
}
void OLED_DISPLAY_LIT (u8 x) //OLED屏亮度设置(0~255)
{
I2C_SAND_BYTE(OLED0561_ADD, COM, 0x81);
I2C_SAND_BYTE(OLED0561_ADD, COM, x); //亮度值
}
void OLED_DISPLAY_CLEAR(void) //清屏操作
{
u8 j, t;
for(t = 0xB0; t < 0xB8; t++) //设置起始页地址为0xB0
{
I2C_SAND_BYTE(OLED0561_ADD, COM, t); //页地址(从0xB0到0xB7)
I2C_SAND_BYTE(OLED0561_ADD, COM, 0x10); //起始列地址的高4位
I2C_SAND_BYTE(OLED0561_ADD, COM, 0x00); //起始列地址的低4位
for(j = 0; j < 132; j++) //整页内容填充(其实是128,但是屏幕一共132只是可以显示的只有128)
{
I2C_SAND_BYTE(OLED0561_ADD, DAT, 0x00);//屏幕清0,注意这里是发送数据DAT不是指令COM(改成0xff整个屏幕都点亮)
}
}
}
//显示英文与数字8*16的ASCII码
//取模大小为16*16,取模方式为“从左到右从上到下”“纵向8点下高位”
void OLED_DISPLAY_8x16(u8 x, //显示英文与数字的页坐标(从0到7)(此处不可修改)
u8 y, //显示英文与数字的列坐标(从0到127)
u16 w) //要显示英文与数字的编号
{
u8 j, t, c = 0;
y = y + 2; //因OLED屏的内置驱动芯片是从0x02列作为屏上最左一列,所以要加上偏移量
for(t = 0; t < 2; t++)
{
I2C_SAND_BYTE(OLED0561_ADD, COM, 0xb0 + x); //页地址(从0xB0到0xB7)
I2C_SAND_BYTE(OLED0561_ADD, COM, y / 16 + 0x10); //起始列地址的高4位是+0x10
I2C_SAND_BYTE(OLED0561_ADD, COM, y % 16); //起始列地址的低4位是+0x00(省略了而已)
for(j = 0; j < 8; j++) //整页内容填充
{
I2C_SAND_BYTE(OLED0561_ADD, DAT, ASCII_8x16[(w * 16) + c - 512]); //为了和ASII表对应要减512
c++;
}
x++; //页地址加1(因为字符是8x16像素首先是第1行显示然后再显示第2行这样刚刚好是16)
}
}
//向LCM发送一个字符串,长度64字符之内。
//应用:OLED_DISPLAY_8_16_BUFFER(0," DoYoung Studio");
void OLED_DISPLAY_8x16_BUFFER(u8 row, u8 *str)
{
u8 r = 0;
while(*str != '\0')
{
OLED_DISPLAY_8x16(row, r * 8, *str++);//每次移动为8列(因为一个字符占8x16)
r++;
}
}
oled0561.h
# ifndef __OLED_H
# define __OLED_H
# include "sys.h"
# include "i2c.h"
# define OLED0561_ADD 0x78 // OLED的I2C地址(禁止修改)
# define COM 0x00 // OLED 指令(禁止修改)
# define DAT 0x40 // OLED 数据(禁止修改)
void OLED0561_Init(void);//初始化
void OLED_DISPLAY_ON (void);//OLED屏开显示
void OLED_DISPLAY_OFF (void);//OLED屏关显示
void OLED_DISPLAY_LIT (u8 x);//OLED屏亮度设置(0~255)
void OLED_DISPLAY_CLEAR(void);//清屏操作
void OLED_DISPLAY_8x16(u8 x,u8 y,u16 w);//显示8x16的单个字符
void OLED_DISPLAY_8x16_BUFFER(u8 row,u8 *str);//显示8x16的字符串
# endif
main.c
/*********************************************************************************************
程序名: 温度值OLED屏显示程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "lm75a.h"
# include "oled0561.h"
int main (void) //主程序
{
u8 buffer[3];
delay_ms(100); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
I2C_Configuration();//I2C初始化
LM75A_GetTemp(buffer); //读取LM75A的温度数据
OLED0561_Init(); //OLED初始化
OLED_DISPLAY_8x16_BUFFER(0, " I LOVE YOU"); //显示字符串(空格加字符一定要是16个字符,不能少,空格占两个字符)
OLED_DISPLAY_8x16_BUFFER(6, " Temp:"); //显示字符串
while(1)
{
LM75A_GetTemp(buffer); //读取LM75A的温度数据
if(buffer[0])OLED_DISPLAY_8x16(6, 7 * 8, '-'); //如果第1组为1即是负温度
OLED_DISPLAY_8x16(6, 8 * 8, buffer[1] / 10 + 0x30); //显示温度值
OLED_DISPLAY_8x16(6, 9 * 8, buffer[1] % 10 + 0x30); //
OLED_DISPLAY_8x16(6, 10 * 8, '.'); //
OLED_DISPLAY_8x16(6, 11 * 8, buffer[2] / 10 + 0x30); //
OLED_DISPLAY_8x16(6, 12 * 8, buffer[2] % 10 + 0x30); //
OLED_DISPLAY_8x16(6, 13 * 8, 'C'); //
delay_ms(200); //延时
}
}
实验现象
OLED屏汉字与图片显示程序
sys.h,sys.c,delay.c,delay.h 和 10 一样;Im75a.c,Im75a.h 和 16 相同; oled0561.c,oled0561.h 在 17 的基础上增加下面的东西(需要添加 ASCII_8x16.h(字库)和 CHS_16x16.h(显示汉字),PIC1.h(显示图片))
oled0561.h 增加
void OLED_DISPLAY_16x16(u8 x,u8 y,u16 w); //汉字显示
void OLED_DISPLAY_PIC1(void);//图片显示
oled0561.c开头增加
# include "CHS_16x16.h" //引入汉字字体
# include "PIC1.h" //引入图片
oled0561.c在下面增加
//----- 用于汉字显示的程序 ------//
//显示汉字16*16
//取模大小为16*16,取模方式为“从左到右从上到下”“纵向8点下高位”
void OLED_DISPLAY_16x16(u8 x, //显示汉字的页坐标(从0xB0到0xB7)
u8 y, //显示汉字的列坐标(从0到63)
u16 w) //要显示汉字的编号
{
u8 j, t, c = 0;
for(t = 0; t < 2; t++)
{
I2C_SAND_BYTE(OLED0561_ADD, COM, 0xb0 + x); //页地址(从0xB0到0xB7)
I2C_SAND_BYTE(OLED0561_ADD, COM, y / 16 + 0x10); //起始列地址的高4位
I2C_SAND_BYTE(OLED0561_ADD, COM, y % 16); //起始列地址的低4位
for(j = 0; j < 16; j++) //整页内容填充
{
I2C_SAND_BYTE(OLED0561_ADD, DAT, GB_16[(w * 32) + c]);
c++;
}
x++; //页地址加1
}
I2C_SAND_BYTE(OLED0561_ADD, COM, 0xAF); //开显示
}
void OLED_DISPLAY_PIC1(void) //显示全屏图片
{
u8 m, i;
for(m = 0; m < 8; m++) //
{
I2C_SAND_BYTE(OLED0561_ADD, COM, 0xb0 + m);
I2C_SAND_BYTE(OLED0561_ADD, COM, 0x10); //起始列地址的高4位
I2C_SAND_BYTE(OLED0561_ADD, COM, 0x02); //起始列地址的低4位
for(i = 0; i < 128; i++) //送入128次图片显示内容
{
I2C_SAND_BYTE(OLED0561_ADD, DAT, PIC1[i + m * 128]);
}
}
}
显示图片的main.c
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "lm75a.h"
# include "oled0561.h"
int main(void)
{
delay_ms(100); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
I2C_Configuration();//I2C初始化
OLED0561_Init(); //OLED初始化
OLED_DISPLAY_LIT(100);//亮度设置
OLED_DISPLAY_PIC1();//显示全屏图片
delay_ms(1000); //延时
}
显示汉字的main.c
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "lm75a.h"
# include "oled0561.h"
int main(void)
{
u8 buffer[3];
delay_ms(100); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
I2C_Configuration();//I2C初始化
LM75A_GetTemp(buffer); //读取LM75A的温度数据
OLED0561_Init(); //OLED初始化
OLED_DISPLAY_8x16_BUFFER(0, " I LOVE YOU"); //显示字符串(空格加字符一定要是16个字符,不能少,空格占两个字符)
OLED_DISPLAY_8x16_BUFFER(6, " Temp:"); //显示字符串
OLED_DISPLAY_16x16(2,0*16,0);//汉字显示 广东理工职业学院
OLED_DISPLAY_16x16(2,1*16,1);
OLED_DISPLAY_16x16(2,2*16,2);
OLED_DISPLAY_16x16(2,3*16,3);
OLED_DISPLAY_16x16(2,4*16,4);
OLED_DISPLAY_16x16(2,5*16,5);
OLED_DISPLAY_16x16(2,6*16,6);
OLED_DISPLAY_16x16(2,7*16,7);
while(1)
{
LM75A_GetTemp(buffer); //读取LM75A的温度数据
if(buffer[0])OLED_DISPLAY_8x16(6, 7 * 8, '-'); //如果第1组为1即是负温度
OLED_DISPLAY_8x16(6, 8 * 8, buffer[1] / 10 + 0x30); //显示温度值
OLED_DISPLAY_8x16(6, 9 * 8, buffer[1] % 10 + 0x30); //
OLED_DISPLAY_8x16(6, 10 * 8, '.'); //
OLED_DISPLAY_8x16(6, 11 * 8, buffer[2] / 10 + 0x30); //
OLED_DISPLAY_8x16(6, 12 * 8, buffer[2] % 10 + 0x30); //
OLED_DISPLAY_8x16(6, 13 * 8, 'C'); //
delay_ms(200); //延时
}
}
CHS_16x16.h
# ifndef __CHS_16x16_H
# define __CHS_16x16_H
uc8 GB_16[] = { // 数据表
//"广"
0x00, 0x00, 0xFC, 0x04, 0x04, 0x04, 0x04, 0x05,
0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00,
0x40, 0x30, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//"东"
0x08, 0x08, 0x08, 0x88, 0x68, 0x18, 0x0F, 0xE8,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00,
0x00, 0x40, 0x21, 0x11, 0x0D, 0x41, 0x81, 0x7F,
0x01, 0x05, 0x09, 0x31, 0x61, 0x00, 0x00, 0x00,
//"理"
0x44, 0x44, 0xFC, 0x44, 0x44, 0x00, 0xFE, 0x92,
0x92, 0xFE, 0x92, 0x92, 0xFE, 0x00, 0x00, 0x00,
0x10, 0x10, 0x1F, 0x08, 0x48, 0x48, 0x44, 0x44,
0x44, 0x7F, 0x44, 0x44, 0x44, 0x40, 0x40, 0x00,
//"工"
0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0xFC,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3F,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00,
//"职"
0x02, 0x02, 0xFE, 0x92, 0x92, 0xFE, 0x02, 0x00,
0xFC, 0x04, 0x04, 0x04, 0x04, 0xFC, 0x00, 0x00,
0x08, 0x08, 0x0F, 0x08, 0x08, 0xFF, 0x04, 0x44,
0x33, 0x0D, 0x01, 0x01, 0x0D, 0x33, 0x60, 0x00,
//"业"
0x00, 0x10, 0x60, 0x80, 0x00, 0xFF, 0x00, 0x00,
0x00, 0xFF, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00,
0x40, 0x40, 0x40, 0x47, 0x40, 0x7F, 0x40, 0x40,
0x40, 0x7F, 0x44, 0x43, 0x40, 0x40, 0x40, 0x00,
//"学"
0x40, 0x30, 0x11, 0x96, 0x90, 0x90, 0x91, 0x96,
0x90, 0x90, 0x98, 0x14, 0x13, 0x50, 0x30, 0x00,
0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x84, 0x7E,
0x06, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00,
//"院"
0x00, 0xFE, 0x22, 0x5A, 0x96, 0x0C, 0x24, 0x24,
0x25, 0x26, 0x24, 0x24, 0x24, 0x04, 0x0C, 0x00,
0x00, 0xFF, 0x04, 0x08, 0x87, 0x81, 0x41, 0x31,
0x0F, 0x01, 0x3F, 0x41, 0x41, 0x41, 0x70, 0x00
};
# endif
PIC1.h
/////////////////////////////////////////////////////////////////////////
// Bitmap点阵数据表 //
// 图片: E:\..萌∧246.bmp,纵向取模下高位,数据排列:从左到右从上到下 //
// 图片尺寸: 128 * 64 //
/////////////////////////////////////////////////////////////////////////
uc8 PIC1[] = // 数据表
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0xF8,0xFC,0x1C,0x38,0x30,0x38,0x1C,
0xFC,0xF8,0x00,0xF8,0xF8,0x00,0xF8,0xF8,
0x00,0x00,0x00,0xF8,0xFC,0x8C,0x8C,0x8C,
0x00,0x80,0xD0,0x50,0xF0,0xE0,0x00,0xF0,
0xF0,0x20,0x30,0x00,0x00,0x80,0x80,0x00,
0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x0C,
0x12,0x24,0x12,0x0C,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x18,0x24,0x44,0x88,
0x44,0x24,0x18,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x03,0x83,0x80,0x00,0x80,0x80,0x00,
0x03,0x03,0x00,0x08,0x0B,0x0B,0x0F,0x07,
0x80,0x40,0x80,0x40,0x83,0x03,0x03,0x03,
0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x03,
0x83,0x80,0x80,0x80,0x80,0xC3,0xC3,0xC0,
0xC3,0xC3,0xC0,0xC3,0xC3,0xC0,0xC0,0xC0,
0xC0,0xC0,0x80,0x80,0x80,0xC0,0xC0,0x40,
0x20,0x18,0x04,0x03,0x00,0x00,0x00,0x00,
0x00,0x20,0x50,0xA0,0x50,0x20,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x03,0x04,0x08,0x11,0x08,0x04,0x03,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x02,0x84,0xC2,0xE1,0x60,0x30,0x10,
0x78,0xEC,0xFC,0xF6,0x3A,0x1B,0x05,0x85,
0x97,0x13,0x12,0x13,0x13,0x11,0x11,0x11,
0x11,0x11,0x11,0x01,0x81,0xFF,0xF9,0xF9,
0xF2,0xC2,0x82,0x22,0x25,0x05,0x8D,0x8B,
0x93,0xB3,0xE6,0xC6,0x8C,0x1C,0x18,0x38,
0x70,0x70,0xE0,0xC0,0x80,0x00,0x00,0x00,
0x00,0x00,0x00,0x18,0x24,0x48,0x24,0x18,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0xE0,0xE0,0x70,0x30,0x18,
0x18,0x1C,0x1C,0x1C,0x0C,0x1C,0x16,0x16,
0x16,0x06,0x27,0x63,0x6D,0xAE,0xCE,0x0E,
0x0E,0x0F,0x0F,0x0F,0x0E,0x06,0x06,0x07,
0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,
0x06,0x06,0x06,0x66,0x77,0x67,0x67,0xE7,
0x07,0x07,0x07,0x06,0x06,0x06,0x07,0x07,
0x87,0xC7,0x67,0x23,0x23,0x12,0x10,0x00,
0x08,0x08,0x09,0x0B,0x0F,0x0E,0x0E,0x0C,
0x3C,0x70,0xF0,0xC0,0x80,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0xF8,
0xDE,0xFF,0xF6,0x36,0x11,0xE0,0xF0,0xF8,
0xFC,0xFE,0xDE,0x1E,0x3F,0x3F,0x1E,0xFE,
0xFE,0xFC,0xF8,0xF2,0xE4,0x08,0x01,0x07,
0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x00,0x00,0x00,0x00,0x00,0x3C,0xFF,0xE7,
0x31,0x1C,0xE6,0xFF,0xFF,0xFF,0xFE,0xFE,
0x1F,0x3F,0x3F,0x1F,0xFF,0xFE,0xFE,0xFC,
0xF8,0xF8,0xF0,0x31,0xFB,0xFF,0xFE,0xFC,
0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,
0x07,0x4F,0x4C,0x60,0x60,0x7F,0x7F,0x7F,
0x7F,0x7D,0x4D,0x44,0x72,0x72,0x40,0x79,
0x7D,0x7F,0x7F,0x7F,0x7F,0x7C,0x70,0x70,
0x73,0x73,0x70,0x70,0x74,0x74,0x74,0x74,
0x74,0x74,0x74,0x74,0x74,0x74,0x74,0x74,
0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x73,
0x70,0x70,0x72,0x72,0x72,0x73,0x70,0x70,
0x70,0x7C,0x7F,0x7F,0x7F,0x7F,0x7D,0x7D,
0x62,0x72,0x72,0x60,0x68,0x7D,0x7F,0x7F,
0x7F,0x7F,0x77,0x74,0x67,0x67,0x63,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
实验现象
取模软件的使用
在上面的 ASCII_8x16.h,PIC1.h,CHS_16x16.h 里面的数据都是通过取模软件生成的(软件可在书签第一个内找)下面介绍如何使用:
- ASCII_8x16.h
- CHS_16x16.h
- PIC1.h
注:把数组里面内容替换即可别把数组名也替换了
继电器的原理与驱动程序
本节用到的固件库函数
- GPIO_PinRemapConfig(手册 10.2.16)
sys.h,sys.c,delay.c,delay.h 和 10 一样;touch_key.c,touch_key.h 与 13 相同;只需增加 relay.c,relay.h 即可
继电器是一种自动控制开关,通常用在小电流电路控制大电流电路
应用:自动控制(单片机领域),电气隔离,安全保护,转换电路
- 通过控制 PA13,PA14端口,输出高电平时对应的继电器吸合,反之松开(和控制 LED 差不多)
- 上电时默认是 JTAG 模式,需要关闭它才能用I/O模式
- 因为继电器的线圈需要很大的电流才能驱动,单凭单片机 I/O口输出电平是无法驱动继电器的,所以要使用 ULN2003芯片,将单片机端口连接芯片输入端,则输出端就可以输出很大电流
ULN2003 芯片
ULN2003 是高耐压、大电流达林顿陈列,由七个硅NPN 达林顿管组成。ULN2003 工作电压高,工作电流大,灌电流可达500mA,并且能够在关态时承受50V 的电压,输出还可以在高负载电流并行运行。
in
:输入 out
:输出
- XQ:线圈(线圈不分正负极只要有电流通过就可以吸合)
relay.h
# ifndef __RELAY_H
# define __RELAY_H
# include "sys.h"
# define RELAYPORT GPIOA //定义IO接口
# define RELAY1 GPIO_Pin_14 //定义IO接口
# define RELAY2 GPIO_Pin_13 //定义IO接口
void RELAY_Init(void);//继电器初始化
void RELAY_1(u8 c);//继电器控制1
void RELAY_2(u8 c);//继电器控制2
# endif
relay.c
/*
注意:
本程序所占用的GPIO接口PA13、PA14上电后为JTAG功能,
需要在RCC程序里启动AFIO时钟,再在RELAY_Init函数里加入:
GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);
// 改变指定管脚的映射,完全禁用JTAG+SW-DP才能将JATG接口重定义为GPIO
*/
# include "relay.h"
void RELAY_Init(void) //继电器的接口初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); //APB2外设时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//启动AFIO重映射功能时钟
GPIO_InitStructure.GPIO_Pin = RELAY1 | RELAY2; //选择端口号(0~15或all)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
GPIO_Init(RELAYPORT, &GPIO_InitStructure);
//必须将禁用JTAG功能才能做GPIO使用
GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);// 改变指定管脚的映射,完全禁用JTAG+SW-DP
GPIO_ResetBits(RELAYPORT, RELAY1 | RELAY2); //都为低电平(0) 初始为关继电器
}
void RELAY_1(u8 c) //继电器的控制程序(c=0继电器放开,c=1继电器吸合)
{
GPIO_WriteBit(RELAYPORT, RELAY1, (BitAction)(c)); //通过参数值写入接口
}
void RELAY_2(u8 c) //继电器的控制程序(c=0继电器放开,c=1继电器吸合)
{
GPIO_WriteBit(RELAYPORT, RELAY2, (BitAction)(c)); //通过参数值写入接口
}
main.c
/*********************************************************************************************
程序名: 按键控制继电器程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
int main (void){//主程序
RCC_Configuration(); //系统时钟初始化
TOUCH_KEY_Init();//触摸按键初始化
RELAY_Init();//继电器初始化
while(1){
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A))
RELAY_1(1); //当按键A按下时继电器1标志置位
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B))
RELAY_1(0); //当按键B按下时继电器1标志置位
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C))
RELAY_2(1); //当按键C按下时继电器2标志置位
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D))
RELAY_2(0); //当按键D按下时继电器2标志置位
}
}
实验现象
步进电机的原理与驱动程序
sys.h,sys.c,delay.c,delay.h 和 10 一样;touch_key.c,touch_key.h 与 13 相同; relay.c,relay.h 和 18 相同,只需增加 step_motor.c,step_motor.h
- 因为单片机 I/O口不能输出很大的驱动电流,无法直接带动步进电机,所以通过 ULN2003L 芯片输出强大电流带动步进电机
- 型号:28BJY48 (28表示电机直径28毫米,B表示步进电机,Y表示永磁,J表示带减速箱,48表示可以四拍和八拍运行。)
- 4拍的顺序图
- 8拍的顺序图
具体步进电机信息可查看 51单片机~步进电机 文章
步进电机的重要说明:
- 不同型号的步进电机有不同的驱动电压,使用前要确定标称电压。
- 步进电机停转时不能给线圈长时间通电,会导致电机发热损坏。
- 除4步、8步驱动方式外,还可用专用步进驱动器做最大256倍的精细角度驱动。
- 步进电机的扭矩(力度)是与电流相关的。旋转速度是与切换时间相关的。
step_motor.h
# ifndef __STEP_MOTOR_H
# define __STEP_MOTOR_H
# include "sys.h"
# include "delay.h"
extern u8 STEP; //定义单步计数 全局变量
# define STEP_MOTOR_PORT GPIOB //定义IO接口所在组
# define STEP_MOTOR_A GPIO_Pin_3 //定义IO接口
# define STEP_MOTOR_B GPIO_Pin_4 //定义IO接口
# define STEP_MOTOR_C GPIO_Pin_8 //定义IO接口
# define STEP_MOTOR_D GPIO_Pin_9 //定义IO接口
void STEP_MOTOR_Init(void);//初始化
void STEP_MOTOR_OFF (void);//断电状态
void STEP_MOTOR_8A (u8 a,u16 speed);
void STEP_MOTOR_NUM (u8 RL,u16 num,u8 speed);//电机按步数运行
void STEP_MOTOR_LOOP (u8 RL,u8 LOOP,u8 speed);//电机按圈数运行
# endif
step_motor.c
- 单步8拍
# include "step_motor.h"
u8 STEP;
void STEP_MOTOR_Init(void){ //接口初始化
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); //APB2外设GPIO时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//启动AFIO重映射功能时钟
GPIO_InitStructure.GPIO_Pin = STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D; //选择端口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
GPIO_Init(STEP_MOTOR_PORT, &GPIO_InitStructure);
//必须将禁用JTAG功能才能做GPIO使用
GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);// 改变指定管脚的映射,完全禁用JTAG+SW-DP
STEP_MOTOR_OFF(); //初始状态是断电状态
}
void STEP_MOTOR_OFF (void){//电机断电
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D);//各接口置0
}
void STEP_MOTOR_8A (u8 a,u16 speed){//电机单步8拍
switch (a){
case 0:
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D);//0(B,C,D不通电)
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A);//1(给A通电)
break;
case 1:
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//1
break;
case 2:
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_C | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B);//1
break;
case 3:
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//1
break;
case 4:
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C);//1
break;
case 5:
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//1
break;
case 6:
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_D);//1
break;
case 7:
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//1
break;
default:
break;
}
delay_ms(speed); //延时
STEP_MOTOR_OFF(); //进入断电状态,防电机过热
}
void STEP_MOTOR_NUM (u8 RL,u16 num,u8 speed){//电机按步数运行
u16 i;
for(i=0;i<num;i++){
if(RL==1){ //当RL=1右转,RL=0左转
STEP++;
if(STEP>7)STEP=0;
}else{
if(STEP==0)STEP=8;
STEP--;
}
STEP_MOTOR_8A(STEP,speed);
}
}
void STEP_MOTOR_LOOP (u8 RL,u8 LOOP,u8 speed){//电机按圈数运行
STEP_MOTOR_NUM(RL,LOOP*4076,speed); //具体数值需要自己测试,一圈刚刚好是4076
}
main.c
/*********************************************************************************************
程序名: 步进电机步数控制程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "step_motor.h"
int main (void){//主程序
RCC_Configuration(); //系统时钟初始化
TOUCH_KEY_Init();//触摸按键初始化
RELAY_Init();//继电器初始化
STEP_MOTOR_Init();//步进电机初始化
while(1){
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A))
STEP_MOTOR_LOOP(0,1,3); // 按圈数左转 参1:RL,参2:LOOP ,参3:speed(值越小速度越快不能为0不然不会动)
else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B))
STEP_MOTOR_LOOP(1,1,3); //按圈数右转
else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C))
STEP_MOTOR_NUM(0,100,3); //按步数左转 参1:RL,参2:num,参3:speed
else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D))
STEP_MOTOR_NUM(1,100,3); //按步数右转 参1:RL,参2:num,参3:speed
else STEP_MOTOR_OFF();//当没有按键时步进电机断电
}
}
实验现象
按键控制步进电机程序
sys.h,sys.c,delay.c,delay.h 和 10 一样;touch_key.c,touch_key.h 与 13 相同; relay.c,relay.h 和 18 相同,只需增加 step_motor.c,step_motor.h
step_motor.h
# ifndef __STEP_MOTOR_H
# define __STEP_MOTOR_H
# include "sys.h"
# include "delay.h"
# define STEP_MOTOR_PORT GPIOB //定义IO接口所在组
# define STEP_MOTOR_A GPIO_Pin_3 //定义IO接口
# define STEP_MOTOR_B GPIO_Pin_4 //定义IO接口
# define STEP_MOTOR_C GPIO_Pin_8 //定义IO接口
# define STEP_MOTOR_D GPIO_Pin_9 //定义IO接口
void STEP_MOTOR_Init(void);//初始化
void STEP_MOTOR_OFF (void);//断电状态
void STEP_MOTOR_4S (u8 speed);//固定位置(制动)
void STEP_MOTOR_4R (u8 speed);//
void STEP_MOTOR_4L (u8 speed);
void STEP_MOTOR_8R (u8 speed);
void STEP_MOTOR_8L (u8 speed);
# endif
step_motor.c
# include "step_motor.h"
void STEP_MOTOR_Init(void){ //LED灯的接口初始化
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); //APB2外设GPIO时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//启动AFIO重映射功能时钟
GPIO_InitStructure.GPIO_Pin = STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D; //选择端口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)
GPIO_Init(STEP_MOTOR_PORT, &GPIO_InitStructure);
//必须将禁用JTAG功能才能做GPIO使用
GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);// 改变指定管脚的映射,完全禁用JTAG+SW-DP
STEP_MOTOR_OFF(); //初始状态是断电状态
}
void STEP_MOTOR_OFF (void){//电机断电
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D);//各接口置0
}
void STEP_MOTOR_4S (u8 speed){//电机固定位置
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A| STEP_MOTOR_C); //各接口置0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_D); //各接口置1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D);
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_C); //1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D);
delay_ms(speed); //延时
STEP_MOTOR_OFF(); //进入断电状态,防电机过热
}
void STEP_MOTOR_4R (u8 speed){//电机顺时针,4拍,速度快,力小
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A| STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//1
delay_ms(speed); //延时
STEP_MOTOR_OFF(); //进入断电状态,防电机过热
}
void STEP_MOTOR_4L (u8 speed){//电机逆时针,4拍,速度快,力小
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//1
delay_ms(speed); //延时
STEP_MOTOR_OFF(); //进入断电状态,防电机过热
}
void STEP_MOTOR_8R (u8 speed){//电机顺时针,8拍,角度小,速度慢,力大
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_C | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_D);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//1
delay_ms(speed); //延时
STEP_MOTOR_OFF(); //进入断电状态,防电机过热
}
void STEP_MOTOR_8L (u8 speed){//电机逆时针,8拍,角度小,速度慢,力大
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_C);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_D);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_C);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_C | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_B);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_C | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_B);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C | STEP_MOTOR_D);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A);//1
delay_ms(speed); //延时
GPIO_ResetBits(STEP_MOTOR_PORT,STEP_MOTOR_B | STEP_MOTOR_C);//0
GPIO_SetBits(STEP_MOTOR_PORT,STEP_MOTOR_A | STEP_MOTOR_D);//1
delay_ms(speed); //延时
STEP_MOTOR_OFF(); //进入断电状态,防电机过热
}
main.c
/*********************************************************************************************
程序名: 按键控制步进电机程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "step_motor.h"
int main (void){//主程序
RCC_Configuration(); //系统时钟初始化
TOUCH_KEY_Init();//触摸按键初始化
RELAY_Init();//继电器初始化
STEP_MOTOR_Init();//步进电机初始化
while(1){
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A))STEP_MOTOR_4R(3); //当按键A按下时步进电机4步右转
else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B))STEP_MOTOR_4L(3); //当按键B按下时步进电机4步左转
else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C))STEP_MOTOR_8R(3); //当按键C按下时步进电机8步右转
else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D))STEP_MOTOR_8L(3); //当按键D按下时步进电机8步左转
else STEP_MOTOR_OFF();//当没有按键时步进电机断电
}
}
RS232 原理与驱动程序
sys.h,sys.c,delay.c,delay.h 和 10 一样;touch_key.c,touch_key.h 与 13 相同; relay.c,relay.h 和 18 相同;oled0561.c,oled0561.h 和 17 相同;只需更改一下 usart.c,usart.h
- 如果没有RS232的线可以用杜邦线代替实验,只需把上方第二针和第三针短接(RX 和 TX)
usart.h
# ifndef __USART_H
# define __USART_H
# include <stdarg.h>
# include <stdlib.h>
# include <string.h>
# include "stdio.h"
# include "sys.h"
# define USART_n USART1 //定义使用printf函数的串口,其他串口要使用USART_printf专用函数发送
# define USART1_REC_LEN 200 //定义USART1最大接收字节数
# define USART2_REC_LEN 200 //定义USART2最大接收字节数
# define USART3_REC_LEN 200 //定义USART3最大接收字节数
//不使用某个串口时要禁止此串口,以减少编译量
# define EN_USART1 1 //使能(1)/禁止(0)串口1
# define EN_USART2 0 //使能(1)/禁止(0)串口2
# define EN_USART3 1 //使能(1)/禁止(0)串口3
extern u8 USART1_RX_BUF[USART1_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u8 USART2_RX_BUF[USART2_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u8 USART3_RX_BUF[USART3_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART1_RX_STA; //接收状态标记
extern u16 USART2_RX_STA; //接收状态标记
extern u16 USART3_RX_STA; //接收状态标记
//函数声明
void USART1_Init(u32 bound);//串口1初始化并启动
void USART2_Init(u32 bound);//串口2初始化并启动
void USART3_Init(u32 bound);//串口3初始化并启动
void USART1_printf(char* fmt,...); //串口1的专用printf函数
void USART2_printf(char* fmt,...); //串口2的专用printf函数
void USART3_printf(char* fmt,...); //串口3的专用printf函数
# endif
usart.c
- 因为使用的是串口3所以我把其他串口的函数删除只留下串口3的
- RS232通常遵循“96-N-8-1”格式,96指波特率9600,N指无校验,8指8bits数据位,1指1bit停止位
# include "sys.h"
# include "usart.h"
//使UASRT串口可用printf函数发送
//在usart.h文件里可更换使用printf函数的串口号
# if 1
# pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE {
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x){
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f){
while((USART_n->SR&0X40)==0);//循环发送,直到发送完毕
USART_n->DR = (u8) ch;
return ch;
}
# endif
# if EN_USART3 //如果使能了接收
u8 USART3_RX_BUF[USART3_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART3_RX_STA=0; //接收状态标记
/*
USART3专用的printf函数
当同时开启2个以上串口时,printf函数只能用于其中之一,其他串口要自创独立的printf函数
调用方法:USART3_printf("123"); //向USART3发送字符123
*/
void USART3_printf (char *fmt, ...){
char buffer[USART3_REC_LEN+1]; // 数据长度
u8 i = 0;
va_list arg_ptr;
va_start(arg_ptr, fmt);
vsnprintf(buffer, USART3_REC_LEN+1, fmt, arg_ptr);
while ((i < USART3_REC_LEN) && (i < strlen(buffer))){
USART_SendData(USART3, (u8) buffer[i++]);
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
}
va_end(arg_ptr);
}
void USART3_Init(u32 BaudRate){ //USART3初始化并启动
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE); //使能UART3所在GPIOB的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //使能串口的RCC时钟
//串口使用的GPIO口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//设置USART3的RX接口是PB11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//接口模式 浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//设置USART3的TX接口是PB10
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度50MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//接口模式 复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
//配置串口
USART_InitStructure.USART_BaudRate = BaudRate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART3, &USART_InitStructure);//配置串口3
USART_ITConfig(USART3, USART_IT_RXNE, DISABLE);//使能串口接收中断
//USART_ITConfig(USART3, USART_IT_TXE, ENABLE);//串口发送中断在发送数据时开启
USART_Cmd(USART3, ENABLE);//使能串口3
//串口中断配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;//允许USART3中断
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//中断等级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//串口3中断服务程序(固定的函数名不能修改)
void USART3_IRQHandler(void){
//这里没有用中断,所以上面 USART_ITConfig是 DISABLE
}
# endif
main.c
/*********************************************************************************************
程序名: RS232通信测试程序
硬件支持: STM32F103C8 外部晶振8MHz RCC函数设置主频72MHz
说明:
# 本模板加载了STM32F103内部的RCC时钟设置,并加入了利用滴答定时器的延时函数。
# 可根据自己的需要增加或删减。
*********************************************************************************************/
# include "stm32f10x.h" //STM32头文件
# include "sys.h"
# include "delay.h"
# include "touch_key.h"
# include "relay.h"
# include "oled0561.h"
# include "usart.h"
int main (void){//主程序
u8 a;
delay_ms(100); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
TOUCH_KEY_Init();//触摸按键初始化
RELAY_Init();//继电器初始化
I2C_Configuration();//I2C初始化
OLED0561_Init(); //OLED初始化
OLED_DISPLAY_8x16_BUFFER(0," YoungTalk "); //显示字符串
OLED_DISPLAY_8x16_BUFFER(2," RS232 TEST "); //显示字符串
OLED_DISPLAY_8x16_BUFFER(6,"TX: RX: "); //显示字符串
USART3_Init(115200);//串口3初始化并启动
while(1){
if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_A)){USART3_printf("%c",'A');OLED_DISPLAY_8x16(6,4*8,'A');} //向RS232串口发送字符并在OLED上显示
else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_B)){USART3_printf("%c",'B');OLED_DISPLAY_8x16(6,4*8,'B');} //向RS232串口发送字符并在OLED上显示
else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_C)){USART3_printf("%c",'C');OLED_DISPLAY_8x16(6,4*8,'C');} //向RS232串口发送字符并在OLED上显示
else if(!GPIO_ReadInputDataBit(TOUCH_KEYPORT,TOUCH_KEY_D)){USART3_printf("%c",'D');OLED_DISPLAY_8x16(6,4*8,'D');} //向RS232串口发送字符并在OLED上显示
//查询方式接收
if(USART_GetFlagStatus(USART3,USART_FLAG_RXNE) != RESET){ //查询串口待处理标志位
a =USART_ReceiveData(USART3);//读取接收到的数据
OLED_DISPLAY_8x16(6,11*8,a);//在OLED上显示
}
}
}
实验现象