1.外设驱动配置上
时钟&SWD
原理图分析
时钟
如下图所示,STM32F407外部高速晶振为25MHz,分别连接到PH0和PH1引脚!
SWD
如下图所示,STM32F407仿真接口SWD分别连接到PA13和PA14引脚!
外设配置
时钟
使能时钟源
- 选择RCC外设
- 选择高速时钟为外部时钟源
- PH0和PH1引脚自动高亮
配置时钟树
- 锁相环时钟源为25MHz外部高速时钟
- 高速时钟分频系数配置为25,输出为1MHz
- 倍频系数配置为336
- 分频系数配置为2,输出为168MHz
- 系统时钟源选择PLL
- APB1配置为4分频,为42MHz
- APB2配置为2分频,为84MHz
SWD
- 选择SYS外设
- 配置debug接口为串行接口(SWD)
- 引脚自动高亮
串口
原理图分析
Zigbee通信接口
如下图所示:
- J28为Zigbee模块底座,其中Z_W_R和Z_W_T分别Zigbee串行通信接口
- Z_W_R和Z_W_T网络连接到J13,通过J13选择连接STM32还是USB转串口,我们选择连接到STM32上的USART1(PA9和PA10)
串口调试接口
如下图所示:
- 板载两个串行通信接口,串口1连接到USART1,串口2连接到USART3,我们选择USART3
- 由于USART3可以用于串口和485通信,我们选择485必须要把CON4和CON5拨到串口通信
外设配置
USART1
- 配置PA9和PA10为USART1模式
- 打开USART1,配置为异步通信模式
USART2
- 配置PB10和PB11为USART3模式
- 打开USART3外设,并配置为异步通信模式
FSMC配置
SRAM
原理图分析
通过下图所示:
- 采用IS61LV51216 SRM 为1MB,其实为了节约成本焊接的为IS61LV25616 为512KB
- 占用地址总线为18bit,数据总线为16bit
- 内存访问起始地址为0x6800 0000
外设配置
- 打开FSMC外设
- 配置FSMC
- 选择存储块为NE3
- 内存类型为SRAM
- 寻址长度为18bit
- 数据宽度为16bit
- 配置FSMC时序
- 地址建立时间为1分频
- 数据建立时间为3分频
- 字节访问使能
LCD
原理图分析
- 如下图所示,LCD采用8080接口,CS片选,D/C命令/数据切换,RD读操作,WR写操作,D[23:0]数据总线

- 如下图所示,数据总线D[0:15]连接FSMC总线接口处,RS起始就D/C接口,连接到FSMC地址总线A0,CS片选总线连接到FSMC_NE4上,WR写操作连接FSMC_NWE总线上,RD读操作总线连接到FSMC_NOE上,背光控制连接到PC7上

- 写命令操作0x6C00 0000
- 写数据操作0x6C00 0002
外设配置
- 打开FSMC外设
- 配置FSMC参数
- 内存块为NE4
- 内存类型为LCD
- LCD数据/命令切换映射到A0
- 数据宽度为16bit

