This paper describes an approach to teaching embedded systems from the perspective of cyber-physical systems. We place less emphasis on the mechanics of embedded system design and more on critical thinking about design technologies and on how the design of embedded software a↵ects the behavior, safety, and reliability of cyber-physical systems. The course gives students experience with three distinct levels of design of embedded software, namely bare-iron programming (software that executes in the absence of an operating system), programming within a real-time operating system, and model-based design. In each case, students are taught to think critically about the technology, to probe deeply the mechanisms and abstractions that are provided, and to understand the consequences of chosen abstractions on overall system design. This paper describes a laboratory experience that first exposes students to the three levels of abstraction through a structured sequence of exercises, followed by an open-ended capstone project. Several example projects are described.