STM32 独立按键控制 LED 流水灯详细解析

1. 项目背景

在嵌入式开发中,按键控制 LED 进行不同的显示效果是一个常见的实验。本项目基于 STM32 单片机,实现四个独立按键控制 LED 进行不同的灯效变化。

2. 硬件设计

2.1 按键电路原理

从按键电路图来看,每个按键(KEY1-KEY4)都与 GPIO 端口(PC0-PC3)相连,同时通过 220Ω 的限流电阻接地。

  • 按键按下时,GPIO 读取为低电平(0)。
  • 按键释放时,GPIO 读取为高电平(1)。

2.2 LED 控制电路

LED 电路由 8 颗 LED(LED1-LED8)组成,每个 LED 通过 510Ω 的限流电阻连接到 VCC,并受 PNP 型三极管 S8550 控制。

  • 三极管 Q1 的基极由 LED_SW 控制,当 LED_SW 置低时,三极管导通,LED 获得驱动电流。
  • 通过 LED1LED8 对应的 GPIO 控制各个 LED 的亮灭状态。

3. 代码实现

3.1 按键扫描函数

uint8_t key_status(void)
{
	if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 0)
	{
		key_flag = 1;
	}
	if (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == 0)
	{
		key_flag = 2;
	}
	if (HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == 0)
	{
		key_flag = 3;
	}
	if (HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == 0)
	{
		key_flag = 4;
	}
	if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == 1 &&
		HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == 1 &&
		HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == 1 &&
		HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == 1)
	{
		if (key_flag == 3 || key_flag == 4)
		{
			key_flag = 0;
		}
	}
	return key_flag;
}

3.2 LED 控制函数

单个 LED 控制
void led_on(uint8_t led) {
	HAL_GPIO_WritePin(LED_SW_GPIO_Port, LED_SW_Pin, GPIO_PIN_RESET);
	switch (led) {
		case 1: HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); break;
		case 2: HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET); break;
		case 3: HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET); break;
		case 4: HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_RESET); break;
		case 5: HAL_GPIO_WritePin(LED5_GPIO_Port, LED5_Pin, GPIO_PIN_RESET); break;
		case 6: HAL_GPIO_WritePin(LED6_GPIO_Port, LED6_Pin, GPIO_PIN_RESET); break;
		case 7: HAL_GPIO_WritePin(LED7_GPIO_Port, LED7_Pin, GPIO_PIN_RESET); break;
		case 8: HAL_GPIO_WritePin(LED8_GPIO_Port, LED8_Pin, GPIO_PIN_RESET); break;
	}
}
全部 LED 控制
void led_all_on(){
	HAL_GPIO_WritePin(LED_SW_GPIO_Port,LED_SW_Pin,GPIO_PIN_RESET);
	for (int i = 1; i <= 8; i++) {
		led_on(i);
	}
}

void led_all_off(){
	HAL_GPIO_WritePin(LED_SW_GPIO_Port,LED_SW_Pin,GPIO_PIN_RESET);
	for (int i = 1; i <= 8; i++) {
		led_off(i);
	}
}

3.3 流水灯功能

左移流水灯
void led_left(void){
	uint8_t j;
	HAL_GPIO_WritePin(LED_SW_GPIO_Port, LED_SW_Pin, GPIO_PIN_RESET);
	for(uint8_t i = 1; i <= 8; i++){
		led_on(i);
		HAL_Delay(DELAY_MS);
		led_off(i-1);
		for (j = 0; j < DELAY_MS / 10; j++) {
			HAL_Delay(10);
			key_status();
			if (key_flag != 1) return;
		}
	}
}
右移流水灯
void led_right(void){
	uint8_t j;
	HAL_GPIO_WritePin(LED_SW_GPIO_Port, LED_SW_Pin, GPIO_PIN_RESET);
	for(uint8_t i = 8; i > 0; i--){
		led_on(i);
		HAL_Delay(DELAY_MS);
		led_off(i+1);
		for (j = 0; j < DELAY_MS / 10; j++) {
			HAL_Delay(10);
			key_status();
			if (key_flag != 2) return;
		}
	}
}

3.4 红绿灯控制

void led_red(){
	HAL_GPIO_WritePin(LED_SW_GPIO_Port,LED_SW_Pin,GPIO_PIN_RESET);
	led_on(1); led_on(3); led_on(5); led_on(7);
}

