IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STC - STC15库函数实验 - 用表驱动来实现串口应答 -> 正文阅读

[嵌入式]STC - STC15库函数实验 - 用表驱动来实现串口应答

STC - 库函数实验 - 用表驱动来实现串口应答

概述

准备用STC15库函数写个板子的测试程序.
今天将串口命令的收发的架子整完了, 有点收获.

实验环境

MDK5 for C51(keil C51)

知识点

  • 基于表驱动的串口应答, 符合OCP(封闭开放原则).
    当增加新的串口命令后, 流程不用改, 只需要在表中增加新的串口命令字符串和对应的实现函数名称.

    以前还见过其他工程用一种处理接口注册的机制来实现流程处理的方法, 也很好维护.
    不过, 这种实现是要占用内存的, 对于下位机来说, 能节省1byte的内存都是好的.
    用表驱动的方法, 最大的好处是只占用代码空间, 不会占用内存空间.
    对于大部分流程处理的操作, 在编译期都是可以确定的.
    对于在编译期就能知道处理流程的操作, 用表驱动来处理是比较合理的.

  • C51的函数指针写法和VC/GCC环境不太一样. 如果写错了, 也能编译过, 但是调用函数指针时会阻塞住(卡死, 不往下走)

  • 如果命令中出现中文字符, 有些中文字符不显示或显示乱码, 这时, 要在该中文字符后加上 “\xFD”

  • 为了不编译用不到的函数实现
    在Options(魔术棒) => Device页 => 勾选 “Use Extended Linker(LX51) instead of B51”
    在Options(魔术棒) => LX51 Misc页 => 在Misc controls 编辑框中填入 REMOVEUNUSED

  • 在代码区定义结构体或大数组, 需要使用code关键字进行修饰, 否则编译不过.

验证过的串口应答实现 - 基于表驱动

// @file main.c

// --------------------------------------------------------------------------------
// 头文件包含
// --------------------------------------------------------------------------------

// 不能包含 STC15F2K60S2.h, 即使是指定了一个不存在的 STC15F2K60S2.h
// 如果在指定目录下找不到 STC15F2K60S2.h, 也会去MDK5-C51目录下找
// 会和config.h中的STC15Fxxxx.H 中的寄存器定义冲突, 导致编译报错.
// #include "stc/STC15F2K60S2.h" // 包含工程目录下的.h

// 如果使用STC库方式编程, 包含config.h, 不用再包含 STC*.h(e.g. STC15F2K60S2.h)
#include "stc/config.h"

#include "stc/usart.h"
#include "stc/delay.h"

#include <stdio.h> // for sprintf
#include <string.h> // for memset
#include <stdlib.h> // for atoi

#include <assert.h> // for assert

// 为了防止不调用所有库中的函数引起的编译警告, 只应在左面板的stc节点下包含必须的.c

// --------------------------------------------------------------------------------
// 宏定义
// --------------------------------------------------------------------------------
// STC15Fxxxx.H 中有 TRUE/FALSE 的宏定义
#define LINE_60 "--------------------------------------------------------------------------------"
#define COM1_BAUDRATE_BPS 115200ul
#define COM2_BAUDRATE_BPS 115200ul
#define BOOL u8

// warning C275: expression with possibly no effect
#define UNUSED(x) (x != x)

// --------------------------------------------------------------------------------
// 全局变量定义
// --------------------------------------------------------------------------------
// 在这里定义一个128 bytes的buffer, 编译时, keil C51 经常报错, 将变量移动到非main.c中定义, 然后再非main.c的.h中用extern 声明变量.
// 好像不是在这定义全局变量的事情, 有时编译报错, 有时单步调试刚进入main时报错, 可能是keilC51本身的问题.

typedef struct _tag_cmd_param {
	int cmd_index;
	const char* pc_cmd_name;
	const char* pc_cmd_desc;
}TAG_CMD_PARAM;

// typedef u8 (*PFN_CMD_PROC)(TAG_CMD_PARAM* p_param); // 这样也能编译过, 但是调用函数指针时Keil C51阻塞了
typedef u8 (PFN_CMD_PROC)(TAG_CMD_PARAM* p_param); // 函数指针的声明, 编译通过, 正常使用

