APM32E103ZET6 EVAL评估板是APM32E1系列增强型MCU的完整演示和开发平台,它带有一颗基于Arm® Cortex®-M3内核的32位MCU(APM32E103ZET6),工作主频120MHz、Flash 512KB、SRAM 128KB;可提供一个2.4英寸TFT LCD,像素240x320px;存储方面支持EEPROM、SPI FLASH、SD Card以及SDRAM,满足用户更多开发需求;内置2*CAN、1*USB、RTC、ADC等外设资料,并支持USB和CAN同时使用。

作为一颗Cortex®-M3内核的MCU,想来应该和STM32F1系列的有较大的通用性,程序的例程应该一搜一大把,然而仅仅是移值DHT11的驱动就颇费了一番折腾。下面就折腾过程中的一些经验教训做一番总结,期待读过此文的人能少绕些圈,能快速上手。首先从厂家提供的SDK例程开始熟悉开发环境和工程组件结构。

解开SDK包可以看到如上图的目录结构,Readme文件里有关于SDK目录结构的介绍。相关的资源和外设程序构件在Boards和Libraries目录下,板子程序的实例在Examples目录下。进入Examples\ADC\ADC_Potentiometer\Project\MDK,即可打开第一个例程工程。打开后会提示找不到APM32E1硬件设备,可以通过Keil的库管理工具直接下载安装。

库安装后就没有报错了,可以直接试着编译,暂时不能通过,仔细看是编译器版本不合。打开工程配置Target选项卡右上角Arm Compiler另选一个编译器版本就解决了,合着就是默认的这个编译器版本是编译通不过的。顺便更改晶振频率为8MHz,Output选项卡下勾选输出HEX文件。

修改编译器后就顺利通过编译了,接下来就得把编译好的程序下载到开发板了。首先试了FlyMcu,开发板Boot0置位后,可以连接、识别为STM32F0x,可以下载程序,显示为下载成功,但实际板载程序并未改变,串口下载模式实验失败。

试了Jlink V8,可能是固件版本原因下载也不成功。最后找出了看家宝贝,9.9的PWLINK2连接、下载、调试均成功。

Keil5相关设置:Debug选先卡右上角选CMSIS-DAP,用于程序下载。Utilities选项卡左上角sitting内Debug选项卡左上角选CMSIS-DAP,用于调试连接,此项须先连上PWLINK才有得选。

例程在板载按钮和串口输出条件下可以很容易获得结果检验,需要用USB线连接板载串口模块,运行串口调试工具接收程序输出。下面就是I2C EEROM读写例程的运行效果。

下面开始说道正题,移值DHT11温湿度采集模块驱动的移植。

DHT11 是一款湿温度一体化的数字传感器。该传感器包括一个电阻式测湿元件和一个 NTC测温元件,并与一个高性能 8 位单片机相连接。通过单片机等微处理器简单的电路连接就能够实时的采集本地湿度和温度。DHT11 与单片机之间能采用简单的单总线进行通信,仅仅需要一个 I/O 口。

