当前位置:首页 >> 信息与通信 >>

由浅入深


本系列教程将结合 TI 推出的 CC254x SoC 系列,讲解从环境的搭建到蓝牙 4.0 协议栈的开 发来深入学习蓝牙 4.0 的开发过程。教程共分为六部分,本文为第四部分: 第四部分知识点: 第十六节 协议栈 LED 实验 第十七节 协议栈 LCD 显示 第十八节 协议栈 UART 实验 第十九节 协议栈五向按键 第二十节 协议栈 Flash 数据存储

有关 TI 的 CC254x 芯片介绍,可点击下面链接查看: 主流蓝牙 BLE 控制芯片详解(1):TI CC2540

同系列资料推荐: 由浅入深,蓝牙 4.0/BLE 协议栈开发攻略大全(1) 由浅入深,蓝牙 4.0/BLE 协议栈开发攻略大全(2) 由浅入深,蓝牙 4.0/BLE 协议栈开发攻略大全(3)

有关本文的工具下载,大家可以到以下这个地址: 朱兆祺 ForARM

第十六节 协议栈 LED 实验 TI 的协议栈中在 HAL 层已经有了 LED 的驱动,我们只需要针对我们的开发板进行配置 即可,我们的开发板有两个 LED,分别对应 P1.0 和 P1.1。这个在裸机开发的时候已经介绍 了。

为了保持协议栈原有的代码不变,我们在 BLE-CC254x-1.4.0\Components\hal\target 目录下新建一个文件夹,使它适应我们的开发板。

打开 LED 实验工程 LEDExample,选择 MT254xboard,并且在工程配置中要定义 HAL_LED=TRUE,下载到开发板运行,可以看到两个 LED 同时在闪烁。

那我们的实现代码在哪里呢?其实在协议栈中实现这个很简单, 在启动事件中我们调用 了一个 HalLedSet 函数,并且设置了两个 LED 同时闪烁。

就是这么简单,协议栈已经把其它事情做好了,只需要我们调用设置函数即可。设置的 模式总共有 5 种。 #define HAL_LED_MODE_OFF 0x00 // 关闭 LED #define HAL_LED_MODE_ON 0x01 // 打开 LED #define HAL_LED_MODE_BLINK 0x02 // 闪烁一次 #define HAL_LED_MODE_FLASH 0x04 // 不断的闪烁,最多 255 次 #define HAL_LED_MODE_TOGGLE 0x08 // 翻转 LED 状态 为了适应不同的需求,我们可能需要更改 LED 的输出引脚,如图板级配置在 hal_board_cfg.h 文件中。

这里我们的开发板只有两个 LED, 所以我们在这里根据开发板的实际情况修改相应的 IO 口。

第十七节 协议栈 LCD 显示实验 打开 LCD12864 的实验工程,一样的在工程配置中打开 LCD,选择 MT254xboard 然后直 接编译下载,我们可以看到 LCD 上已经有显示了。

这些显示来自哪里呢?

在初始化函数中可以看到图中的函数调用,这里是将字符串显示到 LCD 的第一行。

在事件回调函数中可以看到这里将本机地址显示到第二行, 将字符串 Initialized 显示 到第三行,但是为什么我们在第三行没有看到这行字符串呢?而显示的字符串是 Advertising , 这是因为系统启动后运行非常快, 在我们还没反应过来的时候已经进入了广 播状态,并且将原来的字符串覆盖了,所以我们最后只能看到 Advertising 了。

HalLcdWriteString 是将第一个参数指向的字符串显示到第二个参数指定第几行中, 例 如我们需要在第 5 行显示系统启动信息,我们可以在启动事件中,添加如下代码。

