diff --git a/rt-thread-version/rt-thread-standard/_sidebar.md b/rt-thread-version/rt-thread-standard/_sidebar.md index b5232ff66412cc667e002a22abd1de9bb4f27279..4006975991317bc7e63971ba9ba7be30ad91ffe3 100644 --- a/rt-thread-version/rt-thread-standard/_sidebar.md +++ b/rt-thread-version/rt-thread-standard/_sidebar.md @@ -217,6 +217,9 @@ - [富瀚微FH8626V300L开发实践指南](/rt-thread-version/rt-thread-standard/tutorial/make-bsp/fullhan/富瀚微FH8626V300L开发实践指南.md) - [富瀚微MC632X开发实践指南](/rt-thread-version/rt-thread-standard/tutorial/make-bsp/fullhan/富瀚微MC632X开发实践指南.md) + - 纳芯微系列 + - [纳芯微NS800RT7P65D开发实践指南](/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/纳芯微NS800RT7P65D开发实践指南.md) + - XUANTIE系列 - [XUANTIE开发实践指南](/rt-thread-version/rt-thread-standard/tutorial/make-bsp/xuantie/XUANTIE开发实践指南.md) diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-01.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-01.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..c85d4c90b6afa2645dec903c607757d9483bd1d0 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-01.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-02.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-02.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..1317dc268df30bb9c2dbf1839135bf14b47eb19e Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-02.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-03.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-03.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..9a7b033350d2fd121f8e9bbe881660e83dcb9fa5 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-03.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-04.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-04.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..3de919f26ab56a4ce7d474dac5a3a61e1644e430 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-04.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-05.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-05.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..6a3fbaa9b0caad074b8ce177ee1ce7e4322bc0ea Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-05.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-06.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-06.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..4a23fa401bc5f6df8a70f03e18f77d1618028c17 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-06.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-07.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-07.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..e4616dc8b0767f78107509c7c6afb463fc054136 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/adc-\346\234\261\347\216\211\346\226\275-07.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/ecap-\347\250\213\345\273\267\346\241\242-01.jpg.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/ecap-\347\250\213\345\273\267\346\241\242-01.jpg.webp" new file mode 100644 index 0000000000000000000000000000000000000000..3f0ef556e26c17e266f9ba67b815ded114f54fdc Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/ecap-\347\250\213\345\273\267\346\241\242-01.jpg.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/ecap-\347\250\213\345\273\267\346\241\242-02.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/ecap-\347\250\213\345\273\267\346\241\242-02.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..40d1f79d0e40baf77ef2972b4f0dc013e623b03f Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/ecap-\347\250\213\345\273\267\346\241\242-02.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-01.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-01.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..0e915527d08c9d6e54ad77730cebdf023bf10f39 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-01.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-02.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-02.png" new file mode 100644 index 0000000000000000000000000000000000000000..8b2e8665fdab73972dba261a2de9cf4b0965a796 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-02.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-03.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-03.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..ad8a0783040fd8d82121fb203607d462c560096b Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-03.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-04.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-04.png" new file mode 100644 index 0000000000000000000000000000000000000000..403e92ea44d3a4205dc638ba3a412fc336d7f27d Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-04.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-05.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-05.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..461fe4a945e91db2926d10557efbe706edc1db20 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-05.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-06.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-06.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..55e0a9abea7646a9bd0923f286172924d1fe19ca Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-06.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-07.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-07.png" new file mode 100644 index 0000000000000000000000000000000000000000..b8fbac536fd1691b0a7da2b79d7d0182f1857b14 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-07.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-08.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-08.png" new file mode 100644 index 0000000000000000000000000000000000000000..6fdfad1a560c3c2f92dacd4725ea4b2c409f6f78 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-08.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-09.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-09.png" new file mode 100644 index 0000000000000000000000000000000000000000..a2699b36d1ab3c6d564399c6e29cbd732c83135c Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-09.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-10.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-10.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..724767b8488d3ab9e6ee9c6261119f31888629e9 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-10.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-11.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-11.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..3972b172ed79f7fbae31f24e0e45169143da1836 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-11.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-12.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-12.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..9a125f5775801f4973a4ae020abce8aa89d86427 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-12.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-13.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-13.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..398027987ab9a3ba84bc60f25b015367a203f4fd Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/gpio-\347\206\212\346\262\273\345\235\244-13.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/hwtimer-\345\210\230\345\215\217\346\263\211-01.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/hwtimer-\345\210\230\345\215\217\346\263\211-01.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..0dd1ed048f64900c320045ccfee4e0f807ce2530 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/hwtimer-\345\210\230\345\215\217\346\263\211-01.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/hwtimer-\345\210\230\345\215\217\346\263\211-02.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/hwtimer-\345\210\230\345\215\217\346\263\211-02.png" new file mode 100644 index 0000000000000000000000000000000000000000..5a48e458e125410ba3b91121ba0526a44241d21e Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/hwtimer-\345\210\230\345\215\217\346\263\211-02.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/hwtimer-\345\210\230\345\215\217\346\263\211-03.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/hwtimer-\345\210\230\345\215\217\346\263\211-03.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..fe0a89b8f57b2c5479e4b5e063288fe6c68b36b8 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/hwtimer-\345\210\230\345\215\217\346\263\211-03.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\210\230\345\273\272\345\215\216-01.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\210\230\345\273\272\345\215\216-01.png" new file mode 100644 index 0000000000000000000000000000000000000000..4aac8c4ec0edccfb2c68287a380172f681e8ae97 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\210\230\345\273\272\345\215\216-01.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-01.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-01.png" new file mode 100644 index 0000000000000000000000000000000000000000..18205ee23bcb5f6e3cc53a57486138dd910907f3 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-01.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-02.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-02.png" new file mode 100644 index 0000000000000000000000000000000000000000..79181f32f827508922f9f4715aed97be8f507175 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-02.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-03.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-03.png" new file mode 100644 index 0000000000000000000000000000000000000000..525eae0940c5a692de05ac19654fc006caf15948 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-03.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-04.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-04.png" new file mode 100644 index 0000000000000000000000000000000000000000..00de13ee93e9e07357449d4d3275eb203cd583ad Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-04.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-05.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-05.png" new file mode 100644 index 0000000000000000000000000000000000000000..3212a4b603d6561f776e43c9a2cd742d0c3c9786 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-05.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-06.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-06.png" new file mode 100644 index 0000000000000000000000000000000000000000..602025f73d0584c9d3a29c5b8adb0820a9f4c5cd Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/spi-\345\274\240\345\267\245-06.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-01.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-01.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..a356428e31a6d3486d1e204251cb7a3031d2dd50 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-01.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-02.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-02.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..0d6525cf48b4beabcac91f4d38996d888be1df1b Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-02.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-03.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-03.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..1f0d59e4feb56f8a800074fa76159870db096e1c Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-03.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-04.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-04.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..a06d3e54f708071284164947b87b6d0cec2da6fe Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-04.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-05.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-05.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..ac89ae96d65f5a85099661876233e6f06e75cd9e Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-05.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-06.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-06.png" new file mode 100644 index 0000000000000000000000000000000000000000..6ee977243fce5c0ce13818f892c157c60b2410f6 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-06.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-07.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-07.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..370a9de40399e16838946843e6a93c8be79834d8 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-07.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-08.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-08.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..ff44d8bfa5e33b1282677f0d40f820736f676e34 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-08.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-09.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-09.png" new file mode 100644 index 0000000000000000000000000000000000000000..dd65cbb458cb498e79d4aa3a869ed96f42f825b7 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-09.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-10.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-10.png" new file mode 100644 index 0000000000000000000000000000000000000000..29572f1a4a744dbf6c6d3c72ef6dabf503a6f4ae Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-10.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-11.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-11.png" new file mode 100644 index 0000000000000000000000000000000000000000..5ec13e677fcc7da9ff921bb3992c77bbe66274cf Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-11.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-12.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-12.png" new file mode 100644 index 0000000000000000000000000000000000000000..755e12cd014dbaf554df92a3d0dbfceaa6ec492b Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-12.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-13.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-13.png" new file mode 100644 index 0000000000000000000000000000000000000000..7087771b86bc9952bce23918e554b9fad19cccab Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-13.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-14.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-14.png" new file mode 100644 index 0000000000000000000000000000000000000000..5914e3abc41ee6597067ded71d7338d45b340e12 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-14.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-15.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-15.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..6c165663d8f96aa48a849349d81095645e9f2243 Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/uart-\345\274\240\346\265\267\346\266\233-15.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/wdt-\350\242\201\350\203\234\345\257\214-01.png" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/wdt-\350\242\201\350\203\234\345\257\214-01.png" new file mode 100644 index 0000000000000000000000000000000000000000..5ade5336a366d7c7ea5b180d67d6c4383ddaeb1f Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/wdt-\350\242\201\350\203\234\345\257\214-01.png" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/wdt-\350\242\201\350\203\234\345\257\214-02.png.webp" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/wdt-\350\242\201\350\203\234\345\257\214-02.png.webp" new file mode 100644 index 0000000000000000000000000000000000000000..fbd869c5741488e3ceb3f5dfc436ecc68bfd99fc Binary files /dev/null and "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/figures/wdt-\350\242\201\350\203\234\345\257\214-02.png.webp" differ diff --git "a/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/\347\272\263\350\212\257\345\276\256NS800RT7P65D\345\274\200\345\217\221\345\256\236\350\267\265\346\214\207\345\215\227.md" "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/\347\272\263\350\212\257\345\276\256NS800RT7P65D\345\274\200\345\217\221\345\256\236\350\267\265\346\214\207\345\215\227.md" new file mode 100644 index 0000000000000000000000000000000000000000..a301d4e36e93de1e5b0879093ba8365c4439d9cc --- /dev/null +++ "b/rt-thread-version/rt-thread-standard/tutorial/make-bsp/novosns/ns800/\347\272\263\350\212\257\345\276\256NS800RT7P65D\345\274\200\345\217\221\345\256\236\350\267\265\346\214\207\345\215\227.md" @@ -0,0 +1,5150 @@ +# 纳芯微NS800RT7P65D开发实践指南 + +| 目录 | 作者 | +| --- | --- | +| 零、实践指南说明 | RT-Thread & 纳芯微 | +| 一、NS800RT7P65D上的UART实践 | 张海涛 | +| 二、NS800RT7P65D上的GPIO实践 | 熊治坤 | +| 三、NS800RT7P65D上的ADC实践 | 朱玉施 | +| 四、NS800RT7P65D上的HWTimer实践 | 刘协泉 | +| 五、NS800RT7P65D上的WDT实践 | 袁胜富 | +| 六、NS800RT7P65D上的SPI实践 | 刘建华 | +| 七、NS800RT7P65D上的SPI实践 | 张工 | +| 八、NS800RT7P65D上的eCAP实践 | 程廷桢 | +| FAQ | RT-Thread & 纳芯微 | + +## 零、纳芯微NS800RT7P65D实践指南说明 + +### 1. 简介 + +NS800RT7P65D(S) 系列是纳芯微基于 Arm Cortex-M7 内核推出的实时微控制器。本文档依据腾讯文档“开发评测提交”中“测评结果”打勾的条目整理,并使用本机 Chrome 读取 RT-Thread Club 文章正文后合并生成。已完成条目的图片均已本地化到 figures 目录。 + +### 2. 已确认打勾条目 + +| 模块 | 作者 | 源文章状态 | +| --- | --- | --- | +| UART | 张海涛 | [已整理](https://club.rt-thread.org/ask/article/3975175f1d690de4.html) | +| GPIO | 熊治坤 | [已整理](https://club.rt-thread.org/ask/article/d7a4e5d88394553f.html) | +| ADC | 朱玉施 | [已整理](https://club.rt-thread.org/ask/article/233cd28f73853683.html) | +| HWTimer | 刘协泉 | [已整理](https://club.rt-thread.org/ask/article/9eb602c9d20cf27b.html) | +| WDT | 袁胜富 | [已整理](https://club.rt-thread.org/ask/article/4e8e5f60f9162bd2.html) | +| SPI | 刘建华 | [已整理](https://club.rt-thread.org/ask/article/119a830b1b1d5b0e.html) | +| SPI | 张工 | [已整理](https://club.rt-thread.org/ask/article/58f1c25ed63721ef.html) | +| eCAP | 程廷桢 | [已整理](https://club.rt-thread.org/ask/article/b122efd1dec8cf92.html) | + +## 一、纳芯微NS800RT7P65D上的UART实践(张海涛) + +### 1. NS800RT7P65 概述 +飞书链接(优先更新):https://my.feishu.cn/wiki/DhMnwIciFiYMQuk3HtBcI1nZn5c +#### 1.1 总览 + +NS800RTP65D(S)系列是纳芯微(NSSine™)基于 ARM Cortex®-M7 内核推出的一款实时微控制器。该系 列融合了数字信号处理与微控制器两大优势,为纳芯微的最新研发成果。 本产品搭载两个 ARM Cortex®-M7 内核。该内核基于 ARMv7E-M 架构,集成双精度浮点单元(FPU),符 合 IEEE 754 标准;内置 DSP 扩展指令集,支持单周期 16/32 位 MAC 及 SIMD 运算;并配备内存保护单 元(MPU),用于内存访问权限管理,提升系统安全性。 + +#### 1.2 资源概述 + +**核心架构与处理性能** + +* **双核架构:** 搭载两个 ARM Cortex®-M7 内核,基于 ARMv7E-M 架构。内核采用六级双发射超标量流水线,支持分支预测。 + +* **浮点单元(FPU):** 每个内核均配备双精度浮点单元,符合 IEEE 754 标准,支持单精度与双精度数据处理指令和数据类型。 + +* DSP 指令集:支持完整的 DSP 扩展指令集,包括单周期 16/32 位乘法累加(MAC)及 8/16 位单指令 多数据流(SIMD)运算。 + +* **保护单元(MPU):** 内置内存保护单元,支持多区域内存访问权限控制,实现任务级代码、数据与 堆栈隔离,增强系统安全性。 + +* **代码执行:** 支持从片上 FLASH、TCM(紧耦合内存)或片上 SRAM 中执行浮点或定点代码,可高效处 理 DSP 算术与系统控制任务。TCM 提供与内核同频的高速低延迟访问。 + +**数学加速单元** + +* **MMATH 单元:** 搭载与内核 Cortex®-M7 紧耦合的三角函数运算单元,可与内核并行运算,显著增强三 角函数计算性能。 + +* **EMATH 单元:** 集成系统级数学运算加速器,支持单精度浮点运算(FPU),可与内核并行运算,提升 通用数学函数处理速度。 + +**存储资源** + +* **片上存储:** 集成高达 1MB 支持 ECC 的片上 FLASH,以及 256KB 支持 ECC 的片上 SRAM。 + +* **紧耦合存储(TCM):** 每个内核独立配备 128KB ITCM 和 128KB DTCM。在 ITCM 或 DTCM 中运行代 码可实现零等待访问,且所有 TCM 均支持 ECC 校验。 + +#### 1.3 模块框图及系统总线框图 + +**模块框图** + +![NS800RT7P65 Uart模块测评-image-6.png](./figures/uart-张海涛-01.png.webp) + +**系统总线框图** + +![NS800RT7P65 Uart模块测评-image-4.png](./figures/uart-张海涛-02.png.webp) + +![NS800RT7P65 Uart模块测评-image-5.png](./figures/uart-张海涛-03.png.webp) + +### 2. 芯片层面 Uart 资源 - 解析 + +UART 支持全双工、异步、NRZ 串行通信,由波特率发生器、发送器和接收器组成。发送器和接收器独立工作,尽管它们使 用相同的波特率发生器。其资源分布主要如下: + +* 从[1.3.1 模块框图](https://my.feishu.cn/wiki/DhMnwIciFiYMQuk3HtBcI1nZn5c#share-UPxXd14ZIohXxLxSfqdc3z9gncf) 可以看出,该芯片总共支持4路Uart; + +* 根据[1.3.2 系统总线框图](https://my.feishu.cn/wiki/DhMnwIciFiYMQuk3HtBcI1nZn5c#share-AoeadknAsoJDYSxl55XcVQf4nGh) 可以观察到,芯片的Uart1 / 2分布在APB 2,Uart3 / 4分布在APB4,总线最大时钟频率200Mhz; + +根据参考手册,Uart模块具有以下特性,此处不再赘述: + +![NS800RT7P65 Uart模块测评-image.png](./figures/uart-张海涛-04.png.webp) + +### 3. 芯片层面 Uart 配置 - 说明 + +#### 3.1 引脚复用配置 + +* 从 **数据手册 Page 63页** 开始,查看GPIO引脚复用信息,根据自身板卡情况,决定板级引脚复用配置,下图仅列出部分引脚复用情况; + +![NS800RT7P65 Uart模块测评-image-1.png](./figures/uart-张海涛-05.png.webp) + +当前板卡驱动直接在drv_uart的config数组中 **HardCode** 为具体配置; + +![NS800RT7P65 Uart模块测评-image-2.png](./figures/uart-张海涛-06.png) + +#### 3.2 波特率配置 + +* 根据下图芯片手册中的介绍,可以看到波特率主要由: **芯片时钟配置、SBR寄存器、OSR寄存器三方决定:** + +![NS800RT7P65 Uart模块测评-image-2.png](./figures/uart-张海涛-07.png.webp) + +* 如是不想计算,则可以参考数据手册建议的 **波特率配置参考值** : + +![NS800RT7P65 Uart模块测评-image-3.png](./figures/uart-张海涛-08.png.webp) + +**注意: 上述表格对应的 UART 时钟频率为 200M !!!** + +当前板卡使用的是默认配置; + +![NS800RT7P65 Uart模块测评-image-5.png](./figures/uart-张海涛-09.png) + +![NS800RT7P65 Uart模块测评-image-6.png](./figures/uart-张海涛-10.png) + +### 4. 代码层面 - Uart 功能测试 + +#### 4.1 基础功能测评 + +**Msh 功能** + +![NS800RT7P65 Uart模块测评-e75620bf-f716-4f9b-9361-993dfb799511.png](./figures/uart-张海涛-11.png) + +可以看到shell功能运行正常,基于shell功能编写测试用例; + +**收发数据测试** + +虽然第一步shell的成功使用,基本说明串口功能的正常,但是还是编写了Uart测试代码;在shell中执行uart_test; + +```c + +/* 与 USB 串口助手相连的控制台口,默认 uart0 (TX=PB15 RX=PA8) */ +#ifndef UART_TEST_DEV_NAME +#define UART_TEST_DEV_NAME RT_CONSOLE_DEVICE_NAME +#endif + +#define UART_TEST_BAUD 115200 +#define UART_RX_TIMEOUT_MS 5000 + +static rt_device_t uart_dev = RT_NULL; +static struct rt_semaphore uart_rx_sem; +static rt_thread_t uart_echo_tid = RT_NULL; +static rt_bool_t uart_inited = RT_FALSE; + +static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size) +{ + RT_UNUSED(dev); + RT_UNUSED(size); + rt_sem_release(&uart_rx_sem); + return RT_EOK; +} + +static rt_err_t uart_ensure_ready(void) +{ + if (uart_inited) + return RT_EOK; + + uart_dev = rt_console_get_device(); + if (uart_dev == RT_NULL) + { + rt_kprintf("[uart] console device unavailable\n"); + return -RT_ERROR; + } + + rt_device_set_rx_indicate(uart_dev, uart_rx_ind); + uart_inited = RT_TRUE; + + rt_kprintf("[uart] ready: %s, %d 8N1, TX=PB15 RX=PA8\n", + UART_TEST_DEV_NAME, UART_TEST_BAUD); + return RT_EOK; +} + +static rt_err_t uart_do_tx(rt_size_t len) +{ + rt_size_t i, written; + char *buf = RT_NULL; + + if (uart_ensure_ready() != RT_EOK) + return -RT_ERROR; + + if (len == 0) + len = 64; + if (len > 256) + len = 256; + + buf = rt_malloc(len + 1); + if (buf == RT_NULL) + return -RT_ENOMEM; + + rt_snprintf(buf, len + 1, "[UART TX test, %u bytes]\r\n", (unsigned)len); + for (i = rt_strlen(buf); i < len; i++) + buf[i] = (char)('A' + (i % 26)); + + written = rt_device_write(uart_dev, 0, buf, len); + rt_kprintf("[uart] TX: sent %u / %u bytes %s\n", + (unsigned)written, (unsigned)len, + (written == len) ? "OK" : "FAIL"); + + rt_free(buf); + return (written == len) ? RT_EOK : -RT_ERROR; +} + +static rt_err_t uart_do_rx(rt_uint32_t timeout_ms) +{ + rt_size_t rx_len, total = 0; + rt_uint8_t rx_buf[128]; + rt_tick_t deadline; + + if (timeout_ms == 0) + timeout_ms = UART_RX_TIMEOUT_MS; + + if (uart_ensure_ready() != RT_EOK) + return -RT_ERROR; + + rt_sem_control(&uart_rx_sem, RT_IPC_CMD_RESET, RT_NULL); + rt_kprintf("[uart] RX: please send data from PC within %u ms...\n", timeout_ms); + + deadline = rt_tick_get() + rt_tick_from_millisecond(timeout_ms); + while (rt_tick_get() < deadline) + { + rt_tick_t wait = deadline - rt_tick_get(); + if (wait > rt_tick_from_millisecond(100)) + wait = rt_tick_from_millisecond(100); + + if (rt_sem_take(&uart_rx_sem, wait) != RT_EOK) + continue; + + do + { + rx_len = rt_device_read(uart_dev, 0, rx_buf, sizeof(rx_buf) - 1); + if (rx_len > 0) + { + total += rx_len; + rx_buf[rx_len] = '\0'; + rt_kprintf("[uart] RX(%u): %s\n", (unsigned)rx_len, rx_buf); + } + } while (rx_len > 0); + } + + rt_kprintf("[uart] RX done, total %u bytes %s\n", + (unsigned)total, (total > 0) ? "OK" : "FAIL"); + + return (total > 0) ? RT_EOK : -RT_ERROR; +} + +``` + +测试结果: + +![NS800RT7P65 Uart模块测评-image-7.png](./figures/uart-张海涛-12.png) + +#### 4.2 Uart Menuconfig 功能配置测评 + +1. 在Menuconfig中将Uart2 \~ 4均打开,测试Uart功能; + +![NS800RT7P65 Uart模块测评-image-8.png](./figures/uart-张海涛-13.png) + +* 可以看到配置完成后rtconfig.h中新增下列宏定义; + +![NS800RT7P65 Uart模块测评-image-9.png](./figures/uart-张海涛-14.png) + +* 此时再次刷写测试,发现所有Uart Device均被register; + +* 由于当前题主仅有一个usb转串口工具,因此依次将console映射到不同Uart进行测试;测试结果均无问题; + +![NS800RT7P65 Uart模块测评-image-10.png](./figures/uart-张海涛-15.png.webp) + +### 5. Uart 代码优化 + +针对板级驱动中的部分 **HardCode** 考虑可以进行优化,增加BSP包的 **鲁棒性** ;将目前 HardCode 部分映射到 Menuconfig 配置中,用户可以 **直接通过 Menuconfig 配置** Uart 的停止位和波特率等; + +PR: https://github.com/RT-Thread/rt-thread/pull/11423 + +#### 5.1 串口基础配置项 - 优化 + +drv_uart.c + +```c +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-10-30 SummerGift first version + * 2020-03-16 SummerGift add device close feature + * 2020-03-20 SummerGift fix bug caused by ORE + * 2026-05-10 Codex Reworked driver around BSP UART config tables + */ + +#include "board.h" +#include "drv_uart.h" +#include "drv_config.h" + +#ifdef RT_USING_SERIAL + +#define DRV_DEBUG +#define LOG_TAG "drv.uart" +#include + +#if !defined(BSP_USING_UART1) && !defined(BSP_USING_UART2) && !defined(BSP_USING_UART3) && \ + !defined(BSP_USING_UART4) +#error "Please define at least one BSP_USING_UARTx" +#endif + +#ifndef BSP_NS800_UART_TX_TIMEOUT +#define BSP_NS800_UART_TX_TIMEOUT 6000 +#endif + +#ifdef RT_USING_SERIAL_V2 +#define NS800_UART_BUF_CONFIG(_rxbuf, _txbuf) \ + .rx_bufsz = (_rxbuf), \ + .tx_bufsz = (_txbuf), +#define NS800_UART_DEFAULT_TX_TIMEOUT 0U +#else +#ifndef RT_SERIAL_RB_BUFSZ +#define RT_SERIAL_RB_BUFSZ 64 +#endif + +#define NS800_UART_BUF_CONFIG(_rxbuf, _txbuf) \ + .rx_bufsz = RT_SERIAL_RB_BUFSZ, \ + .tx_bufsz = 0U, +#define NS800_UART_DEFAULT_TX_TIMEOUT BSP_NS800_UART_TX_TIMEOUT +#endif + +/* Default UART configuration from Kconfig */ +#ifndef BSP_UART_DEFAULT_BAUDRATE +#define BSP_UART_DEFAULT_BAUDRATE 115200 +#endif + +/* Data bits mapping */ +#if defined(BSP_UART_DATABITS_5) +#define NS800_UART_DEFAULT_DATA_BITS DATA_BITS_5 +#elif defined(BSP_UART_DATABITS_6) +#define NS800_UART_DEFAULT_DATA_BITS DATA_BITS_6 +#elif defined(BSP_UART_DATABITS_7) +#define NS800_UART_DEFAULT_DATA_BITS DATA_BITS_7 +#elif defined(BSP_UART_DATABITS_9) +#define NS800_UART_DEFAULT_DATA_BITS DATA_BITS_9 +#else +#define NS800_UART_DEFAULT_DATA_BITS DATA_BITS_8 +#endif + +/* Stop bits mapping */ +#ifdef BSP_UART_STOPBITS_2 +#define NS800_UART_DEFAULT_STOP_BITS STOP_BITS_2 +#else +#define NS800_UART_DEFAULT_STOP_BITS STOP_BITS_1 +#endif + +enum +{ +#ifdef BSP_USING_UART1 + UART1_INDEX, +#endif +#ifdef BSP_USING_UART2 + UART2_INDEX, +#endif +#ifdef BSP_USING_UART3 + UART3_INDEX, +#endif +#ifdef BSP_USING_UART4 + UART4_INDEX, +#endif +}; + +#ifdef BSP_USING_UART1 +void UART1_IRQHandler(void); +#endif +#ifdef BSP_USING_UART2 +void UART2_IRQHandler(void); +#endif +#ifdef BSP_USING_UART3 +void UART3_IRQHandler(void); +#endif +#ifdef BSP_USING_UART4 +void UART4_IRQHandler(void); +#endif + +static struct ns800_uart_config uart_config[] = +{ +#ifdef BSP_USING_UART1 + { + .name = "uart1", + .Instance = UART1, + .rx_irq_type = UART1_RX_IRQn, + .tx_irq_type = UART1_TX_IRQn, + .irq_handler = UART1_IRQHandler, + .rx_port = GPIOA, + .rx_pin = GPIO_PIN_13, + .rx_mux = ALT6_FUNCTION, + .rx_pad = GPIO_PIN_TYPE_PULLUP, + .rx_direction = GPIO_DIR_MODE_IN, + .rx_drive_max = RT_FALSE, + .tx_port = GPIOA, + .tx_pin = GPIO_PIN_12, + .tx_mux = ALT6_FUNCTION, + .tx_pad = GPIO_PIN_TYPE_STD, + .tx_direction = GPIO_DIR_MODE_OUT, + .tx_drive_max = RT_FALSE, + .tx_block_timeout = NS800_UART_DEFAULT_TX_TIMEOUT, + NS800_UART_BUF_CONFIG(BSP_UART1_RX_BUFSIZE, BSP_UART1_TX_BUFSIZE) + }, +#endif +#ifdef BSP_USING_UART2 + { + .name = "uart2", + .Instance = UART2, + .rx_irq_type = UART2_RX_IRQn, + .tx_irq_type = UART2_TX_IRQn, + .irq_handler = UART2_IRQHandler, + .rx_port = GPIOB, + .rx_pin = GPIO_PIN_7, + .rx_mux = ALT1_FUNCTION, + .rx_pad = GPIO_PIN_TYPE_PULLUP, + .rx_direction = GPIO_DIR_MODE_IN, + .rx_drive_max = RT_FALSE, + .tx_port = GPIOB, + .tx_pin = GPIO_PIN_6, + .tx_mux = ALT1_FUNCTION, + .tx_pad = GPIO_PIN_TYPE_STD, + .tx_direction = GPIO_DIR_MODE_OUT, + .tx_drive_max = RT_TRUE, + .tx_block_timeout = NS800_UART_DEFAULT_TX_TIMEOUT, + NS800_UART_BUF_CONFIG(BSP_UART2_RX_BUFSIZE, BSP_UART2_TX_BUFSIZE) + }, +#endif +#ifdef BSP_USING_UART3 + { + .name = "uart3", + .Instance = UART3, + .rx_irq_type = UART3_RX_IRQn, + .tx_irq_type = UART3_TX_IRQn, + .irq_handler = UART3_IRQHandler, + .rx_port = GPIOB, + .rx_pin = GPIO_PIN_25, + .rx_mux = ALT11_FUNCTION, + .rx_pad = GPIO_PIN_TYPE_PULLUP, + .rx_direction = GPIO_DIR_MODE_IN, + .rx_drive_max = RT_FALSE, + .tx_port = GPIOB, + .tx_pin = GPIO_PIN_24, + .tx_mux = ALT11_FUNCTION, + .tx_pad = GPIO_PIN_TYPE_STD, + .tx_direction = GPIO_DIR_MODE_OUT, + .tx_drive_max = RT_TRUE, + .tx_block_timeout = NS800_UART_DEFAULT_TX_TIMEOUT, + NS800_UART_BUF_CONFIG(BSP_UART3_RX_BUFSIZE, BSP_UART3_TX_BUFSIZE) + }, +#endif +#ifdef BSP_USING_UART4 + { + .name = "uart4", + .Instance = UART4, + .rx_irq_type = UART4_RX_IRQn, + .tx_irq_type = UART4_TX_IRQn, + .irq_handler = UART4_IRQHandler, + .rx_port = GPIOB, + .rx_pin = GPIO_PIN_13, + .rx_mux = ALT7_FUNCTION, + .rx_pad = GPIO_PIN_TYPE_PULLUP, + .rx_direction = GPIO_DIR_MODE_IN, + .rx_drive_max = RT_FALSE, + .tx_port = GPIOB, + .tx_pin = GPIO_PIN_12, + .tx_mux = ALT7_FUNCTION, + .tx_pad = GPIO_PIN_TYPE_STD, + .tx_direction = GPIO_DIR_MODE_OUT, + .tx_drive_max = RT_TRUE, + .tx_block_timeout = NS800_UART_DEFAULT_TX_TIMEOUT, + NS800_UART_BUF_CONFIG(BSP_UART4_RX_BUFSIZE, BSP_UART4_TX_BUFSIZE) + }, +#endif +}; + +static struct ns800_uart uart_obj[sizeof(uart_config) / sizeof(uart_config[0])] = {0}; + +static void ns800_uart_gpio_init(const struct ns800_uart_config *config) +{ + RT_ASSERT(config != RT_NULL); + + GPIO_setPinConfig(config->rx_port, config->rx_pin, config->rx_mux); + GPIO_setAnalogMode(config->rx_port, config->rx_pin, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(config->rx_port, config->rx_pin, config->rx_pad); + GPIO_setQualificationMode(config->rx_port, config->rx_pin, GPIO_QUAL_SYNC); + GPIO_setDirectionMode(config->rx_port, config->rx_pin, config->rx_direction); + if (config->rx_drive_max) + { + GPIO_setDriveLevel(config->rx_port, config->rx_pin, GPIO_DRV_MAX); + } + + GPIO_setPinConfig(config->tx_port, config->tx_pin, config->tx_mux); + GPIO_setAnalogMode(config->tx_port, config->tx_pin, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(config->tx_port, config->tx_pin, config->tx_pad); + GPIO_setQualificationMode(config->tx_port, config->tx_pin, GPIO_QUAL_SYNC); + GPIO_setDirectionMode(config->tx_port, config->tx_pin, config->tx_direction); + if (config->tx_drive_max) + { + GPIO_setDriveLevel(config->tx_port, config->tx_pin, GPIO_DRV_MAX); + } +} + +static UART_BitCountPerChar ns800_uart_data_bits(rt_uint32_t data_bits) +{ + switch (data_bits) + { + case DATA_BITS_7: + return UART_7_BITS_PER_CHAR; + case DATA_BITS_9: + return UART_9_BITS_PER_CHAR; + case DATA_BITS_8: + default: + return UART_8_BITS_PER_CHAR; + } +} + +static void ns800_uart_apply_runtime_cfg(struct ns800_uart *uart, struct serial_configure *cfg) +{ + RT_ASSERT(uart != RT_NULL); + RT_ASSERT(cfg != RT_NULL); + + uart->handle.Instance = uart->config->Instance; + uart->handle.baud_rate = cfg->baud_rate; + uart->handle.data_bits = cfg->data_bits; + uart->handle.stop_bits = cfg->stop_bits; + uart->handle.parity = cfg->parity; +} + +static void ns800_uart_clear_errors(UART_TypeDef *instance) +{ + UART_clearErrorFlags(instance, + UART_STAT_OR_M | + UART_STAT_NF_M | + UART_STAT_FE_M | + UART_STAT_PF_M); +} + +static rt_err_t ns800_configure(struct rt_serial_device *serial, struct serial_configure *cfg) +{ + struct ns800_uart *uart; + UART_BitCountPerChar bit_count; + + RT_ASSERT(serial != RT_NULL); + RT_ASSERT(cfg != RT_NULL); + + uart = rt_container_of(serial, struct ns800_uart, serial); + + ns800_uart_apply_runtime_cfg(uart, cfg); + ns800_uart_gpio_init(uart->config); + + bit_count = ns800_uart_data_bits(cfg->data_bits); + + UART_resetModule(uart->handle.Instance); + UART_setBaud(uart->handle.Instance, cfg->baud_rate); + + if (cfg->parity == PARITY_ODD) + { + UART_setBitCountPerChar(uart->handle.Instance, bit_count, true); + UART_setParityMode(uart->handle.Instance, UART_PAR_ODD); + } + else if (cfg->parity == PARITY_EVEN) + { + UART_setBitCountPerChar(uart->handle.Instance, bit_count, true); + UART_setParityMode(uart->handle.Instance, UART_PAR_EVEN); + } + else + { + UART_setBitCountPerChar(uart->handle.Instance, bit_count, false); + } + + switch (cfg->stop_bits) + { + case STOP_BITS_2: + UART_setStopBitCount(uart->handle.Instance, UART_TWO_STOP_BIT); + break; + case STOP_BITS_1: + default: + UART_setStopBitCount(uart->handle.Instance, UART_ONE_STOP_BIT); + break; + } + + ns800_uart_clear_errors(uart->handle.Instance); + +#ifdef RT_USING_SERIAL_V2 + UART_enableTxFifo(uart->handle.Instance); + UART_resetTxFifo(uart->handle.Instance); + UART_setTxFifoWatermark(uart->handle.Instance, UART_FIFO_TX6); + + UART_enableRxFifo(uart->handle.Instance); + UART_resetRxFifo(uart->handle.Instance); + UART_setRxFifoWatermark(uart->handle.Instance, UART_FIFO_RX1); + UART_setRxIdleCharacter(uart->handle.Instance, UART_IDLE_CHARACTER_CNT0); +#else + /* + * RT-Thread serial v1 consumes RX data byte-by-byte from getc(). + * Keep the hardware in the simplest non-FIFO mode to avoid RDRF + * reasserting on idle-partial FIFO conditions. + */ + UART_disableTxFifo(uart->handle.Instance); + UART_disableRxFifo(uart->handle.Instance); + UART_setRxIdleCharacter(uart->handle.Instance, UART_IDLE_CHARACTER_CNT0); +#endif + + UART_enableTxModule(uart->handle.Instance); + UART_enableRxModule(uart->handle.Instance); + + uart->tx_block_timeout = uart->config->tx_block_timeout; + + return RT_EOK; +} + +static rt_err_t ns800_control(struct rt_serial_device *serial, int cmd, void *arg) +{ + struct ns800_uart *uart; + + RT_ASSERT(serial != RT_NULL); + uart = rt_container_of(serial, struct ns800_uart, serial); + + switch (cmd) + { + case RT_DEVICE_CTRL_CLR_INT: + { + rt_uint32_t direction = (rt_uint32_t)arg; + + Interrupt_disable(uart->config->rx_irq_type); + if (direction == RT_DEVICE_FLAG_INT_RX) + { + UART_disableInterrupt(uart->handle.Instance, UART_INT_RX_DATA_REG_FULL); + } + else if (direction == RT_DEVICE_FLAG_INT_TX) + { + UART_disableInterrupt(uart->handle.Instance, UART_INT_TX_COMPLETE); + Interrupt_disable(uart->config->tx_irq_type); + } + break; + } + + case RT_DEVICE_CTRL_SET_INT: + { + rt_uint32_t direction = (rt_uint32_t)arg; + + if (direction == RT_DEVICE_FLAG_INT_RX) + { + UART_enableInterrupt(uart->handle.Instance, UART_INT_RX_DATA_REG_FULL); + Interrupt_register(uart->config->rx_irq_type, uart->config->irq_handler); + Interrupt_enable(uart->config->rx_irq_type); + } + else if (direction == RT_DEVICE_FLAG_INT_TX) + { + UART_enableInterrupt(uart->handle.Instance, UART_INT_TX_COMPLETE); + Interrupt_register(uart->config->tx_irq_type, uart->config->irq_handler); + Interrupt_enable(uart->config->tx_irq_type); + } + break; + } + + case RT_DEVICE_CTRL_CLOSE: + UART_disableTxModule(uart->handle.Instance); + UART_disableRxModule(uart->handle.Instance); + break; + + case UART_CTRL_SET_BLOCK_TIMEOUT: + { + rt_uint32_t block_timeout = (rt_uint32_t)arg; + + if (block_timeout == 0U) + { + return -RT_ERROR; + } + + uart->tx_block_timeout = block_timeout; + break; + } + + default: + break; + } + + return RT_EOK; +} + +static int ns800_putc(struct rt_serial_device *serial, char c) +{ + struct ns800_uart *uart; + rt_uint32_t block_timeout; + + RT_ASSERT(serial != RT_NULL); + + uart = rt_container_of(serial, struct ns800_uart, serial); + block_timeout = uart->tx_block_timeout; + + while (!UART_isSpaceAvailable(uart->handle.Instance)) + { + if (block_timeout-- == 0U) + { + return -1; + } + } + + UART_writeChar(uart->handle.Instance, (rt_uint8_t)c); + + while ((uart->handle.Instance->STAT.BIT.TC == false) && (--block_timeout != 0U)) + { + } + + return (block_timeout != 0U) ? 1 : -1; +} + +static int ns800_getc(struct rt_serial_device *serial) +{ + struct ns800_uart *uart; + + RT_ASSERT(serial != RT_NULL); + uart = rt_container_of(serial, struct ns800_uart, serial); + + if (UART_isDataAvailable(uart->handle.Instance)) + { + return (int)UART_readChar(uart->handle.Instance); + } + + return -1; +} + +static void uart_isr(struct rt_serial_device *serial) +{ + struct ns800_uart *uart; + + RT_ASSERT(serial != RT_NULL); + uart = rt_container_of(serial, struct ns800_uart, serial); + + if (UART_getStatusFlag(uart->handle.Instance, UART_RX_OVERRUN) || + UART_getStatusFlag(uart->handle.Instance, UART_NOISE_DETECT) || + UART_getStatusFlag(uart->handle.Instance, UART_FRAME_ERR) || + UART_getStatusFlag(uart->handle.Instance, UART_PARITY_ERR)) + { + ns800_uart_clear_errors(uart->handle.Instance); + } + + if (UART_getStatusFlag(uart->handle.Instance, UART_RX_DATA_REG_FULL)) + { + rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_IND); + } + else if (UART_getStatusFlag(uart->handle.Instance, UART_TX_COMPLETE)) + { + UART_disableInterrupt(uart->handle.Instance, UART_INT_TX_COMPLETE); + rt_hw_serial_isr(serial, RT_SERIAL_EVENT_TX_DONE); + } +} + +#ifdef BSP_USING_UART1 +void UART1_IRQHandler(void) +{ + rt_interrupt_enter(); + uart_isr(&uart_obj[UART1_INDEX].serial); + rt_interrupt_leave(); +} +#endif + +#ifdef BSP_USING_UART2 +void UART2_IRQHandler(void) +{ + rt_interrupt_enter(); + uart_isr(&uart_obj[UART2_INDEX].serial); + rt_interrupt_leave(); +} +#endif + +#ifdef BSP_USING_UART3 +void UART3_IRQHandler(void) +{ + rt_interrupt_enter(); + uart_isr(&uart_obj[UART3_INDEX].serial); + rt_interrupt_leave(); +} +#endif + +#ifdef BSP_USING_UART4 +void UART4_IRQHandler(void) +{ + rt_interrupt_enter(); + uart_isr(&uart_obj[UART4_INDEX].serial); + rt_interrupt_leave(); +} +#endif + +static void ns800_uart_fill_default_config(struct serial_configure *config, + const struct ns800_uart_config *hw) +{ + RT_ASSERT(config != RT_NULL); + RT_ASSERT(hw != RT_NULL); + + *config = (struct serial_configure)RT_SERIAL_CONFIG_DEFAULT; + + /* Override with Kconfig settings */ + config->baud_rate = BSP_UART_DEFAULT_BAUDRATE; + config->data_bits = NS800_UART_DEFAULT_DATA_BITS; + config->stop_bits = NS800_UART_DEFAULT_STOP_BITS; + +#ifdef RT_USING_SERIAL_V2 + config->rx_bufsz = hw->rx_bufsz; + config->tx_bufsz = hw->tx_bufsz; +#else + config->bufsz = hw->rx_bufsz; +#endif +} + +static const struct rt_uart_ops ns800_uart_ops = +{ + .configure = ns800_configure, + .control = ns800_control, + .putc = ns800_putc, + .getc = ns800_getc, +}; + +int rt_hw_uart_init(void) +{ + rt_err_t result = RT_EOK; + rt_size_t i; + + for (i = 0; i < sizeof(uart_obj) / sizeof(uart_obj[0]); i++) + { + uart_obj[i].config = &uart_config[i]; + uart_obj[i].serial.ops = &ns800_uart_ops; + ns800_uart_fill_default_config(&uart_obj[i].serial.config, uart_obj[i].config); + uart_obj[i].tx_block_timeout = uart_obj[i].config->tx_block_timeout; + + result = rt_hw_serial_register(&uart_obj[i].serial, + uart_obj[i].config->name, + RT_DEVICE_FLAG_RDWR | + RT_DEVICE_FLAG_INT_RX | + RT_DEVICE_FLAG_INT_TX, + RT_NULL); + RT_ASSERT(result == RT_EOK); + } + + return result; +} + +#endif /* RT_USING_SERIAL */ + +``` + +Kconfig + +```go +menu "On-chip Peripheral Drivers" + + menuconfig BSP_USING_GPIO + bool "Enable GPIO" + select RT_USING_PIN + default y + if BSP_USING_GPIO + config BSP_GPIO_PIN_IRQ + bool "Enable GPIO pin IRQ hooks" + default n + endif + + menuconfig BSP_USING_UART + bool "Enable UART" + default y + select RT_USING_SERIAL + if BSP_USING_UART + config BSP_NS800_UART_TX_TIMEOUT + int "UART TX timeout" + default 6000 + depends on !RT_USING_SERIAL_V2 + + config BSP_UART_DEFAULT_BAUDRATE + int "UART default baudrate" + default 115200 + help + Default baudrate for all UART ports. + + choice BSP_UART_DEFAULT_DATABITS + prompt "UART default data bits" + default BSP_UART_DATABITS_8 + config BSP_UART_DATABITS_5 + bool "5 bits" + config BSP_UART_DATABITS_6 + bool "6 bits" + config BSP_UART_DATABITS_7 + bool "7 bits" + config BSP_UART_DATABITS_8 + bool "8 bits" + config BSP_UART_DATABITS_9 + bool "9 bits" + endchoice + + choice BSP_UART_DEFAULT_STOPBITS + prompt "UART default stop bits" + default BSP_UART_STOPBITS_1 + config BSP_UART_STOPBITS_1 + bool "1 bit" + config BSP_UART_STOPBITS_2 + bool "2 bits" + endchoice + + menuconfig BSP_USING_UART1 + bool "Enable UART1" + default y + if BSP_USING_UART1 + config BSP_UART1_RX_BUFSIZE + int "UART1 RX buffer size" + range 64 65535 + depends on RT_USING_SERIAL_V2 + default 256 + + config BSP_UART1_TX_BUFSIZE + int "UART1 TX buffer size" + range 0 65535 + depends on RT_USING_SERIAL_V2 + default 0 + endif + + menuconfig BSP_USING_UART2 + bool "Enable UART2" + default n + if BSP_USING_UART2 + config BSP_UART2_RX_BUFSIZE + int "UART2 RX buffer size" + range 64 65535 + depends on RT_USING_SERIAL_V2 + default 256 + + config BSP_UART2_TX_BUFSIZE + int "UART2 TX buffer size" + range 0 65535 + depends on RT_USING_SERIAL_V2 + default 0 + endif + + menuconfig BSP_USING_UART3 + bool "Enable UART3" + default n + if BSP_USING_UART3 + config BSP_UART3_RX_BUFSIZE + int "UART3 RX buffer size" + range 64 65535 + depends on RT_USING_SERIAL_V2 + default 256 + + config BSP_UART3_TX_BUFSIZE + int "UART3 TX buffer size" + range 0 65535 + depends on RT_USING_SERIAL_V2 + default 0 + endif + + menuconfig BSP_USING_UART4 + bool "Enable UART4" + default n + if BSP_USING_UART4 + config BSP_UART4_RX_BUFSIZE + int "UART4 RX buffer size" + range 64 65535 + depends on RT_USING_SERIAL_V2 + default 256 + + config BSP_UART4_TX_BUFSIZE + int "UART4 TX buffer size" + range 0 65535 + depends on RT_USING_SERIAL_V2 + default 0 + endif + endif + + menuconfig BSP_USING_ECAP + bool "Enable ECAP" + default n + + menuconfig BSP_USING_CAN + bool "Enable CAN" + select RT_USING_CAN + default n + if BSP_USING_CAN + config BSP_USING_CANFD1 + bool "Enable CANFD1" + select RT_CAN_USING_CANFD + default n + + endif + +endmenu + +``` + +#### 5.2 串口低波特率时 - 丢失串口数据 + +* 新增 `ns800_wait_tx_space()` + + * 用 `rt_tick_from_millisecond()` + `rt_tick_get()` 做真实时间超时等待 TX 可写 + +* 修改 `ns800_putc()` + + * 先调用 `ns800_wait_tx_space()`,可写后直接 `UART_writeChar()` + + * 去掉“每个字节都等待 `TC` 置位”的逻辑(这在低波特率下最容易导致截断) + +解决方案: + +* 之前是“循环次数”超时,CPU快/波特率低时很容易提前超时,且每字节强行等待 `TC` 会把发送路径变得非常脆弱,低波特率下更明显。 + +* 现在按系统 tick 等待 TX 空位,低波特率也能稳定逐字节输出。 + +### 6. 后续可优化点 + +1. 将波特率和停止位等 **基础配置** 信息,分别映射到 **每一路Uart** 上,而不是所有Uart共用; + +2. 后续可以考虑 **是否可以** 将 **引脚复用** 和 **引脚配置** 也映射到 **Menuconfig** 中; + +## 二、纳芯微NS800RT7P65D上的GPIO实践(熊治坤) + +作者:熊治坤 + +原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的GPIO实践 + +源文章: + +### 硬件 + +我的硬件版本是1.3,如下图所示: + +![screenshot_image.png](./figures/gpio-熊治坤-01.png.webp) + +不同的版本,硬件会有所不同,需要特别注意。 + +### 源代码下载 + +由于我之前已经下载过,需要进行更新即可。 + +git pull命令就行。 + +![screenshot_image.png](./figures/gpio-熊治坤-02.png) + +会提示是否最新。 + +### MDK工程生成 + +#### ENV进行GPIO配置 + +![screenshot_image.png](./figures/gpio-熊治坤-03.png.webp) + +这里为了验证中断功能。 + +#### ENV串口使能 + +![screenshot_image.png](./figures/gpio-熊治坤-04.png) + +#### ENV配置保存,选择Y + +![screenshot_image.png](./figures/gpio-熊治坤-05.png.webp) + +#### ENV生成MDK工程 + +配置完成后,会自动生成工程文件 + +![screenshot_image.png](./figures/gpio-熊治坤-06.png.webp) + +这里成功后会有如上提示。 + +这里可以从文件时间可以查看是否是最新生成的 + +![screenshot_image.png](./figures/gpio-熊治坤-07.png) + +### 代码编写与调试 + +打开工程后进行编译 + +成功后会有如下提示 + +![screenshot_image.png](./figures/gpio-熊治坤-08.png) + +下面需要对如下三个文件进行修改 + +![screenshot_image.png](./figures/gpio-熊治坤-09.png) + +GPIO部分代码较长,这里不展示。后面根据GPIO验证,对main文件进行说明。 + +#### GPIO验证 + +这里根据板子上的LED1、LED2、KEY 1进行测试 + +对应的GPIO如下原理图所示 + +![screenshot_image.png](./figures/gpio-熊治坤-10.png.webp) + +实现功能如下: + +LED1,1秒钟闪烁1次。 + +LED2,根据KEY1的按下的状态,进行翻转。 + +KEY1通过中断,进行串口输出和LED2转态翻转 + +主代码实现 + +``` +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-05-06 Jiawei.Deng first version + */ + +#include +#include +#include + +/* defined the LED1 pin: GPIO_68 = PC4 */ +#define LED1_PIN PIN_NUM(GPIO_68) +#define LED2_PIN PIN_NUM(GPIO_69) +#define KEY1_PIN PIN_NUM(GPIO_41) + +void keydown(void *args) +{ + rt_kprintf("KEY1 Press Down!\n"); + rt_pin_write(LED2_PIN, !rt_pin_read(LED2_PIN)); +} + +int main(void) +{ + rt_base_t level; + level = rt_hw_interrupt_disable(); + rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT); + rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT); + + /* 按键1引脚为输入模式 */ + rt_pin_mode(KEY1_PIN, PIN_MODE_INPUT_PULLUP); + /* 绑定中断,下降沿模式,回调函数名为keydown */ + if(rt_pin_attach_irq(KEY1_PIN, PIN_IRQ_MODE_FALLING, keydown, RT_NULL) == RT_EOK) + { + rt_kprintf("bangding chenggong!\n"); + } + /* 使能中断 */ + if(rt_pin_irq_enable(KEY1_PIN, PIN_IRQ_ENABLE)== RT_EOK) + { + rt_kprintf("shineng chenggong!\n"); + } + + rt_hw_interrupt_enable(level); + while (1) + { +/* rt_kprintf("\r\n led1_thread_entry running! \r\n"); */ + rt_pin_write(LED1_PIN, PIN_HIGH); + rt_thread_mdelay(1000); + rt_pin_write(LED1_PIN, PIN_LOW); + rt_thread_mdelay(1000); + + } +} +``` + +串口输出情况,说明中断有效 + +![screenshot_image.png](./figures/gpio-熊治坤-11.png.webp) + +LED1、2运行如下 + +![screenshot_image.png](./figures/gpio-熊治坤-12.png.webp) + +![screenshot_image.png](./figures/gpio-熊治坤-13.png.webp) + +### 总结 + +基于板上的GPIO进行了读写测试,完成了GPIO的验证。对这个芯片有了进一步的了解和认识。 + +## 三、纳芯微NS800RT7P65D上的ADC实践(朱玉施) + +作者:朱玉施 + +原文标题:【纳芯微NS800RT7P65D】基于 RT-Thread 的 ADC 外设及其拓展的移植与应用 + +源文章: + +### 1. 实践目标 + +在本工程/博客中,解决了以下几个问题: + +1. 基于NS800的SDK,实现RT-Thread的ADC外设移植与测试 +2. 实现了ADC拓展外设:irq中断,PPB后处理模块的移植与测试 +3. 梳理实现思路,即“如何从基础SDK到RT-Thread ADC框架的移植” + +![screenshot_image.png](./figures/adc-朱玉施-01.png.webp) + +### 2. 背景与目标 + +本次实践的目标是在 RT-Thread 主线 BSP `bsp/novosns/ns800` 中补充 NS800RT7P65D 的 ADC 驱动支持,并在本地 demo 工程中完成基础功能验证。最终 PR 侧代码以主线仓库中的如下文件为准: + +* `bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_adc.c` +* `bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_adc.h` +* `bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig` + +### 3. 资料收集与代码参考 + +#### 3.1 RT-Thread ADC 设备模型 + +RT-Thread ADC 设备模型的核心接口是 `rt_adc_ops`: + +``` +static const struct rt_adc_ops ns800_adc_ops = +{ + ns800_adc_enabled, + ns800_adc_convert, + ns800_adc_get_resolution, + ns800_adc_get_vref, +}; +``` + +这四个函数分别对应: + +* `enabled`:使能或关闭某个 ADC 通道。 +* `convert`:执行一次通道转换并返回结果。 +* `get_resolution`:返回 ADC 分辨率。 +* `get_vref`:返回参考电压。 + +来自官方ADC设备接口 + +![screenshot_image.png](./figures/adc-朱玉施-02.png.webp) + +驱动通过 `rt_hw_adc_register()` 注册为 RT-Thread ADC 设备: + +``` +result = rt_hw_adc_register(&adc_obj.device, + adc_obj.config->name, + &ns800_adc_ops, + &adc_obj); +``` + +注册后,应用层可通过设备名 `adc0` 找到该设备,并使用 RT-Thread 标准 ADC API 读取通道。 + +#### 3.2 NS800 HAL DriverLib 资料 + +ADC 底层操作来自 `bsp/novosns/ns800/libraries` 下的 HAL/DriverLib。驱动主要使用这些底层能力: + +* `ADC_setVREF()`:设置 ADC 参考电压。 +* `ADC_setPrescaler()`:设置 ADC 时钟分频。 +* `ADC_setInterruptPulsePosMode()`:设置 EOC 中断脉冲位置。 +* `ADC_enableConverter()`:使能 ADC 转换器。 +* `ADC_setupSOC()`:配置 SOC 采样序列。 +* `ADC_forceMultipleSOC()`:软件触发指定 SOC。 +* `ADC_readResult()`:读取普通转换结果。 +* `ADC_setupPPB()`:绑定 PPB 与 SOC。 +* `ADC_configureRepeater()`:配置 repeater oversampling。 +* `ADC_forceRepeaterTrigger()`:触发 repeater 采样。 +* `ADC_readPPBSum()`、`ADC_readPPBMin()`、`ADC_readPPBMax()`:读取 PPB 统计结果。 +* `Interrupt_register()`、`Interrupt_enable()`、`Interrupt_disable()`:注册和控制 ADC 中断。 + +### 4. 驱动代码设计思路 + +#### 4.1 文件边界 + +PR 中新增两个驱动文件: + +* `drv_adc.h`:公开 RT-Thread 侧需要使用的 ADC 控制命令、PPB/IRQ 常量、IRQ 回调结构,以及 `rt_hw_adc_init()`。 +* `drv_adc.c`:保存所有 NS800 ADC 内部状态、硬件配置、SOC 配置、PPB oversampling、中断绑定和 RT-Thread ops。 + +头文件保持很薄: + +``` +#define NS800_ADC_CHANNEL_MAX 32U +#define NS800_ADC_SOC_MAX 32U +#define NS800_ADC_INT_MAX 4U +#define NS800_ADC_PPB_MAX 4U + +#define NS800_ADC_CMD_DISABLE_EXT (RT_DEVICE_CTRL_BASE(ADC) + 0x10) +#define NS800_ADC_CMD_ENABLE_PPB (RT_DEVICE_CTRL_BASE(ADC) + 0x11) +#define NS800_ADC_CMD_ENABLE_IRQ (RT_DEVICE_CTRL_BASE(ADC) + 0x12) +#define NS800_ADC_CMD_SET_CALLBACK (RT_DEVICE_CTRL_BASE(ADC) + 0x13) + +typedef void (*ns800_adc_irq_callback_t)(void *user_data); + +struct ns800_adc_callback +{ + ns800_adc_irq_callback_t callback; + void *user_data; +}; + +int rt_hw_adc_init(void); +``` + +这样不需要知道内部配置结构与具体实现也能快速调用。 + +#### 4.2 静态配置表 + +ADC 驱动使用 `struct ns800_adc_config` 描述硬件实例: + +``` +struct ns800_adc_config +{ + const char *name; + ADC_TypeDef *instance; + ADCRESULT_TypeDef *result; + IRQn_Type conv_irq; + ADC_ClkPrescale clock_prescaler; + rt_uint8_t resolution_bits; + ADC_PulsePosMode eoc_pulse_pos; + ADC_Trigger trigger; + rt_bool_t continuous; +}; +``` + +当前 PR 只打开一个 RT-Thread ADC 设备 `adc0`,映射到 NS800 的 `ADCA`: + +``` +static const struct ns800_adc_config adc_config[] = +{ +#ifdef BSP_USING_ADC + { + .name = "adc0", + .instance = ADCA, + .result = ADCARESULT, + .conv_irq = ADCA_CONV_IRQn, + .clock_prescaler = ADC_CLK_DIV_4, + .resolution_bits = NS800_ADC_RESOLUTION_BITS, + .eoc_pulse_pos = ADC_PULSE_END_OF_CONV, + .trigger = ADC_TRIGGER_SW_ONLY, + .continuous = RT_FALSE, + }, +#endif +}; +``` + +`adc0` 内部状态由 `struct ns800_adc` 保存: + +``` +struct ns800_adc +{ + struct rt_adc_device device; + const struct ns800_adc_config *config; + const struct ns800_adc_pin_config *pins; + rt_uint32_t pin_count; + rt_uint32_t enabled_mask; + enum ns800_adc_mode mode; + rt_uint32_t active_channel; +}; +``` + +其中: + +* `device` 是 RT-Thread ADC 设备对象。 +* `config` 指向静态硬件配置。 +* `pins` 和 `pin_count` 描述 ADC 输入 GPIO。 +* `enabled_mask` 记录哪些 ADC 通道已使能。 +* `mode` 记录当前读取模式:普通、PPB、IRQ。 +* `active_channel` 记录当前活跃通道,IRQ 模式依赖该字段绑定 EOC SOC。 + +#### 4.3 GPIO 与 ADC 输入声明 + +ADC 输入引脚采用静态数组声明: + +``` +static const struct ns800_adc_pin_config adc_pins[] = +{ + {GPIOH, GPIO_PIN_0, ALT0_FUNCTION}, + {GPIOH, GPIO_PIN_1, ALT0_FUNCTION}, + {GPIOH, GPIO_PIN_2, ALT0_FUNCTION}, + {GPIOH, GPIO_PIN_3, ALT0_FUNCTION}, + {GPIOH, GPIO_PIN_4, ALT0_FUNCTION}, + {GPIOH, GPIO_PIN_5, ALT0_FUNCTION}, + {GPIOH, GPIO_PIN_6, ALT0_FUNCTION}, + {GPIOH, GPIO_PIN_7, ALT0_FUNCTION}, +}; +``` + +初始化时统一配置为模拟输入: + +``` +GPIO_setPinConfig(adc->pins[index].port, + adc->pins[index].pin, + adc->pins[index].mux); +GPIO_setAnalogMode(adc->pins[index].port, + adc->pins[index].pin, + GPIO_ANALOG_ENABLED); +GPIO_setPadConfig(adc->pins[index].port, + adc->pins[index].pin, + GPIO_PIN_TYPE_STD); +GPIO_setQualificationMode(adc->pins[index].port, + adc->pins[index].pin, + GPIO_QUAL_ASYNC); +GPIO_setDirectionMode(adc->pins[index].port, + adc->pins[index].pin, + GPIO_DIR_MODE_IN); +``` + +这里需要注意:GPIO 声明必须显式存在,通道号描述 ADC 内部 channel,GPIO 配置描述芯片封装引脚复用,两者不是同一层概念。 + +#### 4.4 硬件初始化 + +硬件初始化集中在 `ns800_adc_hw_init()`: + +``` +ADC_setVREF(adc->config->instance, ADC_REFERENCE_INTERNAL, ADC_REFERENCE_3_3V); +ADC_setPrescaler(adc->config->instance, adc->config->clock_prescaler); +ADC_setInterruptPulsePosMode(adc->config->instance, adc->config->eoc_pulse_pos); +ADC_enableConverter(adc->config->instance); + +ADC_disableBurstMode(adc->config->instance); +ADC_setSOCPriority(adc->config->instance, ADC_PRI_ALL_ROUND_ROBIN); +ADC_disableContinuousMode(adc->config->instance, ADC_INT_NUMBER1); +ADC_disableInterrupt(adc->config->instance, ADC_INT_NUMBER1); +ADC_clearInterruptStatus(adc->config->instance, ADC_INT_NUMBER1); +ADC_clearInterruptOverflowStatus(adc->config->instance, ADC_INT_NUMBER1); +``` + +当前默认配置是: + +* 内部 3.3 V 参考电压。 +* ADC 时钟 4 分频。 +* 12 bit 分辨率。 +* EOC 位于转换结束。 +* 默认软件触发。 +* 默认非连续转换。 + +`adc_inited` 用于避免重复初始化。 + +#### 4.5 SOC 配置与普通读取 + +普通读取的核心流程是: + +1. 检查通道号是否合法。 +2. 将 channel 映射到同编号 SOC。 +3. 配置 SOC 为软件触发。 +4. 将 `ADC_INT_NUMBER1` 的中断源设置为该 SOC。 +5. 清 pending。 +6. `ADC_forceMultipleSOC()` 触发转换。 +7. 轮询等待 EOC。 +8. 读取结果寄存器。 +9. 清除 pending 和 overflow。 + +实现对应: + +``` +static rt_err_t ns800_adc_read_normal(struct ns800_adc *adc, + rt_uint32_t channel, + rt_uint32_t *value) +{ + if (ns800_adc_config_sw_soc(adc, channel) != RT_EOK) + { + return -RT_EINVAL; + } + + ns800_adc_clear(adc, ADC_INT_NUMBER1); + ADC_forceMultipleSOC(adc->config->instance, 1UL << channel); + if (ns800_adc_wait_done(adc) != RT_EOK) + { + return -RT_ETIMEOUT; + } + + *value = ADC_readResult(adc->config->result, (ADC_SOCNumber)channel); + ns800_adc_clear(adc, ADC_INT_NUMBER1); + + return RT_EOK; +} +``` + +#### 4.6 读取模式切换 + +驱动内部定义三种模式: + +``` +enum ns800_adc_mode +{ + NS800_ADC_MODE_NORMAL = 0, + NS800_ADC_MODE_PPB, + NS800_ADC_MODE_IRQ, +}; +``` + +`ns800_adc_convert()` 根据当前模式分派: + +``` +switch (adc->mode) +{ +case NS800_ADC_MODE_NORMAL: + return ns800_adc_read_normal(adc, adc_channel, value); + +case NS800_ADC_MODE_IRQ: + return ns800_adc_read_irq(adc, adc_channel, value); + +case NS800_ADC_MODE_PPB: + return ns800_adc_read_ppb(adc, adc_channel, value); + +default: + break; +} +``` + +这部分是驱动结构的关键点: + +1. 模式配置由 `control` 完成,读取由 `read/convert` 完成。 +2. 应用层切换模式后继续调用 `rt_adc_read()`,不需要直接接触 SOC、PPB 或中断寄存器。 + +#### 4.7 IRQ 模式 + +IRQ 模式使用 `NS800_ADC_CMD_ENABLE_IRQ` 进入。应用层可传入: + +``` +struct ns800_adc_callback +{ + ns800_adc_irq_callback_t callback; + void *user_data; +}; +``` + +驱动内部会: + +1. 保存 callback 和 user data。 +2. 根据 `active_channel` 配置软件触发 SOC。 +3. 将 `ADC_INT_NUMBER1` 绑定到该 SOC 的 EOC。 +4. 注册 `ADCA_CONV_IRQn` 的 ISR。 +5. 使能 ADC 中断与 NVIC 中断。 +6. 模式切换为 `NS800_ADC_MODE_IRQ`。 + +关键代码: + +``` +ret = ns800_adc_irq_attach(NS800_ADC_INT1, + adc->active_channel, + adc_irq_entries[NS800_ADC_INT1].callback, + adc_irq_entries[NS800_ADC_INT1].user_data); +if (ret == RT_EOK) +{ + adc->mode = NS800_ADC_MODE_IRQ; +} +``` + +ISR 中无论是否存在 callback,都先清 ADC pending: + +``` +if (ADC_getInterruptStatus(adc_obj.config->instance, (ADC_IntNumber)index) != 0U) +{ + ns800_adc_clear(&adc_obj, (ADC_IntNumber)index); + if (adc_irq_entries[index].callback != RT_NULL) + { + adc_irq_entries[index].callback(adc_irq_entries[index].user_data); + } +} +``` + +**调试过程中曾出现 `adc_pr_irq` 后再执行普通 `adc read` 卡死的问题,根因就是 IRQ 状态与 pending 处理被漏掉了。** + +现在 detach 时会关闭 ADC 中断、清 ADC pending、清 callback,并关闭 NVIC pending: + +``` +ADC_disableInterrupt(adc_obj.config->instance, (ADC_IntNumber)int_no); +ns800_adc_clear(&adc_obj, (ADC_IntNumber)int_no); +adc_irq_entries[int_no].callback = RT_NULL; +adc_irq_entries[int_no].user_data = RT_NULL; +Interrupt_disable(adc_obj.config->conv_irq); +NVIC_ClearPendingIRQ(adc_obj.config->conv_irq); +``` + +注意: + +使用 ADC IRQ 前,务必完成 **全局中断向量表初始化** (也是使用任意中断外设的前提条件),否则进入 IRQ 模式时会卡死。 + +#### 4.8 PPB oversampling 模式 + +**简介** + +这是一个新的ADC外设拓展,PPB(Post-Processing Block)后处理块 + +可以实现一系列常用的拓展功能,由硬件进行操作,降低算力负担 + +如: + +1. 过限检测(上下限) +2. 过零检测 +3. 偏置 +4. 误差计算 +5. oversamping(重复采样取均值) + +![screenshot_image.png](./figures/adc-朱玉施-03.png.webp) + +**使用** + +PPB 模式通过 `NS800_ADC_CMD_ENABLE_PPB` 打开。 + +默认行为是: + +* 使用 `PPB1`。 +* 使用 `SOC0`。 +* 对当前读取通道执行 oversampling。 +* 默认采样 8 次。 +* 默认 sample window 为 8。 +* 默认丢弃最大值和最小值后进行均值计算,得到计算结果。 +* `rt_adc_read()` 返回 PPB 处理后的值。 + +读取入口: + +``` +static rt_err_t ns800_adc_read_ppb(struct ns800_adc *adc, + rt_uint32_t channel, + rt_uint32_t *value) +{ + struct ns800_adc_ppb_oversampling_config ppb_config; + struct ns800_adc_ppb_oversampling_result ppb_result; + + rt_memset(&ppb_config, 0, sizeof(ppb_config)); + ppb_config.bind.ppb = NS800_ADC_PPB1; + ppb_config.bind.soc = 0U; + ppb_config.adc_channel = channel; + ppb_config.sample_count = NS800_ADC_DEFAULT_PPB_SAMPLES; + ppb_config.sample_window = NS800_ADC_SAMPLE_WINDOW; + ppb_config.drop_min_max = RT_TRUE; + + if (ns800_adc_ppb_oversampling_start(&ppb_config, &ppb_result) != RT_EOK) + { + return -RT_ERROR; + } + + *value = (rt_uint32_t)ppb_result.value; + + return RT_EOK; +} +``` + +PPB 内部流程: + +1. Reset repeater。 +2. 将 SOC 配置为 repeater trigger。 +3. `ADC_setupPPB()` 绑定 PPB 与 SOC。 +4. 配置 PPB trip limit、event 和 offset。 +5. 配置 repeater 为 oversampling mode。 +6. 设置 PPB count limit、compare source、OSINT source 和 shift。 +7. 触发 repeater。 +8. 读取 count、sum、min、max、min_index、max_index。 +9. 计算 average 或 trimmed average。 + +其中 shift 处理是一个实现细节:当采样次数是 2 的幂且用户没有显式给出 shift 时,驱动会自动取 `log2(sample_count)`。默认 8 次采样对应 shift 3。 + +``` +static rt_uint32_t ns800_adc_ppb_effective_shift(const struct ns800_adc_ppb_oversampling_config *config) +{ + if ((config->shift == 0U) && + (ns800_adc_ppb_is_power_of_two(config->sample_count) == RT_TRUE)) + { + return ns800_adc_ppb_log2(config->sample_count); + } + + return config->shift; +} +``` + +### 5. 应用代码编写 + +#### 5.1 普通读取测试 + +核心代码: + +``` +device = adc_pr_find_device(); +if (device == RT_NULL) +{ + return -RT_ERROR; +} + +//enable ADC设备与指定通道 +rt_adc_enable((rt_adc_device_t)device, channel); + +//调用read函数进行读取该设备与对应通道 +value = rt_adc_read((rt_adc_device_t)device, channel); + +rt_kprintf("adc_pr_read: channel=%u value=%u\r\n", + (unsigned int)channel, + (unsigned int)value); +``` + +普通读取用于验证最基础链路: + +``` +rt_adc_enable() + -> ns800_adc_enabled() + -> ns800_adc_hw_init() + -> 记录 enabled_mask / active_channel + +rt_adc_read() + -> ns800_adc_convert() + -> ns800_adc_read_normal() + -> ADC_setupSOC() + -> ADC_forceMultipleSOC() + -> 等待 EOC + -> ADC_readResult() +``` + +#### 5.2 IRQ 测试 + +测试代码中的回调: + +``` +//我们手动实现的回调函数 +static void adc_pr_irq_callback(void *user_data) +{ + RT_UNUSED(user_data); + rt_kprintf("adc irq callback test.\r\n"); + adc_pr_irq_count++; + + if (adc_pr_irq_sem_inited == RT_TRUE) + { + rt_sem_release(&adc_pr_irq_sem); + } +} +``` + +开启 IRQ 模式: + +``` +//配置回调函数接口 +struct ns800_adc_callback callback = +{ + .callback = adc_pr_irq_callback, + .user_data = (void *)(rt_ubase_t)channel, +}; + +//使能外设与通道 +rt_adc_enable((rt_adc_device_t)device, channel); + +//发布control信号,将adc外设配置为irq模式,并传入参数来绑定我们的回调函数与其参数 +result = rt_device_control(device, NS800_ADC_CMD_ENABLE_IRQ, &callback); + +if (result != RT_EOK) +{ + rt_kprintf("adc_pr_irq: enable irq failed %d\r\n", result); + return result; +} + +//发起read请求 +value = rt_adc_read((rt_adc_device_t)device, channel); + +//测试用:阻塞式等待中断执行完毕。落实到具体应用的话没必要等中断,adc的采样特别快,等中断会拖慢速度 +result = rt_sem_take(&adc_pr_irq_sem, rt_tick_from_millisecond(ADC_PR_TIMEOUT_MS)); + +//发布control信号,退出中断模式 +rt_device_control(device, NS800_ADC_CMD_DISABLE_EXT, RT_NULL); +``` + +IRQ 测试的调用路径: + +``` +rt_device_control(NS800_ADC_CMD_ENABLE_IRQ) + -> ns800_adc_set_mode(NS800_ADC_MODE_IRQ) + -> ns800_adc_config_sw_soc() + -> ns800_adc_irq_attach() + -> ADC_setInterruptSource() + -> ADC_enableInterrupt() + -> Interrupt_register() + -> Interrupt_enable() + +rt_adc_read() + -> ns800_adc_read_irq() + -> ADC_forceMultipleSOC() + -> 等待 ADC not busy + -> ADC_readResult() + +ISR + -> ns800_adc_conv_irq_handler() + -> ns800_adc_clear() + -> callback(user_data) + -> rt_sem_release() +``` + +测试结束后必须调用: + +``` +rt_device_control(device, NS800_ADC_CMD_DISABLE_EXT, RT_NULL); +``` + +否则设备会保持 IRQ 模式,后续普通 `adc read` 可能受中断状态影响。 + +当前驱动在 `DISABLE_EXT` 中恢复 normal 模式并 detach IRQ。 + +#### 5.3 PPB 测试 + +测试代码: + +``` +//enable ADC设备与指定通道 +rt_adc_enable((rt_adc_device_t)device, channel); + +//发布control信号,将adc外设配置为PPB模式 +result = rt_device_control(device, NS800_ADC_CMD_ENABLE_PPB, RT_NULL); +if (result != RT_EOK) +{ + rt_kprintf("adc_pr_ppb: enable ppb failed %d\r\n", result); + return result; +} + +//发起read,这里read到的已经是PPB模式下的返回值了 +value = rt_adc_read((rt_adc_device_t)device, channel); + +//退出PPB模式 +rt_device_control(device, NS800_ADC_CMD_DISABLE_EXT, RT_NULL); +rt_kprintf("adc_pr_ppb: channel=%u value=%u\r\n", + (unsigned int)channel, + (unsigned int)value); +``` + +PPB 模式下,应用层仍然调用 `rt_adc_read()`,但驱动内部返回的是 PPB oversampling 后的结果,而不是单次 `ADC_readResult()` 的结果。 + +### 6. Kconfig 实现思路与应用方法 + +#### 6.1 主线 BSP Kconfig + +主线 BSP 中 ADC 配置项位于: + +``` +bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig +``` + +ADC 配置为: + +``` +menuconfig BSP_USING_ADC + bool "Enable ADC" + select RT_USING_ADC + default n +``` + +这里的逻辑是: + +* 用户在 menuconfig 中打开 `BSP_USING_ADC`。 +* Kconfig 自动选择 `RT_USING_ADC`。 +* `RT_USING_ADC` 使 RT-Thread ADC 设备框架可用。 +* `BSP_USING_ADC` 让 BSP 的 `SConscript` 编译 `drv_adc.c`。 + +#### 6.2 SConscript 接入 + +主线驱动编译接入位于: + +``` +bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript +``` + +ADC 条件编译: + +``` +if GetDepend('BSP_USING_ADC'): + src += ['drv_adc.c'] +``` + +这样可以保证未开启 ADC 时不会引入 ADC 驱动和对应 HAL 依赖,减少对其他 BSP 配置的影响。 + +#### 6.3 CI attachconfig + +为了让主线 CI 覆盖 ADC 配置,在: + +``` +bsp/novosns/ns800/ns800rt7p65-nssinepad/.ci/attachconfig/ci.attachconfig.yml +``` + +加入: + +``` +devices.adc: + <<: *scons + kconfig: + - CONFIG_BSP_USING_ADC=y +``` + +这样 CI 能以“打开 ADC 外设”的配置构建一次 BSP,避免 ADC 文件只在本地可编译、主线 CI 未覆盖的问题。 + +#### 6.4 推荐配置流程 + +主线 BSP: + +``` +cd /path/to/rt-thread/bsp/novosns/ns800/ns800rt7p65-nssinepad +source ~/.env/env.sh +scons --menuconfig +``` + +![screenshot_image.png](./figures/adc-朱玉施-04.png.webp) + +在 menuconfig 中打开: + +``` +On-chip Peripheral Drivers + Enable ADC +``` + +![screenshot_image.png](./figures/adc-朱玉施-05.png.webp) + +保存后生成 `.config` 和 `rtconfig.h`,然后构建: + +![screenshot_image.png](./figures/adc-朱玉施-06.png.webp) + +``` +scons -j4 +``` + +![screenshot_image.png](./figures/adc-朱玉施-07.png.webp) + +### 7. 运行时调用说明 + +#### 7.1 基础读取 + +``` +rt_device_t dev; +rt_uint32_t value; + +dev = rt_device_find("adc0"); +if (dev == RT_NULL) +{ + return -RT_ERROR; +} + +rt_adc_enable((rt_adc_device_t)dev, 0); +value = rt_adc_read((rt_adc_device_t)dev, 0); +rt_kprintf("adc0 ch0=%u\r\n", value); +``` + +#### 7.2 查询分辨率和参考电压 + +``` +rt_uint8_t resolution; +rt_int16_t vref; + +rt_device_control(dev, RT_ADC_CMD_GET_RESOLUTION, &resolution); +rt_device_control(dev, RT_ADC_CMD_GET_VREF, &vref); +``` + +当前默认返回: + +* resolution:12 bit。 +* vref:3300 mV。 + +#### 7.3 切换到 PPB 模式 + +``` +rt_adc_enable((rt_adc_device_t)dev, channel); +rt_device_control(dev, NS800_ADC_CMD_ENABLE_PPB, RT_NULL); +value = rt_adc_read((rt_adc_device_t)dev, channel); +rt_device_control(dev, NS800_ADC_CMD_DISABLE_EXT, RT_NULL); +``` + +PPB 模式默认使用 8 次 oversampling,并返回 PPB 处理后的值。 + +#### 7.4 切换到 IRQ 模式 + +``` +static void adc_irq_cb(void *user_data) +{ + rt_kprintf("adc irq\r\n"); +} + +struct ns800_adc_callback cb = +{ + .callback = adc_irq_cb, + .user_data = RT_NULL, +}; + +rt_adc_enable((rt_adc_device_t)dev, channel); +rt_device_control(dev, NS800_ADC_CMD_ENABLE_IRQ, &cb); +value = rt_adc_read((rt_adc_device_t)dev, channel); +rt_device_control(dev, NS800_ADC_CMD_DISABLE_EXT, RT_NULL); +``` + +IRQ 模式使用前应确保全局向量表已初始化。驱动只注册 ADC IRQ handler,不负责系统级向量表初始化。 + +#### 7.5 control 命令总结 + +| 命令 | 参数 | 作用 | +| --- | --- | --- | +| `RT_ADC_CMD_ENABLE` | channel | 使能指定 ADC 通道 | +| `RT_ADC_CMD_DISABLE` | channel | 关闭指定 ADC 通道 | +| `RT_ADC_CMD_GET_RESOLUTION` | `rt_uint8_t *` | 获取 ADC 分辨率 | +| `RT_ADC_CMD_GET_VREF` | `rt_int16_t *` | 获取参考电压,单位 mV | +| `NS800_ADC_CMD_ENABLE_PPB` | `RT_NULL` | 切换到默认 PPB oversampling 模式 | +| `NS800_ADC_CMD_ENABLE_IRQ` | `struct ns800_adc_callback *` | 切换到 IRQ 模式并注册回调 | +| `NS800_ADC_CMD_SET_CALLBACK` | `struct ns800_adc_callback *` | 仅设置 IRQ callback | +| `NS800_ADC_CMD_DISABLE_EXT` | `RT_NULL` | 退出 PPB/IRQ 扩展模式,恢复 normal | + +## 四、纳芯微NS800RT7P65D上的HWTimer实践(刘协泉) + +作者:刘协泉 + +原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的硬件定时器实践 + +源文章: + +### 前提知识 + +  RTT的硬件定时器框架,本质上是通过计算出误差最小的分频系数和计数值来实现定时功能,使用硬件定时加软件计数(如果计时超过了定时器能支持的最大单次时间)的方式实现计时功能。 +  另外框架层其实更倾向于硬件定时器跑在1M的速率,若做不到,则设置为最低主频(这个可以在clock time框架层代码的init函数中见到),个人猜测是功耗方面的考量,毕竟现在的硬件定时器,主频都动不动几十上百兆的,其实可以做的更加精确。 +  最后,由于最终的主频和预分频系数是框架层设置的,因此驱动层最好是能做到可以设置任意的预分频系数,否则硬件定时器的计时功能就大概率误差大。 +  基于这些信息,如果要很好的适配上RTT的硬件定时器框架,硬件定时器就得有如下功能: + +1. 定时器的主频最好是大于或等于1M +2. 定时器支持中断事件 +3. 最好定时器的分频系数可以任意设置(有效范围内的整数分频系数任意设置) + + # 资料收集 + +   通过查询NS800RT7P65D的规格书以及SDK代码,发现这颗芯片支持14个高级定时器,分别是: + +![screenshot_image.png](./figures/hwtimer-刘协泉-01.png.webp) + +  经过查看这些资料,基本上确认完全做定时器的模块只有有 TIM、BTIM、LPTIM和STIM,其他模块都属于特定应用场景下使用的。 +  而在对接RTT定时器框架的需求上看,STIM因为不支持预分频,只能排除掉。BTIM和LPTIM由于只支持特定系数的分频,若强行适配上,会出现大部分时间计时有偏差的问题,因此也只能放弃,以免留雷。 + +### package拉取 + +  在bsp目录下启动env,运行 pkgs —upgrade后运行pkgs —update拉取代码。 + +### 驱动代码编写 + +  首先,RTT在5.2.2之后的版本,对hwtimer模块进行了重构,新的模块叫clock time。 +  虽然名字变更了,但是实际上接口变化不算大,对于驱动来说,基本上就三个变更项: + +1. 把HWTIMER宏变更为CLOCK_TIMER +2. hwtimer变更为clock_timer +3. 驱动文件名由drv_hwtimer.c变更为drv_timer.c + + ## KConfig + +   这个文件的位置在bsp目录下的bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig,修改主要是增加硬件定时器相关宏开关 + +``` + menuconfig BSP_USING_TIM + bool "Enable TIM (Hardware Timer)" + select RT_USING_CLOCK_TIME + default n + help + Enable hardware timer (TIM/BTIM/STIM/LPTIM) driver + + if BSP_USING_TIM + menu "TIM Timer Configuration" + config BSP_USING_TIM1 + bool "Enable TIM1" + default n + config BSP_USING_TIM2 + bool "Enable TIM2" + default n + endmenu + + endif +``` + +#### drv_timer.c + +  这个文件加在bsp/novosns/ns800/libraries/HAL_Drivers/drivers/目录下 + +``` +/* + * Copyright (c) 2006-2025, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * Author: oxlm + */ + +#include +#include +#include +#include "drv_config.h" + +#ifdef BSP_USING_TIM + +struct ns800_clock_timer +{ + rt_clock_timer_t timer; + void *instance; + IRQn_Type irqno; + void (*irq_handler)(void); + char *name; + rt_clock_timer_mode_t mode; +}; + +enum +{ +#ifdef BSP_USING_TIM1 + TIM1_INDEX, +#endif +#ifdef BSP_USING_TIM2 + TIM2_INDEX, +#endif +}; + +#ifdef BSP_USING_TIM1 +void TIM1_IRQHandler(void); +#endif +#ifdef BSP_USING_TIM2 +void TIM2_IRQHandler(void); +#endif + +static struct ns800_clock_timer ns800_timers[] = +{ +#ifdef BSP_USING_TIM1 + {.instance = TIM1, .irqno = TIM1_IRQn, .irq_handler = TIM1_IRQHandler, .name = "timer1"}, +#endif +#ifdef BSP_USING_TIM2 + {.instance = TIM2, .irqno = TIM2_IRQn, .irq_handler = TIM2_IRQHandler, .name = "timer2"}, +#endif +}; + +#define NS800_TIMER_NUM (sizeof(ns800_timers) / sizeof(ns800_timers[0])) + +static void ns800_clock_timer_isr(void *param) +{ + rt_interrupt_enter(); + + struct ns800_clock_timer *tim = (struct ns800_clock_timer *)param; + + TIM_TypeDef *htim = (TIM_TypeDef *)tim->instance; + if (TIM_getFlags(htim, TIM_FLAG_UPDATE)) + { + TIM_clearFlags(htim, TIM_FLAG_UPDATE); + rt_clock_timer_isr(&tim->timer); + + if (tim->mode == CLOCK_TIMER_MODE_ONESHOT) + TIM_disableCounter(htim); + } + + rt_interrupt_leave(); + __DSB(); +} + +#ifdef BSP_USING_TIM1 +void TIM1_IRQHandler(void) { ns800_clock_timer_isr(&ns800_timers[TIM1_INDEX]); } +#endif +#ifdef BSP_USING_TIM2 +void TIM2_IRQHandler(void) { ns800_clock_timer_isr(&ns800_timers[TIM2_INDEX]); } +#endif + +static void ns800_clock_timer_init(rt_clock_timer_t *timer, rt_uint32_t state) +{ + struct ns800_clock_timer *tim = (struct ns800_clock_timer *)timer->parent.user_data; + TIM_TypeDef *htim = (TIM_TypeDef *)tim->instance; + + if(state) + { + __IO uint32_t cfg = + TIM_PWMMODE_ONEPOINT | + TIM_CLOCKDIVISION_DIV1 | + TIM_AUTORELOADPRELOAD_ENABLE | + TIM_COUNTERMODE_UP | + TIM_ONEPULSEMODE_REPETITIVE; + + TIM_configTimeBase(htim, 200-1, 100-1, cfg); + TIM_clearFlags(htim, TIM_FLAG_UPDATE); + TIM_enableInterruptSource(htim, TIM_IT_UPDATE); + } + else + { + TIM_disableInterruptSource(htim, TIM_IT_UPDATE); + TIM_disableCounter(htim, TIM_FLAG_UPDATE); + } +} + +static rt_err_t ns800_clock_timer_start(rt_clock_timer_t *timer, rt_uint32_t cnt, rt_clock_timer_mode_t mode) +{ + struct ns800_clock_timer *tim = (struct ns800_clock_timer *)timer->parent.user_data; + tim->mode = mode; + + TIM_TypeDef *htim = (TIM_TypeDef *)tim->instance; + + if (cnt > 0xFFFF) cnt = 0xFFFF; + TIM_setAutoReload(htim, cnt-1); + TIM_setCounter(htim, 0); + TIM_enableCounter(htim); + return RT_EOK; +} + +static void ns800_clock_timer_stop(rt_clock_timer_t *timer) +{ + struct ns800_clock_timer *tim = (struct ns800_clock_timer *)timer->parent.user_data; + + TIM_disableCounter((TIM_TypeDef *)tim->instance); +} + +static rt_uint32_t ns800_clock_timer_count_get(rt_clock_timer_t *timer) +{ + struct ns800_clock_timer *tim = (struct ns800_clock_timer *)timer->parent.user_data; + + return TIM_getCounter((TIM_TypeDef *)tim->instance); +} + +static rt_err_t __set_timerx_freq(rt_clock_timer_t *timer, uint32_t freq) +{ + #define TIM_SRC_CLK 200000000UL + struct ns800_clock_timer *tim = timer->parent.user_data; + + rt_uint32_t psc = (TIM_SRC_CLK / freq ) - 1; + TIM_setPrescaler(tim->instance, psc); + + return RT_EOK; +} + +static rt_err_t ns800_clock_timer_control(rt_clock_timer_t *timer, rt_uint32_t cmd, void *args) +{ + rt_err_t err = RT_EOK; + rt_int32_t freq; + + switch (cmd) + { + case CLOCK_TIMER_CTRL_FREQ_SET: + freq = *(rt_uint32_t *)args; + __set_timerx_freq(timer, freq); + break; + case CLOCK_TIMER_CTRL_INFO_GET: + *(struct rt_clock_timer_info*)args = *timer->info; + err = RT_EOK; + break; + + case CLOCK_TIMER_CTRL_MODE_SET: + timer->mode = *(rt_uint32_t *)args; + break; + + case CLOCK_TIMER_CTRL_STOP: + ns800_clock_timer_stop(timer); + break; + } + return err; +} + +static const struct rt_clock_timer_info ns800_clock_timer_info[] = +{ +#ifdef BSP_USING_TIM1 + { + .maxfreq = 200000000UL, + .minfreq = 1UL, + .maxcnt = 0xFFFF, + .cntmode = CLOCK_TIMER_CNTMODE_UP, + }, +#endif +#ifdef BSP_USING_TIM2 + { + .maxfreq = 200000000UL, + .minfreq = 1UL, + .maxcnt = 0xFFFF, + .cntmode = CLOCK_TIMER_CNTMODE_UP, + }, +#endif +}; + +static const struct rt_clock_timer_ops ns800_clock_timer_ops = +{ + .init = ns800_clock_timer_init, + .start = ns800_clock_timer_start, + .stop = ns800_clock_timer_stop, + .count_get = ns800_clock_timer_count_get, + .control = ns800_clock_timer_control, +}; + +int rt_hw_clock_timer_init(void) +{ + if (NS800_TIMER_NUM == 0) + return RT_EOK; + + uint8_t i; + IRQn_Type last_irq = (IRQn_Type)0; + + for (i = 0; i < NS800_TIMER_NUM; i++) + { + ns800_timers[i].timer.info = &ns800_clock_timer_info[i]; + ns800_timers[i].timer.ops = &ns800_clock_timer_ops; + + rt_clock_timer_register(&ns800_timers[i].timer, + ns800_timers[i].name, + &ns800_timers[i]); + + if (ns800_timers[i].irqno != last_irq) + { + Interrupt_register(ns800_timers[i].irqno, ns800_timers[i].irq_handler); + Interrupt_enable(ns800_timers[i].irqno); + last_irq = ns800_timers[i].irqno; + } + + rt_kprintf("[%s] register ok\n", ns800_timers[i].name); + } + + return RT_EOK; +} + +INIT_DEVICE_EXPORT(rt_hw_clock_timer_init); + +#endif /* BSP_USING_TIM */ +``` + +SConscript +  这个文件的位置是bsp中的驱动目录下存储的,具体路径为bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript,主要选择编译时选择哪些文件编译 + +``` +if GetDepend(['BSP_USING_TIM']): + src += ['drv_timer.c'] +``` + +### 测试代码编写 + +``` +#include +#include + +static rt_err_t test_timer_callback(rt_device_t dev, rt_size_t size) +{ + rt_kprintf("[clock_timer] %d!\n",rt_tick_get()); + return RT_EOK; +} + +int clock_timer_test(int argc, char** argv) +{ + rt_device_t dev; + rt_err_t err; + rt_clock_timerval_t t = { + .sec = 1, + .usec = 0, + }; + rt_uint32_t mode; + + if (argc < 2) + { + rt_kprintf("usage: clock_timer_test \n"); + rt_kprintf("eg: clock_timer_test timer1\n"); + return -1; + } + + dev = rt_device_find(argv[1]); + if (!dev) + { + rt_kprintf("device not found\n"); + return -1; + } + + err = rt_device_open(dev, RT_DEVICE_OFLAG_RDWR); + if (err != RT_EOK) + { + rt_kprintf("open failed\n"); + return err; + } + + rt_device_set_rx_indicate(dev, test_timer_callback); + + rt_kprintf("\n[TEST] ONESHOT mode\n"); + mode = CLOCK_TIMER_MODE_ONESHOT; + rt_device_control(dev, CLOCK_TIMER_CTRL_MODE_SET, &mode); + rt_device_write(dev, 0, &t, sizeof(t)); + + rt_thread_mdelay(2000); + rt_device_control(dev, CLOCK_TIMER_CTRL_STOP, RT_NULL); + + rt_kprintf("\n[TEST] PERIOD mode\n"); + mode = CLOCK_TIMER_MODE_PERIOD; + rt_device_control(dev, CLOCK_TIMER_CTRL_MODE_SET, &mode); + rt_device_write(dev, 0, &t, sizeof(t)); + + rt_thread_mdelay(3000); + + rt_kprintf("\n[TEST] read count\n"); + rt_device_read(dev, 0, &t, sizeof(t)); + rt_kprintf("Read: Sec = %d, Usec = %d\n", t.sec, t.usec); + + rt_device_control(dev, CLOCK_TIMER_CTRL_STOP, RT_NULL); + + rt_device_close(dev); + rt_kprintf("\n[OK] all test done\n"); + return 0; +} + +MSH_CMD_EXPORT(clock_timer_test, test clock timer); +``` + +### 配置工程 + +#### 拉取依赖的驱动 + +``` +pkgs --upgrade +pkgs --update +``` + +#### 选择驱动 + +  menuconfig后选择如下内容后保存 + +![screenshot_image.png](./figures/hwtimer-刘协泉-02.png) + +#### 更新工程 + +``` +scons --target=mdk5 +``` + +### 编译后验证 + +``` + \ | / +- RT - Thread Operating System + / | \ 5.3.0 build May 16 2026 10:47:47 + 2006 - 2024 Copyright by RT-Thread team +[timer1] register ok +[timer2] register ok +[btim1] register ok +[btim2] register ok +[lptim] register ok +msh >clo +clock_timer_test +msh >clock_timer_test timer1 + +[TEST] ONESHOT mode +[clock_timer] 4784! + +[TEST] PERIOD mode +[clock_timer] 6849! +[clock_timer] 7848! +[clock_timer] 8847! + +[TEST] read count +Read: Sec = 3, Usec = 4897 + +[OK] all test done +msh >clo +clock_timer_test +msh >clock_timer_test timer2 + +[TEST] ONESHOT mode +[clock_timer] 14707! + +[TEST] PERIOD mode +[clock_timer] 16772! +[clock_timer] 17771! +[clock_timer] 18770! + +[TEST] read count +Read: Sec = 3, Usec = 4897 + +[OK] all test done +msh > +``` + +  从测试结果看,定时器的误差很小 + +### 总结 + +  总体来说,这颗芯片的资料还是挺全的,开放度也高,基本上看SDK驱动部分就知道能力大致在哪。最主要的是,整个资料全中文编写,降低了开发者的理解门槛。 + +### 问题点处理 + +#### 选择clock time框架后,直接编译报错 + +  原因是bsp下的rtconfig.py在生成keil工程时,默认选择的工具链是armcc。由于我实际的工具链是armclang,因此会编译报错。 + +**解决办法:** + +  直接修改py脚本,改成选择keil时的PLATFORM为armclang就行 + +#### 模板工程转化后无法编译通过 + +  原因是生成的工程没有选择芯片,暂时采用在SConscript里预定义宏的方式规避 + +**解决办法:** + +![screenshot_image.png](./figures/hwtimer-刘协泉-03.png.webp) + +## 五、纳芯微NS800RT7P65D上的WDT实践(袁胜富) + +作者:袁胜富 + +原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的IWDG1实践 + +源文章: + +### 1. 概述 + +1. 本文档记录将 RT-Thread WDG 总线驱动移植到 NS800RT7P65X BSP 的完整过程。NS800 芯片有 1 个 iwdg1 外设,本移植以iwdg1的中断和复位测试进行验证。 +2. 内部集成两个高安全性的独立看门狗定时器(IWDG1),每个 CPU 控制一个,其计时时钟源都为 MIRC1。 该独立看门狗可检测并解决由软件错误导致的故障,并在计数器从给定的超时值计数到 0 时触发系统复位;该独立看门狗还支持窗口刷新功能,用户只能在设定的计时窗口内刷计数器。用户可配置 IWDG1 在低功耗模式下继续运行或暂停运行。 +3. IWDG1 的溢出周期由 PSC 及 TOPS 共同控制,其溢出周期的范围为 102.4us ~ 13.42s。 + +![screenshot_image.png](./figures/wdt-袁胜富-01.png) + +![screenshot_image.png](./figures/wdt-袁胜富-02.png.webp) + +4. IWDG1 运行时,其计数器计数值范围为 0 ~ ARR,其溢出周期可通过如下公式进行计算: + +* 𝑡𝑂𝑉 = (𝐴𝑅𝑅 + 1) × 𝑡𝑊𝐷𝐶𝐿𝐾𝐷 +* 当 ARR 为 0x3FF、 tWDCLKD 为 0.1us 时, IWDG1 具有最小溢出时间 102.4us。 +* 当 ARR 为 0x3FFF、 tWDCLKD为 819.2us 时, IWDG1 具有最大溢出时间 13.42s。 +* 注意:用户应合理配置 PSC 及 TOPS 寄存器,以使 IWDG1 的溢出时间符合应用预期。 +* 由于硬件寄存器 PSC 和 TOPS 的组合有限,无法精确匹配任意超时时间,只能选择最接近的配置。不过驱动中的 iwdg1_calc_config 函数已经帮您自动完成了计算,您只需要传入期望的超时毫秒数,它会返回最接近的硬件配置和实际超时。 + +| PSC编码 | 分频比 | tWDCLKD (μs) | TOPS编码 | ARR | 计数值 | 实际超时 (ms) | +| --- | --- | --- | --- | --- | --- | --- | +| 0 | 1 | 0.1 | 0 | 1023 | 1024 | 0.1024 | +| 0 | 1 | 0.1 | 1 | 4095 | 4096 | 0.4096 | +| 0 | 1 | 0.1 | 2 | 8191 | 8192 | 0.8192 | +| 0 | 1 | 0.1 | 3 | 16383 | 16384 | 1.6384 | +| 1 | 2 | 0.2 | 0 | 1023 | 1024 | 0.2048 | +| 1 | 2 | 0.2 | 1 | 4095 | 4096 | 0.8192 | +| 1 | 2 | 0.2 | 2 | 8191 | 8192 | 1.6384 | +| 1 | 2 | 0.2 | 3 | 16383 | 16384 | 3.2768 | +| 2 | 4 | 0.4 | 0 | 1023 | 1024 | 0.4096 | +| 2 | 4 | 0.4 | 1 | 4095 | 4096 | 1.6384 | +| 2 | 4 | 0.4 | 2 | 8191 | 8192 | 3.2768 | +| 2 | 4 | 0.4 | 3 | 16383 | 16384 | 6.5536 | +| 3 | 8 | 0.8 | 0 | 1023 | 1024 | 0.8192 | +| 3 | 8 | 0.8 | 1 | 4095 | 4096 | 3.2768 | +| 3 | 8 | 0.8 | 2 | 8191 | 8192 | 6.5536 | +| 3 | 8 | 0.8 | 3 | 16383 | 16384 | 13.1072 | +| 4 | 16 | 1.6 | 0 | 1023 | 1024 | 1.6384 | +| 4 | 16 | 1.6 | 1 | 4095 | 4096 | 6.5536 | +| 4 | 16 | 1.6 | 2 | 8191 | 8192 | 13.1072 | +| 4 | 16 | 1.6 | 3 | 16383 | 16384 | 26.2144 | +| 5 | 32 | 3.2 | 0 | 1023 | 1024 | 3.2768 | +| 5 | 32 | 3.2 | 1 | 4095 | 4096 | 13.1072 | +| 5 | 32 | 3.2 | 2 | 8191 | 8192 | 26.2144 | +| 5 | 32 | 3.2 | 3 | 16383 | 16384 | 52.4288 | +| 8 | 128 | 12.8 | 0 | 1023 | 1024 | 13.1072 | +| 8 | 128 | 12.8 | 1 | 4095 | 4096 | 52.4288 | +| 8 | 128 | 12.8 | 2 | 8191 | 8192 | 104.8576 | +| 8 | 128 | 12.8 | 3 | 16383 | 16384 | 209.7152 | +| 9 | 256 | 25.6 | 0 | 1023 | 1024 | 26.2144 | +| 9 | 256 | 25.6 | 1 | 4095 | 4096 | 104.8576 | +| 9 | 256 | 25.6 | 2 | 8191 | 8192 | 209.7152 | +| 9 | 256 | 25.6 | 3 | 16383 | 16384 | 419.4304 | +| 10 | 512 | 51.2 | 0 | 1023 | 1024 | 52.4288 | +| 10 | 512 | 51.2 | 1 | 4095 | 4096 | 209.7152 | +| 10 | 512 | 51.2 | 2 | 8191 | 8192 | 419.4304 | +| 10 | 512 | 51.2 | 3 | 16383 | 16384 | 838.8608 | +| 11 | 1024 | 102.4 | 0 | 1023 | 1024 | 104.8576 | +| 11 | 1024 | 102.4 | 1 | 4095 | 4096 | 419.4304 | +| 11 | 1024 | 102.4 | 2 | 8191 | 8192 | 838.8608 | +| 11 | 1024 | 102.4 | 3 | 16383 | 16384 | 1677.7216 | +| 12 | 2048 | 204.8 | 0 | 1023 | 1024 | 209.7152 | +| 12 | 2048 | 204.8 | 1 | 4095 | 4096 | 838.8608 | +| 12 | 2048 | 204.8 | 2 | 8191 | 8192 | 1677.7216 | +| 12 | 2048 | 204.8 | 3 | 16383 | 16384 | 3355.4432 | +| 13 | 4096 | 409.6 | 0 | 1023 | 1024 | 419.4304 | +| 13 | 4096 | 409.6 | 1 | 4095 | 4096 | 1677.7216 | +| 13 | 4096 | 409.6 | 2 | 8191 | 8192 | 3355.4432 | +| 13 | 4096 | 409.6 | 3 | 16383 | 16384 | 6710.8864 | +| 14 | 8192 | 819.2 | 0 | 1023 | 1024 | 838.8608 | +| 14 | 8192 | 819.2 | 1 | 4095 | 4096 | 3355.4432 | +| 14 | 8192 | 819.2 | 2 | 8191 | 8192 | 6710.8864 | +| 14 | 8192 | 819.2 | 3 | 16383 | 16384 | 13421.7728 | +| 15 | 64 | 6.4 | 0 | 1023 | 1024 | 6.5536 | +| 15 | 64 | 6.4 | 1 | 4095 | 4096 | 26.2144 | +| 15 | 64 | 6.4 | 2 | 8191 | 8192 | 52.4288 | +| 15 | 64 | 6.4 | 3 | 16383 | 16384 | 104.8576 | + +> 注意:PSC 编码 6、7 无效,已跳过。 +> 从表中可以看出,实际超时是离散的,您只需要选择最接近您需求的那个值即可。例如: +> 希望 150ms → 最接近的是 104.86ms (PSC=8, TOPS=2) 或 209.72ms (PSC=8, TOPS=3) +> 希望 500ms → 最接近的是 419.43ms (PSC=9, TOPS=3) 或 838.86ms (PSC=10, TOPS=3) +> 驱动已经采用了“最接近”算法,所以您无需手动查表。 +> +### 2. 移植思路 + +1. RT-Thread WDG 框架概述 + RT-Thread 提供了一套标准 WDG 框架,核心结构包括: + struct rt_watchdog_device:WDG设备对象,用于挂载总线 + struct rt_watchdog_ops:WDG操作函数集,包括init和control + +``` +int rt_hw_iwdg1_init(void) +{ + iwdg1_dev.base = IWDG1; + /* Default factory configuration: maximum timeout 13421.7728 ms */ + iwdg1_dev.psc_code = 14; // PSC = 14 (DIV8192), tWDCLKD = 819.2us + iwdg1_dev.tops_code = 3; // TOPS = 3 (ARR=16383) + iwdg1_dev.timeout_ms = (tops_arr_table[3] + 1) * tWDCLKD_us_table[14] / 1000.0f; // 16384*819.2/1000 = 13421.7728 ms + iwdg1_dev.started = RT_FALSE; + iwdg1_dev.interrupt_mode = RT_TRUE; + + watchdog.ops = &iwdg1_ops; + if (rt_hw_watchdog_register(&watchdog, IWDG1_DEVICE_NAME, RT_DEVICE_FLAG_DEACTIVATE, RT_NULL) != RT_EOK) + { + rt_kprintf("iwdg1: register failed\n"); + return -RT_ERROR; + } + rt_kprintf("iwdg1: driver initialized (clock=%d Hz, max timeout=%.4f ms)\n", IWDG1_CLK_FREQ_HZ, iwdg1_dev.timeout_ms); + return RT_EOK; +} + +INIT_BOARD_EXPORT(rt_hw_iwdg1_init); +``` + +iwdg1_dev.interrupt_mode = RT_TRUE;这行代码RT_FALSE代表看门狗超时复位,RT_TRUE代表看门狗超时进入中断。 + +#### 2.1 驱动实现流程 + +1.打开env,第一次的话,需要输入命令pkgs —upgrade然后输入pkgs —update; +2.输入命令scons —menuconfig,进入RT-Thread Componets,进入Device Drivers,选择使用Using Watch Dog device drivers,按s键保存,然后按ESC; +3.在路径rt-thread\bsp\novosns\ns800\libraries\HAL_Drivers\drivers下创建drv_iwdg1.c和drv_iwdg1.h.并在SConscript文件中添加 + +``` +if GetDepend(['BSP_USING_IWDG1']): + src += ['drv_iwdg1.c'] +``` + +4.在路径rt-thread\bsp\novosns\ns800\ns800rt7p65-nssinepad\board下的Kconfig文件里添加 + +``` + menuconfig BSP_USING_WDG + bool "Enable WDG" + select RT_USING_WDT + default n + if BSP_USING_WDG + menuconfig BSP_USING_IWDG1 + bool "Enable IWDG1" + default n + endif +``` + +5.进入ENV,输入scons —menuconfig,选择On-Chip Peripheral Drivers,进入选择Enable WDG,进入选择Enable IWDG1,按s键保存,然后按ESC; +6.输入scons —target=mdk5身材Keil5 MDK工程 + +#### 2.2 测试 + +``` +int rt_hw_wwdg_init(void) +{ + wwdg_dev.base = WWDG; + + /* 默认配置:最大分频 128,计数器初值 0x7F,窗口 0x7F,约 167.772 ms */ + wwdg_dev.wdgtb = 7; + wwdg_dev.reload = 0x7F; + wwdg_dev.window = 0x7F; + wwdg_dev.timeout_s = 0.16777216f; + wwdg_dev.started = RT_FALSE; + wwdg_dev.interrupt_mode = RT_FALSE; /* 默认复位模式 */ + + watchdog.ops = &wwdg_ops; + if (rt_hw_watchdog_register(&watchdog, WWDG_DEVICE_NAME, RT_DEVICE_FLAG_DEACTIVATE, RT_NULL) != RT_EOK) + { + rt_kprintf("wwdg: register failed\n"); + return -RT_ERROR; + } + + rt_kprintf("wwdg: driver initialized (HCLK=%d Hz)\n", HCLK_FREQ_HZ); + rt_kprintf("wwdg: timeout range: 20.48 us ~ 167.772 ms\n"); + rt_kprintf("wwdg: default mode: reset, timeout: 167.772 ms\n"); + + return RT_EOK; +} + +INIT_BOARD_EXPORT(rt_hw_wwdg_init); +``` + +wwdg_dev.interrupt_mode = RT_FALSE; / *默认复位模式* / + +``` +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-05-06 Jiawei.Deng first version + */ + +#include +#include +#include + +#define LED1_PIN PIN_NUM(GPIO_68) +#define LED2_PIN PIN_NUM(GPIO_69) + +int main(void) +{ + rt_device_t wdg = rt_device_find("iwdg1"); + if (wdg) + { + rt_device_init(wdg); + rt_uint32_t timeout = 100; // /* NOTE: The maximum possible timeout is about 13.42 seconds. + rt_device_control(wdg, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout); + rt_uint32_t actual; + rt_device_control(wdg, RT_DEVICE_CTRL_WDT_GET_TIMEOUT, &actual); + rt_kprintf("actual timeout = %d ms\n", actual); + rt_device_control(wdg, RT_DEVICE_CTRL_WDT_START, RT_NULL); + uint32_t cr = IWDG1->CR.WORDVAL; + rt_kprintf("CR = 0x%08X, CKS=%d, TOPS=%d\n", cr, (cr >> IWDG_CR_CKS_S) & IWDG_CR_CKS_M, (cr >> IWDG_CR_TOPS_S) & IWDG_CR_TOPS_M); + } + else + { + rt_kprintf("can't find iwdg1 device\n"); + } + rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT); + rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT); + + while (1) + { + rt_pin_write(LED1_PIN, PIN_HIGH); + rt_pin_write(LED2_PIN, PIN_HIGH); + rt_thread_mdelay(49); + rt_pin_write(LED1_PIN, PIN_LOW); + rt_pin_write(LED2_PIN, PIN_LOW); + rt_thread_mdelay(49); + if (wdg) + { + rt_device_control(wdg, RT_DEVICE_CTRL_WDT_KEEPALIVE, RT_NULL); + rt_kprintf("Feed\n"); // 可选择性打印 + } + + } +} +``` + +测试 +编译,烧录,使用Xshell8 +结果,定时100ms,实际有误差为104.8576ms + +``` + \ | / +- RT - Thread Operating System + / | \ 5.3.0 build May 14 2026 00:59:12 + 2006 - 2024 Copyright by RT-Thread team +[I/drv.ecap] ecap1 register done +psc_code=8, tops_code=2, actual=104.8576 ms +iwdg1: set timeout req=100 ms, actual=104.8576 ms (PSC=8, TOPS=2) +actual timeout = 104 ms +iwdg1: started, timeout=104.8576 ms +CR = 0x00003386, CKS=48, TOPS=2 +msh >Feed +``` + +wwdg_dev.interrupt_mode = RT_TRUE; / *中断模式* / + +``` +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-05-06 Jiawei.Deng first version + */ +#include +#include +#include + +#define LED1_PIN PIN_NUM(GPIO_68) +#define LED2_PIN PIN_NUM(GPIO_69) + +int main(void) +{ + rt_device_t wdg = rt_device_find("iwdg1"); + if (wdg) + { + rt_device_init(wdg); + rt_uint32_t timeout = 100; // /* NOTE: The maximum possible timeout is about 13.42 seconds. + rt_device_control(wdg, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout); + rt_uint32_t actual; + rt_device_control(wdg, RT_DEVICE_CTRL_WDT_GET_TIMEOUT, &actual); + rt_kprintf("actual timeout = %d ms\n", actual); + rt_device_control(wdg, RT_DEVICE_CTRL_WDT_START, RT_NULL); + uint32_t cr = IWDG1->CR.WORDVAL; + rt_kprintf("CR = 0x%08X, CKS=%d, TOPS=%d\n", cr, (cr >> IWDG_CR_CKS_S) & IWDG_CR_CKS_M, (cr >> IWDG_CR_TOPS_S) & IWDG_CR_TOPS_M); + } + else + { + rt_kprintf("can't find iwdg1 device\n"); + } + rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT); + rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT); + + while (1) + { + rt_pin_write(LED1_PIN, PIN_HIGH); + rt_pin_write(LED2_PIN, PIN_HIGH); + rt_thread_mdelay(49); + rt_pin_write(LED1_PIN, PIN_LOW); + rt_pin_write(LED2_PIN, PIN_LOW); + rt_thread_mdelay(49); + //if (wdg) + //{ + //rt_device_control(wdg, RT_DEVICE_CTRL_WDT_KEEPALIVE, RT_NULL); + //rt_kprintf("Feed\n"); // 可选择性打印 + //} + + } +} +编译,烧录,使用Xshell8 +结果,定时100ms,实际有误差为104.8576ms +``` + +\ | / + +* RT - Thread Operating System + / | \ 5.3.0 build May 14 2026 00:59:12 + 2006 - 2024 Copyright by RT-Thread team + [I/drv.ecap] ecap1 register done + psc_code=8, tops_code=2, actual=104.8576 ms + iwdg1: set timeout req=100 ms, actual=104.8576 ms (PSC=8, TOPS=2) + actual timeout = 104 ms + iwdg1: started, timeout=104.8576 ms + CR = 0x00003382, CKS=48, TOPS=2 + msh >Enter iwdg1 IRQ! + +``` +``` +### 3.文件源码 +#### drv_iwdg1.c + +```c +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-05-13 Jeffery.Yuan Floating-point timeout support + */ + +#include +#include +#include "board.h" +#include "iwdg1.h" +#include "drv_iwdg1.h" + +#ifdef RT_USING_WDT +#ifdef BSP_USING_IWDG1 + +/* IWDG1 clock source frequency (Hz) = 10 MHz */ +#define IWDG1_CLK_FREQ_HZ (10000000U) + +/* PSC encoding to division ratio and tWDCLKD (microseconds) */ +static const uint32_t psc_div_table[] = { + 1, /* 0: DIV1 */ + 2, /* 1: DIV2 */ + 4, /* 2: DIV4 */ + 8, /* 3: DIV8 */ + 16, /* 4: DIV16 */ + 32, /* 5: DIV32 */ + 0, /* 6: Invalid */ + 0, /* 7: Invalid */ + 128, /* 8: DIV128 */ + 256, /* 9: DIV256 */ + 512, /* 10: DIV512*/ + 1024, /* 11: DIV1024*/ + 2048, /* 12: DIV2048*/ + 4096, /* 13: DIV4096*/ + 8192, /* 14: DIV8192*/ + 64 /* 15: DIV64 */ +}; +static const float tWDCLKD_us_table[] = { + 0.1f, 0.2f, 0.4f, 0.8f, 1.6f, 3.2f, + 0.0f, 0.0f, + 12.8f, 25.6f, 51.2f, 102.4f, 204.8f, 409.6f, 819.2f, 6.4f +}; + +/* TOPS encoding to ARR value */ +static const uint32_t tops_arr_table[] = { + 1023, /* 00: 0x3FF */ + 4095, /* 01: 0xFFF */ + 8191, /* 10: 0x1FFF */ + 16383 /* 11: 0x3FFF */ +}; + +/* IWDG1 device object */ +struct iwdg1_device +{ + IWDG1_TypeDef *base; + uint8_t psc_code; /* PSC encoding (0~15) */ + uint8_t tops_code; /* TOPS encoding (0~3) */ + float timeout_ms; /* Actual timeout in milliseconds (floating point) */ + rt_bool_t started; + rt_bool_t interrupt_mode; /* intterrupt flag */ +}; + +static struct iwdg1_device iwdg1_dev; +static rt_watchdog_t watchdog; + +/** + * @brief Calculate the closest hardware configuration for a desired timeout + * @param timeout_ms Desired timeout in milliseconds (float) + * @param psc_code Output PSC encoding + * @param tops_code Output TOPS encoding + * @param actual_ms Output actual timeout in milliseconds (float) + * @return RT_EOK always successful + */ +static rt_err_t iwdg1_calc_config_float(float timeout_ms, uint8_t *psc_code, uint8_t *tops_code, float *actual_ms) +{ + float target_us = timeout_ms * 1000.0f; + float best_diff = 1e9f; + uint8_t best_psc = 0, best_tops = 0; + float best_actual_us = 0; + + for (uint8_t psc = 0; psc < 16; psc++) + { + float tWDCLKD_us = tWDCLKD_us_table[psc]; + if (tWDCLKD_us == 0.0f) continue; + for (uint8_t tops = 0; tops < 4; tops++) + { + uint32_t arr = tops_arr_table[tops]; + float ov_us = (arr + 1) * tWDCLKD_us; + float diff = (ov_us > target_us) ? (ov_us - target_us) : (target_us - ov_us); + if (diff < best_diff) + { + best_diff = diff; + best_psc = psc; + best_tops = tops; + best_actual_us = ov_us; + if (diff == 0) goto found; + } + } + } + +found: + *psc_code = best_psc; + *tops_code = best_tops; + *actual_ms = best_actual_us / 1000.0f; + if (*actual_ms < 0.0001f) *actual_ms = 0.1024f; // 最小 0.1024ms + rt_kprintf("psc_code=%d, tops_code=%d, actual=%.4f ms\n", best_psc, best_tops, *actual_ms); + return RT_EOK; +} + +/* IWDG1 Interrupt Handler + NOTE: Due the IWDG1 operates in a low-speed clock domain and the operation + to clear the flag requires a certain number of clock cycles, so wait + for the flag status update to complete before exiting! + */ +void IWDG1_Handler(void) +{ + rt_interrupt_enter(); + rt_kprintf("Enter iwdg1 IRQ!\n"); + IWDG1_clearErrorStatus(iwdg1_dev.base); + IWDG1_clearIntStatus(iwdg1_dev.base); + while(IWDG1_getIntStatus(iwdg1_dev.base) == 1) + { + ; + } + + IWDG1_enableModule(iwdg1_dev.base); + rt_interrupt_leave(); +} + +/* Apply hardware configuration (without enabling) */ +static void iwdg1_apply_config(void) +{ + uint32_t temp; + uint32_t cr = iwdg1_dev.base->CR.WORDVAL; + if (cr != 0x000033FF) { + rt_kprintf("iwdg1: CR already configured (0x%08X), skip write\n", cr); + // Reverse calculate actual timeout from existing CR + uint8_t psc = (cr >> 4) & 0xF; + uint8_t tops = cr & 0x3; + iwdg1_dev.psc_code = psc; + iwdg1_dev.tops_code = tops; + iwdg1_dev.timeout_ms = (tops_arr_table[tops] + 1) * tWDCLKD_us_table[psc] / 1000.0f; + rt_kprintf("iwdg1: using existing config, timeout=%.4f ms\n", iwdg1_dev.timeout_ms); + return; + } + if (iwdg1_dev.interrupt_mode) + { + /* Interrupt handler function registration. */ + Interrupt_register(IWDG1_IRQn, &IWDG1_Handler); + /* Enable the IWDG1 interrupt signals. Enable global interrupts.*/ + Interrupt_enable(IWDG1_IRQn); + temp = iwdg1_dev.tops_code | (IRQ_EN << IWDG_CR_RSTIRQS_S) \ + | (LPRUN << IWDG_CR_SLCSTP_S) \ + | (iwdg1_dev.psc_code << IWDG_CR_CKS_S) \ + | (END_PERCENT0 << IWDG_CR_RPES_S) \ + | (HEAD_PERCENT100 << IWDG_CR_RPSS_S); + } + else + { + temp = iwdg1_dev.tops_code | (RESET_EN << IWDG_CR_RSTIRQS_S) \ + | (LPRUN << IWDG_CR_SLCSTP_S) \ + | (iwdg1_dev.psc_code << IWDG_CR_CKS_S) \ + | (END_PERCENT0 << IWDG_CR_RPES_S) \ + | (HEAD_PERCENT100 << IWDG_CR_RPSS_S); + } + WRITE_REG(iwdg1_dev.base->CR.WORDVAL, temp); + + IWDG1_clearErrorStatus(iwdg1_dev.base); + IWDG1_clearIntStatus(iwdg1_dev.base); +} + +/* Start watchdog (enable) */ +static void iwdg1_start_hw(void) +{ + IWDG1_enableModule(iwdg1_dev.base); + iwdg1_dev.started = RT_TRUE; + rt_kprintf("iwdg1: started, timeout=%.4f ms\n", iwdg1_dev.timeout_ms); +} + +static rt_err_t iwdg1_init(rt_watchdog_t *wdt) +{ + return RT_EOK; +} + +static rt_err_t iwdg1_control(rt_watchdog_t *wdt, int cmd, void *arg) +{ + switch (cmd) + { + case RT_DEVICE_CTRL_WDT_KEEPALIVE: + IWDG1_refreshModule(iwdg1_dev.base); + break; + + case RT_DEVICE_CTRL_WDT_SET_TIMEOUT: + { + // Standard integer millisecond interface (backward compatible) + uint32_t req_ms_int = *(uint32_t *)arg; + float req_ms = (float)req_ms_int; + uint8_t psc, tops; + float actual_ms; + iwdg1_calc_config_float(req_ms, &psc, &tops, &actual_ms); + iwdg1_dev.psc_code = psc; + iwdg1_dev.tops_code = tops; + iwdg1_dev.timeout_ms = actual_ms; + rt_kprintf("iwdg1: set timeout req=%u ms, actual=%.4f ms (PSC=%d, TOPS=%d)\n", + req_ms_int, actual_ms, psc, tops); + if (iwdg1_dev.started) + { + iwdg1_apply_config(); + IWDG1_enableModule(iwdg1_dev.base); + } + } + break; + + case RT_DEVICE_CTRL_WDT_GET_TIMEOUT: + // Standard integer millisecond interface returns floor of actual timeout + *(uint32_t *)arg = (uint32_t)iwdg1_dev.timeout_ms; + break; + + case RT_DEVICE_CTRL_WDT_START: + if (!iwdg1_dev.started) + { + iwdg1_apply_config(); + iwdg1_start_hw(); + } + else + { + rt_kprintf("iwdg1: already started\n"); + } + break; + + case RT_DEVICE_CTRL_WDT_STOP: + rt_kprintf("iwdg1: stop not supported\n"); + return -RT_ENOSYS; + + default: + return -RT_ERROR; + } + return RT_EOK; +} + +/* Operation function table - constant */ +static const struct rt_watchdog_ops iwdg1_ops = { + .init = iwdg1_init, + .control = iwdg1_control, +}; + +int rt_hw_iwdg1_init(void) +{ + iwdg1_dev.base = IWDG1; + /* Default factory configuration: maximum timeout 13421.7728 ms */ + iwdg1_dev.psc_code = 14; // PSC = 14 (DIV8192), tWDCLKD = 819.2us + iwdg1_dev.tops_code = 3; // TOPS = 3 (ARR=16383) + iwdg1_dev.timeout_ms = (tops_arr_table[3] + 1) * tWDCLKD_us_table[14] / 1000.0f; // 16384*819.2/1000 = 13421.7728 ms + iwdg1_dev.started = RT_FALSE; + iwdg1_dev.interrupt_mode = RT_TRUE; + + watchdog.ops = &iwdg1_ops; + if (rt_hw_watchdog_register(&watchdog, IWDG1_DEVICE_NAME, RT_DEVICE_FLAG_DEACTIVATE, RT_NULL) != RT_EOK) + { + rt_kprintf("iwdg1: register failed\n"); + return -RT_ERROR; + } + rt_kprintf("iwdg1: driver initialized (clock=%d Hz, max timeout=%.4f ms)\n", IWDG1_CLK_FREQ_HZ, iwdg1_dev.timeout_ms); + return RT_EOK; +} + +INIT_BOARD_EXPORT(rt_hw_iwdg1_init); + +#endif /* BSP_USING_IWDG1 */ +#endif /* RT_USING_WDT */ +``` + +#### drv_iwdg1.h + +``` +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-05-12 Jeffery Yuan first version + */ + +#ifndef DRV_IWDG1_H__ +#define DRV_IWDG1_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define IWDG1_DEVICE_NAME "iwdg1" + +/** + * @brief Initialize IWDG1 hardware and register it as a watchdog device. + * + * @return RT_EOK on success, negative error code otherwise. + */ +int rt_hw_iwdg1_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* DRV_IWDG1_H__ */ +``` + +## 六、纳芯微NS800RT7P65D上的SPI实践(刘建华) + +作者:刘建华 + +原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的SPI实践 + +源文章: + +### NS800 RT-Thread SPI 驱动移植指南 + +#### 1. 概述 + +本文档记录将 RT-Thread SPI 总线驱动移植到 NS800RT7P65X BSP 的完整过程。NS800 芯片有 4 个 SPI 外设 (SPI1~SPI4),本移植以 SPI1 为例,配置为内部回环模式进行测试验证。 + +#### 2. 移植思路 + +#### 2.1 RT-Thread SPI 框架概述 + +RT-Thread 提供了一套标准 SPI 框架,核心结构包括: + +| 结构体 | 说明 | +| --- | --- | +| `struct rt_spi_bus` | SPI 总线对象,封装底层硬件 | +| `struct rt_spi_device` | SPI 设备对象,挂载到总线上 | +| `struct rt_spi_ops` | SPI 操作函数集,包含 `configure` 和 `xfer` | +| `struct rt_spi_configuration` | SPI 配置参数(模式、位宽、频率) | + +驱动实现流程: + +``` +INIT_BOARD_EXPORT(rt_hw_spi_init) + → 遍历 spi_config[] 数组 + → 初始化时钟和 GPIO + → rt_spi_bus_register() 注册 SPI 总线 +``` + +#### 2.2 移植步骤概览 + +1. **创建驱动文件** - `drv_spi.h` 和 `drv_spi.c` +2. **实现 GPIO 初始化** - 配置 SCK/MOSI/MISO 引脚及复用功能 +3. **实现 SPI 配置** - 设置 CPOL/CPHA、位宽、时钟频率 +4. **实现数据传输** - 基于轮询的 `SPI_transmitReceive()` 实现 +5. **更新构建系统** - 修改 `SConscript` +6. **配置 Kconfig** - 添加 SPI 配置选项 +7. **更新 rtconfig.h** - 启用 SPI 宏定义 +8. **编写测试代码** - SPI 回环测试 + +#### 3. 文件清单 + +#### 3.1 新建文件 + +| 文件路径 | 说明 | +| --- | --- | +| `bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_spi.h` | SPI 驱动头文件 | +| `bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_spi.c` | SPI 驱动源文件 | + +#### 3.2 修改文件 + +| 文件路径 | 修改内容 | +| --- | --- | +| `bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript` | 添加 `drv_spi.c` 编译条件 | +| `bsp/novosns/ns800/libraries/HAL_Drivers/drivers/Kconfig` | 添加 SPI1~4 配置选项 | +| `bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig` | 添加 SPI menuconfig | +| `bsp/novosns/ns800/ns800rt7p65-nssinepad/rtconfig.h` | 添加 `BSP_USING_SPI`、`BSP_USING_SPI1` 宏 | +| `bsp/novosns/ns800/ns800rt7p65-nssinepad/applications/main.c` | 添加 SPI 回环测试代码 | + +#### 4. 驱动实现详解 + +#### 4.1 头文件 `drv_spi.h` + +定义 SPI 配置结构和 SPI 对象结构体: + +``` +struct ns800_spi_config +{ + const char *name; // SPI 总线名称,如 "spi1" + SPI_TypeDef *Instance; // SPI 外设基地址,如 SPI1 + IRQn_Type rx_irq_type; // RX 中断号 + IRQn_Type tx_irq_type; // TX 中断号 + // GPIO 配置 + GPIO_TypeDef *sck_port; // SCK 端口 + GPIO_PinNum sck_pin; // SCK 引脚号 + GPIO_AltFunc sck_mux; // SCK 复用功能 + GPIO_TypeDef *mosi_port; + GPIO_PinNum mosi_pin; + GPIO_AltFunc mosi_mux; + GPIO_TypeDef *miso_port; + GPIO_PinNum miso_pin; + GPIO_AltFunc miso_mux; +}; + +struct ns800_spi +{ + SPI_TypeDef *Instance; + struct rt_spi_bus spi_bus; // 继承 RT-Thread SPI 总线 + struct ns800_spi_config *config; +}; +``` + +#### 4.2 GPIO 初始化 `ns800_spi_gpio_init()` + +配置 SPI 四个引脚的复用模式、模拟模式、驱动强度、滤波和方向: + +``` +static void ns800_spi_gpio_init(const struct ns800_spi_config *config) +{ + // SCK - 输出 + GPIO_setPinConfig(config->sck_port, config->sck_pin, config->sck_mux); + GPIO_setAnalogMode(config->sck_port, config->sck_pin, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(config->sck_port, config->sck_pin, GPIO_PIN_TYPE_STD); + GPIO_setQualificationMode(config->sck_port, config->sck_pin, GPIO_QUAL_SYNC); + GPIO_setDirectionMode(config->sck_port, config->sck_pin, GPIO_DIR_MODE_OUT); + + // MOSI - 输出 + GPIO_setPinConfig(config->mosi_port, config->mosi_pin, config->mosi_mux); + GPIO_setAnalogMode(config->mosi_port, config->mosi_pin, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(config->mosi_port, config->mosi_pin, GPIO_PIN_TYPE_STD); + GPIO_setQualificationMode(config->mosi_port, config->mosi_pin, GPIO_QUAL_SYNC); + GPIO_setDirectionMode(config->mosi_port, config->mosi_pin, GPIO_DIR_MODE_OUT); + + // MISO - 输入,配置上拉 + GPIO_setPinConfig(config->miso_port, config->miso_pin, config->miso_mux); + GPIO_setAnalogMode(config->miso_port, config->miso_pin, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(config->miso_port, config->miso_pin, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(config->miso_port, config->miso_pin, GPIO_QUAL_SYNC); + GPIO_setDirectionMode(config->miso_port, config->miso_pin, GPIO_DIR_MODE_IN); +} +``` + +#### 4.3 SPI 配置 `ns800_spi_configure()` + +根据 `rt_spi_configuration` 参数配置 SPI 硬件: + +``` +static rt_err_t ns800_spi_configure(struct rt_spi_device *device, + struct rt_spi_configuration *configuration) +{ + // 解析 SPI 模式 (CPOL/CPHA) + switch (configuration->mode & RT_SPI_MODE_3) + { + case RT_SPI_MODE_0: protocol = SPI_PROT_POL0PHA0; break; + case RT_SPI_MODE_1: protocol = SPI_PROT_POL0PHA1; break; + case RT_SPI_MODE_2: protocol = SPI_PROT_POL1PHA0; break; + case RT_SPI_MODE_3: protocol = SPI_PROT_POL1PHA1; break; + } + + // 解析数据位宽 + switch (configuration->data_width) + { + case 8: data_width = SPI_BIT_WIDTH_8_BITS; break; + case 16: data_width = SPI_BIT_WIDTH_16_BITS; break; + default: data_width = SPI_BIT_WIDTH_8_BITS; break; + } + + // 配置并使能 SPI + SPI_disableModule(spi->config->Instance); + SPI_setConfig(spi->config->Instance, protocol, SPI_MASTER_MODE, + SPI_FULL_DUPLEX_COMM_MODE, configuration->max_hz, data_width); + SPI_resetFifo(spi->config->Instance); + SPI_enableModule(spi->config->Instance); + + return RT_EOK; +} +``` + +#### 4.4 数据传输 `ns800_spi_xfer()` + +实现 RT-Thread SPI 框架的 `xfer` 回调,使用轮询方式逐字节收发: + +``` +static rt_ssize_t ns800_spi_xfer(struct rt_spi_device *device, + struct rt_spi_message *message) +{ + while (length) + { + if (send_buf) + { + if (recv_buf) + { + recv_buf[0] = SPI_transmitReceive(instance, send_buf[0]); + recv_buf++; // 注意:recv_buf 必须递增! + } + else + { + SPI_transmitReceive(instance, send_buf[0]); + } + send_buf++; + } + else + { + if (recv_buf) + { + recv_buf[0] = SPI_transmitReceive(instance, 0xFF); + recv_buf++; + } + else + { + SPI_transmitReceive(instance, 0xFF); + } + } + length--; + } + return message->length; +} +``` + +#### 4.5 SPI1 引脚配置 + +根据 NS800 官方 datasheet,SPI1 使用以下引脚: + +| 信号 | GPIO | 引脚号 | 复用功能 | +| --- | --- | --- | --- | +| SCK | GPIOA | PIN_18 | ALT1 | +| MOSI (SIMO) | GPIOA | PIN_16 | ALT1 | +| MISO (SOMI) | GPIOA | PIN_17 | ALT1 | + +``` +#ifdef BSP_USING_SPI1 +{ + .name = "spi1", + .Instance = SPI1, + .rx_irq_type = SPI1_RX_IRQn, + .tx_irq_type = SPI1_TX_IRQn, + .sck_port = GPIOA, + .sck_pin = GPIO_PIN_18, + .sck_mux = ALT1_FUNCTION, + .mosi_port = GPIOA, + .mosi_pin = GPIO_PIN_16, + .mosi_mux = ALT1_FUNCTION, + .miso_port = GPIOA, + .miso_pin = GPIO_PIN_17, + .miso_mux = ALT1_FUNCTION, +}, +#endif +``` + +#### 4.6 总线注册 `rt_hw_spi_init()` + +``` +int rt_hw_spi_init(void) +{ + for (i = 0; i < sizeof(spi_config) / sizeof(spi_config[0]); i++) + { + ns800_spi_clock_init(spi_config[i].Instance); // 使能时钟 + spi_obj[i].config = &spi_config[i]; + spi_obj[i].spi_bus.parent.user_data = &spi_config[i]; + + result = rt_spi_bus_register(&spi_obj[i].spi_bus, + spi_config[i].name, + &ns800_spi_ops); + } + return RT_EOK; +} +INIT_BOARD_EXPORT(rt_hw_spi_init); +``` + +#### 5. 配置文件修改 + +#### 5.1 `SConscript` - 添加编译条件 + +``` +if GetDepend('BSP_USING_SPI'): + src += ['drv_spi.c'] +``` + +#### 5.2 `drivers/Kconfig` - SPI 外设配置 + +``` +menuconfig BSP_USING_SPI + bool "Enable SPI" + select RT_USING_SPI + default n + if BSP_USING_SPI + menuconfig BSP_USING_SPI1 + bool "Enable SPI1" + default n + menuconfig BSP_USING_SPI2 + bool "Enable SPI2" + default n + # ... SPI3, SPI4 类似 + endif +``` + +#### 5.3 `board/Kconfig` - 添加 SPI menu + +``` +menuconfig BSP_USING_SPI + bool "Enable SPI" + select RT_USING_SPI + default n + if BSP_USING_SPI + menuconfig BSP_USING_SPI1 + bool "Enable SPI1" + default n + menuconfig BSP_USING_SPI2 + bool "Enable SPI2" + default n + # ... SPI3, SPI4 类似 + endif +``` + +#### 5.4 `rtconfig.h` - 添加宏定义 + +``` +#define RT_USING_SPI +#define RT_USING_SPI_ISR +#define BSP_USING_SPI +#define BSP_USING_SPI1 +``` + +#### 6. 测试代码 + +#### 6.1 回环测试 `main.c` + +``` +#ifdef BSP_USING_SPI1 +#include "spi.h" + +static struct rt_spi_device spi_dev; +static struct rt_spi_configuration cfg; + +static void spi_loopback_test(void) +{ + rt_err_t result; + rt_uint8_t send_data[4] = {0x55, 0xAA, 0x5A, 0xA5}; + rt_uint8_t recv_data[4] = {0}; + + /* 将 SPI 设备挂载到 spi1 总线 */ + result = rt_spi_bus_attach_device(&spi_dev, "spi10", "spi1", RT_NULL); + if (result != RT_EOK) + { + rt_kprintf("spi loopback test: bus attach failed: %d\n", result); + return; + } + + /* 配置 SPI */ + cfg.data_width = 8; + cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB; + cfg.max_hz = 1000000; + result = rt_spi_configure(&spi_dev, &cfg); + if (result != RT_EOK) + { + rt_kprintf("spi loopback test: configure failed: %d\n", result); + return; + } + + /* 使能内部回环模式 */ + SPI_enableLoopback2(SPI1); + + rt_kprintf("spi loopback test: sending 0x%02X, 0x%02X, 0x%02X, 0x%02X\n", + send_data[0], send_data[1], send_data[2], send_data[3]); + + /* 传输数据 */ + result = rt_spi_transfer(&spi_dev, send_data, recv_data, 4); + + if (result == 4) + { + rt_kprintf("spi loopback test: received 0x%02X, 0x%02X, 0x%02X, 0x%02X\n", + recv_data[0], recv_data[1], recv_data[2], recv_data[3]); + + if (rt_memcmp(send_data, recv_data, 4) == 0) + rt_kprintf("spi loopback test: PASSED!\n"); + else + rt_kprintf("spi loopback test: FAILED - data mismatch!\n"); + } + else + { + rt_kprintf("spi loopback test: transfer failed: %d\n", result); + } + + SPI_disableLoopback2(SPI1); +} +#endif + +int main(void) +{ +#ifdef BSP_USING_SPI1 + rt_thread_mdelay(500); + spi_loopback_test(); +#endif + while (1) { /* LED blink */ } +} +``` + +#### 7. 测试步骤 + +#### 7.1 编译前配置 + +1. 使用 `menuconfig` 启用 SPI: + +``` +On-chip Peripheral Drivers → BSP_USING_SPI → BSP_USING_SPI1 +``` + +1. 或手动修改 `rtconfig.h` 添加: + + ``` + #define RT_USING_SPI + #define RT_USING_SPI_ISR + #define BSP_USING_SPI + #define BSP_USING_SPI1 + ``` + +#### 7.2 编译 + +使用 MDK5 或 GCC 编译项目。 + +#### 7.3 运行测试 + +下载到开发板,串口输出显示: + +![screenshot_image.png](./figures/spi-刘建华-01.png) + +#### 8. 常见问题 + +#### 8.1 `rt_spi_transfer` 断言失败 + +**错误信息:** + +``` +(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex) assertion failed +``` + +**原因:** 直接对 SPI 总线操作,而没有先挂载 SPI 设备。 + +**解决:** 使用 `rt_spi_bus_attach_device()` 将设备挂载到总线,再对设备操作。 + +#### 8.2 回环数据全为 0x00 + +**原因:** `recv_buf` 指针未递增,所有数据都写到 `recv_buf[0]`。 + +**解决:** + +``` +recv_buf[0] = SPI_transmitReceive(instance, send_buf[0]); +recv_buf++; // 必须递增 +``` + +#### 8.3 `rt_device_find("spi1")` 返回 NULL + +**原因:** `spi1` 是 SPI 总线名称,不是设备名称。 + +**解决:** 先 `rt_spi_bus_attach_device()` 挂载设备,再用 `rt_device_find()` 查找设备名称。 + +#### 9. 扩展到其他 SPI 外设 + +SPI2~SPI4 的配置方法与 SPI1 类似,只需: + +1. 在 `board/Kconfig` 中启用对应 SPI +2. 在 `rtconfig.h` 中添加 `BSP_USING_SPIx` 宏 +3. 在 `drv_spi.c` 的 `spi_config[]` 数组中添加引脚配置(参考 NS800 datasheet) + +#### 10. 参考资料 + +* [RT-Thread SPI 设备驱动框架](https://www.rt-thread.org/document/site/) +* NS800RT7XXX StdDriver SPI 头文件:`packages/novosns-series-latest/NS800RT7XXX/StdDriver/Inc/spi.h` +* 其他 BSP 驱动参考:`bsp/stm32/libraries/HAL_Drivers/drivers/drv_spi.c` + + ## 附drv_spi.c源码: + +``` +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-05-12 Codex first version + */ + +#include "board.h" +#include "drv_spi.h" +#include "drv_config.h" + +#ifdef RT_USING_SPI + +#define DRV_DEBUG +#define LOG_TAG "drv.spi" +#include + +#if !defined(BSP_USING_SPI1) && !defined(BSP_USING_SPI2) && !defined(BSP_USING_SPI3) && \ + !defined(BSP_USING_SPI4) +#error "Please define at least one BSP_USING_SPIx" +#endif + +enum +{ +#ifdef BSP_USING_SPI1 + SPI1_INDEX, +#endif +#ifdef BSP_USING_SPI2 + SPI2_INDEX, +#endif +#ifdef BSP_USING_SPI3 + SPI3_INDEX, +#endif +#ifdef BSP_USING_SPI4 + SPI4_INDEX, +#endif +}; + +#ifdef BSP_USING_SPI1 +void SPI1_IRQHandler(void); +#endif +#ifdef BSP_USING_SPI2 +void SPI2_IRQHandler(void); +#endif +#ifdef BSP_USING_SPI3 +void SPI3_IRQHandler(void); +#endif +#ifdef BSP_USING_SPI4 +void SPI4_IRQHandler(void); +#endif + +static struct ns800_spi_config spi_config[] = +{ +#ifdef BSP_USING_SPI1 + { + .name = "spi1", + .Instance = SPI1, + .rx_irq_type = SPI1_RX_IRQn, + .tx_irq_type = SPI1_TX_IRQn, + .sck_port = GPIOA, + .sck_pin = GPIO_PIN_18, + .sck_mux = ALT1_FUNCTION, + .mosi_port = GPIOA, + .mosi_pin = GPIO_PIN_16, + .mosi_mux = ALT1_FUNCTION, + .miso_port = GPIOA, + .miso_pin = GPIO_PIN_17, + .miso_mux = ALT1_FUNCTION, + }, +#endif +#ifdef BSP_USING_SPI2 + { + .name = "spi2", + .Instance = SPI2, + .rx_irq_type = SPI2_RX_IRQn, + .tx_irq_type = SPI2_TX_IRQn, + .sck_port = GPIOB, + .sck_pin = GPIO_PIN_0, + .sck_mux = ALT9_FUNCTION, + .mosi_port = GPIOB, + .mosi_pin = GPIO_PIN_1, + .mosi_mux = ALT9_FUNCTION, + .miso_port = GPIOB, + .miso_pin = GPIO_PIN_2, + .miso_mux = ALT9_FUNCTION, + }, +#endif +#ifdef BSP_USING_SPI3 + { + .name = "spi3", + .Instance = SPI3, + .rx_irq_type = SPI3_RX_IRQn, + .tx_irq_type = SPI3_TX_IRQn, + .sck_port = GPIOC, + .sck_pin = GPIO_PIN_0, + .sck_mux = ALT7_FUNCTION, + .mosi_port = GPIOC, + .mosi_pin = GPIO_PIN_1, + .mosi_mux = ALT7_FUNCTION, + .miso_port = GPIOC, + .miso_pin = GPIO_PIN_2, + .miso_mux = ALT7_FUNCTION, + }, +#endif +#ifdef BSP_USING_SPI4 + { + .name = "spi4", + .Instance = SPI4, + .rx_irq_type = SPI4_RX_IRQn, + .tx_irq_type = SPI4_TX_IRQn, + .sck_port = GPIOC, + .sck_pin = GPIO_PIN_4, + .sck_mux = ALT7_FUNCTION, + .mosi_port = GPIOC, + .mosi_pin = GPIO_PIN_5, + .mosi_mux = ALT7_FUNCTION, + .miso_port = GPIOC, + .miso_pin = GPIO_PIN_6, + .miso_mux = ALT7_FUNCTION, + }, +#endif +}; + +static struct ns800_spi spi_obj[sizeof(spi_config) / sizeof(spi_config[0])] = {0}; + +static void ns800_spi_gpio_init(const struct ns800_spi_config *config) +{ + RT_ASSERT(config != RT_NULL); + + GPIO_setPinConfig(config->sck_port, config->sck_pin, config->sck_mux); + GPIO_setAnalogMode(config->sck_port, config->sck_pin, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(config->sck_port, config->sck_pin, GPIO_PIN_TYPE_STD); + GPIO_setQualificationMode(config->sck_port, config->sck_pin, GPIO_QUAL_SYNC); + GPIO_setDirectionMode(config->sck_port, config->sck_pin, GPIO_DIR_MODE_OUT); + + GPIO_setPinConfig(config->mosi_port, config->mosi_pin, config->mosi_mux); + GPIO_setAnalogMode(config->mosi_port, config->mosi_pin, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(config->mosi_port, config->mosi_pin, GPIO_PIN_TYPE_STD); + GPIO_setQualificationMode(config->mosi_port, config->mosi_pin, GPIO_QUAL_SYNC); + GPIO_setDirectionMode(config->mosi_port, config->mosi_pin, GPIO_DIR_MODE_OUT); + + GPIO_setPinConfig(config->miso_port, config->miso_pin, config->miso_mux); + GPIO_setAnalogMode(config->miso_port, config->miso_pin, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(config->miso_port, config->miso_pin, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(config->miso_port, config->miso_pin, GPIO_QUAL_SYNC); + GPIO_setDirectionMode(config->miso_port, config->miso_pin, GPIO_DIR_MODE_IN); +} + +static rt_err_t ns800_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration) +{ + struct ns800_spi *spi; + SPI_TransferProtocol protocol; + SPI_BitWidth data_width; + + RT_ASSERT(device != RT_NULL); + RT_ASSERT(configuration != RT_NULL); + + spi = rt_container_of(device->bus, struct ns800_spi, spi_bus); + + ns800_spi_gpio_init(spi->config); + + switch (configuration->mode & RT_SPI_MODE_3) + { + case RT_SPI_MODE_0: + protocol = SPI_PROT_POL0PHA0; + break; + case RT_SPI_MODE_1: + protocol = SPI_PROT_POL0PHA1; + break; + case RT_SPI_MODE_2: + protocol = SPI_PROT_POL1PHA0; + break; + case RT_SPI_MODE_3: + protocol = SPI_PROT_POL1PHA1; + break; + default: + protocol = SPI_PROT_POL0PHA0; + break; + } + + switch (configuration->data_width) + { + case 8: + data_width = SPI_BIT_WIDTH_8_BITS; + break; + case 16: + data_width = SPI_BIT_WIDTH_16_BITS; + break; + default: + data_width = SPI_BIT_WIDTH_8_BITS; + break; + } + + SPI_disableModule(spi->config->Instance); + + SPI_setConfig(spi->config->Instance, + protocol, + SPI_MASTER_MODE, + SPI_FULL_DUPLEX_COMM_MODE, + configuration->max_hz, + data_width); + + SPI_resetFifo(spi->config->Instance); + SPI_enableModule(spi->config->Instance); + + return RT_EOK; +} + +static rt_ssize_t ns800_spi_xfer(struct rt_spi_device *device, struct rt_spi_message *message) +{ + struct ns800_spi *spi; + SPI_TypeDef *instance; + const rt_uint8_t *send_buf; + rt_uint8_t *recv_buf; + rt_size_t length; + + RT_ASSERT(device != RT_NULL); + RT_ASSERT(device->bus != RT_NULL); + + spi = rt_container_of(device->bus, struct ns800_spi, spi_bus); + instance = spi->config->Instance; + length = message->length; + send_buf = message->send_buf; + recv_buf = message->recv_buf; + + if (message->cs_take) + { + rt_pin_write(device->cs_pin, PIN_LOW); + } + + while (length) + { + if (send_buf) + { + if (recv_buf) + { + recv_buf[0] = SPI_transmitReceive(instance, send_buf[0]); + recv_buf++; + } + else + { + SPI_transmitReceive(instance, send_buf[0]); + } + send_buf++; + } + else + { + if (recv_buf) + { + recv_buf[0] = SPI_transmitReceive(instance, 0xFF); + recv_buf++; + } + else + { + SPI_transmitReceive(instance, 0xFF); + } + } + length--; + } + + if (message->cs_release) + { + rt_pin_write(device->cs_pin, PIN_HIGH); + } + + return message->length; +} + +static const struct rt_spi_ops ns800_spi_ops = +{ + .configure = ns800_spi_configure, + .xfer = ns800_spi_xfer, +}; + +static void ns800_spi_clock_init(SPI_TypeDef *Instance) +{ +#ifdef BSP_USING_SPI1 + if (Instance == SPI1) + { + RCC_enableSpi1Clock(); + RCC_resetSpi1Module(); + RCC_releaseSpi1Module(); + } +#endif +#ifdef BSP_USING_SPI2 + if (Instance == SPI2) + { + RCC_enableSpi2Clock(); + RCC_resetSpi2Module(); + RCC_releaseSpi2Module(); + } +#endif +#ifdef BSP_USING_SPI3 + if (Instance == SPI3) + { + RCC_enableSpi3Clock(); + RCC_resetSpi3Module(); + RCC_releaseSpi3Module(); + } +#endif +#ifdef BSP_USING_SPI4 + if (Instance == SPI4) + { + RCC_enableSpi4Clock(); + RCC_resetSpi4Module(); + RCC_releaseSpi4Module(); + } +#endif +} + +int rt_hw_spi_init(void) +{ + rt_size_t i; + rt_err_t result; + + for (i = 0; i < sizeof(spi_config) / sizeof(spi_config[0]); i++) + { + ns800_spi_clock_init(spi_config[i].Instance); + + spi_obj[i].config = &spi_config[i]; + spi_obj[i].spi_bus.parent.user_data = &spi_config[i]; + + result = rt_spi_bus_register(&spi_obj[i].spi_bus, + spi_config[i].name, + &ns800_spi_ops); + if (result != RT_EOK) + { + LOG_E("rt_spi_bus_register(%s) failed: %d", spi_config[i].name, result); + } + } + + return RT_EOK; +} + +INIT_BOARD_EXPORT(rt_hw_spi_init); + +#endif +``` + +drv_spi.h + +``` +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-05-12 Codex first version + */ + +#ifndef __DRV_SPI_H__ +#define __DRV_SPI_H__ + +#include +#include "rtdevice.h" +#include +#include + +#ifdef RT_USING_SPI + +struct ns800_spi_config +{ + const char *name; + SPI_TypeDef *Instance; + IRQn_Type rx_irq_type; + IRQn_Type tx_irq_type; + GPIO_TypeDef *sck_port; + GPIO_PinNum sck_pin; + GPIO_AltFunc sck_mux; + GPIO_TypeDef *mosi_port; + GPIO_PinNum mosi_pin; + GPIO_AltFunc mosi_mux; + GPIO_TypeDef *miso_port; + GPIO_PinNum miso_pin; + GPIO_AltFunc miso_mux; +}; + +struct ns800_spi +{ + SPI_TypeDef *Instance; + struct rt_spi_bus spi_bus; + struct ns800_spi_config *config; +}; + +int rt_hw_spi_init(void); + +#endif + +#endif /* __DRV_SPI_H__ */ +``` + +## 七、纳芯微NS800RT7P65D上的SPI实践(张工) + +作者:张工 + +原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的QSPI实践 + +源文章: + +### 1.前言 + +从手册中可以看到该芯片有一路qspi,4路spi,由于spi驱动已经适配好了,所以本次适配纳芯微NS800RT7P65D芯片的qspi驱动。 + +### 2.准备工作 + +拉取当前rt-thread的最新sdk,最新的sdk中才有纳芯微的工程,使用命令git pull 即可。在bsp路径下会增加novosns文件夹,里面就是纳芯微的rt-thread 的适配工程。 + +![screenshot_image.png](./figures/spi-张工-01.png) + +使用env工具生成mdk工程,由于我的环境不知道什么原因,env工具生成不了keil工程。最终没有解决掉,找群友发了一个工程进行qspi驱动的适配。 + +![screenshot_image.png](./figures/spi-张工-02.png) + +### 3.适配过程 + +#### 1、创建drv_qspi.h 和 drv_qspi.c这两个文件, + +在drv_qspi.c中添加qspi的引脚初始化 + +``` +static void qspi_pin_init(void) +{ + GPIO_setPinConfig(GPIO_15_QSPI_NCS); + GPIO_setAnalogMode(GPIO_15, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_15, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_15, GPIO_QUAL_ASYNC); + + GPIO_setPinConfig(GPIO_16_QSPI_D0); + GPIO_setAnalogMode(GPIO_16, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_16, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_16, GPIO_QUAL_ASYNC); + GPIO_setDirectionMode(GPIO_16, GPIO_DIR_MODE_OUT); + + GPIO_setPinConfig(GPIO_17_QSPI_D1); + GPIO_setAnalogMode(GPIO_17, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_17, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_17, GPIO_QUAL_ASYNC); + GPIO_setDirectionMode(GPIO_17, GPIO_DIR_MODE_IN); + + GPIO_setPinConfig(GPIO_18_QSPI_D2); + GPIO_setAnalogMode(GPIO_18, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_18, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_18, GPIO_QUAL_ASYNC); + GPIO_setDirectionMode(GPIO_18, GPIO_DIR_MODE_IN); + + GPIO_setPinConfig(GPIO_20_QSPI_D3); + GPIO_setAnalogMode(GPIO_20, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_20, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_20, GPIO_QUAL_ASYNC); + GPIO_setDirectionMode(GPIO_20, GPIO_DIR_MODE_IN); + + GPIO_setPinConfig(GPIO_21_QSPI_SCLK); + GPIO_setAnalogMode(GPIO_21, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_21, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_21, GPIO_QUAL_ASYNC); +} +``` + +#### 2、添加qspi的时钟初始化 + +``` +static void qspi_clock_enable(void) +{ + RCC_unlockRccRegister(); + RCC_enableQspiClock(); + RCC_resetQspiModule(); + RCC_releaseQspiModule(); + RCC_lockRccRegister(); +} +``` + +#### 3、qspi的初始化 + +``` +static void qspi_controller_init(void) +{ + QSPI_open(EXTENDED_SPI_PROTOCOL, + HLCK_DIV_48, + ADDRESS_3_BYTES, + DEFAULT_DUMMY_CYCLE, + HIGH_LVL_4_QSCK, + FAST_READ_QUAD_IN_OUT); +} +``` + +#### 4、qspi的设备接口 + +``` +static rt_err_t qspi_configure(struct rt_spi_device *device, + struct rt_spi_configuration *cfg) +{ + device->config = *cfg; + return RT_EOK; +} +``` + +#### 5、核心数据传输回调函数实现 + +``` +static rt_ssize_t qspi_xfer(struct rt_spi_device *device, + struct rt_spi_message *message) +{ + struct rt_qspi_message *qspi_msg = rt_container_of(message, struct rt_qspi_message, parent); + uint8_t *send_buf = (uint8_t *)message->send_buf; + uint8_t *recv_buf = (uint8_t *)message->recv_buf; + uint32_t send_len = (send_buf != NULL) ? message->length : 0; + uint32_t recv_len = (recv_buf != NULL) ? message->length : 0; + + /* Build command buffer (instruction + address + dummy) */ + uint8_t cmd_buf[260]; + uint8_t *ptr = cmd_buf; + + if (qspi_msg->instruction.content) + *ptr++ = qspi_msg->instruction.content; + + if (qspi_msg->address.size) { + uint32_t addr = qspi_msg->address.content; + uint8_t addr_bytes = qspi_msg->address.size / 8; + for (uint8_t i = addr_bytes; i > 0; i--) + *ptr++ = (addr >> (8*(i-1))) & 0xFF; + } + + if (qspi_msg->dummy_cycles) { + uint8_t dummy_bytes = qspi_msg->dummy_cycles / 8; + for (uint8_t i = 0; i < dummy_bytes; i++) + *ptr++ = 0xFF; + } + + uint32_t cmd_len = ptr - cmd_buf; + + /* Execute transfer using direct API */ + if (send_len && recv_len) { + /* Half-duplex not supported in this driver */ + return 0; + } + + if (send_len) { + /* Append send data to command buffer */ + if (cmd_len + send_len <= sizeof(cmd_buf)) { + rt_memcpy(ptr, send_buf, send_len); + QSPI_writeDirect(cmd_buf, cmd_len + send_len, 0); + } else { + /* Not enough buffer, split into two writes? Not needed for normal use */ + return 0; + } + } else if (recv_len) { + /* Write command, keep CS active, then read data */ + QSPI_writeDirect(cmd_buf, cmd_len, 1); + QSPI_readDirect(recv_buf, recv_len); + } else { + /* No data phase, just send command */ + QSPI_writeDirect(cmd_buf, cmd_len, 0); + } + + return message->length; +} +``` + +#### 6、自动初始化qspi + +``` +int rt_hw_qspi_init(void) +{ + rt_kprintf("Initializing QSPI...\n"); + qspi_clock_enable(); + qspi_pin_init(); + qspi_controller_init(); + + qspi_cfg.parent.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; + qspi_cfg.parent.max_hz = 25000000; + qspi_cfg.parent.data_width = 8; + qspi_cfg.medium_size = 0x1000000; + qspi_cfg.ddr_mode = 0; + qspi_cfg.qspi_dl_width = 0; + + rt_qspi_bus_register(&qspi_bus, "qspi0", &qspi_ops); + + qspi_dev.parent.bus = &qspi_bus; + qspi_dev.parent.config = qspi_cfg.parent; + qspi_dev.config = qspi_cfg; + rt_spi_bus_attach_device(&qspi_dev.parent, "qspi_dev", "qspi0", NULL); + + return 0; +} + +INIT_DEVICE_EXPORT(rt_hw_qspi_init); +``` + +#### 7、当前通过qpsi接口外接了w25q128 flash,用于测试qspi,操作命令定义 + +``` +#define CMD_READ_STATUS 0x05 +#define CMD_WRITE_ENABLE 0x06 +#define CMD_SECTOR_ERASE 0x20 +#define CMD_PAGE_PROGRAM 0x02 +#define CMD_READ_DATA 0x03 + +#define TEST_ADDR 0x001FF000 +#define TEST_SIZE 256 +``` + +#### 8、qspi等待准备完成 + +``` +static void qspi_wait_ready(struct rt_qspi_device *qspi) +{ + struct rt_qspi_message msg = {0}; + uint8_t status; + msg.instruction.content = CMD_READ_STATUS; + msg.instruction.qspi_lines = 1; + msg.address.size = 0; + msg.dummy_cycles = 0; + msg.qspi_data_lines = 1; + msg.parent.send_buf = RT_NULL; + msg.parent.recv_buf = &status; + msg.parent.length = 1; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + + do { + if (rt_qspi_transfer_message(qspi, &msg) != 1) { + rt_kprintf("Read status error\n"); + return; + } + if (status & 0x01) rt_thread_mdelay(1); + } while (status & 0x01); +} +``` + +#### 9、qspi写使能 + +``` +static void qspi_write_enable(struct rt_qspi_device *qspi) +{ + struct rt_qspi_message msg = {0}; + msg.instruction.content = CMD_WRITE_ENABLE; + msg.instruction.qspi_lines = 1; + msg.parent.send_buf = RT_NULL; + msg.parent.length = 0; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + rt_qspi_transfer_message(qspi, &msg); +} +``` + +#### 10、qspi_erase_sector + +``` +static int qspi_erase_sector(struct rt_qspi_device *qspi, uint32_t addr) +{ + struct rt_qspi_message msg = {0}; + uint8_t cmd[4]; + cmd[0] = CMD_SECTOR_ERASE; + cmd[1] = (addr >> 16) & 0xFF; + cmd[2] = (addr >> 8) & 0xFF; + cmd[3] = addr & 0xFF; + qspi_write_enable(qspi); + msg.parent.send_buf = cmd; + msg.parent.length = 4; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + if (rt_qspi_transfer_message(qspi, &msg) != 4) + return -1; + qspi_wait_ready(qspi); + return 0; +} +``` + +#### 11、qspi_page_program + +``` +static int qspi_page_program(struct rt_qspi_device *qspi, uint32_t addr, + const uint8_t *data, uint32_t len) +{ + struct rt_qspi_message msg = {0}; + uint8_t cmd[4 + 256]; + if (len > 256) len = 256; + cmd[0] = CMD_PAGE_PROGRAM; + cmd[1] = (addr >> 16) & 0xFF; + cmd[2] = (addr >> 8) & 0xFF; + cmd[3] = addr & 0xFF; + rt_memcpy(cmd + 4, data, len); + qspi_write_enable(qspi); + msg.parent.send_buf = cmd; + msg.parent.length = 4 + len; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + if (rt_qspi_transfer_message(qspi, &msg) != 4 + len) + return -1; + qspi_wait_ready(qspi); + return len; +} +``` + +#### 12、qspi读数据 + +``` +static int qspi_read_data(struct rt_qspi_device *qspi, uint32_t addr, + uint8_t *buf, uint32_t len) +{ + struct rt_qspi_message msg = {0}; + msg.instruction.content = CMD_READ_DATA; + msg.instruction.qspi_lines = 1; + msg.address.content = addr; + msg.address.size = 24; + msg.address.qspi_lines = 1; + msg.dummy_cycles = 0; + msg.qspi_data_lines = 1; + msg.parent.send_buf = RT_NULL; + msg.parent.recv_buf = buf; + msg.parent.length = len; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + return (rt_qspi_transfer_message(qspi, &msg) == len) ? 0 : -1; +} +``` + +#### 13、qspi读ID + +``` +static uint32_t qspi_read_jedec_id(struct rt_qspi_device *qspi) +{ + struct rt_qspi_message msg = {0}; + uint8_t id[3] = {0}; + msg.instruction.content = 0x9F; + msg.instruction.qspi_lines = 1; + msg.address.size = 0; + msg.dummy_cycles = 0; + msg.qspi_data_lines = 1; + msg.parent.send_buf = RT_NULL; + msg.parent.recv_buf = id; + msg.parent.length = 3; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + if (rt_qspi_transfer_message(qspi, &msg) == 3) + return (id[0] << 16) | (id[1] << 8) | id[2]; + return 0; +} +``` + +#### 14、编写qspi驱动测试代码,并导入命令 + +``` +void qspi_test(void) +{ + struct rt_device *dev = rt_device_find("qspi_dev"); + if (!dev) { + rt_kprintf("QSPI device not found!\n"); + return; + } + struct rt_qspi_device *qspi = (struct rt_qspi_device *)dev; + + rt_kprintf("\n========== QSPI Test ==========\n"); + uint32_t id = qspi_read_jedec_id(qspi); + rt_kprintf("JEDEC ID : 0x%06X\n", id); + if (id != 0xEF4018) { + rt_kprintf("Unexpected ID, check hardware or driver.\n"); + return; + } + + uint8_t write_buf[TEST_SIZE]; + uint8_t read_buf[TEST_SIZE]; + for (int i = 0; i < TEST_SIZE; i++) + write_buf[i] = i & 0xFF; + + rt_kprintf("Erasing sector at 0x%08X... ", TEST_ADDR); + if (qspi_erase_sector(qspi, TEST_ADDR) != 0) { + rt_kprintf("FAILED\n"); + return; + } + rt_kprintf("OK\n"); + + rt_kprintf("Writing %d bytes... ", TEST_SIZE); + if (qspi_page_program(qspi, TEST_ADDR, write_buf, TEST_SIZE) != TEST_SIZE) { + rt_kprintf("FAILED\n"); + return; + } + rt_kprintf("OK\n"); + + rt_kprintf("Reading back... "); + if (qspi_read_data(qspi, TEST_ADDR, read_buf, TEST_SIZE) != 0) { + rt_kprintf("FAILED\n"); + return; + } + rt_kprintf("OK\n"); + + if (rt_memcmp(write_buf, read_buf, TEST_SIZE) == 0) { + rt_kprintf("Data verification: PASSED\n"); + } else { + rt_kprintf("Data verification: FAILED\n"); + } + rt_kprintf("========== Test Finished ==========\n"); +} +MSH_CMD_EXPORT(qspi_test, QSPI test); +``` + +#### 15、配置文件更改,SConscript添加编译条件 + +``` +if GetDepend('BSP_USING_QSPI'): + src += ['drv_qspi.c'] +``` + +#### 16、配置文件更改,drivers/Kconfig - QSPI 外设配置 + +``` +menuconfig BSP_USING_QSPI + bool "Enable SPI" + select RT_USING_QSPI + default n + endif +``` + +#### 17、配置文件更改,board/Kconfig - 添加 QSPI menu + +``` +menuconfig BSP_USING_QSPI + bool "Enable QSPI" + select RT_USING_QSPI + default n +``` + +#### 18、rtconfig.h添加宏定义 + +``` +#define RT_USING_QSPI +``` + +### 4.验证 + +代码修改好后,编译完成升级,查看输出信息,qspi初始化正常 + +![screenshot_image.png](./figures/spi-张工-03.png) + +在终端中输入list device,qspi设备初始化正常 + +![screenshot_image.png](./figures/spi-张工-04.png) + +在终端中输入help命令,qspi的测试代码初始化正常,可以看到测试命令 + +![screenshot_image.png](./figures/spi-张工-05.png) + +在终端中输入qspi的测试命令:qspi_test,输出以下字符,测试正常。 + +![screenshot_image.png](./figures/spi-张工-06.png) + +### 5.附件 + +完整代码: + +#### 1、drv_qspi.h + +``` +#ifndef __DRV_QSPI_H__ +#define __DRV_QSPI_H__ + +#ifdef RT_USING_QSPI + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int rt_hw_qspi_init(void); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif /* __DRV_QSPI_H__ */ +``` + +#### 2、drv_qspi.c + +``` +/* + * QSPI driver for NS800RT7xxx + W25Q128 + * Fully supports RT-Thread QSPI (rt_qspi_transfer_message) + * All transfers use QSPI_writeDirect / QSPI_readDirect (same as working direct API) + */ + +#include +#include +#include "qspi.h" +#include "board.h" + +#ifdef RT_USING_QSPI + +/*=========================================================================== + * GPIO initialization + *===========================================================================*/ +static void qspi_pin_init(void) +{ + GPIO_setPinConfig(GPIO_15_QSPI_NCS); + GPIO_setAnalogMode(GPIO_15, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_15, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_15, GPIO_QUAL_ASYNC); + + GPIO_setPinConfig(GPIO_16_QSPI_D0); + GPIO_setAnalogMode(GPIO_16, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_16, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_16, GPIO_QUAL_ASYNC); + GPIO_setDirectionMode(GPIO_16, GPIO_DIR_MODE_OUT); + + GPIO_setPinConfig(GPIO_17_QSPI_D1); + GPIO_setAnalogMode(GPIO_17, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_17, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_17, GPIO_QUAL_ASYNC); + GPIO_setDirectionMode(GPIO_17, GPIO_DIR_MODE_IN); + + GPIO_setPinConfig(GPIO_18_QSPI_D2); + GPIO_setAnalogMode(GPIO_18, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_18, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_18, GPIO_QUAL_ASYNC); + GPIO_setDirectionMode(GPIO_18, GPIO_DIR_MODE_IN); + + GPIO_setPinConfig(GPIO_20_QSPI_D3); + GPIO_setAnalogMode(GPIO_20, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_20, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_20, GPIO_QUAL_ASYNC); + GPIO_setDirectionMode(GPIO_20, GPIO_DIR_MODE_IN); + + GPIO_setPinConfig(GPIO_21_QSPI_SCLK); + GPIO_setAnalogMode(GPIO_21, GPIO_ANALOG_DISABLED); + GPIO_setPadConfig(GPIO_21, GPIO_PIN_TYPE_PULLUP); + GPIO_setQualificationMode(GPIO_21, GPIO_QUAL_ASYNC); +} + +/*=========================================================================== + * QSPI clock & controller initialization + *===========================================================================*/ +static void qspi_clock_enable(void) +{ + RCC_unlockRccRegister(); + RCC_enableQspiClock(); + RCC_resetQspiModule(); + RCC_releaseQspiModule(); + RCC_lockRccRegister(); +} + +static void qspi_controller_init(void) +{ + QSPI_open(EXTENDED_SPI_PROTOCOL, + HLCK_DIV_48, + ADDRESS_3_BYTES, + DEFAULT_DUMMY_CYCLE, + HIGH_LVL_4_QSCK, + FAST_READ_QUAD_IN_OUT); +} + +/*=========================================================================== + * RT-Thread QSPI device interface + *===========================================================================*/ +static struct rt_spi_bus qspi_bus; +static struct rt_qspi_device qspi_dev; +static struct rt_qspi_configuration qspi_cfg; + +static rt_err_t qspi_configure(struct rt_spi_device *device, + struct rt_spi_configuration *cfg) +{ + device->config = *cfg; + return RT_EOK; +} + +static rt_ssize_t qspi_xfer(struct rt_spi_device *device, + struct rt_spi_message *message) +{ + struct rt_qspi_message *qspi_msg = rt_container_of(message, struct rt_qspi_message, parent); + uint8_t *send_buf = (uint8_t *)message->send_buf; + uint8_t *recv_buf = (uint8_t *)message->recv_buf; + uint32_t send_len = (send_buf != NULL) ? message->length : 0; + uint32_t recv_len = (recv_buf != NULL) ? message->length : 0; + + /* Build command buffer (instruction + address + dummy) */ + uint8_t cmd_buf[260]; + uint8_t *ptr = cmd_buf; + + if (qspi_msg->instruction.content) + *ptr++ = qspi_msg->instruction.content; + + if (qspi_msg->address.size) { + uint32_t addr = qspi_msg->address.content; + uint8_t addr_bytes = qspi_msg->address.size / 8; + for (uint8_t i = addr_bytes; i > 0; i--) + *ptr++ = (addr >> (8*(i-1))) & 0xFF; + } + + if (qspi_msg->dummy_cycles) { + uint8_t dummy_bytes = qspi_msg->dummy_cycles / 8; + for (uint8_t i = 0; i < dummy_bytes; i++) + *ptr++ = 0xFF; + } + + uint32_t cmd_len = ptr - cmd_buf; + + /* Execute transfer using direct API */ + if (send_len && recv_len) { + /* Half-duplex not supported in this driver */ + return 0; + } + + if (send_len) { + /* Append send data to command buffer */ + if (cmd_len + send_len <= sizeof(cmd_buf)) { + rt_memcpy(ptr, send_buf, send_len); + QSPI_writeDirect(cmd_buf, cmd_len + send_len, 0); + } else { + /* Not enough buffer, split into two writes? Not needed for normal use */ + return 0; + } + } else if (recv_len) { + /* Write command, keep CS active, then read data */ + QSPI_writeDirect(cmd_buf, cmd_len, 1); + QSPI_readDirect(recv_buf, recv_len); + } else { + /* No data phase, just send command */ + QSPI_writeDirect(cmd_buf, cmd_len, 0); + } + + return message->length; +} + +static const struct rt_spi_ops qspi_ops = { + .configure = qspi_configure, + .xfer = qspi_xfer, +}; + +/*=========================================================================== + * Initialization + *===========================================================================*/ +int rt_hw_qspi_init(void) +{ + rt_kprintf("Initializing QSPI...\n"); + qspi_clock_enable(); + qspi_pin_init(); + qspi_controller_init(); + + qspi_cfg.parent.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; + qspi_cfg.parent.max_hz = 25000000; + qspi_cfg.parent.data_width = 8; + qspi_cfg.medium_size = 0x1000000; + qspi_cfg.ddr_mode = 0; + qspi_cfg.qspi_dl_width = 0; + + rt_qspi_bus_register(&qspi_bus, "qspi0", &qspi_ops); + + qspi_dev.parent.bus = &qspi_bus; + qspi_dev.parent.config = qspi_cfg.parent; + qspi_dev.config = qspi_cfg; + rt_spi_bus_attach_device(&qspi_dev.parent, "qspi_dev", "qspi0", NULL); + + return 0; +} + +INIT_DEVICE_EXPORT(rt_hw_qspi_init); + +#endif +``` + +#### 3、测试代码qspi_test.c + +``` +#include +#include + +#ifdef RT_USING_QSPI + +#define CMD_READ_STATUS 0x05 +#define CMD_WRITE_ENABLE 0x06 +#define CMD_SECTOR_ERASE 0x20 +#define CMD_PAGE_PROGRAM 0x02 +#define CMD_READ_DATA 0x03 + +#define TEST_ADDR 0x001FF000 +#define TEST_SIZE 256 + +static void qspi_wait_ready(struct rt_qspi_device *qspi) +{ + struct rt_qspi_message msg = {0}; + uint8_t status; + msg.instruction.content = CMD_READ_STATUS; + msg.instruction.qspi_lines = 1; + msg.address.size = 0; + msg.dummy_cycles = 0; + msg.qspi_data_lines = 1; + msg.parent.send_buf = RT_NULL; + msg.parent.recv_buf = &status; + msg.parent.length = 1; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + + do { + if (rt_qspi_transfer_message(qspi, &msg) != 1) { + rt_kprintf("Read status error\n"); + return; + } + if (status & 0x01) rt_thread_mdelay(1); + } while (status & 0x01); +} + +static void qspi_write_enable(struct rt_qspi_device *qspi) +{ + struct rt_qspi_message msg = {0}; + msg.instruction.content = CMD_WRITE_ENABLE; + msg.instruction.qspi_lines = 1; + msg.parent.send_buf = RT_NULL; + msg.parent.length = 0; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + rt_qspi_transfer_message(qspi, &msg); +} + +static int qspi_erase_sector(struct rt_qspi_device *qspi, uint32_t addr) +{ + struct rt_qspi_message msg = {0}; + uint8_t cmd[4]; + cmd[0] = CMD_SECTOR_ERASE; + cmd[1] = (addr >> 16) & 0xFF; + cmd[2] = (addr >> 8) & 0xFF; + cmd[3] = addr & 0xFF; + qspi_write_enable(qspi); + msg.parent.send_buf = cmd; + msg.parent.length = 4; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + if (rt_qspi_transfer_message(qspi, &msg) != 4) + return -1; + qspi_wait_ready(qspi); + return 0; +} + +static int qspi_page_program(struct rt_qspi_device *qspi, uint32_t addr, + const uint8_t *data, uint32_t len) +{ + struct rt_qspi_message msg = {0}; + uint8_t cmd[4 + 256]; + if (len > 256) len = 256; + cmd[0] = CMD_PAGE_PROGRAM; + cmd[1] = (addr >> 16) & 0xFF; + cmd[2] = (addr >> 8) & 0xFF; + cmd[3] = addr & 0xFF; + rt_memcpy(cmd + 4, data, len); + qspi_write_enable(qspi); + msg.parent.send_buf = cmd; + msg.parent.length = 4 + len; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + if (rt_qspi_transfer_message(qspi, &msg) != 4 + len) + return -1; + qspi_wait_ready(qspi); + return len; +} + +static int qspi_read_data(struct rt_qspi_device *qspi, uint32_t addr, + uint8_t *buf, uint32_t len) +{ + struct rt_qspi_message msg = {0}; + msg.instruction.content = CMD_READ_DATA; + msg.instruction.qspi_lines = 1; + msg.address.content = addr; + msg.address.size = 24; + msg.address.qspi_lines = 1; + msg.dummy_cycles = 0; + msg.qspi_data_lines = 1; + msg.parent.send_buf = RT_NULL; + msg.parent.recv_buf = buf; + msg.parent.length = len; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + return (rt_qspi_transfer_message(qspi, &msg) == len) ? 0 : -1; +} + +static uint32_t qspi_read_jedec_id(struct rt_qspi_device *qspi) +{ + struct rt_qspi_message msg = {0}; + uint8_t id[3] = {0}; + msg.instruction.content = 0x9F; + msg.instruction.qspi_lines = 1; + msg.address.size = 0; + msg.dummy_cycles = 0; + msg.qspi_data_lines = 1; + msg.parent.send_buf = RT_NULL; + msg.parent.recv_buf = id; + msg.parent.length = 3; + msg.parent.cs_take = 1; + msg.parent.cs_release = 1; + if (rt_qspi_transfer_message(qspi, &msg) == 3) + return (id[0] << 16) | (id[1] << 8) | id[2]; + return 0; +} + +void qspi_test(void) +{ + struct rt_device *dev = rt_device_find("qspi_dev"); + if (!dev) { + rt_kprintf("QSPI device not found!\n"); + return; + } + struct rt_qspi_device *qspi = (struct rt_qspi_device *)dev; + + rt_kprintf("\n========== QSPI Test ==========\n"); + uint32_t id = qspi_read_jedec_id(qspi); + rt_kprintf("JEDEC ID : 0x%06X\n", id); + if (id != 0xEF4018) { + rt_kprintf("Unexpected ID, check hardware or driver.\n"); + return; + } + + uint8_t write_buf[TEST_SIZE]; + uint8_t read_buf[TEST_SIZE]; + for (int i = 0; i < TEST_SIZE; i++) + write_buf[i] = i & 0xFF; + + rt_kprintf("Erasing sector at 0x%08X... ", TEST_ADDR); + if (qspi_erase_sector(qspi, TEST_ADDR) != 0) { + rt_kprintf("FAILED\n"); + return; + } + rt_kprintf("OK\n"); + + rt_kprintf("Writing %d bytes... ", TEST_SIZE); + if (qspi_page_program(qspi, TEST_ADDR, write_buf, TEST_SIZE) != TEST_SIZE) { + rt_kprintf("FAILED\n"); + return; + } + rt_kprintf("OK\n"); + + rt_kprintf("Reading back... "); + if (qspi_read_data(qspi, TEST_ADDR, read_buf, TEST_SIZE) != 0) { + rt_kprintf("FAILED\n"); + return; + } + rt_kprintf("OK\n"); + + if (rt_memcmp(write_buf, read_buf, TEST_SIZE) == 0) { + rt_kprintf("Data verification: PASSED\n"); + } else { + rt_kprintf("Data verification: FAILED\n"); + } + rt_kprintf("========== Test Finished ==========\n"); +} +MSH_CMD_EXPORT(qspi_test, QSPI test); + +#endif +``` + +## 八、纳芯微NS800RT7P65D上的eCAP实践(程廷桢) + +作者:程廷桢 + +原文标题:【纳芯微】纳芯微 NS800RT7P65 eCAP 模块应用笔记 + +源文章: + +### 前言 + +NS800RT7P65 是纳芯微 NSSine™系列高性能实时控制 MCU,搭载 400MHz Cortex‑M7 内核,集成 7 路 ECAP(增强型捕获)模块,专为电机测速、脉冲周期 / 占空比测量、位置传感器信号采集等场景设计。本文基于 RT‑Thread 实时操作系统,从硬件特性、寄存器配置、RT‑Thread 驱动适配、实测验证、避坑总结五大维度,分享 ECAP 模块的开发与测评经验,为国产化电力电子、运动控制项目提供参考。 + +### ECAP 驱动 + +NS800RT7P65 有7个 ECAP (增强型输入捕获)模块。用于精准捕获外部数字信号的边沿跳变、测量脉冲宽度、信号周期、占空比。 + +核心特色:内置 XBAR 交叉开关 + +* ECAP 不绑定固定引脚,任意 GPIO 均可通过 XBAR 路由为 ECAP 输入 + +**驱动文件** + +`bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_ecap.c` + +#### 硬件配置 + +驱动采用数组配置 + 条件编译管理多 ECAP 实例(ECAP1 ~ ECAP7),通过 **BSP_USING_ECAPx** 宏开关按需启用通道。初始化按照物理GPIO -> XBAR通道 -> ECAP 模块的顺序进行,默认对 ECAP 内部计数器进行 30*2 分频。 + +``` +static const struct rt_ecap_config ecap_config[] = +{ +#ifdef BSP_USING_ECAP1 + { + .name = "ecap1", + .instance = ECAP1, /* 底层ECAP硬件外设基地址 */ + .irq_type = ECAP1_IRQn, /* 中断号 */ + .input_signal = ECAP_INPUT_XBAR_INPUT7, /* ECAP 选择XBAR输入通道 */ + .input_xbar = XBAR_INPUT7, /* XBAR 交叉开关通道号 */ + .input_source = GPIO_PIN_16, + .pre_scaler = 30U, /* 实际预分频是 30*2 */ + .gpio_port = GPIOA, + .gpio_pin = GPIO_PIN_16, + .gpio_mux = ALT0_FUNCTION, + .irq_handler = ECAP1_IRQHandler + }, +#endif +} +``` + +#### 边沿配置 + +ECAP 模块可以设置 4个事件,每个事件可以配置一个跳变沿。每个事件发生的时候会记录相对应的定时器计数。比如说一个方波信号,起始信号为高电平,当方波信号跳变到下降沿时,产生Event1,并记录当前计数器的计数值。 +驱动固定配置连续捕获模式,4 个事件对应边沿如下:: + +| 事件编号 | 触发边沿 | 触发时机说明 | +| --- | --- | --- | +| Event_1 | 下降沿 Falling | 第 1 次电平由高→低 | +| Event_2 | 上降沿 Rising | 第 1 次电平由低→高 | +| Event_3 | 下降沿 Falling | 第 2 次电平由高→低 | +| Event_4 | 上降沿 Rising | 第 2 次电平由低→高 | + +``` +// 工作模式:连续捕获模式,以 EVENT4 为一轮结束点 +ECAP_setCaptureMode(instance, ECAP_CONTINUOUS_CAPTURE_MODE, ECAP_EVENT_4); +// 每个事件触发后,计数器自动清零 +ECAP_enableCounterResetOnEvent(instance, ECAP_EVENT_x); +``` + +* **计数器自动复位** :每一次边沿触发后,ECAP计数器清零重新计数 +* `cap1 ~ cap4 ~` 存储的是 **相邻两个边沿之间的计数值** ,而非绝对时间戳。 + +驱动中将 Event2 和 Event3 记为一个周期,period_total 可以通过计算得到所输入方波信号的周期。 + +``` +cap->period_high = cap->cap2; // 高电平宽度 +cap->period_low = cap->cap3; // 低电平宽度 +cap->period_total = cap->cap2 + cap->cap3; // 信号总周期 +``` + +**注意:** + +rt_ecap_read 在获取不到由中断发出信号量时,会直接返回。也就意味着在应用层使用 rt_device_read 时需要判断读取的字节数来确认数据的实时性。 + +### ECAP 应用 + +该代码是 **RT-Thread 下 ECAP 驱动的上层应用示例** ,同时启用 **ecap1** 、 **ecap2** 两路捕获,采用「中断回调 + 线程轮询读取」双方式获取捕获数据,打印脉冲 / 周期参数。 + +``` +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-05-06 Jiawei.Deng first version + */ + +#include +#include +#include +#include "drv_ecap.h" + +/* defined the LED1 pin: GPIO_68 = PC4 */ +#define LED1_PIN PIN_NUM(GPIO_68) + +#define ECAP_DEV "ecap1" +#define ECAP_OTHER_DEV "ecap2" + +static struct rt_mutex ecap_lock; + +typedef struct { + rt_uint32_t cap1; + rt_uint32_t cap2; + rt_uint32_t cap3; + rt_uint32_t cap4; + rt_uint32_t period_low; + rt_uint32_t period_high; + rt_uint32_t period_total; + rt_uint32_t status; + + rt_int32_t irq_cnt; + +}usr_ecap_rx_struct; + +void usr_ecap_rx_cb(struct rt_ecap_capture *capture, void *user_data) +{ + static rt_int32_t irq_cnt = 0; + usr_ecap_rx_struct *usr_struct = (usr_ecap_rx_struct *)user_data; + + usr_struct->cap1 = capture->cap1; + usr_struct->cap2 = capture->cap2; + usr_struct->cap3 = capture->cap3; + usr_struct->cap4 = capture->cap4; + + usr_struct->period_low = capture->period_low; + usr_struct->period_high = capture->period_high; + usr_struct->period_total = capture->period_total; + usr_struct->status = capture->status; + + irq_cnt++; + usr_struct->irq_cnt = irq_cnt; +} + +void ecap_thread_entry(void *parg) +{ + usr_ecap_rx_struct usr_struct; + rt_ssize_t val = 0; + struct rt_device *ecap_dev = rt_device_find(ECAP_DEV); + + rt_kprintf("\n"); + + while (1) + { + if (RT_NULL != ecap_dev) { + rt_mutex_take(&ecap_lock, RT_WAITING_FOREVER); + rt_memset(&usr_struct, sizeof(usr_struct), 0); + val = rt_device_read (ecap_dev, 0, &usr_struct, sizeof(usr_struct)); + if (0 != val) { + rt_kprintf("========= %s 第%d包 ==========\n", ECAP_DEV, ((usr_ecap_rx_struct *)parg)->irq_cnt); + for (rt_int32_t i = 0; i < 4; i++) { + rt_kprintf("[cap%d]: %d\n", i+1, *(&usr_struct.cap1 + i)); + } + + rt_kprintf("[period_low]: %d\n", usr_struct.period_low); + rt_kprintf("[period_high]: %d\n", usr_struct.period_high); + rt_kprintf("[period_total]: %d\n", usr_struct.period_total); + rt_kprintf("[status]: %d\n", usr_struct.status); + + rt_kprintf("=============================\n"); + } + rt_mutex_release(&ecap_lock); + } + + rt_thread_mdelay(20); + } +} + +void ecap_o_thread_entry(void *parg) +{ + usr_ecap_rx_struct usr_struct; + rt_ssize_t val = 0; + struct rt_device *ecap_dev = rt_device_find(ECAP_OTHER_DEV); + + rt_kprintf("\n"); + + while (1) + { + if (RT_NULL != ecap_dev) { + rt_mutex_take(&ecap_lock, RT_WAITING_FOREVER); + rt_memset(&usr_struct, sizeof(usr_struct), 0); + val = rt_device_read (ecap_dev, 0, &usr_struct, sizeof(usr_struct)); + if (0 != val) { + rt_kprintf("========= %s 第%d包 ==========\n", ECAP_OTHER_DEV, ((usr_ecap_rx_struct *)parg)->irq_cnt); + for (rt_int32_t i = 0; i < 4; i++) { + rt_kprintf("[cap%d]: %d\n", i+1, *(&usr_struct.cap1 + i)); + } + + rt_kprintf("[period_low]: %d\n", usr_struct.period_low); + rt_kprintf("[period_high]: %d\n", usr_struct.period_high); + rt_kprintf("[period_total]: %d\n", usr_struct.period_total); + rt_kprintf("[status]: %d\n", usr_struct.status); + + rt_kprintf("=============================\n"); + } + rt_mutex_release(&ecap_lock); + } + + rt_thread_mdelay(20); + } +} + +int main(void) +{ + rt_thread_t tid; + + /* 打开 ecap 设备 */ + rt_err_t res = RT_ERROR; + usr_ecap_rx_struct usr_struct = {0}; + usr_ecap_rx_struct usr_o_struct; + struct rt_device *ecap_dev; + struct rt_ecap_callback ecap_cb = { + .callback = usr_ecap_rx_cb, + .user_data = &usr_struct, + }; + struct rt_ecap_callback ecap_o_cb = { + .callback = usr_ecap_rx_cb, + .user_data = &usr_o_struct, + }; + + rt_memset(&usr_struct, sizeof(usr_struct), 0); + rt_memset(&usr_o_struct, sizeof(usr_o_struct), 0); + rt_mutex_init(&ecap_lock, "elk", RT_IPC_FLAG_PRIO); + + ecap_dev = rt_device_find(ECAP_DEV); + if (RT_NULL != ecap_dev) { + res = rt_device_open(ecap_dev, RT_DEVICE_OFLAG_RDONLY); + if (res != RT_EOK) { + rt_kprintf("rt_device_open ecap failed\n"); + } + } + + if (res == RT_EOK) { + rt_device_control(ecap_dev, ECAP_CMD_SET_CALLBACK, &ecap_cb); + rt_device_control(ecap_dev, ECAP_CMD_ENABLE_IRQ, RT_NULL); + rt_device_control(ecap_dev, ECAP_CMD_ENABLE, RT_NULL); + } + + /* 创建一个线程来读取 ecap 设备 */ + tid = rt_thread_create("ecap", ecap_thread_entry, &usr_struct, 512, 6, 20); + if ((RT_NULL != tid) && (res == RT_EOK)) { + rt_thread_startup(tid); + } + + /* 第二个线程... */ + ecap_dev = rt_device_find(ECAP_OTHER_DEV); + if (RT_NULL != ecap_dev) { + res = rt_device_open(ecap_dev, RT_DEVICE_OFLAG_RDONLY); + if (res != RT_EOK) { + rt_kprintf("rt_device_open ecap failed\n"); + } + } + + if (res == RT_EOK) { + rt_device_control(ecap_dev, ECAP_CMD_SET_CALLBACK, &ecap_o_cb); + rt_device_control(ecap_dev, ECAP_CMD_ENABLE_IRQ, RT_NULL); + rt_device_control(ecap_dev, ECAP_CMD_ENABLE, RT_NULL); + } + + tid = rt_thread_create("ecap_o", ecap_o_thread_entry, &usr_o_struct, 512, 7, 20); + if ((RT_NULL != tid) && (res == RT_EOK)) { + rt_thread_startup(tid); + } + + /* 指示灯闪烁 */ + rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT); + + while (1) + { +/* rt_kprintf("\r\n led1_thread_entry running! \r\n"); */ + rt_pin_write(LED1_PIN, PIN_HIGH); + rt_thread_mdelay(1000); + rt_pin_write(LED1_PIN, PIN_LOW); + rt_thread_mdelay(1000); + } +} +``` + +#### 计算周期 + +ECAP 模块工作频率最高 **200M Hz** ,意味着1秒最高计数 : +2×1082\times10^82×10​8​​ + +驱动配置 **30*2 分频** ,所以实际工作频率为: +f=fSYSprescaler=2×10830×2Hzf=\frac{f_{SYS}}{prescaler}=\frac{2\times10^8}{30\times2}\text{Hz}f=​prescaler​​f​SYS​​​​=​30×2​​2×10​8​​​​Hz + +因此1秒需要计数: +PeriodTotal=2×108×30×2=12×109Period_{Total} = 2\times10^8\times30\times2=12\times10^9Period​Total​​=2×10​8​​×30×2=12×10​9​​ + +有这个等量关系,因此可得出测量周期的公式为: +T=PeriodTotal=12×10412×109=1105secondT=\frac{Period}{Total}=\frac{12\times10^4}{12\times10^9}=\frac{1}{10^5}\text{second}T=​Total​​Period​​=​12×10​9​​​​12×10​4​​​​=​10​5​​​​1​​second + +频率为: +fsrc=1T=105Hz=100kHzf_{src}=\frac{1}{T}=10^5\text{Hz}=100\text{kHz}f​src​​=​T​​1​​=10​5​​Hz=100kHz + +![信号源](./figures/ecap-程廷桢-01.jpg.webp) + +![方波周期采集](./figures/ecap-程廷桢-02.png.webp) + +## FAQ + +### 待补充 + +FAQ 章节暂无腾讯表格中打勾的源文章内容。