void led_green(){
	HAL_GPIO_WritePin(LED_SW_GPIO_Port,LED_SW_Pin,GPIO_PIN_RESET);
	led_on(2); led_on(4); led_on(6); led_on(8);
}

4. 主循环

while(1){
	key_status();
	switch(key_flag){
		case 0: led_all_off(); break;
		case 1: led_left(); break;
		case 2: led_right(); break;
		case 3: led_red(); break;
		case 4: led_green(); break;
	}
}

目前 led_left()led_right() 使用 for 循环逐个点亮 LED,并在 HAL_Delay 过程中不断轮询按键状态。可以优化为:仅当 key_flag 仍为 1 或 2 时继续循环

避免多次调用 HAL_GPIO_WritePin(LED_SW_GPIO_Port, LED_SW_Pin, GPIO_PIN_RESET);

void led_left(void) {
    uint8_t i = 1;
    while (key_flag == 1) {
        led_on(i);
        HAL_Delay(DELAY_MS);
        led_off(i);
        i = (i % 8) + 1; // 让 LED 依次点亮
    }
}

使用数组优化 LED 控制 目前 led_on()led_off() 代码冗余,可用数组优化:

GPIO_TypeDef* LED_PORTS[] = {LED1_GPIO_Port, LED2_GPIO_Port, LED3_GPIO_Port, LED4_GPIO_Port, 
                             LED5_GPIO_Port, LED6_GPIO_Port, LED7_GPIO_Port, LED8_GPIO_Port};
uint16_t LED_PINS[] = {LED1_Pin, LED2_Pin, LED3_Pin, LED4_Pin, 
                       LED5_Pin, LED6_Pin, LED7_Pin, LED8_Pin};

void led_on(uint8_t led) {
    if (led >= 1 && led <= 8) {
        HAL_GPIO_WritePin(LED_PORTS[led - 1], LED_PINS[led - 1], GPIO_PIN_RESET);
    }
}
 

5. 结论

本项目实现了按键控制 LED 的不同显示模式,涵盖流水灯、红绿灯控制等。整个程序逻辑清晰,能够用于 STM32 的按键与 LED 控制实验。希望对大家有所帮助!

6. 优化

增加调试技巧

  1. 按键消抖
    当前代码没有进行按键消抖,可能会导致误触发。可以使用简单的软件消抖,例如:

uint8_t read_key(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) {
    if (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET) {
        HAL_Delay(10);  // 简单消抖
        if (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET) {
            return 1;
        }
    }
    return 0;
}
然后在 key_status() 中使用 read_key(KEY1_GPIO_Port, KEY1_Pin) 代替 HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)

添加串口调试信息
在关键位置加入 printf 输出,有助于调试: 