- 分析LCD驱动芯片时序图,计算得出地址和数据总线建立时间
- 配置PC7为输出模式
- 上电默认输出高电平
2.外设驱动配置下
SPI配置
原理图分析
- 如下图所示,SPI接口,CS连接到PG15,MISO连接到PI2,MOSI连接到PI3,SCK连接到PI1
- 触摸中断连接到PG7
数据手册分析
- 通过计算TCH+TCL得出SPI通信速率
- 通过时序图分析,SPI不工作时为低电平
- 时钟边沿为奇数边沿
- 通信速率最小为400ns,大概2Mbit/S左右
外设配置
- 配置SPI时钟和数据引脚
- 配置SPI为全双工主机模式
- 配置SPI参数
- 通信速率为系统时钟32分频
- 时钟极性为低电平
- 相位为奇数边沿
- 配置SPI片选引脚
- 配置PG15为输出模式
- 配置PG15上电默认输出高,SPI低电平有效
GPIO配置
原理图分析
- 如下图所示
- PF7 PF8 PF9 P10 控制板载的D6 D7 D8 D9
- PF6控制板载蜂鸣器
外设配置
- 配置PF6-PF10为输出模式
- PF6默认输出低
- PF7-PF10默认输出高
SDIO配置
原理图分析
根据原理图分析,我们采用SD总线,4bit
外设配置
- 打开SDIO外设
- 配置SD总线为4bit位宽
- 配置DMA接收和发送
- 使能sdio全局中断
ETHERNET配置
原理图分析
- 如下图所示,以太网PHY采用DP83848芯片,通信模式采用RMII接口
外设配置
3.FreeRTOS配置及使用
##配置内核定时器
由于我们采用STM32HAL库进行开发,HAL库内部使用systick定时器用于系统延时功能,而FreeRTOS也需要一个定时器用于操作系统内核调度使用, 顾需要修改HAL定时器时钟源
- 打开SYS选项
- 配置时钟源为TIM1
配置FreeRTOS内核功能
- 多数功能在后续程序设计中,需要根据具体功能,进行配置
- 前期只需要配置动态内存空间和创建开始任务就可以
配置动态内存分配空间
-
采用FreeRTOS动态内存分配,开发效率高!顾我们程序内存使用,多数使用动态内存分配方式,分配动态内存总空间为23k=23552byte
2. 使能FreeRTOS功能
3. 分配内存空间为40960
创建开始任务
STM32cubemx默认已经创建了一个任务,只需要简单修改一下即可。
- 打开任务和消息队列选型卡
- 双击系统默认任务
- 配置任务名称和任务函数名称(保证其不一样)
上电打印启动信息
配置工程信息
- 配置工程名称为SmartClassRomm
- 配置生成keil5工程
3. 单独为每个外设生成.c和.h文件
####生成keil工程
printf重定向
- 在main.c文件内添加fputc函数,采用USART3作为调试接口
int fputc(int ch, FILE *f)
{
while((USART3->SR & 0X40)==0);
USART3->DR = (uint8_t) ch;
return ch;
}
- 在freertos.c文件内,Start_Task打印启动信息
/* USER CODE END Header_Start_Task */
void Start_Task(void const * argument)
{
/* USER CODE BEGIN Start_Task */
printf("start is runing!\r\n");
/* Infinite loop */
for(;;)
{
osDelay(1);
}
/* USER CODE END Start_Task */
}
4.LwIP配置及使用
LwIP配置
LwIP使用,只需要把以太网链路检测功能开启即可,其他后续再添加
- 使能LwIP组件
- 分配10k内存空间
- 使能链路检测回调功能
获取DHCP分配的IP地址
- 在lwip.c添加代码
void LwIPTask(void){
struct dhcp *dhcp;
//等待DHCP获取IP成功
while(!(dhcp_supplied_address(&gnetif))){
vTaskDelay(200);
}
//打印获取到的IP地址
printf("GET IP address\n");
dhcp = netif_dhcp_data(&gnetif);
printf("IP address: %s\n", ip4addr_ntoa(&dhcp->offered_ip_addr));
printf("Subnet mask: %s\n", ip4addr_ntoa(&dhcp->offered_sn_mask));
printf("Default gateway: %s\n", ip4addr_ntoa(&dhcp->offered_gw_addr));
for(;;){
vTaskDelay(100);
}
}
- 在lwip.h添加代码
/* USER CODE BEGIN 0 */
void LwIPTask(void);
/* USER CODE END 0 */
- 在freertos.c修改代码
#include "lwip.h"
void LwIP_Task(void const * argument)
{
/* USER CODE BEGIN LwIP_Task */
/* Infinite loop */
LwIPTask();
/* USER CODE END LwIP_Task */
}
链路检测处理
- 在lwip.c添加代码
/**
* @brief This function notify user about link status changement.
* @param netif: the network interface
* @retval None
*/
__weak void ethernetif_notify_conn_changed(struct netif *netif)
{
/* NOTE : This is function could be implemented in user file
when the callback is needed,
*/
if(netif_is_link_up(netif)){
printf("netif link is up\r\n");
if(!netif_is_up(netif)){
netif_set_up(netif);
printf("netif is up\r\n");
}
}else{
printf("netif link is down\r\n");
}
}
/* USER CODE END 8 */
功能测试
检查DHCP是否获取到IP
- 设备上电
- 等待打印获取到的IP地址,如下图所示
3. ping设备信息
以太网断线检测
两种情况,一种是设备通电时断开网线,一种是网线没插,设备通电
- 第一种,只需要在获取到IP地址后, 断开网线之后再插入,通过ping命令进行检测
- 第二种,断开网线设备通电,之后再插入网线
5.FatFS配置及使用
FatFS配置
- 打开FatFS
- 使能磁盘为SD卡
- 配置中文编码
- 配置命名空间为HEAP
- 增大C库堆空间
FatFS代码分析
FATFS模块
- 文件访问
f_open - 打开/创建文件
f_close - 关闭一个打开的文件
f_read - 读取数据
f_write - 写入数据
f_lseek - 移动读/写指针,扩展大小
f_truncate - 截断大小
f_sync - 刷新缓存的数据
f_forward - 将数据转发到流
f_expand - 为文件分配一个连续的块
f_gets - 读取一个字符串
f_putc - 写一个字符
f_puts - 写一个字符串
f_printf - 写一个格式化的字符串
f_tell - 获取当前的读/写指针
f_eof - 测试文件结尾
f_size - 获取大小
f_error - 测试错误 - 目录访问
f_opendir - 打开目录
f_closedir - 关闭一个打开的目录
f_readdir - 读取目录项
f_findfirst - 打开一个目录并读取匹配的第一个项目
f_findnext - 读取下一个匹配的项目 - 文件/目录管理
f_stat - 检查文件或子目录的存在
f_unlink - 删除文件或子目录
f_rename - 重命名或移动文件或子目录
f_chmod - 更改文件或子目录的属性
f_utime - 更改文件或子目录的时间戳
f_mkdir - 创建子目录
f_chdir - 更改当前目录
f_chdrive - 更改当前驱动器
f_getcwd - 检索当前目录和驱动器 - 卷管理
f_mount - 注册/取消注册卷的工作区
f_mkfs - 在逻辑驱动器上创建FAT卷
f_fdisk - 在物理驱动器上创建逻辑驱动器
f_getfree - 获取卷上的总大小和可用大小
f_getlabel - 获取卷标
f_setlabel - 设置卷标
BSP底层磁盘IO驱动
disk_status - 获取设备状态
disk_initialize - 初始化设备
disk_read - 读取扇区
disk_write - 写扇区
disk_ioctl - 控制设备相关的功能
get_fattime - 获取当前时间
通用底层驱动接口
typedef struct
{
DSTATUS (*disk_initialize) (BYTE);// 初始化设备
DSTATUS (*disk_status) (BYTE); // 获取设备状态
DRESULT (*disk_read) (BYTE, BYTE*, DWORD, UINT); //读取扇区
#if _USE_WRITE == 1
DRESULT (*disk_write) (BYTE, const BYTE*, DWORD, UINT); //写扇区
#endif
#if _USE_IOCTL == 1
DRESULT (*disk_ioctl) (BYTE, BYTE, void*); //控制设备相关的功能
}Diskio_drvTypeDef;
typedef struct
{
uint8_t is_initialized[_VOLUMES]; //设备装在状态
const Diskio_drvTypeDef *drv[_VOLUMES]; //设备驱动结构体
uint8_t lun[_VOLUMES];
volatile uint8_t nbr; //目前驱动的数量
}Disk_drvTypeDef;
BSP驱动层分析
uint8_t BSP_SD_Init(void);
uint8_t BSP_SD_ITConfig(void);
void BSP_SD_DetectIT(void);
void BSP_SD_DetectCallback(void);
uint8_t BSP_SD_ReadBlocks(uint32_t *pData, uint32_t ReadAddr, uint32_t NumOfBlocks, uint32_t Timeout);
uint8_t BSP_SD_WriteBlocks(uint32_t *pData, uint32_t WriteAddr, uint32_t NumOfBlocks, uint32_t Timeout);
uint8_t BSP_SD_ReadBlocks_DMA(uint32_t *pData, uint32_t ReadAddr, uint32_t NumOfBlocks);
uint8_t BSP_SD_WriteBlocks_DMA(uint32_t *pData, uint32_t WriteAddr, uint32_t NumOfBlocks);
uint8_t BSP_SD_Erase(uint32_t StartAddr, uint32_t EndAddr);
void BSP_SD_IRQHandler(void);
void BSP_SD_DMA_Tx_IRQHandler(void);
void BSP_SD_DMA_Rx_IRQHandler(void);
uint8_t BSP_SD_GetCardState(void);
void BSP_SD_GetCardInfo(HAL_SD_CardInfoTypeDef *CardInfo);
uint8_t BSP_SD_IsDetected(void);
void BSP_SD_AbortCallback(void);
void BSP_SD_WriteCpltCallback(void);
void BSP_SD_ReadCpltCallback(void);
#Fatfs使用
uint8_t u8chr[] = "hello";
uint32_t u32Wbytes;
/* USER CODE END Variables */
void MX_FATFS_Init(void)
{
/*## FatFS: Link the SD driver ###########################*/
retSD = FATFS_LinkDriver(&SD_Driver, SDPath);
/* USER CODE BEGIN Init */
if(f_mount(&SDFatFS,SDPath,1) == FR_OK)
{
if(f_open(&SDFile,(const char*)"fatfs.txt",FA_CREATE_ALWAYS|FA_WRITE) == FR_OK)
{
if(f_write(&SDFile,u8chr,sizeof(u8chr),&u32Wbytes) == FR_OK)
{
f_close(&SDFile);
}
}
}
FATFS_UnLinkDriver(SDPath);
/* additional user code for init */
/* USER CODE END Init */
}
6.STemWin文件移植
获取STemWin源码文件
STemWin默认在STM32CUBEMX文档下
例如C:\Users\Think\STM32Cube\Repository\STM32Cube_FW_F4_V1.23.0\Middlewares\ST\STemWin
STemWin移植到项目工程
-
复制STemWin源码到项目工程中
工程目录:SmartClassRoom\Middlewares\Third_Party\STemWin -
在keil工程中添加相关文件
- 新建工作组:Middlewares/STemWin
- 添加需要编译的C和库文件
- 新建工作组:Middlewares/STemWin
文件名称 | 文件描述 |
---|---|
GUI_X_OS.c | OS支持文件,不需要修改 |
GUIConf.c | GUI配置文件,主要用于GUI内存块初始化 |
GUIDRV_Template.c | GUI驱动模块,主要针对LCD操作接口 |
LCDConf_FlexColor_Template.c | GUI显示配置文件,主要用于LCD参数配置,初始化 |
GUI_X_Touch_Analog.c | 需要自己单独定义,用于触摸笔驱动 |
STemWin_CM4_OS_wc16_ot.a | 基于Cortex-M4驱动库,STemWin源码不开放 |
- 修改库文件格式(keil默认不识别.a文件格式,需要我们手动配置)
移植lcd和touch驱动文件
- 添加lcd.c和Touch.c到Src目录下
- 添加lcd.h和Touch.h到Inc目录下
修改GUIConf.c
#include "GUI.h"
/*********************************************************************
*
* Defines
*
**********************************************************************
*/
//
// Define the available number of bytes available for the GUI
//
#define GUI_NUMBYTES (512*1024) //定义外部存储器大小
#define GUI_BLOCKSUZE (0X80) //定义最小内存库操作大小
#define SRAM_BANK_ADDR ((U32)0x68000000) //定义外部存储器首地址
/*********************************************************************
*
* Public code
*
**********************************************************************
*/
/*********************************************************************
*
* GUI_X_Config
*
* Purpose:
* Called during the initialization process in order to set up the
* available memory for the GUI.
*/
void GUI_X_Config(void) {
//
// 32 bit aligned memory area
//
volatile U32* aMemory = (volatile U32*)(SRAM_BANK_ADDR);
//
// Assign memory to emWin
//分配GUI存储器首地址及最小操作内存块大小
GUI_ALLOC_AssignMemory((void *)aMemory, GUI_NUMBYTES);
GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSUZE);
//
// Set default font
//
GUI_SetDefaultFont(GUI_FONT_6X8);
}
修改GUIDRV_Template.c
只需要完成画点和读取点操作即可
/*********************************************************************
*
* _SetPixelIndex
*
* Purpose:
* Sets the index of the given pixel. The upper layers
* calling this routine make sure that the coordinates are in range, so
* that no check on the parameters needs to be performed.
*/
static void _SetPixelIndex(GUI_DEVICE * pDevice, int x, int y, int PixelIndex) {
//
// Convert logical into physical coordinates (Dep. on LCDConf.h)
//
#if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
int xPhys, yPhys;
xPhys = LOG2PHYS_X(x, y);
yPhys = LOG2PHYS_Y(x, y);
#else
#define xPhys x
#define yPhys y
#endif
GUI_USE_PARA(pDevice);
GUI_USE_PARA(x);
GUI_USE_PARA(y);
GUI_USE_PARA(PixelIndex);
{
//
// Write into hardware ... Adapt to your system
//添加lcd画点接口
LCD_DrawPoint(x,y,PixelIndex);
// TBD by customer...
//
}
#if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
#undef xPhys
#undef yPhys
#endif
}
/*********************************************************************
*
* _GetPixelIndex
*
* Purpose:
* Returns the index of the given pixel. The upper layers
* calling this routine make sure that the coordinates are in range, so
* that no check on the parameters needs to be performed.
*/
static unsigned int _GetPixelIndex(GUI_DEVICE * pDevice, int x, int y) {
unsigned int PixelIndex;
//
// Convert logical into physical coordinates (Dep. on LCDConf.h)
//
#if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
int xPhys, yPhys;
xPhys = LOG2PHYS_X(x, y);
yPhys = LOG2PHYS_Y(x, y);
#else
#define xPhys x
#define yPhys y
#endif
GUI_USE_PARA(pDevice);
GUI_USE_PARA(x);
GUI_USE_PARA(y);
{
//
// Write into hardware ... Adapt to your system
//添加lcd读取点接口
PixelIndex = lcd_read_gram(x,y);
// TBD by customer...
//
PixelIndex = 0;
}
#if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
#undef xPhys
#undef yPhys
#endif
return PixelIndex;
}
修改LCDConf_FlexColor_Template.c
- 定义显示尺寸 480*272
- 定义触摸笔X,Y AD测量值(需要自己测量获得)
- 添加触摸笔校准函数
- 添加lcd初始化函数
#include "GUI.h"
#include "GUIDRV_FlexColor.h"
#include "lcd.h"
/*********************************************************************
*
* Layer configuration (to be modified)
*
**********************************************************************
*/
//
// Physical display size
//
#define XSIZE_PHYS 480 // 屏幕X坐标长度
#define YSIZE_PHYS 272 // 屏幕Y坐标长度
#define GUI_TOUCH_AD_Y_TOP 170 // 屏幕X0点坐标AD值
#define GUI_TOUCH_AD_Y_BOTTOM 1900 // 屏幕X480点坐标AD值
#define GUI_TOUCH_AD_X_LEFT 100 // 屏幕Y0点坐标AD值
#define GUI_TOUCH_AD_X_RIGHT 1930 // 屏幕Y272点坐标AD值
/*********************************************************************
*
* Configuration checking
*
**********************************************************************
*/
#ifndef VXSIZE_PHYS
#define VXSIZE_PHYS XSIZE_PHYS
#endif
#ifndef VYSIZE_PHYS
#define VYSIZE_PHYS YSIZE_PHYS
#endif
#ifndef XSIZE_PHYS
#error Physical X size of display is not defined!
#endif
#ifndef YSIZE_PHYS
#error Physical Y size of display is not defined!
#endif
#ifndef GUICC_565
#error Color conversion not defined!
#endif
#ifndef GUIDRV_FLEXCOLOR
#error No display driver defined!
#endif
/*********************************************************************
*
* Public functions
*
**********************************************************************
*/
/*********************************************************************
*
* LCD_X_Config
*
* Function description:
* Called during the initialization process in order to set up the
* display driver configuration.
*
*/
void LCD_X_Config(void) {
//
// 配置GUI LCD驱动以及颜色显示方式
//
GUI_DEVICE_CreateAndLink(&GUIDRV_Template_API, GUICC_M565, 0, 0);
//
// 显示尺寸配置
//
LCD_SetSizeEx (0, XSIZE_PHYS , YSIZE_PHYS);
LCD_SetVSizeEx(0, VXSIZE_PHYS, VYSIZE_PHYS);
//触摸笔校准
GUI_TOUCH_Calibrate(GUI_COORD_X, 0, 480, GUI_TOUCH_AD_X_LEFT , GUI_TOUCH_AD_X_RIGHT);
GUI_TOUCH_Calibrate(GUI_COORD_Y, 0, 272, GUI_TOUCH_AD_Y_TOP, GUI_TOUCH_AD_Y_BOTTOM);
//
// Orientation
//
//
// Set controller and operation mode
//
}
/*********************************************************************
*
* LCD_X_DisplayDriver
*
* Function description:
* This function is called by the display driver for several purposes.
* To support the according task the routine needs to be adapted to
* the display controller. Please note that the commands marked with
* 'optional' are not cogently required and should only be adapted if
* the display controller supports these features.
*
* Parameter:
* LayerIndex - Index of layer to be configured
* Cmd - Please refer to the details in the switch statement below
* pData - Pointer to a LCD_X_DATA structure
*
* Return Value:
* < -1 - Error
* -1 - Command not handled
* 0 - Ok
*/
int LCD_X_DisplayDriver(unsigned LayerIndex, unsigned Cmd, void * pData) {
int r;
(void) LayerIndex;
(void) pData;
switch (Cmd) {
case LCD_X_INITCONTROLLER: {
//
// Called during the initialization process in order to set up the
// display controller and put it into operation. If the display
// controller is not initialized by any external routine this needs
// to be adapted by the customer...
//
// ...
//添加lcd初始化
lcd_init();
return 0;
}
default:
r = -1;
}
return r;
}
添加GUI_X_Touch_Analog.c
#include "GUI.h"
#include "Touch.h"
void GUI_TOUCH_X_ActivateX(void)
{
}
void GUI_TOUCH_X_ActivateY(void)
{
}
//获取X坐标AD值
int GUI_TOUCH_X_MeasureX(void)
{
return XPT_Read_XY(CMD_RDX);
}
//获取Y坐标AD值
int GUI_TOUCH_X_MeasureY(void)
{
return XPT_Read_XY(CMD_RDY);
}
创建touch任务
- 创建touch任务
- 配置任务优先级为正常
- 配置栈大小为256
获取触摸笔X,Y坐标 四点AD值
/* USER CODE BEGIN Header_Touch_Task */
/**
* @brief Function implementing the TouchTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Touch_Task */
void Touch_Task(void const * argument)
{
/* USER CODE BEGIN Touch_Task */
int x,y;
GUI_PID_STATE State;
/* Infinite loop */
for(;;)
{
// 获取TOUCH AD值
x = GUI_TOUCH_X_MeasureX();
y = GUI_TOUCH_X_MeasureY();
//显示X坐标信息
GUI_DispStringAt("X:",0,0);
GUI_DispDecAt(x,32,0,4);
//显示Y坐标信息
GUI_DispStringAt("Y:",0,24);
GUI_DispDecAt(y,32,24,4);
osDelay(10);
}
/* USER CODE END Touch_Task */
}
测试程序编写
/* USER CODE BEGIN Header_Touch_Task */
/**
* @brief Function implementing the TouchTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Touch_Task */
void Touch_Task(void const * argument)
{
/* USER CODE BEGIN Touch_Task */
int x,y;
GUI_PID_STATE State;
/* Infinite loop */
for(;;)
{
//执行触摸笔检测
GUI_TOUCH_Exec();
//获取触摸笔状态值
GUI_TOUCH_GetState(&State);
//是否按下
if(State.Pressed){
//打印触摸笔坐标信息
GUI_DispStringAt("X:",0,0);
GUI_DispDecAt(State.x,32,0,4);
GUI_DispStringAt("Y:",0,24);
GUI_DispDecAt(State.y,32,24,4);
}
osDelay(10);
}
/* USER CODE END Touch_Task */
}
测试结果
当触摸笔按下时,显示X(0-480)和Y(0-272)坐标信息