安卓设备通过USB串口与STM32单片机通讯之三
本博文系JGB联合商务组的原创作品,引用请标明出处。
本博文接续上一篇的末尾章节
(三)STM32芯片内的C代码部分(使用keil 5.1 环境开发 )
前述提及的JGB01开发板使用的芯片型号为STM32F103C8T6, 在ARM公司的Keil 5.1环境中用C语言开发。
在这里我首先需要把此开发板的硬件电路图展示在这里,因为我此后的C编程是需要面向管脚功能的定义来进行的。
而在下一节中会有更加详细的本开发板硬件制作介绍。
(图3-1)
从该图中得出如下几个重要功能管脚的定义如下:
蓝灯(图中标号为Blue,为最小板上内置)的阴极控制脚: PB12
(开漏输出,即高电平时灯熄灭,低电平时亮起)
温控传感器DS18B20(图中标号为U1)的信号脚S : PB7
(单总线,输入和输出共用此脚)
USB-TTL桥接器(图中标号为P1) : TXD 连接到 PB11, RXD 连接到 PB10
(即使用的是STM32F103C8T6芯片的USART3串口)
以上的PBXX管脚在最小板(图中标号为P2)实物上的丝印均简单标记为BXX,如PB12就是B12,余类同。
先使用位带(BB, BitsBand)定义几个管脚。使用位带,我们能够象操作一个逻辑变量一样操作某个硬件管脚,非常方便。
下面是几个功能管脚的位带定义:
LEDAPin管脚指向PB12,输出类型。
<exti.h>
#define LEDAPin PBout(12) // 蓝灯的阴极控制脚 PB12
DS18B20_DQ_OUT 和 DS18B20_DQ_IN 管脚定义均指向PB7,既是输出类型也是输入类型(单总线)。
<ds18b20.h>
#define DS18B20_DQ_OUT PBout(7) //单总线输出脚 PB7
#define DS18B20_DQ_IN PBin(7) //单总线输入脚 PB7
随意在某宝上购置STM32开发板时,商家都会给你发来一个包罗万象的函数包和开发实例,供你学习。本文中主要用到的头函数如下:
(1)温度传感器驱动:ds18b20.h / ds18b20.c
(2)串口2和串口3的初始化及通用读写 : usart123.h / usart123.c
(3)位带定义头函数: BitBandDef.h
其中常用到的ms级和us级的延时函数我把它定义在: Global.h / Global.c
Global.c 源文件如下:
#include "Global.h"
/*******************************************************************************
* 函 数 名 : Delay
* 函数功能 : 延时函数,通过while循环占用CPU,达到延时功能
* 输 入 : i
* 输 出 : 无
*******************************************************************************/
// 即1毫秒=8000 个while循环 (条件: 1 SysTick=1/9us, 在main()内的时钟初始化时完成)
void DelayMS(u32 i)
{
u32 cnt;
cnt=(i*8000) ;
while(cnt--);
}
//即 1微秒=8 个while循环 (条件: 1 SysTick=1/9us, 在main()内的时钟初始化时完成),
void DelayUS(u32 i)
{
u32 cnt;
cnt=(i*8) ;
while(cnt--);
}
而重定向函数printf()声明在main.h, 指定为USART3
FILE __stdout;
int fputc(int ch, FILE *f)
{
//重定向到USART3
while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET);
USART_SendData(USART3,(u8)ch);
return ch;
}
主函数main.c (代码已实测OK) 如下:
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_rcc.h"
#include "stdio.h"
#include "main.h"
#include "string.h"
#include "Global.h"
#include "BitBandDef.h"
#include "exti.h"
#include "SysTick.h"
#include "ds18b20.h"
#include "usart123.h"
#include <math.h>
//UA3RxOKFlag=USART3接收正常的标记, UA3TxOKFlag=USART3发送正常的标记, UA3RxCNT = USART3接收到的字节总数
//这几个定义在usart123.c, extern 关键词表示该变量在其它 *.c 文件中定义过了
extern u8 UA3RxOKFlag, UA3TxOKFlag, UA3BufRecei[];
//USART3接收到的字节总数
extern u16 UA3RxCNT;
//逻辑标记位: 是否点亮指示灯
u8 SetLedxFlag = 0;
//逻辑标记位: 是否循环取回温度值
u8 GetTempFlag = 0;
int main()
{
u32 cnt = 0;
u32 chCounter =0 ;
//本次检查的温度值
float temper = 0;
//注意不要申请过大的内存(例如1280字节),否则直接死机
//USART3发送缓冲区
char chUSART3[128] = {
0 };
char *pos = NULL;
SetLedxFlag = 0;
GetTempFlag = 0;
//时钟初始化,一个时钟跳步计数(SysTick) = 1/9 us
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
//最大装入
SysTick->LOAD = 0xFFFFFF;
//NVIC分组设定只能设定一次,此句放在Main()函数中的靠前位置较好
//分组1,有2个抢占优先级 (0..1) ,8个响应优先级
//分组2,有4个抢占优先级 (0..3), 4个响应优先级(这里改为这个比较合适)
//约定: USB-中断抢占优先级1(预留给以后开发使用),
//轻触按钮/TIM4定时器-中断抢占优先级2,
//USART2/USART3-中断抢占优先级3
//中断分组设定 : 使用分组2
//该函数由系统固件库: misc.h / misc.c 提供
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//串口初始化 : usart123.h / usart123.c 提供
//本开发板使用的串口管脚PB11/PB10,因此使用的是USART3,只需调用相应的USART3初始化函数即可
USART3_Init(115200);
//串口3发送长度为strlen的字符串
USART3SendString((u8*)"USART3 Init. OK\r\n", strlen("USART3 Init. OK\r\n"));
//清空USART3的接收缓存
memset(UA3BufRecei, 0, UA3_MAX_RECV_LEN);
UA3RxCNT = 0;
//相关GPIO管脚初始化
LEDKey_Config();
EXTI_Config();
printf("JGB01开发板的GPIO初始化完成\r\n");
//温度传感器初始化: 由 ds18b20.h / ds18b20.c 提供
if (DS18B20_Init())
{
printf("DS18B20器件未找到!\r\n");
DelayMS(100);
}
else {
printf("DS18B20 Test OK!\r\n");
//刚上电时第一次取的温度值总不是太准,可以多取一次
temper = DS18B20_GetTemperture();
DelayMS(1000);
temper = DS18B20_GetTemperture(); //多取一次
printf("Test Temperture: %.2f\r\n", temper);
}
while (1)
{
//UA3RxOKFlag=USART3接收正常的标记,
//UA3TxOKFlag=USART3发送正常的标记,
//UA3RxCNT = USART3接收到的字节总数
//USART3接收正常后的动作
if (UA3RxOKFlag)
{
UA3RxOKFlag = 0;
//如果收到的USART3字符串为: SetLedx:1,则准备点亮蓝灯
pos = strstr((char *)(UA3BufRecei), (char *)("SetLedx:1"));
if (pos != NULL) SetLedxFlag = 1;
//如果收到的USART3字符串为: SetLedx:0,则准备熄灭蓝灯
pos = strstr((char *)(UA3BufRecei), (char *)("SetLedx:0"));
if (pos != NULL) SetLedxFlag = 0;
//如果收到的USART3字符串为: GetTemp:1,则准备循环发送温度值
pos = strstr((char *)(UA3BufRecei), (char *)("GetTemp:1"));
if (pos != NULL) GetTempFlag = 1;
//如果收到的USART3字符串为: GetTemp:0,则停止循环发送温度值
pos = strstr((char *)(UA3BufRecei), (char *)("GetTemp:0"));
if (pos != NULL) GetTempFlag = 0;
//给USART3的TX端回发收到的消息
UA3TxOKFlag = 0;
//字符串格式化到内存块chUSART3, 返回值chCounter是格式化成功的字节数
memset(chUSART3, 0, sizeof(chUSART3));
chCounter = sprintf(chUSART3, "Received: %s\r\n", UA3BufRecei);
USART3SendString((u8*)(chUSART3), chCounter);
//一直等到发送完成后UA3TxOKFlag =1,才进入下一行
while (UA3TxOKFlag == 0);
//这里清空接收数据缓存
memset(UA3BufRecei, 0, UA3_MAX_RECV_LEN);
UA3RxCNT = 0;
} //if (UA3RxOKFlag) end
//每隔10ms循环一次
DelayMS(10);
//计数
cnt += 1;
//根据前面的逻辑标记检查是否需要点亮蓝色LED
if (cnt > 600) {
if (SetLedxFlag) {
//点亮蓝色LED,使用位带操作,是最简单的,推荐使用
LEDAPin = 0;
}
else {
//熄灭蓝色LED
LEDAPin = 1;
}
}
else {
if ((cnt % 30) == 0) {
//内部引线LED灯A :PB12 , 开漏输出 ,最小板上的蓝灯
//只在上电后的6秒钟内闪动
//使用位带(BB)定义,对硬件管脚的操作和逻辑变量一样,很方便
LEDAPin = !LEDAPin;
}
} // if (cnt>600) end
//根据前面的逻辑标记检查是否需要每隔5S往USART3串口发送当前温度
if (((cnt % 500) == 0) && GetTempFlag) {
temper = DS18B20_GetTemperture();
printf("Now Temperture: %.2f\r\n", temper);
}
} //while end
} //main() end
在厂家提供的串口头函数 usart123.h / usart123.c 中对USART3的初始化有个缺点如下:
void USART3_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//使用USART1时用
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
//这里使用USART3时要改为两句,因为USART3是挂在APB1上的,和USART1挂在APB2上不同
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3 , ENABLE);
USART_DeInit(USART3);
//这里使用USART3 ,使用的管脚是 PB10-TX,
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//这里使用USART3 ,使用的管脚是 PB11-RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//原码是设为浮空输入
//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//本人修改为上拉电阻输入,其原因有二:
//(1) 在大多数的最小板上是没有TTL电平到RS232电平转换模块的,如果由于某个原因你又没在USART3接入USB-TTL桥接器
//或者接上了桥接器但未给它接上+5V供电,
//此时: RX脚要么是悬空的要么是被未上电的USB-TTL桥接器拉为低电平,
//此时初始化或运行串口接收大概率是死机的(此故障极难查找,特此提示),即程序运行到这个USART3_Init()就停下来了.
//(2) 对于CMOS器件,浮空引脚易造成干扰或静电击穿,电路设计是一定要避免这样做的.
//修改后为上拉电阻输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// ................
}
重建,编译并调试这个项目是很简单的,在最后一节中我将连同此开发板的硬件加工和制作做一个完整的介绍.