我们移植一个STM32工程中的DHT11程序到本开发板。在Boards\Board_APM32E103_EVAL\inc下复制bsp_dht11.h,在Boards\Board_APM32E103_EVAL\src下复制bsp_dht11.c,为保持一致性已更名。然后在Examples目录下复制一个ADC目录副本,更名为DHT11,相应更改工程文件名并打开main.c文件编辑调用和串口输出程序。
添加包含文件:
#include "bsp_dht11.h"
复制代码添加变量定义:
u8 rec_data[2];
复制代码在main主程序的while (1)过程内添加如下程序:
if(DHT11_Init()==0) { if(DHT11_ReadData(rec_data)==0) { printf("温度:%d℃\r\n",rec_data[1]); printf("湿度:%d\r\n",rec_data[0]); }else{ printf("DHT11 read error!\r\n"); } }else{ printf("DHT11 Init error!\r\n"); }
复制代码
多是显示未定义标签类,原来定义的函数名不一样,逐个对应修改后错误逐一消除,后来编译通过了。下面展示修改内容,有错误标示的做了注释,下方输入相应的修改内容,部分多处同样修改,不重复展示。
bsp_dht11.h文件内修改:
// #include "sys.h"// #include "delay.h" #include "Board.h" #include "bsp_delay.h" #include "apm32e10x_gpio.h" #include <stdio.h> // #define DHT11PORT GPIOA //定义IO接口 // #define DHT11_IO GPIO_Pin_15 //定义IO接口 #define DHT11PORT GPIOC //定义IO接口 #define DHT11_IO GPIO_PIN_7 //修改IO口为PC7
复制代码1 . 配置端口为输出、输出两个子函数
void DHT11_IO_OUT (void){ //端口变为输出//GPIO_InitTypeDef GPIO_InitStructure; // GPIO_InitStructure.GPIO_Pin = DHT11_IO; //选择端口号(0~15或all) // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //选择IO接口工作方式 // GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz) GPIO_Config_T gpioConfig; gpioConfig.pin = DHT11_IO; gpioConfig.mode = GPIO_MODE_OUT_PP; //!< Alternate function output Push-pull GPIO_Config(GPIOC, &gpioConfig); //GPIO_Init(DHT11PORT, &GPIO_InitStructure); GPIO_Config(DHT11PORT, &gpioConfig); } void DHT11_IO_IN (void){ //端口变为输入 // GPIO_InitTypeDef GPIO_InitStructure; // GPIO_InitStructure.GPIO_Pin = DHT11_IO; //选择端口号(0~15或all) // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //选择IO接口工作方式 GPIO_Config_T gpioConfig; gpioConfig.pin = DHT11_IO; gpioConfig.mode = GPIO_MODE_IN_PU; //!< Input with pull-up GPIO_Config(GPIOC, &gpioConfig); //GPIO_Init(DHT11PORT, &GPIO_InitStructure); GPIO_Config(DHT11PORT, &gpioConfig); }
复制代码void DHT11_RST (void){ //DHT11端口复位,发出起始信号(IO发送) DHT11_IO_OUT(); //GPIO_ResetBits(DHT11PORT,DHT11_IO); // GPIO_ResetBit(DHT11PORT,DHT11_IO); //delay_ms(20); //拉低至少18ms APM_EVAL_DelayMs(20); //GPIO_SetBits(DHT11PORT,DHT11_IO); // GPIO_ResetBit(DHT11PORT,DHT11_IO); //delay_us(30); //主机拉高20~40us APM_EVAL_DelayUs(30); }
复制代码u8 Dht11_Check(void){ //等待DHT11回应,返回1:未检测到DHT11,返回0:成功(IO接收) u8 retry=0; DHT11_IO_IN();//IO到输入状态 // while (GPIO_ReadInputDataBit(DHT11PORT,DHT11_IO)&&retry<100){//DHT11会拉低40~80us while (GPIO_ReadInputBit(DHT11PORT,DHT11_IO)&&retry<100){ retry++; //delay_us(1); APM_EVAL_DelayUs(1); } if(retry>=100)return 1; else retry=0; //while (!GPIO_ReadInputDataBit(DHT11PORT,DHT11_IO)&&retry<100){//DHT11拉低后会再次拉高40~80us while (!GPIO_ReadInputBit(DHT11PORT,DHT11_IO)&&retry<100){ retry++; //delay_us(1); APM_EVAL_DelayUs(1); } if(retry>=100)return 1; return 0; }
复制代码u8 Dht11_ReadBit(void){ //从DHT11读取一个位 返回值:1/0 u8 retry=0; //while(GPIO_ReadInputDataBit(DHT11PORT,DHT11_IO)&&retry<100){//等待变为低电平 while(GPIO_ReadInputBit(DHT11PORT,DHT11_IO)&&retry<100){ retry++; //delay_us(1); APM_EVAL_DelayUs(1); } retry=0; //while(!GPIO_ReadInputDataBit(DHT11PORT,DHT11_IO)&&retry<100){//等待变高电平 while(!GPIO_ReadInputBit(DHT11PORT,DHT11_IO)&&retry<100){ retry++; //delay_us(1); APM_EVAL_DelayUs(1); } //delay_us(40);//等待40us //用于判断高低电平,即数据1或0 APM_EVAL_DelayUs(40); //if(GPIO_ReadInputDataBit(DHT11PORT,DHT11_IO))return 1; else return 0; if(GPIO_ReadInputBit(DHT11PORT,DHT11_IO))return 1; else return 0; }
复制代码u8 DHT11_Init (void){ //RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); //APB2外设时钟使能 //GPIO_Reset(GPIOA ); /* Enable the BUTTON Clock */ // RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA | RCM_APB2_PERIPH_GPIOB | RCM_APB2_PERIPH_GPIOC | RCM_APB2_PERIPH_GPIOD | RCM_APB2_PERIPH_GPIOE ); RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOC|RCM_APB2_PERIPH_AFIO); DHT11_RST(); return Dht11_Check(); }
复制代码经过以上修改,程序编译通过。

串口波特率115200,运行的结果:

似乎不能获得DHT11应答,还在进一步调试捉虫中。
最后补充一个DHT11单线数据传输时序定义。单线就是分时用来输入输出,每次需要读取数据时,MCU发出一个触发信号,DHT则在接受触发信号后连续发送40位的数据信息。

上图中粗黑部分就是MCU发出的触发信号,淡色部分就是DHT回送的数据信号。

DHT11 上电后要等待 1S 以越过不稳定状态在此期间不能发送任何指令。要读数时MCU输出低电平,且低电平保持时间不能小于 18ms(最大不得超过 30ms),随后进入上拉输入等待接收DHT回送的数据信号。

DHT则在接受触发信号后连续发送40位的数据信息,因为单线,数据0和1也是通过高低电平持续的时长不同来区分的。位数据“0”的格式为: 54 微秒的低电平和 23-27 微秒的高电平,位数据“1”的格式为: 54 微秒的低电平加 68-74微秒的高电平。
那么,前面移植的程序没有触发DHT11回送信号,问题是不是出在电平持续时间这里呢?