u8 fn_proc_cmd_default(TAG_CMD_PARAM* p_param);
u8 fn_proc_cmd_help(TAG_CMD_PARAM* p_param);

typedef struct _tag_cmd_item {
	int cmd_index;
	const char* pc_cmd_name;
	const char* pc_cmd_desc;
	PFN_CMD_PROC* pfn_cmd_proc;
} TAG_CMD_ITEM;

// 数组定义前面必须加code, 说明这段定义在代码区, 不使用ram.
code const TAG_CMD_ITEM g_cmd_array[] = {
	{0, "help", "命令使用说明", fn_proc_cmd_help}, // 这里赋值的函数指针 要使用 fn_proc_cmd_default, 不能是 &fn_proc_cmd_default
	
	// stc15_exp_box4_mb_main.pdf // nothing
	
	//	PrintString1("02. cmd_test_usb_micro_b\r\n"); // stc15_exp_box4_mb_usb.pdf
	{1, "test_usb_micro_b", "测试左下角usb_micro-b接口", fn_proc_cmd_default},
	{2, "test_usb_a", "测试左下角usb_A接口", fn_proc_cmd_default},
	
	//	PrintString1("02. cmd_test_2uart_self_send_recv\r\n"); // stc15_exp_box4_mb_uart.pdf
	{3, "test_2uart_self_send_recv", "测试2个串口自收发", fn_proc_cmd_default},
	
	// PrintString1("01. cmd_test_Led\r\n"); // stc15_exp_box4_mb_mcu.pdf
	{3, "test_ind_leds", "测试7个LED指示灯", fn_proc_cmd_default},
	
	//	PrintString1("02. cmd_test_7seg\r\n"); // stc15_exp_box4_mb_display.pdf
	// 部分字符串通过串口打印后乱码的问题 - 官方说法 - https://developer.arm.com/documentation/ka004187/latest
	// 在乱码的汉字后面加上 \xFD - ref https://blog.csdn.net/weixin_42378319/article/details/106126461
	{4, "test_7seg", "测试8个7段数\xFD码管", fn_proc_cmd_default},
	
	//	PrintString1("12. cmd_test_12864\r\n"); // stc15_exp_box4_mb_display.pdf
	{5, "cmd_test_12864", "测试12864", fn_proc_cmd_default},
	
	//	PrintString1("13. cmd_test_12864_backlight\r\n"); // stc15_exp_box4_mb_display.pdf
	{6, "test_12864_backlight", "测试12864的背光", fn_proc_cmd_default},
	
	//	PrintString1("14. cmd_test_1602\r\n"); // stc15_exp_box4_mb_display.pdf
	{7, "test_1602", "测试1602", fn_proc_cmd_default},
	
	//	PrintString1("05. cmd_test_ex_int_2key\r\n"); // stc15_exp_box4_mb_kb.pdf
	{8, "test_exint_2key", "测试外中断的2个按键", fn_proc_cmd_default},
	
	//	PrintString1("04. cmd_test_key_normal\r\n"); // stc15_exp_box4_mb_kb.pdf
	{9, "test_matrix_keys", "测试16个矩阵按键", fn_proc_cmd_default},
	
	//	PrintString1("03. cmd_test_key_adc\r\n"); // stc15_exp_box4_mb_kb.pdf
	{10, "test_adc_keys", "测试16个ADC按键", fn_proc_cmd_default},
	
	//	PrintString1("01. cmd_6pwm\r\n"); // stc15_exp_box4_mb_mcu.pdf
	{11, "test_6pwm", "测试6个PWM", fn_proc_cmd_default},

	//	PrintString1("08. cmd_test_e2prom\r\n"); // stc15_exp_box4_mb_mcu.pdf
	{12, "test_e2prom", "测试e2prom的读写", fn_proc_cmd_default},
	
	//	PrintString1("15. cmd_test_adc_ref_2d5v\r\n"); // stc15_exp_box4_mb_power.pdf
	{13, "test_adc_ref_2d5v", "测试2.5V基准电压", fn_proc_cmd_default},
	
	//	PrintString1("15. cmd_test_low_v\r\n"); // stc15_exp_box4_mb_power.pdf
	{14, "test_low_v", "测试低压检测", fn_proc_cmd_default},
	
	//	PrintString1("07. cmd_test_xram\r\n"); // stc15_exp_box4_mb_ram.pdf
	{15, "test_xram", "测试外部32KB的XRAM", fn_proc_cmd_default},
	
	//	PrintString1("10. cmd_test_rtc\r\n"); // stc15_exp_box4_mb_rtc.pdf
	{16, "test_rtc", "测试RTC时钟", fn_proc_cmd_default},
	
	//	PrintString1("11. cmd_test_pca_pwm_as_adc\r\n"); // stc15_exp_box4_mb_sensor.pdf
	{17, "test_pca_pwm_as_adc", "用PWM波调频来测试ADC的值", fn_proc_cmd_default},
	
	//	PrintString1("11. cmd_test_ntc\r\n"); // stc15_exp_box4_mb_sensor.pdf
	{18, "test_ntc", "测试ntc温度", fn_proc_cmd_default},
	
	//	PrintString1("11. cmd_test_ir\r\n"); // stc15_exp_box4_mb_sensor.pdf
	{19, "test_ir", "测试红外发送自接收", fn_proc_cmd_default},
	
	//	PrintString1("09. cmd_test_pm25\r\n"); // stc15_exp_box4_mb_spi.pdf
	{20, "test_pm25", "测试PM25存储", fn_proc_cmd_default},
	
	// 最后一个是标记数组定义结束用的, 不处理
	{-1, NULL, NULL, NULL}
};