这里我们来介绍一下 Lcd 驱动的实现, 在 Hal_lcd.h 文件中申明了以下函数, 这些函数 的功能都有英文注释,这里我就不再累述了。 /* * Initialize LCD Service */ extern void HalLcdInit(void); /* * Write a string to the LCD

*/ extern void HalLcdWriteString ( char *str, uint8 option); /* * Write a value to the LCD */ extern void HalLcdWriteValue( uint32 value,const uint8 radix,uint8 option) ; /* * Write a value to the LCD */ extern void HalLcdWriteScreen( char *line1, char *line2 ); /* * Write a string followed by a value to the LCD */ extern void HalLcdWriteStringValue ( char *title, uint16 value, uint8 format, uint8 line ); /* * Write a string followed by 2 values to the LCD */ extern void HalLcdWriteStringValueValue( char *title, uint16 value1, uint8 format1, uint16 value2, uint8 format2, uint8 line ); /* * Write a percentage bar to the LCD

*/ extern void HalLcdDisplayPercentBar( char *title, uint8 value ); 协议栈中很多地方都调用了这些函数, 我们如果要使我们的硬件能够兼容协议栈, 被协 议栈使用,就需要实现这些函数的定义,当然,为了适应我们的开发板,我已经实现了这些 函数,实现都在 hal_lcd.c 中。 第十八节 协议栈 UART 实验 协议栈中已经用了串口的驱动, 我们要做的只是对串口进行初始化, 然后就可以进行串 口数据的收发了。 用使用串口,第一步,需要打开使能串口功能,通过配置工程来实现,这里注意,我们 现在不使用 USB 的 CDC 类来实现串口,所以 HAL_UART_USB=FALSE。 HAL_UART=TRUE HAL_UART_USB=FALSE 要使用串口必须先初始化相应的串口,那该如何初始化呢?在 Hal_uart.h 文件中我们 可以看到如下函数。 uint8 HalUARTOpen(uint8 port, halUARTCfg_t *config); 这个函数就是用来初始化串口的,这个函数有两个参数,第一个指定串口号,第二个是 串口的配置参数。我们来看看这个结构体的定义: typedef struct { bool configured; // 配置与否 uint8 baudRate; // 波特率 bool flowControl; // 流控制 uint16 flowControlThreshold; uint8 idleTimeout; // 空闲时间

halUARTBufControl_t rx; // 接收 halUARTBufControl_t tx; // 发送 bool intEnable; // 中断使能 uint32 rxChRvdTime; // 接收数据时间 halUARTCBack_t callBackFunc; // 回调函数 }halUARTCfg_t; 这个结构体成员很多,但是我们在使用串口的时候并不需要使用所有的成员。 void Serial_Init(void) { halUARTCfg_t SerialCfg = {0}; SerialCfg.baudRate = HAL_UART_BR_115200; // 波特率 SerialCfg.flowControl = HAL_UART_FLOW_OFF; // 流控制 SerialCfg.callBackFunc = SerialCb; // 回调函数 SerialCfg.intEnable = TRUE; SerialCfg.configured = TRUE; HalLcdWriteString( “Open Uart0”, HAL_LCD_LINE_5 ); // 在第 5 行显示启动 信息 HalUARTOpen(HAL_UART_PORT_0, &SerialCfg); HalUARTWrite (HAL_UART_PORT_0,“Hello MT254xBoard\r\n” , osal_strlen ( “Hello MT254xBoard\r\n”)); } 在串口回调函数中我们只做一件事, 将串口接收到的数据显示到 LCD 中并且原样的从串 口输出。回调函数的实现如下:

static void SerialCb( uint8 port, uint8 events ) { uint8 RxBuf[64]={0}; if((events & HAL_UART_TX_EMPTY)||( events & HAL_UART_TX_FULL )) // 发 送区满或者空 { return; } uint16 usRxBufLen = Hal_UART_RxBufLen(HAL_UART_PORT_0); // 读取接收据量 usRxBufLen = MIN(64,usRxBufLen); uint16 readLen = HalUARTRead(HAL_UART_PORT_0, RxBuf, usRxBufLen); HalUARTWrite(HAL_UART_PORT_0, RxBuf, usRxBufLen); } 实验现象,从实验现象中可以看到,一开始在串口中输出了一个标志字符串,然后我们 通过串口发送了 0123456789,然后数据原样的从串口输出了,这和我们预期的结果是一样 的。

但是我们发现 LCD 上的显示和我们预期的不一样, LCD 上只显示了 6789, 前面的数据并 没有显示,这是怎么一回事呢?进行单步调试可以发现,我们发送一次数据,回调函数被回 调了两次,第一次回调只接受到了 012345,第二次回调接收到了 6789,而在 LCD 上的显示 第二次覆盖了第一次的显示,所以我们会看到这种现象,解决的办法,我们需要定义一个数 据帧的时间间隔,当接收数据的间隔超过了此间隔就认为接收结束。

下面我们改写接收处理,我们在接收到数据后开启定时器,定时 5ms 这样,当接收间隔 大于 5ms 后,我们就可以在定时事件中处理串口接收到的数据。 static void SerialCb( uint8 port, uint8 events ) { if((events & HAL_UART_TX_EMPTY)||( events & HAL_UART_TX_FULL )) // 发 送区满或者空 { return; } uint16 usRxBufLen = Hal_UART_RxBufLen(HAL_UART_PORT_0); // 读取接收据量

if(usRxBufLen) { usRxBufLen = MIN(128,usRxBufLen); uint16 readLen = HalUARTRead(HAL_UART_PORT_0, &SerialRxBuf[RxIndex], usRxBufLen); // 读取数据到缓冲区 RxIndex += readLen; readLen %= 128; osal_start_timerEx(simpleBLEPeripheral_TaskID, UART_EVENT, 5); // 启动定 时器 } } 事件处理代码: if ( events & UART_EVENT ) { HalLcdWriteString( (char*)SerialRxBuf, HAL_LCD_LINE_6 ); // 在第 5 行显 示启动信息 HalUARTWrite(HAL_UART_PORT_0, SerialRxBuf, osal_strlen(SerialRxBuf)); osal_memset(SerialRxBuf, 0, 128); return (events ^ UART_EVENT); } 经过这样的处理后,可以发现我们刚刚的问题已经解决了。

到这里串口已经可以正常使用了, 为了更加方便的使用串口, 我在这里添加一个函数实 现标准 C 中 printf,这样更有利于我们输出。 int SerialPrintf(const char*fmt, 。。。) { uint32 ulLen; va_list ap; char *pBuf = (char*)osal_mem_alloc(PRINT_BUF_LEN); // 开辟缓冲区 va_start(ap, fmt); ulLen = vsprintf(pBuf, fmt, ap); // 用虚拟打印函数实现 va_end(ap);

HalUARTWrite(HAL_UART_PORT_0, (uint8*)pBuf, ulLen); // 从串口 0 输出 osal_mem_free(pBuf); // 释放内存空间 return ulLen; } 我们可以像使用 C 标准中的 printf 来使用这个函数,例如我们将 LCD 的输出全部导向 串口的输出,在 HalLcdWriteString 的实现中添加串口输出代码,如下图:

重新编译并且烧录后可以看到 LCD 的输出和串口的输出是一样的了。

第十九节 协议栈五向按键 和前面几个一样,按键的驱动在协议栈中也已经有了,我们只需要做一些小的修改,使 它适应我们的开发板即可。 1. 修改工程配置,使能按键功能。

2. 在我们的工程中要使用按键功能,仅仅打开配置选项是不够的。因为协议栈代码默 认只有 MINIDK 开发板才有按键。

从这里可以看到 (类似的地方有很多) , 如果要使能按键功能还需要定义 CC2540_MINIDK, 但是阅读整个协议栈你会发现,定义 CC2540_MINIDK 后还会打开其它的功能,而那些功能

并不是我们想要的, 所以在这里我们使用另外一种方法来实现。 我们定义我们的开发板也能 使用按键功能,所以在工程配置中添加 MT254xboard=TRUE,然后在按键功能有宏开关的地 方加入这个条件。具体位置参见代码。

按下相应的按键后可以看到串口输出相应的按键值。 五向按键的工作原理在裸机开发的 时候已经讲过了,在协议栈中已经有相应的驱动代码了,无需我们编写,只需要按照实际情 况改写即可。 例如我们的开发板每个按键对应的电压值和原来的值并不一样, 所以我们这里 改写了每个按键值的电压范围。 uint8 halGetJoyKeyInput(void) {

/* The joystick control is encoded as an analog voltage. * Read the JOY_LEVEL analog value and map it to joy movement. */ uint16 adc; uint8 ksave0 = 0; uint8 ksave1; /* Keep on reading the ADC until two consecutive key decisions are the same. */ do { ksave1 = ksave0; /* save previouse key reading */ adc = HalAdcRead (HAL_KEY_JOY_CHN, HAL_ADC_RESOLUTION_10); if ((adc 》= 2) && (adc 《= 95)) // 85 right { ksave0 |= HAL_KEY_RIGHT; } else if ((adc 》= 96) && (adc 《= 110)) // 101 cent { ksave0 |= HAL_KEY_CENTER; } else if ((adc 》= 111) && (adc 《= 140)) // 127 up {

ksave0 |= HAL_KEY_UP; } else if ((adc 》= 141) && (adc 《= 200)) // 170 left { ksave0 |= HAL_KEY_LEFT; } else if ((adc 》= 201) && (adc 《= 300)) // 257 down { ksave0 |= HAL_KEY_DOWN; } } while (ksave0 != ksave1); return ksave0; } 第二十节 协议栈 Flash 数据存储 CC254x 自带了 256K Flash,这 256K 的储存空间不仅可以储存代码,也可以储存用户的 数据,协议栈自带了 SNV 管理代码,我们只需要学会使用即可。 SNV 的使用只有两个函数, 分别是读函数 osal_snv_read 和写函数 osal_snv_write, 在 SNV 的储存中,储存的每个数据都有一个唯一的 ID,SNV 也正是利用这个 ID 来管理储存在 Flash 中的数据,在 BLE 的协议栈中,蓝牙自身数据储存用了一部分 ID,我们储存的数据 ID 不可使用这些 ID,在 bcomdef.h 中有这些 ID 的定义。

下面我们往 SNV 中存入串口接收到的数据, 然后开发板断电重启后读取出这串字符串并 通过串口发送出去,来演示 SNV 的断电保存。 首先我们定义一个我们储存数据的 ID,注意不能和已经有的定义冲突。 #define BLE_NVID_USER_CFG_START 0x80 //!《 Start of the USER Configuration NV IDs #define BLE_NVID_USER_CFG_END 0x89 //!《 End of the USER Configuration NV IDs 我们在启动事件中读取 SNV 中 0x80 的值并通过串口输出读取结果,如果读取成功,则 会将读取结果打印到 PC 端,如果读取失败,则会提示读取失败。

在串口接收事件中将接收到的数据存入 SNV 中,并且也进行相应的提示。

将工程编译下载后,可以看到现象如下:

第一次上电可以看到,提示读取数据失败了,说明第一次运行时是没有存储数据的,接 下来我们通过串口发送字符串 MT254xboard SNV Test 字符串。

可以看到成功的将我们发送过去的字符存入了 SNV 中, 那是否成功存入呢?我们将开发 板断电后重启,看看第二次上电是否能够读取出我们存入的数据。

重启后可以发现我们成功的读取出了第一次存入的数据, 说明我们成功的将数据存入了 SNV 中。

第十五节 BLE 蓝牙 4.0 协议栈启动分析 TI 的这款 CC2540/CC2541 器件可以单芯片实现 BLE 蓝牙协议栈结构图的所有组件,包 括应用程序。从这章开始我们来剖析协议栈源码,我们选用 SimpleBLEPeripheral 工程开 刀,这是一个从机的例程,基本的工作是对外广播,等待主机来连接,读写展示的属性。

首先打开工程文件,打开后可以看到整个工程的结构。

我们按照系统的启动顺序来一步一步走, 我们都知道在 C 代码中, 一般启动的首个函数 为 main,这个函数在 SimpleBLEPeripheral_Main.c 中,打开文件,可以看到这个文件只有 一个 main 函数和一个函数的申明, 我们暂时不理会那个申明的函数, 先看 main 都做了些什 么工作: Int main(void) { /* Initialize hardware */ HAL_BOARD_INIT(); // 硬件初始化

// Initialize board I/O InitBoard( OB_COLD ); // 板级初始化 /* Initialze the HAL driver */ HalDriverInit(); // Hal 驱动初始化 /* Initialize NV system */ osal_snv_init(); // Flash 存储 SNV 初始化 /* Initialize LL */ /* Initialize the operating system */ osal_init_system(); // OSAL 初始化 /* Enable interrupts */ HAL_ENABLE_INTERRUPTS(); // 使能总中断 // Final board initialization InitBoard( OB_READY ); // 板级初始化 #if defined ( POWER_SAVING ) osal_pwrmgr_device( PWRMGR_BATTERY ); // 低功耗管理 #endif /* Start OSAL */ osal_start_system(); // No Return from here 启动 OSAL return 0; }

通过代码我们可以看到, 系统启动的过程, 主要是做了一些初始化, 如果开启了低功耗, 则还需要开启低功耗管理。我们先不去理会初始化做了什么,但是我们知道在 main 函数的 最后启动了 OSAL,那么我们就进去看看 OSAL 是如何运作的。 在 IAR 中如果需要跳转到某个函数或变量的定义,可以在此函数名中右击然后选择 Go To Definition??就可以调到相应的定义。

void osal_start_system( void ) { #if !defined ( ZBIT ) && !defined ( UBIT )

for(;;) // Forever Loop #endif { osal_run_system(); } } 这里看到我们进入了一个死循环,并且一直调用 osal_run_system(),那我们再进入 此函数。 </blockquote></div><div style=“text-align: left;”><div class= “blockcode”><blockquote>void osal_run_system( void ) { uint8 idx = 0; #ifndef HAL_BOARD_CC2538 osalTimeUpdate(); // 定时器更新 #endif Hal_ProcessPoll(); // Hal 层信息处理 do { if (tasksEvents[idx]) // Task is highest priority that is ready. { break; } } while (++idx < tasksCnt); // 检查每个人任务是否有事件

if (idx < tasksCnt) // 有事件发生 { uint16 events; halIntState_t intState; HAL_ENTER_CRITICAL_SECTION(intState); // 进入临界区 events = tasksEvents[idx]; tasksEvents[idx] = 0; // Clear the Events for this task. 清除事件标志 HAL_EXIT_CRITICAL_SECTION(intState); // 退出临界区 activeTaskID = idx; events = (tasksArr[idx])( idx, events ); // 执行事件处理函数 activeTaskID = TASK_NO_TASK; HAL_ENTER_CRITICAL_SECTION(intState); // 进入临界区 tasksEvents [idx] |= events; // Add back unprocessed events to the current task. HAL_EXIT_CRITICAL_SECTION(intState); // 退出临界区 } #if defined( POWER_SAVING ) // 没有事件发生,并且开启了低功耗模式 else // Complete pass through all task events with no activity? { // 系统进入低功耗模式 osal_pwrmgr_powerconserve(); // Put the processor/system into sleep } #endif /* Yield in case cooperative scheduling is being used. */

#if defined (configUSE_PREEMPTION) && (configUSE_PREEMPTION == 0) { osal_task_yield(); } #endif } 在这里可以看到这个 OSAL 的核心, 整个 OSAL 通过检测每个任务是否有事件发生, 如果 有则执行相应的任务,处理相应的事件。如果没有事件需要处理并且开启了低功耗模式,则 系统就会进入低功耗模式。 这里有一个很关键的地方,OSAL 是如何知道哪个事件需要哪个任务来处理呢? events = (tasksArr[idx])( idx, events ); // 执行事件处理函数 我们看这里有一个很关键的数组 tasksArr,很显然,这是一个函数指针数组,我们看 看它的定义。 const pTaskEventHandlerFn tasksArr[] = { LL_ProcessEvent, // task 0 Hal_ProcessEvent, // task 1 HCI_ProcessEvent, // task 2 #if defined ( OSAL_CBTIMER_NUM_TASKS ) OSAL_CBTIMER_PROCESS_EVENT( osal_CbTimerProcessEvent ), // task 3 #endif L2CAP_ProcessEvent, // task 4 GAP_ProcessEvent, // task 5

GATT_ProcessEvent, // task 6 SM_ProcessEvent, // task 7 GAPRole_ProcessEvent, // task 8 GAPBondMgr_ProcessEvent, // task 9 GATTServApp_ProcessEvent, // task 10 SimpleBLEPeripheral_ProcessEvent // task 11 }; 可以看到在这个数组的定义中, 每个成员都是任务的执行函数, 按照任务的优先级排序, 并且在 osalInitTasks 中初始化的时候, 我们可以看到每个任务都有一个对应的初始化函数, 并且传递了一个 taskID,此 ID 从 0 开始自增,这里有一点非常重要,初始化的顺序和任务 数组的定义顺序是一样的, 这就保证了我们给任务发生消息或事件时能够准确的传递到相应 的任务处理函数。 void osalInitTasks( void ) { uint8 taskID = 0; tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt)); /* LL Task */ LL_Init( taskID++ ); /* Hal Task */ Hal_Init( taskID++ ); /* HCI Task */ HCI_Init( taskID++ );

#if defined ( OSAL_CBTIMER_NUM_TASKS ) /* Callback Timer Tasks */ osal_CbTimerInit( taskID ); taskID += OSAL_CBTIMER_NUM_TASKS; #endif /* L2CAP Task */ L2CAP_Init( taskID++ ); /* GAP Task */ GAP_Init( taskID++ ); /* GATT Task */ GATT_Init( taskID++ ); /* SM Task */ SM_Init( taskID++ ); /* Profiles */ GAPRole_Init( taskID++ ); GAPBondMgr_Init( taskID++ ); GATTServApp_Init( taskID++ ); /* Application */ SimpleBLEPeripheral_Init( taskID ); }

应用层的初始化 SimpleBLEPeripheral_Init,SimpleBLEPeripheral_Init ( uint8task_id )主要对 GAP 和 GATT 进行配置,最后调用 osal_set_event (simpleBLEPeripheral_TaskID, SBP_START_DEVICE_EVT )启动设备。 设备启动后应用层就能接收到这个设置的事件并进行处理, 可以看到设备启动中主要是 启动设备,注册绑定管理,并且启动了一个定时器,这个定时器是一个周期事件的第一次启 动。

周期事件中每次都会重启这个定时器,并且处理周期事件。

在初始化的时候我们注册了一个很重要的函数, 设备状态改变时的回调函数, 这个函数 在设备的状态改变时会被底层的协议栈回调, 我们可以从这个回调函数中看的设备的状态的 改变。 static void peripheralStateNotificationCB( gaprole_States_t newState);

从函数的定义可以看出,设备的状态类型都在数据类型 gaprole_States_t 中定义了, 我们看一下这个数据类型的定义: typedef enum { GAPROLE_INIT = 0, //!< Waiting to be started GAPROLE_STARTED, //!< Started but not advertising GAPROLE_ADVERTISING, //!< Currently Advertising GAPROLE_WAITING, //!< Device is started but not advertising, is in waiting period before advertising again GAPROLE_WAITING_AFTER_TIMEOUT, //!< Device just timed out from a connection but is not yet advertising, is in waiting period before advertising again GAPROLE_CONNECTED, //!< In a connection GAPROLE_CONNECTED_ADV, //!< In a connection + advertising GAPROLE_ERROR //!< Error occurred - invalid state } gaprole_States_t; 看到这个定义就很明确了,设备的状态就在这几种状态间切换。


相关文章:
由点到面、由浅入深、由此及彼
龙源期刊网 http://www.qikan.com.cn 由点到面、由浅入深、由此及彼 作者:马庆国 来源:《中国教师》2014 年第 12 期 古典诗词的学习从小学就开始了,这不仅...
小学作文由浅入深教法
小学作文由浅入深教法 一、启蒙先启口 一个刚上三年级的学生,所积累的文字不会超过 4000 字,而对所认识的字在不同语境中的 意义和用法更是知之甚少。在这种...
由浅入深,循序渐进
龙源期刊网 http://www.qikan.com.cn 由浅入深,循序渐进 作者:杨裕琴 来源:《中国校外教育· 综合(上旬)》2014 年第 03 期 新经典,是一抹靓丽的精神文化...
开发OPC由浅入深的几个境界
开发OPC由浅入深的几个境界_计算机软件及应用_IT/计算机_专业资料。开发 OPC 有浅入深的几个境界. 1. 超简单的: 用组态软件去实现^_^, 随便找一个组态软件...
宏程序由浅入深10篇
宏程序由浅入深10篇_机械/仪表_工程科技_专业资料。数控车宏程序由浅入深由浅入深宏程序 1-宏 程序入门基础之销轴 加工对于没有接触过宏程序人,觉得它很神秘...
Java_JDBC由浅入深
Java_JDBC由浅入深_计算机软件及应用_IT/计算机_专业资料 暂无评价|0人阅读|0次下载|举报文档 Java_JDBC由浅入深_计算机软件及应用_IT/计算机_专业资料。...
由浅入深 循序渐进
由浅入深 循序渐进_法律资料_人文社科_专业资料。由浅入深 循序渐进 小学一二年级的学生,要求会写 200 到 300 字左右的连贯通顺 的话,为三年级开始的作文...
伺服通俗理解进阶 —— 由浅入深,用熟知知识常识理解伺...
伺服通俗理解进阶 —— 由浅入深, 用熟知知识常识理解伺服知识与参 数设置 上次说到,伺服可看成一公司的财务部,——根据老板的需求指令,自动运作出明确精 准的...
小学作文由浅入深教法
小学作文由浅入深教法 一、启蒙先启口 一个刚上三年级的学生,所积累的文字不会超过 4000 字,而对所认识 的字在不同语境中的意义和用法更是知之甚少。在这种...
由浅入深,精设课堂练习
由浅入深,精设课堂练习_教学案例/设计_教学研究_教育专区。由浅入深,精设课堂练习 新课程教材综合性增强,实践、操作性的内容增多,注重培养学 生的创新思维。课堂...
更多相关标签: