rp_cvideo/
cvideo.rs

1use core::marker::PhantomData;
2
3#[cfg(feature = "rp2040")]
4use rp2040_hal as hal;
5
6#[cfg(feature = "rp235x")]
7use rp235x_hal as hal;
8
9use hal::dma::{SingleChannel, Word, double_buffer};
10use hal::gpio::{Pin, PinId, PullDown};
11use hal::pio::{
12    Buffers, PIO, PIOBuilder, PIOExt, PinDir, Running, Rx, ShiftDirection, StateMachine,
13    StateMachineIndex, Tx, UninitStateMachine,
14};
15
16use pio::Program;
17use pio_proc::pio_file;
18use rp_cvideo_core::buffer::Buffer;
19use rp_cvideo_core::buffer_preparation::LabelAddresses;
20use rp_cvideo_core::config::CVideoConfig;
21use rp_cvideo_core::display_buffer::{DisplayBuffer, NoFields};
22
23type DmaTxStateQueued<'t, NF> = double_buffer::ReadNext<Buffer<'t, NF>>;
24type DmaTxStateUnQueued = ();
25
26type CVideoDmaTransfer<'t, DmaCh1, DmaCh2, NF, P, SM, DmaTxState> =
27    double_buffer::Transfer<DmaCh1, DmaCh2, Buffer<'t, NF>, Tx<(P, SM), Word>, DmaTxState>;
28
29#[allow(missing_docs)]
30pub struct CVideo<
31    'buffer,
32    P: PIOExt,
33    SM: StateMachineIndex,
34    DmaCh1: SingleChannel,
35    DmaCh2: SingleChannel,
36    DmaTxState,
37    NF: NoFields,
38    PinSync: PinId,
39    PinVideo: PinId,
40> {
41    sm: StateMachine<(P, SM), Running>,
42    dma_transfer: CVideoDmaTransfer<'buffer, DmaCh1, DmaCh2, NF, P, SM, DmaTxState>,
43
44    // Purely for reclaiming the peripherals
45    pin_sync: Pin<PinSync, P::PinFunction, PullDown>,
46    pin_video: Pin<PinVideo, P::PinFunction, PullDown>,
47    pio_sm_rx: Rx<(P, SM)>,
48
49    _nf: PhantomData<NF>,
50}
51
52/// All the peripherals that is taken and potentially returned by a CVideo
53pub struct CVideoPeripherals<P, SM, DmaCh1, DmaCh2, PinSync, PinVideo>
54where
55    P: PIOExt,
56    SM: StateMachineIndex,
57    DmaCh1: SingleChannel,
58    DmaCh2: SingleChannel,
59    PinSync: PinId,
60    PinVideo: PinId,
61{
62    #[allow(missing_docs)]
63    pub pin_sync: Pin<PinSync, P::PinFunction, PullDown>,
64
65    #[allow(missing_docs)]
66    pub pin_video: Pin<PinVideo, P::PinFunction, PullDown>,
67
68    #[allow(missing_docs)]
69    pub sm: UninitStateMachine<(P, SM)>,
70
71    #[allow(missing_docs)]
72    pub dma_ch1: DmaCh1,
73
74    #[allow(missing_docs)]
75    pub dma_ch2: DmaCh2,
76}
77
78/// What gets returned by [CVideo::init]
79pub type CVideoInitResult<'buffer, P, SM, DmaCh1, DmaCh2, NF, PinSync, PinVideo> = Result<
80    /*Ok*/
81    (
82        CVideo<'buffer, P, SM, DmaCh1, DmaCh2, DmaTxStateUnQueued, NF, PinSync, PinVideo>,
83        DisplayBuffer<'buffer, NF>,
84        PIO<P>,
85    ),
86    /*Err*/
87    (
88        CVideoPeripherals<P, SM, DmaCh1, DmaCh2, PinSync, PinVideo>,
89        PIO<P>,
90        &'static str,
91    ),
92>;
93
94impl<'buffer, P, SM, DmaCh1, DmaCh2, NF, PinSync, PinVideo>
95    CVideo<'buffer, P, SM, DmaCh1, DmaCh2, DmaTxStateUnQueued, NF, PinSync, PinVideo>
96where
97    P: PIOExt,
98    SM: StateMachineIndex,
99    DmaCh1: SingleChannel,
100    DmaCh2: SingleChannel,
101    NF: NoFields,
102    PinSync: PinId,
103    PinVideo: PinId,
104{
105    /// Configures the PIO, DMA channels and pins and immediately starts the generation of the video signal
106    /// In the case of successful initialisation, you get an Ok with a tuple with:
107    ///  1. an initialised `CVideo`.
108    ///  2. a [DisplayBuffer] you can start drawing on.
109    ///  3. The PIO you passed, so you can do other things with it. You need it to de-initialise the created CVideo.
110    ///
111    /// If initialisation was not successful, you get back an Err with a tuple with:
112    ///  1. The [CVideoPeripherals] with everything de-initialised so you can use it again.
113    ///  2. The PIO you passed, just as in the `Ok` case.
114    ///  3. An string giving the reason for the error.
115    pub fn init<'conf>(
116        config: CVideoConfig<'conf, NF>,
117        mut pio: PIO<P>,
118        data_buf: &'buffer mut [u32],
119        hw: CVideoPeripherals<P, SM, DmaCh1, DmaCh2, PinSync, PinVideo>,
120    ) -> CVideoInitResult<'buffer, P, SM, DmaCh1, DmaCh2, NF, PinSync, PinVideo> {
121        if !config.check() {
122            return Err((hw, pio, "config.check() returned false"));
123        }
124
125        let pin_sync_id = hw.pin_sync.id().num;
126        let pin_video_id = hw.pin_video.id().num;
127
128        let (program, program_labels) = load_patched_program(config.ticks_per_pixel());
129
130        let maybe_installed = pio.install(&program);
131        if maybe_installed.is_err() {
132            return Err((hw, pio, "Could not install PIO program"));
133        }
134        let installed = maybe_installed.unwrap();
135
136        let installed_offset = installed.offset() as u32;
137        let (mut sm, pio_sm_rx, pio_sm_tx) = PIOBuilder::from_installed_program(installed)
138            .out_pins(pin_video_id, 1)
139            .set_pins(pin_video_id, 1)
140            .side_set_pin_base(pin_sync_id)
141            .autopull(true)
142            .pull_threshold(32)
143            .out_shift_direction(ShiftDirection::Right)
144            .buffers(Buffers::OnlyTx)
145            .clock_divisor_fixed_point(config.pio_prescaler(), 0)
146            .build(hw.sm);
147
148        let maybe_prep_buf =
149            config.prepare_buffer(data_buf, program_labels.with_offset(installed_offset));
150        if let Err(reason) = maybe_prep_buf {
151            let (sm, installed) = sm.uninit(pio_sm_rx, pio_sm_tx);
152            pio.uninstall(installed);
153            return Err((
154                CVideoPeripherals {
155                    pin_sync: hw.pin_sync,
156                    pin_video: hw.pin_video,
157                    sm,
158                    dma_ch1: hw.dma_ch1,
159                    dma_ch2: hw.dma_ch2,
160                },
161                pio,
162                reason,
163            ));
164        }
165        let (buffer_a, buffer_b) = maybe_prep_buf.unwrap();
166
167        assert!(matches!(buffer_b, Buffer::DisplayBuffer(_))); // TODO: return error
168
169        let dma_transfer =
170            double_buffer::Config::new((hw.dma_ch1, hw.dma_ch2), buffer_a, pio_sm_tx).start();
171
172        sm.set_pindirs([
173            (pin_sync_id, PinDir::Output),
174            (pin_video_id, PinDir::Output),
175        ]);
176
177        // TODO investigate why this prevents the state machine from starting,
178        //  only on this commit and not in the working_dma_example branch
179        // sm.clear_fifos();
180
181        Ok((
182            Self {
183                sm: sm.start(),
184                dma_transfer,
185                pin_sync: hw.pin_sync,
186                pin_video: hw.pin_video,
187                pio_sm_rx,
188                _nf: Default::default(),
189            },
190            buffer_b.unwrap_display_buffer(),
191            pio,
192        ))
193    }
194
195    /// Buffer the next image, giving back a CVideo instance reflecting the new state
196    pub fn commit_draw_buffer(
197        self,
198        display_buffer: DisplayBuffer<'buffer, NF>,
199    ) -> CVideo<'buffer, P, SM, DmaCh1, DmaCh2, DmaTxStateQueued<'buffer, NF>, NF, PinSync, PinVideo>
200    {
201        CVideo::<P, SM, DmaCh1, DmaCh2, DmaTxStateQueued<NF>, NF, PinSync, PinVideo> {
202            sm: self.sm,
203            dma_transfer: self
204                .dma_transfer
205                .read_next(Buffer::DisplayBuffer(display_buffer)),
206            pin_sync: self.pin_sync,
207            pin_video: self.pin_video,
208            pio_sm_rx: self.pio_sm_rx,
209            _nf: Default::default(),
210        }
211    }
212
213    /// Waits on the DMA transfer to finish, de-initialises the PIO state-machine and uninstalls
214    /// the PIO program. Finally gives back all the peripherals
215    ///
216    /// Now you can safely drop this CVideo
217    #[allow(clippy::type_complexity)]
218    pub fn uninit(
219        self,
220        mut pio: PIO<P>,
221    ) -> (
222        CVideoPeripherals<P, SM, DmaCh1, DmaCh2, PinSync, PinVideo>,
223        PIO<P>,
224    ) {
225        let (dma_ch1, dma_ch2, _, pio_sm_tx) = self.dma_transfer.wait();
226        let (sm, installed) = self.sm.uninit(self.pio_sm_rx, pio_sm_tx);
227        pio.uninstall(installed);
228
229        (
230            CVideoPeripherals {
231                pin_sync: self.pin_sync,
232                pin_video: self.pin_video,
233                sm,
234                dma_ch1,
235                dma_ch2,
236            },
237            pio,
238        )
239    }
240}
241
242impl<'buffer, P, SM, DmaCh1, DmaCh2, NF, PinSync, PinVideo>
243    CVideo<'buffer, P, SM, DmaCh1, DmaCh2, DmaTxStateQueued<'buffer, NF>, NF, PinSync, PinVideo>
244where
245    P: PIOExt,
246    SM: StateMachineIndex,
247    DmaCh1: SingleChannel,
248    DmaCh2: SingleChannel,
249    NF: NoFields,
250    PinSync: PinId,
251    PinVideo: PinId,
252{
253    /// Wait for the next DisplayBuffer to be released, giving you back a CVideo instance
254    /// reflecting the new state
255    #[allow(clippy::type_complexity)]
256    pub fn wait(
257        self,
258    ) -> (
259        CVideo<'buffer, P, SM, DmaCh1, DmaCh2, DmaTxStateUnQueued, NF, PinSync, PinVideo>,
260        DisplayBuffer<'buffer, NF>,
261    ) {
262        let (mut buffer, mut next_transfer) = self.dma_transfer.wait();
263
264        if matches!(buffer, Buffer::SyncStuff(_)) {
265            (buffer, next_transfer) = next_transfer.read_next(buffer).wait();
266        }
267
268        (
269            CVideo::<P, SM, DmaCh1, DmaCh2, DmaTxStateUnQueued, NF, PinSync, PinVideo> {
270                sm: self.sm,
271                dma_transfer: next_transfer,
272                _nf: Default::default(),
273                pin_sync: self.pin_sync,
274                pin_video: self.pin_video,
275                pio_sm_rx: self.pio_sm_rx,
276            },
277            buffer.unwrap_display_buffer(),
278        )
279    }
280}
281
282/// Loads a program after patching it so the display section has a specific ticks per pixel.
283fn load_patched_program(ticks_per_pixel: u32) -> (Program<32>, LabelAddresses) {
284    assert!(ticks_per_pixel >= 2);
285    let mut pio_program = pio_file!("src/cvideo.pio", select_program("cvideo"));
286    let display_out_instruction_ptr = pio_program.public_defines.display_out_instruction as usize;
287
288    let mut display_out_instruction = pio::Instruction::decode(
289        pio_program.program.code[display_out_instruction_ptr],
290        pio_program.program.side_set,
291    )
292    .unwrap();
293    display_out_instruction.delay = (ticks_per_pixel - 2) as u8;
294    pio_program.program.code[display_out_instruction_ptr] =
295        display_out_instruction.encode(pio_program.program.side_set);
296
297    let label_addresses = LabelAddresses {
298        sync: pio_program.public_defines.sync.try_into().unwrap(),
299        blank: pio_program.public_defines.blank.try_into().unwrap(),
300        display: pio_program.public_defines.display.try_into().unwrap(),
301    };
302
303    (pio_program.program, label_addresses)
304}