// --------------------------------------------------------------------------------
// 函数声明
// --------------------------------------------------------------------------------
void Hardware_init(void);
void UART1_init(void);
void UART2_init(void);
void process_uart_cmd(void);

void my_assert(const char* pc_file_name, int iLine, BOOL bResult, const char* pDesc);
u8 wait_until_user_input_cmd_from_uart1(void);

// --------------------------------------------------------------------------------
// 函数实现
// --------------------------------------------------------------------------------
int main(void)
{
	Hardware_init();
	
	PrintString1("me - STC15实验箱4 - 硬件出厂测试程序\r\n");

	fn_proc_cmd_help(NULL);
	
	do {
		process_uart_cmd();
	} while (1);
	
	// return 0;
}

u8 fn_proc_cmd_help(TAG_CMD_PARAM* p_param)
{
	int len = 0;
	TAG_CMD_ITEM* pcmd_array = g_cmd_array;
	UNUSED(p_param);
	
	memset(&g_tmp_buf[0], 0, sizeof(g_tmp_buf));
	sprintf(g_tmp_buf, "%s\r\n", LINE_60); // sprintf正常用就好, 挺正常的.
	PrintString1(g_tmp_buf);

	PrintString1("串口命令列表\r\n");
	
	memset(g_tmp_buf, 0, sizeof(g_tmp_buf));
	sprintf(g_tmp_buf, "%s\r\n", LINE_60);
	PrintString1(g_tmp_buf);

	do {
		if ((NULL == pcmd_array) 
			|| (NULL == pcmd_array->pc_cmd_name)
			|| (NULL == pcmd_array->pc_cmd_desc)
			|| (NULL == pcmd_array->pfn_cmd_proc))
		{
			break;
		}
		
		memset(g_tmp_buf, 0, sizeof(g_tmp_buf));
		
		// 防止不定长的字符串撑爆缓冲区, 先ASSERT再用. 在ASSERT中下断点, 就可以在单步调试时, 发现编译期的问题
		// 也为了格式化出一致的字符串.
		my_assert(__FILE__, __LINE__, (strlen(pcmd_array->pc_cmd_name) < 26), pcmd_array->pc_cmd_name);
		my_assert(__FILE__, __LINE__, (strlen(pcmd_array->pc_cmd_desc) < 30), pcmd_array->pc_cmd_desc);
		len = sprintf(g_tmp_buf,
			 // 固定内容是22个字符, 80 - 22 = 58 剩下可供格式化的字符为58个.
			// 58 - 2 - 32 = 24
			"cmd[%02d], name[%26s], desc[%30s]\r\n",
			(int)pcmd_array->cmd_index,
			pcmd_array->pc_cmd_name,
			pcmd_array->pc_cmd_desc);
		
		PrintString1(g_tmp_buf);
			
		pcmd_array++;
		
	} while(TRUE);
 
	memset(g_tmp_buf, 0, sizeof(g_tmp_buf));
	sprintf(g_tmp_buf, "%s\r\n", LINE_60);
	PrintString1(g_tmp_buf);
	
	PrintString1("请观察串口1和串口2输出, 如果能看到正\xFD常上报信息, 说明串口1, 串口2, USB通讯是好的\r\n");
	PrintString1("请输入列出的测试命令, 进行后续测试\r\n");
	
	return TRUE;
}