<think>好的,用户现在想获取在Android平台上实现仿淘宝购物车功能的解决方案或示例代码,特别是UI部分的实现。我需要结合之前的对话和提供的引用内容来给出合适的回答。 首先,回顾用户之前的提问和我的回答,淘宝购物车页面涉及的技术架构包括前端动态渲染、后端微服务、数据存储以及高性能保障措施。用户现在聚焦于Android端的实现,尤其是UI部分,可能需要详细的步骤和代码示例。 接下来,查看用户提供的三个引用。引用[1]提到在uni-app中实现购物车功能,包括单选/全选、修改数量、计算总金额、店铺分类等,这提示我需要考虑Android端的类似功能模块。引用[2]讨论了移动端滑动删除的实现,这部分在淘宝购物车中常见,需要包含在解决方案中。引用[3]则涉及Android购物车的初始化、数据绑定和空布局处理,这些是基础但重要的点。 用户需要仿淘宝的UI,所以需要分析淘宝购物车的界面元素:店铺分组、商品列表、单选/全选、滑动删除、数量加减、总价计算等。同时,可能还需要考虑数据加载方式,如懒加载和分页,以处理大量数据。 我需要将这些元素拆解为具体的Android组件和实现步骤。例如,使用RecyclerView嵌套展示店铺和商品,自定义Adapter处理多级列表,ItemTouchHelper实现滑动删除,数据绑定库或手动更新UI来同步选中状态和总价计算。此外,本地数据存储和网络请求的整合也是需要考虑的。 还需要注意性能优化,比如RecyclerView的视图复用,避免内存泄漏,以及如何处理异步数据加载和UI更新。引用[1]中的懒加载和分页加载更多数据可以应用到Android的RecyclerView滚动监听中,实现动态加载。 最后,用户可能需要示例代码,所以需要提供关键的代码片段,如Adapter的实现、ViewHolder的结构、滑动删除的处理逻辑,以及总价计算的方法。同时,要确保代码符合Android最佳实践,例如使用ViewBinding或DataBinding来简化视图操作,避免在Adapter中处理业务逻辑,而是通过接口回调与Activity/Fragment通信。 可能还需要考虑不同Android版本的兼容性,以及如何测试这些功能,比如使用Espresso进行UI测试,确保交互的稳定性。此外,引用[3]提到的空布局和下拉刷新功能也需要整合到示例中,提升用户体验。 总结下来,回答需要结构清晰,分步骤讲解UI构建、功能实现和数据处理,并提供可运行的代码示例,同时引用之前的架构设计中的要点,如数据一致性和高性能保障,确保解决方案的全面性。</think>根据淘宝购物车功能特点及Android实现要点,以下为分模块实现方案及核心代码示例: ### 一、UI架构设计(仿淘宝布局) ```xml <!-- 购物车容器 --> <CoordinatorLayout> <RecyclerView app:layout_behavior="@string/appbar_scrolling_view_behavior"/> <LinearLayout android:id="@+id/bottom_bar"> <CheckBox android:id="@+id/cb_all_select"/> <TextView android:id="@+id/tv_total_price"/> <Button android:text="结算"/> </LinearLayout> </CoordinatorLayout> ``` ### 二、核心功能实现 #### 1. 多级嵌套列表(店铺+商品) ```kotlin // 数据结构 data class CartEntity( val shopId: Long, var isShopSelected: Boolean = false, val goodsList: MutableList<CartItem> = mutableListOf() ) data class CartItem( val itemId: String, var count: Int = 1, var isSelected: Boolean = false ) // 多类型Adapter实现 class CartAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { companion object { const val TYPE_SHOP = 0 const val TYPE_ITEM = 1 } override fun getItemViewType(position: Int): Int { return when (dataList[position]) { is CartEntity -> TYPE_SHOP is CartItem -> TYPE_ITEM else -> throw IllegalArgumentException() } } } ``` #### 2. 滑动删除实现(ItemTouchHelper) ```kotlin class SwipeDeleteCallback : ItemTouchHelper.SimpleCallback( 0, ItemTouchHelper.LEFT) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position = viewHolder.adapterPosition // 执行删除操作 (mAdapter as CartAdapter).removeItem(position) } override fun onChildDraw( c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean ) { // 绘制删除背景和图标 val itemView = viewHolder.itemView val deleteIcon = ContextCompat.getDrawable(itemView.context, R.drawable.ic_delete) val deleteMargin = 16.dpToPx() deleteIcon?.let { val iconTop = itemView.top + (itemView.height - it.intrinsicHeight) / 2 val iconLeft = itemView.right - deleteMargin - it.intrinsicWidth it.setBounds(iconLeft, iconTop, iconLeft + it.intrinsicWidth, iconTop + it.intrinsicHeight) it.draw(c) } } } ``` ### 三、业务逻辑处理 #### 1. 总价计算(响应式更新) ```kotlin private fun calculateTotalPrice() { var total = 0.0 cartData.forEach { shop -> shop.goodsList.filter { it.isSelected }.forEach { item -> total += item.price * item.count } } binding.tvTotalPrice.text = "¥${String.format("%.2f", total)}" } ``` #### 2. 联动选择逻辑 ```kotlin // 店铺选择状态变化 fun onShopSelectChanged(shopPosition: Int, isChecked: Boolean) { val shop = dataList[shopPosition] as CartEntity shop.isShopSelected = isChecked shop.goodsList.forEach { it.isSelected = isChecked } checkAllSelectState() } // 全选按钮点击 binding.cbAllSelect.setOnCheckedChangeListener { _, isChecked -> cartData.forEach { shop -> shop.isShopSelected = isChecked shop.goodsList.forEach { it.isSelected = isChecked } } adapter.notifyDataSetChanged() calculateTotalPrice() } ``` ### 四、性能优化要点 1. **视图复用**:使用`RecyclerView.RecycledViewPool`共享视图池 2. **差异更新**:实现`ListAdapter`配合`DiffUtil`进行增量更新 3. **内存优化**:商品图片加载使用Glide/Picasso的RecyclerView预加载策略[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值