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.h10 一样;只需增加 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.h10 一样; 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.h10 一样;led.c,led.h,key.c,key.h8 相同,buzzer.c,buzzer.h10.1 相同;usart.c,usart.h11.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.h10 一样;led.c,led.h,key.c,key.h8 相同,buzzer.c,buzzer.h10.1 相同;usart.c,usart.h11.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.hLib 文件记得添加 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.h10 一样;led.c,led.h,key.c,key.h8 相同,buzzer.c,buzzer.h10.1 相同;usart.c,usart.h11.1 相同,rtc.c,rtc.h12 相同

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.h10 一样;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.h13 相同

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.h13 相同;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.h10 一样;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.h10 一样;rtc.c,rtc.h 12相同;TM1640.c TM1640.h14 相同;只需添加 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.h10 一样;TM1640.c TM1640.h14 相同;只需添加 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位来判断起始条件是否产生。
​ 主发送器发送地址后,如果从设备产生了应答信号,就会产生 EV6EV8 事件,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 通信虽然用了TXDRXD两根线,但是实际一次通信中, 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.h10 一样;Im75a.c,Im75a.h16 相同;只需添加 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.h10 一样;Im75a.c,Im75a.h16 相同; oled0561.c,oled0561.h17 的基础上增加下面的东西(需要添加 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.h10 一样;touch_key.c,touch_key.h13 相同;只需增加 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.h10 一样;touch_key.c,touch_key.h13 相同; relay.c,relay.h18 相同,只需增加 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.h10 一样;touch_key.c,touch_key.h13 相同; relay.c,relay.h18 相同,只需增加 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.h10 一样;touch_key.c,touch_key.h13 相同; relay.c,relay.h18 相同;oled0561.c,oled0561.h17 相同;只需更改一下 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上显示
		}
	}
}

实验现象