u8 wait_until_user_input_cmd_from_uart1(void)
{
	u8 i = 0;
	
	while (1)
	{
		delay_ms(1);
		if(COM1.RX_TimeOut > 0)		//超时计数
		{
			if (0 == --COM1.RX_TimeOut)
			{
				if(COM1.RX_Cnt > 0)
				{
					if ((COM1.RX_Cnt >= 2) && ('\r' == RX1_Buffer[COM1.RX_Cnt - 2]) && ('\n' == RX1_Buffer[COM1.RX_Cnt - 1]))
					{
						// 命令是以 0x0d 0x0a 结尾的
						// 如果收到了命令, 就返回.
						break;
					}
				}
			}
		}
	}
	
	return COM1.RX_Cnt;
}

void process_uart_cmd(void)
{
	int len = 0;
	TAG_CMD_ITEM* pcmd_array = g_cmd_array;
	TAG_CMD_PARAM param;
	BOOL is_cmd_was_process = FALSE;
	
	wait_until_user_input_cmd_from_uart1();
		
	do {
		if ((NULL == pcmd_array) 
			|| (NULL == pcmd_array->pc_cmd_name)
			|| (NULL == pcmd_array->pc_cmd_desc)
			|| (NULL == pcmd_array->pfn_cmd_proc)
			|| (COM1.RX_Cnt < 3))
		{
			break;
		}
		
		RX1_Buffer[COM1.RX_Cnt - 2] = '\0';
		len = strlen(RX1_Buffer);
		if ((len <= 0) || (len >= (sizeof(g_tmp_buf) - 1)))
		{
			break;
		}
		
		memset(g_tmp_buf, 0, sizeof(g_tmp_buf));
		strcpy(g_tmp_buf, RX1_Buffer); // 得到了没有\r\n, 以\0结尾的用户命令
		
		// 用户输入命令名称或者命令序号都可以执行命令
		if (
			(0 == strcmp(g_tmp_buf, pcmd_array->pc_cmd_name))
			|| (pcmd_array->cmd_index == atoi(g_tmp_buf))
			)
		{
			// 在表中, 找到了用户输入的命令
			param.cmd_index = pcmd_array->cmd_index;
			param.pc_cmd_name = pcmd_array->pc_cmd_name;
			param.pc_cmd_desc = pcmd_array->pc_cmd_desc;

			(*pcmd_array->pfn_cmd_proc)(&param);
			is_cmd_was_process = TRUE; // 命令被处理过了
			break;
		}

			
		pcmd_array++;
		
	} while(TRUE);	
		
	// 如果命令没有处理(有可能是用户输入错了命令), 再次显示一次帮助
	if (!is_cmd_was_process)
	{
		fn_proc_cmd_help(NULL);
	}

	// 处理完用户发来的命令后, 重新接收新的用户命令
	COM1.RX_Cnt = 0;
}

void Hardware_init(void)
{
	EA = 0; // 总中断 - 关
	
	UART1_init();
	UART2_init();
	
	EA = 1; // 总中断 - 开
	
	// 在总中断开之后, 才可以操作和中断相关的操作. e.g. 串口打印
	// 否则需要等进中断才能设置的标志, 就会死等在那里
	
	// COM1_BAUDRATE_BPS
	memset(g_tmp_buf, 0, sizeof(g_tmp_buf));
	sprintf(g_tmp_buf, "COM1 - 初始化完成(%ld/N/8/1)\r\n", COM1_BAUDRATE_BPS);
	PrintString1(g_tmp_buf);

	// COM2_BAUDRATE_BPS
	memset(g_tmp_buf, 0, sizeof(g_tmp_buf));
	sprintf(g_tmp_buf, "COM2 - 初始化完成(%ld/N/8/1)\r\n", COM2_BAUDRATE_BPS);
	PrintString2(g_tmp_buf);
}

