LPC175x and LPC176x Standard Peripheral Firmware Driver Library
Взяв я трохи поколупати платку з мікроконтролером LPC1768.
Спробував використати стандартну бібліотеку lpc17xx cmsis driver library, щоб трохи менше думати. Звісно, документацію на мікроконтролер все одно читати треба. Але здалося простіше викликати функцію для налаштування потрібної периферії, передавши їй кілька парметрів, ніж самому уважно комбінувати ті параметри в кілька регістрів. Та ще й, можливо, про порядок запису треба буде думати.
З часів інтелівського ApBUILDER-а — програми для генерації коду ініціалізації для MCS-51, MCS-196, … — я всю роботу з периферією завжди робив вручну. Тобто, я і до цього робив вручну, а тим ApBUILDER-ом спробував і відмовився. І знову лише вручну. Читаєш собі документацію на потрібний модуль та й потихеньку пишеш всі ці маски/зсуви. Константи для них у файлах від компілятора чи виробника мікроконтролерів є — і то добре.
А тут — на тобі… Вирішив полінуватися…
Про всяк випадок спочатку глянув, як воно компілюється та скільки займає місця. Додав до проекту весь каталог Drivers/source
, закоментував у makefile
рядок, який просить лінкера викидати код, що не викликається (точніше, секції, на які нема посилань)
і запустив компіляцію.
Почалося з того, що половина файлів бібліотеки відмовилися компілюватися, бо вони включають файл lpc17xx.h
, а в комплекті йде файл LPC17xx.h
. Різниця в регістрі літер може десь і неважлива, але не для gcc в Linux. Та це було поремонтувати неважко, я просто додав в каталог проекту файл lpc17xx.h
з таким вмістом:
// частина — lpc17xx.h. Поки-що так, щоб не патчити бібліотеку
#pragma once
#include "LPC17xx.h"
Після цього все зібралося. Розмір коду під сорок кілобайт за «зручності» бачити було незвично, але ж флеша пів мегабайта, хай вже. Може воно там купу корисної роботи робить, я ж ще детально не дивився.
А от кілька десятків попереджень компілятора в дусі
cast discards ‘__attribute__((const))’ qualifier from pointer target type [-Wcast-qual]
./LPC17xx/Drivers/source/lpc17xx_rtc.c:736:9: warning:
cast discards ‘__attribute__((noreturn))’ qualifier from pointer target type [-Wcast-qual]
./LPC17xx/Drivers/source/lpc17xx_rtc.c:758:2: warning:
comparison is always true due to limited range of data type [-Wtype-limits]
трохи налякали. За такими попередженнями іноді ховаються дуже неприємні помилки.
З останнім, про «comparison is always true» виявилося просто. Бібліотека має купу макросів для перевірки аргументів функцій на припустимий діапазон. Місцями аргументи оголошено як беззнакові цілі, а макрос перевіряє діапазон так:
#define PARAM_RTC_GPREG_CH(n) ((n>=0) && (n<=4))
На перше порівнювання компілятор і видає попередження. Нічого наче страшного, все працюватиме, але купа таких попереджень засмічує лог компілятора.
Місцями стоять зовсім незрозумілі приведення типів стоять. Наприклад, тут аргумент функції має кваліфікатор const, а для локальної змінної він знімається явним приведенням. При тому, що по цьому вказівникові однак дані лише читаються:
{
uint8_t *s = (uint8_t *) str;
while (*s)
{
Більш за все, початковий код виглядав так:
{
uint8_t *s = str;
while (*s)
{
Вказівник const void *str
дає можливість передати у функцію як масив char
, так і масив uint8_t
без приведення у точці виклику. Але для циклу передачі байтів необхідно мати вказівник на конкретний тип, тому заведено локальну змінну типу uint8_t *
. Про const
для цього вказівника автор забув. Отримав від компілятора попередження, що при ініціалізації змінної s
автоматичним приведенням відкидається кваліфікатор для об’єкта, на який вона вказуватиме:
При компіляції в режимі C++ тут взагалі буде помилка:
На простому рівні діагностики явне «ручне» приведення (uint8_t *)
зняло авторові коду попередження. Замів сміття під килимочок і заспокоївся. У мене ж код компілювався з ключем -Wcast-qual
:
Тобто і під килимочом сміття теж буде видно.
Проста правка — визначення вказівника потрібного типу — робить код коректним:
{
const uint8_t *s = (const uint8_t*)str;
В іншому місці ще веселіше:
* @brief Lookup Table of GPDMA Channel Number matched with
* GPDMA channel pointer
*/
const LPC_GPDMACH_TypeDef * pGPDMACh[8] = {
LPC_GPDMACH0, // GPDMA Channel 0
LPC_GPDMACH1, // GPDMA Channel 1
LPC_GPDMACH2, // GPDMA Channel 2
LPC_GPDMACH3, // GPDMA Channel 3
LPC_GPDMACH4, // GPDMA Channel 4
LPC_GPDMACH5, // GPDMA Channel 5
LPC_GPDMACH6, // GPDMA Channel 6
LPC_GPDMACH7 // GPDMA Channel 7
};
Тут визначається неконстантний масив вказівників на константні об’єкти типу LPC_GPDMACH_TypeDef
. Звісно, масив розміщено в оперативній пам’яті. 32 байти туди, 32 байти сюди — хто їх на 64 кілобайтах помітить?
Але далі у функції по вказівнику, вибраному з таблиці, йде модифікація об’єкта. Щоб компілятор дав це зробити, автор додав приведення вказівника зі зняттям кваліфікатора:
{
LPC_GPDMACH_TypeDef *pDMAch;
...
// Get Channel pointer
pDMAch = (LPC_GPDMACH_TypeDef *) pGPDMACh[GPDMAChannelConfig->ChannelNum];
...
// Clear DMA configure
pDMAch->DMACCControl = 0x00;
pDMAch->DMACCConfig = 0x00;
Знову помилку попереднього коду замасковано приведенням типу в наступному.
А треба ж було лишень правильно описати масив:
LPC_GPDMACH0, // GPDMA Channel 0
LPC_GPDMACH1, // GPDMA Channel 1
LPC_GPDMACH2, // GPDMA Channel 2
...
Тепер ми маємо константний масив (ляже у флеш) вказівників на неконстантні об’єкти. Ніякого приведення типу у функції робити вже не треба.
Ще одне місце, тут вже знімається кваліфікатор volatile
:
{
uint32_t pinnum_t = pinnum;
uint32_t pinselreg_idx = 2 * portnum;
uint32_t *pPinCon = (uint32_t *)&LPC_PINCON->PINSEL0;
if (pinnum_t >= 16) {
pinnum_t -= 16;
pinselreg_idx++;
}
*(uint32_t *)(pPinCon + pinselreg_idx) &= ~(0x03UL << (pinnum_t * 2));
*(uint32_t *)(pPinCon + pinselreg_idx) |= ((uint32_t)funcnum) << (pinnum_t * 2);
}
Для звертання до потрібного регістра зі структури береться вказівник на перший з однотипних регістрів, до нього додається індекс, по отриманому вказівникові проводиться запис.
Але тип LPC_PINCON_TypeDef
має біля всіх полів модифікатор __IO
(макрос, що замінюється на volatile
). Вказівник pPinCon
такого кваліфікатора не має. Попередження компілятора знову сховані за явним приведенням замість того, щоб відразу оголосити потрібний тип вказівника. І таких місць в бібліотеці багатенько.
Дану конкретну функцію я переписав так:
{
uint32_t pinbits = 2 * pinnum; // 2 bits per pin
__IO uint32_t *pPinCon = &LPC_PINCON->PINSEL0 + 2 * portnum; // 2 regs per port
if (pinbits >= 32) {
pinbits -= 32;
pPinCon++;
}
*pPinCon = (*pPinCon & ~(0x03UL << pinbits)) | ((uint32_t)funcnum << pinbits);
}
Використав той же модифікатор __IO
, яким позначено регістри PINSELx
. Ну і просто дещо скоротив запис, не змінивши функціонал.
Все одно це мені не подобається, бо тут нема перевірки на припустимі значення всіх трьох аргументів. Якщо задати занадто великі числа, то будуть попсовані як мінімум біти режиму інших виводів процесора. А можна і до зовсім іншої периферії дістати. Якщо вже робити перевірку аргументів, як це зроблено в інших файлах бібліотеки, то і тут треба б.
Я вважаю, що чим жорсткіші перевірки робитиме компілятор і чим менше при цьому попереджень — тим краще. Менше шансів отримати проблеми при редагуванні коду чи зміні версії компілятора. Тому так старанно і передивлявся бібліотеку. І ще дивитимуся.
p.s. Мало не забув. Прикладаю файл з моїм патчем до бібліотеки LPC175x and LPC176x CMSIS-Compliant Standard Peripheral Firmware Driver Library (GNU, Keil, IAR) (Jun 21, 2011), яка на даний момент знаходиться на сторінці підтримки мікроконтролерів NXP.
Купу повідомлень про порівнювання беззнакової змінної на невід’ємність не вичищав, бо для цього треба правити ще й відповідні макроси в h-файлах, глибше та уважніше копати. А я вже вирішив, що цю бібліотеку використовувати не буду, візьму лише h-файли, як джерело описів структур регістрів та констант номерів/масок бітів. Та й ті по дорозі перевіряттиму.
Тож лише вичистив попередження про приведення типів. Та й то — більше з дослідницькою метою.
Attached Files:
- cmsis-lpc17xx.patch.zip
Patch for "LPC175x and LPC176x CMSIS-Compliant Standard Peripheral Firmware Driver Library"