void UART1_init(void)
{
	COMx_InitDefine		COMx_InitStructure;					//结构定义
	
	ES = 0; // 串行中断 - 关
	
	// COM1 9600/N/8/1
	COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;		//模式,       UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
	COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//使用波特率,   BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
	COMx_InitStructure.UART_BaudRate  = COM1_BAUDRATE_BPS;			//波特率, 一般 110 ~ 115200
	COMx_InitStructure.Morecommunicate = DISABLE; //多机通讯允许, ENABLE,DISABLE
	COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
	COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
	COMx_InitStructure.UART_Interrupt = ENABLE;				//中断允许,   ENABLE或DISABLE
	COMx_InitStructure.UART_Polity    = PolityLow;			//中断优先级, PolityLow,PolityHigh
	COMx_InitStructure.UART_P_SW      = UART1_SW_P16_P17;	//切换端口,   UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17(必须使用内部时钟)
	COMx_InitStructure.UART_RXD_TXD_Short = DISABLE;		//内部短路RXD与TXD, 做中继, ENABLE,DISABLE
	USART_Configuration(USART1, &COMx_InitStructure);		//初始化串口1 USART1,USART2
	
	ES = 1; // 串行中断 - 开
	
	
}

void UART2_init(void)
{
	COMx_InitDefine		COMx_InitStructure;					//结构定义
	
	ES = 0; // 串行中断 - 关
	
	// COM2 115200/N/8/1
	COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;		//模式,       UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
	COMx_InitStructure.UART_BRT_Use   = BRT_Timer2;			//使用波特率,   BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
	COMx_InitStructure.UART_BaudRate  = COM2_BAUDRATE_BPS;			//波特率,     110 ~ 115200
	COMx_InitStructure.Morecommunicate = DISABLE; //多机通讯允许, ENABLE,DISABLE
	COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
	COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
	COMx_InitStructure.UART_Interrupt = ENABLE;				//中断允许,   ENABLE或DISABLE
	COMx_InitStructure.UART_Polity    = PolityLow;			//中断优先级, PolityLow,PolityHigh
	COMx_InitStructure.UART_P_SW      = UART2_SW_P46_P47;	//切换端口,   UART2_SW_P10_P11,UART2_SW_P46_P47
	COMx_InitStructure.UART_RXD_TXD_Short = DISABLE;		//内部短路RXD与TXD, 做中继, ENABLE,DISABLE
	USART_Configuration(USART2, &COMx_InitStructure);		//初始化串口2 USART1,USART2

	ES = 1; // 串行中断 - 开
	
	
}

u8 fn_proc_cmd_default(TAG_CMD_PARAM* p_param)
{
	int len = 0;
	TAG_CMD_ITEM* pcmd_array = p_param;
	
	if (NULL == p_param)
	{
		return FALSE;
	}
	
	memset(&g_tmp_buf[0], 0, sizeof(g_tmp_buf));
	sprintf(g_tmp_buf, "您输入的命令是 : [%s]\r\n", (NULL != p_param->pc_cmd_name) ? p_param->pc_cmd_name : "NULL");
	PrintString1(g_tmp_buf);

	PrintString1("命令执行中, 请稍候...\r\n");
	return TRUE;
}

void my_assert(const char* pc_file_name, int iLine, BOOL bResult, const char* pDesc)
{
	int len = 0;
	UNUSED(pc_file_name);
	UNUSED(iLine);
	UNUSED(pDesc);
	
	if (!bResult)
	{
		do {
			if (NULL != pDesc)
			{
				len = strlen(pDesc);
			}
			
			if (!bResult)
			{
				// 如果想从死循环中出去, 将bResult放进watch, 改成0
				break;
			}
		} while (TRUE);
	}
}


END

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-11-05 00:42:58  更:2022-11-05 00:45:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/26 9:19:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码